Skip to content

fix(spec): give each Bitcoin-family spec a chain-id only its own chain answers (MAG-2850) - #198

Open
AnnaR-prog wants to merge 2 commits into
feat/MAG-2850-chain-identity-guardfrom
feat/MAG-2850-bitcoin-chain-id
Open

AnnaR-prog wants to merge 2 commits into
feat/MAG-2850-chain-identity-guardfrom
feat/MAG-2850-bitcoin-chain-id

Conversation

@AnnaR-prog

@AnnaR-prog AnnaR-prog commented Oct 21, 2026 •

Copy link
Copy Markdown
Contributor

What

Each Bitcoin-family spec's chain-id check now asks for a block only its own chain has: getblockhash at a fixed height, expecting that block's hash. QTUM and PEP already work this way.

  • Genesis block (height 0) wherever no other spec shares it: DASH, DASHT, DOGE, DOGET, LTC, LTCT, ZCASH, ZCASHT, BTCT4, BTCS.
  • 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. It comes after the BSV (2018) and XEC (2020) splits as well, so nodes of those chains fail too.
  • BTC testnet3 and BCH testnet3 (BTCT, BCHT) check block 1500206, the same upgrade on testnet3.

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-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 all expected test. 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 its chainparams.cpp). A BCHN checkpoint is enforced by Bitcoin Cash Node.

Spec Height Expected hash Sources
BTC 740238 000000000000000000029075e262ea5f71c5c8e5706439e4983bbd443d9aed2b PublicNode RPC, mempool.space, blockstream.info
BCH 740238 000000000000000002afc6fbd302f01f8cf4533f4b45207abc61d9f4297bf969 BCHN checkpoint, Haskoin API
BTCT 1500206 00000000000190129e62503b3363c58149382d685589e5a06aab4745aeceba1c PublicNode RPC, mempool.space, blockstream.info
BCHT 1500206 000000000000360769353e933530c40d3f00565a4e7731ff56027e23fa74a8ef BCHN checkpoint, a production node
BTCT4 0 00000000da84f2bafbbc53dee25a72ae507ff4914b867c565be350b0da8bf043 Bitcoin Core assert, mempool.space
BTCS 0 00000008819873e925422c1ff0f99f7cc9bbb232af63a077a480a3633bee1ef6 Bitcoin Core assert, PublicNode RPC, mempool.space
DASH 0 00000ffd590b1485b3caadc19b22e6379c733355108f107a430458cdf3407ab6 Dash Core assert, PublicNode RPC
DASHT 0 00000bafbc94add76cb75e2ec92894837288a481e5c005f6563d91623bf8bc2c Dash Core assert
DOGE 0 1a91e3dace36e2be3bf030a65679fe821aa1d6ef92e7c9902eb318182c355691 Dogecoin Core assert, BlockCypher
DOGET 0 bb0a78264637406b6360aad926284d544d7049f45189db5664f3c4d07350559e Dogecoin Core assert
LTC 0 12a765e31ffd4059bada1e25190f6e98c99d9714d334efa41a195a7e7e04bfe2 Litecoin Core assert, litecoinspace.org
LTCT 0 4966625a4b2851d9fdee139e56211a0d88575f59ed816ff5e6a63deb4e3e29a0 Litecoin Core assert, litecoinspace.org
ZCASH 0 00040fe8ec8471911baa1db1266ea15dd06b4a8a5c453883c000b031973dce08 zcashd assert, Zebra checkpoint list
ZCASHT 0 05a60a92d99d85997cce3b87616c089f6124d7342af37106edc76126334a2c38 zcashd assert, Zebra checkpoint list

The 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-router main at 03da4544, was run against public nodes with this branch's specs and with main's:

Spec Node main This PR
BTC Bitcoin mainnet pass pass
BTCT Bitcoin testnet3 pass pass
BTCS Bitcoin signet pass pass
DASH Dash mainnet pass pass
BCH Bitcoin mainnet pass (wrong chain) fail
DASH Bitcoin mainnet pass (wrong chain) fail
LTC Bitcoin mainnet pass (wrong chain) fail
BTC Dash mainnet pass (wrong chain) fail
DOGET Bitcoin testnet3 pass (wrong chain) fail
BTCT4 Bitcoin testnet3 fail fail

