chore: checkpoint in-flight work across domains Sweep of accumulated uncommitted work on design/dex-p2p-architecture into one checkpoint (multiple sessions share this tree). Main areas: - anthemus: covenant-wiring follow-ups (engine, ExchangePanel), AgoraScreen seam - spv: ServerPool + p2p header transport (pool.ts, core/spv/*), wallet.ts wiring - walletconnect: responder/useConnect updates + connect-responder-compat test - wallet: send-tier-a rework + swap.ts, plus send-tier-a unit tests - asm: chipnet throughput/stress harness (scripts/chipnet-asm-*) + ASM_Native docs - app UI: screens/* and shell.css touch-ups; sat -> bits copy migration - docs: new snapshots (INDEX regenerated), Parameters (WalletConnect/SSH/ASM), KnowledgeBase, WALLET-COORDINATION changelog Checkpoint only; not a release. Secrets (Keys/, .chipnet-keys*, .env) remain gitignored and were verified absent from the staged set. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> @
88 lines
4.3 KiB
TypeScript
88 lines
4.3 KiB
TypeScript
#!/usr/bin/env -S npx tsx
|
|
/**
|
|
* spv-pool-multiconn-test.mts — exercise the REAL ServerPool (shells/app/src/core/spv/pool.ts)
|
|
* against live endpoints to prove the multi-connection + failover model:
|
|
* "multiple connections simultaneously; if one is down, connect to another; keep our Fulcrum too."
|
|
*
|
|
* Endpoints: local Fulcrum (own), one DEAD (connection-refused), 3 public.
|
|
* targetLive=3. We then force-close one live (non-local) connection and confirm
|
|
* the pool rotates to a spare and returns to 3 — with the new backoff preventing
|
|
* the dead endpoint from spinning. A hard 22s watchdog guarantees exit.
|
|
*/
|
|
import { ServerPool, type SpvEndpoint } from '../shells/app/src/core/spv/pool.ts';
|
|
|
|
const endpoints: SpvEndpoint[] = [
|
|
{ url: 'ws://127.0.0.1:50003', label: 'local-fulcrum', own: true },
|
|
{ url: 'wss://127.0.0.1:59999', label: 'DEAD(test)' },
|
|
{ url: 'wss://chipnet.bch.ninja:50004', label: 'bch.ninja' },
|
|
{ url: 'wss://chipnet.imaginary.cash:50004', label: 'imaginary' },
|
|
{ url: 'wss://tbch4.loping.net:62002', label: 'loping' },
|
|
];
|
|
|
|
const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));
|
|
const line = () => console.log('─'.repeat(64));
|
|
|
|
let pool: ServerPool;
|
|
// Hard watchdog: no matter what, print and exit — this can never run away again.
|
|
const watchdog = setTimeout(() => {
|
|
console.log('\n[watchdog] 22s elapsed — forcing exit');
|
|
try { pool?.stop(); } catch { /* */ }
|
|
process.exit(0);
|
|
}, 22_000);
|
|
|
|
async function main() {
|
|
let lastStatus = '';
|
|
pool = new ServerPool({
|
|
endpoints,
|
|
targetLive: 3,
|
|
quorum: 3,
|
|
connectTimeoutMs: 5000,
|
|
requestTimeoutMs: 8000,
|
|
onStatus: ({ live, dead }) => { // de-duped: log only when it changes
|
|
const s = `live=${live.length} dead=${dead.length}`;
|
|
if (s !== lastStatus) { console.log(` [status] ${s}`); lastStatus = s; }
|
|
},
|
|
onDivergence: ({ method }) => console.log(` [DIVERGENCE] ${method}`),
|
|
onTipSpread: ({ minHeight, maxHeight }) => console.log(` [tip-spread] Δ${maxHeight - minHeight}`),
|
|
});
|
|
|
|
line(); console.log('1) start() — open 3 simultaneous connections (skip dead, keep Fulcrum)'); line();
|
|
await pool.start();
|
|
await sleep(2500);
|
|
|
|
const live = pool.liveUrls();
|
|
console.log(`\nLive connections (${live.length}):`);
|
|
for (const [url, h] of Object.entries(pool.tips())) console.log(` ✓ ${url} tip=${h}`);
|
|
const localLive = live.some((u) => u.includes('127.0.0.1:50003'));
|
|
const deadLive = live.some((u) => u.includes('59999'));
|
|
console.log(` local Fulcrum present: ${localLive ? 'YES' : 'NO'} | dead skipped: ${!deadLive ? 'YES' : 'NO'}`);
|
|
|
|
line(); console.log('2) quorum read — blockchain.block.header @300000 (servers must agree)'); line();
|
|
try {
|
|
const hdr = await pool.request<string>('blockchain.block.header', [300000], { quorum: 3 });
|
|
console.log(` majority header ${hdr.length / 2}B: ${hdr.slice(0, 24)}…`);
|
|
} catch (e) { console.log(` quorum read failed: ${(e as Error).message}`); }
|
|
|
|
line(); console.log('3) FAILOVER — force-close one live (non-local) connection'); line();
|
|
const conns = (pool as unknown as { conns: Map<string, { ep: SpvEndpoint; ws: WebSocket; alive: boolean }> }).conns;
|
|
const victim = [...conns.values()].find((c) => c.alive && !c.ep.own);
|
|
if (victim) { console.log(` closing: ${victim.ep.url}`); victim.ws.close(); }
|
|
else console.log(' (no non-local live connection found)');
|
|
await sleep(6000); // allow drop() → fill() to rotate to the spare candidate
|
|
|
|
const live2 = pool.liveUrls();
|
|
console.log(`\nLive connections after failover (${live2.length}):`);
|
|
for (const [url, h] of Object.entries(pool.tips())) console.log(` ✓ ${url} tip=${h}`);
|
|
|
|
line(); console.log('=== SUMMARY ==='); line();
|
|
console.log(` simultaneous connections: ${live.length}/3`);
|
|
console.log(` own Fulcrum kept as a peer: ${localLive ? 'YES' : 'NO'}`);
|
|
console.log(` dead endpoint auto-skipped: ${!deadLive ? 'YES' : 'NO'}`);
|
|
console.log(` refilled to 3 after a live drop: ${live2.length >= 3 ? 'YES' : 'NO (' + live2.length + ')'}`);
|
|
console.log(` local Fulcrum still connected: ${live2.some((u) => u.includes('127.0.0.1:50003')) ? 'YES' : 'NO'}`);
|
|
|
|
clearTimeout(watchdog);
|
|
pool.stop();
|
|
}
|
|
|
|
main().then(() => process.exit(0)).catch((e) => { console.error('TEST ERROR:', e?.message ?? e); clearTimeout(watchdog); try { pool?.stop(); } catch {} process.exit(1); });
|