igneum/tools/prove-fixtures/complete-export.mjs
igneum-labs fa6bbb0d5f txgen: real transactions on the devnet, the first non-empty shard proven and paid, the exporter's block structure fixed
tools/txgen/run.mjs funds generated wallets from the devnet dev-fee key and sends transfers at a steady rate through
one node (nonce tracking from the pool's pending nonce, two-strike drop verdict, pool back-pressure counted as
deferred, spend cap, clean stop, summary JSON); tools/txgen/proving-watch.mjs watches the proving layer and builds the
per-block report. Two runs through the Mac node: 2,275 sent at 2/s, 2,161 included at 1.86/s (run 1), 1,650 sent,
1,633 included at 1.71/s with no failure (run 2, fixed code). Block 72704 shard 0 (29 transfers, 5,800 pgas) proven on
PC 2 in 34 s, verified on the Mac in 0.297 s, paid 1.7623 IGN.

Block 72803 (seven skipped copies, no executed transaction) failed the native-execution veto: the exporter rebuilt the
including blocks from an export that names no block, position or skipped copy's miner, sorting skipped copies out
of their block, merging consecutive blocks of one miner, dropping empty blocks (the node counts them in the link's
block index) and guessing the zero address. blocks_of now rebuilds from the 0.3.9 export's "blocks", "block" and
"position" fields (the fork change on vendor/igneum-node-txgen branch txgen-export), keeps an old export in its
order and refuses a skipped-only block without a miner. Fixtures block-72803-skipped-copies and
block-72854-empty-block-first with the node's shard plan beside each; the fixture test now checks the cut's links,
roots, gas, pgas and counts against the node's plan (shown failing on the old 72854 cut). No change under core/.

Bench-log entry and evidence rows 15 and 21.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-05 16:18:32 +00:00

53 lines
4.4 KiB
JavaScript

#!/usr/bin/env node
// Completes an `igneum_exportSegments` file from a node before 0.3.9 with the block structure the 0.3.9 export carries
// (5 October 2026, after devnet block 72803 failed the native-execution veto): per segment "blocks" (the mergeset in
// order, empty blocks included: hash, miner, blue, txCount) and per transaction entry "block" (its index in that
// list) and "position" (its place in the segment's body order). The structure comes from `igneum_getSegment`, which
// names every skipped copy's including block; executed entries are placed by their "sequence" in the one block of
// the segment that executed them (a segment whose executed transactions span several blocks cannot be completed
// this way and is refused: re-export it from a 0.3.9 node). A block that mixes executed and skipped copies keeps
// the export's order (executed first), which is the body order only when no skipped copy precedes an executed one;
// such a segment is refused too. No dependencies.
//
// node tools/prove-fixtures/complete-export.mjs <seq.json> <out.json> <block>[,<block>...] [--rpc http://127.0.0.1:26800]
import { readFileSync, writeFileSync } from 'node:fs';
const [seqPath, outPath, list] = process.argv.slice(2);
const rpcUrl = process.argv.includes('--rpc') ? process.argv[process.argv.indexOf('--rpc') + 1] : 'http://127.0.0.1:26800';
if (!seqPath || !outPath || !list) { console.error('usage: complete-export.mjs <seq.json> <out.json> <block>[,<block>...] [--rpc url]'); process.exit(2); }
const rpc = async (method, params) => { const r = await fetch(rpcUrl, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ jsonrpc: '2.0', id: 1, method, params }) }); const j = await r.json(); if (j.error) throw new Error(j.error.message); return j.result; };
const seq = JSON.parse(readFileSync(seqPath, 'utf8'));
const segments = seq.result ? seq.result.segments : seq.segments;
for (const n of list.split(',').map(Number)) {
const seg = segments.find((s) => s.number === n);
if (!seg) throw new Error(`segment ${n} is not in ${seqPath}`);
const node = await rpc('igneum_getSegment', ['0x' + n.toString(16)]);
seg.blocks = node.mergeset.map((m) => ({ hash: m.hash, miner: m.miner, blue: m.blue, txCount: m.txCount }));
const skippedBlock = new Map((node.skipped || []).map((s) => [s.hash.toLowerCase(), s.includingBlock || s.block]));
const txBlocks = seg.blocks.map((b, i) => [i, b]).filter(([, b]) => b.txCount > 0);
const executedBlocks = new Set();
let position = 0;
const entries = seg.txs.slice();
// executed entries first in sequence order (the export already lists them so), then the skipped copies in list order
entries.sort((a, b) => (a.sequence ?? Infinity) - (b.sequence ?? Infinity));
for (const t of entries) {
if (t.skipped == null) {
const candidates = txBlocks.filter(([, b]) => b.miner.toLowerCase() === t.miner.toLowerCase());
if (candidates.length !== 1) throw new Error(`segment ${n}: executed ${t.hash} by ${t.miner}: ${candidates.length} blocks of that miner carry transactions; re-export from a 0.3.9 node`);
t.block = candidates[0][0]; executedBlocks.add(t.block);
} else {
const h = skippedBlock.get(t.hash.toLowerCase());
if (!h) throw new Error(`segment ${n}: skipped ${t.hash} has no including block in igneum_getSegment`);
t.block = seg.blocks.findIndex((b) => b.hash.toLowerCase() === h.toLowerCase());
if (t.block < 0) throw new Error(`segment ${n}: skipped ${t.hash}: block ${h} is not in the mergeset`);
if (executedBlocks.has(t.block)) throw new Error(`segment ${n}: block ${t.block} mixes executed and skipped copies; the body order is not in this export, re-export from a 0.3.9 node`);
t.miner = seg.blocks[t.block].miner; t.blue = seg.blocks[t.block].blue;
}
t.position = position++;
}
for (const [i, b] of seg.blocks.entries()) { const got = entries.filter((t) => t.block === i).length; if (got !== b.txCount) throw new Error(`segment ${n}: block ${i} has ${got} entries, txCount ${b.txCount}`); }
seg.txs = entries;
console.log(`segment ${n}: ${seg.blocks.length} blocks (${seg.blocks.map((b) => `${b.miner.slice(0, 6)}:${b.txCount}`).join(', ')}), ${entries.length} entries placed`);
}
// written as the bare export object (what igneum-prove-export reads), whether the input carried the JSON-RPC envelope or not
writeFileSync(outPath, JSON.stringify({ ...(seq.result || seq), segments }));
console.log(`wrote ${outPath}`);