2bb2f4a07df9e4a0aa79759249d795cc81cd833d
[rust-lightning] / lightning-block-sync / src / poll.rs
1 //! Adapters that make one or more [`BlockSource`]s simpler to poll for new chain tip transitions.
2
3 use crate::{AsyncBlockSourceResult, BlockData, BlockHeaderData, BlockSource, BlockSourceError, BlockSourceResult};
4
5 use bitcoin::hash_types::BlockHash;
6 use bitcoin::network::constants::Network;
7
8 use std::ops::Deref;
9
10 /// The `Poll` trait defines behavior for polling block sources for a chain tip and retrieving
11 /// related chain data. It serves as an adapter for `BlockSource`.
12 ///
13 /// [`ChainPoller`] adapts a single `BlockSource`, while any other implementations of `Poll` are
14 /// required to be built in terms of it to ensure chain data validity.
15 ///
16 /// [`ChainPoller`]: ../struct.ChainPoller.html
17 pub trait Poll {
18         /// Returns a chain tip in terms of its relationship to the provided chain tip.
19         fn poll_chain_tip<'a>(&'a self, best_known_chain_tip: ValidatedBlockHeader) ->
20                 AsyncBlockSourceResult<'a, ChainTip>;
21
22         /// Returns the header that preceded the given header in the chain.
23         fn look_up_previous_header<'a>(&'a self, header: &'a ValidatedBlockHeader) ->
24                 AsyncBlockSourceResult<'a, ValidatedBlockHeader>;
25
26         /// Returns the block associated with the given header.
27         fn fetch_block<'a>(&'a self, header: &'a ValidatedBlockHeader) ->
28                 AsyncBlockSourceResult<'a, ValidatedBlock>;
29 }
30
31 /// A chain tip relative to another chain tip in terms of block hash and chainwork.
32 #[derive(Clone, Debug, PartialEq)]
33 pub enum ChainTip {
34         /// A chain tip with the same hash as another chain's tip.
35         Common,
36
37         /// A chain tip with more chainwork than another chain's tip.
38         Better(ValidatedBlockHeader),
39
40         /// A chain tip with less or equal chainwork than another chain's tip. In either case, the
41         /// hashes of each tip will be different.
42         Worse(ValidatedBlockHeader),
43 }
44
45 /// The `Validate` trait defines behavior for validating chain data.
46 ///
47 /// This trait is sealed and not meant to be implemented outside of this crate.
48 pub trait Validate: sealed::Validate {
49         /// The validated data wrapper which can be dereferenced to obtain the validated data.
50         type T: std::ops::Deref<Target = Self>;
51
52         /// Validates the chain data against the given block hash and any criteria needed to ensure that
53         /// it is internally consistent.
54         fn validate(self, block_hash: BlockHash) -> BlockSourceResult<Self::T>;
55 }
56
57 impl Validate for BlockHeaderData {
58         type T = ValidatedBlockHeader;
59
60         fn validate(self, block_hash: BlockHash) -> BlockSourceResult<Self::T> {
61                 let pow_valid_block_hash = self.header
62                         .validate_pow(&self.header.target())
63                         .or_else(|e| Err(BlockSourceError::persistent(e)))?;
64
65                 if pow_valid_block_hash != block_hash {
66                         return Err(BlockSourceError::persistent("invalid block hash"));
67                 }
68
69                 Ok(ValidatedBlockHeader { block_hash, inner: self })
70         }
71 }
72
73 impl Validate for BlockData {
74         type T = ValidatedBlock;
75
76         fn validate(self, block_hash: BlockHash) -> BlockSourceResult<Self::T> {
77                 let header = match &self {
78                         BlockData::FullBlock(block) => &block.header,
79                         BlockData::HeaderOnly(header) => header,
80                 };
81
82                 let pow_valid_block_hash = header
83                         .validate_pow(&header.target())
84                         .or_else(|e| Err(BlockSourceError::persistent(e)))?;
85
86                 if pow_valid_block_hash != block_hash {
87                         return Err(BlockSourceError::persistent("invalid block hash"));
88                 }
89
90                 if let BlockData::FullBlock(block) = &self {
91                         if !block.check_merkle_root() {
92                                 return Err(BlockSourceError::persistent("invalid merkle root"));
93                         }
94
95                         if !block.check_witness_commitment() {
96                                 return Err(BlockSourceError::persistent("invalid witness commitment"));
97                         }
98                 }
99
100                 Ok(ValidatedBlock { block_hash, inner: self })
101         }
102 }
103
104 /// A block header with validated proof of work and corresponding block hash.
105 #[derive(Clone, Copy, Debug, PartialEq)]
106 pub struct ValidatedBlockHeader {
107         pub(crate) block_hash: BlockHash,
108         inner: BlockHeaderData,
109 }
110
111 impl std::ops::Deref for ValidatedBlockHeader {
112         type Target = BlockHeaderData;
113
114         fn deref(&self) -> &Self::Target {
115                 &self.inner
116         }
117 }
118
119 impl ValidatedBlockHeader {
120         /// Checks that the header correctly builds on previous_header: the claimed work differential
121         /// matches the actual PoW and the difficulty transition is possible, i.e., within 4x.
122         fn check_builds_on(&self, previous_header: &ValidatedBlockHeader, network: Network) -> BlockSourceResult<()> {
123                 if self.header.prev_blockhash != previous_header.block_hash {
124                         return Err(BlockSourceError::persistent("invalid previous block hash"));
125                 }
126
127                 if self.height != previous_header.height + 1 {
128                         return Err(BlockSourceError::persistent("invalid block height"));
129                 }
130
131                 let work = self.header.work();
132                 if self.chainwork != previous_header.chainwork + work {
133                         return Err(BlockSourceError::persistent("invalid chainwork"));
134                 }
135
136                 if let Network::Bitcoin = network {
137                         if self.height % 2016 == 0 {
138                                 let previous_work = previous_header.header.work();
139                                 if work > (previous_work << 2) || work < (previous_work >> 2) {
140                                         return Err(BlockSourceError::persistent("invalid difficulty transition"))
141                                 }
142                         } else if self.header.bits != previous_header.header.bits {
143                                 return Err(BlockSourceError::persistent("invalid difficulty"))
144                         }
145                 }
146
147                 Ok(())
148         }
149 }
150
151 /// A block with validated data against its transaction list and corresponding block hash.
152 pub struct ValidatedBlock {
153         pub(crate) block_hash: BlockHash,
154         inner: BlockData,
155 }
156
157 impl std::ops::Deref for ValidatedBlock {
158         type Target = BlockData;
159
160         fn deref(&self) -> &Self::Target {
161                 &self.inner
162         }
163 }
164
165 mod sealed {
166         /// Used to prevent implementing [`super::Validate`] outside the crate but still allow its use.
167         pub trait Validate {}
168
169         impl Validate for crate::BlockHeaderData {}
170         impl Validate for crate::BlockData {}
171 }
172
173 /// The canonical `Poll` implementation used for a single `BlockSource`.
174 ///
175 /// Other `Poll` implementations should be built using `ChainPoller` as it provides the simplest way
176 /// of validating chain data and checking consistency.
177 pub struct ChainPoller<B: Deref<Target=T> + Sized + Send + Sync, T: BlockSource + ?Sized> {
178         block_source: B,
179         network: Network,
180 }
181
182 impl<B: Deref<Target=T> + Sized + Send + Sync, T: BlockSource + ?Sized> ChainPoller<B, T> {
183         /// Creates a new poller for the given block source.
184         ///
185         /// If the `network` parameter is mainnet, then the difficulty between blocks is checked for
186         /// validity.
187         pub fn new(block_source: B, network: Network) -> Self {
188                 Self { block_source, network }
189         }
190 }
191
192 impl<B: Deref<Target=T> + Sized + Send + Sync, T: BlockSource + ?Sized> Poll for ChainPoller<B, T> {
193         fn poll_chain_tip<'a>(&'a self, best_known_chain_tip: ValidatedBlockHeader) ->
194                 AsyncBlockSourceResult<'a, ChainTip>
195         {
196                 Box::pin(async move {
197                         let (block_hash, height) = self.block_source.get_best_block().await?;
198                         if block_hash == best_known_chain_tip.header.block_hash() {
199                                 return Ok(ChainTip::Common);
200                         }
201
202                         let chain_tip = self.block_source
203                                 .get_header(&block_hash, height).await?
204                                 .validate(block_hash)?;
205                         if chain_tip.chainwork > best_known_chain_tip.chainwork {
206                                 Ok(ChainTip::Better(chain_tip))
207                         } else {
208                                 Ok(ChainTip::Worse(chain_tip))
209                         }
210                 })
211         }
212
213         fn look_up_previous_header<'a>(&'a self, header: &'a ValidatedBlockHeader) ->
214                 AsyncBlockSourceResult<'a, ValidatedBlockHeader>
215         {
216                 Box::pin(async move {
217                         if header.height == 0 {
218                                 return Err(BlockSourceError::persistent("genesis block reached"));
219                         }
220
221                         let previous_hash = &header.header.prev_blockhash;
222                         let height = header.height - 1;
223                         let previous_header = self.block_source
224                                 .get_header(previous_hash, Some(height)).await?
225                                 .validate(*previous_hash)?;
226                         header.check_builds_on(&previous_header, self.network)?;
227
228                         Ok(previous_header)
229                 })
230         }
231
232         fn fetch_block<'a>(&'a self, header: &'a ValidatedBlockHeader) ->
233                 AsyncBlockSourceResult<'a, ValidatedBlock>
234         {
235                 Box::pin(async move {
236                         self.block_source
237                                 .get_block(&header.block_hash).await?
238                                 .validate(header.block_hash)
239                 })
240         }
241 }
242
243 #[cfg(test)]
244 mod tests {
245         use crate::*;
246         use crate::test_utils::Blockchain;
247         use super::*;
248         use bitcoin::util::uint::Uint256;
249
250         #[tokio::test]
251         async fn poll_empty_chain() {
252                 let mut chain = Blockchain::default().with_height(0);
253                 let best_known_chain_tip = chain.tip();
254                 chain.disconnect_tip();
255
256                 let poller = ChainPoller::new(&chain, Network::Bitcoin);
257                 match poller.poll_chain_tip(best_known_chain_tip).await {
258                         Err(e) => {
259                                 assert_eq!(e.kind(), BlockSourceErrorKind::Transient);
260                                 assert_eq!(e.into_inner().as_ref().to_string(), "empty chain");
261                         },
262                         Ok(_) => panic!("Expected error"),
263                 }
264         }
265
266         #[tokio::test]
267         async fn poll_chain_without_headers() {
268                 let chain = Blockchain::default().with_height(1).without_headers();
269                 let best_known_chain_tip = chain.at_height(0);
270
271                 let poller = ChainPoller::new(&chain, Network::Bitcoin);
272                 match poller.poll_chain_tip(best_known_chain_tip).await {
273                         Err(e) => {
274                                 assert_eq!(e.kind(), BlockSourceErrorKind::Persistent);
275                                 assert_eq!(e.into_inner().as_ref().to_string(), "header not found");
276                         },
277                         Ok(_) => panic!("Expected error"),
278                 }
279         }
280
281         #[tokio::test]
282         async fn poll_chain_with_invalid_pow() {
283                 let mut chain = Blockchain::default().with_height(1);
284                 let best_known_chain_tip = chain.at_height(0);
285
286                 // Invalidate the tip by changing its target.
287                 chain.blocks.last_mut().unwrap().header.bits =
288                         BlockHeader::compact_target_from_u256(&Uint256::from_be_bytes([0; 32]));
289
290                 let poller = ChainPoller::new(&chain, Network::Bitcoin);
291                 match poller.poll_chain_tip(best_known_chain_tip).await {
292                         Err(e) => {
293                                 assert_eq!(e.kind(), BlockSourceErrorKind::Persistent);
294                                 assert_eq!(e.into_inner().as_ref().to_string(), "block target correct but not attained");
295                         },
296                         Ok(_) => panic!("Expected error"),
297                 }
298         }
299
300         #[tokio::test]
301         async fn poll_chain_with_malformed_headers() {
302                 let chain = Blockchain::default().with_height(1).malformed_headers();
303                 let best_known_chain_tip = chain.at_height(0);
304
305                 let poller = ChainPoller::new(&chain, Network::Bitcoin);
306                 match poller.poll_chain_tip(best_known_chain_tip).await {
307                         Err(e) => {
308                                 assert_eq!(e.kind(), BlockSourceErrorKind::Persistent);
309                                 assert_eq!(e.into_inner().as_ref().to_string(), "invalid block hash");
310                         },
311                         Ok(_) => panic!("Expected error"),
312                 }
313         }
314
315         #[tokio::test]
316         async fn poll_chain_with_common_tip() {
317                 let chain = Blockchain::default().with_height(0);
318                 let best_known_chain_tip = chain.tip();
319
320                 let poller = ChainPoller::new(&chain, Network::Bitcoin);
321                 match poller.poll_chain_tip(best_known_chain_tip).await {
322                         Err(e) => panic!("Unexpected error: {:?}", e),
323                         Ok(tip) => assert_eq!(tip, ChainTip::Common),
324                 }
325         }
326
327         #[tokio::test]
328         async fn poll_chain_with_uncommon_tip_but_equal_chainwork() {
329                 let mut chain = Blockchain::default().with_height(1);
330                 let best_known_chain_tip = chain.tip();
331
332                 // Change the nonce to get a different block hash with the same chainwork.
333                 chain.blocks.last_mut().unwrap().header.nonce += 1;
334                 let worse_chain_tip = chain.tip();
335                 assert_eq!(best_known_chain_tip.chainwork, worse_chain_tip.chainwork);
336
337                 let poller = ChainPoller::new(&chain, Network::Bitcoin);
338                 match poller.poll_chain_tip(best_known_chain_tip).await {
339                         Err(e) => panic!("Unexpected error: {:?}", e),
340                         Ok(tip) => assert_eq!(tip, ChainTip::Worse(worse_chain_tip)),
341                 }
342         }
343
344         #[tokio::test]
345         async fn poll_chain_with_worse_tip() {
346                 let mut chain = Blockchain::default().with_height(1);
347                 let best_known_chain_tip = chain.tip();
348
349                 chain.disconnect_tip();
350                 let worse_chain_tip = chain.tip();
351
352                 let poller = ChainPoller::new(&chain, Network::Bitcoin);
353                 match poller.poll_chain_tip(best_known_chain_tip).await {
354                         Err(e) => panic!("Unexpected error: {:?}", e),
355                         Ok(tip) => assert_eq!(tip, ChainTip::Worse(worse_chain_tip)),
356                 }
357         }
358
359         #[tokio::test]
360         async fn poll_chain_with_better_tip() {
361                 let chain = Blockchain::default().with_height(1);
362                 let best_known_chain_tip = chain.at_height(0);
363
364                 let better_chain_tip = chain.tip();
365
366                 let poller = ChainPoller::new(&chain, Network::Bitcoin);
367                 match poller.poll_chain_tip(best_known_chain_tip).await {
368                         Err(e) => panic!("Unexpected error: {:?}", e),
369                         Ok(tip) => assert_eq!(tip, ChainTip::Better(better_chain_tip)),
370                 }
371         }
372 }