Files
brk/crates/brk_rpc/examples/compare_backends.rs
2026-04-17 21:23:11 +02:00

270 lines
9.7 KiB
Rust

//! Compares results from the bitcoincore-rpc and corepc backends.
//!
//! Run with:
//! cargo run -p brk_rpc --example compare_backends --features corepc
#[cfg(all(feature = "bitcoincore-rpc", feature = "corepc"))]
use std::time::{Duration, Instant};
#[cfg(not(all(feature = "bitcoincore-rpc", feature = "corepc")))]
fn main() {
eprintln!("This example requires both features: --features bitcoincore-rpc,corepc");
std::process::exit(1);
}
#[cfg(all(feature = "bitcoincore-rpc", feature = "corepc"))]
fn main() {
use brk_rpc::backend::{self, Auth};
brk_logger::init(None).unwrap();
let bitcoin_dir = brk_rpc::Client::default_bitcoin_path();
let auth = Auth::CookieFile(bitcoin_dir.join(".cookie"));
let url = brk_rpc::Client::default_url();
let bc = backend::bitcoincore::ClientInner::new(url, auth.clone(), 10, Duration::from_secs(1))
.expect("bitcoincore client");
let cp = backend::corepc::ClientInner::new(url, auth, 10, Duration::from_secs(1))
.expect("corepc client");
println!("=== Comparing backends ===\n");
// --- get_blockchain_info ---
{
let (t1, r1) = timed(|| bc.get_blockchain_info());
let (t2, r2) = timed(|| cp.get_blockchain_info());
let r1 = r1.unwrap();
let r2 = r2.unwrap();
println!("get_blockchain_info:");
println!(
" bitcoincore: headers={} blocks={} ({t1:?})",
r1.headers, r1.blocks
);
println!(
" corepc: headers={} blocks={} ({t2:?})",
r2.headers, r2.blocks
);
assert_eq!(r1.headers, r2.headers, "headers mismatch");
assert_eq!(r1.blocks, r2.blocks, "blocks mismatch");
println!(" MATCH\n");
}
// --- get_block_count ---
{
let (t1, r1) = timed(|| bc.get_block_count());
let (t2, r2) = timed(|| cp.get_block_count());
let r1 = r1.unwrap();
let r2 = r2.unwrap();
println!("get_block_count:");
println!(" bitcoincore: {r1} ({t1:?})");
println!(" corepc: {r2} ({t2:?})");
assert_eq!(r1, r2, "block count mismatch");
println!(" MATCH\n");
}
// --- get_block_hash (height 0) ---
let genesis_hash;
{
let (t1, r1) = timed(|| bc.get_block_hash(0));
let (t2, r2) = timed(|| cp.get_block_hash(0));
let r1 = r1.unwrap();
let r2 = r2.unwrap();
genesis_hash = r1;
println!("get_block_hash(0):");
println!(" bitcoincore: {r1} ({t1:?})");
println!(" corepc: {r2} ({t2:?})");
assert_eq!(r1, r2, "genesis hash mismatch");
println!(" MATCH\n");
}
// --- get_block_header ---
{
let (t1, r1) = timed(|| bc.get_block_header(&genesis_hash));
let (t2, r2) = timed(|| cp.get_block_header(&genesis_hash));
let r1 = r1.unwrap();
let r2 = r2.unwrap();
println!("get_block_header(genesis):");
println!(" bitcoincore: prev={} ({t1:?})", r1.prev_blockhash);
println!(" corepc: prev={} ({t2:?})", r2.prev_blockhash);
assert_eq!(r1, r2, "header mismatch");
println!(" MATCH\n");
}
// --- get_block_info ---
{
let (t1, r1) = timed(|| bc.get_block_info(&genesis_hash));
let (t2, r2) = timed(|| cp.get_block_info(&genesis_hash));
let r1 = r1.unwrap();
let r2 = r2.unwrap();
println!("get_block_info(genesis):");
println!(
" bitcoincore: height={} confirmations={} ({t1:?})",
r1.height, r1.confirmations
);
println!(
" corepc: height={} confirmations={} ({t2:?})",
r2.height, r2.confirmations
);
assert_eq!(r1.height, r2.height, "height mismatch");
// confirmations can drift by 1 between calls
assert!(
(r1.confirmations - r2.confirmations).abs() <= 1,
"confirmations mismatch: {} vs {}",
r1.confirmations,
r2.confirmations
);
println!(" MATCH\n");
}
// --- get_block_header_info ---
{
let (t1, r1) = timed(|| bc.get_block_header_info(&genesis_hash));
let (t2, r2) = timed(|| cp.get_block_header_info(&genesis_hash));
let r1 = r1.unwrap();
let r2 = r2.unwrap();
println!("get_block_header_info(genesis):");
println!(
" bitcoincore: height={} prev={:?} ({t1:?})",
r1.height, r1.previous_block_hash
);
println!(
" corepc: height={} prev={:?} ({t2:?})",
r2.height, r2.previous_block_hash
);
assert_eq!(r1.height, r2.height, "height mismatch");
assert_eq!(
r1.previous_block_hash, r2.previous_block_hash,
"prev hash mismatch"
);
println!(" MATCH\n");
}
// --- get_block (genesis) ---
{
let (t1, r1) = timed(|| bc.get_block(&genesis_hash));
let (t2, r2) = timed(|| cp.get_block(&genesis_hash));
let r1 = r1.unwrap();
let r2 = r2.unwrap();
println!("get_block(genesis):");
println!(" bitcoincore: txs={} ({t1:?})", r1.txdata.len());
println!(" corepc: txs={} ({t2:?})", r2.txdata.len());
assert_eq!(r1, r2, "block mismatch");
println!(" MATCH\n");
}
// --- get_raw_mempool ---
{
let (t1, r1) = timed(|| bc.get_raw_mempool());
let (t2, r2) = timed(|| cp.get_raw_mempool());
let r1 = r1.unwrap();
let r2 = r2.unwrap();
println!("get_raw_mempool:");
println!(" bitcoincore: {} txs ({t1:?})", r1.len());
println!(" corepc: {} txs ({t2:?})", r2.len());
// Mempool can change between calls, just check they're reasonable
println!(
" {} (mempool is live, counts may differ slightly)\n",
if r1.len() == r2.len() {
"MATCH"
} else {
"CLOSE"
}
);
}
// --- get_raw_mempool_verbose ---
{
let (t1, r1) = timed(|| bc.get_raw_mempool_verbose());
let (t2, r2) = timed(|| cp.get_raw_mempool_verbose());
let r1 = r1.unwrap();
let r2 = r2.unwrap();
println!("get_raw_mempool_verbose:");
println!(" bitcoincore: {} entries ({t1:?})", r1.len());
println!(" corepc: {} entries ({t2:?})", r2.len());
// Compare a sample entry if both have data
if let (Some((txid1, e1)), Some(_)) = (r1.first(), r2.first())
&& let Some((_, e2)) = r2.iter().find(|(t, _)| t == txid1)
{
println!(" sample txid {txid1}:");
println!(
" bitcoincore: vsize={} fee={} ancestor_count={}",
e1.vsize, e1.base_fee_sats, e1.ancestor_count
);
println!(
" corepc: vsize={} fee={} ancestor_count={}",
e2.vsize, e2.base_fee_sats, e2.ancestor_count
);
assert_eq!(e1.base_fee_sats, e2.base_fee_sats, "fee mismatch");
assert_eq!(
e1.ancestor_count, e2.ancestor_count,
"ancestor_count mismatch"
);
println!(" MATCH");
}
println!();
}
// --- get_raw_transaction_hex (tx from block 1, genesis coinbase can't be retrieved) ---
let block1_hash;
{
block1_hash = bc.get_block_hash(1).unwrap();
let block = bc.get_block(&block1_hash).unwrap();
let coinbase_txid = block.txdata[0].compute_txid();
let (t1, r1) = timed(|| bc.get_raw_transaction_hex(&coinbase_txid, Some(&block1_hash)));
let (t2, r2) = timed(|| cp.get_raw_transaction_hex(&coinbase_txid, Some(&block1_hash)));
let r1 = r1.unwrap();
let r2 = r2.unwrap();
println!("get_raw_transaction_hex(block 1 coinbase):");
println!(" bitcoincore: {}... ({t1:?})", &r1[..40.min(r1.len())]);
println!(" corepc: {}... ({t2:?})", &r2[..40.min(r2.len())]);
assert_eq!(r1, r2, "raw tx hex mismatch");
println!(" MATCH\n");
}
// --- get_tx_out (genesis coinbase, likely unspendable but test the call) ---
{
let block = bc.get_block(&genesis_hash).unwrap();
let coinbase_txid = block.txdata[0].compute_txid();
let (t1, r1) = timed(|| bc.get_tx_out(&coinbase_txid, 0, Some(false)));
let (t2, r2) = timed(|| cp.get_tx_out(&coinbase_txid, 0, Some(false)));
let r1 = r1.unwrap();
let r2 = r2.unwrap();
println!("get_tx_out(genesis coinbase, vout=0):");
match (&r1, &r2) {
(Some(a), Some(b)) => {
println!(
" bitcoincore: coinbase={} value={:?} ({t1:?})",
a.coinbase, a.value
);
println!(
" corepc: coinbase={} value={:?} ({t2:?})",
b.coinbase, b.value
);
assert_eq!(a.coinbase, b.coinbase, "coinbase mismatch");
assert_eq!(a.value, b.value, "value mismatch");
assert_eq!(a.script_pub_key, b.script_pub_key, "script mismatch");
println!(" MATCH");
}
(None, None) => {
println!(" both: None (spent) ({t1:?} / {t2:?})");
println!(" MATCH");
}
_ => {
println!(" MISMATCH: bitcoincore={r1:?}, corepc={r2:?}");
panic!("get_tx_out mismatch");
}
}
println!();
}
println!("=== All checks passed ===");
}
#[cfg(all(feature = "bitcoincore-rpc", feature = "corepc"))]
fn timed<T>(f: impl FnOnce() -> T) -> (Duration, T) {
let start = Instant::now();
let result = f();
(start.elapsed(), result)
}