diff --git a/parachain/Cargo.lock b/parachain/Cargo.lock index e9777ce158..a908e722ec 100644 --- a/parachain/Cargo.lock +++ b/parachain/Cargo.lock @@ -3561,6 +3561,7 @@ dependencies = [ "milagro_bls", "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 1f232a5d71..8c5a22052c 100644 --- a/parachain/pallets/ethereum-beacon-client/Cargo.toml +++ b/parachain/pallets/ethereum-beacon-client/Cargo.toml @@ -34,6 +34,7 @@ snowbridge-ethereum = { path = "../../primitives/ethereum", default-features = f snowbridge-beacon-primitives = { path = "../../primitives/beacon", default-features = false } [dev-dependencies] +rand = "0.8.5" sp-keyring = { git = "https://github.com/paritytech/substrate.git", branch = "master" } snowbridge-testutils = { path = "../../primitives/testutils" } serde_json = "1.0.96" diff --git a/parachain/pallets/ethereum-beacon-client/src/lib.rs b/parachain/pallets/ethereum-beacon-client/src/lib.rs index f868d4c05a..a5380a6343 100644 --- a/parachain/pallets/ethereum-beacon-client/src/lib.rs +++ b/parachain/pallets/ethereum-beacon-client/src/lib.rs @@ -18,9 +18,12 @@ mod tests_minimal; #[cfg(feature = "runtime-benchmarks")] mod benchmarking; -pub use weights::WeightInfo; +mod ringbuffer; -use crate::merkleization::get_sync_committee_bits; +use crate::{ + merkleization::get_sync_committee_bits, + ringbuffer::{RingBufferMap, RingBufferMapImpl}, +}; use frame_support::{dispatch::DispatchResult, log, traits::UnixTime, transactional}; use frame_system::ensure_signed; use snowbridge_beacon_primitives::{ @@ -32,6 +35,7 @@ use snowbridge_core::{Message, Verifier}; use sp_core::H256; use sp_io::hashing::sha2_256; use sp_std::prelude::*; +pub use weights::WeightInfo; use frame_support::{traits::Get, BoundedVec}; @@ -99,6 +103,12 @@ pub mod pallet { type MaxFinalizedHeaderSlotArray: Get; #[pallet::constant] type ForkVersions: Get; + /// Maximum execution headers are stored + #[pallet::constant] + type ExecutionHeadersPruneThreshold: Get; + /// Maximum sync committees to be stored + #[pallet::constant] + type SyncCommitteePruneThreshold: Get; type WeightInfo: WeightInfo; type WeakSubjectivityPeriodSeconds: Get; } @@ -165,7 +175,7 @@ pub mod pallet { #[pallet::storage] pub(super) type FinalizedBeaconHeaderSlots = - StorageValue<_, BoundedVec, ValueQuery>; + StorageValue<_, BoundedVec<(u64, H256), T::MaxFinalizedHeaderSlotArray>, ValueQuery>; #[pallet::storage] pub(super) type FinalizedBeaconHeadersBlockRoot = @@ -175,12 +185,53 @@ pub mod pallet { pub(super) type ExecutionHeaders = StorageMap<_, Identity, H256, ExecutionHeader, OptionQuery>; + /// Execution headers ring buffer map implementation + + /// Index storage for execution header ring buffer map + #[pallet::storage] + pub(crate) type ExecutionHeaderBufferIndex = StorageValue<_, u64, ValueQuery>; + + /// Intermediate storage for execution header mapping + #[pallet::storage] + pub(crate) type ExecutionHeaderMapping = + StorageMap<_, Identity, u64, H256, ValueQuery>; + + /// Ring buffer Map for Execution header + pub(crate) type ExecutionHeaderRingBufferMap = RingBufferMapImpl< + u64, + ::ExecutionHeadersPruneThreshold, + ExecutionHeaderBufferIndex, + ExecutionHeaderMapping, + ExecutionHeaders, + OptionQuery, + >; + /// Current sync committee corresponding to the active header. - /// TODO prune older sync committees than xxx #[pallet::storage] pub(super) type SyncCommittees = StorageMap<_, Identity, u64, SyncCommitteeOf, ValueQuery>; + /// Sync committee ring buffer map implementation + + /// Index storage for sync committee ring buffer + #[pallet::storage] + pub(crate) type SyncCommitteesBufferIndex = StorageValue<_, u64, ValueQuery>; + + /// Intermediate storage for sync committee mapping + #[pallet::storage] + pub(crate) type SyncCommitteesMapping = + StorageMap<_, Identity, u64, u64, ValueQuery>; + + /// Ring buffer Map for Sync committee + pub(crate) type SyncCommitteesRingBufferMap = RingBufferMapImpl< + u64, + ::SyncCommitteePruneThreshold, + SyncCommitteesBufferIndex, + SyncCommitteesMapping, + SyncCommittees, + ValueQuery, + >; + #[pallet::storage] pub(super) type ValidatorsRoot = StorageValue<_, H256, ValueQuery>; @@ -384,7 +435,7 @@ pub mod pallet { }; >::insert(block_root, initial_sync.header); - Self::add_finalized_header_slot(slot)?; + Self::add_finalized_header_slot(slot, block_root)?; >::set(last_finalized_header); Ok(()) @@ -433,12 +484,12 @@ pub mod pallet { signature_slot_period ); ensure!( - >::contains_key(current_period), + >::contains_key(current_period), Error::::SyncCommitteeMissing ); let next_period = current_period + 1; ensure!( - !>::contains_key(next_period), + !>::contains_key(next_period), Error::::InvalidSyncCommitteePeriodUpdateWithDuplication ); ensure!( @@ -864,8 +915,8 @@ pub mod pallet { Ok(()) } - fn store_sync_committee(period: u64, sync_committee: SyncCommitteeOf) { - >::insert(period, sync_committee); + pub(crate) fn store_sync_committee(period: u64, sync_committee: SyncCommitteeOf) { + >::insert(period, sync_committee); log::trace!( target: "ethereum-beacon-client", @@ -882,7 +933,7 @@ pub mod pallet { let slot = header.slot; >::insert(block_root, header); - Self::add_finalized_header_slot(slot)?; + Self::add_finalized_header_slot(slot, block_root)?; log::info!( target: "ethereum-beacon-client", @@ -902,19 +953,27 @@ pub mod pallet { Ok(()) } - fn add_finalized_header_slot(slot: u64) -> DispatchResult { + pub(super) fn add_finalized_header_slot( + slot: u64, + finalized_header_hash: H256, + ) -> DispatchResult { >::try_mutate(|b_vec| { if b_vec.len() as u32 == T::MaxFinalizedHeaderSlotArray::get() { - b_vec.remove(0); + let (_slot, finalized_header_hash) = b_vec.remove(0); + // Removing corresponding finalized header data of popped slot + // as that data will not be used by relayer anyway. + >::remove(finalized_header_hash); + >::remove(finalized_header_hash); + >::remove(finalized_header_hash); } - b_vec.try_push(slot) + b_vec.try_push((slot, finalized_header_hash)) }) .map_err(|_| >::FinalizedBeaconHeaderSlotsExceeded)?; Ok(()) } - fn store_execution_header( + pub(crate) fn store_execution_header( block_hash: H256, header: ExecutionHeader, beacon_slot: u64, @@ -922,7 +981,7 @@ pub mod pallet { ) { let block_number = header.block_number; - >::insert(block_hash, header); + >::insert(block_hash, header); log::trace!( target: "ethereum-beacon-client", @@ -1044,7 +1103,7 @@ pub mod pallet { pub(super) fn get_sync_committee_for_period( period: u64, ) -> Result, DispatchError> { - let sync_committee = >::get(period); + let sync_committee = >::get(period); if sync_committee.pubkeys.len() == 0 { log::error!(target: "ethereum-beacon-client", "💫 Sync committee for period {} missing", period); @@ -1128,7 +1187,7 @@ pub mod pallet { message.proof.block_hash, ); - let stored_header = >::get(message.proof.block_hash) + let stored_header = >::get(message.proof.block_hash) .ok_or(Error::::MissingHeader)?; let receipt = match Self::verify_receipt_inclusion(stored_header, &message.proof) { diff --git a/parachain/pallets/ethereum-beacon-client/src/mock.rs b/parachain/pallets/ethereum-beacon-client/src/mock.rs index ff2bc96380..9bd508a853 100644 --- a/parachain/pallets/ethereum-beacon-client/src/mock.rs +++ b/parachain/pallets/ethereum-beacon-client/src/mock.rs @@ -77,7 +77,9 @@ pub mod mock_minimal { pub const MaxPublicKeySize: u32 = config::PUBKEY_SIZE as u32; pub const MaxSignatureSize: u32 = config::SIGNATURE_SIZE as u32; pub const MaxSlotsPerHistoricalRoot: u64 = 64; - pub const MaxFinalizedHeaderSlotArray: u32 = 1000; + pub const MaxFinalizedHeaderSlotArray: u32 = 12; + pub const SyncCommitteePruneThreshold: u64 = 4; + pub const ExecutionHeadersPruneThreshold: u64 = 10; pub const WeakSubjectivityPeriodSeconds: u32 = 97200; pub const ChainForkVersions: ForkVersions = ForkVersions{ genesis: Fork { @@ -112,6 +114,8 @@ pub mod mock_minimal { type MaxSlotsPerHistoricalRoot = MaxSlotsPerHistoricalRoot; type MaxFinalizedHeaderSlotArray = MaxFinalizedHeaderSlotArray; type ForkVersions = ChainForkVersions; + type SyncCommitteePruneThreshold = SyncCommitteePruneThreshold; + type ExecutionHeadersPruneThreshold = ExecutionHeadersPruneThreshold; type WeakSubjectivityPeriodSeconds = WeakSubjectivityPeriodSeconds; type WeightInfo = (); } @@ -203,6 +207,8 @@ pub mod mock_mainnet { epoch: 162304, }, }; + pub const SyncCommitteePruneThreshold: u64 = 4; + pub const ExecutionHeadersPruneThreshold: u64 = 10; } impl ethereum_beacon_client::Config for Test { @@ -218,6 +224,8 @@ pub mod mock_mainnet { type MaxSlotsPerHistoricalRoot = MaxSlotsPerHistoricalRoot; type MaxFinalizedHeaderSlotArray = MaxFinalizedHeaderSlotArray; type ForkVersions = ChainForkVersions; + type SyncCommitteePruneThreshold = SyncCommitteePruneThreshold; + type ExecutionHeadersPruneThreshold = ExecutionHeadersPruneThreshold; type WeakSubjectivityPeriodSeconds = WeakSubjectivityPeriodSeconds; type WeightInfo = (); } diff --git a/parachain/pallets/ethereum-beacon-client/src/ringbuffer.rs b/parachain/pallets/ethereum-beacon-client/src/ringbuffer.rs new file mode 100644 index 0000000000..94e0b66c2c --- /dev/null +++ b/parachain/pallets/ethereum-beacon-client/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/parachain/pallets/ethereum-beacon-client/src/tests.rs b/parachain/pallets/ethereum-beacon-client/src/tests.rs index ebd07b8651..604cb5c670 100644 --- a/parachain/pallets/ethereum-beacon-client/src/tests.rs +++ b/parachain/pallets/ethereum-beacon-client/src/tests.rs @@ -5,10 +5,11 @@ mod beacon_tests { merkleization::MerkleizationError, mock::*, ssz::{SSZExecutionPayloadHeader, SSZSyncAggregate}, - BeaconHeader, Error, PublicKey, + BeaconHeader, Error, ExecutionHeader, PublicKey, SyncCommittee, }; use frame_support::{assert_err, assert_ok}; use hex_literal::hex; + use rand::{thread_rng, Rng}; use snowbridge_beacon_primitives::{ExecutionPayloadHeader, SyncAggregate}; use sp_core::{H256, U256}; use ssz_rs::prelude::Vector; @@ -230,7 +231,7 @@ mod beacon_tests { #[test] pub fn test_bls_fast_aggregate_verify_minimal() { new_tester::().execute_with(|| { - assert_ok!(mock_minimal::EthereumBeaconClient::bls_fast_aggregate_verify( + assert_ok!(mock_minimal::EthereumBeaconClient::bls_fast_aggregate_verify( vec![ PublicKey(hex!("a73eb991aa22cdb794da6fcde55a427f0a4df5a4a70de23a988b5e5fc8c4d844f66d990273267a54dd21579b7ba6a086").into()), PublicKey(hex!("b29043a7273d0a2dbc2b747dcf6a5eccbd7ccb44b2d72e985537b117929bc3fd3a99001481327788ad040b4077c47c0d").into()), @@ -240,13 +241,13 @@ mod beacon_tests { hex!("69241e7146cdcc5a5ddc9a60bab8f378c0271e548065a38bcc60624e1dbed97f").into(), hex!("b204e9656cbeb79a9a8e397920fd8e60c5f5d9443f58d42186f773c6ade2bd263e2fe6dbdc47f148f871ed9a00b8ac8b17a40d65c8d02120c00dca77495888366b4ccc10f1c6daa02db6a7516555ca0665bca92a647b5f3a514fa083fdc53b6e").to_vec().try_into().expect("signature is too long"), )); - }); + }); } #[test] pub fn test_bls_fast_aggregate_verify_invalid_point() { new_tester::().execute_with(|| { - assert_err!(mock_minimal::EthereumBeaconClient::bls_fast_aggregate_verify( + assert_err!(mock_minimal::EthereumBeaconClient::bls_fast_aggregate_verify( vec![ PublicKey(hex!("973eb991aa22cdb794da6fcde55a427f0a4df5a4a70de23a988b5e5fc8c4d844f66d990273267a54dd21579b7ba6a086").into()), PublicKey(hex!("b29043a7273d0a2dbc2b747dcf6a5eccbd7ccb44b2d72e985537b117929bc3fd3a99001481327788ad040b4077c47c0d").into()), @@ -256,13 +257,13 @@ mod beacon_tests { hex!("69241e7146cdcc5a5ddc9a60bab8f378c0271e548065a38bcc60624e1dbed97f").into(), hex!("b204e9656cbeb79a9a8e397920fd8e60c5f5d9443f58d42186f773c6ade2bd263e2fe6dbdc47f148f871ed9a00b8ac8b17a40d65c8d02120c00dca77495888366b4ccc10f1c6daa02db6a7516555ca0665bca92a647b5f3a514fa083fdc53b6e").to_vec().try_into().expect("signature is too long"), ), Error::::InvalidSignaturePoint); - }); + }); } #[test] pub fn test_bls_fast_aggregate_verify_invalid_message() { new_tester::().execute_with(|| { - assert_err!(mock_minimal::EthereumBeaconClient::bls_fast_aggregate_verify( + assert_err!(mock_minimal::EthereumBeaconClient::bls_fast_aggregate_verify( vec![ PublicKey(hex!("a73eb991aa22cdb794da6fcde55a427f0a4df5a4a70de23a988b5e5fc8c4d844f66d990273267a54dd21579b7ba6a086").into()), PublicKey(hex!("b29043a7273d0a2dbc2b747dcf6a5eccbd7ccb44b2d72e985537b117929bc3fd3a99001481327788ad040b4077c47c0d").into()), @@ -272,13 +273,13 @@ mod beacon_tests { hex!("99241e7146cdcc5a5ddc9a60bab8f378c0271e548065a38bcc60624e1dbed97f").into(), hex!("b204e9656cbeb79a9a8e397920fd8e60c5f5d9443f58d42186f773c6ade2bd263e2fe6dbdc47f148f871ed9a00b8ac8b17a40d65c8d02120c00dca77495888366b4ccc10f1c6daa02db6a7516555ca0665bca92a647b5f3a514fa083fdc53b6e").to_vec().try_into().expect("signature is too long"), ), Error::::SignatureVerificationFailed); - }); + }); } #[test] pub fn test_bls_fast_aggregate_verify_invalid_signature() { new_tester::().execute_with(|| { - assert_err!(mock_minimal::EthereumBeaconClient::bls_fast_aggregate_verify( + assert_err!(mock_minimal::EthereumBeaconClient::bls_fast_aggregate_verify( vec![ PublicKey(hex!("a73eb991aa22cdb794da6fcde55a427f0a4df5a4a70de23a988b5e5fc8c4d844f66d990273267a54dd21579b7ba6a086").into()), PublicKey(hex!("b29043a7273d0a2dbc2b747dcf6a5eccbd7ccb44b2d72e985537b117929bc3fd3a99001481327788ad040b4077c47c0d").into()), @@ -288,7 +289,7 @@ mod beacon_tests { hex!("69241e7146cdcc5a5ddc9a60bab8f378c0271e548065a38bcc60624e1dbed97f").into(), hex!("c204e9656cbeb79a9a8e397920fd8e60c5f5d9443f58d42186f773c6ade2bd263e2fe6dbdc47f148f871ed9a00b8ac8b17a40d65c8d02120c00dca77495888366b4ccc10f1c6daa02db6a7516555ca0665bca92a647b5f3a514fa083fdc53b6e").to_vec().try_into().expect("signature is too long"), ), Error::::InvalidSignature); - }); + }); } #[test] @@ -524,21 +525,21 @@ mod beacon_tests { #[test] pub fn test_hash_tree_root_execution_payload() { let payload: Result = - ExecutionPayloadHeader::{ - parent_hash: hex!("eadee5ab098dde64e9fd02ae5858064bad67064070679625b09f8d82dec183f7").into(), - fee_recipient: hex!("f97e180c050e5ab072211ad2c213eb5aee4df134").to_vec().try_into().expect("fee recipient bits are too long"), - state_root: hex!("564fa064c2a324c2b5978d7fdfc5d4224d4f421a45388af1ed405a399c845dff").into(), - receipts_root: hex!("56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421").into(), - logs_bloom: hex!("00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000").to_vec().try_into().expect("logs bloom is too long"), - prev_randao: hex!("6bf538bdfbdf1c96ff528726a40658a91d0bda0f1351448c4c4f3604db2a0ccf").into(), - block_number: 477434, - gas_limit: 8154925, - gas_used: 0, - timestamp: 1652816940, - extra_data: vec![].try_into().expect("extra data field is too long"), - base_fee_per_gas: U256::from(7 as i16), - block_hash: hex!("cd8df91b4503adb8f2f1c7a4f60e07a1f1a2cbdfa2a95bceba581f3ff65c1968").into(), - transactions_root: hex!("7ffe241ea60187fdb0187bfa22de35d1f9bed7ab061d9401fd47e34a54fbede1").into(), + ExecutionPayloadHeader::{ + parent_hash: hex!("eadee5ab098dde64e9fd02ae5858064bad67064070679625b09f8d82dec183f7").into(), + fee_recipient: hex!("f97e180c050e5ab072211ad2c213eb5aee4df134").to_vec().try_into().expect("fee recipient bits are too long"), + state_root: hex!("564fa064c2a324c2b5978d7fdfc5d4224d4f421a45388af1ed405a399c845dff").into(), + receipts_root: hex!("56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421").into(), + logs_bloom: hex!("00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000").to_vec().try_into().expect("logs bloom is too long"), + prev_randao: hex!("6bf538bdfbdf1c96ff528726a40658a91d0bda0f1351448c4c4f3604db2a0ccf").into(), + block_number: 477434, + gas_limit: 8154925, + gas_used: 0, + timestamp: 1652816940, + extra_data: vec![].try_into().expect("extra data field is too long"), + base_fee_per_gas: U256::from(7 as i16), + block_hash: hex!("cd8df91b4503adb8f2f1c7a4f60e07a1f1a2cbdfa2a95bceba581f3ff65c1968").into(), + transactions_root: hex!("7ffe241ea60187fdb0187bfa22de35d1f9bed7ab061d9401fd47e34a54fbede1").into(), withdrawals_root: hex!("28ba1834a3a7b657460ce79fa3a1d909ab8828fd557659d4d0554a9bdbc0ec30").into(), }.try_into(); assert_ok!(&payload); @@ -546,4 +547,156 @@ mod beacon_tests { let hash_root = merkleization::hash_tree_root(payload.unwrap()); assert_ok!(&hash_root); } + + #[test] + pub fn test_prune_finalized_header() { + new_tester::().execute_with(|| { + let max_finalized_slots = ::MaxFinalizedHeaderSlotArray::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); + } + + ethereum_beacon_client::FinalizedBeaconHeadersBlockRoot::::insert(hash, hash); + ethereum_beacon_client::FinalizedBeaconHeaders::::insert(hash, BeaconHeader::default()); + assert_ok!(mock_minimal::EthereumBeaconClient::add_finalized_header_slot(i, hash)); + + } + + // We first verify if the data corresponding to that hash is still there. + let slot_vec = ethereum_beacon_client::FinalizedBeaconHeaderSlots::::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].0, i as u64); + assert_eq!(slot_vec[i].1, to_be_deleted_hash_list[i]); + + assert!(ethereum_beacon_client::FinalizedBeaconHeadersBlockRoot::::contains_key(to_be_deleted_hash_list[i])); + assert!(ethereum_beacon_client::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(); + ethereum_beacon_client::FinalizedBeaconHeadersBlockRoot::::insert(hash, hash); + ethereum_beacon_client::FinalizedBeaconHeaders::::insert(hash, BeaconHeader::default()); + assert_ok!(mock_minimal::EthereumBeaconClient::add_finalized_header_slot(i, hash)); + } + + // Now, previous hashes should be pruned and in array those elements are replaced by later elements + let slot_vec = ethereum_beacon_client::FinalizedBeaconHeaderSlots::::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].0, (i as u64 + amount_of_data_to_be_deleted)); + assert_eq!(slot_vec[i].1, to_be_preserved_hash_list[i]); + + // Previous values should not exists + assert!(!ethereum_beacon_client::FinalizedBeaconHeadersBlockRoot::::contains_key(to_be_deleted_hash_list[i])); + assert!(!ethereum_beacon_client::FinalizedBeaconHeaders::::contains_key(to_be_deleted_hash_list[i])); + + // data that was preserved should exists + assert!(ethereum_beacon_client::FinalizedBeaconHeadersBlockRoot::::contains_key(to_be_preserved_hash_list[i])); + assert!(ethereum_beacon_client::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 = ::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, + ExecutionHeader::default(), + i, + hash + ); + stored_hashes.push(hash); + } + + // We should have stored everything until now + assert_eq!(ethereum_beacon_client::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, + ExecutionHeader::default(), + i+execution_header_prune_threshold, + hash + ); + + stored_hashes.push(hash); + } + + // We should have only stored upto `execution_header_prune_threshold` + assert_eq!(ethereum_beacon_client::ExecutionHeaders::::iter().count() as u64, execution_header_prune_threshold); + + // First `to_be_deleted` items must be deleted + for i in 0..to_be_deleted { + assert!(!ethereum_beacon_client::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!(ethereum_beacon_client::ExecutionHeaders::::contains_key(stored_hashes[i as usize])); + } + }); + } + + #[test] + pub fn test_prune_sync_committee() { + new_tester::().execute_with(|| { + let sync_committee_prune_threshold = ::SyncCommitteePruneThreshold::get(); + let to_be_deleted = sync_committee_prune_threshold / 2; + let mut storing_periods = vec![]; + + for i in 0..sync_committee_prune_threshold { + mock_minimal::EthereumBeaconClient::store_sync_committee(i, SyncCommittee::default()); + storing_periods.push(i); + } + + // We should retain every sync committee till prune threshold + assert_eq!(ethereum_beacon_client::SyncCommittees::::iter().count() as u64, 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, SyncCommittee::default()); + storing_periods.push(i+sync_committee_prune_threshold); + } + + // We should retain last prune threshold sync committee + assert_eq!(ethereum_beacon_client::SyncCommittees::::iter().count() as u64, sync_committee_prune_threshold); + + // We verify that first periods of sync committees are not present now + for i in 0..to_be_deleted { + assert!(!ethereum_beacon_client::SyncCommittees::::contains_key(i)); + } + + // Rest of the sync committee should still exists + for i in to_be_deleted..(sync_committee_prune_threshold+to_be_deleted) { + assert!(ethereum_beacon_client::SyncCommittees::::contains_key(i)); + } + + }); + } }