dash(replay-fold): gate payee cross-check on DIP3 ENFORCEMENT (fix h=1028163 self-derive divergence) [DRAFT] - #1274
Closed
frstrtr wants to merge 1 commit into
Conversation
…t activation The DML fold's coinbase payee cross-check fired from DIP3 ACTIVATION (h=1028160), but dashd only ENFORCES the coinbase paying GetMNPayee(list) from DIP0003EnforcementHeight (mainnet 1047200). dashd's own consensus rule CMNPaymentsProcessor::IsTransactionValid (masternode/payments.cpp:114) early-returns valid WITHOUT checking the det payee when !DeploymentDIP0003Enforced(nBlockHeight); deploymentstatus.h:52 spells out that DIP3 'active' and 'enforced' are deliberately different statuses. In the [activation, enforcement) window the historical coinbase pays the LEGACY masternode winner, not the deterministic projection, so a cross-check there is STRICTER THAN DASHD and false-poisons a byte-correct fold. Observed in the full-DIP3 self-derive replay: h=1028163 (the 3rd DIP3 block, 14 fresh MNs) poisoned with DML FOLD PAYEE MISMATCH even though merkleRootMNList MATCHED (computed==committed) — the MN SET was right, the projection was sound, the coinbase simply paid the legacy winner. The tie- break itself was already dashd-exact: payee_before() is memcmp over the proTxHash bytes == dashd uint256 operator< (base_blob::Compare), NOT the base_uint arithmetic order that bit Tier-2 (#1271); the SML-root match at the divergence height also proves the proTxHash byte layout is correct. So the fix is the enforcement gate, not the comparison. Mirror dashd exactly: run the payee cross-check only when DeploymentDIP0003Enforced(H) (H >= dip0003_enforcement_height, default 1047200). The merkleRootMNList SET self-check stays UNCONDITIONAL; the payee projection (pass 0) and nLastPaidHeight bookkeeping (pass 5) also run unconditionally, exactly as dashd BuildNewListFromBlock does from activation, so the payee axis stays fully derived and self-consistent across the window and is correct the instant enforcement begins. Production mines at tip (~2.5M), far above enforcement, so the money-path check is UNCHANGED — reward-safe. KAT (test_dash_replay_fold.cpp, DashReplayFoldPreEnforcement): the same state + same payment-block (coinbase pays nobody) POISONS at/above enforcement (strict where dashd enforces) and FOLDS THROUGH below it, with the SET self-check binding in both arms. Verified red (unconditional check poisons at h=102) -> green (gated check folds through). BLS=REAL.
This was referenced Aug 17, 2026
Owner
Author
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Port (B) — payee-ordering fold-correctness (DIP3-genesis regime)
Stacked on #1270 (
dash/uaf-coldbridge-replayfold-store-exclusion). DRAFT, do not merge.Root cause (NOT the tie-break)
The full-DIP3 self-derive replay poisoned at h=1028163 (3rd DIP3 block, 14 fresh MNs) with
DML FOLD PAYEE MISMATCHeven thoughmerkleRootMNListMATCHED (computed==committed). The MN SET was right; the payee cross-check was wrong to run there.I audited the tie-break the task fingered and it is already dashd-exact:
payee_before()=memcmp(a.data(), b.data(), 32) < 0== dashduint256 operator<(base_blob::Compare, uint256.h:55/59) — the storage-byte order, not thebase_uintarithmetic order that bit Tier-2 (dash(mempool): tx-serving block order — dashd GetHash() memcmp tie-break (ordering residual → 0) #1271).compare_by_last_paid_height/nRegisteredHeight/ pass-5 bookkeeping all byte-match dashdCompareByLastPaid_GetHeight/RebuildListFromBlock.proTxHashbyte layout is dashd-correct (it commitsproRegTxHash).The real bug: DIP3 has two heights. The deterministic list is built from ACTIVATION (1028160) but the coinbase paying
GetMNPayee(list)is only ENFORCED fromDIP0003EnforcementHeight(mainnet 1047200). dashd's own consensus ruleCMNPaymentsProcessor::IsTransactionValid(masternode/payments.cpp:114) early-returns valid without checking the det payee when!DeploymentDIP0003Enforced(h)(deploymentstatus.h:53). In[activation, enforcement)the historical coinbase pays the legacy masternode winner, not the deterministic projection — so the cross-check is stricter than dashd there and false-poisons a byte-correct fold.Fix (dashd-faithful, reward-safe)
Gate the payee cross-check on
DeploymentDIP0003Enforced(H)(H >= dip0003_enforcement_height, default 1047200), mirroring dashd exactly.merkleRootMNListSET self-check stays UNCONDITIONAL.nLastPaidHeightbookkeeping (pass 5) also stay unconditional — exactly as dashdBuildNewListFromBlockruns them from activation — so the payee axis is fully derived and self-consistent across the window and correct the instant enforcement begins.KAT (red → green), BLS=REAL
test_dash_replay_fold.cpp :: DashReplayFoldPreEnforcement— same state + same payment-block (coinbase pays nobody):AtEnforcementPayeeMismatchStillPoisons— H≥enforcement → poison (strict where dashd enforces).BelowEnforcementPayeeMismatchFoldsThrough— H<enforcement → folds through, SET self-check still binds,nLastPaidHeightstill bumped.BelowEnforcementPayeePaidIsNotFlaggedSkipped— control.Verified RED (forcing the old unconditional check poisons at h=102 with the exact
merkleRootMNList self-check PASSED … PAYEE MISMATCHmessage — the h=1028163 signature) → GREEN (gated check folds through). Fulltest_dash_mn_statefold+prestate suites: 31/31 pass, no regressions.