sc_consensus_nakamoto/verification/
header_verify.rs1use crate::chain_params::ChainParams;
2use bitcoin::Target;
3use bitcoin::blockdata::block::{Header as BitcoinHeader, ValidationError};
4use bitcoin::consensus::Params;
5use bitcoin::pow::U256;
6use sc_client_api::AuxStore;
7use sp_blockchain::HeaderBackend;
8use sp_runtime::traits::Block as BlockT;
9use std::marker::PhantomData;
10use std::sync::Arc;
11use std::time::{SystemTime, UNIX_EPOCH};
12use subcoin_primitives::{BackendExt, ClientExt};
13
14const MAX_FUTURE_BLOCK_TIME: u32 = 2 * 60 * 60;
16
17#[derive(Debug, thiserror::Error)]
19pub enum Error {
20 #[error("Incorrect proof-of-work: {{ got: {got:?}, expected: {expected:?} }}")]
22 BadDifficultyBits { got: Target, expected: Target },
23 #[error("proof-of-work validation failed: {0:?}")]
25 InvalidProofOfWork(ValidationError),
26 #[error("Block time is too far in the future")]
28 TooFarInFuture,
29 #[error("Time is the median time of last 11 blocks or before")]
31 TimeTooOld,
32 #[error("Outdated version")]
33 BadVersion,
34 #[error(transparent)]
36 Client(#[from] sp_blockchain::Error),
37}
38
39pub struct HeaderVerifier<Block, Client> {
41 client: Arc<Client>,
42 chain_params: ChainParams,
43 _phantom: PhantomData<Block>,
44}
45
46impl<Block, Client> Clone for HeaderVerifier<Block, Client> {
47 fn clone(&self) -> Self {
48 Self {
49 client: self.client.clone(),
50 chain_params: self.chain_params.clone(),
51 _phantom: self._phantom,
52 }
53 }
54}
55
56impl<Block, Client> HeaderVerifier<Block, Client> {
57 pub fn new(client: Arc<Client>, chain_params: ChainParams) -> Self {
59 Self {
60 client,
61 chain_params,
62 _phantom: Default::default(),
63 }
64 }
65}
66
67impl<Block, Client> HeaderVerifier<Block, Client>
68where
69 Block: BlockT,
70 Client: HeaderBackend<Block> + AuxStore,
71{
72 pub fn verify(&self, header: &BitcoinHeader) -> Result<u32, Error> {
83 let prev_block_hash = header.prev_blockhash;
84
85 let prev_block_header = self.client.block_header(prev_block_hash).ok_or(
86 sp_blockchain::Error::MissingHeader(prev_block_hash.to_string()),
87 )?;
88
89 let prev_block_height = self
90 .client
91 .block_number(prev_block_hash)
92 .expect("Prev block must exist as we checked before; qed");
93
94 let expected_target = get_next_work_required(
95 prev_block_height,
96 prev_block_header,
97 &self.chain_params.params,
98 &self.client,
99 );
100 let expected_bits = expected_target.to_compact_lossy().to_consensus();
101
102 let actual_target = header.target();
103 let actual_bits = actual_target.to_compact_lossy().to_consensus();
104
105 if actual_bits != expected_bits {
106 return Err(Error::BadDifficultyBits {
107 got: actual_target,
108 expected: expected_target,
109 });
110 }
111
112 header
113 .validate_pow(actual_target)
114 .map_err(Error::InvalidProofOfWork)?;
115
116 let current_time = SystemTime::now()
118 .duration_since(UNIX_EPOCH)
119 .expect("Time went backwards")
120 .as_secs() as u32;
121
122 if header.time > current_time + MAX_FUTURE_BLOCK_TIME {
123 return Err(Error::TooFarInFuture);
124 }
125
126 let block_number = prev_block_height + 1;
127
128 let version = header.version.to_consensus();
129
130 if version < 2 && block_number >= self.chain_params.params.bip34_height
131 || version < 3 && block_number >= self.chain_params.params.bip66_height
132 || version < 4 && block_number >= self.chain_params.params.bip65_height
133 {
134 return Err(Error::BadVersion);
135 }
136
137 let lock_time_cutoff = if block_number >= self.chain_params.csv_height {
139 let block_hash = header.block_hash();
140 let mtp = self
141 .client
142 .calculate_median_time_past(block_hash)
143 .expect("Block must exist; qed") as u32;
144 if header.time <= mtp {
145 return Err(Error::TimeTooOld);
146 }
147 mtp
148 } else {
149 header.time
150 };
151
152 Ok(lock_time_cutoff)
153 }
154
155 pub fn has_valid_proof_of_work(&self, header: &BitcoinHeader) -> bool {
157 let target = header.target();
158
159 if target == Target::ZERO
160 || target > Target::MAX
161 || target > self.chain_params.params.max_attainable_target
162 {
163 return false;
164 }
165
166 header.validate_pow(target).is_ok()
167 }
168}
169
170fn get_next_work_required<Block, Client>(
175 last_block_height: u32,
176 last_block: BitcoinHeader,
177 params: &Params,
178 client: &Arc<Client>,
179) -> Target
180where
181 Block: BlockT,
182 Client: HeaderBackend<Block> + AuxStore,
183{
184 if params.no_pow_retargeting {
185 return last_block.target();
186 }
187
188 let height = last_block_height + 1;
189
190 let difficulty_adjustment_interval = params.difficulty_adjustment_interval() as u32;
191
192 if height >= difficulty_adjustment_interval && height % difficulty_adjustment_interval == 0 {
194 let last_retarget_height = height - difficulty_adjustment_interval;
195
196 let retarget_header_hash = client
197 .block_hash(last_retarget_height)
198 .expect("Retarget block must be available; qed");
199
200 let retarget_header = client
201 .block_header(retarget_header_hash)
202 .expect("Retarget block must be available; qed");
203
204 let first_block_time = retarget_header.time;
205
206 let last_block_time = last_block.time;
208
209 calculate_next_work_required(
210 last_block.target().0,
211 first_block_time.into(),
212 last_block_time.into(),
213 params,
214 )
215 } else {
216 last_block.target()
217 }
218}
219
220fn calculate_next_work_required(
222 previous_target: U256,
223 first_block_time: u64,
224 last_block_time: u64,
225 params: &Params,
226) -> Target {
227 let mut actual_timespan = last_block_time.saturating_sub(first_block_time);
228
229 let pow_target_timespan = params.pow_target_timespan;
230
231 if actual_timespan < pow_target_timespan / 4 {
235 actual_timespan = pow_target_timespan / 4;
236 }
237
238 if actual_timespan > pow_target_timespan * 4 {
239 actual_timespan = pow_target_timespan * 4;
240 }
241
242 let pow_limit = params.max_attainable_target;
243
244 let target = previous_target * actual_timespan.into();
246 let target = Target(target / pow_target_timespan.into());
247
248 if target > pow_limit {
249 pow_limit
250 } else {
251 target
252 }
253}
254
255#[cfg(test)]
256mod tests {
257 use super::*;
258 use bitcoin::consensus::encode::deserialize_hex;
259
260 #[test]
261 fn test_calculate_next_work_required() {
262 let block_354816: BitcoinHeader = deserialize_hex
264 ("020000003f99814a36d2a2043b1d4bf61a410f71828eca1decbf56000000000000000000b3762ed278ac44bb953e24262cfeb952d0abe6d3b7f8b74fd24e009b96b6cb965d674655dd1317186436e79d").unwrap();
265
266 let expected_target = block_354816.target();
267
268 let first_block: BitcoinHeader = deserialize_hex("0200000074c51c1cc53aaf478c643bb612da6bd17b268cd9bdccc4000000000000000000ccc0a2618a1f973dfac37827435b463abd18cbfd0f280a90432d3d78497a36cc02f33355f0171718b72a1dc7").unwrap();
270
271 let last_block: BitcoinHeader = deserialize_hex("030000004c9c1b59250f30b8d360886a5433501120b056a000bdc0160000000000000000caca1bf0c55a5ba2299f9e60d10c01c679bb266c7df815ff776a1b97fd3a199ac1644655f01717182707bd59").unwrap();
273
274 let new_target = calculate_next_work_required(
275 last_block.target().0,
276 first_block.time as u64,
277 last_block.time as u64,
278 &Params::new(bitcoin::Network::Bitcoin),
279 );
280
281 assert_eq!(
282 new_target.to_compact_lossy().to_consensus(),
283 expected_target.to_compact_lossy().to_consensus(),
284 "Difficulty bits must match"
285 );
286 }
287}