aboutsummaryrefslogtreecommitdiffstats
path: root/tools/electrum-ws.test.js
blob: f743819942ca112a0a15c434fe88eda659726d40 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
// Verify the regtest electrum-ws bridge gives realtime push: subscribe to an
// address scripthash over ws://localhost:50003, fund it, expect a notification.
// Run (stack + ews up): bun test tools/electrum-ws.test.js
// Integration test: skips (with a note) when no bridge listens on :50003 — the
// WS<->TCP bridge (coinos-server/electrum-ws) in front of a regtest electrs.
import { test, expect } from 'bun:test';
import { $ } from 'bun';
import { p2wpkh } from '@scure/btc-signer/payment';
import * as btc from '@scure/btc-signer';
import { secp256k1 } from '@noble/curves/secp256k1';
import { sha256 } from '@noble/hashes/sha2';
import { bytesToHex } from '@noble/hashes/utils';

const URL = 'ws://localhost:50003';
const REGTEST = { ...btc.TEST_NETWORK, bech32: 'bcrt' };
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
const bcli = (...a) => $`docker exec bc bitcoin-cli -regtest -rpcuser=admin1 -rpcpassword=123 ${a}`.text();

// open the socket up front so an absent bridge becomes a skip, not a failure
const ws = await new Promise((res) => {
  const s = new WebSocket(URL);
  const t = setTimeout(() => { s.close(); res(null); }, 3000);
  s.onopen = () => { clearTimeout(t); res(s); };
  s.onerror = () => { clearTimeout(t); res(null); };
});
if (!ws) console.log(`SKIP: no Electrum-over-WebSocket bridge at ${URL} (start the regtest ews bridge to run this)`);

test.skipIf(!ws)('realtime scripthash push over the WS bridge', async () => {
  const pub = secp256k1.getPublicKey(secp256k1.utils.randomPrivateKey(), true);
  const pay = p2wpkh(pub, REGTEST);
  const shHex = bytesToHex(sha256(pay.script).reverse()); // electrum scripthash (reversed)
  console.log('address', pay.address, '\nscripthash', shHex);

  let gotInitial = false, gotPush = false;
  ws.onmessage = (ev) => {
    for (const line of String(ev.data).split('\n')) {
      if (!line.trim()) continue;
      let m; try { m = JSON.parse(line); } catch { continue; }
      if (m.id === 2) { gotInitial = true; console.log('subscribe ok (initial status:', m.result, ')'); }
      if (m.method === 'blockchain.scripthash.subscribe') { gotPush = true; console.log('PUSH notification received:', JSON.stringify(m.params)); }
    }
  };
  ws.send(JSON.stringify({ id: 1, method: 'server.version', params: ['test', '1.4'] }) + '\n');
  ws.send(JSON.stringify({ id: 2, method: 'blockchain.scripthash.subscribe', params: [shHex] }) + '\n');

  await sleep(1500);
  expect(gotInitial).toBe(true);

  console.log('funding the address…');
  await bcli('-rpcwallet=coinos', 'sendtoaddress', pay.address, '0.005');
  await bcli('generatetoaddress', '1', (await bcli('-rpcwallet=coinos', 'getnewaddress')).trim());

  for (let i = 0; i < 20 && !gotPush; i++) await sleep(500);
  ws.close();
  expect(gotPush).toBe(true);
}, 30000);