1use crate::async_poll::{MultiResultFuturePoller, ResultFuture};
5use crate::poll::{ChainPoller, Poll, Validate, ValidatedBlockHeader};
6use crate::{BlockData, BlockSource, BlockSourceResult, ChainNotifier, HeaderCache};
7
8use bitcoin::block::Header;
9use bitcoin::network::Network;
10
11use lightning::chain;
12use lightning::chain::BlockLocator;
13
14use std::ops::Deref;
15
16pub async fn validate_best_block_header<B: Deref>(
23 block_source: B,
24) -> BlockSourceResult<ValidatedBlockHeader>
25where
26 B::Target: BlockSource,
27{
28 let (best_block_hash, best_block_height) = block_source.get_best_block().await?;
29 block_source.get_header(&best_block_hash, best_block_height).await?.validate(best_block_hash)
30}
31
32pub async fn synchronize_listeners<B: Deref + Sized + Send + Sync, L: chain::Listen + ?Sized>(
143 block_source: B, network: Network, mut chain_listeners: Vec<(BlockLocator, &L)>,
144) -> BlockSourceResult<(HeaderCache, ValidatedBlockHeader)>
145where
146 B::Target: BlockSource,
147{
148 let best_header = validate_best_block_header(&*block_source).await?;
149
150 let mut chain_poller = ChainPoller::new(block_source, network);
152 let mut chain_listeners_at_height = Vec::new();
153 let mut most_connected_blocks = Vec::new();
154 let mut header_cache = HeaderCache::new();
155 header_cache.retain_on_disconnect = true;
156 for (old_best_block, chain_listener) in chain_listeners.drain(..) {
157 let (common_ancestor, connected_blocks) = {
159 let chain_listener = &DynamicChainListener(chain_listener);
160 let mut chain_notifier =
161 ChainNotifier { header_cache: &mut header_cache, chain_listener };
162 let difference = chain_notifier
163 .find_difference_from_best_block(best_header, old_best_block, &mut chain_poller)
164 .await?;
165 if difference.common_ancestor.block_hash != old_best_block.block_hash {
166 chain_notifier.disconnect_blocks(difference.common_ancestor);
167 }
168 (difference.common_ancestor, difference.connected_blocks)
169 };
170
171 chain_listeners_at_height.push((common_ancestor.height, chain_listener));
173 if connected_blocks.len() > most_connected_blocks.len() {
174 most_connected_blocks = connected_blocks;
175 }
176 }
177
178 while !most_connected_blocks.is_empty() {
179 #[cfg(not(test))]
180 const MAX_BLOCKS_AT_ONCE: usize = 6 * 6; #[cfg(test)]
182 const MAX_BLOCKS_AT_ONCE: usize = 2;
183
184 let mut fetch_block_futures =
185 Vec::with_capacity(core::cmp::min(MAX_BLOCKS_AT_ONCE, most_connected_blocks.len()));
186 for header in most_connected_blocks.iter().rev().take(MAX_BLOCKS_AT_ONCE) {
187 let fetch_future = chain_poller.fetch_block(header);
188 fetch_block_futures
189 .push(ResultFuture::Pending(Box::pin(async move { (header, fetch_future.await) })));
190 }
191 let results = MultiResultFuturePoller::new(fetch_block_futures).await.into_iter();
192
193 const NO_BLOCK: Option<(u32, crate::poll::ValidatedBlock)> = None;
194 let mut fetched_blocks = [NO_BLOCK; MAX_BLOCKS_AT_ONCE];
195 for ((header, block_res), result) in results.into_iter().zip(fetched_blocks.iter_mut()) {
196 let block = block_res?;
197 header_cache.block_connected(header.block_hash, *header);
198 *result = Some((header.height, block));
199 }
200 debug_assert!(fetched_blocks.iter().take(most_connected_blocks.len()).all(|r| r.is_some()));
201 debug_assert!(fetched_blocks.windows(2).all(|blocks| {
203 if let (Some(a), Some(b)) = (&blocks[0], &blocks[1]) {
204 a.0 < b.0
205 } else {
206 blocks[1].is_none()
208 }
209 }));
210
211 for (listener_height, listener) in chain_listeners_at_height.iter() {
212 for (height, block_data) in fetched_blocks.iter().flatten() {
214 if *height > *listener_height {
215 match &**block_data {
216 BlockData::FullBlock(block) => {
217 listener.block_connected(&block, *height);
218 },
219 BlockData::HeaderOnly(header_data) => {
220 listener.filtered_block_connected(&header_data, &[], *height);
221 },
222 }
223 }
224 }
225 }
226
227 most_connected_blocks
228 .truncate(most_connected_blocks.len().saturating_sub(MAX_BLOCKS_AT_ONCE));
229 }
230
231 header_cache.retain_on_disconnect = false;
232 Ok((header_cache, best_header))
233}
234
235struct DynamicChainListener<'a, L: chain::Listen + ?Sized>(&'a L);
237
238impl<'a, L: chain::Listen + ?Sized> chain::Listen for DynamicChainListener<'a, L> {
239 fn filtered_block_connected(
240 &self, _header: &Header, _txdata: &chain::transaction::TransactionData, _height: u32,
241 ) {
242 unreachable!()
243 }
244
245 fn blocks_disconnected(&self, fork_point: BlockLocator) {
246 self.0.blocks_disconnected(fork_point)
247 }
248}
249
250#[cfg(test)]
251mod tests {
252 use super::*;
253 use crate::test_utils::{Blockchain, MockChainListener};
254
255 #[tokio::test]
256 async fn sync_from_same_chain() {
257 let chain = Blockchain::default().with_height(4);
258
259 let listener_1 = MockChainListener::new()
260 .expect_block_connected(*chain.at_height(2))
261 .expect_block_connected(*chain.at_height(3))
262 .expect_block_connected(*chain.at_height(4));
263 let listener_2 = MockChainListener::new()
264 .expect_block_connected(*chain.at_height(3))
265 .expect_block_connected(*chain.at_height(4));
266 let listener_3 = MockChainListener::new().expect_block_connected(*chain.at_height(4));
267
268 let listeners = vec![
269 (chain.block_locator_at_height(1), &listener_1 as &dyn chain::Listen),
270 (chain.block_locator_at_height(2), &listener_2 as &dyn chain::Listen),
271 (chain.block_locator_at_height(3), &listener_3 as &dyn chain::Listen),
272 ];
273 match synchronize_listeners(&chain, Network::Bitcoin, listeners).await {
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 },
281 Err(e) => panic!("Unexpected error: {:?}", e),
282 }
283 }
284
285 #[tokio::test]
286 async fn sync_from_different_chains() {
287 let main_chain = Blockchain::default().with_height(4);
288 let fork_chain_1 = main_chain.fork_at_height(1);
289 let fork_chain_2 = main_chain.fork_at_height(2);
290 let fork_chain_3 = main_chain.fork_at_height(3);
291
292 let listener_1 = MockChainListener::new()
293 .expect_blocks_disconnected(*fork_chain_1.at_height(1))
294 .expect_block_connected(*main_chain.at_height(2))
295 .expect_block_connected(*main_chain.at_height(3))
296 .expect_block_connected(*main_chain.at_height(4));
297 let listener_2 = MockChainListener::new()
298 .expect_blocks_disconnected(*fork_chain_2.at_height(2))
299 .expect_block_connected(*main_chain.at_height(3))
300 .expect_block_connected(*main_chain.at_height(4));
301 let listener_3 = MockChainListener::new()
302 .expect_blocks_disconnected(*fork_chain_3.at_height(3))
303 .expect_block_connected(*main_chain.at_height(4));
304
305 let listeners = vec![
306 (fork_chain_1.best_block(), &listener_1 as &dyn chain::Listen),
307 (fork_chain_2.best_block(), &listener_2 as &dyn chain::Listen),
308 (fork_chain_3.best_block(), &listener_3 as &dyn chain::Listen),
309 ];
310 match synchronize_listeners(&main_chain, Network::Bitcoin, listeners).await {
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 },
320 Err(e) => panic!("Unexpected error: {:?}", e),
321 }
322 }
323
324 #[tokio::test]
325 async fn sync_from_overlapping_chains() {
326 let main_chain = Blockchain::default().with_height(4);
327 let fork_chain_1 = main_chain.fork_at_height(1);
328 let fork_chain_2 = fork_chain_1.fork_at_height(2);
329 let fork_chain_3 = fork_chain_2.fork_at_height(3);
330
331 let listener_1 = MockChainListener::new()
332 .expect_blocks_disconnected(*fork_chain_1.at_height(1))
333 .expect_block_connected(*main_chain.at_height(2))
334 .expect_block_connected(*main_chain.at_height(3))
335 .expect_block_connected(*main_chain.at_height(4));
336 let listener_2 = MockChainListener::new()
337 .expect_blocks_disconnected(*fork_chain_2.at_height(1))
338 .expect_block_connected(*main_chain.at_height(2))
339 .expect_block_connected(*main_chain.at_height(3))
340 .expect_block_connected(*main_chain.at_height(4));
341 let listener_3 = MockChainListener::new()
342 .expect_blocks_disconnected(*fork_chain_3.at_height(1))
343 .expect_block_connected(*main_chain.at_height(2))
344 .expect_block_connected(*main_chain.at_height(3))
345 .expect_block_connected(*main_chain.at_height(4));
346
347 let listeners = vec![
348 (fork_chain_1.best_block(), &listener_1 as &dyn chain::Listen),
349 (fork_chain_2.best_block(), &listener_2 as &dyn chain::Listen),
350 (fork_chain_3.best_block(), &listener_3 as &dyn chain::Listen),
351 ];
352 match synchronize_listeners(&main_chain, Network::Bitcoin, listeners).await {
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 },
363 Err(e) => panic!("Unexpected error: {:?}", e),
364 }
365 }
366}