diff --git a/pallets/slow-clap/Cargo.toml b/pallets/slow-clap/Cargo.toml index 37cac17..dad0a04 100644 --- a/pallets/slow-clap/Cargo.toml +++ b/pallets/slow-clap/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ghost-slow-clap" -version = "0.4.15" +version = "0.4.16" description = "Applause protocol for the EVM bridge" license.workspace = true authors.workspace = true diff --git a/pallets/slow-clap/src/lib.rs b/pallets/slow-clap/src/lib.rs index 4e1e634..7aa2180 100644 --- a/pallets/slow-clap/src/lib.rs +++ b/pallets/slow-clap/src/lib.rs @@ -28,7 +28,7 @@ use sp_runtime::{ storage::StorageValueRef, storage_lock::{StorageLock, Time}, }, - traits::{BlockNumberProvider, Convert, Saturating}, + traits::{BlockNumberProvider, Convert, Saturating, UniqueSaturatedInto}, Perbill, RuntimeAppPublic, RuntimeDebug, }; use sp_staking::{ @@ -80,6 +80,7 @@ const LOCK_BLOCK_EXPIRATION: u64 = 20; const COMMITMENT_DELAY_MILLIS: u64 = 600_000; const ONE_HOUR_MILLIS: u64 = 3_600_000; +const CHECK_BLOCK_INTERVAL: u64 = 300; pub type AuthIndex = u32; @@ -347,6 +348,10 @@ pub mod pallet { authority_id: AuthIndex, network_id: NetworkIdOf, }, + BlockCommitmentsCheck { + network_id: NetworkIdOf, + block_number: BlockNumberFor, + }, } #[pallet::error] @@ -470,6 +475,68 @@ pub mod pallet { #[pallet::hooks] impl Hooks> for Pallet { + fn on_initialize(current_block: BlockNumberFor) -> Weight { + // TODO: what about start of the session??? + let mut weight = T::DbWeight::get().reads(1); + + let networks_count = T::NetworkDataHandler::count(); + let current_block_number: u64 = current_block.unique_saturated_into(); + //let cycle_start = (current_block_number / CHECK_BLOCK_INTERVAL) * CHECK_BLOCK_INTERVAL; + let cycle_offset = current_block_number % CHECK_BLOCK_INTERVAL; + + if cycle_offset >= networks_count.into() { + return Default::default(); + } + + let converted_block: usize = current_block_number.unique_saturated_into(); + + if let Some((network_id, network_data)) = + T::NetworkDataHandler::network_for_block(converted_block) + { + weight = weight.saturating_add(T::DbWeight::get().reads_writes(3, 1)); + + let session_index = T::ValidatorSet::session_index(); + let block_commitments = BlockCommitments::::get(&network_id); + let validators = Validators::::get(&session_index); + + if validators.len() == 0 { + return weight; + } + + let disabled_bitmap = DisabledAuthorityIndexes::::get(&session_index) + .unwrap_or(BitMap::new(validators.len() as u32)); + + let max_block_deviation = ONE_HOUR_MILLIS + .saturating_mul(6) // TODO: make it constant or calculate + .saturating_div(network_data.avg_block_speed); + + let offence_bitmap = Self::capture_deviation_in_commitments_and_remove( + &disabled_bitmap, + &block_commitments, + &validators, + max_block_deviation, + ); + + let offence_type = OffenceType::CommitmentOffence; + let extra_weight = Self::try_offend_validators( + &session_index, + &validators, + offence_bitmap, + disabled_bitmap, + offence_type, + ); + + Self::deposit_event(Event::::BlockCommitmentsCheck { + block_number: current_block, + network_id, + }); + + weight = weight.saturating_add(extra_weight); + } + + weight + } + fn offchain_worker(now: BlockNumberFor) { match Self::start_slow_clapping(now) { Ok(_) => { @@ -695,10 +762,6 @@ impl Pallet { } fn try_applause(clap: &Clap, BalanceOf>) -> DispatchResult { - if T::NetworkDataHandler::is_nullification_period() { - return Ok(()); - } - let commission = T::NetworkDataHandler::get(&clap.network_id) .map(|network_data| Perbill::from_parts(network_data.incoming_fee)) .unwrap_or_default() @@ -740,73 +803,35 @@ impl Pallet { Error::::CommitInWrongSession, ); - let block_commitments = BlockCommitments::::try_mutate( - &network_id, - |current_commitments| -> Result, DispatchError> { - let mut new_commitment_details = new_commitment.commitment; + BlockCommitments::::try_mutate(&network_id, |current_commitments| -> DispatchResult { + let mut new_commitment_details = new_commitment.commitment; - let (current_commits, current_last_updated) = current_commitments - .get(&authority_index) - .map(|details| { - ( - details.commits.saturating_add(1), - details.last_updated.saturating_add(COMMITMENT_DELAY_MILLIS), - ) - }) - .unwrap_or((1, 0)); + let (current_commits, current_last_updated) = current_commitments + .get(&authority_index) + .map(|details| { + ( + details.commits.saturating_add(1), + details.last_updated.saturating_add(COMMITMENT_DELAY_MILLIS), + ) + }) + .unwrap_or((1, 0)); - ensure!( - new_commitment_details.last_updated > current_last_updated, - Error::::TimeWentBackwards - ); + ensure!( + new_commitment_details.last_updated > current_last_updated, + Error::::TimeWentBackwards + ); - new_commitment_details.commits = current_commits; - current_commitments.insert(authority_index, new_commitment_details); + new_commitment_details.commits = current_commits; + current_commitments.insert(authority_index, new_commitment_details); - Ok(current_commitments.clone()) - }, - )?; - - let current_commits = block_commitments - .get(&authority_index) - .map(|details| details.commits) - .unwrap_or(1); + Ok(()) + })?; Self::deposit_event(Event::::BlockCommited { network_id, authority_id: authority_index, }); - let validators = Validators::::get(&session_index); - let disabled_bitmap = DisabledAuthorityIndexes::::get(&session_index) - .unwrap_or(BitMap::new(validators.len() as u32)); - - let max_block_deviation = T::NetworkDataHandler::get(&network_id) - .map(|network| { - ONE_HOUR_MILLIS - .saturating_mul(6) - .saturating_div(network.avg_block_speed) - }) - .unwrap_or_default(); - - if current_commits % 3 == 0 && validators.len() > 0 { - let offence_bitmap = Self::capture_deviation_in_commitments_and_remove( - &disabled_bitmap, - &block_commitments, - &validators, - max_block_deviation, - ); - - let offence_type = OffenceType::CommitmentOffence; - Self::try_offend_validators( - &session_index, - &validators, - offence_bitmap, - disabled_bitmap, - offence_type, - ); - } - Ok(()) } @@ -1394,7 +1419,7 @@ impl Pallet { offence_bitmap: BitMap, disabled_bitmap: BitMap, offence_type: OffenceType, - ) { + ) -> Weight { let validator_set_count = validators.len() as u32; let offenders = validators @@ -1417,16 +1442,17 @@ impl Pallet { if not_enough_validators_left && offenders.len() > 0 { Self::deposit_event(Event::::BlackSwan); - return; + return T::DbWeight::get().writes(1); } - if offenders.len() == 0 { + let offenders_len = offenders.len() as u32; + if offenders_len == 0 { let equilibrium_event = match offence_type { OffenceType::CommitmentOffence => Event::::AuthoritiesCommitmentEquilibrium, OffenceType::ThrottlingOffence(_) => Event::::AuthoritiesApplauseEquilibrium, }; Self::deposit_event(equilibrium_event); - return; + return T::DbWeight::get().writes(1); } let offence_event = match offence_type { @@ -1460,6 +1486,8 @@ impl Pallet { if let Err(e) = T::ReportUnresponsiveness::report_offence(reporters, offence) { sp_runtime::print(e); } + + T::WeightInfo::try_offend_validators(offenders_len) } } diff --git a/pallets/slow-clap/src/mock.rs b/pallets/slow-clap/src/mock.rs index 365acc5..f1e379c 100644 --- a/pallets/slow-clap/src/mock.rs +++ b/pallets/slow-clap/src/mock.rs @@ -130,6 +130,7 @@ parameter_types! { parameter_types! { pub static MockAverageSessionLength: Option = None; + pub const MaxNetworks: u32 = 5; } impl ghost_networks::Config for Runtime { @@ -139,6 +140,7 @@ impl ghost_networks::Config for Runtime { type RegisterOrigin = EnsureRoot; type UpdateOrigin = EnsureRoot; type RemoveOrigin = EnsureRoot; + type MaxNetworks = MaxNetworks; type WeightInfo = (); } diff --git a/pallets/slow-clap/src/tests.rs b/pallets/slow-clap/src/tests.rs index b3becd9..1b7a725 100644 --- a/pallets/slow-clap/src/tests.rs +++ b/pallets/slow-clap/src/tests.rs @@ -1,6 +1,9 @@ #![cfg(test)] -use std::time::{SystemTime, UNIX_EPOCH}; +use std::{ + collections::HashMap, + time::{SystemTime, UNIX_EPOCH}, +}; use super::*; use crate::evm_types::Log; @@ -892,7 +895,6 @@ fn should_nullify_commission_on_finalize() { assert_ok!(do_clap_from(session_index, network_id, 1, false)); assert_ok!(do_clap_from(session_index, network_id, 2, false)); assert_eq!(Networks::accumulated_commission(), amount); - assert_eq!(Networks::is_nullification_period(), false); assert_applaused(&session_index, &unique_hash); assert_eq!( @@ -903,63 +905,13 @@ fn should_nullify_commission_on_finalize() { ), (amount, 0u64) ); // precomputed values - assert_eq!(Networks::is_nullification_period(), true); - Networks::on_finalize(System::block_number()); - assert_eq!(Networks::is_nullification_period(), false); assert_eq!(Networks::accumulated_commission(), 0); assert_ok!(do_clap_from(session_index, network_id, 3, false)); assert_eq!(Networks::accumulated_commission(), 0); }); } -#[test] -fn should_avoid_applause_during_nullification_period() { - let zero: u64 = 0u64; - let total_staked = 69_000_000; - let total_issuance = 100_000_000; - - let (network_id, _, unique_hash) = generate_unique_hash(None, None, None, None, None); - let (_, receiver, amount, _) = get_mocked_metadata(); - - new_test_ext().execute_with(|| { - let _ = prepare_evm_network(None, None); - let session_index = advance_session_and_get_index(); - - assert_eq!(Networks::is_nullification_period(), false); - assert_eq!( - BridgedInflationCurve::::era_payout( - total_staked, - total_issuance, - zero - ), - (zero, zero) - ); - assert_eq!(Networks::is_nullification_period(), true); - - assert_ok!(do_clap_from(session_index, network_id, 0, false)); - assert_ok!(do_clap_from(session_index, network_id, 1, false)); - assert_eq!(Balances::total_balance(&receiver), 0); - assert_clapped(&session_index, &unique_hash, 0, true); - assert_clapped(&session_index, &unique_hash, 1, true); - assert_clapped(&session_index, &unique_hash, 2, false); - assert_clapped(&session_index, &unique_hash, 3, false); - - Networks::on_finalize(System::block_number()); - assert_eq!(Networks::is_nullification_period(), false); - - assert_ok!(do_clap_from(session_index, network_id, 2, false)); - assert_ok!(do_clap_from(session_index, network_id, 3, false)); - assert_eq!(Balances::total_balance(&receiver), amount); - - assert_applaused(&session_index, &unique_hash); - assert_clapped(&session_index, &unique_hash, 0, true); - assert_clapped(&session_index, &unique_hash, 1, true); - assert_clapped(&session_index, &unique_hash, 2, true); - assert_clapped(&session_index, &unique_hash, 3, true); - }); -} - #[test] fn should_avoid_session_overlap_on_mended_session_index() { let (network_id, _, unique_hash) = generate_unique_hash(None, None, None, None, None); @@ -1194,6 +1146,7 @@ fn should_disable_on_commitment_inactivity() { } } + SlowClap::on_initialize(CHECK_BLOCK_INTERVAL); System::assert_has_event(RuntimeEvent::SlowClap( crate::Event::SomeAuthoritiesDelayed { delayed: vec![(3, 3)], @@ -1259,6 +1212,7 @@ fn should_disable_on_commitment_block_deviation() { )); } + SlowClap::on_initialize(CHECK_BLOCK_INTERVAL); System::assert_has_event(RuntimeEvent::SlowClap( crate::Event::SomeAuthoritiesDelayed { delayed: vec![(3, 3)], @@ -1372,6 +1326,7 @@ fn should_not_offend_disabled_authorities() { } } + SlowClap::on_initialize(CHECK_BLOCK_INTERVAL); System::assert_has_event(RuntimeEvent::SlowClap( crate::Event::AuthoritiesCommitmentEquilibrium, )); @@ -1417,6 +1372,7 @@ fn should_not_slash_by_applause_if_disabled_by_commitment() { } } + SlowClap::on_initialize(CHECK_BLOCK_INTERVAL); System::assert_has_event(RuntimeEvent::SlowClap( crate::Event::AuthoritiesCommitmentEquilibrium, )); @@ -1501,6 +1457,7 @@ fn should_split_commit_slash_between_active_validators() { } } + SlowClap::on_initialize(CHECK_BLOCK_INTERVAL); System::assert_has_event(RuntimeEvent::SlowClap( crate::Event::SomeAuthoritiesDelayed { delayed: vec![(3, 3)], @@ -1508,7 +1465,7 @@ fn should_split_commit_slash_between_active_validators() { )); let offences = Offences::get(); - assert_eq!(offences.len(), 3); + assert_eq!(offences.len(), 1); for offence in offences { assert_eq!(offence.0, vec![0, 1, 2]); assert_eq!(offence.1.session_index, session_index); @@ -1520,6 +1477,55 @@ fn should_split_commit_slash_between_active_validators() { }); } +#[test] +fn should_check_different_networks_during_on_initialize() { + let times = 69; + let networks_count = 3; + + let mut check_commitment_events = HashMap::new(); + + new_test_ext().execute_with(|| { + let _ = advance_session_and_get_index(); + for network_id in 0..networks_count { + prepare_evm_network(Some(network_id), None); + } + + let check_interval = times * CHECK_BLOCK_INTERVAL; + for check_block in 0..check_interval { + SlowClap::on_initialize(check_block); + } + + let binding = System::events(); + let total_number_of_events = binding + .iter() + .filter(|x| { + x.event == RuntimeEvent::SlowClap(crate::Event::AuthoritiesCommitmentEquilibrium) + }) + .count(); + + binding + .iter() + .map(|record| record.event.clone()) + .for_each(|event| { + if let RuntimeEvent::SlowClap(crate::Event::BlockCommitmentsCheck { + network_id, + .. + }) = event + { + *check_commitment_events.entry(network_id).or_insert(0) += 1; + } + }); + + let expected_events = networks_count * (times as u32); + assert_eq!(total_number_of_events, expected_events as usize); + assert_eq!(check_commitment_events.len() as u32, networks_count); + for network_id in 0..networks_count { + let network_id_count = check_commitment_events.get(&network_id).unwrap(); + assert_eq!(*network_id_count, times); + } + }); +} + #[test] fn should_check_responses_correctly() { new_test_ext().execute_with(|| { @@ -1884,6 +1890,7 @@ fn evm_block_response(state: &mut testing::OffchainState) { headers: vec![ ("Accept".to_string(), "application/json".to_string()), ("Content-Type".to_string(), "application/json".to_string()), + ("User-Agent".to_string(), "curl/8.9.0".to_string()), ], response: Some(b"{\"id\":0,\"jsonrpc\":\"2.0\",\"result\":\"0x1364c81\"}".to_vec()), body: expected_body.clone(), @@ -1897,6 +1904,7 @@ fn evm_block_response(state: &mut testing::OffchainState) { headers: vec![ ("Accept".to_string(), "application/json".to_string()), ("Content-Type".to_string(), "application/json".to_string()), + ("User-Agent".to_string(), "curl/8.9.0".to_string()), ], response: Some(b"{\"id\":0,\"jsonrpc\":\"2.0\",\"result\":\"0x1364c81\"}".to_vec()), body: expected_body, @@ -1951,6 +1959,7 @@ fn evm_logs_response(state: &mut testing::OffchainState) { headers: vec![ ("Accept".to_string(), "application/json".to_string()), ("Content-Type".to_string(), "application/json".to_string()), + ("User-Agent".to_string(), "curl/8.9.0".to_string()), ], response_headers: vec![ ("Accept".to_string(), "application/json".to_string()), @@ -1968,6 +1977,7 @@ fn evm_logs_response(state: &mut testing::OffchainState) { headers: vec![ ("Accept".to_string(), "application/json".to_string()), ("Content-Type".to_string(), "application/json".to_string()), + ("User-Agent".to_string(), "curl/8.9.0".to_string()), ], response_headers: vec![ ("Accept".to_string(), "application/json".to_string()), diff --git a/pallets/slow-clap/src/weights.rs b/pallets/slow-clap/src/weights.rs index ce723ac..8d32173 100644 --- a/pallets/slow-clap/src/weights.rs +++ b/pallets/slow-clap/src/weights.rs @@ -49,6 +49,7 @@ use core::marker::PhantomData; pub trait WeightInfo { fn slow_clap() -> Weight; fn commit_block()-> Weight; + fn try_offend_validators(offenders_len: u32) -> Weight; } impl WeightInfo for () { @@ -86,4 +87,8 @@ impl WeightInfo for () { fn commit_block()-> Weight { Default::default() } + + fn try_offend_validators(offenders_len: u32) -> Weight { + Default::default() + } }