Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
89 changes: 88 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,90 @@
# bdk-bitcoind-client

Bitcoin Core RPC Client (experimental)
A minimal Bitcoin Core RPC client designed specifically for the Bitcoin Dev Kit (BDK). It retrieves blockchain data from `bitcoind` over JSON-RPC and supports multiple versions of Bitcoin Core (v28.0 through v30.0+).

### Features

- _Version Pinning_: Explicit support for different Bitcoin Core RPC schemas via feature flags (28_0, 29_0, 30_0).
- _Minimal Dependencies_: Uses bitreq_http for a lightweight HTTP transport by default.
- _Wallet-Agnostic_: Focused on blockchain data emission (blocks, headers, mempool) rather than wallet management.
- _Robust Error Handling_: Specific error variants for RPC failures, deserialization issues, and transport timeouts.

### Installation

Add this to your `Cargo.toml`:
```toml
# For the latest Bitcoin Core (v30.0+)
bdk-bitcoind-client = { version = "0.1.0" }

# OR for older nodes (e.g., v28.x)
bdk-bitcoind-client = { version = "0.1.0", default-features = false, features = ["28_0"] }
```

### Quick Start

```rust
use bdk_bitcoind_client::{Auth, Client};
use std::path::PathBuf;
fn main() -> anyhow::Result<()> {
// 1. Setup authentication (Cookie file is recommended for security)
let auth = Auth::CookieFile(PathBuf::from("/path/to/regtest/.cookie"));

// 2. Initialize the client
let client = Client::with_auth("http://127.0.0.1:18443", auth)?;

// 3. Query the blockchain
let block_count = client.get_block_count()?;
let best_hash = client.get_block_hash(block_count)?;

// 4. Get verbose headers (handles schema differences automatically)
let header = client.get_block_header_verbose(&best_hash)?;

println!("Chain tip: {} at height {}", header.hash, header.height);

Ok(())
}
```

### Version Compatibility

Bitcoin Core often changes its JSON-RPC response fields (e.g., adding the target field in `v29/v30`). This client manages these differences through compile-time features.

| Feature | Bitcoin Core Version | Notes |
| ----------------- | --------------------- | -------------------------------------------- |
| 30_0 (default) | v30.x and newer | Supports latest target and difficulty fields.|
| 29_0 | v29.x | Aligned with v29 schema. |
| 28_0 | v28.x and older | Omits newer fields |


### Development and Testing
To run tests against a specific Bitcoin Core version, use the corresponding feature flag:

```
cargo test --no-default-features --features 28_0
```

### Minimum Supported Rust Version (MSRV)

The library maintain a MSRV of 1.85.0.

## Just

This project has a [`justfile`](/justfile) for easy command running. You must have [`just`](https://github.com/casey/just) installed.

To see a list of available recipes: `just`

## License

Licensed under either of

* Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or <https://www.apache.org/licenses/LICENSE-2.0>)
* MIT license ([LICENSE-MIT](LICENSE-MIT) or <https://opensource.org/licenses/MIT>)

at your option.
Comment on lines +76 to +83

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's no Apache-2.0 License in this PR or the repo.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should have a LICENSE-MIT, LICENSE-APACHE and a LICENSE.md (so GH renders it).


### Contribution

Unless you explicitly state otherwise, any contribution intentionally
submitted for inclusion in the work by you, as defined in the Apache-2.0
license, shall be dual licensed as above, without any additional terms or
conditions.
28 changes: 14 additions & 14 deletions src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,15 +63,15 @@ pub struct Client {
}

impl Client {
/// Creates a client connection to a bitcoind JSON-RPC server with authentication
/// Creates a client connection to a bitcoind JSON-RPC server with authentication.
///
/// Requires authentication via username/password or cookie file.
/// For connections without authentication, use `with_transport` instead.
///
/// # Arguments
///
/// * `url` - URL of the RPC server
/// * `auth` - authentication method (`UserPass` or `CookieFile`).
/// * `auth` - authentication method (`UserPass` or `CookieFile`)
///
/// # Errors
///
Expand Down Expand Up @@ -109,7 +109,7 @@ impl Client {
}
}

/// Calls the underlying RPC `method` with given `args` list
/// Calls the underlying RPC `method` with the given `args`.
///
/// This is the generic function used by all specific RPC methods.
pub fn call<T>(&self, method: &str, args: &[serde_json::Value]) -> Result<T, Error>
Expand All @@ -124,9 +124,9 @@ impl Client {
}
}

/// `Bitcoind` RPC methods implementation for `Client`
/// `bitcoind` RPC methods implementation for `Client`.
impl Client {
/// Retrieves the raw block data for a given block hash (verbosity 0)
/// Retrieves the raw block data for a given block hash (verbosity 0).
///
/// # Arguments
///
Expand All @@ -140,7 +140,7 @@ impl Client {
.and_then(|block_hex| deserialize_hex(&block_hex).map_err(Error::DecodeHex))
}

/// Retrieves the hash of the tip of the best block chain.
/// Retrieves the hash of the best chain's block.
///
/// # Returns
///
Expand All @@ -150,7 +150,7 @@ impl Client {
.and_then(|blockhash_hex| blockhash_hex.parse().map_err(Error::HexToArray))
}

/// Retrieves the number of blocks in the longest chain
/// Retrieves the number of blocks in the longest chain.
///
/// # Returns
///
Expand All @@ -162,36 +162,36 @@ impl Client {
.map_err(Error::TryFromInt)
}

/// Retrieves the block hash at a given height
/// Retrieves the [`BlockHash`] of the block at `height`.
///
/// # Arguments
///
/// * `height`: The block height
///
/// # Returns
///
/// The `BlockHash` for the given height
/// The [`BlockHash`] of the block at `height`
pub fn get_block_hash(&self, height: u32) -> Result<BlockHash, Error> {
self.call::<String>("getblockhash", &[json!(height)])
.and_then(|blockhash_hex| blockhash_hex.parse().map_err(Error::HexToArray))
}

/// Retrieve the `basic` BIP 157 content filter for a particular block
/// Retrieve the Compact Block Filter (BIP-0158) with type `basic` for the block given its `Blockhash`.
///
/// # Arguments
///
/// * `block_hash`: The hash of the block whose filter is requested
///
/// # Returns
///
/// The `GetBlockFilter` structure containing the filter data
/// The `GetBlockFilter` structure containing the filter data for the block
pub fn get_block_filter(&self, block_hash: &BlockHash) -> Result<GetBlockFilter, Error> {
let block_filter: v30::GetBlockFilter =
self.call("getblockfilter", &[json!(block_hash)])?;
block_filter.into_model().map_err(Error::GetBlockFilter)
}

/// Retrieves the raw block header for a given block hash.
/// Retrieves the `Header` for a `Block` given its `BlockHash`.
///
/// # Arguments
///
Expand All @@ -205,7 +205,7 @@ impl Client {
.and_then(|header_hex: String| deserialize_hex(&header_hex).map_err(Error::DecodeHex))
}

/// Retrieves the transaction IDs of all transactions currently in the mempool
/// Retrieves the `Txid`s for all transactions in the mempool.
///
/// # Returns
///
Expand All @@ -215,7 +215,7 @@ impl Client {
.map(|txids| txids.0)
}

/// Retrieves the raw transaction data for a given transaction ID
/// Retrieves the raw transaction data for a given transaction ID.
///
/// # Arguments
///
Expand Down
Loading