mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-05-30 08:11:03 +00:00
a79c34778c
* babe: fix report_equivocation weight * node: bump spec_version * babe: fix floor in report_equivocation weight calculation Co-authored-by: Gavin Wood <gavin@parity.io> * grandpa: fix floor in report_equivocation weight calculation * babe, grandpa: add test for weight_for::report_equivocation Co-authored-by: Gavin Wood <gavin@parity.io>
674 lines
22 KiB
Rust
674 lines
22 KiB
Rust
// This file is part of Substrate.
|
|
|
|
// Copyright (C) 2017-2020 Parity Technologies (UK) Ltd.
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
//! 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 sp_finality_grandpa as fg_primitives;
|
|
|
|
use sp_std::prelude::*;
|
|
|
|
use codec::{self as codec, Decode, Encode};
|
|
pub use fg_primitives::{AuthorityId, AuthorityList, AuthorityWeight, VersionedAuthorityList};
|
|
use fg_primitives::{
|
|
ConsensusLog, EquivocationProof, ScheduledChange, SetId, GRANDPA_AUTHORITIES_KEY,
|
|
GRANDPA_ENGINE_ID,
|
|
};
|
|
use frame_support::{
|
|
decl_error, decl_event, decl_module, decl_storage, storage, traits::KeyOwnerProofSystem,
|
|
Parameter,
|
|
};
|
|
use frame_system::{ensure_none, ensure_root, ensure_signed};
|
|
use pallet_finality_tracker::OnFinalizationStalled;
|
|
use sp_runtime::{
|
|
generic::DigestItem,
|
|
traits::Zero,
|
|
DispatchResult, KeyTypeId,
|
|
};
|
|
use sp_session::{GetSessionNumber, GetValidatorCount};
|
|
use sp_staking::SessionIndex;
|
|
|
|
mod equivocation;
|
|
|
|
#[cfg(any(feature = "runtime-benchmarks", test))]
|
|
mod benchmarking;
|
|
#[cfg(all(feature = "std", test))]
|
|
mod mock;
|
|
#[cfg(all(feature = "std", test))]
|
|
mod tests;
|
|
|
|
pub use equivocation::{
|
|
EquivocationHandler, GrandpaEquivocationOffence, GrandpaOffence, GrandpaTimeSlot,
|
|
HandleEquivocation,
|
|
};
|
|
|
|
pub trait Trait: frame_system::Trait {
|
|
/// The event type of this module.
|
|
type Event: From<Event> + Into<<Self as frame_system::Trait>::Event>;
|
|
|
|
/// The function call.
|
|
type Call: From<Call<Self>>;
|
|
|
|
/// The proof of key ownership, used for validating equivocation reports.
|
|
/// The proof must include the session index and validator count of the
|
|
/// session at which the equivocation occurred.
|
|
type KeyOwnerProof: Parameter + GetSessionNumber + GetValidatorCount;
|
|
|
|
/// The identification of a key owner, used when reporting equivocations.
|
|
type KeyOwnerIdentification: Parameter;
|
|
|
|
/// A system for proving ownership of keys, i.e. that a given key was part
|
|
/// of a validator set, needed for validating equivocation reports.
|
|
type KeyOwnerProofSystem: KeyOwnerProofSystem<
|
|
(KeyTypeId, AuthorityId),
|
|
Proof = Self::KeyOwnerProof,
|
|
IdentificationTuple = Self::KeyOwnerIdentification,
|
|
>;
|
|
|
|
/// The equivocation handling subsystem, defines methods to report an
|
|
/// offence (after the equivocation has been validated) and for submitting a
|
|
/// transaction to report an equivocation (from an offchain context).
|
|
/// NOTE: when enabling equivocation handling (i.e. this type isn't set to
|
|
/// `()`) you must use this pallet's `ValidateUnsigned` in the runtime
|
|
/// definition.
|
|
type HandleEquivocation: HandleEquivocation<Self>;
|
|
}
|
|
|
|
/// 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, codec::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. [authority_set]
|
|
NewAuthorities(AuthorityList),
|
|
/// Current authority set has been paused.
|
|
Paused,
|
|
/// Current authority set has been resumed.
|
|
Resumed,
|
|
}
|
|
}
|
|
|
|
decl_error! {
|
|
pub enum Error for Module<T: Trait> {
|
|
/// Attempt to signal GRANDPA pause when the authority set isn't live
|
|
/// (either paused or already pending pause).
|
|
PauseFailed,
|
|
/// Attempt to signal GRANDPA resume when the authority set isn't paused
|
|
/// (either live or already pending resume).
|
|
ResumeFailed,
|
|
/// Attempt to signal GRANDPA change with one already pending.
|
|
ChangePending,
|
|
/// Cannot signal forced change so soon after last.
|
|
TooSoon,
|
|
/// A key ownership proof provided as part of an equivocation report is invalid.
|
|
InvalidKeyOwnershipProof,
|
|
/// An equivocation proof provided as part of an equivocation report is invalid.
|
|
InvalidEquivocationProof,
|
|
/// A given equivocation report is valid but already previously reported.
|
|
DuplicateOffenceReport,
|
|
}
|
|
}
|
|
|
|
decl_storage! {
|
|
trait Store for Module<T: Trait> as GrandpaFinality {
|
|
/// State of the current authority set.
|
|
State get(fn state): StoredState<T::BlockNumber> = StoredState::Live;
|
|
|
|
/// Pending change: (signaled at, scheduled change).
|
|
PendingChange get(fn pending_change): 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.
|
|
///
|
|
/// TWOX-NOTE: `SetId` is not under user control.
|
|
SetIdSession get(fn session_for_set): map hasher(twox_64_concat) SetId => Option<SessionIndex>;
|
|
}
|
|
add_extra_genesis {
|
|
config(authorities): AuthorityList;
|
|
build(|config| {
|
|
Module::<T>::initialize(&config.authorities)
|
|
})
|
|
}
|
|
}
|
|
|
|
decl_module! {
|
|
pub struct Module<T: Trait> for enum Call where origin: T::Origin {
|
|
type Error = Error<T>;
|
|
|
|
fn deposit_event() = default;
|
|
|
|
/// Report voter equivocation/misbehavior. This method will verify the
|
|
/// equivocation proof and validate the given key ownership proof
|
|
/// against the extracted offender. If both are valid, the offence
|
|
/// will be reported.
|
|
#[weight = weight_for::report_equivocation::<T>(key_owner_proof.validator_count())]
|
|
fn report_equivocation(
|
|
origin,
|
|
equivocation_proof: EquivocationProof<T::Hash, T::BlockNumber>,
|
|
key_owner_proof: T::KeyOwnerProof,
|
|
) {
|
|
let reporter = ensure_signed(origin)?;
|
|
|
|
Self::do_report_equivocation(
|
|
Some(reporter),
|
|
equivocation_proof,
|
|
key_owner_proof,
|
|
)?;
|
|
}
|
|
|
|
/// Report voter equivocation/misbehavior. This method will verify the
|
|
/// equivocation proof and validate the given key ownership proof
|
|
/// against the extracted offender. If both are valid, the offence
|
|
/// will be reported.
|
|
///
|
|
/// This extrinsic must be called unsigned and it is expected that only
|
|
/// block authors will call it (validated in `ValidateUnsigned`), as such
|
|
/// if the block author is defined it will be defined as the equivocation
|
|
/// reporter.
|
|
#[weight = weight_for::report_equivocation::<T>(key_owner_proof.validator_count())]
|
|
fn report_equivocation_unsigned(
|
|
origin,
|
|
equivocation_proof: EquivocationProof<T::Hash, T::BlockNumber>,
|
|
key_owner_proof: T::KeyOwnerProof,
|
|
) {
|
|
ensure_none(origin)?;
|
|
|
|
Self::do_report_equivocation(
|
|
T::HandleEquivocation::block_author(),
|
|
equivocation_proof,
|
|
key_owner_proof,
|
|
)?;
|
|
}
|
|
|
|
/// Note that the current authority set of the GRANDPA finality gadget has
|
|
/// stalled. This will trigger a forced authority set change at the beginning
|
|
/// of the next session, to be enacted `delay` blocks after that. The delay
|
|
/// should be high enough to safely assume that the block signalling the
|
|
/// forced change will not be re-orged (e.g. 1000 blocks). The GRANDPA voters
|
|
/// will start the new authority set using the given finalized block as base.
|
|
/// Only callable by root.
|
|
#[weight = weight_for::note_stalled::<T>()]
|
|
fn note_stalled(
|
|
origin,
|
|
delay: T::BlockNumber,
|
|
best_finalized_block_number: T::BlockNumber,
|
|
) {
|
|
ensure_root(origin)?;
|
|
|
|
Self::on_stalled(delay, best_finalized_block_number)
|
|
}
|
|
|
|
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);
|
|
}
|
|
},
|
|
_ => {},
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
mod weight_for {
|
|
use frame_support::{
|
|
traits::Get,
|
|
weights::{
|
|
constants::{WEIGHT_PER_MICROS, WEIGHT_PER_NANOS},
|
|
Weight,
|
|
},
|
|
};
|
|
|
|
pub fn report_equivocation<T: super::Trait>(validator_count: u32) -> Weight {
|
|
// we take the validator set count from the membership proof to
|
|
// calculate the weight but we set a floor of 100 validators.
|
|
let validator_count = validator_count.max(100) as u64;
|
|
|
|
// worst case we are considering is that the given offender
|
|
// is backed by 200 nominators
|
|
const MAX_NOMINATORS: u64 = 200;
|
|
|
|
// checking membership proof
|
|
(35 * WEIGHT_PER_MICROS)
|
|
.saturating_add((175 * WEIGHT_PER_NANOS).saturating_mul(validator_count))
|
|
.saturating_add(T::DbWeight::get().reads(5))
|
|
// check equivocation proof
|
|
.saturating_add(95 * WEIGHT_PER_MICROS)
|
|
// report offence
|
|
.saturating_add(110 * WEIGHT_PER_MICROS)
|
|
.saturating_add(25 * WEIGHT_PER_MICROS * MAX_NOMINATORS)
|
|
.saturating_add(T::DbWeight::get().reads(14 + 3 * MAX_NOMINATORS))
|
|
.saturating_add(T::DbWeight::get().writes(10 + 3 * MAX_NOMINATORS))
|
|
// fetching set id -> session index mappings
|
|
.saturating_add(T::DbWeight::get().reads(2))
|
|
}
|
|
|
|
pub fn note_stalled<T: super::Trait>() -> Weight {
|
|
(3 * WEIGHT_PER_MICROS)
|
|
.saturating_add(T::DbWeight::get().writes(1))
|
|
}
|
|
}
|
|
|
|
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) -> DispatchResult {
|
|
if let StoredState::Live = <State<T>>::get() {
|
|
let scheduled_at = <frame_system::Module<T>>::block_number();
|
|
<State<T>>::put(StoredState::PendingPause {
|
|
delay: in_blocks,
|
|
scheduled_at,
|
|
});
|
|
|
|
Ok(())
|
|
} else {
|
|
Err(Error::<T>::PauseFailed)?
|
|
}
|
|
}
|
|
|
|
/// Schedule a resume of GRANDPA after pausing.
|
|
pub fn schedule_resume(in_blocks: T::BlockNumber) -> DispatchResult {
|
|
if let StoredState::Paused = <State<T>>::get() {
|
|
let scheduled_at = <frame_system::Module<T>>::block_number();
|
|
<State<T>>::put(StoredState::PendingResume {
|
|
delay: in_blocks,
|
|
scheduled_at,
|
|
});
|
|
|
|
Ok(())
|
|
} else {
|
|
Err(Error::<T>::ResumeFailed)?
|
|
}
|
|
}
|
|
|
|
/// 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>,
|
|
) -> DispatchResult {
|
|
if !<PendingChange<T>>::exists() {
|
|
let scheduled_at = <frame_system::Module<T>>::block_number();
|
|
|
|
if let Some(_) = forced {
|
|
if Self::next_forced().map_or(false, |next| next > scheduled_at) {
|
|
Err(Error::<T>::TooSoon)?
|
|
}
|
|
|
|
// 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(Error::<T>::ChangePending)?
|
|
}
|
|
}
|
|
|
|
/// 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());
|
|
<frame_system::Module<T>>::deposit_log(log.into());
|
|
}
|
|
|
|
// Perform module initialization, abstracted so that it can be called either through genesis
|
|
// config builder or through `on_genesis_session`.
|
|
fn initialize(authorities: &AuthorityList) {
|
|
if !authorities.is_empty() {
|
|
assert!(
|
|
Self::grandpa_authorities().is_empty(),
|
|
"Authorities are already initialized!"
|
|
);
|
|
Self::set_grandpa_authorities(authorities);
|
|
}
|
|
|
|
// NOTE: initialize first session of first set. this is necessary for
|
|
// the genesis set and session since we only update the set -> session
|
|
// mapping whenever a new session starts, i.e. through `on_new_session`.
|
|
SetIdSession::insert(0, 0);
|
|
}
|
|
|
|
fn do_report_equivocation(
|
|
reporter: Option<T::AccountId>,
|
|
equivocation_proof: EquivocationProof<T::Hash, T::BlockNumber>,
|
|
key_owner_proof: T::KeyOwnerProof,
|
|
) -> Result<(), Error<T>> {
|
|
// we check the equivocation within the context of its set id (and
|
|
// associated session) and round. we also need to know the validator
|
|
// set count when the offence since it is required to calculate the
|
|
// slash amount.
|
|
let set_id = equivocation_proof.set_id();
|
|
let round = equivocation_proof.round();
|
|
let session_index = key_owner_proof.session();
|
|
let validator_count = key_owner_proof.validator_count();
|
|
|
|
// validate the key ownership proof extracting the id of the offender.
|
|
let offender =
|
|
T::KeyOwnerProofSystem::check_proof(
|
|
(fg_primitives::KEY_TYPE, equivocation_proof.offender().clone()),
|
|
key_owner_proof,
|
|
).ok_or(Error::<T>::InvalidKeyOwnershipProof)?;
|
|
|
|
// validate equivocation proof (check votes are different and
|
|
// signatures are valid).
|
|
if !sp_finality_grandpa::check_equivocation_proof(equivocation_proof) {
|
|
return Err(Error::<T>::InvalidEquivocationProof.into());
|
|
}
|
|
|
|
// fetch the current and previous sets last session index. on the
|
|
// genesis set there's no previous set.
|
|
let previous_set_id_session_index = if set_id == 0 {
|
|
None
|
|
} else {
|
|
let session_index =
|
|
if let Some(session_id) = Self::session_for_set(set_id - 1) {
|
|
session_id
|
|
} else {
|
|
return Err(Error::<T>::InvalidEquivocationProof.into());
|
|
};
|
|
|
|
Some(session_index)
|
|
};
|
|
|
|
let set_id_session_index =
|
|
if let Some(session_id) = Self::session_for_set(set_id) {
|
|
session_id
|
|
} else {
|
|
return Err(Error::<T>::InvalidEquivocationProof.into());
|
|
};
|
|
|
|
// check that the session id for the membership proof is within the
|
|
// bounds of the set id reported in the equivocation.
|
|
if session_index > set_id_session_index ||
|
|
previous_set_id_session_index
|
|
.map(|previous_index| session_index <= previous_index)
|
|
.unwrap_or(false)
|
|
{
|
|
return Err(Error::<T>::InvalidEquivocationProof.into());
|
|
}
|
|
|
|
// report to the offences module rewarding the sender.
|
|
T::HandleEquivocation::report_offence(
|
|
reporter.into_iter().collect(),
|
|
<T::HandleEquivocation as HandleEquivocation<T>>::Offence::new(
|
|
session_index,
|
|
validator_count,
|
|
offender,
|
|
set_id,
|
|
round,
|
|
),
|
|
).map_err(|_| Error::<T>::DuplicateOffenceReport)
|
|
}
|
|
|
|
/// Submits an extrinsic to report an equivocation. This method will create
|
|
/// an unsigned extrinsic with a call to `report_equivocation_unsigned` and
|
|
/// will push the transaction to the pool. Only useful in an offchain
|
|
/// context.
|
|
pub fn submit_unsigned_equivocation_report(
|
|
equivocation_proof: EquivocationProof<T::Hash, T::BlockNumber>,
|
|
key_owner_proof: T::KeyOwnerProof,
|
|
) -> Option<()> {
|
|
T::HandleEquivocation::submit_unsigned_equivocation_report(
|
|
equivocation_proof,
|
|
key_owner_proof,
|
|
)
|
|
.ok()
|
|
}
|
|
}
|
|
|
|
impl<T: Trait> sp_runtime::BoundToRuntimeAppPublic for Module<T> {
|
|
type Public = AuthorityId;
|
|
}
|
|
|
|
impl<T: Trait> pallet_session::OneSessionHandler<T::AccountId> for Module<T>
|
|
where T: pallet_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);
|
|
}
|
|
|
|
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 underlying economic
|
|
// identities have changed.
|
|
let current_set_id = if changed || <Stalled<T>>::exists() {
|
|
let next_authorities = validators.map(|(_, k)| (k, 1)).collect::<Vec<_>>();
|
|
|
|
let res = if let Some((further_wait, median)) = <Stalled<T>>::take() {
|
|
Self::schedule_change(next_authorities, further_wait, Some(median))
|
|
} else {
|
|
Self::schedule_change(next_authorities, Zero::zero(), None)
|
|
};
|
|
|
|
if res.is_ok() {
|
|
CurrentSetId::mutate(|s| {
|
|
*s += 1;
|
|
*s
|
|
})
|
|
} else {
|
|
// either the session module signalled that the validators have changed
|
|
// or the set was stalled. but since we didn't successfully schedule
|
|
// an authority set change we do not increment the set id.
|
|
Self::current_set_id()
|
|
}
|
|
} 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 = <pallet_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> 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 `pallet_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));
|
|
}
|
|
}
|