From e73f3855fdb2271f1316e7e4091153ff968449b5 Mon Sep 17 00:00:00 2001 From: Uncle Stinky Date: Wed, 11 Jun 2025 22:56:48 +0300 Subject: [PATCH] authorities stored based on the session hash map Signed-off-by: Uncle Stinky --- Cargo.lock | 2 +- pallets/slow-clap/Cargo.toml | 2 +- pallets/slow-clap/src/benchmarking.rs | 7 +- pallets/slow-clap/src/lib.rs | 58 +++++++------ pallets/slow-clap/src/mock.rs | 26 +++++- pallets/slow-clap/src/tests.rs | 117 ++++++++++++++++++++++---- 6 files changed, 161 insertions(+), 51 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 11d358b..1c8f0ec 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3834,7 +3834,7 @@ dependencies = [ [[package]] name = "ghost-slow-clap" -version = "0.3.20" +version = "0.3.23" dependencies = [ "frame-benchmarking", "frame-support", diff --git a/pallets/slow-clap/Cargo.toml b/pallets/slow-clap/Cargo.toml index 0b9bc67..af39f50 100644 --- a/pallets/slow-clap/Cargo.toml +++ b/pallets/slow-clap/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ghost-slow-clap" -version = "0.3.21" +version = "0.3.23" description = "Applause protocol for the EVM bridge" license.workspace = true authors.workspace = true diff --git a/pallets/slow-clap/src/benchmarking.rs b/pallets/slow-clap/src/benchmarking.rs index b547876..26fb21d 100644 --- a/pallets/slow-clap/src/benchmarking.rs +++ b/pallets/slow-clap/src/benchmarking.rs @@ -18,11 +18,12 @@ benchmarks! { let receiver = create_account::(); let amount = minimum_balance + minimum_balance; let network_id = NetworkIdOf::::default(); + let session_index = T::ValidatorSet::session_index(); let authorities = vec![T::AuthorityId::generate_pair(None)]; let bounded_authorities = WeakBoundedVec::<_, T::MaxAuthorities>::try_from(authorities.clone()) .map_err(|()| "more than the maximum number of keys provided")?; - Authorities::::put(bounded_authorities); + Authorities::::set(&session_index, bounded_authorities); let clap = Clap { session_index: 0, @@ -48,17 +49,17 @@ benchmarks! { } self_applause { + let session_index = T::ValidatorSet::session_index(); let authority = T::AuthorityId::generate_pair(Some(vec![69u8])); let bounded_authorities = WeakBoundedVec::<_, T::MaxAuthorities>::try_from(vec![authority.clone()]) .map_err(|()| "more than the maximum number of keys provided")?; - Authorities::::put(bounded_authorities); + Authorities::::set(&session_index, bounded_authorities); let minimum_balance = <::Currency>::minimum_balance(); let receiver = create_account::(); let receiver_clone = receiver.clone(); let amount = minimum_balance + minimum_balance; let network_id = NetworkIdOf::::default(); - let session_index = Default::default(); let transaction_hash = H256::repeat_byte(1u8); let unique_transaction_hash = >::generate_unique_hash( diff --git a/pallets/slow-clap/src/lib.rs b/pallets/slow-clap/src/lib.rs index f30e3f2..317fe89 100644 --- a/pallets/slow-clap/src/lib.rs +++ b/pallets/slow-clap/src/lib.rs @@ -13,7 +13,7 @@ use frame_support::{ EstimateNextSessionRotation, ValidatorSet, ValidatorSetWithIdentification, OneSessionHandler, Get, }, - BoundedSlice, WeakBoundedVec, + WeakBoundedVec, }; use frame_system::{ @@ -325,7 +325,6 @@ pub mod pallet { pub enum Error { NotEnoughClaps, NotAnAuthority, - ClapForWrongSession, CurrentValidatorIsDisabled, AlreadyClapped, UnregisteredClapRemove, @@ -371,20 +370,27 @@ pub mod pallet { >; #[pallet::storage] - #[pallet::getter(fn keys)] - pub(super) type Authorities = - StorageValue<_, WeakBoundedVec, ValueQuery>; + #[pallet::getter(fn authorities)] + pub(super) type Authorities = StorageMap< + _, + Twox64Concat, + SessionIndex, + WeakBoundedVec, + ValueQuery, + >; #[pallet::genesis_config] #[derive(frame_support::DefaultNoBound)] pub struct GenesisConfig { - pub keys: Vec, + pub authorities: Vec, } #[pallet::genesis_build] impl BuildGenesisConfig for GenesisConfig { fn build(&self) { - Pallet::::initialize_authorities(&self.keys); + if !self.authorities.is_empty() { + Pallet::::initialize_authorities(self.authorities.clone()); + } } } @@ -465,7 +471,7 @@ pub mod pallet { fn validate_unsigned(_source: TransactionSource, call: &Self::Call) -> TransactionValidity { if let Call::slow_clap { clap, signature } = call { - let authorities = Authorities::::get(); + let authorities = Authorities::::get(&clap.session_index); let authority = match authorities.get(clap.authority_index as usize) { Some(authority) => authority, None => return InvalidTransaction::BadSigner.into(), @@ -506,10 +512,7 @@ impl Pallet { } fn try_slow_clap(clap: &Clap, BalanceOf>) -> DispatchResult { - let current_session_index = T::ValidatorSet::session_index(); - ensure!(current_session_index == clap.session_index, Error::::ClapForWrongSession); - - let authorities = Authorities::::get(); + let authorities = Authorities::::get(&clap.session_index); ensure!(authorities.get(clap.authority_index as usize).is_some(), Error::::NotAnAuthority); let clap_unique_hash = Self::generate_unique_hash(&clap.receiver, &clap.amount, &clap.network_id); @@ -632,7 +635,7 @@ impl Pallet { let enough_authorities = Perbill::from_rational( ReceivedClaps::::get(&received_claps_key).len() as u32, - Authorities::::get().len() as u32, + Authorities::::get(session_index).len() as u32, ) > Perbill::from_percent(T::ApplauseThreshold::get()); ensure!(enough_authorities, Error::::NotEnoughClaps); @@ -795,7 +798,8 @@ impl Pallet { } fn local_authorities() -> impl Iterator { - let authorities = Authorities::::get(); + let session_index = T::ValidatorSet::session_index(); + let authorities = Authorities::::get(&session_index); let mut local_authorities = T::AuthorityId::all(); local_authorities.sort(); authorities.into_iter().enumerate().filter_map(move |(index, authority)| { @@ -920,13 +924,13 @@ impl Pallet { authority_deviation < Perbill::from_percent(T::OffenceThreshold::get()) } - fn initialize_authorities(authorities: &[T::AuthorityId]) { - if !authorities.is_empty() { - assert!(Authorities::::get().is_empty(), "Authorities are already initilized!"); - let bounded_authorities = BoundedSlice::<'_, _, T::MaxAuthorities>::try_from(authorities) - .expect("more than the maximum number of authorities"); - Authorities::::put(bounded_authorities); - } + fn initialize_authorities(authorities: Vec) { + let session_index = T::ValidatorSet::session_index(); + assert!(Authorities::::get(&session_index).is_empty(), "Authorities are already initilized!"); + let bounded_authorities = WeakBoundedVec::<_, T::MaxAuthorities>::try_from(authorities) + .expect("more than the maximum number of authorities"); + Authorities::::set(&session_index, bounded_authorities); + ClapsInSession::::set(&session_index, Default::default()); } fn calculate_median_claps(session_index: &SessionIndex) -> u32 { @@ -952,10 +956,10 @@ impl Pallet { } #[cfg(test)] - fn set_test_authorities(authorities: Vec) { + fn set_test_authorities(session_index: SessionIndex, authorities: Vec) { let bounded_authorities = WeakBoundedVec::<_, T::MaxAuthorities>::try_from(authorities) .expect("more than the maximum number of authorities"); - Authorities::::put(bounded_authorities); + Authorities::::set(session_index, bounded_authorities); } #[cfg(feature = "runtime-benchmarks")] @@ -983,23 +987,21 @@ impl OneSessionHandler for Pallet { I: Iterator, { let authorities = validators.map(|x| x.1).collect::>(); - Self::initialize_authorities(&authorities); + Self::initialize_authorities(authorities); } fn on_new_session<'a, I: 'a>(_changed: bool, validators: I, _queued_validators: I) where I: Iterator, { - let previous_session = T::ValidatorSet::session_index().saturating_sub(1); let authorities = validators.map(|x| x.1).collect::>(); - Self::initialize_authorities(&authorities); - ClapsInSession::::set(previous_session, Default::default()); + Self::initialize_authorities(authorities); } fn on_before_session_ending() { let session_index = T::ValidatorSet::session_index(); let validators = T::ValidatorSet::validators(); - let authorities = Authorities::::get(); + let authorities = Authorities::::get(&session_index); let median_claps = Self::calculate_median_claps(&session_index); diff --git a/pallets/slow-clap/src/mock.rs b/pallets/slow-clap/src/mock.rs index 088bc8d..eb221c7 100644 --- a/pallets/slow-clap/src/mock.rs +++ b/pallets/slow-clap/src/mock.rs @@ -16,7 +16,6 @@ use sp_staking::{ SessionIndex, }; -#[cfg(feature = "runtime-benchmarks")] use sp_runtime::BuildStorage; use crate as slow_clap; @@ -231,12 +230,33 @@ pub fn advance_session() { let now = System::block_number().max(1); System::set_block_number(now + 1); Session::rotate_session(); + let session_index = Session::current_index(); let authorities = Session::validators() .into_iter() .map(UintAuthorityId) .collect(); - SlowClap::set_test_authorities(authorities); - assert_eq!(Session::current_index(), (now / Period::get()) as u32); + SlowClap::set_test_authorities(session_index, authorities); + assert_eq!(session_index, (now / Period::get()) as u32); +} + +pub fn advance_session_with_authority(authority: u64) { + let now = System::block_number().max(1); + System::set_block_number(now + 1); + Session::rotate_session(); + + let session_index = Session::current_index(); + + SlowClap::set_test_authorities( + session_index, + vec![ + UintAuthorityId::from(authority), + UintAuthorityId::from(69), + UintAuthorityId::from(420), + UintAuthorityId::from(1337), + ] + ); + assert_eq!(session_index, (now / Period::get()) as u32); + } diff --git a/pallets/slow-clap/src/tests.rs b/pallets/slow-clap/src/tests.rs index 069454e..5349b8b 100644 --- a/pallets/slow-clap/src/tests.rs +++ b/pallets/slow-clap/src/tests.rs @@ -2,18 +2,19 @@ use super::*; use crate::mock::*; + use frame_support::{assert_err, assert_ok, dispatch}; use sp_core::offchain::{ testing, testing::{TestOffchainExt, TestTransactionPoolExt}, OffchainWorkerExt, OffchainDbExt, TransactionPoolExt, }; -use sp_runtime::testing::UintAuthorityId; +use sp_runtime::{DispatchError, testing::UintAuthorityId}; use ghost_networks::BridgedInflationCurve; use pallet_staking::EraPayout; -const MAX_DEVIATION_DEPTH: u32 = 10; +const MAX_DEVIATION_DEPTH: u64 = 10; fn prepare_evm_network( maybe_network_id: Option, @@ -39,6 +40,34 @@ fn prepare_evm_network( network_data } +fn do_clap_from_first_authority( + session_index: u32, + network_id: u32, + authority: u64, +) -> dispatch::DispatchResult { + let (transaction_hash, receiver, amount) = get_mocked_metadata(); + let clap = Clap { + block_number: 420, + removed: false, + transaction_hash, + session_index, + authority_index: 0, + network_id, + receiver, + amount, + }; + let authority = UintAuthorityId::from(authority); + let signature = authority.sign(&clap.encode()).unwrap(); + + SlowClap::pre_dispatch(&crate::Call::slow_clap { + clap: clap.clone(), + signature: signature.clone(), + }) + .map_err(|e| <&'static str>::from(e))?; + + SlowClap::slow_clap(RuntimeOrigin::none(), clap, signature) +} + fn do_clap_from( session_index: u32, network_id: u32, @@ -325,23 +354,63 @@ fn should_throw_error_if_session_index_is_not_current() { let (network_id, transaction_hash, unique_transaction_hash) = generate_unique_hash(None, None, None, None); + let bad_signer = 777; + let bad_signer_id = 5; + new_test_ext().execute_with(|| { - let session_index = advance_session_and_get_index(); + let mut session_and_indexes = Vec::new(); for deviation in 1..MAX_DEVIATION_DEPTH { - let session_index_up = session_index.saturating_add(deviation); - let session_index_down = session_index.saturating_sub(deviation); - let storage_key_up = (session_index_up, transaction_hash, unique_transaction_hash); - let storage_key_down = (session_index_down, transaction_hash, unique_transaction_hash); + advance_session_with_authority(deviation); + session_and_indexes.push((deviation, Session::current_index())); + } + + for chunk in session_and_indexes.chunks(3).into_iter() { + let authority_curr = chunk[1].0; + let authority_prev = chunk[0].0; + let authority_next = chunk[2].0; + + let session_index_curr = chunk[1].1; + let session_index_prev = chunk[0].1; + let session_index_next = chunk[2].1; + + let storage_key_curr = (session_index_curr, transaction_hash, unique_transaction_hash); + let storage_key_prev = (session_index_prev, transaction_hash, unique_transaction_hash); + let storage_key_next = (session_index_next, transaction_hash, unique_transaction_hash); + + assert_claps_info_correct(&storage_key_curr, &session_index_curr, 0); + assert_claps_info_correct(&storage_key_prev, &session_index_prev, 0); + assert_claps_info_correct(&storage_key_next, &session_index_next, 0); + + assert_invalid_signing_address(session_index_curr, network_id, bad_signer_id); + assert_invalid_signing_address(session_index_prev, network_id, bad_signer_id); + assert_invalid_signing_address(session_index_prev, network_id, bad_signer_id); + + assert_transaction_has_bad_signature(session_index_curr, network_id, bad_signer); + assert_transaction_has_bad_signature(session_index_prev, network_id, bad_signer); + assert_transaction_has_bad_signature(session_index_prev, network_id, bad_signer); + + assert_transaction_has_bad_signature(session_index_curr, network_id, authority_prev); + assert_transaction_has_bad_signature(session_index_curr, network_id, authority_next); + + assert_transaction_has_bad_signature(session_index_prev, network_id, authority_curr); + assert_transaction_has_bad_signature(session_index_prev, network_id, authority_next); + + assert_transaction_has_bad_signature(session_index_next, network_id, authority_prev); + assert_transaction_has_bad_signature(session_index_next, network_id, authority_curr); + + assert_claps_info_correct(&storage_key_curr, &session_index_curr, 0); + assert_claps_info_correct(&storage_key_prev, &session_index_prev, 0); + assert_claps_info_correct(&storage_key_next, &session_index_next, 0); + + assert_ok!(do_clap_from_first_authority(session_index_curr, network_id, authority_curr)); + assert_ok!(do_clap_from_first_authority(session_index_prev, network_id, authority_prev)); + assert_ok!(do_clap_from_first_authority(session_index_next, network_id, authority_next)); + + assert_claps_info_correct(&storage_key_curr, &session_index_curr, 1); + assert_claps_info_correct(&storage_key_prev, &session_index_prev, 1); + assert_claps_info_correct(&storage_key_next, &session_index_next, 1); - assert_claps_info_correct(&storage_key_up, &session_index, 0); - assert_claps_info_correct(&storage_key_down, &session_index, 0); - assert_err!(do_clap_from(session_index_up, network_id, 0, false), - Error::::ClapForWrongSession); - assert_err!(do_clap_from(session_index_down, network_id, 0, false), - Error::::ClapForWrongSession); - assert_claps_info_correct(&storage_key_up, &session_index, 0); - assert_claps_info_correct(&storage_key_down, &session_index, 0); } }); } @@ -768,6 +837,24 @@ fn assert_claps_info_correct( assert_eq!(pallet::ClapsInSession::::get(session_index).len(), index); } +fn assert_transaction_has_bad_signature( + session_index: u32, + network_id: u32, + authority: u64, +) { + assert_err!(do_clap_from_first_authority(session_index, network_id, authority), + DispatchError::Other("Transaction has a bad signature")); +} + +fn assert_invalid_signing_address( + session_index: u32, + network_id: u32, + authority_index: u32, +) { + assert_err!(do_clap_from(session_index, network_id, authority_index, false), + DispatchError::Other("Invalid signing address")); +} + fn get_rpc_endpoint() -> Vec { b"https://rpc.endpoint.network.com".to_vec() }