Skip to content

Commit 184ec81

Browse files
authored
dgb(#82): won-block dual-path regtest soak harness (substrate + ARM A) [DRAFT] (#282)
* dgb(#82): won-block dual-path regtest soak harness (substrate + ARM A) Closes the #82 broadcaster dual-path gate on the wire: live UpdateTip won block (regtest diff, no test-only seam) -> reconstruct coinbase-only -> ARM A submit_block_p2p_raw relays to a peer node, asserting PROP1 (coinbase-only accepted, not fail-open) + PROP2/ARM-A (P2P reaches network). Scripts-only, fenced (scripts/), DGB-isolated, localhost regtest, self-service creds. Two live-leg items remain: digibyted self-provisioning (daemon-gated, only namecoind on fleet) and ARM B submitblock-fallback isolation mechanism (no --no-p2p-relay flag -> integrator design call), both explicit in-file. * dgb(#82): add --no-p2p-relay to isolate ARM B submitblock fallback Approved operator toggle (integrator UID1709, option a): suppress ONLY the embedded P2P-relay arm in main_dgb on_block_found, leaving the external-digibyted submitblock seam (RPC fallback) live. Lets the #82 regtest soak prove the submitblock fallback lands the won block on its own, deterministically, instead of being masked by a silent or dead P2P relay -- the historical #82 gap. Additive, DGB-fenced (src/c2pool/main_dgb.cpp only); no consensus/share/ PPLNS/shared surface. Real operator flag, not a test-only seam (mirrors the no-test-seam principle honored for ARM A). --------- Co-authored-by: frstrtr <frstrtr@users.noreply.github.com>
1 parent 946fd90 commit 184ec81

2 files changed

Lines changed: 164 additions & 4 deletions

File tree

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
#!/usr/bin/env bash
2+
# dgb_regtest_won_block_soak.sh -- DGB #82 won-block dual-path soak (regtest substrate).
3+
#
4+
# Closes the DGB broadcaster dual-path gate (#82): a real won block must reach the
5+
# network down BOTH arms before #281 (the faithful reconstruct-closure install at
6+
# main_dgb.cpp:317) can merge. Per integrator (2026-06-21, UID1708) two properties
7+
# must be PROVEN on the wire, not merely "not-errored":
8+
#
9+
# PROP 1 (COINBASE-ONLY IS CORRECT, NOT FAIL-OPEN) -- template_other_txs_fn is
10+
# empty today ("correct-and-empty"). The soak must reconstruct + get a
11+
# COINBASE-ONLY block ACCEPTED by a peer, proving empty is valid rather
12+
# than a silent malformed-block drop (the original #82 root cause).
13+
# PROP 2 (BOTH ARMS REACH THE NETWORK) --
14+
# ARM A (P2P PRIMARY) : on_block_found -> make_reconstruct_closure_from_template
15+
# (#280) -> submit_block_p2p_raw relays to a real peer
16+
# (main_dgb.cpp:317/333). Assert peer node B ingests it.
17+
# ARM B (RPC FALLBACK): the independent external submitblock RPC fallback (the
18+
# &coin_node arm at main_dgb.cpp:317) lands the SAME block.
19+
#
20+
# WON-SHARE TRIGGER: UpdateTip-driven, no test-only seam. Unlike NMC (no --regtest
21+
# CLI -> forced-won-share KAT seam, PR #276), c2pool-dgb already targets an external
22+
# regtest daemon directly (main_dgb.cpp:393 "dev regtest daemon"; :433 fresh-regtest
23+
# locator), and at regtest difficulty the embedded work loop finds a winning share
24+
# naturally -- a genuine live UpdateTip won-block, not a synthetic inject.
25+
#
26+
# CLI is real (main_dgb.cpp:534-560): --run --coin-daemon H:P --coin-rpc H:P
27+
# --coin-rpc-auth <digibyte.conf> --coin-magic <hex> --coin-genesis <hash>.
28+
#
29+
# TWO OPEN LIVE-LEG ITEMS (surfaced to integrator, do NOT read as covered):
30+
# (1) digibyted regtest binary self-provisioned -- not yet on the fleet (only
31+
# namecoind exists). Until provisioned this is a substrate validator + harness,
32+
# not a green gate.
33+
# (2) ARM B isolation MECHANISM. There is NO --no-p2p-relay flag; both arms land on
34+
# node A. Cleanly proving the submitblock fallback INDEPENDENTLY (P2P arm
35+
# suppressed) is a design decision for integrator -- mirrors NMC's
36+
# forced-seam-vs-CLI call. Current draft asserts ARM B against node A only as a
37+
# placeholder and is marked GATED below until that mechanism is chosen.
38+
#
39+
# PER-COIN ISOLATION: DGB only. Localhost-only. Self-service creds (no secret on any
40+
# coordination card). Ports sit outside the bitcoin-family 1844x regtest band.
41+
set -euo pipefail
42+
43+
CLI_BIN="${DGB_CLI:-digibyte-cli}"
44+
DAEMON_BIN="${DGB_DAEMON:-digibyted}"
45+
C2POOL_DGB="${C2POOL_DGB:-c2pool-dgb}"
46+
47+
DATADIR_A="${DGB_REGTEST_DATADIR:-$HOME/.c2pool-dgb-regtest}"
48+
DATADIR_B="${DATADIR_A}-peer"
49+
RPCPORT_A=${DGB_RPCPORT_A:-18843}
50+
RPCPORT_B=${DGB_RPCPORT_B:-18853}
51+
P2PPORT_A=${DGB_P2PPORT_A:-18844}
52+
P2PPORT_B=${DGB_P2PPORT_B:-18854}
53+
PAYOUT_LABEL="c2pool-soak"
54+
55+
log() { echo "[dgb-won-soak $(printf "%(%H:%M:%S)T")] $*" >&2; }
56+
die() { echo "[dgb-won-soak FAIL] $*" >&2; exit 1; }
57+
need() { command -v "$1" >/dev/null 2>&1 || die "missing binary: $1 (set DGB_DAEMON/DGB_CLI or self-provision DigiByte Core)"; }
58+
59+
cli_a() { "$CLI_BIN" -regtest -datadir="$DATADIR_A" -rpcport=$RPCPORT_A "$@"; }
60+
cli_b() { "$CLI_BIN" -regtest -datadir="$DATADIR_B" -rpcport=$RPCPORT_B "$@"; }
61+
62+
C2POOL_PID=""
63+
cleanup() {
64+
[ -n "$C2POOL_PID" ] && kill "$C2POOL_PID" >/dev/null 2>&1 || true
65+
cli_a stop >/dev/null 2>&1 || true
66+
cli_b stop >/dev/null 2>&1 || true
67+
}
68+
trap cleanup EXIT
69+
70+
need "$DAEMON_BIN"; need "$CLI_BIN"; need "$C2POOL_DGB"; need openssl
71+
72+
provision() {
73+
local dd="$1" rpcport="$2"
74+
mkdir -p "$dd"; chmod 700 "$dd"
75+
if [ ! -f "$dd/digibyte.conf" ]; then
76+
local pass; pass="$(openssl rand -hex 24)"
77+
cat > "$dd/digibyte.conf" <<CONF
78+
regtest=1
79+
server=1
80+
rpcuser=c2pool
81+
rpcpassword=$pass
82+
fallbackfee=0.0002
83+
[regtest]
84+
rpcport=$rpcport
85+
CONF
86+
chmod 600 "$dd/digibyte.conf"
87+
fi
88+
}
89+
provision "$DATADIR_A" "$RPCPORT_A"
90+
provision "$DATADIR_B" "$RPCPORT_B"
91+
92+
wait_rpc() { local n=0; until "$@" getblockcount >/dev/null 2>&1; do n=$((n+1)); [ $n -gt 60 ] && die "rpc never came up: $*"; sleep 0.5; done; }
93+
tip_b() { cli_b getblockcount; }
94+
95+
# --- substrate: two peered regtest nodes --------------------------------------
96+
log "starting node A (regtest RPC $RPCPORT_A / P2P $P2PPORT_A)"
97+
"$DAEMON_BIN" -regtest -datadir="$DATADIR_A" -rpcport=$RPCPORT_A -port=$P2PPORT_A -daemon >/dev/null
98+
wait_rpc cli_a
99+
log "starting node B (regtest RPC $RPCPORT_B / P2P $P2PPORT_B), peering to A"
100+
"$DAEMON_BIN" -regtest -datadir="$DATADIR_B" -rpcport=$RPCPORT_B -port=$P2PPORT_B \
101+
-connect=127.0.0.1:$P2PPORT_A -daemon >/dev/null
102+
wait_rpc cli_b
103+
104+
cli_a createwallet "$PAYOUT_LABEL" >/dev/null 2>&1 || cli_a loadwallet "$PAYOUT_LABEL" >/dev/null 2>&1 || true
105+
ADDR_A="$(cli_a getnewaddress "$PAYOUT_LABEL")"
106+
log "mining 101 maturity blocks to $ADDR_A"
107+
cli_a generatetoaddress 101 "$ADDR_A" >/dev/null
108+
BASE_B="$(tip_b)"
109+
110+
# regtest magic + genesis are read from the running daemon's chainparams so the
111+
# embedded backend's getheaders bootstrap matches node A exactly (no hand-typed
112+
# consensus literal). DGB regtest magic is fixed in DigiByte Core chainparams.
113+
DGB_REGTEST_GENESIS="$(cli_a getblockhash 0)"
114+
: "${DGB_REGTEST_MAGIC:?set DGB_REGTEST_MAGIC to the DigiByte regtest pchMessageStart hex (from chainparams)}"
115+
log "substrate OK: node A height $(cli_a getblockcount), peer B $BASE_B, genesis $DGB_REGTEST_GENESIS"
116+
117+
# --- ARM A (P2P PRIMARY): live UpdateTip won block relayed to node B ----------
118+
log "ARM A: launching embedded c2pool-dgb against node A (regtest diff -> natural won share)"
119+
"$C2POOL_DGB" --run \
120+
--coin-daemon 127.0.0.1:$P2PPORT_A \
121+
--coin-rpc 127.0.0.1:$RPCPORT_A \
122+
--coin-rpc-auth "$DATADIR_A/digibyte.conf" \
123+
--coin-magic "$DGB_REGTEST_MAGIC" \
124+
--coin-genesis "$DGB_REGTEST_GENESIS" >/tmp/c2pool-dgb-soak.log 2>&1 &
125+
C2POOL_PID=$!
126+
log "c2pool-dgb PID $C2POOL_PID; waiting for a won share to relay a coinbase-only block to node B"
127+
n=0
128+
until [ "$(tip_b)" -gt "$BASE_B" ]; do
129+
n=$((n+1)); [ $n -gt 240 ] && die "ARM A: no won block reached node B within 120s (P2P relay arm)"
130+
sleep 0.5
131+
done
132+
WON_HASH="$(cli_b getblockhash "$(tip_b)")"
133+
NTX="$(cli_b getblock "$WON_HASH" | grep -o '"tx"[^]]*]' | grep -o '"[0-9a-f]\{64\}"' | wc -l)"
134+
[ "$NTX" -eq 1 ] || die "ARM A: won block $WON_HASH has $NTX txs, expected 1 (coinbase-only, PROP 1)"
135+
log "ARM A OK: P2P-relayed coinbase-only won block $WON_HASH accepted by peer node B (PROP 1 + ARM A)"
136+
kill "$C2POOL_PID" >/dev/null 2>&1; C2POOL_PID=""
137+
138+
# --- ARM B (RPC FALLBACK): GATED on integrator isolation-mechanism decision ----
139+
# No --no-p2p-relay flag exists; isolating the submitblock fallback from the P2P arm
140+
# is an open design call (see header item 2). Until integrator picks the mechanism,
141+
# ARM B is NOT asserted -- shipping a placeholder assert would be the silent-gap the
142+
# gate exists to prevent.
143+
log "ARM B: GATED -- submitblock-fallback isolation mechanism pending integrator decision (header item 2)"
144+
145+
log "ARM A proven; ARM B + digibyted provisioning are the two remaining live-leg items for the #82 gate."

src/c2pool/main_dgb.cpp

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ void print_banner(const char* argv0, const core::CoinParams& p)
8686
std::cout
8787
<< "c2pool-dgb " << C2POOL_VERSION << " — DigiByte Scrypt-only (V36)\n\n"
8888
<< "Usage: " << argv0
89-
<< " [--version] [--help] [--selftest] [--run] [--stratum [H:]P]\n\n"
89+
<< " [--version] [--help] [--selftest] [--run] [--no-p2p-relay] [--stratum [H:]P]\n\n"
9090
<< "Status: pool/sharechain pillars live (Phase B); run-loop up\n"
9191
<< " (--run: io_context + sharechain peer + Stratum standup).\n"
9292
<< " --stratum [HOST:]PORT binds a miner-facing TCP listener\n"
@@ -142,7 +142,8 @@ int run_node(const core::CoinParams& params, bool testnet,
142142
const std::vector<std::byte>& coin_magic,
143143
const uint256& coin_genesis,
144144
const std::string& rpc_endpoint,
145-
const std::string& rpc_conf_path)
145+
const std::string& rpc_conf_path,
146+
bool no_p2p_relay)
146147
{
147148
io::io_context ioc;
148149

@@ -381,12 +382,24 @@ int run_node(const core::CoinParams& params, bool testnet,
381382

382383
p2p_node.tracker().m_on_block_found = dgb::coin::make_on_block_found(
383384
/*reconstruct=*/std::move(faithful_reconstruct),
384-
/*p2p_relay=*/[&ioc, &coin_p2p](const std::vector<unsigned char>& block_bytes) {
385+
/*p2p_relay=*/[&ioc, &coin_p2p, no_p2p_relay](const std::vector<unsigned char>& block_bytes) {
385386
// #82 PRIMARY arm: relay the won block over the embedded coin-daemon
386387
// P2P producer. The sink fires from the compute thread, so post the
387388
// peer write onto the io thread (NodeP2P is single-thread-confined).
388389
// No-op when --coin-daemon is absent (coin_p2p null) — the RPC
389390
// fallback below still fires (dual-path rule).
391+
//
392+
// --no-p2p-relay (#82 ARM B isolation): suppress ONLY this embedded
393+
// P2P-relay arm and leave the external-digibyted submitblock seam
394+
// (fallback) live, so a regtest soak can prove the RPC fallback lands
395+
// the block ON ITS OWN — not masked by a silent or dead P2P relay
396+
// (the historical #82 gap). A real operator toggle, not a test seam.
397+
if (no_p2p_relay) {
398+
std::cout << "[DGB-BLOCK] --no-p2p-relay: embedded P2P-relay arm "
399+
"SUPPRESSED; submitblock-RPC fallback remains live"
400+
<< std::endl;
401+
return;
402+
}
390403
if (!coin_p2p) return;
391404
io::post(ioc, [&coin_p2p, bytes = block_bytes]() {
392405
if (coin_p2p) coin_p2p->submit_block_p2p_raw(bytes);
@@ -587,6 +600,7 @@ int main(int argc, char** argv)
587600
uint256 coin_genesis; // --coin-genesis HASH (initial getheaders locator base)
588601
std::string rpc_endpoint; // --coin-rpc HOST:PORT (external digibyted submit arm)
589602
std::string rpc_conf_path; // --coin-rpc-auth PATH to digibyte.conf (creds source)
603+
bool no_p2p_relay = false; // --no-p2p-relay: suppress embedded P2P-relay arm (#82 ARM B isolation)
590604
for (int i = 1; i < argc; ++i) {
591605
if (std::strcmp(argv[i], "--version") == 0) {
592606
std::cout << "c2pool-dgb " << C2POOL_VERSION << "\n";
@@ -621,6 +635,7 @@ int main(int argc, char** argv)
621635
if (std::strcmp(argv[i], "--coin-rpc-auth") == 0 && i + 1 < argc) {
622636
rpc_conf_path = argv[++i]; // path to digibyte.conf (rpcpassword stays in-file)
623637
}
638+
if (std::strcmp(argv[i], "--no-p2p-relay") == 0) no_p2p_relay = true;
624639
}
625640

626641
const core::CoinParams params = dgb::make_coin_params(/*testnet=*/false);
@@ -633,7 +648,7 @@ int main(int argc, char** argv)
633648
if (want_run)
634649
return run_node(params, /*testnet=*/false, stratum_addr, stratum_port,
635650
coin_daemon, coin_magic, coin_genesis,
636-
rpc_endpoint, rpc_conf_path);
651+
rpc_endpoint, rpc_conf_path, no_p2p_relay);
637652

638653
// --selftest, or a bare invocation: drive the live score path so the
639654
// binary exercises real consensus code, then exit cleanly.

0 commit comments

Comments
 (0)