The 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.

  • All four spec_guards.yml jobs pass locally:
    • Guard self-tests: 20/20.
    • Node-admin check: passes on the six changed files.
    • 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/bash 3.2.
    • Disabled count: report-only, because more than one spec changed. The disabled sets are unchanged.
  • check_verifications.sh passes on all six changed files.
  • The expanded chain-id of all 18 Bitcoin-family specs was dumped before and after, using the guard's mirror of the router's DoExpandSpec. 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:

  • Only BTC, BTCT, BCH, BCHT and ZCASH are deployed anywhere today. The other nine specs serve no deployment.
  • Every primary provider behind those deployments was asked for the exact block its new check uses, pinned one provider at a time: 13 of 13 return the expected hash. That includes BCHT, whose value otherwise rests on the BCHN checkpoint alone.
  • Three backup providers can't be pinned through the router, so they were not asked. Their last seven days of verification logs show rate limits only, no chain mismatch. If one of them answered a different hash, the router would drop that backup, not the primary.

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

…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>
@magmadevs-bot

Copy link
Copy Markdown
Contributor

🤖 Spec pipeline started on PR open — running Phase 8 → 11. Results post here as comments. View run

@magmadevs-bot

Copy link
Copy Markdown
Contributor

Phase 8 — smart-router boot + probe

Resumed pipeline run, Phases 8–11, endpoint source self_research (no URLs supplied; none in the PR body). Six spec files / 14 spec entries booted in ghcr.io/magma-devs/smart-router:main.

Headline: every changed chain-id executes and matches, and wrong-chain nodes are now rejected

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.

@magmadevs-bot

Copy link
Copy Markdown
Contributor

Phase 9 — parallel reviewers (3 independent passes)

Three fresh-context reviewers each ran /review-spec over all six changed files, with the Phase 8 probe reports as input. Distinct reports (md5-checked, no shared-path collision):

Reviewer Report TALLY
1 docs/bitcoin_family/SPEC_REVIEW_GAPS_parallel_1.md CRITICAL=1 MEDIUM=1 MINOR=1
2 docs/bitcoin_family/SPEC_REVIEW_GAPS_parallel_2.md CRITICAL=0 MEDIUM=1 MINOR=3
3 docs/bitcoin_family/SPEC_REVIEW_GAPS_parallel_3.md CRITICAL=1 MEDIUM=1 MINOR=0

Aggregate: CRITICAL=2 (both the same finding), MEDIUM=3 (two the same), MINOR=4.

What all three agree is correct

Every reviewer independently confirmed, and none disputed:

  • Inheritance is correct for all five override-only entries (BCH, DASHT, DOGET, LTCT, ZCASHT). Each inherits a request whose height exists on the child chain and yields a hash unique to it.
  • Schema/shape matches the pre-existing QTUM/PEP precedent byte-for-byte — function_tag: VERIFICATION, parser_func: PARSE_BY_ARG, parser_arg: ["0"], api_name: getblockhash. getblockhash is present and enabled in every spec's api list, and PARSE_BY_ARG with ["0"] is the right parser for a bare-string result.
  • Pruned-node safe. getblockhash reads the block index, which a pruned node retains in full, so none of the three heights (0, 740238, 1500206) excludes a legitimately-pruned provider.
  • No fixed-height fragility. Every height sits far below its chain's live tip (e.g. BTC 740238 vs ≈972 490; BTCT 1 500 206 vs ≈5 195 969).
  • No collateral damage. REST chain-id/network, the enabled verification, addon-gated verifications, and QTUM/QTUMT/PEP/PEPT are all untouched.

CRITICAL ×2 — same finding, and it does not apply to this PR's diff

Reviewers 1 and 3 both raised the uncommitted working-tree reversion of the three chain-identity guard files (chain_identity.jq, chain_identity_baseline.txt, check_chain_identity.sh) that I reported in Phase 8. Both were explicit that it is not a defect in the committed diff — commit 141a95a is correct — and scored it CRITICAL purely for the blast radius if it were committed.

