Skip to content

dgb(#82): won-block dual-path broadcaster — grind->reconstruct->submit, both arms ACCEPT on node B - #291

Merged
frstrtr merged 9 commits into
masterfrom
dgb/82-gate-grind-wire
Jun 21, 2026
Merged

dgb(#82): won-block dual-path broadcaster — grind->reconstruct->submit, both arms ACCEPT on node B#291
frstrtr merged 9 commits into
masterfrom
dgb/82-gate-grind-wire

Conversation

@frstrtr

@frstrtr frstrtr commented Jun 21, 2026

Copy link
Copy Markdown
Owner

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):

  • won_block_finalize.hpp: grind->reconstruct->submit JOINT — finalize_won_block_pow grinds nonce [76..79] through the dgb(#82 4b/4c): scrypt_pow.hpp digest CALL SSOT + satisfaction-gate KAT #286 scrypt_pow SSOT against the parent target, splices the winning nonce, regenerates RPC-fallback hex in lockstep with the P2P-arm bytes. Fail-closed on <80-byte input. 5/5 KAT.
  • main_dgb.cpp run-loop: forced-won-share fires on UpdateTip-ready (not a blind 5s one-shot); ARM B isolated via --no-p2p-relay; GBT-seeded connectable block resolves the synthetic-share reconstruct-nullopt gap.
  • soak harness: node-B IBD-sync wait before BASE_B baseline; stale-port pre-flight; multi-line getblock JSON flattened before NTX parse.

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.

frstrtr added 7 commits June 21, 2026 07:05
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.
frstrtr added 2 commits June 21, 2026 07:37
…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
frstrtr merged commit 6cc3bab into master Jun 21, 2026
30 checks passed
@frstrtr
frstrtr deleted the dgb/82-gate-grind-wire branch June 21, 2026 08:31
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
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