diff --git a/test/regtest_smoke.sh b/test/regtest_smoke.sh new file mode 100755 index 0000000..dd01c13 --- /dev/null +++ b/test/regtest_smoke.sh @@ -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)"