]> Untitled Git - bdk/commitdiff
feat(chain)!: make `KeychainTxOutIndex` more range based
authorLLFourn <lloyd.fourn@gmail.com>
Tue, 6 Feb 2024 06:31:22 +0000 (17:31 +1100)
committer志宇 <hello@evanlinjin.me>
Thu, 18 Apr 2024 07:31:14 +0000 (15:31 +0800)
`KeychainTxOutIndex` should try and avoid "all" kind of queries.
There may be subranges of interest. If the user wants "all" they can
just query "..".

crates/bdk/src/wallet/mod.rs
crates/chain/src/keychain/txout_index.rs
crates/chain/src/spk_txout_index.rs
crates/chain/tests/test_spk_txout_index.rs
example-crates/example_electrum/src/main.rs
example-crates/example_esplora/src/main.rs

index 846878823c4895d8ff44cad5d4e90975aa68c8af..f596f397c29558cdef58eafde31182cb03574b07 100644 (file)
@@ -1015,7 +1015,7 @@ impl<D> Wallet<D> {
     /// let (sent, received) = wallet.sent_and_received(tx);
     /// ```
     pub fn sent_and_received(&self, tx: &Transaction) -> (u64, u64) {
-        self.indexed_graph.index.sent_and_received(tx)
+        self.indexed_graph.index.sent_and_received(tx, ..)
     }
 
     /// Get a single transaction from the wallet as a [`CanonicalTx`] (if the transaction exists).
index 79f98fad23ebb063ddfc5ff7585ef7f78a5c93c4..4b2479ec906a492abcd37681bedc995e00b76262 100644 (file)
@@ -268,15 +268,14 @@ impl<K: Clone + Ord + Debug> KeychainTxOutIndex<K> {
         self.inner.unmark_used(&(keychain, index))
     }
 
-    /// Computes total input value going from script pubkeys in the index (sent) and the total output
-    /// value going to script pubkeys in the index (received) in `tx`. For the `sent` to be computed
-    /// correctly, the output being spent must have already been scanned by the index. Calculating
-    /// received just uses the [`Transaction`] outputs directly, so it will be correct even if it has
-    /// not been scanned.
-    ///
-    /// This calls [`SpkTxOutIndex::sent_and_received`] internally.
-    pub fn sent_and_received(&self, tx: &Transaction) -> (u64, u64) {
-        self.inner.sent_and_received(tx)
+    /// Computes the total value transfer effect `tx` has on the script pubkeys belonging to the
+    /// keychains in `range`. Value is *sent* when a script pubkey in the `range` is on an input and
+    /// *received* when it is on an output. For `sent` to be computed correctly, the output being
+    /// spent must have already been scanned by the index. Calculating received just uses the
+    /// [`Transaction`] outputs directly, so it will be correct even if it has not been scanned.
+    pub fn sent_and_received(&self, tx: &Transaction, range: impl RangeBounds<K>) -> (u64, u64) {
+        self.inner
+            .sent_and_received(tx, Self::map_to_inner_bounds(range))
     }
 
     /// Computes the net value that this transaction gives to the script pubkeys in the index and
@@ -286,8 +285,8 @@ impl<K: Clone + Ord + Debug> KeychainTxOutIndex<K> {
     /// This calls [`SpkTxOutIndex::net_value`] internally.
     ///
     /// [`sent_and_received`]: Self::sent_and_received
-    pub fn net_value(&self, tx: &Transaction) -> i64 {
-        self.inner.net_value(tx)
+    pub fn net_value(&self, tx: &Transaction, range: impl RangeBounds<K>) -> i64 {
+        self.inner.net_value(tx, Self::map_to_inner_bounds(range))
     }
 }
 
@@ -390,24 +389,32 @@ impl<K: Clone + Ord + Debug> KeychainTxOutIndex<K> {
             .collect()
     }
 
-    /// Iterate over revealed spks of all keychains.
-    pub fn revealed_spks(&self) -> impl DoubleEndedIterator<Item = (K, u32, &Script)> + Clone {
-        self.keychains.keys().flat_map(|keychain| {
-            self.revealed_keychain_spks(keychain)
-                .map(|(i, spk)| (keychain.clone(), i, spk))
+    /// Iterate over revealed spks of keychains in `range`
+    pub fn revealed_spks(
+        &self,
+        range: impl RangeBounds<K>,
+    ) -> impl DoubleEndedIterator<Item = (&K, u32, &Script)> + Clone {
+        self.keychains.range(range).flat_map(|(keychain, _)| {
+            let start = Bound::Included((keychain.clone(), u32::MIN));
+            let end = match self.last_revealed.get(keychain) {
+                Some(last_revealed) => Bound::Included((keychain.clone(), *last_revealed)),
+                None => Bound::Excluded((keychain.clone(), u32::MIN)),
+            };
+
+            self.inner
+                .all_spks()
+                .range((start, end))
+                .map(|((keychain, i), spk)| (keychain, *i, spk.as_script()))
         })
     }
 
     /// Iterate over revealed spks of the given `keychain`.
-    pub fn revealed_keychain_spks(
-        &self,
-        keychain: &K,
-    ) -> impl DoubleEndedIterator<Item = (u32, &Script)> + Clone {
-        let next_i = self.last_revealed.get(keychain).map_or(0, |&i| i + 1);
-        self.inner
-            .all_spks()
-            .range((keychain.clone(), u32::MIN)..(keychain.clone(), next_i))
-            .map(|((_, i), spk)| (*i, spk.as_script()))
+    pub fn revealed_keychain_spks<'a>(
+        &'a self,
+        keychain: &'a K,
+    ) -> impl DoubleEndedIterator<Item = (u32, &Script)> + 'a {
+        self.revealed_spks(keychain..=keychain)
+            .map(|(_, i, spk)| (i, spk))
     }
 
     /// Iterate over revealed, but unused, spks of all keychains.
@@ -617,38 +624,40 @@ impl<K: Clone + Ord + Debug> KeychainTxOutIndex<K> {
         }
     }
 
-    /// Iterate over all [`OutPoint`]s that point to `TxOut`s with script pubkeys derived from
+    /// Iterate over all [`OutPoint`]s that have `TxOut`s with script pubkeys derived from
     /// `keychain`.
-    ///
-    /// Use [`keychain_outpoints_in_range`](KeychainTxOutIndex::keychain_outpoints_in_range) to
-    /// iterate over a specific derivation range.
-    pub fn keychain_outpoints(
-        &self,
-        keychain: &K,
-    ) -> impl DoubleEndedIterator<Item = (u32, OutPoint)> + '_ {
-        self.keychain_outpoints_in_range(keychain, ..)
+    pub fn keychain_outpoints<'a>(
+        &'a self,
+        keychain: &'a K,
+    ) -> impl DoubleEndedIterator<Item = (u32, OutPoint)> + 'a {
+        self.keychain_outpoints_in_range(keychain..=keychain)
+            .map(move |(_, i, op)| (i, op))
+    }
+
+    /// Iterate over [`OutPoint`]s that have script pubkeys derived from keychains in `range`.
+    pub fn keychain_outpoints_in_range<'a>(
+        &'a self,
+        range: impl RangeBounds<K> + 'a,
+    ) -> impl DoubleEndedIterator<Item = (&'a K, u32, OutPoint)> + 'a {
+        let bounds = Self::map_to_inner_bounds(range);
+        self.inner
+            .outputs_in_range(bounds)
+            .map(move |((keychain, i), op)| (keychain, *i, op))
     }
 
-    /// Iterate over [`OutPoint`]s that point to `TxOut`s with script pubkeys derived from
-    /// `keychain` in a given derivation `range`.
-    pub fn keychain_outpoints_in_range(
-        &self,
-        keychain: &K,
-        range: impl RangeBounds<u32>,
-    ) -> impl DoubleEndedIterator<Item = (u32, OutPoint)> + '_ {
-        let start = match range.start_bound() {
-            Bound::Included(i) => Bound::Included((keychain.clone(), *i)),
-            Bound::Excluded(i) => Bound::Excluded((keychain.clone(), *i)),
+    fn map_to_inner_bounds(bound: impl RangeBounds<K>) -> impl RangeBounds<(K, u32)> {
+        let start = match bound.start_bound() {
+            Bound::Included(keychain) => Bound::Included((keychain.clone(), u32::MIN)),
+            Bound::Excluded(keychain) => Bound::Excluded((keychain.clone(), u32::MAX)),
             Bound::Unbounded => Bound::Unbounded,
         };
-        let end = match range.end_bound() {
-            Bound::Included(i) => Bound::Included((keychain.clone(), *i)),
-            Bound::Excluded(i) => Bound::Excluded((keychain.clone(), *i)),
+        let end = match bound.end_bound() {
+            Bound::Included(keychain) => Bound::Included((keychain.clone(), u32::MAX)),
+            Bound::Excluded(keychain) => Bound::Excluded((keychain.clone(), u32::MIN)),
             Bound::Unbounded => Bound::Unbounded,
         };
-        self.inner
-            .outputs_in_range((start, end))
-            .map(|((_, i), op)| (*i, op))
+
+        (start, end)
     }
 
     /// Returns the highest derivation index of the `keychain` where [`KeychainTxOutIndex`] has
index 24d06d123cbe891775d9bbeb95790cc494786303..a3ad48d0ae5bf9b0f14b788f45fd39e5658798bc 100644 (file)
@@ -270,36 +270,39 @@ impl<I: Clone + Ord> SpkTxOutIndex<I> {
         self.spk_indices.get(script)
     }
 
-    /// Computes total input value going from script pubkeys in the index (sent) and the total output
-    /// value going to script pubkeys in the index (received) in `tx`. For the `sent` to be computed
-    /// correctly, the output being spent must have already been scanned by the index. Calculating
-    /// received just uses the [`Transaction`] outputs directly, so it will be correct even if it has
-    /// not been scanned.
-    pub fn sent_and_received(&self, tx: &Transaction) -> (u64, u64) {
+    /// Computes the total value transfer effect `tx` has on the script pubkeys in `range`. Value is
+    /// *sent* when a script pubkey in the `range` is on an input and *received* when it is on an
+    /// output. For `sent` to be computed correctly, the output being spent must have already been
+    /// scanned by the index. Calculating received just uses the [`Transaction`] outputs directly,
+    /// so it will be correct even if it has not been scanned.
+    pub fn sent_and_received(&self, tx: &Transaction, range: impl RangeBounds<I>) -> (u64, u64) {
         let mut sent = 0;
         let mut received = 0;
 
         for txin in &tx.input {
-            if let Some((_, txout)) = self.txout(txin.previous_output) {
-                sent += txout.value.to_sat();
+            if let Some((index, txout)) = self.txout(txin.previous_output) {
+                if range.contains(index) {
+                    sent += txout.value.to_sat();
+                }
             }
         }
         for txout in &tx.output {
-            if self.index_of_spk(&txout.script_pubkey).is_some() {
-                received += txout.value.to_sat();
+            if let Some(index) = self.index_of_spk(&txout.script_pubkey) {
+                if range.contains(index) {
+                    received += txout.value.to_sat();
+                }
             }
         }
 
         (sent, received)
     }
 
-    /// Computes the net value that this transaction gives to the script pubkeys in the index and
-    /// *takes* from the transaction outputs in the index. Shorthand for calling
-    /// [`sent_and_received`] and subtracting sent from received.
+    /// Computes the net value transfer effect of `tx` on the script pubkeys in `range`. Shorthand
+    /// for calling [`sent_and_received`] and subtracting sent from received.
     ///
     /// [`sent_and_received`]: Self::sent_and_received
-    pub fn net_value(&self, tx: &Transaction) -> i64 {
-        let (sent, received) = self.sent_and_received(tx);
+    pub fn net_value(&self, tx: &Transaction, range: impl RangeBounds<I>) -> i64 {
+        let (sent, received) = self.sent_and_received(tx, range);
         received as i64 - sent as i64
     }
 
index c84bd719496cd9a23ee8817f1e08fe9c08426068..e6c3908055ae7410617c95d556ea18105db683ad 100644 (file)
@@ -20,11 +20,13 @@ fn spk_txout_sent_and_received() {
         }],
     };
 
-    assert_eq!(index.sent_and_received(&tx1), (0, 42_000));
-    assert_eq!(index.net_value(&tx1), 42_000);
+    assert_eq!(index.sent_and_received(&tx1, ..), (0, 42_000));
+    assert_eq!(index.sent_and_received(&tx1, ..1), (0, 42_000));
+    assert_eq!(index.sent_and_received(&tx1, 1..), (0, 0));
+    assert_eq!(index.net_value(&tx1, ..), 42_000);
     index.index_tx(&tx1);
     assert_eq!(
-        index.sent_and_received(&tx1),
+        index.sent_and_received(&tx1, ..),
         (0, 42_000),
         "shouldn't change after scanning"
     );
@@ -51,8 +53,10 @@ fn spk_txout_sent_and_received() {
         ],
     };
 
-    assert_eq!(index.sent_and_received(&tx2), (42_000, 50_000));
-    assert_eq!(index.net_value(&tx2), 8_000);
+    assert_eq!(index.sent_and_received(&tx2, ..), (42_000, 50_000));
+    assert_eq!(index.sent_and_received(&tx2, ..1), (42_000, 30_000));
+    assert_eq!(index.sent_and_received(&tx2, 1..), (0, 20_000));
+    assert_eq!(index.net_value(&tx2, ..), 8_000);
 }
 
 #[test]
index f651b85e27c3733dd5d2a959c36d529c3c097d22..e3b758e74551698c19db7764152a98e850fd9774 100644 (file)
@@ -210,8 +210,8 @@ fn main() -> anyhow::Result<()> {
             if all_spks {
                 let all_spks = graph
                     .index
-                    .revealed_spks()
-                    .map(|(k, i, spk)| (k, i, spk.to_owned()))
+                    .revealed_spks(..)
+                    .map(|(k, i, spk)| (k.to_owned(), i, spk.to_owned()))
                     .collect::<Vec<_>>();
                 spks = Box::new(spks.chain(all_spks.into_iter().map(|(k, i, spk)| {
                     eprintln!("scanning {}:{}", k, i);
index 9232081b6590463284918cb89df3f27077224231..1abbf1ca7fdd85dee8deba4c50ed526cad47dba2 100644 (file)
@@ -241,8 +241,8 @@ fn main() -> anyhow::Result<()> {
                 if *all_spks {
                     let all_spks = graph
                         .index
-                        .revealed_spks()
-                        .map(|(k, i, spk)| (k, i, spk.to_owned()))
+                        .revealed_spks(..)
+                        .map(|(k, i, spk)| (k.to_owned(), i, spk.to_owned()))
                         .collect::<Vec<_>>();
                     spks = Box::new(spks.chain(all_spks.into_iter().map(|(k, i, spk)| {
                         eprintln!("scanning {}:{}", k, i);