Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
145 changes: 145 additions & 0 deletions scripts/dgb_regtest_won_block_soak.sh
Original file line number Diff line number Diff line change
@@ -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 <digibyte.conf> --coin-magic <hex> --coin-genesis <hash>.
#
# 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" <<CONF
regtest=1
server=1
rpcuser=c2pool
rpcpassword=$pass
fallbackfee=0.0002
[regtest]
rpcport=$rpcport
CONF
chmod 600 "$dd/digibyte.conf"
fi
}
provision "$DATADIR_A" "$RPCPORT_A"
provision "$DATADIR_B" "$RPCPORT_B"

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; }
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."
23 changes: 19 additions & 4 deletions src/c2pool/main_dgb.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -142,7 +142,8 @@ int run_node(const core::CoinParams& params, bool testnet,
const std::vector<std::byte>& 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;

Expand Down Expand Up @@ -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<unsigned char>& block_bytes) {
/*p2p_relay=*/[&ioc, &coin_p2p, no_p2p_relay](const std::vector<unsigned char>& 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);
Expand Down Expand Up @@ -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";
Expand Down Expand Up @@ -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);
Expand All @@ -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.
Expand Down
Loading