I then verified that premise, and it does not hold here:

  • The pipeline's commit step stages git add -- ':(glob)*.json' — root-level spec JSON only. Its own comment says "so skill/docs JSON under .claude/ is never swept in." The three dirty files are .jq/.txt/.sh under .claude/ — excluded twice over. This run cannot commit them.
  • Running the guard against the committed versions of those three files (extracted with git show HEAD:… into a clean tree) gives === NEW CHAIN-IDENTITY FINDINGS === (none), exit 0, and no stale rows. So spec_guards.yml passes on the branch as committed.

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 — check_chain_identity.sh run inside this working tree reports FAIL: 12 baseline row(s) describe a finding the catalogue no longer has, which would mislead anyone reading this run's raw logs, and a human carrying the same dirt locally would break the guard on commit. Conservative auto-decision recorded in Phase 8 stands: left untouched (not spec files; reverting another actor's working tree is outside this run's scope).

MEDIUM ×3 → 2 distinct

  1. DASHT has no live confirmation of any kind (reviewers 1 and 3). The only entry of the 14 that Phase 8 could not boot. Its hash 00000bafbc…bc2c rests solely on Dash Core's chainparams.cpp genesis assert, and — unlike DOGET/LTCT/ZCASHT — even its inheritance plumbing went unexercised. Ten candidate endpoints were searched and none is a key-free Bitcoin-Core-dialect Dash testnet node. Reviewer 1 and 3 both call this a non-blocker because no deployment serves DASHT today (per the PR's own deployment check). Not fixable in this run — there is nothing to change in the spec; the hash is almost certainly right. Carried forward as a transparent gap.

  2. BCH's expected_value is opaquely coupled to BTC's height, across files (reviewer 2). BCH's chain-id carries no parse_directive at all — the request resolves only through imports: ["BTC"]. Before this PR the shared value was the self-evident literal "main"; now it is an opaque 64-hex-char hash with no visible trace of which height produced it. A future maintainer who bumps BTC's height in btc.json would silently break BCH, with no schema-level signal linking the two files. The four other override-only entries inherit genesis (height 0) from a parent in the same file, so this concern is specific to BCH. Reviewer 2's suggested remedies: document the coupling, or mirror the explicit parse_directive the way BCHT/BTCT/BTCT4/BTCS already do. → carried into the Phase 10 fix list.

