#!/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)"