From 9f9dd20e46f265bc9437bdabe294ae4bca7e5c3b Mon Sep 17 00:00:00 2001 From: Uncle Stinky Date: Thu, 20 Nov 2025 03:06:48 +0300 Subject: [PATCH] make starter script usable for network bootstrap update default chain spec for casper network legacy endowments and gatekeeped amount for networks update weights for the latest runtime make gatekeeped amount part of the genesis correct median; on genesis validators list is empty add latest from_block to logs update chain specs for local testing update casper runtime based on ghost pallets rustfmt slow-clap pallet apply changes from ghost-traits for ExposureListener conversion function to AccountId added rustfmt ghost slow clap pallet rustfmt ghost traits pallet rustfmt ghost networks pallet update casper runtime in accordance with new functionality. not final updated version of slow clap extend networks pallet with avg_block_speed bump traits package version trait to get exposure from external pallets such as staking simple bitmap implementation set ghost emoji as prefix for logging applause based on the external expousre offchain worker restructure and block commitments added Signed-off-by: Doctor K --- Cargo.lock | 22 +- Cargo.toml | 2 +- pallets/networks/Cargo.toml | 2 +- pallets/networks/src/benchmarking.rs | 15 + pallets/networks/src/lib.rs | 47 +- pallets/networks/src/math.rs | 1 - pallets/networks/src/mock.rs | 2 +- pallets/networks/src/tests.rs | 5 +- pallets/networks/src/weights.rs | 13 + pallets/slow-clap/Cargo.toml | 3 +- pallets/slow-clap/src/benchmarking.rs | 142 +- pallets/slow-clap/src/bitmap.rs | 117 ++ pallets/slow-clap/src/evm_types.rs | 215 ++- pallets/slow-clap/src/lib.rs | 1149 ++++++----- pallets/slow-clap/src/mock.rs | 81 +- pallets/slow-clap/src/tests.rs | 1711 +++++++++-------- pallets/slow-clap/src/weights.rs | 95 +- pallets/traits/Cargo.toml | 2 +- pallets/traits/src/exposure.rs | 7 + pallets/traits/src/lib.rs | 1 + runtime/casper/Cargo.toml | 3 +- runtime/casper/src/impls.rs | 32 + runtime/casper/src/lib.rs | 15 +- .../weights/frame_benchmarking_baseline.rs | 32 +- .../frame_election_provider_support.rs | 28 +- runtime/casper/src/weights/frame_system.rs | 64 +- runtime/casper/src/weights/ghost_claims.rs | 8 +- runtime/casper/src/weights/ghost_networks.rs | 148 +- runtime/casper/src/weights/ghost_slow_clap.rs | 86 +- runtime/casper/src/weights/ghost_sudo.rs | 20 +- runtime/casper/src/weights/pallet_alliance.rs | 182 +- runtime/casper/src/weights/pallet_babe.rs | 12 +- .../casper/src/weights/pallet_bags_list.rs | 16 +- runtime/casper/src/weights/pallet_balances.rs | 60 +- .../casper/src/weights/pallet_collective.rs | 120 +- .../src/weights/pallet_core_fellowship.rs | 62 +- .../pallet_election_provider_multi_phase.rs | 72 +- runtime/casper/src/weights/pallet_grandpa.rs | 16 +- runtime/casper/src/weights/pallet_identity.rs | 168 +- runtime/casper/src/weights/pallet_indices.rs | 26 +- runtime/casper/src/weights/pallet_multisig.rs | 80 +- .../src/weights/pallet_nomination_pools.rs | 124 +- runtime/casper/src/weights/pallet_preimage.rs | 72 +- runtime/casper/src/weights/pallet_proxy.rs | 100 +- .../src/weights/pallet_ranked_collective.rs | 50 +- .../casper/src/weights/pallet_referenda.rs | 124 +- runtime/casper/src/weights/pallet_salary.rs | 40 +- .../casper/src/weights/pallet_scheduler.rs | 107 +- runtime/casper/src/weights/pallet_session.rs | 24 +- runtime/casper/src/weights/pallet_staking.rs | 232 +-- .../casper/src/weights/pallet_timestamp.rs | 16 +- runtime/casper/src/weights/pallet_utility.rs | 36 +- runtime/casper/src/weights/pallet_vesting.rs | 114 +- .../casper/src/weights/pallet_whitelist.rs | 28 +- scripts/starter.sh | 9 +- service/chain-specs/casper.json | 222 ++- service/src/chain_spec.rs | 11 +- service/src/legacy.rs | 187 ++ service/src/lib.rs | 1 + 59 files changed, 3662 insertions(+), 2717 deletions(-) create mode 100644 pallets/slow-clap/src/bitmap.rs create mode 100644 pallets/traits/src/exposure.rs create mode 100644 service/src/legacy.rs 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::