1010#![ warn( missing_docs) ]
1111
1212use bdk_core:: { BlockId , CheckPoint } ;
13- use bitcoin:: { block:: Header , Block , BlockHash , Transaction } ;
13+ use bitcoin:: { block:: Header , Block , BlockHash , Transaction , Txid } ;
1414use bitcoincore_rpc:: bitcoincore_rpc_json;
15+ use std:: collections:: HashSet ;
1516
1617pub mod bip158;
1718
@@ -43,6 +44,9 @@ pub struct Emitter<'c, C> {
4344 /// The last emitted block during our last mempool emission. This is used to determine whether
4445 /// there has been a reorg since our last mempool emission.
4546 last_mempool_tip : Option < u32 > ,
47+
48+ /// Expected mempool txs. TODO: Docs.
49+ expected_mempool_txids : HashSet < Txid > ,
4650}
4751
4852impl < ' c , C : bitcoincore_rpc:: RpcApi > Emitter < ' c , C > {
@@ -53,29 +57,38 @@ impl<'c, C: bitcoincore_rpc::RpcApi> Emitter<'c, C> {
5357 ///
5458 /// `start_height` starts emission from a given height (if there are no conflicts with the
5559 /// original chain).
56- pub fn new ( client : & ' c C , last_cp : CheckPoint , start_height : u32 ) -> Self {
60+ pub fn new (
61+ client : & ' c C ,
62+ last_cp : CheckPoint ,
63+ start_height : u32 ,
64+ expected_mempool_txids : HashSet < Txid > ,
65+ ) -> Self {
5766 Self {
5867 client,
5968 start_height,
6069 last_cp,
6170 last_block : None ,
6271 last_mempool_time : 0 ,
6372 last_mempool_tip : None ,
73+ expected_mempool_txids,
6474 }
6575 }
6676
67- /// Emit mempool transactions, alongside their first-seen unix timestamps .
77+ /// Emit mempool transactions and capture the initial snapshot of all mempool [`Txid`]s .
6878 ///
69- /// This method emits each transaction only once, unless we cannot guarantee the transaction's
70- /// ancestors are already emitted.
79+ /// This method returns a [`MempoolEvent`] containing the full transactions (with their
80+ /// first-seen unix timestamps) that were emitted, and the set of all [`Txid`]s present from the
81+ /// initial mempool query. Each transaction is emitted only once, unless we cannot guarantee the
82+ /// transaction's ancestors are already emitted.
7183 ///
7284 /// To understand why, consider a receiver which filters transactions based on whether it
7385 /// alters the UTXO set of tracked script pubkeys. If an emitted mempool transaction spends a
7486 /// tracked UTXO which is confirmed at height `h`, but the receiver has only seen up to block
7587 /// of height `h-1`, we want to re-emit this transaction until the receiver has seen the block
7688 /// at height `h`.
77- pub fn mempool ( & mut self ) -> Result < Vec < ( Transaction , u64 ) > , bitcoincore_rpc:: Error > {
89+ pub fn mempool ( & mut self ) -> Result < MempoolEvent , bitcoincore_rpc:: Error > {
7890 let client = self . client ;
91+ let mut evicted_txids = HashSet :: < Txid > :: new ( ) ;
7992
8093 // This is the emitted tip height during the last mempool emission.
8194 let prev_mempool_tip = self
@@ -84,18 +97,39 @@ impl<'c, C: bitcoincore_rpc::RpcApi> Emitter<'c, C> {
8497 // `start_height` has been emitted.
8598 . unwrap_or ( self . start_height . saturating_sub ( 1 ) ) ;
8699
100+ // Clear out `expected_mempool_txids` if we are on a different block.
101+ if prev_mempool_tip != self . last_cp . height ( ) {
102+ self . expected_mempool_txids . clear ( ) ;
103+ }
104+
87105 // Mempool txs come with a timestamp of when the tx is introduced to the mempool. We keep
88106 // track of the latest mempool tx's timestamp to determine whether we have seen a tx
89107 // before. `prev_mempool_time` is the previous timestamp and `last_time` records what will
90108 // be the new latest timestamp.
91109 let prev_mempool_time = self . last_mempool_time ;
92110 let mut latest_time = prev_mempool_time;
93111
94- let txs_to_emit = client
95- . get_raw_mempool_verbose ( ) ?
112+ // Get the raw mempool result from the RPC client.
113+ let raw_mempool = client. get_raw_mempool_verbose ( ) ?;
114+ let raw_mempool_txids = raw_mempool. keys ( ) . copied ( ) . collect :: < HashSet < _ > > ( ) ;
115+
116+ // Check if missing txs have been confirmed. If not, they have been evicted.
117+ for & txid in self . expected_mempool_txids . difference ( & raw_mempool_txids) {
118+ // Check if missing tx was confirmed. If not, then the tx has been evicted. Tx is also
119+ // considered evicted if it was not found.
120+ if client
121+ . get_transaction ( & txid, None )
122+ . map_or ( true , |tx_res| tx_res. info . confirmations <= 0 )
123+ {
124+ evicted_txids. insert ( txid) ;
125+ }
126+ }
127+
128+ let new_txs = raw_mempool
96129 . into_iter ( )
97130 . filter_map ( {
98131 let latest_time = & mut latest_time;
132+ let evicted_txids = & mut evicted_txids;
99133 move |( txid, tx_entry) | -> Option < Result < _ , bitcoincore_rpc:: Error > > {
100134 let tx_time = tx_entry. time as usize ;
101135 if tx_time > * latest_time {
@@ -115,8 +149,18 @@ impl<'c, C: bitcoincore_rpc::RpcApi> Emitter<'c, C> {
115149
116150 let tx = match client. get_raw_transaction ( & txid, None ) {
117151 Ok ( tx) => tx,
118- // the tx is confirmed or evicted since `get_raw_mempool_verbose`
119- Err ( err) if err. is_not_found_error ( ) => return None ,
152+ Err ( err) if err. is_not_found_error ( ) => {
153+ // Check if the tx was confirmed since `get_raw_mempool_verbose`. If
154+ // not, then the tx has been evicted. Tx is also considered evicted if
155+ // it was not found.
156+ if client
157+ . get_transaction ( & txid, None )
158+ . map_or ( true , |tx_res| tx_res. info . confirmations <= 0 )
159+ {
160+ evicted_txids. insert ( txid) ;
161+ }
162+ return None ;
163+ }
120164 Err ( err) => return Some ( Err ( err) ) ,
121165 } ;
122166
@@ -127,8 +171,13 @@ impl<'c, C: bitcoincore_rpc::RpcApi> Emitter<'c, C> {
127171
128172 self . last_mempool_time = latest_time;
129173 self . last_mempool_tip = Some ( self . last_cp . height ( ) ) ;
174+ self . expected_mempool_txids = new_txs. iter ( ) . map ( |( tx, _) | tx. compute_txid ( ) ) . collect ( ) ;
130175
131- Ok ( txs_to_emit)
176+ Ok ( MempoolEvent {
177+ new_txs,
178+ evicted_txids,
179+ latest_update_time : latest_time as u64 ,
180+ } )
132181 }
133182
134183 /// Emit the next block height and header (if any).
@@ -144,6 +193,27 @@ impl<'c, C: bitcoincore_rpc::RpcApi> Emitter<'c, C> {
144193 }
145194}
146195
196+ /// A new emission from mempool.
197+ #[ derive( Debug ) ]
198+ pub struct MempoolEvent {
199+ /// Unemitted transactions or transactions with ancestors that are unseen by the receiver.
200+ ///
201+ /// To understand the second condition, consider a receiver which filters transactions based on
202+ /// whether it alters the UTXO set of tracked script pubkeys. If an emitted mempool transaction
203+ /// spends a tracked UTXO which is confirmed at height `h`, but the receiver has only seen up to
204+ /// block of height `h-1`, we want to re-emit this transaction until the receiver has seen the
205+ /// block at height `h`.
206+ pub new_txs : Vec < ( Transaction , u64 ) > ,
207+
208+ /// [`Txid`]s of all transactions that have been evicted from mempool.
209+ pub evicted_txids : HashSet < Txid > ,
210+
211+ /// The latest timestamp of when a transaction entered the mempool.
212+ ///
213+ /// This is useful for setting the timestamp for evicted transactions.
214+ pub latest_update_time : u64 ,
215+ }
216+
147217/// A newly emitted block from [`Emitter`].
148218#[ derive( Debug ) ]
149219pub struct BlockEvent < B > {
0 commit comments