@@ -15,8 +15,8 @@ use bitcoin::{
15
15
bip158:: { self , BlockFilter } ,
16
16
Block , BlockHash , ScriptBuf ,
17
17
} ;
18
- use bitcoincore_rpc;
19
18
use bitcoincore_rpc:: RpcApi ;
19
+ use bitcoincore_rpc:: { self , jsonrpc} ;
20
20
21
21
/// Block height
22
22
type Height = u32 ;
@@ -44,9 +44,6 @@ pub struct FilterIter<'c, C> {
44
44
}
45
45
46
46
impl < ' 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
-
50
47
/// Construct [`FilterIter`] from a given `client` and start `height`.
51
48
pub fn new_with_height ( client : & ' c C , height : u32 ) -> Self {
52
49
Self {
@@ -148,82 +145,7 @@ impl<C: RpcApi> Iterator for FilterIter<'_, C> {
148
145
type Item = Result < Event , Error > ;
149
146
150
147
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 ( )
227
149
}
228
150
}
229
151
@@ -274,6 +196,99 @@ impl<C: RpcApi> FilterIter<'_, C> {
274
196
. expect ( "blocks must be in order" ) ,
275
197
)
276
198
}
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
+ }
277
292
}
278
293
279
294
/// Errors that may occur during a compact filters sync.
@@ -285,8 +300,6 @@ pub enum Error {
285
300
NoScripts ,
286
301
/// `bitcoincore_rpc` error
287
302
Rpc ( bitcoincore_rpc:: Error ) ,
288
- /// `MAX_REORG_DEPTH` exceeded
289
- ReorgDepthExceeded ,
290
303
}
291
304
292
305
impl From < bitcoincore_rpc:: Error > for Error {
@@ -301,7 +314,6 @@ impl fmt::Display for Error {
301
314
Self :: Bip158 ( e) => e. fmt ( f) ,
302
315
Self :: NoScripts => write ! ( f, "no script pubkeys were provided to match with" ) ,
303
316
Self :: Rpc ( e) => e. fmt ( f) ,
304
- Self :: ReorgDepthExceeded => write ! ( f, "maximum reorg depth exceeded" ) ,
305
317
}
306
318
}
307
319
}
0 commit comments