]> Untitled Git - bdk/commitdiff
test(electrum): add `criterion` benchmark for `sync`
authorWei Chen <wzc110@gmail.com>
Sun, 25 May 2025 18:28:19 +0000 (18:28 +0000)
committerWei Chen <wzc110@gmail.com>
Thu, 26 Jun 2025 17:18:45 +0000 (17:18 +0000)
crates/electrum/Cargo.toml
crates/electrum/benches/test_sync.rs [new file with mode: 0644]
crates/electrum/tests/test_electrum.rs

index a6b619eb15d18d1fed30cfee6e5700194da01c9a..95982eefa4f05f7477644766ba06c11e6678606c 100644 (file)
@@ -19,6 +19,7 @@ electrum-client = { version = "0.23.1", features = [ "proxy" ], default-features
 [dev-dependencies]
 bdk_testenv = { path = "../testenv" }
 bdk_chain = { path = "../chain" }
+criterion = { version = "0.2" }
 
 [features]
 default = ["use-rustls"]
@@ -29,3 +30,7 @@ use-openssl = ["electrum-client/use-openssl"]
 [[test]]
 name = "test_electrum"
 required-features = ["use-rustls"]
+
+[[bench]]
+name = "test_sync"
+harness = false
diff --git a/crates/electrum/benches/test_sync.rs b/crates/electrum/benches/test_sync.rs
new file mode 100644 (file)
index 0000000..8a0191f
--- /dev/null
@@ -0,0 +1,111 @@
+use bdk_chain::{
+    bitcoin::{Address, Amount, ScriptBuf},
+    local_chain::LocalChain,
+    spk_client::{SyncRequest, SyncResponse},
+    spk_txout::SpkTxOutIndex,
+    ConfirmationBlockTime, IndexedTxGraph, Indexer, Merge,
+};
+use bdk_core::bitcoin::{
+    key::{Secp256k1, UntweakedPublicKey},
+    Network,
+};
+use bdk_electrum::BdkElectrumClient;
+use bdk_testenv::{anyhow, bitcoincore_rpc::RpcApi, TestEnv};
+use criterion::{criterion_group, criterion_main, Criterion};
+use std::time::Duration;
+
+// Batch size for `sync_with_electrum`.
+const BATCH_SIZE: usize = 5;
+
+pub fn get_test_spk() -> ScriptBuf {
+    const PK_BYTES: &[u8] = &[
+        12, 244, 72, 4, 163, 4, 211, 81, 159, 82, 153, 123, 125, 74, 142, 40, 55, 237, 191, 231,
+        31, 114, 89, 165, 83, 141, 8, 203, 93, 240, 53, 101,
+    ];
+    let secp = Secp256k1::new();
+    let pk = UntweakedPublicKey::from_slice(PK_BYTES).expect("Must be valid PK");
+    ScriptBuf::new_p2tr(&secp, pk, None)
+}
+
+fn sync_with_electrum<I, Spks>(
+    client: &BdkElectrumClient<electrum_client::Client>,
+    spks: Spks,
+    chain: &mut LocalChain,
+    graph: &mut IndexedTxGraph<ConfirmationBlockTime, I>,
+) -> anyhow::Result<SyncResponse>
+where
+    I: Indexer,
+    I::ChangeSet: Default + Merge,
+    Spks: IntoIterator<Item = ScriptBuf>,
+    Spks::IntoIter: ExactSizeIterator + Send + 'static,
+{
+    let update = client.sync(
+        SyncRequest::builder().chain_tip(chain.tip()).spks(spks),
+        BATCH_SIZE,
+        true,
+    )?;
+
+    assert!(
+        !update.tx_update.txs.is_empty(),
+        "expected some transactions from sync, but got none"
+    );
+
+    if let Some(chain_update) = update.chain_update.clone() {
+        let _ = chain
+            .apply_update(chain_update)
+            .map_err(|err| anyhow::anyhow!("LocalChain update error: {:?}", err))?;
+    }
+    let _ = graph.apply_update(update.tx_update.clone());
+
+    Ok(update)
+}
+
+pub fn test_sync_performance(c: &mut Criterion) {
+    let env = TestEnv::new().unwrap();
+    let electrum_client = electrum_client::Client::new(env.electrsd.electrum_url.as_str()).unwrap();
+    let client = BdkElectrumClient::new(electrum_client);
+
+    const NUM_BLOCKS: usize = 100;
+    let mut spks = Vec::with_capacity(NUM_BLOCKS);
+
+    // Mine some blocks and send transactions.
+    env.mine_blocks(101, None).unwrap();
+
+    // Scatter UTXOs across many blocks.
+    for _ in 0..NUM_BLOCKS {
+        let spk = get_test_spk();
+        let addr = Address::from_script(&spk, Network::Regtest).unwrap();
+        env.send(&addr, Amount::from_sat(10_000)).unwrap();
+        env.mine_blocks(1, None).unwrap();
+
+        spks.push(spk);
+    }
+    let _ = env.wait_until_electrum_sees_block(Duration::from_secs(6));
+
+    // Setup receiver.
+    let genesis = env.bitcoind.client.get_block_hash(0).unwrap();
+    let (chain, _) = LocalChain::from_genesis_hash(genesis);
+    let graph = IndexedTxGraph::<ConfirmationBlockTime, _>::new({
+        let mut idx = SpkTxOutIndex::default();
+        idx.insert_spk((), spks[0].clone());
+        idx
+    });
+
+    c.bench_function("sync_with_electrum", move |b| {
+        b.iter(|| {
+            let spks = spks.clone();
+            let mut recv_chain = chain.clone();
+            let mut recv_graph = graph.clone();
+
+            let _ = sync_with_electrum(&client, spks, &mut recv_chain, &mut recv_graph);
+        })
+    });
+}
+
+criterion_group! {
+    name = benches;
+    config = Criterion::default()
+        .sample_size(10);
+    targets = test_sync_performance
+}
+criterion_main!(benches);
index 1973b456e3b1efec063b06a83f1f69ae5347c207..5302e62f271a51fd97cea0dc626a3c08e1c0931e 100644 (file)
@@ -20,7 +20,6 @@ use core::time::Duration;
 use electrum_client::ElectrumApi;
 use std::collections::{BTreeSet, HashMap, HashSet};
 use std::str::FromStr;
-use std::time::Instant;
 
 // Batch size for `sync_with_electrum`.
 const BATCH_SIZE: usize = 5;
@@ -882,51 +881,3 @@ fn test_check_fee_calculation() -> anyhow::Result<()> {
     }
     Ok(())
 }
