diff --git a/cumulus b/cumulus index 87bccc9eb..9638bf40c 160000 --- a/cumulus +++ b/cumulus @@ -1 +1 @@ -Subproject commit 87bccc9ebdd7863c0d812a0ad6d31ac99f6ced17 +Subproject commit 9638bf40c53a01261b24f74db2f71db7fff864cd diff --git a/parachain/Cargo.lock b/parachain/Cargo.lock index 6eb8f7660..b50d081d0 100644 --- a/parachain/Cargo.lock +++ b/parachain/Cargo.lock @@ -3590,6 +3590,7 @@ dependencies = [ "hex-literal", "pallet-timestamp", "parity-scale-codec", + "rand 0.8.5", "rlp", "scale-info", "serde", diff --git a/parachain/pallets/ethereum-beacon-client/Cargo.toml b/parachain/pallets/ethereum-beacon-client/Cargo.toml index f22159bc9..0ab0c810c 100644 --- a/parachain/pallets/ethereum-beacon-client/Cargo.toml +++ b/parachain/pallets/ethereum-beacon-client/Cargo.toml @@ -33,6 +33,7 @@ primitives = { package = "snowbridge-beacon-primitives", path = "../../primitive static_assertions = { version = "1.1.0" } [dev-dependencies] +rand = "0.8.5" sp-keyring = { git = "https://github.com/paritytech/substrate.git", branch = "master" } sp-io = { git = "https://github.com/paritytech/substrate.git", branch = "master" } snowbridge-testutils = { path = "../../primitives/testutils" } diff --git a/parachain/pallets/ethereum-beacon-client/src/benchmarking/mod.rs b/parachain/pallets/ethereum-beacon-client/src/benchmarking/mod.rs index caeaa8f9b..f3da4d3ed 100644 --- a/parachain/pallets/ethereum-beacon-client/src/benchmarking/mod.rs +++ b/parachain/pallets/ethereum-beacon-client/src/benchmarking/mod.rs @@ -93,7 +93,7 @@ benchmarks! { unblock_bridge { }: _(RawOrigin::Root) verify { - assert_eq!(>::get(),false); + assert!(!>::get()); } bls_fast_aggregate_verify_pre_aggregated { @@ -103,7 +103,7 @@ benchmarks! { let agg_sig = prepare_aggregate_signature(&update.sync_aggregate.sync_committee_signature).unwrap(); let agg_pub_key = prepare_aggregate_pubkey(&participant_pubkeys).unwrap(); }:{ - agg_sig.fast_aggregate_verify_pre_aggregated(&signing_root.as_bytes(), &agg_pub_key) + agg_sig.fast_aggregate_verify_pre_aggregated(signing_root.as_bytes(), &agg_pub_key) } bls_fast_aggregate_verify_legacy { diff --git a/parachain/pallets/ethereum-beacon-client/src/benchmarking/util.rs b/parachain/pallets/ethereum-beacon-client/src/benchmarking/util.rs index 1e044540e..57acc1a3b 100644 --- a/parachain/pallets/ethereum-beacon-client/src/benchmarking/util.rs +++ b/parachain/pallets/ethereum-beacon-client/src/benchmarking/util.rs @@ -42,11 +42,11 @@ pub fn participant_pubkeys( update: &SyncCommitteeUpdate, ) -> Result, &'static str> { let sync_committee_bits = - decompress_sync_committee_bits(update.sync_aggregate.sync_committee_bits.clone()); + decompress_sync_committee_bits(update.sync_aggregate.sync_committee_bits); let current_sync_committee = sync_committee::(update)?; let pubkeys = EthereumBeaconClient::::find_pubkeys( &sync_committee_bits, - ¤t_sync_committee.pubkeys.to_vec(), + ¤t_sync_committee.pubkeys.as_ref(), true, ); Ok(pubkeys) @@ -56,11 +56,11 @@ pub fn absent_pubkeys( update: &SyncCommitteeUpdate, ) -> Result, &'static str> { let sync_committee_bits = - decompress_sync_committee_bits(update.sync_aggregate.sync_committee_bits.clone()); + decompress_sync_committee_bits(update.sync_aggregate.sync_committee_bits); let current_sync_committee = sync_committee::(update)?; let pubkeys = EthereumBeaconClient::::find_pubkeys( &sync_committee_bits, - ¤t_sync_committee.pubkeys.to_vec(), + ¤t_sync_committee.pubkeys.as_ref(), false, ); Ok(pubkeys) @@ -69,7 +69,7 @@ pub fn absent_pubkeys( pub fn signing_root(update: &SyncCommitteeUpdate) -> Result { let validators_root = >::get(); let signing_root = EthereumBeaconClient::::signing_root( - update.attested_header.clone(), + update.attested_header, validators_root, update.signature_slot, )?; diff --git a/parachain/pallets/ethereum-beacon-client/src/config/mainnet.rs b/parachain/pallets/ethereum-beacon-client/src/config/mainnet.rs index 294335981..9c8f888b7 100644 --- a/parachain/pallets/ethereum-beacon-client/src/config/mainnet.rs +++ b/parachain/pallets/ethereum-beacon-client/src/config/mainnet.rs @@ -1,8 +1,8 @@ -pub const SLOTS_PER_EPOCH: u64 = 32; -pub const SECONDS_PER_SLOT: u64 = 12; -pub const EPOCHS_PER_SYNC_COMMITTEE_PERIOD: u64 = 256; +pub const SLOTS_PER_EPOCH: usize = 32; +pub const SECONDS_PER_SLOT: usize = 12; +pub const EPOCHS_PER_SYNC_COMMITTEE_PERIOD: usize = 256; pub const SYNC_COMMITTEE_SIZE: usize = 512; pub const SYNC_COMMITTEE_BITS_SIZE: usize = SYNC_COMMITTEE_SIZE / 8; pub const SLOTS_PER_HISTORICAL_ROOT: usize = 8192; pub const IS_MINIMAL: bool = false; -pub const BLOCK_ROOT_AT_INDEX_PROOF_DEPTH: u64 = 13; +pub const BLOCK_ROOT_AT_INDEX_PROOF_DEPTH: usize = 13; diff --git a/parachain/pallets/ethereum-beacon-client/src/config/minimal.rs b/parachain/pallets/ethereum-beacon-client/src/config/minimal.rs index 130978ea2..c2d081976 100644 --- a/parachain/pallets/ethereum-beacon-client/src/config/minimal.rs +++ b/parachain/pallets/ethereum-beacon-client/src/config/minimal.rs @@ -1,8 +1,8 @@ -pub const SLOTS_PER_EPOCH: u64 = 8; -pub const SECONDS_PER_SLOT: u64 = 6; -pub const EPOCHS_PER_SYNC_COMMITTEE_PERIOD: u64 = 8; +pub const SLOTS_PER_EPOCH: usize = 8; +pub const SECONDS_PER_SLOT: usize = 6; +pub const EPOCHS_PER_SYNC_COMMITTEE_PERIOD: usize = 8; pub const SYNC_COMMITTEE_SIZE: usize = 32; pub const SYNC_COMMITTEE_BITS_SIZE: usize = SYNC_COMMITTEE_SIZE / 8; pub const SLOTS_PER_HISTORICAL_ROOT: usize = 64; pub const IS_MINIMAL: bool = true; -pub const BLOCK_ROOT_AT_INDEX_PROOF_DEPTH: u64 = 6; +pub const BLOCK_ROOT_AT_INDEX_PROOF_DEPTH: usize = 6; diff --git a/parachain/pallets/ethereum-beacon-client/src/config/mod.rs b/parachain/pallets/ethereum-beacon-client/src/config/mod.rs index d8bc59383..281fe14cc 100644 --- a/parachain/pallets/ethereum-beacon-client/src/config/mod.rs +++ b/parachain/pallets/ethereum-beacon-client/src/config/mod.rs @@ -9,25 +9,24 @@ pub use minimal::*; #[cfg(not(feature = "minimal"))] pub use mainnet::*; -pub const CURRENT_SYNC_COMMITTEE_INDEX: u64 = 22; -pub const NEXT_SYNC_COMMITTEE_INDEX: u64 = 23; -pub const SYNC_COMMITTEE_DEPTH: u64 = 5; +pub const CURRENT_SYNC_COMMITTEE_INDEX: usize = 22; +pub const NEXT_SYNC_COMMITTEE_INDEX: usize = 23; +pub const SYNC_COMMITTEE_DEPTH: usize = 5; -pub const FINALIZED_ROOT_DEPTH: u64 = 6; -pub const FINALIZED_ROOT_INDEX: u64 = 41; +pub const FINALIZED_ROOT_DEPTH: usize = 6; +pub const FINALIZED_ROOT_INDEX: usize = 41; -pub const BLOCK_ROOTS_DEPTH: u64 = 5; -pub const BLOCK_ROOTS_INDEX: u64 = 5; +pub const BLOCK_ROOTS_DEPTH: usize = 5; +pub const BLOCK_ROOTS_INDEX: usize = 5; -pub const EXECUTION_HEADER_DEPTH: u64 = 4; -pub const EXECUTION_HEADER_INDEX: u64 = 9; +pub const EXECUTION_HEADER_DEPTH: usize = 4; +pub const EXECUTION_HEADER_INDEX: usize = 9; pub const MAX_EXTRA_DATA_BYTES: usize = 32; pub const MAX_LOGS_BLOOM_SIZE: usize = 256; pub const MAX_FEE_RECIPIENT_SIZE: usize = 20; -pub const MAX_FINALIZED_HEADER_SLOT_ARRAY: u32 = 1000; -pub const MAX_BRANCH_PROOF_SIZE: u32 = 20; +pub const MAX_BRANCH_PROOF_SIZE: usize = 20; /// DomainType('0x07000000') /// https://github.com/ethereum/consensus-specs/blob/dev/specs/altair/beacon-chain.md#domain-types diff --git a/parachain/pallets/ethereum-beacon-client/src/lib.rs b/parachain/pallets/ethereum-beacon-client/src/lib.rs index a9a640257..84e257f2e 100644 --- a/parachain/pallets/ethereum-beacon-client/src/lib.rs +++ b/parachain/pallets/ethereum-beacon-client/src/lib.rs @@ -19,12 +19,11 @@ mod tests_minimal; #[cfg(feature = "runtime-benchmarks")] mod benchmarking; -pub use weights::WeightInfo; - use frame_support::{ dispatch::DispatchResult, log, - traits::{ConstU32, Get, UnixTime}, + pallet_prelude::OptionQuery, + traits::{Get, UnixTime}, transactional, BoundedVec, }; use frame_system::ensure_signed; @@ -33,9 +32,10 @@ use primitives::{ CompactExecutionHeader, ExecutionHeaderState, FinalizedHeaderState, ForkData, ForkVersion, ForkVersions, PublicKeyPrepared, Signature, SigningData, }; -use snowbridge_core::{Message, Verifier}; +use snowbridge_core::{Message, RingBufferMap, RingBufferMapImpl, Verifier}; use sp_core::H256; use sp_std::prelude::*; +pub use weights::WeightInfo; use snowbridge_core::Proof; use snowbridge_ethereum::{Header as EthereumHeader, Log, Receipt}; @@ -43,10 +43,7 @@ use sp_core::U256; pub use pallet::*; -pub use config::{ - MAX_FINALIZED_HEADER_SLOT_ARRAY, SLOTS_PER_HISTORICAL_ROOT, SYNC_COMMITTEE_BITS_SIZE, - SYNC_COMMITTEE_SIZE, -}; +pub use config::{SLOTS_PER_HISTORICAL_ROOT, SYNC_COMMITTEE_BITS_SIZE, SYNC_COMMITTEE_SIZE}; pub type InitialUpdate = primitives::InitialUpdate; pub type HeaderUpdate = primitives::HeaderUpdate; @@ -65,6 +62,26 @@ fn decompress_sync_committee_bits( ) } +/// ExecutionHeader ring buffer implementation +pub(crate) type ExecutionHeaderBuffer = RingBufferMapImpl< + u32, + ::MaxExecutionHeadersToKeep, + ExecutionHeaderIndex, + ExecutionHeaderMapping, + ExecutionHeaders, + OptionQuery, +>; + +/// Sync committee ring buffer implementation +pub(crate) type SyncCommitteesBuffer = RingBufferMapImpl< + u32, + ::MaxSyncCommitteesToKeep, + SyncCommitteesIndex, + SyncCommitteesMapping, + SyncCommittees, + OptionQuery, +>; + #[frame_support::pallet] pub mod pallet { use super::*; @@ -83,6 +100,15 @@ pub mod pallet { type ForkVersions: Get; #[pallet::constant] type WeakSubjectivityPeriodSeconds: Get; + /// Maximum finalized headers + #[pallet::constant] + type MaxFinalizedHeadersToKeep: Get; + /// Maximum execution headers + #[pallet::constant] + type MaxExecutionHeadersToKeep: Get; + /// Maximum sync committees + #[pallet::constant] + type MaxSyncCommitteesToKeep: Get; type WeightInfo: WeightInfo; } @@ -139,16 +165,13 @@ pub mod pallet { BLSVerificationFailed(BlsError), } - #[pallet::hooks] - impl Hooks> for Pallet {} - #[pallet::storage] pub(super) type FinalizedBeaconHeaders = StorageMap<_, Identity, H256, BeaconHeader, OptionQuery>; #[pallet::storage] - pub(super) type FinalizedBeaconHeaderSlots = - StorageValue<_, BoundedVec>, ValueQuery>; + pub(super) type FinalizedBeaconHeaderStates = + StorageValue<_, BoundedVec, ValueQuery>; #[pallet::storage] pub(super) type FinalizedBeaconHeadersBlockRoot = @@ -179,18 +202,30 @@ pub mod pallet { pub(super) type SyncCommittees = StorageMap<_, Identity, u64, SyncCommitteePrepared, OptionQuery>; + /// Index storage for execution header + #[pallet::storage] + pub(crate) type ExecutionHeaderIndex = StorageValue<_, u32, ValueQuery>; + + /// Intermediate storage for execution header mapping + #[pallet::storage] + pub(crate) type ExecutionHeaderMapping = + StorageMap<_, Identity, u32, H256, ValueQuery>; + + /// Index storage for sync committee ring buffer + #[pallet::storage] + pub(crate) type SyncCommitteesIndex = StorageValue<_, u32, ValueQuery>; + + /// Intermediate storage for sync committee mapping + #[pallet::storage] + pub(crate) type SyncCommitteesMapping = + StorageMap<_, Identity, u32, u64, ValueQuery>; + #[pallet::genesis_config] + #[derive(Default)] pub struct GenesisConfig { pub initial_sync: Option, } - #[cfg(feature = "std")] - impl Default for GenesisConfig { - fn default() -> Self { - GenesisConfig { initial_sync: None } - } - } - #[pallet::genesis_build] impl GenesisBuild for GenesisConfig { fn build(&self) { @@ -352,18 +387,7 @@ pub mod pallet { Self::store_sync_committee(period, &update.current_sync_committee)?; Self::store_validators_root(update.validators_root); - - let slot = update.header.slot; - - let last_finalized_header = FinalizedHeaderState { - beacon_block_root: block_root, - beacon_slot: slot, - import_time: update.import_time, - }; - - >::insert(block_root, update.header.clone()); - Self::add_finalized_header_slot(slot)?; - >::set(last_finalized_header); + Self::store_finalized_header(block_root, update.header, Some(update.import_time))?; Ok(()) } @@ -397,19 +421,17 @@ pub mod pallet { )?; let current_period = Self::compute_current_sync_period(update.attested_header.slot); - let signature_slot_period = Self::compute_current_sync_period(update.signature_slot); let latest_committee_period = >::get(); log::trace!( target: "ethereum-beacon-client", - "💫 latest committee period is: {}, attested_header period is: {}, signature_slot period is: {}", + "💫 latest committee period is: {}, attested_header period is: {}", latest_committee_period, current_period, - signature_slot_period ); let next_period = current_period + 1; ensure!( - !>::contains_key(next_period), + !>::contains_key(next_period), Error::::InvalidSyncCommitteeUpdateWithDuplication ); ensure!( @@ -423,12 +445,12 @@ pub mod pallet { &participation, &update.sync_aggregate.sync_committee_signature, &sync_committee, - update.attested_header.clone(), + update.attested_header, validators_root, update.signature_slot, )?; ensure!( - update.block_roots_branch.len() as u64 == config::BLOCK_ROOTS_DEPTH && + update.block_roots_branch.len() == config::BLOCK_ROOTS_DEPTH && verify_merkle_proof( update.block_roots_root, &update.block_roots_branch, @@ -440,7 +462,7 @@ pub mod pallet { Self::store_block_root(update.block_roots_root, block_root); Self::store_sync_committee(next_period, &update.next_sync_committee)?; - Self::store_finalized_header(block_root, update.finalized_header.clone())?; + Self::store_finalized_header(block_root, update.finalized_header, None)?; Ok(()) } @@ -515,7 +537,7 @@ pub mod pallet { )?; ensure!( - update.block_roots_branch.len() as u64 == config::BLOCK_ROOTS_DEPTH && + update.block_roots_branch.len() == config::BLOCK_ROOTS_DEPTH && verify_merkle_proof( update.block_roots_root, &update.block_roots_branch, @@ -527,7 +549,7 @@ pub mod pallet { Self::store_block_root(update.block_roots_root, block_root); - Self::store_finalized_header(block_root, update.finalized_header)?; + Self::store_finalized_header(block_root, update.finalized_header, None)?; Ok(()) } @@ -557,7 +579,7 @@ pub mod pallet { .map_err(|_| Error::::BlockBodyHashTreeRootFailed)?; ensure!( - update.execution_branch.len() as u64 == config::EXECUTION_HEADER_DEPTH && + update.execution_branch.len() == config::EXECUTION_HEADER_DEPTH && verify_merkle_proof( execution_root, &update.execution_branch, @@ -648,7 +670,7 @@ pub mod pallet { } let index_in_array = block_slot % (SLOTS_PER_HISTORICAL_ROOT as u64); - let leaf_index = (SLOTS_PER_HISTORICAL_ROOT as u64) + index_in_array; + let leaf_index = (SLOTS_PER_HISTORICAL_ROOT) + index_in_array as usize; log::info!( target: "ethereum-beacon-client", @@ -656,7 +678,7 @@ pub mod pallet { ); ensure!( - block_root_proof.len() as u64 == config::BLOCK_ROOT_AT_INDEX_PROOF_DEPTH && + block_root_proof.len() == config::BLOCK_ROOT_AT_INDEX_PROOF_DEPTH && verify_merkle_proof( beacon_block_root, &block_root_proof, @@ -727,14 +749,14 @@ pub mod pallet { sync_committee: &SyncCommittee, sync_committee_branch: &[H256], header_state_root: H256, - index: u64, + index: usize, ) -> DispatchResult { let sync_committee_root = sync_committee .hash_tree_root() .map_err(|_| Error::::SyncCommitteeHashTreeRootFailed)?; ensure!( - sync_committee_branch.len() as u64 == config::SYNC_COMMITTEE_DEPTH && + sync_committee_branch.len() == config::SYNC_COMMITTEE_DEPTH && verify_merkle_proof( sync_committee_root, sync_committee_branch, @@ -751,10 +773,10 @@ pub mod pallet { block_root: H256, proof_branch: &[H256], attested_header_state_root: H256, - index: u64, + index: usize, ) -> DispatchResult { ensure!( - proof_branch.len() as u64 == config::FINALIZED_ROOT_DEPTH && + proof_branch.len() == config::FINALIZED_ROOT_DEPTH && verify_merkle_proof( block_root, proof_branch, @@ -766,10 +788,13 @@ pub mod pallet { Ok(()) } - pub fn store_sync_committee(period: u64, sync_committee: &SyncCommittee) -> DispatchResult { + pub(crate) fn store_sync_committee( + period: u64, + sync_committee: &SyncCommittee, + ) -> DispatchResult { let prepare_sync_committee: SyncCommitteePrepared = sync_committee.try_into().map_err(|_| >::BLSPreparePublicKeysFailed)?; - >::insert(period, prepare_sync_committee); + >::insert(period, prepare_sync_committee); >::set(period); @@ -783,11 +808,23 @@ pub mod pallet { Ok(()) } - fn store_finalized_header(block_root: H256, header: BeaconHeader) -> DispatchResult { + fn store_finalized_header( + block_root: H256, + header: BeaconHeader, + last_import_time: Option, + ) -> DispatchResult { let slot = header.slot; + let import_time = last_import_time.unwrap_or_else(|| T::TimeProvider::now().as_secs()); + + let finalized_header = FinalizedHeaderState { + beacon_block_root: block_root, + beacon_slot: slot, + import_time, + }; >::insert(block_root, header); - Self::add_finalized_header_slot(slot)?; + LatestFinalizedHeaderState::::set(finalized_header); + Self::add_finalized_header_state(finalized_header)?; log::info!( target: "ethereum-beacon-client", @@ -796,55 +833,54 @@ pub mod pallet { slot ); - LatestFinalizedHeaderState::::mutate(|s| { - s.import_time = T::TimeProvider::now().as_secs(); - s.beacon_block_root = block_root; - s.beacon_slot = slot; - }); - Self::deposit_event(Event::BeaconHeaderImported { block_hash: block_root, slot }); Ok(()) } - fn add_finalized_header_slot(slot: u64) -> DispatchResult { - >::try_mutate(|b_vec| { - if b_vec.len() as u32 == MAX_FINALIZED_HEADER_SLOT_ARRAY { - b_vec.remove(0); + pub(super) fn add_finalized_header_state( + finalized_header_state: FinalizedHeaderState, + ) -> DispatchResult { + >::try_mutate(|b_vec| { + if b_vec.len() as u32 == T::MaxFinalizedHeadersToKeep::get() { + let oldest = b_vec.remove(0); + // Removing corresponding finalized header data of popped slot + // as that data will not be used by relayer anyway. + >::remove(oldest.beacon_block_root); + >::remove(oldest.beacon_block_root); } - b_vec.try_push(slot) + b_vec.try_push(finalized_header_state) }) .map_err(|_| >::FinalizedBeaconHeaderSlotsExceeded)?; Ok(()) } - fn store_execution_header( + pub(crate) fn store_execution_header( block_hash: H256, header: CompactExecutionHeader, beacon_slot: u64, beacon_block_root: H256, ) { - >::insert(block_hash, header.clone()); + let block_number = header.block_number; + + >::insert(block_hash, header); log::trace!( target: "ethereum-beacon-client", "💫 Updated latest execution block at {} to number {}.", block_hash, - header.block_number + block_number ); LatestExecutionHeaderState::::mutate(|s| { s.beacon_block_root = beacon_block_root; s.beacon_slot = beacon_slot; s.block_hash = block_hash; - s.block_number = header.block_number; + s.block_number = block_number; }); - Self::deposit_event(Event::ExecutionHeaderImported { - block_hash, - block_number: header.block_number, - }); + Self::deposit_event(Event::ExecutionHeaderImported { block_hash, block_number }); } fn store_validators_root(validators_root: H256) { @@ -857,12 +893,13 @@ pub mod pallet { /// /// let sync_committee_bits = vec![0, 1, 0, 1, 1, 1]; /// ensure!(get_sync_committee_sum(sync_committee_bits), 4); - pub(super) fn get_sync_committee_sum(sync_committee_bits: &[u8]) -> u64 { - sync_committee_bits.iter().fold(0, |acc: u64, x| acc + *x as u64) + pub(super) fn get_sync_committee_sum(sync_committee_bits: &[u8]) -> u32 { + sync_committee_bits.iter().fold(0, |acc: u32, x| acc + *x as u32) } pub(super) fn compute_current_sync_period(slot: u64) -> u64 { - slot / config::SLOTS_PER_EPOCH / config::EPOCHS_PER_SYNC_COMMITTEE_PERIOD + (slot as usize / config::SLOTS_PER_EPOCH / config::EPOCHS_PER_SYNC_COMMITTEE_PERIOD) + as u64 } /// Return the domain for the domain_type and fork_version. @@ -900,7 +937,7 @@ pub mod pallet { ) -> DispatchResult { let sync_committee_sum = Self::get_sync_committee_sum(sync_committee_bits); ensure!( - (sync_committee_sum * 3 >= sync_committee_bits.len() as u64 * 2), + ((sync_committee_sum * 3) as usize) >= sync_committee_bits.len() * 2, Error::::SyncCommitteeParticipantsNotSupermajority ); @@ -910,7 +947,7 @@ pub mod pallet { pub(super) fn sync_committee_for_period( period: u64, ) -> Result { - >::get(period).ok_or(Error::::SyncCommitteeMissing.into()) + >::get(period).ok_or(Error::::SyncCommitteeMissing.into()) } pub(super) fn compute_fork_version(epoch: u64) -> ForkVersion { @@ -983,7 +1020,7 @@ pub mod pallet { let mut pubkeys: Vec = 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.push(*pubkey); } } pubkeys @@ -997,7 +1034,7 @@ pub mod pallet { ) -> Result { let fork_version = Self::compute_fork_version(Self::compute_epoch_at_slot( signature_slot, - config::SLOTS_PER_EPOCH, + config::SLOTS_PER_EPOCH as u64, )); let domain_type = config::DOMAIN_SYNC_COMMITTEE.to_vec(); // Domains are used for for seeds, for signatures, and for selecting aggregators. @@ -1018,7 +1055,7 @@ pub mod pallet { message.proof.block_hash, ); - let header = >::get(message.proof.block_hash) + let header = >::get(message.proof.block_hash) .ok_or(Error::::MissingHeader)?; let receipt = match Self::verify_receipt_inclusion(header.receipts_root, &message.proof) diff --git a/parachain/pallets/ethereum-beacon-client/src/mock.rs b/parachain/pallets/ethereum-beacon-client/src/mock.rs index ae22dcd63..e33957b30 100644 --- a/parachain/pallets/ethereum-beacon-client/src/mock.rs +++ b/parachain/pallets/ethereum-beacon-client/src/mock.rs @@ -72,6 +72,9 @@ pub mod mock_minimal { parameter_types! { pub const WeakSubjectivityPeriodSeconds: u32 = 97200; + pub const FinalizedHeaderPruneThreshold: u32 = 10; + pub const SyncCommitteePruneThreshold: u32 = 4; + pub const ExecutionHeadersPruneThreshold: u32 = 10; pub const ChainForkVersions: ForkVersions = ForkVersions{ genesis: Fork { version: [0, 0, 0, 1], // 0x00000001 @@ -97,6 +100,9 @@ pub mod mock_minimal { type RuntimeEvent = RuntimeEvent; type ForkVersions = ChainForkVersions; type WeakSubjectivityPeriodSeconds = WeakSubjectivityPeriodSeconds; + type MaxSyncCommitteesToKeep = SyncCommitteePruneThreshold; + type MaxExecutionHeadersToKeep = ExecutionHeadersPruneThreshold; + type MaxFinalizedHeadersToKeep = FinalizedHeaderPruneThreshold; type WeightInfo = (); } } @@ -178,13 +184,19 @@ pub mod mock_mainnet { epoch: 162304, }, }; + pub const SyncCommitteePruneThreshold: u32 = 4; + pub const ExecutionHeadersPruneThreshold: u32 = 10; + pub const FinalizedHeaderPruneThreshold: u32 = 10; } impl ethereum_beacon_client::Config for Test { type RuntimeEvent = RuntimeEvent; type TimeProvider = pallet_timestamp::Pallet; type ForkVersions = ChainForkVersions; + type MaxSyncCommitteesToKeep = SyncCommitteePruneThreshold; + type MaxExecutionHeadersToKeep = ExecutionHeadersPruneThreshold; type WeakSubjectivityPeriodSeconds = WeakSubjectivityPeriodSeconds; + type MaxFinalizedHeadersToKeep = FinalizedHeaderPruneThreshold; type WeightInfo = (); } } diff --git a/parachain/pallets/ethereum-beacon-client/src/tests.rs b/parachain/pallets/ethereum-beacon-client/src/tests.rs index 62e471412..b1725ef93 100644 --- a/parachain/pallets/ethereum-beacon-client/src/tests.rs +++ b/parachain/pallets/ethereum-beacon-client/src/tests.rs @@ -1,9 +1,18 @@ -use crate::{mock::*, verify_merkle_proof, BeaconHeader, Error}; +use crate::{ + mock::{get_initial_sync, mock_minimal, new_tester}, + pallet::{ + ExecutionHeaders, FinalizedBeaconHeaderStates, FinalizedBeaconHeaders, + FinalizedBeaconHeadersBlockRoot, SyncCommittees, + }, + verify_merkle_proof, BeaconHeader, Error, H256, SYNC_COMMITTEE_SIZE, +}; use frame_support::{assert_err, assert_ok}; use hex_literal::hex; use primitives::{ - fast_aggregate_verify_legacy, prepare_g1_pubkeys, BlsError, PublicKey, PublicKeyPrepared, + fast_aggregate_verify_legacy, prepare_g1_pubkeys, BlsError, CompactExecutionHeader, + FinalizedHeaderState, PublicKey, PublicKeyPrepared, }; +use rand::{thread_rng, Rng}; pub fn prepare_milagro_pubkeys() -> Result, &'static str> { let pubkeys: Vec = vec![ @@ -299,3 +308,207 @@ pub fn test_sync_committee_participation_is_supermajority_errors_when_not_superm ); }); } + +#[test] +pub fn test_prune_finalized_header() { + new_tester::().execute_with(|| { + let max_finalized_slots = + mock_minimal::FinalizedHeaderPruneThreshold::get().try_into().unwrap(); + + // Keeping track of to be deleted data + let amount_of_data_to_be_deleted = max_finalized_slots / 2; + let mut to_be_deleted_hash_list = vec![]; + let mut to_be_preserved_hash_list = vec![]; + for i in 0..max_finalized_slots { + let mut hash = H256::default(); + thread_rng().try_fill(&mut hash.0[..]).unwrap(); + + if i < amount_of_data_to_be_deleted { + to_be_deleted_hash_list.push(hash); + } else { + to_be_preserved_hash_list.push(hash); + } + let finalized_state = FinalizedHeaderState { + beacon_block_root: hash, + beacon_slot: i, + import_time: u64::default(), + }; + + FinalizedBeaconHeadersBlockRoot::::insert(hash, hash); + FinalizedBeaconHeaders::::insert(hash, BeaconHeader::default()); + assert_ok!(mock_minimal::EthereumBeaconClient::add_finalized_header_state( + finalized_state + )); + } + + // We first verify if the data corresponding to that hash is still there. + let slot_vec = FinalizedBeaconHeaderStates::::get(); + assert_eq!(slot_vec.len(), max_finalized_slots as usize); + for i in 0..(amount_of_data_to_be_deleted as usize) { + assert_eq!(slot_vec[i].beacon_slot, i as u64); + assert_eq!(slot_vec[i].beacon_block_root, to_be_deleted_hash_list[i]); + + assert!(FinalizedBeaconHeadersBlockRoot::::contains_key( + to_be_deleted_hash_list[i] + )); + assert!(FinalizedBeaconHeaders::::contains_key( + to_be_deleted_hash_list[i] + )); + } + + // We insert `amount_of_hash_to_be_deleted` number of new finalized headers + for i in max_finalized_slots..(max_finalized_slots + amount_of_data_to_be_deleted) { + let mut hash = H256::default(); + thread_rng().try_fill(&mut hash.0[..]).unwrap(); + FinalizedBeaconHeadersBlockRoot::::insert(hash, hash); + FinalizedBeaconHeaders::::insert(hash, BeaconHeader::default()); + let finalized_state = FinalizedHeaderState { + beacon_block_root: hash, + beacon_slot: i, + import_time: u64::default(), + }; + assert_ok!(mock_minimal::EthereumBeaconClient::add_finalized_header_state( + finalized_state + )); + } + + // Now, previous hashes should be pruned and in array those elements are replaced by later + // elements + let slot_vec = FinalizedBeaconHeaderStates::::get(); + assert_eq!(slot_vec.len(), max_finalized_slots as usize); + for i in 0..(amount_of_data_to_be_deleted as usize) { + assert_eq!(slot_vec[i].beacon_slot, (i as u64 + amount_of_data_to_be_deleted)); + assert_eq!(slot_vec[i].beacon_block_root, to_be_preserved_hash_list[i]); + + // Previous values should not exists + assert!(!FinalizedBeaconHeadersBlockRoot::::contains_key( + to_be_deleted_hash_list[i] + )); + assert!(!FinalizedBeaconHeaders::::contains_key( + to_be_deleted_hash_list[i] + )); + + // data that was preserved should exists + assert!(FinalizedBeaconHeadersBlockRoot::::contains_key( + to_be_preserved_hash_list[i] + )); + assert!(FinalizedBeaconHeaders::::contains_key( + to_be_preserved_hash_list[i] + )); + } + }); +} + +#[test] +pub fn test_prune_execution_headers() { + new_tester::().execute_with(|| { + let execution_header_prune_threshold = mock_minimal::ExecutionHeadersPruneThreshold::get(); + let to_be_deleted = execution_header_prune_threshold / 2; + + let mut stored_hashes = vec![]; + + for i in 0..execution_header_prune_threshold { + let mut hash = H256::default(); + thread_rng().try_fill(&mut hash.0[..]).unwrap(); + mock_minimal::EthereumBeaconClient::store_execution_header( + hash, + CompactExecutionHeader::default(), + i as u64, + hash, + ); + stored_hashes.push(hash); + } + + // We should have stored everything until now + assert_eq!( + ExecutionHeaders::::iter().count() as usize, + stored_hashes.len() + ); + + // Let's push extra entries so that some of the previous entries are deleted. + for i in 0..to_be_deleted { + let mut hash = H256::default(); + thread_rng().try_fill(&mut hash.0[..]).unwrap(); + mock_minimal::EthereumBeaconClient::store_execution_header( + hash, + CompactExecutionHeader::default(), + (i + execution_header_prune_threshold) as u64, + hash, + ); + + stored_hashes.push(hash); + } + + // We should have only stored upto `execution_header_prune_threshold` + assert_eq!( + ExecutionHeaders::::iter().count() as u32, + execution_header_prune_threshold + ); + + // First `to_be_deleted` items must be deleted + for i in 0..to_be_deleted { + assert!(!ExecutionHeaders::::contains_key( + stored_hashes[i as usize] + )); + } + + // Other entries should be part of data + for i in to_be_deleted..(to_be_deleted + execution_header_prune_threshold) { + assert!(ExecutionHeaders::::contains_key( + stored_hashes[i as usize] + )); + } + }); +} + +#[test] +pub fn test_prune_sync_committee() { + new_tester::().execute_with(|| { + let sync_committee_prune_threshold = mock_minimal::SyncCommitteePruneThreshold::get(); + let to_be_deleted = sync_committee_prune_threshold / 2; + let mut storing_periods = vec![]; + + let initial_sync = get_initial_sync::<{ SYNC_COMMITTEE_SIZE }>(); + + for i in 0..sync_committee_prune_threshold { + mock_minimal::EthereumBeaconClient::store_sync_committee( + i as u64, + &initial_sync.current_sync_committee, + ) + .unwrap(); + storing_periods.push(i); + } + + // We should retain every sync committee till prune threshold + assert_eq!( + SyncCommittees::::iter().count() as u32, + sync_committee_prune_threshold + ); + + // Now, we try to insert more than threshold, this should make previous entries deleted + for i in 0..to_be_deleted { + mock_minimal::EthereumBeaconClient::store_sync_committee( + (i + sync_committee_prune_threshold).into(), + &initial_sync.current_sync_committee, + ) + .unwrap(); + storing_periods.push(i + sync_committee_prune_threshold); + } + + // We should retain last prune threshold sync committee + assert_eq!( + SyncCommittees::::iter().count() as u32, + sync_committee_prune_threshold + ); + + // We verify that first periods of sync committees are not present now + for i in 0..to_be_deleted { + assert!(!SyncCommittees::::contains_key(i as u64)); + } + + // Rest of the sync committee should still exists + for i in to_be_deleted..(sync_committee_prune_threshold + to_be_deleted) { + assert!(SyncCommittees::::contains_key(i as u64)); + } + }); +} diff --git a/parachain/pallets/ethereum-beacon-client/src/tests_mainnet.rs b/parachain/pallets/ethereum-beacon-client/src/tests_mainnet.rs index 71d7ed646..3304e7c07 100644 --- a/parachain/pallets/ethereum-beacon-client/src/tests_mainnet.rs +++ b/parachain/pallets/ethereum-beacon-client/src/tests_mainnet.rs @@ -65,7 +65,7 @@ fn it_processes_a_finalized_header_update() { ); let slot = update.finalized_header.slot; - let import_time = 1616508000u64 + (slot * config::SECONDS_PER_SLOT); // Goerli genesis time + + let import_time = 1616508000u64 + (slot * config::SECONDS_PER_SLOT as u64); // Goerli genesis time + let mock_pallet_time = import_time + 3600; // plus one hour new_tester::().execute_with(|| { @@ -102,7 +102,7 @@ fn it_errors_when_weak_subjectivity_period_exceeded_for_a_finalized_header_updat ); let slot = update.finalized_header.slot; - let import_time = 1616508000u64 + (slot * config::SECONDS_PER_SLOT); + let import_time = 1616508000u64 + (slot * config::SECONDS_PER_SLOT as u64); let mock_pallet_time = import_time + 100800; // plus 28 hours new_tester::().execute_with(|| { diff --git a/parachain/pallets/ethereum-beacon-client/src/tests_minimal.rs b/parachain/pallets/ethereum-beacon-client/src/tests_minimal.rs index c76cd1589..e3063a4a4 100644 --- a/parachain/pallets/ethereum-beacon-client/src/tests_minimal.rs +++ b/parachain/pallets/ethereum-beacon-client/src/tests_minimal.rs @@ -159,7 +159,7 @@ fn it_processes_a_finalized_header_update() { .expect("Time went backwards") .as_secs(); - let import_time = time_now + (update.finalized_header.slot * config::SECONDS_PER_SLOT); // Goerli genesis time + finalized header update time + let import_time = time_now + (update.finalized_header.slot * config::SECONDS_PER_SLOT as u64); // Goerli genesis time + finalized header update time let mock_pallet_time = import_time + 3600; // plus one hour new_tester::().execute_with(|| { diff --git a/parachain/primitives/beacon/src/bls.rs b/parachain/primitives/beacon/src/bls.rs index 4612b82db..f4dd72a8b 100644 --- a/parachain/primitives/beacon/src/bls.rs +++ b/parachain/primitives/beacon/src/bls.rs @@ -20,7 +20,7 @@ pub enum BlsError { // legacy fast_aggregate_verify from all participant keys pub fn fast_aggregate_verify_legacy( - pubkeys: &Vec, + pubkeys: &[PublicKeyPrepared], message: H256, signature: &Signature, ) -> Result<(), BlsError> { diff --git a/parachain/primitives/beacon/src/merkle_proof.rs b/parachain/primitives/beacon/src/merkle_proof.rs index 0c794b27c..0eebb5784 100644 --- a/parachain/primitives/beacon/src/merkle_proof.rs +++ b/parachain/primitives/beacon/src/merkle_proof.rs @@ -3,11 +3,11 @@ use sp_io::hashing::sha2_256; // Reference https://github.com/ethereum/consensus-specs/blob/dev/ssz/merkle-proofs.md // p.s. index here is actually [subtree_index](https://github.com/ethereum/consensus-specs/blob/dev/specs/altair/light-client/sync-protocol.md#get_subtree_index) -pub fn verify_merkle_proof(leaf: H256, branch: &[H256], index: u64, root: H256) -> bool { +pub fn verify_merkle_proof(leaf: H256, branch: &[H256], index: usize, root: H256) -> bool { let mut value: [u8; 32] = leaf.into(); for (i, node) in branch.iter().enumerate() { let mut data = [0u8; 64]; - if (index / (2u32.pow(i as u32) as u64) % 2) == 0 { + if (index / (2_u32.pow(i as u32) as usize) % 2) == 0 { // left node data[0..32].copy_from_slice(&value); data[32..64].copy_from_slice(&node.0); diff --git a/parachain/primitives/beacon/src/types.rs b/parachain/primitives/beacon/src/types.rs index 14594994d..4bb8ce37d 100644 --- a/parachain/primitives/beacon/src/types.rs +++ b/parachain/primitives/beacon/src/types.rs @@ -107,7 +107,7 @@ impl<'de> Deserialize<'de> for Signature { } } -#[derive(Default, Encode, Decode, TypeInfo, MaxEncodedLen)] +#[derive(Copy, Clone, Default, Encode, Decode, TypeInfo, MaxEncodedLen)] pub struct ExecutionHeaderState { pub beacon_block_root: H256, pub beacon_slot: u64, @@ -115,7 +115,7 @@ pub struct ExecutionHeaderState { pub block_number: u64, } -#[derive(Default, Encode, Decode, TypeInfo, MaxEncodedLen)] +#[derive(Copy, Clone, Default, Encode, Decode, TypeInfo, MaxEncodedLen)] pub struct FinalizedHeaderState { pub beacon_block_root: H256, pub beacon_slot: u64, @@ -202,7 +202,9 @@ impl TryFrom<&SyncCommittee> /// Beacon block header as it is stored in the runtime storage. The block root is the /// Merklization of a BeaconHeader. -#[derive(Clone, Default, Encode, Decode, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)] +#[derive( + Copy, Clone, Default, Encode, Decode, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen, +)] #[cfg_attr(feature = "std", derive(Serialize, Deserialize))] pub struct BeaconHeader { // The slot for which this block is created. Must be greater than the slot of the block defined @@ -220,7 +222,7 @@ pub struct BeaconHeader { impl BeaconHeader { pub fn hash_tree_root(&self) -> Result { - hash_tree_root::(self.clone().into()) + hash_tree_root::((*self).into()) } } diff --git a/parachain/primitives/core/src/lib.rs b/parachain/primitives/core/src/lib.rs index bb286c6ac..f19647978 100644 --- a/parachain/primitives/core/src/lib.rs +++ b/parachain/primitives/core/src/lib.rs @@ -10,8 +10,10 @@ use frame_support::dispatch::DispatchError; use snowbridge_ethereum::{Header, Log, U256}; use sp_std::prelude::*; +pub mod ringbuffer; pub mod types; +pub use ringbuffer::{RingBufferMap, RingBufferMapImpl}; pub use types::{Message, MessageId, MessageNonce, Proof}; /// A trait for verifying messages. diff --git a/parachain/primitives/core/src/ringbuffer.rs b/parachain/primitives/core/src/ringbuffer.rs new file mode 100644 index 000000000..94e0b66c2 --- /dev/null +++ b/parachain/primitives/core/src/ringbuffer.rs @@ -0,0 +1,74 @@ +use codec::FullCodec; +use core::{cmp::Ord, marker::PhantomData, ops::Add}; +use frame_support::storage::{types::QueryKindTrait, StorageMap, StorageValue}; +use sp_core::{Get, GetDefault}; +use sp_runtime::traits::{One, Zero}; + +/// Trait object presenting the ringbuffer interface. +pub trait RingBufferMap +where + Key: FullCodec, + Value: FullCodec, + QueryKind: QueryKindTrait, +{ + /// Insert a map entry. + fn insert(k: Key, v: Value); + + /// Check if map contains a key + fn contains_key(k: Key) -> bool; + + /// Get the value of the key + fn get(k: Key) -> QueryKind::Query; +} + +pub struct RingBufferMapImpl( + PhantomData<(Index, B, CurrentIndex, Intermediate, M, QueryKind)>, +); + +/// Ringbuffer implementation based on `RingBufferTransient` +impl + RingBufferMap + for RingBufferMapImpl +where + Key: FullCodec + Clone, + Value: FullCodec, + Index: Ord + One + Zero + Add + Copy + FullCodec + Eq, + B: Get, + CurrentIndex: StorageValue, + Intermediate: StorageMap, + M: StorageMap, + QueryKind: QueryKindTrait, +{ + /// Insert a map entry. + fn insert(k: Key, v: Value) { + let bound = B::get(); + let mut current_index = CurrentIndex::get(); + + // Adding one here as bound denotes number of items but our index starts with zero. + if (current_index + Index::one()) >= bound { + current_index = Index::zero(); + } else { + current_index = current_index + Index::one(); + } + + // Deleting earlier entry if it exists + if Intermediate::contains_key(current_index) { + let older_key = Intermediate::get(current_index); + M::remove(older_key); + } + + Intermediate::insert(current_index, k.clone()); + CurrentIndex::set(current_index); + M::insert(k, v); + } + + /// Check if map contains a key + fn contains_key(k: Key) -> bool { + M::contains_key(k) + } + + /// Get the value associated with key + fn get(k: Key) -> M::Query { + M::get(k) + } +} diff --git a/relayer/chain/parachain/writer.go b/relayer/chain/parachain/writer.go index 656ce2661..ee12984b1 100644 --- a/relayer/chain/parachain/writer.go +++ b/relayer/chain/parachain/writer.go @@ -226,20 +226,26 @@ func (wr *ParachainWriter) GetLastBasicChannelNonceByAddress(address common.Addr } func (wr *ParachainWriter) GetFinalizedSlots() ([]uint64, error) { - key, err := types.CreateStorageKey(wr.conn.Metadata(), "EthereumBeaconClient", "FinalizedBeaconHeaderSlots", nil, nil) + key, err := types.CreateStorageKey(wr.conn.Metadata(), "EthereumBeaconClient", "FinalizedBeaconHeaderStates", nil, nil) if err != nil { return nil, fmt.Errorf("create storage key for basic channel nonces: %w", err) } - var slots []types.U64 - _, err = wr.conn.API().RPC.State.GetStorageLatest(key, &slots) + type StorageState struct { + BeaconBlockRoot types.H256 + BeaconSlot types.U64 + ImportTime types.U64 + } + + var states []StorageState + _, err = wr.conn.API().RPC.State.GetStorageLatest(key, &states) if err != nil { return nil, fmt.Errorf("get storage for latest basic channel nonces (err): %w", err) } result := []uint64{} - for _, slot := range slots { - result = append(result, uint64(slot)) + for _, state := range states { + result = append(result, uint64(state.BeaconSlot)) } return result, nil