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