dgb(stratum): emit only the won-block body the frozen header commits to (G3b bad-txnmrklroot) - #815
Merged
Merged
Conversation
The G3b won block on the empty-embedded-chain path was rejected
bad-txnmrklroot. The reconstruct in mining_submit() hand-rolled the block
framing and could emit a body the ALREADY-HASHED header does not commit
to. Two divergences, neither of which the header can be re-derived around
(the miner hashed the merkle root; any post-hoc coinbase mutation
invalidates the PoW binding):
1. TX-SET vs COMMITMENT. The header merkle root is the ascent of the
frozen stratum merkle branches from the coinbase txid. With NO branches
in the job -- the empty-chain case, where get_stratum_merkle_branches()
is still a Stage-4c stub returning {} -- the committed root IS the bare
coinbase txid, i.e. the miner committed to a coinbase-only block.
Appending job->tx_data anyway is a deterministic bad-txnmrklroot.
2. BIP144 WITNESS FORM vs BIP141 COMMITMENT. The marker/flag + reserved
witness item were inserted on segwit_active alone. A witness-bearing
coinbase without the aa21a9ed commitment output is rejected
(unexpected-witness), and the PPLNS connection coinbase only carries
that output when the producer seam populates segwit_commitment_script
-- which the empty-chain path does not. The commitment CANNOT be
injected at submit time: it changes the coinbase txid.
Routes the framing through a new pure byte-level SSOT,
coin/won_block_serialize.hpp: body == committed tx set, witness form IFF
the coinbase carries the commitment, legacy otherwise (a coinbase-only
legacy block is valid on a segwit-active chain). Both degradations are
logged loudly with the upstream seam to populate -- the block ships
degraded-but-valid instead of complete-but-rejected -- and the won-block
wire bytes are now dumped so any daemon-side reject is decodable offline
without a second live repro.
KAT: 7 cases pinning both invariants plus the structural guarantee that
the BIP144 reserialize leaves the txid preimage byte-identical.
dgb_won_block_serialize_test 7/7, dgb_work_source_test 31/31, c2pool-dgb
links. Registered in the build.yml target allowlist (#143 NOT_BUILT trap).
Scope: src/impl/dgb only. p2pool-merged-v36 surface: none -- parent-block
framing, not share format / PoW / PPLNS.
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.
Closes the G3b
bad-txnmrklrootreject on the empty-embedded-chain won block.Root cause (corrected). The reconstruct in
mining_submit()hand-rolled the block framing and could emit a body the ALREADY-HASHED header does not commit to. Neither divergence can be fixed by re-deriving the header — the miner hashed the merkle root, so any post-hoc coinbase mutation invalidates the PoW binding:get_stratum_merkle_branches()is still a Stage-4c stub returning{}) the committed root is the bare coinbase txid — the miner committed to a coinbase-only block. Appendingjob->tx_dataanyway is a deterministicbad-txnmrklroot.segwit_activealone. A witness-bearing coinbase with noaa21a9edcommitment output is rejectedunexpected-witness, and the PPLNS connection coinbase only carries that output when the producer seam populatessegwit_commitment_script(the empty-chain path does not). The commitment cannot be injected at submit time — it changes the coinbase txid. That correction supersedes the earlier "inject via add_witness_commitment at reconstruct" plan; injection belongs at job build (coinb2), a follow-on producer-seam slice.Fix. New pure byte-level SSOT
src/impl/dgb/coin/won_block_serialize.hpp: body == committed tx set; witness form IFF the coinbase carries the commitment, legacy otherwise (a coinbase-only legacy block is valid on a segwit-active chain). Both degradations log loudly naming the upstream seam to populate — the block ships degraded-but-valid rather than complete-but-rejected — and the won-block wire bytes are now dumped so any daemon-side reject is decodable offline without a second live repro.Evidence.
dgb_won_block_serialize_test7/7 (both invariants + the structural guarantee that BIP144 reserialize leaves the txid preimage byte-identical),dgb_work_source_test31/31,c2pool-dgblinks. Registered in thebuild.ymltarget allowlist (#143 NOT_BUILT trap).Scope.
src/impl/dgbonly, isolation-clean. p2pool-merged-v36 surface: none — parent-block (daemon-facing) framing, not share format / PoW / PPLNS.Follow-ons (not this PR): populate
segwit_commitment_script+coinbase_script(BIP34 height) at themain_dgb.cpp:861producer seam — both are currently left unset, so the coinbase also carries no BIP34 height push (bad-cb-heightwould surface next, after the merkle root check passes); and un-stub the branch cache to claim body-tx fees.