1use crate::{
4 AsyncBlockSourceResult, BlockData, BlockHeaderData, BlockSource, BlockSourceError,
5 BlockSourceResult,
6};
7
8use bitcoin::hash_types::BlockHash;
9use bitcoin::network::Network;
10use lightning::chain::BestBlock;
11
12use std::ops::Deref;
13
14pub trait Poll {
22 fn poll_chain_tip<'a>(
24 &'a self, best_known_chain_tip: ValidatedBlockHeader,
25 ) -> AsyncBlockSourceResult<'a, ChainTip>;
26
27 fn look_up_previous_header<'a>(
29 &'a self, header: &'a ValidatedBlockHeader,
30 ) -> AsyncBlockSourceResult<'a, ValidatedBlockHeader>;
31
32 fn fetch_block<'a>(
34 &'a self, header: &'a ValidatedBlockHeader,
35 ) -> AsyncBlockSourceResult<'a, ValidatedBlock>;
36}
37
38#[derive(Clone, Debug, PartialEq, Eq)]
40pub enum ChainTip {
41 Common,
43
44 Better(ValidatedBlockHeader),
46
47 Worse(ValidatedBlockHeader),
50}
51
52pub trait Validate: sealed::Validate {
56 type T: std::ops::Deref<Target = Self>;
58
59 fn validate(self, block_hash: BlockHash) -> BlockSourceResult<Self::T>;
62}
63
64impl Validate for BlockHeaderData {
65 type T = ValidatedBlockHeader;
66
67 fn validate(self, block_hash: BlockHash) -> BlockSourceResult<Self::T> {
68 let pow_valid_block_hash =
69 self.header.validate_pow(self.header.target()).map_err(BlockSourceError::persistent)?;
70
71 if pow_valid_block_hash != block_hash {
72 return Err(BlockSourceError::persistent("invalid block hash"));
73 }
74
75 Ok(ValidatedBlockHeader { block_hash, inner: self })
76 }
77}
78
79impl Validate for BlockData {
80 type T = ValidatedBlock;
81
82 fn validate(self, block_hash: BlockHash) -> BlockSourceResult<Self::T> {
83 let header = match &self {
84 BlockData::FullBlock(block) => &block.header,
85 BlockData::HeaderOnly(header) => header,
86 };
87
88 let pow_valid_block_hash =
89 header.validate_pow(header.target()).map_err(BlockSourceError::persistent)?;
90
91 if pow_valid_block_hash != block_hash {
92 return Err(BlockSourceError::persistent("invalid block hash"));
93 }
94
95 if let BlockData::FullBlock(block) = &self {
96 if !block.check_merkle_root() {
97 return Err(BlockSourceError::persistent("invalid merkle root"));
98 }
99
100 if !block.check_witness_commitment() {
101 return Err(BlockSourceError::persistent("invalid witness commitment"));
102 }
103 }
104
105 Ok(ValidatedBlock { block_hash, inner: self })
106 }
107}
108
109#[derive(Clone, Copy, Debug, PartialEq, Eq)]
111pub struct ValidatedBlockHeader {
112 pub(crate) block_hash: BlockHash,
113 inner: BlockHeaderData,
114}
115
116impl std::ops::Deref for ValidatedBlockHeader {
117 type Target = BlockHeaderData;
118
119 fn deref(&self) -> &Self::Target {
120 &self.inner
121 }
122}
123
124impl ValidatedBlockHeader {
125 fn check_builds_on(
128 &self, previous_header: &ValidatedBlockHeader, network: Network,
129 ) -> BlockSourceResult<()> {
130 if self.header.prev_blockhash != previous_header.block_hash {
131 return Err(BlockSourceError::persistent("invalid previous block hash"));
132 }
133
134 if self.height != previous_header.height + 1 {
135 return Err(BlockSourceError::persistent("invalid block height"));
136 }
137
138 let work = self.header.work();
139 if self.chainwork != previous_header.chainwork + work {
140 return Err(BlockSourceError::persistent("invalid chainwork"));
141 }
142
143 if let Network::Bitcoin = network {
144 if self.height % 2016 == 0 {
145 let target = self.header.target();
146 let previous_target = previous_header.header.target();
147 let min_target = previous_target.min_transition_threshold();
148 let max_target = previous_target.max_transition_threshold_unchecked();
149 if target > max_target || target < min_target {
150 return Err(BlockSourceError::persistent("invalid difficulty transition"));
151 }
152 } else if self.header.bits != previous_header.header.bits {
153 return Err(BlockSourceError::persistent("invalid difficulty"));
154 }
155 }
156
157 Ok(())
158 }
159
160 pub fn to_best_block(&self) -> BestBlock {
170 BestBlock::new(self.block_hash, self.inner.height)
171 }
172}
173
174pub struct ValidatedBlock {
176 pub(crate) block_hash: BlockHash,
177 inner: BlockData,
178}
179
180impl std::ops::Deref for ValidatedBlock {
181 type Target = BlockData;
182
183 fn deref(&self) -> &Self::Target {
184 &self.inner
185 }
186}
187
188mod sealed {
189 pub trait Validate {}
191
192 impl Validate for crate::BlockHeaderData {}
193 impl Validate for crate::BlockData {}
194}
195
196pub struct ChainPoller<B: Deref<Target = T> + Sized + Send + Sync, T: BlockSource + ?Sized> {
201 block_source: B,
202 network: Network,
203}
204
205impl<B: Deref<Target = T> + Sized + Send + Sync, T: BlockSource + ?Sized> ChainPoller<B, T> {
206 pub fn new(block_source: B, network: Network) -> Self {
211 Self { block_source, network }
212 }
213}
214
215impl<B: Deref<Target = T> + Sized + Send + Sync, T: BlockSource + ?Sized> Poll
216 for ChainPoller<B, T>
217{
218 fn poll_chain_tip<'a>(
219 &'a self, best_known_chain_tip: ValidatedBlockHeader,
220 ) -> AsyncBlockSourceResult<'a, ChainTip> {
221 Box::pin(async move {
222 let (block_hash, height) = self.block_source.get_best_block().await?;
223 if block_hash == best_known_chain_tip.header.block_hash() {
224 return Ok(ChainTip::Common);
225 }
226
227 let chain_tip =
228 self.block_source.get_header(&block_hash, height).await?.validate(block_hash)?;
229 if chain_tip.chainwork > best_known_chain_tip.chainwork {
230 Ok(ChainTip::Better(chain_tip))
231 } else {
232 Ok(ChainTip::Worse(chain_tip))
233 }
234 })
235 }
236
237 fn look_up_previous_header<'a>(
238 &'a self, header: &'a ValidatedBlockHeader,
239 ) -> AsyncBlockSourceResult<'a, ValidatedBlockHeader> {
240 Box::pin(async move {
241 if header.height == 0 {
242 return Err(BlockSourceError::persistent("genesis block reached"));
243 }
244
245 let previous_hash = &header.header.prev_blockhash;
246 let height = header.height - 1;
247 let previous_header = self
248 .block_source
249 .get_header(previous_hash, Some(height))
250 .await?
251 .validate(*previous_hash)?;
252 header.check_builds_on(&previous_header, self.network)?;
253
254 Ok(previous_header)
255 })
256 }
257
258 fn fetch_block<'a>(
259 &'a self, header: &'a ValidatedBlockHeader,
260 ) -> AsyncBlockSourceResult<'a, ValidatedBlock> {
261 Box::pin(async move {
262 self.block_source.get_block(&header.block_hash).await?.validate(header.block_hash)
263 })
264 }
265}
266
267#[cfg(test)]
268mod tests {
269 use super::*;
270 use crate::test_utils::Blockchain;
271 use crate::*;
272
273 #[tokio::test]
274 async fn poll_empty_chain() {
275 let mut chain = Blockchain::default().with_height(0);
276 let best_known_chain_tip = chain.tip();
277 chain.disconnect_tip();
278
279 let poller = ChainPoller::new(&chain, Network::Bitcoin);
280 match poller.poll_chain_tip(best_known_chain_tip).await {
281 Err(e) => {
282 assert_eq!(e.kind(), BlockSourceErrorKind::Transient);
283 assert_eq!(e.into_inner().as_ref().to_string(), "empty chain");
284 },
285 Ok(_) => panic!("Expected error"),
286 }
287 }
288
289 #[tokio::test]
290 async fn poll_chain_without_headers() {
291 let chain = Blockchain::default().with_height(1).without_headers();
292 let best_known_chain_tip = chain.at_height(0);
293
294 let poller = ChainPoller::new(&chain, Network::Bitcoin);
295 match poller.poll_chain_tip(best_known_chain_tip).await {
296 Err(e) => {
297 assert_eq!(e.kind(), BlockSourceErrorKind::Persistent);
298 assert_eq!(e.into_inner().as_ref().to_string(), "header not found");
299 },
300 Ok(_) => panic!("Expected error"),
301 }
302 }
303
304 #[tokio::test]
305 async fn poll_chain_with_invalid_pow() {
306 let mut chain = Blockchain::default().with_height(1);
307 let best_known_chain_tip = chain.at_height(0);
308
309 chain.blocks.last_mut().unwrap().header.bits =
311 bitcoin::Target::from_be_bytes([0x01; 32]).to_compact_lossy();
312
313 let poller = ChainPoller::new(&chain, Network::Bitcoin);
314 match poller.poll_chain_tip(best_known_chain_tip).await {
315 Err(e) => {
316 assert_eq!(e.kind(), BlockSourceErrorKind::Persistent);
317 assert_eq!(
318 e.into_inner().as_ref().to_string(),
319 "block target correct but not attained"
320 );
321 },
322 Ok(_) => panic!("Expected error"),
323 }
324 }
325
326 #[tokio::test]
327 async fn poll_chain_with_malformed_headers() {
328 let chain = Blockchain::default().with_height(1).malformed_headers();
329 let best_known_chain_tip = chain.at_height(0);
330
331 let poller = ChainPoller::new(&chain, Network::Bitcoin);
332 match poller.poll_chain_tip(best_known_chain_tip).await {
333 Err(e) => {
334 assert_eq!(e.kind(), BlockSourceErrorKind::Persistent);
335 assert_eq!(e.into_inner().as_ref().to_string(), "invalid block hash");
336 },
337 Ok(_) => panic!("Expected error"),
338 }
339 }
340
341 #[tokio::test]
342 async fn poll_chain_with_common_tip() {
343 let chain = Blockchain::default().with_height(0);
344 let best_known_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::Common),
350 }
351 }
352
353 #[tokio::test]
354 async fn poll_chain_with_uncommon_tip_but_equal_chainwork() {
355 let mut chain = Blockchain::default().with_height(1);
356 let best_known_chain_tip = chain.tip();
357
358 chain.blocks.last_mut().unwrap().header.nonce += 1;
360 let worse_chain_tip = chain.tip();
361 assert_eq!(best_known_chain_tip.chainwork, worse_chain_tip.chainwork);
362
363 let poller = ChainPoller::new(&chain, Network::Bitcoin);
364 match poller.poll_chain_tip(best_known_chain_tip).await {
365 Err(e) => panic!("Unexpected error: {:?}", e),
366 Ok(tip) => assert_eq!(tip, ChainTip::Worse(worse_chain_tip)),
367 }
368 }
369
370 #[tokio::test]
371 async fn poll_chain_with_worse_tip() {
372 let mut chain = Blockchain::default().with_height(1);
373 let best_known_chain_tip = chain.tip();
374
375 chain.disconnect_tip();
376 let worse_chain_tip = chain.tip();
377
378 let poller = ChainPoller::new(&chain, Network::Bitcoin);
379 match poller.poll_chain_tip(best_known_chain_tip).await {
380 Err(e) => panic!("Unexpected error: {:?}", e),
381 Ok(tip) => assert_eq!(tip, ChainTip::Worse(worse_chain_tip)),
382 }
383 }
384
385 #[tokio::test]
386 async fn poll_chain_with_better_tip() {
387 let chain = Blockchain::default().with_height(1);
388 let best_known_chain_tip = chain.at_height(0);
389
390 let better_chain_tip = chain.tip();
391
392 let poller = ChainPoller::new(&chain, Network::Bitcoin);
393 match poller.poll_chain_tip(best_known_chain_tip).await {
394 Err(e) => panic!("Unexpected error: {:?}", e),
395 Ok(tip) => assert_eq!(tip, ChainTip::Better(better_chain_tip)),
396 }
397 }
398}