diff --git a/Cargo.lock b/Cargo.lock index fc9820446..2a9e669c4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3405,6 +3405,7 @@ dependencies = [ "pallet-external-validator-slashes", "pallet-external-validators", "pallet-external-validators-rewards", + "pallet-external-validators-rewards-runtime-api", "pallet-grandpa", "pallet-identity", "pallet-inflation-rewards", @@ -9605,6 +9606,7 @@ dependencies = [ "frame-benchmarking", "frame-support", "frame-system", + "log", "pallet-balances", "pallet-session", "pallet-timestamp", @@ -9612,14 +9614,27 @@ dependencies = [ "polkadot-primitives", "polkadot-runtime-parachains", "scale-info", + "snowbridge-core", + "snowbridge-outbound-queue-merkle-tree", "sp-core", "sp-io", "sp-runtime", "sp-staking", "sp-std", + "tp-bridge", "tp-traits", ] +[[package]] +name = "pallet-external-validators-rewards-runtime-api" +version = "0.1.0" +dependencies = [ + "parity-scale-codec", + "snowbridge-outbound-queue-merkle-tree", + "sp-api", + "sp-core", +] + [[package]] name = "pallet-fast-unstake" version = "37.0.0" diff --git a/Cargo.toml b/Cargo.toml index b36c2ea95..c62dc5ee4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -63,6 +63,7 @@ pallet-data-preservers-runtime-api = { path = "pallets/data-preservers/runtime-a pallet-external-validator-slashes = { path = "pallets/external-validator-slashes", default-features = false } pallet-external-validators = { path = "pallets/external-validators", default-features = false } pallet-external-validators-rewards = { path = "pallets/external-validators-rewards", default-features = false } +pallet-external-validators-rewards-runtime-api = { path = "pallets/external-validators-rewards/runtime-api", default-features = false } pallet-inflation-rewards = { path = "pallets/inflation-rewards", default-features = false } pallet-initializer = { path = "pallets/initializer", default-features = false } pallet-invulnerables = { path = "pallets/invulnerables", default-features = false } @@ -260,9 +261,11 @@ xcm-runtime-apis = { git = "https://github.com/moondance-labs/polkadot-sdk", bra # Bridges (wasm) alloy-sol-types = { version = "0.4.2", default-features = false } +bridge-hub-common = { git = "https://github.com/moondance-labs/polkadot-sdk", branch = "tanssi-polkadot-stable2407", default-features = false } milagro-bls = { package = "snowbridge-milagro-bls", version = "1.5.4", default-features = false } snowbridge-beacon-primitives = { git = "https://github.com/moondance-labs/polkadot-sdk", branch = "tanssi-polkadot-stable2409", default-features = false } snowbridge-core = { git = "https://github.com/moondance-labs/polkadot-sdk", branch = "tanssi-polkadot-stable2409", default-features = false } +snowbridge-outbound-queue-merkle-tree = { git = "https://github.com/moondance-labs/polkadot-sdk", branch = "tanssi-polkadot-stable2409", default-features = false } snowbridge-pallet-ethereum-client = { git = "https://github.com/moondance-labs/polkadot-sdk", branch = "tanssi-polkadot-stable2409", default-features = false } snowbridge-pallet-inbound-queue = { git = "https://github.com/moondance-labs/polkadot-sdk", branch = "tanssi-polkadot-stable2409", default-features = false } snowbridge-pallet-inbound-queue-fixtures = { git = "https://github.com/moondance-labs/polkadot-sdk", branch = "tanssi-polkadot-stable2409", default-features = false } diff --git a/pallets/external-validators-rewards/Cargo.toml b/pallets/external-validators-rewards/Cargo.toml index 579f871b0..43f6da0ce 100644 --- a/pallets/external-validators-rewards/Cargo.toml +++ b/pallets/external-validators-rewards/Cargo.toml @@ -13,14 +13,17 @@ targets = [ "x86_64-unknown-linux-gnu" ] workspace = true [dependencies] +log = { workspace = true } parity-scale-codec = { workspace = true } scale-info = { workspace = true, features = [ "derive" ] } frame-support = { workspace = true } frame-system = { workspace = true } +sp-core = { workspace = true } sp-runtime = { workspace = true } sp-staking = { workspace = true } sp-std = { workspace = true } +tp-bridge = { workspace = true } tp-traits = { workspace = true } frame-benchmarking = { workspace = true } @@ -29,11 +32,13 @@ pallet-balances = { workspace = true, optional = true } pallet-session = { workspace = true, features = [ "historical" ] } runtime-parachains = { workspace = true } +snowbridge-core = { workspace = true } +snowbridge-outbound-queue-merkle-tree = { workspace = true } + polkadot-primitives = { workspace = true } [dev-dependencies] pallet-timestamp = { workspace = true } -sp-core = { workspace = true } sp-io = { workspace = true } [features] @@ -42,6 +47,7 @@ std = [ "frame-benchmarking/std", "frame-support/std", "frame-system/std", + "log/std", "pallet-balances/std", "pallet-session/std", "pallet-timestamp/std", @@ -49,11 +55,14 @@ std = [ "polkadot-primitives/std", "runtime-parachains/std", "scale-info/std", + "snowbridge-core/std", + "snowbridge-outbound-queue-merkle-tree/std", "sp-core/std", "sp-io/std", "sp-runtime/std", "sp-staking/std", "sp-std/std", + "tp-bridge/std", "tp-traits/std", ] runtime-benchmarks = [ @@ -64,8 +73,10 @@ runtime-benchmarks = [ "pallet-timestamp/runtime-benchmarks", "polkadot-primitives/runtime-benchmarks", "runtime-parachains/runtime-benchmarks", + "snowbridge-core/runtime-benchmarks", "sp-runtime/runtime-benchmarks", "sp-staking/runtime-benchmarks", + "tp-bridge/runtime-benchmarks", "tp-traits/runtime-benchmarks", ] diff --git a/pallets/external-validators-rewards/runtime-api/Cargo.toml b/pallets/external-validators-rewards/runtime-api/Cargo.toml new file mode 100644 index 000000000..4fe0c85a8 --- /dev/null +++ b/pallets/external-validators-rewards/runtime-api/Cargo.toml @@ -0,0 +1,28 @@ +[package] +name = "pallet-external-validators-rewards-runtime-api" +authors = { workspace = true } +description = "Runtime API definition of pallet-external-validators-rewards" +edition = "2021" +license = "GPL-3.0-only" +version = "0.1.0" + +[package.metadata.docs.rs] +targets = [ "x86_64-unknown-linux-gnu" ] + +[lints] +workspace = true + +[dependencies] +parity-scale-codec = { workspace = true } +snowbridge-outbound-queue-merkle-tree = { workspace = true } +sp-api = { workspace = true } +sp-core = { workspace = true } + +[features] +default = [ "std" ] +std = [ + "parity-scale-codec/std", + "snowbridge-outbound-queue-merkle-tree/std", + "sp-api/std", + "sp-core/std", +] diff --git a/pallets/external-validators-rewards/runtime-api/src/lib.rs b/pallets/external-validators-rewards/runtime-api/src/lib.rs new file mode 100644 index 000000000..3d8c6e1b6 --- /dev/null +++ b/pallets/external-validators-rewards/runtime-api/src/lib.rs @@ -0,0 +1,32 @@ +// Copyright (C) Moondance Labs Ltd. +// This file is part of Tanssi. + +// Tanssi is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Tanssi is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Tanssi. If not, see + +//! Runtime API for External Validators Rewards pallet + +#![cfg_attr(not(feature = "std"), no_std)] + +use snowbridge_outbound_queue_merkle_tree::MerkleProof; + +sp_api::decl_runtime_apis! { + pub trait ExternalValidatorsRewardsApi + where + AccountId: parity_scale_codec::Codec, + EraIndex: parity_scale_codec::Codec, + { + fn generate_rewards_merkle_proof(account_id: AccountId, era_index: EraIndex) -> Option; + fn verify_rewards_merkle_proof(merkle_proof: MerkleProof) -> bool; + } +} diff --git a/pallets/external-validators-rewards/src/benchmarking.rs b/pallets/external-validators-rewards/src/benchmarking.rs new file mode 100644 index 000000000..4c068b026 --- /dev/null +++ b/pallets/external-validators-rewards/src/benchmarking.rs @@ -0,0 +1,81 @@ +// Copyright (C) Moondance Labs Ltd. +// This file is part of Tanssi. + +// Tanssi is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Tanssi is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Tanssi. If not, see + +//! Benchmarking setup for pallet_external_validators_rewards + +use super::*; + +#[allow(unused)] +use crate::Pallet as ExternalValidatorsRewards; +use { + frame_benchmarking::{account, v2::*, BenchmarkError}, + frame_support::traits::{Currency, Get}, + sp_std::prelude::*, + tp_traits::OnEraEnd, +}; + +const SEED: u32 = 0; + +fn create_funded_user( + string: &'static str, + n: u32, + balance_factor: u32, +) -> T::AccountId { + let user = account(string, n, SEED); + let balance = as Currency>::minimum_balance() + * balance_factor.into(); + let _ = as Currency>::make_free_balance_be( + &user, balance, + ); + user +} + +#[allow(clippy::multiple_bound_locations)] +#[benchmarks(where T: pallet_balances::Config)] +mod benchmarks { + use super::*; + + // worst case for the end of an era. + #[benchmark] + fn on_era_end() -> Result<(), BenchmarkError> { + frame_system::Pallet::::set_block_number(0u32.into()); + + let mut era_reward_points = EraRewardPoints::default(); + era_reward_points.total = T::BackingPoints::get() * 1000; + + for i in 0..1000 { + let account_id = create_funded_user::("candidate", i, 100); + era_reward_points + .individual + .insert(account_id, T::BackingPoints::get()); + } + + >::insert(1u32, era_reward_points); + + #[block] + { + as OnEraEnd>::on_era_end(1u32); + } + + Ok(()) + } + + impl_benchmark_test_suite!( + ExternalValidatorsRewards, + crate::mock::new_test_ext(), + crate::mock::Test, + ); +} diff --git a/pallets/external-validators-rewards/src/lib.rs b/pallets/external-validators-rewards/src/lib.rs index 4888c206a..7f2162eff 100644 --- a/pallets/external-validators-rewards/src/lib.rs +++ b/pallets/external-validators-rewards/src/lib.rs @@ -25,20 +25,43 @@ mod mock; #[cfg(test)] mod tests; +#[cfg(feature = "runtime-benchmarks")] +mod benchmarking; + +pub mod weights; + pub use pallet::*; use { frame_support::traits::{Defensive, Get, ValidatorSet}, + parity_scale_codec::Encode, polkadot_primitives::ValidatorIndex, runtime_parachains::session_info, + snowbridge_core::ChannelId, + snowbridge_outbound_queue_merkle_tree::{merkle_proof, merkle_root, verify_proof, MerkleProof}, + sp_core::H256, + sp_runtime::traits::Hash, sp_staking::SessionIndex, sp_std::collections::btree_set::BTreeSet, + sp_std::vec, + sp_std::vec::Vec, + tp_bridge::{Command, DeliverMessage, Message, ValidateMessage}, }; +/// Utils needed to generate/verify merkle roots/proofs inside this pallet. +#[derive(Debug, PartialEq, Eq, Clone)] +pub struct EraRewardsUtils { + pub rewards_merkle_root: H256, + pub leaves: Vec, + pub leaf_index: Option, + pub total_points: u128, +} + #[frame_support::pallet] pub mod pallet { + pub use crate::weights::WeightInfo; use { - frame_support::pallet_prelude::*, sp_std::collections::btree_map::BTreeMap, + super::*, frame_support::pallet_prelude::*, sp_std::collections::btree_map::BTreeMap, tp_traits::EraIndexProvider, }; @@ -64,6 +87,26 @@ pub mod pallet { /// The amount of era points given by dispute voting on a candidate. #[pallet::constant] type DisputeStatementPoints: Get; + + /// Provider to know how may tokens were inflated (added) in a specific era. + type EraInflationProvider: Get; + + /// Provider to retrieve the current block timestamp. + type TimestampProvider: Get; + + /// Hashing tool used to generate/verify merkle roots and proofs. + type Hashing: Hash; + + /// Validate a message that will be sent to Ethereum. + type ValidateMessage: ValidateMessage; + + /// Send a message to Ethereum. Needs to be validated first. + type OutboundQueue: DeliverMessage< + Ticket = <::ValidateMessage as ValidateMessage>::Ticket, + >; + + /// The weight information of this pallet. + type WeightInfo: WeightInfo; } #[pallet::pallet] @@ -104,6 +147,76 @@ pub mod pallet { } }) } + + // Helper function used to generate the following utils: + // - rewards_merkle_root: merkle root corresponding [(validatorId, rewardPoints)] + // for the era_index specified. + // - leaves: that were used to generate the previous merkle root. + // - leaf_index: index of the validatorId's leaf in the previous leaves array (if any). + // - total_points: number of total points of the era_index specified. + pub fn generate_era_rewards_utils( + era_index: EraIndex, + maybe_account_id_check: Option, + ) -> Option { + let era_rewards = RewardPointsForEra::::get(&era_index); + let total_points: u128 = era_rewards.total as u128; + let mut leaves = Vec::with_capacity(era_rewards.individual.len()); + let mut leaf_index = None; + + if let Some(account) = &maybe_account_id_check { + if !era_rewards.individual.contains_key(account) { + log::error!( + target: "ext_validators_rewards", + "AccountId {:?} not found for era {:?}!", + account, + era_index + ); + return None; + } + } + + for (index, (account_id, reward_points)) in era_rewards.individual.iter().enumerate() { + let encoded = (account_id, reward_points).encode(); + let hashed = ::Hashing::hash(&encoded); + leaves.push(hashed); + + if let Some(ref check_account_id) = maybe_account_id_check { + if account_id == check_account_id { + leaf_index = Some(index as u64); + } + } + } + + let rewards_merkle_root = + merkle_root::<::Hashing, _>(leaves.iter().cloned()); + + Some(EraRewardsUtils { + rewards_merkle_root, + leaves, + leaf_index, + total_points, + }) + } + + pub fn generate_rewards_merkle_proof( + account_id: T::AccountId, + era_index: EraIndex, + ) -> Option { + let utils = Self::generate_era_rewards_utils(era_index, Some(account_id))?; + utils.leaf_index.map(|index| { + merkle_proof::<::Hashing, _>(utils.leaves.into_iter(), index) + }) + } + + pub fn verify_rewards_merkle_proof(merkle_proof: MerkleProof) -> bool { + verify_proof::<::Hashing, _, _>( + &merkle_proof.root, + merkle_proof.proof, + merkle_proof.number_of_leaves, + merkle_proof.leaf_index, + merkle_proof.leaf, + ) + } } impl tp_traits::OnEraStart for Pallet { @@ -115,6 +228,53 @@ pub mod pallet { RewardPointsForEra::::remove(era_index_to_delete); } } + + impl tp_traits::OnEraEnd for Pallet { + fn on_era_end(era_index: EraIndex) { + if let Some(utils) = Self::generate_era_rewards_utils(era_index, None) { + let command = Command::ReportRewards { + timestamp: T::TimestampProvider::get(), + era_index, + total_points: utils.total_points, + tokens_inflated: T::EraInflationProvider::get(), + rewards_merkle_root: utils.rewards_merkle_root, + }; + + let channel_id: ChannelId = snowbridge_core::PRIMARY_GOVERNANCE_CHANNEL; + + let outbound_message = Message { + id: None, + channel_id, + command, + }; + + // Validate and deliver the message + match T::ValidateMessage::validate(&outbound_message) { + Ok((ticket, _fee)) => { + if let Err(err) = T::OutboundQueue::deliver(ticket) { + log::error!(target: "ext_validators_rewards", "OutboundQueue delivery of message failed. {err:?}"); + } + } + Err(err) => { + log::error!(target: "ext_validators_rewards", "OutboundQueue validation of message failed. {err:?}"); + } + } + + frame_system::Pallet::::register_extra_weight_unchecked( + T::WeightInfo::on_era_end(), + DispatchClass::Mandatory, + ); + } else { + // Unreachable, this should never happen as we are sending + // None as the second param in Self::generate_era_rewards_utils. + log::error!( + target: "ext_validators_rewards", + "Outbound message not sent for era {:?}!", + era_index + ); + } + } + } } /// Rewards validators for participating in parachains with era points in pallet-staking. diff --git a/pallets/external-validators-rewards/src/mock.rs b/pallets/external-validators-rewards/src/mock.rs index a80eca4fb..4c366f5fb 100644 --- a/pallets/external-validators-rewards/src/mock.rs +++ b/pallets/external-validators-rewards/src/mock.rs @@ -20,9 +20,10 @@ use { traits::{ConstU32, ConstU64}, }, pallet_balances::AccountData, + snowbridge_core::outbound::{SendError, SendMessageFeeProvider}, sp_core::H256, sp_runtime::{ - traits::{BlakeTwo256, IdentityLookup}, + traits::{BlakeTwo256, Get, IdentityLookup, Keccak256}, BuildStorage, }, }; @@ -109,11 +110,41 @@ impl pallet_timestamp::Config for Test { impl mock_data::Config for Test {} +pub struct MockOkOutboundQueue; +impl tp_bridge::DeliverMessage for MockOkOutboundQueue { + type Ticket = (); + + fn deliver(_: Self::Ticket) -> Result { + Ok(H256::zero()) + } +} + +impl SendMessageFeeProvider for MockOkOutboundQueue { + type Balance = u128; + + fn local_fee() -> Self::Balance { + 1 + } +} + +pub struct TimestampProvider; +impl Get for TimestampProvider { + fn get() -> u64 { + Timestamp::get() + } +} + impl pallet_external_validators_rewards::Config for Test { type EraIndexProvider = Mock; type HistoryDepth = ConstU32<10>; type BackingPoints = ConstU32<20>; type DisputeStatementPoints = ConstU32<20>; + type EraInflationProvider = (); + type TimestampProvider = TimestampProvider; + type Hashing = Keccak256; + type ValidateMessage = (); + type OutboundQueue = MockOkOutboundQueue; + type WeightInfo = (); } // Pallet to provide some mock data, used to test diff --git a/pallets/external-validators-rewards/src/weights.rs b/pallets/external-validators-rewards/src/weights.rs new file mode 100644 index 000000000..766adfcfb --- /dev/null +++ b/pallets/external-validators-rewards/src/weights.rs @@ -0,0 +1,116 @@ +// Copyright (C) Moondance Labs Ltd. +// This file is part of Tanssi. + +// Tanssi is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Tanssi is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Tanssi. If not, see + + +//! Autogenerated weights for pallet_external_validators_rewards +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 43.0.0 +//! DATE: 2024-12-05, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `COV0768`, CPU: `AMD Ryzen 9 7950X 16-Core Processor` +//! EXECUTION: , WASM-EXECUTION: Compiled, CHAIN: Some("dancelight-dev"), DB CACHE: 1024 + +// Executed Command: +// target/release/tanssi-relay +// benchmark +// pallet +// --execution=wasm +// --wasm-execution=compiled +// --pallet +// pallet_external_validators_rewards +// --extrinsic +// * +// --chain=dancelight-dev +// --steps +// 50 +// --repeat +// 20 +// --template=benchmarking/frame-weight-pallet-template.hbs +// --json-file +// raw.json +// --output +// pallets/external-validators-rewards/src/pallet_external_validators_rewards.rs + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] + +use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; +use sp_std::marker::PhantomData; + +/// Weight functions needed for pallet_external_validators_rewards. +pub trait WeightInfo { + fn on_era_end() -> Weight; +} + +/// Weights for pallet_external_validators_rewards using the Substrate node and recommended hardware. +pub struct SubstrateWeight(PhantomData); +impl WeightInfo for SubstrateWeight { + /// Storage: `ExternalValidatorsRewards::RewardPointsForEra` (r:1 w:0) + /// Proof: `ExternalValidatorsRewards::RewardPointsForEra` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `Timestamp::Now` (r:1 w:0) + /// Proof: `Timestamp::Now` (`max_values`: Some(1), `max_size`: Some(8), added: 503, mode: `MaxEncodedLen`) + /// Storage: `EthereumSystem::Channels` (r:1 w:0) + /// Proof: `EthereumSystem::Channels` (`max_values`: None, `max_size`: Some(76), added: 2551, mode: `MaxEncodedLen`) + /// Storage: `MessageQueue::BookStateFor` (r:1 w:1) + /// Proof: `MessageQueue::BookStateFor` (`max_values`: None, `max_size`: Some(136), added: 2611, mode: `MaxEncodedLen`) + /// Storage: `MessageQueue::ServiceHead` (r:1 w:1) + /// Proof: `MessageQueue::ServiceHead` (`max_values`: Some(1), `max_size`: Some(33), added: 528, mode: `MaxEncodedLen`) + /// Storage: UNKNOWN KEY `0x3a72656c61795f64697370617463685f71756575655f72656d61696e696e675f` (r:0 w:1) + /// Proof: UNKNOWN KEY `0x3a72656c61795f64697370617463685f71756575655f72656d61696e696e675f` (r:0 w:1) + /// Storage: `MessageQueue::Pages` (r:0 w:1) + /// Proof: `MessageQueue::Pages` (`max_values`: None, `max_size`: Some(32845), added: 35320, mode: `MaxEncodedLen`) + /// Storage: UNKNOWN KEY `0xf5207f03cfdce586301014700e2c2593fad157e461d71fd4c1f936839a5f1f3e` (r:0 w:1) + /// Proof: UNKNOWN KEY `0xf5207f03cfdce586301014700e2c2593fad157e461d71fd4c1f936839a5f1f3e` (r:0 w:1) + fn on_era_end() -> Weight { + // Proof Size summary in bytes: + // Measured: `36522` + // Estimated: `39987` + // Minimum execution time: 1_042_933_000 picoseconds. + Weight::from_parts(1_136_401_000, 39987) + .saturating_add(T::DbWeight::get().reads(5_u64)) + .saturating_add(T::DbWeight::get().writes(5_u64)) + } +} + +// For backwards compatibility and tests +impl WeightInfo for () { + /// Storage: `ExternalValidatorsRewards::RewardPointsForEra` (r:1 w:0) + /// Proof: `ExternalValidatorsRewards::RewardPointsForEra` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `Timestamp::Now` (r:1 w:0) + /// Proof: `Timestamp::Now` (`max_values`: Some(1), `max_size`: Some(8), added: 503, mode: `MaxEncodedLen`) + /// Storage: `EthereumSystem::Channels` (r:1 w:0) + /// Proof: `EthereumSystem::Channels` (`max_values`: None, `max_size`: Some(76), added: 2551, mode: `MaxEncodedLen`) + /// Storage: `MessageQueue::BookStateFor` (r:1 w:1) + /// Proof: `MessageQueue::BookStateFor` (`max_values`: None, `max_size`: Some(136), added: 2611, mode: `MaxEncodedLen`) + /// Storage: `MessageQueue::ServiceHead` (r:1 w:1) + /// Proof: `MessageQueue::ServiceHead` (`max_values`: Some(1), `max_size`: Some(33), added: 528, mode: `MaxEncodedLen`) + /// Storage: UNKNOWN KEY `0x3a72656c61795f64697370617463685f71756575655f72656d61696e696e675f` (r:0 w:1) + /// Proof: UNKNOWN KEY `0x3a72656c61795f64697370617463685f71756575655f72656d61696e696e675f` (r:0 w:1) + /// Storage: `MessageQueue::Pages` (r:0 w:1) + /// Proof: `MessageQueue::Pages` (`max_values`: None, `max_size`: Some(32845), added: 35320, mode: `MaxEncodedLen`) + /// Storage: UNKNOWN KEY `0xf5207f03cfdce586301014700e2c2593fad157e461d71fd4c1f936839a5f1f3e` (r:0 w:1) + /// Proof: UNKNOWN KEY `0xf5207f03cfdce586301014700e2c2593fad157e461d71fd4c1f936839a5f1f3e` (r:0 w:1) + fn on_era_end() -> Weight { + // Proof Size summary in bytes: + // Measured: `36522` + // Estimated: `39987` + // Minimum execution time: 1_042_933_000 picoseconds. + Weight::from_parts(1_136_401_000, 39987) + .saturating_add(RocksDbWeight::get().reads(5_u64)) + .saturating_add(RocksDbWeight::get().writes(5_u64)) + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8a000259e..64f93a6ca 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -29,7 +29,7 @@ importers: version: 5.6.0(@types/node@22.9.0)(chokidar@3.6.0)(encoding@0.1.13)(jsdom@23.2.0)(ts-node@10.9.2(@types/node@22.9.0)(typescript@5.6.3))(typescript@5.6.3)(zod@3.23.8) '@moonwall/util': specifier: 5.6.0 - version: 5.6.0(@types/node@22.9.0)(@vitest/ui@2.1.5)(chokidar@3.6.0)(encoding@0.1.13)(jsdom@23.2.0)(typescript@5.6.3)(zod@3.23.8) + version: 5.6.0(@types/node@22.9.0)(@vitest/ui@2.1.5(vitest@2.1.5))(chokidar@3.6.0)(encoding@0.1.13)(jsdom@23.2.0)(typescript@5.6.3)(zod@3.23.8) '@polkadot/api': specifier: 14.3.1 version: 14.3.1 @@ -127,46 +127,46 @@ importers: typescript-api: devDependencies: '@polkadot/api': - specifier: ^14.0.0 + specifier: ^14.3.1 version: 14.3.1 '@polkadot/api-augment': - specifier: ^14.0.0 + specifier: ^14.3.1 version: 14.3.1 '@polkadot/api-base': - specifier: ^14.0.0 + specifier: ^14.3.1 version: 14.3.1 '@polkadot/api-derive': - specifier: ^14.0.0 + specifier: ^14.3.1 version: 14.3.1 '@polkadot/rpc-augment': - specifier: ^14.0.0 + specifier: ^14.3.1 version: 14.3.1 '@polkadot/rpc-core': - specifier: ^14.0.0 + specifier: ^14.3.1 version: 14.3.1 '@polkadot/rpc-provider': - specifier: ^14.0.0 + specifier: ^14.3.1 version: 14.3.1 '@polkadot/typegen': - specifier: ^14.0.0 + specifier: ^14.3.1 version: 14.3.1 '@polkadot/types': - specifier: ^14.0.0 + specifier: ^14.3.1 version: 14.3.1 '@polkadot/types-augment': - specifier: ^14.0.0 + specifier: ^14.3.1 version: 14.3.1 '@polkadot/types-codec': - specifier: ^14.0.0 + specifier: ^14.3.1 version: 14.3.1 '@polkadot/types-create': - specifier: ^14.0.0 + specifier: ^14.3.1 version: 14.3.1 '@polkadot/types-known': - specifier: ^14.0.0 + specifier: ^14.3.1 version: 14.3.1 '@polkadot/types-support': - specifier: ^14.0.0 + specifier: ^14.3.1 version: 14.3.1 '@types/node': specifier: 22.5.0 @@ -1077,7 +1077,6 @@ packages: '@substrate/connect@0.8.11': resolution: {integrity: sha512-ofLs1PAO9AtDdPbdyTYj217Pe+lBfTLltdHDs3ds8no0BseoLeAGxpz1mHfi7zB4IxI3YyAiLjH6U8cw4pj4Nw==} - deprecated: versions below 1.x are no longer maintained '@substrate/light-client-extension-helpers@1.0.0': resolution: {integrity: sha512-TdKlni1mBBZptOaeVrKnusMg/UBpWUORNDv5fdCaJklP4RJiFOzBCrzC+CyVI5kQzsXBisZ+2pXm+rIjS38kHg==} @@ -2865,10 +2864,6 @@ packages: neo-async@2.6.2: resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==} - nock@13.5.5: - resolution: {integrity: sha512-XKYnqUrCwXC8DGG1xX4YH5yNIrlh9c065uaMZZHUoeUUINTOyt+x/G+ezYk0Ft6ExSREVIs+qBJDK503viTfFA==} - engines: {node: '>= 10.13'} - nock@13.5.6: resolution: {integrity: sha512-o2zOYiCpzRqSzPj0Zt/dQ/DqZeYoaQ7TUonc/xUPjCGl9WeHpNbxgVvOquXYAaJzI0M9BXV3HTzG0p8IUAbBTQ==} engines: {node: '>= 10.13'} @@ -4410,7 +4405,7 @@ snapshots: '@acala-network/chopsticks': 1.0.1(debug@4.3.7)(ts-node@10.9.2(@types/node@22.9.0)(typescript@5.6.3)) '@moonbeam-network/api-augment': 0.3200.3 '@moonwall/types': 5.6.0(chokidar@3.6.0)(encoding@0.1.13)(typescript@5.6.3)(zod@3.23.8) - '@moonwall/util': 5.6.0(@types/node@22.9.0)(@vitest/ui@2.1.5)(chokidar@3.6.0)(encoding@0.1.13)(jsdom@23.2.0)(typescript@5.6.3)(zod@3.23.8) + '@moonwall/util': 5.6.0(@types/node@22.9.0)(@vitest/ui@2.1.5(vitest@2.1.5))(chokidar@3.6.0)(encoding@0.1.13)(jsdom@23.2.0)(typescript@5.6.3)(zod@3.23.8) '@octokit/rest': 21.0.2 '@polkadot/api': 14.3.1 '@polkadot/api-derive': 14.3.1 @@ -4513,7 +4508,7 @@ snapshots: - utf-8-validate - zod - '@moonwall/util@5.6.0(@types/node@22.9.0)(@vitest/ui@2.1.5)(chokidar@3.6.0)(encoding@0.1.13)(jsdom@23.2.0)(typescript@5.6.3)(zod@3.23.8)': + '@moonwall/util@5.6.0(@types/node@22.9.0)(@vitest/ui@2.1.5(vitest@2.1.5))(chokidar@3.6.0)(encoding@0.1.13)(jsdom@23.2.0)(typescript@5.6.3)(zod@3.23.8)': dependencies: '@moonbeam-network/api-augment': 0.3200.3 '@moonwall/types': 5.6.0(chokidar@3.6.0)(encoding@0.1.13)(typescript@5.6.3)(zod@3.23.8) @@ -4958,7 +4953,7 @@ snapshots: '@polkadot/x-ws': 13.2.3 eventemitter3: 5.0.1 mock-socket: 9.3.1 - nock: 13.5.5 + nock: 13.5.6 tslib: 2.8.1 optionalDependencies: '@substrate/connect': 0.8.11 @@ -7510,14 +7505,6 @@ snapshots: neo-async@2.6.2: {} - nock@13.5.5: - dependencies: - debug: 4.3.7(supports-color@8.1.1) - json-stringify-safe: 5.0.1 - propagate: 2.0.1 - transitivePeerDependencies: - - supports-color - nock@13.5.6: dependencies: debug: 4.3.7(supports-color@8.1.1) diff --git a/primitives/bridge/src/custom_do_process_message.rs b/primitives/bridge/src/custom_do_process_message.rs index 788fb8587..0a15634d1 100644 --- a/primitives/bridge/src/custom_do_process_message.rs +++ b/primitives/bridge/src/custom_do_process_message.rs @@ -151,6 +151,8 @@ impl ConstantGasMeter { fn maximum_dispatch_gas_used_at_most(command: &Command) -> u64 { match command { Command::Test { .. } => 60_000, + // TODO: revisit gas cost + Command::ReportRewards { .. } => 60_000, } } } diff --git a/primitives/bridge/src/lib.rs b/primitives/bridge/src/lib.rs index cf80217f0..62835bb9e 100644 --- a/primitives/bridge/src/lib.rs +++ b/primitives/bridge/src/lib.rs @@ -27,7 +27,7 @@ use { relay_chain::{AccountId, Balance}, Assets, Location, SendResult, SendXcm, Xcm, XcmHash, }, - ethabi::Token, + ethabi::{Token, U256}, frame_support::{ ensure, pallet_prelude::{Decode, Encode, Get}, @@ -67,6 +67,18 @@ mod custom_send_message; pub enum Command { // TODO: add real commands here Test(Vec), + ReportRewards { + // block timestamp + timestamp: u64, + // index of the era we are sending info of + era_index: u32, + // total_points for the era + total_points: u128, + // new tokens inflated during the era + tokens_inflated: u128, + // merkle root of vec![(validatorId, rewardPoints)] + rewards_merkle_root: H256, + }, } impl Command { @@ -75,6 +87,7 @@ impl Command { match self { // Starting from 32 to keep compatibility with Snowbridge Command enum Command::Test { .. } => 32, + Command::ReportRewards { .. } => 33, } } @@ -84,6 +97,26 @@ impl Command { Command::Test(payload) => { ethabi::encode(&[Token::Tuple(vec![Token::Bytes(payload.clone())])]) } + Command::ReportRewards { + timestamp, + era_index, + total_points, + tokens_inflated, + rewards_merkle_root, + } => { + let timestamp_token = Token::Uint(U256::from(*timestamp)); + let era_index_token = Token::Uint(U256::from(*era_index)); + let total_points_token = Token::Uint(U256::from(*total_points)); + let tokens_inflated_token = Token::Uint(U256::from(*tokens_inflated)); + let rewards_mr_token = Token::FixedBytes(rewards_merkle_root.0.to_vec()); + ethabi::encode(&[Token::Tuple(vec![ + timestamp_token, + era_index_token, + total_points_token, + tokens_inflated_token, + rewards_mr_token, + ])]) + } } } } diff --git a/solo-chains/runtime/dancelight/Cargo.toml b/solo-chains/runtime/dancelight/Cargo.toml index 01dd9bf7e..868e9faf5 100644 --- a/solo-chains/runtime/dancelight/Cargo.toml +++ b/solo-chains/runtime/dancelight/Cargo.toml @@ -74,6 +74,7 @@ pallet-elections-phragmen = { workspace = true } pallet-external-validator-slashes = { workspace = true } pallet-external-validators = { workspace = true } pallet-external-validators-rewards = { workspace = true } +pallet-external-validators-rewards-runtime-api = { workspace = true } pallet-grandpa = { workspace = true } pallet-identity = { workspace = true } pallet-initializer = { workspace = true } @@ -220,6 +221,7 @@ std = [ "pallet-democracy/std", "pallet-elections-phragmen/std", "pallet-external-validator-slashes/std", + "pallet-external-validators-rewards-runtime-api/std", "pallet-external-validators-rewards/std", "pallet-external-validators/std", "pallet-grandpa/std", diff --git a/solo-chains/runtime/dancelight/src/lib.rs b/solo-chains/runtime/dancelight/src/lib.rs index ff39977df..f30bd9c31 100644 --- a/solo-chains/runtime/dancelight/src/lib.rs +++ b/solo-chains/runtime/dancelight/src/lib.rs @@ -78,6 +78,7 @@ use { scale_info::TypeInfo, serde::{Deserialize, Serialize}, snowbridge_core::ChannelId, + snowbridge_pallet_outbound_queue::MerkleProof, sp_core::{storage::well_known_keys as StorageWellKnownKeys, Get}, sp_genesis_builder::PresetId, sp_runtime::{ @@ -555,7 +556,7 @@ impl pallet_session::Config for Runtime { } pub struct FullIdentificationOf; -impl sp_runtime::traits::Convert> for FullIdentificationOf { +impl Convert> for FullIdentificationOf { fn convert(_: AccountId) -> Option<()> { Some(()) } @@ -1335,17 +1336,32 @@ impl pallet_external_validators::Config for Runtime { type UnixTime = Timestamp; type SessionsPerEra = SessionsPerEra; type OnEraStart = (ExternalValidatorSlashes, ExternalValidatorsRewards); - type OnEraEnd = (); + type OnEraEnd = ExternalValidatorsRewards; type WeightInfo = weights::pallet_external_validators::SubstrateWeight; #[cfg(feature = "runtime-benchmarks")] type Currency = Balances; } +pub struct TimestampProvider; +impl Get for TimestampProvider { + fn get() -> u64 { + Timestamp::get() + } +} + impl pallet_external_validators_rewards::Config for Runtime { type EraIndexProvider = ExternalValidators; type HistoryDepth = ConstU32<64>; type BackingPoints = ConstU32<20>; type DisputeStatementPoints = ConstU32<20>; + // TODO: add a proper way to retrieve the inflated tokens. + // Will likely be through InflationRewards. + type EraInflationProvider = (); + type TimestampProvider = TimestampProvider; + type Hashing = Keccak256; + type ValidateMessage = tp_bridge::MessageValidator; + type OutboundQueue = tp_bridge::CustomSendMessage; + type WeightInfo = weights::pallet_external_validators_rewards::SubstrateWeight; } impl pallet_external_validator_slashes::Config for Runtime { @@ -2078,6 +2094,7 @@ mod benches { [pallet_registrar, ContainerRegistrar] [pallet_collator_assignment, TanssiCollatorAssignment] [pallet_external_validators, ExternalValidators] + [pallet_external_validators_rewards, ExternalValidatorsRewards] [pallet_external_validator_slashes, ExternalValidatorSlashes] [pallet_invulnerables, TanssiInvulnerables] // XCM @@ -2696,6 +2713,19 @@ sp_api::impl_runtime_apis! { } } + impl pallet_external_validators_rewards_runtime_api::ExternalValidatorsRewardsApi for Runtime + where + EraIndex: parity_scale_codec::Codec, + { + fn generate_rewards_merkle_proof(account_id: AccountId, era_index: EraIndex) -> Option { + ExternalValidatorsRewards::generate_rewards_merkle_proof(account_id, era_index) + } + + fn verify_rewards_merkle_proof(merkle_proof: MerkleProof) -> bool { + ExternalValidatorsRewards::verify_rewards_merkle_proof(merkle_proof) + } + } + impl dp_consensus::TanssiAuthorityAssignmentApi for Runtime { /// Return the current authorities assigned to a given paraId fn para_id_authorities(para_id: ParaId) -> Option> { diff --git a/solo-chains/runtime/dancelight/src/tests/external_validators_tests.rs b/solo-chains/runtime/dancelight/src/tests/external_validators_tests.rs index 630fa2430..2316e4949 100644 --- a/solo-chains/runtime/dancelight/src/tests/external_validators_tests.rs +++ b/solo-chains/runtime/dancelight/src/tests/external_validators_tests.rs @@ -18,10 +18,15 @@ use { crate::{ - tests::common::*, ExternalValidators, MaxExternalValidators, SessionKeys, SessionsPerEra, + tests::common::*, ExternalValidators, ExternalValidatorsRewards, MaxExternalValidators, + RuntimeEvent, SessionKeys, SessionsPerEra, System, }, frame_support::{assert_ok, traits::fungible::Mutate}, pallet_external_validators::Forcing, + parity_scale_codec::Encode, + snowbridge_core::{Channel, PRIMARY_GOVERNANCE_CHANNEL}, + sp_core::H256, + sp_io::hashing::twox_64, std::{collections::HashMap, ops::RangeInclusive}, }; @@ -692,3 +697,145 @@ fn external_validators_manual_reward_points() { ); }); } + +#[test] +fn external_validators_rewards_sends_message_on_era_end() { + ExtBuilder::default() + .with_balances(vec![ + (AccountId::from(ALICE), 210_000 * UNIT), + (AccountId::from(BOB), 100_000 * UNIT), + ]) + .build() + .execute_with(|| { + // SessionsPerEra depends on fast-runtime feature, this test should pass regardless + let sessions_per_era = SessionsPerEra::get(); + + let channel_id = PRIMARY_GOVERNANCE_CHANNEL.encode(); + + // Insert PRIMARY_GOVERNANCE_CHANNEL channel id into storage. + let mut combined_channel_id_key = Vec::new(); + let hashed_key = twox_64(&channel_id); + + combined_channel_id_key.extend_from_slice(&hashed_key); + combined_channel_id_key.extend_from_slice(PRIMARY_GOVERNANCE_CHANNEL.as_ref()); + + let mut full_storage_key = Vec::new(); + full_storage_key.extend_from_slice(&frame_support::storage::storage_prefix( + b"EthereumSystem", + b"Channels", + )); + full_storage_key.extend_from_slice(&combined_channel_id_key); + + let channel = Channel { + agent_id: H256::default(), + para_id: 1000u32.into(), + }; + + frame_support::storage::unhashed::put(&full_storage_key, &channel); + + // This will call on_era_end for era 0 + run_to_session(sessions_per_era); + + let outbound_msg_queue_event = System::events() + .iter() + .filter(|r| match r.event { + RuntimeEvent::EthereumOutboundQueue( + snowbridge_pallet_outbound_queue::Event::MessageQueued { .. }, + ) => true, + _ => false, + }) + .count(); + + assert_eq!( + outbound_msg_queue_event, 1, + "MessageQueued event should be emitted" + ); + }); +} + +#[test] +fn external_validators_rewards_merkle_proofs() { + use {crate::ValidatorIndex, runtime_parachains::inclusion::RewardValidators}; + + ExtBuilder::default() + .with_balances(vec![ + (AccountId::from(ALICE), 210_000 * UNIT), + (AccountId::from(BOB), 100_000 * UNIT), + ]) + .build() + .execute_with(|| { + // SessionsPerEra depends on fast-runtime feature, this test should pass regardless + let sessions_per_era = SessionsPerEra::get(); + + assert_ok!(ExternalValidators::skip_external_validators( + root_origin(), + true + )); + + run_to_session(sessions_per_era); + let validators = Session::validators(); + + // Only whitelisted validators get selected + assert_eq!( + validators, + vec![AccountId::from(ALICE), AccountId::from(BOB)] + ); + + assert!( + pallet_external_validators_rewards::RewardPointsForEra::::iter().count() + == 0 + ); + + // Reward Alice and Bob in era 1 + crate::RewardValidators::reward_backing(vec![ValidatorIndex(0)]); + crate::RewardValidators::reward_backing(vec![ValidatorIndex(1)]); + + assert!( + pallet_external_validators_rewards::RewardPointsForEra::::iter().count() + == 1 + ); + + let alice_merkle_proof = ExternalValidatorsRewards::generate_rewards_merkle_proof( + AccountId::from(ALICE), + 1u32, + ); + let is_alice_merkle_proof_valid = + ExternalValidatorsRewards::verify_rewards_merkle_proof(alice_merkle_proof.unwrap()); + + let bob_merkle_proof = ExternalValidatorsRewards::generate_rewards_merkle_proof( + AccountId::from(BOB), + 1u32, + ); + let is_bob_merkle_proof_valid = + ExternalValidatorsRewards::verify_rewards_merkle_proof(bob_merkle_proof.unwrap()); + + assert!(is_alice_merkle_proof_valid); + assert!(is_bob_merkle_proof_valid); + + // Let's check invalid proofs now. + let charlie_merkle_proof = ExternalValidatorsRewards::generate_rewards_merkle_proof( + AccountId::from(CHARLIE), + 1u32, + ); + + let alice_invalid_merkle_proof = + ExternalValidatorsRewards::generate_rewards_merkle_proof( + AccountId::from(ALICE), + 0u32, + ); + + let bob_invalid_merkle_proof = ExternalValidatorsRewards::generate_rewards_merkle_proof( + AccountId::from(BOB), + 2u32, + ); + + // Charlie is not present in the validator set, so no merkle proof for him. + assert!(charlie_merkle_proof.is_none()); + + // Alice wasn't rewarded for era 0. + assert!(alice_invalid_merkle_proof.is_none()); + + // Proof for a future era should also be invalid. + assert!(bob_invalid_merkle_proof.is_none()); + }); +} diff --git a/solo-chains/runtime/dancelight/src/weights/mod.rs b/solo-chains/runtime/dancelight/src/weights/mod.rs index 9eec1d86d..1673a91c3 100644 --- a/solo-chains/runtime/dancelight/src/weights/mod.rs +++ b/solo-chains/runtime/dancelight/src/weights/mod.rs @@ -22,6 +22,7 @@ pub mod pallet_collator_assignment; pub mod pallet_conviction_voting; pub mod pallet_external_validator_slashes; pub mod pallet_external_validators; +pub mod pallet_external_validators_rewards; pub mod pallet_identity; pub mod pallet_invulnerables; pub mod pallet_message_queue; diff --git a/solo-chains/runtime/dancelight/src/weights/pallet_external_validators_rewards.rs b/solo-chains/runtime/dancelight/src/weights/pallet_external_validators_rewards.rs new file mode 100644 index 000000000..dcdf9b1e0 --- /dev/null +++ b/solo-chains/runtime/dancelight/src/weights/pallet_external_validators_rewards.rs @@ -0,0 +1,82 @@ +// Copyright (C) Moondance Labs Ltd. +// This file is part of Tanssi. + +// Tanssi is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Tanssi is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Tanssi. If not, see + + +//! Autogenerated weights for pallet_external_validators_rewards +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 43.0.0 +//! DATE: 2024-12-05, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `COV0768`, CPU: `AMD Ryzen 9 7950X 16-Core Processor` +//! EXECUTION: , WASM-EXECUTION: Compiled, CHAIN: Some("dancelight-dev"), DB CACHE: 1024 + +// Executed Command: +// target/release/tanssi-relay +// benchmark +// pallet +// --execution=wasm +// --wasm-execution=compiled +// --pallet +// pallet_external_validators_rewards +// --extrinsic +// * +// --chain=dancelight-dev +// --steps +// 50 +// --repeat +// 20 +// --template=benchmarking/frame-weight-runtime-template.hbs +// --json-file +// raw.json +// --output +// solo-chains/runtime/dancelight/src/weights/pallet_external_validators_rewards.rs + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] + +use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; +use sp_std::marker::PhantomData; + +/// Weights for pallet_external_validators_rewards using the Substrate node and recommended hardware. +pub struct SubstrateWeight(PhantomData); +impl pallet_external_validators_rewards::WeightInfo for SubstrateWeight { + /// Storage: `ExternalValidatorsRewards::RewardPointsForEra` (r:1 w:0) + /// Proof: `ExternalValidatorsRewards::RewardPointsForEra` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `Timestamp::Now` (r:1 w:0) + /// Proof: `Timestamp::Now` (`max_values`: Some(1), `max_size`: Some(8), added: 503, mode: `MaxEncodedLen`) + /// Storage: `EthereumSystem::Channels` (r:1 w:0) + /// Proof: `EthereumSystem::Channels` (`max_values`: None, `max_size`: Some(76), added: 2551, mode: `MaxEncodedLen`) + /// Storage: `MessageQueue::BookStateFor` (r:1 w:1) + /// Proof: `MessageQueue::BookStateFor` (`max_values`: None, `max_size`: Some(136), added: 2611, mode: `MaxEncodedLen`) + /// Storage: `MessageQueue::ServiceHead` (r:1 w:1) + /// Proof: `MessageQueue::ServiceHead` (`max_values`: Some(1), `max_size`: Some(33), added: 528, mode: `MaxEncodedLen`) + /// Storage: UNKNOWN KEY `0x3a72656c61795f64697370617463685f71756575655f72656d61696e696e675f` (r:0 w:1) + /// Proof: UNKNOWN KEY `0x3a72656c61795f64697370617463685f71756575655f72656d61696e696e675f` (r:0 w:1) + /// Storage: `MessageQueue::Pages` (r:0 w:1) + /// Proof: `MessageQueue::Pages` (`max_values`: None, `max_size`: Some(32845), added: 35320, mode: `MaxEncodedLen`) + /// Storage: UNKNOWN KEY `0xf5207f03cfdce586301014700e2c2593fad157e461d71fd4c1f936839a5f1f3e` (r:0 w:1) + /// Proof: UNKNOWN KEY `0xf5207f03cfdce586301014700e2c2593fad157e461d71fd4c1f936839a5f1f3e` (r:0 w:1) + fn on_era_end() -> Weight { + // Proof Size summary in bytes: + // Measured: `36522` + // Estimated: `39987` + // Minimum execution time: 1_005_863_000 picoseconds. + Weight::from_parts(1_017_752_000, 39987) + .saturating_add(T::DbWeight::get().reads(5_u64)) + .saturating_add(T::DbWeight::get().writes(5_u64)) + } +} \ No newline at end of file diff --git a/test/suites/dev-tanssi-relay/external-validators-rewards/test-rewards-runtime-api.ts b/test/suites/dev-tanssi-relay/external-validators-rewards/test-rewards-runtime-api.ts new file mode 100644 index 000000000..06014e305 --- /dev/null +++ b/test/suites/dev-tanssi-relay/external-validators-rewards/test-rewards-runtime-api.ts @@ -0,0 +1,44 @@ +import { beforeAll, customDevRpcRequest, describeSuite, expect } from "@moonwall/cli"; +import { ApiPromise, Keyring } from "@polkadot/api"; +import { jumpToSession } from "util/block"; + +describeSuite({ + id: "DTR0820", + title: "Starlight <> Ethereum - Rewards mapping", + foundationMethods: "dev", + testCases: ({ it, context }) => { + let polkadotJs: ApiPromise; + + beforeAll(async function () { + polkadotJs = context.polkadotJs(); + }); + + it({ + id: "T01", + title: "Should succeed calling runtimeApi for generating/validating merkle proofs", + test: async function () { + const keyring = new Keyring({ type: "sr25519" }); + const aliceStash = keyring.addFromUri("//Alice//stash"); + await context.createBlock(); + // Send RPC call to enable para inherent candidate generation + await customDevRpcRequest("mock_enableParaInherentCandidate", []); + // Since collators are not assigned until session 2, we need to go till session 2 to actually see heads being injected + await jumpToSession(context, 3); + await context.createBlock(); + + // We will only check alice's proof as she is the only one validating candidates + const aliceMerkleProof = await polkadotJs.call.externalValidatorsRewardsApi.generateRewardsMerkleProof( + aliceStash.address, + 1 + ); + expect(aliceMerkleProof.isEmpty).to.be.false; + + const isValidProofAlice = await polkadotJs.call.externalValidatorsRewardsApi.verifyRewardsMerkleProof( + aliceMerkleProof.toJSON() + ); + + expect(isValidProofAlice.toJSON()).to.be.eq(true); + }, + }); + }, +}); diff --git a/typescript-api/package.json b/typescript-api/package.json index f03fa559a..cc2157974 100644 --- a/typescript-api/package.json +++ b/typescript-api/package.json @@ -95,20 +95,20 @@ "@polkadot/types-codec": "14.x" }, "devDependencies": { - "@polkadot/api": "^14.0.0", - "@polkadot/api-augment": "^14.0.0", - "@polkadot/api-base": "^14.0.0", - "@polkadot/api-derive": "^14.0.0", - "@polkadot/rpc-augment": "^14.0.0", - "@polkadot/rpc-core": "^14.0.0", - "@polkadot/rpc-provider": "^14.0.0", - "@polkadot/typegen": "^14.0.0", - "@polkadot/types": "^14.0.0", - "@polkadot/types-augment": "^14.0.0", - "@polkadot/types-codec": "^14.0.0", - "@polkadot/types-create": "^14.0.0", - "@polkadot/types-known": "^14.0.0", - "@polkadot/types-support": "^14.0.0", + "@polkadot/api": "^14.3.1", + "@polkadot/api-augment": "^14.3.1", + "@polkadot/api-base": "^14.3.1", + "@polkadot/api-derive": "^14.3.1", + "@polkadot/rpc-augment": "^14.3.1", + "@polkadot/rpc-core": "^14.3.1", + "@polkadot/rpc-provider": "^14.3.1", + "@polkadot/typegen": "^14.3.1", + "@polkadot/types": "^14.3.1", + "@polkadot/types-augment": "^14.3.1", + "@polkadot/types-codec": "^14.3.1", + "@polkadot/types-create": "^14.3.1", + "@polkadot/types-known": "^14.3.1", + "@polkadot/types-support": "^14.3.1", "@types/node": "22.5.0", "chalk": "^5.3.0", "prettier": "^3.3.3",