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:
Gavin Wood
2019-08-07 20:47:48 +02:00
committed by GitHub
parent a6a6779f01
commit 1a524b8207
160 changed files with 4467 additions and 2769 deletions
+1
View File
@@ -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;
+3 -1
View File
@@ -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",
]
+3 -3
View File
@@ -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! {
+5 -3
View File
@@ -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()
}
+1
View File
@@ -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;
+2 -2
View File
@@ -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]
+3
View File
@@ -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> {
+4
View File
@@ -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() }
+7 -4
View File
@@ -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 -41
View File
@@ -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![] })
+1 -1
View File
@@ -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
+3 -3
View File
@@ -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();
+1
View File
@@ -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>;
+1
View File
@@ -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;
+64 -107
View File
@@ -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"
);
});
+4 -3
View File
@@ -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();
+1
View File
@@ -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>;
+3 -3
View File
@@ -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;
+1
View File
@@ -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;
+1
View File
@@ -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;
+4 -1
View File
@@ -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> {
+2
View File
@@ -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
View File
@@ -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(&current_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(
&current_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(&current_session, &heartbeat.authority_id, &network_state);
<ReceivedHeartbeats>::insert(
&current_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(&current_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(&current_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)
}
}
+1
View File
@@ -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;
+29
View File
@@ -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",
]
+347
View File
@@ -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());
});
}
}
+1
View File
@@ -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]
+9 -22
View File
@@ -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]);
+48 -31
View File
@@ -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![],
)
);
});
}
}
+21 -6
View File
@@ -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 = ();
}
+1
View File
@@ -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)
}
+55 -6
View File
@@ -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]) {}
}
+1
View File
@@ -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;
+32 -18
View File
@@ -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()) }
};
+1
View File
@@ -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;
+1
View File
@@ -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;