From 90d0396819ddfbabb8b7f45e8fc3ac0992a6b014 Mon Sep 17 00:00:00 2001 From: Adam Soltys Date: Wed, 7 Oct 2026 08:21:47 -0700 Subject: ark tests: shared aliceReady() keeps the bark funding wallet usable scheduled-refresh-test mines a whole vtxo lifetime, which expires alice's coins; every test run after it funded nothing (spendableSat 0) or was handed an expired coin (receive-test). The helper refreshes expired coins and boards more when alice runs low, and the funding tests call it before paying. Co-Authored-By: Claude Opus 5.5 --- tools/ark/alice.js | 46 +++++++++++++++++++++++++++++++++++++ tools/ark/ln-e2e-test.js | 4 ++-- tools/ark/ln-test.js | 4 ++-- tools/ark/manager-test.js | 4 ++-- tools/ark/receive-test.js | 5 ++-- tools/ark/scheduled-refresh-test.js | 17 +++----------- 6 files changed, 58 insertions(+), 22 deletions(-) create mode 100644 tools/ark/alice.js (limited to 'tools') diff --git a/tools/ark/alice.js b/tools/ark/alice.js new file mode 100644 index 0000000..9c33661 --- /dev/null +++ b/tools/ark/alice.js @@ -0,0 +1,46 @@ +// alice = the bark CLI wallet the regtest ark tests fund from. +// +// scheduled-refresh-test mines a coin's whole lifetime, which expires alice's +// coins too — an expired vtxo can be refreshed but not sent, so any test that +// runs after it would fund nothing ("spendableSat 0") or hand out an expired +// coin. Every funding test also drains her a little. aliceReady() refreshes +// expired coins back to life and boards more when she runs low; a no-op when +// she's healthy. + +import { execSync } from 'node:child_process'; + +export const ALICE = `${process.env.HOME}/bark/target/debug/bark --datadir ${process.env.HOME}/ark-regtest/alice`; +const BCLI = 'docker exec bc bitcoin-cli -rpcwallet=coinos'; +const sh = (cmd) => execSync(cmd, { shell: '/bin/bash' }).toString().trim(); +const mine = (n) => sh(`${BCLI} generatetoaddress ${n} $(${BCLI} getnewaddress) >/dev/null`); +const sleep = (ms) => new Promise((r) => setTimeout(r, ms)); + +const LOW_SAT = 200_000; +const TOP_UP_BTC = '0.01'; + +export const aliceBalance = () => JSON.parse(sh(`${ALICE} -q balance`)).spendable_sat; +const expiredCoins = () => { + const tip = Number(sh(`${BCLI} getblockcount`)); + return JSON.parse(sh(`${ALICE} -q vtxos`)).filter((v) => v.expiry_height <= tip); +}; + +export async function aliceReady() { + if (expiredCoins().length) { + console.log(' … refreshing alice\'s expired coins first'); + sh(`${ALICE} -q refresh --all >/dev/null`); + // the round's output turns spendable once its funding tx confirms and the + // indexer has seen it — keep mining until bark agrees + for (let i = 0; i < 20 && (expiredCoins().length || !aliceBalance()); i++) { mine(1); await sleep(1500); } + } + if (aliceBalance() < LOW_SAT) { + console.log(` … topping alice up (board ${TOP_UP_BTC} BTC)`); + const { address } = JSON.parse(sh(`${ALICE} -q onchain address`)); + sh(`${BCLI} sendtoaddress ${address} ${TOP_UP_BTC} >/dev/null`); + mine(1); + for (let i = 0; i < 20; i++) { + try { sh(`${ALICE} -q board --all >/dev/null 2>&1`); break; } catch { await sleep(1500); } // wallet sync lag + } + const before = aliceBalance(); + for (let i = 0; i < 20 && aliceBalance() <= before; i++) { mine(1); await sleep(1500); } + } +} diff --git a/tools/ark/ln-e2e-test.js b/tools/ark/ln-e2e-test.js index 4d31b56..088dcb0 100644 --- a/tools/ark/ln-e2e-test.js +++ b/tools/ark/ln-e2e-test.js @@ -18,11 +18,10 @@ import { sha256 } from '@noble/hashes/sha256'; import { ArkManager } from '../../src/ark/manager.js'; import { lnReceiveFee } from '../../src/ark/lightning.js'; +import { ALICE, aliceReady } from './alice.js'; const ARK = process.env.ARK_URL || 'http://127.0.0.1:3535'; const ESPLORA = process.env.ESPLORA_URL || 'http://127.0.0.1:30002'; -const BARK = `${process.env.HOME}/bark/target/debug/bark`; -const ALICE = `${BARK} --datadir ${process.env.HOME}/ark-regtest/alice`; const CLC = 'docker exec clc lightning-cli --regtest'; const sleep = (ms) => new Promise((r) => setTimeout(r, ms)); @@ -45,6 +44,7 @@ const account = HDKey.fromMasterSeed(mnemonicToSeedSync(mnemonic)).derive("m/86' const mgr = await new ArkManager({ account, storage, arkUrl: ARK, esploraUrl: ESPLORA }).init(); console.log('\n[0] fund manager from alice'); +await aliceReady(); sh(`${ALICE} -q send ${mgr.address()} "25000 sat"`); for (let i = 0; i < 10 && !mgr.balance().spendableSat; i++) { await mgr.sync(); await sleep(1500); } check('funded 25000 sat', mgr.balance().spendableSat === 25000, JSON.stringify(mgr.balance())); diff --git a/tools/ark/ln-test.js b/tools/ark/ln-test.js index c76d914..87e29d4 100644 --- a/tools/ark/ln-test.js +++ b/tools/ark/ln-test.js @@ -19,11 +19,10 @@ import { secp256k1 } from '@noble/curves/secp256k1'; import { ArkManager } from '../../src/ark/manager.js'; import { decodeBolt11 } from '../../src/ark/lightning.js'; +import { ALICE, aliceReady } from './alice.js'; const ARK = process.env.ARK_URL || 'http://127.0.0.1:3535'; const ESPLORA = process.env.ESPLORA_URL || 'http://127.0.0.1:30002'; -const BARK = `${process.env.HOME}/bark/target/debug/bark`; -const ALICE = `${BARK} --datadir ${process.env.HOME}/ark-regtest/alice`; const sleep = (ms) => new Promise((r) => setTimeout(r, ms)); const sh = (cmd) => execSync(cmd, { shell: '/bin/bash' }).toString().trim(); @@ -89,6 +88,7 @@ const account = HDKey.fromMasterSeed(mnemonicToSeedSync(mnemonic)).derive("m/86' const mgr = await new ArkManager({ account, storage, arkUrl: ARK, esploraUrl: ESPLORA }).init(); console.log('\n[0] fund manager from alice'); +await aliceReady(); sh(`${ALICE} -q send ${mgr.address()} "25000 sat"`); for (let i = 0; i < 10 && !mgr.balance().spendableSat; i++) { await mgr.sync(); await sleep(1500); } check('funded 25000 sat', mgr.balance().spendableSat === 25000, JSON.stringify(mgr.balance())); diff --git a/tools/ark/manager-test.js b/tools/ark/manager-test.js index 74a1c5c..6cbc506 100644 --- a/tools/ark/manager-test.js +++ b/tools/ark/manager-test.js @@ -17,11 +17,10 @@ import { hex } from '@scure/base'; import { ArkManager } from '../../src/ark/manager.js'; import { decodeVtxo, vtxoBytesFromStr } from '../../src/ark/proto.js'; import { validateVtxo } from '../../src/ark/validate.js'; +import { ALICE, aliceReady } from './alice.js'; const ARK = process.env.ARK_URL || 'http://127.0.0.1:3535'; const ESPLORA = process.env.ESPLORA_URL || 'http://127.0.0.1:30002'; -const BARK = `${process.env.HOME}/bark/target/debug/bark`; -const ALICE = `${BARK} --datadir ${process.env.HOME}/ark-regtest/alice`; const BCLI = `docker exec bc bitcoin-cli -rpcwallet=coinos`; const sleep = (ms) => new Promise((r) => setTimeout(r, ms)); @@ -52,6 +51,7 @@ let mgr = await newManager(); // --- 1. receive with full validation --- console.log('\n[1] receive'); const addr = mgr.address(); +await aliceReady(); sh(`${ALICE} -q send ${addr} "20000 sat"`); for (let i = 0; i < 10 && !mgr.balance().spendableSat; i++) { await mgr.sync(); await sleep(1500); } check('received 20000 sat', mgr.balance().spendableSat === 20000, JSON.stringify(mgr.balance())); diff --git a/tools/ark/receive-test.js b/tools/ark/receive-test.js index 1e6adbe..ac916ad 100644 --- a/tools/ark/receive-test.js +++ b/tools/ark/receive-test.js @@ -17,11 +17,11 @@ import { getArkInfo, handshake, encodeAddress, decodeAddress, blindMailboxId, readMailbox, } from '../../src/ark/proto.js'; +import { ALICE as ALICE_CLI, aliceReady } from './alice.js'; const ARK = process.env.ARK_URL || 'http://127.0.0.1:3535'; const ESPLORA = process.env.ESPLORA_URL || 'http://127.0.0.1:30002'; -const BARK = `${process.env.HOME}/bark/target/debug/bark`; -const ALICE = `${BARK} --datadir ${process.env.HOME}/ark-regtest/alice -q`; +const ALICE = `${ALICE_CLI} -q`; const AMOUNT_SAT = 12345; const MNEMONIC = 'rug rebuild abstract kiwi rifle vast food robust rifle spirit dilemma stumble'; @@ -57,6 +57,7 @@ console.log('round-trip decode:', decodeAddress(address)); // --- 3. pay it from the bark CLI --- console.log(`\npaying ${AMOUNT_SAT} sat from alice (bark CLI)...`); +await aliceReady(); execSync(`${ALICE} send ${address} "${AMOUNT_SAT} sat"`, { stdio: 'inherit' }); // --- 4. read mailbox, decode, validate --- diff --git a/tools/ark/scheduled-refresh-test.js b/tools/ark/scheduled-refresh-test.js index bb030c4..c406514 100644 --- a/tools/ark/scheduled-refresh-test.js +++ b/tools/ark/scheduled-refresh-test.js @@ -15,11 +15,10 @@ import { wordlist } from '@scure/bip39/wordlists/english'; import { HDKey } from '@scure/bip32'; import { ArkManager } from '../../src/ark/manager.js'; +import { ALICE, aliceReady } from './alice.js'; const ARK = process.env.ARK_URL || 'http://127.0.0.1:3535'; const ESPLORA = process.env.ESPLORA_URL || 'http://127.0.0.1:30002'; -const BARK = `${process.env.HOME}/bark/target/debug/bark`; -const ALICE = `${BARK} --datadir ${process.env.HOME}/ark-regtest/alice`; const BCLI = `docker exec bc bitcoin-cli -rpcwallet=coinos`; const sleep = (ms) => new Promise((r) => setTimeout(r, ms)); @@ -44,18 +43,8 @@ const account = HDKey.fromMasterSeed(mnemonicToSeedSync(mnemonic)).derive("m/86' const mgr = await new ArkManager({ account, storage, arkUrl: ARK, esploraUrl: ESPLORA }).init(); // This test mines a coin's whole lifetime in step 5, which also expires the -// funding wallet's coins — an expired vtxo can be refreshed but not sent, so -// re-running against a chain that already ran it would fund nothing. -const fundingReady = () => { - const alice = JSON.parse(sh(`${ALICE} -q vtxos`)); - const tip = tipHeight(); - if (!alice.some((v) => v.expiry_height <= tip)) return; - console.log(' … refreshing the funding wallet\'s expired coins first'); - sh(`${ALICE} refresh --all >/dev/null`); - mine(3); - sh(`${ALICE} -q balance >/dev/null`); -}; -fundingReady(); +// funding wallet's coins — aliceReady() refreshes them on the next run. +await aliceReady(); const receive = async (sat) => { const before = mgr.balance().spendableSat; -- cgit v1.2.3