feat: initialize Kurdistan SDK - independent fork of Polkadot SDK
This commit is contained in:
@@ -0,0 +1,142 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-FileCopyrightText: 2023 Snowfork <hello@snowfork.com>
|
||||
use super::*;
|
||||
mod util;
|
||||
|
||||
use crate::Pallet as EthereumBeaconClient;
|
||||
use frame_benchmarking::v2::*;
|
||||
use frame_system::RawOrigin;
|
||||
use hex_literal::hex;
|
||||
use snowbridge_beacon_primitives::{
|
||||
fast_aggregate_verify,
|
||||
merkle_proof::{generalized_index_length, subtree_index},
|
||||
prepare_aggregate_pubkey, prepare_aggregate_signature, verify_merkle_branch, Fork,
|
||||
};
|
||||
use snowbridge_pallet_ethereum_client_fixtures::*;
|
||||
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(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(
|
||||
¤t_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();
|
||||
|
||||
let fork_versions = ForkVersions {
|
||||
genesis: Fork { version: hex!("00000000"), epoch: 0 },
|
||||
altair: Fork { version: hex!("01000000"), epoch: 0 },
|
||||
bellatrix: Fork { version: hex!("02000000"), epoch: 0 },
|
||||
capella: Fork { version: hex!("03000000"), epoch: 0 },
|
||||
deneb: Fork { version: hex!("04000000"), epoch: 0 },
|
||||
electra: Fork { version: hex!("05000000"), epoch: 80000000000 },
|
||||
fulu: Fork { version: hex!("06000000"), epoch: 80000000001 },
|
||||
};
|
||||
let finalized_root_gindex = EthereumBeaconClient::<T>::finalized_root_gindex_at_slot(
|
||||
update.attested_header.slot,
|
||||
fork_versions,
|
||||
);
|
||||
#[block]
|
||||
{
|
||||
verify_merkle_branch(
|
||||
block_root,
|
||||
&update.finality_branch,
|
||||
subtree_index(finalized_root_gindex),
|
||||
generalized_index_length(finalized_root_gindex),
|
||||
update.attested_header.state_root,
|
||||
);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
impl_benchmark_test_suite!(EthereumBeaconClient, crate::mock::new_tester(), crate::mock::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 snowbridge_beacon_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,15 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-FileCopyrightText: 2023 Snowfork <hello@snowfork.com>
|
||||
|
||||
/// Generalized Indices
|
||||
/// related to Merkle proofs
|
||||
/// get_generalized_index(BeaconState, 'block_roots')
|
||||
pub const BLOCK_ROOTS_INDEX: usize = 37;
|
||||
/// get_generalized_index(BeaconState, 'finalized_checkpoint', 'root')
|
||||
pub const FINALIZED_ROOT_INDEX: usize = 105;
|
||||
/// get_generalized_index(BeaconState, 'current_sync_committee')
|
||||
pub const CURRENT_SYNC_COMMITTEE_INDEX: usize = 54;
|
||||
/// get_generalized_index(BeaconState, 'next_sync_committee')
|
||||
pub const NEXT_SYNC_COMMITTEE_INDEX: usize = 55;
|
||||
/// get_generalized_index(BeaconBlockBody, 'execution_payload')
|
||||
pub const EXECUTION_HEADER_INDEX: usize = 25;
|
||||
@@ -0,0 +1,13 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-FileCopyrightText: 2023 Snowfork <hello@snowfork.com>
|
||||
|
||||
/// Generalized Indices
|
||||
/// related to Merkle proofs
|
||||
/// get_generalized_index(BeaconState, 'block_roots')
|
||||
pub const BLOCK_ROOTS_INDEX: usize = 69;
|
||||
/// get_generalized_index(BeaconState, 'finalized_checkpoint', 'root')
|
||||
pub const FINALIZED_ROOT_INDEX: usize = 169;
|
||||
/// get_generalized_index(BeaconState, 'current_sync_committee')
|
||||
pub const CURRENT_SYNC_COMMITTEE_INDEX: usize = 86;
|
||||
/// get_generalized_index(BeaconState, 'next_sync_committee')
|
||||
pub const NEXT_SYNC_COMMITTEE_INDEX: usize = 87;
|
||||
@@ -0,0 +1,40 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-FileCopyrightText: 2023 Snowfork <hello@snowfork.com>
|
||||
use static_assertions::const_assert;
|
||||
|
||||
pub mod altair;
|
||||
pub mod electra;
|
||||
|
||||
/// Sizes related to SSZ encoding
|
||||
pub const MAX_EXTRA_DATA_BYTES: usize = 32;
|
||||
pub const MAX_LOGS_BLOOM_SIZE: usize = 256;
|
||||
pub const MAX_FEE_RECIPIENT_SIZE: usize = 20;
|
||||
|
||||
/// Sanity value to constrain the max size of a merkle branch proof.
|
||||
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];
|
||||
/// Validators public keys are 48 bytes.
|
||||
pub const PUBKEY_SIZE: usize = 48;
|
||||
/// Signatures produced by validators are 96 bytes.
|
||||
pub const SIGNATURE_SIZE: usize = 96;
|
||||
|
||||
// Sanity check for the sync committee bits (see SYNC_COMMITTEE_BITS_SIZE).
|
||||
const_assert!(SYNC_COMMITTEE_BITS_SIZE == SYNC_COMMITTEE_SIZE / 8);
|
||||
|
||||
/// Defined in <https://github.com/ethereum/consensus-specs/tree/f1dff5f6768608d890fc0b347e548297fc3e1f1c/presets/mainnet>
|
||||
/// There are 32 slots in an epoch. An epoch is 6.4 minutes long.
|
||||
pub const SLOTS_PER_EPOCH: usize = 32;
|
||||
/// 256 epochs in a sync committee period. Frequency of sync committee (subset of Ethereum
|
||||
/// validators) change is every ~27 hours.
|
||||
pub const EPOCHS_PER_SYNC_COMMITTEE_PERIOD: usize = 256;
|
||||
/// A sync committee contains 512 randomly selected validators.
|
||||
pub const SYNC_COMMITTEE_SIZE: usize = 512;
|
||||
/// An array of sync committee block votes, one bit representing the vote of one validator.
|
||||
pub const SYNC_COMMITTEE_BITS_SIZE: usize = SYNC_COMMITTEE_SIZE / 8;
|
||||
/// The size of the block root array in the beacon state, used for ancestry proofs.
|
||||
pub const SLOTS_PER_HISTORICAL_ROOT: usize = 8192;
|
||||
/// The index of the block_roots field in the beacon state tree.
|
||||
pub const BLOCK_ROOT_AT_INDEX_DEPTH: usize = 13;
|
||||
@@ -0,0 +1,32 @@
|
||||
// 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] {
|
||||
snowbridge_beacon_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,163 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-FileCopyrightText: 2023 Snowfork <hello@snowfork.com>
|
||||
use super::*;
|
||||
use frame_support::ensure;
|
||||
use snowbridge_beacon_primitives::ExecutionProof;
|
||||
|
||||
use snowbridge_beacon_primitives::{
|
||||
merkle_proof::{generalized_index_length, subtree_index},
|
||||
receipt::verify_receipt_proof,
|
||||
};
|
||||
use snowbridge_ethereum::Log as AlloyLog;
|
||||
use snowbridge_verification_primitives::{
|
||||
VerificationError::{self, *},
|
||||
Verifier, *,
|
||||
};
|
||||
|
||||
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 is sent with the message. The beacon header containing the execution header
|
||||
/// is also sent with the message, to check if the header is an ancestor of a finalized
|
||||
/// header.
|
||||
fn verify(event_log: &Log, proof: &Proof) -> Result<(), VerificationError> {
|
||||
Self::verify_execution_proof(&proof.execution_proof)
|
||||
.map_err(|e| InvalidExecutionProof(e.into()))?;
|
||||
|
||||
Self::verify_receipt_inclusion(
|
||||
proof.execution_proof.execution_header.receipts_root(),
|
||||
&proof.receipt_proof.1,
|
||||
event_log,
|
||||
)?;
|
||||
|
||||
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,
|
||||
receipt_proof: &[Vec<u8>],
|
||||
log: &Log,
|
||||
) -> Result<(), VerificationError> {
|
||||
let receipt = verify_receipt_proof(receipts_root, receipt_proof).ok_or(InvalidProof)?;
|
||||
if !receipt.logs().iter().any(|l| Self::check_log_match(log, l)) {
|
||||
tracing::error!(
|
||||
target: "ethereum-client",
|
||||
"💫 Event log not found in receipt for transaction",
|
||||
);
|
||||
return Err(LogNotFound);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn check_log_match(log: &Log, receipt_log: &AlloyLog) -> bool {
|
||||
let equal = receipt_log.data.data.0 == log.data &&
|
||||
receipt_log.address.0 == log.address.0 &&
|
||||
receipt_log.topics().len() == log.topics.len();
|
||||
if !equal {
|
||||
return false;
|
||||
}
|
||||
for (_, (topic1, topic2)) in receipt_log.topics().iter().zip(log.topics.iter()).enumerate()
|
||||
{
|
||||
if topic1.0 != topic2.0 {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
/// Validates an execution header with ancestry_proof against a finalized checkpoint on
|
||||
/// chain.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 verify_execution_proof(execution_proof: &ExecutionProof) -> 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!(
|
||||
execution_proof.header.slot <= latest_finalized_state.slot,
|
||||
Error::<T>::HeaderNotFinalized
|
||||
);
|
||||
|
||||
let beacon_block_root: H256 = execution_proof
|
||||
.header
|
||||
.hash_tree_root()
|
||||
.map_err(|_| Error::<T>::HeaderHashTreeRootFailed)?;
|
||||
|
||||
match &execution_proof.ancestry_proof {
|
||||
Some(proof) => {
|
||||
Self::verify_ancestry_proof(
|
||||
beacon_block_root,
|
||||
execution_proof.header.slot,
|
||||
&proof.header_branch,
|
||||
proof.finalized_block_root,
|
||||
)?;
|
||||
},
|
||||
None => {
|
||||
// If the ancestry proof is not provided, we expect this beacon header to be a
|
||||
// finalized beacon header. We need to check that the header hash matches the
|
||||
// finalized header root at the expected slot.
|
||||
let state = <FinalizedBeaconState<T>>::get(beacon_block_root)
|
||||
.ok_or(Error::<T>::ExpectedFinalizedHeaderNotStored)?;
|
||||
if execution_proof.header.slot != state.slot {
|
||||
return Err(Error::<T>::ExpectedFinalizedHeaderNotStored.into());
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
// 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 = execution_proof
|
||||
.execution_header
|
||||
.hash_tree_root()
|
||||
.map_err(|_| Error::<T>::BlockBodyHashTreeRootFailed)?;
|
||||
|
||||
let execution_header_gindex = Self::execution_header_gindex();
|
||||
ensure!(
|
||||
verify_merkle_branch(
|
||||
execution_header_root,
|
||||
&execution_proof.execution_branch,
|
||||
subtree_index(execution_header_gindex),
|
||||
generalized_index_length(execution_header_gindex),
|
||||
execution_proof.header.body_root
|
||||
),
|
||||
Error::<T>::InvalidExecutionHeaderProof
|
||||
);
|
||||
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(())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,764 @@
|
||||
// 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 consensus updates.
|
||||
//!
|
||||
//! ## Consensus Updates
|
||||
//!
|
||||
//! * [`Call::submit`]: Submit a finalized beacon header with an optional sync committee update
|
||||
#![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(test)]
|
||||
mod tests;
|
||||
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
mod benchmarking;
|
||||
|
||||
use frame_support::{
|
||||
dispatch::{DispatchResult, PostDispatchInfo},
|
||||
pallet_prelude::OptionQuery,
|
||||
traits::Get,
|
||||
transactional,
|
||||
};
|
||||
use frame_system::ensure_signed;
|
||||
use snowbridge_beacon_primitives::{
|
||||
fast_aggregate_verify,
|
||||
merkle_proof::{generalized_index_length, subtree_index},
|
||||
verify_merkle_branch, BeaconHeader, BlsError, CompactBeaconState, 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,
|
||||
};
|
||||
use types::{CheckpointUpdate, FinalizedBeaconStateBuffer, SyncCommitteePrepared, Update};
|
||||
|
||||
pub use pallet::*;
|
||||
|
||||
pub use config::SLOTS_PER_HISTORICAL_ROOT;
|
||||
|
||||
pub const LOG_TARGET: &str = "ethereum-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 {
|
||||
const MAX_REDUNDANCY: u32 = 20;
|
||||
config::EPOCHS_PER_SYNC_COMMITTEE_PERIOD as u32 * MAX_REDUNDANCY
|
||||
}
|
||||
}
|
||||
|
||||
#[pallet::pallet]
|
||||
pub struct Pallet<T>(_);
|
||||
|
||||
#[pallet::config]
|
||||
pub trait Config: frame_system::Config {
|
||||
#[allow(deprecated)]
|
||||
type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
|
||||
#[pallet::constant]
|
||||
type ForkVersions: Get<ForkVersions>;
|
||||
/// Minimum gap between finalized headers for an update to be free.
|
||||
#[pallet::constant]
|
||||
type FreeHeadersInterval: 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,
|
||||
},
|
||||
SyncCommitteeUpdated {
|
||||
period: u64,
|
||||
},
|
||||
/// Set OperatingMode
|
||||
OperatingModeChanged {
|
||||
mode: BasicOperatingMode,
|
||||
},
|
||||
}
|
||||
|
||||
#[pallet::error]
|
||||
pub enum Error<T> {
|
||||
SkippedSyncCommitteePeriod,
|
||||
SyncCommitteeUpdateRequired,
|
||||
/// Attested header is older than latest finalized header.
|
||||
IrrelevantUpdate,
|
||||
NotBootstrapped,
|
||||
SyncCommitteeParticipantsNotSupermajority,
|
||||
InvalidHeaderMerkleProof,
|
||||
InvalidSyncCommitteeMerkleProof,
|
||||
InvalidExecutionHeaderProof,
|
||||
InvalidAncestryMerkleProof,
|
||||
InvalidBlockRootsRootMerkleProof,
|
||||
/// The gap between the finalized headers is larger than the sync committee period,
|
||||
/// rendering execution headers unprovable using ancestry proofs (blocks root size is
|
||||
/// the same as the sync committee period slots).
|
||||
InvalidFinalizedHeaderGap,
|
||||
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 type InitialCheckpointRoot<T: Config> = StorageValue<_, H256, ValueQuery>;
|
||||
|
||||
/// Latest imported finalized block root
|
||||
#[pallet::storage]
|
||||
#[pallet::getter(fn latest_finalized_block_root)]
|
||||
pub type LatestFinalizedBlockRoot<T: Config> = StorageValue<_, H256, ValueQuery>;
|
||||
|
||||
/// Beacon state by finalized block root
|
||||
#[pallet::storage]
|
||||
#[pallet::getter(fn finalized_beacon_state)]
|
||||
pub type FinalizedBeaconState<T: Config> =
|
||||
StorageMap<_, Identity, H256, CompactBeaconState, OptionQuery>;
|
||||
|
||||
/// Finalized Headers: Current position in ring buffer
|
||||
#[pallet::storage]
|
||||
pub type FinalizedBeaconStateIndex<T: Config> = StorageValue<_, u32, ValueQuery>;
|
||||
|
||||
/// Finalized Headers: Mapping of ring buffer index to a pruning candidate
|
||||
#[pallet::storage]
|
||||
pub type FinalizedBeaconStateMapping<T: Config> =
|
||||
StorageMap<_, Identity, u32, H256, ValueQuery>;
|
||||
|
||||
#[pallet::storage]
|
||||
#[pallet::getter(fn validators_root)]
|
||||
pub type ValidatorsRoot<T: Config> = StorageValue<_, H256, ValueQuery>;
|
||||
|
||||
/// Sync committee for current period
|
||||
#[pallet::storage]
|
||||
pub type CurrentSyncCommittee<T: Config> = StorageValue<_, SyncCommitteePrepared, ValueQuery>;
|
||||
|
||||
/// Sync committee for next period
|
||||
#[pallet::storage]
|
||||
pub type NextSyncCommittee<T: Config> = StorageValue<_, SyncCommitteePrepared, ValueQuery>;
|
||||
|
||||
/// The last period where the next sync committee was updated for free.
|
||||
#[pallet::storage]
|
||||
pub type LatestSyncCommitteeUpdatePeriod<T: Config> = StorageValue<_, u64, 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>) -> DispatchResultWithPostInfo {
|
||||
ensure_signed(origin)?;
|
||||
ensure!(!Self::operating_mode().is_halted(), Error::<T>::Halted);
|
||||
Self::process_update(&update)
|
||||
}
|
||||
|
||||
/// 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)?;
|
||||
|
||||
let fork_versions = T::ForkVersions::get();
|
||||
let sync_committee_gindex = Self::current_sync_committee_gindex_at_slot(
|
||||
update.header.slot,
|
||||
fork_versions.clone(),
|
||||
);
|
||||
// Verifies the sync committee in the Beacon state.
|
||||
ensure!(
|
||||
verify_merkle_branch(
|
||||
sync_committee_root,
|
||||
&update.current_sync_committee_branch,
|
||||
subtree_index(sync_committee_gindex),
|
||||
generalized_index_length(sync_committee_gindex),
|
||||
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.
|
||||
let block_roots_gindex =
|
||||
Self::block_roots_gindex_at_slot(update.header.slot, fork_versions);
|
||||
ensure!(
|
||||
verify_merkle_branch(
|
||||
update.block_roots_root,
|
||||
&update.block_roots_branch,
|
||||
subtree_index(block_roots_gindex),
|
||||
generalized_index_length(block_roots_gindex),
|
||||
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);
|
||||
|
||||
Self::store_validators_root(update.validators_root);
|
||||
Self::store_finalized_header(update.header, update.block_roots_root)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn process_update(update: &Update) -> DispatchResultWithPostInfo {
|
||||
Self::verify_update(update)?;
|
||||
Self::apply_update(update)
|
||||
}
|
||||
|
||||
/// 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_finalized_period = compute_period(update.finalized_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 the finalized header gap between the current finalized header and new imported
|
||||
// header is not larger than the sync committee period, otherwise we cannot do
|
||||
// ancestry proofs for execution headers in the gap.
|
||||
ensure!(
|
||||
latest_finalized_state
|
||||
.slot
|
||||
.saturating_add(config::SLOTS_PER_HISTORICAL_ROOT as u64) >=
|
||||
update.finalized_header.slot,
|
||||
Error::<T>::InvalidFinalizedHeaderGap
|
||||
);
|
||||
|
||||
let fork_versions = T::ForkVersions::get();
|
||||
let finalized_root_gindex = Self::finalized_root_gindex_at_slot(
|
||||
update.attested_header.slot,
|
||||
fork_versions.clone(),
|
||||
);
|
||||
// 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,
|
||||
subtree_index(finalized_root_gindex),
|
||||
generalized_index_length(finalized_root_gindex),
|
||||
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`.
|
||||
let block_roots_gindex = Self::block_roots_gindex_at_slot(
|
||||
update.finalized_header.slot,
|
||||
fork_versions.clone(),
|
||||
);
|
||||
ensure!(
|
||||
verify_merkle_branch(
|
||||
update.block_roots_root,
|
||||
&update.block_roots_branch,
|
||||
subtree_index(block_roots_gindex),
|
||||
generalized_index_length(block_roots_gindex),
|
||||
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
|
||||
);
|
||||
}
|
||||
let next_sync_committee_gindex = Self::next_sync_committee_gindex_at_slot(
|
||||
update.attested_header.slot,
|
||||
fork_versions,
|
||||
);
|
||||
ensure!(
|
||||
verify_merkle_branch(
|
||||
sync_committee_root,
|
||||
&next_sync_committee_update.next_sync_committee_branch,
|
||||
subtree_index(next_sync_committee_gindex),
|
||||
generalized_index_length(next_sync_committee_gindex),
|
||||
update.attested_header.state_root
|
||||
),
|
||||
Error::<T>::InvalidSyncCommitteeMerkleProof
|
||||
);
|
||||
} else {
|
||||
ensure!(
|
||||
update_finalized_period == store_period,
|
||||
Error::<T>::SyncCommitteeUpdateRequired
|
||||
);
|
||||
}
|
||||
|
||||
// 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. Updates are free
|
||||
/// if the certain conditions specified in `check_refundable` are met.
|
||||
fn apply_update(update: &Update) -> DispatchResultWithPostInfo {
|
||||
let latest_finalized_state =
|
||||
FinalizedBeaconState::<T>::get(LatestFinalizedBlockRoot::<T>::get())
|
||||
.ok_or(Error::<T>::NotBootstrapped)?;
|
||||
|
||||
let pays_fee = Self::check_refundable(update, latest_finalized_state.slot);
|
||||
let actual_weight = match update.next_sync_committee_update {
|
||||
None => T::WeightInfo::submit(),
|
||||
Some(_) => T::WeightInfo::submit_with_sync_committee(),
|
||||
};
|
||||
|
||||
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);
|
||||
}
|
||||
tracing::info!(
|
||||
target: LOG_TARGET,
|
||||
period=%update_finalized_period,
|
||||
"💫 SyncCommitteeUpdated."
|
||||
);
|
||||
<LatestSyncCommitteeUpdatePeriod<T>>::set(update_finalized_period);
|
||||
Self::deposit_event(Event::SyncCommitteeUpdated {
|
||||
period: update_finalized_period,
|
||||
});
|
||||
};
|
||||
|
||||
if update.finalized_header.slot > latest_finalized_state.slot {
|
||||
Self::store_finalized_header(update.finalized_header, update.block_roots_root)?;
|
||||
}
|
||||
|
||||
Ok(PostDispatchInfo { actual_weight: Some(actual_weight), pays_fee })
|
||||
}
|
||||
|
||||
/// 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.
|
||||
pub fn store_finalized_header(
|
||||
header: BeaconHeader,
|
||||
block_roots_root: H256,
|
||||
) -> DispatchResult {
|
||||
let slot = header.slot;
|
||||
|
||||
let header_root: H256 =
|
||||
header.hash_tree_root().map_err(|_| Error::<T>::HeaderHashTreeRootFailed)?;
|
||||
|
||||
<FinalizedBeaconStateBuffer<T>>::insert(
|
||||
header_root,
|
||||
CompactBeaconState { slot: header.slot, block_roots_root },
|
||||
);
|
||||
<LatestFinalizedBlockRoot<T>>::set(header_root);
|
||||
|
||||
tracing::info!(
|
||||
target: LOG_TARGET,
|
||||
root=%header_root,
|
||||
%slot,
|
||||
"💫 Updated latest finalized block."
|
||||
);
|
||||
|
||||
Self::deposit_event(Event::BeaconHeaderImported { block_hash: header_root, slot });
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 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.fulu.epoch {
|
||||
return fork_versions.fulu.version;
|
||||
}
|
||||
if epoch >= fork_versions.electra.epoch {
|
||||
return fork_versions.electra.version;
|
||||
}
|
||||
if epoch >= fork_versions.deneb.epoch {
|
||||
return fork_versions.deneb.version;
|
||||
}
|
||||
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.clone());
|
||||
}
|
||||
}
|
||||
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 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)
|
||||
}
|
||||
|
||||
/// Updates are free if the update is successful and the interval between the latest
|
||||
/// finalized header in storage and the newly imported header is large enough. All
|
||||
/// successful sync committee updates are free.
|
||||
pub(super) fn check_refundable(update: &Update, latest_slot: u64) -> Pays {
|
||||
// If the sync committee was successfully updated, the update may be free.
|
||||
let update_period = compute_period(update.finalized_header.slot);
|
||||
let latest_free_update_period = LatestSyncCommitteeUpdatePeriod::<T>::get();
|
||||
// If the next sync committee is not known and this update sets it, the update is free.
|
||||
// If the sync committee update is in a period that we have not received an update for,
|
||||
// the update is free.
|
||||
let refundable =
|
||||
!<NextSyncCommittee<T>>::exists() || update_period > latest_free_update_period;
|
||||
if update.next_sync_committee_update.is_some() && refundable {
|
||||
return Pays::No;
|
||||
}
|
||||
|
||||
// If the latest finalized header is larger than the minimum slot interval, the header
|
||||
// import transaction is free.
|
||||
if update.finalized_header.slot >=
|
||||
latest_slot.saturating_add(T::FreeHeadersInterval::get() as u64)
|
||||
{
|
||||
return Pays::No;
|
||||
}
|
||||
|
||||
Pays::Yes
|
||||
}
|
||||
|
||||
pub fn finalized_root_gindex_at_slot(slot: u64, fork_versions: ForkVersions) -> usize {
|
||||
let epoch = compute_epoch(slot, config::SLOTS_PER_EPOCH as u64);
|
||||
|
||||
if epoch >= fork_versions.electra.epoch {
|
||||
return config::electra::FINALIZED_ROOT_INDEX;
|
||||
}
|
||||
|
||||
config::altair::FINALIZED_ROOT_INDEX
|
||||
}
|
||||
|
||||
pub fn current_sync_committee_gindex_at_slot(
|
||||
slot: u64,
|
||||
fork_versions: ForkVersions,
|
||||
) -> usize {
|
||||
let epoch = compute_epoch(slot, config::SLOTS_PER_EPOCH as u64);
|
||||
|
||||
if epoch >= fork_versions.electra.epoch {
|
||||
return config::electra::CURRENT_SYNC_COMMITTEE_INDEX;
|
||||
}
|
||||
|
||||
config::altair::CURRENT_SYNC_COMMITTEE_INDEX
|
||||
}
|
||||
|
||||
pub fn next_sync_committee_gindex_at_slot(slot: u64, fork_versions: ForkVersions) -> usize {
|
||||
let epoch = compute_epoch(slot, config::SLOTS_PER_EPOCH as u64);
|
||||
|
||||
if epoch >= fork_versions.electra.epoch {
|
||||
return config::electra::NEXT_SYNC_COMMITTEE_INDEX;
|
||||
}
|
||||
|
||||
config::altair::NEXT_SYNC_COMMITTEE_INDEX
|
||||
}
|
||||
|
||||
pub fn block_roots_gindex_at_slot(slot: u64, fork_versions: ForkVersions) -> usize {
|
||||
let epoch = compute_epoch(slot, config::SLOTS_PER_EPOCH as u64);
|
||||
|
||||
if epoch >= fork_versions.electra.epoch {
|
||||
return config::electra::BLOCK_ROOTS_INDEX;
|
||||
}
|
||||
|
||||
config::altair::BLOCK_ROOTS_INDEX
|
||||
}
|
||||
|
||||
pub fn execution_header_gindex() -> usize {
|
||||
config::altair::EXECUTION_HEADER_INDEX
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,170 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-FileCopyrightText: 2023 Snowfork <hello@snowfork.com>
|
||||
use crate as ethereum_beacon_client;
|
||||
use crate::config;
|
||||
use frame_support::{derive_impl, dispatch::DispatchResult, parameter_types};
|
||||
use pallet_timestamp;
|
||||
use snowbridge_beacon_primitives::{Fork, ForkVersions};
|
||||
use snowbridge_verification_primitives::{Log, Proof};
|
||||
use sp_std::default::Default;
|
||||
use std::{fs::File, path::PathBuf};
|
||||
|
||||
type Block = frame_system::mocking::MockBlock<Test>;
|
||||
use frame_support::traits::ConstU32;
|
||||
use hex_literal::hex;
|
||||
use sp_runtime::BuildStorage;
|
||||
|
||||
fn load_fixture<T>(basename: String) -> 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_proof_fixture() -> snowbridge_beacon_primitives::ExecutionProof {
|
||||
load_fixture("execution-proof.json".to_string()).unwrap()
|
||||
}
|
||||
|
||||
pub fn load_checkpoint_update_fixture(
|
||||
) -> snowbridge_beacon_primitives::CheckpointUpdate<{ config::SYNC_COMMITTEE_SIZE }> {
|
||||
load_fixture("initial-checkpoint.json".to_string()).unwrap()
|
||||
}
|
||||
|
||||
pub fn load_sync_committee_update_fixture() -> snowbridge_beacon_primitives::Update<
|
||||
{ config::SYNC_COMMITTEE_SIZE },
|
||||
{ config::SYNC_COMMITTEE_BITS_SIZE },
|
||||
> {
|
||||
load_fixture("sync-committee-update.json".to_string()).unwrap()
|
||||
}
|
||||
|
||||
pub fn load_finalized_header_update_fixture() -> snowbridge_beacon_primitives::Update<
|
||||
{ config::SYNC_COMMITTEE_SIZE },
|
||||
{ config::SYNC_COMMITTEE_BITS_SIZE },
|
||||
> {
|
||||
load_fixture("finalized-header-update.json".to_string()).unwrap()
|
||||
}
|
||||
|
||||
pub fn load_next_sync_committee_update_fixture() -> snowbridge_beacon_primitives::Update<
|
||||
{ config::SYNC_COMMITTEE_SIZE },
|
||||
{ config::SYNC_COMMITTEE_BITS_SIZE },
|
||||
> {
|
||||
load_fixture("next-sync-committee-update.json".to_string()).unwrap()
|
||||
}
|
||||
|
||||
pub fn load_next_finalized_header_update_fixture() -> snowbridge_beacon_primitives::Update<
|
||||
{ config::SYNC_COMMITTEE_SIZE },
|
||||
{ config::SYNC_COMMITTEE_BITS_SIZE },
|
||||
> {
|
||||
load_fixture("next-finalized-header-update.json".to_string()).unwrap()
|
||||
}
|
||||
|
||||
pub fn load_sync_committee_update_period_0() -> Box<
|
||||
snowbridge_beacon_primitives::Update<
|
||||
{ config::SYNC_COMMITTEE_SIZE },
|
||||
{ config::SYNC_COMMITTEE_BITS_SIZE },
|
||||
>,
|
||||
> {
|
||||
Box::new(load_fixture("sync-committee-update-period-0.json".to_string()).unwrap())
|
||||
}
|
||||
|
||||
pub fn load_sync_committee_update_period_0_older_fixture() -> Box<
|
||||
snowbridge_beacon_primitives::Update<
|
||||
{ config::SYNC_COMMITTEE_SIZE },
|
||||
{ config::SYNC_COMMITTEE_BITS_SIZE },
|
||||
>,
|
||||
> {
|
||||
Box::new(load_fixture("sync-committee-update-period-0-older.json".to_string()).unwrap())
|
||||
}
|
||||
|
||||
pub fn load_sync_committee_update_period_0_newer_fixture() -> Box<
|
||||
snowbridge_beacon_primitives::Update<
|
||||
{ config::SYNC_COMMITTEE_SIZE },
|
||||
{ config::SYNC_COMMITTEE_BITS_SIZE },
|
||||
>,
|
||||
> {
|
||||
Box::new(load_fixture("sync-committee-update-period-0-newer.json".to_string()).unwrap())
|
||||
}
|
||||
|
||||
pub fn get_message_verification_payload() -> (Log, Proof) {
|
||||
let inbound_fixture = snowbridge_pallet_ethereum_client_fixtures::make_inbound_fixture();
|
||||
(inbound_fixture.event.event_log, inbound_fixture.event.proof)
|
||||
}
|
||||
|
||||
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>},
|
||||
}
|
||||
);
|
||||
|
||||
#[derive_impl(frame_system::config_preludes::TestDefaultConfig)]
|
||||
impl frame_system::Config for Test {
|
||||
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: hex!("00000000"),
|
||||
epoch: 0,
|
||||
},
|
||||
altair: Fork {
|
||||
version: hex!("01000000"),
|
||||
epoch: 0,
|
||||
},
|
||||
bellatrix: Fork {
|
||||
version: hex!("02000000"),
|
||||
epoch: 0,
|
||||
},
|
||||
capella: Fork {
|
||||
version: hex!("03000000"),
|
||||
epoch: 0,
|
||||
},
|
||||
deneb: Fork {
|
||||
version: hex!("04000000"),
|
||||
epoch: 0,
|
||||
},
|
||||
electra: Fork {
|
||||
version: hex!("05000000"),
|
||||
epoch: 0,
|
||||
},
|
||||
fulu: Fork {
|
||||
version: hex!("06000000"),
|
||||
epoch: 100000000,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub const FREE_SLOTS_INTERVAL: u32 = config::SLOTS_PER_EPOCH as u32;
|
||||
|
||||
impl ethereum_beacon_client::Config for Test {
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type ForkVersions = ChainForkVersions;
|
||||
type FreeHeadersInterval = ConstU32<FREE_SLOTS_INTERVAL>;
|
||||
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 ext = sp_io::TestExternalities::new(t);
|
||||
ext
|
||||
}
|
||||
|
||||
pub fn initialize_storage() -> DispatchResult {
|
||||
let inbound_fixture = snowbridge_pallet_ethereum_client_fixtures::make_inbound_fixture();
|
||||
EthereumBeaconClient::store_finalized_header(
|
||||
inbound_fixture.finalized_header,
|
||||
inbound_fixture.block_roots_root,
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,976 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-FileCopyrightText: 2023 Snowfork <hello@snowfork.com>
|
||||
pub use crate::mock::*;
|
||||
use crate::{
|
||||
config::{EPOCHS_PER_SYNC_COMMITTEE_PERIOD, SLOTS_PER_EPOCH, SLOTS_PER_HISTORICAL_ROOT},
|
||||
functions::compute_period,
|
||||
mock::{
|
||||
get_message_verification_payload, load_checkpoint_update_fixture,
|
||||
load_finalized_header_update_fixture, load_next_finalized_header_update_fixture,
|
||||
load_next_sync_committee_update_fixture, load_sync_committee_update_fixture,
|
||||
},
|
||||
sync_committee_sum, verify_merkle_branch, BeaconHeader, CompactBeaconState, Error,
|
||||
FinalizedBeaconState, LatestFinalizedBlockRoot, LatestSyncCommitteeUpdatePeriod,
|
||||
NextSyncCommittee, SyncCommitteePrepared,
|
||||
};
|
||||
use frame_support::{assert_err, assert_noop, assert_ok, pallet_prelude::Pays};
|
||||
use hex_literal::hex;
|
||||
use snowbridge_beacon_primitives::{
|
||||
merkle_proof::{generalized_index_length, subtree_index},
|
||||
types::deneb,
|
||||
Fork, ForkVersions, NextSyncCommitteeUpdate, VersionedExecutionPayloadHeader,
|
||||
};
|
||||
use snowbridge_verification_primitives::{VerificationError, Verifier};
|
||||
use sp_core::H256;
|
||||
use sp_runtime::DispatchError;
|
||||
|
||||
/// Arbitrary hash used for tests and invalid hashes.
|
||||
const TEST_HASH: [u8; 32] =
|
||||
hex!["5f6f02af29218292d21a69b64a794a7c0873b3e0f54611972863706e8cbdf371"];
|
||||
|
||||
/* UNIT TESTS */
|
||||
|
||||
#[test]
|
||||
pub fn sum_sync_committee_participation() {
|
||||
new_tester().execute_with(|| {
|
||||
assert_eq!(sync_committee_sum(&[0, 1, 0, 1, 1, 0, 1, 0, 1]), 5);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn compute_domain() {
|
||||
new_tester().execute_with(|| {
|
||||
let domain = EthereumBeaconClient::compute_domain(
|
||||
hex!("07000000").into(),
|
||||
hex!("00000001"),
|
||||
hex!("5dec7ae03261fde20d5b024dfabce8bac3276c9a4908e23d50ba8c9b50b0adff").into(),
|
||||
);
|
||||
|
||||
assert_ok!(&domain);
|
||||
assert_eq!(
|
||||
domain.unwrap(),
|
||||
hex!("0700000046324489ceb6ada6d118eacdbe94f49b1fcb49d5481a685979670c7c").into()
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn compute_signing_root_bls() {
|
||||
new_tester().execute_with(|| {
|
||||
let signing_root = EthereumBeaconClient::compute_signing_root(
|
||||
&BeaconHeader {
|
||||
slot: 3529537,
|
||||
proposer_index: 192549,
|
||||
parent_root: hex!(
|
||||
"1f8dc05ea427f78e84e2e2666e13c3befb7106fd1d40ef8a3f67cf615f3f2a4c"
|
||||
)
|
||||
.into(),
|
||||
state_root: hex!(
|
||||
"0dfb492a83da711996d2d76b64604f9bca9dc08b6c13cf63b3be91742afe724b"
|
||||
)
|
||||
.into(),
|
||||
body_root: hex!("66fba38f7c8c2526f7ddfe09c1a54dd12ff93bdd4d0df6a0950e88e802228bfa")
|
||||
.into(),
|
||||
},
|
||||
hex!("07000000afcaaba0efab1ca832a15152469bb09bb84641c405171dfa2d3fb45f").into(),
|
||||
);
|
||||
|
||||
assert_ok!(&signing_root);
|
||||
assert_eq!(
|
||||
signing_root.unwrap(),
|
||||
hex!("3ff6e9807da70b2f65cdd58ea1b25ed441a1d589025d2c4091182026d7af08fb").into()
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn compute_signing_root() {
|
||||
new_tester().execute_with(|| {
|
||||
let signing_root = EthereumBeaconClient::compute_signing_root(
|
||||
&BeaconHeader {
|
||||
slot: 222472,
|
||||
proposer_index: 10726,
|
||||
parent_root: hex!(
|
||||
"5d481a9721f0ecce9610eab51d400d223683d599b7fcebca7e4c4d10cdef6ebb"
|
||||
)
|
||||
.into(),
|
||||
state_root: hex!(
|
||||
"14eb4575895f996a84528b789ff2e4d5148242e2983f03068353b2c37015507a"
|
||||
)
|
||||
.into(),
|
||||
body_root: hex!("7bb669c75b12e0781d6fa85d7fc2f32d64eafba89f39678815b084c156e46cac")
|
||||
.into(),
|
||||
},
|
||||
hex!("07000000e7acb21061790987fa1c1e745cccfb358370b33e8af2b2c18938e6c2").into(),
|
||||
);
|
||||
|
||||
assert_ok!(&signing_root);
|
||||
assert_eq!(
|
||||
signing_root.unwrap(),
|
||||
hex!("da12b6a6d3516bc891e8a49f82fc1925cec40b9327e06457f695035303f55cd8").into()
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn compute_domain_bls() {
|
||||
new_tester().execute_with(|| {
|
||||
let domain = EthereumBeaconClient::compute_domain(
|
||||
hex!("07000000").into(),
|
||||
hex!("01000000"),
|
||||
hex!("4b363db94e286120d76eb905340fdd4e54bfe9f06bf33ff6cf5ad27f511bfe95").into(),
|
||||
);
|
||||
|
||||
assert_ok!(&domain);
|
||||
assert_eq!(
|
||||
domain.unwrap(),
|
||||
hex!("07000000afcaaba0efab1ca832a15152469bb09bb84641c405171dfa2d3fb45f").into()
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn may_refund_call_fee() {
|
||||
let finalized_update = Box::new(load_next_finalized_header_update_fixture());
|
||||
let sync_committee_update = Box::new(load_sync_committee_update_fixture());
|
||||
new_tester().execute_with(|| {
|
||||
let free_headers_interval: u64 = crate::mock::FREE_SLOTS_INTERVAL as u64;
|
||||
// Not free, smaller than the allowed free header interval
|
||||
assert_eq!(
|
||||
EthereumBeaconClient::check_refundable(
|
||||
&finalized_update.clone(),
|
||||
finalized_update.finalized_header.slot + free_headers_interval
|
||||
),
|
||||
Pays::Yes
|
||||
);
|
||||
// Is free, larger than the minimum interval
|
||||
assert_eq!(
|
||||
EthereumBeaconClient::check_refundable(
|
||||
&finalized_update,
|
||||
finalized_update.finalized_header.slot - (free_headers_interval + 2)
|
||||
),
|
||||
Pays::No
|
||||
);
|
||||
// Is free, valid sync committee update
|
||||
assert_eq!(
|
||||
EthereumBeaconClient::check_refundable(
|
||||
&sync_committee_update,
|
||||
finalized_update.finalized_header.slot
|
||||
),
|
||||
Pays::No
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn verify_merkle_branch_for_finalized_root() {
|
||||
new_tester().execute_with(|| {
|
||||
assert!(verify_merkle_branch(
|
||||
hex!("0000000000000000000000000000000000000000000000000000000000000000").into(),
|
||||
&[
|
||||
hex!("0000000000000000000000000000000000000000000000000000000000000000").into(),
|
||||
hex!("5f6f02af29218292d21a69b64a794a7c0873b3e0f54611972863706e8cbdf371").into(),
|
||||
hex!("e7125ff9ab5a840c44bedb4731f440a405b44e15f2d1a89e27341b432fabe13d").into(),
|
||||
hex!("002c1fe5bc0bd62db6f299a582f2a80a6d5748ccc82e7ed843eaf0ae0739f74a").into(),
|
||||
hex!("d2dc4ba9fd4edff6716984136831e70a6b2e74fca27b8097a820cbbaa5a6e3c3").into(),
|
||||
hex!("91f77a19d8afa4a08e81164bb2e570ecd10477b3b65c305566a6d2be88510584").into(),
|
||||
],
|
||||
subtree_index(crate::config::altair::FINALIZED_ROOT_INDEX),
|
||||
generalized_index_length(crate::config::altair::FINALIZED_ROOT_INDEX),
|
||||
hex!("e46559327592741956f6beaa0f52e49625eb85dce037a0bd2eff333c743b287f").into()
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn verify_merkle_branch_fails_if_depth_and_branch_dont_match() {
|
||||
new_tester().execute_with(|| {
|
||||
assert!(!verify_merkle_branch(
|
||||
hex!("0000000000000000000000000000000000000000000000000000000000000000").into(),
|
||||
&[
|
||||
hex!("0000000000000000000000000000000000000000000000000000000000000000").into(),
|
||||
hex!("5f6f02af29218292d21a69b64a794a7c0873b3e0f54611972863706e8cbdf371").into(),
|
||||
hex!("e7125ff9ab5a840c44bedb4731f440a405b44e15f2d1a89e27341b432fabe13d").into(),
|
||||
],
|
||||
subtree_index(crate::config::altair::FINALIZED_ROOT_INDEX),
|
||||
generalized_index_length(crate::config::altair::FINALIZED_ROOT_INDEX),
|
||||
hex!("e46559327592741956f6beaa0f52e49625eb85dce037a0bd2eff333c743b287f").into()
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn sync_committee_participation_is_supermajority() {
|
||||
let bits =
|
||||
hex!("bffffffff7f1ffdfcfeffeffbfdffffbfffffdffffefefffdffff7f7ffff77fffdf7bff77ffdf7fffafffffff77fefffeff7effffffff5f7fedfffdfb6ddff7b"
|
||||
);
|
||||
let participation =
|
||||
snowbridge_beacon_primitives::decompress_sync_committee_bits::<512, 64>(bits);
|
||||
assert_ok!(EthereumBeaconClient::sync_committee_participation_is_supermajority(&participation));
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn sync_committee_participation_is_supermajority_errors_when_not_supermajority() {
|
||||
new_tester().execute_with(|| {
|
||||
let participation = hex!("0000000000000000000000000000000000000001010100010100000000000000000000000101010101000100010101010101010101010101010101010100010101000000000001010101010100010101000000000000000000000000000101000101010101010001010101010100010101010101010101010101000101010101010100010101010100000000010101010100000000000000000001010101010101010101010101010101010100010101010101010001010101010101010101010101010101000101010101010101010101010100010101010101010101010101010101010101010101010101010101010101010001010100010101010101010101000101010101010101010001010101010101010101000101010100010101010101010101010100010000000000000000000100000000000001010100000001000100010101010100000000000000000000000000000000000000010101010101010100010101010101010101010100010101010001010101010101010101010101010100000000000000000101010101000000000001000000000000000000010000000000000000000101010101010100010001010101010101000101010101010101010101010101010101000101010101010101010101010101010001010101010101010001010001000000000000000000000000000001000000000000");
|
||||
|
||||
assert_err!(
|
||||
EthereumBeaconClient::sync_committee_participation_is_supermajority(&participation),
|
||||
Error::<Test>::SyncCommitteeParticipantsNotSupermajority
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn compute_fork_version() {
|
||||
let mock_fork_versions = ForkVersions {
|
||||
genesis: Fork { version: [0, 0, 0, 0], epoch: 0 },
|
||||
altair: Fork { version: [0, 0, 0, 1], epoch: 10 },
|
||||
bellatrix: Fork { version: [0, 0, 0, 2], epoch: 20 },
|
||||
capella: Fork { version: [0, 0, 0, 3], epoch: 30 },
|
||||
deneb: Fork { version: [0, 0, 0, 4], epoch: 40 },
|
||||
electra: Fork { version: [0, 0, 0, 5], epoch: 50 },
|
||||
fulu: Fork { version: [0, 0, 0, 6], epoch: 60 },
|
||||
};
|
||||
new_tester().execute_with(|| {
|
||||
assert_eq!(EthereumBeaconClient::select_fork_version(&mock_fork_versions, 0), [0, 0, 0, 0]);
|
||||
assert_eq!(EthereumBeaconClient::select_fork_version(&mock_fork_versions, 1), [0, 0, 0, 0]);
|
||||
assert_eq!(
|
||||
EthereumBeaconClient::select_fork_version(&mock_fork_versions, 10),
|
||||
[0, 0, 0, 1]
|
||||
);
|
||||
assert_eq!(
|
||||
EthereumBeaconClient::select_fork_version(&mock_fork_versions, 21),
|
||||
[0, 0, 0, 2]
|
||||
);
|
||||
assert_eq!(
|
||||
EthereumBeaconClient::select_fork_version(&mock_fork_versions, 20),
|
||||
[0, 0, 0, 2]
|
||||
);
|
||||
assert_eq!(
|
||||
EthereumBeaconClient::select_fork_version(&mock_fork_versions, 32),
|
||||
[0, 0, 0, 3]
|
||||
);
|
||||
assert_eq!(
|
||||
EthereumBeaconClient::select_fork_version(&mock_fork_versions, 40),
|
||||
[0, 0, 0, 4]
|
||||
);
|
||||
assert_eq!(
|
||||
EthereumBeaconClient::select_fork_version(&mock_fork_versions, 50),
|
||||
[0, 0, 0, 5]
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn find_absent_keys() {
|
||||
let participation: [u8; 32] =
|
||||
hex!("0001010101010100010101010101010101010101010101010101010101010101").into();
|
||||
let update = load_sync_committee_update_fixture();
|
||||
let sync_committee_prepared: SyncCommitteePrepared =
|
||||
(&update.next_sync_committee_update.unwrap().next_sync_committee)
|
||||
.try_into()
|
||||
.unwrap();
|
||||
|
||||
new_tester().execute_with(|| {
|
||||
let pubkeys = EthereumBeaconClient::find_pubkeys(
|
||||
&participation,
|
||||
(*sync_committee_prepared.pubkeys).as_ref(),
|
||||
false,
|
||||
);
|
||||
assert_eq!(pubkeys.len(), 2);
|
||||
assert_eq!(pubkeys[0], sync_committee_prepared.pubkeys[0]);
|
||||
assert_eq!(pubkeys[1], sync_committee_prepared.pubkeys[7]);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn find_present_keys() {
|
||||
let participation: [u8; 32] =
|
||||
hex!("0001000000000000010000000000000000000000000000000000010000000100").into();
|
||||
let update = load_sync_committee_update_fixture();
|
||||
let sync_committee_prepared: SyncCommitteePrepared =
|
||||
(&update.next_sync_committee_update.unwrap().next_sync_committee)
|
||||
.try_into()
|
||||
.unwrap();
|
||||
|
||||
new_tester().execute_with(|| {
|
||||
let pubkeys = EthereumBeaconClient::find_pubkeys(
|
||||
&participation,
|
||||
(*sync_committee_prepared.pubkeys).as_ref(),
|
||||
true,
|
||||
);
|
||||
assert_eq!(pubkeys.len(), 4);
|
||||
assert_eq!(pubkeys[0], sync_committee_prepared.pubkeys[1]);
|
||||
assert_eq!(pubkeys[1], sync_committee_prepared.pubkeys[8]);
|
||||
assert_eq!(pubkeys[2], sync_committee_prepared.pubkeys[26]);
|
||||
assert_eq!(pubkeys[3], sync_committee_prepared.pubkeys[30]);
|
||||
});
|
||||
}
|
||||
|
||||
/* SYNC PROCESS TESTS */
|
||||
|
||||
#[test]
|
||||
fn process_initial_checkpoint() {
|
||||
let checkpoint = Box::new(load_checkpoint_update_fixture());
|
||||
|
||||
new_tester().execute_with(|| {
|
||||
assert_ok!(EthereumBeaconClient::force_checkpoint(
|
||||
RuntimeOrigin::root(),
|
||||
checkpoint.clone()
|
||||
));
|
||||
let block_root: H256 = checkpoint.header.hash_tree_root().unwrap();
|
||||
assert!(<FinalizedBeaconState<Test>>::contains_key(block_root));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn process_initial_checkpoint_with_invalid_sync_committee_proof() {
|
||||
let mut checkpoint = Box::new(load_checkpoint_update_fixture());
|
||||
checkpoint.current_sync_committee_branch[0] = TEST_HASH.into();
|
||||
|
||||
new_tester().execute_with(|| {
|
||||
assert_err!(
|
||||
EthereumBeaconClient::force_checkpoint(RuntimeOrigin::root(), checkpoint),
|
||||
Error::<Test>::InvalidSyncCommitteeMerkleProof
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn process_initial_checkpoint_with_invalid_blocks_root_proof() {
|
||||
let mut checkpoint = Box::new(load_checkpoint_update_fixture());
|
||||
checkpoint.block_roots_branch[0] = TEST_HASH.into();
|
||||
|
||||
new_tester().execute_with(|| {
|
||||
assert_err!(
|
||||
EthereumBeaconClient::force_checkpoint(RuntimeOrigin::root(), checkpoint),
|
||||
Error::<Test>::InvalidBlockRootsRootMerkleProof
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn submit_update_in_current_period() {
|
||||
let checkpoint = Box::new(load_checkpoint_update_fixture());
|
||||
let update = Box::new(load_finalized_header_update_fixture());
|
||||
let initial_period = compute_period(checkpoint.header.slot);
|
||||
let update_period = compute_period(update.finalized_header.slot);
|
||||
assert_eq!(initial_period, update_period);
|
||||
|
||||
new_tester().execute_with(|| {
|
||||
assert_ok!(EthereumBeaconClient::process_checkpoint_update(&checkpoint));
|
||||
let result = EthereumBeaconClient::submit(RuntimeOrigin::signed(1), update.clone());
|
||||
assert_ok!(result);
|
||||
assert_eq!(result.unwrap().pays_fee, Pays::No);
|
||||
let block_root: H256 = update.finalized_header.hash_tree_root().unwrap();
|
||||
assert!(<FinalizedBeaconState<Test>>::contains_key(block_root));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn submit_update_with_sync_committee_in_current_period() {
|
||||
let checkpoint = Box::new(load_checkpoint_update_fixture());
|
||||
let update = Box::new(load_sync_committee_update_fixture());
|
||||
let init_period = compute_period(checkpoint.header.slot);
|
||||
let update_period = compute_period(update.finalized_header.slot);
|
||||
assert_eq!(init_period, update_period);
|
||||
|
||||
new_tester().execute_with(|| {
|
||||
assert_ok!(EthereumBeaconClient::process_checkpoint_update(&checkpoint));
|
||||
assert!(!<NextSyncCommittee<Test>>::exists());
|
||||
let result = EthereumBeaconClient::submit(RuntimeOrigin::signed(1), update);
|
||||
assert_ok!(result);
|
||||
assert_eq!(result.unwrap().pays_fee, Pays::No);
|
||||
assert!(<NextSyncCommittee<Test>>::exists());
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reject_submit_update_in_next_period() {
|
||||
let checkpoint = Box::new(load_checkpoint_update_fixture());
|
||||
let sync_committee_update = Box::new(load_sync_committee_update_fixture());
|
||||
let update = Box::new(load_next_finalized_header_update_fixture());
|
||||
let sync_committee_period = compute_period(sync_committee_update.finalized_header.slot);
|
||||
let next_sync_committee_period = compute_period(update.finalized_header.slot);
|
||||
assert_eq!(sync_committee_period + 1, next_sync_committee_period);
|
||||
let next_sync_committee_update = Box::new(load_next_sync_committee_update_fixture());
|
||||
|
||||
new_tester().execute_with(|| {
|
||||
assert_ok!(EthereumBeaconClient::process_checkpoint_update(&checkpoint));
|
||||
let result =
|
||||
EthereumBeaconClient::submit(RuntimeOrigin::signed(1), sync_committee_update.clone());
|
||||
assert_ok!(result);
|
||||
assert_eq!(result.unwrap().pays_fee, Pays::No);
|
||||
|
||||
// check an update in the next period is rejected
|
||||
let second_result = EthereumBeaconClient::submit(RuntimeOrigin::signed(1), update.clone());
|
||||
assert_err!(second_result, Error::<Test>::SyncCommitteeUpdateRequired);
|
||||
assert_eq!(second_result.unwrap_err().post_info.pays_fee, Pays::Yes);
|
||||
|
||||
// submit update with next sync committee
|
||||
let third_result =
|
||||
EthereumBeaconClient::submit(RuntimeOrigin::signed(1), next_sync_committee_update);
|
||||
assert_ok!(third_result);
|
||||
assert_eq!(third_result.unwrap().pays_fee, Pays::No);
|
||||
// check same header in the next period can now be submitted successfully
|
||||
assert_ok!(EthereumBeaconClient::submit(RuntimeOrigin::signed(1), update.clone()));
|
||||
let block_root: H256 = update.finalized_header.clone().hash_tree_root().unwrap();
|
||||
assert!(<FinalizedBeaconState<Test>>::contains_key(block_root));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn submit_update_with_invalid_header_proof() {
|
||||
let checkpoint = Box::new(load_checkpoint_update_fixture());
|
||||
let mut update = Box::new(load_sync_committee_update_fixture());
|
||||
let init_period = compute_period(checkpoint.header.slot);
|
||||
let update_period = compute_period(update.finalized_header.slot);
|
||||
assert_eq!(init_period, update_period);
|
||||
update.finality_branch[0] = TEST_HASH.into();
|
||||
|
||||
new_tester().execute_with(|| {
|
||||
assert_ok!(EthereumBeaconClient::process_checkpoint_update(&checkpoint));
|
||||
assert!(!<NextSyncCommittee<Test>>::exists());
|
||||
let result = EthereumBeaconClient::submit(RuntimeOrigin::signed(1), update);
|
||||
assert_err!(result, Error::<Test>::InvalidHeaderMerkleProof);
|
||||
assert_eq!(result.unwrap_err().post_info.pays_fee, Pays::Yes);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn submit_update_with_invalid_block_roots_proof() {
|
||||
let checkpoint = Box::new(load_checkpoint_update_fixture());
|
||||
let mut update = Box::new(load_sync_committee_update_fixture());
|
||||
let init_period = compute_period(checkpoint.header.slot);
|
||||
let update_period = compute_period(update.finalized_header.slot);
|
||||
assert_eq!(init_period, update_period);
|
||||
update.block_roots_branch[0] = TEST_HASH.into();
|
||||
|
||||
new_tester().execute_with(|| {
|
||||
assert_ok!(EthereumBeaconClient::process_checkpoint_update(&checkpoint));
|
||||
assert!(!<NextSyncCommittee<Test>>::exists());
|
||||
let result = EthereumBeaconClient::submit(RuntimeOrigin::signed(1), update);
|
||||
assert_err!(result, Error::<Test>::InvalidBlockRootsRootMerkleProof);
|
||||
assert_eq!(result.unwrap_err().post_info.pays_fee, Pays::Yes);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn submit_update_with_invalid_next_sync_committee_proof() {
|
||||
let checkpoint = Box::new(load_checkpoint_update_fixture());
|
||||
let mut update = Box::new(load_sync_committee_update_fixture());
|
||||
let init_period = compute_period(checkpoint.header.slot);
|
||||
let update_period = compute_period(update.finalized_header.slot);
|
||||
assert_eq!(init_period, update_period);
|
||||
if let Some(ref mut next_sync_committee_update) = update.next_sync_committee_update {
|
||||
next_sync_committee_update.next_sync_committee_branch[0] = TEST_HASH.into();
|
||||
}
|
||||
|
||||
new_tester().execute_with(|| {
|
||||
assert_ok!(EthereumBeaconClient::process_checkpoint_update(&checkpoint));
|
||||
assert!(!<NextSyncCommittee<Test>>::exists());
|
||||
let result = EthereumBeaconClient::submit(RuntimeOrigin::signed(1), update);
|
||||
assert_err!(result, Error::<Test>::InvalidSyncCommitteeMerkleProof);
|
||||
assert_eq!(result.unwrap_err().post_info.pays_fee, Pays::Yes);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn submit_update_with_skipped_period() {
|
||||
let checkpoint = Box::new(load_checkpoint_update_fixture());
|
||||
let sync_committee_update = Box::new(load_sync_committee_update_fixture());
|
||||
let mut update = Box::new(load_next_finalized_header_update_fixture());
|
||||
update.signature_slot += (EPOCHS_PER_SYNC_COMMITTEE_PERIOD * SLOTS_PER_EPOCH) as u64;
|
||||
update.attested_header.slot = update.signature_slot - 1;
|
||||
|
||||
new_tester().execute_with(|| {
|
||||
assert_ok!(EthereumBeaconClient::process_checkpoint_update(&checkpoint));
|
||||
let result =
|
||||
EthereumBeaconClient::submit(RuntimeOrigin::signed(1), sync_committee_update.clone());
|
||||
assert_ok!(result);
|
||||
assert_eq!(result.unwrap().pays_fee, Pays::No);
|
||||
|
||||
let second_result = EthereumBeaconClient::submit(RuntimeOrigin::signed(1), update);
|
||||
assert_err!(second_result, Error::<Test>::SkippedSyncCommitteePeriod);
|
||||
assert_eq!(second_result.unwrap_err().post_info.pays_fee, Pays::Yes);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn submit_update_with_sync_committee_in_next_period() {
|
||||
let checkpoint = Box::new(load_checkpoint_update_fixture());
|
||||
let update = Box::new(load_sync_committee_update_fixture());
|
||||
let next_update = Box::new(load_next_sync_committee_update_fixture());
|
||||
let update_period = compute_period(update.finalized_header.slot);
|
||||
let next_update_period = compute_period(next_update.finalized_header.slot);
|
||||
assert_eq!(update_period + 1, next_update_period);
|
||||
|
||||
new_tester().execute_with(|| {
|
||||
assert_ok!(EthereumBeaconClient::process_checkpoint_update(&checkpoint));
|
||||
assert!(!<NextSyncCommittee<Test>>::exists());
|
||||
|
||||
let result = EthereumBeaconClient::submit(RuntimeOrigin::signed(1), update.clone());
|
||||
assert_ok!(result);
|
||||
assert_eq!(result.unwrap().pays_fee, Pays::No);
|
||||
assert!(<NextSyncCommittee<Test>>::exists());
|
||||
|
||||
let second_result =
|
||||
EthereumBeaconClient::submit(RuntimeOrigin::signed(1), next_update.clone());
|
||||
assert_ok!(second_result);
|
||||
assert_eq!(second_result.unwrap().pays_fee, Pays::No);
|
||||
let last_finalized_state =
|
||||
FinalizedBeaconState::<Test>::get(LatestFinalizedBlockRoot::<Test>::get()).unwrap();
|
||||
let last_synced_period = compute_period(last_finalized_state.slot);
|
||||
assert_eq!(last_synced_period, next_update_period);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn submit_update_with_sync_committee_invalid_signature_slot() {
|
||||
let checkpoint = Box::new(load_checkpoint_update_fixture());
|
||||
let mut update = Box::new(load_sync_committee_update_fixture());
|
||||
|
||||
new_tester().execute_with(|| {
|
||||
assert_ok!(EthereumBeaconClient::process_checkpoint_update(&checkpoint));
|
||||
|
||||
// makes an invalid update with signature_slot should be more than attested_slot
|
||||
update.signature_slot = update.attested_header.slot;
|
||||
|
||||
let result = EthereumBeaconClient::submit(RuntimeOrigin::signed(1), update);
|
||||
assert_err!(result, Error::<Test>::InvalidUpdateSlot);
|
||||
assert_eq!(result.unwrap_err().post_info.pays_fee, Pays::Yes);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn submit_update_with_skipped_sync_committee_period() {
|
||||
let checkpoint = Box::new(load_checkpoint_update_fixture());
|
||||
let finalized_update = Box::new(load_next_finalized_header_update_fixture());
|
||||
let checkpoint_period = compute_period(checkpoint.header.slot);
|
||||
let next_sync_committee_period = compute_period(finalized_update.finalized_header.slot);
|
||||
assert_eq!(checkpoint_period + 1, next_sync_committee_period);
|
||||
|
||||
new_tester().execute_with(|| {
|
||||
assert_ok!(EthereumBeaconClient::process_checkpoint_update(&checkpoint));
|
||||
let result = EthereumBeaconClient::submit(RuntimeOrigin::signed(1), finalized_update);
|
||||
assert_err!(result, Error::<Test>::SkippedSyncCommitteePeriod);
|
||||
assert_eq!(result.unwrap_err().post_info.pays_fee, Pays::Yes);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn submit_irrelevant_update() {
|
||||
let checkpoint = Box::new(load_checkpoint_update_fixture());
|
||||
let mut update = Box::new(load_next_finalized_header_update_fixture());
|
||||
|
||||
new_tester().execute_with(|| {
|
||||
assert_ok!(EthereumBeaconClient::process_checkpoint_update(&checkpoint));
|
||||
|
||||
// makes an invalid update where the attested_header slot value should be greater than the
|
||||
// checkpoint slot value
|
||||
update.finalized_header.slot = checkpoint.header.slot;
|
||||
update.attested_header.slot = checkpoint.header.slot;
|
||||
update.signature_slot = checkpoint.header.slot + 1;
|
||||
|
||||
let result = EthereumBeaconClient::submit(RuntimeOrigin::signed(1), update);
|
||||
assert_err!(result, Error::<Test>::IrrelevantUpdate);
|
||||
assert_eq!(result.unwrap_err().post_info.pays_fee, Pays::Yes);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn submit_update_with_missing_bootstrap() {
|
||||
let update = Box::new(load_next_finalized_header_update_fixture());
|
||||
|
||||
new_tester().execute_with(|| {
|
||||
let result = EthereumBeaconClient::submit(RuntimeOrigin::signed(1), update);
|
||||
assert_err!(result, Error::<Test>::NotBootstrapped);
|
||||
assert_eq!(result.unwrap_err().post_info.pays_fee, Pays::Yes);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn submit_update_with_invalid_sync_committee_update() {
|
||||
let checkpoint = Box::new(load_checkpoint_update_fixture());
|
||||
let update = Box::new(load_sync_committee_update_fixture());
|
||||
let mut next_update = Box::new(load_next_sync_committee_update_fixture());
|
||||
|
||||
new_tester().execute_with(|| {
|
||||
assert_ok!(EthereumBeaconClient::process_checkpoint_update(&checkpoint));
|
||||
|
||||
let result = EthereumBeaconClient::submit(RuntimeOrigin::signed(1), update);
|
||||
assert_ok!(result);
|
||||
assert_eq!(result.unwrap().pays_fee, Pays::No);
|
||||
|
||||
// makes update with invalid next_sync_committee
|
||||
<FinalizedBeaconState<Test>>::mutate(<LatestFinalizedBlockRoot<Test>>::get(), |x| {
|
||||
let prev = x.unwrap();
|
||||
*x = Some(CompactBeaconState { slot: next_update.attested_header.slot, ..prev });
|
||||
});
|
||||
next_update.attested_header.slot += 1;
|
||||
next_update.signature_slot = next_update.attested_header.slot + 1;
|
||||
let next_sync_committee = NextSyncCommitteeUpdate::default();
|
||||
next_update.next_sync_committee_update = Some(next_sync_committee);
|
||||
|
||||
let second_result = EthereumBeaconClient::submit(RuntimeOrigin::signed(1), next_update);
|
||||
assert_err!(second_result, Error::<Test>::InvalidSyncCommitteeUpdate);
|
||||
assert_eq!(second_result.unwrap_err().post_info.pays_fee, Pays::Yes);
|
||||
});
|
||||
}
|
||||
|
||||
/// Check that a gap of more than 8192 slots between finalized headers is not allowed.
|
||||
#[test]
|
||||
fn submit_finalized_header_update_with_too_large_gap() {
|
||||
let checkpoint = Box::new(load_checkpoint_update_fixture());
|
||||
let update = Box::new(load_sync_committee_update_fixture());
|
||||
let mut next_update = Box::new(load_next_sync_committee_update_fixture());
|
||||
|
||||
// Adds 8193 slots, so that the next update is still in the next sync committee, but the
|
||||
// gap between the finalized headers is more than 8192 slots.
|
||||
let slot_with_large_gap = checkpoint.header.slot + SLOTS_PER_HISTORICAL_ROOT as u64 + 1;
|
||||
|
||||
next_update.finalized_header.slot = slot_with_large_gap;
|
||||
// Adding some slots to the attested header and signature slot since they need to be ahead
|
||||
// of the finalized header.
|
||||
next_update.attested_header.slot = slot_with_large_gap + 33;
|
||||
next_update.signature_slot = slot_with_large_gap + 43;
|
||||
|
||||
new_tester().execute_with(|| {
|
||||
assert_ok!(EthereumBeaconClient::process_checkpoint_update(&checkpoint));
|
||||
let result = EthereumBeaconClient::submit(RuntimeOrigin::signed(1), update.clone());
|
||||
assert_ok!(result);
|
||||
assert_eq!(result.unwrap().pays_fee, Pays::No);
|
||||
assert!(<NextSyncCommittee<Test>>::exists());
|
||||
|
||||
let second_result =
|
||||
EthereumBeaconClient::submit(RuntimeOrigin::signed(1), next_update.clone());
|
||||
assert_err!(second_result, Error::<Test>::InvalidFinalizedHeaderGap);
|
||||
assert_eq!(second_result.unwrap_err().post_info.pays_fee, Pays::Yes);
|
||||
});
|
||||
}
|
||||
|
||||
/// Check that a gap of 8192 slots between finalized headers is allowed.
|
||||
#[test]
|
||||
fn submit_finalized_header_update_with_gap_at_limit() {
|
||||
let checkpoint = Box::new(load_checkpoint_update_fixture());
|
||||
let update = Box::new(load_sync_committee_update_fixture());
|
||||
let mut next_update = Box::new(load_next_sync_committee_update_fixture());
|
||||
|
||||
next_update.finalized_header.slot = checkpoint.header.slot + SLOTS_PER_HISTORICAL_ROOT as u64;
|
||||
// Adding some slots to the attested header and signature slot since they need to be ahead
|
||||
// of the finalized header.
|
||||
next_update.attested_header.slot =
|
||||
checkpoint.header.slot + SLOTS_PER_HISTORICAL_ROOT as u64 + 33;
|
||||
next_update.signature_slot = checkpoint.header.slot + SLOTS_PER_HISTORICAL_ROOT as u64 + 43;
|
||||
|
||||
new_tester().execute_with(|| {
|
||||
assert_ok!(EthereumBeaconClient::process_checkpoint_update(&checkpoint));
|
||||
|
||||
let result = EthereumBeaconClient::submit(RuntimeOrigin::signed(1), update.clone());
|
||||
assert_ok!(result);
|
||||
assert_eq!(result.unwrap().pays_fee, Pays::No);
|
||||
assert!(<NextSyncCommittee<Test>>::exists());
|
||||
|
||||
let second_result =
|
||||
EthereumBeaconClient::submit(RuntimeOrigin::signed(1), next_update.clone());
|
||||
assert_err!(
|
||||
second_result,
|
||||
// The test should pass the InvalidFinalizedHeaderGap check, and will fail at the
|
||||
// next check, the merkle proof, because we changed the next_update slots.
|
||||
Error::<Test>::InvalidHeaderMerkleProof
|
||||
);
|
||||
assert_eq!(second_result.unwrap_err().post_info.pays_fee, Pays::Yes);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn duplicate_sync_committee_updates_are_not_free() {
|
||||
let checkpoint = Box::new(load_checkpoint_update_fixture());
|
||||
let sync_committee_update = Box::new(load_sync_committee_update_fixture());
|
||||
|
||||
new_tester().execute_with(|| {
|
||||
assert_ok!(EthereumBeaconClient::process_checkpoint_update(&checkpoint));
|
||||
let result =
|
||||
EthereumBeaconClient::submit(RuntimeOrigin::signed(1), sync_committee_update.clone());
|
||||
assert_ok!(result);
|
||||
assert_eq!(result.unwrap().pays_fee, Pays::No);
|
||||
|
||||
// Check that if the same update is submitted, the update is not free.
|
||||
let second_result =
|
||||
EthereumBeaconClient::submit(RuntimeOrigin::signed(1), sync_committee_update);
|
||||
assert_ok!(second_result);
|
||||
assert_eq!(second_result.unwrap().pays_fee, Pays::Yes);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sync_committee_update_for_sync_committee_already_imported_are_not_free() {
|
||||
let checkpoint = Box::new(load_checkpoint_update_fixture());
|
||||
let sync_committee_update = Box::new(load_sync_committee_update_fixture()); // slot 129
|
||||
let second_sync_committee_update = load_sync_committee_update_period_0(); // slot 128
|
||||
let third_sync_committee_update = load_sync_committee_update_period_0_newer_fixture(); // slot 224
|
||||
let fourth_sync_committee_update = load_sync_committee_update_period_0_older_fixture(); // slot 96
|
||||
let fith_sync_committee_update = Box::new(load_next_sync_committee_update_fixture()); // slot 8259
|
||||
|
||||
new_tester().execute_with(|| {
|
||||
assert_ok!(EthereumBeaconClient::process_checkpoint_update(&checkpoint));
|
||||
assert_eq!(<LatestSyncCommitteeUpdatePeriod<Test>>::get(), 0);
|
||||
|
||||
// Check that setting the next sync committee for period 0 is free (it is not set yet).
|
||||
let result =
|
||||
EthereumBeaconClient::submit(RuntimeOrigin::signed(1), sync_committee_update.clone());
|
||||
assert_ok!(result);
|
||||
assert_eq!(result.unwrap().pays_fee, Pays::No);
|
||||
assert_eq!(<LatestSyncCommitteeUpdatePeriod<Test>>::get(), 0);
|
||||
|
||||
// Check that setting the next sync committee for period 0 again is not free.
|
||||
let second_result =
|
||||
EthereumBeaconClient::submit(RuntimeOrigin::signed(1), second_sync_committee_update);
|
||||
assert_eq!(second_result.unwrap().pays_fee, Pays::Yes);
|
||||
assert_eq!(<LatestSyncCommitteeUpdatePeriod<Test>>::get(), 0);
|
||||
|
||||
// Check that setting an update with a sync committee that has already been set, but with a
|
||||
// newer finalized header, is free.
|
||||
let third_result =
|
||||
EthereumBeaconClient::submit(RuntimeOrigin::signed(1), third_sync_committee_update);
|
||||
assert_eq!(third_result.unwrap().pays_fee, Pays::No);
|
||||
assert_eq!(<LatestSyncCommitteeUpdatePeriod<Test>>::get(), 0);
|
||||
|
||||
// Check that setting the next sync committee for period 0 again with an earlier slot is not
|
||||
// free.
|
||||
let fourth_result =
|
||||
EthereumBeaconClient::submit(RuntimeOrigin::signed(1), fourth_sync_committee_update);
|
||||
assert_err!(fourth_result, Error::<Test>::IrrelevantUpdate);
|
||||
assert_eq!(fourth_result.unwrap_err().post_info.pays_fee, Pays::Yes);
|
||||
|
||||
// Check that setting the next sync committee for period 1 is free.
|
||||
let fith_result =
|
||||
EthereumBeaconClient::submit(RuntimeOrigin::signed(1), fith_sync_committee_update);
|
||||
assert_eq!(fith_result.unwrap().pays_fee, Pays::No);
|
||||
assert_eq!(<LatestSyncCommitteeUpdatePeriod<Test>>::get(), 1);
|
||||
});
|
||||
}
|
||||
|
||||
/* IMPLS */
|
||||
|
||||
#[test]
|
||||
fn verify_message() {
|
||||
let (event_log, proof) = get_message_verification_payload();
|
||||
|
||||
new_tester().execute_with(|| {
|
||||
assert_ok!(initialize_storage());
|
||||
assert_ok!(EthereumBeaconClient::verify(&event_log, &proof));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn verify_message_invalid_proof() {
|
||||
let (event_log, mut proof) = get_message_verification_payload();
|
||||
proof.receipt_proof.1[0] = TEST_HASH.into();
|
||||
|
||||
new_tester().execute_with(|| {
|
||||
assert_ok!(initialize_storage());
|
||||
assert_err!(
|
||||
EthereumBeaconClient::verify(&event_log, &proof),
|
||||
VerificationError::InvalidProof
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn verify_message_invalid_receipts_root() {
|
||||
let (event_log, mut proof) = get_message_verification_payload();
|
||||
let mut payload = deneb::ExecutionPayloadHeader::default();
|
||||
payload.receipts_root = TEST_HASH.into();
|
||||
proof.execution_proof.execution_header = VersionedExecutionPayloadHeader::Deneb(payload);
|
||||
|
||||
new_tester().execute_with(|| {
|
||||
assert_ok!(initialize_storage());
|
||||
assert_err!(
|
||||
EthereumBeaconClient::verify(&event_log, &proof),
|
||||
VerificationError::InvalidExecutionProof(
|
||||
Error::<Test>::BlockBodyHashTreeRootFailed.into()
|
||||
)
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn verify_message_invalid_log() {
|
||||
let (mut event_log, proof) = get_message_verification_payload();
|
||||
event_log.topics = vec![H256::zero(); 10];
|
||||
new_tester().execute_with(|| {
|
||||
assert_ok!(initialize_storage());
|
||||
assert_err!(
|
||||
EthereumBeaconClient::verify(&event_log, &proof),
|
||||
VerificationError::LogNotFound
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn verify_message_receipt_does_not_contain_log() {
|
||||
let (mut event_log, proof) = get_message_verification_payload();
|
||||
event_log.data = hex!("f9013c94ee9170abfbf9421ad6dd07f6bdec9d89f2b581e0f863a01b11dcf133cc240f682dab2d3a8e4cd35c5da8c9cf99adac4336f8512584c5ada000000000000000000000000000000000000000000000000000000000000003e8a00000000000000000000000000000000000000000000000000000000000000002b8c000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000068000f000000000000000101d184c103f7acc340847eee82a0b909e3358bc28d440edffa1352b13227e8ee646f3ea37456dec70100000101001cbd2d43530a44705ad088af313e18f80b53ef16b36177cd4b77b846f2a5f07c0000e8890423c78a0000000000000000000000000000000000000000000000000000000000000000").to_vec();
|
||||
|
||||
new_tester().execute_with(|| {
|
||||
assert_ok!(initialize_storage());
|
||||
assert_err!(
|
||||
EthereumBeaconClient::verify(&event_log, &proof),
|
||||
VerificationError::LogNotFound
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn set_operating_mode() {
|
||||
let checkpoint = Box::new(load_checkpoint_update_fixture());
|
||||
let update = Box::new(load_finalized_header_update_fixture());
|
||||
|
||||
new_tester().execute_with(|| {
|
||||
assert_ok!(EthereumBeaconClient::process_checkpoint_update(&checkpoint));
|
||||
|
||||
assert_ok!(EthereumBeaconClient::set_operating_mode(
|
||||
RuntimeOrigin::root(),
|
||||
snowbridge_core::BasicOperatingMode::Halted
|
||||
));
|
||||
|
||||
assert_noop!(
|
||||
EthereumBeaconClient::submit(RuntimeOrigin::signed(1), update),
|
||||
Error::<Test>::Halted
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn set_operating_mode_root_only() {
|
||||
new_tester().execute_with(|| {
|
||||
assert_noop!(
|
||||
EthereumBeaconClient::set_operating_mode(
|
||||
RuntimeOrigin::signed(1),
|
||||
snowbridge_core::BasicOperatingMode::Halted
|
||||
),
|
||||
DispatchError::BadOrigin
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn verify_execution_proof_invalid_ancestry_proof() {
|
||||
let checkpoint = Box::new(load_checkpoint_update_fixture());
|
||||
let finalized_header_update = Box::new(load_finalized_header_update_fixture());
|
||||
let mut execution_header_update = Box::new(load_execution_proof_fixture());
|
||||
if let Some(ref mut ancestry_proof) = execution_header_update.ancestry_proof {
|
||||
ancestry_proof.header_branch[0] = TEST_HASH.into()
|
||||
}
|
||||
|
||||
new_tester().execute_with(|| {
|
||||
assert_ok!(EthereumBeaconClient::process_checkpoint_update(&checkpoint));
|
||||
assert_ok!(EthereumBeaconClient::submit(RuntimeOrigin::signed(1), finalized_header_update));
|
||||
assert_err!(
|
||||
EthereumBeaconClient::verify_execution_proof(&execution_header_update),
|
||||
Error::<Test>::InvalidAncestryMerkleProof
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn verify_execution_proof_invalid_execution_header_proof() {
|
||||
let checkpoint = Box::new(load_checkpoint_update_fixture());
|
||||
let finalized_header_update = Box::new(load_finalized_header_update_fixture());
|
||||
let mut execution_header_update = Box::new(load_execution_proof_fixture());
|
||||
execution_header_update.execution_branch[0] = TEST_HASH.into();
|
||||
|
||||
new_tester().execute_with(|| {
|
||||
assert_ok!(EthereumBeaconClient::process_checkpoint_update(&checkpoint));
|
||||
assert_ok!(EthereumBeaconClient::submit(RuntimeOrigin::signed(1), finalized_header_update));
|
||||
assert_err!(
|
||||
EthereumBeaconClient::verify_execution_proof(&execution_header_update),
|
||||
Error::<Test>::InvalidExecutionHeaderProof
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn verify_execution_proof_that_is_also_finalized_header_which_is_not_stored() {
|
||||
let checkpoint = Box::new(load_checkpoint_update_fixture());
|
||||
let finalized_header_update = Box::new(load_finalized_header_update_fixture());
|
||||
let mut execution_header_update = Box::new(load_execution_proof_fixture());
|
||||
execution_header_update.ancestry_proof = None;
|
||||
|
||||
new_tester().execute_with(|| {
|
||||
assert_ok!(EthereumBeaconClient::process_checkpoint_update(&checkpoint));
|
||||
assert_ok!(EthereumBeaconClient::submit(RuntimeOrigin::signed(1), finalized_header_update));
|
||||
assert_err!(
|
||||
EthereumBeaconClient::verify_execution_proof(&execution_header_update),
|
||||
Error::<Test>::ExpectedFinalizedHeaderNotStored
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn submit_execution_proof_that_is_also_finalized_header_which_is_stored_but_slots_dont_match() {
|
||||
let checkpoint = Box::new(load_checkpoint_update_fixture());
|
||||
let finalized_header_update = Box::new(load_finalized_header_update_fixture());
|
||||
let mut execution_header_update = Box::new(load_execution_proof_fixture());
|
||||
execution_header_update.ancestry_proof = None;
|
||||
|
||||
new_tester().execute_with(|| {
|
||||
assert_ok!(EthereumBeaconClient::process_checkpoint_update(&checkpoint));
|
||||
assert_ok!(EthereumBeaconClient::submit(RuntimeOrigin::signed(1), finalized_header_update));
|
||||
|
||||
let block_root: H256 = execution_header_update.header.hash_tree_root().unwrap();
|
||||
|
||||
<FinalizedBeaconState<Test>>::insert(
|
||||
block_root,
|
||||
CompactBeaconState {
|
||||
slot: execution_header_update.header.slot + 1,
|
||||
block_roots_root: Default::default(),
|
||||
},
|
||||
);
|
||||
LatestFinalizedBlockRoot::<Test>::set(block_root);
|
||||
|
||||
assert_err!(
|
||||
EthereumBeaconClient::verify_execution_proof(&execution_header_update),
|
||||
Error::<Test>::ExpectedFinalizedHeaderNotStored
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn verify_execution_proof_not_finalized() {
|
||||
let checkpoint = Box::new(load_checkpoint_update_fixture());
|
||||
let finalized_header_update = Box::new(load_finalized_header_update_fixture());
|
||||
let update = Box::new(load_execution_proof_fixture());
|
||||
|
||||
new_tester().execute_with(|| {
|
||||
assert_ok!(EthereumBeaconClient::process_checkpoint_update(&checkpoint));
|
||||
assert_ok!(EthereumBeaconClient::submit(RuntimeOrigin::signed(1), finalized_header_update));
|
||||
|
||||
<FinalizedBeaconState<Test>>::mutate(<LatestFinalizedBlockRoot<Test>>::get(), |x| {
|
||||
let prev = x.unwrap();
|
||||
*x = Some(CompactBeaconState { slot: update.header.slot - 1, ..prev });
|
||||
});
|
||||
|
||||
assert_err!(
|
||||
EthereumBeaconClient::verify_execution_proof(&update),
|
||||
Error::<Test>::HeaderNotFinalized
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn verify_message_invalid_topic() {
|
||||
let (event_log, proof) = get_message_verification_payload();
|
||||
let mut event_log_muted = event_log.clone();
|
||||
event_log_muted.topics[0] = H256::default();
|
||||
|
||||
new_tester().execute_with(|| {
|
||||
assert_ok!(initialize_storage());
|
||||
assert_err!(
|
||||
EthereumBeaconClient::verify(&event_log_muted, &proof),
|
||||
VerificationError::LogNotFound
|
||||
);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
// 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 = snowbridge_beacon_primitives::SyncCommittee<SC_SIZE>;
|
||||
pub type SyncCommitteePrepared = snowbridge_beacon_primitives::SyncCommitteePrepared<SC_SIZE>;
|
||||
pub type SyncAggregate = snowbridge_beacon_primitives::SyncAggregate<SC_SIZE, SC_BITS_SIZE>;
|
||||
pub type CheckpointUpdate = snowbridge_beacon_primitives::CheckpointUpdate<SC_SIZE>;
|
||||
pub type Update = snowbridge_beacon_primitives::Update<SC_SIZE, SC_BITS_SIZE>;
|
||||
pub type NextSyncCommitteeUpdate = snowbridge_beacon_primitives::NextSyncCommitteeUpdate<SC_SIZE>;
|
||||
|
||||
pub use snowbridge_beacon_primitives::{AncestryProof, ExecutionProof};
|
||||
|
||||
/// FinalizedState ring buffer implementation
|
||||
pub type FinalizedBeaconStateBuffer<T> = RingBufferMapImpl<
|
||||
u32,
|
||||
crate::MaxFinalizedHeadersToKeep<T>,
|
||||
crate::FinalizedBeaconStateIndex<T>,
|
||||
crate::FinalizedBeaconStateMapping<T>,
|
||||
crate::FinalizedBeaconState<T>,
|
||||
OptionQuery,
|
||||
>;
|
||||
@@ -0,0 +1,61 @@
|
||||
//! 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-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;
|
||||
}
|
||||
|
||||
// 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))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user