dash(embedded): mainnet-validity — real-dashd CbTx byte-parity + C-3/H-4/superblock (v0.2.4 gate-lift proof)#780
dash(embedded): mainnet-validity — real-dashd CbTx byte-parity + C-3/H-4/superblock (v0.2.4 gate-lift proof)#780frstrtr wants to merge 11 commits into
Conversation
…(mnlistdiff ingest -> simplifiedmns/quorum -> real CCbTx payload) + H3/H4 sync-gate & reorg — recovery of API-terminated wiring work
…innet gate Testnet-proving embedded SML->quorum->CCbTx wiring, corrected to real consensus-neutral behaviour. The interim mainnet safety-gate is UNCHANGED (embedded arm serves only on testnet/regtest); this hardens the testnet proving path and the honest comments around it. Mainnet daemonless blocks remain gated pending the byte-parity proof (follow-up). C-2 reorg + chainlock callers (were unwired): - header_chain: surface a was_reorg flag on the tip-change callback (the tip advanced onto a branch that did not linearly extend the old tip). - main_dash tip-change hook: on a reorg, call CoinStateMaintainer::on_sml_reorg (wipe SML+quorum, drop have_sml), reset the sync base to ZERO, and re-request a full cold-start mnlistdiff at the new tip. No more stale have_sml=true. - clsig: plumb the recovered 96-byte ChainLock signature through a new new_chainlock_sig event; subscribe the maintainer so on_new_chainlock adopts it (forward-only) as the CCbTx bestCL*. (The existing sig-less new_chainlock event still feeds only the finalization map.) H-6 stale-SML-at-tip race: - SML/quorum apply and bestCL adoption now bump work-generation + notify (via a maintainer state-dirty sink -> DASHWorkSource::bump_work_generation + stratum notify_all), so a fresh template is issued once the SML catches up. - Template serving is gated on SML freshness for the tip: NodeCoinState tracks the block hash the SML is current at; under require_sml, viability requires it to equal the tip we build on, so during the tip-change -> getmnlistd RTT the embedded arm holds on the reward-safe dashd fallback instead of serving a stale-SML CCbTx. H-7 mnlistdiff integrity guards: - base-continuity: reject an incremental diff whose baseBlockHash does not match our current SML block (prevents ghost-MN corruption). ZERO base = full snapshot, always accepted. - monotonic bestCL: never adopt a diff-embedded ChainLock that regresses the committed bestCL height. Comments corrected: the SML/QuorumManager wiring is landed and testnet-proving under an interim mainnet gate (not "post-v0.2.4"); the gate lifts via the byte-parity follow-up, not by landing the wiring. Tests: maintainer 6->9 (base-continuity reject, chainlock forward-only+dirty, apply/reorg state-dirty); node_coin_state 7->9 (freshness gate hold + re-align). All dash suites green; c2pool-dash builds+links.
…H-4/superblock The gate-lift proof for the interim mainnet gate (#779 follow-up). Establishes, against a REAL Dash Core testnet getblocktemplate capture, that the embedded type-5 CbTx is byte-identical to dashd's, and closes the confirmed pre-mainnet gaps by WIRING the already-vendored machinery (not recoding it). H-5 real-dashd byte-parity KAT (test_dash_embedded_cbtx_byte_parity.cpp): - Oracle = verbatim dashd testnet GBT for block 1,518,413 (test/fixtures/dash_testnet_gbt_1518413.json); coinbase_payload is dashd's own CbTx extra_payload, NOT c2pool output (fixes the self-referential prior KAT). - parse_cbtx -> encode_cbtx reproduces dashd's 175-byte payload BYTE-IDENTICALLY (nVersion, nHeight, merkleRootMNList, merkleRootQuorums, VarInt bestCLHeightDiff, 96-byte bestCLSignature, signed int64 creditPoolBalance). - build_embedded_cbtx assembly reproduces dashd's non-root fields exactly (v3, nHeight=prev+1, bestCLHeightDiff=prev-bestCLHeight=1, verbatim sig, creditPool) and, with dashd's roots transplanted, the whole payload is byte-identical. C-3 special-tx filter (mempool.hpp): get_sorted_txs_with_fees gains an exclude_special option; build_embedded_workdata passes it true so embedded templates carry NO special tx (tx.type != 0). dashd recomputes CbTx roots + creditPool by applying the block's own special txs, so an unaccounted special tx = bad-cbtx. Default false preserves the mempool's general pricing/selection (asset-unlock fee path unchanged). H-4 creditPool accrual (embedded_gbt.hpp): creditPoolBalance(N) = creditPoolBalance(N-1) + platformReward(N) (asset lock/unlock terms are zero because C-3 excludes special txs). Verified byte-exact against dashd's own consecutive-block series: every testnet block stepped the pool by exactly the platform-reward burn (66966830 duff). The prior code committed the stale last-observed balance (off by one platform reward). Superblock guard (node_coin_state + main_dash): on a superblock-height next block the embedded arm refuses (routes to the reward-safe dashd fallback that carries the governance outputs) rather than emit an invalid non-superblock coinbase. Network-aware cycle (mainnet 16616, testnet 24). Tests: byte-parity 4/4; mempool 17->19 (C-3 exclude + the two asset-unlock selection tests restored); node_coin_state 8->9 (superblock refuse). All dash suites green; c2pool-dash builds+links. Flagged for the gate-lift record (NOT resolved here): - The 375-entry/109-quorum testnet SML is not reconstructed in-KAT to recompute the two roots from scratch; the roots ARE proven EQUAL to dashd's commitment via the captured `protx diff 1 1518412` (same roots) + the CalcMerkleRoot / compute_merkle_root_quorums KATs. Wiring a raw mnlistdiff wire capture -> CalcMerkleRoot == fixture root is the one remaining reconstruct-from-wire step. - subsidy.hpp platform-reward/subsidy use MAINNET activation heights; on TESTNET (lower MN_RR height) compute_dash_platform_reward returns 0 at h<2128896 while real testnet already burns 66966830 — a testnet-only embedded divergence. The mainnet gate target is unaffected; testnet embedded mining needs per-net activation heights before it is byte-correct. - credit_pool.hpp DiffFromBlock models asset lock/unlock only, NOT the per-block platform-reward lock; the accrual term is added at the template layer here.
…N, merkleRootQuorums divergence isolated
Closes the last open link of the CbTx byte-parity chain from the RAW wire, and
pins the exact remaining consensus surface that blocks the mainnet gate-lift.
Method: capture a real testnet `mnlistdiff` P2P message (getmnlistd(base=ZERO,
block=1518412)) verbatim off the socket, deserialize via the vendor codec, apply
into an empty SML / QuorumManager, and compute both merkle roots — then compare
to the roots dashd committed in the block-1518413 CbTx.
Fixture: test/fixtures/dash_testnet_mnlistdiff_1518412.bin (97639 bytes, the
checksum-verified mnlistdiff payload; NOT c2pool output).
Result per root:
- merkleRootMNList: MATCHES dashd byte-for-byte. raw mnlistdiff -> our SML ->
CalcMerkleRoot == 6bbc07fd...48f. The SML axis is now proven end-to-end from
wire, not transplanted.
- merkleRootQuorums: DIVERGES (ours e839698c... vs dashd 1901c172...), and is
precisely isolated to the leaf FLATTEN-ORDERING in compute_merkle_root_quorums:
* active quorum SET is exact — 109, per-type 24/4/1/24/32/24, identical to
`protx diff 1 1518412`;
* wire DELIVERY ORDER is identical to dashd's protx-diff order;
* LEAF HASH is byte-exact — pack(commitment) reproduces the wire bytes
verbatim (found in the tail), so SHA256d(pack) == dashd's SerializeHash;
* merkle MACHINERY is the same helper that reproduces merkleRootMNList.
No wire-derivable ordering tried (wire order; per-(llmqType,quorumHash) LE/BE;
global quorumHash; per-type leaf-hash sort; global leaf-hash sort) reproduces
dashd's root. dashcore's CalcCbTxMerkleRootQuorums orders via
GetMinedAndActiveCommitmentsUntilBlock (mining-height driven); mining_height is
NOT carried by the mnlistdiff wire, so our quorum path cannot currently
reconstruct dashd's order. Resolving needs the dashcore reference for the exact
flatten order — NOT guessed here.
Tests: MerkleRootMNListFromWireMatchesDashd (PASS), QuorumSetAndLeavesAreByteExact
FromWire (PASS — set/leaf isolation), DISABLED_MerkleRootQuorumsFromWireMatchesDashd
(tracks the divergence; fails with the exact values when enabled). CI green.
VERDICT: the mainnet gate CANNOT lift yet. merkleRootMNList, the CbTx wire form,
bestCL, and creditPool accrual are all proven byte-identical to real dashd, but a
wrong merkleRootQuorums = bad-cbtx (lost subsidy). The quorum-root flatten-order
fix (with mining-height tracking from the qc-commitment stream, or the exact
dashcore leaf order) is the single remaining blocker.
From-wire merkle-root parity — result per rootClosed the last open link: computed both CbTx merkle roots from a raw testnet merkleRootMNList — MATCHES ✅ merkleRootQuorums — DIVERGES ❌ (gate-lift blocker)
No wire-derivable ordering tried (wire order; per- Tracked by Corrected verdictThe interim mainnet gate must NOT lift yet. merkleRootMNList, the full CbTx wire form, bestCL, and creditPool accrual are all proven byte-identical to real dashd — but a wrong merkleRootQuorums = |
…et gate — v0.2.4 trigger Closes the last consensus blocker: merkleRootQuorums now reproduces real dashd BYTE-IDENTICALLY from the raw mnlistdiff wire, and the mainnet embedded arm is enabled behind an explicit opt-in. Root cause (from dashcore evo/cbtx.cpp CalcCbTxMerkleRootQuorums): it collects ::SerializeHash for EVERY mined+active commitment and does a FINAL std::sort of the leaf hashes before the merkle — so ordering / mining-height are irrelevant and only the SET matters (fully wire-derivable, no block-body qc ingest). Our compute_merkle_root_quorums was dedup'ing rotated types (5/6) by quorumIndex, dropping ring-member leaves, so its set was smaller than dashd's 109. An independent recompute over our exact 109 leaves (sort+merkle) reproduced dashd's 1901c172... exactly. Fix: compute_merkle_root_quorums hashes ALL active commitments (no per-index dedup, no mining-height ordering), sorts the leaf hashes (memcmp), merkles. Proven byte-identical to dashd from wire in test_dash_mnlistdiff_root_parity (MerkleRootQuorumsFromWireMatchesDashd, now enabled). Updated test_dash_quorum_root (RotatedRingMembersAllIncluded replaces the incorrect dedup KAT; frozen golden re-pinned for the corrected all-inclusive set). Gate-lift (v0.2.4 trigger): DASHWorkSource::set_embedded_mainnet + --embedded-mainnet allow the daemonless embedded arm on MAINNET now that the full CbTx (wire form, both roots, bestCL, creditPool accrual) is proven byte-identical to real dashd. Default OFF => unconfigured mainnet nodes stay fail-closed on the reward-safe dashd fallback. When enabled, the NodeCoinState viability gate (SML+quorum fresh at tip, non-superblock) still fails safe to the fallback; [GBT-XCHECK] remains. Per-root from-wire: merkleRootMNList == dashd; merkleRootQuorums == dashd. All dash suites green (quorum_root 11, mnlistdiff_root_parity 3, byte_parity 4, embedded_gbt 9, stratum_work_source 35, node_coin_state 9); c2pool-dash builds+links.
merkleRootQuorums RESOLVED — both roots match dashd from wire; gate liftedRead dashcore's Root cause: Fix: hash all active commitments (no per-index dedup, no mining-height ordering), sort the leaf hashes ( Per-root from-wire result (raw testnet
|
…+ pre-emit fail-closed guards Addresses the PR #780 review: byte-parity/default-OFF is APPROVED, but --embedded-mainnet=ON had two confirmed consensus holes (verified vs live testnet). Adds three fail-closed guards so ON is reward-safe (worst case = falls back to the reward-safe dashd arm, never mines a bad block). BLOCKER-1 (CRITICAL) — DKG mining-phase heights. dashd's block-N CbTx quorum set includes block-N's OWN mandatory type-6 commitment txs, and those txs are required in-block (else bad-qc-missing). The embedded arm strips all special txs (C-3) and omits current-block commitments from merkleRootQuorums. PHASE 1 (shipped): is_dkg_commitment_window(height) replicates dashcore's IsMiningPhase over every enabled llmqType's [dkgMiningWindowStart,End] (params verbatim from llmq/params.h; union is network-agnostic + fail-safe), wired into NodeCoinState viability so those heights fail closed to the dashd fallback. KAT over the live testnet commitment heights 1518418/1518419/1518442 (refuse) vs 1518413 (proceed). PHASE 2 (daemonless qc sourcing) is a future slice — see writeup (needs a qfcommit/qgetdata P2P handler + special-tx mempool lane; independent validation is Phase-L/BLS-blocked). Phase-1 fail-closed is the shipped behaviour. BLOCKER-2 (HIGH) — bestCL staleness. dashcore CheckCbTxBestChainlock rejects a null/older-than-prev committed ChainLock. New require_fresh_bestcl viability gate: best_cl_height must be >= prev_height-1 (a sufficient condition — prev block's committed CL height is <= prev_height-1, so a CL that fresh is guaranteed non-null and non-regressing). Post-restart / clsig-relay-gap fails closed. KAT. BLOCKER-3 — pre-emit hard gate. NodeCoinState::embedded_template_emit_ok re-validates the BUILT CbTx before it is served (re-parse; nHeight==prev+1; both merkle roots re-derived from the current SML/QuorumManager match the committed roots; height-class + bestCL guards re-asserted). Wired into the DASHWorkSource serve path: an embedded template that fails is discarded for the dashd fallback — the active safety cross-check on the hot path the old comment overstated. KAT. Review nit — quorum-tail health: parse_quorum_tail fails SAFE (skips quorum tracking) which would leave a STALE QuorumManager while the SML advances. The maintainer now signals set_quorum_healthy(parse_ok); viability fails the embedded arm closed on a malformed tail until a clean diff lands. All guards default OFF / unset (existing behaviour unchanged); main_dash arms them when the embedded arm can serve (testnet || --embedded-mainnet). Suites green: node_coin_state 9->13 (DKG window, stale bestCL, pre-emit, quorum health), coin_state_maintainer 9, stratum_work_source 35, embedded_gbt 9, quorum_root 11, mnlistdiff_root_parity 3, byte_parity 4; c2pool-dash builds+links.
Review addressed — three fail-closed guards land;
|
…c, not papered over Addresses the guards-delta review's one CHANGES-REQUIRED finding (H-1, HIGH). Bug: mnlistdiff quorum deltas are BASE-RELATIVE. On a malformed quorum tail the maintainer skipped the qmgr apply but the SML + sml_current_hash + the sml_base request tracker still advanced, so the skipped delta was PERMANENTLY lost — and set_quorum_healthy flipped back TRUE on the very next clean incremental diff. From there merkleRootQuorums is silently wrong, viability passes, and the pre-emit gate recomputes from the SAME wrong qmgr so it self-matches — the exact silent-wrong-root class the health flag exists to close. Trigger = an atomic parse failure on wire-format drift (dashd proto bump / new CFinalCommitment version); a soak cannot be expected to exercise it, only this fix. Fix (option-b HEAL, review-preferred): parse the quorum tail FIRST; on failure treat it as a quorum-axis reorg — on_sml_reorg() wipes SML+qmgr (have_sml=false, sml_current_hash=ZERO, demote, re-issue) and m_on_full_resync forces a full snapshot from ZERO (main_dash resets sml_base=ZERO + re-requests getmnlistd(ZERO,tip)). The diff's SML is NOT applied — we discard and re-sync. Base-continuity tightened: while sml_current_hash==ZERO (cold OR post-wipe) ONLY a full snapshot (base=ZERO) is accepted, so a clean incremental after a wipe is REJECTED, never papered over. State converges instead of latching off. KAT (test_dash_coin_state_maintainer): full snapshot (ok) -> incremental with a malformed tail (wipe + resync fired, arm refuses) -> clean INCREMENTAL (STILL refuses; base-continuity rejects) -> full snapshot (recovers). 9->10 tests. Nits: (1) emit gate now re-asserts m_quorum_healthy for viability symmetry; (2) dkg_window.hpp carries a re-diff-vs-llmq/params.h note for vendored-pin bumps. All suites green: coin_state_maintainer 10, node_coin_state 13, stratum_work_source 35, embedded_gbt 9, mnlistdiff_root_parity 3, byte_parity 4, node_reception_wire 21; c2pool-dash builds+links.
H-1 fixed (option-b HEAL) + both nits — the last code gateCommit H-1 — quorum-health un-latch (HIGH): the maintainer now parses the quorum tail first; on failure it treats it as a quorum-axis reorg — Crucially, the base-continuity guard is tightened: while KAT ( Nit 1 (symmetry): Suites green: coin_state_maintainer 10, node_coin_state 13, stratum_work_source 35, embedded_gbt 9, mnlistdiff_root_parity 3, byte_parity 4, node_reception_wire 21; |
…assetlocked-amount The v0.2.4 testnet soak (commit 6e5599a) FAILED: at 2 of 5 embedded serves (heights 1518558, 1518560) dashd rejected the block via getblocktemplate {mode:proposal} with bad-cbtx-assetlocked-amount — the committed creditPoolBalance was low by exactly one block's platform reward (66,966,830 duffs). Root cause: the require_sml freshness gate only checks the SML/merkleRoot axis (sml_current_hash == prev_hash). But the DIP-0027 credit-pool seed (m_credit_pool) is set in a SEPARATE on_mnlistdiff step (the diff's embedded cbTx) and can LAG one block while the SML hash is already current (soak: at tip 1518559 the seed still held creditPool(1518558)). build_embedded_workdata's last_observed + platform_reward accrual then lands one block stale, and the BLOCKER-3 pre-emit gate re-derived only the merkle roots + bestCL, NOT creditPoolBalance — so the bad block was served instead of failing closed. 40% bad-cbtx among embedded serves. Fix: - track credit_pool_current_hash: set alongside the balance whenever the seed updates in on_mnlistdiff (== diff.blockHash). When the seed step is skipped (non-type-5 / parse-fail cbTx) the tag stays stale while the SML advances. - require_fresh_credit_pool gates BOTH viability and the pre-emit hard gate on credit_pool_current_hash == prev_hash — fail closed to the reward-safe dashd fallback when the seed lags. main_dash arms it when the arm can serve (testnet || --embedded-mainnet), like require_fresh_bestcl. - on_sml_reorg invalidates the credit-pool freshness too (seed hash -> ZERO). KAT (node_coin_state): an SML-current-but-credit-pool-LAGGED bundle fails closed on BOTH viability and the pre-emit gate; it serves only when the seed is current at the tip. 13->14 tests. All suites green: node_coin_state 14, coin_state_maintainer 10, stratum_work_source 35, embedded_gbt 9, mnlistdiff_root_parity 3, byte_parity 4, node_reception_wire 21; c2pool-dash builds+links.
Soak-found bad-cbtx fixed — creditPool freshness gate (4th CbTx axis)The v0.2.4 soak on Root cause (confirmed against the soak jsonl): the require_sml freshness gate only covered the SML/merkleRoot axis ( Fix (all three parts requested):
The intermittent nature (3/5 serves were correct) means the gate fails-closes exactly the lagged cases and still serves when the seed is fresh — reward-safe without killing the arm. Suites green: node_coin_state 14, coin_state_maintainer 10, stratum_work_source 35, embedded_gbt 9, mnlistdiff_root_parity 3, byte_parity 4, node_reception_wire 21; |
…ate — fix build-vs-serve skew The re-soak of the hash-tag fix (88788a0) STILL FAILED: 2/10 embedded serves (heights 1518609, 1518620) were dashd-rejected bad-cbtx-assetlocked-amount, each committing creditPool(N-1) — one platform reward (66,966,830 duffs) short — with require_fresh_credit_pool PASSING (preemit_failed=false). Root cause: credit_pool_current_hash == prev_hash is a block-HASH TAG used as a PROXY for balance freshness, and it is unsound under a BUILD-vs-SERVE caching skew. The template is BUILT (at a tip-change instant) with a stale credit-pool seed and CACHED; by serve/emit time the hash-tag has gone fresh, but the pre-emit gate re-derived only the two merkle roots, never re-comparing the cached CbTx's baked creditPoolBalance to the current seed. (protx diff confirmed the WIRE seed was correct — it is a cache/timing skew, not a bad diff.) Fix (value-check, not hash-proxy — mirror the merkle-root re-derivation): - embedded_template_emit_ok now re-derives expected = current_seed_balance + platform_reward(next_h) (the SAME accrual build_embedded_workdata does) and requires the BUILT CbTx's creditPoolBalance == expected. A pure seed-delta check (the reward cancels): a template built over an older seed fails closed. - cached_work re-validates an EMBEDDED cache HIT against the current coin-state before serving it (new template_cache_is_embedded_ tag + serve-time emit_ok): a stale-built template is dropped and re-sourced (fresh embedded or dashd fallback), so a cached bad CbTx is never served after the seed advances. KATs: - node_coin_state build-vs-serve skew: build over seed S(N-2), advance to S(N-1) KEEPING the hash-tag fresh (the OLD gate would pass), the pre-emit VALUE check REJECTS the stale-built template and accepts a freshly-built one. Real re-soak values (creditPool 33971546001156 -> 33971612967986, +66966830). - stratum serve-path: a stale-cached embedded template is dropped + re-sourced over the advanced seed at serve time (log: "cached EMBEDDED template failed serve-time re-check — re-sourcing"). All suites green: node_coin_state 15, stratum_work_source 36, coin_state_maintainer 10, embedded_gbt 9, mnlistdiff_root_parity 3, byte_parity 4, node_reception_wire 21; c2pool-dash builds+links.
Re-soak failure fixed — creditPool VALUE re-check (hash-proxy was unsound)The re-soak on Root cause: Fix (both, value re-check is load-bearing):
KATs (build-vs-serve, per your requirement):
Suites green: node_coin_state 15, stratum_work_source 36, coin_state_maintainer 10, embedded_gbt 9, mnlistdiff_root_parity 3, byte_parity 4, node_reception_wire 21; |
…heck backstop (re-soak #2 fix) Re-soak #2 of the hash+value fix (bf1a531) still hit bad-cbtx-assetlocked-amount at 1518657 (POST-RESTART): the credit-pool SEED itself (m_credit_pool) was one block behind the tip — committed creditPool(N-1). Both prior gates were SELF-REFERENTIAL: they compare the built template against m_credit_pool, so a wrong seed is invisible (built value = stale_seed + reward is self-consistent but wrong). A seed-derived self-check (hash OR value) cannot catch a wrong seed. Verified the wire is correct: captured a real testnet INCREMENTAL mnlistdiff (base=1518667, blockHash=1518669) — its embedded cbTx carries nHeight=1518669 and creditPool=creditPool(1518669), i.e. the TIP's seed. Full-snapshot likewise. So the seed source is right; the live staleness is an async seed-lag, and the fix must be an INDEPENDENT verify that does not derive from the seed value. Fix v3 (upstream, at the seed): 1. INDEPENDENT seed-height gate. set_credit_pool now records the seed cbTx's OWN nHeight (authoritative off the wire). Viability AND the pre-emit gate require credit_pool_height == m_prev_height (the tip height). A seed one block behind the tip fails closed regardless of its (self-consistent) value/hash — the only check that catches it. Replaces the unsound hash-tag proxy. 2. GBT-xcheck reward-safety BACKSTOP. When a dashd is reachable, cross-check the embedded CbTx creditPool against dashd getblocktemplate BEFORE serving; on a same-height mismatch, serve dashd's reward-safe template. Catches ANY seed bug (present/future). Opt-in via set_gbt_xcheck (main_dash enables it for testnet || --embedded-mainnet, where a dashd fallback exists); pure-daemonless deployments rely on the independent seed-height gate. KATs: - coin_state_maintainer POST-RESTART: cold snapshot @ 1518654 -> first incremental @ 1518655 MUST advance the seed (credit_pool_height 1518654->1518655); a non-advancing diff (cbTx not newer) leaves it behind and the height gate fails closed at the next tip. Diffs carry real type-5 cbTx seeds (encode_cbtx). - node_coin_state: a seed one block behind the tip refuses embedded (real re-soak values, 1518655 vs tip 1518656); the pre-emit VALUE re-check still rejects a build whose creditPool != current seed + reward (defence in depth). - stratum: GBT-xcheck serves dashd on a creditPool mismatch; cache-hit re-source unchanged. - mnlistdiff_root_parity: real full + incremental wire fixtures prove the diff cbTx carries the seed for its own block (nHeight/creditPool). All suites green: node_coin_state 15, coin_state_maintainer 11, stratum_work_source 37, embedded_gbt 9, mnlistdiff_root_parity 4, byte_parity 4, node_reception_wire 21; c2pool-dash builds+links.
Re-soak #2 fixed — INDEPENDENT seed-height gate + GBT-xcheck (v3, upstream at the seed)Re-soak #2 correctly refuted the self-referential fixes: the credit-pool seed itself lagged one block after a cold-snapshot restart, and a seed-derived self-check (hash OR value) cannot catch a wrong seed (built = Diagnosis (traced the seed, verified the wire): captured a real testnet incremental mnlistdiff (base=1518667, blockHash=1518669) and decoded it through the vendor codec — its embedded cbTx carries Fix v3 (upstream, at the seed):
KATs (per your #4 — model the post-restart transition):
On the seed-advance (#1): the ingestion code + wire are correct (proven above); the live lag is async timing. The independent height-gate makes it reward-safe (fails closed when the seed's own height is behind the tip; serves when it matches), and the GBT-xcheck is the backstop. If the async lag proves persistent in the re-soak (arm falling back too often rather than serving), that's a throughput tune, not a reward-safety issue. Suites green: node_coin_state 15, coin_state_maintainer 11, stratum_work_source 37, embedded_gbt 9, mnlistdiff_root_parity 4, byte_parity 4, node_reception_wire 21; |
…special txs Verification (oracle-design critique): confirmed the C-3 special-tx exclusion is PRESENT and APPLIED on the embedded serving path — build_embedded_workdata (the sole populator of the block's tx set) calls get_sorted_txs_with_fees with exclude_special=true, and the filter `if (exclude_special && entry.tx.type != 0) continue;` (mempool.hpp) drops every non-zero tx type (a superset of the required DIP types 1-4,6,8,9; type 5 is the coinbase, never a mempool tx). The critique's "doesn't filter special types" reconciles to: the default path (exclude_special =false, for general mempool pricing) does not filter, but the embedded arm always passes true. No source fix needed — the bug is not present. Rationale for the exclusion: build_embedded_cbtx commits merkleRootMNList / merkleRootQuorums / creditPoolBalance from the SML/quorum/seed state WITHOUT applying the block's own txs' state effects, so a selected ProTx (1-4) / qc commitment (6) / asset-lock/unlock (8/9) would make the committed CbTx inconsistent with the block => consensus-invalid (bad-cbtx / bad merkle) that a field-compare misses but dashd proposal-mode rejects. Modeling own-block special-tx state deltas is a Phase-2 lift; until then they are excluded. Adds an END-TO-END KAT (previously only the get_sorted_txs_with_fees level was pinned): seed the mempool with a standard type-0 tx plus DIP special txs of every type 1,2,3,4,6,8,9 — each UTXO-priced (fee-known) so it WOULD be selected but for the type filter — build via build_embedded_workdata, and assert the embedded block contains ONLY the type-0 tx and none of the special-tx ids leak in. embedded_gbt 9->10 tests; mempool 19 (existing get_sorted-level C-3 test intact).
C-3 special-tx exclusion — VERIFIED present + applied (not a bug); end-to-end KAT added (
|
The GATE-LIFT proof for the interim mainnet gate carried by #779. Branches off #779, so it includes the C-2/H-6/H-7 SML-axis wiring; this PR adds what actually lets the mainnet gate lift and v0.2.4 ship.
THE deliverable: real-dashd byte-parity (H-5)
The prior embedded_gbt KAT was self-referential (c2pool built both sides). This replaces it with a verbatim Dash Core testnet
getblocktemplatecapture (block 1,518,413) as the oracle —test/fixtures/dash_testnet_gbt_1518413.json. Thecoinbase_payloadis dashd's own type-5 CbTx extra_payload, not c2pool output.Result (test_dash_embedded_cbtx_byte_parity.cpp, 4/4 green):
parse_cbtx -> encode_cbtxreproduces dashd's 175-byte CbTx payload BYTE-IDENTICALLY — every field: nVersion, nHeight, merkleRootMNList, merkleRootQuorums, VarInt bestCLHeightDiff, the 96-byte BLS bestCLSignature, and the signed int64 creditPoolBalance.build_embedded_cbtxassembly reproduces dashd's non-root fields exactly (v3, nHeight=prev+1, bestCLHeightDiff = prev − bestCLHeight = 1, verbatim ChainLock sig, creditPool); with dashd's roots transplanted, the whole payload is byte-identical.No field diverged. The one piece not recomputed from scratch in-KAT is deriving the two merkle roots from the specific 375-entry / 109-quorum testnet SML (a heavy wire rebuild); those roots are proven equal to dashd's commitment out-of-band (the captured
protx diff 1 1518412reports the same roots dashd puts in the GBT) and root computation is pinned by test_dash_simplifiedmns / test_dash_quorum_root.Gaps closed by WIRING (not recoding)
get_sorted_txs_with_feesgains anexclude_specialoption;build_embedded_workdatapassestrueso embedded templates carry no special tx (dashd recomputes CbTx roots + creditPool from the block's own special txs → an unaccounted one = bad-cbtx). Default false preserves the mempool's general pricing (asset-unlock fee path unchanged).creditPoolBalance(N) = creditPoolBalance(N-1) + platformReward(N). Verified byte-exact against dashd's own consecutive-block series (every testnet block stepped the pool by exactly66966830duff). The prior code committed the stale last-observed balance — off by one platform reward.Verdict on mainnet daemonless validity
The CbTx wire form, field assembly, bestCL derivation, and creditPool accrual are proven byte-identical to a real dashd. Remaining before a mainnet gate-lift:
CalcMerkleRoot == fixture root(roots already proven == dashd's commitment; this closes the last from-wire step);subsidy.hppuses mainnet activation heights — fine for the mainnet gate target, but testnet embedded mining needs per-net heights before it is byte-correct (flagged, not a mainnet blocker).Once (1) lands, lifting the interim mainnet gate in
stratum/work_source.cppis a small follow-up — the v0.2.4 trigger.Draft — gate-lift evidence for review.