]> Untitled Git - bdk/commitdiff
feat(chain)!: `TxGraph::apply_update` auto-adds `seen_at` for unanchored
author志宇 <hello@evanlinjin.me>
Fri, 23 Aug 2024 09:53:42 +0000 (09:53 +0000)
committer志宇 <hello@evanlinjin.me>
Fri, 23 Aug 2024 13:42:57 +0000 (13:42 +0000)
Change `apply_update` to use the current timestamp as `seen_at` for
unanchored transactions of the update. This makes `apply_update` only
avaliable with the "std" feature.

Introduce `apply_update_at` which includes an optional `seen_at` input.
This is the no-std version of `apply_update`.

Also update docs.

crates/chain/src/indexed_tx_graph.rs
crates/chain/src/tx_graph.rs
crates/electrum/tests/test_electrum.rs
crates/wallet/src/wallet/mod.rs
example-crates/example_electrum/src/main.rs
example-crates/example_esplora/src/main.rs
example-crates/wallet_electrum/src/main.rs
example-crates/wallet_esplora_async/src/main.rs
example-crates/wallet_esplora_blocking/src/main.rs

index d24b1b307b77355ccdc56d0dd7197c24f9ad892c..73ae458ffd500aff4379736308862c63069f8d80 100644 (file)
@@ -90,13 +90,38 @@ where
 
     /// Apply an `update` directly.
     ///
-    /// `update` is a [`TxGraph<A>`] and the resultant changes is returned as [`ChangeSet`].
+    /// `update` is a [`tx_graph::Update<A>`] and the resultant changes is returned as [`ChangeSet`].
+    #[cfg(feature = "std")]
+    #[cfg_attr(docsrs, doc(cfg(feature = "std")))]
     pub fn apply_update(&mut self, update: tx_graph::Update<A>) -> ChangeSet<A, I::ChangeSet> {
         let tx_graph = self.graph.apply_update(update);
         let indexer = self.index_tx_graph_changeset(&tx_graph);
         ChangeSet { tx_graph, indexer }
     }
 
