Skip to content

Commit f717530

Browse files
committed
Replace custom header cache with HashMap
1 parent 18b9f44 commit f717530

File tree

2 files changed

+61
-58
lines changed

2 files changed

+61
-58
lines changed

lightning-block-sync/src/lib.rs

Lines changed: 50 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -155,16 +155,15 @@ fn check_builds_on(child_header: &BlockHeaderData, previous_header: &BlockHeader
155155
Ok(())
156156
}
157157

158-
async fn look_up_prev_header<'a, 'b>(block_source: &'a mut dyn BlockSource, header: &BlockHeaderData, cache: &mut &'b [BlockHeaderData], mainnet: bool) -> BlockSourceResult<BlockHeaderData> {
159-
if !cache.is_empty() {
160-
let prev_header = *cache.last().unwrap();
161-
*cache = &cache[..cache.len() - 1];
162-
return Ok(prev_header);
158+
async fn look_up_prev_header(block_source: &mut dyn BlockSource, header: &BlockHeaderData, cache: &HeaderCache, mainnet: bool) -> BlockSourceResult<BlockHeaderData> {
159+
match cache.get(&header.header.prev_blockhash) {
160+
Some(prev_header) => Ok(*prev_header),
161+
None => {
162+
let prev_header = block_source.get_header(&header.header.prev_blockhash, Some(header.height - 1)).await?;
163+
check_builds_on(&header, &prev_header, mainnet)?;
164+
Ok(prev_header)
165+
},
163166
}
164-
165-
let prev_header = block_source.get_header(&header.header.prev_blockhash, Some(header.height - 1)).await?;
166-
check_builds_on(&header, &prev_header, mainnet)?;
167-
Ok(prev_header)
168167
}
169168

170169
enum ForkStep {
@@ -173,10 +172,9 @@ enum ForkStep {
173172
ConnectBlock(BlockHeaderData),
174173
}
175174

176-
async fn find_fork_step<'a>(steps_tx: &'a mut Vec<ForkStep>, current_header: BlockHeaderData, prev_header: &'a BlockHeaderData, block_source: &'a mut dyn BlockSource, head_blocks: &'a [BlockHeaderData], mainnet: bool) -> BlockSourceResult<()> {
175+
async fn find_fork_step(steps_tx: &mut Vec<ForkStep>, current_header: BlockHeaderData, prev_header: &BlockHeaderData, block_source: &mut dyn BlockSource, cache: &HeaderCache, mainnet: bool) -> BlockSourceResult<()> {
177176
let mut current = current_header;
178177
let mut previous = *prev_header;
179-
let mut cache = &head_blocks[..];
180178
loop {
181179
// Found a different genesis block.
182180
if current.height == 0 {
@@ -192,7 +190,7 @@ async fn find_fork_step<'a>(steps_tx: &'a mut Vec<ForkStep>, current_header: Blo
192190

193191
// Found a chain fork.
194192
if current.header.prev_blockhash == previous.header.prev_blockhash {
195-
let fork_point = look_up_prev_header(block_source, &previous, &mut cache, mainnet).await?;
193+
let fork_point = look_up_prev_header(block_source, &previous, cache, mainnet).await?;
196194
steps_tx.push(ForkStep::DisconnectBlock(previous));
197195
steps_tx.push(ForkStep::ConnectBlock(current));
198196
steps_tx.push(ForkStep::ForkPoint(fork_point));
@@ -205,29 +203,23 @@ async fn find_fork_step<'a>(steps_tx: &'a mut Vec<ForkStep>, current_header: Blo
205203
let previous_height = previous.height;
206204
if current_height <= previous_height {
207205
steps_tx.push(ForkStep::DisconnectBlock(previous));
208-
previous = look_up_prev_header(block_source, &previous, &mut cache, mainnet).await?;
206+
previous = look_up_prev_header(block_source, &previous, cache, mainnet).await?;
209207
}
210208
if current_height >= previous_height {
211209
steps_tx.push(ForkStep::ConnectBlock(current));
212-
current = look_up_prev_header(block_source, &current, &mut &[][..], mainnet).await?;
210+
current = look_up_prev_header(block_source, &current, cache, mainnet).await?;
213211
}
214212
}
215213
}
216214

217215
/// Walks backwards from current_header and prev_header finding the fork and sending ForkStep events
218216
/// into the steps_tx Sender. There is no ordering guarantee between different ForkStep types, but
219217
/// DisconnectBlock and ConnectBlock events are each in reverse, height-descending order.
220-
async fn find_fork<'a>(current_header: BlockHeaderData, prev_header: &'a BlockHeaderData, block_source: &'a mut dyn BlockSource, mut head_blocks: &'a [BlockHeaderData], mainnet: bool) -> BlockSourceResult<Vec<ForkStep>> {
218+
async fn find_fork(current_header: BlockHeaderData, prev_header: &BlockHeaderData, block_source: &mut dyn BlockSource, cache: &HeaderCache, mainnet: bool) -> BlockSourceResult<Vec<ForkStep>> {
221219
let mut steps_tx = Vec::new();
222220
if current_header.header == prev_header.header { return Ok(steps_tx); }
223221

224-
// If we have cached headers, they have to end with where we used to be
225-
head_blocks = if !head_blocks.is_empty() {
226-
assert_eq!(head_blocks.last().unwrap(), prev_header);
227-
&head_blocks[..head_blocks.len() - 1]
228-
} else { head_blocks };
229-
230-
find_fork_step(&mut steps_tx, current_header, &prev_header, block_source, head_blocks, mainnet).await?;
222+
find_fork_step(&mut steps_tx, current_header, &prev_header, block_source, cache, mainnet).await?;
231223
Ok(steps_tx)
232224
}
233225

@@ -273,17 +265,18 @@ impl<CS, B, F, L> ChainListener for (&mut ChannelMonitor<CS>, &B, &F, &L)
273265
/// disconnected to the fork point. Thus, we may return an Err() that includes where our tip ended
274266
/// up which may not be new_header. Note that iff the returned Err has a BlockHeaderData, the
275267
/// header transition from old_header to new_header is valid.
276-
async fn sync_chain_monitor<CL: ChainListener + Sized>(new_header: BlockHeaderData, old_header: &BlockHeaderData, block_source: &mut dyn BlockSource, chain_notifier: &mut CL, head_blocks: &mut Vec<BlockHeaderData>, mainnet: bool)
268+
async fn sync_chain_monitor<CL: ChainListener + Sized>(new_header: BlockHeaderData, old_header: &BlockHeaderData, block_source: &mut dyn BlockSource, chain_notifier: &mut CL, header_cache: &mut HeaderCache, mainnet: bool)
277269
-> Result<(), (BlockSourceError, Option<BlockHeaderData>)> {
278-
let mut events = find_fork(new_header, old_header, block_source, &*head_blocks, mainnet).await.map_err(|e| (e, None))?;
270+
let mut events = find_fork(new_header, old_header, block_source, header_cache, mainnet).await.map_err(|e| (e, None))?;
279271

280272
let mut last_disconnect_tip = None;
281273
let mut new_tip = None;
282274
for event in events.iter() {
283275
match &event {
284276
&ForkStep::DisconnectBlock(ref header) => {
285-
println!("Disconnecting block {}", header.header.block_hash());
286-
if let Some(cached_head) = head_blocks.pop() {
277+
let block_hash = header.header.block_hash();
278+
println!("Disconnecting block {}", block_hash);
279+
if let Some(cached_head) = header_cache.remove(&block_hash) {
287280
assert_eq!(cached_head, *header);
288281
}
289282
chain_notifier.block_disconnected(&header.header, header.height);
@@ -296,14 +289,10 @@ async fn sync_chain_monitor<CL: ChainListener + Sized>(new_header: BlockHeaderDa
296289
}
297290
}
298291

299-
// If we disconnected any blocks, we should have new tip data available, which should match our
300-
// cached header data if it is available. If we didn't disconnect any blocks we shouldn't have
301-
// set a ForkPoint as there is no fork.
292+
// If we disconnected any blocks, we should have new tip data available. If we didn't disconnect
293+
// any blocks we shouldn't have set a ForkPoint as there is no fork.
302294
assert_eq!(last_disconnect_tip.is_some(), new_tip.is_some());
303295
if let &Some(ref tip_header) = &new_tip {
304-
if let Some(cached_head) = head_blocks.last() {
305-
assert_eq!(cached_head, tip_header);
306-
}
307296
debug_assert_eq!(tip_header.header.block_hash(), *last_disconnect_tip.as_ref().unwrap());
308297
} else {
309298
// Set new_tip to indicate that we got a valid header chain we wanted to connect to, but
@@ -313,16 +302,17 @@ async fn sync_chain_monitor<CL: ChainListener + Sized>(new_header: BlockHeaderDa
313302

314303
for event in events.drain(..).rev() {
315304
if let ForkStep::ConnectBlock(header_data) = event {
316-
let block = match block_source.get_block(&header_data.header.block_hash()).await {
305+
let block_hash = header_data.header.block_hash();
306+
let block = match block_source.get_block(&block_hash).await {
317307
Err(e) => return Err((e, new_tip)),
318308
Ok(b) => b,
319309
};
320310
if block.header != header_data.header || !block.check_merkle_root() || !block.check_witness_commitment() {
321311
return Err((BlockSourceError::Persistent, new_tip));
322312
}
323-
println!("Connecting block {}", header_data.header.block_hash().to_hex());
313+
println!("Connecting block {}", block_hash.to_hex());
324314
chain_notifier.block_connected(&block, header_data.height);
325-
head_blocks.push(header_data.clone());
315+
header_cache.insert(block_hash, header_data.clone());
326316
new_tip = Some(header_data);
327317
}
328318
}
@@ -343,9 +333,12 @@ pub async fn init_sync_chain_monitor<CL: ChainListener + Sized, B: BlockSource>(
343333
let old_header = block_source.get_header(&old_block, None).await.unwrap();
344334
assert_eq!(old_header.header.block_hash(), old_block);
345335
stateless_check_header(&old_header.header).unwrap();
346-
sync_chain_monitor(new_header, &old_header, block_source, chain_notifier, &mut Vec::new(), false).await.unwrap();
336+
sync_chain_monitor(new_header, &old_header, block_source, chain_notifier, &mut HeaderCache::new(), false).await.unwrap();
347337
}
348338

339+
/// Unbounded cache of header data keyed by block hash.
340+
pub(crate) type HeaderCache = std::collections::HashMap<BlockHash, BlockHeaderData>;
341+
349342
/// Keep the chain that a chain listener knows about up-to-date with the best chain from any of the
350343
/// given block_sources.
351344
///
@@ -368,7 +361,7 @@ where P: Poll<'a, B>,
368361
chain_tip: (BlockHash, BlockHeaderData),
369362
chain_poller: P,
370363
backup_block_sources: Vec<B>,
371-
blocks_past_common_tip: Vec<BlockHeaderData>,
364+
header_cache: HeaderCache,
372365
chain_notifier: CL,
373366
mainnet: bool
374367
}
@@ -391,10 +384,10 @@ where P: Poll<'a, B>,
391384
/// which only provides headers. In this case, we can use such source(s) to learn of a censorship
392385
/// attack without giving up privacy by querying a privacy-losing block sources.
393386
pub fn init(chain_tip: BlockHeaderData, chain_poller: P, backup_block_sources: Vec<B>, chain_notifier: CL, mainnet: bool) -> Self {
394-
let blocks_past_common_tip = Vec::new();
387+
let header_cache = HeaderCache::new();
395388
Self {
396389
chain_tip: (chain_tip.header.block_hash(), chain_tip),
397-
chain_poller, backup_block_sources, blocks_past_common_tip, chain_notifier, mainnet
390+
chain_poller, backup_block_sources, header_cache, chain_notifier, mainnet
398391
}
399392
}
400393

@@ -404,7 +397,7 @@ where P: Poll<'a, B>,
404397
macro_rules! sync_chain_monitor {
405398
($new_hash: expr, $new_header: expr, $source: expr) => { {
406399
let mut blocks_connected = false;
407-
match sync_chain_monitor($new_header, &self.chain_tip.1, $source, &mut self.chain_notifier, &mut self.blocks_past_common_tip, self.mainnet).await {
400+
match sync_chain_monitor($new_header, &self.chain_tip.1, $source, &mut self.chain_notifier, &mut self.header_cache, self.mainnet).await {
408401
Err((_, latest_tip)) => {
409402
if let Some(latest_tip) = latest_tip {
410403
let latest_tip_hash = latest_tip.header.block_hash();
@@ -428,7 +421,7 @@ where P: Poll<'a, B>,
428421
Err(BlockSourceError::Transient) => false,
429422
Ok((ChainTip::Common(all_common), _)) => {
430423
if all_common {
431-
self.blocks_past_common_tip.clear();
424+
self.header_cache.clear();
432425
}
433426
false
434427
},
@@ -470,7 +463,7 @@ mod sync_tests {
470463
let mut listener = MockChainListener::new()
471464
.expect_block_connected(chain.at_height(2))
472465
.expect_block_connected(new_tip);
473-
let mut cache = (0..=1).map(|i| chain.at_height(i)).collect();
466+
let mut cache = chain.header_cache(0..=1);
474467
match sync_chain_monitor(new_tip, &old_tip, &mut chain, &mut listener, &mut cache, false).await {
475468
Err((e, _)) => panic!("Unexpected error: {:?}", e),
476469
Ok(_) => {},
@@ -485,7 +478,7 @@ mod sync_tests {
485478
let new_tip = test_chain.tip();
486479
let old_tip = main_chain.tip();
487480
let mut listener = MockChainListener::new();
488-
let mut cache = (0..=1).map(|i| main_chain.at_height(i)).collect();
481+
let mut cache = main_chain.header_cache(0..=1);
489482
match sync_chain_monitor(new_tip, &old_tip, &mut test_chain, &mut listener, &mut cache, false).await {
490483
Err((e, _)) => assert_eq!(e, BlockSourceError::Persistent),
491484
Ok(_) => panic!("Expected error"),
@@ -502,7 +495,7 @@ mod sync_tests {
502495
let mut listener = MockChainListener::new()
503496
.expect_block_disconnected(old_tip)
504497
.expect_block_connected(new_tip);
505-
let mut cache = (0..=2).map(|i| main_chain.at_height(i)).collect();
498+
let mut cache = main_chain.header_cache(0..=2);
506499
match sync_chain_monitor(new_tip, &old_tip, &mut fork_chain, &mut listener, &mut cache, false).await {
507500
Err((e, _)) => panic!("Unexpected error: {:?}", e),
508501
Ok(_) => {},
@@ -521,7 +514,7 @@ mod sync_tests {
521514
.expect_block_disconnected(old_tip)
522515
.expect_block_disconnected(main_chain.at_height(2))
523516
.expect_block_connected(new_tip);
524-
let mut cache = (0..=3).map(|i| main_chain.at_height(i)).collect();
517+
let mut cache = main_chain.header_cache(0..=3);
525518
match sync_chain_monitor(new_tip, &old_tip, &mut fork_chain, &mut listener, &mut cache, false).await {
526519
Err((e, _)) => panic!("Unexpected error: {:?}", e),
527520
Ok(_) => {},
@@ -540,7 +533,7 @@ mod sync_tests {
540533
.expect_block_disconnected(old_tip)
541534
.expect_block_connected(fork_chain.at_height(2))
542535
.expect_block_connected(new_tip);
543-
let mut cache = (0..=2).map(|i| main_chain.at_height(i)).collect();
536+
let mut cache = main_chain.header_cache(0..=2);
544537
match sync_chain_monitor(new_tip, &old_tip, &mut fork_chain, &mut listener, &mut cache, false).await {
545538
Err((e, _)) => panic!("Unexpected error: {:?}", e),
546539
Ok(_) => {},
@@ -809,9 +802,9 @@ mod tests {
809802
assert!(client.poll_best_tip().await);
810803
assert_eq!(&chain_notifier.blocks_disconnected.lock().unwrap()[..], &[(block_1a_hash, 1)][..]);
811804
assert_eq!(&chain_notifier.blocks_connected.lock().unwrap()[..], &[(block_1b_hash, 1), (block_2b_hash, 2)][..]);
812-
assert_eq!(client.blocks_past_common_tip.len(), 2);
813-
assert_eq!(client.blocks_past_common_tip[0].header.block_hash(), block_1b_hash);
814-
assert_eq!(client.blocks_past_common_tip[1].header.block_hash(), block_2b_hash);
805+
assert_eq!(client.header_cache.len(), 2);
806+
assert!(client.header_cache.contains_key(&block_1b_hash));
807+
assert!(client.header_cache.contains_key(&block_2b_hash));
815808

816809
// Test that even if chain_one (which we just got blocks from) stops responding to block or
817810
// header requests we can still reorg back because we never wiped our block cache as
@@ -837,10 +830,10 @@ mod tests {
837830

838831
// Note that blocks_past_common_tip is not wiped as chain_one still returns 2a as its tip
839832
// (though a smarter MicroSPVClient may wipe 1a and 2a from the set eventually.
840-
assert_eq!(client.blocks_past_common_tip.len(), 3);
841-
assert_eq!(client.blocks_past_common_tip[0].header.block_hash(), block_1a_hash);
842-
assert_eq!(client.blocks_past_common_tip[1].header.block_hash(), block_2a_hash);
843-
assert_eq!(client.blocks_past_common_tip[2].header.block_hash(), block_3a_hash);
833+
assert_eq!(client.header_cache.len(), 3);
834+
assert!(client.header_cache.contains_key(&block_1a_hash));
835+
assert!(client.header_cache.contains_key(&block_2a_hash));
836+
assert!(client.header_cache.contains_key(&block_3a_hash));
844837

845838
chain_notifier.blocks_connected.lock().unwrap().clear();
846839
chain_notifier.blocks_disconnected.lock().unwrap().clear();
@@ -853,7 +846,7 @@ mod tests {
853846
assert!(chain_notifier.blocks_disconnected.lock().unwrap().is_empty());
854847
assert!(chain_notifier.blocks_connected.lock().unwrap().is_empty());
855848

856-
assert!(client.blocks_past_common_tip.is_empty());
849+
assert!(client.header_cache.is_empty());
857850

858851
// Test that setting the header chain to 4a does...almost nothing (though backup_chain
859852
// should now be queried) since we can't get the blocks from anywhere.
@@ -866,7 +859,7 @@ mod tests {
866859
assert!(!client.poll_best_tip().await);
867860
assert!(chain_notifier.blocks_disconnected.lock().unwrap().is_empty());
868861
assert!(chain_notifier.blocks_connected.lock().unwrap().is_empty());
869-
assert!(client.blocks_past_common_tip.is_empty());
862+
assert!(client.header_cache.is_empty());
870863

871864
// But if backup_chain *also* has 4a, we'll fetch it from there:
872865
backup_chain.blocks.lock().unwrap().insert(block_4a_hash, block_4a);
@@ -875,8 +868,8 @@ mod tests {
875868
assert!(client.poll_best_tip().await);
876869
assert!(chain_notifier.blocks_disconnected.lock().unwrap().is_empty());
877870
assert_eq!(&chain_notifier.blocks_connected.lock().unwrap()[..], &[(block_4a_hash, 4)][..]);
878-
assert_eq!(client.blocks_past_common_tip.len(), 1);
879-
assert_eq!(client.blocks_past_common_tip[0].header.block_hash(), block_4a_hash);
871+
assert_eq!(client.header_cache.len(), 1);
872+
assert!(client.header_cache.contains_key(&block_4a_hash));
880873

881874
chain_notifier.blocks_connected.lock().unwrap().clear();
882875
chain_notifier.blocks_disconnected.lock().unwrap().clear();

lightning-block-sync/src/test_utils.rs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use crate::{AsyncBlockSourceResult, BlockHeaderData, BlockSource, BlockSourceError, ChainListener};
1+
use crate::{AsyncBlockSourceResult, BlockHeaderData, BlockSource, BlockSourceError, ChainListener, HeaderCache};
22
use bitcoin::blockdata::block::{Block, BlockHeader};
33
use bitcoin::blockdata::constants::genesis_block;
44
use bitcoin::hash_types::BlockHash;
@@ -83,6 +83,16 @@ impl Blockchain {
8383
pub fn disconnect_tip(&mut self) -> Option<Block> {
8484
self.blocks.pop()
8585
}
86+
87+
pub fn header_cache(&self, heights: std::ops::RangeInclusive<usize>) -> HeaderCache {
88+
let mut cache = HeaderCache::new();
89+
for i in heights {
90+
let value = self.at_height(i);
91+
let key = value.header.block_hash();
92+
assert!(cache.insert(key, value).is_none());
93+
}
94+
cache
95+
}
8696
}
8797

8898
impl BlockSource for Blockchain {

0 commit comments

Comments
 (0)