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