Skip to content

Commit 74e1da3

Browse files
Matt CoralloTheBlueMatt
authored andcommitted
Include recent blocks in the synchronize_listeners-returned cache
When `synchronize_listeners` runs, it returns a cache of the headers it needed when doing chain difference-finding. This allows us to ensure that when we start running normally we have all the recent headers in case we need them to reorg. Sadly, in some cases it was returning a mostly-empty cache. Because it was only being filled during block difference reconciliation it would only get a block around each listener's fork point. Worse, because we were calling `disconnect_blocks` with the cache the cache would assume we were reorging against the main chain and drop blocks we actually want. Instead, we avoid dropping blocks on `disconnect_blocks` calls and ensure we always add connected blocks to the cache.
1 parent 941846a commit 74e1da3

File tree

2 files changed

+40
-7
lines changed

2 files changed

+40
-7
lines changed

lightning-block-sync/src/init.rs

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,7 @@ where
152152
let mut chain_listeners_at_height = Vec::new();
153153
let mut most_connected_blocks = Vec::new();
154154
let mut header_cache = HeaderCache::new();
155+
header_cache.retain_on_disconnect = true;
155156
for (old_best_block, chain_listener) in chain_listeners.drain(..) {
156157
// Disconnect any stale blocks, but keep them in the cache for the next iteration.
157158
let (common_ancestor, connected_blocks) = {
@@ -192,7 +193,9 @@ where
192193
const NO_BLOCK: Option<(u32, crate::poll::ValidatedBlock)> = None;
193194
let mut fetched_blocks = [NO_BLOCK; MAX_BLOCKS_AT_ONCE];
194195
for ((header, block_res), result) in results.into_iter().zip(fetched_blocks.iter_mut()) {
195-
*result = Some((header.height, block_res?));
196+
let block = block_res?;
197+
header_cache.block_connected(header.block_hash, *header);
198+
*result = Some((header.height, block));
196199
}
197200
debug_assert!(fetched_blocks.iter().take(most_connected_blocks.len()).all(|r| r.is_some()));
198201
// TODO: When our MSRV is 1.82, use is_sorted_by_key
@@ -225,6 +228,7 @@ where
225228
.truncate(most_connected_blocks.len().saturating_sub(MAX_BLOCKS_AT_ONCE));
226229
}
227230

231+
header_cache.retain_on_disconnect = false;
228232
Ok((header_cache, best_header))
229233
}
230234

@@ -267,7 +271,13 @@ mod tests {
267271
(chain.best_block_at_height(3), &listener_3 as &dyn chain::Listen),
268272
];
269273
match synchronize_listeners(&chain, Network::Bitcoin, listeners).await {
270-
Ok((_, header)) => assert_eq!(header, chain.tip()),
274+
Ok((cache, header)) => {
275+
assert_eq!(header, chain.tip());
276+
assert!(cache.look_up(&chain.at_height(1).block_hash).is_some());
277+
assert!(cache.look_up(&chain.at_height(2).block_hash).is_some());
278+
assert!(cache.look_up(&chain.at_height(3).block_hash).is_some());
279+
assert!(cache.look_up(&chain.at_height(4).block_hash).is_some());
280+
},
271281
Err(e) => panic!("Unexpected error: {:?}", e),
272282
}
273283
}
@@ -298,7 +308,15 @@ mod tests {
298308
(fork_chain_3.best_block(), &listener_3 as &dyn chain::Listen),
299309
];
300310
match synchronize_listeners(&main_chain, Network::Bitcoin, listeners).await {
301-
Ok((_, header)) => assert_eq!(header, main_chain.tip()),
311+
Ok((cache, header)) => {
312+
assert_eq!(header, main_chain.tip());
313+
assert!(cache.look_up(&main_chain.at_height(1).block_hash).is_some());
314+
assert!(cache.look_up(&main_chain.at_height(2).block_hash).is_some());
315+
assert!(cache.look_up(&main_chain.at_height(3).block_hash).is_some());
316+
assert!(cache.look_up(&fork_chain_1.at_height(2).block_hash).is_none());
317+
assert!(cache.look_up(&fork_chain_2.at_height(3).block_hash).is_none());
318+
assert!(cache.look_up(&fork_chain_3.at_height(4).block_hash).is_none());
319+
},
302320
Err(e) => panic!("Unexpected error: {:?}", e),
303321
}
304322
}
@@ -332,7 +350,16 @@ mod tests {
332350
(fork_chain_3.best_block(), &listener_3 as &dyn chain::Listen),
333351
];
334352
match synchronize_listeners(&main_chain, Network::Bitcoin, listeners).await {
335-
Ok((_, header)) => assert_eq!(header, main_chain.tip()),
353+
Ok((cache, header)) => {
354+
assert_eq!(header, main_chain.tip());
355+
assert!(cache.look_up(&main_chain.at_height(1).block_hash).is_some());
356+
assert!(cache.look_up(&main_chain.at_height(2).block_hash).is_some());
357+
assert!(cache.look_up(&main_chain.at_height(3).block_hash).is_some());
358+
assert!(cache.look_up(&main_chain.at_height(4).block_hash).is_some());
359+
assert!(cache.look_up(&fork_chain_1.at_height(2).block_hash).is_none());
360+
assert!(cache.look_up(&fork_chain_1.at_height(3).block_hash).is_none());
361+
assert!(cache.look_up(&fork_chain_1.at_height(4).block_hash).is_none());
362+
},
336363
Err(e) => panic!("Unexpected error: {:?}", e),
337364
}
338365
}

lightning-block-sync/src/lib.rs

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -193,12 +193,15 @@ pub const HEADER_CACHE_LIMIT: u32 = 6 * 24 * 7;
193193
/// Retains only the latest [`HEADER_CACHE_LIMIT`] block headers based on height.
194194
pub struct HeaderCache {
195195
headers: std::collections::HashMap<BlockHash, ValidatedBlockHeader>,
196+
/// When set, [`Self::blocks_disconnected`] will not evict headers above the fork point.
197+
/// This is used during initial sync to retain headers across multiple listeners.
198+
retain_on_disconnect: bool,
196199
}
197200

198201
impl HeaderCache {
199202
/// Creates a new empty header cache.
200203
pub fn new() -> Self {
201-
Self { headers: std::collections::HashMap::new() }
204+
Self { headers: std::collections::HashMap::new(), retain_on_disconnect: false }
202205
}
203206

204207
/// Retrieves the block header keyed by the given block hash.
@@ -234,9 +237,12 @@ impl HeaderCache {
234237
/// Called when blocks have been disconnected from the best chain. Only the fork point
235238
/// (best common ancestor) is provided.
236239
///
237-
/// Once disconnected, a block's header is no longer needed and thus can be removed.
240+
/// Once disconnected, unless [`Self::retain_on_disconnect`] is set, a block's header is no
241+
/// longer needed and thus can be removed.
238242
pub(crate) fn blocks_disconnected(&mut self, fork_point: &ValidatedBlockHeader) {
239-
self.headers.retain(|_, block_info| block_info.height <= fork_point.height);
243+
if !self.retain_on_disconnect {
244+
self.headers.retain(|_, block_info| block_info.height <= fork_point.height);
245+
}
240246
}
241247
}
242248

0 commit comments

Comments
 (0)