Unverified Commit b3611713 authored by Fedor Sakharov's avatar Fedor Sakharov Committed by GitHub
Browse files

Parachains double vote handler initial implementation. (#840)



* Parachains double vote handler initial implementation.

* Make tests test the actual slashing.

* Implement SignedExtension validation of double vote reports.

* Fixes build after merge

* Review fixes

* Adds historical session proofs

* Review fixes.

* Bump runtime spec_version

* Get the session number from the proof

* Check that proof matches session

* Change signature type on DoubleVoteReport

* Adds docs and removes blank lines

* Removes leftover code

* Fix build

* Fix build after a merge

* Apply suggestions from code review

Co-Authored-By: asynchronous rob's avatarRobert Habermeier <rphmeier@gmail.com>

* Prune ParentToSessionIndex

* Remove a clone and a warning

Co-authored-by: asynchronous rob's avatarRobert Habermeier <rphmeier@gmail.com>
Co-authored-by: default avatarGavin Wood <gavin@parity.io>
parent 9c45e89d
Pipeline #84112 passed with stages
in 23 minutes and 14 seconds
...@@ -4043,6 +4043,7 @@ dependencies = [ ...@@ -4043,6 +4043,7 @@ dependencies = [
"pallet-authorship 2.0.0-alpha.4 (git+https://github.com/paritytech/substrate)", "pallet-authorship 2.0.0-alpha.4 (git+https://github.com/paritytech/substrate)",
"pallet-babe 2.0.0-alpha.4 (git+https://github.com/paritytech/substrate)", "pallet-babe 2.0.0-alpha.4 (git+https://github.com/paritytech/substrate)",
"pallet-balances 2.0.0-alpha.4 (git+https://github.com/paritytech/substrate)", "pallet-balances 2.0.0-alpha.4 (git+https://github.com/paritytech/substrate)",
"pallet-offences 2.0.0-alpha.4 (git+https://github.com/paritytech/substrate)",
"pallet-randomness-collective-flip 2.0.0-alpha.4 (git+https://github.com/paritytech/substrate)", "pallet-randomness-collective-flip 2.0.0-alpha.4 (git+https://github.com/paritytech/substrate)",
"pallet-session 2.0.0-alpha.4 (git+https://github.com/paritytech/substrate)", "pallet-session 2.0.0-alpha.4 (git+https://github.com/paritytech/substrate)",
"pallet-staking 2.0.0-alpha.4 (git+https://github.com/paritytech/substrate)", "pallet-staking 2.0.0-alpha.4 (git+https://github.com/paritytech/substrate)",
...@@ -4154,6 +4155,7 @@ dependencies = [ ...@@ -4154,6 +4155,7 @@ dependencies = [
"pallet-grandpa 2.0.0-alpha.4 (git+https://github.com/paritytech/substrate)", "pallet-grandpa 2.0.0-alpha.4 (git+https://github.com/paritytech/substrate)",
"pallet-indices 2.0.0-alpha.4 (git+https://github.com/paritytech/substrate)", "pallet-indices 2.0.0-alpha.4 (git+https://github.com/paritytech/substrate)",
"pallet-nicks 2.0.0-alpha.4 (git+https://github.com/paritytech/substrate)", "pallet-nicks 2.0.0-alpha.4 (git+https://github.com/paritytech/substrate)",
"pallet-offences 2.0.0-alpha.4 (git+https://github.com/paritytech/substrate)",
"pallet-randomness-collective-flip 2.0.0-alpha.4 (git+https://github.com/paritytech/substrate)", "pallet-randomness-collective-flip 2.0.0-alpha.4 (git+https://github.com/paritytech/substrate)",
"pallet-session 2.0.0-alpha.4 (git+https://github.com/paritytech/substrate)", "pallet-session 2.0.0-alpha.4 (git+https://github.com/paritytech/substrate)",
"pallet-staking 2.0.0-alpha.4 (git+https://github.com/paritytech/substrate)", "pallet-staking 2.0.0-alpha.4 (git+https://github.com/paritytech/substrate)",
......
...@@ -580,7 +580,7 @@ pub struct Activity(#[cfg_attr(feature = "std", serde(with="bytes"))] pub Vec<u8 ...@@ -580,7 +580,7 @@ pub struct Activity(#[cfg_attr(feature = "std", serde(with="bytes"))] pub Vec<u8
/// Statements that can be made about parachain candidates. These are the /// Statements that can be made about parachain candidates. These are the
/// actual values that are signed. /// actual values that are signed.
#[derive(Clone, PartialEq, Eq, Encode)] #[derive(Clone, PartialEq, Eq, Encode, Decode)]
#[cfg_attr(feature = "std", derive(Debug))] #[cfg_attr(feature = "std", derive(Debug))]
pub enum Statement { pub enum Statement {
/// Proposal of a parachain candidate. /// Proposal of a parachain candidate.
...@@ -596,8 +596,7 @@ pub enum Statement { ...@@ -596,8 +596,7 @@ pub enum Statement {
/// An either implicit or explicit attestation to the validity of a parachain /// An either implicit or explicit attestation to the validity of a parachain
/// candidate. /// candidate.
#[derive(Clone, PartialEq, Decode, Encode)] #[derive(Clone, Eq, PartialEq, Decode, Encode, RuntimeDebug)]
#[cfg_attr(feature = "std", derive(Debug))]
pub enum ValidityAttestation { pub enum ValidityAttestation {
/// Implicit validity attestation by issuing. /// Implicit validity attestation by issuing.
/// This corresponds to issuance of a `Candidate` statement. /// This corresponds to issuance of a `Candidate` statement.
......
...@@ -28,6 +28,7 @@ staking = { package = "pallet-staking", git = "https://github.com/paritytech/sub ...@@ -28,6 +28,7 @@ staking = { package = "pallet-staking", git = "https://github.com/paritytech/sub
system = { package = "frame-system", git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } system = { package = "frame-system", git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
timestamp = { package = "pallet-timestamp", git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } timestamp = { package = "pallet-timestamp", git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
vesting = { package = "pallet-vesting", git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } vesting = { package = "pallet-vesting", git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
offences = { package = "pallet-offences", git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
frame-benchmarking = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false, optional = true } frame-benchmarking = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false, optional = true }
primitives = { package = "polkadot-primitives", path = "../../primitives", default-features = false } primitives = { package = "polkadot-primitives", path = "../../primitives", default-features = false }
......
...@@ -20,28 +20,42 @@ use sp_std::prelude::*; ...@@ -20,28 +20,42 @@ use sp_std::prelude::*;
use sp_std::result; use sp_std::result;
use codec::{Decode, Encode}; use codec::{Decode, Encode};
use sp_runtime::traits::{ use sp_runtime::{
Hash as HashT, BlakeTwo256, Saturating, One, Dispatchable, KeyTypeId, Perbill, RuntimeDebug,
AccountIdConversion, BadOrigin, traits::{
Hash as HashT, BlakeTwo256, Saturating, One, Dispatchable,
AccountIdConversion, BadOrigin, Convert, SignedExtension, AppVerify,
},
transaction_validity::{TransactionValidityError, ValidTransaction, TransactionValidity},
};
use sp_staking::{
SessionIndex,
offence::{ReportOffence, Offence, Kind},
};
use frame_support::{
traits::KeyOwnerProofSystem,
dispatch::{IsSubType},
weights::{DispatchInfo, SimpleDispatchInfo},
}; };
use frame_support::weights::SimpleDispatchInfo;
use primitives::{ use primitives::{
Balance, Balance,
parachain::{ parachain::{
self, Id as ParaId, Chain, DutyRoster, AttestedCandidate, Statement, ParachainDispatchOrigin, self, Id as ParaId, Chain, DutyRoster, AttestedCandidate, Statement, ParachainDispatchOrigin,
UpwardMessage, ValidatorId, ActiveParas, CollatorId, Retriable, OmittedValidationData, UpwardMessage, ValidatorId, ActiveParas, CollatorId, Retriable, OmittedValidationData,
CandidateReceipt, GlobalValidationSchedule, AbridgedCandidateReceipt, CandidateReceipt, GlobalValidationSchedule, AbridgedCandidateReceipt,
LocalValidationData, NEW_HEADS_IDENTIFIER, LocalValidationData, ValidityAttestation, NEW_HEADS_IDENTIFIER, PARACHAIN_KEY_TYPE_ID,
ValidatorSignature,
}, },
}; };
use frame_support::{ use frame_support::{
Parameter, dispatch::DispatchResult, decl_storage, decl_module, decl_error, ensure, Parameter, dispatch::DispatchResult, decl_storage, decl_module, decl_error, ensure,
traits::{Currency, Get, WithdrawReason, ExistenceRequirement, Randomness}, traits::{Currency, Get, WithdrawReason, ExistenceRequirement, Randomness},
}; };
use sp_runtime::transaction_validity::InvalidTransaction;
use inherents::{ProvideInherent, InherentData, MakeFatalError, InherentIdentifier}; use inherents::{ProvideInherent, InherentData, MakeFatalError, InherentIdentifier};
use system::ensure_none; use system::{ensure_none, ensure_signed};
use crate::attestations::{self, IncludedBlocks}; use crate::attestations::{self, IncludedBlocks};
use crate::registrar::Registrar; use crate::registrar::Registrar;
...@@ -100,13 +114,96 @@ impl<AccountId, T: Currency<AccountId>> ParachainCurrency<AccountId> for T where ...@@ -100,13 +114,96 @@ impl<AccountId, T: Currency<AccountId>> ParachainCurrency<AccountId> for T where
/// Interface to the persistent (stash) identities of the current validators. /// Interface to the persistent (stash) identities of the current validators.
pub struct ValidatorIdentities<T>(sp_std::marker::PhantomData<T>); pub struct ValidatorIdentities<T>(sp_std::marker::PhantomData<T>);
/// A structure used to report conflicting votes by validators.
///
/// It is generic over two parameters:
/// `Proof` - proof of historical ownership of a key by some validator.
/// `Hash` - a type of a hash used in the runtime.
#[derive(RuntimeDebug, Encode, Decode)]
#[derive(Clone, Eq, PartialEq)]
pub struct DoubleVoteReport<Proof, Hash> {
/// Identity of the double-voter.
pub identity: ValidatorId,
/// First vote of the double-vote.
pub first: (Statement, ValidatorSignature),
/// Second vote of the double-vote.
pub second: (Statement, ValidatorSignature),
/// Proof that the validator with `identity` id was actually a validator at `parent_hash`.
pub proof: Proof,
/// Parent hash of the block this offence was commited.
pub parent_hash: Hash,
}
impl<Proof: Parameter + GetSessionNumber, Hash: AsRef<[u8]>> DoubleVoteReport<Proof, Hash> {
fn verify<T: Trait<Proof = Proof>>(
&self,
) -> Result<(), DoubleVoteValidityError> {
let first = self.first.clone();
let second = self.second.clone();
let id = self.identity.encode();
T::KeyOwnerProofSystem::check_proof((PARACHAIN_KEY_TYPE_ID, id), self.proof.clone())
.ok_or(DoubleVoteValidityError::InvalidProof)?;
// Check signatures.
Self::verify_vote(&first, &self.parent_hash, &self.identity)?;
Self::verify_vote(&second, &self.parent_hash, &self.identity)?;
match (&first.0, &second.0) {
// If issuing a `Candidate` message on a parachain block, neither a `Valid` or
// `Invalid` vote cannot be issued on that parachain block, as the `Candidate`
// message is an implicit validity vote.
(Statement::Candidate(candidate_hash), Statement::Valid(hash)) |
(Statement::Candidate(candidate_hash), Statement::Invalid(hash)) |
(Statement::Valid(hash), Statement::Candidate(candidate_hash)) |
(Statement::Invalid(hash), Statement::Candidate(candidate_hash))
if *candidate_hash == *hash => {},
// Otherwise, it is illegal to cast both a `Valid` and
// `Invalid` vote on a given parachain block.
(Statement::Valid(hash_1), Statement::Invalid(hash_2)) |
(Statement::Invalid(hash_1), Statement::Valid(hash_2))
if *hash_1 == *hash_2 => {},
_ => {
return Err(DoubleVoteValidityError::NotDoubleVote);
}
}
Ok(())
}
fn verify_vote(
vote: &(Statement, ValidatorSignature),
parent_hash: &Hash,
authority: &ValidatorId,
) -> Result<(), DoubleVoteValidityError> {
let payload = localized_payload(vote.0.clone(), parent_hash);
if !vote.1.verify(&payload[..], authority) {
return Err(DoubleVoteValidityError::InvalidSignature);
}
Ok(())
}
}
impl<T: session::Trait> Get<Vec<T::ValidatorId>> for ValidatorIdentities<T> { impl<T: session::Trait> Get<Vec<T::ValidatorId>> for ValidatorIdentities<T> {
fn get() -> Vec<T::ValidatorId> { fn get() -> Vec<T::ValidatorId> {
<session::Module<T>>::validators() <session::Module<T>>::validators()
} }
} }
pub trait Trait: attestations::Trait { /// A trait to get a session number the `Proof` belongs to.
pub trait GetSessionNumber {
fn session(&self) -> SessionIndex;
}
impl GetSessionNumber for session::historical::Proof {
fn session(&self) -> SessionIndex {
self.session()
}
}
pub trait Trait: attestations::Trait + session::historical::Trait + staking::Trait {
/// The outer origin type. /// The outer origin type.
type Origin: From<Origin> + From<system::RawOrigin<Self::AccountId>>; type Origin: From<Origin> + From<system::RawOrigin<Self::AccountId>>;
...@@ -132,6 +229,30 @@ pub trait Trait: attestations::Trait { ...@@ -132,6 +229,30 @@ pub trait Trait: attestations::Trait {
/// Max head data size. /// Max head data size.
type MaxHeadDataSize: Get<u32>; type MaxHeadDataSize: Get<u32>;
/// Proof type.
///
/// We need this type to bind the `KeyOwnerProofSystem::Proof` to necessary bounds.
/// As soon as https://rust-lang.github.io/rfcs/2289-associated-type-bounds.html
/// gets in this can be simplified.
type Proof: Parameter + GetSessionNumber;
/// Compute and check proofs of historical key owners.
type KeyOwnerProofSystem: KeyOwnerProofSystem<
(KeyTypeId, Vec<u8>),
Proof = Self::Proof,
IdentificationTuple = Self::IdentificationTuple,
>;
/// An identification tuple type bound to `Parameter`.
type IdentificationTuple: Parameter;
/// Report an offence.
type ReportOffence: ReportOffence<
Self::AccountId,
Self::IdentificationTuple,
DoubleVoteOffence<Self::IdentificationTuple>
>;
} }
/// Origin for the parachains module. /// Origin for the parachains module.
...@@ -142,6 +263,44 @@ pub enum Origin { ...@@ -142,6 +263,44 @@ pub enum Origin {
Parachain(ParaId), Parachain(ParaId),
} }
/// An offence that is filed if the validator has submitted a double vote.
#[derive(RuntimeDebug)]
#[cfg_attr(feature = "std", derive(Clone, PartialEq, Eq))]
pub struct DoubleVoteOffence<Offender> {
/// The current session index in which we report a validator.
session_index: SessionIndex,
/// The size of the validator set in current session/era.
validator_set_count: u32,
/// An offender that has submitted two conflicting votes.
offender: Offender,
}
impl<Offender: Clone> Offence<Offender> for DoubleVoteOffence<Offender> {
const ID: Kind = *b"para:double-vote";
type TimeSlot = SessionIndex;
fn offenders(&self) -> Vec<Offender> {
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.session_index
}
fn slash_fraction(_offenders_count: u32, _validator_set_count: u32) -> Perbill {
// Slash 100%.
Perbill::from_percent(100)
}
}
/// Total number of individual messages allowed in the parachain -> relay-chain message queue. /// Total number of individual messages allowed in the parachain -> relay-chain message queue.
const MAX_QUEUE_COUNT: usize = 100; const MAX_QUEUE_COUNT: usize = 100;
/// Total size of messages allowed in the parachain -> relay-chain message queue before which no /// Total size of messages allowed in the parachain -> relay-chain message queue before which no
...@@ -173,6 +332,18 @@ decl_storage! { ...@@ -173,6 +332,18 @@ decl_storage! {
/// ///
/// `None` if not yet updated. /// `None` if not yet updated.
pub DidUpdate: Option<Vec<ParaId>>; pub DidUpdate: Option<Vec<ParaId>>;
/// The mapping from parent block hashes to session indexes.
///
/// Used for double vote report validation.
pub ParentToSessionIndex get(session_at_block):
map hasher(twox_64_concat) T::Hash => SessionIndex;
/// The era that is active currently.
///
/// Changes with the `ActiveEra` from `staking`. Upon these changes `ParentToSessionIndex`
/// is pruned.
ActiveEra get(active_era): Option<staking::EraIndex>;
} }
add_extra_genesis { add_extra_genesis {
config(authorities): Vec<ValidatorId>; config(authorities): Vec<ValidatorId>;
...@@ -304,8 +475,69 @@ decl_module! { ...@@ -304,8 +475,69 @@ decl_module! {
Ok(()) Ok(())
} }
/// Provide a proof that some validator has commited a double-vote.
///
/// The weight is 0; in order to avoid DoS a `SignedExtension` validation
/// is implemented.
#[weight = SimpleDispatchInfo::FixedNormal(0)]
pub fn report_double_vote(
origin,
report: DoubleVoteReport<
<T::KeyOwnerProofSystem as KeyOwnerProofSystem<(KeyTypeId, Vec<u8>)>>::Proof,
T::Hash,
>,
) -> DispatchResult {
let reporter = ensure_signed(origin)?;
let validators = <session::Module<T>>::validators();
let validator_set_count = validators.len() as u32;
let session_index = report.proof.session();
let DoubleVoteReport { identity, proof, .. } = report;
// We have already checked this proof in `SignedExtension`, but we need
// this here to get the full identification of the offender.
let offender = T::KeyOwnerProofSystem::check_proof(
(PARACHAIN_KEY_TYPE_ID, identity.encode()),
proof,
).ok_or("Invalid/outdated key ownership proof.")?;
let offence = DoubleVoteOffence {
session_index,
validator_set_count,
offender,
};
// Checks if this is actually a double vote are
// implemented in `ValidateDoubleVoteReports::validete`.
T::ReportOffence::report_offence(vec![reporter], offence)
.map_err(|_| "Failed to report offence")?;
Ok(())
}
fn on_initialize() { fn on_initialize() {
<Self as Store>::DidUpdate::kill(); <Self as Store>::DidUpdate::kill();
let current_session = <session::Module<T>>::current_index();
let parent_hash = <system::Module<T>>::parent_hash();
match Self::active_era() {
Some(era) => {
if let Some(active_era) = <staking::Module<T>>::current_era() {
if era != active_era {
<Self as Store>::ActiveEra::put(active_era);
<ParentToSessionIndex<T>>::remove_all();
}
}
}
None => {
if let Some(active_era) = <staking::Module<T>>::current_era() {
<Self as Store>::ActiveEra::set(Some(active_era));
}
}
}
<ParentToSessionIndex<T>>::insert(parent_hash, current_session);
} }
fn on_finalize() { fn on_finalize() {
...@@ -587,9 +819,6 @@ impl<T: Trait> Module<T> { ...@@ -587,9 +819,6 @@ impl<T: Trait> Module<T> {
active_parachains: &[(ParaId, Option<(CollatorId, Retriable)>)] active_parachains: &[(ParaId, Option<(CollatorId, Retriable)>)]
) -> sp_std::result::Result<IncludedBlocks<T>, sp_runtime::DispatchError> ) -> sp_std::result::Result<IncludedBlocks<T>, sp_runtime::DispatchError>
{ {
use primitives::parachain::ValidityAttestation;
use sp_runtime::traits::AppVerify;
// returns groups of slices that have the same chain ID. // returns groups of slices that have the same chain ID.
// assumes the inner slice is sorted by id. // assumes the inner slice is sorted by id.
struct GroupedDutyIter<'a> { struct GroupedDutyIter<'a> {
...@@ -866,6 +1095,103 @@ pub fn ensure_parachain<OuterOrigin>(o: OuterOrigin) -> result::Result<ParaId, B ...@@ -866,6 +1095,103 @@ pub fn ensure_parachain<OuterOrigin>(o: OuterOrigin) -> result::Result<ParaId, B
} }
} }
/// Ensure that double vote reports are only processed if valid.
#[derive(Encode, Decode, Clone, Eq, PartialEq)]
pub struct ValidateDoubleVoteReports<T>(sp_std::marker::PhantomData<T>);
impl<T> sp_std::fmt::Debug for ValidateDoubleVoteReports<T> where
{
fn fmt(&self, f: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {
write!(f, "ValidateDoubleVoteReports<T>")
}
}
/// Custom validity error used while validating double vote reports.
#[derive(RuntimeDebug)]
#[repr(u8)]
pub enum DoubleVoteValidityError {
/// The authority being reported is not in the authority set.
NotAnAuthority = 0,
/// Failed to convert offender's `FullIdentificationOf`.
FailedToConvertId = 1,
/// The signature on one or both of the statements in the report is wrong.
InvalidSignature = 2,
/// The two statements in the report are not conflicting.
NotDoubleVote = 3,
/// Invalid report. Indicates that statement doesn't match the attestation on one of the votes.
InvalidReport = 4,
/// The proof provided in the report is not valid.
InvalidProof = 5,
}
impl<T: Trait + Send + Sync> SignedExtension for ValidateDoubleVoteReports<T> where
<T as system::Trait>::Call: IsSubType<Module<T>, T>
{
const IDENTIFIER: &'static str = "ValidateDoubleVoteReports";
type AccountId = T::AccountId;
type Call = <T as system::Trait>::Call;
type AdditionalSigned = ();
type Pre = ();
type DispatchInfo = DispatchInfo;
fn additional_signed(&self)
-> sp_std::result::Result<Self::AdditionalSigned, TransactionValidityError>
{
Ok(())
}
fn validate(
&self,
_who: &Self::AccountId,
call: &Self::Call,
_info: DispatchInfo,
_len: usize,
) -> TransactionValidity {
let r = ValidTransaction::default();
if let Some(local_call) = call.is_sub_type() {
if let Call::report_double_vote(report) = local_call {
let validators = <session::Module<T>>::validators();
let parent_hash = report.parent_hash;
let expected_session = Module::<T>::session_at_block(parent_hash);
let session = report.proof.session();
if session != expected_session {
return Err(InvalidTransaction::BadProof.into());
}
let authorities = Module::<T>::authorities();
let offender_idx = match authorities.iter().position(|a| *a == report.identity) {
Some(idx) => idx,
None => return Err(InvalidTransaction::Custom(
DoubleVoteValidityError::NotAnAuthority as u8).into()
),
};
if T::FullIdentificationOf::convert(validators[offender_idx].clone()).is_none() {
return Err(InvalidTransaction::Custom(
DoubleVoteValidityError::FailedToConvertId as u8).into()
);
}
report
.verify::<T>()
.map_err(|e| TransactionValidityError::from(InvalidTransaction::Custom(e as u8)))?;
}
}
Ok(r)
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
...@@ -875,8 +1201,12 @@ mod tests { ...@@ -875,8 +1201,12 @@ mod tests {
use sp_core::{H256, Blake2Hasher}; use sp_core::{H256, Blake2Hasher};
use sp_trie::NodeCodec; use sp_trie::NodeCodec;
use sp_runtime::{ use sp_runtime::{
Perbill, curve::PiecewiseLinear, testing::{UintAuthorityId, Header}, impl_opaque_keys,
traits::{BlakeTwo256, IdentityLookup, OnInitialize, OnFinalize}, Perbill, curve::PiecewiseLinear, testing::{Header},
traits::{
BlakeTwo256, IdentityLookup, OnInitialize, OnFinalize, SaturatedConversion,
OpaqueKeys,
},
}; };
use primitives::{ use primitives::{
parachain::{ parachain::{
...@@ -892,6 +1222,8 @@ mod tests { ...@@ -892,6 +1222,8 @@ mod tests {
use crate::parachains; use crate::parachains;
use crate::registrar; use crate::registrar;
use crate::slots; use crate::slots;
use session::{SessionHandler, SessionManager};
use staking::EraIndex;
// result of <NodeCodec<Blake2Hasher> as trie_db::NodeCodec<Blake2Hasher>>::hashed_null_node() // result of <NodeCodec<Blake2Hasher> as trie_db::NodeCodec<Blake2Hasher>>::hashed_null_node()
const EMPTY_TRIE_ROOT: [u8; 32] = [ const EMPTY_TRIE_ROOT: [u8; 32] = [
...@@ -911,6 +1243,12 @@ mod tests { ...@@ -911,6 +1243,12 @@ mod tests {
} }
} }
impl_opaque_keys! {
pub struct TestSessionKeys {
pub parachain_validator: super::Module<Test>,
}
}
#[derive(Clone, Eq, PartialEq)] #[derive(Clone, Eq, PartialEq)]
pub struct Test; pub struct Test;
parameter_types! { parameter_types! {
...@@ -948,14 +1286,28 @@ mod tests { ...@@ -948,14 +1286,28 @@ mod tests {
pub const DisabledValidatorsThreshold: Perbill = Perbill::from_percent(17); pub const DisabledValidatorsThreshold: Perbill = Perbill::from_percent(17);
} }
/// Custom `SessionHandler` since we use `TestSessionKeys` as `Keys`.
pub struct TestSessionHandler;
impl<AId> SessionHandler<AId> for TestSessionHandler {
const KEY_TYPE_IDS: &'static [KeyTypeId] = &[PARACHAIN_KEY_TYPE_ID];
fn on_genesis_session<Ks: