bitcoin-remote 0.1.22

Low-level, Bitcoin Core–compatible JSON-RPC helper crate providing HTTP status mapping, RPC error codes, auth-cookie management, parameter-conversion tables, and UniValue-based request/reply utilities.
docs.rs failed to build bitcoin-remote-0.1.22
Please check the build logs for more information.
See Builds for ideas on how to fix a failed build, or Metadata for how to configure docs.rs builds.
If you believe this is docs.rs' fault, open an issue.
Visit the last successful build: bitcoin-remote-0.1.16-alpha.0

bitcoin-remote

Low-level, zero-magic utilities for speaking Bitcoin Core's JSON‑RPC over HTTP, faithfully ported from upstream C++ with strongly typed Rust interfaces.

This crate does not try to be a full Bitcoin wallet or a high-level client library. Instead, it focuses on being a precise, observable, and testable substrate for:

  • constructing and serializing JSON‑RPC requests and replies,
  • handling Bitcoin Core's RPC error model (codes and HTTP mappings),
  • managing rpcauth/cookie-based authentication,
  • resolving Bitcoin-style configuration paths,
  • and handling parameter-conversion metadata reproduced from Bitcoin Core's own RPC tables.

If you want exact behavioral parity with bitcoind's RPC interface—down to error codes, HTTP status behavior, and batch semantics—this crate is intended for you.


Features at a Glance

  • HTTP / JSON‑RPC fidelity

    • HTTPStatusCode enum for mapping internal failures to HTTP status codes.
    • JSON‑RPC 1.0 compatible wire format with selective adoption of 1.1/2.0 semantics for error structure.
    • Batch‑reply processing that mirrors upstream C++ behavior, including panic conditions on malformed replies.
  • Bitcoin Core RPC error codes

    • RPCErrorCode is a bitflags! set of all Bitcoin Core RPC error codes, including wallet, P2P, chain, and general application errors.
    • Aliases for backward compatibility (e.g. RPC_TRANSACTION_ERROR) closely track upstream.
  • RPC parameter conversion metadata

    • RPCConvertParam and RPCConvertTable encode which RPC parameters must be treated as JSON even when passed as textual CLI arguments.
    • Lookup by (method, index) or (method, name) gives you a boolean indicating whether a parameter must be JSON‑parsed before being passed to Core.
  • Authentication cookie helpers

    • generate_auth_cookie, get_auth_cookie, and delete_auth_cookie implement the same cookie‑file strategy as Bitcoin Core:
      • random password generation
      • temporary file + atomic rename
      • respect for -rpccookiefile from global args
  • JSON‑RPC request and response composition

    • JSONRPCRequest encapsulates method, params, id, and metadata like uri, auth_user, and peer_addr.
    • jsonrpc_request_obj, jsonrpc_reply_obj, jsonrpc_reply, and jsonrpc_error are low-level helpers over UniValue.
  • Instrumentation

    • Strategic, structured logging via the tracing crate at trace/debug/info levels, intended for production observability without polluting the hot path.

The crate assumes you are comfortable working close to the protocol: you will handle HTTP transport concerns, lifetimes, and concurrency at your own abstraction layer.


Design Philosophy

The design mirrors Bitcoin Core's internals:

  • Minimal abstraction leak: the Rust types are thin wrappers around the behavior of upstream C++ constructs, including panic conditions and error code usage.
  • Deterministic edge behavior: malformed batch responses, invalid request objects, and type errors produce panics consistent with the original exception‑throwing C++ implementation.
  • Explicit data flow: all JSON objects are composed using UniValue (from uni_value), not serde_json, to follow Core's semantics exactly.

The goal is to allow you to reconstruct, inspect, and control your interaction with a Bitcoin node with maximal transparency.


Core Types and Modules

HTTPStatusCode

#[repr(i32)]
pub enum HTTPStatusCode {
    HTTP_OK,
    HTTP_BAD_REQUEST,
    HTTP_UNAUTHORIZED,
    HTTP_FORBIDDEN,
    HTTP_NOT_FOUND,
    HTTP_BAD_METHOD,
    HTTP_INTERNAL_SERVER_ERROR,
    HTTP_SERVICE_UNAVAILABLE,
}

HTTPStatusCode encodes the subset of HTTP codes relevant to JSON‑RPC error mapping. Upstream Core maps certain RPC errors to specific HTTP codes (e.g. RPC_INVALID_REQUESTHTTP_BAD_REQUEST). Use this when designing your HTTP layer.

JSONRPCRequestMode

pub enum JSONRPCRequestMode {
    EXECUTE,
    GET_HELP,
    GET_ARGS,
}

JSONRPCRequestMode allows you to model whether an incoming request is meant to be executed or used for help/argument introspection. This is primarily useful for CLI frontends or meta‑RPC layers.

RPCErrorCode (bitflags)

RPCErrorCode is defined using bitflags! over i32. It enumerates the entire Bitcoin Core RPC error space:

  • Standard JSON‑RPC 2.0 errors: RPC_INVALID_REQUEST, RPC_METHOD_NOT_FOUND, RPC_INVALID_PARAMS, RPC_INTERNAL_ERROR, RPC_PARSE_ERROR.
  • General application errors: RPC_MISC_ERROR, RPC_TYPE_ERROR, RPC_INVALID_PARAMETER, etc.
  • P2P client errors: RPC_CLIENT_NOT_CONNECTED, RPC_CLIENT_IN_INITIAL_DOWNLOAD, RPC_CLIENT_NODE_CAPACITY_REACHED, etc.
  • Chain and mempool errors.
  • Wallet errors: RPC_WALLET_ERROR, RPC_WALLET_INSUFFICIENT_FUNDS, RPC_WALLET_UNLOCK_NEEDED, RPC_WALLET_ALREADY_LOADED, etc.
  • Legacy aliases and reserved codes, retained for backwards compatibility.

Each error is a bitflag with a specific negative code matching Bitcoin Core's public RPC interface. Example:

use bitcoin_remote::RPCErrorCode;

fn classify(err_code: i32) {
    if err_code == RPCErrorCode::RPC_WALLET_INSUFFICIENT_FUNDS.bits() {
        eprintln!("insufficient funds: {err_code}");
    }
}

Using a bitflag type instead of raw i32 clarifies intent and allows consistent code completion.

RPCConvertParam & RPCConvertTable

Bitcoin Core distinguishes between positional and named RPC parameters that must be treated as JSON instead of simple strings. For example, sendrawtransaction expects a hex string, whereas createrawtransaction may expect complex JSON objects.

This crate mirrors Core's table‑driven approach using:

#[derive(Debug, Getters, MutGetters, Setters, Default, Builder)]
pub struct RPCConvertParam {
    method_name: &'static str,
    param_idx:   i32,
    param_name:  &'static str,
}

#[derive(Debug, Getters, MutGetters, Setters, Default, Builder)]
pub struct RPCConvertTable {
    members:         HashSet<(String, i32)>,
    members_by_name: HashSet<(String, String)>,
}

and the global instance:

lazy_static! {
    pub static ref RPC_CVT_TABLE: std::sync::Mutex<RPCConvertTable> =
        std::sync::Mutex::new(RPCConvertTable::new());
}

RPCConvertTable::new() populates members and members_by_name from a static vRPCConvertParams list emitted from upstream definitions. You can query it as follows:

use bitcoin_remote::RPC_CVT_TABLE;

fn param_requires_json(method: &str, index: i32) -> bool {
    let mut tbl = RPC_CVT_TABLE.lock().expect("RPC_CVT_TABLE poisoned");
    tbl.convert_with_method_and_idx(method, index)
}

fn param_requires_json_by_name(method: &str, name: &str) -> bool {
    let mut tbl = RPC_CVT_TABLE.lock().expect("RPC_CVT_TABLE poisoned");
    tbl.convert_with_method_and_name(method, name)
}

These APIs are intentionally simple: they answer "must this parameter be JSON‑parsed?" without hiding the table's structure.

JSONRPCRequest

