dash(fees): W5-A fold-backed fee pricing behind a hash_serialized_2 graduation gate - #1237
dash(fees): W5-A fold-backed fee pricing behind a hash_serialized_2 graduation gate#1237frstrtr wants to merge 1 commit into
Conversation
…raduation gate The dominant dashd-only tx class behind the gbt-xcheck tx-merkle mismatches (20/34 across 26 events) is ordinary fee-payers spending coins OLDER than the node's replay horizon: the forward-built UTXOViewCache cannot price them, so compute_fee_locked marks them fee-unknown and the conservative exclusion drops them — embedded serves a strict SUBSET of dashd and the guard swaps. This wires the W3 full-history UTXO fold (replay_utxo_fold.hpp) in as the mempool's THIRD coin source, behind a reward-safety GRADUATION GATE: * replay_utxo_fold.hpp: format v2 adds the 'G' graduation record, written ONLY by a PASSING try_graduate() with the cursor standing exactly on the pinned anchor (h=2,516,758 / dashd gettxoutsetinfo hash_serialized_2); a hash mismatch POISONS the fold (latched refusal, nothing written). Anchor is KAT-injectable via ReplayUtxoFoldOptions. Opt-in prev-hash linkage check makes silently folding across a reorg structurally impossible (adapter always arms it; synthetic-chain KATs unaffected). * fold_fee_source.hpp (new): FoldFeeSource — the trust latch + threading adapter. lookup() answers ONLY while graduated AND live at the tip AND undisarmed; missing/spent/immature-coinbase all answer false. Any feed refusal (gap, prev-mismatch reorg tripwire, gate FAIL, store error) disarms until restart. Lock order Mempool::m_mutex -> FoldFeeSource:: m_mutex; the feed path never calls into Mempool. * mempool.hpp: set_fee_coin_lookup — a slot SEPARATE from the pinned-tx m_external_coin_lookup (that one is the dashd-RPC arm). Consulted only on the UTXOViewCache-miss + in-pool-parent-miss double miss, in compute_fee_locked AND in the selection-time vin resolution (pricing without resolution would be undone by the stale-input guard). Unset => byte-identical behaviour. * main_dash.cpp: --embedded-utxo-fold-fees DBPATH + REQUIRED --embedded-utxo-fold-expect HEX (the operator restates the anchor; a stale/foreign DB cannot graduate on its own say-so). DEFAULT OFF == nothing constructed. Feeds: the W2 --replay-bulk lane (Tee side consumer; genesis start-height sentinel refuses a cold fold above h=1 at STARTUP instead of a GATE FAIL 15 h of download later; tip_exclusion=0) + the live block_connected leg (idempotent acks, tolerated gaps, note_tip). The #1218 gbt-xcheck-txmerkle guard is untouched and stays the referee: serving still refuses on any divergence; this work makes the arm MATCH. KATs (test_dash_fold_fee_source.cpp, rides the test_dash_mempool target): red/green pair — UngraduatedFoldDoesNotPrice (master's exact behaviour with the fold wired and holding the coin) vs GraduatedFoldPricesOldCoinAndSelectsIt (exact fee, selected into the template); spent-in-fold and immature-coinbase exclusion; GateFailPoisonsAndDisarms; TipLagDropsTrust; GraduationRestoreAcrossReopen (wrong restatement => ignored); PrevLinkageRefusesOrphanFold.
|
vm905 build + KAT evidence (BLS=REAL)
Fold run STARTED on vm905 (the subsequent-soak half; PR does not block on it): Monitor: Sibling: #1238 (W5-B DSTX leg). Both legs are needed to close all 34/34 dashd-only txs; acceptance = 24 h ZERO tx-merkle mismatches with guards armed. |
|
Reconcile 2026-08-22: PARKED (keep-draft), not a dashd-cut blocker. W5-A fold-backed fee pricing (fold_fee_source.hpp) is a LIVE want for POST-CUT full-mempool tx-serving (reopens #145 tier-2 tx-merkle fee-unknown parity), but tx-serving is dropped from the full-cut recipe, and this PR is 56 behind with replay_utxo_fold.hpp/mempool.hpp churned underneath — rebasing now is wasted effort. Rebase when tx-serving parity (#145 / #130-#133) is picked up post-cut. |
W5-A: fold-backed fee pricing behind a hash_serialized_2 graduation gate
Goal (dashd-cut Tier-2 "W5", leg A): close the DOMINANT dashd-only tx class behind the
gbt-xcheck-txmerklemismatches — 20/34 txs across the 26-event diagnosis are ordinary fee-payers spending coins OLDER than the replay horizon. The forward-builtUTXOViewCachecannot price them,compute_fee_lockedmarks them fee-unknown, the conservative exclusion drops them, embedded serves a strict SUBSET of dashd, and the guard swaps to the dashd arm. This PR wires the W3 full-history UTXO fold (replay_utxo_fold.hpp) in as the mempool's THIRD coin source — behind a reward-safety graduation gate.DARK / flag-gated
--embedded-utxo-fold-fees DBPATH+ required--embedded-utxo-fold-expect HEX(the operator RESTATES the anchor hash — a stale/foreign DB cannot graduate on its own say-so). Default OFF: nothing is constructed,Mempool::m_fee_coin_lookupstays empty, production byte-identical. DASH lane only. No wire/consensus/share-format change.The graduation gate (reward-safety)
A wrong fee VALUE can overstate coinbasevalue = an invalid found block = a lost block — the tx-merkle guard does not cover fee values. So fold answers are arithmetically unreachable from the fee path until ALL of:
ReplayUtxoFold::try_graduate()runs with the cursor standing exactly on the pinned anchor (h=2,516,758) and proveshash_serialized_2byte-equal to dashd'sgettxoutsetinfo(3d14913768a9d492bfa7a42fe9b111cff625b80e35bb4133e1d60cf3991c2319). PASS writes the durable'G'record (format v2); FAIL poisons the fold (latched refusal, nothing written). Restart restores trust ONLY from a recorded pass that also matches the operator restatement.Every failure path reverts to today's fee-unknown exclusion. The #1218 gbt-xcheck-txmerkle guard is untouched and stays the referee — this PR makes the arm MATCH, it never relaxes the guard.
Pieces
replay_utxo_fold.hpp: format v2 +'G'graduation record,try_graduate()/graduation()/ poison latch, KAT-injectable anchor, opt-in prev-hash linkage check (adapter always arms it; pre-existing synthetic-chain KATs byte-identical).fold_fee_source.hpp(new):FoldFeeSource— trust latch + threading adapter (fold core is single-thread by design). Lock orderMempool::m_mutex → FoldFeeSource::m_mutex; the feed path never calls into Mempool — no cycle. Missing / spent / immature-coinbase outpoints all answer false.mempool.hpp:set_fee_coin_lookup— a slot SEPARATE from the pinned-txm_external_coin_lookup(the dashd-RPC arm; routing mempool pricing through it would be an unflagged production change). Consulted only on the view-miss + in-pool-parent-miss double miss, incompute_fee_lockedAND in the selection-time vin resolution (pricing without resolution would be undone by the stale-input guard).main_dash.cpp: flag family; feeds = the W2--replay-bulklane (Tee side consumer; genesis start-height sentinel refuses a cold fold above h=1 at STARTUP instead of a GATE FAIL 15 h of download later;tip_exclusion=0) + the liveblock_connectedleg (idempotent acks, tolerated gaps,note_tip).KATs (
test_dash_fold_fee_source.cpp, rides thetest_dash_mempooltarget)Red/green pair:
UngraduatedFoldDoesNotPrice(master's exact behaviour with the fold wired and holding the coin — the red shape) vsGraduatedFoldPricesOldCoinAndSelectsIt(exact fee, SELECTED into the template). PlusSpentCoinInFoldNotPriced,ImmatureCoinbaseInFoldNotPriced,GateFailPoisonsAndDisarms,TipLagDropsTrust,GraduationRestoreAcrossReopen(wrong restatement ⇒ ignored),PrevLinkageRefusesOrphanFold.What this PR does NOT include
The full genesis→anchor fold run to the live graduation gate (~29-31 GB download, 6-16 h on vm905) is the SUBSEQUENT soak; the PR lands on KATs + mechanism. The DSTX leg (14/34, necessary-but-not-sufficient) is the sibling PR.
Measurement plan (post-merge, canary posture:
--coin-rpckept, guards active)--replay-utxo-hash --replay-utxo-expectexit-0 as independent re-check.--embedded-utxo-fold-feesarmed on the canary — expect the 20/34 fee-unknown class to close; classify residual dashd-only txs (should be ≈pure DSTX).gbt-xcheck-txmerkle-mismatchevents over all tx-carrying templates, guard still armed; quote fallback-TIME share as well as event counts.