-
-#[test]
-pub fn test_sync_performance() -> anyhow::Result<()> {
-    const EXPECTED_MAX_SYNC_TIME: Duration = Duration::from_secs(15);
-    const NUM_ADDRESSES: usize = 1000;
-
-    let env = TestEnv::new()?;
-    let electrum_client = electrum_client::Client::new(env.electrsd.electrum_url.as_str())?;
-    let client = BdkElectrumClient::new(electrum_client);
-
-    // Generate test addresses.
-    let mut spks = Vec::with_capacity(NUM_ADDRESSES);
-    for _ in 0..NUM_ADDRESSES {
-        spks.push(get_test_spk());
-    }
-
-    // Mine some blocks and send transactions.
-    env.mine_blocks(101, None)?;
-    for spk in spks.iter().take(10) {
-        let addr = Address::from_script(spk, Network::Regtest)?;
-        env.send(&addr, Amount::from_sat(10_000))?;
-    }
-    env.mine_blocks(1, None)?;
-
-    // Setup receiver.
-    let (mut recv_chain, _) = LocalChain::from_genesis_hash(env.bitcoind.client.get_block_hash(0)?);
-    let mut recv_graph = IndexedTxGraph::<ConfirmationBlockTime, _>::new({
-        let mut recv_index = SpkTxOutIndex::default();
-        for spk in spks.iter() {
-            recv_index.insert_spk((), spk.clone());
-        }
-        recv_index
-    });
-
-    // Measure sync time.
-    let start = Instant::now();
-    let _ = sync_with_electrum(&client, spks.clone(), &mut recv_chain, &mut recv_graph)?;
-    let sync_duration = start.elapsed();
-
-    assert!(
-        sync_duration <= EXPECTED_MAX_SYNC_TIME,
-        "Sync took {:?}, which is longer than expected {:?}",
-        sync_duration,
-        EXPECTED_MAX_SYNC_TIME
-    );
-
-    Ok(())
-}