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 DeriveConsensusParametersChainInfo, DeriveConsensusParametersError,
6 ShardMembershipEntropySourceChainInfo, derive_consensus_parameters,
7 shard_membership_entropy_source,
8};
9use ab_client_proof_of_time::PotNextSlotInput;
10use ab_client_proof_of_time::verifier::PotVerifier;
11use ab_core_primitives::block::body::{BeaconChainBody, IntermediateShardBlocksInfo, OwnSegments};
12use ab_core_primitives::block::header::{
13 BeaconChainHeader, BlockHeaderConsensusParameters, BlockHeaderPrefix,
14 OwnedBlockHeaderConsensusParameters,
15};
16use ab_core_primitives::block::owned::OwnedBeaconChainBlock;
17use ab_core_primitives::block::{BlockNumber, BlockRoot, BlockTimestamp};
18use ab_core_primitives::hashes::Blake3Hash;
19use ab_core_primitives::pot::{PotCheckpoints, PotOutput, PotParametersChange, SlotNumber};
20use ab_core_primitives::shard::ShardIndex;
21use ab_core_primitives::solutions::{SolutionVerifyError, SolutionVerifyParams};
22use ab_proof_of_space::Table;
23use rand::prelude::*;
24use rayon::prelude::*;
25use std::iter;
26use std::marker::PhantomData;
27use std::time::SystemTime;
28use tracing::{debug, trace};
29
30#[derive(Debug, thiserror::Error)]
32pub enum BeaconChainBlockVerificationError {
33 #[error("Consensus parameters derivation error: {error}")]
35 ConsensusParametersDerivation {
36 #[from]
38 error: DeriveConsensusParametersError,
39 },
40 #[error("Invalid consensus parameters: expected {expected:?}, actual {actual:?}")]
42 InvalidConsensusParameters {
43 expected: Box<OwnedBlockHeaderConsensusParameters>,
45 actual: Box<OwnedBlockHeaderConsensusParameters>,
47 },
48 #[error("Invalid PoT checkpoints")]
50 InvalidPotCheckpoints,
51 #[error("Invalid proof of time")]
53 InvalidProofOfTime,
54 #[error("Solution error: {error}")]
56 SolutionError {
57 #[from]
59 error: SolutionVerifyError,
60 },
61}
62
63impl From<BeaconChainBlockVerificationError> for BlockVerificationError {
64 #[inline(always)]
65 fn from(error: BeaconChainBlockVerificationError) -> Self {
66 Self::Custom {
67 error: error.into(),
68 }
69 }
70}
71
72#[derive(Debug)]
73pub struct BeaconChainBlockVerification<PosTable, CI, CSS> {
74 consensus_constants: ConsensusConstants,
75 pot_verifier: PotVerifier,
76 chain_info: CI,
77 chain_sync_status: CSS,
78 _pos_table: PhantomData<PosTable>,
79}
80
81impl<PosTable, CI, CSS> BlockVerification<OwnedBeaconChainBlock>
82 for BeaconChainBlockVerification<PosTable, CI, CSS>
83where
84 PosTable: Table,
85 CI: ChainInfo<OwnedBeaconChainBlock>,
86 CSS: ChainSyncStatus,
87{
88 #[inline(always)]
89 async fn verify_concurrent<BCI>(
90 &self,
91 parent_header: &GenericHeader<'_, OwnedBeaconChainBlock>,
92 parent_block_mmr_root: &Blake3Hash,
93 header: &GenericHeader<'_, OwnedBeaconChainBlock>,
94 body: &GenericBody<'_, OwnedBeaconChainBlock>,
95 origin: &BlockOrigin,
96 beacon_chain_info: &BCI,
97 ) -> Result<(), BlockVerificationError>
98 where
99 BCI: DeriveConsensusParametersChainInfo + ShardMembershipEntropySourceChainInfo,
100 {
101 self.verify_concurrent(
102 parent_header,
103 parent_block_mmr_root,
104 header,
105 body,
106 origin,
107 beacon_chain_info,
108 )
109 .await
110 }
111
112 #[inline(always)]
113 async fn verify_sequential(
114 &self,
115 parent_header: &GenericHeader<'_, OwnedBeaconChainBlock>,
116 parent_block_mmr_root: &Blake3Hash,
117 header: &GenericHeader<'_, OwnedBeaconChainBlock>,
118 body: &GenericBody<'_, OwnedBeaconChainBlock>,
119 origin: &BlockOrigin,
120 ) -> Result<(), BlockVerificationError> {
121 self.verify_sequential(parent_header, parent_block_mmr_root, header, body, origin)
122 .await
123 }
124}
125
126impl<PosTable, CI, CSS> BeaconChainBlockVerification<PosTable, CI, CSS>
127where
128 PosTable: Table,
129 CI: ChainInfo<OwnedBeaconChainBlock>,
130 CSS: ChainSyncStatus,
131{
132 #[inline(always)]
134 pub fn new(
135 consensus_constants: ConsensusConstants,
136 pot_verifier: PotVerifier,
137 chain_info: CI,
138 chain_sync_status: CSS,
139 ) -> Self {
140 Self {
141 consensus_constants,
142 pot_verifier,
143 chain_info,
144 chain_sync_status,
145 _pos_table: PhantomData,
146 }
147 }
148
149 fn full_pot_verification(&self, block_number: BlockNumber) -> bool {
151 let sync_target_block_number = self.chain_sync_status.target_block_number();
152 let Some(diff) = sync_target_block_number.checked_sub(block_number) else {
153 return true;
154 };
155 let diff = u64::from(diff);
156
157 let sample_size = match diff {
158 ..=1_581 => {
159 return true;
160 }
161 1_582..=6_234 => 1_581,
162 6_235..=63_240 => 3_162 * (diff - 3_162) / (diff - 1),
163 63_241..=3_162_000 => 3_162,
164 _ => diff / 1_000,
165 };
166
167 let n = rand::rng().random_range(0..=diff);
168
169 n < sample_size
170 }
171
172 fn check_header_prefix(
173 &self,
174 parent_header_prefix: &BlockHeaderPrefix,
175 parent_block_mmr_root: &Blake3Hash,
176 header_prefix: &BlockHeaderPrefix,
177 ) -> Result<(), BlockVerificationError> {
178 let basic_valid = header_prefix.number == parent_header_prefix.number + BlockNumber::ONE
179 && header_prefix.shard_index == parent_header_prefix.shard_index
180 && &header_prefix.mmr_root == parent_block_mmr_root
181 && header_prefix.timestamp > parent_header_prefix.timestamp;
182
183 if !basic_valid {
184 return Err(BlockVerificationError::InvalidHeaderPrefix);
185 }
186
187 let timestamp_now = SystemTime::now()
188 .duration_since(SystemTime::UNIX_EPOCH)
189 .unwrap_or_default()
190 .as_millis();
191 let timestamp_now =
192 BlockTimestamp::from_millis(u64::try_from(timestamp_now).unwrap_or(u64::MAX));
193
194 if header_prefix.timestamp
195 > timestamp_now.saturating_add(self.consensus_constants.max_block_timestamp_drift)
196 {
197 return Err(BlockVerificationError::TimestampTooFarInTheFuture);
198 }
199
200 Ok(())
201 }
202
203 fn check_consensus_parameters<BCI>(
204 &self,
205 parent_block_root: &BlockRoot,
206 parent_header: &BeaconChainHeader<'_>,
207 header: &BeaconChainHeader<'_>,
208 beacon_chain_info: &BCI,
209 ) -> Result<(), BeaconChainBlockVerificationError>
210 where
211 BCI: DeriveConsensusParametersChainInfo,
212 {
213 let derived_consensus_parameters = derive_consensus_parameters(
214 &self.consensus_constants,
215 beacon_chain_info,
216 parent_block_root,
217 parent_header.consensus_parameters(),
218 parent_header.consensus_info.slot,
219 header.prefix.number,
220 header.consensus_info.slot,
221 )?;
222
223 let expected_consensus_parameters = OwnedBlockHeaderConsensusParameters {
224 fixed_parameters: derived_consensus_parameters.fixed_parameters,
225 super_segment_root: None,
227 next_solution_range: derived_consensus_parameters.next_solution_range,
228 pot_parameters_change: derived_consensus_parameters.pot_parameters_change,
229 };
230
231 if header.consensus_parameters() != &expected_consensus_parameters.as_ref() {
232 return Err(
233 BeaconChainBlockVerificationError::InvalidConsensusParameters {
234 expected: Box::new(expected_consensus_parameters),
235 actual: Box::new(OwnedBlockHeaderConsensusParameters {
236 fixed_parameters: header.consensus_parameters().fixed_parameters,
237 super_segment_root: header
238 .consensus_parameters()
239 .super_segment_root
240 .copied(),
241 next_solution_range: header.consensus_parameters().next_solution_range,
242 pot_parameters_change: header
243 .consensus_parameters()
244 .pot_parameters_change
245 .copied(),
246 }),
247 },
248 );
249 }
250
251 Ok(())
252 }
253
254 #[expect(
263 clippy::too_many_arguments,
264 reason = "Explicit minimal input for better testability"
265 )]
266 fn check_proof_of_time(
267 pot_verifier: &PotVerifier,
268 block_authoring_delay: SlotNumber,
269 parent_slot: SlotNumber,
270 parent_proof_of_time: PotOutput,
271 parent_future_proof_of_time: PotOutput,
272 parent_consensus_parameters: &BlockHeaderConsensusParameters<'_>,
273 slot: SlotNumber,
274 proof_of_time: PotOutput,
275 future_proof_of_time: PotOutput,
276 checkpoints: &[PotCheckpoints],
277 verify_checkpoints: bool,
278 ) -> Result<(), BeaconChainBlockVerificationError> {
279 let parent_pot_parameters_change = parent_consensus_parameters
280 .pot_parameters_change
281 .copied()
282 .map(PotParametersChange::from);
283
284 if checkpoints.last().map(PotCheckpoints::output) != Some(future_proof_of_time) {
286 return Err(BeaconChainBlockVerificationError::InvalidPotCheckpoints);
287 }
288
289 let future_slot = slot + block_authoring_delay;
290 let parent_future_slot = if parent_slot == SlotNumber::ZERO {
291 parent_slot
292 } else {
293 parent_slot + block_authoring_delay
294 };
295
296 let slots_between_blocks = slot
297 .checked_sub(parent_slot)
298 .ok_or(BeaconChainBlockVerificationError::InvalidPotCheckpoints)?;
299 if !(u64::from(slots_between_blocks) == checkpoints.len() as u64
306 || (parent_slot == SlotNumber::ZERO
307 && u64::from(future_slot) == checkpoints.len() as u64))
308 {
309 return Err(BeaconChainBlockVerificationError::InvalidPotCheckpoints);
310 }
311
312 let mut pot_input = if parent_slot == SlotNumber::ZERO {
313 PotNextSlotInput {
314 slot: parent_slot + SlotNumber::ONE,
315 slot_iterations: parent_consensus_parameters.fixed_parameters.slot_iterations,
316 seed: pot_verifier.genesis_seed(),
317 }
318 } else {
319 let slot_iterations = parent_pot_parameters_change
321 .and_then(|parameters_change| {
322 (parameters_change.slot <= parent_future_slot)
323 .then_some(parameters_change.slot_iterations)
324 })
325 .unwrap_or(parent_consensus_parameters.fixed_parameters.slot_iterations);
326 PotNextSlotInput::derive(
328 slot_iterations,
329 parent_future_slot,
330 parent_future_proof_of_time,
331 &parent_pot_parameters_change,
332 )
333 };
334
335 let checkpoints_verification_input = iter::once((
337 pot_input,
338 *checkpoints
339 .first()
340 .expect("Not empty, contents was checked above; qed"),
341 ));
342 let checkpoints_verification_input = checkpoints_verification_input
343 .chain(checkpoints.array_windows::<2>().map(|[left, right]| {
344 pot_input = PotNextSlotInput::derive(
345 pot_input.slot_iterations,
346 pot_input.slot,
347 left.output(),
348 &parent_pot_parameters_change,
349 );
350
351 (pot_input, *right)
352 }))
353 .collect::<Vec<_>>();
355
356 let all_checkpoints_valid =
358 checkpoints_verification_input
359 .into_par_iter()
360 .all(|(pot_input, checkpoints)| {
361 if verify_checkpoints {
362 pot_verifier.verify_checkpoints(
363 pot_input.seed,
364 pot_input.slot_iterations,
365 &checkpoints,
366 )
367 } else {
368 pot_verifier.inject_verified_checkpoints(
370 pot_input.seed,
371 pot_input.slot_iterations,
372 checkpoints,
373 );
374 true
375 }
376 });
377
378 if !all_checkpoints_valid {
379 return Err(BeaconChainBlockVerificationError::InvalidPotCheckpoints);
380 }
381
382 {
384 let pot_input = if parent_slot == SlotNumber::ZERO {
385 PotNextSlotInput {
386 slot: parent_slot + SlotNumber::ONE,
387 slot_iterations: parent_consensus_parameters.fixed_parameters.slot_iterations,
388 seed: pot_verifier.genesis_seed(),
389 }
390 } else {
391 let slot_iterations = parent_pot_parameters_change
393 .and_then(|parameters_change| {
394 (parameters_change.slot <= parent_slot)
395 .then_some(parameters_change.slot_iterations)
396 })
397 .unwrap_or(parent_consensus_parameters.fixed_parameters.slot_iterations);
398 PotNextSlotInput::derive(
400 slot_iterations,
401 parent_slot,
402 parent_proof_of_time,
403 &parent_pot_parameters_change,
404 )
405 };
406
407 if !pot_verifier.is_output_valid(
408 pot_input,
409 slots_between_blocks,
410 proof_of_time,
411 parent_pot_parameters_change,
412 ) {
413 return Err(BeaconChainBlockVerificationError::InvalidProofOfTime);
414 }
415 }
416
417 Ok(())
418 }
419
420 fn check_body(
421 &self,
422 block_number: BlockNumber,
423 own_segments: Option<OwnSegments<'_>>,
424 _intermediate_shard_blocks: &IntermediateShardBlocksInfo<'_>,
425 ) -> Result<(), BlockVerificationError> {
426 let expected_segment_headers = self.chain_info.segment_headers_for_block(block_number);
427 let expected_first_local_segment_index = expected_segment_headers
428 .first()
429 .map(|segment_header| segment_header.segment_index.as_inner());
430 let correct_first_local_segment_index = expected_first_local_segment_index
431 == own_segments
432 .as_ref()
433 .map(|own_segments| own_segments.first_local_segment_index);
434 let correct_segment_roots = expected_segment_headers
435 .iter()
436 .map(|segment_header| &segment_header.segment_root)
437 .eq(own_segments
438 .as_ref()
439 .map(|own_segments| own_segments.segment_roots)
440 .unwrap_or_default());
441 if !(correct_first_local_segment_index && correct_segment_roots) {
442 return Err(BlockVerificationError::InvalidOwnSegments {
443 expected_first_local_segment_index,
444 expected_segment_roots: expected_segment_headers
445 .iter()
446 .map(|segment_header| segment_header.segment_root)
447 .collect(),
448 actual_first_local_segment_index: own_segments
449 .as_ref()
450 .map(|own_segments| own_segments.first_local_segment_index),
451 actual_segment_roots: own_segments
452 .as_ref()
453 .map(|own_segments| own_segments.segment_roots.to_vec())
454 .unwrap_or_default(),
455 });
456 }
457
458 Ok(())
461 }
462
463 async fn verify_concurrent<BCI>(
464 &self,
465 parent_header: &BeaconChainHeader<'_>,
466 parent_block_mmr_root: &Blake3Hash,
467 header: &BeaconChainHeader<'_>,
468 body: &BeaconChainBody<'_>,
469 _origin: &BlockOrigin,
470 beacon_chain_info: &BCI,
471 ) -> Result<(), BlockVerificationError>
472 where
473 BCI: DeriveConsensusParametersChainInfo + ShardMembershipEntropySourceChainInfo,
474 {
475 trace!(header = ?header, "Verify concurrent");
476
477 let parent_block_root = parent_header.root();
478
479 let block_number = header.prefix.number;
480 let consensus_info = header.consensus_info;
481 let consensus_parameters = header.consensus_parameters();
482 let slot = consensus_info.slot;
483
484 let best_header = self.chain_info.best_header();
485 let best_header = best_header.header();
486 let best_number = best_header.prefix.number;
487
488 if block_number + self.consensus_constants.confirmation_depth_k < best_number {
490 debug!(
491 ?header,
492 %best_number,
493 "Rejecting a block below the archiving point"
494 );
495
496 return Err(BlockVerificationError::BelowArchivingPoint);
497 }
498
499 self.check_header_prefix(parent_header.prefix, parent_block_mmr_root, header.prefix)?;
500
501 self.check_consensus_parameters(
502 &parent_block_root,
503 parent_header,
504 header,
505 beacon_chain_info,
506 )?;
507
508 if !header.is_sealed_correctly() {
509 return Err(BlockVerificationError::InvalidSeal);
510 }
511
512 let shard_membership_entropy = shard_membership_entropy_source(
514 header.prefix.number,
515 best_header,
516 self.consensus_constants.shard_rotation_interval,
517 self.consensus_constants.shard_rotation_delay,
518 beacon_chain_info,
519 )?;
520
521 consensus_info
523 .solution
524 .verify::<PosTable>(
525 slot,
526 &SolutionVerifyParams {
527 shard_index: ShardIndex::BEACON_CHAIN,
528 proof_of_time: consensus_info.proof_of_time,
529 solution_range: consensus_parameters.fixed_parameters.solution_range,
530 shard_membership_entropy,
531 num_shards: consensus_parameters.fixed_parameters.num_shards,
532 piece_check_params: None,
534 },
535 )
536 .map_err(BeaconChainBlockVerificationError::from)?;
537
538 Self::check_proof_of_time(
539 &self.pot_verifier,
540 self.consensus_constants.block_authoring_delay,
541 parent_header.consensus_info.slot,
542 parent_header.consensus_info.proof_of_time,
543 parent_header.consensus_info.future_proof_of_time,
544 parent_header.consensus_parameters(),
545 consensus_info.slot,
546 consensus_info.proof_of_time,
547 consensus_info.future_proof_of_time,
548 body.pot_checkpoints(),
549 self.full_pot_verification(block_number),
550 )?;
551
552 Ok(())
555 }
556
557 async fn verify_sequential(
558 &self,
559 _parent_header: &BeaconChainHeader<'_>,
561 _parent_block_mmr_root: &Blake3Hash,
562 header: &BeaconChainHeader<'_>,
563 body: &BeaconChainBody<'_>,
564 _origin: &BlockOrigin,
565 ) -> Result<(), BlockVerificationError> {
566 trace!(header = ?header, "Verify sequential");
567
568 let block_number = header.prefix.number;
569
570 let best_header = self.chain_info.best_header();
571 let best_header = best_header.header();
572 let best_number = best_header.prefix.number;
573
574 if block_number + self.consensus_constants.confirmation_depth_k < best_number {
576 debug!(
577 ?header,
578 %best_number,
579 "Rejecting a block below the archiving point"
580 );
581
582 return Err(BlockVerificationError::BelowArchivingPoint);
583 }
584
585 self.check_body(
586 block_number,
587 body.own_segments(),
588 body.intermediate_shard_blocks(),
589 )?;
590
591 Ok(())
592 }
593}