wasm-smoke.cjs raw

   1  // Smoke test for the browser build: load the wasm light client, sync a
   2  // regtest chain through a real relay and bridge, then match a script locally.
   3  //
   4  //   node --experimental-websocket wasm-smoke.cjs <nostrino.wasm> <gateway.uri> <script-hex>
   5  //
   6  // Node 21+ exposes WebSocket globally; Node 20 needs --experimental-websocket.
   7  'use strict';
   8  
   9  const fs = require('fs');
  10  const path = require('path');
  11  
  12  const [wasmPath, uriFile, scriptHex] = process.argv.slice(2);
  13  if (!wasmPath || !uriFile || !scriptHex) {
  14    console.error('usage: wasm-smoke.cjs <nostrino.wasm> <gateway.uri> <script-hex>');
  15    process.exit(2);
  16  }
  17  if (typeof globalThis.WebSocket === 'undefined') {
  18    console.error('this Node has no global WebSocket; run with --experimental-websocket');
  19    process.exit(2);
  20  }
  21  
  22  const execPath = process.env.TINYGO_WASM_EXEC || '/usr/local/lib/tinygo/targets/wasm_exec.js';
  23  if (!fs.existsSync(execPath)) {
  24    console.error('wasm_exec.js not found at ' + execPath + '; set TINYGO_WASM_EXEC');
  25    process.exit(2);
  26  }
  27  
  28  globalThis.require = require;
  29  globalThis.fs = fs;
  30  globalThis.performance = {
  31    now() {
  32      const [sec, nsec] = process.hrtime();
  33      return sec * 1000 + nsec / 1e6;
  34    },
  35  };
  36  require(execPath);
  37  
  38  const uri = fs.readFileSync(uriFile, 'utf8').trim();
  39  const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
  40  
  41  (async () => {
  42    const go = new Go();
  43    const result = await WebAssembly.instantiate(fs.readFileSync(wasmPath), go.importObject);
  44    go.run(result.instance);
  45  
  46    for (let i = 0; i < 500 && !globalThis.nostrino; i++) await sleep(10);
  47    if (!globalThis.nostrino) {
  48      console.error('FAIL: the wasm module did not install the nostrino global');
  49      process.exit(1);
  50    }
  51    // First prove cancellation: start a sync, cancel it, and require the module
  52    // to settle as cancelled rather than hanging on a read.
  53    globalThis.nostrino.sync(uri, 'regtest');
  54    // Cancel in the same turn: the sync goroutine either has not started, is
  55    // connecting, or is mid-sync. All three must settle as cancelled.
  56    globalThis.nostrino.cancel();
  57    let cancelled = false;
  58    let lastCancel = '';
  59    for (let i = 0; i < 100; i++) {
  60      lastCancel = globalThis.nostrino.status();
  61      const st = JSON.parse(lastCancel);
  62      if (!st.running) {
  63        cancelled = st.status === 'cancelled';
  64        break;
  65      }
  66      await sleep(50);
  67    }
  68    if (!cancelled) {
  69      console.error('FAIL: cancel did not settle as cancelled; last status: ' + lastCancel);
  70      process.exit(1);
  71    }
  72    console.log('cancel: ok');
  73  
  74    // Then a full sync.
  75    globalThis.nostrino.sync(uri, 'regtest');
  76    let last = '';
  77    for (let i = 0; i < 900; i++) {
  78      const raw = globalThis.nostrino.status();
  79      if (raw !== last) {
  80        console.log(raw);
  81        last = raw;
  82      }
  83      const st = JSON.parse(raw);
  84      if (!st.running) {
  85        if (st.status !== 'done') {
  86          console.error('FAIL: sync status ' + st.status + ': ' + st.error);
  87          process.exit(1);
  88        }
  89        if (st.transport !== 'websocket') {
  90          console.error('FAIL: expected the websocket data plane, got ' + st.transport);
  91          process.exit(1);
  92        }
  93        if (st.tip === 0 || st.filters === 0) {
  94          console.error('FAIL: sync produced no tip or filters');
  95          process.exit(1);
  96        }
  97        const matches = JSON.parse(globalThis.nostrino.match(scriptHex));
  98        console.log('matches: ' + JSON.stringify(matches));
  99        if (!Array.isArray(matches) || matches.length !== 1 || matches[0] !== 7) {
 100          console.error('FAIL: expected the watched script at height 7');
 101          process.exit(1);
 102        }
 103        console.log('PASS: wasm synced tip ' + st.tip + ' with ' + st.filters + ' filters in ' + st.elapsed);
 104        process.exit(0);
 105      }
 106      await sleep(100);
 107    }
 108    console.error('FAIL: timeout waiting for sync');
 109    process.exit(1);
 110  })().catch((err) => {
 111    console.error('FAIL:', err);
 112    process.exit(1);
 113  });
 114