aboutsummaryrefslogtreecommitdiffstats
path: root/tools/ark-reconcile-race.test.js
blob: 165eb58da817c61ded3a8c04ac597626766fd69d (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// bun test tools/ark-reconcile-race.test.js — a reconcile pass must not hand a
// coin back to spendable after a payment took it mid-pass (double-tapped zap).
import { test, expect, mock } from 'bun:test';
import * as proto from '../src/ark/proto.js';
let release;
const gate = new Promise((r) => { release = r; });
mock.module('../src/ark/proto.js', () => ({ ...proto, getVtxoStatus: async () => { await gate; return proto.VTXO_STATE_SPENDABLE; } }));
const { ArkManager } = await import('../src/ark/manager.js');
test('a coin taken mid-reconcile stays taken', async () => {
  const m = Object.create(ArkManager.prototype);
  m.state = { vtxos: [{ id: 'a', state: 'spendable', amountSat: 85, bytes: 'x' }, { id: 'b', state: 'pending', amountSat: 5, bytes: 'x' }], actions: [], scheduled: [] };
  m.arkUrl = 'x'; m._decoded = () => ({ point: { raw: new Uint8Array(36) } }); m._keyForVtxo = () => ({ privkey: new Uint8Array(32) });
  m._save = () => {}; m._movement = () => {};
  const p = m.reconcile();
  await new Promise((r) => setTimeout(r, 10));
  m.state.vtxos[0].state = 'pending'; // the zap takes coin a
  m.state.actions.push({ type: 'ln-pay', step: 'created', parts: [{ inputId: 'a' }] });
  release();
  await p;
  expect(m.state.vtxos[0].state).toBe('pending');
  expect(m.state.vtxos[1].state).toBe('spendable'); // a real orphan is still freed
});