Skip to content

Latest commit

 

History

History
158 lines (114 loc) · 6.6 KB

File metadata and controls

158 lines (114 loc) · 6.6 KB

ipc-exporter-rust

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.

Prerequisites

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.

Architecture

Single binary that does three things concurrently:

  1. IPC subscriptions — subscribes to ChainNotifications and UtxoCacheTrace via Cap'n Proto RPC. Callbacks fire in real-time for block/mempool/flush/UTXO cache events and update atomic counters. The UtxoCacheTrace subscription is deferred during IBD to avoid excessive RPC overhead; it is registered automatically once the poll loop detects IBD has completed.
  2. Polling loop — queries Chain and Node interfaces every 60 seconds for gauges (height, mempool stats, peer count, bandwidth). Initial poll runs at startup.
  3. HTTP server — raw TcpListener on 127.0.0.1:9332 (configurable) serving Prometheus text format. No HTTP framework — just reads one request and writes the response.

Source Layout

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.

Cap'n Proto Schemas

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.

CLI

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 (default 127.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.

Metrics

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_.

Building

Requires capnproto (the compiler, not the Rust crate) at build time for schema codegen.

nix develop -c cargo build

Or via Nix:

nix build

Bitcoin Node Package

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.

NixOS Module

The flake exports nixosModules.default (defined in module.nix). It creates a systemd service bitcoind-ipc-exporter.

Module Options (services.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

Integrating into another flake

# 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" ]; }];
  }];
}

Not Yet Implemented

  • Reconnect on bitcoind restart (currently the exporter exits; systemd Restart=on-failure handles it)
  • Grafana dashboard JSON provisioning
  • Dashboard parity validation against the existing USDT/RPC exporter

Development Rules

  • Keep this CLAUDE.md updated when making structural changes (new modules, changed architecture, new CLI flags, etc.).