#!/usr/bin/env node /** * spv-p2p-headersync-chipnet.mjs — Phase 1a end-to-end demo. * * Anchors at a real chipnet checkpoint (from test/fixtures/spv-chipnet.json) * and pulls all headers up to the live tip via getheaders/headers from * ≥1 chipnet peer, feeding the existing HeaderChain (PoW + link + anchor * validated on every append). * * Proves the transport-independent core actually walks the chain end-to-end, * not just handshakes. Runs against real network — not part of vitest. */ // Run via tsx so the TS imports resolve without a build step: // node --import tsx scripts/spv-p2p-headersync-chipnet.mjs // or: npx tsx scripts/spv-p2p-headersync-chipnet.mjs import dns from 'node:dns/promises'; import { NETWORKS } from '../shells/app/src/core/spv/p2p/wire.ts'; import { NodeNetTransport } from '../shells/app/src/core/spv/p2p/transport-node.ts'; import { syncHeaders } from '../shells/app/src/core/spv/p2p/header-sync.ts'; // Real chipnet checkpoint from test/fixtures/spv-chipnet.json (2026-07-26). // syncHeaders() re-derives the hash from headerHex and passes it into // HeaderChain, which pins the anchor. const ANCHOR = { height: 315684, headerHex: '0000002021cd2665f2a91a793add02d2722b3103ef24a6d796d79244c125be4b00000000029c1f97ee4286151c0440ee3f35cfbbe9369a7998b938aa96b6ed48c78c87050d26616a15b66a1c12b6850c', }; async function resolveSeeds(seeds, per = 3) { const out = new Set(); for (const s of seeds) { try { const ips = await dns.resolve4(s); for (const ip of ips.slice(0, per)) out.add(ip); } catch { /* seed unreachable, skip */ } } return [...out]; } async function main() { console.log(`Phase 1a end-to-end — chipnet header sync from checkpoint ${ANCHOR.height}\n`); const hosts = await resolveSeeds(NETWORKS.chipnet.seeds, 3); console.log(`Resolved ${hosts.length} chipnet peers: ${hosts.join(', ')}\n`); if (hosts.length === 0) { console.error('no peers resolved'); process.exit(1); } const t0 = Date.now(); const result = await syncHeaders({ transport: new NodeNetTransport(), network: NETWORKS.chipnet, anchor: ANCHOR, peerHosts: hosts, minPeers: Math.min(2, hosts.length), maxHeaders: 5000, onProgress: ({ height, peers, batch }) => console.log(` batch ${batch}: height=${height} peers-live=${peers}`), onDropped: (host, reason) => console.log(` ✗ dropped ${host}: ${reason}`), }); const dt = Date.now() - t0; console.log(`\n✅ synced ${result.chain.height - ANCHOR.height} headers in ${dt} ms`); console.log(` tip height = ${result.chain.height}`); console.log(` tip hash = ${result.chain.tip?.hash}`); console.log(` cumulative work = ${result.chain.tip?.cumWork.toString()}`); console.log(` leader = ${result.syncedFromHost}`); console.log(` peers handshook = ${result.peersHandshook.length} spread=${result.tipSpread}`); for (const p of result.peersHandshook) console.log(` • ${p.host} ${p.userAgent} h=${p.startHeight}`); console.log(` batches = ${result.batches}`); } main().catch((e) => { console.error('FAILED:', e?.message ?? e); if (e?.stack) console.error(e.stack); process.exit(1); });