]> Untitled Git - bdk/commitdiff
feat(tx_graph): Add method update_last_seen_unconfirmed
authorvalued mammal <valuedmammal@protonmail.com>
Sat, 30 Mar 2024 01:37:38 +0000 (21:37 -0400)
committervalued mammal <valuedmammal@protonmail.com>
Sat, 6 Apr 2024 15:50:37 +0000 (11:50 -0400)
That accepts a `u64` as param representing the latest timestamp
and internally calls `insert_seen_at` for all transactions in
graph that aren't yet anchored in a confirmed block.

crates/chain/src/tx_graph.rs
crates/chain/tests/test_tx_graph.rs

index 34cbccf5ce5e1c4022923b9e52d36c317ae13e2a..06bbc2b32dd7c887a1189dd3b503f38e9b9f681b 100644 (file)
@@ -541,7 +541,11 @@ impl<A: Clone + Ord> TxGraph<A> {
 
     /// Inserts the given `seen_at` for `txid` into [`TxGraph`].
     ///
-    /// Note that [`TxGraph`] only keeps track of the latest `seen_at`.
+    /// Note that [`TxGraph`] only keeps track of the latest `seen_at`. To batch
+    /// update all unconfirmed transactions with the latest `seen_at`, see
+    /// [`update_last_seen_unconfirmed`].
+    ///
+    /// [`update_last_seen_unconfirmed`]: Self::update_last_seen_unconfirmed
     pub fn insert_seen_at(&mut self, txid: Txid, seen_at: u64) -> ChangeSet<A> {
         let mut update = Self::default();
         let (_, _, update_last_seen) = update.txs.entry(txid).or_default();
@@ -549,6 +553,65 @@ impl<A: Clone + Ord> TxGraph<A> {
         self.apply_update(update)
     }
 
+    /// Update the last seen time for all unconfirmed transactions.
+    ///
+    /// This method updates the last seen unconfirmed time for this [`TxGraph`] by inserting
+    /// the given `seen_at` for every transaction not yet anchored to a confirmed block,
+    /// and returns the [`ChangeSet`] after applying all updates to `self`.
+    ///
+    /// This is useful for keeping track of the latest time a transaction was seen
+    /// unconfirmed, which is important for evaluating transaction conflicts in the same
+    /// [`TxGraph`]. For details of how [`TxGraph`] resolves conflicts, see the docs for
+    /// [`try_get_chain_position`].
+    ///
+    /// A normal use of this method is to call it with the current system time. Although
+    /// block headers contain a timestamp, using the header time would be less effective
+    /// at tracking mempool transactions, because it can drift from actual clock time, plus
+    /// we may want to update a transaction's last seen time repeatedly between blocks.
+    ///
+    /// # Example
+    ///
+    /// ```rust
+    /// # use bdk_chain::example_utils::*;
+    /// # use std::time::UNIX_EPOCH;
+    /// # let tx = tx_from_hex(RAW_TX_1);
+    /// # let mut tx_graph = bdk_chain::TxGraph::<()>::new([tx]);
+    /// let now = std::time::SystemTime::now()
+    ///     .duration_since(UNIX_EPOCH)
+    ///     .expect("valid duration")
+    ///     .as_secs();
+    /// let changeset = tx_graph.update_last_seen_unconfirmed(now);
+    /// assert!(!changeset.last_seen.is_empty());
+    /// ```
+    ///
+    /// Note that [`TxGraph`] only keeps track of the latest `seen_at`, so the given time must
+    /// by strictly greater than what is currently stored for a transaction to have an effect.
+    /// To insert a last seen time for a single txid, see [`insert_seen_at`].
+    ///
+    /// [`insert_seen_at`]: Self::insert_seen_at
+    /// [`try_get_chain_position`]: Self::try_get_chain_position
+    pub fn update_last_seen_unconfirmed(&mut self, seen_at: u64) -> ChangeSet<A> {
+        let mut changeset = ChangeSet::default();
+        let unanchored_txs: Vec<Txid> = self
+            .txs
+            .iter()
+            .filter_map(
+                |(&txid, (_, anchors, _))| {
+                    if anchors.is_empty() {
+                        Some(txid)
+                    } else {
+                        None
+                    }
+                },
+            )
+            .collect();
+
+        for txid in unanchored_txs {
+            changeset.append(self.insert_seen_at(txid, seen_at));
+        }
+        changeset
+    }
+
     /// Extends this graph with another so that `self` becomes the union of the two sets of
     /// transactions.
     ///
index 37e8c71925b4b36e4b2ffa11cbda132f0f9ac1f3..c646d431f1cfd77bca487e73a23fa89cd472dd28 100644 (file)
@@ -1048,6 +1048,34 @@ fn test_changeset_last_seen_append() {
     }
 }
 
+#[test]
+fn update_last_seen_unconfirmed() {
+    let mut graph = TxGraph::<()>::default();
+    let tx = new_tx(0);
+    let txid = tx.txid();
+
+    // insert a new tx
+    // initially we have a last_seen of 0, and no anchors
+    let _ = graph.insert_tx(tx);
+    let tx = graph.full_txs().next().unwrap();
+    assert_eq!(tx.last_seen_unconfirmed, 0);
+    assert!(tx.anchors.is_empty());
+
+    // higher timestamp should update last seen
+    let changeset = graph.update_last_seen_unconfirmed(2);
+    assert_eq!(changeset.last_seen.get(&txid).unwrap(), &2);
+
+    // lower timestamp has no effect
+    let changeset = graph.update_last_seen_unconfirmed(1);
+    assert!(changeset.last_seen.is_empty());
+
+    // once anchored, last seen is not updated
+    let _ = graph.insert_anchor(txid, ());
+    let changeset = graph.update_last_seen_unconfirmed(4);
+    assert!(changeset.is_empty());
+    assert_eq!(graph.full_txs().next().unwrap().last_seen_unconfirmed, 2);
+}
+
 #[test]
 fn test_missing_blocks() {
     /// An anchor implementation for testing, made up of `(the_anchor_block, random_data)`.