Skip to content

Commit 7d6fce7

Browse files
committed
Define a Poll trait as an adaptor on BlockSource
SPV clients need to poll one or more block sources for the best chain tip and to retrieve related chain data. The Poll trait serves as an adaptor interface for BlockSource. Implementations may define an appropriate polling strategy.
1 parent 94bb0c9 commit 7d6fce7

File tree

2 files changed

+150
-0
lines changed

2 files changed

+150
-0
lines changed

lightning-block-sync/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
#[cfg(any(feature = "rest-client", feature = "rpc-client"))]
1515
pub mod http;
1616

17+
pub mod poll;
18+
1719
#[cfg(feature = "rest-client")]
1820
pub mod rest;
1921

lightning-block-sync/src/poll.rs

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
use crate::{AsyncBlockSourceResult, BlockHeaderData, BlockSourceError, BlockSourceResult};
2+
3+
use bitcoin::blockdata::block::Block;
4+
use bitcoin::hash_types::BlockHash;
5+
use bitcoin::network::constants::Network;
6+
7+
/// The `Poll` trait defines behavior for polling block sources for a chain tip and retrieving
8+
/// related chain data. It serves as an adapter for `BlockSource`.
9+
pub trait Poll {
10+
/// Returns a chain tip in terms of its relationship to the provided chain tip.
11+
fn poll_chain_tip<'a>(&'a mut self, best_known_chain_tip: ValidatedBlockHeader) ->
12+
AsyncBlockSourceResult<'a, ChainTip>;
13+
14+
/// Returns the header that preceded the given header in the chain.
15+
fn look_up_previous_header<'a>(&'a mut self, header: &'a ValidatedBlockHeader) ->
16+
AsyncBlockSourceResult<'a, ValidatedBlockHeader>;
17+
18+
/// Returns the block associated with the given header.
19+
fn fetch_block<'a>(&'a mut self, header: &'a ValidatedBlockHeader) ->
20+
AsyncBlockSourceResult<'a, ValidatedBlock>;
21+
}
22+
23+
/// A chain tip relative to another chain tip in terms of block hash and chainwork.
24+
#[derive(Clone, Debug, PartialEq)]
25+
pub enum ChainTip {
26+
/// A chain tip with the same hash as another chain's tip.
27+
Common,
28+
29+
/// A chain tip with more chainwork than another chain's tip.
30+
Better(ValidatedBlockHeader),
31+
32+
/// A chain tip with less or equal chainwork than another chain's tip. In either case, the
33+
/// hashes of each tip will be different.
34+
Worse(ValidatedBlockHeader),
35+
}
36+
37+
/// The `Validate` trait defines behavior for validating chain data.
38+
pub(crate) trait Validate {
39+
/// The validated data wrapper which can be dereferenced to obtain the validated data.
40+
type T: std::ops::Deref<Target = Self>;
41+
42+
/// Validates the chain data against the given block hash and any criteria needed to ensure that
43+
/// it is internally consistent.
44+
fn validate(self, block_hash: BlockHash) -> BlockSourceResult<Self::T>;
45+
}
46+
47+
impl Validate for BlockHeaderData {
48+
type T = ValidatedBlockHeader;
49+
50+
fn validate(self, block_hash: BlockHash) -> BlockSourceResult<Self::T> {
51+
self.header
52+
.validate_pow(&self.header.target())
53+
.or_else(|e| Err(BlockSourceError::persistent(e)))?;
54+
55+
// TODO: Use the result of validate_pow instead of recomputing the block hash once upstream.
56+
if self.header.block_hash() != block_hash {
57+
return Err(BlockSourceError::persistent("invalid block hash"));
58+
}
59+
60+
Ok(ValidatedBlockHeader { block_hash, inner: self })
61+
}
62+
}
63+
64+
impl Validate for Block {
65+
type T = ValidatedBlock;
66+
67+
fn validate(self, block_hash: BlockHash) -> BlockSourceResult<Self::T> {
68+
self.header
69+
.validate_pow(&self.header.target())
70+
.or_else(|e| Err(BlockSourceError::persistent(e)))?;
71+
72+
// TODO: Use the result of validate_pow instead of recomputing the block hash once upstream.
73+
if self.block_hash() != block_hash {
74+
return Err(BlockSourceError::persistent("invalid block hash"));
75+
}
76+
77+
if !self.check_merkle_root() {
78+
return Err(BlockSourceError::persistent("invalid merkle root"));
79+
}
80+
81+
if !self.check_witness_commitment() {
82+
return Err(BlockSourceError::persistent("invalid witness commitment"));
83+
}
84+
85+
Ok(ValidatedBlock { block_hash, inner: self })
86+
}
87+
}
88+
89+
/// A block header with validated proof of work and corresponding block hash.
90+
#[derive(Clone, Copy, Debug, PartialEq)]
91+
pub struct ValidatedBlockHeader {
92+
block_hash: BlockHash,
93+
inner: BlockHeaderData,
94+
}
95+
96+
impl std::ops::Deref for ValidatedBlockHeader {
97+
type Target = BlockHeaderData;
98+
99+
fn deref(&self) -> &Self::Target {
100+
&self.inner
101+
}
102+
}
103+
104+
impl ValidatedBlockHeader {
105+
/// Checks that the header correctly builds on previous_header: the claimed work differential
106+
/// matches the actual PoW and the difficulty transition is possible, i.e., within 4x.
107+
fn check_builds_on(&self, previous_header: &ValidatedBlockHeader, network: Network) -> BlockSourceResult<()> {
108+
if self.header.prev_blockhash != previous_header.block_hash {
109+
return Err(BlockSourceError::persistent("invalid previous block hash"));
110+
}
111+
112+
if self.height != previous_header.height + 1 {
113+
return Err(BlockSourceError::persistent("invalid block height"));
114+
}
115+
116+
let work = self.header.work();
117+
if self.chainwork != previous_header.chainwork + work {
118+
return Err(BlockSourceError::persistent("invalid chainwork"));
119+
}
120+
121+
if let Network::Bitcoin = network {
122+
if self.height % 2016 == 0 {
123+
let previous_work = previous_header.header.work();
124+
if work > (previous_work << 2) || work < (previous_work >> 2) {
125+
return Err(BlockSourceError::persistent("invalid difficulty transition"))
126+
}
127+
} else if self.header.bits != previous_header.header.bits {
128+
return Err(BlockSourceError::persistent("invalid difficulty"))
129+
}
130+
}
131+
132+
Ok(())
133+
}
134+
}
135+
136+
/// A block with validated data against its transaction list and corresponding block hash.
137+
pub struct ValidatedBlock {
138+
block_hash: BlockHash,
139+
inner: Block,
140+
}
141+
142+
impl std::ops::Deref for ValidatedBlock {
143+
type Target = Block;
144+
145+
fn deref(&self) -> &Self::Target {
146+
&self.inner
147+
}
148+
}

0 commit comments

Comments
 (0)