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