@@ -31,8 +31,10 @@ pub struct FilterIter<'c, C> {
31
31
spks : Vec < ScriptBuf > ,
32
32
// local cp
33
33
cp : Option < CheckPoint > ,
34
- // blocks map
35
- blocks : BTreeMap < Height , BlockHash > ,
34
+ // map of height -> (hash, is_match)
35
+ blocks : BTreeMap < Height , ( BlockHash , bool ) > ,
36
+ // initial height
37
+ start : Height ,
36
38
// best height counter
37
39
height : Height ,
38
40
// stop height
@@ -47,6 +49,7 @@ impl<'c, C: RpcApi> FilterIter<'c, C> {
47
49
spks : vec ! [ ] ,
48
50
cp : None ,
49
51
blocks : BTreeMap :: new ( ) ,
52
+ start : height,
50
53
height,
51
54
stop : 0 ,
52
55
}
@@ -69,22 +72,14 @@ impl<'c, C: RpcApi> FilterIter<'c, C> {
69
72
self . spks . push ( spk) ;
70
73
}
71
74
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) ) )
75
+ /// Get the block hash by `height` if it is found in the blocks map.
76
+ fn get_block_hash ( & self , height : & Height ) -> Option < BlockHash > {
77
+ Some ( self . blocks . get ( height) ?. 0 )
78
+ }
79
+
80
+ /// Insert a (non-matching) block height and hash into the blocks map.
81
+ fn insert_block ( & mut self , height : Height , hash : BlockHash ) {
82
+ self . blocks . insert ( height, ( hash, false ) ) ;
88
83
}
89
84
90
85
/// Get the remote tip.
@@ -93,33 +88,17 @@ impl<'c, C: RpcApi> FilterIter<'c, C> {
93
88
/// [`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
93
if self . height >= tip_height {
99
94
// nothing to do
100
95
return Ok ( None ) ;
101
96
}
102
- self . blocks . insert ( tip_height, tip_hash) ;
103
97
104
- // if we have a checkpoint we use a lookback of ten blocks
105
- // to ensure consistency of the local chain
98
+ // start scanning from point of agreement + 1
106
99
if let Some ( cp) = self . cp . as_ref ( ) {
107
- // adjust start height to point of agreement + 1
108
100
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
- }
101
+ self . height = base. height . saturating_add ( 1 ) ;
123
102
}
124
103
125
104
self . stop = tip_height;
@@ -131,9 +110,6 @@ impl<'c, C: RpcApi> FilterIter<'c, C> {
131
110
}
132
111
}
133
112
134
- /// Alias for a compact filter and associated block id.
135
- type NextFilter = ( BlockId , BlockFilter ) ;
136
-
137
113
/// Event inner type
138
114
#[ derive( Debug , Clone ) ]
139
115
pub struct EventInner {
@@ -171,27 +147,53 @@ impl<C: RpcApi> Iterator for FilterIter<'_, C> {
171
147
type Item = Result < Event , Error > ;
172
148
173
149
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) ) )
150
+ ( || -> Result < Option < _ > , Error > {
151
+ if self . height > self . stop {
152
+ return Ok ( None ) ;
153
+ }
154
+ // Fetch next filter
155
+ let mut height = self . height ;
156
+ let mut hash = self . client . get_block_hash ( height as _ ) ?;
157
+ loop {
158
+ let header = self . client . get_block_header ( & hash) ?;
159
+ let prev_height = height. saturating_sub ( 1 ) ;
160
+ let prev_hash = match self . get_block_hash ( & prev_height) {
161
+ Some ( hash) => hash,
162
+ None => break ,
163
+ } ;
164
+ if header. prev_blockhash == prev_hash {
165
+ break ;
193
166
}
194
- } )
167
+ // reorg detected, keep backtracking
168
+ height = height. saturating_sub ( 1 ) ;
169
+ hash = self . client . get_block_hash ( height as _ ) ?;
170
+ }
171
+ let filter_bytes = self . client . get_block_filter ( & hash) ?. filter ;
172
+ let filter = BlockFilter :: new ( & filter_bytes) ;
173
+
174
+ // record the scanned block
175
+ self . insert_block ( height, hash) ;
176
+ // increment best height
177
+ self . height = height. saturating_add ( 1 ) ;
178
+
179
+ // If the filter matches any of our watched SPKs, fetch the full
180
+ // block, and record the matching block entry.
181
+ 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
+ // update for matched block
189
+ self . blocks . entry ( height) . and_modify ( |( _, is_match) | {
190
+ * is_match = true ;
191
+ } ) ;
192
+ let inner = EventInner { height, block } ;
193
+ Ok ( Some ( Event :: Block ( inner) ) )
194
+ } else {
195
+ Ok ( Some ( Event :: NoMatch ( height) ) )
196
+ }
195
197
} ) ( )
196
198
. transpose ( )
197
199
}
@@ -202,36 +204,45 @@ impl<C: RpcApi> FilterIter<'_, C> {
202
204
fn find_base_with ( & mut self , mut cp : CheckPoint ) -> Result < BlockId , Error > {
203
205
loop {
204
206
let height = cp. height ( ) ;
205
- let fetched_hash = match self . blocks . get ( & height) {
206
- Some ( hash) => * hash,
207
+ let fetched_hash = match self . get_block_hash ( & height) {
208
+ Some ( hash) => hash,
207
209
None if height == 0 => cp. hash ( ) ,
208
210
_ => self . client . get_block_hash ( height as _ ) ?,
209
211
} ;
210
212
if cp. hash ( ) == fetched_hash {
211
213
// ensure this block also exists in self
212
- self . blocks . insert ( height, cp. hash ( ) ) ;
214
+ self . insert_block ( height, cp. hash ( ) ) ;
213
215
return Ok ( cp. block_id ( ) ) ;
214
216
}
215
217
// remember conflicts
216
- self . blocks . insert ( height, fetched_hash) ;
218
+ self . insert_block ( height, fetched_hash) ;
217
219
cp = cp. prev ( ) . expect ( "must break before genesis" ) ;
218
220
}
219
221
}
220
222
221
223
/// Returns a chain update from the newly scanned blocks.
222
224
///
223
225
/// 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 ).
226
+ /// if not all events have been emitted ( by calling `next` ).
225
227
pub fn chain_update ( & mut self ) -> Option < CheckPoint > {
226
- if self . cp . is_none ( ) || self . blocks . is_empty ( ) {
228
+ if self . cp . is_none ( ) || self . blocks . is_empty ( ) || self . height <= self . stop {
227
229
return None ;
228
230
}
229
231
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`.
232
+ // We return blocks up to and including the initial height, all of the matching blocks,
233
+ // and blocks in the terminal range.
234
+ let tail_range = self . stop . saturating_sub ( 9 ) ..=self . stop ;
232
235
Some (
233
- CheckPoint :: from_block_ids ( self . blocks . iter ( ) . map ( BlockId :: from) )
234
- . expect ( "blocks must be in order" ) ,
236
+ CheckPoint :: from_block_ids ( self . blocks . iter ( ) . filter_map (
237
+ |( & height, & ( hash, is_match) ) | {
238
+ if height <= self . start || is_match || tail_range. contains ( & height) {
239
+ Some ( BlockId { height, hash } )
240
+ } else {
241
+ None
242
+ }
243
+ } ,
244
+ ) )
245
+ . expect ( "blocks must be in order" ) ,
235
246
)
236
247
}
237
248
}
0 commit comments