diff --git a/docs/plans/counter-asic-3-gate/override-v4-publish-example.json b/docs/plans/counter-asic-3-gate/override-v4-publish-example.json new file mode 100644 index 000000000..426f8a050 --- /dev/null +++ b/docs/plans/counter-asic-3-gate/override-v4-publish-example.json @@ -0,0 +1 @@ +{"difficulty_v2_activation_daa":33000,"proving_v0_activation_daa":84100,"fees_v1_activation_daa":210000,"finality_v3_activation_daa":135200,"program_class_v3_activation_daa":154800,"proving_v1_activation_daa":154800,"proving_v1_segment_blocks":8,"proving_v1_unproven_daa":600,"proving_v1_aggregator_share_bps":1000,"proving_v1_fresh_rule_daa":198000,"exec_restart_number":27276,"exec_restart_hash":"bb45cf0dd2d7cc97ebfa5a2701527c09a8ede5d32de74efead9caa293b15688a","exec_restart_trust_daa":200000,"program_class_v4_activation_daa":219600,"program_class_v4_signal_window_daa":86400} diff --git a/docs/plans/counter-asic-3-gate/override-v4-rehearsal.json b/docs/plans/counter-asic-3-gate/override-v4-rehearsal.json new file mode 100644 index 000000000..670a9ab5f --- /dev/null +++ b/docs/plans/counter-asic-3-gate/override-v4-rehearsal.json @@ -0,0 +1 @@ +{"difficulty_v2_activation_daa":0,"proving_v0_activation_daa":0,"fees_v1_activation_daa":0,"finality_v3_activation_daa":0,"program_class_v3_activation_daa":0,"proving_v1_activation_daa":0,"proving_v1_segment_blocks":8,"proving_v1_unproven_daa":600,"proving_v1_aggregator_share_bps":1000,"proving_v1_fresh_rule_daa":0,"exec_restart_number":18446744073709551615,"exec_restart_hash":"","exec_restart_trust_daa":18446744073709551615,"program_class_v4_activation_daa":14400,"program_class_v4_signal_window_daa":3600} diff --git a/docs/plans/counter-asic-3-node.md b/docs/plans/counter-asic-3-node.md index 6d3ab1e73..e709267af 100644 --- a/docs/plans/counter-asic-3-node.md +++ b/docs/plans/counter-asic-3-node.md @@ -88,3 +88,36 @@ The hash lane found the seven gate packs under `proto-cuda/packs-ca3-v4` carryin - Wire: `RpcPowEpochInfo` gained one Borsh field (wRPC, versioned by `GetBlockTemplateResponse`) and one optional proto field; a 0.3.13 miner against this node reads the v4 height as never (the field absent) and would key epochs on the v3 switch alone, so every miner moves in the same binary sweep as the node (the rollout order of 2.0). - The build job has no `features` key for test units (G6 row): the feature was on through unification; adding the key needs the PC's installed app to honour it, a 0.3.14 app item. - The 48 GiB APFS clone of the 0.3.13 target dir (`vendor/igneum-node-ca3v4/target-ca3v4`, untracked) can be deleted after the cut. + +## 6. PROPOSED: miner-signalled class activation (precondition 2 of the cut) + +Status: PROPOSED spec text for spec 01 (a new section 1.12.2 beside the epoch rule) and spec 02; implemented behind the override on the fork branch `ca3-v4-node` (`consensus/src/processes/class_signal.rs`, the pure rule in `consensus/core/src/igneum.rs`), gated by the fast-time runs of section 6.4. Not in `docs/spec` until the project lead adopts it. The 2.0 fixed height stays, as the floor. + +### 6.1 The rule + +1. **The carrier: the block header's `version` field, 2 bytes little-endian.** The low byte is the block version as before (2 on every Igneum network; `block_version_of`). The high byte is the producer's OBJECT VERSION, the highest program class the node that built the template runs: 4 for this binary (`CLASS_SIGNAL_V4`), 0 on every block made before it (`signalled_version(2, 4) = 0x0402`; `class_signal_of`). Why the header and not the coinbase extra data: the tally is read in header validation and by a node that holds headers only (a pruning-proof sync, a headers-first IBD), the header is what GHOSTDAG orders and what the finality rule already reads for its weight (the vote key hash is a header field, `finality.rs`), and the bytes are already there: no new field, no new hash preimage, no proto change for the header. A node before this binary rejects any header whose version is not exactly 2 (`check_header_version`), which is why the signalling binary ships in the same one-sweep rollout as the digest flip it already needs (section 2); from this binary on, only the low byte is checked, so a later object (5, 6) can be signalled to it without another header rule. +2. **The weight: blue blocks, the finality rule's convention.** For epoch `e` the anchor is its seed block `S_e`, the last selected-chain block whose DAA score is below `L e - lead` (the block the epoch seed is already taken from, `HeaderProcessor::epoch_seed`; so an epoch's seed and its class are decided at the same block of the same chain). The window is the `W` DAA below and including `S_e`: the selected chain is walked down from `S_e`, every chain block's mergeset blues counted once (the `compute_weights` walk of `finality.rs`, bounded by the merge depth), a blue block counted when its DAA score lies in `(daa(S_e) - W, daa(S_e)]`, and signalling when its object byte is at least 4. Share = signalling / total, in basis points. +3. **The decision, per epoch, monotone.** Epoch `e` is class v4 when (a) `L e >= N6`, the floor (the fixed height, rounded up to the epoch boundary exactly as the v3 switch is: `program_class_for_epoch_at`), or (b) epoch `e - 1` was v4, or (c) the window ending at `S_e` is FULL (`daa(S_e) >= W`) and its share is at least 9,500 bps. A signal moves the class one step: the rule answers v4 only where the floor rule answers v3 (below the v3 switch the answer is v2 whatever is signalled). The decision is memoised per seed block, so a fork of the chain has its own entries and one tally is paid per epoch per process. The epoch-boundary rounding of the v2 to v3 switch is unchanged: a class never changes inside an epoch, every block's class is its epoch's. +4. **The window `W`: 86,400 DAA (one day of blocks at 1 block/s), `program_class_v4_signal_window_daa` in the override file, in the digest right after the floor; 0 = signalling off, the floor alone (2.0's rule, byte for byte).** Why a day: the share must mean "the fleet that mines, not the fleet that happens to be up this hour", and a day covers every box's daily pattern (the rented boxes come and go by the hour); it is long enough that 95 percent cannot be reached by a burst and short enough that the flip lands within a day of the last upgrade; the finality rule's 30-day window answers a different question (who may vote) and would hold the class for a month after the fleet was ready. On the fast-time profile `W` is 120 (two epochs of 60). +5. **The threshold: 9,500 bps (95 percent), a constant (`CLASS_SIGNAL_THRESHOLD_BPS`), not a file field**, so no file can lower it; 95 percent of the blue blocks of a day is 95 percent of the hash rate of that day, the coordinator's figure; a box that cannot mine v4 (an old worker, section 2's wire note) is at most 5 percent of the hash rate at the flip, and the floor catches the rest. +6. **The floor `N6`: `program_class_v4_activation_daa`, set at the publish as DAA + 14,400 rounded up to the epoch boundary (the 2.0 rule for N4), checked `N6 - DAA >= 10,800`.** A stalled signal (a fleet that never reaches 95 percent) cannot hold the class forever: at `N6` v4 holds regardless. `never` is allowed in the file and means no floor (the signal alone decides; not for the devnet publish). +7. **What a node reports.** `PowEpochInfo` and the template's `powEpoch` carry `programClassV4SignalWindowDaa`, `programClassV4SignalBps` (the share of the window ending at the SINK, the live tally the next decision is heading for), `programClassSignal` (this node's byte) and `programClassV4SignalEpoch` (the epoch v4 was decided by signal on this chain, when it has been); gRPC fields 20 to 23. The daemon prints `Program class v4 signal window from the override file: W DAA ending at each epoch's seed block, threshold 9500 bps of blue blocks; the fixed height is the floor` beside the floor line, and `Program class v4 by miner signal: epoch E (share X bps over W DAA ending at seed block S, threshold 9500 bps, N of M blue blocks)` once per flip. +8. **The miner.** Nothing: the node builds the template header (the miner varies the nonce), so the signal is the node's binary; the miner takes the class of the epoch and the next from the template as before (`next_program_class` is the next epoch's decision once its seed block is known, else this epoch's class; a boundary that decides otherwise costs one refused pair and one prepare, the 2.0 era-boundary shape). `IGNEUM_CLASS_SIGNAL=` on devnet and simnet only lowers a node's byte (the fast-time gate's non-signalling node); it is not read on mainnet or the testnet. + +### 6.2 The devnet object changes shape + +Two fields join the live object: `program_class_v4_activation_daa` is now the FLOOR (its name and its rounding unchanged: the 2.0 fixed-height form), and `program_class_v4_signal_window_daa` is the window (86,400 on the devnet; 0 turns signalling off). Both enter the digest (unconditionally, right after the v3 field), so the digest moves on the binary rollout once more; the pinned devnet digest of the fork's test is re-pinned to the value of this binary (section 6.4). The publish object and the rehearsal object are in `docs/plans/counter-asic-3-rehearsal.md` section 2. Defaults: devnet and mainnet 86,400, testnet and simnet 0 (the testnet is v4 from genesis by its floor of 0; the simnet keeps 2.0's rule). + +### 6.3 What it does not cover (owed) + +| Item | Why | What is done about it | +|---|---|---| +| A class-signal witness in the pruning-proof format | a node that synced from a proof holds no headers below its pruning point, so an epoch whose window reaches below it cannot be tallied; the node then takes the floor rule for that epoch and logs it (`class_signal.rs`), which can disagree with a full-history node for the epochs between a signal flip and the floor | the same class as the era witness of 2.0 section 7 (`MissingEraSeed`); the floor bounds the exposure to at most `N6 - flip`; the witness (the per-epoch decision beside the epoch seed in the proof) is the next node item | +| The first tally after a restart | one walk of `W` chain blocks' mergesets per epoch per process, memoised; a day of blocks is about 86,400 header reads, under a second on the Mac's store | measured in the fast-time runs below at `W` = 120 only; the devnet figure is owed from the rehearsal | +| A byte above 4 | accepted and counted as a v4 signal (a later object contains v4); a v5 rule would count bytes at or above 5 | nothing now | + +### 6.4 The fast-time gate (three cases and the failed case) + +`infra/fast-time/class-v4-signal.mjs`: three nodes on `override-60x.json` (v3 from DAA 60, window 120, the floor at `--floor`), each node's byte set by `IGNEUM_CLASS_SIGNAL` (`--signal a,b,c`), one real CPU miner each, the id assertion of G4 on every v4 epoch. + +SIGNAL_RUNS_PLACEHOLDER diff --git a/docs/plans/counter-asic-3-rehearsal.md b/docs/plans/counter-asic-3-rehearsal.md new file mode 100644 index 000000000..76c4b42eb --- /dev/null +++ b/docs/plans/counter-asic-3-rehearsal.md @@ -0,0 +1,78 @@ +# Counter ASIC 3.0: the class v4 rehearsal on the rented fleet (precondition 1 of the cut) + +6 October 2026, worker "ca3-v4-node", on the coordinator's word of about 17:40 UTC. A precondition: no live-devnet date for class v4 is named until this rehearsal has PASSED on the rented fleet. The fleet agent owns the boxes (memory `gpu-rental.md`: the Vast and RunPod accounts, the fleet SSH key, the 6 October fleet plan and budget); this file is the plan it runs and the objects it runs with. The node lane touches no box. Nothing here is published to the live devnet; the rehearsal chain is a fleet-only network that the live devnet's nodes cannot join and that cannot join them. + +## 1. What is rehearsed + +The class v4 activation on a chain of real boxes, in the shape the live devnet will see, with both activation rules of the v4 seam (`docs/plans/counter-asic-3-node.md`): the miner-signalled flip (section 6 there, PROPOSED) and the fixed-height floor behind it; the stale-box case, where one box runs the old binary against the new object and must be refused at the handshake without forking the chain; and the live form of the G4 checks. + +| Fact | Value | Why | +|---|---|---| +| Network | `--devnet-suffix=400` (`igneum-devnet-400`), every box; the live devnet has no suffix and node 1, the observer and the seeds refuse any other network name at the handshake | fleet-only by construction; the chain starts at its own genesis state (DAA 0) because no box carries a devnet-400 database | +| Binary | one commit of ca3-v4-node (main) and ca3-v4-node (fork), the build job's Linux `igneumd`, `igneum-miner`, `igneum-app` from PC 2 (the G6 job of the signalling commit; the shas in `docs/plans/counter-asic-3-gate/node-gates.md`) on every box but the stale one; the stale box runs 0.3.13's Linux node (fork bb43e9a8, the 0.3.13 outputs) | the same objects as the cut's step 1 | +| Boxes | 12 or more mining boxes (the 50-miner wave's shape at a tenth of the size; 1 CPU miner or 1 GPU worker each), 1 seed box (`--listen`, no miner), 1 stale box | 12 keeps the per-box share near 8 percent, so one box's absence moves the tally by 8 percent: the 95 percent threshold is exercised, not trivially met (with 3 boxes it is 67 or 100 percent, the fast-time shape) | +| Override object | `rehearsal` below, the same file on every box (the stale box too) | the stale case is the digest refusal, so the file must be the same | +| Duration | about 2 h 40 min of chain at 1 block/s: the flip by signal at DAA 7,200 (epoch 2), two epochs after it, then the floor at 14,400 is NOT reached (the run ends at DAA 10,800) | the floor is the backstop; the rehearsal proves the signal path, the fast-time gate proved the floor path | + +## 2. The override objects + +Both objects below are JSON text to be written verbatim; a `never` height is `18446744073709551615`, which no JSON tool that goes through a double may rewrite (the fast-time harness's rule). The digests are what a node of the signalling commit prints at start (`Consensus params digest`); the fleet agent compares every box's line against them and the stale box's against its own. + +### 2a. The live devnet publish object (NOT published by this plan; the shape the cut will use) + +The live file today (`/tmp/igneum-devnet/override-v3.json`, 13 fields, read 16:55Z) plus the two v4 fields. `N6` is the floor: DAA at the publish + 14,400 rounded UP to a multiple of 3,600 (the 2.0 rule for N4), checked at publish (`N6 - DAA >= 10,800`). At the live DAA of 202,919 (16:57:43Z) that would be 219,600 (epoch 61); the number is set at the publish, not here. + +``` +{"difficulty_v2_activation_daa":33000,"proving_v0_activation_daa":84100,"fees_v1_activation_daa":210000,"finality_v3_activation_daa":135200,"program_class_v3_activation_daa":154800,"proving_v1_activation_daa":154800,"proving_v1_segment_blocks":8,"proving_v1_unproven_daa":600,"proving_v1_aggregator_share_bps":1000,"proving_v1_fresh_rule_daa":198000,"exec_restart_number":27276,"exec_restart_hash":"bb45cf0dd2d7cc97ebfa5a2701527c09a8ede5d32de74efead9caa293b15688a","exec_restart_trust_daa":200000,"program_class_v4_activation_daa":N6,"program_class_v4_signal_window_daa":86400} +``` + +What the two fields do on the live devnet: every node of the signalling binary stamps object byte 4 into its templates from its first block, so the signal share climbs as the fleet updates; the class flips at the first epoch boundary whose window (the 86,400 DAA, one day, below that epoch's seed block) has 95 percent of its blue blocks signalling, which is about a day after the LAST box of 95 percent of the hash rate has updated; the floor `N6` flips it regardless at the latest. Digest of this object: `ac8e60ce205852bdda6b554f8cbfbd9dbb040187f487cbd8affe8103633dfd56` (read from the signalling node's start line, 18:05Z; the node also prints `Program class v4 from the override file: active from epoch 61 (DAA score 219600 rounded up to the epoch boundary at 219600, epochs of 3600 DAA)` and the window line) (with `N6` = 219,600 as the worked example; any other `N6` moves it). + +### 2b. The rehearsal object (the fleet chain) + +A fresh chain, every earlier switch at 0 (the testnet's shape: the chain is born on calibrated difficulty v1, proving v1, fees v1, finality v3, class v3), the v4 signal window one epoch, the floor four hours out: + +``` +{"difficulty_v2_activation_daa":0,"proving_v0_activation_daa":0,"fees_v1_activation_daa":0,"finality_v3_activation_daa":0,"program_class_v3_activation_daa":0,"proving_v1_activation_daa":0,"proving_v1_segment_blocks":8,"proving_v1_unproven_daa":600,"proving_v1_aggregator_share_bps":1000,"proving_v1_fresh_rule_daa":0,"exec_restart_number":18446744073709551615,"exec_restart_hash":"","exec_restart_trust_daa":18446744073709551615,"program_class_v4_activation_daa":14400,"program_class_v4_signal_window_daa":3600} +``` + +The arithmetic: epochs of 3,600 DAA, lead 600. Epoch `e`'s seed block is the last chain block below `3600 e - 600`; its window is full when that block's DAA is at least 3,600: epoch 1's seed block sits at DAA 2,999 (not full), epoch 2's at 6,599 (full). With every mining box signalling 4, the tally at epoch 2's seed block is 100 percent of the blue blocks in DAA 2,999 to 6,599, so the class flips at epoch 2, DAA 7,200, about 2 hours after genesis; the floor (epoch 4, DAA 14,400) is 2 hours later and is not reached by the run. Digest: `bc2142b178ff367ae84ff0699ff523760d8878f883375da21864ed8d3ad39237` (the signalling node's start line on this object, 18:05Z, with `Program class v4 from the override file: active from epoch 4 (DAA score 14400 ...)` and `Program class v4 signal window from the override file: 3600 DAA ...`); the devnet digest with no file at all is `7f2e49beabc253f327c5ac6bb457a674ea7f527af2971c95d3bdf65ef8bcf977` on this binary (the fork's pinned test), `c562d70e...` on 0.3.11 to 0.3.13. + +## 3. The steps the fleet agent runs + +| Step | What | Done when | +|---|---|---| +| 1 | Fetch the signalling commit's Linux binaries from the G6 build job (the shas in node-gates.md), verify every sha256, place `igneumd` and `igneum-miner` on every box; the 0.3.13 Linux `igneumd` on the stale box | every sha matches | +| 2 | Write the rehearsal object (2b) as `override.json` on every box, byte for byte (sha256 the file on each box and compare) | one sha on every box | +| 3 | Start the seed box: `igneumd --devnet --devnet-suffix=400 --nodnsseed --disable-upnp --listen=0.0.0.0:16411 --rpclisten=127.0.0.1:16410 --rpclisten-json=127.0.0.1:16412 --override-params-file=override.json --utxoindex --enable-unsynced-mining --yes --appdir=` (ports of the box's choosing, never the live devnet's 26610/26611); read its first lines: `Consensus params digest` equals 2b's, `Program class v4 from the override file: active from epoch 4 (DAA score 14400 ...)`, `Program class v4 signal window from the override file: 3600 DAA ...` | the three lines | +| 4 | Start every mining box the same way with `--connect=:16411`, then its miner: `igneum-miner mine grpc://127.0.0.1:16410 1 100000000