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