pub struct JSONRPCRequest {
    id:         UniValue,
    str_method: String,
    params:     UniValue,
    mode:       JSONRPCRequestMode,
    uri:        String,
    auth_user:  String,
    peer_addr:  String,
    context:    Box<dyn Any>,
}

The parse method implements JSON‑RPC request parsing consistent with Bitcoin Core:

  • Validates that the top-level value is an object.
  • Extracts id early so subsequent error reports can include it.
  • Checks method is present and a string.
  • Accepts params as either an array or object; null becomes an empty array; all other types panic as invalid.

Example:

use bitcoin_remote::{JSONRPCRequest, jsonrpc_error, RPCErrorCode};
use uni_value::{UniValue, VType};

fn parse_single_request(val_request: &UniValue) -> JSONRPCRequest {
    let mut req = JSONRPCRequest {
        // reasonable defaults; context populated by caller
        id: UniValue::null(),
        str_method: String::new(),
        params: UniValue::empty_array(),
        mode: bitcoin_remote::JSONRPCRequestMode::EXECUTE,
        uri: String::new(),
        auth_user: String::new(),
        peer_addr: String::new(),
        context: Box::new(()),
    };

    // May panic with a JSON‑encoded error, per Core behavior
    req.parse(val_request);
    req
}

Because the implementation intentionally panics on structural violations (mirroring C++ exceptions), you should either sanitize input at your HTTP boundary or wrap parsing in higher‑level error handling if you need a non‑panic path.


JSON‑RPC Utilities

Building requests

pub fn jsonrpc_request_obj(str_method: &str, params: &UniValue, id: &UniValue) -> UniValue

Creates a minimal JSON‑RPC request object:

{ "method": <str_method>, "params": <params>, "id": <id> }

Example:

use bitcoin_remote::jsonrpc_request_obj;
use uni_value::{UniValue, VType};

let mut params = UniValue::new(VType::VARR, None);
params.push_back("getblockchaininfo");

let id = UniValue::from(1_i64);
let req = jsonrpc_request_obj("getblockchaininfo", &params, &id);

let wire = req.write(None, None);

Building replies and errors

pub fn jsonrpc_reply_obj(result: &UniValue, error: &UniValue, id: &UniValue) -> UniValue
pub fn jsonrpc_reply(result: &UniValue, error: &UniValue, id: &UniValue) -> String
pub fn jsonrpc_error(code: i32, message: &str) -> UniValue

