diff --git a/scripts/dgb_regtest_won_block_soak.sh b/scripts/dgb_regtest_won_block_soak.sh new file mode 100755 index 000000000..bdcef3dc0 --- /dev/null +++ b/scripts/dgb_regtest_won_block_soak.sh @@ -0,0 +1,145 @@ +#!/usr/bin/env bash +# dgb_regtest_won_block_soak.sh -- DGB #82 won-block dual-path soak (regtest substrate). +# +# Closes the DGB broadcaster dual-path gate (#82): a real won block must reach the +# network down BOTH arms before #281 (the faithful reconstruct-closure install at +# main_dgb.cpp:317) can merge. Per integrator (2026-06-21, UID1708) two properties +# must be PROVEN on the wire, not merely "not-errored": +# +# PROP 1 (COINBASE-ONLY IS CORRECT, NOT FAIL-OPEN) -- template_other_txs_fn is +# empty today ("correct-and-empty"). The soak must reconstruct + get a +# COINBASE-ONLY block ACCEPTED by a peer, proving empty is valid rather +# than a silent malformed-block drop (the original #82 root cause). +# PROP 2 (BOTH ARMS REACH THE NETWORK) -- +# ARM A (P2P PRIMARY) : on_block_found -> make_reconstruct_closure_from_template +# (#280) -> submit_block_p2p_raw relays to a real peer +# (main_dgb.cpp:317/333). Assert peer node B ingests it. +# ARM B (RPC FALLBACK): the independent external submitblock RPC fallback (the +# &coin_node arm at main_dgb.cpp:317) lands the SAME block. +# +# WON-SHARE TRIGGER: UpdateTip-driven, no test-only seam. Unlike NMC (no --regtest +# CLI -> forced-won-share KAT seam, PR #276), c2pool-dgb already targets an external +# regtest daemon directly (main_dgb.cpp:393 "dev regtest daemon"; :433 fresh-regtest +# locator), and at regtest difficulty the embedded work loop finds a winning share +# naturally -- a genuine live UpdateTip won-block, not a synthetic inject. +# +# CLI is real (main_dgb.cpp:534-560): --run --coin-daemon H:P --coin-rpc H:P +# --coin-rpc-auth --coin-magic --coin-genesis . +# +# TWO OPEN LIVE-LEG ITEMS (surfaced to integrator, do NOT read as covered): +# (1) digibyted regtest binary self-provisioned -- not yet on the fleet (only +# namecoind exists). Until provisioned this is a substrate validator + harness, +# not a green gate. +# (2) ARM B isolation MECHANISM. There is NO --no-p2p-relay flag; both arms land on +# node A. Cleanly proving the submitblock fallback INDEPENDENTLY (P2P arm +# suppressed) is a design decision for integrator -- mirrors NMC's +# forced-seam-vs-CLI call. Current draft asserts ARM B against node A only as a +# placeholder and is marked GATED below until that mechanism is chosen. +# +# PER-COIN ISOLATION: DGB only. Localhost-only. Self-service creds (no secret on any +# coordination card). Ports sit outside the bitcoin-family 1844x regtest band. +set -euo pipefail + +CLI_BIN="${DGB_CLI:-digibyte-cli}" +DAEMON_BIN="${DGB_DAEMON:-digibyted}" +C2POOL_DGB="${C2POOL_DGB:-c2pool-dgb}" + +DATADIR_A="${DGB_REGTEST_DATADIR:-$HOME/.c2pool-dgb-regtest}" +DATADIR_B="${DATADIR_A}-peer" +RPCPORT_A=${DGB_RPCPORT_A:-18843} +RPCPORT_B=${DGB_RPCPORT_B:-18853} +P2PPORT_A=${DGB_P2PPORT_A:-18844} +P2PPORT_B=${DGB_P2PPORT_B:-18854} +PAYOUT_LABEL="c2pool-soak" + +log() { echo "[dgb-won-soak $(printf "%(%H:%M:%S)T")] $*" >&2; } +die() { echo "[dgb-won-soak FAIL] $*" >&2; exit 1; } +need() { command -v "$1" >/dev/null 2>&1 || die "missing binary: $1 (set DGB_DAEMON/DGB_CLI or self-provision DigiByte Core)"; } + +cli_a() { "$CLI_BIN" -regtest -datadir="$DATADIR_A" -rpcport=$RPCPORT_A "$@"; } +cli_b() { "$CLI_BIN" -regtest -datadir="$DATADIR_B" -rpcport=$RPCPORT_B "$@"; } + +C2POOL_PID="" +cleanup() { + [ -n "$C2POOL_PID" ] && kill "$C2POOL_PID" >/dev/null 2>&1 || true + cli_a stop >/dev/null 2>&1 || true + cli_b stop >/dev/null 2>&1 || true +} +trap cleanup EXIT + +need "$DAEMON_BIN"; need "$CLI_BIN"; need "$C2POOL_DGB"; need openssl + +provision() { + local dd="$1" rpcport="$2" + mkdir -p "$dd"; chmod 700 "$dd" + if [ ! -f "$dd/digibyte.conf" ]; then + local pass; pass="$(openssl rand -hex 24)" + cat > "$dd/digibyte.conf" </dev/null 2>&1; do n=$((n+1)); [ $n -gt 60 ] && die "rpc never came up: $*"; sleep 0.5; done; } +tip_b() { cli_b getblockcount; } + +# --- substrate: two peered regtest nodes -------------------------------------- +log "starting node A (regtest RPC $RPCPORT_A / P2P $P2PPORT_A)" +"$DAEMON_BIN" -regtest -datadir="$DATADIR_A" -rpcport=$RPCPORT_A -port=$P2PPORT_A -daemon >/dev/null +wait_rpc cli_a +log "starting node B (regtest RPC $RPCPORT_B / P2P $P2PPORT_B), peering to A" +"$DAEMON_BIN" -regtest -datadir="$DATADIR_B" -rpcport=$RPCPORT_B -port=$P2PPORT_B \ + -connect=127.0.0.1:$P2PPORT_A -daemon >/dev/null +wait_rpc cli_b + +cli_a createwallet "$PAYOUT_LABEL" >/dev/null 2>&1 || cli_a loadwallet "$PAYOUT_LABEL" >/dev/null 2>&1 || true +ADDR_A="$(cli_a getnewaddress "$PAYOUT_LABEL")" +log "mining 101 maturity blocks to $ADDR_A" +cli_a generatetoaddress 101 "$ADDR_A" >/dev/null +BASE_B="$(tip_b)" + +# regtest magic + genesis are read from the running daemon's chainparams so the +# embedded backend's getheaders bootstrap matches node A exactly (no hand-typed +# consensus literal). DGB regtest magic is fixed in DigiByte Core chainparams. +DGB_REGTEST_GENESIS="$(cli_a getblockhash 0)" +: "${DGB_REGTEST_MAGIC:?set DGB_REGTEST_MAGIC to the DigiByte regtest pchMessageStart hex (from chainparams)}" +log "substrate OK: node A height $(cli_a getblockcount), peer B $BASE_B, genesis $DGB_REGTEST_GENESIS" + +# --- ARM A (P2P PRIMARY): live UpdateTip won block relayed to node B ---------- +log "ARM A: launching embedded c2pool-dgb against node A (regtest diff -> natural won share)" +"$C2POOL_DGB" --run \ + --coin-daemon 127.0.0.1:$P2PPORT_A \ + --coin-rpc 127.0.0.1:$RPCPORT_A \ + --coin-rpc-auth "$DATADIR_A/digibyte.conf" \ + --coin-magic "$DGB_REGTEST_MAGIC" \ + --coin-genesis "$DGB_REGTEST_GENESIS" >/tmp/c2pool-dgb-soak.log 2>&1 & +C2POOL_PID=$! +log "c2pool-dgb PID $C2POOL_PID; waiting for a won share to relay a coinbase-only block to node B" +n=0 +until [ "$(tip_b)" -gt "$BASE_B" ]; do + n=$((n+1)); [ $n -gt 240 ] && die "ARM A: no won block reached node B within 120s (P2P relay arm)" + sleep 0.5 +done +WON_HASH="$(cli_b getblockhash "$(tip_b)")" +NTX="$(cli_b getblock "$WON_HASH" | grep -o '"tx"[^]]*]' | grep -o '"[0-9a-f]\{64\}"' | wc -l)" +[ "$NTX" -eq 1 ] || die "ARM A: won block $WON_HASH has $NTX txs, expected 1 (coinbase-only, PROP 1)" +log "ARM A OK: P2P-relayed coinbase-only won block $WON_HASH accepted by peer node B (PROP 1 + ARM A)" +kill "$C2POOL_PID" >/dev/null 2>&1; C2POOL_PID="" + +# --- ARM B (RPC FALLBACK): GATED on integrator isolation-mechanism decision ---- +# No --no-p2p-relay flag exists; isolating the submitblock fallback from the P2P arm +# is an open design call (see header item 2). Until integrator picks the mechanism, +# ARM B is NOT asserted -- shipping a placeholder assert would be the silent-gap the +# gate exists to prevent. +log "ARM B: GATED -- submitblock-fallback isolation mechanism pending integrator decision (header item 2)" + +log "ARM A proven; ARM B + digibyted provisioning are the two remaining live-leg items for the #82 gate." diff --git a/src/c2pool/main_dgb.cpp b/src/c2pool/main_dgb.cpp index 6d88ee747..2dadf6858 100644 --- a/src/c2pool/main_dgb.cpp +++ b/src/c2pool/main_dgb.cpp @@ -86,7 +86,7 @@ void print_banner(const char* argv0, const core::CoinParams& p) std::cout << "c2pool-dgb " << C2POOL_VERSION << " — DigiByte Scrypt-only (V36)\n\n" << "Usage: " << argv0 - << " [--version] [--help] [--selftest] [--run] [--stratum [H:]P]\n\n" + << " [--version] [--help] [--selftest] [--run] [--no-p2p-relay] [--stratum [H:]P]\n\n" << "Status: pool/sharechain pillars live (Phase B); run-loop up\n" << " (--run: io_context + sharechain peer + Stratum standup).\n" << " --stratum [HOST:]PORT binds a miner-facing TCP listener\n" @@ -142,7 +142,8 @@ int run_node(const core::CoinParams& params, bool testnet, const std::vector& coin_magic, const uint256& coin_genesis, const std::string& rpc_endpoint, - const std::string& rpc_conf_path) + const std::string& rpc_conf_path, + bool no_p2p_relay) { io::io_context ioc; @@ -381,12 +382,24 @@ int run_node(const core::CoinParams& params, bool testnet, p2p_node.tracker().m_on_block_found = dgb::coin::make_on_block_found( /*reconstruct=*/std::move(faithful_reconstruct), - /*p2p_relay=*/[&ioc, &coin_p2p](const std::vector& block_bytes) { + /*p2p_relay=*/[&ioc, &coin_p2p, no_p2p_relay](const std::vector& block_bytes) { // #82 PRIMARY arm: relay the won block over the embedded coin-daemon // P2P producer. The sink fires from the compute thread, so post the // peer write onto the io thread (NodeP2P is single-thread-confined). // No-op when --coin-daemon is absent (coin_p2p null) — the RPC // fallback below still fires (dual-path rule). + // + // --no-p2p-relay (#82 ARM B isolation): suppress ONLY this embedded + // P2P-relay arm and leave the external-digibyted submitblock seam + // (fallback) live, so a regtest soak can prove the RPC fallback lands + // the block ON ITS OWN — not masked by a silent or dead P2P relay + // (the historical #82 gap). A real operator toggle, not a test seam. + if (no_p2p_relay) { + std::cout << "[DGB-BLOCK] --no-p2p-relay: embedded P2P-relay arm " + "SUPPRESSED; submitblock-RPC fallback remains live" + << std::endl; + return; + } if (!coin_p2p) return; io::post(ioc, [&coin_p2p, bytes = block_bytes]() { if (coin_p2p) coin_p2p->submit_block_p2p_raw(bytes); @@ -587,6 +600,7 @@ int main(int argc, char** argv) uint256 coin_genesis; // --coin-genesis HASH (initial getheaders locator base) std::string rpc_endpoint; // --coin-rpc HOST:PORT (external digibyted submit arm) std::string rpc_conf_path; // --coin-rpc-auth PATH to digibyte.conf (creds source) + bool no_p2p_relay = false; // --no-p2p-relay: suppress embedded P2P-relay arm (#82 ARM B isolation) for (int i = 1; i < argc; ++i) { if (std::strcmp(argv[i], "--version") == 0) { std::cout << "c2pool-dgb " << C2POOL_VERSION << "\n"; @@ -621,6 +635,7 @@ int main(int argc, char** argv) if (std::strcmp(argv[i], "--coin-rpc-auth") == 0 && i + 1 < argc) { rpc_conf_path = argv[++i]; // path to digibyte.conf (rpcpassword stays in-file) } + if (std::strcmp(argv[i], "--no-p2p-relay") == 0) no_p2p_relay = true; } const core::CoinParams params = dgb::make_coin_params(/*testnet=*/false); @@ -633,7 +648,7 @@ int main(int argc, char** argv) if (want_run) return run_node(params, /*testnet=*/false, stratum_addr, stratum_port, coin_daemon, coin_magic, coin_genesis, - rpc_endpoint, rpc_conf_path); + rpc_endpoint, rpc_conf_path, no_p2p_relay); // --selftest, or a bare invocation: drive the live score path so the // binary exercises real consensus code, then exit cleanly.