diff --git a/operator/Cargo.lock b/operator/Cargo.lock
index b0c9ddcc..da0b1ee7 100644
--- a/operator/Cargo.lock
+++ b/operator/Cargo.lock
@@ -2337,6 +2337,7 @@ dependencies = [
"frame-try-runtime",
"hex",
"hex-literal 0.3.4",
+ "log",
"pallet-authorship",
"pallet-babe",
"pallet-balances",
@@ -2346,6 +2347,8 @@ dependencies = [
"pallet-evm",
"pallet-evm-chain-id",
"pallet-external-validators",
+ "pallet-external-validators-rewards",
+ "pallet-external-validators-rewards-runtime-api",
"pallet-grandpa",
"pallet-identity",
"pallet-im-online",
@@ -2517,6 +2520,7 @@ dependencies = [
"frame-try-runtime",
"hex",
"hex-literal 0.3.4",
+ "log",
"pallet-authorship",
"pallet-babe",
"pallet-balances",
@@ -2526,6 +2530,8 @@ dependencies = [
"pallet-evm",
"pallet-evm-chain-id",
"pallet-external-validators",
+ "pallet-external-validators-rewards",
+ "pallet-external-validators-rewards-runtime-api",
"pallet-grandpa",
"pallet-identity",
"pallet-im-online",
@@ -2605,6 +2611,7 @@ dependencies = [
"frame-try-runtime",
"hex",
"hex-literal 0.3.4",
+ "log",
"pallet-authorship",
"pallet-babe",
"pallet-balances",
@@ -2614,6 +2621,8 @@ dependencies = [
"pallet-evm",
"pallet-evm-chain-id",
"pallet-external-validators",
+ "pallet-external-validators-rewards",
+ "pallet-external-validators-rewards-runtime-api",
"pallet-grandpa",
"pallet-identity",
"pallet-im-online",
@@ -7647,6 +7656,43 @@ dependencies = [
"sp-std",
]
+[[package]]
+name = "pallet-external-validators-rewards"
+version = "0.1.0"
+dependencies = [
+ "cumulus-primitives-core",
+ "frame-benchmarking",
+ "frame-support",
+ "frame-system",
+ "log",
+ "pallet-balances",
+ "pallet-external-validators",
+ "pallet-session",
+ "pallet-timestamp",
+ "parity-scale-codec",
+ "polkadot-primitives",
+ "polkadot-runtime-parachains",
+ "scale-info",
+ "snowbridge-core 0.2.0",
+ "snowbridge-merkle-tree",
+ "snowbridge-outbound-queue-primitives",
+ "sp-core",
+ "sp-io",
+ "sp-runtime",
+ "sp-staking",
+ "sp-std",
+]
+
+[[package]]
+name = "pallet-external-validators-rewards-runtime-api"
+version = "0.1.0"
+dependencies = [
+ "parity-scale-codec",
+ "snowbridge-merkle-tree",
+ "sp-api",
+ "sp-core",
+]
+
[[package]]
name = "pallet-fast-unstake"
version = "38.1.0"
diff --git a/operator/Cargo.toml b/operator/Cargo.toml
index 4583d483..44633ada 100644
--- a/operator/Cargo.toml
+++ b/operator/Cargo.toml
@@ -25,6 +25,8 @@ datahaven-stagenet-runtime = { path = "./runtime/stagenet", default-features = f
datahaven-testnet-runtime = { path = "./runtime/testnet", default-features = false }
dhp-bridge = { path = "./primitives/bridge", 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-outbound-commitment-store = { path = "./pallets/outbound-commitment-store", default-features = false }
# Crates.io (wasm)
@@ -57,7 +59,7 @@ parity-bytes = { version = "0.1.2", default-features = false }
parity-scale-codec = { version = "3.0.0", default-features = false, features = [
"derive",
] }
-rand = { version = "0.8.5", default-features = false }
+rand = { version = "0.8.5", default-features = false, features = [ "std_rng" ] }
rlp = { version = "0.6.1", default-features = false }
scale-info = { version = "2.11.6", default-features = false }
serde = { version = "1.0.197", default-features = false, features = ["derive"] }
@@ -109,6 +111,7 @@ pallet-xcm = { git = "https://github.com/paritytech/polkadot-sdk", branch = "sta
polkadot-parachain-primitives = { git = "https://github.com/paritytech/polkadot-sdk", branch = "stable2412", default-features = false }
polkadot-primitives = { git = "https://github.com/paritytech/polkadot-sdk", branch = "stable2412", default-features = false }
polkadot-runtime-common = { git = "https://github.com/paritytech/polkadot-sdk", branch = "stable2412", default-features = false }
+runtime-parachains = { package = "polkadot-runtime-parachains", git = "https://github.com/paritytech/polkadot-sdk", branch = "stable2412", default-features = false }
sc-basic-authorship = { git = "https://github.com/paritytech/polkadot-sdk", branch = "stable2412", default-features = false }
sc-cli = { git = "https://github.com/paritytech/polkadot-sdk", branch = "stable2412", default-features = false }
sc-client-api = { git = "https://github.com/paritytech/polkadot-sdk", branch = "stable2412", default-features = false }
diff --git a/operator/pallets/external-validators-rewards/Cargo.toml b/operator/pallets/external-validators-rewards/Cargo.toml
new file mode 100644
index 00000000..10c4cc61
--- /dev/null
+++ b/operator/pallets/external-validators-rewards/Cargo.toml
@@ -0,0 +1,89 @@
+[package]
+name = "pallet-external-validators-rewards"
+authors = { workspace = true }
+description = "Simple pallet to store 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]
+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 }
+
+frame-benchmarking = { workspace = true }
+
+pallet-balances = { workspace = true, optional = true }
+pallet-external-validators = { workspace = true }
+pallet-session = { workspace = true, features = [ "historical" ] }
+runtime-parachains = { workspace = true }
+
+snowbridge-core = { workspace = true }
+snowbridge-merkle-tree = { workspace = true }
+snowbridge-outbound-queue-primitives = { workspace = true }
+cumulus-primitives-core = { workspace = true }
+polkadot-primitives = { workspace = true }
+
+[dev-dependencies]
+pallet-timestamp = { workspace = true }
+sp-io = { workspace = true }
+
+[features]
+default = [ "std" ]
+std = [
+ "cumulus-primitives-core/std",
+ "frame-benchmarking/std",
+ "frame-support/std",
+ "frame-system/std",
+ "log/std",
+ "pallet-balances/std",
+ "pallet-session/std",
+ "pallet-timestamp/std",
+ "parity-scale-codec/std",
+ "polkadot-primitives/std",
+ "runtime-parachains/std",
+ "scale-info/std",
+ "snowbridge-core/std",
+ "snowbridge-merkle-tree/std",
+ "sp-core/std",
+ "sp-io/std",
+ "sp-runtime/std",
+ "sp-staking/std",
+ "sp-std/std",
+]
+runtime-benchmarks = [
+ "cumulus-primitives-core/runtime-benchmarks",
+ "frame-benchmarking/runtime-benchmarks",
+ "frame-support/runtime-benchmarks",
+ "frame-system/runtime-benchmarks",
+ "pallet-balances/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",
+]
+
+try-runtime = [
+ "frame-support/try-runtime",
+ "frame-system/try-runtime",
+ "pallet-balances?/try-runtime",
+ "pallet-session/try-runtime",
+ "pallet-timestamp/try-runtime",
+ "runtime-parachains/try-runtime",
+ "sp-runtime/try-runtime",
+]
diff --git a/operator/pallets/external-validators-rewards/runtime-api/Cargo.toml b/operator/pallets/external-validators-rewards/runtime-api/Cargo.toml
new file mode 100644
index 00000000..d97c7ba6
--- /dev/null
+++ b/operator/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-merkle-tree = { workspace = true }
+sp-api = { workspace = true }
+sp-core = { workspace = true }
+
+[features]
+default = [ "std" ]
+std = [
+ "parity-scale-codec/std",
+ "snowbridge-merkle-tree/std",
+ "sp-api/std",
+ "sp-core/std",
+]
diff --git a/operator/pallets/external-validators-rewards/runtime-api/src/lib.rs b/operator/pallets/external-validators-rewards/runtime-api/src/lib.rs
new file mode 100644
index 00000000..713d6078
--- /dev/null
+++ b/operator/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_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/operator/pallets/external-validators-rewards/src/benchmarking.rs b/operator/pallets/external-validators-rewards/src/benchmarking.rs
new file mode 100644
index 00000000..335557f4
--- /dev/null
+++ b/operator/pallets/external-validators-rewards/src/benchmarking.rs
@@ -0,0 +1,80 @@
+// 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 {
+ crate::{types::BenchmarkHelper, OnEraEnd},
+ frame_benchmarking::{account, v2::*, BenchmarkError},
+ frame_support::traits::Currency,
+ sp_std::prelude::*,
+};
+
+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 = 20 * 1000;
+
+ for i in 0..1000 {
+ let account_id = create_funded_user::("candidate", i, 100);
+ era_reward_points.individual.insert(account_id, 20);
+ }
+
+ T::BenchmarkHelper::setup();
+ >::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/operator/pallets/external-validators-rewards/src/lib.rs b/operator/pallets/external-validators-rewards/src/lib.rs
new file mode 100644
index 00000000..d5569392
--- /dev/null
+++ b/operator/pallets/external-validators-rewards/src/lib.rs
@@ -0,0 +1,402 @@
+// 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
+
+//! This pallet keep tracks of the validators reward points.
+//! Storage will be cleared after a period of time.
+
+#![cfg_attr(not(feature = "std"), no_std)]
+
+#[cfg(test)]
+mod mock;
+
+#[cfg(test)]
+mod tests;
+
+#[cfg(feature = "runtime-benchmarks")]
+mod benchmarking;
+
+pub mod types;
+pub mod weights;
+
+pub use pallet::*;
+
+use {
+ crate::types::{EraRewardsUtils, HandleInflation, SendMessage},
+ frame_support::traits::{Defensive, Get, ValidatorSet},
+ pallet_external_validators::traits::{ExternalIndexProvider, OnEraEnd, OnEraStart},
+ parity_scale_codec::Encode,
+ polkadot_primitives::ValidatorIndex,
+ runtime_parachains::session_info,
+ snowbridge_merkle_tree::{merkle_proof, merkle_root, verify_proof, MerkleProof},
+ sp_core::H256,
+ sp_runtime::traits::{Hash, Zero},
+ sp_staking::SessionIndex,
+ sp_std::{collections::btree_set::BTreeSet, vec::Vec},
+};
+
+#[frame_support::pallet]
+pub mod pallet {
+ use frame_support::traits::fungible;
+
+ pub use crate::weights::WeightInfo;
+ use {
+ super::*, frame_support::pallet_prelude::*,
+ pallet_external_validators::traits::EraIndexProvider, sp_runtime::Saturating,
+ sp_std::collections::btree_map::BTreeMap,
+ };
+
+ /// The current storage version.
+ const STORAGE_VERSION: StorageVersion = StorageVersion::new(0);
+
+ pub type RewardPoints = u32;
+ pub type EraIndex = u32;
+
+ #[pallet::config]
+ pub trait Config: frame_system::Config {
+ /// Overarching event type.
+ type RuntimeEvent: From> + IsType<::RuntimeEvent>;
+
+ /// How to fetch the current era info.
+ type EraIndexProvider: EraIndexProvider;
+
+ /// For how many eras points are kept in storage.
+ #[pallet::constant]
+ type HistoryDepth: Get;
+
+ /// The amount of era points given by backing a candidate that is included.
+ #[pallet::constant]
+ type BackingPoints: Get;
+
+ /// 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 external index indetifying the validators
+ type ExternalIndexProvider: ExternalIndexProvider;
+
+ type GetWhitelistedValidators: Get>;
+
+ /// Hashing tool used to generate/verify merkle roots and proofs.
+ type Hashing: Hash