Skip to content

Commit 37fca35

Browse files
committed
feat(tx_graph): Add method update_last_seen_unconfirmed
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.
1 parent b5557dc commit 37fca35

File tree

2 files changed

+92
-1
lines changed

2 files changed

+92
-1
lines changed

crates/chain/src/tx_graph.rs

Lines changed: 64 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -541,14 +541,77 @@ impl<A: Clone + Ord> TxGraph<A> {
541541

542542
/// Inserts the given `seen_at` for `txid` into [`TxGraph`].
543543
///
544-
/// Note that [`TxGraph`] only keeps track of the latest `seen_at`.
544+
/// Note that [`TxGraph`] only keeps track of the latest `seen_at`. To batch
545+
/// update all unconfirmed transactions with the latest `seen_at`, see
546+
/// [`update_last_seen_unconfirmed`].
547+
///
548+
/// [`update_last_seen_unconfirmed`]: Self::update_last_seen_unconfirmed
545549
pub fn insert_seen_at(&mut self, txid: Txid, seen_at: u64) -> ChangeSet<A> {
546550
let mut update = Self::default();
547551
let (_, _, update_last_seen) = update.txs.entry(txid).or_default();
548552
*update_last_seen = seen_at;
549553
self.apply_update(update)
550554
}
551555

556+
/// Update the last seen time for all unconfirmed transactions.
557+
///
558+
/// This method updates the last seen unconfirmed time for this [`TxGraph`] by inserting
559+
/// the given `seen_at` for every transaction not yet anchored to a confirmed block,
560+
/// and returns the [`ChangeSet`] after applying all updates to `self`.
561+
///
562+
/// This is useful for keeping track of the latest time a transaction was seen
563+
/// unconfirmed, which is important for evaluating transaction conflicts in the same
564+
/// [`TxGraph`]. For details of how [`TxGraph`] resolves conflicts, see the docs for
565+
/// [`try_get_chain_position`].
566+
///
567+
/// A normal use of this method is to call it with the current system time. Although
568+
/// block headers contain a timestamp, using the header time would be less effective
569+
/// at tracking mempool transactions, because it can drift from actual clock time, plus
570+
/// we may want to update a transaction's last seen time repeatedly between blocks.
571+
///
572+
/// # Example
573+
///
574+
/// ```rust
575+
/// # use bdk_chain::example_utils::*;
576+
/// # use std::time::UNIX_EPOCH;
577+
/// # let tx = tx_from_hex(RAW_TX_1);
578+
/// # let mut tx_graph = bdk_chain::TxGraph::<()>::new([tx]);
579+
/// let now = std::time::SystemTime::now()
580+
/// .duration_since(UNIX_EPOCH)
581+
/// .expect("valid duration")
582+
/// .as_secs();
583+
/// let changeset = tx_graph.update_last_seen_unconfirmed(now);
584+
/// assert!(!changeset.last_seen.is_empty());
585+
/// ```
586+
///
587+
/// Note that [`TxGraph`] only keeps track of the latest `seen_at`, so the given time must
588+
/// by strictly greater than what is currently stored for a transaction to have an effect.
589+
/// To insert a last seen time for a single txid, see [`insert_seen_at`].
590+
///
591+
/// [`insert_seen_at`]: Self::insert_seen_at
592+
/// [`try_get_chain_position`]: Self::try_get_chain_position
593+
pub fn update_last_seen_unconfirmed(&mut self, seen_at: u64) -> ChangeSet<A> {
594+
let mut changeset = ChangeSet::default();
595+
let unanchored_txs: Vec<Txid> = self
596+
.txs
597+
.iter()
598+
.filter_map(
599+
|(&txid, (_, anchors, _))| {
600+
if anchors.is_empty() {
601+
Some(txid)
602+
} else {
603+
None
604+
}
605+
},
606+
)
607+
.collect();
608+
609+
for txid in unanchored_txs {
610+
changeset.append(self.insert_seen_at(txid, seen_at));
611+
}
612+
changeset
613+
}
614+
552615
/// Extends this graph with another so that `self` becomes the union of the two sets of
553616
/// transactions.
554617
///

crates/chain/tests/test_tx_graph.rs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1048,6 +1048,34 @@ fn test_changeset_last_seen_append() {
10481048
}
10491049
}
10501050

1051+
#[test]
1052+
fn update_last_seen_unconfirmed() {
1053+
let mut graph = TxGraph::<()>::default();
1054+
let tx = new_tx(0);
1055+
let txid = tx.txid();
1056+
1057+
// insert a new tx
1058+
// initially we have a last_seen of 0, and no anchors
1059+
let _ = graph.insert_tx(tx);
1060+
let tx = graph.full_txs().next().unwrap();
1061+
assert_eq!(tx.last_seen_unconfirmed, 0);
1062+
assert!(tx.anchors.is_empty());
1063+
1064+
// higher timestamp should update last seen
1065+
let changeset = graph.update_last_seen_unconfirmed(2);
1066+
assert_eq!(changeset.last_seen.get(&txid).unwrap(), &2);
1067+
1068+
// lower timestamp has no effect
1069+
let changeset = graph.update_last_seen_unconfirmed(1);
1070+
assert!(changeset.last_seen.is_empty());
1071+
1072+
// once anchored, last seen is not updated
1073+
let _ = graph.insert_anchor(txid, ());
1074+
let changeset = graph.update_last_seen_unconfirmed(4);
1075+
assert!(changeset.is_empty());
1076+
assert_eq!(graph.full_txs().next().unwrap().last_seen_unconfirmed, 2);
1077+
}
1078+
10511079
#[test]
10521080
fn test_missing_blocks() {
10531081
/// An anchor implementation for testing, made up of `(the_anchor_block, random_data)`.

0 commit comments

Comments
 (0)