Repository navigation
fix(spec): give each Bitcoin-family spec a chain-id only its own chain answers (MAG-2850) - #198
AnnaR-prog wants to merge 2 commits into
Conversation
…n answers (MAG-2850) chain-id read the `chain` field of getblockchaininfo, which is the network type, not the coin. BTC, BCH, DASH, DOGE, LTC and ZCASH all expected `main` and their testnets `test`, so a node of any of them passed as any other. chain-id is now getblockhash at a fixed height, expecting that block's hash (the pattern QTUM and PEP already use): - Height 0 (the genesis block) where it is unique: DASH, DOGE, LTC, ZCASH, their testnets, BTC testnet4 and BTC signet. - BTC and BCH share every block before their 2017 split, so both check block 740238, the first block of Bitcoin Cash's May 2022 upgrade. That is also after the BSV (2018) and XEC (2020) splits, so those nodes fail too. - BTC testnet3 and BCH testnet3 check block 1500206, the same upgrade on testnet3. QTUM, QTUMT, PEP and PEPT already declare their own request and are unchanged. The 12 Bitcoin-family rows leave the chain-identity baseline. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
|
🤖 Spec pipeline started on PR open — running Phase 8 → 11. Results post here as comments. View run |
|
Phase 8 — smart-router boot + probe Resumed pipeline run, Phases 8–11, endpoint source Headline: every changed
|
| Entry | height | request declared | chain-id verify |
wrong-chain node rejected |
|---|---|---|---|---|
| BTC | 740238 | own parse_directive |
OK | YES (vs Dash node) |
| BTCT | 1500206 | own | OK | — |
| BTCT4 | 0 | own | OK | — |
| BTCS | 0 | own | OK | — |
| BCH | 740238 | inherits BTC's | OK | YES (vs Bitcoin node) |
| BCHT | 1500206 | own | OK | — |
| DASH | 0 | own | OK | YES (vs Bitcoin node) |
| DASHT | 0 | inherits DASH's | NOT_TESTABLE |
— |
| DOGE | 0 | own | OK | YES (vs Bitcoin node) |
| DOGET | 0 | inherits DOGE's | OK | — |
| LTC | 0 | own | OK | YES (vs Bitcoin node) |
| LTCT | 0 | inherits LTC's | OK | — |
| ZCASH | 0 | own | OK | YES (vs Bitcoin node) |
| ZCASHT | 0 | inherits ZCASH's | OK | — |
13 of 14 entries live-verified through the router — the committed expected_value matched a [+] verified successfully line byte-for-byte in every case. The four inherit-only entries (BCH, DOGET, LTCT, ZCASHT) all executed; none silently no-opped, so imports carries the new getblockhash request as intended. BCH is the load-bearing case: it declares no directive of its own and inherits BTC's getblockhash[740238] across files.
Before booting, each of those 13 expected hashes was also confirmed by direct getblockhash curl against a node of that chain's own network — independently of the router.
Wrong-chain rejection (the point of MAG-2850) — 5/5 confirmed
Each mainnet spec was booted against a node of a different chain. In all five cases the verification failed, the provider was excluded, and the router refused to serve (all static providers failed verification — cannot serve endpoint, then No pairings available.).
The BCH ↔ BTC pair is the one that proves the chosen height actually separates two chains that share pre-2017 history:
[-] verify failed expected and received are different
received (Bitcoin node, getblockhash 740238): 000000000000000000029075e262ea5f71c5c8e5706439e4983bbd443d9aed2b
expected (BCH spec): 000000000000000002afc6fbd302f01f8cf4533f4b45207abc61d9f4297bf969
Under main's old getblockchaininfo.chain check every one of these five combinations booted successfully, because both sides answer "main". That is the bug this PR closes, reproduced and then closed at runtime.
Per-chain verdicts
| Chain | PARSE | VERIFY | TESTNET_VERIFY | ADDONS (ok/fail/not-testable) | Methods |
|---|---|---|---|---|---|
| btc | OK | PARTIAL¹ | BTCT OK, BTCT4 OK, BTCS OK | 0/0/2 | PASS=28 FAIL=2 SKIP=2 WARN=2 |
| bch | PARTIAL² | OK | BCHT OK | 0/0/2 | PASS=5 FAIL=1 UNPROBED≈40³ |
| dash | PARTIAL² | OK | SKIPPED⁴ | 0/0/1 | PASS=46 FAIL=0 SKIP=4 WARN=6 |
| doge | PARTIAL² | OK | DOGET OK | none declared | PASS=5 FAIL=1 UNPROBED≈29³ |
| litecoin | PARTIAL² | OK | LTCT OK | none declared | PASS=6 FAIL=0 UNPROBED≈29³ |
| zcash | PARTIAL² | OK | ZCASHT OK | 0/0/1 | PASS=4 FAIL=2 UNPROBED≈79³ |
¹ btc's VERIFY: PARTIAL is the rest collection, which this PR does not touch. That collection is built around Blockdaemon's proprietary REST shape (/block_identifier/current, /account/{address}, …), not Bitcoin Core's built-in /rest/*; no key-free upstream implements it, so its chain-id/network rows are NOT_TESTABLE. Pre-existing on main and out of scope here. The jsonrpc chain-id — the row this PR changes — passed on every upstream.
² PARSE_BLOCK_BY_NUM: NOT_EXERCISED on every chain. Cause is router-side, not spec-side: the boot log says block-hash polling (fork detection) resolved enabled=false reason=off-operator-choice, i.e. this image defaults the by-number hash poll off. The identically-shaped getblockhash directive is proven to execute — it is what the chain-id verification runs. PARSE_BLOCKNUM is OK everywhere (smartrouter_latest_block tracked the live tip). Not a defect in these specs.
³ UNPROBED, not coverage. Every non-BTC/DASH upstream is Tatum's key-free tier at 5 requests/minute shared across the whole gateway (confirmed by repeated HTTP 429s). Budget went, in order, to boot + verification + the free wrong-chain check, then a capped 6-method probe per chain. The unprobed methods are unverified, not passing. No 429 was ever recorded as a method verdict. To close this gap, re-run with keyed nodes: /rerun-probe mainnet=<url> testnet=<url>.
⁴ DASHT has no live verification. No key-free Dash-testnet Bitcoin-Core-RPC node exists. Searched: seed-1.testnet.networks.dash.org:1443 (answers JSON-RPC but is the DAPI/Platform surface — -32601 for getblockchaininfo), test.explorer.dash.org, test.insight.dash.siampm.com, test.explorer.dashninja.pl, test.insight.masternode.io:3001, insight.test.dash.crowdnode.io (all custom Insight REST), rpc.coinsdo.net/dash, dash-testnet.gateway.tatum.io, dash-testnet-rpc.publicnode.com (unreachable). DASHT's hash 00000bafbc94add76cb75e2ec92894837288a481e5c005f6563d91623bf8bc2c therefore still rests on the Dash Core chainparams.cpp genesis assert alone, as the PR body states. It was not booted against a mainnet URL — that would only have produced a meaningless failure.
Endpoint discovery (self-researched)
13 of 14 networks got a validated key-free endpoint — better coverage than the PR body's "Tested" section claims (it reports own-chain nodes only for BTC, BTCT, BTCS and DASH). Newly found: *-mainnet/-testnet.gateway.tatum.io serves BCH, BCHT, BTCT4, DOGE, DOGET, LTC, LTCT, ZCASH and ZCASHT key-free. So BCH, BCHT, LTC, DOGET and BTCT4 — which the PR body lists as wrong-chain-only — are now confirmed against nodes of their own chain. DASHT remains the sole gap.
Static guards (re-run on this branch)
check_chain_identity.sh: 0 new chain-identity findings, and all 12 Bitcoin-family UNSEPARATED findings are gone — the guard's DoExpandSpec mirror agrees with the live router. Guard self-tests 20/20. check_unused_fields.sh, check_method_schema.sh, check_verifications.sh, check_disabled_count.sh: PASS on all six files (the BTC/jsonrpc/enabled wildcard row is pre-existing INFO on main, not a FAIL).
⚠️ Working-tree issue found — not caused by this run
The checkout arrived with uncommitted edits to three guard files that partially revert them to main:
M .claude/skills/create-spec/scripts/chain_identity.jq
M .claude/skills/create-spec/scripts/chain_identity_baseline.txt
M .claude/skills/create-spec/scripts/check_chain_identity.sh
chain_identity_baseline.txt has the 12 Bitcoin-family UNSEPARATED rows added back, which the committed branch correctly deleted ("The 12 Bitcoin-family rows leave chain_identity_baseline.txt"). With them present the guard FAILS: 12 baseline row(s) describe a finding the catalogue no longer has. Conservative auto-decision: left untouched — these are not spec files and reverting another actor's working tree is outside this run's scope. Flagging because a workflow step after this run commits the working tree, which would push this revert and break spec_guards.yml. Discard those three files' uncommitted changes before any commit.
Reports: docs/{btc,bch,dash,doge,litecoin,zcash}/METHOD_PROBE_REPORT.md. All containers torn down; no spec file modified by this phase.
|
Phase 9 — parallel reviewers (3 independent passes) Three fresh-context reviewers each ran
Aggregate: CRITICAL=2 (both the same finding), MEDIUM=3 (two the same), MINOR=4. What all three agree is correctEvery reviewer independently confirmed, and none disputed:
CRITICAL ×2 — same finding, and it does not apply to this PR's diffReviewers 1 and 3 both raised the uncommitted working-tree reversion of the three chain-identity guard files ( I then verified that premise, and it does not hold here:
Resolution: downgraded to INFO, not carried into the Phase 10 fix list. The dirt is a runner-local checkout artifact that dies with the runner. It is still worth knowing — MEDIUM ×3 → 2 distinct
MINOR ×4 (dropped from the fix list per Phase 10's rule, recorded here)
Reviewer 2's closing assessment is representative: "the core change … is correctly implemented across all 14 entries, matches the pre-existing QTUM/PEP pattern byte-for-byte, introduces no collateral changes, and is independently confirmed working (including rejecting a wrong-chain node) by live Phase 8 probes for 13 of the 14 entries." |
|
Phase 10 — synthesize gaps + fix pass (+ 10b smoke re-probe) Consolidated fix list: Triage
On the downgraded CRITICAL. Both reviewers stated it is not a defect in the committed diff, and scored it CRITICAL solely for the blast radius if committed. That premise is false for this pipeline: the commit step stages On the not-actionable MEDIUM. DASHT's hash is almost certainly right — it matches Dash Core's The one fix applied —
|
| Gate | Result |
|---|---|
jq parse |
exit 0 |
check_method_schema.sh |
PASS |
check_unused_fields.sh |
PASS |
check_hanging_api.sh (--base origin/main) |
PASS |
check_stateful.sh (--base origin/main) |
PASS |
check_verifications.sh |
PASS (no FAIL rows) |
check_disabled_count.sh |
PASS — disabled set unchanged, still 0, so the body's <!-- disabled-count: 0 --> marker remains true |
check_chain_identity.sh (committed guard files, fixed specs) |
294 enabled specs, 452 endpoint shapes, 0 new findings, 0 stale |
Phase 10b — smoke re-probe → SMOKE: OK
Re-booted ghcr.io/magma-devs/smart-router:main (same tag as Phase 8, for a like-for-like comparison) against the fixed spec. BCH and BCHT run sequentially, spaced ~15s, to respect the shared 5 req/min gateway budget.
BCH_CHAIN_ID_AFTER_FIX: OK — the decisive row. The verification still executes and still matches, now resolved from BCH's own local directive rather than the inherited BTC import:
{"level":"info","chainId":"BCH","verification":"chain-id","block":"-1",
"rawData":"000000000000000002afc6fbd302f01f8cf4533f4b45207abc61d9f4297bf969",
"message":"[+] verified successfully"}
BCHT_VERIFY_AFTER_FIX: OK — rawData 000000000000360769353e933530c40d3f00565a4e7731ff56027e23fa74a8ef, identical to Phase 8; the sibling entry in the same file is unaffected.
| Verdict | |
|---|---|
PARSE |
OK (GET_BLOCKNUM: smartrouter_latest_block{spec="BCH"} 973885→973893). GET_BLOCK_BY_NUM NOT_EXERCISED, unchanged from Phase 8 — router-side fork detection … enabled=false, not a regression |
VERIFY |
OK (both BCH and BCHT chain-id) |
ADDONS |
0 tested-ok / 0 failed / 2 not-testable (blockdaemon, rest) — carried forward unchanged; nothing was TESTED_OK in Phase 8, so there was nothing to re-probe |
Re-probed 5 methods through the router, all PASS and all matching Phase 8: getblockchaininfo, getblockhash, getblock, getrawmempool, estimatefee. No regressions. The smoke agent reused Phase 8's five proven calls rather than the contract's literal alphabetical-first-five (which need crafted raw-tx/script params) — a deliberate, reported deviation so a scarce-budget run yields a clean like-for-like read rather than param-crafting noise. Log scan: cold-start adaptive-latency noise and one 429 from the readiness probe before pacing engaged; both benign, neither mapped to a probed method.
Both containers torn down; docker ps -a empty. The fixed bch.json is left uncommitted in the working tree for the pipeline's commit step.
|
Phase 11 — final reviewer verdict Final pass run in a clean context over all six files as they now stand on disk (committed change + the Phase 10 fix), with the Phase 8 probe reports and Verdict: CHANGES REQUESTEDReported mechanically and honestly: the Phase 11 gate is That verdict needs one honest caveat, because it will read as worse than it is. The final reviewer's own narrative conclusion was "Ready to merge … No CRITICAL finding survives this pass", and neither remaining MEDIUM is a defect in any spec file:
So CHANGES REQUESTED here means "two open items a human should sign off on", not "the spec is broken". Both were raised in Phase 9, both were triaged in Phase 10, and both resolve outside the six JSON files — M1 by someone with a keyed Dash testnet node, M2 by an edit to the PR body. Neither blocks the mechanism this PR ships. What the final pass confirmed correct
Two MINOR notes were also recorded: the PR body's "single source each" framing is now stale for DOGET (Phase 8 live-verified it, so only DASHT still has one source), and Bitcoin testnet3 deprecation (Core v28 deprecated, removed in the v30 line) is a pre-existing long-term risk to BTCT/BCHT that is independent of this PR and would have applied equally to the old check. Final state
|
|
Phase 12 — closing summary checklist Resumable pipeline run, Phases 8–11 only. Phases 1–7 did not run this invocation (the spec was reconstructed from the committed Scope note: this PR is a six-file family change (14 spec entries), not the single-chain shape the pipeline normally handles. Every phase was fanned out across all six files. ① Probe coverage is capped by an upstream billing tier, not by the specs. BCH, BCHT, BTCT4, DOGE, DOGET, LTC, LTCT, ZCASH and ZCASHT are only reachable key-free via Tatum's public gateway, which allows 5 requests/minute shared across the entire gateway. Budget went to boot + verification + the wrong-chain check first, then a capped 6-method probe. Unprobed methods are recorded ② ③ DASHT shipped without live verification — the one real gap. No key-free Bitcoin-Core-dialect Dash testnet node exists (ten endpoints searched; DAPI/Platform surfaces return Headline resultThe mechanism works, verified at runtime rather than by inspection: 5/5 wrong-chain rejections confirmed. Each mainnet spec booted against a node of a different chain had its verification fail, its provider excluded, and the router refuse to serve. Under Phase 8 also found better endpoint coverage than the PR body claims — BCH, BCHT, LTC, DOGET and BTCT4, listed there as wrong-chain-only, are now confirmed against nodes of their own chain. Verdict carried forwardCHANGES REQUESTED — Decisions auto-made in this unattended run
Run stats — this pipeline run only (Phases 8–11)These figures cover Phases 8–11 in this run only — not a 1–11 grand total. This PR's spec content was authored outside the create_spec pipeline, so there is no sibling Phases-1–7 run to add; the true end-to-end cost is this run plus whatever produced the committed change. Left in the working tree for the commit step
|
Auto-committed by spec_pipeline.yml after the Phase-10 fix pass. Run: https://github.com/Magma-Devs/lava-specs/actions/runs/39623527800
|
📦 Committed pipeline fixes to |
What
Each Bitcoin-family spec's
chain-idcheck now asks for a block only its own chain has:getblockhashat a fixed height, expecting that block's hash. QTUM and PEP already work this way.The check keeps the name
chain-id, so any skip-verification list that names it still matches. The REST checks (handle,network) are unchanged, and so are QTUM, QTUMT, PEP and PEPT.Ten specs declare the full request: BTC, BCH, BTCT, BCHT, BTCT4, BTCS, DASH, DOGE, LTC and ZCASH. BCH used to inherit the identical request from BTC, and the spec pipeline's fix pass (
b3f8ce2) wrote it out. DASHT, DOGET, LTCT and ZCASHT override only the expected value and inherit their parent's request.The 12 Bitcoin-family rows leave
chain_identity_baseline.txt.Why
chain-idread thechainfield ofgetblockchaininfo, which is the network type, not the coin. BTC, BCH, DASH, DOGE, LTC and ZCASH all expectedmain, and their testnets all expectedtest. A Dogecoin URL configured as BTC booted and served Dogecoin as Bitcoin, with no error anywhere. The chain-identity guard (#196) lists these as UNSEPARATED.Where each value comes from
"Assert" means the node itself checks this hash at startup (
assert(consensus.hashGenesisBlock == …)in itschainparams.cpp). A BCHN checkpoint is enforced by Bitcoin Cash Node.000000000000000000029075e262ea5f71c5c8e5706439e4983bbd443d9aed2b000000000000000002afc6fbd302f01f8cf4533f4b45207abc61d9f4297bf96900000000000190129e62503b3363c58149382d685589e5a06aab4745aeceba1c000000000000360769353e933530c40d3f00565a4e7731ff56027e23fa74a8ef00000000da84f2bafbbc53dee25a72ae507ff4914b867c565be350b0da8bf04300000008819873e925422c1ff0f99f7cc9bbb232af63a077a480a3633bee1ef600000ffd590b1485b3caadc19b22e6379c733355108f107a430458cdf3407ab600000bafbc94add76cb75e2ec92894837288a481e5c005f6563d91623bf8bc2c1a91e3dace36e2be3bf030a65679fe821aa1d6ef92e7c9902eb318182c355691bb0a78264637406b6360aad926284d544d7049f45189db5664f3c4d07350559e12a765e31ffd4059bada1e25190f6e98c99d9714d334efa41a195a7e7e04bfe24966625a4b2851d9fdee139e56211a0d88575f59ed816ff5e6a63deb4e3e29a000040fe8ec8471911baa1db1266ea15dd06b4a8a5c453883c000b031973dce0805a60a92d99d85997cce3b87616c089f6124d7342af37106edc76126334a2c38The spec pipeline's Phase 8 on this PR also matched 13 of these 14 values against a live node of the spec's own chain, both directly and through the router. DASHT is the exception: no key-free Dash testnet node exists.
Tested
smartrouter health, built from smart-routermainat03da4544, was run against public nodes with this branch's specs and withmain's:mainThe spec pipeline's Phase 8 extended this to 13 of the 14 specs against nodes of their own chain, and to 5 of 5 wrong-chain pairs; see its comment on this PR. DASHT is the only spec with no live run.
spec_guards.ymljobs pass locally:check_chain_identity.sh: 294 enabled specs, 452 endpoint shapes, 36 known findings, 0 new, 0 stale. It gives the same result under macOS/bin/bash3.2.check_verifications.shpasses on all six changed files.chain-idof all 18 Bitcoin-family specs was dumped before and after, using the guard's mirror of the router'sDoExpandSpec. The 14 changed specs read as in the table above, and QTUM, QTUMT, PEP and PEPT are identical.Before merging
Routers load specs from
main, so a wrong hash would refuse every node of that chain.Deployment check, done 2026-10-21:
DASHT still has a single source (Dash Core's genesis assert), but no deployment serves it today.
Known limit
LTC and ZCASH check their genesis block. Litecoin Cash (a 2018 fork of Litecoin) and Ycash (a 2019 fork of Zcash) share those genesis blocks, so their nodes would still pass. LTCT and ZCASHT inherit the same genesis check, so the same would hold for a testnet fork of either chain; none is known. No spec in this catalogue serves either chain. The guard's advice, a height after any fork the chain shares history with, is stricter than this PR for those two. A later block would close the gap once two independent live sources confirm its hash.
Depends on #196; merge that first.
Refs MAG-2850
🤖 Generated with Claude Code