Add regtest smoke test for the block-detection critical path
Validates the end-to-end path log tailer → aggregator → bitcoind
enrichment → /api/blocks without a real miner:
1. Starts a fresh bitcoind in regtest mode (temp datadir, port 18443)
2. Mines 102 blocks so the coinbase is mature and the target block
is live in bitcoind's chain
3. Starts kamado-api wired to regtest bitcoind (ckpool socket dir
points at an empty dir — connect failures are expected and
non-fatal)
4. Appends "Solved and confirmed block <height>" to the ckpool log
file; the log tailer picks it up and calls getblockhash to enrich
5. Polls /api/blocks until the block appears (up to 30 s)
6. Verifies the hash is a 64-char hex string and matches what bitcoind
returns for getblockhash at that height
The binary is auto-built from source if go is in PATH; set KAMADO_BIN
to skip the build step. All processes and temp files are cleaned up on
exit via trap.
This commit is contained in:
Executable
+201
@@ -0,0 +1,201 @@
|
||||
#!/usr/bin/env bash
|
||||
# Regtest smoke test for kamado-api.
|
||||
#
|
||||
# Validates the full critical path:
|
||||
# ckpool log line → log tailer → aggregator → bitcoind RPC enrichment → /api/blocks
|
||||
#
|
||||
# Prerequisites: bitcoind, bitcoin-cli, jq, curl
|
||||
# The kamado-api binary is auto-built from source if go is in PATH;
|
||||
# alternatively set KAMADO_BIN to point at a pre-built binary.
|
||||
#
|
||||
# Usage:
|
||||
# test/regtest_smoke.sh
|
||||
# KAMADO_BIN=./api/bin/kamado-api test/regtest_smoke.sh
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
|
||||
API_DIR="$SCRIPT_DIR/../api"
|
||||
|
||||
# ---- helpers ----------------------------------------------------------------
|
||||
|
||||
log() { printf '[smoke] %s\n' "$*"; }
|
||||
die() { printf '[smoke] FAIL: %s\n' "$*" >&2; exit 1; }
|
||||
pass() { printf '[smoke] PASS: %s\n' "$*"; exit 0; }
|
||||
|
||||
WORK_DIR=""
|
||||
BITCOIN_PID=""
|
||||
KAMADO_PID=""
|
||||
|
||||
cleanup() {
|
||||
[[ -n "$KAMADO_PID" ]] && kill "$KAMADO_PID" 2>/dev/null || true
|
||||
[[ -n "$BITCOIN_PID" ]] && kill "$BITCOIN_PID" 2>/dev/null || true
|
||||
[[ -n "$WORK_DIR" ]] && rm -rf "$WORK_DIR"
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
# ---- prerequisites ----------------------------------------------------------
|
||||
|
||||
for cmd in bitcoind bitcoin-cli jq curl; do
|
||||
command -v "$cmd" >/dev/null 2>&1 || die "required command not found: $cmd"
|
||||
done
|
||||
|
||||
# Locate or build kamado-api.
|
||||
if [[ -n "${KAMADO_BIN:-}" ]]; then
|
||||
[[ -x "$KAMADO_BIN" ]] || die "KAMADO_BIN=$KAMADO_BIN is not executable"
|
||||
elif command -v go >/dev/null 2>&1; then
|
||||
WORK_DIR=$(mktemp -d)
|
||||
log "building kamado-api…"
|
||||
(cd "$API_DIR" && go build -o "$WORK_DIR/kamado-api" ./cmd/kamado-api) \
|
||||
|| die "go build failed"
|
||||
KAMADO_BIN="$WORK_DIR/kamado-api"
|
||||
else
|
||||
die "KAMADO_BIN not set and go not in PATH; build the binary first:
|
||||
cd api && go build -o bin/kamado-api ./cmd/kamado-api
|
||||
KAMADO_BIN=api/bin/kamado-api test/regtest_smoke.sh"
|
||||
fi
|
||||
|
||||
[[ -n "${WORK_DIR:-}" ]] || WORK_DIR=$(mktemp -d)
|
||||
|
||||
# ---- configuration ----------------------------------------------------------
|
||||
|
||||
BTC_DIR="$WORK_DIR/bitcoin"
|
||||
BTC_RPC_PORT=18443 # regtest default
|
||||
BTC_USER=smoketest
|
||||
BTC_PASS=smokepass
|
||||
|
||||
KAMADO_PORT=19080 # avoid clashing with a running instance
|
||||
CKPOOL_LOG="$WORK_DIR/ckpool.log"
|
||||
KAMADO_DB="$WORK_DIR/kamado.db"
|
||||
SOCK_DIR="$WORK_DIR/sockets" # no real ckpool; connect attempts fail gracefully
|
||||
|
||||
mkdir -p "$BTC_DIR" "$SOCK_DIR"
|
||||
touch "$CKPOOL_LOG"
|
||||
|
||||
# Shorthand for bitcoin-cli pointing at the regtest instance.
|
||||
cli() {
|
||||
bitcoin-cli \
|
||||
-regtest \
|
||||
-datadir="$BTC_DIR" \
|
||||
-rpcport="$BTC_RPC_PORT" \
|
||||
-rpcuser="$BTC_USER" \
|
||||
-rpcpassword="$BTC_PASS" \
|
||||
"$@"
|
||||
}
|
||||
|
||||
# ---- start bitcoind ---------------------------------------------------------
|
||||
|
||||
log "starting bitcoind regtest on port $BTC_RPC_PORT"
|
||||
bitcoind \
|
||||
-regtest \
|
||||
-datadir="$BTC_DIR" \
|
||||
-rpcport="$BTC_RPC_PORT" \
|
||||
-rpcuser="$BTC_USER" \
|
||||
-rpcpassword="$BTC_PASS" \
|
||||
-rpcbind=127.0.0.1 \
|
||||
-rpcallowip=127.0.0.1 \
|
||||
-fallbackfee=0.0001 \
|
||||
-nodaemon \
|
||||
>"$WORK_DIR/bitcoind.log" 2>&1 &
|
||||
BITCOIN_PID=$!
|
||||
|
||||
log "waiting for bitcoind RPC…"
|
||||
ready=false
|
||||
for i in $(seq 1 30); do
|
||||
if cli getblockcount >/dev/null 2>&1; then ready=true; break; fi
|
||||
sleep 1
|
||||
done
|
||||
$ready || die "bitcoind did not become ready within 30s (see $WORK_DIR/bitcoind.log)"
|
||||
log "bitcoind ready (pid $BITCOIN_PID)"
|
||||
|
||||
# Create a descriptor wallet and mine enough blocks for coinbase maturity,
|
||||
# then mine the one block we'll claim to have "solved".
|
||||
log "setting up wallet and generating blocks"
|
||||
cli createwallet smoke >/dev/null 2>&1 || cli loadwallet smoke >/dev/null 2>&1 || true
|
||||
MINE_ADDR=$(cli getnewaddress)
|
||||
|
||||
# 101 maturity blocks so the wallet's coinbase is spendable, then the
|
||||
# target block that we'll inject into the ckpool log.
|
||||
cli generatetoaddress 101 "$MINE_ADDR" >/dev/null
|
||||
BEFORE_HEIGHT=$(cli getblockcount)
|
||||
cli generatetoaddress 1 "$MINE_ADDR" >/dev/null
|
||||
TARGET_HEIGHT=$((BEFORE_HEIGHT + 1))
|
||||
log "target block at height $TARGET_HEIGHT is live in bitcoind"
|
||||
|
||||
# ---- start kamado-api -------------------------------------------------------
|
||||
|
||||
log "starting kamado-api on port $KAMADO_PORT"
|
||||
LISTEN_ADDR=":$KAMADO_PORT" \
|
||||
BITCOIN_RPC_URL="http://127.0.0.1:$BTC_RPC_PORT" \
|
||||
BITCOIN_RPC_USER="$BTC_USER" \
|
||||
BITCOIN_RPC_PASSWORD="$BTC_PASS" \
|
||||
BITCOIN_ZMQ_BLOCK="" \
|
||||
CKPOOL_SOCKDIR="$SOCK_DIR" \
|
||||
CKPOOL_LOGFILE="$CKPOOL_LOG" \
|
||||
DB_PATH="$KAMADO_DB" \
|
||||
POLL_INTERVAL=2s \
|
||||
"$KAMADO_BIN" >"$WORK_DIR/kamado.log" 2>&1 &
|
||||
KAMADO_PID=$!
|
||||
|
||||
log "waiting for kamado-api snapshot endpoint…"
|
||||
ready=false
|
||||
for i in $(seq 1 30); do
|
||||
if curl -sf "http://127.0.0.1:$KAMADO_PORT/api/snapshot" >/dev/null 2>&1; then
|
||||
ready=true; break
|
||||
fi
|
||||
# Bail early if the process died.
|
||||
kill -0 "$KAMADO_PID" 2>/dev/null || die "kamado-api exited unexpectedly (see $WORK_DIR/kamado.log)"
|
||||
sleep 1
|
||||
done
|
||||
$ready || die "kamado-api did not become ready within 30s (see $WORK_DIR/kamado.log)"
|
||||
log "kamado-api ready (pid $KAMADO_PID)"
|
||||
|
||||
# ---- inject the ckpool solve line -------------------------------------------
|
||||
#
|
||||
# The block is already in bitcoind (generated above), so the tailer's
|
||||
# enrichment call (getblockhash → getblock) will succeed immediately.
|
||||
|
||||
log "writing 'Solved and confirmed block $TARGET_HEIGHT' to ckpool log"
|
||||
printf 'Solved and confirmed block %d\n' "$TARGET_HEIGHT" >>"$CKPOOL_LOG"
|
||||
|
||||
# ---- wait for the block to appear in /api/blocks ----------------------------
|
||||
|
||||
log "polling /api/blocks for height $TARGET_HEIGHT (up to 30s)…"
|
||||
found=false
|
||||
for i in $(seq 1 30); do
|
||||
RESP=$(curl -sf "http://127.0.0.1:$KAMADO_PORT/api/blocks" 2>/dev/null || echo "[]")
|
||||
if echo "$RESP" | jq -e "any(.[]; .height == $TARGET_HEIGHT)" >/dev/null 2>&1; then
|
||||
found=true; break
|
||||
fi
|
||||
sleep 1
|
||||
done
|
||||
|
||||
if ! $found; then
|
||||
log "last /api/blocks response: $(curl -sf "http://127.0.0.1:$KAMADO_PORT/api/blocks" || echo "(unreachable)")"
|
||||
log "kamado-api log tail:"
|
||||
tail -20 "$WORK_DIR/kamado.log" | sed 's/^/ /'
|
||||
die "block $TARGET_HEIGHT did not appear in /api/blocks within 30s"
|
||||
fi
|
||||
|
||||
# ---- verify enrichment: block must carry a hash from bitcoind ---------------
|
||||
|
||||
BLOCK_HASH=$(
|
||||
curl -sf "http://127.0.0.1:$KAMADO_PORT/api/blocks" \
|
||||
| jq -r "first(.[] | select(.height == $TARGET_HEIGHT) | .hash // \"\")"
|
||||
)
|
||||
if [[ -z "$BLOCK_HASH" || "$BLOCK_HASH" == "null" ]]; then
|
||||
die "block $TARGET_HEIGHT in /api/blocks but hash enrichment failed (hash=$BLOCK_HASH)"
|
||||
fi
|
||||
|
||||
# Verify the hash looks like a real block hash (64 hex chars).
|
||||
if [[ ! "$BLOCK_HASH" =~ ^[0-9a-f]{64}$ ]]; then
|
||||
die "block hash looks wrong: $BLOCK_HASH"
|
||||
fi
|
||||
|
||||
# Cross-check: the hash must match what bitcoind says is at that height.
|
||||
CANONICAL=$(cli getblockhash "$TARGET_HEIGHT")
|
||||
if [[ "$BLOCK_HASH" != "$CANONICAL" ]]; then
|
||||
die "hash mismatch: kamado has $BLOCK_HASH, bitcoind says $CANONICAL"
|
||||
fi
|
||||
|
||||
pass "block $TARGET_HEIGHT → hash $BLOCK_HASH (matches bitcoind canonical)"
|
||||
Reference in New Issue
Block a user