Skip to content

dash(fees): W5-A fold-backed fee pricing behind a hash_serialized_2 graduation gate - #1237

Draft
frstrtr wants to merge 1 commit into
masterfrom
w5-fold-fee-seam
Draft

dash(fees): W5-A fold-backed fee pricing behind a hash_serialized_2 graduation gate#1237
frstrtr wants to merge 1 commit into
masterfrom
w5-fold-fee-seam

Conversation

@frstrtr

@frstrtr frstrtr commented Aug 14, 2026

Copy link
Copy Markdown
Owner

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-txmerkle mismatches — 20/34 txs across the 26-event diagnosis are ordinary fee-payers spending coins OLDER than the replay horizon. The forward-built UTXOViewCache cannot price them, compute_fee_locked marks 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_lookup stays 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:

  1. GRADUATEDReplayUtxoFold::try_graduate() runs with the cursor standing exactly on the pinned anchor (h=2,516,758) and proves hash_serialized_2 byte-equal to dashd's gettxoutsetinfo (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.
  2. LIVE at the tip — fold cursor within 2 blocks of the last observed tip (a catching-up fold could hold since-spent coins ⇒ superset ⇒ guard trip).
  3. UNDISARMED — any feed refusal (gap, store error, gate FAIL, prev-hash linkage break = the reorg tripwire, structurally preventing a silent orphan fold) latches disarm until restart. With ChainLocks live, mainnet reorgs are ~nonexistent; the latch is a rare-case backstop.

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 order Mempool::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-tx m_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, in compute_fee_locked AND 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-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).

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 — the red shape) vs GraduatedFoldPricesOldCoinAndSelectsIt (exact fee, SELECTED into the template). Plus SpentCoinInFoldNotPriced, 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-rpc kept, guards active)

  1. BEFORE: 24 h on this build, both flags OFF (byte-identical dark ship) — re-baseline the mismatch rate (the 26/34 figure predates this build).
  2. Fold run on vm905 to the anchor; graduation record + --replay-utxo-hash --replay-utxo-expect exit-0 as independent re-check.
  3. AFTER-1: --embedded-utxo-fold-fees armed on the canary — expect the 20/34 fee-unknown class to close; classify residual dashd-only txs (should be ≈pure DSTX).
  4. Acceptance (with the DSTX PR): 24 h, ZERO gbt-xcheck-txmerkle-mismatch events over all tx-carrying templates, guard still armed; quote fallback-TIME share as well as event counts.

…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.
@frstrtr

frstrtr commented Aug 14, 2026

Copy link
Copy Markdown
Owner Author

vm905 build + KAT evidence (BLS=REAL)

  • Build: cmake -DC2POOL_DASH_BLS=ON -DDASHBLS_ROOT=~/dashbls-prefix on vm905, exit 0; [init] DASH BLS backend: REAL.
  • test_dash_mempool (mempool + W3 fold + new W5-A suites): 71/71 PASSED — including FoldFeeSource.UngraduatedFoldDoesNotPrice (red shape = master behaviour), FoldFeeSource.GraduatedFoldPricesOldCoinAndSelectsIt (green), spent/immature exclusion, GateFailPoisonsAndDisarms, TipLagDropsTrust, GraduationRestoreAcrossReopen, ReplayUtxoFoldGate.PrevLinkageRefusesOrphanFold.
  • test_dash_isdlock regression: 12/12 PASSED.

Fold run STARTED on vm905 (the subsequent-soak half; PR does not block on it):
~/utxo-fold/, genesis start (--replay-bulk-start 1), capture retained (~/utxo-fold/capture, one-chance rule from #1147), separate ports (9033/8199), nice/ionice — no interference with running soaks. Startup log confirms the dark posture:

[run] W5-A FOLD FEE SOURCE armed (DARK until graduated): db=/home/ubuntu/utxo-fold/db fold_h=0 graduated=no anchor_h=2516758
[run] W5-A: UTXO fold FEEDS from the bulk lane (Tee side; fold cursor h=0)

Monitor: ssh vm905 "grep -E 'GATE (PASS|FAIL)|REPLAY-UTXO|FOLD-FEE' ~/utxo-fold/fold.log | tail" — the GATE line at h=2,516,758 is the graduation event; expect a 6-16 h window (download-bound).

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.

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.

1 participant