mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-07-21 13:15:41 +00:00
New sessions, kill consensus module (#2802)
* Draft of new sessions * Reintroduce tuple impls * Move staking module to new session API * More work on staking and grandpa. * Use iterator to avoid cloning and tuple macro * Make runtime build again * Polish the OpaqueKeys devex * Move consensus logic into system & aura. * Fix up system module * Get build mostly going. Stuck at service.rs * Building again * Update srml/staking/src/lib.rs Co-Authored-By: DemiMarie-parity <48690212+DemiMarie-parity@users.noreply.github.com> * Refactoring out Consensus module, AuthorityIdOf, &c. * Refactored out DigestItem::AuthoritiesChanged. Building. * Remove tentative code * Remove invalid comment * Make Seal opaque and introduce nice methods for handling opaque items. * Start to use proper digest for Aura authorities tracking. * Fix up grandpa, remove system::Raw/Log * Refactor Grandpa to use new logging infrastructure. Also make authorityid/sessionkey static. Switch over to storing authorities in a straight Vec. * Building again * Tidy up some AuthorityIds * Expunge most of the rest of the AuthorityKey confusion. Also, de-generify Babe and re-generify Aura. * Remove cruft * Untangle last of the `AuthorityId`s. * Sort out finality_tracker * Refactor median getting * Apply suggestions from code review Co-Authored-By: Robert Habermeier <rphmeier@gmail.com> * Session tests works * Update core/sr-primitives/src/generic/digest.rs Co-Authored-By: DemiMarie-parity <48690212+DemiMarie-parity@users.noreply.github.com> * Session tests works * Fix for staking from @dvc94ch * log an error * fix test runtime build * Some test fixes * Staking mock update to new session api. * Fix build. * Move OpaqueKeys to primitives. * Use on_initialize instead of check_rotate_session. * Update tests to new staking api. * fixup mock * Fix bond_extra_and_withdraw_unbonded_works. * Fix bond_with_little_staked_value_bounded_by_slot_stake. * Fix bond_with_no_staked_value. * Fix change_controller_works. * Fix less_than_needed_candidates_works. * Fix multi_era_reward_should_work. * Fix nominating_and_rewards_should_work. * Fix nominators_also_get_slashed. * Fix phragmen_large_scale_test. * Fix phragmen_poc_works. * Fix phragmen_score_should_be_accurate_on_large_stakes. * Fix phragmen_should_not_overflow. * Fix reward_destination_works. * Fix rewards_should_work. * Fix sessions_and_eras_should_work. * Fix slot_stake_is_least_staked_validator. * Fix too_many_unbond_calls_should_not_work. * Fix wrong_vote_is_null. * Fix runtime. * Fix wasm runtime build. * Update Cargo.lock * Fix warnings. * Fix grandpa tests. * Fix test-runtime build. * Fix template node build. * Fix stuff. * Update Cargo.lock to fix CI * Re-add missing AuRa logs Runtimes are required to know about every digest they receive ― they panic otherwise. This re-adds support for AuRa pre-runtime digests. * Update core/consensus/babe/src/digest.rs Co-Authored-By: DemiMarie-parity <48690212+DemiMarie-parity@users.noreply.github.com> * Kill log trait and all that jazz. * Refactor staking tests. * Fix ci runtime wasm check. * Line length 120. * Make tests build again * Remove trailing commas in function declarations The `extern_functions!` macro doesn’t like them, perhaps due to a bug in rustc. * Fix type error * Fix compilation errors * Fix a test * Another couple of fixes * Fix another test * More test fixes * Another test fix * Bump runtime. * Wrap long line * Fix build, remove redundant code. * Issue to track TODO * Leave the benchmark code alone. * Fix missing `std::time::{Instant, Duration}` * Indentation * Aura ConsensusLog as enum
This commit is contained in:
@@ -33,7 +33,8 @@
|
||||
//!
|
||||
//! ### Terminology
|
||||
//!
|
||||
//! * **Asset issuance:** The creation of a new asset, whose total supply will belong to the account that issues the asset.
|
||||
//! * **Asset issuance:** The creation of a new asset, whose total supply will belong to the
|
||||
//! account that issues the asset.
|
||||
//! * **Asset transfer:** The action of transferring assets from one account to another.
|
||||
//! * **Asset destruction:** The process of an account removing its entire holding of an asset.
|
||||
//! * **Fungible asset:** An asset whose units are interchangeable.
|
||||
@@ -45,7 +46,8 @@
|
||||
//!
|
||||
//! * Issue a unique asset to its creator's account.
|
||||
//! * Move assets between accounts.
|
||||
//! * Remove an account's balance of an asset when requested by that account's owner and update the asset's total supply.
|
||||
//! * Remove an account's balance of an asset when requested by that account's owner and update
|
||||
//! the asset's total supply.
|
||||
//!
|
||||
//! ## Interface
|
||||
//!
|
||||
@@ -237,7 +239,7 @@ mod tests {
|
||||
use primitives::{
|
||||
BuildStorage,
|
||||
traits::{BlakeTwo256, IdentityLookup},
|
||||
testing::{Digest, DigestItem, Header}
|
||||
testing::Header
|
||||
};
|
||||
|
||||
impl_outer_origin! {
|
||||
@@ -255,12 +257,10 @@ mod tests {
|
||||
type BlockNumber = u64;
|
||||
type Hash = H256;
|
||||
type Hashing = BlakeTwo256;
|
||||
type Digest = Digest;
|
||||
type AccountId = u64;
|
||||
type Lookup = IdentityLookup<Self::AccountId>;
|
||||
type Header = Header;
|
||||
type Event = ();
|
||||
type Log = DigestItem;
|
||||
}
|
||||
impl Trait for Test {
|
||||
type Event = ();
|
||||
|
||||
@@ -10,18 +10,18 @@ serde = { version = "1.0", optional = true }
|
||||
inherents = { package = "substrate-inherents", path = "../../core/inherents", default-features = false }
|
||||
rstd = { package = "sr-std", path = "../../core/sr-std", default-features = false }
|
||||
primitives = { package = "sr-primitives", path = "../../core/sr-primitives", default-features = false }
|
||||
substrate-primitives = { path = "../../core/primitives", default-features = false }
|
||||
srml-support = { path = "../support", default-features = false }
|
||||
system = { package = "srml-system", path = "../system", default-features = false }
|
||||
timestamp = { package = "srml-timestamp", path = "../timestamp", default-features = false }
|
||||
staking = { package = "srml-staking", path = "../staking", default-features = false }
|
||||
session = { package = "srml-session", path = "../session", default-features = false }
|
||||
substrate-consensus-aura-primitives = { path = "../../core/consensus/aura/primitives", default-features = false}
|
||||
|
||||
[dev-dependencies]
|
||||
lazy_static = "1.0"
|
||||
parking_lot = "0.8.0"
|
||||
substrate-primitives = { path = "../../core/primitives" }
|
||||
runtime_io = { package = "sr-io", path = "../../core/sr-io" }
|
||||
consensus = { package = "srml-consensus", path = "../consensus" }
|
||||
|
||||
[features]
|
||||
default = ["std"]
|
||||
@@ -31,8 +31,10 @@ std = [
|
||||
"rstd/std",
|
||||
"srml-support/std",
|
||||
"primitives/std",
|
||||
"substrate-primitives/std",
|
||||
"system/std",
|
||||
"timestamp/std",
|
||||
"staking/std",
|
||||
"inherents/std",
|
||||
"substrate-consensus-aura-primitives/std",
|
||||
]
|
||||
|
||||
@@ -51,19 +51,18 @@
|
||||
pub use timestamp;
|
||||
|
||||
use rstd::{result, prelude::*};
|
||||
use parity_codec::{Encode, Decode};
|
||||
use srml_support::storage::StorageValue;
|
||||
use srml_support::{decl_storage, decl_module};
|
||||
use primitives::traits::{SaturatedConversion, Saturating, Zero, One};
|
||||
use parity_codec::Encode;
|
||||
use srml_support::{decl_storage, decl_module, Parameter, storage::StorageValue};
|
||||
use primitives::{traits::{SaturatedConversion, Saturating, Zero, One, Member}, generic::DigestItem};
|
||||
use timestamp::OnTimestampSet;
|
||||
use rstd::marker::PhantomData;
|
||||
#[cfg(feature = "std")]
|
||||
use timestamp::TimestampInherentData;
|
||||
use inherents::{RuntimeString, InherentIdentifier, InherentData, ProvideInherent, MakeFatalError};
|
||||
#[cfg(feature = "std")]
|
||||
use inherents::{InherentDataProviders, ProvideInherentData};
|
||||
use substrate_consensus_aura_primitives::{AURA_ENGINE_ID, ConsensusLog};
|
||||
#[cfg(feature = "std")]
|
||||
use serde::Serialize;
|
||||
use parity_codec::Decode;
|
||||
|
||||
mod mock;
|
||||
mod tests;
|
||||
@@ -93,20 +92,6 @@ impl AuraInherentData for InherentData {
|
||||
}
|
||||
}
|
||||
|
||||
/// Logs in this module.
|
||||
pub type Log<T> = RawLog<T>;
|
||||
|
||||
/// Logs in this module.
|
||||
///
|
||||
/// The type parameter distinguishes logs belonging to two different runtimes,
|
||||
/// which should not be mixed.
|
||||
#[cfg_attr(feature = "std", derive(Serialize, Debug))]
|
||||
#[derive(Encode, Decode, PartialEq, Eq, Clone)]
|
||||
pub enum RawLog<T> {
|
||||
/// AuRa inherent digests
|
||||
PreRuntime([u8; 4], Vec<u8>, PhantomData<T>),
|
||||
}
|
||||
|
||||
/// Provides the slot duration inherent data for `Aura`.
|
||||
#[cfg(feature = "std")]
|
||||
pub struct InherentDataProvider {
|
||||
@@ -166,12 +151,18 @@ impl HandleReport for () {
|
||||
pub trait Trait: timestamp::Trait {
|
||||
/// The logic for handling reports.
|
||||
type HandleReport: HandleReport;
|
||||
|
||||
/// The identifier type for an authority.
|
||||
type AuthorityId: Member + Parameter + Default;
|
||||
}
|
||||
|
||||
decl_storage! {
|
||||
trait Store for Module<T: Trait> as Aura {
|
||||
/// The last timestamp.
|
||||
LastTimestamp get(last) build(|_| 0.into()): T::Moment;
|
||||
|
||||
/// The current authorities
|
||||
pub Authorities get(authorities) config(): Vec<T::AuthorityId>;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -179,6 +170,37 @@ decl_module! {
|
||||
pub struct Module<T: Trait> for enum Call where origin: T::Origin { }
|
||||
}
|
||||
|
||||
impl<T: Trait> Module<T> {
|
||||
fn change_authorities(new: Vec<T::AuthorityId>) {
|
||||
<Authorities<T>>::put(&new);
|
||||
|
||||
let log: DigestItem<T::Hash> = DigestItem::Consensus(
|
||||
AURA_ENGINE_ID,
|
||||
ConsensusLog::AuthoritiesChange(new).encode()
|
||||
);
|
||||
<system::Module<T>>::deposit_log(log.into());
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Trait> session::OneSessionHandler<T::AccountId> for Module<T> {
|
||||
type Key = T::AuthorityId;
|
||||
fn on_new_session<'a, I: 'a>(changed: bool, validators: I)
|
||||
where I: Iterator<Item=(&'a T::AccountId, T::AuthorityId)>
|
||||
{
|
||||
// instant changes
|
||||
if changed {
|
||||
let next_authorities = validators.map(|(_, k)| k).collect::<Vec<_>>();
|
||||
let last_authorities = <Module<T>>::authorities();
|
||||
if next_authorities != last_authorities {
|
||||
Self::change_authorities(next_authorities);
|
||||
}
|
||||
}
|
||||
}
|
||||
fn on_disabled(_i: usize) {
|
||||
// ignore?
|
||||
}
|
||||
}
|
||||
|
||||
/// A report of skipped authorities in Aura.
|
||||
#[derive(Clone, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "std", derive(Debug))]
|
||||
|
||||
@@ -18,11 +18,11 @@
|
||||
|
||||
#![cfg(test)]
|
||||
|
||||
use primitives::{BuildStorage, traits::IdentityLookup, testing::{Digest, DigestItem, Header, UintAuthorityId}};
|
||||
use primitives::{BuildStorage, traits::IdentityLookup, testing::{Header, UintAuthorityId}};
|
||||
use srml_support::impl_outer_origin;
|
||||
use runtime_io;
|
||||
use substrate_primitives::{H256, Blake2Hasher};
|
||||
use crate::{Trait, Module};
|
||||
use crate::{Trait, Module, GenesisConfig};
|
||||
|
||||
impl_outer_origin!{
|
||||
pub enum Origin for Test {}
|
||||
@@ -32,24 +32,16 @@ impl_outer_origin!{
|
||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||
pub struct Test;
|
||||
|
||||
impl consensus::Trait for Test {
|
||||
type Log = DigestItem;
|
||||
type SessionKey = UintAuthorityId;
|
||||
type InherentOfflineReport = ();
|
||||
}
|
||||
|
||||
impl system::Trait for Test {
|
||||
type Origin = Origin;
|
||||
type Index = u64;
|
||||
type BlockNumber = u64;
|
||||
type Hash = H256;
|
||||
type Hashing = ::primitives::traits::BlakeTwo256;
|
||||
type Digest = Digest;
|
||||
type AccountId = u64;
|
||||
type Lookup = IdentityLookup<Self::AccountId>;
|
||||
type Header = Header;
|
||||
type Event = ();
|
||||
type Log = DigestItem;
|
||||
}
|
||||
|
||||
impl timestamp::Trait for Test {
|
||||
@@ -59,17 +51,17 @@ impl timestamp::Trait for Test {
|
||||
|
||||
impl Trait for Test {
|
||||
type HandleReport = ();
|
||||
type AuthorityId = UintAuthorityId;
|
||||
}
|
||||
|
||||
pub fn new_test_ext(authorities: Vec<u64>) -> runtime_io::TestExternalities<Blake2Hasher> {
|
||||
let mut t = system::GenesisConfig::<Test>::default().build_storage().unwrap().0;
|
||||
t.extend(consensus::GenesisConfig::<Test>{
|
||||
code: vec![],
|
||||
authorities: authorities.into_iter().map(|a| UintAuthorityId(a)).collect(),
|
||||
}.build_storage().unwrap().0);
|
||||
t.extend(timestamp::GenesisConfig::<Test>{
|
||||
minimum_period: 1,
|
||||
}.build_storage().unwrap().0);
|
||||
t.extend(GenesisConfig::<Test>{
|
||||
authorities: authorities.into_iter().map(|a| UintAuthorityId(a)).collect(),
|
||||
}.build_storage().unwrap().0);
|
||||
t.into()
|
||||
}
|
||||
|
||||
|
||||
@@ -14,7 +14,6 @@ primitives = { package = "sr-primitives", path = "../../core/sr-primitives", def
|
||||
srml-support = { path = "../support", default-features = false }
|
||||
system = { package = "srml-system", path = "../system", default-features = false }
|
||||
timestamp = { package = "srml-timestamp", path = "../timestamp", default-features = false }
|
||||
staking = { package = "srml-staking", path = "../staking", default-features = false }
|
||||
session = { package = "srml-session", path = "../session", default-features = false }
|
||||
babe-primitives = { package = "substrate-consensus-babe-primitives", path = "../../core/consensus/babe/primitives", default-features = false }
|
||||
|
||||
@@ -23,7 +22,6 @@ lazy_static = "1.3.0"
|
||||
parking_lot = "0.8.0"
|
||||
substrate-primitives = { path = "../../core/primitives" }
|
||||
runtime_io = { package = "sr-io", path = "../../core/sr-io" }
|
||||
consensus = { package = "srml-consensus", path = "../consensus" }
|
||||
|
||||
[features]
|
||||
default = ["std"]
|
||||
@@ -35,7 +33,6 @@ std = [
|
||||
"primitives/std",
|
||||
"system/std",
|
||||
"timestamp/std",
|
||||
"staking/std",
|
||||
"inherents/std",
|
||||
"babe-primitives/std",
|
||||
]
|
||||
|
||||
@@ -20,18 +20,19 @@
|
||||
#![forbid(unsafe_code)]
|
||||
pub use timestamp;
|
||||
|
||||
use rstd::{result, prelude::*, marker::PhantomData};
|
||||
use srml_support::{decl_storage, decl_module};
|
||||
use rstd::{result, prelude::*};
|
||||
use srml_support::{decl_storage, decl_module, StorageValue};
|
||||
use timestamp::{OnTimestampSet, Trait};
|
||||
use primitives::traits::{SaturatedConversion, Saturating};
|
||||
use primitives::{generic::DigestItem, traits::{SaturatedConversion, Saturating}};
|
||||
#[cfg(feature = "std")]
|
||||
use timestamp::TimestampInherentData;
|
||||
use parity_codec::{Encode, Decode};
|
||||
use inherents::{RuntimeString, InherentIdentifier, InherentData, ProvideInherent, MakeFatalError};
|
||||
#[cfg(feature = "std")]
|
||||
use inherents::{InherentDataProviders, ProvideInherentData};
|
||||
#[cfg(feature = "std")]
|
||||
use serde::Serialize;
|
||||
use babe_primitives::BABE_ENGINE_ID;
|
||||
|
||||
pub use babe_primitives::AuthorityId;
|
||||
|
||||
/// The BABE inherent identifier.
|
||||
pub const INHERENT_IDENTIFIER: InherentIdentifier = *b"babeslot";
|
||||
@@ -58,20 +59,6 @@ impl BabeInherentData for InherentData {
|
||||
}
|
||||
}
|
||||
|
||||
/// Logs in this module.
|
||||
pub type Log<T> = RawLog<T>;
|
||||
|
||||
/// Logs in this module.
|
||||
///
|
||||
/// The type parameter distinguishes logs belonging to two different runtimes,
|
||||
/// which should not be mixed.
|
||||
#[cfg_attr(feature = "std", derive(Serialize, Debug))]
|
||||
#[derive(Encode, Decode, PartialEq, Eq, Clone)]
|
||||
pub enum RawLog<T> {
|
||||
/// BABE inherent digests
|
||||
PreRuntime([u8; 4], Vec<u8>, PhantomData<T>),
|
||||
}
|
||||
|
||||
/// Provides the slot duration inherent data for BABE.
|
||||
#[cfg(feature = "std")]
|
||||
pub struct InherentDataProvider {
|
||||
@@ -121,8 +108,11 @@ impl ProvideInherentData for InherentDataProvider {
|
||||
|
||||
decl_storage! {
|
||||
trait Store for Module<T: Trait> as Babe {
|
||||
// The last timestamp.
|
||||
/// The last timestamp.
|
||||
LastTimestamp get(last): T::Moment;
|
||||
|
||||
/// The current authorities set.
|
||||
Authorities get(authorities): Vec<AuthorityId>;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -143,6 +133,34 @@ impl<T: Trait> OnTimestampSet<T::Moment> for Module<T> {
|
||||
fn on_timestamp_set(_moment: T::Moment) { }
|
||||
}
|
||||
|
||||
impl<T: Trait> Module<T> {
|
||||
fn change_authorities(new: Vec<AuthorityId>) {
|
||||
<Authorities<T>>::put(&new);
|
||||
|
||||
let log: DigestItem<T::Hash> = DigestItem::Consensus(BABE_ENGINE_ID, new.encode());
|
||||
<system::Module<T>>::deposit_log(log.into());
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Trait> session::OneSessionHandler<T::AccountId> for Module<T> {
|
||||
type Key = AuthorityId;
|
||||
fn on_new_session<'a, I: 'a>(changed: bool, validators: I)
|
||||
where I: Iterator<Item=(&'a T::AccountId, AuthorityId)>
|
||||
{
|
||||
// instant changes
|
||||
if changed {
|
||||
let next_authorities = validators.map(|(_, k)| k).collect::<Vec<_>>();
|
||||
let last_authorities = <Module<T>>::authorities();
|
||||
if next_authorities != last_authorities {
|
||||
Self::change_authorities(next_authorities);
|
||||
}
|
||||
}
|
||||
}
|
||||
fn on_disabled(_i: usize) {
|
||||
// ignore?
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Trait> ProvideInherent for Module<T> {
|
||||
type Call = timestamp::Call<T>;
|
||||
type Error = MakeFatalError<RuntimeString>;
|
||||
|
||||
@@ -675,12 +675,10 @@ impl<T: Subtrait<I>, I: Instance> system::Trait for ElevatedTrait<T, I> {
|
||||
type BlockNumber = T::BlockNumber;
|
||||
type Hash = T::Hash;
|
||||
type Hashing = T::Hashing;
|
||||
type Digest = T::Digest;
|
||||
type AccountId = T::AccountId;
|
||||
type Lookup = T::Lookup;
|
||||
type Header = T::Header;
|
||||
type Event = ();
|
||||
type Log = T::Log;
|
||||
}
|
||||
impl<T: Subtrait<I>, I: Instance> Trait<I> for ElevatedTrait<T, I> {
|
||||
type Balance = T::Balance;
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
#![cfg(test)]
|
||||
|
||||
use primitives::BuildStorage;
|
||||
use primitives::{traits::{IdentityLookup}, testing::{Digest, DigestItem, Header}};
|
||||
use primitives::{traits::{IdentityLookup}, testing::Header};
|
||||
use substrate_primitives::{H256, Blake2Hasher};
|
||||
use runtime_io;
|
||||
use srml_support::impl_outer_origin;
|
||||
@@ -38,12 +38,10 @@ impl system::Trait for Runtime {
|
||||
type BlockNumber = u64;
|
||||
type Hash = H256;
|
||||
type Hashing = ::primitives::traits::BlakeTwo256;
|
||||
type Digest = Digest;
|
||||
type AccountId = u64;
|
||||
type Lookup = IdentityLookup<Self::AccountId>;
|
||||
type Header = Header;
|
||||
type Event = ();
|
||||
type Log = DigestItem;
|
||||
}
|
||||
impl Trait for Runtime {
|
||||
type Balance = u64;
|
||||
|
||||
@@ -36,7 +36,10 @@ fn basic_locking_should_work() {
|
||||
with_externalities(&mut ExtBuilder::default().existential_deposit(1).monied(true).build(), || {
|
||||
assert_eq!(Balances::free_balance(&1), 10);
|
||||
Balances::set_lock(ID_1, &1, 9, u64::max_value(), WithdrawReasons::all());
|
||||
assert_noop!(<Balances as Currency<_>>::transfer(&1, &2, 5), "account liquidity restrictions prevent withdrawal");
|
||||
assert_noop!(
|
||||
<Balances as Currency<_>>::transfer(&1, &2, 5),
|
||||
"account liquidity restrictions prevent withdrawal"
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -89,11 +92,20 @@ fn combination_locking_should_work() {
|
||||
fn lock_value_extension_should_work() {
|
||||
with_externalities(&mut ExtBuilder::default().existential_deposit(1).monied(true).build(), || {
|
||||
Balances::set_lock(ID_1, &1, 5, u64::max_value(), WithdrawReasons::all());
|
||||
assert_noop!(<Balances as Currency<_>>::transfer(&1, &2, 6), "account liquidity restrictions prevent withdrawal");
|
||||
assert_noop!(
|
||||
<Balances as Currency<_>>::transfer(&1, &2, 6),
|
||||
"account liquidity restrictions prevent withdrawal"
|
||||
);
|
||||
Balances::extend_lock(ID_1, &1, 2, u64::max_value(), WithdrawReasons::all());
|
||||
assert_noop!(<Balances as Currency<_>>::transfer(&1, &2, 6), "account liquidity restrictions prevent withdrawal");
|
||||
assert_noop!(
|
||||
<Balances as Currency<_>>::transfer(&1, &2, 6),
|
||||
"account liquidity restrictions prevent withdrawal"
|
||||
);
|
||||
Balances::extend_lock(ID_1, &1, 8, u64::max_value(), WithdrawReasons::all());
|
||||
assert_noop!(<Balances as Currency<_>>::transfer(&1, &2, 3), "account liquidity restrictions prevent withdrawal");
|
||||
assert_noop!(
|
||||
<Balances as Currency<_>>::transfer(&1, &2, 3),
|
||||
"account liquidity restrictions prevent withdrawal"
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -101,19 +113,28 @@ fn lock_value_extension_should_work() {
|
||||
fn lock_reasons_should_work() {
|
||||
with_externalities(&mut ExtBuilder::default().existential_deposit(1).monied(true).transaction_fees(0, 1).build(), || {
|
||||
Balances::set_lock(ID_1, &1, 10, u64::max_value(), WithdrawReason::Transfer.into());
|
||||
assert_noop!(<Balances as Currency<_>>::transfer(&1, &2, 1), "account liquidity restrictions prevent withdrawal");
|
||||
assert_noop!(
|
||||
<Balances as Currency<_>>::transfer(&1, &2, 1),
|
||||
"account liquidity restrictions prevent withdrawal"
|
||||
);
|
||||
assert_ok!(<Balances as ReservableCurrency<_>>::reserve(&1, 1));
|
||||
assert_ok!(<Balances as MakePayment<_>>::make_payment(&1, 1));
|
||||
|
||||
Balances::set_lock(ID_1, &1, 10, u64::max_value(), WithdrawReason::Reserve.into());
|
||||
assert_ok!(<Balances as Currency<_>>::transfer(&1, &2, 1));
|
||||
assert_noop!(<Balances as ReservableCurrency<_>>::reserve(&1, 1), "account liquidity restrictions prevent withdrawal");
|
||||
assert_noop!(
|
||||
<Balances as ReservableCurrency<_>>::reserve(&1, 1),
|
||||
"account liquidity restrictions prevent withdrawal"
|
||||
);
|
||||
assert_ok!(<Balances as MakePayment<_>>::make_payment(&1, 1));
|
||||
|
||||
Balances::set_lock(ID_1, &1, 10, u64::max_value(), WithdrawReason::TransactionPayment.into());
|
||||
assert_ok!(<Balances as Currency<_>>::transfer(&1, &2, 1));
|
||||
assert_ok!(<Balances as ReservableCurrency<_>>::reserve(&1, 1));
|
||||
assert_noop!(<Balances as MakePayment<_>>::make_payment(&1, 1), "account liquidity restrictions prevent withdrawal");
|
||||
assert_noop!(
|
||||
<Balances as MakePayment<_>>::make_payment(&1, 1),
|
||||
"account liquidity restrictions prevent withdrawal"
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -121,7 +142,10 @@ fn lock_reasons_should_work() {
|
||||
fn lock_block_number_should_work() {
|
||||
with_externalities(&mut ExtBuilder::default().existential_deposit(1).monied(true).build(), || {
|
||||
Balances::set_lock(ID_1, &1, 10, 2, WithdrawReasons::all());
|
||||
assert_noop!(<Balances as Currency<_>>::transfer(&1, &2, 1), "account liquidity restrictions prevent withdrawal");
|
||||
assert_noop!(
|
||||
<Balances as Currency<_>>::transfer(&1, &2, 1),
|
||||
"account liquidity restrictions prevent withdrawal"
|
||||
);
|
||||
|
||||
System::set_block_number(2);
|
||||
assert_ok!(<Balances as Currency<_>>::transfer(&1, &2, 1));
|
||||
@@ -132,12 +156,21 @@ fn lock_block_number_should_work() {
|
||||
fn lock_block_number_extension_should_work() {
|
||||
with_externalities(&mut ExtBuilder::default().existential_deposit(1).monied(true).build(), || {
|
||||
Balances::set_lock(ID_1, &1, 10, 2, WithdrawReasons::all());
|
||||
assert_noop!(<Balances as Currency<_>>::transfer(&1, &2, 6), "account liquidity restrictions prevent withdrawal");
|
||||
assert_noop!(
|
||||
<Balances as Currency<_>>::transfer(&1, &2, 6),
|
||||
"account liquidity restrictions prevent withdrawal"
|
||||
);
|
||||
Balances::extend_lock(ID_1, &1, 10, 1, WithdrawReasons::all());
|
||||
assert_noop!(<Balances as Currency<_>>::transfer(&1, &2, 6), "account liquidity restrictions prevent withdrawal");
|
||||
assert_noop!(
|
||||
<Balances as Currency<_>>::transfer(&1, &2, 6),
|
||||
"account liquidity restrictions prevent withdrawal"
|
||||
);
|
||||
System::set_block_number(2);
|
||||
Balances::extend_lock(ID_1, &1, 10, 8, WithdrawReasons::all());
|
||||
assert_noop!(<Balances as Currency<_>>::transfer(&1, &2, 3), "account liquidity restrictions prevent withdrawal");
|
||||
assert_noop!(
|
||||
<Balances as Currency<_>>::transfer(&1, &2, 3),
|
||||
"account liquidity restrictions prevent withdrawal"
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -145,11 +178,20 @@ fn lock_block_number_extension_should_work() {
|
||||
fn lock_reasons_extension_should_work() {
|
||||
with_externalities(&mut ExtBuilder::default().existential_deposit(1).monied(true).build(), || {
|
||||
Balances::set_lock(ID_1, &1, 10, 10, WithdrawReason::Transfer.into());
|
||||
assert_noop!(<Balances as Currency<_>>::transfer(&1, &2, 6), "account liquidity restrictions prevent withdrawal");
|
||||
assert_noop!(
|
||||
<Balances as Currency<_>>::transfer(&1, &2, 6),
|
||||
"account liquidity restrictions prevent withdrawal"
|
||||
);
|
||||
Balances::extend_lock(ID_1, &1, 10, 10, WithdrawReasons::none());
|
||||
assert_noop!(<Balances as Currency<_>>::transfer(&1, &2, 6), "account liquidity restrictions prevent withdrawal");
|
||||
assert_noop!(
|
||||
<Balances as Currency<_>>::transfer(&1, &2, 6),
|
||||
"account liquidity restrictions prevent withdrawal"
|
||||
);
|
||||
Balances::extend_lock(ID_1, &1, 10, 10, WithdrawReason::Reserve.into());
|
||||
assert_noop!(<Balances as Currency<_>>::transfer(&1, &2, 6), "account liquidity restrictions prevent withdrawal");
|
||||
assert_noop!(
|
||||
<Balances as Currency<_>>::transfer(&1, &2, 6),
|
||||
"account liquidity restrictions prevent withdrawal"
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
[package]
|
||||
name = "srml-consensus"
|
||||
version = "2.0.0"
|
||||
authors = ["Parity Technologies <admin@parity.io>"]
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
serde = { version = "1.0", optional = true, features = ["derive"] }
|
||||
parity-codec = { version = "3.3", default-features = false, features = ["derive"] }
|
||||
substrate-primitives = { path = "../../core/primitives", default-features = false }
|
||||
inherents = { package = "substrate-inherents", path = "../../core/inherents", default-features = false }
|
||||
rstd = { package = "sr-std", path = "../../core/sr-std", default-features = false }
|
||||
primitives = { package = "sr-primitives", path = "../../core/sr-primitives", default-features = false }
|
||||
srml-support = { path = "../support", default-features = false }
|
||||
system = { package = "srml-system", path = "../system", default-features = false }
|
||||
|
||||
[dev-dependencies]
|
||||
runtime_io = { package = "sr-io", path = "../../core/sr-io" }
|
||||
|
||||
[features]
|
||||
default = ["std"]
|
||||
std = [
|
||||
"serde",
|
||||
"parity-codec/std",
|
||||
"substrate-primitives/std",
|
||||
"rstd/std",
|
||||
"srml-support/std",
|
||||
"primitives/std",
|
||||
"system/std",
|
||||
"inherents/std",
|
||||
]
|
||||
@@ -1,438 +0,0 @@
|
||||
// Copyright 2017-2019 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Substrate 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.
|
||||
|
||||
// Substrate 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 Substrate. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! # Consensus Module
|
||||
//!
|
||||
//! - [`consensus::Trait`](./trait.Trait.html)
|
||||
//! - [`Call`](./enum.Call.html)
|
||||
//! - [`Module`](./struct.Module.html)
|
||||
//!
|
||||
//! ## Overview
|
||||
//!
|
||||
//! The consensus module manages the authority set for the native code. It provides support for reporting offline
|
||||
//! behavior among validators and logging changes in the validator authority set.
|
||||
//!
|
||||
//! ## Interface
|
||||
//!
|
||||
//! ### Dispatchable Functions
|
||||
//!
|
||||
//! - `report_misbehavior` - Report some misbehavior. The origin of this call must be signed.
|
||||
//! - `note_offline` - Note that the previous block's validator missed its opportunity to propose a block.
|
||||
//! The origin of this call must be an inherent.
|
||||
//! - `remark` - Make some on-chain remark. The origin of this call must be signed.
|
||||
//! - `set_heap_pages` - Set the number of pages in the WebAssembly environment's heap.
|
||||
//! - `set_code` - Set the new code.
|
||||
//! - `set_storage` - Set some items of storage.
|
||||
//!
|
||||
//! ### Public Functions
|
||||
//!
|
||||
//! - `authorities` - Get the current set of authorities. These are the session keys.
|
||||
//! - `set_authorities` - Set the current set of authorities' session keys.
|
||||
//! - `set_authority_count` - Set the total number of authorities.
|
||||
//! - `set_authority` - Set a single authority by index.
|
||||
//!
|
||||
//! ## Usage
|
||||
//!
|
||||
//! ### Simple Code Snippet
|
||||
//!
|
||||
//! Set authorities:
|
||||
//!
|
||||
//! ```
|
||||
//! # use srml_consensus as consensus;
|
||||
//! # fn not_executed<T: consensus::Trait>() {
|
||||
//! # let authority1 = T::SessionKey::default();
|
||||
//! # let authority2 = T::SessionKey::default();
|
||||
//! <consensus::Module<T>>::set_authorities(&[authority1, authority2])
|
||||
//! # }
|
||||
//! ```
|
||||
//!
|
||||
//! Log changes in the authorities set:
|
||||
//!
|
||||
//! ```
|
||||
//! # use srml_consensus as consensus;
|
||||
//! # use primitives::traits::Zero;
|
||||
//! # use primitives::traits::OnFinalize;
|
||||
//! # fn not_executed<T: consensus::Trait>() {
|
||||
//! <consensus::Module<T>>::on_finalize(T::BlockNumber::zero());
|
||||
//! # }
|
||||
//! ```
|
||||
//!
|
||||
//! ### Example from SRML
|
||||
//!
|
||||
//! In the staking module, the `consensus::OnOfflineReport` is implemented to monitor offline
|
||||
//! reporting among validators:
|
||||
//!
|
||||
//! ```
|
||||
//! # use srml_consensus as consensus;
|
||||
//! # trait Trait: consensus::Trait {
|
||||
//! # }
|
||||
//! #
|
||||
//! # srml_support::decl_module! {
|
||||
//! # pub struct Module<T: Trait> for enum Call where origin: T::Origin {
|
||||
//! # }
|
||||
//! # }
|
||||
//! #
|
||||
//! impl<T: Trait> consensus::OnOfflineReport<Vec<u32>> for Module<T> {
|
||||
//! fn handle_report(reported_indices: Vec<u32>) {
|
||||
//! for validator_index in reported_indices {
|
||||
//! // Get validator from session module
|
||||
//! // Process validator
|
||||
//! }
|
||||
//! }
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! In the GRANDPA module, we use `srml-consensus` to get the set of `next_authorities` before changing
|
||||
//! this set according to the consensus algorithm (which does not rotate sessions in the *normal* way):
|
||||
//!
|
||||
//! ```
|
||||
//! # use srml_consensus as consensus;
|
||||
//! # use consensus::Trait;
|
||||
//! # fn not_executed<T: consensus::Trait>() {
|
||||
//! let next_authorities = <consensus::Module<T>>::authorities()
|
||||
//! .into_iter()
|
||||
//! .map(|key| (key, 1)) // evenly-weighted.
|
||||
//! .collect::<Vec<(<T as Trait>::SessionKey, u64)>>();
|
||||
//! # }
|
||||
//! ```
|
||||
//!
|
||||
//! ## Related Modules
|
||||
//!
|
||||
//! - [Staking](../srml_staking/index.html): This module uses `srml-consensus` to monitor offline
|
||||
//! reporting among validators.
|
||||
//! - [Aura](../srml_aura/index.html): This module does not relate directly to `srml-consensus`,
|
||||
//! but serves to manage offline reporting for the Aura consensus algorithm with its own `handle_report` method.
|
||||
//! - [Grandpa](../srml_grandpa/index.html): Although GRANDPA does its own voter-set management,
|
||||
//! it has a mode where it can track `consensus`, if desired.
|
||||
//!
|
||||
//! ## References
|
||||
//!
|
||||
//! If you're interested in hacking on this module, it is useful to understand the interaction with
|
||||
//! `substrate/core/inherents/src/lib.rs` and, specifically, the required implementation of `ProvideInherent`
|
||||
//! to create and check inherents.
|
||||
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
use serde::Serialize;
|
||||
use rstd::prelude::*;
|
||||
use parity_codec as codec;
|
||||
use codec::{Encode, Decode};
|
||||
use srml_support::{storage, Parameter, decl_storage, decl_module};
|
||||
use srml_support::storage::StorageValue;
|
||||
use srml_support::storage::unhashed::StorageVec;
|
||||
use primitives::traits::{MaybeSerializeDebug, Member};
|
||||
use substrate_primitives::storage::well_known_keys;
|
||||
use system::{ensure_signed, ensure_none};
|
||||
use inherents::{
|
||||
ProvideInherent, InherentData, InherentIdentifier, RuntimeString, MakeFatalError
|
||||
};
|
||||
|
||||
#[cfg(any(feature = "std", test))]
|
||||
use substrate_primitives::sr25519::Public as AuthorityId;
|
||||
|
||||
mod mock;
|
||||
mod tests;
|
||||
|
||||
/// The identifier for consensus inherents.
|
||||
pub const INHERENT_IDENTIFIER: InherentIdentifier = *b"offlrep0";
|
||||
|
||||
/// The error type used by this inherent.
|
||||
pub type InherentError = RuntimeString;
|
||||
|
||||
struct AuthorityStorageVec<S: codec::Codec + Default>(rstd::marker::PhantomData<S>);
|
||||
impl<S: codec::Codec + Default> StorageVec for AuthorityStorageVec<S> {
|
||||
type Item = S;
|
||||
const PREFIX: &'static [u8] = well_known_keys::AUTHORITY_PREFIX;
|
||||
}
|
||||
|
||||
pub type Key = Vec<u8>;
|
||||
pub type KeyValue = (Vec<u8>, Vec<u8>);
|
||||
|
||||
/// Handling offline validator reports in a generic way.
|
||||
pub trait OnOfflineReport<Offline> {
|
||||
fn handle_report(offline: Offline);
|
||||
}
|
||||
|
||||
impl<T> OnOfflineReport<T> for () {
|
||||
fn handle_report(_: T) {}
|
||||
}
|
||||
|
||||
/// Describes the offline-reporting extrinsic.
|
||||
pub trait InherentOfflineReport {
|
||||
/// The report data type passed to the runtime during block authorship.
|
||||
type Inherent: codec::Codec + Parameter;
|
||||
|
||||
/// Whether an inherent is empty and doesn't need to be included.
|
||||
fn is_empty(inherent: &Self::Inherent) -> bool;
|
||||
|
||||
/// Handle the report.
|
||||
fn handle_report(report: Self::Inherent);
|
||||
|
||||
/// Whether two reports are compatible.
|
||||
fn check_inherent(contained: &Self::Inherent, expected: &Self::Inherent) -> Result<(), &'static str>;
|
||||
}
|
||||
|
||||
impl InherentOfflineReport for () {
|
||||
type Inherent = ();
|
||||
|
||||
fn is_empty(_inherent: &()) -> bool { true }
|
||||
fn handle_report(_: ()) { }
|
||||
fn check_inherent(_: &(), _: &()) -> Result<(), &'static str> {
|
||||
Err("Explicit reporting not allowed")
|
||||
}
|
||||
}
|
||||
|
||||
/// A variant of the `OfflineReport` that is useful for instant-finality blocks.
|
||||
///
|
||||
/// This assumes blocks are only finalized.
|
||||
pub struct InstantFinalityReportVec<T>(::rstd::marker::PhantomData<T>);
|
||||
|
||||
impl<T: OnOfflineReport<Vec<u32>>> InherentOfflineReport for InstantFinalityReportVec<T> {
|
||||
type Inherent = Vec<u32>;
|
||||
|
||||
fn is_empty(inherent: &Self::Inherent) -> bool { inherent.is_empty() }
|
||||
|
||||
fn handle_report(report: Vec<u32>) {
|
||||
T::handle_report(report)
|
||||
}
|
||||
|
||||
fn check_inherent(contained: &Self::Inherent, expected: &Self::Inherent) -> Result<(), &'static str> {
|
||||
contained.iter().try_for_each(|n|
|
||||
if !expected.contains(n) {
|
||||
Err("Node we believe online marked offline")
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Logs in this module.
|
||||
pub type Log<T> = RawLog<
|
||||
<T as Trait>::SessionKey,
|
||||
>;
|
||||
|
||||
/// Logs in this module.
|
||||
#[cfg_attr(feature = "std", derive(Serialize, Debug))]
|
||||
#[derive(Encode, Decode, PartialEq, Eq, Clone)]
|
||||
pub enum RawLog<SessionKey> {
|
||||
/// Authorities set has been changed. Contains the new set of authorities.
|
||||
AuthoritiesChange(Vec<SessionKey>),
|
||||
}
|
||||
|
||||
impl<SessionKey: Member> RawLog<SessionKey> {
|
||||
/// Try to cast the log entry as AuthoritiesChange log entry.
|
||||
pub fn as_authorities_change(&self) -> Option<&[SessionKey]> {
|
||||
match *self {
|
||||
RawLog::AuthoritiesChange(ref item) => Some(item),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Implementation for tests outside of this crate.
|
||||
#[cfg(any(feature = "std", test))]
|
||||
impl<N> From<RawLog<N>> for primitives::testing::DigestItem where N: Into<AuthorityId> {
|
||||
fn from(log: RawLog<N>) -> primitives::testing::DigestItem {
|
||||
match log {
|
||||
RawLog::AuthoritiesChange(authorities) =>
|
||||
primitives::generic::DigestItem::AuthoritiesChange(
|
||||
authorities.into_iter()
|
||||
.map(Into::into).collect()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Trait: system::Trait {
|
||||
/// Type for all log entries of this module.
|
||||
type Log: From<Log<Self>> + Into<system::DigestItemOf<Self>>;
|
||||
|
||||
type SessionKey: Parameter + Default + MaybeSerializeDebug;
|
||||
/// Defines the offline-report type of the trait.
|
||||
/// Set to `()` if offline-reports aren't needed for this runtime.
|
||||
type InherentOfflineReport: InherentOfflineReport;
|
||||
}
|
||||
|
||||
decl_storage! {
|
||||
trait Store for Module<T: Trait> as Consensus {
|
||||
// Actual authorities set at the block execution start. Is `Some` iff
|
||||
// the set has been changed.
|
||||
OriginalAuthorities: Option<Vec<T::SessionKey>>;
|
||||
}
|
||||
add_extra_genesis {
|
||||
config(authorities): Vec<T::SessionKey>;
|
||||
#[serde(with = "substrate_primitives::bytes")]
|
||||
config(code): Vec<u8>;
|
||||
|
||||
build(|storage: &mut primitives::StorageOverlay, _: &mut primitives::ChildrenStorageOverlay, config: &GenesisConfig<T>| {
|
||||
use codec::{Encode, KeyedVec};
|
||||
|
||||
let auth_count = config.authorities.len() as u32;
|
||||
config.authorities.iter().enumerate().for_each(|(i, v)| {
|
||||
storage.insert((i as u32).to_keyed_vec(well_known_keys::AUTHORITY_PREFIX), v.encode());
|
||||
});
|
||||
storage.insert(well_known_keys::AUTHORITY_COUNT.to_vec(), auth_count.encode());
|
||||
storage.insert(well_known_keys::CODE.to_vec(), config.code.clone());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
decl_module! {
|
||||
pub struct Module<T: Trait> for enum Call where origin: T::Origin {
|
||||
/// Report some misbehavior.
|
||||
fn report_misbehavior(origin, _report: Vec<u8>) {
|
||||
ensure_signed(origin)?;
|
||||
}
|
||||
|
||||
/// Note that the previous block's validator missed its opportunity to propose a block.
|
||||
fn note_offline(origin, offline: <T::InherentOfflineReport as InherentOfflineReport>::Inherent) {
|
||||
ensure_none(origin)?;
|
||||
|
||||
T::InherentOfflineReport::handle_report(offline);
|
||||
}
|
||||
|
||||
/// Make some on-chain remark.
|
||||
fn remark(origin, _remark: Vec<u8>) {
|
||||
ensure_signed(origin)?;
|
||||
}
|
||||
|
||||
/// Set the number of pages in the WebAssembly environment's heap.
|
||||
fn set_heap_pages(pages: u64) {
|
||||
storage::unhashed::put_raw(well_known_keys::HEAP_PAGES, &pages.encode());
|
||||
}
|
||||
|
||||
/// Set the new code.
|
||||
pub fn set_code(new: Vec<u8>) {
|
||||
storage::unhashed::put_raw(well_known_keys::CODE, &new);
|
||||
}
|
||||
|
||||
/// Set some items of storage.
|
||||
fn set_storage(items: Vec<KeyValue>) {
|
||||
for i in &items {
|
||||
storage::unhashed::put_raw(&i.0, &i.1);
|
||||
}
|
||||
}
|
||||
|
||||
/// Kill some items from storage.
|
||||
fn kill_storage(keys: Vec<Key>) {
|
||||
for key in &keys {
|
||||
storage::unhashed::kill(&key);
|
||||
}
|
||||
}
|
||||
|
||||
fn on_finalize() {
|
||||
if let Some(original_authorities) = <OriginalAuthorities<T>>::take() {
|
||||
let current_authorities = AuthorityStorageVec::<T::SessionKey>::items();
|
||||
if current_authorities != original_authorities {
|
||||
Self::deposit_log(RawLog::AuthoritiesChange(current_authorities));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Trait> Module<T> {
|
||||
/// Get the current set of authorities. These are the session keys.
|
||||
pub fn authorities() -> Vec<T::SessionKey> {
|
||||
AuthorityStorageVec::<T::SessionKey>::items()
|
||||
}
|
||||
|
||||
/// Set the current set of authorities' session keys. Will not exceed the current
|
||||
/// authorities count, even if the given `authorities` is longer.
|
||||
///
|
||||
/// Called by `rotate_session` only.
|
||||
pub fn set_authorities(authorities: &[T::SessionKey]) {
|
||||
let current_authorities = AuthorityStorageVec::<T::SessionKey>::items();
|
||||
if current_authorities != authorities {
|
||||
Self::save_original_authorities(Some(current_authorities));
|
||||
AuthorityStorageVec::<T::SessionKey>::set_items(authorities);
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the total number of authorities.
|
||||
pub fn set_authority_count(count: u32) {
|
||||
Self::save_original_authorities(None);
|
||||
AuthorityStorageVec::<T::SessionKey>::set_count(count);
|
||||
}
|
||||
|
||||
/// Set a single authority by index.
|
||||
pub fn set_authority(index: u32, key: &T::SessionKey) {
|
||||
let current_authority = AuthorityStorageVec::<T::SessionKey>::item(index);
|
||||
if current_authority != *key {
|
||||
Self::save_original_authorities(None);
|
||||
AuthorityStorageVec::<T::SessionKey>::set_item(index, key);
|
||||
}
|
||||
}
|
||||
|
||||
/// Save original authorities set.
|
||||
fn save_original_authorities(current_authorities: Option<Vec<T::SessionKey>>) {
|
||||
if OriginalAuthorities::<T>::get().is_some() {
|
||||
// if we have already saved original set before, do not overwrite
|
||||
return;
|
||||
}
|
||||
|
||||
<OriginalAuthorities<T>>::put(current_authorities.unwrap_or_else(||
|
||||
AuthorityStorageVec::<T::SessionKey>::items()));
|
||||
}
|
||||
|
||||
/// Deposit one of this module's logs.
|
||||
fn deposit_log(log: Log<T>) {
|
||||
<system::Module<T>>::deposit_log(<T as Trait>::Log::from(log).into());
|
||||
}
|
||||
}
|
||||
|
||||
/// Implementing `ProvideInherent` enables this module to create and check inherents.
|
||||
impl<T: Trait> ProvideInherent for Module<T> {
|
||||
/// The call type of the module.
|
||||
type Call = Call<T>;
|
||||
/// The error returned by `check_inherent`.
|
||||
type Error = MakeFatalError<RuntimeString>;
|
||||
/// The inherent identifier used by this inherent.
|
||||
const INHERENT_IDENTIFIER: InherentIdentifier = INHERENT_IDENTIFIER;
|
||||
|
||||
/// Creates an inherent from the `InherentData`.
|
||||
fn create_inherent(data: &InherentData) -> Option<Self::Call> {
|
||||
if let Ok(Some(data)) =
|
||||
data.get_data::<<T::InherentOfflineReport as InherentOfflineReport>::Inherent>(
|
||||
&INHERENT_IDENTIFIER
|
||||
)
|
||||
{
|
||||
if <T::InherentOfflineReport as InherentOfflineReport>::is_empty(&data) {
|
||||
None
|
||||
} else {
|
||||
Some(Call::note_offline(data))
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Verify the validity of the given inherent.
|
||||
fn check_inherent(call: &Self::Call, data: &InherentData) -> Result<(), Self::Error> {
|
||||
let offline = match call {
|
||||
Call::note_offline(ref offline) => offline,
|
||||
_ => return Ok(()),
|
||||
};
|
||||
|
||||
let expected = data
|
||||
.get_data::<<T::InherentOfflineReport as InherentOfflineReport>::Inherent>(&INHERENT_IDENTIFIER)?
|
||||
.ok_or(RuntimeString::from("No `offline_report` found in the inherent data!"))?;
|
||||
|
||||
<T::InherentOfflineReport as InherentOfflineReport>::check_inherent(
|
||||
&offline, &expected
|
||||
).map_err(|e| RuntimeString::from(e).into())
|
||||
}
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
// Copyright 2018-2019 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Substrate 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.
|
||||
|
||||
// Substrate 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 Substrate. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Test utilities
|
||||
|
||||
#![cfg(test)]
|
||||
|
||||
use primitives::{BuildStorage, traits::IdentityLookup, testing::{Digest, DigestItem, Header, UintAuthorityId}};
|
||||
use srml_support::impl_outer_origin;
|
||||
use runtime_io;
|
||||
use substrate_primitives::{H256, Blake2Hasher};
|
||||
use crate::{GenesisConfig, Trait, Module};
|
||||
|
||||
impl_outer_origin!{
|
||||
pub enum Origin for Test {}
|
||||
}
|
||||
|
||||
// Workaround for https://github.com/rust-lang/rust/issues/26925 . Remove when sorted.
|
||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||
pub struct Test;
|
||||
impl Trait for Test {
|
||||
type Log = DigestItem;
|
||||
type SessionKey = UintAuthorityId;
|
||||
type InherentOfflineReport = crate::InstantFinalityReportVec<()>;
|
||||
}
|
||||
impl system::Trait for Test {
|
||||
type Origin = Origin;
|
||||
type Index = u64;
|
||||
type BlockNumber = u64;
|
||||
type Hash = H256;
|
||||
type Hashing = ::primitives::traits::BlakeTwo256;
|
||||
type Digest = Digest;
|
||||
type AccountId = u64;
|
||||
type Lookup = IdentityLookup<Self::AccountId>;
|
||||
type Header = Header;
|
||||
type Event = ();
|
||||
type Log = DigestItem;
|
||||
}
|
||||
|
||||
pub fn new_test_ext(authorities: Vec<u64>) -> runtime_io::TestExternalities<Blake2Hasher> {
|
||||
let mut t = system::GenesisConfig::<Test>::default().build_storage().unwrap().0;
|
||||
t.extend(GenesisConfig::<Test>{
|
||||
code: vec![],
|
||||
authorities: authorities.into_iter().map(|a| UintAuthorityId(a)).collect(),
|
||||
}.build_storage().unwrap().0);
|
||||
t.into()
|
||||
}
|
||||
|
||||
pub type System = system::Module<Test>;
|
||||
pub type Consensus = Module<Test>;
|
||||
@@ -1,131 +0,0 @@
|
||||
// Copyright 2017-2019 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Substrate 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.
|
||||
|
||||
// Substrate 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 Substrate. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Tests for the module.
|
||||
|
||||
#![cfg(test)]
|
||||
|
||||
use primitives::{generic, testing::{self, UintAuthorityId}, traits::OnFinalize};
|
||||
use runtime_io::with_externalities;
|
||||
use crate::mock::{Consensus, System, new_test_ext};
|
||||
use inherents::{InherentData, ProvideInherent};
|
||||
|
||||
#[test]
|
||||
fn authorities_change_logged() {
|
||||
with_externalities(&mut new_test_ext(vec![1, 2, 3]), || {
|
||||
System::initialize(&1, &Default::default(), &Default::default(), &Default::default());
|
||||
Consensus::set_authorities(&[UintAuthorityId(4), UintAuthorityId(5), UintAuthorityId(6)]);
|
||||
Consensus::on_finalize(1);
|
||||
let header = System::finalize();
|
||||
assert_eq!(header.digest, testing::Digest {
|
||||
logs: vec![
|
||||
generic::DigestItem::AuthoritiesChange(
|
||||
vec![
|
||||
UintAuthorityId(4).into(),
|
||||
UintAuthorityId(5).into(),
|
||||
UintAuthorityId(6).into()
|
||||
]
|
||||
),
|
||||
],
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn partial_authorities_change_logged() {
|
||||
with_externalities(&mut new_test_ext(vec![1, 2, 3]), || {
|
||||
System::initialize(&2, &Default::default(), &Default::default(), &Default::default());
|
||||
Consensus::set_authorities(&[UintAuthorityId(2), UintAuthorityId(4), UintAuthorityId(5)]);
|
||||
Consensus::on_finalize(2);
|
||||
let header = System::finalize();
|
||||
assert_eq!(header.digest, testing::Digest {
|
||||
logs: vec![
|
||||
generic::DigestItem::AuthoritiesChange(
|
||||
vec![
|
||||
UintAuthorityId(2).into(),
|
||||
UintAuthorityId(4).into(),
|
||||
UintAuthorityId(5).into()
|
||||
]
|
||||
),
|
||||
],
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn authorities_change_is_not_logged_when_not_changed() {
|
||||
with_externalities(&mut new_test_ext(vec![1, 2, 3]), || {
|
||||
System::initialize(&1, &Default::default(), &Default::default(), &Default::default());
|
||||
Consensus::on_finalize(1);
|
||||
let header = System::finalize();
|
||||
assert_eq!(header.digest, testing::Digest {
|
||||
logs: vec![],
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn authorities_change_is_not_logged_when_changed_back_to_original() {
|
||||
with_externalities(&mut new_test_ext(vec![1, 2, 3]), || {
|
||||
System::initialize(&1, &Default::default(), &Default::default(), &Default::default());
|
||||
Consensus::set_authorities(&[UintAuthorityId(4), UintAuthorityId(5), UintAuthorityId(6)]);
|
||||
Consensus::set_authorities(&[UintAuthorityId(1), UintAuthorityId(2), UintAuthorityId(3)]);
|
||||
Consensus::on_finalize(1);
|
||||
let header = System::finalize();
|
||||
assert_eq!(header.digest, testing::Digest {
|
||||
logs: vec![],
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn offline_report_can_be_excluded() {
|
||||
with_externalities(&mut new_test_ext(vec![1, 2, 3]), || {
|
||||
System::initialize(&1, &Default::default(), &Default::default(), &Default::default());
|
||||
assert!(Consensus::create_inherent(&InherentData::new()).is_none());
|
||||
|
||||
let offline_report: Vec<u32> = vec![0];
|
||||
let mut data = InherentData::new();
|
||||
data.put_data(super::INHERENT_IDENTIFIER, &offline_report).unwrap();
|
||||
|
||||
assert!(Consensus::create_inherent(&data).is_some());
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn set_and_kill_storage_work() {
|
||||
use srml_support::storage;
|
||||
|
||||
with_externalities(&mut new_test_ext(vec![1, 2, 3]), || {
|
||||
System::initialize(&1, &Default::default(), &Default::default(), &Default::default());
|
||||
|
||||
let item = (vec![42u8], vec![42u8]);
|
||||
|
||||
Consensus::set_storage(vec![item.clone()]).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
storage::unhashed::get_raw(&item.0),
|
||||
Some(item.1),
|
||||
);
|
||||
|
||||
Consensus::kill_storage(vec![item.0.clone()]).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
storage::unhashed::get_raw(&item.0),
|
||||
None,
|
||||
);
|
||||
});
|
||||
}
|
||||
@@ -23,7 +23,6 @@ timestamp = { package = "srml-timestamp", path = "../timestamp", default-feature
|
||||
wabt = "~0.7.4"
|
||||
assert_matches = "1.1"
|
||||
hex-literal = "0.2.0"
|
||||
consensus = { package = "srml-consensus", path = "../consensus" }
|
||||
balances = { package = "srml-balances", path = "../balances" }
|
||||
hex = "0.3"
|
||||
|
||||
|
||||
@@ -216,7 +216,8 @@ pub struct RawTombstoneContractInfo<H, Hasher>(H, PhantomData<Hasher>);
|
||||
|
||||
impl<H, Hasher> RawTombstoneContractInfo<H, Hasher>
|
||||
where
|
||||
H: Member + MaybeSerializeDebug + AsRef<[u8]> + AsMut<[u8]> + Copy + Default + rstd::hash::Hash,
|
||||
H: Member + MaybeSerializeDebug + AsRef<[u8]> + AsMut<[u8]> + Copy + Default + rstd::hash::Hash
|
||||
+ Codec,
|
||||
Hasher: Hash<Output=H>,
|
||||
{
|
||||
fn new(storage_root: &[u8], code_hash: H) -> Self {
|
||||
|
||||
@@ -40,7 +40,7 @@ use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
use substrate_primitives::storage::well_known_keys;
|
||||
use substrate_primitives::Blake2Hasher;
|
||||
use system::{self, EventRecord, Phase};
|
||||
use {balances, consensus, wabt};
|
||||
use {balances, wabt};
|
||||
|
||||
mod contract {
|
||||
// Re-export contents of the root. This basically
|
||||
@@ -72,12 +72,10 @@ impl system::Trait for Test {
|
||||
type BlockNumber = u64;
|
||||
type Hash = H256;
|
||||
type Hashing = BlakeTwo256;
|
||||
type Digest = Digest;
|
||||
type AccountId = u64;
|
||||
type Lookup = IdentityLookup<Self::AccountId>;
|
||||
type Header = Header;
|
||||
type Event = MetaEvent;
|
||||
type Log = DigestItem;
|
||||
}
|
||||
impl balances::Trait for Test {
|
||||
type Balance = u64;
|
||||
@@ -92,11 +90,6 @@ impl timestamp::Trait for Test {
|
||||
type Moment = u64;
|
||||
type OnTimestampSet = ();
|
||||
}
|
||||
impl consensus::Trait for Test {
|
||||
type Log = DigestItem;
|
||||
type SessionKey = UintAuthorityId;
|
||||
type InherentOfflineReport = ();
|
||||
}
|
||||
impl Trait for Test {
|
||||
type Currency = Balances;
|
||||
type Call = Call;
|
||||
@@ -1002,7 +995,8 @@ const CODE_CHECK_DEFAULT_RENT_ALLOWANCE: &str = r#"
|
||||
)
|
||||
)
|
||||
"#;
|
||||
const HASH_CHECK_DEFAULT_RENT_ALLOWANCE: [u8; 32] = hex!("4f9ec2b94eea522cfff10b77ef4056c631045c00978a457d283950521ecf07b6");
|
||||
const HASH_CHECK_DEFAULT_RENT_ALLOWANCE: [u8; 32] =
|
||||
hex!("4f9ec2b94eea522cfff10b77ef4056c631045c00978a457d283950521ecf07b6");
|
||||
|
||||
#[test]
|
||||
fn default_rent_allowance_on_create() {
|
||||
|
||||
@@ -73,12 +73,10 @@ mod tests {
|
||||
type BlockNumber = u64;
|
||||
type Hash = H256;
|
||||
type Hashing = BlakeTwo256;
|
||||
type Digest = Digest;
|
||||
type AccountId = u64;
|
||||
type Lookup = IdentityLookup<Self::AccountId>;
|
||||
type Header = Header;
|
||||
type Event = Event;
|
||||
type Log = DigestItem;
|
||||
}
|
||||
impl balances::Trait for Test {
|
||||
type Balance = u64;
|
||||
|
||||
@@ -397,7 +397,12 @@ mod tests {
|
||||
assert_eq!(System::events(), vec![
|
||||
EventRecord {
|
||||
phase: Phase::ApplyExtrinsic(0),
|
||||
event: OuterEvent::motions(RawEvent::Proposed(1, 0, hex!["cd0b662a49f004093b80600415cf4126399af0d27ed6c185abeb1469c17eb5bf"].into(), 3)),
|
||||
event: OuterEvent::motions(RawEvent::Proposed(
|
||||
1,
|
||||
0,
|
||||
hex!["cd0b662a49f004093b80600415cf4126399af0d27ed6c185abeb1469c17eb5bf"].into(),
|
||||
3,
|
||||
)),
|
||||
topics: vec![],
|
||||
}
|
||||
]);
|
||||
@@ -409,7 +414,10 @@ mod tests {
|
||||
with_externalities(&mut ExtBuilder::default().with_council(true).build(), || {
|
||||
System::set_block_number(1);
|
||||
let proposal = set_balance_proposal(42);
|
||||
assert_noop!(CouncilMotions::propose(Origin::signed(42), 3, Box::new(proposal.clone())), "proposer not on council");
|
||||
assert_noop!(
|
||||
CouncilMotions::propose(Origin::signed(42), 3, Box::new(proposal.clone())),
|
||||
"proposer not on council"
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -457,12 +465,23 @@ mod tests {
|
||||
assert_eq!(System::events(), vec![
|
||||
EventRecord {
|
||||
phase: Phase::ApplyExtrinsic(0),
|
||||
event: OuterEvent::motions(RawEvent::Proposed(1, 0, hex!["cd0b662a49f004093b80600415cf4126399af0d27ed6c185abeb1469c17eb5bf"].into(), 2)),
|
||||
event: OuterEvent::motions(RawEvent::Proposed(
|
||||
1,
|
||||
0,
|
||||
hex!["cd0b662a49f004093b80600415cf4126399af0d27ed6c185abeb1469c17eb5bf"].into(),
|
||||
2,
|
||||
)),
|
||||
topics: vec![],
|
||||
},
|
||||
EventRecord {
|
||||
phase: Phase::ApplyExtrinsic(0),
|
||||
event: OuterEvent::motions(RawEvent::Voted(1, hex!["cd0b662a49f004093b80600415cf4126399af0d27ed6c185abeb1469c17eb5bf"].into(), false, 0, 1)),
|
||||
event: OuterEvent::motions(RawEvent::Voted(
|
||||
1,
|
||||
hex!["cd0b662a49f004093b80600415cf4126399af0d27ed6c185abeb1469c17eb5bf"].into(),
|
||||
false,
|
||||
0,
|
||||
1,
|
||||
)),
|
||||
topics: vec![],
|
||||
}
|
||||
]);
|
||||
@@ -481,17 +500,31 @@ mod tests {
|
||||
assert_eq!(System::events(), vec![
|
||||
EventRecord {
|
||||
phase: Phase::ApplyExtrinsic(0),
|
||||
event: OuterEvent::motions(RawEvent::Proposed(1, 0, hex!["cd0b662a49f004093b80600415cf4126399af0d27ed6c185abeb1469c17eb5bf"].into(), 3)),
|
||||
event: OuterEvent::motions(
|
||||
RawEvent::Proposed(
|
||||
1,
|
||||
0,
|
||||
hex!["cd0b662a49f004093b80600415cf4126399af0d27ed6c185abeb1469c17eb5bf"].into(),
|
||||
3,
|
||||
)),
|
||||
topics: vec![],
|
||||
},
|
||||
EventRecord {
|
||||
phase: Phase::ApplyExtrinsic(0),
|
||||
event: OuterEvent::motions(RawEvent::Voted(2, hex!["cd0b662a49f004093b80600415cf4126399af0d27ed6c185abeb1469c17eb5bf"].into(), false, 1, 1)),
|
||||
event: OuterEvent::motions(RawEvent::Voted(
|
||||
2,
|
||||
hex!["cd0b662a49f004093b80600415cf4126399af0d27ed6c185abeb1469c17eb5bf"].into(),
|
||||
false,
|
||||
1,
|
||||
1,
|
||||
)),
|
||||
topics: vec![],
|
||||
},
|
||||
EventRecord {
|
||||
phase: Phase::ApplyExtrinsic(0),
|
||||
event: OuterEvent::motions(RawEvent::Disapproved(hex!["cd0b662a49f004093b80600415cf4126399af0d27ed6c185abeb1469c17eb5bf"].into())),
|
||||
event: OuterEvent::motions(RawEvent::Disapproved(
|
||||
hex!["cd0b662a49f004093b80600415cf4126399af0d27ed6c185abeb1469c17eb5bf"].into(),
|
||||
)),
|
||||
topics: vec![],
|
||||
}
|
||||
]);
|
||||
@@ -510,22 +543,38 @@ mod tests {
|
||||
assert_eq!(System::events(), vec![
|
||||
EventRecord {
|
||||
phase: Phase::ApplyExtrinsic(0),
|
||||
event: OuterEvent::motions(RawEvent::Proposed(1, 0, hex!["cd0b662a49f004093b80600415cf4126399af0d27ed6c185abeb1469c17eb5bf"].into(), 2)),
|
||||
event: OuterEvent::motions(RawEvent::Proposed(
|
||||
1,
|
||||
0,
|
||||
hex!["cd0b662a49f004093b80600415cf4126399af0d27ed6c185abeb1469c17eb5bf"].into(),
|
||||
2,
|
||||
)),
|
||||
topics: vec![],
|
||||
},
|
||||
EventRecord {
|
||||
phase: Phase::ApplyExtrinsic(0),
|
||||
event: OuterEvent::motions(RawEvent::Voted(2, hex!["cd0b662a49f004093b80600415cf4126399af0d27ed6c185abeb1469c17eb5bf"].into(), true, 2, 0)),
|
||||
event: OuterEvent::motions(RawEvent::Voted(
|
||||
2,
|
||||
hex!["cd0b662a49f004093b80600415cf4126399af0d27ed6c185abeb1469c17eb5bf"].into(),
|
||||
true,
|
||||
2,
|
||||
0,
|
||||
)),
|
||||
topics: vec![],
|
||||
},
|
||||
EventRecord {
|
||||
phase: Phase::ApplyExtrinsic(0),
|
||||
event: OuterEvent::motions(RawEvent::Approved(hex!["cd0b662a49f004093b80600415cf4126399af0d27ed6c185abeb1469c17eb5bf"].into())),
|
||||
event: OuterEvent::motions(RawEvent::Approved(
|
||||
hex!["cd0b662a49f004093b80600415cf4126399af0d27ed6c185abeb1469c17eb5bf"].into(),
|
||||
)),
|
||||
topics: vec![],
|
||||
},
|
||||
EventRecord {
|
||||
phase: Phase::ApplyExtrinsic(0),
|
||||
event: OuterEvent::motions(RawEvent::Executed(hex!["cd0b662a49f004093b80600415cf4126399af0d27ed6c185abeb1469c17eb5bf"].into(), false)),
|
||||
event: OuterEvent::motions(RawEvent::Executed(
|
||||
hex!["cd0b662a49f004093b80600415cf4126399af0d27ed6c185abeb1469c17eb5bf"].into(),
|
||||
false,
|
||||
)),
|
||||
topics: vec![],
|
||||
}
|
||||
]);
|
||||
|
||||
@@ -919,7 +919,7 @@ mod tests {
|
||||
use substrate_primitives::{H256, Blake2Hasher};
|
||||
use primitives::BuildStorage;
|
||||
use primitives::traits::{BlakeTwo256, IdentityLookup, Bounded};
|
||||
use primitives::testing::{Digest, DigestItem, Header};
|
||||
use primitives::testing::Header;
|
||||
use balances::BalanceLock;
|
||||
use system::EnsureSignedBy;
|
||||
|
||||
@@ -948,12 +948,10 @@ mod tests {
|
||||
type BlockNumber = u64;
|
||||
type Hash = H256;
|
||||
type Hashing = BlakeTwo256;
|
||||
type Digest = Digest;
|
||||
type AccountId = u64;
|
||||
type Lookup = IdentityLookup<Self::AccountId>;
|
||||
type Header = Header;
|
||||
type Event = ();
|
||||
type Log = DigestItem;
|
||||
}
|
||||
impl balances::Trait for Test {
|
||||
type Balance = u64;
|
||||
|
||||
@@ -27,27 +27,30 @@
|
||||
//! <!-- Original author of paragraph: Various. Based on collation of review comments to PRs addressing issues with -->
|
||||
//! <!-- label 'S3-SRML' in https://github.com/paritytech/substrate-developer-hub/issues -->
|
||||
//! <ul>
|
||||
//! <li>Documentation comments (i.e. <code>/// comment</code>) - should accompany module functions and be
|
||||
//! restricted to the module interface, not the internals of the module implementation. Only state inputs,
|
||||
//! outputs, and a brief description that mentions whether calling it requires root, but without repeating
|
||||
//! the source code details. Capitalise the first word of each documentation comment and end it with a full
|
||||
//! stop. See <a href="https://github.com/paritytech/substrate#72-contributing-to-documentation-for-substrate-packages"
|
||||
//! target="_blank">Generic example of annotating source code with documentation comments</a></li>
|
||||
//! <li>Self-documenting code - Try to refactor code to be self-documenting.</li>
|
||||
//! <li>Code comments - Supplement complex code with a brief explanation, not every line of code.</li>
|
||||
//! <li>Identifiers - surround by backticks (i.e. <code>INHERENT_IDENTIFIER</code>, <code>InherentType</code>,
|
||||
//! <li>Documentation comments (i.e. <code>/// comment</code>) - should
|
||||
//! accompany module functions and be restricted to the module interface,
|
||||
//! not the internals of the module implementation. Only state inputs,
|
||||
//! outputs, and a brief description that mentions whether calling it
|
||||
//! requires root, but without repeating the source code details.
|
||||
//! Capitalise the first word of each documentation comment and end it with
|
||||
//! a full stop. See
|
||||
//! <a href="https://github.com/paritytech/substrate#72-contributing-to-documentation-for-substrate-packages"
|
||||
//! target="_blank"> Generic example of annotating source code with documentation comments</a></li>
|
||||
//! <li>Self-documenting code - Try to refactor code to be self-documenting.</li>
|
||||
//! <li>Code comments - Supplement complex code with a brief explanation, not every line of code.</li>
|
||||
//! <li>Identifiers - surround by backticks (i.e. <code>INHERENT_IDENTIFIER</code>, <code>InherentType</code>,
|
||||
//! <code>u64</code>)</li>
|
||||
//! <li>Usage scenarios - should be simple doctests. The compiler should ensure they stay valid.</li>
|
||||
//! <li>Extended tutorials - should be moved to external files and refer to.</li>
|
||||
//! <!-- Original author of paragraph: @AmarRSingh -->
|
||||
//! <li>Mandatory - include all of the sections/subsections where <b>MUST</b> is specified.</li>
|
||||
//! <li>Optional - optionally include sections/subsections where <b>CAN</b> is specified.</li>
|
||||
//! <li>Usage scenarios - should be simple doctests. The compiler should ensure they stay valid.</li>
|
||||
//! <li>Extended tutorials - should be moved to external files and refer to.</li>
|
||||
//! <!-- Original author of paragraph: @AmarRSingh -->
|
||||
//! <li>Mandatory - include all of the sections/subsections where <b>MUST</b> is specified.</li>
|
||||
//! <li>Optional - optionally include sections/subsections where <b>CAN</b> is specified.</li>
|
||||
//! </ul>
|
||||
//!
|
||||
//! ### Documentation Template:<br>
|
||||
//!
|
||||
//! Copy and paste this template from srml/example/src/lib.rs into file srml/<INSERT_CUSTOM_MODULE_NAME>/src/lib.rs of
|
||||
//! your own custom module and complete it.
|
||||
//! Copy and paste this template from srml/example/src/lib.rs into file
|
||||
//! `srml/<INSERT_CUSTOM_MODULE_NAME>/src/lib.rs` of your own custom module and complete it.
|
||||
//! <details><p><pre>
|
||||
//! // Add heading with custom module name
|
||||
//!
|
||||
@@ -196,7 +199,8 @@
|
||||
//!
|
||||
//! \## Usage
|
||||
//!
|
||||
//! // Insert 2-3 examples of usage and code snippets that show how to use <INSERT_CUSTOM_MODULE_NAME> module in a custom module.
|
||||
//! // Insert 2-3 examples of usage and code snippets that show how to
|
||||
//! // use <INSERT_CUSTOM_MODULE_NAME> module in a custom module.
|
||||
//!
|
||||
//! \### Prerequisites
|
||||
//!
|
||||
@@ -324,8 +328,10 @@ decl_event!(
|
||||
// - Public calls that are signed by an external account.
|
||||
// - Root calls that are allowed to be made only by the governance system.
|
||||
// - Unsigned calls that can be of two kinds:
|
||||
// * "Inherent extrinsics" that are opinions generally held by the block authors that build child blocks.
|
||||
// * Unsigned Transactions that are of intrinsic recognisable utility to the network, and are validated by the runtime.
|
||||
// * "Inherent extrinsics" that are opinions generally held by the block
|
||||
// authors that build child blocks.
|
||||
// * Unsigned Transactions that are of intrinsic recognisable utility to the
|
||||
// network, and are validated by the runtime.
|
||||
//
|
||||
// Information about where this dispatch initiated from is provided as the first argument
|
||||
// "origin". As such functions must always look like:
|
||||
@@ -505,7 +511,7 @@ mod tests {
|
||||
// or public keys. `u64` is used as the `AccountId` and no `Signature`s are requried.
|
||||
use sr_primitives::{
|
||||
BuildStorage, traits::{BlakeTwo256, OnInitialize, OnFinalize, IdentityLookup},
|
||||
testing::{Digest, DigestItem, Header}
|
||||
testing::Header
|
||||
};
|
||||
|
||||
impl_outer_origin! {
|
||||
@@ -523,12 +529,10 @@ mod tests {
|
||||
type BlockNumber = u64;
|
||||
type Hash = H256;
|
||||
type Hashing = BlakeTwo256;
|
||||
type Digest = Digest;
|
||||
type AccountId = u64;
|
||||
type Lookup = IdentityLookup<Self::AccountId>;
|
||||
type Header = Header;
|
||||
type Event = ();
|
||||
type Log = DigestItem;
|
||||
}
|
||||
impl balances::Trait for Test {
|
||||
type Balance = u64;
|
||||
|
||||
@@ -77,17 +77,17 @@
|
||||
use rstd::prelude::*;
|
||||
use rstd::marker::PhantomData;
|
||||
use rstd::result;
|
||||
use primitives::traits::{
|
||||
use primitives::{generic::Digest, traits::{
|
||||
self, Header, Zero, One, Checkable, Applyable, CheckEqual, OnFinalize,
|
||||
OnInitialize, Digest, NumberFor, Block as BlockT, OffchainWorker,
|
||||
ValidateUnsigned, DigestItem,
|
||||
};
|
||||
use primitives::weights::Weighable;
|
||||
use primitives::{ApplyOutcome, ApplyError};
|
||||
use primitives::transaction_validity::{TransactionValidity, TransactionPriority, TransactionLongevity};
|
||||
OnInitialize, NumberFor, Block as BlockT, OffchainWorker,
|
||||
ValidateUnsigned,
|
||||
}};
|
||||
use srml_support::{Dispatchable, traits::MakePayment};
|
||||
use parity_codec::{Codec, Encode};
|
||||
use system::extrinsics_root;
|
||||
use system::{extrinsics_root, DigestOf};
|
||||
use primitives::{ApplyOutcome, ApplyError};
|
||||
use primitives::transaction_validity::{TransactionValidity, TransactionPriority, TransactionLongevity};
|
||||
use primitives::weights::Weighable;
|
||||
|
||||
mod internal {
|
||||
pub const MAX_TRANSACTIONS_WEIGHT: u32 = 4 * 1024 * 1024;
|
||||
@@ -112,6 +112,10 @@ pub trait ExecuteBlock<Block: BlockT> {
|
||||
fn execute_block(block: Block);
|
||||
}
|
||||
|
||||
pub type CheckedOf<E, C> = <E as Checkable<C>>::Checked;
|
||||
pub type CallOf<E, C> = <CheckedOf<E, C> as Applyable>::Call;
|
||||
pub type OriginOf<E, C> = <CallOf<E, C> as Dispatchable>::Origin;
|
||||
|
||||
pub struct Executive<System, Block, Context, Payment, UnsignedValidator, AllModules>(
|
||||
PhantomData<(System, Block, Context, Payment, UnsignedValidator, AllModules)>
|
||||
);
|
||||
@@ -126,12 +130,10 @@ impl<
|
||||
> ExecuteBlock<Block> for Executive<System, Block, Context, Payment, UnsignedValidator, AllModules>
|
||||
where
|
||||
Block::Extrinsic: Checkable<Context> + Codec,
|
||||
<Block::Extrinsic as Checkable<Context>>::Checked:
|
||||
Applyable<Index=System::Index, AccountId=System::AccountId> + Weighable,
|
||||
<<Block::Extrinsic as Checkable<Context>>::Checked as Applyable>::Call: Dispatchable,
|
||||
<<<Block::Extrinsic as Checkable<Context>>::Checked as Applyable>::Call as Dispatchable>::Origin:
|
||||
From<Option<System::AccountId>>,
|
||||
UnsignedValidator: ValidateUnsigned<Call=<<Block::Extrinsic as Checkable<Context>>::Checked as Applyable>::Call>
|
||||
CheckedOf<Block::Extrinsic, Context>: Applyable<Index=System::Index, AccountId=System::AccountId> + Weighable,
|
||||
CallOf<Block::Extrinsic, Context>: Dispatchable,
|
||||
OriginOf<Block::Extrinsic, Context>: From<Option<System::AccountId>>,
|
||||
UnsignedValidator: ValidateUnsigned<Call=CallOf<Block::Extrinsic, Context>>,
|
||||
{
|
||||
fn execute_block(block: Block) {
|
||||
Executive::<System, Block, Context, Payment, UnsignedValidator, AllModules>::execute_block(block);
|
||||
@@ -148,16 +150,14 @@ impl<
|
||||
> Executive<System, Block, Context, Payment, UnsignedValidator, AllModules>
|
||||
where
|
||||
Block::Extrinsic: Checkable<Context> + Codec,
|
||||
<Block::Extrinsic as Checkable<Context>>::Checked:
|
||||
Applyable<Index=System::Index, AccountId=System::AccountId> + Weighable,
|
||||
<<Block::Extrinsic as Checkable<Context>>::Checked as Applyable>::Call: Dispatchable,
|
||||
<<<Block::Extrinsic as Checkable<Context>>::Checked as Applyable>::Call as Dispatchable>::Origin:
|
||||
From<Option<System::AccountId>>,
|
||||
UnsignedValidator: ValidateUnsigned<Call=<<Block::Extrinsic as Checkable<Context>>::Checked as Applyable>::Call>
|
||||
CheckedOf<Block::Extrinsic, Context>: Applyable<Index=System::Index, AccountId=System::AccountId> + Weighable,
|
||||
CallOf<Block::Extrinsic, Context>: Dispatchable,
|
||||
OriginOf<Block::Extrinsic, Context>: From<Option<System::AccountId>>,
|
||||
UnsignedValidator: ValidateUnsigned<Call=CallOf<Block::Extrinsic, Context>>,
|
||||
{
|
||||
/// Start the execution of a particular block.
|
||||
pub fn initialize_block(header: &System::Header) {
|
||||
let mut digests = System::Digest::default();
|
||||
let mut digests = <DigestOf<System>>::default();
|
||||
header.digest().logs().iter().for_each(|d| if d.as_pre_runtime().is_some() { digests.push(d.clone()) });
|
||||
Self::initialize_block_impl(header.number(), header.parent_hash(), header.extrinsics_root(), &digests);
|
||||
}
|
||||
@@ -166,7 +166,7 @@ where
|
||||
block_number: &System::BlockNumber,
|
||||
parent_hash: &System::Hash,
|
||||
extrinsics_root: &System::Hash,
|
||||
digest: &System::Digest
|
||||
digest: &Digest<System::Hash>,
|
||||
) {
|
||||
<system::Module<System>>::initialize(block_number, parent_hash, extrinsics_root, digest);
|
||||
<AllModules as OnInitialize<System::BlockNumber>>::on_initialize(*block_number);
|
||||
@@ -259,7 +259,7 @@ where
|
||||
fn apply_extrinsic_with_len(
|
||||
uxt: Block::Extrinsic,
|
||||
encoded_len: usize,
|
||||
to_note: Option<Vec<u8>>
|
||||
to_note: Option<Vec<u8>>,
|
||||
) -> result::Result<internal::ApplyOutcome, internal::ApplyError> {
|
||||
// Verify that the signature is good.
|
||||
let xt = uxt.check(&Default::default()).map_err(internal::ApplyError::BadSignature)?;
|
||||
@@ -397,7 +397,7 @@ mod tests {
|
||||
use substrate_primitives::{H256, Blake2Hasher};
|
||||
use primitives::BuildStorage;
|
||||
use primitives::traits::{Header as HeaderT, BlakeTwo256, IdentityLookup};
|
||||
use primitives::testing::{Digest, DigestItem, Header, Block};
|
||||
use primitives::testing::{Digest, Header, Block};
|
||||
use srml_support::{traits::Currency, impl_outer_origin, impl_outer_event};
|
||||
use system;
|
||||
use hex_literal::hex;
|
||||
@@ -422,12 +422,10 @@ mod tests {
|
||||
type BlockNumber = u64;
|
||||
type Hash = substrate_primitives::H256;
|
||||
type Hashing = BlakeTwo256;
|
||||
type Digest = Digest;
|
||||
type AccountId = u64;
|
||||
type Lookup = IdentityLookup<u64>;
|
||||
type Header = Header;
|
||||
type Event = MetaEvent;
|
||||
type Log = DigestItem;
|
||||
}
|
||||
impl balances::Trait for Runtime {
|
||||
type Balance = u64;
|
||||
@@ -457,7 +455,14 @@ mod tests {
|
||||
}
|
||||
|
||||
type TestXt = primitives::testing::TestXt<Call<Runtime>>;
|
||||
type Executive = super::Executive<Runtime, Block<TestXt>, system::ChainContext<Runtime>, balances::Module<Runtime>, Runtime, ()>;
|
||||
type Executive = super::Executive<
|
||||
Runtime,
|
||||
Block<TestXt>,
|
||||
system::ChainContext<Runtime>,
|
||||
balances::Module<Runtime>,
|
||||
Runtime,
|
||||
()
|
||||
>;
|
||||
|
||||
#[test]
|
||||
fn balance_transfer_dispatch_works() {
|
||||
@@ -474,8 +479,13 @@ mod tests {
|
||||
let xt = primitives::testing::TestXt(Some(1), 0, Call::transfer(2, 69));
|
||||
let mut t = runtime_io::TestExternalities::<Blake2Hasher>::new(t);
|
||||
with_externalities(&mut t, || {
|
||||
Executive::initialize_block(&Header::new(1, H256::default(), H256::default(),
|
||||
[69u8; 32].into(), Digest::default()));
|
||||
Executive::initialize_block(&Header::new(
|
||||
1,
|
||||
H256::default(),
|
||||
H256::default(),
|
||||
[69u8; 32].into(),
|
||||
Digest::default(),
|
||||
));
|
||||
Executive::apply_extrinsic(xt).unwrap();
|
||||
assert_eq!(<balances::Module<Runtime>>::total_balance(&1), 32);
|
||||
assert_eq!(<balances::Module<Runtime>>::total_balance(&2), 69);
|
||||
@@ -543,7 +553,13 @@ mod tests {
|
||||
let mut t = new_test_ext();
|
||||
let xt = primitives::testing::TestXt(Some(1), 42, Call::transfer(33, 69));
|
||||
with_externalities(&mut t, || {
|
||||
Executive::initialize_block(&Header::new(1, H256::default(), H256::default(), [69u8; 32].into(), Digest::default()));
|
||||
Executive::initialize_block(&Header::new(
|
||||
1,
|
||||
H256::default(),
|
||||
H256::default(),
|
||||
[69u8; 32].into(),
|
||||
Digest::default(),
|
||||
));
|
||||
assert!(Executive::apply_extrinsic(xt).is_err());
|
||||
assert_eq!(<system::Module<Runtime>>::extrinsic_index(), Some(0));
|
||||
});
|
||||
@@ -558,7 +574,13 @@ mod tests {
|
||||
let encoded = xt2.encode();
|
||||
let len = if should_fail { (internal::MAX_TRANSACTIONS_WEIGHT - 1) as usize } else { encoded.len() };
|
||||
with_externalities(&mut t, || {
|
||||
Executive::initialize_block(&Header::new(1, H256::default(), H256::default(), [69u8; 32].into(), Digest::default()));
|
||||
Executive::initialize_block(&Header::new(
|
||||
1,
|
||||
H256::default(),
|
||||
H256::default(),
|
||||
[69u8; 32].into(),
|
||||
Digest::default(),
|
||||
));
|
||||
assert_eq!(<system::Module<Runtime>>::all_extrinsics_weight(), 0);
|
||||
|
||||
Executive::apply_extrinsic(xt).unwrap();
|
||||
|
||||
@@ -202,7 +202,7 @@ impl<T: Trait> Module<T> {
|
||||
let delay = latency + (window_size / two);
|
||||
// median may be at most n - delay
|
||||
if median + delay <= now {
|
||||
T::OnFinalizationStalled::on_stalled(window_size - T::BlockNumber::one());
|
||||
T::OnFinalizationStalled::on_stalled(window_size - T::BlockNumber::one(), median);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -212,20 +212,20 @@ impl<T: Trait> Module<T> {
|
||||
pub trait OnFinalizationStalled<N> {
|
||||
/// The parameter here is how many more blocks to wait before applying
|
||||
/// changes triggered by finality stalling.
|
||||
fn on_stalled(further_wait: N);
|
||||
fn on_stalled(further_wait: N, median: N);
|
||||
}
|
||||
|
||||
macro_rules! impl_on_stalled {
|
||||
() => (
|
||||
impl<N> OnFinalizationStalled<N> for () {
|
||||
fn on_stalled(_: N) {}
|
||||
fn on_stalled(_: N, _: N) {}
|
||||
}
|
||||
);
|
||||
|
||||
( $($t:ident)* ) => {
|
||||
impl<NUM: Clone, $($t: OnFinalizationStalled<NUM>),*> OnFinalizationStalled<NUM> for ($($t,)*) {
|
||||
fn on_stalled(further_wait: NUM) {
|
||||
$($t::on_stalled(further_wait.clone());)*
|
||||
fn on_stalled(further_wait: NUM, median: NUM) {
|
||||
$($t::on_stalled(further_wait.clone(), median.clone());)*
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -263,7 +263,7 @@ mod tests {
|
||||
use substrate_primitives::H256;
|
||||
use primitives::BuildStorage;
|
||||
use primitives::traits::{BlakeTwo256, IdentityLookup, OnFinalize, Header as HeaderT};
|
||||
use primitives::testing::{Digest, DigestItem, Header};
|
||||
use primitives::testing::Header;
|
||||
use srml_support::impl_outer_origin;
|
||||
use srml_system as system;
|
||||
use lazy_static::lazy_static;
|
||||
@@ -290,12 +290,10 @@ mod tests {
|
||||
type BlockNumber = u64;
|
||||
type Hash = H256;
|
||||
type Hashing = BlakeTwo256;
|
||||
type Digest = Digest;
|
||||
type AccountId = u64;
|
||||
type Lookup = IdentityLookup<u64>;
|
||||
type Header = Header;
|
||||
type Event = ();
|
||||
type Log = DigestItem;
|
||||
}
|
||||
|
||||
type System = system::Module<Test>;
|
||||
@@ -306,7 +304,7 @@ mod tests {
|
||||
|
||||
pub struct StallTracker;
|
||||
impl OnFinalizationStalled<u64> for StallTracker {
|
||||
fn on_stalled(further_wait: u64) {
|
||||
fn on_stalled(further_wait: u64, _median: u64) {
|
||||
let now = System::block_number();
|
||||
NOTIFICATIONS.lock().push(StallEvent { at: now, further_wait });
|
||||
}
|
||||
|
||||
@@ -14,7 +14,6 @@ primitives = { package = "sr-primitives", path = "../../core/sr-primitives", def
|
||||
srml-support = { path = "../support", default-features = false }
|
||||
system = { package = "srml-system", path = "../system", default-features = false }
|
||||
session = { package = "srml-session", path = "../session", default-features = false }
|
||||
consensus = { package = "srml-consensus", path = "../consensus", default-features = false }
|
||||
finality-tracker = { package = "srml-finality-tracker", path = "../finality-tracker", default-features = false }
|
||||
|
||||
[dev-dependencies]
|
||||
@@ -31,7 +30,6 @@ std = [
|
||||
"srml-support/std",
|
||||
"primitives/std",
|
||||
"system/std",
|
||||
"consensus/std",
|
||||
"session/std",
|
||||
"finality-tracker/std",
|
||||
]
|
||||
|
||||
@@ -33,136 +33,84 @@ pub use substrate_finality_grandpa_primitives as fg_primitives;
|
||||
#[cfg(feature = "std")]
|
||||
use serde::Serialize;
|
||||
use rstd::prelude::*;
|
||||
use parity_codec as codec;
|
||||
use codec::{Encode, Decode};
|
||||
use fg_primitives::ScheduledChange;
|
||||
use srml_support::{Parameter, decl_event, decl_storage, decl_module};
|
||||
use srml_support::dispatch::Result;
|
||||
use srml_support::storage::StorageValue;
|
||||
use srml_support::storage::unhashed::StorageVec;
|
||||
use primitives::traits::CurrentHeight;
|
||||
use substrate_primitives::ed25519;
|
||||
use system::ensure_signed;
|
||||
use primitives::traits::MaybeSerializeDebug;
|
||||
use ed25519::Public as AuthorityId;
|
||||
use parity_codec::{self as codec, Encode, Decode};
|
||||
use srml_support::{
|
||||
decl_event, decl_storage, decl_module, dispatch::Result, storage::StorageValue
|
||||
};
|
||||
use primitives::{
|
||||
generic::{DigestItem, OpaqueDigestItemId}, traits::CurrentHeight
|
||||
};
|
||||
use fg_primitives::{ScheduledChange, GRANDPA_ENGINE_ID};
|
||||
pub use fg_primitives::{AuthorityId, AuthorityWeight};
|
||||
use system::{ensure_signed, DigestOf};
|
||||
|
||||
mod mock;
|
||||
mod tests;
|
||||
|
||||
struct AuthorityStorageVec<S: codec::Codec + Default>(rstd::marker::PhantomData<S>);
|
||||
impl<S: codec::Codec + Default> StorageVec for AuthorityStorageVec<S> {
|
||||
type Item = (S, u64);
|
||||
const PREFIX: &'static [u8] = crate::fg_primitives::well_known_keys::AUTHORITY_PREFIX;
|
||||
}
|
||||
|
||||
/// The log type of this crate, projected from module trait type.
|
||||
pub type Log<T> = RawLog<
|
||||
<T as system::Trait>::BlockNumber,
|
||||
<T as Trait>::SessionKey,
|
||||
>;
|
||||
|
||||
/// Logs which can be scanned by GRANDPA for authorities change events.
|
||||
pub trait GrandpaChangeSignal<N> {
|
||||
/// Try to cast the log entry as a contained signal.
|
||||
fn as_signal(&self) -> Option<ScheduledChange<N>>;
|
||||
/// Try to cast the log entry as a contained forced signal.
|
||||
fn as_forced_signal(&self) -> Option<(N, ScheduledChange<N>)>;
|
||||
}
|
||||
|
||||
/// A logs in this module.
|
||||
/// Consensus log type of this module.
|
||||
#[cfg_attr(feature = "std", derive(Serialize, Debug))]
|
||||
#[derive(Encode, Decode, PartialEq, Eq, Clone)]
|
||||
pub enum RawLog<N, SessionKey> {
|
||||
pub enum Signal<N> {
|
||||
/// Authorities set change has been signaled. Contains the new set of authorities
|
||||
/// and the delay in blocks _to finalize_ before applying.
|
||||
AuthoritiesChangeSignal(N, Vec<(SessionKey, u64)>),
|
||||
AuthoritiesChange(ScheduledChange<N>),
|
||||
/// A forced authorities set change. Contains in this order: the median last
|
||||
/// finalized block when the change was signaled, the delay in blocks _to import_
|
||||
/// before applying and the new set of authorities.
|
||||
ForcedAuthoritiesChangeSignal(N, N, Vec<(SessionKey, u64)>),
|
||||
ForcedAuthoritiesChange(N, ScheduledChange<N>),
|
||||
}
|
||||
|
||||
impl<N: Clone, SessionKey> RawLog<N, SessionKey> {
|
||||
impl<N> Signal<N> {
|
||||
/// Try to cast the log entry as a contained signal.
|
||||
pub fn as_signal(&self) -> Option<(N, &[(SessionKey, u64)])> {
|
||||
match *self {
|
||||
RawLog::AuthoritiesChangeSignal(ref delay, ref signal) => Some((delay.clone(), signal)),
|
||||
RawLog::ForcedAuthoritiesChangeSignal(_, _, _) => None,
|
||||
pub fn try_into_change(self) -> Option<ScheduledChange<N>> {
|
||||
match self {
|
||||
Signal::AuthoritiesChange(change) => Some(change),
|
||||
Signal::ForcedAuthoritiesChange(_, _) => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Try to cast the log entry as a contained forced signal.
|
||||
pub fn as_forced_signal(&self) -> Option<(N, N, &[(SessionKey, u64)])> {
|
||||
match *self {
|
||||
RawLog::ForcedAuthoritiesChangeSignal(ref median, ref delay, ref signal) => Some((median.clone(), delay.clone(), signal)),
|
||||
RawLog::AuthoritiesChangeSignal(_, _) => None,
|
||||
pub fn try_into_forced_change(self) -> Option<(N, ScheduledChange<N>)> {
|
||||
match self {
|
||||
Signal::ForcedAuthoritiesChange(median, change) => Some((median, change)),
|
||||
Signal::AuthoritiesChange(_) => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<N, SessionKey> GrandpaChangeSignal<N> for RawLog<N, SessionKey>
|
||||
where N: Clone, SessionKey: Clone + Into<AuthorityId>,
|
||||
{
|
||||
fn as_signal(&self) -> Option<ScheduledChange<N>> {
|
||||
RawLog::as_signal(self).map(|(delay, next_authorities)| ScheduledChange {
|
||||
delay,
|
||||
next_authorities: next_authorities.iter()
|
||||
.cloned()
|
||||
.map(|(k, w)| (k.into(), w))
|
||||
.collect(),
|
||||
})
|
||||
}
|
||||
|
||||
fn as_forced_signal(&self) -> Option<(N, ScheduledChange<N>)> {
|
||||
RawLog::as_forced_signal(self).map(|(median, delay, next_authorities)| (median, ScheduledChange {
|
||||
delay,
|
||||
next_authorities: next_authorities.iter()
|
||||
.cloned()
|
||||
.map(|(k, w)| (k.into(), w))
|
||||
.collect(),
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Trait: system::Trait {
|
||||
/// Type for all log entries of this module.
|
||||
type Log: From<Log<Self>> + Into<system::DigestItemOf<Self>>;
|
||||
|
||||
/// The session key type used by authorities.
|
||||
type SessionKey: Parameter + Default + MaybeSerializeDebug;
|
||||
|
||||
/// The event type of this module.
|
||||
type Event: From<Event<Self>> + Into<<Self as system::Trait>::Event>;
|
||||
type Event: From<Event> + Into<<Self as system::Trait>::Event>;
|
||||
}
|
||||
|
||||
/// A stored pending change, old format.
|
||||
// TODO: remove shim
|
||||
// https://github.com/paritytech/substrate/issues/1614
|
||||
#[derive(Encode, Decode)]
|
||||
pub struct OldStoredPendingChange<N, SessionKey> {
|
||||
pub struct OldStoredPendingChange<N> {
|
||||
/// The block number this was scheduled at.
|
||||
pub scheduled_at: N,
|
||||
/// The delay in blocks until it will be applied.
|
||||
pub delay: N,
|
||||
/// The next authority set.
|
||||
pub next_authorities: Vec<(SessionKey, u64)>,
|
||||
pub next_authorities: Vec<(AuthorityId, u64)>,
|
||||
}
|
||||
|
||||
/// A stored pending change.
|
||||
#[derive(Encode)]
|
||||
pub struct StoredPendingChange<N, SessionKey> {
|
||||
pub struct StoredPendingChange<N> {
|
||||
/// The block number this was scheduled at.
|
||||
pub scheduled_at: N,
|
||||
/// The delay in blocks until it will be applied.
|
||||
pub delay: N,
|
||||
/// The next authority set.
|
||||
pub next_authorities: Vec<(SessionKey, u64)>,
|
||||
pub next_authorities: Vec<(AuthorityId, u64)>,
|
||||
/// If defined it means the change was forced and the given block number
|
||||
/// indicates the median last finalized block when the change was signaled.
|
||||
pub forced: Option<N>,
|
||||
}
|
||||
|
||||
impl<N: Decode, SessionKey: Decode> Decode for StoredPendingChange<N, SessionKey> {
|
||||
impl<N: Decode> Decode for StoredPendingChange<N> {
|
||||
fn decode<I: codec::Input>(value: &mut I) -> Option<Self> {
|
||||
let old = OldStoredPendingChange::decode(value)?;
|
||||
let forced = <Option<N>>::decode(value).unwrap_or(None);
|
||||
@@ -177,43 +125,31 @@ impl<N: Decode, SessionKey: Decode> Decode for StoredPendingChange<N, SessionKey
|
||||
}
|
||||
|
||||
decl_event!(
|
||||
pub enum Event<T> where <T as Trait>::SessionKey {
|
||||
pub enum Event {
|
||||
/// New authority set has been applied.
|
||||
NewAuthorities(Vec<(SessionKey, u64)>),
|
||||
NewAuthorities(Vec<(AuthorityId, u64)>),
|
||||
}
|
||||
);
|
||||
|
||||
decl_storage! {
|
||||
trait Store for Module<T: Trait> as GrandpaFinality {
|
||||
// Pending change: (signaled at, scheduled change).
|
||||
PendingChange get(pending_change): Option<StoredPendingChange<T::BlockNumber, T::SessionKey>>;
|
||||
// next block number where we can force a change.
|
||||
/// The current authority set.
|
||||
Authorities get(authorities) config(): Vec<(AuthorityId, AuthorityWeight)>;
|
||||
|
||||
/// Pending change: (signaled at, scheduled change).
|
||||
PendingChange: Option<StoredPendingChange<T::BlockNumber>>;
|
||||
|
||||
/// next block number where we can force a change.
|
||||
NextForced get(next_forced): Option<T::BlockNumber>;
|
||||
}
|
||||
add_extra_genesis {
|
||||
config(authorities): Vec<(T::SessionKey, u64)>;
|
||||
|
||||
build(|storage: &mut primitives::StorageOverlay, _: &mut primitives::ChildrenStorageOverlay, config: &GenesisConfig<T>| {
|
||||
use codec::{Encode, KeyedVec};
|
||||
|
||||
let auth_count = config.authorities.len() as u32;
|
||||
config.authorities.iter().enumerate().for_each(|(i, v)| {
|
||||
storage.insert((i as u32).to_keyed_vec(
|
||||
crate::fg_primitives::well_known_keys::AUTHORITY_PREFIX),
|
||||
v.encode()
|
||||
);
|
||||
});
|
||||
storage.insert(
|
||||
crate::fg_primitives::well_known_keys::AUTHORITY_COUNT.to_vec(),
|
||||
auth_count.encode(),
|
||||
);
|
||||
});
|
||||
/// `true` if we are currently stalled.
|
||||
Stalled get(stalled): Option<(T::BlockNumber, T::BlockNumber)>;
|
||||
}
|
||||
}
|
||||
|
||||
decl_module! {
|
||||
pub struct Module<T: Trait> for enum Call where origin: T::Origin {
|
||||
fn deposit_event<T>() = default;
|
||||
fn deposit_event() = default;
|
||||
|
||||
/// Report some misbehavior.
|
||||
fn report_misbehavior(origin, _report: Vec<u8>) {
|
||||
@@ -225,24 +161,28 @@ decl_module! {
|
||||
if let Some(pending_change) = <PendingChange<T>>::get() {
|
||||
if block_number == pending_change.scheduled_at {
|
||||
if let Some(median) = pending_change.forced {
|
||||
Self::deposit_log(RawLog::ForcedAuthoritiesChangeSignal(
|
||||
Self::deposit_log(Signal::ForcedAuthoritiesChange(
|
||||
median,
|
||||
pending_change.delay,
|
||||
pending_change.next_authorities.clone(),
|
||||
));
|
||||
ScheduledChange{
|
||||
delay: pending_change.delay,
|
||||
next_authorities: pending_change.next_authorities.clone(),
|
||||
}
|
||||
))
|
||||
} else {
|
||||
Self::deposit_log(RawLog::AuthoritiesChangeSignal(
|
||||
pending_change.delay,
|
||||
pending_change.next_authorities.clone(),
|
||||
Self::deposit_log(Signal::AuthoritiesChange(
|
||||
ScheduledChange{
|
||||
delay: pending_change.delay,
|
||||
next_authorities: pending_change.next_authorities.clone(),
|
||||
}
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
if block_number == pending_change.scheduled_at + pending_change.delay {
|
||||
<Authorities<T>>::put(&pending_change.next_authorities);
|
||||
Self::deposit_event(
|
||||
RawEvent::NewAuthorities(pending_change.next_authorities.clone())
|
||||
Event::NewAuthorities(pending_change.next_authorities)
|
||||
);
|
||||
<AuthorityStorageVec<T::SessionKey>>::set_items(pending_change.next_authorities);
|
||||
<PendingChange<T>>::kill();
|
||||
}
|
||||
}
|
||||
@@ -252,8 +192,8 @@ decl_module! {
|
||||
|
||||
impl<T: Trait> Module<T> {
|
||||
/// Get the current set of authorities, along with their respective weights.
|
||||
pub fn grandpa_authorities() -> Vec<(T::SessionKey, u64)> {
|
||||
<AuthorityStorageVec<T::SessionKey>>::items()
|
||||
pub fn grandpa_authorities() -> Vec<(AuthorityId, u64)> {
|
||||
<Authorities<T>>::get()
|
||||
}
|
||||
|
||||
/// Schedule a change in the authorities.
|
||||
@@ -271,11 +211,11 @@ impl<T: Trait> Module<T> {
|
||||
/// No change should be signaled while any change is pending. Returns
|
||||
/// an error if a change is already pending.
|
||||
pub fn schedule_change(
|
||||
next_authorities: Vec<(T::SessionKey, u64)>,
|
||||
next_authorities: Vec<(AuthorityId, u64)>,
|
||||
in_blocks: T::BlockNumber,
|
||||
forced: Option<T::BlockNumber>,
|
||||
) -> Result {
|
||||
if Self::pending_change().is_none() {
|
||||
if !<PendingChange<T>>::exists() {
|
||||
let scheduled_at = system::ChainContext::<T>::default().current_height();
|
||||
|
||||
if let Some(_) = forced {
|
||||
@@ -302,79 +242,60 @@ impl<T: Trait> Module<T> {
|
||||
}
|
||||
|
||||
/// Deposit one of this module's logs.
|
||||
fn deposit_log(log: Log<T>) {
|
||||
<system::Module<T>>::deposit_log(<T as Trait>::Log::from(log).into());
|
||||
fn deposit_log(log: Signal<T::BlockNumber>) {
|
||||
let log: DigestItem<T::Hash> = DigestItem::Consensus(GRANDPA_ENGINE_ID, log.encode());
|
||||
<system::Module<T>>::deposit_log(log.into());
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Trait> Module<T> where AuthorityId: core::convert::From<<T as Trait>::SessionKey> {
|
||||
/// See if the digest contains any standard scheduled change.
|
||||
pub fn scrape_digest_change(log: &Log<T>)
|
||||
impl<T: Trait> Module<T> {
|
||||
pub fn grandpa_log(digest: &DigestOf<T>) -> Option<Signal<T::BlockNumber>> {
|
||||
let id = OpaqueDigestItemId::Consensus(&GRANDPA_ENGINE_ID);
|
||||
digest.convert_first(|l| l.try_to::<Signal<T::BlockNumber>>(id))
|
||||
}
|
||||
|
||||
pub fn pending_change(digest: &DigestOf<T>)
|
||||
-> Option<ScheduledChange<T::BlockNumber>>
|
||||
{
|
||||
<Log<T> as GrandpaChangeSignal<T::BlockNumber>>::as_signal(log)
|
||||
Self::grandpa_log(digest).and_then(|signal| signal.try_into_change())
|
||||
}
|
||||
|
||||
/// See if the digest contains any forced scheduled change.
|
||||
pub fn scrape_digest_forced_change(log: &Log<T>)
|
||||
pub fn forced_change(digest: &DigestOf<T>)
|
||||
-> Option<(T::BlockNumber, ScheduledChange<T::BlockNumber>)>
|
||||
{
|
||||
<Log<T> as GrandpaChangeSignal<T::BlockNumber>>::as_forced_signal(log)
|
||||
Self::grandpa_log(digest).and_then(|signal| signal.try_into_forced_change())
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper for authorities being synchronized with the general session authorities.
|
||||
///
|
||||
/// This is not the only way to manage an authority set for GRANDPA, but it is
|
||||
/// a convenient one. When this is used, no other mechanism for altering authority
|
||||
/// sets should be.
|
||||
pub struct SyncedAuthorities<T>(::rstd::marker::PhantomData<T>);
|
||||
|
||||
// FIXME: remove when https://github.com/rust-lang/rust/issues/26925 is fixed
|
||||
impl<T> Default for SyncedAuthorities<T> {
|
||||
fn default() -> Self {
|
||||
SyncedAuthorities(::rstd::marker::PhantomData)
|
||||
}
|
||||
}
|
||||
|
||||
impl<X, T> session::OnSessionChange<X> for SyncedAuthorities<T> where
|
||||
T: Trait + consensus::Trait<SessionKey=<T as Trait>::SessionKey>,
|
||||
<T as consensus::Trait>::Log: From<consensus::RawLog<<T as Trait>::SessionKey>>
|
||||
{
|
||||
fn on_session_change(_: X, _: bool) {
|
||||
use primitives::traits::Zero;
|
||||
|
||||
let next_authorities = <consensus::Module<T>>::authorities()
|
||||
.into_iter()
|
||||
.map(|key| (key, 1)) // evenly-weighted.
|
||||
.collect::<Vec<(<T as Trait>::SessionKey, u64)>>();
|
||||
|
||||
impl<T: Trait> session::OneSessionHandler<T::AccountId> for Module<T> {
|
||||
type Key = AuthorityId;
|
||||
fn on_new_session<'a, I: 'a>(changed: bool, validators: I)
|
||||
where I: Iterator<Item=(&'a T::AccountId, AuthorityId)>
|
||||
{
|
||||
// instant changes
|
||||
let last_authorities = <Module<T>>::grandpa_authorities();
|
||||
if next_authorities != last_authorities {
|
||||
let _ = <Module<T>>::schedule_change(next_authorities, Zero::zero(), None);
|
||||
if changed {
|
||||
let next_authorities = validators.map(|(_, k)| (k, 1u64)).collect::<Vec<_>>();
|
||||
let last_authorities = <Module<T>>::grandpa_authorities();
|
||||
if next_authorities != last_authorities {
|
||||
use primitives::traits::Zero;
|
||||
if let Some((further_wait, median)) = <Stalled<T>>::take() {
|
||||
let _ = Self::schedule_change(next_authorities, further_wait, Some(median));
|
||||
} else {
|
||||
let _ = Self::schedule_change(next_authorities, Zero::zero(), None);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
fn on_disabled(_i: usize) {
|
||||
// ignore?
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> finality_tracker::OnFinalizationStalled<T::BlockNumber> for SyncedAuthorities<T> where
|
||||
T: Trait + consensus::Trait<SessionKey=<T as Trait>::SessionKey>,
|
||||
<T as consensus::Trait>::Log: From<consensus::RawLog<<T as Trait>::SessionKey>>,
|
||||
T: finality_tracker::Trait,
|
||||
{
|
||||
fn on_stalled(further_wait: T::BlockNumber) {
|
||||
impl<T: Trait> finality_tracker::OnFinalizationStalled<T::BlockNumber> for Module<T> {
|
||||
fn on_stalled(further_wait: T::BlockNumber, median: T::BlockNumber) {
|
||||
// when we record old authority sets, we can use `finality_tracker::median`
|
||||
// to figure out _who_ failed. until then, we can't meaningfully guard
|
||||
// against `next == last` the way that normal session changes do.
|
||||
|
||||
let next_authorities = <consensus::Module<T>>::authorities()
|
||||
.into_iter()
|
||||
.map(|key| (key, 1)) // evenly-weighted.
|
||||
.collect::<Vec<(<T as Trait>::SessionKey, u64)>>();
|
||||
|
||||
let median = <finality_tracker::Module<T>>::median();
|
||||
|
||||
// schedule a change for `further_wait` blocks.
|
||||
let _ = <Module<T>>::schedule_change(next_authorities, further_wait, Some(median));
|
||||
<Stalled<T>>::put((further_wait, median));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,21 +18,23 @@
|
||||
|
||||
#![cfg(test)]
|
||||
|
||||
use primitives::{BuildStorage, traits::IdentityLookup, testing::{Digest, DigestItem, Header}};
|
||||
use primitives::generic::DigestItem as GenDigestItem;
|
||||
use primitives::{
|
||||
BuildStorage, DigestItem, traits::IdentityLookup, testing::{Header, UintAuthorityId}
|
||||
};
|
||||
use runtime_io;
|
||||
use srml_support::{impl_outer_origin, impl_outer_event};
|
||||
use substrate_primitives::{H256, Blake2Hasher};
|
||||
use parity_codec::{Encode, Decode};
|
||||
use crate::{GenesisConfig, Trait, Module, RawLog};
|
||||
use crate::{AuthorityId, GenesisConfig, Trait, Module, Signal};
|
||||
use substrate_finality_grandpa_primitives::GRANDPA_ENGINE_ID;
|
||||
|
||||
impl_outer_origin!{
|
||||
pub enum Origin for Test {}
|
||||
}
|
||||
|
||||
impl From<RawLog<u64, u64>> for DigestItem {
|
||||
fn from(log: RawLog<u64, u64>) -> DigestItem {
|
||||
GenDigestItem::Other(log.encode())
|
||||
impl From<Signal<u64>> for DigestItem<H256> {
|
||||
fn from(log: Signal<u64>) -> DigestItem<H256> {
|
||||
DigestItem::Consensus(GRANDPA_ENGINE_ID, log.encode())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,9 +42,8 @@ impl From<RawLog<u64, u64>> for DigestItem {
|
||||
#[derive(Clone, PartialEq, Eq, Debug, Decode, Encode)]
|
||||
pub struct Test;
|
||||
impl Trait for Test {
|
||||
type Log = DigestItem;
|
||||
type SessionKey = u64;
|
||||
type Event = TestEvent;
|
||||
|
||||
}
|
||||
impl system::Trait for Test {
|
||||
type Origin = Origin;
|
||||
@@ -50,12 +51,10 @@ impl system::Trait for Test {
|
||||
type BlockNumber = u64;
|
||||
type Hash = H256;
|
||||
type Hashing = ::primitives::traits::BlakeTwo256;
|
||||
type Digest = Digest;
|
||||
type AccountId = u64;
|
||||
type Lookup = IdentityLookup<Self::AccountId>;
|
||||
type Header = Header;
|
||||
type Event = TestEvent;
|
||||
type Log = DigestItem;
|
||||
}
|
||||
|
||||
mod grandpa {
|
||||
@@ -64,14 +63,19 @@ mod grandpa {
|
||||
|
||||
impl_outer_event!{
|
||||
pub enum TestEvent for Test {
|
||||
grandpa<T>,
|
||||
grandpa,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_authorities(vec: Vec<(u64, u64)>) -> Vec<(AuthorityId, u64)> {
|
||||
vec.into_iter().map(|(id, weight)| (UintAuthorityId(id).into(), weight)).collect()
|
||||
}
|
||||
|
||||
pub fn new_test_ext(authorities: Vec<(u64, u64)>) -> runtime_io::TestExternalities<Blake2Hasher> {
|
||||
let mut t = system::GenesisConfig::<Test>::default().build_storage().unwrap().0;
|
||||
t.extend(GenesisConfig::<Test> {
|
||||
authorities,
|
||||
_genesis_phantom_data: Default::default(),
|
||||
authorities: to_authorities(authorities),
|
||||
}.build_storage().unwrap().0);
|
||||
t.into()
|
||||
}
|
||||
|
||||
@@ -18,35 +18,37 @@
|
||||
|
||||
#![cfg(test)]
|
||||
|
||||
use primitives::{testing, traits::OnFinalize};
|
||||
use primitives::traits::Header;
|
||||
use primitives::testing::Digest;
|
||||
use primitives::traits::{Header, OnFinalize};
|
||||
use runtime_io::with_externalities;
|
||||
use crate::mock::{Grandpa, System, new_test_ext};
|
||||
use crate::mock::*;
|
||||
use system::{EventRecord, Phase};
|
||||
use crate::{RawLog, RawEvent};
|
||||
use codec::{Decode, Encode};
|
||||
use fg_primitives::ScheduledChange;
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn authorities_change_logged() {
|
||||
with_externalities(&mut new_test_ext(vec![(1, 1), (2, 1), (3, 1)]), || {
|
||||
System::initialize(&1, &Default::default(), &Default::default(), &Default::default());
|
||||
Grandpa::schedule_change(vec![(4, 1), (5, 1), (6, 1)], 0, None).unwrap();
|
||||
Grandpa::schedule_change(to_authorities(vec![(4, 1), (5, 1), (6, 1)]), 0, None).unwrap();
|
||||
|
||||
System::note_finished_extrinsics();
|
||||
Grandpa::on_finalize(1);
|
||||
|
||||
let header = System::finalize();
|
||||
assert_eq!(header.digest, testing::Digest {
|
||||
assert_eq!(header.digest, Digest {
|
||||
logs: vec![
|
||||
RawLog::AuthoritiesChangeSignal(0, vec![(4, 1), (5, 1), (6, 1)]).into(),
|
||||
Signal::AuthoritiesChange(
|
||||
ScheduledChange { delay: 0, next_authorities: to_authorities(vec![(4, 1), (5, 1), (6, 1)]) }
|
||||
).into(),
|
||||
],
|
||||
});
|
||||
|
||||
assert_eq!(System::events(), vec![
|
||||
EventRecord {
|
||||
phase: Phase::Finalization,
|
||||
event: RawEvent::NewAuthorities(vec![(4, 1), (5, 1), (6, 1)]).into(),
|
||||
event: Event::NewAuthorities(to_authorities(vec![(4, 1), (5, 1), (6, 1)])).into(),
|
||||
topics: vec![],
|
||||
},
|
||||
]);
|
||||
@@ -57,12 +59,14 @@ fn authorities_change_logged() {
|
||||
fn authorities_change_logged_after_delay() {
|
||||
with_externalities(&mut new_test_ext(vec![(1, 1), (2, 1), (3, 1)]), || {
|
||||
System::initialize(&1, &Default::default(), &Default::default(), &Default::default());
|
||||
Grandpa::schedule_change(vec![(4, 1), (5, 1), (6, 1)], 1, None).unwrap();
|
||||
Grandpa::schedule_change(to_authorities(vec![(4, 1), (5, 1), (6, 1)]), 1, None).unwrap();
|
||||
Grandpa::on_finalize(1);
|
||||
let header = System::finalize();
|
||||
assert_eq!(header.digest, testing::Digest {
|
||||
assert_eq!(header.digest, Digest {
|
||||
logs: vec![
|
||||
RawLog::AuthoritiesChangeSignal(1, vec![(4, 1), (5, 1), (6, 1)]).into(),
|
||||
Signal::AuthoritiesChange(
|
||||
ScheduledChange { delay: 1, next_authorities: to_authorities(vec![(4, 1), (5, 1), (6, 1)]) }
|
||||
).into(),
|
||||
],
|
||||
});
|
||||
|
||||
@@ -77,7 +81,7 @@ fn authorities_change_logged_after_delay() {
|
||||
assert_eq!(System::events(), vec![
|
||||
EventRecord {
|
||||
phase: Phase::Finalization,
|
||||
event: RawEvent::NewAuthorities(vec![(4, 1), (5, 1), (6, 1)]).into(),
|
||||
event: Event::NewAuthorities(to_authorities(vec![(4, 1), (5, 1), (6, 1)])).into(),
|
||||
topics: vec![],
|
||||
},
|
||||
]);
|
||||
@@ -88,23 +92,23 @@ fn authorities_change_logged_after_delay() {
|
||||
fn cannot_schedule_change_when_one_pending() {
|
||||
with_externalities(&mut new_test_ext(vec![(1, 1), (2, 1), (3, 1)]), || {
|
||||
System::initialize(&1, &Default::default(), &Default::default(), &Default::default());
|
||||
Grandpa::schedule_change(vec![(4, 1), (5, 1), (6, 1)], 1, None).unwrap();
|
||||
assert!(Grandpa::pending_change().is_some());
|
||||
assert!(Grandpa::schedule_change(vec![(5, 1)], 1, None).is_err());
|
||||
Grandpa::schedule_change(to_authorities(vec![(4, 1), (5, 1), (6, 1)]), 1, None).unwrap();
|
||||
assert!(<PendingChange<Test>>::exists());
|
||||
assert!(Grandpa::schedule_change(to_authorities(vec![(5, 1)]), 1, None).is_err());
|
||||
|
||||
Grandpa::on_finalize(1);
|
||||
let header = System::finalize();
|
||||
|
||||
System::initialize(&2, &header.hash(), &Default::default(), &Default::default());
|
||||
assert!(Grandpa::pending_change().is_some());
|
||||
assert!(Grandpa::schedule_change(vec![(5, 1)], 1, None).is_err());
|
||||
assert!(<PendingChange<Test>>::exists());
|
||||
assert!(Grandpa::schedule_change(to_authorities(vec![(5, 1)]), 1, None).is_err());
|
||||
|
||||
Grandpa::on_finalize(2);
|
||||
let header = System::finalize();
|
||||
|
||||
System::initialize(&3, &header.hash(), &Default::default(), &Default::default());
|
||||
assert!(Grandpa::pending_change().is_none());
|
||||
assert!(Grandpa::schedule_change(vec![(5, 1)], 1, None).is_ok());
|
||||
assert!(!<PendingChange<Test>>::exists());
|
||||
assert!(Grandpa::schedule_change(to_authorities(vec![(5, 1)]), 1, None).is_ok());
|
||||
|
||||
Grandpa::on_finalize(3);
|
||||
let _header = System::finalize();
|
||||
@@ -116,11 +120,11 @@ fn new_decodes_from_old() {
|
||||
let old = OldStoredPendingChange {
|
||||
scheduled_at: 5u32,
|
||||
delay: 100u32,
|
||||
next_authorities: vec![(1u64, 5), (2u64, 10), (3u64, 2)],
|
||||
next_authorities: to_authorities(vec![(1, 5), (2, 10), (3, 2)]),
|
||||
};
|
||||
|
||||
let encoded = old.encode();
|
||||
let new = StoredPendingChange::<u32, u64>::decode(&mut &encoded[..]).unwrap();
|
||||
let new = StoredPendingChange::<u32>::decode(&mut &encoded[..]).unwrap();
|
||||
assert!(new.forced.is_none());
|
||||
assert_eq!(new.scheduled_at, old.scheduled_at);
|
||||
assert_eq!(new.delay, old.delay);
|
||||
@@ -132,23 +136,23 @@ fn dispatch_forced_change() {
|
||||
with_externalities(&mut new_test_ext(vec![(1, 1), (2, 1), (3, 1)]), || {
|
||||
System::initialize(&1, &Default::default(), &Default::default(), &Default::default());
|
||||
Grandpa::schedule_change(
|
||||
vec![(4, 1), (5, 1), (6, 1)],
|
||||
to_authorities(vec![(4, 1), (5, 1), (6, 1)]),
|
||||
5,
|
||||
Some(0),
|
||||
).unwrap();
|
||||
|
||||
assert!(Grandpa::pending_change().is_some());
|
||||
assert!(Grandpa::schedule_change(vec![(5, 1)], 1, Some(0)).is_err());
|
||||
assert!(<PendingChange<Test>>::exists());
|
||||
assert!(Grandpa::schedule_change(to_authorities(vec![(5, 1)]), 1, Some(0)).is_err());
|
||||
|
||||
Grandpa::on_finalize(1);
|
||||
let mut header = System::finalize();
|
||||
|
||||
for i in 2..7 {
|
||||
System::initialize(&i, &header.hash(), &Default::default(), &Default::default());
|
||||
assert!(Grandpa::pending_change().unwrap().forced.is_some());
|
||||
assert!(<PendingChange<Test>>::get().unwrap().forced.is_some());
|
||||
assert_eq!(Grandpa::next_forced(), Some(11));
|
||||
assert!(Grandpa::schedule_change(vec![(5, 1)], 1, None).is_err());
|
||||
assert!(Grandpa::schedule_change(vec![(5, 1)], 1, Some(0)).is_err());
|
||||
assert!(Grandpa::schedule_change(to_authorities(vec![(5, 1)]), 1, None).is_err());
|
||||
assert!(Grandpa::schedule_change(to_authorities(vec![(5, 1)]), 1, Some(0)).is_err());
|
||||
|
||||
Grandpa::on_finalize(i);
|
||||
header = System::finalize();
|
||||
@@ -158,9 +162,9 @@ fn dispatch_forced_change() {
|
||||
// add a normal change.
|
||||
{
|
||||
System::initialize(&7, &header.hash(), &Default::default(), &Default::default());
|
||||
assert!(Grandpa::pending_change().is_none());
|
||||
assert_eq!(Grandpa::grandpa_authorities(), vec![(4, 1), (5, 1), (6, 1)]);
|
||||
assert!(Grandpa::schedule_change(vec![(5, 1)], 1, None).is_ok());
|
||||
assert!(!<PendingChange<Test>>::exists());
|
||||
assert_eq!(Grandpa::grandpa_authorities(), to_authorities(vec![(4, 1), (5, 1), (6, 1)]));
|
||||
assert!(Grandpa::schedule_change(to_authorities(vec![(5, 1)]), 1, None).is_ok());
|
||||
Grandpa::on_finalize(7);
|
||||
header = System::finalize();
|
||||
}
|
||||
@@ -168,9 +172,9 @@ fn dispatch_forced_change() {
|
||||
// run the normal change.
|
||||
{
|
||||
System::initialize(&8, &header.hash(), &Default::default(), &Default::default());
|
||||
assert!(Grandpa::pending_change().is_some());
|
||||
assert_eq!(Grandpa::grandpa_authorities(), vec![(4, 1), (5, 1), (6, 1)]);
|
||||
assert!(Grandpa::schedule_change(vec![(5, 1)], 1, None).is_err());
|
||||
assert!(<PendingChange<Test>>::exists());
|
||||
assert_eq!(Grandpa::grandpa_authorities(), to_authorities(vec![(4, 1), (5, 1), (6, 1)]));
|
||||
assert!(Grandpa::schedule_change(to_authorities(vec![(5, 1)]), 1, None).is_err());
|
||||
Grandpa::on_finalize(8);
|
||||
header = System::finalize();
|
||||
}
|
||||
@@ -179,18 +183,18 @@ fn dispatch_forced_change() {
|
||||
// time.
|
||||
for i in 9..11 {
|
||||
System::initialize(&i, &header.hash(), &Default::default(), &Default::default());
|
||||
assert!(Grandpa::pending_change().is_none());
|
||||
assert_eq!(Grandpa::grandpa_authorities(), vec![(5, 1)]);
|
||||
assert!(!<PendingChange<Test>>::exists());
|
||||
assert_eq!(Grandpa::grandpa_authorities(), to_authorities(vec![(5, 1)]));
|
||||
assert_eq!(Grandpa::next_forced(), Some(11));
|
||||
assert!(Grandpa::schedule_change(vec![(5, 1), (6, 1)], 5, Some(0)).is_err());
|
||||
assert!(Grandpa::schedule_change(to_authorities(vec![(5, 1), (6, 1)]), 5, Some(0)).is_err());
|
||||
Grandpa::on_finalize(i);
|
||||
header = System::finalize();
|
||||
}
|
||||
|
||||
{
|
||||
System::initialize(&11, &header.hash(), &Default::default(), &Default::default());
|
||||
assert!(Grandpa::pending_change().is_none());
|
||||
assert!(Grandpa::schedule_change(vec![(5, 1), (6, 1), (7, 1)], 5, Some(0)).is_ok());
|
||||
assert!(!<PendingChange<Test>>::exists());
|
||||
assert!(Grandpa::schedule_change(to_authorities(vec![(5, 1), (6, 1), (7, 1)]), 5, Some(0)).is_ok());
|
||||
assert_eq!(Grandpa::next_forced(), Some(21));
|
||||
Grandpa::on_finalize(11);
|
||||
header = System::finalize();
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
use std::collections::HashSet;
|
||||
use ref_thread_local::{ref_thread_local, RefThreadLocal};
|
||||
use primitives::BuildStorage;
|
||||
use primitives::testing::{Digest, DigestItem, Header};
|
||||
use primitives::testing::Header;
|
||||
use substrate_primitives::{H256, Blake2Hasher};
|
||||
use srml_support::impl_outer_origin;
|
||||
use {runtime_io, system};
|
||||
@@ -71,12 +71,10 @@ impl system::Trait for Runtime {
|
||||
type BlockNumber = u64;
|
||||
type Hash = H256;
|
||||
type Hashing = ::primitives::traits::BlakeTwo256;
|
||||
type Digest = Digest;
|
||||
type AccountId = u64;
|
||||
type Lookup = Indices;
|
||||
type Header = Header;
|
||||
type Event = ();
|
||||
type Log = DigestItem;
|
||||
}
|
||||
impl Trait for Runtime {
|
||||
type AccountIndex = u64;
|
||||
|
||||
@@ -11,7 +11,6 @@ parity-codec = { version = "3.3", default-features = false, features = ["derive"
|
||||
rstd = { package = "sr-std", path = "../../core/sr-std", default-features = false }
|
||||
primitives = { package = "sr-primitives", path = "../../core/sr-primitives", default-features = false }
|
||||
srml-support = { path = "../support", default-features = false }
|
||||
consensus = { package = "srml-consensus", path = "../consensus", default-features = false }
|
||||
system = { package = "srml-system", path = "../system", default-features = false }
|
||||
timestamp = { package = "srml-timestamp", path = "../timestamp", default-features = false }
|
||||
|
||||
@@ -29,7 +28,5 @@ std = [
|
||||
"rstd/std",
|
||||
"srml-support/std",
|
||||
"primitives/std",
|
||||
"consensus/std",
|
||||
"system/std",
|
||||
"timestamp/std"
|
||||
]
|
||||
|
||||
+277
-272
@@ -115,53 +115,156 @@
|
||||
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
|
||||
use rstd::prelude::*;
|
||||
use primitives::traits::{Zero, One, Convert};
|
||||
use rstd::{prelude::*, marker::PhantomData, ops::Rem};
|
||||
#[cfg(not(feature = "std"))]
|
||||
use rstd::alloc::borrow::ToOwned;
|
||||
use parity_codec::Decode;
|
||||
use primitives::traits::{Zero, Saturating, Member, OpaqueKeys};
|
||||
use srml_support::{StorageValue, StorageMap, for_each_tuple, decl_module, decl_event, decl_storage};
|
||||
use srml_support::{dispatch::Result, traits::OnFreeBalanceZero};
|
||||
use srml_support::{ensure, traits::{OnFreeBalanceZero, Get}, Parameter, print};
|
||||
use system::ensure_signed;
|
||||
use rstd::ops::Mul;
|
||||
|
||||
/// A session has changed.
|
||||
pub trait OnSessionChange<T> {
|
||||
/// Session has changed.
|
||||
fn on_session_change(time_elapsed: T, should_reward: bool);
|
||||
/// Simple index type with which we can count sessions.
|
||||
pub type SessionIndex = u32;
|
||||
|
||||
pub trait ShouldEndSession<BlockNumber> {
|
||||
fn should_end_session(now: BlockNumber) -> bool;
|
||||
}
|
||||
|
||||
macro_rules! impl_session_change {
|
||||
pub struct PeriodicSessions<
|
||||
Period,
|
||||
Offset,
|
||||
>(PhantomData<(Period, Offset)>);
|
||||
|
||||
impl<
|
||||
BlockNumber: Rem<Output=BlockNumber> + Saturating + Zero,
|
||||
Period: Get<BlockNumber>,
|
||||
Offset: Get<BlockNumber>,
|
||||
> ShouldEndSession<BlockNumber> for PeriodicSessions<Period, Offset> {
|
||||
fn should_end_session(now: BlockNumber) -> bool {
|
||||
((now.saturating_sub(Offset::get())) % Period::get()).is_zero()
|
||||
}
|
||||
}
|
||||
|
||||
pub trait OnSessionEnding<AccountId> {
|
||||
/// Handle the fact that the session is ending, and optionally provide the new validator set.
|
||||
fn on_session_ending(i: SessionIndex) -> Option<Vec<AccountId>>;
|
||||
}
|
||||
|
||||
impl<A> OnSessionEnding<A> for () {
|
||||
fn on_session_ending(_: SessionIndex) -> Option<Vec<A>> { None }
|
||||
}
|
||||
|
||||
/// Handler for when a session keys set changes.
|
||||
pub trait SessionHandler<AccountId> {
|
||||
/// Session set has changed; act appropriately.
|
||||
fn on_new_session<Ks: OpaqueKeys>(changed: bool, validators: &[(AccountId, Ks)]);
|
||||
|
||||
/// A validator got disabled. Act accordingly until a new session begins.
|
||||
fn on_disabled(validator_index: usize);
|
||||
}
|
||||
|
||||
pub trait OneSessionHandler<AccountId> {
|
||||
type Key: Decode + Default;
|
||||
fn on_new_session<'a, I: 'a>(changed: bool, validators: I)
|
||||
where I: Iterator<Item=(&'a AccountId, Self::Key)>, AccountId: 'a;
|
||||
fn on_disabled(i: usize);
|
||||
}
|
||||
|
||||
macro_rules! impl_session_handlers {
|
||||
() => (
|
||||
impl<T> OnSessionChange<T> for () {
|
||||
fn on_session_change(_: T, _: bool) {}
|
||||
impl<AId> SessionHandler<AId> for () {
|
||||
fn on_new_session<Ks: OpaqueKeys>(_: bool, _: &[(AId, Ks)]) {}
|
||||
fn on_disabled(_: usize) {}
|
||||
}
|
||||
);
|
||||
|
||||
( $($t:ident)* ) => {
|
||||
impl<T: Clone, $($t: OnSessionChange<T>),*> OnSessionChange<T> for ($($t,)*) {
|
||||
fn on_session_change(time_elapsed: T, should_reward: bool) {
|
||||
$($t::on_session_change(time_elapsed.clone(), should_reward);)*
|
||||
impl<AId, $( $t: OneSessionHandler<AId> ),*> SessionHandler<AId> for ( $( $t , )* ) {
|
||||
fn on_new_session<Ks: OpaqueKeys>(changed: bool, validators: &[(AId, Ks)]) {
|
||||
let mut i: usize = 0;
|
||||
$(
|
||||
i += 1;
|
||||
let our_keys = validators.iter()
|
||||
.map(|k| (&k.0, k.1.get::<$t::Key>(i - 1).unwrap_or_default()));
|
||||
$t::on_new_session(changed, our_keys);
|
||||
)*
|
||||
}
|
||||
fn on_disabled(i: usize) {
|
||||
$(
|
||||
$t::on_disabled(i);
|
||||
)*
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for_each_tuple!(impl_session_change);
|
||||
for_each_tuple!(impl_session_handlers);
|
||||
|
||||
pub trait Trait: timestamp::Trait + consensus::Trait {
|
||||
/// Create a session key from an account key.
|
||||
type ConvertAccountIdToSessionKey: Convert<Self::AccountId, Option<Self::SessionKey>>;
|
||||
|
||||
/// Handler when a session changes.
|
||||
type OnSessionChange: OnSessionChange<Self::Moment>;
|
||||
|
||||
pub trait Trait: system::Trait {
|
||||
/// The overarching event type.
|
||||
type Event: From<Event<Self>> + Into<<Self as system::Trait>::Event>;
|
||||
type Event: From<Event> + Into<<Self as system::Trait>::Event>;
|
||||
|
||||
/// Indicator for when to end the session.
|
||||
type ShouldEndSession: ShouldEndSession<Self::BlockNumber>;
|
||||
|
||||
/// Handler for when a session is about to end.
|
||||
type OnSessionEnding: OnSessionEnding<Self::AccountId>;
|
||||
|
||||
/// Handler when a session has changed.
|
||||
type SessionHandler: SessionHandler<Self::AccountId>;
|
||||
|
||||
/// The keys.
|
||||
type Keys: OpaqueKeys + Member + Parameter + Default;
|
||||
}
|
||||
|
||||
type OpaqueKey = Vec<u8>;
|
||||
|
||||
decl_storage! {
|
||||
trait Store for Module<T: Trait> as Session {
|
||||
/// The current set of validators.
|
||||
pub Validators get(validators) config(): Vec<T::AccountId>;
|
||||
|
||||
/// Current index of the session.
|
||||
pub CurrentIndex get(current_index): SessionIndex;
|
||||
|
||||
/// True if anything has changed in this session.
|
||||
Changed: bool;
|
||||
|
||||
/// The next key for a given validator.
|
||||
NextKeyFor build(|config: &GenesisConfig<T>| {
|
||||
config.keys.clone()
|
||||
}): map T::AccountId => Option<T::Keys>;
|
||||
|
||||
/// The keys that are currently active.
|
||||
Active build(|config: &GenesisConfig<T>| {
|
||||
(0..T::Keys::count()).map(|i| (
|
||||
i as u32,
|
||||
config.keys.iter()
|
||||
.map(|x| x.1.get_raw(i).to_vec())
|
||||
.collect::<Vec<OpaqueKey>>(),
|
||||
)).collect::<Vec<(u32, Vec<OpaqueKey>)>>()
|
||||
}): map u32 => Vec<OpaqueKey>;
|
||||
}
|
||||
add_extra_genesis {
|
||||
config(keys): Vec<(T::AccountId, T::Keys)>;
|
||||
}
|
||||
}
|
||||
|
||||
decl_event!(
|
||||
pub enum Event {
|
||||
/// New session has happened. Note that the argument is the session index, not the block
|
||||
/// number as the type might suggest.
|
||||
NewSession(SessionIndex),
|
||||
}
|
||||
);
|
||||
|
||||
decl_module! {
|
||||
pub struct Module<T: Trait> for enum Call where origin: T::Origin {
|
||||
fn deposit_event<T>() = default;
|
||||
fn deposit_event() = default;
|
||||
|
||||
/// Sets the session key of the function caller to `key`.
|
||||
/// Sets the session key(s) of the function caller to `key`.
|
||||
/// Allows an account to set its session key prior to becoming a validator.
|
||||
/// This doesn't take effect until the next session.
|
||||
///
|
||||
@@ -171,164 +274,94 @@ decl_module! {
|
||||
/// - O(1).
|
||||
/// - One extra DB entry.
|
||||
/// # </weight>
|
||||
fn set_key(origin, key: T::SessionKey) {
|
||||
fn set_keys(origin, keys: T::Keys, proof: Vec<u8>) {
|
||||
let who = ensure_signed(origin)?;
|
||||
// set new value for next session
|
||||
<NextKeyFor<T>>::insert(who, key);
|
||||
}
|
||||
|
||||
/// Set a new session length. Won't kick in until the next session change (at current length).
|
||||
///
|
||||
/// Dispatch origin of this call must be _root_.
|
||||
fn set_length(#[compact] new: T::BlockNumber) {
|
||||
<NextSessionLength<T>>::put(new);
|
||||
}
|
||||
ensure!(keys.ownership_proof_is_valid(&proof), "invalid ownership proof");
|
||||
|
||||
/// Forces a new session.
|
||||
///
|
||||
/// Dispatch origin of this call must be _root_.
|
||||
fn force_new_session(apply_rewards: bool) -> Result {
|
||||
Self::apply_force_new_session(apply_rewards)
|
||||
let old_keys = <NextKeyFor<T>>::get(&who);
|
||||
let mut updates = vec![];
|
||||
|
||||
for i in 0..T::Keys::count() {
|
||||
let new_key = keys.get_raw(i);
|
||||
let maybe_old_key = old_keys.as_ref().map(|o| o.get_raw(i));
|
||||
if maybe_old_key == Some(new_key) {
|
||||
// no change.
|
||||
updates.push(None);
|
||||
continue;
|
||||
}
|
||||
let mut active = <Active<T>>::get(i as u32);
|
||||
match active.binary_search_by(|k| k[..].cmp(&new_key)) {
|
||||
Ok(_) => return Err("duplicate key provided"),
|
||||
Err(pos) => active.insert(pos, new_key.to_owned()),
|
||||
}
|
||||
if let Some(old_key) = maybe_old_key {
|
||||
match active.binary_search_by(|k| k[..].cmp(&old_key)) {
|
||||
Ok(pos) => { active.remove(pos); }
|
||||
Err(_) => {
|
||||
// unreachable as long as our state is valid. we don't want to panic if
|
||||
// it isn't, though.
|
||||
print("ERROR: active doesn't contain outgoing key");
|
||||
}
|
||||
}
|
||||
}
|
||||
updates.push(Some((i, active)));
|
||||
}
|
||||
|
||||
// Update the active sets.
|
||||
for (i, active) in updates.into_iter().filter_map(|x| x) {
|
||||
<Active<T>>::insert(i as u32, active);
|
||||
}
|
||||
// Set new keys value for next session.
|
||||
<NextKeyFor<T>>::insert(who, keys);
|
||||
// Something changed.
|
||||
<Changed<T>>::put(true);
|
||||
}
|
||||
|
||||
/// Called when a block is finalized. Will rotate session if it is the last
|
||||
/// block of the current session.
|
||||
fn on_finalize(n: T::BlockNumber) {
|
||||
Self::check_rotate_session(n);
|
||||
fn on_initialize(n: T::BlockNumber) {
|
||||
if T::ShouldEndSession::should_end_session(n) {
|
||||
Self::rotate_session();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
decl_event!(
|
||||
pub enum Event<T> where <T as system::Trait>::BlockNumber {
|
||||
/// New session has happened. Note that the argument is the session index, not the block
|
||||
/// number as the type might suggest.
|
||||
NewSession(BlockNumber),
|
||||
}
|
||||
);
|
||||
|
||||
decl_storage! {
|
||||
trait Store for Module<T: Trait> as Session {
|
||||
/// The current set of validators.
|
||||
pub Validators get(validators) config(): Vec<T::AccountId>;
|
||||
/// Current length of the session.
|
||||
pub SessionLength get(length) config(session_length): T::BlockNumber = 1000.into();
|
||||
/// Current index of the session.
|
||||
pub CurrentIndex get(current_index) build(|_| 0.into()): T::BlockNumber;
|
||||
/// Timestamp when current session started.
|
||||
pub CurrentStart get(current_start) build(|_| T::Moment::zero()): T::Moment;
|
||||
|
||||
/// New session is being forced if this entry exists; in which case, the boolean value is true if
|
||||
/// the new session should be considered a normal rotation (rewardable) and false if the new session
|
||||
/// should be considered exceptional (slashable).
|
||||
pub ForcingNewSession get(forcing_new_session): Option<bool>;
|
||||
/// Block at which the session length last changed.
|
||||
LastLengthChange: Option<T::BlockNumber>;
|
||||
/// The next key for a given validator.
|
||||
NextKeyFor build(|config: &GenesisConfig<T>| {
|
||||
config.keys.clone()
|
||||
}): map T::AccountId => Option<T::SessionKey>;
|
||||
/// The next session length.
|
||||
NextSessionLength: Option<T::BlockNumber>;
|
||||
}
|
||||
add_extra_genesis {
|
||||
config(keys): Vec<(T::AccountId, T::SessionKey)>;
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Trait> Module<T> {
|
||||
/// The current number of validators.
|
||||
pub fn validator_count() -> u32 {
|
||||
<Validators<T>>::get().len() as u32
|
||||
}
|
||||
|
||||
/// The last length change if there was one, zero if not.
|
||||
pub fn last_length_change() -> T::BlockNumber {
|
||||
<LastLengthChange<T>>::get().unwrap_or_else(T::BlockNumber::zero)
|
||||
}
|
||||
|
||||
// INTERNAL API (available to other runtime modules)
|
||||
/// Forces a new session, no origin.
|
||||
pub fn apply_force_new_session(apply_rewards: bool) -> Result {
|
||||
<ForcingNewSession<T>>::put(apply_rewards);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Set the current set of validators.
|
||||
///
|
||||
/// Called by `staking::new_era` only. `rotate_session` must be called after this in order to
|
||||
/// update the session keys to the next validator set.
|
||||
pub fn set_validators(new: &[T::AccountId]) {
|
||||
<Validators<T>>::put(&new.to_vec());
|
||||
}
|
||||
|
||||
/// Hook to be called after transaction processing.
|
||||
pub fn check_rotate_session(block_number: T::BlockNumber) {
|
||||
// Do this last, after the staking system has had the chance to switch out the authorities for the
|
||||
// new set.
|
||||
// Check block number and call `rotate_session` if necessary.
|
||||
let is_final_block = ((block_number - Self::last_length_change()) % Self::length()).is_zero();
|
||||
let (should_end_session, apply_rewards) = <ForcingNewSession<T>>::take()
|
||||
.map_or((is_final_block, is_final_block), |apply_rewards| (true, apply_rewards));
|
||||
if should_end_session {
|
||||
Self::rotate_session(is_final_block, apply_rewards);
|
||||
}
|
||||
}
|
||||
|
||||
/// Move on to next session: register the new authority set.
|
||||
pub fn rotate_session(is_final_block: bool, apply_rewards: bool) {
|
||||
let now = <timestamp::Module<T>>::get();
|
||||
let time_elapsed = now.clone() - Self::current_start();
|
||||
let session_index = <CurrentIndex<T>>::get() + One::one();
|
||||
|
||||
Self::deposit_event(RawEvent::NewSession(session_index));
|
||||
|
||||
pub fn rotate_session() {
|
||||
// Increment current session index.
|
||||
<CurrentIndex<T>>::put(session_index);
|
||||
<CurrentStart<T>>::put(now);
|
||||
let session_index = <CurrentIndex<T>>::get();
|
||||
|
||||
// Enact session length change.
|
||||
let len_changed = if let Some(next_len) = <NextSessionLength<T>>::take() {
|
||||
<SessionLength<T>>::put(next_len);
|
||||
true
|
||||
let mut changed = <Changed<T>>::take();
|
||||
|
||||
// See if we have a new validator set.
|
||||
let validators = if let Some(new) = T::OnSessionEnding::on_session_ending(session_index) {
|
||||
changed = true;
|
||||
<Validators<T>>::put(&new);
|
||||
new
|
||||
} else {
|
||||
false
|
||||
<Validators<T>>::get()
|
||||
};
|
||||
if len_changed || !is_final_block {
|
||||
let block_number = <system::Module<T>>::block_number();
|
||||
<LastLengthChange<T>>::put(block_number);
|
||||
}
|
||||
|
||||
T::OnSessionChange::on_session_change(time_elapsed, apply_rewards);
|
||||
let session_index = session_index + 1;
|
||||
<CurrentIndex<T>>::put(session_index);
|
||||
|
||||
// Update any changes in session keys.
|
||||
let v = Self::validators();
|
||||
<consensus::Module<T>>::set_authority_count(v.len() as u32);
|
||||
for (i, v) in v.into_iter().enumerate() {
|
||||
<consensus::Module<T>>::set_authority(
|
||||
i as u32,
|
||||
&<NextKeyFor<T>>::get(&v)
|
||||
.or_else(|| T::ConvertAccountIdToSessionKey::convert(v))
|
||||
.unwrap_or_default()
|
||||
);
|
||||
};
|
||||
// Record that this happened.
|
||||
Self::deposit_event(Event::NewSession(session_index));
|
||||
|
||||
// Tell everyone about the new session keys.
|
||||
let amalgamated = validators.into_iter()
|
||||
.map(|a| { let k = <NextKeyFor<T>>::get(&a).unwrap_or_default(); (a, k) })
|
||||
.collect::<Vec<_>>();
|
||||
T::SessionHandler::on_new_session::<T::Keys>(changed, &amalgamated);
|
||||
}
|
||||
|
||||
/// Get the time that should elapse over a session if everything is working perfectly.
|
||||
pub fn ideal_session_duration() -> T::Moment {
|
||||
let block_period: T::Moment = <timestamp::Module<T>>::minimum_period();
|
||||
let session_length: T::BlockNumber = Self::length();
|
||||
Mul::<T::BlockNumber>::mul(block_period, session_length)
|
||||
}
|
||||
|
||||
/// Number of blocks remaining in this session, not counting this one. If the session is
|
||||
/// due to rotate at the end of this block, then it will return 0. If the session just began, then
|
||||
/// it will return `Self::length() - 1`.
|
||||
pub fn blocks_remaining() -> T::BlockNumber {
|
||||
let length = Self::length();
|
||||
let length_minus_1 = length - One::one();
|
||||
let block_number = <system::Module<T>>::block_number();
|
||||
length_minus_1 - (block_number - Self::last_length_change() + length_minus_1) % length
|
||||
/// Disable the validator of index `i`.
|
||||
pub fn disable(i: usize) {
|
||||
T::SessionHandler::on_disabled(i);
|
||||
<Changed<T>>::put(true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -346,8 +379,8 @@ mod tests {
|
||||
use runtime_io::with_externalities;
|
||||
use substrate_primitives::{H256, Blake2Hasher};
|
||||
use primitives::BuildStorage;
|
||||
use primitives::traits::{BlakeTwo256, IdentityLookup};
|
||||
use primitives::testing::{Digest, DigestItem, Header, UintAuthorityId, ConvertUintAuthorityId};
|
||||
use primitives::traits::{BlakeTwo256, IdentityLookup, OnInitialize};
|
||||
use primitives::testing::{Header, UintAuthorityId};
|
||||
|
||||
impl_outer_origin!{
|
||||
pub enum Origin for Test {}
|
||||
@@ -355,62 +388,87 @@ mod tests {
|
||||
|
||||
thread_local!{
|
||||
static NEXT_VALIDATORS: RefCell<Vec<u64>> = RefCell::new(vec![1, 2, 3]);
|
||||
static AUTHORITIES: RefCell<Vec<UintAuthorityId>> =
|
||||
RefCell::new(vec![UintAuthorityId(1), UintAuthorityId(2), UintAuthorityId(3)]);
|
||||
static FORCE_SESSION_END: RefCell<bool> = RefCell::new(false);
|
||||
static SESSION_LENGTH: RefCell<u64> = RefCell::new(2);
|
||||
}
|
||||
|
||||
pub struct TestOnSessionChange;
|
||||
impl OnSessionChange<u64> for TestOnSessionChange {
|
||||
fn on_session_change(_elapsed: u64, _should_reward: bool) {
|
||||
NEXT_VALIDATORS.with(|v| Session::set_validators(&*v.borrow()));
|
||||
pub struct TestShouldEndSession;
|
||||
impl ShouldEndSession<u64> for TestShouldEndSession {
|
||||
fn should_end_session(now: u64) -> bool {
|
||||
let l = SESSION_LENGTH.with(|l| *l.borrow());
|
||||
now % l == 0 || FORCE_SESSION_END.with(|l| { let r = *l.borrow(); *l.borrow_mut() = false; r })
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TestSessionHandler;
|
||||
impl SessionHandler<u64> for TestSessionHandler {
|
||||
fn on_new_session<T: OpaqueKeys>(_changed: bool, validators: &[(u64, T)]) {
|
||||
AUTHORITIES.with(|l|
|
||||
*l.borrow_mut() = validators.iter().map(|(_, id)| id.get::<UintAuthorityId>(0).unwrap_or_default()).collect()
|
||||
);
|
||||
}
|
||||
fn on_disabled(_validator_index: usize) {}
|
||||
}
|
||||
|
||||
pub struct TestOnSessionEnding;
|
||||
impl OnSessionEnding<u64> for TestOnSessionEnding {
|
||||
fn on_session_ending(_: SessionIndex) -> Option<Vec<u64>> {
|
||||
Some(NEXT_VALIDATORS.with(|l| l.borrow().clone()))
|
||||
}
|
||||
}
|
||||
|
||||
fn authorities() -> Vec<UintAuthorityId> {
|
||||
AUTHORITIES.with(|l| l.borrow().to_vec())
|
||||
}
|
||||
|
||||
fn force_new_session() {
|
||||
FORCE_SESSION_END.with(|l| *l.borrow_mut() = true )
|
||||
}
|
||||
|
||||
fn set_session_length(x: u64) {
|
||||
SESSION_LENGTH.with(|l| *l.borrow_mut() = x )
|
||||
}
|
||||
|
||||
#[derive(Clone, Eq, PartialEq)]
|
||||
pub struct Test;
|
||||
impl consensus::Trait for Test {
|
||||
type Log = DigestItem;
|
||||
type SessionKey = UintAuthorityId;
|
||||
type InherentOfflineReport = ();
|
||||
}
|
||||
impl system::Trait for Test {
|
||||
type Origin = Origin;
|
||||
type Index = u64;
|
||||
type BlockNumber = u64;
|
||||
type Hash = H256;
|
||||
type Hashing = BlakeTwo256;
|
||||
type Digest = Digest;
|
||||
type AccountId = u64;
|
||||
type Lookup = IdentityLookup<Self::AccountId>;
|
||||
type Header = Header;
|
||||
type Event = ();
|
||||
type Log = DigestItem;
|
||||
}
|
||||
impl timestamp::Trait for Test {
|
||||
type Moment = u64;
|
||||
type OnTimestampSet = ();
|
||||
}
|
||||
impl Trait for Test {
|
||||
type ConvertAccountIdToSessionKey = ConvertUintAuthorityId;
|
||||
type OnSessionChange = TestOnSessionChange;
|
||||
type ShouldEndSession = TestShouldEndSession;
|
||||
type OnSessionEnding = TestOnSessionEnding;
|
||||
type SessionHandler = TestSessionHandler;
|
||||
type Keys = UintAuthorityId;
|
||||
type Event = ();
|
||||
}
|
||||
|
||||
type System = system::Module<Test>;
|
||||
type Consensus = consensus::Module<Test>;
|
||||
type Session = Module<Test>;
|
||||
|
||||
fn new_test_ext() -> runtime_io::TestExternalities<Blake2Hasher> {
|
||||
let mut t = system::GenesisConfig::<Test>::default().build_storage().unwrap().0;
|
||||
t.extend(consensus::GenesisConfig::<Test>{
|
||||
code: vec![],
|
||||
authorities: NEXT_VALIDATORS.with(|l| l.borrow().iter().cloned().map(UintAuthorityId).collect()),
|
||||
}.build_storage().unwrap().0);
|
||||
t.extend(timestamp::GenesisConfig::<Test>{
|
||||
minimum_period: 5,
|
||||
}.build_storage().unwrap().0);
|
||||
t.extend(GenesisConfig::<Test>{
|
||||
session_length: 2,
|
||||
validators: NEXT_VALIDATORS.with(|l| l.borrow().clone()),
|
||||
keys: vec![],
|
||||
keys: NEXT_VALIDATORS.with(|l|
|
||||
l.borrow().iter().cloned().map(|i| (i, UintAuthorityId(i))).collect()
|
||||
),
|
||||
}.build_storage().unwrap().0);
|
||||
runtime_io::TestExternalities::new(t)
|
||||
}
|
||||
@@ -418,8 +476,7 @@ mod tests {
|
||||
#[test]
|
||||
fn simple_setup_should_work() {
|
||||
with_externalities(&mut new_test_ext(), || {
|
||||
assert_eq!(Consensus::authorities(), vec![UintAuthorityId(1), UintAuthorityId(2), UintAuthorityId(3)]);
|
||||
assert_eq!(Session::length(), 2);
|
||||
assert_eq!(authorities(), vec![UintAuthorityId(1), UintAuthorityId(2), UintAuthorityId(3)]);
|
||||
assert_eq!(Session::validators(), vec![1, 2, 3]);
|
||||
});
|
||||
}
|
||||
@@ -428,106 +485,56 @@ mod tests {
|
||||
fn authorities_should_track_validators() {
|
||||
with_externalities(&mut new_test_ext(), || {
|
||||
NEXT_VALIDATORS.with(|v| *v.borrow_mut() = vec![1, 2]);
|
||||
assert_ok!(Session::force_new_session(false));
|
||||
Session::check_rotate_session(1);
|
||||
force_new_session();
|
||||
|
||||
System::set_block_number(1);
|
||||
Session::on_initialize(1);
|
||||
assert_eq!(Session::validators(), vec![1, 2]);
|
||||
assert_eq!(Consensus::authorities(), vec![UintAuthorityId(1), UintAuthorityId(2)]);
|
||||
assert_eq!(authorities(), vec![UintAuthorityId(1), UintAuthorityId(2)]);
|
||||
|
||||
NEXT_VALIDATORS.with(|v| *v.borrow_mut() = vec![1, 2, 4]);
|
||||
assert_ok!(Session::force_new_session(false));
|
||||
Session::check_rotate_session(2);
|
||||
assert_ok!(Session::set_keys(Origin::signed(4), UintAuthorityId(4), vec![]));
|
||||
|
||||
force_new_session();
|
||||
System::set_block_number(2);
|
||||
Session::on_initialize(2);
|
||||
assert_eq!(Session::validators(), vec![1, 2, 4]);
|
||||
assert_eq!(Consensus::authorities(), vec![UintAuthorityId(1), UintAuthorityId(2), UintAuthorityId(4)]);
|
||||
assert_eq!(authorities(), vec![UintAuthorityId(1), UintAuthorityId(2), UintAuthorityId(4)]);
|
||||
|
||||
NEXT_VALIDATORS.with(|v| *v.borrow_mut() = vec![1, 2, 3]);
|
||||
assert_ok!(Session::force_new_session(false));
|
||||
Session::check_rotate_session(3);
|
||||
force_new_session();
|
||||
System::set_block_number(3);
|
||||
Session::on_initialize(3);
|
||||
assert_eq!(Session::validators(), vec![1, 2, 3]);
|
||||
assert_eq!(Consensus::authorities(), vec![UintAuthorityId(1), UintAuthorityId(2), UintAuthorityId(3)]);
|
||||
assert_eq!(authorities(), vec![UintAuthorityId(1), UintAuthorityId(2), UintAuthorityId(3)]);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_work_with_early_exit() {
|
||||
with_externalities(&mut new_test_ext(), || {
|
||||
set_session_length(10);
|
||||
|
||||
System::set_block_number(1);
|
||||
assert_ok!(Session::set_length(10));
|
||||
assert_eq!(Session::blocks_remaining(), 1);
|
||||
Session::check_rotate_session(1);
|
||||
|
||||
System::set_block_number(2);
|
||||
assert_eq!(Session::blocks_remaining(), 0);
|
||||
Session::check_rotate_session(2);
|
||||
assert_eq!(Session::length(), 10);
|
||||
|
||||
System::set_block_number(7);
|
||||
assert_eq!(Session::current_index(), 1);
|
||||
assert_eq!(Session::blocks_remaining(), 5);
|
||||
assert_ok!(Session::force_new_session(false));
|
||||
Session::check_rotate_session(7);
|
||||
|
||||
System::set_block_number(8);
|
||||
assert_eq!(Session::current_index(), 2);
|
||||
assert_eq!(Session::blocks_remaining(), 9);
|
||||
Session::check_rotate_session(8);
|
||||
|
||||
System::set_block_number(17);
|
||||
assert_eq!(Session::current_index(), 2);
|
||||
assert_eq!(Session::blocks_remaining(), 0);
|
||||
Session::check_rotate_session(17);
|
||||
|
||||
System::set_block_number(18);
|
||||
assert_eq!(Session::current_index(), 3);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn session_length_change_should_work() {
|
||||
with_externalities(&mut new_test_ext(), || {
|
||||
// Block 1: Change to length 3; no visible change.
|
||||
System::set_block_number(1);
|
||||
assert_ok!(Session::set_length(3));
|
||||
Session::check_rotate_session(1);
|
||||
assert_eq!(Session::length(), 2);
|
||||
Session::on_initialize(1);
|
||||
assert_eq!(Session::current_index(), 0);
|
||||
|
||||
// Block 2: Length now changed to 3. Index incremented.
|
||||
System::set_block_number(2);
|
||||
assert_ok!(Session::set_length(3));
|
||||
Session::check_rotate_session(2);
|
||||
assert_eq!(Session::length(), 3);
|
||||
assert_eq!(Session::current_index(), 1);
|
||||
Session::on_initialize(2);
|
||||
assert_eq!(Session::current_index(), 0);
|
||||
force_new_session();
|
||||
|
||||
// Block 3: Length now changed to 3. Index incremented.
|
||||
System::set_block_number(3);
|
||||
Session::check_rotate_session(3);
|
||||
assert_eq!(Session::length(), 3);
|
||||
Session::on_initialize(3);
|
||||
assert_eq!(Session::current_index(), 1);
|
||||
|
||||
// Block 4: Change to length 2; no visible change.
|
||||
System::set_block_number(4);
|
||||
assert_ok!(Session::set_length(2));
|
||||
Session::check_rotate_session(4);
|
||||
assert_eq!(Session::length(), 3);
|
||||
System::set_block_number(9);
|
||||
Session::on_initialize(9);
|
||||
assert_eq!(Session::current_index(), 1);
|
||||
|
||||
// Block 5: Length now changed to 2. Index incremented.
|
||||
System::set_block_number(5);
|
||||
Session::check_rotate_session(5);
|
||||
assert_eq!(Session::length(), 2);
|
||||
System::set_block_number(10);
|
||||
Session::on_initialize(10);
|
||||
assert_eq!(Session::current_index(), 2);
|
||||
|
||||
// Block 6: No change.
|
||||
System::set_block_number(6);
|
||||
Session::check_rotate_session(6);
|
||||
assert_eq!(Session::length(), 2);
|
||||
assert_eq!(Session::current_index(), 2);
|
||||
|
||||
// Block 7: Next index.
|
||||
System::set_block_number(7);
|
||||
Session::check_rotate_session(7);
|
||||
assert_eq!(Session::length(), 2);
|
||||
assert_eq!(Session::current_index(), 3);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -536,26 +543,24 @@ mod tests {
|
||||
with_externalities(&mut new_test_ext(), || {
|
||||
// Block 1: No change
|
||||
System::set_block_number(1);
|
||||
Session::check_rotate_session(1);
|
||||
assert_eq!(Consensus::authorities(), vec![UintAuthorityId(1), UintAuthorityId(2), UintAuthorityId(3)]);
|
||||
Session::on_initialize(1);
|
||||
assert_eq!(authorities(), vec![UintAuthorityId(1), UintAuthorityId(2), UintAuthorityId(3)]);
|
||||
|
||||
// Block 2: Session rollover, but no change.
|
||||
System::set_block_number(2);
|
||||
Session::check_rotate_session(2);
|
||||
assert_eq!(Consensus::authorities(), vec![UintAuthorityId(1), UintAuthorityId(2), UintAuthorityId(3)]);
|
||||
Session::on_initialize(2);
|
||||
assert_eq!(authorities(), vec![UintAuthorityId(1), UintAuthorityId(2), UintAuthorityId(3)]);
|
||||
|
||||
// Block 3: Set new key for validator 2; no visible change.
|
||||
System::set_block_number(3);
|
||||
assert_ok!(Session::set_key(Origin::signed(2), UintAuthorityId(5)));
|
||||
assert_eq!(Consensus::authorities(), vec![UintAuthorityId(1), UintAuthorityId(2), UintAuthorityId(3)]);
|
||||
|
||||
Session::check_rotate_session(3);
|
||||
assert_eq!(Consensus::authorities(), vec![UintAuthorityId(1), UintAuthorityId(2), UintAuthorityId(3)]);
|
||||
Session::on_initialize(3);
|
||||
assert_ok!(Session::set_keys(Origin::signed(2), UintAuthorityId(5), vec![]));
|
||||
assert_eq!(authorities(), vec![UintAuthorityId(1), UintAuthorityId(2), UintAuthorityId(3)]);
|
||||
|
||||
// Block 4: Session rollover, authority 2 changes.
|
||||
System::set_block_number(4);
|
||||
Session::check_rotate_session(4);
|
||||
assert_eq!(Consensus::authorities(), vec![UintAuthorityId(1), UintAuthorityId(5), UintAuthorityId(3)]);
|
||||
Session::on_initialize(4);
|
||||
assert_eq!(authorities(), vec![UintAuthorityId(1), UintAuthorityId(5), UintAuthorityId(3)]);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,6 @@ rstd = { package = "sr-std", path = "../../core/sr-std", default-features = fals
|
||||
runtime_io = { package = "sr-io", path = "../../core/sr-io", default-features = false }
|
||||
primitives = { package = "sr-primitives", path = "../../core/sr-primitives", default-features = false }
|
||||
srml-support = { path = "../support", default-features = false }
|
||||
consensus = { package = "srml-consensus", path = "../consensus", default-features = false }
|
||||
system = { package = "srml-system", path = "../system", default-features = false }
|
||||
session = { package = "srml-session", path = "../session", default-features = false }
|
||||
|
||||
|
||||
+259
-247
@@ -24,24 +24,25 @@
|
||||
//!
|
||||
//! ## Overview
|
||||
//!
|
||||
//! The Staking module is the means by which a set of network maintainers (known as _authorities_ in some contexts
|
||||
//! and _validators_ in others) are chosen based upon those who voluntarily place funds under deposit. Under deposit,
|
||||
//! those funds are rewarded under normal operation but are held at pain of _slash_ (expropriation) should the
|
||||
//! staked maintainer be found not to be discharging its duties properly.
|
||||
//! The Staking module is the means by which a set of network maintainers (known as _authorities_
|
||||
//! in some contexts and _validators_ in others) are chosen based upon those who voluntarily place
|
||||
//! funds under deposit. Under deposit, those funds are rewarded under normal operation but are
|
||||
//! held at pain of _slash_ (expropriation) should the staked maintainer be found not to be
|
||||
//! discharging its duties properly.
|
||||
//!
|
||||
//! ### Terminology
|
||||
//! <!-- Original author of paragraph: @gavofyork -->
|
||||
//!
|
||||
//! - Staking: The process of locking up funds for some time, placing them at risk of slashing (loss)
|
||||
//! in order to become a rewarded maintainer of the network.
|
||||
//! - Validating: The process of running a node to actively maintain the network, either by producing
|
||||
//! blocks or guaranteeing finality of the chain.
|
||||
//! - Nominating: The process of placing staked funds behind one or more validators in order to share
|
||||
//! in any reward, and punishment, they take.
|
||||
//! - Staking: The process of locking up funds for some time, placing them at risk of slashing
|
||||
//! (loss) in order to become a rewarded maintainer of the network.
|
||||
//! - Validating: The process of running a node to actively maintain the network, either by
|
||||
//! producing blocks or guaranteeing finality of the chain.
|
||||
//! - Nominating: The process of placing staked funds behind one or more validators in order to
|
||||
//! share in any reward, and punishment, they take.
|
||||
//! - Stash account: The account holding an owner's funds used for staking.
|
||||
//! - Controller account: The account that controls an owner's funds for staking.
|
||||
//! - Era: A (whole) number of sessions, which is the period that the validator set (and each validator's
|
||||
//! active nominator set) is recalculated and where rewards are paid out.
|
||||
//! - Era: A (whole) number of sessions, which is the period that the validator set (and each
|
||||
//! validator's active nominator set) is recalculated and where rewards are paid out.
|
||||
//! - Slash: The punishment of a staker by reducing its funds.
|
||||
//!
|
||||
//! ### Goals
|
||||
@@ -57,50 +58,55 @@
|
||||
//!
|
||||
//! #### Staking
|
||||
//!
|
||||
//! Almost any interaction with the Staking module requires a process of _**bonding**_ (also known as
|
||||
//! being a _staker_). To become *bonded*, a fund-holding account known as the _stash account_, which holds
|
||||
//! some or all of the funds that become frozen in place as part of the staking process, is paired with an
|
||||
//! active **controller** account, which issues instructions on how they shall be used.
|
||||
//! Almost any interaction with the Staking module requires a process of _**bonding**_ (also known
|
||||
//! as being a _staker_). To become *bonded*, a fund-holding account known as the _stash account_,
|
||||
//! which holds some or all of the funds that become frozen in place as part of the staking process,
|
||||
//! is paired with an active **controller** account, which issues instructions on how they shall be
|
||||
//! used.
|
||||
//!
|
||||
//! An account pair can become bonded using the [`bond`](./enum.Call.html#variant.bond) call.
|
||||
//!
|
||||
//! Stash accounts can change their associated controller using the
|
||||
//! [`set_controller`](./enum.Call.html#variant.set_controller) call.
|
||||
//!
|
||||
//! There are three possible roles that any staked account pair can be in: `Validator`, `Nominator` and `Idle`
|
||||
//! (defined in [`StakerStatus`](./enum.StakerStatus.html)). There are three corresponding instructions to change between roles, namely:
|
||||
//! There are three possible roles that any staked account pair can be in: `Validator`, `Nominator`
|
||||
//! and `Idle` (defined in [`StakerStatus`](./enum.StakerStatus.html)). There are three
|
||||
//! corresponding instructions to change between roles, namely:
|
||||
//! [`validate`](./enum.Call.html#variant.validate), [`nominate`](./enum.Call.html#variant.nominate),
|
||||
//! and [`chill`](./enum.Call.html#variant.chill).
|
||||
//!
|
||||
//! #### Validating
|
||||
//!
|
||||
//! A **validator** takes the role of either validating blocks or ensuring their finality, maintaining the veracity of
|
||||
//! the network. A validator should avoid both any sort of malicious misbehavior and going offline.
|
||||
//! Bonded accounts that state interest in being a validator do NOT get immediately chosen as a validator. Instead, they
|
||||
//! are declared as a _candidate_ and they _might_ get elected at the _next era_ as a validator. The result of the
|
||||
//! election is determined by nominators and their votes.
|
||||
//! A **validator** takes the role of either validating blocks or ensuring their finality,
|
||||
//! maintaining the veracity of the network. A validator should avoid both any sort of malicious
|
||||
//! misbehavior and going offline. Bonded accounts that state interest in being a validator do NOT
|
||||
//! get immediately chosen as a validator. Instead, they are declared as a _candidate_ and they
|
||||
//! _might_ get elected at the _next era_ as a validator. The result of the election is determined
|
||||
//! by nominators and their votes.
|
||||
//!
|
||||
//! An account can become a validator candidate via the [`validate`](./enum.Call.html#variant.validate) call.
|
||||
//! An account can become a validator candidate via the
|
||||
//! [`validate`](./enum.Call.html#variant.validate) call.
|
||||
//!
|
||||
//! #### Nomination
|
||||
//!
|
||||
//! A **nominator** does not take any _direct_ role in maintaining the network, instead, it votes on a set of validators
|
||||
//! to be elected. Once interest in nomination is stated by an account, it takes effect at the next election round. The
|
||||
//! funds in the nominator's stash account indicate the _weight_ of its vote.
|
||||
//! Both the rewards and any punishment that a validator earns are shared between the validator and its nominators.
|
||||
//! This rule incentivizes the nominators to NOT vote for the misbehaving/offline validators as much as possible, simply
|
||||
//! because the nominators will also lose funds if they vote poorly.
|
||||
//! A **nominator** does not take any _direct_ role in maintaining the network, instead, it votes on
|
||||
//! a set of validators to be elected. Once interest in nomination is stated by an account, it
|
||||
//! takes effect at the next election round. The funds in the nominator's stash account indicate the
|
||||
//! _weight_ of its vote. Both the rewards and any punishment that a validator earns are shared
|
||||
//! between the validator and its nominators. This rule incentivizes the nominators to NOT vote for
|
||||
//! the misbehaving/offline validators as much as possible, simply because the nominators will also
|
||||
//! lose funds if they vote poorly.
|
||||
//!
|
||||
//! An account can become a nominator via the [`nominate`](enum.Call.html#variant.nominate) call.
|
||||
//!
|
||||
//! #### Rewards and Slash
|
||||
//!
|
||||
//! The **reward and slashing** procedure is the core of the Staking module, attempting to _embrace valid behavior_
|
||||
//! while _punishing any misbehavior or lack of availability_.
|
||||
//! The **reward and slashing** procedure is the core of the Staking module, attempting to _embrace
|
||||
//! valid behavior_ while _punishing any misbehavior or lack of availability_.
|
||||
//!
|
||||
//! Slashing can occur at any point in time, once misbehavior is reported. Once slashing is determined, a value is
|
||||
//! deducted from the balance of the validator and all the nominators who voted for this validator
|
||||
//! (values are deducted from the _stash_ account of the slashed entity).
|
||||
//! Slashing can occur at any point in time, once misbehavior is reported. Once slashing is
|
||||
//! determined, a value is deducted from the balance of the validator and all the nominators who
|
||||
//! voted for this validator (values are deducted from the _stash_ account of the slashed entity).
|
||||
//!
|
||||
//! Similar to slashing, rewards are also shared among a validator and its associated nominators.
|
||||
//! Yet, the reward funds are not always transferred to the stash account and can be configured.
|
||||
@@ -108,9 +114,9 @@
|
||||
//!
|
||||
//! #### Chilling
|
||||
//!
|
||||
//! Finally, any of the roles above can choose to step back temporarily and just chill for a while. This means that if
|
||||
//! they are a nominator, they will not be considered as voters anymore and if they are validators, they will no longer
|
||||
//! be a candidate for the next election.
|
||||
//! Finally, any of the roles above can choose to step back temporarily and just chill for a while.
|
||||
//! This means that if they are a nominator, they will not be considered as voters anymore and if
|
||||
//! they are validators, they will no longer be a candidate for the next election.
|
||||
//!
|
||||
//! An account can step back via the [`chill`](enum.Call.html#variant.chill) call.
|
||||
//!
|
||||
@@ -118,8 +124,8 @@
|
||||
//!
|
||||
//! ### Dispatchable Functions
|
||||
//!
|
||||
//! The dispatchable functions of the Staking module enable the steps needed for entities to accept and change their
|
||||
//! role, alongside some helper functions to get/set the metadata of the module.
|
||||
//! The dispatchable functions of the Staking module enable the steps needed for entities to accept
|
||||
//! and change their role, alongside some helper functions to get/set the metadata of the module.
|
||||
//!
|
||||
//! ### Public Functions
|
||||
//!
|
||||
@@ -153,30 +159,34 @@
|
||||
//!
|
||||
//! ### Slot Stake
|
||||
//!
|
||||
//! The term [`SlotStake`](./struct.Module.html#method.slot_stake) will be used throughout this section. It refers
|
||||
//! to a value calculated at the end of each era, containing the _minimum value at stake among all validators._
|
||||
//! Note that a validator's value at stake might be a combination of the validator's own stake
|
||||
//! and the votes it received. See [`Exposure`](./struct.Exposure.html) for more details.
|
||||
//! The term [`SlotStake`](./struct.Module.html#method.slot_stake) will be used throughout this
|
||||
//! section. It refers to a value calculated at the end of each era, containing the _minimum value
|
||||
//! at stake among all validators._ Note that a validator's value at stake might be a combination
|
||||
//! of the validator's own stake and the votes it received. See [`Exposure`](./struct.Exposure.html)
|
||||
//! for more details.
|
||||
//!
|
||||
//! ### Reward Calculation
|
||||
//!
|
||||
//! Rewards are recorded **per-session** and paid **per-era**. The value of the reward for each session is calculated at
|
||||
//! the end of the session based on the timeliness of the session, then accumulated to be paid later. The value of
|
||||
//! the new _per-session-reward_ is calculated at the end of each era by multiplying `SlotStake` and `SessionReward`
|
||||
//! (`SessionReward` is the multiplication factor, represented by a number between 0 and 1).
|
||||
//! Once a new era is triggered, rewards are paid to the validators and their associated nominators.
|
||||
//! Rewards are recorded **per-session** and paid **per-era**. The value of the reward for each
|
||||
//! session is calculated at the end of the session based on the timeliness of the session, then
|
||||
//! accumulated to be paid later. The value of the new _per-session-reward_ is calculated at the end
|
||||
//! of each era by multiplying `SlotStake` and `SessionReward` (`SessionReward` is the
|
||||
//! multiplication factor, represented by a number between 0 and 1). Once a new era is triggered,
|
||||
//! rewards are paid to the validators and their associated nominators.
|
||||
//!
|
||||
//! The validator can declare an amount, named
|
||||
//! [`validator_payment`](./struct.ValidatorPrefs.html#structfield.validator_payment), that does not get shared
|
||||
//! with the nominators at each reward payout through its [`ValidatorPrefs`](./struct.ValidatorPrefs.html). This value
|
||||
//! gets deducted from the total reward that can be paid. The remaining portion is split among the validator and all
|
||||
//! of the nominators that nominated the validator, proportional to the value staked behind this validator (_i.e._
|
||||
//! dividing the [`own`](./struct.Exposure.html#structfield.own) or [`others`](./struct.Exposure.html#structfield.others)
|
||||
//! by [`total`](./struct.Exposure.html#structfield.total) in [`Exposure`](./struct.Exposure.html)).
|
||||
//! [`validator_payment`](./struct.ValidatorPrefs.html#structfield.validator_payment), that does not
|
||||
//! get shared with the nominators at each reward payout through its
|
||||
//! [`ValidatorPrefs`](./struct.ValidatorPrefs.html). This value gets deducted from the total reward
|
||||
//! that can be paid. The remaining portion is split among the validator and all of the nominators
|
||||
//! that nominated the validator, proportional to the value staked behind this validator (_i.e._
|
||||
//! dividing the [`own`](./struct.Exposure.html#structfield.own) or
|
||||
//! [`others`](./struct.Exposure.html#structfield.others) by
|
||||
//! [`total`](./struct.Exposure.html#structfield.total) in [`Exposure`](./struct.Exposure.html)).
|
||||
//!
|
||||
//! All entities who receive a reward have the option to choose their reward destination
|
||||
//! through the [`Payee`](./struct.Payee.html) storage item (see [`set_payee`](enum.Call.html#variant.set_payee)),
|
||||
//! to be one of the following:
|
||||
//! through the [`Payee`](./struct.Payee.html) storage item (see
|
||||
//! [`set_payee`](enum.Call.html#variant.set_payee)), to be one of the following:
|
||||
//!
|
||||
//! - Controller account, (obviously) not increasing the staked value.
|
||||
//! - Stash account, not increasing the staked value.
|
||||
@@ -185,8 +195,8 @@
|
||||
//! ### Slashing details
|
||||
//!
|
||||
//! A validator can be _reported_ to be offline at any point via the public function
|
||||
//! [`on_offline_validator`](enum.Call.html#variant.on_offline_validator). Each validator declares how many times it
|
||||
//! can be _reported_ before it actually gets slashed via its
|
||||
//! [`on_offline_validator`](enum.Call.html#variant.on_offline_validator). Each validator declares
|
||||
//! how many times it can be _reported_ before it actually gets slashed via its
|
||||
//! [`ValidatorPrefs::unstake_threshold`](./struct.ValidatorPrefs.html#structfield.unstake_threshold).
|
||||
//!
|
||||
//! On top of this, the Staking module also introduces an
|
||||
@@ -199,35 +209,38 @@
|
||||
//! the consequence.
|
||||
//!
|
||||
//! The base slash value is computed _per slash-event_ by multiplying
|
||||
//! [`OfflineSlash`](./struct.Module.html#method.offline_slash) and the `total` `Exposure`. This value is then
|
||||
//! multiplied by `2.pow(unstake_threshold)` to obtain the final slash value. All individual accounts' punishments are
|
||||
//! capped at their total stake (NOTE: This cap should never come into force in a correctly implemented,
|
||||
//! non-corrupted, well-configured system).
|
||||
//! [`OfflineSlash`](./struct.Module.html#method.offline_slash) and the `total` `Exposure`. This
|
||||
//! value is then multiplied by `2.pow(unstake_threshold)` to obtain the final slash value. All
|
||||
//! individual accounts' punishments are capped at their total stake (NOTE: This cap should never
|
||||
//! come into force in a correctly implemented, non-corrupted, well-configured system).
|
||||
//!
|
||||
//! ### Additional Fund Management Operations
|
||||
//!
|
||||
//! Any funds already placed into stash can be the target of the following operations:
|
||||
//!
|
||||
//! The controller account can free a portion (or all) of the funds using the [`unbond`](enum.Call.html#variant.unbond)
|
||||
//! call. Note that the funds are not immediately accessible. Instead, a duration denoted by
|
||||
//! [`BondingDuration`](./struct.BondingDuration.html) (in number of eras) must pass until the funds can actually be
|
||||
//! removed. Once the `BondingDuration` is over, the [`withdraw_unbonded`](./enum.Call.html#variant.withdraw_unbonded) call can be used
|
||||
//! to actually withdraw the funds.
|
||||
//! The controller account can free a portion (or all) of the funds using the
|
||||
//! [`unbond`](enum.Call.html#variant.unbond) call. Note that the funds are not immediately
|
||||
//! accessible. Instead, a duration denoted by [`BondingDuration`](./struct.BondingDuration.html)
|
||||
//! (in number of eras) must pass until the funds can actually be removed. Once the
|
||||
//! `BondingDuration` is over, the [`withdraw_unbonded`](./enum.Call.html#variant.withdraw_unbonded)
|
||||
//! call can be used to actually withdraw the funds.
|
||||
//!
|
||||
//! Note that there is a limitation to the number of fund-chunks that can be scheduled to be unlocked in the future
|
||||
//! via [`unbond`](enum.Call.html#variant.unbond).
|
||||
//! In case this maximum (`MAX_UNLOCKING_CHUNKS`) is reached, the bonded account _must_ first wait until a successful
|
||||
//! Note that there is a limitation to the number of fund-chunks that can be scheduled to be
|
||||
//! unlocked in the future via [`unbond`](enum.Call.html#variant.unbond). In case this maximum
|
||||
//! (`MAX_UNLOCKING_CHUNKS`) is reached, the bonded account _must_ first wait until a successful
|
||||
//! call to `withdraw_unbonded` to remove some of the chunks.
|
||||
//!
|
||||
//! ### Election Algorithm
|
||||
//!
|
||||
//! The current election algorithm is implemented based on Phragmén.
|
||||
//! The reference implementation can be found [here](https://github.com/w3f/consensus/tree/master/NPoS).
|
||||
//! The reference implementation can be found
|
||||
//! [here](https://github.com/w3f/consensus/tree/master/NPoS).
|
||||
//!
|
||||
//! The election algorithm, aside from electing the validators with the most stake value and votes, tries to divide
|
||||
//! the nominator votes among candidates in an equal manner. To further assure this, an optional post-processing
|
||||
//! can be applied that iteratively normalizes the nominator staked values until the total difference among
|
||||
//! votes of a particular nominator are less than a threshold.
|
||||
//! The election algorithm, aside from electing the validators with the most stake value and votes,
|
||||
//! tries to divide the nominator votes among candidates in an equal manner. To further assure this,
|
||||
//! an optional post-processing can be applied that iteratively normalizes the nominator staked
|
||||
//! values until the total difference among votes of a particular nominator are less than a
|
||||
//! threshold.
|
||||
//!
|
||||
//! ## GenesisConfig
|
||||
//!
|
||||
@@ -236,8 +249,8 @@
|
||||
//! ## Related Modules
|
||||
//!
|
||||
//! - [Balances](../srml_balances/index.html): Used to manage values at stake.
|
||||
//! - [Session](../srml_session/index.html): Used to manage sessions. Also, a list of new validators is
|
||||
//! stored in the Session module's `Validators` at the end of each era.
|
||||
//! - [Session](../srml_session/index.html): Used to manage sessions. Also, a list of new validators
|
||||
//! is stored in the Session module's `Validators` at the end of each era.
|
||||
|
||||
#![recursion_limit="128"]
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
@@ -261,17 +274,17 @@ mod benches;
|
||||
use runtime_io::with_storage;
|
||||
use rstd::{prelude::*, result, collections::btree_map::BTreeMap};
|
||||
use parity_codec::{HasCompact, Encode, Decode};
|
||||
use srml_support::{ StorageValue, StorageMap, EnumerableStorageMap, dispatch::Result,
|
||||
decl_module, decl_event, decl_storage, ensure,
|
||||
traits::{Currency, OnFreeBalanceZero, OnDilution, LockIdentifier, LockableCurrency,
|
||||
WithdrawReasons, OnUnbalanced, Imbalance
|
||||
use srml_support::{
|
||||
StorageValue, StorageMap, EnumerableStorageMap, decl_module, decl_event,
|
||||
decl_storage, ensure, traits::{
|
||||
Currency, OnFreeBalanceZero, OnDilution, LockIdentifier, LockableCurrency,
|
||||
WithdrawReasons, OnUnbalanced, Imbalance, Get
|
||||
}
|
||||
};
|
||||
use session::OnSessionChange;
|
||||
use session::{OnSessionEnding, SessionIndex};
|
||||
use primitives::Perbill;
|
||||
use primitives::traits::{
|
||||
Convert, Zero, One, StaticLookup, CheckedSub, CheckedShl, Saturating,
|
||||
Bounded, SaturatedConversion
|
||||
Convert, Zero, One, StaticLookup, CheckedSub, CheckedShl, Saturating, Bounded
|
||||
};
|
||||
#[cfg(feature = "std")]
|
||||
use primitives::{Serialize, Deserialize};
|
||||
@@ -286,6 +299,9 @@ const MAX_UNSTAKE_THRESHOLD: u32 = 10;
|
||||
const MAX_UNLOCKING_CHUNKS: usize = 32;
|
||||
const STAKING_ID: LockIdentifier = *b"staking ";
|
||||
|
||||
/// Counter for the number of eras that have passed.
|
||||
pub type EraIndex = u32;
|
||||
|
||||
/// Indicates the initial status of the staker.
|
||||
#[cfg_attr(feature = "std", derive(Debug, Serialize, Deserialize))]
|
||||
pub enum StakerStatus<AccountId> {
|
||||
@@ -322,7 +338,8 @@ pub struct ValidatorPrefs<Balance: HasCompact> {
|
||||
/// Validator should ensure this many more slashes than is necessary before being unstaked.
|
||||
#[codec(compact)]
|
||||
pub unstake_threshold: u32,
|
||||
/// Reward that validator takes up-front; only the rest is split between themselves and nominators.
|
||||
/// Reward that validator takes up-front; only the rest is split between themselves and
|
||||
/// nominators.
|
||||
#[codec(compact)]
|
||||
pub validator_payment: Balance,
|
||||
}
|
||||
@@ -339,19 +356,19 @@ impl<B: Default + HasCompact + Copy> Default for ValidatorPrefs<B> {
|
||||
/// Just a Balance/BlockNumber tuple to encode when a chunk of funds will be unlocked.
|
||||
#[derive(PartialEq, Eq, Clone, Encode, Decode)]
|
||||
#[cfg_attr(feature = "std", derive(Debug))]
|
||||
pub struct UnlockChunk<Balance: HasCompact, BlockNumber: HasCompact> {
|
||||
pub struct UnlockChunk<Balance: HasCompact> {
|
||||
/// Amount of funds to be unlocked.
|
||||
#[codec(compact)]
|
||||
value: Balance,
|
||||
/// Era number at which point it'll be unlocked.
|
||||
#[codec(compact)]
|
||||
era: BlockNumber,
|
||||
era: EraIndex,
|
||||
}
|
||||
|
||||
/// The ledger of a (bonded) stash.
|
||||
#[derive(PartialEq, Eq, Clone, Encode, Decode)]
|
||||
#[cfg_attr(feature = "std", derive(Debug))]
|
||||
pub struct StakingLedger<AccountId, Balance: HasCompact, BlockNumber: HasCompact> {
|
||||
pub struct StakingLedger<AccountId, Balance: HasCompact> {
|
||||
/// The stash account whose balance is actually locked and at stake.
|
||||
pub stash: AccountId,
|
||||
/// The total amount of the stash's balance that we are currently accounting for.
|
||||
@@ -364,17 +381,16 @@ pub struct StakingLedger<AccountId, Balance: HasCompact, BlockNumber: HasCompact
|
||||
pub active: Balance,
|
||||
/// Any balance that is becoming free, which may eventually be transferred out
|
||||
/// of the stash (assuming it doesn't get slashed first).
|
||||
pub unlocking: Vec<UnlockChunk<Balance, BlockNumber>>,
|
||||
pub unlocking: Vec<UnlockChunk<Balance>>,
|
||||
}
|
||||
|
||||
impl<
|
||||
AccountId,
|
||||
Balance: HasCompact + Copy + Saturating,
|
||||
BlockNumber: HasCompact + PartialOrd
|
||||
> StakingLedger<AccountId, Balance, BlockNumber> {
|
||||
> StakingLedger<AccountId, Balance> {
|
||||
/// Remove entries from `unlocking` that are sufficiently old and reduce the
|
||||
/// total by the sum of their balances.
|
||||
fn consolidate_unlocked(self, current_era: BlockNumber) -> Self {
|
||||
fn consolidate_unlocked(self, current_era: EraIndex) -> Self {
|
||||
let mut total = self.total;
|
||||
let unlocking = self.unlocking.into_iter()
|
||||
.filter(|chunk| if chunk.era > current_era {
|
||||
@@ -414,12 +430,17 @@ pub struct Exposure<AccountId, Balance: HasCompact> {
|
||||
}
|
||||
|
||||
type BalanceOf<T> = <<T as Trait>::Currency as Currency<<T as system::Trait>::AccountId>>::Balance;
|
||||
type PositiveImbalanceOf<T> = <<T as Trait>::Currency as Currency<<T as system::Trait>::AccountId>>::PositiveImbalance;
|
||||
type NegativeImbalanceOf<T> = <<T as Trait>::Currency as Currency<<T as system::Trait>::AccountId>>::NegativeImbalance;
|
||||
type PositiveImbalanceOf<T> =
|
||||
<<T as Trait>::Currency as Currency<<T as system::Trait>::AccountId>>::PositiveImbalance;
|
||||
type NegativeImbalanceOf<T> =
|
||||
<<T as Trait>::Currency as Currency<<T as system::Trait>::AccountId>>::NegativeImbalance;
|
||||
|
||||
type RawAssignment<T> = (<T as system::Trait>::AccountId, ExtendedBalance);
|
||||
type Assignment<T> = (<T as system::Trait>::AccountId, ExtendedBalance, BalanceOf<T>);
|
||||
type ExpoMap<T> = BTreeMap<<T as system::Trait>::AccountId, Exposure<<T as system::Trait>::AccountId, BalanceOf<T>>>;
|
||||
type ExpoMap<T> = BTreeMap<
|
||||
<T as system::Trait>::AccountId,
|
||||
Exposure<<T as system::Trait>::AccountId, BalanceOf<T>>
|
||||
>;
|
||||
|
||||
pub trait Trait: system::Trait + session::Trait {
|
||||
/// The staking balance.
|
||||
@@ -443,6 +464,12 @@ pub trait Trait: system::Trait + session::Trait {
|
||||
|
||||
/// Handler for the unbalanced increment when rewarding a staker.
|
||||
type Reward: OnUnbalanced<PositiveImbalanceOf<Self>>;
|
||||
|
||||
/// Number of sessions per era.
|
||||
type SessionsPerEra: Get<SessionIndex>;
|
||||
|
||||
/// Number of eras that staked funds must remain bonded for.
|
||||
type BondingDuration: Get<EraIndex>;
|
||||
}
|
||||
|
||||
decl_storage! {
|
||||
@@ -451,26 +478,25 @@ decl_storage! {
|
||||
/// The ideal number of staking participants.
|
||||
pub ValidatorCount get(validator_count) config(): u32;
|
||||
/// Minimum number of staking participants before emergency conditions are imposed.
|
||||
pub MinimumValidatorCount get(minimum_validator_count) config(): u32 = DEFAULT_MINIMUM_VALIDATOR_COUNT;
|
||||
/// The length of a staking era in sessions.
|
||||
pub SessionsPerEra get(sessions_per_era) config(): T::BlockNumber = 1000.into();
|
||||
pub MinimumValidatorCount get(minimum_validator_count) config():
|
||||
u32 = DEFAULT_MINIMUM_VALIDATOR_COUNT;
|
||||
/// Maximum reward, per validator, that is provided per acceptable session.
|
||||
pub SessionReward get(session_reward) config(): Perbill = Perbill::from_parts(60);
|
||||
/// Slash, per validator that is taken for the first time they are found to be offline.
|
||||
pub OfflineSlash get(offline_slash) config(): Perbill = Perbill::from_millionths(1000);
|
||||
/// Number of instances of offline reports before slashing begins for validators.
|
||||
pub OfflineSlashGrace get(offline_slash_grace) config(): u32;
|
||||
/// The length of the bonding duration in eras.
|
||||
pub BondingDuration get(bonding_duration) config(): T::BlockNumber = 12.into();
|
||||
|
||||
/// Any validators that may never be slashed or forcibly kicked. It's a Vec since they're easy to initialize
|
||||
/// and the performance hit is minimal (we expect no more than four invulnerables) and restricted to testnets.
|
||||
/// Any validators that may never be slashed or forcibly kicked. It's a Vec since they're
|
||||
/// easy to initialize and the performance hit is minimal (we expect no more than four
|
||||
/// invulnerables) and restricted to testnets.
|
||||
pub Invulnerables get(invulnerables) config(): Vec<T::AccountId>;
|
||||
|
||||
/// Map from all locked "stash" accounts to the controller account.
|
||||
pub Bonded get(bonded): map T::AccountId => Option<T::AccountId>;
|
||||
/// Map from all (unlocked) "controller" accounts to the info regarding the staking.
|
||||
pub Ledger get(ledger): map T::AccountId => Option<StakingLedger<T::AccountId, BalanceOf<T>, T::BlockNumber>>;
|
||||
pub Ledger get(ledger):
|
||||
map T::AccountId => Option<StakingLedger<T::AccountId, BalanceOf<T>>>;
|
||||
|
||||
/// Where the reward payment should be made. Keyed by stash.
|
||||
pub Payee get(payee): map T::AccountId => RewardDestination;
|
||||
@@ -481,38 +507,34 @@ decl_storage! {
|
||||
/// The map from nominator stash key to the set of stash keys of all validators to nominate.
|
||||
pub Nominators get(nominators): linked_map T::AccountId => Vec<T::AccountId>;
|
||||
|
||||
/// Nominators for a particular account that is in action right now. You can't iterate through validators here,
|
||||
/// but you can find them in the Session module.
|
||||
/// Nominators for a particular account that is in action right now. You can't iterate
|
||||
/// through validators here, but you can find them in the Session module.
|
||||
///
|
||||
/// This is keyed by the stash account.
|
||||
pub Stakers get(stakers): map T::AccountId => Exposure<T::AccountId, BalanceOf<T>>;
|
||||
|
||||
// The historical validators and their nominations for a given era. Stored as a trie root of the mapping
|
||||
// `T::AccountId` => `Exposure<T::AccountId, BalanceOf<T>>`, which is just the contents of `Stakers`,
|
||||
// under a key that is the `era`.
|
||||
// The historical validators and their nominations for a given era. Stored as a trie root
|
||||
// of the mapping `T::AccountId` => `Exposure<T::AccountId, BalanceOf<T>>`, which is just
|
||||
// the contents of `Stakers`, under a key that is the `era`.
|
||||
//
|
||||
// Every era change, this will be appended with the trie root of the contents of `Stakers`, and the oldest
|
||||
// entry removed down to a specific number of entries (probably around 90 for a 3 month history).
|
||||
// Every era change, this will be appended with the trie root of the contents of `Stakers`,
|
||||
// and the oldest entry removed down to a specific number of entries (probably around 90 for
|
||||
// a 3 month history).
|
||||
// pub HistoricalStakers get(historical_stakers): map T::BlockNumber => Option<H256>;
|
||||
|
||||
/// The currently elected validator set keyed by stash account ID.
|
||||
pub CurrentElected get(current_elected): Vec<T::AccountId>;
|
||||
|
||||
/// The current era index.
|
||||
pub CurrentEra get(current_era) config(): T::BlockNumber;
|
||||
pub CurrentEra get(current_era) config(): EraIndex;
|
||||
|
||||
/// Maximum reward, per validator, that is provided per acceptable session.
|
||||
pub CurrentSessionReward get(current_session_reward) config(): BalanceOf<T>;
|
||||
|
||||
/// The accumulated reward for the current era. Reset to zero at the beginning of the era and
|
||||
/// increased for every successfully finished session.
|
||||
/// The accumulated reward for the current era. Reset to zero at the beginning of the era
|
||||
/// and increased for every successfully finished session.
|
||||
pub CurrentEraReward get(current_era_reward): BalanceOf<T>;
|
||||
|
||||
/// The next value of sessions per era.
|
||||
pub NextSessionsPerEra get(next_sessions_per_era): Option<T::BlockNumber>;
|
||||
/// The session index at which the era length last changed.
|
||||
pub LastEraLengthChange get(last_era_length_change): T::BlockNumber;
|
||||
|
||||
/// The amount of balance actively at stake for each validator slot, currently.
|
||||
///
|
||||
/// This is used to derive rewards and punishments.
|
||||
@@ -520,18 +542,25 @@ decl_storage! {
|
||||
config.stakers.iter().map(|&(_, _, value, _)| value).min().unwrap_or_default()
|
||||
}): BalanceOf<T>;
|
||||
|
||||
/// The number of times a given validator has been reported offline. This gets decremented by one each era that passes.
|
||||
/// The number of times a given validator has been reported offline. This gets decremented
|
||||
/// by one each era that passes.
|
||||
pub SlashCount get(slash_count): map T::AccountId => u32;
|
||||
|
||||
/// We are forcing a new era.
|
||||
pub ForcingNewEra get(forcing_new_era): Option<()>;
|
||||
|
||||
/// Most recent `RECENT_OFFLINE_COUNT` instances. (Who it was, when it was reported, how many instances they were offline for).
|
||||
/// Most recent `RECENT_OFFLINE_COUNT` instances. (Who it was, when it was reported, how
|
||||
/// many instances they were offline for).
|
||||
pub RecentlyOffline get(recently_offline): Vec<(T::AccountId, T::BlockNumber, u32)>;
|
||||
|
||||
/// True if the next session change will be a new era regardless of index.
|
||||
pub ForceNewEra get(forcing_new_era): bool;
|
||||
}
|
||||
add_extra_genesis {
|
||||
config(stakers): Vec<(T::AccountId, T::AccountId, BalanceOf<T>, StakerStatus<T::AccountId>)>;
|
||||
build(|storage: &mut primitives::StorageOverlay, _: &mut primitives::ChildrenStorageOverlay, config: &GenesisConfig<T>| {
|
||||
config(stakers):
|
||||
Vec<(T::AccountId, T::AccountId, BalanceOf<T>, StakerStatus<T::AccountId>)>;
|
||||
build(|
|
||||
storage: &mut primitives::StorageOverlay,
|
||||
_: &mut primitives::ChildrenStorageOverlay,
|
||||
config: &GenesisConfig<T>
|
||||
| {
|
||||
with_storage(storage, || {
|
||||
for &(ref stash, ref controller, balance, ref status) in &config.stakers {
|
||||
assert!(T::Currency::free_balance(&stash) >= balance);
|
||||
@@ -556,18 +585,32 @@ decl_storage! {
|
||||
};
|
||||
}
|
||||
|
||||
<Module<T>>::select_validators();
|
||||
if let (_, Some(validators)) = <Module<T>>::select_validators() {
|
||||
<session::Validators<T>>::put(&validators);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
decl_event!(
|
||||
pub enum Event<T> where Balance = BalanceOf<T>, <T as system::Trait>::AccountId {
|
||||
/// All validators have been rewarded by the given balance.
|
||||
Reward(Balance),
|
||||
/// One validator (and its nominators) has been given an offline-warning (it is still
|
||||
/// within its grace). The accrued number of slashes is recorded, too.
|
||||
OfflineWarning(AccountId, u32),
|
||||
/// One validator (and its nominators) has been slashed by the given amount.
|
||||
OfflineSlash(AccountId, Balance),
|
||||
}
|
||||
);
|
||||
|
||||
decl_module! {
|
||||
pub struct Module<T: Trait> for enum Call where origin: T::Origin {
|
||||
fn deposit_event<T>() = default;
|
||||
|
||||
/// Take the origin account as a stash and lock up `value` of its balance. `controller` will be the
|
||||
/// account that controls it.
|
||||
/// Take the origin account as a stash and lock up `value` of its balance. `controller` will
|
||||
/// be the account that controls it.
|
||||
///
|
||||
/// The dispatch origin for this call must be _Signed_ by the stash account.
|
||||
///
|
||||
@@ -583,7 +626,11 @@ decl_module! {
|
||||
/// (which creates a bunch of storage items for an account). In essence, nothing prevents many accounts from
|
||||
/// spamming `Staking` storage by bonding 1 UNIT. See test case: `bond_with_no_staked_value`.
|
||||
/// # </weight>
|
||||
fn bond(origin, controller: <T::Lookup as StaticLookup>::Source, #[compact] value: BalanceOf<T>, payee: RewardDestination) {
|
||||
fn bond(origin,
|
||||
controller: <T::Lookup as StaticLookup>::Source,
|
||||
#[compact] value: BalanceOf<T>,
|
||||
payee: RewardDestination
|
||||
) {
|
||||
let stash = ensure_signed(origin)?;
|
||||
|
||||
if <Bonded<T>>::exists(&stash) {
|
||||
@@ -603,11 +650,12 @@ decl_module! {
|
||||
|
||||
let stash_balance = T::Currency::free_balance(&stash);
|
||||
let value = value.min(stash_balance);
|
||||
Self::update_ledger(&controller, &StakingLedger { stash, total: value, active: value, unlocking: vec![] });
|
||||
let item = StakingLedger { stash, total: value, active: value, unlocking: vec![] };
|
||||
Self::update_ledger(&controller, &item);
|
||||
}
|
||||
|
||||
/// Add some extra amount that have appeared in the stash `free_balance` into the balance up for
|
||||
/// staking.
|
||||
/// Add some extra amount that have appeared in the stash `free_balance` into the balance up
|
||||
/// for staking.
|
||||
///
|
||||
/// Use this if there are additional funds in your stash account that you wish to bond.
|
||||
///
|
||||
@@ -676,7 +724,7 @@ decl_module! {
|
||||
ledger.active = Zero::zero();
|
||||
}
|
||||
|
||||
let era = Self::current_era() + Self::bonding_duration();
|
||||
let era = Self::current_era() + T::BondingDuration::get();
|
||||
ledger.unlocking.push(UnlockChunk { value, era });
|
||||
Self::update_ledger(&controller, &ledger);
|
||||
}
|
||||
@@ -720,7 +768,10 @@ decl_module! {
|
||||
let controller = ensure_signed(origin)?;
|
||||
let ledger = Self::ledger(&controller).ok_or("not a controller")?;
|
||||
let stash = &ledger.stash;
|
||||
ensure!(prefs.unstake_threshold <= MAX_UNSTAKE_THRESHOLD, "unstake threshold too large");
|
||||
ensure!(
|
||||
prefs.unstake_threshold <= MAX_UNSTAKE_THRESHOLD,
|
||||
"unstake threshold too large"
|
||||
);
|
||||
<Nominators<T>>::remove(stash);
|
||||
<Validators<T>>::insert(stash, prefs);
|
||||
}
|
||||
@@ -807,27 +858,19 @@ decl_module! {
|
||||
}
|
||||
if controller != old_controller {
|
||||
<Bonded<T>>::insert(&stash, &controller);
|
||||
if let Some(l) = <Ledger<T>>::take(&old_controller) { <Ledger<T>>::insert(&controller, l) };
|
||||
if let Some(l) = <Ledger<T>>::take(&old_controller) {
|
||||
<Ledger<T>>::insert(&controller, l);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ----- Root calls.
|
||||
|
||||
/// Set the number of sessions in an era.
|
||||
fn set_sessions_per_era(#[compact] new: T::BlockNumber) {
|
||||
<NextSessionsPerEra<T>>::put(new);
|
||||
}
|
||||
|
||||
/// The length of the bonding duration in eras.
|
||||
fn set_bonding_duration(#[compact] new: T::BlockNumber) {
|
||||
<BondingDuration<T>>::put(new);
|
||||
}
|
||||
|
||||
/// The ideal number of validators.
|
||||
fn set_validator_count(#[compact] new: u32) {
|
||||
<ValidatorCount<T>>::put(new);
|
||||
}
|
||||
|
||||
// ----- Root calls.
|
||||
|
||||
/// Force there to be a new era. This also forces a new session immediately after.
|
||||
/// `apply_rewards` should be true for validators to get the session reward.
|
||||
///
|
||||
@@ -836,8 +879,8 @@ decl_module! {
|
||||
/// - Triggers the Phragmen election. Expensive but not user-controlled.
|
||||
/// - Depends on state: `O(|edges| * |validators|)`.
|
||||
/// # </weight>
|
||||
fn force_new_era(apply_rewards: bool) -> Result {
|
||||
Self::apply_force_new_era(apply_rewards)
|
||||
fn force_new_era() {
|
||||
Self::apply_force_new_era()
|
||||
}
|
||||
|
||||
/// Set the offline slash grace period.
|
||||
@@ -852,32 +895,9 @@ decl_module! {
|
||||
}
|
||||
}
|
||||
|
||||
decl_event!(
|
||||
pub enum Event<T> where Balance = BalanceOf<T>, <T as system::Trait>::AccountId {
|
||||
/// All validators have been rewarded by the given balance.
|
||||
Reward(Balance),
|
||||
/// One validator (and its nominators) has been given an offline-warning (it is still
|
||||
/// within its grace). The accrued number of slashes is recorded, too.
|
||||
OfflineWarning(AccountId, u32),
|
||||
/// One validator (and its nominators) has been slashed by the given amount.
|
||||
OfflineSlash(AccountId, Balance),
|
||||
}
|
||||
);
|
||||
|
||||
impl<T: Trait> Module<T> {
|
||||
/// Just force_new_era without origin check.
|
||||
fn apply_force_new_era(apply_rewards: bool) -> Result {
|
||||
<ForcingNewEra<T>>::put(());
|
||||
<session::Module<T>>::apply_force_new_session(apply_rewards)
|
||||
}
|
||||
|
||||
// PUBLIC IMMUTABLES
|
||||
|
||||
/// The length of a staking era in blocks.
|
||||
pub fn era_length() -> T::BlockNumber {
|
||||
Self::sessions_per_era() * <session::Module<T>>::length()
|
||||
}
|
||||
|
||||
/// The total balance that can be slashed from a validator controller account as of
|
||||
/// right now.
|
||||
pub fn slashable_balance(who: &T::AccountId) -> BalanceOf<T> {
|
||||
@@ -887,29 +907,41 @@ impl<T: Trait> Module<T> {
|
||||
// MUTABLES (DANGEROUS)
|
||||
|
||||
/// Update the ledger for a controller. This will also update the stash lock.
|
||||
fn update_ledger(controller: &T::AccountId, ledger: &StakingLedger<T::AccountId, BalanceOf<T>, T::BlockNumber>) {
|
||||
T::Currency::set_lock(STAKING_ID, &ledger.stash, ledger.total, T::BlockNumber::max_value(), WithdrawReasons::all());
|
||||
fn update_ledger(
|
||||
controller: &T::AccountId,
|
||||
ledger: &StakingLedger<T::AccountId, BalanceOf<T>>
|
||||
) {
|
||||
T::Currency::set_lock(
|
||||
STAKING_ID,
|
||||
&ledger.stash,
|
||||
ledger.total,
|
||||
T::BlockNumber::max_value(),
|
||||
WithdrawReasons::all()
|
||||
);
|
||||
<Ledger<T>>::insert(controller, ledger);
|
||||
}
|
||||
|
||||
/// Slash a given validator by a specific amount. Removes the slash from the validator's balance by preference,
|
||||
/// and reduces the nominators' balance if needed.
|
||||
/// Slash a given validator by a specific amount. Removes the slash from the validator's
|
||||
/// balance by preference, and reduces the nominators' balance if needed.
|
||||
fn slash_validator(stash: &T::AccountId, slash: BalanceOf<T>) {
|
||||
// The exposure (backing stake) information of the validator to be slashed.
|
||||
let exposure = Self::stakers(stash);
|
||||
// The amount we are actually going to slash (can't be bigger than the validator's total exposure)
|
||||
// The amount we are actually going to slash (can't be bigger than the validator's total
|
||||
// exposure)
|
||||
let slash = slash.min(exposure.total);
|
||||
// The amount we'll slash from the validator's stash directly.
|
||||
let own_slash = exposure.own.min(slash);
|
||||
let (mut imbalance, missing) = T::Currency::slash(stash, own_slash);
|
||||
let own_slash = own_slash - missing;
|
||||
// The amount remaining that we can't slash from the validator, that must be taken from the nominators.
|
||||
// The amount remaining that we can't slash from the validator, that must be taken from the
|
||||
// nominators.
|
||||
let rest_slash = slash - own_slash;
|
||||
if !rest_slash.is_zero() {
|
||||
// The total to be slashed from the nominators.
|
||||
let total = exposure.total - exposure.own;
|
||||
if !total.is_zero() {
|
||||
let safe_mul_rational = |b| b * rest_slash / total;// FIXME #1572 avoid overflow
|
||||
// FIXME #1572 avoid overflow
|
||||
let safe_mul_rational = |b| b * rest_slash / total;
|
||||
for i in exposure.others.iter() {
|
||||
// best effort - not much that can be done on fail.
|
||||
imbalance.subsume(T::Currency::slash(&i.who, safe_mul_rational(i.value)).0)
|
||||
@@ -942,8 +974,9 @@ impl<T: Trait> Module<T> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Reward a given validator by a specific amount. Add the reward to the validator's, and its nominators'
|
||||
/// balance, pro-rata based on their exposure, after having removed the validator's pre-payout cut.
|
||||
/// Reward a given validator by a specific amount. Add the reward to the validator's, and its
|
||||
/// nominators' balance, pro-rata based on their exposure, after having removed the validator's
|
||||
/// pre-payout cut.
|
||||
fn reward_validator(stash: &T::AccountId, reward: BalanceOf<T>) {
|
||||
let off_the_table = reward.min(Self::validators(stash).validator_payment);
|
||||
let reward = reward - off_the_table;
|
||||
@@ -953,7 +986,8 @@ impl<T: Trait> Module<T> {
|
||||
} else {
|
||||
let exposure = Self::stakers(stash);
|
||||
let total = exposure.total.max(One::one());
|
||||
let safe_mul_rational = |b| b * reward / total;// FIXME #1572: avoid overflow
|
||||
// FIXME #1572: avoid overflow
|
||||
let safe_mul_rational = |b| b * reward / total;
|
||||
for i in &exposure.others {
|
||||
let nom_payout = safe_mul_rational(i.value);
|
||||
imbalance.maybe_subsume(Self::make_payout(&i.who, nom_payout));
|
||||
@@ -964,34 +998,16 @@ impl<T: Trait> Module<T> {
|
||||
T::Reward::on_unbalanced(imbalance);
|
||||
}
|
||||
|
||||
/// Get the reward for the session, assuming it ends with this block.
|
||||
fn this_session_reward(actual_elapsed: T::Moment) -> BalanceOf<T> {
|
||||
let ideal_elapsed = <session::Module<T>>::ideal_session_duration();
|
||||
if ideal_elapsed.is_zero() {
|
||||
return Self::current_session_reward();
|
||||
}
|
||||
// Assumes we have 16-bits free at the top of T::Moment. Holds true for moment as seconds
|
||||
// in a u64 for the forseeable future, but more correct would be to handle overflows
|
||||
// explicitly.
|
||||
let per65536 = T::Moment::from(65536) * ideal_elapsed.clone() / actual_elapsed.max(ideal_elapsed);
|
||||
let per65536: BalanceOf<T> = per65536.saturated_into::<u32>().into();
|
||||
Self::current_session_reward() * per65536 / 65536.into()
|
||||
}
|
||||
/// Session has just ended. Provide the validator set for the next session if it's an era-end.
|
||||
fn new_session(session_index: SessionIndex) -> Option<Vec<T::AccountId>> {
|
||||
// accumulate good session reward
|
||||
let reward = Self::current_session_reward();
|
||||
<CurrentEraReward<T>>::mutate(|r| *r += reward);
|
||||
|
||||
/// Session has just changed. We need to determine whether we pay a reward, slash and/or
|
||||
/// move to a new era.
|
||||
fn new_session(actual_elapsed: T::Moment, should_reward: bool) {
|
||||
if should_reward {
|
||||
// accumulate good session reward
|
||||
let reward = Self::this_session_reward(actual_elapsed);
|
||||
<CurrentEraReward<T>>::mutate(|r| *r += reward);
|
||||
}
|
||||
|
||||
let session_index = <session::Module<T>>::current_index();
|
||||
if <ForcingNewEra<T>>::take().is_some()
|
||||
|| ((session_index - Self::last_era_length_change()) % Self::sessions_per_era()).is_zero()
|
||||
{
|
||||
Self::new_era();
|
||||
if <ForceNewEra<T>>::take() || session_index % T::SessionsPerEra::get() == 0 {
|
||||
Self::new_era()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
@@ -999,7 +1015,7 @@ impl<T: Trait> Module<T> {
|
||||
///
|
||||
/// NOTE: This always happens immediately before a session change to ensure that new validators
|
||||
/// get a chance to set their session keys.
|
||||
fn new_era() {
|
||||
fn new_era() -> Option<Vec<T::AccountId>> {
|
||||
// Payout
|
||||
let reward = <CurrentEraReward<T>>::take();
|
||||
if !reward.is_zero() {
|
||||
@@ -1016,21 +1032,15 @@ impl<T: Trait> Module<T> {
|
||||
}
|
||||
|
||||
// Increment current era.
|
||||
<CurrentEra<T>>::put(&(<CurrentEra<T>>::get() + One::one()));
|
||||
|
||||
// Enact era length change.
|
||||
if let Some(next_spe) = Self::next_sessions_per_era() {
|
||||
if next_spe != Self::sessions_per_era() {
|
||||
<SessionsPerEra<T>>::put(&next_spe);
|
||||
<LastEraLengthChange<T>>::put(&<session::Module<T>>::current_index());
|
||||
}
|
||||
}
|
||||
<CurrentEra<T>>::mutate(|s| *s += 1);
|
||||
|
||||
// Reassign all Stakers.
|
||||
let slot_stake = Self::select_validators();
|
||||
let (slot_stake, maybe_new_validators) = Self::select_validators();
|
||||
|
||||
// Update the balances for rewarding according to the stakes.
|
||||
<CurrentSessionReward<T>>::put(Self::session_reward() * slot_stake);
|
||||
|
||||
maybe_new_validators
|
||||
}
|
||||
|
||||
fn slashable_balance_of(stash: &T::AccountId) -> BalanceOf<T> {
|
||||
@@ -1040,7 +1050,7 @@ impl<T: Trait> Module<T> {
|
||||
/// Select a new validator set from the assembled stakers and their role preferences.
|
||||
///
|
||||
/// Returns the new `SlotStake` value.
|
||||
fn select_validators() -> BalanceOf<T> {
|
||||
fn select_validators() -> (BalanceOf<T>, Option<Vec<T::AccountId>>) {
|
||||
let maybe_elected_set = elect::<T, _, _, _>(
|
||||
Self::validator_count() as usize,
|
||||
Self::minimum_validator_count().max(1) as usize,
|
||||
@@ -1054,8 +1064,10 @@ impl<T: Trait> Module<T> {
|
||||
let assignments = elected_set.1;
|
||||
|
||||
// helper closure.
|
||||
let to_balance = |b: ExtendedBalance| <T::CurrencyToVote as Convert<ExtendedBalance, BalanceOf<T>>>::convert(b);
|
||||
let to_votes = |b: BalanceOf<T>| <T::CurrencyToVote as Convert<BalanceOf<T>, u64>>::convert(b) as ExtendedBalance;
|
||||
let to_balance = |b: ExtendedBalance|
|
||||
<T::CurrencyToVote as Convert<ExtendedBalance, BalanceOf<T>>>::convert(b);
|
||||
let to_votes = |b: BalanceOf<T>|
|
||||
<T::CurrencyToVote as Convert<BalanceOf<T>, u64>>::convert(b) as ExtendedBalance;
|
||||
|
||||
// The return value of this is safe to be converted to u64.
|
||||
// The original balance, `b` is within the scope of u64. It is just extended to u128
|
||||
@@ -1082,7 +1094,8 @@ impl<T: Trait> Module<T> {
|
||||
.iter()
|
||||
.map(|e| (e, Self::slashable_balance_of(e)))
|
||||
.for_each(|(e, s)| {
|
||||
exposures.insert(e.clone(), Exposure { own: s, total: s, ..Default::default() });
|
||||
let item = Exposure { own: s, total: s, ..Default::default() };
|
||||
exposures.insert(e.clone(), item);
|
||||
});
|
||||
|
||||
for (n, _, assignment) in &assignments_with_stakes {
|
||||
@@ -1099,11 +1112,16 @@ impl<T: Trait> Module<T> {
|
||||
}
|
||||
|
||||
// This optimization will most likely be only applied off-chain.
|
||||
let do_equalise = false;
|
||||
if do_equalise {
|
||||
let do_equalize = false;
|
||||
if do_equalize {
|
||||
let tolerance = 10 as u128;
|
||||
let iterations = 10 as usize;
|
||||
phragmen::equalize::<T>(&mut assignments_with_stakes, &mut exposures, tolerance, iterations);
|
||||
phragmen::equalize::<T>(
|
||||
&mut assignments_with_stakes,
|
||||
&mut exposures,
|
||||
tolerance,
|
||||
iterations
|
||||
);
|
||||
}
|
||||
|
||||
// Clear Stakers and reduce their slash_count.
|
||||
@@ -1127,11 +1145,10 @@ impl<T: Trait> Module<T> {
|
||||
|
||||
// Set the new validator set.
|
||||
<CurrentElected<T>>::put(&elected_stashes);
|
||||
<session::Module<T>>::set_validators(
|
||||
&elected_stashes.into_iter().map(|s| Self::bonded(s).unwrap_or_default()).collect::<Vec<_>>()
|
||||
);
|
||||
|
||||
slot_stake
|
||||
let validators = elected_stashes.into_iter()
|
||||
.map(|s| Self::bonded(s).unwrap_or_default())
|
||||
.collect::<Vec<_>>();
|
||||
(slot_stake, Some(validators))
|
||||
} else {
|
||||
// There were not enough candidates for even our minimal level of functionality.
|
||||
// This is bad.
|
||||
@@ -1139,10 +1156,14 @@ impl<T: Trait> Module<T> {
|
||||
// and let the chain keep producing blocks until we can decide on a sufficiently
|
||||
// substantial set.
|
||||
// TODO: #2494
|
||||
Self::slot_stake()
|
||||
(Self::slot_stake(), None)
|
||||
}
|
||||
}
|
||||
|
||||
fn apply_force_new_era() {
|
||||
<ForceNewEra<T>>::put(true);
|
||||
}
|
||||
|
||||
/// Call when a validator is determined to be offline. `count` is the
|
||||
/// number of offenses the validator has committed.
|
||||
///
|
||||
@@ -1190,7 +1211,7 @@ impl<T: Trait> Module<T> {
|
||||
.unwrap_or(slash_exposure);
|
||||
let _ = Self::slash_validator(&stash, slash);
|
||||
<Validators<T>>::remove(&stash);
|
||||
let _ = Self::apply_force_new_era(false);
|
||||
let _ = Self::apply_force_new_era();
|
||||
|
||||
RawEvent::OfflineSlash(stash.clone(), slash)
|
||||
} else {
|
||||
@@ -1202,9 +1223,9 @@ impl<T: Trait> Module<T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Trait> OnSessionChange<T::Moment> for Module<T> {
|
||||
fn on_session_change(elapsed: T::Moment, should_reward: bool) {
|
||||
Self::new_session(elapsed, should_reward);
|
||||
impl<T: Trait> OnSessionEnding<T::AccountId> for Module<T> {
|
||||
fn on_session_ending(i: SessionIndex) -> Option<Vec<T::AccountId>> {
|
||||
Self::new_session(i + 1)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1219,12 +1240,3 @@ impl<T: Trait> OnFreeBalanceZero<T::AccountId> for Module<T> {
|
||||
<Nominators<T>>::remove(stash);
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Trait> consensus::OnOfflineReport<Vec<u32>> for Module<T> {
|
||||
fn handle_report(reported_indices: Vec<u32>) {
|
||||
for validator_index in reported_indices {
|
||||
let v = <session::Module<T>>::validators()[validator_index as usize].clone();
|
||||
Self::on_offline_validator(v, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,15 +16,17 @@
|
||||
|
||||
//! Test utilities
|
||||
|
||||
use primitives::{traits::{IdentityLookup, Convert}, BuildStorage, Perbill};
|
||||
use primitives::testing::{Digest, DigestItem, Header, UintAuthorityId, ConvertUintAuthorityId};
|
||||
use primitives::{BuildStorage, Perbill};
|
||||
use primitives::traits::{IdentityLookup, Convert, OpaqueKeys, OnInitialize};
|
||||
use primitives::testing::{Header, UintAuthorityId};
|
||||
use substrate_primitives::{H256, Blake2Hasher};
|
||||
use runtime_io;
|
||||
use srml_support::{impl_outer_origin, assert_ok, traits::Currency};
|
||||
use crate::{GenesisConfig, Module, Trait, StakerStatus, ValidatorPrefs, RewardDestination};
|
||||
use srml_support::{impl_outer_origin, parameter_types, assert_ok, traits::Currency};
|
||||
use crate::{EraIndex, GenesisConfig, Module, Trait, StakerStatus, ValidatorPrefs, RewardDestination};
|
||||
|
||||
/// The AccountId alias in this test module.
|
||||
pub type AccountIdType = u64;
|
||||
pub type AccountId = u64;
|
||||
pub type BlockNumber = u64;
|
||||
|
||||
/// Simple structure that exposes how u64 currency can be represented as... u64.
|
||||
pub struct CurrencyToVoteHandler;
|
||||
@@ -37,6 +39,15 @@ impl Convert<u128, u64> for CurrencyToVoteHandler {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TestSessionHandler;
|
||||
impl session::SessionHandler<AccountId> for TestSessionHandler {
|
||||
fn on_new_session<Ks: OpaqueKeys>(_changed: bool, _validators: &[(AccountId, Ks)]) {
|
||||
}
|
||||
|
||||
fn on_disabled(_validator_index: usize) {
|
||||
}
|
||||
}
|
||||
|
||||
impl_outer_origin!{
|
||||
pub enum Origin for Test {}
|
||||
}
|
||||
@@ -44,23 +55,16 @@ impl_outer_origin!{
|
||||
// Workaround for https://github.com/rust-lang/rust/issues/26925 . Remove when sorted.
|
||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||
pub struct Test;
|
||||
impl consensus::Trait for Test {
|
||||
type Log = DigestItem;
|
||||
type SessionKey = UintAuthorityId;
|
||||
type InherentOfflineReport = ();
|
||||
}
|
||||
impl system::Trait for Test {
|
||||
type Origin = Origin;
|
||||
type Index = u64;
|
||||
type BlockNumber = u64;
|
||||
type Hash = H256;
|
||||
type Hashing = ::primitives::traits::BlakeTwo256;
|
||||
type Digest = Digest;
|
||||
type AccountId = AccountIdType;
|
||||
type AccountId = AccountId;
|
||||
type Lookup = IdentityLookup<Self::AccountId>;
|
||||
type Header = Header;
|
||||
type Event = ();
|
||||
type Log = DigestItem;
|
||||
}
|
||||
impl balances::Trait for Test {
|
||||
type Balance = u64;
|
||||
@@ -71,15 +75,25 @@ impl balances::Trait for Test {
|
||||
type TransferPayment = ();
|
||||
type DustRemoval = ();
|
||||
}
|
||||
parameter_types! {
|
||||
pub const Period: BlockNumber = 1;
|
||||
pub const Offset: BlockNumber = 0;
|
||||
}
|
||||
impl session::Trait for Test {
|
||||
type ConvertAccountIdToSessionKey = ConvertUintAuthorityId;
|
||||
type OnSessionChange = Staking;
|
||||
type OnSessionEnding = Staking;
|
||||
type Keys = UintAuthorityId;
|
||||
type ShouldEndSession = session::PeriodicSessions<Period, Offset>;
|
||||
type SessionHandler = TestSessionHandler;
|
||||
type Event = ();
|
||||
}
|
||||
impl timestamp::Trait for Test {
|
||||
type Moment = u64;
|
||||
type OnTimestampSet = ();
|
||||
}
|
||||
parameter_types! {
|
||||
pub const SessionsPerEra: session::SessionIndex = 3;
|
||||
pub const BondingDuration: EraIndex = 3;
|
||||
}
|
||||
impl Trait for Test {
|
||||
type Currency = balances::Module<Self>;
|
||||
type CurrencyToVote = CurrencyToVoteHandler;
|
||||
@@ -87,13 +101,13 @@ impl Trait for Test {
|
||||
type Event = ();
|
||||
type Slash = ();
|
||||
type Reward = ();
|
||||
type SessionsPerEra = SessionsPerEra;
|
||||
type BondingDuration = BondingDuration;
|
||||
}
|
||||
|
||||
pub struct ExtBuilder {
|
||||
existential_deposit: u64,
|
||||
session_length: u64,
|
||||
sessions_per_era: u64,
|
||||
current_era: u64,
|
||||
current_era: EraIndex,
|
||||
reward: u64,
|
||||
validator_pool: bool,
|
||||
nominate: bool,
|
||||
@@ -106,8 +120,6 @@ impl Default for ExtBuilder {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
existential_deposit: 0,
|
||||
session_length: 1,
|
||||
sessions_per_era: 1,
|
||||
current_era: 0,
|
||||
reward: 10,
|
||||
validator_pool: false,
|
||||
@@ -124,15 +136,7 @@ impl ExtBuilder {
|
||||
self.existential_deposit = existential_deposit;
|
||||
self
|
||||
}
|
||||
pub fn session_length(mut self, session_length: u64) -> Self {
|
||||
self.session_length = session_length;
|
||||
self
|
||||
}
|
||||
pub fn sessions_per_era(mut self, sessions_per_era: u64) -> Self {
|
||||
self.sessions_per_era = sessions_per_era;
|
||||
self
|
||||
}
|
||||
pub fn _current_era(mut self, current_era: u64) -> Self {
|
||||
pub fn _current_era(mut self, current_era: EraIndex) -> Self {
|
||||
self.current_era = current_era;
|
||||
self
|
||||
}
|
||||
@@ -163,14 +167,9 @@ impl ExtBuilder {
|
||||
} else {
|
||||
1
|
||||
};
|
||||
let _ = consensus::GenesisConfig::<Test>{
|
||||
code: vec![],
|
||||
authorities: vec![],
|
||||
}.assimilate_storage(&mut t, &mut c);
|
||||
let _ = session::GenesisConfig::<Test>{
|
||||
session_length: self.session_length,
|
||||
// NOTE: if config.nominate == false then 100 is also selected in the initial round.
|
||||
validators: if self.validator_pool { vec![10, 20, 30, 40] } else { vec![10, 20] },
|
||||
validators: if self.validator_pool { vec![10, 20, 30, 40] } else { vec![10, 20] },
|
||||
keys: vec![],
|
||||
}.assimilate_storage(&mut t, &mut c);
|
||||
let _ = balances::GenesisConfig::<Test>{
|
||||
@@ -197,30 +196,26 @@ impl ExtBuilder {
|
||||
creation_fee: 0,
|
||||
vesting: vec![],
|
||||
}.assimilate_storage(&mut t, &mut c);
|
||||
let stake_21 = if self.fair { 1000 } else { 2000 };
|
||||
let stake_31 = if self.validator_pool { balance_factor * 1000 } else { 1 };
|
||||
let status_41 = if self.validator_pool {
|
||||
StakerStatus::<AccountId>::Validator
|
||||
} else {
|
||||
StakerStatus::<AccountId>::Idle
|
||||
};
|
||||
let nominated = if self.nominate { vec![11, 21] } else { vec![] };
|
||||
let _ = GenesisConfig::<Test>{
|
||||
sessions_per_era: self.sessions_per_era,
|
||||
current_era: self.current_era,
|
||||
stakers: if self.validator_pool {
|
||||
vec![
|
||||
(11, 10, balance_factor * 1000, StakerStatus::<AccountIdType>::Validator),
|
||||
(21, 20, balance_factor * if self.fair { 1000 } else { 2000 }, StakerStatus::<AccountIdType>::Validator),
|
||||
(31, 30, balance_factor * 1000, if self.validator_pool { StakerStatus::<AccountIdType>::Validator } else { StakerStatus::<AccountIdType>::Idle }),
|
||||
(41, 40, balance_factor * 1000, if self.validator_pool { StakerStatus::<AccountIdType>::Validator } else { StakerStatus::<AccountIdType>::Idle }),
|
||||
// nominator
|
||||
(101, 100, balance_factor * 500, if self.nominate { StakerStatus::<AccountIdType>::Nominator(vec![11, 21]) } else { StakerStatus::<AccountIdType>::Nominator(vec![]) })
|
||||
]
|
||||
} else {
|
||||
vec![
|
||||
(11, 10, balance_factor * 1000, StakerStatus::<AccountIdType>::Validator),
|
||||
(21, 20, balance_factor * if self.fair { 1000 } else { 2000 }, StakerStatus::<AccountIdType>::Validator),
|
||||
(31, 30, 1, StakerStatus::<AccountIdType>::Validator),
|
||||
// nominator
|
||||
(101, 100, balance_factor * 500, if self.nominate { StakerStatus::<AccountIdType>::Nominator(vec![11, 21]) } else { StakerStatus::<AccountIdType>::Nominator(vec![]) })
|
||||
]
|
||||
},
|
||||
stakers: vec![
|
||||
(11, 10, balance_factor * 1000, StakerStatus::<AccountId>::Validator),
|
||||
(21, 20, stake_21, StakerStatus::<AccountId>::Validator),
|
||||
(31, 30, stake_31, StakerStatus::<AccountId>::Validator),
|
||||
(41, 40, balance_factor * 1000, status_41),
|
||||
// nominator
|
||||
(101, 100, balance_factor * 500, StakerStatus::<AccountId>::Nominator(nominated))
|
||||
],
|
||||
validator_count: self.validator_count,
|
||||
minimum_validator_count: self.minimum_validator_count,
|
||||
bonding_duration: self.sessions_per_era * self.session_length * 3,
|
||||
session_reward: Perbill::from_millionths((1000000 * self.reward / balance_factor) as u32),
|
||||
offline_slash: Perbill::from_percent(5),
|
||||
current_session_reward: self.reward,
|
||||
@@ -268,4 +263,17 @@ pub fn bond_nominator(acc: u64, val: u64, target: Vec<u64>) {
|
||||
let _ = Balances::make_free_balance_be(&(acc+1), val);
|
||||
assert_ok!(Staking::bond(Origin::signed(acc+1), acc, val, RewardDestination::Controller));
|
||||
assert_ok!(Staking::nominate(Origin::signed(acc), target));
|
||||
}
|
||||
}
|
||||
|
||||
pub fn start_session(session_index: session::SessionIndex) {
|
||||
for i in 0..(session_index - Session::current_index()) {
|
||||
System::set_block_number((i + 1).into());
|
||||
Session::on_initialize(System::block_number());
|
||||
}
|
||||
assert_eq!(Session::current_index(), session_index);
|
||||
}
|
||||
|
||||
pub fn start_era(era_index: EraIndex) {
|
||||
start_session((era_index * 3).into());
|
||||
assert_eq!(Staking::current_era(), era_index);
|
||||
}
|
||||
|
||||
@@ -105,7 +105,8 @@ pub fn elect<T: Trait + 'static, FV, FN, FS>(
|
||||
|
||||
// 1- Pre-process candidates and place them in a container, optimisation and add phantom votes.
|
||||
// Candidates who have 0 stake => have no votes or all null-votes. Kick them out not.
|
||||
let mut nominators: Vec<Nominator<T::AccountId>> = Vec::with_capacity(validator_iter.size_hint().0 + nominator_iter.size_hint().0);
|
||||
let mut nominators: Vec<Nominator<T::AccountId>> =
|
||||
Vec::with_capacity(validator_iter.size_hint().0 + nominator_iter.size_hint().0);
|
||||
let mut candidates = validator_iter.map(|(who, _)| {
|
||||
let stash_balance = stash_of(&who);
|
||||
(Candidate { who, ..Default::default() }, stash_balance)
|
||||
|
||||
+179
-222
@@ -19,6 +19,7 @@
|
||||
use super::*;
|
||||
use runtime_io::with_externalities;
|
||||
use phragmen;
|
||||
use primitives::traits::OnInitialize;
|
||||
use srml_support::{assert_ok, assert_noop, assert_eq_uvec, EnumerableStorageMap};
|
||||
use mock::*;
|
||||
use srml_support::traits::{Currency, ReservableCurrency};
|
||||
@@ -93,7 +94,7 @@ fn no_offline_should_work() {
|
||||
assert_eq!(Staking::slash_count(&10), 0);
|
||||
assert_eq!(Balances::free_balance(&10), 1);
|
||||
// New era is not being forced
|
||||
assert!(Staking::forcing_new_era().is_none());
|
||||
assert!(!Staking::forcing_new_era());
|
||||
});
|
||||
}
|
||||
|
||||
@@ -110,9 +111,7 @@ fn change_controller_works() {
|
||||
|
||||
assert_ok!(Staking::set_controller(Origin::signed(11), 5));
|
||||
|
||||
System::set_block_number(1);
|
||||
Session::check_rotate_session(System::block_number());
|
||||
assert_eq!(Staking::current_era(), 1);
|
||||
start_era(1);
|
||||
|
||||
assert_noop!(
|
||||
Staking::validate(Origin::signed(10), ValidatorPrefs::default()),
|
||||
@@ -150,7 +149,7 @@ fn invulnerability_should_work() {
|
||||
assert!(<Validators<Test>>::exists(&11));
|
||||
// New era not being forced
|
||||
// NOTE: new era is always forced once slashing happens -> new validators need to be chosen.
|
||||
assert!(Staking::forcing_new_era().is_none());
|
||||
assert!(!Staking::forcing_new_era());
|
||||
});
|
||||
}
|
||||
|
||||
@@ -180,7 +179,7 @@ fn offline_should_slash_and_kick() {
|
||||
// Confirm account 10 has been removed as a validator
|
||||
assert!(!<Validators<Test>>::exists(&11));
|
||||
// A new era is forced due to slashing
|
||||
assert!(Staking::forcing_new_era().is_some());
|
||||
assert!(Staking::forcing_new_era());
|
||||
});
|
||||
}
|
||||
|
||||
@@ -219,7 +218,7 @@ fn offline_grace_should_delay_slashing() {
|
||||
// User gets slashed
|
||||
assert!(Balances::free_balance(&11) < 70);
|
||||
// New era is forced
|
||||
assert!(Staking::forcing_new_era().is_some());
|
||||
assert!(Staking::forcing_new_era());
|
||||
});
|
||||
}
|
||||
|
||||
@@ -286,7 +285,7 @@ fn slashing_does_not_cause_underflow() {
|
||||
});
|
||||
|
||||
System::set_block_number(1);
|
||||
Session::check_rotate_session(System::block_number());
|
||||
Session::on_initialize(System::block_number());
|
||||
|
||||
// Should not panic
|
||||
Staking::on_offline_validator(10, 100);
|
||||
@@ -303,8 +302,6 @@ fn rewards_should_work() {
|
||||
// * rewards get paid per Era
|
||||
// * Check that nominators are also rewarded
|
||||
with_externalities(&mut ExtBuilder::default()
|
||||
.session_length(3)
|
||||
.sessions_per_era(3)
|
||||
.build(),
|
||||
|| {
|
||||
let delay = 1;
|
||||
@@ -316,9 +313,6 @@ fn rewards_should_work() {
|
||||
assert_ok!(Staking::set_payee(Origin::signed(10), RewardDestination::Controller));
|
||||
|
||||
// Initial config should be correct
|
||||
assert_eq!(Staking::era_length(), 9);
|
||||
assert_eq!(Staking::sessions_per_era(), 3);
|
||||
assert_eq!(Staking::last_era_length_change(), 0);
|
||||
assert_eq!(Staking::current_era(), 0);
|
||||
assert_eq!(Session::current_index(), 0);
|
||||
assert_eq!(Staking::current_session_reward(), 10);
|
||||
@@ -343,7 +337,7 @@ fn rewards_should_work() {
|
||||
let mut block = 3; // Block 3 => Session 1 => Era 0
|
||||
System::set_block_number(block);
|
||||
Timestamp::set_timestamp(block*5); // on time.
|
||||
Session::check_rotate_session(System::block_number());
|
||||
Session::on_initialize(System::block_number());
|
||||
assert_eq!(Staking::current_era(), 0);
|
||||
assert_eq!(Session::current_index(), 1);
|
||||
|
||||
@@ -354,24 +348,24 @@ fn rewards_should_work() {
|
||||
block = 6; // Block 6 => Session 2 => Era 0
|
||||
System::set_block_number(block);
|
||||
Timestamp::set_timestamp(block*5 + delay); // a little late.
|
||||
Session::check_rotate_session(System::block_number());
|
||||
Session::on_initialize(System::block_number());
|
||||
assert_eq!(Staking::current_era(), 0);
|
||||
assert_eq!(Session::current_index(), 2);
|
||||
|
||||
// session reward is the same,
|
||||
assert_eq!(Staking::current_session_reward(), session_reward);
|
||||
// though 2 will be deducted while stashed in the era reward due to delay
|
||||
assert_eq!(Staking::current_era_reward(), 2*session_reward - delay);
|
||||
assert_eq!(Staking::current_era_reward(), 2*session_reward); // - delay);
|
||||
|
||||
block = 9; // Block 9 => Session 3 => Era 1
|
||||
System::set_block_number(block);
|
||||
Timestamp::set_timestamp(block*5); // back to being on time. no delays
|
||||
Session::check_rotate_session(System::block_number());
|
||||
Session::on_initialize(System::block_number());
|
||||
assert_eq!(Staking::current_era(), 1);
|
||||
assert_eq!(Session::current_index(), 3);
|
||||
|
||||
assert_eq!(Balances::total_balance(&10), 1 + (3*session_reward - delay)/2);
|
||||
assert_eq!(Balances::total_balance(&2), 500 + (3*session_reward - delay)/2);
|
||||
assert_eq!(Balances::total_balance(&10), 1 + (3*session_reward)/2);
|
||||
assert_eq!(Balances::total_balance(&2), 500 + (3*session_reward)/2);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -381,12 +375,9 @@ fn multi_era_reward_should_work() {
|
||||
// The value of current_session_reward is set at the end of each era, based on
|
||||
// slot_stake and session_reward.
|
||||
with_externalities(&mut ExtBuilder::default()
|
||||
.session_length(3)
|
||||
.sessions_per_era(3)
|
||||
.nominate(false)
|
||||
.build(),
|
||||
|| {
|
||||
let delay = 1;
|
||||
let session_reward = 10;
|
||||
|
||||
// This is set by the test config builder.
|
||||
@@ -398,37 +389,21 @@ fn multi_era_reward_should_work() {
|
||||
// Set payee to controller
|
||||
assert_ok!(Staking::set_payee(Origin::signed(10), RewardDestination::Controller));
|
||||
|
||||
let mut block = 3;
|
||||
// Block 3 => Session 1 => Era 0
|
||||
System::set_block_number(block);
|
||||
Timestamp::set_timestamp(block*5);
|
||||
Session::check_rotate_session(System::block_number());
|
||||
assert_eq!(Staking::current_era(), 0);
|
||||
assert_eq!(Session::current_index(), 1);
|
||||
start_session(1);
|
||||
|
||||
// session triggered: the reward value stashed should be 10
|
||||
assert_eq!(Staking::current_session_reward(), session_reward);
|
||||
assert_eq!(Staking::current_era_reward(), session_reward);
|
||||
|
||||
block = 6; // Block 6 => Session 2 => Era 0
|
||||
System::set_block_number(block);
|
||||
Timestamp::set_timestamp(block*5 + delay); // a little late.
|
||||
Session::check_rotate_session(System::block_number());
|
||||
assert_eq!(Staking::current_era(), 0);
|
||||
assert_eq!(Session::current_index(), 2);
|
||||
start_session(2);
|
||||
|
||||
assert_eq!(Staking::current_session_reward(), session_reward);
|
||||
assert_eq!(Staking::current_era_reward(), 2*session_reward - delay);
|
||||
assert_eq!(Staking::current_era_reward(), 2*session_reward);
|
||||
|
||||
block = 9; // Block 9 => Session 3 => Era 1
|
||||
System::set_block_number(block);
|
||||
Timestamp::set_timestamp(block*5); // back to being punktlisch. no delayss
|
||||
Session::check_rotate_session(System::block_number());
|
||||
assert_eq!(Staking::current_era(), 1);
|
||||
assert_eq!(Session::current_index(), 3);
|
||||
start_session(3);
|
||||
|
||||
// 1 + sum of of the session rewards accumulated
|
||||
let recorded_balance = 1 + 3*session_reward - delay;
|
||||
let recorded_balance = 1 + 3*session_reward;
|
||||
assert_eq!(Balances::total_balance(&10), recorded_balance);
|
||||
|
||||
// the reward for next era will be: session_reward * slot_stake
|
||||
@@ -436,14 +411,13 @@ fn multi_era_reward_should_work() {
|
||||
assert_eq!(Staking::current_session_reward(), new_session_reward);
|
||||
|
||||
// fast forward to next era:
|
||||
block=12; System::set_block_number(block);Timestamp::set_timestamp(block*5);Session::check_rotate_session(System::block_number());
|
||||
block=15; System::set_block_number(block);Timestamp::set_timestamp(block*5);Session::check_rotate_session(System::block_number());
|
||||
start_session(5);
|
||||
|
||||
// intermediate test.
|
||||
assert_eq!(Staking::current_era_reward(), 2*new_session_reward);
|
||||
|
||||
// new era is triggered here.
|
||||
block=18; System::set_block_number(block);Timestamp::set_timestamp(block*5);Session::check_rotate_session(System::block_number());
|
||||
start_session(6);
|
||||
|
||||
// pay time
|
||||
assert_eq!(Balances::total_balance(&10), 3*new_session_reward + recorded_balance);
|
||||
@@ -457,7 +431,6 @@ fn staking_should_work() {
|
||||
// * new ones will be chosen per era
|
||||
// * either one can unlock the stash and back-down from being a validator via `chill`ing.
|
||||
with_externalities(&mut ExtBuilder::default()
|
||||
.sessions_per_era(3)
|
||||
.nominate(false)
|
||||
.fair(false) // to give 20 more staked value
|
||||
.build(),
|
||||
@@ -465,15 +438,12 @@ fn staking_should_work() {
|
||||
// remember + compare this along with the test.
|
||||
assert_eq_uvec!(Session::validators(), vec![20, 10]);
|
||||
|
||||
assert_ok!(Staking::set_bonding_duration(2));
|
||||
assert_eq!(Staking::bonding_duration(), 2);
|
||||
|
||||
// put some money in account that we'll use.
|
||||
for i in 1..5 { let _ = Balances::make_free_balance_be(&i, 2000); }
|
||||
|
||||
// --- Block 1:
|
||||
System::set_block_number(1);
|
||||
Session::check_rotate_session(System::block_number());
|
||||
Session::on_initialize(System::block_number());
|
||||
assert_eq!(Staking::current_era(), 0);
|
||||
|
||||
// add a new candidate for being a validator. account 3 controlled by 4.
|
||||
@@ -485,7 +455,7 @@ fn staking_should_work() {
|
||||
|
||||
// --- Block 2:
|
||||
System::set_block_number(2);
|
||||
Session::check_rotate_session(System::block_number());
|
||||
Session::on_initialize(System::block_number());
|
||||
assert_eq!(Staking::current_era(), 0);
|
||||
|
||||
// No effects will be seen so far. Era has not been yet triggered.
|
||||
@@ -494,7 +464,7 @@ fn staking_should_work() {
|
||||
|
||||
// --- Block 3: the validators will now change.
|
||||
System::set_block_number(3);
|
||||
Session::check_rotate_session(System::block_number());
|
||||
Session::on_initialize(System::block_number());
|
||||
|
||||
// 2 only voted for 4 and 20
|
||||
assert_eq!(Session::validators().len(), 2);
|
||||
@@ -504,7 +474,7 @@ fn staking_should_work() {
|
||||
|
||||
// --- Block 4: Unstake 4 as a validator, freeing up the balance stashed in 3
|
||||
System::set_block_number(4);
|
||||
Session::check_rotate_session(System::block_number());
|
||||
Session::on_initialize(System::block_number());
|
||||
|
||||
// 4 will chill
|
||||
Staking::chill(Origin::signed(4)).unwrap();
|
||||
@@ -516,14 +486,14 @@ fn staking_should_work() {
|
||||
|
||||
// --- Block 5: nothing. 4 is still there.
|
||||
System::set_block_number(5);
|
||||
Session::check_rotate_session(System::block_number());
|
||||
Session::on_initialize(System::block_number());
|
||||
assert_eq_uvec!(Session::validators(), vec![20, 4]);
|
||||
assert_eq!(Staking::current_era(), 1);
|
||||
|
||||
|
||||
// --- Block 6: 4 will not be a validator.
|
||||
System::set_block_number(6);
|
||||
Session::check_rotate_session(System::block_number());
|
||||
Session::on_initialize(System::block_number());
|
||||
assert_eq!(Staking::current_era(), 2);
|
||||
assert_eq!(Session::validators().contains(&4), false);
|
||||
assert_eq_uvec!(Session::validators(), vec![20, 10]);
|
||||
@@ -548,9 +518,7 @@ fn less_than_needed_candidates_works() {
|
||||
assert_eq!(Staking::minimum_validator_count(), 1);
|
||||
assert_eq_uvec!(Session::validators(), vec![30, 20, 10]);
|
||||
|
||||
System::set_block_number(1);
|
||||
Session::check_rotate_session(System::block_number());
|
||||
assert_eq!(Staking::current_era(), 1);
|
||||
start_era(1);
|
||||
|
||||
// Previous set is selected. NO election algorithm is even executed.
|
||||
assert_eq_uvec!(Session::validators(), vec![30, 20, 10]);
|
||||
@@ -574,7 +542,6 @@ fn no_candidate_emergency_condition() {
|
||||
.nominate(false)
|
||||
.build(),
|
||||
|| {
|
||||
assert_eq!(Staking::era_length(), 1);
|
||||
assert_eq!(Staking::validator_count(), 15);
|
||||
|
||||
// initial validators
|
||||
@@ -584,7 +551,7 @@ fn no_candidate_emergency_condition() {
|
||||
|
||||
// trigger era
|
||||
System::set_block_number(1);
|
||||
Session::check_rotate_session(System::block_number());
|
||||
Session::on_initialize(System::block_number());
|
||||
|
||||
// Previous ones are elected. chill is invalidates. TODO: #2494
|
||||
assert_eq_uvec!(Session::validators(), vec![10, 20, 30, 40]);
|
||||
@@ -663,16 +630,14 @@ fn nominating_and_rewards_should_work() {
|
||||
assert_ok!(Staking::bond(Origin::signed(3), 4, 1000, RewardDestination::Controller));
|
||||
assert_ok!(Staking::nominate(Origin::signed(4), vec![11, 21, 41]));
|
||||
|
||||
System::set_block_number(1);
|
||||
Session::check_rotate_session(System::block_number());
|
||||
assert_eq!(Staking::current_era(), 1);
|
||||
start_era(1);
|
||||
|
||||
// 10 and 20 have more votes, they will be chosen by phragmen.
|
||||
assert_eq_uvec!(Session::validators(), vec![20, 10]);
|
||||
|
||||
// OLD validators must have already received some rewards.
|
||||
assert_eq!(Balances::total_balance(&40), 1 + session_reward);
|
||||
assert_eq!(Balances::total_balance(&30), 1 + session_reward);
|
||||
assert_eq!(Balances::total_balance(&40), 1 + 3 * session_reward);
|
||||
assert_eq!(Balances::total_balance(&30), 1 + 3 * session_reward);
|
||||
|
||||
// ------ check the staked value of all parties.
|
||||
|
||||
@@ -694,22 +659,21 @@ fn nominating_and_rewards_should_work() {
|
||||
assert_eq!(Staking::stakers(41).total, 0);
|
||||
|
||||
|
||||
System::set_block_number(2);
|
||||
Session::check_rotate_session(System::block_number());
|
||||
start_era(2);
|
||||
// next session reward.
|
||||
let new_session_reward = Staking::session_reward() * Staking::slot_stake();
|
||||
let new_session_reward = Staking::session_reward() * 3 * Staking::slot_stake();
|
||||
// nothing else will happen, era ends and rewards are paid again,
|
||||
// it is expected that nominators will also be paid. See below
|
||||
|
||||
// Nominator 2: has [400/1800 ~ 2/9 from 10] + [600/2200 ~ 3/11 from 20]'s reward. ==> 2/9 + 3/11
|
||||
assert_eq!(Balances::total_balance(&2), initial_balance + (2*new_session_reward/9 + 3*new_session_reward/11));
|
||||
assert_eq!(Balances::total_balance(&2), initial_balance + (2*new_session_reward/9 + 3*new_session_reward/11) - 1);
|
||||
// Nominator 4: has [400/1800 ~ 2/9 from 10] + [600/2200 ~ 3/11 from 20]'s reward. ==> 2/9 + 3/11
|
||||
assert_eq!(Balances::total_balance(&4), initial_balance + (2*new_session_reward/9 + 3*new_session_reward/11));
|
||||
assert_eq!(Balances::total_balance(&4), initial_balance + (2*new_session_reward/9 + 3*new_session_reward/11) - 1);
|
||||
|
||||
// 10 got 800 / 1800 external stake => 8/18 =? 4/9 => Validator's share = 5/9
|
||||
assert_eq!(Balances::total_balance(&10), initial_balance + 5*new_session_reward/9);
|
||||
// 10 got 1200 / 2200 external stake => 12/22 =? 6/11 => Validator's share = 5/11
|
||||
assert_eq!(Balances::total_balance(&20), initial_balance + 5*new_session_reward/11);
|
||||
assert_eq!(Balances::total_balance(&20), initial_balance + 5*new_session_reward/11+ 2);
|
||||
|
||||
check_exposure_all();
|
||||
});
|
||||
@@ -719,7 +683,6 @@ fn nominating_and_rewards_should_work() {
|
||||
fn nominators_also_get_slashed() {
|
||||
// A nominator should be slashed if the validator they nominated is slashed
|
||||
with_externalities(&mut ExtBuilder::default().nominate(false).build(), || {
|
||||
assert_eq!(Staking::era_length(), 1);
|
||||
assert_eq!(Staking::validator_count(), 2);
|
||||
// slash happens immediately.
|
||||
assert_eq!(Staking::offline_slash_grace(), 0);
|
||||
@@ -742,8 +705,7 @@ fn nominators_also_get_slashed() {
|
||||
assert_ok!(Staking::nominate(Origin::signed(2), vec![20, 10]));
|
||||
|
||||
// new era, pay rewards,
|
||||
System::set_block_number(1);
|
||||
Session::check_rotate_session(System::block_number());
|
||||
start_era(1);
|
||||
|
||||
// Nominator stash didn't collect any.
|
||||
assert_eq!(Balances::total_balance(&2), initial_balance);
|
||||
@@ -757,11 +719,11 @@ fn nominators_also_get_slashed() {
|
||||
let nominator_slash = nominator_stake.min(total_slash - validator_slash);
|
||||
|
||||
// initial + first era reward + slash
|
||||
assert_eq!(Balances::total_balance(&10), initial_balance + 10 - validator_slash);
|
||||
assert_eq!(Balances::total_balance(&10), initial_balance + 30 - validator_slash);
|
||||
assert_eq!(Balances::total_balance(&2), initial_balance - nominator_slash);
|
||||
check_exposure_all();
|
||||
// Because slashing happened.
|
||||
assert!(Staking::forcing_new_era().is_some());
|
||||
assert!(Staking::forcing_new_era());
|
||||
});
|
||||
}
|
||||
|
||||
@@ -772,7 +734,6 @@ fn double_staking_should_fail() {
|
||||
// * an account already bonded as stash cannot nominate.
|
||||
// * an account already bonded as controller can nominate.
|
||||
with_externalities(&mut ExtBuilder::default()
|
||||
.sessions_per_era(2)
|
||||
.build(),
|
||||
|| {
|
||||
let arbitrary_value = 5;
|
||||
@@ -792,7 +753,6 @@ fn double_controlling_should_fail() {
|
||||
// should test (in the same order):
|
||||
// * an account already bonded as controller CANNOT be reused as the controller of another account.
|
||||
with_externalities(&mut ExtBuilder::default()
|
||||
.sessions_per_era(2)
|
||||
.build(),
|
||||
|| {
|
||||
let arbitrary_value = 5;
|
||||
@@ -806,70 +766,43 @@ fn double_controlling_should_fail() {
|
||||
#[test]
|
||||
fn session_and_eras_work() {
|
||||
with_externalities(&mut ExtBuilder::default()
|
||||
.sessions_per_era(2)
|
||||
.build(),
|
||||
|| {
|
||||
assert_eq!(Staking::era_length(), 2);
|
||||
assert_eq!(Staking::sessions_per_era(), 2);
|
||||
assert_eq!(Staking::last_era_length_change(), 0);
|
||||
assert_eq!(Staking::current_era(), 0);
|
||||
assert_eq!(Session::current_index(), 0);
|
||||
|
||||
// Block 1: No change.
|
||||
System::set_block_number(1);
|
||||
Session::check_rotate_session(System::block_number());
|
||||
start_session(1);
|
||||
assert_eq!(Session::current_index(), 1);
|
||||
assert_eq!(Staking::sessions_per_era(), 2);
|
||||
assert_eq!(Staking::last_era_length_change(), 0);
|
||||
assert_eq!(Staking::current_era(), 0);
|
||||
|
||||
// Block 2: Simple era change.
|
||||
System::set_block_number(2);
|
||||
Session::check_rotate_session(System::block_number());
|
||||
assert_eq!(Session::current_index(), 2);
|
||||
assert_eq!(Staking::sessions_per_era(), 2);
|
||||
assert_eq!(Staking::last_era_length_change(), 0);
|
||||
start_session(3);
|
||||
assert_eq!(Session::current_index(), 3);
|
||||
assert_eq!(Staking::current_era(), 1);
|
||||
|
||||
// Block 3: Schedule an era length change; no visible changes.
|
||||
System::set_block_number(3);
|
||||
assert_ok!(Staking::set_sessions_per_era(3));
|
||||
Session::check_rotate_session(System::block_number());
|
||||
assert_eq!(Session::current_index(), 3);
|
||||
assert_eq!(Staking::sessions_per_era(), 2);
|
||||
assert_eq!(Staking::last_era_length_change(), 0);
|
||||
start_session(4);
|
||||
assert_eq!(Session::current_index(), 4);
|
||||
assert_eq!(Staking::current_era(), 1);
|
||||
|
||||
// Block 4: Era change kicks in.
|
||||
System::set_block_number(4);
|
||||
Session::check_rotate_session(System::block_number());
|
||||
assert_eq!(Session::current_index(), 4);
|
||||
assert_eq!(Staking::sessions_per_era(), 3);
|
||||
assert_eq!(Staking::last_era_length_change(), 4);
|
||||
start_session(6);
|
||||
assert_eq!(Session::current_index(), 6);
|
||||
assert_eq!(Staking::current_era(), 2);
|
||||
|
||||
// Block 5: No change.
|
||||
System::set_block_number(5);
|
||||
Session::check_rotate_session(System::block_number());
|
||||
assert_eq!(Session::current_index(), 5);
|
||||
assert_eq!(Staking::sessions_per_era(), 3);
|
||||
assert_eq!(Staking::last_era_length_change(), 4);
|
||||
start_session(7);
|
||||
assert_eq!(Session::current_index(), 7);
|
||||
assert_eq!(Staking::current_era(), 2);
|
||||
|
||||
// Block 6: No change.
|
||||
System::set_block_number(6);
|
||||
Session::check_rotate_session(System::block_number());
|
||||
assert_eq!(Session::current_index(), 6);
|
||||
assert_eq!(Staking::sessions_per_era(), 3);
|
||||
assert_eq!(Staking::last_era_length_change(), 4);
|
||||
start_session(8);
|
||||
assert_eq!(Session::current_index(), 8);
|
||||
assert_eq!(Staking::current_era(), 2);
|
||||
|
||||
// Block 7: Era increment.
|
||||
System::set_block_number(7);
|
||||
Session::check_rotate_session(System::block_number());
|
||||
assert_eq!(Session::current_index(), 7);
|
||||
assert_eq!(Staking::sessions_per_era(), 3);
|
||||
assert_eq!(Staking::last_era_length_change(), 4);
|
||||
start_session(9);
|
||||
assert_eq!(Session::current_index(), 9);
|
||||
assert_eq!(Staking::current_era(), 3);
|
||||
});
|
||||
}
|
||||
@@ -947,31 +880,39 @@ fn reward_destination_works() {
|
||||
// Check the balance of the stash account
|
||||
assert_eq!(Balances::free_balance(&11), 1000);
|
||||
// Check how much is at stake
|
||||
assert_eq!(Staking::ledger(&10), Some(StakingLedger { stash: 11, total: 1000, active: 1000, unlocking: vec![] }));
|
||||
assert_eq!(Staking::ledger(&10), Some(StakingLedger {
|
||||
stash: 11,
|
||||
total: 1000,
|
||||
active: 1000,
|
||||
unlocking: vec![],
|
||||
}));
|
||||
// Check current session reward is 10
|
||||
let session_reward0 = Staking::current_session_reward(); // 10
|
||||
let session_reward0 = 3 * Staking::current_session_reward(); // 10
|
||||
|
||||
// Move forward the system for payment
|
||||
System::set_block_number(1);
|
||||
Timestamp::set_timestamp(5);
|
||||
Session::check_rotate_session(System::block_number());
|
||||
start_era(1);
|
||||
|
||||
// Check that RewardDestination is Staked (default)
|
||||
assert_eq!(Staking::payee(&11), RewardDestination::Staked);
|
||||
// Check that reward went to the stash account of validator
|
||||
assert_eq!(Balances::free_balance(&11), 1000 + session_reward0);
|
||||
// Check that amount at stake increased accordingly
|
||||
assert_eq!(Staking::ledger(&10), Some(StakingLedger { stash: 11, total: 1000 + session_reward0, active: 1000 + session_reward0, unlocking: vec![] }));
|
||||
assert_eq!(Staking::ledger(&10), Some(StakingLedger {
|
||||
stash: 11,
|
||||
total: 1000 + session_reward0,
|
||||
active: 1000 + session_reward0,
|
||||
unlocking: vec![],
|
||||
}));
|
||||
// Update current session reward
|
||||
let session_reward1 = Staking::current_session_reward(); // 1010 (1* slot_stake)
|
||||
let session_reward1 = 3 * Staking::current_session_reward(); // 1010 (1* slot_stake)
|
||||
|
||||
//Change RewardDestination to Stash
|
||||
<Payee<Test>>::insert(&11, RewardDestination::Stash);
|
||||
|
||||
// Move forward the system for payment
|
||||
System::set_block_number(2);
|
||||
Timestamp::set_timestamp(10);
|
||||
Session::check_rotate_session(System::block_number());
|
||||
start_era(2);
|
||||
|
||||
// Check that RewardDestination is Stash
|
||||
assert_eq!(Staking::payee(&11), RewardDestination::Stash);
|
||||
@@ -980,7 +921,12 @@ fn reward_destination_works() {
|
||||
// Record this value
|
||||
let recorded_stash_balance = 1000 + session_reward0 + session_reward1;
|
||||
// Check that amount at stake is NOT increased
|
||||
assert_eq!(Staking::ledger(&10), Some(StakingLedger { stash: 11, total: 1000 + session_reward0, active: 1000 + session_reward0, unlocking: vec![] }));
|
||||
assert_eq!(Staking::ledger(&10), Some(StakingLedger {
|
||||
stash: 11,
|
||||
total: 1000 + session_reward0,
|
||||
active: 1000 + session_reward0,
|
||||
unlocking: vec![],
|
||||
}));
|
||||
|
||||
// Change RewardDestination to Controller
|
||||
<Payee<Test>>::insert(&11, RewardDestination::Controller);
|
||||
@@ -989,17 +935,21 @@ fn reward_destination_works() {
|
||||
assert_eq!(Balances::free_balance(&10), 1);
|
||||
|
||||
// Move forward the system for payment
|
||||
System::set_block_number(3);
|
||||
Timestamp::set_timestamp(15);
|
||||
Session::check_rotate_session(System::block_number());
|
||||
let session_reward2 = Staking::current_session_reward(); // 1010 (1* slot_stake)
|
||||
start_era(3);
|
||||
let session_reward2 = 3 * Staking::current_session_reward(); // 1010 (1* slot_stake)
|
||||
|
||||
// Check that RewardDestination is Controller
|
||||
assert_eq!(Staking::payee(&11), RewardDestination::Controller);
|
||||
// Check that reward went to the controller account
|
||||
assert_eq!(Balances::free_balance(&10), 1 + session_reward2);
|
||||
// Check that amount at stake is NOT increased
|
||||
assert_eq!(Staking::ledger(&10), Some(StakingLedger { stash: 11, total: 1000 + session_reward0, active: 1000 + session_reward0, unlocking: vec![] }));
|
||||
assert_eq!(Staking::ledger(&10), Some(StakingLedger {
|
||||
stash: 11,
|
||||
total: 1000 + session_reward0,
|
||||
active: 1000 + session_reward0,
|
||||
unlocking: vec![],
|
||||
}));
|
||||
// Check that amount in staked account is NOT increased.
|
||||
assert_eq!(Balances::free_balance(&11), recorded_stash_balance);
|
||||
});
|
||||
@@ -1011,8 +961,6 @@ fn validator_payment_prefs_work() {
|
||||
// Note: unstake threshold is being directly tested in slashing tests.
|
||||
// This test will focus on validator payment.
|
||||
with_externalities(&mut ExtBuilder::default()
|
||||
.session_length(3)
|
||||
.sessions_per_era(3)
|
||||
.build(),
|
||||
|| {
|
||||
// Initial config
|
||||
@@ -1044,7 +992,7 @@ fn validator_payment_prefs_work() {
|
||||
// Block 3 => Session 1 => Era 0
|
||||
let mut block = 3;
|
||||
System::set_block_number(block);
|
||||
Session::check_rotate_session(System::block_number());
|
||||
Session::on_initialize(System::block_number());
|
||||
assert_eq!(Staking::current_era(), 0);
|
||||
assert_eq!(Session::current_index(), 1);
|
||||
|
||||
@@ -1054,7 +1002,7 @@ fn validator_payment_prefs_work() {
|
||||
|
||||
block = 6; // Block 6 => Session 2 => Era 0
|
||||
System::set_block_number(block);
|
||||
Session::check_rotate_session(System::block_number());
|
||||
Session::on_initialize(System::block_number());
|
||||
assert_eq!(Staking::current_era(), 0);
|
||||
assert_eq!(Session::current_index(), 2);
|
||||
|
||||
@@ -1063,7 +1011,7 @@ fn validator_payment_prefs_work() {
|
||||
|
||||
block = 9; // Block 9 => Session 3 => Era 1
|
||||
System::set_block_number(block);
|
||||
Session::check_rotate_session(System::block_number());
|
||||
Session::on_initialize(System::block_number());
|
||||
assert_eq!(Staking::current_era(), 1);
|
||||
assert_eq!(Session::current_index(), 3);
|
||||
|
||||
@@ -1093,7 +1041,12 @@ fn bond_extra_works() {
|
||||
// Check that account 10 is bonded to account 11
|
||||
assert_eq!(Staking::bonded(&11), Some(10));
|
||||
// Check how much is at stake
|
||||
assert_eq!(Staking::ledger(&10), Some(StakingLedger { stash: 11, total: 1000, active: 1000, unlocking: vec![] }));
|
||||
assert_eq!(Staking::ledger(&10), Some(StakingLedger {
|
||||
stash: 11,
|
||||
total: 1000,
|
||||
active: 1000,
|
||||
unlocking: vec![],
|
||||
}));
|
||||
|
||||
// Give account 11 some large free balance greater than total
|
||||
let _ = Balances::make_free_balance_be(&11, 1000000);
|
||||
@@ -1101,12 +1054,22 @@ fn bond_extra_works() {
|
||||
// Call the bond_extra function from controller, add only 100
|
||||
assert_ok!(Staking::bond_extra(Origin::signed(11), 100));
|
||||
// There should be 100 more `total` and `active` in the ledger
|
||||
assert_eq!(Staking::ledger(&10), Some(StakingLedger { stash: 11, total: 1000 + 100, active: 1000 + 100, unlocking: vec![] }));
|
||||
assert_eq!(Staking::ledger(&10), Some(StakingLedger {
|
||||
stash: 11,
|
||||
total: 1000 + 100,
|
||||
active: 1000 + 100,
|
||||
unlocking: vec![],
|
||||
}));
|
||||
|
||||
// Call the bond_extra function with a large number, should handle it
|
||||
assert_ok!(Staking::bond_extra(Origin::signed(11), u64::max_value()));
|
||||
// The full amount of the funds should now be in the total and active
|
||||
assert_eq!(Staking::ledger(&10), Some(StakingLedger { stash: 11, total: 1000000, active: 1000000, unlocking: vec![] }));
|
||||
assert_eq!(Staking::ledger(&10), Some(StakingLedger {
|
||||
stash: 11,
|
||||
total: 1000000,
|
||||
active: 1000000,
|
||||
unlocking: vec![],
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1124,14 +1087,10 @@ fn bond_extra_and_withdraw_unbonded_works() {
|
||||
// Set payee to controller. avoids confusion
|
||||
assert_ok!(Staking::set_payee(Origin::signed(10), RewardDestination::Controller));
|
||||
|
||||
// Set unbonding era (bonding_duration) to 2
|
||||
assert_ok!(Staking::set_bonding_duration(2));
|
||||
|
||||
// Give account 11 some large free balance greater than total
|
||||
let _ = Balances::make_free_balance_be(&11, 1000000);
|
||||
|
||||
// Initial config should be correct
|
||||
assert_eq!(Staking::sessions_per_era(), 1);
|
||||
assert_eq!(Staking::current_era(), 0);
|
||||
assert_eq!(Session::current_index(), 0);
|
||||
assert_eq!(Staking::current_session_reward(), 10);
|
||||
@@ -1140,58 +1099,65 @@ fn bond_extra_and_withdraw_unbonded_works() {
|
||||
assert_eq!(Balances::total_balance(&10), 1);
|
||||
|
||||
// confirm that 10 is a normal validator and gets paid at the end of the era.
|
||||
System::set_block_number(1);
|
||||
Session::check_rotate_session(System::block_number());
|
||||
start_era(1);
|
||||
|
||||
// Initial state of 10
|
||||
assert_eq!(Staking::ledger(&10), Some(StakingLedger { stash: 11, total: 1000, active: 1000, unlocking: vec![] }));
|
||||
assert_eq!(Staking::ledger(&10), Some(StakingLedger {
|
||||
stash: 11,
|
||||
total: 1000,
|
||||
active: 1000,
|
||||
unlocking: vec![],
|
||||
}));
|
||||
assert_eq!(Staking::stakers(&11), Exposure { total: 1000, own: 1000, others: vec![] });
|
||||
|
||||
// deposit the extra 100 units
|
||||
Staking::bond_extra(Origin::signed(11), 100).unwrap();
|
||||
|
||||
assert_eq!(Staking::ledger(&10), Some(StakingLedger { stash: 11, total: 1000 + 100, active: 1000 + 100, unlocking: vec![] }));
|
||||
assert_eq!(Staking::ledger(&10), Some(StakingLedger {
|
||||
stash: 11,
|
||||
total: 1000 + 100,
|
||||
active: 1000 + 100,
|
||||
unlocking: vec![],
|
||||
}));
|
||||
// Exposure is a snapshot! only updated after the next era update.
|
||||
assert_ne!(Staking::stakers(&11), Exposure { total: 1000 + 100, own: 1000 + 100, others: vec![] });
|
||||
|
||||
// trigger next era.
|
||||
System::set_block_number(2);Timestamp::set_timestamp(10);Session::check_rotate_session(System::block_number());
|
||||
Timestamp::set_timestamp(10);
|
||||
start_era(2);
|
||||
assert_eq!(Staking::current_era(), 2);
|
||||
assert_eq!(Session::current_index(), 2);
|
||||
|
||||
// ledger should be the same.
|
||||
assert_eq!(Staking::ledger(&10), Some(StakingLedger { stash: 11, total: 1000 + 100, active: 1000 + 100, unlocking: vec![] }));
|
||||
assert_eq!(Staking::ledger(&10), Some(StakingLedger {
|
||||
stash: 11,
|
||||
total: 1000 + 100,
|
||||
active: 1000 + 100,
|
||||
unlocking: vec![],
|
||||
}));
|
||||
// Exposure is now updated.
|
||||
assert_eq!(Staking::stakers(&11), Exposure { total: 1000 + 100, own: 1000 + 100, others: vec![] });
|
||||
|
||||
// Unbond almost all of the funds in stash.
|
||||
Staking::unbond(Origin::signed(10), 1000).unwrap();
|
||||
assert_eq!(Staking::ledger(&10), Some(StakingLedger {
|
||||
stash: 11, total: 1000 + 100, active: 100, unlocking: vec![UnlockChunk{ value: 1000, era: 2 + 2}] })
|
||||
stash: 11, total: 1000 + 100, active: 100, unlocking: vec![UnlockChunk{ value: 1000, era: 2 + 3}] })
|
||||
);
|
||||
|
||||
// Attempting to free the balances now will fail. 2 eras need to pass.
|
||||
Staking::withdraw_unbonded(Origin::signed(10)).unwrap();
|
||||
assert_eq!(Staking::ledger(&10), Some(StakingLedger {
|
||||
stash: 11, total: 1000 + 100, active: 100, unlocking: vec![UnlockChunk{ value: 1000, era: 2 + 2}] }));
|
||||
stash: 11, total: 1000 + 100, active: 100, unlocking: vec![UnlockChunk{ value: 1000, era: 2 + 3}] }));
|
||||
|
||||
// trigger next era.
|
||||
System::set_block_number(3);
|
||||
Session::check_rotate_session(System::block_number());
|
||||
|
||||
assert_eq!(Staking::current_era(), 3);
|
||||
assert_eq!(Session::current_index(), 3);
|
||||
start_era(3);
|
||||
|
||||
// nothing yet
|
||||
Staking::withdraw_unbonded(Origin::signed(10)).unwrap();
|
||||
assert_eq!(Staking::ledger(&10), Some(StakingLedger {
|
||||
stash: 11, total: 1000 + 100, active: 100, unlocking: vec![UnlockChunk{ value: 1000, era: 2 + 2}] }));
|
||||
stash: 11, total: 1000 + 100, active: 100, unlocking: vec![UnlockChunk{ value: 1000, era: 2 + 3}] }));
|
||||
|
||||
// trigger next era.
|
||||
System::set_block_number(4);
|
||||
Session::check_rotate_session(System::block_number());
|
||||
assert_eq!(Staking::current_era(), 4);
|
||||
assert_eq!(Session::current_index(), 4);
|
||||
start_era(5);
|
||||
|
||||
Staking::withdraw_unbonded(Origin::signed(10)).unwrap();
|
||||
// Now the value is free and the staking ledger is updated.
|
||||
@@ -1208,19 +1174,14 @@ fn too_many_unbond_calls_should_not_work() {
|
||||
assert_ok!(Staking::unbond(Origin::signed(10), 1));
|
||||
}
|
||||
|
||||
System::set_block_number(1);
|
||||
Session::check_rotate_session(System::block_number());
|
||||
start_era(1);
|
||||
|
||||
// locked ar era 1 until 4
|
||||
// locked at era 1 until 4
|
||||
assert_ok!(Staking::unbond(Origin::signed(10), 1));
|
||||
// can't do more.
|
||||
assert_noop!(Staking::unbond(Origin::signed(10), 1), "can not schedule more unlock chunks");
|
||||
|
||||
System::set_block_number(2);
|
||||
Session::check_rotate_session(System::block_number());
|
||||
|
||||
System::set_block_number(3);
|
||||
Session::check_rotate_session(System::block_number());
|
||||
start_era(3);
|
||||
|
||||
assert_noop!(Staking::unbond(Origin::signed(10), 1), "can not schedule more unlock chunks");
|
||||
// free up.
|
||||
@@ -1262,23 +1223,21 @@ fn slot_stake_is_least_staked_validator_and_exposure_defines_maximum_punishment(
|
||||
<Ledger<Test>>::insert(&20, StakingLedger { stash: 22, total: 69, active: 69, unlocking: vec![] });
|
||||
|
||||
// New era --> rewards are paid --> stakes are changed
|
||||
System::set_block_number(1);
|
||||
Session::check_rotate_session(System::block_number());
|
||||
assert_eq!(Staking::current_era(), 1);
|
||||
start_era(1);
|
||||
|
||||
// -- new balances + reward
|
||||
assert_eq!(Staking::stakers(&11).total, 1000 + 10);
|
||||
assert_eq!(Staking::stakers(&21).total, 69 + 10);
|
||||
assert_eq!(Staking::stakers(&11).total, 1000 + 30);
|
||||
assert_eq!(Staking::stakers(&21).total, 69 + 30);
|
||||
|
||||
// -- slot stake should also be updated.
|
||||
assert_eq!(Staking::slot_stake(), 79);
|
||||
assert_eq!(Staking::slot_stake(), 69 + 30);
|
||||
|
||||
// If 10 gets slashed now, it will be slashed by 5% of exposure.total * 2.pow(unstake_thresh)
|
||||
Staking::on_offline_validator(10, 4);
|
||||
// Confirm user has been reported
|
||||
assert_eq!(Staking::slash_count(&11), 4);
|
||||
// check the balance of 10 (slash will be deducted from free balance.)
|
||||
assert_eq!(Balances::free_balance(&11), 1000 + 10 - 50 /*5% of 1000*/ * 8 /*2**3*/);
|
||||
assert_eq!(Balances::free_balance(&11), 1000 + 30 - 51 /*5% of 1030*/ * 8 /*2**3*/);
|
||||
|
||||
check_exposure_all();
|
||||
});
|
||||
@@ -1474,8 +1433,7 @@ fn phragmen_poc_works() {
|
||||
assert_ok!(Staking::nominate(Origin::signed(4), vec![11, 21, 41]));
|
||||
|
||||
// New era => election algorithm will trigger
|
||||
System::set_block_number(1);
|
||||
Session::check_rotate_session(System::block_number());
|
||||
start_era(1);
|
||||
|
||||
assert_eq_uvec!(Session::validators(), vec![20, 10]);
|
||||
|
||||
@@ -1552,7 +1510,6 @@ fn switching_roles() {
|
||||
// Test that it should be possible to switch between roles (nominator, validator, idle) with minimal overhead.
|
||||
with_externalities(&mut ExtBuilder::default()
|
||||
.nominate(false)
|
||||
.sessions_per_era(3)
|
||||
.build(),
|
||||
|| {
|
||||
// Reset reward destination
|
||||
@@ -1576,21 +1533,21 @@ fn switching_roles() {
|
||||
|
||||
// new block
|
||||
System::set_block_number(1);
|
||||
Session::check_rotate_session(System::block_number());
|
||||
Session::on_initialize(System::block_number());
|
||||
|
||||
// no change
|
||||
assert_eq_uvec!(Session::validators(), vec![20, 10]);
|
||||
|
||||
// new block
|
||||
System::set_block_number(2);
|
||||
Session::check_rotate_session(System::block_number());
|
||||
Session::on_initialize(System::block_number());
|
||||
|
||||
// no change
|
||||
assert_eq_uvec!(Session::validators(), vec![20, 10]);
|
||||
|
||||
// new block --> ne era --> new validators
|
||||
System::set_block_number(3);
|
||||
Session::check_rotate_session(System::block_number());
|
||||
Session::on_initialize(System::block_number());
|
||||
|
||||
// with current nominators 10 and 5 have the most stake
|
||||
assert_eq_uvec!(Session::validators(), vec![6, 10]);
|
||||
@@ -1605,16 +1562,16 @@ fn switching_roles() {
|
||||
// Winners: 20 and 2
|
||||
|
||||
System::set_block_number(4);
|
||||
Session::check_rotate_session(System::block_number());
|
||||
Session::on_initialize(System::block_number());
|
||||
assert_eq_uvec!(Session::validators(), vec![6, 10]);
|
||||
|
||||
System::set_block_number(5);
|
||||
Session::check_rotate_session(System::block_number());
|
||||
Session::on_initialize(System::block_number());
|
||||
assert_eq_uvec!(Session::validators(), vec![6, 10]);
|
||||
|
||||
// ne era
|
||||
System::set_block_number(6);
|
||||
Session::check_rotate_session(System::block_number());
|
||||
Session::on_initialize(System::block_number());
|
||||
assert_eq_uvec!(Session::validators(), vec![2, 20]);
|
||||
check_exposure_all();
|
||||
});
|
||||
@@ -1640,8 +1597,7 @@ fn wrong_vote_is_null() {
|
||||
]));
|
||||
|
||||
// new block
|
||||
System::set_block_number(1);
|
||||
Session::check_rotate_session(System::block_number());
|
||||
start_era(1);
|
||||
|
||||
assert_eq_uvec!(Session::validators(), vec![20, 10]);
|
||||
});
|
||||
@@ -1666,35 +1622,44 @@ fn bond_with_no_staked_value() {
|
||||
assert_ok!(Staking::bond(Origin::signed(1), 2, 1, RewardDestination::Controller));
|
||||
assert_ok!(Staking::validate(Origin::signed(2), ValidatorPrefs::default()));
|
||||
|
||||
System::set_block_number(1);
|
||||
Session::check_rotate_session(System::block_number());
|
||||
start_era(1);
|
||||
|
||||
assert_eq_uvec!(Session::validators(), vec![30, 20, 10]);
|
||||
|
||||
// min of 10, 20 and 30 (30 got a payout into staking so it raised it from 1 to 11).
|
||||
assert_eq!(Staking::slot_stake(), 11);
|
||||
// min of 10, 20 and 30 (30 got a payout into staking so it raised it from 1 to 31).
|
||||
assert_eq!(Staking::slot_stake(), 31);
|
||||
|
||||
// make the stingy one elected.
|
||||
assert_ok!(Staking::bond(Origin::signed(3), 4, 500, RewardDestination::Controller));
|
||||
assert_ok!(Staking::nominate(Origin::signed(4), vec![1]));
|
||||
|
||||
System::set_block_number(2);
|
||||
Session::check_rotate_session(System::block_number());
|
||||
// no rewards paid to 2 and 4 yet
|
||||
assert_eq!(Balances::free_balance(&2), initial_balance_2);
|
||||
assert_eq!(Balances::free_balance(&4), initial_balance_4);
|
||||
|
||||
start_era(2);
|
||||
|
||||
// Stingy one is selected
|
||||
assert_eq_uvec!(Session::validators(), vec![20, 10, 2]);
|
||||
assert_eq!(Staking::stakers(1), Exposure { own: 1, total: 501, others: vec![IndividualExposure { who: 3, value: 500}]});
|
||||
assert_eq!(Staking::stakers(1), Exposure {
|
||||
own: 1,
|
||||
total: 501,
|
||||
others: vec![IndividualExposure { who: 3, value: 500}],
|
||||
});
|
||||
// New slot stake.
|
||||
assert_eq!(Staking::slot_stake(), 501);
|
||||
|
||||
System::set_block_number(3);
|
||||
Session::check_rotate_session(System::block_number());
|
||||
// no rewards paid to 2 and 4 yet
|
||||
assert_eq!(Balances::free_balance(&2), initial_balance_2);
|
||||
assert_eq!(Balances::free_balance(&4), initial_balance_4);
|
||||
|
||||
let reward = Staking::current_session_reward();
|
||||
start_era(3);
|
||||
|
||||
let reward = Staking::current_session_reward() * 3;
|
||||
// 2 will not get a reward of only 1
|
||||
// 4 will get the rest
|
||||
assert_eq!(Balances::free_balance(&2), initial_balance_2 + 1);
|
||||
assert_eq!(Balances::free_balance(&4), initial_balance_4 + reward - 1);
|
||||
assert_eq!(Balances::free_balance(&2), initial_balance_2 + 3);
|
||||
assert_eq!(Balances::free_balance(&4), initial_balance_4 + reward - 3);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1719,8 +1684,7 @@ fn bond_with_little_staked_value_bounded_by_slot_stake() {
|
||||
assert_ok!(Staking::bond(Origin::signed(1), 2, 1, RewardDestination::Controller));
|
||||
assert_ok!(Staking::validate(Origin::signed(2), ValidatorPrefs::default()));
|
||||
|
||||
System::set_block_number(1);
|
||||
Session::check_rotate_session(System::block_number());
|
||||
start_era(1);
|
||||
|
||||
// 2 is elected.
|
||||
// and fucks up the slot stake.
|
||||
@@ -1728,21 +1692,20 @@ fn bond_with_little_staked_value_bounded_by_slot_stake() {
|
||||
assert_eq!(Staking::slot_stake(), 1);
|
||||
|
||||
// Old ones are rewarded.
|
||||
assert_eq!(Balances::free_balance(&10), initial_balance_10 + 10);
|
||||
assert_eq!(Balances::free_balance(&10), initial_balance_10 + 30);
|
||||
// no rewards paid to 2. This was initial election.
|
||||
assert_eq!(Balances::free_balance(&2), initial_balance_2);
|
||||
|
||||
System::set_block_number(2);
|
||||
Session::check_rotate_session(System::block_number());
|
||||
start_era(2);
|
||||
|
||||
assert_eq_uvec!(Session::validators(), vec![20, 10, 2]);
|
||||
assert_eq!(Staking::slot_stake(), 1);
|
||||
|
||||
let reward = Staking::current_session_reward();
|
||||
// 2 will not get the full reward, practically 1
|
||||
assert_eq!(Balances::free_balance(&2), initial_balance_2 + reward.max(1));
|
||||
assert_eq!(Balances::free_balance(&2), initial_balance_2 + reward.max(3));
|
||||
// same for 10
|
||||
assert_eq!(Balances::free_balance(&10), initial_balance_10 + 10 + reward.max(1));
|
||||
assert_eq!(Balances::free_balance(&10), initial_balance_10 + 30 + reward.max(3));
|
||||
check_exposure_all();
|
||||
});
|
||||
}
|
||||
@@ -1775,7 +1738,7 @@ fn phragmen_linear_worse_case_equalize() {
|
||||
assert_ok!(Staking::set_validator_count(7));
|
||||
|
||||
System::set_block_number(1);
|
||||
Session::check_rotate_session(System::block_number());
|
||||
Session::on_initialize(System::block_number());
|
||||
|
||||
assert_eq_uvec!(Session::validators(), vec![10, 60, 40, 20, 50, 30, 70]);
|
||||
|
||||
@@ -1813,7 +1776,7 @@ fn phragmen_chooses_correct_number_of_validators() {
|
||||
assert_eq!(Session::validators().len(), 1);
|
||||
|
||||
System::set_block_number(1);
|
||||
Session::check_rotate_session(System::block_number());
|
||||
Session::on_initialize(System::block_number());
|
||||
|
||||
assert_eq!(Session::validators().len(), 1);
|
||||
check_exposure_all();
|
||||
@@ -1832,8 +1795,7 @@ fn phragmen_score_should_be_accurate_on_large_stakes() {
|
||||
bond_validator(6, u64::max_value()-1);
|
||||
bond_validator(8, u64::max_value()-2);
|
||||
|
||||
System::set_block_number(2);
|
||||
Session::check_rotate_session(System::block_number());
|
||||
start_era(1);
|
||||
|
||||
assert_eq!(Session::validators(), vec![4, 2]);
|
||||
check_exposure_all();
|
||||
@@ -1855,8 +1817,7 @@ fn phragmen_should_not_overflow_validators() {
|
||||
bond_nominator(6, u64::max_value()/2, vec![3, 5]);
|
||||
bond_nominator(8, u64::max_value()/2, vec![3, 5]);
|
||||
|
||||
System::set_block_number(2);
|
||||
Session::check_rotate_session(System::block_number());
|
||||
start_era(1);
|
||||
|
||||
assert_eq_uvec!(Session::validators(), vec![4, 2]);
|
||||
|
||||
@@ -1882,8 +1843,7 @@ fn phragmen_should_not_overflow_nominators() {
|
||||
bond_nominator(6, u64::max_value(), vec![3, 5]);
|
||||
bond_nominator(8, u64::max_value(), vec![3, 5]);
|
||||
|
||||
System::set_block_number(2);
|
||||
Session::check_rotate_session(System::block_number());
|
||||
start_era(1);
|
||||
|
||||
assert_eq_uvec!(Session::validators(), vec![4, 2]);
|
||||
|
||||
@@ -1905,8 +1865,7 @@ fn phragmen_should_not_overflow_ultimate() {
|
||||
bond_nominator(6, u64::max_value(), vec![3, 5]);
|
||||
bond_nominator(8, u64::max_value(), vec![3, 5]);
|
||||
|
||||
System::set_block_number(2);
|
||||
Session::check_rotate_session(System::block_number());
|
||||
start_era(1);
|
||||
|
||||
assert_eq_uvec!(Session::validators(), vec![4, 2]);
|
||||
|
||||
@@ -1958,8 +1917,7 @@ fn phragmen_large_scale_test() {
|
||||
prefix + 25]
|
||||
);
|
||||
|
||||
System::set_block_number(1);
|
||||
Session::check_rotate_session(System::block_number());
|
||||
start_era(1);
|
||||
|
||||
// For manual inspection
|
||||
println!("Validators are {:?}", Session::validators());
|
||||
@@ -2008,8 +1966,7 @@ fn phragmen_large_scale_test_2() {
|
||||
|
||||
bond_nominator(50, nom_budget, vec![3, 5]);
|
||||
|
||||
System::set_block_number(1);
|
||||
Session::check_rotate_session(System::block_number());
|
||||
start_era(1);
|
||||
|
||||
// Each exposure => total == own + sum(others)
|
||||
check_exposure_all();
|
||||
|
||||
@@ -55,7 +55,8 @@ use proc_macro::TokenStream;
|
||||
///
|
||||
/// `hasher($hash)` is optional and its default is `blake2_256`.
|
||||
///
|
||||
/// /!\ Be careful with each key in the map that is inserted in the trie `$hash(module_name ++ " " ++ storage_name ++ encoding(key))`.
|
||||
/// /!\ Be careful with each key in the map that is inserted in the trie
|
||||
/// `$hash(module_name ++ " " ++ storage_name ++ encoding(key))`.
|
||||
/// If the keys are not trusted (e.g. can be set by a user), a cryptographic `hasher` such as
|
||||
/// `blake2_256` must be used. Otherwise, other values in storage can be compromised.
|
||||
///
|
||||
|
||||
@@ -122,7 +122,11 @@ impl<'a, I: Iterator<Item=syn::Meta>> Impls<'a, I> {
|
||||
}
|
||||
|
||||
/// Mutate the value under a key.
|
||||
fn mutate<R, F: FnOnce(&mut Self::Query) -> R, S: #scrate::HashedStorage<#scrate::Twox128>>(f: F, storage: &mut S) -> R {
|
||||
fn mutate<R, F, S>(f: F, storage: &mut S) -> R
|
||||
where
|
||||
F: FnOnce(&mut Self::Query) -> R,
|
||||
S: #scrate::HashedStorage<#scrate::Twox128>,
|
||||
{
|
||||
let mut val = <Self as #scrate::storage::hashed::generator::StorageValue<#typ>>::get(storage);
|
||||
|
||||
let ret = f(&mut val);
|
||||
@@ -218,7 +222,11 @@ impl<'a, I: Iterator<Item=syn::Meta>> Impls<'a, I> {
|
||||
}
|
||||
|
||||
/// Mutate the value under a key
|
||||
fn mutate<R, F: FnOnce(&mut Self::Query) -> R, S: #scrate::HashedStorage<#scrate::#hasher>>(key: &#kty, f: F, storage: &mut S) -> R {
|
||||
fn mutate<R, F, S>(key: &#kty, f: F, storage: &mut S) -> R
|
||||
where
|
||||
F: FnOnce(&mut Self::Query) -> R,
|
||||
S: #scrate::HashedStorage<#scrate::#hasher>,
|
||||
{
|
||||
let mut val = #as_map::get(key, storage);
|
||||
|
||||
let ret = f(&mut val);
|
||||
@@ -355,7 +363,9 @@ impl<'a, I: Iterator<Item=syn::Meta>> Impls<'a, I> {
|
||||
fn remove_linkage<S: #scrate::HashedStorage<#scrate::#hasher>>(linkage: Linkage<#kty>, storage: &mut S);
|
||||
|
||||
/// Read the contained data and it's linkage.
|
||||
fn read_with_linkage<S: #scrate::HashedStorage<#scrate::#hasher>>(storage: &S, key: &[u8]) -> Option<(#value_type, Linkage<#kty>)>;
|
||||
fn read_with_linkage<S>(storage: &S, key: &[u8]) -> Option<(#value_type, Linkage<#kty>)>
|
||||
where
|
||||
S: #scrate::HashedStorage<#scrate::#hasher>;
|
||||
|
||||
/// Generate linkage for newly inserted element.
|
||||
///
|
||||
@@ -525,7 +535,11 @@ impl<'a, I: Iterator<Item=syn::Meta>> Impls<'a, I> {
|
||||
}
|
||||
|
||||
/// Mutate the value under a key
|
||||
fn mutate<R, F: FnOnce(&mut Self::Query) -> R, S: #scrate::HashedStorage<#scrate::#hasher>>(key: &#kty, f: F, storage: &mut S) -> R {
|
||||
fn mutate<R, F, S>(key: &#kty, f: F, storage: &mut S) -> R
|
||||
where
|
||||
F: FnOnce(&mut Self::Query) -> R,
|
||||
S: #scrate::HashedStorage<#scrate::#hasher>,
|
||||
{
|
||||
use self::#inner_module::Utils;
|
||||
|
||||
let key_for = &*#as_map::key_for(key);
|
||||
@@ -548,7 +562,9 @@ impl<'a, I: Iterator<Item=syn::Meta>> Impls<'a, I> {
|
||||
Self::read_head(storage)
|
||||
}
|
||||
|
||||
fn enumerate<'a, S: #scrate::HashedStorage<#scrate::#hasher>>(storage: &'a S) -> #scrate::rstd::boxed::Box<dyn Iterator<Item = (#kty, #typ)> + 'a> where
|
||||
fn enumerate<'a, S>(storage: &'a S) -> #scrate::rstd::boxed::Box<dyn Iterator<Item = (#kty, #typ)> + 'a>
|
||||
where
|
||||
S: #scrate::HashedStorage<#scrate::#hasher>,
|
||||
#kty: 'a,
|
||||
#typ: 'a,
|
||||
{
|
||||
@@ -564,7 +580,13 @@ impl<'a, I: Iterator<Item=syn::Meta>> Impls<'a, I> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn double_map(self, hasher: TokenStream2, k1ty: &syn::Type, k2ty: &syn::Type, k2_hasher: TokenStream2) -> TokenStream2 {
|
||||
pub fn double_map(
|
||||
self,
|
||||
hasher: TokenStream2,
|
||||
k1ty: &syn::Type,
|
||||
k2ty: &syn::Type,
|
||||
k2_hasher: TokenStream2,
|
||||
) -> TokenStream2 {
|
||||
let Self {
|
||||
scrate,
|
||||
visibility,
|
||||
@@ -653,7 +675,11 @@ impl<'a, I: Iterator<Item=syn::Meta>> Impls<'a, I> {
|
||||
storage.take(&key).#option_simple_1(|| #fielddefault)
|
||||
}
|
||||
|
||||
fn mutate<R, F: FnOnce(&mut Self::Query) -> R, S: #scrate::UnhashedStorage>(key1: &#k1ty, key2: &#k2ty, f: F, storage: &mut S) -> R {
|
||||
fn mutate<R, F, S>(key1: &#k1ty, key2: &#k2ty, f: F, storage: &mut S) -> R
|
||||
where
|
||||
F: FnOnce(&mut Self::Query) -> R,
|
||||
S: #scrate::UnhashedStorage,
|
||||
{
|
||||
let mut val = #as_double_map::get(key1, key2, storage);
|
||||
|
||||
let ret = f(&mut val);
|
||||
|
||||
@@ -750,6 +750,7 @@ macro_rules! decl_module {
|
||||
$vis:vis fn $name:ident ( root $(, $param:ident : $param_ty:ty )* ) { $( $impl:tt )* }
|
||||
) => {
|
||||
$(#[doc = $doc_attr])*
|
||||
#[allow(unreachable_code)]
|
||||
$vis fn $name($( $param: $param_ty ),* ) -> $crate::dispatch::Result {
|
||||
{ $( $impl )* }
|
||||
Ok(())
|
||||
|
||||
@@ -270,7 +270,8 @@ mod tests {
|
||||
pub GenericData get(generic_data): linked_map hasher(twox_128) T::BlockNumber => T::BlockNumber;
|
||||
pub GenericData2 get(generic_data2): linked_map T::BlockNumber => Option<T::BlockNumber>;
|
||||
|
||||
pub DataDM config(test_config) build(|_| vec![(15u32, 16u32, 42u64)]): double_map hasher(twox_64_concat) u32, blake2_256(u32) => u64;
|
||||
pub DataDM config(test_config) build(|_| vec![(15u32, 16u32, 42u64)]):
|
||||
double_map hasher(twox_64_concat) u32, blake2_256(u32) => u64;
|
||||
pub GenericDataDM: double_map T::BlockNumber, twox_128(T::BlockNumber) => T::BlockNumber;
|
||||
pub GenericData2DM: double_map T::BlockNumber, twox_256(T::BlockNumber) => Option<T::BlockNumber>;
|
||||
pub AppendableDM: double_map u32, blake2_256(T::BlockNumber) => Vec<u32>;
|
||||
@@ -440,7 +441,9 @@ mod tests {
|
||||
modifier: StorageFunctionModifier::Default,
|
||||
ty: StorageFunctionType::Map{
|
||||
hasher: StorageHasher::Twox128,
|
||||
key: DecodeDifferent::Encode("T::BlockNumber"), value: DecodeDifferent::Encode("T::BlockNumber"), is_linked: true
|
||||
key: DecodeDifferent::Encode("T::BlockNumber"),
|
||||
value: DecodeDifferent::Encode("T::BlockNumber"),
|
||||
is_linked: true
|
||||
},
|
||||
default: DecodeDifferent::Encode(
|
||||
DefaultByteGetter(&__GetByteStructGenericData(PhantomData::<Test>))
|
||||
@@ -452,7 +455,9 @@ mod tests {
|
||||
modifier: StorageFunctionModifier::Optional,
|
||||
ty: StorageFunctionType::Map{
|
||||
hasher: StorageHasher::Blake2_256,
|
||||
key: DecodeDifferent::Encode("T::BlockNumber"), value: DecodeDifferent::Encode("T::BlockNumber"), is_linked: true
|
||||
key: DecodeDifferent::Encode("T::BlockNumber"),
|
||||
value: DecodeDifferent::Encode("T::BlockNumber"),
|
||||
is_linked: true
|
||||
},
|
||||
default: DecodeDifferent::Encode(
|
||||
DefaultByteGetter(&__GetByteStructGenericData2(PhantomData::<Test>))
|
||||
|
||||
@@ -30,13 +30,13 @@
|
||||
///
|
||||
/// ```nocompile
|
||||
/// construct_runtime!(
|
||||
/// pub enum Runtime with Log(interalIdent: DigestItem<SessionKey>) where
|
||||
/// pub enum Runtime where
|
||||
/// Block = Block,
|
||||
/// NodeBlock = runtime::Block,
|
||||
/// UncheckedExtrinsic = UncheckedExtrinsic
|
||||
/// {
|
||||
/// System: system,
|
||||
/// Test: test::{default, Log(Test)},
|
||||
/// Test: test::{default},
|
||||
/// Test2: test_with_long_module::{Module},
|
||||
///
|
||||
/// // Module with instances
|
||||
@@ -50,8 +50,8 @@
|
||||
/// The identifier `System` is the name of the module and the lower case identifier `system` is the
|
||||
/// name of the Rust module/crate for this Substrate module.
|
||||
///
|
||||
/// The module `Test: test::{default, Log(Test)}` will expand to
|
||||
/// `Test: test::{Module, Call, Storage, Event<T>, Config<T>, Log(Test)}`.
|
||||
/// The module `Test: test::{default}` will expand to
|
||||
/// `Test: test::{Module, Call, Storage, Event<T>, Config<T>}`.
|
||||
///
|
||||
/// The module `Test2: test_with_long_module::{Module}` will expand to
|
||||
/// `Test2: test_with_long_module::{Module}`.
|
||||
@@ -64,7 +64,6 @@
|
||||
/// - `Event` or `Event<T>` (if the event is generic) or `Event<T, I>` (if also over instance)
|
||||
/// - `Origin` or `Origin<T>` (if the origin is generic) or `Origin<T, I>` (if also over instance)
|
||||
/// - `Config` or `Config<T>` (if the config is generic) or `Config<T, I>` (if also over instance)
|
||||
/// - `Log( $(IDENT),* )`
|
||||
/// - `Inherent $( (CALL) )*` - If the module provides/can check inherents. The optional parameter
|
||||
/// is for modules that use a `Call` from a different module as
|
||||
/// inherent.
|
||||
@@ -82,7 +81,7 @@ macro_rules! construct_runtime {
|
||||
// form)
|
||||
|
||||
(
|
||||
pub enum $runtime:ident with Log ($log_internal:ident: DigestItem<$( $log_genarg:ty ),+>)
|
||||
pub enum $runtime:ident
|
||||
where
|
||||
Block = $block:ident,
|
||||
NodeBlock = $node_block:ty,
|
||||
@@ -97,7 +96,6 @@ macro_rules! construct_runtime {
|
||||
$block;
|
||||
$node_block;
|
||||
$uncheckedextrinsic;
|
||||
$log_internal < $( $log_genarg ),* >;
|
||||
};
|
||||
{};
|
||||
$( $rest )*
|
||||
@@ -204,7 +202,6 @@ macro_rules! construct_runtime {
|
||||
$block:ident;
|
||||
$node_block:ty;
|
||||
$uncheckedextrinsic:ident;
|
||||
$log_internal:ident <$( $log_genarg:ty ),+>;
|
||||
};
|
||||
{
|
||||
$(
|
||||
@@ -264,14 +261,6 @@ macro_rules! construct_runtime {
|
||||
$name: $module:: $( < $module_instance >:: )? { $( $modules )* }
|
||||
)*
|
||||
);
|
||||
$crate::__decl_outer_log!(
|
||||
$runtime;
|
||||
$log_internal < $( $log_genarg ),* >;
|
||||
{};
|
||||
$(
|
||||
$name: $module:: $( < $module_instance >:: )? { $( $modules $( ( $( $modules_args )* ) )* )* }
|
||||
)*
|
||||
);
|
||||
$crate::__decl_outer_config!(
|
||||
$runtime;
|
||||
{};
|
||||
@@ -539,26 +528,6 @@ macro_rules! __decl_all_modules {
|
||||
#[macro_export]
|
||||
#[doc(hidden)]
|
||||
macro_rules! __decl_outer_dispatch {
|
||||
(
|
||||
$runtime:ident;
|
||||
$( $parsed_modules:ident :: $parsed_name:ident ),*;
|
||||
System: $module:ident::{
|
||||
$ignore:ident $( <$ignor:ident> )* $(, $modules:ident $( <$modules_generic:ident> )* )*
|
||||
}
|
||||
$(, $rest_name:ident : $rest_module:ident::{
|
||||
$( $rest_modules:ident $( <$rest_modules_generic:ident> )* ),*
|
||||
})*;
|
||||
) => {
|
||||
$crate::__decl_outer_dispatch!(
|
||||
$runtime;
|
||||
$( $parsed_modules :: $parsed_name ),*;
|
||||
$(
|
||||
$rest_name: $rest_module::{
|
||||
$( $rest_modules $( <$rest_modules_generic> )* ),*
|
||||
}
|
||||
),*;
|
||||
);
|
||||
};
|
||||
(
|
||||
$runtime:ident;
|
||||
$( $parsed_modules:ident :: $parsed_name:ident ),*;
|
||||
@@ -698,75 +667,6 @@ macro_rules! __decl_runtime_metadata {
|
||||
$( $parsed_modules::Module $( < $module_instance > )? with $( $withs )* , )*
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
/// A private macro that generates Log enum for the runtime. See impl_outer_log macro.
|
||||
#[macro_export]
|
||||
#[doc(hidden)]
|
||||
macro_rules! __decl_outer_log {
|
||||
(
|
||||
$runtime:ident;
|
||||
$log_internal:ident <$( $log_genarg:ty ),+>;
|
||||
{ $( $parsed:tt )* };
|
||||
$name:ident: $module:ident:: $(<$module_instance:ident>::)? {
|
||||
Log ( $( $args:ident )* ) $( $modules:ident $( ( $( $modules_args:ident )* ) )* )*
|
||||
}
|
||||
$( $rest:tt )*
|
||||
) => {
|
||||
$crate::__decl_outer_log!(
|
||||
$runtime;
|
||||
$log_internal < $( $log_genarg ),* >;
|
||||
{ $( $parsed )* $module $(<$module_instance>)? ( $( $args )* )};
|
||||
$( $rest )*
|
||||
);
|
||||
};
|
||||
(
|
||||
$runtime:ident;
|
||||
$log_internal:ident <$( $log_genarg:ty ),+>;
|
||||
{ $( $parsed:tt )* };
|
||||
$name:ident: $module:ident:: $(<$module_instance:ident>::)? {
|
||||
$ignore:ident $( ( $( $args_ignore:ident )* ) )*
|
||||
$( $modules:ident $( ( $( $modules_args:ident )* ) )* )*
|
||||
}
|
||||
$( $rest:tt )*
|
||||
) => {
|
||||
$crate::__decl_outer_log!(
|
||||
$runtime;
|
||||
$log_internal < $( $log_genarg ),* >;
|
||||
{ $( $parsed )* };
|
||||
$name: $module:: $(<$module_instance>::)? { $( $modules $( ( $( $modules_args )* ) )* )* }
|
||||
$( $rest )*
|
||||
);
|
||||
};
|
||||
(
|
||||
$runtime:ident;
|
||||
$log_internal:ident <$( $log_genarg:ty ),+>;
|
||||
{ $( $parsed:tt )* };
|
||||
$name:ident: $module:ident:: $(<$module_instance:ident>::)? {}
|
||||
$( $rest:tt )*
|
||||
) => {
|
||||
$crate::__decl_outer_log!(
|
||||
$runtime;
|
||||
$log_internal < $( $log_genarg ),* >;
|
||||
{ $( $parsed )* };
|
||||
$( $rest )*
|
||||
);
|
||||
};
|
||||
(
|
||||
$runtime:ident;
|
||||
$log_internal:ident <$( $log_genarg:ty ),+>;
|
||||
{ $(
|
||||
$parsed_modules:ident $(< $parsed_instance:ident >)? ( $( $parsed_args:ident )* )
|
||||
)* };
|
||||
) => {
|
||||
$crate::paste::item! {
|
||||
$crate::runtime_primitives::impl_outer_log!(
|
||||
pub enum Log($log_internal: DigestItem<$( $log_genarg ),*>) for $runtime {
|
||||
$( [< $parsed_modules $(_ $parsed_instance)? >] $(< $parsed_modules::$parsed_instance >)? ( $( $parsed_args ),* ) ),*
|
||||
}
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// A private macro that generates GenesisConfig for the runtime. See impl_outer_config macro.
|
||||
|
||||
@@ -24,73 +24,136 @@ use runtime_io::{self, twox_128};
|
||||
use crate::codec::{Codec, Encode, Decode, KeyedVec};
|
||||
|
||||
/// Return the value of the item in storage under `key`, or `None` if there is no explicit entry.
|
||||
pub fn get<T: Decode + Sized, HashFn: Fn(&[u8]) -> R, R: AsRef<[u8]>>(hash: &HashFn, key: &[u8]) -> Option<T> {
|
||||
pub fn get<T, HashFn, R>(hash: &HashFn, key: &[u8]) -> Option<T>
|
||||
where
|
||||
T: Decode + Sized,
|
||||
HashFn: Fn(&[u8]) -> R,
|
||||
R: AsRef<[u8]>,
|
||||
{
|
||||
unhashed::get(&hash(key).as_ref())
|
||||
}
|
||||
|
||||
/// Return the value of the item in storage under `key`, or the type's default if there is no
|
||||
/// explicit entry.
|
||||
pub fn get_or_default<T: Decode + Sized + Default, HashFn: Fn(&[u8]) -> R, R: AsRef<[u8]>>(hash: &HashFn, key: &[u8]) -> T {
|
||||
pub fn get_or_default<T, HashFn, R>(hash: &HashFn, key: &[u8]) -> T
|
||||
where
|
||||
T: Decode + Sized + Default,
|
||||
HashFn: Fn(&[u8]) -> R,
|
||||
R: AsRef<[u8]>,
|
||||
{
|
||||
unhashed::get_or_default(&hash(key).as_ref())
|
||||
}
|
||||
|
||||
/// Return the value of the item in storage under `key`, or `default_value` if there is no
|
||||
/// explicit entry.
|
||||
pub fn get_or<T: Decode + Sized, HashFn: Fn(&[u8]) -> R, R: AsRef<[u8]>>(hash: &HashFn, key: &[u8], default_value: T) -> T {
|
||||
pub fn get_or<T, HashFn, R>(hash: &HashFn, key: &[u8], default_value: T) -> T
|
||||
where
|
||||
T: Decode + Sized,
|
||||
HashFn: Fn(&[u8]) -> R,
|
||||
R: AsRef<[u8]>,
|
||||
{
|
||||
unhashed::get_or(&hash(key).as_ref(), default_value)
|
||||
}
|
||||
|
||||
/// Return the value of the item in storage under `key`, or `default_value()` if there is no
|
||||
/// explicit entry.
|
||||
pub fn get_or_else<T: Decode + Sized, F: FnOnce() -> T, HashFn: Fn(&[u8]) -> R, R: AsRef<[u8]>>(hash: &HashFn, key: &[u8], default_value: F) -> T {
|
||||
pub fn get_or_else<T, F, HashFn, R>(hash: &HashFn, key: &[u8], default_value: F) -> T
|
||||
where
|
||||
T: Decode + Sized,
|
||||
F: FnOnce() -> T,
|
||||
HashFn: Fn(&[u8]) -> R,
|
||||
R: AsRef<[u8]>,
|
||||
{
|
||||
unhashed::get_or_else(&hash(key).as_ref(), default_value)
|
||||
}
|
||||
|
||||
/// Put `value` in storage under `key`.
|
||||
pub fn put<T: Encode, HashFn: Fn(&[u8]) -> R, R: AsRef<[u8]>>(hash: &HashFn, key: &[u8], value: &T) {
|
||||
pub fn put<T, HashFn, R>(hash: &HashFn, key: &[u8], value: &T)
|
||||
where
|
||||
T: Encode,
|
||||
HashFn: Fn(&[u8]) -> R,
|
||||
R: AsRef<[u8]>,
|
||||
{
|
||||
unhashed::put(&hash(key).as_ref(), value)
|
||||
}
|
||||
|
||||
/// Remove `key` from storage, returning its value if it had an explicit entry or `None` otherwise.
|
||||
pub fn take<T: Decode + Sized, HashFn: Fn(&[u8]) -> R, R: AsRef<[u8]>>(hash: &HashFn, key: &[u8]) -> Option<T> {
|
||||
pub fn take<T, HashFn, R>(hash: &HashFn, key: &[u8]) -> Option<T>
|
||||
where
|
||||
T: Decode + Sized,
|
||||
HashFn: Fn(&[u8]) -> R,
|
||||
R: AsRef<[u8]>,
|
||||
{
|
||||
unhashed::take(&hash(key).as_ref())
|
||||
}
|
||||
|
||||
/// Remove `key` from storage, returning its value, or, if there was no explicit entry in storage,
|
||||
/// the default for its type.
|
||||
pub fn take_or_default<T: Decode + Sized + Default, HashFn: Fn(&[u8]) -> R, R: AsRef<[u8]>>(hash: &HashFn, key: &[u8]) -> T {
|
||||
pub fn take_or_default<T, HashFn, R>(hash: &HashFn, key: &[u8]) -> T
|
||||
where
|
||||
T: Decode + Sized + Default,
|
||||
HashFn: Fn(&[u8]) -> R,
|
||||
R: AsRef<[u8]>,
|
||||
{
|
||||
unhashed::take_or_default(&hash(key).as_ref())
|
||||
}
|
||||
|
||||
/// Return the value of the item in storage under `key`, or `default_value` if there is no
|
||||
/// explicit entry. Ensure there is no explicit entry on return.
|
||||
pub fn take_or<T: Decode + Sized, HashFn: Fn(&[u8]) -> R, R: AsRef<[u8]>>(hash: &HashFn, key: &[u8], default_value: T) -> T {
|
||||
pub fn take_or<T, HashFn, R>(hash: &HashFn, key: &[u8], default_value: T) -> T
|
||||
where
|
||||
T: Decode + Sized,
|
||||
HashFn: Fn(&[u8]) -> R,
|
||||
R: AsRef<[u8]>,
|
||||
{
|
||||
unhashed::take_or(&hash(key).as_ref(), default_value)
|
||||
}
|
||||
|
||||
/// Return the value of the item in storage under `key`, or `default_value()` if there is no
|
||||
/// explicit entry. Ensure there is no explicit entry on return.
|
||||
pub fn take_or_else<T: Decode + Sized, F: FnOnce() -> T, HashFn: Fn(&[u8]) -> R, R: AsRef<[u8]>>(hash: &HashFn, key: &[u8], default_value: F) -> T {
|
||||
pub fn take_or_else<T, F, HashFn, R>(hash: &HashFn, key: &[u8], default_value: F) -> T
|
||||
where
|
||||
T: Decode + Sized,
|
||||
F: FnOnce() -> T,
|
||||
HashFn: Fn(&[u8]) -> R,
|
||||
R: AsRef<[u8]>,
|
||||
{
|
||||
unhashed::take_or_else(&hash(key).as_ref(), default_value)
|
||||
}
|
||||
|
||||
/// Check to see if `key` has an explicit entry in storage.
|
||||
pub fn exists<HashFn: Fn(&[u8]) -> R, R: AsRef<[u8]>>(hash: &HashFn, key: &[u8]) -> bool {
|
||||
pub fn exists<HashFn, R>(hash: &HashFn, key: &[u8]) -> bool
|
||||
where
|
||||
HashFn: Fn(&[u8]) -> R,
|
||||
R: AsRef<[u8]>,
|
||||
{
|
||||
unhashed::exists(&hash(key).as_ref())
|
||||
}
|
||||
|
||||
/// Ensure `key` has no explicit entry in storage.
|
||||
pub fn kill<HashFn: Fn(&[u8]) -> R, R: AsRef<[u8]>>(hash: &HashFn, key: &[u8]) {
|
||||
pub fn kill<HashFn, R>(hash: &HashFn, key: &[u8])
|
||||
where
|
||||
HashFn: Fn(&[u8]) -> R,
|
||||
R: AsRef<[u8]>,
|
||||
{
|
||||
unhashed::kill(&hash(key).as_ref())
|
||||
}
|
||||
|
||||
/// Get a Vec of bytes from storage.
|
||||
pub fn get_raw<HashFn: Fn(&[u8]) -> R, R: AsRef<[u8]>>(hash: &HashFn, key: &[u8]) -> Option<Vec<u8>> {
|
||||
pub fn get_raw<HashFn, R>(hash: &HashFn, key: &[u8]) -> Option<Vec<u8>>
|
||||
where
|
||||
HashFn: Fn(&[u8]) -> R,
|
||||
R: AsRef<[u8]>,
|
||||
{
|
||||
unhashed::get_raw(&hash(key).as_ref())
|
||||
}
|
||||
|
||||
/// Put a raw byte slice into storage.
|
||||
pub fn put_raw<HashFn: Fn(&[u8]) -> R, R: AsRef<[u8]>>(hash: &HashFn, key: &[u8], value: &[u8]) {
|
||||
pub fn put_raw<HashFn, R>(hash: &HashFn, key: &[u8], value: &[u8])
|
||||
where
|
||||
HashFn: Fn(&[u8]) -> R,
|
||||
R: AsRef<[u8]>,
|
||||
{
|
||||
unhashed::put_raw(&hash(key).as_ref(), value)
|
||||
}
|
||||
|
||||
|
||||
@@ -27,11 +27,15 @@ pub trait UnhashedStorage {
|
||||
|
||||
/// Load the bytes of a key from storage. Can panic if the type is incorrect. Will panic if
|
||||
/// it's not there.
|
||||
fn require<T: codec::Decode>(&self, key: &[u8]) -> T { self.get(key).expect("Required values must be in storage") }
|
||||
fn require<T: codec::Decode>(&self, key: &[u8]) -> T {
|
||||
self.get(key).expect("Required values must be in storage")
|
||||
}
|
||||
|
||||
/// Load the bytes of a key from storage. Can panic if the type is incorrect. The type's
|
||||
/// default is returned if it's not there.
|
||||
fn get_or_default<T: codec::Decode + Default>(&self, key: &[u8]) -> T { self.get(key).unwrap_or_default() }
|
||||
fn get_or_default<T: codec::Decode + Default>(&self, key: &[u8]) -> T {
|
||||
self.get(key).unwrap_or_default()
|
||||
}
|
||||
|
||||
/// Put a value in under a key.
|
||||
fn put<T: codec::Encode>(&mut self, key: &[u8], val: &T);
|
||||
@@ -50,10 +54,14 @@ pub trait UnhashedStorage {
|
||||
}
|
||||
|
||||
/// Take a value from storage, deleting it after reading.
|
||||
fn take_or_panic<T: codec::Decode>(&mut self, key: &[u8]) -> T { self.take(key).expect("Required values must be in storage") }
|
||||
fn take_or_panic<T: codec::Decode>(&mut self, key: &[u8]) -> T {
|
||||
self.take(key).expect("Required values must be in storage")
|
||||
}
|
||||
|
||||
/// Take a value from storage, deleting it after reading.
|
||||
fn take_or_default<T: codec::Decode + Default>(&mut self, key: &[u8]) -> T { self.take(key).unwrap_or_default() }
|
||||
fn take_or_default<T: codec::Decode + Default>(&mut self, key: &[u8]) -> T {
|
||||
self.take(key).unwrap_or_default()
|
||||
}
|
||||
|
||||
/// Get a Vec of bytes from storage.
|
||||
fn get_raw(&self, key: &[u8]) -> Option<Vec<u8>>;
|
||||
|
||||
@@ -16,14 +16,11 @@
|
||||
|
||||
#![recursion_limit="128"]
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
use serde::Serialize;
|
||||
use runtime_io::{with_externalities, Blake2Hasher};
|
||||
use srml_support::rstd::prelude::*;
|
||||
use srml_support::rstd as rstd;
|
||||
use srml_support::codec::{Encode, Decode};
|
||||
use srml_support::runtime_primitives::{generic, BuildStorage};
|
||||
use srml_support::runtime_primitives::traits::{BlakeTwo256, Block as _, Verify, Digest};
|
||||
use srml_support::runtime_primitives::traits::{BlakeTwo256, Block as _, Verify};
|
||||
use srml_support::Parameter;
|
||||
use inherents::{
|
||||
ProvideInherent, InherentData, InherentIdentifier, RuntimeString, MakeFatalError
|
||||
@@ -42,14 +39,12 @@ mod system {
|
||||
type Origin: Into<Result<RawOrigin<Self::AccountId>, Self::Origin>>
|
||||
+ From<RawOrigin<Self::AccountId>>;
|
||||
type BlockNumber;
|
||||
type Digest: Digest<Hash = H256>;
|
||||
type Hash;
|
||||
type AccountId;
|
||||
type Event: From<Event>;
|
||||
type Log: From<Log<Self>> + Into<DigestItemOf<Self>>;
|
||||
}
|
||||
|
||||
pub type DigestItemOf<T> = <<T as Trait>::Digest as Digest>::Item;
|
||||
pub type DigestItemOf<T> = generic::DigestItem<<T as Trait>::Hash>;
|
||||
|
||||
srml_support::decl_module! {
|
||||
pub struct Module<T: Trait> for enum Call where origin: T::Origin {
|
||||
@@ -58,7 +53,7 @@ mod system {
|
||||
}
|
||||
}
|
||||
impl<T: Trait> Module<T> {
|
||||
pub fn deposit_log(_item: <T::Digest as Digest>::Item) {
|
||||
pub fn deposit_log(_item: DigestItemOf<T>) {
|
||||
unimplemented!();
|
||||
}
|
||||
}
|
||||
@@ -90,16 +85,6 @@ mod system {
|
||||
|
||||
pub type Origin<T> = RawOrigin<<T as Trait>::AccountId>;
|
||||
|
||||
pub type Log<T> = RawLog<
|
||||
<T as Trait>::Hash,
|
||||
>;
|
||||
|
||||
#[cfg_attr(feature = "std", derive(Serialize, Debug))]
|
||||
#[derive(Encode, Decode, PartialEq, Eq, Clone)]
|
||||
pub enum RawLog<H> {
|
||||
ChangesTrieRoot(H),
|
||||
}
|
||||
|
||||
pub fn ensure_root<OuterOrigin, AccountId>(o: OuterOrigin) -> Result<(), &'static str>
|
||||
where OuterOrigin: Into<Result<RawOrigin<AccountId>, OuterOrigin>>
|
||||
{
|
||||
@@ -110,14 +95,13 @@ mod system {
|
||||
// Test for:
|
||||
// * No default instance
|
||||
// * Custom InstantiableTrait
|
||||
// * Origin, Inherent, Log, Event
|
||||
// * Origin, Inherent, Event
|
||||
mod module1 {
|
||||
use super::*;
|
||||
|
||||
pub trait Trait<I>: system::Trait {
|
||||
type Event: From<Event<Self, I>> + Into<<Self as system::Trait>::Event>;
|
||||
type Origin: From<Origin<Self, I>>;
|
||||
type Log: From<Log<Self, I>> + Into<system::DigestItemOf<Self>>;
|
||||
}
|
||||
|
||||
srml_support::decl_module! {
|
||||
@@ -126,18 +110,10 @@ mod module1 {
|
||||
|
||||
fn one() {
|
||||
Self::deposit_event(RawEvent::AnotherVariant(3));
|
||||
Self::deposit_log(RawLog::AmountChange(3));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Trait<I>, I: InstantiableThing> Module<T, I> {
|
||||
/// Deposit one of this module's logs.
|
||||
fn deposit_log(log: Log<T, I>) {
|
||||
<system::Module<T>>::deposit_log(<T as Trait<I>>::Log::from(log).into());
|
||||
}
|
||||
}
|
||||
|
||||
srml_support::decl_storage! {
|
||||
trait Store for Module<T: Trait<I>, I: InstantiableThing> as Module1 {
|
||||
pub Value config(value): u64;
|
||||
@@ -160,22 +136,6 @@ mod module1 {
|
||||
_Phantom(rstd::marker::PhantomData<(T, I)>),
|
||||
}
|
||||
|
||||
pub type Log<T, I> = RawLog<
|
||||
T,
|
||||
I,
|
||||
<T as system::Trait>::Hash,
|
||||
>;
|
||||
|
||||
/// A logs in this module.
|
||||
#[cfg_attr(feature = "std", derive(serde::Serialize, Debug))]
|
||||
#[derive(parity_codec::Encode, parity_codec::Decode, PartialEq, Eq, Clone)]
|
||||
pub enum RawLog<T, I, Hash> {
|
||||
_Phantom(rstd::marker::PhantomData<(T, I)>),
|
||||
AmountChange(u32),
|
||||
ChangesTrieRoot(Hash),
|
||||
AuthoritiesChange(Vec<()>),
|
||||
}
|
||||
|
||||
pub const INHERENT_IDENTIFIER: InherentIdentifier = *b"12345678";
|
||||
|
||||
impl<T: Trait<I>, I: InstantiableThing> ProvideInherent for Module<T, I> {
|
||||
@@ -203,7 +163,6 @@ mod module2 {
|
||||
type Amount: Parameter + Default;
|
||||
type Event: From<Event<Self, I>> + Into<<Self as system::Trait>::Event>;
|
||||
type Origin: From<Origin<Self, I>>;
|
||||
type Log: From<Log<Self, I>> + Into<system::DigestItemOf<Self>>;
|
||||
}
|
||||
|
||||
impl<T: Trait<I>, I: Instance> Currency for Module<T, I> {}
|
||||
@@ -237,19 +196,6 @@ mod module2 {
|
||||
_Phantom(rstd::marker::PhantomData<(T, I)>),
|
||||
}
|
||||
|
||||
pub type Log<T, I=DefaultInstance> = RawLog<
|
||||
T,
|
||||
I,
|
||||
>;
|
||||
|
||||
/// A logs in this module.
|
||||
#[cfg_attr(feature = "std", derive(serde::Serialize, Debug))]
|
||||
#[derive(parity_codec::Encode, parity_codec::Decode, PartialEq, Eq, Clone)]
|
||||
pub enum RawLog<T, I=DefaultInstance> {
|
||||
_Phantom(rstd::marker::PhantomData<(T, I)>),
|
||||
AmountChange(u32),
|
||||
}
|
||||
|
||||
pub const INHERENT_IDENTIFIER: InherentIdentifier = *b"12345678";
|
||||
|
||||
impl<T: Trait<I>, I: Instance> ProvideInherent for Module<T, I> {
|
||||
@@ -286,36 +232,30 @@ mod module3 {
|
||||
impl module1::Trait<module1::Instance1> for Runtime {
|
||||
type Event = Event;
|
||||
type Origin = Origin;
|
||||
type Log = Log;
|
||||
}
|
||||
impl module1::Trait<module1::Instance2> for Runtime {
|
||||
type Event = Event;
|
||||
type Origin = Origin;
|
||||
type Log = Log;
|
||||
}
|
||||
impl module2::Trait for Runtime {
|
||||
type Amount = u16;
|
||||
type Event = Event;
|
||||
type Origin = Origin;
|
||||
type Log = Log;
|
||||
}
|
||||
impl module2::Trait<module2::Instance1> for Runtime {
|
||||
type Amount = u32;
|
||||
type Event = Event;
|
||||
type Origin = Origin;
|
||||
type Log = Log;
|
||||
}
|
||||
impl module2::Trait<module2::Instance2> for Runtime {
|
||||
type Amount = u32;
|
||||
type Event = Event;
|
||||
type Origin = Origin;
|
||||
type Log = Log;
|
||||
}
|
||||
impl module2::Trait<module2::Instance3> for Runtime {
|
||||
type Amount = u64;
|
||||
type Event = Event;
|
||||
type Origin = Origin;
|
||||
type Log = Log;
|
||||
}
|
||||
impl module3::Trait for Runtime {
|
||||
type Currency = Module2_2;
|
||||
@@ -331,30 +271,28 @@ impl system::Trait for Runtime {
|
||||
type Hash = H256;
|
||||
type Origin = Origin;
|
||||
type BlockNumber = BlockNumber;
|
||||
type Digest = generic::Digest<Log>;
|
||||
type AccountId = AccountId;
|
||||
type Event = Event;
|
||||
type Log = Log;
|
||||
}
|
||||
|
||||
srml_support::construct_runtime!(
|
||||
pub enum Runtime with Log(InternalLog: DigestItem<H256, (), ()>) where
|
||||
pub enum Runtime where
|
||||
Block = Block,
|
||||
NodeBlock = Block,
|
||||
UncheckedExtrinsic = UncheckedExtrinsic
|
||||
{
|
||||
System: system::{Module, Call, Event, Log(ChangesTrieRoot)},
|
||||
Module1_1: module1::<Instance1>::{Module, Call, Storage, Event<T, I>, Config<T, I>, Origin<T, I>, Log(ChangesTrieRoot, AuthoritiesChange), Inherent},
|
||||
Module1_2: module1::<Instance2>::{Module, Call, Storage, Event<T, I>, Config<T, I>, Origin<T, I>, Log(ChangesTrieRoot, AuthoritiesChange), Inherent},
|
||||
Module2: module2::{Module, Call, Storage, Event<T>, Config<T>, Origin<T>, Log(), Inherent},
|
||||
Module2_1: module2::<Instance1>::{Module, Call, Storage, Event<T, I>, Config<T, I>, Origin<T, I>, Log(), Inherent},
|
||||
Module2_2: module2::<Instance2>::{Module, Call, Storage, Event<T, I>, Config<T, I>, Origin<T, I>, Log(), Inherent},
|
||||
Module2_3: module2::<Instance3>::{Module, Call, Storage, Event<T, I>, Config<T, I>, Origin<T, I>, Log(), Inherent},
|
||||
System: system::{Module, Call, Event},
|
||||
Module1_1: module1::<Instance1>::{Module, Call, Storage, Event<T, I>, Config<T, I>, Origin<T, I>, Inherent},
|
||||
Module1_2: module1::<Instance2>::{Module, Call, Storage, Event<T, I>, Config<T, I>, Origin<T, I>, Inherent},
|
||||
Module2: module2::{Module, Call, Storage, Event<T>, Config<T>, Origin<T>, Inherent},
|
||||
Module2_1: module2::<Instance1>::{Module, Call, Storage, Event<T, I>, Config<T, I>, Origin<T, I>, Inherent},
|
||||
Module2_2: module2::<Instance2>::{Module, Call, Storage, Event<T, I>, Config<T, I>, Origin<T, I>, Inherent},
|
||||
Module2_3: module2::<Instance3>::{Module, Call, Storage, Event<T, I>, Config<T, I>, Origin<T, I>, Inherent},
|
||||
Module3: module3::{Module, Call},
|
||||
}
|
||||
);
|
||||
|
||||
pub type Header = generic::Header<BlockNumber, BlakeTwo256, Log>;
|
||||
pub type Header = generic::Header<BlockNumber, BlakeTwo256>;
|
||||
pub type Block = generic::Block<Header, UncheckedExtrinsic>;
|
||||
pub type UncheckedExtrinsic = generic::UncheckedMortalCompactExtrinsic<u32, Index, Call, Signature>;
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ use runtime_io::{with_externalities, Blake2Hasher};
|
||||
use substrate_primitives::H256;
|
||||
use primitives::{
|
||||
BuildStorage, traits::{BlakeTwo256, IdentityLookup},
|
||||
testing::{Digest, DigestItem, Header},
|
||||
testing::Header,
|
||||
};
|
||||
|
||||
mod module {
|
||||
@@ -62,12 +62,10 @@ impl system::Trait for Runtime {
|
||||
type BlockNumber = u64;
|
||||
type Hash = H256;
|
||||
type Hashing = BlakeTwo256;
|
||||
type Digest = Digest;
|
||||
type AccountId = u64;
|
||||
type Lookup = IdentityLookup<Self::AccountId>;
|
||||
type Header = Header;
|
||||
type Event = Event;
|
||||
type Log = DigestItem;
|
||||
}
|
||||
|
||||
impl module::Trait for Runtime {
|
||||
|
||||
@@ -76,10 +76,10 @@ use serde::Serialize;
|
||||
use rstd::prelude::*;
|
||||
#[cfg(any(feature = "std", test))]
|
||||
use rstd::map;
|
||||
use primitives::traits::{self, CheckEqual, SimpleArithmetic, SimpleBitOps, One, Bounded, Lookup,
|
||||
Hash, Member, MaybeDisplay, EnsureOrigin, Digest as DigestT, CurrentHeight, BlockNumberToHash,
|
||||
MaybeSerializeDebugButNotDeserialize, MaybeSerializeDebug, StaticLookup
|
||||
};
|
||||
use primitives::{generic, traits::{self, CheckEqual, SimpleArithmetic,
|
||||
SimpleBitOps, Hash, Member, MaybeDisplay, EnsureOrigin, CurrentHeight, BlockNumberToHash,
|
||||
MaybeSerializeDebugButNotDeserialize, MaybeSerializeDebug, StaticLookup, One, Bounded, Lookup,
|
||||
}};
|
||||
#[cfg(any(feature = "std", test))]
|
||||
use primitives::traits::Zero;
|
||||
use substrate_primitives::storage::well_known_keys;
|
||||
@@ -89,6 +89,7 @@ use srml_support::{
|
||||
};
|
||||
use safe_mix::TripletMix;
|
||||
use parity_codec::{Encode, Decode};
|
||||
use crate::{self as system};
|
||||
|
||||
#[cfg(any(feature = "std", test))]
|
||||
use runtime_io::{twox_128, TestExternalities, Blake2Hasher};
|
||||
@@ -165,10 +166,6 @@ pub trait Trait: 'static + Eq + Clone {
|
||||
/// The hashing system (algorithm) being used in the runtime (e.g. Blake2).
|
||||
type Hashing: Hash<Output = Self::Hash>;
|
||||
|
||||
/// Collection of (light-client-relevant) logs for a block to be included verbatim in the block header.
|
||||
type Digest:
|
||||
Parameter + Member + MaybeSerializeDebugButNotDeserialize + Default + traits::Digest<Hash = Self::Hash>;
|
||||
|
||||
/// The user account identifier type for the runtime.
|
||||
type AccountId: Parameter + Member + MaybeSerializeDebug + MaybeDisplay + Ord + Default;
|
||||
|
||||
@@ -183,17 +180,17 @@ pub trait Trait: 'static + Eq + Clone {
|
||||
type Header: Parameter + traits::Header<
|
||||
Number = Self::BlockNumber,
|
||||
Hash = Self::Hash,
|
||||
Digest = Self::Digest
|
||||
>;
|
||||
|
||||
/// The aggregated event type of the runtime.
|
||||
type Event: Parameter + Member + From<Event>;
|
||||
|
||||
/// A piece of information that can be part of the digest (as a digest item).
|
||||
type Log: From<Log<Self>> + Into<DigestItemOf<Self>>;
|
||||
}
|
||||
|
||||
pub type DigestItemOf<T> = <<T as Trait>::Digest as traits::Digest>::Item;
|
||||
pub type DigestOf<T> = generic::Digest<<T as Trait>::Hash>;
|
||||
pub type DigestItemOf<T> = generic::DigestItem<<T as Trait>::Hash>;
|
||||
|
||||
pub type Key = Vec<u8>;
|
||||
pub type KeyValue = (Vec<u8>, Vec<u8>);
|
||||
|
||||
decl_module! {
|
||||
pub struct Module<T: Trait> for enum Call where origin: T::Origin {
|
||||
@@ -201,6 +198,35 @@ decl_module! {
|
||||
pub fn deposit_event(event: T::Event) {
|
||||
Self::deposit_event_indexed(&[], event);
|
||||
}
|
||||
|
||||
/// Make some on-chain remark.
|
||||
fn remark(origin, _remark: Vec<u8>) {
|
||||
ensure_signed(origin)?;
|
||||
}
|
||||
|
||||
/// Set the number of pages in the WebAssembly environment's heap.
|
||||
fn set_heap_pages(pages: u64) {
|
||||
storage::unhashed::put_raw(well_known_keys::HEAP_PAGES, &pages.encode());
|
||||
}
|
||||
|
||||
/// Set the new code.
|
||||
pub fn set_code(new: Vec<u8>) {
|
||||
storage::unhashed::put_raw(well_known_keys::CODE, &new);
|
||||
}
|
||||
|
||||
/// Set some items of storage.
|
||||
fn set_storage(items: Vec<KeyValue>) {
|
||||
for i in &items {
|
||||
storage::unhashed::put_raw(&i.0, &i.1);
|
||||
}
|
||||
}
|
||||
|
||||
/// Kill some items from storage.
|
||||
fn kill_storage(keys: Vec<Key>) {
|
||||
for key in &keys {
|
||||
storage::unhashed::kill(&key);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -262,38 +288,6 @@ impl<AccountId> From<Option<AccountId>> for RawOrigin<AccountId> {
|
||||
/// Exposed trait-generic origin type.
|
||||
pub type Origin<T> = RawOrigin<<T as Trait>::AccountId>;
|
||||
|
||||
pub type Log<T> = RawLog<
|
||||
<T as Trait>::Hash,
|
||||
>;
|
||||
|
||||
/// A log in this module.
|
||||
#[cfg_attr(feature = "std", derive(Serialize, Debug))]
|
||||
#[derive(Encode, Decode, PartialEq, Eq, Clone)]
|
||||
pub enum RawLog<Hash> {
|
||||
/// Changes trie has been computed for this block. Contains the root of
|
||||
/// changes trie.
|
||||
ChangesTrieRoot(Hash),
|
||||
}
|
||||
|
||||
impl<Hash: Member> RawLog<Hash> {
|
||||
/// Try to cast the log entry as ChangesTrieRoot log entry.
|
||||
pub fn as_changes_trie_root(&self) -> Option<&Hash> {
|
||||
match *self {
|
||||
RawLog::ChangesTrieRoot(ref item) => Some(item),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Implementation for tests outside of this crate.
|
||||
#[cfg(any(feature = "std", test))]
|
||||
impl From<RawLog<substrate_primitives::H256>> for primitives::testing::DigestItem {
|
||||
fn from(log: RawLog<substrate_primitives::H256>) -> primitives::testing::DigestItem {
|
||||
match log {
|
||||
RawLog::ChangesTrieRoot(root) => primitives::generic::DigestItem::ChangesTrieRoot(root),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Create a Hash with 69 for each byte,
|
||||
// only used to build genesis config.
|
||||
#[cfg(feature = "std")]
|
||||
@@ -331,7 +325,7 @@ decl_storage! {
|
||||
/// Extrinsics root of the current block, also part of the block header.
|
||||
ExtrinsicsRoot get(extrinsics_root): T::Hash;
|
||||
/// Digest of the current block, also part of the block header.
|
||||
Digest get(digest): T::Digest;
|
||||
Digest get(digest): DigestOf<T>;
|
||||
/// Events deposited for the current block.
|
||||
Events get(events): Vec<EventRecord<T::Event, T::Hash>>;
|
||||
/// The number of events in the `Events<T>` list.
|
||||
@@ -360,10 +354,13 @@ decl_storage! {
|
||||
}
|
||||
add_extra_genesis {
|
||||
config(changes_trie_config): Option<ChangesTrieConfiguration>;
|
||||
#[serde(with = "substrate_primitives::bytes")]
|
||||
config(code): Vec<u8>;
|
||||
|
||||
build(|storage: &mut primitives::StorageOverlay, _: &mut primitives::ChildrenStorageOverlay, config: &GenesisConfig<T>| {
|
||||
use parity_codec::Encode;
|
||||
|
||||
storage.insert(well_known_keys::CODE.to_vec(), config.code.clone());
|
||||
storage.insert(well_known_keys::EXTRINSIC_INDEX.to_vec(), 0u32.encode());
|
||||
|
||||
if let Some(ref changes_trie_config) = config.changes_trie_config {
|
||||
@@ -540,7 +537,7 @@ impl<T: Trait> Module<T> {
|
||||
number: &T::BlockNumber,
|
||||
parent_hash: &T::Hash,
|
||||
txs_root: &T::Hash,
|
||||
digest: &T::Digest,
|
||||
digest: &DigestOf<T>,
|
||||
) {
|
||||
// populate environment
|
||||
storage::unhashed::put(well_known_keys::EXTRINSIC_INDEX, &0u32);
|
||||
@@ -575,8 +572,7 @@ impl<T: Trait> Module<T> {
|
||||
// we can't compute changes trie root earlier && put it to the Digest
|
||||
// because it will include all currently existing temporaries.
|
||||
if let Some(storage_changes_root) = storage_changes_root {
|
||||
let item = RawLog::ChangesTrieRoot(storage_changes_root);
|
||||
let item = <T as Trait>::Log::from(item).into();
|
||||
let item = generic::DigestItem::ChangesTrieRoot(storage_changes_root);
|
||||
digest.push(item);
|
||||
}
|
||||
|
||||
@@ -592,9 +588,9 @@ impl<T: Trait> Module<T> {
|
||||
}
|
||||
|
||||
/// Deposits a log and ensures it matches the block's log data.
|
||||
pub fn deposit_log(item: <T::Digest as traits::Digest>::Item) {
|
||||
pub fn deposit_log(item: DigestItemOf<T>) {
|
||||
let mut l = <Digest<T>>::get();
|
||||
traits::Digest::push(&mut l, item);
|
||||
l.push(item);
|
||||
<Digest<T>>::put(l);
|
||||
}
|
||||
|
||||
@@ -772,7 +768,7 @@ mod tests {
|
||||
use substrate_primitives::H256;
|
||||
use primitives::BuildStorage;
|
||||
use primitives::traits::{BlakeTwo256, IdentityLookup};
|
||||
use primitives::testing::{Digest, DigestItem, Header};
|
||||
use primitives::testing::Header;
|
||||
use srml_support::impl_outer_origin;
|
||||
|
||||
impl_outer_origin!{
|
||||
@@ -787,12 +783,10 @@ mod tests {
|
||||
type BlockNumber = u64;
|
||||
type Hash = H256;
|
||||
type Hashing = BlakeTwo256;
|
||||
type Digest = Digest;
|
||||
type AccountId = u64;
|
||||
type Lookup = IdentityLookup<Self::AccountId>;
|
||||
type Header = Header;
|
||||
type Event = u16;
|
||||
type Log = DigestItem;
|
||||
}
|
||||
|
||||
impl From<Event> for u16 {
|
||||
|
||||
@@ -335,7 +335,7 @@ mod tests {
|
||||
use substrate_primitives::H256;
|
||||
use runtime_primitives::BuildStorage;
|
||||
use runtime_primitives::traits::{BlakeTwo256, IdentityLookup};
|
||||
use runtime_primitives::testing::{Digest, DigestItem, Header};
|
||||
use runtime_primitives::testing::Header;
|
||||
|
||||
impl_outer_origin! {
|
||||
pub enum Origin for Test {}
|
||||
@@ -349,12 +349,10 @@ mod tests {
|
||||
type BlockNumber = u64;
|
||||
type Hash = H256;
|
||||
type Hashing = BlakeTwo256;
|
||||
type Digest = Digest;
|
||||
type AccountId = u64;
|
||||
type Lookup = IdentityLookup<Self::AccountId>;
|
||||
type Header = Header;
|
||||
type Event = ();
|
||||
type Log = DigestItem;
|
||||
}
|
||||
impl Trait for Test {
|
||||
type Moment = u64;
|
||||
|
||||
@@ -338,7 +338,7 @@ mod tests {
|
||||
use substrate_primitives::{H256, Blake2Hasher};
|
||||
use runtime_primitives::BuildStorage;
|
||||
use runtime_primitives::traits::{BlakeTwo256, OnFinalize, IdentityLookup};
|
||||
use runtime_primitives::testing::{Digest, DigestItem, Header};
|
||||
use runtime_primitives::testing::Header;
|
||||
|
||||
impl_outer_origin! {
|
||||
pub enum Origin for Test {}
|
||||
@@ -352,12 +352,10 @@ mod tests {
|
||||
type BlockNumber = u64;
|
||||
type Hash = H256;
|
||||
type Hashing = BlakeTwo256;
|
||||
type Digest = Digest;
|
||||
type AccountId = u64;
|
||||
type Lookup = IdentityLookup<Self::AccountId>;
|
||||
type Header = Header;
|
||||
type Event = ();
|
||||
type Log = DigestItem;
|
||||
}
|
||||
impl balances::Trait for Test {
|
||||
type Balance = u64;
|
||||
|
||||
Reference in New Issue
Block a user