Adds Snowbridge to Rococo runtime (#2522)

# Description

Adds Snowbridge to the Rococo bridge hub runtime. Includes config
changes required in Rococo asset hub.

---------

Co-authored-by: Alistair Singh <alistair.singh7@gmail.com>
Co-authored-by: ron <yrong1997@gmail.com>
Co-authored-by: Vincent Geddes <vincent.geddes@hey.com>
Co-authored-by: claravanstaden <Cats 4 life!>
This commit is contained in:
Clara van Staden
2023-12-21 18:06:36 +02:00
committed by GitHub
parent 9f5221cc2f
commit 18d53dbf91
151 changed files with 19379 additions and 149 deletions
@@ -0,0 +1,156 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2023 Snowfork <hello@snowfork.com>
use super::*;
mod fixtures;
mod util;
use crate::Pallet as EthereumBeaconClient;
use frame_benchmarking::v2::*;
use frame_system::RawOrigin;
use fixtures::{
make_checkpoint, make_execution_header_update, make_finalized_header_update,
make_sync_committee_update,
};
use primitives::{
fast_aggregate_verify, prepare_aggregate_pubkey, prepare_aggregate_signature,
verify_merkle_branch,
};
use util::*;
#[benchmarks]
mod benchmarks {
use super::*;
#[benchmark]
fn force_checkpoint() -> Result<(), BenchmarkError> {
let checkpoint_update = make_checkpoint();
let block_root: H256 = checkpoint_update.header.hash_tree_root().unwrap();
#[extrinsic_call]
_(RawOrigin::Root, Box::new(*checkpoint_update));
assert!(<LatestFinalizedBlockRoot<T>>::get() == block_root);
assert!(<FinalizedBeaconState<T>>::get(block_root).is_some());
Ok(())
}
#[benchmark]
fn submit() -> Result<(), BenchmarkError> {
let caller: T::AccountId = whitelisted_caller();
let checkpoint_update = make_checkpoint();
let finalized_header_update = make_finalized_header_update();
let block_root: H256 = finalized_header_update.finalized_header.hash_tree_root().unwrap();
EthereumBeaconClient::<T>::process_checkpoint_update(&checkpoint_update)?;
#[extrinsic_call]
submit(RawOrigin::Signed(caller.clone()), Box::new(*finalized_header_update));
assert!(<LatestFinalizedBlockRoot<T>>::get() == block_root);
assert!(<FinalizedBeaconState<T>>::get(block_root).is_some());
Ok(())
}
#[benchmark]
fn submit_with_sync_committee() -> Result<(), BenchmarkError> {
let caller: T::AccountId = whitelisted_caller();
let checkpoint_update = make_checkpoint();
let sync_committee_update = make_sync_committee_update();
EthereumBeaconClient::<T>::process_checkpoint_update(&checkpoint_update)?;
#[extrinsic_call]
submit(RawOrigin::Signed(caller.clone()), Box::new(*sync_committee_update));
assert!(<NextSyncCommittee<T>>::exists());
Ok(())
}
#[benchmark]
fn submit_execution_header() -> Result<(), BenchmarkError> {
let caller: T::AccountId = whitelisted_caller();
let checkpoint_update = make_checkpoint();
let finalized_header_update = make_finalized_header_update();
let execution_header_update = make_execution_header_update();
let execution_header_hash = execution_header_update.execution_header.block_hash;
EthereumBeaconClient::<T>::process_checkpoint_update(&checkpoint_update)?;
EthereumBeaconClient::<T>::process_update(&finalized_header_update)?;
#[extrinsic_call]
_(RawOrigin::Signed(caller.clone()), Box::new(*execution_header_update));
assert!(<ExecutionHeaders<T>>::contains_key(execution_header_hash));
Ok(())
}
#[benchmark(extra)]
fn bls_fast_aggregate_verify_pre_aggregated() -> Result<(), BenchmarkError> {
EthereumBeaconClient::<T>::process_checkpoint_update(&make_checkpoint())?;
let update = make_sync_committee_update();
let participant_pubkeys = participant_pubkeys::<T>(&update)?;
let signing_root = signing_root::<T>(&update)?;
let agg_sig =
prepare_aggregate_signature(&update.sync_aggregate.sync_committee_signature).unwrap();
let agg_pub_key = prepare_aggregate_pubkey(&participant_pubkeys).unwrap();
#[block]
{
agg_sig.fast_aggregate_verify_pre_aggregated(signing_root.as_bytes(), &agg_pub_key);
}
Ok(())
}
#[benchmark(extra)]
fn bls_fast_aggregate_verify() -> Result<(), BenchmarkError> {
EthereumBeaconClient::<T>::process_checkpoint_update(&make_checkpoint())?;
let update = make_sync_committee_update();
let current_sync_committee = <CurrentSyncCommittee<T>>::get();
let absent_pubkeys = absent_pubkeys::<T>(&update)?;
let signing_root = signing_root::<T>(&update)?;
#[block]
{
fast_aggregate_verify(
&current_sync_committee.aggregate_pubkey,
&absent_pubkeys,
signing_root,
&update.sync_aggregate.sync_committee_signature,
)
.unwrap();
}
Ok(())
}
#[benchmark(extra)]
fn verify_merkle_proof() -> Result<(), BenchmarkError> {
EthereumBeaconClient::<T>::process_checkpoint_update(&make_checkpoint())?;
let update = make_sync_committee_update();
let block_root: H256 = update.finalized_header.hash_tree_root().unwrap();
#[block]
{
verify_merkle_branch(
block_root,
&update.finality_branch,
config::FINALIZED_ROOT_SUBTREE_INDEX,
config::FINALIZED_ROOT_DEPTH,
update.attested_header.state_root,
);
}
Ok(())
}
impl_benchmark_test_suite!(
EthereumBeaconClient,
crate::mock::mainnet::new_tester(),
crate::mock::mainnet::Test
);
}
@@ -0,0 +1,44 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2023 Snowfork <hello@snowfork.com>
use crate::{
decompress_sync_committee_bits, Config, CurrentSyncCommittee, Pallet as EthereumBeaconClient,
Update, ValidatorsRoot, Vec,
};
use primitives::PublicKeyPrepared;
use sp_core::H256;
pub fn participant_pubkeys<T: Config>(
update: &Update,
) -> Result<Vec<PublicKeyPrepared>, &'static str> {
let sync_committee_bits =
decompress_sync_committee_bits(update.sync_aggregate.sync_committee_bits);
let current_sync_committee = <CurrentSyncCommittee<T>>::get();
let pubkeys = EthereumBeaconClient::<T>::find_pubkeys(
&sync_committee_bits,
(*current_sync_committee.pubkeys).as_ref(),
true,
);
Ok(pubkeys)
}
pub fn absent_pubkeys<T: Config>(update: &Update) -> Result<Vec<PublicKeyPrepared>, &'static str> {
let sync_committee_bits =
decompress_sync_committee_bits(update.sync_aggregate.sync_committee_bits);
let current_sync_committee = <CurrentSyncCommittee<T>>::get();
let pubkeys = EthereumBeaconClient::<T>::find_pubkeys(
&sync_committee_bits,
(*current_sync_committee.pubkeys).as_ref(),
false,
);
Ok(pubkeys)
}
pub fn signing_root<T: Config>(update: &Update) -> Result<H256, &'static str> {
let validators_root = <ValidatorsRoot<T>>::get();
let signing_root = EthereumBeaconClient::<T>::signing_root(
&update.attested_header,
validators_root,
update.signature_slot,
)?;
Ok(signing_root)
}
@@ -0,0 +1,10 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2023 Snowfork <hello@snowfork.com>
pub const SLOTS_PER_EPOCH: usize = 32;
pub const SECONDS_PER_SLOT: usize = 12;
pub const EPOCHS_PER_SYNC_COMMITTEE_PERIOD: usize = 256;
pub const SYNC_COMMITTEE_SIZE: usize = 512;
pub const SYNC_COMMITTEE_BITS_SIZE: usize = SYNC_COMMITTEE_SIZE / 8;
pub const SLOTS_PER_HISTORICAL_ROOT: usize = 8192;
pub const IS_MINIMAL: bool = false;
pub const BLOCK_ROOT_AT_INDEX_DEPTH: usize = 13;
@@ -0,0 +1,10 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2023 Snowfork <hello@snowfork.com>
pub const SLOTS_PER_EPOCH: usize = 8;
pub const SECONDS_PER_SLOT: usize = 6;
pub const EPOCHS_PER_SYNC_COMMITTEE_PERIOD: usize = 8;
pub const SYNC_COMMITTEE_SIZE: usize = 32;
pub const SYNC_COMMITTEE_BITS_SIZE: usize = SYNC_COMMITTEE_SIZE / 8;
pub const SLOTS_PER_HISTORICAL_ROOT: usize = 64;
pub const IS_MINIMAL: bool = true;
pub const BLOCK_ROOT_AT_INDEX_DEPTH: usize = 6;
@@ -0,0 +1,56 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2023 Snowfork <hello@snowfork.com>
use primitives::merkle_proof::{generalized_index_length, subtree_index};
use static_assertions::const_assert;
pub mod mainnet;
pub mod minimal;
#[cfg(not(feature = "beacon-spec-mainnet"))]
pub use minimal::*;
#[cfg(feature = "beacon-spec-mainnet")]
pub use mainnet::*;
// Generalized Indices
// get_generalized_index(BeaconState, 'block_roots')
pub const BLOCK_ROOTS_INDEX: usize = 37;
pub const BLOCK_ROOTS_SUBTREE_INDEX: usize = subtree_index(BLOCK_ROOTS_INDEX);
pub const BLOCK_ROOTS_DEPTH: usize = generalized_index_length(BLOCK_ROOTS_INDEX);
// get_generalized_index(BeaconState, 'finalized_checkpoint', 'root')
pub const FINALIZED_ROOT_INDEX: usize = 105;
pub const FINALIZED_ROOT_SUBTREE_INDEX: usize = subtree_index(FINALIZED_ROOT_INDEX);
pub const FINALIZED_ROOT_DEPTH: usize = generalized_index_length(FINALIZED_ROOT_INDEX);
// get_generalized_index(BeaconState, 'current_sync_committee')
pub const CURRENT_SYNC_COMMITTEE_INDEX: usize = 54;
pub const CURRENT_SYNC_COMMITTEE_SUBTREE_INDEX: usize = subtree_index(CURRENT_SYNC_COMMITTEE_INDEX);
pub const CURRENT_SYNC_COMMITTEE_DEPTH: usize =
generalized_index_length(CURRENT_SYNC_COMMITTEE_INDEX);
// get_generalized_index(BeaconState, 'next_sync_committee')
pub const NEXT_SYNC_COMMITTEE_INDEX: usize = 55;
pub const NEXT_SYNC_COMMITTEE_SUBTREE_INDEX: usize = subtree_index(NEXT_SYNC_COMMITTEE_INDEX);
pub const NEXT_SYNC_COMMITTEE_DEPTH: usize = generalized_index_length(NEXT_SYNC_COMMITTEE_INDEX);
// get_generalized_index(BeaconBlockBody, 'execution_payload')
pub const EXECUTION_HEADER_INDEX: usize = 25;
pub const EXECUTION_HEADER_SUBTREE_INDEX: usize = subtree_index(EXECUTION_HEADER_INDEX);
pub const EXECUTION_HEADER_DEPTH: usize = generalized_index_length(EXECUTION_HEADER_INDEX);
pub const MAX_EXTRA_DATA_BYTES: usize = 32;
pub const MAX_LOGS_BLOOM_SIZE: usize = 256;
pub const MAX_FEE_RECIPIENT_SIZE: usize = 20;
pub const MAX_BRANCH_PROOF_SIZE: usize = 20;
/// DomainType('0x07000000')
/// <https://github.com/ethereum/consensus-specs/blob/dev/specs/altair/beacon-chain.md#domain-types>
pub const DOMAIN_SYNC_COMMITTEE: [u8; 4] = [7, 0, 0, 0];
pub const PUBKEY_SIZE: usize = 48;
pub const SIGNATURE_SIZE: usize = 96;
const_assert!(SYNC_COMMITTEE_BITS_SIZE == SYNC_COMMITTEE_SIZE / 8);
@@ -0,0 +1,31 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2023 Snowfork <hello@snowfork.com>
use crate::config::{
EPOCHS_PER_SYNC_COMMITTEE_PERIOD, SLOTS_PER_EPOCH, SYNC_COMMITTEE_BITS_SIZE,
SYNC_COMMITTEE_SIZE,
};
/// Decompress packed bitvector into byte vector according to SSZ deserialization rules. Each byte
/// in the decompressed vector is either 0 or 1.
pub fn decompress_sync_committee_bits(
input: [u8; SYNC_COMMITTEE_BITS_SIZE],
) -> [u8; SYNC_COMMITTEE_SIZE] {
primitives::decompress_sync_committee_bits::<SYNC_COMMITTEE_SIZE, SYNC_COMMITTEE_BITS_SIZE>(
input,
)
}
/// Compute the sync committee period in which a slot is contained.
pub fn compute_period(slot: u64) -> u64 {
slot / SLOTS_PER_EPOCH as u64 / EPOCHS_PER_SYNC_COMMITTEE_PERIOD as u64
}
/// Compute epoch in which a slot is contained.
pub fn compute_epoch(slot: u64, slots_per_epoch: u64) -> u64 {
slot / slots_per_epoch
}
/// Sums the bit vector of sync committee participation.
pub fn sync_committee_sum(sync_committee_bits: &[u8]) -> u32 {
sync_committee_bits.iter().fold(0, |acc: u32, x| acc + *x as u32)
}
@@ -0,0 +1,93 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2023 Snowfork <hello@snowfork.com>
use super::*;
use snowbridge_core::inbound::{
VerificationError::{self, *},
*,
};
use snowbridge_ethereum::Receipt;
impl<T: Config> Verifier for Pallet<T> {
/// Verify a message by verifying the existence of the corresponding
/// Ethereum log in a block. Returns the log if successful. The execution header containing
/// the log should be in the beacon client storage, meaning it has been verified and is an
/// ancestor of a finalized beacon block.
fn verify(event_log: &Log, proof: &Proof) -> Result<(), VerificationError> {
log::info!(
target: "ethereum-beacon-client",
"💫 Verifying message with block hash {}",
proof.block_hash,
);
let header = <ExecutionHeaderBuffer<T>>::get(proof.block_hash).ok_or(HeaderNotFound)?;
let receipt = match Self::verify_receipt_inclusion(header.receipts_root, proof) {
Ok(receipt) => receipt,
Err(err) => {
log::error!(
target: "ethereum-beacon-client",
"💫 Verification of receipt inclusion failed for block {}: {:?}",
proof.block_hash,
err
);
return Err(err)
},
};
log::trace!(
target: "ethereum-beacon-client",
"💫 Verified receipt inclusion for transaction at index {} in block {}",
proof.tx_index, proof.block_hash,
);
event_log.validate().map_err(|_| InvalidLog)?;
// Convert snowbridge_core::inbound::Log to snowbridge_ethereum::Log.
let event_log = snowbridge_ethereum::Log {
address: event_log.address,
topics: event_log.topics.clone(),
data: event_log.data.clone(),
};
if !receipt.contains_log(&event_log) {
log::error!(
target: "ethereum-beacon-client",
"💫 Event log not found in receipt for transaction at index {} in block {}",
proof.tx_index, proof.block_hash,
);
return Err(LogNotFound)
}
log::info!(
target: "ethereum-beacon-client",
"💫 Receipt verification successful for {}",
proof.block_hash,
);
Ok(())
}
}
impl<T: Config> Pallet<T> {
/// Verifies that the receipt encoded in `proof.data` is included in the block given by
/// `proof.block_hash`.
pub fn verify_receipt_inclusion(
receipts_root: H256,
proof: &Proof,
) -> Result<Receipt, VerificationError> {
let result = verify_receipt_proof(receipts_root, &proof.data.1).ok_or(InvalidProof)?;
match result {
Ok(receipt) => Ok(receipt),
Err(err) => {
log::trace!(
target: "ethereum-beacon-client",
"💫 Failed to decode transaction receipt: {}",
err
);
Err(InvalidProof)
},
}
}
}
@@ -0,0 +1,841 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2023 Snowfork <hello@snowfork.com>
//! Ethereum Beacon Client
//!
//! A light client that verifies consensus updates signed by the sync committee of the beacon chain.
//!
//! # Extrinsics
//!
//! ## Governance
//!
//! * [`Call::force_checkpoint`]: Set the initial trusted consensus checkpoint.
//! * [`Call::set_operating_mode`]: Set the operating mode of the pallet. Can be used to disable
//! processing of conensus updates.
//!
//! ## Consensus Updates
//!
//! * [`Call::submit`]: Submit a finalized beacon header with an optional sync committee update
//! * [`Call::submit_execution_header`]: Submit an execution header together with an ancestry proof
//! that can be verified against an already imported finalized beacon header.
#![cfg_attr(not(feature = "std"), no_std)]
pub mod config;
pub mod functions;
pub mod impls;
pub mod types;
pub mod weights;
#[cfg(any(test, feature = "fuzzing"))]
pub mod mock;
#[cfg(all(test, not(feature = "beacon-spec-mainnet")))]
mod tests;
#[cfg(feature = "runtime-benchmarks")]
mod benchmarking;
use frame_support::{
dispatch::DispatchResult, pallet_prelude::OptionQuery, traits::Get, transactional,
};
use frame_system::ensure_signed;
use primitives::{
fast_aggregate_verify, verify_merkle_branch, verify_receipt_proof, BeaconHeader, BlsError,
CompactBeaconState, CompactExecutionHeader, ExecutionHeaderState, ForkData, ForkVersion,
ForkVersions, PublicKeyPrepared, SigningData,
};
use snowbridge_core::{BasicOperatingMode, RingBufferMap};
use sp_core::H256;
use sp_std::prelude::*;
pub use weights::WeightInfo;
use functions::{
compute_epoch, compute_period, decompress_sync_committee_bits, sync_committee_sum,
};
pub use types::ExecutionHeaderBuffer;
use types::{
CheckpointUpdate, ExecutionHeaderUpdate, FinalizedBeaconStateBuffer, SyncCommitteePrepared,
Update,
};
pub use pallet::*;
pub use config::SLOTS_PER_HISTORICAL_ROOT;
pub const LOG_TARGET: &str = "ethereum-beacon-client";
#[frame_support::pallet]
pub mod pallet {
use super::*;
use frame_support::pallet_prelude::*;
use frame_system::pallet_prelude::*;
#[derive(scale_info::TypeInfo, codec::Encode, codec::Decode, codec::MaxEncodedLen)]
#[codec(mel_bound(T: Config))]
#[scale_info(skip_type_params(T))]
pub struct MaxFinalizedHeadersToKeep<T: Config>(PhantomData<T>);
impl<T: Config> Get<u32> for MaxFinalizedHeadersToKeep<T> {
fn get() -> u32 {
// Consider max latency allowed between LatestFinalizedState and LatestExecutionState is
// the total slots in one sync_committee_period so 1 should be fine we keep 2 periods
// here for redundancy.
const MAX_REDUNDANCY: u32 = 2;
config::EPOCHS_PER_SYNC_COMMITTEE_PERIOD as u32 * MAX_REDUNDANCY
}
}
#[pallet::pallet]
pub struct Pallet<T>(_);
#[pallet::config]
pub trait Config: frame_system::Config {
type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
#[pallet::constant]
type ForkVersions: Get<ForkVersions>;
/// Maximum number of execution headers to keep
#[pallet::constant]
type MaxExecutionHeadersToKeep: Get<u32>;
type WeightInfo: WeightInfo;
}
#[pallet::event]
#[pallet::generate_deposit(pub(super) fn deposit_event)]
pub enum Event<T: Config> {
BeaconHeaderImported {
block_hash: H256,
slot: u64,
},
ExecutionHeaderImported {
block_hash: H256,
block_number: u64,
},
SyncCommitteeUpdated {
period: u64,
},
/// Set OperatingMode
OperatingModeChanged {
mode: BasicOperatingMode,
},
}
#[pallet::error]
pub enum Error<T> {
SkippedSyncCommitteePeriod,
/// Attested header is older than latest finalized header.
IrrelevantUpdate,
NotBootstrapped,
SyncCommitteeParticipantsNotSupermajority,
InvalidHeaderMerkleProof,
InvalidSyncCommitteeMerkleProof,
InvalidExecutionHeaderProof,
InvalidAncestryMerkleProof,
InvalidBlockRootsRootMerkleProof,
HeaderNotFinalized,
BlockBodyHashTreeRootFailed,
HeaderHashTreeRootFailed,
SyncCommitteeHashTreeRootFailed,
SigningRootHashTreeRootFailed,
ForkDataHashTreeRootFailed,
ExpectedFinalizedHeaderNotStored,
BLSPreparePublicKeysFailed,
BLSVerificationFailed(BlsError),
InvalidUpdateSlot,
/// The given update is not in the expected period, or the given next sync committee does
/// not match the next sync committee in storage.
InvalidSyncCommitteeUpdate,
ExecutionHeaderTooFarBehind,
ExecutionHeaderSkippedBlock,
Halted,
}
/// Latest imported checkpoint root
#[pallet::storage]
#[pallet::getter(fn initial_checkpoint_root)]
pub(super) type InitialCheckpointRoot<T: Config> = StorageValue<_, H256, ValueQuery>;
/// Latest imported finalized block root
#[pallet::storage]
#[pallet::getter(fn latest_finalized_block_root)]
pub(super) type LatestFinalizedBlockRoot<T: Config> = StorageValue<_, H256, ValueQuery>;
/// Beacon state by finalized block root
#[pallet::storage]
#[pallet::getter(fn finalized_beacon_state)]
pub(super) type FinalizedBeaconState<T: Config> =
StorageMap<_, Identity, H256, CompactBeaconState, OptionQuery>;
/// Finalized Headers: Current position in ring buffer
#[pallet::storage]
pub(crate) type FinalizedBeaconStateIndex<T: Config> = StorageValue<_, u32, ValueQuery>;
/// Finalized Headers: Mapping of ring buffer index to a pruning candidate
#[pallet::storage]
pub(crate) type FinalizedBeaconStateMapping<T: Config> =
StorageMap<_, Identity, u32, H256, ValueQuery>;
#[pallet::storage]
#[pallet::getter(fn validators_root)]
pub(super) type ValidatorsRoot<T: Config> = StorageValue<_, H256, ValueQuery>;
/// Sync committee for current period
#[pallet::storage]
pub(super) type CurrentSyncCommittee<T: Config> =
StorageValue<_, SyncCommitteePrepared, ValueQuery>;
/// Sync committee for next period
#[pallet::storage]
pub(super) type NextSyncCommittee<T: Config> =
StorageValue<_, SyncCommitteePrepared, ValueQuery>;
/// Latest imported execution header
#[pallet::storage]
#[pallet::getter(fn latest_execution_state)]
pub(super) type LatestExecutionState<T: Config> =
StorageValue<_, ExecutionHeaderState, ValueQuery>;
/// Execution Headers
#[pallet::storage]
pub type ExecutionHeaders<T: Config> =
StorageMap<_, Identity, H256, CompactExecutionHeader, OptionQuery>;
/// Execution Headers: Current position in ring buffer
#[pallet::storage]
pub type ExecutionHeaderIndex<T: Config> = StorageValue<_, u32, ValueQuery>;
/// Execution Headers: Mapping of ring buffer index to a pruning candidate
#[pallet::storage]
pub type ExecutionHeaderMapping<T: Config> = StorageMap<_, Identity, u32, H256, ValueQuery>;
/// The current operating mode of the pallet.
#[pallet::storage]
#[pallet::getter(fn operating_mode)]
pub type OperatingMode<T: Config> = StorageValue<_, BasicOperatingMode, ValueQuery>;
#[pallet::call]
impl<T: Config> Pallet<T> {
#[pallet::call_index(0)]
#[pallet::weight(T::WeightInfo::force_checkpoint())]
#[transactional]
/// Used for pallet initialization and light client resetting. Needs to be called by
/// the root origin.
pub fn force_checkpoint(
origin: OriginFor<T>,
update: Box<CheckpointUpdate>,
) -> DispatchResult {
ensure_root(origin)?;
Self::process_checkpoint_update(&update)?;
Ok(())
}
#[pallet::call_index(1)]
#[pallet::weight({
match update.next_sync_committee_update {
None => T::WeightInfo::submit(),
Some(_) => T::WeightInfo::submit_with_sync_committee(),
}
})]
#[transactional]
/// Submits a new finalized beacon header update. The update may contain the next
/// sync committee.
pub fn submit(origin: OriginFor<T>, update: Box<Update>) -> DispatchResult {
ensure_signed(origin)?;
ensure!(!Self::operating_mode().is_halted(), Error::<T>::Halted);
Self::process_update(&update)?;
Ok(())
}
#[pallet::call_index(2)]
#[pallet::weight(T::WeightInfo::submit_execution_header())]
#[transactional]
/// Submits a new execution header update. The relevant related beacon header
/// is also included to prove the execution header, as well as ancestry proof data.
pub fn submit_execution_header(
origin: OriginFor<T>,
update: Box<ExecutionHeaderUpdate>,
) -> DispatchResult {
ensure_signed(origin)?;
ensure!(!Self::operating_mode().is_halted(), Error::<T>::Halted);
Self::process_execution_header_update(&update)?;
Ok(())
}
/// Halt or resume all pallet operations. May only be called by root.
#[pallet::call_index(3)]
#[pallet::weight((T::DbWeight::get().reads_writes(1, 1), DispatchClass::Operational))]
pub fn set_operating_mode(
origin: OriginFor<T>,
mode: BasicOperatingMode,
) -> DispatchResult {
ensure_root(origin)?;
OperatingMode::<T>::set(mode);
Self::deposit_event(Event::OperatingModeChanged { mode });
Ok(())
}
}
impl<T: Config> Pallet<T> {
/// Forces a finalized beacon header checkpoint update. The current sync committee,
/// with a header attesting to the current sync committee, should be provided.
/// An `block_roots` proof should also be provided. This is used for ancestry proofs
/// for execution header updates.
pub(crate) fn process_checkpoint_update(update: &CheckpointUpdate) -> DispatchResult {
let sync_committee_root = update
.current_sync_committee
.hash_tree_root()
.map_err(|_| Error::<T>::SyncCommitteeHashTreeRootFailed)?;
// Verifies the sync committee in the Beacon state.
ensure!(
verify_merkle_branch(
sync_committee_root,
&update.current_sync_committee_branch,
config::CURRENT_SYNC_COMMITTEE_SUBTREE_INDEX,
config::CURRENT_SYNC_COMMITTEE_DEPTH,
update.header.state_root
),
Error::<T>::InvalidSyncCommitteeMerkleProof
);
let header_root: H256 = update
.header
.hash_tree_root()
.map_err(|_| Error::<T>::HeaderHashTreeRootFailed)?;
// This is used for ancestry proofs in ExecutionHeader updates. This verifies the
// BeaconState: the beacon state root is the tree root; the `block_roots` hash is the
// tree leaf.
ensure!(
verify_merkle_branch(
update.block_roots_root,
&update.block_roots_branch,
config::BLOCK_ROOTS_SUBTREE_INDEX,
config::BLOCK_ROOTS_DEPTH,
update.header.state_root
),
Error::<T>::InvalidBlockRootsRootMerkleProof
);
let sync_committee_prepared: SyncCommitteePrepared = (&update.current_sync_committee)
.try_into()
.map_err(|_| <Error<T>>::BLSPreparePublicKeysFailed)?;
<CurrentSyncCommittee<T>>::set(sync_committee_prepared);
<NextSyncCommittee<T>>::kill();
InitialCheckpointRoot::<T>::set(header_root);
<LatestExecutionState<T>>::kill();
Self::store_validators_root(update.validators_root);
Self::store_finalized_header(header_root, update.header, update.block_roots_root)?;
Ok(())
}
pub(crate) fn process_update(update: &Update) -> DispatchResult {
Self::cross_check_execution_state()?;
Self::verify_update(update)?;
Self::apply_update(update)?;
Ok(())
}
/// Cross check to make sure that execution header import does not fall too far behind
/// finalised beacon header import. If that happens just return an error and pause
/// processing until execution header processing has caught up.
pub(crate) fn cross_check_execution_state() -> DispatchResult {
let latest_finalized_state =
FinalizedBeaconState::<T>::get(LatestFinalizedBlockRoot::<T>::get())
.ok_or(Error::<T>::NotBootstrapped)?;
let latest_execution_state = Self::latest_execution_state();
// The execution header import should be at least within the slot range of a sync
// committee period.
let max_latency = config::EPOCHS_PER_SYNC_COMMITTEE_PERIOD * config::SLOTS_PER_EPOCH;
ensure!(
latest_execution_state.beacon_slot == 0 ||
latest_finalized_state.slot <
latest_execution_state.beacon_slot + max_latency as u64,
Error::<T>::ExecutionHeaderTooFarBehind
);
Ok(())
}
/// References and strictly follows <https://github.com/ethereum/consensus-specs/blob/dev/specs/altair/light-client/sync-protocol.md#validate_light_client_update>
/// Verifies that provided next sync committee is valid through a series of checks
/// (including checking that a sync committee period isn't skipped and that the header is
/// signed by the current sync committee.
fn verify_update(update: &Update) -> DispatchResult {
// Verify sync committee has sufficient participants.
let participation =
decompress_sync_committee_bits(update.sync_aggregate.sync_committee_bits);
Self::sync_committee_participation_is_supermajority(&participation)?;
// Verify update does not skip a sync committee period.
ensure!(
update.signature_slot > update.attested_header.slot &&
update.attested_header.slot >= update.finalized_header.slot,
Error::<T>::InvalidUpdateSlot
);
// Retrieve latest finalized state.
let latest_finalized_state =
FinalizedBeaconState::<T>::get(LatestFinalizedBlockRoot::<T>::get())
.ok_or(Error::<T>::NotBootstrapped)?;
let store_period = compute_period(latest_finalized_state.slot);
let signature_period = compute_period(update.signature_slot);
if <NextSyncCommittee<T>>::exists() {
ensure!(
(store_period..=store_period + 1).contains(&signature_period),
Error::<T>::SkippedSyncCommitteePeriod
)
} else {
ensure!(signature_period == store_period, Error::<T>::SkippedSyncCommitteePeriod)
}
// Verify update is relevant.
let update_attested_period = compute_period(update.attested_header.slot);
let update_has_next_sync_committee = !<NextSyncCommittee<T>>::exists() &&
(update.next_sync_committee_update.is_some() &&
update_attested_period == store_period);
ensure!(
update.attested_header.slot > latest_finalized_state.slot ||
update_has_next_sync_committee,
Error::<T>::IrrelevantUpdate
);
// Verify that the `finality_branch`, if present, confirms `finalized_header` to match
// the finalized checkpoint root saved in the state of `attested_header`.
let finalized_block_root: H256 = update
.finalized_header
.hash_tree_root()
.map_err(|_| Error::<T>::HeaderHashTreeRootFailed)?;
ensure!(
verify_merkle_branch(
finalized_block_root,
&update.finality_branch,
config::FINALIZED_ROOT_SUBTREE_INDEX,
config::FINALIZED_ROOT_DEPTH,
update.attested_header.state_root
),
Error::<T>::InvalidHeaderMerkleProof
);
// Though following check does not belong to ALC spec we verify block_roots_root to
// match the finalized checkpoint root saved in the state of `finalized_header` so to
// cache it for later use in `verify_ancestry_proof`.
ensure!(
verify_merkle_branch(
update.block_roots_root,
&update.block_roots_branch,
config::BLOCK_ROOTS_SUBTREE_INDEX,
config::BLOCK_ROOTS_DEPTH,
update.finalized_header.state_root
),
Error::<T>::InvalidBlockRootsRootMerkleProof
);
// Verify that the `next_sync_committee`, if present, actually is the next sync
// committee saved in the state of the `attested_header`.
if let Some(next_sync_committee_update) = &update.next_sync_committee_update {
let sync_committee_root = next_sync_committee_update
.next_sync_committee
.hash_tree_root()
.map_err(|_| Error::<T>::SyncCommitteeHashTreeRootFailed)?;
if update_attested_period == store_period && <NextSyncCommittee<T>>::exists() {
let next_committee_root = <NextSyncCommittee<T>>::get().root;
ensure!(
sync_committee_root == next_committee_root,
Error::<T>::InvalidSyncCommitteeUpdate
);
}
ensure!(
verify_merkle_branch(
sync_committee_root,
&next_sync_committee_update.next_sync_committee_branch,
config::NEXT_SYNC_COMMITTEE_SUBTREE_INDEX,
config::NEXT_SYNC_COMMITTEE_DEPTH,
update.attested_header.state_root
),
Error::<T>::InvalidSyncCommitteeMerkleProof
);
}
// Verify sync committee aggregate signature.
let sync_committee = if signature_period == store_period {
<CurrentSyncCommittee<T>>::get()
} else {
<NextSyncCommittee<T>>::get()
};
let absent_pubkeys =
Self::find_pubkeys(&participation, (*sync_committee.pubkeys).as_ref(), false);
let signing_root = Self::signing_root(
&update.attested_header,
Self::validators_root(),
update.signature_slot,
)?;
// Improvement here per <https://eth2book.info/capella/part2/building_blocks/signatures/#sync-aggregates>
// suggested start from the full set aggregate_pubkey then subtracting the absolute
// minority that did not participate.
fast_aggregate_verify(
&sync_committee.aggregate_pubkey,
&absent_pubkeys,
signing_root,
&update.sync_aggregate.sync_committee_signature,
)
.map_err(|e| Error::<T>::BLSVerificationFailed(e))?;
Ok(())
}
/// Reference and strictly follows <https://github.com/ethereum/consensus-specs/blob/dev/specs/altair/light-client/sync-protocol.md#apply_light_client_update
/// Applies a finalized beacon header update to the beacon client. If a next sync committee
/// is present in the update, verify the sync committee by converting it to a
/// SyncCommitteePrepared type. Stores the provided finalized header.
fn apply_update(update: &Update) -> DispatchResult {
let latest_finalized_state =
FinalizedBeaconState::<T>::get(LatestFinalizedBlockRoot::<T>::get())
.ok_or(Error::<T>::NotBootstrapped)?;
if let Some(next_sync_committee_update) = &update.next_sync_committee_update {
let store_period = compute_period(latest_finalized_state.slot);
let update_finalized_period = compute_period(update.finalized_header.slot);
let sync_committee_prepared: SyncCommitteePrepared = (&next_sync_committee_update
.next_sync_committee)
.try_into()
.map_err(|_| <Error<T>>::BLSPreparePublicKeysFailed)?;
if !<NextSyncCommittee<T>>::exists() {
ensure!(
update_finalized_period == store_period,
<Error<T>>::InvalidSyncCommitteeUpdate
);
<NextSyncCommittee<T>>::set(sync_committee_prepared);
} else if update_finalized_period == store_period + 1 {
<CurrentSyncCommittee<T>>::set(<NextSyncCommittee<T>>::get());
<NextSyncCommittee<T>>::set(sync_committee_prepared);
}
log::info!(
target: LOG_TARGET,
"💫 SyncCommitteeUpdated at period {}.",
update_finalized_period
);
Self::deposit_event(Event::SyncCommitteeUpdated {
period: update_finalized_period,
});
};
if update.finalized_header.slot > latest_finalized_state.slot {
let finalized_block_root: H256 = update
.finalized_header
.hash_tree_root()
.map_err(|_| Error::<T>::HeaderHashTreeRootFailed)?;
Self::store_finalized_header(
finalized_block_root,
update.finalized_header,
update.block_roots_root,
)?;
}
Ok(())
}
/// Validates an execution header for import. The beacon header containing the execution
/// header is sent, plus the execution header, along with a proof that the execution header
/// is rooted in the beacon header body.
pub(crate) fn process_execution_header_update(
update: &ExecutionHeaderUpdate,
) -> DispatchResult {
let latest_finalized_state =
FinalizedBeaconState::<T>::get(LatestFinalizedBlockRoot::<T>::get())
.ok_or(Error::<T>::NotBootstrapped)?;
// Checks that the header is an ancestor of a finalized header, using slot number.
ensure!(
update.header.slot <= latest_finalized_state.slot,
Error::<T>::HeaderNotFinalized
);
// Checks that we don't skip execution headers, they need to be imported sequentially.
let latest_execution_state: ExecutionHeaderState = Self::latest_execution_state();
ensure!(
latest_execution_state.block_number == 0 ||
update.execution_header.block_number ==
latest_execution_state.block_number + 1,
Error::<T>::ExecutionHeaderSkippedBlock
);
// Gets the hash tree root of the execution header, in preparation for the execution
// header proof (used to check that the execution header is rooted in the beacon
// header body.
let execution_header_root: H256 = update
.execution_header
.hash_tree_root()
.map_err(|_| Error::<T>::BlockBodyHashTreeRootFailed)?;
ensure!(
verify_merkle_branch(
execution_header_root,
&update.execution_branch,
config::EXECUTION_HEADER_SUBTREE_INDEX,
config::EXECUTION_HEADER_DEPTH,
update.header.body_root
),
Error::<T>::InvalidExecutionHeaderProof
);
let block_root: H256 = update
.header
.hash_tree_root()
.map_err(|_| Error::<T>::HeaderHashTreeRootFailed)?;
match &update.ancestry_proof {
Some(proof) => {
Self::verify_ancestry_proof(
block_root,
update.header.slot,
&proof.header_branch,
proof.finalized_block_root,
)?;
},
None => {
// If the ancestry proof is not provided, we expect this header to be a
// finalized header. We need to check that the header hash matches the finalized
// header root at the expected slot.
let state = <FinalizedBeaconState<T>>::get(block_root)
.ok_or(Error::<T>::ExpectedFinalizedHeaderNotStored)?;
if update.header.slot != state.slot {
return Err(Error::<T>::ExpectedFinalizedHeaderNotStored.into())
}
},
}
Self::store_execution_header(
update.execution_header.block_hash,
update.execution_header.clone().into(),
update.header.slot,
block_root,
);
Ok(())
}
/// Verify that `block_root` is an ancestor of `finalized_block_root` Used to prove that
/// an execution header is an ancestor of a finalized header (i.e. the blocks are
/// on the same chain).
fn verify_ancestry_proof(
block_root: H256,
block_slot: u64,
block_root_proof: &[H256],
finalized_block_root: H256,
) -> DispatchResult {
let state = <FinalizedBeaconState<T>>::get(finalized_block_root)
.ok_or(Error::<T>::ExpectedFinalizedHeaderNotStored)?;
ensure!(block_slot < state.slot, Error::<T>::HeaderNotFinalized);
let index_in_array = block_slot % (SLOTS_PER_HISTORICAL_ROOT as u64);
let leaf_index = (SLOTS_PER_HISTORICAL_ROOT as u64) + index_in_array;
ensure!(
verify_merkle_branch(
block_root,
block_root_proof,
leaf_index as usize,
config::BLOCK_ROOT_AT_INDEX_DEPTH,
state.block_roots_root
),
Error::<T>::InvalidAncestryMerkleProof
);
Ok(())
}
/// Computes the signing root for a given beacon header and domain. The hash tree root
/// of the beacon header is computed, and then the combination of the beacon header hash
/// and the domain makes up the signing root.
pub(super) fn compute_signing_root(
beacon_header: &BeaconHeader,
domain: H256,
) -> Result<H256, DispatchError> {
let beacon_header_root = beacon_header
.hash_tree_root()
.map_err(|_| Error::<T>::HeaderHashTreeRootFailed)?;
let hash_root = SigningData { object_root: beacon_header_root, domain }
.hash_tree_root()
.map_err(|_| Error::<T>::SigningRootHashTreeRootFailed)?;
Ok(hash_root)
}
/// Stores a compacted (slot and block roots root (hash of the `block_roots` beacon state
/// field, used for ancestry proof)) beacon state in a ring buffer map, with the header root
/// as map key.
fn store_finalized_header(
header_root: H256,
header: BeaconHeader,
block_roots_root: H256,
) -> DispatchResult {
let slot = header.slot;
<FinalizedBeaconStateBuffer<T>>::insert(
header_root,
CompactBeaconState { slot: header.slot, block_roots_root },
);
<LatestFinalizedBlockRoot<T>>::set(header_root);
log::info!(
target: LOG_TARGET,
"💫 Updated latest finalized block root {} at slot {}.",
header_root,
slot
);
Self::deposit_event(Event::BeaconHeaderImported { block_hash: header_root, slot });
Ok(())
}
/// Stores the provided execution header in pallet storage. The header is stored
/// in a ring buffer map, with the block hash as map key. The last imported execution
/// header is also kept in storage, for the relayer to check import progress.
pub(crate) fn store_execution_header(
block_hash: H256,
header: CompactExecutionHeader,
beacon_slot: u64,
beacon_block_root: H256,
) {
let block_number = header.block_number;
<ExecutionHeaderBuffer<T>>::insert(block_hash, header);
log::trace!(
target: LOG_TARGET,
"💫 Updated latest execution block at {} to number {}.",
block_hash,
block_number
);
LatestExecutionState::<T>::mutate(|s| {
s.beacon_block_root = beacon_block_root;
s.beacon_slot = beacon_slot;
s.block_hash = block_hash;
s.block_number = block_number;
});
Self::deposit_event(Event::ExecutionHeaderImported { block_hash, block_number });
}
/// Stores the validators root in storage. Validators root is the hash tree root of all the
/// validators at genesis and is used to used to identify the chain that we are on
/// (used in conjunction with the fork version).
/// <https://eth2book.info/capella/part3/containers/state/#genesis_validators_root>
fn store_validators_root(validators_root: H256) {
<ValidatorsRoot<T>>::set(validators_root);
}
/// Returns the domain for the domain_type and fork_version. The domain is used to
/// distinguish between the different players in the chain (see DomainTypes
/// <https://eth2book.info/capella/part3/config/constants/#domain-types>) and to ensure we are
/// addressing the correct chain.
/// <https://eth2book.info/capella/part3/helper/misc/#compute_domain>
pub(super) fn compute_domain(
domain_type: Vec<u8>,
fork_version: ForkVersion,
genesis_validators_root: H256,
) -> Result<H256, DispatchError> {
let fork_data_root =
Self::compute_fork_data_root(fork_version, genesis_validators_root)?;
let mut domain = [0u8; 32];
domain[0..4].copy_from_slice(&(domain_type));
domain[4..32].copy_from_slice(&(fork_data_root.0[..28]));
Ok(domain.into())
}
/// Computes the fork data root. The fork data root is a merkleization of the current
/// fork version and the genesis validators root.
fn compute_fork_data_root(
current_version: ForkVersion,
genesis_validators_root: H256,
) -> Result<H256, DispatchError> {
let hash_root = ForkData {
current_version,
genesis_validators_root: genesis_validators_root.into(),
}
.hash_tree_root()
.map_err(|_| Error::<T>::ForkDataHashTreeRootFailed)?;
Ok(hash_root)
}
/// Checks that the sync committee bits (the votes of the sync committee members,
/// represented by bits 0 and 1) is more than a supermajority (2/3 of the votes are
/// positive).
pub(super) fn sync_committee_participation_is_supermajority(
sync_committee_bits: &[u8],
) -> DispatchResult {
let sync_committee_sum = sync_committee_sum(sync_committee_bits);
ensure!(
((sync_committee_sum * 3) as usize) >= sync_committee_bits.len() * 2,
Error::<T>::SyncCommitteeParticipantsNotSupermajority
);
Ok(())
}
/// Returns the fork version based on the current epoch. The hard fork versions
/// are defined in pallet config.
pub(super) fn compute_fork_version(epoch: u64) -> ForkVersion {
Self::select_fork_version(&T::ForkVersions::get(), epoch)
}
/// Returns the fork version based on the current epoch.
pub(super) fn select_fork_version(fork_versions: &ForkVersions, epoch: u64) -> ForkVersion {
if epoch >= fork_versions.capella.epoch {
return fork_versions.capella.version
}
if epoch >= fork_versions.bellatrix.epoch {
return fork_versions.bellatrix.version
}
if epoch >= fork_versions.altair.epoch {
return fork_versions.altair.version
}
fork_versions.genesis.version
}
/// Returns a vector of public keys that participated in the sync committee block signage.
/// Sync committee bits is an array of 0s and 1s, 0 meaning the corresponding sync committee
/// member did not participate in the vote, 1 meaning they participated.
/// This method can find the absent or participating members, based on the participant
/// parameter. participant = false will return absent participants, participant = true will
/// return participating members.
pub fn find_pubkeys(
sync_committee_bits: &[u8],
sync_committee_pubkeys: &[PublicKeyPrepared],
participant: bool,
) -> Vec<PublicKeyPrepared> {
let mut pubkeys: Vec<PublicKeyPrepared> = Vec::new();
for (bit, pubkey) in sync_committee_bits.iter().zip(sync_committee_pubkeys.iter()) {
if *bit == u8::from(participant) {
pubkeys.push(*pubkey);
}
}
pubkeys
}
/// Calculates signing root for BeaconHeader. The signing root is used for the message
/// value in BLS signature verification.
pub fn signing_root(
header: &BeaconHeader,
validators_root: H256,
signature_slot: u64,
) -> Result<H256, DispatchError> {
let fork_version = Self::compute_fork_version(compute_epoch(
signature_slot,
config::SLOTS_PER_EPOCH as u64,
));
let domain_type = config::DOMAIN_SYNC_COMMITTEE.to_vec();
// Domains are used for for seeds, for signatures, and for selecting aggregators.
let domain = Self::compute_domain(domain_type, fork_version, validators_root)?;
// Hash tree root of SigningData - object root + domain
let signing_root = Self::compute_signing_root(header, domain)?;
Ok(signing_root)
}
}
}
@@ -0,0 +1,275 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2023 Snowfork <hello@snowfork.com>
use crate as ethereum_beacon_client;
use frame_support::parameter_types;
use pallet_timestamp;
use primitives::{Fork, ForkVersions};
use sp_core::H256;
use sp_runtime::traits::{BlakeTwo256, IdentityLookup};
#[cfg(not(feature = "beacon-spec-mainnet"))]
pub mod minimal {
use super::*;
use crate::config;
use hex_literal::hex;
use primitives::CompactExecutionHeader;
use snowbridge_core::inbound::{Log, Proof};
use sp_runtime::BuildStorage;
use std::{fs::File, path::PathBuf};
type Block = frame_system::mocking::MockBlock<Test>;
frame_support::construct_runtime!(
pub enum Test {
System: frame_system::{Pallet, Call, Storage, Event<T>},
Timestamp: pallet_timestamp::{Pallet, Call, Storage, Inherent},
EthereumBeaconClient: ethereum_beacon_client::{Pallet, Call, Storage, Event<T>},
}
);
parameter_types! {
pub const BlockHashCount: u64 = 250;
pub const SS58Prefix: u8 = 42;
}
impl frame_system::Config for Test {
type BaseCallFilter = frame_support::traits::Everything;
type OnSetCode = ();
type BlockWeights = ();
type BlockLength = ();
type DbWeight = ();
type RuntimeOrigin = RuntimeOrigin;
type RuntimeCall = RuntimeCall;
type RuntimeTask = RuntimeTask;
type Hash = H256;
type Hashing = BlakeTwo256;
type AccountId = u64;
type Lookup = IdentityLookup<Self::AccountId>;
type RuntimeEvent = RuntimeEvent;
type BlockHashCount = BlockHashCount;
type Version = ();
type PalletInfo = PalletInfo;
type AccountData = ();
type OnNewAccount = ();
type OnKilledAccount = ();
type SystemWeightInfo = ();
type SS58Prefix = SS58Prefix;
type MaxConsumers = frame_support::traits::ConstU32<16>;
type Nonce = u64;
type Block = Block;
}
impl pallet_timestamp::Config for Test {
type Moment = u64;
type OnTimestampSet = ();
type MinimumPeriod = ();
type WeightInfo = ();
}
parameter_types! {
pub const ExecutionHeadersPruneThreshold: u32 = 10;
pub const ChainForkVersions: ForkVersions = ForkVersions{
genesis: Fork {
version: [0, 0, 0, 1], // 0x00000001
epoch: 0,
},
altair: Fork {
version: [1, 0, 0, 1], // 0x01000001
epoch: 0,
},
bellatrix: Fork {
version: [2, 0, 0, 1], // 0x02000001
epoch: 0,
},
capella: Fork {
version: [3, 0, 0, 1], // 0x03000001
epoch: 0,
},
};
}
impl ethereum_beacon_client::Config for Test {
type RuntimeEvent = RuntimeEvent;
type ForkVersions = ChainForkVersions;
type MaxExecutionHeadersToKeep = ExecutionHeadersPruneThreshold;
type WeightInfo = ();
}
// Build genesis storage according to the mock runtime.
pub fn new_tester() -> sp_io::TestExternalities {
let t = frame_system::GenesisConfig::<Test>::default().build_storage().unwrap();
let mut ext = sp_io::TestExternalities::new(t);
let _ = ext.execute_with(|| Timestamp::set(RuntimeOrigin::signed(1), 30_000));
ext
}
fn load_fixture<T>(basename: &str) -> Result<T, serde_json::Error>
where
T: for<'de> serde::Deserialize<'de>,
{
let filepath: PathBuf =
[env!("CARGO_MANIFEST_DIR"), "tests", "fixtures", basename].iter().collect();
serde_json::from_reader(File::open(filepath).unwrap())
}
pub fn load_execution_header_update_fixture() -> primitives::ExecutionHeaderUpdate {
load_fixture("execution-header-update.minimal.json").unwrap()
}
pub fn load_checkpoint_update_fixture(
) -> primitives::CheckpointUpdate<{ config::SYNC_COMMITTEE_SIZE }> {
load_fixture("initial-checkpoint.minimal.json").unwrap()
}
pub fn load_sync_committee_update_fixture(
) -> primitives::Update<{ config::SYNC_COMMITTEE_SIZE }, { config::SYNC_COMMITTEE_BITS_SIZE }> {
load_fixture("sync-committee-update.minimal.json").unwrap()
}
pub fn load_finalized_header_update_fixture(
) -> primitives::Update<{ config::SYNC_COMMITTEE_SIZE }, { config::SYNC_COMMITTEE_BITS_SIZE }> {
load_fixture("finalized-header-update.minimal.json").unwrap()
}
pub fn load_next_sync_committee_update_fixture(
) -> primitives::Update<{ config::SYNC_COMMITTEE_SIZE }, { config::SYNC_COMMITTEE_BITS_SIZE }> {
load_fixture("next-sync-committee-update.minimal.json").unwrap()
}
pub fn load_next_finalized_header_update_fixture(
) -> primitives::Update<{ config::SYNC_COMMITTEE_SIZE }, { config::SYNC_COMMITTEE_BITS_SIZE }> {
load_fixture("next-finalized-header-update.minimal.json").unwrap()
}
pub fn get_message_verification_payload() -> (Log, Proof) {
(
Log {
address: hex!("ee9170abfbf9421ad6dd07f6bdec9d89f2b581e0").into(),
topics: vec![
hex!("1b11dcf133cc240f682dab2d3a8e4cd35c5da8c9cf99adac4336f8512584c5ad").into(),
hex!("00000000000000000000000000000000000000000000000000000000000003e8").into(),
hex!("0000000000000000000000000000000000000000000000000000000000000001").into(),
],
data: hex!("0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000004b000f000000000000000100d184c103f7acc340847eee82a0b909e3358bc28d440edffa1352b13227e8ee646f3ea37456dec701345772617070656420457468657210574554481235003511000000000000000000000000000000000000000000").into(),
},
Proof {
block_hash: hex!("05aaa60b0f27cce9e71909508527264b77ee14da7b5bf915fcc4e32715333213").into(),
tx_index: 0,
data: (vec![
hex!("cf0d1c1ba57d1e0edfb59786c7e30c2b7e12bd54612b00cd21c4eaeecedf44fb").to_vec(),
hex!("d21fc4f68ab05bc4dcb23c67008e92c4d466437cdd6ed7aad0c008944c185510").to_vec(),
hex!("b9890f91ca0d77aa2a4adfaf9b9e40c94cac9e638b6d9797923865872944b646").to_vec(),
], vec![
hex!("f90131a0b601337b3aa10a671caa724eba641e759399979856141d3aea6b6b4ac59b889ba00c7d5dd48be9060221a02fb8fa213860b4c50d47046c8fa65ffaba5737d569e0a094601b62a1086cd9c9cb71a7ebff9e718f3217fd6e837efe4246733c0a196f63a06a4b0dd0aefc37b3c77828c8f07d1b7a2455ceb5dbfd3c77d7d6aeeddc2f7e8ca0d6e8e23142cdd8ec219e1f5d8b56aa18e456702b195deeaa210327284d42ade4a08a313d4c87023005d1ab631bbfe3f5de1e405d0e66d0bef3e033f1e5711b5521a0bf09a5d9a48b10ade82b8d6a5362a15921c8b5228a3487479b467db97411d82fa0f95cccae2a7c572ef3c566503e30bac2b2feb2d2f26eebf6d870dcf7f8cf59cea0d21fc4f68ab05bc4dcb23c67008e92c4d466437cdd6ed7aad0c008944c1855108080808080808080").to_vec(),
hex!("f851a0b9890f91ca0d77aa2a4adfaf9b9e40c94cac9e638b6d9797923865872944b646a060a634b9280e3a23fb63375e7bbdd9ab07fd379ab6a67e2312bbc112195fa358808080808080808080808080808080").to_vec(),
hex!("f9030820b9030402f90300018301d6e2b9010000000000000800000000000020040008000000000000000000000000400000008000000000000000000000000000000000000000000000000000000000042010000000001000000000000000000000000000000000040000000000000000000000000000000000000000000000008000000000000000002000000000000000000000000200000000000000200000000000100000000040000001000200008000000000000200000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000000f901f5f87a942ffa5ecdbe006d30397c7636d3e015eee251369ff842a0c965575a00553e094ca7c5d14f02e107c258dda06867cbf9e0e69f80e71bbcc1a000000000000000000000000000000000000000000000000000000000000003e8a000000000000000000000000000000000000000000000000000000000000003e8f9011c94ee9170abfbf9421ad6dd07f6bdec9d89f2b581e0f863a01b11dcf133cc240f682dab2d3a8e4cd35c5da8c9cf99adac4336f8512584c5ada000000000000000000000000000000000000000000000000000000000000003e8a00000000000000000000000000000000000000000000000000000000000000001b8a00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000004b000f000000000000000100d184c103f7acc340847eee82a0b909e3358bc28d440edffa1352b13227e8ee646f3ea37456dec701345772617070656420457468657210574554481235003511000000000000000000000000000000000000000000f858948cf6147918a5cbb672703f879f385036f8793a24e1a01449abf21e49fd025f33495e77f7b1461caefdd3d4bb646424a3f445c4576a5ba0000000000000000000000000440edffa1352b13227e8ee646f3ea37456dec701").to_vec(),
]),
}
)
}
pub fn get_message_verification_header() -> CompactExecutionHeader {
CompactExecutionHeader {
parent_hash: hex!("04a7f6ab8282203562c62f38b0ab41d32aaebe2c7ea687702b463148a6429e04")
.into(),
block_number: 55,
state_root: hex!("894d968712976d613519f973a317cb0781c7b039c89f27ea2b7ca193f7befdb3")
.into(),
receipts_root: hex!("cf0d1c1ba57d1e0edfb59786c7e30c2b7e12bd54612b00cd21c4eaeecedf44fb")
.into(),
}
}
}
#[cfg(feature = "beacon-spec-mainnet")]
pub mod mainnet {
use super::*;
type Block = frame_system::mocking::MockBlock<Test>;
use sp_runtime::BuildStorage;
frame_support::construct_runtime!(
pub enum Test {
System: frame_system::{Pallet, Call, Storage, Event<T>},
Timestamp: pallet_timestamp::{Pallet, Call, Storage, Inherent},
EthereumBeaconClient: ethereum_beacon_client::{Pallet, Call, Storage, Event<T>},
}
);
parameter_types! {
pub const BlockHashCount: u64 = 250;
pub const SS58Prefix: u8 = 42;
}
impl frame_system::Config for Test {
type BaseCallFilter = frame_support::traits::Everything;
type OnSetCode = ();
type BlockWeights = ();
type BlockLength = ();
type DbWeight = ();
type RuntimeOrigin = RuntimeOrigin;
type RuntimeCall = RuntimeCall;
type RuntimeTask = RuntimeTask;
type Hash = H256;
type Hashing = BlakeTwo256;
type AccountId = u64;
type Lookup = IdentityLookup<Self::AccountId>;
type RuntimeEvent = RuntimeEvent;
type BlockHashCount = BlockHashCount;
type Version = ();
type PalletInfo = PalletInfo;
type AccountData = ();
type OnNewAccount = ();
type OnKilledAccount = ();
type SystemWeightInfo = ();
type SS58Prefix = SS58Prefix;
type MaxConsumers = frame_support::traits::ConstU32<16>;
type Nonce = u64;
type Block = Block;
}
impl pallet_timestamp::Config for Test {
type Moment = u64;
type OnTimestampSet = ();
type MinimumPeriod = ();
type WeightInfo = ();
}
parameter_types! {
pub const ChainForkVersions: ForkVersions = ForkVersions{
genesis: Fork {
version: [0, 0, 16, 32], // 0x00001020
epoch: 0,
},
altair: Fork {
version: [1, 0, 16, 32], // 0x01001020
epoch: 36660,
},
bellatrix: Fork {
version: [2, 0, 16, 32], // 0x02001020
epoch: 112260,
},
capella: Fork {
version: [3, 0, 16, 32], // 0x03001020
epoch: 162304,
},
};
pub const ExecutionHeadersPruneThreshold: u32 = 10;
}
impl ethereum_beacon_client::Config for Test {
type RuntimeEvent = RuntimeEvent;
type ForkVersions = ChainForkVersions;
type MaxExecutionHeadersToKeep = ExecutionHeadersPruneThreshold;
type WeightInfo = ();
}
// Build genesis storage according to the mock runtime.
pub fn new_tester() -> sp_io::TestExternalities {
let t = frame_system::GenesisConfig::<Test>::default().build_storage().unwrap();
let mut ext = sp_io::TestExternalities::new(t);
let _ = ext.execute_with(|| Timestamp::set(RuntimeOrigin::signed(1), 30_000));
ext
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,38 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2023 Snowfork <hello@snowfork.com>
pub use crate::config::{
SLOTS_PER_HISTORICAL_ROOT, SYNC_COMMITTEE_BITS_SIZE as SC_BITS_SIZE,
SYNC_COMMITTEE_SIZE as SC_SIZE,
};
use frame_support::storage::types::OptionQuery;
use snowbridge_core::RingBufferMapImpl;
// Specialize types based on configured sync committee size
pub type SyncCommittee = primitives::SyncCommittee<SC_SIZE>;
pub type SyncCommitteePrepared = primitives::SyncCommitteePrepared<SC_SIZE>;
pub type SyncAggregate = primitives::SyncAggregate<SC_SIZE, SC_BITS_SIZE>;
pub type CheckpointUpdate = primitives::CheckpointUpdate<SC_SIZE>;
pub type Update = primitives::Update<SC_SIZE, SC_BITS_SIZE>;
pub type NextSyncCommitteeUpdate = primitives::NextSyncCommitteeUpdate<SC_SIZE>;
pub use primitives::ExecutionHeaderUpdate;
/// ExecutionHeader ring buffer implementation
pub type ExecutionHeaderBuffer<T> = RingBufferMapImpl<
u32,
<T as crate::Config>::MaxExecutionHeadersToKeep,
crate::ExecutionHeaderIndex<T>,
crate::ExecutionHeaderMapping<T>,
crate::ExecutionHeaders<T>,
OptionQuery,
>;
/// FinalizedState ring buffer implementation
pub(crate) type FinalizedBeaconStateBuffer<T> = RingBufferMapImpl<
u32,
crate::MaxFinalizedHeadersToKeep<T>,
crate::FinalizedBeaconStateIndex<T>,
crate::FinalizedBeaconStateMapping<T>,
crate::FinalizedBeaconState<T>,
OptionQuery,
>;
@@ -0,0 +1,68 @@
//! Autogenerated weights for ethereum_beacon_client
//!
//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev
//! DATE: 2022-09-27, STEPS: `10`, REPEAT: 10, LOW RANGE: `[]`, HIGH RANGE: `[]`
//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("/tmp/snowbridge/spec.json"), DB CACHE: 1024
// Executed Command:
// ./target/release/snowbridge
// benchmark
// pallet
// --chain
// /tmp/snowbridge/spec.json
// --execution=wasm
// --pallet
// ethereum_beacon_client
// --extrinsic
// *
// --steps
// 10
// --repeat
// 10
// --output
// pallets/ethereum-beacon-client/src/weights.rs
// --template
// templates/module-weight-template.hbs
#![cfg_attr(rustfmt, rustfmt_skip)]
#![allow(unused_parens)]
#![allow(unused_imports)]
use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}};
use sp_std::marker::PhantomData;
/// Weight functions needed for ethereum_beacon_client.
pub trait WeightInfo {
fn force_checkpoint() -> Weight;
fn submit() -> Weight;
fn submit_with_sync_committee() -> Weight;
fn submit_execution_header() -> Weight;
}
// For backwards compatibility and tests
impl WeightInfo for () {
fn force_checkpoint() -> Weight {
Weight::from_parts(97_263_571_000_u64, 0)
.saturating_add(Weight::from_parts(0, 3501))
.saturating_add(RocksDbWeight::get().reads(2))
.saturating_add(RocksDbWeight::get().writes(9))
}
fn submit() -> Weight {
Weight::from_parts(26_051_019_000_u64, 0)
.saturating_add(Weight::from_parts(0, 93857))
.saturating_add(RocksDbWeight::get().reads(8))
.saturating_add(RocksDbWeight::get().writes(4))
}
fn submit_with_sync_committee() -> Weight {
Weight::from_parts(122_461_312_000_u64, 0)
.saturating_add(Weight::from_parts(0, 93857))
.saturating_add(RocksDbWeight::get().reads(6))
.saturating_add(RocksDbWeight::get().writes(1))
}
fn submit_execution_header() -> Weight {
Weight::from_parts(113_158_000_u64, 0)
.saturating_add(Weight::from_parts(0, 3537))
.saturating_add(RocksDbWeight::get().reads(5))
.saturating_add(RocksDbWeight::get().writes(4))
}
}