diff --git a/crates/bitcoind_rpc/Cargo.toml b/crates/bitcoind_rpc/Cargo.toml index 572d1f5230..47260b9dae 100644 --- a/crates/bitcoind_rpc/Cargo.toml +++ b/crates/bitcoind_rpc/Cargo.toml @@ -24,6 +24,7 @@ bdk_core = { path = "../core", version = "0.6.1", default-features = false } bdk_bitcoind_rpc = { path = "." } bdk_testenv = { path = "../testenv" } bdk_chain = { path = "../chain" } +criterion = { version = "0.7" } [features] default = ["std"] @@ -33,3 +34,7 @@ serde = ["bitcoin/serde", "bdk_core/serde"] [[example]] name = "filter_iter" required-features = ["std"] + +[[bench]] +name = "txid_check" +harness = false diff --git a/crates/bitcoind_rpc/benches/txid_check.rs b/crates/bitcoind_rpc/benches/txid_check.rs new file mode 100644 index 0000000000..37b66bea4d --- /dev/null +++ b/crates/bitcoind_rpc/benches/txid_check.rs @@ -0,0 +1,60 @@ +use bitcoin::consensus::encode; +use bitcoin::hashes::Hash; +use bitcoin::{ + absolute::LockTime, transaction::Version, Amount, OutPoint, ScriptBuf, Sequence, Transaction, + TxIn, TxOut, Txid, Witness, +}; +use criterion::{criterion_group, criterion_main, Criterion}; +use std::hint::black_box; + +fn make_tx(n_in: usize, n_out: usize) -> Transaction { + let mut w = Witness::new(); + w.push(vec![0u8; 72]); + w.push(vec![0u8; 33]); + Transaction { + version: Version::TWO, + lock_time: LockTime::ZERO, + input: (0..n_in) + .map(|k| TxIn { + previous_output: OutPoint { + txid: Txid::from_byte_array([k as u8 + 1; 32]), + vout: 0, + }, + script_sig: ScriptBuf::new(), + sequence: Sequence::MAX, + witness: w.clone(), + }) + .collect(), + output: (0..n_out) + .map(|k| TxOut { + value: Amount::from_sat(1000 + k as u64), + script_pubkey: ScriptBuf::from(vec![0u8; 22]), + }) + .collect(), + } +} + +fn bench(c: &mut Criterion) { + for (label, i, o) in [ + ("small_1in_2out", 1, 2), + ("typical_2in_2out", 2, 2), + ("large_10in_10out", 10, 10), + ] { + let tx = make_tx(i, o); + let ser = encode::serialize(&tx); + let mut g = c.benchmark_group(label); + g.bench_function("compute_txid", |b| { + b.iter(|| black_box(black_box(&tx).compute_txid())) + }); + g.bench_function("deserialize", |b| { + b.iter(|| { + let t: Transaction = encode::deserialize(black_box(&ser)).unwrap(); + black_box(t); + }) + }); + g.finish(); + } +} + +criterion_group!(benches, bench); +criterion_main!(benches); diff --git a/crates/bitcoind_rpc/tests/perf_check.rs b/crates/bitcoind_rpc/tests/perf_check.rs new file mode 100644 index 0000000000..d06145f46f --- /dev/null +++ b/crates/bitcoind_rpc/tests/perf_check.rs @@ -0,0 +1,64 @@ +use bdk_bitcoind_rpc::bitcoincore_rpc::{self, RpcApi}; +use bdk_testenv::{anyhow, TestEnv}; +use bitcoin::{hashes::Hash, Address, Amount, ScriptBuf, WScriptHash}; +use std::time::Instant; + +/// Measures the marginal cost of the `compute_txid()` verification against the +/// `get_raw_transaction` fetch it accompanies, using a real regtest node. +#[allow(clippy::print_stdout)] +#[test] +fn measure_txid_check_cost() -> anyhow::Result<()> { + let env = TestEnv::new()?; + let client = bitcoincore_rpc::Client::new( + &env.bitcoind.rpc_url(), + bitcoincore_rpc::Auth::CookieFile(env.bitcoind.params.cookie_file.clone()), + )?; + env.mine_blocks(500, None)?; + + let addr = Address::from_script( + &ScriptBuf::new_p2wsh(&WScriptHash::all_zeros()), + bitcoin::Network::Regtest, + )?; + let mut txids = Vec::new(); + for _ in 0..500 { + match env.send(&addr, Amount::from_sat(1_000)) { + Ok(txid) => txids.push(txid), + Err(_) => break, + } + } + let n = txids.len(); + assert!(n > 0); + + // Fetch phase: exactly what mempool_at pays per new tx (RPC + hex + deserialize). + let t = Instant::now(); + let mut txs = Vec::with_capacity(n); + for txid in &txids { + txs.push(client.get_raw_transaction(txid, None)?); + } + let rpc = t.elapsed(); + + // The verification we added. + let t = Instant::now(); + let mut mismatches = 0usize; + for (txid, tx) in txids.iter().zip(&txs) { + if tx.compute_txid() != *txid { + mismatches += 1; + } + } + let check = t.elapsed(); + + println!("\n==== txid-check perf (n={n} mempool txs, localhost regtest) ===="); + println!( + "get_raw_transaction : {rpc:?} ({:.1} us/tx)", + rpc.as_secs_f64() * 1e6 / n as f64 + ); + println!( + "txid check : {check:?} ({:.3} us/tx)", + check.as_secs_f64() * 1e6 / n as f64 + ); + println!( + "check as % of fetch : {:.3}% (mismatches={mismatches})", + check.as_secs_f64() / rpc.as_secs_f64() * 100.0 + ); + Ok(()) +}