Skip to content

Commit

Permalink
External Validators Rewards pallet (#736)
Browse files Browse the repository at this point in the history
* Add pallet_external_validators, remove ValidatorManager

* Rename extrinsics and add tests

* Fix tests

* Fix benchmarks

* typescript-api

* Add traits

* Some PR comments

* Change era every n sessions

* WIP integration test and hooks

* Benchmark new extrinsics

* Migrate queued keys instead of current validators

* typescript-api

* Add era index to hook

* impl for tuples

* Make tests pass

* Add integration tests

* current era and active era

* typescript-api

* Add one typescript test

* console log

* run_to_block not working

* Unit tests still not working

* Fix unit tests

* Start era 0 in session 0

* Rewrite pallet logic, copy it from pallet_staking

* Test hook calls

* typescript-api

* Add era session start to trait

* pallet prototype

* add pallet to runtime

* use EraIndexProvider

* remove points after some time

* mock and tests

* copyright

* zepter

* docs

* ts api

* tests

* use type alias

* fmt

* points in Config

* add comment about bounds

* update ts apis

---------

Co-authored-by: Tomasz Polaczyk <[email protected]>
  • Loading branch information
nanocryk and tmpolaczyk authored Nov 14, 2024
1 parent 1d26cc9 commit a9fd0af
Show file tree
Hide file tree
Showing 17 changed files with 915 additions and 256 deletions.
23 changes: 23 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ pallet-data-preservers = { path = "pallets/data-preservers", default-features =
pallet-data-preservers-runtime-api = { path = "pallets/data-preservers/runtime-api", default-features = false }
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-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 }
Expand Down
80 changes: 80 additions & 0 deletions pallets/external-validators-rewards/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
[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]
parity-scale-codec = { workspace = true }
scale-info = { workspace = true, features = [ "derive" ] }

frame-support = { workspace = true }
frame-system = { workspace = true }
sp-runtime = { workspace = true }
sp-staking = { workspace = true }
sp-std = { workspace = true }
tp-traits = { workspace = true }

frame-benchmarking = { workspace = true }

pallet-balances = { workspace = true, optional = true }
pallet-session = { workspace = true, features = [ "historical" ] }
runtime-parachains = { workspace = true }

polkadot-primitives = { workspace = true }

[dev-dependencies]
pallet-timestamp = { workspace = true }
sp-core = { workspace = true }
sp-io = { workspace = true }

[features]
default = [ "std" ]
std = [
"frame-benchmarking/std",
"frame-support/std",
"frame-system/std",
"pallet-balances/std",
"pallet-session/std",
"pallet-timestamp/std",
"parity-scale-codec/std",
"polkadot-primitives/std",
"runtime-parachains/std",
"scale-info/std",
"sp-core/std",
"sp-io/std",
"sp-runtime/std",
"sp-staking/std",
"sp-std/std",
"tp-traits/std",
]
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",
"sp-runtime/runtime-benchmarks",
"sp-staking/runtime-benchmarks",
"tp-traits/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",
]
178 changes: 178 additions & 0 deletions pallets/external-validators-rewards/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
// 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 <http://www.gnu.org/licenses/>

//! 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;

pub use pallet::*;

use {
frame_support::traits::{Defensive, Get, ValidatorSet},
polkadot_primitives::ValidatorIndex,
runtime_parachains::session_info,
sp_staking::SessionIndex,
sp_std::collections::btree_set::BTreeSet,
};

#[frame_support::pallet]
pub mod pallet {
use {
frame_support::pallet_prelude::*, sp_std::collections::btree_map::BTreeMap,
tp_traits::EraIndexProvider,
};

/// 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 {
/// How to fetch the current era info.
type EraIndexProvider: EraIndexProvider;

/// For how many eras points are kept in storage.
#[pallet::constant]
type HistoryDepth: Get<EraIndex>;

/// The amount of era points given by backing a candidate that is included.
#[pallet::constant]
type BackingPoints: Get<u32>;

/// The amount of era points given by dispute voting on a candidate.
#[pallet::constant]
type DisputeStatementPoints: Get<u32>;
}

