1#![cfg_attr(not(feature = "std"), no_std)]
19
20pub mod config;
21pub mod functions;
22pub mod impls;
23pub mod types;
24pub mod weights;
25
26#[cfg(any(test, feature = "fuzzing"))]
27pub mod mock;
28
29#[cfg(test)]
30mod tests;
31
32#[cfg(feature = "runtime-benchmarks")]
33mod benchmarking;
34
35use frame_support::{
36 dispatch::{DispatchResult, PostDispatchInfo},
37 pallet_prelude::OptionQuery,
38 traits::Get,
39 transactional,
40};
41use frame_system::ensure_signed;
42use snowbridge_beacon_primitives::{
43 fast_aggregate_verify,
44 merkle_proof::{generalized_index_length, subtree_index},
45 verify_merkle_branch, BeaconHeader, BlsError, CommitmentError, CommitmentScheme,
46 CompactBeaconState, ExecutionCommitment, ForkData, ForkVersion, ForkVersions,
47 PublicKeyPrepared, SigningData, VersionedExecutionPayloadHeader,
48};
49use snowbridge_core::{BasicOperatingMode, RingBufferMap};
50use sp_core::H256;
51use sp_std::prelude::*;
52pub use weights::WeightInfo;
53
54use functions::{
55 compute_epoch, compute_period, decompress_sync_committee_bits, sync_committee_sum,
56};
57use types::{CheckpointUpdate, FinalizedBeaconStateBuffer, SyncCommitteePrepared, Update};
58
59pub use pallet::*;
60
61pub use config::SLOTS_PER_HISTORICAL_ROOT;
62
63pub const LOG_TARGET: &str = "ethereum-client";
64
65#[frame_support::pallet]
66pub mod pallet {
67 use super::*;
68
69 use frame_support::pallet_prelude::*;
70 use frame_system::pallet_prelude::*;
71
72 #[derive(scale_info::TypeInfo, codec::Encode, codec::Decode, codec::MaxEncodedLen)]
73 #[codec(mel_bound(T: Config))]
74 #[scale_info(skip_type_params(T))]
75 pub struct MaxFinalizedHeadersToKeep<T: Config>(PhantomData<T>);
76 impl<T: Config> Get<u32> for MaxFinalizedHeadersToKeep<T> {
77 fn get() -> u32 {
78 const MAX_REDUNDANCY: u32 = 20;
79 config::EPOCHS_PER_SYNC_COMMITTEE_PERIOD as u32 * MAX_REDUNDANCY
80 }
81 }
82
83 #[pallet::pallet]
84 pub struct Pallet<T>(_);
85
86 #[pallet::config]
87 pub trait Config: frame_system::Config {
88 #[allow(deprecated)]
89 type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
90 #[pallet::constant]
91 type ForkVersions: Get<ForkVersions>;
92 #[pallet::constant]
94 type FreeHeadersInterval: Get<u32>;
95 type WeightInfo: WeightInfo;
96 }
97
98 #[pallet::event]
99 #[pallet::generate_deposit(pub(super) fn deposit_event)]
100 pub enum Event<T: Config> {
101 BeaconHeaderImported {
102 block_hash: H256,
103 slot: u64,
104 },
105 SyncCommitteeUpdated {
106 period: u64,
107 },
108 OperatingModeChanged {
110 mode: BasicOperatingMode,
111 },
112 }
113
114 #[pallet::error]
115 pub enum Error<T> {
116 SkippedSyncCommitteePeriod,
117 SyncCommitteeUpdateRequired,
118 IrrelevantUpdate,
120 NotBootstrapped,
121 SyncCommitteeParticipantsNotSupermajority,
122 InvalidHeaderMerkleProof,
123 InvalidSyncCommitteeMerkleProof,
124 InvalidExecutionHeaderProof,
125 InvalidAncestryMerkleProof,
126 InvalidBlockRootsRootMerkleProof,
127 InvalidFinalizedHeaderGap,
131 HeaderNotFinalized,
132 BlockBodyHashTreeRootFailed,
133 HeaderHashTreeRootFailed,
134 SyncCommitteeHashTreeRootFailed,
135 SigningRootHashTreeRootFailed,
136 ForkDataHashTreeRootFailed,
137 ExpectedFinalizedHeaderNotStored,
138 BLSPreparePublicKeysFailed,
139 BLSVerificationFailed(BlsError),
140 InvalidUpdateSlot,
141 InvalidSyncCommitteeUpdate,
144 ExecutionHeaderTooFarBehind,
145 ExecutionHeaderSkippedBlock,
146 Halted,
147 MalformedExecutionHeader,
149 ExecutionHeaderEraMismatch,
151 }
152
153 #[pallet::storage]
155 #[pallet::getter(fn initial_checkpoint_root)]
156 pub type InitialCheckpointRoot<T: Config> = StorageValue<_, H256, ValueQuery>;
157
158 #[pallet::storage]
160 #[pallet::getter(fn latest_finalized_block_root)]
161 pub type LatestFinalizedBlockRoot<T: Config> = StorageValue<_, H256, ValueQuery>;
162
163 #[pallet::storage]
165 #[pallet::getter(fn finalized_beacon_state)]
166 pub type FinalizedBeaconState<T: Config> =
167 StorageMap<_, Identity, H256, CompactBeaconState, OptionQuery>;
168
169 #[pallet::storage]
171 pub type FinalizedBeaconStateIndex<T: Config> = StorageValue<_, u32, ValueQuery>;
172
173 #[pallet::storage]
175 pub type FinalizedBeaconStateMapping<T: Config> =
176 StorageMap<_, Identity, u32, H256, ValueQuery>;
177
178 #[pallet::storage]
179 #[pallet::getter(fn validators_root)]
180 pub type ValidatorsRoot<T: Config> = StorageValue<_, H256, ValueQuery>;
181
182 #[pallet::storage]
184 pub type CurrentSyncCommittee<T: Config> = StorageValue<_, SyncCommitteePrepared, ValueQuery>;
185
186 #[pallet::storage]
188 pub type NextSyncCommittee<T: Config> = StorageValue<_, SyncCommitteePrepared, ValueQuery>;
189
190 #[pallet::storage]
192 pub type LatestSyncCommitteeUpdatePeriod<T: Config> = StorageValue<_, u64, ValueQuery>;
193
194 #[pallet::storage]
196 #[pallet::getter(fn operating_mode)]
197 pub type OperatingMode<T: Config> = StorageValue<_, BasicOperatingMode, ValueQuery>;
198
199 #[pallet::call]
200 impl<T: Config> Pallet<T> {
201 #[pallet::call_index(0)]
202 #[pallet::weight(T::WeightInfo::force_checkpoint())]
203 #[transactional]
204 pub fn force_checkpoint(
207 origin: OriginFor<T>,
208 update: Box<CheckpointUpdate>,
209 ) -> DispatchResult {
210 ensure_root(origin)?;
211 Self::process_checkpoint_update(&update)?;
212 Ok(())
213 }
214
215 #[pallet::call_index(1)]
216 #[pallet::weight({
217 match update.next_sync_committee_update {
218 None => T::WeightInfo::submit(),
219 Some(_) => T::WeightInfo::submit_with_sync_committee(),
220 }
221 })]
222 #[transactional]
223 pub fn submit(origin: OriginFor<T>, update: Box<Update>) -> DispatchResultWithPostInfo {
226 ensure_signed(origin)?;
227 ensure!(!Self::operating_mode().is_halted(), Error::<T>::Halted);
228 Self::process_update(&update)
229 }
230
231 #[pallet::call_index(3)]
233 #[pallet::weight((T::DbWeight::get().reads_writes(1, 1), DispatchClass::Operational))]
234 pub fn set_operating_mode(
235 origin: OriginFor<T>,
236 mode: BasicOperatingMode,
237 ) -> DispatchResult {
238 ensure_root(origin)?;
239 OperatingMode::<T>::set(mode);
240 Self::deposit_event(Event::OperatingModeChanged { mode });
241 Ok(())
242 }
243 }
244
245 impl<T: Config> Pallet<T> {
246 pub(crate) fn process_checkpoint_update(update: &CheckpointUpdate) -> DispatchResult {
251 let sync_committee_root = update
252 .current_sync_committee
253 .hash_tree_root()
254 .map_err(|_| Error::<T>::SyncCommitteeHashTreeRootFailed)?;
255
256 let fork_versions = T::ForkVersions::get();
257 let sync_committee_gindex = Self::current_sync_committee_gindex_at_slot(
258 update.header.slot,
259 fork_versions.clone(),
260 );
261 ensure!(
263 verify_merkle_branch(
264 sync_committee_root,
265 &update.current_sync_committee_branch,
266 subtree_index(sync_committee_gindex),
267 generalized_index_length(sync_committee_gindex),
268 update.header.state_root
269 ),
270 Error::<T>::InvalidSyncCommitteeMerkleProof
271 );
272
273 let header_root: H256 = update
274 .header
275 .hash_tree_root()
276 .map_err(|_| Error::<T>::HeaderHashTreeRootFailed)?;
277
278 let block_roots_gindex =
282 Self::block_roots_gindex_at_slot(update.header.slot, fork_versions);
283 ensure!(
284 verify_merkle_branch(
285 update.block_roots_root,
286 &update.block_roots_branch,
287 subtree_index(block_roots_gindex),
288 generalized_index_length(block_roots_gindex),
289 update.header.state_root
290 ),
291 Error::<T>::InvalidBlockRootsRootMerkleProof
292 );
293
294 let sync_committee_prepared: SyncCommitteePrepared = (&update.current_sync_committee)
295 .try_into()
296 .map_err(|_| <Error<T>>::BLSPreparePublicKeysFailed)?;
297 <CurrentSyncCommittee<T>>::set(sync_committee_prepared);
298 <NextSyncCommittee<T>>::kill();
299 InitialCheckpointRoot::<T>::set(header_root);
300
301 Self::store_validators_root(update.validators_root);
302 Self::store_finalized_header(update.header, update.block_roots_root)?;
303
304 Ok(())
305 }
306
307 pub(crate) fn process_update(update: &Update) -> DispatchResultWithPostInfo {
308 Self::verify_update(update)?;
309 Self::apply_update(update)
310 }
311
312 fn verify_update(update: &Update) -> DispatchResult {
317 let participation =
319 decompress_sync_committee_bits(update.sync_aggregate.sync_committee_bits);
320 Self::sync_committee_participation_is_supermajority(&participation)?;
321
322 ensure!(
324 update.signature_slot > update.attested_header.slot &&
325 update.attested_header.slot >= update.finalized_header.slot,
326 Error::<T>::InvalidUpdateSlot
327 );
328 let latest_finalized_state =
330 FinalizedBeaconState::<T>::get(LatestFinalizedBlockRoot::<T>::get())
331 .ok_or(Error::<T>::NotBootstrapped)?;
332 let store_period = compute_period(latest_finalized_state.slot);
333 let signature_period = compute_period(update.signature_slot);
334 if <NextSyncCommittee<T>>::exists() {
335 ensure!(
336 (store_period..=store_period + 1).contains(&signature_period),
337 Error::<T>::SkippedSyncCommitteePeriod
338 )
339 } else {
340 ensure!(signature_period == store_period, Error::<T>::SkippedSyncCommitteePeriod)
341 }
342
343 let update_attested_period = compute_period(update.attested_header.slot);
345 let update_finalized_period = compute_period(update.finalized_header.slot);
346 let update_has_next_sync_committee = !<NextSyncCommittee<T>>::exists() &&
347 (update.next_sync_committee_update.is_some() &&
348 update_attested_period == store_period);
349 ensure!(
350 update.attested_header.slot > latest_finalized_state.slot ||
351 update_has_next_sync_committee,
352 Error::<T>::IrrelevantUpdate
353 );
354
355 ensure!(
359 latest_finalized_state
360 .slot
361 .saturating_add(config::SLOTS_PER_HISTORICAL_ROOT as u64) >=
362 update.finalized_header.slot,
363 Error::<T>::InvalidFinalizedHeaderGap
364 );
365
366 let fork_versions = T::ForkVersions::get();
367 let finalized_root_gindex = Self::finalized_root_gindex_at_slot(
368 update.attested_header.slot,
369 fork_versions.clone(),
370 );
371 let finalized_block_root: H256 = update
374 .finalized_header
375 .hash_tree_root()
376 .map_err(|_| Error::<T>::HeaderHashTreeRootFailed)?;
377 ensure!(
378 verify_merkle_branch(
379 finalized_block_root,
380 &update.finality_branch,
381 subtree_index(finalized_root_gindex),
382 generalized_index_length(finalized_root_gindex),
383 update.attested_header.state_root
384 ),
385 Error::<T>::InvalidHeaderMerkleProof
386 );
387
388 let block_roots_gindex = Self::block_roots_gindex_at_slot(
392 update.finalized_header.slot,
393 fork_versions.clone(),
394 );
395 ensure!(
396 verify_merkle_branch(
397 update.block_roots_root,
398 &update.block_roots_branch,
399 subtree_index(block_roots_gindex),
400 generalized_index_length(block_roots_gindex),
401 update.finalized_header.state_root
402 ),
403 Error::<T>::InvalidBlockRootsRootMerkleProof
404 );
405
406 if let Some(next_sync_committee_update) = &update.next_sync_committee_update {
409 let sync_committee_root = next_sync_committee_update
410 .next_sync_committee
411 .hash_tree_root()
412 .map_err(|_| Error::<T>::SyncCommitteeHashTreeRootFailed)?;
413 if update_attested_period == store_period && <NextSyncCommittee<T>>::exists() {
414 let next_committee_root = <NextSyncCommittee<T>>::get().root;
415 ensure!(
416 sync_committee_root == next_committee_root,
417 Error::<T>::InvalidSyncCommitteeUpdate
418 );
419 }
420 let next_sync_committee_gindex = Self::next_sync_committee_gindex_at_slot(
421 update.attested_header.slot,
422 fork_versions,
423 );
424 ensure!(
425 verify_merkle_branch(
426 sync_committee_root,
427 &next_sync_committee_update.next_sync_committee_branch,
428 subtree_index(next_sync_committee_gindex),
429 generalized_index_length(next_sync_committee_gindex),
430 update.attested_header.state_root
431 ),
432 Error::<T>::InvalidSyncCommitteeMerkleProof
433 );
434 } else {
435 ensure!(
436 update_finalized_period == store_period,
437 Error::<T>::SyncCommitteeUpdateRequired
438 );
439 }
440
441 let sync_committee = if signature_period == store_period {
443 <CurrentSyncCommittee<T>>::get()
444 } else {
445 <NextSyncCommittee<T>>::get()
446 };
447 let absent_pubkeys =
448 Self::find_pubkeys(&participation, (*sync_committee.pubkeys).as_ref(), false);
449 let signing_root = Self::signing_root(
450 &update.attested_header,
451 Self::validators_root(),
452 update.signature_slot,
453 )?;
454 fast_aggregate_verify(
458 &sync_committee.aggregate_pubkey,
459 &absent_pubkeys,
460 signing_root,
461 &update.sync_aggregate.sync_committee_signature,
462 )
463 .map_err(|e| Error::<T>::BLSVerificationFailed(e))?;
464
465 Ok(())
466 }
467
468 fn apply_update(update: &Update) -> DispatchResultWithPostInfo {
474 let latest_finalized_state =
475 FinalizedBeaconState::<T>::get(LatestFinalizedBlockRoot::<T>::get())
476 .ok_or(Error::<T>::NotBootstrapped)?;
477
478 let pays_fee = Self::check_refundable(update, latest_finalized_state.slot);
479 let actual_weight = match update.next_sync_committee_update {
480 None => T::WeightInfo::submit(),
481 Some(_) => T::WeightInfo::submit_with_sync_committee(),
482 };
483
484 if let Some(next_sync_committee_update) = &update.next_sync_committee_update {
485 let store_period = compute_period(latest_finalized_state.slot);
486 let update_finalized_period = compute_period(update.finalized_header.slot);
487 let sync_committee_prepared: SyncCommitteePrepared = (&next_sync_committee_update
488 .next_sync_committee)
489 .try_into()
490 .map_err(|_| <Error<T>>::BLSPreparePublicKeysFailed)?;
491
492 if !<NextSyncCommittee<T>>::exists() {
493 ensure!(
494 update_finalized_period == store_period,
495 <Error<T>>::InvalidSyncCommitteeUpdate
496 );
497 <NextSyncCommittee<T>>::set(sync_committee_prepared);
498 } else if update_finalized_period == store_period + 1 {
499 <CurrentSyncCommittee<T>>::set(<NextSyncCommittee<T>>::get());
500 <NextSyncCommittee<T>>::set(sync_committee_prepared);
501 }
502 tracing::info!(
503 target: LOG_TARGET,
504 period=%update_finalized_period,
505 "💫 SyncCommitteeUpdated."
506 );
507 <LatestSyncCommitteeUpdatePeriod<T>>::set(update_finalized_period);
508 Self::deposit_event(Event::SyncCommitteeUpdated {
509 period: update_finalized_period,
510 });
511 };
512
513 if update.finalized_header.slot > latest_finalized_state.slot {
514 Self::store_finalized_header(update.finalized_header, update.block_roots_root)?;
515 }
516
517 Ok(PostDispatchInfo { actual_weight: Some(actual_weight), pays_fee })
518 }
519
520 pub(super) fn compute_signing_root(
524 beacon_header: &BeaconHeader,
525 domain: H256,
526 ) -> Result<H256, DispatchError> {
527 let beacon_header_root = beacon_header
528 .hash_tree_root()
529 .map_err(|_| Error::<T>::HeaderHashTreeRootFailed)?;
530
531 let hash_root = SigningData { object_root: beacon_header_root, domain }
532 .hash_tree_root()
533 .map_err(|_| Error::<T>::SigningRootHashTreeRootFailed)?;
534
535 Ok(hash_root)
536 }
537
538 pub fn store_finalized_header(
542 header: BeaconHeader,
543 block_roots_root: H256,
544 ) -> DispatchResult {
545 let slot = header.slot;
546
547 let header_root: H256 =
548 header.hash_tree_root().map_err(|_| Error::<T>::HeaderHashTreeRootFailed)?;
549
550 <FinalizedBeaconStateBuffer<T>>::insert(
551 header_root,
552 CompactBeaconState { slot: header.slot, block_roots_root },
553 );
554 <LatestFinalizedBlockRoot<T>>::set(header_root);
555
556 tracing::info!(
557 target: LOG_TARGET,
558 root=%header_root,
559 %slot,
560 "💫 Updated latest finalized block."
561 );
562
563 Self::deposit_event(Event::BeaconHeaderImported { block_hash: header_root, slot });
564
565 Ok(())
566 }
567
568 fn store_validators_root(validators_root: H256) {
573 <ValidatorsRoot<T>>::set(validators_root);
574 }
575
576 pub(super) fn compute_domain(
582 domain_type: Vec<u8>,
583 fork_version: ForkVersion,
584 genesis_validators_root: H256,
585 ) -> Result<H256, DispatchError> {
586 let fork_data_root =
587 Self::compute_fork_data_root(fork_version, genesis_validators_root)?;
588
589 let mut domain = [0u8; 32];
590 domain[0..4].copy_from_slice(&(domain_type));
591 domain[4..32].copy_from_slice(&(fork_data_root.0[..28]));
592
593 Ok(domain.into())
594 }
595
596 fn compute_fork_data_root(
599 current_version: ForkVersion,
600 genesis_validators_root: H256,
601 ) -> Result<H256, DispatchError> {
602 let hash_root = ForkData {
603 current_version,
604 genesis_validators_root: genesis_validators_root.into(),
605 }
606 .hash_tree_root()
607 .map_err(|_| Error::<T>::ForkDataHashTreeRootFailed)?;
608
609 Ok(hash_root)
610 }
611
612 pub(super) fn sync_committee_participation_is_supermajority(
616 sync_committee_bits: &[u8],
617 ) -> DispatchResult {
618 let sync_committee_sum = sync_committee_sum(sync_committee_bits);
619 ensure!(
620 ((sync_committee_sum * 3) as usize) >= sync_committee_bits.len() * 2,
621 Error::<T>::SyncCommitteeParticipantsNotSupermajority
622 );
623
624 Ok(())
625 }
626
627 pub(super) fn compute_fork_version(epoch: u64) -> ForkVersion {
630 Self::select_fork_version(&T::ForkVersions::get(), epoch)
631 }
632
633 pub(super) fn select_fork_version(fork_versions: &ForkVersions, epoch: u64) -> ForkVersion {
635 if epoch >= fork_versions.gloas.epoch {
636 return fork_versions.gloas.version;
637 }
638 if epoch >= fork_versions.fulu.epoch {
639 return fork_versions.fulu.version;
640 }
641 if epoch >= fork_versions.electra.epoch {
642 return fork_versions.electra.version;
643 }
644 if epoch >= fork_versions.deneb.epoch {
645 return fork_versions.deneb.version;
646 }
647 if epoch >= fork_versions.capella.epoch {
648 return fork_versions.capella.version;
649 }
650 if epoch >= fork_versions.bellatrix.epoch {
651 return fork_versions.bellatrix.version;
652 }
653 if epoch >= fork_versions.altair.epoch {
654 return fork_versions.altair.version;
655 }
656 fork_versions.genesis.version
657 }
658
659 pub fn find_pubkeys(
666 sync_committee_bits: &[u8],
667 sync_committee_pubkeys: &[PublicKeyPrepared],
668 participant: bool,
669 ) -> Vec<PublicKeyPrepared> {
670 let mut pubkeys: Vec<PublicKeyPrepared> = Vec::new();
671 for (bit, pubkey) in sync_committee_bits.iter().zip(sync_committee_pubkeys.iter()) {
672 if *bit == u8::from(participant) {
673 pubkeys.push(pubkey.clone());
674 }
675 }
676 pubkeys
677 }
678
679 pub fn signing_root(
682 header: &BeaconHeader,
683 validators_root: H256,
684 signature_slot: u64,
685 ) -> Result<H256, DispatchError> {
686 let fork_version = Self::compute_fork_version(compute_epoch(
690 signature_slot.saturating_sub(1),
691 config::SLOTS_PER_EPOCH as u64,
692 ));
693 let domain_type = config::DOMAIN_SYNC_COMMITTEE.to_vec();
694 let domain = Self::compute_domain(domain_type, fork_version, validators_root)?;
696 let signing_root = Self::compute_signing_root(header, domain)?;
698 Ok(signing_root)
699 }
700
701 pub(super) fn check_refundable(update: &Update, latest_slot: u64) -> Pays {
705 let update_period = compute_period(update.finalized_header.slot);
707 let latest_free_update_period = LatestSyncCommitteeUpdatePeriod::<T>::get();
708 let refundable =
712 !<NextSyncCommittee<T>>::exists() || update_period > latest_free_update_period;
713 if update.next_sync_committee_update.is_some() && refundable {
714 return Pays::No;
715 }
716
717 if update.finalized_header.slot >=
720 latest_slot.saturating_add(T::FreeHeadersInterval::get() as u64)
721 {
722 return Pays::No;
723 }
724
725 Pays::Yes
726 }
727
728 pub fn finalized_root_gindex_at_slot(slot: u64, fork_versions: ForkVersions) -> usize {
729 let epoch = compute_epoch(slot, config::SLOTS_PER_EPOCH as u64);
730
731 if epoch >= fork_versions.gloas.epoch {
732 return config::gloas::FINALIZED_ROOT_INDEX;
733 }
734
735 if epoch >= fork_versions.electra.epoch {
736 return config::electra::FINALIZED_ROOT_INDEX;
737 }
738
739 config::altair::FINALIZED_ROOT_INDEX
740 }
741
742 pub fn current_sync_committee_gindex_at_slot(
743 slot: u64,
744 fork_versions: ForkVersions,
745 ) -> usize {
746 let epoch = compute_epoch(slot, config::SLOTS_PER_EPOCH as u64);
747
748 if epoch >= fork_versions.gloas.epoch {
749 return config::gloas::CURRENT_SYNC_COMMITTEE_INDEX;
750 }
751
752 if epoch >= fork_versions.electra.epoch {
753 return config::electra::CURRENT_SYNC_COMMITTEE_INDEX;
754 }
755
756 config::altair::CURRENT_SYNC_COMMITTEE_INDEX
757 }
758
759 pub fn next_sync_committee_gindex_at_slot(slot: u64, fork_versions: ForkVersions) -> usize {
760 let epoch = compute_epoch(slot, config::SLOTS_PER_EPOCH as u64);
761
762 if epoch >= fork_versions.gloas.epoch {
763 return config::gloas::NEXT_SYNC_COMMITTEE_INDEX;
764 }
765
766 if epoch >= fork_versions.electra.epoch {
767 return config::electra::NEXT_SYNC_COMMITTEE_INDEX;
768 }
769
770 config::altair::NEXT_SYNC_COMMITTEE_INDEX
771 }
772
773 pub fn block_roots_gindex_at_slot(slot: u64, fork_versions: ForkVersions) -> usize {
774 let epoch = compute_epoch(slot, config::SLOTS_PER_EPOCH as u64);
775
776 if epoch >= fork_versions.gloas.epoch {
777 return config::gloas::BLOCK_ROOTS_INDEX;
778 }
779
780 if epoch >= fork_versions.electra.epoch {
781 return config::electra::BLOCK_ROOTS_INDEX;
782 }
783
784 config::altair::BLOCK_ROOTS_INDEX
785 }
786
787 pub fn commitment_scheme_at_slot(
789 slot: u64,
790 fork_versions: ForkVersions,
791 ) -> CommitmentScheme {
792 let epoch = compute_epoch(slot, config::SLOTS_PER_EPOCH as u64);
793
794 if epoch >= fork_versions.gloas.epoch {
795 return CommitmentScheme::BlockHash;
796 }
797
798 CommitmentScheme::PayloadHeaderRoot
799 }
800
801 pub fn execution_commitment_gindex_at_slot(
803 slot: u64,
804 fork_versions: ForkVersions,
805 ) -> usize {
806 let epoch = compute_epoch(slot, config::SLOTS_PER_EPOCH as u64);
807
808 if epoch >= fork_versions.gloas.epoch {
809 return config::gloas::EXECUTION_BLOCK_HASH_INDEX;
810 }
811
812 config::altair::EXECUTION_HEADER_INDEX
813 }
814
815 pub fn execution_commitment_at_slot(
817 header: &VersionedExecutionPayloadHeader,
818 slot: u64,
819 fork_versions: ForkVersions,
820 ) -> Result<(ExecutionCommitment, usize), CommitmentError> {
821 let scheme = Self::commitment_scheme_at_slot(slot, fork_versions.clone());
822 let gindex = Self::execution_commitment_gindex_at_slot(slot, fork_versions);
823
824 Ok((header.commitment(scheme)?, gindex))
825 }
826 }
827}