dash(tools): gen_mn_checkpoint.py — the missing daemonless MN-set anchor generator (#955) - #956
Open
frstrtr wants to merge 1 commit into
Open
dash(tools): gen_mn_checkpoint.py — the missing daemonless MN-set anchor generator (#955)#956frstrtr wants to merge 1 commit into
frstrtr wants to merge 1 commit into
Conversation
… anchor generator (#955) E2d (#899) ships a release-pinned masternode-set checkpoint that the daemonless embedded arm cold-starts from, and FAILS CLOSED when it is unpinned/corrupt. But the generator its README, its .inc headers, and its runtime ERROR all name — tools/dash/gen_mn_checkpoint.py — did not exist, and neither did tools/dash/. So the fail-closed state was PERMANENT: no supported path could produce an anchor, and --embedded-mainnet could never build a template. This lands the remedy. pin fetch `protx list valid true <height>` from a dashd (RPC or an offline --protx-json capture), convert it to the checkpoint payload, and OVERWRITE the target .inc in place; print a provenance block for the release notes. Brackets the fetch with getblockcount and refetches if the tip moved (auto-height), verifies getblockchaininfo.chain matches --network, converts payoutAddress -> scriptPayout once, sorts by proTxHash for reproducible bytes, and re-parses what it wrote so it never ships a file the runtime would refuse. verify re-derive the digest and re-run every fail-closed rule the parser enforces (mn_checkpoint.hpp). Stdlib only (urllib/hashlib/json/base58check) — a release box needs nothing but python3. The field conversions MIRROR mn_seed.hpp::parse_protx_list_seed exactly (scriptPayout via base58check->P2PKH/P2SH, the dashd -1 "never" height clamps, Evo/Regular typing, operatorReward pct->fixed-point), so a checkpoint seed and an RPC seed produce byte-identical MNState — the coinbase-correctness contract. tools/dash/test_gen_mn_checkpoint.py proves this OFFLINE against the SAME committed testnet fixture (test/dash_mn_checkpoint_testnet_1519543.inc) that CheckpointSetIsFieldIdenticalToRpcSeed certifies equals the RPC seed: - the fixture re-derives its own digest eddc3386…; - pinning from the payee-relevant protx JSON subset reproduces the fixtures
|
|
||
|
|
||
| def main(): | ||
| fixture_text = open(FIXTURE).read() |
| g.main(["pin", "--network", "testnet", "--protx-json", jsrc, | ||
| "--height", str(FIXTURE_HEIGHT), "--blockhash", FIXTURE_BLOCKHASH, | ||
| "--generated", "2026-07-26T13:23:51Z", "--source", "kat", "--output", out, "--quiet"]) | ||
| R, G = mn_map(g.unwrap_inc(fixture_text)), mn_map(g.unwrap_inc(open(out).read())) |
| so = tf.name | ||
| g.main(["pin", "--network", "testnet", "--protx-json", sj, "--height", "12345", | ||
| "--blockhash", "ab" * 32, "--generated", "2026-01-01T00:00:00Z", "--output", so, "--quiet"]) | ||
| st = open(so).read() |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What / why
#899 (E2d, merged
827d758d) ships a release-pinned masternode-set checkpoint that the daemonless embedded arm cold-starts from, and fails closed when it is unpinned/corrupt. The generator its README, its.incheaders, and its runtimeERRORall name —tools/dash/gen_mn_checkpoint.py— did not exist, and neither didtools/dash/. So the fail-closed state was permanent: no supported path could produce an anchor and--embedded-mainnetcould never build a template (#955). This lands the remedy.What it does
pin— fetchprotx list valid true <height>from a dashd (RPC or an offline--protx-jsoncapture), convert it to the checkpoint payload, and overwrite the target.incin place; print a provenance block for the release notes. Brackets the fetch withgetblockcountand refetches if the tip moved (auto-height), assertsgetblockchaininfo.chainmatches--network, convertspayoutAddress → scriptPayoutonce, sorts byproTxHashfor reproducible bytes, and re-parses what it wrote so it never ships a file the runtime would refuse.verify— re-derive the digest and re-run every fail-closed rulemn_checkpoint.hppenforces.Stdlib only (
urllib/hashlib/json/base58check) — a release box needs nothing butpython3.Byte-parity proof (this is why it's correct)
The field conversions mirror
mn_seed.hpp::parse_protx_list_seedexactly (scriptPayout via base58check→P2PKH/P2SH, the dashd-1"never" height clamps, Evo/Regular typing,operatorRewardpct→fixed-point), so a checkpoint seed and an RPC seed produce byte-identicalMNState— the coinbase-correctness contract.tools/dash/test_gen_mn_checkpoint.pyproves this offline, against the SAME committed testnet fixture (test/dash_mn_checkpoint_testnet_1519543.inc) thattest_dash_mn_checkpoint.cpp::CheckpointSetIsFieldIdenticalToRpcSeedcertifies equals the RPC seed:eddc3386…;verifyaccepts → one-byte edit → refused..gitignore
tools/was wholly ignored (build artifacts). This un-ignores only the tracked generator source, so the path the checkpoints README instructs operators to run actually exists in the tree.Not in this PR — the runtime acceptance
#955's acceptance bar ("pin an anchor, start
--embedded-<net>with no coin-RPC, observe the node LEAVE the CHECKPOINT REFUSED state and build a template") is the live end-to-end I am driving next (fresh testnet anchor from a live node → build → run), and I'll attach that log as the merge-gating evidence. This PR is the generator + its byte-parity proof — the offline half — and unblocks pinning the mainnet anchor right now.