MINOR ×4 (dropped from the fix list per Phase 10's rule, recorded here)

  • The PR's "Known limit" disclosure (LTC/ZCASH genesis shared with Litecoin Cash / Ycash) should be worded to cover LTCT and ZCASHT too, since they inherit getblockhash[0] and the identical structural weakness. No such live testnet fork is known, so real-world risk is lower than the mainnet case — but a reader should not assume the testnets are exempt.
  • Residual, unquantified risk that one of DASH/DOGE/BTCT4/BTCS's genesis hash is shared with an unmodeled obscure fork. Reviewer 1 checked each against public fork history and found none; flagged as awareness only.
  • Two further pre-existing-on-main notes, neither made worse by this PR.

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."

@magmadevs-bot

Copy link
Copy Markdown
Contributor

Phase 10 — synthesize gaps + fix pass (+ 10b smoke re-probe)

Consolidated fix list: docs/bitcoin_family/FIX_LIST.md — 1 critical, 2 medium raised → 1 fix applied; 1 critical downgraded to INFO with evidence, 1 medium not actionable, 0 disable-suggestions stripped (no reviewer proposed disabling anything).

Triage

Severity Finding Disposition
CRITICAL ×2 (same) Uncommitted working-tree reversion of the three chain-identity guard files Downgraded to INFO — see below
MEDIUM BCH's chain-id request implicit + cross-file coupled to BTC's height FIXED
MEDIUM ×2 (same) DASHT has no live confirmation Not actionable — no spec change applies
MINOR ×4 — Dropped per Phase 10 rule; recorded in the Phase 9 comment

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 git add -- ':(glob)*.json' — root-level spec JSON only, with the explicit comment "so skill/docs JSON under .claude/ is never swept in" — and the three dirty files are .jq/.txt/.sh under .claude/. Running the guard against the committed versions of those files in a clean tree returns NEW CHAIN-IDENTITY FINDINGS: (none), exit 0, zero stale rows. So the branch as committed is correct, spec_guards.yml passes, and this run cannot push the dirt. Left untouched (conservative auto-decision: not spec files, and reverting another actor's working tree is out of scope).

On the not-actionable MEDIUM. DASHT's hash is almost certainly right — it matches Dash Core's chainparams.cpp genesis assert — and the gap is missing evidence, not a wrong field. There is nothing to edit. Carried into the summary as a transparent gap.

The one fix applied — bch.json, purely additive, 11 lines

  • bch.json:290 — added an explicit parse_directive to the BCH entry's jsonrpc base-collection chain-id verification (gap: MEDIUM, "BCH's expected_value is opaquely coupled to BTC's height, across files").
"parse_directive": {
    "function_template": "{\"jsonrpc\":\"2.0\",\"method\":\"getblockhash\",\"params\":[740238],\"id\":1}",
    "function_tag": "VERIFICATION",
    "result_parsing": { "parser_arg": ["0"], "parser_func": "PARSE_BY_ARG" },
    "api_name": "getblockhash"
}

BCH previously declared no directive at all — the request resolved only through imports: ["BTC"]. Before this PR the shared value was the self-evident literal "main"; it became an opaque 64-hex hash with no visible trace of the height that produced it, so a future edit to BTC's height in btc.json would silently break BCH. The fix moves BCH to the pattern the other nine explicit entries in this PR already use. expected_value is unchanged (00000000…bf969), as is the height (740238) — so this is behaviour-preserving, which Phase 10b then confirmed on a live node.

Verified minimal: jq -S comparison against the pre-fix snapshot with only that key deleted is byte-identical, so nothing else moved. The file's no-trailing-newline convention is preserved. No other spec file was touched by this phase.

Note this fix is additive only — it introduces none of the 15 fields removed in smart-router#218, and the envelope remains { "proposal": { "specs": [ … ] } }.

Phase 10a — post-fix offline gates on bch.json

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.

@magmadevs-bot

Copy link
Copy Markdown
Contributor

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 FIX_LIST.md as input. Report: docs/bitcoin_family/SPEC_REVIEW_GAPS_final.md.

TALLY: CRITICAL=0 MEDIUM=2 MINOR=2

Verdict: CHANGES REQUESTED

Reported mechanically and honestly: the Phase 11 gate is CRITICAL=0 MEDIUM=0 → APPROVED, and 2 MEDIUM findings remain, so the verdict is CHANGES REQUESTED. It is not upgraded.

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:

# MEDIUM Why it is not a spec change
M1 DASHT's chain-id hash has no live confirmation (dash.json:661) Missing evidence, not a wrong field. The only one of 14 entries Phase 8 could not boot — ten candidate endpoints searched, none a key-free Bitcoin-Core-dialect Dash testnet node. The value rests on Dash Core's chainparams.cpp genesis assert, which the PR body already discloses. Nothing in the spec is editable to close it. No deployment serves DASHT today.
M2 The "Known limit" disclosure doesn't name LTCT/ZCASHT (litecoin.json:117, zcash.json:1567) A wording gap in the PR description, not in the specs. LTCT/ZCASHT inherit getblockhash[0] and so share the same shared-genesis weakness the PR already discloses for LTC/ZCASH (Litecoin Cash, Ycash). No such live testnet fork is known, and no spec in the catalogue serves either.

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

  • The Phase 10 fix is applied correctly and is behaviour-preserving — BCH's chain-id now declares its own getblockhash[740238] directive at bch.json:290-299 with expected_value unchanged at bch.json:302.
  • All 14 entries' parse_directive/expected_value pairs on disk match the PR body's sourcing table exactly. Every hash is a valid 64-hex block hash; no duplicate or orphaned verification blocks.
  • Schema is byte-identical to the already-shipped PEP/QTUM precedent, and PARSE_BY_ARG with parser_arg: ["0"] is the right parser for getblockhash's bare-string result.
  • getblockhash resolves to an enabled API in every affected spec, directly or transitively through imports — and is confirmed live-exercised, not merely declared.
  • Inheritance is correct for all five override-only entries; four of five live-confirmed (DASHT is M1).
  • Pruned-node safe — getblockhash is answered from the block index, which pruned nodes retain, for heights 0, 740238 and 1500206 alike.
  • No fixed-height fragility — every height sits far below its chain's tip; genesis never moves.
  • No collateral damage — only chain-id blocks changed. REST chain-id/network, the enabled verification, the addon-gated verifications (blockdaemon, addressindex, insightexplorer) and every network-timing parameter are untouched, and qtum.json/pepecoin.json are not in the diff at all.
  • Disabled-API justifications: nothing to report. Every enabled: false entry in all six files is byte-identical to origin/main (bch 12, zcash 10, the other four 0 distinct methods). This PR neither added nor removed a disable, so the empty ledger for this run is correct — no justification gap is attributable to this PR.

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

Check Result
jq parse, all six files exit 0
check_unused_fields.sh, all six PASS — none of the 15 fields removed in smart-router#218
check_chain_identity.sh (committed guard files) 294 enabled specs, 452 endpoint shapes, 0 new findings, 0 stale
Disabled count 0 introduced; sets byte-identical to main, so <!-- disabled-count: 0 --> stays true
Working tree to be committed bch.json only, +11 lines, purely additive

@magmadevs-bot

Copy link
Copy Markdown
Contributor

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 <chain>.json files on this branch plus the PR comments, per references/phase-entrypoints.md).

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.

#### File Validation
- ✓ JSON syntax valid                                    (Phase 10a/11: `jq` exit 0 on all six files)
- ✓ All required fields present                          (Phase 9 ×3 + Phase 11 reviewers)
- ✓ No duplicate API names within any collection         (Phase 11: no duplicate/orphaned chain-id blocks)
- ✓ Proper indentation/formatting                        (Phase 10 fix matched btc.json's indent byte-for-byte; no whole-file reformat)

#### Configuration Verification
- ✓ Network parameters calculated per formulas           (unchanged by this PR; Phase 8 confirmed no timing field moved)
- ~ All APIs tested and working                          (Phase 8: full probe on BTC (28 PASS) and DASH (46 PASS); BCH/DOGE/LTC/ZCASH capped at 6 methods each — the rest are UNPROBED, not covered. See note ① )
- ~ Block parsing validated for each API                 (Phase 8 existence-tested; full per-API parse validation needs production traffic)
- ✓ Parse directives executed by live router             (Phase 8 Step 3.5: PARSE=OK on all six (GET_BLOCKNUM); VERIFY=OK on five, PARTIAL on btc (pre-existing REST, note ②). Phase 10b re-check: PARSE=OK, VERIFY=OK)
- ~ Addons & extensions tested                            (Phase 8: 0 tested-ok / 0 failed / 6 not-testable — `blockdaemon` ×2, `rest` ×2, `addressindex`, `insightexplorer`. All need a supporting node; none is changed by this PR)
- ✓ Verifications pass on live nodes                     (**13 of 14 entries** live-confirmed through the router AND by direct `getblockhash` curl; the 14th is DASHT, note ③)
- ☐ Compute units benchmarked under expected load        (user to measure)

#### Documentation (out of skill scope — manual reminder)
- ☐ SPEC_IMPLEMENTATION.md created
- ☐ API_REFERENCE.md created
- ☐ TESTING_GUIDE.md created
- ☐ QUICK_START.md created

#### Testnet vs Mainnet
- ✓ Mainnet spec complete                                (6 mainnet entries: BTC, BCH, DASH, DOGE, LTC, ZCASH)
- ✓ Testnet spec inherits correctly                      (8 testnet entries; Phase 11 confirmed all five override-only entries resolve through `imports`)
- ~ Chain IDs verified for mainnet AND testnet           (all 6 mainnets + 7 of 8 testnets live-verified; DASHT not verified, note ③)
- ~ Both tested on respective networks                   (Phase 8 booted 13 of 14 entries against a node of that entry's own network)

#### Governance Prep (out of skill scope — manual reminder)
- ☐ Proposal JSON formatted correctly                    (all six files carry the `{ "proposal": { "specs": [ … ] } }` wrapper — no title/description/deposit)
- ☐ Community feedback gathered (if applicable)

① 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 UNPROBED (unverified), never SKIP and never as coverage. To close this: /rerun-probe mainnet=<url> testnet=<url> with keyed nodes.

② btc's VERIFY: PARTIAL is pre-existing and out of scope. Its rest collection targets Blockdaemon's proprietary REST shape, not Bitcoin Core's /rest/*; no key-free upstream implements it. Unchanged by this PR, which touches only jsonrpc chain-id (plus the already-separated REST checks it leaves alone).

③ 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 -32601, the rest are Insight REST). Its hash rests on Dash Core's chainparams.cpp genesis assert alone. No deployment serves DASHT today.

Headline result

The 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 main's old getblockchaininfo.chain check every one of those five combinations booted successfully, since both sides answer "main". That is the MAG-2850 bug, reproduced and then closed.

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 forward

CHANGES REQUESTED — CRITICAL=0 MEDIUM=2 MINOR=2. Reported mechanically, not upgraded. Both MEDIUMs resolve outside the six JSON files: M1 needs a keyed Dash testnet node; M2 is a wording gap in the PR body (extend the "Known limit" paragraph to name LTCT/ZCASHT). The final reviewer's own narrative read was "Ready to merge". No spec defect survives.

Decisions auto-made in this unattended run

  1. Self-researched endpoints (both URL lists empty) — 13 of 14 networks got a validated key-free node; DASHT recorded NOT_TESTABLE with searched-sources evidence rather than guessed at.
  2. Serialized the tatum-backed chains and parallelized only the publicnode-backed ones, so the shared 5 req/min gateway was never contended — otherwise concurrent boots would have produced false BOOT_FAILED results.
  3. Downgraded the 2× Phase-9 CRITICAL to INFO after verifying its premise was false: the pipeline stages git add -- ':(glob)*.json' (root spec JSON only), so the dirty .claude/ guard files cannot reach the branch, and the guard passes clean against their committed versions.
  4. Left those three uncommitted guard-file edits untouched — not spec files; reverting another actor's working tree is outside this run's scope.
  5. Applied the one actionable MEDIUM (BCH's explicit parse_directive) and left expected_value alone, keeping the fix behaviour-preserving — then re-booted to prove it.
  6. Did not treat DASHT's missing endpoint as a spec defect, and did not boot DASHT against a mainnet URL, which would only have manufactured a meaningless failure.
  7. No ws gate triggered — these specs declare zero subscription methods, verified by jq across the import closure, so the absence of a ws upstream is not a blocker.

Run stats — this pipeline run only (Phases 8–11)

Elapsed:         01h 53m 54s  (first→last logged activity)
Transcripts:     1 main + 9 subagent
API round-trips: 976  (main=125, subagents=851)

Fresh tokens:    input=1952  output=500142  (sum=502094)
Cached tokens:   cache_read=79344899  cache_write=2180846
Total billed:    82027839 tokens

Split (total billed):  main=16699434  subagents=65328405

Models used:
  claude-sonnet-5         851 calls   65328405 billed
  claude-opus-5           127 calls   17115632 billed

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

bch.json only — +11 lines, purely additive, uncommitted. Verified minimal: a jq -S comparison against the pre-fix snapshot with just that key deleted is byte-identical.

⚠️ Before anything is staged: discard the three uncommitted edits under .claude/skills/create-spec/scripts/ (chain_identity.jq, chain_identity_baseline.txt, check_chain_identity.sh). This pipeline's commit step cannot pick them up, but a human committing from this tree would re-add 12 stale baseline rows and break check_chain_identity.sh.

@magmadevs-bot

Copy link
Copy Markdown
Contributor

📦 Committed pipeline fixes to feat/MAG-2850-bitcoin-chain-id (b3f8ce2): bch.json — the branch now matches the spec that Phase 10b/11 verified.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants