// node backfill-chains.js [--dry-run]: for every stored entry whose funding sources lack `chain` (entries recorded // before the lower bound was added on 2026-09-22), read each non-operator source's blocks up to the entry's received_at // from the node and store them with the source. Blocks before that moment are immutable, so a later read gives the same // chain an entry-time read would have. Never changes amount_raw, first_at or receives. Idempotent. const path = require('path'); const L = require('./ladder'); const DATA = process.env.DATA_DIR || path.join(__dirname, 'data'); const ENTRIES = path.join(DATA, 'entries.json'); const RPC = process.env.NANO_RPC || 'http://127.0.0.1:7076'; const OWN_ADDRESSES = process.env.OWN_ADDRESSES || '/var/lib/gambit/workspace/api/data/own-addresses.json'; const LADDER_ADDRESS = process.env.LADDER_ADDRESS || 'nano_1xug1q5t7nxoj3ywwzokiea9jz8fq8qfgzp8pbyfr3co3e5xgj755uofu8ue'; const dry = process.argv.includes('--dry-run'); async function rpc(body) { const r = await fetch(RPC, { method: 'POST', body: JSON.stringify(body) }); return r.json(); } (async () => { const own = new Set([LADDER_ADDRESS]); try { for (const a of (L.readJson(OWN_ADDRESSES, { addresses: [] }).addresses || [])) own.add(a.address); } catch {} const all = L.readJson(ENTRIES, {}); let n = 0, skipped = 0; for (const [rid, byAddr] of Object.entries(all)) for (const [addr, e] of Object.entries(byAddr)) { if (!e.funding || !Array.isArray(e.funding.sources) || !e.funding.sources.length) continue; for (const s of e.funding.sources) { if (own.has(s.account) || s.chain) continue; const chain = await L.sourceChain(rpc, s.account, Date.parse(e.received_at)).catch(err => { console.log('round', rid, addr.slice(0, 16), 'source', s.account.slice(0, 16), 'read failed:', err.message); return null; }); if (!chain) { skipped++; continue; } const sent = chain.blocks ? chain.blocks.filter(b => b.subtype === 'send' && b.account === addr).length : null; console.log('round', rid, addr.slice(0, 16), 'source', s.account.slice(0, 16), '->', chain.truncated ? 'truncated at ' + chain.block_count : chain.block_count + ' block(s), ' + sent + ' send(s) to the entrant, stored receives ' + (s.receives || []).length); if (!dry) s.chain = chain; n++; } if (!dry) e.funding.chains_backfilled_at = new Date().toISOString(); } if (!dry && n) L.writeJsonAtomic(ENTRIES, all); console.log(dry ? 'dry run,' : 'wrote', n, 'source chain(s) backfilled,', skipped, 'skipped'); })();