diff --git a/README.md b/README.md index 0198908..6d429fa 100644 --- a/README.md +++ b/README.md @@ -13,10 +13,13 @@ This project contains Docker configurations and orchestration files for running The base system consists of: - **Bitcoin Node**: A full Bitcoin Core node instrumented for monitoring. -- **ebpf-extractor**: An extractor tool that consumes ebpf kernel events and routes messages to the NATS server. +- **Extractor**: Tool that extracts Bitcoin network events and routes messages to the NATS server. Three types are available: + - **ebpf-extractor** (default): Uses eBPF kernel events to monitor Bitcoin Core internals + - **p2p-extractor**: Acts as a Bitcoin P2P node to observe network messages + - **rpc-extractor**: Periodically queries Bitcoin Core RPC interfaces - **NATS**: Message broker for inter-service communication. -The default network is regtest for test and development, it is provided by [docker-compose.yml](docker-compose.yml) and doesn't require command line arguments. +The default network is regtest for test and development, it is provided by [docker-compose.yml](docker-compose.yml) and doesn't require command line arguments. The default extractor is **ebpf-extractor**. Other networks are provided by [node.mainnet.yml](node.mainnet.yml), [node.signet.yml](node.signet.yml), and [node.regtest.yml](node.regtest.yml). You refer to one of them using the `-f` option, e.g., `docker compose -f node.mainnet.yml up`. @@ -52,7 +55,7 @@ Each tool is configured as a service with the `monitoring` profile and a profile docker compose --profile monitoring build --no-cache ``` -3. **Start the base system in regtest**: +3. **Start the base system in regtest** (with default eBPF extractor): ```bash docker compose up -d ``` @@ -62,7 +65,7 @@ Each tool is configured as a service with the `monitoring` profile and a profile docker compose ps ``` -You should see two containers running since the bitcoin node and the ebpf-extractor tool should run in the same container. +You should see two containers running since the bitcoin node and the extractor tool run in the same container. 5. **Start the logger tool**: ```bash @@ -76,6 +79,86 @@ You should now have three containers. docker compose --profile logger logs -f ``` +## Choosing an Extractor + +You can choose which extractor to use by setting the `EXTRACTOR_TYPE` environment variable: + +### eBPF Extractor (Default) + +Uses eBPF (extended Berkeley Packet Filter) to monitor Bitcoin Core at the kernel level. Provides the most detailed insights but requires privileged container access. + +```bash +# Default - no need to specify +docker compose up -d + +# Or explicitly +EXTRACTOR_TYPE=ebpf docker compose up -d +``` + +**Requirements:** +- Privileged container +- `SYS_ADMIN` and `SYS_PTRACE` capabilities +- Access to `/sys/kernel/debug` + +### P2P Extractor + +Acts as a Bitcoin P2P peer, intercepting and analyzing network messages between Bitcoin nodes. + +```bash +EXTRACTOR_TYPE=p2p docker compose up -d +``` + +**Configuration Options:** +- `P2P_EXTRACTOR_PORT`: Port for P2P connections (default: 8555) +- `P2P_EXTRACTOR_HOST`: Host address to bind (default: 0.0.0.0) + +**Example with custom port:** +```bash +EXTRACTOR_TYPE=p2p P2P_EXTRACTOR_PORT=9000 docker compose up -d +``` + +### RPC Extractor + +Periodically queries Bitcoin Core's RPC interface and publishes the results as events. + +```bash +EXTRACTOR_TYPE=rpc docker compose up -d +``` + +**Configuration Options:** +- `RPC_QUERY_INTERVAL`: Time between queries in seconds (default: 20) +- `RPC_USER`: RPC username (optional, uses cookie file by default) +- `RPC_PASSWORD`: RPC password (optional, uses cookie file by default) + +**Example with custom interval:** +```bash +EXTRACTOR_TYPE=rpc RPC_QUERY_INTERVAL=30 docker compose up -d +``` + +**Example with authentication:** +```bash +EXTRACTOR_TYPE=rpc RPC_USER=peer-observer RPC_PASSWORD=hunter2 docker compose up -d +``` + +**Note:** If `RPC_USER` and `RPC_PASSWORD` are not provided, the extractor will automatically use Bitcoin Core's cookie file for authentication. + +### Using Environment Files + +For convenience, create a `.env` file in the project root: + +```bash +# .env +EXTRACTOR_TYPE=rpc +RPC_QUERY_INTERVAL=30 +RPC_USER=peer-observer +RPC_PASSWORD=hunter2 +``` + +Then simply run: +```bash +docker compose up -d +``` + ## Services and Ports | Service | Port | Description | diff --git a/docker-compose/node.usdt.yml b/docker-compose/node.usdt.yml index cd37c42..9d58f11 100644 --- a/docker-compose/node.usdt.yml +++ b/docker-compose/node.usdt.yml @@ -4,12 +4,20 @@ services: build: context: ../ dockerfile: docker/bitcoin-node.dockerfile + args: + EXTRACTOR_TYPE: ${EXTRACTOR_TYPE:-ebpf} cap_add: - SYS_ADMIN - SYS_PTRACE security_opt: - apparmor:unconfined privileged: true + environment: + # RPC Extractor configuration (only used when EXTRACTOR_TYPE=rpc) + - RPC_QUERY_INTERVAL=${RPC_QUERY_INTERVAL:-20} + - RPC_USER=${RPC_USER:-} + - RPC_PASSWORD=${RPC_PASSWORD:-} + - RPC_COOKIE_FILE=${RPC_COOKIE_FILE:-} volumes: - bitcoin-data:/home/bitcoin/.bitcoin - /sys/kernel/debug:/sys/kernel/debug diff --git a/docker/bitcoin-node-entrypoint.sh b/docker/bitcoin-node-entrypoint.sh index 267a92f..db79706 100755 --- a/docker/bitcoin-node-entrypoint.sh +++ b/docker/bitcoin-node-entrypoint.sh @@ -2,8 +2,14 @@ set -e BITCOIND_PID="" +EXTRACTOR_PID="" cleanup() { + if [ -n "$EXTRACTOR_PID" ] && kill -0 "$EXTRACTOR_PID" 2>/dev/null; then + echo "Shutting down extractor (PID $EXTRACTOR_PID)..." + kill "$EXTRACTOR_PID" + wait "$EXTRACTOR_PID" + fi if [ -n "$BITCOIND_PID" ] && kill -0 "$BITCOIND_PID" 2>/dev/null; then echo "Shutting down bitcoind (PID $BITCOIND_PID)..." kill "$BITCOIND_PID" @@ -15,7 +21,7 @@ trap cleanup EXIT # Fix permissions on the data directory chown -R bitcoin:bitcoin /home/bitcoin/.bitcoin 2>/dev/null || true -# Determine network flag +# Determine network flag for bitcoin-cli NETWORK="" case "$BITCOIN_NETWORK" in mainnet) NETWORK="" ;; @@ -28,31 +34,152 @@ case "$BITCOIN_NETWORK" in ;; esac -# Start bitcoind as bitcoin user, in the background -echo "Launching Bitcoin node in $BITCOIN_NETWORK mode..." -/usr/sbin/runuser -u bitcoin -- $BTC_BIN_PATH/bitcoind $NETWORK & -BITCOIND_PID=$! +# Set default P2P port and address based on extractor type +P2P_EXTRACTOR_PORT=${P2P_EXTRACTOR_PORT:-8555} +P2P_EXTRACTOR_HOST=${P2P_EXTRACTOR_HOST:-0.0.0.0} -# Determine the bitcoind PID file path -BITCOIND_PID_FILE="/home/bitcoin/.bitcoin/$BITCOIN_NETWORK/bitcoind.pid" -echo "Reading Bitcoind PID from: $BITCOIND_PID_FILE" +echo "Starting $EXTRACTOR_TYPE-extractor setup..." -# Now wait for the RPC -for i in {1..30}; do - /usr/sbin/runuser -u bitcoin -- $BTC_BIN_PATH/bitcoin-cli $NETWORK getblockchaininfo >/dev/null 2>&1 && break - sleep 1 -done +# Different startup logic based on extractor type +case "$EXTRACTOR_TYPE" in + ebpf) + # For eBPF: start bitcoind normally, then attach extractor + echo "Launching Bitcoin node in $BITCOIN_NETWORK mode..." + /usr/sbin/runuser -u bitcoin -- $BTC_BIN_PATH/bitcoind $NETWORK & + BITCOIND_PID=$! + + # Determine the bitcoind PID file path + BITCOIND_PID_FILE="/home/bitcoin/.bitcoin/$BITCOIN_NETWORK/bitcoind.pid" + echo "Reading Bitcoind PID from: $BITCOIND_PID_FILE" + + # Wait for RPC to be ready + for i in {1..30}; do + /usr/sbin/runuser -u bitcoin -- $BTC_BIN_PATH/bitcoin-cli $NETWORK getblockchaininfo >/dev/null 2>&1 && break + sleep 1 + done + + if ! /usr/sbin/runuser -u bitcoin -- $BTC_BIN_PATH/bitcoin-cli $NETWORK getblockchaininfo >/dev/null 2>&1; then + echo "Error: bitcoind did not start in time." >&2 + exit 1 + fi + + echo "Starting ebpf-extractor..." + exec /usr/local/bin/ebpf-extractor \ + --no-idle-exit \ + --nats-address nats://nats:4222 \ + --bitcoind-path $BTC_BIN_PATH/bitcoind \ + --bitcoind-pid-file $BITCOIND_PID_FILE + ;; + + p2p) + # For P2P: start extractor first (listening), then bitcoind connects to it + echo "Starting p2p-extractor on $P2P_EXTRACTOR_HOST:$P2P_EXTRACTOR_PORT..." + /usr/local/bin/p2p-extractor \ + --nats-address nats://nats:4222 \ + --p2p-network ${BITCOIN_NETWORK:-regtest} \ + --p2p-address $P2P_EXTRACTOR_HOST:$P2P_EXTRACTOR_PORT & + EXTRACTOR_PID=$! + + # Give extractor time to start listening + sleep 2 + + # Check if extractor is still running + if ! kill -0 "$EXTRACTOR_PID" 2>/dev/null; then + echo "Error: p2p-extractor failed to start." >&2 + exit 1 + fi + + echo "Launching Bitcoin node in $BITCOIN_NETWORK mode (connecting to p2p-extractor)..." + # Start bitcoind with addnode pointing to the p2p-extractor + /usr/sbin/runuser -u bitcoin -- $BTC_BIN_PATH/bitcoind $NETWORK \ + -addnode=127.0.0.1:$P2P_EXTRACTOR_PORT & + BITCOIND_PID=$! + + # Wait for RPC to be ready + for i in {1..30}; do + /usr/sbin/runuser -u bitcoin -- $BTC_BIN_PATH/bitcoin-cli $NETWORK getblockchaininfo >/dev/null 2>&1 && break + sleep 1 + done + + if ! /usr/sbin/runuser -u bitcoin -- $BTC_BIN_PATH/bitcoin-cli $NETWORK getblockchaininfo >/dev/null 2>&1; then + echo "Error: bitcoind did not start in time." >&2 + exit 1 + fi + + echo "Bitcoin node connected to p2p-extractor successfully" + # Keep both processes running + wait $EXTRACTOR_PID + ;; -# Final check -if ! /usr/sbin/runuser -u bitcoin -- $BTC_BIN_PATH/bitcoin-cli $NETWORK getblockchaininfo >/dev/null 2>&1; then - echo "Error: bitcoind did not start in time." >&2 + rpc) + # For RPC: start bitcoind normally, then start rpc-extractor + echo "Launching Bitcoin node in $BITCOIN_NETWORK mode..." + /usr/sbin/runuser -u bitcoin -- $BTC_BIN_PATH/bitcoind $NETWORK & + BITCOIND_PID=$! + + # Wait for RPC to be ready + for i in {1..30}; do + /usr/sbin/runuser -u bitcoin -- $BTC_BIN_PATH/bitcoin-cli $NETWORK getblockchaininfo >/dev/null 2>&1 && break + sleep 1 + done + + if ! /usr/sbin/runuser -u bitcoin -- $BTC_BIN_PATH/bitcoin-cli $NETWORK getblockchaininfo >/dev/null 2>&1; then + echo "Error: bitcoind did not start in time." >&2 + exit 1 + fi + + # Set default query interval (in seconds) + RPC_QUERY_INTERVAL=${RPC_QUERY_INTERVAL:-20} + + # RPC credentials - use cookie file by default, or user/password if provided + RPC_AUTH_ARGS="" + if [ -n "$RPC_USER" ] && [ -n "$RPC_PASSWORD" ]; then + echo "Starting rpc-extractor with user/password authentication..." + RPC_AUTH_ARGS="--rpc-user $RPC_USER --rpc-password $RPC_PASSWORD" + else + echo "Starting rpc-extractor with cookie file authentication..." + RPC_COOKIE_FILE=${RPC_COOKIE_FILE:-/home/bitcoin/.bitcoin${NETWORK}/.cookie} + + # Verify cookie file exists (it should be auto-created by bitcoind) + if [ ! -f "$RPC_COOKIE_FILE" ]; then + echo "Warning: Cookie file not found at $RPC_COOKIE_FILE" >&2 + echo "Waiting for bitcoind to create it..." >&2 + for i in {1..10}; do + [ -f "$RPC_COOKIE_FILE" ] && break + sleep 1 + done + if [ ! -f "$RPC_COOKIE_FILE" ]; then + echo "Error: Cookie file was not created. Check if bitcoind RPC is enabled." >&2 + exit 1 + fi + fi + + echo "Using cookie file: $RPC_COOKIE_FILE" + RPC_AUTH_ARGS="--rpc-cookie-file $RPC_COOKIE_FILE" + fi + + echo "Starting rpc-extractor (query interval: ${RPC_QUERY_INTERVAL}s)..." + /usr/local/bin/rpc-extractor \ + --nats-address nats://nats:4222 \ + --query-interval $RPC_QUERY_INTERVAL \ + $RPC_AUTH_ARGS & + EXTRACTOR_PID=$! + + # Check if extractor is still running + sleep 1 + if ! kill -0 "$EXTRACTOR_PID" 2>/dev/null; then + echo "Error: rpc-extractor failed to start." >&2 + exit 1 + fi + + echo "rpc-extractor started successfully" + # Keep both processes running + wait $EXTRACTOR_PID + ;; + + *) + echo "Unknown EXTRACTOR_TYPE: $EXTRACTOR_TYPE" >&2 + echo "Supported types: ebpf, p2p, rpc" >&2 exit 1 -fi - -echo "Starting ebpf-extractor" -# Run ebpf-extractor as root (needs CAP_SYS_ADMIN for BPF) -exec /usr/local/bin/ebpf-extractor \ - --no-idle-exit \ - --nats-address nats://nats:4222 \ - --bitcoind-path "$BTC_BIN_PATH/bitcoind" \ - --bitcoind-pid-file "$BITCOIND_PID_FILE" + ;; +esac diff --git a/docker/bitcoin-node.dockerfile b/docker/bitcoin-node.dockerfile index a76af30..a9b6489 100644 --- a/docker/bitcoin-node.dockerfile +++ b/docker/bitcoin-node.dockerfile @@ -29,7 +29,7 @@ FROM ubuntu:22.04 AS peer-observer-builder ARG PEER_EXTRACTOR_REPO=https://github.com/0xB10C/peer-observer.git ARG PEER_EXTRACTOR_BRANCH=master -ARG PEER_EXTRACTOR_COMMIT=4a49347dcc764daabd047c01274afc6c5399bee6 +ARG EXTRACTOR_TYPE=ebpf # Install peer-extractor dependencies RUN apt-get update && apt-get install -y \ @@ -59,14 +59,16 @@ RUN chmod +x scripts/bitcoin-node-entrypoint.sh COPY docker/bitcoin-node-healthcheck.sh scripts/bitcoin-node-healthcheck.sh RUN chmod +x scripts/bitcoin-node-healthcheck.sh -# Build the project -RUN bash -c ". scripts/set-bpf-environment.sh && cargo build --release" +# Build only the specified extractor (faster build, smaller cache) +# Valid values: ebpf, p2p, rpc +RUN bash -c ". scripts/set-bpf-environment.sh && cargo build --release -p ${EXTRACTOR_TYPE}-extractor" ### Runtime stage ### FROM ubuntu:22.04 AS runtime ENV DEBIAN_FRONTEND=noninteractive +ARG EXTRACTOR_TYPE=ebpf # Install runtime dependencies RUN apt-get update && apt-get install -y \ @@ -93,7 +95,10 @@ ENV BTC_BIN_PATH=/bitcoin/build/bin COPY --from=btc-core-builder $BTC_BIN_PATH $BTC_BIN_PATH COPY --from=peer-observer-builder /peer-observer/scripts/bitcoin-node-entrypoint.sh /peer-observer/scripts/bitcoin-node-entrypoint.sh COPY --from=peer-observer-builder /peer-observer/scripts/bitcoin-node-healthcheck.sh /peer-observer/scripts/bitcoin-node-healthcheck.sh -COPY --from=peer-observer-builder /peer-observer/target/release/ebpf-extractor /usr/local/bin/ebpf-extractor +COPY --from=peer-observer-builder /peer-observer/target/release/${EXTRACTOR_TYPE}-extractor /usr/local/bin/${EXTRACTOR_TYPE}-extractor + +# Set which extractor is being used (for scripts/debugging) +ENV EXTRACTOR_TYPE=${EXTRACTOR_TYPE} # Expose Bitcoin ports (RPC: 8332, P2P: 8333) EXPOSE 8332 8333