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