+    /// Apply the given `update` with an optional `seen_at` timestamp.
+    ///
+    /// `seen_at` represents when the update is seen (in unix seconds). It is used to determine the
+    /// `last_seen`s for all transactions in the update which have no corresponding anchor(s). The
+    /// `last_seen` value is used internally to determine precedence of conflicting unconfirmed
+    /// transactions (where the transaction with the lower `last_seen` value is omitted from the
+    /// canonical history).
+    ///
+    /// Not setting a `seen_at` value means unconfirmed transactions introduced by this update will
+    /// not be part of the canonical history of transactions.
+    ///
+    /// Use [`apply_update`](IndexedTxGraph::apply_update) to have the `seen_at` value automatically
+    /// set to the current time.
+    pub fn apply_update_at(
+        &mut self,
+        update: tx_graph::Update<A>,
+        seen_at: Option<u64>,
+    ) -> ChangeSet<A, I::ChangeSet> {
+        let tx_graph = self.graph.apply_update_at(update, seen_at);
+        let indexer = self.index_tx_graph_changeset(&tx_graph);
+        ChangeSet { tx_graph, indexer }
+    }
+
     /// Insert a floating `txout` of given `outpoint`.
     pub fn insert_txout(&mut self, outpoint: OutPoint, txout: TxOut) -> ChangeSet<A, I::ChangeSet> {
         let graph = self.graph.insert_txout(outpoint, txout);
index ba894fa938da538dd8d9c03fe08d04ef980c3432..6da4d8afd1167a6c2d6000a250791862b70cef86 100644 (file)
@@ -146,29 +146,12 @@ impl<A> From<TxGraph<A>> for Update<A> {
 impl<A: Ord + Clone> From<Update<A>> for TxGraph<A> {
     fn from(update: Update<A>) -> Self {
         let mut graph = TxGraph::<A>::default();
-        let _ = graph.apply_update(update);
+        let _ = graph.apply_update_at(update, None);
         graph
     }
 }
 
 impl<A: Ord> Update<A> {
-    /// Update the [`seen_ats`](Self::seen_ats) for all unanchored transactions.
-    pub fn update_last_seen_unconfirmed(&mut self, seen_at: u64) {
-        let seen_ats = &mut self.seen_ats;
-        let anchors = &self.anchors;
-        let unanchored_txids = self.txs.iter().map(|tx| tx.compute_txid()).filter(|txid| {
-            for (_, anchor_txid) in anchors {
-                if txid == anchor_txid {
-                    return false;
-                }
-            }
-            true
-        });
-        for txid in unanchored_txids {
-            seen_ats.insert(txid, seen_at);
-        }
-    }
-
     /// Extend this update with `other`.
     pub fn extend(&mut self, other: Update<A>) {
         self.txs.extend(other.txs);
@@ -762,25 +745,56 @@ impl<A: Clone + Ord> TxGraph<A> {
         changeset
     }
 
-    /// Extends this graph with another so that `self` becomes the union of the two sets of
-    /// transactions.
+    /// Extends this graph with the given `update`.
     ///
     /// The returned [`ChangeSet`] is the set difference between `update` and `self` (transactions that
     /// exist in `update` but not in `self`).
+    #[cfg(feature = "std")]
+    #[cfg_attr(docsrs, doc(cfg(feature = "std")))]
     pub fn apply_update(&mut self, update: Update<A>) -> ChangeSet<A> {
+        use std::time::*;
+        let now = SystemTime::now()
+            .duration_since(UNIX_EPOCH)
+            .expect("current time must be greater than epoch anchor");
+        self.apply_update_at(update, Some(now.as_secs()))
+    }
+
+    /// Extends this graph with the given `update` alongside an optional `seen_at` timestamp.
+    ///
+    /// `seen_at` represents when the update is seen (in unix seconds). It is used to determine the
+    /// `last_seen`s for all transactions in the update which have no corresponding anchor(s). The
+    /// `last_seen` value is used internally to determine precedence of conflicting unconfirmed
+    /// transactions (where the transaction with the lower `last_seen` value is omitted from the
+    /// canonical history).
+    ///
+    /// Not setting a `seen_at` value means unconfirmed transactions introduced by this update will
+    /// not be part of the canonical history of transactions.
+    ///
+    /// Use [`apply_update`](TxGraph::apply_update) to have the `seen_at` value automatically set
+    /// to the current time.
+    pub fn apply_update_at(&mut self, update: Update<A>, seen_at: Option<u64>) -> ChangeSet<A> {
         let mut changeset = ChangeSet::<A>::default();
+        let mut unanchored_txs = HashSet::<Txid>::new();
         for tx in update.txs {
-            changeset.merge(self.insert_tx(tx));
+            if unanchored_txs.insert(tx.compute_txid()) {
+                changeset.merge(self.insert_tx(tx));
+            }
         }
         for (outpoint, txout) in update.txouts {
             changeset.merge(self.insert_txout(outpoint, txout));
         }
         for (anchor, txid) in update.anchors {
+            unanchored_txs.remove(&txid);
             changeset.merge(self.insert_anchor(txid, anchor));
         }
         for (txid, seen_at) in update.seen_ats {
             changeset.merge(self.insert_seen_at(txid, seen_at));
         }
+        if let Some(seen_at) = seen_at {
+            for txid in unanchored_txs {
+                changeset.merge(self.insert_seen_at(txid, seen_at));
+            }
+        }
         changeset
     }
 
index e8b054d339703da5e7f0ae3ab119c3ee9ec7cca5..d5e4a1596e748ac4f876b8877249b087f69750a6 100644 (file)
@@ -38,19 +38,12 @@ where
     Spks: IntoIterator<Item = ScriptBuf>,
     Spks::IntoIter: ExactSizeIterator + Send + 'static,
 {
-    let mut update = client.sync(
+    let update = client.sync(
         SyncRequest::builder().chain_tip(chain.tip()).spks(spks),
         BATCH_SIZE,
         true,
     )?;
 
-    // Update `last_seen` to be able to calculate balance for unconfirmed transactions.
-    let now = std::time::UNIX_EPOCH
-        .elapsed()
-        .expect("must get time")
-        .as_secs();
-    update.graph_update.update_last_seen_unconfirmed(now);
-
     if let Some(chain_update) = update.chain_update.clone() {
         let _ = chain
             .apply_update(chain_update)
index 0d6cdf184400acd8cc026848e88c20d7341f890d..638bb575795f9b3f4e1fc0fdb97f66dd924c7716 100644 (file)
@@ -2277,7 +2277,34 @@ impl Wallet {
     /// to persist staged wallet changes see [`Wallet::reveal_next_address`]. `
     ///
     /// [`commit`]: Self::commit
+    #[cfg(feature = "std")]
+    #[cfg_attr(docsrs, doc(cfg(feature = "std")))]
     pub fn apply_update(&mut self, update: impl Into<Update>) -> Result<(), CannotConnectError> {
+        use std::time::*;
+        let now = SystemTime::now()
+            .duration_since(UNIX_EPOCH)
+            .expect("time now must surpass epoch anchor");
+        self.apply_update_at(update, Some(now.as_secs()))
+    }
+
+    /// Applies an `update` alongside an optional `seen_at` timestamp and stages the changes.
+    ///
+    /// `seen_at` represents when the update is seen (in unix seconds). It is used to determine the
+    /// `last_seen`s for all transactions in the update which have no corresponding anchor(s). The
+    /// `last_seen` value is used internally to determine precedence of conflicting unconfirmed
+    /// transactions (where the transaction with the lower `last_seen` value is omitted from the
+    /// canonical history).
+    ///
+    /// Not setting a `seen_at` value means unconfirmed transactions introduced by this update will
+    /// not be part of the canonical history of transactions.
+    ///
+    /// Use [`apply_update`](Wallet::apply_update) to have the `seen_at` value automatically set to
+    /// the current time.
+    pub fn apply_update_at(
+        &mut self,
+        update: impl Into<Update>,
+        seen_at: Option<u64>,
+    ) -> Result<(), CannotConnectError> {
         let update = update.into();
         let mut changeset = match update.chain {
             Some(chain_update) => ChangeSet::from(self.chain.apply_update(chain_update)?),
@@ -2289,7 +2316,11 @@ impl Wallet {
             .index
             .reveal_to_target_multi(&update.last_active_indices);
         changeset.merge(index_changeset.into());
-        changeset.merge(self.indexed_graph.apply_update(update.graph).into());
+        changeset.merge(
+            self.indexed_graph
+                .apply_update_at(update.graph, seen_at)
+                .into(),
+        );
         self.stage.merge(changeset);
         Ok(())
     }
index 7212547d6e6a58eef823f69d2dc431266794212d..662bc42373fdd1bbd031034140bb8b0d167f6fa9 100644 (file)
@@ -129,7 +129,7 @@ fn main() -> anyhow::Result<()> {
     // Tell the electrum client about the txs we've already got locally so it doesn't re-download them
     client.populate_tx_cache(&*graph.lock().unwrap());
 
-    let (chain_update, mut graph_update, keychain_update) = match electrum_cmd.clone() {
+    let (chain_update, graph_update, keychain_update) = match electrum_cmd.clone() {
         ElectrumCommands::Scan {
             stop_gap,
             scan_options,
@@ -248,12 +248,6 @@ fn main() -> anyhow::Result<()> {
         }
     };
 
-    let now = std::time::UNIX_EPOCH
-        .elapsed()
-        .expect("must get time")
-        .as_secs();
-    graph_update.update_last_seen_unconfirmed(now);
-
     let db_changeset = {
         let mut chain = chain.lock().unwrap();
         let mut graph = graph.lock().unwrap();
index d188eab76153347203bdf89f3f1a41c9d909012c..d4692e35c333c456dce98a411440d71a758b0ad4 100644 (file)
@@ -166,14 +166,10 @@ fn main() -> anyhow::Result<()> {
             // is reached. It returns a `TxGraph` update (`graph_update`) and a structure that
             // represents the last active spk derivation indices of keychains
             // (`keychain_indices_update`).
-            let mut update = client
+            let update = client
                 .full_scan(request, *stop_gap, scan_options.parallel_requests)
                 .context("scanning for transactions")?;
 
-            // We want to keep track of the latest time a transaction was seen unconfirmed.
-            let now = std::time::UNIX_EPOCH.elapsed().unwrap().as_secs();
-            update.graph_update.update_last_seen_unconfirmed(now);
-
             let mut graph = graph.lock().expect("mutex must not be poisoned");
             let mut chain = chain.lock().expect("mutex must not be poisoned");
             // Because we did a stop gap based scan we are likely to have some updates to our
@@ -265,11 +261,7 @@ fn main() -> anyhow::Result<()> {
                 }
             }
 
-            let mut update = client.sync(request, scan_options.parallel_requests)?;
-
-            // Update last seen unconfirmed
-            let now = std::time::UNIX_EPOCH.elapsed().unwrap().as_secs();
-            update.graph_update.update_last_seen_unconfirmed(now);
+            let update = client.sync(request, scan_options.parallel_requests)?;
 
             (
                 chain
index 4cc698a008514d349539198b182ecd169d61e6a4..c05184052be7a6075964699dea33ffb9004590dd 100644 (file)
@@ -64,10 +64,7 @@ fn main() -> Result<(), anyhow::Error> {
         }
     });
 
-    let mut update = client.full_scan(request, STOP_GAP, BATCH_SIZE, false)?;
-
-    let now = std::time::UNIX_EPOCH.elapsed().unwrap().as_secs();
-    update.graph_update.update_last_seen_unconfirmed(now);
+    let update = client.full_scan(request, STOP_GAP, BATCH_SIZE, false)?;
 
     println!();
 
index d4dae1f3f5d270a89a33d11370fa0dca2d8d7771..6fd215dff4d5a90c547cf9b35b7f80bd1917a993 100644 (file)
@@ -57,11 +57,9 @@ async fn main() -> Result<(), anyhow::Error> {
         }
     });
 
-    let mut update = client
+    let update = client
         .full_scan(request, STOP_GAP, PARALLEL_REQUESTS)
         .await?;
-    let now = std::time::UNIX_EPOCH.elapsed().unwrap().as_secs();
-    update.graph_update.update_last_seen_unconfirmed(now);
 
     wallet.apply_update(update)?;
     wallet.persist(&mut conn)?;
index 9f79d6bf6f1c4609027ea1bbd9c9c5a85bd77e2f..45e4685b79fc4b52edcb59a6c311d887c4df8716 100644 (file)
@@ -59,9 +59,7 @@ fn main() -> Result<(), anyhow::Error> {
         }
     });
 
-    let mut update = client.full_scan(request, STOP_GAP, PARALLEL_REQUESTS)?;
-    let now = std::time::UNIX_EPOCH.elapsed().unwrap().as_secs();
-    update.graph_update.update_last_seen_unconfirmed(now);
+    let update = client.full_scan(request, STOP_GAP, PARALLEL_REQUESTS)?;
 
     wallet.apply_update(update)?;
     if let Some(changeset) = wallet.take_staged() {