Skip to content
This repository has been archived by the owner on Nov 15, 2023. It is now read-only.

Add srml/im-online integrations #3223

Closed
wants to merge 11 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

18 changes: 18 additions & 0 deletions core/sr-primitives/src/traits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -602,6 +602,24 @@ pub trait IsMember<MemberId> {
fn is_member(member_id: &MemberId) -> bool;
}

/// Mean for getting the current session keys.
pub trait CurrentSessionKeys<AccountId> {
/// Get the keys of the current session.
fn current_keys<Key: Decode + Default + TypedKey>() -> Vec<(AccountId, Key)>;
}

impl<T> CurrentSessionKeys<T> for () {
fn current_keys<Key>() -> Vec<(T, Key)> {
Vec::new()
}
}

/// Disable a validator referenced by an `AccountId`.
pub trait DisableValidator<AccountId> {
/// Disable a validator referenced by an `AccountId`.
fn disable(account_id: &AccountId) -> Result<(), ()>;
}

/// Something which fulfills the abstract idea of a Substrate header. It has types for a `Number`,
/// a `Hash` and a `Digest`. It provides access to an `extrinsics_root`, `state_root` and
/// `parent_hash`, as well as a `digest` and a block `number`.
Expand Down
1 change: 1 addition & 0 deletions core/test-runtime/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -368,6 +368,7 @@ parameter_types! {

impl srml_babe::Trait for Runtime {
type EpochDuration = EpochDuration;
type CurrentSessionKeys = ();
type ExpectedBlockTime = ExpectedBlockTime;
}

Expand Down
12 changes: 2 additions & 10 deletions node/cli/src/chain_spec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ use primitives::{ed25519, sr25519, Pair, crypto::UncheckedInto};
use node_primitives::{AccountId, Balance};
use node_runtime::{
BabeConfig, BalancesConfig, ContractsConfig, CouncilConfig, DemocracyConfig,
ElectionsConfig, GrandpaConfig, ImOnlineConfig, IndicesConfig, Perbill,
SessionConfig, SessionKeys, StakerStatus, StakingConfig, SudoConfig, SystemConfig,
ElectionsConfig, GrandpaConfig, IndicesConfig, Perbill, SessionConfig,
SessionKeys, StakerStatus, StakingConfig, SudoConfig, SystemConfig,
TechnicalCommitteeConfig, WASM_BINARY,
};
use node_runtime::constants::{time::*, currency::*};
Expand Down Expand Up @@ -161,10 +161,6 @@ fn staging_testnet_config_genesis() -> GenesisConfig {
babe: Some(BabeConfig {
authorities: initial_authorities.iter().map(|x| (x.2.clone(), 1)).collect(),
}),
im_online: Some(ImOnlineConfig {
gossip_at: 0,
last_new_era_start: 0,
}),
grandpa: Some(GrandpaConfig {
authorities: initial_authorities.iter().map(|x| (x.3.clone(), 1)).collect(),
}),
Expand Down Expand Up @@ -304,10 +300,6 @@ pub fn testnet_genesis(
babe: Some(BabeConfig {
authorities: initial_authorities.iter().map(|x| (x.2.clone(), 1)).collect(),
}),
im_online: Some(ImOnlineConfig{
gossip_at: 0,
last_new_era_start: 0,
}),
grandpa: Some(GrandpaConfig {
authorities: initial_authorities.iter().map(|x| (x.3.clone(), 1)).collect(),
}),
Expand Down
1 change: 0 additions & 1 deletion node/executor/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -388,7 +388,6 @@ mod tests {
gas_price: 1 * MILLICENTS,
}),
sudo: Some(Default::default()),
im_online: Some(Default::default()),
grandpa: Some(GrandpaConfig {
authorities: vec![],
}),
Expand Down
12 changes: 8 additions & 4 deletions node/runtime/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,8 +79,8 @@ pub const VERSION: RuntimeVersion = RuntimeVersion {
// and set impl_version to equal spec_version. If only runtime
// implementation changes and behavior does not, then leave spec_version as
// is and increment impl_version.
spec_version: 129,
impl_version: 129,
spec_version: 130,
impl_version: 130,
apis: RUNTIME_API_VERSIONS,
};

Expand Down Expand Up @@ -134,6 +134,7 @@ parameter_types! {
impl babe::Trait for Runtime {
type EpochDuration = EpochDuration;
type ExpectedBlockTime = ExpectedBlockTime;
type CurrentSessionKeys = session::CurrentSessionKeys<Self>;
}

impl indices::Trait for Runtime {
Expand Down Expand Up @@ -184,7 +185,7 @@ impl authorship::Trait for Runtime {
type FindAuthor = session::FindAccountFromAuthorIndex<Self, Babe>;
type UncleGenerations = UncleGenerations;
type FilterUncle = ();
type EventHandler = Staking;
type EventHandler = (ImOnline, Staking);
}

type SessionHandlers = (Grandpa, Babe, ImOnline);
Expand Down Expand Up @@ -212,6 +213,7 @@ impl session::Trait for Runtime {
type Keys = SessionKeys;
type ValidatorId = AccountId;
type ValidatorIdOf = staking::StashOf<Self>;
type AccountIdOf = staking::ControllerOf<Self>;
type SelectInitialValidators = Staking;
}

Expand Down Expand Up @@ -375,6 +377,8 @@ impl im_online::Trait for Runtime {
type SessionsPerEra = SessionsPerEra;
type UncheckedExtrinsic = UncheckedExtrinsic;
type IsValidAuthorityId = Babe;
type AuthorityIdOf = babe::AuthorityIdOf<Self, BabeId>;
type DisableValidator = staking::DisableValidatorInterface<Self>;
}

impl grandpa::Trait for Runtime {
Expand Down Expand Up @@ -415,7 +419,7 @@ construct_runtime!(
Treasury: treasury::{Module, Call, Storage, Event<T>},
Contracts: contracts,
Sudo: sudo,
ImOnline: im_online::{default, ValidateUnsigned},
ImOnline: im_online::{Module, Call, Event<T>, ValidateUnsigned},
}
);

Expand Down
30 changes: 29 additions & 1 deletion srml/babe/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,10 @@ use rstd::{result, prelude::*};
use srml_support::{decl_storage, decl_module, StorageValue, StorageMap, traits::FindAuthor, traits::Get};
use timestamp::{OnTimestampSet};
use sr_primitives::{generic::DigestItem, ConsensusEngineId};
use sr_primitives::traits::{IsMember, SaturatedConversion, Saturating, RandomnessBeacon, Convert};
use sr_primitives::traits::{
CurrentSessionKeys, IsMember, SaturatedConversion, Saturating, RandomnessBeacon, Convert,
TypedKey,
};
#[cfg(feature = "std")]
use timestamp::TimestampInherentData;
use parity_codec::{Encode, Decode};
Expand Down Expand Up @@ -109,7 +112,11 @@ impl ProvideInherentData for InherentDataProvider {

pub trait Trait: timestamp::Trait {
type EpochDuration: Get<u64>;

type ExpectedBlockTime: Get<Self::Moment>;

/// Retrieve the current session keys.
type CurrentSessionKeys: CurrentSessionKeys<Self::AccountId>;
}

/// The length of the BABE randomness
Expand Down Expand Up @@ -234,6 +241,27 @@ impl<T: Trait> IsMember<AuthorityId> for Module<T> {
}
}

/// A `Convert` implementation that finds the babe authority id of the given controller
/// account, if any.
pub struct AuthorityIdOf<T, Id>(rstd::marker::PhantomData<T>, rstd::marker::PhantomData<Id>);

impl<T, AuthorityId> Convert<T::AccountId, Option<AuthorityId>> for AuthorityIdOf<T, AuthorityId>
where
T: Trait,
AuthorityId: Decode + Default + TypedKey,
{
fn convert(account_id: T::AccountId) -> Option<AuthorityId> {
let keys = T::CurrentSessionKeys::current_keys::<AuthorityId>();
let maybe_authority_id = keys
.into_iter()
.find(|(id, _)| {
*id == account_id
})
.map(|(_, a)| a);
maybe_authority_id
}
}

impl<T: Trait> session::ShouldEndSession<T::BlockNumber> for Module<T> {
fn should_end_session(_: T::BlockNumber) -> bool {
let diff = CurrentSlot::get().saturating_sub(EpochStartSlot::get());
Expand Down
1 change: 1 addition & 0 deletions srml/im-online/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ authors = ["Parity Technologies <admin@parity.io>"]
edition = "2018"

[dependencies]
authorship = { package = "srml-authorship", path = "../authorship", default-features = false }
parity-codec = { version = "4.1.1", default-features = false, features = ["derive"] }
sr-primitives = { path = "../../core/sr-primitives", default-features = false }
primitives = { package = "substrate-primitives", path = "../../core/primitives", default-features = false }
Expand Down
112 changes: 100 additions & 12 deletions srml/im-online/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,9 @@
//!
//! ## Dependencies
//!
//! This module depends on the [Session module](../srml_session/index.html).
//! This module depends on the [Session module](../srml_session/index.html) for
//! generic session functionality and on the [Authorship module](../srml_authorship/index.html)
//! to mark validators automatically as online once they author a block.

// Ensure we're `no_std` when compiling for Wasm.
#![cfg_attr(not(feature = "std"), no_std)]
Expand All @@ -76,7 +78,9 @@ use primitives::{
};
use parity_codec::{Encode, Decode};
use sr_primitives::{
ApplyError, traits::{Member, IsMember, Extrinsic as ExtrinsicT},
ApplyError, traits::{
Convert, DisableValidator, Member, IsMember, Extrinsic as ExtrinsicT, Zero,
},
transaction_validity::{TransactionValidity, TransactionLongevity, ValidTransaction},
};
use rstd::prelude::*;
Expand Down Expand Up @@ -158,6 +162,12 @@ pub trait Trait: system::Trait + session::Trait {

/// Determine if an `AuthorityId` is a valid authority.
type IsValidAuthorityId: IsMember<Self::AuthorityId>;

/// A conversion of `AccountId` to `AuthorityId`.
type AuthorityIdOf: Convert<Self::AccountId, Option<Self::AuthorityId>>;

/// Disable a given validator.
type DisableValidator: DisableValidator<Self::AccountId>;
}

decl_event!(
Expand All @@ -173,15 +183,24 @@ decl_event!(
decl_storage! {
trait Store for Module<T: Trait> as ImOnline {
// The block number when we should gossip.
GossipAt get(gossip_at) config(): T::BlockNumber;
GossipAt get(gossip_at) build(|_| T::BlockNumber::zero()): T::BlockNumber;

// The session index when the last new era started.
LastNewEraStart get(last_new_era_start) config(): Option<session::SessionIndex>;
LastNewEraStart get(last_new_era_start): Option<session::SessionIndex>;

// 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 track if an `AuthorityId` was noted as
// a block author by the authorship module.
BlockAuthors get(block_authors): double_map session::SessionIndex,
blake2_256(T::AuthorityId) => bool;

// The validators in the current session.
CurrentSessionValidators get(current_session_validators):
Vec<(T::AccountId, T::AuthorityId)>;
}
}

Expand Down Expand Up @@ -336,25 +355,61 @@ impl<T: Trait> Module<T> {
Some(start) => {
// iterate over every session
for index in start..curr {
if <ReceivedHeartbeats<T>>::exists(&index, authority_id) {
let got_heartbeat = <ReceivedHeartbeats<T>>::exists(&index, authority_id);
let was_author = <BlockAuthors<T>>::exists(&index, authority_id);
if got_heartbeat || was_author {
return true;
}
}
false
},
None => <ReceivedHeartbeats<T>>::exists(&curr, authority_id),
None => {
let got_heartbeat = <ReceivedHeartbeats<T>>::exists(&curr, authority_id);
let was_author = <BlockAuthors<T>>::exists(&curr, authority_id);
got_heartbeat || was_author
},
}
}

/// 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)
let curr = <session::Module<T>>::current_index();
let got_heartbeat = <ReceivedHeartbeats<T>>::exists(&curr, authority_id);
let was_author = <BlockAuthors<T>>::exists(&curr, authority_id);
got_heartbeat || was_author
}

/// Returns `true` if a heartbeat has been received for `AuthorityId`
/// during the previous session. Otherwise `false`.
pub fn was_online_in_previous_session(authority_id: &T::AuthorityId) -> bool {
let curr = <session::Module<T>>::current_index();
if curr == 0 {
return false;
}

let index = curr - 1;
let got_heartbeat = <ReceivedHeartbeats<T>>::exists(&index, authority_id);
let was_author = <BlockAuthors<T>>::exists(&index, authority_id);
got_heartbeat || was_author
}

/// Disables all validators which haven't been online in the previous session.
fn disable_offline_validators() {
<Module<T>>::current_session_validators()
.iter()
.for_each(|(validator_id, authority_id)| {
if !Self::was_online_in_previous_session(authority_id) {
let _ = T::DisableValidator::disable(validator_id);
}
});
}

/// Session has just changed.
fn new_session() {
fn new_session(validators: Vec<(T::AccountId, T::AuthorityId)>) {
Self::disable_offline_validators();
<CurrentSessionValidators<T>>::put(validators);

let now = <system::Module<T>>::block_number();
<GossipAt<T>>::put(now);

Expand All @@ -381,25 +436,58 @@ impl<T: Trait> Module<T> {
Some(start) => {
for index in start..curr {
<ReceivedHeartbeats<T>>::remove_prefix(&index);
<BlockAuthors<T>>::remove_prefix(&index);
}
},
None => <ReceivedHeartbeats<T>>::remove_prefix(&curr),
None => {
<ReceivedHeartbeats<T>>::remove_prefix(&curr);
<BlockAuthors<T>>::remove_prefix(&curr);
},
}
}
}

impl<T: Trait> session::OneSessionHandler<T::AccountId> for Module<T> {
type Key = <T as Trait>::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, T::AuthorityId)>
{
let validators: Vec<(T::AccountId, T::AuthorityId)> = validators
.map(|(acc_id, auth_id)| (acc_id.clone(), auth_id))
.collect();

Self::new_session(validators);
}

fn on_disabled(_i: usize) {
// ignore
}
}

/// Mark nodes which authored automatically as online.
impl<T: Trait + authorship::Trait> authorship::EventHandler<T::AccountId, T::BlockNumber> for Module<T> {
fn note_author(account_id: T::AccountId) {
let maybe_authority_id = T::AuthorityIdOf::convert(account_id);
if let Some(authority_id) = maybe_authority_id {
let current_session = <session::Module<T>>::current_index();

let exists = <BlockAuthors<T>>::exists(&current_session, &authority_id);
if !exists {
<BlockAuthors<T>>::insert(&current_session, &authority_id, &true);
}
}
}

fn note_uncle(_author: T::AccountId, _age: T::BlockNumber) {
// ignore
}
}

impl<T: Trait> srml_support::unsigned::ValidateUnsigned for Module<T> {
type Call = Call<T>;

Expand Down
Loading