mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-05-30 07:01:03 +00:00
Rename Palette to FRAME (#4182)
* palette -> frame * PALETTE, Palette -> FRAME * Move folder pallete -> frame * Update docs/Structure.adoc Co-Authored-By: Benjamin Kampmann <ben.kampmann@googlemail.com> * Update docs/README.adoc Co-Authored-By: Benjamin Kampmann <ben.kampmann@googlemail.com> * Update README.adoc
This commit is contained in:
@@ -0,0 +1,510 @@
|
||||
// Copyright 2017-2019 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Substrate is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Substrate is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! 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)]
|
||||
|
||||
// re-export since this is necessary for `impl_apis` in runtime.
|
||||
pub use substrate_finality_grandpa_primitives as fg_primitives;
|
||||
|
||||
use rstd::prelude::*;
|
||||
use codec::{self as codec, Encode, Decode, Error};
|
||||
use support::{decl_event, decl_storage, decl_module, dispatch::Result, storage};
|
||||
use sr_primitives::{
|
||||
generic::{DigestItem, OpaqueDigestItemId}, traits::Zero, Perbill,
|
||||
};
|
||||
use sr_staking_primitives::{
|
||||
SessionIndex,
|
||||
offence::{Offence, Kind},
|
||||
};
|
||||
use fg_primitives::{
|
||||
GRANDPA_AUTHORITIES_KEY, GRANDPA_ENGINE_ID, ScheduledChange, ConsensusLog, SetId, RoundNumber,
|
||||
};
|
||||
pub use fg_primitives::{AuthorityId, AuthorityList, AuthorityWeight, VersionedAuthorityList};
|
||||
use system::{ensure_signed, DigestOf};
|
||||
|
||||
mod mock;
|
||||
mod tests;
|
||||
|
||||
pub trait Trait: system::Trait {
|
||||
/// The event type of this module.
|
||||
type Event: From<Event> + Into<<Self as system::Trait>::Event>;
|
||||
}
|
||||
|
||||
/// A stored pending change, old format.
|
||||
// TODO: remove shim
|
||||
// https://github.com/paritytech/substrate/issues/1614
|
||||
#[derive(Encode, Decode)]
|
||||
pub struct OldStoredPendingChange<N> {
|
||||
/// 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: AuthorityList,
|
||||
}
|
||||
|
||||
/// A stored pending change.
|
||||
#[derive(Encode)]
|
||||
pub struct StoredPendingChange<N> {
|
||||
/// The block number this was scheduled at.
|
||||
pub scheduled_at: N,
|
||||
/// The delay in blocks until it will be applied.
|
||||
pub delay: N,
|
||||
/// The next authority set.
|
||||
pub next_authorities: AuthorityList,
|
||||
/// If defined it means the change was forced and the given block number
|
||||
/// indicates the median last finalized block when the change was signaled.
|
||||
pub forced: Option<N>,
|
||||
}
|
||||
|
||||
impl<N: Decode> Decode for StoredPendingChange<N> {
|
||||
fn decode<I: codec::Input>(value: &mut I) -> core::result::Result<Self, Error> {
|
||||
let old = OldStoredPendingChange::decode(value)?;
|
||||
let forced = <Option<N>>::decode(value).unwrap_or(None);
|
||||
|
||||
Ok(StoredPendingChange {
|
||||
scheduled_at: old.scheduled_at,
|
||||
delay: old.delay,
|
||||
next_authorities: old.next_authorities,
|
||||
forced,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Current state of the GRANDPA authority set. State transitions must happen in
|
||||
/// the same order of states defined below, e.g. `Paused` implies a prior
|
||||
/// `PendingPause`.
|
||||
#[derive(Decode, Encode)]
|
||||
#[cfg_attr(test, derive(Debug, PartialEq))]
|
||||
pub enum StoredState<N> {
|
||||
/// The current authority set is live, and GRANDPA is enabled.
|
||||
Live,
|
||||
/// There is a pending pause event which will be enacted at the given block
|
||||
/// height.
|
||||
PendingPause {
|
||||
/// Block at which the intention to pause was scheduled.
|
||||
scheduled_at: N,
|
||||
/// Number of blocks after which the change will be enacted.
|
||||
delay: N
|
||||
},
|
||||
/// The current GRANDPA authority set is paused.
|
||||
Paused,
|
||||
/// There is a pending resume event which will be enacted at the given block
|
||||
/// height.
|
||||
PendingResume {
|
||||
/// Block at which the intention to resume was scheduled.
|
||||
scheduled_at: N,
|
||||
/// Number of blocks after which the change will be enacted.
|
||||
delay: N,
|
||||
},
|
||||
}
|
||||
|
||||
decl_event!(
|
||||
pub enum Event {
|
||||
/// New authority set has been applied.
|
||||
NewAuthorities(AuthorityList),
|
||||
/// Current authority set has been paused.
|
||||
Paused,
|
||||
/// Current authority set has been resumed.
|
||||
Resumed,
|
||||
}
|
||||
);
|
||||
|
||||
decl_storage! {
|
||||
trait Store for Module<T: Trait> as GrandpaFinality {
|
||||
/// DEPRECATED
|
||||
///
|
||||
/// This used to store the current authority set, which has been migrated to the well-known
|
||||
/// GRANDPA_AUTHORITES_KEY unhashed key.
|
||||
#[cfg(feature = "migrate-authorities")]
|
||||
pub(crate) Authorities get(fn authorities): AuthorityList;
|
||||
|
||||
/// State of the current authority set.
|
||||
State get(fn state): StoredState<T::BlockNumber> = StoredState::Live;
|
||||
|
||||
/// Pending change: (signaled at, scheduled change).
|
||||
PendingChange: Option<StoredPendingChange<T::BlockNumber>>;
|
||||
|
||||
/// next block number where we can force a change.
|
||||
NextForced get(fn next_forced): Option<T::BlockNumber>;
|
||||
|
||||
/// `true` if we are currently stalled.
|
||||
Stalled get(fn stalled): Option<(T::BlockNumber, T::BlockNumber)>;
|
||||
|
||||
/// The number of changes (both in terms of keys and underlying economic responsibilities)
|
||||
/// in the "set" of Grandpa validators from genesis.
|
||||
CurrentSetId get(fn current_set_id) build(|_| fg_primitives::SetId::default()): SetId;
|
||||
|
||||
/// A mapping from grandpa set ID to the index of the *most recent* session for which its members were responsible.
|
||||
SetIdSession get(fn session_for_set): map SetId => Option<SessionIndex>;
|
||||
}
|
||||
add_extra_genesis {
|
||||
config(authorities): AuthorityList;
|
||||
build(|config| Module::<T>::initialize_authorities(&config.authorities))
|
||||
}
|
||||
}
|
||||
|
||||
decl_module! {
|
||||
pub struct Module<T: Trait> for enum Call where origin: T::Origin {
|
||||
fn deposit_event() = default;
|
||||
|
||||
/// Report some misbehavior.
|
||||
fn report_misbehavior(origin, _report: Vec<u8>) {
|
||||
ensure_signed(origin)?;
|
||||
// FIXME: https://github.com/paritytech/substrate/issues/1112
|
||||
}
|
||||
|
||||
fn on_initialize() {
|
||||
#[cfg(feature = "migrate-authorities")]
|
||||
Self::migrate_authorities();
|
||||
}
|
||||
|
||||
fn on_finalize(block_number: T::BlockNumber) {
|
||||
// check for scheduled pending authority set changes
|
||||
if let Some(pending_change) = <PendingChange<T>>::get() {
|
||||
// emit signal if we're at the block that scheduled the change
|
||||
if block_number == pending_change.scheduled_at {
|
||||
if let Some(median) = pending_change.forced {
|
||||
Self::deposit_log(ConsensusLog::ForcedChange(
|
||||
median,
|
||||
ScheduledChange {
|
||||
delay: pending_change.delay,
|
||||
next_authorities: pending_change.next_authorities.clone(),
|
||||
}
|
||||
))
|
||||
} else {
|
||||
Self::deposit_log(ConsensusLog::ScheduledChange(
|
||||
ScheduledChange{
|
||||
delay: pending_change.delay,
|
||||
next_authorities: pending_change.next_authorities.clone(),
|
||||
}
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
// enact the change if we've reached the enacting block
|
||||
if block_number == pending_change.scheduled_at + pending_change.delay {
|
||||
Self::set_grandpa_authorities(&pending_change.next_authorities);
|
||||
Self::deposit_event(
|
||||
Event::NewAuthorities(pending_change.next_authorities)
|
||||
);
|
||||
<PendingChange<T>>::kill();
|
||||
}
|
||||
}
|
||||
|
||||
// check for scheduled pending state changes
|
||||
match <State<T>>::get() {
|
||||
StoredState::PendingPause { scheduled_at, delay } => {
|
||||
// signal change to pause
|
||||
if block_number == scheduled_at {
|
||||
Self::deposit_log(ConsensusLog::Pause(delay));
|
||||
}
|
||||
|
||||
// enact change to paused state
|
||||
if block_number == scheduled_at + delay {
|
||||
<State<T>>::put(StoredState::Paused);
|
||||
Self::deposit_event(Event::Paused);
|
||||
}
|
||||
},
|
||||
StoredState::PendingResume { scheduled_at, delay } => {
|
||||
// signal change to resume
|
||||
if block_number == scheduled_at {
|
||||
Self::deposit_log(ConsensusLog::Resume(delay));
|
||||
}
|
||||
|
||||
// enact change to live state
|
||||
if block_number == scheduled_at + delay {
|
||||
<State<T>>::put(StoredState::Live);
|
||||
Self::deposit_event(Event::Resumed);
|
||||
}
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Trait> Module<T> {
|
||||
/// Get the current set of authorities, along with their respective weights.
|
||||
pub fn grandpa_authorities() -> AuthorityList {
|
||||
storage::unhashed::get_or_default::<VersionedAuthorityList>(GRANDPA_AUTHORITIES_KEY).into()
|
||||
}
|
||||
|
||||
/// Set the current set of authorities, along with their respective weights.
|
||||
fn set_grandpa_authorities(authorities: &AuthorityList) {
|
||||
storage::unhashed::put(
|
||||
GRANDPA_AUTHORITIES_KEY,
|
||||
&VersionedAuthorityList::from(authorities),
|
||||
);
|
||||
}
|
||||
|
||||
/// Schedule GRANDPA to pause starting in the given number of blocks.
|
||||
/// Cannot be done when already paused.
|
||||
pub fn schedule_pause(in_blocks: T::BlockNumber) -> Result {
|
||||
if let StoredState::Live = <State<T>>::get() {
|
||||
let scheduled_at = <system::Module<T>>::block_number();
|
||||
<State<T>>::put(StoredState::PendingPause {
|
||||
delay: in_blocks,
|
||||
scheduled_at,
|
||||
});
|
||||
|
||||
Ok(())
|
||||
} else {
|
||||
Err("Attempt to signal GRANDPA pause when the authority set isn't live \
|
||||
(either paused or already pending pause).")
|
||||
}
|
||||
}
|
||||
|
||||
/// Schedule a resume of GRANDPA after pausing.
|
||||
pub fn schedule_resume(in_blocks: T::BlockNumber) -> Result {
|
||||
if let StoredState::Paused = <State<T>>::get() {
|
||||
let scheduled_at = <system::Module<T>>::block_number();
|
||||
<State<T>>::put(StoredState::PendingResume {
|
||||
delay: in_blocks,
|
||||
scheduled_at,
|
||||
});
|
||||
|
||||
Ok(())
|
||||
} else {
|
||||
Err("Attempt to signal GRANDPA resume when the authority set isn't paused \
|
||||
(either live or already pending resume).")
|
||||
}
|
||||
}
|
||||
|
||||
/// 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.
|
||||
///
|
||||
/// If the `forced` parameter is defined, this indicates that the current
|
||||
/// set has been synchronously determined to be offline and that after
|
||||
/// `in_blocks` the given change should be applied. The given block number
|
||||
/// indicates the median last finalized block number and it should be used
|
||||
/// as the canon block when starting the new grandpa voter.
|
||||
///
|
||||
/// No change should be signaled while any change is pending. Returns
|
||||
/// an error if a change is already pending.
|
||||
pub fn schedule_change(
|
||||
next_authorities: AuthorityList,
|
||||
in_blocks: T::BlockNumber,
|
||||
forced: Option<T::BlockNumber>,
|
||||
) -> Result {
|
||||
if !<PendingChange<T>>::exists() {
|
||||
let scheduled_at = <system::Module<T>>::block_number();
|
||||
|
||||
if let Some(_) = forced {
|
||||
if Self::next_forced().map_or(false, |next| next > scheduled_at) {
|
||||
return Err("Cannot signal forced change so soon after last.");
|
||||
}
|
||||
|
||||
// only allow the next forced change when twice the window has passed since
|
||||
// this one.
|
||||
<NextForced<T>>::put(scheduled_at + in_blocks * 2.into());
|
||||
}
|
||||
|
||||
<PendingChange<T>>::put(StoredPendingChange {
|
||||
delay: in_blocks,
|
||||
scheduled_at,
|
||||
next_authorities,
|
||||
forced,
|
||||
});
|
||||
|
||||
Ok(())
|
||||
} else {
|
||||
Err("Attempt to signal GRANDPA change with one already pending.")
|
||||
}
|
||||
}
|
||||
|
||||
/// Deposit one of this module's logs.
|
||||
fn deposit_log(log: ConsensusLog<T::BlockNumber>) {
|
||||
let log: DigestItem<T::Hash> = DigestItem::Consensus(GRANDPA_ENGINE_ID, log.encode());
|
||||
<system::Module<T>>::deposit_log(log.into());
|
||||
}
|
||||
|
||||
fn initialize_authorities(authorities: &AuthorityList) {
|
||||
if !authorities.is_empty() {
|
||||
assert!(
|
||||
Self::grandpa_authorities().is_empty(),
|
||||
"Authorities are already initialized!"
|
||||
);
|
||||
Self::set_grandpa_authorities(authorities);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "migrate-authorities")]
|
||||
fn migrate_authorities() {
|
||||
if Authorities::exists() {
|
||||
Self::set_grandpa_authorities(&Authorities::take());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Trait> Module<T> {
|
||||
/// Attempt to extract a GRANDPA log from a generic digest.
|
||||
pub fn grandpa_log(digest: &DigestOf<T>) -> Option<ConsensusLog<T::BlockNumber>> {
|
||||
let id = OpaqueDigestItemId::Consensus(&GRANDPA_ENGINE_ID);
|
||||
digest.convert_first(|l| l.try_to::<ConsensusLog<T::BlockNumber>>(id))
|
||||
}
|
||||
|
||||
/// Attempt to extract a pending set-change signal from a digest.
|
||||
pub fn pending_change(digest: &DigestOf<T>)
|
||||
-> Option<ScheduledChange<T::BlockNumber>>
|
||||
{
|
||||
Self::grandpa_log(digest).and_then(|signal| signal.try_into_change())
|
||||
}
|
||||
|
||||
/// Attempt to extract a forced set-change signal from a digest.
|
||||
pub fn forced_change(digest: &DigestOf<T>)
|
||||
-> Option<(T::BlockNumber, ScheduledChange<T::BlockNumber>)>
|
||||
{
|
||||
Self::grandpa_log(digest).and_then(|signal| signal.try_into_forced_change())
|
||||
}
|
||||
|
||||
/// Attempt to extract a pause signal from a digest.
|
||||
pub fn pending_pause(digest: &DigestOf<T>)
|
||||
-> Option<T::BlockNumber>
|
||||
{
|
||||
Self::grandpa_log(digest).and_then(|signal| signal.try_into_pause())
|
||||
}
|
||||
|
||||
/// Attempt to extract a resume signal from a digest.
|
||||
pub fn pending_resume(digest: &DigestOf<T>)
|
||||
-> Option<T::BlockNumber>
|
||||
{
|
||||
Self::grandpa_log(digest).and_then(|signal| signal.try_into_resume())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Trait> sr_primitives::BoundToRuntimeAppPublic for Module<T> {
|
||||
type Public = AuthorityId;
|
||||
}
|
||||
|
||||
impl<T: Trait> session::OneSessionHandler<T::AccountId> for Module<T>
|
||||
where T: session::Trait
|
||||
{
|
||||
type Key = AuthorityId;
|
||||
|
||||
fn on_genesis_session<'a, I: 'a>(validators: I)
|
||||
where I: Iterator<Item=(&'a T::AccountId, AuthorityId)>
|
||||
{
|
||||
let authorities = validators.map(|(_, k)| (k, 1)).collect::<Vec<_>>();
|
||||
Self::initialize_authorities(&authorities);
|
||||
}
|
||||
|
||||
fn on_new_session<'a, I: 'a>(changed: bool, validators: I, _queued_validators: I)
|
||||
where I: Iterator<Item=(&'a T::AccountId, AuthorityId)>
|
||||
{
|
||||
// Always issue a change if `session` says that the validators have changed.
|
||||
// Even if their session keys are the same as before, the underyling economic
|
||||
// identities have changed.
|
||||
let current_set_id = if changed {
|
||||
let next_authorities = validators.map(|(_, k)| (k, 1)).collect::<Vec<_>>();
|
||||
if let Some((further_wait, median)) = <Stalled<T>>::take() {
|
||||
let _ = Self::schedule_change(next_authorities, further_wait, Some(median));
|
||||
} else {
|
||||
let _ = Self::schedule_change(next_authorities, Zero::zero(), None);
|
||||
}
|
||||
CurrentSetId::mutate(|s| { *s += 1; *s })
|
||||
} else {
|
||||
// nothing's changed, neither economic conditions nor session keys. update the pointer
|
||||
// of the current set.
|
||||
Self::current_set_id()
|
||||
};
|
||||
|
||||
// if we didn't issue a change, we update the mapping to note that the current
|
||||
// set corresponds to the latest equivalent session (i.e. now).
|
||||
let session_index = <session::Module<T>>::current_index();
|
||||
SetIdSession::insert(current_set_id, &session_index);
|
||||
}
|
||||
|
||||
fn on_disabled(i: usize) {
|
||||
Self::deposit_log(ConsensusLog::OnDisabled(i as u64))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Trait> finality_tracker::OnFinalizationStalled<T::BlockNumber> for Module<T> {
|
||||
fn on_stalled(further_wait: T::BlockNumber, median: T::BlockNumber) {
|
||||
// when we record old authority sets, we can use `finality_tracker::median`
|
||||
// to figure out _who_ failed. until then, we can't meaningfully guard
|
||||
// against `next == last` the way that normal session changes do.
|
||||
<Stalled<T>>::put((further_wait, median));
|
||||
}
|
||||
}
|
||||
|
||||
/// A round number and set id which point on the time of an offence.
|
||||
#[derive(Copy, Clone, PartialOrd, Ord, Eq, PartialEq, Encode, Decode)]
|
||||
struct GrandpaTimeSlot {
|
||||
// The order of these matters for `derive(Ord)`.
|
||||
set_id: SetId,
|
||||
round: RoundNumber,
|
||||
}
|
||||
|
||||
// TODO [slashing]: Integrate this.
|
||||
/// A grandpa equivocation offence report.
|
||||
#[allow(dead_code)]
|
||||
struct GrandpaEquivocationOffence<FullIdentification> {
|
||||
/// Time slot at which this incident happened.
|
||||
time_slot: GrandpaTimeSlot,
|
||||
/// The session index in which the incident happened.
|
||||
session_index: SessionIndex,
|
||||
/// The size of the validator set at the time of the offence.
|
||||
validator_set_count: u32,
|
||||
/// The authority which produced this equivocation.
|
||||
offender: FullIdentification,
|
||||
}
|
||||
|
||||
impl<FullIdentification: Clone> Offence<FullIdentification> for GrandpaEquivocationOffence<FullIdentification> {
|
||||
const ID: Kind = *b"grandpa:equivoca";
|
||||
type TimeSlot = GrandpaTimeSlot;
|
||||
|
||||
fn offenders(&self) -> Vec<FullIdentification> {
|
||||
vec![self.offender.clone()]
|
||||
}
|
||||
|
||||
fn session_index(&self) -> SessionIndex {
|
||||
self.session_index
|
||||
}
|
||||
|
||||
fn validator_set_count(&self) -> u32 {
|
||||
self.validator_set_count
|
||||
}
|
||||
|
||||
fn time_slot(&self) -> Self::TimeSlot {
|
||||
self.time_slot
|
||||
}
|
||||
|
||||
fn slash_fraction(
|
||||
offenders_count: u32,
|
||||
validator_set_count: u32,
|
||||
) -> Perbill {
|
||||
// the formula is min((3k / n)^2, 1)
|
||||
let x = Perbill::from_rational_approximation(3 * offenders_count, validator_set_count);
|
||||
// _ ^ 2
|
||||
x.square()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
// Copyright 2018-2019 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Substrate is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Substrate is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Test utilities
|
||||
|
||||
#![cfg(test)]
|
||||
|
||||
use sr_primitives::{Perbill, DigestItem, traits::IdentityLookup, testing::{Header, UintAuthorityId}};
|
||||
use runtime_io;
|
||||
use support::{impl_outer_origin, impl_outer_event, parameter_types};
|
||||
use primitives::H256;
|
||||
use codec::{Encode, Decode};
|
||||
use crate::{AuthorityId, AuthorityList, GenesisConfig, Trait, Module, ConsensusLog};
|
||||
use substrate_finality_grandpa_primitives::GRANDPA_ENGINE_ID;
|
||||
|
||||
impl_outer_origin!{
|
||||
pub enum Origin for Test {}
|
||||
}
|
||||
|
||||
pub fn grandpa_log(log: ConsensusLog<u64>) -> DigestItem<H256> {
|
||||
DigestItem::Consensus(GRANDPA_ENGINE_ID, 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 Event = TestEvent;
|
||||
}
|
||||
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 Call = ();
|
||||
type Hash = H256;
|
||||
type Hashing = sr_primitives::traits::BlakeTwo256;
|
||||
type AccountId = u64;
|
||||
type Lookup = IdentityLookup<Self::AccountId>;
|
||||
type Header = Header;
|
||||
type Event = TestEvent;
|
||||
type BlockHashCount = BlockHashCount;
|
||||
type MaximumBlockWeight = MaximumBlockWeight;
|
||||
type MaximumBlockLength = MaximumBlockLength;
|
||||
type AvailableBlockRatio = AvailableBlockRatio;
|
||||
type Version = ();
|
||||
}
|
||||
|
||||
mod grandpa {
|
||||
pub use crate::Event;
|
||||
}
|
||||
|
||||
impl_outer_event!{
|
||||
pub enum TestEvent for Test {
|
||||
grandpa,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_authorities(vec: Vec<(u64, u64)>) -> AuthorityList {
|
||||
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 {
|
||||
let mut t = system::GenesisConfig::default().build_storage::<Test>().unwrap();
|
||||
GenesisConfig {
|
||||
authorities: to_authorities(authorities),
|
||||
}.assimilate_storage::<Test>(&mut t).unwrap();
|
||||
t.into()
|
||||
}
|
||||
|
||||
pub type System = system::Module<Test>;
|
||||
pub type Grandpa = Module<Test>;
|
||||
@@ -0,0 +1,328 @@
|
||||
// Copyright 2017-2019 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Substrate is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Substrate is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Tests for the module.
|
||||
|
||||
#![cfg(test)]
|
||||
|
||||
use sr_primitives::{testing::Digest, traits::{Header, OnFinalize}};
|
||||
use crate::mock::*;
|
||||
use system::{EventRecord, Phase};
|
||||
use codec::{Decode, Encode};
|
||||
use fg_primitives::ScheduledChange;
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn authorities_change_logged() {
|
||||
new_test_ext(vec![(1, 1), (2, 1), (3, 1)]).execute_with(|| {
|
||||
System::initialize(&1, &Default::default(), &Default::default(), &Default::default());
|
||||
Grandpa::schedule_change(to_authorities(vec![(4, 1), (5, 1), (6, 1)]), 0, None).unwrap();
|
||||
|
||||
System::note_finished_extrinsics();
|
||||
Grandpa::on_finalize(1);
|
||||
|
||||
let header = System::finalize();
|
||||
assert_eq!(header.digest, Digest {
|
||||
logs: vec![
|
||||
grandpa_log(ConsensusLog::ScheduledChange(
|
||||
ScheduledChange { delay: 0, next_authorities: to_authorities(vec![(4, 1), (5, 1), (6, 1)]) }
|
||||
)),
|
||||
],
|
||||
});
|
||||
|
||||
assert_eq!(System::events(), vec![
|
||||
EventRecord {
|
||||
phase: Phase::Finalization,
|
||||
event: Event::NewAuthorities(to_authorities(vec![(4, 1), (5, 1), (6, 1)])).into(),
|
||||
topics: vec![],
|
||||
},
|
||||
]);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn authorities_change_logged_after_delay() {
|
||||
new_test_ext(vec![(1, 1), (2, 1), (3, 1)]).execute_with(|| {
|
||||
System::initialize(&1, &Default::default(), &Default::default(), &Default::default());
|
||||
Grandpa::schedule_change(to_authorities(vec![(4, 1), (5, 1), (6, 1)]), 1, None).unwrap();
|
||||
Grandpa::on_finalize(1);
|
||||
let header = System::finalize();
|
||||
assert_eq!(header.digest, Digest {
|
||||
logs: vec![
|
||||
grandpa_log(ConsensusLog::ScheduledChange(
|
||||
ScheduledChange { delay: 1, next_authorities: to_authorities(vec![(4, 1), (5, 1), (6, 1)]) }
|
||||
)),
|
||||
],
|
||||
});
|
||||
|
||||
// no change at this height.
|
||||
assert_eq!(System::events(), vec![]);
|
||||
|
||||
System::initialize(&2, &header.hash(), &Default::default(), &Default::default());
|
||||
System::note_finished_extrinsics();
|
||||
Grandpa::on_finalize(2);
|
||||
|
||||
let _header = System::finalize();
|
||||
assert_eq!(System::events(), vec![
|
||||
EventRecord {
|
||||
phase: Phase::Finalization,
|
||||
event: Event::NewAuthorities(to_authorities(vec![(4, 1), (5, 1), (6, 1)])).into(),
|
||||
topics: vec![],
|
||||
},
|
||||
]);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cannot_schedule_change_when_one_pending() {
|
||||
new_test_ext(vec![(1, 1), (2, 1), (3, 1)]).execute_with(|| {
|
||||
System::initialize(&1, &Default::default(), &Default::default(), &Default::default());
|
||||
Grandpa::schedule_change(to_authorities(vec![(4, 1), (5, 1), (6, 1)]), 1, None).unwrap();
|
||||
assert!(<PendingChange<Test>>::exists());
|
||||
assert!(Grandpa::schedule_change(to_authorities(vec![(5, 1)]), 1, None).is_err());
|
||||
|
||||
Grandpa::on_finalize(1);
|
||||
let header = System::finalize();
|
||||
|
||||
System::initialize(&2, &header.hash(), &Default::default(), &Default::default());
|
||||
assert!(<PendingChange<Test>>::exists());
|
||||
assert!(Grandpa::schedule_change(to_authorities(vec![(5, 1)]), 1, None).is_err());
|
||||
|
||||
Grandpa::on_finalize(2);
|
||||
let header = System::finalize();
|
||||
|
||||
System::initialize(&3, &header.hash(), &Default::default(), &Default::default());
|
||||
assert!(!<PendingChange<Test>>::exists());
|
||||
assert!(Grandpa::schedule_change(to_authorities(vec![(5, 1)]), 1, None).is_ok());
|
||||
|
||||
Grandpa::on_finalize(3);
|
||||
let _header = System::finalize();
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn new_decodes_from_old() {
|
||||
let old = OldStoredPendingChange {
|
||||
scheduled_at: 5u32,
|
||||
delay: 100u32,
|
||||
next_authorities: to_authorities(vec![(1, 5), (2, 10), (3, 2)]),
|
||||
};
|
||||
|
||||
let encoded = old.encode();
|
||||
let new = StoredPendingChange::<u32>::decode(&mut &encoded[..]).unwrap();
|
||||
assert!(new.forced.is_none());
|
||||
assert_eq!(new.scheduled_at, old.scheduled_at);
|
||||
assert_eq!(new.delay, old.delay);
|
||||
assert_eq!(new.next_authorities, old.next_authorities);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dispatch_forced_change() {
|
||||
new_test_ext(vec![(1, 1), (2, 1), (3, 1)]).execute_with(|| {
|
||||
System::initialize(&1, &Default::default(), &Default::default(), &Default::default());
|
||||
Grandpa::schedule_change(
|
||||
to_authorities(vec![(4, 1), (5, 1), (6, 1)]),
|
||||
5,
|
||||
Some(0),
|
||||
).unwrap();
|
||||
|
||||
assert!(<PendingChange<Test>>::exists());
|
||||
assert!(Grandpa::schedule_change(to_authorities(vec![(5, 1)]), 1, Some(0)).is_err());
|
||||
|
||||
Grandpa::on_finalize(1);
|
||||
let mut header = System::finalize();
|
||||
|
||||
for i in 2..7 {
|
||||
System::initialize(&i, &header.hash(), &Default::default(), &Default::default());
|
||||
assert!(<PendingChange<Test>>::get().unwrap().forced.is_some());
|
||||
assert_eq!(Grandpa::next_forced(), Some(11));
|
||||
assert!(Grandpa::schedule_change(to_authorities(vec![(5, 1)]), 1, None).is_err());
|
||||
assert!(Grandpa::schedule_change(to_authorities(vec![(5, 1)]), 1, Some(0)).is_err());
|
||||
|
||||
Grandpa::on_finalize(i);
|
||||
header = System::finalize();
|
||||
}
|
||||
|
||||
// change has been applied at the end of block 6.
|
||||
// add a normal change.
|
||||
{
|
||||
System::initialize(&7, &header.hash(), &Default::default(), &Default::default());
|
||||
assert!(!<PendingChange<Test>>::exists());
|
||||
assert_eq!(Grandpa::grandpa_authorities(), to_authorities(vec![(4, 1), (5, 1), (6, 1)]));
|
||||
assert!(Grandpa::schedule_change(to_authorities(vec![(5, 1)]), 1, None).is_ok());
|
||||
Grandpa::on_finalize(7);
|
||||
header = System::finalize();
|
||||
}
|
||||
|
||||
// run the normal change.
|
||||
{
|
||||
System::initialize(&8, &header.hash(), &Default::default(), &Default::default());
|
||||
assert!(<PendingChange<Test>>::exists());
|
||||
assert_eq!(Grandpa::grandpa_authorities(), to_authorities(vec![(4, 1), (5, 1), (6, 1)]));
|
||||
assert!(Grandpa::schedule_change(to_authorities(vec![(5, 1)]), 1, None).is_err());
|
||||
Grandpa::on_finalize(8);
|
||||
header = System::finalize();
|
||||
}
|
||||
|
||||
// normal change applied. but we can't apply a new forced change for some
|
||||
// time.
|
||||
for i in 9..11 {
|
||||
System::initialize(&i, &header.hash(), &Default::default(), &Default::default());
|
||||
assert!(!<PendingChange<Test>>::exists());
|
||||
assert_eq!(Grandpa::grandpa_authorities(), to_authorities(vec![(5, 1)]));
|
||||
assert_eq!(Grandpa::next_forced(), Some(11));
|
||||
assert!(Grandpa::schedule_change(to_authorities(vec![(5, 1), (6, 1)]), 5, Some(0)).is_err());
|
||||
Grandpa::on_finalize(i);
|
||||
header = System::finalize();
|
||||
}
|
||||
|
||||
{
|
||||
System::initialize(&11, &header.hash(), &Default::default(), &Default::default());
|
||||
assert!(!<PendingChange<Test>>::exists());
|
||||
assert!(Grandpa::schedule_change(to_authorities(vec![(5, 1), (6, 1), (7, 1)]), 5, Some(0)).is_ok());
|
||||
assert_eq!(Grandpa::next_forced(), Some(21));
|
||||
Grandpa::on_finalize(11);
|
||||
header = System::finalize();
|
||||
}
|
||||
let _ = header;
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn schedule_pause_only_when_live() {
|
||||
new_test_ext(vec![(1, 1), (2, 1), (3, 1)]).execute_with(|| {
|
||||
// we schedule a pause at block 1 with delay of 1
|
||||
System::initialize(&1, &Default::default(), &Default::default(), &Default::default());
|
||||
Grandpa::schedule_pause(1).unwrap();
|
||||
|
||||
// we've switched to the pending pause state
|
||||
assert_eq!(
|
||||
Grandpa::state(),
|
||||
StoredState::PendingPause {
|
||||
scheduled_at: 1u64,
|
||||
delay: 1,
|
||||
},
|
||||
);
|
||||
|
||||
Grandpa::on_finalize(1);
|
||||
let _ = System::finalize();
|
||||
|
||||
System::initialize(&2, &Default::default(), &Default::default(), &Default::default());
|
||||
|
||||
// signaling a pause now should fail
|
||||
assert!(Grandpa::schedule_pause(1).is_err());
|
||||
|
||||
Grandpa::on_finalize(2);
|
||||
let _ = System::finalize();
|
||||
|
||||
// after finalizing block 2 the set should have switched to paused state
|
||||
assert_eq!(
|
||||
Grandpa::state(),
|
||||
StoredState::Paused,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn schedule_resume_only_when_paused() {
|
||||
new_test_ext(vec![(1, 1), (2, 1), (3, 1)]).execute_with(|| {
|
||||
System::initialize(&1, &Default::default(), &Default::default(), &Default::default());
|
||||
|
||||
// the set is currently live, resuming it is an error
|
||||
assert!(Grandpa::schedule_resume(1).is_err());
|
||||
|
||||
assert_eq!(
|
||||
Grandpa::state(),
|
||||
StoredState::Live,
|
||||
);
|
||||
|
||||
// we schedule a pause to be applied instantly
|
||||
Grandpa::schedule_pause(0).unwrap();
|
||||
Grandpa::on_finalize(1);
|
||||
let _ = System::finalize();
|
||||
|
||||
assert_eq!(
|
||||
Grandpa::state(),
|
||||
StoredState::Paused,
|
||||
);
|
||||
|
||||
// we schedule the set to go back live in 2 blocks
|
||||
System::initialize(&2, &Default::default(), &Default::default(), &Default::default());
|
||||
Grandpa::schedule_resume(2).unwrap();
|
||||
Grandpa::on_finalize(2);
|
||||
let _ = System::finalize();
|
||||
|
||||
System::initialize(&3, &Default::default(), &Default::default(), &Default::default());
|
||||
Grandpa::on_finalize(3);
|
||||
let _ = System::finalize();
|
||||
|
||||
System::initialize(&4, &Default::default(), &Default::default(), &Default::default());
|
||||
Grandpa::on_finalize(4);
|
||||
let _ = System::finalize();
|
||||
|
||||
// it should be live at block 4
|
||||
assert_eq!(
|
||||
Grandpa::state(),
|
||||
StoredState::Live,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn time_slot_have_sane_ord() {
|
||||
// Ensure that `Ord` implementation is sane.
|
||||
const FIXTURE: &[GrandpaTimeSlot] = &[
|
||||
GrandpaTimeSlot {
|
||||
set_id: 0,
|
||||
round: 0,
|
||||
},
|
||||
GrandpaTimeSlot {
|
||||
set_id: 0,
|
||||
round: 1,
|
||||
},
|
||||
GrandpaTimeSlot {
|
||||
set_id: 1,
|
||||
round: 0,
|
||||
},
|
||||
GrandpaTimeSlot {
|
||||
set_id: 1,
|
||||
round: 1,
|
||||
},
|
||||
GrandpaTimeSlot {
|
||||
set_id: 1,
|
||||
round: 2,
|
||||
}
|
||||
];
|
||||
assert!(FIXTURE.windows(2).all(|f| f[0] < f[1]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "migrate-authorities")]
|
||||
fn authorities_migration() {
|
||||
use sr_primitives::traits::OnInitialize;
|
||||
|
||||
with_externalities(&mut new_test_ext(vec![]), || {
|
||||
let authorities = to_authorities(vec![(1, 1), (2, 1), (3, 1)]);
|
||||
|
||||
Authorities::put(authorities.clone());
|
||||
assert!(Grandpa::grandpa_authorities().is_empty());
|
||||
|
||||
Grandpa::on_initialize(1);
|
||||
|
||||
assert!(!Authorities::exists());
|
||||
assert_eq!(Grandpa::grandpa_authorities(), authorities);
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user