mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-05-30 07:01:03 +00:00
Minimal switch of substrate-node to GRANDPA /Aura (#1128)
* add beginnings of SRML grandpa library * get srml-grandpa compiling * tests for srml-grandpa * add optional session integration to grandpa SRML * start integration into node runtime * Allow extracting pending change from header digest * Make it compile on wasm * make tests compile again * Move Authority Key fetching into service, simplify service factory construction * Generalize Authority Consensus Setup system * Add Authority Setup Docs * Allow CLI params to be extensible - move params to structopts - split parsing and default command execution - add custom config to node - extended parsing of custom config - extending params via structop's flatten * Minor fixes on cli extension params: - added docs - re-add actual app name, rather than node-name - make strategy and subcommand optional * better cli params * synchronize GRANDPA and normal node authorities * Implement grandpa::network for gossip consensus * run_grandpa in Node * Fix missed merge error * Integrate grandpa import queue * more specific type def * link up linkhalf and import block * make grandpa future send * get compiling * Fix new params convention and license header * get it running * rebuild node runtime WASM * change logging level * Update node/cli/src/params.rs Co-Authored-By: rphmeier <rphmeier@gmail.com> * Update node/cli/src/params.rs Co-Authored-By: rphmeier <rphmeier@gmail.com> * Update node/cli/src/lib.rs Co-Authored-By: rphmeier <rphmeier@gmail.com> * Update node/runtime/src/lib.rs Co-Authored-By: rphmeier <rphmeier@gmail.com> * Update node/cli/src/lib.rs Co-Authored-By: rphmeier <rphmeier@gmail.com> * Clean up and Fixme for mutable config * Move GrandpaService Integration into grandpa, feature gated but on per default * Fixing grandpa runtime module test * Update wasm runtime hashes for tests * GRANDPA: use post-header hash when logging scheduled changes * add an extra bit of logging to authorities * fixing missing constrain * remove old code * move `NewAuthorities` to an event in srml-grandpa * fix node-executor tests to use grandpa log * Remove GossipConsensus from tests, use newly provided sync-feature, fixes tests * Update to latest wasm runtimes * address grumbles * address grumbles * only derive deserialize when using std * Clean up use of Deserialize
This commit is contained in:
committed by
GitHub
parent
84da9d4a02
commit
11fe84a742
@@ -0,0 +1,35 @@
|
||||
[package]
|
||||
name = "srml-grandpa"
|
||||
version = "0.1.0"
|
||||
authors = ["Parity Technologies <admin@parity.io>"]
|
||||
|
||||
[dependencies]
|
||||
hex-literal = "0.1.0"
|
||||
serde = { version = "1.0", default-features = false }
|
||||
serde_derive = { version = "1.0", optional = true }
|
||||
parity-codec = { version = "2.1", default-features = false }
|
||||
parity-codec-derive = { version = "2.1", default-features = false }
|
||||
substrate-primitives = { path = "../../core/primitives", default-features = false }
|
||||
substrate-finality-grandpa-primitives = { path = "../../core/finality-grandpa/primitives", default-features = false }
|
||||
sr-std = { path = "../../core/sr-std", default-features = false }
|
||||
sr-io = { path = "../../core/sr-io", default-features = false }
|
||||
sr-primitives = { path = "../../core/sr-primitives", default-features = false }
|
||||
srml-support = { path = "../support", default-features = false }
|
||||
srml-system = { path = "../system", default-features = false }
|
||||
srml-session = { path = "../session", default-features = false }
|
||||
|
||||
[features]
|
||||
default = ["std"]
|
||||
std = [
|
||||
"serde/std",
|
||||
"serde_derive",
|
||||
"parity-codec/std",
|
||||
"substrate-primitives/std",
|
||||
"substrate-finality-grandpa-primitives/std",
|
||||
"sr-std/std",
|
||||
"sr-io/std",
|
||||
"srml-support/std",
|
||||
"sr-primitives/std",
|
||||
"srml-system/std",
|
||||
"srml-session/std",
|
||||
]
|
||||
@@ -0,0 +1,303 @@
|
||||
// Copyright 2017-2018 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/>.
|
||||
|
||||
//! GRANDPA Consensus module for runtime.
|
||||
//!
|
||||
//! This manages the GRANDPA authority set ready for the native code.
|
||||
//! These authorities are only for GRANDPA finality, not for consensus overall.
|
||||
//!
|
||||
//! In the future, it will also handle misbehavior reports, and on-chain
|
||||
//! finality notifications.
|
||||
//!
|
||||
//! For full integration with GRANDPA, the `GrandpaApi` should be implemented.
|
||||
//! The necessary items are re-exported via the `fg_primitives` crate.
|
||||
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
|
||||
#[allow(unused_imports)]
|
||||
#[macro_use]
|
||||
extern crate sr_std as rstd;
|
||||
|
||||
#[macro_use]
|
||||
extern crate srml_support as runtime_support;
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
#[macro_use]
|
||||
extern crate serde_derive;
|
||||
|
||||
extern crate parity_codec;
|
||||
#[macro_use]
|
||||
extern crate parity_codec_derive;
|
||||
|
||||
extern crate sr_primitives as primitives;
|
||||
extern crate parity_codec as codec;
|
||||
extern crate srml_system as system;
|
||||
extern crate srml_session as session;
|
||||
extern crate substrate_primitives;
|
||||
|
||||
#[cfg(test)]
|
||||
extern crate sr_io as runtime_io;
|
||||
|
||||
// re-export since this is necessary for `impl_apis` in runtime.
|
||||
pub extern crate substrate_finality_grandpa_primitives as fg_primitives;
|
||||
|
||||
use rstd::prelude::*;
|
||||
use fg_primitives::ScheduledChange;
|
||||
use runtime_support::Parameter;
|
||||
use runtime_support::dispatch::Result;
|
||||
use runtime_support::storage::StorageValue;
|
||||
use runtime_support::storage::unhashed::StorageVec;
|
||||
use primitives::traits::{CurrentHeight, Convert};
|
||||
use substrate_primitives::AuthorityId;
|
||||
use system::ensure_signed;
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
use primitives::traits::MaybeSerializeDebug;
|
||||
|
||||
#[cfg(not(feature = "std"))]
|
||||
use primitives::traits::MaybeSerializeDebugButNotDeserialize;
|
||||
|
||||
mod mock;
|
||||
mod tests;
|
||||
|
||||
struct AuthorityStorageVec<S: codec::Codec + Default>(rstd::marker::PhantomData<S>);
|
||||
impl<S: codec::Codec + Default> StorageVec for AuthorityStorageVec<S> {
|
||||
type Item = (S, u64);
|
||||
const PREFIX: &'static [u8] = ::fg_primitives::well_known_keys::AUTHORITY_PREFIX;
|
||||
}
|
||||
|
||||
/// The log type of this crate, projected from module trait type.
|
||||
pub type Log<T> = RawLog<
|
||||
<T as system::Trait>::BlockNumber,
|
||||
<T as Trait>::SessionKey,
|
||||
>;
|
||||
|
||||
/// Logs which can be scanned by GRANDPA for authorities change events.
|
||||
pub trait GrandpaChangeSignal<N> {
|
||||
/// Try to cast the log entry as a contained signal.
|
||||
fn as_signal(&self) -> Option<ScheduledChange<N>>;
|
||||
}
|
||||
|
||||
/// A logs in this module.
|
||||
#[cfg_attr(feature = "std", derive(Serialize, Debug))]
|
||||
#[derive(Encode, Decode, PartialEq, Eq, Clone)]
|
||||
pub enum RawLog<N, SessionKey> {
|
||||
/// Authorities set change has been signalled. Contains the new set of authorities
|
||||
/// and the delay in blocks before applying.
|
||||
AuthoritiesChangeSignal(N, Vec<(SessionKey, u64)>),
|
||||
}
|
||||
|
||||
impl<N: Clone, SessionKey> RawLog<N, SessionKey> {
|
||||
/// Try to cast the log entry as a contained signal.
|
||||
pub fn as_signal(&self) -> Option<(N, &[(SessionKey, u64)])> {
|
||||
match *self {
|
||||
RawLog::AuthoritiesChangeSignal(ref n, ref signal) => Some((n.clone(), signal)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<N, SessionKey> GrandpaChangeSignal<N> for RawLog<N, SessionKey>
|
||||
where N: Clone, SessionKey: Clone + Into<AuthorityId>,
|
||||
{
|
||||
fn as_signal(&self) -> Option<ScheduledChange<N>> {
|
||||
RawLog::as_signal(self).map(|(delay, next_authorities)| ScheduledChange {
|
||||
delay,
|
||||
next_authorities: next_authorities.iter()
|
||||
.cloned()
|
||||
.map(|(k, w)| (k.into(), w))
|
||||
.collect(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Trait: system::Trait {
|
||||
/// Type for all log entries of this module.
|
||||
type Log: From<Log<Self>> + Into<system::DigestItemOf<Self>>;
|
||||
|
||||
/// The session key type used by authorities.
|
||||
#[cfg(not(feature = "std"))]
|
||||
type SessionKey: Parameter + Default + MaybeSerializeDebugButNotDeserialize;
|
||||
|
||||
/// The session key type used by authorities.
|
||||
#[cfg(feature = "std")]
|
||||
type SessionKey: Parameter + Default + MaybeSerializeDebug;
|
||||
|
||||
/// The event type of this module.
|
||||
type Event: From<Event<Self>> + Into<<Self as system::Trait>::Event>;
|
||||
}
|
||||
|
||||
/// A stored pending change.
|
||||
#[derive(Encode, Decode)]
|
||||
pub struct StoredPendingChange<N, SessionKey> {
|
||||
/// The block number this was scheduled at.
|
||||
pub scheduled_at: N,
|
||||
/// The delay in blocks until it will be applied.
|
||||
pub delay: N,
|
||||
/// The next authority set.
|
||||
pub next_authorities: Vec<(SessionKey, u64)>,
|
||||
}
|
||||
|
||||
/// GRANDPA events.
|
||||
decl_event!(
|
||||
pub enum Event<T> where <T as Trait>::SessionKey {
|
||||
/// New authority set has been applied.
|
||||
NewAuthorities(Vec<(SessionKey, u64)>),
|
||||
}
|
||||
);
|
||||
|
||||
decl_storage! {
|
||||
trait Store for Module<T: Trait> as GrandpaFinality {
|
||||
// Pending change: (signalled at, scheduled change).
|
||||
PendingChange get(pending_change): Option<StoredPendingChange<T::BlockNumber, T::SessionKey>>;
|
||||
}
|
||||
add_extra_genesis {
|
||||
config(authorities): Vec<(T::SessionKey, u64)>;
|
||||
|
||||
build(|storage: &mut primitives::StorageMap, _: &mut primitives::ChildrenStorageMap, config: &GenesisConfig<T>| {
|
||||
use codec::{Encode, KeyedVec};
|
||||
|
||||
let auth_count = config.authorities.len() as u32;
|
||||
config.authorities.iter().enumerate().for_each(|(i, v)| {
|
||||
storage.insert((i as u32).to_keyed_vec(
|
||||
::fg_primitives::well_known_keys::AUTHORITY_PREFIX),
|
||||
v.encode()
|
||||
);
|
||||
});
|
||||
storage.insert(
|
||||
::fg_primitives::well_known_keys::AUTHORITY_COUNT.to_vec(),
|
||||
auth_count.encode(),
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
decl_module! {
|
||||
pub struct Module<T: Trait> for enum Call where origin: T::Origin {
|
||||
fn deposit_event() = default;
|
||||
|
||||
/// Report some misbehaviour.
|
||||
fn report_misbehavior(origin, _report: Vec<u8>) -> Result {
|
||||
ensure_signed(origin)?;
|
||||
// TODO: https://github.com/paritytech/substrate/issues/1112
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn on_finalise(block_number: T::BlockNumber) {
|
||||
if let Some(pending_change) = <PendingChange<T>>::get() {
|
||||
if block_number == pending_change.scheduled_at {
|
||||
Self::deposit_log(RawLog::AuthoritiesChangeSignal(
|
||||
pending_change.delay,
|
||||
pending_change.next_authorities.clone(),
|
||||
));
|
||||
}
|
||||
|
||||
if block_number == pending_change.scheduled_at + pending_change.delay {
|
||||
Self::deposit_event(
|
||||
RawEvent::NewAuthorities(pending_change.next_authorities.clone())
|
||||
);
|
||||
<AuthorityStorageVec<T::SessionKey>>::set_items(pending_change.next_authorities);
|
||||
<PendingChange<T>>::kill();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Trait> Module<T> {
|
||||
/// Get the current set of authorities, along with their respective weights.
|
||||
pub fn grandpa_authorities() -> Vec<(T::SessionKey, u64)> {
|
||||
<AuthorityStorageVec<T::SessionKey>>::items()
|
||||
}
|
||||
|
||||
/// Schedule a change in the authorities.
|
||||
///
|
||||
/// The change will be applied at the end of execution of the block
|
||||
/// `in_blocks` after the current block. This value may be 0, in which
|
||||
/// case the change is applied at the end of the current block.
|
||||
///
|
||||
/// No change should be signalled while any change is pending. Returns
|
||||
/// an error if a change is already pending.
|
||||
pub fn schedule_change(
|
||||
next_authorities: Vec<(T::SessionKey, u64)>,
|
||||
in_blocks: T::BlockNumber,
|
||||
) -> Result {
|
||||
if Self::pending_change().is_none() {
|
||||
let scheduled_at = system::ChainContext::<T>::default().current_height();
|
||||
<PendingChange<T>>::put(StoredPendingChange {
|
||||
delay: in_blocks,
|
||||
scheduled_at,
|
||||
next_authorities,
|
||||
});
|
||||
|
||||
Ok(())
|
||||
} else {
|
||||
Err("Attempt to signal GRANDPA change with one already pending.")
|
||||
}
|
||||
}
|
||||
|
||||
/// Deposit one of this module's logs.
|
||||
fn deposit_log(log: Log<T>) {
|
||||
<system::Module<T>>::deposit_log(<T as Trait>::Log::from(log).into());
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Trait> Module<T> where AuthorityId: core::convert::From<<T as Trait>::SessionKey> {
|
||||
/// See if the digest contains any scheduled change.
|
||||
pub fn scrape_digest_change(log: &Log<T>)
|
||||
-> Option<ScheduledChange<T::BlockNumber>>
|
||||
{
|
||||
<Log<T> as GrandpaChangeSignal<T::BlockNumber>>::as_signal(log)
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper for authorities being synchronized with the general session authorities.
|
||||
///
|
||||
/// This is not the only way to manage an authority set for GRANDPA, but it is
|
||||
/// a convenient one. When this is used, no other mechanism for altering authority
|
||||
/// sets should be.
|
||||
pub struct SyncedAuthorities<T>(::rstd::marker::PhantomData<T>);
|
||||
|
||||
// TODO: remove when https://github.com/rust-lang/rust/issues/26925 is fixed
|
||||
impl<T> Default for SyncedAuthorities<T> {
|
||||
fn default() -> Self {
|
||||
SyncedAuthorities(::rstd::marker::PhantomData)
|
||||
}
|
||||
}
|
||||
|
||||
impl<X, T> session::OnSessionChange<X> for SyncedAuthorities<T> where
|
||||
T: Trait,
|
||||
T: session::Trait,
|
||||
<T as session::Trait>::ConvertAccountIdToSessionKey: Convert<
|
||||
<T as system::Trait>::AccountId,
|
||||
<T as Trait>::SessionKey,
|
||||
>,
|
||||
{
|
||||
fn on_session_change(_: X, _: bool) {
|
||||
use primitives::traits::Zero;
|
||||
|
||||
let next_authorities = <session::Module<T>>::validators()
|
||||
.into_iter()
|
||||
.map(T::ConvertAccountIdToSessionKey::convert)
|
||||
.map(|key| (key, 1)) // evenly-weighted.
|
||||
.collect::<Vec<(<T as Trait>::SessionKey, u64)>>();
|
||||
|
||||
// instant changes
|
||||
let last_authorities = <Module<T>>::grandpa_authorities();
|
||||
if next_authorities != last_authorities {
|
||||
let _ = <Module<T>>::schedule_change(next_authorities, Zero::zero());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
// Copyright 2018 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Substrate is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Substrate is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Test utilities
|
||||
|
||||
#![cfg(test)]
|
||||
|
||||
use primitives::{BuildStorage, testing::{Digest, DigestItem, Header}};
|
||||
use primitives::generic::DigestItem as GenDigestItem;
|
||||
use runtime_io;
|
||||
use substrate_primitives::{H256, Blake2Hasher};
|
||||
use parity_codec::Encode;
|
||||
use {system, GenesisConfig, Trait, Module, RawLog};
|
||||
|
||||
impl_outer_origin!{
|
||||
pub enum Origin for Test {}
|
||||
}
|
||||
|
||||
impl From<RawLog<u64, u64>> for DigestItem {
|
||||
fn from(log: RawLog<u64, u64>) -> DigestItem {
|
||||
GenDigestItem::Other(log.encode())
|
||||
}
|
||||
}
|
||||
|
||||
// Workaround for https://github.com/rust-lang/rust/issues/26925 . Remove when sorted.
|
||||
#[derive(Clone, PartialEq, Eq, Debug, Decode, Encode)]
|
||||
pub struct Test;
|
||||
impl Trait for Test {
|
||||
type Log = DigestItem;
|
||||
type SessionKey = u64;
|
||||
type Event = TestEvent;
|
||||
}
|
||||
impl system::Trait for Test {
|
||||
type Origin = Origin;
|
||||
type Index = u64;
|
||||
type BlockNumber = u64;
|
||||
type Hash = H256;
|
||||
type Hashing = ::primitives::traits::BlakeTwo256;
|
||||
type Digest = Digest;
|
||||
type AccountId = u64;
|
||||
type Header = Header;
|
||||
type Event = TestEvent;
|
||||
type Log = DigestItem;
|
||||
}
|
||||
|
||||
mod grandpa {
|
||||
pub use ::Event;
|
||||
}
|
||||
|
||||
impl_outer_event!{
|
||||
pub enum TestEvent for Test {
|
||||
grandpa<T>,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_test_ext(authorities: Vec<(u64, u64)>) -> runtime_io::TestExternalities<Blake2Hasher> {
|
||||
let mut t = system::GenesisConfig::<Test>::default().build_storage().unwrap().0;
|
||||
t.extend(GenesisConfig::<Test> {
|
||||
_genesis_phantom_data: Default::default(),
|
||||
authorities,
|
||||
}.build_storage().unwrap().0);
|
||||
t.into()
|
||||
}
|
||||
|
||||
pub type System = system::Module<Test>;
|
||||
pub type Grandpa = Module<Test>;
|
||||
@@ -0,0 +1,108 @@
|
||||
// Copyright 2017-2018 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Substrate is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Substrate is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Tests for the module.
|
||||
|
||||
#![cfg(test)]
|
||||
|
||||
use primitives::{testing, traits::OnFinalise};
|
||||
use primitives::traits::Header;
|
||||
use runtime_io::with_externalities;
|
||||
use mock::{Grandpa, System, new_test_ext};
|
||||
use system::{EventRecord, Phase};
|
||||
use {RawLog, RawEvent};
|
||||
|
||||
#[test]
|
||||
fn authorities_change_logged() {
|
||||
with_externalities(&mut new_test_ext(vec![(1, 1), (2, 1), (3, 1)]), || {
|
||||
System::initialise(&1, &Default::default(), &Default::default());
|
||||
Grandpa::schedule_change(vec![(4, 1), (5, 1), (6, 1)], 0).unwrap();
|
||||
|
||||
System::note_finished_extrinsics();
|
||||
Grandpa::on_finalise(1);
|
||||
|
||||
let header = System::finalise();
|
||||
assert_eq!(header.digest, testing::Digest {
|
||||
logs: vec![
|
||||
RawLog::AuthoritiesChangeSignal(0, vec![(4, 1), (5, 1), (6, 1)]).into(),
|
||||
],
|
||||
});
|
||||
|
||||
assert_eq!(System::events(), vec![
|
||||
EventRecord {
|
||||
phase: Phase::Finalization,
|
||||
event: RawEvent::NewAuthorities(vec![(4, 1), (5, 1), (6, 1)]).into(),
|
||||
},
|
||||
]);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn authorities_change_logged_after_delay() {
|
||||
with_externalities(&mut new_test_ext(vec![(1, 1), (2, 1), (3, 1)]), || {
|
||||
System::initialise(&1, &Default::default(), &Default::default());
|
||||
Grandpa::schedule_change(vec![(4, 1), (5, 1), (6, 1)], 1).unwrap();
|
||||
Grandpa::on_finalise(1);
|
||||
let header = System::finalise();
|
||||
assert_eq!(header.digest, testing::Digest {
|
||||
logs: vec![
|
||||
RawLog::AuthoritiesChangeSignal(1, vec![(4, 1), (5, 1), (6, 1)]).into(),
|
||||
],
|
||||
});
|
||||
|
||||
// no change at this height.
|
||||
assert_eq!(System::events(), vec![]);
|
||||
|
||||
System::initialise(&2, &header.hash(), &Default::default());
|
||||
System::note_finished_extrinsics();
|
||||
Grandpa::on_finalise(2);
|
||||
|
||||
let _header = System::finalise();
|
||||
assert_eq!(System::events(), vec![
|
||||
EventRecord {
|
||||
phase: Phase::Finalization,
|
||||
event: RawEvent::NewAuthorities(vec![(4, 1), (5, 1), (6, 1)]).into(),
|
||||
},
|
||||
]);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cannot_schedule_change_when_one_pending() {
|
||||
with_externalities(&mut new_test_ext(vec![(1, 1), (2, 1), (3, 1)]), || {
|
||||
System::initialise(&1, &Default::default(), &Default::default());
|
||||
Grandpa::schedule_change(vec![(4, 1), (5, 1), (6, 1)], 1).unwrap();
|
||||
assert!(Grandpa::pending_change().is_some());
|
||||
assert!(Grandpa::schedule_change(vec![(5, 1)], 1).is_err());
|
||||
|
||||
Grandpa::on_finalise(1);
|
||||
let header = System::finalise();
|
||||
|
||||
System::initialise(&2, &header.hash(), &Default::default());
|
||||
assert!(Grandpa::pending_change().is_some());
|
||||
assert!(Grandpa::schedule_change(vec![(5, 1)], 1).is_err());
|
||||
|
||||
Grandpa::on_finalise(2);
|
||||
let header = System::finalise();
|
||||
|
||||
System::initialise(&3, &header.hash(), &Default::default());
|
||||
assert!(Grandpa::pending_change().is_none());
|
||||
assert!(Grandpa::schedule_change(vec![(5, 1)], 1).is_ok());
|
||||
|
||||
Grandpa::on_finalise(3);
|
||||
let _header = System::finalise();
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user