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.
202 lines
6.9 KiB
Bash
Executable File
202 lines
6.9 KiB
Bash
Executable File
#!/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)"
|