Skip to content

Commit b056bea

Browse files
committed
dgb(#82): tx-bearing funded dual-path won-block soak harness
Fail-closed regtest soak proving a won block carrying a non-coinbase funded legacy tx is reconstructed (#300/#302/#303) and ACCEPTED by a peer down BOTH arms (P2P primary + submitblock isolated). Asserts NTX>=2 and funded-txid presence per arm via getrawmempool/getblock; die() aborts on any miss (no getrawtransaction-without-txindex false-green path).
1 parent b91face commit b056bea

1 file changed

Lines changed: 185 additions & 0 deletions

File tree

Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
#!/usr/bin/env bash
2+
# dgb_regtest_won_block_soak_funded.sh -- DGB #82 TX-BEARING won-block dual-path soak.
3+
#
4+
# Sibling of dgb_regtest_won_block_soak.sh. That script proves PROP-1 (coinbase-only
5+
# is ACCEPTED, not a silent malformed-block drop). This script proves the stronger,
6+
# #82-closeable bar from integrator (UID1801/UID1817): a won block carrying a
7+
# NON-coinbase tx must be reconstructed (#300 per-job template-capture + #302
8+
# merkle-link + #303 captured-tx wiring) and ACCEPTED by a peer down BOTH arms.
9+
#
10+
# ASSERT: won block on node B has NTX >= 2 (coinbase + >=1 funded tx), both arms.
11+
#
12+
# BIP141 ISOLATION (legacy txs first, per integrator): the maturity coinbases AND
13+
# the funded spend both use LEGACY (P2PKH) addresses, so the funded tx carries NO
14+
# witness data. This isolates plain tx-inclusion + merkle-path correctness from the
15+
# segwit witness-commitment path; a segwit-tx variant is a follow-up only after this
16+
# legacy gate is green.
17+
#
18+
# PER-COIN ISOLATION: DGB only. Localhost-only. Self-service creds. Ports outside the
19+
# bitcoin-family 1844x regtest band.
20+
set -euo pipefail
21+
22+
CLI_BIN="${DGB_CLI:-digibyte-cli}"
23+
DAEMON_BIN="${DGB_DAEMON:-digibyted}"
24+
C2POOL_DGB="${C2POOL_DGB:-c2pool-dgb}"
25+
26+
DATADIR_A="${DGB_REGTEST_DATADIR:-$HOME/.c2pool-dgb-regtest-funded}"
27+
DATADIR_B="${DATADIR_A}-peer"
28+
RPCPORT_A=${DGB_RPCPORT_A:-18863}
29+
RPCPORT_B=${DGB_RPCPORT_B:-18873}
30+
P2PPORT_A=${DGB_P2PPORT_A:-18864}
31+
P2PPORT_B=${DGB_P2PPORT_B:-18874}
32+
PAYOUT_LABEL="c2pool-soak-funded"
33+
34+
log() { echo "[dgb-funded-soak $(printf "%(%H:%M:%S)T")] $*" >&2; }
35+
die() { echo "[dgb-funded-soak FAIL] $*" >&2; exit 1; }
36+
need() { command -v "$1" >/dev/null 2>&1 || die "missing binary: $1"; }
37+
38+
cli_a() { "$CLI_BIN" -regtest -datadir="$DATADIR_A" -rpcport=$RPCPORT_A "$@"; }
39+
cli_b() { "$CLI_BIN" -regtest -datadir="$DATADIR_B" -rpcport=$RPCPORT_B "$@"; }
40+
41+
C2POOL_PID=""
42+
cleanup() {
43+
[ -n "$C2POOL_PID" ] && kill "$C2POOL_PID" >/dev/null 2>&1 || true
44+
cli_a stop >/dev/null 2>&1 || true
45+
cli_b stop >/dev/null 2>&1 || true
46+
}
47+
trap cleanup EXIT
48+
49+
need "$DAEMON_BIN"; need "$CLI_BIN"; need "$C2POOL_DGB"; need openssl
50+
51+
provision() {
52+
local dd="$1" rpcport="$2"
53+
# fresh datadir each run -> predictable heights, no mempool/chain carryover
54+
rm -rf "$dd"
55+
mkdir -p "$dd"; chmod 700 "$dd"
56+
if [ ! -f "$dd/digibyte.conf" ]; then
57+
local pass; pass="$(openssl rand -hex 24)"
58+
cat > "$dd/digibyte.conf" <<CONF
59+
regtest=1
60+
server=1
61+
rpcuser=c2pool
62+
rpcpassword=$pass
63+
fallbackfee=0.01
64+
dandelion=0
65+
[regtest]
66+
rpcport=$rpcport
67+
CONF
68+
chmod 600 "$dd/digibyte.conf"
69+
fi
70+
}
71+
provision "$DATADIR_A" "$RPCPORT_A"
72+
provision "$DATADIR_B" "$RPCPORT_B"
73+
74+
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; }
75+
tip_b() { cli_b getblockcount; }
76+
mempool_a() { cli_a getmempoolinfo | grep -o '"size"[^,]*' | grep -o '[0-9]\+'; }
77+
78+
ntx_of() { # ntx_of <cli_fn> <blockhash> -> number of txs in block
79+
local fn="$1" h="$2"
80+
"$fn" getblock "$h" | tr -d '\n' | grep -o '"tx"[^]]*]' | grep -o '"[0-9a-f]\{64\}"' | wc -l
81+
}
82+
83+
kill_stale_c2pool() {
84+
local pids; pids="$(pgrep -f 'c2pool-dgb .*--run' 2>/dev/null || true)"
85+
[ -z "$pids" ] && return 0
86+
log "pre-flight: freeing pool P2P port from stale c2pool-dgb: $pids"
87+
kill $pids 2>/dev/null || true; sleep 2
88+
pids="$(pgrep -f 'c2pool-dgb .*--run' 2>/dev/null || true)"
89+
[ -n "$pids" ] && { kill -9 $pids 2>/dev/null || true; sleep 1; }
90+
return 0
91+
}
92+
kill_stale_c2pool
93+
94+
# --- substrate: two peered regtest nodes --------------------------------------
95+
log "starting node A (regtest RPC $RPCPORT_A / P2P $P2PPORT_A)"
96+
"$DAEMON_BIN" -regtest -dandelion=0 -datadir="$DATADIR_A" -rpcport=$RPCPORT_A -port=$P2PPORT_A -daemon >/dev/null
97+
wait_rpc cli_a
98+
log "starting node B (regtest RPC $RPCPORT_B / P2P $P2PPORT_B), peering to A"
99+
"$DAEMON_BIN" -regtest -dandelion=0 -datadir="$DATADIR_B" -rpcport=$RPCPORT_B -port=$P2PPORT_B \
100+
-connect=127.0.0.1:$P2PPORT_A -daemon >/dev/null
101+
wait_rpc cli_b
102+
103+
cli_a createwallet "$PAYOUT_LABEL" >/dev/null 2>&1 || cli_a loadwallet "$PAYOUT_LABEL" >/dev/null 2>&1 || true
104+
# LEGACY (P2PKH) coinbase + spend -> funded tx carries no witness (BIP141 isolated).
105+
ADDR_A="$(cli_a getnewaddress "$PAYOUT_LABEL" legacy)"
106+
# Mine 110 so blocks 1..10 coinbases are mature (spendable) -> enough legacy UTXOs
107+
# to fund a distinct tx for each arm without a 100-block re-wait between arms.
108+
log "mining 110 maturity blocks to legacy addr $ADDR_A"
109+
cli_a generatetoaddress 110 "$ADDR_A" >/dev/null
110+
HEIGHT_A="$(cli_a getblockcount)"
111+
n=0; until [ "$(tip_b)" -ge "$HEIGHT_A" ]; do
112+
n=$((n+1)); [ $n -gt 120 ] && die "node B never synced to A height $HEIGHT_A (got $(tip_b))"; sleep 0.5
113+
done
114+
115+
DGB_REGTEST_GENESIS="$(cli_a getblockhash 0)"
116+
: "${DGB_REGTEST_MAGIC:=fabfb5da}"
117+
118+
# fund_mempool_legacy -- broadcast ONE legacy (non-witness) tx into node A mempool
119+
# and confirm it landed (via getrawmempool, unambiguous), so the c2pool
120+
# getblocktemplate capture includes it. Sets global FUNDED_TXID. NOTE: must run in
121+
# the PARENT shell (NOT a $() subshell) so a die() actually aborts the soak.
122+
FUNDED_TXID=""
123+
fund_mempool_legacy() {
124+
local dest amt
125+
dest="$(cli_a getnewaddress funded legacy)"
126+
amt="1.0"
127+
local sendout; sendout="$(cli_a sendtoaddress "$dest" "$amt" 2>&1)" || true
128+
FUNDED_TXID="$sendout"
129+
log "DEBUG send dest=$dest -> [$sendout]"
130+
# NB: no `cli | grep -q` -- grep -q closes the pipe early, SIGPIPEs cli, and under
131+
# `set -o pipefail` the pipeline reports failure even on a match. Capture + match.
132+
local n=0 mp; until mp="$(cli_a getrawmempool 2>&1)"; [[ "$mp" == *"$FUNDED_TXID"* ]]; do
133+
n=$((n+1)); [ $n -gt 40 ] && { log "DEBUG rawmempool=[$mp] mempoolinfo=[$(cli_a getmempoolinfo 2>&1 | tr '\n' ' ')] balance=[$(cli_a getbalance 2>&1)]"; die "funded tx $FUNDED_TXID never appeared in node A mempool"; }; sleep 0.25
134+
done
135+
# legacy/no-witness assertion via raw-tx serialization: a segwit tx carries
136+
# the BIP144 marker+flag (0001) immediately after the 4-byte version; a legacy
137+
# tx has its input-count varint there instead. (Core removed getmempoolentry's
138+
# "size" field, so the old size==vsize discriminator is no longer queryable.)
139+
local rawhex marker
140+
rawhex="$(cli_a getrawtransaction "$FUNDED_TXID")"
141+
marker="${rawhex:8:4}"
142+
[ "$marker" != "0001" ] || die "funded tx $FUNDED_TXID carries a witness (marker=$marker) -- BIP141 isolation broken"
143+
log "funded legacy tx $FUNDED_TXID in node A mempool (no-witness marker ok); mempool size=$(mempool_a)"
144+
}
145+
146+
# run_arm -- runs in the PARENT shell (no $() capture) so die() aborts. Sets global
147+
# ARM_WON to the accepted block hash.
148+
ARM_WON=""
149+
run_arm() { # run_arm <label> <extra c2pool flags> <logfile>
150+
local label="$1" extra="$2" logf="$3" base won ntx
151+
base="$(tip_b)"
152+
fund_mempool_legacy
153+
log "ARM $label: arming c2pool-dgb (funded mempool has the legacy tx) $extra"
154+
"$C2POOL_DGB" --run \
155+
--coin-daemon 127.0.0.1:$P2PPORT_A \
156+
--coin-rpc 127.0.0.1:$RPCPORT_A \
157+
--coin-rpc-auth "$DATADIR_A/digibyte.conf" \
158+
--coin-magic "$DGB_REGTEST_MAGIC" \
159+
--coin-genesis "$DGB_REGTEST_GENESIS" --regtest --regtest-force-won-share \
160+
--soak-regrind $extra >"$logf" 2>&1 &
161+
C2POOL_PID=$!
162+
log "ARM $label: c2pool-dgb PID $C2POOL_PID; waiting for tx-bearing won block on node B"
163+
local n=0
164+
until [ "$(tip_b)" -gt "$base" ]; do
165+
n=$((n+1)); [ $n -gt 240 ] && die "ARM $label: no won block reached node B within 120s"
166+
sleep 0.5
167+
done
168+
won="$(cli_b getblockhash "$(tip_b)")"
169+
ntx="$(ntx_of cli_b "$won")"
170+
[ "$ntx" -ge 2 ] || die "ARM $label: won block $won has $ntx txs, expected >=2 (coinbase + funded legacy tx) -- captured-template tx-wire (#300/#302/#303) did not carry the mempool tx into the reconstructed block"
171+
local blk; blk="$(cli_b getblock "$won")"
172+
[[ "$blk" == *"$FUNDED_TXID"* ]] \
173+
|| die "ARM $label: funded tx $FUNDED_TXID not present in accepted block $won"
174+
log "ARM $label OK: tx-bearing won block $won ($ntx txs incl funded $FUNDED_TXID) ACCEPTED by peer node B"
175+
kill "$C2POOL_PID" >/dev/null 2>&1; C2POOL_PID=""
176+
ARM_WON="$won"
177+
}
178+
179+
run_arm "A (P2P primary)" "" /tmp/c2pool-dgb-funded-soak-arma.log
180+
WON_A="$ARM_WON"
181+
run_arm "B (submitblock isolated)" "--no-p2p-relay" /tmp/c2pool-dgb-funded-soak-armb.log
182+
WON_B="$ARM_WON"
183+
184+
log "BOTH ARMS PROVEN (TX-BEARING): ARM A (P2P relay) block $WON_A + ARM B (submitblock isolated) block $WON_B each carry a funded legacy tx and were ACCEPTED by peer node B. #82 tx-bearing dual-path gate satisfied."
185+
echo "FUNDED_SOAK_RESULT armA=$WON_A armB=$WON_B"

0 commit comments

Comments
 (0)