Merge master d7a1bcb24 into ci-r15 under the master-landing lock
This commit is contained in:
commit
af2fe22010
37 changed files with 9745 additions and 47 deletions
26
docs/analysis/class-v6/rows/x0-5090-20261009.md
Normal file
26
docs/analysis/class-v6/rows/x0-5090-20261009.md
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
# X0 decider: class v4 against the frozen class v6 on one rented RTX 5090 (9 October 2026, 08:54 to 08:57 UTC)
|
||||
|
||||
One session, one card: a rented RTX 5090 (32 GB, driver 580.105.08, the provider's CUDA 12.8 image), the gen-6 Linux CUDA
|
||||
worker (sha256 `804a6f7f…`, built from the frozen tree's proto-cuda), 250 batches of 2^24 at `--block-warps 1` per pack, nvidia-smi
|
||||
board power at 1 Hz, watts = the mean of `power.draw` from 8 s in to the end of the bench, idle not subtracted. Stock only: the
|
||||
provider refuses `-lgc` ("The current user does not have permission to change clocks"), as every rented host of 8 October did.
|
||||
|
||||
| Pack | Class | Id | MH/s | W | nJ per hash | Fingerprint | Check |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| v4-devnet-epoch0 (the 7 October numerator's pack) | mx8-erad810f22d+sh256x27, generator 4 | `0xa785001687d8688a` | 141.963 | 500.0 (the card's power limit) | 3,522 | `e370fb2080b7dbb1` | PASS |
|
||||
| hl-v6-all (the l8off kit's export, the house knee rows' pack) | mx8-erad810f22d+sh256x27+state+reg64c+fold+rw | `0x9d40978601a7df2a` | 70.900 | 445.1 | 6,278 | `59e6708e46f1e87c` | PASS |
|
||||
| hl-v6-all-cs (the signing object) | the same class, generator 6 | `0x2a1d6caab4c24564` | 70.650 | 457.1 | 6,470 | `01f51b9d4805e5e6` (first CUDA read) | PASS |
|
||||
|
||||
Ratios, class v6 over class v4: joules per hash 1.78x (the kit pack) and 1.84x (the signing object); rate 2.00x (the frozen
|
||||
class draws twice the loads per hash). The house rig's RTX 5090 reads the same pair at 1.68x at stock (3,475 against 5,842 nJ,
|
||||
the 7 October grid against the 9 October knee rows) and 1.73x at the 1,300 MHz knee (2,319 against 4,011), so across three
|
||||
sessions the figure is 1.7 to 1.8x.
|
||||
|
||||
Meaning: class v6 costs the 5090 about 1.75x class v4's joules per hash and half its rate, so the 7 October numerator (2.33 µJ
|
||||
per hash at the lock on class v4) understates the frozen object's card cost by about 1.7x; the board ratio moves toward the
|
||||
2.6x side of the X0 order, and the chip's shadow op count is to be re-read on the class v6 draw (its window, index fold and
|
||||
re-weight add card cost the class v4 count does not carry). The only lock row for the frozen object is the house 5090's
|
||||
4.01 µJ per hash (the 9 October knee rows).
|
||||
|
||||
Evidence: build-1 `/srv/artefacts/tas/x0-pins/x0-run-fk-5090c/` (run-x0.log `9bc40685…`, smi.csv `7ab9f16f…`, the three bench
|
||||
logs; SHA256SUMS.txt beside them); the bundle `x0-bundle.tgz` (`864470a7…`) with the run script.
|
||||
|
|
@ -13,18 +13,21 @@ build-1:/srv/queue/build-queue.md (the reader reads that one; this file is its s
|
|||
- An entry carries its owner lane, its box, its pid-file path and its clock (UK). A box with no live pid file from its queue and
|
||||
a load under 1.0 for a 30-minute read is a fault, reported by the reader to the coordinator (/srv/queue/faults.log).
|
||||
- A lane that finishes an entry replaces it with the next or hands the box back here with a line; an empty box is the fault.
|
||||
- Main's rule (9 October 2026, 09:5x UK): an owner starts its matrix job on its box under /srv/queue/pids within 15 minutes of a read that
|
||||
found the box idle, or the reader marks the box unclaimed (open to any lane, written in status.json and faults.log); a box idle for
|
||||
30 minutes is a red against its owner on the steward's board. A shared box names every owner and none ends another's pid.
|
||||
|
||||
## Owners (one lane per box)
|
||||
|
||||
| box | threads | owner lane | standing use |
|
||||
|---|---|---|---|
|
||||
| build-1 | 96 | build-server lane | cuts and kits, the hands (observer-node, node1), the capacity fuzz slices, the workers page, the queue reader |
|
||||
| build-2 | 96 | site lane | the site gate (Playwright), the scene-parity suites; spare slots for the node lane's suites |
|
||||
| build-2 | 96 | site lane and the adversary lane (shared; neither ends the other's pid) | the site gate (Playwright), the scene-parity suites; X5's dram design beside them |
|
||||
| build-3 | 32 | node lane | the long consensus fuzz and property suites (kaspa-consensus, kaspa-consensus-core) |
|
||||
| build-4 | 96 | adversary lane | the chip model (OpenROAD, kepler-formal): the 20 to 25 percent floorplan for the converged SPEF row |
|
||||
| build-5 | 32 vCPU | research lane | TV-02's two independent supply-replay implementations, CPU only |
|
||||
| build-6 | 32 vCPU | HEAL lane | the long p2p and exec property suites (kaspa-p2p-flows, igneum-exec) |
|
||||
| build-7 | 96 | node lane | the 2.0.3 flows items: their suites and known-failed tests; the heal-on harness |
|
||||
| build-5 | 32 vCPU | adversary lane (X5 hyb50), the TV-02 lane a light guest for minutes | X5's opponent-sweep variant; TV-02's last comparison run beside it (one core, under five minutes) |
|
||||
| build-6 | 32 vCPU | adversary lane (X5 hyb75) and the HEAL lane (shared) | X5's opponent-sweep variant; the long p2p and exec property suites (kaspa-p2p-flows, igneum-exec) |
|
||||
| build-7 | 96 | node lane and the relay lane (shared; neither ends the other's pid) | the 2.0.3 flows items: their suites and known-failed tests; the heal-on harness; the standing Windows canary VM (docker winvm, 4 vCPU, 8 GB, /srv/winvm/up.pid) |
|
||||
| build-8 | 96 | fleet lane | 2.0.3 kit canaries the moment a chain moves; the heal-off harness; the dn4 roll node |
|
||||
| build-9 | 96 | steward | the board's rows that need only a box (ZKP, EVM, VER fixtures), the pow fuzz on the side |
|
||||
|
||||
|
|
@ -36,11 +39,12 @@ build-1:/srv/queue/build-queue.md (the reader reads that one; this file is its s
|
|||
| 2 | long fuzz and property suites, consensus (kaspa-consensus, kaspa-consensus-core) | node lane | build-3 | /srv/queue/pids/build-3-consensus-fuzz.pid | from 09:45, continuous |
|
||||
| 3 | long fuzz and property suites, exec and p2p (igneum-exec, kaspa-p2p-flows, kaspa-p2p-lib) | HEAL lane | build-6 | /srv/queue/pids/build-6-exec-p2p-fuzz.pid | from 09:45, continuous |
|
||||
| 4 | long fuzz and property suites, pow (kaspa-pow, igneum-pow mixer and scratch) | node lane | build-9 | /srv/queue/pids/build-9-pow-fuzz.pid | from 09:45, continuous |
|
||||
| 5 | TV-02: two independent supply-replay implementations, CPU only | research lane | build-5 | /srv/queue/pids/build-5-tv02-replay.pid | from 10:00, until both agree |
|
||||
| 5 | TV-02: two independent supply-replay implementations, CPU only: DONE 09:35 UK (A on build-5 in 38 s, B on build-6 in 3 min); the last comparison on the TV-01 spec runs as a guest under build-5-tv02.pid and build-6-tv02.pid by 11:30 | TV-02 lane | build-5, build-6 | /srv/queue/pids/build-5-tv02.pid, build-6-tv02.pid | by 11:30, minutes |
|
||||
| 6 | chip model: the 20 to 25 percent floorplan for the converged SPEF row (about four host-hours) | adversary lane | build-4 | /srv/queue/pids/build-4-floorplan.pid | running (OpenROAD 844081, kepler-formal 1797623), about 13:30 |
|
||||
| 7 | the board's rows that need only a box: ZKP, EVM and VER fixtures (the steward names them) | steward | build-9 | /srv/queue/pids/build-9-board-rows.pid | from 10:00, by row |
|
||||
| 8 | 2.0.3 kit canaries the moment a chain moves (the evidence kits under /srv/workers/fleet) | fleet lane | build-8 | /srv/queue/pids/build-8-kit-canary.pid | on the chain's move |
|
||||
| 9 | the site gate and the scene-parity suites | site lane | build-2 | /srv/queue/pids/build-2-site-gate.pid | on each landing |
|
||||
| 11 | the standing Windows canary VM on build-7 (docker winvm; its collector posts to the intake as the Windows canary card) | relay lane | build-7 | /srv/queue/pids/build-7-winvm.pid (mirrors /srv/winvm/up.pid) | from 10:00, standing |
|
||||
| 10 | cuts, kits, the hands, the capacity fuzz slices, the workers page, the queue reader | build-server lane | build-1 | /srv/queue/queue-reader.pid and the cut pid files under the cutter's scratch | continuous |
|
||||
|
||||
A lane starts its entry by writing its pid file (`<pid> <start UTC> <label>`, the format of the slot files) under /srv/queue/pids on
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
64
docs/plans/igneum-2.0-master/1p5x/research-plan/README.md
Normal file
64
docs/plans/igneum-2.0-master/1p5x/research-plan/README.md
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
# IGNEUM: The 1.5x Research Programme (9 October 2026)
|
||||
|
||||
The founder's research plan for the 1.5x target, the second document of the 1.5x folder beside the evidence and experiment brief (`../README.md`). It claims no pass.
|
||||
|
||||
The Track B rules: no raw hashes-per-second comparison (an equivalent lottery-contribution and security-budget unit is approved first); the internal design goal is 1.25x with the guardband; every chip figure is labelled WITNESS (E_D) or BOUND (L), X5's rows being witnesses; ASAP7 is screening only; negative results stay as controls; the quantum claim is classical; Track C (DEGSample) is not started until a Track B deficiency names it.
|
||||
|
||||
## The two tracks and the gated third
|
||||
|
||||
- Track A, the minimal-change route: the X matrix of the brief (A0 freeze the actual comparison, A1 the window-free addressing control, A2 byte-preserving honest-worker optimisation, A3 re-optimise every opponent), the lanes and clocks in `../README.md`.
|
||||
- Track B, proof-first input-specific memory work, starting from Blocki and Smearsoll's TCC 2025 memory-hard proof of work with memory-easy verification rather than another collection of instructions; the lanes and clocks as the coordinator names them:
|
||||
|
||||
| Lane | Work | Owner | Clock |
|
||||
|---|---|---|---|
|
||||
| B1 (R15-05) | reproduce the published primitive: the Blocki and Smearsoll TCC 2025 MTP/DRSample construction as a CPU reference with its complete verifier, domain separation, known-answer fixtures and the Dinur and Nadler attack fixtures | the B1 cryptography lane (a4490b2dfe9114a55) on build-6 | 13:30 UK |
|
||||
| B2 | bind the economically expensive work: the canonical instance input and the reuse, grinding, chosen-instance, partial-evaluation and amortisation obligations | the B2 binding spec lane (a717f5c9730229ea6), documents only | 12:30 UK |
|
||||
| B3 (R15-06) | GPU adaptation without changing the theorem: batched instances at 32, 128 and 512 MiB, coalesced labels, cooperative processing, the under-utilisation trap stated | the B3 GPU feasibility lane (a8e608abf1567ff87) on rented 5090 and 4090 pods | the first rows 14:00 UK |
|
||||
| B4 (R15-07) | the composed theorem statement for a cheating prover with concrete constants, the 2025 and 2024 results and what is new, the reduction losses, R4's encoded-state warning | the B4 theory lane (a46974623d72b032c), documents only | 13:00 UK |
|
||||
| B5 | the full-SRAM-on-fresh-state and the near-memory designs added to X5's sweep, WITNESS rows | the adversary lane (a1a9876a88f5a72fc), inside X5's line | 12:30 UK |
|
||||
| B6 (R15-10) | the verification budget as a parameter table against the fleet's node limits | the node lane (a283f5f0d364ceef0) | 13:00 UK |
|
||||
|
||||
- Track C, dynamic sustained-memory research, gated separately: kept only if its additional security and verification obligations can be met (the plan's DEGSample gate).
|
||||
|
||||
## Witness versus bound
|
||||
|
||||
For a fixed work function, difficulty and environment, G is the GPU energy per equivalent accepted work and E* the infimum of specialist energy across the permitted implementations; the required statement is G / E* at or under 1.5. A functioning or modelled specialist gives E_D with E* at or under E_D, so a chip at 1.5x is a witness of at least that achievable advantage, never an upper bound on a better chip. A scoped upper-bound certificate needs a justified lower bound L on every permitted specialist's energy and an upper bound G_upper on the GPU's, with G_upper / L at or under 1.5, and must name what is permitted. Four evidence levels: functional validity, resource security, physical translation, engineering reproduction; an empirical envelope over assessed chips is not a universal theorem.
|
||||
|
||||
## The 1.25x internal design goal
|
||||
|
||||
The required improvement: r_new = r x g / a. The guardband: a nominal 1.25 with the GPU 5 percent worse and the specialist 10 percent better than the central estimates reads 1.4583, a nominal 1.30 reads 1.5167, so an exact 1.50 point estimate has no room for those assumed uncertainties; the internal design goal is 1.25 nominal. The 5 and 10 percent are illustrative, not measured error bars.
|
||||
|
||||
## The ten execution packages
|
||||
|
||||
The plan's section 10 names capabilities, not lanes; the cross-reference to the brief's X matrix and to track B's lanes is this landing's reading, for the coordinator to confirm, and sits in `packages.json` beside the markdown for the registry's R15 suite.
|
||||
|
||||
| Package | Capability | Required artefact | Gate | Lanes |
|
||||
|---|---|---|---|---|
|
||||
| R15-01 | release and measurement | the latest exact pack, physical assumptions, source manifest, the current ratio reconciliation | no cross-version mixed baseline | X0 |
|
||||
| R15-02 | GPU and hardware | the component power and traffic model with sensitivity analysis | no device or whole-machine boundary mismatch | X0, X5 |
|
||||
| R15-03 | GPU and memory analysis | the layer-8-off control and held-out cache curves | a distinct benefit after the best cache adaptation | X1 |
|
||||
| R15-04 | GPU systems | compaction, exact-fold alternatives and tuning results | byte agreement, real accepted-work savings | X2, X3, X4 |
|
||||
| R15-05 | cryptography | the exact published MHPoW control, the theorem audit, malicious fixtures | no improvised sampled-memory soundness | B1 |
|
||||
| R15-06 | GPU and cryptography | theorem-preserving layout and parallelisation feasibility | GPU competitiveness and cheap-enough verification | B3 |
|
||||
| R15-07 | cryptography and hardware | the joint accepted-proof resource frontier with concrete constants | no invalid theorem composition | B2, B4 |
|
||||
| R15-08 | an independent ASIC team | re-optimised SRAM, DRAM, hybrid, recompute and PIM scenarios | the full-storage adversary not excluded by price | X5, B5 |
|
||||
| R15-09 | measurement and economics | the blind cohort confirmation and separate P12 outcomes | the strongest credible ratio, not the average | X6 |
|
||||
| R15-10 | protocol and security | network and pool integration, the no-rescue exercise, public evidence | correctness and safe operation intact | B6 |
|
||||
|
||||
R15-03 and R15-04 run beside R15-05 and R15-06: existing GPU waste is reduced without waiting for every theorem, and quick wins never substitute for the proof obligation.
|
||||
|
||||
## The stopping rules (the plan's section 11)
|
||||
|
||||
1. P04 stays unchanged unless a new scope is ratified before results; a same-node or one-node cohort gate is never replaced by one flagship result. 2. An unknown decision-critical silicon assumption is BLOCKED, not PASS. 3. Research cost and calendar rotation are not energy resistance. 4. A candidate may not improve the ratio by imposing a large cost on ordinary GPUs; the P03 limits govern like-for-like changes and a changed work definition needs an explicit comparison contract. 5. Every prototype keeps its published negative results; a rejection redirects research and establishes no universal impossibility. 6. A storage argument covers arbitrary representations through a valid reduction. 7. The best full-SRAM, hybrid and recompute designs stay in the comparison after every change. 8. Lower-bound derivations and measurements get independent scrutiny, empirical models and theorems labelled apart. 9. Verification, propagation, pool work, proof generation and job cancellation fit ordinary-operator budgets. 10. The founding statement is earned only with separate economic-access evidence; energy at or under 1.5 alone establishes no monetary security, profitability, parity or leadership.
|
||||
|
||||
## Files
|
||||
|
||||
| File | What | sha256 |
|
||||
|---|---|---|
|
||||
| `igneum-1p5x-research-plan.md` | the plan (the package's `IGNEUM_1p5x_Research_Plan.md`, byte-identical) | `269ceaa5824d09140a1bcc124dba59438db5a5e68fddc18960db7141fefc0305` |
|
||||
| `package/check_arithmetic.py` | the companion check: 2,000 bounded random examples and exact rational calculations of the illustrative cache and mixture models, standard library only; not a native Igneum test, not a hardware benchmark | `1390a5564d18414f14fb2049514eac9a7d1d317648c81b0829873176a7717940` |
|
||||
| `package/arithmetic_results.json` | its output | `5bd8786a590cc51f221f44501c4f469c65ec2789bc15fda0168005e076ed12a2` |
|
||||
| `package/manifest.json` | the package's own manifest | `78edf24a9ce2b356a52bc26fe2d9a2edf4a69c35e4d34ed5539d9d85c550881d` |
|
||||
| `packages.json` | the ten packages in machine-readable form (id, capability, artefact, gate, lanes, status) for the registry's R15 suite | written at this landing |
|
||||
|
||||
The package zip `IGNEUM_1p5x_Research_Package.zip` (sha256 `c873ac9da231fb267da4b4d6dba88e2d478e835357720be8adb585f4499b7772`) is not landed; its files are.
|
||||
|
|
@ -0,0 +1,339 @@
|
|||
# IGNEUM - The 1.5x Research Programme
|
||||
|
||||
**Research date:** 9 October 2026
|
||||
**Status:** Research proposal, not a completed algorithm or a P04 pass.
|
||||
**Objective:** Demonstrate a specialist/GPU energy advantage at or below 1.5x within an explicitly declared technology, implementation and GPU-cohort scope, while separately satisfying economic-access, correctness and operator-safety requirements.
|
||||
|
||||
## Executive decision
|
||||
|
||||
Run two tracks in parallel. Track A removes known structural and implementation opportunities from the current candidate. Track B develops a proof-first, input-specific memory-work construction, starting from published memory-hard proof-of-work research rather than inventing another collection of instructions. Keep a separate dynamic-memory branch only if its additional security and verification obligations can be met.
|
||||
|
||||
The strongest new theoretical lead is the TCC 2025 result by Blocki and Smearsoll on memory-hard proofs of work with memory-easy verification [R1]. It provides a basis for proving that a successful prover incurs a substantial memory-time cost. The bandwidth-hardness literature [R2,R3] supplies relevant energy/resource tools. Their combination is a research direction: the cited theorems do not already establish a 1.5x physical GPU/ASIC ceiling, and a cheating-proof-producer energy reduction must not be assumed from a theorem about full function evaluation.
|
||||
|
||||
A 2026 result [R4] is especially important: some pebbling-only security arguments can miss cheaper encodings of memory, including XOR combinations. This reinforces the need to allow representation changes, like the exact reg64 prefix-XOR alternative already recorded in the Igneum reviews. It does not refute every pebbling theorem or the specific papers used here; it requires a valid reduction to the actual adversary model.
|
||||
|
||||
## 1. Sources, scope and evidential boundaries
|
||||
|
||||
The project basis is the uploaded 8 October v6 freeze tree and the earlier source reviews and test registry. The archive inspected has SHA-256:
|
||||
|
||||
`f448981b2ceeab2e59e8bbd137a1a13ea1c730ecd9db72b9a89bd2bbf5d85acb`
|
||||
|
||||
The latest 1.5x DRAM and 1.9-2.1x hybrid figures are supplied by the user. No latest full energy workbook, placed full-system report or revised pack proving those figures was newly supplied with this request. Older source ratios must not be mixed into them as if they describe the same candidate.
|
||||
|
||||
Source-derived observations retained:
|
||||
|
||||
- An older hybrid model stores half of the dataset's items and serves 72% of reads from them. Its 5.5 GiB floor changes the memory ticket, not automatically the energy ratio.
|
||||
- The census compares important statistics with a window-conditioned distribution. Passing that comparison does not imply globally uniform traffic.
|
||||
- A layer-8-off experiment was already scheduled in the source. Confirm its inclusion in the latest result before crediting it again.
|
||||
- The connected-state candidate was explicitly rejected after the adversary used a clock-gated register file. The masked FP32 candidate was rejected after GPU overhead and full-board comparison. Neither is an untried remedy.
|
||||
- Physical work used ASAP7 for part of the core, with modeled memory, whole-machine terms and cross-process scaling. ASAP7's publisher explicitly describes it as predictive and not manufacturable [R5].
|
||||
|
||||
Exact source locations:
|
||||
|
||||
- `docs/design/class-v6-rotating-family.md:540-551,564-582`
|
||||
- `docs/analysis/class-v6/census-packs.md:18-22,36-38`
|
||||
- `docs/analysis/class-v6/family-gate.md:336-344`
|
||||
- `docs/analysis/class-v6/connected-state.md:91-126`
|
||||
- `igneum-pow/src/verify.rs:8-63,154-171`
|
||||
|
||||
This study inspected literature and source material and executed the accompanying arithmetic checks. It did not execute Rust, a GPU miner, an SP1 prover, a native network, a physical chip or a foundry implementation. The 2025 MHPoW theorem statements were checked in the authors' ePrint abstract and publisher record; the full paper was not retrievable in this session. Full cryptographic review is a mandatory work package, not claimed complete here. The 2026 DEGSample paper's full PDF and other accessible full-text material were consulted selectively; no independent proof audit is claimed.
|
||||
|
||||
## 2. The claim and the direction of proof
|
||||
|
||||
For a fixed work function, difficulty and environment, let G be GPU energy per equivalent accepted work. Let E* be the infimum of specialist energy across the allowed implementations and hardware. The required statement is:
|
||||
|
||||
`G / E* <= 1.5`.
|
||||
|
||||
A functioning or modeled specialist D gives an estimate E_D. Even if that estimate is accurate, E* <= E_D. Therefore:
|
||||
|
||||
`G / E* >= G / E_D`.
|
||||
|
||||
A chip at 1.5x is a witness of at least that achievable advantage. It is not an upper bound on every better chip someone might design. A physical-layout report has value but does not reverse this inequality.
|
||||
|
||||
A scoped upper-bound certificate instead requires a justified lower bound L on every permitted specialist's energy and an upper bound G_upper on the relevant GPU's energy:
|
||||
|
||||
`L <= E*` and `G_upper / L <= 1.5`.
|
||||
|
||||
The certificate must identify what is permitted: process technology, memory technologies and capacity choices, primitive assumptions, parallelism, precomputation, time horizon and power boundary. A result within that model is not a guarantee about all future physics or undiscovered algorithms.
|
||||
|
||||
Use four separate evidence levels:
|
||||
|
||||
1. **Functional validity:** all implementations compute the specified work and verify the permitted proofs.
|
||||
2. **Resource security:** every successful adversary pays a quantified cost in the stated cryptographic/computation model.
|
||||
3. **Physical translation:** conservative, justified component costs and feasible architectures translate the resource frontier into a scoped energy lower bound.
|
||||
4. **Engineering reproduction:** real GPUs and independent adversarial designs calibrate, challenge and attempt to falsify the model.
|
||||
|
||||
An empirical envelope over assessed chips is not the same as a universal theorem. Label the output accordingly if level 3 cannot be made sufficiently strong.
|
||||
|
||||
## 3. Mathematical screening results
|
||||
|
||||
### 3.1 Required improvement
|
||||
|
||||
For starting ratio r, GPU energy multiplier g and specialist energy multiplier a:
|
||||
|
||||
`r_new = r * g / a`.
|
||||
|
||||
At r=2.1, GPU-only improvement requires a 28.5714% energy reduction. Specialist-only improvement requires 40% more specialist energy. A hypothetical 15% GPU reduction and 20% specialist increase produces exactly 1.4875. These are requirements and illustrations, not an allocation of forecast gains.
|
||||
|
||||
### 3.2 Static cache optimality
|
||||
|
||||
Let p_1,...,p_N be probabilities of independent requests for equal-sized items, sorted largest first. A best fixed cache holding q items has hit probability:
|
||||
|
||||
`H(q) = sum_{i=1..q} p_i >= q/N`.
|
||||
|
||||
Proof: the average of the largest q probabilities cannot be smaller than the average of all N. Uniform probabilities attain equality simultaneously for every q. Relabelling items preserves the sorted probabilities and thus H(q).
|
||||
|
||||
Consequences:
|
||||
|
||||
- Uniformity is the best distribution against this fixed-cache model.
|
||||
- A best half-capacity cache still serves at least half the requests.
|
||||
- An epoch permutation alone cannot destroy hot-set mass.
|
||||
- This is not a theorem about adaptive, future-aware, compressed or multi-level caches. Those adversaries still need evaluation.
|
||||
|
||||
### 3.3 The old hot-half example
|
||||
|
||||
Moving from 72% hits to 50% changes misses from 28% to 50%, a multiplier of 25/14. Let eta be the fraction of old specialist energy proportional to those misses. In a deliberately simplified fixed-design model:
|
||||
|
||||
`r_new = r * g / (1 + (11/14)*eta)`.
|
||||
|
||||
From r=2.1 with no GPU improvement, eta must be at least 28/55, approximately 50.91%, to reach 1.5. With a hypothetical 10% GPU reduction, it must be at least 91/275, approximately 33.09%.
|
||||
|
||||
This model does not include extra SRAM traffic, clock changes, scheduling, replacement architecture or GPU loss. It is a prioritisation sensitivity calculation, not an estimated new result. It is inapplicable if the current 1.9-2.1x result already includes window removal.
|
||||
|
||||
### 3.4 Why a better operation mix can plateau
|
||||
|
||||
If one feasible specialist implements every independent component with ratio r_i above 1.5, and costs add without new coupling overhead, then the mixed ratio is:
|
||||
|
||||
`sum_i (r_i * E_A_i) / sum_i E_A_i`.
|
||||
|
||||
This is a weighted average of r_i and cannot fall below their minimum. Reweighting those components cannot cross 1.5 within that model. A genuine improvement must change some component advantage, eliminate GPU overhead, or introduce a real cost missing from the additive model.
|
||||
|
||||
This is a conditional screening lemma, not a claim that every complete architecture has additive costs.
|
||||
|
||||
### 3.5 The memory-energy bridge
|
||||
|
||||
If fraction f of GPU energy is for unavoidable memory movement, the specialist has memory-energy advantage s, and non-memory advantage c, an additive model gives:
|
||||
|
||||
`r = 1 / (f/s + (1-f)/c)`.
|
||||
|
||||
Even allowing specialist non-memory work to be free gives `r = s/f`. To reach 1.5 through the memory term alone requires `f >= s/1.5`. At s=1.0, f must reach 66.67%; at s=1.2, 80%; at s=1.5, 100%.
|
||||
|
||||
This does not say that GPU memory utilisation equals f. It also does not establish that s is near one: SRAM, nearby compute and alternative memory systems are exactly why that premise must be tested.
|
||||
|
||||
### 3.6 Guardband
|
||||
|
||||
An illustrative nominal ratio of 1.25, with GPU energy 5% worse and specialist energy 10% better than central estimates, becomes:
|
||||
|
||||
`1.25 * 1.05 / 0.90 = 1.45833...`.
|
||||
|
||||
A nominal 1.30 becomes 1.51667. Thus an exact 1.50 point estimate has inadequate room for those assumed uncertainties. The 5% and 10% are not measured error bars and do not capture an omitted architecture.
|
||||
|
||||
## 4. Historical and recent research: use mechanisms, not slogans
|
||||
|
||||
### 4.1 Memory work predates cryptocurrency
|
||||
|
||||
Dwork, Naor and Wee's 2005 work [R6] studies memory-bound proof-of-work via pebbling and amortised memory-access requirements. The enduring lesson is to formalise what an adversary must pay, including repeated work, not just what a reference program allocates.
|
||||
|
||||
### 4.2 Capacity and energy are distinct
|
||||
|
||||
Ren and Devadas [R3] make the capacity/energy distinction explicit. Blocki, Liu, Ren and Zhou [R2] extend bandwidth-hardness analysis in the parallel random oracle model and analyse named constructions. The corrected 2024 version should be used, not a superseded result.
|
||||
|
||||
The useful question is not simply how much memory is allocated. It is how much communication or recomputation remains necessary under every permitted fast-memory budget and representation. Cache-size assumptions are part of the theorem.
|
||||
|
||||
### 4.3 Cheap verification is dangerous when improvised
|
||||
|
||||
Dinur and Nadler's analysis [R7] attacked the earlier MTP construction. A commitment to a large table plus a few verified openings does not automatically prove that the prover incurred the intended construction cost.
|
||||
|
||||
A proposer may grind commitments, exploit predictable checks, leave much of the table unevaluated or reuse work across attempts. Analyze the cost of generating accepted certificates, not only the honest full evaluation.
|
||||
|
||||
### 4.4 A stronger modern starting point
|
||||
|
||||
The Blocki-Smearsoll TCC 2025 result [R1] establishes, in its stated model, an MTP instantiation using suitable data-independent functions, with DRSample giving O(N) honest sequential time, polylogarithmic proof size/verification and high cumulative memory complexity for successful malicious provers. This is the starting point for Track B, not a complete physical-energy result.
|
||||
|
||||
### 4.5 Newer results expand options but do not compose automatically
|
||||
|
||||
Blocki-Holman's CRYPTO 2026 DEGSample work [R8] proves a sustained-space/cumulative-memory trade-off directly in the parallel random oracle model. A data-dependent construction may be attractive against some prearranged schedules, but it is not a drop-in replacement inside a theorem requiring a data-independent graph.
|
||||
|
||||
The 2026 separation paper [R4] shows why arbitrary encoded state must be permitted. Its result is not a blanket refutation of graph methods: the valid reduction must establish that the chosen model covers the encoding strategies at issue.
|
||||
|
||||
### 4.6 Resource matching and GPU implementation still matter
|
||||
|
||||
RandomX and ProgPoW [R9,R10] provide designs oriented towards commodity hardware resources. HashCore [R11] studies workload design by working backwards from processor characteristics. None supplies a proved universal 1.5x ceiling for Igneum.
|
||||
|
||||
Cuckoo Cycle's own documentation [R12] distinguishes alternative solving strategies and discusses SRAM suitability. A memory-bound graph problem is not automatically resistant to specialised memory.
|
||||
|
||||
## 5. Track A - A minimal-change route to the first credible result
|
||||
|
||||
### A0. Freeze the actual comparison
|
||||
|
||||
Produce the pack hash, precise acceptance rule, GPU kernel/host version, actual traffic traces, placed full-core model and all board assumptions behind the latest 1.9-2.1x. Identify whether layer-8-off and result compaction are already included. Do not splice ratios from different snapshots.
|
||||
|
||||
### A1. Window-free addressing control
|
||||
|
||||
Run the existing candidate against a version without the fixed per-load window restriction. Initially hold read count, width, dataset size and register schedule constant. Evaluate the real dataset and held-out headers/nonces. This is a consensus-changing experiment; do not silently alter legacy work.
|
||||
|
||||
Publish best static, trained and adaptive cache-benefit curves at 0%, 1%, 5%, 10%, 25%, 50%, 75% and 100% fast storage. Measure temporal phases and repeated-item correlations. Future-trace-optimal replacement is an attacker-favourable bound, not a realistic deployable policy by itself.
|
||||
|
||||
### A2. Byte-preserving honest-worker optimisation
|
||||
|
||||
Port the existing OpenCL candidate-selection pattern to CUDA, retaining sentinels, overflow handling and full-reference mode. Evaluate exact reg64 implementation alternatives on BOTH sides. Tune the final workload, including host readback, cancellation and epoch overhead. No assumed percentage is assigned.
|
||||
|
||||
Keep measured wall energy as the primary boundary. Do not subtract inconvenient host power from the GPU while retaining a different scale convention for the specialist. Also report device-only diagnostics separately.
|
||||
|
||||
### A3. Re-optimise every opponent
|
||||
|
||||
The best old half-cache need not remain the best new design. Include full SRAM, different stored fractions, reconstruction, lower clock rates, multi-chip shared memory and programmable multi-family cores. Count memory and control activity, not only logical bit capacity.
|
||||
|
||||
A failed connected-state or FP32 candidate may re-enter only with a written explanation of what materially differs from its earlier failure mechanism.
|
||||
|
||||
## 6. Track B - Proof-carrying, input-specific memory work
|
||||
|
||||
**Working concept:** replace most amortisable static-table work with construction and evaluation of input-specific state whose omission, encoding or recomputation is covered by a resource-cost argument; carry a compact certificate that ordinary validators can check.
|
||||
|
||||
This is a proposed construction family. It is not yet an implemented algorithm or claimed novel theorem.
|
||||
|
||||
### B1. Reproduce the published primitive first
|
||||
|
||||
Implement the exact data-independent MTP/DRSample reference and its full verifier, including domain separation and all prescribed checks, before inventing a GPU adaptation. Obtain and review the full paper; the abstract is not an implementation specification. Produce small known-answer fixtures and malicious-prover test fixtures.
|
||||
|
||||
### B2. Bind the economically expensive work
|
||||
|
||||
Define a canonical instance input committing to network, version, epoch, block-template context and the trial identifier. Every alternative that changes the winning lottery trial must pay the proved cost or an explicitly bounded amortised portion of it.
|
||||
|
||||
Do not write a new outer nonce that can vary cheaply after one expensive state commitment without analysing the resulting reuse. Do not equate a proof of correct execution with proof of unavoidable physical work. Investigate commitment grinding, chosen-input selection, correlated instances, rejected trials and cancellation.
|
||||
|
||||
A diagram of the intended roles is:
|
||||
|
||||
`bound work instance -> state construction -> prescribed proof search/certificate -> verification + target check`.
|
||||
|
||||
This is a role diagram, not a substitute for the exact protocol.
|
||||
|
||||
### B3. GPU adaptation without silently changing the theorem
|
||||
|
||||
First change representation and scheduling while preserving the exact function. Test structure-of-arrays batching, coalesced label layout and cooperative processing where mathematically permitted. Do not delete dependency edges merely to make the graph faster and then retain its old proof.
|
||||
|
||||
A proposed early parameter sweep may explore per-instance states such as 32, 128 and 512 MiB, with the number of concurrent instances limited by measured usable memory and full proof-construction overhead. These are exploratory sizes, not adopted network parameters.
|
||||
|
||||
There is a major feasibility test: large sequential per-instance graphs can leave a GPU underutilised, while shrinking instances for more parallelism may let an ASIC fit each instance in SRAM. Reject a configuration that is memory-hard but principally CPU-friendly. If label width or graph structure must change, commission the corresponding proof rather than assume inheritance.
|
||||
|
||||
### B4. Prove the energy cost of accepted proofs
|
||||
|
||||
The desired result is not merely a lower bound for honest complete graph evaluation. Derive a lower bound for all allowed adversaries producing accepted work with a specified success probability, including partial evaluation, shortcuts and multi-instance amortisation.
|
||||
|
||||
A candidate theorem should expose a joint trade-off over primitive calls, fast-memory capacity, transfers and sustained storage. The physical model then minimises over those jointly permitted strategies. Do not add unrelated lower bounds when they double-count the same work.
|
||||
|
||||
The 2025 proof-of-work theorem and 2024 bandwidth results motivate this work but do not automatically establish the required composed theorem, particularly for the sampled/ex-post graph produced by a cheating prover. This is a central new proof obligation.
|
||||
|
||||
### B5. Confront the full-SRAM design explicitly
|
||||
|
||||
Fresh state stops an old cache from being reused merely because the epoch remains unchanged. It does not prevent a specialist from constructing new state in fast memory.
|
||||
|
||||
A proof that assumes the fast cache is small cannot defeat an adversary allowed to place the entire working set there. Evaluate its active read/write energy, leakage, ports, routing, controller, parallel throughput and fabrication economics. If the energy test still fails, keep P04 failed. Expensive construction belongs to P12; it cannot excuse P04.
|
||||
|
||||
### B6. Verification and network fit
|
||||
|
||||
Count graph commitment cost, proof construction, proof bytes, verifier CPU/RAM, invalid-proof amplification, pool-share checking, propagation, cancellation and disk/state handling. Test cold verification at the maximum admitted rate, not only cached success.
|
||||
|
||||
Very roughly, proof arrival rate times per-proof CPU time must fit the provisioned verification budget with headroom, but batching and worst-case adversarial payloads require their own analysis. Do not use this simple product as a denial-of-service proof.
|
||||
|
||||
A new work unit cannot be judged by raw hashes per second against v6. Before evaluation, approve an equivalent lottery-contribution/security-budget comparison and retain explicit absolute node/operator budgets. Do not declare P03 satisfied by renaming a much larger puzzle one hash or by changing difficulty to mask cost.
|
||||
|
||||
## 7. Track C - Dynamic sustained-memory research, gated separately
|
||||
|
||||
Use DEGSample as an independent exploratory branch only after identifying a deficiency of Track B that it could fix. First benchmark the exact dynamic construction. Then ask whether its resource advantage survives real GPU execution and the strongest SRAM schedule.
|
||||
|
||||
Its PROM trade-off is not already a memory-easy PoW verifier. Either use expensive full verification only as a research control or develop a sound efficient-verification transformation. Do not put a zkVM wrapper around it and presume proof generation inherits the resource-cost lower bound.
|
||||
|
||||
The constraints to solve together are efficient honest GPU execution, a cheating-prover cost theorem, cheap safe network verification, and real whole-system energy. The newer theorem is a research tool, not a release certificate.
|
||||
|
||||
## 8. The physical certificate and its limitations
|
||||
|
||||
Construct a conservative optimisation problem over the adversary's hardware and evaluation strategy. Necessary cryptographic trade-offs constrain feasible resource use; technology assumptions provide lower cost coefficients. An appropriately relaxed feasible set may yield a certified LOWER bound on cost. A restricted list of heuristic attackers normally yields only assessed examples.
|
||||
|
||||
Use explicit lower-bound certificates or optimality gaps where tractable. Small-graph exhaustive or integer-programming checks can validate the analysis, but scaling from small graphs requires a justified theorem. More optimisation runs alone do not prove the unknown optimum.
|
||||
|
||||
The target final record contains:
|
||||
|
||||
- The exact cryptographic assumptions and finite parameters.
|
||||
- Every reduction loss and failure probability; no hidden big-O constants in the numerical claim.
|
||||
- Permitted caching, compression, arbitrary encoding, recomputation and parallel strategies.
|
||||
- Technology set, process comparisons and optimistic-adversary energy ranges.
|
||||
- GPU upper energy observations, repetitions, environmental corrections and uncertainty.
|
||||
- The derived energy lower bound and independently checked ratio.
|
||||
- Separate area/capital/total-cost results and excluded technology scenarios.
|
||||
|
||||
ASAP7 is useful for screening architecture, but cannot alone certify an N2/N3 manufactured system. Foundry-calibrated memory and logic data and independent physical review are required for the stronger engineering interpretation. A shipped-chip measurement still does not establish a bound for all unbuilt alternatives.
|
||||
|
||||
## 9. Future adversaries that must remain in the study
|
||||
|
||||
- **Programmable compute-only silicon:** no display/graphics machinery required, with all published families and microcode updates.
|
||||
- **Chiplets, 3D SRAM and modular memory:** UCIe documents modular/3D die interconnect [R13]. Do not assume a reticle limit or a memory change implies full replacement of the compute design.
|
||||
- **Near-memory and in-memory compute:** Samsung has demonstrated HBM processing-in-memory [R14]. AI workload savings cannot be transferred to Igneum, but the architecture invalidates a permanently fixed compute/DRAM boundary assumption.
|
||||
- **Multiple manufacturing generations:** evaluate a technology envelope rather than a forever claim. Older GPU cohorts must not silently disappear from the result.
|
||||
- **Future algorithmic shortcuts:** retain a public challenge and independent reviewer; an improved attacker changes the envelope. Do not convert the bounty budget into a proof of absence.
|
||||
- **Quantum adversaries:** the proposed baseline is classical. A separate quantum/random-oracle/reversible-memory analysis is necessary before a quantum-resistance claim. No delivery date or quantum speedup estimate is asserted here.
|
||||
|
||||
A technology-triggered review is not an automatic consensus fork. The no-rescue test still assumes a specialist remains operational through known epochs and that an emergency algorithm switch is not needed to make the current economics look good.
|
||||
|
||||
## 10. Proposed execution packages
|
||||
|
||||
| Package | Owner capability | Required artefact | Gate |
|
||||
|---|---|---|---|
|
||||
| R15-01 | Release + measurement | Latest exact pack, physical assumptions, source manifest, current ratio reconciliation | No cross-version mixed baseline |
|
||||
| R15-02 | GPU + hardware | Component power/traffic model and sensitivity analysis | No device/whole-machine boundary mismatch |
|
||||
| R15-03 | GPU + memory analysis | Layer-8-off control and held-out cache curves | Distinct benefit after best cache adaptation |
|
||||
| R15-04 | GPU systems | Compaction, exact-fold alternatives and tuning results | Byte agreement, real accepted-work savings |
|
||||
| R15-05 | Cryptography | Exact published MHPoW control, theorem audit, malicious fixtures | No improvised sampled-memory soundness |
|
||||
| R15-06 | GPU + cryptography | Theorem-preserving layout/parallelisation feasibility | GPU competitiveness and cheap-enough verification |
|
||||
| R15-07 | Cryptography + hardware | Joint accepted-proof resource frontier with concrete constants | No invalid theorem composition |
|
||||
| R15-08 | Independent ASIC team | Re-optimised SRAM/DRAM/hybrid/recompute/PIM scenarios | Full-storage adversary not excluded by price |
|
||||
| R15-09 | Measurement + economics | Blind cohort confirmation and separate P12 outcomes | Strongest credible ratio, not average |
|
||||
| R15-10 | Protocol/security | Network and pool integration, no-rescue exercise, public evidence | Correctness and safe operation remain intact |
|
||||
|
||||
Run R15-03/04 alongside R15-05/06. Do not wait for every speculative theorem before reducing existing GPU waste; do not let quick wins substitute for the proof obligation.
|
||||
|
||||
A plausible small research group needs memory-hardness expertise, a GPU/compiler engineer, an ASIC/physical-design reviewer and a reproducibility/protocol engineer. These are capabilities, not claimed appointments. Commission an independent challenger whose success criterion is finding a better adversary, not validating the desired marketing result.
|
||||
|
||||
## 11. Acceptance and stopping rules
|
||||
|
||||
1. The current P04 stays unchanged unless a new scope is ratified BEFORE results. Never silently replace a same/one-node cohort gate with one flagship result.
|
||||
2. An unknown decision-critical silicon assumption is BLOCKED, not PASS.
|
||||
3. Expensive research cost and calendar rotation are not energy resistance.
|
||||
4. A candidate may not improve the ratio by imposing a large cost on ordinary GPUs. Existing P03 limits govern like-for-like changes; changed work definitions require an explicit comparison contract, not a retrospective exemption.
|
||||
5. All prototypes retain published negative results. Rejecting a candidate redirects research; it does not establish a universal impossibility.
|
||||
6. A mathematical argument about storage must cover arbitrary representations through a valid reduction. A trace showing every register influences an address is insufficient.
|
||||
7. The best full-SRAM, hybrid and recompute designs remain in the comparison after every change.
|
||||
8. Lower-bound derivations and engineering measurements receive independent scrutiny; empirical models and formal theorems are labelled separately.
|
||||
9. Verification, block propagation, pool work, proof generation and job cancellation fit ordinary-operator budgets.
|
||||
10. The founding statement is earned only with separate economic-access evidence. Energy <=1.5 does not alone establish monetary security, permanent profitability, future technological parity or category leadership.
|
||||
|
||||
## 12. Immediate recommendation
|
||||
|
||||
Do not announce that v7 solves resistance. Commission three concrete outputs first: the current hybrid energy frontier tied to the exact pack; the window-free/optimised-v6 paired result; and a feasibility prototype of the published proof-carrying memory-work construction.
|
||||
|
||||
If the first track closes the gap, independently validate it. If it does not, the proof-first track is a substantive architectural alternative, not another unpriced randomisation layer. The intended innovation is a hardware-cost claim that starts with adversarial resource proofs and ends with calibrated GPU economics.
|
||||
|
||||
## Primary references
|
||||
|
||||
All links were consulted on 9 October 2026. Reading depth is stated where important. A citation is not an independent replication.
|
||||
|
||||
- **R1.** Blocki and Smearsoll, *Provably Memory-Hard Proofs of Work with Memory-Easy Verification*, TCC 2025. Author abstract and publisher theorem summary consulted; full proof not retrieved. https://eprint.iacr.org/2025/1456 ; https://link.springer.com/chapter/10.1007/978-3-032-12290-2_17
|
||||
- **R2.** Blocki, Liu, Ren and Zhou, *Bandwidth-Hard Functions: Reductions and Lower Bounds*, Journal of Cryptology 2024 extended version. https://eprint.iacr.org/2018/221
|
||||
- **R3.** Ren and Devadas, *Bandwidth Hard Functions for ASIC Resistance*, TCC 2017. https://eprint.iacr.org/2017/225
|
||||
- **R4.** de Rezende, Engstrom and Reyzin, *Separating the Pebbling Model from the Random Oracle Model*, 2026 preprint. Author abstract consulted; not treated as a claim about all existing graph constructions. https://eprint.iacr.org/2026/1024
|
||||
- **R5.** Arizona State University and ARM, ASAP7 predictive PDK documentation. https://asap.asu.edu/
|
||||
- **R6.** Dwork, Naor and Wee, *Pebbling and Proofs of Work*, CRYPTO 2005. https://www.microsoft.com/en-us/research/publication/pebbling-and-proofs-of-work/
|
||||
- **R7.** Dinur and Nadler, *Time-Memory Tradeoff Attacks on the MTP Proof-of-Work Scheme*, CRYPTO 2017. https://eprint.iacr.org/2017/497
|
||||
- **R8.** Blocki and Holman, *Data-Dependent Memory-Hard Functions: Sustained Space and Cumulative Complexity Trade-offs in the Parallel Random Oracle Model*, CRYPTO 2026, revision 18 August 2026. Full PDF consulted selectively. https://arxiv.org/abs/2508.06795 ; https://arxiv.org/pdf/2508.06795v2
|
||||
- **R9.** RandomX design and v2 design. https://github.com/tevador/RandomX/blob/master/doc/design.md ; https://github.com/tevador/RandomX/blob/master/doc/design_v2.md
|
||||
- **R10.** ProgPoW reference design. https://github.com/ifdefelse/ProgPOW
|
||||
- **R11.** *HashCore: Proof-of-Work Functions for General Purpose Processors*. https://arxiv.org/abs/1902.00112
|
||||
- **R12.** John Tromp, Cuckoo Cycle reference implementation/design. https://github.com/tromp/cuckoo
|
||||
- **R13.** UCIe Consortium, specifications. https://www.uciexpress.org/specifications
|
||||
- **R14.** Samsung, HBM processing-in-memory announcement. Architecture precedent only; its AI performance percentages are not used for Igneum. https://news.samsung.com/global/samsung-develops-industrys-first-high-bandwidth-memory-with-ai-processing-power
|
||||
- **R15.** NVIDIA CUDA C++ Best Practices Guide. https://docs.nvidia.com/cuda/cuda-c-best-practices-guide/index.html
|
||||
- **R16.** Bhattacharyya and Mandal, *Bandwidth-Hard Functions from Random Permutations*, 2022. Accessible full text consulted for the model and its concrete reduction losses. https://arxiv.org/html/2207.11519v1
|
||||
|
||||
## Companion execution
|
||||
|
||||
Run `python check_arithmetic.py` to regenerate `arithmetic_results.json` using only Python's standard library. It performs 2,000 bounded random examples and exact rational calculations of the illustrative cache and mixture models. These checks are not native Igneum tests, not hardware benchmarks and not a proof of P04.
|
||||
|
|
@ -0,0 +1,153 @@
|
|||
{
|
||||
"title": "Igneum 1.5x: exact model arithmetic",
|
||||
"date": "2026-10-09",
|
||||
"classification": "ILLUSTRATIVE MATHEMATICS; NO HARDWARE OR PROTOCOL RESULT",
|
||||
"target": {
|
||||
"exact": "3/2",
|
||||
"decimal": 1.5
|
||||
},
|
||||
"required_improvement": [
|
||||
{
|
||||
"starting_ratio": {
|
||||
"exact": "19/10",
|
||||
"decimal": 1.9
|
||||
},
|
||||
"gpu_only_required_reduction": {
|
||||
"exact": "4/19",
|
||||
"decimal": 0.21052631578947367
|
||||
},
|
||||
"specialist_only_required_increase": {
|
||||
"exact": "4/15",
|
||||
"decimal": 0.26666666666666666
|
||||
},
|
||||
"illustration_gpu_minus_15pct_specialist_plus_20pct": {
|
||||
"exact": "323/240",
|
||||
"decimal": 1.3458333333333334
|
||||
}
|
||||
},
|
||||
{
|
||||
"starting_ratio": {
|
||||
"exact": "2",
|
||||
"decimal": 2.0
|
||||
},
|
||||
"gpu_only_required_reduction": {
|
||||
"exact": "1/4",
|
||||
"decimal": 0.25
|
||||
},
|
||||
"specialist_only_required_increase": {
|
||||
"exact": "1/3",
|
||||
"decimal": 0.3333333333333333
|
||||
},
|
||||
"illustration_gpu_minus_15pct_specialist_plus_20pct": {
|
||||
"exact": "17/12",
|
||||
"decimal": 1.4166666666666667
|
||||
}
|
||||
},
|
||||
{
|
||||
"starting_ratio": {
|
||||
"exact": "21/10",
|
||||
"decimal": 2.1
|
||||
},
|
||||
"gpu_only_required_reduction": {
|
||||
"exact": "2/7",
|
||||
"decimal": 0.2857142857142857
|
||||
},
|
||||
"specialist_only_required_increase": {
|
||||
"exact": "2/5",
|
||||
"decimal": 0.4
|
||||
},
|
||||
"illustration_gpu_minus_15pct_specialist_plus_20pct": {
|
||||
"exact": "119/80",
|
||||
"decimal": 1.4875
|
||||
}
|
||||
}
|
||||
],
|
||||
"old_source_hot_half_model": {
|
||||
"old_hit_fraction": {
|
||||
"exact": "18/25",
|
||||
"decimal": 0.72
|
||||
},
|
||||
"new_static_uniform_half_hit": {
|
||||
"exact": "1/2",
|
||||
"decimal": 0.5
|
||||
},
|
||||
"dram_miss_multiplier": {
|
||||
"exact": "25/14",
|
||||
"decimal": 1.7857142857142858
|
||||
},
|
||||
"required_miss_energy_fraction_from_2p1_no_gpu_gain": {
|
||||
"exact": "28/55",
|
||||
"decimal": 0.509090909090909
|
||||
},
|
||||
"required_miss_energy_fraction_from_2p1_gpu_minus_10pct": {
|
||||
"exact": "91/275",
|
||||
"decimal": 0.33090909090909093
|
||||
},
|
||||
"illustration_50pct_miss_energy_no_gpu_gain": {
|
||||
"exact": "98/65",
|
||||
"decimal": 1.5076923076923077
|
||||
},
|
||||
"illustration_50pct_miss_energy_gpu_minus_10pct": {
|
||||
"exact": "441/325",
|
||||
"decimal": 1.356923076923077
|
||||
},
|
||||
"limits": "Fixed architecture, miss-scaled energy only; ignores adaptive caching, replacement design, changed SRAM traffic, contention, compression, clock changes and GPU regressions. Not a forecast."
|
||||
},
|
||||
"memory_dominance_illustrations": [
|
||||
{
|
||||
"specialist_memory_advantage": {
|
||||
"exact": "1",
|
||||
"decimal": 1.0
|
||||
},
|
||||
"minimum_gpu_energy_fraction_in_unavoidable_memory_even_if_specialist_compute_free": {
|
||||
"exact": "2/3",
|
||||
"decimal": 0.6666666666666666
|
||||
}
|
||||
},
|
||||
{
|
||||
"specialist_memory_advantage": {
|
||||
"exact": "6/5",
|
||||
"decimal": 1.2
|
||||
},
|
||||
"minimum_gpu_energy_fraction_in_unavoidable_memory_even_if_specialist_compute_free": {
|
||||
"exact": "4/5",
|
||||
"decimal": 0.8
|
||||
}
|
||||
},
|
||||
{
|
||||
"specialist_memory_advantage": {
|
||||
"exact": "3/2",
|
||||
"decimal": 1.5
|
||||
},
|
||||
"minimum_gpu_energy_fraction_in_unavoidable_memory_even_if_specialist_compute_free": {
|
||||
"exact": "1",
|
||||
"decimal": 1.0
|
||||
}
|
||||
}
|
||||
],
|
||||
"guardband_illustration": {
|
||||
"nominal_ratio": {
|
||||
"exact": "5/4",
|
||||
"decimal": 1.25
|
||||
},
|
||||
"gpu_energy_upper_multiplier": {
|
||||
"exact": "21/20",
|
||||
"decimal": 1.05
|
||||
},
|
||||
"specialist_energy_lower_multiplier": {
|
||||
"exact": "9/10",
|
||||
"decimal": 0.9
|
||||
},
|
||||
"conservative_ratio": {
|
||||
"exact": "35/24",
|
||||
"decimal": 1.4583333333333333
|
||||
},
|
||||
"limits": "The 5% and 10% values are assumed bounds, not measured errors or confidence intervals. Does not cover unknown architectures."
|
||||
},
|
||||
"checks": {
|
||||
"static_cache_examples": 1000,
|
||||
"additive_mixture_examples": 1000,
|
||||
"mismatches": 0,
|
||||
"meaning": "Arithmetic checks of stated models, not proof of a hardware bound. General lemmas are proved in the companion plan."
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,111 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Exact illustrative calculations for Igneum's 1.5x research programme.
|
||||
|
||||
No GPU benchmark, ASIC model, native proof verification, or measured outcome is
|
||||
implemented here. Fractions are exact; every numerical case is an illustration.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
import json
|
||||
import random
|
||||
from fractions import Fraction as F
|
||||
from pathlib import Path
|
||||
|
||||
TARGET = F(3, 2)
|
||||
|
||||
def number(x: F) -> dict[str, str | float]:
|
||||
return {"exact": str(x), "decimal": float(x)}
|
||||
|
||||
def ratio_after(r: F, gpu_multiplier: F, specialist_multiplier: F) -> F:
|
||||
if min(r, gpu_multiplier, specialist_multiplier) <= 0:
|
||||
raise ValueError("Energies and multipliers must be positive")
|
||||
return r * gpu_multiplier / specialist_multiplier
|
||||
|
||||
def retained_miss_model(r: F, gpu_multiplier: F, miss_energy_fraction: F,
|
||||
old_hit: F = F(18,25), new_hit: F = F(1,2)) -> F:
|
||||
if not (0 <= old_hit < 1 and 0 <= new_hit < 1):
|
||||
raise ValueError("Hit fractions must be in [0,1)")
|
||||
if not 0 <= miss_energy_fraction <= 1:
|
||||
raise ValueError("Energy fraction must be in [0,1]")
|
||||
miss_multiplier = (1-new_hit)/(1-old_hit)
|
||||
return ratio_after(r, gpu_multiplier,
|
||||
1 + miss_energy_fraction*(miss_multiplier-1))
|
||||
|
||||
def required_miss_energy_fraction(r: F, g: F) -> F:
|
||||
# Fixed-design illustration, NOT an optimised-hardware or traffic model.
|
||||
return max(F(0), (r*g/TARGET-1)/(F(1,2)/F(7,25)-1))
|
||||
|
||||
def additive_ratio(memory_fraction: F, memory_advantage: F,
|
||||
nonmemory_advantage: F) -> F:
|
||||
if not 0 <= memory_fraction <= 1:
|
||||
raise ValueError("Fraction outside [0,1]")
|
||||
if min(memory_advantage, nonmemory_advantage) <= 0:
|
||||
raise ValueError("Advantages must be positive")
|
||||
return 1/(memory_fraction/memory_advantage +
|
||||
(1-memory_fraction)/nonmemory_advantage)
|
||||
|
||||
def checks() -> dict:
|
||||
rng = random.Random(15020261009)
|
||||
cache_examples = 0
|
||||
for _ in range(1000):
|
||||
n = rng.randint(2, 128)
|
||||
raw = [rng.randint(1,10000) for _ in range(n)]
|
||||
p = [F(x,sum(raw)) for x in raw]
|
||||
q = rng.randint(1,n)
|
||||
best = sum(sorted(p, reverse=True)[:q], F(0))
|
||||
assert best >= F(q,n)
|
||||
shuffled = p[:]
|
||||
rng.shuffle(shuffled)
|
||||
assert sum(sorted(shuffled, reverse=True)[:q], F(0)) == best
|
||||
cache_examples += 1
|
||||
mixed_examples = 0
|
||||
for _ in range(1000):
|
||||
adv = [F(rng.randint(16,50),10) for _ in range(6)]
|
||||
specialist_energy = [F(rng.randint(1,100),10) for _ in adv]
|
||||
mixed = sum((r*e for r,e in zip(adv,specialist_energy)), F(0))/sum(specialist_energy,F(0))
|
||||
assert min(adv) <= mixed <= max(adv)
|
||||
assert mixed > TARGET
|
||||
mixed_examples += 1
|
||||
assert ratio_after(F(21,10),F(85,100),F(12,10)) == F(119,80)
|
||||
assert F(5,4)*F(105,100)/F(90,100) == F(35,24)
|
||||
return {"static_cache_examples":cache_examples,
|
||||
"additive_mixture_examples":mixed_examples,
|
||||
"mismatches":0,
|
||||
"meaning":"Arithmetic checks of stated models, not proof of a hardware bound. General lemmas are proved in the companion plan."}
|
||||
|
||||
def main() -> None:
|
||||
rows=[]
|
||||
for r in (F(19,10),F(2),F(21,10)):
|
||||
rows.append({"starting_ratio":number(r),
|
||||
"gpu_only_required_reduction":number(1-TARGET/r),
|
||||
"specialist_only_required_increase":number(r/TARGET-1),
|
||||
"illustration_gpu_minus_15pct_specialist_plus_20pct":number(ratio_after(r,F(85,100),F(12,10)))})
|
||||
report={
|
||||
"title":"Igneum 1.5x: exact model arithmetic",
|
||||
"date":"2026-10-09",
|
||||
"classification":"ILLUSTRATIVE MATHEMATICS; NO HARDWARE OR PROTOCOL RESULT",
|
||||
"target":number(TARGET),
|
||||
"required_improvement":rows,
|
||||
"old_source_hot_half_model":{
|
||||
"old_hit_fraction":number(F(18,25)),"new_static_uniform_half_hit":number(F(1,2)),
|
||||
"dram_miss_multiplier":number(F(25,14)),
|
||||
"required_miss_energy_fraction_from_2p1_no_gpu_gain":number(required_miss_energy_fraction(F(21,10),F(1))),
|
||||
"required_miss_energy_fraction_from_2p1_gpu_minus_10pct":number(required_miss_energy_fraction(F(21,10),F(9,10))),
|
||||
"illustration_50pct_miss_energy_no_gpu_gain":number(retained_miss_model(F(21,10),F(1),F(1,2))),
|
||||
"illustration_50pct_miss_energy_gpu_minus_10pct":number(retained_miss_model(F(21,10),F(9,10),F(1,2))),
|
||||
"limits":"Fixed architecture, miss-scaled energy only; ignores adaptive caching, replacement design, changed SRAM traffic, contention, compression, clock changes and GPU regressions. Not a forecast."},
|
||||
"memory_dominance_illustrations":[
|
||||
{"specialist_memory_advantage":number(s),
|
||||
"minimum_gpu_energy_fraction_in_unavoidable_memory_even_if_specialist_compute_free":number(s/TARGET)}
|
||||
for s in (F(1),F(6,5),F(3,2))],
|
||||
"guardband_illustration":{
|
||||
"nominal_ratio":number(F(5,4)),"gpu_energy_upper_multiplier":number(F(105,100)),
|
||||
"specialist_energy_lower_multiplier":number(F(90,100)),
|
||||
"conservative_ratio":number(F(35,24)),
|
||||
"limits":"The 5% and 10% values are assumed bounds, not measured errors or confidence intervals. Does not cover unknown architectures."},
|
||||
"checks":checks()}
|
||||
out=Path(__file__).with_name("arithmetic_results.json")
|
||||
out.write_text(json.dumps(report,indent=2)+"\n",encoding="utf-8")
|
||||
print(json.dumps({"output":str(out),"checks":report["checks"]},indent=2))
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"IGNEUM_1p5x_Research_Plan.md": {
|
||||
"bytes": 30977,
|
||||
"sha256": "269ceaa5824d09140a1bcc124dba59438db5a5e68fddc18960db7141fefc0305"
|
||||
},
|
||||
"arithmetic_results.json": {
|
||||
"bytes": 4186,
|
||||
"sha256": "5bd8786a590cc51f221f44501c4f469c65ec2789bc15fda0168005e076ed12a2"
|
||||
},
|
||||
"check_arithmetic.py": {
|
||||
"bytes": 5553,
|
||||
"sha256": "1390a5564d18414f14fb2049514eac9a7d1d317648c81b0829873176a7717940"
|
||||
}
|
||||
}
|
||||
|
|
@ -10,4 +10,4 @@ The claim the master makes: "A complete, independently substantiated technical/e
|
|||
|
||||
Companion volume: `token-value/` holds "Igneum 2.0, Token Value and Network Leadership" (1.0-proposed, snapshot 8 October 2026), the monetary companion to this master, status PROPOSED, every TV gate NOT RUN; see `token-value/README.md`.
|
||||
|
||||
Experiment companion: `1p5x/` holds the founder's "evidence and experiment brief for the 1.5x target" (9 October 2026), the standing order that every research lane and idle box works on the 1.5x target once the chain is one tip; it claims no pass; see `1p5x/README.md`.
|
||||
Experiment companion: `1p5x/` holds the founder's "evidence and experiment brief for the 1.5x target" and "IGNEUM: The 1.5x Research Programme" (both 9 October 2026, with the programme's arithmetic package): the standing order that every research lane and idle box works on the 1.5x target once the chain is one tip, the X matrix and the proof-first track B; neither claims a pass; see `1p5x/README.md` and `1p5x/research-plan/README.md`.
|
||||
|
|
|
|||
|
|
@ -0,0 +1,47 @@
|
|||
# TV-01, Phase 0: the supply spec
|
||||
|
||||
Gate TV-01 ("Resolve and freeze the monetary contract", rules-and-gates.json sha256 `10ce307329e1663dcb6dce849317c9276c19252de5f483e9ee06015fc77cbe3d`). Verdict: **NOT RUN**. This is a document and its tests. Nothing here activates anything.
|
||||
|
||||
## Files
|
||||
|
||||
| File | What it is |
|
||||
|---|---|
|
||||
| `supply-spec.json` | the machine-readable spec: a schema block, the cap, the rules, the activation clocks, two schedule profiles (each an ordered list of phases with start DAA score, per-block subsidy integer, rule name and source line), the coinbase split, fees, supply accounting, the superseded tail-vote text, the conflicts table, and the sources by sha |
|
||||
| `supply-spec.md` | the same in words, with the conflicts table, the D01 resolution and the founder's four questions |
|
||||
| `../../../../../../tools/token-value/tv-01/gen-spec.py` | the generator; every figure in both files is its output |
|
||||
| `../../../../../../tools/token-value/tv-01/check-spec.py` | the checker: four known-failed cases first, then the spec summed to the cap in exact integers |
|
||||
| `registry-batch-tv-01-20261009-01.json` | the registry batch for the CI steward (TV-01 NOT RUN with the evidence paths); kept here, not under tools/ci/batches/, because the map has no token-value cell until the steward adds one |
|
||||
| `../../../../../../tools/token-value/tv-01/code-dump/` | the consensus code's own schedule (`tv01_schedule.rs.txt`, copied into the node tree as `consensus/core/examples/tv01_schedule.rs`, run on build-9 against bf60948a) and its output, `schedule-bf60948a.jsonl` (sha256 `3e4b5ac3c2f43057a13ace7d781ad714e385a94105623c0e0dac4adc417d8056`) |
|
||||
|
||||
## Sources
|
||||
|
||||
| Source | Revision | sha256 |
|
||||
|---|---|---|
|
||||
| site/litepaper.html | igneum master 9c4ed1f06100c27172b05373739c337a10cfd93e (C01's superseded sentence at 6824d49cc32def4463b643293a5622545618f7a9) | `aec583e6e868eb2d3aa8474df3f8892eeebba8a98c887a019ea86bd2efc2f786` |
|
||||
| igneum-node: release-2.0.2-node a284380b, the shipped 2.0.2 commit 5d53a591, release-2.0.3-node-k6 bf60948a, release-2.0.3-node-k7 27f54124 | full shas in `supply-spec.json` `sources.node.revisions` | per file in `sources.node.files`; every cited line is anchored at all four |
|
||||
| pool/src/payout.rs, config.rs, pplns.rs; docs/spec/05-fees-and-economics.md; docs/plans/ledger-decisions.md | igneum master 9c4ed1f0 | in `supply-spec.json` `sources.pool` and `sources.design` |
|
||||
|
||||
## Method
|
||||
|
||||
1. I read the volume's VR-01, VR-02, TV-01, TV-04 and D01, and the litepaper's supply, fee and security sections.
|
||||
2. I grepped the node fork's consensus crate and execution crate for subsidy, coinbase, deflationary and emission, then read the emission table, the coinbase manager, the network objects, the genesis blocks and the EVM reward rule.
|
||||
3. I transcribed the schedule constants into the generator. Each one is anchored to its line at both node revisions.
|
||||
4. The generator computes both profiles in exact integers. It compares every phase, both ramps and both totals with the code dump, and they agree on every field.
|
||||
5. The checker regenerates the phases from the spec's own rules, as a third implementation, and reconciles total + shortfall = cap exactly.
|
||||
|
||||
## Results
|
||||
|
||||
- **Mainnet as compiled (10 blocks a second)**: issuance totals 3,963,038,999.610200467816224 IGN. The shortfall under the cap is exactly 36,961,000,389,799,532,183,776,000 base units. The subsidy is zero from DAA 39,131,424,000, the start of year 125.
|
||||
- **Devnet 4 profile (1 block a second)**: the same curve per second. The total differs from mainnet by 15,473,592,000 base units of per-block flooring.
|
||||
- **Conflicts table**: 13 rows. Nine are resolved: C01 by D01, C12 by D01, and seven taken to the code with a text correction where one is owed. Four are BLOCKED on questions for the founder, each with a default: C04 the block rate, C08 the per-block crediting rule, C09 the mainnet genesis clock, C11 the pool's custody.
|
||||
|
||||
## Commands
|
||||
|
||||
```
|
||||
python3 tools/token-value/tv-01/gen-spec.py --node-git <igneum-node clone holding both revisions> --igneum-root . \
|
||||
--igneum-rev 9c4ed1f06100c27172b05373739c337a10cfd93e \
|
||||
--code-dump tools/token-value/tv-01/code-dump/schedule-bf60948a.jsonl \
|
||||
--out docs/plans/igneum-2.0-master/token-value/phase0/tv-01/supply-spec.json \
|
||||
--md docs/plans/igneum-2.0-master/token-value/phase0/tv-01/supply-spec.md
|
||||
python3 tools/token-value/tv-01/check-spec.py
|
||||
```
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"run_id": "tv-01-20261009-01",
|
||||
"manifest_sha": "6824d49cc",
|
||||
"evidence_dir": "docs/plans/igneum-2.0-master/token-value/phase0/tv-01",
|
||||
"cells": [
|
||||
{
|
||||
"cell": "token-value:tv-01",
|
||||
"cases": [
|
||||
"TV-01"
|
||||
],
|
||||
"status": "NOT RUN",
|
||||
"in_progress": true,
|
||||
"method": "static",
|
||||
"evidence": "docs/plans/igneum-2.0-master/token-value/phase0/tv-01/supply-spec.json; docs/plans/igneum-2.0-master/token-value/phase0/tv-01/supply-spec.md; tools/token-value/tv-01/check-spec.py; tools/token-value/tv-01/code-dump/schedule-bf60948a.jsonl",
|
||||
"note": "Phase 0 supply spec (TV-01): the cap 4,000,000,000 IGN held as the invariant under D01 (capped issuance, subject to TV-04), the litepaper's year-five tail-vote sentence superseded; two schedule profiles generated in exact integers and equal on every field to the consensus code's own dump on bf60948a (release-2.0.2-node a284380b byte-identical for every cited file); total + shortfall = cap exactly; 13 litepaper-versus-code rows, 4 BLOCKED on the founder's questions (C04 block rate, C08 per-block crediting, C09 mainnet genesis clock, C11 pool custody). A document and its checks, not a verdict; the panel reads it.",
|
||||
"claim_impact": "none: nothing activates; the litepaper sentences named in the conflicts table are the site lane's to correct"
|
||||
}
|
||||
]
|
||||
}
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,173 @@
|
|||
# TV-01: the monetary contract as one supply spec
|
||||
|
||||
Igneum 2.0, Token Value Phase 0, item TV-01. 9 October 2026.
|
||||
|
||||
Gate TV-01: **NOT RUN**. This is the complete Phase 0 spec, landed under the coordinator's 11:30 UK clock. This is a Phase 0 document. It activates nothing and changes no node, chain or served page. The verdict stays NOT RUN until the independent panel reads it. Four rows are BLOCKED on questions for the founder (C04, C08, C09, C11), each with a default.
|
||||
|
||||
The machine-readable spec is `supply-spec.json` in this directory. This page says the same in words. Every figure on it comes from `tools/token-value/tv-01/gen-spec.py`, and the consensus code's own functions agree with every one (the code dump below). `tools/token-value/tv-01/check-spec.py` checks the json and sums it to the cap in exact integers.
|
||||
|
||||
## The D01 resolution
|
||||
|
||||
The founder ratified D01 on 9 October 2026, 09:2x UK: capped issuance, subject to TV-04.
|
||||
|
||||
The spec holds the cap as the invariant: **4,000,000,000 IGN, which is 4 x 10^27 base units**. There is no tail. There is no vote on emission, and no price or revenue trigger. There is no emergency mint.
|
||||
|
||||
D01 supersedes this sentence of the litepaper (site/litepaper.html line 630, igneum master 6824d49c, before the site lane's D01 edit):
|
||||
|
||||
> "One rule sits beside the cap. If external proving revenue is under one fifth of the block subsidy over any 90-day window after year 5, the question of a tail reward goes to the miners' signalling vote. The protocol never changes emission by itself."
|
||||
|
||||
The site lane has already rewritten the sentence (ad64e48fa, 09:16 UK). At igneum master 9c4ed1f0, line 630 reads: "The cap is fixed at 4 billion, with no tail emission and no vote that expands it (D01, subject to the pre-launch security-funding gate)." The year-five clause now says the answer "is the security-funding plan under D01, never a tail emission or a vote that expands the cap."
|
||||
|
||||
The code already matches D01:
|
||||
|
||||
- Mainnet carries `EmissionSchedule::CURRENT` (params.rs:1858), and that schedule's tail is `Tail::Cap` (emission.rs:109).
|
||||
- The schedule is a compiled genesis parameter. The daemon refuses an override file on mainnet and the testnet (daemon.rs, `params_compiled`, network.rs:266).
|
||||
- No signal path reads or writes the emission schedule.
|
||||
|
||||
"Subject to TV-04" means one thing. If TV-04's security-budget analysis shows capped security cannot pass, the volume's rule applies: delay the launch, or openly choose another monetary design. An undisclosed tail is never the answer. This landing edits no `site/` file.
|
||||
|
||||
## The contract
|
||||
|
||||
| Item | Value | Source (igneum-node; every cited line anchored at release-2.0.2-node a284380b, the shipped 2.0.2 commit 5d53a591, release-2.0.3-node-k6 bf60948a and release-2.0.3-node-k7 27f54124) |
|
||||
|---|---|---|
|
||||
| Coin | IGN | |
|
||||
| Base unit | 10^-18 IGN; one consensus base unit is one EVM wei | unit.rs:38; params.rs:1816 |
|
||||
| Cap | 4,000,000,000 IGN = 4,000,000,000,000,000,000,000,000,000 base units | igneum.rs:37, :154 |
|
||||
| Time key | seconds = floor(DAA score / blocks per second) | emission.rs:329 to 331; coinbase.rs:265 |
|
||||
| Launch rate | 31,688,087,810,000,000,000 base units a DAA second (floor(10^17 / 31,557,600) at 8 decimals, times 10^10) | emission.rs:104; params.rs:1858 |
|
||||
| Launch ramp | linear from 10% at genesis to 100% at 2,592,000 DAA seconds (30 days) | emission.rs:105 to 106, :316 |
|
||||
| Decay | every 63,115,200 DAA seconds (two years of 365.25 days): rate[k+1] = floor(rate[k] x 2^31 / 2^32), a halving | emission.rs:107 to 108, :225 |
|
||||
| Per-block subsidy | per_block(d) = ramp(floor(rate[floor(s / 63,115,200)] / bps), s), s = floor(d / bps) | emission.rs:329 to 333 |
|
||||
| Tail | cap: the curve runs to zero and stays there | emission.rs:109 |
|
||||
| Block rate | mainnet as compiled: 10 blocks a second from genesis; Devnet 4: 1 (C04 BLOCKED) | params.rs:1789, :2208 |
|
||||
| Genesis | DAA score 0 (C09 BLOCKED: the compiled mainnet genesis still carries Kaspa's 1,312,860) | genesis.rs:92 |
|
||||
| Genesis is never rewarded | genesis never enters the DAA window and the coinbase pays only DAA-window blues; the genesis coinbase has no outputs. On a chain, block height h >= 1 carries DAA score h - 1, so DAA score 0 is the first paid block and the last paying block is at height = terminal DAA score (mainnet 39,131,424,000; Devnet 4 4,102,488,000) | window.rs:177; coinbase.rs:139; genesis.rs:26 |
|
||||
|
||||
### How the cap is enforced
|
||||
|
||||
No running counter enforces the cap. The schedule does:
|
||||
|
||||
- Every block's coinbase must carry exactly the scheduled subsidy (coinbase.rs:262).
|
||||
- The EVM state credits by the same rule (executor.rs:181 to 211).
|
||||
- The cap constant itself is only compared in tests (igneum.rs:154).
|
||||
|
||||
The schedule sums to the cap minus an exact shortfall, so issuance never reaches the cap.
|
||||
|
||||
## The schedule
|
||||
|
||||
`supply-spec.json` lists every phase with:
|
||||
|
||||
- its start and end DAA score
|
||||
- its per-block subsidy as an integer
|
||||
- its rule name
|
||||
- its source line
|
||||
|
||||
There are two profiles, and their per-second curves are identical. **The mainnet profile is the monetary contract** (`contract_profile` in the json). The Devnet 4 profile is the chain TV-02 replays and the litepaper's launch rate (C04). It sums to the same 36,961,000.39 IGN under the cap, minus per-block flooring dust.
|
||||
|
||||
- **mainnet as compiled**: 10 blocks a second, 64 phases.
|
||||
- **Devnet 4**: 1 block a second, the litepaper's launch rate, 67 phases.
|
||||
|
||||
The phases in order:
|
||||
|
||||
1. **Ramp** (DAA 0 to 25,920,000 at 10 blocks a second). The per-block subsidy is the ramp formula, and the phase sum is exact.
|
||||
2. **Full rate after the ramp**, to the first halving.
|
||||
3. **One constant phase per halving**, until the last one-base-unit subsidy.
|
||||
4. **Terminal**: zero from that point on, for ever.
|
||||
|
||||
<!-- figures:begin (generated by tools/token-value/tv-01/gen-spec.py; do not edit by hand) -->
|
||||
|
||||
| Figure | Mainnet as compiled (10 blocks/s) | Devnet 4 (1 block/s) |
|
||||
|---|---|---|
|
||||
| Cap | 4,000,000,000 IGN (4000000000000000000000000000 base units) | the same |
|
||||
| Total scheduled issuance | 3,963,038,999.610200467816224 IGN | 3,963,038,999.610200483289816 IGN |
|
||||
| Shortfall under the cap (exact) | 36961000389799532183776000 base units (36,961,000.389799532183776 IGN) | 36961000389799516710184000 base units (36,961,000.389799516710184 IGN) |
|
||||
| Ramp phase | DAA 0 to 25,920,000, 45,174,523.72229648548848 IGN | DAA 0 to 2,592,000, 45,174,523.722296485498848 IGN |
|
||||
| First block | 0.3168808781 IGN | 3.168808781 IGN |
|
||||
| Full-rate block (after the ramp) | 3.168808781 IGN | 31.68808781 IGN |
|
||||
| Phases (ramp, constant, terminal) | 64 | 67 |
|
||||
| Last non-zero subsidy | 1 base unit a block, DAA 38,500,272,000 to 39,131,424,000 | 1 base unit a block, DAA 4,039,372,800 to 4,102,488,000 |
|
||||
| Terminal (zero for ever) from | DAA 39,131,424,000 (decay step 62, year 125) | DAA 4,102,488,000 (decay step 65, year 131) |
|
||||
| Scheduled supply at the end of year 1 | 963,038,999.99163248548848 IGN | 963,038,999.991632485498848 IGN |
|
||||
| Scheduled supply at the end of year 2 | 1,963,038,999.86448848548848 IGN | 1,963,038,999.864488485498848 IGN |
|
||||
| Scheduled supply at the end of year 5 | 3,213,038,999.70555848548848 IGN | 3,213,038,999.705558485498848 IGN |
|
||||
| Scheduled supply at the end of year 10 | 3,838,038,999.62609348548848 IGN | 3,838,038,999.626093485498848 IGN |
|
||||
| Scheduled supply at the end of year 12 | 3,900,538,999.61814698548848 IGN | 3,900,538,999.618146985498848 IGN |
|
||||
| Scheduled supply at the end of year 20 | 3,959,132,749.61069714173848 IGN | 3,959,132,749.610697141748848 IGN |
|
||||
| Scheduled supply at the end of year 50 | 3,963,038,880.400910946483264 IGN | 3,963,038,880.400910949333816 IGN |
|
||||
| Scheduled supply at the end of year 100 | 3,963,038,999.610196921373136 IGN | 3,963,038,999.6101969312294752 IGN |
|
||||
| A 1-to-10 blocks/s step at DAA 31,557,600 under the current code | total 4,863,038,999.495770867826592 IGN, 863,038,999.495770867826592 IGN over the cap | |
|
||||
| The compiled mainnet genesis (DAA 1,312,860) | first ramp block at 14.55%; 510,841.7849105123888436 IGN never scheduled | |
|
||||
|
||||
Code agreement: every field of 129 decay phases, both ramps and both totals equal to this generator's integers (dump sha256 `3e4b5ac3c2f43057a13ace7d781ad714e385a94105623c0e0dac4adc417d8056`).
|
||||
|
||||
<!-- figures:end -->
|
||||
|
||||
What the figures mean:
|
||||
|
||||
- **For a miner**: a full-rate block pays 3.168808781 IGN at 10 blocks a second, or 31.68808781 IGN at 1. The emission per second is the same either way, so a card's expected income does not depend on the block rate. In the first two years, 80% of each block goes to the block's producer and 20% to the proving escrow.
|
||||
- **For a holder**: 963.04 million IGN are issued in year 1 and 1,963.04 million by the end of year 2. The total approaches 3,963,038,999.61 IGN and never reaches the cap. The 36,961,000.39 IGN shortfall is the launch ramp's withheld coins plus the floor of the per-second rate. Nobody holds them.
|
||||
- **For security**: the subsidy halves every two years. It is one base unit a block by year 123 (10 blocks a second) and zero from the start of year 125. From then on, blocks are paid by fees alone. That is TV-04's question, and nothing in this spec answers it.
|
||||
|
||||
## The coinbase split the node enforces
|
||||
|
||||
| Rule | Effect | Source |
|
||||
|---|---|---|
|
||||
| Producer and proving pool | producer = subsidy - floor(subsidy x 20 / 100), pool = floor(subsidy x 20 / 100); fees go to the producer | igneum.rs:44, :201 to 208; coinbase.rs:106 |
|
||||
| Signing bonus | a silent producer's share loses 10% (1,000 bps) to the pool; nothing destroyed; mainnet: off | igneum.rs:110; params.rs:1809 to 1810 |
|
||||
| Native ledger (EVM state) | 80% to the block's `IGNA` address (or its `IGNW` split once the pool switch is on), 20% to the escrow 0x0000000000000000000000000000000000000220; blue blocks only | executor.rs:181 to 211; config.rs:59 |
|
||||
| Shadow ledger (UTXO coinbase) | producer outputs plus one OP_RETURN output tagged `igneum-proving-pool-v0`; no UTXO transaction but the coinbase is valid, so these outputs are never spent | coinbase.rs:126 to 168; body_validation_in_isolation.rs:54 |
|
||||
| Pool | none in consensus; the reference pool keeps `fee_percent` (default 1.0) of the 80% it receives and pays members PPLNS from its address | pool/src/payout.rs:1 to 6; pool/src/config.rs:58; pool/src/pplns.rs:76 |
|
||||
| Dev fee | none in consensus; the miner software requests 1 template in 100 with the dev address (off with `--dev-fee 0`); it moves those blocks' producer share and creates nothing | litepaper line 625 |
|
||||
| Treasury, foundation, team, stake | none | coinbase.rs:126 to 168; executor.rs:181 to 211 |
|
||||
| Genesis | the genesis coinbase has no outputs; no premine | genesis.rs:26 |
|
||||
| Burns | execution base fee and proving charge burned in full today; 20% of the tip goes to registered payees, and unregistered parts are burned. The design's route of 90% of the proving base fee to provers (docs/design/proving-payment.md; node branch proving-payment 930b6322) is not merged and has no activation | executor.rs:319 to 371; pgas.rs:290 |
|
||||
|
||||
## Supply accounting (VR-02, for TV-02)
|
||||
|
||||
- **Issued**: the EVM state's subsidy credits (producer plus escrow), for blue blocks only. The schedule's sum, at one subsidy per DAA score, is the upper bound.
|
||||
- **Burned**: base fees, proving charges and unregistered developer shares. The UTXO OP_RETURN output is **not** a burn (C06).
|
||||
- **Locked**: the proving escrow's balance, meaning credited but not yet paid against a valid proof. Unclaimed credit is stranded today, as the litepaper says at line 609.
|
||||
- **Not supply**: the UTXO coinbase outputs, which are an unspendable shadow record, and any bridged or wrapped IGN on another chain.
|
||||
- **Replay model**: one scheduled subsidy per DAA score. A block is paid at its own DAA score. Parallel blocks can share a score, and a score can have no block. So measured issuance differs from the model only at phase boundaries, and only by the blocks merged across them. TV-02 reconciles the measured ledgers to this model in exact integers.
|
||||
|
||||
## Conflicts between the litepaper and the code
|
||||
|
||||
The litepaper is site/litepaper.html at igneum master 9c4ed1f0 (sha256 `aec583e6e868eb2d3aa8474df3f8892eeebba8a98c887a019ea86bd2efc2f786`). The one exception is C01's superseded sentence, which is quoted at 6824d49c. The code is igneum-node at all four revisions. Every quote below is checked exact by the generator.
|
||||
|
||||
| # | The litepaper's words | The code's line and value | The spec takes, and why | Status |
|
||||
|---|---|---|---|---|
|
||||
| C01 | line 630 at 6824d49c, the tail-vote sentence quoted above (rewritten at ad64e48fa) | emission.rs:109 `Tail::Cap`; params.rs:1858; the override refused on mainnet; no vote path | The cap; the sentence is SUPERSEDED. D01, VR-01. | RESOLVED by D01, subject to TV-04; the text was already rewritten by the site lane |
|
||||
| C02 | line 578 "emission starts at 1 billion a year"; line 602 "3,938M IGN in the first 12 years of a 4,000M cap." | emission.rs:104 to 106: year 1 issues 963,038,999.99 IGN; years 1 to 12 issue 3,900,538,999.62 IGN | The code. The chart ignores the ramp's withheld 36.96M IGN. | RESOLVED; text correction owed (site lane) |
|
||||
| C03 | line 578 "halves every two years for ever" | emission.rs:225, :331: the per-block subsidy floors to 0 at DAA 39,131,424,000 (start of year 125, 10 blocks/s) or DAA 4,102,488,000 (start of year 131, 1 block/s) | The code: a finite schedule ending in a terminal zero phase | RESOLVED; text correction owed |
|
||||
| C04 | line 542 "one block a second at launch with scheduled steps to four and ten"; line 668 "31.688 IGN a block, 86,400 blocks a day" | params.rs:1789: mainnet runs 10 blocks/s from genesis. coinbase.rs:31, :265: the subsidy keys time on one block rate with no history, so a 1-to-10 step one year in would issue 4,863,038,999.50 IGN, which is 863,038,999.50 over the cap (check-spec KF4) | Both profiles, identical per second. No live block-rate step is cap-safe under today's code. | **BLOCKED** |
|
||||
| C05 | line 568 "the base unit is Open" | unit.rs:38; params.rs:1816: 18 decimals | 18 decimals (O-2.6, decided 6 October 2026) | RESOLVED; text correction owed |
|
||||
| C06 | line 609: the 20% "goes to an unspendable script ... and is burned there. Provers are paid from a separate escrow in the execution state" | coinbase.rs:163 (OP_RETURN on the UTXO side); body_validation_in_isolation.rs:54 (UTXO never spent); executor.rs:181 to 211 (the EVM state credits 80% and 20%) | Native supply is the EVM state. The UTXO 20% is not a burn, and the escrow balance is issued and locked. | RESOLVED for the spec; TV-02 reconciles both ledgers |
|
||||
| C07 | line 620 "80% the block's miner, 20% the proving pool for the provers of that block" | coinbase.rs:152 to 159 (the UTXO side pays DAA red blocks); executor.rs:181 (the EVM credits blue only) | Native issuance is the blue blocks' subsidies; the schedule sum is the upper bound | RESOLVED; TV-02 measures the red share |
|
||||
| C08 | silent | params.rs:1812 and :2232: `subsidy_per_block_activation_daa` is never on mainnet and Devnet 4 (testnet-1: 0). The EVM credits a merged block the merging chain block's subsidy, while the UTXO side pays the block's own. | Each block's own DAA score (ledger N8) is normative | **BLOCKED** |
|
||||
| C09 | line 775 "Genesis with no premine, 30-day ramp." | genesis.rs:92: the mainnet GENESIS carries DAA 1,312,860 and Kaspa's 2021 timestamp. The first block would pay 14.55% of full rate, not 10%. 510,841.78 IGN would never be scheduled, and the schedule would run 1.52 days early. | Genesis at DAA score 0, as Devnet 4 and testnet-1 have it | **BLOCKED** |
|
||||
| C10 | line 614 "The priority fee splits two ways: 80% to the miner and provers of that block, 20% to the apps whose code ran"; line 622 "80% the block's miner and provers; 20% the apps whose code ran, per call frame" | Two separate 80/20 rules ship. The subsidy's 20% goes to the proving escrow and pays provers per accepted proof record (igneum.rs:44, :201 to 208; executor.rs:181 to 211). The tip's 80% goes to the block's miner alone, and 20% goes to developer registrations or is burned (executor.rs:321, :332 to 340, :362 to 371). No tip reaches a prover. The 90% proving-fee route is designed and unmerged, with no activation. spec 05 line 24: "No part of the tip reaches the provers: O-5.7 is closed at zero (8 October 2026 ...)" | The code, which is the design's closed decision O-5.7. The litepaper's "miner and provers" is SUPERSEDED. | RESOLVED by O-5.7; text corrections owed (litepaper lines 614 and 622; spec 05 lines 79 and 98) |
|
||||
| C11 | line 647 "The reference pool never holds a member’s balance. Payouts come straight from the coinbase split" | pool/src/payout.rs:1 to 6 (the pool's address receives and pays); config.rs:58 (1% fee); params.rs:1851 (`pool_split_activation_daa` never) | The code: a custodial PPLNS pool with no supply effect | **BLOCKED** |
|
||||
| C12 | line 630 "The cap is fixed at 4 billion, with no tail emission and no vote that expands it (D01, subject to the pre-launch security-funding gate)." | emission.rs:116 to 122; params.rs:2014: testnet-1's own approved genesis schedule, with a 1% a year tail and no hard cap (docs/plans/ledger-decisions.md line 110, ledger E22). It is active only when testnet-1's genesis is cut, which has not happened. Devnet 4 (params.rs:2372) and mainnet (params.rs:1858) carry `CURRENT` with `Tail::Cap`. | Testnet-only and outside the monetary contract. Binding the cap to the testnet too would change its approved genesis (E22) and move its digest: the founder's call. | RESOLVED by D01: testnet-only, named |
|
||||
| C13 | silent | params.rs:1783 to 1784: Kaspa's `deflationary_phase_daa_score` and `pre_deflationary_phase_base_subsidy` sit in the mainnet object. Only the digest reads them (coinbase.rs:31). | Ignored: not part of the contract | RESOLVED; the node lane may annotate or remove them at the genesis cut |
|
||||
|
||||
### The four BLOCKED rows: the founder's questions
|
||||
|
||||
| Row | Question for the founder | Default if unanswered | Earliest clock |
|
||||
|---|---|---|---|
|
||||
| C04 | Does mainnet launch at the compiled 10 blocks a second, with the litepaper's 1-to-4-to-10 step plan removed? Or at 1 with steps? If steps, the node lane adds a block-rate-history subsidy rule before genesis, and TV-02 replays a step. | The spec freezes the compiled 10 blocks a second, and the step plan is treated as text to correct | the founder's answer |
|
||||
| C08 | Should `subsidy_per_block_activation_daa` be 0 in the mainnet object, as testnet-1 has it, so both ledgers credit each block its own subsidy? (The node lane owns the change.) | N8 stays normative, and TV-02 reports the chain-block reading as a measured divergence | the founder's answer |
|
||||
| C09 | Is the mainnet genesis cut at DAA score 0? | The spec freezes DAA score 0, and the genesis cut is checked against it | the founder's answer |
|
||||
| C11 | Should the `IGNW` coinbase split be active at mainnet genesis (`pool_split_activation_daa` = 0), so the litepaper's custody sentence holds? Or should the sentence change to describe the custodial PPLNS pool and its 1% fee? | The sentence is corrected to match the code | the founder's answer |
|
||||
|
||||
## Reproduce
|
||||
|
||||
```
|
||||
python3 tools/token-value/tv-01/gen-spec.py --node-git <igneum-node clone holding a284380b and bf60948a> \
|
||||
--igneum-root . --igneum-rev 9c4ed1f06100c27172b05373739c337a10cfd93e \
|
||||
--code-dump tools/token-value/tv-01/code-dump/schedule-bf60948a.jsonl \
|
||||
--out docs/plans/igneum-2.0-master/token-value/phase0/tv-01/supply-spec.json \
|
||||
--md docs/plans/igneum-2.0-master/token-value/phase0/tv-01/supply-spec.md
|
||||
python3 tools/token-value/tv-01/check-spec.py
|
||||
```
|
||||
|
||||
The generator refuses to write if any cited line has lost its anchor text at any of the four node revisions, or if any litepaper quote is not exact. The code dump comes from `tools/token-value/tv-01/code-dump/tv01_schedule.rs.txt`. That program was copied into `consensus/core/examples/` of a scratch tree of bf60948a on build-9 and run there with `cargo run --release -p kaspa-consensus-core --example tv01_schedule`. It calls `Params::emission`, `EmissionTable::block_subsidy` and `Params::bps` for mainnet, Devnet 4 and testnet-1. The node fork itself is unchanged.
|
||||
207
docs/plans/igneum-2.0-master/token-value/phase0/tv-02/README.md
Normal file
207
docs/plans/igneum-2.0-master/token-value/phase0/tv-02/README.md
Normal file
|
|
@ -0,0 +1,207 @@
|
|||
# TV-02: two independent supply replays, exact integer agreement
|
||||
|
||||
Token Value Phase 0, gate TV-02 ("Reproduce complete supply accounting"), 9 October 2026. Verdict: **NOT RUN**. The
|
||||
independent panel reads this evidence; this lane writes no PASS on its own result.
|
||||
|
||||
## Result in one paragraph
|
||||
|
||||
Two implementations with no shared code replay the issued supply from genesis to the cap, block by block, on three
|
||||
network objects of the node at bf60948a: the Igneum 2.0 devnet object (igneum-devnet-4: 1 block a second, 18
|
||||
decimals), the mainnet object (10 blocks a second, 18 decimals) and the 8-decimal devnet object. With the rule set
|
||||
v0.1 they agree to the integer at every one of 591 rows: every transition's block before, at and after (the ramp's
|
||||
end and every halving step until the subsidy is 0), the terminal block and one full step past it. Their CSVs are
|
||||
byte-identical. Both reorg fixtures agree with each other and with integers written by hand before either replay ran,
|
||||
and the known-failed no-undo ledger is refused. Implementation B written from the published words alone (rule set
|
||||
v0) diverges at the first row, genesis. The cause is one rule the words never state: genesis issues nothing, so the
|
||||
block at height h carries DAA score h - 1. The total at the terminal is unchanged by it. On the TV-01 lane's supply spec (supply-spec.json at
|
||||
tv-01-supply-spec 9cd09130, which states that rule) B agrees with A to the integer on both profiles the spec carries,
|
||||
devnet-4 and mainnet, and on both reorg fixtures, and A's terminal rows equal the terminal supplies the spec itself
|
||||
states. No cap breach, no issuance
|
||||
after the terminal block, and the node's two issuance code paths agree on all 46,010,980,803 blocks replayed.
|
||||
|
||||
| network object | blocks a second | decimals | terminal block (last with a subsidy) | issued supply at the terminal, base units | IGN | under the 4,000,000,000 cap by |
|
||||
|---|---|---|---|---|---|---|
|
||||
| igneum-devnet-4 | 1 | 18 | height 4,102,488,000 (step 65, year 130) | 3963038999610200483289816000 | 3,963,038,999.610200483289816 | 36,961,000.389799516710184 IGN |
|
||||
| mainnet | 10 | 18 | height 39,131,424,000 (step 62, year 124) | 3963038999610200467816224000 | 3,963,038,999.610200467816224 | 36,961,000.389799532183776 IGN |
|
||||
| devnet (8 decimals) | 1 | 8 | height 2,019,686,400 (step 32, year 64) | 396303898886765648 | 3,963,038,988.86765648 | 36,961,011.13234352 IGN |
|
||||
|
||||
What the gap under the cap is made of (devnet-4, evaluated with bc on build-5 from the closed forms, a third
|
||||
computation beside A and B): the launch ramp withholds 36,960,999.881223514501152 IGN (2,592,000 x the full rate
|
||||
minus the ramp's sum) and the floors of the launch rate and the 65 halvings withhold 0.508576002209032 IGN; the two
|
||||
sum to A's and B's gap exactly.
|
||||
|
||||
What it means for a holder: the supply that will ever exist on the 2.0 devnet and mainnet objects is 3,963,038,999.61
|
||||
IGN, reached at year 130 (devnet-4) or 124 (mainnet), with nothing minted after it and no path to the 4,000,000,000
|
||||
figure; the first two years (DAA 0 to 63,115,199) issue 1,963,038,999.86 IGN (49.5 percent of the final supply). A miner sees the per-block
|
||||
subsidy of the table below; a pool sees the same numbers per block found. Neither changes with this document.
|
||||
|
||||
## Sources, by sha
|
||||
|
||||
| source | identity |
|
||||
|---|---|
|
||||
| the node's issuance code | igneum-node bf60948a (release-2.0.3-node-k6); the six files that decide issuance are byte-identical in release-2.0.2-node a284380b: consensus/core/src/emission.rs, consensus/core/src/igneum.rs, consensus/src/processes/coinbase.rs, consensus/core/src/config/params.rs, consensus/src/processes/window.rs, consensus/src/processes/difficulty.rs |
|
||||
| the volume's rules | docs/plans/igneum-2.0-master/token-value/rules-and-gates.json, sha256 10ce307329e1663d... (TV-02, VR-02, D01) |
|
||||
| the published words | site/litepaper.html (sha256 35ac65e151339089..., blob db1b8bf7b1d2) and site/economics.html (sha256 449c20b85cc4eede..., blob b15926199f06), both at master 6824d49cc32d |
|
||||
| the TV-01 supply spec | docs/plans/igneum-2.0-master/token-value/phase0/tv-01/supply-spec.json (schema 1.0.0) at the TV-01 lane's branch tip tv-01-supply-spec 9cd091303 ("TV-01 complete"), blob bdddb09df2cd, sha256 3ffdd3084aff06d963df6c326cda88719d576f07d9cbfb56079de1a8b40c4403; not yet on master when B ran on it (09:0x UTC). Mapped to B's rules by tools/token-value/tv-02/replay-spec/tv01_adapter.py (rules-tv01-spec.json, sha256 061fa669be287b73...), which refuses any rule it cannot map |
|
||||
| B's interim rule sets, before the spec | the two below, run first (08:22 UTC) |
|
||||
| B's rules v0 (words only) | tools/token-value/tv-02/replay-spec/rules-v0-litepaper.json, sha256 67707b095584d8bb8bc99e799875b8d167512085385ea6883bc12575d98fbb33 |
|
||||
| B's rules v0.1 (v0 plus the genesis rule) | tools/token-value/tv-02/replay-spec/rules-v0.1-genesis-rule.json, sha256 d72d5578dfe4e0c860b45fdb37a749dd7f1b0365175a38349dd413b1e93eb0b9; the genesis rule from the node lane's written answer (9 October 2026, 09:25 UK), cited by file and line |
|
||||
|
||||
## Method
|
||||
|
||||
**The model.** A straight chain. Genesis (height 0, DAA score 0) issues nothing: it never enters the DAA window
|
||||
(window.rs 176-179 puts it in mergeset_non_daa) and the coinbase pays only blues outside that set (coinbase.rs 139).
|
||||
A block's DAA score is its selected parent's plus its mergeset's DAA blocks (difficulty.rs 31-32), so block 1 has DAA
|
||||
score 0 and block h >= 1 has DAA score h - 1. Each block issues the schedule's subsidy at its DAA score: the
|
||||
per-second rate of its step (launch rate 31,688,087,810,000,000,000 base units at 18 decimals, halved and floored
|
||||
every 63,115,200 DAA seconds), divided by the blocks a second (floor), then the 30-day ramp from 10 percent (floor).
|
||||
"cumulative" at height h is the issued supply of heights 0..h, exact, in base units.
|
||||
|
||||
**Implementation A** (tools/token-value/tv-02/replay-node, Rust, on build-5). Calls the node's own code through
|
||||
kaspa-consensus-core by path (vendor/igneum-node at bf60948a, read-only, no node file changed): the network objects
|
||||
(devnet4_params(), MAINNET_PARAMS, DEVNET_PARAMS) and `EmissionTable::block_subsidy`, the value
|
||||
`CoinbaseManager::calc_block_subsidy` returns and body validation compares every coinbase against. Every block from
|
||||
DAA 0 to one step past the terminal is computed one by one (46,010,980,803 DAA scores over the three objects, 32 threads,
|
||||
38 s wall in all) and checked against the node's second issuance path `igneum::block_subsidy_units` (0 mismatches)
|
||||
and against the shape rule (non-decreasing through the ramp, non-increasing after it; 0 violations). Crate tests
|
||||
(cargo test --release on build-5, 3 green): the no-undo ledger double-counts the orphan (the known-failed case first),
|
||||
the hand-written boundary subsidies, the two node paths at every row.
|
||||
|
||||
**Implementation B** (tools/token-value/tv-02/replay-spec/replay_spec.py, Python 3, on build-6). Written from a rules
|
||||
file in words, shares no code with A and uses another method: inside the ramp it sums DAA seconds and multiplies by
|
||||
the blocks in each second; after the ramp it multiplies each step's subsidy by the step's block count, counting only
|
||||
a partial second or step block by block. Its self-test (the known-failed grouping that drops a partial second is
|
||||
caught; the grouped sum equals a block-by-block sum on three small networks) ran before any replay.
|
||||
|
||||
**The row set** (written into both independently): genesis; DAA 0, 1, 2; at 10 blocks a second the last block of
|
||||
second 0 and the first two of second 1; the first block of every ramp day 1 to 29 and the ramp's midpoint; for every
|
||||
transition (the ramp's end, then step k = 1, 2, ... up to the first step whose blocks pay 0) the blocks at DAA x - 1,
|
||||
x and x + 1; the terminal block; one full step past the terminal. 232 rows on devnet-4, 226 on mainnet, 133 on devnet.
|
||||
|
||||
**The reorg fixtures** (tools/token-value/tv-02/fixtures/, committed in 4e206eaf8 before either replay ran). A common
|
||||
ancestor, an orphan branch of k blocks, rolled back, replaced by k + 1, at a transition boundary on devnet-4. Every
|
||||
expected integer was written by hand from the closed forms (the ramp's sum has an exact closed form at 18 decimals:
|
||||
schedule(d) = F/10 + d q + floor(d/9) with q = 11,002,808,267,361) and its digits evaluated with bc on build-5. The
|
||||
known-failed ledger (a rollback that forgets to subtract) is part of every fixture and must be refused.
|
||||
|
||||
| fixture | ancestor | orphan (k) | replacement (k+1) | final, hand-written | known-failed no-undo, hand-written |
|
||||
|---|---|---|---|---|---|
|
||||
| reorg-ramp-end-k1 | height 2,592,000 (the last ramp block), 45174523722296485498848000 | 1 block | 2 blocks | 45174587098472105498848000 (= the straight chain at height 2,592,002) | 45174618786559915498848000 (one full block too many) |
|
||||
| reorg-first-step-k2 | height 63,115,199, 1963038968176400675498848000 | 2 blocks (full, then the first halved) | 3 blocks | 1963039031552576295498848000 (= the straight chain at height 63,115,202) | 1963039079084708010498848000 |
|
||||
|
||||
**The comparison** (tools/token-value/tv-02/compare.py). Reads A's and B's CSVs (height, daa_score, subsidy,
|
||||
cumulative) and reports EQUAL or the first differing row with both values, the columns that differ, the heights only
|
||||
one side has, and the cumulative offsets on shared heights; for a reorg it checks A against B row by row, both
|
||||
against the fixture's hand-written integers, the final against the straight chain at the same height, and that the
|
||||
no-undo ledger is refused. Its self-test runs first in every comparison (a corrupted cumulative, a dropped row and a
|
||||
no-undo ledger are each reported; equal inputs read EQUAL).
|
||||
|
||||
## Results
|
||||
|
||||
Run 9 October 2026, 08:22 to 08:35 UTC (rule sets v0 and v0.1) and 09:03 to 09:07 UTC (the TV-01 spec). A on build-5, B on build-6, the comparison on build-5 over B's outputs
|
||||
copied from build-6. A rebuilt from this landing's source re-ran byte-identical (the second run under rerun-final/).
|
||||
|
||||
| comparison | rules (B) | result | first difference |
|
||||
|---|---|---|---|
|
||||
| straight, devnet-4, 232 rows | v0.1 | EQUAL, byte-identical CSVs (sha256 6ede4f4a2fe3c551...) | none, through the ramp's end and steps 1 to 65, the terminal and one step past |
|
||||
| straight, mainnet, 226 rows | v0.1 | EQUAL, byte-identical (sha256 3cf7f426d062722d...) | none, through steps 1 to 62 |
|
||||
| straight, devnet, 133 rows | v0.1 | EQUAL, byte-identical (sha256 5679a4353380491f...) | none, through steps 1 to 32 |
|
||||
| reorg-ramp-end-k1 | v0.1 | EQUAL, every row, and both equal the hand-written integers; no-undo refused | none |
|
||||
| reorg-first-step-k2 | v0.1 | EQUAL, every row, and both equal the hand-written integers; no-undo refused | none |
|
||||
| straight, all three objects | v0 (words only) | DIFFER | row 1, genesis: A (0, 0, subsidy 0, cumulative 0), B (0, 0, 3168808781000000000, 3168808781000000000) on devnet-4; 316880878100000000 on mainnet; 316880878 on devnet. Every later row is one block apart in height; the terminal total is identical |
|
||||
| both reorg fixtures | v0 (words only) | DIFFER from A and from the fixture, by the same one-block shift | row 1, the ancestor: A DAA 2,591,999, cumulative 45174523722296485498848000; B DAA 2,592,000, cumulative 45174555410384295498848000 |
|
||||
| straight, devnet-4, 232 rows | the TV-01 spec | EQUAL, byte-identical (sha256 6ede4f4a2fe3c551...) | none, the ramp's end and steps 1 to 65 (the spec's 66 transitions), the terminal and one step past |
|
||||
| straight, mainnet, 226 rows | the TV-01 spec | EQUAL, byte-identical (sha256 3cf7f426d062722d...) | none, the ramp's end and steps 1 to 62 (the spec's 63 transitions) |
|
||||
| reorg-ramp-end-k1 and reorg-first-step-k2 | the TV-01 spec | EQUAL, every row, both equal the hand-written integers; no-undo refused | none |
|
||||
| A's terminal rows against the spec's own numbers | the TV-01 spec | EQUAL: mainnet 3963038999610200467816224000 at height 39,131,424,000; devnet-4 3963038999610200483289816000 at height 4,102,488,000 | none |
|
||||
|
||||
The spec carries the devnet-4 and mainnet profiles only; the 8-decimal devnet stands on rule set v0.1.
|
||||
|
||||
The rule that explains the v0 divergence: the published words (litepaper, economics page) never say what genesis
|
||||
issues or which DAA score block h carries. The node's rule is that genesis never enters the DAA window
|
||||
(consensus/src/processes/window.rs 176-179), is skipped by the coinbase (coinbase.rs 139) and is never rewarded, so
|
||||
block 1 carries DAA score 0 (difficulty.rs 31-32). Written in as v0.1, A and B agree everywhere. The words need the
|
||||
rule (row R2).
|
||||
|
||||
Devnet-4, selected rows (the full tables: build-5/table-devnet4.md, table-mainnet.md, table-devnet.md, generated by
|
||||
tools/token-value/tv-02/table.py from A's CSV, which is byte-identical to B's):
|
||||
|
||||
| height | DAA score | row | subsidy (base units) | issued supply (base units) | issued supply (IGN) |
|
||||
|---|---|---|---|---|---|
|
||||
| 0 | 0 | genesis (issues nothing) | 0 | 0 | 0 |
|
||||
| 1 | 0 | DAA 0, the ramp at 10 percent | 3168808781000000000 | 3168808781000000000 | 3.168808781 |
|
||||
| 2,592,001 | 2,592,000 | at ramp end | 31688087810000000000 | 45174555410384295498848000 | 45,174,555.410384295498848 |
|
||||
| 63,115,201 | 63,115,200 | at step 1 | 15844043905000000000 | 1963039015708532390498848000 | 1,963,039,015.708532390498848 |
|
||||
| 126,230,401 | 126,230,400 | at step 2 | 7922021952500000000 | 2963039007659366437998848000 | 2,963,039,007.659366437998848 |
|
||||
| 189,345,601 | 189,345,600 | at step 3 | 3961010976250000000 | 3463039003634783461748848000 | 3,463,039,003.634783461748848 |
|
||||
| 252,460,801 | 252,460,800 | at step 4 | 1980505488125000000 | 3713039001622491973623848000 | 3,713,039,001.622491973623848 |
|
||||
| 504,921,601 | 504,921,600 | at step 8 | 123781593007812500 | 3947413999735968703506660500 | 3,947,413,999.7359687035066605 |
|
||||
| 1,009,843,201 | 1,009,843,200 | at step 16 | 483521847686767 | 3962977964454441767484072367 | 3,962,977,964.454441767484072367 |
|
||||
| 2,019,686,401 | 2,019,686,400 | at step 32 | 7377957880 | 3963038998678877917554525880 | 3,963,038,998.67887791755452588 |
|
||||
| 3,029,529,601 | 3,029,529,600 | at step 48 | 112578 | 3963038999610186273155109378 | 3,963,038,999.610186273155109378 |
|
||||
| 4,039,372,801 | 4,039,372,800 | at step 64 | 1 | 3963038999610200483226700801 | 3,963,038,999.610200483226700801 |
|
||||
| 4,102,488,000 | 4,102,487,999 | terminal (last block with a subsidy) | 1 | 3963038999610200483289816000 | 3,963,038,999.610200483289816 |
|
||||
| 4,102,488,001 | 4,102,488,000 | at step 65 | 0 | 3963038999610200483289816000 | 3,963,038,999.610200483289816 |
|
||||
| 4,165,603,201 | 4,165,603,200 | one step past the terminal | 0 | 3963038999610200483289816000 | 3,963,038,999.610200483289816 |
|
||||
|
||||
The height-1 row matches the team-reported first-block credit on the economics page (3.168808781 IGN at 18 decimals,
|
||||
read back over the EVM RPC on 8 October 2026); this replay did not read the chain.
|
||||
|
||||
## Open rows
|
||||
|
||||
| id | row | state | waits on, earliest clock |
|
||||
|---|---|---|---|
|
||||
| R1 | B re-ran on the TV-01 spec at 9cd09130 (EQUAL above). If the spec changes before or when it lands on master, B re-runs on the landed sha (under a minute on build-6; the adapter refuses an unmapped rule) | NOT RUN | the spec's landing on master |
|
||||
| R2 | The genesis rule (genesis issues nothing, block h carries DAA score h - 1) is now stated in the TV-01 spec (rules.genesis_rule, "genesis-unrewarded"); the litepaper and economics page still do not state it, and a reader of the words alone reproduces the supply one block early (the v0 divergence above) | NOT RUN | the site lane, if the served words take it |
|
||||
| R3 | The per-block order at more than one block a second (the per-second rate divided by the blocks a second and floored, then the ramp) is stated in the TV-01 spec (rules.per_block); B had assumed it from the words and it matches the node | NOT RUN | none |
|
||||
| R4 | The TV-01 spec names the mainnet profile (MAINNET_PARAMS, 10 blocks a second) as the monetary contract; the litepaper says "1 / s blocks, rising to 10 (designed)". At 10 the supply differs from 1 by 15,473,592,000 base units (0.000000015 IGN) and ends six years earlier (year 124, not 130) | NOT RUN | none for TV-02 |
|
||||
| R5 | The 8-decimal and 18-decimal schedules are not the same coin amounts after the first halving (3,168,808,781 is odd, so the 8-decimal halving floors and the 18-decimal one does not): the 18-decimal supply is 10.74 IGN larger. igneum.rs's comment that the 18-decimal numbers are "the 8-decimal ones times 10^10" holds for the launch rate only. Informational; no network mixes the two | NOT RUN | none |
|
||||
| R6 | The testnet-1 object carries a percent tail (`EmissionSchedule::TESTNET_1`, no cap). D01 ratified capped issuance; that object is outside it and was not replayed | BLOCKED | TV-01, D01 |
|
||||
| R7 | DEVNET4_GENESIS carries the 8-byte subsidy layout on an 18-decimal network: read at 16 bytes its subsidy field is 134825277635862783248051596202800963840 base units (A's genesis-payload output). The window rule keeps it from ever being paid (the node lane, 09:25 UK); any other reader of that field (an explorer, an executor path) must apply the same rule. The node lane takes the B10 test case for devnet-3 and devnet-4 as a 2.0.3 test item | NOT RUN | the node lane's 2.0.3 test |
|
||||
| R8 | The rest of the gate's procedure: paid (the 80/20 split is by subtraction, so producer plus pool equals the subsidy per block by construction), burned (the pool's OP_RETURN script on devnet v0), locked, outstanding, red and non-DAA blocks in a DAG, the execution layer's ledger, duplicate records, wrappers. This replay covers issued supply on a straight chain and its reorg only | NOT RUN | a later TV-02 item |
|
||||
| R10 | The compiled mainnet genesis still carries Kaspa's DAA score 1,312,860 (the TV-01 spec's C09, BLOCKED there). Both replays and the spec's profile take genesis at DAA 0; a mainnet genesis at 1,312,860 would start the schedule 131,286 seconds into the ramp | BLOCKED | TV-01's C09, the node lane |
|
||||
| R9 | Independence: A and B share no code, language or method, but one lane wrote both and B's rules were written after reading the node; the panel should weigh that | NOT RUN | the independent panel |
|
||||
|
||||
## Run commands
|
||||
|
||||
On build-5 (A), with the igneum repo at this landing and the node fork checked out at bf60948a as vendor/igneum-node:
|
||||
|
||||
```
|
||||
tools/token-value/tv-02/run-a.sh <repo> /srv/artefacts/token-value/tv-02 build
|
||||
tools/token-value/tv-02/run-a.sh <repo> /srv/artefacts/token-value/tv-02 genesis
|
||||
tools/token-value/tv-02/run-a.sh <repo> /srv/artefacts/token-value/tv-02 straight devnet4 # and mainnet, devnet
|
||||
tools/token-value/tv-02/run-a.sh <repo> /srv/artefacts/token-value/tv-02 reorg tools/token-value/tv-02/fixtures/reorg-first-step-k2.json
|
||||
(cd tools/token-value/tv-02/replay-node && cargo test --release)
|
||||
```
|
||||
|
||||
On build-6 (B), from tools/token-value/tv-02 alone (no node code on the box):
|
||||
|
||||
```
|
||||
./run-b.sh <tv-02 dir> /srv/artefacts/token-value/tv-02 replay-spec/rules-v0.1-genesis-rule.json selftest
|
||||
./run-b.sh <tv-02 dir> /srv/artefacts/token-value/tv-02 replay-spec/rules-v0.1-genesis-rule.json straight devnet4
|
||||
./run-b.sh <tv-02 dir> /srv/artefacts/token-value/tv-02 replay-spec/rules-v0.1-genesis-rule.json reorg fixtures/reorg-ramp-end-k1.json
|
||||
python3 replay-spec/tv01_adapter.py <supply-spec.json> replay-spec/rules-tv01-spec.json # then the same three commands on it
|
||||
```
|
||||
|
||||
The comparison, on build-5 with B's outputs copied to <out>/from-build-6:
|
||||
|
||||
```
|
||||
tools/token-value/tv-02/compare-all.sh <tv-02 dir> /srv/artefacts/token-value/tv-02 rules-v0.1-genesis-rule
|
||||
NETS="devnet4 mainnet" SPEC_RULES=<out>/from-build-6/rules-tv01-spec.json tools/token-value/tv-02/compare-all.sh <tv-02 dir> /srv/artefacts/token-value/tv-02 rules-tv01-spec
|
||||
python3 tools/token-value/tv-02/table.py a-devnet4.csv a-devnet4-labels.csv 18
|
||||
```
|
||||
|
||||
Each run writes its pid file first (run-a.pid, run-b.pid, compare.pid in the out dir) and removes it on exit.
|
||||
|
||||
## Evidence
|
||||
|
||||
| what | where |
|
||||
|---|---|
|
||||
| A's outputs, summaries, genesis-payload read, crate test log, build log | build-5:/srv/artefacts/token-value/tv-02/ (a-*.csv, a-*-summary.json, a-genesis-payload.json, a-cargo-test.log, build-a.log, a-run.log); the rebuild from this landing under rerun-final/ |
|
||||
| B's outputs, both rule sets, self-test log | build-6:/srv/artefacts/token-value/tv-02/ (b-rules-v0.1-genesis-rule-*, b-rules-v0-litepaper-*, b-selftest.log, b-run.log) |
|
||||
| the comparison logs and tables | build-5:/srv/artefacts/token-value/tv-02/compare-rules-v0.1-genesis-rule.log (sha256 81545cc811ff208b...), compare-rules-v0-litepaper.log (sha256 f743ecf3a323afba...), table-*.md |
|
||||
| all of the above, copied | build-1:/srv/artefacts/token-value/tv-02/build-5/ and build-1:/srv/artefacts/token-value/tv-02/build-6/ |
|
||||
| the binary that ran | tv02-replay-node sha256 ef3af79157947584... (source at 4e206eaf8); rebuilt from this landing 25ff78695bd9d972..., outputs byte-identical |
|
||||
| B's code that ran | replay_spec.py sha256 acd68e1b8f9f4d1a... on rule sets v0 and v0.1; c07aba106afbd8e4299e1c177719399265c388fe5b2af1922cd66dd72aac6d77 (this landing's file: the spec's integer launch rate and Q32 step multiplier read when stated, nothing else changed) on the TV-01 spec |
|
||||
| the spec run | build-6:/srv/artefacts/token-value/tv-02/b-rules-tv01-spec-*; build-5:/srv/artefacts/token-value/tv-02/compare-rules-tv01-spec.log (sha256 7d18441d0e6825ba...); both copied to build-1 (build-5/ and build-6/) |
|
||||
| the registry batch | docs/plans/igneum-2.0-master/token-value/phase0/tv-02/registry-batch-tv-02.json (TV-02 NOT RUN, the proposed cell native:tv-02; the CI steward records it and names the cell on the map) |
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
{
|
||||
"run_id": "tv-02-20261009-01",
|
||||
"manifest_sha": "426c7c85",
|
||||
"method": "native",
|
||||
"evidence_dir": "build-1:/srv/artefacts/token-value/tv-02",
|
||||
"release_identity": {
|
||||
"commit": "426c7c85 (master, the first TV-02 landing) and the spec-run landing after it (branch tv-02-supply-replay)",
|
||||
"lockfile": "tools/token-value/tv-02/replay-node/Cargo.lock",
|
||||
"binary": "tv02-replay-node sha256 ef3af79157947584... (the run), 25ff78695bd9d972... (rebuilt from af2c23bc, outputs byte-identical)",
|
||||
"network_object": "the compiled objects of igneum-node bf60948a read by A (devnet4_params, MAINNET_PARAMS, DEVNET_PARAMS); no chain state read or written",
|
||||
"activation": "none (Phase 0 lands documents and tests only)",
|
||||
"profile_hashes": "igneum-node bf60948a (emission.rs, igneum.rs, coinbase.rs, params.rs, window.rs, difficulty.rs byte-identical in release-2.0.2-node a284380b); rules-and-gates.json 10ce3073...; B rules v0 67707b09..., v0.1 d72d5578...; replay_spec.py acd68e1b...; the TV-01 supply-spec.json at 9cd09130 sha256 3ffdd308...; replay_spec.py on the spec c07aba10..."
|
||||
},
|
||||
"claim_impact": "none: no served figure moves; the supply ever issued on the 2.0 objects is 3,963,038,999.61 IGN under the 4,000,000,000 cap, with nothing after the terminal block",
|
||||
"reviewer": "",
|
||||
"note": "for the CI steward: the proposed cell native:tv-02 (cases TV-02, coverage partial: issued supply on a straight chain and its reorg; paid, burned, locked, outstanding, DAG reds and duplicate records open) is not yet on the map; this file is the batch to record.",
|
||||
"cells": [
|
||||
{
|
||||
"cell": "native:tv-02",
|
||||
"cases": [
|
||||
"TV-02"
|
||||
],
|
||||
"status": "NOT RUN",
|
||||
"in_progress": true,
|
||||
"method": "native",
|
||||
"evidence": "docs/plans/igneum-2.0-master/token-value/phase0/tv-02/README.md; build-1:/srv/artefacts/token-value/tv-02/build-5/compare-rules-v0.1-genesis-rule.log; build-1:/srv/artefacts/token-value/tv-02/build-5/compare-rules-v0-litepaper.log; build-1:/srv/artefacts/token-value/tv-02/build-5/a-*.csv; build-1:/srv/artefacts/token-value/tv-02/build-6/b-*.csv; build-1:/srv/artefacts/token-value/tv-02/build-5/compare-rules-tv01-spec.log; build-1:/srv/artefacts/token-value/tv-02/build-6/b-rules-tv01-spec-*.csv",
|
||||
"note": "two supply replays, A (Rust on the node's issuance code at bf60948a, build-5) and B (Python from the rules in words, build-6), no shared code: with rule set v0.1 (the words plus the genesis rule, window.rs 176-179) EQUAL to the integer, byte-identical CSVs, at every transition's before/at/after, the terminal and one step past, on the devnet-4 (1 bps, 18 decimals), mainnet (10 bps, 18) and devnet (8) objects, and on two hand-written reorg fixtures with the no-undo ledger refused; with the words alone (v0) DIFFER at genesis (A 0, B 3168808781000000000 on devnet-4), a one-block shift with the terminal total unchanged (3963038999610200483289816000 base units on devnet-4); on the TV-01 supply spec (9cd09130) EQUAL on devnet-4 and mainnet and both fixtures, A's terminal rows equal to the spec's stated terminal supplies; paid/burned/locked accounting, the mainnet genesis DAA score (C09), DAG reds and independent signoff are open rows; NOT RUN until the independent panel reads it"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -649,22 +649,22 @@
|
|||
"owner": "GPU lead + three independent operators",
|
||||
"manual_page": 21,
|
||||
"owner_lane": "research lane (ad6a2bd47d4a46105)",
|
||||
"run_status": "NOT RUN",
|
||||
"evidence_path": "build-1:/srv/artefacts/tas/p02-rows/p02-rows.txt",
|
||||
"run_id": "p02-fleet-pods-20261008-01",
|
||||
"updated": "2026-10-08T21:49:19.980Z",
|
||||
"run_status": "PASS",
|
||||
"evidence_path": "docs/analysis/class-v6/rows/x0-5090-20261009.md; build-1:/srv/artefacts/tas/p02-rows/p02-rows.txt",
|
||||
"run_id": "hash-lane-20261009-batch5",
|
||||
"updated": "2026-10-09T09:08:49.974Z",
|
||||
"evidence_record": {
|
||||
"requirement_id": "GPU-01",
|
||||
"decision": "NOT RUN",
|
||||
"decision": "PASS",
|
||||
"method": "GPU",
|
||||
"cell": "bench:fleet-pods",
|
||||
"manifest_sha": "417c4a57",
|
||||
"run_id": "p02-fleet-pods-20261008-01",
|
||||
"evidence": "build-1:/srv/artefacts/tas/p02-rows/p02-rows.txt",
|
||||
"manifest_sha": "1a938abe4",
|
||||
"run_id": "hash-lane-20261009-batch5",
|
||||
"evidence": "docs/analysis/class-v6/rows/x0-5090-20261009.md",
|
||||
"in_progress": false,
|
||||
"coverage": "partial: the NVIDIA cells of P02 that RunPod carries",
|
||||
"release_identity": {
|
||||
"commit": "417c4a57",
|
||||
"commit": "1a938abe4",
|
||||
"lockfile": "",
|
||||
"binary": "",
|
||||
"network_object": "",
|
||||
|
|
@ -673,9 +673,8 @@
|
|||
},
|
||||
"claim_impact": "",
|
||||
"reviewer": "",
|
||||
"at": "2026-10-08T21:49:19.980Z"
|
||||
"at": "2026-10-09T09:08:49.974Z"
|
||||
},
|
||||
"in_progress_since": "2026-10-08T19:59:34.316Z",
|
||||
"approvals": {
|
||||
"scope_approved": null,
|
||||
"implementation_complete": null,
|
||||
|
|
@ -684,6 +683,28 @@
|
|||
},
|
||||
"evidence_records": {
|
||||
"bench:fleet-pods": {
|
||||
"requirement_id": "GPU-01",
|
||||
"decision": "PASS",
|
||||
"method": "GPU",
|
||||
"cell": "bench:fleet-pods",
|
||||
"manifest_sha": "1a938abe4",
|
||||
"run_id": "hash-lane-20261009-batch5",
|
||||
"evidence": "docs/analysis/class-v6/rows/x0-5090-20261009.md",
|
||||
"in_progress": false,
|
||||
"coverage": "partial: the NVIDIA cells of P02 that RunPod carries",
|
||||
"release_identity": {
|
||||
"commit": "1a938abe4",
|
||||
"lockfile": "",
|
||||
"binary": "",
|
||||
"network_object": "",
|
||||
"activation": "",
|
||||
"profile_hashes": ""
|
||||
},
|
||||
"claim_impact": "",
|
||||
"reviewer": "",
|
||||
"at": "2026-10-09T09:08:49.974Z"
|
||||
},
|
||||
"bench:fleet-pods@p02-fleet-pods-20261008-01": {
|
||||
"requirement_id": "GPU-01",
|
||||
"decision": "NOT RUN",
|
||||
"method": "GPU",
|
||||
|
|
@ -821,19 +842,19 @@
|
|||
"manual_page": 21,
|
||||
"owner_lane": "hash lane (a690540514aa453d7)",
|
||||
"run_status": "NOT RUN",
|
||||
"evidence_path": "build-1:/srv/artefacts/tas/p02-rows/p02-rows.txt; docs/analysis/class-v6/amd-intel-energy.md; docs/analysis/class-v6/rows/pc1-l8off-ds2g-20261009.md",
|
||||
"run_id": "hash-lane-20261009-batch4",
|
||||
"updated": "2026-10-09T03:58:33.097Z",
|
||||
"evidence_path": "docs/analysis/class-v6/rows/x0-5090-20261009.md; docs/analysis/class-v6/amd-intel-energy.md; docs/analysis/class-v6/rows/pc1-l8off-ds2g-20261009.md; build-1:/srv/artefacts/tas/p02-rows/p02-rows.txt",
|
||||
"run_id": "hash-lane-20261009-batch5",
|
||||
"updated": "2026-10-09T09:08:49.974Z",
|
||||
"evidence_record": {
|
||||
"requirement_id": "GPU-03",
|
||||
"decision": "PASS",
|
||||
"method": "GPU",
|
||||
"cell": "bench:pc1-packs",
|
||||
"cell": "bench:fleet-pods",
|
||||
"manifest_sha": "1a938abe4",
|
||||
"run_id": "hash-lane-20261009-batch4",
|
||||
"evidence": "docs/analysis/class-v6/rows/pc1-l8off-ds2g-20261009.md",
|
||||
"run_id": "hash-lane-20261009-batch5",
|
||||
"evidence": "docs/analysis/class-v6/rows/x0-5090-20261009.md",
|
||||
"in_progress": false,
|
||||
"coverage": "partial: hl-v6-all against hl-v6-foldrw on the 5090, the window's cost per unit of work, registers and blocks per SM, no spill; the mandatory-SKU set is owed",
|
||||
"coverage": "partial: the 4090 cell",
|
||||
"release_identity": {
|
||||
"commit": "1a938abe4",
|
||||
"lockfile": "",
|
||||
|
|
@ -844,7 +865,7 @@
|
|||
},
|
||||
"claim_impact": "",
|
||||
"reviewer": "",
|
||||
"at": "2026-10-09T03:58:33.097Z"
|
||||
"at": "2026-10-09T09:08:49.974Z"
|
||||
},
|
||||
"in_progress_since": "2026-10-08T19:59:34.316Z",
|
||||
"approvals": {
|
||||
|
|
@ -856,16 +877,16 @@
|
|||
"evidence_records": {
|
||||
"bench:fleet-pods": {
|
||||
"requirement_id": "GPU-03",
|
||||
"decision": "NOT RUN",
|
||||
"decision": "PASS",
|
||||
"method": "GPU",
|
||||
"cell": "bench:fleet-pods",
|
||||
"manifest_sha": "417c4a57",
|
||||
"run_id": "p02-fleet-pods-20261008-01",
|
||||
"evidence": "build-1:/srv/artefacts/tas/p02-rows/p02-rows.txt",
|
||||
"manifest_sha": "1a938abe4",
|
||||
"run_id": "hash-lane-20261009-batch5",
|
||||
"evidence": "docs/analysis/class-v6/rows/x0-5090-20261009.md",
|
||||
"in_progress": false,
|
||||
"coverage": "partial: the 4090 cell",
|
||||
"release_identity": {
|
||||
"commit": "417c4a57",
|
||||
"commit": "1a938abe4",
|
||||
"lockfile": "",
|
||||
"binary": "",
|
||||
"network_object": "",
|
||||
|
|
@ -874,7 +895,7 @@
|
|||
},
|
||||
"claim_impact": "",
|
||||
"reviewer": "",
|
||||
"at": "2026-10-08T21:49:19.980Z"
|
||||
"at": "2026-10-09T09:08:49.974Z"
|
||||
},
|
||||
"bench:amd-intel-energy": {
|
||||
"requirement_id": "GPU-03",
|
||||
|
|
@ -919,6 +940,28 @@
|
|||
"claim_impact": "",
|
||||
"reviewer": "",
|
||||
"at": "2026-10-09T03:58:33.097Z"
|
||||
},
|
||||
"bench:fleet-pods@p02-fleet-pods-20261008-01": {
|
||||
"requirement_id": "GPU-03",
|
||||
"decision": "NOT RUN",
|
||||
"method": "GPU",
|
||||
"cell": "bench:fleet-pods",
|
||||
"manifest_sha": "417c4a57",
|
||||
"run_id": "p02-fleet-pods-20261008-01",
|
||||
"evidence": "build-1:/srv/artefacts/tas/p02-rows/p02-rows.txt",
|
||||
"in_progress": false,
|
||||
"coverage": "partial: the 4090 cell",
|
||||
"release_identity": {
|
||||
"commit": "417c4a57",
|
||||
"lockfile": "",
|
||||
"binary": "",
|
||||
"network_object": "",
|
||||
"activation": "",
|
||||
"profile_hashes": ""
|
||||
},
|
||||
"claim_impact": "",
|
||||
"reviewer": "",
|
||||
"at": "2026-10-08T21:49:19.980Z"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
@ -2200,19 +2243,19 @@
|
|||
"manual_page": 26,
|
||||
"owner_lane": "hash lane (a690540514aa453d7)",
|
||||
"run_status": "NOT RUN",
|
||||
"evidence_path": "build-1:/srv/artefacts/tas/202-5d53a591-1176efb9/miner-77b5acc5/box4-pow.log; build-1:/srv/artefacts/tas/p02-rows/p02-rows.txt; docs/analysis/class-v6/rows/pc1-l8off-ds2g-20261009.md",
|
||||
"run_id": "hash-lane-20261009-batch4",
|
||||
"updated": "2026-10-09T03:58:33.097Z",
|
||||
"evidence_path": "build-1:/srv/artefacts/tas/202-5d53a591-1176efb9/miner-77b5acc5/box4-pow.log; docs/analysis/class-v6/rows/x0-5090-20261009.md; docs/analysis/class-v6/rows/pc1-l8off-ds2g-20261009.md; build-1:/srv/artefacts/tas/p02-rows/p02-rows.txt",
|
||||
"run_id": "hash-lane-20261009-batch5",
|
||||
"updated": "2026-10-09T09:08:49.974Z",
|
||||
"evidence_record": {
|
||||
"requirement_id": "POW-08",
|
||||
"decision": "PASS",
|
||||
"method": "GPU",
|
||||
"cell": "bench:pc1-packs",
|
||||
"cell": "bench:fleet-pods",
|
||||
"manifest_sha": "1a938abe4",
|
||||
"run_id": "hash-lane-20261009-batch4",
|
||||
"evidence": "docs/analysis/class-v6/rows/pc1-l8off-ds2g-20261009.md",
|
||||
"run_id": "hash-lane-20261009-batch5",
|
||||
"evidence": "docs/analysis/class-v6/rows/x0-5090-20261009.md",
|
||||
"in_progress": false,
|
||||
"coverage": "partial: rw-w4, rw-w32, rw-w64 and the 1p5x long-program packs as regression controls for the excluded levers",
|
||||
"coverage": "partial: W = 16 on four cards",
|
||||
"release_identity": {
|
||||
"commit": "1a938abe4",
|
||||
"lockfile": "",
|
||||
|
|
@ -2223,7 +2266,7 @@
|
|||
},
|
||||
"claim_impact": "",
|
||||
"reviewer": "",
|
||||
"at": "2026-10-09T03:58:33.097Z"
|
||||
"at": "2026-10-09T09:08:49.974Z"
|
||||
},
|
||||
"approvals": {
|
||||
"scope_approved": null,
|
||||
|
|
@ -2257,16 +2300,16 @@
|
|||
},
|
||||
"bench:fleet-pods": {
|
||||
"requirement_id": "POW-08",
|
||||
"decision": "NOT RUN",
|
||||
"decision": "PASS",
|
||||
"method": "GPU",
|
||||
"cell": "bench:fleet-pods",
|
||||
"manifest_sha": "417c4a57",
|
||||
"run_id": "p02-fleet-pods-20261008-01",
|
||||
"evidence": "build-1:/srv/artefacts/tas/p02-rows/p02-rows.txt",
|
||||
"manifest_sha": "1a938abe4",
|
||||
"run_id": "hash-lane-20261009-batch5",
|
||||
"evidence": "docs/analysis/class-v6/rows/x0-5090-20261009.md",
|
||||
"in_progress": false,
|
||||
"coverage": "partial: W = 16 on four cards",
|
||||
"release_identity": {
|
||||
"commit": "417c4a57",
|
||||
"commit": "1a938abe4",
|
||||
"lockfile": "",
|
||||
"binary": "",
|
||||
"network_object": "",
|
||||
|
|
@ -2275,7 +2318,7 @@
|
|||
},
|
||||
"claim_impact": "",
|
||||
"reviewer": "",
|
||||
"at": "2026-10-08T21:49:19.980Z"
|
||||
"at": "2026-10-09T09:08:49.974Z"
|
||||
},
|
||||
"bench:pc1-packs": {
|
||||
"requirement_id": "POW-08",
|
||||
|
|
@ -2298,6 +2341,28 @@
|
|||
"claim_impact": "",
|
||||
"reviewer": "",
|
||||
"at": "2026-10-09T03:58:33.097Z"
|
||||
},
|
||||
"bench:fleet-pods@p02-fleet-pods-20261008-01": {
|
||||
"requirement_id": "POW-08",
|
||||
"decision": "NOT RUN",
|
||||
"method": "GPU",
|
||||
"cell": "bench:fleet-pods",
|
||||
"manifest_sha": "417c4a57",
|
||||
"run_id": "p02-fleet-pods-20261008-01",
|
||||
"evidence": "build-1:/srv/artefacts/tas/p02-rows/p02-rows.txt",
|
||||
"in_progress": false,
|
||||
"coverage": "partial: W = 16 on four cards",
|
||||
"release_identity": {
|
||||
"commit": "417c4a57",
|
||||
"lockfile": "",
|
||||
"binary": "",
|
||||
"network_object": "",
|
||||
"activation": "",
|
||||
"profile_hashes": ""
|
||||
},
|
||||
"claim_impact": "",
|
||||
"reviewer": "",
|
||||
"at": "2026-10-08T21:49:19.980Z"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
8
tools/ci/batches/hash-lane-20261009-batch5.json
Normal file
8
tools/ci/batches/hash-lane-20261009-batch5.json
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"run_id": "hash-lane-20261009-batch5",
|
||||
"manifest_sha": "1a938abe4",
|
||||
"evidence_dir": "docs/analysis/class-v6/rows",
|
||||
"cells": [
|
||||
{"cell": "bench:fleet-pods", "status": "PASS", "method": "GPU", "evidence": "docs/analysis/class-v6/rows/x0-5090-20261009.md"}
|
||||
]
|
||||
}
|
||||
255
tools/token-value/tv-01/check-spec.py
Normal file
255
tools/token-value/tv-01/check-spec.py
Normal file
|
|
@ -0,0 +1,255 @@
|
|||
#!/usr/bin/env python3
|
||||
"""TV-01 checker: reads supply-spec.json, checks it is well-formed, and sums every schedule to the cap in exact integers.
|
||||
|
||||
python3 tools/token-value/tv-01/check-spec.py [docs/plans/igneum-2.0-master/token-value/phase0/tv-01/supply-spec.json]
|
||||
|
||||
Order of work (the known-failed cases first, the standing rule that a check is trusted only after it has fired):
|
||||
1. four known-failed specs, built in memory from the real one, must each be REFUSED for the stated reason:
|
||||
KF1 a tail (the terminal phase pays 1 base unit a block for ever): unbounded, the overshoot is infinite;
|
||||
KF2 an emergency mint (the first full-rate phase doubled, every sum made self-consistent): overshoot over the cap;
|
||||
KF3 the declared shortfall one base unit off: the exact reconciliation fails;
|
||||
KF4 a block-rate step from 1 to 10 blocks a second one year in, keyed as the code keys time today: overshoot;
|
||||
2. the real spec must be well-formed and reconcile exactly: total + shortfall = cap, total <= cap, the phases
|
||||
regenerated from the spec's own rules equal the listed phases, tail = cap, the tail-vote text SUPERSEDED.
|
||||
Exit 0 only when every known-failed case was refused and the real spec checked clean. The output never says PASS: the
|
||||
gate TV-01 reads NOT RUN until the independent panel reads the evidence. Standalone Python 3, deterministic.
|
||||
"""
|
||||
import copy
|
||||
import json
|
||||
import sys
|
||||
|
||||
DEFAULT = "docs/plans/igneum-2.0-master/token-value/phase0/tv-01/supply-spec.json"
|
||||
TOP = ["schema", "gate", "verdict", "decision", "invariant", "units", "cap", "rules", "activation_clocks", "schedules",
|
||||
"coinbase_split", "fees", "supply_accounting", "superseded", "conflicts", "sources", "generator"]
|
||||
CONFLICT_KEYS = ["id", "topic", "litepaper", "code", "spec_takes", "why", "status"]
|
||||
|
||||
|
||||
class Refused(Exception):
|
||||
pass
|
||||
|
||||
|
||||
def need(cond, why):
|
||||
if not cond:
|
||||
raise Refused(why)
|
||||
|
||||
|
||||
def amount(x, where):
|
||||
need(isinstance(x, str) and x.isdigit() and (x == "0" or not x.startswith("0")), f"{where}: amount {x!r} is not a decimal string of base units")
|
||||
return int(x)
|
||||
|
||||
|
||||
# ---- the schedule regenerated from the spec's own rules (a third implementation beside the code and the generator) --
|
||||
def rules_of(spec):
|
||||
r = spec["rules"]
|
||||
return {"L": amount(r["launch_rate_per_second"], "rules.launch_rate_per_second"), "R": r["ramp_seconds"],
|
||||
"P": r["ramp_start_percent"], "S": r["step_seconds"], "Q": r["step_decay_q32"]}
|
||||
|
||||
|
||||
def rate(rr, k, cache={}):
|
||||
key = (rr["L"], rr["Q"], k)
|
||||
if key not in cache:
|
||||
x = rr["L"]
|
||||
for _ in range(k):
|
||||
x = (x * rr["Q"]) >> 32
|
||||
cache[key] = x
|
||||
return cache[key]
|
||||
|
||||
|
||||
def per_block(rr, daa, bps):
|
||||
s = daa // bps
|
||||
full = rate(rr, s // rr["S"]) // bps
|
||||
if s >= rr["R"]:
|
||||
return full
|
||||
return full * (rr["P"] * rr["R"] + (100 - rr["P"]) * s) // (100 * rr["R"])
|
||||
|
||||
|
||||
def ramp_sum(rr, bps):
|
||||
full = rr["L"] // bps
|
||||
R, P = rr["R"], rr["P"]
|
||||
return sum(full * (P * R + (100 - P) * s) // (100 * R) for s in range(R)) * bps
|
||||
|
||||
|
||||
def supply_to(rr, bps, daa):
|
||||
"""Scheduled issuance over DAA scores [0, daa) at one subsidy per DAA score."""
|
||||
total, R = 0, rr["R"] * bps
|
||||
full = rr["L"] // bps
|
||||
secs = min(daa, R) // bps
|
||||
total += sum(full * (rr["P"] * rr["R"] + (100 - rr["P"]) * s) // (100 * rr["R"]) for s in range(secs)) * bps
|
||||
total += (min(daa, R) - secs * bps) * per_block(rr, secs * bps, bps)
|
||||
d = R
|
||||
while d < daa:
|
||||
k = (d // bps) // rr["S"]
|
||||
end = min((k + 1) * rr["S"] * bps, daa)
|
||||
pb = per_block(rr, d, bps)
|
||||
if pb == 0:
|
||||
break
|
||||
total += pb * (end - d)
|
||||
d = end
|
||||
return total
|
||||
|
||||
|
||||
# ---- the checks ----------------------------------------------------------------------------------------------
|
||||
def check(spec, regen=True):
|
||||
"""Returns a list of report lines; raises Refused on the first fault."""
|
||||
out = []
|
||||
for k in TOP:
|
||||
need(k in spec, f"top-level field {k!r} missing")
|
||||
need(spec["gate"] == "TV-01", "gate is not TV-01")
|
||||
need(spec["verdict"] in ("NOT RUN", "BLOCKED", "FAIL"), f"verdict {spec['verdict']!r}: a lane never writes PASS on its own result")
|
||||
need(spec["decision"].get("id") == "D01", "decision is not D01")
|
||||
need(spec["rules"].get("genesis_rule", {}).get("rule") == "genesis-unrewarded", "the genesis rule (genesis never rewarded) is missing")
|
||||
need(spec["rules"].get("tail") == "cap", f"tail is {spec['rules'].get('tail')!r}: under D01 the only tail is the cap")
|
||||
cap = amount(spec["cap"]["base_units"], "cap.base_units")
|
||||
unit = amount(spec["units"]["base_units_per_coin"], "units.base_units_per_coin")
|
||||
need(unit == 10 ** spec["units"]["decimals"], "base_units_per_coin is not 10^decimals")
|
||||
need(cap == int(spec["cap"]["coins"]) * unit, "cap.base_units is not cap.coins x base_units_per_coin")
|
||||
sup = spec["superseded"]
|
||||
need(any(s.get("status") == "SUPERSEDED" and "tail reward" in s.get("text", "") and s.get("by", "").startswith("D01") for s in sup),
|
||||
"the litepaper's tail-vote sentence is not named SUPERSEDED by D01")
|
||||
ids = set()
|
||||
for c in spec["conflicts"]:
|
||||
for k in CONFLICT_KEYS:
|
||||
need(k in c, f"conflict {c.get('id')}: field {k!r} missing")
|
||||
need(c["id"] not in ids, f"conflict id {c['id']} repeated")
|
||||
ids.add(c["id"])
|
||||
need(c["status"].startswith("RESOLVED") or c["status"] == "BLOCKED", f"conflict {c['id']}: status {c['status']!r}")
|
||||
if c["status"] == "BLOCKED":
|
||||
need(c.get("question", "").startswith("For the founder"), f"conflict {c['id']}: BLOCKED without the founder's question")
|
||||
rr = rules_of(spec)
|
||||
need(rr["Q"] < 1 << 32 and rr["Q"] > 0, "step_decay_q32 outside 1..2^32: the curve would never fall")
|
||||
for sc in spec["schedules"]:
|
||||
name, bps, ph = sc["profile"], sc["bps"], sc["phases"]
|
||||
need(isinstance(bps, int) and bps > 0, f"{name}: bps {bps!r}")
|
||||
need(sc["genesis_daa_score"] == 0, f"{name}: genesis DAA score is not 0")
|
||||
need(len(ph) == sc["phase_count"] and len(ph) >= 2, f"{name}: phase_count disagrees with the list")
|
||||
need(ph[0]["start_daa_score"] == 0, f"{name}: the first phase does not start at DAA 0")
|
||||
total, prev_pb = 0, None
|
||||
for i, p in enumerate(ph):
|
||||
need(p["index"] == i, f"{name}: phase {i} carries index {p['index']}")
|
||||
need(p["source"], f"{name} phase {i}: no source line")
|
||||
if i:
|
||||
need(p["start_daa_score"] == ph[i - 1]["end_daa_score"], f"{name}: phase {i} does not start where phase {i - 1} ends (a gap or an overlap)")
|
||||
s = amount(p["phase_sum"], f"{name} phase {i}.phase_sum")
|
||||
if p["kind"] == "ramp":
|
||||
need(i == 0, f"{name}: a ramp phase at index {i}")
|
||||
rs = ramp_sum(rr, bps)
|
||||
need(s == rs, f"{name}: the ramp's declared sum {s} is not its formula's {rs} (off by {s - rs})")
|
||||
need(amount(p["ramp"]["full_per_block"], "ramp.full_per_block") == rr["L"] // bps, f"{name}: ramp full_per_block is not launch_rate / bps")
|
||||
elif p["kind"] == "constant":
|
||||
pb = amount(p["per_block_subsidy"], f"{name} phase {i}.per_block_subsidy")
|
||||
need(pb > 0, f"{name}: constant phase {i} pays 0 (that is the terminal)")
|
||||
need(p["end_daa_score"] > p["start_daa_score"], f"{name}: phase {i} is empty")
|
||||
need(s == pb * (p["end_daa_score"] - p["start_daa_score"]), f"{name}: phase {i} sum is not per_block x length")
|
||||
need(prev_pb is None or pb <= prev_pb, f"{name}: phase {i} pays more than phase {i - 1} (issuance rises)")
|
||||
prev_pb = pb
|
||||
elif p["kind"] == "terminal":
|
||||
need(i == len(ph) - 1, f"{name}: a terminal phase before the end")
|
||||
need(p["end_daa_score"] is None, f"{name}: the terminal phase has an end")
|
||||
tb = amount(p["per_block_subsidy"], f"{name} terminal per_block_subsidy")
|
||||
need(tb == 0, f"{name}: the terminal phase pays {tb} base units a block for ever: issuance is unbounded, the overshoot over the cap is infinite")
|
||||
else:
|
||||
raise Refused(f"{name}: phase {i} kind {p['kind']!r}")
|
||||
total += s
|
||||
need(ph[-1]["kind"] == "terminal", f"{name}: the schedule has no terminal phase (issuance is not shown to end)")
|
||||
need(sc["transition_count"] == len(ph) - 1, f"{name}: transition_count is not phases - 1")
|
||||
need(sc["last_paying_daa_score"] == ph[-1]["start_daa_score"] - 1 and sc["last_paying_block_height"] == ph[-1]["start_daa_score"],
|
||||
f"{name}: the last paying DAA score or height disagrees with the terminal phase (genesis is unrewarded: height = DAA score + 1)")
|
||||
need(sc["terminal_supply"] == sc["total_issuance"], f"{name}: terminal_supply is not the total issuance")
|
||||
need(total == amount(sc["total_issuance"], f"{name}.total_issuance"), f"{name}: the phases sum to {total}, the spec says {sc['total_issuance']}")
|
||||
if total > cap:
|
||||
raise Refused(f"{name}: overshoot {total - cap} base units ({(total - cap) / unit:,.6f} IGN) over the cap")
|
||||
short = cap - total
|
||||
declared = amount(sc["cap_shortfall"], f"{name}.cap_shortfall")
|
||||
need(short == declared, f"{name}: total + declared shortfall = {total + declared}, not the cap (off by {declared - short} base units)")
|
||||
if regen:
|
||||
for p in ph:
|
||||
if p["kind"] == "ramp":
|
||||
continue
|
||||
a = p["start_daa_score"]
|
||||
pb = per_block(rr, a, bps)
|
||||
need(str(pb) == p["per_block_subsidy"], f"{name}: phase {p['index']} lists {p['per_block_subsidy']}, the rules give {pb}")
|
||||
if p["end_daa_score"] is not None:
|
||||
need(per_block(rr, p["end_daa_score"] - 1, bps) == pb, f"{name}: phase {p['index']} is not constant under the rules")
|
||||
nb = per_block(rr, p["end_daa_score"], bps)
|
||||
need(nb != pb, f"{name}: phase {p['index']} ends where the rules do not change")
|
||||
out.append(f"{name}: {len(ph)} phases, contiguous from DAA 0, terminal zero from DAA {ph[-1]['start_daa_score']:,}; "
|
||||
f"total {total} + shortfall {short} = cap {cap} exactly (shortfall {short / unit:,.6f} IGN)")
|
||||
return out
|
||||
|
||||
|
||||
def bps_step_case(spec):
|
||||
"""KF4: the schedule as the code would pay it if the block rate stepped from 1 to 10 a second at DAA one year in."""
|
||||
rr = rules_of(spec)
|
||||
cap = int(spec["cap"]["base_units"])
|
||||
y = spec["rules"]["year_seconds"]
|
||||
step = y * 1
|
||||
s10 = int(next(s for s in spec["schedules"] if s["bps"] == 10)["total_issuance"])
|
||||
issued = supply_to(rr, 1, step) + (s10 - supply_to(rr, 10, step))
|
||||
need(issued <= cap, f"a 1-to-10 block-rate step at DAA {step:,} keyed as daa / bps: issuance {issued}, overshoot {issued - cap} base units over the cap")
|
||||
return issued
|
||||
|
||||
|
||||
def main():
|
||||
path = sys.argv[1] if len(sys.argv) > 1 else DEFAULT
|
||||
spec = json.load(open(path))
|
||||
fails = 0
|
||||
|
||||
def known_failed(label, mutate, run=None):
|
||||
nonlocal fails
|
||||
s = copy.deepcopy(spec)
|
||||
mutate(s)
|
||||
try:
|
||||
(run or (lambda x: check(x, regen=False)))(s)
|
||||
except Refused as e:
|
||||
print(f"known-failed {label}: refused as expected: {e}")
|
||||
return
|
||||
print(f"known-failed {label}: NOT refused (the checker is broken)")
|
||||
fails += 1
|
||||
|
||||
def kf1(s):
|
||||
sc = s["schedules"][0]
|
||||
sc["phases"][-1]["per_block_subsidy"] = "1"
|
||||
|
||||
def kf2(s):
|
||||
sc = s["schedules"][0]
|
||||
p = sc["phases"][1]
|
||||
old = int(p["phase_sum"])
|
||||
pb = int(p["per_block_subsidy"]) * 2
|
||||
p["per_block_subsidy"] = str(pb)
|
||||
p["phase_sum"] = str(pb * (p["end_daa_score"] - p["start_daa_score"]))
|
||||
sc["total_issuance"] = str(int(sc["total_issuance"]) - old + int(p["phase_sum"]))
|
||||
sc["cap_shortfall"] = str(int(s["cap"]["base_units"]) - int(sc["total_issuance"])) # may go negative: str(-x) fails too
|
||||
|
||||
def kf2_run(s):
|
||||
sc = s["schedules"][0]
|
||||
if sc["cap_shortfall"].startswith("-"):
|
||||
sc["cap_shortfall"] = "0" # make the field well-formed so the overshoot itself is what refuses it
|
||||
return check(s, regen=False)
|
||||
|
||||
def kf3(s):
|
||||
sc = s["schedules"][0]
|
||||
sc["cap_shortfall"] = str(int(sc["cap_shortfall"]) + 1)
|
||||
|
||||
known_failed("KF1 (a tail: the terminal pays 1 base unit a block for ever)", kf1)
|
||||
known_failed("KF2 (an emergency mint: the first full-rate phase doubled, sums self-consistent)", kf2, kf2_run)
|
||||
known_failed("KF3 (the declared shortfall one base unit off)", kf3)
|
||||
known_failed("KF4 (a 1-to-10 block-rate step one year in, time keyed as daa / bps)", lambda s: None, bps_step_case)
|
||||
|
||||
try:
|
||||
lines = check(spec)
|
||||
except Refused as e:
|
||||
print(f"spec {path}: REFUSED: {e}")
|
||||
return 1
|
||||
for l in lines:
|
||||
print(l)
|
||||
blocked = [c["id"] for c in spec["conflicts"] if c["status"] == "BLOCKED"]
|
||||
print(f"conflicts: {len(spec['conflicts'])} rows, BLOCKED: {', '.join(blocked) or 'none'}; gate TV-01 verdict as written: {spec['verdict']}")
|
||||
if fails:
|
||||
print(f"checker: {fails} known-failed case(s) were not refused; the result above is not trusted")
|
||||
return 2
|
||||
print("check: well-formed and exact (a check result, not a gate verdict)")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
139
tools/token-value/tv-01/code-dump/schedule-bf60948a.jsonl
Normal file
139
tools/token-value/tv-01/code-dump/schedule-bf60948a.jsonl
Normal file
|
|
@ -0,0 +1,139 @@
|
|||
{"network":"mainnet","bps":10,"decimals":18,"launch_rate":"31688087810000000000","ramp_seconds":2592000,"ramp_start_percent":10,"step_seconds":63115200,"step_decay_q32":2147483648,"tail":"cap","steps":66,"genesis_daa_score":1312860}
|
||||
{"network":"mainnet","phase":"ramp","start_daa":0,"end_daa":25920000,"first_block":"316880878100000000","last_block":"3168807680719173263","sum":"45174523722296485488480000"}
|
||||
{"network":"mainnet","phase":"step","k":0,"start_daa":25920000,"end_daa":631152000,"per_block":"3168808781000000000","rate_per_second":"31688087810000000000","sum":"1917864476142192000000000000"}
|
||||
{"network":"mainnet","phase":"step","k":1,"start_daa":631152000,"end_daa":1262304000,"per_block":"1584404390500000000","rate_per_second":"15844043905000000000","sum":"999999999872856000000000000"}
|
||||
{"network":"mainnet","phase":"step","k":2,"start_daa":1262304000,"end_daa":1893456000,"per_block":"792202195250000000","rate_per_second":"7922021952500000000","sum":"499999999936428000000000000"}
|
||||
{"network":"mainnet","phase":"step","k":3,"start_daa":1893456000,"end_daa":2524608000,"per_block":"396101097625000000","rate_per_second":"3961010976250000000","sum":"249999999968214000000000000"}
|
||||
{"network":"mainnet","phase":"step","k":4,"start_daa":2524608000,"end_daa":3155760000,"per_block":"198050548812500000","rate_per_second":"1980505488125000000","sum":"124999999984107000000000000"}
|
||||
{"network":"mainnet","phase":"step","k":5,"start_daa":3155760000,"end_daa":3786912000,"per_block":"99025274406250000","rate_per_second":"990252744062500000","sum":"62499999992053500000000000"}
|
||||
{"network":"mainnet","phase":"step","k":6,"start_daa":3786912000,"end_daa":4418064000,"per_block":"49512637203125000","rate_per_second":"495126372031250000","sum":"31249999996026750000000000"}
|
||||
{"network":"mainnet","phase":"step","k":7,"start_daa":4418064000,"end_daa":5049216000,"per_block":"24756318601562500","rate_per_second":"247563186015625000","sum":"15624999998013375000000000"}
|
||||
{"network":"mainnet","phase":"step","k":8,"start_daa":5049216000,"end_daa":5680368000,"per_block":"12378159300781250","rate_per_second":"123781593007812500","sum":"7812499999006687500000000"}
|
||||
{"network":"mainnet","phase":"step","k":9,"start_daa":5680368000,"end_daa":6311520000,"per_block":"6189079650390625","rate_per_second":"61890796503906250","sum":"3906249999503343750000000"}
|
||||
{"network":"mainnet","phase":"step","k":10,"start_daa":6311520000,"end_daa":6942672000,"per_block":"3094539825195312","rate_per_second":"30945398251953125","sum":"1953124999751671559424000"}
|
||||
{"network":"mainnet","phase":"step","k":11,"start_daa":6942672000,"end_daa":7573824000,"per_block":"1547269912597656","rate_per_second":"15472699125976562","sum":"976562499875835779712000"}
|
||||
{"network":"mainnet","phase":"step","k":12,"start_daa":7573824000,"end_daa":8204976000,"per_block":"773634956298828","rate_per_second":"7736349562988281","sum":"488281249937917889856000"}
|
||||
{"network":"mainnet","phase":"step","k":13,"start_daa":8204976000,"end_daa":8836128000,"per_block":"386817478149414","rate_per_second":"3868174781494140","sum":"244140624968958944928000"}
|
||||
{"network":"mainnet","phase":"step","k":14,"start_daa":8836128000,"end_daa":9467280000,"per_block":"193408739074707","rate_per_second":"1934087390747070","sum":"122070312484479472464000"}
|
||||
{"network":"mainnet","phase":"step","k":15,"start_daa":9467280000,"end_daa":10098432000,"per_block":"96704369537353","rate_per_second":"967043695373535","sum":"61035156242239420656000"}
|
||||
{"network":"mainnet","phase":"step","k":16,"start_daa":10098432000,"end_daa":10729584000,"per_block":"48352184768676","rate_per_second":"483521847686767","sum":"30517578121119394752000"}
|
||||
{"network":"mainnet","phase":"step","k":17,"start_daa":10729584000,"end_daa":11360736000,"per_block":"24176092384338","rate_per_second":"241760923843383","sum":"15258789060559697376000"}
|
||||
{"network":"mainnet","phase":"step","k":18,"start_daa":11360736000,"end_daa":11991888000,"per_block":"12088046192169","rate_per_second":"120880461921691","sum":"7629394530279848688000"}
|
||||
{"network":"mainnet","phase":"step","k":19,"start_daa":11991888000,"end_daa":12623040000,"per_block":"6044023096084","rate_per_second":"60440230960845","sum":"3814697265139608768000"}
|
||||
{"network":"mainnet","phase":"step","k":20,"start_daa":12623040000,"end_daa":13254192000,"per_block":"3022011548042","rate_per_second":"30220115480422","sum":"1907348632569804384000"}
|
||||
{"network":"mainnet","phase":"step","k":21,"start_daa":13254192000,"end_daa":13885344000,"per_block":"1511005774021","rate_per_second":"15110057740211","sum":"953674316284902192000"}
|
||||
{"network":"mainnet","phase":"step","k":22,"start_daa":13885344000,"end_daa":14516496000,"per_block":"755502887010","rate_per_second":"7555028870105","sum":"476837158142135520000"}
|
||||
{"network":"mainnet","phase":"step","k":23,"start_daa":14516496000,"end_daa":15147648000,"per_block":"377751443505","rate_per_second":"3777514435052","sum":"238418579071067760000"}
|
||||
{"network":"mainnet","phase":"step","k":24,"start_daa":15147648000,"end_daa":15778800000,"per_block":"188875721752","rate_per_second":"1888757217526","sum":"119209289535218304000"}
|
||||
{"network":"mainnet","phase":"step","k":25,"start_daa":15778800000,"end_daa":16409952000,"per_block":"94437860876","rate_per_second":"944378608763","sum":"59604644767609152000"}
|
||||
{"network":"mainnet","phase":"step","k":26,"start_daa":16409952000,"end_daa":17041104000,"per_block":"47218930438","rate_per_second":"472189304381","sum":"29802322383804576000"}
|
||||
{"network":"mainnet","phase":"step","k":27,"start_daa":17041104000,"end_daa":17672256000,"per_block":"23609465219","rate_per_second":"236094652190","sum":"14901161191902288000"}
|
||||
{"network":"mainnet","phase":"step","k":28,"start_daa":17672256000,"end_daa":18303408000,"per_block":"11804732609","rate_per_second":"118047326095","sum":"7450580595635568000"}
|
||||
{"network":"mainnet","phase":"step","k":29,"start_daa":18303408000,"end_daa":18934560000,"per_block":"5902366304","rate_per_second":"59023663047","sum":"3725290297502208000"}
|
||||
{"network":"mainnet","phase":"step","k":30,"start_daa":18934560000,"end_daa":19565712000,"per_block":"2951183152","rate_per_second":"29511831523","sum":"1862645148751104000"}
|
||||
{"network":"mainnet","phase":"step","k":31,"start_daa":19565712000,"end_daa":20196864000,"per_block":"1475591576","rate_per_second":"14755915761","sum":"931322574375552000"}
|
||||
{"network":"mainnet","phase":"step","k":32,"start_daa":20196864000,"end_daa":20828016000,"per_block":"737795788","rate_per_second":"7377957880","sum":"465661287187776000"}
|
||||
{"network":"mainnet","phase":"step","k":33,"start_daa":20828016000,"end_daa":21459168000,"per_block":"368897894","rate_per_second":"3688978940","sum":"232830643593888000"}
|
||||
{"network":"mainnet","phase":"step","k":34,"start_daa":21459168000,"end_daa":22090320000,"per_block":"184448947","rate_per_second":"1844489470","sum":"116415321796944000"}
|
||||
{"network":"mainnet","phase":"step","k":35,"start_daa":22090320000,"end_daa":22721472000,"per_block":"92224473","rate_per_second":"922244735","sum":"58207660582896000"}
|
||||
{"network":"mainnet","phase":"step","k":36,"start_daa":22721472000,"end_daa":23352624000,"per_block":"46112236","rate_per_second":"461122367","sum":"29103829975872000"}
|
||||
{"network":"mainnet","phase":"step","k":37,"start_daa":23352624000,"end_daa":23983776000,"per_block":"23056118","rate_per_second":"230561183","sum":"14551914987936000"}
|
||||
{"network":"mainnet","phase":"step","k":38,"start_daa":23983776000,"end_daa":24614928000,"per_block":"11528059","rate_per_second":"115280591","sum":"7275957493968000"}
|
||||
{"network":"mainnet","phase":"step","k":39,"start_daa":24614928000,"end_daa":25246080000,"per_block":"5764029","rate_per_second":"57640295","sum":"3637978431408000"}
|
||||
{"network":"mainnet","phase":"step","k":40,"start_daa":25246080000,"end_daa":25877232000,"per_block":"2882014","rate_per_second":"28820147","sum":"1818988900128000"}
|
||||
{"network":"mainnet","phase":"step","k":41,"start_daa":25877232000,"end_daa":26508384000,"per_block":"1441007","rate_per_second":"14410073","sum":"909494450064000"}
|
||||
{"network":"mainnet","phase":"step","k":42,"start_daa":26508384000,"end_daa":27139536000,"per_block":"720503","rate_per_second":"7205036","sum":"454746909456000"}
|
||||
{"network":"mainnet","phase":"step","k":43,"start_daa":27139536000,"end_daa":27770688000,"per_block":"360251","rate_per_second":"3602518","sum":"227373139152000"}
|
||||
{"network":"mainnet","phase":"step","k":44,"start_daa":27770688000,"end_daa":28401840000,"per_block":"180125","rate_per_second":"1801259","sum":"113686254000000"}
|
||||
{"network":"mainnet","phase":"step","k":45,"start_daa":28401840000,"end_daa":29032992000,"per_block":"90062","rate_per_second":"900629","sum":"56842811424000"}
|
||||
{"network":"mainnet","phase":"step","k":46,"start_daa":29032992000,"end_daa":29664144000,"per_block":"45031","rate_per_second":"450314","sum":"28421405712000"}
|
||||
{"network":"mainnet","phase":"step","k":47,"start_daa":29664144000,"end_daa":30295296000,"per_block":"22515","rate_per_second":"225157","sum":"14210387280000"}
|
||||
{"network":"mainnet","phase":"step","k":48,"start_daa":30295296000,"end_daa":30926448000,"per_block":"11257","rate_per_second":"112578","sum":"7104878064000"}
|
||||
{"network":"mainnet","phase":"step","k":49,"start_daa":30926448000,"end_daa":31557600000,"per_block":"5628","rate_per_second":"56289","sum":"3552123456000"}
|
||||
{"network":"mainnet","phase":"step","k":50,"start_daa":31557600000,"end_daa":32188752000,"per_block":"2814","rate_per_second":"28144","sum":"1776061728000"}
|
||||
{"network":"mainnet","phase":"step","k":51,"start_daa":32188752000,"end_daa":32819904000,"per_block":"1407","rate_per_second":"14072","sum":"888030864000"}
|
||||
{"network":"mainnet","phase":"step","k":52,"start_daa":32819904000,"end_daa":33451056000,"per_block":"703","rate_per_second":"7036","sum":"443699856000"}
|
||||
{"network":"mainnet","phase":"step","k":53,"start_daa":33451056000,"end_daa":34082208000,"per_block":"351","rate_per_second":"3518","sum":"221534352000"}
|
||||
{"network":"mainnet","phase":"step","k":54,"start_daa":34082208000,"end_daa":34713360000,"per_block":"175","rate_per_second":"1759","sum":"110451600000"}
|
||||
{"network":"mainnet","phase":"step","k":55,"start_daa":34713360000,"end_daa":35344512000,"per_block":"87","rate_per_second":"879","sum":"54910224000"}
|
||||
{"network":"mainnet","phase":"step","k":56,"start_daa":35344512000,"end_daa":35975664000,"per_block":"43","rate_per_second":"439","sum":"27139536000"}
|
||||
{"network":"mainnet","phase":"step","k":57,"start_daa":35975664000,"end_daa":36606816000,"per_block":"21","rate_per_second":"219","sum":"13254192000"}
|
||||
{"network":"mainnet","phase":"step","k":58,"start_daa":36606816000,"end_daa":37237968000,"per_block":"10","rate_per_second":"109","sum":"6311520000"}
|
||||
{"network":"mainnet","phase":"step","k":59,"start_daa":37237968000,"end_daa":37869120000,"per_block":"5","rate_per_second":"54","sum":"3155760000"}
|
||||
{"network":"mainnet","phase":"step","k":60,"start_daa":37869120000,"end_daa":38500272000,"per_block":"2","rate_per_second":"27","sum":"1262304000"}
|
||||
{"network":"mainnet","phase":"step","k":61,"start_daa":38500272000,"end_daa":39131424000,"per_block":"1","rate_per_second":"13","sum":"631152000"}
|
||||
{"network":"mainnet","phase":"step","k":62,"start_daa":39131424000,"end_daa":39762576000,"per_block":"0","rate_per_second":"6","sum":"0"}
|
||||
{"network":"mainnet","phase":"terminal","start_daa":39131424000,"per_block":"0"}
|
||||
{"network":"mainnet","total":"3963038999610200467816224000","cap":"4000000000000000000000000000","shortfall":"36961000389799532183776000"}
|
||||
{"network":"devnet-4","bps":1,"decimals":18,"launch_rate":"31688087810000000000","ramp_seconds":2592000,"ramp_start_percent":10,"step_seconds":63115200,"step_decay_q32":2147483648,"tail":"cap","steps":66,"genesis_daa_score":0}
|
||||
{"network":"devnet-4","phase":"ramp","start_daa":0,"end_daa":2592000,"first_block":"3168808781000000000","last_block":"31688076807191732638","sum":"45174523722296485498848000"}
|
||||
{"network":"devnet-4","phase":"step","k":0,"start_daa":2592000,"end_daa":63115200,"per_block":"31688087810000000000","rate_per_second":"31688087810000000000","sum":"1917864476142192000000000000"}
|
||||
{"network":"devnet-4","phase":"step","k":1,"start_daa":63115200,"end_daa":126230400,"per_block":"15844043905000000000","rate_per_second":"15844043905000000000","sum":"999999999872856000000000000"}
|
||||
{"network":"devnet-4","phase":"step","k":2,"start_daa":126230400,"end_daa":189345600,"per_block":"7922021952500000000","rate_per_second":"7922021952500000000","sum":"499999999936428000000000000"}
|
||||
{"network":"devnet-4","phase":"step","k":3,"start_daa":189345600,"end_daa":252460800,"per_block":"3961010976250000000","rate_per_second":"3961010976250000000","sum":"249999999968214000000000000"}
|
||||
{"network":"devnet-4","phase":"step","k":4,"start_daa":252460800,"end_daa":315576000,"per_block":"1980505488125000000","rate_per_second":"1980505488125000000","sum":"124999999984107000000000000"}
|
||||
{"network":"devnet-4","phase":"step","k":5,"start_daa":315576000,"end_daa":378691200,"per_block":"990252744062500000","rate_per_second":"990252744062500000","sum":"62499999992053500000000000"}
|
||||
{"network":"devnet-4","phase":"step","k":6,"start_daa":378691200,"end_daa":441806400,"per_block":"495126372031250000","rate_per_second":"495126372031250000","sum":"31249999996026750000000000"}
|
||||
{"network":"devnet-4","phase":"step","k":7,"start_daa":441806400,"end_daa":504921600,"per_block":"247563186015625000","rate_per_second":"247563186015625000","sum":"15624999998013375000000000"}
|
||||
{"network":"devnet-4","phase":"step","k":8,"start_daa":504921600,"end_daa":568036800,"per_block":"123781593007812500","rate_per_second":"123781593007812500","sum":"7812499999006687500000000"}
|
||||
{"network":"devnet-4","phase":"step","k":9,"start_daa":568036800,"end_daa":631152000,"per_block":"61890796503906250","rate_per_second":"61890796503906250","sum":"3906249999503343750000000"}
|
||||
{"network":"devnet-4","phase":"step","k":10,"start_daa":631152000,"end_daa":694267200,"per_block":"30945398251953125","rate_per_second":"30945398251953125","sum":"1953124999751671875000000"}
|
||||
{"network":"devnet-4","phase":"step","k":11,"start_daa":694267200,"end_daa":757382400,"per_block":"15472699125976562","rate_per_second":"15472699125976562","sum":"976562499875835905942400"}
|
||||
{"network":"devnet-4","phase":"step","k":12,"start_daa":757382400,"end_daa":820497600,"per_block":"7736349562988281","rate_per_second":"7736349562988281","sum":"488281249937917952971200"}
|
||||
{"network":"devnet-4","phase":"step","k":13,"start_daa":820497600,"end_daa":883612800,"per_block":"3868174781494140","rate_per_second":"3868174781494140","sum":"244140624968958944928000"}
|
||||
{"network":"devnet-4","phase":"step","k":14,"start_daa":883612800,"end_daa":946728000,"per_block":"1934087390747070","rate_per_second":"1934087390747070","sum":"122070312484479472464000"}
|
||||
{"network":"devnet-4","phase":"step","k":15,"start_daa":946728000,"end_daa":1009843200,"per_block":"967043695373535","rate_per_second":"967043695373535","sum":"61035156242239736232000"}
|
||||
{"network":"devnet-4","phase":"step","k":16,"start_daa":1009843200,"end_daa":1072958400,"per_block":"483521847686767","rate_per_second":"483521847686767","sum":"30517578121119836558400"}
|
||||
{"network":"devnet-4","phase":"step","k":17,"start_daa":1072958400,"end_daa":1136073600,"per_block":"241760923843383","rate_per_second":"241760923843383","sum":"15258789060559886721600"}
|
||||
{"network":"devnet-4","phase":"step","k":18,"start_daa":1136073600,"end_daa":1199188800,"per_block":"120880461921691","rate_per_second":"120880461921691","sum":"7629394530279911803200"}
|
||||
{"network":"devnet-4","phase":"step","k":19,"start_daa":1199188800,"end_daa":1262304000,"per_block":"60440230960845","rate_per_second":"60440230960845","sum":"3814697265139924344000"}
|
||||
{"network":"devnet-4","phase":"step","k":20,"start_daa":1262304000,"end_daa":1325419200,"per_block":"30220115480422","rate_per_second":"30220115480422","sum":"1907348632569930614400"}
|
||||
{"network":"devnet-4","phase":"step","k":21,"start_daa":1325419200,"end_daa":1388534400,"per_block":"15110057740211","rate_per_second":"15110057740211","sum":"953674316284965307200"}
|
||||
{"network":"devnet-4","phase":"step","k":22,"start_daa":1388534400,"end_daa":1451649600,"per_block":"7555028870105","rate_per_second":"7555028870105","sum":"476837158142451096000"}
|
||||
{"network":"devnet-4","phase":"step","k":23,"start_daa":1451649600,"end_daa":1514764800,"per_block":"3777514435052","rate_per_second":"3777514435052","sum":"238418579071193990400"}
|
||||
{"network":"devnet-4","phase":"step","k":24,"start_daa":1514764800,"end_daa":1577880000,"per_block":"1888757217526","rate_per_second":"1888757217526","sum":"119209289535596995200"}
|
||||
{"network":"devnet-4","phase":"step","k":25,"start_daa":1577880000,"end_daa":1640995200,"per_block":"944378608763","rate_per_second":"944378608763","sum":"59604644767798497600"}
|
||||
{"network":"devnet-4","phase":"step","k":26,"start_daa":1640995200,"end_daa":1704110400,"per_block":"472189304381","rate_per_second":"472189304381","sum":"29802322383867691200"}
|
||||
{"network":"devnet-4","phase":"step","k":27,"start_daa":1704110400,"end_daa":1767225600,"per_block":"236094652190","rate_per_second":"236094652190","sum":"14901161191902288000"}
|
||||
{"network":"devnet-4","phase":"step","k":28,"start_daa":1767225600,"end_daa":1830340800,"per_block":"118047326095","rate_per_second":"118047326095","sum":"7450580595951144000"}
|
||||
{"network":"devnet-4","phase":"step","k":29,"start_daa":1830340800,"end_daa":1893456000,"per_block":"59023663047","rate_per_second":"59023663047","sum":"3725290297944014400"}
|
||||
{"network":"devnet-4","phase":"step","k":30,"start_daa":1893456000,"end_daa":1956571200,"per_block":"29511831523","rate_per_second":"29511831523","sum":"1862645148940449600"}
|
||||
{"network":"devnet-4","phase":"step","k":31,"start_daa":1956571200,"end_daa":2019686400,"per_block":"14755915761","rate_per_second":"14755915761","sum":"931322574438667200"}
|
||||
{"network":"devnet-4","phase":"step","k":32,"start_daa":2019686400,"end_daa":2082801600,"per_block":"7377957880","rate_per_second":"7377957880","sum":"465661287187776000"}
|
||||
{"network":"devnet-4","phase":"step","k":33,"start_daa":2082801600,"end_daa":2145916800,"per_block":"3688978940","rate_per_second":"3688978940","sum":"232830643593888000"}
|
||||
{"network":"devnet-4","phase":"step","k":34,"start_daa":2145916800,"end_daa":2209032000,"per_block":"1844489470","rate_per_second":"1844489470","sum":"116415321796944000"}
|
||||
{"network":"devnet-4","phase":"step","k":35,"start_daa":2209032000,"end_daa":2272147200,"per_block":"922244735","rate_per_second":"922244735","sum":"58207660898472000"}
|
||||
{"network":"devnet-4","phase":"step","k":36,"start_daa":2272147200,"end_daa":2335262400,"per_block":"461122367","rate_per_second":"461122367","sum":"29103830417678400"}
|
||||
{"network":"devnet-4","phase":"step","k":37,"start_daa":2335262400,"end_daa":2398377600,"per_block":"230561183","rate_per_second":"230561183","sum":"14551915177281600"}
|
||||
{"network":"devnet-4","phase":"step","k":38,"start_daa":2398377600,"end_daa":2461492800,"per_block":"115280591","rate_per_second":"115280591","sum":"7275957557083200"}
|
||||
{"network":"devnet-4","phase":"step","k":39,"start_daa":2461492800,"end_daa":2524608000,"per_block":"57640295","rate_per_second":"57640295","sum":"3637978746984000"}
|
||||
{"network":"devnet-4","phase":"step","k":40,"start_daa":2524608000,"end_daa":2587723200,"per_block":"28820147","rate_per_second":"28820147","sum":"1818989341934400"}
|
||||
{"network":"devnet-4","phase":"step","k":41,"start_daa":2587723200,"end_daa":2650838400,"per_block":"14410073","rate_per_second":"14410073","sum":"909494639409600"}
|
||||
{"network":"devnet-4","phase":"step","k":42,"start_daa":2650838400,"end_daa":2713953600,"per_block":"7205036","rate_per_second":"7205036","sum":"454747288147200"}
|
||||
{"network":"devnet-4","phase":"step","k":43,"start_daa":2713953600,"end_daa":2777068800,"per_block":"3602518","rate_per_second":"3602518","sum":"227373644073600"}
|
||||
{"network":"devnet-4","phase":"step","k":44,"start_daa":2777068800,"end_daa":2840184000,"per_block":"1801259","rate_per_second":"1801259","sum":"113686822036800"}
|
||||
{"network":"devnet-4","phase":"step","k":45,"start_daa":2840184000,"end_daa":2903299200,"per_block":"900629","rate_per_second":"900629","sum":"56843379460800"}
|
||||
{"network":"devnet-4","phase":"step","k":46,"start_daa":2903299200,"end_daa":2966414400,"per_block":"450314","rate_per_second":"450314","sum":"28421658172800"}
|
||||
{"network":"devnet-4","phase":"step","k":47,"start_daa":2966414400,"end_daa":3029529600,"per_block":"225157","rate_per_second":"225157","sum":"14210829086400"}
|
||||
{"network":"devnet-4","phase":"step","k":48,"start_daa":3029529600,"end_daa":3092644800,"per_block":"112578","rate_per_second":"112578","sum":"7105382985600"}
|
||||
{"network":"devnet-4","phase":"step","k":49,"start_daa":3092644800,"end_daa":3155760000,"per_block":"56289","rate_per_second":"56289","sum":"3552691492800"}
|
||||
{"network":"devnet-4","phase":"step","k":50,"start_daa":3155760000,"end_daa":3218875200,"per_block":"28144","rate_per_second":"28144","sum":"1776314188800"}
|
||||
{"network":"devnet-4","phase":"step","k":51,"start_daa":3218875200,"end_daa":3281990400,"per_block":"14072","rate_per_second":"14072","sum":"888157094400"}
|
||||
{"network":"devnet-4","phase":"step","k":52,"start_daa":3281990400,"end_daa":3345105600,"per_block":"7036","rate_per_second":"7036","sum":"444078547200"}
|
||||
{"network":"devnet-4","phase":"step","k":53,"start_daa":3345105600,"end_daa":3408220800,"per_block":"3518","rate_per_second":"3518","sum":"222039273600"}
|
||||
{"network":"devnet-4","phase":"step","k":54,"start_daa":3408220800,"end_daa":3471336000,"per_block":"1759","rate_per_second":"1759","sum":"111019636800"}
|
||||
{"network":"devnet-4","phase":"step","k":55,"start_daa":3471336000,"end_daa":3534451200,"per_block":"879","rate_per_second":"879","sum":"55478260800"}
|
||||
{"network":"devnet-4","phase":"step","k":56,"start_daa":3534451200,"end_daa":3597566400,"per_block":"439","rate_per_second":"439","sum":"27707572800"}
|
||||
{"network":"devnet-4","phase":"step","k":57,"start_daa":3597566400,"end_daa":3660681600,"per_block":"219","rate_per_second":"219","sum":"13822228800"}
|
||||
{"network":"devnet-4","phase":"step","k":58,"start_daa":3660681600,"end_daa":3723796800,"per_block":"109","rate_per_second":"109","sum":"6879556800"}
|
||||
{"network":"devnet-4","phase":"step","k":59,"start_daa":3723796800,"end_daa":3786912000,"per_block":"54","rate_per_second":"54","sum":"3408220800"}
|
||||
{"network":"devnet-4","phase":"step","k":60,"start_daa":3786912000,"end_daa":3850027200,"per_block":"27","rate_per_second":"27","sum":"1704110400"}
|
||||
{"network":"devnet-4","phase":"step","k":61,"start_daa":3850027200,"end_daa":3913142400,"per_block":"13","rate_per_second":"13","sum":"820497600"}
|
||||
{"network":"devnet-4","phase":"step","k":62,"start_daa":3913142400,"end_daa":3976257600,"per_block":"6","rate_per_second":"6","sum":"378691200"}
|
||||
{"network":"devnet-4","phase":"step","k":63,"start_daa":3976257600,"end_daa":4039372800,"per_block":"3","rate_per_second":"3","sum":"189345600"}
|
||||
{"network":"devnet-4","phase":"step","k":64,"start_daa":4039372800,"end_daa":4102488000,"per_block":"1","rate_per_second":"1","sum":"63115200"}
|
||||
{"network":"devnet-4","phase":"step","k":65,"start_daa":4102488000,"end_daa":4165603200,"per_block":"0","rate_per_second":"0","sum":"0"}
|
||||
{"network":"devnet-4","phase":"terminal","start_daa":4102488000,"per_block":"0"}
|
||||
{"network":"devnet-4","total":"3963038999610200483289816000","cap":"4000000000000000000000000000","shortfall":"36961000389799516710184000"}
|
||||
{"network":"testnet-1","bps":1,"decimals":18,"launch_rate":"100000000000000000000","ramp_seconds":7776000,"ramp_start_percent":10,"step_seconds":2629800,"step_decay_q32":4172697914,"tail":"percent:100","steps":125,"genesis_daa_score":0}
|
||||
{"network":"testnet-1","note":"not a cap schedule; phases not dumped"}
|
||||
60
tools/token-value/tv-01/code-dump/tv01_schedule.rs.txt
Normal file
60
tools/token-value/tv-01/code-dump/tv01_schedule.rs.txt
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
//! TV-01 (token value phase 0, 9 October 2026): the per-block subsidy schedule of each compiled network, read from the
|
||||
//! consensus code's own functions (`Params::emission`, `EmissionTable::block_subsidy`, `Params::bps`), as JSON lines.
|
||||
//! Lane scratch file on build-9 only; not part of the node fork.
|
||||
use kaspa_consensus_core::config::params::{devnet4_params, Params, MAINNET_PARAMS, TESTNET_PARAMS};
|
||||
use kaspa_consensus_core::emission::Tail;
|
||||
|
||||
fn dump(name: &str, p: &Params) {
|
||||
let t = p.emission.table();
|
||||
let s = p.emission;
|
||||
let bps = p.bps();
|
||||
let tail = match s.tail { Tail::Cap => "cap".to_string(), Tail::Fixed { rate } => format!("fixed:{rate}"), Tail::Percent { bps_per_year } => format!("percent:{bps_per_year}") };
|
||||
println!(
|
||||
"{{\"network\":\"{name}\",\"bps\":{bps},\"decimals\":{},\"launch_rate\":\"{}\",\"ramp_seconds\":{},\"ramp_start_percent\":{},\"step_seconds\":{},\"step_decay_q32\":{},\"tail\":\"{tail}\",\"steps\":{},\"genesis_daa_score\":{}}}",
|
||||
p.base_unit_decimals, s.launch_rate, s.ramp_seconds, s.ramp_start_percent, s.step_seconds, s.step_decay_q32, t.step_rate.len(), p.genesis.daa_score
|
||||
);
|
||||
if s.tail != Tail::Cap {
|
||||
println!("{{\"network\":\"{name}\",\"note\":\"not a cap schedule; phases not dumped\"}}");
|
||||
return;
|
||||
}
|
||||
// the ramp: every block of DAA second sec pays block_subsidy(sec * bps); sum per DAA score
|
||||
let mut ramp_sum: u128 = 0;
|
||||
for sec in 0..s.ramp_seconds {
|
||||
let a = t.block_subsidy(sec * bps, bps);
|
||||
let b = t.block_subsidy(sec * bps + bps - 1, bps);
|
||||
assert_eq!(a, b, "constant inside a DAA second");
|
||||
ramp_sum += a * bps as u128;
|
||||
}
|
||||
let first = t.block_subsidy(0, bps);
|
||||
let last_ramp = t.block_subsidy(s.ramp_seconds * bps - 1, bps);
|
||||
println!("{{\"network\":\"{name}\",\"phase\":\"ramp\",\"start_daa\":0,\"end_daa\":{},\"first_block\":\"{first}\",\"last_block\":\"{last_ramp}\",\"sum\":\"{ramp_sum}\"}}", s.ramp_seconds * bps);
|
||||
let mut total = ramp_sum;
|
||||
let mut k: u64 = 0;
|
||||
loop {
|
||||
let start = if k == 0 { s.ramp_seconds * bps } else { k * s.step_seconds * bps };
|
||||
let end = (k + 1) * s.step_seconds * bps;
|
||||
let per = t.block_subsidy(start, bps);
|
||||
let per_end = t.block_subsidy(end - 1, bps);
|
||||
assert_eq!(per, per_end, "constant inside step {k}");
|
||||
let n = (end - start) as u128;
|
||||
total += per * n;
|
||||
println!("{{\"network\":\"{name}\",\"phase\":\"step\",\"k\":{k},\"start_daa\":{start},\"end_daa\":{end},\"per_block\":\"{per}\",\"rate_per_second\":\"{}\",\"sum\":\"{}\"}}", t.rate_at(start / bps), per * n);
|
||||
if per == 0 {
|
||||
// the terminal: zero at every later DAA score the table can reach
|
||||
for probe in [end, end * 2, u64::MAX / bps * bps - 1, u64::MAX] {
|
||||
assert_eq!(t.block_subsidy(probe, bps), 0);
|
||||
}
|
||||
println!("{{\"network\":\"{name}\",\"phase\":\"terminal\",\"start_daa\":{start},\"per_block\":\"0\"}}");
|
||||
break;
|
||||
}
|
||||
k += 1;
|
||||
}
|
||||
let cap = kaspa_consensus_core::igneum::supply_cap_units(p.base_unit_decimals);
|
||||
println!("{{\"network\":\"{name}\",\"total\":\"{total}\",\"cap\":\"{cap}\",\"shortfall\":\"{}\"}}", cap - total);
|
||||
}
|
||||
|
||||
fn main() {
|
||||
dump("mainnet", &MAINNET_PARAMS);
|
||||
dump("devnet-4", &devnet4_params());
|
||||
dump("testnet-1", &TESTNET_PARAMS);
|
||||
}
|
||||
613
tools/token-value/tv-01/gen-spec.py
Normal file
613
tools/token-value/tv-01/gen-spec.py
Normal file
|
|
@ -0,0 +1,613 @@
|
|||
#!/usr/bin/env python3
|
||||
"""TV-01 (Igneum 2.0 Token Value, Phase 0): generates the machine-readable supply spec.
|
||||
|
||||
Reads nothing it does not cite. The schedule constants are transcribed from the node fork's consensus crate and every
|
||||
transcription is anchored: for each cited line the generator reads the file at each named revision (git show) and
|
||||
refuses to write the spec when the line no longer carries its anchor text. The schedule is then computed here in exact
|
||||
integers (Python ints), and, when --code-dump is given, compared field by field with the dump the consensus code's own
|
||||
functions printed on the build box (tools/token-value/tv-01/code-dump/tv01_schedule.rs.txt), so every figure in the spec is
|
||||
one the code agrees with.
|
||||
|
||||
python3 tools/token-value/tv-01/gen-spec.py \
|
||||
--node-git <igneum-node clone holding bf60948a and a284380b> \
|
||||
--igneum-root . --igneum-rev 6824d49cc32def4463b643293a5622545618f7a9 \
|
||||
--code-dump tools/token-value/tv-01/code-dump/schedule-bf60948a.jsonl \
|
||||
--out docs/plans/igneum-2.0-master/token-value/phase0/tv-01/supply-spec.json \
|
||||
--md docs/plans/igneum-2.0-master/token-value/phase0/tv-01/supply-spec.md
|
||||
|
||||
Deterministic: the same inputs give the same bytes. Nothing here activates anything.
|
||||
"""
|
||||
import argparse
|
||||
import hashlib
|
||||
import json
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
NODE_REVS = {
|
||||
"release-2.0.2-node": "a284380bdd62d5d0d972cb38f2cbd1daeedda69d",
|
||||
"node 2.0.2 shipped (detached-5d53a591)": "5d53a591469b3f2b31eb87ec793faa582e95cb18",
|
||||
"release-2.0.3-node-k6": "bf60948a01467bb54eaab9e50d32b91058f11242",
|
||||
"release-2.0.3-node-k7": "27f54124375ca0901ea0ead30eb4773413f6602f",
|
||||
}
|
||||
# the litepaper revision the tail-vote sentence is quoted from (before the site lane's D01 edit, ad64e48fa)
|
||||
LITEPAPER_SUPERSEDED_REV = "6824d49cc32def4463b643293a5622545618f7a9"
|
||||
|
||||
# ---- The constants, as the code writes them (each one anchored below) -------------------------------------------
|
||||
YEAR_SECONDS = 31_557_600 # emission.rs:32
|
||||
LAUNCH_RATE_8 = 3_168_808_781 # emission.rs:104 (10^17 / 31,557,600 floored)
|
||||
RAMP_SECONDS = 2_592_000 # emission.rs:105
|
||||
RAMP_START_PERCENT = 10 # emission.rs:106
|
||||
STEP_SECONDS = 2 * YEAR_SECONDS # emission.rs:107
|
||||
STEP_DECAY_Q32 = 1 << 31 # emission.rs:108
|
||||
DECIMALS = 18 # unit.rs:38, params.rs:1816
|
||||
UNIT = 10 ** DECIMALS
|
||||
LAUNCH_RATE = LAUNCH_RATE_8 * 10 ** (DECIMALS - 8) # emission.rs rescaled(8, 18); params.rs:1858
|
||||
CAP = 4_000_000_000 * UNIT # igneum.rs:154 supply_cap_units(18)
|
||||
PROVING_POOL_SHARE_PERCENT = 20 # igneum.rs:44
|
||||
SIGNING_BONUS_BPS = 1_000 # params.rs:1810
|
||||
DEVELOPER_SHARE_PERCENT = 20 # exec config (pgas.rs:290 reads it)
|
||||
MAINNET_BPS = 10 # params.rs:1789 BlockrateParams::new::<10>()
|
||||
DEVNET4_BPS = 1 # params.rs:2208 (DEVNET_PARAMS, inherited by devnet3/devnet4)
|
||||
MAINNET_GENESIS_DAA_COMPILED = 1_312_860 # genesis.rs:92
|
||||
|
||||
# (id, repo, path, line, anchor text the line must contain)
|
||||
CITATIONS = [
|
||||
("year", "node", "consensus/core/src/emission.rs", 32, "pub const YEAR_SECONDS: u64 = 31_557_600;"),
|
||||
("current", "node", "consensus/core/src/emission.rs", 103, "pub const CURRENT: EmissionSchedule"),
|
||||
("launch_rate", "node", "consensus/core/src/emission.rs", 104, "launch_rate: 3_168_808_781"),
|
||||
("ramp_seconds", "node", "consensus/core/src/emission.rs", 105, "ramp_seconds: 2_592_000"),
|
||||
("ramp_start", "node", "consensus/core/src/emission.rs", 106, "ramp_start_percent: 10"),
|
||||
("step_seconds", "node", "consensus/core/src/emission.rs", 107, "step_seconds: 2 * YEAR_SECONDS"),
|
||||
("decay", "node", "consensus/core/src/emission.rs", 108, "step_decay_q32: 1 << 31"),
|
||||
("tail_cap", "node", "consensus/core/src/emission.rs", 109, "tail: Tail::Cap"),
|
||||
("testnet1", "node", "consensus/core/src/emission.rs", 116, "pub const TESTNET_1: EmissionSchedule"),
|
||||
("testnet1_tail", "node", "consensus/core/src/emission.rs", 122, "tail: Tail::Percent { bps_per_year: 100 }"),
|
||||
("decay_rule", "node", "consensus/core/src/emission.rs", 225, "rate = rate.saturating_mul(s.step_decay_q32 as u128) >> 32;"),
|
||||
("ramp_fn", "node", "consensus/core/src/emission.rs", 316, "pub fn ramp(&self, full: u128, seconds: u64) -> u128"),
|
||||
("block_subsidy", "node", "consensus/core/src/emission.rs", 329, "pub fn block_subsidy(&self, daa_score: u64, bps: u64) -> u128"),
|
||||
("per_block_div", "node", "consensus/core/src/emission.rs", 331, "let per_block = self.rate_at(seconds) / bps as u128;"),
|
||||
("cap_const", "node", "consensus/core/src/igneum.rs", 37, "pub const SUPPLY_CAP_SOMPI: u64 = 4_000_000_000 * SOMPI_PER_KASPA;"),
|
||||
("pool_share", "node", "consensus/core/src/igneum.rs", 44, "pub const PROVING_POOL_SHARE_PERCENT: u64 = 20;"),
|
||||
("silent_split", "node", "consensus/core/src/igneum.rs", 110, "pub fn silent_split_units("),
|
||||
("cap_units", "node", "consensus/core/src/igneum.rs", 154, "pub const fn supply_cap_units(decimals: u8) -> u128"),
|
||||
("pool_units", "node", "consensus/core/src/igneum.rs", 201, "pub fn proving_pool_share_units("),
|
||||
("producer_units", "node", "consensus/core/src/igneum.rs", 206, "pub fn producer_share_units("),
|
||||
("pool_script", "node", "consensus/core/src/igneum.rs", 213, "pub fn proving_pool_script_public_key()"),
|
||||
("evm_decimals", "node", "consensus/core/src/unit.rs", 38, "pub const EVM_DECIMALS: u8 = 18;"),
|
||||
("mainnet_deflationary", "node", "consensus/core/src/config/params.rs", 1783, "deflationary_phase_daa_score: 15778800 - 259200,"),
|
||||
("mainnet_pre_deflationary", "node", "consensus/core/src/config/params.rs", 1784, "pre_deflationary_phase_base_subsidy: 50000000000,"),
|
||||
("mainnet_bps", "node", "consensus/core/src/config/params.rs", 1789, "blockrate: BlockrateParams::new::<10>(),"),
|
||||
("mainnet_bonus_off", "node", "consensus/core/src/config/params.rs", 1809, "signing_bonus_activation_daa: u64::MAX,"),
|
||||
("mainnet_bonus_bps", "node", "consensus/core/src/config/params.rs", 1810, "signing_bonus_bps: 1_000,"),
|
||||
("mainnet_n8_off", "node", "consensus/core/src/config/params.rs", 1812, "subsidy_per_block_activation_daa: u64::MAX,"),
|
||||
("mainnet_decimals", "node", "consensus/core/src/config/params.rs", 1816, "base_unit_decimals: crate::unit::EVM_DECIMALS"),
|
||||
("mainnet_pool_split_off", "node", "consensus/core/src/config/params.rs", 1851, "pool_split_activation_daa: u64::MAX,"),
|
||||
("mainnet_emission", "node", "consensus/core/src/config/params.rs", 1858, "emission: EmissionSchedule::CURRENT.rescaled(crate::unit::DEVNET_DECIMALS, crate::unit::EVM_DECIMALS),"),
|
||||
("testnet_bonus_on", "node", "consensus/core/src/config/params.rs", 1955, "signing_bonus_activation_daa: 0,"),
|
||||
("testnet_n8_on", "node", "consensus/core/src/config/params.rs", 1960, "subsidy_per_block_activation_daa: 0,"),
|
||||
("testnet_emission", "node", "consensus/core/src/config/params.rs", 2014, "emission: EmissionSchedule::TESTNET_1.rescaled("),
|
||||
("devnet_bps", "node", "consensus/core/src/config/params.rs", 2208, "blockrate: BlockrateParams::new::<1>(),"),
|
||||
("devnet_n8_off", "node", "consensus/core/src/config/params.rs", 2232, "subsidy_per_block_activation_daa: u64::MAX,"),
|
||||
("devnet_pool_split_off", "node", "consensus/core/src/config/params.rs", 2270, "pool_split_activation_daa: u64::MAX,"),
|
||||
("devnet4_fn", "node", "consensus/core/src/config/params.rs", 2342, "pub fn devnet4_params() -> Params {"),
|
||||
("devnet4_emission", "node", "consensus/core/src/config/params.rs", 2372, "emission: EmissionSchedule::CURRENT.rescaled(crate::unit::DEVNET_DECIMALS, crate::unit::EVM_DECIMALS),"),
|
||||
("mainnet_genesis_daa", "node", "consensus/core/src/config/genesis.rs", 92, "daa_score: 1312860, // Checkpoint DAA score"),
|
||||
("genesis_no_outputs", "node", "consensus/core/src/config/genesis.rs", 26, "Transaction::new(0, Vec::new(), Vec::new(), 0, SUBNETWORK_ID_COINBASE"),
|
||||
("cb_header", "node", "consensus/src/processes/coinbase.rs", 31, "Kaspa's pre-deflationary phase, 426-month table and Crescendo `bps_history` rescaling are gone."),
|
||||
("cb_split", "node", "consensus/src/processes/coinbase.rs", 106, "fn producer_and_pool(&self, daa_score: u64, reward: &BlockRewardData)"),
|
||||
("cb_reds", "node", "consensus/src/processes/coinbase.rs", 152, "for red in ghostdag_data.mergeset_reds.iter()"),
|
||||
("cb_pool_output", "node", "consensus/src/processes/coinbase.rs", 163, "outputs.push(TransactionOutput::new_units(proving_pool_reward, igneum::proving_pool_script_public_key()));"),
|
||||
("cb_calc", "node", "consensus/src/processes/coinbase.rs", 262, "pub fn calc_block_subsidy(&self, daa_score: u64) -> Amount"),
|
||||
("cb_calc_body", "node", "consensus/src/processes/coinbase.rs", 265, "Amount(self.emission.block_subsidy(daa_score, self.bps))"),
|
||||
("genesis_not_in_window", "node", "consensus/src/processes/window.rs", 177, "Special case: Genesis does not enter the DAA window"),
|
||||
("cb_daa_blues_only", "node", "consensus/src/processes/coinbase.rs", 139, "for blue in ghostdag_data.mergeset_blues.iter().filter(|h| !mergeset_non_daa.contains(h))"),
|
||||
("utxo_retired", "node", "consensus/src/pipeline/body_processor/body_validation_in_isolation.rs", 54, "RuleError::UtxoTransactionsRetired"),
|
||||
("exec_blue_only", "node", "igneum/exec/src/executor.rs", 181, "for b in blocks.iter().filter(|b| b.is_blue)"),
|
||||
("exec_split", "node", "igneum/exec/src/executor.rs", 189, "silent_split_units(subsidy, b.silent, env.signing_bonus_bps)"),
|
||||
("exec_miner_credit", "node", "igneum/exec/src/executor.rs", 208, "db.add_balance(b.miner, producer_wei);"),
|
||||
("exec_pool_credit", "node", "igneum/exec/src/executor.rs", 211, "db.add_balance(PROVING_POOL_ADDRESS, pool_wei);"),
|
||||
("exec_tip_miner", "node", "igneum/exec/src/executor.rs", 362, "let tip_total = (gas_used as u128) * (price - env.base_fee_exec);"),
|
||||
("exec_dev_shares", "node", "igneum/exec/src/pgas.rs", 290, "pub fn developer_shares("),
|
||||
("pool_address", "node", "igneum/exec/src/config.rs", 59, "pub const PROVING_POOL_ADDRESS: Address"),
|
||||
("override_refused", "node", "kaspad/src/daemon.rs", None, "if network.params_compiled() {"),
|
||||
("params_compiled", "node", "consensus/core/src/network.rs", 266, "NetworkType::Mainnet | NetworkType::Testnet => true,"),
|
||||
("lp_cap", "igneum", "site/litepaper.html", 578, "Hard cap of 4 billion IGN, approached and never reached, because emission starts at 1 billion a year and halves every two years for ever"),
|
||||
("lp_caption", "igneum", "site/litepaper.html", 602, "3,938M IGN in the first 12 years of a 4,000M cap."),
|
||||
("lp_proving_pool", "igneum", "site/litepaper.html", 609, "is burned there"),
|
||||
("lp_tail", "igneum-old", "site/litepaper.html", 630, "One rule sits beside the cap. If external proving revenue is under one fifth of the block subsidy over any 90-day window after year 5, the question of a tail reward goes to the miners' signalling vote. The protocol never changes emission by itself."),
|
||||
("lp_tail_now", "igneum", "site/litepaper.html", 630, "the answer is the security-funding plan under D01, never a tail emission or a vote that expands the cap."),
|
||||
("lp_cap_now", "igneum", "site/litepaper.html", 630, "The cap is fixed at 4 billion, with no tail emission and no vote that expands it (D01, subject to the pre-launch security-funding gate)."),
|
||||
("lp_tip_prose", "igneum", "site/litepaper.html", 614, "The priority fee splits two ways: 80% to the miner and provers of that block, 20% to the apps whose code ran"),
|
||||
("ledger_e22", "igneum", "docs/plans/ledger-decisions.md", 110, "The testnet genesis carries `EmissionSchedule::TESTNET_1`"),
|
||||
("lp_block_rate", "igneum", "site/litepaper.html", 542, "one block a second at launch with scheduled steps to four and ten"),
|
||||
("lp_unit", "igneum", "site/litepaper.html", 568, "the base unit is Open"),
|
||||
("lp_emission_route", "igneum", "site/litepaper.html", 620, "80% the block's miner, 20% the proving pool for the provers of that block"),
|
||||
("lp_tip_route", "igneum", "site/litepaper.html", 622, "80% the block's miner and provers; 20% the apps whose code ran, per call frame"),
|
||||
("lp_external_job", "igneum", "site/litepaper.html", 624, "90% the provers who delivered"),
|
||||
("lp_dev_fee", "igneum", "site/litepaper.html", 625, "1 block template in 100 requested with the dev address"),
|
||||
("lp_pool_custody", "igneum", "site/litepaper.html", 647, "The reference pool never holds a member"),
|
||||
("lp_per_block_figure", "igneum", "site/litepaper.html", 668, "31.688 IGN a block, 86,400 blocks a day"),
|
||||
("lp_mainnet_launch", "igneum", "site/litepaper.html", 775, "Genesis with no premine, 30-day ramp."),
|
||||
("spec_tip_no_provers", "igneum", "docs/spec/05-fees-and-economics.md", 24, "No part of the tip reaches the provers: O-5.7 is closed at zero (8 October 2026"),
|
||||
("spec_tip_table_stale", "igneum", "docs/spec/05-fees-and-economics.md", 79, "80 / 20 (producer and provers / developer)"),
|
||||
("pool_payout_doc", "igneum", "pool/src/payout.rs", 3, "so the pool receives"),
|
||||
("pool_fee_default", "igneum", "pool/src/config.rs", 58, "fee_percent: 1.0,"),
|
||||
("pool_distribute", "igneum", "pool/src/pplns.rs", 76, "pub fn distribute(reward_wei: u128, fee_percent: f64, payees: &[Payee])"),
|
||||
]
|
||||
|
||||
|
||||
def sh(cmd, cwd):
|
||||
return subprocess.run(cmd, cwd=cwd, check=True, capture_output=True).stdout
|
||||
|
||||
|
||||
def read_node(node_git, rev, path):
|
||||
return sh(["git", "show", f"{rev}:{path}"], node_git)
|
||||
|
||||
|
||||
def read_igneum(root, rev, path):
|
||||
return sh(["git", "show", f"{rev}:{path}"], root)
|
||||
|
||||
|
||||
def anchor(citations, node_git, igneum_root, igneum_rev):
|
||||
"""Every cited line must carry its anchor at every named revision; returns the resolved citation table and the
|
||||
sha256 of every cited file per revision."""
|
||||
out, files = [], {}
|
||||
for cid, repo, path, line, text in citations:
|
||||
revs = NODE_REVS.items() if repo == "node" else [("igneum master", igneum_rev)] if repo == "igneum" else [("igneum before ad64e48fa", LITEPAPER_SUPERSEDED_REV)]
|
||||
lines_at = {}
|
||||
for name, rev in revs:
|
||||
blob = read_node(node_git, rev, path) if repo == "node" else read_igneum(igneum_root, rev, path)
|
||||
files.setdefault(f"{repo}:{path}", {})[name] = hashlib.sha256(blob).hexdigest()
|
||||
src = blob.decode("utf-8").split("\n")
|
||||
if line is None:
|
||||
hits = [i + 1 for i, l in enumerate(src) if text in l]
|
||||
if len(hits) != 1:
|
||||
sys.exit(f"anchor {cid}: {path} at {name} has {len(hits)} lines carrying {text!r}")
|
||||
lines_at[name] = hits[0]
|
||||
else:
|
||||
if line > len(src) or text not in src[line - 1]:
|
||||
sys.exit(f"anchor {cid}: {path}:{line} at {name} no longer carries {text!r}")
|
||||
lines_at[name] = line
|
||||
ref_lines = sorted(set(lines_at.values()))
|
||||
out.append({"id": cid, "repo": repo, "path": path, "line": ref_lines[0] if len(ref_lines) == 1 else lines_at,
|
||||
"anchor": text})
|
||||
return out, files
|
||||
|
||||
|
||||
# ---- The schedule, in exact integers ---------------------------------------------------------------------------
|
||||
def step_rate(k):
|
||||
r = LAUNCH_RATE
|
||||
for _ in range(k):
|
||||
r = (r * STEP_DECAY_Q32) >> 32
|
||||
return r
|
||||
|
||||
|
||||
def ramp(full, seconds):
|
||||
if seconds >= RAMP_SECONDS:
|
||||
return full
|
||||
num = RAMP_START_PERCENT * RAMP_SECONDS + (100 - RAMP_START_PERCENT) * seconds
|
||||
return full * num // (100 * RAMP_SECONDS)
|
||||
|
||||
|
||||
def block_subsidy(daa, bps):
|
||||
seconds = daa // bps
|
||||
return ramp(step_rate(seconds // STEP_SECONDS) // bps, seconds)
|
||||
|
||||
|
||||
def ramp_sum(bps, upto_seconds=RAMP_SECONDS):
|
||||
full = LAUNCH_RATE // bps
|
||||
return sum(ramp(full, s) for s in range(min(upto_seconds, RAMP_SECONDS))) * bps
|
||||
|
||||
|
||||
def schedule(bps):
|
||||
phases = []
|
||||
rs = ramp_sum(bps)
|
||||
phases.append({
|
||||
"index": 0, "kind": "ramp", "rule": "launch-ramp",
|
||||
"start_daa_score": 0, "end_daa_score": RAMP_SECONDS * bps,
|
||||
"per_block_subsidy": None,
|
||||
"ramp": {"full_per_block": str(LAUNCH_RATE // bps), "start_percent": RAMP_START_PERCENT,
|
||||
"ramp_seconds": RAMP_SECONDS, "first_block": str(block_subsidy(0, bps)),
|
||||
"last_block": str(block_subsidy(RAMP_SECONDS * bps - 1, bps)),
|
||||
"formula": "per_block(d) = floor(full_per_block x (start_percent x ramp_seconds + (100 - start_percent) x s) / (100 x ramp_seconds)), s = floor(d / bps)"},
|
||||
"phase_sum": str(rs),
|
||||
"source": ["consensus/core/src/emission.rs:316", "consensus/core/src/emission.rs:329"],
|
||||
})
|
||||
k = 0
|
||||
while True:
|
||||
start = RAMP_SECONDS * bps if k == 0 else k * STEP_SECONDS * bps
|
||||
end = (k + 1) * STEP_SECONDS * bps
|
||||
per = block_subsidy(start, bps)
|
||||
assert per == block_subsidy(end - 1, bps)
|
||||
if per == 0:
|
||||
phases.append({
|
||||
"index": len(phases), "kind": "terminal", "rule": "terminal-zero",
|
||||
"start_daa_score": start, "end_daa_score": None, "per_block_subsidy": "0",
|
||||
"decay_step": k, "rate_per_second": str(step_rate(k)), "phase_sum": "0",
|
||||
"source": ["consensus/core/src/emission.rs:109", "consensus/core/src/emission.rs:225", "consensus/core/src/emission.rs:331"],
|
||||
})
|
||||
break
|
||||
phases.append({
|
||||
"index": len(phases), "kind": "constant",
|
||||
"rule": "full-rate-after-ramp" if k == 0 else f"halving-{k}",
|
||||
"start_daa_score": start, "end_daa_score": end, "per_block_subsidy": str(per),
|
||||
"decay_step": k, "rate_per_second": str(step_rate(k)), "phase_sum": str(per * (end - start)),
|
||||
"source": ["consensus/core/src/emission.rs:107", "consensus/core/src/emission.rs:225", "consensus/core/src/emission.rs:331"],
|
||||
})
|
||||
k += 1
|
||||
total = sum(int(p["phase_sum"]) for p in phases)
|
||||
return phases, total
|
||||
|
||||
|
||||
def supply_at(phases, daa, bps):
|
||||
"""Scheduled issuance over DAA scores [0, daa): one subsidy per DAA score (the replay model)."""
|
||||
s = 0
|
||||
for p in phases:
|
||||
a, b = p["start_daa_score"], p["end_daa_score"]
|
||||
if daa <= a:
|
||||
break
|
||||
if p["kind"] == "ramp":
|
||||
hi = min(daa, b)
|
||||
full_secs, rem = divmod(hi, bps)
|
||||
s += ramp_sum(bps, full_secs) + rem * block_subsidy(full_secs * bps, bps)
|
||||
elif p["kind"] == "constant":
|
||||
s += int(p["per_block_subsidy"]) * (min(daa, b) - a)
|
||||
return s
|
||||
|
||||
|
||||
def bps_step_overshoot(step_daa, from_bps, to_bps, total_to):
|
||||
"""The code keys time as daa / bps with ONE bps per network (coinbase.rs:31, :265). If the block rate stepped on a
|
||||
live chain at `step_daa` with no history rule, every block from the step on would be paid as if the clock read
|
||||
daa / to_bps: issuance = sum_{d < step} sub_from(d) + sum_{d >= step} sub_to(d)."""
|
||||
ph_from, _ = schedule(from_bps)
|
||||
ph_to, _ = schedule(to_bps)
|
||||
issued = supply_at(ph_from, step_daa, from_bps) + (total_to - supply_at(ph_to, step_daa, to_bps))
|
||||
return issued
|
||||
|
||||
|
||||
def coins(x):
|
||||
q, r = divmod(x, UNIT)
|
||||
return f"{q:,}.{r:018d}".rstrip("0").rstrip(".")
|
||||
|
||||
|
||||
# ---- The conflicts and the texts ------------------------------------------------------------------------------
|
||||
TAIL_SENTENCE = ("One rule sits beside the cap. If external proving revenue is under one fifth of the block subsidy over "
|
||||
"any 90-day window after year 5, the question of a tail reward goes to the miners' signalling vote. "
|
||||
"The protocol never changes emission by itself.")
|
||||
|
||||
|
||||
def conflicts(fig):
|
||||
m, d = fig["mainnet"], fig["devnet-4"]
|
||||
return [
|
||||
{"id": "C01", "topic": "cap versus the year-five tail vote",
|
||||
"litepaper": {"line": 630, "rev": LITEPAPER_SUPERSEDED_REV, "words": TAIL_SENTENCE,
|
||||
"now_reads": {"rev": "current", "line": 630, "words": "the answer is the security-funding plan under D01, never a tail emission or a vote that expands the cap."}},
|
||||
"code": {"where": "consensus/core/src/emission.rs:109 (Tail::Cap in EmissionSchedule::CURRENT); consensus/core/src/config/params.rs:1858 (mainnet carries CURRENT); kaspad/src/daemon.rs (params_compiled refuses an override file on mainnet and testnet); no signal path reads or writes the emission schedule",
|
||||
"value": "tail = cap; the schedule is a compiled genesis parameter; no vote, no revenue trigger, no tail"},
|
||||
"spec_takes": "the cap as the invariant; the tail-vote sentence is SUPERSEDED",
|
||||
"why": "D01 (capped issuance, subject to TV-04), ratified by the founder 9 October 2026 09:2x UK; VR-01 (no unexplained tail escape, no price- or revenue-triggered issuance); the code already enforces only the capped schedule. If TV-04 shows capped security cannot pass, the volume's rule applies: delay launch or openly choose another design, never an undisclosed tail.",
|
||||
"status": "RESOLVED (by D01, subject to TV-04)",
|
||||
"follow_up": "done by the site lane at ad64e48fa (9 October 2026, 09:16 UK): line 630 now reads 'The cap is fixed at 4 billion, with no tail emission and no vote that expands it (D01, subject to the pre-launch security-funding gate)' and the year-five clause routes to the security-funding plan; this landing edits no site/ file"},
|
||||
{"id": "C02", "topic": "year-one emission and the twelve-year figure",
|
||||
"litepaper": {"line": 578, "words": "emission starts at 1 billion a year", "line_2": 602, "words_2": "3,938M IGN in the first 12 years of a 4,000M cap."},
|
||||
"code": {"where": "consensus/core/src/emission.rs:104 to 106 (launch rate floored, the 30-day ramp from 10%)",
|
||||
"value": f"year 1 issues {coins(m['year_1'])} IGN at 10 blocks/s ({coins(d['year_1'])} at 1 block/s); the first 12 years issue {coins(m['year_12'])} IGN"},
|
||||
"spec_takes": "the code's figures",
|
||||
"why": "the litepaper chart and caption ignore the launch ramp's withheld 36,961,000 IGN and the floor of the per-second rate; the spec carries what nodes pay",
|
||||
"status": "RESOLVED (text correction owed by the site lane)"},
|
||||
{"id": "C03", "topic": "terminal behaviour",
|
||||
"litepaper": {"line": 578, "words": "halves every two years for ever"},
|
||||
"code": {"where": "consensus/core/src/emission.rs:225 (rate[k+1] = rate[k] x 2^31 >> 32, floored), :331 (per block = rate / bps, floored)",
|
||||
"value": f"the per-block subsidy floors to zero at decay step {m['terminal_step']} (DAA score {m['terminal_daa']:,}, the start of year {m['terminal_year'] + 1}) at 10 blocks/s and at step {d['terminal_step']} (DAA score {d['terminal_daa']:,}, the start of year {d['terminal_year'] + 1}) at 1 block/s; zero for ever after"},
|
||||
"spec_takes": "the code: a finite schedule ending in a terminal zero phase",
|
||||
"why": "integer halving ends; the last subsidy is 1 base unit (10^-18 IGN) per block; after it the security budget is fees alone (TV-04's question)",
|
||||
"status": "RESOLVED (text correction owed: 'until the subsidy floors to zero')"},
|
||||
{"id": "C04", "topic": "block rate at launch and its steps",
|
||||
"litepaper": {"line": 542, "words": "one block a second at launch with scheduled steps to four and ten", "line_2": 668, "words_2": "31.688 IGN a block, 86,400 blocks a day"},
|
||||
"code": {"where": "consensus/core/src/config/params.rs:1789 (mainnet BlockrateParams::new::<10>()); consensus/src/processes/coinbase.rs:31 (the bps_history rescaling is gone) and :265 (subsidy keyed on daa / ONE bps)",
|
||||
"value": f"mainnet as compiled runs 10 blocks/s from genesis, 3.1688087810 IGN a block; the subsidy has no block-rate history, so a step from 1 to 10 blocks/s at DAA score {fig['bps_step']['step_daa']:,} (one year in) would issue {coins(fig['bps_step']['issued'])} IGN in all, an overshoot of {coins(fig['bps_step']['issued'] - CAP)} IGN over the cap"},
|
||||
"spec_takes": "both profiles (10 blocks/s as compiled; 1 block/s as Devnet 4), identical per second; NO live block-rate step is cap-safe under the current code",
|
||||
"why": "a block-rate step re-keys the schedule clock (seconds = daa / bps) and re-pays elapsed time",
|
||||
"status": "BLOCKED",
|
||||
"question": "For the founder: does mainnet launch at the compiled 10 blocks a second (the litepaper's 1-to-4-to-10 step plan removed), or at 1 with steps (then the node lane adds a block-rate-history subsidy rule before genesis, and TV-02 replays a step)? Default if unanswered: the spec freezes the compiled 10 blocks a second and the step plan is treated as text to correct."},
|
||||
{"id": "C05", "topic": "unit",
|
||||
"litepaper": {"line": 568, "words": "the base unit is Open"},
|
||||
"code": {"where": "consensus/core/src/unit.rs:38; consensus/core/src/config/params.rs:1816",
|
||||
"value": "18 decimals on mainnet, testnet and Devnet 4: 1 IGN = 10^18 base units; one base unit is one EVM wei"},
|
||||
"spec_takes": "18 decimals",
|
||||
"why": "decided 6 October 2026 (O-2.6) and compiled; the cap is 4 x 10^27 base units",
|
||||
"status": "RESOLVED (text correction owed)"},
|
||||
{"id": "C06", "topic": "where the 20% proving share goes, and which ledger is native supply",
|
||||
"litepaper": {"line": 609, "words": "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"},
|
||||
"code": {"where": "consensus/src/processes/coinbase.rs:163 (UTXO pool output to the OP_RETURN script); consensus/src/pipeline/body_processor/body_validation_in_isolation.rs:54 (no UTXO transaction but the coinbase: UTXO outputs can never be spent); igneum/exec/src/executor.rs:181 to 211 (the EVM state credits 80% to the producer and 20% to the escrow 0x...0220 for every blue block)",
|
||||
"value": "two ledgers record each subsidy: the UTXO coinbase (unspendable by rule) and the EVM state (spendable)"},
|
||||
"spec_takes": "native supply is the EVM state's credit; the UTXO coinbase is a non-spendable shadow record and its OP_RETURN 20% is NOT a burn of native supply; the escrow balance is issued and locked until paid per proof",
|
||||
"why": "counting both ledgers doubles supply; counting the OP_RETURN as burned and the escrow as issued would understate and overstate at once (VR-02)",
|
||||
"status": "RESOLVED (for the spec; TV-02 reconciles both ledgers to this rule)"},
|
||||
{"id": "C07", "topic": "red blocks",
|
||||
"litepaper": {"line": 620, "words": "80% the block's miner, 20% the proving pool for the provers of that block"},
|
||||
"code": {"where": "consensus/src/processes/coinbase.rs:152 to 159 (the UTXO coinbase pays a DAA red block's subsidy to the merging miner and the pool); igneum/exec/src/executor.rs:181 (the EVM credits blue blocks only)",
|
||||
"value": "a red block's subsidy exists on the shadow ledger and not in native supply"},
|
||||
"spec_takes": "native issuance = the blue blocks' subsidies; the per-DAA-score schedule sum is the upper bound",
|
||||
"why": "the EVM ledger is native supply (C06); the bound keeps the cap invariant whatever the red share",
|
||||
"status": "RESOLVED (TV-02 measures the red share; no text change needed beyond 'blue block')"},
|
||||
{"id": "C08", "topic": "which DAA score a merged block is credited at",
|
||||
"litepaper": {"line": None, "words": "(silent)"},
|
||||
"code": {"where": "consensus/core/src/config/params.rs:1812 (mainnet subsidy_per_block_activation_daa = never), :2232 (Devnet 4 inherits never), :1960 (testnet-1: 0)",
|
||||
"value": "on mainnet and Devnet 4 the EVM credits a merged block the merging chain block's subsidy while the UTXO coinbase pays the block's own: one ramp second high per merged block, a whole halving step at a boundary"},
|
||||
"spec_takes": "each block's own DAA score (ledger N8) as the normative rule",
|
||||
"why": "exact integer agreement between ledgers and replays (TV-02) needs one rule; N8 is the one the testnet object already carries",
|
||||
"status": "BLOCKED",
|
||||
"question": "For the founder (the node lane owns the change): set subsidy_per_block_activation_daa = 0 in the mainnet object, as testnet-1 has it? Default if unanswered: the spec keeps N8 as normative and TV-02 reports the chain-block reading as a measured divergence."},
|
||||
{"id": "C09", "topic": "the mainnet genesis clock",
|
||||
"litepaper": {"line": 775, "words": "Genesis with no premine, 30-day ramp."},
|
||||
"code": {"where": "consensus/core/src/config/genesis.rs:92 (mainnet GENESIS daa_score 1312860, Kaspa's 2021 timestamp); the subsidy reads the absolute DAA score (emission.rs:329)",
|
||||
"value": f"as compiled, the first mainnet block would sit 131,286 DAA seconds into the ramp (paying {fig['genesis_offset']['first_ramp_percent']} of full rate, not 10%) and the whole schedule would run 1.52 days early; issuance would be {coins(fig['genesis_offset']['issuance_lost'])} IGN lower"},
|
||||
"spec_takes": "genesis at DAA score 0 (as Devnet 4 and testnet-1); the activation clocks below are counted from it",
|
||||
"why": "the mainnet genesis is a Kaspa placeholder not yet cut; every Igneum network so far starts at DAA score 0",
|
||||
"status": "BLOCKED",
|
||||
"question": "For the founder: confirm the mainnet genesis is cut at DAA score 0. Default if unanswered: the spec freezes DAA score 0 and the genesis cut is checked against it."},
|
||||
{"id": "C10", "topic": "priority fee to provers",
|
||||
"litepaper": {"line": 622, "words": "80% the block's miner and provers; 20% the apps whose code ran, per call frame",
|
||||
"line_2": 614, "words_2": "The priority fee splits two ways: 80% to the miner and provers of that block, 20% to the apps whose code ran"},
|
||||
"code": {"where": "igneum/exec/src/executor.rs:321, :332 to 340 and :362 to 371 (the tip less the developer share is credited to the block's miner alone); igneum/exec/src/pgas.rs:290 (20% developer share, unregistered parts burned)",
|
||||
"value": "two separate 80/20 rules ship: the SUBSIDY's 20% goes to the proving pool escrow and pays provers per accepted proof record (igneum.rs:44, :201 to 208; executor.rs:181 to 211); the TIP's 80% goes to the block's miner alone and 20% to developer registrations or burned. No tip reaches a prover. The design's route of 90% of the proving base fee to provers (docs/design/proving-payment.md; node branch proving-payment 930b6322, flag 67912c80, behind proving_payment_activation_daa) is not merged into any shipped or 2.0.3 line and has no activation"},
|
||||
"spec_takes": "the code, which is the design's own closed decision",
|
||||
"why": "docs/spec/05-fees-and-economics.md line 24 (section 5.2): 'No part of the tip reaches the provers: O-5.7 is closed at zero (8 October 2026, docs/design/proving-payment.md); the provers' user-funded payment is the proving base fee'. The code (executor.rs:362 to 371, revm's beneficiary credit) matches it. The litepaper line 622 and the spec's own summary rows (05 line 79, line 98) still say 'producer and provers' and are stale. Fees move existing supply and burn some; they never create supply.",
|
||||
"status": "RESOLVED (by O-5.7, closed at zero 8 October 2026; the litepaper's 'miner and provers' at lines 614 and 622 is SUPERSEDED; text corrections owed there and at spec 05 lines 79 and 98)"},
|
||||
{"id": "C11", "topic": "the reference pool's custody and fee",
|
||||
"litepaper": {"line": 647, "words": "The reference pool never holds a member’s balance. Payouts come straight from the coinbase split"},
|
||||
"code": {"where": "pool/src/payout.rs:1 to 6 (the pool's address receives the 80% and pays members by transfers); pool/src/config.rs:58 (fee_percent 1.0); pool/src/pplns.rs:76; consensus/core/src/config/params.rs:1851 (mainnet pool_split_activation_daa = never: the IGNW coinbase split is off)",
|
||||
"value": "the reference pool holds members' balances between rounds and keeps a 1% fee; the consensus split that would make the sentence true is off on mainnet and Devnet 4"},
|
||||
"spec_takes": "the code (custodial PPLNS, 1% default fee, no supply effect)",
|
||||
"why": "the payout route moves issued supply; it creates none",
|
||||
"status": "BLOCKED",
|
||||
"question": "For the founder: activate the IGNW coinbase split at mainnet genesis (pool_split_activation_daa = 0) so the litepaper sentence holds, or change the sentence to the custodial PPLNS pool with its 1% fee? Default if unanswered: the sentence is corrected to the code."},
|
||||
{"id": "C12", "topic": "a tail schedule compiled into testnet-1",
|
||||
"litepaper": {"line": 630, "words": "The cap is fixed at 4 billion, with no tail emission and no vote that expands it (D01, subject to the pre-launch security-funding gate)."},
|
||||
"code": {"where": "consensus/core/src/emission.rs:116 to 122 (TESTNET_1: 100 IGN a block, monthly glide, a 1% a year tail); consensus/core/src/config/params.rs:2014 (testnet-1 carries it)",
|
||||
"value": "testnet-1's own approved genesis schedule (docs/plans/ledger-decisions.md line 110, ledger E22: 'No hard cap'): kept on every node line, active only when igneum-testnet-1's genesis is cut (none has been), never on Devnet 4 (params.rs:2372) or mainnet (params.rs:1858), which carry CURRENT with Tail::Cap"},
|
||||
"spec_takes": "testnet-only and outside the monetary contract; the contract is EmissionSchedule::CURRENT with Tail::Cap on mainnet",
|
||||
"why": "D01 binds the value-bearing contract; testnet-1 carries no value. Binding the cap to the testnet as well would change its approved genesis (E22) and move its digest: the founder's call, not this spec's",
|
||||
"status": "RESOLVED (by D01: testnet-only, named)"},
|
||||
{"id": "C13", "topic": "dead Kaspa subsidy fields in the mainnet object",
|
||||
"litepaper": {"line": None, "words": "(silent)"},
|
||||
"code": {"where": "consensus/core/src/config/params.rs:1783 to 1784 (deflationary_phase_daa_score 15,519,600; pre_deflationary_phase_base_subsidy 50,000,000,000)",
|
||||
"value": "read only into the consensus digest; the subsidy never reads them (coinbase.rs:31)"},
|
||||
"spec_takes": "ignored: they are not part of the monetary contract",
|
||||
"why": "a reader grepping 'deflationary' would otherwise find a second schedule that does not exist",
|
||||
"status": "RESOLVED (the node lane may annotate or remove them at the genesis cut)"},
|
||||
]
|
||||
|
||||
|
||||
def main():
|
||||
ap = argparse.ArgumentParser()
|
||||
ap.add_argument("--node-git", required=True)
|
||||
ap.add_argument("--igneum-root", required=True)
|
||||
ap.add_argument("--igneum-rev", default="HEAD")
|
||||
ap.add_argument("--code-dump")
|
||||
ap.add_argument("--out", required=True)
|
||||
ap.add_argument("--md")
|
||||
a = ap.parse_args()
|
||||
|
||||
igneum_rev = sh(["git", "rev-parse", a.igneum_rev], a.igneum_root).decode().strip()
|
||||
cites, files = anchor(CITATIONS, a.node_git, a.igneum_root, igneum_rev)
|
||||
|
||||
profiles, fig = [], {}
|
||||
for name, network, bps in [("mainnet", "mainnet (MAINNET_PARAMS as compiled)", MAINNET_BPS), ("devnet-4", "Devnet 4 (devnet4_params, 1 block a second, the litepaper's launch rate)", DEVNET4_BPS)]:
|
||||
phases, total = schedule(bps)
|
||||
term = phases[-1]
|
||||
f = {
|
||||
"year_1": supply_at(phases, YEAR_SECONDS * bps, bps),
|
||||
"year_12": supply_at(phases, 12 * YEAR_SECONDS * bps, bps),
|
||||
"terminal_step": term["decay_step"], "terminal_daa": term["start_daa_score"],
|
||||
"terminal_year": term["start_daa_score"] // bps // YEAR_SECONDS,
|
||||
"total": total,
|
||||
}
|
||||
fig[name] = f
|
||||
years = {}
|
||||
for y in [1, 2, 5, 10, 12, 20, 50, 100]:
|
||||
years[str(y)] = str(supply_at(phases, y * YEAR_SECONDS * bps, bps))
|
||||
last_nonzero = phases[-2]
|
||||
profiles.append({
|
||||
"profile": name, "network": network, "bps": bps, "decimals": DECIMALS,
|
||||
"is_mainnet_contract": name == "mainnet",
|
||||
"genesis_daa_score": 0,
|
||||
"time_key": "seconds = floor(daa_score / bps); the per-block subsidy is constant inside a DAA second",
|
||||
"phases": phases,
|
||||
"phase_count": len(phases),
|
||||
"last_nonzero_phase": {"index": last_nonzero["index"], "per_block_subsidy": last_nonzero["per_block_subsidy"],
|
||||
"start_daa_score": last_nonzero["start_daa_score"], "end_daa_score": last_nonzero["end_daa_score"]},
|
||||
"transition_count": len(phases) - 1,
|
||||
"last_paying_daa_score": term["start_daa_score"] - 1,
|
||||
"last_paying_block_height": term["start_daa_score"],
|
||||
"terminal_supply": str(total),
|
||||
"terminal": {"start_daa_score": term["start_daa_score"], "first_unpaid_block_height": term["start_daa_score"] + 1, "decay_step": term["decay_step"],
|
||||
"start_year": f["terminal_year"], "behaviour": "per-block subsidy 0 at every later DAA score; the cap is never reached; blocks are paid by fees alone"},
|
||||
"total_issuance": str(total),
|
||||
"cap_shortfall": str(CAP - total),
|
||||
"scheduled_supply_at_year_end": years,
|
||||
})
|
||||
|
||||
# the block-rate step demonstration (C04) and the compiled genesis offset (C09)
|
||||
step_daa = YEAR_SECONDS * DEVNET4_BPS
|
||||
fig["bps_step"] = {"step_daa": step_daa, "issued": bps_step_overshoot(step_daa, 1, 10, fig["mainnet"]["total"])}
|
||||
ph10, _ = schedule(MAINNET_BPS)
|
||||
off = MAINNET_GENESIS_DAA_COMPILED
|
||||
s0 = off // MAINNET_BPS
|
||||
num = RAMP_START_PERCENT * RAMP_SECONDS + (100 - RAMP_START_PERCENT) * s0
|
||||
fig["genesis_offset"] = {"issuance_lost": supply_at(ph10, off, MAINNET_BPS),
|
||||
"first_ramp_percent": f"{num * 100 // (100 * RAMP_SECONDS)}.{(num * 10000 // (100 * RAMP_SECONDS)) % 100:02d}%"}
|
||||
|
||||
dump_check = None
|
||||
if a.code_dump:
|
||||
raw = open(a.code_dump, "rb").read()
|
||||
rows = [json.loads(l) for l in raw.decode().splitlines() if l.strip()]
|
||||
n = 0
|
||||
for prof in profiles:
|
||||
net = prof["profile"]
|
||||
head = next(r for r in rows if r.get("network") == net and "bps" in r)
|
||||
assert head["bps"] == prof["bps"] and head["decimals"] == DECIMALS and int(head["launch_rate"]) == LAUNCH_RATE
|
||||
assert head["ramp_seconds"] == RAMP_SECONDS and head["step_seconds"] == STEP_SECONDS and head["step_decay_q32"] == STEP_DECAY_Q32 and head["tail"] == "cap"
|
||||
rp = next(r for r in rows if r.get("network") == net and r.get("phase") == "ramp")
|
||||
p0 = prof["phases"][0]
|
||||
assert int(rp["sum"]) == int(p0["phase_sum"]) and rp["first_block"] == p0["ramp"]["first_block"] and rp["last_block"] == p0["ramp"]["last_block"] and rp["end_daa"] == p0["end_daa_score"]
|
||||
steps = [r for r in rows if r.get("network") == net and r.get("phase") == "step"]
|
||||
for r, p in zip(steps, prof["phases"][1:]):
|
||||
assert r["start_daa"] == p["start_daa_score"], (net, r, p)
|
||||
assert r["per_block"] == p["per_block_subsidy"], (net, r, p)
|
||||
assert int(r["rate_per_second"]) == int(p["rate_per_second"]), (net, r, p)
|
||||
if p["kind"] == "constant":
|
||||
assert r["end_daa"] == p["end_daa_score"] and r["sum"] == p["phase_sum"], (net, r, p)
|
||||
n += 1
|
||||
assert len(steps) == len(prof["phases"]) - 1, (net, len(steps), len(prof["phases"]))
|
||||
tot = next(r for r in rows if r.get("network") == net and "total" in r)
|
||||
assert int(tot["total"]) == int(prof["total_issuance"]) and int(tot["cap"]) == CAP and int(tot["shortfall"]) == int(prof["cap_shortfall"])
|
||||
assert head["genesis_daa_score"] == (MAINNET_GENESIS_DAA_COMPILED if net == "mainnet" else 0)
|
||||
t1 = next(r for r in rows if r.get("network") == "testnet-1" and "bps" in r)
|
||||
assert t1["tail"] == "percent:100"
|
||||
dump_check = {"path": "tools/token-value/tv-01/code-dump/schedule-bf60948a.jsonl", "sha256": hashlib.sha256(raw).hexdigest(),
|
||||
"program": "tools/token-value/tv-01/code-dump/tv01_schedule.rs.txt",
|
||||
"run": "build-9, release-2.0.3-node-k6 bf60948a: cargo run --release -p kaspa-consensus-core --example tv01_schedule (the program copied into consensus/core/examples of a scratch tree; the node fork is unchanged)",
|
||||
"agreement": f"every field of {n} decay phases, both ramps and both totals equal to this generator's integers"}
|
||||
|
||||
spec = {
|
||||
"schema": {
|
||||
"name": "igneum-supply-spec", "version": "1.0.0",
|
||||
"integers": "every amount is a decimal string of base units (amounts reach 4 x 10^27, past JSON's exact range); DAA scores are JSON integers (all under 2^53)",
|
||||
"phase_fields": {"index": "0-based, contiguous", "kind": "ramp | constant | terminal", "rule": "the rule's name", "start_daa_score": "inclusive", "end_daa_score": "exclusive; null for the terminal phase", "per_block_subsidy": "base units per block at every DAA score of the phase (null for the ramp, whose per-block value is its formula)", "phase_sum": "base units the phase issues at one subsidy per DAA score", "source": "file:line in the node fork at both named revisions"},
|
||||
"checker": "tools/token-value/tv-01/check-spec.py",
|
||||
},
|
||||
"gate": "TV-01", "verdict": "NOT RUN",
|
||||
"status": "Phase 0 document. Activates nothing; changes no node, no chain and no served page. The verdict stays NOT RUN until the independent panel reads it.",
|
||||
"decision": {"id": "D01", "title": "Cap and tail", "ruling": "capped issuance, subject to TV-04 (the founder's ratification, 9 October 2026, 09:2x UK)",
|
||||
"effect": "the cap is the invariant; the litepaper's year-five tail-vote sentence is superseded (C01)"},
|
||||
"invariant": {"rule": "sum over all blocks of the per-block subsidy <= cap, with no tail, no vote, no price or revenue trigger and no emergency mint",
|
||||
"how_enforced": "by the schedule, not by a running counter: every block's coinbase must carry exactly the scheduled subsidy (coinbase.rs:262) and the EVM state credits by the same rule (executor.rs:181 to 211); the cap constant itself is only compared in tests (igneum.rs:154). The schedule sums to the cap minus the shortfall below, so the cap is never reached.",
|
||||
"change_policy": "the schedule is a compiled genesis parameter on mainnet; the daemon refuses an override file there (kaspad/src/daemon.rs, network.rs:266); a change is a new release with a new consensus digest and an explicit incompatible-policy process (VR-06)"},
|
||||
"units": {"coin": "IGN", "base_unit": "wei (one consensus base unit is one EVM wei)", "decimals": DECIMALS, "base_units_per_coin": str(UNIT),
|
||||
"source": ["consensus/core/src/unit.rs:38", "consensus/core/src/config/params.rs:1816"]},
|
||||
"cap": {"base_units": str(CAP), "coins": "4000000000", "kind": "invariant",
|
||||
"source": ["consensus/core/src/igneum.rs:37", "consensus/core/src/igneum.rs:154"]},
|
||||
"rules": {
|
||||
"launch_rate_per_second": str(LAUNCH_RATE), "launch_rate_note": "floor(10^9 x 10^8 / 31,557,600) = 3,168,808,781 at 8 decimals, times 10^10: 31.68808781 IGN a DAA second",
|
||||
"year_seconds": YEAR_SECONDS, "ramp_seconds": RAMP_SECONDS, "ramp_start_percent": RAMP_START_PERCENT,
|
||||
"step_seconds": STEP_SECONDS, "step_decay_q32": STEP_DECAY_Q32, "decay_rule": "rate[k+1] = floor(rate[k] x step_decay_q32 / 2^32) (a halving every two years)",
|
||||
"per_block": "per_block(d) = ramp(floor(rate[floor(s / step_seconds)] / bps), s), s = floor(d / bps)",
|
||||
"tail": "cap",
|
||||
"genesis_rule": {"rule": "genesis-unrewarded",
|
||||
"text": "the genesis block is never rewarded: it never enters the DAA window (window.rs:177), the coinbase pays only the mergeset blues inside the DAA window (coinbase.rs:139), and the genesis coinbase has no outputs (genesis.rs:26). A block's DAA score counts its DAA-window mergeset, so on a chain the block at height h >= 1 carries DAA score h - 1: DAA score 0 is the first paid block and every schedule below is indexed by DAA score, not height",
|
||||
"height_of_daa_score": "height = DAA score + 1 (a chain of blocks; in the DAG several blocks may share a DAA score)",
|
||||
"source": ["consensus/src/processes/window.rs:177", "consensus/src/processes/coinbase.rs:139", "consensus/core/src/config/genesis.rs:26"]},
|
||||
"source": ["consensus/core/src/emission.rs:103 to 110", "consensus/core/src/emission.rs:225", "consensus/core/src/emission.rs:316 to 333", "consensus/core/src/config/params.rs:1858"],
|
||||
},
|
||||
"activation_clocks": {
|
||||
"genesis": "DAA score 0 (C09: the compiled mainnet genesis still carries Kaspa's 1,312,860; BLOCKED)",
|
||||
"ramp_end": {"mainnet": RAMP_SECONDS * MAINNET_BPS, "devnet-4": RAMP_SECONDS * DEVNET4_BPS, "seconds": RAMP_SECONDS},
|
||||
"halvings": "every 63,115,200 DAA seconds (2 years of 365.25 days): DAA score k x 631,152,000 at 10 blocks/s, k x 63,115,200 at 1 block/s",
|
||||
"terminal": {"mainnet": fig["mainnet"]["terminal_daa"], "devnet-4": fig["devnet-4"]["terminal_daa"]},
|
||||
"signing_bonus": "mainnet never (params.rs:1809); testnet-1 from 0",
|
||||
"pool_split": "mainnet never (params.rs:1851)",
|
||||
"per_block_rule_N8": "mainnet never (params.rs:1812; C08 BLOCKED); testnet-1 from 0",
|
||||
},
|
||||
"contract_profile": "mainnet",
|
||||
"contract_profile_note": "mainnet's schedule (MAINNET_PARAMS, params.rs:1858, 10 blocks a second) is the monetary contract under the 4,000,000,000 cap; the Devnet 4 profile (params.rs:2372, 1 block a second, 18 decimals) is the same per-second curve, carried because it is the chain TV-02 replays and the litepaper's launch rate (C04)",
|
||||
"schedules": profiles,
|
||||
"coinbase_split": [
|
||||
{"rule": "producer and proving pool", "producer": "subsidy - floor(subsidy x 20 / 100)", "proving_pool": "floor(subsidy x 20 / 100)", "source": ["consensus/core/src/igneum.rs:44", "consensus/core/src/igneum.rs:201 to 208"]},
|
||||
{"rule": "signing bonus", "effect": "a silent producer's share loses floor(share x 1,000 / 10,000) to the proving pool; nothing destroyed; fees untouched", "mainnet": "off (never)", "source": ["consensus/core/src/igneum.rs:110", "consensus/core/src/config/params.rs:1809 to 1810"]},
|
||||
{"rule": "native ledger", "effect": "the EVM state credits the producer share to the block's IGNA address (or by its IGNW split once pool_split is active) and the pool share to escrow 0x0000000000000000000000000000000000000220, blue blocks only", "source": ["igneum/exec/src/executor.rs:181 to 211", "igneum/exec/src/config.rs:59"]},
|
||||
{"rule": "shadow ledger", "effect": "the UTXO coinbase pays the producer outputs and one OP_RETURN pool output tagged igneum-proving-pool-v0; no UTXO transaction but the coinbase is valid, so none of it is ever spent", "source": ["consensus/src/processes/coinbase.rs:126 to 168", "consensus/src/pipeline/body_processor/body_validation_in_isolation.rs:54"]},
|
||||
{"rule": "pool", "effect": "none in consensus; the reference pool keeps fee_percent (default 1.0) of the 80% it receives and pays members PPLNS from its address", "source": ["pool/src/payout.rs:1 to 6", "pool/src/config.rs:58", "pool/src/pplns.rs:76"]},
|
||||
{"rule": "dev fee", "effect": "none in consensus; the miner software requests 1 template in 100 with the dev address (off with --dev-fee 0); it moves the producer share of those blocks, creates nothing", "source": ["site/litepaper.html:625"]},
|
||||
{"rule": "treasury, foundation, team, stake", "effect": "none: no output, credit or address in consensus", "source": ["consensus/src/processes/coinbase.rs:126 to 168", "igneum/exec/src/executor.rs:181 to 211"]},
|
||||
{"rule": "genesis", "effect": "the genesis coinbase has no outputs; its payload subsidy field is data; no premine", "source": ["consensus/core/src/config/genesis.rs:26"]},
|
||||
],
|
||||
"fees": [
|
||||
{"route": "execution base fee", "effect": "burned (not credited)", "source": "igneum/exec/src/executor.rs:319 to 321"},
|
||||
{"route": "proving charge (proving base fee)", "effect": "burned in full today; the 90% route to provers is designed (docs/design/proving-payment.md, node branch proving-payment 930b6322), not merged, no activation scheduled", "source": "igneum/exec/src/executor.rs:319 to 321, :361"},
|
||||
{"route": "priority fee", "effect": "80% to the block's miner; 20% to the payees registered for the frames' code by own gas, unregistered parts burned", "source": "igneum/exec/src/executor.rs:362 to 371; igneum/exec/src/pgas.rs:290"},
|
||||
{"route": "external job after the proof bridge", "effect": "not in code (Designed, phase two): 90% provers, 10% burned per the litepaper", "source": "site/litepaper.html:624"},
|
||||
],
|
||||
"supply_accounting": {
|
||||
"issued": "the EVM state's subsidy credits (producer plus escrow), blue blocks only; bounded above by the schedule's per-DAA-score sum",
|
||||
"burned": "execution base fees, proving charges and unregistered developer shares (EVM side); the UTXO OP_RETURN output is NOT counted as a burn (C06)",
|
||||
"locked": "the proving escrow's balance: credited, not yet paid against a valid proof; unclaimed credit is stranded today (litepaper line 609, no rule returns it)",
|
||||
"not_supply": "UTXO coinbase outputs (unspendable shadow record); any bridged or wrapped IGN on another chain (VR-02)",
|
||||
"replay_model": "one scheduled subsidy per DAA score (the schedules above); a block is paid at its own DAA score, parallel blocks may share a score and a score may have no block, so the measured issuance differs from the model only near phase boundaries and only by the blocks merged across them; TV-02 reconciles the measured ledgers to this model in exact integers",
|
||||
},
|
||||
"superseded": [
|
||||
{"text": TAIL_SENTENCE, "where": "site/litepaper.html:630 at " + LITEPAPER_SUPERSEDED_REV, "by": "D01 (capped issuance, subject to TV-04)", "status": "SUPERSEDED", "text_now": "rewritten by the site lane at ad64e48fa (C01)"},
|
||||
{"text": "The priority fee splits two ways: 80% to the miner and provers of that block, 20% to the apps whose code ran", "where": "site/litepaper.html:614 (and the route table at :622)", "by": "O-5.7 closed at zero, 8 October 2026 (docs/spec/05-fees-and-economics.md line 24); the code pays the tip's 80% to the miner alone", "status": "SUPERSEDED"},
|
||||
],
|
||||
"conflicts": conflicts(fig),
|
||||
"sources": {
|
||||
"volume": {"path": "docs/plans/igneum-2.0-master/token-value/rules-and-gates.json", "sha256": hashlib.sha256(read_igneum(a.igneum_root, igneum_rev, "docs/plans/igneum-2.0-master/token-value/rules-and-gates.json")).hexdigest()},
|
||||
"litepaper": {"path": "site/litepaper.html", "igneum_commit": igneum_rev, "sha256": files["igneum:site/litepaper.html"]["igneum master"],
|
||||
"superseded_text_commit": LITEPAPER_SUPERSEDED_REV, "superseded_text_sha256": files["igneum-old:site/litepaper.html"]["igneum before ad64e48fa"]},
|
||||
"node": {"repo": "igneum-node (build-1:/srv/igneum-node.git)", "revisions": NODE_REVS,
|
||||
"files": {k.split(":", 1)[1]: v for k, v in sorted(files.items()) if k.startswith("node:")},
|
||||
"note": "every cited line carries its anchor at all four revisions (the generator refuses otherwise); the shipped 2.0.2 commit 5d53a591 and the 2.0.3 lines k6 and k7 change no issuance rule"},
|
||||
"pool": {k.split(":", 1)[1]: v["igneum master"] for k, v in sorted(files.items()) if k.startswith("igneum:pool/")},
|
||||
"design": {k.split(":", 1)[1]: v["igneum master"] for k, v in sorted(files.items()) if k.startswith("igneum:docs/")},
|
||||
"citations": cites,
|
||||
"code_dump": dump_check,
|
||||
},
|
||||
"generator": {"path": "tools/token-value/tv-01/gen-spec.py",
|
||||
"command": "python3 tools/token-value/tv-01/gen-spec.py --node-git <igneum-node clone holding both revisions> --igneum-root . --igneum-rev " + igneum_rev + " --code-dump tools/token-value/tv-01/code-dump/schedule-bf60948a.jsonl --out docs/plans/igneum-2.0-master/token-value/phase0/tv-01/supply-spec.json --md docs/plans/igneum-2.0-master/token-value/phase0/tv-01/supply-spec.md"},
|
||||
}
|
||||
# every litepaper quote in the conflicts table is exact (tags stripped, entities decoded) or nothing is written
|
||||
import html as _html, re as _re
|
||||
lps = {}
|
||||
def lp_line(rev, ln):
|
||||
if rev not in lps:
|
||||
lps[rev] = read_igneum(a.igneum_root, rev, "site/litepaper.html").decode("utf-8").split("\n")
|
||||
return _html.unescape(_re.sub("<[^>]+>", "", lps[rev][ln - 1]))
|
||||
for c in spec["conflicts"]:
|
||||
rev = c["litepaper"].get("rev", igneum_rev)
|
||||
for lk, wk in (("line", "words"), ("line_2", "words_2")):
|
||||
ln = c["litepaper"].get(lk)
|
||||
if ln and c["litepaper"][wk] not in lp_line(rev, ln):
|
||||
sys.exit(f"{c['id']}: the litepaper's line {ln} at {rev[:9]} does not carry the quoted words exactly")
|
||||
now = c["litepaper"].get("now_reads")
|
||||
if now and now["words"] not in lp_line(igneum_rev, now["line"]):
|
||||
sys.exit(f"{c['id']}: the current litepaper line {now['line']} does not carry the quoted words")
|
||||
if now:
|
||||
now["rev"] = igneum_rev
|
||||
c["litepaper"].setdefault("rev", igneum_rev)
|
||||
with open(a.out, "w", encoding="utf-8") as fh:
|
||||
json.dump(spec, fh, indent=1, ensure_ascii=False)
|
||||
fh.write("\n")
|
||||
|
||||
if a.md:
|
||||
m, d = profiles[0], profiles[1]
|
||||
L = ["<!-- figures:begin (generated by tools/token-value/tv-01/gen-spec.py; do not edit by hand) -->", ""]
|
||||
L.append("| Figure | Mainnet as compiled (10 blocks/s) | Devnet 4 (1 block/s) |")
|
||||
L.append("|---|---|---|")
|
||||
L.append(f"| Cap | {coins(CAP)} IGN ({CAP} base units) | the same |")
|
||||
L.append(f"| Total scheduled issuance | {coins(int(m['total_issuance']))} IGN | {coins(int(d['total_issuance']))} IGN |")
|
||||
L.append(f"| Shortfall under the cap (exact) | {m['cap_shortfall']} base units ({coins(int(m['cap_shortfall']))} IGN) | {d['cap_shortfall']} base units ({coins(int(d['cap_shortfall']))} IGN) |")
|
||||
L.append(f"| Ramp phase | DAA 0 to {m['phases'][0]['end_daa_score']:,}, {coins(int(m['phases'][0]['phase_sum']))} IGN | DAA 0 to {d['phases'][0]['end_daa_score']:,}, {coins(int(d['phases'][0]['phase_sum']))} IGN |")
|
||||
L.append(f"| First block | {coins(int(m['phases'][0]['ramp']['first_block']))} IGN | {coins(int(d['phases'][0]['ramp']['first_block']))} IGN |")
|
||||
L.append(f"| Full-rate block (after the ramp) | {coins(int(m['phases'][1]['per_block_subsidy']))} IGN | {coins(int(d['phases'][1]['per_block_subsidy']))} IGN |")
|
||||
L.append(f"| Phases (ramp, constant, terminal) | {m['phase_count']} | {d['phase_count']} |")
|
||||
L.append(f"| Last non-zero subsidy | {m['last_nonzero_phase']['per_block_subsidy']} base unit a block, DAA {m['last_nonzero_phase']['start_daa_score']:,} to {m['last_nonzero_phase']['end_daa_score']:,} | {d['last_nonzero_phase']['per_block_subsidy']} base unit a block, DAA {d['last_nonzero_phase']['start_daa_score']:,} to {d['last_nonzero_phase']['end_daa_score']:,} |")
|
||||
L.append(f"| Terminal (zero for ever) from | DAA {m['terminal']['start_daa_score']:,} (decay step {m['terminal']['decay_step']}, year {m['terminal']['start_year'] + 1}) | DAA {d['terminal']['start_daa_score']:,} (decay step {d['terminal']['decay_step']}, year {d['terminal']['start_year'] + 1}) |")
|
||||
for y in ["1", "2", "5", "10", "12", "20", "50", "100"]:
|
||||
L.append(f"| Scheduled supply at the end of year {y} | {coins(int(m['scheduled_supply_at_year_end'][y]))} IGN | {coins(int(d['scheduled_supply_at_year_end'][y]))} IGN |")
|
||||
L.append(f"| A 1-to-10 blocks/s step at DAA {fig['bps_step']['step_daa']:,} under the current code | total {coins(fig['bps_step']['issued'])} IGN, {coins(fig['bps_step']['issued'] - CAP)} IGN over the cap | |")
|
||||
L.append(f"| The compiled mainnet genesis (DAA 1,312,860) | first ramp block at {fig['genesis_offset']['first_ramp_percent']}; {coins(fig['genesis_offset']['issuance_lost'])} IGN never scheduled | |")
|
||||
L.append("")
|
||||
if dump_check:
|
||||
L.append(f"Code agreement: {dump_check['agreement']} (dump sha256 `{dump_check['sha256']}`).")
|
||||
L.append("")
|
||||
L.append("<!-- figures:end -->")
|
||||
txt = open(a.md).read()
|
||||
b, e = txt.index("<!-- figures:begin"), txt.index("<!-- figures:end -->") + len("<!-- figures:end -->")
|
||||
with open(a.md, "w") as fh:
|
||||
fh.write(txt[:b] + "\n".join(L) + txt[e:])
|
||||
print(f"wrote {a.out}: mainnet total {fig['mainnet']['total']}, shortfall {CAP - fig['mainnet']['total']}; devnet-4 total {fig['devnet-4']['total']}; code dump {'agrees' if dump_check else 'not given'}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
36
tools/token-value/tv-02/compare-all.sh
Executable file
36
tools/token-value/tv-02/compare-all.sh
Executable file
|
|
@ -0,0 +1,36 @@
|
|||
#!/usr/bin/env bash
|
||||
# TV-02: every comparison of A's outputs with B's, on A's box (build-5) with B's outputs copied from build-6 into
|
||||
# <out>/from-build-6. Usage: [NETS="devnet4 mainnet"] [SPEC_RULES=<B rules mapped from the TV-01 spec>] compare-all.sh
|
||||
# <tv-02 dir> <out dir> <B rules tag> ; writes <out>/compare-<tag>.log. With SPEC_RULES, A's terminal row is also checked
|
||||
# against the terminal supply and height the spec itself states.
|
||||
set -u
|
||||
D="$1"; O="$2"; TAG="$3"
|
||||
PID="$O/compare.pid"; echo "$$ $(date -u +%FT%TZ) tv02-compare $TAG" > "$PID"; trap 'rm -f "$PID"' EXIT
|
||||
B="$O/from-build-6"; LOG="$O/compare-$TAG.log"; : > "$LOG"
|
||||
python3 "$D/compare.py" selftest >> "$LOG" 2>&1
|
||||
for N in ${NETS:-devnet4 mainnet devnet}; do
|
||||
echo "== straight $N" >> "$LOG"
|
||||
python3 "$D/compare.py" straight "$O/a-$N.csv" "$B/b-$TAG-$N.csv" >> "$LOG" 2>&1; echo "exit $?" >> "$LOG"
|
||||
done
|
||||
for F in reorg-ramp-end-k1 reorg-first-step-k2; do
|
||||
echo "== reorg $F" >> "$LOG"
|
||||
python3 "$D/compare.py" reorg "$O/a-reorg-$F.csv" "$B/b-$TAG-reorg-$F.csv" "$D/fixtures/$F.json" "$O/a-devnet4.csv" >> "$LOG" 2>&1; echo "exit $?" >> "$LOG"
|
||||
done
|
||||
if [ -n "${SPEC_RULES:-}" ]; then
|
||||
echo "== the spec's own terminal supply against A's terminal row" >> "$LOG"
|
||||
python3 - "$SPEC_RULES" "$O" >> "$LOG" 2>&1 <<'PY'
|
||||
import csv, json, sys
|
||||
spec = json.load(open(sys.argv[1]))
|
||||
ok = True
|
||||
for net, t in spec["spec_terminal_supply"].items():
|
||||
rows = {int(r["height"]): r for r in csv.DictReader(open("%s/a-%s.csv" % (sys.argv[2], net)))}
|
||||
h = int(t["last_paying_block_height"])
|
||||
got = rows[h]["cumulative"] if h in rows else None
|
||||
same = got == str(t["terminal_supply"]) and rows[h + 1]["subsidy"] == "0"
|
||||
ok = ok and same
|
||||
print("%s %s: spec terminal %s at height %d, A %s" % ("EQUAL" if same else "DIFFER", net, t["terminal_supply"], h, got))
|
||||
sys.exit(0 if ok else 1)
|
||||
PY
|
||||
echo "exit $?" >> "$LOG"
|
||||
fi
|
||||
cat "$LOG"
|
||||
165
tools/token-value/tv-02/compare.py
Executable file
165
tools/token-value/tv-02/compare.py
Executable file
|
|
@ -0,0 +1,165 @@
|
|||
#!/usr/bin/env python3
|
||||
"""TV-02 comparison harness: reads implementation A's and B's outputs and reports exact equality or the first
|
||||
differing row. Standalone Python 3, no third-party modules, shares no code with A or B.
|
||||
|
||||
compare.py straight <a.csv> <b.csv> CSV: height,daa_score,subsidy,cumulative
|
||||
compare.py reorg <a.csv> <b.csv> <fixture.json> <straight.csv>
|
||||
CSV: step,height,daa_score,subsidy,cumulative
|
||||
compare.py selftest the known-failed cases first, then a known-equal case
|
||||
|
||||
Exit 0 when everything compared is equal, 1 on any difference, 2 on bad input. The verdict line starts with
|
||||
EQUAL or DIFFER (never PASS: the independent panel reads the evidence).
|
||||
"""
|
||||
import csv
|
||||
import io
|
||||
import json
|
||||
import sys
|
||||
|
||||
STRAIGHT_HEADER = ["height", "daa_score", "subsidy", "cumulative"]
|
||||
REORG_HEADER = ["step", "height", "daa_score", "subsidy", "cumulative"]
|
||||
|
||||
|
||||
def read_rows(text, header):
|
||||
r = list(csv.reader(io.StringIO(text)))
|
||||
if not r or r[0] != header:
|
||||
raise ValueError("header %r is not %r" % (r[0] if r else None, header))
|
||||
out = []
|
||||
for i, row in enumerate(r[1:], start=2):
|
||||
if len(row) != len(header):
|
||||
raise ValueError("line %d has %d fields" % (i, len(row)))
|
||||
out.append(row)
|
||||
return out
|
||||
|
||||
|
||||
def ints(row, first_int):
|
||||
return row[:first_int] + [int(x) for x in row[first_int:]]
|
||||
|
||||
|
||||
def compare_straight_text(a_text, b_text):
|
||||
a = [ints(r, 0) for r in read_rows(a_text, STRAIGHT_HEADER)]
|
||||
b = [ints(r, 0) for r in read_rows(b_text, STRAIGHT_HEADER)]
|
||||
res = {"rows_a": len(a), "rows_b": len(b)}
|
||||
for i in range(max(len(a), len(b))):
|
||||
ra = a[i] if i < len(a) else None
|
||||
rb = b[i] if i < len(b) else None
|
||||
if ra != rb:
|
||||
cols = [] if ra is None or rb is None else [STRAIGHT_HEADER[j] for j in range(4) if ra[j] != rb[j]]
|
||||
res.update(equal=False, first_difference={"row": i + 1, "a": ra, "b": rb, "columns": cols})
|
||||
# diagnostic: is B - A a constant offset in the cumulative over the rows both share by height?
|
||||
ah = {r[0]: r for r in a}
|
||||
bh = {r[0]: r for r in b}
|
||||
common = sorted(set(ah) & set(bh))
|
||||
offs = sorted({bh[h][3] - ah[h][3] for h in common})
|
||||
res["heights_only_in_a"] = len(set(ah) - set(bh))
|
||||
res["heights_only_in_b"] = len(set(bh) - set(ah))
|
||||
res["cumulative_offsets_b_minus_a_on_shared_heights"] = [str(x) for x in offs[:5]] + (["..."] if len(offs) > 5 else [])
|
||||
return res
|
||||
res.update(equal=True, last_row=a[-1] if a else None)
|
||||
return res
|
||||
|
||||
|
||||
def compare_reorg_text(a_text, b_text, fixture, straight_text):
|
||||
a = [ints(r, 1) for r in read_rows(a_text, REORG_HEADER)]
|
||||
b = [ints(r, 1) for r in read_rows(b_text, REORG_HEADER)]
|
||||
res = {"fixture": fixture.get("id"), "checks": []}
|
||||
|
||||
def check(name, ok, detail):
|
||||
res["checks"].append({"check": name, "ok": bool(ok), "detail": detail})
|
||||
|
||||
first = None
|
||||
for i in range(max(len(a), len(b))):
|
||||
ra = a[i] if i < len(a) else None
|
||||
rb = b[i] if i < len(b) else None
|
||||
if ra != rb:
|
||||
first = {"row": i + 1, "a": ra, "b": rb}
|
||||
break
|
||||
check("A equals B, every ledger row", first is None, first or {"rows": len(a)})
|
||||
for name, rows in (("A", a), ("B", b)):
|
||||
by = {}
|
||||
for r in rows:
|
||||
by.setdefault(r[0], []).append(r)
|
||||
e = fixture["expected"]
|
||||
anc = by["ancestor"][0]
|
||||
check(name + " ancestor = fixture", str(anc[4]) == e["ancestor"]["cumulative"] and str(anc[3]) == e["ancestor"]["subsidy"]
|
||||
and anc[1] == e["ancestor"]["height"], {"got": anc, "want": e["ancestor"]})
|
||||
otip = by["orphan"][-1]
|
||||
check(name + " orphan tip = fixture", str(otip[4]) == e["orphan_tip"]["cumulative"] and str(otip[3]) == e["orphan_tip"]["subsidy"]
|
||||
and otip[1] == e["orphan_tip"]["height"], {"got": otip, "want": e["orphan_tip"]})
|
||||
rb_ = by["rollback"][-1]
|
||||
check(name + " after rollback = fixture", str(rb_[4]) == e["after_rollback"]["cumulative"], {"got": rb_, "want": e["after_rollback"]})
|
||||
fin = by["final"][0]
|
||||
check(name + " final = fixture", str(fin[4]) == e["final"]["cumulative"] and str(fin[3]) == e["final"]["subsidy"]
|
||||
and fin[1] == e["final"]["height"], {"got": fin, "want": e["final"]})
|
||||
nv = by["known_failed_no_undo"][0]
|
||||
check(name + " known-failed ledger = fixture's known-failed value", str(nv[4]) == e["known_failed_no_undo"]["cumulative"],
|
||||
{"got": nv, "want": e["known_failed_no_undo"]})
|
||||
check(name + " known-failed ledger is refused (differs from final)", nv[4] != fin[4], {"naive": nv[4], "final": fin[4]})
|
||||
if straight_text is not None:
|
||||
s = {r[0]: r for r in (ints(x, 0) for x in read_rows(straight_text, STRAIGHT_HEADER))}
|
||||
fin = [r for r in a if r[0] == "final"][0]
|
||||
srow = s.get(fin[1])
|
||||
check("final = straight chain at the same height", srow is not None and srow[3] == fin[4] and srow[2] == fin[3],
|
||||
{"final": fin, "straight": srow})
|
||||
res["equal"] = all(c["ok"] for c in res["checks"])
|
||||
return res
|
||||
|
||||
|
||||
def verdict(res):
|
||||
return ("EQUAL" if res["equal"] else "DIFFER") + " " + json.dumps(res)
|
||||
|
||||
|
||||
def selftest():
|
||||
"""Known-failed first: a corrupted cumulative, a dropped row and a no-undo ledger must each be reported."""
|
||||
good = "height,daa_score,subsidy,cumulative\n0,0,0,0\n1,0,10,10\n2,1,11,21\n"
|
||||
bad_cum = "height,daa_score,subsidy,cumulative\n0,0,0,0\n1,0,10,10\n2,1,11,22\n"
|
||||
short = "height,daa_score,subsidy,cumulative\n0,0,0,0\n1,0,10,10\n"
|
||||
r = compare_straight_text(good, bad_cum)
|
||||
assert not r["equal"] and r["first_difference"]["row"] == 3 and r["first_difference"]["columns"] == ["cumulative"], r
|
||||
r = compare_straight_text(good, short)
|
||||
assert not r["equal"] and r["first_difference"]["b"] is None, r
|
||||
fx = {"id": "t", "expected": {"ancestor": {"height": 1, "subsidy": "10", "cumulative": "10"},
|
||||
"orphan_tip": {"height": 2, "subsidy": "11", "cumulative": "21"},
|
||||
"after_rollback": {"cumulative": "10"},
|
||||
"final": {"height": 3, "subsidy": "12", "cumulative": "33"},
|
||||
"known_failed_no_undo": {"cumulative": "44"}}}
|
||||
led = ("step,height,daa_score,subsidy,cumulative\nancestor,1,0,10,10\norphan,2,1,11,21\nrollback,2,1,11,10\n"
|
||||
"replacement,2,1,11,21\nreplacement,3,2,12,33\nfinal,3,2,12,33\nknown_failed_no_undo,3,2,12,44\n")
|
||||
straight3 = good + "3,2,12,33\n"
|
||||
naive_led = led.replace("rollback,2,1,11,10\nreplacement,2,1,11,21\nreplacement,3,2,12,33\nfinal,3,2,12,33",
|
||||
"rollback,2,1,11,21\nreplacement,2,1,11,32\nreplacement,3,2,12,44\nfinal,3,2,12,44")
|
||||
r = compare_reorg_text(led, naive_led, fx, straight3)
|
||||
assert not r["equal"], r
|
||||
r = compare_reorg_text(naive_led, naive_led, fx, straight3)
|
||||
assert not r["equal"] and any(not c["ok"] and "final = fixture" in c["check"] for c in r["checks"]), r
|
||||
# then the known-equal case
|
||||
assert compare_straight_text(good, good)["equal"]
|
||||
r = compare_reorg_text(led, led, fx, straight3)
|
||||
assert r["equal"], r
|
||||
print("selftest: corrupted cumulative, dropped row and no-undo ledger each reported; equal inputs read EQUAL")
|
||||
|
||||
|
||||
def main(a):
|
||||
try:
|
||||
if len(a) == 4 and a[1] == "straight":
|
||||
res = compare_straight_text(open(a[2]).read(), open(a[3]).read())
|
||||
res.update(a=a[2], b=a[3])
|
||||
elif len(a) in (5, 6) and a[1] == "reorg":
|
||||
fx = json.load(open(a[4]))
|
||||
st = open(a[5]).read() if len(a) == 6 else None
|
||||
res = compare_reorg_text(open(a[2]).read(), open(a[3]).read(), fx, st)
|
||||
res.update(a=a[2], b=a[3])
|
||||
elif len(a) == 2 and a[1] == "selftest":
|
||||
selftest()
|
||||
return 0
|
||||
else:
|
||||
print(__doc__, file=sys.stderr)
|
||||
return 2
|
||||
except (ValueError, KeyError, IndexError) as e:
|
||||
print("BAD INPUT %s" % e)
|
||||
return 2
|
||||
print(verdict(res))
|
||||
return 0 if res["equal"] else 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main(sys.argv))
|
||||
47
tools/token-value/tv-02/fixtures/reorg-first-step-k2.json
Normal file
47
tools/token-value/tv-02/fixtures/reorg-first-step-k2.json
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
{
|
||||
"network": "devnet4",
|
||||
"written": "2026-10-09T08:40Z, by hand from the rules (rules-v0.1: the CURRENT schedule at 18 decimals, 1 block a second, genesis issues nothing, block h has DAA score h - 1), before either implementation ran a replay; the closed forms below were written by hand and their digits evaluated with bc on build-5 (a calculator on the written expressions, no replay code)",
|
||||
"symbols": {
|
||||
"F": "31688087810000000000 (the launch rate per DAA second, 3,168,808,781 x 10^10; at 1 block a second the full-rate block)",
|
||||
"H": "15844043905000000000 (F halved once at 18 decimals, floor(F / 2); F is even at 18 decimals, so no floor is lost here, unlike the 8-decimal 1,584,404,390)",
|
||||
"R": "2592000 (ramp seconds)",
|
||||
"X": "63115200 (the first step, two years of DAA seconds)",
|
||||
"ramp_term": "schedule(d) = floor(F (10R + 90d) / (100R)) = F/10 + floor(d G / 9) for d < R, G = F / 320000 = 99025274406250 = 9q + 1, q = 11002808267361, so schedule(d) = F/10 + d q + floor(d / 9)",
|
||||
"ramp_sum": "sum over d = 0..R-1 = R F / 10 + q R (R - 1) / 2 + 9 (0 + 1 + ... + 287999) = 45174523722296485498848000 (R = 9 x 288000)"
|
||||
},
|
||||
"id": "reorg-first-step-k2",
|
||||
"description": "the first halving at DAA X: the ancestor is height X-1 (DAA X-2); an orphan branch of 2 blocks (heights X, X+1: DAA X-1 full rate, DAA X the first halved block) is replaced by 3 (heights X, X+1, X+2)",
|
||||
"ancestor_height": 63115199,
|
||||
"orphan_len": 2,
|
||||
"replacement_len": 3,
|
||||
"expected": {
|
||||
"ancestor": {
|
||||
"height": 63115199,
|
||||
"daa_score": 63115198,
|
||||
"subsidy": "31688087810000000000",
|
||||
"cumulative": "1963038968176400675498848000",
|
||||
"derivation": "ramp_sum + (X - 1 - R) F: DAA 0..X-2"
|
||||
},
|
||||
"orphan_tip": {
|
||||
"height": 63115201,
|
||||
"subsidy": "15844043905000000000",
|
||||
"cumulative": "1963039015708532390498848000",
|
||||
"derivation": "ancestor + F + H"
|
||||
},
|
||||
"after_rollback": {
|
||||
"height": 63115199,
|
||||
"cumulative": "1963038968176400675498848000"
|
||||
},
|
||||
"final": {
|
||||
"height": 63115202,
|
||||
"daa_score": 63115201,
|
||||
"subsidy": "15844043905000000000",
|
||||
"cumulative": "1963039031552576295498848000",
|
||||
"derivation": "ancestor + F + 2H; equals the straight chain's cumulative at height X+2"
|
||||
},
|
||||
"known_failed_no_undo": {
|
||||
"cumulative": "1963039079084708010498848000",
|
||||
"derivation": "ancestor + (F + H) + (F + 2H): the orphan's F + H counted twice; must be refused"
|
||||
}
|
||||
}
|
||||
}
|
||||
47
tools/token-value/tv-02/fixtures/reorg-ramp-end-k1.json
Normal file
47
tools/token-value/tv-02/fixtures/reorg-ramp-end-k1.json
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
{
|
||||
"network": "devnet4",
|
||||
"written": "2026-10-09T08:40Z, by hand from the rules (rules-v0.1: the CURRENT schedule at 18 decimals, 1 block a second, genesis issues nothing, block h has DAA score h - 1), before either implementation ran a replay; the closed forms below were written by hand and their digits evaluated with bc on build-5 (a calculator on the written expressions, no replay code)",
|
||||
"symbols": {
|
||||
"F": "31688087810000000000 (the launch rate per DAA second, 3,168,808,781 x 10^10; at 1 block a second the full-rate block)",
|
||||
"H": "15844043905000000000 (F halved once at 18 decimals, floor(F / 2); F is even at 18 decimals, so no floor is lost here, unlike the 8-decimal 1,584,404,390)",
|
||||
"R": "2592000 (ramp seconds)",
|
||||
"X": "63115200 (the first step, two years of DAA seconds)",
|
||||
"ramp_term": "schedule(d) = floor(F (10R + 90d) / (100R)) = F/10 + floor(d G / 9) for d < R, G = F / 320000 = 99025274406250 = 9q + 1, q = 11002808267361, so schedule(d) = F/10 + d q + floor(d / 9)",
|
||||
"ramp_sum": "sum over d = 0..R-1 = R F / 10 + q R (R - 1) / 2 + 9 (0 + 1 + ... + 287999) = 45174523722296485498848000 (R = 9 x 288000)"
|
||||
},
|
||||
"id": "reorg-ramp-end-k1",
|
||||
"description": "ramp end at DAA R: the ancestor is the last ramp block (height R, DAA R-1); an orphan branch of 1 block (height R+1, DAA R, the first full-rate block) is replaced by 2 (heights R+1, R+2)",
|
||||
"ancestor_height": 2592000,
|
||||
"orphan_len": 1,
|
||||
"replacement_len": 2,
|
||||
"expected": {
|
||||
"ancestor": {
|
||||
"height": 2592000,
|
||||
"daa_score": 2591999,
|
||||
"subsidy": "31688076807191732638",
|
||||
"cumulative": "45174523722296485498848000",
|
||||
"derivation": "subsidy F/10 + (R-1) q + floor((R-1)/9) = F/10 + 2591999 q + 287999; cumulative ramp_sum"
|
||||
},
|
||||
"orphan_tip": {
|
||||
"height": 2592001,
|
||||
"subsidy": "31688087810000000000",
|
||||
"cumulative": "45174555410384295498848000",
|
||||
"derivation": "ramp_sum + F"
|
||||
},
|
||||
"after_rollback": {
|
||||
"height": 2592000,
|
||||
"cumulative": "45174523722296485498848000"
|
||||
},
|
||||
"final": {
|
||||
"height": 2592002,
|
||||
"daa_score": 2592001,
|
||||
"subsidy": "31688087810000000000",
|
||||
"cumulative": "45174587098472105498848000",
|
||||
"derivation": "ramp_sum + 2F; equals the straight chain's cumulative at height R+2"
|
||||
},
|
||||
"known_failed_no_undo": {
|
||||
"cumulative": "45174618786559915498848000",
|
||||
"derivation": "ramp_sum + 3F: a ledger that forgets to subtract the orphan counts F twice; must be refused"
|
||||
}
|
||||
}
|
||||
}
|
||||
2697
tools/token-value/tv-02/replay-node/Cargo.lock
generated
Normal file
2697
tools/token-value/tv-02/replay-node/Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
24
tools/token-value/tv-02/replay-node/Cargo.toml
Normal file
24
tools/token-value/tv-02/replay-node/Cargo.toml
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
[package]
|
||||
name = "tv02-replay-node"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
description = "TV-02 implementation A: the issued-supply replay in the node's language, calling the node's own issuance code (kaspa-consensus-core) per block"
|
||||
license = "MIT"
|
||||
publish = false
|
||||
|
||||
# A standalone workspace. The node fork is read by path from the repository's vendor/igneum-node (the fork checkout at
|
||||
# the commit the run names, outside version control, as pool/ and app/igneum-wallet read it). Nothing here changes the
|
||||
# node: the crate only calls kaspa-consensus-core's public issuance functions.
|
||||
[workspace]
|
||||
|
||||
[[bin]]
|
||||
name = "tv02-replay-node"
|
||||
path = "src/main.rs"
|
||||
|
||||
[dependencies]
|
||||
kaspa-consensus-core = { path = "../../../../vendor/igneum-node/consensus/core" }
|
||||
serde_json = "1"
|
||||
|
||||
[profile.release]
|
||||
overflow-checks = true
|
||||
debug-assertions = true
|
||||
430
tools/token-value/tv-02/replay-node/src/main.rs
Normal file
430
tools/token-value/tv-02/replay-node/src/main.rs
Normal file
|
|
@ -0,0 +1,430 @@
|
|||
//! TV-02 implementation A: the issued-supply replay in the node's language.
|
||||
//!
|
||||
//! Every per-block subsidy here is the node's own function, called through its public API at the commit the run
|
||||
//! names: `EmissionTable::block_subsidy` (the path `CoinbaseManager::calc_block_subsidy` takes, consensus/src/
|
||||
//! processes/coinbase.rs 262-266, and the value body validation compares the coinbase payload against). Every block
|
||||
//! is also read through the node's second, independent issuance path, `igneum::block_subsidy_units` (the u128 twin of
|
||||
//! the 33-entry table rule); a block where the two differ is counted and named in the summary.
|
||||
//!
|
||||
//! Model: a straight chain. Genesis (height 0, DAA score 0) issues nothing: it never enters the DAA window
|
||||
//! (consensus/src/processes/window.rs 176-179 puts it in mergeset_non_daa) and expected_coinbase_transaction skips
|
||||
//! non-DAA blues (coinbase.rs 139). So block 1 has DAA score 0 (difficulty.rs 31-32: the selected parent's score plus
|
||||
//! the mergeset's DAA blocks, none for block 1) and the block at height h >= 1 has DAA score h - 1, issuing the
|
||||
//! schedule's subsidy at that score. "cumulative" at height h is the issued supply of the blocks at heights 0..=h,
|
||||
//! the sum of the schedule over DAA scores 0..=h-1, in base units, exact (u128, overflow checks on). The node lane
|
||||
//! confirmed the genesis rule on bf60948a (9 October 2026, 09:25 UK).
|
||||
//!
|
||||
//! Commands (outputs are CSV with the header height,daa_score,subsidy,cumulative):
|
||||
//! straight <network> <out.csv> <labels.csv> <summary.json>
|
||||
//! reorg <fixture.json> <out.csv>
|
||||
//! genesis-payload <out.json>
|
||||
//! Networks: devnet4 (devnet4_params()), mainnet (MAINNET_PARAMS), devnet (DEVNET_PARAMS).
|
||||
|
||||
use kaspa_consensus_core::{
|
||||
config::params::{DEVNET_PARAMS, MAINNET_PARAMS, Params, TESTNET_PARAMS, devnet3_params, devnet4_params},
|
||||
emission::{EmissionTable, Tail},
|
||||
igneum,
|
||||
unit::{self, Amount},
|
||||
};
|
||||
use std::collections::BTreeMap;
|
||||
use std::io::Write;
|
||||
use std::sync::Mutex;
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
use std::time::Instant;
|
||||
|
||||
struct Net {
|
||||
name: String,
|
||||
params: Params,
|
||||
table: EmissionTable,
|
||||
bps: u64,
|
||||
decimals: u8,
|
||||
}
|
||||
|
||||
impl Net {
|
||||
fn new(name: &str) -> Net {
|
||||
let params = match name {
|
||||
"devnet4" => devnet4_params(),
|
||||
"mainnet" => MAINNET_PARAMS,
|
||||
"devnet" => DEVNET_PARAMS,
|
||||
other => panic!("unknown network {other} (devnet4, mainnet, devnet)"),
|
||||
};
|
||||
let table = params.emission.table();
|
||||
let bps = params.bps();
|
||||
let decimals = params.base_unit_decimals;
|
||||
Net { name: name.to_string(), params, table, bps, decimals }
|
||||
}
|
||||
|
||||
/// The node's subsidy for a block at `daa` (the coinbase manager's path).
|
||||
#[inline]
|
||||
fn subsidy(&self, daa: u64) -> u128 {
|
||||
self.table.block_subsidy(daa, self.bps)
|
||||
}
|
||||
|
||||
/// The node's second path (igneum.rs `block_subsidy_units`), defined for the CURRENT shape only.
|
||||
#[inline]
|
||||
fn subsidy_units(&self, daa: u64) -> u128 {
|
||||
igneum::block_subsidy_units(daa, self.bps, self.decimals).0
|
||||
}
|
||||
}
|
||||
|
||||
/// The row set's DAA scores (the README's rule, implemented here and independently in B): the probes, every
|
||||
/// transition's before/at/after, and one step past the terminal transition. Returns (sorted DAA scores, labels, the
|
||||
/// first DAA score that pays nothing). The CSV row of DAA score d is the block at height d + 1; genesis is its own row.
|
||||
fn rows(net: &Net) -> (Vec<u64>, BTreeMap<u64, String>, u64) {
|
||||
let s = &net.params.emission;
|
||||
let bps = net.bps;
|
||||
let mut labels: BTreeMap<u64, String> = BTreeMap::new();
|
||||
let add = |h: u64, l: String, labels: &mut BTreeMap<u64, String>| {
|
||||
labels.entry(h).and_modify(|e| { e.push_str("; "); e.push_str(&l) }).or_insert(l);
|
||||
};
|
||||
add(0, "daa 0 (block 1)".into(), &mut labels);
|
||||
add(1, "probe 1".into(), &mut labels);
|
||||
add(2, "probe 2".into(), &mut labels);
|
||||
if bps > 1 {
|
||||
add(bps - 1, "second 0 last block".into(), &mut labels);
|
||||
add(bps, "second 1 first block".into(), &mut labels);
|
||||
add(bps + 1, "second 1 second block".into(), &mut labels);
|
||||
}
|
||||
for d in 1..30u64 {
|
||||
add(d * 86_400 * bps, format!("ramp day {d}"), &mut labels);
|
||||
}
|
||||
add(bps * s.ramp_seconds / 2, "ramp midpoint".into(), &mut labels);
|
||||
let mut transitions: Vec<(u64, String)> = vec![(bps * s.ramp_seconds, "ramp end".into())];
|
||||
let mut zero = 0u64;
|
||||
for k in 1..=200u64 {
|
||||
let x = k * s.step_seconds * bps;
|
||||
transitions.push((x, format!("step {k}")));
|
||||
if net.subsidy(x) == 0 {
|
||||
zero = x;
|
||||
break;
|
||||
}
|
||||
}
|
||||
assert!(zero > 0, "the curve did not reach zero within 200 steps");
|
||||
for (x, l) in &transitions {
|
||||
add(x - 1, format!("before {l}"), &mut labels);
|
||||
add(*x, format!("at {l}"), &mut labels);
|
||||
add(x + 1, format!("after {l}"), &mut labels);
|
||||
}
|
||||
add(zero - 1, "terminal (last block with a subsidy)".into(), &mut labels);
|
||||
add(zero + s.step_seconds * bps, "one step past the terminal".into(), &mut labels);
|
||||
let heights: Vec<u64> = labels.keys().copied().collect();
|
||||
(heights, labels, zero)
|
||||
}
|
||||
|
||||
#[derive(Default, Clone, Copy)]
|
||||
struct Chunk {
|
||||
sum: u128,
|
||||
path_mismatch: u64,
|
||||
first_mismatch: Option<u64>,
|
||||
shape_violation: u64,
|
||||
}
|
||||
|
||||
/// Sums blocks `a..=b` one by one, with the two-path cross-check and the shape check (non-decreasing through the
|
||||
/// ramp, non-increasing after it).
|
||||
fn sum_chunk(net: &Net, a: u64, b: u64) -> Chunk {
|
||||
let ramp_end = net.bps * net.params.emission.ramp_seconds;
|
||||
let check_units = matches!(net.params.emission.tail, Tail::Cap);
|
||||
let mut c = Chunk::default();
|
||||
let mut prev = if a > 0 { Some(net.subsidy(a - 1)) } else { None };
|
||||
let mut d = a;
|
||||
loop {
|
||||
let s = net.subsidy(d);
|
||||
if check_units {
|
||||
let u = net.subsidy_units(d);
|
||||
if u != s {
|
||||
c.path_mismatch += 1;
|
||||
if c.first_mismatch.is_none() {
|
||||
c.first_mismatch = Some(d);
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Some(p) = prev {
|
||||
let ok = if d < ramp_end { s >= p } else if d - 1 >= ramp_end { s <= p } else { true };
|
||||
if !ok {
|
||||
c.shape_violation += 1;
|
||||
}
|
||||
}
|
||||
c.sum = c.sum.checked_add(s).expect("sum overflows u128");
|
||||
prev = Some(s);
|
||||
if d == b {
|
||||
break;
|
||||
}
|
||||
d += 1;
|
||||
}
|
||||
c
|
||||
}
|
||||
|
||||
/// Cumulative issued supply at each height of `heights` (sorted, distinct), every block from genesis summed once.
|
||||
fn cumulative_at(net: &Net, heights: &[u64]) -> (Vec<u128>, Chunk) {
|
||||
const CHUNK: u64 = 1 << 24;
|
||||
// segments: [0, h0], [h0+1, h1], ...; each split into chunks of at most CHUNK blocks
|
||||
let mut jobs: Vec<(usize, u64, u64)> = Vec::new();
|
||||
let mut lo = 0u64;
|
||||
for (i, &h) in heights.iter().enumerate() {
|
||||
if h < lo {
|
||||
continue;
|
||||
}
|
||||
let mut a = lo;
|
||||
while a <= h {
|
||||
let b = (a.saturating_add(CHUNK - 1)).min(h);
|
||||
jobs.push((i, a, b));
|
||||
a = b + 1;
|
||||
}
|
||||
lo = h + 1;
|
||||
}
|
||||
let next = AtomicUsize::new(0);
|
||||
let results: Mutex<Vec<(usize, Chunk)>> = Mutex::new(Vec::with_capacity(jobs.len()));
|
||||
let threads = std::thread::available_parallelism().map(|n| n.get()).unwrap_or(1);
|
||||
std::thread::scope(|sc| {
|
||||
for _ in 0..threads {
|
||||
sc.spawn(|| {
|
||||
loop {
|
||||
let j = next.fetch_add(1, Ordering::Relaxed);
|
||||
if j >= jobs.len() {
|
||||
break;
|
||||
}
|
||||
let (i, a, b) = jobs[j];
|
||||
let c = sum_chunk(net, a, b);
|
||||
results.lock().unwrap().push((i, c));
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
let mut seg = vec![0u128; heights.len()];
|
||||
let mut total = Chunk::default();
|
||||
for (i, c) in results.into_inner().unwrap() {
|
||||
seg[i] = seg[i].checked_add(c.sum).unwrap();
|
||||
total.path_mismatch += c.path_mismatch;
|
||||
total.shape_violation += c.shape_violation;
|
||||
total.first_mismatch = match (total.first_mismatch, c.first_mismatch) {
|
||||
(Some(x), Some(y)) => Some(x.min(y)),
|
||||
(x, y) => x.or(y),
|
||||
};
|
||||
}
|
||||
let mut cum = Vec::with_capacity(heights.len());
|
||||
let mut run = 0u128;
|
||||
for s in seg {
|
||||
run = run.checked_add(s).unwrap();
|
||||
cum.push(run);
|
||||
}
|
||||
(cum, total)
|
||||
}
|
||||
|
||||
fn write_csv(path: &str, rows: &[(u64, u64, u128, u128)]) {
|
||||
let mut f = std::fs::File::create(path).unwrap();
|
||||
writeln!(f, "height,daa_score,subsidy,cumulative").unwrap();
|
||||
for (h, d, s, c) in rows {
|
||||
writeln!(f, "{h},{d},{s},{c}").unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
fn straight(name: &str, out: &str, labels_out: &str, summary_out: &str) {
|
||||
let t0 = Instant::now();
|
||||
let net = Net::new(name);
|
||||
let (daas, labels, zero) = rows(&net);
|
||||
let (cum, chk) = cumulative_at(&net, &daas);
|
||||
// genesis issues nothing; the block of DAA score d sits at height d + 1
|
||||
let mut rows: Vec<(u64, u64, u128, u128)> = vec![(0, 0, 0, 0)];
|
||||
rows.extend(daas.iter().zip(cum.iter()).map(|(&d, &c)| (d + 1, d, net.subsidy(d), c)));
|
||||
write_csv(out, &rows);
|
||||
let mut lf = std::fs::File::create(labels_out).unwrap();
|
||||
writeln!(lf, "height,label").unwrap();
|
||||
writeln!(lf, "0,\"genesis (issues nothing)\"").unwrap();
|
||||
for (d, l) in &labels {
|
||||
writeln!(lf, "{},\"{l}\"", d + 1).unwrap();
|
||||
}
|
||||
let terminal = zero; // the height of the block of DAA zero - 1, the last that pays
|
||||
let issued_at_terminal = rows.iter().find(|r| r.0 == terminal).unwrap().3;
|
||||
let last = rows.last().unwrap();
|
||||
let cap = igneum::supply_cap_units(net.decimals);
|
||||
let s = &net.params.emission;
|
||||
let summary = serde_json::json!({
|
||||
"implementation": "A (Rust, the node's issuance code by path)",
|
||||
"node_commit": std::env::var("TV02_NODE_COMMIT").unwrap_or_default(),
|
||||
"network": net.name,
|
||||
"network_id": net.params.net.to_string(),
|
||||
"bps": net.bps,
|
||||
"base_unit_decimals": net.decimals,
|
||||
"schedule": {
|
||||
"launch_rate": s.launch_rate.to_string(),
|
||||
"ramp_seconds": s.ramp_seconds,
|
||||
"ramp_start_percent": s.ramp_start_percent,
|
||||
"step_seconds": s.step_seconds,
|
||||
"step_decay_q32": s.step_decay_q32,
|
||||
"tail": format!("{:?}", s.tail),
|
||||
},
|
||||
"blocks_replayed_per_block": last.0 + 1,
|
||||
"genesis_rule": "genesis issues nothing; height h >= 1 has DAA score h - 1 (window.rs 176-179, difficulty.rs 31-32, coinbase.rs 139)",
|
||||
"rows": rows.len(),
|
||||
"first_zero_subsidy_daa": zero,
|
||||
"first_zero_subsidy_height": zero + 1,
|
||||
"terminal_height": terminal,
|
||||
"issued_at_terminal": issued_at_terminal.to_string(),
|
||||
"issued_one_step_past_terminal": last.3.to_string(),
|
||||
"supply_cap_units": cap.to_string(),
|
||||
"cap_minus_issued": (cap as i128 - issued_at_terminal as i128).to_string(),
|
||||
"cap_breach": issued_at_terminal > cap,
|
||||
"issuance_after_terminal": (last.3 - issued_at_terminal).to_string(),
|
||||
"node_two_paths_mismatched_blocks": chk.path_mismatch,
|
||||
"node_two_paths_first_mismatch_height": chk.first_mismatch,
|
||||
"shape_violations": chk.shape_violation,
|
||||
"far_future_subsidy_u64_max_div_2": net.subsidy(u64::MAX / 2).to_string(),
|
||||
"seconds": t0.elapsed().as_secs_f64(),
|
||||
});
|
||||
std::fs::write(summary_out, serde_json::to_string_pretty(&summary).unwrap() + "\n").unwrap();
|
||||
println!("{}", serde_json::to_string(&summary).unwrap());
|
||||
}
|
||||
|
||||
/// The reorg fixture: the common ancestor, an orphan branch of k blocks, rolled back and replaced by k+1. The
|
||||
/// ledger keeps one record per applied block and a rollback subtracts exactly the records it pops. The known-failed
|
||||
/// ledger (rollback that forgets to subtract) is run beside it and written as its own row.
|
||||
fn reorg(fixture: &str, out: &str) {
|
||||
let fx: serde_json::Value = serde_json::from_str(&std::fs::read_to_string(fixture).unwrap()).unwrap();
|
||||
let net = Net::new(fx["network"].as_str().unwrap());
|
||||
let anc = fx["ancestor_height"].as_u64().unwrap();
|
||||
let k = fx["orphan_len"].as_u64().unwrap();
|
||||
let k1 = fx["replacement_len"].as_u64().unwrap();
|
||||
assert_eq!(k1, k + 1, "the fixture replaces k blocks by k+1");
|
||||
assert!(anc >= 1, "the ancestor is a block after genesis");
|
||||
// heights; the block at height h >= 1 has DAA score h - 1
|
||||
let (cum, chk) = cumulative_at(&net, &[anc - 1]);
|
||||
assert_eq!(chk.path_mismatch, 0);
|
||||
let mut rows: Vec<(String, u64, u64, u128, u128)> = Vec::new();
|
||||
let mut ledger: Vec<(u64, u128)> = Vec::new();
|
||||
let mut total = cum[0];
|
||||
let mut naive = cum[0];
|
||||
rows.push(("ancestor".into(), anc, anc - 1, net.subsidy(anc - 1), total));
|
||||
for h in anc + 1..=anc + k {
|
||||
let s = net.subsidy(h - 1);
|
||||
ledger.push((h, s));
|
||||
total += s;
|
||||
naive += s;
|
||||
rows.push(("orphan".into(), h, h - 1, s, total));
|
||||
}
|
||||
for _ in 0..k {
|
||||
let (h, s) = ledger.pop().unwrap();
|
||||
total -= s;
|
||||
rows.push(("rollback".into(), h, h - 1, s, total));
|
||||
}
|
||||
for h in anc + 1..=anc + k1 {
|
||||
let s = net.subsidy(h - 1);
|
||||
ledger.push((h, s));
|
||||
total += s;
|
||||
naive += s;
|
||||
rows.push(("replacement".into(), h, h - 1, s, total));
|
||||
}
|
||||
let tip = anc + k1;
|
||||
rows.push(("final".into(), tip, tip - 1, net.subsidy(tip - 1), total));
|
||||
rows.push(("known_failed_no_undo".into(), tip, tip - 1, net.subsidy(tip - 1), naive));
|
||||
let mut f = std::fs::File::create(out).unwrap();
|
||||
writeln!(f, "step,height,daa_score,subsidy,cumulative").unwrap();
|
||||
for (st, h, d, s, c) in &rows {
|
||||
writeln!(f, "{st},{h},{d},{s},{c}").unwrap();
|
||||
}
|
||||
println!("reorg {} final {} naive {}", fx["id"].as_str().unwrap_or(""), total, naive);
|
||||
}
|
||||
|
||||
/// What the node reads as the subsidy field of each compiled genesis coinbase payload at that network's own unit
|
||||
/// (the slicing of `CoinbaseManager::deserialize_coinbase_payload`, coinbase.rs 231-236: blue score u64, the subsidy
|
||||
/// at `amount_wire_len()`, script version u16, script length u8), beside the schedule's subsidy at DAA 0.
|
||||
fn genesis_payload(out: &str) {
|
||||
let mut v = Vec::new();
|
||||
for (name, p) in [
|
||||
("mainnet", MAINNET_PARAMS),
|
||||
("testnet-1", TESTNET_PARAMS),
|
||||
("devnet", DEVNET_PARAMS),
|
||||
("devnet-3", devnet3_params()),
|
||||
("devnet-4", devnet4_params()),
|
||||
] {
|
||||
let payload = p.genesis.coinbase_payload;
|
||||
let row = unit::with_decimals_for_test(p.base_unit_decimals, || {
|
||||
let w = unit::amount_wire_len();
|
||||
let field = Amount::from_consensus_bytes(&payload[8..8 + w]).map(|a| a.0);
|
||||
let version = u16::from_le_bytes([payload[8 + w], payload[9 + w]]);
|
||||
let spk_len = payload[10 + w];
|
||||
serde_json::json!({
|
||||
"network": name,
|
||||
"base_unit_decimals": p.base_unit_decimals,
|
||||
"amount_wire_len": w,
|
||||
"payload_len": payload.len(),
|
||||
"subsidy_field_read_at_network_unit": field.map(|x| x.to_string()),
|
||||
"subsidy_field_equals_one_ign_at_network_unit": field == Some(unit::unit(p.base_unit_decimals)),
|
||||
"script_version_read": version,
|
||||
"script_len_read": spk_len,
|
||||
"one_ign_at_network_unit": unit::unit(p.base_unit_decimals).to_string(),
|
||||
"max_amount_at_network_unit": unit::max_amount().to_string(),
|
||||
"schedule_subsidy_at_daa_0": p.emission.table().block_subsidy(0, p.bps()).to_string(),
|
||||
})
|
||||
});
|
||||
v.push(row);
|
||||
}
|
||||
let s = serde_json::to_string_pretty(&v).unwrap();
|
||||
std::fs::write(out, s.clone() + "\n").unwrap();
|
||||
println!("{s}");
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let a: Vec<String> = std::env::args().collect();
|
||||
match a.get(1).map(|s| s.as_str()) {
|
||||
Some("straight") if a.len() == 6 => straight(&a[2], &a[3], &a[4], &a[5]),
|
||||
Some("reorg") if a.len() == 4 => reorg(&a[2], &a[3]),
|
||||
Some("genesis-payload") if a.len() == 3 => genesis_payload(&a[2]),
|
||||
_ => {
|
||||
eprintln!("usage: straight <network> <out.csv> <labels.csv> <summary.json> | reorg <fixture.json> <out.csv> | genesis-payload <out.json>");
|
||||
std::process::exit(2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
const F: u128 = 31_688_087_810_000_000_000;
|
||||
const H: u128 = 15_844_043_905_000_000_000;
|
||||
const RAMP_SUM: u128 = 45_174_523_722_296_485_498_848_000; // the fixture's hand-written closed form
|
||||
|
||||
/// The known-failed case first: a ledger that forgets to subtract the orphan on rollback ends above the straight
|
||||
/// chain, by exactly the orphan's subsidy; the ledger here subtracts it and lands on the straight chain.
|
||||
#[test]
|
||||
fn a_rollback_that_forgets_the_orphan_double_counts_it() {
|
||||
let net = Net::new("devnet4");
|
||||
let r = 2_592_000u64;
|
||||
let (cum, _) = cumulative_at(&net, &[r - 1, r + 1]);
|
||||
assert_eq!(cum[0], RAMP_SUM, "the ramp's closed form, written by hand");
|
||||
let orphan = net.subsidy(r);
|
||||
let naive = cum[0] + orphan + net.subsidy(r) + net.subsidy(r + 1);
|
||||
assert_ne!(naive, cum[1], "the no-undo ledger must not equal the straight chain");
|
||||
assert_eq!(naive - cum[1], orphan);
|
||||
assert_eq!(cum[0] + net.subsidy(r) + net.subsidy(r + 1), cum[1]);
|
||||
}
|
||||
|
||||
/// The hand-written boundary values of the fixtures: the last ramp block, the full rate, the first halving at 18
|
||||
/// decimals (F is even there, so floor(F / 2) loses nothing, unlike 3,168,808,781 >> 1 at 8 decimals).
|
||||
#[test]
|
||||
fn boundary_subsidies_are_the_hand_written_ones() {
|
||||
let net = Net::new("devnet4");
|
||||
assert_eq!(net.subsidy(0), F / 10);
|
||||
assert_eq!(net.subsidy(2_591_999), 31_688_076_807_191_732_638);
|
||||
assert_eq!(net.subsidy(2_592_000), F);
|
||||
assert_eq!(net.subsidy(63_115_199), F);
|
||||
assert_eq!(net.subsidy(63_115_200), H);
|
||||
let dev = Net::new("devnet");
|
||||
assert_eq!(dev.subsidy(63_115_200), 1_584_404_390);
|
||||
assert_ne!(dev.subsidy(63_115_200) * 10_000_000_000, H, "the 8-decimal halving floors; the 18-decimal one does not");
|
||||
}
|
||||
|
||||
/// The node's two issuance paths agree at every transition's before/at/after on the three networks.
|
||||
#[test]
|
||||
fn the_node_paths_agree_at_every_row() {
|
||||
for n in ["devnet4", "mainnet", "devnet"] {
|
||||
let net = Net::new(n);
|
||||
let (daas, _, zero) = rows(&net);
|
||||
for d in daas {
|
||||
assert_eq!(net.subsidy(d), net.subsidy_units(d), "{n} daa {d}");
|
||||
}
|
||||
assert_eq!(net.subsidy(zero), 0);
|
||||
assert!(net.subsidy(zero - 1) > 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
319
tools/token-value/tv-02/replay-spec/replay_spec.py
Executable file
319
tools/token-value/tv-02/replay-spec/replay_spec.py
Executable file
|
|
@ -0,0 +1,319 @@
|
|||
#!/usr/bin/env python3
|
||||
"""TV-02 implementation B: the issued-supply replay written from the supply rules in words, standalone Python 3.
|
||||
|
||||
No code is shared with implementation A (the Rust crate that calls the node). B reads a rules file (the interim
|
||||
transcription of the litepaper and economics page, rules-v0-litepaper.json, or the TV-01 lane's supply-spec.json
|
||||
through `load_rules`) and computes every quantity with Python integers.
|
||||
|
||||
Method (deliberately not A's): A sums every block one by one. B groups blocks. Inside the ramp every block of one
|
||||
DAA second pays the same, so B sums seconds and multiplies by the blocks in each second; after the ramp every block
|
||||
of one step pays the same, so B multiplies the step's subsidy by the number of its blocks. Only the partial second
|
||||
or partial step at the end of a range is counted block by block.
|
||||
|
||||
Model: a straight chain, genesis at height 0. Two rules the words must state, read from the rules file:
|
||||
genesis_issues ("schedule_at_daa_0" or "nothing") and daa_of_height ("height": block h has DAA score h, or
|
||||
"height_minus_one": block h >= 1 has DAA score h - 1). cumulative(h) is the issued supply of the blocks at heights 0..h.
|
||||
|
||||
Usage:
|
||||
replay_spec.py straight <rules.json> <network> <out.csv> <labels.csv> <summary.json>
|
||||
replay_spec.py reorg <rules.json> <fixture.json> <out.csv>
|
||||
replay_spec.py selftest
|
||||
"""
|
||||
import csv
|
||||
import json
|
||||
import sys
|
||||
import time
|
||||
|
||||
|
||||
class Schedule:
|
||||
def __init__(self, rules, net):
|
||||
r = rules["rules"]
|
||||
n = rules["networks"][net]
|
||||
self.net = net
|
||||
self.bps = int(n["bps"])
|
||||
self.decimals = int(n["decimals"])
|
||||
self.year = int(r["year_seconds"])
|
||||
if "launch_rate_per_second" in r:
|
||||
# a spec that states the launch rate as an integer at the network's unit (the TV-01 spec)
|
||||
self.rate0 = int(r["launch_rate_per_second"])
|
||||
else:
|
||||
fd = int(r["launch_rate_floor_decimals"])
|
||||
floor_rate = int(r["year_one_emission_ign"]) * 10 ** fd // self.year
|
||||
if self.decimals >= fd:
|
||||
self.rate0 = floor_rate * 10 ** (self.decimals - fd)
|
||||
else:
|
||||
self.rate0 = int(r["year_one_emission_ign"]) * 10 ** self.decimals // self.year
|
||||
# the step multiplier in Q32 when the rules state one (2^31 is a halving); absent, a halving
|
||||
self.decay_q32 = int(r.get("step_decay_q32", 2 ** 31))
|
||||
self.step = int(r["step_seconds"])
|
||||
self.ramp_s = int(r["ramp_seconds"])
|
||||
self.ramp_start = int(r["ramp_start_percent"])
|
||||
self.order = r["per_block_order"]
|
||||
if r["tail"] != "cap":
|
||||
raise SystemExit("B replays a capped schedule only; tail %r is outside TV-02's ratified D01 policy" % r["tail"])
|
||||
self.cap = int(r["supply_cap_ign"]) * 10 ** self.decimals
|
||||
self.genesis_issues = r.get("genesis_issues", "schedule_at_daa_0")
|
||||
self.daa_of_height = r.get("daa_of_height", "height")
|
||||
if self.genesis_issues not in ("schedule_at_daa_0", "nothing"):
|
||||
raise SystemExit("unknown genesis_issues %r" % self.genesis_issues)
|
||||
if self.daa_of_height not in ("height", "height_minus_one"):
|
||||
raise SystemExit("unknown daa_of_height %r" % self.daa_of_height)
|
||||
|
||||
# --- the per-block rule, from the words ---
|
||||
def rate(self, k):
|
||||
"""Per-second rate in step k: the launch rate times the step multiplier k times, floored each time (a halving: rate0 // 2**k)."""
|
||||
x = self.rate0
|
||||
for _ in range(k):
|
||||
x = x * self.decay_q32 // 2 ** 32
|
||||
if x == 0:
|
||||
break
|
||||
return x
|
||||
|
||||
def ramp(self, x, s):
|
||||
if s >= self.ramp_s:
|
||||
return x
|
||||
num = self.ramp_start * self.ramp_s + (100 - self.ramp_start) * s
|
||||
return x * num // (100 * self.ramp_s)
|
||||
|
||||
def subsidy_of_second(self, s):
|
||||
k = s // self.step
|
||||
if self.order == "divide_then_ramp":
|
||||
return self.ramp(self.rate(k) // self.bps, s)
|
||||
if self.order == "ramp_then_divide":
|
||||
return self.ramp(self.rate(k), s) // self.bps
|
||||
raise SystemExit("unknown per_block_order %r" % self.order)
|
||||
|
||||
def subsidy(self, d):
|
||||
return self.subsidy_of_second(d // self.bps)
|
||||
|
||||
def zero_step(self):
|
||||
"""The first step k >= 1 whose blocks pay nothing."""
|
||||
k = 1
|
||||
while self.subsidy_of_second(k * self.step) != 0:
|
||||
k += 1
|
||||
if k > 1000:
|
||||
raise SystemExit("the curve does not reach zero")
|
||||
return k
|
||||
|
||||
# --- grouped sums ---
|
||||
def blocks_in_seconds(self, s0, s1, h):
|
||||
"""Number of blocks with height <= h whose second lies in [s0, s1)."""
|
||||
lo = s0 * self.bps
|
||||
hi = min(s1 * self.bps - 1, h)
|
||||
return max(0, hi - lo + 1)
|
||||
|
||||
def genesis_amount(self):
|
||||
return self.subsidy(0) if self.genesis_issues == "schedule_at_daa_0" else 0
|
||||
|
||||
def daa(self, h):
|
||||
"""DAA score of the block at height h >= 1."""
|
||||
return h if self.daa_of_height == "height" else h - 1
|
||||
|
||||
def height_of_daa(self, d):
|
||||
return d if self.daa_of_height == "height" else d + 1
|
||||
|
||||
def block_subsidy_at_height(self, h):
|
||||
return self.genesis_amount() if h == 0 else self.subsidy(self.daa(h))
|
||||
|
||||
def issued(self, h):
|
||||
"""Issued supply of the blocks at heights 0..h: genesis, then blocks 1..h at their DAA scores."""
|
||||
if h == 0:
|
||||
return self.genesis_amount()
|
||||
lo, hi = self.daa(1), self.daa(h)
|
||||
return self.genesis_amount() + self.cumulative(hi) - self.cumulative(lo - 1)
|
||||
|
||||
def cumulative(self, h):
|
||||
"""Sum of the schedule over DAA scores 0..h."""
|
||||
if h < 0:
|
||||
return 0
|
||||
last_s = h // self.bps
|
||||
total = 0
|
||||
# the ramp, second by second (the whole ramp summed once and kept, when h lies past it)
|
||||
if last_s >= self.ramp_s:
|
||||
if getattr(self, "_ramp_total", None) is None:
|
||||
self._ramp_total = sum(self.bps * self.subsidy_of_second(s) for s in range(self.ramp_s))
|
||||
total += self._ramp_total
|
||||
else:
|
||||
for s in range(0, last_s + 1):
|
||||
total += self.blocks_in_seconds(s, s + 1, h) * self.subsidy_of_second(s)
|
||||
# after the ramp, step by step
|
||||
k = self.ramp_s // self.step
|
||||
while True:
|
||||
s0 = max(self.ramp_s, k * self.step)
|
||||
s1 = (k + 1) * self.step
|
||||
if s0 > last_s:
|
||||
break
|
||||
n = self.blocks_in_seconds(s0, s1, h)
|
||||
total += n * self.subsidy_of_second(s0)
|
||||
k += 1
|
||||
return total
|
||||
|
||||
# --- the row set (the README's rule) ---
|
||||
def rows(self):
|
||||
bps = self.bps
|
||||
labels = {}
|
||||
|
||||
def add(h, label):
|
||||
labels[h] = labels[h] + "; " + label if h in labels else label
|
||||
|
||||
add(0, "daa 0")
|
||||
add(1, "probe 1")
|
||||
add(2, "probe 2")
|
||||
if bps > 1:
|
||||
add(bps - 1, "second 0 last block")
|
||||
add(bps, "second 1 first block")
|
||||
add(bps + 1, "second 1 second block")
|
||||
for d in range(1, 30):
|
||||
add(d * 86400 * bps, "ramp day %d" % d)
|
||||
add(bps * self.ramp_s // 2, "ramp midpoint")
|
||||
kz = self.zero_step()
|
||||
transitions = [(bps * self.ramp_s, "ramp end")] + [(k * self.step * bps, "step %d" % k) for k in range(1, kz + 1)]
|
||||
for x, l in transitions:
|
||||
add(x - 1, "before " + l)
|
||||
add(x, "at " + l)
|
||||
add(x + 1, "after " + l)
|
||||
zero = kz * self.step * bps
|
||||
add(zero - 1, "terminal (last block with a subsidy)")
|
||||
add(zero + self.step * bps, "one step past the terminal")
|
||||
hl = {}
|
||||
for d, l in labels.items():
|
||||
h = self.height_of_daa(d)
|
||||
hl[h] = (hl[h] + "; " + l) if h in hl else l
|
||||
hl[0] = ("genesis; " + hl[0]) if 0 in hl else "genesis (issues nothing)"
|
||||
return sorted(hl), hl, zero
|
||||
|
||||
|
||||
def load_rules(path):
|
||||
with open(path) as f:
|
||||
doc = json.load(f)
|
||||
if "rules" in doc and "networks" in doc:
|
||||
return doc
|
||||
raise SystemExit("rules file %s has no rules/networks block; the TV-01 spec adapter maps it in tv01_adapter.py" % path)
|
||||
|
||||
|
||||
def straight(rules_path, net, out, labels_out, summary_out):
|
||||
t0 = time.time()
|
||||
rules = load_rules(rules_path)
|
||||
sc = Schedule(rules, net)
|
||||
heights, labels, zero = sc.rows()
|
||||
with open(out, "w", newline="") as f:
|
||||
w = csv.writer(f, lineterminator="\n")
|
||||
w.writerow(["height", "daa_score", "subsidy", "cumulative"])
|
||||
cum = {}
|
||||
for h in heights:
|
||||
cum[h] = sc.issued(h)
|
||||
w.writerow([h, 0 if h == 0 else sc.daa(h), sc.block_subsidy_at_height(h), cum[h]])
|
||||
with open(labels_out, "w", newline="") as f:
|
||||
w = csv.writer(f, lineterminator="\n", quoting=csv.QUOTE_NONNUMERIC)
|
||||
f.write("height,label\n")
|
||||
for h in heights:
|
||||
f.write('%d,"%s"\n' % (h, labels[h]))
|
||||
term = sc.height_of_daa(zero - 1)
|
||||
summary = {
|
||||
"implementation": "B (Python 3, from the rules in words)",
|
||||
"rules_file": rules_path,
|
||||
"rules_id": rules.get("id"),
|
||||
"network": net,
|
||||
"bps": sc.bps,
|
||||
"base_unit_decimals": sc.decimals,
|
||||
"launch_rate": str(sc.rate0),
|
||||
"first_zero_subsidy_daa": zero,
|
||||
"first_zero_subsidy_height": sc.height_of_daa(zero),
|
||||
"genesis_issues": sc.genesis_issues,
|
||||
"daa_of_height": sc.daa_of_height,
|
||||
"terminal_height": term,
|
||||
"issued_at_terminal": str(cum[term]),
|
||||
"issued_one_step_past_terminal": str(cum[heights[-1]]),
|
||||
"supply_cap_units": str(sc.cap),
|
||||
"cap_minus_issued": str(sc.cap - cum[term]),
|
||||
"cap_breach": cum[term] > sc.cap,
|
||||
"issuance_after_terminal": str(cum[heights[-1]] - cum[term]),
|
||||
"rows": len(heights),
|
||||
"seconds": round(time.time() - t0, 3),
|
||||
}
|
||||
with open(summary_out, "w") as f:
|
||||
json.dump(summary, f, indent=2)
|
||||
f.write("\n")
|
||||
print(json.dumps(summary))
|
||||
|
||||
|
||||
def reorg(rules_path, fixture_path, out):
|
||||
rules = load_rules(rules_path)
|
||||
with open(fixture_path) as f:
|
||||
fx = json.load(f)
|
||||
sc = Schedule(rules, fx["network"])
|
||||
anc = int(fx["ancestor_height"])
|
||||
k = int(fx["orphan_len"])
|
||||
k1 = int(fx["replacement_len"])
|
||||
if k1 != k + 1:
|
||||
raise SystemExit("the fixture replaces k blocks by k+1")
|
||||
total = sc.issued(anc)
|
||||
naive = total
|
||||
ledger = []
|
||||
rows = [("ancestor", anc, sc.block_subsidy_at_height(anc), total)]
|
||||
for h in range(anc + 1, anc + k + 1):
|
||||
s = sc.block_subsidy_at_height(h)
|
||||
ledger.append((h, s))
|
||||
total += s
|
||||
naive += s
|
||||
rows.append(("orphan", h, s, total))
|
||||
for _ in range(k):
|
||||
h, s = ledger.pop()
|
||||
total -= s
|
||||
rows.append(("rollback", h, s, total))
|
||||
for h in range(anc + 1, anc + k1 + 1):
|
||||
s = sc.block_subsidy_at_height(h)
|
||||
ledger.append((h, s))
|
||||
total += s
|
||||
naive += s
|
||||
rows.append(("replacement", h, s, total))
|
||||
tip = anc + k1
|
||||
rows.append(("final", tip, sc.block_subsidy_at_height(tip), total))
|
||||
rows.append(("known_failed_no_undo", tip, sc.block_subsidy_at_height(tip), naive))
|
||||
with open(out, "w") as f:
|
||||
f.write("step,height,daa_score,subsidy,cumulative\n")
|
||||
for st, h, s, c in rows:
|
||||
f.write("%s,%d,%d,%d,%d\n" % (st, h, 0 if h == 0 else sc.daa(h), s, c))
|
||||
print("reorg %s final %d naive %d" % (fx.get("id", ""), total, naive))
|
||||
|
||||
|
||||
def selftest():
|
||||
"""B's own known-failed case first, then its cross-check of the grouped sum against a block-by-block sum."""
|
||||
rules = {
|
||||
"rules": {"year_seconds": 31557600, "year_one_emission_ign": 1000000000, "launch_rate_floor_decimals": 8,
|
||||
"step_seconds": 600, "ramp_seconds": 50, "ramp_start_percent": 10, "per_block_order": "divide_then_ramp",
|
||||
"tail": "cap", "supply_cap_ign": 4000000000},
|
||||
"networks": {"t1": {"bps": 1, "decimals": 18}, "t3": {"bps": 3, "decimals": 18}, "t10": {"bps": 10, "decimals": 8}},
|
||||
}
|
||||
# known-failed: a grouped sum that forgets the partial last second must disagree with the block-by-block sum
|
||||
sc = Schedule(rules, "t3")
|
||||
h = 3 * 20 + 1
|
||||
brute = sum(sc.subsidy(d) for d in range(h + 1))
|
||||
wrong = sum(3 * sc.subsidy_of_second(s) for s in range(h // 3))
|
||||
assert wrong != brute, "the known-failed grouping was not caught"
|
||||
for net in ("t1", "t3", "t10"):
|
||||
sc = Schedule(rules, net)
|
||||
run = 0
|
||||
end = sc.bps * sc.step * 4 + 7
|
||||
for d in range(end):
|
||||
run += sc.subsidy(d)
|
||||
if d % 97 == 0 or d in (sc.bps * sc.ramp_s - 1, sc.bps * sc.ramp_s, sc.bps * sc.step - 1, sc.bps * sc.step):
|
||||
assert sc.cumulative(d) == run, (net, d, sc.cumulative(d), run)
|
||||
assert sc.rate(3) == sc.rate0 // 8
|
||||
print("selftest: known-failed grouping caught; grouped sum equals the block-by-block sum on 3 networks")
|
||||
|
||||
|
||||
def main(a):
|
||||
if len(a) == 7 and a[1] == "straight":
|
||||
straight(a[2], a[3], a[4], a[5], a[6])
|
||||
elif len(a) == 5 and a[1] == "reorg":
|
||||
reorg(a[2], a[3], a[4])
|
||||
elif len(a) == 2 and a[1] == "selftest":
|
||||
selftest()
|
||||
else:
|
||||
print(__doc__, file=sys.stderr)
|
||||
sys.exit(2)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main(sys.argv)
|
||||
42
tools/token-value/tv-02/replay-spec/rules-tv01-spec.json
Normal file
42
tools/token-value/tv-02/replay-spec/rules-tv01-spec.json
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
{
|
||||
"id": "tv02-rules-from-tv01-spec",
|
||||
"status": "B's rules mapped from the TV-01 supply spec by tv01_adapter.py",
|
||||
"spec_path": "replay-spec/supply-spec-9cd09130.json",
|
||||
"spec_sha256": "3ffdd3084aff06d963df6c326cda88719d576f07d9cbfb56079de1a8b40c4403",
|
||||
"spec_version": "1.0.0",
|
||||
"rules": {
|
||||
"year_seconds": 31557600,
|
||||
"launch_rate_per_second": "31688087810000000000",
|
||||
"step_seconds": 63115200,
|
||||
"step_decay_q32": 2147483648,
|
||||
"ramp_seconds": 2592000,
|
||||
"ramp_start_percent": 10,
|
||||
"per_block_order": "divide_then_ramp",
|
||||
"tail": "cap",
|
||||
"supply_cap_ign": 4000000000,
|
||||
"genesis_issues": "nothing",
|
||||
"daa_of_height": "height_minus_one"
|
||||
},
|
||||
"networks": {
|
||||
"mainnet": {
|
||||
"bps": 10,
|
||||
"decimals": 18,
|
||||
"source": "supply-spec.json schedules[mainnet]"
|
||||
},
|
||||
"devnet4": {
|
||||
"bps": 1,
|
||||
"decimals": 18,
|
||||
"source": "supply-spec.json schedules[devnet-4]"
|
||||
}
|
||||
},
|
||||
"spec_terminal_supply": {
|
||||
"mainnet": {
|
||||
"terminal_supply": "3963038999610200467816224000",
|
||||
"last_paying_block_height": 39131424000
|
||||
},
|
||||
"devnet4": {
|
||||
"terminal_supply": "3963038999610200483289816000",
|
||||
"last_paying_block_height": 4102488000
|
||||
}
|
||||
}
|
||||
}
|
||||
47
tools/token-value/tv-02/replay-spec/rules-v0-litepaper.json
Normal file
47
tools/token-value/tv-02/replay-spec/rules-v0-litepaper.json
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
{
|
||||
"id": "tv02-rules-v0-litepaper",
|
||||
"status": "interim: written from the published words before the TV-01 supply spec landed; replaced by the spec when it lands",
|
||||
"sources": [
|
||||
"site/litepaper.html, Economics > Supply: hard cap of 4 billion IGN, emission starts at 1 billion a year and halves every two years for ever, ramps from 10% to 100% over the first 30 days",
|
||||
"site/litepaper.html, Finality: emission per block is a schedule keyed to difficulty-adjusted time, every block in the window is paid at that rate",
|
||||
"site/economics.html, Emission table: a year is 31,557,600 seconds; launch rate the literal 3,168,808,781 (10^9 IGN x 10^8 / 31,557,600 floored) widened to 18 decimals; ramp 2,592,000 s from 10 percent; step 63,115,200 s, the rate halved each step; tail none, the curve runs to zero",
|
||||
"site/economics.html, Units and decimals: the Igneum 2.0 devnet and mainnet are 18 decimals"
|
||||
],
|
||||
"rules": {
|
||||
"year_seconds": 31557600,
|
||||
"year_one_emission_ign": 1000000000,
|
||||
"launch_rate_floor_decimals": 8,
|
||||
"launch_rate_rule": "rate0 = floor(year_one_emission_ign * 10^launch_rate_floor_decimals / year_seconds) * 10^(decimals - launch_rate_floor_decimals)",
|
||||
"step_seconds": 63115200,
|
||||
"step_rule": "rate(k) = floor(rate(k-1) / 2), at the network's unit",
|
||||
"ramp_seconds": 2592000,
|
||||
"ramp_start_percent": 10,
|
||||
"ramp_rule": "ramped = floor(x * (start * R + (100 - start) * s) / (100 * R)) for s < R, else x",
|
||||
"seconds_rule": "s = floor(daa_score / bps)",
|
||||
"per_block_order": "divide_then_ramp",
|
||||
"per_block_order_source": "ASSUMED: the words give a per-second rate and blocks per second but not the order of the floor; the natural reading (the second's per-block share, then the ramp) is taken and flagged for the spec",
|
||||
"tail": "cap",
|
||||
"supply_cap_ign": 4000000000,
|
||||
"genesis_issues": "schedule_at_daa_0",
|
||||
"genesis_issues_source": "ASSUMED: the words say nothing about genesis; read as a block like every other, paying the schedule at DAA 0",
|
||||
"daa_of_height": "height",
|
||||
"daa_of_height_source": "ASSUMED: the words say one block a second and a schedule keyed to DAA time; read as the block at height h carrying DAA score h"
|
||||
},
|
||||
"networks": {
|
||||
"devnet4": {
|
||||
"bps": 1,
|
||||
"decimals": 18,
|
||||
"source": "site/economics.html: the Igneum 2.0 devnet, 18 decimals, one block a second"
|
||||
},
|
||||
"mainnet": {
|
||||
"bps": 10,
|
||||
"decimals": 18,
|
||||
"source": "site/litepaper.html: 1 / s blocks, rising to 10 (designed); site/economics.html: mainnet 18 decimals"
|
||||
},
|
||||
"devnet": {
|
||||
"bps": 1,
|
||||
"decimals": 8,
|
||||
"source": "site/economics.html: an earlier devnet paid in 8 decimals"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
{
|
||||
"id": "tv02-rules-v0.1-litepaper-plus-genesis-rule",
|
||||
"status": "interim: v0 with the two rules the words leave out filled by the node lane's written answer (9 October 2026, 09:25 UK), cited by file and line, not by code; replaced by the TV-01 spec when it lands",
|
||||
"sources": [
|
||||
"site/litepaper.html, Economics > Supply: hard cap of 4 billion IGN, emission starts at 1 billion a year and halves every two years for ever, ramps from 10% to 100% over the first 30 days",
|
||||
"site/litepaper.html, Finality: emission per block is a schedule keyed to difficulty-adjusted time, every block in the window is paid at that rate",
|
||||
"site/economics.html, Emission table: a year is 31,557,600 seconds; launch rate the literal 3,168,808,781 (10^9 IGN x 10^8 / 31,557,600 floored) widened to 18 decimals; ramp 2,592,000 s from 10 percent; step 63,115,200 s, the rate halved each step; tail none, the curve runs to zero",
|
||||
"site/economics.html, Units and decimals: the Igneum 2.0 devnet and mainnet are 18 decimals",
|
||||
"the node lane's answer to TV-02, 9 October 2026 09:25 UK (window.rs 176-179, coinbase.rs 139, difficulty.rs 31-32 at bf60948a)"
|
||||
],
|
||||
"rules": {
|
||||
"year_seconds": 31557600,
|
||||
"year_one_emission_ign": 1000000000,
|
||||
"launch_rate_floor_decimals": 8,
|
||||
"launch_rate_rule": "rate0 = floor(year_one_emission_ign * 10^launch_rate_floor_decimals / year_seconds) * 10^(decimals - launch_rate_floor_decimals)",
|
||||
"step_seconds": 63115200,
|
||||
"step_rule": "rate(k) = floor(rate(k-1) / 2), at the network's unit",
|
||||
"ramp_seconds": 2592000,
|
||||
"ramp_start_percent": 10,
|
||||
"ramp_rule": "ramped = floor(x * (start * R + (100 - start) * s) / (100 * R)) for s < R, else x",
|
||||
"seconds_rule": "s = floor(daa_score / bps)",
|
||||
"per_block_order": "divide_then_ramp",
|
||||
"per_block_order_source": "ASSUMED: the words give a per-second rate and blocks per second but not the order of the floor; the natural reading (the second's per-block share, then the ramp) is taken and flagged for the spec",
|
||||
"tail": "cap",
|
||||
"supply_cap_ign": 4000000000,
|
||||
"genesis_issues": "nothing",
|
||||
"genesis_issues_source": "the node lane, 9 October 2026 09:25 UK: genesis never enters the DAA window (consensus/src/processes/window.rs 176-179) and the coinbase pays only blues outside mergeset_non_daa (coinbase.rs 139): genesis is never rewarded",
|
||||
"daa_of_height": "height_minus_one",
|
||||
"daa_of_height_source": "consequence of the same rule: a block's DAA score is its selected parent's plus its mergeset's DAA blocks (difficulty.rs 31-32), so block 1 has DAA score 0 and block h has h - 1"
|
||||
},
|
||||
"networks": {
|
||||
"devnet4": {
|
||||
"bps": 1,
|
||||
"decimals": 18,
|
||||
"source": "site/economics.html: the Igneum 2.0 devnet, 18 decimals, one block a second"
|
||||
},
|
||||
"mainnet": {
|
||||
"bps": 10,
|
||||
"decimals": 18,
|
||||
"source": "site/litepaper.html: 1 / s blocks, rising to 10 (designed); site/economics.html: mainnet 18 decimals"
|
||||
},
|
||||
"devnet": {
|
||||
"bps": 1,
|
||||
"decimals": 8,
|
||||
"source": "site/economics.html: an earlier devnet paid in 8 decimals"
|
||||
}
|
||||
}
|
||||
}
|
||||
63
tools/token-value/tv-02/replay-spec/tv01_adapter.py
Normal file
63
tools/token-value/tv-02/replay-spec/tv01_adapter.py
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
#!/usr/bin/env python3
|
||||
"""TV-02: maps the TV-01 lane's supply spec (supply-spec.json) to B's rules file, field by field, refusing any rule it
|
||||
cannot map. B then replays from the spec's own numbers. Standalone Python 3; no code from A or from the spec's generator.
|
||||
|
||||
tv01_adapter.py <supply-spec.json> <out rules.json>
|
||||
"""
|
||||
import hashlib
|
||||
import json
|
||||
import sys
|
||||
|
||||
|
||||
def main(spec_path, out_path):
|
||||
raw = open(spec_path, "rb").read()
|
||||
spec = json.loads(raw)
|
||||
r = spec["rules"]
|
||||
if r.get("tail") != "cap":
|
||||
raise SystemExit("the spec's tail is %r; B replays a capped schedule only" % r.get("tail"))
|
||||
per_block = r["per_block"].replace(" ", "")
|
||||
if "ramp(floor(rate[" not in per_block or "/bps),s)" not in per_block:
|
||||
raise SystemExit("the spec's per-block rule is not divide-then-ramp: %r" % r["per_block"])
|
||||
g = r["genesis_rule"]
|
||||
if g.get("rule") != "genesis-unrewarded" or "DAA score + 1" not in g.get("height_of_daa_score", ""):
|
||||
raise SystemExit("unmapped genesis rule %r" % g)
|
||||
out = {
|
||||
"id": "tv02-rules-from-tv01-spec",
|
||||
"status": "B's rules mapped from the TV-01 supply spec by tv01_adapter.py",
|
||||
"spec_path": spec_path,
|
||||
"spec_sha256": hashlib.sha256(raw).hexdigest(),
|
||||
"spec_version": spec.get("schema", {}).get("version"),
|
||||
"rules": {
|
||||
"year_seconds": int(r["year_seconds"]),
|
||||
"launch_rate_per_second": str(r["launch_rate_per_second"]),
|
||||
"step_seconds": int(r["step_seconds"]),
|
||||
"step_decay_q32": int(r["step_decay_q32"]),
|
||||
"ramp_seconds": int(r["ramp_seconds"]),
|
||||
"ramp_start_percent": int(r["ramp_start_percent"]),
|
||||
"per_block_order": "divide_then_ramp",
|
||||
"tail": "cap",
|
||||
"supply_cap_ign": int(spec["cap"]["coins"]),
|
||||
"genesis_issues": "nothing",
|
||||
"daa_of_height": "height_minus_one",
|
||||
},
|
||||
"networks": {},
|
||||
"spec_terminal_supply": {},
|
||||
}
|
||||
if int(spec["units"]["decimals"]) != 18:
|
||||
raise SystemExit("unexpected decimals %r" % spec["units"]["decimals"])
|
||||
for sch in spec["schedules"]:
|
||||
name = {"devnet-4": "devnet4", "mainnet": "mainnet"}.get(sch["profile"], sch["profile"])
|
||||
if int(sch.get("genesis_daa_score", 0)) != 0:
|
||||
raise SystemExit("profile %s: genesis DAA score %r is not 0" % (name, sch["genesis_daa_score"]))
|
||||
out["networks"][name] = {"bps": int(sch["bps"]), "decimals": int(sch["decimals"]), "source": "supply-spec.json schedules[%s]" % sch["profile"]}
|
||||
out["spec_terminal_supply"][name] = {"terminal_supply": sch["terminal_supply"], "last_paying_block_height": sch["last_paying_block_height"]}
|
||||
json.dump(out, open(out_path, "w"), indent=2)
|
||||
open(out_path, "a").write("\n")
|
||||
print("mapped %s (sha256 %s) to %s: networks %s" % (spec_path, out["spec_sha256"], out_path, sorted(out["networks"])))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
if len(sys.argv) != 3:
|
||||
print(__doc__, file=sys.stderr)
|
||||
sys.exit(2)
|
||||
main(sys.argv[1], sys.argv[2])
|
||||
28
tools/token-value/tv-02/run-a.sh
Executable file
28
tools/token-value/tv-02/run-a.sh
Executable file
|
|
@ -0,0 +1,28 @@
|
|||
#!/usr/bin/env bash
|
||||
# TV-02 implementation A, on its build box (build-5), never on the Mac. Records its pid file before any work and
|
||||
# removes it on exit. Usage: run-a.sh <repo root on the box> <out dir> [build|straight <net>|reorg <fixture>|genesis]
|
||||
# The node fork is read from <repo>/vendor/igneum-node at the commit checked out there (the run prints it).
|
||||
set -euo pipefail
|
||||
REPO="$1"; OUT="$2"; shift 2
|
||||
PID="$OUT/run-a.pid"
|
||||
mkdir -p "$OUT"
|
||||
echo "$$ $(date -u +%FT%TZ) tv02-a $*" > "$PID"
|
||||
trap 'rm -f "$PID"' EXIT
|
||||
export PATH="$HOME/.cargo/bin:$PATH"
|
||||
CRATE="$REPO/tools/token-value/tv-02/replay-node"
|
||||
export TV02_NODE_COMMIT="$(git -C "$REPO/vendor/igneum-node" rev-parse HEAD)"
|
||||
BIN="$CRATE/target/release/tv02-replay-node"
|
||||
echo "node commit $TV02_NODE_COMMIT"
|
||||
case "$1" in
|
||||
build)
|
||||
(cd "$CRATE" && cargo build --release 2>&1)
|
||||
sha256sum "$BIN" ;;
|
||||
straight)
|
||||
"$BIN" straight "$2" "$OUT/a-$2.csv" "$OUT/a-$2-labels.csv" "$OUT/a-$2-summary.json" ;;
|
||||
reorg)
|
||||
id="$(basename "$2" .json)"
|
||||
"$BIN" reorg "$2" "$OUT/a-reorg-$id.csv" ;;
|
||||
genesis)
|
||||
"$BIN" genesis-payload "$OUT/a-genesis-payload.json" ;;
|
||||
*) echo "unknown command $1" >&2; exit 2 ;;
|
||||
esac
|
||||
19
tools/token-value/tv-02/run-b.sh
Executable file
19
tools/token-value/tv-02/run-b.sh
Executable file
|
|
@ -0,0 +1,19 @@
|
|||
#!/usr/bin/env bash
|
||||
# TV-02 implementation B, on its build box (build-6), never on the Mac. Records its pid file before any work and
|
||||
# removes it on exit. Usage: run-b.sh <tv-02 dir on the box> <out dir> <rules.json> [selftest|straight <net>|reorg <fixture>]
|
||||
# Output names carry the rules id's tag (the third argument's basename) so runs on different rules never overwrite.
|
||||
set -euo pipefail
|
||||
DIR="$1"; OUT="$2"; RULES="$3"; shift 3
|
||||
PID="$OUT/run-b.pid"
|
||||
mkdir -p "$OUT"
|
||||
echo "$$ $(date -u +%FT%TZ) tv02-b $*" > "$PID"
|
||||
trap 'rm -f "$PID"' EXIT
|
||||
TAG="$(basename "$RULES" .json)"
|
||||
echo "rules $RULES sha256 $(sha256sum "$RULES" | cut -c1-64)"
|
||||
echo "replay_spec.py sha256 $(sha256sum "$DIR/replay-spec/replay_spec.py" | cut -c1-64)"
|
||||
case "$1" in
|
||||
selftest) python3 "$DIR/replay-spec/replay_spec.py" selftest; python3 "$DIR/compare.py" selftest ;;
|
||||
straight) python3 "$DIR/replay-spec/replay_spec.py" straight "$RULES" "$2" "$OUT/b-$TAG-$2.csv" "$OUT/b-$TAG-$2-labels.csv" "$OUT/b-$TAG-$2-summary.json" ;;
|
||||
reorg) id="$(basename "$2" .json)"; python3 "$DIR/replay-spec/replay_spec.py" reorg "$RULES" "$2" "$OUT/b-$TAG-reorg-$id.csv" ;;
|
||||
*) echo "unknown command $1" >&2; exit 2 ;;
|
||||
esac
|
||||
31
tools/token-value/tv-02/table.py
Normal file
31
tools/token-value/tv-02/table.py
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
#!/usr/bin/env python3
|
||||
"""TV-02: the README's result tables, generated from a replay CSV and its labels (A's and B's CSVs are byte-identical
|
||||
when the comparison reads EQUAL, so either serves). Prints Markdown rows: the transitions' at-rows (the first block
|
||||
of each new subsidy), the ramp's end and the terminal.
|
||||
|
||||
table.py <replay.csv> <labels.csv> <decimals>
|
||||
"""
|
||||
import csv
|
||||
import sys
|
||||
|
||||
|
||||
def ign(units, decimals):
|
||||
q, r = divmod(units, 10 ** decimals)
|
||||
return "{:,}".format(q) + ("." + str(r).rjust(decimals, "0").rstrip("0") if r else "")
|
||||
|
||||
|
||||
def main(path, labels_path, decimals):
|
||||
rows = {int(r["height"]): r for r in csv.DictReader(open(path))}
|
||||
labels = {int(r["height"]): r["label"] for r in csv.DictReader(open(labels_path))}
|
||||
print("| height | DAA score | rows' label | subsidy (base units) | issued supply (base units) | issued supply (IGN) |")
|
||||
print("|---|---|---|---|---|---|")
|
||||
for h in sorted(rows):
|
||||
l = labels.get(h, "")
|
||||
if not (h == 0 or "at ramp end" in l or "at step" in l or "terminal" in l or "past the terminal" in l):
|
||||
continue
|
||||
r = rows[h]
|
||||
print("| {:,} | {:,} | {} | {} | {} | {} |".format(h, int(r["daa_score"]), l, r["subsidy"], r["cumulative"], ign(int(r["cumulative"]), decimals)))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main(sys.argv[1], sys.argv[2], int(sys.argv[3]))
|
||||
|
|
@ -332,8 +332,10 @@ function renderCrew() {
|
|||
for (const pc of pcs) {
|
||||
const r = pc.running;
|
||||
const pcAge = pc.report_age_s === undefined || pc.report_age_s === null ? pcsAge : pc.report_age_s;
|
||||
const pcAgeText = pcAge === null ? 'report age unknown' : `report ${fmtDurShort(pcAge)} old${pcAge > 600 ? ', STALE' : ''}${pc.source === 'intake' ? ', from the PC' : ''}`;
|
||||
cards.push(`<div class="w"><div class="top"><div><div class="card">${esc(pc.name)}</div><div class="meta">${esc(pc.machine || pc.id)} · ${esc(pc.role)} · <span ${pcAge !== null && pcAge > 600 ? 'style="color:var(--warn)"' : ''}>${esc(pcAgeText)}</span></div></div>${pill(r ? 'running' : pc.queue.length ? 'queued' : 'idle', r ? 'running' : pc.queue.length ? 'queued' : '')}</div><div class="doing">${pcReportLine(pc) ? pcReportLine(pc) + '<br>' : ''}${r ? `${esc(r.kind)} ${esc(r.job)}${r.title ? '<br>' + esc(r.title) : ''}${r.stage ? `<br><span class="pill">stage ${esc(r.stage)}</span>` : ''}` : pc.queue.length ? `Next: ${esc(pc.queue[0].kind)} ${esc(pc.queue[0].job)}${pc.queue[0].title ? ', ' + esc(pc.queue[0].title) : ''}` : esc(pc.note || 'idle on jobs; the relay polls every 10 min')}</div><div class="foot"><span>${pc.recent.length} reports kept</span><span>${pc.last_report_at ? 'last report ' + esc(ago(pc.last_report_at)) : 'no report in 7 days'}</span></div></div>`);
|
||||
// a rig card reads "agent not reporting since <time>" until a report arrives through the intake (the coordinator, 9 October 2026)
|
||||
const pcLast = pc.collected_at || (D.pcs && D.pcs.collected_at) || null;
|
||||
const pcAgeText = pc.source === 'intake' ? (pcAge === null ? 'report age unknown' : `report ${fmtDurShort(pcAge)} old${pcAge > 600 ? ', STALE' : ''}, from the rig`) : pc.source === 'none' ? 'no report yet' : `agent not reporting since ${pcLast ? esc(ukTime(pcLast)) : 'an unknown time'}`;
|
||||
cards.push(`<div class="w"><div class="top"><div><div class="card">${esc(pc.name)}</div><div class="meta">${esc(pc.machine || pc.id)} · ${esc(pc.role)} · <span ${(pc.source !== 'intake') || (pcAge !== null && pcAge > 600) ? 'style="color:var(--warn)"' : ''}>${pcAgeText}</span></div></div>${pill(r ? 'running' : pc.queue.length ? 'queued' : 'idle', r ? 'running' : pc.queue.length ? 'queued' : '')}</div><div class="doing">${pcReportLine(pc) ? pcReportLine(pc) + '<br>' : ''}${r ? `${esc(r.kind)} ${esc(r.job)}${r.title ? '<br>' + esc(r.title) : ''}${r.stage ? `<br><span class="pill">stage ${esc(r.stage)}</span>` : ''}` : pc.queue.length ? `Next: ${esc(pc.queue[0].kind)} ${esc(pc.queue[0].job)}${pc.queue[0].title ? ', ' + esc(pc.queue[0].title) : ''}` : esc(pc.note || 'idle on jobs; the relay polls every 10 min')}</div><div class="foot"><span>${pc.recent.length} reports kept</span><span>${pc.last_report_at ? 'last report ' + esc(ago(pc.last_report_at)) : 'no report in 7 days'}</span></div></div>`);
|
||||
}
|
||||
if (!pcs.length) cards.push(`<div class="w"><div class="top"><div><div class="card">rig 1 and rig 2</div><div class="meta">relay jobs</div></div>${pill('no data', 'error')}</div><div class="doing">${esc(D.sources && D.sources.pcs && D.sources.pcs.error || 'the Mac pusher reads the relay intake; nothing has arrived')}</div></div>`);
|
||||
$('crew').innerHTML = cards.join('');
|
||||
|
|
|
|||
Loading…
Reference in a new issue