Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
91 changes: 87 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`.

Expand Down Expand 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
```
Expand All @@ -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
Expand All @@ -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 |
Expand Down
8 changes: 8 additions & 0 deletions docker-compose/node.usdt.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
177 changes: 152 additions & 25 deletions docker/bitcoin-node-entrypoint.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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="" ;;
Expand All @@ -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
13 changes: 9 additions & 4 deletions docker/bitcoin-node.dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -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 \
Expand Down Expand Up @@ -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 \
Expand All @@ -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
Expand Down