@@ -15,8 +15,8 @@ use bitcoin::{
1515 bip158:: { self , BlockFilter } ,
1616 Block , BlockHash , ScriptBuf ,
1717} ;
18- use bitcoincore_rpc;
1918use bitcoincore_rpc:: RpcApi ;
19+ use bitcoincore_rpc:: { self , jsonrpc} ;
2020
2121/// Block height
2222type Height = u32 ;
@@ -44,9 +44,6 @@ pub struct FilterIter<'c, C> {
4444}
4545
4646impl < ' c , C : RpcApi > FilterIter < ' c , C > {
47- /// Hard cap on how far to walk back when a reorg is detected.
48- const MAX_REORG_DEPTH : u32 = 100 ;
49-
5047 /// Construct [`FilterIter`] from a given `client` and start `height`.
5148 pub fn new_with_height ( client : & ' c C , height : u32 ) -> Self {
5249 Self {
@@ -148,82 +145,7 @@ impl<C: RpcApi> Iterator for FilterIter<'_, C> {
148145 type Item = Result < Event , Error > ;
149146
150147 fn next ( & mut self ) -> Option < Self :: Item > {
151- ( || -> Result < Option < _ > , Error > {
152- if self . height > self . stop {
153- return Ok ( None ) ;
154- }
155- // Fetch next header.
156- let mut height = self . height ;
157- let mut hash = self . client . get_block_hash ( height as _ ) ?;
158- let mut header = self . client . get_block_header ( & hash) ?;
159-
160- // Detect and resolve reorgs: either block at height changed, or its parent changed.
161- let stored_hash = self . blocks . get ( & height) . copied ( ) ;
162- let prev_hash = height
163- . checked_sub ( 1 )
164- . and_then ( |height| self . blocks . get ( & height) . copied ( ) ) ;
165-
166- // If we've seen this height before but the hash has changed, or parent changed, trigger
167- // reorg.
168- let reorg_detected = if let Some ( old_hash) = stored_hash {
169- old_hash != hash
170- } else if let Some ( expected_prev) = prev_hash {
171- header. prev_blockhash != expected_prev
172- } else {
173- false
174- } ;
175-
176- // Reorg detected, rewind to last known-good ancestor.
177- if reorg_detected {
178- let mut reorg_depth = 0 ;
179- loop {
180- if reorg_depth >= Self :: MAX_REORG_DEPTH || height == 0 {
181- return Err ( Error :: ReorgDepthExceeded ) ;
182- }
183-
184- height = height. saturating_sub ( 1 ) ;
185- hash = self . client . get_block_hash ( height as _ ) ?;
186- header = self . client . get_block_header ( & hash) ?;
187-
188- let prev_height = height. saturating_sub ( 1 ) ;
189- if let Some ( prev_hash) = self . blocks . get ( & prev_height) {
190- if header. prev_blockhash == * prev_hash {
191- break ;
192- }
193- }
194-
195- reorg_depth += 1 ;
196- }
197-
198- self . blocks . split_off ( & height) ;
199- self . matched . split_off ( & height) ;
200- }
201-
202- let filter_bytes = self . client . get_block_filter ( & hash) ?. filter ;
203- let filter = BlockFilter :: new ( & filter_bytes) ;
204-
205- // record the scanned block
206- self . blocks . insert ( height, hash) ;
207- // increment best height
208- self . height = height. saturating_add ( 1 ) ;
209-
210- // If the filter matches any of our watched SPKs, fetch the full
211- // block, and record the matching block entry.
212- if self . spks . is_empty ( ) {
213- Err ( Error :: NoScripts )
214- } else if filter
215- . match_any ( & hash, self . spks . iter ( ) . map ( |s| s. as_bytes ( ) ) )
216- . map_err ( Error :: Bip158 ) ?
217- {
218- let block = self . client . get_block ( & hash) ?;
219- self . matched . insert ( height) ;
220- let inner = EventInner { height, block } ;
221- Ok ( Some ( Event :: Block ( inner) ) )
222- } else {
223- Ok ( Some ( Event :: NoMatch ( height) ) )
224- }
225- } ) ( )
226- . transpose ( )
148+ self . next_event ( ) . transpose ( )
227149 }
228150}
229151
@@ -274,6 +196,99 @@ impl<C: RpcApi> FilterIter<'_, C> {
274196 . expect ( "blocks must be in order" ) ,
275197 )
276198 }
199+
200+ fn next_event ( & mut self ) -> Result < Option < Event > , Error > {
201+ let ( height, hash) = match self . find_next_block ( ) ? {
202+ None => return Ok ( None ) ,
203+ Some ( ( height, _) ) if height > self . stop => return Ok ( None ) ,
204+ Some ( block) => block,
205+ } ;
206+
207+ // Emit and increment `height` (which should really be `next_height`).
208+ let is_match = BlockFilter :: new ( & self . client . get_block_filter ( & hash) ?. filter )
209+ . match_any ( & hash, self . spks . iter ( ) . map ( ScriptBuf :: as_ref) )
210+ . map_err ( Error :: Bip158 ) ?;
211+
212+ let event = if is_match {
213+ Event :: Block ( EventInner {
214+ height,
215+ block : self . client . get_block ( & hash) ?,
216+ } )
217+ } else {
218+ Event :: NoMatch ( height)
219+ } ;
220+
221+ // Mutate internal state at the end, once we are sure there are no more errors.
222+ if is_match {
223+ self . matched . insert ( height) ;
224+ }
225+ self . matched . split_off ( & height) ;
226+ self . blocks . split_off ( & height) ;
227+ self . blocks . insert ( height, hash) ;
228+ self . height = height. saturating_add ( 1 ) ;
229+ self . cp = self
230+ . cp
231+ . as_ref ( )
232+ . and_then ( |cp| cp. range ( ..=cp. height ( ) ) . next ( ) ) ;
233+
234+ Ok ( Some ( event) )
235+ }
236+
237+ /// Non-mutating method that finds the next block which connects with our previously-emitted
238+ /// history.
239+ fn find_next_block ( & self ) -> Result < Option < ( Height , BlockHash ) > , bitcoincore_rpc:: Error > {
240+ let mut height = self . height ;
241+
242+ // Search blocks backwards until we find a block which connects with something the consumer
243+ // has already seen.
244+ let hash = loop {
245+ let hash = match self . client . get_block_hash ( height as _ ) {
246+ Ok ( hash) => hash,
247+ Err ( bitcoincore_rpc:: Error :: JsonRpc ( jsonrpc:: Error :: Rpc ( rpc_err) ) )
248+ // -8: Out of bounds, -5: Not found
249+ if rpc_err. code == -8 || rpc_err. code == -5 =>
250+ {
251+ return Ok ( None )
252+ }
253+ Err ( err) => return Err ( err) ,
254+ } ;
255+ let header = self . client . get_block_header ( & hash) ?;
256+
257+ let prev_height = match height. checked_sub ( 1 ) {
258+ Some ( prev_height) => prev_height,
259+ // Always emit the genesis block as it cannot change.
260+ None => break hash,
261+ } ;
262+
263+ let prev_hash_remote = header. prev_blockhash ;
264+ if let Some ( & prev_hash) = self . blocks . get ( & prev_height) {
265+ if prev_hash == prev_hash_remote {
266+ break hash;
267+ }
268+ height = prev_height;
269+ continue ;
270+ }
271+
272+ let maybe_prev_cp = self
273+ . cp
274+ . as_ref ( )
275+ . and_then ( |cp| cp. range ( ..=prev_height) . next ( ) ) ;
276+ if let Some ( prev_cp) = maybe_prev_cp {
277+ if prev_cp. height ( ) != prev_height {
278+ // Try again at a height that the consumer can compare against.
279+ height = prev_cp. height ( ) ;
280+ continue ;
281+ }
282+ if prev_cp. hash ( ) != prev_hash_remote {
283+ height = prev_height;
284+ continue ;
285+ }
286+ }
287+ break hash;
288+ } ;
289+
290+ Ok ( Some ( ( height, hash) ) )
291+ }
277292}
278293
279294/// Errors that may occur during a compact filters sync.
@@ -285,8 +300,6 @@ pub enum Error {
285300 NoScripts ,
286301 /// `bitcoincore_rpc` error
287302 Rpc ( bitcoincore_rpc:: Error ) ,
288- /// `MAX_REORG_DEPTH` exceeded
289- ReorgDepthExceeded ,
290303}
291304
292305impl From < bitcoincore_rpc:: Error > for Error {
@@ -301,7 +314,6 @@ impl fmt::Display for Error {
301314 Self :: Bip158 ( e) => e. fmt ( f) ,
302315 Self :: NoScripts => write ! ( f, "no script pubkeys were provided to match with" ) ,
303316 Self :: Rpc ( e) => e. fmt ( f) ,
304- Self :: ReorgDepthExceeded => write ! ( f, "maximum reorg depth exceeded" ) ,
305317 }
306318 }
307319}
0 commit comments