16
16
//! documentation for more details), and the timestamp of the last time we saw the transaction as
17
17
//! unconfirmed.
18
18
//!
19
- //! Conflicting transactions are allowed to coexist within a [`TxGraph`]. This is useful for
20
- //! identifying and traversing conflicts and descendants of a given transaction. Some [`TxGraph`]
21
- //! methods only consider transactions that are "canonical" (i.e., in the best chain or in mempool).
22
- //! We decide which transactions are canonical based on the transaction's anchors and the
23
- //! `last_seen` (as unconfirmed) timestamp.
19
+ //! # Canonicalization
24
20
//!
25
- //! The [`ChangeSet`] reports changes made to a [`TxGraph`]; it can be used to either save to
26
- //! persistent storage, or to be applied to another [`TxGraph`].
21
+ //! Conflicting transactions are allowed to coexist within a [`TxGraph`]. A process called
22
+ //! canonicalization is required to get a conflict-free history of transactions.
23
+ //!
24
+ //! * [`list_canonical_txs`](TxGraph::list_canonical_txs) lists canonical transactions.
25
+ //! * [`filter_chain_txouts`](TxGraph::filter_chain_txouts) filters out canonical outputs from a
26
+ //! list of outpoints.
27
+ //! * [`filter_chain_unspents`](TxGraph::filter_chain_unspents) filters out canonical unspent
28
+ //! outputs from a list of outpoints.
29
+ //! * [`balance`](TxGraph::balance) gets the total sum of unspent outputs filtered from a list of
30
+ //! outpoints.
31
+ //! * [`canonical_iter`](TxGraph::canonical_iter) returns the [`CanonicalIter`] which contains all
32
+ //! of the canonicalization logic.
33
+ //!
34
+ //! All these methods require a `chain` and `chain_tip` argument. The `chain` must be a
35
+ //! [`ChainOracle`] implementation (such as [`LocalChain`](crate::local_chain::LocalChain)) which
36
+ //! identifies which blocks exist under a given `chain_tip`.
37
+ //!
38
+ //! The canonicalization algorithm uses the following associated data to determine which
39
+ //! transactions have precedence over others:
27
40
//!
28
- //! Lastly, you can use [`TxAncestors`]/[`TxDescendants`] to traverse ancestors and descendants of
29
- //! a given transaction, respectively.
41
+ //! * [`Anchor`] - This bit of data represents that a transaction is anchored in a given block. If
42
+ //! the transaction is anchored in chain of `chain_tip`, or is an ancestor of a transaction
43
+ //! anchored in chain of `chain_tip`, then the transaction must be canonical.
44
+ //! * `last_seen` - This is the timestamp of when a transaction is last-seen in the mempool. This
45
+ //! value is updated by [`insert_seen_at`](TxGraph::insert_seen_at) and
46
+ //! [`apply_update`](TxGraph::apply_update). Transactions that are seen later have higher
47
+ //! priority than those that are seen earlier. `last_seen` values are transitive. Meaning that
48
+ //! the actual `last_seen` value of a transaction is the max of all the `last_seen` values of
49
+ //! it's descendants.
50
+ //! * `last_evicted` - This is the timestamp of when a transaction is last-seen as evicted in the
51
+ //! mempool. If this value is equal to or higher than the transaction's `last_seen` value, then
52
+ //! it will not be considered canonical.
53
+ //!
54
+ //! # Graph traversal
55
+ //!
56
+ //! You can use [`TxAncestors`]/[`TxDescendants`] to traverse ancestors and descendants of a given
57
+ //! transaction, respectively.
30
58
//!
31
59
//! # Applying changes
32
60
//!
61
+ //! The [`ChangeSet`] reports changes made to a [`TxGraph`]; it can be used to either save to
62
+ //! persistent storage, or to be applied to another [`TxGraph`].
63
+ //!
33
64
//! Methods that change the state of [`TxGraph`] will return [`ChangeSet`]s.
34
- //! [`ChangeSet`]s can be applied back to a [`TxGraph`] or be used to inform persistent storage
35
- //! of the changes to [`TxGraph`].
36
65
//!
37
66
//! # Generics
38
67
//!
@@ -122,6 +151,7 @@ impl<A: Ord> From<TxGraph<A>> for TxUpdate<A> {
122
151
. flat_map ( |( txid, anchors) | anchors. into_iter ( ) . map ( move |a| ( a, txid) ) )
123
152
. collect ( ) ;
124
153
tx_update. seen_ats = graph. last_seen . into_iter ( ) . collect ( ) ;
154
+ tx_update. evicted_ats = graph. last_evicted . into_iter ( ) . collect ( ) ;
125
155
tx_update
126
156
}
127
157
}
@@ -145,6 +175,7 @@ pub struct TxGraph<A = ConfirmationBlockTime> {
145
175
spends : BTreeMap < OutPoint , HashSet < Txid > > ,
146
176
anchors : HashMap < Txid , BTreeSet < A > > ,
147
177
last_seen : HashMap < Txid , u64 > ,
178
+ last_evicted : HashMap < Txid , u64 > ,
148
179
149
180
txs_by_highest_conf_heights : BTreeSet < ( u32 , Txid ) > ,
150
181
txs_by_last_seen : BTreeSet < ( u64 , Txid ) > ,
@@ -162,6 +193,7 @@ impl<A> Default for TxGraph<A> {
162
193
spends : Default :: default ( ) ,
163
194
anchors : Default :: default ( ) ,
164
195
last_seen : Default :: default ( ) ,
196
+ last_evicted : Default :: default ( ) ,
165
197
txs_by_highest_conf_heights : Default :: default ( ) ,
166
198
txs_by_last_seen : Default :: default ( ) ,
167
199
empty_outspends : Default :: default ( ) ,
@@ -715,6 +747,34 @@ impl<A: Anchor> TxGraph<A> {
715
747
changeset
716
748
}
717
749
750
+ /// Inserts the given `evicted_at` for `txid` into [`TxGraph`].
751
+ ///
752
+ /// The `evicted_at` timestamp represents the last known time when the transaction was observed
753
+ /// to be missing from the mempool. If `txid` was previously recorded with an earlier
754
+ /// `evicted_at` value, it is updated only if the new value is greater.
755
+ pub fn insert_evicted_at ( & mut self , txid : Txid , evicted_at : u64 ) -> ChangeSet < A > {
756
+ let is_changed = match self . last_evicted . entry ( txid) {
757
+ hash_map:: Entry :: Occupied ( mut e) => {
758
+ let last_evicted = e. get_mut ( ) ;
759
+ let change = * last_evicted < evicted_at;
760
+ if change {
761
+ * last_evicted = evicted_at;
762
+ }
763
+ change
764
+ }
765
+ hash_map:: Entry :: Vacant ( e) => {
766
+ e. insert ( evicted_at) ;
767
+ true
768
+ }
769
+ } ;
770
+
771
+ let mut changeset = ChangeSet :: < A > :: default ( ) ;
772
+ if is_changed {
773
+ changeset. last_evicted . insert ( txid, evicted_at) ;
774
+ }
775
+ changeset
776
+ }
777
+
718
778
/// Extends this graph with the given `update`.
719
779
///
720
780
/// The returned [`ChangeSet`] is the set difference between `update` and `self` (transactions that
@@ -733,6 +793,9 @@ impl<A: Anchor> TxGraph<A> {
733
793
for ( txid, seen_at) in update. seen_ats {
734
794
changeset. merge ( self . insert_seen_at ( txid, seen_at) ) ;
735
795
}
796
+ for ( txid, evicted_at) in update. evicted_ats {
797
+ changeset. merge ( self . insert_evicted_at ( txid, evicted_at) ) ;
798
+ }
736
799
changeset
737
800
}
738
801
@@ -750,6 +813,7 @@ impl<A: Anchor> TxGraph<A> {
750
813
. flat_map ( |( txid, anchors) | anchors. iter ( ) . map ( |a| ( a. clone ( ) , * txid) ) )
751
814
. collect ( ) ,
752
815
last_seen : self . last_seen . iter ( ) . map ( |( & k, & v) | ( k, v) ) . collect ( ) ,
816
+ last_evicted : self . last_evicted . iter ( ) . map ( |( & k, & v) | ( k, v) ) . collect ( ) ,
753
817
}
754
818
}
755
819
@@ -767,6 +831,9 @@ impl<A: Anchor> TxGraph<A> {
767
831
for ( txid, seen_at) in changeset. last_seen {
768
832
let _ = self . insert_seen_at ( txid, seen_at) ;
769
833
}
834
+ for ( txid, evicted_at) in changeset. last_evicted {
835
+ let _ = self . insert_evicted_at ( txid, evicted_at) ;
836
+ }
770
837
}
771
838
}
772
839
@@ -937,9 +1004,14 @@ impl<A: Anchor> TxGraph<A> {
937
1004
938
1005
/// List txids by descending last-seen order.
939
1006
///
940
- /// Transactions without last-seens are excluded.
941
- pub fn txids_by_descending_last_seen ( & self ) -> impl ExactSizeIterator < Item = ( u64 , Txid ) > + ' _ {
942
- self . txs_by_last_seen . iter ( ) . copied ( ) . rev ( )
1007
+ /// Transactions without last-seens are excluded. Transactions with a last-evicted timestamp
1008
+ /// equal or higher than it's last-seen timestamp are excluded.
1009
+ pub fn txids_by_descending_last_seen ( & self ) -> impl Iterator < Item = ( u64 , Txid ) > + ' _ {
1010
+ self . txs_by_last_seen
1011
+ . iter ( )
1012
+ . copied ( )
1013
+ . rev ( )
1014
+ . filter ( |( last_seen, txid) | !matches ! ( self . last_evicted. get( txid) , Some ( last_evicted) if last_evicted >= last_seen) )
943
1015
}
944
1016
945
1017
/// Returns a [`CanonicalIter`].
@@ -1107,6 +1179,8 @@ pub struct ChangeSet<A = ()> {
1107
1179
pub anchors : BTreeSet < ( A , Txid ) > ,
1108
1180
/// Added last-seen unix timestamps of transactions.
1109
1181
pub last_seen : BTreeMap < Txid , u64 > ,
1182
+ /// Added timestamps of when a transaction is last evicted from the mempool.
1183
+ pub last_evicted : BTreeMap < Txid , u64 > ,
1110
1184
}
1111
1185
1112
1186
impl < A > Default for ChangeSet < A > {
@@ -1116,6 +1190,7 @@ impl<A> Default for ChangeSet<A> {
1116
1190
txouts : Default :: default ( ) ,
1117
1191
anchors : Default :: default ( ) ,
1118
1192
last_seen : Default :: default ( ) ,
1193
+ last_evicted : Default :: default ( ) ,
1119
1194
}
1120
1195
}
1121
1196
}
@@ -1170,13 +1245,22 @@ impl<A: Ord> Merge for ChangeSet<A> {
1170
1245
. filter ( |( txid, update_ls) | self . last_seen . get ( txid) < Some ( update_ls) )
1171
1246
. collect :: < Vec < _ > > ( ) ,
1172
1247
) ;
1248
+ // last_evicted timestamps should only increase
1249
+ self . last_evicted . extend (
1250
+ other
1251
+ . last_evicted
1252
+ . into_iter ( )
1253
+ . filter ( |( txid, update_lm) | self . last_evicted . get ( txid) < Some ( update_lm) )
1254
+ . collect :: < Vec < _ > > ( ) ,
1255
+ ) ;
1173
1256
}
1174
1257
1175
1258
fn is_empty ( & self ) -> bool {
1176
1259
self . txs . is_empty ( )
1177
1260
&& self . txouts . is_empty ( )
1178
1261
&& self . anchors . is_empty ( )
1179
1262
&& self . last_seen . is_empty ( )
1263
+ && self . last_evicted . is_empty ( )
1180
1264
}
1181
1265
}
1182
1266
@@ -1196,6 +1280,7 @@ impl<A: Ord> ChangeSet<A> {
1196
1280
self . anchors . into_iter ( ) . map ( |( a, txid) | ( f ( a) , txid) ) ,
1197
1281
) ,
1198
1282
last_seen : self . last_seen ,
1283
+ last_evicted : self . last_evicted ,
1199
1284
}
1200
1285
}
1201
1286
}
0 commit comments