mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-07-21 10:55:42 +00:00
Refactor key management (#3296)
* Add Call type to extensible transactions. Cleanup some naming * Merge Resource and BlockExhausted into just Exhausted * Fix * Another fix * Call * Some fixes * Fix srml tests. * Fix all tests. * Refactor crypto so each application of it has its own type. * Introduce new AuthorityProvider API into Aura This will eventually allow for dynamic determination of authority keys and avoid having to set them directly on CLI. * Introduce authority determinator for Babe. Experiment with modular consensus API. * Work in progress to introduce KeyTypeId and avoid polluting API with validator IDs * Finish up drafting imonline * Rework offchain workers API. * Rework API implementation. * Make it compile for wasm, simplify app_crypto. * Fix compilation of im-online. * Fix compilation of im-online. * Fix more compilation errors. * Make it compile. * Fixing tests. * Rewrite `keystore` * Fix session tests * Bring back `TryFrom`'s' * Fix `srml-grandpa` * Fix `srml-aura` * Fix consensus babe * More fixes * Make service generate keys from dev_seed * Build fixes * Remove offchain tests * More fixes and cleanups * Fixes finality grandpa * Fix `consensus-aura` * Fix cli * Fix `node-cli` * Fix chain_spec builder * Fix doc tests * Add authority getter for grandpa. * Test fix * Fixes * Make keystore accessible from the runtime * Move app crypto to its own crate * Update `Cargo.lock` * Make the crypto stuff usable from the runtime * Adds some runtime crypto tests * Use last finalized block for grandpa authority * Fix warning * Adds `SessionKeys` runtime api * Remove `FinalityPair` and `ConsensusPair` * Minor governance tweaks to get it inline with docs. * Make the governance be up to date with the docs. * Build fixes. * Generate the inital session keys * Failing keystore is a hard error * Make babe work again * Fix grandpa * Fix tests * Disable `keystore` in consensus critical stuff * Build fix. * ImOnline supports multiple authorities at once. * Update core/application-crypto/src/ed25519.rs * Merge branch 'master' into gav-in-progress * Remove unneeded code for now. * Some `session` testing * Support querying the public keys * Cleanup offchain * Remove warnings * More cleanup * Apply suggestions from code review Co-Authored-By: Benjamin Kampmann <ben.kampmann@googlemail.com> * More cleanups * JSONRPC API for setting keys. Also, rename traits::KeyStore* -> traits::BareCryptoStore* * Bad merge * Fix integration tests * Fix test build * Test fix * Fixes * Warnings * Another warning * Bump version.
This commit is contained in:
@@ -264,6 +264,7 @@ mod tests {
|
||||
impl system::Trait for Test {
|
||||
type Origin = Origin;
|
||||
type Index = u64;
|
||||
type Call = ();
|
||||
type BlockNumber = u64;
|
||||
type Hash = H256;
|
||||
type Hashing = BlakeTwo256;
|
||||
|
||||
@@ -11,6 +11,7 @@ inherents = { package = "substrate-inherents", path = "../../core/inherents", de
|
||||
rstd = { package = "sr-std", path = "../../core/sr-std", default-features = false }
|
||||
sr-primitives = { path = "../../core/sr-primitives", default-features = false }
|
||||
primitives = { package = "substrate-primitives", path = "../../core/primitives", default-features = false }
|
||||
app-crypto = { package = "substrate-application-crypto", path = "../../core/application-crypto", 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 }
|
||||
@@ -20,7 +21,7 @@ substrate-consensus-aura-primitives = { path = "../../core/consensus/aura/primit
|
||||
|
||||
[dev-dependencies]
|
||||
lazy_static = "1.0"
|
||||
parking_lot = "0.8.0"
|
||||
parking_lot = "0.9.0"
|
||||
runtime_io = { package = "sr-io", path = "../../core/sr-io" }
|
||||
|
||||
[features]
|
||||
@@ -37,4 +38,5 @@ std = [
|
||||
"staking/std",
|
||||
"inherents/std",
|
||||
"substrate-consensus-aura-primitives/std",
|
||||
"app-crypto/std",
|
||||
]
|
||||
|
||||
@@ -53,9 +53,9 @@ pub use timestamp;
|
||||
use rstd::{result, prelude::*};
|
||||
use codec::Encode;
|
||||
use srml_support::{decl_storage, decl_module, Parameter, storage::StorageValue, traits::Get};
|
||||
use app_crypto::AppPublic;
|
||||
use sr_primitives::{
|
||||
traits::{SaturatedConversion, Saturating, Zero, One, Member, IsMember, TypedKey},
|
||||
generic::DigestItem,
|
||||
traits::{SaturatedConversion, Saturating, Zero, One, Member, IsMember}, generic::DigestItem,
|
||||
};
|
||||
use timestamp::OnTimestampSet;
|
||||
#[cfg(feature = "std")]
|
||||
@@ -156,7 +156,7 @@ pub trait Trait: timestamp::Trait {
|
||||
type HandleReport: HandleReport;
|
||||
|
||||
/// The identifier type for an authority.
|
||||
type AuthorityId: Member + Parameter + TypedKey + Default;
|
||||
type AuthorityId: Member + Parameter + AppPublic + Default;
|
||||
}
|
||||
|
||||
decl_storage! {
|
||||
|
||||
@@ -18,6 +18,8 @@
|
||||
|
||||
#![cfg(test)]
|
||||
|
||||
use crate::{Trait, Module, GenesisConfig};
|
||||
use substrate_consensus_aura_primitives::ed25519::AuthorityId;
|
||||
use sr_primitives::{
|
||||
traits::IdentityLookup, Perbill,
|
||||
testing::{Header, UintAuthorityId},
|
||||
@@ -25,7 +27,6 @@ use sr_primitives::{
|
||||
use srml_support::{impl_outer_origin, parameter_types};
|
||||
use runtime_io;
|
||||
use primitives::{H256, Blake2Hasher};
|
||||
use crate::{Trait, Module, GenesisConfig};
|
||||
|
||||
impl_outer_origin!{
|
||||
pub enum Origin for Test {}
|
||||
@@ -47,6 +48,7 @@ impl system::Trait for Test {
|
||||
type Origin = Origin;
|
||||
type Index = u64;
|
||||
type BlockNumber = u64;
|
||||
type Call = ();
|
||||
type Hash = H256;
|
||||
type Hashing = ::sr_primitives::traits::BlakeTwo256;
|
||||
type AccountId = u64;
|
||||
@@ -68,13 +70,13 @@ impl timestamp::Trait for Test {
|
||||
|
||||
impl Trait for Test {
|
||||
type HandleReport = ();
|
||||
type AuthorityId = UintAuthorityId;
|
||||
type AuthorityId = AuthorityId;
|
||||
}
|
||||
|
||||
pub fn new_test_ext(authorities: Vec<u64>) -> runtime_io::TestExternalities<Blake2Hasher> {
|
||||
let mut t = system::GenesisConfig::default().build_storage::<Test>().unwrap().0;
|
||||
t.extend(GenesisConfig::<Test>{
|
||||
authorities: authorities.into_iter().map(|a| UintAuthorityId(a)).collect(),
|
||||
authorities: authorities.into_iter().map(|a| UintAuthorityId(a).to_public_key()).collect(),
|
||||
}.build_storage().unwrap().0);
|
||||
t.into()
|
||||
}
|
||||
|
||||
@@ -349,6 +349,7 @@ mod tests {
|
||||
type Origin = Origin;
|
||||
type Index = u64;
|
||||
type BlockNumber = u64;
|
||||
type Call = ();
|
||||
type Hash = H256;
|
||||
type Hashing = BlakeTwo256;
|
||||
type AccountId = u64;
|
||||
|
||||
@@ -17,11 +17,11 @@ system = { package = "srml-system", path = "../system", default-features = false
|
||||
timestamp = { package = "srml-timestamp", path = "../timestamp", 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 }
|
||||
runtime_io = { package = "sr-io", path = "../../core/sr-io", default-features = false }
|
||||
runtime_io = { package = "sr-io", path = "../../core/sr-io", default-features = false, features = [ "wasm-nice-panic-message" ] }
|
||||
|
||||
[dev-dependencies]
|
||||
lazy_static = "1.3.0"
|
||||
parking_lot = "0.8.0"
|
||||
parking_lot = "0.9.0"
|
||||
primitives = { package = "substrate-primitives", path = "../../core/primitives" }
|
||||
|
||||
[features]
|
||||
|
||||
@@ -763,6 +763,7 @@ impl<T: Subtrait<I>, I: Instance> PartialEq for ElevatedTrait<T, I> {
|
||||
impl<T: Subtrait<I>, I: Instance> Eq for ElevatedTrait<T, I> {}
|
||||
impl<T: Subtrait<I>, I: Instance> system::Trait for ElevatedTrait<T, I> {
|
||||
type Origin = T::Origin;
|
||||
type Call = T::Call;
|
||||
type Index = T::Index;
|
||||
type BlockNumber = T::BlockNumber;
|
||||
type Hash = T::Hash;
|
||||
@@ -1213,12 +1214,14 @@ impl<T: Trait<I>, I: Instance> rstd::fmt::Debug for TakeFees<T, I> {
|
||||
|
||||
impl<T: Trait<I>, I: Instance + Clone + Eq> SignedExtension for TakeFees<T, I> {
|
||||
type AccountId = T::AccountId;
|
||||
type Call = T::Call;
|
||||
type AdditionalSigned = ();
|
||||
fn additional_signed(&self) -> rstd::result::Result<(), &'static str> { Ok(()) }
|
||||
|
||||
fn validate(
|
||||
&self,
|
||||
who: &Self::AccountId,
|
||||
_call: &Self::Call,
|
||||
info: DispatchInfo,
|
||||
len: usize,
|
||||
) -> rstd::result::Result<ValidTransaction, DispatchError> {
|
||||
|
||||
@@ -85,6 +85,7 @@ impl system::Trait for Runtime {
|
||||
type Origin = Origin;
|
||||
type Index = u64;
|
||||
type BlockNumber = u64;
|
||||
type Call = ();
|
||||
type Hash = H256;
|
||||
type Hashing = ::sr_primitives::traits::BlakeTwo256;
|
||||
type AccountId = u64;
|
||||
@@ -208,6 +209,9 @@ impl ExtBuilder {
|
||||
pub type System = system::Module<Runtime>;
|
||||
pub type Balances = Module<Runtime>;
|
||||
|
||||
|
||||
pub const CALL: &<Runtime as system::Trait>::Call = &();
|
||||
|
||||
/// create a transaction info struct from weight. Handy to avoid building the whole struct.
|
||||
pub fn info_from_weight(w: Weight) -> DispatchInfo {
|
||||
DispatchInfo { weight: w, ..Default::default() }
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
#![cfg(test)]
|
||||
|
||||
use super::*;
|
||||
use mock::{Balances, ExtBuilder, Runtime, System, info_from_weight};
|
||||
use mock::{Balances, ExtBuilder, Runtime, System, info_from_weight, CALL};
|
||||
use runtime_io::with_externalities;
|
||||
use srml_support::{
|
||||
assert_noop, assert_ok, assert_err,
|
||||
@@ -127,6 +127,7 @@ fn lock_reasons_should_work() {
|
||||
assert!(<TakeFees<Runtime> as SignedExtension>::pre_dispatch(
|
||||
TakeFees::from(1),
|
||||
&1,
|
||||
CALL,
|
||||
info_from_weight(1),
|
||||
0,
|
||||
).is_ok());
|
||||
@@ -140,6 +141,7 @@ fn lock_reasons_should_work() {
|
||||
assert!(<TakeFees<Runtime> as SignedExtension>::pre_dispatch(
|
||||
TakeFees::from(1),
|
||||
&1,
|
||||
CALL,
|
||||
info_from_weight(1),
|
||||
0,
|
||||
).is_ok());
|
||||
@@ -150,6 +152,7 @@ fn lock_reasons_should_work() {
|
||||
assert!(<TakeFees<Runtime> as SignedExtension>::pre_dispatch(
|
||||
TakeFees::from(1),
|
||||
&1,
|
||||
CALL,
|
||||
info_from_weight(1),
|
||||
0,
|
||||
).is_err());
|
||||
@@ -757,9 +760,9 @@ fn signed_extension_take_fees_work() {
|
||||
.build(),
|
||||
|| {
|
||||
let len = 10;
|
||||
assert!(TakeFees::<Runtime>::from(0).pre_dispatch(&1, info_from_weight(5), len).is_ok());
|
||||
assert!(TakeFees::<Runtime>::from(0).pre_dispatch(&1, CALL, info_from_weight(5), len).is_ok());
|
||||
assert_eq!(Balances::free_balance(&1), 100 - 20 - 25);
|
||||
assert!(TakeFees::<Runtime>::from(5 /* tipped */).pre_dispatch(&1, info_from_weight(3), len).is_ok());
|
||||
assert!(TakeFees::<Runtime>::from(5 /* tipped */).pre_dispatch(&1, CALL, info_from_weight(3), len).is_ok());
|
||||
assert_eq!(Balances::free_balance(&1), 100 - 20 - 25 - 20 - 5 - 15);
|
||||
}
|
||||
);
|
||||
@@ -777,7 +780,7 @@ fn signed_extension_take_fees_is_bounded() {
|
||||
use sr_primitives::weights::Weight;
|
||||
|
||||
// maximum weight possible
|
||||
assert!(TakeFees::<Runtime>::from(0).pre_dispatch(&1, info_from_weight(Weight::max_value()), 10).is_ok());
|
||||
assert!(TakeFees::<Runtime>::from(0).pre_dispatch(&1, CALL, info_from_weight(Weight::max_value()), 10).is_ok());
|
||||
// fee will be proportional to what is the actual maximum weight in the runtime.
|
||||
assert_eq!(
|
||||
Balances::free_balance(&1),
|
||||
|
||||
@@ -16,6 +16,9 @@
|
||||
|
||||
//! Collective system: Members of a set of account IDs can make their collective feelings known
|
||||
//! through dispatched calls from one of two specialised origins.
|
||||
//!
|
||||
//! The membership can be provided in one of two ways: either directly, using the Root-dispatchable
|
||||
//! function `set_members`, or indirectly, through implementing the `ChangeMembers`
|
||||
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
#![recursion_limit="128"]
|
||||
@@ -119,7 +122,7 @@ decl_event!(
|
||||
}
|
||||
);
|
||||
|
||||
// Note: this module is not benchmarked. The weights are obtained based on the similarity fo the
|
||||
// Note: this module is not benchmarked. The weights are obtained based on the similarity of the
|
||||
// executed logic with other democracy function. Note that councillor operations are assigned to the
|
||||
// operational class.
|
||||
decl_module! {
|
||||
@@ -133,41 +136,12 @@ decl_module! {
|
||||
#[weight = SimpleDispatchInfo::FixedOperational(100_000)]
|
||||
fn set_members(origin, new_members: Vec<T::AccountId>) {
|
||||
ensure_root(origin)?;
|
||||
|
||||
// stable sorting since they will generally be provided sorted.
|
||||
let mut old_members = <Members<T, I>>::get();
|
||||
old_members.sort();
|
||||
let mut new_members = new_members;
|
||||
new_members.sort();
|
||||
let mut old_iter = old_members.iter();
|
||||
let mut new_iter = new_members.iter();
|
||||
let mut incoming = vec![];
|
||||
let mut outgoing = vec![];
|
||||
let mut old_i = old_iter.next();
|
||||
let mut new_i = new_iter.next();
|
||||
loop {
|
||||
match (old_i, new_i) {
|
||||
(None, None) => break,
|
||||
(Some(old), Some(new)) if old == new => {
|
||||
old_i = old_iter.next();
|
||||
new_i = new_iter.next();
|
||||
}
|
||||
(Some(old), Some(new)) if old < new => {
|
||||
outgoing.push(old.clone());
|
||||
old_i = old_iter.next();
|
||||
}
|
||||
(Some(old), None) => {
|
||||
outgoing.push(old.clone());
|
||||
old_i = old_iter.next();
|
||||
}
|
||||
(_, Some(new)) => {
|
||||
incoming.push(new.clone());
|
||||
new_i = new_iter.next();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Self::change_members(&incoming, &outgoing, &new_members);
|
||||
<Members<T, I>>::mutate(|m| {
|
||||
<Self as ChangeMembers<T::AccountId>>::set_members_sorted(&new_members[..], m);
|
||||
*m = new_members;
|
||||
});
|
||||
}
|
||||
|
||||
/// Dispatch a proposal from a member using the `Member` origin.
|
||||
@@ -287,18 +261,18 @@ impl<T: Trait<I>, I: Instance> Module<T, I> {
|
||||
}
|
||||
|
||||
impl<T: Trait<I>, I: Instance> ChangeMembers<T::AccountId> for Module<T, I> {
|
||||
fn change_members(_incoming: &[T::AccountId], outgoing: &[T::AccountId], new: &[T::AccountId]) {
|
||||
fn change_members_sorted(_incoming: &[T::AccountId], outgoing: &[T::AccountId], new: &[T::AccountId]) {
|
||||
// remove accounts from all current voting in motions.
|
||||
let mut old = outgoing.to_vec();
|
||||
old.sort_unstable();
|
||||
let mut outgoing = outgoing.to_vec();
|
||||
outgoing.sort_unstable();
|
||||
for h in Self::proposals().into_iter() {
|
||||
<Voting<T, I>>::mutate(h, |v|
|
||||
if let Some(mut votes) = v.take() {
|
||||
votes.ayes = votes.ayes.into_iter()
|
||||
.filter(|i| old.binary_search(i).is_err())
|
||||
.filter(|i| outgoing.binary_search(i).is_err())
|
||||
.collect();
|
||||
votes.nays = votes.nays.into_iter()
|
||||
.filter(|i| old.binary_search(i).is_err())
|
||||
.filter(|i| outgoing.binary_search(i).is_err())
|
||||
.collect();
|
||||
*v = Some(votes);
|
||||
}
|
||||
@@ -413,6 +387,7 @@ mod tests {
|
||||
type Origin = Origin;
|
||||
type Index = u64;
|
||||
type BlockNumber = u64;
|
||||
type Call = ();
|
||||
type Hash = H256;
|
||||
type Hashing = BlakeTwo256;
|
||||
type AccountId = u64;
|
||||
@@ -486,7 +461,7 @@ mod tests {
|
||||
Collective::voting(&hash),
|
||||
Some(Votes { index: 0, threshold: 3, ayes: vec![1, 2], nays: vec![] })
|
||||
);
|
||||
Collective::change_members(&[4], &[1], &[2, 3, 4]);
|
||||
Collective::change_members_sorted(&[4], &[1], &[2, 3, 4]);
|
||||
assert_eq!(
|
||||
Collective::voting(&hash),
|
||||
Some(Votes { index: 0, threshold: 3, ayes: vec![2], nays: vec![] })
|
||||
@@ -500,7 +475,7 @@ mod tests {
|
||||
Collective::voting(&hash),
|
||||
Some(Votes { index: 1, threshold: 2, ayes: vec![2], nays: vec![3] })
|
||||
);
|
||||
Collective::change_members(&[], &[3], &[2, 4]);
|
||||
Collective::change_members_sorted(&[], &[3], &[2, 4]);
|
||||
assert_eq!(
|
||||
Collective::voting(&hash),
|
||||
Some(Votes { index: 1, threshold: 2, ayes: vec![2], nays: vec![] })
|
||||
|
||||
@@ -275,7 +275,7 @@ pub enum DeferredAction<T: Trait> {
|
||||
/// The account id of the contract who dispatched this call.
|
||||
origin: T::AccountId,
|
||||
/// The call to dispatch.
|
||||
call: T::Call,
|
||||
call: <T as Trait>::Call,
|
||||
},
|
||||
RestoreTo {
|
||||
/// The account id of the contract which is removed during the restoration and transfers
|
||||
|
||||
@@ -333,7 +333,7 @@ pub trait Trait: timestamp::Trait {
|
||||
///
|
||||
/// It is recommended (though not required) for this function to return a fee that would be taken
|
||||
/// by the Executive module for regular dispatch.
|
||||
type ComputeDispatchFee: ComputeDispatchFee<Self::Call, BalanceOf<Self>>;
|
||||
type ComputeDispatchFee: ComputeDispatchFee<<Self as Trait>::Call, BalanceOf<Self>>;
|
||||
|
||||
/// trie id generator
|
||||
type TrieIdGenerator: TrieIdGenerator<Self::AccountId>;
|
||||
@@ -427,8 +427,8 @@ where
|
||||
/// The default dispatch fee computor computes the fee in the same way that
|
||||
/// the implementation of `MakePayment` for the Balances module does.
|
||||
pub struct DefaultDispatchFeeComputor<T: Trait>(PhantomData<T>);
|
||||
impl<T: Trait> ComputeDispatchFee<T::Call, BalanceOf<T>> for DefaultDispatchFeeComputor<T> {
|
||||
fn compute_dispatch_fee(call: &T::Call) -> BalanceOf<T> {
|
||||
impl<T: Trait> ComputeDispatchFee<<T as Trait>::Call, BalanceOf<T>> for DefaultDispatchFeeComputor<T> {
|
||||
fn compute_dispatch_fee(call: &<T as Trait>::Call) -> BalanceOf<T> {
|
||||
let encoded_len = call.using_encoded(|encoded| encoded.len() as u32);
|
||||
let base_fee = T::TransactionBaseFee::get();
|
||||
let byte_fee = T::TransactionByteFee::get();
|
||||
|
||||
@@ -107,6 +107,7 @@ impl system::Trait for Test {
|
||||
type Index = u64;
|
||||
type BlockNumber = u64;
|
||||
type Hash = H256;
|
||||
type Call = ();
|
||||
type Hashing = BlakeTwo256;
|
||||
type AccountId = u64;
|
||||
type Lookup = IdentityLookup<Self::AccountId>;
|
||||
|
||||
@@ -107,6 +107,7 @@ mod tests {
|
||||
type Origin = Origin;
|
||||
type Index = u64;
|
||||
type BlockNumber = u64;
|
||||
type Call = ();
|
||||
type Hash = H256;
|
||||
type Hashing = BlakeTwo256;
|
||||
type AccountId = u64;
|
||||
|
||||
@@ -61,6 +61,8 @@ pub enum Conviction {
|
||||
Locked4x,
|
||||
/// 5x votes, locked for 16x...
|
||||
Locked5x,
|
||||
/// 6x votes, locked for 32x...
|
||||
Locked6x,
|
||||
}
|
||||
|
||||
impl Default for Conviction {
|
||||
@@ -78,6 +80,7 @@ impl From<Conviction> for u8 {
|
||||
Conviction::Locked3x => 3,
|
||||
Conviction::Locked4x => 4,
|
||||
Conviction::Locked5x => 5,
|
||||
Conviction::Locked6x => 6,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -92,6 +95,7 @@ impl TryFrom<u8> for Conviction {
|
||||
3 => Conviction::Locked3x,
|
||||
4 => Conviction::Locked4x,
|
||||
5 => Conviction::Locked5x,
|
||||
6 => Conviction::Locked6x,
|
||||
_ => return Err(()),
|
||||
})
|
||||
}
|
||||
@@ -108,6 +112,7 @@ impl Conviction {
|
||||
Conviction::Locked3x => 4,
|
||||
Conviction::Locked4x => 8,
|
||||
Conviction::Locked5x => 16,
|
||||
Conviction::Locked6x => 32,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -134,7 +139,7 @@ impl Bounded for Conviction {
|
||||
}
|
||||
|
||||
fn max_value() -> Self {
|
||||
Conviction::Locked5x
|
||||
Conviction::Locked6x
|
||||
}
|
||||
}
|
||||
|
||||
@@ -208,18 +213,19 @@ pub trait Trait: system::Trait + Sized {
|
||||
/// a majority-carries referendum.
|
||||
type ExternalMajorityOrigin: EnsureOrigin<Self::Origin>;
|
||||
|
||||
/// Origin from which the next tabled referendum may be forced; this allows for the tabling of
|
||||
/// a negative-turnout-bias (default-carries) referendum.
|
||||
type ExternalDefaultOrigin: EnsureOrigin<Self::Origin>;
|
||||
|
||||
/// Origin from which the next referendum proposed by the external majority may be immediately
|
||||
/// tabled to vote asynchronously in a similar manner to the emergency origin. It remains a
|
||||
/// majority-carries vote.
|
||||
type ExternalPushOrigin: EnsureOrigin<Self::Origin>;
|
||||
type FastTrackOrigin: EnsureOrigin<Self::Origin>;
|
||||
|
||||
/// Origin from which emergency referenda may be scheduled.
|
||||
type EmergencyOrigin: EnsureOrigin<Self::Origin>;
|
||||
|
||||
/// Minimum voting period allowed for an emergency referendum.
|
||||
/// Minimum voting period allowed for an fast-track/emergency referendum.
|
||||
type EmergencyVotingPeriod: Get<Self::BlockNumber>;
|
||||
|
||||
/// Origin from which any referenda may be cancelled in an emergency.
|
||||
/// Origin from which any referendum may be cancelled in an emergency.
|
||||
type CancellationOrigin: EnsureOrigin<Self::Origin>;
|
||||
|
||||
/// Origin for anyone able to veto proposals.
|
||||
@@ -434,32 +440,6 @@ decl_module! {
|
||||
Self::do_vote(who, ref_index, vote)
|
||||
}
|
||||
|
||||
/// Schedule an emergency referendum.
|
||||
///
|
||||
/// This will create a new referendum for the `proposal`, approved as long as counted votes
|
||||
/// exceed `threshold` and, if approved, enacted after the given `delay`.
|
||||
///
|
||||
/// It may be called from either the Root or the Emergency origin.
|
||||
#[weight = SimpleDispatchInfo::FixedOperational(500_000)]
|
||||
fn emergency_propose(origin,
|
||||
proposal: Box<T::Proposal>,
|
||||
threshold: VoteThreshold,
|
||||
voting_period: T::BlockNumber,
|
||||
delay: T::BlockNumber
|
||||
) {
|
||||
T::EmergencyOrigin::try_origin(origin)
|
||||
.map(|_| ())
|
||||
.or_else(|origin| ensure_root(origin))?;
|
||||
let now = <system::Module<T>>::block_number();
|
||||
// We don't consider it an error if `vote_period` is too low, but we do enforce the
|
||||
// minimum. This is primarily due to practicality. If it's an emergency, we don't want
|
||||
// to introduce more delays than is strictly needed by requiring a potentially costly
|
||||
// resubmission in the case of a mistakenly low `vote_period`; better to just let the
|
||||
// referendum take place with the lowest valid value.
|
||||
let period = voting_period.max(T::EmergencyVotingPeriod::get());
|
||||
Self::inject_referendum(now + period, *proposal, threshold, delay).map(|_| ())?;
|
||||
}
|
||||
|
||||
/// Schedule an emergency cancellation of a referendum. Cannot happen twice to the same
|
||||
/// referendum.
|
||||
#[weight = SimpleDispatchInfo::FixedOperational(500_000)]
|
||||
@@ -489,17 +469,26 @@ decl_module! {
|
||||
|
||||
/// Schedule a majority-carries referendum to be tabled next once it is legal to schedule
|
||||
/// an external referendum.
|
||||
///
|
||||
/// Unlike `external_propose`, blacklisting has no effect on this and it may replace a
|
||||
/// pre-scheduled `external_propose` call.
|
||||
#[weight = SimpleDispatchInfo::FixedNormal(5_000_000)]
|
||||
fn external_propose_majority(origin, proposal: Box<T::Proposal>) {
|
||||
T::ExternalMajorityOrigin::ensure_origin(origin)?;
|
||||
ensure!(!<NextExternal<T>>::exists(), "proposal already made");
|
||||
let proposal_hash = T::Hashing::hash_of(&proposal);
|
||||
if let Some((until, _)) = <Blacklist<T>>::get(proposal_hash) {
|
||||
ensure!(<system::Module<T>>::block_number() >= until, "proposal still blacklisted");
|
||||
}
|
||||
<NextExternal<T>>::put((*proposal, VoteThreshold::SimpleMajority));
|
||||
}
|
||||
|
||||
/// Schedule a negative-turnout-bias referendum to be tabled next once it is legal to
|
||||
/// schedule an external referendum.
|
||||
///
|
||||
/// Unlike `external_propose`, blacklisting has no effect on this and it may replace a
|
||||
/// pre-scheduled `external_propose` call.
|
||||
#[weight = SimpleDispatchInfo::FixedNormal(5_000_000)]
|
||||
fn external_propose_default(origin, proposal: Box<T::Proposal>) {
|
||||
T::ExternalDefaultOrigin::ensure_origin(origin)?;
|
||||
<NextExternal<T>>::put((*proposal, VoteThreshold::SuperMajorityAgainst));
|
||||
}
|
||||
|
||||
/// Schedule the currently externally-proposed majority-carries referendum to be tabled
|
||||
/// immediately. If there is no externally-proposed referendum currently, or if there is one
|
||||
/// but it is not a majority-carries referendum then it fails.
|
||||
@@ -509,14 +498,14 @@ decl_module! {
|
||||
/// - `delay`: The number of block after voting has ended in approval and this should be
|
||||
/// enacted. Increased to `EmergencyVotingPeriod` if too low.
|
||||
#[weight = SimpleDispatchInfo::FixedNormal(200_000)]
|
||||
fn external_push(origin,
|
||||
fn fast_track(origin,
|
||||
proposal_hash: T::Hash,
|
||||
voting_period: T::BlockNumber,
|
||||
delay: T::BlockNumber
|
||||
) {
|
||||
T::ExternalPushOrigin::ensure_origin(origin)?;
|
||||
T::FastTrackOrigin::ensure_origin(origin)?;
|
||||
let (proposal, threshold) = <NextExternal<T>>::get().ok_or("no proposal made")?;
|
||||
ensure!(threshold == VoteThreshold::SimpleMajority, "next external proposal not simple majority");
|
||||
ensure!(threshold != VoteThreshold::SuperMajorityApprove, "next external proposal not simple majority");
|
||||
ensure!(proposal_hash == T::Hashing::hash_of(&proposal), "invalid hash");
|
||||
|
||||
<NextExternal<T>>::kill();
|
||||
@@ -1028,6 +1017,7 @@ mod tests {
|
||||
type Origin = Origin;
|
||||
type Index = u64;
|
||||
type BlockNumber = u64;
|
||||
type Call = ();
|
||||
type Hash = H256;
|
||||
type Hashing = BlakeTwo256;
|
||||
type AccountId = u64;
|
||||
@@ -1090,10 +1080,10 @@ mod tests {
|
||||
type VotingPeriod = VotingPeriod;
|
||||
type EmergencyVotingPeriod = EmergencyVotingPeriod;
|
||||
type MinimumDeposit = MinimumDeposit;
|
||||
type EmergencyOrigin = EnsureSignedBy<One, u64>;
|
||||
type ExternalOrigin = EnsureSignedBy<Two, u64>;
|
||||
type ExternalMajorityOrigin = EnsureSignedBy<Three, u64>;
|
||||
type ExternalPushOrigin = EnsureSignedBy<Five, u64>;
|
||||
type ExternalDefaultOrigin = EnsureSignedBy<One, u64>;
|
||||
type FastTrackOrigin = EnsureSignedBy<Five, u64>;
|
||||
type CancellationOrigin = EnsureSignedBy<Four, u64>;
|
||||
type VetoOrigin = EnsureSignedBy<OneToFive, u64>;
|
||||
type CooloffPeriod = CooloffPeriod;
|
||||
@@ -1347,64 +1337,6 @@ mod tests {
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn emergency_referendum_works() {
|
||||
with_externalities(&mut new_test_ext(), || {
|
||||
System::set_block_number(0);
|
||||
assert_noop!(Democracy::emergency_propose(
|
||||
Origin::signed(6), // invalid
|
||||
Box::new(set_balance_proposal(2)),
|
||||
VoteThreshold::SuperMajorityAgainst,
|
||||
0,
|
||||
0,
|
||||
), "bad origin: expected to be a root origin");
|
||||
assert_ok!(Democracy::emergency_propose(
|
||||
Origin::signed(1),
|
||||
Box::new(set_balance_proposal(2)),
|
||||
VoteThreshold::SuperMajorityAgainst,
|
||||
0,
|
||||
0,
|
||||
));
|
||||
assert_eq!(
|
||||
Democracy::referendum_info(0),
|
||||
Some(ReferendumInfo {
|
||||
end: 1,
|
||||
proposal: set_balance_proposal(2),
|
||||
threshold: VoteThreshold::SuperMajorityAgainst,
|
||||
delay: 0
|
||||
})
|
||||
);
|
||||
|
||||
assert_ok!(Democracy::vote(Origin::signed(1), 0, AYE));
|
||||
fast_forward_to(1);
|
||||
assert_eq!(Balances::free_balance(&42), 0);
|
||||
fast_forward_to(2);
|
||||
assert_eq!(Balances::free_balance(&42), 2);
|
||||
|
||||
assert_ok!(Democracy::emergency_propose(
|
||||
Origin::signed(1),
|
||||
Box::new(set_balance_proposal(4)),
|
||||
VoteThreshold::SuperMajorityAgainst,
|
||||
3,
|
||||
3
|
||||
));
|
||||
assert_eq!(
|
||||
Democracy::referendum_info(1),
|
||||
Some(ReferendumInfo {
|
||||
end: 5,
|
||||
proposal: set_balance_proposal(4),
|
||||
threshold: VoteThreshold::SuperMajorityAgainst,
|
||||
delay: 3
|
||||
})
|
||||
);
|
||||
assert_ok!(Democracy::vote(Origin::signed(1), 1, AYE));
|
||||
fast_forward_to(8);
|
||||
assert_eq!(Balances::free_balance(&42), 2);
|
||||
fast_forward_to(9);
|
||||
assert_eq!(Balances::free_balance(&42), 4);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn external_referendum_works() {
|
||||
with_externalities(&mut new_test_ext(), || {
|
||||
@@ -1460,17 +1392,42 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn external_push_referendum_works() {
|
||||
fn external_default_referendum_works() {
|
||||
with_externalities(&mut new_test_ext(), || {
|
||||
System::set_block_number(0);
|
||||
assert_noop!(Democracy::external_propose_default(
|
||||
Origin::signed(3),
|
||||
Box::new(set_balance_proposal(2))
|
||||
), "Invalid origin");
|
||||
assert_ok!(Democracy::external_propose_default(
|
||||
Origin::signed(1),
|
||||
Box::new(set_balance_proposal(2))
|
||||
));
|
||||
fast_forward_to(1);
|
||||
assert_eq!(
|
||||
Democracy::referendum_info(0),
|
||||
Some(ReferendumInfo {
|
||||
end: 2,
|
||||
proposal: set_balance_proposal(2),
|
||||
threshold: VoteThreshold::SuperMajorityAgainst,
|
||||
delay: 2,
|
||||
})
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fast_track_referendum_works() {
|
||||
with_externalities(&mut new_test_ext(), || {
|
||||
System::set_block_number(0);
|
||||
let h = BlakeTwo256::hash_of(&set_balance_proposal(2));
|
||||
assert_noop!(Democracy::external_push(Origin::signed(5), h, 3, 2), "no proposal made");
|
||||
assert_noop!(Democracy::fast_track(Origin::signed(5), h, 3, 2), "no proposal made");
|
||||
assert_ok!(Democracy::external_propose_majority(
|
||||
Origin::signed(3),
|
||||
Box::new(set_balance_proposal(2))
|
||||
));
|
||||
assert_noop!(Democracy::external_push(Origin::signed(1), h, 3, 2), "Invalid origin");
|
||||
assert_ok!(Democracy::external_push(Origin::signed(5), h, 0, 0));
|
||||
assert_noop!(Democracy::fast_track(Origin::signed(1), h, 3, 2), "Invalid origin");
|
||||
assert_ok!(Democracy::fast_track(Origin::signed(5), h, 0, 0));
|
||||
assert_eq!(
|
||||
Democracy::referendum_info(0),
|
||||
Some(ReferendumInfo {
|
||||
@@ -1484,7 +1441,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn external_push_referendum_fails_when_no_simple_majority() {
|
||||
fn fast_track_referendum_fails_when_no_simple_majority() {
|
||||
with_externalities(&mut new_test_ext(), || {
|
||||
System::set_block_number(0);
|
||||
let h = BlakeTwo256::hash_of(&set_balance_proposal(2));
|
||||
@@ -1493,7 +1450,7 @@ mod tests {
|
||||
Box::new(set_balance_proposal(2))
|
||||
));
|
||||
assert_noop!(
|
||||
Democracy::external_push(Origin::signed(5), h, 3, 2),
|
||||
Democracy::fast_track(Origin::signed(5), h, 3, 2),
|
||||
"next external proposal not simple majority"
|
||||
);
|
||||
});
|
||||
|
||||
@@ -593,7 +593,7 @@ decl_module! {
|
||||
.collect();
|
||||
<Members<T>>::put(&new_set);
|
||||
let new_set = new_set.into_iter().map(|x| x.0).collect::<Vec<_>>();
|
||||
T::ChangeMembers::change_members(&[], &[who], &new_set[..]);
|
||||
T::ChangeMembers::change_members(&[], &[who], new_set);
|
||||
}
|
||||
|
||||
/// Set the presentation duration. If there is currently a vote being presented for, will
|
||||
@@ -876,7 +876,7 @@ impl<T: Trait> Module<T> {
|
||||
<Members<T>>::put(&new_set);
|
||||
|
||||
let new_set = new_set.into_iter().map(|x| x.0).collect::<Vec<_>>();
|
||||
T::ChangeMembers::change_members(&incoming, &outgoing, &new_set[..]);
|
||||
T::ChangeMembers::change_members(&incoming, &outgoing, new_set);
|
||||
|
||||
// clear all except runners-up from candidate list.
|
||||
let candidates = Self::candidates();
|
||||
@@ -1132,6 +1132,7 @@ mod tests {
|
||||
type Origin = Origin;
|
||||
type Index = u64;
|
||||
type BlockNumber = u64;
|
||||
type Call = ();
|
||||
type Hash = H256;
|
||||
type Hashing = BlakeTwo256;
|
||||
type AccountId = u64;
|
||||
@@ -1203,7 +1204,7 @@ mod tests {
|
||||
|
||||
pub struct TestChangeMembers;
|
||||
impl ChangeMembers<u64> for TestChangeMembers {
|
||||
fn change_members(incoming: &[u64], outgoing: &[u64], new: &[u64]) {
|
||||
fn change_members_sorted(incoming: &[u64], outgoing: &[u64], new: &[u64]) {
|
||||
let mut old_plus_incoming = MEMBERS.with(|m| m.borrow().to_vec());
|
||||
old_plus_incoming.extend_from_slice(incoming);
|
||||
old_plus_incoming.sort();
|
||||
|
||||
@@ -533,6 +533,7 @@ mod tests {
|
||||
type Index = u64;
|
||||
type BlockNumber = u64;
|
||||
type Hash = H256;
|
||||
type Call = ();
|
||||
type Hashing = BlakeTwo256;
|
||||
type AccountId = u64;
|
||||
type Lookup = IdentityLookup<Self::AccountId>;
|
||||
|
||||
@@ -108,7 +108,7 @@ mod internal {
|
||||
fn from(d: DispatchError) -> Self {
|
||||
match d {
|
||||
DispatchError::Payment => ApplyError::CantPay,
|
||||
DispatchError::Resource => ApplyError::FullBlock,
|
||||
DispatchError::Exhausted => ApplyError::FullBlock,
|
||||
DispatchError::NoPermission => ApplyError::CantPay,
|
||||
DispatchError::BadState => ApplyError::CantPay,
|
||||
DispatchError::Stale => ApplyError::Stale,
|
||||
@@ -368,8 +368,7 @@ mod tests {
|
||||
use hex_literal::hex;
|
||||
|
||||
impl_outer_origin! {
|
||||
pub enum Origin for Runtime {
|
||||
}
|
||||
pub enum Origin for Runtime { }
|
||||
}
|
||||
|
||||
impl_outer_event!{
|
||||
@@ -390,6 +389,7 @@ mod tests {
|
||||
impl system::Trait for Runtime {
|
||||
type Origin = Origin;
|
||||
type Index = u64;
|
||||
type Call = Call<Runtime>;
|
||||
type BlockNumber = u64;
|
||||
type Hash = primitives::H256;
|
||||
type Hashing = BlakeTwo256;
|
||||
|
||||
@@ -308,6 +308,7 @@ mod tests {
|
||||
type Origin = Origin;
|
||||
type Index = u64;
|
||||
type BlockNumber = u64;
|
||||
type Call = ();
|
||||
type Hash = H256;
|
||||
type Hashing = BlakeTwo256;
|
||||
type AccountId = u64;
|
||||
|
||||
@@ -1050,6 +1050,7 @@ impl<T: Subtrait> PartialEq for ElevatedTrait<T> {
|
||||
impl<T: Subtrait> Eq for ElevatedTrait<T> {}
|
||||
impl<T: Subtrait> system::Trait for ElevatedTrait<T> {
|
||||
type Origin = T::Origin;
|
||||
type Call = T::Call;
|
||||
type Index = T::Index;
|
||||
type BlockNumber = T::BlockNumber;
|
||||
type Hash = T::Hash;
|
||||
|
||||
@@ -45,6 +45,7 @@ impl system::Trait for Test {
|
||||
type Origin = Origin;
|
||||
type Index = u64;
|
||||
type BlockNumber = u64;
|
||||
type Call = ();
|
||||
type Hash = H256;
|
||||
type Hashing = BlakeTwo256;
|
||||
type AccountId = u64;
|
||||
|
||||
@@ -51,6 +51,7 @@ impl system::Trait for Test {
|
||||
type Origin = Origin;
|
||||
type Index = u64;
|
||||
type BlockNumber = u64;
|
||||
type Call = ();
|
||||
type Hash = H256;
|
||||
type Hashing = ::sr_primitives::traits::BlakeTwo256;
|
||||
type AccountId = u64;
|
||||
@@ -75,7 +76,9 @@ impl_outer_event!{
|
||||
}
|
||||
|
||||
pub fn to_authorities(vec: Vec<(u64, u64)>) -> Vec<(AuthorityId, u64)> {
|
||||
vec.into_iter().map(|(id, weight)| (UintAuthorityId(id).into(), weight)).collect()
|
||||
vec.into_iter()
|
||||
.map(|(id, weight)| (UintAuthorityId(id).to_public_key::<AuthorityId>(), weight))
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn new_test_ext(authorities: Vec<(u64, u64)>) -> runtime_io::TestExternalities<Blake2Hasher> {
|
||||
|
||||
@@ -8,6 +8,7 @@ edition = "2018"
|
||||
codec = { package = "parity-scale-codec", version = "1.0.0", default-features = false, features = ["derive"] }
|
||||
sr-primitives = { path = "../../core/sr-primitives", default-features = false }
|
||||
primitives = { package = "substrate-primitives", path = "../../core/primitives", default-features = false }
|
||||
app-crypto = { package = "substrate-application-crypto", path = "../../core/application-crypto", default-features = false }
|
||||
rstd = { package = "sr-std", path = "../../core/sr-std", default-features = false }
|
||||
serde = { version = "1.0", optional = true }
|
||||
session = { package = "srml-session", path = "../session", default-features = false }
|
||||
@@ -26,4 +27,5 @@ std = [
|
||||
"srml-support/std",
|
||||
"sr-io/std",
|
||||
"system/std",
|
||||
"app-crypto/std",
|
||||
]
|
||||
|
||||
+214
-252
@@ -37,7 +37,6 @@
|
||||
//!
|
||||
//! ### Public Functions
|
||||
//!
|
||||
//! - `is_online_in_current_era` - True if the validator sent a heartbeat in the current era.
|
||||
//! - `is_online_in_current_session` - True if the validator sent a heartbeat in the current session.
|
||||
//!
|
||||
//! ## Usage
|
||||
@@ -51,9 +50,9 @@
|
||||
//!
|
||||
//! decl_module! {
|
||||
//! pub struct Module<T: Trait> for enum Call where origin: T::Origin {
|
||||
//! pub fn is_online(origin, authority_id: T::AuthorityId) -> Result {
|
||||
//! pub fn is_online(origin, authority_index: u32) -> Result {
|
||||
//! let _sender = ensure_signed(origin)?;
|
||||
//! let _is_online = <im_online::Module<T>>::is_online_in_current_era(&authority_id);
|
||||
//! let _is_online = <im_online::Module<T>>::is_online_in_current_session(authority_index);
|
||||
//! Ok(())
|
||||
//! }
|
||||
//! }
|
||||
@@ -68,25 +67,39 @@
|
||||
// Ensure we're `no_std` when compiling for Wasm.
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
|
||||
use primitives::{
|
||||
crypto::TypedKey, offchain::CryptoKey,
|
||||
offchain::OpaqueNetworkState,
|
||||
offchain::StorageKind,
|
||||
sr25519, ed25519,
|
||||
};
|
||||
use primitives::offchain::{OpaqueNetworkState, StorageKind};
|
||||
use codec::{Encode, Decode};
|
||||
use sr_primitives::{
|
||||
ApplyError, traits::{Member, IsMember, Extrinsic as ExtrinsicT},
|
||||
ApplyError, traits::{Extrinsic as ExtrinsicT},
|
||||
transaction_validity::{TransactionValidity, TransactionLongevity, ValidTransaction},
|
||||
};
|
||||
use rstd::prelude::*;
|
||||
use session::SessionIndex;
|
||||
use sr_io::Printable;
|
||||
use srml_support::{
|
||||
Parameter, StorageValue, decl_module, decl_event, decl_storage,
|
||||
traits::Get, StorageDoubleMap, print,
|
||||
StorageValue, decl_module, decl_event, decl_storage, StorageDoubleMap, print,
|
||||
};
|
||||
use system::ensure_none;
|
||||
use app_crypto::RuntimeAppPublic;
|
||||
|
||||
mod app {
|
||||
pub use app_crypto::sr25519 as crypto;
|
||||
use app_crypto::{app_crypto, key_types::IM_ONLINE, sr25519};
|
||||
|
||||
app_crypto!(sr25519, IM_ONLINE);
|
||||
}
|
||||
|
||||
/// A Babe authority keypair. Necessarily equivalent to the schnorrkel public key used in
|
||||
/// the main Babe module. If that ever changes, then this must, too.
|
||||
#[cfg(feature = "std")]
|
||||
pub type AuthorityPair = app::Pair;
|
||||
|
||||
/// A Babe authority signature.
|
||||
pub type AuthoritySignature = app::Signature;
|
||||
|
||||
/// A Babe authority identifier. Necessarily equivalent to the schnorrkel public key used in
|
||||
/// the main Babe module. If that ever changes, then this must, too.
|
||||
pub type AuthorityId = app::Public;
|
||||
|
||||
// The local storage database key under which the worker progress status
|
||||
// is tracked.
|
||||
@@ -106,7 +119,6 @@ struct WorkerStatus<BlockNumber> {
|
||||
|
||||
// Error which may occur while executing the off-chain code.
|
||||
enum OffchainErr {
|
||||
DecodeAuthorityId,
|
||||
DecodeWorkerStatus,
|
||||
ExtrinsicCreation,
|
||||
FailedSigning,
|
||||
@@ -117,7 +129,6 @@ enum OffchainErr {
|
||||
impl Printable for OffchainErr {
|
||||
fn print(self) {
|
||||
match self {
|
||||
OffchainErr::DecodeAuthorityId => print("Offchain error: decoding AuthorityId failed!"),
|
||||
OffchainErr::DecodeWorkerStatus => print("Offchain error: decoding WorkerStatus failed!"),
|
||||
OffchainErr::ExtrinsicCreation => print("Offchain error: extrinsic creation failed!"),
|
||||
OffchainErr::FailedSigning => print("Offchain error: signing failed!"),
|
||||
@@ -127,272 +138,236 @@ impl Printable for OffchainErr {
|
||||
}
|
||||
}
|
||||
|
||||
/// Heartbeat which is send/received.
|
||||
pub type AuthIndex = u32;
|
||||
|
||||
/// Heartbeat which is sent/received.
|
||||
#[derive(Encode, Decode, Clone, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "std", derive(Debug))]
|
||||
pub struct Heartbeat<BlockNumber, AuthorityId>
|
||||
pub struct Heartbeat<BlockNumber>
|
||||
where BlockNumber: PartialEq + Eq + Decode + Encode,
|
||||
{
|
||||
block_number: BlockNumber,
|
||||
network_state: OpaqueNetworkState,
|
||||
session_index: session::SessionIndex,
|
||||
authority_id: AuthorityId,
|
||||
session_index: SessionIndex,
|
||||
authority_index: AuthIndex,
|
||||
}
|
||||
|
||||
pub trait Trait: system::Trait + session::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>;
|
||||
|
||||
/// The function call.
|
||||
type Call: From<Call<Self>>;
|
||||
|
||||
/// A extrinsic right from the external world. This is unchecked and so
|
||||
/// can contain a signature.
|
||||
type UncheckedExtrinsic: ExtrinsicT<Call=Self::Call> + Encode + Decode;
|
||||
|
||||
/// The identifier type for an authority.
|
||||
type AuthorityId: Member + Parameter + Default + TypedKey + Decode + Encode + AsRef<[u8]>;
|
||||
|
||||
/// Number of sessions per era.
|
||||
type SessionsPerEra: Get<SessionIndex>;
|
||||
|
||||
/// Determine if an `AuthorityId` is a valid authority.
|
||||
type IsValidAuthorityId: IsMember<Self::AuthorityId>;
|
||||
type UncheckedExtrinsic: ExtrinsicT<Call=<Self as Trait>::Call> + Encode + Decode;
|
||||
}
|
||||
|
||||
decl_event!(
|
||||
pub enum Event<T> where
|
||||
<T as system::Trait>::BlockNumber,
|
||||
<T as Trait>::AuthorityId
|
||||
{
|
||||
/// A new heartbeat was received at this `BlockNumber` from `AuthorityId`
|
||||
HeartbeatReceived(BlockNumber, AuthorityId),
|
||||
pub enum Event {
|
||||
/// A new heartbeat was received from `AuthorityId`
|
||||
HeartbeatReceived(AuthorityId),
|
||||
}
|
||||
);
|
||||
|
||||
decl_storage! {
|
||||
trait Store for Module<T: Trait> as ImOnline {
|
||||
// The block number when we should gossip.
|
||||
/// The block number when we should gossip.
|
||||
GossipAt get(gossip_at) config(): T::BlockNumber;
|
||||
|
||||
// The session index when the last new era started.
|
||||
LastNewEraStart get(last_new_era_start) config(): Option<session::SessionIndex>;
|
||||
/// The current set of keys that may issue a heartbeat.
|
||||
Keys get(keys) config(): Vec<AuthorityId>;
|
||||
|
||||
// For each session index we keep a mapping of `AuthorityId` to
|
||||
// `offchain::OpaqueNetworkState`.
|
||||
ReceivedHeartbeats get(received_heartbeats): double_map session::SessionIndex,
|
||||
blake2_256(T::AuthorityId) => Vec<u8>;
|
||||
/// For each session index we keep a mapping of `AuthorityId`
|
||||
/// to `offchain::OpaqueNetworkState`.
|
||||
ReceivedHeartbeats get(received_heartbeats): double_map SessionIndex,
|
||||
blake2_256(AuthIndex) => Vec<u8>;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
decl_module! {
|
||||
pub struct Module<T: Trait> for enum Call where origin: T::Origin {
|
||||
/// Number of sessions per era.
|
||||
const SessionsPerEra: session::SessionIndex = T::SessionsPerEra::get();
|
||||
|
||||
fn deposit_event<T>() = default;
|
||||
fn deposit_event() = default;
|
||||
|
||||
fn heartbeat(
|
||||
origin,
|
||||
heartbeat: Heartbeat<T::BlockNumber, T::AuthorityId>,
|
||||
_signature: Vec<u8>
|
||||
heartbeat: Heartbeat<T::BlockNumber>,
|
||||
_signature: AuthoritySignature
|
||||
) {
|
||||
ensure_none(origin)?;
|
||||
|
||||
let current_session = <session::Module<T>>::current_index();
|
||||
let exists = <ReceivedHeartbeats<T>>::exists(¤t_session, &heartbeat.authority_id);
|
||||
if !exists {
|
||||
let now = <system::Module<T>>::block_number();
|
||||
Self::deposit_event(RawEvent::HeartbeatReceived(now, heartbeat.authority_id.clone()));
|
||||
let exists = <ReceivedHeartbeats>::exists(
|
||||
¤t_session,
|
||||
&heartbeat.authority_index
|
||||
);
|
||||
let keys = Keys::get();
|
||||
let public = keys.get(heartbeat.authority_index as usize);
|
||||
if let (true, Some(public)) = (!exists, public) {
|
||||
Self::deposit_event(Event::HeartbeatReceived(public.clone()));
|
||||
|
||||
let network_state = heartbeat.network_state.encode();
|
||||
<ReceivedHeartbeats<T>>::insert(¤t_session, &heartbeat.authority_id, &network_state);
|
||||
<ReceivedHeartbeats>::insert(
|
||||
¤t_session,
|
||||
&heartbeat.authority_index,
|
||||
&network_state
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Runs after every block.
|
||||
fn offchain_worker(now: T::BlockNumber) {
|
||||
fn gossip_at<T: Trait>(block_number: T::BlockNumber) -> Result<(), OffchainErr> {
|
||||
// we run only when a local authority key is configured
|
||||
if let Ok(key) = sr_io::pubkey(CryptoKey::AuthorityKey) {
|
||||
let authority_id = <T as Trait>::AuthorityId::decode(&mut &key[..])
|
||||
.map_err(|_| OffchainErr::DecodeAuthorityId)?;
|
||||
let network_state =
|
||||
sr_io::network_state().map_err(|_| OffchainErr::NetworkState)?;
|
||||
let heartbeat_data = Heartbeat {
|
||||
block_number,
|
||||
network_state,
|
||||
session_index: <session::Module<T>>::current_index(),
|
||||
authority_id,
|
||||
};
|
||||
|
||||
let signature = sr_io::sign(CryptoKey::AuthorityKey, &heartbeat_data.encode())
|
||||
.map_err(|_| OffchainErr::FailedSigning)?;
|
||||
let call = Call::heartbeat(heartbeat_data, signature);
|
||||
let ex = T::UncheckedExtrinsic::new_unsigned(call.into())
|
||||
.ok_or(OffchainErr::ExtrinsicCreation)?;
|
||||
sr_io::submit_transaction(&ex)
|
||||
.map_err(|_| OffchainErr::SubmitTransaction)?;
|
||||
|
||||
// once finished we set the worker status without comparing
|
||||
// if the existing value changed in the meantime. this is
|
||||
// because at this point the heartbeat was definitely submitted.
|
||||
set_worker_status::<T>(block_number, true);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn compare_and_set_worker_status<T: Trait>(
|
||||
gossipping_at: T::BlockNumber,
|
||||
done: bool,
|
||||
curr_worker_status: Option<Vec<u8>>,
|
||||
) -> bool {
|
||||
let enc = WorkerStatus {
|
||||
done,
|
||||
gossipping_at,
|
||||
};
|
||||
sr_io::local_storage_compare_and_set(
|
||||
StorageKind::PERSISTENT,
|
||||
DB_KEY,
|
||||
curr_worker_status.as_ref().map(Vec::as_slice),
|
||||
&enc.encode()
|
||||
)
|
||||
}
|
||||
|
||||
fn set_worker_status<T: Trait>(
|
||||
gossipping_at: T::BlockNumber,
|
||||
done: bool,
|
||||
) {
|
||||
let enc = WorkerStatus {
|
||||
done,
|
||||
gossipping_at,
|
||||
};
|
||||
sr_io::local_storage_set(
|
||||
StorageKind::PERSISTENT, DB_KEY, &enc.encode());
|
||||
}
|
||||
|
||||
// Checks if a heartbeat gossip already occurred at this block number.
|
||||
// Returns a tuple of `(current worker status, bool)`, whereby the bool
|
||||
// is true if not yet gossipped.
|
||||
fn check_not_yet_gossipped<T: Trait>(
|
||||
now: T::BlockNumber,
|
||||
next_gossip: T::BlockNumber,
|
||||
) -> Result<(Option<Vec<u8>>, bool), OffchainErr> {
|
||||
let last_gossip = sr_io::local_storage_get(StorageKind::PERSISTENT, DB_KEY);
|
||||
match last_gossip {
|
||||
Some(last) => {
|
||||
let worker_status: WorkerStatus<T::BlockNumber> = Decode::decode(&mut &last[..])
|
||||
.map_err(|_| OffchainErr::DecodeWorkerStatus)?;
|
||||
|
||||
let was_aborted = !worker_status.done && worker_status.gossipping_at < now;
|
||||
|
||||
// another off-chain worker is currently in the process of submitting
|
||||
let already_submitting =
|
||||
!worker_status.done && worker_status.gossipping_at == now;
|
||||
|
||||
let not_yet_gossipped =
|
||||
worker_status.done && worker_status.gossipping_at < next_gossip;
|
||||
|
||||
let ret = (was_aborted && !already_submitting) || not_yet_gossipped;
|
||||
Ok((Some(last), ret))
|
||||
},
|
||||
None => Ok((None, true)),
|
||||
}
|
||||
}
|
||||
|
||||
let next_gossip = <GossipAt<T>>::get();
|
||||
let check = check_not_yet_gossipped::<T>(now, next_gossip);
|
||||
let (curr_worker_status, not_yet_gossipped) = match check {
|
||||
Ok((s, v)) => (s, v),
|
||||
Err(err) => {
|
||||
print(err);
|
||||
return;
|
||||
},
|
||||
};
|
||||
if next_gossip < now && not_yet_gossipped {
|
||||
let value_set = compare_and_set_worker_status::<T>(now, false, curr_worker_status);
|
||||
if !value_set {
|
||||
// value could not be set in local storage, since the value was
|
||||
// different from `curr_worker_status`. this indicates that
|
||||
// another worker was running in parallel.
|
||||
return;
|
||||
}
|
||||
|
||||
match gossip_at::<T>(now) {
|
||||
Ok(_) => {},
|
||||
Err(err) => print(err),
|
||||
}
|
||||
}
|
||||
Self::offchain(now);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Trait> Module<T> {
|
||||
/// Returns `true` if a heartbeat has been received for `AuthorityId`
|
||||
/// during the current era. Otherwise `false`.
|
||||
pub fn is_online_in_current_era(authority_id: &T::AuthorityId) -> bool {
|
||||
let curr = <session::Module<T>>::current_index();
|
||||
match LastNewEraStart::get() {
|
||||
Some(start) => {
|
||||
// iterate over every session
|
||||
for index in start..curr {
|
||||
if <ReceivedHeartbeats<T>>::exists(&index, authority_id) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
},
|
||||
None => <ReceivedHeartbeats<T>>::exists(&curr, authority_id),
|
||||
}
|
||||
/// Returns `true` if a heartbeat has been received for the authority at `authority_index` in
|
||||
/// the authorities series, during the current session. Otherwise `false`.
|
||||
pub fn is_online_in_current_session(authority_index: AuthIndex) -> bool {
|
||||
let current_session = <session::Module<T>>::current_index();
|
||||
<ReceivedHeartbeats>::exists(¤t_session, &authority_index)
|
||||
}
|
||||
|
||||
/// Returns `true` if a heartbeat has been received for `AuthorityId`
|
||||
/// during the current session. Otherwise `false`.
|
||||
pub fn is_online_in_current_session(authority_id: &T::AuthorityId) -> bool {
|
||||
let current_session = <session::Module<T>>::current_index();
|
||||
<ReceivedHeartbeats<T>>::exists(¤t_session, authority_id)
|
||||
}
|
||||
|
||||
/// Session has just changed.
|
||||
fn new_session() {
|
||||
let now = <system::Module<T>>::block_number();
|
||||
<GossipAt<T>>::put(now);
|
||||
|
||||
let current_session = <session::Module<T>>::current_index();
|
||||
|
||||
match LastNewEraStart::get() {
|
||||
Some(last_new_era_start) => {
|
||||
let sessions_per_era = T::SessionsPerEra::get();
|
||||
|
||||
let new_era = current_session - last_new_era_start > sessions_per_era;
|
||||
if new_era {
|
||||
LastNewEraStart::put(current_session);
|
||||
Self::remove_heartbeats();
|
||||
}
|
||||
fn offchain(now: T::BlockNumber) {
|
||||
let next_gossip = <GossipAt<T>>::get();
|
||||
let check = Self::check_not_yet_gossipped(now, next_gossip);
|
||||
let (curr_worker_status, not_yet_gossipped) = match check {
|
||||
Ok((s, v)) => (s, v),
|
||||
Err(err) => {
|
||||
print(err);
|
||||
return;
|
||||
},
|
||||
None => LastNewEraStart::put(current_session),
|
||||
};
|
||||
}
|
||||
if next_gossip < now && not_yet_gossipped {
|
||||
let value_set = Self::compare_and_set_worker_status(now, false, curr_worker_status);
|
||||
if !value_set {
|
||||
// value could not be set in local storage, since the value was
|
||||
// different from `curr_worker_status`. this indicates that
|
||||
// another worker was running in parallel.
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove all stored heartbeats.
|
||||
fn remove_heartbeats() {
|
||||
let curr = <session::Module<T>>::current_index();
|
||||
match LastNewEraStart::get() {
|
||||
Some(start) => {
|
||||
for index in start..curr {
|
||||
<ReceivedHeartbeats<T>>::remove_prefix(&index);
|
||||
}
|
||||
},
|
||||
None => <ReceivedHeartbeats<T>>::remove_prefix(&curr),
|
||||
match Self::do_gossip_at(now) {
|
||||
Ok(_) => {},
|
||||
Err(err) => print(err),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn do_gossip_at(block_number: T::BlockNumber) -> Result<(), OffchainErr> {
|
||||
// we run only when a local authority key is configured
|
||||
let authorities = Keys::get();
|
||||
let mut local_keys = app::Public::all();
|
||||
local_keys.sort();
|
||||
|
||||
for (authority_index, key) in authorities.into_iter()
|
||||
.enumerate()
|
||||
.filter_map(|(index, authority)| {
|
||||
local_keys.binary_search(&authority)
|
||||
.ok()
|
||||
.map(|location| (index as u32, &local_keys[location]))
|
||||
})
|
||||
{
|
||||
let network_state = sr_io::network_state().map_err(|_| OffchainErr::NetworkState)?;
|
||||
let heartbeat_data = Heartbeat {
|
||||
block_number,
|
||||
network_state,
|
||||
session_index: <session::Module<T>>::current_index(),
|
||||
authority_index,
|
||||
};
|
||||
|
||||
let signature = key.sign(&heartbeat_data.encode()).ok_or(OffchainErr::FailedSigning)?;
|
||||
let call = Call::heartbeat(heartbeat_data, signature);
|
||||
let ex = T::UncheckedExtrinsic::new_unsigned(call.into())
|
||||
.ok_or(OffchainErr::ExtrinsicCreation)?;
|
||||
sr_io::submit_transaction(&ex).map_err(|_| OffchainErr::SubmitTransaction)?;
|
||||
|
||||
// once finished we set the worker status without comparing
|
||||
// if the existing value changed in the meantime. this is
|
||||
// because at this point the heartbeat was definitely submitted.
|
||||
Self::set_worker_status(block_number, true);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn compare_and_set_worker_status(
|
||||
gossipping_at: T::BlockNumber,
|
||||
done: bool,
|
||||
curr_worker_status: Option<Vec<u8>>,
|
||||
) -> bool {
|
||||
let enc = WorkerStatus {
|
||||
done,
|
||||
gossipping_at,
|
||||
};
|
||||
sr_io::local_storage_compare_and_set(
|
||||
StorageKind::PERSISTENT,
|
||||
DB_KEY,
|
||||
curr_worker_status.as_ref().map(Vec::as_slice),
|
||||
&enc.encode()
|
||||
)
|
||||
}
|
||||
|
||||
fn set_worker_status(
|
||||
gossipping_at: T::BlockNumber,
|
||||
done: bool,
|
||||
) {
|
||||
let enc = WorkerStatus {
|
||||
done,
|
||||
gossipping_at,
|
||||
};
|
||||
sr_io::local_storage_set(
|
||||
StorageKind::PERSISTENT, DB_KEY, &enc.encode());
|
||||
}
|
||||
|
||||
// Checks if a heartbeat gossip already occurred at this block number.
|
||||
// Returns a tuple of `(current worker status, bool)`, whereby the bool
|
||||
// is true if not yet gossipped.
|
||||
fn check_not_yet_gossipped(
|
||||
now: T::BlockNumber,
|
||||
next_gossip: T::BlockNumber,
|
||||
) -> Result<(Option<Vec<u8>>, bool), OffchainErr> {
|
||||
let last_gossip = sr_io::local_storage_get(StorageKind::PERSISTENT, DB_KEY);
|
||||
match last_gossip {
|
||||
Some(last) => {
|
||||
let worker_status: WorkerStatus<T::BlockNumber> = Decode::decode(&mut &last[..])
|
||||
.map_err(|_| OffchainErr::DecodeWorkerStatus)?;
|
||||
|
||||
let was_aborted = !worker_status.done && worker_status.gossipping_at < now;
|
||||
|
||||
// another off-chain worker is currently in the process of submitting
|
||||
let already_submitting =
|
||||
!worker_status.done && worker_status.gossipping_at == now;
|
||||
|
||||
let not_yet_gossipped =
|
||||
worker_status.done && worker_status.gossipping_at < next_gossip;
|
||||
|
||||
let ret = (was_aborted && !already_submitting) || not_yet_gossipped;
|
||||
Ok((Some(last), ret))
|
||||
},
|
||||
None => Ok((None, true)),
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
impl<T: Trait> session::OneSessionHandler<T::AccountId> for Module<T> {
|
||||
type Key = <T as Trait>::AuthorityId;
|
||||
type Key = AuthorityId;
|
||||
|
||||
fn on_new_session<'a, I: 'a>(_changed: bool, _validators: I, _next_validators: I) {
|
||||
Self::new_session();
|
||||
fn on_new_session<'a, I: 'a>(_changed: bool, _validators: I, next_validators: I)
|
||||
where I: Iterator<Item=(&'a T::AccountId, AuthorityId)>
|
||||
{
|
||||
// Reset heartbeats
|
||||
<ReceivedHeartbeats>::remove_prefix(&<session::Module<T>>::current_index());
|
||||
|
||||
// Tell the offchain worker to start making the next session's heartbeats.
|
||||
<GossipAt<T>>::put(<system::Module<T>>::block_number());
|
||||
|
||||
// Remember who the authorities are for the new session.
|
||||
Keys::put(next_validators.map(|x| x.1).collect::<Vec<_>>());
|
||||
}
|
||||
|
||||
fn on_disabled(_i: usize) {
|
||||
@@ -405,55 +380,42 @@ impl<T: Trait> srml_support::unsigned::ValidateUnsigned for Module<T> {
|
||||
|
||||
fn validate_unsigned(call: &Self::Call) -> srml_support::unsigned::TransactionValidity {
|
||||
if let Call::heartbeat(heartbeat, signature) = call {
|
||||
// verify that the incoming (unverified) pubkey is actually an authority id
|
||||
let is_authority = T::IsValidAuthorityId::is_member(&heartbeat.authority_id);
|
||||
if !is_authority {
|
||||
return TransactionValidity::Invalid(ApplyError::BadSignature as i8);
|
||||
}
|
||||
|
||||
if <Module<T>>::is_online_in_current_session(&heartbeat.authority_id) {
|
||||
if <Module<T>>::is_online_in_current_session(heartbeat.authority_index) {
|
||||
// we already received a heartbeat for this authority
|
||||
return TransactionValidity::Invalid(ApplyError::BadSignature as i8);
|
||||
}
|
||||
|
||||
if signature.len() != 64 {
|
||||
return TransactionValidity::Invalid(ApplyError::BadSignature as i8);
|
||||
}
|
||||
|
||||
let signature = {
|
||||
let mut array = [0; 64];
|
||||
array.copy_from_slice(&signature); // panics if not enough, hence the check above
|
||||
array
|
||||
};
|
||||
|
||||
let encoded_heartbeat = heartbeat.encode();
|
||||
|
||||
let signature_valid = match <T::AuthorityId as TypedKey>::KEY_TYPE {
|
||||
ed25519::Public::KEY_TYPE =>
|
||||
sr_io::ed25519_verify(&signature, &encoded_heartbeat, &heartbeat.authority_id),
|
||||
sr25519::Public::KEY_TYPE =>
|
||||
sr_io::sr25519_verify(&signature, &encoded_heartbeat, &heartbeat.authority_id),
|
||||
_ => return TransactionValidity::Invalid(ApplyError::BadSignature as i8),
|
||||
};
|
||||
|
||||
if !signature_valid {
|
||||
return TransactionValidity::Invalid(ApplyError::BadSignature as i8);
|
||||
return TransactionValidity::Invalid(ApplyError::Stale as i8);
|
||||
}
|
||||
|
||||
// check if session index from heartbeat is recent
|
||||
let current_session = <session::Module<T>>::current_index();
|
||||
if heartbeat.session_index < current_session {
|
||||
if heartbeat.session_index != current_session {
|
||||
return TransactionValidity::Invalid(ApplyError::Stale as i8);
|
||||
}
|
||||
|
||||
// verify that the incoming (unverified) pubkey is actually an authority id
|
||||
let keys = Keys::get();
|
||||
let authority_id = match keys.get(heartbeat.authority_index as usize) {
|
||||
Some(id) => id,
|
||||
None => return TransactionValidity::Invalid(ApplyError::BadSignature as i8),
|
||||
};
|
||||
|
||||
// check signature (this is expensive so we do it last).
|
||||
let signature_valid = heartbeat.using_encoded(|encoded_heartbeat| {
|
||||
authority_id.verify(&encoded_heartbeat, &signature)
|
||||
});
|
||||
|
||||
if !signature_valid {
|
||||
return TransactionValidity::Invalid(ApplyError::BadSignature as i8);
|
||||
}
|
||||
|
||||
return TransactionValidity::Valid(ValidTransaction {
|
||||
priority: 0,
|
||||
requires: vec![],
|
||||
provides: vec![encoded_heartbeat],
|
||||
provides: vec![(current_session, authority_id).encode()],
|
||||
longevity: TransactionLongevity::max_value(),
|
||||
propagate: true,
|
||||
})
|
||||
}
|
||||
|
||||
TransactionValidity::Invalid(0)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,6 +75,7 @@ impl system::Trait for Runtime {
|
||||
type Origin = Origin;
|
||||
type Index = u64;
|
||||
type BlockNumber = u64;
|
||||
type Call = ();
|
||||
type Hash = H256;
|
||||
type Hashing = ::sr_primitives::traits::BlakeTwo256;
|
||||
type AccountId = u64;
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
[package]
|
||||
name = "srml-membership"
|
||||
version = "2.0.0"
|
||||
authors = ["Parity Technologies <admin@parity.io>"]
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
serde = { version = "1.0", optional = true }
|
||||
codec = { package = "parity-scale-codec", version = "1.0.0", default-features = false }
|
||||
sr-std = { path = "../../core/sr-std", default-features = false }
|
||||
sr-io = { path = "../../core/sr-io", default-features = false }
|
||||
srml-support = { path = "../support", default-features = false }
|
||||
system = { package = "srml-system", path = "../system", default-features = false }
|
||||
sr-primitives = { path = "../../core/sr-primitives", default-features = false }
|
||||
|
||||
[dev-dependencies]
|
||||
primitives = { package = "substrate-primitives", path = "../../core/primitives" }
|
||||
|
||||
[features]
|
||||
default = ["std"]
|
||||
std = [
|
||||
"serde",
|
||||
"codec/std",
|
||||
"sr-primitives/std",
|
||||
"sr-std/std",
|
||||
"sr-io/std",
|
||||
"srml-support/std",
|
||||
"system/std",
|
||||
]
|
||||
@@ -0,0 +1,347 @@
|
||||
// Copyright 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/>.
|
||||
|
||||
//! # Membership Module
|
||||
//!
|
||||
//! Allows control of membership of a set of `AccountId`s, useful for managing membership of of a
|
||||
//! collective.
|
||||
|
||||
// Ensure we're `no_std` when compiling for Wasm.
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
|
||||
use sr_std::prelude::*;
|
||||
use srml_support::{
|
||||
StorageValue, decl_module, decl_storage, decl_event,
|
||||
traits::{ChangeMembers}
|
||||
};
|
||||
use system::ensure_root;
|
||||
use sr_primitives::{traits::EnsureOrigin, weights::SimpleDispatchInfo};
|
||||
|
||||
pub trait Trait<I=DefaultInstance>: system::Trait {
|
||||
/// The overarching event type.
|
||||
type Event: From<Event<Self, I>> + Into<<Self as system::Trait>::Event>;
|
||||
|
||||
/// Required origin for adding a member (though can always be Root).
|
||||
type AddOrigin: EnsureOrigin<Self::Origin>;
|
||||
|
||||
/// Required origin for removing a member (though can always be Root).
|
||||
type RemoveOrigin: EnsureOrigin<Self::Origin>;
|
||||
|
||||
/// Required origin for adding and removing a member in a single action.
|
||||
type SwapOrigin: EnsureOrigin<Self::Origin>;
|
||||
|
||||
/// Required origin for resetting membership.
|
||||
type ResetOrigin: EnsureOrigin<Self::Origin>;
|
||||
|
||||
/// The receiver of the signal for when the membership has been initialized. This happens pre-
|
||||
/// genesis and will usually be the same as `MembershipChanged`. If you need to do something
|
||||
/// different on initialization, then you can change this accordingly.
|
||||
type MembershipInitialized: ChangeMembers<Self::AccountId>;
|
||||
|
||||
/// The receiver of the signal for when the membership has changed.
|
||||
type MembershipChanged: ChangeMembers<Self::AccountId>;
|
||||
}
|
||||
|
||||
decl_storage! {
|
||||
trait Store for Module<T: Trait<I>, I: Instance=DefaultInstance> as Membership {
|
||||
/// The current membership, stored as an ordered Vec.
|
||||
Members get(members): Vec<T::AccountId>;
|
||||
}
|
||||
add_extra_genesis {
|
||||
config(members): Vec<T::AccountId>;
|
||||
config(phantom): sr_std::marker::PhantomData<I>;
|
||||
build(|
|
||||
storage: &mut sr_primitives::StorageOverlay,
|
||||
_: &mut sr_primitives::ChildrenStorageOverlay,
|
||||
config: &GenesisConfig<T, I>
|
||||
| {
|
||||
sr_io::with_storage(storage, || {
|
||||
let mut members = config.members.clone();
|
||||
members.sort();
|
||||
T::MembershipInitialized::set_members_sorted(&members[..], &[]);
|
||||
<Members<T, I>>::put(members);
|
||||
});
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
decl_event!(
|
||||
pub enum Event<T, I=DefaultInstance> where
|
||||
<T as system::Trait>::AccountId,
|
||||
{
|
||||
/// The given member was added; see the transaction for who.
|
||||
MemberAdded,
|
||||
/// The given member was removed; see the transaction for who.
|
||||
MemberRemoved,
|
||||
/// Two members were swapped; see the transaction for who.
|
||||
MembersSwapped,
|
||||
/// The membership was reset; see the transaction for who the new set is.
|
||||
MembersReset,
|
||||
/// Phantom member, never used.
|
||||
Dummy(sr_std::marker::PhantomData<(AccountId, I)>),
|
||||
}
|
||||
);
|
||||
|
||||
decl_module! {
|
||||
pub struct Module<T: Trait<I>, I: Instance=DefaultInstance>
|
||||
for enum Call
|
||||
where origin: T::Origin
|
||||
{
|
||||
fn deposit_event<T, I>() = default;
|
||||
|
||||
/// Add a member `who` to the set.
|
||||
///
|
||||
/// May only be called from `AddOrigin` or root.
|
||||
#[weight = SimpleDispatchInfo::FixedNormal(50_000)]
|
||||
fn add_member(origin, who: T::AccountId) {
|
||||
T::AddOrigin::try_origin(origin)
|
||||
.map(|_| ())
|
||||
.or_else(ensure_root)
|
||||
.map_err(|_| "bad origin")?;
|
||||
|
||||
let mut members = <Members<T, I>>::get();
|
||||
let location = members.binary_search(&who).err().ok_or("already a member")?;
|
||||
members.insert(location, who.clone());
|
||||
<Members<T, I>>::put(&members);
|
||||
|
||||
T::MembershipChanged::change_members_sorted(&[who], &[], &members[..]);
|
||||
|
||||
Self::deposit_event(RawEvent::MemberAdded);
|
||||
}
|
||||
|
||||
/// Remove a member `who` from the set.
|
||||
///
|
||||
/// May only be called from `RemoveOrigin` or root.
|
||||
#[weight = SimpleDispatchInfo::FixedNormal(50_000)]
|
||||
fn remove_member(origin, who: T::AccountId) {
|
||||
T::RemoveOrigin::try_origin(origin)
|
||||
.map(|_| ())
|
||||
.or_else(ensure_root)
|
||||
.map_err(|_| "bad origin")?;
|
||||
|
||||
let mut members = <Members<T, I>>::get();
|
||||
let location = members.binary_search(&who).ok().ok_or("not a member")?;
|
||||
members.remove(location);
|
||||
<Members<T, I>>::put(&members);
|
||||
|
||||
T::MembershipChanged::change_members_sorted(&[], &[who], &members[..]);
|
||||
|
||||
Self::deposit_event(RawEvent::MemberRemoved);
|
||||
}
|
||||
|
||||
/// Swap out one member `remove` for another `add`.
|
||||
///
|
||||
/// May only be called from `SwapOrigin` or root.
|
||||
#[weight = SimpleDispatchInfo::FixedNormal(50_000)]
|
||||
fn swap_member(origin, remove: T::AccountId, add: T::AccountId) {
|
||||
T::SwapOrigin::try_origin(origin)
|
||||
.map(|_| ())
|
||||
.or_else(ensure_root)
|
||||
.map_err(|_| "bad origin")?;
|
||||
|
||||
if remove == add { return Ok(()) }
|
||||
|
||||
let mut members = <Members<T, I>>::get();
|
||||
let location = members.binary_search(&remove).ok().ok_or("not a member")?;
|
||||
members[location] = add.clone();
|
||||
let _location = members.binary_search(&add).err().ok_or("already a member")?;
|
||||
members.sort();
|
||||
<Members<T, I>>::put(&members);
|
||||
|
||||
T::MembershipChanged::change_members_sorted(
|
||||
&[add],
|
||||
&[remove],
|
||||
&members[..],
|
||||
);
|
||||
|
||||
Self::deposit_event(RawEvent::MembersSwapped);
|
||||
}
|
||||
|
||||
/// Change the membership to a new set, disregarding the existing membership. Be nice and
|
||||
/// pass `members` pre-sorted.
|
||||
///
|
||||
/// May only be called from `ResetOrigin` or root.
|
||||
#[weight = SimpleDispatchInfo::FixedNormal(50_000)]
|
||||
fn reset_members(origin, members: Vec<T::AccountId>) {
|
||||
T::ResetOrigin::try_origin(origin)
|
||||
.map(|_| ())
|
||||
.or_else(ensure_root)
|
||||
.map_err(|_| "bad origin")?;
|
||||
|
||||
let mut members = members;
|
||||
members.sort();
|
||||
<Members<T, I>>::mutate(|m| {
|
||||
T::MembershipChanged::set_members_sorted(&members[..], m);
|
||||
*m = members;
|
||||
});
|
||||
|
||||
Self::deposit_event(RawEvent::MembersReset);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
use std::cell::RefCell;
|
||||
use srml_support::{assert_ok, assert_noop, impl_outer_origin, parameter_types};
|
||||
use sr_io::with_externalities;
|
||||
use primitives::{H256, Blake2Hasher};
|
||||
// The testing primitives are very useful for avoiding having to work with signatures
|
||||
// or public keys. `u64` is used as the `AccountId` and no `Signature`s are requried.
|
||||
use sr_primitives::{
|
||||
Perbill, traits::{BlakeTwo256, IdentityLookup}, testing::Header
|
||||
};
|
||||
use system::EnsureSignedBy;
|
||||
|
||||
impl_outer_origin! {
|
||||
pub enum Origin for Test {}
|
||||
}
|
||||
|
||||
// For testing the module, we construct most of a mock runtime. This means
|
||||
// first constructing a configuration type (`Test`) which `impl`s each of the
|
||||
// configuration traits of modules we want to use.
|
||||
#[derive(Clone, Eq, PartialEq)]
|
||||
pub struct Test;
|
||||
parameter_types! {
|
||||
pub const BlockHashCount: u64 = 250;
|
||||
pub const MaximumBlockWeight: u32 = 1024;
|
||||
pub const MaximumBlockLength: u32 = 2 * 1024;
|
||||
pub const AvailableBlockRatio: Perbill = Perbill::one();
|
||||
}
|
||||
impl system::Trait for Test {
|
||||
type Origin = Origin;
|
||||
type Index = u64;
|
||||
type BlockNumber = u64;
|
||||
type Hash = H256;
|
||||
type Call = ();
|
||||
type Hashing = BlakeTwo256;
|
||||
type AccountId = u64;
|
||||
type Lookup = IdentityLookup<Self::AccountId>;
|
||||
type Header = Header;
|
||||
type WeightMultiplierUpdate = ();
|
||||
type Event = ();
|
||||
type BlockHashCount = BlockHashCount;
|
||||
type MaximumBlockWeight = MaximumBlockWeight;
|
||||
type MaximumBlockLength = MaximumBlockLength;
|
||||
type AvailableBlockRatio = AvailableBlockRatio;
|
||||
}
|
||||
parameter_types! {
|
||||
pub const One: u64 = 1;
|
||||
pub const Two: u64 = 2;
|
||||
pub const Three: u64 = 3;
|
||||
pub const Four: u64 = 4;
|
||||
pub const Five: u64 = 5;
|
||||
}
|
||||
|
||||
thread_local! {
|
||||
static MEMBERS: RefCell<Vec<u64>> = RefCell::new(vec![]);
|
||||
}
|
||||
|
||||
pub struct TestChangeMembers;
|
||||
impl ChangeMembers<u64> for TestChangeMembers {
|
||||
fn change_members_sorted(incoming: &[u64], outgoing: &[u64], new: &[u64]) {
|
||||
let mut old_plus_incoming = MEMBERS.with(|m| m.borrow().to_vec());
|
||||
old_plus_incoming.extend_from_slice(incoming);
|
||||
old_plus_incoming.sort();
|
||||
let mut new_plus_outgoing = new.to_vec();
|
||||
new_plus_outgoing.extend_from_slice(outgoing);
|
||||
new_plus_outgoing.sort();
|
||||
assert_eq!(old_plus_incoming, new_plus_outgoing);
|
||||
|
||||
MEMBERS.with(|m| *m.borrow_mut() = new.to_vec());
|
||||
}
|
||||
}
|
||||
|
||||
impl Trait for Test {
|
||||
type Event = ();
|
||||
type AddOrigin = EnsureSignedBy<One, u64>;
|
||||
type RemoveOrigin = EnsureSignedBy<Two, u64>;
|
||||
type SwapOrigin = EnsureSignedBy<Three, u64>;
|
||||
type ResetOrigin = EnsureSignedBy<Four, u64>;
|
||||
type MembershipInitialized = TestChangeMembers;
|
||||
type MembershipChanged = TestChangeMembers;
|
||||
}
|
||||
|
||||
type Membership = Module<Test>;
|
||||
|
||||
// This function basically just builds a genesis storage key/value store according to
|
||||
// our desired mockup.
|
||||
fn new_test_ext() -> sr_io::TestExternalities<Blake2Hasher> {
|
||||
let mut t = system::GenesisConfig::default().build_storage::<Test>().unwrap().0;
|
||||
// We use default for brevity, but you can configure as desired if needed.
|
||||
t.extend(GenesisConfig::<Test>{
|
||||
members: vec![10, 20, 30],
|
||||
.. Default::default()
|
||||
}.build_storage().unwrap().0);
|
||||
t.into()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn query_membership_works() {
|
||||
with_externalities(&mut new_test_ext(), || {
|
||||
assert_eq!(Membership::members(), vec![10, 20, 30]);
|
||||
assert_eq!(MEMBERS.with(|m| m.borrow().clone()), vec![10, 20, 30]);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn add_member_works() {
|
||||
with_externalities(&mut new_test_ext(), || {
|
||||
assert_noop!(Membership::add_member(Origin::signed(5), 15), "bad origin");
|
||||
assert_noop!(Membership::add_member(Origin::signed(1), 10), "already a member");
|
||||
assert_ok!(Membership::add_member(Origin::signed(1), 15));
|
||||
assert_eq!(Membership::members(), vec![10, 15, 20, 30]);
|
||||
assert_eq!(MEMBERS.with(|m| m.borrow().clone()), Membership::members());
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn remove_member_works() {
|
||||
with_externalities(&mut new_test_ext(), || {
|
||||
assert_noop!(Membership::remove_member(Origin::signed(5), 20), "bad origin");
|
||||
assert_noop!(Membership::remove_member(Origin::signed(2), 15), "not a member");
|
||||
assert_ok!(Membership::remove_member(Origin::signed(2), 20));
|
||||
assert_eq!(Membership::members(), vec![10, 30]);
|
||||
assert_eq!(MEMBERS.with(|m| m.borrow().clone()), Membership::members());
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn swap_member_works() {
|
||||
with_externalities(&mut new_test_ext(), || {
|
||||
assert_noop!(Membership::swap_member(Origin::signed(5), 10, 25), "bad origin");
|
||||
assert_noop!(Membership::swap_member(Origin::signed(3), 15, 25), "not a member");
|
||||
assert_noop!(Membership::swap_member(Origin::signed(3), 10, 30), "already a member");
|
||||
assert_ok!(Membership::swap_member(Origin::signed(3), 20, 20));
|
||||
assert_eq!(Membership::members(), vec![10, 20, 30]);
|
||||
assert_ok!(Membership::swap_member(Origin::signed(3), 10, 25));
|
||||
assert_eq!(Membership::members(), vec![20, 25, 30]);
|
||||
assert_eq!(MEMBERS.with(|m| m.borrow().clone()), Membership::members());
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reset_members_works() {
|
||||
with_externalities(&mut new_test_ext(), || {
|
||||
assert_noop!(Membership::reset_members(Origin::signed(1), vec![20, 40, 30]), "bad origin");
|
||||
assert_ok!(Membership::reset_members(Origin::signed(4), vec![20, 40, 30]));
|
||||
assert_eq!(Membership::members(), vec![20, 30, 40]);
|
||||
assert_eq!(MEMBERS.with(|m| m.borrow().clone()), Membership::members());
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -18,6 +18,7 @@ runtime_io = { package = "sr-io", path = "../../core/sr-io", default-features =
|
||||
|
||||
[dev-dependencies]
|
||||
primitives = { package = "substrate-primitives", path = "../../core/primitives" }
|
||||
app-crypto = { package = "substrate-application-crypto", path = "../../core/application-crypto" }
|
||||
lazy_static = "1.0"
|
||||
|
||||
[features]
|
||||
|
||||
@@ -100,9 +100,9 @@ impl<T: Trait> Module<T> {
|
||||
/// Specialization of the crate-level `OnSessionEnding` which returns the old
|
||||
/// set of full identification when changing the validator set.
|
||||
pub trait OnSessionEnding<ValidatorId, FullIdentification>: crate::OnSessionEnding<ValidatorId> {
|
||||
/// Returns the set of new validators, if any, along with the old validators
|
||||
/// and their full identifications.
|
||||
fn on_session_ending(ending: SessionIndex, applied_at: SessionIndex)
|
||||
/// If there was a validator set change, its returns the set of new validators along with the
|
||||
/// old validators and their full identifications.
|
||||
fn on_session_ending(ending: SessionIndex, will_apply_at: SessionIndex)
|
||||
-> Option<(Vec<ValidatorId>, Vec<(ValidatorId, FullIdentification)>)>;
|
||||
}
|
||||
|
||||
@@ -312,11 +312,8 @@ impl<T: Trait, D: AsRef<[u8]>> srml_support::traits::KeyOwnerProofSystem<(KeyTyp
|
||||
mod tests {
|
||||
use super::*;
|
||||
use runtime_io::with_externalities;
|
||||
use primitives::Blake2Hasher;
|
||||
use sr_primitives::{
|
||||
traits::OnInitialize,
|
||||
testing::{UintAuthorityId, UINT_DUMMY_KEY},
|
||||
};
|
||||
use primitives::{Blake2Hasher, crypto::key_types::DUMMY};
|
||||
use sr_primitives::{traits::OnInitialize, testing::UintAuthorityId};
|
||||
use crate::mock::{
|
||||
NEXT_VALIDATORS, force_new_session,
|
||||
set_next_validators, Test, System, Session,
|
||||
@@ -329,7 +326,7 @@ mod tests {
|
||||
let mut t = system::GenesisConfig::default().build_storage::<Test>().unwrap().0;
|
||||
let (storage, _child_storage) = crate::GenesisConfig::<Test> {
|
||||
keys: NEXT_VALIDATORS.with(|l|
|
||||
l.borrow().iter().cloned().map(|i| (i, UintAuthorityId(i))).collect()
|
||||
l.borrow().iter().cloned().map(|i| (i, UintAuthorityId(i).into())).collect()
|
||||
),
|
||||
}.build_storage().unwrap();
|
||||
t.extend(storage);
|
||||
@@ -346,15 +343,10 @@ mod tests {
|
||||
Session::on_initialize(1);
|
||||
|
||||
let encoded_key_1 = UintAuthorityId(1).encode();
|
||||
let proof = Historical::prove((UINT_DUMMY_KEY, &encoded_key_1[..])).unwrap();
|
||||
let proof = Historical::prove((DUMMY, &encoded_key_1[..])).unwrap();
|
||||
|
||||
// proof-checking in the same session is OK.
|
||||
assert!(
|
||||
Historical::check_proof(
|
||||
(UINT_DUMMY_KEY, &encoded_key_1[..]),
|
||||
proof.clone(),
|
||||
).is_some()
|
||||
);
|
||||
assert!(Historical::check_proof((DUMMY, &encoded_key_1[..]), proof.clone()).is_some());
|
||||
|
||||
set_next_validators(vec![1, 2, 4]);
|
||||
force_new_session();
|
||||
@@ -370,12 +362,7 @@ mod tests {
|
||||
assert!(Session::current_index() > proof.session);
|
||||
|
||||
// proof-checking in the next session is also OK.
|
||||
assert!(
|
||||
Historical::check_proof(
|
||||
(UINT_DUMMY_KEY, &encoded_key_1[..]),
|
||||
proof.clone(),
|
||||
).is_some()
|
||||
);
|
||||
assert!(Historical::check_proof((DUMMY, &encoded_key_1[..]), proof.clone()).is_some());
|
||||
|
||||
set_next_validators(vec![1, 2, 5]);
|
||||
|
||||
|
||||
@@ -121,9 +121,9 @@
|
||||
|
||||
use rstd::{prelude::*, marker::PhantomData, ops::{Sub, Rem}};
|
||||
use codec::Decode;
|
||||
use sr_primitives::KeyTypeId;
|
||||
use sr_primitives::{KeyTypeId, AppKey};
|
||||
use sr_primitives::weights::SimpleDispatchInfo;
|
||||
use sr_primitives::traits::{Convert, Zero, Member, OpaqueKeys, TypedKey};
|
||||
use sr_primitives::traits::{Convert, Zero, Member, OpaqueKeys};
|
||||
use srml_support::{
|
||||
dispatch::Result, ConsensusEngineId, StorageValue, StorageDoubleMap, for_each_tuple,
|
||||
decl_module, decl_event, decl_storage,
|
||||
@@ -172,10 +172,13 @@ pub trait OnSessionEnding<ValidatorId> {
|
||||
/// Handle the fact that the session is ending, and optionally provide the new validator set.
|
||||
///
|
||||
/// `ending_index` is the index of the currently ending session.
|
||||
/// The returned validator set, if any, will not be applied until `next_index`.
|
||||
/// `next_index` is guaranteed to be at least `ending_index + 1`, since session indices don't
|
||||
/// repeat.
|
||||
fn on_session_ending(ending_index: SessionIndex, next_index: SessionIndex) -> Option<Vec<ValidatorId>>;
|
||||
/// The returned validator set, if any, will not be applied until `will_apply_at`.
|
||||
/// `will_apply_at` is guaranteed to be at least `ending_index + 1`, since session indices don't
|
||||
/// repeat, but it could be some time after in case we are staging authority set changes.
|
||||
fn on_session_ending(
|
||||
ending_index: SessionIndex,
|
||||
will_apply_at: SessionIndex
|
||||
) -> Option<Vec<ValidatorId>>;
|
||||
}
|
||||
|
||||
impl<A> OnSessionEnding<A> for () {
|
||||
@@ -198,7 +201,7 @@ pub trait SessionHandler<ValidatorId> {
|
||||
/// One session-key type handler.
|
||||
pub trait OneSessionHandler<ValidatorId> {
|
||||
/// The key type expected.
|
||||
type Key: Decode + Default + TypedKey;
|
||||
type Key: Decode + Default + AppKey;
|
||||
|
||||
fn on_new_session<'a, I: 'a>(changed: bool, validators: I, queued_validators: I)
|
||||
where I: Iterator<Item=(&'a ValidatorId, Self::Key)>, ValidatorId: 'a;
|
||||
@@ -222,10 +225,10 @@ macro_rules! impl_session_handlers {
|
||||
) {
|
||||
$(
|
||||
let our_keys: Box<dyn Iterator<Item=_>> = Box::new(validators.iter()
|
||||
.map(|k| (&k.0, k.1.get::<$t::Key>(<$t::Key as TypedKey>::KEY_TYPE)
|
||||
.map(|k| (&k.0, k.1.get::<$t::Key>(<$t::Key as AppKey>::ID)
|
||||
.unwrap_or_default())));
|
||||
let queued_keys: Box<dyn Iterator<Item=_>> = Box::new(queued_validators.iter()
|
||||
.map(|k| (&k.0, k.1.get::<$t::Key>(<$t::Key as TypedKey>::KEY_TYPE)
|
||||
.map(|k| (&k.0, k.1.get::<$t::Key>(<$t::Key as AppKey>::ID)
|
||||
.unwrap_or_default())));
|
||||
$t::on_new_session(changed, our_keys, queued_keys);
|
||||
)*
|
||||
@@ -562,7 +565,7 @@ mod tests {
|
||||
use super::*;
|
||||
use srml_support::assert_ok;
|
||||
use runtime_io::with_externalities;
|
||||
use primitives::Blake2Hasher;
|
||||
use primitives::{Blake2Hasher, crypto::key_types::DUMMY};
|
||||
use sr_primitives::{
|
||||
traits::OnInitialize,
|
||||
testing::UintAuthorityId,
|
||||
@@ -576,7 +579,7 @@ mod tests {
|
||||
let mut t = system::GenesisConfig::default().build_storage::<Test>().unwrap();
|
||||
GenesisConfig::<Test> {
|
||||
keys: NEXT_VALIDATORS.with(|l|
|
||||
l.borrow().iter().cloned().map(|i| (i, UintAuthorityId(i))).collect()
|
||||
l.borrow().iter().cloned().map(|i| (i, UintAuthorityId(i).into())).collect()
|
||||
),
|
||||
}.assimilate_storage(&mut t.0, &mut t.1).unwrap();
|
||||
runtime_io::TestExternalities::new_with_children(t)
|
||||
@@ -599,8 +602,8 @@ mod tests {
|
||||
#[test]
|
||||
fn put_get_keys() {
|
||||
with_externalities(&mut new_test_ext(), || {
|
||||
Session::put_keys(&10, &UintAuthorityId(10));
|
||||
assert_eq!(Session::load_keys(&10), Some(UintAuthorityId(10)));
|
||||
Session::put_keys(&10, &UintAuthorityId(10).into());
|
||||
assert_eq!(Session::load_keys(&10), Some(UintAuthorityId(10).into()));
|
||||
})
|
||||
}
|
||||
|
||||
@@ -609,9 +612,9 @@ mod tests {
|
||||
let mut ext = new_test_ext();
|
||||
with_externalities(&mut ext, || {
|
||||
assert_eq!(Session::validators(), vec![1, 2, 3]);
|
||||
assert_eq!(Session::load_keys(&1), Some(UintAuthorityId(1)));
|
||||
assert_eq!(Session::load_keys(&1), Some(UintAuthorityId(1).into()));
|
||||
|
||||
let id = <UintAuthorityId as TypedKey>::KEY_TYPE;
|
||||
let id = DUMMY;
|
||||
assert_eq!(Session::key_owner(id, UintAuthorityId(1).get_raw(id)), Some(1));
|
||||
|
||||
Session::on_free_balance_zero(&1);
|
||||
@@ -629,8 +632,8 @@ mod tests {
|
||||
force_new_session();
|
||||
initialize_block(1);
|
||||
assert_eq!(Session::queued_keys(), vec![
|
||||
(1, UintAuthorityId(1)),
|
||||
(2, UintAuthorityId(2)),
|
||||
(1, UintAuthorityId(1).into()),
|
||||
(2, UintAuthorityId(2).into()),
|
||||
]);
|
||||
assert_eq!(Session::validators(), vec![1, 2, 3]);
|
||||
assert_eq!(authorities(), vec![UintAuthorityId(1), UintAuthorityId(2), UintAuthorityId(3)]);
|
||||
@@ -638,20 +641,20 @@ mod tests {
|
||||
force_new_session();
|
||||
initialize_block(2);
|
||||
assert_eq!(Session::queued_keys(), vec![
|
||||
(1, UintAuthorityId(1)),
|
||||
(2, UintAuthorityId(2)),
|
||||
(1, UintAuthorityId(1).into()),
|
||||
(2, UintAuthorityId(2).into()),
|
||||
]);
|
||||
assert_eq!(Session::validators(), vec![1, 2]);
|
||||
assert_eq!(authorities(), vec![UintAuthorityId(1), UintAuthorityId(2)]);
|
||||
|
||||
set_next_validators(vec![1, 2, 4]);
|
||||
assert_ok!(Session::set_keys(Origin::signed(4), UintAuthorityId(4), vec![]));
|
||||
assert_ok!(Session::set_keys(Origin::signed(4), UintAuthorityId(4).into(), vec![]));
|
||||
force_new_session();
|
||||
initialize_block(3);
|
||||
assert_eq!(Session::queued_keys(), vec![
|
||||
(1, UintAuthorityId(1)),
|
||||
(2, UintAuthorityId(2)),
|
||||
(4, UintAuthorityId(4)),
|
||||
(1, UintAuthorityId(1).into()),
|
||||
(2, UintAuthorityId(2).into()),
|
||||
(4, UintAuthorityId(4).into()),
|
||||
]);
|
||||
assert_eq!(Session::validators(), vec![1, 2]);
|
||||
assert_eq!(authorities(), vec![UintAuthorityId(1), UintAuthorityId(2)]);
|
||||
@@ -659,9 +662,9 @@ mod tests {
|
||||
force_new_session();
|
||||
initialize_block(4);
|
||||
assert_eq!(Session::queued_keys(), vec![
|
||||
(1, UintAuthorityId(1)),
|
||||
(2, UintAuthorityId(2)),
|
||||
(4, UintAuthorityId(4)),
|
||||
(1, UintAuthorityId(1).into()),
|
||||
(2, UintAuthorityId(2).into()),
|
||||
(4, UintAuthorityId(4).into()),
|
||||
]);
|
||||
assert_eq!(Session::validators(), vec![1, 2, 4]);
|
||||
assert_eq!(authorities(), vec![UintAuthorityId(1), UintAuthorityId(2), UintAuthorityId(4)]);
|
||||
@@ -704,7 +707,7 @@ mod tests {
|
||||
|
||||
// Block 3: Set new key for validator 2; no visible change.
|
||||
initialize_block(3);
|
||||
assert_ok!(Session::set_keys(Origin::signed(2), UintAuthorityId(5), vec![]));
|
||||
assert_ok!(Session::set_keys(Origin::signed(2), UintAuthorityId(5).into(), vec![]));
|
||||
assert_eq!(authorities(), vec![UintAuthorityId(1), UintAuthorityId(2), UintAuthorityId(3)]);
|
||||
|
||||
// Block 4: Session rollover; no visible change.
|
||||
@@ -726,11 +729,11 @@ mod tests {
|
||||
with_externalities(&mut new_test_ext(), || {
|
||||
System::set_block_number(1);
|
||||
Session::on_initialize(1);
|
||||
assert!(Session::set_keys(Origin::signed(4), UintAuthorityId(1), vec![]).is_err());
|
||||
assert!(Session::set_keys(Origin::signed(1), UintAuthorityId(10), vec![]).is_ok());
|
||||
assert!(Session::set_keys(Origin::signed(4), UintAuthorityId(1).into(), vec![]).is_err());
|
||||
assert!(Session::set_keys(Origin::signed(1), UintAuthorityId(10).into(), vec![]).is_ok());
|
||||
|
||||
// is fine now that 1 has migrated off.
|
||||
assert!(Session::set_keys(Origin::signed(4), UintAuthorityId(1), vec![]).is_ok());
|
||||
assert!(Session::set_keys(Origin::signed(4), UintAuthorityId(1).into(), vec![]).is_ok());
|
||||
});
|
||||
}
|
||||
|
||||
@@ -760,7 +763,7 @@ mod tests {
|
||||
initialize_block(5);
|
||||
assert!(!session_changed());
|
||||
|
||||
assert_ok!(Session::set_keys(Origin::signed(2), UintAuthorityId(5), vec![]));
|
||||
assert_ok!(Session::set_keys(Origin::signed(2), UintAuthorityId(5).into(), vec![]));
|
||||
force_new_session();
|
||||
initialize_block(6);
|
||||
assert!(!session_changed());
|
||||
@@ -799,4 +802,18 @@ mod tests {
|
||||
|
||||
assert!(P::should_end_session(13));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn session_keys_generate_output_works_as_set_keys_input() {
|
||||
with_externalities(&mut new_test_ext(), || {
|
||||
let new_keys = mock::MockSessionKeys::generate(None);
|
||||
assert_ok!(
|
||||
Session::set_keys(
|
||||
Origin::signed(2),
|
||||
<mock::Test as Trait>::Keys::decode(&mut &new_keys[..]).expect("Decode keys"),
|
||||
vec![],
|
||||
)
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,13 +19,24 @@
|
||||
use super::*;
|
||||
use std::cell::RefCell;
|
||||
use srml_support::{impl_outer_origin, parameter_types};
|
||||
use primitives::H256;
|
||||
use primitives::{crypto::key_types::DUMMY, H256};
|
||||
use sr_primitives::{
|
||||
Perbill,
|
||||
traits::{BlakeTwo256, IdentityLookup, ConvertInto},
|
||||
Perbill, impl_opaque_keys, traits::{BlakeTwo256, IdentityLookup, ConvertInto},
|
||||
testing::{Header, UintAuthorityId}
|
||||
};
|
||||
|
||||
impl_opaque_keys! {
|
||||
pub struct MockSessionKeys {
|
||||
#[id(DUMMY)]
|
||||
pub dummy: UintAuthorityId,
|
||||
}
|
||||
}
|
||||
|
||||
impl From<UintAuthorityId> for MockSessionKeys {
|
||||
fn from(dummy: UintAuthorityId) -> Self {
|
||||
Self { dummy }
|
||||
}
|
||||
}
|
||||
|
||||
impl_outer_origin! {
|
||||
pub enum Origin for Test {}
|
||||
@@ -58,7 +69,9 @@ impl SessionHandler<u64> for TestSessionHandler {
|
||||
) {
|
||||
SESSION_CHANGED.with(|l| *l.borrow_mut() = changed);
|
||||
AUTHORITIES.with(|l|
|
||||
*l.borrow_mut() = validators.iter().map(|(_, id)| id.get::<UintAuthorityId>(0).unwrap_or_default()).collect()
|
||||
*l.borrow_mut() = validators.iter()
|
||||
.map(|(_, id)| id.get::<UintAuthorityId>(DUMMY).unwrap_or_default())
|
||||
.collect()
|
||||
);
|
||||
}
|
||||
fn on_disabled(_validator_index: usize) {}
|
||||
@@ -119,10 +132,12 @@ parameter_types! {
|
||||
pub const MinimumPeriod: u64 = 5;
|
||||
pub const AvailableBlockRatio: Perbill = Perbill::one();
|
||||
}
|
||||
|
||||
impl system::Trait for Test {
|
||||
type Origin = Origin;
|
||||
type Index = u64;
|
||||
type BlockNumber = u64;
|
||||
type Call = ();
|
||||
type Hash = H256;
|
||||
type Hashing = BlakeTwo256;
|
||||
type AccountId = u64;
|
||||
@@ -135,13 +150,13 @@ impl system::Trait for Test {
|
||||
type AvailableBlockRatio = AvailableBlockRatio;
|
||||
type MaximumBlockLength = MaximumBlockLength;
|
||||
}
|
||||
|
||||
impl timestamp::Trait for Test {
|
||||
type Moment = u64;
|
||||
type OnTimestampSet = ();
|
||||
type MinimumPeriod = MinimumPeriod;
|
||||
}
|
||||
|
||||
|
||||
impl Trait for Test {
|
||||
type ShouldEndSession = TestShouldEndSession;
|
||||
#[cfg(feature = "historical")]
|
||||
@@ -151,7 +166,7 @@ impl Trait for Test {
|
||||
type SessionHandler = TestSessionHandler;
|
||||
type ValidatorId = u64;
|
||||
type ValidatorIdOf = ConvertInto;
|
||||
type Keys = UintAuthorityId;
|
||||
type Keys = MockSessionKeys;
|
||||
type Event = ();
|
||||
type SelectInitialValidators = ();
|
||||
}
|
||||
|
||||
@@ -110,6 +110,7 @@ impl system::Trait for Test {
|
||||
type Origin = Origin;
|
||||
type Index = u64;
|
||||
type BlockNumber = BlockNumber;
|
||||
type Call = ();
|
||||
type Hash = H256;
|
||||
type Hashing = ::sr_primitives::traits::BlakeTwo256;
|
||||
type AccountId = AccountId;
|
||||
|
||||
@@ -238,6 +238,23 @@ pub trait StorageMap<K: codec::Codec, V: codec::Codec> {
|
||||
/// Take the value under a key.
|
||||
fn take<S: HashedStorage<Self::Hasher>>(key: &K, storage: &mut S) -> Self::Query;
|
||||
|
||||
/// Swap the values of two keys.
|
||||
fn swap<S: HashedStorage<Self::Hasher>>(key1: &K, key2: &K, storage: &mut S) {
|
||||
let k1 = Self::key_for(key1);
|
||||
let k2 = Self::key_for(key2);
|
||||
let v1 = storage.get_raw(&k1[..]);
|
||||
if let Some(val) = storage.get_raw(&k2[..]) {
|
||||
storage.put_raw(&k1[..], &val[..]);
|
||||
} else {
|
||||
storage.kill(&k1[..])
|
||||
}
|
||||
if let Some(val) = v1 {
|
||||
storage.put_raw(&k2[..], &val[..]);
|
||||
} else {
|
||||
storage.kill(&k2[..])
|
||||
}
|
||||
}
|
||||
|
||||
/// Store a value to be associated with the given key from the map.
|
||||
fn insert<S: HashedStorage<Self::Hasher>>(key: &K, val: &V, storage: &mut S) {
|
||||
storage.put(&Self::key_for(key)[..], val);
|
||||
|
||||
@@ -191,6 +191,9 @@ pub trait StorageMap<K: Codec, V: Codec> {
|
||||
/// Load the value associated with the given key from the map.
|
||||
fn get<KeyArg: Borrow<K>>(key: KeyArg) -> Self::Query;
|
||||
|
||||
/// Swap the values of two keys.
|
||||
fn swap<KeyArg1: Borrow<K>, KeyArg2: Borrow<K>>(key1: KeyArg1, key2: KeyArg2);
|
||||
|
||||
/// Store a value to be associated with the given key from the map.
|
||||
fn insert<KeyArg: Borrow<K>, ValArg: Borrow<V>>(key: KeyArg, val: ValArg);
|
||||
|
||||
@@ -227,6 +230,10 @@ impl<K: Codec, V: Codec, U> StorageMap<K, V> for U where U: hashed::generator::S
|
||||
U::get(key.borrow(), &RuntimeStorage)
|
||||
}
|
||||
|
||||
fn swap<KeyArg1: Borrow<K>, KeyArg2: Borrow<K>>(key1: KeyArg1, key2: KeyArg2) {
|
||||
U::swap(key1.borrow(), key2.borrow(), &mut RuntimeStorage)
|
||||
}
|
||||
|
||||
fn insert<KeyArg: Borrow<K>, ValArg: Borrow<V>>(key: KeyArg, val: ValArg) {
|
||||
U::insert(key.borrow(), val.borrow(), &mut RuntimeStorage)
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
//!
|
||||
//! NOTE: If you're looking for `parameter_types`, it has moved in to the top-level module.
|
||||
|
||||
use crate::rstd::{result, marker::PhantomData, ops::Div};
|
||||
use crate::rstd::{prelude::*, result, marker::PhantomData, ops::Div};
|
||||
use crate::codec::{Codec, Encode, Decode};
|
||||
use primitives::u32_trait::Value as U32;
|
||||
use crate::sr_primitives::traits::{MaybeSerializeDebug, SimpleArithmetic, Saturating};
|
||||
@@ -631,12 +631,61 @@ impl WithdrawReasons {
|
||||
}
|
||||
|
||||
/// Trait for type that can handle incremental changes to a set of account IDs.
|
||||
pub trait ChangeMembers<AccountId> {
|
||||
pub trait ChangeMembers<AccountId: Clone + Ord> {
|
||||
/// A number of members `incoming` just joined the set and replaced some `outgoing` ones. The
|
||||
/// new set is given by `new`, and need not be sorted.
|
||||
fn change_members(incoming: &[AccountId], outgoing: &[AccountId], mut new: Vec<AccountId>) {
|
||||
new.sort_unstable();
|
||||
Self::change_members_sorted(incoming, outgoing, &new[..]);
|
||||
}
|
||||
|
||||
/// A number of members `_incoming` just joined the set and replaced some `_outgoing` ones. The
|
||||
/// new set is thus given by `_new`.
|
||||
fn change_members(_incoming: &[AccountId], _outgoing: &[AccountId], _new: &[AccountId]);
|
||||
/// new set is thus given by `sorted_new` and **must be sorted**.
|
||||
///
|
||||
/// NOTE: This is the only function that needs to be implemented in `ChangeMembers`.
|
||||
fn change_members_sorted(
|
||||
incoming: &[AccountId],
|
||||
outgoing: &[AccountId],
|
||||
sorted_new: &[AccountId],
|
||||
);
|
||||
|
||||
/// Set the new members; they **must already be sorted**. This will compute the diff and use it to
|
||||
/// call `change_members_sorted`.
|
||||
fn set_members_sorted(new_members: &[AccountId], old_members: &[AccountId]) {
|
||||
let mut old_iter = old_members.iter();
|
||||
let mut new_iter = new_members.iter();
|
||||
let mut incoming = Vec::new();
|
||||
let mut outgoing = Vec::new();
|
||||
let mut old_i = old_iter.next();
|
||||
let mut new_i = new_iter.next();
|
||||
loop {
|
||||
match (old_i, new_i) {
|
||||
(None, None) => break,
|
||||
(Some(old), Some(new)) if old == new => {
|
||||
old_i = old_iter.next();
|
||||
new_i = new_iter.next();
|
||||
}
|
||||
(Some(old), Some(new)) if old < new => {
|
||||
outgoing.push(old.clone());
|
||||
old_i = old_iter.next();
|
||||
}
|
||||
(Some(old), None) => {
|
||||
outgoing.push(old.clone());
|
||||
old_i = old_iter.next();
|
||||
}
|
||||
(_, Some(new)) => {
|
||||
incoming.push(new.clone());
|
||||
new_i = new_iter.next();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Self::change_members_sorted(&incoming[..], &outgoing[..], &new_members);
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> ChangeMembers<T> for () {
|
||||
fn change_members(_incoming: &[T], _outgoing: &[T], _new_set: &[T]) {}
|
||||
impl<T: Clone + Ord> ChangeMembers<T> for () {
|
||||
fn change_members(_: &[T], _: &[T], _: Vec<T>) {}
|
||||
fn change_members_sorted(_: &[T], _: &[T], _: &[T]) {}
|
||||
fn set_members_sorted(_: &[T], _: &[T]) {}
|
||||
}
|
||||
|
||||
@@ -63,6 +63,7 @@ impl system::Trait for Runtime {
|
||||
type Origin = Origin;
|
||||
type Index = u64;
|
||||
type BlockNumber = u64;
|
||||
type Call = ();
|
||||
type Hash = H256;
|
||||
type Hashing = BlakeTwo256;
|
||||
type AccountId = u64;
|
||||
|
||||
@@ -154,6 +154,9 @@ pub trait Trait: 'static + Eq + Clone {
|
||||
/// The aggregated `Origin` type used by dispatchable calls.
|
||||
type Origin: Into<Result<RawOrigin<Self::AccountId>, Self::Origin>> + From<RawOrigin<Self::AccountId>>;
|
||||
|
||||
/// The aggregated `Call` type.
|
||||
type Call;
|
||||
|
||||
/// Account index (aka nonce) type. This stores the number of previous transactions associated with a sender
|
||||
/// account.
|
||||
type Index:
|
||||
@@ -839,7 +842,7 @@ impl<T: Trait + Send + Sync> CheckWeight<T> {
|
||||
let added_weight = info.weight.min(limit);
|
||||
let next_weight = current_weight.saturating_add(added_weight);
|
||||
if next_weight > limit {
|
||||
return Err(DispatchError::Resource)
|
||||
return Err(DispatchError::Exhausted)
|
||||
}
|
||||
Ok(next_weight)
|
||||
}
|
||||
@@ -854,7 +857,7 @@ impl<T: Trait + Send + Sync> CheckWeight<T> {
|
||||
let added_len = len as u32;
|
||||
let next_len = current_len.saturating_add(added_len);
|
||||
if next_len > limit {
|
||||
return Err(DispatchError::Resource)
|
||||
return Err(DispatchError::Exhausted)
|
||||
}
|
||||
Ok(next_len)
|
||||
}
|
||||
@@ -876,6 +879,7 @@ impl<T: Trait + Send + Sync> CheckWeight<T> {
|
||||
|
||||
impl<T: Trait + Send + Sync> SignedExtension for CheckWeight<T> {
|
||||
type AccountId = T::AccountId;
|
||||
type Call = T::Call;
|
||||
type AdditionalSigned = ();
|
||||
|
||||
fn additional_signed(&self) -> rstd::result::Result<(), &'static str> { Ok(()) }
|
||||
@@ -883,6 +887,7 @@ impl<T: Trait + Send + Sync> SignedExtension for CheckWeight<T> {
|
||||
fn pre_dispatch(
|
||||
self,
|
||||
_who: &Self::AccountId,
|
||||
_call: &Self::Call,
|
||||
info: DispatchInfo,
|
||||
len: usize,
|
||||
) -> Result<(), DispatchError> {
|
||||
@@ -896,6 +901,7 @@ impl<T: Trait + Send + Sync> SignedExtension for CheckWeight<T> {
|
||||
fn validate(
|
||||
&self,
|
||||
_who: &Self::AccountId,
|
||||
_call: &Self::Call,
|
||||
info: DispatchInfo,
|
||||
len: usize,
|
||||
) -> Result<ValidTransaction, DispatchError> {
|
||||
@@ -936,6 +942,7 @@ impl<T: Trait> rstd::fmt::Debug for CheckNonce<T> {
|
||||
|
||||
impl<T: Trait> SignedExtension for CheckNonce<T> {
|
||||
type AccountId = T::AccountId;
|
||||
type Call = T::Call;
|
||||
type AdditionalSigned = ();
|
||||
|
||||
fn additional_signed(&self) -> rstd::result::Result<(), &'static str> { Ok(()) }
|
||||
@@ -943,6 +950,7 @@ impl<T: Trait> SignedExtension for CheckNonce<T> {
|
||||
fn pre_dispatch(
|
||||
self,
|
||||
who: &Self::AccountId,
|
||||
_call: &Self::Call,
|
||||
_info: DispatchInfo,
|
||||
_len: usize,
|
||||
) -> Result<(), DispatchError> {
|
||||
@@ -959,6 +967,7 @@ impl<T: Trait> SignedExtension for CheckNonce<T> {
|
||||
fn validate(
|
||||
&self,
|
||||
who: &Self::AccountId,
|
||||
_call: &Self::Call,
|
||||
info: DispatchInfo,
|
||||
_len: usize,
|
||||
) -> Result<ValidTransaction, DispatchError> {
|
||||
@@ -1006,6 +1015,7 @@ impl<T: Trait + Send + Sync> rstd::fmt::Debug for CheckEra<T> {
|
||||
|
||||
impl<T: Trait + Send + Sync> SignedExtension for CheckEra<T> {
|
||||
type AccountId = T::AccountId;
|
||||
type Call = T::Call;
|
||||
type AdditionalSigned = T::Hash;
|
||||
fn additional_signed(&self) -> Result<Self::AdditionalSigned, &'static str> {
|
||||
let current_u64 = <Module<T>>::block_number().saturated_into::<u64>();
|
||||
@@ -1035,6 +1045,7 @@ impl<T: Trait + Send + Sync> CheckGenesis<T> {
|
||||
|
||||
impl<T: Trait + Send + Sync> SignedExtension for CheckGenesis<T> {
|
||||
type AccountId = T::AccountId;
|
||||
type Call = <T as Trait>::Call;
|
||||
type AdditionalSigned = T::Hash;
|
||||
fn additional_signed(&self) -> Result<Self::AdditionalSigned, &'static str> {
|
||||
Ok(<Module<T>>::block_hash(T::BlockNumber::zero()))
|
||||
@@ -1080,6 +1091,7 @@ mod tests {
|
||||
|
||||
impl Trait for Test {
|
||||
type Origin = Origin;
|
||||
type Call = ();
|
||||
type Index = u64;
|
||||
type BlockNumber = u64;
|
||||
type Hash = H256;
|
||||
@@ -1106,6 +1118,8 @@ mod tests {
|
||||
|
||||
type System = Module<Test>;
|
||||
|
||||
const CALL: &<Test as Trait>::Call = &();
|
||||
|
||||
fn new_test_ext() -> runtime_io::TestExternalities<Blake2Hasher> {
|
||||
GenesisConfig::default().build_storage::<Test>().unwrap().0.into()
|
||||
}
|
||||
@@ -1259,14 +1273,14 @@ mod tests {
|
||||
let info = DispatchInfo::default();
|
||||
let len = 0_usize;
|
||||
// stale
|
||||
assert!(CheckNonce::<Test>(0).validate(&1, info, len).is_err());
|
||||
assert!(CheckNonce::<Test>(0).pre_dispatch(&1, info, len).is_err());
|
||||
assert!(CheckNonce::<Test>(0).validate(&1, CALL, info, len).is_err());
|
||||
assert!(CheckNonce::<Test>(0).pre_dispatch(&1, CALL, info, len).is_err());
|
||||
// correct
|
||||
assert!(CheckNonce::<Test>(1).validate(&1, info, len).is_ok());
|
||||
assert!(CheckNonce::<Test>(1).pre_dispatch(&1, info, len).is_ok());
|
||||
assert!(CheckNonce::<Test>(1).validate(&1, CALL, info, len).is_ok());
|
||||
assert!(CheckNonce::<Test>(1).pre_dispatch(&1, CALL, info, len).is_ok());
|
||||
// future
|
||||
assert!(CheckNonce::<Test>(5).validate(&1, info, len).is_ok());
|
||||
assert!(CheckNonce::<Test>(5).pre_dispatch(&1, info, len).is_err());
|
||||
assert!(CheckNonce::<Test>(5).validate(&1, CALL, info, len).is_ok());
|
||||
assert!(CheckNonce::<Test>(5).pre_dispatch(&1, CALL, info, len).is_err());
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1287,7 +1301,7 @@ mod tests {
|
||||
|
||||
let reset_check_weight = |i, f, s| {
|
||||
AllExtrinsicsWeight::put(s);
|
||||
let r = CheckWeight::<Test>(PhantomData).pre_dispatch(&1, i, len);
|
||||
let r = CheckWeight::<Test>(PhantomData).pre_dispatch(&1, CALL, i, len);
|
||||
if f { assert!(r.is_err()) } else { assert!(r.is_ok()) }
|
||||
};
|
||||
|
||||
@@ -1304,7 +1318,7 @@ mod tests {
|
||||
let len = 0_usize;
|
||||
|
||||
assert_eq!(System::all_extrinsics_weight(), 0);
|
||||
let r = CheckWeight::<Test>(PhantomData).pre_dispatch(&1, free, len);
|
||||
let r = CheckWeight::<Test>(PhantomData).pre_dispatch(&1, CALL, free, len);
|
||||
assert!(r.is_ok());
|
||||
assert_eq!(System::all_extrinsics_weight(), 0);
|
||||
})
|
||||
@@ -1318,7 +1332,7 @@ mod tests {
|
||||
let normal_limit = normal_weight_limit();
|
||||
|
||||
assert_eq!(System::all_extrinsics_weight(), 0);
|
||||
let r = CheckWeight::<Test>(PhantomData).pre_dispatch(&1, max, len);
|
||||
let r = CheckWeight::<Test>(PhantomData).pre_dispatch(&1, CALL, max, len);
|
||||
assert!(r.is_ok());
|
||||
assert_eq!(System::all_extrinsics_weight(), normal_limit);
|
||||
})
|
||||
@@ -1335,15 +1349,15 @@ mod tests {
|
||||
// given almost full block
|
||||
AllExtrinsicsWeight::put(normal_limit);
|
||||
// will not fit.
|
||||
assert!(CheckWeight::<Test>(PhantomData).pre_dispatch(&1, normal, len).is_err());
|
||||
assert!(CheckWeight::<Test>(PhantomData).pre_dispatch(&1, CALL, normal, len).is_err());
|
||||
// will fit.
|
||||
assert!(CheckWeight::<Test>(PhantomData).pre_dispatch(&1, op, len).is_ok());
|
||||
assert!(CheckWeight::<Test>(PhantomData).pre_dispatch(&1, CALL, op, len).is_ok());
|
||||
|
||||
// likewise for length limit.
|
||||
let len = 100_usize;
|
||||
AllExtrinsicsLen::put(normal_length_limit());
|
||||
assert!(CheckWeight::<Test>(PhantomData).pre_dispatch(&1, normal, len).is_err());
|
||||
assert!(CheckWeight::<Test>(PhantomData).pre_dispatch(&1, op, len).is_ok());
|
||||
assert!(CheckWeight::<Test>(PhantomData).pre_dispatch(&1, CALL, normal, len).is_err());
|
||||
assert!(CheckWeight::<Test>(PhantomData).pre_dispatch(&1, CALL, op, len).is_ok());
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1355,11 +1369,11 @@ mod tests {
|
||||
let len = 0_usize;
|
||||
|
||||
assert_eq!(
|
||||
CheckWeight::<Test>(PhantomData).validate(&1, normal, len).unwrap().priority,
|
||||
CheckWeight::<Test>(PhantomData).validate(&1, CALL, normal, len).unwrap().priority,
|
||||
100,
|
||||
);
|
||||
assert_eq!(
|
||||
CheckWeight::<Test>(PhantomData).validate(&1, op, len).unwrap().priority,
|
||||
CheckWeight::<Test>(PhantomData).validate(&1, CALL, op, len).unwrap().priority,
|
||||
Bounded::max_value(),
|
||||
);
|
||||
})
|
||||
@@ -1372,7 +1386,7 @@ mod tests {
|
||||
let normal_limit = normal_weight_limit() as usize;
|
||||
let reset_check_weight = |tx, s, f| {
|
||||
AllExtrinsicsLen::put(0);
|
||||
let r = CheckWeight::<Test>(PhantomData).pre_dispatch(&1, tx, s);
|
||||
let r = CheckWeight::<Test>(PhantomData).pre_dispatch(&1, CALL, tx, s);
|
||||
if f { assert!(r.is_err()) } else { assert!(r.is_ok()) }
|
||||
};
|
||||
|
||||
|
||||
@@ -358,6 +358,7 @@ mod tests {
|
||||
type Origin = Origin;
|
||||
type Index = u64;
|
||||
type BlockNumber = u64;
|
||||
type Call = ();
|
||||
type Hash = H256;
|
||||
type Hashing = BlakeTwo256;
|
||||
type AccountId = u64;
|
||||
|
||||
@@ -382,6 +382,7 @@ mod tests {
|
||||
type Origin = Origin;
|
||||
type Index = u64;
|
||||
type BlockNumber = u64;
|
||||
type Call = ();
|
||||
type Hash = H256;
|
||||
type Hashing = BlakeTwo256;
|
||||
type AccountId = u64;
|
||||
|
||||
Reference in New Issue
Block a user