99//! mempool.
1010#![ warn( missing_docs) ]
1111
12- use std:: collections:: BTreeMap ;
13-
12+ use bdk_chain:: { local_chain:: CheckPoint , BlockId } ;
1413use bitcoin:: { block:: Header , Block , BlockHash , Transaction } ;
1514pub use bitcoincore_rpc;
1615use bitcoincore_rpc:: bitcoincore_rpc_json;
@@ -24,7 +23,7 @@ pub struct Emitter<'c, C> {
2423 client : & ' c C ,
2524 start_height : u32 ,
2625
27- emitted_blocks : BTreeMap < u32 , BlockHash > ,
26+ last_cp : Option < CheckPoint > ,
2827 last_block : Option < bitcoincore_rpc_json:: GetBlockResult > ,
2928
3029 /// The latest first-seen epoch of emitted mempool transactions. This is used to determine
@@ -37,14 +36,29 @@ pub struct Emitter<'c, C> {
3736}
3837
3938impl < ' c , C : bitcoincore_rpc:: RpcApi > Emitter < ' c , C > {
40- /// Constructs a new [`Emitter`] with the provided [`bitcoincore_rpc::Client`] .
39+ /// Construct a new [`Emitter`] with the given RPC `client` and `start_height` .
4140 ///
4241 /// `start_height` is the block height to start emitting blocks from.
43- pub fn new ( client : & ' c C , start_height : u32 ) -> Self {
42+ pub fn from_height ( client : & ' c C , start_height : u32 ) -> Self {
4443 Self {
4544 client,
4645 start_height,
47- emitted_blocks : BTreeMap :: new ( ) ,
46+ last_cp : None ,
47+ last_block : None ,
48+ last_mempool_time : 0 ,
49+ last_mempool_tip : None ,
50+ }
51+ }
52+
53+ /// Construct a new [`Emitter`] with the given RPC `client` and `checkpoint`.
54+ ///
55+ /// `checkpoint` is used to find the latest block which is still part of the best chain. The
56+ /// [`Emitter`] will emit blocks starting right above this block.
57+ pub fn from_checkpoint ( client : & ' c C , checkpoint : CheckPoint ) -> Self {
58+ Self {
59+ client,
60+ start_height : 0 ,
61+ last_cp : Some ( checkpoint) ,
4862 last_block : None ,
4963 last_mempool_time : 0 ,
5064 last_mempool_tip : None ,
@@ -114,7 +128,7 @@ impl<'c, C: bitcoincore_rpc::RpcApi> Emitter<'c, C> {
114128 . collect :: < Result < Vec < _ > , _ > > ( ) ?;
115129
116130 self . last_mempool_time = latest_time;
117- self . last_mempool_tip = self . emitted_blocks . iter ( ) . last ( ) . map ( |( & height , _ ) | height) ;
131+ self . last_mempool_tip = self . last_cp . as_ref ( ) . map ( |cp| cp . height ( ) ) ;
118132
119133 Ok ( txs_to_emit)
120134 }
@@ -135,7 +149,7 @@ enum PollResponse {
135149 NoMoreBlocks ,
136150 /// Fetched block is not in the best chain.
137151 BlockNotInBestChain ,
138- AgreementFound ( bitcoincore_rpc_json:: GetBlockResult ) ,
152+ AgreementFound ( bitcoincore_rpc_json:: GetBlockResult , CheckPoint ) ,
139153 AgreementPointNotFound ,
140154}
141155
@@ -146,7 +160,10 @@ where
146160 let client = emitter. client ;
147161
148162 if let Some ( last_res) = & emitter. last_block {
149- assert ! ( !emitter. emitted_blocks. is_empty( ) ) ;
163+ assert ! (
164+ emitter. last_cp. is_some( ) ,
165+ "must not have block result without last cp"
166+ ) ;
150167
151168 let next_hash = match last_res. nextblockhash {
152169 None => return Ok ( PollResponse :: NoMoreBlocks ) ,
@@ -160,7 +177,7 @@ where
160177 return Ok ( PollResponse :: Block ( res) ) ;
161178 }
162179
163- if emitter. emitted_blocks . is_empty ( ) {
180+ if emitter. last_cp . is_none ( ) {
164181 let hash = client. get_block_hash ( emitter. start_height as _ ) ?;
165182
166183 let res = client. get_block_info ( & hash) ?;
@@ -170,15 +187,15 @@ where
170187 return Ok ( PollResponse :: Block ( res) ) ;
171188 }
172189
173- for ( & _ , hash ) in emitter. emitted_blocks . iter ( ) . rev ( ) {
174- let res = client. get_block_info ( hash) ?;
190+ for cp in emitter. last_cp . iter ( ) . flat_map ( CheckPoint :: iter ) {
191+ let res = client. get_block_info ( & cp . hash ( ) ) ?;
175192 if res. confirmations < 0 {
176193 // block is not in best chain
177194 continue ;
178195 }
179196
180197 // agreement point found
181- return Ok ( PollResponse :: AgreementFound ( res) ) ;
198+ return Ok ( PollResponse :: AgreementFound ( res, cp ) ) ;
182199 }
183200
184201 Ok ( PollResponse :: AgreementPointNotFound )
@@ -196,9 +213,28 @@ where
196213 match poll_once ( emitter) ? {
197214 PollResponse :: Block ( res) => {
198215 let height = res. height as u32 ;
199- let item = get_item ( & res. hash ) ?;
200- assert_eq ! ( emitter. emitted_blocks. insert( height, res. hash) , None ) ;
216+ let hash = res. hash ;
217+ let item = get_item ( & hash) ?;
218+
219+ let this_id = BlockId { height, hash } ;
220+ let prev_id = res. previousblockhash . map ( |prev_hash| BlockId {
221+ height : height - 1 ,
222+ hash : prev_hash,
223+ } ) ;
224+
225+ match ( & mut emitter. last_cp , prev_id) {
226+ ( Some ( cp) , _) => * cp = cp. clone ( ) . push ( this_id) . expect ( "must push" ) ,
227+ ( last_cp, None ) => * last_cp = Some ( CheckPoint :: new ( this_id) ) ,
228+ // When the receiver constructs a local_chain update from a block, the previous
229+ // checkpoint is also included in the update. We need to reflect this state in
230+ // `Emitter::last_cp` as well.
231+ ( last_cp, Some ( prev_id) ) => {
232+ * last_cp = Some ( CheckPoint :: new ( prev_id) . push ( this_id) . expect ( "must push" ) )
233+ }
234+ }
235+
201236 emitter. last_block = Some ( res) ;
237+
202238 return Ok ( Some ( ( height, item) ) ) ;
203239 }
204240 PollResponse :: NoMoreBlocks => {
@@ -209,11 +245,11 @@ where
209245 emitter. last_block = None ;
210246 continue ;
211247 }
212- PollResponse :: AgreementFound ( res) => {
248+ PollResponse :: AgreementFound ( res, cp ) => {
213249 let agreement_h = res. height as u32 ;
214250
215251 // get rid of evicted blocks
216- emitter. emitted_blocks . split_off ( & ( agreement_h + 1 ) ) ;
252+ emitter. last_cp = Some ( cp ) ;
217253
218254 // The tip during the last mempool emission needs to in the best chain, we reduce
219255 // it if it is not.
@@ -226,7 +262,11 @@ where
226262 continue ;
227263 }
228264 PollResponse :: AgreementPointNotFound => {
229- emitter. emitted_blocks . clear ( ) ;
265+ // We want to clear `last_cp` and set `start_height` to the first checkpoint's
266+ // height. This way, the first checkpoint in `LocalChain` can be replaced.
267+ if let Some ( last_cp) = emitter. last_cp . take ( ) {
268+ emitter. start_height = last_cp. height ( ) ;
269+ }
230270 emitter. last_block = None ;
231271 continue ;
232272 }
0 commit comments