]> Untitled Git - bdk/commitdiff
feat(chain)!: Add ability to modify canonicalization algorithm
author志宇 <hello@evanlinjin.me>
Thu, 23 Jan 2025 04:16:46 +0000 (15:16 +1100)
committer志宇 <hello@evanlinjin.me>
Thu, 1 May 2025 05:24:55 +0000 (15:24 +1000)
Introduce `CanonicalizationParams` which is passed in to
`CanonicalIter::new`.

`CanonicalizationParams::assume_canonical` is the only field right now.
This contains a list of txids that we assume to be canonical,
superceding any other canonicalization rules.

14 files changed:
crates/bitcoind_rpc/examples/filter_iter.rs
crates/bitcoind_rpc/tests/test_emitter.rs
crates/chain/benches/canonicalization.rs
crates/chain/src/canonical_iter.rs
crates/chain/src/tx_graph.rs
crates/chain/tests/common/tx_template.rs
crates/chain/tests/test_indexed_tx_graph.rs
crates/chain/tests/test_tx_graph.rs
crates/chain/tests/test_tx_graph_conflicts.rs
crates/electrum/tests/test_electrum.rs
examples/example_bitcoind_rpc_polling/src/main.rs
examples/example_cli/src/lib.rs
examples/example_electrum/src/main.rs
examples/example_esplora/src/main.rs