Semantics:

  • If error is not null, the reply's "result" field is forced to null.
  • jsonrpc_reply serializes the reply to a compact string and appends a newline (matching Core's write() + "\n").

Example:

use bitcoin_remote::{jsonrpc_reply, jsonrpc_error, RPCErrorCode};
use uni_value::UniValue;

// success reply
let result = UniValue::from("ok");
let error = UniValue::null();
let id = UniValue::from(1_i64);
let success_wire = jsonrpc_reply(&result, &error, &id);

// error reply
let err_obj = jsonrpc_error(
    RPCErrorCode::RPC_INVALID_PARAMS.bits(),
    "invalid parameters supplied",
);
let error_wire = jsonrpc_reply(&UniValue::null(), &err_obj, &id);

Batch reply processing

pub fn jsonrpc_process_batch_reply(in_: &UniValue) -> Vec<UniValue>

Parses a batch reply into a Vec<UniValue> indexed by numeric "id":

  • in_ must be an array; otherwise the function panics with "Batch must be an array".
  • Each member must be an object; non‑objects panic with "Batch member must be an object".
  • Each member must have an integer "id" between 0 and batch_size - 1; otherwise, a panic with "Batch member id is larger than batch size" occurs.

This deterministic behavior makes it straightforward to implement batch clients that rely strictly on index‑based mapping.

Example:

use bitcoin_remote::jsonrpc_process_batch_reply;
use uni_value::{UniValue, VType};

fn handle_batch(batch_uv: &UniValue) {
    let replies = jsonrpc_process_batch_reply(batch_uv);
    for (idx, reply) in replies.iter().enumerate() {
        println!("reply {idx}: {reply:?}");
    }
}

Authentication Cookie Helpers

Bitcoin Core supports a cookie‑file based authentication mechanism. This crate provides low-level helpers that implement the same behavior, including path resolution and atomic writes.

get_auth_cookie_file

pub fn get_auth_cookie_file(temp: Option<bool>) -> Box<Path>
  • Resolves the absolute path for the RPC auth cookie.
  • If temp == Some(true), appends .tmp and returns the path for the temporary file location.
  • Uses -rpccookiefile=<path> from global args if present; otherwise it falls back to the default (typically something like $DATADIR/.cookie).

generate_auth_cookie

pub fn generate_auth_cookie(cookie_out: &mut String) -> bool

Steps:

  1. Generate 32 random bytes and hex‑encode them as the password portion.
  2. Construct "<COOKIEAUTH_USER>:<hex_password>".
  3. Write to the temporary cookie file (.tmp), creating parent directories as needed.
  4. Atomically rename the temporary file to the final auth cookie path.

On success, cookie_out is populated and true is returned. Errors are logged via tracing::error! and false is returned; best‑effort clean‑up is attempted.

Example:

use bitcoin_remote::generate_auth_cookie;

let mut cookie = String::new();
if generate_auth_cookie(&mut cookie) {
    println!("Generated cookie: {cookie}");
}

get_auth_cookie

pub fn get_auth_cookie(cookie_out: &mut String) -> bool
  • Reads the cookie from disk into cookie_out.
  • Trims trailing \n and optional \r to match C++ getline behavior.
  • Returns false if the file cannot be opened or read, logging at debug or warn levels.

delete_auth_cookie

pub fn delete_auth_cookie()

Attempts to delete the cookie file. Any I/O error other than file not found is logged but otherwise ignored.

Example integration pattern:

use bitcoin_remote::{generate_auth_cookie, get_auth_cookie, delete_auth_cookie};

fn rotate_cookie() {
    let mut new_cookie = String::new();
    if !generate_auth_cookie(&mut new_cookie) {
        eprintln!("failed to generate new cookie");
        return;
    }

    let mut read_back = String::new();
    if get_auth_cookie(&mut read_back) {
        assert_eq!(new_cookie, read_back);
    }

    delete_auth_cookie();
}

Error Semantics and Stability

  • Panics vs. errors: many functions (JSONRPCRequest::parse, jsonrpc_process_batch_reply) intentionally use panic! on structural violations, to mimic Bitcoin Core's use of exceptions for programmer / protocol errors rather than expected runtime failures.
  • Logging: trace!, debug!, info!, warn!, and error! invocations align with logical phases—construction, parsing, and I/O. In production, you can adjust your tracing subscriber level to tune verbosity.
  • Error codes: RPCErrorCode values are kept numerically identical to upstream. Where upstream deprecates or repurposes codes, the crate favors backward compatibility.

If you are building public APIs or user‑facing tooling on top of this crate, you may want to wrap these primitives into a non‑panicking facade that translates panics into structured error values.


Example: End‑to‑End Single Call

Below is a sketch of how you might integrate bitcoin-remote into a higher-level HTTP client. This example omits error‑handling and TLS for brevity.

use bitcoin_remote::{
    jsonrpc_request_obj,
    jsonrpc_reply,
    jsonrpc_process_batch_reply,
};
use uni_value::{UniValue, VType};

fn build_getblockchaininfo_request() -> String {
    let params = UniValue::empty_array();
    let id = UniValue::from(0_i64);
    let req_obj = jsonrpc_request_obj("getblockchaininfo", &params, &id);
    let mut s = req_obj.write(None, None);
    s.push('\n');
    s
}

fn main() {
    let body = build_getblockchaininfo_request();

    // send `body` over HTTP POST to bitcoind's RPC endpoint using your
    // HTTP client of choice (e.g., reqwest). Parse the response into
    // `UniValue` using the `uni_value` crate, then handle it.
}

Crate Metadata


Caveats

  • This README was produced by an AI model and may diverge slightly from the exact current API surface, especially around auxiliary functions and constants not shown in the provided interface.
  • Always rely on the actual Rustdoc and source code in the repository for authoritative reference.