1#![deny(rustdoc::broken_intra_doc_links)]
17#![deny(rustdoc::private_intra_doc_links)]
18#![deny(missing_docs)]
19#![cfg_attr(docsrs, feature(doc_cfg))]
20
21extern crate alloc;
22extern crate core;
23
24#[cfg(any(feature = "rest-client", feature = "rpc-client"))]
25pub mod http;
26
27pub mod init;
28pub mod poll;
29
30pub mod gossip;
31
32#[cfg(feature = "rest-client")]
33pub mod rest;
34
35#[cfg(feature = "rpc-client")]
36pub mod rpc;
37
38#[cfg(any(feature = "rest-client", feature = "rpc-client"))]
39mod convert;
40
41#[cfg(test)]
42mod test_utils;
43
44#[cfg(any(feature = "rest-client", feature = "rpc-client"))]
45mod utils;
46
47#[allow(unused)]
48mod async_poll;
49
50use crate::poll::{ChainTip, Poll, ValidatedBlockHeader};
51
52use bitcoin::block::{Block, Header};
53use bitcoin::hash_types::BlockHash;
54use bitcoin::pow::Work;
55
56use lightning::chain;
57use lightning::chain::BlockLocator;
58
59use std::future::Future;
60use std::ops::Deref;
61
62pub trait BlockSource: Sync + Send {
64 fn get_header<'a>(
71 &'a self, header_hash: &'a BlockHash, height_hint: Option<u32>,
72 ) -> impl Future<Output = BlockSourceResult<BlockHeaderData>> + Send + 'a;
73
74 fn get_block<'a>(
77 &'a self, header_hash: &'a BlockHash,
78 ) -> impl Future<Output = BlockSourceResult<BlockData>> + Send + 'a;
79
80 fn get_best_block<'a>(
87 &'a self,
88 ) -> impl Future<Output = BlockSourceResult<(BlockHash, Option<u32>)>> + Send + 'a;
89}
90
91pub type BlockSourceResult<T> = Result<T, BlockSourceError>;
93
94#[derive(Debug)]
99pub struct BlockSourceError {
100 kind: BlockSourceErrorKind,
101 error: Box<dyn std::error::Error + Send + Sync>,
102}
103
104#[derive(Clone, Copy, Debug, PartialEq, Eq)]
106pub enum BlockSourceErrorKind {
107 Persistent,
109
110 Transient,
112}
113
114impl BlockSourceError {
115 pub fn persistent<E>(error: E) -> Self
117 where
118 E: Into<Box<dyn std::error::Error + Send + Sync>>,
119 {
120 Self { kind: BlockSourceErrorKind::Persistent, error: error.into() }
121 }
122
123 pub fn transient<E>(error: E) -> Self
125 where
126 E: Into<Box<dyn std::error::Error + Send + Sync>>,
127 {
128 Self { kind: BlockSourceErrorKind::Transient, error: error.into() }
129 }
130
131 pub fn kind(&self) -> BlockSourceErrorKind {
133 self.kind
134 }
135
136 pub fn into_inner(self) -> Box<dyn std::error::Error + Send + Sync> {
141 self.error
142 }
143}
144
145#[derive(Clone, Copy, Debug, PartialEq, Eq)]
148pub struct BlockHeaderData {
149 pub header: Header,
151
152 pub height: u32,
154
155 pub chainwork: Work,
157}
158
159pub enum BlockData {
165 FullBlock(Block),
167 HeaderOnly(Header),
169}
170
171pub struct SpvClient<P: Poll, L: Deref>
179where
180 L::Target: chain::Listen,
181{
182 chain_tip: ValidatedBlockHeader,
183 chain_poller: P,
184 header_cache: HeaderCache,
185 chain_listener: L,
186}
187
188pub const HEADER_CACHE_LIMIT: u32 = 6 * 24 * 7;
190
191pub struct HeaderCache {
195 headers: std::collections::HashMap<BlockHash, ValidatedBlockHeader>,
196 retain_on_disconnect: bool,
199}
200
201impl HeaderCache {
202 pub fn new() -> Self {
204 Self { headers: std::collections::HashMap::new(), retain_on_disconnect: false }
205 }
206
207 pub fn look_up(&self, block_hash: &BlockHash) -> Option<&ValidatedBlockHeader> {
209 self.headers.get(block_hash)
210 }
211
212 pub(crate) fn block_connected(
215 &mut self, block_hash: BlockHash, block_header: ValidatedBlockHeader,
216 ) {
217 self.headers.insert(block_hash, block_header);
218
219 let cutoff_height = block_header.height.saturating_sub(HEADER_CACHE_LIMIT);
221 self.headers.retain(|_, header| header.height >= cutoff_height);
222 }
223
224 pub(crate) fn insert_during_diff(
227 &mut self, block_hash: BlockHash, block_header: ValidatedBlockHeader,
228 ) {
229 self.headers.insert(block_hash, block_header);
230
231 let best_height = self.headers.iter().map(|(_, header)| header.height).max().unwrap_or(0);
233 let cutoff_height = best_height.saturating_sub(HEADER_CACHE_LIMIT);
234 self.headers.retain(|_, header| header.height >= cutoff_height);
235 }
236
237 pub(crate) fn blocks_disconnected(&mut self, fork_point: &ValidatedBlockHeader) {
243 if !self.retain_on_disconnect {
244 self.headers.retain(|_, block_info| block_info.height <= fork_point.height);
245 }
246 }
247}
248
249impl<P: Poll, L: Deref> SpvClient<P, L>
250where
251 L::Target: chain::Listen,
252{
253 pub fn new(
264 chain_tip: ValidatedBlockHeader, chain_poller: P, header_cache: HeaderCache,
265 chain_listener: L,
266 ) -> Self {
267 Self { chain_tip, chain_poller, header_cache, chain_listener }
268 }
269
270 pub async fn poll_best_tip(&mut self) -> BlockSourceResult<(ChainTip, bool)> {
276 let chain_tip = self.chain_poller.poll_chain_tip(self.chain_tip).await?;
277 let blocks_connected = match chain_tip {
278 ChainTip::Common => false,
279 ChainTip::Better(chain_tip) => {
280 debug_assert_ne!(chain_tip.block_hash, self.chain_tip.block_hash);
281 debug_assert!(chain_tip.chainwork > self.chain_tip.chainwork);
282 self.update_chain_tip(chain_tip).await
283 },
284 ChainTip::Worse(chain_tip) => {
285 debug_assert_ne!(chain_tip.block_hash, self.chain_tip.block_hash);
286 debug_assert!(chain_tip.chainwork <= self.chain_tip.chainwork);
287 false
288 },
289 };
290 Ok((chain_tip, blocks_connected))
291 }
292
293 async fn update_chain_tip(&mut self, best_chain_tip: ValidatedBlockHeader) -> bool {
296 let mut chain_notifier = ChainNotifier {
297 header_cache: &mut self.header_cache,
298 chain_listener: &*self.chain_listener,
299 };
300 match chain_notifier
301 .synchronize_listener(best_chain_tip, &self.chain_tip, &mut self.chain_poller)
302 .await
303 {
304 Ok(_) => {
305 self.chain_tip = best_chain_tip;
306 true
307 },
308 Err((_, Some(chain_tip))) if chain_tip.block_hash != self.chain_tip.block_hash => {
309 self.chain_tip = chain_tip;
310 true
311 },
312 Err(_) => false,
313 }
314 }
315}
316
317pub(crate) struct ChainNotifier<'a, L: chain::Listen + ?Sized> {
321 pub(crate) header_cache: &'a mut HeaderCache,
323
324 pub(crate) chain_listener: &'a L,
326}
327
328struct ChainDifference {
334 common_ancestor: ValidatedBlockHeader,
338
339 connected_blocks: Vec<ValidatedBlockHeader>,
341}
342
343impl<'a, L: chain::Listen + ?Sized> ChainNotifier<'a, L> {
344 async fn synchronize_listener<P: Poll>(
352 &mut self, new_header: ValidatedBlockHeader, old_header: &ValidatedBlockHeader,
353 chain_poller: &mut P,
354 ) -> Result<(), (BlockSourceError, Option<ValidatedBlockHeader>)> {
355 let difference = self
356 .find_difference_from_header(new_header, old_header, chain_poller)
357 .await
358 .map_err(|e| (e, None))?;
359 if difference.common_ancestor != *old_header {
360 self.disconnect_blocks(difference.common_ancestor);
361 }
362 self.connect_blocks(difference.common_ancestor, difference.connected_blocks, chain_poller)
363 .await
364 }
365
366 async fn find_difference_from_best_block<P: Poll>(
375 &mut self, current_header: ValidatedBlockHeader, prev_best_block: BlockLocator,
376 chain_poller: &mut P,
377 ) -> BlockSourceResult<ChainDifference> {
378 let cur_tip = core::iter::once((0, &prev_best_block.block_hash));
381 let prev_tips =
382 prev_best_block.previous_blocks.iter().enumerate().filter_map(|(idx, hash_opt)| {
383 if let Some(block_hash) = hash_opt {
384 Some((idx as u32 + 1, block_hash))
385 } else {
386 None
387 }
388 });
389 let mut found_header = None;
390 for (height_diff, block_hash) in cur_tip.chain(prev_tips) {
391 if let Some(header) = self.header_cache.look_up(block_hash) {
392 found_header = Some(*header);
393 break;
394 }
395 let height = prev_best_block.height.checked_sub(height_diff).ok_or(
396 BlockSourceError::persistent(
397 "BlockLocator had more previous_blocks than its height",
398 ),
399 )?;
400 if let Ok(header) = chain_poller.get_header(block_hash, Some(height)).await {
401 found_header = Some(header);
402 self.header_cache.insert_during_diff(*block_hash, header);
403 break;
404 }
405 }
406 let found_header = found_header.ok_or_else(|| {
407 BlockSourceError::persistent("could not resolve any block from BlockLocator")
408 })?;
409
410 self.find_difference_from_header(current_header, &found_header, chain_poller).await
411 }
412
413 async fn find_difference_from_header<P: Poll>(
418 &self, current_header: ValidatedBlockHeader, prev_header: &ValidatedBlockHeader,
419 chain_poller: &mut P,
420 ) -> BlockSourceResult<ChainDifference> {
421 let mut connected_blocks = Vec::new();
422 let mut current = current_header;
423 let mut previous = *prev_header;
424 loop {
425 if current.block_hash == previous.block_hash {
427 break;
428 }
429
430 let current_height = current.height;
433 let previous_height = previous.height;
434 if current_height <= previous_height {
435 previous = self.look_up_previous_header(chain_poller, &previous).await?;
436 }
437 if current_height >= previous_height {
438 connected_blocks.push(current);
439 current = self.look_up_previous_header(chain_poller, ¤t).await?;
440 }
441 }
442
443 let common_ancestor = current;
444 Ok(ChainDifference { common_ancestor, connected_blocks })
445 }
446
447 async fn look_up_previous_header<P: Poll>(
450 &self, chain_poller: &mut P, header: &ValidatedBlockHeader,
451 ) -> BlockSourceResult<ValidatedBlockHeader> {
452 match self.header_cache.look_up(&header.header.prev_blockhash) {
453 Some(prev_header) => Ok(*prev_header),
454 None => chain_poller.look_up_previous_header(header).await,
455 }
456 }
457
458 fn disconnect_blocks(&mut self, fork_point: ValidatedBlockHeader) {
460 self.header_cache.blocks_disconnected(&fork_point);
461 let best_block = BlockLocator::new(fork_point.block_hash, fork_point.height);
462 self.chain_listener.blocks_disconnected(best_block);
463 }
464
465 async fn connect_blocks<P: Poll>(
467 &mut self, mut new_tip: ValidatedBlockHeader,
468 mut connected_blocks: Vec<ValidatedBlockHeader>, chain_poller: &mut P,
469 ) -> Result<(), (BlockSourceError, Option<ValidatedBlockHeader>)> {
470 for header in connected_blocks.drain(..).rev() {
471 let height = header.height;
472 let block_data =
473 chain_poller.fetch_block(&header).await.map_err(|e| (e, Some(new_tip)))?;
474 debug_assert_eq!(block_data.block_hash, header.block_hash);
475
476 match block_data.deref() {
477 BlockData::FullBlock(block) => {
478 self.chain_listener.block_connected(block, height);
479 },
480 BlockData::HeaderOnly(header) => {
481 self.chain_listener.filtered_block_connected(header, &[], height);
482 },
483 }
484
485 self.header_cache.block_connected(header.block_hash, header);
486 new_tip = header;
487 }
488
489 Ok(())
490 }
491}
492
493#[cfg(test)]
494mod spv_client_tests {
495 use super::*;
496 use crate::test_utils::{Blockchain, NullChainListener};
497
498 use bitcoin::network::Network;
499
500 #[tokio::test]
501 async fn poll_from_chain_without_headers() {
502 let mut chain = Blockchain::default().with_height(3).without_headers();
503 let best_tip = chain.at_height(1);
504
505 let poller = poll::ChainPoller::new(&mut chain, Network::Testnet);
506 let cache = HeaderCache::new();
507 let mut listener = NullChainListener {};
508 let mut client = SpvClient::new(best_tip, poller, cache, &mut listener);
509 match client.poll_best_tip().await {
510 Err(e) => {
511 assert_eq!(e.kind(), BlockSourceErrorKind::Persistent);
512 assert_eq!(e.into_inner().as_ref().to_string(), "header not found");
513 },
514 Ok(_) => panic!("Expected error"),
515 }
516 assert_eq!(client.chain_tip, best_tip);
517 }
518
519 #[tokio::test]
520 async fn poll_from_chain_with_common_tip() {
521 let mut chain = Blockchain::default().with_height(3);
522 let common_tip = chain.tip();
523
524 let poller = poll::ChainPoller::new(&mut chain, Network::Testnet);
525 let cache = HeaderCache::new();
526 let mut listener = NullChainListener {};
527 let mut client = SpvClient::new(common_tip, poller, cache, &mut listener);
528 match client.poll_best_tip().await {
529 Err(e) => panic!("Unexpected error: {:?}", e),
530 Ok((chain_tip, blocks_connected)) => {
531 assert_eq!(chain_tip, ChainTip::Common);
532 assert!(!blocks_connected);
533 },
534 }
535 assert_eq!(client.chain_tip, common_tip);
536 }
537
538 #[tokio::test]
539 async fn poll_from_chain_with_better_tip() {
540 let mut chain = Blockchain::default().with_height(3);
541 let new_tip = chain.tip();
542 let old_tip = chain.at_height(1);
543
544 let poller = poll::ChainPoller::new(&mut chain, Network::Testnet);
545 let cache = HeaderCache::new();
546 let mut listener = NullChainListener {};
547 let mut client = SpvClient::new(old_tip, poller, cache, &mut listener);
548 match client.poll_best_tip().await {
549 Err(e) => panic!("Unexpected error: {:?}", e),
550 Ok((chain_tip, blocks_connected)) => {
551 assert_eq!(chain_tip, ChainTip::Better(new_tip));
552 assert!(blocks_connected);
553 },
554 }
555 assert_eq!(client.chain_tip, new_tip);
556 }
557
558 #[tokio::test]
559 async fn poll_from_chain_with_better_tip_and_without_any_new_blocks() {
560 let mut chain = Blockchain::default().with_height(3).without_blocks(2..);
561 let new_tip = chain.tip();
562 let old_tip = chain.at_height(1);
563
564 let poller = poll::ChainPoller::new(&mut chain, Network::Testnet);
565 let cache = HeaderCache::new();
566 let mut listener = NullChainListener {};
567 let mut client = SpvClient::new(old_tip, poller, cache, &mut listener);
568 match client.poll_best_tip().await {
569 Err(e) => panic!("Unexpected error: {:?}", e),
570 Ok((chain_tip, blocks_connected)) => {
571 assert_eq!(chain_tip, ChainTip::Better(new_tip));
572 assert!(!blocks_connected);
573 },
574 }
575 assert_eq!(client.chain_tip, old_tip);
576 }
577
578 #[tokio::test]
579 async fn poll_from_chain_with_better_tip_and_without_some_new_blocks() {
580 let mut chain = Blockchain::default().with_height(3).without_blocks(3..);
581 let new_tip = chain.tip();
582 let old_tip = chain.at_height(1);
583
584 let poller = poll::ChainPoller::new(&mut chain, Network::Testnet);
585 let cache = HeaderCache::new();
586 let mut listener = NullChainListener {};
587 let mut client = SpvClient::new(old_tip, poller, cache, &mut listener);
588 match client.poll_best_tip().await {
589 Err(e) => panic!("Unexpected error: {:?}", e),
590 Ok((chain_tip, blocks_connected)) => {
591 assert_eq!(chain_tip, ChainTip::Better(new_tip));
592 assert!(blocks_connected);
593 },
594 }
595 assert_eq!(client.chain_tip, chain.at_height(2));
596 }
597
598 #[tokio::test]
599 async fn poll_from_chain_with_worse_tip() {
600 let mut chain = Blockchain::default().with_height(3);
601 let best_tip = chain.tip();
602 chain.disconnect_tip();
603 let worse_tip = chain.tip();
604
605 let poller = poll::ChainPoller::new(&mut chain, Network::Testnet);
606 let cache = HeaderCache::new();
607 let mut listener = NullChainListener {};
608 let mut client = SpvClient::new(best_tip, poller, cache, &mut listener);
609 match client.poll_best_tip().await {
610 Err(e) => panic!("Unexpected error: {:?}", e),
611 Ok((chain_tip, blocks_connected)) => {
612 assert_eq!(chain_tip, ChainTip::Worse(worse_tip));
613 assert!(!blocks_connected);
614 },
615 }
616 assert_eq!(client.chain_tip, best_tip);
617 }
618}
619
620#[cfg(test)]
621mod chain_notifier_tests {
622 use super::*;
623 use crate::test_utils::{Blockchain, MockChainListener};
624
625 use bitcoin::network::Network;
626
627 #[tokio::test]
628 async fn sync_from_same_chain() {
629 let mut chain = Blockchain::default().with_height(3);
630
631 let new_tip = chain.tip();
632 let old_tip = chain.at_height(1);
633 let chain_listener = &MockChainListener::new()
634 .expect_block_connected(*chain.at_height(2))
635 .expect_block_connected(*new_tip);
636 let mut notifier =
637 ChainNotifier { header_cache: &mut chain.header_cache(0..=1), chain_listener };
638 let mut poller = poll::ChainPoller::new(&mut chain, Network::Testnet);
639 match notifier.synchronize_listener(new_tip, &old_tip, &mut poller).await {
640 Err((e, _)) => panic!("Unexpected error: {:?}", e),
641 Ok(_) => {},
642 }
643 }
644
645 #[tokio::test]
646 async fn sync_from_different_chains() {
647 let mut test_chain = Blockchain::with_network(Network::Testnet).with_height(1);
648 let main_chain = Blockchain::with_network(Network::Bitcoin).with_height(1);
649
650 let new_tip = test_chain.tip();
651 let old_tip = main_chain.tip();
652 let chain_listener = &MockChainListener::new();
653 let mut notifier =
654 ChainNotifier { header_cache: &mut main_chain.header_cache(0..=1), chain_listener };
655 let mut poller = poll::ChainPoller::new(&mut test_chain, Network::Testnet);
656 match notifier.synchronize_listener(new_tip, &old_tip, &mut poller).await {
657 Err((e, _)) => {
658 assert_eq!(e.kind(), BlockSourceErrorKind::Persistent);
659 assert_eq!(e.into_inner().as_ref().to_string(), "genesis block reached");
660 },
661 Ok(_) => panic!("Expected error"),
662 }
663 }
664
665 #[tokio::test]
666 async fn sync_from_equal_length_fork() {
667 let main_chain = Blockchain::default().with_height(2);
668 let mut fork_chain = main_chain.fork_at_height(1);
669
670 let new_tip = fork_chain.tip();
671 let old_tip = main_chain.tip();
672 let chain_listener = &MockChainListener::new()
673 .expect_blocks_disconnected(*fork_chain.at_height(1))
674 .expect_block_connected(*new_tip);
675 let mut notifier =
676 ChainNotifier { header_cache: &mut main_chain.header_cache(0..=2), chain_listener };
677 let mut poller = poll::ChainPoller::new(&mut fork_chain, Network::Testnet);
678 match notifier.synchronize_listener(new_tip, &old_tip, &mut poller).await {
679 Err((e, _)) => panic!("Unexpected error: {:?}", e),
680 Ok(_) => {},
681 }
682 }
683
684 #[tokio::test]
685 async fn sync_from_shorter_fork() {
686 let main_chain = Blockchain::default().with_height(3);
687 let mut fork_chain = main_chain.fork_at_height(1);
688 fork_chain.disconnect_tip();
689
690 let new_tip = fork_chain.tip();
691 let old_tip = main_chain.tip();
692 let chain_listener = &MockChainListener::new()
693 .expect_blocks_disconnected(*main_chain.at_height(1))
694 .expect_block_connected(*new_tip);
695 let mut notifier =
696 ChainNotifier { header_cache: &mut main_chain.header_cache(0..=3), chain_listener };
697 let mut poller = poll::ChainPoller::new(&mut fork_chain, Network::Testnet);
698 match notifier.synchronize_listener(new_tip, &old_tip, &mut poller).await {
699 Err((e, _)) => panic!("Unexpected error: {:?}", e),
700 Ok(_) => {},
701 }
702 }
703
704 #[tokio::test]
705 async fn sync_from_longer_fork() {
706 let mut main_chain = Blockchain::default().with_height(3);
707 let mut fork_chain = main_chain.fork_at_height(1);
708 main_chain.disconnect_tip();
709
710 let new_tip = fork_chain.tip();
711 let old_tip = main_chain.tip();
712 let chain_listener = &MockChainListener::new()
713 .expect_blocks_disconnected(*fork_chain.at_height(1))
714 .expect_block_connected(*fork_chain.at_height(2))
715 .expect_block_connected(*new_tip);
716 let mut notifier =
717 ChainNotifier { header_cache: &mut main_chain.header_cache(0..=2), chain_listener };
718 let mut poller = poll::ChainPoller::new(&mut fork_chain, Network::Testnet);
719 match notifier.synchronize_listener(new_tip, &old_tip, &mut poller).await {
720 Err((e, _)) => panic!("Unexpected error: {:?}", e),
721 Ok(_) => {},
722 }
723 }
724
725 #[tokio::test]
726 async fn sync_from_chain_without_headers() {
727 let mut chain = Blockchain::default().with_height(3).without_headers();
728
729 let new_tip = chain.tip();
730 let old_tip = chain.at_height(1);
731 let chain_listener = &MockChainListener::new();
732 let mut notifier =
733 ChainNotifier { header_cache: &mut chain.header_cache(0..=1), chain_listener };
734 let mut poller = poll::ChainPoller::new(&mut chain, Network::Testnet);
735 match notifier.synchronize_listener(new_tip, &old_tip, &mut poller).await {
736 Err((_, tip)) => assert_eq!(tip, None),
737 Ok(_) => panic!("Expected error"),
738 }
739 }
740
741 #[tokio::test]
742 async fn sync_from_chain_without_any_new_blocks() {
743 let mut chain = Blockchain::default().with_height(3).without_blocks(2..);
744
745 let new_tip = chain.tip();
746 let old_tip = chain.at_height(1);
747 let chain_listener = &MockChainListener::new();
748 let mut notifier =
749 ChainNotifier { header_cache: &mut chain.header_cache(0..=3), chain_listener };
750 let mut poller = poll::ChainPoller::new(&mut chain, Network::Testnet);
751 match notifier.synchronize_listener(new_tip, &old_tip, &mut poller).await {
752 Err((_, tip)) => assert_eq!(tip, Some(old_tip)),
753 Ok(_) => panic!("Expected error"),
754 }
755 }
756
757 #[tokio::test]
758 async fn sync_from_chain_without_some_new_blocks() {
759 let mut chain = Blockchain::default().with_height(3).without_blocks(3..);
760
761 let new_tip = chain.tip();
762 let old_tip = chain.at_height(1);
763 let chain_listener = &MockChainListener::new().expect_block_connected(*chain.at_height(2));
764 let mut notifier =
765 ChainNotifier { header_cache: &mut chain.header_cache(0..=3), chain_listener };
766 let mut poller = poll::ChainPoller::new(&mut chain, Network::Testnet);
767 match notifier.synchronize_listener(new_tip, &old_tip, &mut poller).await {
768 Err((_, tip)) => assert_eq!(tip, Some(chain.at_height(2))),
769 Ok(_) => panic!("Expected error"),
770 }
771 }
772
773 #[tokio::test]
774 async fn sync_from_chain_with_filtered_blocks() {
775 let mut chain = Blockchain::default().with_height(3).filtered_blocks();
776
777 let new_tip = chain.tip();
778 let old_tip = chain.at_height(1);
779 let chain_listener = &MockChainListener::new()
780 .expect_filtered_block_connected(*chain.at_height(2))
781 .expect_filtered_block_connected(*new_tip);
782 let mut notifier =
783 ChainNotifier { header_cache: &mut chain.header_cache(0..=1), chain_listener };
784 let mut poller = poll::ChainPoller::new(&mut chain, Network::Testnet);
785 match notifier.synchronize_listener(new_tip, &old_tip, &mut poller).await {
786 Err((e, _)) => panic!("Unexpected error: {:?}", e),
787 Ok(_) => {},
788 }
789 }
790}