diff --git a/Cargo.lock b/Cargo.lock index 1fe79a2..fff695c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1186,7 +1186,7 @@ dependencies = [ [[package]] name = "casper-runtime" -version = "3.5.36" +version = "3.5.35" dependencies = [ "casper-runtime-constants", "frame-benchmarking", @@ -1204,6 +1204,7 @@ dependencies = [ "ghost-runtime-common", "ghost-slow-clap", "ghost-sudo", + "ghost-traits", "log", "pallet-alliance", "pallet-authority-discovery", @@ -3529,7 +3530,7 @@ dependencies = [ [[package]] name = "ghost-cli" -version = "0.8.2" +version = "0.8.4" dependencies = [ "cfg-if", "clap 4.5.4", @@ -3585,7 +3586,7 @@ dependencies = [ [[package]] name = "ghost-machine-primitives" -version = "0.8.2" +version = "0.8.4" dependencies = [ "lazy_static", "sc-sysinfo", @@ -3594,7 +3595,7 @@ dependencies = [ [[package]] name = "ghost-metrics" -version = "0.8.2" +version = "0.8.4" dependencies = [ "assert_cmd", "bs58 0.5.1", @@ -3649,7 +3650,7 @@ dependencies = [ [[package]] name = "ghost-networks" -version = "0.1.16" +version = "0.1.20" dependencies = [ "frame-benchmarking", "frame-support", @@ -3669,7 +3670,7 @@ dependencies = [ [[package]] name = "ghost-node" -version = "0.8.2" +version = "0.8.4" dependencies = [ "assert_cmd", "color-eyre", @@ -3700,7 +3701,7 @@ dependencies = [ [[package]] name = "ghost-rpc" -version = "0.8.2" +version = "0.8.4" dependencies = [ "ghost-core-primitives", "jsonrpsee", @@ -3752,7 +3753,7 @@ dependencies = [ [[package]] name = "ghost-service" -version = "0.8.2" +version = "0.8.4" dependencies = [ "assert_matches", "async-trait", @@ -3836,12 +3837,13 @@ dependencies = [ [[package]] name = "ghost-slow-clap" -version = "0.3.53" +version = "0.4.5" dependencies = [ "frame-benchmarking", "frame-support", "frame-system", "ghost-networks", + "ghost-traits", "log", "pallet-balances", "pallet-session", @@ -3887,7 +3889,7 @@ dependencies = [ [[package]] name = "ghost-traits" -version = "0.3.23" +version = "0.3.26" dependencies = [ "frame-support", "sp-runtime 31.0.1", diff --git a/Cargo.toml b/Cargo.toml index b5ecdad..9548616 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,7 +17,7 @@ homepage.workspace = true [workspace.package] license = "GPL-3.0-only" authors = ["571nky", "57r37ch", "f4750"] -version = "0.8.2" +version = "0.8.4" edition = "2021" homepage = "https://ghostchain.io" repository = "https://git.ghostchain.io/ghostchain/ghost-node" diff --git a/pallets/networks/Cargo.toml b/pallets/networks/Cargo.toml index 377100c..64f6d90 100644 --- a/pallets/networks/Cargo.toml +++ b/pallets/networks/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ghost-networks" -version = "0.1.16" +version = "0.1.20" license.workspace = true authors.workspace = true edition.workspace = true diff --git a/pallets/networks/src/benchmarking.rs b/pallets/networks/src/benchmarking.rs index c01eed4..a3402ee 100644 --- a/pallets/networks/src/benchmarking.rs +++ b/pallets/networks/src/benchmarking.rs @@ -43,6 +43,7 @@ fn prepare_network( gatekeeper, topic_name, network_type: NetworkType::Evm, + avg_block_speed: 12, finality_delay: 69, rate_limit_delay: 69, block_distance: 69, @@ -239,6 +240,20 @@ benchmarks! { assert_ne!(GhostNetworks::::networks(chain_id.clone()), prev_network); } + update_avg_block_speed { + let avg_block_speed = 420; + let (chain_id, network) = prepare_network::(1, 1, 1); + let authority = T::UpdateOrigin::try_successful_origin() + .map_err(|_| BenchmarkError::Weightless)?; + let prev_network = create_network::(chain_id.clone(), network.clone())?; + }: _(authority, chain_id.clone(), avg_block_speed) + verify { + assert_last_event::(Event::NetworkAvgBlockSpeedUpdated { + chain_id: chain_id.clone(), avg_block_speed, + }.into()); + assert_ne!(GhostNetworks::::networks(chain_id.clone()), prev_network); + } + remove_network { let (chain_id, network) = prepare_network::(1, 1, 1); let authority = T::RemoveOrigin::try_successful_origin() diff --git a/pallets/networks/src/lib.rs b/pallets/networks/src/lib.rs index c6db8e6..344df73 100644 --- a/pallets/networks/src/lib.rs +++ b/pallets/networks/src/lib.rs @@ -38,7 +38,7 @@ mod tests; pub type BalanceOf = <::Currency as Inspect<::AccountId>>::Balance; -#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo)] +#[derive(Encode, Decode, Clone, Copy, PartialEq, Eq, RuntimeDebug, TypeInfo)] pub enum NetworkType { Evm = 0, Utxo = 1, @@ -61,6 +61,7 @@ pub struct NetworkData { pub finality_delay: u64, pub rate_limit_delay: u64, pub block_distance: u64, + pub avg_block_speed: u64, pub incoming_fee: u32, pub outgoing_fee: u32, } @@ -220,6 +221,10 @@ pub mod module { chain_id: T::NetworkId, outgoing_fee: u32, }, + NetworkAvgBlockSpeedUpdated { + chain_id: T::NetworkId, + avg_block_speed: u64, + }, NetworkRemoved { chain_id: T::NetworkId, }, @@ -250,7 +255,7 @@ pub mod module { #[pallet::genesis_config] pub struct GenesisConfig { - pub networks: Vec<(T::NetworkId, Vec)>, + pub networks: Vec<(T::NetworkId, Vec, BalanceOf)>, } impl Default for GenesisConfig { @@ -265,11 +270,12 @@ pub mod module { if !self.networks.is_empty() { self.networks .iter() - .for_each(|(chain_id, network_metadata)| { + .for_each(|(chain_id, network_metadata, gatekeeper_amount)| { let network = NetworkData::decode(&mut &network_metadata[..]) .expect("Error decoding NetworkData"); Pallet::::do_register_network(chain_id.clone(), network) .expect("Error registering network"); + GatekeeperAmount::::insert(chain_id, gatekeeper_amount); }); } } @@ -433,6 +439,17 @@ pub mod module { } #[pallet::call_index(11)] + #[pallet::weight(T::WeightInfo::update_avg_block_speed())] + pub fn update_avg_block_speed( + origin: OriginFor, + chain_id: T::NetworkId, + avg_block_speed: u64, + ) -> DispatchResult { + T::UpdateOrigin::ensure_origin_or_root(origin)?; + Self::do_update_avg_block_speed(chain_id, avg_block_speed) + } + + #[pallet::call_index(12)] #[pallet::weight(T::WeightInfo::remove_network())] pub fn remove_network(origin: OriginFor, chain_id: T::NetworkId) -> DispatchResult { T::RemoveOrigin::ensure_origin_or_root(origin)?; @@ -589,7 +606,7 @@ impl Pallet { Networks::::try_mutate(&chain_id, |maybe_network| -> DispatchResult { ensure!(maybe_network.is_some(), Error::::NetworkDoesNotExist); let net = maybe_network.as_mut().unwrap(); - net.network_type = network_type.clone(); + net.network_type = network_type; *maybe_network = Some(net.clone()); Ok(()) })?; @@ -653,7 +670,7 @@ impl Pallet { Networks::::try_mutate(&chain_id, |maybe_network| -> DispatchResult { ensure!(maybe_network.is_some(), Error::::NetworkDoesNotExist); let net = maybe_network.as_mut().unwrap(); - net.incoming_fee = incoming_fee.clone(); + net.incoming_fee = incoming_fee; *maybe_network = Some(net.clone()); Ok(()) })?; @@ -671,7 +688,7 @@ impl Pallet { Networks::::try_mutate(&chain_id, |maybe_network| -> DispatchResult { ensure!(maybe_network.is_some(), Error::::NetworkDoesNotExist); let net = maybe_network.as_mut().unwrap(); - net.outgoing_fee = outgoing_fee.clone(); + net.outgoing_fee = outgoing_fee; *maybe_network = Some(net.clone()); Ok(()) })?; @@ -681,6 +698,24 @@ impl Pallet { }); Ok(()) } + + pub fn do_update_avg_block_speed( + chain_id: T::NetworkId, + avg_block_speed: u64, + ) -> DispatchResult { + Networks::::try_mutate(&chain_id, |maybe_network| -> DispatchResult { + ensure!(maybe_network.is_some(), Error::::NetworkDoesNotExist); + let net = maybe_network.as_mut().unwrap(); + net.avg_block_speed = avg_block_speed; + *maybe_network = Some(net.clone()); + Ok(()) + })?; + Self::deposit_event(Event::::NetworkAvgBlockSpeedUpdated { + chain_id, + avg_block_speed, + }); + Ok(()) + } } impl NetworkDataBasicHandler for Pallet { diff --git a/pallets/networks/src/math.rs b/pallets/networks/src/math.rs index 97c2bca..794351e 100644 --- a/pallets/networks/src/math.rs +++ b/pallets/networks/src/math.rs @@ -13,7 +13,6 @@ where + sp_std::ops::Shr + sp_std::ops::BitAnd, { - fn zero(&self) -> Balance { 0u32.into() } diff --git a/pallets/networks/src/mock.rs b/pallets/networks/src/mock.rs index 3b18062..d4e106e 100644 --- a/pallets/networks/src/mock.rs +++ b/pallets/networks/src/mock.rs @@ -98,7 +98,7 @@ impl ghost_networks::Config for Test { type RegisterOrigin = EnsureSignedBy; type UpdateOrigin = EnsureSignedBy; type RemoveOrigin = EnsureSignedBy; - type WeightInfo = crate::weights::SubstrateWeight; + type WeightInfo = (); } type Block = frame_system::mocking::MockBlock; diff --git a/pallets/networks/src/tests.rs b/pallets/networks/src/tests.rs index b7a980c..cb52bde 100644 --- a/pallets/networks/src/tests.rs +++ b/pallets/networks/src/tests.rs @@ -19,6 +19,7 @@ fn prepare_network_data() -> (u32, NetworkData) { finality_delay: 69, rate_limit_delay: 69, block_distance: 69, + avg_block_speed: 12_000, network_type: NetworkType::Evm, gatekeeper: b"0x1234567891234567891234567891234567891234".to_vec(), topic_name: b"0x12345678912345678912345678912345678912345678912345678912345678" @@ -1882,7 +1883,7 @@ fn check_bridged_inflation_curve_for_big_commissions() { let (payout, rest) = BridgedInflationCurve::::era_payout( total_staked_gt_ideal, total_issuance + amount, - 0 + 0, ); assert!(payout < commission); assert!(rest < commission); @@ -1890,7 +1891,7 @@ fn check_bridged_inflation_curve_for_big_commissions() { let (payout, rest) = BridgedInflationCurve::::era_payout( total_staked_lt_ideal, total_issuance + amount, - 0 + 0, ); assert!(payout < commission); assert!(rest < commission); diff --git a/pallets/networks/src/weights.rs b/pallets/networks/src/weights.rs index 17ef44d..cfeabe1 100644 --- a/pallets/networks/src/weights.rs +++ b/pallets/networks/src/weights.rs @@ -60,6 +60,7 @@ pub trait WeightInfo { fn update_network_topic_name() -> Weight; fn update_incoming_network_fee() -> Weight; fn update_outgoing_network_fee() -> Weight; + fn update_avg_block_speed() -> Weight; fn remove_network() -> Weight; } @@ -209,6 +210,18 @@ impl WeightInfo for () { } /// Storage: `GhostNetworks::Networks` (r:1 w:1) /// Proof: `GhostNetworks::Networks` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn update_avg_block_speed() -> Weight { + // Proof Size summary in bytes: + // Measured: `302` + // Estimated: `3767` + // Minimum execution time: 49_579_000 picoseconds. + Weight::from_parts(51_126_000, 0) + .saturating_add(Weight::from_parts(0, 3767)) + .saturating_add(RocksDbWeight::get().reads(1)) + .saturating_add(RocksDbWeight::get().writes(1)) + } + /// Storage: `GhostNetworks::Networks` (r:1 w:1) + /// Proof: `GhostNetworks::Networks` (`max_values`: None, `max_size`: None, mode: `Measured`) fn remove_network() -> Weight { // Proof Size summary in bytes: // Measured: `302` diff --git a/pallets/slow-clap/Cargo.toml b/pallets/slow-clap/Cargo.toml index c0bdefc..b0b5118 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.54" +version = "0.4.5" description = "Applause protocol for the EVM bridge" license.workspace = true authors.workspace = true @@ -28,6 +28,7 @@ sp-io = { workspace = true } sp-std = { workspace = true } ghost-networks = { workspace = true } +ghost-traits = { workspace = true } [dev-dependencies] pallet-balances = { workspace = true } diff --git a/pallets/slow-clap/src/benchmarking.rs b/pallets/slow-clap/src/benchmarking.rs index 432ce5f..f664c58 100644 --- a/pallets/slow-clap/src/benchmarking.rs +++ b/pallets/slow-clap/src/benchmarking.rs @@ -3,7 +3,6 @@ use super::*; use frame_benchmarking::v1::*; -use frame_support::traits::fungible::Inspect; use frame_system::RawOrigin; pub fn create_account() -> T::AccountId { @@ -12,73 +11,45 @@ pub fn create_account() -> T::AccountId { .expect("32 bytes always construct an AccountId32") } +pub fn prepare_evm_network(network_id: NetworkIdOf) { + let network_data = NetworkData { + chain_name: "Ethereum".into(), + default_endpoints: vec![b"https://other.endpoint.network.com".to_vec()], + finality_delay: 69, + avg_block_speed: 69, + rate_limit_delay: 69, + block_distance: 69, + network_type: ghost_networks::NetworkType::Evm, + gatekeeper: b"0x4d224452801ACEd8B2F0aebE155379bb5D594381".to_vec(), + topic_name: b"0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef".to_vec(), + incoming_fee: 0, + outgoing_fee: 0, + }; + + let _ = T::NetworkDataHandler::register(network_id, network_data.clone()); +} + benchmarks! { slow_clap { + let network_id = NetworkIdOf::::default(); + prepare_evm_network::(network_id); + let minimum_balance = <::Currency>::minimum_balance(); let receiver = create_account::(); let amount = minimum_balance + minimum_balance; - let network_id = NetworkIdOf::::default(); + let session_index = T::ValidatorSet::session_index(); + let transaction_hash = H256::repeat_byte(1u8); 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::::set(&session_index, bounded_authorities); + let authority_index = 0u32; - let clap = Clap { - session_index: 0, - authority_index: 0, - transaction_hash: H256::repeat_byte(1u8), - block_number: 69, - removed: false, - network_id, - receiver: receiver.clone(), - amount, - }; - - let authority_id = authorities - .get(0usize) - .expect("first authority should exist"); - let encoded_clap = clap.encode(); - let signature = authority_id.sign(&encoded_clap) - .ok_or("couldn't make signature")?; - - }: _(RawOrigin::None, clap, signature) - verify { - assert_eq!(<::Currency>::total_balance(&receiver), amount); - } - - self_applause { - let session_index = T::ValidatorSet::session_index(); - let next_session_index = session_index.saturating_add(1); - let authorities = vec![ - T::AuthorityId::generate_pair(None), - 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::::set(&session_index, bounded_authorities.clone()); - Authorities::::set(&next_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 transaction_hash = H256::repeat_byte(1u8); - - let unique_transaction_hash = >::generate_unique_hash( - &receiver, - &amount, - &network_id, - ); - let storage_key = (session_index, &transaction_hash, &unique_transaction_hash); - let next_storage_key = (next_session_index, &transaction_hash, &unique_transaction_hash); - - >::trigger_nullification_for_benchmark(); let clap = Clap { session_index, - authority_index: 0, + authority_index, transaction_hash, block_number: 69, removed: false, @@ -86,28 +57,61 @@ benchmarks! { receiver: receiver.clone(), amount, }; + let args_hash = Pallet::::generate_unique_hash(&clap); let authority_id = authorities - .get(0usize) + .get(authority_index as usize) .expect("first authority should exist"); - let encoded_clap = clap.encode(); - let signature = authority_id.sign(&encoded_clap).unwrap(); - Pallet::::slow_clap(RawOrigin::None.into(), clap, signature)?; - Pallet::::trigger_nullification_for_benchmark(); + let signature = authority_id.sign(&clap.encode()) + .ok_or("couldn't make signature")?; - assert_eq!(<::Currency>::total_balance(&receiver), Default::default()); - assert_eq!(ApplausesForTransaction::::get(&storage_key), false); + let empty_bitmap = BitMap::new(1); + DisabledAuthorityIndexes::::insert(session_index, empty_bitmap); - frame_system::Pallet::::on_initialize(1u32.into()); - - let mut fake_received_clap = - BoundedBTreeSet::::new(); - assert_eq!(fake_received_clap.try_insert(1).unwrap(), true); - pallet::ReceivedClaps::::insert(&next_storage_key, fake_received_clap); - }: _(RawOrigin::Signed(receiver_clone), network_id, session_index, transaction_hash, receiver_clone.clone(), amount) + assert_eq!(ApplauseDetails::::get(&session_index, &args_hash).is_none(), true); + }: _(RawOrigin::None, clap, signature) verify { + assert_eq!(ApplauseDetails::::get(&session_index, &args_hash).is_some(), true); assert_eq!(<::Currency>::total_balance(&receiver), amount); - assert_eq!(ApplausesForTransaction::::get(&storage_key), true); + } + + commit_block { + let session_index = T::ValidatorSet::session_index(); + let network_id = NetworkIdOf::::default(); + prepare_evm_network::(network_id); + + 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::::set(&session_index, bounded_authorities); + let authority_index = 0u32; + + let block_commitment = BlockCommitment { + session_index, + authority_index, + network_id, + commitment: CommitmentDetails { + last_stored_block: 69, + commits: 420, + last_updated: 1337, + } + }; + + let authority_id = authorities + .get(authority_index as usize) + .expect("first authority should exist"); + let signature = authority_id.sign(&block_commitment.encode()) + .ok_or("couldn't make signature")?; + + }: _(RawOrigin::None, block_commitment, signature) + verify { + let stored_commitment = BlockCommitments::::get(&network_id) + .get(&authority_index) + .cloned() + .unwrap_or_default(); + assert_eq!(stored_commitment.last_stored_block, 69); + assert_eq!(stored_commitment.commits, 1); + assert_eq!(stored_commitment.last_updated, 1337); } impl_benchmark_test_suite!( diff --git a/pallets/slow-clap/src/bitmap.rs b/pallets/slow-clap/src/bitmap.rs new file mode 100644 index 0000000..843df57 --- /dev/null +++ b/pallets/slow-clap/src/bitmap.rs @@ -0,0 +1,117 @@ +use codec::{Decode, Encode}; +use scale_info::TypeInfo; +use sp_core::RuntimeDebug; +use sp_std::collections::btree_map::BTreeMap; + +use crate::AuthIndex; + +pub type BucketId = u32; +pub type Bucket = u64; + +#[derive(Clone, PartialEq, Encode, Decode, RuntimeDebug, TypeInfo)] +pub struct BitMap { + buckets: BTreeMap, + max_length: u32, + max_value: u32, +} + +impl BitMap { + pub fn new(max_value: u32) -> Self { + let mut buckets: BTreeMap = Default::default(); + let max_length = (max_value >> Self::size_of_bucket()) + 1; + + for bucket_id in 0..(max_length) { + buckets.insert(bucket_id, 0); + } + + BitMap { + buckets, + max_length, + max_value, + } + } + + pub fn size_of_bucket() -> u32 { + (core::mem::size_of::() * 8).trailing_zeros() + } + + pub fn iter_buckets(&self) -> impl Iterator { + self.buckets.iter() + } + + pub fn exists(&self, index: &AuthIndex) -> bool { + let (bucket_id, bit_position) = self.bitmap_positions(index); + self.buckets + .get(&bucket_id) + .map(|bucket| (bucket >> bit_position) & 1 == 1) + .unwrap_or_default() + } + + pub fn set(&mut self, index: AuthIndex) { + let (bucket_id, bit_position) = self.bitmap_positions(&index); + if bucket_id < self.max_length { + let bucket = self.buckets.entry(bucket_id).or_insert(0); + *bucket |= 1 << bit_position; + } + } + + pub fn unset(&mut self, index: AuthIndex) { + let (bucket_id, bit_position) = self.bitmap_positions(&index); + if let Some(bucket) = self.buckets.get_mut(&bucket_id) { + *bucket &= !(1 << bit_position); + if *bucket == 0 { + self.buckets.remove(&bucket_id); + } + } + } + + pub fn get_bucket(&self, bucket_id: &BucketId) -> Bucket { + self.buckets.get(bucket_id).copied().unwrap_or_default() + } + + pub fn count_ones(&self) -> u32 { + let zeros: u32 = self + .iter_buckets() + .map(|(_, bucket)| bucket.count_zeros()) + .sum(); + let total_bits = self.total_bits(); + total_bits.saturating_sub(zeros) + } + + pub fn bitor(self, rhs: Self) -> Self { + let (mut base, to_merge) = if self.buckets.len() < rhs.buckets.len() { + (rhs, self) + } else { + (self, rhs) + }; + + for (key, rhs_value) in to_merge.buckets { + base.buckets + .entry(key) + .and_modify(|lhs_value| *lhs_value |= rhs_value) + .or_insert(rhs_value); + } + + base + } + + pub fn max_value(&self) -> u32 { + self.max_value + } + + fn total_bits(&self) -> u32 { + let size_of_bucket = Self::size_of_bucket(); + let bucket_length: u32 = 1 << size_of_bucket; + self.max_length.saturating_mul(bucket_length) + } + + fn bitmap_positions(&self, index: &AuthIndex) -> (u32, u32) { + let size_of_bucket = Self::size_of_bucket(); + let bucket_length = 1 << size_of_bucket; + + let bucket_id = index >> size_of_bucket; + let bit_position = index % bucket_length; + + (bucket_id, bit_position) + } +} diff --git a/pallets/slow-clap/src/evm_types.rs b/pallets/slow-clap/src/evm_types.rs index 28a2104..0115a4a 100644 --- a/pallets/slow-clap/src/evm_types.rs +++ b/pallets/slow-clap/src/evm_types.rs @@ -1,9 +1,14 @@ +use sp_runtime::SaturatedConversion; +use sp_staking::SessionIndex; + use crate::{ deserialisations::{ de_string_to_bytes, de_string_to_h256, de_string_to_u64, de_string_to_u64_pure, de_string_to_vec_of_bytes, }, - Decode, Deserialize, Encode, RuntimeDebug, Vec, H256, + AuthIndex, BalanceOf, BlockCommitment, BlockCommitments, Call, Clap, CommitmentDetails, Config, + Decode, Deserialize, Encode, NetworkIdOf, RuntimeAppPublic, RuntimeDebug, SubmitTransaction, + Vec, COMMITMENT_DELAY_MILLIS, H256, LOG_TARGET, }; const NUMBER_OF_TOPICS: usize = 3; @@ -40,6 +45,214 @@ pub struct Log { pub removed: bool, } +impl EvmResponseType { + fn prepare_block_commitment( + &self, + from_block: u64, + authority_index: AuthIndex, + session_index: SessionIndex, + network_id: NetworkIdOf, + ) -> BlockCommitment> { + let last_updated = sp_io::offchain::timestamp().unix_millis(); + BlockCommitment { + session_index, + authority_index, + network_id, + commitment: CommitmentDetails { + last_stored_block: from_block, + last_updated, + commits: 420, + }, + } + } + + fn prepare_clap( + &self, + authority_index: AuthIndex, + session_index: SessionIndex, + network_id: NetworkIdOf, + log: &Log, + ) -> Clap, BalanceOf> { + Clap { + authority_index, + session_index, + network_id, + removed: log.removed, + receiver: T::AccountId::decode(&mut &log.topics[1][0..32]) + .expect("32 bytes always construct an AccountId32"), + amount: u128::from_be_bytes( + log.topics[2][16..32] + .try_into() + .expect("amount is valid hex; qed"), + ) + .saturated_into::>(), + transaction_hash: log.transaction_hash.clone().expect("tx hash exists; qed"), + block_number: log.block_number.expect("block number exists; qed"), + } + } + + fn iter_claps_from_logs( + &self, + authority_index: AuthIndex, + session_index: SessionIndex, + network_id: NetworkIdOf, + ) -> Vec, BalanceOf>> { + match self { + EvmResponseType::TransactionLogs(evm_logs) => evm_logs + .iter() + .filter_map(move |log| { + log.is_sufficient().then(|| { + self.prepare_clap::(authority_index, session_index, network_id, log) + }) + }) + .collect(), + EvmResponseType::BlockNumber(_) => Vec::new(), + } + } + + fn sign_and_submit_claps( + &self, + authority_index: AuthIndex, + authority_key: T::AuthorityId, + session_index: SessionIndex, + network_id: NetworkIdOf, + ) { + let claps = self.iter_claps_from_logs::(authority_index, session_index, network_id); + let claps_len = claps.len(); + + log::info!( + target: LOG_TARGET, + "👻 Found {:?} claps for network {:?}", + claps_len, + network_id, + ); + + for (clap_index, clap) in claps.iter().enumerate() { + let signature = match authority_key.sign(&clap.encode()) { + Some(signature) => signature, + None => { + log::info!( + target: LOG_TARGET, + "👻 Clap #{} signing failed from authority #{:?} for network {:?}", + clap_index, + authority_index, + network_id, + ); + return; + } + }; + + let call = Call::slow_clap { + clap: clap.clone(), + signature, + }; + + if let Err(e) = + SubmitTransaction::>::submit_unsigned_transaction(call.into()) + { + log::info!( + target: LOG_TARGET, + "👻 Failed to submit clap #{} from authority #{:?} for network {:?}: {:?}", + clap_index, + authority_index, + network_id, + e, + ); + } + } + } + + fn sign_and_submit_block_commitment( + &self, + from_block: u64, + authority_index: AuthIndex, + authority_key: T::AuthorityId, + session_index: SessionIndex, + network_id: NetworkIdOf, + ) { + let block_commitment = self.prepare_block_commitment::( + from_block, + authority_index, + session_index, + network_id, + ); + + let stored_last_updated = BlockCommitments::::get(&network_id) + .get(&authority_index) + .map(|details| details.last_updated) + .unwrap_or_default(); + + let current_last_updated = block_commitment + .commitment + .last_updated + .saturating_sub(COMMITMENT_DELAY_MILLIS); + + if current_last_updated < stored_last_updated { + return; + } + + log::info!( + target: LOG_TARGET, + "👻 New block commitment from authority #{:?} for network {:?}", + authority_index, + network_id, + ); + + let signature = match authority_key.sign(&block_commitment.encode()) { + Some(signature) => signature, + None => { + log::info!( + target: LOG_TARGET, + "👻 Block commitment signing failed from authority #{:?} for network {:?}", + authority_index, + network_id, + ); + return; + } + }; + + let call = Call::commit_block { + block_commitment, + signature, + }; + + if let Err(e) = SubmitTransaction::>::submit_unsigned_transaction(call.into()) { + log::info!( + target: LOG_TARGET, + "👻 Failed to submit block commitment from authority #{:?} for network {:?}: {:?}", + authority_index, + network_id, + e, + ); + } + } + + pub fn sign_and_submit( + &self, + from_block: u64, + authority_index: AuthIndex, + authority_key: T::AuthorityId, + session_index: SessionIndex, + network_id: NetworkIdOf, + ) { + match self { + EvmResponseType::TransactionLogs(_) => self.sign_and_submit_claps::( + authority_index, + authority_key, + session_index, + network_id, + ), + EvmResponseType::BlockNumber(_) => self.sign_and_submit_block_commitment::( + from_block, + authority_index, + authority_key, + session_index, + network_id, + ), + } + } +} + impl Log { pub fn is_sufficient(&self) -> bool { self.transaction_hash.is_some() diff --git a/pallets/slow-clap/src/lib.rs b/pallets/slow-clap/src/lib.rs index 48b61b2..656a80d 100644 --- a/pallets/slow-clap/src/lib.rs +++ b/pallets/slow-clap/src/lib.rs @@ -1,6 +1,8 @@ // Ensure we're `no_std` when compiling for Wasm. #![cfg_attr(not(feature = "std"), no_std)] +use core::usize; + use codec::{Decode, Encode, MaxEncodedLen}; use scale_info::TypeInfo; use serde::{Deserialize, Deserializer}; @@ -8,8 +10,8 @@ use serde::{Deserialize, Deserializer}; use frame_support::{ pallet_prelude::*, traits::{ - tokens::fungible::{Inspect, Mutate}, - DisabledValidators, Get, OneSessionHandler, ValidatorSet, ValidatorSetWithIdentification, + Currency, DisabledValidators, Get, OneSessionHandler, ValidatorSet, + ValidatorSetWithIdentification, }, WeakBoundedVec, }; @@ -17,6 +19,7 @@ use frame_system::{ offchain::{SendTransactionTypes, SubmitTransaction}, pallet_prelude::*, }; + pub use pallet::*; use sp_core::H256; @@ -28,7 +31,7 @@ use sp_runtime::{ HttpError, }, traits::{BlockNumberProvider, Convert, Saturating, TrailingZeroInput}, - Perbill, RuntimeAppPublic, RuntimeDebug, SaturatedConversion, + Perbill, RuntimeAppPublic, RuntimeDebug, }; use sp_staking::{ offence::{Kind, Offence, ReportOffence}, @@ -40,6 +43,7 @@ use ghost_networks::{ NetworkData, NetworkDataBasicHandler, NetworkDataInspectHandler, NetworkDataMutateHandler, NetworkType, }; +use ghost_traits::exposure::ExposureListener; pub mod weights; pub use crate::weights::WeightInfo; @@ -47,9 +51,11 @@ mod benchmarking; mod mock; mod tests; +mod bitmap; mod deserialisations; mod evm_types; +use bitmap::{BitMap, Bucket}; use evm_types::{EvmResponse, EvmResponseType}; pub mod sr25519 { @@ -70,11 +76,45 @@ pub mod sr25519 { const LOG_TARGET: &str = "runtime::ghost-slow-clap"; const DB_PREFIX: &[u8] = b"slow_clap::"; +const MIN_LOCK_GUARD_PERIOD: u64 = 15_000; const FETCH_TIMEOUT_PERIOD: u64 = 3_000; const LOCK_BLOCK_EXPIRATION: u64 = 20; +const COMMITMENT_DELAY_MILLIS: u64 = 600_000; +const ONE_HOUR_MILLIS: u64 = 3_600_000; + pub type AuthIndex = u32; +#[derive( + RuntimeDebug, + Default, + Copy, + Clone, + Eq, + PartialEq, + Ord, + PartialOrd, + Encode, + Decode, + TypeInfo, + MaxEncodedLen, +)] +pub struct CommitmentDetails { + pub last_stored_block: u64, + pub last_updated: u64, + pub commits: u64, +} + +#[derive( + RuntimeDebug, Clone, Eq, PartialEq, Ord, PartialOrd, Encode, Decode, TypeInfo, MaxEncodedLen, +)] +pub struct BlockCommitment { + pub session_index: SessionIndex, + pub authority_index: AuthIndex, + pub network_id: NetworkId, + pub commitment: CommitmentDetails, +} + #[derive( RuntimeDebug, Clone, Eq, PartialEq, Ord, PartialOrd, Encode, Decode, TypeInfo, MaxEncodedLen, )] @@ -89,10 +129,33 @@ pub struct Clap { pub amount: Balance, } +#[derive(Clone, PartialEq, Encode, Decode, RuntimeDebug, TypeInfo)] +pub struct ApplauseDetail { + pub network_id: NetworkId, + pub authorities: BitMap, + pub clapped_amount: Balance, + pub block_number: u64, + pub finalized: bool, +} + +impl ApplauseDetail { + pub fn new(network_id: NetworkId, block_number: u64, max_authorities: usize) -> Self { + ApplauseDetail { + network_id, + block_number, + finalized: false, + clapped_amount: Default::default(), + authorities: BitMap::new(max_authorities as AuthIndex), + } + } +} + #[derive(Default, Clone, Copy, PartialEq, Encode, Decode, RuntimeDebug, TypeInfo)] -pub struct SessionAuthorityInfo { - pub claps: u32, - pub disabled: bool, +pub struct PreparedApplause { + pub network_id: NetworkId, + pub receiver: AccountId, + pub amount: Balance, + pub block_number: u64, } #[cfg_attr(test, derive(PartialEq))] @@ -170,7 +233,7 @@ impl core::fmt::Debug for OffchainErr { pub type NetworkIdOf = <::NetworkDataHandler as NetworkDataBasicHandler>::NetworkId; pub type BalanceOf = - <::Currency as Inspect<::AccountId>>::Balance; + <::Currency as Currency<::AccountId>>::Balance; pub type ValidatorId = <::ValidatorSet as ValidatorSet< ::AccountId, @@ -207,7 +270,7 @@ pub mod pallet { + MaxEncodedLen; type ValidatorSet: ValidatorSetWithIdentification; - type Currency: Inspect + Mutate; + type Currency: Currency; type NetworkDataHandler: NetworkDataBasicHandler + NetworkDataInspectHandler + NetworkDataMutateHandler>; @@ -215,9 +278,10 @@ pub mod pallet { type ReportUnresponsiveness: ReportOffence< Self::AccountId, IdentificationTuple, - ThrottlingOffence>, + SlowClapOffence>, >; type DisabledValidators: DisabledValidators; + type ExposureListener: ExposureListener, Self::AccountId>; #[pallet::constant] type MaxAuthorities: Get; @@ -225,9 +289,6 @@ pub mod pallet { #[pallet::constant] type ApplauseThreshold: Get; - #[pallet::constant] - type OffenceThreshold: Get; - #[pallet::constant] type UnsignedPriority: Get; @@ -244,21 +305,31 @@ pub mod pallet { #[pallet::generate_deposit(pub(super) fn deposit_event)] pub enum Event { BlackSwan, - AuthoritiesEquilibrium, + AuthoritiesCommitmentEquilibrium, + AuthoritiesApplauseEquilibrium, + SomeAuthoritiesDelayed { + delayed: Vec>, + }, SomeAuthoritiesTrottling { throttling: Vec>, }, Clapped { authority_id: AuthIndex, network_id: NetworkIdOf, - transaction_hash: H256, + clap_unique_hash: H256, receiver: T::AccountId, amount: BalanceOf, + removed: bool, }, Applaused { network_id: NetworkIdOf, receiver: T::AccountId, received_amount: BalanceOf, + block_number: u64, + }, + BlockCommited { + authority_id: AuthIndex, + network_id: NetworkIdOf, }, } @@ -266,49 +337,54 @@ pub mod pallet { pub enum Error { NotEnoughClaps, AlreadyClapped, + UnregistedNetwork, UnregisteredClapRemove, TooMuchAuthorities, CouldNotAccumulateCommission, CouldNotAccumulateIncomingImbalance, CouldNotIncreaseGatekeeperAmount, + NonExistentAuthorityIndex, + TimeWentBackwards, + DisabledAuthority, + ExecutedBlockIsHigher, } #[pallet::storage] - #[pallet::getter(fn received_claps)] - pub(super) type ReceivedClaps = StorageNMap< + #[pallet::getter(fn total_exposure)] + pub(super) type TotalExposure = StorageValue<_, BalanceOf, ValueQuery>; + + #[pallet::storage] + #[pallet::getter(fn latest_executed_block)] + pub(super) type LatestExecutedBlock = + StorageMap<_, Twox64Concat, NetworkIdOf, u64, ValueQuery>; + + #[pallet::storage] + #[pallet::getter(fn block_commitments)] + pub(super) type BlockCommitments = StorageMap< _, - ( - NMapKey, - NMapKey, - NMapKey, - ), - BoundedBTreeSet, + Twox64Concat, + NetworkIdOf, + BTreeMap, ValueQuery, >; #[pallet::storage] - #[pallet::getter(fn applauses_for_transaction)] - pub(super) type ApplausesForTransaction = StorageNMap< - _, - ( - NMapKey, - NMapKey, - NMapKey, - ), - bool, - ValueQuery, - >; - - #[pallet::storage] - #[pallet::getter(fn claps_in_session)] - pub(super) type ClapsInSession = StorageMap< + #[pallet::getter(fn applause_details)] + pub(super) type ApplauseDetails = StorageDoubleMap< _, Twox64Concat, SessionIndex, - BTreeMap, - ValueQuery, + Twox64Concat, + H256, + ApplauseDetail, BalanceOf>, + OptionQuery, >; + #[pallet::storage] + #[pallet::getter(fn disabled_authority_indexes)] + pub(super) type DisabledAuthorityIndexes = + StorageMap<_, Twox64Concat, SessionIndex, BitMap, OptionQuery>; + #[pallet::storage] #[pallet::getter(fn authorities)] pub(super) type Authorities = StorageMap< @@ -326,7 +402,7 @@ pub mod pallet { Twox64Concat, SessionIndex, WeakBoundedVec, T::MaxAuthorities>, - OptionQuery, + ValueQuery, >; #[pallet::genesis_config] @@ -361,23 +437,17 @@ pub mod pallet { } #[pallet::call_index(1)] - #[pallet::weight(T::WeightInfo::self_applause())] - pub fn self_applause( + #[pallet::weight((T::WeightInfo::commit_block(), DispatchClass::Normal, Pays::No))] + pub fn commit_block( origin: OriginFor, - network_id: NetworkIdOf, - prev_session_index: SessionIndex, - transaction_hash: H256, - receiver: T::AccountId, - amount: BalanceOf, + block_commitment: BlockCommitment>, + // since signature verification is done in `validate_unsigned` + // we can skip doing it here again. + _signature: ::Signature, ) -> DispatchResult { - let _ = ensure_signed(origin)?; - Self::applause_if_posible( - network_id, - prev_session_index, - transaction_hash, - receiver, - amount, - ) + ensure_none(origin)?; + Self::try_commit_block(&block_commitment)?; + Ok(()) } } @@ -387,7 +457,7 @@ pub mod pallet { if let Err(e) = Self::start_slow_clapping(now) { log::info!( target: LOG_TARGET, - "👏 Skipping slow clap at {:?}: {:?}", + "👻 Skipping slow clap at {:?}: {:?}", now, e, ) @@ -398,39 +468,54 @@ pub mod pallet { #[pallet::validate_unsigned] impl ValidateUnsigned for Pallet { type Call = Call; - fn validate_unsigned(_source: TransactionSource, call: &Self::Call) -> TransactionValidity { - if let Call::slow_clap { clap, signature } = call { - let (session_index, _) = Self::mended_session_index(&clap); - let authorities = Authorities::::get(&session_index); - let authority = match authorities.get(clap.authority_index as usize) { - Some(authority) => authority, - None => return InvalidTransaction::BadSigner.into(), - }; + match call { + Call::commit_block { + block_commitment, + signature, + } => { + let session_index = block_commitment.session_index; + let authority_index = block_commitment.authority_index; - if ClapsInSession::::get(&session_index) - .get(&clap.authority_index) - .map(|info| info.disabled) - .unwrap_or_default() - { - return InvalidTransaction::BadSigner.into(); + match Authorities::::get(&session_index).get(authority_index as usize) { + Some(authority) => { + if !block_commitment + .using_encoded(|encoded| authority.verify(&encoded, signature)) + { + return InvalidTransaction::BadProof.into(); + } + } + None => return InvalidTransaction::BadSigner.into(), + } + + ValidTransaction::with_tag_prefix("SlowClap") + .priority(T::UnsignedPriority::get()) + .and_provides(block_commitment.commitment.encode()) + .longevity(LOCK_BLOCK_EXPIRATION) + .propagate(true) + .build() } + Call::slow_clap { clap, signature } => { + let (session_index, _) = Self::mended_session_index(&clap); - let signature_valid = - clap.using_encoded(|encoded_clap| authority.verify(&encoded_clap, signature)); + match Authorities::::get(&session_index).get(clap.authority_index as usize) { + Some(authority) => { + if !clap.using_encoded(|encoded| authority.verify(&encoded, signature)) + { + return InvalidTransaction::BadProof.into(); + } + } + None => return InvalidTransaction::BadSigner.into(), + } - if !signature_valid { - return InvalidTransaction::BadProof.into(); + ValidTransaction::with_tag_prefix("SlowClap") + .priority(T::UnsignedPriority::get()) + .and_provides(signature) + .longevity(LOCK_BLOCK_EXPIRATION) + .propagate(true) + .build() } - - ValidTransaction::with_tag_prefix("SlowClap") - .priority(T::UnsignedPriority::get()) - .and_provides(signature) - .longevity(LOCK_BLOCK_EXPIRATION) - .propagate(true) - .build() - } else { - InvalidTransaction::Call.into() + _ => InvalidTransaction::Call.into(), } } } @@ -455,14 +540,11 @@ impl Pallet { .unwrap_or(default_value) } - fn generate_unique_hash( - receiver: &T::AccountId, - amount: &BalanceOf, - network_id: &NetworkIdOf, - ) -> H256 { - let mut clap_args_str = receiver.encode(); - clap_args_str.extend(&amount.encode()); - clap_args_str.extend(&network_id.encode()); + fn generate_unique_hash(clap: &Clap, BalanceOf>) -> H256 { + let mut clap_args_str = clap.receiver.encode(); + clap_args_str.extend(&clap.amount.encode()); + clap_args_str.extend(&clap.block_number.encode()); + clap_args_str.extend(&clap.network_id.encode()); H256::from_slice(&sp_io::hashing::keccak_256(&clap_args_str)[..]) } @@ -495,200 +577,206 @@ impl Pallet { clap: &Clap, BalanceOf>, ) -> (SessionIndex, H256) { let prev_session_index = clap.session_index.saturating_sub(1); - let clap_unique_hash = - Self::generate_unique_hash(&clap.receiver, &clap.amount, &clap.network_id); + let clap_unique_hash = Self::generate_unique_hash(&clap); - let received_claps_key = ( - prev_session_index, - &clap.transaction_hash, - &clap_unique_hash, - ); - - let session_index = ReceivedClaps::::get(&received_claps_key) - .is_empty() - .then(|| clap.session_index) - .unwrap_or(prev_session_index); + let session_index = + if ApplauseDetails::::get(&prev_session_index, &clap_unique_hash).is_some() { + prev_session_index + } else { + clap.session_index + }; (session_index, clap_unique_hash) } fn try_slow_clap(clap: &Clap, BalanceOf>) -> DispatchResult { + let network_id = clap.network_id; + ensure!( + T::NetworkDataHandler::get(&network_id).is_some(), + Error::::UnregistedNetwork + ); + let (session_index, clap_unique_hash) = Self::mended_session_index(&clap); - let mut claps_in_session = ClapsInSession::::get(&session_index); + let authorities = Authorities::::get(&session_index); + let authorities_length = authorities.len(); - let disabled_authorities = claps_in_session - .values() - .filter(|info| info.disabled) - .count(); + let is_disabled = DisabledAuthorityIndexes::::get(&session_index) + .map(|bitmap| bitmap.exists(&clap.authority_index)) + .unwrap_or(true); - let active_authorities = Authorities::::get(&session_index) - .len() - .saturating_sub(disabled_authorities); + ensure!(!is_disabled, Error::::DisabledAuthority); - let received_claps_key = (session_index, &clap.transaction_hash, &clap_unique_hash); + let applause_threshold = Perbill::from_parts(T::ApplauseThreshold::get()); + let threshold_amount = applause_threshold.mul_floor(TotalExposure::::get()); - let number_of_received_claps = - ReceivedClaps::::try_mutate(&received_claps_key, |tree_of_claps| { - let number_of_claps = tree_of_claps.len(); - match (tree_of_claps.contains(&clap.authority_index), clap.removed) { - (true, true) => tree_of_claps - .remove(&clap.authority_index) - .then(|| number_of_claps.saturating_sub(1)) - .ok_or(Error::::UnregisteredClapRemove), - (true, false) => Err(Error::::AlreadyClapped), - (false, true) => Err(Error::::UnregisteredClapRemove), - (false, false) => tree_of_claps - .try_insert(clap.authority_index) - .map(|_| number_of_claps.saturating_add(1)) - .map_err(|_| Error::::TooMuchAuthorities), + let maybe_account_id = + T::ExposureListener::get_account_by_index(clap.authority_index as usize); + ensure!( + maybe_account_id.is_some(), + Error::::NonExistentAuthorityIndex + ); + + let account_id = maybe_account_id.unwrap(); + let new_clapped_amount = T::ExposureListener::get_validator_exposure(&account_id); + + let mut applause_details = + match ApplauseDetails::::take(&session_index, &clap_unique_hash) { + Some(applause_details) => applause_details, + None => { + ensure!( + LatestExecutedBlock::::get(&network_id) <= clap.block_number, + Error::::ExecutedBlockIsHigher, + ); + ApplauseDetail::new(network_id, clap.block_number, authorities_length) } - })?; + }; - claps_in_session - .entry(clap.authority_index) - .and_modify(|individual| individual.claps.saturating_inc()) - .or_insert(SessionAuthorityInfo { - claps: 1u32, - disabled: false, - }); + let total_clapped = if clap.removed { + ensure!( + applause_details.authorities.exists(&clap.authority_index), + Error::::UnregisteredClapRemove, + ); + applause_details.authorities.unset(clap.authority_index); + applause_details + .clapped_amount + .saturating_sub(new_clapped_amount) + } else { + ensure!( + !applause_details.authorities.exists(&clap.authority_index), + Error::::AlreadyClapped, + ); + applause_details.authorities.set(clap.authority_index); + applause_details + .clapped_amount + .saturating_add(new_clapped_amount) + }; - ClapsInSession::::insert(&session_index, claps_in_session); + applause_details.clapped_amount = total_clapped; Self::deposit_event(Event::::Clapped { + network_id, authority_id: clap.authority_index, - network_id: clap.network_id, - transaction_hash: clap.transaction_hash, + clap_unique_hash, receiver: clap.receiver.clone(), amount: clap.amount, + removed: clap.removed, }); - let enough_authorities = - Perbill::from_rational(number_of_received_claps as u32, active_authorities as u32) - > Perbill::from_percent(T::ApplauseThreshold::get()); + let is_enough = applause_details + .authorities + .count_ones() + .gt(&(authorities_length as u32 / 2)); - if enough_authorities { - let _ = Self::try_applause(&clap, &received_claps_key).inspect_err(|error_msg| { - log::info!( - target: LOG_TARGET, - "👏 Could not applause because of: {:?}", - error_msg, - ) - }); + if total_clapped > threshold_amount && is_enough && !applause_details.finalized { + applause_details.finalized = true; + if let Err(e) = Self::try_applause(&clap) { + sp_runtime::print(e); + } } + ApplauseDetails::::insert(&session_index, &clap_unique_hash, applause_details); + Ok(()) } - fn try_applause( - clap: &Clap, BalanceOf>, - received_claps_key: &(SessionIndex, &H256, &H256), - ) -> DispatchResult { - ApplausesForTransaction::::try_mutate(received_claps_key, |is_applaused| { - if *is_applaused || T::NetworkDataHandler::is_nullification_period() { - return Ok(()); - } + 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() - .mul_ceil(clap.amount); - let final_amount = clap.amount.saturating_sub(commission); + let commission = T::NetworkDataHandler::get(&clap.network_id) + .map(|network_data| Perbill::from_parts(network_data.incoming_fee)) + .unwrap_or_default() + .mul_ceil(clap.amount); + let final_amount = clap.amount.saturating_sub(commission); - let _ = - T::NetworkDataHandler::increase_gatekeeper_amount(&clap.network_id, &clap.amount) - .map_err(|_| Error::::CouldNotIncreaseGatekeeperAmount)?; - let _ = T::NetworkDataHandler::accumulate_incoming_imbalance(&final_amount) - .map_err(|_| Error::::CouldNotAccumulateIncomingImbalance)?; - let _ = T::NetworkDataHandler::accumulate_commission(&commission) - .map_err(|_| Error::::CouldNotAccumulateCommission)?; + let _ = T::NetworkDataHandler::increase_gatekeeper_amount(&clap.network_id, &clap.amount) + .map_err(|_| Error::::CouldNotIncreaseGatekeeperAmount)?; + let _ = T::NetworkDataHandler::accumulate_incoming_imbalance(&final_amount) + .map_err(|_| Error::::CouldNotAccumulateIncomingImbalance)?; + let _ = T::NetworkDataHandler::accumulate_commission(&commission) + .map_err(|_| Error::::CouldNotAccumulateCommission)?; + let _ = T::Currency::deposit_creating(&clap.receiver, final_amount); - if final_amount > T::Currency::minimum_balance() { - T::Currency::mint_into(&clap.receiver, final_amount)?; - } + LatestExecutedBlock::::set(clap.network_id, clap.block_number); - *is_applaused = true; + Self::deposit_event(Event::::Applaused { + network_id: clap.network_id, + receiver: clap.receiver.clone(), + received_amount: final_amount, + block_number: clap.block_number, + }); - Self::deposit_event(Event::::Applaused { - network_id: clap.network_id, - receiver: clap.receiver.clone(), - received_amount: final_amount, - }); - - Ok(()) - }) + Ok(()) } - fn applause_if_posible( - network_id: NetworkIdOf, - prev_session_index: SessionIndex, - transaction_hash: H256, - receiver: T::AccountId, - amount: BalanceOf, - ) -> DispatchResult { - let curr_session_index = prev_session_index.saturating_add(1); - let clap_unique_hash = Self::generate_unique_hash(&receiver, &amount, &network_id); + fn try_commit_block(new_commitment: &BlockCommitment>) -> DispatchResult { + let authority_index = new_commitment.authority_index; + let network_id = new_commitment.network_id; + ensure!( + T::NetworkDataHandler::get(&network_id).is_some(), + Error::::UnregistedNetwork + ); - let prev_authorities = Authorities::::get(&prev_session_index) - .into_iter() - .enumerate() - .map(|(i, auth)| (auth, i as AuthIndex)) - .collect::>(); - let curr_authorities = Authorities::::get(&curr_session_index); + let current_commits = BlockCommitments::::try_mutate( + &network_id, + |current_commitments| -> Result { + let mut new_commitment_details = new_commitment.commitment; - let prev_received_claps_key = (prev_session_index, &transaction_hash, &clap_unique_hash); - let curr_received_claps_key = (curr_session_index, &transaction_hash, &clap_unique_hash); + let (current_commits, current_last_updated) = current_commitments + .get(&authority_index) + .map(|details| (details.commits + 1, details.last_updated)) + .unwrap_or((1, 0)); - let mut previous_claps = ClapsInSession::::get(&prev_session_index); - let mut total_received_claps = - ReceivedClaps::::get(&prev_received_claps_key).into_inner(); + ensure!( + new_commitment_details.last_updated > current_last_updated, + Error::::TimeWentBackwards + ); - for (auth_index, info) in ClapsInSession::::get(&curr_session_index).iter() { - if !info.disabled { - continue; - } + new_commitment_details.commits = current_commits; + current_commitments.insert(authority_index, new_commitment_details); - if let Some(curr_authority) = curr_authorities.get(*auth_index as usize) { - if let Some(prev_position) = prev_authorities.get(&curr_authority) { - previous_claps - .entry(*prev_position as AuthIndex) - .and_modify(|individual| (*individual).disabled = true) - .or_insert(SessionAuthorityInfo { - claps: 0u32, - disabled: true, - }); - } - } - } + Ok(current_commits) + }, + )?; - for auth_index in ReceivedClaps::::get(&curr_received_claps_key).into_iter() { - if let Some(curr_authority) = curr_authorities.get(auth_index as usize) { - if let Some(prev_position) = prev_authorities.get(&curr_authority) { - let _ = total_received_claps.insert(*prev_position as AuthIndex); - } - } - } - - let disabled_authorities = previous_claps.values().filter(|info| info.disabled).count(); - - let active_authorities = prev_authorities.len().saturating_sub(disabled_authorities); - - let clap = Clap { - authority_index: Default::default(), - block_number: Default::default(), - removed: Default::default(), - session_index: Default::default(), - transaction_hash: Default::default(), + Self::deposit_event(Event::::BlockCommited { network_id, - receiver, - amount, - }; + authority_id: authority_index, + }); - let enough_authorities = - Perbill::from_rational(total_received_claps.len() as u32, active_authorities as u32) - > Perbill::from_percent(T::ApplauseThreshold::get()); + let session_index = T::ValidatorSet::session_index(); + let validators = Validators::::get(&session_index); + let block_commitments = BlockCommitments::::get(&network_id); - ensure!(enough_authorities, Error::::NotEnoughClaps); - Self::try_applause(&clap, &prev_received_claps_key)?; + 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 > 2 && 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(()) } @@ -716,7 +804,7 @@ impl Pallet { let network_lock_key = Self::create_storage_key(b"network-lock-", &network_id_encoded); let block_until = - rt_offchain::Duration::from_millis(rate_limit_delay.max(FETCH_TIMEOUT_PERIOD)); + rt_offchain::Duration::from_millis(rate_limit_delay.max(MIN_LOCK_GUARD_PERIOD)); let mut network_lock = StorageLock::