Skip to content

Commit 9291422

Browse files
committed
perf(cbf): filter matched block txs by script relevance
Only include transactions whose outputs pay to watched scripts or whose inputs spend known outpoints, instead of pushing every tx from a matched block into BDK's TxGraph. On mainnet blocks with thousands of txs this avoids significant memory and CPU overhead during wide filter scans.
1 parent 2533c29 commit 9291422

1 file changed

Lines changed: 37 additions & 9 deletions

File tree

src/chain/cbf.rs

Lines changed: 37 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
// http://opensource.org/licenses/MIT>, at your option. You may not use this file except in
66
// accordance with one or both of these licenses.
77

8-
use std::collections::{BTreeMap, HashMap};
8+
use std::collections::{BTreeMap, HashMap, HashSet};
99
use std::net::SocketAddr;
1010
use std::sync::atomic::{AtomicU32, Ordering};
1111
use std::sync::{Arc, Mutex, RwLock};
@@ -19,7 +19,7 @@ use bip157::{
1919
SyncUpdate, TrustedPeer, Warning,
2020
};
2121
use bitcoin::constants::SUBSIDY_HALVING_INTERVAL;
22-
use bitcoin::{Amount, FeeRate, Network, Script, ScriptBuf, Transaction, Txid};
22+
use bitcoin::{Amount, FeeRate, Network, OutPoint, Script, ScriptBuf, Transaction, Txid};
2323
use electrum_client::ElectrumApi;
2424
use lightning::chain::{Confirm, WatchedOutput};
2525
use lightning::util::ser::Writeable;
@@ -705,6 +705,7 @@ impl CbfChainSource {
705705
all_scripts.extend(self.registered_scripts.lock().unwrap().iter().cloned());
706706
let skip_height =
707707
onchain_wallet.latest_checkpoint().height().checked_sub(REORG_SAFETY_BLOCKS);
708+
let script_set: HashSet<ScriptBuf> = all_scripts.iter().cloned().collect();
708709
let (sync_update, matched) = self.run_filter_scan(all_scripts, skip_height).await?;
709710

710711
log_debug!(
@@ -713,10 +714,25 @@ impl CbfChainSource {
713714
matched.len()
714715
);
715716

716-
// Fetch matching blocks and include all their transactions.
717-
// The compact block filter already matched our scripts (covering both
718-
// created outputs and spent inputs), so we include every transaction
719-
// from matched blocks and let BDK determine relevance.
717+
// Fetch matching blocks and include only script-relevant transactions.
718+
// The compact filter gives us block-level matches; here we narrow to
719+
// individual transactions that create or spend outputs to watched
720+
// scripts. This avoids pushing thousands of irrelevant txs into BDK's
721+
// TxGraph on wide filter scans.
722+
723+
// Seed the known-outpoint set with outputs from BDK's existing tx
724+
// graph that pay to our scripts, so we can also detect txs that
725+
// *spend* our UTXOs (not just receive to them).
726+
let mut known_outpoints: HashSet<OutPoint> = HashSet::new();
727+
for cached_tx in onchain_wallet.get_cached_txs() {
728+
let cached_txid = cached_tx.compute_txid();
729+
for (vout, output) in cached_tx.output.iter().enumerate() {
730+
if script_set.contains(&output.script_pubkey) {
731+
known_outpoints.insert(OutPoint::new(cached_txid, vout as u32));
732+
}
733+
}
734+
}
735+
720736
let mut tx_update = TxUpdate::default();
721737
let per_request_timeout =
722738
Duration::from_secs(self.sync_config.timeouts_config.per_request_timeout_secs.into());
@@ -737,9 +753,21 @@ impl CbfChainSource {
737753
let conf_time =
738754
ConfirmationBlockTime { block_id, confirmation_time: block.header.time as u64 };
739755
for tx in &block.txdata {
740-
let txid = tx.compute_txid();
741-
tx_update.txs.push(Arc::new(tx.clone()));
742-
tx_update.anchors.insert((conf_time, txid));
756+
let dominated = tx.output.iter().any(|o| script_set.contains(&o.script_pubkey));
757+
let spends_known =
758+
tx.input.iter().any(|i| known_outpoints.contains(&i.previous_output));
759+
760+
if dominated || spends_known {
761+
let txid = tx.compute_txid();
762+
tx_update.txs.push(Arc::new(tx.clone()));
763+
tx_update.anchors.insert((conf_time, txid));
764+
765+
for (vout, output) in tx.output.iter().enumerate() {
766+
if script_set.contains(&output.script_pubkey) {
767+
known_outpoints.insert(OutPoint::new(txid, vout as u32));
768+
}
769+
}
770+
}
743771
}
744772
}
745773

0 commit comments

Comments
 (0)