index 55c3325d70a542186b51554588386f9376d75e7c..21300e700e13b9de6768a1317581b1548e819cad 100644 (file)
@@ -92,6 +92,7 @@ fn main() -> anyhow::Result<()> {
         .filter_chain_unspents(
             &chain,
             chain.tip().block_id(),
+            Default::default(),
             graph.index.outpoints().clone(),
         )
         .collect();
index 14b0c9212234dd9ea169140ec6db1c36df6e8e83..5753b82f879d89fe356f66a472c38406bb9a10c2 100644 (file)
@@ -5,7 +5,7 @@ use bdk_chain::{
     bitcoin::{Address, Amount, Txid},
     local_chain::{CheckPoint, LocalChain},
     spk_txout::SpkTxOutIndex,
-    Balance, BlockId, IndexedTxGraph, Merge,
+    Balance, BlockId, CanonicalizationParams, IndexedTxGraph, Merge,
 };
 use bdk_testenv::{anyhow, TestEnv};
 use bitcoin::{hashes::Hash, Block, OutPoint, ScriptBuf, WScriptHash};
@@ -306,9 +306,13 @@ fn get_balance(
 ) -> anyhow::Result<Balance> {
     let chain_tip = recv_chain.tip().block_id();
     let outpoints = recv_graph.index.outpoints().clone();
-    let balance = recv_graph
-        .graph()
-        .balance(recv_chain, chain_tip, outpoints, |_, _| true);
+    let balance = recv_graph.graph().balance(
+        recv_chain,
+        chain_tip,
+        CanonicalizationParams::default(),
+        outpoints,
+        |_, _| true,
+    );
     Ok(balance)
 }
 
index 6893e6df885c6209bb7a89b10b0be622f48d8306..d425ecc6c00c414c4f9f2e43d24c7d4701627801 100644 (file)
@@ -1,3 +1,4 @@
+use bdk_chain::CanonicalizationParams;
 use bdk_chain::{keychain_txout::KeychainTxOutIndex, local_chain::LocalChain, IndexedTxGraph};
 use bdk_core::{BlockId, CheckPoint};
 use bdk_core::{ConfirmationBlockTime, TxUpdate};
@@ -90,9 +91,11 @@ fn setup<F: Fn(&mut KeychainTxGraph, &LocalChain)>(f: F) -> (KeychainTxGraph, Lo
 }
 
 fn run_list_canonical_txs(tx_graph: &KeychainTxGraph, chain: &LocalChain, exp_txs: usize) {
-    let txs = tx_graph
-        .graph()
-        .list_canonical_txs(chain, chain.tip().block_id());
+    let txs = tx_graph.graph().list_canonical_txs(
+        chain,
+        chain.tip().block_id(),
+        CanonicalizationParams::default(),
+    );
     assert_eq!(txs.count(), exp_txs);
 }
 
@@ -100,6 +103,7 @@ fn run_filter_chain_txouts(tx_graph: &KeychainTxGraph, chain: &LocalChain, exp_t
     let utxos = tx_graph.graph().filter_chain_txouts(
         chain,
         chain.tip().block_id(),
+        CanonicalizationParams::default(),
         tx_graph.index.outpoints().clone(),
     );
     assert_eq!(utxos.count(), exp_txos);
@@ -109,6 +113,7 @@ fn run_filter_chain_unspents(tx_graph: &KeychainTxGraph, chain: &LocalChain, exp
     let utxos = tx_graph.graph().filter_chain_unspents(
         chain,
         chain.tip().block_id(),
+        CanonicalizationParams::default(),
         tx_graph.index.outpoints().clone(),
     );
     assert_eq!(utxos.count(), exp_utxos);
index 837af4c9b913622c399a23bcb4b62bc11727634f..58f266f89102db49eab588acef62f1643442433a 100644 (file)
@@ -11,12 +11,23 @@ use bitcoin::{Transaction, Txid};
 type CanonicalMap<A> = HashMap<Txid, (Arc<Transaction>, CanonicalReason<A>)>;
 type NotCanonicalSet = HashSet<Txid>;
 
+/// Modifies the canonicalization algorithm.
+#[derive(Debug, Default, Clone)]
+pub struct CanonicalizationParams {
+    /// Transactions that will supercede all other transactions.
+    ///
+    /// In case of conflicting transactions within `assume_canonical`, transactions that appear
+    /// later in the list (have higher index) have precedence.
+    pub assume_canonical: Vec<Txid>,
+}
+
 /// Iterates over canonical txs.
 pub struct CanonicalIter<'g, A, C> {
     tx_graph: &'g TxGraph<A>,
     chain: &'g C,
     chain_tip: BlockId,
 
+    unprocessed_assumed_txs: Box<dyn Iterator<Item = (Txid, Arc<Transaction>)> + 'g>,
     unprocessed_anchored_txs:
         Box<dyn Iterator<Item = (Txid, Arc<Transaction>, &'g BTreeSet<A>)> + 'g>,
     unprocessed_seen_txs: Box<dyn Iterator<Item = (Txid, Arc<Transaction>, u64)> + 'g>,
@@ -30,8 +41,20 @@ pub struct CanonicalIter<'g, A, C> {
 
 impl<'g, A: Anchor, C: ChainOracle> CanonicalIter<'g, A, C> {
     /// Constructs [`CanonicalIter`].
-    pub fn new(tx_graph: &'g TxGraph<A>, chain: &'g C, chain_tip: BlockId) -> Self {
+    pub fn new(
+        tx_graph: &'g TxGraph<A>,
+        chain: &'g C,
+        chain_tip: BlockId,
+        params: CanonicalizationParams,
+    ) -> Self {
         let anchors = tx_graph.all_anchors();
+        let unprocessed_assumed_txs = Box::new(
+            params
+                .assume_canonical
+                .into_iter()
+                .rev()
+                .filter_map(|txid| Some((txid, tx_graph.get_tx(txid)?))),
+        );
         let unprocessed_anchored_txs = Box::new(
             tx_graph
                 .txids_by_descending_anchor_height()
@@ -46,6 +69,7 @@ impl<'g, A: Anchor, C: ChainOracle> CanonicalIter<'g, A, C> {
             tx_graph,
             chain,
             chain_tip,
+            unprocessed_assumed_txs,
             unprocessed_anchored_txs,
             unprocessed_seen_txs,
             unprocessed_leftover_txs: VecDeque::new(),
@@ -190,6 +214,12 @@ impl<A: Anchor, C: ChainOracle> Iterator for CanonicalIter<'_, A, C> {
                 return Some(Ok((txid, tx, reason)));
             }
 
+            if let Some((txid, tx)) = self.unprocessed_assumed_txs.next() {
+                if !self.is_canonicalized(txid) {
+                    self.mark_canonical(txid, tx, CanonicalReason::assumed());
+                }
+            }
+
             if let Some((txid, tx, anchors)) = self.unprocessed_anchored_txs.next() {
                 if !self.is_canonicalized(txid) {
                     if let Err(err) = self.scan_anchors(txid, tx, anchors) {
@@ -232,6 +262,12 @@ pub enum ObservedIn {
 /// The reason why a transaction is canonical.
 #[derive(Debug, Clone, PartialEq, Eq)]
 pub enum CanonicalReason<A> {
+    /// This transaction is explicitly assumed to be canonical by the caller, superceding all other
+    /// canonicalization rules.
+    Assumed {
+        /// Whether it is a descendant that is assumed to be canonical.
+        descendant: Option<Txid>,
+    },
     /// This transaction is anchored in the best chain by `A`, and therefore canonical.
     Anchor {
         /// The anchor that anchored the transaction in the chain.
@@ -250,6 +286,12 @@ pub enum CanonicalReason<A> {
 }
 
 impl<A: Clone> CanonicalReason<A> {
+    /// Constructs a [`CanonicalReason`] for a transaction that is assumed to supercede all other
+    /// transactions.
+    pub fn assumed() -> Self {
+        Self::Assumed { descendant: None }
+    }
+
     /// Constructs a [`CanonicalReason`] from an `anchor`.
     pub fn from_anchor(anchor: A) -> Self {
         Self::Anchor {
@@ -272,6 +314,9 @@ impl<A: Clone> CanonicalReason<A> {
     /// descendant, but is transitively relevant.
     pub fn to_transitive(&self, descendant: Txid) -> Self {
         match self {
+            CanonicalReason::Assumed { .. } => Self::Assumed {
+                descendant: Some(descendant),
+            },
             CanonicalReason::Anchor { anchor, .. } => Self::Anchor {
                 anchor: anchor.clone(),
                 descendant: Some(descendant),
@@ -287,6 +332,7 @@ impl<A: Clone> CanonicalReason<A> {
     /// descendant.
     pub fn descendant(&self) -> &Option<Txid> {
         match self {
+            CanonicalReason::Assumed { descendant, .. } => descendant,
             CanonicalReason::Anchor { descendant, .. } => descendant,
             CanonicalReason::ObservedIn { descendant, .. } => descendant,
         }
index 3e6dc866d87926ba097e746110df7de113e44b61..2358b188561e384e4a9e4ac5b58757638511a267 100644 (file)
@@ -124,6 +124,7 @@ use crate::spk_txout::SpkTxOutIndex;
 use crate::BlockId;
 use crate::CanonicalIter;
 use crate::CanonicalReason;
+use crate::CanonicalizationParams;
 use crate::ObservedIn;
 use crate::{Anchor, Balance, ChainOracle, ChainPosition, FullTxOut, Merge};
 use alloc::collections::vec_deque::VecDeque;
@@ -914,25 +915,46 @@ impl<A: Anchor> TxGraph<A> {
         &'a self,
         chain: &'a C,
         chain_tip: BlockId,
+        params: CanonicalizationParams,
     ) -> impl Iterator<Item = Result<CanonicalTx<'a, Arc<Transaction>, A>, C::Error>> {
-        self.canonical_iter(chain, chain_tip).flat_map(move |res| {
-            res.map(|(txid, _, canonical_reason)| {
-                let tx_node = self.get_tx_node(txid).expect("must contain tx");
-                let chain_position = match canonical_reason {
-                    CanonicalReason::Anchor { anchor, descendant } => match descendant {
-                        Some(_) => {
-                            let direct_anchor = tx_node
-                                .anchors
-                                .iter()
-                                .find_map(|a| -> Option<Result<A, C::Error>> {
-                                    match chain.is_block_in_chain(a.anchor_block(), chain_tip) {
-                                        Ok(Some(true)) => Some(Ok(a.clone())),
-                                        Ok(Some(false)) | Ok(None) => None,
-                                        Err(err) => Some(Err(err)),
-                                    }
-                                })
-                                .transpose()?;
-                            match direct_anchor {
+        fn find_direct_anchor<A: Anchor, C: ChainOracle>(
+            tx_node: &TxNode<'_, Arc<Transaction>, A>,
+            chain: &C,
+            chain_tip: BlockId,
+        ) -> Result<Option<A>, C::Error> {
+            tx_node
+                .anchors
+                .iter()
+                .find_map(|a| -> Option<Result<A, C::Error>> {
+                    match chain.is_block_in_chain(a.anchor_block(), chain_tip) {
+                        Ok(Some(true)) => Some(Ok(a.clone())),
+                        Ok(Some(false)) | Ok(None) => None,
+                        Err(err) => Some(Err(err)),
+                    }
+                })
+                .transpose()
+        }
+        self.canonical_iter(chain, chain_tip, params)
+            .flat_map(move |res| {
+                res.map(|(txid, _, canonical_reason)| {
+                    let tx_node = self.get_tx_node(txid).expect("must contain tx");
+                    let chain_position = match canonical_reason {
+                        CanonicalReason::Assumed { descendant } => match descendant {
+                            Some(_) => match find_direct_anchor(&tx_node, chain, chain_tip)? {
+                                Some(anchor) => ChainPosition::Confirmed {
+                                    anchor,
+                                    transitively: None,
+                                },
+                                None => ChainPosition::Unconfirmed {
+                                    last_seen: tx_node.last_seen_unconfirmed,
+                                },
+                            },
+                            None => ChainPosition::Unconfirmed {
+                                last_seen: tx_node.last_seen_unconfirmed,
+                            },
+                        },
+                        CanonicalReason::Anchor { anchor, descendant } => match descendant {
+                            Some(_) => match find_direct_anchor(&tx_node, chain, chain_tip)? {
                                 Some(anchor) => ChainPosition::Confirmed {
                                     anchor,
                                     transitively: None,
@@ -941,26 +963,25 @@ impl<A: Anchor> TxGraph<A> {
                                     anchor,
                                     transitively: descendant,
                                 },
-                            }
-                        }
-                        None => ChainPosition::Confirmed {
-                            anchor,
-                            transitively: None,
+                            },
+                            None => ChainPosition::Confirmed {
+                                anchor,
+                                transitively: None,
+                            },
                         },
-                    },
-                    CanonicalReason::ObservedIn { observed_in, .. } => match observed_in {
-                        ObservedIn::Mempool(last_seen) => ChainPosition::Unconfirmed {
-                            last_seen: Some(last_seen),
+                        CanonicalReason::ObservedIn { observed_in, .. } => match observed_in {
+                            ObservedIn::Mempool(last_seen) => ChainPosition::Unconfirmed {
+                                last_seen: Some(last_seen),
+                            },
+                            ObservedIn::Block(_) => ChainPosition::Unconfirmed { last_seen: None },
                         },
-                        ObservedIn::Block(_) => ChainPosition::Unconfirmed { last_seen: None },
-                    },
-                };
-                Ok(CanonicalTx {
-                    chain_position,
-                    tx_node,
+                    };
+                    Ok(CanonicalTx {
+                        chain_position,
+                        tx_node,
+                    })
                 })
             })
-        })
     }
 
     /// List graph transactions that are in `chain` with `chain_tip`.
@@ -972,8 +993,9 @@ impl<A: Anchor> TxGraph<A> {
         &'a self,
         chain: &'a C,
         chain_tip: BlockId,
+        params: CanonicalizationParams,
     ) -> impl Iterator<Item = CanonicalTx<'a, Arc<Transaction>, A>> {
-        self.try_list_canonical_txs(chain, chain_tip)
+        self.try_list_canonical_txs(chain, chain_tip, params)
             .map(|res| res.expect("infallible"))
     }
 
@@ -1000,11 +1022,12 @@ impl<A: Anchor> TxGraph<A> {
         &'a self,
         chain: &'a C,
         chain_tip: BlockId,
+        params: CanonicalizationParams,
         outpoints: impl IntoIterator<Item = (OI, OutPoint)> + 'a,
     ) -> Result<impl Iterator<Item = (OI, FullTxOut<A>)> + 'a, C::Error> {
         let mut canon_txs = HashMap::<Txid, CanonicalTx<Arc<Transaction>, A>>::new();
         let mut canon_spends = HashMap::<OutPoint, Txid>::new();
-        for r in self.try_list_canonical_txs(chain, chain_tip) {
+        for r in self.try_list_canonical_txs(chain, chain_tip, params) {
             let canonical_tx = r?;
             let txid = canonical_tx.tx_node.txid;
 
@@ -1081,8 +1104,9 @@ impl<A: Anchor> TxGraph<A> {
         &'a self,
         chain: &'a C,
         chain_tip: BlockId,
+        params: CanonicalizationParams,
     ) -> CanonicalIter<'a, A, C> {
-        CanonicalIter::new(self, chain, chain_tip)
+        CanonicalIter::new(self, chain, chain_tip, params)
     }
 
     /// Get a filtered list of outputs from the given `outpoints` that are in `chain` with
@@ -1095,9 +1119,10 @@ impl<A: Anchor> TxGraph<A> {
         &'a self,
         chain: &'a C,
         chain_tip: BlockId,
+        params: CanonicalizationParams,
         outpoints: impl IntoIterator<Item = (OI, OutPoint)> + 'a,
     ) -> impl Iterator<Item = (OI, FullTxOut<A>)> + 'a {
-        self.try_filter_chain_txouts(chain, chain_tip, outpoints)
+        self.try_filter_chain_txouts(chain, chain_tip, params, outpoints)
             .expect("oracle is infallible")
     }
 
@@ -1123,10 +1148,11 @@ impl<A: Anchor> TxGraph<A> {
         &'a self,
         chain: &'a C,
         chain_tip: BlockId,
+        params: CanonicalizationParams,
         outpoints: impl IntoIterator<Item = (OI, OutPoint)> + 'a,
     ) -> Result<impl Iterator<Item = (OI, FullTxOut<A>)> + 'a, C::Error> {
         Ok(self
-            .try_filter_chain_txouts(chain, chain_tip, outpoints)?
+            .try_filter_chain_txouts(chain, chain_tip, params, outpoints)?
             .filter(|(_, full_txo)| full_txo.spent_by.is_none()))
     }
 
@@ -1140,9 +1166,10 @@ impl<A: Anchor> TxGraph<A> {
         &'a self,
         chain: &'a C,
         chain_tip: BlockId,
+        params: CanonicalizationParams,
         txouts: impl IntoIterator<Item = (OI, OutPoint)> + 'a,
     ) -> impl Iterator<Item = (OI, FullTxOut<A>)> + 'a {
-        self.try_filter_chain_unspents(chain, chain_tip, txouts)
+        self.try_filter_chain_unspents(chain, chain_tip, params, txouts)
             .expect("oracle is infallible")
     }
 
@@ -1162,6 +1189,7 @@ impl<A: Anchor> TxGraph<A> {
         &self,
         chain: &C,
         chain_tip: BlockId,
+        params: CanonicalizationParams,
         outpoints: impl IntoIterator<Item = (OI, OutPoint)>,
         mut trust_predicate: impl FnMut(&OI, ScriptBuf) -> bool,
     ) -> Result<Balance, C::Error> {
@@ -1170,7 +1198,7 @@ impl<A: Anchor> TxGraph<A> {
         let mut untrusted_pending = Amount::ZERO;
         let mut confirmed = Amount::ZERO;
 
-        for (spk_i, txout) in self.try_filter_chain_unspents(chain, chain_tip, outpoints)? {
+        for (spk_i, txout) in self.try_filter_chain_unspents(chain, chain_tip, params, outpoints)? {
             match &txout.chain_position {
                 ChainPosition::Confirmed { .. } => {
                     if txout.is_confirmed_and_spendable(chain_tip.height) {
@@ -1206,10 +1234,11 @@ impl<A: Anchor> TxGraph<A> {
         &self,
         chain: &C,
         chain_tip: BlockId,
+        params: CanonicalizationParams,
         outpoints: impl IntoIterator<Item = (OI, OutPoint)>,
         trust_predicate: impl FnMut(&OI, ScriptBuf) -> bool,
     ) -> Balance {
-        self.try_balance(chain, chain_tip, outpoints, trust_predicate)
+        self.try_balance(chain, chain_tip, params, outpoints, trust_predicate)
             .expect("oracle is infallible")
     }
 
@@ -1238,8 +1267,8 @@ impl<A: Anchor> TxGraph<A> {
         I: fmt::Debug + Clone + Ord + 'a,
     {
         let indexer = indexer.as_ref();
-        self.try_list_canonical_txs(chain, chain_tip).flat_map(
-            move |res| -> Vec<Result<(ScriptBuf, Txid), C::Error>> {
+        self.try_list_canonical_txs(chain, chain_tip, CanonicalizationParams::default())
+            .flat_map(move |res| -> Vec<Result<(ScriptBuf, Txid), C::Error>> {
                 let range = &spk_index_range;
                 let c_tx = match res {
                     Ok(c_tx) => c_tx,
@@ -1251,8 +1280,7 @@ impl<A: Anchor> TxGraph<A> {
                     .filter(|(i, _)| range.contains(i))
                     .map(|(_, spk)| Ok((spk, c_tx.tx_node.txid)))
                     .collect()
-            },
-        )
+            })
     }
 
     /// List txids that are expected to exist under the given spks.
index 0b0e2fd9e49a13c4cb5d222f8322c2d7bb25ee43..29f36169ad05a19728d26bb2cc31ef6c5043fea0 100644 (file)
@@ -4,7 +4,7 @@ use bdk_testenv::utils::DESCRIPTORS;
 use rand::distributions::{Alphanumeric, DistString};
 use std::collections::HashMap;
 
-use bdk_chain::{spk_txout::SpkTxOutIndex, tx_graph::TxGraph, Anchor};
+use bdk_chain::{spk_txout::SpkTxOutIndex, tx_graph::TxGraph, Anchor, CanonicalizationParams};
 use bitcoin::{
     locktime::absolute::LockTime, secp256k1::Secp256k1, transaction, Amount, OutPoint, ScriptBuf,
     Sequence, Transaction, TxIn, TxOut, Txid, Witness,
@@ -24,6 +24,7 @@ pub struct TxTemplate<'a, A> {
     pub outputs: &'a [TxOutTemplate],
     pub anchors: &'a [A],
     pub last_seen: Option<u64>,
+    pub assume_canonical: bool,
 }
 
 #[allow(dead_code)]
@@ -51,16 +52,24 @@ impl TxOutTemplate {
     }
 }
 
+#[allow(dead_code)]
+pub struct TxTemplateEnv<'a, A> {
+    pub tx_graph: TxGraph<A>,
+    pub indexer: SpkTxOutIndex<u32>,
+    pub txid_to_name: HashMap<&'a str, Txid>,
+    pub canonicalization_params: CanonicalizationParams,
+}
+
 #[allow(dead_code)]
 pub fn init_graph<'a, A: Anchor + Clone + 'a>(
     tx_templates: impl IntoIterator<Item = &'a TxTemplate<'a, A>>,
-) -> (TxGraph<A>, SpkTxOutIndex<u32>, HashMap<&'a str, Txid>) {
+) -> TxTemplateEnv<'a, A> {
     let (descriptor, _) =
         Descriptor::parse_descriptor(&Secp256k1::signing_only(), DESCRIPTORS[2]).unwrap();
-    let mut graph = TxGraph::<A>::default();
-    let mut spk_index = SpkTxOutIndex::default();
+    let mut tx_graph = TxGraph::<A>::default();
+    let mut indexer = SpkTxOutIndex::default();
     (0..10).for_each(|index| {
-        spk_index.insert_spk(
+        indexer.insert_spk(
             index,
             descriptor
                 .at_derivation_index(index)
@@ -68,8 +77,9 @@ pub fn init_graph<'a, A: Anchor + Clone + 'a>(
                 .script_pubkey(),
         );
     });
-    let mut tx_ids = HashMap::<&'a str, Txid>::new();
+    let mut txid_to_name = HashMap::<&'a str, Txid>::new();
 
+    let mut canonicalization_params = CanonicalizationParams::default();
     for (bogus_txin_vout, tx_tmp) in tx_templates.into_iter().enumerate() {
         let tx = Transaction {
             version: transaction::Version::non_standard(0),
@@ -98,7 +108,7 @@ pub fn init_graph<'a, A: Anchor + Clone + 'a>(
                         witness: Witness::new(),
                     },
                     TxInTemplate::PrevTx(prev_name, prev_vout) => {
-                        let prev_txid = tx_ids.get(prev_name).expect(
+                        let prev_txid = txid_to_name.get(prev_name).expect(
                             "txin template must spend from tx of template that comes before",
                         );
                         TxIn {
@@ -120,21 +130,30 @@ pub fn init_graph<'a, A: Anchor + Clone + 'a>(
                     },
                     Some(index) => TxOut {
                         value: Amount::from_sat(output.value),
-                        script_pubkey: spk_index.spk_at_index(index).unwrap(),
+                        script_pubkey: indexer.spk_at_index(index).unwrap(),
                     },
                 })
                 .collect(),
         };
 
-        tx_ids.insert(tx_tmp.tx_name, tx.compute_txid());
-        spk_index.scan(&tx);
-        let _ = graph.insert_tx(tx.clone());
+        let txid = tx.compute_txid();
+        if tx_tmp.assume_canonical {
+            canonicalization_params.assume_canonical.push(txid);
+        }
+        txid_to_name.insert(tx_tmp.tx_name, txid);
+        indexer.scan(&tx);
+        let _ = tx_graph.insert_tx(tx.clone());
         for anchor in tx_tmp.anchors.iter() {
-            let _ = graph.insert_anchor(tx.compute_txid(), anchor.clone());
+            let _ = tx_graph.insert_anchor(txid, anchor.clone());
         }
         if let Some(last_seen) = tx_tmp.last_seen {
-            let _ = graph.insert_seen_at(tx.compute_txid(), last_seen);
+            let _ = tx_graph.insert_seen_at(txid, last_seen);
         }
     }
-    (graph, spk_index, tx_ids)
+    TxTemplateEnv {
+        tx_graph,
+        indexer,
+        txid_to_name,
+        canonicalization_params,
+    }
 }
index 1e28eb6a2baf072feeb55163b5f432b8a9da686d..b84642291b8efc61baabd832b1fa0c6eef1b6e17 100644 (file)
@@ -9,7 +9,7 @@ use bdk_chain::{
     indexed_tx_graph::{self, IndexedTxGraph},
     indexer::keychain_txout::KeychainTxOutIndex,
     local_chain::LocalChain,
-    tx_graph, Balance, ChainPosition, ConfirmationBlockTime, DescriptorExt,
+    tx_graph, Balance, CanonicalizationParams, ChainPosition, ConfirmationBlockTime, DescriptorExt,
 };
 use bdk_testenv::{
     block_id, hash,
@@ -271,6 +271,7 @@ fn test_list_owned_txouts() {
                 .filter_chain_txouts(
                     &local_chain,
                     chain_tip,
+                    CanonicalizationParams::default(),
                     graph.index.outpoints().iter().cloned(),
                 )
                 .collect::<Vec<_>>();
@@ -280,6 +281,7 @@ fn test_list_owned_txouts() {
                 .filter_chain_unspents(
                     &local_chain,
                     chain_tip,
+                    CanonicalizationParams::default(),
                     graph.index.outpoints().iter().cloned(),
                 )
                 .collect::<Vec<_>>();
@@ -287,6 +289,7 @@ fn test_list_owned_txouts() {
             let balance = graph.graph().balance(
                 &local_chain,
                 chain_tip,
+                CanonicalizationParams::default(),
                 graph.index.outpoints().iter().cloned(),
                 |_, spk: ScriptBuf| trusted_spks.contains(&spk),
             );
@@ -589,7 +592,11 @@ fn test_get_chain_position() {
         // check chain position
         let chain_pos = graph
             .graph()
-            .list_canonical_txs(chain, chain.tip().block_id())
+            .list_canonical_txs(
+                chain,
+                chain.tip().block_id(),
+                CanonicalizationParams::default(),
+            )
             .find_map(|canon_tx| {
                 if canon_tx.tx_node.txid == txid {
                     Some(canon_tx.chain_position)
index a747061304057180fc8abd0fcd370ac4c4ae91bb..350d0b0dddea443d7e010b073ec2da905b73657a 100644 (file)
@@ -2,7 +2,7 @@
 
 #[macro_use]
 mod common;
-use bdk_chain::{collections::*, BlockId, ConfirmationBlockTime};
+use bdk_chain::{collections::*, BlockId, CanonicalizationParams, ConfirmationBlockTime};
 use bdk_chain::{
     local_chain::LocalChain,
     tx_graph::{self, CalculateFeeError},
@@ -955,6 +955,7 @@ fn test_chain_spends() {
                 .filter_chain_txouts(
                     chain,
                     tip.block_id(),
+                    CanonicalizationParams::default(),
                     tx_graph.all_txouts().map(|(op, _)| ((), op)),
                 )
                 .filter_map(|(_, full_txo)| Some((full_txo.outpoint, full_txo.spent_by?)))
@@ -964,7 +965,7 @@ fn test_chain_spends() {
                                      tx_graph: &TxGraph<ConfirmationBlockTime>|
      -> HashMap<Txid, ChainPosition<ConfirmationBlockTime>> {
         tx_graph
-            .list_canonical_txs(chain, tip.block_id())
+            .list_canonical_txs(chain, tip.block_id(), CanonicalizationParams::default())
             .map(|canon_tx| (canon_tx.tx_node.txid, canon_tx.chain_position))
             .collect()
     };
@@ -1133,13 +1134,21 @@ fn transactions_inserted_into_tx_graph_are_not_canonical_until_they_have_an_anch
         .collect();
     let chain = LocalChain::from_blocks(blocks).unwrap();
     let canonical_txs: Vec<_> = graph
-        .list_canonical_txs(&chain, chain.tip().block_id())
+        .list_canonical_txs(
+            &chain,
+            chain.tip().block_id(),
+            CanonicalizationParams::default(),
+        )
         .collect();
     assert!(canonical_txs.is_empty());
 
     // tx0 with seen_at should be returned by canonical txs
     let _ = graph.insert_seen_at(txids[0], 2);
-    let mut canonical_txs = graph.list_canonical_txs(&chain, chain.tip().block_id());
+    let mut canonical_txs = graph.list_canonical_txs(
+        &chain,
+        chain.tip().block_id(),
+        CanonicalizationParams::default(),
+    );
     assert_eq!(
         canonical_txs.next().map(|tx| tx.tx_node.txid).unwrap(),
         txids[0]
@@ -1149,7 +1158,11 @@ fn transactions_inserted_into_tx_graph_are_not_canonical_until_they_have_an_anch
     // tx1 with anchor is also canonical
     let _ = graph.insert_anchor(txids[1], block_id!(2, "B"));
     let canonical_txids: Vec<_> = graph
-        .list_canonical_txs(&chain, chain.tip().block_id())
+        .list_canonical_txs(
+            &chain,
+            chain.tip().block_id(),
+            CanonicalizationParams::default(),
+        )
         .map(|tx| tx.tx_node.txid)
         .collect();
     assert!(canonical_txids.contains(&txids[1]));
@@ -1211,6 +1224,7 @@ fn call_map_anchors_with_non_deterministic_anchor() {
             outputs: &[TxOutTemplate::new(10000, Some(1))],
             anchors: &[block_id!(1, "A")],
             last_seen: None,
+            ..Default::default()
         },
         TxTemplate {
             tx_name: "tx2",
@@ -1227,7 +1241,7 @@ fn call_map_anchors_with_non_deterministic_anchor() {
             ..Default::default()
         },
     ];
-    let (graph, _, _) = init_graph(&template);
+    let graph = init_graph(&template).tx_graph;
     let new_graph = graph.clone().map_anchors(|a| NonDeterministicAnchor {
         anchor_block: a,
         // A non-deterministic value
index 4de6573593ef1cc816f2eb7e48de6c96fe6c133f..862eb9b4bc148b29a77f6a51e5c68706f596b29b 100644 (file)
@@ -59,6 +59,7 @@ fn test_tx_conflict_handling() {
                     outputs: &[TxOutTemplate::new(10000, Some(1))],
                     anchors: &[block_id!(1, "B")],
                     last_seen: None,
+                    ..Default::default()
                 },
                 TxTemplate {
                     tx_name: "unconfirmed_conflict",
@@ -130,6 +131,7 @@ fn test_tx_conflict_handling() {
                     outputs: &[TxOutTemplate::new(10000, Some(0)), TxOutTemplate::new(10000, Some(1))],
                     anchors: &[block_id!(1, "B")],
                     last_seen: None,
+                    ..Default::default()
                 },
                 TxTemplate {
                     tx_name: "tx_conflict_1",
@@ -165,6 +167,7 @@ fn test_tx_conflict_handling() {
                     outputs: &[TxOutTemplate::new(10000, Some(0))],
                     anchors: &[block_id!(1, "B")],
                     last_seen: None,
+                    ..Default::default()
                 },
                 TxTemplate {
                     tx_name: "tx_conflict_1",
@@ -207,6 +210,7 @@ fn test_tx_conflict_handling() {
                     outputs: &[TxOutTemplate::new(10000, Some(0))],
                     anchors: &[block_id!(1, "B")],
                     last_seen: None,
+                    ..Default::default()
                 },
                 TxTemplate {
                     tx_name: "tx_conflict_1",
@@ -221,6 +225,7 @@ fn test_tx_conflict_handling() {
                     outputs: &[TxOutTemplate::new(30000, Some(2))],
                     anchors: &[block_id!(4, "Orphaned Block")],
                     last_seen: Some(300),
+                    ..Default::default()
                 },
             ],
             exp_chain_txs: HashSet::from(["tx1", "tx_orphaned_conflict"]),
@@ -242,6 +247,7 @@ fn test_tx_conflict_handling() {
                     outputs: &[TxOutTemplate::new(10000, Some(0))],
                     anchors: &[block_id!(1, "B")],
                     last_seen: None,
+                    ..Default::default()
                 },
                 TxTemplate {
                     tx_name: "tx_conflict_1",
@@ -256,6 +262,7 @@ fn test_tx_conflict_handling() {
                     outputs: &[TxOutTemplate::new(30000, Some(2))],
                     anchors: &[block_id!(4, "Orphaned Block")],
                     last_seen: Some(100),
+                    ..Default::default()
                 },
             ],
             exp_chain_txs: HashSet::from(["tx1", "tx_conflict_1"]),
@@ -277,6 +284,7 @@ fn test_tx_conflict_handling() {
                     outputs: &[TxOutTemplate::new(10000, Some(0))],
                     anchors: &[block_id!(1, "B")],
                     last_seen: None,
+                    ..Default::default()
                 },
                 TxTemplate {
                     tx_name: "tx_conflict_1",
@@ -371,6 +379,7 @@ fn test_tx_conflict_handling() {
                     outputs: &[TxOutTemplate::new(10000, Some(0))],
                     anchors: &[block_id!(1, "B")],
                     last_seen: None,
+                    ..Default::default()
                 },
                 TxTemplate {
                     tx_name: "B",
@@ -459,6 +468,7 @@ fn test_tx_conflict_handling() {
                     outputs: &[TxOutTemplate::new(10000, Some(0))],
                     anchors: &[block_id!(1, "B")],
                     last_seen: None,
+                    ..Default::default()
                 },
                 TxTemplate {
                     tx_name: "B",
@@ -504,6 +514,7 @@ fn test_tx_conflict_handling() {
                     outputs: &[TxOutTemplate::new(10000, Some(0))],
                     anchors: &[block_id!(1, "B")],
                     last_seen: None,
+                    ..Default::default()
                 },
                 TxTemplate {
                     tx_name: "B",
@@ -549,6 +560,7 @@ fn test_tx_conflict_handling() {
                     outputs: &[TxOutTemplate::new(10000, Some(0))],
                     anchors: &[block_id!(1, "B")],
                     last_seen: None,
+                    ..Default::default()
                 },
                 TxTemplate {
                     tx_name: "B",
@@ -791,19 +803,160 @@ fn test_tx_conflict_handling() {
             exp_unspents: HashSet::from([("B", 0)]),
             exp_balance: Balance { trusted_pending: Amount::from_sat(8_000), ..Default::default() },
         },
+        Scenario {
+            name: "assume-canonical-tx displaces unconfirmed chain",
+            tx_templates: &[
+                TxTemplate {
+                    tx_name: "root",
+                    inputs: &[TxInTemplate::Bogus],
+                    outputs: &[
+                        TxOutTemplate::new(21_000, Some(0)),
+                        TxOutTemplate::new(21_000, Some(1)),
+                    ],
+                    anchors: &[block_id!(1, "B")],
+                    ..Default::default()
+                },
+                TxTemplate {
+                    tx_name: "unconfirmed",
+                    inputs: &[TxInTemplate::PrevTx("root", 0)],
+                    outputs: &[TxOutTemplate::new(20_000, Some(1))],
+                    last_seen: Some(2),
+                    ..Default::default()
+                },
+                TxTemplate {
+                    tx_name: "unconfirmed_descendant",
+                    inputs: &[
+                        TxInTemplate::PrevTx("unconfirmed", 0),
+                        TxInTemplate::PrevTx("root", 1),
+                    ],
+                    outputs: &[TxOutTemplate::new(28_000, Some(2))],
+                    last_seen: Some(2),
+                    ..Default::default()
+                },
+                TxTemplate {
+                    tx_name: "assume_canonical",
+                    inputs: &[TxInTemplate::PrevTx("root", 0)],
+                    outputs: &[TxOutTemplate::new(19_000, Some(3))],
+                    assume_canonical: true,
+                    ..Default::default()
+                },
+            ],
+            exp_chain_txs: HashSet::from(["root", "assume_canonical"]),
+            exp_chain_txouts: HashSet::from([("root", 0), ("root", 1), ("assume_canonical", 0)]),
+            exp_unspents: HashSet::from([("root", 1), ("assume_canonical", 0)]),
+            exp_balance: Balance {
+                immature: Amount::ZERO,
+                trusted_pending: Amount::from_sat(19_000),
+                untrusted_pending: Amount::ZERO,
+                confirmed: Amount::from_sat(21_000),
+            },
+        },
+        Scenario {
+            name: "assume-canonical-tx displaces confirmed chain",
+            tx_templates: &[
+                TxTemplate {
+                    tx_name: "root",
+                    inputs: &[TxInTemplate::Bogus],
+                    outputs: &[
+                        TxOutTemplate::new(21_000, Some(0)),
+                        TxOutTemplate::new(21_000, Some(1)),
+                    ],
+                    anchors: &[block_id!(1, "B")],
+                    ..Default::default()
+                },
+                TxTemplate {
+                    tx_name: "confirmed",
+                    inputs: &[TxInTemplate::PrevTx("root", 0)],
+                    outputs: &[TxOutTemplate::new(20_000, Some(1))],
+                    anchors: &[block_id!(2, "C")],
+                    ..Default::default()
+                },
+                TxTemplate {
+                    tx_name: "confirmed_descendant",
+                    inputs: &[
+                        TxInTemplate::PrevTx("confirmed", 0),
+                        TxInTemplate::PrevTx("root", 1),
+                    ],
+                    outputs: &[TxOutTemplate::new(28_000, Some(2))],
+                    anchors: &[block_id!(3, "D")],
+                    ..Default::default()
+                },
+                TxTemplate {
+                    tx_name: "assume_canonical",
+                    inputs: &[TxInTemplate::PrevTx("root", 0)],
+                    outputs: &[TxOutTemplate::new(19_000, Some(3))],
+                    assume_canonical: true,
+                    ..Default::default()
+                },
+            ],
+            exp_chain_txs: HashSet::from(["root", "assume_canonical"]),
+            exp_chain_txouts: HashSet::from([("root", 0), ("root", 1), ("assume_canonical", 0)]),
+            exp_unspents: HashSet::from([("root", 1), ("assume_canonical", 0)]),
+            exp_balance: Balance {
+                immature: Amount::ZERO,
+                trusted_pending: Amount::from_sat(19_000),
+                untrusted_pending: Amount::ZERO,
+                confirmed: Amount::from_sat(21_000),
+            },
+        },
+        Scenario {
+            name: "assume-canonical txs respects order",
+            tx_templates: &[
+                TxTemplate {
+                    tx_name: "root",
+                    inputs: &[TxInTemplate::Bogus],
+                    outputs: &[
+                        TxOutTemplate::new(21_000, Some(0)),
+                    ],
+                    anchors: &[block_id!(1, "B")],
+                    ..Default::default()
+                },
+                TxTemplate {
+                    tx_name: "assume_a",
+                    inputs: &[TxInTemplate::PrevTx("root", 0)],
+                    outputs: &[TxOutTemplate::new(20_000, Some(1))],
+                    assume_canonical: true,
+                    ..Default::default()
+                },
+                TxTemplate {
+                    tx_name: "assume_b",
+                    inputs: &[TxInTemplate::PrevTx("root", 0)],
+                    outputs: &[TxOutTemplate::new(19_000, Some(1))],
+                    assume_canonical: true,
+                    ..Default::default()
+                },
+                TxTemplate {
+                    tx_name: "assume_c",
+                    inputs: &[TxInTemplate::PrevTx("root", 0)],
+                    outputs: &[TxOutTemplate::new(18_000, Some(1))],
+                    assume_canonical: true,
+                    ..Default::default()
+                },
+            ],
+            exp_chain_txs: HashSet::from(["root", "assume_c"]),
+            exp_chain_txouts: HashSet::from([("root", 0), ("assume_c", 0)]),
+            exp_unspents: HashSet::from([("assume_c", 0)]),
+            exp_balance: Balance {
+                immature: Amount::ZERO,
+                trusted_pending: Amount::from_sat(18_000),
+                untrusted_pending: Amount::ZERO,
+                confirmed: Amount::ZERO,
+            },
+        },
     ];
 
     for scenario in scenarios {
-        let (tx_graph, spk_index, exp_tx_ids) = init_graph(scenario.tx_templates.iter());
+        let env = init_graph(scenario.tx_templates.iter());
 
-        let txs = tx_graph
-            .list_canonical_txs(&local_chain, chain_tip)
+        let txs = env
+            .tx_graph
+            .list_canonical_txs(&local_chain, chain_tip, env.canonicalization_params.clone())
             .map(|tx| tx.tx_node.txid)
             .collect::<BTreeSet<_>>();
         let exp_txs = scenario
             .exp_chain_txs
             .iter()
-            .map(|txid| *exp_tx_ids.get(txid).expect("txid must exist"))
+            .map(|txid| *env.txid_to_name.get(txid).expect("txid must exist"))
             .collect::<BTreeSet<_>>();
         assert_eq!(
             txs, exp_txs,
@@ -811,11 +964,13 @@ fn test_tx_conflict_handling() {
             scenario.name
         );
 
-        let txouts = tx_graph
+        let txouts = env
+            .tx_graph
             .filter_chain_txouts(
                 &local_chain,
                 chain_tip,
-                spk_index.outpoints().iter().cloned(),
+                env.canonicalization_params.clone(),
+                env.indexer.outpoints().iter().cloned(),
             )
             .map(|(_, full_txout)| full_txout.outpoint)
             .collect::<BTreeSet<_>>();
@@ -823,7 +978,7 @@ fn test_tx_conflict_handling() {
             .exp_chain_txouts
             .iter()
             .map(|(txid, vout)| OutPoint {
-                txid: *exp_tx_ids.get(txid).expect("txid must exist"),
+                txid: *env.txid_to_name.get(txid).expect("txid must exist"),
                 vout: *vout,
             })
             .collect::<BTreeSet<_>>();
@@ -833,11 +988,13 @@ fn test_tx_conflict_handling() {
             scenario.name
         );
 
-        let utxos = tx_graph
+        let utxos = env
+            .tx_graph
             .filter_chain_unspents(
                 &local_chain,
                 chain_tip,
-                spk_index.outpoints().iter().cloned(),
+                env.canonicalization_params.clone(),
+                env.indexer.outpoints().iter().cloned(),
             )
             .map(|(_, full_txout)| full_txout.outpoint)
             .collect::<BTreeSet<_>>();
@@ -845,7 +1002,7 @@ fn test_tx_conflict_handling() {
             .exp_unspents
             .iter()
             .map(|(txid, vout)| OutPoint {
-                txid: *exp_tx_ids.get(txid).expect("txid must exist"),
+                txid: *env.txid_to_name.get(txid).expect("txid must exist"),
                 vout: *vout,
             })
             .collect::<BTreeSet<_>>();
@@ -855,11 +1012,12 @@ fn test_tx_conflict_handling() {
             scenario.name
         );
 
-        let balance = tx_graph.balance(
+        let balance = env.tx_graph.balance(
             &local_chain,
             chain_tip,
-            spk_index.outpoints().iter().cloned(),
-            |_, spk: ScriptBuf| spk_index.index_of_spk(spk).is_some(),
+            env.canonicalization_params.clone(),
+            env.indexer.outpoints().iter().cloned(),
+            |_, spk: ScriptBuf| env.indexer.index_of_spk(spk).is_some(),
         );
         assert_eq!(
             balance, scenario.exp_balance,
index 3c1d11803c2eb71c4a75a2c5ace4a89d192fa47e..5302e62f271a51fd97cea0dc626a3c08e1c0931e 100644 (file)
@@ -3,7 +3,8 @@ use bdk_chain::{
     local_chain::LocalChain,
     spk_client::{FullScanRequest, SyncRequest, SyncResponse},
     spk_txout::SpkTxOutIndex,
-    Balance, ConfirmationBlockTime, IndexedTxGraph, Indexer, Merge, TxGraph,
+    Balance, CanonicalizationParams, ConfirmationBlockTime, IndexedTxGraph, Indexer, Merge,
+    TxGraph,
 };
 use bdk_core::bitcoin::{
     key::{Secp256k1, UntweakedPublicKey},
@@ -39,9 +40,13 @@ fn get_balance(
 ) -> anyhow::Result<Balance> {
     let chain_tip = recv_chain.tip().block_id();
     let outpoints = recv_graph.index.outpoints().clone();
-    let balance = recv_graph
-        .graph()
-        .balance(recv_chain, chain_tip, outpoints, |_, _| true);
+    let balance = recv_graph.graph().balance(
+        recv_chain,
+        chain_tip,
+        CanonicalizationParams::default(),
+        outpoints,
+        |_, _| true,
+    );
     Ok(balance)
 }
 
index 83cb25f8a524fa60cec24db78e9e0120431bc132..5eb3d3eb1902d136f36b648bef6b7a4b31713d64 100644 (file)
@@ -13,7 +13,7 @@ use bdk_bitcoind_rpc::{
 };
 use bdk_chain::{
     bitcoin::{Block, Transaction},
-    local_chain, Merge,
+    local_chain, CanonicalizationParams, Merge,
 };
 use example_cli::{
     anyhow,
@@ -186,6 +186,7 @@ fn main() -> anyhow::Result<()> {
                         graph.graph().balance(
                             &*chain,
                             synced_to.block_id(),
+                            CanonicalizationParams::default(),
                             graph.index.outpoints().iter().cloned(),
                             |(k, _), _| k == &Keychain::Internal,
                         )
@@ -323,6 +324,7 @@ fn main() -> anyhow::Result<()> {
                         graph.graph().balance(
                             &*chain,
                             synced_to.block_id(),
+                            CanonicalizationParams::default(),
                             graph.index.outpoints().iter().cloned(),
                             |(k, _), _| k == &Keychain::Internal,
                         )
index 2cb27849192cb82cfc3307715925a8d6739f99ba..5e8b062af8c2d5e518811807f0b6c3dc29d8984a 100644 (file)
@@ -19,6 +19,7 @@ use bdk_chain::miniscript::{
     psbt::PsbtExt,
     Descriptor, DescriptorPublicKey, ForEachKey,
 };
+use bdk_chain::CanonicalizationParams;
 use bdk_chain::ConfirmationBlockTime;
 use bdk_chain::{
     indexed_tx_graph,
@@ -431,7 +432,12 @@ pub fn planned_utxos<O: ChainOracle>(
     let outpoints = graph.index.outpoints();
     graph
         .graph()
-        .try_filter_chain_unspents(chain, chain_tip, outpoints.iter().cloned())?
+        .try_filter_chain_unspents(
+            chain,
+            chain_tip,
+            CanonicalizationParams::default(),
+            outpoints.iter().cloned(),
+        )?
         .filter_map(|((k, i), full_txo)| -> Option<Result<PlanUtxo, _>> {
             let desc = graph
                 .index
@@ -525,6 +531,7 @@ pub fn handle_commands<CS: clap::Subcommand, S: clap::Args>(
             let balance = graph.graph().try_balance(
                 chain,
                 chain.get_chain_tip()?,
+                CanonicalizationParams::default(),
                 graph.index.outpoints().iter().cloned(),
                 |(k, _), _| k == &Keychain::Internal,
             )?;
@@ -566,7 +573,12 @@ pub fn handle_commands<CS: clap::Subcommand, S: clap::Args>(
                 } => {
                     let txouts = graph
                         .graph()
-                        .try_filter_chain_txouts(chain, chain_tip, outpoints.iter().cloned())?
+                        .try_filter_chain_txouts(
+                            chain,
+                            chain_tip,
+                            CanonicalizationParams::default(),
+                            outpoints.iter().cloned(),
+                        )?
                         .filter(|(_, full_txo)| match (spent, unspent) {
                             (true, false) => full_txo.spent_by.is_some(),
                             (false, true) => full_txo.spent_by.is_none(),
index b6c93a1d0ccd8d0d6649a22fac09a9b68d955c0e..a64836fe71014b9ab533a256a6d00b2ee161950c 100644 (file)
@@ -5,7 +5,7 @@ use bdk_chain::{
     collections::BTreeSet,
     indexed_tx_graph,
     spk_client::{FullScanRequest, SyncRequest},
-    ConfirmationBlockTime, Merge,
+    CanonicalizationParams, ConfirmationBlockTime, Merge,
 };
 use bdk_electrum::{
     electrum_client::{self, Client, ElectrumApi},
@@ -234,6 +234,7 @@ fn main() -> anyhow::Result<()> {
                         .filter_chain_unspents(
                             &*chain,
                             chain_tip.block_id(),
+                            CanonicalizationParams::default(),
                             init_outpoints.iter().cloned(),
                         )
                         .map(|(_, utxo)| utxo.outpoint),
@@ -243,7 +244,11 @@ fn main() -> anyhow::Result<()> {
                 request = request.txids(
                     graph
                         .graph()
-                        .list_canonical_txs(&*chain, chain_tip.block_id())
+                        .list_canonical_txs(
+                            &*chain,
+                            chain_tip.block_id(),
+                            CanonicalizationParams::default(),
+                        )
                         .filter(|canonical_tx| !canonical_tx.chain_position.is_confirmed())
                         .map(|canonical_tx| canonical_tx.tx_node.txid),
                 );
index 8ef39c2fb459fd34958e63d676ceac8d19df45a8..9b81055413e4c64c7256d7cef81a7739351c03d2 100644 (file)
@@ -8,7 +8,7 @@ use bdk_chain::{
     bitcoin::Network,
     keychain_txout::FullScanRequestBuilderExt,
     spk_client::{FullScanRequest, SyncRequest},
-    Merge,
+    CanonicalizationParams, Merge,
 };
 use bdk_esplora::{esplora_client, EsploraExt};
 use example_cli::{
@@ -247,6 +247,7 @@ fn main() -> anyhow::Result<()> {
                             .filter_chain_unspents(
                                 &*chain,
                                 local_tip.block_id(),
+                                CanonicalizationParams::default(),
                                 init_outpoints.iter().cloned(),
                             )
                             .map(|(_, utxo)| utxo.outpoint),
@@ -259,7 +260,11 @@ fn main() -> anyhow::Result<()> {
                     request = request.txids(
                         graph
                             .graph()
-                            .list_canonical_txs(&*chain, local_tip.block_id())
+                            .list_canonical_txs(
+                                &*chain,
+                                local_tip.block_id(),
+                                CanonicalizationParams::default(),
+                            )
                             .filter(|canonical_tx| !canonical_tx.chain_position.is_confirmed())
                             .map(|canonical_tx| canonical_tx.tx_node.txid),
                     );