1use crate::{BlockVerification, BlockVerificationError, GenericBody, GenericHeader};
2use ab_client_api::{BlockOrigin, ChainInfo, ChainSyncStatus};
3use ab_client_archiving::segment_headers_store::SegmentHeadersStore;
4use ab_client_consensus_common::ConsensusConstants;
5use ab_client_consensus_common::consensus_parameters::{
6 DeriveConsensusParametersError, derive_consensus_parameters,
7};
8use ab_client_proof_of_time::PotNextSlotInput;
9use ab_client_proof_of_time::verifier::PotVerifier;
10use ab_core_primitives::block::body::{BeaconChainBody, IntermediateShardBlocksInfo};
11use ab_core_primitives::block::header::{
12 BeaconChainHeader, BlockHeaderConsensusParameters, BlockHeaderPrefix,
13 OwnedBlockHeaderConsensusParameters,
14};
15use ab_core_primitives::block::owned::OwnedBeaconChainBlock;
16use ab_core_primitives::block::{BlockNumber, BlockRoot, BlockTimestamp};
17use ab_core_primitives::hashes::Blake3Hash;
18use ab_core_primitives::pot::{PotCheckpoints, PotOutput, PotParametersChange, SlotNumber};
19use ab_core_primitives::segments::SegmentRoot;
20use ab_core_primitives::solutions::{SolutionVerifyError, SolutionVerifyParams};
21use ab_proof_of_space::Table;
22use rand::prelude::*;
23use rayon::prelude::*;
24use std::iter;
25use std::marker::PhantomData;
26use std::time::SystemTime;
27use tracing::{debug, trace};
28
29#[derive(Debug, thiserror::Error)]
31pub enum BeaconChainBlockVerificationError {
32 #[error("Consensus parameters derivation error: {error}")]
34 ConsensusParametersDerivation {
35 #[from]
37 error: DeriveConsensusParametersError,
38 },
39 #[error("Invalid consensus parameters")]
41 InvalidConsensusParameters,
42 #[error("Invalid PoT checkpoints")]
44 InvalidPotCheckpoints,
45 #[error("Invalid proof of time")]
47 InvalidProofOfTime,
48 #[error("Solution error: {error}")]
50 SolutionError {
51 #[from]
53 error: SolutionVerifyError,
54 },
55}
56
57impl From<BeaconChainBlockVerificationError> for BlockVerificationError {
58 #[inline(always)]
59 fn from(error: BeaconChainBlockVerificationError) -> Self {
60 Self::Custom {
61 error: error.into(),
62 }
63 }
64}
65
66#[derive(Debug)]
67pub struct BeaconChainBlockVerification<PosTable, CI, CSS> {
68 segment_headers_store: SegmentHeadersStore,
69 consensus_constants: ConsensusConstants,
70 pot_verifier: PotVerifier,
71 chain_info: CI,
72 chain_sync_status: CSS,
73 _pos_table: PhantomData<PosTable>,
74}
75
76impl<PosTable, CI, CSS> BlockVerification<OwnedBeaconChainBlock>
77 for BeaconChainBlockVerification<PosTable, CI, CSS>
78where
79 PosTable: Table,
80 CI: ChainInfo<OwnedBeaconChainBlock>,
81 CSS: ChainSyncStatus,
82{
83 #[inline(always)]
84 async fn verify(
85 &self,
86 parent_header: &GenericHeader<'_, OwnedBeaconChainBlock>,
87 parent_block_mmr_root: &Blake3Hash,
88 header: &GenericHeader<'_, OwnedBeaconChainBlock>,
89 body: &GenericBody<'_, OwnedBeaconChainBlock>,
90 origin: BlockOrigin,
91 ) -> Result<(), BlockVerificationError> {
92 self.verify(parent_header, parent_block_mmr_root, header, body, origin)
93 .await
94 }
95}
96
97impl<PosTable, CI, CSS> BeaconChainBlockVerification<PosTable, CI, CSS>
98where
99 PosTable: Table,
100 CI: ChainInfo<OwnedBeaconChainBlock>,
101 CSS: ChainSyncStatus,
102{
103 #[inline(always)]
105 pub fn new(
106 segment_headers_store: SegmentHeadersStore,
107 consensus_constants: ConsensusConstants,
108 pot_verifier: PotVerifier,
109 chain_info: CI,
110 chain_sync_status: CSS,
111 ) -> Self {
112 Self {
113 segment_headers_store,
114 consensus_constants,
115 pot_verifier,
116 chain_info,
117 chain_sync_status,
118 _pos_table: PhantomData,
119 }
120 }
121
122 fn full_pot_verification(&self, block_number: BlockNumber) -> bool {
124 let sync_target_block_number = self.chain_sync_status.target_block_number();
125 let Some(diff) = sync_target_block_number.checked_sub(block_number) else {
126 return true;
127 };
128 let diff = diff.as_u64();
129
130 let sample_size = match diff {
131 ..=1_581 => {
132 return true;
133 }
134 1_582..=6_234 => 1_581,
135 6_235..=63_240 => 3_162 * (diff - 3_162) / (diff - 1),
136 63_241..=3_162_000 => 3_162,
137 _ => diff / 1_000,
138 };
139
140 let n = rand::rng().random_range(0..=diff);
141
142 n < sample_size
143 }
144
145 fn check_header_prefix(
146 &self,
147 parent_header_prefix: &BlockHeaderPrefix,
148 parent_block_mmr_root: &Blake3Hash,
149 header_prefix: &BlockHeaderPrefix,
150 ) -> Result<(), BlockVerificationError> {
151 let basic_valid = header_prefix.number == parent_header_prefix.number + BlockNumber::ONE
152 && header_prefix.shard_index == parent_header_prefix.shard_index
153 && &header_prefix.mmr_root == parent_block_mmr_root
154 && header_prefix.timestamp > parent_header_prefix.timestamp;
155
156 if !basic_valid {
157 return Err(BlockVerificationError::InvalidHeaderPrefix);
158 }
159
160 let timestamp_now = SystemTime::now()
161 .duration_since(SystemTime::UNIX_EPOCH)
162 .unwrap_or_default()
163 .as_millis();
164 let timestamp_now =
165 BlockTimestamp::from_millis(u64::try_from(timestamp_now).unwrap_or(u64::MAX));
166
167 if header_prefix.timestamp
168 > timestamp_now.saturating_add(self.consensus_constants.max_block_timestamp_drift)
169 {
170 return Err(BlockVerificationError::TimestampTooFarInTheFuture);
171 }
172
173 Ok(())
174 }
175
176 fn check_consensus_parameters(
177 &self,
178 parent_block_root: &BlockRoot,
179 parent_header: &BeaconChainHeader<'_>,
180 header: &BeaconChainHeader<'_>,
181 ) -> Result<(), BeaconChainBlockVerificationError> {
182 let derived_consensus_parameters = derive_consensus_parameters(
183 &self.consensus_constants,
184 &self.chain_info,
185 parent_block_root,
186 parent_header.consensus_parameters(),
187 parent_header.consensus_info.slot,
188 header.prefix.number,
189 header.consensus_info.slot,
190 )?;
191
192 let expected_consensus_parameters = OwnedBlockHeaderConsensusParameters {
193 fixed_parameters: derived_consensus_parameters.fixed_parameters,
194 super_segment_root: None,
196 next_solution_range: derived_consensus_parameters.next_solution_range,
197 pot_parameters_change: derived_consensus_parameters.pot_parameters_change,
198 };
199
200 if header.consensus_parameters() != &expected_consensus_parameters.as_ref() {
201 return Err(BeaconChainBlockVerificationError::InvalidConsensusParameters);
202 }
203
204 Ok(())
205 }
206
207 #[expect(
216 clippy::too_many_arguments,
217 reason = "Explicit minimal input for better testability"
218 )]
219 fn check_proof_of_time(
220 pot_verifier: &PotVerifier,
221 block_authoring_delay: SlotNumber,
222 parent_slot: SlotNumber,
223 parent_proof_of_time: PotOutput,
224 parent_future_proof_of_time: PotOutput,
225 parent_consensus_parameters: &BlockHeaderConsensusParameters<'_>,
226 slot: SlotNumber,
227 proof_of_time: PotOutput,
228 future_proof_of_time: PotOutput,
229 consensus_parameters: &BlockHeaderConsensusParameters<'_>,
230 checkpoints: &[PotCheckpoints],
231 verify_checkpoints: bool,
232 ) -> Result<(), BeaconChainBlockVerificationError> {
233 let pot_parameters_change = consensus_parameters
234 .pot_parameters_change
235 .copied()
236 .map(PotParametersChange::from);
237
238 let parent_pot_parameters_change = parent_consensus_parameters
239 .pot_parameters_change
240 .copied()
241 .map(PotParametersChange::from);
242
243 if checkpoints.last().map(PotCheckpoints::output) != Some(future_proof_of_time) {
245 return Err(BeaconChainBlockVerificationError::InvalidPotCheckpoints);
246 }
247
248 let parent_future_slot = if parent_slot == SlotNumber::ZERO {
249 parent_slot
250 } else {
251 parent_slot + block_authoring_delay
252 };
253
254 let slots_between_blocks = slot
255 .checked_sub(parent_slot)
256 .ok_or(BeaconChainBlockVerificationError::InvalidPotCheckpoints)?;
257 if slots_between_blocks.as_u64() != checkpoints.len() as u64 {
262 return Err(BeaconChainBlockVerificationError::InvalidPotCheckpoints);
263 }
264
265 let mut pot_input = if parent_slot == SlotNumber::ZERO {
266 PotNextSlotInput {
267 slot: parent_slot + SlotNumber::ONE,
268 slot_iterations: parent_consensus_parameters.fixed_parameters.slot_iterations,
269 seed: pot_verifier.genesis_seed(),
270 }
271 } else {
272 let slot_iterations = parent_pot_parameters_change
274 .and_then(|parameters_change| {
275 (parameters_change.slot <= parent_future_slot)
276 .then_some(parameters_change.slot_iterations)
277 })
278 .unwrap_or(parent_consensus_parameters.fixed_parameters.slot_iterations);
279 PotNextSlotInput::derive(
281 slot_iterations,
282 parent_future_slot,
283 parent_future_proof_of_time,
284 &parent_pot_parameters_change,
285 )
286 };
287
288 let checkpoints_verification_input = iter::once((
290 pot_input,
291 *checkpoints
292 .first()
293 .expect("Not empty, contents was checked above; qed"),
294 ));
295 let checkpoints_verification_input = checkpoints_verification_input
296 .chain(checkpoints.array_windows::<2>().map(|[left, right]| {
297 pot_input = PotNextSlotInput::derive(
298 pot_input.slot_iterations,
299 pot_input.slot,
300 left.output(),
301 &pot_parameters_change,
302 );
303
304 (pot_input, *right)
305 }))
306 .collect::<Vec<_>>();
308
309 let all_checkpoints_valid =
311 checkpoints_verification_input
312 .into_par_iter()
313 .all(|(pot_input, checkpoints)| {
314 if verify_checkpoints {
315 pot_verifier.verify_checkpoints(
316 pot_input.seed,
317 pot_input.slot_iterations,
318 &checkpoints,
319 )
320 } else {
321 pot_verifier.inject_verified_checkpoints(
323 pot_input.seed,
324 pot_input.slot_iterations,
325 checkpoints,
326 );
327 true
328 }
329 });
330
331 if !all_checkpoints_valid {
332 return Err(BeaconChainBlockVerificationError::InvalidPotCheckpoints);
333 }
334
335 {
337 let pot_input = if parent_slot == SlotNumber::ZERO {
338 PotNextSlotInput {
339 slot: parent_slot + SlotNumber::ONE,
340 slot_iterations: parent_consensus_parameters.fixed_parameters.slot_iterations,
341 seed: pot_verifier.genesis_seed(),
342 }
343 } else {
344 let slot_iterations = parent_pot_parameters_change
346 .and_then(|parameters_change| {
347 (parameters_change.slot <= parent_slot)
348 .then_some(parameters_change.slot_iterations)
349 })
350 .unwrap_or(parent_consensus_parameters.fixed_parameters.slot_iterations);
351 PotNextSlotInput::derive(
353 slot_iterations,
354 parent_slot,
355 parent_proof_of_time,
356 &parent_pot_parameters_change,
357 )
358 };
359
360 if pot_verifier.is_output_valid(
361 pot_input,
362 slots_between_blocks,
363 proof_of_time,
364 pot_parameters_change,
365 ) {
366 return Err(BeaconChainBlockVerificationError::InvalidProofOfTime);
367 }
368 }
369
370 Ok(())
371 }
372
373 fn check_body(
374 &self,
375 block_number: BlockNumber,
376 own_segment_roots: &[SegmentRoot],
377 _intermediate_shard_blocks: &IntermediateShardBlocksInfo<'_>,
378 ) -> Result<(), BlockVerificationError> {
379 let expected_segment_headers = self
380 .segment_headers_store
381 .segment_headers_for_block(block_number);
382 let correct_segment_roots = expected_segment_headers
383 .iter()
384 .map(|segment_header| &segment_header.segment_root)
385 .eq(own_segment_roots);
386 if !correct_segment_roots {
387 return Err(BlockVerificationError::InvalidOwnSegmentRoots {
388 expected: expected_segment_headers
389 .iter()
390 .map(|segment_header| segment_header.segment_root)
391 .collect(),
392 actual: own_segment_roots.to_vec(),
393 });
394 }
395
396 Ok(())
399 }
400
401 async fn verify(
402 &self,
403 parent_header: &BeaconChainHeader<'_>,
404 parent_block_mmr_root: &Blake3Hash,
405 header: &BeaconChainHeader<'_>,
406 body: &BeaconChainBody<'_>,
407 _origin: BlockOrigin,
408 ) -> Result<(), BlockVerificationError> {
409 trace!(header = ?header, "Verifying");
410
411 let parent_block_root = parent_header.root();
412
413 let block_number = header.prefix.number;
414 let consensus_info = header.consensus_info;
415 let consensus_parameters = header.consensus_parameters();
416 let slot = consensus_info.slot;
417
418 let best_header = self.chain_info.best_header();
419 let best_header = best_header.header();
420 let best_number = best_header.prefix.number;
421
422 if block_number + self.consensus_constants.confirmation_depth_k < best_number {
424 debug!(
425 ?header,
426 %best_number,
427 "Rejecting a block below the archiving point"
428 );
429
430 return Err(BlockVerificationError::BelowArchivingPoint);
431 }
432
433 self.check_header_prefix(parent_header.prefix, parent_block_mmr_root, header.prefix)?;
434
435 self.check_consensus_parameters(&parent_block_root, parent_header, header)?;
436
437 if !header.is_sealed_correctly() {
438 return Err(BlockVerificationError::InvalidSeal);
439 }
440
441 consensus_info
443 .solution
444 .verify::<PosTable>(
445 slot,
446 &SolutionVerifyParams {
447 proof_of_time: consensus_info.proof_of_time,
448 solution_range: consensus_parameters.fixed_parameters.solution_range,
449 piece_check_params: None,
451 },
452 )
453 .map_err(BeaconChainBlockVerificationError::from)?;
454
455 Self::check_proof_of_time(
456 &self.pot_verifier,
457 self.consensus_constants.block_authoring_delay,
458 parent_header.consensus_info.slot,
459 parent_header.consensus_info.proof_of_time,
460 parent_header.consensus_info.future_proof_of_time,
461 parent_header.consensus_parameters(),
462 consensus_info.slot,
463 consensus_info.proof_of_time,
464 consensus_info.future_proof_of_time,
465 consensus_parameters,
466 body.pot_checkpoints(),
467 self.full_pot_verification(block_number),
468 )?;
469
470 self.check_body(
471 block_number,
472 body.own_segment_roots(),
473 body.intermediate_shard_blocks(),
474 )?;
475
476 Ok(())
479 }
480}