]> git.bitcoin.ninja Git - rust-lightning/blob - lightning-block-sync/src/lib.rs
But what about mut?
[rust-lightning] / lightning-block-sync / src / lib.rs
1 //! A lightweight client for keeping in sync with chain activity.
2 //!
3 //! Defines an [`SpvClient`] utility for polling one or more block sources for the best chain tip.
4 //! It is used to notify listeners of blocks connected or disconnected since the last poll. Useful
5 //! for keeping a Lightning node in sync with the chain.
6 //!
7 //! Defines a [`BlockSource`] trait, which is an asynchronous interface for retrieving block headers
8 //! and data.
9 //!
10 //! Enabling feature `rest-client` or `rpc-client` allows configuring the client to fetch blocks
11 //! using Bitcoin Core's REST or RPC interface, respectively.
12 //!
13 //! Both features support either blocking I/O using `std::net::TcpStream` or, with feature `tokio`,
14 //! non-blocking I/O using `tokio::net::TcpStream` from inside a Tokio runtime.
15 //!
16 //! [`SpvClient`]: struct.SpvClient.html
17 //! [`BlockSource`]: trait.BlockSource.html
18
19 #[cfg(any(feature = "rest-client", feature = "rpc-client"))]
20 pub mod http;
21
22 pub mod init;
23 pub mod poll;
24
25 #[cfg(feature = "rest-client")]
26 pub mod rest;
27
28 #[cfg(feature = "rpc-client")]
29 pub mod rpc;
30
31 #[cfg(any(feature = "rest-client", feature = "rpc-client"))]
32 mod convert;
33
34 #[cfg(test)]
35 mod test_utils;
36
37 #[cfg(any(feature = "rest-client", feature = "rpc-client"))]
38 mod utils;
39
40 use crate::poll::{ChainTip, Poll, ValidatedBlockHeader};
41
42 use bitcoin::blockdata::block::{Block, BlockHeader};
43 use bitcoin::hash_types::BlockHash;
44 use bitcoin::util::uint::Uint256;
45
46 use lightning::chain;
47 use lightning::chain::Listen;
48
49 use std::future::Future;
50 use std::ops::Deref;
51 use std::pin::Pin;
52
53 /// Abstract type for retrieving block headers and data.
54 pub trait BlockSource : Sync + Send {
55         /// Returns the header for a given hash. A height hint may be provided in case a block source
56         /// cannot easily find headers based on a hash. This is merely a hint and thus the returned
57         /// header must have the same hash as was requested. Otherwise, an error must be returned.
58         ///
59         /// Implementations that cannot find headers based on the hash should return a `Transient` error
60         /// when `height_hint` is `None`.
61         fn get_header<'a>(&'a mut self, header_hash: &'a BlockHash, height_hint: Option<u32>) -> AsyncBlockSourceResult<'a, BlockHeaderData>;
62
63         /// Returns the block for a given hash. A headers-only block source should return a `Transient`
64         /// error.
65         fn get_block<'a>(&'a mut self, header_hash: &'a BlockHash) -> AsyncBlockSourceResult<'a, Block>;
66
67         /// Returns the hash of the best block and, optionally, its height.
68         ///
69         /// When polling a block source, [`Poll`] implementations may pass the height to [`get_header`]
70         /// to allow for a more efficient lookup.
71         ///
72         /// [`Poll`]: poll/trait.Poll.html
73         /// [`get_header`]: #tymethod.get_header
74         fn get_best_block<'a>(&'a mut self) -> AsyncBlockSourceResult<(BlockHash, Option<u32>)>;
75 }
76
77 /// Result type for `BlockSource` requests.
78 type BlockSourceResult<T> = Result<T, BlockSourceError>;
79
80 // TODO: Replace with BlockSourceResult once `async` trait functions are supported. For details,
81 // see: https://areweasyncyet.rs.
82 /// Result type for asynchronous `BlockSource` requests.
83 type AsyncBlockSourceResult<'a, T> = Pin<Box<dyn Future<Output = BlockSourceResult<T>> + 'a + Send>>;
84
85 /// Error type for `BlockSource` requests.
86 ///
87 /// Transient errors may be resolved when re-polling, but no attempt will be made to re-poll on
88 /// persistent errors.
89 #[derive(Debug)]
90 pub struct BlockSourceError {
91         kind: BlockSourceErrorKind,
92         error: Box<dyn std::error::Error + Send + Sync>,
93 }
94
95 /// The kind of `BlockSourceError`, either persistent or transient.
96 #[derive(Clone, Copy, Debug, PartialEq)]
97 pub enum BlockSourceErrorKind {
98         /// Indicates an error that won't resolve when retrying a request (e.g., invalid data).
99         Persistent,
100
101         /// Indicates an error that may resolve when retrying a request (e.g., unresponsive).
102         Transient,
103 }
104
105 impl BlockSourceError {
106         /// Creates a new persistent error originated from the given error.
107         pub fn persistent<E>(error: E) -> Self
108         where E: Into<Box<dyn std::error::Error + Send + Sync>> {
109                 Self {
110                         kind: BlockSourceErrorKind::Persistent,
111                         error: error.into(),
112                 }
113         }
114
115         /// Creates a new transient error originated from the given error.
116         pub fn transient<E>(error: E) -> Self
117         where E: Into<Box<dyn std::error::Error + Send + Sync>> {
118                 Self {
119                         kind: BlockSourceErrorKind::Transient,
120                         error: error.into(),
121                 }
122         }
123
124         /// Returns the kind of error.
125         pub fn kind(&self) -> BlockSourceErrorKind {
126                 self.kind
127         }
128
129         /// Converts the error into the underlying error.
130         pub fn into_inner(self) -> Box<dyn std::error::Error + Send + Sync> {
131                 self.error
132         }
133 }
134
135 /// A block header and some associated data. This information should be available from most block
136 /// sources (and, notably, is available in Bitcoin Core's RPC and REST interfaces).
137 #[derive(Clone, Copy, Debug, PartialEq)]
138 pub struct BlockHeaderData {
139         /// The block header itself.
140         pub header: BlockHeader,
141
142         /// The block height where the genesis block has height 0.
143         pub height: u32,
144
145         /// The total chain work in expected number of double-SHA256 hashes required to build a chain
146         /// of equivalent weight.
147         pub chainwork: Uint256,
148 }
149
150 /// A lightweight client for keeping a listener in sync with the chain, allowing for Simplified
151 /// Payment Verification (SPV).
152 ///
153 /// The client is parameterized by a chain poller which is responsible for polling one or more block
154 /// sources for the best chain tip. During this process it detects any chain forks, determines which
155 /// constitutes the best chain, and updates the listener accordingly with any blocks that were
156 /// connected or disconnected since the last poll.
157 ///
158 /// Block headers for the best chain are maintained in the parameterized cache, allowing for a
159 /// custom cache eviction policy. This offers flexibility to those sensitive to resource usage.
160 /// Hence, there is a trade-off between a lower memory footprint and potentially increased network
161 /// I/O as headers are re-fetched during fork detection.
162 pub struct SpvClient<'a, P: Poll, C: Cache, L: chain::Listen> {
163         chain_tip: ValidatedBlockHeader,
164         chain_poller: P,
165         chain_notifier: ChainNotifier<'a, C, L>,
166 }
167
168 /// The `Cache` trait defines behavior for managing a block header cache, where block headers are
169 /// keyed by block hash.
170 ///
171 /// Used by [`ChainNotifier`] to store headers along the best chain, which is important for ensuring
172 /// that blocks can be disconnected if they are no longer accessible from a block source (e.g., if
173 /// the block source does not store stale forks indefinitely).
174 ///
175 /// Implementations may define how long to retain headers such that it's unlikely they will ever be
176 /// needed to disconnect a block.  In cases where block sources provide access to headers on stale
177 /// forks reliably, caches may be entirely unnecessary.
178 ///
179 /// [`ChainNotifier`]: struct.ChainNotifier.html
180 pub trait Cache {
181         /// Retrieves the block header keyed by the given block hash.
182         fn look_up(&self, block_hash: &BlockHash) -> Option<&ValidatedBlockHeader>;
183
184         /// Called when a block has been connected to the best chain to ensure it is available to be
185         /// disconnected later if needed.
186         fn block_connected(&mut self, block_hash: BlockHash, block_header: ValidatedBlockHeader);
187
188         /// Called when a block has been disconnected from the best chain. Once disconnected, a block's
189         /// header is no longer needed and thus can be removed.
190         fn block_disconnected(&mut self, block_hash: &BlockHash) -> Option<ValidatedBlockHeader>;
191 }
192
193 /// Unbounded cache of block headers keyed by block hash.
194 pub type UnboundedCache = std::collections::HashMap<BlockHash, ValidatedBlockHeader>;
195
196 impl Cache for UnboundedCache {
197         fn look_up(&self, block_hash: &BlockHash) -> Option<&ValidatedBlockHeader> {
198                 self.get(block_hash)
199         }
200
201         fn block_connected(&mut self, block_hash: BlockHash, block_header: ValidatedBlockHeader) {
202                 self.insert(block_hash, block_header);
203         }
204
205         fn block_disconnected(&mut self, block_hash: &BlockHash) -> Option<ValidatedBlockHeader> {
206                 self.remove(block_hash)
207         }
208 }
209
210 impl<'a, P: Poll, C: Cache, L: chain::Listen> SpvClient<'a, P, C, L> {
211         /// Creates a new SPV client using `chain_tip` as the best known chain tip.
212         ///
213         /// Subsequent calls to [`poll_best_tip`] will poll for the best chain tip using the given chain
214         /// poller, which may be configured with one or more block sources to query. At least one block
215         /// source must provide headers back from the best chain tip to its common ancestor with
216         /// `chain_tip`.
217         /// * `header_cache` is used to look up and store headers on the best chain
218         /// * `chain_listener` is notified of any blocks connected or disconnected
219         ///
220         /// [`poll_best_tip`]: struct.SpvClient.html#method.poll_best_tip
221         pub fn new(
222                 chain_tip: ValidatedBlockHeader,
223                 chain_poller: P,
224                 header_cache: &'a mut C,
225                 chain_listener: L,
226         ) -> Self {
227                 let chain_notifier = ChainNotifier { header_cache, chain_listener };
228                 Self { chain_tip, chain_poller, chain_notifier }
229         }
230
231         /// Polls for the best tip and updates the chain listener with any connected or disconnected
232         /// blocks accordingly.
233         ///
234         /// Returns the best polled chain tip relative to the previous best known tip and whether any
235         /// blocks were indeed connected or disconnected.
236         pub async fn poll_best_tip(&mut self) -> BlockSourceResult<(ChainTip, bool)> {
237                 let chain_tip = self.chain_poller.poll_chain_tip(self.chain_tip).await?;
238                 let blocks_connected = match chain_tip {
239                         ChainTip::Common => false,
240                         ChainTip::Better(chain_tip) => {
241                                 debug_assert_ne!(chain_tip.block_hash, self.chain_tip.block_hash);
242                                 debug_assert!(chain_tip.chainwork > self.chain_tip.chainwork);
243                                 self.update_chain_tip(chain_tip).await
244                         },
245                         ChainTip::Worse(chain_tip) => {
246                                 debug_assert_ne!(chain_tip.block_hash, self.chain_tip.block_hash);
247                                 debug_assert!(chain_tip.chainwork <= self.chain_tip.chainwork);
248                                 false
249                         },
250                 };
251                 Ok((chain_tip, blocks_connected))
252         }
253
254         /// Updates the chain tip, syncing the chain listener with any connected or disconnected
255         /// blocks. Returns whether there were any such blocks.
256         async fn update_chain_tip(&mut self, best_chain_tip: ValidatedBlockHeader) -> bool {
257                 match self.chain_notifier.sync_listener(best_chain_tip, &self.chain_tip, &mut self.chain_poller).await {
258                         Ok(_) => {
259                                 self.chain_tip = best_chain_tip;
260                                 true
261                         },
262                         Err((_, Some(chain_tip))) if chain_tip.block_hash != self.chain_tip.block_hash => {
263                                 self.chain_tip = chain_tip;
264                                 true
265                         },
266                         Err(_) => false,
267                 }
268         }
269 }
270
271 /// Notifies [listeners] of blocks that have been connected or disconnected from the chain.
272 ///
273 /// [listeners]: ../../lightning/chain/trait.Listen.html
274 pub struct ChainNotifier<'a, C: Cache, L: chain::Listen> {
275         /// Cache for looking up headers before fetching from a block source.
276         header_cache: &'a mut C,
277
278         /// Listener that will be notified of connected or disconnected blocks.
279         chain_listener: L,
280 }
281
282 /// Changes made to the chain between subsequent polls that transformed it from having one chain tip
283 /// to another.
284 ///
285 /// Blocks are given in height-descending order. Therefore, blocks are first disconnected in order
286 /// before new blocks are connected in reverse order.
287 struct ChainDifference {
288         /// The most recent ancestor common between the chain tips.
289         ///
290         /// If there are any disconnected blocks, this is where the chain forked.
291         common_ancestor: ValidatedBlockHeader,
292
293         /// Blocks that were disconnected from the chain since the last poll.
294         disconnected_blocks: Vec<ValidatedBlockHeader>,
295
296         /// Blocks that were connected to the chain since the last poll.
297         connected_blocks: Vec<ValidatedBlockHeader>,
298 }
299
300 impl<'a, C: Cache, L: chain::Listen> ChainNotifier<'a, C, L> {
301         /// Finds the first common ancestor between `new_header` and `old_header`, disconnecting blocks
302         /// from `old_header` to get to that point and then connecting blocks until `new_header`.
303         ///
304         /// Validates headers along the transition path, but doesn't fetch blocks until the chain is
305         /// disconnected to the fork point. Thus, this may return an `Err` that includes where the tip
306         /// ended up which may not be `new_header`. Note that iff the returned `Err` contains `Some`
307         /// header then the transition from `old_header` to `new_header` is valid.
308         async fn sync_listener<P: Poll>(
309                 &mut self,
310                 new_header: ValidatedBlockHeader,
311                 old_header: &ValidatedBlockHeader,
312                 chain_poller: &mut P,
313         ) -> Result<(), (BlockSourceError, Option<ValidatedBlockHeader>)> {
314                 let difference = self.find_difference(new_header, old_header, chain_poller).await
315                         .map_err(|e| (e, None))?;
316                 self.disconnect_blocks(difference.disconnected_blocks);
317                 self.connect_blocks(
318                         difference.common_ancestor,
319                         difference.connected_blocks,
320                         chain_poller,
321                 ).await
322         }
323
324         /// Returns the changes needed to produce the chain with `current_header` as its tip from the
325         /// chain with `prev_header` as its tip.
326         ///
327         /// Walks backwards from `current_header` and `prev_header`, finding the common ancestor.
328         async fn find_difference<P: Poll>(
329                 &self,
330                 current_header: ValidatedBlockHeader,
331                 prev_header: &ValidatedBlockHeader,
332                 chain_poller: &mut P,
333         ) -> BlockSourceResult<ChainDifference> {
334                 let mut disconnected_blocks = Vec::new();
335                 let mut connected_blocks = Vec::new();
336                 let mut current = current_header;
337                 let mut previous = *prev_header;
338                 loop {
339                         // Found the common ancestor.
340                         if current.block_hash == previous.block_hash {
341                                 break;
342                         }
343
344                         // Walk back the chain, finding blocks needed to connect and disconnect. Only walk back
345                         // the header with the greater height, or both if equal heights.
346                         let current_height = current.height;
347                         let previous_height = previous.height;
348                         if current_height <= previous_height {
349                                 disconnected_blocks.push(previous);
350                                 previous = self.look_up_previous_header(chain_poller, &previous).await?;
351                         }
352                         if current_height >= previous_height {
353                                 connected_blocks.push(current);
354                                 current = self.look_up_previous_header(chain_poller, &current).await?;
355                         }
356                 }
357
358                 let common_ancestor = current;
359                 Ok(ChainDifference { common_ancestor, disconnected_blocks, connected_blocks })
360         }
361
362         /// Returns the previous header for the given header, either by looking it up in the cache or
363         /// fetching it if not found.
364         async fn look_up_previous_header<P: Poll>(
365                 &self,
366                 chain_poller: &mut P,
367                 header: &ValidatedBlockHeader,
368         ) -> BlockSourceResult<ValidatedBlockHeader> {
369                 match self.header_cache.look_up(&header.header.prev_blockhash) {
370                         Some(prev_header) => Ok(*prev_header),
371                         None => chain_poller.look_up_previous_header(header).await,
372                 }
373         }
374
375         /// Notifies the chain listeners of disconnected blocks.
376         fn disconnect_blocks(&mut self, mut disconnected_blocks: Vec<ValidatedBlockHeader>) {
377                 for header in disconnected_blocks.drain(..) {
378                         if let Some(cached_header) = self.header_cache.block_disconnected(&header.block_hash) {
379                                 assert_eq!(cached_header, header);
380                         }
381                         self.chain_listener.block_disconnected(&header.header, header.height);
382                 }
383         }
384
385         /// Notifies the chain listeners of connected blocks.
386         async fn connect_blocks<P: Poll>(
387                 &mut self,
388                 mut new_tip: ValidatedBlockHeader,
389                 mut connected_blocks: Vec<ValidatedBlockHeader>,
390                 chain_poller: &mut P,
391         ) -> Result<(), (BlockSourceError, Option<ValidatedBlockHeader>)> {
392                 for header in connected_blocks.drain(..).rev() {
393                         let block = chain_poller
394                                 .fetch_block(&header).await
395                                 .or_else(|e| Err((e, Some(new_tip))))?;
396                         debug_assert_eq!(block.block_hash, header.block_hash);
397
398                         self.header_cache.block_connected(header.block_hash, header);
399                         self.chain_listener.block_connected(&block, header.height);
400                         new_tip = header;
401                 }
402
403                 Ok(())
404         }
405 }
406
407 #[cfg(test)]
408 mod spv_client_tests {
409         use crate::test_utils::{Blockchain, NullChainListener};
410         use super::*;
411
412         use bitcoin::network::constants::Network;
413
414         #[tokio::test]
415         async fn poll_from_chain_without_headers() {
416                 let mut chain = Blockchain::default().with_height(3).without_headers();
417                 let best_tip = chain.at_height(1);
418
419                 let poller = poll::ChainPoller::new(&mut chain, Network::Testnet);
420                 let mut cache = UnboundedCache::new();
421                 let listener = NullChainListener {};
422                 let mut client = SpvClient::new(best_tip, poller, &mut cache, listener);
423                 match client.poll_best_tip().await {
424                         Err(e) => {
425                                 assert_eq!(e.kind(), BlockSourceErrorKind::Persistent);
426                                 assert_eq!(e.into_inner().as_ref().to_string(), "header not found");
427                         },
428                         Ok(_) => panic!("Expected error"),
429                 }
430                 assert_eq!(client.chain_tip, best_tip);
431         }
432
433         #[tokio::test]
434         async fn poll_from_chain_with_common_tip() {
435                 let mut chain = Blockchain::default().with_height(3);
436                 let common_tip = chain.tip();
437
438                 let poller = poll::ChainPoller::new(&mut chain, Network::Testnet);
439                 let mut cache = UnboundedCache::new();
440                 let listener = NullChainListener {};
441                 let mut client = SpvClient::new(common_tip, poller, &mut cache, listener);
442                 match client.poll_best_tip().await {
443                         Err(e) => panic!("Unexpected error: {:?}", e),
444                         Ok((chain_tip, blocks_connected)) => {
445                                 assert_eq!(chain_tip, ChainTip::Common);
446                                 assert!(!blocks_connected);
447                         },
448                 }
449                 assert_eq!(client.chain_tip, common_tip);
450         }
451
452         #[tokio::test]
453         async fn poll_from_chain_with_better_tip() {
454                 let mut chain = Blockchain::default().with_height(3);
455                 let new_tip = chain.tip();
456                 let old_tip = chain.at_height(1);
457
458                 let poller = poll::ChainPoller::new(&mut chain, Network::Testnet);
459                 let mut cache = UnboundedCache::new();
460                 let listener = NullChainListener {};
461                 let mut client = SpvClient::new(old_tip, poller, &mut cache, listener);
462                 match client.poll_best_tip().await {
463                         Err(e) => panic!("Unexpected error: {:?}", e),
464                         Ok((chain_tip, blocks_connected)) => {
465                                 assert_eq!(chain_tip, ChainTip::Better(new_tip));
466                                 assert!(blocks_connected);
467                         },
468                 }
469                 assert_eq!(client.chain_tip, new_tip);
470         }
471
472         #[tokio::test]
473         async fn poll_from_chain_with_better_tip_and_without_any_new_blocks() {
474                 let mut chain = Blockchain::default().with_height(3).without_blocks(2..);
475                 let new_tip = chain.tip();
476                 let old_tip = chain.at_height(1);
477
478                 let poller = poll::ChainPoller::new(&mut chain, Network::Testnet);
479                 let mut cache = UnboundedCache::new();
480                 let listener = NullChainListener {};
481                 let mut client = SpvClient::new(old_tip, poller, &mut cache, listener);
482                 match client.poll_best_tip().await {
483                         Err(e) => panic!("Unexpected error: {:?}", e),
484                         Ok((chain_tip, blocks_connected)) => {
485                                 assert_eq!(chain_tip, ChainTip::Better(new_tip));
486                                 assert!(!blocks_connected);
487                         },
488                 }
489                 assert_eq!(client.chain_tip, old_tip);
490         }
491
492         #[tokio::test]
493         async fn poll_from_chain_with_better_tip_and_without_some_new_blocks() {
494                 let mut chain = Blockchain::default().with_height(3).without_blocks(3..);
495                 let new_tip = chain.tip();
496                 let old_tip = chain.at_height(1);
497
498                 let poller = poll::ChainPoller::new(&mut chain, Network::Testnet);
499                 let mut cache = UnboundedCache::new();
500                 let listener = NullChainListener {};
501                 let mut client = SpvClient::new(old_tip, poller, &mut cache, listener);
502                 match client.poll_best_tip().await {
503                         Err(e) => panic!("Unexpected error: {:?}", e),
504                         Ok((chain_tip, blocks_connected)) => {
505                                 assert_eq!(chain_tip, ChainTip::Better(new_tip));
506                                 assert!(blocks_connected);
507                         },
508                 }
509                 assert_eq!(client.chain_tip, chain.at_height(2));
510         }
511
512         #[tokio::test]
513         async fn poll_from_chain_with_worse_tip() {
514                 let mut chain = Blockchain::default().with_height(3);
515                 let best_tip = chain.tip();
516                 chain.disconnect_tip();
517                 let worse_tip = chain.tip();
518
519                 let poller = poll::ChainPoller::new(&mut chain, Network::Testnet);
520                 let mut cache = UnboundedCache::new();
521                 let listener = NullChainListener {};
522                 let mut client = SpvClient::new(best_tip, poller, &mut cache, listener);
523                 match client.poll_best_tip().await {
524                         Err(e) => panic!("Unexpected error: {:?}", e),
525                         Ok((chain_tip, blocks_connected)) => {
526                                 assert_eq!(chain_tip, ChainTip::Worse(worse_tip));
527                                 assert!(!blocks_connected);
528                         },
529                 }
530                 assert_eq!(client.chain_tip, best_tip);
531         }
532 }
533
534 #[cfg(test)]
535 mod chain_notifier_tests {
536         use crate::test_utils::{Blockchain, MockChainListener};
537         use super::*;
538
539         use bitcoin::network::constants::Network;
540
541         #[tokio::test]
542         async fn sync_from_same_chain() {
543                 let mut chain = Blockchain::default().with_height(3);
544
545                 let new_tip = chain.tip();
546                 let old_tip = chain.at_height(1);
547                 let chain_listener = MockChainListener::new()
548                         .expect_block_connected(*chain.at_height(2))
549                         .expect_block_connected(*new_tip);
550                 let mut notifier = ChainNotifier {
551                         header_cache: &mut chain.header_cache(0..=1),
552                         chain_listener,
553                 };
554                 let mut poller = poll::ChainPoller::new(&mut chain, Network::Testnet);
555                 match notifier.sync_listener(new_tip, &old_tip, &mut poller).await {
556                         Err((e, _)) => panic!("Unexpected error: {:?}", e),
557                         Ok(_) => {},
558                 }
559         }
560
561         #[tokio::test]
562         async fn sync_from_different_chains() {
563                 let mut test_chain = Blockchain::with_network(Network::Testnet).with_height(1);
564                 let main_chain = Blockchain::with_network(Network::Bitcoin).with_height(1);
565
566                 let new_tip = test_chain.tip();
567                 let old_tip = main_chain.tip();
568                 let chain_listener = MockChainListener::new();
569                 let mut notifier = ChainNotifier {
570                         header_cache: &mut main_chain.header_cache(0..=1),
571                         chain_listener,
572                 };
573                 let mut poller = poll::ChainPoller::new(&mut test_chain, Network::Testnet);
574                 match notifier.sync_listener(new_tip, &old_tip, &mut poller).await {
575                         Err((e, _)) => {
576                                 assert_eq!(e.kind(), BlockSourceErrorKind::Persistent);
577                                 assert_eq!(e.into_inner().as_ref().to_string(), "genesis block reached");
578                         },
579                         Ok(_) => panic!("Expected error"),
580                 }
581         }
582
583         #[tokio::test]
584         async fn sync_from_equal_length_fork() {
585                 let main_chain = Blockchain::default().with_height(2);
586                 let mut fork_chain = main_chain.fork_at_height(1);
587
588                 let new_tip = fork_chain.tip();
589                 let old_tip = main_chain.tip();
590                 let chain_listener = MockChainListener::new()
591                         .expect_block_disconnected(*old_tip)
592                         .expect_block_connected(*new_tip);
593                 let mut notifier = ChainNotifier {
594                         header_cache: &mut main_chain.header_cache(0..=2),
595                         chain_listener,
596                 };
597                 let mut poller = poll::ChainPoller::new(&mut fork_chain, Network::Testnet);
598                 match notifier.sync_listener(new_tip, &old_tip, &mut poller).await {
599                         Err((e, _)) => panic!("Unexpected error: {:?}", e),
600                         Ok(_) => {},
601                 }
602         }
603
604         #[tokio::test]
605         async fn sync_from_shorter_fork() {
606                 let main_chain = Blockchain::default().with_height(3);
607                 let mut fork_chain = main_chain.fork_at_height(1);
608                 fork_chain.disconnect_tip();
609
610                 let new_tip = fork_chain.tip();
611                 let old_tip = main_chain.tip();
612                 let chain_listener = MockChainListener::new()
613                         .expect_block_disconnected(*old_tip)
614                         .expect_block_disconnected(*main_chain.at_height(2))
615                         .expect_block_connected(*new_tip);
616                 let mut notifier = ChainNotifier {
617                         header_cache: &mut main_chain.header_cache(0..=3),
618                         chain_listener,
619                 };
620                 let mut poller = poll::ChainPoller::new(&mut fork_chain, Network::Testnet);
621                 match notifier.sync_listener(new_tip, &old_tip, &mut poller).await {
622                         Err((e, _)) => panic!("Unexpected error: {:?}", e),
623                         Ok(_) => {},
624                 }
625         }
626
627         #[tokio::test]
628         async fn sync_from_longer_fork() {
629                 let mut main_chain = Blockchain::default().with_height(3);
630                 let mut fork_chain = main_chain.fork_at_height(1);
631                 main_chain.disconnect_tip();
632
633                 let new_tip = fork_chain.tip();
634                 let old_tip = main_chain.tip();
635                 let chain_listener = MockChainListener::new()
636                         .expect_block_disconnected(*old_tip)
637                         .expect_block_connected(*fork_chain.at_height(2))
638                         .expect_block_connected(*new_tip);
639                 let mut notifier = ChainNotifier {
640                         header_cache: &mut main_chain.header_cache(0..=2),
641                         chain_listener,
642                 };
643                 let mut poller = poll::ChainPoller::new(&mut fork_chain, Network::Testnet);
644                 match notifier.sync_listener(new_tip, &old_tip, &mut poller).await {
645                         Err((e, _)) => panic!("Unexpected error: {:?}", e),
646                         Ok(_) => {},
647                 }
648         }
649
650         #[tokio::test]
651         async fn sync_from_chain_without_headers() {
652                 let mut chain = Blockchain::default().with_height(3).without_headers();
653
654                 let new_tip = chain.tip();
655                 let old_tip = chain.at_height(1);
656                 let chain_listener = MockChainListener::new();
657                 let mut notifier = ChainNotifier {
658                         header_cache: &mut chain.header_cache(0..=1),
659                         chain_listener,
660                 };
661                 let mut poller = poll::ChainPoller::new(&mut chain, Network::Testnet);
662                 match notifier.sync_listener(new_tip, &old_tip, &mut poller).await {
663                         Err((_, tip)) => assert_eq!(tip, None),
664                         Ok(_) => panic!("Expected error"),
665                 }
666         }
667
668         #[tokio::test]
669         async fn sync_from_chain_without_any_new_blocks() {
670                 let mut chain = Blockchain::default().with_height(3).without_blocks(2..);
671
672                 let new_tip = chain.tip();
673                 let old_tip = chain.at_height(1);
674                 let chain_listener = MockChainListener::new();
675                 let mut notifier = ChainNotifier {
676                         header_cache: &mut chain.header_cache(0..=3),
677                         chain_listener,
678                 };
679                 let mut poller = poll::ChainPoller::new(&mut chain, Network::Testnet);
680                 match notifier.sync_listener(new_tip, &old_tip, &mut poller).await {
681                         Err((_, tip)) => assert_eq!(tip, Some(old_tip)),
682                         Ok(_) => panic!("Expected error"),
683                 }
684         }
685
686         #[tokio::test]
687         async fn sync_from_chain_without_some_new_blocks() {
688                 let mut chain = Blockchain::default().with_height(3).without_blocks(3..);
689
690                 let new_tip = chain.tip();
691                 let old_tip = chain.at_height(1);
692                 let chain_listener = MockChainListener::new()
693                         .expect_block_connected(*chain.at_height(2));
694                 let mut notifier = ChainNotifier {
695                         header_cache: &mut chain.header_cache(0..=3),
696                         chain_listener,
697                 };
698                 let mut poller = poll::ChainPoller::new(&mut chain, Network::Testnet);
699                 match notifier.sync_listener(new_tip, &old_tip, &mut poller).await {
700                         Err((_, tip)) => assert_eq!(tip, Some(chain.at_height(2))),
701                         Ok(_) => panic!("Expected error"),
702                 }
703         }
704 }