dgb(#82): won-block dual-path broadcaster — grind->reconstruct->submit, both arms ACCEPT on node B - #291
Merged
Merged
Conversation
The missing composition between leg-2's faithful won-block reconstruction and a node-B ProcessNewBlock ACCEPT. finalize_won_block_pow takes a reconstructed block (header[0..79] with merkle_root already set -- merkle FIRST, integrator-pinned ordering) and grinds the nonce at [76..79] THROUGH grind_won_nonce -> scrypt_pow_hash (the #286 digest CALL SSOT) until the DGB-Scrypt PoW satisfies the parent target, splices the winning nonce back, and regenerates the RPC-fallback hex in lockstep with the P2P-arm bytes. This closes the "high-hash, proof of work failed" rejection: the forced-won seam reached node B but carried no real PoW. SSOT call-through, never a bypass hash -- a finalized block is by construction one node B's own Scrypt validation accepts. Fail-closed (nullopt) on sub-80-byte input or no-nonce-in-budget, mirroring make_reconstruct_closure's never-a-partial-block posture. Standalone-guard discipline: depends ONLY on nonce_grinder.hpp (real btclibs scrypt + header-only u256); no core, no dgb OBJECT lib, no util/HexStr (local lowercase encoder, byte-identical to HexStr). Test links the SAME _dgb_scrypt_tus set as dgb_nonce_grinder_test. +5 KATs (dgb_won_block_finalize_test, 5/5 green): finalized header pow<=target via the same SSOT; winning nonce spliced LE into [76..79]; hex == bytes; ONLY the 4 nonce bytes mutate (merkle + tx tail untouched); fail-closed on a 79-byte input. Registered in BOTH build.yml --target allowlists (#143 trap). Fenced: src/impl/dgb/** + build.yml allowlist only. p2pool-merged-v36 surface: NONE. DGB-Scrypt standalone parent in the V36 default build.
…egtest magic default Cherry-picks e25de52 (regtest-gated forced-won-share live seam) onto the gate-grind-wire JOINT (won_block_finalize.hpp grind->reconstruct->submit), so the trigger and the grind machinery finally meet on ONE branch — the missing link behind ARM A 0-shares/120s timeout (trigger and machinery never met: empty forced-won grep on 91cc453 + e25de52 not an ancestor). Conflict resolution (6 hunks, all single-file main_dgb.cpp run-loop): union the run_node signature + flag parse + banner toward the forced-won seam (regtest/force_won_share/no_p2p_relay), keep the verbose --no-p2p-relay suppression log from grind-wire. --regtest-force-won-share stays FAIL-CLOSED: refused unless --regtest AND a --coin-daemon target, can never fire on mainnet. Folds DGB_REGTEST_MAGIC default = fabfb5da (DigiByte Core regtest pchMessageStart) into the soak script so the A/B harness runs without a manual env export; still overridable. Combined HEAD: build EXIT=0, --selftest EXIT=0, --help advertises both seams.
…ing BASE_B The ARM A won-detector fires on tip_b > BASE_B. If BASE_B is captured while node B is still IBD-syncing node A's 101 maturity blocks, B's normal sync catch-up trips the detector — a false positive that defeats the won-block gate. Block until B reaches A's height (120 x 0.5s cap) before baselining. Scripts-only, single-coin (DGB) scope; rides on combined HEAD 8079c94.
… stale c2pool-dgb ARM A aborts on "bind: Address already in use" when a prior --run instance still holds the fixed PREFIX-derived pool P2P listen port (5024), turning the gate into a phantom 120s timeout that proves nothing. Add kill_stale_c2pool() pre-flight before substrate startup. DGB-only pgrep match; localhost regtest.
…5s one-shot The forced-won-share live seam fired on a fixed 5s timer, BEFORE the embedded coin-daemon P2P producer completed its version/verack handshake and before any header reached HeaderChain (the first UpdateTip). A won block dispatched that early cannot reach node B by the P2P-relay arm (peer not yet connected) and races ahead of node B own sync, so neither broadcast path produced a ProcessNewBlock ACCEPT and the A/B soak grep saw "Final share count: 0" — the named blocker behind the gate. Replace the one-shot with a 1.5s readiness poll that latches and fires once BOTH (a) coin_p2p->is_handshake_complete() (node B reachable for the P2P-relay arm + submitblock RPC fallback) AND (b) HeaderChain::tip_hash() carries a tip (>=1 UpdateTip ingested -> a work template with a real previousblockhash exists). Bounded 60s fallback fires last-resort with a LOUD attributable log so a slow/stuck peer surfaces as a named blocker rather than a silent no-fire. Regtest-only; zero production-path change. Soak harness: ARM A now launches with --regtest --regtest-force-won-share so the gated seam is actually enabled (gate = --regtest AND --coin-daemon, both present on ARM A). build EXIT=0, --selftest EXIT=0, --help advertises both seams.
…(--no-p2p-relay) Rebuild the synthetic forced-won share header from node B's live getblocktemplate (prev + bits + height + coinbasevalue) so the reconstructed coinbase-only block is fully connectable in one move, not just prevhash-seeded (fixes residual bad-prevblk/bad-txnmrklroot/ bad-cb-height risk). BIP34 height encoded into the synthetic coinbase scriptSig. Soak now asserts BOTH arms: ARM A P2P relay, then ARM B submitblock-RPC fallback isolated via --no-p2p-relay.
…arse digibyted getblock pretty-prints the tx array across lines, so the single-line grep -o on "tx"[^]]*] matched nothing and tripped set -euo pipefail AFTER a won block had already been accepted by node B -- aborting the harness before the ARM A OK assertion and ARM B run. tr -d newlines before the grep so both arms assert correctly.
…et to make Line 87 carried a mid-line backslash (dgb_won_block_finalize_test \ v37_test) whose backslash-space sequence the shell parsed as a single literal-space argument, which make received as an empty target: gmake: *** No rule to make target " ". Stop. (exit 2) All test targets themselves built fine; this was purely the allowlist line continuation. Collapse to a single space (v37_test stays on the same line), matching the clean continuation form already used at the COIN_BCH allowlist.
…219) The second test-allowlist block carried the identical mid-line backslash before test_coin_broadcaster, which would feed make the same empty " " target. Collapse to a single space. grep -P "\\\\\s+\S" now clean across the whole workflow.
frstrtr
added a commit
that referenced
this pull request
Jun 23, 2026
dgb(#82): won-block dual-path broadcaster — grind->reconstruct->submit, both arms ACCEPT on node B
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 #82 broadcaster gate (BLOCK-REWARD-CRITICAL). Consolidates the gate-closer work onto master after #282 (soak substrate) and #286 (scrypt_pow/regrind SSOT) landed; duplicate substrate commits dropped as already-upstream.
What this lands (DGB-fenced: src/c2pool/main_dgb.cpp + scripts/dgb_regtest_won_block_soak.sh + src/impl/dgb/coin/won_block_finalize.hpp + test):
Live A/B proof (regtest, peer node B): ARM A P2P relay UpdateTip height=1115; ARM B submitblock fallback UpdateTip height=1116; both ProcessNewBlock ACCEPT, both coinbase-only NTX==1. Dual-path broadcaster invariant satisfied.
No consensus-value change (no subsidy/payout/version-gate/PPLNS). All commits GPG-signed.