Prometheus metrics exporter for Bitcoin Core using IPC (Cap'n Proto over Unix socket). Replaces the older USDT/BCC + RPC polling approach with direct subscription-based metrics from Bitcoin Core's multiprocess mode.
Bitcoin Core must be built with multiprocess support (the bitcoin-node binary from the pr-10102 branch or equivalent). The exporter connects to its IPC Unix socket.
Single binary that does three things concurrently:
- IPC subscriptions — subscribes to
ChainNotificationsandUtxoCacheTracevia Cap'n Proto RPC. Callbacks fire in real-time for block/mempool/flush/UTXO cache events and update atomic counters. TheUtxoCacheTracesubscription is deferred during IBD to avoid excessive RPC overhead; it is registered automatically once the poll loop detects IBD has completed. - Polling loop — queries
ChainandNodeinterfaces every 60 seconds for gauges (height, mempool stats, peer count, bandwidth). Initial poll runs at startup. - HTTP server — raw
TcpListeneron127.0.0.1:9332(configurable) serving Prometheus text format. No HTTP framework — just reads one request and writes the response.
| File | Responsibility |
|---|---|
src/main.rs |
CLI parsing, signal handling, poll loop, orchestration. Declares capnp generated modules. |
src/rpc.rs |
RpcInterface — IPC handshake and all Cap'n Proto RPC query methods. |
src/metrics.rs |
Metrics struct (atomic counters/gauges) and format_metrics() Prometheus formatter. |
src/server.rs |
serve_metrics() — TCP listener serving the Prometheus text endpoint. |
src/notifications.rs |
NotificationHandler and UtxoCacheHandler — callback impls that update metrics. |
All metrics live in a Metrics struct using stdlib atomics (AtomicU64, AtomicI32, AtomicBool, etc.), shared via Arc. The f64 verification_progress is stored as bits in an AtomicU64.
The IPC handshake sequence: Init.construct() → ThreadMap.makeThread() → Init.makeChain() / Init.makeNode() / Init.makeTracing(). Every RPC call requires passing a thread context.
Located in schema/, sourced from Bitcoin Core's multiprocess branch. build.rs compiles them into Rust code at build time via capnpc. The generated modules are included with include!(concat!(env!("OUT_DIR"), "/<name>_capnp.rs")).
Schemas: chain, common, echo, handler, init, mining, node, tracing, wallet, mp/proxy. Not all are actively used — chain, node, handler, init, tracing, and proxy are the ones the exporter calls.
ipc-exporter-rust [--debug] [--metrics-addr HOST:PORT] <socket-path>
--debug— verbose per-event and per-poll logging to stderr (off by default)--metrics-addr— listen address for Prometheus endpoint (default127.0.0.1:9332)<socket-path>— path to bitcoind's IPC Unix socket
Without --debug, only info-level messages (startup/shutdown) are logged to stderr. Logging uses env_logger; RUST_LOG can override the level.
See METRICS.md for the full reference. Summary:
- 6 chain callback counters (blocks connected/disconnected, mempool tx add/remove, tip updates, flushes)
- 1 chain callback gauge (block_height from block_connected)
- 6 UTXO cache trace counters (add/spend/uncache counts and cumulative values)
- 7 polled gauges (chain_height, ibd, verification_progress, mempool_size/bytes/max, peers)
- 2 polled counters (bytes_recv, bytes_sent)
All metric names are prefixed bitcoin_.
Requires capnproto (the compiler, not the Rust crate) at build time for schema codegen.
nix develop -c cargo build
Or via Nix:
nix build
The flake provides a multiprocess-enabled Bitcoin Core package built from ryanofsky's pr/ipc branch (version 30.99). This includes the bitcoin-node binary required for IPC socket communication.
nix build .#bitcoin-node
The package contains: libexec/bitcoin-node (multiprocess daemon), bin/bitcoind, bin/bitcoin-cli, bin/bitcoin-wallet, bin/bitcoin. The multiprocess daemon is in libexec/, not bin/.
Available as packages.<system>.bitcoin-node and via overlays.default (exposes pkgs.bitcoin-node). The source is pinned as the bitcoin-node-src flake input — update with nix flake update bitcoin-node-src.
The flake exports nixosModules.default (defined in module.nix). It creates a systemd service bitcoind-ipc-exporter.
| Option | Type | Default | Description |
|---|---|---|---|
enable |
bool | false |
Enable the service |
package |
package | — (required) | The ipc-exporter-rust package |
socketPath |
string | — (required) | Path to bitcoind IPC Unix socket |
metricsAddr |
string | "127.0.0.1:9332" |
Prometheus endpoint listen address |
debug |
bool | false |
Verbose stderr logging |
user |
string | "root" |
Systemd service user |
group |
string | "root" |
Systemd service group |
after |
list of strings | [] |
Systemd After= dependencies |
bindsTo |
list of strings | [] |
Systemd BindsTo= dependencies |
# flake.nix
{
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-25.11";
ipc-exporter.url = "github:bitcoin-dev-tools/ipc-exporter-rust";
};
outputs = { nixpkgs, ipc-exporter, ... }: {
nixosConfigurations.myhost = nixpkgs.lib.nixosSystem {
system = "x86_64-linux";
modules = [
ipc-exporter.nixosModules.default
./configuration.nix
];
specialArgs = { inherit ipc-exporter; };
};
};
}# configuration.nix
{ ipc-exporter, ... }:
{
services.bitcoind-ipc-exporter = {
enable = true;
package = ipc-exporter.packages.x86_64-linux.default;
socketPath = "/var/lib/bitcoind-mainnet/node.sock";
user = "bitcoind-mainnet";
group = "bitcoind-mainnet";
after = [ "bitcoind-mainnet.service" ];
bindsTo = [ "bitcoind-mainnet.service" ];
};
# Add a Prometheus scrape target
services.prometheus.scrapeConfigs = [{
job_name = "bitcoind-ipc";
static_configs = [{ targets = [ "127.0.0.1:9332" ]; }];
}];
}- Reconnect on bitcoind restart (currently the exporter exits; systemd
Restart=on-failurehandles it) - Grafana dashboard JSON provisioning
- Dashboard parity validation against the existing USDT/RPC exporter
- Keep this CLAUDE.md updated when making structural changes (new modules, changed architecture, new CLI flags, etc.).