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,21 +33,30 @@ pub struct FilterIter<'c, C> {
33
33
cp : Option < CheckPoint > ,
34
34
// blocks map
35
35
blocks : BTreeMap < Height , BlockHash > ,
36
+ // heights of matching blocks
37
+ matched : BTreeSet < Height > ,
36
38
// best height counter
37
39
height : Height ,
40
+ // initial height
41
+ start : 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 ( ) ,
50
58
height,
59
+ start : height,
51
60
stop : 0 ,
52
61
}
53
62
}
@@ -69,57 +78,21 @@ 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) ) )
88
- }
89
-
90
81
/// Get the remote tip.
91
82
///
92
- /// Returns `None` if the remote height is not strictly greater than the height of this
93
- /// [`FilterIter`].
83
+ /// Returns `None` if the remote height is less than the height of this [`FilterIter`].
94
84
pub fn get_tip ( & mut self ) -> Result < Option < BlockId > , Error > {
95
85
let tip_hash = self . client . get_best_block_hash ( ) ?;
96
- let mut header = self . client . get_block_header_info ( & tip_hash) ?;
86
+ let header = self . client . get_block_header_info ( & tip_hash) ?;
97
87
let tip_height = header. height as u32 ;
98
- if self . height >= tip_height {
99
- // nothing to do
88
+ if self . height > tip_height {
100
89
return Ok ( None ) ;
101
90
}
102
- self . blocks . insert ( tip_height, tip_hash) ;
103
91
104
- // if we have a checkpoint we use a lookback of ten blocks
105
- // to ensure consistency of the local chain
92
+ // start scanning from point of agreement + 1
106
93
if let Some ( cp) = self . cp . as_ref ( ) {
107
- // adjust start height to point of agreement + 1
108
94
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
- }
95
+ self . height = base. height . saturating_add ( 1 ) ;
123
96
}
124
97
125
98
self . stop = tip_height;
@@ -131,9 +104,6 @@ impl<'c, C: RpcApi> FilterIter<'c, C> {
131
104
}
132
105
}
133
106
134
- /// Alias for a compact filter and associated block id.
135
- type NextFilter = ( BlockId , BlockFilter ) ;
136
-
137
107
/// Event inner type
138
108
#[ derive( Debug , Clone ) ]
139
109
pub struct EventInner {
@@ -171,27 +141,71 @@ impl<C: RpcApi> Iterator for FilterIter<'_, C> {
171
141
type Item = Result < Event , Error > ;
172
142
173
143
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) ) )
144
+ ( || -> Result < Option < _ > , Error > {
145
+ if self . height > self . stop {
146
+ return Ok ( None ) ;
147
+ }
148
+ // Fetch next header.
149
+ let mut height = self . height ;
150
+ let mut hash = self . client . get_block_hash ( height as u64 ) ?;
151
+
152
+ let mut reorg_depth = 0 ;
153
+
154
+ loop {
155
+ if reorg_depth >= Self :: MAX_REORG_DEPTH {
156
+ return Err ( Error :: ReorgDepthExceeded ) ;
193
157
}
194
- } )
158
+
159
+ let header = self . client . get_block_header ( & hash) ?;
160
+
161
+ let prev_height = height. saturating_sub ( 1 ) ;
162
+ match self . blocks . get ( & prev_height) . copied ( ) {
163
+ // Not enough data.
164
+ None => break ,
165
+ // Ok, the chain is consistent.
166
+ Some ( prev_hash) if prev_hash == header. prev_blockhash => break ,
167
+ _ => {
168
+ // Reorg detected, keep backtracking.
169
+ height = height. saturating_sub ( 1 ) ;
170
+ hash = self . client . get_block_hash ( height as u64 ) ?;
171
+ reorg_depth += 1 ;
172
+ }
173
+ }
174
+ }
175
+
176
+ let filter_bytes = self . client . get_block_filter ( & hash) ?. filter ;
177
+ let filter = BlockFilter :: new ( & filter_bytes) ;
178
+
179
+ // If the filter matches any of our watched SPKs, fetch the full
180
+ // block and prepare the next event.
181
+ let next_event = if self . spks . is_empty ( ) {
182
+ Err ( Error :: NoScripts )
183
+ } else if filter
184
+ . match_any ( & hash, self . spks . iter ( ) . map ( |s| s. as_bytes ( ) ) )
185
+ . map_err ( Error :: Bip158 ) ?
186
+ {
187
+ let block = self . client . get_block ( & hash) ?;
188
+ let inner = EventInner { height, block } ;
189
+ Ok ( Some ( Event :: Block ( inner) ) )
190
+ } else {
191
+ Ok ( Some ( Event :: NoMatch ( height) ) )
192
+ } ;
193
+
194
+ // In case of a reorg, throw out any stale entries.
195
+ if reorg_depth > 0 {
196
+ self . blocks . split_off ( & height) ;
197
+ self . matched . split_off ( & height) ;
198
+ }
199
+ // Record the scanned block
200
+ self . blocks . insert ( height, hash) ;
201
+ // Record the matching block
202
+ if let Ok ( Some ( Event :: Block ( ..) ) ) = next_event {
203
+ self . matched . insert ( height) ;
204
+ }
205
+ // Increment next height
206
+ self . height = height. saturating_add ( 1 ) ;
207
+
208
+ next_event
195
209
} ) ( )
196
210
. transpose ( )
197
211
}
@@ -220,17 +234,27 @@ impl<C: RpcApi> FilterIter<'_, C> {
220
234
/// Returns a chain update from the newly scanned blocks.
221
235
///
222
236
/// Returns `None` if this [`FilterIter`] was not constructed using a [`CheckPoint`], or
223
- /// if no blocks have been fetched for example by using [`get_tip`](Self::get_tip ).
224
- pub fn chain_update ( & mut self ) -> Option < CheckPoint > {
225
- if self . cp . is_none ( ) || self . blocks . is_empty ( ) {
237
+ /// if not all events have been emitted ( by calling `next` ).
238
+ pub fn chain_update ( & self ) -> Option < CheckPoint > {
239
+ if self . cp . is_none ( ) || self . blocks . is_empty ( ) || self . height <= self . stop {
226
240
return None ;
227
241
}
228
242
229
- // note: to connect with the local chain we must guarantee that `self.blocks.first()`
230
- // is also the point of agreement with `self.cp`.
243
+ // We return blocks up to and including the initial height, all of the matching blocks,
244
+ // and blocks in the terminal range.
245
+ let tail_range = self . stop . saturating_sub ( 9 ) ..=self . stop ;
231
246
Some (
232
- CheckPoint :: from_block_ids ( self . blocks . iter ( ) . map ( BlockId :: from) )
233
- . expect ( "blocks must be in order" ) ,
247
+ CheckPoint :: from_block_ids ( self . blocks . iter ( ) . filter_map ( |( & height, & hash) | {
248
+ if height <= self . start
249
+ || self . matched . contains ( & height)
250
+ || tail_range. contains ( & height)
251
+ {
252
+ Some ( BlockId { height, hash } )
253
+ } else {
254
+ None
255
+ }
256
+ } ) )
257
+ . expect ( "blocks must be in order" ) ,
234
258
)
235
259
}
236
260
}
0 commit comments