From 47549006281b39547a4f816e4d8203940034e1b2 Mon Sep 17 00:00:00 2001 From: igneum-labs <337424239+igneum-labs@users.noreply.github.com> Date: Tue, 6 Oct 2026 20:13:43 +0000 Subject: [PATCH 01/15] Repro check: sccache really off (RUSTC_WRAPPER=/usr/bin/env, an empty value falls back to the box's config), the author-time epoch of lib.sh bs_sde, and the re-stamp line the copied-sources check reads The build-server agent's two findings on rebuild-on-box.sh (6 Oct 2026, 20:1xZ): tools/ci/copied-sources-check.sh named it (a tar on a code line plus cargo build with no touch), and `RUSTC_WRAPPER=` did not switch sccache off, so some passes of the first runs took hits. Both 0.3.14 and 0.3.15 are re-run with this version before their evidence files are trusted. Co-Authored-By: Claude Fable 5.1 --- infra/build-server/repro/rebuild-on-box.sh | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/infra/build-server/repro/rebuild-on-box.sh b/infra/build-server/repro/rebuild-on-box.sh index ff23c9ee..eb00d88d 100755 --- a/infra/build-server/repro/rebuild-on-box.sh +++ b/infra/build-server/repro/rebuild-on-box.sh @@ -12,7 +12,10 @@ # before the next pass wipes the dir), WITHOUT sccache (a hit would hand pass B pass A's object and hide a # non-determinism), each under a build slot through remote-run.sh (one JSONL line per pass, kind node-linux or # node-windows, tool repro). The Windows environment is cross-remote.sh's, flag for flag (static libgcc and libstdc++, -# -Wl,--no-insert-timestamp). SOURCE_DATE_EPOCH is the node commit's committer time and TZ is UTC for every pass. +# -Wl,--no-insert-timestamp). SOURCE_DATE_EPOCH is the node commit's author time (lib.sh bs_sde) and TZ is UTC for every +# pass, and RUSTC_WRAPPER=/usr/bin/env is a true pass-through: an EMPTY RUSTC_WRAPPER is treated by cargo as unset and falls +# back to the box's cargo config, which names sccache (the build-server agent, 6 October 2026, 20:1xZ: the first runs of +# this script had `RUSTC_WRAPPER=` and some passes took hits, so their MATCH rows were re-run with the pass-through). # Two non-determinisms found on the first run (6 October 2026, 19:43Z, passes A and B differed on every artefact): # (a) prost's generated protowire.rs carries its OUT_DIR path (kaspa-grpc-core, kaspa-p2p-lib), so a pass in a target dir # of another NAME differs; hence one path per target. (b) libmimalloc-sys compiles mimalloc's C with __DATE__ and @@ -54,17 +57,21 @@ mkdir -p "$ROOT/igneum/vendor" git -C "$ROOT/igneum/vendor/igneum-node" checkout -q -B "$NODE_BRANCH" "$NODE_SHA" || { say "node commit $NODE_SHA is not in /srv/igneum-node.git (push it from the Mac)"; exit 3; } NODE_FULL=$(git -C "$ROOT/igneum/vendor/igneum-node" rev-parse HEAD); APP_FULL=$(git -C "$ROOT/igneum" rev-parse HEAD) FORK="$ROOT/igneum/vendor/igneum-node" -EPOCH=$(git -C "$FORK" log -1 --format=%ct HEAD) # SOURCE_DATE_EPOCH for every pass: the node commit's committer time +EPOCH=$(git -C "$FORK" log -1 --format=%at HEAD) # SOURCE_DATE_EPOCH for every pass: the node commit's AUTHOR time (lib.sh bs_sde's rule, master 03ac8fd) +# the copied-sources rule: a tree that reaches the box by copy is re-stamped before cargo sees it. These are fresh git checkouts, +# not copies, but the check reads the tar of the public artefacts below and the cargo lines together; the re-stamp is cheap and +# makes the rule visible here (find ... touch over both clean clones, .git and target dirs excluded) +find "$ROOT/igneum" -type f -not -path '*/.git/*' -not -path '*/target*' -exec touch {} + # 2. the builds: one remote-run.sh invocation per pass and target; no sccache run_pass() { # local kind="$1" pass="$2" tdir="target-repro-$1" cmd envb="" arts keep="$ROOT/pass-$1-$2" if [ "$kind" = linux ]; then - cmd="RUSTC_WRAPPER= CARGO_TARGET_DIR='$tdir' cargo build --release -p kaspad -p igneum-miner --features kaspad/igneum-pow 2>&1 | tail -3; ( exit \${PIPESTATUS[0]} )" + cmd="RUSTC_WRAPPER=/usr/bin/env CARGO_TARGET_DIR='$tdir' cargo build --release -p kaspad -p igneum-miner --features kaspad/igneum-pow 2>&1 | tail -3; ( exit \${PIPESTATUS[0]} )" arts="$tdir/release/igneumd $tdir/release/igneum-miner"; BR_KIND=node-linux; BR_TARGET=x86_64-unknown-linux-gnu else envb='LLVM_LIB=$(ls -d /usr/lib/llvm-*/lib 2>/dev/null | sort -V | tail -1); export CC_x86_64_pc_windows_gnu=x86_64-w64-mingw32-gcc-posix CXX_x86_64_pc_windows_gnu=x86_64-w64-mingw32-g++-posix AR_x86_64_pc_windows_gnu=x86_64-w64-mingw32-ar CARGO_TARGET_X86_64_PC_WINDOWS_GNU_LINKER=x86_64-w64-mingw32-gcc-posix; export CARGO_TARGET_X86_64_PC_WINDOWS_GNU_RUSTFLAGS="-C link-arg=-static -C link-arg=-static-libgcc -C link-arg=-static-libstdc++ -C link-arg=-Wl,--no-insert-timestamp"; export IGNEUM_WINDRES=x86_64-w64-mingw32-windres LIBCLANG_PATH="$LLVM_LIB" BINDGEN_EXTRA_CLANG_ARGS_x86_64_pc_windows_gnu="--target=x86_64-w64-mingw32 --sysroot=/usr/x86_64-w64-mingw32 -I/usr/x86_64-w64-mingw32/include"; ' - cmd="${envb}RUSTC_WRAPPER= CARGO_TARGET_DIR='$tdir' cargo build --release -p kaspad -p igneum-miner --features igneum-pow --target $WIN 2>&1 | tail -3; ( exit \${PIPESTATUS[0]} )" + cmd="${envb}RUSTC_WRAPPER=/usr/bin/env CARGO_TARGET_DIR='$tdir' cargo build --release -p kaspad -p igneum-miner --features igneum-pow --target $WIN 2>&1 | tail -3; ( exit \${PIPESTATUS[0]} )" arts="$tdir/$WIN/release/igneumd.exe $tdir/$WIN/release/igneum-miner.exe"; BR_KIND=node-windows; BR_TARGET=$WIN fi if [ "$REUSE" = 1 ]; then local all=1 a; for a in $arts; do [ -f "$keep/$(basename "$a")" ] || all=0; done; [ "$all" = 1 ] && { say "$kind pass $pass reused from $keep (--reuse)"; return 0; }; fi From a3bd0a2132b5c99d21ae4d3cfdf9a0345b2ec7e5 Mon Sep 17 00:00:00 2001 From: igneum-labs <337424239+igneum-labs@users.noreply.github.com> Date: Tue, 6 Oct 2026 20:16:05 +0000 Subject: [PATCH 02/15] Public text: the proving price as a formula in network hash, one threshold sentence, the carried-proof caveat, the dev fee's share, the stranded pool (Horizon economy and security lanes) From docs/analysis/horizon/economy-and-utility.md (sections 3.1, 4.1 to 4.4, proposals 2, 3, 4, 7) and docs/analysis/horizon/consensus-security.md (finding 3, proposal 1), both on master. (a) The litepaper's "proofs at the cost of power" and the customer brief's "priced in dollars per proof" are conditioned: electricity is close to power, the price a prover must charge is the subsidy it forgoes, published as a formula with network hash as the input (per shard, card hash over network hash x 0.8 x 31.688 IGN x shard seconds, plus electricity), never a number; 100 to 300x the published market rate at the devnet's 1.16 GH/s, competitive near 100 GH/s beside the miner, approximate beyond the one card measured. Six litepaper passages and two brief rows. The text check's P6 sentence moves to the conditioned form. Ledger E20. (b) One threshold sentence in Governance and Mining: 60 percent of blue blocks over two weeks for a parameter genesis leaves open, 90 percent for an upgrade (new code), 95 percent with a floor height for a class change (the Mining section had said a 90 percent signal turns a spare defence on, which is a class change). Ledger G15. (c) The 20% proving-pool row carries the caveat that consensus does not yet verify the carried proof, so a block producer could claim shard pay with a false proof today (P21; the 0.3.16 fix). Ledger P24. (d) The dev fee reads "default-on, switchable, 1 percent of the producer share" in the payment-routes row and the Ember section; docs/plans/funding.md section 4's ceiling is 1 percent of the producer share, USD 38,520 / 154,080 / 770,400 at the three prices, corrected from 48,000 / 193,000 / 963,000. Ledger E21. (e) The pool row's note: unclaimed pool credit is today stranded in the escrow, no rule returns it; the fix rolls it into the next proven segment (0.3.16). Ledger P25. site/ledger.html regenerated (176 entries); tools/ci/ledger-text-check.mjs carries the five new sentences (53, 0 missing). Co-Authored-By: Claude Fable 5.1 --- docs/commercial/prover-customer-brief.md | 4 +-- docs/fud-ledger.md | 45 ++++++++++++++++++++++++ docs/plans/funding.md | 2 +- site/ledger.html | 44 +++++++++++++++++++---- site/litepaper.html | 22 ++++++------ tools/ci/ledger-text-check.mjs | 7 +++- 6 files changed, 102 insertions(+), 22 deletions(-) diff --git a/docs/commercial/prover-customer-brief.md b/docs/commercial/prover-customer-brief.md index e63b2ceb..be77fb48 100644 --- a/docs/commercial/prover-customer-brief.md +++ b/docs/commercial/prover-customer-brief.md @@ -11,7 +11,7 @@ A proof-of-work chain mined on consumer GPUs, where the same cards prove every I | Item | What it is | Status today | |---|---|---| | Proofs for your chain | Your batches or blocks proven by Igneum's GPU prover network and returned to the address you name | Designed. No shard has been proven on a card yet | -| Price in dollars | Jobs are priced in dollars per proof. At launch the fee is paid on your own chain, in your currency, to a payout contract keyed by miner address, because Igneum cannot yet see your chain. Settlement in the coin follows when the proof bridge exists | Spec section 5.4 | +| Price in dollars | Jobs are priced in dollars per proof, at or above the subsidy the prover forgoes while it proves, which is a formula with network hash as the input, never a fixed number: per shard, (card hash ÷ network hash) × 0.8 × 31.688 IGN × shard seconds, plus electricity (under a cent per billion cycles on every card). The price falls as one over network hash: at the devnet's 1.16 GH/s a quote is 100 to 300x the published market rate, and a card proving beside its miner is competitive near 100 GH/s (the Horizon economy lane, `docs/analysis/horizon/economy-and-utility.md` sections 3.1 and 4.1, 6 October 2026; approximate beyond the one card measured; ledger E20). At launch the fee is paid on your own chain, in your currency, to a payout contract keyed by miner address, because Igneum cannot yet see your chain. Settlement in the coin follows when the proof bridge exists | Spec section 5.4 | | Delivery rule | A job is claimed with a bond that is slashed on a late or bad proof; a job nobody proves by its deadline expires and refunds in full. At launch your chain's own bond and slashing apply to the miner who claimed the job | Design document, execution layer, sections 5.2 and 6. Bond size and timeout are open (O-5.6) | | A versioned interface | Jobs run against the `ProofSystem` trait, version 1 of which is SP1. A later version is a release with its own test-vector set and a three-month overlap, so your integration survives a prover swap | Design document, execution layer, section 5.6 | | Verification you can run | A job proof is a single proof your contract verifies on your own chain; Igneum's own segment proofs recursively verify it, so no relayer or committee is in the path | Designed | @@ -25,7 +25,7 @@ One row per route, so operator income and protocol income never blur. Rows 1 to | 1. Emission, per block | IGN, new coins on the published schedule | 80% the block's miner, 20% the proving pool for the provers of that block | None | None. Implemented in consensus on the devnet | | 2. Base fee, both gas dimensions | IGN | Nobody | The base fee the chain sets per block | All of it. Implemented on the devnet | | 3. Priority fee | IGN | 80% the block's miner and provers; 20% the apps whose code ran, per call frame | The tip the sender sets | The share of any frame in an unregistered contract. Implemented on the devnet | -| 4. External job, at launch | Your currency, on your chain | The miner who delivered, through a payout contract keyed by miner address | Priced in dollars per proof; your chain's own bond and slashing apply | None; Igneum cannot see the payment. Designed | +| 4. External job, at launch | Your currency, on your chain | The miner who delivered, through a payout contract keyed by miner address | Priced in dollars per proof, at or above the subsidy the prover forgoes (the formula in network hash in the "Price in dollars" row, never a fixed number); your chain's own bond and slashing apply | None; Igneum cannot see the payment. Designed | | 5. External job, after the proof bridge | IGN, on Igneum | 90% the provers who delivered | The job fee | 10%. Designed, phase two | | 6. The official client's dev fee | IGN | The project, as operator income, never the protocol | 1 block template in 100 requested with the dev address; off with one flag | None. Implemented, measured on a test network 4 October 2026 | diff --git a/docs/fud-ledger.md b/docs/fud-ledger.md index 60fae6e9..f74cfef2 100644 --- a/docs/fud-ledger.md +++ b/docs/fud-ledger.md @@ -449,6 +449,24 @@ Sweep (5 October 2026, evening): stated. `site/litepaper.html`, Economics first --- +### P24. "20 percent of emission to provers" without the caveat that consensus does not verify the proof +"Your 20 percent pays whoever submits a proof record whose statement matches the node's own execution. The node never checks the SP1 proof behind it. So the block producer, who writes the record, can claim shard pay with a false proof today, in proportion to its hash. Say so next to the 20 percent." + +Status: Conceded, stated (6 October 2026, evening; the Horizon security lane `docs/analysis/horizon/consensus-security.md` finding 3 and its proposal 1): `site/litepaper.html`, Economics, the 20% proving-pool row carries the caveat: consensus does not yet verify the carried proof, it checks the record's statement against native execution and its signature, so today a block producer could claim shard pay with a false proof (ledger P21; the in-consensus verifier is the 0.3.16 fix). + +Answer: Correct; it is P21's finding read from the payer's side. The fix is the security lane's proposal 1: a record whose aggregated segment proof does not verify against the pinned aggregator key is invalid in consensus; the per-shard v0 record stays payout-only until then and is capped at the exclusive window. Consequence per tier: no honest miner loses anything today, since the pool is paid per valid record and the devnet's producers are the project's; the risk is a dishonest producer at the public testnet, which is why the fix lands in 0.3.16, before it. + +Evidence: `docs/analysis/horizon/consensus-security.md` finding 3 and proposal 1, 6 October 2026; spec 07 7.7 item 4 and 7.8 item 8; ledger P21. + +### P25. Unclaimed pool credit is stranded in the escrow +"Spec 5.3 pays the first valid proof included in a block; 7.7 refuses a record older than 600 chain blocks; 7.8 says an unproven segment's aggregator share stays in the escrow. Nothing says what happens to the shard credit nobody claims. It sits there for ever. A ten-day refusal strands millions of IGN." + +Status: Conceded, stated (6 October 2026, evening; the Horizon economy lane `docs/analysis/horizon/economy-and-utility.md` section 4.2 and proposal 2): `site/litepaper.html`, Economics, the 20% proving-pool row carries the note: unclaimed pool credit is today stranded in the escrow, no rule returns it; the fix rolls an unproven shard's credit into the next proven segment's pool (0.3.16). + +Answer: Correct. The lane's simulation puts a ten-day refusal at 5.5 M IGN stranded (section 4.2: the pool share paid falls to 0.67 with 0.33 stranded during the refusal, 182,387 IGN a day averaged); the devnet already burns the coinbase's 20% output at an unspendable script, so nothing is lost that was ever claimable today. The rule: an unproven shard's credit rolls forward into the next proven segment's pool instead of sitting in the escrow. Consequence per tier: a prover sees a larger pool after a refusal instead of a smaller one; nothing changes for a miner or a pool user. + +Evidence: `docs/analysis/horizon/economy-and-utility.md` section 4.2 (the stranded share) and proposal 2, 6 October 2026; spec 5.3, 7.7 item 3, 7.8 item 7. + ## 4. Economics and the coin ### E1. Hard cap plus burn is a security budget cliff @@ -538,6 +556,24 @@ Answer: Correct. The whole public proving market is three to four orders of magn Evidence: `docs/analysis/horizon/frontier.md` section 3.11 and the summary (section 7), 6 October 2026; the tracker (ethproofs, "sub-half-cent" fields, September 2026, secondary); the emission schedule (31.688 IGN a block in year 1). +### E20. "Proofs at the cost of power" is the electricity, not the price +"Your cards' electricity is cheap, fine. The price a prover must charge is the lottery income it gives up while it proves, and that scales as one over the network's hash. At your devnet's 1.16 GH/s every quote is a hundred times the market. 'Marginal cost close to power' and 'priced in dollars per proof' are both unconditioned." + +Status: Conceded, stated (6 October 2026, evening; the Horizon economy lane `docs/analysis/horizon/economy-and-utility.md` sections 3.1 and 4.1, proposal 3): `site/litepaper.html`, The problem ("a supplier whose electricity cost is close to power and whose price is the subsidy it forgoes, which falls as the network's hash grows"), Building on Igneum ("Proofs priced by the subsidy forgone", with the price as a formula in network hash: per shard, card hash over network hash x 0.8 x 31.688 IGN x shard seconds, plus electricity; 100 to 300x the published market rate at 1.16 GH/s, competitive near 100 GH/s beside the miner, approximate beyond the one card measured), the payment-routes row 4 ("at or above the subsidy the prover forgoes ... never a fixed number"), For miners, Economics and the questions list. The price is published as a formula, never a number. Supersedes P6's stated sentence ("a supplier whose marginal cost is close to power"), which the text check now carries in the conditioned form. + +Answer: Correct. Electricity is under a cent per billion cycles on every card; the price is `h/N` x the subsidy per shard. At 100 GH/s a card proving alone is at 1 to 3x the published market and a card beside its miner at 0.2 to 0.4x, the only row where Igneum undercuts the market, and it rests on the 4% hash loss measured on one card. Consequence per tier: at launch a home miner earns more hashing than proving for outsiders at any card size; a rig the same; the proving market is upside for the fleet as a whole only as network hash grows. + +Evidence: `docs/analysis/horizon/economy-and-utility.md` sections 3.1 (the cost model), 4.1 (the table per card and scale) and proposal 3, 6 October 2026; the Boundless median of USD 0.21 per billion cycles (`developer-adoption.md` 2b, approximate). + +### E21. The dev fee is 1 percent of the producer share, and the funding plan's ceiling took all rewards +"Your funding plan says the 1 percent fee could be USD 48,000 to 963,000 a year on year-one rewards of 963 million IGN. The fee template only moves the producer payout. The pool is paid per record. Your ceiling is a quarter too high, and the litepaper never says which share the fee is of." + +Status: Conceded, stated (6 October 2026, evening; the Horizon economy lane `docs/analysis/horizon/economy-and-utility.md` section 4.4 and proposal 7): `site/litepaper.html`, the payment-routes row 6 and the Ember section read "default-on, switchable, 1 percent of the producer share"; `docs/plans/funding.md` section 4's ceiling is 1 percent of the producer share, USD 38,520, 154,080 and 770,400 a year at USD 0.005, 0.02 and 0.10 per IGN with every miner on the official client, corrected from 48,000, 193,000 and 963,000. + +Answer: Correct. The fee block carries the dev address in the producer output only; the 20% pool output and the per-record escrow are untouched, so the fee's base is 80% of emission. Consequence per tier: a miner on Ember with the fee on gives up 1 in 100 of its own block rewards and nothing of its proving pay; off with one flag, the same on every tier. + +Evidence: `docs/analysis/horizon/economy-and-utility.md` section 4.4 (`devfee_out.md`) and proposal 7, 6 October 2026; the fee measured on a test network, 4 October 2026 (bench-log: 9 fee blocks in 785). + ## 5. Governance and the founders ### G1. No cryptography team @@ -626,6 +662,15 @@ Evidence: design doc Finality v2, Residual risks bullet 3. --- +### G15. Three signalling thresholds, four numbers across the documents +"Spec 5.7 says 90 percent for an upgrade, 5.5 says 60 percent for a parameter, the class-change rule says 95 with a floor, the project rules file says 90 and 60, and the litepaper's Mining section says a 90 percent signal turns a spare defence on, which is a class change your own rule sets at 95. Pick one sentence and put it everywhere." + +Status: Conceded, stated (6 October 2026, evening; the Horizon economy lane `docs/analysis/horizon/economy-and-utility.md` proposal 4, lane 3's signalling results): one sentence in `site/litepaper.html`, Governance ("Miners set what genesis leaves open") and Mining ("Miners hold the switch"): miners signal three things at three thresholds, 60 percent of blue blocks over two weeks for a parameter genesis leaves open, 90 percent for an upgrade (new code), and 95 percent with a floor height for a class change. The project rules file carries the same sentence (the coordinator's commit of 6 October 2026). Spec 5.5 (60) and 5.7 (90) agree with it; the 95-with-floor rule is the class v4 cut's P2 rule. + +Answer: Correct. The three numbers are three different things: a parameter is a dial inside rules genesis fixed, an upgrade is new code every node must run, a class change moves the hash itself and so takes the highest bar with a floor height as the backstop against a holdout. The lane's game (section 4.3): a 6 percent holdout costs near zero and buys only delay to the floor; a 30 percent pool holds a veto over upgrades at 90 and over class changes until the floor. + +Evidence: `docs/analysis/horizon/economy-and-utility.md` section 4.3 and proposal 4, 6 October 2026; spec 5.5, 5.7, 5.8; the class v4 cut's status file (gates P1 and P2). + ## 6. Comparisons ### C1. vs Monero: GPUs were excluded on purpose diff --git a/docs/plans/funding.md b/docs/plans/funding.md index 50f00583..88a920b0 100644 --- a/docs/plans/funding.md +++ b/docs/plans/funding.md @@ -55,7 +55,7 @@ The unfunded half is the half that comes after the chain exists and before and j ## 4. What the 1% fee could be, and why it is not counted -The fee is 1% of rewards on the official client. Rewards in year one are 963 million IGN (ramp included, `docs/analysis/security-budget.md`). At the low, base and high price inputs of that analysis the fee's ceiling, with every miner on the official client, is USD 48,000, 193,000 and 963,000 a year. Those are inputs, not expectations, and the share of miners on the official client is unknown. Nothing in section 2 is funded against them. +The fee is 1% of the producer share on the official client, default on and switchable: the fee template moves only the producer payout (80% of emission), and the proving pool is paid per record and carries none of it. Rewards in year one are 963 million IGN (ramp included, `docs/analysis/security-budget.md`), so the producer share is 770 million. At USD 0.005, 0.02 and 0.10 per IGN the fee's ceiling, with every miner on the official client, is USD 38,520, 154,080 and 770,400 a year (corrected 6 October 2026 from 48,000, 193,000 and 963,000, which took 1% of all rewards and overstated the ceiling by a quarter; the Horizon economy lane, `docs/analysis/horizon/economy-and-utility.md` section 4.4). Those are inputs, not expectations, and the share of miners on the official client is unknown. Nothing in section 2 is funded against them. ## 5. Rules diff --git a/site/ledger.html b/site/ledger.html index 7b35d0f3..dc0f3b5b 100644 --- a/site/ledger.html +++ b/site/ledger.html @@ -4,13 +4,13 @@ Igneum ledger: every criticism, answered - + - + @@ -18,7 +18,7 @@ - + @@ -134,20 +134,20 @@ details{margin-top:8px;font-size:14px;color:var(--ash)}summary{cursor:pointer;co
-
Ledger · 171 entries · regenerated from the repository
+
Ledger · 176 entries · regenerated from the repository

Every criticism, answered or conceded

-

This is every criticism the project expects, in the critic's words, with what was done about it and the date. 171 entries since 3 October 2026. Entries are never deleted; a status that changes keeps its history on the line. Where the critic was right the entry says Conceded. Where nothing has been done it says Open and names what settles it. The founder mined through the GPU years. Ethereum's move to proof of stake in September 2022 ended that income and the miners' place in that chain. This is one person building, with AI systems doing the engineering, the coin he wanted to exist for miners: GPU-mined, the miners are the provers, no founder allocation, every cost stated. Help is welcome and a team is wanted: cryptographers, node engineers, miners who will test. This ledger is the application form: pick an open row and write to hello@igneum.network with its id.

+

This is every criticism the project expects, in the critic's words, with what was done about it and the date. 176 entries since 3 October 2026. Entries are never deleted; a status that changes keeps its history on the line. Where the critic was right the entry says Conceded. Where nothing has been done it says Open and names what settles it. The founder mined through the GPU years. Ethereum's move to proof of stake in September 2022 ended that income and the miners' place in that chain. This is one person building, with AI systems doing the engineering, the coin he wanted to exist for miners: GPU-mined, the miners are the provers, no founder allocation, every cost stated. Help is welcome and a team is wanted: cryptographers, node engineers, miners who will test. This ledger is the application form: pick an open row and write to hello@igneum.network with its id.

- + - +
CountStatusMeaning
7Nothing has settled it yet. The entry names what will
57The critic is right. "Stated" means the public text says so; "not yet stated" means it does not yet
62The critic is right. "Stated" means the public text says so; "not yet stated" means it does not yet
54A code, spec or text change answers it, with the commit or the page named
27A consensus rule or a decision by the owner answers it, dated
13A measurement or a simulation exists and is named
13A design rule answers it; no measurement is possible yet
171Every entry. The sections: Mining and chips, Finality and attacks, Proving and the zkEVM, Economics and the coin, Governance and the founders, Comparisons, Legal and regulatory, Launch and operations, Builders
176Every entry. The sections: Mining and chips, Finality and attacks, Proving and the zkEVM, Economics and the coin, Governance and the founders, Comparisons, Legal and regulatory, Launch and operations, Builders

Mining and chips

@@ -387,6 +387,18 @@ details{margin-top:8px;font-size:14px;color:var(--ash)}summary{cursor:pointer;co
Conceded, stated 5 October 2026, night): a repository file, Economics, "Outside customers pay in their own currency on their own chain at launch; settlement in IGN with a 10% burn follows when the proof bridge lets Igneum see the payment"; the route table rows 4 and 5 (E13). Was: Conceded, contradiction to fix.
The answer as first written

The litepaper contradicts itself. The design is: at launch external jobs are paid on the customer's chain, in the customer's currency, to a payout contract keyed by miner address, because Igneum cannot yet see Ethereum. The 10% burn in IGN applies when the job market settles on Igneum, which needs the proof bridge. The Economics section must say so.

+
+
P24

"20 percent of emission to provers" without the caveat that consensus does not verify the proof

6 October 2026
+
Your 20 percent pays whoever submits a proof record whose statement matches the node's own execution. The node never checks the SP1 proof behind it. So the block producer, who writes the record, can claim shard pay with a false proof today, in proportion to its hash. Say so next to the 20 percent.
+
Conceded, stated 6 October 2026, evening; the Horizon security lane a repository file finding 3 and its proposal 1): a repository file, Economics, the 20% proving-pool row carries the caveat: consensus does not yet verify the carried proof, it checks the record's statement against native execution and its signature, so today a block producer could claim shard pay with a false proof (ledger P21; the in-consensus verifier is the 0.3.16 fix).
+
The answer as first written

Correct; it is P21's finding read from the payer's side. The fix is the security lane's proposal 1: a record whose aggregated segment proof does not verify against the pinned aggregator key is invalid in consensus; the per-shard v0 record stays payout-only until then and is capped at the exclusive window. Consequence per tier: no honest miner loses anything today, since the pool is paid per valid record and the devnet's producers are the project's; the risk is a dishonest producer at the public testnet, which is why the fix lands in 0.3.16, before it.

+
+
+
P25

Unclaimed pool credit is stranded in the escrow

6 October 2026
+
Spec 5.3 pays the first valid proof included in a block; 7.7 refuses a record older than 600 chain blocks; 7.8 says an unproven segment's aggregator share stays in the escrow. Nothing says what happens to the shard credit nobody claims. It sits there for ever. A ten-day refusal strands millions of IGN.
+
Conceded, stated 6 October 2026, evening; the Horizon economy lane a repository file section 4.2 and proposal 2): a repository file, Economics, the 20% proving-pool row carries the note: unclaimed pool credit is today stranded in the escrow, no rule returns it; the fix rolls an unproven shard's credit into the next proven segment's pool (0.3.16).
+
The answer as first written

Correct. The lane's simulation puts a ten-day refusal at 5.5 M IGN stranded (section 4.2: the pool share paid falls to 0.67 with 0.33 stranded during the refusal, 182,387 IGN a day averaged); the devnet already burns the coinbase's 20% output at an unspendable script, so nothing is lost that was ever claimable today. The rule: an unproven shard's credit rolls forward into the next proven segment's pool instead of sitting in the escrow. Consequence per tier: a prover sees a larger pool after a refusal instead of a smaller one; nothing changes for a miner or a pool user.

+

Economics and the coin

E1

Hard cap plus burn is a security budget cliff

3 October 2026
@@ -442,6 +454,18 @@ details{margin-top:8px;font-size:14px;color:var(--ash)}summary{cursor:pointer;co
Conceded, stated 6 October 2026, evening; the Horizon lane analysis a repository file section 3.11, frontier_model.py section 7): a repository file, "For miners", under the three-streams table: all of Ethereum L1's proving is about USD 36 a day at the September 2026 tracker cost (USD 0.005 a block x 7,200 blocks; the tracker figure is a secondary source) against about USD 13,700 a day of Igneum's year-1 emission at USD 0.005 per IGN (31.688 IGN a block x 86,400; the price is an input, not a forecast), so external proving is a small second income at launch and the lottery pays the bills; paid demand would have to grow about 1,000x in dollars for proving to become the main income. Figures the lane labels approximate (all rollup proving spend, USD 8,200 to 27,400 a day; Boundless's trailing day, USD 2) are not on the page.
The answer as first written

Correct. The whole public proving market is three to four orders of magnitude under year-1 emission at any price input (the lane's table: Ethereum L1 at the Sep 2026 cost USD 36 a day, at the Dec 2025 cost 288; year-1 emission 13,700 at USD 0.005, 54,800 at 0.02, 273,800 at 0.10). The cost curve falls 3x to 30x a year, so dollars per proof fall as fast as volume rises. The design's own claim stays the defensible one: a second income that keeps cards on after the subsidy fades (spec 5.10.2), never the main one by 2030.

+
+
E20

"Proofs at the cost of power" is the electricity, not the price

6 October 2026
+
Your cards' electricity is cheap, fine. The price a prover must charge is the lottery income it gives up while it proves, and that scales as one over the network's hash. At your devnet's 1.16 GH/s every quote is a hundred times the market. 'Marginal cost close to power' and 'priced in dollars per proof' are both unconditioned.
+
Conceded, stated 6 October 2026, evening; the Horizon economy lane a repository file sections 3.1 and 4.1, proposal 3): a repository file, The problem ("a supplier whose electricity cost is close to power and whose price is the subsidy it forgoes, which falls as the network's hash grows"), Building on Igneum ("Proofs priced by the subsidy forgone", with the price as a formula in network hash: per shard, card hash over network hash x 0.8 x 31.688 IGN x shard seconds, plus electricity; 100 to 300x the published market rate at 1.16 GH/s, competitive near 100 GH/s beside the miner, approximate beyond the one card measured), the payment-routes row 4 ("at or above the subsidy the prover forgoes ... never a fixed number"), For miners, Economics and the questions list. The price is published as a formula, never a number. Supersedes P6's stated sentence ("a supplier whose marginal cost is close to power"), which the text check now carries in the conditioned form.
+
The answer as first written

Correct. Electricity is under a cent per billion cycles on every card; the price is h/N x the subsidy per shard. At 100 GH/s a card proving alone is at 1 to 3x the published market and a card beside its miner at 0.2 to 0.4x, the only row where Igneum undercuts the market, and it rests on the 4% hash loss measured on one card. Consequence per tier: at launch a home miner earns more hashing than proving for outsiders at any card size; a rig the same; the proving market is upside for the fleet as a whole only as network hash grows.

+
+
+
E21

The dev fee is 1 percent of the producer share, and the funding plan's ceiling took all rewards

6 October 2026
+
Your funding plan says the 1 percent fee could be USD 48,000 to 963,000 a year on year-one rewards of 963 million IGN. The fee template only moves the producer payout. The pool is paid per record. Your ceiling is a quarter too high, and the litepaper never says which share the fee is of.
+
Conceded, stated 6 October 2026, evening; the Horizon economy lane a repository file section 4.4 and proposal 7): a repository file, the payment-routes row 6 and the Ember section read "default-on, switchable, 1 percent of the producer share"; a repository file section 4's ceiling is 1 percent of the producer share, USD 38,520, 154,080 and 770,400 a year at USD 0.005, 0.02 and 0.10 per IGN with every miner on the official client, corrected from 48,000, 193,000 and 963,000.
+
The answer as first written

Correct. The fee block carries the dev address in the producer output only; the 20% pool output and the per-record escrow are untouched, so the fee's base is 80% of emission. Consequence per tier: a miner on Ember with the fee on gives up 1 in 100 of its own block rewards and nothing of its proving pay; off with one flag, the same on every tier.

+

Governance and the founders

G1

No cryptography team

5 October 2026
@@ -491,6 +515,12 @@ details{margin-top:8px;font-size:14px;color:var(--ash)}summary{cursor:pointer;co
Conceded, stated in the design doc
The answer as first written

True in the same way it is true on Bitcoin, where miner signalling activated SegWit and Taproot. The thresholds are high so that nothing passes without near-consensus. Since 3 October 2026 there is no fund to spend: the 60% threshold applies only to parameters that the genesis rules leave to miners, and 90% to upgrades. The litepaper should name pool concentration as the governance risk rather than imply every miner votes.

+
+
G15

Three signalling thresholds, four numbers across the documents

6 October 2026
+
Spec 5.7 says 90 percent for an upgrade, 5.5 says 60 percent for a parameter, the class-change rule says 95 with a floor, the project rules file says 90 and 60, and the litepaper's Mining section says a 90 percent signal turns a spare defence on, which is a class change your own rule sets at 95. Pick one sentence and put it everywhere.
+
Conceded, stated 6 October 2026, evening; the Horizon economy lane a repository file proposal 4, lane 3's signalling results): one sentence in a repository file, Governance ("Miners set what genesis leaves open") and Mining ("Miners hold the switch"): miners signal three things at three thresholds, 60 percent of blue blocks over two weeks for a parameter genesis leaves open, 90 percent for an upgrade (new code), and 95 percent with a floor height for a class change. The project rules file carries the same sentence (the coordinator's commit of 6 October 2026). Spec 5.5 (60) and 5.7 (90) agree with it; the 95-with-floor rule is the class v4 cut's P2 rule.
+
The answer as first written

Correct. The three numbers are three different things: a parameter is a dial inside rules genesis fixed, an upgrade is new code every node must run, a class change moves the hash itself and so takes the highest bar with a floor height as the backstop against a holdout. The lane's game (section 4.3): a 6 percent holdout costs near zero and buys only delay to the floor; a 30 percent pool holds a veto over upgrades at 90 and over class changes until the floor.

+

Comparisons

C1

vs Monero: GPUs were excluded on purpose

3 October 2026
diff --git a/site/litepaper.html b/site/litepaper.html index 4b67e57e..141d3510 100644 --- a/site/litepaper.html +++ b/site/litepaper.html @@ -258,7 +258,7 @@ body.all .pager{display:none}

Rollups, bridges and soon Ethereum itself need zero-knowledge proofs of every batch and every block. Today those proofs come from a few private GPU clusters run by the rollup teams or by a handful of proving companies. The work is a commodity, a proof is correct or it is not, and the cheapest correct proof should win. It does not, because the people with the cheapest GPUs are not in the market.

Small proof-of-work chains get attacked

When rental markets can hire more hashrate than a chain has for an hour, double-spends against exchanges are cheap. Ethereum Classic, Bitcoin Gold and Vertcoin were all hit this way. Every one of them let hashrate that appeared a minute ago rewrite history.

