Skip to main content

ab_client_block_verification/
beacon_chain.rs

1use crate::{BlockVerification, BlockVerificationError, GenericBody, GenericHeader};
2use ab_client_api::{BeaconChainInfo, BlockOrigin, ChainSyncStatus};
3use ab_client_consensus_common::ConsensusConstants;
4use ab_client_consensus_common::consensus_parameters::{
5    DeriveConsensusParametersChainInfo, DeriveConsensusParametersError,
6    DeriveSuperSegmentForBlockError, ShardMembershipEntropySourceChainInfo,
7    derive_consensus_parameters, derive_super_segments_for_block, 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::segments::{
21    HistorySize, SuperSegment, SuperSegmentIndex, SuperSegmentRoot,
22};
23use ab_core_primitives::shard::ShardIndex;
24use ab_core_primitives::solutions::{
25    SolutionVerifyError, SolutionVerifyPieceParams, SolutionVerifyStatelessParams,
26};
27use ab_proof_of_space::Table;
28use rand::prelude::*;
29use rayon::prelude::*;
30use std::iter;
31use std::marker::PhantomData;
32use std::time::SystemTime;
33use tracing::{debug, trace};
34
35/// Errors for [`BeaconChainBlockVerification`]
36#[derive(Debug, thiserror::Error)]
37pub enum BeaconChainBlockVerificationError {
38    /// Consensus parameters derivation error
39    #[error("Consensus parameters derivation error: {error}")]
40    ConsensusParametersDerivation {
41        /// Consensus parameters derivation error
42        #[from]
43        error: DeriveConsensusParametersError,
44    },
45    /// Super segment derivation error
46    #[error("Super segment derivation error: {error}")]
47    SuperSegmentDerivation {
48        /// Super segment derivation error
49        #[from]
50        error: DeriveSuperSegmentForBlockError,
51    },
52    /// Invalid consensus parameters
53    #[error("Invalid consensus parameters: expected {expected:?}, actual {actual:?}")]
54    InvalidConsensusParameters {
55        /// Expected consensus parameters
56        expected: Box<OwnedBlockHeaderConsensusParameters>,
57        /// Actual consensus parameters
58        actual: Box<OwnedBlockHeaderConsensusParameters>,
59    },
60    /// Missing a super segment in the first block
61    #[error("Missing super segment in the first block")]
62    MissingSuperSegmentInFirstBlock,
63    /// Previous super segment header not found
64    #[error("Previous super segment header not found")]
65    PreviousSuperSegmentHeaderNotFound,
66    /// Solution super segment not found
67    #[error("Solution super segment {index} not found")]
68    SolutionSuperSegmentNotFound {
69        /// Expected super segment index
70        index: SuperSegmentIndex,
71    },
72    /// Invalid history size
73    #[error("Invalid history size {history_size} (current {current_history_size})")]
74    InvalidHistorySize {
75        /// History size in the solution
76        history_size: HistorySize,
77        /// Current history size
78        current_history_size: HistorySize,
79    },
80    /// Invalid super segment root
81    #[error("Invalid super segment root: expected {expected:?}, actual {actual:?}")]
82    InvalidSuperSegmentRoot {
83        /// Expected super segment root
84        expected: Box<Option<SuperSegmentRoot>>,
85        /// Actual super segment root
86        actual: Box<Option<SuperSegmentRoot>>,
87    },
88    /// Invalid PoT checkpoints
89    #[error("Invalid PoT checkpoints")]
90    InvalidPotCheckpoints,
91    /// Invalid proof of time
92    #[error("Invalid proof of time")]
93    InvalidProofOfTime,
94    /// Solution error
95    #[error("Solution error: {error}")]
96    SolutionError {
97        /// Solution error
98        #[from]
99        error: SolutionVerifyError,
100    },
101}
102
103impl From<BeaconChainBlockVerificationError> for BlockVerificationError {
104    #[inline(always)]
105    fn from(error: BeaconChainBlockVerificationError) -> Self {
106        Self::Custom {
107            error: error.into(),
108        }
109    }
110}
111
112#[derive(Debug)]
113pub struct BeaconChainBlockVerification<PosTable, CI, CSS> {
114    consensus_constants: ConsensusConstants,
115    pot_verifier: PotVerifier,
116    chain_info: CI,
117    chain_sync_status: CSS,
118    _pos_table: PhantomData<PosTable>,
119}
120
121impl<PosTable, CI, CSS> BlockVerification<OwnedBeaconChainBlock, Option<SuperSegment>>
122    for BeaconChainBlockVerification<PosTable, CI, CSS>
123where
124    PosTable: Table,
125    CI: BeaconChainInfo,
126    CSS: ChainSyncStatus,
127{
128    #[inline(always)]
129    async fn verify_concurrent<BCI>(
130        &self,
131        parent_header: &GenericHeader<'_, OwnedBeaconChainBlock>,
132        parent_block_mmr_root: &Blake3Hash,
133        header: &GenericHeader<'_, OwnedBeaconChainBlock>,
134        body: &GenericBody<'_, OwnedBeaconChainBlock>,
135        origin: &BlockOrigin,
136        beacon_chain_info: &BCI,
137    ) -> Result<(), BlockVerificationError>
138    where
139        BCI: DeriveConsensusParametersChainInfo + ShardMembershipEntropySourceChainInfo,
140    {
141        self.verify_concurrent(
142            parent_header,
143            parent_block_mmr_root,
144            header,
145            body,
146            origin,
147            beacon_chain_info,
148        )
149        .await
150    }
151
152    #[inline(always)]
153    async fn verify_sequential(
154        &self,
155        parent_header: &GenericHeader<'_, OwnedBeaconChainBlock>,
156        parent_block_mmr_root: &Blake3Hash,
157        header: &GenericHeader<'_, OwnedBeaconChainBlock>,
158        body: &GenericBody<'_, OwnedBeaconChainBlock>,
159        origin: &BlockOrigin,
160    ) -> Result<Option<SuperSegment>, BlockVerificationError> {
161        self.verify_sequential(parent_header, parent_block_mmr_root, header, body, origin)
162            .await
163    }
164}
165
166impl<PosTable, CI, CSS> BeaconChainBlockVerification<PosTable, CI, CSS>
167where
168    PosTable: Table,
169    CI: BeaconChainInfo,
170    CSS: ChainSyncStatus,
171{
172    /// Create a new instance
173    #[inline(always)]
174    pub fn new(
175        consensus_constants: ConsensusConstants,
176        pot_verifier: PotVerifier,
177        chain_info: CI,
178        chain_sync_status: CSS,
179    ) -> Self {
180        Self {
181            consensus_constants,
182            pot_verifier,
183            chain_info,
184            chain_sync_status,
185            _pos_table: PhantomData,
186        }
187    }
188
189    /// Determine if full proof of time verification is needed for this block number
190    fn full_pot_verification(&self, block_number: BlockNumber) -> bool {
191        let sync_target_block_number = self.chain_sync_status.target_block_number();
192        let Some(diff) = sync_target_block_number.checked_sub(block_number) else {
193            return true;
194        };
195        let diff = u64::from(diff);
196
197        let sample_size = match diff {
198            ..=1_581 => {
199                return true;
200            }
201            1_582..=6_234 => 1_581,
202            6_235..=63_240 => 3_162 * (diff - 3_162) / (diff - 1),
203            63_241..=3_162_000 => 3_162,
204            _ => diff / 1_000,
205        };
206
207        let n = rand::rng().random_range(0..=diff);
208
209        n < sample_size
210    }
211
212    fn check_header_prefix(
213        &self,
214        parent_header_prefix: &BlockHeaderPrefix,
215        parent_block_mmr_root: &Blake3Hash,
216        header_prefix: &BlockHeaderPrefix,
217    ) -> Result<(), BlockVerificationError> {
218        let basic_valid = header_prefix.number == parent_header_prefix.number + BlockNumber::ONE
219            && header_prefix.shard_index == parent_header_prefix.shard_index
220            && &header_prefix.mmr_root == parent_block_mmr_root
221            && header_prefix.timestamp > parent_header_prefix.timestamp;
222
223        if !basic_valid {
224            return Err(BlockVerificationError::InvalidHeaderPrefix);
225        }
226
227        let timestamp_now = SystemTime::now()
228            .duration_since(SystemTime::UNIX_EPOCH)
229            .unwrap_or_default()
230            .as_millis();
231        let timestamp_now =
232            BlockTimestamp::from_millis(u64::try_from(timestamp_now).unwrap_or(u64::MAX));
233
234        if header_prefix.timestamp
235            > timestamp_now.saturating_add(self.consensus_constants.max_block_timestamp_drift)
236        {
237            return Err(BlockVerificationError::TimestampTooFarInTheFuture);
238        }
239
240        Ok(())
241    }
242
243    fn check_consensus_parameters_concurrent<BCI>(
244        &self,
245        parent_block_root: &BlockRoot,
246        parent_header: &BeaconChainHeader<'_>,
247        header: &BeaconChainHeader<'_>,
248        beacon_chain_info: &BCI,
249    ) -> Result<(), BeaconChainBlockVerificationError>
250    where
251        BCI: DeriveConsensusParametersChainInfo,
252    {
253        let derived_consensus_parameters = derive_consensus_parameters(
254            &self.consensus_constants,
255            beacon_chain_info,
256            parent_block_root,
257            parent_header.consensus_parameters(),
258            parent_header.consensus_info.slot,
259            header.prefix.number,
260            header.consensus_info.slot,
261        )?;
262
263        let expected_consensus_parameters = OwnedBlockHeaderConsensusParameters {
264            fixed_parameters: derived_consensus_parameters.fixed_parameters,
265            // TODO: This field is verified separately in the sequential part
266            super_segment_root: header.consensus_parameters().super_segment_root.copied(),
267            next_solution_range: derived_consensus_parameters.next_solution_range,
268            pot_parameters_change: derived_consensus_parameters.pot_parameters_change,
269        };
270
271        if header.consensus_parameters() != &expected_consensus_parameters.as_ref() {
272            return Err(
273                BeaconChainBlockVerificationError::InvalidConsensusParameters {
274                    expected: Box::new(expected_consensus_parameters),
275                    actual: Box::new(OwnedBlockHeaderConsensusParameters {
276                        fixed_parameters: header.consensus_parameters().fixed_parameters,
277                        super_segment_root: header
278                            .consensus_parameters()
279                            .super_segment_root
280                            .copied(),
281                        next_solution_range: header.consensus_parameters().next_solution_range,
282                        pot_parameters_change: header
283                            .consensus_parameters()
284                            .pot_parameters_change
285                            .copied(),
286                    }),
287                },
288            );
289        }
290
291        Ok(())
292    }
293
294    // TODO: This is a blocking function, but ideally wouldn't be block an executor
295    /// Checks current/future proof of time in the consensus info for the slot and corresponding
296    /// checkpoints.
297    ///
298    /// `consensus_parameters` is assumed to be correct and needs to be verified separately.
299    ///
300    /// When `verify_checkpoints == false` checkpoints are assumed to be correct and verification
301    /// for them is skipped.
302    #[expect(
303        clippy::too_many_arguments,
304        reason = "Explicit minimal input for better testability"
305    )]
306    fn check_proof_of_time(
307        pot_verifier: &PotVerifier,
308        block_authoring_delay: SlotNumber,
309        parent_slot: SlotNumber,
310        parent_proof_of_time: PotOutput,
311        parent_future_proof_of_time: PotOutput,
312        parent_consensus_parameters: &BlockHeaderConsensusParameters<'_>,
313        slot: SlotNumber,
314        proof_of_time: PotOutput,
315        future_proof_of_time: PotOutput,
316        checkpoints: &[PotCheckpoints],
317        verify_checkpoints: bool,
318    ) -> Result<(), BeaconChainBlockVerificationError> {
319        let parent_pot_parameters_change = parent_consensus_parameters
320            .pot_parameters_change
321            .copied()
322            .map(PotParametersChange::from);
323
324        // The last checkpoint must be the future proof of time
325        if checkpoints.last().map(PotCheckpoints::output) != Some(future_proof_of_time) {
326            return Err(BeaconChainBlockVerificationError::InvalidPotCheckpoints);
327        }
328
329        let future_slot = slot + block_authoring_delay;
330        let parent_future_slot = if parent_slot == SlotNumber::ZERO {
331            parent_slot
332        } else {
333            parent_slot + block_authoring_delay
334        };
335
336        let slots_between_blocks = slot
337            .checked_sub(parent_slot)
338            .ok_or(BeaconChainBlockVerificationError::InvalidPotCheckpoints)?;
339        // The number of checkpoints must match the difference between parent's and this block's
340        // future slots. This also implicitly checks that there is a non-zero number of slots
341        // between this and parent block because the list of checkpoints is already known to be not
342        // empty from the check above.
343        //
344        // The first block after genesis is a special case and is handled separately here.
345        if !(u64::from(slots_between_blocks) == checkpoints.len() as u64
346            || (parent_slot == SlotNumber::ZERO
347                && u64::from(future_slot) == checkpoints.len() as u64))
348        {
349            return Err(BeaconChainBlockVerificationError::InvalidPotCheckpoints);
350        }
351
352        let mut pot_input = if parent_slot == SlotNumber::ZERO {
353            PotNextSlotInput {
354                slot: parent_slot + SlotNumber::ONE,
355                slot_iterations: parent_consensus_parameters.fixed_parameters.slot_iterations,
356                seed: pot_verifier.genesis_seed(),
357            }
358        } else {
359            // Calculate slot iterations as of parent future slot
360            let slot_iterations = parent_pot_parameters_change
361                .and_then(|parameters_change| {
362                    (parameters_change.slot <= parent_future_slot)
363                        .then_some(parameters_change.slot_iterations)
364                })
365                .unwrap_or(parent_consensus_parameters.fixed_parameters.slot_iterations);
366            // Derive inputs to the slot, which follows the parent future slot
367            PotNextSlotInput::derive(
368                slot_iterations,
369                parent_future_slot,
370                parent_future_proof_of_time,
371                &parent_pot_parameters_change,
372            )
373        };
374
375        // Collect all the data we will use for verification so we can process it in parallel
376        let checkpoints_verification_input = iter::once((
377            pot_input,
378            *checkpoints
379                .first()
380                .expect("Not empty, contents was checked above; qed"),
381        ));
382        let checkpoints_verification_input = checkpoints_verification_input
383            .chain(checkpoints.array_windows::<2>().map(|[left, right]| {
384                pot_input = PotNextSlotInput::derive(
385                    pot_input.slot_iterations,
386                    pot_input.slot,
387                    left.output(),
388                    &parent_pot_parameters_change,
389                );
390
391                (pot_input, *right)
392            }))
393            // TODO: Would be nice to avoid extra allocation here
394            .collect::<Vec<_>>();
395
396        // All checkpoints must be valid, search for the first verification failure
397        let all_checkpoints_valid =
398            checkpoints_verification_input
399                .into_par_iter()
400                .all(|(pot_input, checkpoints)| {
401                    if verify_checkpoints {
402                        pot_verifier.verify_checkpoints(
403                            pot_input.seed,
404                            pot_input.slot_iterations,
405                            &checkpoints,
406                        )
407                    } else {
408                        // Store checkpoints as verified when verification is skipped
409                        pot_verifier.inject_verified_checkpoints(
410                            pot_input.seed,
411                            pot_input.slot_iterations,
412                            checkpoints,
413                        );
414                        true
415                    }
416                });
417
418        if !all_checkpoints_valid {
419            return Err(BeaconChainBlockVerificationError::InvalidPotCheckpoints);
420        }
421
422        // Make sure proof of time of this block correctly extends proof of time of the parent block
423        {
424            let pot_input = if parent_slot == SlotNumber::ZERO {
425                PotNextSlotInput {
426                    slot: parent_slot + SlotNumber::ONE,
427                    slot_iterations: parent_consensus_parameters.fixed_parameters.slot_iterations,
428                    seed: pot_verifier.genesis_seed(),
429                }
430            } else {
431                // Calculate slot iterations as of the parent slot
432                let slot_iterations = parent_pot_parameters_change
433                    .and_then(|parameters_change| {
434                        (parameters_change.slot <= parent_slot)
435                            .then_some(parameters_change.slot_iterations)
436                    })
437                    .unwrap_or(parent_consensus_parameters.fixed_parameters.slot_iterations);
438                // Derive inputs to the slot, which follows the parent slot
439                PotNextSlotInput::derive(
440                    slot_iterations,
441                    parent_slot,
442                    parent_proof_of_time,
443                    &parent_pot_parameters_change,
444                )
445            };
446
447            if !pot_verifier.is_output_valid(
448                pot_input,
449                slots_between_blocks,
450                proof_of_time,
451                parent_pot_parameters_change,
452            ) {
453                return Err(BeaconChainBlockVerificationError::InvalidProofOfTime);
454            }
455        }
456
457        Ok(())
458    }
459
460    fn check_body(
461        &self,
462        block_number: BlockNumber,
463        own_segments: Option<OwnSegments<'_>>,
464        _intermediate_shard_blocks: &IntermediateShardBlocksInfo<'_>,
465    ) -> Result<(), BlockVerificationError> {
466        let expected_segment_headers = self.chain_info.segment_headers_for_block(block_number);
467        let expected_first_local_segment_index = expected_segment_headers
468            .first()
469            .map(|segment_header| segment_header.index.as_inner());
470        let correct_first_local_segment_index = expected_first_local_segment_index
471            == own_segments
472                .as_ref()
473                .map(|own_segments| own_segments.first_local_segment_index);
474        let correct_segment_roots = expected_segment_headers
475            .iter()
476            .map(|segment_header| &segment_header.root)
477            .eq(own_segments
478                .as_ref()
479                .map(|own_segments| own_segments.segment_roots)
480                .unwrap_or_default());
481        if !(correct_first_local_segment_index && correct_segment_roots) {
482            return Err(BlockVerificationError::InvalidOwnSegments {
483                expected_first_local_segment_index,
484                expected_segment_roots: expected_segment_headers
485                    .iter()
486                    .map(|segment_header| segment_header.root)
487                    .collect(),
488                actual_first_local_segment_index: own_segments
489                    .as_ref()
490                    .map(|own_segments| own_segments.first_local_segment_index),
491                actual_segment_roots: own_segments
492                    .as_ref()
493                    .map(|own_segments| own_segments.segment_roots.to_vec())
494                    .unwrap_or_default(),
495            });
496        }
497
498        // TODO: check intermediate shard blocks and all segment roots included in the body
499
500        Ok(())
501    }
502
503    async fn verify_concurrent<BCI>(
504        &self,
505        parent_header: &BeaconChainHeader<'_>,
506        parent_block_mmr_root: &Blake3Hash,
507        header: &BeaconChainHeader<'_>,
508        body: &BeaconChainBody<'_>,
509        _origin: &BlockOrigin,
510        beacon_chain_info: &BCI,
511    ) -> Result<(), BlockVerificationError>
512    where
513        BCI: DeriveConsensusParametersChainInfo + ShardMembershipEntropySourceChainInfo,
514    {
515        trace!(header = ?header, "Verify concurrent");
516
517        let parent_block_root = parent_header.root();
518
519        let block_number = header.prefix.number;
520        let consensus_info = header.consensus_info;
521        let consensus_parameters = header.consensus_parameters();
522        let slot = consensus_info.slot;
523
524        let best_header = self.chain_info.best_header();
525        let best_header = best_header.header();
526        let best_number = best_header.prefix.number;
527
528        // Reject block below archiving point
529        if block_number + self.consensus_constants.block_confirmation_depth < best_number {
530            debug!(
531                ?header,
532                %best_number,
533                "Rejecting a block below the archiving point"
534            );
535
536            return Err(BlockVerificationError::BelowArchivingPoint);
537        }
538
539        self.check_header_prefix(parent_header.prefix, parent_block_mmr_root, header.prefix)?;
540
541        self.check_consensus_parameters_concurrent(
542            &parent_block_root,
543            parent_header,
544            header,
545            beacon_chain_info,
546        )?;
547
548        if !header.is_sealed_correctly() {
549            return Err(BlockVerificationError::InvalidSeal);
550        }
551
552        // Find shard membership entropy for the slot
553        let shard_membership_entropy = shard_membership_entropy_source(
554            header.prefix.number,
555            best_header,
556            self.consensus_constants.shard_rotation_interval,
557            self.consensus_constants.shard_rotation_delay,
558            beacon_chain_info,
559        )?;
560
561        // Verify that the solution is valid (stateless half)
562        consensus_info
563            .solution
564            .verify_stateless::<PosTable>(
565                slot,
566                &SolutionVerifyStatelessParams {
567                    shard_index: ShardIndex::BEACON_CHAIN,
568                    proof_of_time: consensus_info.proof_of_time,
569                    solution_range: consensus_parameters.fixed_parameters.solution_range,
570                    shard_membership_entropy,
571                    num_shards: consensus_parameters.fixed_parameters.num_shards,
572                },
573            )
574            .map_err(BeaconChainBlockVerificationError::from)?;
575
576        Self::check_proof_of_time(
577            &self.pot_verifier,
578            self.consensus_constants.block_authoring_delay,
579            parent_header.consensus_info.slot,
580            parent_header.consensus_info.proof_of_time,
581            parent_header.consensus_info.future_proof_of_time,
582            parent_header.consensus_parameters(),
583            consensus_info.slot,
584            consensus_info.proof_of_time,
585            consensus_info.future_proof_of_time,
586            body.pot_checkpoints(),
587            self.full_pot_verification(block_number),
588        )?;
589
590        // TODO: Do something about equivocation?
591
592        Ok(())
593    }
594
595    async fn verify_sequential(
596        &self,
597        parent_header: &BeaconChainHeader<'_>,
598        // TODO: Probably remove unused arguments
599        _parent_block_mmr_root: &Blake3Hash,
600        header: &BeaconChainHeader<'_>,
601        body: &BeaconChainBody<'_>,
602        _origin: &BlockOrigin,
603    ) -> Result<Option<SuperSegment>, BlockVerificationError> {
604        trace!(header = ?header, "Verify sequential");
605
606        let block_number = header.prefix.number;
607        let consensus_info = header.consensus_info;
608
609        let best_header = self.chain_info.best_header();
610        let best_header = best_header.header();
611        let best_number = best_header.prefix.number;
612
613        // Reject block below archiving point
614        if block_number + self.consensus_constants.block_confirmation_depth < best_number {
615            debug!(
616                ?header,
617                %best_number,
618                "Rejecting a block below the archiving point"
619            );
620
621            return Err(BlockVerificationError::BelowArchivingPoint);
622        }
623
624        let maybe_super_segment = derive_super_segments_for_block(
625            &self.chain_info,
626            parent_header.prefix.number,
627            self.consensus_constants.block_confirmation_depth,
628            self.consensus_constants.shard_confirmation_depth,
629        )
630        .map_err(BeaconChainBlockVerificationError::from)?;
631        let maybe_super_segment_root = maybe_super_segment
632            .as_ref()
633            .map(|super_segment| super_segment.header.root);
634
635        if maybe_super_segment_root.as_ref() != header.consensus_parameters().super_segment_root {
636            return Err(BlockVerificationError::from(
637                BeaconChainBlockVerificationError::InvalidSuperSegmentRoot {
638                    expected: Box::new(maybe_super_segment_root),
639                    actual: Box::new(header.consensus_parameters().super_segment_root.copied()),
640                },
641            ));
642        }
643
644        // Verify that the solution is valid (piece verification half)
645        {
646            let (current_history_size, solution_num_segments, solution_super_segment_root) =
647                if block_number == BlockNumber::ONE {
648                    let latest_super_segment = maybe_super_segment.as_ref().ok_or(
649                        BeaconChainBlockVerificationError::MissingSuperSegmentInFirstBlock,
650                    )?;
651
652                    (
653                        HistorySize::ONE,
654                        latest_super_segment.header.num_segments,
655                        latest_super_segment.header.root,
656                    )
657                } else {
658                    let max_segment_index = self
659                        .chain_info
660                        .previous_super_segment_header(block_number)
661                        .ok_or(
662                            BeaconChainBlockVerificationError::PreviousSuperSegmentHeaderNotFound,
663                        )?
664                        .max_segment_index
665                        .as_inner();
666                    let current_history_size = HistorySize::from(max_segment_index);
667
668                    let solution_super_segment_header = self
669                        .chain_info
670                        .get_super_segment_header(consensus_info.solution.piece_super_segment_index)
671                        .ok_or(
672                            BeaconChainBlockVerificationError::SolutionSuperSegmentNotFound {
673                                index: consensus_info.solution.piece_super_segment_index,
674                            },
675                        )?;
676
677                    (
678                        current_history_size,
679                        solution_super_segment_header.num_segments,
680                        solution_super_segment_header.root,
681                    )
682                };
683
684            let sector_expiration_check_super_segment_root = self
685                .chain_info
686                .get_super_segment_header_for_segment_index(
687                    consensus_info
688                        .solution
689                        .history_size
690                        .sector_expiration_check(self.consensus_constants.min_sector_lifetime)
691                        .ok_or(BeaconChainBlockVerificationError::InvalidHistorySize {
692                            history_size: consensus_info.solution.history_size,
693                            current_history_size,
694                        })?
695                        .segment_index(),
696                )
697                .map(|super_segment_header| super_segment_header.root);
698
699            consensus_info
700                .solution
701                .verify_piece(&SolutionVerifyPieceParams {
702                    // TODO: Query it from an actual chain
703                    max_pieces_in_sector: 1000,
704                    super_segment_root: solution_super_segment_root,
705                    num_segments: solution_num_segments,
706                    recent_segments: self.consensus_constants.recent_segments,
707                    recent_history_fraction: self.consensus_constants.recent_history_fraction,
708                    min_sector_lifetime: self.consensus_constants.min_sector_lifetime,
709                    current_history_size,
710                    sector_expiration_check_super_segment_root,
711                })
712                .map_err(BeaconChainBlockVerificationError::from)?;
713        }
714
715        self.check_body(
716            block_number,
717            body.own_segments(),
718            body.intermediate_shard_blocks(),
719        )?;
720
721        Ok(maybe_super_segment)
722    }
723}