Skip to content

sync-kernel.mjs wasm-loader rewrite is not idempotent and corrupts its own output #8

Description

@melvincarvalho

tools/sync-kernel.mjs rewrites the wasm loader so it resolves the binary relative to the module rather than the page:

const loader = (await readFile(new URL('secp-wasm.js', WASM), 'utf8'))
  .replace(/loadSecpWasm\(url = [^)]*\)/, "loadSecpWasm(url = new URL('./secp256k1.wasm', import.meta.url).href)");

[^)]* stops at the first ). The replacement text itself contains one, inside new URL(...). So the pattern matches its own output, and re-running the tool against an already-transformed loader corrupts it:

input : export async function loadSecpWasm(url = new URL('./secp256k1.wasm', import.meta.url).href) {
output: export async function loadSecpWasm(url = new URL('./secp256k1.wasm', import.meta.url).href).href) {
CORRUPTED: true

The result is a syntax error — the generated packages/kernel/wasm/secp-wasm.js would fail to parse, taking the whole package with it.

Why it bites in practice

WASM_DIR defaults to ../../bitcoin-kernel.github.io/engine/wasm/, a sibling checkout that is not present in a plain clone of this repo. The natural workaround when regenerating — point WASM_DIR at the existing packages/kernel/wasm/, which is right there and contains exactly the expected filenames — is precisely the case that corrupts the output. The tool gives no error; it writes a broken file and prints its usual success line.

This is currently latent because the current packages/kernel/wasm/secp-wasm.js is correct. It becomes real the moment someone regenerates without the sibling checkout, which is the likely path for anyone bumping the pin.

Found while preparing #7, which is why that PR copies the codec files verbatim instead of re-running the tool.

Fix

Make the rewrite idempotent — skip it when the loader is already in the target form:

const TARGET = "loadSecpWasm(url = new URL('./secp256k1.wasm', import.meta.url).href)";
let loader = await readFile(new URL('secp-wasm.js', WASM), 'utf8');
if (!loader.includes(TARGET)) {
  loader = loader.replace(/loadSecpWasm\(url = [^)]*\)/, TARGET);
}

Worth also failing loudly if neither the source nor the target form is found, rather than silently emitting an unrewritten loader that resolves the wasm against the page URL.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions