use crate::async_poll::{MultiResultFuturePoller, ResultFuture};
use crate::poll::{ChainPoller, Poll, Validate, ValidatedBlockHeader};
use crate::{BlockData, BlockSource, BlockSourceResult, ChainNotifier, HeaderCache};
use bitcoin::block::Header;
use bitcoin::network::Network;
use lightning::chain;
use lightning::chain::BlockLocator;
use std::ops::Deref;
pub async fn validate_best_block_header<B: Deref>(
block_source: B,
) -> BlockSourceResult<ValidatedBlockHeader>
where
B::Target: BlockSource,
{
let (best_block_hash, best_block_height) = block_source.get_best_block().await?;
block_source.get_header(&best_block_hash, best_block_height).await?.validate(best_block_hash)
}
pub async fn synchronize_listeners<B: Deref + Sized + Send + Sync, L: chain::Listen + ?Sized>(
block_source: B, network: Network, mut chain_listeners: Vec<(BlockLocator, &L)>,
) -> BlockSourceResult<(HeaderCache, ValidatedBlockHeader)>
where
B::Target: BlockSource,
{
let best_header = validate_best_block_header(&*block_source).await?;
let mut chain_poller = ChainPoller::new(block_source, network);
let mut chain_listeners_at_height = Vec::new();
let mut most_connected_blocks = Vec::new();
let mut header_cache = HeaderCache::new();
header_cache.retain_on_disconnect = true;
for (old_best_block, chain_listener) in chain_listeners.drain(..) {
let (common_ancestor, connected_blocks) = {
let chain_listener = &DynamicChainListener(chain_listener);
let mut chain_notifier =
ChainNotifier { header_cache: &mut header_cache, chain_listener };
let difference = chain_notifier
.find_difference_from_best_block(best_header, old_best_block, &mut chain_poller)
.await?;
if difference.common_ancestor.block_hash != old_best_block.block_hash {
chain_notifier.disconnect_blocks(difference.common_ancestor);
}
(difference.common_ancestor, difference.connected_blocks)
};
chain_listeners_at_height.push((common_ancestor.height, chain_listener));
if connected_blocks.len() > most_connected_blocks.len() {
most_connected_blocks = connected_blocks;
}
}
while !most_connected_blocks.is_empty() {
#[cfg(not(test))]
const MAX_BLOCKS_AT_ONCE: usize = 6 * 6; #[cfg(test)]
const MAX_BLOCKS_AT_ONCE: usize = 2;
let mut fetch_block_futures =
Vec::with_capacity(core::cmp::min(MAX_BLOCKS_AT_ONCE, most_connected_blocks.len()));
for header in most_connected_blocks.iter().rev().take(MAX_BLOCKS_AT_ONCE) {
let fetch_future = chain_poller.fetch_block(header);
fetch_block_futures
.push(ResultFuture::Pending(Box::pin(async move { (header, fetch_future.await) })));
}
let results = MultiResultFuturePoller::new(fetch_block_futures).await.into_iter();
const NO_BLOCK: Option<(u32, crate::poll::ValidatedBlock)> = None;
let mut fetched_blocks = [NO_BLOCK; MAX_BLOCKS_AT_ONCE];
for ((header, block_res), result) in results.into_iter().zip(fetched_blocks.iter_mut()) {
let block = block_res?;
header_cache.block_connected(header.block_hash, *header);
*result = Some((header.height, block));
}
debug_assert!(fetched_blocks.iter().take(most_connected_blocks.len()).all(|r| r.is_some()));
debug_assert!(fetched_blocks.windows(2).all(|blocks| {
if let (Some(a), Some(b)) = (&blocks[0], &blocks[1]) {
a.0 < b.0
} else {
blocks[1].is_none()
}
}));
for (listener_height, listener) in chain_listeners_at_height.iter() {
for (height, block_data) in fetched_blocks.iter().flatten() {
if *height > *listener_height {
match &**block_data {
BlockData::FullBlock(block) => {
listener.block_connected(&block, *height);
},
BlockData::HeaderOnly(header_data) => {
listener.filtered_block_connected(&header_data, &[], *height);
},
}
}
}
}
most_connected_blocks
.truncate(most_connected_blocks.len().saturating_sub(MAX_BLOCKS_AT_ONCE));
}
header_cache.retain_on_disconnect = false;
Ok((header_cache, best_header))
}
struct DynamicChainListener<'a, L: chain::Listen + ?Sized>(&'a L);
impl<'a, L: chain::Listen + ?Sized> chain::Listen for DynamicChainListener<'a, L> {
fn filtered_block_connected(
&self, _header: &Header, _txdata: &chain::transaction::TransactionData, _height: u32,
) {
unreachable!()
}
fn blocks_disconnected(&self, fork_point: BlockLocator) {
self.0.blocks_disconnected(fork_point)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::test_utils::{Blockchain, MockChainListener};
#[tokio::test]
async fn sync_from_same_chain() {
let chain = Blockchain::default().with_height(4);
let listener_1 = MockChainListener::new()
.expect_block_connected(*chain.at_height(2))
.expect_block_connected(*chain.at_height(3))
.expect_block_connected(*chain.at_height(4));
let listener_2 = MockChainListener::new()
.expect_block_connected(*chain.at_height(3))
.expect_block_connected(*chain.at_height(4));
let listener_3 = MockChainListener::new().expect_block_connected(*chain.at_height(4));
let listeners = vec![
(chain.block_locator_at_height(1), &listener_1 as &dyn chain::Listen),
(chain.block_locator_at_height(2), &listener_2 as &dyn chain::Listen),
(chain.block_locator_at_height(3), &listener_3 as &dyn chain::Listen),
];
match synchronize_listeners(&chain, Network::Bitcoin, listeners).await {
Ok((cache, header)) => {
assert_eq!(header, chain.tip());
assert!(cache.look_up(&chain.at_height(1).block_hash).is_some());
assert!(cache.look_up(&chain.at_height(2).block_hash).is_some());
assert!(cache.look_up(&chain.at_height(3).block_hash).is_some());
assert!(cache.look_up(&chain.at_height(4).block_hash).is_some());
},
Err(e) => panic!("Unexpected error: {:?}", e),
}
}
#[tokio::test]
async fn sync_from_different_chains() {
let main_chain = Blockchain::default().with_height(4);
let fork_chain_1 = main_chain.fork_at_height(1);
let fork_chain_2 = main_chain.fork_at_height(2);
let fork_chain_3 = main_chain.fork_at_height(3);
let listener_1 = MockChainListener::new()
.expect_blocks_disconnected(*fork_chain_1.at_height(1))
.expect_block_connected(*main_chain.at_height(2))
.expect_block_connected(*main_chain.at_height(3))
.expect_block_connected(*main_chain.at_height(4));
let listener_2 = MockChainListener::new()
.expect_blocks_disconnected(*fork_chain_2.at_height(2))
.expect_block_connected(*main_chain.at_height(3))
.expect_block_connected(*main_chain.at_height(4));
let listener_3 = MockChainListener::new()
.expect_blocks_disconnected(*fork_chain_3.at_height(3))
.expect_block_connected(*main_chain.at_height(4));
let listeners = vec![
(fork_chain_1.best_block(), &listener_1 as &dyn chain::Listen),
(fork_chain_2.best_block(), &listener_2 as &dyn chain::Listen),
(fork_chain_3.best_block(), &listener_3 as &dyn chain::Listen),
];
match synchronize_listeners(&main_chain, Network::Bitcoin, listeners).await {
Ok((cache, header)) => {
assert_eq!(header, main_chain.tip());
assert!(cache.look_up(&main_chain.at_height(1).block_hash).is_some());
assert!(cache.look_up(&main_chain.at_height(2).block_hash).is_some());
assert!(cache.look_up(&main_chain.at_height(3).block_hash).is_some());
assert!(cache.look_up(&fork_chain_1.at_height(2).block_hash).is_none());
assert!(cache.look_up(&fork_chain_2.at_height(3).block_hash).is_none());
assert!(cache.look_up(&fork_chain_3.at_height(4).block_hash).is_none());
},
Err(e) => panic!("Unexpected error: {:?}", e),
}
}
#[tokio::test]
async fn sync_from_overlapping_chains() {
let main_chain = Blockchain::default().with_height(4);
let fork_chain_1 = main_chain.fork_at_height(1);
let fork_chain_2 = fork_chain_1.fork_at_height(2);
let fork_chain_3 = fork_chain_2.fork_at_height(3);
let listener_1 = MockChainListener::new()
.expect_blocks_disconnected(*fork_chain_1.at_height(1))
.expect_block_connected(*main_chain.at_height(2))
.expect_block_connected(*main_chain.at_height(3))
.expect_block_connected(*main_chain.at_height(4));
let listener_2 = MockChainListener::new()
.expect_blocks_disconnected(*fork_chain_2.at_height(1))
.expect_block_connected(*main_chain.at_height(2))
.expect_block_connected(*main_chain.at_height(3))
.expect_block_connected(*main_chain.at_height(4));
let listener_3 = MockChainListener::new()
.expect_blocks_disconnected(*fork_chain_3.at_height(1))
.expect_block_connected(*main_chain.at_height(2))
.expect_block_connected(*main_chain.at_height(3))
.expect_block_connected(*main_chain.at_height(4));
let listeners = vec![
(fork_chain_1.best_block(), &listener_1 as &dyn chain::Listen),
(fork_chain_2.best_block(), &listener_2 as &dyn chain::Listen),
(fork_chain_3.best_block(), &listener_3 as &dyn chain::Listen),
];
match synchronize_listeners(&main_chain, Network::Bitcoin, listeners).await {
Ok((cache, header)) => {
assert_eq!(header, main_chain.tip());
assert!(cache.look_up(&main_chain.at_height(1).block_hash).is_some());
assert!(cache.look_up(&main_chain.at_height(2).block_hash).is_some());
assert!(cache.look_up(&main_chain.at_height(3).block_hash).is_some());
assert!(cache.look_up(&main_chain.at_height(4).block_hash).is_some());
assert!(cache.look_up(&fork_chain_1.at_height(2).block_hash).is_none());
assert!(cache.look_up(&fork_chain_1.at_height(3).block_hash).is_none());
assert!(cache.look_up(&fork_chain_1.at_height(4).block_hash).is_none());
},
Err(e) => panic!("Unexpected error: {:?}", e),
}
}
}