mirror of
https://github.com/datahaven-xyz/datahaven
synced 2026-05-24 09:50:01 +00:00
feat(operator): Add external validators Pallet (#65)
This PR replaces `pallet-validator-set` with a new
`pallet-external-validators` pallet from Tanssi.
## Key Changes
- **New ExternalValidators Pallet**
- Supports whitelisted validators (set by governance, not rewarded)
- Manages external validators (can be enabled/disabled)
- Implements era-based rotation (eras change after configurable
sessions)
- **Bridge Integration**
- Updated `EigenLayerMessageProcessor` in
`operator/primitives/bridge/src/lib.rs`
- Replaced the old `SetValidators` command with
```rust
ReceiveValidators { validators, external_index }
```
This commit is contained in:
parent
ce59dd9625
commit
b548d3ec39
31 changed files with 2301 additions and 1318 deletions
47
operator/Cargo.lock
generated
47
operator/Cargo.lock
generated
|
|
@ -2345,6 +2345,7 @@ dependencies = [
|
|||
"pallet-ethereum",
|
||||
"pallet-evm",
|
||||
"pallet-evm-chain-id",
|
||||
"pallet-external-validators",
|
||||
"pallet-grandpa",
|
||||
"pallet-identity",
|
||||
"pallet-im-online",
|
||||
|
|
@ -2362,7 +2363,6 @@ dependencies = [
|
|||
"pallet-transaction-payment",
|
||||
"pallet-transaction-payment-rpc-runtime-api",
|
||||
"pallet-utility",
|
||||
"pallet-validator-set",
|
||||
"parity-scale-codec",
|
||||
"polkadot-primitives",
|
||||
"polkadot-runtime-common",
|
||||
|
|
@ -2524,6 +2524,7 @@ dependencies = [
|
|||
"pallet-ethereum",
|
||||
"pallet-evm",
|
||||
"pallet-evm-chain-id",
|
||||
"pallet-external-validators",
|
||||
"pallet-grandpa",
|
||||
"pallet-identity",
|
||||
"pallet-im-online",
|
||||
|
|
@ -2541,7 +2542,6 @@ dependencies = [
|
|||
"pallet-transaction-payment",
|
||||
"pallet-transaction-payment-rpc-runtime-api",
|
||||
"pallet-utility",
|
||||
"pallet-validator-set",
|
||||
"parity-scale-codec",
|
||||
"polkadot-primitives",
|
||||
"polkadot-runtime-common",
|
||||
|
|
@ -2612,6 +2612,7 @@ dependencies = [
|
|||
"pallet-ethereum",
|
||||
"pallet-evm",
|
||||
"pallet-evm-chain-id",
|
||||
"pallet-external-validators",
|
||||
"pallet-grandpa",
|
||||
"pallet-identity",
|
||||
"pallet-im-online",
|
||||
|
|
@ -2629,7 +2630,6 @@ dependencies = [
|
|||
"pallet-transaction-payment",
|
||||
"pallet-transaction-payment-rpc-runtime-api",
|
||||
"pallet-utility",
|
||||
"pallet-validator-set",
|
||||
"parity-scale-codec",
|
||||
"polkadot-primitives",
|
||||
"polkadot-runtime-common",
|
||||
|
|
@ -2811,7 +2811,7 @@ version = "0.1.0"
|
|||
dependencies = [
|
||||
"frame-support",
|
||||
"frame-system",
|
||||
"pallet-validator-set",
|
||||
"pallet-external-validators",
|
||||
"parity-scale-codec",
|
||||
"snowbridge-core 0.2.0",
|
||||
"snowbridge-inbound-queue-primitives",
|
||||
|
|
@ -7614,6 +7614,28 @@ dependencies = [
|
|||
"scale-info",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pallet-external-validators"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"frame-benchmarking",
|
||||
"frame-support",
|
||||
"frame-system",
|
||||
"impl-trait-for-tuples",
|
||||
"log",
|
||||
"pallet-balances",
|
||||
"pallet-session",
|
||||
"pallet-timestamp",
|
||||
"parity-scale-codec",
|
||||
"rand 0.8.5",
|
||||
"scale-info",
|
||||
"sp-core",
|
||||
"sp-io",
|
||||
"sp-runtime",
|
||||
"sp-staking",
|
||||
"sp-std",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pallet-fast-unstake"
|
||||
version = "38.1.0"
|
||||
|
|
@ -7977,23 +7999,6 @@ dependencies = [
|
|||
"sp-runtime",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pallet-validator-set"
|
||||
version = "1.0.0"
|
||||
dependencies = [
|
||||
"frame-benchmarking",
|
||||
"frame-support",
|
||||
"frame-system",
|
||||
"log",
|
||||
"pallet-session",
|
||||
"parity-scale-codec",
|
||||
"scale-info",
|
||||
"sp-core",
|
||||
"sp-io",
|
||||
"sp-runtime",
|
||||
"sp-staking",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pallet-vesting"
|
||||
version = "39.1.0"
|
||||
|
|
|
|||
|
|
@ -24,8 +24,8 @@ datahaven-runtime-common = { path = "./runtime/common", default-features = false
|
|||
datahaven-stagenet-runtime = { path = "./runtime/stagenet", default-features = false }
|
||||
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-outbound-commitment-store = { path = "./pallets/outbound-commitment-store", default-features = false }
|
||||
pallet-validator-set = { path = "./pallets/validator-set", default-features = false }
|
||||
|
||||
# Crates.io (wasm)
|
||||
alloy-core = { version = "0.8.15", default-features = false }
|
||||
|
|
|
|||
75
operator/pallets/external-validators/Cargo.toml
Normal file
75
operator/pallets/external-validators/Cargo.toml
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
[package]
|
||||
name = "pallet-external-validators"
|
||||
authors = { workspace = true }
|
||||
description = "Simple pallet to store external validators."
|
||||
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 }
|
||||
rand = { workspace = true, optional = true }
|
||||
scale-info = { workspace = true, features = [ "derive" ] }
|
||||
|
||||
frame-support = { workspace = true }
|
||||
frame-system = { workspace = true }
|
||||
impl-trait-for-tuples = { 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-session = { workspace = true, features = [ "historical" ] }
|
||||
|
||||
[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",
|
||||
"log/std",
|
||||
"pallet-balances/std",
|
||||
"pallet-session/std",
|
||||
"pallet-timestamp/std",
|
||||
"parity-scale-codec/std",
|
||||
"rand?/std",
|
||||
"scale-info/std",
|
||||
"sp-core/std",
|
||||
"sp-io/std",
|
||||
"sp-runtime/std",
|
||||
"sp-staking/std",
|
||||
"sp-std/std",
|
||||
]
|
||||
runtime-benchmarks = [
|
||||
"frame-benchmarking/runtime-benchmarks",
|
||||
"frame-support/runtime-benchmarks",
|
||||
"frame-system/runtime-benchmarks",
|
||||
"pallet-balances/runtime-benchmarks",
|
||||
"pallet-timestamp/runtime-benchmarks",
|
||||
"rand",
|
||||
"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",
|
||||
"sp-runtime/try-runtime",
|
||||
]
|
||||
252
operator/pallets/external-validators/src/benchmarking.rs
Normal file
252
operator/pallets/external-validators/src/benchmarking.rs
Normal file
|
|
@ -0,0 +1,252 @@
|
|||
// 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/>
|
||||
|
||||
//! Benchmarking setup for pallet_external_validators
|
||||
|
||||
use super::*;
|
||||
|
||||
#[allow(unused)]
|
||||
use crate::Pallet as ExternalValidators;
|
||||
use {
|
||||
frame_benchmarking::{account, v2::*, BenchmarkError},
|
||||
frame_support::traits::{Currency, EnsureOrigin, Get},
|
||||
frame_system::{EventRecord, RawOrigin},
|
||||
pallet_session::{self as session, SessionManager},
|
||||
rand::{RngCore, SeedableRng},
|
||||
sp_runtime::{codec, traits::Convert},
|
||||
sp_std::prelude::*,
|
||||
};
|
||||
const SEED: u32 = 0;
|
||||
|
||||
fn assert_last_event<T: Config>(generic_event: <T as Config>::RuntimeEvent) {
|
||||
let events = frame_system::Pallet::<T>::events();
|
||||
let system_event: <T as frame_system::Config>::RuntimeEvent = generic_event.into();
|
||||
// compare to the last event record
|
||||
let EventRecord { event, .. } = &events[events.len() - 1];
|
||||
assert_eq!(event, &system_event);
|
||||
}
|
||||
|
||||
fn create_funded_user<T: Config + pallet_balances::Config>(
|
||||
string: &'static str,
|
||||
n: u32,
|
||||
balance_factor: u32,
|
||||
) -> T::AccountId {
|
||||
let user = account(string, n, SEED);
|
||||
let balance = <pallet_balances::Pallet<T> as Currency<T::AccountId>>::minimum_balance()
|
||||
* balance_factor.into();
|
||||
let _ = <pallet_balances::Pallet<T> as Currency<T::AccountId>>::make_free_balance_be(
|
||||
&user, balance,
|
||||
);
|
||||
user
|
||||
}
|
||||
|
||||
struct InputFromRng<'a, T>(&'a mut T);
|
||||
impl<'a, T: RngCore> codec::Input for InputFromRng<'a, T> {
|
||||
fn remaining_len(&mut self) -> Result<Option<usize>, codec::Error> {
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
fn read(&mut self, into: &mut [u8]) -> Result<(), codec::Error> {
|
||||
self.0.fill_bytes(into);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn keys<T: Config + session::Config>(c: u32) -> <T as session::Config>::Keys {
|
||||
let mut rng = rand::rngs::StdRng::seed_from_u64(u64::from(c));
|
||||
|
||||
Decode::decode(&mut InputFromRng(&mut rng)).unwrap()
|
||||
}
|
||||
|
||||
fn invulnerable<T: Config + session::Config + pallet_balances::Config>(
|
||||
c: u32,
|
||||
) -> (
|
||||
T::AccountId,
|
||||
<T as Config>::ValidatorId,
|
||||
<T as session::Config>::Keys,
|
||||
) {
|
||||
let funded_user = create_funded_user::<T>("candidate", c, 100);
|
||||
let collator_id = <T as Config>::ValidatorIdOf::convert(funded_user.clone())
|
||||
.expect("Converstion of account id of collator id failed.");
|
||||
(funded_user, collator_id, keys::<T>(c))
|
||||
}
|
||||
|
||||
fn invulnerables<
|
||||
T: Config + frame_system::Config + pallet_session::Config + pallet_balances::Config,
|
||||
>(
|
||||
count: u32,
|
||||
) -> Vec<(T::AccountId, <T as Config>::ValidatorId)> {
|
||||
let invulnerables = (0..count).map(|c| invulnerable::<T>(c)).collect::<Vec<_>>();
|
||||
|
||||
for (who, _collator_id, keys) in invulnerables.clone() {
|
||||
<session::Pallet<T>>::set_keys(RawOrigin::Signed(who).into(), keys, Vec::new()).unwrap();
|
||||
}
|
||||
|
||||
invulnerables
|
||||
.into_iter()
|
||||
.map(|(who, collator_id, _)| (who, collator_id))
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[allow(clippy::multiple_bound_locations)]
|
||||
#[benchmarks(where T: session::Config + pallet_balances::Config)]
|
||||
mod benchmarks {
|
||||
use super::*;
|
||||
|
||||
#[benchmark]
|
||||
fn skip_external_validators() -> Result<(), BenchmarkError> {
|
||||
let origin =
|
||||
T::UpdateOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?;
|
||||
|
||||
#[extrinsic_call]
|
||||
_(origin as T::RuntimeOrigin, true);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[benchmark]
|
||||
fn add_whitelisted(
|
||||
b: Linear<1, { T::MaxWhitelistedValidators::get() - 1 }>,
|
||||
) -> Result<(), BenchmarkError> {
|
||||
let origin =
|
||||
T::UpdateOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?;
|
||||
|
||||
// now we need to fill up invulnerables
|
||||
let invulnerables = invulnerables::<T>(b);
|
||||
|
||||
let (_account_ids, collator_ids): (Vec<T::AccountId>, Vec<<T as Config>::ValidatorId>) =
|
||||
invulnerables.into_iter().unzip();
|
||||
|
||||
let invulnerables: frame_support::BoundedVec<_, T::MaxWhitelistedValidators> =
|
||||
frame_support::BoundedVec::try_from(collator_ids).unwrap();
|
||||
<WhitelistedValidators<T>>::put(invulnerables);
|
||||
|
||||
let (new_invulnerable, _collator_id, keys) = invulnerable::<T>(b + 1);
|
||||
<session::Pallet<T>>::set_keys(
|
||||
RawOrigin::Signed(new_invulnerable.clone()).into(),
|
||||
keys,
|
||||
Vec::new(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
#[extrinsic_call]
|
||||
_(origin as T::RuntimeOrigin, new_invulnerable.clone());
|
||||
|
||||
assert_last_event::<T>(
|
||||
Event::WhitelistedValidatorAdded {
|
||||
account_id: new_invulnerable,
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[benchmark]
|
||||
fn remove_whitelisted(
|
||||
b: Linear<{ 1 }, { T::MaxWhitelistedValidators::get() }>,
|
||||
) -> Result<(), BenchmarkError> {
|
||||
let origin =
|
||||
T::UpdateOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?;
|
||||
let invulnerables = invulnerables::<T>(b);
|
||||
|
||||
let (account_ids, collator_ids): (Vec<T::AccountId>, Vec<<T as Config>::ValidatorId>) =
|
||||
invulnerables.into_iter().unzip();
|
||||
|
||||
let invulnerables: frame_support::BoundedVec<_, T::MaxWhitelistedValidators> =
|
||||
frame_support::BoundedVec::try_from(collator_ids).unwrap();
|
||||
<WhitelistedValidators<T>>::put(invulnerables);
|
||||
|
||||
let to_remove = account_ids.last().unwrap().clone();
|
||||
|
||||
#[extrinsic_call]
|
||||
_(origin as T::RuntimeOrigin, to_remove.clone());
|
||||
|
||||
assert_last_event::<T>(
|
||||
Event::WhitelistedValidatorRemoved {
|
||||
account_id: to_remove,
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[benchmark]
|
||||
fn force_era() -> Result<(), BenchmarkError> {
|
||||
let origin =
|
||||
T::UpdateOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?;
|
||||
|
||||
#[extrinsic_call]
|
||||
_(origin as T::RuntimeOrigin, Forcing::ForceNew);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[benchmark]
|
||||
fn set_external_validators() -> Result<(), BenchmarkError> {
|
||||
let origin =
|
||||
T::UpdateOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?;
|
||||
|
||||
// Insert 4 external, the number should not be critical as its not a map
|
||||
let invulnerables = invulnerables::<T>(4);
|
||||
|
||||
let (_account_ids, validator_ids): (Vec<T::AccountId>, Vec<<T as Config>::ValidatorId>) =
|
||||
invulnerables.into_iter().unzip();
|
||||
|
||||
#[extrinsic_call]
|
||||
_(origin as T::RuntimeOrigin, validator_ids, 0);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// worst case for new session.
|
||||
#[benchmark]
|
||||
fn new_session(
|
||||
r: Linear<1, { T::MaxWhitelistedValidators::get() }>,
|
||||
) -> Result<(), BenchmarkError> {
|
||||
// start fresh
|
||||
WhitelistedValidators::<T>::kill();
|
||||
|
||||
let origin =
|
||||
T::UpdateOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?;
|
||||
|
||||
frame_system::Pallet::<T>::set_block_number(0u32.into());
|
||||
// now we need to fill up invulnerables
|
||||
let invulnerables = invulnerables::<T>(r);
|
||||
|
||||
let (account_ids, _collator_ids): (Vec<T::AccountId>, Vec<<T as Config>::ValidatorId>) =
|
||||
invulnerables.into_iter().unzip();
|
||||
|
||||
for account in account_ids {
|
||||
<ExternalValidators<T>>::add_whitelisted(origin.clone(), account)
|
||||
.expect("add whitelisted failed");
|
||||
}
|
||||
|
||||
let new_era_session = T::SessionsPerEra::get();
|
||||
|
||||
#[block]
|
||||
{
|
||||
<ExternalValidators<T> as SessionManager<_>>::new_session(new_era_session);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
impl_benchmark_test_suite!(
|
||||
ExternalValidators,
|
||||
crate::mock::new_test_ext(),
|
||||
crate::mock::Test,
|
||||
);
|
||||
}
|
||||
717
operator/pallets/external-validators/src/lib.rs
Normal file
717
operator/pallets/external-validators/src/lib.rs
Normal file
|
|
@ -0,0 +1,717 @@
|
|||
// 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/>
|
||||
|
||||
//! ExternalValidators pallet.
|
||||
//!
|
||||
//! A pallet to manage external validators for a solochain.
|
||||
//!
|
||||
//! ## Terminology
|
||||
//!
|
||||
//! - WhitelistedValidators: Fixed validators set by root/governance. Have priority over the external validators.
|
||||
//! Are not rewarded.
|
||||
//! - ExternalValidators: Validators set using storage proofs from another blockchain. Can be disabled by setting
|
||||
//! `SkipExternalValidators` to true.
|
||||
//!
|
||||
//! Validators only change once per era. By default the era changes after a fixed number of sessions, but new eras
|
||||
//! can be forced or disabled using a root extrinsic.
|
||||
//!
|
||||
//! The structure of this pallet and the concept of eras is inspired by `pallet_staking` from Polkadot.
|
||||
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
|
||||
pub use pallet::*;
|
||||
use {
|
||||
frame_support::pallet_prelude::Weight,
|
||||
log::log,
|
||||
parity_scale_codec::{Decode, Encode, MaxEncodedLen},
|
||||
scale_info::TypeInfo,
|
||||
sp_runtime::{traits::Get, RuntimeDebug},
|
||||
sp_staking::SessionIndex,
|
||||
sp_std::{collections::btree_set::BTreeSet, vec::Vec},
|
||||
traits::{
|
||||
ActiveEraInfo, EraIndex, EraIndexProvider, ExternalIndexProvider, InvulnerablesProvider,
|
||||
OnEraEnd, OnEraStart, ValidatorProvider,
|
||||
},
|
||||
};
|
||||
|
||||
#[cfg(test)]
|
||||
mod mock;
|
||||
mod traits;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
mod benchmarking;
|
||||
pub mod weights;
|
||||
|
||||
#[frame_support::pallet]
|
||||
pub mod pallet {
|
||||
pub use crate::weights::WeightInfo;
|
||||
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
use frame_support::traits::Currency;
|
||||
use {
|
||||
super::*,
|
||||
frame_support::{
|
||||
dispatch::DispatchResultWithPostInfo,
|
||||
pallet_prelude::*,
|
||||
traits::{EnsureOrigin, UnixTime, ValidatorRegistration},
|
||||
BoundedVec, DefaultNoBound,
|
||||
},
|
||||
frame_system::pallet_prelude::*,
|
||||
sp_runtime::{traits::Convert, SaturatedConversion},
|
||||
sp_std::vec::Vec,
|
||||
};
|
||||
|
||||
/// Configure the pallet by specifying the parameters and types on which it depends.
|
||||
#[pallet::config]
|
||||
pub trait Config: frame_system::Config {
|
||||
/// Overarching event type.
|
||||
type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
|
||||
|
||||
/// Origin that can dictate updating parameters of this pallet.
|
||||
type UpdateOrigin: EnsureOrigin<Self::RuntimeOrigin>;
|
||||
|
||||
/// Number of eras to keep in history.
|
||||
///
|
||||
/// Following information is kept for eras in `[current_era -
|
||||
/// HistoryDepth, current_era]`: `ErasStartSessionIndex`
|
||||
///
|
||||
/// Must be more than the number of eras delayed by session.
|
||||
/// I.e. active era must always be in history. I.e. `active_era >
|
||||
/// current_era - history_depth` must be guaranteed.
|
||||
///
|
||||
/// If migrating an existing pallet from storage value to config value,
|
||||
/// this should be set to same value or greater as in storage.
|
||||
#[pallet::constant]
|
||||
type HistoryDepth: Get<u32>;
|
||||
|
||||
/// Maximum number of whitelisted validators.
|
||||
#[pallet::constant]
|
||||
type MaxWhitelistedValidators: Get<u32>;
|
||||
|
||||
/// Maximum number of external validators.
|
||||
#[pallet::constant]
|
||||
type MaxExternalValidators: Get<u32>;
|
||||
|
||||
/// A stable ID for a validator.
|
||||
type ValidatorId: Member
|
||||
+ Parameter
|
||||
+ Ord
|
||||
+ MaybeSerializeDeserialize
|
||||
+ MaxEncodedLen
|
||||
+ TryFrom<Self::AccountId>;
|
||||
|
||||
/// A conversion from account ID to validator ID.
|
||||
///
|
||||
/// Its cost must be at most one storage read.
|
||||
type ValidatorIdOf: Convert<Self::AccountId, Option<Self::ValidatorId>>;
|
||||
|
||||
/// Validate a user is registered
|
||||
type ValidatorRegistration: ValidatorRegistration<Self::ValidatorId>;
|
||||
|
||||
/// Time used for computing era duration.
|
||||
///
|
||||
/// It is guaranteed to start being called from the first `on_finalize`. Thus value at
|
||||
/// genesis is not used.
|
||||
type UnixTime: UnixTime;
|
||||
|
||||
/// Number of sessions per era.
|
||||
#[pallet::constant]
|
||||
type SessionsPerEra: Get<SessionIndex>;
|
||||
|
||||
type OnEraStart: OnEraStart;
|
||||
type OnEraEnd: OnEraEnd;
|
||||
|
||||
/// The weight information of this pallet.
|
||||
type WeightInfo: WeightInfo;
|
||||
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
type Currency: Currency<Self::AccountId>
|
||||
+ frame_support::traits::fungible::Balanced<Self::AccountId>;
|
||||
}
|
||||
|
||||
#[pallet::pallet]
|
||||
pub struct Pallet<T>(_);
|
||||
|
||||
/// Fixed validators set by root/governance. Have priority over the external validators.
|
||||
#[pallet::storage]
|
||||
pub type WhitelistedValidators<T: Config> =
|
||||
StorageValue<_, BoundedVec<T::ValidatorId, T::MaxWhitelistedValidators>, ValueQuery>;
|
||||
|
||||
/// Copy of `WhitelistedValidators` at the start of this active era.
|
||||
/// Used to check which validators we don't need to reward.
|
||||
#[pallet::storage]
|
||||
pub type WhitelistedValidatorsActiveEra<T: Config> =
|
||||
StorageValue<_, BoundedVec<T::ValidatorId, T::MaxWhitelistedValidators>, ValueQuery>;
|
||||
|
||||
/// Same as `WhitelistedValidatorsActiveEra` but only exists for a brief period of time when the
|
||||
/// next era has been planned but not enacted yet.
|
||||
#[pallet::storage]
|
||||
pub type WhitelistedValidatorsActiveEraPending<T: Config> =
|
||||
StorageValue<_, BoundedVec<T::ValidatorId, T::MaxWhitelistedValidators>, ValueQuery>;
|
||||
|
||||
/// Validators set using storage proofs from another blockchain. Ignored if `SkipExternalValidators` is true.
|
||||
#[pallet::storage]
|
||||
pub type ExternalValidators<T: Config> =
|
||||
StorageValue<_, BoundedVec<T::ValidatorId, T::MaxExternalValidators>, ValueQuery>;
|
||||
|
||||
/// Allow to disable external validators.
|
||||
#[pallet::storage]
|
||||
pub type SkipExternalValidators<T: Config> = StorageValue<_, bool, ValueQuery>;
|
||||
|
||||
/// The current era information, it is either ActiveEra or ActiveEra + 1 if the new era validators have been queued.
|
||||
#[pallet::storage]
|
||||
pub type CurrentEra<T: Config> = StorageValue<_, EraIndex>;
|
||||
|
||||
/// The active era information, it holds index and start.
|
||||
#[pallet::storage]
|
||||
pub type ActiveEra<T: Config> = StorageValue<_, ActiveEraInfo>;
|
||||
|
||||
/// The session index at which the era start for the last [`Config::HistoryDepth`] eras.
|
||||
///
|
||||
/// Note: This tracks the starting session (i.e. session index when era start being active)
|
||||
/// for the eras in `[CurrentEra - HISTORY_DEPTH, CurrentEra]`.
|
||||
#[pallet::storage]
|
||||
pub type ErasStartSessionIndex<T> = StorageMap<_, Twox64Concat, EraIndex, SessionIndex>;
|
||||
|
||||
/// Mode of era forcing.
|
||||
#[pallet::storage]
|
||||
pub type ForceEra<T> = StorageValue<_, Forcing, ValueQuery>;
|
||||
|
||||
/// Latest received external index. This index can be a timestamp
|
||||
/// a set-id, an epoch or in general anything that identifies
|
||||
/// a particular set of validators selected at a given point in time
|
||||
#[pallet::storage]
|
||||
pub type ExternalIndex<T> = StorageValue<_, u64, ValueQuery>;
|
||||
|
||||
/// Pending external index to be applied in the upcoming era
|
||||
#[pallet::storage]
|
||||
pub type PendingExternalIndex<T> = StorageValue<_, u64, ValueQuery>;
|
||||
|
||||
/// Current external index attached to the latest validators
|
||||
#[pallet::storage]
|
||||
pub type CurrentExternalIndex<T> = StorageValue<_, u64, ValueQuery>;
|
||||
|
||||
#[pallet::genesis_config]
|
||||
#[derive(DefaultNoBound)]
|
||||
pub struct GenesisConfig<T: Config> {
|
||||
pub skip_external_validators: bool,
|
||||
pub whitelisted_validators: Vec<T::ValidatorId>,
|
||||
pub external_validators: Vec<T::ValidatorId>,
|
||||
}
|
||||
|
||||
#[pallet::genesis_build]
|
||||
impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
|
||||
fn build(&self) {
|
||||
let duplicate_validators = self
|
||||
.whitelisted_validators
|
||||
.iter()
|
||||
// T::ValidatorId does not impl Ord or Hash so we cannot collect into set directly,
|
||||
// but we can check for duplicates if we encode them first.
|
||||
.map(|x| x.encode())
|
||||
.collect::<sp_std::collections::btree_set::BTreeSet<_>>();
|
||||
assert!(
|
||||
duplicate_validators.len() == self.whitelisted_validators.len(),
|
||||
"duplicate validators in genesis."
|
||||
);
|
||||
|
||||
let bounded_validators = BoundedVec::<_, T::MaxWhitelistedValidators>::try_from(
|
||||
self.whitelisted_validators.clone(),
|
||||
)
|
||||
.expect("genesis validators are more than T::MaxWhitelistedValidators");
|
||||
|
||||
let bounded_external_validators = BoundedVec::<_, T::MaxExternalValidators>::try_from(
|
||||
self.external_validators.clone(),
|
||||
)
|
||||
.expect("genesis external validators are more than T::MaxExternalValidators");
|
||||
|
||||
<SkipExternalValidators<T>>::put(self.skip_external_validators);
|
||||
<WhitelistedValidators<T>>::put(&bounded_validators);
|
||||
<WhitelistedValidatorsActiveEra<T>>::put(&bounded_validators);
|
||||
<WhitelistedValidatorsActiveEraPending<T>>::put(&bounded_validators);
|
||||
<ExternalValidators<T>>::put(&bounded_external_validators);
|
||||
}
|
||||
}
|
||||
|
||||
#[pallet::event]
|
||||
#[pallet::generate_deposit(pub(super) fn deposit_event)]
|
||||
pub enum Event<T: Config> {
|
||||
/// A new whitelisted validator was added.
|
||||
WhitelistedValidatorAdded { account_id: T::AccountId },
|
||||
/// A whitelisted validator was removed.
|
||||
WhitelistedValidatorRemoved { account_id: T::AccountId },
|
||||
/// A new era has started.
|
||||
NewEra { era: EraIndex },
|
||||
/// A new force era mode was set.
|
||||
ForceEra { mode: Forcing },
|
||||
/// External validators were set.
|
||||
ExternalValidatorsSet {
|
||||
validators: Vec<T::ValidatorId>,
|
||||
external_index: u64,
|
||||
},
|
||||
}
|
||||
|
||||
#[pallet::error]
|
||||
pub enum Error<T> {
|
||||
/// There are too many whitelisted validators.
|
||||
TooManyWhitelisted,
|
||||
/// Account is already whitelisted.
|
||||
AlreadyWhitelisted,
|
||||
/// Account is not whitelisted.
|
||||
NotWhitelisted,
|
||||
/// Account does not have keys registered
|
||||
NoKeysRegistered,
|
||||
/// Unable to derive validator id from account id
|
||||
UnableToDeriveValidatorId,
|
||||
}
|
||||
|
||||
#[pallet::call]
|
||||
impl<T: Config> Pallet<T> {
|
||||
/// Allow to ignore external validators and use only whitelisted ones.
|
||||
///
|
||||
/// The origin for this call must be the `UpdateOrigin`.
|
||||
#[pallet::call_index(0)]
|
||||
#[pallet::weight(T::WeightInfo::skip_external_validators())]
|
||||
pub fn skip_external_validators(origin: OriginFor<T>, skip: bool) -> DispatchResult {
|
||||
T::UpdateOrigin::ensure_origin(origin)?;
|
||||
|
||||
<SkipExternalValidators<T>>::put(skip);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Add a new account `who` to the list of `WhitelistedValidators`.
|
||||
///
|
||||
/// The origin for this call must be the `UpdateOrigin`.
|
||||
#[pallet::call_index(1)]
|
||||
#[pallet::weight(T::WeightInfo::add_whitelisted(
|
||||
T::MaxWhitelistedValidators::get().saturating_sub(1),
|
||||
))]
|
||||
pub fn add_whitelisted(
|
||||
origin: OriginFor<T>,
|
||||
who: T::AccountId,
|
||||
) -> DispatchResultWithPostInfo {
|
||||
T::UpdateOrigin::ensure_origin(origin)?;
|
||||
// don't let one unprepared validator ruin things for everyone.
|
||||
let maybe_validator_id = T::ValidatorIdOf::convert(who.clone())
|
||||
.filter(T::ValidatorRegistration::is_registered);
|
||||
|
||||
let validator_id = maybe_validator_id.ok_or(Error::<T>::NoKeysRegistered)?;
|
||||
|
||||
<WhitelistedValidators<T>>::try_mutate(|whitelisted| -> DispatchResult {
|
||||
if whitelisted.contains(&validator_id) {
|
||||
Err(Error::<T>::AlreadyWhitelisted)?;
|
||||
}
|
||||
whitelisted
|
||||
.try_push(validator_id.clone())
|
||||
.map_err(|_| Error::<T>::TooManyWhitelisted)?;
|
||||
Ok(())
|
||||
})?;
|
||||
|
||||
Self::deposit_event(Event::WhitelistedValidatorAdded { account_id: who });
|
||||
|
||||
let weight_used = <T as Config>::WeightInfo::add_whitelisted(
|
||||
WhitelistedValidators::<T>::decode_len()
|
||||
.unwrap_or_default()
|
||||
.try_into()
|
||||
.unwrap_or(T::MaxWhitelistedValidators::get().saturating_sub(1)),
|
||||
);
|
||||
|
||||
Ok(Some(weight_used).into())
|
||||
}
|
||||
|
||||
/// Remove an account `who` from the list of `WhitelistedValidators` collators.
|
||||
///
|
||||
/// The origin for this call must be the `UpdateOrigin`.
|
||||
#[pallet::call_index(2)]
|
||||
#[pallet::weight(T::WeightInfo::remove_whitelisted(T::MaxWhitelistedValidators::get()))]
|
||||
pub fn remove_whitelisted(origin: OriginFor<T>, who: T::AccountId) -> DispatchResult {
|
||||
T::UpdateOrigin::ensure_origin(origin)?;
|
||||
|
||||
let validator_id = T::ValidatorIdOf::convert(who.clone())
|
||||
.ok_or(Error::<T>::UnableToDeriveValidatorId)?;
|
||||
|
||||
<WhitelistedValidators<T>>::try_mutate(|whitelisted| -> DispatchResult {
|
||||
let pos = whitelisted
|
||||
.iter()
|
||||
.position(|x| x == &validator_id)
|
||||
.ok_or(Error::<T>::NotWhitelisted)?;
|
||||
whitelisted.remove(pos);
|
||||
Ok(())
|
||||
})?;
|
||||
|
||||
Self::deposit_event(Event::WhitelistedValidatorRemoved { account_id: who });
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Force when the next era will start. Possible values: next session, never, same as always.
|
||||
#[pallet::call_index(3)]
|
||||
#[pallet::weight(T::WeightInfo::force_era())]
|
||||
pub fn force_era(origin: OriginFor<T>, mode: Forcing) -> DispatchResult {
|
||||
T::UpdateOrigin::ensure_origin(origin)?;
|
||||
Self::set_force_era(mode);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Manually set external validators. Should only be needed for tests, validators are set
|
||||
/// automatically by the bridge.
|
||||
#[pallet::call_index(4)]
|
||||
#[pallet::weight(T::WeightInfo::set_external_validators())]
|
||||
pub fn set_external_validators(
|
||||
origin: OriginFor<T>,
|
||||
validators: Vec<T::ValidatorId>,
|
||||
external_index: u64,
|
||||
) -> DispatchResult {
|
||||
T::UpdateOrigin::ensure_origin(origin)?;
|
||||
|
||||
Self::set_external_validators_inner(validators, external_index)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config> Pallet<T> {
|
||||
pub fn set_external_validators_inner(
|
||||
validators: Vec<T::ValidatorId>,
|
||||
external_index: u64,
|
||||
) -> DispatchResult {
|
||||
// If more validators than max, take the first n
|
||||
let validators = BoundedVec::truncate_from(validators);
|
||||
<ExternalValidators<T>>::put(&validators);
|
||||
<ExternalIndex<T>>::put(external_index);
|
||||
|
||||
Self::deposit_event(Event::<T>::ExternalValidatorsSet {
|
||||
validators: validators.into_inner(),
|
||||
external_index,
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Helper to set a new `ForceEra` mode.
|
||||
pub(crate) fn set_force_era(mode: Forcing) {
|
||||
log::info!("Setting force era mode {:?}.", mode);
|
||||
ForceEra::<T>::put(mode);
|
||||
Self::deposit_event(Event::<T>::ForceEra { mode });
|
||||
}
|
||||
|
||||
pub fn whitelisted_validators() -> Vec<T::ValidatorId> {
|
||||
<WhitelistedValidators<T>>::get().into()
|
||||
}
|
||||
|
||||
pub fn active_era() -> Option<ActiveEraInfo> {
|
||||
<ActiveEra<T>>::get()
|
||||
}
|
||||
|
||||
pub fn current_era() -> Option<EraIndex> {
|
||||
<CurrentEra<T>>::get()
|
||||
}
|
||||
|
||||
pub fn eras_start_session_index(era: EraIndex) -> Option<u32> {
|
||||
<ErasStartSessionIndex<T>>::get(era)
|
||||
}
|
||||
|
||||
/// Returns validators for the next session. Whitelisted validators first, then external validators.
|
||||
/// The returned list is deduplicated, but the order is respected.
|
||||
/// If `SkipExternalValidators` is true, this function will ignore external validators.
|
||||
pub fn validators() -> Vec<T::ValidatorId> {
|
||||
let mut validators: Vec<_> = WhitelistedValidators::<T>::get().into();
|
||||
|
||||
if !SkipExternalValidators::<T>::get() {
|
||||
validators.extend(ExternalValidators::<T>::get())
|
||||
}
|
||||
|
||||
remove_duplicates(validators)
|
||||
}
|
||||
|
||||
/// Plan a new session potentially trigger a new era.
|
||||
pub(crate) fn new_session(session_index: SessionIndex) -> Option<Vec<T::ValidatorId>> {
|
||||
if let Some(current_era) = Self::current_era() {
|
||||
// Initial era has been set.
|
||||
let current_era_start_session_index = Self::eras_start_session_index(current_era)
|
||||
.unwrap_or_else(|| {
|
||||
frame_support::print(
|
||||
"Error: start_session_index must be set for current_era",
|
||||
);
|
||||
0
|
||||
});
|
||||
|
||||
let era_length = session_index.saturating_sub(current_era_start_session_index); // Must never happen.
|
||||
|
||||
match ForceEra::<T>::get() {
|
||||
// Will be set to `NotForcing` again if a new era has been triggered.
|
||||
Forcing::ForceNew => (),
|
||||
// Short circuit to `try_trigger_new_era`.
|
||||
Forcing::ForceAlways => (),
|
||||
// Only go to `try_trigger_new_era` if deadline reached.
|
||||
Forcing::NotForcing if era_length >= T::SessionsPerEra::get() => (),
|
||||
_ => {
|
||||
// Either `Forcing::ForceNone`,
|
||||
// or `Forcing::NotForcing if era_length < T::SessionsPerEra::get()`.
|
||||
return None;
|
||||
}
|
||||
}
|
||||
|
||||
// New era.
|
||||
let maybe_new_era_validators = Self::try_trigger_new_era(session_index);
|
||||
if maybe_new_era_validators.is_some()
|
||||
&& matches!(ForceEra::<T>::get(), Forcing::ForceNew)
|
||||
{
|
||||
Self::set_force_era(Forcing::NotForcing);
|
||||
}
|
||||
|
||||
maybe_new_era_validators
|
||||
} else {
|
||||
// Set initial era.
|
||||
log!(log::Level::Debug, "Starting the first era.");
|
||||
Self::try_trigger_new_era(session_index)
|
||||
}
|
||||
}
|
||||
|
||||
/// Start a session potentially starting an era.
|
||||
pub(crate) fn start_session(start_session: SessionIndex) {
|
||||
let next_active_era = Self::active_era()
|
||||
.map(|e| e.index.saturating_add(1))
|
||||
.unwrap_or(0);
|
||||
// This is only `Some` when current era has already progressed to the next era, while the
|
||||
// active era is one behind (i.e. in the *last session of the active era*, or *first session
|
||||
// of the new current era*, depending on how you look at it).
|
||||
if let Some(next_active_era_start_session_index) =
|
||||
Self::eras_start_session_index(next_active_era)
|
||||
{
|
||||
if next_active_era_start_session_index == start_session {
|
||||
Self::start_era(start_session);
|
||||
} else if next_active_era_start_session_index < start_session {
|
||||
// This arm should never happen, but better handle it than to stall the pallet.
|
||||
frame_support::print("Warning: A session appears to have been skipped.");
|
||||
Self::start_era(start_session);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// End a session potentially ending an era.
|
||||
pub(crate) fn end_session(session_index: SessionIndex) {
|
||||
if let Some(active_era) = Self::active_era() {
|
||||
if let Some(next_active_era_start_session_index) =
|
||||
Self::eras_start_session_index(active_era.index.saturating_add(1))
|
||||
{
|
||||
if next_active_era_start_session_index == session_index.saturating_add(1) {
|
||||
Self::end_era(active_era, session_index);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Start a new era. It does:
|
||||
/// * Increment `active_era.index`,
|
||||
/// * reset `active_era.start`,
|
||||
/// * emit `NewEra` event,
|
||||
/// * call `OnEraStart` hook,
|
||||
pub(crate) fn start_era(start_session: SessionIndex) {
|
||||
let active_era = ActiveEra::<T>::mutate(|active_era| {
|
||||
let new_index = active_era
|
||||
.as_ref()
|
||||
.map(|info| info.index.saturating_add(1))
|
||||
.unwrap_or(0);
|
||||
*active_era = Some(ActiveEraInfo {
|
||||
index: new_index,
|
||||
// Set new active era start in next `on_finalize`. To guarantee usage of `Time`
|
||||
start: None,
|
||||
});
|
||||
new_index
|
||||
});
|
||||
WhitelistedValidatorsActiveEra::<T>::put(
|
||||
WhitelistedValidatorsActiveEraPending::<T>::take(),
|
||||
);
|
||||
let external_idx = PendingExternalIndex::<T>::take();
|
||||
CurrentExternalIndex::<T>::put(external_idx);
|
||||
Self::deposit_event(Event::NewEra { era: active_era });
|
||||
T::OnEraStart::on_era_start(active_era, start_session, external_idx);
|
||||
}
|
||||
|
||||
/// End era. It does:
|
||||
/// * call `OnEraEnd` hook,
|
||||
pub(crate) fn end_era(active_era: ActiveEraInfo, _session_index: SessionIndex) {
|
||||
// Note: active_era.start can be None if end era is called during genesis config.
|
||||
T::OnEraEnd::on_era_end(active_era.index);
|
||||
}
|
||||
|
||||
/// Plan a new era.
|
||||
///
|
||||
/// * Bump the current era storage (which holds the latest planned era).
|
||||
/// * Store start session index for the new planned era.
|
||||
/// * Clean old era information.
|
||||
///
|
||||
/// Returns the new validator set.
|
||||
pub fn trigger_new_era(start_session_index: SessionIndex) -> Vec<T::ValidatorId> {
|
||||
// Increment or set current era.
|
||||
let new_planned_era = CurrentEra::<T>::mutate(|s| {
|
||||
*s = Some(s.map(|s| s.saturating_add(1)).unwrap_or(0));
|
||||
s.unwrap()
|
||||
});
|
||||
ErasStartSessionIndex::<T>::insert(&new_planned_era, &start_session_index);
|
||||
|
||||
// Clean old era information.
|
||||
if let Some(old_era) =
|
||||
new_planned_era.checked_sub(T::HistoryDepth::get().saturating_add(1))
|
||||
{
|
||||
Self::clear_era_information(old_era);
|
||||
}
|
||||
|
||||
// Save whitelisted validators for when the era truly changes (start_era)
|
||||
WhitelistedValidatorsActiveEraPending::<T>::put(WhitelistedValidators::<T>::get());
|
||||
// Save the external index for when the era truly changes (start_era)
|
||||
PendingExternalIndex::<T>::put(ExternalIndex::<T>::get());
|
||||
|
||||
// Returns new validators
|
||||
Self::validators()
|
||||
}
|
||||
|
||||
/// Potentially plan a new era.
|
||||
///
|
||||
/// In case a new era is planned, the new validator set is returned.
|
||||
pub(crate) fn try_trigger_new_era(
|
||||
start_session_index: SessionIndex,
|
||||
) -> Option<Vec<T::ValidatorId>> {
|
||||
Some(Self::trigger_new_era(start_session_index))
|
||||
}
|
||||
|
||||
/// Clear all era information for given era.
|
||||
pub(crate) fn clear_era_information(era_index: EraIndex) {
|
||||
ErasStartSessionIndex::<T>::remove(era_index);
|
||||
}
|
||||
}
|
||||
|
||||
#[pallet::hooks]
|
||||
impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
|
||||
fn on_initialize(_now: BlockNumberFor<T>) -> Weight {
|
||||
// just return the weight of the on_finalize.
|
||||
T::DbWeight::get().reads(1)
|
||||
}
|
||||
|
||||
fn on_finalize(_n: BlockNumberFor<T>) {
|
||||
// Set the start of the first era.
|
||||
if let Some(mut active_era) = <ActiveEra<T>>::get() {
|
||||
if active_era.start.is_none() {
|
||||
let now_as_millis_u64 = T::UnixTime::now().as_millis().saturated_into::<u64>();
|
||||
active_era.start = Some(now_as_millis_u64);
|
||||
// This write only ever happens once, we don't include it in the weight in
|
||||
// general
|
||||
ActiveEra::<T>::put(active_era);
|
||||
}
|
||||
}
|
||||
// `on_finalize` weight is tracked in `on_initialize`
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config> ExternalIndexProvider for Pallet<T> {
|
||||
fn get_external_index() -> u64 {
|
||||
CurrentExternalIndex::<T>::get()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Keeps only the first instance of each element in the input vec. Respects ordering of elements.
|
||||
fn remove_duplicates<T: Ord + Clone>(input: Vec<T>) -> Vec<T> {
|
||||
let mut seen = BTreeSet::new();
|
||||
let mut result = Vec::with_capacity(input.len());
|
||||
|
||||
for item in input {
|
||||
if seen.insert(item.clone()) {
|
||||
result.push(item);
|
||||
}
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
impl<T: Config> pallet_session::SessionManager<T::ValidatorId> for Pallet<T> {
|
||||
fn new_session(new_index: SessionIndex) -> Option<Vec<T::ValidatorId>> {
|
||||
log!(log::Level::Trace, "planning new session {}", new_index);
|
||||
Self::new_session(new_index)
|
||||
}
|
||||
fn new_session_genesis(new_index: SessionIndex) -> Option<Vec<T::ValidatorId>> {
|
||||
log!(
|
||||
log::Level::Trace,
|
||||
"planning new session {} at genesis",
|
||||
new_index
|
||||
);
|
||||
Self::new_session(new_index)
|
||||
}
|
||||
fn start_session(start_index: SessionIndex) {
|
||||
log!(log::Level::Trace, "starting session {}", start_index);
|
||||
Self::start_session(start_index)
|
||||
}
|
||||
fn end_session(end_index: SessionIndex) {
|
||||
log!(log::Level::Trace, "ending session {}", end_index);
|
||||
Self::end_session(end_index)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config> pallet_session::historical::SessionManager<T::ValidatorId, ()> for Pallet<T> {
|
||||
fn new_session(new_index: SessionIndex) -> Option<Vec<(T::ValidatorId, ())>> {
|
||||
<Self as pallet_session::SessionManager<_>>::new_session(new_index)
|
||||
.map(|r| r.into_iter().map(|v| (v, Default::default())).collect())
|
||||
}
|
||||
|
||||
fn start_session(start_index: SessionIndex) {
|
||||
<Self as pallet_session::SessionManager<_>>::start_session(start_index)
|
||||
}
|
||||
|
||||
fn end_session(end_index: SessionIndex) {
|
||||
<Self as pallet_session::SessionManager<_>>::end_session(end_index)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config> EraIndexProvider for Pallet<T> {
|
||||
fn active_era() -> ActiveEraInfo {
|
||||
<ActiveEra<T>>::get().unwrap_or(ActiveEraInfo {
|
||||
index: 0,
|
||||
start: None,
|
||||
})
|
||||
}
|
||||
|
||||
fn era_to_session_start(era_index: EraIndex) -> Option<u32> {
|
||||
<ErasStartSessionIndex<T>>::get(era_index)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config> ValidatorProvider<T::ValidatorId> for Pallet<T> {
|
||||
fn validators() -> Vec<T::ValidatorId> {
|
||||
Self::validators()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config> InvulnerablesProvider<T::ValidatorId> for Pallet<T> {
|
||||
fn invulnerables() -> Vec<T::ValidatorId> {
|
||||
Self::whitelisted_validators()
|
||||
}
|
||||
}
|
||||
|
||||
/// Mode of era-forcing.
|
||||
#[derive(
|
||||
Copy, Clone, PartialEq, Eq, Default, Encode, Decode, RuntimeDebug, TypeInfo, MaxEncodedLen,
|
||||
)]
|
||||
pub enum Forcing {
|
||||
/// Not forcing anything - just let whatever happen.
|
||||
#[default]
|
||||
NotForcing,
|
||||
/// Force a new era on the next session start, then reset to `NotForcing` as soon as it is done.
|
||||
ForceNew,
|
||||
/// Avoid a new era indefinitely.
|
||||
ForceNone,
|
||||
/// Force a new era at the end of all sessions indefinitely.
|
||||
ForceAlways,
|
||||
}
|
||||
356
operator/pallets/external-validators/src/mock.rs
Normal file
356
operator/pallets/external-validators/src/mock.rs
Normal file
|
|
@ -0,0 +1,356 @@
|
|||
// 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/>
|
||||
|
||||
use {
|
||||
super::*,
|
||||
crate as pallet_external_validators,
|
||||
frame_support::{
|
||||
assert_ok, ord_parameter_types, parameter_types,
|
||||
traits::{
|
||||
fungible::Mutate, ConstU32, ConstU64, OnFinalize, OnInitialize, ValidatorRegistration,
|
||||
},
|
||||
},
|
||||
frame_system::{self as system, EnsureSignedBy},
|
||||
pallet_balances::AccountData,
|
||||
sp_core::H256,
|
||||
sp_runtime::{
|
||||
testing::UintAuthorityId,
|
||||
traits::{BlakeTwo256, ConvertInto, IdentityLookup, OpaqueKeys},
|
||||
BuildStorage, RuntimeAppPublic,
|
||||
},
|
||||
};
|
||||
|
||||
type Block = frame_system::mocking::MockBlock<Test>;
|
||||
|
||||
// Configure a mock runtime to test the pallet.
|
||||
frame_support::construct_runtime!(
|
||||
pub enum Test
|
||||
{
|
||||
System: frame_system,
|
||||
ExternalValidators: pallet_external_validators,
|
||||
Session: pallet_session,
|
||||
Balances: pallet_balances,
|
||||
Timestamp: pallet_timestamp,
|
||||
Mock: mock_data,
|
||||
}
|
||||
);
|
||||
|
||||
parameter_types! {
|
||||
pub const BlockHashCount: u64 = 250;
|
||||
pub const SS58Prefix: u8 = 42;
|
||||
}
|
||||
|
||||
impl system::Config for Test {
|
||||
type BaseCallFilter = frame_support::traits::Everything;
|
||||
type BlockWeights = ();
|
||||
type BlockLength = ();
|
||||
type DbWeight = ();
|
||||
type RuntimeOrigin = RuntimeOrigin;
|
||||
type RuntimeCall = RuntimeCall;
|
||||
type Hash = H256;
|
||||
type Hashing = BlakeTwo256;
|
||||
type AccountId = u64;
|
||||
type Lookup = IdentityLookup<Self::AccountId>;
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type BlockHashCount = BlockHashCount;
|
||||
type Version = ();
|
||||
type PalletInfo = PalletInfo;
|
||||
type AccountData = AccountData<u64>;
|
||||
type OnNewAccount = ();
|
||||
type OnKilledAccount = ();
|
||||
type SystemWeightInfo = ();
|
||||
type SS58Prefix = SS58Prefix;
|
||||
type OnSetCode = ();
|
||||
type MaxConsumers = frame_support::traits::ConstU32<16>;
|
||||
type Nonce = u64;
|
||||
type Block = Block;
|
||||
type RuntimeTask = ();
|
||||
type SingleBlockMigrations = ();
|
||||
type MultiBlockMigrator = ();
|
||||
type PreInherents = ();
|
||||
type PostInherents = ();
|
||||
type PostTransactions = ();
|
||||
type ExtensionsWeightInfo = ();
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
pub const ExistentialDeposit: u64 = 5;
|
||||
pub const MaxReserves: u32 = 50;
|
||||
}
|
||||
|
||||
impl pallet_balances::Config for Test {
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type WeightInfo = ();
|
||||
type Balance = u64;
|
||||
type DustRemoval = ();
|
||||
type ExistentialDeposit = ExistentialDeposit;
|
||||
type AccountStore = System;
|
||||
type ReserveIdentifier = [u8; 8];
|
||||
type RuntimeHoldReason = ();
|
||||
type RuntimeFreezeReason = ();
|
||||
type FreezeIdentifier = ();
|
||||
type MaxLocks = ();
|
||||
type MaxReserves = MaxReserves;
|
||||
type MaxFreezes = ConstU32<0>;
|
||||
type DoneSlashHandler = ();
|
||||
}
|
||||
|
||||
impl pallet_timestamp::Config for Test {
|
||||
type Moment = u64;
|
||||
type OnTimestampSet = ();
|
||||
type MinimumPeriod = ConstU64<5>;
|
||||
type WeightInfo = ();
|
||||
}
|
||||
|
||||
ord_parameter_types! {
|
||||
pub const RootAccount: u64 = 777;
|
||||
}
|
||||
|
||||
pub struct IsRegistered;
|
||||
impl ValidatorRegistration<u64> for IsRegistered {
|
||||
fn is_registered(id: &u64) -> bool {
|
||||
*id != 42u64
|
||||
}
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
pub const SessionsPerEra: SessionIndex = 6;
|
||||
}
|
||||
|
||||
impl Config for Test {
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type UpdateOrigin = EnsureSignedBy<RootAccount, u64>;
|
||||
type HistoryDepth = ConstU32<84>;
|
||||
type MaxWhitelistedValidators = ConstU32<20>;
|
||||
type MaxExternalValidators = ConstU32<20>;
|
||||
type ValidatorId = <Self as frame_system::Config>::AccountId;
|
||||
type ValidatorIdOf = ConvertInto;
|
||||
type ValidatorRegistration = IsRegistered;
|
||||
type UnixTime = Timestamp;
|
||||
type SessionsPerEra = SessionsPerEra;
|
||||
type OnEraStart = Mock;
|
||||
type OnEraEnd = Mock;
|
||||
type WeightInfo = ();
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
type Currency = Balances;
|
||||
}
|
||||
|
||||
sp_runtime::impl_opaque_keys! {
|
||||
pub struct MockSessionKeys {
|
||||
// a key for aura authoring
|
||||
pub aura: UintAuthorityId,
|
||||
}
|
||||
}
|
||||
|
||||
impl From<UintAuthorityId> for MockSessionKeys {
|
||||
fn from(aura: sp_runtime::testing::UintAuthorityId) -> Self {
|
||||
Self { aura }
|
||||
}
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
pub static SessionHandlerCollators: Vec<u64> = Vec::new();
|
||||
pub static SessionChangeBlock: u64 = 0;
|
||||
}
|
||||
|
||||
pub struct TestSessionHandler;
|
||||
impl pallet_session::SessionHandler<u64> for TestSessionHandler {
|
||||
const KEY_TYPE_IDS: &'static [sp_runtime::KeyTypeId] = &[UintAuthorityId::ID];
|
||||
fn on_genesis_session<Ks: OpaqueKeys>(keys: &[(u64, Ks)]) {
|
||||
SessionHandlerCollators::set(keys.iter().map(|(a, _)| *a).collect::<Vec<_>>())
|
||||
}
|
||||
fn on_new_session<Ks: OpaqueKeys>(_: bool, keys: &[(u64, Ks)], _: &[(u64, Ks)]) {
|
||||
SessionChangeBlock::set(System::block_number());
|
||||
SessionHandlerCollators::set(keys.iter().map(|(a, _)| *a).collect::<Vec<_>>())
|
||||
}
|
||||
fn on_before_session_ending() {}
|
||||
fn on_disabled(_: u32) {}
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
pub const Offset: u64 = 0;
|
||||
pub const Period: u64 = 5;
|
||||
}
|
||||
|
||||
impl pallet_session::Config for Test {
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type ValidatorId = <Self as frame_system::Config>::AccountId;
|
||||
// we don't have stash and controller, thus we don't need the convert as well.
|
||||
type ValidatorIdOf = ConvertInto;
|
||||
type ShouldEndSession = pallet_session::PeriodicSessions<Period, Offset>;
|
||||
type NextSessionRotation = pallet_session::PeriodicSessions<Period, Offset>;
|
||||
type SessionManager = ExternalValidators;
|
||||
type SessionHandler = TestSessionHandler;
|
||||
type Keys = MockSessionKeys;
|
||||
type WeightInfo = ();
|
||||
}
|
||||
|
||||
// Pallet to provide some mock data, used to test
|
||||
#[frame_support::pallet]
|
||||
pub mod mock_data {
|
||||
use {crate::mock::Mocks, frame_support::pallet_prelude::*};
|
||||
|
||||
#[pallet::config]
|
||||
pub trait Config: frame_system::Config {}
|
||||
|
||||
#[pallet::call]
|
||||
impl<T: Config> Pallet<T> {}
|
||||
|
||||
#[pallet::pallet]
|
||||
#[pallet::without_storage_info]
|
||||
pub struct Pallet<T>(_);
|
||||
|
||||
#[pallet::storage]
|
||||
pub(super) type Mock<T: Config> = StorageValue<_, Mocks, ValueQuery>;
|
||||
|
||||
impl<T: Config> Pallet<T> {
|
||||
pub fn mock() -> Mocks {
|
||||
Mock::<T>::get()
|
||||
}
|
||||
pub fn mutate<F, R>(f: F) -> R
|
||||
where
|
||||
F: FnOnce(&mut Mocks) -> R,
|
||||
{
|
||||
Mock::<T>::mutate(f)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Encode, Decode, PartialEq, sp_core::RuntimeDebug, scale_info::TypeInfo)]
|
||||
pub enum HookCall {
|
||||
OnEraStart {
|
||||
era: u32,
|
||||
session: u32,
|
||||
external_index: u64,
|
||||
},
|
||||
OnEraEnd {
|
||||
era: u32,
|
||||
},
|
||||
}
|
||||
|
||||
impl mock_data::Config for Test {}
|
||||
|
||||
#[derive(
|
||||
Clone, Default, Encode, Decode, PartialEq, sp_core::RuntimeDebug, scale_info::TypeInfo,
|
||||
)]
|
||||
pub struct Mocks {
|
||||
pub called_hooks: Vec<HookCall>,
|
||||
}
|
||||
|
||||
// We use the mock_data pallet to test hooks: we store a list of all the calls, and then check that
|
||||
// no eras are skipped.
|
||||
impl<T> OnEraStart for mock_data::Pallet<T> {
|
||||
fn on_era_start(era_index: EraIndex, session_start: u32, external_idx: u64) {
|
||||
Mock::mutate(|m| {
|
||||
m.called_hooks.push(HookCall::OnEraStart {
|
||||
era: era_index,
|
||||
session: session_start,
|
||||
external_index: external_idx,
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> OnEraEnd for mock_data::Pallet<T> {
|
||||
fn on_era_end(era_index: EraIndex) {
|
||||
Mock::mutate(|m| {
|
||||
m.called_hooks.push(HookCall::OnEraEnd { era: era_index });
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_test_ext() -> sp_io::TestExternalities {
|
||||
let mut t = frame_system::GenesisConfig::<Test>::default()
|
||||
.build_storage()
|
||||
.unwrap();
|
||||
let whitelisted_validators = vec![1, 2];
|
||||
|
||||
let balances = vec![(1, 100), (2, 100), (3, 100), (4, 100), (5, 100)];
|
||||
let keys = balances
|
||||
.iter()
|
||||
.map(|&(i, _)| {
|
||||
(
|
||||
i,
|
||||
i,
|
||||
MockSessionKeys {
|
||||
aura: UintAuthorityId(i),
|
||||
},
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let session = pallet_session::GenesisConfig::<Test> {
|
||||
keys,
|
||||
..Default::default()
|
||||
};
|
||||
pallet_balances::GenesisConfig::<Test> { balances }
|
||||
.assimilate_storage(&mut t)
|
||||
.unwrap();
|
||||
pallet_external_validators::GenesisConfig::<Test> {
|
||||
skip_external_validators: false,
|
||||
whitelisted_validators,
|
||||
..Default::default()
|
||||
}
|
||||
.assimilate_storage(&mut t)
|
||||
.unwrap();
|
||||
session.assimilate_storage(&mut t).unwrap();
|
||||
|
||||
let mut ext: sp_io::TestExternalities = t.into();
|
||||
|
||||
// Initialize accounts and keys for external validators
|
||||
ext.execute_with(|| {
|
||||
initialize_validators(vec![50, 51]);
|
||||
});
|
||||
|
||||
ext
|
||||
}
|
||||
|
||||
fn initialize_validators(validators: Vec<u64>) {
|
||||
for x in validators {
|
||||
assert_ok!(Balances::mint_into(&x, 10_000_000_000));
|
||||
assert_ok!(Session::set_keys(
|
||||
RuntimeOrigin::signed(x),
|
||||
MockSessionKeys::from(UintAuthorityId(x)),
|
||||
vec![]
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
pub const INIT_TIMESTAMP: u64 = 30_000;
|
||||
pub const BLOCK_TIME: u64 = 1000;
|
||||
|
||||
pub fn run_to_session(n: u32) {
|
||||
let block_number = Period::get() * u64::from(n);
|
||||
run_to_block(block_number + 1);
|
||||
}
|
||||
|
||||
pub fn run_to_block(n: u64) {
|
||||
let old_block_number = System::block_number();
|
||||
|
||||
for x in old_block_number..n {
|
||||
ExternalValidators::on_finalize(System::block_number());
|
||||
Session::on_finalize(System::block_number());
|
||||
|
||||
System::reset_events();
|
||||
System::set_block_number(x + 1);
|
||||
Timestamp::set_timestamp(System::block_number() * BLOCK_TIME + INIT_TIMESTAMP);
|
||||
|
||||
ExternalValidators::on_initialize(System::block_number());
|
||||
Session::on_initialize(System::block_number());
|
||||
}
|
||||
}
|
||||
|
||||
pub fn last_event() -> RuntimeEvent {
|
||||
System::events().pop().expect("Event expected").event
|
||||
}
|
||||
389
operator/pallets/external-validators/src/tests.rs
Normal file
389
operator/pallets/external-validators/src/tests.rs
Normal file
|
|
@ -0,0 +1,389 @@
|
|||
// 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/>
|
||||
|
||||
use {
|
||||
crate::{
|
||||
mock::{
|
||||
last_event, new_test_ext, run_to_block, run_to_session, ExternalValidators, HookCall,
|
||||
Mock, RootAccount, RuntimeEvent, RuntimeOrigin, Session, System, Test,
|
||||
},
|
||||
traits::{ExternalIndexProvider, ValidatorProvider},
|
||||
Error,
|
||||
},
|
||||
frame_support::{assert_noop, assert_ok},
|
||||
sp_runtime::traits::BadOrigin,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn basic_setup_works() {
|
||||
new_test_ext().execute_with(|| {
|
||||
assert_eq!(ExternalValidators::whitelisted_validators(), vec![1, 2]);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn add_whitelisted_works() {
|
||||
new_test_ext().execute_with(|| {
|
||||
run_to_block(1);
|
||||
assert_eq!(ExternalValidators::whitelisted_validators(), vec![1, 2]);
|
||||
let new = 3;
|
||||
|
||||
// function runs
|
||||
assert_ok!(ExternalValidators::add_whitelisted(
|
||||
RuntimeOrigin::signed(RootAccount::get()),
|
||||
new
|
||||
));
|
||||
|
||||
System::assert_last_event(RuntimeEvent::ExternalValidators(
|
||||
crate::Event::WhitelistedValidatorAdded { account_id: new },
|
||||
));
|
||||
|
||||
// same element cannot be added more than once
|
||||
assert_noop!(
|
||||
ExternalValidators::add_whitelisted(RuntimeOrigin::signed(RootAccount::get()), new),
|
||||
Error::<Test>::AlreadyWhitelisted
|
||||
);
|
||||
|
||||
// new element is now part of the invulnerables list
|
||||
assert!(ExternalValidators::whitelisted_validators()
|
||||
.to_vec()
|
||||
.contains(&new));
|
||||
|
||||
// cannot add with non-root
|
||||
assert_noop!(
|
||||
ExternalValidators::add_whitelisted(RuntimeOrigin::signed(1), new),
|
||||
BadOrigin
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn add_whitelisted_does_not_work_if_not_registered() {
|
||||
new_test_ext().execute_with(|| {
|
||||
run_to_block(1);
|
||||
assert_eq!(ExternalValidators::whitelisted_validators(), vec![1, 2]);
|
||||
let new = 42;
|
||||
|
||||
assert_noop!(
|
||||
ExternalValidators::add_whitelisted(RuntimeOrigin::signed(RootAccount::get()), new),
|
||||
Error::<Test>::NoKeysRegistered
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn validator_limit_works() {
|
||||
new_test_ext().execute_with(|| {
|
||||
assert_eq!(ExternalValidators::whitelisted_validators(), vec![1, 2]);
|
||||
|
||||
// MaxExternalValidators: u32 = 20
|
||||
for ii in 3..=21 {
|
||||
if ii < 21 {
|
||||
assert_ok!(ExternalValidators::add_whitelisted(
|
||||
RuntimeOrigin::signed(RootAccount::get()),
|
||||
ii
|
||||
));
|
||||
} else {
|
||||
assert_noop!(
|
||||
ExternalValidators::add_whitelisted(
|
||||
RuntimeOrigin::signed(RootAccount::get()),
|
||||
ii
|
||||
),
|
||||
Error::<Test>::TooManyWhitelisted
|
||||
);
|
||||
}
|
||||
}
|
||||
let expected: Vec<u64> = (1..=20).collect();
|
||||
assert_eq!(ExternalValidators::whitelisted_validators(), expected);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn remove_whitelisted_works() {
|
||||
new_test_ext().execute_with(|| {
|
||||
run_to_block(1);
|
||||
assert_eq!(ExternalValidators::whitelisted_validators(), vec![1, 2]);
|
||||
|
||||
assert_ok!(ExternalValidators::add_whitelisted(
|
||||
RuntimeOrigin::signed(RootAccount::get()),
|
||||
4
|
||||
));
|
||||
assert_ok!(ExternalValidators::add_whitelisted(
|
||||
RuntimeOrigin::signed(RootAccount::get()),
|
||||
3
|
||||
));
|
||||
|
||||
assert_eq!(
|
||||
ExternalValidators::whitelisted_validators(),
|
||||
vec![1, 2, 4, 3]
|
||||
);
|
||||
|
||||
assert_ok!(ExternalValidators::remove_whitelisted(
|
||||
RuntimeOrigin::signed(RootAccount::get()),
|
||||
2
|
||||
));
|
||||
|
||||
System::assert_last_event(RuntimeEvent::ExternalValidators(
|
||||
crate::Event::WhitelistedValidatorRemoved { account_id: 2 },
|
||||
));
|
||||
assert_eq!(ExternalValidators::whitelisted_validators(), vec![1, 4, 3]);
|
||||
|
||||
// cannot remove invulnerable not in the list
|
||||
assert_noop!(
|
||||
ExternalValidators::remove_whitelisted(RuntimeOrigin::signed(RootAccount::get()), 2),
|
||||
Error::<Test>::NotWhitelisted
|
||||
);
|
||||
|
||||
// cannot remove without privilege
|
||||
assert_noop!(
|
||||
ExternalValidators::remove_whitelisted(RuntimeOrigin::signed(1), 3),
|
||||
BadOrigin
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn whitelisted_and_external_order() {
|
||||
new_test_ext().execute_with(|| {
|
||||
run_to_block(1);
|
||||
assert_eq!(ExternalValidators::whitelisted_validators(), vec![1, 2]);
|
||||
assert_ok!(ExternalValidators::set_external_validators_inner(
|
||||
vec![50, 51],
|
||||
1
|
||||
));
|
||||
|
||||
run_to_session(6);
|
||||
let validators = Session::validators();
|
||||
let external_index = ExternalValidators::get_external_index();
|
||||
assert_eq!(validators, vec![1, 2, 50, 51]);
|
||||
assert_eq!(external_index, 1);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn validator_provider_returns_all_validators() {
|
||||
new_test_ext().execute_with(|| {
|
||||
run_to_block(1);
|
||||
assert_eq!(ExternalValidators::whitelisted_validators(), vec![1, 2]);
|
||||
assert_ok!(ExternalValidators::set_external_validators_inner(
|
||||
vec![50, 51],
|
||||
1
|
||||
));
|
||||
|
||||
run_to_session(6);
|
||||
let validators_new_session = Session::validators();
|
||||
let validators_provider = <ExternalValidators as ValidatorProvider<u64>>::validators();
|
||||
assert_eq!(validators_new_session, validators_provider);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_skip_external_validators() {
|
||||
new_test_ext().execute_with(|| {
|
||||
run_to_block(1);
|
||||
assert_eq!(ExternalValidators::whitelisted_validators(), vec![1, 2]);
|
||||
assert_ok!(ExternalValidators::set_external_validators_inner(
|
||||
vec![50, 51],
|
||||
1
|
||||
));
|
||||
assert_ok!(ExternalValidators::skip_external_validators(
|
||||
RuntimeOrigin::signed(RootAccount::get()),
|
||||
true
|
||||
));
|
||||
|
||||
run_to_session(6);
|
||||
let validators = Session::validators();
|
||||
assert_eq!(validators, vec![1, 2]);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn duplicate_validators_are_deduplicated() {
|
||||
new_test_ext().execute_with(|| {
|
||||
run_to_block(1);
|
||||
assert_eq!(ExternalValidators::whitelisted_validators(), vec![1, 2]);
|
||||
assert_ok!(ExternalValidators::set_external_validators_inner(
|
||||
vec![2],
|
||||
1
|
||||
));
|
||||
|
||||
run_to_session(6);
|
||||
let validators = Session::validators();
|
||||
assert_eq!(validators, vec![1, 2]);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn duplicate_validator_order_is_preserved() {
|
||||
new_test_ext().execute_with(|| {
|
||||
run_to_block(1);
|
||||
// Whitelisted validators have priority, so their ordering should be respected
|
||||
// Need to manually remove and add each whitelisted because there is no "set_whitelisted"
|
||||
assert_ok!(ExternalValidators::remove_whitelisted(
|
||||
RuntimeOrigin::signed(RootAccount::get()),
|
||||
1
|
||||
));
|
||||
assert_ok!(ExternalValidators::remove_whitelisted(
|
||||
RuntimeOrigin::signed(RootAccount::get()),
|
||||
2
|
||||
));
|
||||
assert_ok!(ExternalValidators::add_whitelisted(
|
||||
RuntimeOrigin::signed(RootAccount::get()),
|
||||
3
|
||||
));
|
||||
assert_ok!(ExternalValidators::add_whitelisted(
|
||||
RuntimeOrigin::signed(RootAccount::get()),
|
||||
1
|
||||
));
|
||||
assert_ok!(ExternalValidators::add_whitelisted(
|
||||
RuntimeOrigin::signed(RootAccount::get()),
|
||||
2
|
||||
));
|
||||
assert_eq!(ExternalValidators::whitelisted_validators(), vec![3, 1, 2]);
|
||||
assert_ok!(ExternalValidators::set_external_validators_inner(
|
||||
vec![3, 2, 1, 4],
|
||||
1
|
||||
));
|
||||
|
||||
run_to_session(6);
|
||||
let validators = Session::validators();
|
||||
assert_eq!(validators, vec![3, 1, 2, 4]);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn external_index_gets_set_correctly() {
|
||||
new_test_ext().execute_with(|| {
|
||||
run_to_block(1);
|
||||
assert_ok!(ExternalValidators::set_external_validators_inner(
|
||||
vec![2],
|
||||
1
|
||||
));
|
||||
let external_index = ExternalValidators::get_external_index();
|
||||
assert_eq!(external_index, 0);
|
||||
run_to_session(6);
|
||||
|
||||
let external_index = ExternalValidators::get_external_index();
|
||||
assert_eq!(external_index, 1);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn setting_external_validators_emits_event() {
|
||||
new_test_ext().execute_with(|| {
|
||||
run_to_block(1);
|
||||
assert_ok!(ExternalValidators::set_external_validators_inner(
|
||||
vec![2, 3],
|
||||
1
|
||||
));
|
||||
let event = RuntimeEvent::ExternalValidators(crate::Event::ExternalValidatorsSet {
|
||||
validators: vec![2, 3],
|
||||
external_index: 1,
|
||||
});
|
||||
assert_eq!(last_event(), event);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn setting_external_validators_with_more_than_max_external_validators_emits_correct_event() {
|
||||
new_test_ext().execute_with(|| {
|
||||
run_to_block(1);
|
||||
let max_external_validators = 20u64;
|
||||
// Current max external validators is 20 so if we try to set 25 validators
|
||||
// We expect only the first 20 to be set as external validators
|
||||
assert_ok!(ExternalValidators::set_external_validators_inner(
|
||||
(1..(max_external_validators + 5)).collect(),
|
||||
1
|
||||
));
|
||||
let event = RuntimeEvent::ExternalValidators(crate::Event::ExternalValidatorsSet {
|
||||
validators: (1..(max_external_validators + 1)).collect(),
|
||||
external_index: 1,
|
||||
});
|
||||
assert_eq!(last_event(), event);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn era_hooks() {
|
||||
new_test_ext().execute_with(|| {
|
||||
run_to_session(14);
|
||||
|
||||
let expected_calls = vec![
|
||||
HookCall::OnEraStart {
|
||||
era: 0,
|
||||
session: 0,
|
||||
external_index: 0,
|
||||
},
|
||||
HookCall::OnEraEnd { era: 0 },
|
||||
HookCall::OnEraStart {
|
||||
era: 1,
|
||||
session: 6,
|
||||
external_index: 0,
|
||||
},
|
||||
HookCall::OnEraEnd { era: 1 },
|
||||
HookCall::OnEraStart {
|
||||
era: 2,
|
||||
session: 12,
|
||||
external_index: 0,
|
||||
},
|
||||
];
|
||||
|
||||
assert_eq!(Mock::mock().called_hooks, expected_calls);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn era_hooks_with_external_index() {
|
||||
new_test_ext().execute_with(|| {
|
||||
let first_external_index = 1000;
|
||||
assert_ok!(ExternalValidators::set_external_validators_inner(
|
||||
vec![50, 51],
|
||||
first_external_index
|
||||
));
|
||||
|
||||
run_to_session(8);
|
||||
|
||||
let second_external_index = 2000;
|
||||
|
||||
assert_ok!(ExternalValidators::set_external_validators_inner(
|
||||
vec![50, 51],
|
||||
second_external_index
|
||||
));
|
||||
|
||||
run_to_session(14);
|
||||
|
||||
let expected_calls = vec![
|
||||
HookCall::OnEraStart {
|
||||
era: 0,
|
||||
session: 0,
|
||||
external_index: 0,
|
||||
},
|
||||
HookCall::OnEraEnd { era: 0 },
|
||||
HookCall::OnEraStart {
|
||||
era: 1,
|
||||
session: 6,
|
||||
external_index: first_external_index,
|
||||
},
|
||||
HookCall::OnEraEnd { era: 1 },
|
||||
HookCall::OnEraStart {
|
||||
era: 2,
|
||||
session: 12,
|
||||
external_index: second_external_index,
|
||||
},
|
||||
];
|
||||
|
||||
assert_eq!(Mock::mock().called_hooks, expected_calls);
|
||||
});
|
||||
}
|
||||
64
operator/pallets/external-validators/src/traits.rs
Normal file
64
operator/pallets/external-validators/src/traits.rs
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
use parity_scale_codec::{Decode, Encode, MaxEncodedLen};
|
||||
use scale_info::TypeInfo;
|
||||
use sp_runtime::RuntimeDebug;
|
||||
use sp_std::vec::Vec;
|
||||
|
||||
/// Information regarding the active era (era in used in session).
|
||||
#[derive(Clone, Encode, Decode, RuntimeDebug, TypeInfo, MaxEncodedLen)]
|
||||
pub struct ActiveEraInfo {
|
||||
/// Index of era.
|
||||
pub index: EraIndex,
|
||||
/// Moment of start expressed as millisecond from `$UNIX_EPOCH`.
|
||||
///
|
||||
/// Start can be none if start hasn't been set for the era yet,
|
||||
/// Start is set on the first on_finalize of the era to guarantee usage of `Time`.
|
||||
pub start: Option<u64>,
|
||||
}
|
||||
|
||||
/// Counter for the number of eras that have passed.
|
||||
pub type EraIndex = u32;
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub trait EraIndexProvider {
|
||||
fn active_era() -> ActiveEraInfo;
|
||||
fn era_to_session_start(era_index: EraIndex) -> Option<u32>;
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub trait ValidatorProvider<ValidatorId> {
|
||||
fn validators() -> Vec<ValidatorId>;
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub trait InvulnerablesProvider<ValidatorId> {
|
||||
fn invulnerables() -> Vec<ValidatorId>;
|
||||
}
|
||||
|
||||
pub trait OnEraStart {
|
||||
fn on_era_start(_era_index: EraIndex, _session_start: u32, _external_idx: u64) {}
|
||||
}
|
||||
|
||||
#[impl_trait_for_tuples::impl_for_tuples(5)]
|
||||
impl OnEraStart for Tuple {
|
||||
fn on_era_start(era_index: EraIndex, session_start: u32, external_idx: u64) {
|
||||
for_tuples!( #( Tuple::on_era_start(era_index, session_start, external_idx); )* );
|
||||
}
|
||||
}
|
||||
|
||||
pub trait OnEraEnd {
|
||||
fn on_era_end(_era_index: EraIndex) {}
|
||||
}
|
||||
|
||||
#[impl_trait_for_tuples::impl_for_tuples(5)]
|
||||
impl OnEraEnd for Tuple {
|
||||
fn on_era_end(era_index: EraIndex) {
|
||||
for_tuples!( #( Tuple::on_era_end(era_index); )* );
|
||||
}
|
||||
}
|
||||
|
||||
// A trait to retrieve the external index provider identifying some set of data
|
||||
// In starlight, used to retrieve the external index associated to validators
|
||||
#[allow(dead_code)]
|
||||
pub trait ExternalIndexProvider {
|
||||
fn get_external_index() -> u64;
|
||||
}
|
||||
241
operator/pallets/external-validators/src/weights.rs
Normal file
241
operator/pallets/external-validators/src/weights.rs
Normal file
|
|
@ -0,0 +1,241 @@
|
|||
// 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/>
|
||||
|
||||
|
||||
//! Autogenerated weights for pallet_external_validators
|
||||
//!
|
||||
//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 42.0.0
|
||||
//! DATE: 2024-10-22, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]`
|
||||
//! WORST CASE MAP SIZE: `1000000`
|
||||
//! HOSTNAME: `tomasz-XPS-15-9520`, CPU: `12th Gen Intel(R) Core(TM) i7-12700H`
|
||||
//! 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
|
||||
// --extrinsic
|
||||
// *
|
||||
// --chain=dancelight-dev
|
||||
// --steps
|
||||
// 50
|
||||
// --repeat
|
||||
// 20
|
||||
// --template=benchmarking/frame-weight-pallet-template.hbs
|
||||
// --json-file
|
||||
// raw.json
|
||||
// --output
|
||||
// tmp/dancelight_weights/pallet_external_validators.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.
|
||||
pub trait WeightInfo {
|
||||
fn skip_external_validators() -> Weight;
|
||||
fn add_whitelisted(b: u32, ) -> Weight;
|
||||
fn remove_whitelisted(b: u32, ) -> Weight;
|
||||
fn force_era() -> Weight;
|
||||
fn set_external_validators() -> Weight;
|
||||
fn new_session(r: u32, ) -> Weight;
|
||||
}
|
||||
|
||||
/// Weights for pallet_external_validators using the Substrate node and recommended hardware.
|
||||
pub struct SubstrateWeight<T>(PhantomData<T>);
|
||||
impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
|
||||
/// Storage: `ExternalValidators::SkipExternalValidators` (r:0 w:1)
|
||||
/// Proof: `ExternalValidators::SkipExternalValidators` (`max_values`: Some(1), `max_size`: Some(1), added: 496, mode: `MaxEncodedLen`)
|
||||
fn skip_external_validators() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `0`
|
||||
// Estimated: `0`
|
||||
// Minimum execution time: 1_391_000 picoseconds.
|
||||
Weight::from_parts(1_484_000, 0)
|
||||
.saturating_add(T::DbWeight::get().writes(1_u64))
|
||||
}
|
||||
/// Storage: `Session::NextKeys` (r:1 w:0)
|
||||
/// Proof: `Session::NextKeys` (`max_values`: None, `max_size`: None, mode: `Measured`)
|
||||
/// Storage: `ExternalValidators::WhitelistedValidators` (r:1 w:1)
|
||||
/// Proof: `ExternalValidators::WhitelistedValidators` (`max_values`: Some(1), `max_size`: Some(3202), added: 3697, mode: `MaxEncodedLen`)
|
||||
/// The range of component `b` is `[1, 99]`.
|
||||
fn add_whitelisted(b: u32, ) -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `845 + b * (36 ±0)`
|
||||
// Estimated: `4687 + b * (37 ±0)`
|
||||
// Minimum execution time: 12_829_000 picoseconds.
|
||||
Weight::from_parts(17_541_907, 4687)
|
||||
// Standard Error: 1_560
|
||||
.saturating_add(Weight::from_parts(62_143, 0).saturating_mul(b.into()))
|
||||
.saturating_add(T::DbWeight::get().reads(2_u64))
|
||||
.saturating_add(T::DbWeight::get().writes(1_u64))
|
||||
.saturating_add(Weight::from_parts(0, 37).saturating_mul(b.into()))
|
||||
}
|
||||
/// Storage: `ExternalValidators::WhitelistedValidators` (r:1 w:1)
|
||||
/// Proof: `ExternalValidators::WhitelistedValidators` (`max_values`: Some(1), `max_size`: Some(3202), added: 3697, mode: `MaxEncodedLen`)
|
||||
/// The range of component `b` is `[1, 100]`.
|
||||
fn remove_whitelisted(b: u32, ) -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `137 + b * (32 ±0)`
|
||||
// Estimated: `4687`
|
||||
// Minimum execution time: 7_269_000 picoseconds.
|
||||
Weight::from_parts(9_100_286, 4687)
|
||||
// Standard Error: 626
|
||||
.saturating_add(Weight::from_parts(35_303, 0).saturating_mul(b.into()))
|
||||
.saturating_add(T::DbWeight::get().reads(1_u64))
|
||||
.saturating_add(T::DbWeight::get().writes(1_u64))
|
||||
}
|
||||
/// Storage: `ExternalValidators::ForceEra` (r:0 w:1)
|
||||
/// Proof: `ExternalValidators::ForceEra` (`max_values`: Some(1), `max_size`: Some(1), added: 496, mode: `MaxEncodedLen`)
|
||||
fn force_era() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `0`
|
||||
// Estimated: `0`
|
||||
// Minimum execution time: 4_578_000 picoseconds.
|
||||
Weight::from_parts(4_924_000, 0)
|
||||
.saturating_add(T::DbWeight::get().writes(1_u64))
|
||||
}
|
||||
/// Storage: `ExternalValidators::ForceEra` (r:0 w:1)
|
||||
/// Proof: `ExternalValidators::ForceEra` (`max_values`: Some(1), `max_size`: Some(1), added: 496, mode: `MaxEncodedLen`)
|
||||
fn set_external_validators() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `0`
|
||||
// Estimated: `0`
|
||||
// Minimum execution time: 4_578_000 picoseconds.
|
||||
Weight::from_parts(4_924_000, 0)
|
||||
.saturating_add(T::DbWeight::get().writes(1_u64))
|
||||
}
|
||||
/// Storage: `ExternalValidators::ForceEra` (r:1 w:0)
|
||||
/// Proof: `ExternalValidators::ForceEra` (`max_values`: Some(1), `max_size`: Some(1), added: 496, mode: `MaxEncodedLen`)
|
||||
/// Storage: `ExternalValidators::EraSessionStart` (r:1 w:1)
|
||||
/// Proof: `ExternalValidators::EraSessionStart` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`)
|
||||
/// Storage: `ExternalValidators::ActiveEra` (r:1 w:1)
|
||||
/// Proof: `ExternalValidators::ActiveEra` (`max_values`: Some(1), `max_size`: Some(13), added: 508, mode: `MaxEncodedLen`)
|
||||
/// Storage: `ExternalValidators::WhitelistedValidators` (r:1 w:0)
|
||||
/// Proof: `ExternalValidators::WhitelistedValidators` (`max_values`: Some(1), `max_size`: Some(3202), added: 3697, mode: `MaxEncodedLen`)
|
||||
/// Storage: `ExternalValidators::SkipExternalValidators` (r:1 w:0)
|
||||
/// Proof: `ExternalValidators::SkipExternalValidators` (`max_values`: Some(1), `max_size`: Some(1), added: 496, mode: `MaxEncodedLen`)
|
||||
/// Storage: `ExternalValidators::ExternalValidators` (r:1 w:0)
|
||||
/// Proof: `ExternalValidators::ExternalValidators` (`max_values`: Some(1), `max_size`: Some(3202), added: 3697, mode: `MaxEncodedLen`)
|
||||
/// The range of component `r` is `[1, 100]`.
|
||||
fn new_session(r: u32, ) -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `137 + r * (32 ±0)`
|
||||
// Estimated: `4687`
|
||||
// Minimum execution time: 8_587_000 picoseconds.
|
||||
Weight::from_parts(10_453_582, 4687)
|
||||
// Standard Error: 555
|
||||
.saturating_add(Weight::from_parts(27_159, 0).saturating_mul(r.into()))
|
||||
.saturating_add(T::DbWeight::get().reads(6_u64))
|
||||
.saturating_add(T::DbWeight::get().writes(2_u64))
|
||||
}
|
||||
}
|
||||
|
||||
// For backwards compatibility and tests
|
||||
impl WeightInfo for () {
|
||||
/// Storage: `ExternalValidators::SkipExternalValidators` (r:0 w:1)
|
||||
/// Proof: `ExternalValidators::SkipExternalValidators` (`max_values`: Some(1), `max_size`: Some(1), added: 496, mode: `MaxEncodedLen`)
|
||||
fn skip_external_validators() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `0`
|
||||
// Estimated: `0`
|
||||
// Minimum execution time: 1_391_000 picoseconds.
|
||||
Weight::from_parts(1_484_000, 0)
|
||||
.saturating_add(RocksDbWeight::get().writes(1_u64))
|
||||
}
|
||||
/// Storage: `Session::NextKeys` (r:1 w:0)
|
||||
/// Proof: `Session::NextKeys` (`max_values`: None, `max_size`: None, mode: `Measured`)
|
||||
/// Storage: `ExternalValidators::WhitelistedValidators` (r:1 w:1)
|
||||
/// Proof: `ExternalValidators::WhitelistedValidators` (`max_values`: Some(1), `max_size`: Some(3202), added: 3697, mode: `MaxEncodedLen`)
|
||||
/// The range of component `b` is `[1, 99]`.
|
||||
fn add_whitelisted(b: u32, ) -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `845 + b * (36 ±0)`
|
||||
// Estimated: `4687 + b * (37 ±0)`
|
||||
// Minimum execution time: 12_829_000 picoseconds.
|
||||
Weight::from_parts(17_541_907, 4687)
|
||||
// Standard Error: 1_560
|
||||
.saturating_add(Weight::from_parts(62_143, 0).saturating_mul(b.into()))
|
||||
.saturating_add(RocksDbWeight::get().reads(2_u64))
|
||||
.saturating_add(RocksDbWeight::get().writes(1_u64))
|
||||
.saturating_add(Weight::from_parts(0, 37).saturating_mul(b.into()))
|
||||
}
|
||||
/// Storage: `ExternalValidators::WhitelistedValidators` (r:1 w:1)
|
||||
/// Proof: `ExternalValidators::WhitelistedValidators` (`max_values`: Some(1), `max_size`: Some(3202), added: 3697, mode: `MaxEncodedLen`)
|
||||
/// The range of component `b` is `[1, 100]`.
|
||||
fn remove_whitelisted(b: u32, ) -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `137 + b * (32 ±0)`
|
||||
// Estimated: `4687`
|
||||
// Minimum execution time: 7_269_000 picoseconds.
|
||||
Weight::from_parts(9_100_286, 4687)
|
||||
// Standard Error: 626
|
||||
.saturating_add(Weight::from_parts(35_303, 0).saturating_mul(b.into()))
|
||||
.saturating_add(RocksDbWeight::get().reads(1_u64))
|
||||
.saturating_add(RocksDbWeight::get().writes(1_u64))
|
||||
}
|
||||
/// Storage: `ExternalValidators::ForceEra` (r:0 w:1)
|
||||
/// Proof: `ExternalValidators::ForceEra` (`max_values`: Some(1), `max_size`: Some(1), added: 496, mode: `MaxEncodedLen`)
|
||||
fn force_era() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `0`
|
||||
// Estimated: `0`
|
||||
// Minimum execution time: 4_578_000 picoseconds.
|
||||
Weight::from_parts(4_924_000, 0)
|
||||
.saturating_add(RocksDbWeight::get().writes(1_u64))
|
||||
}
|
||||
/// Storage: `ExternalValidators::ForceEra` (r:0 w:1)
|
||||
/// Proof: `ExternalValidators::ForceEra` (`max_values`: Some(1), `max_size`: Some(1), added: 496, mode: `MaxEncodedLen`)
|
||||
fn set_external_validators() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `0`
|
||||
// Estimated: `0`
|
||||
// Minimum execution time: 4_578_000 picoseconds.
|
||||
Weight::from_parts(4_924_000, 0)
|
||||
.saturating_add(RocksDbWeight::get().writes(1_u64))
|
||||
}
|
||||
/// Storage: `ExternalValidators::ForceEra` (r:1 w:0)
|
||||
/// Proof: `ExternalValidators::ForceEra` (`max_values`: Some(1), `max_size`: Some(1), added: 496, mode: `MaxEncodedLen`)
|
||||
/// Storage: `ExternalValidators::EraSessionStart` (r:1 w:1)
|
||||
/// Proof: `ExternalValidators::EraSessionStart` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`)
|
||||
/// Storage: `ExternalValidators::ActiveEra` (r:1 w:1)
|
||||
/// Proof: `ExternalValidators::ActiveEra` (`max_values`: Some(1), `max_size`: Some(13), added: 508, mode: `MaxEncodedLen`)
|
||||
/// Storage: `ExternalValidators::WhitelistedValidators` (r:1 w:0)
|
||||
/// Proof: `ExternalValidators::WhitelistedValidators` (`max_values`: Some(1), `max_size`: Some(3202), added: 3697, mode: `MaxEncodedLen`)
|
||||
/// Storage: `ExternalValidators::SkipExternalValidators` (r:1 w:0)
|
||||
/// Proof: `ExternalValidators::SkipExternalValidators` (`max_values`: Some(1), `max_size`: Some(1), added: 496, mode: `MaxEncodedLen`)
|
||||
/// Storage: `ExternalValidators::ExternalValidators` (r:1 w:0)
|
||||
/// Proof: `ExternalValidators::ExternalValidators` (`max_values`: Some(1), `max_size`: Some(3202), added: 3697, mode: `MaxEncodedLen`)
|
||||
/// The range of component `r` is `[1, 100]`.
|
||||
fn new_session(r: u32, ) -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `137 + r * (32 ±0)`
|
||||
// Estimated: `4687`
|
||||
// Minimum execution time: 8_587_000 picoseconds.
|
||||
Weight::from_parts(10_453_582, 4687)
|
||||
// Standard Error: 555
|
||||
.saturating_add(Weight::from_parts(27_159, 0).saturating_mul(r.into()))
|
||||
.saturating_add(RocksDbWeight::get().reads(6_u64))
|
||||
.saturating_add(RocksDbWeight::get().writes(2_u64))
|
||||
}
|
||||
}
|
||||
|
|
@ -1,38 +0,0 @@
|
|||
[package]
|
||||
authors = [
|
||||
'Gautam Dhameja <quasijatt@outlook.com>',
|
||||
'Parity Technologies <admin@parity.io>',
|
||||
]
|
||||
edition = '2021'
|
||||
license = 'Apache-2.0'
|
||||
name = 'pallet-validator-set'
|
||||
version = '1.0.0'
|
||||
|
||||
[dependencies]
|
||||
codec = { workspace = true, features = ["derive"] }
|
||||
frame-benchmarking = { workspace = true, optional = true }
|
||||
frame-support = { workspace = true }
|
||||
frame-system = { workspace = true }
|
||||
log = { workspace = true }
|
||||
pallet-session = { workspace = true, features = ['historical'] }
|
||||
scale-info = { workspace = true, features = ["derive", "serde"] }
|
||||
sp-core = { workspace = true }
|
||||
sp-io = { workspace = true }
|
||||
sp-runtime = { workspace = true }
|
||||
sp-staking = { workspace = true }
|
||||
|
||||
[features]
|
||||
default = ['std']
|
||||
runtime-benchmarks = ['frame-benchmarking/runtime-benchmarks']
|
||||
std = [
|
||||
'codec/std',
|
||||
'frame-benchmarking/std',
|
||||
'frame-support/std',
|
||||
'frame-system/std',
|
||||
'scale-info/std',
|
||||
'sp-core/std',
|
||||
'sp-io/std',
|
||||
'sp-runtime/std',
|
||||
'pallet-session/std',
|
||||
]
|
||||
try-runtime = ['frame-support/try-runtime']
|
||||
|
|
@ -1,196 +0,0 @@
|
|||
# Substrate Validator Set Pallet
|
||||
|
||||
A [Substrate](https://github.com/paritytech/substrate/) pallet to add/remove authorities/validators in PoA networks.
|
||||
|
||||
**Note: Current master is compatible with Substrate [polkadot-v1.0.0](https://github.com/paritytech/substrate/tree/polkadot-v1.0.0) branch. For older versions, please see releases/tags.**
|
||||
|
||||
## Setup with Substrate Node Template
|
||||
|
||||
### Dependencies - runtime/cargo.toml
|
||||
|
||||
* Add the module's dependency in the `Cargo.toml` of your runtime directory. Make sure to enter the correct path or git url of the pallet as per your setup.
|
||||
|
||||
* Make sure that you also have the Substrate [session pallet](https://github.com/paritytech/substrate/tree/master/frame/session) as part of your runtime. This is because the validator-set pallet is dependent on the session pallet.
|
||||
|
||||
```toml
|
||||
[dependencies.validator-set]
|
||||
default-features = false
|
||||
package = 'substrate-validator-set'
|
||||
git = 'https://github.com/gautamdhameja/substrate-validator-set.git'
|
||||
version = '0.9.42'
|
||||
|
||||
[dependencies.pallet-session]
|
||||
default-features = false
|
||||
git = 'https://github.com/paritytech/substrate.git'
|
||||
branch = 'polkadot-v1.0.0'
|
||||
```
|
||||
|
||||
```toml
|
||||
std = [
|
||||
...
|
||||
'validator-set/std',
|
||||
'pallet-session/std',
|
||||
]
|
||||
```
|
||||
|
||||
### Pallet Initialization - runtime/src/lib.rs
|
||||
|
||||
* Import `OpaqueKeys` in your `runtime/src/lib.rs`.
|
||||
|
||||
```rust
|
||||
use sp_runtime::traits::{
|
||||
AccountIdLookup, BlakeTwo256, Block as BlockT, Verify, IdentifyAccount, NumberFor, OpaqueKeys,
|
||||
};
|
||||
```
|
||||
|
||||
* Also in `runtime/src/lib.rs` import the `EnsureRoot` trait. This would change if you want to configure a custom origin (see below).
|
||||
|
||||
```rust
|
||||
use frame_system::EnsureRoot;
|
||||
```
|
||||
|
||||
* Declare the pallet in your `runtime/src/lib.rs`. The pallet supports configurable origin and you can either set it to use one of the governance pallets (Collective, Democracy, etc.), or just use root as shown below. But **do not use a normal origin here** because the addition and removal of validators should be done using elevated privileges.
|
||||
|
||||
```rust
|
||||
parameter_types! {
|
||||
pub const MinAuthorities: u32 = 2;
|
||||
}
|
||||
|
||||
impl validator_set::Config for Runtime {
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type AddRemoveOrigin = EnsureRoot<AccountId>;
|
||||
type MinAuthorities = MinAuthorities;
|
||||
type WeightInfo = validator_set::weights::SubstrateWeight<Runtime>;
|
||||
}
|
||||
```
|
||||
|
||||
* Also, declare the session pallet in your `runtime/src/lib.rs`. Some of the type configuration of session pallet would depend on the ValidatorSet pallet as shown below.
|
||||
|
||||
```rust
|
||||
parameter_types! {
|
||||
pub const Period: u32 = 2 * MINUTES;
|
||||
pub const Offset: u32 = 0;
|
||||
}
|
||||
|
||||
impl pallet_session::Config for Runtime {
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type ValidatorId = <Self as frame_system::Config>::AccountId;
|
||||
type ValidatorIdOf = validator_set::ValidatorOf<Self>;
|
||||
type ShouldEndSession = pallet_session::PeriodicSessions<Period, Offset>;
|
||||
type NextSessionRotation = pallet_session::PeriodicSessions<Period, Offset>;
|
||||
type SessionManager = ValidatorSet;
|
||||
type SessionHandler = <opaque::SessionKeys as OpaqueKeys>::KeyTypeIdProviders;
|
||||
type Keys = opaque::SessionKeys;
|
||||
type WeightInfo = ();
|
||||
}
|
||||
```
|
||||
|
||||
* Add `validator_set`, and `session` pallets in `construct_runtime` macro. **Make sure to add them before `Aura` and `Grandpa` pallets and after `Balances`. Also make sure that the `validator_set` pallet is added _before_ the `session` pallet, because it provides the initial validators at genesis, and must initialize first.**
|
||||
|
||||
```rust
|
||||
construct_runtime!(
|
||||
pub enum Runtime where
|
||||
Block = Block,
|
||||
NodeBlock = opaque::Block,
|
||||
UncheckedExtrinsic = UncheckedExtrinsic
|
||||
{
|
||||
...
|
||||
Balances: pallet_balances,
|
||||
ValidatorSet: validator_set,
|
||||
Session: pallet_session,
|
||||
Aura: pallet_aura,
|
||||
Grandpa: pallet_grandpa,
|
||||
...
|
||||
...
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
### Genesis config - chain_spec.rs
|
||||
|
||||
* Import `opaque::SessionKeys, ValidatorSetConfig, SessionConfig` from the runtime in `node/src/chain_spec.rs`.
|
||||
|
||||
```rust
|
||||
use node_template_runtime::{
|
||||
AccountId, AuraConfig, BalancesConfig, GenesisConfig, GrandpaConfig,
|
||||
SudoConfig, SystemConfig, WASM_BINARY, Signature,
|
||||
opaque::SessionKeys, ValidatorSetConfig, SessionConfig
|
||||
};
|
||||
```
|
||||
|
||||
* And then in `node/src/chain_spec.rs` update the key generation functions.
|
||||
|
||||
```rust
|
||||
fn session_keys(aura: AuraId, grandpa: GrandpaId) -> SessionKeys {
|
||||
SessionKeys { aura, grandpa }
|
||||
}
|
||||
|
||||
pub fn authority_keys_from_seed(s: &str) -> (AccountId, AuraId, GrandpaId) {
|
||||
(
|
||||
get_account_id_from_seed::<sr25519::Public>(s),
|
||||
get_from_seed::<AuraId>(s),
|
||||
get_from_seed::<GrandpaId>(s)
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
* Add genesis config in the `chain_spec.rs` file for `session` and `validatorset` pallets, and update it for `Aura` and `Grandpa` pallets. Because the validators are provided by the `session` pallet, we do not initialize them explicitly for `Aura` and `Grandpa` pallets. Order is important, notice that `pallet_session` is declared after `pallet_balances` since it depends on it (session accounts should have some balance).
|
||||
|
||||
```rust
|
||||
fn testnet_genesis(
|
||||
wasm_binary: &[u8],
|
||||
initial_authorities: Vec<(AccountId, AuraId, GrandpaId)>,
|
||||
root_key: AccountId,
|
||||
endowed_accounts: Vec<AccountId>,
|
||||
_enable_println: bool,
|
||||
) -> GenesisConfig {
|
||||
GenesisConfig {
|
||||
system: SystemConfig {
|
||||
// Add Wasm runtime to storage.
|
||||
code: wasm_binary.to_vec(),
|
||||
},
|
||||
balances: BalancesConfig {
|
||||
// Configure endowed accounts with initial balance of 1 << 60.
|
||||
balances: endowed_accounts.iter().cloned().map(|k| (k, 1 << 60)).collect(),
|
||||
},
|
||||
validator_set: ValidatorSetConfig {
|
||||
initial_validators: initial_authorities.iter().map(|x| x.0.clone()).collect::<Vec<_>>(),
|
||||
},
|
||||
session: SessionConfig {
|
||||
keys: initial_authorities.iter().map(|x| {
|
||||
(x.0.clone(), x.0.clone(), session_keys(x.1.clone(), x.2.clone()))
|
||||
}).collect::<Vec<_>>(),
|
||||
},
|
||||
aura: AuraConfig {
|
||||
authorities: vec![],
|
||||
},
|
||||
grandpa: GrandpaConfig {
|
||||
authorities: vec![],
|
||||
},
|
||||
sudo: SudoConfig {
|
||||
// Assign network admin rights.
|
||||
key: Some(root_key),
|
||||
},
|
||||
transaction_payment: Default::default(),
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Run
|
||||
|
||||
Once you have set up the pallet in your node/node-template and everything compiles, follow the steps in [docs/local-network-setup.md](./docs/local-network-setup.md) to run a local network and add validators.
|
||||
Also, watch this video to see this in action - https://www.youtube.com/watch?v=lIYxE-tOAdw.
|
||||
|
||||
## Extensions
|
||||
|
||||
### Council-based Governance
|
||||
|
||||
Instead of using `sudo`, for a council-based governance, use the pallet with the `Collective` pallet. Follow the steps in [docs/council-integration.md](./docs/council-integration.md).
|
||||
|
||||
### Auto-removal Of Offline Validators
|
||||
|
||||
When a validator goes offline, it skips its block production slot and that causes increased block times. Sometimes, we want to remove these offline validators so that the block time can recover to normal. The `ImOnline` pallet, when added to a runtime, can report offline validators. The `ValidatorSet` pallet implements the required types to integrate with `ImOnline` pallet for automatic removal of offline validators. To use the `ValidatorSet` pallet with the `ImOnline` pallet, follow the steps in [docs/im-online-integration.md](./docs/im-online-integration.md).
|
||||
|
||||
## Disclaimer
|
||||
|
||||
This code is **not audited** for production use cases. You can expect security vulnerabilities. Do not use it without proper testing and audit in a production applications.
|
||||
|
|
@ -1,67 +0,0 @@
|
|||
// Copyright (C) Gautam Dhameja.
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#![cfg(feature = "runtime-benchmarks")]
|
||||
|
||||
use super::{Pallet as ValidatorSet, *};
|
||||
use frame_benchmarking::v2::{account, benchmarks, impl_test_function, BenchmarkError};
|
||||
use frame_support::traits::EnsureOrigin;
|
||||
use frame_system::{EventRecord, Pallet as System};
|
||||
|
||||
const SEED: u32 = 0;
|
||||
|
||||
fn assert_last_event<T: Config>(generic_event: <T as Config>::RuntimeEvent) {
|
||||
let events = System::<T>::events();
|
||||
let system_event: <T as frame_system::Config>::RuntimeEvent = generic_event.into();
|
||||
let EventRecord { event, .. } = &events[events.len() - 1];
|
||||
assert_eq!(event, &system_event);
|
||||
}
|
||||
|
||||
#[benchmarks]
|
||||
mod benchmarks {
|
||||
use super::*;
|
||||
|
||||
#[benchmark]
|
||||
fn add_validator() -> Result<(), BenchmarkError> {
|
||||
let origin = T::AddRemoveOrigin::try_successful_origin()
|
||||
.map_err(|_| BenchmarkError::Stop("unable to compute origin"))?;
|
||||
let who: T::AccountId = account("validator", 0, SEED);
|
||||
|
||||
#[extrinsic_call]
|
||||
_(origin as T::RuntimeOrigin, who.clone());
|
||||
|
||||
assert_last_event::<T>(Event::ValidatorAdded(who).into());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[benchmark]
|
||||
fn remove_validator() -> Result<(), BenchmarkError> {
|
||||
let origin = T::AddRemoveOrigin::try_successful_origin()
|
||||
.map_err(|_| BenchmarkError::Stop("unable to compute origin"))?;
|
||||
let who: T::AccountId = account("validator", 0, SEED);
|
||||
|
||||
ValidatorSet::<T>::add_validator(origin.clone(), who.clone())
|
||||
.map_err(|_| BenchmarkError::Stop("unable to add validator"))?;
|
||||
|
||||
#[extrinsic_call]
|
||||
_(origin as T::RuntimeOrigin, who.clone());
|
||||
|
||||
assert_last_event::<T>(Event::ValidatorRemoved(who).into());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
impl_benchmark_test_suite!(ValidatorSet, crate::mock::new_test_ext(), crate::mock::Test);
|
||||
}
|
||||
|
|
@ -1,422 +0,0 @@
|
|||
// Copyright (C) Gautam Dhameja.
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Validator set pallet. Maintains a set of validators which can be added to or removed from by a
|
||||
//! privileged origin.
|
||||
//!
|
||||
//! Provides a [`SessionManager`] implementation which returns the current validator set from
|
||||
//! `new_session`. Also provides an [`OnOffenceHandler`] implementation which removes the offending
|
||||
//! validators from the set (if they would be slashed) and temporarily disables them according to
|
||||
//! the [`DisableStrategy`].
|
||||
//!
|
||||
//! Adding a validator to the set increments the validator account's provider reference count. This
|
||||
//! allows the validator to set their session keys with
|
||||
//! [`set_keys`](pallet_session::Pallet::set_keys). When a validator is removed, either explicitly
|
||||
//! via [`remove_validator`](Pallet::remove_validator) or implicitly due to an offence, the
|
||||
//! validator's session keys are automatically purged and the validator account's provider
|
||||
//! reference count is decremented again. Note that failure to decrement the provider reference
|
||||
//! count does not cause removal to fail; the provider reference is just leaked.
|
||||
//!
|
||||
//! This pallet directly depends on [`pallet_session`] and [`pallet_session::historical`].
|
||||
//! [`pallet_session::Config::ValidatorId`] must be [`AccountId`](frame_system::Config::AccountId)
|
||||
//! and [`pallet_session::Config::ValidatorIdOf`] must be [`ConvertInto`].
|
||||
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
// We need this because it clashes with Polkadot macro pallet::pallet
|
||||
#![allow(clippy::manual_inspect)]
|
||||
|
||||
mod benchmarking;
|
||||
mod mock;
|
||||
mod tests;
|
||||
pub mod weights;
|
||||
|
||||
use codec::{Decode, Encode, MaxEncodedLen};
|
||||
use frame_support::{
|
||||
dispatch::RawOrigin,
|
||||
ensure,
|
||||
pallet_prelude::{DispatchResult, Weight},
|
||||
traits::Get,
|
||||
DefaultNoBound,
|
||||
};
|
||||
use frame_system::pallet_prelude::BlockNumberFor;
|
||||
pub use pallet::*;
|
||||
use pallet_session::SessionManager;
|
||||
use sp_runtime::{
|
||||
traits::{ConvertInto, Zero},
|
||||
transaction_validity::{InvalidTransaction, TransactionValidityError},
|
||||
Perbill, Saturating, Vec,
|
||||
};
|
||||
use sp_staking::{
|
||||
offence::{OffenceDetails, OnOffenceHandler},
|
||||
SessionIndex,
|
||||
};
|
||||
pub use weights::WeightInfo;
|
||||
|
||||
const LOG_TARGET: &str = "runtime::validator-set";
|
||||
|
||||
/// Per-validator data stored by this pallet.
|
||||
#[derive(Encode, Decode, scale_info::TypeInfo, MaxEncodedLen)]
|
||||
struct Validator<BlockNumber> {
|
||||
/// The validator may not set its session keys before this block.
|
||||
min_set_keys_block: BlockNumber,
|
||||
}
|
||||
|
||||
#[frame_support::pallet]
|
||||
pub mod pallet {
|
||||
use super::*;
|
||||
use frame_support::pallet_prelude::*;
|
||||
use frame_system::pallet_prelude::*;
|
||||
|
||||
#[pallet::config]
|
||||
pub trait Config:
|
||||
frame_system::Config
|
||||
+ pallet_session::Config<
|
||||
ValidatorId = <Self as frame_system::Config>::AccountId,
|
||||
ValidatorIdOf = ConvertInto,
|
||||
>
|
||||
{
|
||||
/// The overarching event type.
|
||||
type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
|
||||
|
||||
/// Weight information for extrinsics in this pallet.
|
||||
type WeightInfo: WeightInfo;
|
||||
|
||||
/// Origin for adding or removing a validator.
|
||||
type AddRemoveOrigin: EnsureOrigin<Self::RuntimeOrigin>;
|
||||
|
||||
/// Maximum number of validators.
|
||||
#[pallet::constant]
|
||||
type MaxAuthorities: Get<u32>;
|
||||
|
||||
/// Minimum number of blocks between [`set_keys`](pallet_session::Pallet::set_keys) calls
|
||||
/// by a validator.
|
||||
#[pallet::constant]
|
||||
type SetKeysCooldownBlocks: Get<BlockNumberFor<Self>>;
|
||||
}
|
||||
|
||||
#[pallet::pallet]
|
||||
pub struct Pallet<T>(_);
|
||||
|
||||
/// Validator set. Changes to this will take effect in the session after next.
|
||||
#[pallet::storage]
|
||||
pub(super) type Validators<T: Config> =
|
||||
StorageMap<_, Blake2_128Concat, T::AccountId, Validator<BlockNumberFor<T>>, OptionQuery>;
|
||||
|
||||
/// Number of validators in `Validators`.
|
||||
#[pallet::storage]
|
||||
pub(super) type NumValidators<T: Config> = StorageValue<_, u32, ValueQuery>;
|
||||
|
||||
/// Validators that should be disabled in the next session.
|
||||
///
|
||||
/// Validator removal takes effect in the session after next. Validator disabling takes effect
|
||||
/// until the end of the session. We extend disables to cover the next session as well (by
|
||||
/// adding validators here when we disable them) so that when a validator is both disabled and
|
||||
/// removed in response to an offence, there isn't a gap where it is actually present and
|
||||
/// enabled.
|
||||
#[pallet::storage]
|
||||
pub(super) type NextDisabledValidators<T: Config> =
|
||||
StorageMap<_, Blake2_128Concat, T::AccountId, (), OptionQuery>;
|
||||
|
||||
#[pallet::event]
|
||||
#[pallet::generate_deposit(pub(super) fn deposit_event)]
|
||||
pub enum Event<T: Config> {
|
||||
/// New validator added. Effective in session after next.
|
||||
ValidatorAdded(T::AccountId),
|
||||
/// Validator removed. Effective in session after next.
|
||||
ValidatorRemoved(T::AccountId),
|
||||
}
|
||||
|
||||
#[pallet::error]
|
||||
pub enum Error<T> {
|
||||
/// Validator is already in the validator set.
|
||||
Duplicate,
|
||||
/// Validator is not in the validator set.
|
||||
NotAValidator,
|
||||
/// Adding the validator would take the validator count above the maximum.
|
||||
TooManyValidators,
|
||||
}
|
||||
|
||||
#[pallet::genesis_config]
|
||||
#[derive(DefaultNoBound)]
|
||||
pub struct GenesisConfig<T: Config> {
|
||||
pub initial_validators: BoundedVec<T::AccountId, T::MaxAuthorities>,
|
||||
}
|
||||
|
||||
#[pallet::genesis_build]
|
||||
impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
|
||||
fn build(&self) {
|
||||
assert!(
|
||||
Validators::<T>::iter().next().is_none(),
|
||||
"Validators are already initialized"
|
||||
);
|
||||
assert_eq!(NumValidators::<T>::get(), 0);
|
||||
for who in &self.initial_validators {
|
||||
assert!(Pallet::<T>::do_add_validator(who).is_ok());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[pallet::call]
|
||||
impl<T: Config> Pallet<T> {
|
||||
/// Add a new validator.
|
||||
///
|
||||
/// This will increment the validator's provider reference count, allowing the validator to
|
||||
/// call [`set_keys`](pallet_session::Pallet::set_keys).
|
||||
///
|
||||
/// Provided the validator calls `set_keys` in time, the addition will take effect the
|
||||
/// session after next.
|
||||
///
|
||||
/// The origin for this call must be the pallet's `AddRemoveOrigin`. Emits
|
||||
/// [`ValidatorAdded`](Event::ValidatorAdded) when successful.
|
||||
#[pallet::call_index(0)]
|
||||
#[pallet::weight(<T as Config>::WeightInfo::add_validator())]
|
||||
pub fn add_validator(origin: OriginFor<T>, who: T::AccountId) -> DispatchResult {
|
||||
T::AddRemoveOrigin::ensure_origin(origin)?;
|
||||
Self::do_add_validator(&who)?;
|
||||
Self::deposit_event(Event::ValidatorAdded(who));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Set the validator set to exactly match the provided list of validators.
|
||||
///
|
||||
/// This will:
|
||||
/// 1. Keep existing validators that are in the input list
|
||||
/// 2. Remove validators that are not in the input list
|
||||
/// 3. Add new validators from the input list
|
||||
///
|
||||
/// The origin for this call must be the pallet's `AddRemoveOrigin`. Emits
|
||||
/// [`ValidatorAdded`](Event::ValidatorAdded) for each new validator and
|
||||
/// [`ValidatorRemoved`](Event::ValidatorRemoved) for each removed validator.
|
||||
///
|
||||
/// If the number of validators would exceed the maximum, the operation fails.
|
||||
#[pallet::call_index(1)]
|
||||
#[pallet::weight(<T as Config>::WeightInfo::add_validator().saturating_mul(validators.len() as u64))]
|
||||
pub fn set_validators(
|
||||
origin: OriginFor<T>,
|
||||
validators: Vec<T::AccountId>,
|
||||
) -> DispatchResult {
|
||||
T::AddRemoveOrigin::ensure_origin(origin)?;
|
||||
|
||||
// Check if the new validator count would exceed the maximum
|
||||
ensure!(
|
||||
validators.len() <= T::MaxAuthorities::get() as usize,
|
||||
Error::<T>::TooManyValidators
|
||||
);
|
||||
|
||||
// Check for duplicates in the input list
|
||||
for (i, _) in validators.iter().enumerate() {
|
||||
for j in 0..i {
|
||||
ensure!(validators[i] != validators[j], Error::<T>::Duplicate);
|
||||
}
|
||||
}
|
||||
|
||||
// Get current validators
|
||||
let current_validators: Vec<T::AccountId> = Validators::<T>::iter_keys().collect();
|
||||
|
||||
// Remove validators that are not in the new list
|
||||
for who in current_validators.iter() {
|
||||
if !validators.contains(who) && Self::do_remove_validator(who) {
|
||||
Self::deposit_event(Event::ValidatorRemoved(who.clone()));
|
||||
}
|
||||
}
|
||||
|
||||
// Add new validators
|
||||
for who in validators {
|
||||
if Validators::<T>::get(&who).is_none() {
|
||||
Self::do_add_validator(&who)?;
|
||||
Self::deposit_event(Event::ValidatorAdded(who));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Remove a validator.
|
||||
///
|
||||
/// This will purge the validator's session keys and decrement the validator's provider
|
||||
/// reference count.
|
||||
///
|
||||
/// The removal will take effect the session after next.
|
||||
///
|
||||
/// The origin for this call must be the pallet's `AddRemoveOrigin`. Emits
|
||||
/// [`ValidatorRemoved`](Event::ValidatorRemoved) when successful.
|
||||
#[pallet::call_index(2)]
|
||||
#[pallet::weight(<T as Config>::WeightInfo::remove_validator())]
|
||||
pub fn remove_validator(origin: OriginFor<T>, who: T::AccountId) -> DispatchResult {
|
||||
T::AddRemoveOrigin::ensure_origin(origin)?;
|
||||
ensure!(Self::do_remove_validator(&who), Error::<T>::NotAValidator);
|
||||
Self::deposit_event(Event::ValidatorRemoved(who));
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config> Pallet<T> {
|
||||
pub fn validators() -> Vec<T::AccountId> {
|
||||
Validators::<T>::iter_keys().collect()
|
||||
}
|
||||
|
||||
fn do_add_validator(who: &T::AccountId) -> DispatchResult {
|
||||
NumValidators::<T>::mutate(|num| {
|
||||
if *num >= T::MaxAuthorities::get() {
|
||||
return Err(Error::<T>::TooManyValidators);
|
||||
}
|
||||
|
||||
Validators::<T>::mutate(who, |validator| {
|
||||
if !validator.is_none() {
|
||||
return Err(Error::<T>::Duplicate);
|
||||
}
|
||||
*validator = Some(Validator {
|
||||
min_set_keys_block: Zero::zero(),
|
||||
});
|
||||
Ok(())
|
||||
})?;
|
||||
|
||||
*num += 1;
|
||||
Ok(())
|
||||
})?;
|
||||
|
||||
frame_system::Pallet::<T>::inc_providers(who);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Returns `false` if `who` is not a validator.
|
||||
fn do_remove_validator(who: &T::AccountId) -> bool {
|
||||
if Validators::<T>::take(who).is_none() {
|
||||
return false;
|
||||
}
|
||||
NumValidators::<T>::mutate(|num| *num -= 1);
|
||||
|
||||
// Decrement who's provider reference count. Purge who's session keys first as
|
||||
// dec_providers will fail if there are any consumers.
|
||||
if let Err(err) =
|
||||
pallet_session::Pallet::<T>::purge_keys(RawOrigin::Signed(who.clone()).into())
|
||||
{
|
||||
log::trace!(
|
||||
target: LOG_TARGET,
|
||||
"Failed to purge session keys for validator {who:?}: {err:?}"
|
||||
);
|
||||
}
|
||||
if let Err(err) = frame_system::Pallet::<T>::dec_providers(who) {
|
||||
log::warn!(
|
||||
target: LOG_TARGET,
|
||||
"Failed to decrement provider reference count for validator {who:?}, \
|
||||
leaking reference: {err:?}"
|
||||
);
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
fn check_min_set_keys_block(
|
||||
validator: &Validator<BlockNumberFor<T>>,
|
||||
) -> Result<(), TransactionValidityError> {
|
||||
if frame_system::Pallet::<T>::block_number() < validator.min_set_keys_block {
|
||||
Err(InvalidTransaction::Future.into())
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Check the validity of a [`set_keys`](pallet_session::Pallet::set_keys) call by `who`.
|
||||
pub fn validate_set_keys(who: &T::AccountId) -> Result<(), TransactionValidityError> {
|
||||
match Validators::<T>::get(who) {
|
||||
Some(validator) => Self::check_min_set_keys_block(&validator),
|
||||
None => Err(InvalidTransaction::BadSigner.into()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Check the validity of a [`set_keys`](pallet_session::Pallet::set_keys) call by `who`, and,
|
||||
/// if valid, note the call.
|
||||
pub fn pre_dispatch_set_keys(who: &T::AccountId) -> Result<(), TransactionValidityError> {
|
||||
Validators::<T>::mutate(who, |validator| match validator {
|
||||
Some(validator) => {
|
||||
Self::check_min_set_keys_block(validator)?;
|
||||
validator.min_set_keys_block = frame_system::Pallet::<T>::block_number()
|
||||
.saturating_add(T::SetKeysCooldownBlocks::get());
|
||||
Ok(())
|
||||
}
|
||||
None => Err(InvalidTransaction::BadSigner.into()),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config> SessionManager<T::ValidatorId> for Pallet<T> {
|
||||
fn new_session(_new_index: SessionIndex) -> Option<Vec<T::ValidatorId>> {
|
||||
Some(Self::validators())
|
||||
}
|
||||
|
||||
fn end_session(_end_index: SessionIndex) {}
|
||||
|
||||
fn start_session(_start_index: SessionIndex) {
|
||||
for (who, _) in NextDisabledValidators::<T>::drain() {
|
||||
pallet_session::Pallet::<T>::disable(&who);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config> pallet_session::historical::SessionManager<T::ValidatorId, T::ValidatorId>
|
||||
for Pallet<T>
|
||||
{
|
||||
fn new_session(new_index: SessionIndex) -> Option<Vec<(T::ValidatorId, T::ValidatorId)>> {
|
||||
<Self as pallet_session::SessionManager<_>>::new_session(new_index)
|
||||
.map(|r| r.into_iter().map(|v| (v.clone(), v)).collect())
|
||||
}
|
||||
|
||||
fn start_session(start_index: SessionIndex) {
|
||||
<Self as pallet_session::SessionManager<_>>::start_session(start_index)
|
||||
}
|
||||
|
||||
fn end_session(end_index: SessionIndex) {
|
||||
<Self as pallet_session::SessionManager<_>>::end_session(end_index)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config>
|
||||
OnOffenceHandler<T::AccountId, pallet_session::historical::IdentificationTuple<T>, Weight>
|
||||
for Pallet<T>
|
||||
where
|
||||
T: pallet_session::historical::Config,
|
||||
{
|
||||
fn on_offence(
|
||||
offenders: &[OffenceDetails<
|
||||
T::AccountId,
|
||||
pallet_session::historical::IdentificationTuple<T>,
|
||||
>],
|
||||
slash_fractions: &[Perbill],
|
||||
_slash_session: SessionIndex,
|
||||
) -> Weight {
|
||||
let mut weight = Weight::zero();
|
||||
let db_weight = T::DbWeight::get();
|
||||
|
||||
for (offender, slash_fraction) in offenders.iter().zip(slash_fractions) {
|
||||
// Determine actions to take with this validator
|
||||
let remove = !slash_fraction.is_zero();
|
||||
|
||||
if remove {
|
||||
// Note that the validator might already have been removed (explicitly, for another
|
||||
// offence, or even by an earlier report of this offence)
|
||||
weight.saturating_accrue(db_weight.reads(1));
|
||||
if Self::do_remove_validator(&offender.offender.0) {
|
||||
weight.saturating_accrue(db_weight.reads_writes(1, 2));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
weight
|
||||
}
|
||||
}
|
||||
|
|
@ -1,192 +0,0 @@
|
|||
// Copyright (C) Gautam Dhameja.
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Mock helpers for Validator Set pallet.
|
||||
|
||||
#![cfg(test)]
|
||||
|
||||
use crate as pallet_validator_set;
|
||||
use frame_support::{
|
||||
parameter_types,
|
||||
traits::{ConstU32, ConstU64, OnFinalize, OnInitialize, OneSessionHandler},
|
||||
};
|
||||
use frame_system::{pallet_prelude::BlockNumberFor, EnsureRoot};
|
||||
use pallet_session::ShouldEndSession;
|
||||
use sp_core::H256;
|
||||
use sp_runtime::{
|
||||
impl_opaque_keys,
|
||||
testing::UintAuthorityId,
|
||||
traits::{BlakeTwo256, ConvertInto, IdentityLookup},
|
||||
BuildStorage,
|
||||
};
|
||||
use std::cell::Cell;
|
||||
|
||||
pub type AccountId = u64;
|
||||
type Block = frame_system::mocking::MockBlock<Test>;
|
||||
|
||||
frame_support::construct_runtime!(
|
||||
pub struct Test {
|
||||
System: frame_system,
|
||||
ValidatorSet: pallet_validator_set,
|
||||
Session: pallet_session,
|
||||
}
|
||||
);
|
||||
|
||||
pub struct MockSessionHandler;
|
||||
|
||||
impl OneSessionHandler<AccountId> for MockSessionHandler {
|
||||
type Key = UintAuthorityId;
|
||||
|
||||
fn on_genesis_session<'a, I: 'a>(_validators: I)
|
||||
where
|
||||
I: Iterator<Item = (&'a AccountId, Self::Key)>,
|
||||
{
|
||||
}
|
||||
|
||||
fn on_new_session<'a, I: 'a>(_changed: bool, _validators: I, _queued_validators: I)
|
||||
where
|
||||
I: Iterator<Item = (&'a AccountId, Self::Key)>,
|
||||
{
|
||||
}
|
||||
|
||||
fn on_disabled(_i: u32) {}
|
||||
}
|
||||
|
||||
impl sp_runtime::BoundToRuntimeAppPublic for MockSessionHandler {
|
||||
type Public = UintAuthorityId;
|
||||
}
|
||||
|
||||
impl_opaque_keys! {
|
||||
pub struct MockSessionKeys {
|
||||
pub mock: MockSessionHandler,
|
||||
}
|
||||
}
|
||||
|
||||
impl From<AccountId> for MockSessionKeys {
|
||||
fn from(who: AccountId) -> Self {
|
||||
Self {
|
||||
mock: UintAuthorityId(who),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
thread_local! {
|
||||
static END_SESSION: Cell<bool> = Cell::new(false);
|
||||
}
|
||||
|
||||
pub struct MockShouldEndSession;
|
||||
|
||||
impl<T> ShouldEndSession<T> for MockShouldEndSession {
|
||||
fn should_end_session(_now: T) -> bool {
|
||||
END_SESSION.replace(false)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn next_block() {
|
||||
System::on_finalize(System::block_number());
|
||||
System::set_block_number(System::block_number() + 1);
|
||||
System::on_initialize(System::block_number());
|
||||
Session::on_initialize(System::block_number());
|
||||
}
|
||||
|
||||
pub fn next_session() {
|
||||
END_SESSION.set(true);
|
||||
next_block();
|
||||
assert!(!END_SESSION.get());
|
||||
}
|
||||
|
||||
pub fn new_test_ext() -> sp_io::TestExternalities {
|
||||
let validators = vec![1, 2, 3];
|
||||
let keys = validators
|
||||
.iter()
|
||||
.map(|who| (*who, *who, (*who).into()))
|
||||
.collect();
|
||||
let t = RuntimeGenesisConfig {
|
||||
system: Default::default(),
|
||||
session: SessionConfig {
|
||||
keys,
|
||||
non_authority_keys: Default::default(),
|
||||
},
|
||||
validator_set: ValidatorSetConfig {
|
||||
initial_validators: validators.try_into().unwrap(),
|
||||
},
|
||||
}
|
||||
.build_storage()
|
||||
.unwrap();
|
||||
t.into()
|
||||
}
|
||||
|
||||
impl frame_system::Config for Test {
|
||||
type BaseCallFilter = frame_support::traits::Everything;
|
||||
type BlockWeights = ();
|
||||
type BlockLength = ();
|
||||
type DbWeight = ();
|
||||
type RuntimeOrigin = RuntimeOrigin;
|
||||
type Nonce = u64;
|
||||
type RuntimeCall = RuntimeCall;
|
||||
type Hash = H256;
|
||||
type Hashing = BlakeTwo256;
|
||||
type AccountId = AccountId;
|
||||
type Lookup = IdentityLookup<AccountId>;
|
||||
type Block = Block;
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type BlockHashCount = ConstU64<250>;
|
||||
type Version = ();
|
||||
type PalletInfo = PalletInfo;
|
||||
type AccountData = ();
|
||||
type OnNewAccount = ();
|
||||
type OnKilledAccount = ();
|
||||
type SystemWeightInfo = ();
|
||||
type SS58Prefix = ();
|
||||
type OnSetCode = ();
|
||||
type MaxConsumers = ConstU32<16>;
|
||||
type RuntimeTask = ();
|
||||
type SingleBlockMigrations = ();
|
||||
type MultiBlockMigrator = ();
|
||||
type PreInherents = ();
|
||||
type PostInherents = ();
|
||||
type PostTransactions = ();
|
||||
type ExtensionsWeightInfo = ();
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
pub const SetKeysCooldownBlocks: BlockNumberFor<Test> = 2;
|
||||
}
|
||||
|
||||
impl pallet_validator_set::Config for Test {
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type WeightInfo = ();
|
||||
type AddRemoveOrigin = EnsureRoot<AccountId>;
|
||||
type MaxAuthorities = ConstU32<6>;
|
||||
type SetKeysCooldownBlocks = SetKeysCooldownBlocks;
|
||||
}
|
||||
|
||||
impl pallet_session::Config for Test {
|
||||
type ValidatorId = AccountId;
|
||||
type ValidatorIdOf = ConvertInto;
|
||||
type ShouldEndSession = MockShouldEndSession;
|
||||
type NextSessionRotation = ();
|
||||
type SessionManager = ValidatorSet;
|
||||
type SessionHandler = (MockSessionHandler,);
|
||||
type Keys = MockSessionKeys;
|
||||
type WeightInfo = ();
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
}
|
||||
|
||||
impl pallet_session::historical::Config for Test {
|
||||
type FullIdentification = Self::ValidatorId;
|
||||
type FullIdentificationOf = Self::ValidatorIdOf;
|
||||
}
|
||||
|
|
@ -1,177 +0,0 @@
|
|||
// Copyright (C) Gautam Dhameja.
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Tests for the Validator Set pallet.
|
||||
|
||||
#![cfg(test)]
|
||||
|
||||
use super::mock::{
|
||||
new_test_ext, next_block, next_session, AccountId, RuntimeOrigin, Session, System, Test,
|
||||
ValidatorSet,
|
||||
};
|
||||
use frame_support::{assert_noop, assert_ok, traits::ValidatorRegistration};
|
||||
use sp_runtime::{traits::Zero, transaction_validity::InvalidTransaction, DispatchError};
|
||||
use std::collections::HashSet;
|
||||
|
||||
type Error = super::Error<Test>;
|
||||
|
||||
fn validators() -> HashSet<AccountId> {
|
||||
ValidatorSet::validators().into_iter().collect()
|
||||
}
|
||||
|
||||
fn active_validators() -> HashSet<AccountId> {
|
||||
Session::validators().into_iter().collect()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn initial_validators() {
|
||||
new_test_ext().execute_with(|| {
|
||||
assert_eq!(validators(), HashSet::from([1, 2, 3]));
|
||||
assert_eq!(active_validators(), HashSet::from([1, 2, 3]));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn add_validator_updates_validators_list() {
|
||||
new_test_ext().execute_with(|| {
|
||||
assert_eq!(validators(), HashSet::from([1, 2, 3]));
|
||||
assert_ok!(ValidatorSet::add_validator(RuntimeOrigin::root(), 4));
|
||||
assert_eq!(validators(), HashSet::from([1, 2, 3, 4]));
|
||||
|
||||
// add_validator should take effect in the session after next, provided the keys have been
|
||||
// set
|
||||
assert_ok!(Session::set_keys(
|
||||
RuntimeOrigin::signed(4),
|
||||
4.into(),
|
||||
vec![]
|
||||
));
|
||||
assert_eq!(active_validators(), HashSet::from([1, 2, 3]));
|
||||
next_session();
|
||||
assert_eq!(active_validators(), HashSet::from([1, 2, 3]));
|
||||
next_session();
|
||||
assert_eq!(active_validators(), HashSet::from([1, 2, 3, 4]));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn remove_validator_updates_validators_list() {
|
||||
new_test_ext().execute_with(|| {
|
||||
assert_ok!(ValidatorSet::remove_validator(RuntimeOrigin::root(), 2));
|
||||
assert_eq!(validators(), HashSet::from([1, 3]));
|
||||
// Add validator again
|
||||
assert_ok!(ValidatorSet::add_validator(RuntimeOrigin::root(), 2));
|
||||
assert_eq!(validators(), HashSet::from([1, 2, 3]));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn add_validator_fails_with_invalid_origin() {
|
||||
new_test_ext().execute_with(|| {
|
||||
assert_noop!(
|
||||
ValidatorSet::add_validator(RuntimeOrigin::signed(1), 4),
|
||||
DispatchError::BadOrigin
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn remove_validator_fails_with_invalid_origin() {
|
||||
new_test_ext().execute_with(|| {
|
||||
assert_noop!(
|
||||
ValidatorSet::remove_validator(RuntimeOrigin::signed(1), 4),
|
||||
DispatchError::BadOrigin
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn duplicate_check() {
|
||||
new_test_ext().execute_with(|| {
|
||||
assert_ok!(ValidatorSet::add_validator(RuntimeOrigin::root(), 4));
|
||||
assert_eq!(validators(), HashSet::from([1, 2, 3, 4]));
|
||||
assert_noop!(
|
||||
ValidatorSet::add_validator(RuntimeOrigin::root(), 4),
|
||||
Error::Duplicate
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn too_many_validators_check() {
|
||||
new_test_ext().execute_with(|| {
|
||||
assert_ok!(ValidatorSet::add_validator(RuntimeOrigin::root(), 4));
|
||||
assert_ok!(ValidatorSet::add_validator(RuntimeOrigin::root(), 5));
|
||||
assert_ok!(ValidatorSet::add_validator(RuntimeOrigin::root(), 6));
|
||||
assert_noop!(
|
||||
ValidatorSet::add_validator(RuntimeOrigin::root(), 7),
|
||||
Error::TooManyValidators
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn not_a_validator_check() {
|
||||
new_test_ext().execute_with(|| {
|
||||
assert_ok!(ValidatorSet::remove_validator(RuntimeOrigin::root(), 3));
|
||||
assert_noop!(
|
||||
ValidatorSet::remove_validator(RuntimeOrigin::root(), 3),
|
||||
Error::NotAValidator
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn remove_purges_keys_and_decs_providers() {
|
||||
new_test_ext().execute_with(|| {
|
||||
assert!(Session::is_registered(&3));
|
||||
assert!(!System::providers(&3).is_zero());
|
||||
assert_ok!(ValidatorSet::remove_validator(RuntimeOrigin::root(), 3));
|
||||
assert!(!Session::is_registered(&3));
|
||||
assert!(System::providers(&3).is_zero());
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn non_validator_cant_set_keys() {
|
||||
new_test_ext().execute_with(|| {
|
||||
assert_noop!(
|
||||
ValidatorSet::validate_set_keys(&4),
|
||||
InvalidTransaction::BadSigner
|
||||
);
|
||||
assert_noop!(
|
||||
ValidatorSet::pre_dispatch_set_keys(&4),
|
||||
InvalidTransaction::BadSigner
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn set_keys_has_cooldown() {
|
||||
new_test_ext().execute_with(|| {
|
||||
assert_ok!(ValidatorSet::pre_dispatch_set_keys(&3));
|
||||
assert_noop!(
|
||||
ValidatorSet::pre_dispatch_set_keys(&3),
|
||||
InvalidTransaction::Future
|
||||
);
|
||||
next_block();
|
||||
assert_noop!(
|
||||
ValidatorSet::pre_dispatch_set_keys(&3),
|
||||
InvalidTransaction::Future
|
||||
);
|
||||
next_block();
|
||||
assert_ok!(ValidatorSet::pre_dispatch_set_keys(&3));
|
||||
});
|
||||
}
|
||||
|
|
@ -1,108 +0,0 @@
|
|||
// Copyright (C) Gautam Dhameja.
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Autogenerated weights for Validator Set
|
||||
//!
|
||||
//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev
|
||||
//! DATE: 2023-05-29, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]`
|
||||
//! WORST CASE MAP SIZE: `1000000`
|
||||
//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("local"), DB CACHE: 1024
|
||||
|
||||
// Executed Command:
|
||||
// ./target/release/node-template
|
||||
// benchmark
|
||||
// pallet
|
||||
// --chain
|
||||
// local
|
||||
// --pallet
|
||||
// validator_set
|
||||
// --extrinsic
|
||||
// *
|
||||
// --steps
|
||||
// 50
|
||||
// --repeat
|
||||
// 20
|
||||
// --execution=wasm
|
||||
// --wasm-execution=compiled
|
||||
// --heap-pages=4096
|
||||
|
||||
#![cfg_attr(rustfmt, rustfmt_skip)]
|
||||
#![allow(unused_parens)]
|
||||
#![allow(unused_imports)]
|
||||
#![allow(missing_docs)]
|
||||
|
||||
use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}};
|
||||
use core::marker::PhantomData;
|
||||
|
||||
/// Weight functions needed for validator_set.
|
||||
pub trait WeightInfo {
|
||||
fn add_validator() -> Weight;
|
||||
fn remove_validator() -> Weight;
|
||||
}
|
||||
|
||||
/// Weights for validator_set using the Substrate node and recommended hardware.
|
||||
pub struct SubstrateWeight<T>(PhantomData<T>);
|
||||
impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
|
||||
/// Storage: ValidatorSet Validators (r:1 w:1)
|
||||
/// Proof Skipped: ValidatorSet Validators (max_values: Some(1), max_size: None, mode: Measured)
|
||||
fn add_validator() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `117`
|
||||
// Estimated: `1602`
|
||||
// Minimum execution time: 20_810_000 picoseconds.
|
||||
Weight::from_parts(21_330_000, 1602)
|
||||
.saturating_add(T::DbWeight::get().reads(1_u64))
|
||||
.saturating_add(T::DbWeight::get().writes(1_u64))
|
||||
}
|
||||
/// Storage: ValidatorSet Validators (r:1 w:1)
|
||||
/// Proof Skipped: ValidatorSet Validators (max_values: Some(1), max_size: None, mode: Measured)
|
||||
fn remove_validator() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `117`
|
||||
// Estimated: `1602`
|
||||
// Minimum execution time: 18_700_000 picoseconds.
|
||||
Weight::from_parts(19_840_000, 1602)
|
||||
.saturating_add(T::DbWeight::get().reads(1_u64))
|
||||
.saturating_add(T::DbWeight::get().writes(1_u64))
|
||||
}
|
||||
}
|
||||
|
||||
// For backwards compatibility and tests
|
||||
impl WeightInfo for () {
|
||||
/// Storage: ValidatorSet Validators (r:1 w:1)
|
||||
/// Proof Skipped: ValidatorSet Validators (max_values: Some(1), max_size: None, mode: Measured)
|
||||
fn add_validator() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `117`
|
||||
// Estimated: `1602`
|
||||
// Minimum execution time: 20_810_000 picoseconds.
|
||||
Weight::from_parts(21_330_000, 1602)
|
||||
.saturating_add(RocksDbWeight::get().reads(1_u64))
|
||||
.saturating_add(RocksDbWeight::get().writes(1_u64))
|
||||
}
|
||||
/// Storage: ValidatorSet Validators (r:1 w:1)
|
||||
/// Proof Skipped: ValidatorSet Validators (max_values: Some(1), max_size: None, mode: Measured)
|
||||
fn remove_validator() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `117`
|
||||
// Estimated: `1602`
|
||||
// Minimum execution time: 18_700_000 picoseconds.
|
||||
Weight::from_parts(19_840_000, 1602)
|
||||
.saturating_add(RocksDbWeight::get().reads(1_u64))
|
||||
.saturating_add(RocksDbWeight::get().writes(1_u64))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -10,7 +10,7 @@ version = "0.1.0"
|
|||
[dependencies]
|
||||
frame-support = { workspace = true }
|
||||
frame-system = { workspace = true }
|
||||
pallet-validator-set = { workspace = true }
|
||||
pallet-external-validators = { workspace = true }
|
||||
parity-scale-codec = { workspace = true }
|
||||
snowbridge-core = { workspace = true }
|
||||
snowbridge-inbound-queue-primitives = { workspace = true }
|
||||
|
|
@ -23,6 +23,6 @@ std = [
|
|||
"frame-system/std",
|
||||
"snowbridge-core/std",
|
||||
"parity-scale-codec/std",
|
||||
"pallet-validator-set/std",
|
||||
"pallet-external-validators/std",
|
||||
"sp-std/std",
|
||||
]
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
|
||||
use frame_support::pallet_prelude::*;
|
||||
use frame_system::RawOrigin;
|
||||
use parity_scale_codec::DecodeAll;
|
||||
use snowbridge_inbound_queue_primitives::v2::{Message as SnowbridgeMessage, MessageProcessor};
|
||||
use sp_std::vec::Vec;
|
||||
|
|
@ -11,7 +10,7 @@ pub const EL_MESSAGE_ID: [u8; 4] = [112, 21, 0, 56];
|
|||
#[derive(Encode, Decode)]
|
||||
pub struct Payload<T>
|
||||
where
|
||||
T: pallet_validator_set::Config,
|
||||
T: pallet_external_validators::Config,
|
||||
{
|
||||
message: Message<T>,
|
||||
message_id: [u8; 4],
|
||||
|
|
@ -20,7 +19,7 @@ where
|
|||
#[derive(Encode, Decode)]
|
||||
pub enum Message<T>
|
||||
where
|
||||
T: pallet_validator_set::Config,
|
||||
T: pallet_external_validators::Config,
|
||||
{
|
||||
V1(InboundCommand<T>),
|
||||
}
|
||||
|
|
@ -28,9 +27,12 @@ where
|
|||
#[derive(Encode, Decode)]
|
||||
pub enum InboundCommand<T>
|
||||
where
|
||||
T: pallet_validator_set::Config,
|
||||
T: pallet_external_validators::Config,
|
||||
{
|
||||
SetValidators(Vec<T::AccountId>),
|
||||
ReceiveValidators {
|
||||
validators: Vec<<T as pallet_external_validators::Config>::ValidatorId>,
|
||||
external_index: u64,
|
||||
},
|
||||
}
|
||||
|
||||
/// EigenLayer Message Processor
|
||||
|
|
@ -38,7 +40,7 @@ pub struct EigenLayerMessageProcessor<T>(PhantomData<T>);
|
|||
|
||||
impl<T, AccountId> MessageProcessor<AccountId> for EigenLayerMessageProcessor<T>
|
||||
where
|
||||
T: pallet_validator_set::Config,
|
||||
T: pallet_external_validators::Config,
|
||||
{
|
||||
fn can_process_message(_who: &AccountId, message: &SnowbridgeMessage) -> bool {
|
||||
let payload = match &message.xcm {
|
||||
|
|
@ -75,10 +77,13 @@ where
|
|||
};
|
||||
|
||||
match message {
|
||||
Message::V1(InboundCommand::SetValidators(validators)) => {
|
||||
pallet_validator_set::Pallet::<T>::set_validators(
|
||||
RawOrigin::Root.into(),
|
||||
Message::V1(InboundCommand::ReceiveValidators {
|
||||
validators,
|
||||
external_index,
|
||||
}) => {
|
||||
pallet_external_validators::Pallet::<T>::set_external_validators_inner(
|
||||
validators,
|
||||
external_index,
|
||||
)?;
|
||||
let mut id = [0u8; 32];
|
||||
id[..EL_MESSAGE_ID.len()].copy_from_slice(&EL_MESSAGE_ID);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
/// Time and blocks.
|
||||
pub mod time {
|
||||
use polkadot_primitives::{BlockNumber, Moment};
|
||||
use polkadot_primitives::{BlockNumber, Moment, SessionIndex};
|
||||
use polkadot_runtime_common::prod_or_fast;
|
||||
|
||||
pub const MILLISECS_PER_BLOCK: Moment = 6000;
|
||||
|
|
@ -11,6 +11,7 @@ pub mod time {
|
|||
|
||||
frame_support::parameter_types! {
|
||||
pub const EpochDurationInBlocks: BlockNumber = prod_or_fast!(ONE_HOUR, ONE_MINUTE);
|
||||
pub const SessionsPerEra: SessionIndex = prod_or_fast!(6, 3);
|
||||
}
|
||||
|
||||
// These time units are defined in number of blocks.
|
||||
|
|
|
|||
|
|
@ -39,6 +39,7 @@ pallet-beefy-mmr = { workspace = true }
|
|||
pallet-ethereum = { workspace = true }
|
||||
pallet-evm = { workspace = true }
|
||||
pallet-evm-chain-id = { workspace = true }
|
||||
pallet-external-validators = { workspace = true }
|
||||
pallet-grandpa = { workspace = true }
|
||||
pallet-identity = { workspace = true }
|
||||
pallet-im-online = { workspace = true }
|
||||
|
|
@ -56,7 +57,6 @@ pallet-timestamp = { workspace = true }
|
|||
pallet-transaction-payment = { workspace = true }
|
||||
pallet-transaction-payment-rpc-runtime-api = { workspace = true }
|
||||
pallet-utility = { workspace = true }
|
||||
pallet-validator-set = { workspace = true }
|
||||
polkadot-primitives = { workspace = true }
|
||||
polkadot-runtime-common = { workspace = true }
|
||||
scale-info = { workspace = true, features = ["derive", "serde"] }
|
||||
|
|
@ -122,6 +122,7 @@ std = [
|
|||
"pallet-ethereum/std",
|
||||
"pallet-evm-chain-id/std",
|
||||
"pallet-evm/std",
|
||||
"pallet-external-validators/std",
|
||||
"pallet-grandpa/std",
|
||||
"pallet-identity/std",
|
||||
"pallet-im-online/std",
|
||||
|
|
@ -138,7 +139,6 @@ std = [
|
|||
"pallet-transaction-payment-rpc-runtime-api/std",
|
||||
"pallet-transaction-payment/std",
|
||||
"pallet-utility/std",
|
||||
"pallet-validator-set/std",
|
||||
"polkadot-primitives/std",
|
||||
"polkadot-runtime-common/std",
|
||||
"scale-info/std",
|
||||
|
|
@ -188,6 +188,7 @@ runtime-benchmarks = [
|
|||
"pallet-beefy-mmr/runtime-benchmarks",
|
||||
"pallet-ethereum/runtime-benchmarks",
|
||||
"pallet-evm/runtime-benchmarks",
|
||||
"pallet-external-validators/runtime-benchmarks",
|
||||
"pallet-grandpa/runtime-benchmarks",
|
||||
"pallet-identity/runtime-benchmarks",
|
||||
"pallet-im-online/runtime-benchmarks",
|
||||
|
|
@ -226,6 +227,7 @@ try-runtime = [
|
|||
"pallet-beefy/try-runtime",
|
||||
"pallet-ethereum/try-runtime",
|
||||
"pallet-evm/try-runtime",
|
||||
"pallet-external-validators/try-runtime",
|
||||
"pallet-grandpa/try-runtime",
|
||||
"pallet-identity/try-runtime",
|
||||
"pallet-im-online/try-runtime",
|
||||
|
|
|
|||
|
|
@ -27,16 +27,16 @@ mod runtime_params;
|
|||
|
||||
use super::{
|
||||
deposit, AccountId, Babe, Balance, Balances, BeefyMmrLeaf, Block, BlockNumber,
|
||||
EthereumBeaconClient, EvmChainId, Hash, Historical, ImOnline, MessageQueue, Nonce, Offences,
|
||||
OriginCaller, OutboundCommitmentStore, OutboundQueueV2, PalletInfo, Preimage, Runtime,
|
||||
RuntimeCall, RuntimeEvent, RuntimeFreezeReason, RuntimeHoldReason, RuntimeOrigin, RuntimeTask,
|
||||
Session, SessionKeys, Signature, System, Timestamp, ValidatorSet, EXISTENTIAL_DEPOSIT,
|
||||
EthereumBeaconClient, EvmChainId, ExternalValidators, Hash, Historical, ImOnline, MessageQueue,
|
||||
Nonce, Offences, OriginCaller, OutboundCommitmentStore, OutboundQueueV2, PalletInfo, Preimage,
|
||||
Runtime, RuntimeCall, RuntimeEvent, RuntimeFreezeReason, RuntimeHoldReason, RuntimeOrigin,
|
||||
RuntimeTask, Session, SessionKeys, Signature, System, Timestamp, EXISTENTIAL_DEPOSIT,
|
||||
SLOT_DURATION, STORAGE_BYTE_FEE, SUPPLY_FACTOR, UNIT, VERSION,
|
||||
};
|
||||
use codec::{Decode, Encode};
|
||||
use datahaven_runtime_common::{
|
||||
gas::WEIGHT_PER_GAS,
|
||||
time::{EpochDurationInBlocks, DAYS, MILLISECS_PER_BLOCK, MINUTES},
|
||||
time::{EpochDurationInBlocks, DAYS, MILLISECS_PER_BLOCK},
|
||||
};
|
||||
use dhp_bridge::EigenLayerMessageProcessor;
|
||||
use frame_support::{
|
||||
|
|
@ -87,7 +87,9 @@ use sp_consensus_beefy::{
|
|||
use sp_core::{crypto::KeyTypeId, Get, H160, H256, U256};
|
||||
use sp_runtime::FixedU128;
|
||||
use sp_runtime::{
|
||||
traits::{ConvertInto, IdentityLookup, Keccak256, One, OpaqueKeys, UniqueSaturatedInto},
|
||||
traits::{
|
||||
Convert, ConvertInto, IdentityLookup, Keccak256, One, OpaqueKeys, UniqueSaturatedInto,
|
||||
},
|
||||
FixedPointNumber, Perbill,
|
||||
};
|
||||
use sp_staking::{EraIndex, SessionIndex};
|
||||
|
|
@ -242,12 +244,20 @@ impl pallet_authorship::Config for Runtime {
|
|||
impl pallet_offences::Config for Runtime {
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type IdentificationTuple = pallet_session::historical::IdentificationTuple<Self>;
|
||||
type OnOffenceHandler = ValidatorSet;
|
||||
// TODO set to External Validators Slashs Pallet once it's added to the runtime
|
||||
type OnOffenceHandler = ();
|
||||
}
|
||||
|
||||
pub struct FullIdentificationOf;
|
||||
impl Convert<AccountId, Option<()>> for FullIdentificationOf {
|
||||
fn convert(_: AccountId) -> Option<()> {
|
||||
Some(())
|
||||
}
|
||||
}
|
||||
|
||||
impl pallet_session::historical::Config for Runtime {
|
||||
type FullIdentification = Self::ValidatorId;
|
||||
type FullIdentificationOf = Self::ValidatorIdOf;
|
||||
type FullIdentification = ();
|
||||
type FullIdentificationOf = FullIdentificationOf;
|
||||
}
|
||||
|
||||
impl pallet_session::Config for Runtime {
|
||||
|
|
@ -256,24 +266,12 @@ impl pallet_session::Config for Runtime {
|
|||
type ValidatorIdOf = ConvertInto;
|
||||
type ShouldEndSession = Babe;
|
||||
type NextSessionRotation = Babe;
|
||||
type SessionManager = pallet_session::historical::NoteHistoricalRoot<Self, ValidatorSet>;
|
||||
type SessionManager = pallet_session::historical::NoteHistoricalRoot<Self, ExternalValidators>;
|
||||
type SessionHandler = <SessionKeys as OpaqueKeys>::KeyTypeIdProviders;
|
||||
type Keys = SessionKeys;
|
||||
type WeightInfo = pallet_session::weights::SubstrateWeight<Runtime>;
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
pub const SetKeysCooldownBlocks: BlockNumber = 5 * MINUTES;
|
||||
}
|
||||
|
||||
impl pallet_validator_set::Config for Runtime {
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type AddRemoveOrigin = EnsureRoot<AccountId>;
|
||||
type MaxAuthorities = MaxAuthorities;
|
||||
type SetKeysCooldownBlocks = SetKeysCooldownBlocks;
|
||||
type WeightInfo = pallet_validator_set::weights::SubstrateWeight<Runtime>;
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
pub const ImOnlineUnsignedPriority: TransactionPriority = TransactionPriority::MAX;
|
||||
}
|
||||
|
|
@ -872,3 +870,27 @@ pub mod benchmark_helpers {
|
|||
impl pallet_outbound_commitment_store::Config for Runtime {
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
pub const MaxWhitelistedValidators: u32 = 100;
|
||||
pub const MaxExternalValidators: u32 = 100;
|
||||
}
|
||||
|
||||
impl pallet_external_validators::Config for Runtime {
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type UpdateOrigin = EnsureRoot<AccountId>;
|
||||
type HistoryDepth = ConstU32<84>;
|
||||
type MaxWhitelistedValidators = MaxWhitelistedValidators;
|
||||
type MaxExternalValidators = MaxExternalValidators;
|
||||
type ValidatorId = AccountId;
|
||||
type ValidatorIdOf = ConvertInto;
|
||||
type ValidatorRegistration = Session;
|
||||
type UnixTime = Timestamp;
|
||||
type SessionsPerEra = SessionsPerEra;
|
||||
// TODO: Implement OnEraStart and OnEraEnd when ExternalValidatorsRewards is added
|
||||
type OnEraStart = ();
|
||||
type OnEraEnd = ();
|
||||
type WeightInfo = ();
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
type Currency = Balances;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -59,8 +59,10 @@ fn testnet_genesis(
|
|||
sudo: SudoConfig {
|
||||
key: Some(root_key),
|
||||
},
|
||||
validator_set: pallet_validator_set::GenesisConfig {
|
||||
initial_validators: initial_authorities
|
||||
external_validators: pallet_external_validators::GenesisConfig {
|
||||
skip_external_validators: false,
|
||||
whitelisted_validators: vec![],
|
||||
external_validators: initial_authorities
|
||||
.iter()
|
||||
.map(|(account, ..)| *account)
|
||||
.collect::<Vec<_>>()
|
||||
|
|
|
|||
|
|
@ -252,9 +252,9 @@ mod runtime {
|
|||
#[runtime::pallet_index(6)]
|
||||
pub type Historical = pallet_session::historical;
|
||||
|
||||
// Validator set must be before Session.
|
||||
// External Validators must be before Session.
|
||||
#[runtime::pallet_index(7)]
|
||||
pub type ValidatorSet = pallet_validator_set;
|
||||
pub type ExternalValidators = pallet_external_validators;
|
||||
|
||||
#[runtime::pallet_index(8)]
|
||||
pub type Session = pallet_session;
|
||||
|
|
@ -340,6 +340,7 @@ mod runtime {
|
|||
// Start with index 100
|
||||
#[runtime::pallet_index(100)]
|
||||
pub type OutboundCommitmentStore = pallet_outbound_commitment_store;
|
||||
|
||||
// ╚═══════════════════ DataHaven-specific Pallets ══════════════════╝
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -39,6 +39,7 @@ pallet-beefy-mmr = { workspace = true }
|
|||
pallet-ethereum = { workspace = true }
|
||||
pallet-evm = { workspace = true }
|
||||
pallet-evm-chain-id = { workspace = true }
|
||||
pallet-external-validators = { workspace = true }
|
||||
pallet-grandpa = { workspace = true }
|
||||
pallet-identity = { workspace = true }
|
||||
pallet-im-online = { workspace = true }
|
||||
|
|
@ -56,7 +57,6 @@ pallet-timestamp = { workspace = true }
|
|||
pallet-transaction-payment = { workspace = true }
|
||||
pallet-transaction-payment-rpc-runtime-api = { workspace = true }
|
||||
pallet-utility = { workspace = true }
|
||||
pallet-validator-set = { workspace = true }
|
||||
polkadot-primitives = { workspace = true }
|
||||
polkadot-runtime-common = { workspace = true }
|
||||
scale-info = { workspace = true, features = ["derive", "serde"] }
|
||||
|
|
@ -122,6 +122,7 @@ std = [
|
|||
"pallet-ethereum/std",
|
||||
"pallet-evm-chain-id/std",
|
||||
"pallet-evm/std",
|
||||
"pallet-external-validators/std",
|
||||
"pallet-grandpa/std",
|
||||
"pallet-identity/std",
|
||||
"pallet-im-online/std",
|
||||
|
|
@ -138,7 +139,6 @@ std = [
|
|||
"pallet-transaction-payment-rpc-runtime-api/std",
|
||||
"pallet-transaction-payment/std",
|
||||
"pallet-utility/std",
|
||||
"pallet-validator-set/std",
|
||||
"polkadot-primitives/std",
|
||||
"polkadot-runtime-common/std",
|
||||
"scale-info/std",
|
||||
|
|
@ -188,6 +188,7 @@ runtime-benchmarks = [
|
|||
"pallet-beefy-mmr/runtime-benchmarks",
|
||||
"pallet-ethereum/runtime-benchmarks",
|
||||
"pallet-evm/runtime-benchmarks",
|
||||
"pallet-external-validators/runtime-benchmarks",
|
||||
"pallet-grandpa/runtime-benchmarks",
|
||||
"pallet-identity/runtime-benchmarks",
|
||||
"pallet-im-online/runtime-benchmarks",
|
||||
|
|
@ -226,6 +227,7 @@ try-runtime = [
|
|||
"pallet-beefy/try-runtime",
|
||||
"pallet-ethereum/try-runtime",
|
||||
"pallet-evm/try-runtime",
|
||||
"pallet-external-validators/try-runtime",
|
||||
"pallet-grandpa/try-runtime",
|
||||
"pallet-identity/try-runtime",
|
||||
"pallet-im-online/try-runtime",
|
||||
|
|
|
|||
|
|
@ -27,16 +27,16 @@ mod runtime_params;
|
|||
|
||||
use super::{
|
||||
deposit, AccountId, Babe, Balance, Balances, BeefyMmrLeaf, Block, BlockNumber,
|
||||
EthereumBeaconClient, EvmChainId, Hash, Historical, ImOnline, MessageQueue, Nonce, Offences,
|
||||
OriginCaller, OutboundCommitmentStore, OutboundQueueV2, PalletInfo, Preimage, Runtime,
|
||||
RuntimeCall, RuntimeEvent, RuntimeFreezeReason, RuntimeHoldReason, RuntimeOrigin, RuntimeTask,
|
||||
Session, SessionKeys, Signature, System, Timestamp, ValidatorSet, EXISTENTIAL_DEPOSIT,
|
||||
EthereumBeaconClient, EvmChainId, ExternalValidators, Hash, Historical, ImOnline, MessageQueue,
|
||||
Nonce, Offences, OriginCaller, OutboundCommitmentStore, OutboundQueueV2, PalletInfo, Preimage,
|
||||
Runtime, RuntimeCall, RuntimeEvent, RuntimeFreezeReason, RuntimeHoldReason, RuntimeOrigin,
|
||||
RuntimeTask, Session, SessionKeys, Signature, System, Timestamp, EXISTENTIAL_DEPOSIT,
|
||||
SLOT_DURATION, STORAGE_BYTE_FEE, SUPPLY_FACTOR, UNIT, VERSION,
|
||||
};
|
||||
use codec::{Decode, Encode};
|
||||
use datahaven_runtime_common::{
|
||||
gas::WEIGHT_PER_GAS,
|
||||
time::{EpochDurationInBlocks, DAYS, MILLISECS_PER_BLOCK, MINUTES},
|
||||
time::{EpochDurationInBlocks, DAYS, MILLISECS_PER_BLOCK},
|
||||
};
|
||||
use dhp_bridge::EigenLayerMessageProcessor;
|
||||
use frame_support::{
|
||||
|
|
@ -87,7 +87,9 @@ use sp_consensus_beefy::{
|
|||
use sp_core::{crypto::KeyTypeId, Get, H160, H256, U256};
|
||||
use sp_runtime::FixedU128;
|
||||
use sp_runtime::{
|
||||
traits::{ConvertInto, IdentityLookup, Keccak256, One, OpaqueKeys, UniqueSaturatedInto},
|
||||
traits::{
|
||||
Convert, ConvertInto, IdentityLookup, Keccak256, One, OpaqueKeys, UniqueSaturatedInto,
|
||||
},
|
||||
FixedPointNumber, Perbill,
|
||||
};
|
||||
use sp_staking::{EraIndex, SessionIndex};
|
||||
|
|
@ -242,12 +244,19 @@ impl pallet_authorship::Config for Runtime {
|
|||
impl pallet_offences::Config for Runtime {
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type IdentificationTuple = pallet_session::historical::IdentificationTuple<Self>;
|
||||
type OnOffenceHandler = ValidatorSet;
|
||||
// TODO set to External Validators Slashs Pallet once it's added to the runtime
|
||||
type OnOffenceHandler = ();
|
||||
}
|
||||
|
||||
pub struct FullIdentificationOf;
|
||||
impl Convert<AccountId, Option<()>> for FullIdentificationOf {
|
||||
fn convert(_: AccountId) -> Option<()> {
|
||||
Some(())
|
||||
}
|
||||
}
|
||||
impl pallet_session::historical::Config for Runtime {
|
||||
type FullIdentification = Self::ValidatorId;
|
||||
type FullIdentificationOf = Self::ValidatorIdOf;
|
||||
type FullIdentification = ();
|
||||
type FullIdentificationOf = FullIdentificationOf;
|
||||
}
|
||||
|
||||
impl pallet_session::Config for Runtime {
|
||||
|
|
@ -256,24 +265,12 @@ impl pallet_session::Config for Runtime {
|
|||
type ValidatorIdOf = ConvertInto;
|
||||
type ShouldEndSession = Babe;
|
||||
type NextSessionRotation = Babe;
|
||||
type SessionManager = pallet_session::historical::NoteHistoricalRoot<Self, ValidatorSet>;
|
||||
type SessionManager = pallet_session::historical::NoteHistoricalRoot<Self, ExternalValidators>;
|
||||
type SessionHandler = <SessionKeys as OpaqueKeys>::KeyTypeIdProviders;
|
||||
type Keys = SessionKeys;
|
||||
type WeightInfo = pallet_session::weights::SubstrateWeight<Runtime>;
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
pub const SetKeysCooldownBlocks: BlockNumber = 5 * MINUTES;
|
||||
}
|
||||
|
||||
impl pallet_validator_set::Config for Runtime {
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type AddRemoveOrigin = EnsureRoot<AccountId>;
|
||||
type MaxAuthorities = MaxAuthorities;
|
||||
type SetKeysCooldownBlocks = SetKeysCooldownBlocks;
|
||||
type WeightInfo = pallet_validator_set::weights::SubstrateWeight<Runtime>;
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
pub const ImOnlineUnsignedPriority: TransactionPriority = TransactionPriority::MAX;
|
||||
}
|
||||
|
|
@ -872,3 +869,27 @@ pub mod benchmark_helpers {
|
|||
impl pallet_outbound_commitment_store::Config for Runtime {
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
pub const MaxWhitelistedValidators: u32 = 100;
|
||||
pub const MaxExternalValidators: u32 = 100;
|
||||
}
|
||||
|
||||
impl pallet_external_validators::Config for Runtime {
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type UpdateOrigin = EnsureRoot<AccountId>;
|
||||
type HistoryDepth = ConstU32<84>;
|
||||
type MaxWhitelistedValidators = MaxWhitelistedValidators;
|
||||
type MaxExternalValidators = MaxExternalValidators;
|
||||
type ValidatorId = AccountId;
|
||||
type ValidatorIdOf = ConvertInto;
|
||||
type ValidatorRegistration = Session;
|
||||
type UnixTime = Timestamp;
|
||||
type SessionsPerEra = SessionsPerEra;
|
||||
// TODO: Implement OnEraStart and OnEraEnd when ExternalValidatorsRewards is added
|
||||
type OnEraStart = ();
|
||||
type OnEraEnd = ();
|
||||
type WeightInfo = ();
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
type Currency = Balances;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -59,8 +59,10 @@ fn testnet_genesis(
|
|||
sudo: SudoConfig {
|
||||
key: Some(root_key),
|
||||
},
|
||||
validator_set: pallet_validator_set::GenesisConfig {
|
||||
initial_validators: initial_authorities
|
||||
external_validators: pallet_external_validators::GenesisConfig {
|
||||
skip_external_validators: false,
|
||||
whitelisted_validators: vec![],
|
||||
external_validators: initial_authorities
|
||||
.iter()
|
||||
.map(|(account, ..)| *account)
|
||||
.collect::<Vec<_>>()
|
||||
|
|
|
|||
|
|
@ -252,9 +252,9 @@ mod runtime {
|
|||
#[runtime::pallet_index(6)]
|
||||
pub type Historical = pallet_session::historical;
|
||||
|
||||
// Validator set must be before Session.
|
||||
// External Validators must be before Session.
|
||||
#[runtime::pallet_index(7)]
|
||||
pub type ValidatorSet = pallet_validator_set;
|
||||
pub type ExternalValidators = pallet_external_validators;
|
||||
|
||||
#[runtime::pallet_index(8)]
|
||||
pub type Session = pallet_session;
|
||||
|
|
|
|||
|
|
@ -39,6 +39,7 @@ pallet-beefy-mmr = { workspace = true }
|
|||
pallet-ethereum = { workspace = true }
|
||||
pallet-evm = { workspace = true }
|
||||
pallet-evm-chain-id = { workspace = true }
|
||||
pallet-external-validators = { workspace = true }
|
||||
pallet-grandpa = { workspace = true }
|
||||
pallet-identity = { workspace = true }
|
||||
pallet-im-online = { workspace = true }
|
||||
|
|
@ -56,7 +57,6 @@ pallet-timestamp = { workspace = true }
|
|||
pallet-transaction-payment = { workspace = true }
|
||||
pallet-transaction-payment-rpc-runtime-api = { workspace = true }
|
||||
pallet-utility = { workspace = true }
|
||||
pallet-validator-set = { workspace = true }
|
||||
polkadot-primitives = { workspace = true }
|
||||
polkadot-runtime-common = { workspace = true }
|
||||
scale-info = { workspace = true, features = ["derive", "serde"] }
|
||||
|
|
@ -138,7 +138,6 @@ std = [
|
|||
"pallet-transaction-payment-rpc-runtime-api/std",
|
||||
"pallet-transaction-payment/std",
|
||||
"pallet-utility/std",
|
||||
"pallet-validator-set/std",
|
||||
"polkadot-primitives/std",
|
||||
"polkadot-runtime-common/std",
|
||||
"scale-info/std",
|
||||
|
|
@ -175,6 +174,7 @@ std = [
|
|||
"sp-version/std",
|
||||
"substrate-wasm-builder",
|
||||
"pallet-outbound-commitment-store/std",
|
||||
"pallet-external-validators/std",
|
||||
]
|
||||
|
||||
runtime-benchmarks = [
|
||||
|
|
@ -211,6 +211,7 @@ runtime-benchmarks = [
|
|||
"sp-runtime/runtime-benchmarks",
|
||||
"snowbridge-pallet-system/runtime-benchmarks",
|
||||
"pallet-outbound-commitment-store/runtime-benchmarks",
|
||||
"pallet-external-validators/runtime-benchmarks",
|
||||
]
|
||||
|
||||
try-runtime = [
|
||||
|
|
@ -249,6 +250,7 @@ try-runtime = [
|
|||
"sp-runtime/try-runtime",
|
||||
"snowbridge-pallet-system/try-runtime",
|
||||
"pallet-outbound-commitment-store/try-runtime",
|
||||
"pallet-external-validators/try-runtime",
|
||||
]
|
||||
|
||||
fast-runtime = ["datahaven-runtime-common/fast-runtime"]
|
||||
|
|
|
|||
|
|
@ -27,16 +27,16 @@ mod runtime_params;
|
|||
|
||||
use super::{
|
||||
deposit, AccountId, Babe, Balance, Balances, BeefyMmrLeaf, Block, BlockNumber,
|
||||
EthereumBeaconClient, EvmChainId, Hash, Historical, ImOnline, MessageQueue, Nonce, Offences,
|
||||
OriginCaller, OutboundCommitmentStore, OutboundQueueV2, PalletInfo, Preimage, Runtime,
|
||||
RuntimeCall, RuntimeEvent, RuntimeFreezeReason, RuntimeHoldReason, RuntimeOrigin, RuntimeTask,
|
||||
Session, SessionKeys, Signature, System, Timestamp, ValidatorSet, EXISTENTIAL_DEPOSIT,
|
||||
EthereumBeaconClient, EvmChainId, ExternalValidators, Hash, Historical, ImOnline, MessageQueue,
|
||||
Nonce, Offences, OriginCaller, OutboundCommitmentStore, OutboundQueueV2, PalletInfo, Preimage,
|
||||
Runtime, RuntimeCall, RuntimeEvent, RuntimeFreezeReason, RuntimeHoldReason, RuntimeOrigin,
|
||||
RuntimeTask, Session, SessionKeys, Signature, System, Timestamp, EXISTENTIAL_DEPOSIT,
|
||||
SLOT_DURATION, STORAGE_BYTE_FEE, SUPPLY_FACTOR, UNIT, VERSION,
|
||||
};
|
||||
use codec::{Decode, Encode};
|
||||
use datahaven_runtime_common::{
|
||||
gas::WEIGHT_PER_GAS,
|
||||
time::{EpochDurationInBlocks, DAYS, MILLISECS_PER_BLOCK, MINUTES},
|
||||
time::{EpochDurationInBlocks, DAYS, MILLISECS_PER_BLOCK},
|
||||
};
|
||||
use dhp_bridge::EigenLayerMessageProcessor;
|
||||
use frame_support::{
|
||||
|
|
@ -87,7 +87,9 @@ use sp_consensus_beefy::{
|
|||
use sp_core::{crypto::KeyTypeId, Get, H160, H256, U256};
|
||||
use sp_runtime::FixedU128;
|
||||
use sp_runtime::{
|
||||
traits::{ConvertInto, IdentityLookup, Keccak256, One, OpaqueKeys, UniqueSaturatedInto},
|
||||
traits::{
|
||||
Convert, ConvertInto, IdentityLookup, Keccak256, One, OpaqueKeys, UniqueSaturatedInto,
|
||||
},
|
||||
FixedPointNumber, Perbill,
|
||||
};
|
||||
use sp_staking::{EraIndex, SessionIndex};
|
||||
|
|
@ -242,12 +244,19 @@ impl pallet_authorship::Config for Runtime {
|
|||
impl pallet_offences::Config for Runtime {
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type IdentificationTuple = pallet_session::historical::IdentificationTuple<Self>;
|
||||
type OnOffenceHandler = ValidatorSet;
|
||||
// TODO set to External Validators Slashs Pallet once it's added to the runtime
|
||||
type OnOffenceHandler = ();
|
||||
}
|
||||
|
||||
pub struct FullIdentificationOf;
|
||||
impl Convert<AccountId, Option<()>> for FullIdentificationOf {
|
||||
fn convert(_: AccountId) -> Option<()> {
|
||||
Some(())
|
||||
}
|
||||
}
|
||||
impl pallet_session::historical::Config for Runtime {
|
||||
type FullIdentification = Self::ValidatorId;
|
||||
type FullIdentificationOf = Self::ValidatorIdOf;
|
||||
type FullIdentification = ();
|
||||
type FullIdentificationOf = FullIdentificationOf;
|
||||
}
|
||||
|
||||
impl pallet_session::Config for Runtime {
|
||||
|
|
@ -256,24 +265,12 @@ impl pallet_session::Config for Runtime {
|
|||
type ValidatorIdOf = ConvertInto;
|
||||
type ShouldEndSession = Babe;
|
||||
type NextSessionRotation = Babe;
|
||||
type SessionManager = pallet_session::historical::NoteHistoricalRoot<Self, ValidatorSet>;
|
||||
type SessionManager = pallet_session::historical::NoteHistoricalRoot<Self, ExternalValidators>;
|
||||
type SessionHandler = <SessionKeys as OpaqueKeys>::KeyTypeIdProviders;
|
||||
type Keys = SessionKeys;
|
||||
type WeightInfo = pallet_session::weights::SubstrateWeight<Runtime>;
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
pub const SetKeysCooldownBlocks: BlockNumber = 5 * MINUTES;
|
||||
}
|
||||
|
||||
impl pallet_validator_set::Config for Runtime {
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type AddRemoveOrigin = EnsureRoot<AccountId>;
|
||||
type MaxAuthorities = MaxAuthorities;
|
||||
type SetKeysCooldownBlocks = SetKeysCooldownBlocks;
|
||||
type WeightInfo = pallet_validator_set::weights::SubstrateWeight<Runtime>;
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
pub const ImOnlineUnsignedPriority: TransactionPriority = TransactionPriority::MAX;
|
||||
}
|
||||
|
|
@ -872,3 +869,27 @@ pub mod benchmark_helpers {
|
|||
impl pallet_outbound_commitment_store::Config for Runtime {
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
pub const MaxWhitelistedValidators: u32 = 100;
|
||||
pub const MaxExternalValidators: u32 = 100;
|
||||
}
|
||||
|
||||
impl pallet_external_validators::Config for Runtime {
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type UpdateOrigin = EnsureRoot<AccountId>;
|
||||
type HistoryDepth = ConstU32<84>;
|
||||
type MaxWhitelistedValidators = MaxWhitelistedValidators;
|
||||
type MaxExternalValidators = MaxExternalValidators;
|
||||
type ValidatorId = AccountId;
|
||||
type ValidatorIdOf = ConvertInto;
|
||||
type ValidatorRegistration = Session;
|
||||
type UnixTime = Timestamp;
|
||||
type SessionsPerEra = SessionsPerEra;
|
||||
// TODO: Implement OnEraStart and OnEraEnd when ExternalValidatorsRewards is added
|
||||
type OnEraStart = ();
|
||||
type OnEraEnd = ();
|
||||
type WeightInfo = ();
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
type Currency = Balances;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -59,8 +59,10 @@ fn testnet_genesis(
|
|||
sudo: SudoConfig {
|
||||
key: Some(root_key),
|
||||
},
|
||||
validator_set: pallet_validator_set::GenesisConfig {
|
||||
initial_validators: initial_authorities
|
||||
external_validators: pallet_external_validators::GenesisConfig {
|
||||
skip_external_validators: false,
|
||||
whitelisted_validators: vec![],
|
||||
external_validators: initial_authorities
|
||||
.iter()
|
||||
.map(|(account, ..)| *account)
|
||||
.collect::<Vec<_>>()
|
||||
|
|
|
|||
|
|
@ -252,9 +252,9 @@ mod runtime {
|
|||
#[runtime::pallet_index(6)]
|
||||
pub type Historical = pallet_session::historical;
|
||||
|
||||
// Validator set must be before Session.
|
||||
// External Validators must be before Session.
|
||||
#[runtime::pallet_index(7)]
|
||||
pub type ValidatorSet = pallet_validator_set;
|
||||
pub type ExternalValidators = pallet_external_validators;
|
||||
|
||||
#[runtime::pallet_index(8)]
|
||||
pub type Session = pallet_session;
|
||||
|
|
@ -340,6 +340,7 @@ mod runtime {
|
|||
// Start with index 100
|
||||
#[runtime::pallet_index(100)]
|
||||
pub type OutboundCommitmentStore = pallet_outbound_commitment_store;
|
||||
|
||||
// ╚═══════════════════ DataHaven-specific Pallets ══════════════════╝
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue