Skip to main content

ab_client_block_verification/
beacon_chain.rs

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