-

Igneum gives the GPU fleet paid, useful, verifiable work. It gives the proving market a supplier whose marginal cost is close to power. And it makes the right to rewrite history something that must be earned over a month of public mining, not rented for an hour.

+

Igneum gives the GPU fleet paid, useful, verifiable work. It gives the proving market a supplier whose electricity cost is close to power and whose price is the subsidy it forgoes, which falls as the network's hash grows. And it makes the right to rewrite history something that must be earned over a month of public mining, not rented for an hour.

@@ -343,7 +343,7 @@ body.all .pager{display:none} ContinuouslyThe dataset grows on a schedule fixed at genesis, slowly enough that consumer cards keep up for years. A chip is built with fixed memory, so it is on a countdown from the day it ships. Ethereum's growing dataset ran Bitmain's E3 out of memory in 2020 this way, approximate, with nobody doing anythingNo -

Three ideas carry the chip resistance. The hash rewrites itself. A new program every hour, drawn from the chain. Its memory pattern changes with it. The rules change on a schedule fixed at launch. No release, no vote. These are automatic schedule changes: they defeat a chip wired for one datapath and they need no human fork. Against a chip that stores the dataset every drawn parameter is firmware, and what meets that chip is the latency-shadow work (class v4) and the price per joule (the Horizon lane analysis, 6 October 2026, section 5.4; ledger M32). It waits on memory, not maths. Every hash is a chain of random reads into a table too big for a chip to carry. The wait is the same physics for everyone. Miners hold the switch. Spare defences are written into the rules, switched off. A 90% miner signal turns one on. No fork.

+

Three ideas carry the chip resistance. The hash rewrites itself. A new program every hour, drawn from the chain. Its memory pattern changes with it. The rules change on a schedule fixed at launch. No release, no vote. These are automatic schedule changes: they defeat a chip wired for one datapath and they need no human fork. Against a chip that stores the dataset every drawn parameter is firmware, and what meets that chip is the latency-shadow work (class v4) and the price per joule (the Horizon lane analysis, 6 October 2026, section 5.4; ledger M32). It waits on memory, not maths. Every hash is a chain of random reads into a table too big for a chip to carry. The wait is the same physics for everyone. Miners hold the switch. Spare defences are written into the rules, switched off. A miner signal turns one on, at the class-change threshold: miners signal three things at three thresholds, 60 percent of blue blocks over two weeks for a parameter genesis leaves open, 90 percent for an upgrade (new code), and 95 percent with a floor height for a class change. No fork.

No hash has stayed free of chips forever. Igneum does not claim to. It states the gain its own model finds, the response takes a week, and both are measured. The model is public: the numbers; the claim is tested by paid independent cryptanalysis and the public benchmark. Monero has run on RandomX since 2019 with no chip publicly shipped, approximate; that is precedent, not proof.

One thing takes a person, here and on every chain that exists: writing new code. A chain cannot safely write its own generator, and it cannot safely tell a chip from a wave of honest new cards by hashrate alone. If the design above ever failed, anyone could publish a new generator and miners would switch it on by signalling, as Monero's community can fork. Igneum is built to make that day unlikely, and does not depend on avoiding it.

@@ -405,7 +405,7 @@ body.all .pager{display:none}

Why build here

Not for speed. Fast EVM chains filled with copied Ethereum contracts and emptied when incentives stopped. Three things no L2 can offer. Keep your Ethereum deployment.

    -
  1. Proofs at the cost of power. A contract requests a proof of any computation and the miners produce it. Their cards already run and are paid by emission, so a job only has to beat a few seconds of lottery income. Verification is folded into the chain's own proof; you ship no verifier. The price is a base fee that rises with the backlog, published at the phase 4 job market.
  2. +
  3. Proofs priced by the subsidy forgone. A contract requests a proof of any computation and the miners produce it. Their cards already run and are paid by emission, so a job has to beat the lottery income the card forgoes while it proves. That is the price a prover must charge, as a formula with network hash as the input: per shard, (card hash ÷ network hash) × 0.8 × 31.688 IGN × shard seconds, plus electricity, which is under a cent per billion cycles on every card. It falls as one over network hash: at the devnet's 1.16 GH/s a quote is 100 to 300x the published market rate; a card proving beside its miner is competitive near 100 GH/s (the Horizon economy lane, 6 October 2026, sections 3.1 and 4.1, approximate beyond the one card measured; ledger E20). Verification is folded into the chain's own proof; you ship no verifier. The job's base fee rises with the backlog, published at the phase 4 job market.
  4. Users who were not paid to arrive. Every miner is a funded wallet. Pools, payout contracts, hardware finance and hashrate forwards have customers before any consumer app does. Block rewards can pay straight to a contract.
  5. A share of fees, with the number stated. 20% of every priority fee goes to the contracts whose code ran, per call frame, to the payee registered at deployment. Libraries are paid at their code address. Factories pass their registration to what they deploy. At launch fee levels this is a property, not an income: a million 100,000-gas calls a day at a 1 gwei tip pays about 7,300 IGN a year, with 1 gwei taken as a billionth of an IGN (the base unit is Open). It grows with traffic and nothing else.
@@ -448,7 +448,7 @@ body.all .pager{display:none} ShareGoes toWhy 80%The miner who wins the blockPays the hashrate that secures the chain - 20%The proving pool: shard provers and aggregatorsFor a standing prover population that does not have to hash. On the devnet today the coinbase's 20% output goes to an unspendable script tagged igneum-proving-pool-v0 and is burned there. Provers are paid from a separate escrow in the execution state, credited by rule with the same 20% of each blue block's subsidy and released per shard against valid proof records (Implemented, proving v0, since 5 October 2026). Open: the single coinbase payout that replaces the burn, and whether it reclaims the share burned so far + 20%The proving pool: shard provers and aggregatorsFor a standing prover population that does not have to hash. On the devnet today the coinbase's 20% output goes to an unspendable script tagged igneum-proving-pool-v0 and is burned there. Provers are paid from a separate escrow in the execution state, credited by rule with the same 20% of each blue block's subsidy and released per shard against valid proof records (Implemented, proving v0, since 5 October 2026). Caveat: consensus does not yet verify the carried proof, it checks the record's statement against native execution and its signature, so today a block producer could claim shard pay with a false proof (ledger P21; the in-consensus verifier is the 0.3.16 fix). Note: unclaimed pool credit is today stranded in the escrow, no rule returns it; the fix rolls an unproven shard's credit into the next proven segment's pool (0.3.16). Open: the single coinbase payout that replaces the burn, and whether it reclaims the share burned so far 0%Treasury, foundation, team or stakeThere is no coin-holder class in consensus and no tax on emission @@ -462,9 +462,9 @@ body.all .pager{display:none} 1. Emission, per blockIGN, new coins on the schedule above80% the block's miner, 20% the proving pool for the provers of that blockNoneNone. Implemented in consensus: the 80/20 coinbase on the devnet 2. Base fee, both gas dimensionsIGNNobodyThe base fee the chain sets per blockAll of it. Implemented on the devnet 3. Priority feeIGN80% the block's miner and provers; 20% the apps whose code ran, per call frameThe tip the sender setsThe share of any frame in an unregistered contract. Implemented on the devnet - 4. External job, at launchThe customer's currency, on the customer's chainThe miner who delivered, through a payout contract keyed by miner addressPriced in dollars per proof; the customer chain's own bond and slashing applyNone; Igneum cannot see the payment. Designed + 4. External job, at launchThe customer's currency, on the customer's chainThe miner who delivered, through a payout contract keyed by miner addressPriced in the customer's money per proof, at or above the subsidy the prover forgoes (a formula in network hash, under Building on Igneum, never a fixed number); the customer chain's own bond and slashing applyNone; Igneum cannot see the payment. Designed 5. External job, after the proof bridgeIGN, on Igneum90% the provers who deliveredThe job fee10%. Designed, phase two - 6. The official client's dev feeIGNThe project, as operator income, never the protocol1 block template in 100 requested with the dev address; off with one flagNone. Implemented, measured on a test network 4 October 2026 + 6. The official client's dev feeIGNThe project, as operator income, never the protocoldefault-on, switchable, 1 percent of the producer share: 1 block template in 100 requested with the dev address; off with one flagNone. Implemented, measured on a test network 4 October 2026

Sources: specification sections 2.5 and 5.1 to 5.4; the engineering log for the devnet receipts and the dev-fee count.

@@ -495,7 +495,7 @@ body.all .pager{display:none}

The size of that third stream today, in numbers: all of Ethereum L1's proving is about USD 36 a day at the September 2026 tracker cost (USD 0.005 a block, 7,200 blocks a day; the tracker figure is a secondary source), against about USD 13,700 a day of Igneum's year-1 emission at USD 0.005 per IGN (31.688 IGN a block, 86,400 blocks a day; the price is an input, not a forecast). So external proving is a small second income at launch and the lottery pays the bills; for proving to become the main income the paid demand would have to grow about 1,000x in dollars (the Horizon lane analysis, 6 October 2026, section 3.11; ledger E19).

-

The honest bear-market case rests on cost. A miner's card is already running and the power is often domestic, so Igneum miners' marginal cost in the proving market is close to power, which is an edge over data-centre provers and nothing more. Which of the two in-chain streams pays more per GPU-second depends on the size of the fleet: on the devnet of 4 October 2026, three machines at 275 million hashes a second, a second of hashing paid about 4.9x a second of proving the pool share; at 10,000 cards the same arithmetic favours proving by about 930x. That is arithmetic on measured devnet rates, approximate, not a market measurement.

+

The honest bear-market case rests on cost. A miner's card is already running and the power is often domestic, so Igneum miners' electricity cost in the proving market is close to power. The price they must charge is another matter: the price a prover must charge is the subsidy it forgoes while it proves, which falls as one over network hash, so the edge over data-centre provers appears only once the network's hash is large (near 100 GH/s for a card proving beside its miner) and is nothing more. Which of the two in-chain streams pays more per GPU-second depends on the size of the fleet: on the devnet of 4 October 2026, three machines at 275 million hashes a second, a second of hashing paid about 4.9x a second of proving the pool share; at 10,000 cards the same arithmetic favours proving by about 930x. That is arithmetic on measured devnet rates, approximate, not a market measurement.

Hardware

The dataset starts at 2 GB and grows (the proposed schedule, fixed at the testnet genesis: 2 GB, doubling at years 4, 12 and 28, the average of half a gigabyte a year), so a 4 GB card mines for about four years and an 8 GB card for about twelve, approximate. Every NVIDIA card from 8 GB proves; 12 GB and up mine and prove; 24 GB on the stock server (eleven rented cards, RTX 3060 to RTX 5090, 6 October 2026). NVIDIA and AMD both work, because the mining program is generated for the architecture both share and the proof system is hash-based. Apple's chips are GPUs with unified memory, so Macs mine too, at about a fifth of a flagship card: Measured, 26.7 against 123 million hashes a second, an Apple M5 Max beside an RTX 5090 on the live devnet, 4 October 2026. A Mac is a poor miner per dollar. There is no CPU mining lane, on purpose, because CPU mining is what botnets farm. Nodes, wallets and exchanges need no GPU at all.

What a miner's hour looks like

@@ -541,7 +541,7 @@ body.all .pager{display:none}

Measured: engineering log, "miner performance: variant racing" (lever 1), "first hourly program swap on the live devnet" and "miner fault guards and the app watchdog" (lever 5), 4 October 2026; the 0.3.6 release plan, the miner-latency gate (lever 4), 5 October 2026; the efficiency-sweep plan, the RTX 5090 log of 4 October 2026 (lever 3). Levers 2 and 3 are shipped code with no fleet measurement yet.

The software's fee, not the protocol's

-

The protocol is fee-free: no dev fund, no fee to any team, foundation or fund. Ember takes a 1% software dev fee, the norm for GPU miners. One block template in 100 is requested with the dev address instead of yours, by a counter, never a random draw, so it is exactly 1 in 100 and anyone can check it from the source or from the chain. A fee block still carries your vote key, so it still adds to your finality weight. Ember prints the fee and the address when it starts, shows it in Settings next to a switch, and --dev-fee 0 turns it off, as does DEV_FEE=0 in a HiveOS flight sheet. Any other client is welcome.

+

