Merge tv-02-supply-replay 2fd7d3d3 into master (gate: green on 22a4f326, recorded by tools/ci/pre-push.sh; landed on the build mirror)

This commit is contained in:
igneum-labs 2026-10-09 09:15:47 +00:00
commit d7a1bcb24c
6 changed files with 166 additions and 22 deletions

View file

@ -13,7 +13,10 @@ end and every halving step until the subsidy is 0), the terminal block and one f
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. No cap breach, no issuance
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 |
@ -39,7 +42,8 @@ subsidy of the table below; a pool sees the same numbers per block found. Neithe
| 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 | not landed at the time of this run (docs/plans/igneum-2.0-master/token-value/phase0/tv-01/supply-spec.json, a first version due 13:00 UK). B ran on the two interim rule sets below; the re-run on the spec is the open row R1 |
| 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 |
@ -94,7 +98,7 @@ no-undo ledger are each reported; equal inputs read EQUAL).
## Results
Run 9 October 2026, 08:22 to 08:35 UTC. A on build-5, B on build-6, the comparison on build-5 over B's outputs
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 |
@ -106,6 +110,12 @@ copied from build-6. A rebuilt from this landing's source re-ran byte-identical
| 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
@ -141,14 +151,15 @@ read back over the EVM RPC on 8 October 2026); this replay did not read the chai
| id | row | state | waits on, earliest clock |
|---|---|---|---|
| R1 | Re-run B on the TV-01 supply spec and record its sha here | BLOCKED | the spec's first version, due 13:00 UK today; the re-run takes under a minute on build-6 |
| R2 | The published words (and the TV-01 spec) must state the genesis rule: genesis issues nothing, block h carries DAA score h - 1. Without it a reader reproduces the supply one block early (the v0 divergence above) | BLOCKED | TV-01's spec text |
| R3 | The words must state 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 (B assumed it; it matches the node). The other order changes the mainnet ramp's sum | BLOCKED | TV-01's spec text |
| R4 | The mainnet object's 10 blocks a second against the litepaper's "1 / s blocks, rising to 10 (designed)": the supply at 10 differs from 1 by 15,473,592,000 base units (0.000000015 IGN) and ends six years earlier (year 124, not 130); which rate mainnet launches at is a spec question | BLOCKED | TV-01, D01 |
| 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
@ -169,12 +180,14 @@ 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
```
@ -189,5 +202,6 @@ Each run writes its pid file first (run-a.pid, run-b.pid, compare.pid in the out
| 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 acd68e1b8f9f4d1a591791caa408ba539680125db1af763fe0a7fb7e1e25eaf4 (this landing's file) |
| 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) |

View file

@ -1,15 +1,15 @@
{
"run_id": "tv-02-20261009-01",
"manifest_sha": "af2c23bc",
"manifest_sha": "426c7c85",
"method": "native",
"evidence_dir": "build-1:/srv/artefacts/token-value/tv-02",
"release_identity": {
"commit": "af2c23bc (branch tv-02-supply-replay: the two implementations, the fixtures from 4e206eaf8, the compare harness)",
"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..."
"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": "",
@ -23,8 +23,8 @@
"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",
"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); the TV-01 spec re-run, paid/burned/locked accounting, DAG reds and independent signoff are open rows; NOT RUN until the independent panel reads it"
"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"
}
]
}

View file

@ -1,12 +1,14 @@
#!/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: compare-all.sh <tv-02 dir> <out dir> <B rules tag> ; writes <out>/compare-<tag>.log
# <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 devnet4 mainnet devnet; do
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
@ -14,4 +16,21 @@ 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"

View file

@ -33,12 +33,18 @@ class Schedule:
self.bps = int(n["bps"])
self.decimals = int(n["decimals"])
self.year = int(r["year_seconds"])
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)
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:
self.rate0 = int(r["year_one_emission_ign"]) * 10 ** self.decimals // self.year
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"])
@ -55,10 +61,10 @@ class Schedule:
# --- the per-block rule, from the words ---
def rate(self, k):
"""Per-second rate in step k: the launch rate halved k times, floored each time (equal to rate0 // 2**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 //= 2
x = x * self.decay_q32 // 2 ** 32
if x == 0:
break
return x

View 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
}
}
}

View 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])