6
6
//! [0]: https://github.com/bitcoin/bips/blob/master/bip-0157.mediawiki
7
7
//! [1]: https://github.com/bitcoin/bips/blob/master/bip-0158.mediawiki
8
8
9
- use bdk_core:: collections:: BTreeMap ;
9
+ use bdk_core:: collections:: { BTreeMap , BTreeSet } ;
10
10
use core:: fmt;
11
11
12
12
use bdk_core:: bitcoin;
@@ -33,20 +33,29 @@ pub struct FilterIter<'c, C> {
33
33
cp : Option < CheckPoint > ,
34
34
// blocks map
35
35
blocks : BTreeMap < Height , BlockHash > ,
36
+ // set of heights with filters that matched any watched SPK
37
+ matched : BTreeSet < Height > ,
38
+ // initial height
39
+ start : Height ,
36
40
// best height counter
37
41
height : Height ,
38
42
// stop height
39
43
stop : Height ,
40
44
}
41
45
42
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
+
43
50
/// Construct [`FilterIter`] from a given `client` and start `height`.
44
51
pub fn new_with_height ( client : & ' c C , height : u32 ) -> Self {
45
52
Self {
46
53
client,
47
54
spks : vec ! [ ] ,
48
55
cp : None ,
49
56
blocks : BTreeMap :: new ( ) ,
57
+ matched : BTreeSet :: new ( ) ,
58
+ start : height,
50
59
height,
51
60
stop : 0 ,
52
61
}
@@ -69,57 +78,28 @@ impl<'c, C: RpcApi> FilterIter<'c, C> {
69
78
self . spks . push ( spk) ;
70
79
}
71
80
72
- /// Get the next filter and increment the current best height.
73
- ///
74
- /// Returns `Ok(None)` when the stop height is exceeded.
75
- fn next_filter ( & mut self ) -> Result < Option < NextFilter > , Error > {
76
- if self . height > self . stop {
77
- return Ok ( None ) ;
78
- }
79
- let height = self . height ;
80
- let hash = match self . blocks . get ( & height) {
81
- Some ( h) => * h,
82
- None => self . client . get_block_hash ( height as u64 ) ?,
83
- } ;
84
- let filter_bytes = self . client . get_block_filter ( & hash) ?. filter ;
85
- let filter = BlockFilter :: new ( & filter_bytes) ;
86
- self . height += 1 ;
87
- Ok ( Some ( ( BlockId { height, hash } , filter) ) )
81
+ /// Get the block hash by `height` if it is found in the blocks map.
82
+ fn get_block_hash ( & self , height : & Height ) -> Option < BlockHash > {
83
+ self . blocks . get ( height) . copied ( )
88
84
}
89
85
90
86
/// Get the remote tip.
91
87
///
92
- /// Returns `None` if the remote height is not strictly greater than the height of this
93
- /// [`FilterIter`].
88
+ /// Returns `None` if the remote height is less than the height of this [`FilterIter`].
94
89
pub fn get_tip ( & mut self ) -> Result < Option < BlockId > , Error > {
95
90
let tip_hash = self . client . get_best_block_hash ( ) ?;
96
- let mut header = self . client . get_block_header_info ( & tip_hash) ?;
91
+ let header = self . client . get_block_header_info ( & tip_hash) ?;
97
92
let tip_height = header. height as u32 ;
98
- if self . height >= tip_height {
93
+ // Allow returning tip if we're exactly at it. Return `None`` if we've already scanned past.
94
+ if self . height > tip_height {
99
95
// nothing to do
100
96
return Ok ( None ) ;
101
97
}
102
- self . blocks . insert ( tip_height, tip_hash) ;
103
98
104
- // if we have a checkpoint we use a lookback of ten blocks
105
- // to ensure consistency of the local chain
99
+ // start scanning from point of agreement + 1
106
100
if let Some ( cp) = self . cp . as_ref ( ) {
107
- // adjust start height to point of agreement + 1
108
101
let base = self . find_base_with ( cp. clone ( ) ) ?;
109
- self . height = base. height + 1 ;
110
-
111
- for _ in 0 ..9 {
112
- let hash = match header. previous_block_hash {
113
- Some ( hash) => hash,
114
- None => break ,
115
- } ;
116
- header = self . client . get_block_header_info ( & hash) ?;
117
- let height = header. height as u32 ;
118
- if height < self . height {
119
- break ;
120
- }
121
- self . blocks . insert ( height, hash) ;
122
- }
102
+ self . height = base. height . saturating_add ( 1 ) ;
123
103
}
124
104
125
105
self . stop = tip_height;
@@ -131,9 +111,6 @@ impl<'c, C: RpcApi> FilterIter<'c, C> {
131
111
}
132
112
}
133
113
134
- /// Alias for a compact filter and associated block id.
135
- type NextFilter = ( BlockId , BlockFilter ) ;
136
-
137
114
/// Event inner type
138
115
#[ derive( Debug , Clone ) ]
139
116
pub struct EventInner {
@@ -171,27 +148,86 @@ impl<C: RpcApi> Iterator for FilterIter<'_, C> {
171
148
type Item = Result < Event , Error > ;
172
149
173
150
fn next ( & mut self ) -> Option < Self :: Item > {
174
- ( || -> Result < _ , Error > {
175
- // if the next filter matches any of our watched spks, get the block
176
- // and return it, inserting relevant block ids along the way
177
- self . next_filter ( ) ?. map_or ( Ok ( None ) , |( block, filter) | {
178
- let height = block. height ;
179
- let hash = block. hash ;
180
-
181
- if self . spks . is_empty ( ) {
182
- Err ( Error :: NoScripts )
183
- } else if filter
184
- . match_any ( & hash, self . spks . iter ( ) . map ( |script| script. as_bytes ( ) ) )
185
- . map_err ( Error :: Bip158 ) ?
186
- {
187
- let block = self . client . get_block ( & hash) ?;
188
- self . blocks . insert ( height, hash) ;
189
- let inner = EventInner { height, block } ;
190
- Ok ( Some ( Event :: Block ( inner) ) )
191
- } else {
192
- Ok ( Some ( Event :: NoMatch ( height) ) )
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
+ self . blocks . split_off ( & height) ;
179
+ self . matched = self . matched . split_off ( & height) ;
180
+
181
+ let mut reorg_depth = 0 ;
182
+ loop {
183
+ if reorg_depth >= Self :: MAX_REORG_DEPTH || height == 0 {
184
+ return Err ( Error :: ReorgDepthExceeded ) ;
185
+ }
186
+
187
+ height = height. saturating_sub ( 1 ) ;
188
+ hash = self . client . get_block_hash ( height as _ ) ?;
189
+ header = self . client . get_block_header ( & hash) ?;
190
+
191
+ let prev_height = height. saturating_sub ( 1 ) ;
192
+ let prev_hash = self . client . get_block_hash ( prev_height as _ ) ?;
193
+ self . blocks . insert ( prev_height, prev_hash) ;
194
+
195
+ if let Some ( prev_hash) = self . blocks . get ( & prev_height) {
196
+ if header. prev_blockhash == * prev_hash {
197
+ break ;
198
+ }
199
+ }
200
+
201
+ reorg_depth += 1 ;
193
202
}
194
- } )
203
+
204
+ // Update self.height so we reprocess this height
205
+ self . height = height;
206
+ }
207
+
208
+ let filter_bytes = self . client . get_block_filter ( & hash) ?. filter ;
209
+ let filter = BlockFilter :: new ( & filter_bytes) ;
210
+
211
+ // record the scanned block
212
+ self . blocks . insert ( height, hash) ;
213
+ // increment best height
214
+ self . height = height. saturating_add ( 1 ) ;
215
+
216
+ // If the filter matches any of our watched SPKs, fetch the full
217
+ // block, and record the matching block entry.
218
+ if self . spks . is_empty ( ) {
219
+ Err ( Error :: NoScripts )
220
+ } else if filter
221
+ . match_any ( & hash, self . spks . iter ( ) . map ( |s| s. as_bytes ( ) ) )
222
+ . map_err ( Error :: Bip158 ) ?
223
+ {
224
+ let block = self . client . get_block ( & hash) ?;
225
+ self . matched . insert ( height) ;
226
+ let inner = EventInner { height, block } ;
227
+ Ok ( Some ( Event :: Block ( inner) ) )
228
+ } else {
229
+ Ok ( Some ( Event :: NoMatch ( height) ) )
230
+ }
195
231
} ) ( )
196
232
. transpose ( )
197
233
}
@@ -202,8 +238,8 @@ impl<C: RpcApi> FilterIter<'_, C> {
202
238
fn find_base_with ( & mut self , mut cp : CheckPoint ) -> Result < BlockId , Error > {
203
239
loop {
204
240
let height = cp. height ( ) ;
205
- let fetched_hash = match self . blocks . get ( & height) {
206
- Some ( hash) => * hash,
241
+ let fetched_hash = match self . get_block_hash ( & height) {
242
+ Some ( hash) => hash,
207
243
None if height == 0 => cp. hash ( ) ,
208
244
_ => self . client . get_block_hash ( height as _ ) ?,
209
245
} ;
@@ -221,17 +257,27 @@ impl<C: RpcApi> FilterIter<'_, C> {
221
257
/// Returns a chain update from the newly scanned blocks.
222
258
///
223
259
/// Returns `None` if this [`FilterIter`] was not constructed using a [`CheckPoint`], or
224
- /// if no blocks have been fetched for example by using [`get_tip`](Self::get_tip ).
260
+ /// if not all events have been emitted ( by calling `next` ).
225
261
pub fn chain_update ( & mut self ) -> Option < CheckPoint > {
226
- if self . cp . is_none ( ) || self . blocks . is_empty ( ) {
262
+ if self . cp . is_none ( ) || self . blocks . is_empty ( ) || self . height <= self . stop {
227
263
return None ;
228
264
}
229
265
230
- // note: to connect with the local chain we must guarantee that `self.blocks.first()`
231
- // is also the point of agreement with `self.cp`.
266
+ // We return blocks up to and including the initial height, all of the matching blocks,
267
+ // and blocks in the terminal range.
268
+ let tail_range = self . stop . saturating_sub ( 9 ) ..=self . stop ;
232
269
Some (
233
- CheckPoint :: from_block_ids ( self . blocks . iter ( ) . map ( BlockId :: from) )
234
- . expect ( "blocks must be in order" ) ,
270
+ CheckPoint :: from_block_ids ( self . blocks . iter ( ) . filter_map ( |( & height, & hash) | {
271
+ if height <= self . start
272
+ || self . matched . contains ( & height)
273
+ || tail_range. contains ( & height)
274
+ {
275
+ Some ( BlockId { height, hash } )
276
+ } else {
277
+ None
278
+ }
279
+ } ) )
280
+ . expect ( "blocks must be in order" ) ,
235
281
)
236
282
}
237
283
}
@@ -245,6 +291,8 @@ pub enum Error {
245
291
NoScripts ,
246
292
/// `bitcoincore_rpc` error
247
293
Rpc ( bitcoincore_rpc:: Error ) ,
294
+ /// `MAX_REORG_DEPTH` exceeded
295
+ ReorgDepthExceeded ,
248
296
}
249
297
250
298
impl From < bitcoincore_rpc:: Error > for Error {
@@ -259,6 +307,7 @@ impl fmt::Display for Error {
259
307
Self :: Bip158 ( e) => e. fmt ( f) ,
260
308
Self :: NoScripts => write ! ( f, "no script pubkeys were provided to match with" ) ,
261
309
Self :: Rpc ( e) => e. fmt ( f) ,
310
+ Self :: ReorgDepthExceeded => write ! ( f, "maximum reorg depth exceeded" ) ,
262
311
}
263
312
}
264
313
}
0 commit comments