dash(replay-fold): mirror GetBlockTxOuts operator-reward split in the payee cross-check (h=1439234 sub-class) - #1284
Closed
frstrtr wants to merge 1 commit into
Conversation
… payee cross-check
The DML fold's pass-6 payee cross-check unconditionally required the
projected masternode's scriptPayout among the coinbase outputs. dashd
(masternode/payments.cpp GetBlockTxOuts:64-77) does not always emit it:
when nOperatorReward==10000 and scriptOperatorPayout is set, the operator
reward eats the entire MN share, masternodeReward folds to exactly 0, and
the ONLY MN output dashd emits — and its IsTransactionValid:109-139
requires — is scriptOperatorPayout. Demanding scriptPayout there is
stricter than dashd and false-poisons a byte-correct fold.
Live self-derive incident: h=1439234, proTxHash
71ed3bf59baa91d914ed93b6534cb8e2819f230167b6228cf77b9e03ceb2d006,
bps=10000, owner==operator self-host. The coinbase paid the operator
script for the full 1.51818084 share with no owner remainder; the
merkleRootMNList self-check PASSED (411073/411073), yet the fold hard-
stopped "engine poisoned, re-seed required". This recurs for every
100%-operator-reward payment (~every queue length while registered), so a
point patch would re-wedge; the general dashd-faithful branch is required.
Pass 0b now captures the payee's full pre-block payment tuple
(scriptPayout, scriptOperatorPayout, nOperatorReward — state already held
by fold_proreg/fold_proupserv). Pass 6 mirrors GetBlockTxOuts without
needing amounts:
- bps==10000 && operator-script set -> require scriptOperatorPayout, NOT
scriptPayout (dashd emits none).
- 0<bps<10000 -> masternodeReward = mnShare - floor(mnShare*bps/10000)
is provably >0, so require scriptPayout (today's behaviour); the
operator output's exact amount needs the fee reward the fold cannot
price (W5 gap), so never poison on the operator axis for a partial
split.
- otherwise -> require scriptPayout.
The poison message now dumps {scriptPayout, scriptOperatorPayout, bps} so
any future instance self-classifies.
Reward-safe by construction: a check-only change on the verification axis.
The requirement becomes EXACTLY dashd's required-output set, never weaker
— at bps=10000 a coinbase paying scriptPayout instead still hard-stops
(dashd would reject it too). The merkleRootMNList SET self-check and the
payee projection + nLastPaidHeight bookkeeping (passes 0/5) are untouched;
no live-mint behaviour changes.
KATs (test_dash_replay_fold.cpp, DashReplayFoldOperatorSplit):
- FullOperatorRewardPaysOperatorScriptFoldsThrough — the h=1439234 shape;
RED on the old selection (poisons a byte-correct fold), GREEN with the
faithful branch.
- FullOperatorRewardPayingOwnerScriptStillPoisons — strictness: a bps=10000
coinbase paying the owner script is invalid and still poisons.
- PartialOperatorRewardRequiresOwnerScript / ...MissingOwnerScriptPoisons —
partial splits keep requiring scriptPayout, unchanged.
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.
What
Port dashd's operator-reward split into the DML replay fold's pass-6 payee
cross-check so the projected payee matches dashd at h=1439234 and for the
whole event sub-class.
Why (live poison)
The daemonless self-derive hard-stopped:
The projection was correct — dashd paid the very MN c2pool projected. The
check was blind to the DIP3 operator-reward split:
71ed3bf5...ceb2d006, registered h=1434598,nOperatorReward=0x2710 = 10000 bps = 100.00%, owner==operator self-host.masternode/payments.cppGetBlockTxOuts:64-77: at bps=10000 theoperator reward eats the whole MN share,
masternodeRewardfolds to exactly0, so the ONLY MN output emitted (and required by
IsTransactionValid:109-139)is
scriptOperatorPayout.scriptPayoutis legitimately absent.1.51818084with no ownerremainder; roots matched
411073/411073. The fold demandedscriptPayoutandfalse-poisoned a byte-correct fold.
This recurs for every 100%-operator-reward payment (~one queue length apart
while registered), so a point patch would re-wedge — the general dashd-faithful
branch is required.
The fix (
replay_fold_engine.hpp)(scriptPayout, scriptOperatorPayout, nOperatorReward)— state already held byfold_proreg/fold_proupserv.GetBlockTxOutswithout needing amounts:bps==10000 && operator-script set→ requirescriptOperatorPayout, notscriptPayout(dashd emits none);0<bps<10000→masternodeReward = mnShare - floor(mnShare*bps/10000)isprovably
>0, so requirescriptPayout(unchanged); the operator output'sexact amount needs the fee reward the fold cannot price (W5 gap), so never
poison on the operator axis for a partial split;
scriptPayout(unchanged).{scriptPayout, scriptOperatorPayout, bps}so anyfuture instance self-classifies.
Passes 0/5 (projection +
nLastPaidHeightbookkeeping) are untouched — they werecorrect; the projected proTxHash equals the paid MN here.
Reward-safe by construction
Check-only change on the verification axis. The requirement becomes exactly
dashd's required-output set, never weaker: at bps=10000 a coinbase paying
scriptPayoutinstead of the operator output still hard-stops (dashd wouldreject it too). The
merkleRootMNListSET self-check stays unconditional; thepayee projection +
nLastPaidHeightbookkeeping stay derived. No live-mintbehaviour changes.
Tests (
DashReplayFoldOperatorSplit, red→green proven)FullOperatorRewardPaysOperatorScriptFoldsThrough— the h=1439234 shape; REDon the old selection (poisons a byte-correct fold), GREEN with the faithful
branch.
FullOperatorRewardPayingOwnerScriptStillPoisons— strictness: a bps=10000coinbase paying the owner script is invalid and still poisons.
PartialOperatorRewardRequiresOwnerScript/...MissingOwnerScriptPoisons—partial splits keep requiring
scriptPayout, unchanged.Build / verification
-DC2POOL_DASH_BLS=ON(real dashbls, relic-backed) —c2pool-dashself-reportsat init:
DASH BLS backend: REAL (dashbls linked; quorum-commitment verify armed).test_dash_mn_state: 130 passed, 1 skipped (data-driven), 0 failed.Stacked on
integrator/dashd-cut-early-quorum-memberset(#1277 branch). DRAFT— do not merge.