#[pallet::pallet]
#[pallet::storage_version(STORAGE_VERSION)]
pub struct Pallet<T>(_);

/// Keep tracks of distributed points per validator and total.
#[derive(RuntimeDebug, Encode, Decode, PartialEq, Eq, TypeInfo)]
pub struct EraRewardPoints<AccountId> {
pub total: RewardPoints,
pub individual: BTreeMap<AccountId, RewardPoints>,
}

impl<AccountId> Default for EraRewardPoints<AccountId> {
fn default() -> Self {
EraRewardPoints {
total: Default::default(),
individual: BTreeMap::new(),
}
}
}

/// Store reward points per era.
/// Note: EraRewardPoints is actually bounded by the amount of validators.
#[pallet::storage]
#[pallet::unbounded]
pub type RewardPointsForEra<T: Config> =
StorageMap<_, Twox64Concat, EraIndex, EraRewardPoints<T::AccountId>, ValueQuery>;

impl<T: Config> Pallet<T> {
pub fn reward_by_ids(points: impl IntoIterator<Item = (T::AccountId, RewardPoints)>) {
let active_era = T::EraIndexProvider::active_era();

RewardPointsForEra::<T>::mutate(active_era.index, |era_rewards| {
for (validator, points) in points.into_iter() {
*era_rewards.individual.entry(validator).or_default() += points;
era_rewards.total += points;
}
})
}
}

impl<T: Config> tp_traits::OnEraStart for Pallet<T> {
fn on_era_start(era_index: EraIndex, _session_start: u32) {
let Some(era_index_to_delete) = era_index.checked_sub(T::HistoryDepth::get()) else {
return;
};

RewardPointsForEra::<T>::remove(era_index_to_delete);
}
}
}

/// Rewards validators for participating in parachains with era points in pallet-staking.
pub struct RewardValidatorsWithEraPoints<C>(core::marker::PhantomData<C>);

impl<C> RewardValidatorsWithEraPoints<C>
where
C: pallet::Config + session_info::Config,
C::ValidatorSet: ValidatorSet<C::AccountId, ValidatorId = C::AccountId>,
{
/// Reward validators in session with points, but only if they are in the active set.
fn reward_only_active(
session_index: SessionIndex,
indices: impl IntoIterator<Item = ValidatorIndex>,
points: u32,
) {
let validators = session_info::AccountKeys::<C>::get(&session_index);
let validators = match validators
.defensive_proof("account_keys are present for dispute_period sessions")
{
Some(validators) => validators,
None => return,
};
// limit rewards to the active validator set
let active_set: BTreeSet<_> = C::ValidatorSet::validators().into_iter().collect();

let rewards = indices
.into_iter()
.filter_map(|i| validators.get(i.0 as usize).cloned())
.filter(|v| active_set.contains(v))
.map(|v| (v, points));

pallet::Pallet::<C>::reward_by_ids(rewards);
}
}

impl<C> runtime_parachains::inclusion::RewardValidators for RewardValidatorsWithEraPoints<C>
where
C: pallet::Config + runtime_parachains::shared::Config + session_info::Config,
C::ValidatorSet: ValidatorSet<C::AccountId, ValidatorId = C::AccountId>,
{
fn reward_backing(indices: impl IntoIterator<Item = ValidatorIndex>) {
let session_index = runtime_parachains::shared::CurrentSessionIndex::<C>::get();
Self::reward_only_active(session_index, indices, C::BackingPoints::get());
}

fn reward_bitfields(_validators: impl IntoIterator<Item = ValidatorIndex>) {}
}

impl<C> runtime_parachains::disputes::RewardValidators for RewardValidatorsWithEraPoints<C>
where
C: pallet::Config + session_info::Config,
C::ValidatorSet: ValidatorSet<C::AccountId, ValidatorId = C::AccountId>,
{
fn reward_dispute_statement(
session: SessionIndex,
validators: impl IntoIterator<Item = ValidatorIndex>,
) {
Self::reward_only_active(session, validators, C::DisputeStatementPoints::get());
}
}
Loading

0 comments on commit a9fd0af

Please sign in to comment.