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