The protocol is fee-free: no dev fund, no fee to any team, foundation or fund. Ember takes a 1% software dev fee, the norm for GPU miners: default-on, switchable, 1 percent of the producer share (the 80% of emission that pays the block's miner; the proving pool is paid per record and carries none of it). One block template in 100 is requested with the dev address instead of yours, by a counter, never a random draw, so it is exactly 1 in 100 and anyone can check it from the source or from the chain. A fee block still carries your vote key, so it still adds to your finality weight. Ember prints the fee and the address when it starts, shows it in Settings next to a switch, and --dev-fee 0 turns it off, as does DEV_FEE=0 in a HiveOS flight sheet. Any other client is welcome.

Measured: engineering log, "the software dev fee measured on a test network", 4 October 2026: 9 fee blocks in 785 from two fee-paying miners, 0 from the control at --dev-fee 0, the chain and the miners' counters equal.

What Ember does not claim

@@ -523,7 +226,7 @@
Follow
Live devnet Explorer - Journey + Journey Add Igneum to MetaMask GitHub, spec and vectors Miner downloads: devnet build @@ -560,77 +263,20 @@ diff --git a/site/ledger.html b/site/ledger.html index dc0f3b5b..04fa45d2 100644 --- a/site/ledger.html +++ b/site/ledger.html @@ -51,21 +51,10 @@ @@ -1279,7 +1266,7 @@ details{margin-top:8px;font-size:14px;color:var(--ash)}summary{cursor:pointer;co
Follow
Live devnet Explorer - Journey + Journey Add Igneum to MetaMask GitHub, spec and vectors Miner downloads: devnet build diff --git a/site/litepaper.html b/site/litepaper.html index 141d3510..04bfbeb5 100644 --- a/site/litepaper.html +++ b/site/litepaper.html @@ -350,7 +350,7 @@ body.all .pager{display:none}

Monero's idea, finished for GPUs

-

RandomX has kept chips off Monero since 2019 by making the mining program random, so the only hardware that runs it well is the hardware everyone already owns. Igneum takes the same principle to graphics cards and adds what RandomX never had.

+

RandomX has kept chips off Monero since 2019 (approximate) by making the mining program random, so the only hardware that runs it well is the hardware everyone already owns. Igneum takes the same principle to graphics cards and adds what RandomX never had.

@@ -376,7 +376,7 @@ body.all .pager{display:none}

The proving budget

Gas prices execution. Proving cost is a different number, so Igneum meters it separately: every transaction pays in both dimensions, and each block has a proving-cost budget set in consensus from measured prover throughput. A transaction that is cheap to run and expensive to prove pays for what it costs the provers. Measured on 5 October 2026 (an RTX 5090 under SP1 6.8.1's GPU prover, the shard size the chain adopts from its fee switch, 30,000 proving gas, about 4.7 million prover cycles): one full shard proves in 4.3 seconds and needs 20.4 GB of GPU memory with the card to itself, so a 24 GB card proves full shards and a 12 GB or 16 GB card does not on this prover build, whose floor is 13.9 GB for even an empty shard; mining and proving on one card needs 32 GB today (the prototype-size shard beside the miner peaked at 30.1 GB) and 24 GB once the adopted shard size is live (22.2 GB beside the miner, 13.2 seconds a shard, measured on the 32 GB card; a 24 GB card has not run it yet). The old 12 GB gate on the roadmap was withdrawn on 5 October until a prover build with a smaller floor was measured; on 6 October a patched server proved the same shard at 7.4 to 8.0 GB alone on eleven rented cards from the RTX 3060 to the RTX 5090 (the real-card table), so the gate returns as measured and the patched server is not yet in the shipped app. The first proofs exist: on 4 October 2026 an RTX 5090 proved a small two-transaction block in 1.4 seconds (2.7 seconds compressed), verified in 0.22 and 0.038 seconds, and a laptop CPU proved a three-shard block end to end in 19 minutes. Later that day the same card proved a full shard at the provisional size, 6.75 million prover gas, which executed in 60.8 million cycles: core proof 8.3 seconds, compressed proof 10.9 seconds, verified in 0.040 seconds; a four-shard block took 44.5 seconds of GPU stages end to end. Since 5 October 2026 shards are assigned and proven on the live devnet. The gate asks for a mid-range card, and an RTX 5090 is not one, so the gate stands open. Once the gate is measured, the budget rises by schedule as hardware improves. The proof system is hash-based, which is what runs on consumer cards, and sits behind a versioned interface, so Igneum can adopt a better proof system when one exists by a miner-signalled release, and runs for ever on the current one if none is adopted.

Proving for everyone else

-

The same miners accept proving jobs from other chains. Rollups post a job, a miner wins it, proves it, and is paid. The job market is permissionless and is Designed, not yet built. At launch a job is paid on the customer's own chain, in the customer's currency, to a payout contract keyed by miner address, because Igneum cannot yet see Ethereum. Settlement in IGN, with 10% of each fee burned, follows when the proof bridge lets Igneum see the payment, in phase two. The Igneum miner client can also bid on other proving networks and take the best price, where a miner chooses to hold their collateral: Boundless provers post ZKC and Succinct provers stake PROVE (approximate, from their documentation). The proving market is small today. Igneum does not depend on it. We know of no other proof-of-work chain selling proofs to other chains.

+

The same miners accept proving jobs from other chains. Rollups post a job, a miner wins it, proves it, and is paid. The job market is permissionless and is Designed, not yet built. At launch a job is paid on the customer's own chain, in the customer's currency, to a payout contract keyed by miner address, because Igneum cannot yet see Ethereum. Settlement in IGN, with 10% of each fee burned, follows when the proof bridge lets Igneum see the payment, in phase two. The Igneum miner client can also bid on other proving networks and take the best price, where a miner chooses to hold their collateral: Boundless provers post ZKC and Succinct provers stake PROVE (approximate, from their documentation). The proving market is small today. Igneum does not depend on it. We know of no other proof-of-work chain selling proofs to other chains. Live rows arrive with the public testnet, August 2027.

@@ -503,6 +503,11 @@ body.all .pager{display:none}

The protocol carries no fee: no dev fund, no cut to any team. Ember, the miner software, takes an optional 1% dev fee, the way other GPU miners do. One block template in 100 is requested with the dev address instead of yours, by a counter, not a random draw, so it is exactly 1 in 100 and anyone can check it from the source or from the chain. One flag turns it off (--dev-fee 0, a switch in the app, a line in the HiveOS config). The miner prints the fee and the address when it starts. Any other client is welcome.

One click, for everyone else

Farm operators get a HiveOS package. Everyone else gets Igneum Ember: install it on Windows, macOS or Linux, press Start, and the card is mining to a key the app made for you. It is the same client with a face on it. Implemented, Ember 0.3.9 (5 October 2026): the app shows the key once and has you save it before mining starts, or takes an address you already have; the dashboard shows each card's hash rate, blocks found and accepted by the node, the node's height and peers, the next hourly program, the finality votes sent, and a proving tile with shards assigned, submitted and paid and the verifier state; the chain label reads devnet v4 and the welcome screen says nothing is bought or sold; NVIDIA cards are capped at 80% of their default power limit for stability, with a sweep that looks for the best hash per watt, not yet measured on a card; proving the shards the chain assigns is a switch in Settings (proving v0), beside the 1% dev fee switch, finality voting, and signed updates that install themselves at a quiet moment with a switch to turn that off. It shows no earnings in IGN or in any currency, and it has no hardware-wallet path. Roadmap, Designed and not in the app: earnings per block in IGN with the network named, a figure in your currency, mining paused while you game, and a hardware wallet for your key. Ember is downloaded only from this domain, with the version and size on the button and the hash in the signed manifest the app checks. The next section says what is shipped and what is still a design. Nobody from Igneum will ever ask for your seed. Mining never runs in a browser, because browser compute is slow and browser mining has meant malware since Coinhive. The browser is for the dashboard, and for verifying the chain.

+

Testnet terms

+

No value. Testnet IGN cannot be sold, bought or redeemed, now or at mainnet. There is no airdrop, no points scheme and no promise tied to testnet balances. Mainnet starts from an empty genesis.

+

Resets. The chain restarts from a fresh genesis when a consensus rule changes. Every reset is announced at least seven days ahead on the site and in the app. Balances, contracts and history do not carry over. The devnet that runs today resets without notice.

+

What the app sends home. The app version, a random machine id made at install, your operating system, the node version, the hash rate, and the app, node and miner logs (which name the address the card mines to). They go to the project's log intake, a service Igneum runs on its host, and are read by the maintainers to find faults. Never your seed phrase, never a key, never a file you did not make with the app. Nothing is sold or shared.

+

Wallet set-up for MetaMask: chain id, RPC and the one-click button. The miner software takes an optional 1% fee, off with one flag; the protocol carries no fee to anyone.

Fair launch, announced

Launch date and miner software published a month ahead. Pools live on testnet. HiveOS support on day one. The founders mine from genesis like everyone else, with disclosed addresses and the same software. Nobody has coins before block one. The first 30 days of mainnet run on proof of work alone, with no locked checkpoint, while vote weights build; anyone crediting deposits in that month should treat Igneum as plain proof of work with a 12-hour depth.

@@ -727,7 +732,7 @@ body.all .pager{display:none}
Follow
Live devnetExplorer - Journey + JourneyAdd Igneum to MetaMaskGitHub, spec and vectorsMiner downloads: devnet build diff --git a/site/live-steps.js b/site/live-steps.js index f573c35f..efe04dff 100644 --- a/site/live-steps.js +++ b/site/live-steps.js @@ -46,7 +46,7 @@ else parts.push(sh.length + (sh.length === 1 ? ' shard planned' : ' shards planned')); } if (b.locked) { var idx = cpIndexOf(b.hash); parts.push('locked at checkpoint' + (idx !== null ? ' ' + fmt(idx) : '')); } - else if (b.final) parts.push('final under the newest lock'); + else if (b.final && finality && finality.active) parts.push('final under the newest lock'); return name + ': ' + parts.join('; '); } function setCaption(text, muted) { if (!caption) return; caption.textContent = text; caption.classList.toggle('muted', !!muted); } @@ -91,7 +91,8 @@ // parent edges between blocks on screen blocks.forEach(function (b) { (b.src.parents || []).forEach(function (h) { var q = byHash[h]; if (!q || q.x === null || blocks.indexOf(q) < 0) return; var both = b.step === 'proven' && q.step === 'proven'; ctx.strokeStyle = both ? F.rgba(T.ember, 0.45) : F.rgba(T.ash, 0.22); ctx.lineWidth = both ? 1.5 : 1; ctx.beginPath(); ctx.moveTo(b.x, b.y); var mx = (b.x + q.x) / 2; ctx.bezierCurveTo(mx, b.y, mx, q.y, q.x, q.y); ctx.stroke(); }); }); // the lock line: everything left of the newest locked block is final - var lk = null; blocks.forEach(function (b) { if (b.src.locked && (!lk || b.x > lk.x)) lk = b; }); + // the lock line and the word final are drawn only while finality is active (the owner's rule: no "final" anywhere while finality is paused) + var lk = null; if (!finality || finality.active) blocks.forEach(function (b) { if (b.src.locked && (!lk || b.x > lk.x)) lk = b; }); if (lk) { ctx.strokeStyle = F.rgba(T.ember, 0.55); ctx.lineWidth = 1.5; ctx.setLineDash([5, 6]); ctx.beginPath(); ctx.moveTo(lk.x, 6); ctx.lineTo(lk.x, H - 6); ctx.stroke(); ctx.setLineDash([]); ctx.fillStyle = T.ash; ctx.font = '500 ' + Math.max(10, Math.round(S * 0.42)) + 'px IBM Plex Mono, monospace'; ctx.textAlign = 'right'; ctx.textBaseline = 'alphabetic'; ctx.fillText('final', lk.x - 8, 14); } blocks.forEach(function (b) { var h = S / 2, w = b.step, src = b.src, ex = src.color === 'red'; diff --git a/site/live.html b/site/live.html index 586a6630..5be5ee5a 100644 --- a/site/live.html +++ b/site/live.html @@ -54,6 +54,8 @@ ", css + "", 1); changed = True +if "workers.html" not in s: + m = re.search(r"]*>", s) or re.search(r"]*>", s) + s = s[:m.end()] + "\n " + nav + s[m.end():]; changed = True +if changed: open(f, "w").write(s); print("fleet index.html: nav restored") +PY (cd "$DLSITE" && npx --yes vercel@latest --global-config "$HOME/.config/igneum/vercel" deploy --prod --yes >/dev/null 2>&1) || { echo "deploy failed" >&2; exit 1; } for i in $(seq 1 12); do if curl -fsS --max-time 10 "https://dl.igneum.network/$P/fleet.json?t=$(date +%s)" | python3 -c "import json,sys; d=json.load(sys.stdin); sys.exit(0 if d.get('spend',{}).get('updated_at')==json.load(open('$JSON')).get('spend',{}).get('updated_at') else 1)" 2>/dev/null; then echo "live: https://dl.igneum.network/$P/"; exit 0; fi From 4f9d332c8731e98e9f1559dd9913e0bcad9e6345 Mon Sep 17 00:00:00 2001 From: igneum-labs <337424239+igneum-labs@users.noreply.github.com> Date: Tue, 6 Oct 2026 20:39:44 +0000 Subject: [PATCH 12/15] Roadmap: every calendar month out, each phase worded by its gate; ledger X32; the home-page notes of G4, C2 and X8 point at the litepaper The owner's ruling, 6 October 2026: a phase 4 dated "Apr to Jul 2027" before a phase 5 that is weeks away is a contradiction a reader spots at once. The roadmap (site/litepaper.html Roadmap, site/journey.json, the home page's inlined journey) now names no month. Each phase in the shape phase 5 has, the order unchanged: 1 Under way; closes when the specification is out for external review 2 Under way; closes at its gate 3 Live since 3 October 2026; closes at its gate 4 Closes when the finality design passes external review and one rollup signs for the testnet 5 Weeks away: when the go checklist closes 6 After the testnet has passed its gate: 1,000 independent miners for 30 days and rollup proofs on time The lead reads "Six phases from specification to a fair launch, with four public gates and no calendar dates: each phase closes at its gate." The devnet's 3 October 2026 start is a fact and stays. Not in the roadmap and left as it is: the public benchmark's "January 2027" in For miners and Questions miners ask (its own commitment, for the owner's word). docs/fud-ledger.md: row X32 records the change; rows G4, C2 and X8 gain a status line (the old kept as history) saying the home page no longer carries their sentence and the litepaper does, after the home-page redesign; X3 got its line in the previous commit. The ledger page regenerated (178 entries, 0 leaks). Checks: identity grep 0 hits on every served site file, link check 912 links across 16 pages 0 broken, ledger text 51 of 51, contrast 32 pairs 0 under 4.5:1, orphan check 0 site headings, api tests 5 pass, no calendar month on the roadmap surfaces. Co-Authored-By: Claude Fable 5.1 --- docs/fud-ledger.md | 15 +++++++++++++++ site/index.html | 2 +- site/journey.json | 46 ++++++++++++++++++++++----------------------- site/ledger.html | 20 +++++++++++++------- site/litepaper.html | 12 ++++++------ 5 files changed, 58 insertions(+), 37 deletions(-) diff --git a/docs/fud-ledger.md b/docs/fud-ledger.md index 3d4a5ce6..791cf13b 100644 --- a/docs/fud-ledger.md +++ b/docs/fud-ledger.md @@ -614,6 +614,8 @@ Evidence: design doc, decisions table row "Who are you?". Journey: `site/journey Status: Conceded, stated (5 October 2026, night): `site/litepaper.html`, Governance, "There are no admin keys in consensus"; `site/index.html`, tile "admin keys in consensus". Was: Conceded, wording fix needed. +Status: Conceded, stated (6 October 2026, night): the home page was redrawn as one statement, the live scene, three facts and the downloads, so the tile "admin keys in consensus" is no longer on `site/index.html`; the sentence stands on `site/litepaper.html`, Governance ("There are no admin keys in consensus"). The 5 October line below is the history. + Answer: Correct. Consensus has no admin keys. The genesis apps are contracts and each will have an upgrade policy; the bridge's is the one that matters, and if it starts as a committee bridge (E7) it has keys by definition. The development fund contract named in the quote no longer exists: the fund was removed on 3 October 2026. The litepaper's sentence must be scoped to consensus and each genesis contract must publish its key policy before launch. Evidence: litepaper "Governance". Fix: overclaims list, item 46. @@ -687,6 +689,8 @@ Evidence: litepaper "For miners", hardware paragraph. Wording: overclaims list, Status: Conceded, stated (5 October 2026, night): `site/litepaper.html`, Mining, "no chip publicly shipped, approximate" and "Monero is precedent, not proof"; `site/index.html`, hero, "a chip gains too little to take your place". Was: Conceded, label needed. +Status: Conceded, stated (6 October 2026, night): the home page no longer carries the RandomX paragraph; "since 2019 (approximate)" and "precedent, not proof" stand on `site/litepaper.html` (vs RandomX, Mining). The 5 October line below is the history. + Answer: True. "No chip publicly shipped, approximate" is the defensible phrasing. The argument from Monero is precedent, not proof, and the bounty exists because precedent is not proof. Evidence: none. Fix: overclaims list, item 17. @@ -954,6 +958,8 @@ Sweep (5 October 2026, evening): stated in part. `site/partials/footer.html` on Status: Conceded, stated (5 October 2026, night): `site/litepaper.html`, Roadmap phase 6, and `site/journey.json` phase 6, "No listing is arranged, promised or sought by the project". Was: Conceded, fix now. +Status: Conceded, stated (6 October 2026, night): the home page's journey is no longer shown (the inlined feed remains in the page source); the sentence "No listing is arranged, promised or sought by the project" stands on `site/litepaper.html` Roadmap phase 6 and in `site/journey.json`. The 5 October line below is the history. + Answer: Correct. No listing is arranged, promised or sought by the project. The phrase comes off the roadmap and the homepage journey. Evidence: litepaper "Roadmap", `site/journey.json`. Fix: overclaims list, item 75. @@ -2222,6 +2228,15 @@ Answer: The date was the plan of 3 October 2026 and the chain overtook it: the t Evidence: `docs/plans/testnet-go.md`; `docs/igneum-testnet` notes (genesis 87617621..., seeds seed1 to seed3.testnet.igneum.network, public RPC). Checked by `tools/ci/ledger-text-check.mjs` (the X3 and X31 rows). +### X32. The roadmap carried calendar months beside a testnet that is weeks away +"After X31 the roadmap read phase 4 'Apr to Jul 2027' and phase 6 'Nov 2027' with phase 5 'weeks away' between them, and phases 1 to 3 carried 'Oct to Nov 2026', 'Nov 2026 to Jan 2027' and '20 nodes by Mar 2027'. A reader spots the contradiction at once." + +Status: Fixed, stated (6 October 2026, night, the owner's ruling): every calendar month is out of the roadmap. Each phase is worded by its gate in the shape phase 5 has: 1 "Under way; closes when the specification is out for external review", 2 "Under way; closes at its gate", 3 "Live since 3 October 2026; closes at its gate", 4 "Closes when the finality design passes external review and one rollup signs for the testnet", 5 "Weeks away: when the go checklist closes", 6 "After the testnet has passed its gate: 1,000 independent miners for 30 days and rollup proofs on time". The order is unchanged. The roadmap's lead reads "Six phases from specification to a fair launch, with four public gates and no calendar dates: each phase closes at its gate." (`site/litepaper.html` Roadmap, `site/journey.json`, the home page's inlined journey). The 3 October 2026 start of the devnet is a fact, not a target, and stays. + +Answer: Dates were the plan of 3 October 2026; the gates are the plan. "Dates slip. Gates do not." was already the roadmap's own sentence, and the roadmap now says only the gates. The owner gives a month if he wants one. + +Evidence: `site/litepaper.html` Roadmap; `site/journey.json`; X31. + ## Status updates, 4 October 2026 (round 4) - **F21** (the long-partition fork). Extended: a side locks alone when its own share of its own table reaches two thirds, at `t = W (2/3 - s) / (1 - s)`: 50/50 at 2,400 DAA s on the devnet (about 40 minutes), 10 days on mainnet; the 60 side of 60/40 at 1,200 DAA s (20 minutes), 5 days; the ledger's measured `W/(3R)` = 200 s is this formula at s = 1/2. At HEAD a second certificate at an index is kept, logged and ignored (`processes/finality.rs:650-655, 661-666`) and `fork_choice_lock` (`:886-901`) pins the node. Public text: `site/litepaper.html:511` says a third of the blocks is needed to split finality in a partition; the partition alone does it. Replacement sentence in `docs/review/round-4-2026-10-04.md` section 1 (b). Review id R4.1.5. diff --git a/site/index.html b/site/index.html index e18897fd..5839fbe8 100644 --- a/site/index.html +++ b/site/index.html @@ -257,7 +257,7 @@ - + diff --git a/site/journey.json b/site/journey.json index 78421a7f..f0402beb 100644 --- a/site/journey.json +++ b/site/journey.json @@ -5,14 +5,14 @@ { "id": "phase-1", "name": "Specification", - "when": "Oct to Nov 2026", + "when": "Under way; closes when the specification is out for external review", "status": "active", "line": "Mining generator, shard proving, finality rules, written for external review" }, { "id": "phase-2", "name": "Prove the proving", - "when": "Nov 2026 to Jan 2027", + "when": "Under way; closes at its gate", "status": "active", "line": "Mining program proven on Apple, NVIDIA and AMD; shard proving benchmark on consumer cards still to run", "gate": "A fixed published workload, job received to accepted proof on a declared consumer card, sustained with no growing backlog, reproduced by three independent operators" @@ -20,7 +20,7 @@ { "id": "phase-3", "name": "Devnet", - "when": "Started 3 Oct 2026, 20 nodes by Mar 2027", + "when": "Live since 3 October 2026; closes at its gate", "status": "active", "line": "BlockDAG node mining on the new program, GPU miners on three vendors, EVM execution in build", "gate": "1 block a second held with proofs under 60 s behind the tip" @@ -28,7 +28,7 @@ { "id": "phase-4", "name": "Finality and job market", - "when": "Apr to Jul 2027", + "when": "Closes when the finality design passes external review and one rollup signs for the testnet", "status": "next", "line": "Sustained-mining finality, external proving jobs, miner client with auto-switching", "gate": "Finality design passes external review and one rollup signs for testnet" @@ -44,7 +44,7 @@ { "id": "phase-6", "name": "Mainnet fair launch", - "when": "Nov 2027", + "when": "After the testnet has passed its gate: 1,000 independent miners for 30 days and rollup proofs on time", "status": "next", "line": "Genesis with no premine, 30-day ramp. No listing is arranged, promised or sought by the project" } @@ -207,48 +207,48 @@ }, { "date": "2026-10-04", - "text": "One-click Windows workers: what the Apple M5 Max could measure", - "short": "One-click Windows workers" + "text": "Sim/economy: mining versus proving under stress, agent-based", + "short": "Economy simulation: mining versus proving under stress" }, { "date": "2026-10-04", - "text": "First finality lock on the live devnet: checkpoint 242 at 77.4% of all weight, two hours after genesis", - "short": "First live finality lock: 77.4% of weight, 17 voters" + "text": "Difficulty rule under attack: pool hopping, pulsed rental, timestamp stretching, short-lane oscillation, epoch games, polluted window, block flood", + "short": "Difficulty rule attacked seven ways" }, { "date": "2026-10-04", - "text": "The gfx1036 worker fault and what the Apple M5 Max could and could not reproduce", - "short": "The gfx1036 worker fault and what the Apple M5 Max could and could…" + "text": "Difficulty rule: timestamp attack fixed , simulator regression, 3-node forger test", + "short": "Timestamp attack on the difficulty rule fixed" }, { "date": "2026-10-04", - "text": "A node 60 s behind the clock is silently dead", - "short": "A node 60 s behind the clock is silently dead" + "text": "Devnet-v4 integration: nine branches merged, 3-node test network on the merged node, Windows cross-build", + "short": "Devnet v4: nine branches merged into one node" }, { "date": "2026-10-04", - "text": "First machine on the Igneum Miner app: the RTX 5090 Windows rig's RTX 5090 at 118 MH/s, via Setup.exe", - "short": "First machine on the one-click app: a 5090 at 118 MH/s" + "text": "Generator version 2 adopted: exact load count, fresh-source loads, program acceptance; every vector re-cut, three workers re-checked, 20,000-program census, devnet-v4 binaries rebuilt", + "short": "Generator v2 adopted: every hash does 128 distinct reads" }, { "date": "2026-10-04", - "text": "Difficulty rule v2: the live oscillation, its cause, the DAG replay, the fix behind a height switch", - "short": "Difficulty rule v2" + "text": "Proving v0 on the RTX 5090: first GPU proof of an Igneum block", + "short": "First GPU proof of an Igneum block: 1.4 s on an RTX 5090" }, { "date": "2026-10-04", - "text": "The observer stored nothing for 78 minutes, then 7,022 blocks in two minutes", - "short": "The observer stored nothing for 78 minutes, then 7,022 blocks in two…" + "text": "Devnet v4 cut-over: generator v2, 2/3 floor, three nodes and a seed on a fresh chain", + "short": "Devnet v4 live: generator v2, two-thirds floor, fresh chain" }, { "date": "2026-10-04", - "text": "The RTX 5090 Windows rig at the 14:20 boundary: a worker stuck on the previous epoch", - "short": "The RTX 5090 Windows rig at the 14" + "text": "First hourly program swap on the live devnet: compile-ahead, no pause, two cards", + "short": "First live hourly swap: no pause on Mac, NVIDIA or AMD" }, { "date": "2026-10-04", - "text": "Shard proving on the RTX 5090: a full shard compressed in 10.9 s, a two-shard block aggregated in 2.2 s, all verified", - "short": "Shard layer on the RTX 5090: a full shard proven in 10.9 s, a block aggregated in 2.2 s" + "text": "Proving: devnet v4 shards on the Apple M5 Max CPU, loaded machine", + "short": "Proving: devnet v4 shards on the Apple M5 Max CPU, loaded machine" } ] } diff --git a/site/ledger.html b/site/ledger.html index b5a4dc33..f349436f 100644 --- a/site/ledger.html +++ b/site/ledger.html @@ -4,13 +4,13 @@ Igneum ledger: every criticism, answered - + - + @@ -18,7 +18,7 @@ - + @@ -121,20 +121,20 @@ details{margin-top:8px;font-size:14px;color:var(--ash)}summary{cursor:pointer;co
-
Ledger · 177 entries · regenerated from the repository
+
Ledger · 178 entries · regenerated from the repository

Every criticism, answered or conceded

-

This is every criticism the project expects, in the critic's words, with what was done about it and the date. 177 entries since 3 October 2026. Entries are never deleted; a status that changes keeps its history on the line. Where the critic was right the entry says Conceded. Where nothing has been done it says Open and names what settles it. The founder mined through the GPU years. Ethereum's move to proof of stake in September 2022 ended that income and the miners' place in that chain. This is one person building, with AI systems doing the engineering, the coin he wanted to exist for miners: GPU-mined, the miners are the provers, no founder allocation, every cost stated. Help is welcome and a team is wanted: cryptographers, node engineers, miners who will test. This ledger is the application form: pick an open row and write to hello@igneum.network with its id.

+

This is every criticism the project expects, in the critic's words, with what was done about it and the date. 178 entries since 3 October 2026. Entries are never deleted; a status that changes keeps its history on the line. Where the critic was right the entry says Conceded. Where nothing has been done it says Open and names what settles it. The founder mined through the GPU years. Ethereum's move to proof of stake in September 2022 ended that income and the miners' place in that chain. This is one person building, with AI systems doing the engineering, the coin he wanted to exist for miners: GPU-mined, the miners are the provers, no founder allocation, every cost stated. Help is welcome and a team is wanted: cryptographers, node engineers, miners who will test. This ledger is the application form: pick an open row and write to hello@igneum.network with its id.

PropertyRandomX, MoneroIgneum
- + - +
CountStatusMeaning
7Nothing has settled it yet. The entry names what will
62The critic is right. "Stated" means the public text says so; "not yet stated" means it does not yet
55A code, spec or text change answers it, with the commit or the page named
56A code, spec or text change answers it, with the commit or the page named
27A consensus rule or a decision by the owner answers it, dated
13A measurement or a simulation exists and is named
13A design rule answers it; no measurement is possible yet
177Every entry. The sections: Mining and chips, Finality and attacks, Proving and the zkEVM, Economics and the coin, Governance and the founders, Comparisons, Legal and regulatory, Launch and operations, Builders
178Every entry. The sections: Mining and chips, Finality and attacks, Proving and the zkEVM, Economics and the coin, Governance and the founders, Comparisons, Legal and regulatory, Launch and operations, Builders

Mining and chips

@@ -1232,6 +1232,12 @@ details{margin-top:8px;font-size:14px;color:var(--ash)}summary{cursor:pointer;co
Fixed, stated 6 October 2026, night, the owner's decision): every mention of the month is gone from the site. The sentence everywhere is "The public testnet is weeks away: three seed nodes and the public RPC are up, and it opens when the go checklist closes." (a repository file For miners and the proving section, the roadmap row 5 reads "Weeks away: when the go checklist closes", a repository file phase 5 and the home page's inlined journey carry the same row). No calendar month is given for the testnet; the owner gives one if he wants one.
The answer as first written

The date was the plan of 3 October 2026 and the chain overtook it: the testnet genesis was fixed on 5 October, the three seeds and rpc.testnet.igneum.network are up, and the remaining work is the go checklist. Rows that quoted the month (X3, O-X.2's blocker note, overclaim item 75's replacement text) read the new sentence by reference to this row.

+
+
X32

The roadmap carried calendar months beside a testnet that is weeks away

6 October 2026
+
After X31 the roadmap read phase 4 'Apr to Jul 2027' and phase 6 'Nov 2027' with phase 5 'weeks away' between them, and phases 1 to 3 carried 'Oct to Nov 2026', 'Nov 2026 to Jan 2027' and '20 nodes by Mar 2027'. A reader spots the contradiction at once.
+
Fixed, stated 6 October 2026, night, the owner's ruling): every calendar month is out of the roadmap. Each phase is worded by its gate in the shape phase 5 has: 1 "Under way; closes when the specification is out for external review", 2 "Under way; closes at its gate", 3 "Live since 3 October 2026; closes at its gate", 4 "Closes when the finality design passes external review and one rollup signs for the testnet", 5 "Weeks away: when the go checklist closes", 6 "After the testnet has passed its gate: 1,000 independent miners for 30 days and rollup proofs on time". The order is unchanged. The roadmap's lead reads "Six phases from specification to a fair launch, with four public gates and no calendar dates: each phase closes at its gate." (a repository file Roadmap, a repository file, the home page's inlined journey). The 3 October 2026 start of the devnet is a fact, not a target, and stays.
+
The answer as first written

Dates were the plan of 3 October 2026; the gates are the plan. "Dates slip. Gates do not." was already the roadmap's own sentence, and the roadmap now says only the gates. The owner gives a month if he wants one.

+

Proving and the zkEVM

P23

An unwound transaction leaves the node's view until its sender resends it

6 October 2026
diff --git a/site/litepaper.html b/site/litepaper.html index 98e335de..bd7895fe 100644 --- a/site/litepaper.html +++ b/site/litepaper.html @@ -592,16 +592,16 @@ body.all .pager{display:none}

Roadmap

-

Thirteen months from specification to a fair launch, with four public gates. Each gate is a measurement published whether it passes or fails. Miss it and the phase repeats or the project stops. Each piece of Igneum has a precedent in production somewhere; no chain combines them, and the combination is the risk the gates price.

+

Six phases from specification to a fair launch, with four public gates and no calendar dates: each phase closes at its gate. Each gate is a measurement published whether it passes or fails. Miss it and the phase repeats or the project stops. Each piece of Igneum has a precedent in production somewhere; no chain combines them, and the combination is the risk the gates price.

- - - - + + + + - +
PhaseWhenWhatGate to pass
1. SpecificationOct to Nov 2026Mining generator, shard proving, finality rules, written for external review
2. Prove the provingNov 2026 to Jan 2027Mining program prototype on GPU and CPU, shard proving benchmark on consumer cards. So far: an RTX 5090 proves a shard in 10.9 s compressed; a CPU verifies a warp in 0.61 ms (class v2) to 2.1 ms (class v3). A mid-range card has not been measuredA mid-range GPU proves a shard in under 20 s and a CPU verifies a hash in 10 ms
3. DevnetStarted 3 Oct 2026, 20 nodes by Mar 2027BlockDAG node with the new mining program and EVM execution, 20 nodes. Live now: 1 block a second, difficulty v2, finality v2 locks, proving v0, Ember on every machine. Measured on the live chain on 6 October 2026: proofs land a median of about 380 s behind the tip (the observer, /live), against the 60 s gate1 block a second held with proofs under 60 s behind the tip
4. Finality and job marketApr to Jul 2027Sustained-mining finality, external proving jobs, miner client with auto-switchingFinality design passes external review and one rollup signs for testnet
1. SpecificationUnder way; closes when the specification is out for external reviewMining generator, shard proving, finality rules, written for external review
2. Prove the provingUnder way; closes at its gateMining program prototype on GPU and CPU, shard proving benchmark on consumer cards. So far: an RTX 5090 proves a shard in 10.9 s compressed; a CPU verifies a warp in 0.61 ms (class v2) to 2.1 ms (class v3). A mid-range card has not been measuredA mid-range GPU proves a shard in under 20 s and a CPU verifies a hash in 10 ms
3. DevnetLive since 3 October 2026; closes at its gateBlockDAG node with the new mining program and EVM execution, 20 nodes. Live now: 1 block a second, difficulty v2, finality v2 locks, proving v0, Ember on every machine. Measured on the live chain on 6 October 2026: proofs land a median of about 380 s behind the tip (the observer, /live), against the 60 s gate1 block a second held with proofs under 60 s behind the tip
4. Finality and job marketCloses when the finality design passes external review and one rollup signs for the testnetSustained-mining finality, external proving jobs, miner client with auto-switchingFinality design passes external review and one rollup signs for testnet
5. Public testnetWeeks away: when the go checklist closesOne-click miner app on Windows, macOS and Linux, HiveOS, pools, the first rollup as a proving customer, no coin yet1,000 independent miners run 30 days and rollup proofs are delivered on time
6. Mainnet fair launchNov 2027Genesis with no premine, 30-day ramp. No listing is arranged, promised or sought by the project
6. Mainnet fair launchAfter the testnet has passed its gate: 1,000 independent miners for 30 days and rollup proofs on timeGenesis with no premine, 30-day ramp. No listing is arranged, promised or sought by the project

Dates slip. Gates do not. Phase two decides everything. If consumer GPUs cannot prove shards fast enough, Igneum says so and does not launch on promises.

From 362872af499a5ae6603675f737eb0e4335f4cf8f Mon Sep 17 00:00:00 2001 From: igneum-labs <337424239+igneum-labs@users.noreply.github.com> Date: Tue, 6 Oct 2026 20:44:26 +0000 Subject: [PATCH 13/15] The public benchmark ships with the public testnet: no calendar month; ledger X33 The owner's ruling, 6 October 2026, the same as the roadmap's: the litepaper said the public benchmark with a leaderboard "is January 2027" (For miners) and "ships in January 2027" (Questions miners ask). Both now read "The public benchmark with a leaderboard ships with the public testnet." (the second keeps its tail: its source is public with the repository then, so you run it on your own card and post the number). One gate the reader already knows from the roadmap's phase 5. docs/fud-ledger.md gains X33; the 5 October answers that named the month stay as the history of the plan. The ledger page regenerated (179 entries, 0 leaks). Checks: identity grep 0 hits on every served site file, link check 912 links across 16 pages 0 broken, ledger text 51 of 51, contrast 32 pairs 0 under 4.5:1, orphan check 0 site headings, api tests 5 pass, no calendar month on the litepaper or the home page. Co-Authored-By: Claude Fable 5.1 --- docs/fud-ledger.md | 9 +++++++++ site/index.html | 2 +- site/ledger.html | 20 +++++++++++++------- site/litepaper.html | 4 ++-- 4 files changed, 25 insertions(+), 10 deletions(-) diff --git a/docs/fud-ledger.md b/docs/fud-ledger.md index 791cf13b..e8e808d5 100644 --- a/docs/fud-ledger.md +++ b/docs/fud-ledger.md @@ -2237,6 +2237,15 @@ Answer: Dates were the plan of 3 October 2026; the gates are the plan. "Dates sl Evidence: `site/litepaper.html` Roadmap; `site/journey.json`; X31. +### X33. The public benchmark dated "January 2027" +"After X31 and X32 the litepaper still said the public benchmark with a leaderboard 'is January 2027' (For miners) and 'ships in January 2027' (Questions miners ask), a calendar month beside a roadmap that names none." + +Status: Fixed, stated (6 October 2026, night, the owner's ruling): both sentences read "The public benchmark with a leaderboard ships with the public testnet." (`site/litepaper.html`, For miners and Questions miners ask). The gate is one the reader already knows from the roadmap's phase 5. The 5 October answers in M1, X1 and the overclaims list that name January 2027 are the history of the plan and stay as written. + +Answer: The month was the plan of 3 October 2026. The benchmark tool is part of the testnet's go checklist, so it ships with the testnet; no month is given for either. + +Evidence: `site/litepaper.html`; `docs/plans/testnet-go.md`; X31, X32. + ## Status updates, 4 October 2026 (round 4) - **F21** (the long-partition fork). Extended: a side locks alone when its own share of its own table reaches two thirds, at `t = W (2/3 - s) / (1 - s)`: 50/50 at 2,400 DAA s on the devnet (about 40 minutes), 10 days on mainnet; the 60 side of 60/40 at 1,200 DAA s (20 minutes), 5 days; the ledger's measured `W/(3R)` = 200 s is this formula at s = 1/2. At HEAD a second certificate at an index is kept, logged and ignored (`processes/finality.rs:650-655, 661-666`) and `fork_choice_lock` (`:886-901`) pins the node. Public text: `site/litepaper.html:511` says a third of the blocks is needed to split finality in a partition; the partition alone does it. Replacement sentence in `docs/review/round-4-2026-10-04.md` section 1 (b). Review id R4.1.5. diff --git a/site/index.html b/site/index.html index 5839fbe8..8d6a61e6 100644 --- a/site/index.html +++ b/site/index.html @@ -257,7 +257,7 @@ - + diff --git a/site/ledger.html b/site/ledger.html index f349436f..8b814893 100644 --- a/site/ledger.html +++ b/site/ledger.html @@ -4,13 +4,13 @@ Igneum ledger: every criticism, answered - + - + @@ -18,7 +18,7 @@ - + @@ -121,20 +121,20 @@ details{margin-top:8px;font-size:14px;color:var(--ash)}summary{cursor:pointer;co
-
Ledger · 178 entries · regenerated from the repository
+
Ledger · 179 entries · regenerated from the repository

Every criticism, answered or conceded

-

This is every criticism the project expects, in the critic's words, with what was done about it and the date. 178 entries since 3 October 2026. Entries are never deleted; a status that changes keeps its history on the line. Where the critic was right the entry says Conceded. Where nothing has been done it says Open and names what settles it. The founder mined through the GPU years. Ethereum's move to proof of stake in September 2022 ended that income and the miners' place in that chain. This is one person building, with AI systems doing the engineering, the coin he wanted to exist for miners: GPU-mined, the miners are the provers, no founder allocation, every cost stated. Help is welcome and a team is wanted: cryptographers, node engineers, miners who will test. This ledger is the application form: pick an open row and write to hello@igneum.network with its id.

+

This is every criticism the project expects, in the critic's words, with what was done about it and the date. 179 entries since 3 October 2026. Entries are never deleted; a status that changes keeps its history on the line. Where the critic was right the entry says Conceded. Where nothing has been done it says Open and names what settles it. The founder mined through the GPU years. Ethereum's move to proof of stake in September 2022 ended that income and the miners' place in that chain. This is one person building, with AI systems doing the engineering, the coin he wanted to exist for miners: GPU-mined, the miners are the provers, no founder allocation, every cost stated. Help is welcome and a team is wanted: cryptographers, node engineers, miners who will test. This ledger is the application form: pick an open row and write to hello@igneum.network with its id.

- + - +
CountStatusMeaning
7Nothing has settled it yet. The entry names what will
62The critic is right. "Stated" means the public text says so; "not yet stated" means it does not yet
56A code, spec or text change answers it, with the commit or the page named
57A code, spec or text change answers it, with the commit or the page named
27A consensus rule or a decision by the owner answers it, dated
13A measurement or a simulation exists and is named
13A design rule answers it; no measurement is possible yet
178Every entry. The sections: Mining and chips, Finality and attacks, Proving and the zkEVM, Economics and the coin, Governance and the founders, Comparisons, Legal and regulatory, Launch and operations, Builders
179Every entry. The sections: Mining and chips, Finality and attacks, Proving and the zkEVM, Economics and the coin, Governance and the founders, Comparisons, Legal and regulatory, Launch and operations, Builders

Mining and chips

@@ -1238,6 +1238,12 @@ details{margin-top:8px;font-size:14px;color:var(--ash)}summary{cursor:pointer;co
Fixed, stated 6 October 2026, night, the owner's ruling): every calendar month is out of the roadmap. Each phase is worded by its gate in the shape phase 5 has: 1 "Under way; closes when the specification is out for external review", 2 "Under way; closes at its gate", 3 "Live since 3 October 2026; closes at its gate", 4 "Closes when the finality design passes external review and one rollup signs for the testnet", 5 "Weeks away: when the go checklist closes", 6 "After the testnet has passed its gate: 1,000 independent miners for 30 days and rollup proofs on time". The order is unchanged. The roadmap's lead reads "Six phases from specification to a fair launch, with four public gates and no calendar dates: each phase closes at its gate." (a repository file Roadmap, a repository file, the home page's inlined journey). The 3 October 2026 start of the devnet is a fact, not a target, and stays.
The answer as first written

Dates were the plan of 3 October 2026; the gates are the plan. "Dates slip. Gates do not." was already the roadmap's own sentence, and the roadmap now says only the gates. The owner gives a month if he wants one.

+
+
X33

The public benchmark dated "January 2027"

6 October 2026
+
After X31 and X32 the litepaper still said the public benchmark with a leaderboard 'is January 2027' (For miners) and 'ships in January 2027' (Questions miners ask), a calendar month beside a roadmap that names none.
+
Fixed, stated 6 October 2026, night, the owner's ruling): both sentences read "The public benchmark with a leaderboard ships with the public testnet." (a repository file, For miners and Questions miners ask). The gate is one the reader already knows from the roadmap's phase 5. The 5 October answers in M1, X1 and the overclaims list that name January 2027 are the history of the plan and stay as written.
+
The answer as first written

The month was the plan of 3 October 2026. The benchmark tool is part of the testnet's go checklist, so it ships with the testnet; no month is given for either.

+

Proving and the zkEVM

P23

An unwound transaction leaves the node's view until its sender resends it

6 October 2026
diff --git a/site/litepaper.html b/site/litepaper.html index bd7895fe..54d21ad5 100644 --- a/site/litepaper.html +++ b/site/litepaper.html @@ -624,7 +624,7 @@ body.all .pager{display:none}

Questions miners ask

Kaspa was GPU-mined too, and IceRiver shipped a chip within two years.

-

Kaspa never promised chip resistance, and its hash was one fixed function, simple enough to put on silicon. Igneum's program is different every hour, its dataset grows past any fixed memory, and its program space widens every era, with no human involved. The benchmark tool ships in January 2027, and its source is public with the repository at the public testnet, so you run it on your own card and post the number to a leaderboard by card model. The paid independent cryptanalysis and the public benchmark are where a chip design that beats a GPU by more than 2x would show. And if a chip ever appears, miners are the ones who signal the response.

+

Kaspa never promised chip resistance, and its hash was one fixed function, simple enough to put on silicon. Igneum's program is different every hour, its dataset grows past any fixed memory, and its program space widens every era, with no human involved. The public benchmark with a leaderboard ships with the public testnet, and its source is public with the repository then, so you run it on your own card and post the number. The paid independent cryptanalysis and the public benchmark are where a chip design that beats a GPU by more than 2x would show. And if a chip ever appears, miners are the ones who signal the response.

Don't ASICs make a chain safer?

Three parts. First, what the chain asks hash to do. Hash picks who makes the next block. It does not protect history. A checkpoint locks when signatures reach two thirds of the weight of the last 30 days of blocks (specification section 3). A locked checkpoint is never reorganised by any amount of hash: fork choice runs among the tips that pass through every certified checkpoint. Rented hash has no weight today. It can mine blocks. It cannot rewrite anything older than a lock. A renter with 60% of the network holds 0.0% of the vote on day one (the finality simulator, table B); one matching the whole honest network reaches a third of the weight on day 20 and never two thirds. The lock is fast: median 1,018 ms behind the checkpoint on the three-node test network (engineering log, the finality harness), and on the live devnet the first lock came two hours after genesis, once the window was full. Reorganisations under the lock are shallow: at 1, 2 and 5 blocks a second the deepest honest reorganisation measured was 2, 3 and 7 blocks against a determination depth of 20 (ledger F7, round 2, the fast-time network with 100-ms links); across five continents blocks reached every node at p50 343 ms and p99 666 ms (the 12-node cloud network, 4 October 2026).

Second, the cost the ASIC argument skips. Kaspa's hash went to a handful of chip owners within months: the IceRiver KS0 shipped in July 2023, 17 to 20 months after launch; hashrate went from under 100 PH/s to over 700 PH/s in months and the GPU share was negligible by late 2023 (the ASIC history, row 23, approximate for the share). Bitcoin's hash comes from two manufacturers and a few pools (approximate, from memory). The first chip's owner mines in secret with an edge for months: on Monero, 85% of the hashrate vanished at the April 2018 fork, and chips were found at over 85% again four months after the next fork (row 16). A chip does not add security to a chain; it moves the chain's security to whoever owns the chip first.

@@ -645,7 +645,7 @@ body.all .pager{display:none}

One founder, pseudonymous, working with AI systems. The design, the hostile reviews, the code, the simulators and this document were produced that way, and the commit history says so. The software is shipped by Igneum Labs LTD, Unit IH-00-01-01-OF-01, Level 01, Innovation One, Dubai International Financial Centre. The design remains the work of one founder working with AI systems, reviewed in public through the ledger. What that does and does not mean: the measurements are measurements, reproducible from the commands in the engineering log; the simulators are code anyone can run; the design claims stay design claims until people with names have tried to break them. Every criticism the project expects is kept in a ledger with its honest answer, and the entries that were right are marked conceded; the ledger is published with the repository at the public testnet. No cryptographer is hired yet; the plan budgets one for phases 1 and 2, and external reviewers are named and paid before gate 3.

The founders mine from genesis with disclosed addresses and the same software as everyone else, and hold no coins before block one. The team is pseudonymous and there is no team page. The mining addresses and the code history are published with the repository at the public testnet.

Where is the miner?

-

On the devnet now. Igneum Ember runs on Windows, macOS and Linux, a HiveOS package exists, and the devnet's coins have no value. The public benchmark with a leaderboard by card model is January 2027. Pools come with the public testnet. The public testnet is weeks away: three seed nodes and the public RPC are up, and it opens when the go checklist closes. All of it before any coin exists. Nothing is asked of a miner before they can run something. The Ember section says what is shipped and what is still owed.

+

On the devnet now. Igneum Ember runs on Windows, macOS and Linux, a HiveOS package exists, and the devnet's coins have no value. The public benchmark with a leaderboard ships with the public testnet. Pools come with it. The public testnet is weeks away: three seed nodes and the public RPC are up, and it opens when the go checklist closes. All of it before any coin exists. Nothing is asked of a miner before they can run something. The Ember section says what is shipped and what is still owed.

Will my card still pay in a bear market?

Block reward and in-chain proving move with the price. Proving for other chains is priced in the customer's money, and it is a small market today. What Igneum can promise is that its miners' electricity cost in that market is close to power, because the card is already running on domestic power; the price they must charge is the subsidy they forgo, which falls as one over network hash. That is an edge over data-centre provers at scale and nothing more.

From 3349d8049d2e0b71dec12d5402c6f1a526f08954 Mon Sep 17 00:00:00 2001 From: igneum-labs <337424239+igneum-labs@users.noreply.github.com> Date: Tue, 6 Oct 2026 20:52:33 +0000 Subject: [PATCH 14/15] Horizon lane 5, network: block rate, propagation, node cost, bandwidth, pruning and the snapshot path, with the run A schedule and scripts Co-Authored-By: Claude Fable 5.1 --- docs/analysis/horizon-2026-10.md | 146 +++++++++++++- docs/analysis/horizon/network.md | 244 ++++++++++++++++++++++ sim/horizon/network/README.md | 14 ++ sim/horizon/network/burst-schedule.csv | 11 + sim/horizon/network/controller-10bps.md | 25 +++ sim/horizon/network/controller-1bps.md | 22 ++ sim/horizon/network/controller.py | 94 +++++++++ sim/horizon/network/cost-tables.md | 84 ++++++++ sim/horizon/network/cost.py | 169 ++++++++++++++++ sim/horizon/network/propagation.py | 257 ++++++++++++++++++++++++ sim/horizon/network/results-2.md | 48 +++++ sim/horizon/network/results.md | 48 +++++ sim/horizon/network/run-batch.sh | 23 +++ sim/horizon/network/run-batch2.sh | 16 ++ sim/horizon/network/runA-schedule.csv | 35 ++++ 15 files changed, 1225 insertions(+), 11 deletions(-) create mode 100644 docs/analysis/horizon/network.md create mode 100644 sim/horizon/network/README.md create mode 100644 sim/horizon/network/burst-schedule.csv create mode 100644 sim/horizon/network/controller-10bps.md create mode 100644 sim/horizon/network/controller-1bps.md create mode 100644 sim/horizon/network/controller.py create mode 100644 sim/horizon/network/cost-tables.md create mode 100644 sim/horizon/network/cost.py create mode 100644 sim/horizon/network/propagation.py create mode 100644 sim/horizon/network/results-2.md create mode 100644 sim/horizon/network/results.md create mode 100755 sim/horizon/network/run-batch.sh create mode 100755 sim/horizon/network/run-batch2.sh create mode 100644 sim/horizon/network/runA-schedule.csv diff --git a/docs/analysis/horizon-2026-10.md b/docs/analysis/horizon-2026-10.md index 7c7ff3ca..342309df 100644 --- a/docs/analysis/horizon-2026-10.md +++ b/docs/analysis/horizon-2026-10.md @@ -2,36 +2,160 @@ Written 6 to 7 October 2026 by the Horizon coordinator (branch `horizon`, worktree `igneum-wt-horizon`) on the project lead's ask of 6 October 2026, 20:4x UK: "deep backward and forward predictive research and modelling for our algo and all of our systems: is there room for improvement, room for more coin utility, algo improvements, security improvements, anything we can do to make a 51% attack impossible, basically creating a level of polish that has not been seen before." Later the same evening: "research anything else that we can research too, predictions, forward thinking, what we can actually do that has not been done or applied, think outside the box", "be revolutionary", and "if we create a new way of hashing or a new way of proof of work to revolutionise the space then that's absolutely fine, I want you to deploy everything to create something that has not ever been done before." -The bar main set, and the bar this document holds every claim to: "impossible" is not available to any proof-of-work chain. The bar is that a majority of hash buys nothing: it cannot reverse what finality locked, cannot forge a proof the nodes re-execute, cannot change a rule without 95 percent signalling, and loses more than it earns. Every claim here is a number with a model or a simulation behind it, priced in rented hash at the measured 6 October rate (`docs/bench-log.md`, "Rental cost of hash, 6 October 2026": USD 0.0117 per MH/s-hour, the live devnet at 1.16 GH/s), with its consequence per tier and what to build. +The bar main set, and the bar this document holds every claim to: "impossible" is not available to any proof-of-work chain. The bar is that a majority of hash buys nothing: it cannot reverse what finality locked, cannot forge a proof the nodes re-execute, cannot change a rule without 95 percent signalling, and loses more than it earns. Every claim here is a number with a model or a simulation behind it, priced in rented hash at the measured 6 October rate (`docs/bench-log.md`, "Rental cost of hash, 6 October 2026": USD 0.0117 per MH/s-hour, so USD 11.7 per GH/s-hour and about USD 281 per GH/s-day; the live devnet at 1.16 GH/s), with its consequence per tier and what to build. Hours are agent hours (the project lead's rule: Claude-side work takes hours, never weeks). ## The lanes | Lane | File | State | |---|---|---| -| 1 consensus-security | `docs/analysis/horizon/consensus-security.md`, and the paper `docs/analysis/51-percent.md` | running | -| 2 algorithm | `docs/analysis/horizon/algorithm.md` | running | -| 3 finality-and-weight | `docs/analysis/horizon/finality-and-weight.md` | running | -| 4 economy-and-utility | `docs/analysis/horizon/economy-and-utility.md` | running | -| 5 network | `docs/analysis/horizon/network.md` | queued (waits for the 10 bps rows of `block-rate-devnet2.md`) | -| 6 polish | `docs/analysis/horizon/polish.md` | queued | -| 7 frontier | `docs/analysis/horizon/frontier.md`, model `sim/horizon/frontier/frontier_model.py` | landed 6 Oct 2026, 21:1x UK | -| 8 new-proof-of-work | `docs/analysis/horizon/new-pow.md`, prototypes in `proto-newpow/` | running (designs tonight, measured rows tomorrow afternoon UK) | +| 1 consensus-security | `docs/analysis/horizon/consensus-security.md`, and the paper `docs/analysis/51-percent.md`; models `sim/horizon/consensus-security/` | landed, commit eec2cd7 | +| 2 algorithm | `docs/analysis/horizon/algorithm.md`; model `sim/horizon/algorithm/model.py` | landed, commit 5ff7393 | +| 3 finality-and-weight | `docs/analysis/horizon/finality-and-weight.md`; models `sim/horizon/finality-and-weight/` | landed, commit c3aa502 | +| 4 economy-and-utility | `docs/analysis/horizon/economy-and-utility.md`; models `sim/horizon/economy-and-utility/` | landed, commit 4617a01 | +| 5 network | `docs/analysis/horizon/network.md` | running (takes the 10 bps runs A, A2 and the 1 bps control B) | +| 6 polish | `docs/analysis/horizon/polish.md` | running | +| 7 frontier | `docs/analysis/horizon/frontier.md`; model `sim/horizon/frontier/frontier_model.py` | landed, commit 5ff7393 | +| 8 new-proof-of-work | `docs/analysis/horizon/new-pow.md`; prototypes `proto-newpow/` | designs landed (5ff7393); measured rows by the afternoon of 7 October UK | ## 1. One page for the project lead -(Written last, from the lanes.) +What the night found, in the order it matters. + +1. **The one line where a majority earns more than it spends is the proving pool, not the chain.** Consensus checks a carried proof record's signature and its statement, not the proof (spec 07, 7.7 items 3 and 4; ledger P21, decided as v0 through the public testnet). Any block producer can therefore carry its own fake-proof records and be paid its block share of the 20 percent pool: 11,636 IGN an hour at 51 percent of blocks (lane 1, `cost_model.py`). Every other attack line costs more than it earns. Fix: verify the aggregated segment proof in consensus, 8 to 12 hours, no liveness cost. Until it lands, the public text must not say the 20 percent pool is "paid to provers" without the caveat. +2. **Tonight's two-hour finality pause was the rule working, then the frozen table holding it, and the fix is a signed exit.** Twenty keys holding 42.7 percent of the frozen voter table left in three minutes (the class v4 rehearsal job); checkpoint 6843 saw 53.1 percent of total and the 2/3 rule paused as designed; locks had formed without the hub, so topology is refuted (lane 3, from the observer rows). Rule v2 would have re-locked after 35 minutes; rule v3's frozen table holds it for a window: 2 hours on the devnet, 30 days on mainnet for the same event. Of five candidate rules simulated, only the departure announcement (a `leave` item in blocks, the key out of every denominator one hour later) keeps zero conflicting locks in every partition and eclipse AND ends the pause in under an hour (6 hours). The operational rule costs nothing: a standing box never leaves the live chain for an experiment, and any orchestrated departure over 10 percent of weight goes in slices under 10 percent an hour. +3. **A 51 percent attacker on Igneum buys a 90-second reorder window and nothing past a certificate.** A 45 to 51 percent withholder wins the selected-chain race over a 90-s hold 70 to 85 percent of the time (32 to 46 blocks at 1 bps); the lock lands 63 to 93 s after the checkpoint block and bounds what a majority can reorder; sustained withholding lifts weight share to about 56 percent, never two thirds (lane 1, GHOSTDAG simulator mirroring `protocol.rs`, 20 seeds). The veto (one third of weight) costs 20 days at 51 percent of hash: USD 6k at 1 GH/s, USD 5.8 M at 1 TH/s, half earned back as subsidy; once held, a pause is free and a pause-time 12-hour double spend costs USD 146 to 146k. Weight-gated deep fork choice (a tip forked more than 10 minutes back is a candidate only if its builders hold a third of the weight table at the fork) and vote-or-burn (a silent key's blocks burn 20 percent of their producer share) close those two lines, 16 to 24 hours together, no liveness cost. The paper is `docs/analysis/51-percent.md`. +4. **The chip question is settled in kind and open in degree.** The chip that matters is the stored-dataset memory-controller chip: 5.7x per joule against the 5090 at class v3, 2.1x at class v4 with a chip core as efficient as the GPU's (k = 1), 0.9x against the M5 Max (lane 2, `chip-model-v3` method). The reserve and the era draw buy about nothing against a chip (every drawn parameter is firmware; a reserve block is about USD 4 of silicon) and the public text should say what they do buy. The lever is the latency-shadow size N, and lane 2 and lane 7 agree it belongs in the era draw at genesis as a verifier-bounded ladder {100k, 130k, 200k, 330k, 650k, 1.0M}, each step by 90 percent miner signal, never unconditional (an unconditional doubling retires the M5 Max at era 1). First measured verifier proxies tonight: class v4 5.06 ms cold on a box core, 8.23 with the SMT sibling loaded (passes); dr736 10.51 and 15.49 (out); R0 is dr368. +5. **Fees are not a security budget for a decade, and the proving price is a function of network hash.** All utility curves together pay miners and provers USD 450 a day at launch and USD 4,200 in year 5 against USD 54,800 and 13,700 of daily emission (lane 4). The price a prover must charge is the subsidy it forgoes, 1 / network hash: 100 to 300x Boundless's rate at 1.16 GH/s, 0.2 to 0.4x at 100 GH/s beside its miner. The adopted job floor overprices the market above about USD 0.014 per IGN. A ten-day prover refusal strands 547,570 IGN a day of pool credit in an escrow with no rule to return it. Fixes: decouple the job price from `f_p` (16 hours), roll unproven credit forward (8 hours), publish the price as a formula, never a number (3 hours). +6. **The signalling window can be bought for a day.** The P2 one-day 95 percent window costs about 19 N of hash for 24 hours (USD 534k at 100 GH/s) and would force a class flip onto a fleet not yet on the object; a 6 percent holdout buys delay to the floor for nothing. Seven consecutive daily windows with the floor a week past publish fixes it, 3 hours. The three signalling thresholds (60 parameter, 90 upgrade, 95 class with floor) are stated inconsistently across spec 5.7, CLAUDE.md and the litepaper and must become one sentence. +7. **New proof of work.** Scheme A (mining is proving) is ruled out twice over, by lane 8's design pass (2.9 MB of openings per block, a 32 to 40 ms proof verify against the 10 ms gate, sampleability) and by lane 7's prior-art pass (Ball et al. 2017, Ofelimos 2022, Aleo). Schemes B (a tensor-shaped integer shadow that forces a chip to carry a GPU-class datapath) and C (the dataset derived from the execution state, so every hash proves the miner holds the chain) are in prototype on two rented 4090s; measured rows land by the afternoon. (Section 4, lane 8, when it lands.) +8. **The frontier list, honestly cut.** Do now: the finality weight table carried inside the recursive segment proof (a consensus proof at mergeset cost, a browser that trusts no node for the voter set; 60 hours, measure the in-guest BLS and colouring cycles first), reproducible-build attestations with Ember refusing a release under N of M (12 hours), a WASM verifier of the wrapped block proof in the tab (16 hours), Ember as node, wallet and light client for everyone (20 hours). Never: proving others' chains as the main income (all of Ethereum L1's proving is about USD 36 a day at the Sep 2026 tracker cost against USD 13,700 a day of year-1 emission at USD 0.005), the hash partly a proof, proof verify on a hardware wallet's secure element, a general unverifiable compute market, burn-redirect audit bounties (a dev fund with a veto). + +Rows from lanes 5 (network: block rate, the controller's red-block feedback seen in run A, node and bandwidth tiers), 6 (polish: the ten things the project lead would notice) and 8 (the two prototypes' measured rows) join this page and the table below when they land. ## 2. The ranked list: top 25 across every lane +Rank is payoff over cost across lanes, with safety first, then liveness, then money, then text. "L1 r3" means lane 1's own rank 3; the lane file holds the full evidence row. + | Rank | Item | Lane | Evidence | Model | Hours | Consequence per tier | What to build | Gate | |---|---|---|---|---|---|---|---|---| +| 1 | Verify the aggregated segment proof in consensus; a record whose proof fails against the pinned aggregator key is invalid | L1 r1 | spec 07 7.7 items 3 and 4; P21; a producer captures its block share of the 20 percent pool with fake records | capture = pool x (H inside the window + most outside): 11,636 IGN/h at 51 percent; SP1 light-verifier cost per record 1.8 to 2.4 s measured (bench-log) | 8 to 12 | prover: paid only for proofs; holder: the pool is real; node operator: one verify per record on the proof pool thread; miners: nothing | the verify call in the record check of `consensus/core` proving, the v0 per-shard record payout-only and capped at the exclusive window | fast-time: a fake-proof record is rejected by every node and the producer loses the block; a true record pays | +| 2 | The departure announcement: a `leave` item (key, DAA score, signature) in blocks; D = 1 h later the key is in no denominator, sliding or frozen, and its votes are invalid; app Stop and the fleet library send it | L3 r1, L1 r8 | tonight's pause (L3 3.1): 42.7 percent left in 3 min, the frozen table held a window; sim T: first lock 1 h after a 34, 45 or 50 percent departure, 0 conflicts in every partition, eclipse and equivocator row | `finality_horizon.py` seeds 7, 11, 13 | 6 (+4 for F5's trusted certificate) | holder: a 30-day mainnet pause becomes 1 h when leavers are honest; a silent leaver still costs a window; pool and rig: one message on a clean stop; attacker: buying keys to leave them gains nothing (w + L must still reach 2/3) | the item in the coinbase finality section, the denominator rule in `finality.rs`, the send in Ember and `tools/fleet/lib` | harness: 45 percent leaves with leaves, lock within D + 1 checkpoint; 0 conflicts in 50/50, 60/40 and the 34 percent eclipse | +| 3 | Signalling over 7 consecutive daily windows at 95 percent, the floor no nearer than 7 days past the publish, the stale-box list empty before the floor | L1 r6, L4 4.5 | the P2 one-day window is buyable: 19 N of hash for 24 h (USD 534k at 100 GH/s) forces a flip onto an unready fleet; a 6 percent holdout buys delay for nothing | `signalling.py`, `signal_game.py` | 3 | pool and rig: a week more before a class change and a week of visible share; home miner: a week to update; attacker: the bill x7 in public | the window count in the P2 rule, spec 5.7 text, one sentence carrying 60 / 90 / 95 in spec, CLAUDE.md and the litepaper | fast-time: 6 of 7 days at 95 percent does not flip; 7 does; the harness's failed case | +| 4 | Weight-gated deep fork choice: a tip whose fork point is older than D (10 min of past-median time) is a candidate only if the blocks on it since the fork were produced by keys holding at least 1/3 of the weight table at the fork point | L1 r2 | during a pause or the first 20 days a renter with fresh keys double-spends at the 12-h depth for USD 146 at 1 GH/s | `cost_model.py`; deterministic over the block's past | 10 to 16 | holder and exchange: rented hash cannot reorg past 10 min even while nothing locks; honest miners: nothing (their keys hold the weight); new chain: the first 20 days gain a bound they lack today | the candidate filter in GHOSTDAG tip selection over the certified weight table | fast-time: a 51 percent fresh-key fork from 15 min back is refused by every honest node; a 1/3-weight fork is accepted; partition heal unchanged | +| 5 | Vote-or-burn: a block whose producer key has participation under 0.5 over the presence window in its own past burns 20 percent of its producer share | L1 r3 | the pause as a liveness attack has zero marginal cost once the veto is held | 0.2 x A x 0.8 x subsidy: 7,757 IGN/h at 34 percent (USD 155/h at 0.02) | 6 to 8 | attacker: a pause costs; honest home miner: nothing while signing (the official client signs every checkpoint); pool user: the pool's participation; partition-safe because the test is the block's own past | the participation read in coinbase validation, the burn in the subsidy split | fast-time: a 34 percent silent set's blocks pay 80 percent; a 50/50 partition burns nothing on either side | +| 6 | Roll unproven shard credit into the next proven segment's pool instead of the escrow for ever | L4 r2 | a ten-day prover refusal strands 547,570 IGN a day (5.5 M IGN) with no rule; spec 5.3, 7.7 item 3, 7.8 item 7 are silent | `stress.py` refuse scenario, `stranded_share` | 8 | prover: credit is never lost to the market's slow days; holder: no undecided burn; rollup customer: nothing | the roll-forward in the pool credit split, spec 5.3 text | fast-time: 100 unproven then 10 proven segments return the escrow to zero | +| 7 | Decouple the job price from `f_p`: reserve = measured proving electricity per pgas at the published settlement rate (spec 5.10.3), the 1.5 premium becomes a bid | L4 r1 | at the adopted floor a billion-cycle job is 15 IGN = USD 0.075 / 0.30 / 1.50 against Boundless's 0.21 (approximate); above USD 0.014 per IGN the chain overprices by rule | `utility.py` sections 1 to 2 | 16 | rollup customer: a price that can clear; prover: still paid above electricity; holder: more jobs, more burn | the reserve rule in spec 5.4 and 5.11, the bid field on the job | a simulated job book clears within 20 percent of Boundless's median at all three prices | +| 8 | Operational rule, no protocol change: a standing box never leaves the live chain for an experiment; any orchestrated departure over 10 percent of weight is staged in slices under 10 percent an hour; the fleet library refuses to swap a standing box's chain | L3 r2 | the rehearsal took 36.5 percent at once; sim L2 and M4: a gradual departure costs nothing because every lock re-freezes the table | spec 3.7 item 2 | 1 | fleet operator: finality stays on; every tier: no pause from our own experiments | a refusal in `tools/fleet/lib` plus a `--staged` departure | the next rehearsal keeps `finality_active` true | +| 9 | Peer floor and mesh, and no `unwrap` on a peer-driven path with a sync-request fuzz gate (tonight's pruned-node crash: `consensus/src/processes/sync/mod.rs:87`, plus 27 sibling sites listed in L1) | L1 r4 | run A's star (321 tips, 77 percent red); the hub crash at 19:57Z: any peer can crash any pruned node at zero hash cost | a star with its hub down is n islands; the sibling list by file:line | 9 to 11 | node operator: no crash from a peer's request; home miner and rig: the app alarms under 3 outbound peers or 2 checkpoints without a vote; fleet: no star | the fleet lib dials 3 boxes beside the hands; the node alarm and app line; the unwrap sweep; the fuzz in `tools/ci` | the fuzz runs the sync request space against a pruned node with no panic; the alarm fires on a known-cut case and stays quiet on a healthy one | +| 10 | N (the latency-shadow size) as a genesis ladder per era {100k, 130k, 200k, 330k, 650k, 1.0M}, floor and ceiling fixed at genesis (10x verifier headroom on the 2.5x rule; O-1.14 sets the ceiling), one era draw consumed as for `epoch_len`, each step by 90 percent miner signal over 7 days, never unconditional | L2 r5, L7 finding 1 | HBM4 raises the stored-dataset chip's bare edge (4.9x to 12x depending on tFAW, L2 and L7 disagree and both are labelled); N = 100k holds the chip at 2.1x on GDDR7 at k = 1, N = 330k at 1.3x; an unconditional doubling retires the M5 Max at era 1 (-10.5 percent at 200k) | `model.py --section ladder` (L2 5.3a, the reconciled table) | 6 to 8 | 100k to 130k: the M5 Max -3.3 points, nobody else; 130k to 200k: M5 Max -6 more, the 5090 -2.7 at its cap, the 4070 +21 W; 200k to 330k: compute-bound on every capped NVIDIA card; pool users nothing at any step; verifier +0.17 to 0.56 ms per warp | the ladder in the era-draw table of spec 1.13, the step rule on P2's mechanism | per step: every public-benchmark card within 5 percent of its previous-step rate, bit-exact on three vendors, verifier under 10 ms cold on the O-1.14 core | +| 11 | Aggregate-first vote verification and aggregated in-block carriage as the mainnet default (spec 3.4.2 item 2, decided) | L3 r3 | per-vote verification is 1.6 s per checkpoint per node at 1,000 voters and about 13 s at 8,192 (approximate); measured lock delay p50 0.86 to 1.26 s at 4 to 93 voters on node 1 | L3 4.3 table; blst 0.3.17 `fast_aggregate_verify` already in the fork | 8 | node operator on a laptop: a checkpoint costs one pairing, not a thousand; every tier: lock delay stays under 3 s at 8,192 voters | batch votes per (index, hash), verify once, bisect on failure; the in-block aggregate | fast-time with 1,000 and 8,000 synthetic voters: p50 lock delay under 3 s, CPU under 25 percent of a core, 0 conflicts | +| 12 | The finality weight table carried inside the recursive segment proof, updated one mergeset per segment: a consensus proof at mergeset cost | L7 r1 | phase two's hardest item (P4) becomes incremental on code that exists; about one BLS verify per 30 s (one shard's budget, approximate, unmeasured) | `frontier_model.py` | 60 (first 10: measure) | phone wallet and bridge: finality from one proof, no node trusted for the voter set; rollup customer: one object; prover: one more public-value block per segment | the W2 transition in the aggregator guest, the GHOSTDAG colouring check in-guest | first: BLS verify and colouring cycle counts inside the SP1 guest on a 24 GB card within 2x of the estimate; then a proof per checkpoint under 30 s on a proving-only 5090 | +| 13 | Reproducible-build attestations in a registry contract; Ember refuses a release under N of M attestations | L7 r3 | ledger G7, G13: the update key is an operator channel; Bitcoin's guix.sigs is the precedent with the chain as the sigs repo | text and contract | 12 | every tier: an update needs N independent builders, not one key; fleet: the box's reproducible Windows and Linux builds (CLAUDE.md 6 Oct) are the inputs | the contract, the builder tool, the Ember check | a release with 1 of 3 attestations is refused by Ember; 3 of 3 installs; the known-failed case | +| 14 | Close O-1.14 with a real 2019 laptop run; adopt the half-core proxy as the standing stand-in; dr736 out, dr368 in as R0 | L2 r1 | box proxy: dr736 10.51 ms cold and 15.49 half-core; class v4 5.06 and 8.23 | L2 5.5 | 2 | miners 0; pool verifier cores x1.3 at dr368 instead of x2.4; a 2019 node keeps 1.8 ms under the gate on class v4 | Windows cross build on the box, relay to the laptop, bench, log | ms per warp under 10 cold on the real core for class v4 and dr368 | +| 15 | The vote signs the execution root too (chain id, index, block hash, post_root); a certificate pins the state; snapshots check against the last certificate | L1 r5 | snapshot poisoning: a wrong state above the pin is undetectable today | every voter executes natively (spec 07) | 8 to 12 | holder: a lock is a state lock; node joining from a snapshot: cannot be poisoned; cost: exec lag enters lock latency | the vote message, the pin rule, the snapshot check | fast-time: a poisoned snapshot node cannot join the quorum; honest lock delay rises by the measured exec lag only | +| 16 | `finality_provisional` reported beside `finality_active`, never as a lock, plus a detector-driven `security_alert` (correlated group over 1/3 of a day's blue blocks; pause; conflict) shown by wallets and the explorer as "confirm at 12 h" | L3 r4, L1 r7 | sim P: provisional conflicts in every partition (516 to 1,062 per 360 min), final 0; the exchange guidance exists only as text today | L3 4.1; the detector of counter-asic-3 | 3 + 4 to 6 | exchanges: a third row in the guidance; holder: told when to wait; home miner: a line in Ember | the RPC fields, the explorer and wallet copy, spec 3.9 row | forced pause: explorer and wallet show provisional and the alert; a healthy day shows neither | +| 17 | Every miner-signalled execution parameter enters the consensus digest the same release, with a `tools/ci` check that fails a `Params` field marked signalled and absent from the digest | L4 r5 | signal-then-defect on an execution parameter is a silent state fork unless the handshake refuses the defector; `Params.fees` is in the digest, nothing guarantees the next | `signal_game.py` section 3 | 6 | node operator: no quiet fork; every tier: a defector is refused at the handshake | the check, the digest rule in spec 5.8 | the check fails on a planted field and passes on the live set | +| 18 | The 80/20 split as a 60 percent parameter inside a hard band [10, 30] percent | L4 r4 | not load-bearing at launch traffic; 30 percent buys backlog relief at 100 shards a block (economy-2026-10-04 5.3) | `stress.py` | 10 | prover and miner: a split the market can move inside a band nobody can break; holder: the cap and the halving untouched | the band in spec 5.3 and 5.7 | harness: a 60 percent vote reaches 30 percent; a 100 percent vote cannot pass the band | +| 19 | A WebAssembly verifier of the wrapped block proof in the tab, with the millisecond count shown | L7 r4 | three working precedents (Helios WASM, ziren-wasm-verifier, wasm-groth16-verifier); the certificate half already runs at 58 to 68 ms warm, 139 to 155 ms cold (bench-log round 6, P3) | text | 16 | holder and rollup customer: verify in the browser; the P3 wrapper measurement comes forward | the Groth16 or Plonk wrapper, the WASM verifier on the site | a block proof verifies in the tab under 500 ms on a laptop; the measurement labelled on the page | +| 20 | Ember as node, wallet and light client for everyone | L7 r5 | 1,000 testnet miners become 1,000 verifying nodes; Monero shows about 5,000 peers (monero.fail), Ethereum 8,136 execution nodes (ethernodes), approximate | text | 20 | home miner: one app; node count = miner count; Sybil counts of nodes irrelevant by design; node cost per lane 5's tiers | the node and wallet surfaces inside Ember | a fresh Windows install mines, verifies and shows a balance in the first 60 s with no second download | +| 21 | Work-stake: vote weight as the external-job bond, with the spec sentence "no coin stake; the only thing at stake is 30 days of public work" written first | L7 r2 | a 0.1 percent key has about 16,427 IGN of 30-day pool income plus its vote at risk against a designed 0.0015 IGN coin bond per job | `frontier_model.py` section 3 | 24 (prototype) | prover: a bond nobody can buy; rollup customer: a griefing cost that scales with the prover's standing; holder: "no stake" stays true as "no coin stake" | the bond rule in the job claim | a griefed job costs the griefer its weight for 30 days in the fast-time harness; the spec sentence lands before the prototype | +| 22 | Client-shipped certified checkpoint (index, hash, voter-table digest) refused if missing from the DAG; exec generations spaced geometrically to the finality depth (about 16, 1.8 GB today) | L1 r9, r10 | long-range and seed attacks; a pause-time deep reorg needs a peer's snapshot today | assumevalid's shape; 114.8 MB per snapshot measured | 3 + 3 | every tier: a fresh install cannot be bootstrapped onto a private DAG; node operator: disk for generations, no blocked executor after a deep reorg | the checkpoint in the release, the generation schedule | a cold node refuses a DAG missing the checkpoint; a 5,000-block reorg re-executes from a generation with no snapshot request | +| 23 | Public-text corrections, one bundle: the prover's price as a formula with network hash as the input, never a number; the era draw and the reserve described as schedule changes against fixed datapaths, not unpredictability against a chip, with the chip's USD per MH/s-hour beside the honest cards'; F19's "day 19 to 20" is a v2 number (v3: a full window); funding.md section 4's dev-fee ceiling is 1 percent of the producer share (38,520 / 154,080 / 770,400); the dev fee is "default-on, switchable", not "optional"; "proofs at the cost of power" conditioned on hash; the three signalling thresholds in one sentence | L4 r3, r7; L2 r3; L3; L1 | each row cites its lane section | text | 1 to 3 each | holders and critics read claims that survive review | the litepaper, the customer brief, spec 5.7, funding.md, the ledger F19 | the site's forbidden-strings check and a reviewer's read | +| 24 | Measure the FPGA lane on AWS F2 (one VU47P, HBM2, about USD 1.98 an hour) and replace the ceiling row | L2 r2 | the measured 2.4 G reads/s equals the JEDEC tFAW ceiling; the old 1.9x row rests on a 12 ns tFAW the JEDEC HBM2 table does not give (28 ns); the soft overlay reads 0.30x to 0.47x of the 5090 per watt | L2 5.1 | 8 to 10 plus USD 2 to 8 | none today; the public FPGA claim becomes a measured number | the overlay bench on F2 | reads per second per watt at 1 GiB; the row replaced | +| 25 | Ember tune as the shipped default per card model, and the two fleet measurements every price rests on: the miner's hash loss while each tier proves, and a full 30 M-cycle shard beside the miner on 12 and 16 GB cards | L2 r7, L4 r8 | the 4070 at 3.65 uJ untuned and 2.57 tuned (-30 percent); the hybrid row is the only one that undercuts the market and rests on one 5090 measurement; the 4.7 M fixture is 16 percent of a full shard (linear scaling says 240 s on a 3060, outside the 120-s claim timeout) | L2 5.1; `utility.py hybrid_hash_loss` | 2 + 6 (fleet) | every NVIDIA tier gains 10 to 30 percent per joule; the 12 GB tier learns whether it can claim a full shard in time | the defaults table in the app from the fleet priors; eleven rows with both numbers in `prover-tiers-real-cards.md` | the rows land; the claim timeout is set from them | + +Rows from lanes 5, 6 and 8 are inserted and the table re-ranked when they land (expected: the controller's red-aware correction and the block-rate recommendation from lane 5; the ten the project lead-visible polish rows from lane 6; the class v5 verdicts from lane 8). + +### Not recommended, with the reason (as valuable as the list above) + +| Item | Lanes | Why not | +|---|---|---| +| Prover attestations as a second finality leg | L1 r12, L3 r8 | provers are the miners (same vote keys), so no new party; proof coverage is 2.4 to 4.7 percent of blocks tonight with lag p99 62 s, so every lock would wait on proofs; revisit only after 99 percent of blocks are proven within 60 s for 7 days with 3 provers per block, and even then it adds about a minute of lock delay | +| Time-locked (vesting) weight | L1 r13 | a bought key transfers vested weight, so the acquired-keys bound is unchanged; honest new cohorts wait longer | +| Any automatic re-lock after an abrupt departure | L1 r14, L3 r7 | departure and partition are the same observation in one view; the decaying denominator produces 467 to 473 conflicting locks in a 360-minute 50/50 honest split, the hysteresis floor brings the 13.3 percent equivocator bound back (565 to 597 conflicts at 20 percent); the two-tier rule is a report, never a lock | +| Mining is proving (the lottery's work as a proving step) | L8 scheme A, L7 3.10 | dead on bytes (2.9 MB of openings per block), on the verifier (32 to 40 ms against 10), and on sampleability (Ball et al. 2017, Ofelimos 2022); re-opens Aleo's fastest-prover-wins | +| Proving others' chains as the main income | L7 3.11 | all of Ethereum L1's proving is about USD 36 a day at the Sep 2026 tracker cost against USD 13,700 a day of year-1 emission at USD 0.005; demand must grow 1,000x against a falling cost curve | +| Burn-redirect audit bounties, a review escrow by 60 percent signal | L7 3.6, L4 task 3 | a dev fund with a veto, the switch spec 5.5 removed; the honest payer for a second audit is the entity's own provers | +| Proof verification on a hardware wallet's secure element | L7 3.9 | a bn254 pairing on a Cortex-M-class element is seconds to minutes (approximate); do the companion verify | +| A general compute market for unverifiable work (rendering, inference) | L7 3.12 | an escrow without a verifier is a trust-me payment with lower fees; the verifiable subsets are named and kept | ## 3. What a 51 percent attacker can and cannot do -(Summary of `docs/analysis/51-percent.md`.) +The paper is `docs/analysis/51-percent.md` (lane 1). In one table, at the measured USD 11.7 per GH/s-hour: + +| Hash share | What it buys | For how long | Cost at 1 GH/s / 1 TH/s of network | What it earns | Net | +|---|---|---|---|---|---| +| 20 percent | reorders only the last k = 18 blocks; nothing past a certificate | per attempt | subsidy forgone while withholding | its block share minus reds | loss | +| 34 percent | wins the selected-chain race only inside 60 s (40 percent of the time); holds the veto (blocks every lock) after 20 days of mining at 51 percent, or 30 days at 52 percent of the network's hash | 20 to 30 days to acquire, then free to hold while silent | about USD 6k / 5.8 M to acquire (half earned back as subsidy); a pause then costs nothing | subsidy; nothing from the pause itself unless it double-spends at the 12-h depth during the pause (USD 146 to 146k) | the one line vote-or-burn (rank 5) and weight-gated fork choice (rank 4) close | +| 45 to 51 percent | wins the selected-chain race over a 90-s hold 70 to 85 percent of the time (32 to 46 blocks at 1 bps); turns 26 to 28 percent of honest blocks red; lifts its weight share to about 56 percent, never 2/3 | the 63 to 93 s between a checkpoint block and its lock | USD 11.7 / 11,700 per hour of rented hash; the market could not supply a TH/s on 6 October (RunPod: 0 pods for 20 asks) | a quarter of honest subsidy while withholding; the pool capture of rank 1 (11,636 IGN/h) until the in-consensus verifier lands | loss on the chain; gain only through the pool, which rank 1 closes | +| 67 percent of weight | locks alone | 2.03 x N of hash for 30 days: USD 17,100 per GH/s of network | | subsidy | at rental equilibrium about 33 percent of 30 days of subsidy net | +| any share | a rule change | 95 percent of blue-block weight over the window, or the floor | 19 N for 24 h today (USD 534k at 100 GH/s); x7 under rank 3 | | | + +What no share buys: a block the nodes do not re-execute (the native-execution veto), a state the aggregated proof chain does not commit to, a lock past a certificate under two thirds of weight, a parameter change without the signal. + +The residual risks stated plainly: the first 20 days (no weight table yet); a pause after a sudden departure of a third of weight (30 days under v3 until rank 2 lands); the Sybil count of keys (X5's definition adopted, the measurement scheduled); the proving pool until rank 1; the 2/3-of-total rule under long churn (the window bound: a 50/50 honest partition locks on both sides from day 10). ## 4. Per lane: the three biggest findings +### Lane 1, consensus-security +1. The lock bounds a majority; the k-cluster does not (45 to 51 percent wins a 90-s race 70 to 85 percent of the time; the lock at 63 to 93 s is the bound). +2. The veto is cheap (20 days of 51 percent: USD 6k at 1 GH/s, 5.8 M at 1 TH/s, half earned back) and the pause is then free. +3. The proving pool is capturable today (11,636 IGN/h at 51 percent) with a correct-statement fake-proof record; the only attack that earns more than it costs. + +### Lane 2, algorithm +1. Class v4's verifier measured on a 2022 server core: 4.90 / 5.06 / 8.23 ms (steady / cold / half-core); dr736 9.76 / 10.51 / 15.49, out; R0 is dr368. +2. The f = 1 GDDR7 chip: 5.7x per joule against the 5090 at v3, 2.1x at v4 with k = 1; USD 0.00021 per MH/s-hour against USD 0.0117 rented; the FPGA soft overlay 0.30x to 0.47x per watt. +3. The reserve and the era draw buy about nothing against a chip; the N ladder in the era draw at genesis is the lever, and lane 7's HBM4 column is reconciled with three named disagreements. + +### Lane 3, finality-and-weight +1. Tonight's pause: the 2/3 rule at checkpoint 6843 (53.1 percent of total after a 42.7 percent departure), then the frozen table (Q5) holding it for a window; locks formed without the hub; topology refuted. +2. Only the departure announcement passes the line (0 conflicting locks everywhere, the pause under an hour); the decaying denominator, the hysteresis floor and the two-tier rule fail or are reports. +3. Weight capture costs USD 8,424 x N x W/(1 - W) to rent for the window: the veto 0.52 N for 30 days (USD 4,300 per GH/s of network), a lock alone 2.03 N (USD 17,100); lock delay measured p50 0.86 to 1.26 s at 4 to 93 voters; the path breaks near 1,000 voters on per-vote verification. + +### Lane 4, economy-and-utility +1. The proving price is h/N: 100 to 300x Boundless at 1.16 GH/s, 0.2 to 0.4x at 100 GH/s beside the miner; the adopted floor overprices above USD 0.014 per IGN. +2. Fees are not a security budget for a decade (USD 450 a day at launch, 4,200 in year 5, against 54,800 and 13,700 of emission); sustained honest hash costs USD 24.8 per GH/s-day against USD 281 rented, so the 20-day veto costs 11.8x the honest fleet at every price and year. +3. The 80/20 survives every stress but a ten-day prover refusal, which strands 547,570 IGN a day of pool credit with no rule to return it. + +### Lane 7, frontier +1. HBM4 raises the stored-dataset chip's edge (lane 2 bounds the figure at 4.9x to 12x bare by tFAW); the N schedule belongs in the era draw at genesis. +2. Vote weight is already a slashable, non-purchasable bond: work-stake for external jobs, with "no coin stake" written into the spec first. +3. The consensus proof can be incremental: the weight table inside the recursive segment proof, one mergeset per segment; the first measurement is the in-guest BLS verify and colouring cycle counts. + +### Lanes 5, 6, 8 +(Filled when they land.) + ## 5. What was not run, and why +| Item | Lane | Why | +|---|---|---| +| The in-guest BLS verify and GHOSTDAG colouring cycle counts (rank 12's first gate) | 7, 3 | no 24 GB card free tonight: PC 2 and the fleet were on class v4 and Devnet 2 | +| The real 2019-class core (O-1.14) | 2 | the US laptop was not on the relay; the box's half-core proxy stands in | +| The FPGA soft overlay on real HBM2 | 2 | no FPGA in the fleet; AWS F2 plan written | +| The AMD watts at every N | 2 | the ADLX sampler row is owed on the runner's `--cards-off` mechanism (counter-asic-3-status 6a) | +| A `cargo bench` of `fast_aggregate_verify` at 93, 1,000 and 8,192 keys | 3 | the BLS figures are arithmetic on blst's published timings, approximate | +| The 10 bps runs A2 (mesh) and B (control) | 5 | landing during the night; lane 5 reads them before it closes | +| The full 30 M-cycle shard beside the miner on any tier; the hash loss while proving on Ampere and Ada | 4 | the fleet measured the 4.7 M fixture only | +| Market prices for Taiko-class batch proving and Bonsai | 4 | not published; marked approximate | +| The observed end of tonight's pause | 3 | expected at DAA 216,402, about 20:40Z; the timeline closes when the observer rows show the lock | + ## 6. Rules and corrections for main + +| # | Rule or correction | Lane | State | +|---|---|---|---| +| 1 | P21 is a priced economic hole until the in-consensus verifier lands; the 20 percent pool is not "paid to provers" without the caveat | 1 | relayed | +| 2 | The one-day P2 signalling window is buyable for a day; seven consecutive windows | 1, 4 | relayed | +| 3 | The DAG controller reads blue work only, so a withholder or a star eases difficulty 16 to 32 percent (run A's loop) | 1, 5 | relayed; lane 5 owns the fix | +| 4 | No `unwrap` on a peer-driven path; the sibling list; a sync-request fuzz in `tools/ci` | 1 | relayed | +| 5 | A standing box never leaves the live chain for an experiment; orchestrated departures over 10 percent staged under 10 percent an hour | 3 | relayed; the fleet library refusal is rank 8 | +| 6 | Under rule v3 any sudden departure of a third of weight is a 30-day mainnet pause; the leave item is the one safe way to shorten it | 3 | relayed | +| 7 | F19's "stalls until day 19 to 20" is a v2 number; under v3 a full window | 3 | relayed, ledger text owed | +| 8 | "No coin stake; the only thing at stake is 30 days of public work" into spec 03/05 and the litepaper before any work-stake prototype | 7 | relayed; routed to the site-miner agent | +| 9 | The litepaper's "proving: a second income" carries the USD 36 a day arithmetic | 7 | relayed; routed | +| 10 | Ember's updater installs nothing while finality is paused | 7 | relayed; sent to the Ember agent for 0.3.16 | +| 11 | The customer brief's "priced in dollars per proof" and the litepaper's "proofs at the cost of power" are conditioned on network hash | 4 | relayed | +| 12 | The three signalling thresholds (60 / 90 / 95) in one sentence across spec 5.7, CLAUDE.md and the litepaper | 4 | relayed | +| 13 | The spec is silent on pool credit nobody claims; today it is a burn nobody decided | 4 | relayed | +| 14 | funding.md section 4 overstates the dev-fee ceiling by a quarter | 4 | relayed | +| 15 | Any future miner-signalled execution parameter outside the consensus digest makes signal-then-defect a quiet state fork | 4 | relayed | +| 16 | The era draw and the reserve are not unpredictability against a chip; the public text should say what they buy | 2 | relayed | diff --git a/docs/analysis/horizon/network.md b/docs/analysis/horizon/network.md new file mode 100644 index 00000000..5ecbfe85 --- /dev/null +++ b/docs/analysis/horizon/network.md @@ -0,0 +1,244 @@ +# Horizon lane 5: network. Block rate, propagation, node cost, bandwidth, pruning and the snapshot path + +6 October 2026, evening UK. Lane 5 of the Horizon programme. Worktree `/Users/joshm/Projects/igneum-wt-horizon`, branch `horizon`. Scripts in `sim/horizon/network/` (README there). Nothing live was touched: the Devnet 2 seed log on igneum-build-1 and `~/Desktop/fleet/bps/A.jsonl` were read, never written. + +What was read: `docs/spec/02-consensus.md` (2.1 parameters, 2.3 the difficulty rule, 2.4 header, 2.5 emission), `08-client-security.md`, `10-light-client.md`; the fud-close worktree's `docs/spec/03-finality.md` C1 and 3.4.2 (vote item 281 bytes, the bitmap and per-block bounds); `docs/fud-ledger.md` M20, M21 (block sizes, k re-derived, the 490 KB body run), X20, M30; `docs/bench-log.md` entries "4 October 2026, cloud devnet" (inter-region RTT and propagation), "ledger M30" (RSS, the 256 MiB cache, the s8 steady slope), "6 October 2026, 12:25 to 13:20Z, the finality route" (the seed as the only peer, the route overflow), "Rental cost of hash, 6 October 2026"; `docs/plans/hands-on-build-1.md` (node 1 and the observer: data dirs 856 and 822 MB, the 127.6 MB exec snapshot), `seed-nodes.md`, `cloud-devnet.md`, `release-0.3.14.md` (the snapshot path and the restart pin), `release-0.3.13.md` 4a; the fleet worktree's `docs/analysis/block-rate-devnet2.md` (read at 19:5xZ and again at 20:1xZ: RUN_A, RUN_B, TIER_TABLE and RECOMMENDATION are still placeholders), `tools/fleet/bps-collect.py`, `box-dn2.sh` (one `--addpeer=`: the star), `devnet2-override.json`, `docs/bench-log.md` of that branch; the live record of run A: `~/Desktop/fleet/bps/A.jsonl` (43 rows, 19:18 to 19:51Z), `collect-A.log`, `runA-start2.log`, `runB.sh` (run B starts at 20:25Z) and the seed's log `/home/build/dn2seed-A.log` on igneum-build-1 (20,529 lines, 19:17 to 19:59Z, read over ssh); `vendor/rusty-kaspa` `consensus/core/src/config/bps.rs` (the k table, `calculate_ghostdag_k`, parents, mergeset, pruning depth), `constants.rs` (delay bound 5 s, delta 0.01, DAA window), `params.rs` (mainnet `BlockrateParams::new::<10>()`, Crescendo activation 110,165,000), `consensus/src/processes/difficulty.rs` and `window.rs` (the window holds every mergeset block, blue and red), `docs/crescendo-guide.md` (10-bps node requirements); the fork `vendor/igneum-node` (read-only) at `release-0.3.14-node`: `consensus/core/src/igneum.rs` `difficulty` (CAP_BLOCKS 20, the sanitised clock, the clamps), `consensus/src/processes/difficulty.rs` `igneum_difficulty_bits` (blue-work steps on the selected chain), `consensus/src/processes/finality.rs` (MAX_VOTES_PER_BLOCK 48, KEEP_CHECKPOINTS 2,000), `igneum/exec/src/proving.rs` `p2p_snapshot_gate` and `on_exec_snapshot`, `igneum/exec/src/snapshot.rs`; the fork branch `devnet2-bps` 1279a1d6 (`IGNEUMD_DEVNET_BPS`); lane 3's `finality-and-weight.md` sections 5.5 and 8 (the aggregation path tonight), lane 4's `sim/horizon/consensus-security/ghostdag_results_{1,10}bps.md`. + +## 1. The question and the answer in one paragraph + +the project lead asked for Kaspa's answer to solo-miner variance: a higher block rate. Run A ran Devnet 2 at 10 blocks per second through one seed and produced 77 percent red blocks, 321 tips and a 55-block reorg. The propagation model says the links and the star did not do that: with the measured latencies it predicts under 0.1 percent red at 10 bps in a star and in a mesh. What did it is the seed's CPU per block, measured at 61 ms (narrow DAG) to 345 ms (mergeset 150 to 200), against a budget of 100 ms per block at 10 bps; with that cost in the model the star gives 47 to 88 percent red and queueing waits of 26 to 1,769 s, which are the "Accepted 100 blocks via relay" batches in the log. The difficulty rule then read blue work over a chain step capped at 2 s and hardened until the DAG ran at 2 s / (chain-step spacing) of target (model 4 blocks/s at a 5-s spacing, record 3.3 to 3.6), while a narrower-but-still-wide DAG would have made it ease (the direction main reported); counting every mergeset block over the real span, as Kaspa's window does, is unbiased in both regimes. The block rate for the public testnet is 1 bps; 10 bps is a gated step that needs the per-block node cost under 50 ms on a laptop core at a mergeset of 248, the checkpoint interval and the clock cap re-denominated in DAA seconds, and vote aggregation, because with C1 in blue blocks 8,192 voters at 10 bps are 66 GB per node per day of votes. + +## 2. Method + +| Step | What | Where | Machine, lock | +|---|---|---|---| +| Record | run A's per-minute rows (blocks, blue score, tips, peers, exec tip, CPU, RSS, bytes), the seed log's `PoW accepted` per minute, `Processed` lines (parents, mergeset per 10 s), `Finality: checkpoint N determined` (blue score against DAA), reorg lines, route drops | `~/Desktop/fleet/bps/A.jsonl`; `/home/build/dn2seed-A.log` on igneum-build-1 | read only | +| Propagation | event simulation: Poisson production, star or mesh, lognormal links, inv/request/block hops, a hub (or every node) as a single server with a per-block cost, GHOSTDAG colouring with the first k-cluster condition, parent and mergeset caps, reorg depth at miner 0 | `sim/horizon/network/propagation.py`, `results.md`, `results-2.md` | Mac, `with-lock.sh run nice -n 19`, seed 7 | +| Controller | the fork's estimator against a wide DAG in closed loop with the 3% / 10% clamps, against the whole-DAG estimator and a red-corrected blue estimator | `controller.py`, `controller-10bps.md`, `controller-1bps.md` | Mac, seed-free (deterministic) | +| Arithmetic | k and parameters per rate, votes and bounds, bytes per node per day, CPU budget, RSS and disk, pruned and archival growth, subsidy and payout intervals, finality timing, light-client bytes | `cost.py`, `cost-tables.md` | Mac | + +Nothing was built. No node ran. The fleet's run B (1 bps control, starts 20:25Z) and the mesh variant A2 (not scripted in the fleet worktree at 20:1xZ: no `mesh` or `A2` in `tools/fleet/` or its plans) had not landed when this file closed; section 7 names what they owe. + +## 3. Evidence + +### 3.1 Run A, measured (igneum-devnet-2, 10 bps, 41 miners through the seed on igneum-build-1, genesis bits 505413632) + +Phases from the seed log's checkpoint series (blue score against DAA score; red share = 1 - blue / DAA over the interval) and `Processed` lines; CPU per block from `A.jsonl` `cpu_rss` (ps lifetime %CPU times elapsed, differenced) over the accepted-block deltas. + +| Window (Z) | Production at the seed, blocks/s | Blue rate, blocks/s | Red share over the window | Mergeset per block (Processed) | Parents | Tips at the seed | Hub CPU per accepted block | Hub RSS | What the log shows | +|---|---|---|---|---|---|---|---|---|---| +| 19:23 to 19:29 | pods join (peers=1 each, blocks=0 synced=false on some), 26 blocks/s peak at 19:30 | | 33% (cp 1 to 29) | 1 to 35 | 1 to 16 | 6 to 30 (pods) | 61 ms | 0.45 to 1.9 GB | the 55-block reorg at 19:27:41Z "unwinding to height 1" (a late-joining pod's chain from genesis) | +| 19:29 to 19:31 | 19 to 26 | 1.3 | 77% (cp 29 to 37) | 29 to 36 | 15 | | 115 ms | 1.9 to 2.5 GB | | +| 19:31 to 19:37 | 13 to 19 | 0.7 | 94% (cp 37 to 45) | 53 to 197 | 12 to 15 | 295 | | 2.5 to 4.0 GB | `Accepted 97 / 100 / 31 blocks ... via relay` batches; headers ahead of blocks | +| 19:37 to 19:41 | 8 to 14 | 0.5 | 95% (cp 45 to 49) | 150 to 190 | 12 | 218 to 295 | 345 ms (19:37 to 19:51 mean) | 4.0 to 4.5 GB | window filled at 19:37Z; first lock cp 47 at 19:40:08Z | +| 19:41 to 19:47 | 3 to 7 | 1.0 | 50 to 88% (cp 49 to 61) | 80 to 125 | 13 to 15 | 230 to 350 | | 4.6 to 5.2 GB | `incoming route for IgneumFinality is full, message dropped ... the peer stays`: 109 to 2,547 drops per peer by 19:56Z | +| 19:47 to 19:59 | 3.3 to 3.7 | 1.4 to 1.8 | 41 to 56% (cp 61 to 92) | 47 to 90 | 15 to 16 | 348 to 396 | | 5.4 GB at 19:51 | 16 of 47 checkpoints after the window filled LOCKED (cp 47 to 79); cp 61 determined 19:47:00, locked 19:47:18 | +| Cumulative to 19:59:32Z | 6.1 (DAA 12,233 in 2,012 s) | 1.38 (blue 2,786) | 77.2% | | | | | | main's 12.4 blocks/s, 77 percent, 321 tips, max reorg 55 are the 19:3xZ to 19:45Z readings of the same record | + +Other measured rows of the run: 421 selected-chain reorgs at the seed (132 of depth 1, 62 of 2, 42 of 3, 25 of 4, 19 of 5; 2 of 43, 1 of 44, 1 of 55); the seed's `rx_tx` counter (netns-wide, so an upper bound) 325 MB in and 2,133 MB out between 19:37:05 and 19:51:15Z (850 s, 4,284 accepted blocks): 2.5 MB/s out, 61 KB/s per peer, about 12 KB per block per peer (a block with its finality section of up to 48 votes at 281 bytes is about 14 KB); exec tip 154 chain blocks at 19:51Z against 11,239 blocks (the executor follows the selected chain, which advanced about one chain block per 10 s at the widest). The run's difficulty values are not in the record: `bps-collect.py` strips `difficulty=` from the watch line before storing it and the node log carries no bits; main's statement that the rule "lowered difficulty" is therefore unverified here, and the production curve (26 to 3.5 blocks/s at a hash the pods' peers=41 say stayed connected) is the measured fact section 5.4 reads. + +### 3.2 Measured propagation and block sizes (the inputs the model takes) + +| Quantity | Value | Source | Label | +|---|---|---|---| +| Inter-region RTT | 35 ms (hel1-fsn1) to 289 ms (sin-ash); 0.4 to 0.7 inside a location | bench-log, cloud devnet 4 Oct | measured | +| Block propagation to 80% of 12 nodes over about 3 hops, 723-byte bodies | p50 343, p90 497, p99 666, max 2,313 ms | same | measured | +| One relay hop on 100-ms proxied links (inv, request, block) | p50 318 to 329 ms; own-node processing 6 to 16 ms | fud-ledger M21 run, 5 Oct | measured | +| Two hops with 490 KB bodies | p50 641, p99 812, max 857 ms (59-byte bodies: 626 / 1,093 / 2,099) | same | measured | +| Live devnet block, 1 coinbase, 22 keys' votes partly | p50 723 B, p90 1,022, max 6,908; coinbase payload p50 395, max 6,580 | fud-ledger M21 sweep | measured | +| Vote item, certificate, proof record | 281 B; 273 B + V/8; 274 B | spec 03 3.4.2 (fud-close), fork | cited | +| k from the measured delays at 1 bps | p99 0.67 s gives k 5, max 2.3 s gives k 10; k 18 is the 5-s bound | fud-ledger M21, `calculate_ghostdag_k` | cited | +| Kaspa 10-bps node requirements | 8 cores, 16 GB RAM, 256 GB SSD, 40 Mbit/s minimum; 12 to 16 cores, 32 GB preferred | `vendor/rusty-kaspa/docs/crescendo-guide.md` | cited | + +### 3.3 Measured node figures + +| Quantity | Value | Source | Label | +|---|---|---|---| +| PoW cache | 256 MiB per day key, KEEP_DAYS 3, 768 MiB worst, 512 MiB around midnight UTC | bench-log M30 entry | measured | +| RSS slope, narrow DAG, 60x fast time, 1 block/s | 30.2 MB per 1,000 blocks (s8 steady, 1,500 blocks) | same | measured | +| The live app node on the devnet profile | 1,081 MB at 27 min, 2,258 MB at 4 h 14 min (about 80 KB per block, derived) | same | measured, derived | +| Run A's seed | 153 MB before the chain, 5.42 GB at 12,000 blocks (about 440 KB per block; 41 peers, mergeset up to 197) | A.jsonl | measured | +| Exec snapshot | 127,564,588 B at tip 141,700 chain blocks (0.9 KB per chain block); `ExecState.records` 1 to 2 KB per chain block | hands-on-build-1.md; M30 note | measured; approximate | +| Node 1 and observer data dirs after 3 days | 856 MB and 822 MB | hands-on-build-1.md | measured | +| Lottery verify per header | class v3 2.79 ms on a loaded M5 Max core; class v4 4.90 steady, 5.06 cold, 8.23 ms on a half core (box proxy); gate 10 ms | consequences C29; lane 2 section 5.5; spec 01 | measured | +| Hub CPU per accepted block at 10 bps | 61 ms (mergeset about 8), 115 ms (about 30), 345 ms (150 to 200) | A.jsonl, section 3.1 | measured | + +### 3.4 What the fleet still owes (read `block-rate-devnet2.md` at 20:1xZ) + +RUN_A, RUN_B, TIER_TABLE and RECOMMENDATION are placeholders. Run B (1 bps, same boxes, fresh genesis, 30 min) starts at 20:25Z by `runB.sh` and its rows land about 21:00Z; the mesh variant A2 is not scripted (`box-dn2.sh` takes one `--addpeer`). The collector's `red` field is 0 in every row (its grep pattern matches no log line), so the fleet's red share must come from blue score against DAA as section 3.1 does; its `--report` payout arithmetic uses 28 MH/s for a 4070 where the brief uses 25. Section 5.2 states this lane's prediction for A2 before its rows land. + +## 4. Model + +Inputs are labelled measured (M), cited (C), simulated (S) or approximate (A). + +1. **k and the parameters per rate** (C, `bps.rs`): k = min k with P(Poisson(2 D lambda) > k) < 0.01 at D = 5 s: 18, 124, 362 at 1, 10, 32 bps; 1,074 at 100 bps (the table stops at 32; `calculate_ghostdag_k` in f64 underflows at x = 1,000, the lane's `cost.py` does it in log space). Parents = clamp(k/2, 10, 16); mergeset limit = clamp(2k, 180, 512); merge depth 3,600 bps; finality 43,200 bps; pruning max(108,000 bps, the Prunality lower bound); maturity 100 bps. +2. **Red share from topology and delay** (S, `propagation.py`): blocks arrive as Poisson(bps) split over miners; a block reaches a peer after one hop = 3 lognormal link latencies (median `--link`, sigma 0.29 from the cloud devnet's p50/p90) plus 10 ms processing; a star hub (or every node, `--node-s0`) is a single server with service s0 + s1 x mergeset ms and a FIFO queue; each block's colour is GHOSTDAG's first k-cluster condition over its own past (deterministic given parents); red share = reds in the mergesets of the final selected chain over merged blocks. The abstract rule behind it: reds appear when blocks in flight 2 d lambda exceed k, where d is the effective delay including queueing. +3. **Hub queue** (A, M/M/1 reading): utilisation rho = lambda x s; the knee is rho = 1: s = 100 ms at 10 bps, 1 s at 1 bps, 31 ms at 32 bps, 10 ms at 100 bps. Above the knee the wait grows without bound and d becomes the wait, not the link. +4. **The controller against a wide DAG** (C for the rule, `controller.py` for the loop): the fork walks the selected chain; a step carries work = blue_work(b) - blue_work(selected parent) (the mergeset blues only) and solvetime = min(clock step, 20 T) (`igneum.rs` CAP_BLOCKS, `difficulty.rs` `igneum_difficulty_bits`). With chain-step spacing sigma = max(1/lambda, d), mergeset m = lambda sigma and blues = min(m, k + 1): estimate / true hash = (blues / m) x (sigma / min(sigma, cap)). Two biases: when sigma > cap the step is capped and the rule over-reads by sigma / cap and hardens to a DAG rate of target x cap / sigma; when the DAG is wide (m > k + 1) and sigma <= cap it under-reads by (k + 1) / m and eases. Kaspa's window (`window.rs` `push_mergeset`: every mergeset block above the blue-score floor, blue and red; `difficulty.rs` `calculate_difficulty_bits`: average target x measured span / expected span) reads the whole-DAG rate over the real span: estimate / true = 1 in both regimes. A blue estimator divided by (1 - observed red share) is the same quantity. +5. **Bytes per node per day** (C+S+A, `cost.py` section 3): blocks per day x (header 286 + 32 x parents + body 300 + 274 / 8) + relay overhead (degree x 40 B inv + 40 B request per block, Kaspa's blockrelay flow, A) + votes V x checkpoints per day x 281 + one certificate (273 + V / 8) per block. Checkpoints per day = 2,880 x bps if C1 stays "every 30 blue blocks", 2,880 if it is re-denominated in DAA seconds. +6. **CPU budget** (A): the header pipeline validates in order, so per-block cost x bps must stay under about 0.8 core: 800 ms at 1 bps, 80 at 10, 25 at 32, 8 at 100. Cost = lottery verify (M) + BLS per carried vote (A 1.5 ms, unmeasured on this stack, O-10.3) + GHOSTDAG and reachability (O(k x mergeset) store reads; M at the hub only as a total) + exec and record checks. +7. **RSS and disk** (M slopes, A steady state): caches 768 MiB worst plus the DAG store's growth per block (30, 80 or 440 KB measured in three settings) over the pruning window; disk per day = blocks x bytes + votes + exec records; archival = the same per year without pruning. +8. **Payout and subsidy** (C): subsidy per block = 31.688 IGN / bps at full ramp, 80% to the producer; interval = 1 / (bps x miner hash / network hash). +9. **Finality timing** (C, spec 03 C1): checkpoint every 30 blue blocks, determination d = 60 blue (placeholder, 20 on the devnet), lock about 3 s after determination: cadence 30 / bps s, lock (60 / bps + 3) s if C1 and d stay in blue blocks. + +## 5. Results + +### 5.1 Propagation alone: red share and reorg depth per rate and delay (mesh of degree 8, 42 nodes, ideal nodes; `results.md` section D) + +| bps | k | hop delay median ms | red share | mergeset mean / max | tips mean | reorg max / p99 at miner 0 | delay to 90% of nodes, s | +|---|---|---|---|---|---|---|---| +| 1 | 18 | 50 / 100 / 300 / 1,000 | 0.0% in every cell | 1.1 / 2 to 2.3 / 7 | 1.1 to 2.4 | 1 / 1 to 2 / 2 | 0.09 to 1.82 | +| 10 | 124 | 50 / 100 / 300 / 1,000 | 0.0% in every cell | 1.7 / 6 to 9.2 / 22 | 1.7 to 8.1 | 2 / 1 to 4 / 3 | 0.09 to 1.82 | +| 32 | 362 | 50 / 100 / 300 / 1,000 | 0.0% in every cell | 2.9 / 9 to 18.8 / 42 | 2.9 to 14.4 | 3 / 2 to 6 / 5 | 0.09 to 1.82 | +| 100 | 1,074 | 50 / 100 / 300 / 1,000 | 0.0% in every cell | 6.0 / 17 to 31.1 / 104 | 5.6 to 25.3 | 4 / 3 to 8 / 8 | 0.09 to 1.82 | + +Reading. Kaspa's k is derived for a 5-s delay bound; at the measured delays (under 1 s per hop, under 2 s to 90 percent of nodes) blocks in flight stay far under k at every rate and no block turns red. The natural reorg depth is the DAG width: 2 to 4 chain blocks at 10 bps, 8 at 100 bps with 1-s hops. Lane 4's simulator (uniform one-way delay to every node, 8 miners) reads p99 11 at 10 bps with d = 0.35 s and 99 with d = 2 s (`ghostdag_results_10bps.md` section 1); the two agree in shape (depth grows with bps x d) and differ in the delay model, so the fleet's measured reorg distribution at 10 bps (section 3.1: 421 reorgs, p50 1, 4 over 40) is the arbiter: outside the start artefact and the saturated phase it sits at 1 to 8, inside this lane's model. Kaspa's published 10-bps red rates are not in the clone (`docs/crescendo-guide.md` carries requirements only); from the k derivation the design red rate is under 1 percent of blocks (delta 0.01 on anticones, approximate), which both simulators reproduce. + +### 5.2 Run A predicted from topology, then from the hub's cost (`results.md` A to C, `results-2.md` E, F, I) + +| Model input | Red share | Hub wait | Hub utilisation | Mergeset mean / max | Reorg max | What it says | +|---|---|---|---|---|---|---| +| Star, 41 miners, link 40 ms, ideal hub, run A's measured production schedule, join spread 360 s | 0.0% | 0 | 0 | 3.5 / 14 | 4 | topology and latency alone predict no reds at 10 bps | +| Star, hub cost 6 + 2 x mergeset ms (a linear GHOSTDAG cost), same schedule | 0.0% | 0.00 s | 0.14 | 3.7 / 15 | 3 | a hub under 20 ms per block keeps up | +| Star, hub cost 61 ms per block (M, mergeset 8), same schedule, 1,200 s | 46.8% | 26 s | 0.65 mean (1.6 during the 26 blocks/s burst) | 8.8 / 194 | 43 | the genesis burst alone saturates a 61-ms hub for 5 minutes | +| Star, hub cost 115 ms (M, mergeset 30) | 88.1% | 321 s | 1.23 | 20.7 / 199 | 2 | the measured mid-run regime | +| Star, hub cost 345 ms (M, wide DAG) | 82.6% | 1,769 s | 3.66 | 9.8 / 45 | 3 | relay in batches, most blocks unmerged at the end | +| Measured run A, cumulative | 77.2% | relay batches of 100 blocks | 180% of one core sustained (section 3.1) | 8 to 197 | 55 (start artefact) | | +| Star at a constant 10 bps, hub cost 20 / 40 / 60 / 80 ms | 0.0% | 0.00 / 0.01 / 0.05 / 0.18 s | 0.20 / 0.39 / 0.61 / 0.81 | 3.5 to 4.9 | 2 to 4 | under the knee | +| the same, 100 / 150 ms | 15.2% / 75.2% | 8.5 / 157 s | 1.01 / 1.51 | 20 / 116 and 27 / 66 | 8 / 3 | the knee is 100 ms per block at 10 bps | +| Star at 10 bps, ideal hub, link 100 / 200 / 300 ms | 0.0% | 0 | 0 | 5.8 / 15, 10 / 20, 12.4 / 23 | 3 to 4 | pure latency up to a 2.2-s delay to 90% of nodes gives no reds | +| Star with the 26 blocks/s burst for 5 min then 10 bps, hub 6 + 2 m | 0.0% | 0.01 s | 0.33 | 5.4 / 17 | 3 | the burst alone is harmless to a cheap hub | + +So the model predicts run A's red share from the hub's measured per-block CPU and not from its topology: 47 to 88 percent against 77 percent measured, with the waits that the log's relay batches show. The prediction for the mesh variant A2 (`results-2.md` G): a mesh of degree 8 whose nodes each pay 40 or 80 ms per block runs 10 bps at 0 percent red (node wait 0.01 and 0.12 s); at 115 ms per block every node is its own hub and the mesh goes to 52 percent red with 26-s waits and reorgs of 16. The pods run the same binary as the seed on smaller CPUs, so A2 with tonight's genesis bits (a 26 blocks/s burst) is predicted red again; A2 with genesis bits set for 10 bps at 2 GH/s and every node started synced is predicted under 5 percent red if the per-block cost on a pod is under 80 ms, and 50 percent or more if it is 115 ms. The control at 1 bps (`results-2.md` H): 115 or 345 ms per block gives 0 percent red and waits of 0.01 to 0.07 s, which is the live devnet's experience. + +### 5.3 What the hub's 61 to 345 ms per block is made of (the breakdown is not measured; the candidates and their bounds) + +| Component | Per block at 10 bps | Label | Note | +|---|---|---|---| +| Lottery verify, class v3 (the Devnet 2 override activates v3 at epoch 1) | 2.8 ms | M | one warp per header | +| BLS verification of carried votes, up to 48 per block | up to 72 ms at 1.5 ms per verify | A | run A's blocks carried up to 48 of 42 voters' votes; the route drops say the finality path was the hot one | +| GHOSTDAG and reachability at mergeset m, k 124 | O(k x m) store reads: 1,000 at m 8, 25,000 at m 200 | C (protocol.rs shape) | the 61 to 345 ms rise tracks m | +| Relay to 41 peers (serialise 14 KB x 41, inv handling) | 2.5 MB/s out measured | M | a mesh node of degree 8 does one fifth of it | +| Exec of chain blocks, record checks | small: one chain block per 10 s at the widest | M | | + +The gate that settles it is a profile (proposal 5). Whatever the split, the serial budget rule of section 4.6 is the design constraint for every block-rate step: at 10 bps the whole per-block path must stay under 80 ms on the slowest node the network wants to keep, at the mergeset limit 248, not at the narrow-DAG average. + +### 5.4 The controller: what the record shows and what the model says (`controller-10bps.md`, `controller-1bps.md`) + +| Regime (10 bps, k 124, cap 2 s) | Fork's estimator (blue work over the capped chain step) | Whole-DAG estimator (Kaspa's window shape) | Blue estimator corrected by (1 - r) | +|---|---|---|---| +| d = 0.3 / 1 / 2 s | DAG rate 10.00, red 0%, estimate 1.00x | 10.00, 1.00x | 10.00, 1.00x | +| d = 3 s | settles at 6.67 blocks/s, estimate 1.50x, difficulty 1.50x the correct value | 10.00 | 10.00 | +| d = 5 s | 4.00 blocks/s, 2.50x | 10.00 | 10.00 | +| d = 10 s | 2.00 blocks/s, 5.00x | 10.00 (red 0%, mergeset 100) | 10.00 | +| d = 30 s (a saturated hub) | 5.21 blocks/s, red 19.5%, estimate 12.1x, difficulty 1.93x | 10.00, red 58%, mergeset 300 | 10.00 | +| 1 bps, k 18, cap 20 s: d = 20 / 30 / 60 s | runs away upward: 179 / 43 / 10 blocks/s, red 97 to 99%, estimate 0.01 to 0.09x (the under-read, main's direction) | 1.00 / 1.00 / 1.17 blocks/s | 1.00 / 1.00 / 1.17 | + +Reading against the record. Run A's production fell from 26 blocks/s to 3.3 to 3.6 while blue stayed 1.4: the DAG hardened. The fork's rule at a chain-step spacing of 5 to 6 s (the hub's queue made chain blocks 10 to 60 s apart at the widest, 3 to 6 s late in the run) settles at target x 2 s / spacing = 3.3 to 4 blocks/s, which is the record's late plateau. The ease direction main reported is the other bias (blue work only) and the model shows it where chain steps stay under the cap while the DAG is wider than k + 1: at 1 bps with d of 20 s or more, or at 10 bps when production exceeds k / (2 d). Either way the rule is reading the wrong quantity: a controller that counts every mergeset block's work over the real span (what Kaspa's `calculate_difficulty_bits` does over its sampled window of blue and red mergeset blocks) holds 10.00 in every cell. A second inconsistency at 10 bps: CAP_BLOCKS 20 is 2 s while FUTURE_TOLERANCE_MS and BACK_TOLERANCE_MS stay 10 s, so a forged stamp (10 s) no longer fits inside half a cap; spec 2.3 derived the 10 s as half the cap at 1 bps. The cap, the tolerances and the 60 T lag bound should be denominated in DAA seconds (20 s, 10 s, 60 s) at every rate, and the step of a chain block that merges m blocks should be allowed m x 20 T before clamping. + +Cost of the bug tonight per tier: a home miner's blocks were 77 percent red (a red inside the DAA window pays its 80% to the merging miner, spec 2.5, so the solo miner lost the subsidy of 3 blocks in 4); a rig the same; a pool user nothing (Devnet 2 is a staging chain); the node operator saw 5.4 GB RSS and 180% CPU on a 96-thread box; a prover saw the exec tip at 154 chain blocks against 11,239 blocks; a holder nothing (no value on Devnet 2). + +### 5.5 Node tiers per block rate (`cost-tables.md` 4 and 5) + +| Rate | Per-block CPU budget (0.8 core) | Class v4 verify share of it (box proxy 4.9 ms; a 2019 laptop core about 2.5x, lane 2's rule: 12 ms) | Votes per block (V/30 if C1 stays in blue blocks) and their BLS cost at V = 1,000 | RSS: caches + DAG store over the 30-h window at 30 / 80 KB per block | Disk per day (blocks, votes at V = 1,000, exec records) | Verdict: laptop 2019-class 8 GB SATA | Raspberry-class 8 GB USB SSD | +|---|---|---|---|---|---|---|---| +| 1 bps | 800 ms | 0.6% (laptop 1.5%) | 33 votes, 50 ms | 0.77 + 3.3 to 8.6 GB | 0.92 GB | runs if the DAG store plateaus under about 4 GB (owed: the 30-h measurement); marginal at 80 KB per block | the same question; CPU fine (class v4 verify under 15 ms, GHOSTDAG at mergeset 1 to 2) | +| 10 bps | 80 ms | 6% (laptop 15%) | 33 votes, 50 ms: 62% of the budget on its own | 0.77 + 33 to 86 GB | 9.0 GB | no: the hub's measured 61 ms at a narrow DAG already uses 76% of the budget on a server core; RSS over 8 GB within hours unless the per-block footprint falls 10x | no | +| 32 bps | 25 ms | 20% (laptop 48%) | 33 votes, 50 ms: over budget | 0.77 + 104 to 276 GB | 29 GB | no | no | +| 100 bps | 8 ms | 61% (laptop 150%) | over budget | 0.77 + 326 to 864 GB | 91 GB | no: the verifier gate alone forbids it | no | + +Pruning changes the disk, not the RSS and CPU: the pruned node keeps the 108,000 DAA-s window (136 MB of headers and bodies at 1 bps, 1.55 GB at 10 bps, `cost-tables.md` 6) plus the UTXO set, the exec state (128 MB measured plus 1.5 KB per chain block) and the finality store's 2,000 indices (KEEP_CHECKPOINTS). What must change for 10 bps is the per-block footprint in RAM (30 to 440 KB measured; Kaspa's 16 GB minimum at 10 bps says their footprint is near 10 KB per block, derived from 16 GB over 1,080,000 blocks, approximate) and the per-block CPU (under 50 ms at mergeset 248 on a laptop core). + +### 5.6 Bandwidth per node per day (`cost-tables.md` 2 and 3) + +| Rate | Blocks (header + body + records) | Relay overhead (degree 8) | Votes at V = 12 / 100 / 1,000 / 8,192, C1 in blue blocks | Certificates at V = 12 / 8,192 | Total at V = 100 | Total at V = 8,192 | Mean Mbit/s at 8,192 | +|---|---|---|---|---|---|---|---| +| 1 | 57 MB | 31 MB | 10 MB / 81 MB / 809 MB / 6.6 GB | 24 MB / 112 MB | 194 MB | 6.8 GB | 0.63 | +| 10 | 649 MB | 311 MB | 97 MB / 809 MB / 8.1 GB / 66 GB | 237 MB / 1.1 GB | 2.0 GB | 68 GB | 6.3 | +| 32 | 2.4 GB | 1.0 GB | 311 MB / 2.6 GB / 26 GB / 212 GB | 759 MB / 3.6 GB | 6.8 GB | 219 GB | 20 | +| 100 | 9.3 GB | 3.1 GB | 971 MB / 8.1 GB / 81 GB / 663 GB | 2.4 GB / 11 GB | 23 GB | 687 GB | 64 | + +With C1 in DAA seconds the vote column is 81 MB / 809 MB / 6.6 GB per day at every rate; with lane 3's aggregated certificate (1.2 KB per checkpoint) in place of carried votes it is 3.5 MB per day. What carries it: a home connection (10 Mbit/s up, 50 down, approximate) carries 1 bps at any voter count and 10 bps at under 1,000 voters or with C1 in seconds; a Raspberry-class box on ethernet the same, bounded by its CPU not its link; a mobile node is a light client: 3.4 MB per day in checkpoint mode at 1 bps and 1,000 voters, 34 MB at 10 bps if C1 stays in blue blocks (`cost-tables.md` 10). A star hub pays its peer count times the block bytes in upload: run A's seed sent 2.5 MB/s (20 Mbit/s) to 41 peers; the three testnet seeds (cx23, 20 TB per month included, `seed-nodes.md`) would spend 6.5 TB per month each at that rate, inside the allowance and outside good sense; the peer floor of proposal 4 spreads it. + +### 5.7 Votes and the 3.4.2 arithmetic per rate (`cost-tables.md` 2) + +| Rate | Checkpoints per day (C1 in blue blocks) | Votes per block at V = 8,192 (V / 30) | Drain capacity per checkpoint at the cap 48 / 384 | Vote bytes per day at 8,192, single votes | Archival votes per year at 1,000 / 8,192 | +|---|---|---|---|---|---| +| 1 | 2,880 | 273 | 1,440 / 11,520 | 6.6 GB | 296 GB / 2.4 TB | +| 10 | 28,800 | 273 | 1,440 / 11,520 | 66 GB | 3.0 TB / 24 TB | +| 32 | 92,160 | 273 | 1,440 / 11,520 | 212 GB | 9.5 TB / 77 TB | +| 100 | 288,000 | 273 | 1,440 / 11,520 | 663 GB | 30 TB / 242 TB | + +Because C1 counts blue blocks, the votes per block and the drain capacity per checkpoint are the same at every rate (the 3.4.2 arithmetic holds: 384 per block drains 8,192 in 21.3 blocks), while the bytes per day and the checkpoint cadence scale with bps: 3-s checkpoints at 10 bps, 0.3 s at 100. Tonight at 42 voters and 10 bps the seed's inbound IgneumFinality route (4,096 deep since the fin-route fix) overflowed at 109 to 2,547 drops per peer, and 16 of 47 determinable checkpoints locked; at 1 bps the same voters cost one tenth. Re-denominating C1 and d in DAA seconds (300 blue blocks and 600 at 10 bps) keeps the finality cost, the lock delay (63 s) and the light-client bytes at their 1-bps values across every rate step; it is a one-line spec change and a parameter in the fork. + +### 5.8 Pruning, archival and the snapshot path + +What a pruned node keeps (spec 02 2.1, `cost-tables.md` 6): headers and bodies back to the pruning point (108,000 DAA s, never past the latest certified checkpoint, F3), with their GHOSTDAG and reachability data (the 2x index factor is approximate); the pruning proof (levels of headers, `pruning_proof`); the UTXO set; the finality store's last 2,000 indices; the exec state (`exec-snapshot.bin` 128 MB measured at chain block 141,700, growing 0.9 KB per chain block, plus `ExecState.records` 1.5 KB per chain block until a window bounds it, M30 note); the proof-record window (`RECORD_WINDOW_CHAIN_BLOCKS`). An archival node keeps every block and its finality section: 40 GB per year of blocks at 1 bps (453 GB at 10 bps) plus votes as table 5.7, so the archival cost is the votes, not the blocks, until certificates replace carried votes (1.3 GB per year at 1 bps). + +The p2p snapshot path as shipped in 0.3.14 (`proving.rs` `on_exec_snapshot`, `p2p_snapshot_gate`, read on `exec-sync-0313`): a peer's `ExecSnapshot` (version, chain id, genesis, tip number and hash, state root, records, the account and storage dump, fees, epoch, paid shards) is accepted only when its tip is a chain block known to this node's consensus, its tip is not 0, not below this node's exec restart, above this node's own executed tip, and only while the executor is blocked or has no state; and, when the restart pin is configured, the snapshot's record at the restart block must carry the pinned root (`exec_restart_state_root`, the fourteenth override field). The file is then written as the node's own resume point and served onward. Three things it does not check: the snapshot's state root at its tip against anything the chain commits to; the records between the restart block and the tip; agreement among peers. The poisoning attack: a peer of a blocked node (every node is blocked after a reorg deeper than its ring or after a restart below its retention root, the 6 October incident class) serves a snapshot whose tip is a real chain block above the victim's tip, whose record at the restart block is correct, and whose state at the tip is wrong. The victim loads it, executes forward from a false state, persists it, serves it to its own peers, and from then on refuses every honest proof record (`check_record`: the statement over its own records disagrees) while its own prover's records are refused by the network. Bound: it is a liveness attack on the victim and its downstream peers, not a consensus or a proof break (full nodes execute natively and a proof over the false state is a proof of the wrong statement; a light client verifies proofs against the chain's records, not against a node's state), and it needs the attacker among the victim's peers at the moment it is blocked, which the peer floor makes a 1-in-(peer count) race unless the attacker runs most of the victim's peers (an eclipse). The hardening: accept a snapshot only when its state root at the newest proven segment at or below its tip equals the `post_root` of the proof record the chain carries for that segment (the chain already carries 274-byte records in coinbase payloads, so this is a lookup, not a protocol change), and take the (tip, root) pair from N of M peers (the light client's 3 of 5, spec 10.6) before loading; a snapshot that fails either is refused and the peer is dropped for the session. Then poisoning needs a false proof record in the chain, which needs the proving key and a block that carries it, and the bound is the proof system's. Sizes per tier: the snapshot is the state (128 MB today, growing with accounts), so a home node's recovery is a 128 MB download and a sha256; an archival node's history is the table above; a light client never holds one. + +### 5.9 Finality through this lane's eyes (cross-reference to lane 3) + +Lane 3 (`finality-and-weight.md` 1 and 5.5) refutes the topology hypothesis for tonight's pause on the live devnet: certificates 6824 to 6842 formed while node 1 and the observer were down, the zero-aggregator fallback carried a quarter of the certificates, and the pause began at the checkpoint where signing weight fell to 53.1 percent of the frozen table, which is the two-thirds rule; the fleet's star is around the seed (the finality route entry: "on a Vast box the seed is the only peer"), not the Mac. This lane adds the measured shape of that star under load: 41 pods each with peers=1, every block and every vote through one process at 180 percent of one core, 2.5 MB/s of upload, the IgneumFinality route dropping thousands of messages per peer, 16 of 47 checkpoints locked. Lane 3's hub-cut harness case (its proposal 6) and this lane's peer floor (proposal 4) are one piece of work: the floor is what makes the harness case pass (a node with 4 outbound peers keeps blocks and votes when any one peer dies), and the fleet library is where both land. + +### 5.10 Block rate: payout intervals, subsidy, lock delay and the solo-miner answer (`cost-tables.md` 7 to 9) + +| Rate | Subsidy per block (full ramp), producer's 80% | 4070 (25 MH/s) at 1.16 GH/s / 100 GH/s / 1 TH/s / 10 TH/s | 5090 (128 MH/s) at the same | 8x 4090 rig (459 MH/s) at the same | Lock after a checkpoint if d stays 60 blue | +|---|---|---|---|---|---| +| 1 | 31.69 IGN, 25.35 | 46 s / 1.1 h / 11.1 h / 4.6 d | 9.1 s / 13 min / 2.2 h / 21.7 h | 2.5 s / 3.6 min / 36 min / 6.1 h | 63 s | +| 10 | 3.17, 2.54 | 4.6 s / 6.7 min / 1.1 h / 11.1 h | 0.9 s / 1.3 min / 13 min / 2.2 h | 0.3 s / 22 s / 3.6 min / 36 min | 9 s | +| 32 | 0.99, 0.79 | 1.4 s / 2.1 min / 21 min / 3.5 h | 0.3 s / 24 s / 4.1 min / 41 min | 0.1 s / 6.8 s / 1.1 min / 11 min | 4.9 s | +| 100 | 0.32, 0.25 | 0.5 s / 40 s / 6.7 min / 1.1 h | 0.1 s / 7.8 s / 1.3 min / 13 min | 0.0 s / 2.2 s / 22 s / 3.6 min | 3.6 s | + +The solo-miner question: a 4070 at 25 MH/s sees 21.6 blocks a day at 1 bps on a 100 GH/s network (548 IGN a day at full ramp) and 2.16 a day at 1 TH/s (55 IGN); one block a day needs 0.046 bps at 100 GH/s, 0.46 bps at 1 TH/s and 4.6 bps at 10 TH/s. Kaspa's 10 bps buys the solo miner a daily block up to about 20 TH/s; Igneum at 1 bps gives it up to about 2 TH/s, which is 1,700x tonight's hash and USD 562,000 a day of rented hash at the bench entry's USD 281 per GH/s-day. What the rate costs the node, from 5.5 and 5.6: 10x the bytes (2 GB a day at 100 voters), a per-block CPU budget of 80 ms that the measured 61 to 345 ms does not meet, an RSS that leaves 8 GB within hours at the measured footprints, 3-s checkpoints and 66 GB a day of votes at 8,192 voters unless C1 moves to seconds, and the controller's cap inconsistency. Per tier: a home miner on one card gains variance relief it does not yet need (a 4070 at 1 bps sees a block an hour at 100 GH/s); a rig gains nothing (36 min per block at 1 TH/s already); a pool user nothing (pools remove variance); a prover gains nothing and pays the exec follower's 10x chain blocks; a holder gains faster locks (9 s) only if d is left in blue blocks, which costs the finality bytes of 5.7; a rollup customer the same lock question; the node operator pays all of it. + +## 6. Ranked proposals + +| Rank | Proposal | Evidence | Model | Hours | Consequence per tier | Gate | +|---|---|---|---|---|---|---| +| 1 | Block rate: 1 bps for the public testnet and for mainnet launch; 10 bps stays a planned step (spec 2.1) behind three gates: per-block node CPU under 50 ms at mergeset 248 on a 2019 laptop core, C1 / d / the clock cap in DAA seconds, vote aggregation; no 32 or 100 bps step is proposed | 5.2 (reds are node throughput), 5.5 (budget 80 ms against 61 to 345 measured), 5.7 (66 GB a day of votes), 5.10 (1 bps already gives a 4070 a block an hour at 100 GH/s) | `propagation.py` F and G, `cost.py` 4 and 7 | 1 (the spec line) plus the gates below | home miner: no node change, a block an hour at 100 GH/s; rig and pool: unchanged; prover: the exec follower stays at 1 chain block per second; holder and rollup: 63-s locks; node operator: today's node | the three gates pass on Devnet 2 at 10 bps with zero red over the knee, before any rate step | +| 2 | Controller: every lane counts every mergeset block's work (blue and red) over the real span; the clock cap, tolerances and lag bound in DAA seconds (20, 10, 60 s) at every rate; a chain step that merges m blocks may span m x 20 T before clamping | 3.1 (26 to 3.5 blocks/s with blue at 1.4), 5.4 (the fork over-reads by spacing / cap and under-reads by (k + 1) / m; Kaspa's window reads the whole DAG) | `controller.py`: 10.00 in every cell for the whole-DAG estimator | 8 (rule and unit tests in `difficulty.rs`, `igneum.rs`) + 3 (the harness case: fast-time 3 nodes, a 3-s proxy delay at 10 bps; pass = DAG rate within 10% of target and difficulty within 10% of the hash-implied value after 10 min, the same at 1 bps with a 30-s delay) | home miner: the subsidy stops tracking the controller's error (tonight 77% of blocks unpaid to their miner); node operator: no runaway DAG from a slow hub; holder: emission on schedule (spec 2.5's "when the controller lets the rate run" clause stops firing) | the harness case passes; `sim/difficulty/sim.py --dag-delay` replay of run A's production curve settles at 10 bps | +| 3 | Finality interval and determination in DAA seconds (C1: checkpoint every 30 DAA s; d in DAA s), one carriage per vote (a block carries a vote only if no block in its past does, which is the rule as written; the relay dedups by (key, index)), and lane 3's aggregated certificate as the archival object | 5.7 (votes and route load scale with bps under the blue-block rule), 3.1 (2,547 route drops per peer, 16 of 47 locks) | `cost.py` 2 and 10 | 4 (spec 03 C1, 3.10 table) + 8 (fork: `finality.rs` interval in DAA s, relay dedup) | home miner and rig: finality bytes stay at 81 MB a day at 100 voters whatever the rate; node operator: the route stays inside 4,096; light client: 3.4 MB a day at every rate; holder: 63-s locks at every rate | a Devnet 2 run at 10 bps with 42 voters shows 0 route drops and every determinable checkpoint locked | +| 4 | Peer floor and topology: a node reports synced and mines only with at least 4 outbound peers (8 on seeds); the fleet library gives every box the seed plus 2 random boxes; the seed list carries 3 seeds; the hub-cut harness case of lane 3's proposal 6 with the floor as its pass condition | 3.1 (peers=1 on every pod, 2.5 MB/s out of one process), lane 3 section 5.5 ("on a Vast box the seed is the only peer") | `propagation.py` B (mesh of degree 4 and 8 against the star: the same red share at an ideal hub, one fifth of the upload per node, no single queue) | 4 (fleet library and the synced rule in `tools/fleet/lib/`) + 3 (the harness case) | node operator and seed: upload spread 41 ways to 8 ways; home miner: a dead seed no longer stops blocks and votes; finality: votes reach aggregators by two paths | the hub-cut case passes: a lock within 2 checkpoints of the cut, 0 conflicting certificates at the hub's return, every box at 4 or more peers throughout | +| 5 | Measure the per-block node cost and its split (lottery verify, BLS per vote, GHOSTDAG and reachability, relay, exec) with `perf` on a Devnet 2 box and on a 2019-class laptop at 1 and 10 bps, at a narrow DAG and at mergeset 248; set the budget rule (cost x bps under 0.8 core) as a CI check on the fast-time network | 3.1 and 5.3 (61 to 345 ms measured as a total only) | section 4.6 | 3 (profile) + 4 (the CI check in `tools/ci`) | every node tier: a number per machine class instead of the hub's; the 8 GB laptop and the Raspberry-class verdicts of 5.5 become measurements | the profile lands in the bench log with per-component ms; the check fails a build whose per-block cost exceeds the budget at the network's rate | +| 6 | Snapshot path hardening: a p2p snapshot is accepted only when its root at the newest proven segment at or below its tip equals the chain's proof record for that segment, and (tip, root) agree on 3 of 5 peers; a failing peer is dropped for the session; the gate's unit test gains the two cases | 5.8 (the gate checks tips and the restart pin, never the state at the tip; the 6 October seed loaded a tip-0 snapshot from a fleet box before the gate) | section 5.8 | 6 (fork `proving.rs`, `snapshot.rs`; the record lookup exists in `check_record`) | node operator: recovery from a peer cannot be poisoned below an eclipse; prover: no false-state records from a poisoned node; home miner: the app's node recovers by itself after a deep reorg | `tools/exec-sync/reorg.mjs` gains a poisoned-snapshot case: refused, peer dropped, honest snapshot loaded after it | +| 7 | Devnet 2 genesis bits set for the fleet's hash at the run's rate (no 26 blocks/s burst), and the fleet rule: a box mines only after synced with the peer floor (run A's pods mined from genesis for up to 6 minutes before they saw the chain) | 3.1 (the 55-block reorg "unwinding to height 1", the 33% red first phase), 5.2 (the burst alone saturates a 61-ms hub for 5 minutes) | `propagation.py` C and E | 2 (`box-dn2.sh`, `start-seed.sh`, `devnet2-override.json` per rate) | fleet operator: run A2 and run B measure the rate, not the start; every other tier: none | the next Devnet 2 run shows no reorg over 5 in its first 10 minutes and a first-minute production within 2x of target | +| 8 | RSS steady state: run a node through a full 30-h pruning window at 1 bps (fast time) and at 10 bps on Devnet 2, read RSS every 10 minutes, bound the DAG store (reachability and GHOSTDAG per block) and `ExecState.records`; decide the 8 GB tier from the plateau | 3.3 (slopes 30, 80, 440 KB per block over short runs; none is a steady state), 5.5 (8 GB is marginal at 1 bps at 80 KB per block) | `cost.py` 5 | 3 (the run) + 8 (the bounds, if the plateau is over 4 GB) | home miner on an 8 GB laptop: a yes or no at 1 bps; Raspberry-class: the same; node operator: a RAM line on the requirements page | RSS plateau under 4 GB at 1 bps over 30 h; the requirements page carries the measured line | +| 9 | Node and client tiers published with sizes: full pruned node (1 bps: 0.9 GB a day, 136 MB window plus state, 4 GB RAM target), archival node (40 GB a year of blocks plus 1.3 GB of certificates once aggregated; 296 GB a year of votes until then at 1,000 voters), light client checkpoint mode (3.4 MB a day), phase two (2.3 MB a day) | 5.6, 5.8, `cost-tables.md` 6 and 10 | `cost.py` | 2 (the requirements page and spec 10.5's table at 1 bps, with the rate rows) | every tier knows its cost before the testnet | the page's numbers match `cost-tables.md` and the first testnet week's measured bytes within 30% | +| 10 | Bandwidth and route bounds per peer: at most 2 x V / 30 votes per peer per block interval accepted (a second belt behind the dedup), block relay to at most 16 peers per node, the IgneumFinality route sized from V and the rate | 3.1 (2,547 drops per peer), 5.6 | `cost.py` 3 | 4 (fork p2p flows) | seed and node operator: a bound on what one peer can make a node do; home miner: none | the s7 flood scenario gains a vote flood: 0 disconnects, bounded CPU | + +**1. Block rate.** Everything measured tonight says 1 bps is the rate the current node can run and 10 bps is not: the hub spent 61 ms per block at a narrow DAG against a budget of 100, and the model turns that cost into tonight's red share (5.2). Nothing in propagation forbids 10 bps (5.1: zero reds at every measured delay with k 124), so the step stays on the plan (spec 2.1) as Kaspa took it, after a test campaign, with three gates: the per-block cost (proposal 5), the time denomination of finality and the controller (proposals 2 and 3), and vote aggregation (lane 3). The variance argument does not need the step yet: a 4070 sees a block an hour at 100 GH/s and two a day at 1 TH/s at 1 bps (5.10); a pool user never sees variance. Consequences: no node tier changes for the testnet; the testnet gives the measured bytes and RSS that fill proposals 8 and 9. + +**2. Controller.** The rule walks the selected chain and sums blue-work increments over capped clock steps (`igneum_difficulty_bits`, section 4.4). In a wide DAG both parts mislead it: the cap turns a 5-s chain step into 2 s (over-read, harden, the record's fall to 3.5 blocks/s) and the blue-only work hides the reds (under-read, ease, the direction main reported). Kaspa's window pushes every mergeset block, red included, and divides by the real span (`window.rs`, `difficulty.rs`), which the model holds at target in every cell. The change is inside `igneum_difficulty_bits` and `igneum_target`: work = the mergeset's total work per chain step, the step allowed m x 20 T before the cap, the cap and tolerances in DAA seconds. The harness case is the proxy-delayed fast-time network of `sim/difficulty/attacks/README.md` at 10 bps with a 3-s hold, and `sim.py --dag-delay` replaying run A's production curve (the schedule file is in `sim/horizon/network/`). + +**3. Finality interval in seconds.** C1 says 30 blue blocks, d says 60 blue blocks; at 10 bps that is a checkpoint every 3 s and ten times the votes, certificates, route load and light-client bytes per day (5.7), and tonight's seed showed the route overflowing at 42 voters. Written in DAA seconds the whole finality cost is rate-invariant and 3.4.2's arithmetic (384 votes per block drain 8,192 in 21 blocks) gets 10x more headroom at 10 bps. The one-carriage rule is already the spec's text ("not already in its past"); the relay dedup by (key, index) makes the wire match it. + +**4. Peer floor.** Run A's pods had one peer each; the live fleet's boxes have the seed as their only peer; the hub paid 41x the upload and ran one queue for every block and vote. Four outbound peers before a node calls itself synced, the seed plus two random boxes in the fleet library, three seeds in the list: the mesh runs of 5.2 show the same zero red at an ideal hub with one fifth of the per-node upload and no single queue, and lane 3's hub-cut case becomes passable. This is the proposal that serves both lanes. + +**5. The per-block profile.** The 61 to 345 ms is a total; the split decides which fix buys 10 bps: if BLS of carried votes dominates, aggregation and the dedup buy it; if GHOSTDAG at k 124 dominates, the mergeset limit and a cheaper reachability do; if relay dominates, the peer floor does. Three hours with `perf` on a Devnet 2 box, then a CI check that fails a build whose cost exceeds the budget at the network's rate, so the class is closed the way CLAUDE.md asks. + +**6. Snapshot hardening.** The gate refuses tips, not states (5.8). The chain carries the proof records' roots, so a snapshot's root at its newest proven segment can be checked against them without a protocol change, and 3-of-5 peer agreement makes poisoning an eclipse. The attack is a liveness attack on the victim, never a consensus break, but a poisoned seed serving its state onward is the fleet-wide class the 6 October gate was written for. + +**7 to 10.** Devnet 2's genesis bits and the start-synced rule make the next runs measure the rate instead of the start (the 55-block reorg and the first-phase reds were the start); the 30-h RSS run decides the 8 GB tier with a measurement instead of three slopes; the tier page and the per-peer bounds are two hours each and close open rows in spec 10.5 and the finality route entry. + +## 7. Open questions and what could not be run + +| Question | Why not tonight | What closes it | +|---|---|---| +| Run B (1 bps control) and A2 (mesh) rows | B starts at 20:25Z, rows about 21:00Z; A2 is not scripted | the fleet's RUN_B row; A2 built on `box-dn2.sh` with 3 `--addpeer` entries (seed plus two boxes) and genesis bits for 10 bps; this lane's prediction for A2 is in 5.2 | +| The difficulty trajectory of run A | the collector strips `difficulty=`; the node log has no bits | `bps-collect.py` keeps the field; or `getBlockDagInfo` difficulty per minute on the next run | +| The per-block CPU split | no profile; the hub's figure is a total that includes relay to 41 peers | proposal 5 | +| RSS steady state at 1 and 10 bps | three slopes over 1,500 to 15,000 blocks, none over a pruning window | proposal 8 | +| Kaspa's measured 10-bps red rate | not in the clone (`crescendo-guide.md` has requirements only) | the kaspanet KIP-14 text and the Crescendo testnet-10 reports, not cloned; approximate under 1 percent by the k derivation | +| BLS verify per vote on this stack | O-10.3 open; 1.5 ms is approximate | `fast_aggregate_verify` timing with the forked `blst` build | +| The pods' per-block cost (the A2 prediction's fork) | the pods' CPUs and their node logs were not read (fleet boxes are out of this lane's reach) | the fleet's collector reads `cpu_rss` on every box, not only the seed | +| GHOSTDAG's second k-cluster condition | `propagation.py` applies the candidate's own anticone bound only; lane 4's `ghostdag_sim.py` carries both | re-run the grid with lane 4's colouring if a cell is contested (every cell here is 0 percent, so the undercount cannot change the reading) | +| The 55-block reorg's cause | read from the log as a late-joining pod's chain from genesis ("unwinding to height 1") | the start-synced rule of proposal 7 removes the class; the next run's reorg distribution confirms | + +## 8. Summary for the coordinator + +Run A at 10 bps did not measure propagation; it measured a node that needs 61 to 345 ms per block through one hub whose budget at that rate is 100 ms. The propagation model with the measured links predicts under 0.1 percent red in the star and in the mesh; with the hub's measured per-block CPU it predicts 47 to 88 percent against the 77.2 percent measured, with the queueing waits the log's 100-block relay batches show, and it predicts that the mesh variant A2 goes red too unless every pod's per-block cost is under 80 ms and the genesis burst is removed. The controller then hardened (production 26 to 3.5 blocks/s with blue at 1.4) because its estimator reads blue work over a chain step capped at 2 s; the whole-DAG estimator Kaspa uses is unbiased in every regime the model covers. The block rate for the public testnet and mainnet launch is 1 bps; 10 bps stays a gated step, and the gates are a per-block cost under 50 ms on a laptop core at mergeset 248, the finality interval and the clock cap in DAA seconds, and vote aggregation. Lane 3's finding stands (the pause was the rule, the fleet's star is around the seed); the peer floor is the piece of work both lanes need. + +1. Reds from node cost, not topology: red share 0.0 percent in every cell of the bps x delay grid (1 to 100 bps, 50 to 1,000 ms hops, k from Kaspa's table) and in the run A replay with an ideal hub; 47 / 88 / 83 percent with the hub at 61 / 115 / 345 ms per block (measured); the knee at 10 bps is 100 ms per block (`sim/horizon/network/propagation.py`, `results.md`, `results-2.md`). +2. The controller's two biases: estimate / true hash = (blues / m) x (spacing / cap); at a 5-s chain-step spacing the fork settles the DAG at 4.0 blocks/s against 10 (record 3.3 to 3.6), at a 20-s spacing at 1 bps it runs away to 179 blocks/s; the whole-DAG estimator holds 10.00 and 1.00 in every cell (`controller.py`). +3. Finality cost scales with the rate under C1 as written: 8,192 voters cost 6.6 GB a day per node at 1 bps and 66 GB at 10 bps (24 TB a year archival); in DAA seconds 6.6 GB at every rate, 3.5 MB a day with aggregated certificates; tonight's seed dropped up to 2,547 finality messages per peer and locked 16 of 47 checkpoints at 42 voters (`cost.py`, section 3.1). diff --git a/sim/horizon/network/README.md b/sim/horizon/network/README.md new file mode 100644 index 00000000..3eefddc8 --- /dev/null +++ b/sim/horizon/network/README.md @@ -0,0 +1,14 @@ +# sim/horizon/network + +Models behind `docs/analysis/horizon/network.md` (Horizon lane 5, network, 6 October 2026). Python 3.10, standard library only; the Mac, under the main checkout's run lock (`/Users/joshm/Projects/igneum/tools/lock/with-lock.sh run nice -n 19 ...`). Outputs are counts, shares and seconds, never timings of this machine. + +| File | What | Run | +|---|---|---| +| `propagation.py` | Poisson block production, star or mesh topology, lognormal link latency, inv/request/block hops, a hub (or every node) with a per-block validation cost and a queue, GHOSTDAG colouring with the first k-cluster condition, mergeset and parent caps, the natural reorg depth at miner 0 | `python3 sim/horizon/network/propagation.py --grid` (the bps x delay grid); single runs with `--topology star --nodes 41 --bps 10 --k 124 --link 40 --hub-s0 115 --schedule sim/horizon/network/runA-schedule.csv --seconds 1200` | +| `run-batch.sh`, `run-batch2.sh` | the lane's runs, appended to `results.md` and `results-2.md` | `bash sim/horizon/network/run-batch.sh` (about 2 min), `run-batch2.sh` (about 2 min) | +| `runA-schedule.csv` | run A's measured production per minute (the Devnet 2 seed's `PoW accepted` lines on igneum-build-1, `/home/build/dn2seed-A.log`) | input to `--schedule` | +| `burst-schedule.csv` | a 26 blocks/s genesis burst for 5 min then 10 bps | input to `--schedule` | +| `controller.py` | the difficulty rule against a wide DAG: the fork's blue-work-over-capped-chain-step estimator, the whole-DAG estimator (Kaspa's window) and a red-corrected blue estimator, closed loop with the 3% / 10% clamps | `python3 sim/horizon/network/controller.py --delays 0.3,1,2,3,5,10,30` (10 bps); `--bps 1 --k 18 --delays 0.3,2,5,20,30,60 --lambda0 2.6` (1 bps); outputs `controller-10bps.md`, `controller-1bps.md` | +| `cost.py` | GHOSTDAG parameters per rate, the vote bounds, bytes per node per day, CPU per block, RSS and disk, pruned and archival storage, subsidy and payout intervals, the solo-miner table, finality timing, light-client bytes; every input labelled in `INPUTS` | `python3 sim/horizon/network/cost.py > sim/horizon/network/cost-tables.md` | + +Seeds: 7 everywhere. Results files: `results.md`, `results-2.md`, `controller-10bps.md`, `controller-1bps.md`, `cost-tables.md`. diff --git a/sim/horizon/network/burst-schedule.csv b/sim/horizon/network/burst-schedule.csv new file mode 100644 index 00000000..326680f5 --- /dev/null +++ b/sim/horizon/network/burst-schedule.csv @@ -0,0 +1,11 @@ +# burst then target +0,1559 +1,1559 +2,1559 +3,1559 +4,1559 +5,600 +6,600 +7,600 +8,600 +9,600 diff --git a/sim/horizon/network/controller-10bps.md b/sim/horizon/network/controller-10bps.md new file mode 100644 index 00000000..fe770730 --- /dev/null +++ b/sim/horizon/network/controller-10bps.md @@ -0,0 +1,25 @@ +# controller.py: bps 10, k 124, cap 2 s, start at 26 blocks/s, 30 min; settled = mean of the last 5 minutes + +| estimator | delay d, s | DAG rate settled, blocks/s | blue rate, blocks/s | red share | P(anticone > k) | mergeset per chain block | estimate / true hash | difficulty vs correct | time to within 10% of target DAG rate, min | +|---|---|---|---|---|---|---|---|---|---| +| igneum | 0.3 | 10.00 | 10.00 | 0.0% | 0.00 | 3.0 | 1.00x | 1.00x | 0.1 | +| igneum | 1 | 10.00 | 10.00 | 0.0% | 0.00 | 10.0 | 1.00x | 1.00x | 0.5 | +| igneum | 2 | 10.00 | 10.00 | 0.0% | 0.00 | 20.0 | 1.00x | 1.00x | 1.0 | +| igneum | 3 | 6.67 | 6.67 | 0.0% | 0.00 | 20.0 | 1.50x | 1.50x | 1.5 | +| igneum | 5 | 4.00 | 4.00 | 0.0% | 0.00 | 20.0 | 2.50x | 2.50x | 2.5 | +| igneum | 10 | 2.00 | 2.00 | 0.0% | 0.00 | 20.0 | 5.00x | 5.00x | 5.0 | +| igneum | 30 | 5.21 | 4.20 | 19.5% | 1.00 | 156.3 | 12.08x | 1.93x | 15.0 | +| whole | 0.3 | 10.00 | 10.00 | 0.0% | 0.00 | 3.0 | 1.00x | 1.00x | 0.1 | +| whole | 1 | 10.00 | 10.00 | 0.0% | 0.00 | 10.0 | 1.00x | 1.00x | 0.5 | +| whole | 2 | 10.00 | 10.00 | 0.0% | 0.00 | 20.0 | 1.00x | 1.00x | 1.0 | +| whole | 3 | 10.00 | 10.00 | 0.0% | 0.00 | 30.0 | 1.00x | 1.00x | 1.5 | +| whole | 5 | 10.00 | 10.00 | 0.0% | 0.01 | 50.0 | 1.00x | 1.00x | 2.5 | +| whole | 10 | 10.00 | 10.00 | 0.0% | 1.00 | 100.0 | 1.00x | 1.00x | 5.0 | +| whole | 30 | 10.00 | 4.17 | 58.3% | 1.00 | 300.0 | 1.00x | 1.00x | 15.0 | +| blue-rc | 0.3 | 10.00 | 10.00 | 0.0% | 0.00 | 3.0 | 1.00x | 1.00x | 0.1 | +| blue-rc | 1 | 10.00 | 10.00 | 0.0% | 0.00 | 10.0 | 1.00x | 1.00x | 0.5 | +| blue-rc | 2 | 10.00 | 10.00 | 0.0% | 0.00 | 20.0 | 1.00x | 1.00x | 1.0 | +| blue-rc | 3 | 10.00 | 10.00 | 0.0% | 0.00 | 30.0 | 1.00x | 1.00x | 1.5 | +| blue-rc | 5 | 10.00 | 10.00 | 0.0% | 0.01 | 50.0 | 1.00x | 1.00x | 2.5 | +| blue-rc | 10 | 10.00 | 10.00 | 0.0% | 1.00 | 100.0 | 1.00x | 1.00x | 5.0 | +| blue-rc | 30 | 10.00 | 4.17 | 58.3% | 1.00 | 300.0 | 1.00x | 1.00x | 15.0 | diff --git a/sim/horizon/network/controller-1bps.md b/sim/horizon/network/controller-1bps.md new file mode 100644 index 00000000..374b9cb4 --- /dev/null +++ b/sim/horizon/network/controller-1bps.md @@ -0,0 +1,22 @@ +# controller.py: bps 1, k 18, cap 20 s, start at 2.6 blocks/s, 30 min; settled = mean of the last 5 minutes + +| estimator | delay d, s | DAG rate settled, blocks/s | blue rate, blocks/s | red share | P(anticone > k) | mergeset per chain block | estimate / true hash | difficulty vs correct | time to within 10% of target DAG rate, min | +|---|---|---|---|---|---|---|---|---|---| +| igneum | 0.3 | 1.00 | 1.00 | 0.0% | 0.00 | 1.0 | 1.00x | 1.00x | 0.3 | +| igneum | 2 | 1.00 | 1.00 | 0.0% | 0.00 | 2.0 | 1.00x | 1.00x | 1.0 | +| igneum | 5 | 1.00 | 1.00 | 0.0% | 0.01 | 5.0 | 1.00x | 1.00x | 2.5 | +| igneum | 20 | 178.75 | 1.00 | 99.4% | 1.00 | 3575.0 | 0.01x | 0.01x | never | +| igneum | 30 | 43.03 | 0.65 | 98.5% | 1.00 | 1290.8 | 0.02x | 0.02x | never | +| igneum | 60 | 10.41 | 0.32 | 96.9% | 1.00 | 624.8 | 0.09x | 0.10x | never | +| whole | 0.3 | 1.00 | 1.00 | 0.0% | 0.00 | 1.0 | 1.00x | 1.00x | 0.3 | +| whole | 2 | 1.00 | 1.00 | 0.0% | 0.00 | 2.0 | 1.00x | 1.00x | 1.0 | +| whole | 5 | 1.00 | 1.00 | 0.0% | 0.01 | 5.0 | 1.00x | 1.00x | 2.5 | +| whole | 20 | 1.00 | 0.95 | 5.0% | 1.00 | 20.0 | 1.00x | 1.00x | 10.0 | +| whole | 30 | 1.00 | 0.63 | 36.7% | 1.00 | 30.0 | 1.00x | 1.00x | 15.0 | +| whole | 60 | 1.17 | 0.32 | 72.9% | 1.00 | 70.3 | 1.00x | 0.86x | never | +| blue-rc | 0.3 | 1.00 | 1.00 | 0.0% | 0.00 | 1.0 | 1.00x | 1.00x | 0.3 | +| blue-rc | 2 | 1.00 | 1.00 | 0.0% | 0.00 | 2.0 | 1.00x | 1.00x | 1.0 | +| blue-rc | 5 | 1.00 | 1.00 | 0.0% | 0.01 | 5.0 | 1.00x | 1.00x | 2.5 | +| blue-rc | 20 | 1.00 | 0.95 | 5.0% | 1.00 | 20.0 | 1.00x | 1.00x | 10.0 | +| blue-rc | 30 | 1.00 | 0.63 | 36.7% | 1.00 | 30.0 | 1.00x | 1.00x | 15.0 | +| blue-rc | 60 | 1.17 | 0.32 | 72.9% | 1.00 | 70.3 | 1.00x | 0.86x | never | diff --git a/sim/horizon/network/controller.py b/sim/horizon/network/controller.py new file mode 100644 index 00000000..49f3191e --- /dev/null +++ b/sim/horizon/network/controller.py @@ -0,0 +1,94 @@ +#!/usr/bin/env python3 +"""The difficulty controller against a wide DAG: the coupling between red share, chain-step spacing and the estimator +(Horizon network lane, 6 October 2026). + +The rule as the fork runs it (vendor/igneum-node release-0.3.14-node, consensus/src/processes/difficulty.rs +`igneum_difficulty_bits`, consensus/core/src/igneum.rs `difficulty`): the lanes walk the SELECTED CHAIN; every step +carries `work = blue_work(b) - blue_work(selected parent)` (the mergeset BLUES' work only, red work is not in blue +work) and `solvetime = min(c(b) - c(p), CAP_BLOCKS x T)` with CAP_BLOCKS = 20 (2 s at T = 100 ms); the target moves at +most HARDEN_PERCENT 3 (difficulty up) or EASE_PERCENT 10 (difficulty down) per chain block (spec 02 2.3). +Kaspa's rule (vendor/rusty-kaspa consensus/src/processes/window.rs `push_mergeset`, difficulty.rs +`calculate_difficulty_bits`): the DAA window holds every mergeset block, blue AND red, above the window's blue-score +floor, and the new target is the window's average target x measured span / (window size x T): the whole-DAG rate +over the real span. + +The abstract DAG regime (no block-level simulation; the inputs are the propagation lane's): + spacing = max(1 / lambda, d) seconds between selected-chain blocks (every block a chain block when the DAG is + narrow; one chain block per propagation delay when it is wide) + m = lambda x spacing mergeset per chain block + blues = min(m, k + 1) the k-cluster admits at most k blues beside the selected parent per chain step + (protocol.rs); the rest of the mergeset is red: r = 1 - blues / m + (the PHANTOM tail P(Poisson(2 d lambda) > k) that k is derived from, bps.rs `calculate_ghostdag_k`, says when + reds appear at all; it is printed beside r) +Estimators of the network hash H (work per block = D, the difficulty): + igneum = blues x D / min(spacing, cap) (blue work over the capped chain step) + whole = m x D / spacing (every mergeset block's work over the real span) = H + blue-rc = blues x D / spacing / (1 - r_obs) (blue rate with the observed red share corrected) = H +The controller sets the next D toward estimate x T through the 3% / 10% clamps once per chain block. + +Run: python3 sim/horizon/network/controller.py [--bps 10 --k 124 --delays 0.3,1,2,5,10 --minutes 30 --lambda0 26] +""" +import argparse, math + +def pois_tail_gt(k, x): + """P(Poisson(x) > k)""" + if x <= 0: return 0.0 + # sum P(N <= k) in log space + lp = -x; s = math.exp(lp) + for i in range(1, k + 1): + lp += math.log(x / i); s += math.exp(lp) + if lp < -745: break + return max(0.0, min(1.0, 1.0 - s)) + +def run(est, bps, k, d, minutes, lam0, H=1e9): + T = 1.0 / bps; cap = 20 * T + D = H / lam0 # difficulty giving the initial production rate + t = 0.0; rows = [] + while t < minutes * 60: + lam = H / D + x = 2 * d * lam + spacing = max(1.0 / lam, d) + m = lam * spacing + blues = min(m, k + 1) + r = 1 - blues / m + if est == "igneum": + h = blues * D / min(spacing, cap) + elif est == "whole": + h = m * D / spacing + else: + h = blues * D / spacing / max(1e-9, 1 - r) + want = h * T + D_new = min(D * 1.03, max(D * 0.90, want)) # harden at most 3%, ease at most 10% + rows.append((t, lam, r, m, blues, h / H, D, pois_tail_gt(k, x))) + D = D_new; t += spacing + return rows + +def main(): + ap = argparse.ArgumentParser() + ap.add_argument("--bps", type=float, default=10); ap.add_argument("--k", type=int, default=124) + ap.add_argument("--delays", default="0.3,1,2,5,10"); ap.add_argument("--minutes", type=float, default=30) + ap.add_argument("--lambda0", type=float, default=26.0, help="initial production rate, blocks/s (run A's genesis burst was 26)") + ap.add_argument("--out", default=None) + a = ap.parse_args() + lines = [] + def emit(s): print(s); lines.append(s) + emit(f"# controller.py: bps {a.bps:g}, k {a.k}, cap {20/a.bps:g} s, start at {a.lambda0:g} blocks/s, {a.minutes:g} min; settled = mean of the last 5 minutes") + emit("") + emit("| estimator | delay d, s | DAG rate settled, blocks/s | blue rate, blocks/s | red share | P(anticone > k) | mergeset per chain block | estimate / true hash | difficulty vs correct | time to within 10% of target DAG rate, min |") + emit("|---|---|---|---|---|---|---|---|---|---|") + for est in ("igneum", "whole", "blue-rc"): + for d in [float(v) for v in a.delays.split(",")]: + rows = run(est, a.bps, a.k, d, a.minutes, a.lambda0) + tail = [r for r in rows if r[0] >= (a.minutes - 5) * 60] or rows[-5:] + lam = sum(r[1] for r in tail) / len(tail); red = sum(r[2] for r in tail) / len(tail) + m = sum(r[3] for r in tail) / len(tail); ratio = sum(r[5] for r in tail) / len(tail) + Dcorrect = 1e9 / a.bps; Dratio = sum(r[6] for r in tail) / len(tail) / Dcorrect + within = next((r[0] / 60 for r in rows if abs(r[1] - a.bps) <= 0.1 * a.bps), None) + wtxt = f"{within:.1f}" if within is not None else "never" + tail_p = sum(r[7] for r in tail) / len(tail) + emit(f"| {est} | {d:g} | {lam:.2f} | {lam*(1-red):.2f} | {100*red:.1f}% | {tail_p:.2f} | {m:.1f} | {ratio:.2f}x | {Dratio:.2f}x | {wtxt} |") + if a.out: + with open(a.out, "w") as f: f.write("\n".join(lines) + "\n") + +if __name__ == "__main__": + main() diff --git a/sim/horizon/network/cost-tables.md b/sim/horizon/network/cost-tables.md new file mode 100644 index 00000000..ed3e6912 --- /dev/null +++ b/sim/horizon/network/cost-tables.md @@ -0,0 +1,84 @@ +# cost.py tables (sim/horizon/network/cost.py). Labels: M = measured, C = cited, S = simulated, A = approximate; sources in the script's INPUTS. + +## 1. GHOSTDAG parameters per block rate (C: bps.rs formulas; k at 100 bps computed with calculate_ghostdag_k since the table stops at 32) +| bps | k | max parents | mergeset limit | merge depth, blocks | finality depth, blocks | pruning depth, blocks | coinbase maturity, blocks | checkpoint cadence (30 blue), s | determination d = 60 blue, s | blocks per 30-s window | +|---|---|---|---|---|---|---|---|---|---|---| +| 1 | 18 | 10 | 180 | 3,600 | 43,200 | 108,000 | 100 | 30 | 60 | 30 | +| 10 | 124 | 16 | 248 | 36,000 | 432,000 | 1,080,000 | 1,000 | 3 | 6 | 300 | +| 32 | 362 | 16 | 512 | 115,200 | 1,382,400 | 3,456,000 | 3,200 | 0.9375 | 1.875 | 960 | +| 100 | 1074 | 16 | 512 | 360,000 | 4,320,000 | 10,800,000 | 10,000 | 0.3 | 0.6 | 3000 | + +## 2. Votes and the 3.4.2 bounds per block rate (C: 281 B per vote; checkpoint every 30 BLUE blocks as C1 is written) +| bps | checkpoints per day | votes per block at V voters (V/30, C1 in blue blocks) | drain capacity per checkpoint, cap 48 / 384 | vote bytes per day, V = 100 / 1,000 / 8,192 (single votes) | the same if C1 were 30 DAA seconds | +|---|---|---|---|---|---| +| 1 | 2,880 | 3.3 / 33.3 / 273.1 | 1,440 / 11,520 | 80.93 MB / 809.28 MB / 6.63 GB | 80.93 MB / 809.28 MB / 6.63 GB | +| 10 | 28,800 | 3.3 / 33.3 / 273.1 | 1,440 / 11,520 | 809.28 MB / 8.09 GB / 66.30 GB | 80.93 MB / 809.28 MB / 6.63 GB | +| 32 | 92,160 | 3.3 / 33.3 / 273.1 | 1,440 / 11,520 | 2.59 GB / 25.90 GB / 212.15 GB | 80.93 MB / 809.28 MB / 6.63 GB | +| 100 | 288,000 | 3.3 / 33.3 / 273.1 | 1,440 / 11,520 | 8.09 GB / 80.93 GB / 662.96 GB | 80.93 MB / 809.28 MB / 6.63 GB | + +## 3. Bytes per node per day: receive every block once plus relay overhead (A: Kaspa's inv/request flow, degree 8), votes carried in bodies, one certificate per block +| bps | header bytes (C+S) | blocks per day | block bytes per day (header + body + records) | relay overhead per day | votes per day at V = 12 / 100 / 1,000 / 8,192 (C1 in blue blocks) | certificates per day at V = 12 / 8,192 | total per day at V = 100 | total at V = 8,192 | Mbit/s mean at V = 8,192 | +|---|---|---|---|---|---|---|---|---|---| +| 1 | 331 | 86,400 | 57.46 MB | 31.10 MB | 9.71 MB / 80.93 MB / 809.28 MB / 6.63 GB | 23.72 MB / 112.06 MB | 194.16 MB | 6.83 GB | 0.63 | +| 10 | 417 | 864,000 | 649.25 MB | 311.04 MB | 97.11 MB / 809.28 MB / 8.09 GB / 66.30 GB | 237.17 MB / 1.12 GB | 2.02 GB | 68.38 GB | 6.33 | +| 32 | 542 | 2,764,800 | 2.42 GB | 995.33 MB | 310.76 MB / 2.59 GB / 25.90 GB / 212.15 GB | 758.94 MB / 3.59 GB | 6.80 GB | 219.15 GB | 20.29 | +| 100 | 740 | 8,640,000 | 9.28 GB | 3.11 GB | 971.14 MB / 8.09 GB / 80.93 GB / 662.96 GB | 2.37 GB / 11.21 GB | 22.95 GB | 686.56 GB | 63.57 | + +With certificates aggregated (lane 3's 1.2 KB per checkpoint) and votes NOT echoed in every body (one carriage), the vote column is the floor: a vote crosses the network once. A star hub multiplies its upload by its peer count: run A's hub sent 2.13 GB in 850 s to 41 peers (A.jsonl rx_tx, netns-wide counter, so an upper bound): 2.5 MB/s, 61 KB/s per peer. + +## 4. Node CPU per block and the serial budget (the pipeline validates blocks in order; budget = 0.8 core) +| bps | budget per block, ms | lottery verify class v3 / v4 / v4 half core (M) | BLS for V/30 votes per block at V = 100 / 1,000 / 8,192, cap 384 (A: 1.5 ms per verify) | run A hub measured total at mergeset 8 / 30 / 150+ (M) | fits the budget? | +|---|---|---|---|---|---| +| 1 | 800 | 2.79 / 4.9 / 8.23 | 5 / 50 / 410 | 61 / 115 / 345 | yes with margin | +| 10 | 80 | 2.79 / 4.9 / 8.23 | 5 / 50 / 410 | 61 / 115 / 345 | only the verify and a narrow DAG (under 25 ms per block measured nowhere yet) | +| 32 | 25 | 2.79 / 4.9 / 8.23 | 5 / 50 / 410 | 61 / 115 / 345 | only the verify and a narrow DAG (under 25 ms per block measured nowhere yet) | +| 100 | 8 | 2.79 / 4.9 / 8.23 | 5 / 50 / 410 | 61 / 115 / 345 | no: class v4 verify alone (4.9 to 8.2 ms) plus GHOSTDAG exceeds it | + +## 5. RSS and disk per block rate (M: cache 256 MiB x KEEP_DAYS 3; slopes per block from M30 s8, the live app node, run A's hub; all slopes are growth over a run, not a proven steady state) +| bps | caches | DAG store growth per day at 30 / 80 / 440 KB per block | growth over the 30-h pruning window at 30 / 80 KB per block | exec state (M: 128 MB snapshot at 141,700 chain blocks) | disk per day: blocks + votes at V = 1,000 + exec records | +|---|---|---|---|---|---| +| 1 | 768 MiB worst, 256 MiB flat | 2.61 GB / 6.91 GB / 38.02 GB | 3.26 GB / 8.64 GB | 128 MB + 1.5 KB per chain block | 917.78 MB | +| 10 | 768 MiB worst, 256 MiB flat | 26.09 GB / 69.12 GB / 380.16 GB | 32.62 GB / 86.40 GB | 128 MB + 1.5 KB per chain block | 8.97 GB | +| 32 | 768 MiB worst, 256 MiB flat | 83.50 GB / 221.18 GB / 1.22 TB | 104.37 GB / 276.48 GB | 128 MB + 1.5 KB per chain block | 28.69 GB | +| 100 | 768 MiB worst, 256 MiB flat | 260.93 GB / 691.20 GB / 3.80 TB | 326.16 GB / 864.00 GB | 128 MB + 1.5 KB per chain block | 90.77 GB | + +## 6. Pruned and archival storage per year (C: pruning window 108,000 DAA s; A: 2x index overhead on blocks; votes single, one carriage) +| bps | pruned node keeps (window of headers + bodies, 2x) | plus votes in the window at V = 1,000 | archival per year: blocks (2x) | archival votes per year at V = 100 / 1,000 / 8,192 (C1 in blue blocks) | archival votes per year if certificates only (1.2 KB per checkpoint, lane 3) | +|---|---|---|---|---|---| +| 1 | 136.25 MB | 1.01 GB | 39.81 GB | 29.56 GB / 295.59 GB / 2.42 TB | 1.26 GB | +| 10 | 1.55 GB | 10.12 GB | 452.66 GB | 295.59 GB / 2.96 TB / 24.21 TB | 12.62 GB | +| 32 | 5.82 GB | 32.37 GB | 1.70 TB | 945.89 GB / 9.46 TB / 77.49 TB | 40.39 GB | +| 100 | 22.47 GB | 101.16 GB | 6.57 TB | 2.96 TB / 29.56 TB / 242.15 TB | 126.23 GB | + +## 7. Subsidy per block and payout intervals (C: 31.688 IGN per DAA s at full ramp, 80% to the producer; interval = 1 / (bps x share)) +| bps | subsidy per block, IGN | miner's 80%, IGN | 4070 at tonight 1.16 GH/s | 5090 at tonight 1.16 GH/s | rig at tonight 1.16 GH/s | 4070 at 10 GH/s | 5090 at 10 GH/s | rig at 10 GH/s | 4070 at 100 GH/s | 5090 at 100 GH/s | rig at 100 GH/s | 4070 at 1 TH/s | 5090 at 1 TH/s | rig at 1 TH/s | 4070 at 10 TH/s | 5090 at 10 TH/s | rig at 10 TH/s | +|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---| +| 1 | 31.688 | 25.350 | 46.4 s | 9.1 s | 2.5 s | 6.7 min | 1.3 min | 21.8 s | 1.1 h | 13.0 min | 3.6 min | 11.1 h | 2.2 h | 36.3 min | 4.6 d | 21.7 h | 6.1 h | +| 10 | 3.169 | 2.535 | 4.6 s | 0.9 s | 0.3 s | 40.0 s | 7.8 s | 2.2 s | 6.7 min | 1.3 min | 21.8 s | 1.1 h | 13.0 min | 3.6 min | 11.1 h | 2.2 h | 36.3 min | +| 32 | 0.990 | 0.792 | 1.4 s | 0.3 s | 0.1 s | 12.5 s | 2.4 s | 0.7 s | 2.1 min | 24.4 s | 6.8 s | 20.8 min | 4.1 min | 1.1 min | 3.5 h | 40.7 min | 11.3 min | +| 100 | 0.317 | 0.254 | 0.5 s | 0.1 s | 0.0 s | 4.0 s | 0.8 s | 0.2 s | 40.0 s | 7.8 s | 2.2 s | 6.7 min | 1.3 min | 21.8 s | 1.1 h | 13.0 min | 3.6 min | + +## 8. The solo-miner question: a 4070 at 25 MH/s, blocks per day and the rate needed for one block a day +| network hash | share | blocks per day at 1 bps | bps for one block a day | IGN per day at 1 bps (full ramp, 80%) | +|---|---|---|---|---| +| tonight 1.16 GH/s | 2.16e-02 | 1862.07 | 0.0005 | 47,204.2 | +| 10 GH/s | 2.50e-03 | 216.00 | 0.0046 | 5,475.7 | +| 100 GH/s | 2.50e-04 | 21.60 | 0.0463 | 547.6 | +| 1 TH/s | 2.50e-05 | 2.16 | 0.4630 | 54.8 | +| 10 TH/s | 2.50e-06 | 0.22 | 4.6296 | 5.5 | + +## 9. Finality timing per block rate if C1 and d stay in blue blocks (C: spec 03 C1; the lock about 3 s after determination, lane 4's reading of 3.11.3) +| bps | checkpoint cadence, s | determination after the checkpoint, s | lock after the checkpoint, s | checkpoints per presence window of 240 indices, minutes | 30-day window in blue blocks | +|---|---|---|---|---|---| +| 1 | 30 | 60 | 63 | 120 | 2,592,000 | +| 10 | 3 | 6 | 9 | 12 | 25,920,000 | +| 32 | 0.9375 | 1.875 | 4.875 | 3.75 | 82,944,000 | +| 100 | 0.3 | 0.6 | 3.6 | 1.2 | 259,200,000 | + +## 10. Light client bytes per day (C: spec 10.5 shape; checkpoint mode 2,880 x (header 400 + certificate + proof 400) at 1 bps; scales with checkpoints per day if C1 stays in blue blocks) +| bps | checkpoints per day | checkpoint mode at 1,000 voters | at 10,000 voters | phase two (800 B per checkpoint) | +|---|---|---|---|---| +| 1 | 2,880 | 3.45 MB | 6.69 MB | 2.30 MB | +| 10 | 28,800 | 34.50 MB | 66.90 MB | 23.04 MB | +| 32 | 92,160 | 110.41 MB | 214.09 MB | 73.73 MB | +| 100 | 288,000 | 345.02 MB | 669.02 MB | 230.40 MB | diff --git a/sim/horizon/network/cost.py b/sim/horizon/network/cost.py new file mode 100644 index 00000000..7625aae3 --- /dev/null +++ b/sim/horizon/network/cost.py @@ -0,0 +1,169 @@ +#!/usr/bin/env python3 +"""Node cost, bandwidth, pruning and payout arithmetic per block rate (Horizon network lane, 6 October 2026). +Every input is labelled in INPUTS below: measured (entry named), cited (file named) or approximate. +Run: python3 sim/horizon/network/cost.py > sim/horizon/network/cost-tables.md +""" +import math + +def calculate_ghostdag_k(x, delta): + """vendor/rusty-kaspa/consensus/core/src/config/bps.rs `calculate_ghostdag_k`, in log space (the f64 form + underflows exp(-x) at x = 1000 and never terminates; same result where both run: 18 at x 10, 124 at x 100)""" + k_hat, sigma, lterm = 0, 0.0, -x + while True: + sigma += math.exp(lterm) + if 1.0 - sigma < delta: return k_hat + k_hat += 1; lterm += math.log(x / k_hat) + +INPUTS = { + # cited: vendor/rusty-kaspa consensus/core/src/config/constants.rs and bps.rs + "delay_bound_s": 5, "delta": 0.01, "merge_depth_s": 3600, "finality_s": 43200, "pruning_s": 108000, "maturity_s": 100, + # cited: docs/spec/10-light-client.md 10.5 (286 fixed header bytes), 02-consensus.md 2.4 + "header_fixed": 286, + # simulated: sim/horizon/network/results.md grid, tips mean at hop 300 ms (parents cap 10 at 1 bps, 16 above: bps.rs) + "parents_mean": {1: 1.4, 10: 4.1, 32: 8.0, 100: 14.2}, + # measured: docs/fud-ledger.md M21 sweep, live devnet p50 block 723 B with 1 coinbase; the coinbase without votes about 300 B (approximate) + "body_no_votes": 300, "record_bytes": 274, "segment_chain_blocks": 8, + # cited: fud-close docs/spec/03-finality.md 3.4.2: vote item 281 B, certificate 273 B + V/8, bounds 48 (today) and 384 (proposed) + "vote_bytes": 281, "cert_fixed": 273, "vote_cap_today": 48, "vote_cap_proposed": 384, + # cited: spec 03 C1: checkpoint every 30 blue blocks, determination d = 60 blue (placeholder; 20 on devnet) + "cp_blue": 30, "det_blue": 60, + # approximate: Kaspa's relay flow (protocol/flows, blockrelay: inv, request, block): per block, degree x 40 B of inv plus one 40 B request + "degree": 8, "inv_bytes": 40, + # measured: docs/bench-log.md M30 entry: 256 MiB cache, KEEP_DAYS 3 (768 MiB worst), slope 30.2 MB per 1,000 blocks (s8 steady, narrow DAG); + # the live app node 1,081 MB at 27 min to 2,258 MB at 4 h 14 min (about 80 KB per block, derived); run A's hub 5.4 GB at 12,000 blocks (440 KB per block, A.jsonl) + "cache_mib": 256, "keep_days": 3, "rss_per_block_kb": {"narrow (s8 steady)": 30.2, "live devnet app node (derived)": 80, "run A hub, wide DAG, 41 peers": 440}, + # measured: hands-on-build-1.md: exec-snapshot.bin 127,564,588 B at tip 141,700 chain blocks (0.9 KB per chain block); M30 note: ExecState.records 1 to 2 KB per chain block + "exec_snapshot_bytes": 127564588, "exec_snapshot_tip": 141700, "exec_record_kb": 1.5, + # measured: lottery verify per header: class v3 2.79 ms on a loaded M5 Max core (consequences C29); class v4 4.9 ms steady / 8.23 ms half-core on the box proxy (lane 2); gate 10 ms (spec 01) + "verify_ms": {"class v3 (M5 Max loaded core)": 2.79, "class v4 (box proxy)": 4.9, "class v4 half core": 8.23, "gate": 10.0}, + # approximate: one BLS signature verify about 1.5 ms (unmeasured on this stack, O-10.3) + "bls_verify_ms": 1.5, + # measured: run A hub CPU per accepted block (A.jsonl cpu_rss deltas over accepted deltas): 61 ms (mergeset about 8), 115 ms (about 30), 345 ms (150 to 200) + "hub_ms_per_block": {"mergeset 8": 61, "mergeset 30": 115, "mergeset 150 to 200": 345}, + # cited: spec 02 2.5: 31.688 IGN per DAA second at full ramp, 80% to the producer + "ign_per_s": 31.688, "miner_share": 0.8, + # the brief's tiers and networks; the rental entry: USD 0.0117 per MH/s-hour (bench-log "Rental cost of hash, 6 October 2026") + "tiers": {"4070 (25 MH/s)": 25e6, "5090 (128 MH/s)": 128e6, "8x 4090 rig (459 MH/s)": 459e6}, + "networks": {"tonight 1.16 GH/s": 1.16e9, "10 GH/s": 1e10, "100 GH/s": 1e11, "1 TH/s": 1e12, "10 TH/s": 1e13}, +} +BPS = [1, 10, 32, 100] +VOTERS = [12, 100, 1000, 8192] + +def k_of(b): return {1: 18, 10: 124, 32: 362}.get(b) or calculate_ghostdag_k(2 * INPUTS["delay_bound_s"] * b, INPUTS["delta"]) +def parents_cap(k): return max(10, min(16, k // 2)) +def mergeset_cap(k): return max(180, min(512, 2 * k)) +def header_bytes(b): return INPUTS["header_fixed"] + 32 * INPUTS["parents_mean"][b] +def fmt_bytes(n): + for u, d in (("TB", 1e12), ("GB", 1e9), ("MB", 1e6), ("KB", 1e3)): + if n >= d: return f"{n/d:.2f} {u}" + return f"{n:.0f} B" +def fmt_time(s): + if s < 60: return f"{s:.1f} s" + if s < 3600: return f"{s/60:.1f} min" + if s < 86400: return f"{s/3600:.1f} h" + return f"{s/86400:.1f} d" + +out = [] +def P(s=""): out.append(s) + +P("# cost.py tables (sim/horizon/network/cost.py). Labels: M = measured, C = cited, S = simulated, A = approximate; sources in the script's INPUTS.") +P() +P("## 1. GHOSTDAG parameters per block rate (C: bps.rs formulas; k at 100 bps computed with calculate_ghostdag_k since the table stops at 32)") +P("| bps | k | max parents | mergeset limit | merge depth, blocks | finality depth, blocks | pruning depth, blocks | coinbase maturity, blocks | checkpoint cadence (30 blue), s | determination d = 60 blue, s | blocks per 30-s window |") +P("|---|---|---|---|---|---|---|---|---|---|---|") +for b in BPS: + k = k_of(b); ms = mergeset_cap(k) + lower = INPUTS["finality_s"] * b + 2 * INPUTS["merge_depth_s"] * b + 4 * ms * k + 2 * k + 2 + prun = max(lower, INPUTS["pruning_s"] * b) + P(f"| {b} | {k} | {parents_cap(k)} | {ms} | {INPUTS['merge_depth_s']*b:,} | {INPUTS['finality_s']*b:,} | {prun:,} | {INPUTS['maturity_s']*b:,} | {INPUTS['cp_blue']/b:g} | {INPUTS['det_blue']/b:g} | {30*b} |") +P() +P("## 2. Votes and the 3.4.2 bounds per block rate (C: 281 B per vote; checkpoint every 30 BLUE blocks as C1 is written)") +P("| bps | checkpoints per day | votes per block at V voters (V/30, C1 in blue blocks) | drain capacity per checkpoint, cap 48 / 384 | vote bytes per day, V = 100 / 1,000 / 8,192 (single votes) | the same if C1 were 30 DAA seconds |") +P("|---|---|---|---|---|---|") +for b in BPS: + cps = 86400 * b / INPUTS["cp_blue"] + vpb = " / ".join(f"{v/30:.1f}" for v in (100, 1000, 8192)) + cap = f"{30*48:,} / {30*384:,}" + vb = " / ".join(fmt_bytes(v * cps * INPUTS["vote_bytes"]) for v in (100, 1000, 8192)) + vb2 = " / ".join(fmt_bytes(v * 2880 * INPUTS["vote_bytes"]) for v in (100, 1000, 8192)) + P(f"| {b} | {cps:,.0f} | {vpb} | {cap} | {vb} | {vb2} |") +P() +P("## 3. Bytes per node per day: receive every block once plus relay overhead (A: Kaspa's inv/request flow, degree 8), votes carried in bodies, one certificate per block") +P("| bps | header bytes (C+S) | blocks per day | block bytes per day (header + body + records) | relay overhead per day | votes per day at V = 12 / 100 / 1,000 / 8,192 (C1 in blue blocks) | certificates per day at V = 12 / 8,192 | total per day at V = 100 | total at V = 8,192 | Mbit/s mean at V = 8,192 |") +P("|---|---|---|---|---|---|---|---|---|---|") +for b in BPS: + hb = header_bytes(b); n = 86400 * b + blocks = n * (hb + INPUTS["body_no_votes"] + INPUTS["record_bytes"] / INPUTS["segment_chain_blocks"]) + relay = n * (INPUTS["degree"] * INPUTS["inv_bytes"] + INPUTS["inv_bytes"]) + cps = n / INPUTS["cp_blue"] + votes = {v: v * cps * INPUTS["vote_bytes"] for v in VOTERS} + certs = {v: n * (INPUTS["cert_fixed"] + v / 8) for v in VOTERS} + tot100 = blocks + relay + votes[100] + certs[100]; tot8k = blocks + relay + votes[8192] + certs[8192] + P(f"| {b} | {hb:.0f} | {n:,} | {fmt_bytes(blocks)} | {fmt_bytes(relay)} | {' / '.join(fmt_bytes(votes[v]) for v in VOTERS)} | {fmt_bytes(certs[12])} / {fmt_bytes(certs[8192])} | {fmt_bytes(tot100)} | {fmt_bytes(tot8k)} | {tot8k*8/86400/1e6:.2f} |") +P() +P("With certificates aggregated (lane 3's 1.2 KB per checkpoint) and votes NOT echoed in every body (one carriage), the vote column is the floor: a vote crosses the network once. A star hub multiplies its upload by its peer count: run A's hub sent 2.13 GB in 850 s to 41 peers (A.jsonl rx_tx, netns-wide counter, so an upper bound): 2.5 MB/s, 61 KB/s per peer.") +P() +P("## 4. Node CPU per block and the serial budget (the pipeline validates blocks in order; budget = 0.8 core)") +P("| bps | budget per block, ms | lottery verify class v3 / v4 / v4 half core (M) | BLS for V/30 votes per block at V = 100 / 1,000 / 8,192, cap 384 (A: 1.5 ms per verify) | run A hub measured total at mergeset 8 / 30 / 150+ (M) | fits the budget? |") +P("|---|---|---|---|---|---|") +for b in BPS: + budget = 800 / b + bls = " / ".join(f"{min(v/30, 384)*INPUTS['bls_verify_ms']:.0f}" for v in (100, 1000, 8192)) + hub = " / ".join(str(v) for v in INPUTS["hub_ms_per_block"].values()) + fits = "yes with margin" if budget > 120 else ("only the verify and a narrow DAG (under 25 ms per block measured nowhere yet)" if budget > 20 else "no: class v4 verify alone (4.9 to 8.2 ms) plus GHOSTDAG exceeds it") + P(f"| {b} | {budget:.0f} | 2.79 / 4.9 / 8.23 | {bls} | {hub} | {fits} |") +P() +P("## 5. RSS and disk per block rate (M: cache 256 MiB x KEEP_DAYS 3; slopes per block from M30 s8, the live app node, run A's hub; all slopes are growth over a run, not a proven steady state)") +P("| bps | caches | DAG store growth per day at 30 / 80 / 440 KB per block | growth over the 30-h pruning window at 30 / 80 KB per block | exec state (M: 128 MB snapshot at 141,700 chain blocks) | disk per day: blocks + votes at V = 1,000 + exec records |") +P("|---|---|---|---|---|---|") +for b in BPS: + n = 86400 * b + g = [n * kb * 1e3 for kb in (30.2, 80, 440)] + w = [INPUTS["pruning_s"] * b * kb * 1e3 for kb in (30.2, 80)] + hb = header_bytes(b); cps = n / 30 + chain_blocks = n / (1 + INPUTS["parents_mean"][b]) # A: one chain block per mergeset of about 1 + parents blocks + disk = n * (hb + INPUTS["body_no_votes"]) + 1000 * cps * INPUTS["vote_bytes"] + chain_blocks * INPUTS["exec_record_kb"] * 1e3 + P(f"| {b} | 768 MiB worst, 256 MiB flat | {' / '.join(fmt_bytes(x) for x in g)} | {' / '.join(fmt_bytes(x) for x in w)} | 128 MB + 1.5 KB per chain block | {fmt_bytes(disk)} |") +P() +P("## 6. Pruned and archival storage per year (C: pruning window 108,000 DAA s; A: 2x index overhead on blocks; votes single, one carriage)") +P("| bps | pruned node keeps (window of headers + bodies, 2x) | plus votes in the window at V = 1,000 | archival per year: blocks (2x) | archival votes per year at V = 100 / 1,000 / 8,192 (C1 in blue blocks) | archival votes per year if certificates only (1.2 KB per checkpoint, lane 3) |") +P("|---|---|---|---|---|---|") +for b in BPS: + hb = header_bytes(b); win = INPUTS["pruning_s"] * b + kept = 2 * win * (hb + INPUTS["body_no_votes"]); kv = 1000 * (win / 30) * INPUTS["vote_bytes"] + yr = 31557600 * b + arch = 2 * yr * (hb + INPUTS["body_no_votes"]); cps_y = yr / 30 + av = " / ".join(fmt_bytes(v * cps_y * INPUTS["vote_bytes"]) for v in (100, 1000, 8192)) + P(f"| {b} | {fmt_bytes(kept)} | {fmt_bytes(kv)} | {fmt_bytes(arch)} | {av} | {fmt_bytes(cps_y * 1200)} |") +P() +P("## 7. Subsidy per block and payout intervals (C: 31.688 IGN per DAA s at full ramp, 80% to the producer; interval = 1 / (bps x share))") +P("| bps | subsidy per block, IGN | miner's 80%, IGN | " + " | ".join(f"{t} at {nname}" for nname in INPUTS["networks"] for t in ("4070", "5090", "rig")) + " |") +P("|---|---|---|" + "---|" * (3 * len(INPUTS["networks"]))) +for b in BPS: + sub = INPUTS["ign_per_s"] / b + cells = [] + for nname, nh in INPUTS["networks"].items(): + for t, h in INPUTS["tiers"].items(): + cells.append(fmt_time(1 / (b * h / nh))) + P(f"| {b} | {sub:.3f} | {sub*INPUTS['miner_share']:.3f} | " + " | ".join(cells) + " |") +P() +P("## 8. The solo-miner question: a 4070 at 25 MH/s, blocks per day and the rate needed for one block a day") +P("| network hash | share | blocks per day at 1 bps | bps for one block a day | IGN per day at 1 bps (full ramp, 80%) |") +P("|---|---|---|---|---|") +for nname, nh in INPUTS["networks"].items(): + share = 25e6 / nh; bpd = 86400 * share + P(f"| {nname} | {share:.2e} | {bpd:.2f} | {1/bpd:.4f} | {bpd * INPUTS['ign_per_s'] * INPUTS['miner_share']:,.1f} |") +P() +P("## 9. Finality timing per block rate if C1 and d stay in blue blocks (C: spec 03 C1; the lock about 3 s after determination, lane 4's reading of 3.11.3)") +P("| bps | checkpoint cadence, s | determination after the checkpoint, s | lock after the checkpoint, s | checkpoints per presence window of 240 indices, minutes | 30-day window in blue blocks |") +P("|---|---|---|---|---|---|") +for b in BPS: + P(f"| {b} | {30/b:g} | {60/b:g} | {60/b + 3:g} | {240*30/b/60:g} | {2592000*b:,} |") +P() +P("## 10. Light client bytes per day (C: spec 10.5 shape; checkpoint mode 2,880 x (header 400 + certificate + proof 400) at 1 bps; scales with checkpoints per day if C1 stays in blue blocks)") +P("| bps | checkpoints per day | checkpoint mode at 1,000 voters | at 10,000 voters | phase two (800 B per checkpoint) |") +P("|---|---|---|---|---|") +for b in BPS: + cps = 2880 * b + P(f"| {b} | {cps:,} | {fmt_bytes(cps * (400 + 273 + 125 + 400))} | {fmt_bytes(cps * (400 + 273 + 1250 + 400))} | {fmt_bytes(cps * 800)} |") +print("\n".join(out)) diff --git a/sim/horizon/network/propagation.py b/sim/horizon/network/propagation.py new file mode 100644 index 00000000..fde9fae9 --- /dev/null +++ b/sim/horizon/network/propagation.py @@ -0,0 +1,257 @@ +#!/usr/bin/env python3 +"""Propagation and red-share model for the Horizon network lane (lane 5, 6 October 2026). + +What it models: + * Poisson block production at `bps` blocks per second, split evenly over the mining nodes. + * A topology: `star` (every miner peers with one hub only, the Devnet 2 run A shape: tools/fleet/box-dn2.sh passes a + single --addpeer=, the collector read peers=41 at the seed and peers=1 at every pod) or `mesh` (every node + has `degree` random peers, blocks flood by gossip, each node forwards a block once to the peers that have not + announced it). + * One relay hop = inv, request, block: three link traversals (measured in the M21 run, docs/fud-ledger.md M21: + one hop p50 318 to 329 ms on 100-ms proxied links) plus the receiver's own processing (6 to 16 ms measured there). + The link latency is lognormal with median `--link` ms and sigma 0.29 (the cloud devnet's p50 343 / p90 497 ms + spread, docs/bench-log.md "4 October 2026, cloud devnet"). `--hop` sets the hop median directly instead. + * The hub (star only) is a single server: validating a block costs s0 + s1 x mergeset ms (`--hub-s0`, `--hub-s1`); + blocks queue behind it. Run A's hub went from about 6 ms of CPU per accepted block at mergeset 8 to about 400 ms + at mergeset 150 to 200 (A.jsonl cpu_rss against the seed log's Processed lines), so the defaults are 6 and 2. + `--hub-s1 0 --hub-s0 0` is an ideal hub. + * GHOSTDAG colouring: for every block, selected parent = max blue work (hash tiebreak), mergeset = past minus the + selected parent's past, candidates in topological order; a candidate is blue when its anticone inside the blue + set of the new block's chain is at most k (the first k-cluster condition of + vendor/igneum-node/consensus/src/processes/ghostdag/protocol.rs). The second condition (every blue peer's own + anticone bound) is approximated by the candidate's check, so this undercounts reds slightly. Parents capped at + `--parents` by blue work (Kaspa's max_block_parents, consensus/core/src/config/bps.rs). The mergeset size + limit is applied: candidates past the limit are left for a later block (they stay in the anticone). + * Miners join over `--join-spread` seconds with a view of genesis only and mine at once (the fleet's + --enable-unsynced-mining start; run A's pods joined 19:23 to 19:29Z). + * `--schedule a.csv` replaces the constant rate with a per-minute production schedule (blocks per minute). + +What it reports: cumulative red share (reds in the mergesets of the final selected chain, over all merged blocks), +mean mergeset size, mean tips at a miner, the natural selected-chain reorg depth at miner 0 (max and p99), the hub's +mean queueing delay and utilisation, mean end-to-end delay to 90% of nodes. + +Run (the lock is the main checkout's): + /Users/joshm/Projects/igneum/tools/lock/with-lock.sh run nice -n 19 python3 sim/horizon/network/propagation.py --grid + ... --topology star --bps 10 --k 124 --nodes 41 --link 40 --hub-s0 6 --hub-s1 2 --seconds 600 --join-spread 360 +""" +import argparse, heapq, math, random, sys, time + +def lognormal(rng, median, sigma): + return median * math.exp(rng.gauss(0.0, sigma)) + +class Dag: + def __init__(self, k, parents_cap, mergeset_cap, rng): + self.k, self.pcap, self.mcap, self.rng = k, parents_cap, mergeset_cap, rng + self.parents = [()]; self.past = [0]; self.sp = [-1] + self.blueset = [1] # bitset: blues of the block's chain incl. itself + self.nblue = [0]; self.nred = [0] + self.blue_work = [0]; self.hash = [0]; self.time = [0.0]; self.miner = [-1] + self.children = [0] + def key(self, b): return (self.blue_work[b], self.hash[b]) + def anc(self, a, b): return (self.past[b] >> a) & 1 + def add(self, parents, t, miner): + parents = sorted(set(parents), key=self.key, reverse=True)[: self.pcap] + sp = parents[0] + past = 0 + for p in parents: past |= self.past[p] | (1 << p) + x = past & ~self.past[sp] & ~(1 << sp) + ms = [] + while x: + lsb = x & -x; ms.append(lsb.bit_length() - 1); x ^= lsb + ms.sort(key=self.key) + blueset = self.blueset[sp]; nb = 1; nr = 0; k = self.k + for c in ms[: self.mcap]: + cand = blueset & ~self.past[c] & ~(1 << c) + n = 0; ok = True + while cand: + lsb = cand & -cand; y = lsb.bit_length() - 1; cand ^= lsb + if not self.anc(c, y): + n += 1 + if n > k: ok = False; break + if ok: blueset |= (1 << c); nb += 1 + else: nr += 1 + idx = len(self.parents) + self.parents.append(tuple(parents)); self.past.append(past); self.sp.append(sp) + self.blueset.append(blueset | (1 << idx)); self.nblue.append(nb); self.nred.append(nr) + self.blue_work.append(self.blue_work[sp] + nb); self.hash.append(self.rng.getrandbits(62)) + self.time.append(t); self.miner.append(miner); self.children.append(0) + for p in parents: self.children[p] += 1 + return idx + def reorg_depth(self, old_tip, new_tip): + """chain blocks of old_tip's selected chain that are not ancestors (or self) of new_tip""" + if old_tip == new_tip or self.anc(old_tip, new_tip): return 0 + d = 0; cur = old_tip + while cur > 0 and not self.anc(cur, new_tip) and cur != new_tip: + d += 1; cur = self.sp[cur] + return d + +class Node: + __slots__ = ("id", "known", "tips", "peers", "tip", "reorgs", "joined", "ntips") + def __init__(self, i): + self.id = i; self.known = 1; self.tips = {0}; self.peers = []; self.tip = 0; self.reorgs = []; self.joined = False; self.ntips = [] + +def simulate(a, rng): + dag = Dag(a.k, a.parents, a.mergeset, rng) + hub = a.topology == "star" + N = a.nodes + (1 if hub else 0) + nodes = [Node(i) for i in range(N)] + miners = list(range(1, N)) if hub else list(range(N)) + if hub: + for m in miners: nodes[m].peers = [0] + nodes[0].peers = miners[:] + else: + for i in range(N): + others = [j for j in range(N) if j != i] + for j in rng.sample(others, min(a.degree, len(others))): + if j not in nodes[i].peers: nodes[i].peers.append(j) + if i not in nodes[j].peers: nodes[j].peers.append(i) + joins = {m: rng.uniform(0, a.join_spread) for m in miners} + sigma = 0.29 + def hop_delay(): + if a.hop: return lognormal(rng, a.hop, sigma) / 1000.0 + return (3 * lognormal(rng, a.link, sigma) + a.proc) / 1000.0 + ev = [] # (time, seq, kind, payload) + seq = 0 + def push(t, kind, payload): + nonlocal seq; seq += 1; heapq.heappush(ev, (t, seq, kind, payload)) + # production schedule + sched = None + if a.schedule: + sched = [float(l.split(",")[-1]) for l in open(a.schedule) if l.strip() and not l.startswith("#")] + def rate_at(t): + if sched is None: return a.bps + i = int(t // 60); return (sched[i] if i < len(sched) else sched[-1]) / 60.0 + t = 0.0 + r0 = rate_at(0.0) + push(rng.expovariate(r0) if r0 > 0 else 1e9, "mine", None) + hub_busy_until = 0.0; hub_wait = 0.0; hub_served = 0; hub_busy_total = 0.0 + node_busy_until = [0.0] * N; node_wait = 0.0; node_served = 0; node_queued = [0] * N + arrivals = {} # block -> list of arrival times + produced = 0 + def receive(n, b, now): + node = nodes[n] + if (node.known >> b) & 1: return False + node.known |= (1 << b) + for p in dag.parents[b]: node.tips.discard(p) + # b is a tip at this node unless a known child exists (rare: children arrive after parents in this model) + node.tips.add(b) + old = node.tip + if dag.key(b) > dag.key(old): + d = dag.reorg_depth(old, b) + if d > 0: node.reorgs.append(d) + node.tip = b + arrivals.setdefault(b, []).append(now) + return True + def hub_process(b, now): + nonlocal hub_busy_until, hub_wait, hub_served, hub_busy_total + start = max(now, hub_busy_until) + m = dag.nblue[b] + dag.nred[b] + s = (a.hub_s0 + a.hub_s1 * m) / 1000.0 + hub_busy_until = start + s; hub_wait += start - now; hub_served += 1; hub_busy_total += s + push(hub_busy_until, "hubdone", b) + while ev: + t, _, kind, payload = heapq.heappop(ev) + if t > a.seconds: break + if kind == "mine": + r = rate_at(t) + push(t + (rng.expovariate(r) if r > 0 else 1e9), "mine", None) + active = [m for m in miners if joins[m] <= t] + if not active: continue + m = rng.choice(active); node = nodes[m] + if not node.joined: node.joined = True + node.ntips.append(len(node.tips)) + b = dag.add(list(node.tips), t, m); produced += 1 + receive(m, b, t) + for p in node.peers: push(t + hop_delay(), "arrive", (p, b)) + elif kind == "arrive": + n, b = payload + if hub and n == 0: + if (nodes[0].known >> b) & 1: continue + nodes[0].known |= (1 << b) # accepted into the queue once + hub_process(b, t) + elif a.node_s0 or a.node_s1: + if (nodes[n].known >> b) & 1 or (node_queued[n] >> b) & 1: continue + node_queued[n] |= (1 << b) + start = max(t, node_busy_until[n]); m = dag.nblue[b] + dag.nred[b] + node_busy_until[n] = start + (a.node_s0 + a.node_s1 * m) / 1000.0 + node_wait += start - t; node_served += 1 + push(node_busy_until[n], "nodedone", (n, b)) + else: + if receive(n, b, t): + for p in nodes[n].peers: push(t + hop_delay(), "arrive", (p, b)) + elif kind == "nodedone": + n, b = payload + if receive(n, b, t): + for p in nodes[n].peers: + if p != dag.miner[b]: push(t + hop_delay(), "arrive", (p, b)) + elif kind == "hubdone": + b = payload + nodes[0].known &= ~(1 << b); receive(0, b, t) + for p in nodes[0].peers: + if p != dag.miner[b]: push(t + hop_delay(), "arrive", (p, b)) + # metrics from the hub's (or node 0's) final selected chain + tip = max(range(len(dag.parents)), key=dag.key) + blues = reds = chain = 0; ms_sizes = [] + cur = tip + while cur > 0: + blues += dag.nblue[cur]; reds += dag.nred[cur]; chain += 1; ms_sizes.append(dag.nblue[cur] + dag.nred[cur]) + cur = dag.sp[cur] + merged = blues + reds + red_share = reds / merged if merged else 0.0 + m0 = nodes[miners[0]] + reorgs = sorted(m0.reorgs) + p99 = reorgs[int(0.99 * (len(reorgs) - 1))] if reorgs else 0 + # delay to 90% of nodes + d90 = [] + for b, arr in arrivals.items(): + if len(arr) >= 0.9 * len(miners): + arr = sorted(arr); d90.append(arr[int(0.9 * len(miners)) - 1] - dag.time[b]) + tips_mean = sum(sum(n.ntips) for n in nodes) / max(1, sum(len(n.ntips) for n in nodes)) + return dict(produced=produced, merged=merged, red_share=red_share, chain=chain, ms_mean=sum(ms_sizes) / max(1, len(ms_sizes)), + ms_max=max(ms_sizes) if ms_sizes else 0, tips_mean=tips_mean, reorg_max=max(reorgs) if reorgs else 0, reorg_p99=p99, + hub_wait=hub_wait / max(1, hub_served), hub_util=hub_busy_total / max(a.seconds, 1e-9), d90=sum(d90) / max(1, len(d90)), + node_wait=node_wait / max(1, node_served), + blocks_per_s=produced / a.seconds) + +def main(): + ap = argparse.ArgumentParser() + ap.add_argument("--topology", default="mesh", choices=["mesh", "star"]) + ap.add_argument("--bps", type=float, default=10); ap.add_argument("--k", type=int, default=124) + ap.add_argument("--nodes", type=int, default=41); ap.add_argument("--degree", type=int, default=8) + ap.add_argument("--parents", type=int, default=16); ap.add_argument("--mergeset", type=int, default=248) + ap.add_argument("--link", type=float, default=40.0, help="one-way link latency median, ms") + ap.add_argument("--hop", type=float, default=0.0, help="hop delay median, ms (overrides --link)") + ap.add_argument("--proc", type=float, default=10.0, help="receiver processing per hop, ms") + ap.add_argument("--hub-s0", type=float, default=0.0); ap.add_argument("--hub-s1", type=float, default=0.0) + ap.add_argument("--node-s0", type=float, default=0.0, help="every other node's per-block validation cost, ms") + ap.add_argument("--node-s1", type=float, default=0.0, help="plus this many ms per mergeset block") + ap.add_argument("--seconds", type=float, default=300); ap.add_argument("--join-spread", type=float, default=0.0) + ap.add_argument("--schedule", default=None); ap.add_argument("--seed", type=int, default=7) + ap.add_argument("--grid", action="store_true", help="the lane's grid: bps x hop delay, mesh of degree 8, ideal links") + ap.add_argument("--out", default=None) + a = ap.parse_args() + lines = [] + def emit(s): print(s, flush=True); lines.append(s) + if a.grid: + from_k = {1: 18, 10: 124, 32: 362} + emit("| bps | k | hop delay median ms | red share | mergeset mean / max | tips mean | reorg max / p99 (miner 0) | delay to 90% of nodes s | blocks/s |") + emit("|---|---|---|---|---|---|---|---|---|") + for bps in (1, 10, 32, 100): + k = from_k.get(bps) or 1074 + for hop in (50, 100, 300, 1000): + rng = random.Random(a.seed) + b = argparse.Namespace(**vars(a)); b.bps = bps; b.k = k; b.hop = hop; b.topology = "mesh"; b.hub_s0 = 0; b.hub_s1 = 0 + b.mergeset = 180 if bps == 1 else min(512, 2 * k); b.parents = 10 if bps == 1 else 16 + b.seconds = min(a.seconds, max(60.0, 12000.0 / bps)) + t0 = time.time(); r = simulate(b, rng) + emit(f"| {bps} | {k} | {hop} | {100*r['red_share']:.1f}% | {r['ms_mean']:.1f} / {r['ms_max']} | {r['tips_mean']:.1f} | {r['reorg_max']} / {r['reorg_p99']} | {r['d90']:.2f} | {r['blocks_per_s']:.1f} |") + print(f"# ({b.seconds:.0f} s simulated in {time.time()-t0:.0f} s wall)", file=sys.stderr) + else: + rng = random.Random(a.seed); r = simulate(a, rng) + emit(f"{a.topology} bps={a.bps} k={a.k} nodes={a.nodes} degree={a.degree} link={a.link} hop={a.hop} hub=({a.hub_s0},{a.hub_s1}) node=({a.node_s0},{a.node_s1}) join={a.join_spread} sched={a.schedule} seconds={a.seconds} seed={a.seed}") + emit(f"produced {r['produced']} ({r['blocks_per_s']:.2f}/s) merged {r['merged']} red share {100*r['red_share']:.1f}% chain {r['chain']} mergeset mean {r['ms_mean']:.1f} max {r['ms_max']} tips {r['tips_mean']:.1f} reorg max {r['reorg_max']} p99 {r['reorg_p99']} hub wait {r['hub_wait']:.2f}s util {r['hub_util']:.2f} node wait {r['node_wait']:.2f}s d90 {r['d90']:.2f}s") + if a.out: + with open(a.out, "a") as f: f.write("\n".join(lines) + "\n") + +if __name__ == "__main__": + main() diff --git a/sim/horizon/network/results-2.md b/sim/horizon/network/results-2.md new file mode 100644 index 00000000..1b56a6d6 --- /dev/null +++ b/sim/horizon/network/results-2.md @@ -0,0 +1,48 @@ +# propagation.py results, batch 2 (per-block node cost), 2026-10-06T20:07:52Z, Mac, under with-lock run, seed 7 + +## E. Run A replay with the hub's MEASURED per-block CPU (61, 115, 345 ms per accepted block; A.jsonl cpu_rss deltas over accepted deltas), link 40 ms +star bps=10.0 k=124 nodes=41 degree=8 link=40.0 hop=0.0 hub=(61.0,0.0) node=(0.0,0.0) join=0.0 sched=sim/horizon/network/runA-schedule.csv seconds=1200.0 seed=7 +produced 12812 (10.68/s) merged 12812 red share 46.8% chain 1461 mergeset mean 8.8 max 194 tips 20.2 reorg max 43 p99 4 hub wait 26.35s util 0.65 node wait 0.00s d90 26.71s +star bps=10.0 k=124 nodes=41 degree=8 link=40.0 hop=0.0 hub=(115.0,0.0) node=(0.0,0.0) join=0.0 sched=sim/horizon/network/runA-schedule.csv seconds=1200.0 seed=7 +produced 12815 (10.68/s) merged 9108 red share 88.1% chain 441 mergeset mean 20.7 max 199 tips 21.6 reorg max 2 p99 2 hub wait 320.84s util 1.23 node wait 0.00s d90 276.55s +star bps=10.0 k=124 nodes=41 degree=8 link=40.0 hop=0.0 hub=(345.0,0.0) node=(0.0,0.0) join=0.0 sched=sim/horizon/network/runA-schedule.csv seconds=1200.0 seed=7 +produced 12746 (10.62/s) merged 3509 red share 82.6% chain 358 mergeset mean 9.8 max 45 tips 9.9 reorg max 3 p99 2 hub wait 1768.55s util 3.66 node wait 0.00s d90 439.87s + +## F. The knee: star at a constant 10 bps, hub per-block cost 20 to 150 ms, 600 s +star bps=10.0 k=124 nodes=41 degree=8 link=40.0 hop=0.0 hub=(20.0,0.0) node=(0.0,0.0) join=0.0 sched=None seconds=600.0 seed=7 +produced 5923 (9.87/s) merged 5916 red share 0.0% chain 1712 mergeset mean 3.5 max 10 tips 3.5 reorg max 2 p99 2 hub wait 0.00s util 0.20 node wait 0.00s d90 0.33s +star bps=10.0 k=124 nodes=41 degree=8 link=40.0 hop=0.0 hub=(40.0,0.0) node=(0.0,0.0) join=0.0 sched=None seconds=600.0 seed=7 +produced 5906 (9.84/s) merged 5900 red share 0.0% chain 1592 mergeset mean 3.7 max 10 tips 3.7 reorg max 3 p99 2 hub wait 0.01s util 0.39 node wait 0.00s d90 0.36s +star bps=10.0 k=124 nodes=41 degree=8 link=40.0 hop=0.0 hub=(60.0,0.0) node=(0.0,0.0) join=0.0 sched=None seconds=600.0 seed=7 +produced 6072 (10.12/s) merged 6065 red share 0.0% chain 1478 mergeset mean 4.1 max 12 tips 4.1 reorg max 3 p99 2 hub wait 0.05s util 0.61 node wait 0.00s d90 0.41s +star bps=10.0 k=124 nodes=41 degree=8 link=40.0 hop=0.0 hub=(80.0,0.0) node=(0.0,0.0) join=0.0 sched=None seconds=600.0 seed=7 +produced 6101 (10.17/s) merged 6096 red share 0.0% chain 1244 mergeset mean 4.9 max 20 tips 5.2 reorg max 4 p99 2 hub wait 0.18s util 0.81 node wait 0.00s d90 0.57s +star bps=10.0 k=124 nodes=41 degree=8 link=40.0 hop=0.0 hub=(100.0,0.0) node=(0.0,0.0) join=0.0 sched=None seconds=600.0 seed=7 +produced 6089 (10.15/s) merged 5947 red share 15.2% chain 293 mergeset mean 20.3 max 116 tips 22.6 reorg max 8 p99 7 hub wait 8.50s util 1.01 node wait 0.00s d90 8.81s +star bps=10.0 k=124 nodes=41 degree=8 link=40.0 hop=0.0 hub=(150.0,0.0) node=(0.0,0.0) join=0.0 sched=None seconds=600.0 seed=7 +produced 6048 (10.08/s) merged 3986 red share 75.2% chain 146 mergeset mean 27.3 max 66 tips 21.8 reorg max 3 p99 3 hub wait 156.83s util 1.51 node wait 0.00s d90 105.57s + +## G. Mesh of degree 8 where EVERY node pays the per-block cost (40, 80, 115 ms), 10 bps, 600 s +mesh bps=10.0 k=124 nodes=42 degree=8 link=40.0 hop=0.0 hub=(0.0,0.0) node=(40.0,0.0) join=0.0 sched=None seconds=600.0 seed=7 +produced 5763 (9.61/s) merged 5763 red share 0.0% chain 1874 mergeset mean 3.1 max 9 tips 3.0 reorg max 3 p99 2 hub wait 0.00s util 0.00 node wait 0.01s d90 0.34s +mesh bps=10.0 k=124 nodes=42 degree=8 link=40.0 hop=0.0 hub=(0.0,0.0) node=(80.0,0.0) join=0.0 sched=None seconds=600.0 seed=7 +produced 6034 (10.06/s) merged 6030 red share 0.0% chain 1356 mergeset mean 4.4 max 18 tips 4.3 reorg max 3 p99 2 hub wait 0.00s util 0.00 node wait 0.12s d90 0.66s +mesh bps=10.0 k=124 nodes=42 degree=8 link=40.0 hop=0.0 hub=(0.0,0.0) node=(115.0,0.0) join=0.0 sched=None seconds=600.0 seed=7 +produced 6069 (10.12/s) merged 5318 red share 52.4% chain 159 mergeset mean 33.4 max 160 tips 15.2 reorg max 16 p99 11 hub wait 0.00s util 0.00 node wait 25.98s d90 46.48s + +## H. Mesh at 1 bps with the same per-block costs (the control), 600 s +mesh bps=1.0 k=18 nodes=42 degree=8 link=40.0 hop=0.0 hub=(0.0,0.0) node=(115.0,0.0) join=0.0 sched=None seconds=600.0 seed=7 +produced 645 (1.07/s) merged 645 red share 0.0% chain 455 mergeset mean 1.4 max 5 tips 1.4 reorg max 2 p99 1 hub wait 0.00s util 0.00 node wait 0.01s d90 0.49s +mesh bps=1.0 k=18 nodes=42 degree=8 link=40.0 hop=0.0 hub=(0.0,0.0) node=(345.0,0.0) join=0.0 sched=None seconds=600.0 seed=7 +produced 562 (0.94/s) merged 562 red share 0.0% chain 341 mergeset mean 1.6 max 7 tips 1.7 reorg max 2 p99 2 hub wait 0.00s util 0.00 node wait 0.07s d90 1.08s + +## I. Star at 10 bps, ideal hub, link latency 40 to 300 ms (pure latency) +star bps=10.0 k=124 nodes=41 degree=8 link=40.0 hop=0.0 hub=(0.0,0.0) node=(0.0,0.0) join=0.0 sched=None seconds=600.0 seed=7 +produced 5906 (9.84/s) merged 5901 red share 0.0% chain 1802 mergeset mean 3.3 max 9 tips 3.3 reorg max 3 p99 2 hub wait 0.00s util 0.00 node wait 0.00s d90 0.30s +star bps=10.0 k=124 nodes=41 degree=8 link=100.0 hop=0.0 hub=(0.0,0.0) node=(0.0,0.0) join=0.0 sched=None seconds=600.0 seed=7 +produced 5954 (9.92/s) merged 5948 red share 0.0% chain 1017 mergeset mean 5.8 max 15 tips 5.9 reorg max 3 p99 2 hub wait 0.00s util 0.00 node wait 0.00s d90 0.73s +star bps=10.0 k=124 nodes=41 degree=8 link=200.0 hop=0.0 hub=(0.0,0.0) node=(0.0,0.0) join=0.0 sched=None seconds=600.0 seed=7 +produced 6083 (10.14/s) merged 6075 red share 0.0% chain 609 mergeset mean 10.0 max 20 tips 9.4 reorg max 4 p99 3 hub wait 0.00s util 0.00 node wait 0.00s d90 1.45s +star bps=10.0 k=124 nodes=41 degree=8 link=300.0 hop=0.0 hub=(0.0,0.0) node=(0.0,0.0) join=0.0 sched=None seconds=600.0 seed=7 +produced 5862 (9.77/s) merged 5847 red share 0.0% chain 472 mergeset mean 12.4 max 23 tips 11.8 reorg max 4 p99 3 hub wait 0.00s util 0.00 node wait 0.00s d90 2.16s +DONE 20:09:57 diff --git a/sim/horizon/network/results.md b/sim/horizon/network/results.md new file mode 100644 index 00000000..21bfa35b --- /dev/null +++ b/sim/horizon/network/results.md @@ -0,0 +1,48 @@ +# propagation.py results, 2026-10-06T20:06:05Z, Mac, under with-lock run, seed 7 + +## A. Run A replay: star, 41 miners through one hub, measured per-minute production, join spread 360 s +star bps=10.0 k=124 nodes=41 degree=8 link=40.0 hop=0.0 hub=(6.0,2.0) join=360.0 seconds=1500.0 seed=7 +produced 14013 (9.34/s) merged 14011 red share 0.0% chain 3834 mergeset mean 3.7 max 15 tips 4.3 reorg max 3 p99 2 hub wait 0.00s util 0.14 d90 0.32s +star bps=10.0 k=124 nodes=41 degree=8 link=40.0 hop=0.0 hub=(0.0,0.0) join=360.0 seconds=1500.0 seed=7 +produced 13812 (9.21/s) merged 13811 red share 0.0% chain 3901 mergeset mean 3.5 max 14 tips 4.1 reorg max 4 p99 2 hub wait 0.00s util 0.00 d90 0.31s +star bps=10.0 k=124 nodes=41 degree=8 link=40.0 hop=0.0 hub=(6.0,2.0) join=0.0 seconds=1500.0 seed=7 +produced 13833 (9.22/s) merged 13833 red share 0.0% chain 3737 mergeset mean 3.7 max 19 tips 4.5 reorg max 3 p99 2 hub wait 0.01s util 0.14 d90 0.33s + +## B. Star against mesh at 10 bps, constant rate, 600 s, link 40 ms +star bps=10.0 k=124 nodes=41 degree=8 link=40.0 hop=0.0 hub=(0.0,0.0) join=0.0 seconds=600.0 seed=7 +produced 5906 (9.84/s) merged 5901 red share 0.0% chain 1802 mergeset mean 3.3 max 9 tips 3.3 reorg max 3 p99 2 hub wait 0.00s util 0.00 d90 0.30s +star bps=10.0 k=124 nodes=41 degree=8 link=40.0 hop=0.0 hub=(6.0,2.0) join=0.0 seconds=600.0 seed=7 +produced 5973 (9.96/s) merged 5970 red share 0.0% chain 1741 mergeset mean 3.4 max 12 tips 3.5 reorg max 3 p99 2 hub wait 0.00s util 0.13 d90 0.32s +mesh bps=10.0 k=124 nodes=42 degree=8 link=40.0 hop=0.0 hub=(0.0,0.0) join=0.0 seconds=600.0 seed=7 +produced 6038 (10.06/s) merged 6034 red share 0.0% chain 2298 mergeset mean 2.6 max 9 tips 2.6 reorg max 2 p99 2 hub wait 0.00s util 0.00 d90 0.24s +mesh bps=10.0 k=124 nodes=42 degree=4 link=40.0 hop=0.0 hub=(0.0,0.0) join=0.0 seconds=600.0 seed=7 +produced 6003 (10.01/s) merged 6001 red share 0.0% chain 2048 mergeset mean 2.9 max 9 tips 2.9 reorg max 2 p99 2 hub wait 0.00s util 0.00 d90 0.32s +mesh bps=10.0 k=124 nodes=42 degree=8 link=150.0 hop=0.0 hub=(0.0,0.0) join=0.0 seconds=600.0 seed=7 +produced 6090 (10.15/s) merged 6085 red share 0.0% chain 1066 mergeset mean 5.7 max 15 tips 5.3 reorg max 4 p99 3 hub wait 0.00s util 0.00 d90 0.84s + +## C. Star at 10 bps with a genesis burst (26 blocks/s for 5 min) and the measured hub cost +star bps=10.0 k=124 nodes=41 degree=8 link=40.0 hop=0.0 hub=(6.0,2.0) join=0.0 seconds=600.0 seed=7 +produced 10840 (18.07/s) merged 10836 red share 0.0% chain 1995 mergeset mean 5.4 max 17 tips 5.9 reorg max 3 p99 2 hub wait 0.01s util 0.33 d90 0.33s +mesh bps=10.0 k=124 nodes=42 degree=8 link=40.0 hop=0.0 hub=(0.0,0.0) join=0.0 seconds=600.0 seed=7 +produced 10727 (17.88/s) merged 10724 red share 0.0% chain 2824 mergeset mean 3.8 max 11 tips 3.9 reorg max 3 p99 2 hub wait 0.00s util 0.00 d90 0.24s + +## D. The grid: mesh of degree 8, 42 nodes, ideal links, hop delay median 50 to 1,000 ms +| bps | k | hop delay median ms | red share | mergeset mean / max | tips mean | reorg max / p99 (miner 0) | delay to 90% of nodes s | blocks/s | +|---|---|---|---|---|---|---|---|---| +| 1 | 18 | 50 | 0.0% | 1.1 / 2 | 1.1 | 1 / 1 | 0.09 | 1.0 | +| 1 | 18 | 100 | 0.0% | 1.2 / 3 | 1.2 | 1 / 1 | 0.18 | 1.0 | +| 1 | 18 | 300 | 0.0% | 1.4 / 5 | 1.4 | 1 / 1 | 0.55 | 1.0 | +| 1 | 18 | 1000 | 0.0% | 2.3 / 7 | 2.4 | 2 / 2 | 1.82 | 1.0 | +| 10 | 124 | 50 | 0.0% | 1.7 / 6 | 1.7 | 2 / 1 | 0.09 | 10.0 | +| 10 | 124 | 100 | 0.0% | 2.3 / 7 | 2.3 | 2 / 2 | 0.18 | 10.1 | +| 10 | 124 | 300 | 0.0% | 4.2 / 12 | 4.1 | 3 / 2 | 0.55 | 9.7 | +| 10 | 124 | 1000 | 0.0% | 9.2 / 22 | 8.1 | 4 / 3 | 1.82 | 9.9 | +| 32 | 362 | 50 | 0.0% | 2.9 / 9 | 2.9 | 3 / 2 | 0.09 | 32.1 | +| 32 | 362 | 100 | 0.0% | 4.4 / 14 | 4.3 | 3 / 2 | 0.18 | 31.7 | +| 32 | 362 | 300 | 0.0% | 9.2 / 26 | 8.0 | 5 / 3 | 0.55 | 32.1 | +| 32 | 362 | 1000 | 0.0% | 18.8 / 42 | 14.4 | 6 / 5 | 1.82 | 31.6 | +| 100 | 1074 | 50 | 0.0% | 6.0 / 17 | 5.6 | 4 / 3 | 0.09 | 100.4 | +| 100 | 1074 | 100 | 0.0% | 9.4 / 22 | 8.2 | 4 / 3 | 0.18 | 99.1 | +| 100 | 1074 | 300 | 0.0% | 18.5 / 40 | 14.2 | 6 / 5 | 0.55 | 101.4 | +| 100 | 1074 | 1000 | 0.0% | 31.1 / 104 | 25.3 | 8 / 8 | 1.82 | 100.5 | +DONE 20:08:58 diff --git a/sim/horizon/network/run-batch.sh b/sim/horizon/network/run-batch.sh new file mode 100755 index 00000000..d15134d7 --- /dev/null +++ b/sim/horizon/network/run-batch.sh @@ -0,0 +1,23 @@ +#!/usr/bin/env bash +# the lane's propagation runs; output appended to results.md (every line is the simulator's) +cd /Users/joshm/Projects/igneum-wt-horizon +P="python3 sim/horizon/network/propagation.py" +O=sim/horizon/network/results.md +echo "# propagation.py results, $(date -u +%FT%TZ), Mac, under with-lock run, seed 7" > $O +echo "" >> $O; echo "## A. Run A replay: star, 41 miners through one hub, measured per-minute production, join spread 360 s" >> $O +$P --topology star --nodes 41 --bps 10 --k 124 --link 40 --hub-s0 6 --hub-s1 2 --join-spread 360 --schedule sim/horizon/network/runA-schedule.csv --seconds 1500 --out $O +$P --topology star --nodes 41 --bps 10 --k 124 --link 40 --hub-s0 0 --hub-s1 0 --join-spread 360 --schedule sim/horizon/network/runA-schedule.csv --seconds 1500 --out $O +$P --topology star --nodes 41 --bps 10 --k 124 --link 40 --hub-s0 6 --hub-s1 2 --join-spread 0 --schedule sim/horizon/network/runA-schedule.csv --seconds 1500 --out $O +echo "" >> $O; echo "## B. Star against mesh at 10 bps, constant rate, 600 s, link 40 ms" >> $O +$P --topology star --nodes 41 --bps 10 --k 124 --link 40 --hub-s0 0 --hub-s1 0 --seconds 600 --out $O +$P --topology star --nodes 41 --bps 10 --k 124 --link 40 --hub-s0 6 --hub-s1 2 --seconds 600 --out $O +$P --topology mesh --nodes 42 --degree 8 --bps 10 --k 124 --link 40 --seconds 600 --out $O +$P --topology mesh --nodes 42 --degree 4 --bps 10 --k 124 --link 40 --seconds 600 --out $O +$P --topology mesh --nodes 42 --degree 8 --bps 10 --k 124 --link 150 --seconds 600 --out $O +echo "" >> $O; echo "## C. Star at 10 bps with a genesis burst (26 blocks/s for 5 min) and the measured hub cost" >> $O +printf '# burst then target\n0,1559\n1,1559\n2,1559\n3,1559\n4,1559\n5,600\n6,600\n7,600\n8,600\n9,600\n' > sim/horizon/network/burst-schedule.csv +$P --topology star --nodes 41 --bps 10 --k 124 --link 40 --hub-s0 6 --hub-s1 2 --schedule sim/horizon/network/burst-schedule.csv --seconds 600 --out $O +$P --topology mesh --nodes 42 --degree 8 --bps 10 --k 124 --link 40 --schedule sim/horizon/network/burst-schedule.csv --seconds 600 --out $O +echo "" >> $O; echo "## D. The grid: mesh of degree 8, 42 nodes, ideal links, hop delay median 50 to 1,000 ms" >> $O +$P --grid --nodes 42 --degree 8 --seconds 400 --out $O +echo "DONE $(date -u +%T)" >> $O diff --git a/sim/horizon/network/run-batch2.sh b/sim/horizon/network/run-batch2.sh new file mode 100755 index 00000000..7a28d8f2 --- /dev/null +++ b/sim/horizon/network/run-batch2.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env bash +cd /Users/joshm/Projects/igneum-wt-horizon +P="python3 sim/horizon/network/propagation.py" +O=sim/horizon/network/results-2.md +echo "# propagation.py results, batch 2 (per-block node cost), $(date -u +%FT%TZ), Mac, under with-lock run, seed 7" > $O +echo "" >> $O; echo "## E. Run A replay with the hub's MEASURED per-block CPU (61, 115, 345 ms per accepted block; A.jsonl cpu_rss deltas over accepted deltas), link 40 ms" >> $O +for s0 in 61 115 345; do $P --topology star --nodes 41 --bps 10 --k 124 --link 40 --hub-s0 $s0 --schedule sim/horizon/network/runA-schedule.csv --seconds 1200 --out $O; done +echo "" >> $O; echo "## F. The knee: star at a constant 10 bps, hub per-block cost 20 to 150 ms, 600 s" >> $O +for s0 in 20 40 60 80 100 150; do $P --topology star --nodes 41 --bps 10 --k 124 --link 40 --hub-s0 $s0 --seconds 600 --out $O; done +echo "" >> $O; echo "## G. Mesh of degree 8 where EVERY node pays the per-block cost (40, 80, 115 ms), 10 bps, 600 s" >> $O +for s0 in 40 80 115; do $P --topology mesh --nodes 42 --degree 8 --bps 10 --k 124 --link 40 --node-s0 $s0 --seconds 600 --out $O; done +echo "" >> $O; echo "## H. Mesh at 1 bps with the same per-block costs (the control), 600 s" >> $O +for s0 in 115 345; do $P --topology mesh --nodes 42 --degree 8 --bps 1 --k 18 --parents 10 --mergeset 180 --link 40 --node-s0 $s0 --seconds 600 --out $O; done +echo "" >> $O; echo "## I. Star at 10 bps, ideal hub, link latency 40 to 300 ms (pure latency)" >> $O +for l in 40 100 200 300; do $P --topology star --nodes 41 --bps 10 --k 124 --link $l --seconds 600 --out $O; done +echo "DONE $(date -u +%T)" >> $O diff --git a/sim/horizon/network/runA-schedule.csv b/sim/horizon/network/runA-schedule.csv new file mode 100644 index 00000000..53c232a4 --- /dev/null +++ b/sim/horizon/network/runA-schedule.csv @@ -0,0 +1,35 @@ +# Devnet 2 run A, 6 October 2026: the seed's "PoW accepted" lines per minute from 19:25Z (box clock 21:25+02:00), /home/build/dn2seed-A.log on igneum-build-1 +19:25,6 +19:26,350 +19:27,1223 +19:28,672 +19:29,1271 +19:30,1559 +19:31,1114 +19:32,1113 +19:33,934 +19:34,765 +19:35,389 +19:36,510 +19:37,503 +19:38,487 +19:39,466 +19:40,349 +19:41,311 +19:42,233 +19:43,394 +19:44,239 +19:45,235 +19:46,198 +19:47,215 +19:48,238 +19:49,208 +19:50,236 +19:51,178 +19:52,185 +19:53,181 +19:54,182 +19:55,199 +19:56,206 +19:57,224 +19:58,158 From 6cc1bc9d574a3f3c8f6e752cf172dc584e7d8021 Mon Sep 17 00:00:00 2001 From: igneum-labs <337424239+igneum-labs@users.noreply.github.com> Date: Tue, 6 Oct 2026 20:55:42 +0000 Subject: [PATCH 15/15] cross-remote.sh: the default cross-build runs again (a trailing comment had swallowed the defaults line); the class gets a CI check The shipper's report: with no arguments, cross-remote.sh died under the Mac's bash 3.2 with 'CARGO_ARGS: unbound variable' and its chain kept the previous exes. Cause: a comment appended to the defaults line in the box-work merge turned OUT, COMPARE, TARGET_DIR and CARGO_ARGS=() into comment text, so the array was never declared (the same shape build-remote.sh had at 19:5x). Fix: the line split, and the default tests use [ -n "${CARGO_ARGS[*]:-}" ] in both tools, safe whether or not the array exists. Proof: the default cross-build from a fork worktree under /bin/bash 3.2.57 built both exes (igneumd.exe 8f7e2ae3..., igneum-miner.exe 6f8b49d8...). tools/ci/defaults-line-check.sh fails CI on a line where a comment start is followed by an assignment list (quoted strings, ${...}, $# and [^#] dropped first); self-test fires on the swallowed shape and passes ${a#b}, sed s#x#y#, $# loops and prose mentions; the tree is clean. Co-Authored-By: Claude Fable 5.1 --- .github/workflows/ci.yml | 2 ++ tools/build-remote.sh | 10 +++--- tools/ci/defaults-line-check.sh | 57 +++++++++++++++++++++++++++++++++ tools/cross-remote.sh | 9 ++++-- 4 files changed, 70 insertions(+), 8 deletions(-) create mode 100755 tools/ci/defaults-line-check.sh diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5745224e..c895324b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -95,6 +95,8 @@ jobs: run: bash tools/ci/commit-string-check.sh --self-test - name: build server remote checkout self-test (the stale-overlay class of 6 October 2026) run: bash infra/build-server/remote-run.sh --self-test + - name: no shell assignment hides behind a trailing comment (the swallowed-defaults class of 6 October 2026) + run: bash tools/ci/defaults-line-check.sh --self-test && bash tools/ci/defaults-line-check.sh - name: no secret file names and no 64-hex secrets in the tree (self-test first, then the tree) run: bash tools/ci/no-secrets-check.sh --self-test && bash tools/ci/no-secrets-check.sh - name: faucet unit tests (validation, the daily limits, the signed transaction; keccak, RLP and secp256k1 vectors) diff --git a/tools/build-remote.sh b/tools/build-remote.sh index cee55cce..30cffa0a 100755 --- a/tools/build-remote.sh +++ b/tools/build-remote.sh @@ -64,7 +64,7 @@ while [ $# -gt 0 ]; do esac done [ "${CARGO_ARGS[0]:-}" = cargo ] && CARGO_ARGS=("${CARGO_ARGS[@]:1}") -CARGO_ARGS_GIVEN=""; [ "${#CARGO_ARGS[@]}" -gt 0 ] && CARGO_ARGS_GIVEN=1 +CARGO_ARGS_GIVEN=""; [ -n "${CARGO_ARGS[*]:-}" ] && CARGO_ARGS_GIVEN=1 bs_host bs_context @@ -106,16 +106,16 @@ fi # defaults per crate case "$BS_KIND:$BS_CRATE_REL" in node:*) - [ "${#CARGO_ARGS[@]}" -gt 0 ] || CARGO_ARGS=(build --release -p kaspad -p igneum-miner --features kaspad/igneum-pow) + [ -n "${CARGO_ARGS[*]:-}" ] || CARGO_ARGS=(build --release -p kaspad -p igneum-miner --features kaspad/igneum-pow) [ -n "$ARTEFACTS" ] || ARTEFACTS="$TARGET_DIR/release/igneumd $TARGET_DIR/release/igneum-miner" ;; repo:app/igneum-app) - [ "${#CARGO_ARGS[@]}" -gt 0 ] || CARGO_ARGS=(build --release) + [ -n "${CARGO_ARGS[*]:-}" ] || CARGO_ARGS=(build --release) [ -n "$ARTEFACTS" ] || ARTEFACTS="$TARGET_DIR/release/igneum-app $TARGET_DIR/release/igneum-ota-sign $TARGET_DIR/release/igneum-prove-verify" ;; repo:proving/igneum-prove) - [ "${#CARGO_ARGS[@]}" -gt 0 ] || CARGO_ARGS=(build --release) + [ -n "${CARGO_ARGS[*]:-}" ] || CARGO_ARGS=(build --release) [ -n "$ARTEFACTS" ] || ARTEFACTS="$TARGET_DIR/release/igneum-prove-host $TARGET_DIR/release/igneum-prove-export" ;; *) - [ "${#CARGO_ARGS[@]}" -gt 0 ] || CARGO_ARGS=(build --release) ;; + [ -n "${CARGO_ARGS[*]:-}" ] || CARGO_ARGS=(build --release) ;; esac case "${CARGO_ARGS[0]}" in build) ;; *) [ -n "${ARTEFACTS_SET:-}" ] || { FETCH=0; ARTEFACTS=""; } ;; esac # test, check, clippy: nothing to fetch [ -n "$OUT" ] || OUT="$BS_CRATE/target-remote" diff --git a/tools/ci/defaults-line-check.sh b/tools/ci/defaults-line-check.sh new file mode 100755 index 00000000..80f8302e --- /dev/null +++ b/tools/ci/defaults-line-check.sh @@ -0,0 +1,57 @@ +#!/usr/bin/env bash +# The swallowed-defaults class (6 October 2026, twice in one evening): a comment appended to a line of shell assignments +# (`JOBS=...; # ... OUT=""; CARGO_ARGS=()`) turns every assignment after the `#` into comment text; the script still parses, +# bash -n and shellcheck say nothing, and the first use of the undeclared array dies under the Mac's bash 3.2 with +# "unbound variable" (build-remote.sh at 19:5x UTC, cross-remote.sh at 21:xx UTC: the shipper's default cross-build never ran +# and its chain kept the previous exes). Rule: no `#` comment on a line that carries shell assignments after it. +# The scan (python3, which every CI host has) first drops quoted strings, ${...} expansions, $# and [^#] so a `#` inside them +# is not a comment, then flags a line where a comment start (start of line or whitespace, then #) is followed by `NAME=`. +# +# tools/ci/defaults-line-check.sh # exit 1 with file:line on a hit +# tools/ci/defaults-line-check.sh --self-test # fires on the swallowed shape, passes clean ones (including ${a#b} and sed s#x#y#) +set -euo pipefail +cd "$(dirname "$0")/../.." +scan() { # file names as arguments (python3 - takes its script from stdin, so stdin cannot carry the list), file:line per hit, exit 1 if any + python3 - "$@" <<'PY' +import re, sys +strip = [ + (re.compile(r"\$\{[^}]*\}"), ""), # ${var#pat}, ${var%pat}, ${!i} + (re.compile(r"\$#"), ""), # the argument count + (re.compile(r"\[\^#\]"), ""), # a bracket expression excluding # + (re.compile(r"'[^']*'"), "''"), # single-quoted strings + (re.compile(r'"(?:[^"\\]|\\.)*"'), '""'), # double-quoted strings + (re.compile(r"\\#"), ""), # an escaped # +] +# the swallowed shape is an assignment LIST after the comment start: `NAME=...;` or `NAME=()`; a prose mention such as +# "(access public|private, private only in NET_MODE=private)" or "OTA_SKIP=1 (the default now)" has neither +hit = re.compile(r"(^|\s)#.*\s[A-Za-z_][A-Za-z0-9_]*=(\(\)|[^;()]*;)") +bad = 0 +for path in sys.argv[1:]: + try: lines = open(path, encoding="utf-8", errors="replace").read().split("\n") + except OSError: continue + for n, line in enumerate(lines, 1): + s = line + for rx, rep in strip: s = rx.sub(rep, s) + if s.lstrip().startswith("#"): continue # a whole-line comment may say anything + if hit.search(s): + print(f"defaults-line: {path}:{n}: a comment swallows assignments after it: {line.strip()[:140]}"); bad = 1 +sys.exit(bad) +PY +} +if [ "${1:-}" = --self-test ]; then + t=$(mktemp -d); trap 'rm -rf "$t"' EXIT + printf '%s\n' 'JOBS="${JOBS:-}"; # empty = the box decides OUT=""; CARGO_ARGS=()' > "$t/bad.sh" + printf '%s\n' 'JOBS="${JOBS:-}"; OUT=""; CARGO_ARGS=() # one line, nothing assigned after the comment' \ + 'ver=${tarball#node-}; ver=${ver%-linux-x64.tar.xz}' \ + "epoch=\$(sed -n 's/^#define X \"\\(.*\\)\"/\\1/p' \"\$ph\"); day=\$(sed -n 's/^#define Y/x/p')" \ + 'rel="${a#"$TARGET_DIR"/}"; dest="$OUT/$rel"' \ + 'for ((i=1;i<=$#;i++)); do fee="${!i}"; done' \ + "A=\"\$(find . | sed 's#^\\./##' | sort)\"; B=1" \ + 'hetzner_create_one() { # name type location [role] [access] (access public|private, private only in NET_MODE=private)' \ + 'if [ "${OTA_SKIP:-}" = 0 ]; then SIGN=1; fi # the old spelling; OTA_SKIP=1 (the default now) leaves the manifest alone' \ + '# a whole-line comment with OUT=""; CARGO_ARGS=() in it' > "$t/good.sh" + if scan "$t/bad.sh" >/dev/null; then echo "defaults-line self-test: the swallowed shape did NOT fire"; exit 1; fi + if scan "$t/good.sh"; then echo "defaults-line self-test: fires on the swallowed shape, passes the clean shapes"; exit 0; else echo "defaults-line self-test: a clean shape fired"; exit 1; fi +fi +# shellcheck disable=SC2046 # paths in this tree carry no spaces +if scan $(git ls-files 'tools/*.sh' 'tools/**/*.sh' 'infra/**/*.sh' 'proto-cuda/**/*.sh' 'packaging/**/*.sh'); then echo "defaults-line: no assignment hides behind a comment"; else exit 1; fi diff --git a/tools/cross-remote.sh b/tools/cross-remote.sh index cf550960..431137ab 100755 --- a/tools/cross-remote.sh +++ b/tools/cross-remote.sh @@ -37,7 +37,10 @@ BS_TOOL=cross-remote . "$HERE/../infra/build-server/lib.sh" TARGET=x86_64-pc-windows-gnu -JOBS="${JOBS:-}"; # empty = the box decides: 90 alone, 45 beside another slot holder (remote-run.sh, main's ruling 6 Oct 2026) OUT=""; COMPARE=""; TARGET_DIR="target"; CARGO_ARGS=() +# JOBS empty = the box decides: 90 alone, 45 beside another slot holder (remote-run.sh, main's ruling 6 Oct 2026). One line, no +# trailing comment: a `#` on this line once swallowed every assignment after it, CARGO_ARGS was never declared and the default +# cross-build died with "unbound variable" under the Mac's bash 3.2 (the shipper, 6 Oct 2026, 21:xx UTC) +JOBS="${JOBS:-}"; OUT=""; COMPARE=""; TARGET_DIR="target"; CARGO_ARGS=() while [ $# -gt 0 ]; do case "$1" in --jobs) JOBS="$2"; shift 2 ;; @@ -55,10 +58,10 @@ bs_host bs_context case "$BS_KIND:$BS_CRATE_REL" in node:*) - [ "${#CARGO_ARGS[@]}" -gt 0 ] || CARGO_ARGS=(build --release -p kaspad -p igneum-miner --features igneum-pow --target "$TARGET") + [ -n "${CARGO_ARGS[*]:-}" ] || CARGO_ARGS=(build --release -p kaspad -p igneum-miner --features igneum-pow --target "$TARGET") EXES="igneumd.exe igneum-miner.exe" ;; repo:app/igneum-app) - [ "${#CARGO_ARGS[@]}" -gt 0 ] || CARGO_ARGS=(build --release --target "$TARGET") + [ -n "${CARGO_ARGS[*]:-}" ] || CARGO_ARGS=(build --release --target "$TARGET") EXES="igneum-app.exe igneum-ota-sign.exe igneum-prove-verify.exe" ;; *) bs_die "cross-remote builds the fork (run from a vendor/igneum-node* worktree) or app/igneum-app, not $BS_CRATE_REL" ;; esac