// Copyright 2017-2020 Parity Technologies (UK) Ltd. // This file is part of Polkadot. // Polkadot 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. // Polkadot 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 Polkadot. If not, see . //! Main parachains logic. For now this is just the determination of which validators do what. use sp_std::prelude::*; use sp_std::result; use codec::{Decode, Encode}; use sp_core::sr25519; use sp_runtime::{ KeyTypeId, Perbill, RuntimeDebug, traits::{ Hash as HashT, BlakeTwo256, Saturating, One, Zero, Dispatchable, AccountIdConversion, BadOrigin, Convert, SignedExtension, AppVerify, DispatchInfoOf, }, transaction_validity::{TransactionValidityError, ValidTransaction, TransactionValidity}, }; use sp_staking::{ SessionIndex, offence::{ReportOffence, Offence, Kind}, }; use frame_support::{ traits::KeyOwnerProofSystem, dispatch::{IsSubType}, weights::{DispatchClass, Weight}, }; use primitives::{ Balance, BlockNumber, Signature, parachain::{ Id as ParaId, Chain, DutyRoster, AttestedCandidate, Statement, ParachainDispatchOrigin, UpwardMessage, ValidatorId, ActiveParas, CollatorId, Retriable, OmittedValidationData, CandidateReceipt, GlobalValidationSchedule, AbridgedCandidateReceipt, LocalValidationData, Scheduling, ValidityAttestation, NEW_HEADS_IDENTIFIER, PARACHAIN_KEY_TYPE_ID, ValidatorSignature, SigningContext, HeadData, ValidationCode, FishermanId, }, }; use frame_support::{ Parameter, dispatch::DispatchResult, decl_storage, decl_module, decl_error, ensure, traits::{Currency, Get, WithdrawReason, ExistenceRequirement, Randomness}, }; use sp_runtime::{ transaction_validity::InvalidTransaction, traits::Verify, }; use inherents::{ProvideInherent, InherentData, MakeFatalError, InherentIdentifier}; use system::{ ensure_none, ensure_signed, offchain::{CreateSignedTransaction, SendSignedTransaction, Signer}, }; use crate::attestations::{self, IncludedBlocks}; use crate::registrar::Registrar; // An `AppCrypto` type to facilitate submitting signed transactions. pub struct FishermanAuthorityId; impl system::offchain::AppCrypto<::Signer, Signature> for FishermanAuthorityId { type RuntimeAppPublic = FishermanId; type GenericSignature = sr25519::Signature; type GenericPublic = sp_core::sr25519::Public; } // ranges for iteration of general block number don't work, so this // is a utility to get around that. struct BlockNumberRange { low: N, high: N, } impl Iterator for BlockNumberRange { type Item = N; fn next(&mut self) -> Option { if self.low >= self.high { return None } let item = self.low.clone(); self.low = self.low.clone().saturating_add(One::one()); Some(item) } } // wrapper trait because an associated type of `Currency` // doesn't work.` pub trait ParachainCurrency { fn free_balance(para_id: ParaId) -> Balance; fn deduct(para_id: ParaId, amount: Balance) -> DispatchResult; } impl> ParachainCurrency for T where T::Balance: From + Into, ParaId: AccountIdConversion, { fn free_balance(para_id: ParaId) -> Balance { let para_account = para_id.into_account(); T::free_balance(¶_account).into() } fn deduct(para_id: ParaId, amount: Balance) -> DispatchResult { let para_account = para_id.into_account(); // burn the fee. let _ = T::withdraw( ¶_account, amount.into(), WithdrawReason::Fee.into(), ExistenceRequirement::KeepAlive, )?; Ok(()) } } /// Interface to the persistent (stash) identities of the current validators. pub struct ValidatorIdentities(sp_std::marker::PhantomData); /// 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 { /// 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, /// A `SigningContext` with a session and a parent hash of the moment this offence was commited. pub signing_context: SigningContext, } impl DoubleVoteReport { fn verify>( &self, ) -> Result<(), DoubleVoteValidityError> { let first = self.first.clone(); let second = self.second.clone(); let id = self.identity.clone(); T::KeyOwnerProofSystem::check_proof((PARACHAIN_KEY_TYPE_ID, id), self.proof.clone()) .ok_or(DoubleVoteValidityError::InvalidProof)?; if self.proof.session() != self.signing_context.session_index { return Err(DoubleVoteValidityError::InvalidReport); } // Check signatures. Self::verify_vote( &first, &self.signing_context, &self.identity, )?; Self::verify_vote( &second, &self.signing_context, &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), signing_context: &SigningContext, authority: &ValidatorId, ) -> Result<(), DoubleVoteValidityError> { let payload = localized_payload(vote.0.clone(), signing_context); if !vote.1.verify(&payload[..], authority) { return Err(DoubleVoteValidityError::InvalidSignature); } Ok(()) } } impl Get> for ValidatorIdentities { fn get() -> Vec { >::validators() } } /// 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: CreateSignedTransaction> + attestations::Trait + session::historical::Trait { // The transaction signing authority type AuthorityId: system::offchain::AppCrypto; /// The outer origin type. type Origin: From + From>; /// The outer call dispatch type. type Call: Parameter + Dispatchable::Origin> + From>; /// Some way of interacting with balances for fees. type ParachainCurrency: ParachainCurrency; /// Polkadot in practice will always use the `BlockNumber` type. /// Substrate isn't good at giving us ways to bound the supertrait /// associated type, so we introduce this conversion. type BlockNumberConversion: Convert; /// Something that provides randomness in the runtime. type Randomness: Randomness; /// Means to determine what the current set of active parachains are. type ActiveParachains: ActiveParas; /// The way that we are able to register parachains. type Registrar: Registrar; /// Maximum code size for parachains, in bytes. Note that this is not /// the entire storage burden of the parachain, as old code is stored for /// `SlashPeriod` blocks. type MaxCodeSize: Get; /// Max head data size. type MaxHeadDataSize: Get; /// The frequency at which paras can upgrade their validation function. /// This is an integer number of relay-chain blocks that must pass between /// code upgrades. type ValidationUpgradeFrequency: Get; /// The delay before a validation function upgrade is applied. type ValidationUpgradeDelay: Get; /// The period (in blocks) that slash reports are permitted against an /// included candidate. /// /// After validation function upgrades, the old code is persisted on-chain /// for this period, to ensure that candidates validated under old functions /// can be re-checked. type SlashPeriod: Get; /// 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, ValidatorId), 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, >; /// A type that converts the opaque hash type to exact one. type BlockHashConversion: Convert; } /// Origin for the parachains module. #[derive(PartialEq, Eq, Clone)] #[cfg_attr(feature = "std", derive(Debug))] pub enum Origin { /// It comes from a parachain. 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 { /// 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 Offence for DoubleVoteOffence { const ID: Kind = *b"para:double-vote"; type TimeSlot = SessionIndex; fn offenders(&self) -> Vec { 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. const MAX_QUEUE_COUNT: usize = 100; /// Total size of messages allowed in the parachain -> relay-chain message queue before which no /// further messages may be added to it. If it exceeds this then the queue may contain only a /// single message. const WATERMARK_QUEUE_SIZE: usize = 20000; /// Metadata used to track previous parachain validation code that we keep in /// the state. #[derive(Default, Encode, Decode)] #[cfg_attr(test, derive(Debug, Clone, PartialEq))] pub struct ParaPastCodeMeta { // Block numbers where the code was replaced. These can be used as indices // into the `PastCode` map along with the `ParaId` to fetch the code itself. upgrade_times: Vec, // This tracks the highest pruned code-replacement, if any. last_pruned: Option, } #[cfg_attr(test, derive(Debug, PartialEq))] enum UseCodeAt { // Use the current code. Current, // Use the code that was replaced at the given block number. ReplacedAt(N), } impl ParaPastCodeMeta { // note a replacement has occurred at a given block number. fn note_replacement(&mut self, at: N) { self.upgrade_times.insert(0, at) } // Yields the block number of the code that should be used for validating at // the given block number. // // a return value of `None` means that there is no code we are aware of that // should be used to validate at the given height. fn code_at(&self, at: N) -> Option> { // The `PastCode` map stores the code which was replaced at `t`. let end_position = self.upgrade_times.iter().position(|&t| t < at); if let Some(end_position) = end_position { Some(if end_position != 0 { // `end_position` gives us the replacement time where the code used at `at` // was set. But that code has been replaced: `end_position - 1` yields // that index. UseCodeAt::ReplacedAt(self.upgrade_times[end_position - 1]) } else { // the most recent tracked replacement is before `at`. // this means that the code put in place then (i.e. the current code) // is correct for validating at `at`. UseCodeAt::Current }) } else { if self.last_pruned.as_ref().map_or(true, |&n| n < at) { // Our `last_pruned` is before `at`, so we still have the code! // but no code upgrade entries found before the `at` parameter. // // this means one of two things is true: // 1. there are no non-pruned upgrade logs. in this case use `Current` // 2. there are non-pruned upgrade logs all after `at`. // in this case use the oldest upgrade log. Some(self.upgrade_times.last() .map(|n| UseCodeAt::ReplacedAt(*n)) .unwrap_or(UseCodeAt::Current) ) } else { // We don't have the code anymore. None } } } // The block at which the most recently tracked code change occurred. fn most_recent_change(&self) -> Option { self.upgrade_times.first().map(|x| x.clone()) } // prunes all code upgrade logs occurring at or before `max`. // note that code replaced at `x` is the code used to validate all blocks before // `x`. Thus, `max` should be outside of the slashing window when this is invoked. // // returns an iterator of block numbers at which code was replaced, where the replaced // code should be now pruned, in ascending order. fn prune_up_to(&'_ mut self, max: N) -> impl Iterator + '_ { match self.upgrade_times.iter().position(|&t| t <= max) { None => { // this is a no-op `drain` - desired because all // logged code upgrades occurred after `max`. self.upgrade_times.drain(self.upgrade_times.len()..).rev() } Some(pos) => { self.last_pruned = Some(self.upgrade_times[pos]); self.upgrade_times.drain(pos..).rev() } } } } decl_storage! { trait Store for Module as Parachains { /// All authorities' keys at the moment. pub Authorities get(fn authorities): Vec; /// The active code of a currently-registered parachain. pub Code get(fn parachain_code): map hasher(twox_64_concat) ParaId => Option; /// Past code of parachains. The parachains themselves may not be registered anymore, /// but we also keep their code on-chain for the same amount of time as outdated code /// to assist with availability. PastCodeMeta get(fn past_code_meta): map hasher(twox_64_concat) ParaId => ParaPastCodeMeta; /// Actual past code, indicated by the parachain and the block number at which it /// became outdated. PastCode: map hasher(twox_64_concat) (ParaId, T::BlockNumber) => Option; /// Past code pruning, in order of priority. PastCodePruning get(fn past_code_pruning_tasks): Vec<(ParaId, T::BlockNumber)>; // The block number at which the planned code change is expected for a para. // The change will be applied after the first parablock for this ID included which executes // in the context of a relay chain block with a number >= `expected_at`. FutureCodeUpgrades get(fn code_upgrade_schedule): map hasher(twox_64_concat) ParaId => Option; // The actual future code of a para. FutureCode: map hasher(twox_64_concat) ParaId => ValidationCode; /// The heads of the parachains registered at present. pub Heads get(fn parachain_head): map hasher(twox_64_concat) ParaId => Option; /// Messages ready to be dispatched onto the relay chain. It is subject to /// `MAX_MESSAGE_COUNT` and `WATERMARK_MESSAGE_SIZE`. pub RelayDispatchQueue: map hasher(twox_64_concat) ParaId => Vec; /// Size of the dispatch queues. Separated from actual data in order to avoid costly /// decoding when checking receipt validity. First item in tuple is the count of messages /// second if the total length (in bytes) of the message payloads. pub RelayDispatchQueueSize: map hasher(twox_64_concat) ParaId => (u32, u32); /// The ordered list of ParaIds that have a `RelayDispatchQueue` entry. NeedsDispatch: Vec; /// `Some` if the parachain heads get updated in this block, along with the parachain IDs /// that did update. Ordered in the same way as `registrar::Active` (i.e. by ParaId). /// /// `None` if not yet updated. pub DidUpdate: Option>; } add_extra_genesis { config(authorities): Vec; build(|config| Module::::initialize_authorities(&config.authorities)) } } decl_error! { pub enum Error for Module { /// Parachain heads must be updated only once in the block. TooManyHeadUpdates, /// Too many parachain candidates. TooManyParaCandidates, /// Proposed heads must be ascending order by parachain ID without duplicate. HeadsOutOfOrder, /// Candidate is for an unregistered parachain. UnregisteredPara, /// Invalid collator. InvalidCollator, /// The message queue is full. Messages will be added when there is space. QueueFull, /// The message origin is invalid. InvalidMessageOrigin, /// No validator group for parachain. NoValidatorGroup, /// Not enough validity votes for candidate. NotEnoughValidityVotes, /// The number of attestations exceeds the number of authorities. VotesExceedsAuthorities, /// Attesting validator not on this chain's validation duty. WrongValidatorAttesting, /// Invalid signature from attester. InvalidSignature, /// Extra untagged validity votes along with candidate. UntaggedVotes, /// Wrong parent head for parachain receipt. ParentMismatch, /// Head data was too large. HeadDataTooLarge, /// New validation code was too large. ValidationCodeTooLarge, /// Disallowed code upgrade. DisallowedCodeUpgrade, /// Para does not have enough balance to pay fees. CannotPayFees, /// Unexpected relay-parent for a candidate receipt. UnexpectedRelayParent, } } decl_module! { /// Parachains module. pub struct Module for enum Call where origin: ::Origin { type Error = Error; fn on_initialize(now: T::BlockNumber) -> Weight { ::DidUpdate::kill(); Self::do_old_code_pruning(now); // TODO https://github.com/paritytech/polkadot/issues/977: set correctly 0 } fn on_finalize() { assert!(::DidUpdate::exists(), "Parachain heads must be updated once in the block"); } /// Provide candidate receipts for parachains, in ascending order by id. #[weight = (1_000_000_000, DispatchClass::Mandatory)] pub fn set_heads(origin, heads: Vec) -> DispatchResult { ensure_none(origin)?; ensure!(!::exists(), Error::::TooManyHeadUpdates); let active_parachains = Self::active_parachains(); let parachain_count = active_parachains.len(); ensure!(heads.len() <= parachain_count, Error::::TooManyParaCandidates); let mut proceeded = Vec::with_capacity(heads.len()); let schedule = Self::global_validation_schedule(); if !active_parachains.is_empty() { // perform integrity checks before writing to storage. { let mut last_id = None; let mut iter = active_parachains.iter(); for head in &heads { let id = head.parachain_index(); // proposed heads must be ascending order by parachain ID without duplicate. ensure!( last_id.as_ref().map_or(true, |x| x < &id), Error::::HeadsOutOfOrder ); // must be unknown since active parachains are always sorted. let (_, maybe_required_collator) = iter.find(|para| para.0 == id) .ok_or(Error::::UnregisteredPara)?; if let Some((required_collator, _)) = maybe_required_collator { ensure!(required_collator == &head.candidate.collator, Error::::InvalidCollator); } Self::check_upward_messages( id, &head.candidate.commitments.upward_messages, MAX_QUEUE_COUNT, WATERMARK_QUEUE_SIZE, )?; let id = head.parachain_index(); proceeded.push(id); last_id = Some(id); } } let para_blocks = Self::check_candidates( &schedule, &heads, &active_parachains, )?; >::note_included(&heads, para_blocks); Self::update_routing( &heads, ); // note: we dispatch new messages _after_ the call to `check_candidates` // which deducts any fees. if that were not the case, an upward message // could be dispatched and spend money that invalidated a candidate. Self::dispatch_upward_messages( MAX_QUEUE_COUNT, WATERMARK_QUEUE_SIZE, Self::dispatch_message, ); } DidUpdate::put(proceeded); 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 = 0] pub fn report_double_vote( origin, report: DoubleVoteReport< >::Proof, >, ) -> DispatchResult { let reporter = ensure_signed(origin)?; let validators = >::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), 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 majority_of(list_len: usize) -> usize { list_len / 2 + list_len % 2 } fn localized_payload( statement: Statement, signing_context: &SigningContext, ) -> Vec { let mut encoded = statement.encode(); signing_context.using_encoded(|s| encoded.extend(s)); encoded } impl Module { /// Initialize the state of a new parachain/parathread. pub fn initialize_para( id: ParaId, code: ValidationCode, initial_head_data: HeadData, ) { ::insert(id, code); ::insert(id, initial_head_data); } /// Cleanup all storage related to a para. Some pieces of data may remain /// available in the on-chain state. pub fn cleanup_para( id: ParaId, ) { let code = ::take(id); ::remove(id); // clean up from all code-upgrade maps. // we don't clean up the meta or planned-code maps as that's handled // by the pruning process. if let Some(_planned_future_at) = ::FutureCodeUpgrades::take(&id) { ::FutureCode::remove(&id); } if let Some(code) = code { Self::note_past_code(id, >::block_number(), code); } } // note replacement of the code of para with given `id`, which occured in the // context of the given relay-chain block number. provide the replaced code. // // `at` for para-triggered replacement is the block number of the relay-chain // block in whose context the parablock was executed // (i.e. number of `relay_parent` in the receipt) fn note_past_code(id: ParaId, at: T::BlockNumber, old_code: ValidationCode) { ::PastCodeMeta::mutate(&id, |past_meta| { past_meta.note_replacement(at); }); ::PastCode::insert(&(id, at), old_code); // Schedule pruning for this past-code to be removed as soon as it // exits the slashing window. ::PastCodePruning::mutate(|pruning| { let insert_idx = pruning.binary_search_by_key(&at, |&(_, b)| b) .unwrap_or_else(|idx| idx); pruning.insert(insert_idx, (id, at)); }) } // does old code pruning. fn do_old_code_pruning(now: T::BlockNumber) { let slash_period = T::SlashPeriod::get(); if now <= slash_period { return } // The height of any changes we no longer should keep around. let pruning_height = now - (slash_period + One::one()); ::PastCodePruning::mutate(|pruning_tasks: &mut Vec<(_, T::BlockNumber)>| { let pruning_tasks_to_do = { // find all past code that has just exited the pruning window. let up_to_idx = pruning_tasks.iter() .take_while(|&(_, at)| at <= &pruning_height) .count(); pruning_tasks.drain(..up_to_idx) }; for (para_id, _) in pruning_tasks_to_do { let full_deactivate = ::PastCodeMeta::mutate(¶_id, |meta| { for pruned_repl_at in meta.prune_up_to(pruning_height) { ::PastCode::remove(&(para_id, pruned_repl_at)); } meta.most_recent_change().is_none() && Self::parachain_head(¶_id).is_none() }); // This parachain has been removed and now the vestigial code // has been removed from the state. clean up meta as well. if full_deactivate { ::PastCodeMeta::remove(¶_id); } } }); } // Performs a code upgrade of a parachain. fn do_code_upgrade(id: ParaId, at: T::BlockNumber, new_code: &ValidationCode) { let old_code = Self::parachain_code(&id).unwrap_or_default(); Code::insert(&id, new_code); Self::note_past_code(id, at, old_code); } /// Get a `SigningContext` with a current `SessionIndex` and parent hash. pub fn signing_context() -> SigningContext { let session_index = >::current_index(); let parent_hash = >::parent_hash(); SigningContext { session_index, parent_hash: T::BlockHashConversion::convert(parent_hash), } } /// Submit a double vote report. pub fn submit_double_vote_report( report: DoubleVoteReport, ) -> Option<()> { Signer::::all_accounts() .send_signed_transaction( move |_account| { Call::report_double_vote(report.clone()) } ) .iter() .find_map(|(_, res)| res.ok().map(|_| ())) } /// Dispatch some messages from a parachain. fn dispatch_message( id: ParaId, origin: ParachainDispatchOrigin, data: &[u8], ) { if let Ok(message_call) = ::Call::decode(&mut &data[..]) { let origin: ::Origin = match origin { ParachainDispatchOrigin::Signed => system::RawOrigin::Signed(id.into_account()).into(), ParachainDispatchOrigin::Parachain => Origin::Parachain(id).into(), ParachainDispatchOrigin::Root => system::RawOrigin::Root.into(), }; let _ok = message_call.dispatch(origin).is_ok(); // Not much to do with the result as it is. It's up to the parachain to ensure that the // message makes sense. } } /// Ensure all is well with the upward messages. fn check_upward_messages( id: ParaId, upward_messages: &[UpwardMessage], max_queue_count: usize, watermark_queue_size: usize, ) -> DispatchResult { // Either there are no more messages to add... if !upward_messages.is_empty() { let (count, size) = ::get(id); ensure!( // ...or we are appending one message onto an empty queue... upward_messages.len() + count as usize == 1 // ...or... || ( // ...the total messages in the queue ends up being no greater than the // limit... upward_messages.len() + count as usize <= max_queue_count && // ...and the total size of the payloads in the queue ends up being no // greater than the limit. upward_messages.iter() .fold(size as usize, |a, x| a + x.data.len()) <= watermark_queue_size ), Error::::QueueFull ); if !id.is_system() { for m in upward_messages.iter() { ensure!(m.origin != ParachainDispatchOrigin::Root, Error::::InvalidMessageOrigin); } } } Ok(()) } /// Update routing information from the parachain heads. This queues upwards /// messages to the relay chain as well. fn update_routing( heads: &[AttestedCandidate], ) { // we sort them in order to provide a fast lookup to ensure we can avoid duplicates in the // needs_dispatch queue. let mut ordered_needs_dispatch = NeedsDispatch::get(); for head in heads.iter() { let id = head.parachain_index(); Heads::insert(id, &head.candidate.head_data); // Queue up upwards messages (from parachains to relay chain). Self::queue_upward_messages( id, &head.candidate.commitments.upward_messages, &mut ordered_needs_dispatch, ); } NeedsDispatch::put(ordered_needs_dispatch); } /// Place any new upward messages into our queue for later dispatch. /// /// `ordered_needs_dispatch` is mutated to ensure it reflects the new value of /// `RelayDispatchQueueSize`. It is up to the caller to guarantee that it gets written into /// storage after this call. fn queue_upward_messages( id: ParaId, upward_messages: &[UpwardMessage], ordered_needs_dispatch: &mut Vec, ) { if !upward_messages.is_empty() { RelayDispatchQueueSize::mutate(id, |&mut(ref mut count, ref mut len)| { *count += upward_messages.len() as u32; *len += upward_messages.iter() .fold(0, |a, x| a + x.data.len()) as u32; }); // Should never be able to fail assuming our state is uncorrupted, but best not // to panic, even if it does. let _ = RelayDispatchQueue::append(id, upward_messages); if let Err(i) = ordered_needs_dispatch.binary_search(&id) { // same. ordered_needs_dispatch.insert(i, id); } else { sp_runtime::print("ordered_needs_dispatch contains id?!"); } } } /// Simple FIFO dispatcher. This must be called after parachain fees are checked, /// as dispatched messages may spend parachain funds. fn dispatch_upward_messages( max_queue_count: usize, watermark_queue_size: usize, mut dispatch_message: impl FnMut(ParaId, ParachainDispatchOrigin, &[u8]), ) { let queueds = NeedsDispatch::get(); let mut drained_count = 0usize; let mut dispatched_count = 0usize; let mut dispatched_size = 0usize; for id in queueds.iter() { drained_count += 1; let (count, size) = ::get(id); let count = count as usize; let size = size as usize; if dispatched_count == 0 || ( dispatched_count + count <= max_queue_count && dispatched_size + size <= watermark_queue_size ) { if count > 0 { // still dispatching messages... RelayDispatchQueueSize::remove(id); let messages = RelayDispatchQueue::take(id); for UpwardMessage { origin, data } in messages.into_iter() { dispatch_message(*id, origin, &data); } dispatched_count += count; dispatched_size += size; if dispatched_count >= max_queue_count || dispatched_size >= watermark_queue_size { break } } } } NeedsDispatch::put(&queueds[drained_count..]); } /// Calculate the current block's duty roster using system's random seed. /// Returns the duty roster along with the random seed. pub fn calculate_duty_roster() -> (DutyRoster, [u8; 32]) { let parachains = Self::active_parachains(); let parachain_count = parachains.len(); // TODO: use decode length. substrate #2794 let validator_count = Self::authorities().len(); let validators_per_parachain = if parachain_count == 0 { 0 } else { (validator_count - 1) / parachain_count }; let mut roles_val = (0..validator_count).map(|i| match i { i if i < parachain_count * validators_per_parachain => { let idx = i / validators_per_parachain; Chain::Parachain(parachains[idx].0.clone()) } _ => Chain::Relay, }).collect::>(); let mut seed = { let phrase = b"validator_role_pairs"; let seed = T::Randomness::random(&phrase[..]); let seed_len = seed.as_ref().len(); let needed_bytes = validator_count * 4; // hash only the needed bits of the random seed. // if earlier bits are influencable, they will not factor into // the seed used here. let seed_off = if needed_bytes >= seed_len { 0 } else { seed_len - needed_bytes }; BlakeTwo256::hash(&seed.as_ref()[seed_off..]) }; let orig_seed = seed.clone().to_fixed_bytes(); // shuffle for i in 0..(validator_count.saturating_sub(1)) { // 4 bytes of entropy used per cycle, 32 bytes entropy per hash let offset = (i * 4 % 32) as usize; // number of roles remaining to select from. let remaining = sp_std::cmp::max(1, (validator_count - i) as usize); // 8 32-bit ints per 256-bit seed. let val_index = u32::decode(&mut &seed[offset..offset + 4]) .expect("using 4 bytes for a 32-bit quantity") as usize % remaining; if offset == 28 { // into the last 4 bytes - rehash to gather new entropy seed = BlakeTwo256::hash(seed.as_ref()); } // exchange last item with randomly chosen first. roles_val.swap(remaining - 1, val_index); } (DutyRoster { validator_duty: roles_val, }, orig_seed) } /// Get the global validation schedule for all parachains. pub fn global_validation_schedule() -> GlobalValidationSchedule { let now = >::block_number(); GlobalValidationSchedule { max_code_size: T::MaxCodeSize::get(), max_head_data_size: T::MaxHeadDataSize::get(), block_number: T::BlockNumberConversion::convert(if now.is_zero() { now } else { // parablocks included in this block will execute in the context // of the current block's parent. now - One::one() }), } } /// Get the local validation schedule for a particular parachain. pub fn local_validation_data(id: &ParaId, perceived_height: T::BlockNumber) -> Option { if perceived_height + One::one() != >::block_number() { // sanity-check - no non-direct-parent blocks allowed at the moment. return None } let code_upgrade_allowed: Option = (|| { match T::Registrar::para_info(*id)?.scheduling { Scheduling::Always => {}, Scheduling::Dynamic => return None, // parathreads can't upgrade code. } // if perceived-height were not the parent of `now`, then this should // not be drawn from current-runtime configuration. however the sanity-check // above prevents that. let min_upgrade_frequency = T::ValidationUpgradeFrequency::get(); let upgrade_delay = T::ValidationUpgradeDelay::get(); let no_planned = Self::code_upgrade_schedule(id) .map_or(true, |expected: T::BlockNumber| expected <= perceived_height); let can_upgrade_code = no_planned && Self::past_code_meta(id).most_recent_change() .map_or(true, |at| at + min_upgrade_frequency < perceived_height); if can_upgrade_code { let applied_at = perceived_height + upgrade_delay; Some(T::BlockNumberConversion::convert(applied_at)) } else { None } })(); Self::parachain_head(id).map(|parent_head| LocalValidationData { parent_head, balance: T::ParachainCurrency::free_balance(*id), code_upgrade_allowed, }) } /// Get the local validation data for a particular parent w.r.t. the current /// block height. pub fn current_local_validation_data(id: &ParaId) -> Option { let now: T::BlockNumber = >::block_number(); if now >= One::one() { Self::local_validation_data(id, now - One::one()) } else { None } } /// Fetch the code used for verifying a parachain at a particular height. pub fn parachain_code_at(id: &ParaId, at: T::BlockNumber) -> Option { // note - we don't check that the parachain is currently registered // as this might be a deregistered parachain whose old code should still // stick around on-chain for some time. Self::past_code_meta(id).code_at(at).and_then(|to_use| match to_use { UseCodeAt::Current => Self::parachain_code(id), UseCodeAt::ReplacedAt(replaced_at) => ::PastCode::get(&(*id, replaced_at)), }) } /// Get the currently active set of parachains. pub fn active_parachains() -> Vec<(ParaId, Option<(CollatorId, Retriable)>)> { T::ActiveParachains::active_paras() } // check the attestations on these candidates. The candidates should have been checked // that each candidates' chain ID is valid. fn check_candidates( schedule: &GlobalValidationSchedule, attested_candidates: &[AttestedCandidate], active_parachains: &[(ParaId, Option<(CollatorId, Retriable)>)] ) -> sp_std::result::Result, sp_runtime::DispatchError> { // returns groups of slices that have the same chain ID. // assumes the inner slice is sorted by id. struct GroupedDutyIter<'a> { next_idx: usize, inner: &'a [(usize, ParaId)], } impl<'a> GroupedDutyIter<'a> { fn new(inner: &'a [(usize, ParaId)]) -> Self { GroupedDutyIter { next_idx: 0, inner } } fn group_for(&mut self, wanted_id: ParaId) -> Option<&'a [(usize, ParaId)]> { while let Some((id, keys)) = self.next() { if wanted_id == id { return Some(keys) } } None } } impl<'a> Iterator for GroupedDutyIter<'a> { type Item = (ParaId, &'a [(usize, ParaId)]); fn next(&mut self) -> Option { if self.next_idx == self.inner.len() { return None } let start_idx = self.next_idx; self.next_idx += 1; let start_id = self.inner[start_idx].1; while self.inner.get(self.next_idx).map_or(false, |&(_, ref id)| id == &start_id) { self.next_idx += 1; } Some((start_id, &self.inner[start_idx..self.next_idx])) } } let authorities = Self::authorities(); let (duty_roster, random_seed) = Self::calculate_duty_roster(); // convert a duty roster, which is originally a Vec, where each // item corresponds to the same position in the session keys, into // a list containing (index, parachain duty) where indices are into the session keys. // this list is sorted ascending by parachain duty, just like the // parachain candidates are. let make_sorted_duties = |duty: &[Chain]| { let mut sorted_duties = Vec::with_capacity(duty.len()); for (val_idx, duty) in duty.iter().enumerate() { let id = match duty { Chain::Relay => continue, Chain::Parachain(id) => id, }; let idx = sorted_duties.binary_search_by_key(&id, |&(_, ref id)| id) .unwrap_or_else(|idx| idx); sorted_duties.insert(idx, (val_idx, *id)); } sorted_duties }; // computes the omitted validation data for a particular parachain. // // pass the perceived relay chain height of the para-block. This is the block number of // `abridged.relay_parent`. let full_candidate = | abridged: &AbridgedCandidateReceipt, perceived_height: T::BlockNumber, | -> sp_std::result::Result { let para_id = abridged.parachain_index; let local_validation = Self::local_validation_data(¶_id, perceived_height) .ok_or(Error::::ParentMismatch)?; let omitted = OmittedValidationData { global_validation: schedule.clone(), local_validation, }; Ok(abridged.clone().complete(omitted)) }; let sorted_validators = make_sorted_duties(&duty_roster.validator_duty); let relay_height_now = >::block_number(); let parent_hash = >::parent_hash(); let signing_context = Self::signing_context(); let localized_payload = |statement: Statement| localized_payload(statement, &signing_context); let code_upgrade_delay = T::ValidationUpgradeDelay::get(); let mut validator_groups = GroupedDutyIter::new(&sorted_validators[..]); let mut para_block_hashes = Vec::new(); for candidate in attested_candidates { let para_id = candidate.parachain_index(); let validator_group = validator_groups.group_for(para_id) .ok_or(Error::::NoValidatorGroup)?; // NOTE: when changing this to allow older blocks, // care must be taken in the availability store pruning to ensure that // data is stored correctly. A block containing a candidate C can be // orphaned before a block containing C is finalized. Care must be taken // not to prune the data for C simply because an orphaned block contained // it. ensure!( candidate.candidate().relay_parent.as_ref() == parent_hash.as_ref(), Error::::UnexpectedRelayParent, ); // Since we only allow execution in context of parent hash. let perceived_relay_block_height = >::block_number() - One::one(); ensure!( candidate.validity_votes.len() >= majority_of(validator_group.len()), Error::::NotEnoughValidityVotes, ); ensure!( candidate.validity_votes.len() <= authorities.len(), Error::::VotesExceedsAuthorities, ); ensure!( schedule.max_head_data_size >= candidate.candidate().head_data.0.len() as _, Error::::HeadDataTooLarge, ); let full_candidate = full_candidate( candidate.candidate(), perceived_relay_block_height, )?; // apply any scheduled code upgrade. if let Some(expected_at) = Self::code_upgrade_schedule(¶_id) { if expected_at <= perceived_relay_block_height { let new_code = FutureCode::take(¶_id); ::FutureCodeUpgrades::remove(¶_id); Self::do_code_upgrade(para_id, perceived_relay_block_height, &new_code); } } if let Some(ref new_code) = full_candidate.commitments.new_validation_code { ensure!( full_candidate.local_validation.code_upgrade_allowed.is_some(), Error::::DisallowedCodeUpgrade, ); ensure!( schedule.max_code_size >= new_code.0.len() as u32, Error::::ValidationCodeTooLarge, ); if code_upgrade_delay.is_zero() { Self::do_code_upgrade(para_id, perceived_relay_block_height, new_code); } else { ::FutureCodeUpgrades::insert( ¶_id, &(perceived_relay_block_height + code_upgrade_delay), ); FutureCode::insert( ¶_id, new_code, ); } } let fees = full_candidate.commitments.fees; ensure!( full_candidate.local_validation.balance >= full_candidate.commitments.fees, Error::::CannotPayFees, ); T::ParachainCurrency::deduct(para_id, fees)?; let candidate_hash = candidate.candidate().hash(); let mut encoded_implicit = None; let mut encoded_explicit = None; let mut expected_votes_len = 0; for (vote_index, (auth_index, _)) in candidate.validator_indices .iter() .enumerate() .filter(|(_, bit)| **bit) .enumerate() { let validity_attestation = match candidate.validity_votes.get(vote_index) { None => Err(Error::::NotEnoughValidityVotes)?, Some(v) => { expected_votes_len = vote_index + 1; v } }; if validator_group.iter().find(|&(idx, _)| *idx == auth_index).is_none() { Err(Error::::WrongValidatorAttesting)? } let (payload, sig) = match validity_attestation { ValidityAttestation::Implicit(sig) => { let payload = encoded_implicit.get_or_insert_with(|| localized_payload( Statement::Candidate(candidate_hash), )); (payload, sig) } ValidityAttestation::Explicit(sig) => { let payload = encoded_explicit.get_or_insert_with(|| localized_payload( Statement::Valid(candidate_hash), )); (payload, sig) } }; ensure!( sig.verify(&payload[..], &authorities[auth_index]), Error::::InvalidSignature, ); } ensure!( candidate.validity_votes.len() == expected_votes_len, Error::::UntaggedVotes ); para_block_hashes.push(candidate_hash); } Ok(IncludedBlocks { actual_number: relay_height_now, session: >::current_index(), random_seed, active_parachains: active_parachains.iter().map(|x| x.0).collect(), para_blocks: para_block_hashes, }) } fn initialize_authorities(authorities: &[ValidatorId]) { if !authorities.is_empty() { assert!(Authorities::get().is_empty(), "Authorities are already initialized!"); Authorities::put(authorities); } } /* // TODO: Consider integrating if needed. (https://github.com/paritytech/polkadot/issues/223) /// Extract the parachain heads from the block. pub fn parachain_heads(&self) -> &[CandidateReceipt] { let x = self.inner.extrinsics.get(PARACHAINS_SET_POSITION as usize).and_then(|xt| match xt.function { Call::Parachains(ParachainsCall::set_heads(ref x)) => Some(&x[..]), _ => None }); match x { Some(x) => x, None => panic!("Invalid polkadot block asserted at {:?}", self.file_line), } } */ } impl sp_runtime::BoundToRuntimeAppPublic for Module { type Public = ValidatorId; } impl session::OneSessionHandler for Module { type Key = ValidatorId; fn on_genesis_session<'a, I: 'a>(validators: I) where I: Iterator { Self::initialize_authorities(&validators.map(|(_, key)| key).collect::>()); } fn on_new_session<'a, I: 'a>(changed: bool, validators: I, _queued: I) where I: Iterator { if changed { ::Authorities::put(validators.map(|(_, key)| key).collect::>()); } } fn on_disabled(_i: usize) { } } pub type InherentType = Vec; impl ProvideInherent for Module { type Call = Call; type Error = MakeFatalError; const INHERENT_IDENTIFIER: InherentIdentifier = NEW_HEADS_IDENTIFIER; fn create_inherent(data: &InherentData) -> Option { let data = data.get_data::(&NEW_HEADS_IDENTIFIER) .expect("Parachain heads could not be decoded.") .expect("No parachain heads found in inherent data."); Some(Call::set_heads(data)) } } /// Ensure that the origin `o` represents a parachain. /// Returns `Ok` with the parachain ID that effected the extrinsic or an `Err` otherwise. pub fn ensure_parachain(o: OuterOrigin) -> result::Result where OuterOrigin: Into> { match o.into() { Ok(Origin::Parachain(id)) => Ok(id), _ => Err(BadOrigin), } } /// Ensure that double vote reports are only processed if valid. #[derive(Encode, Decode, Clone, Eq, PartialEq)] pub struct ValidateDoubleVoteReports(sp_std::marker::PhantomData); impl sp_std::fmt::Debug for ValidateDoubleVoteReports where { fn fmt(&self, f: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result { write!(f, "ValidateDoubleVoteReports") } } impl ValidateDoubleVoteReports { /// Create a new `ValidateDoubleVoteReports` struct. pub fn new() -> Self { ValidateDoubleVoteReports(sp_std::marker::PhantomData) } } /// 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 SignedExtension for ValidateDoubleVoteReports where ::Call: IsSubType, T> { const IDENTIFIER: &'static str = "ValidateDoubleVoteReports"; type AccountId = T::AccountId; type Call = ::Call; type AdditionalSigned = (); type Pre = (); fn additional_signed(&self) -> sp_std::result::Result { Ok(()) } fn validate( &self, _who: &Self::AccountId, call: &Self::Call, _info: &DispatchInfoOf, _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 = >::validators(); let expected_session = report.signing_context.session_index; let session = report.proof.session(); if session != expected_session { return Err(InvalidTransaction::BadProof.into()); } let authorities = Module::::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::() .map_err(|e| TransactionValidityError::from(InvalidTransaction::Custom(e as u8)))?; } } Ok(r) } } #[cfg(test)] mod tests { use super::*; use super::Call as ParachainsCall; use bitvec::{bitvec, vec::BitVec}; use sp_io::TestExternalities; use sp_core::{H256, Blake2Hasher, sr25519}; use sp_trie::NodeCodec; use sp_runtime::{ impl_opaque_keys, Perbill, curve::PiecewiseLinear, traits::{ BlakeTwo256, IdentityLookup, SaturatedConversion, OpaqueKeys, Extrinsic as ExtrinsicT, }, testing::TestXt, }; use primitives::{ parachain::{ CandidateReceipt, ValidityAttestation, ValidatorId, Info as ParaInfo, Scheduling, CandidateCommitments, }, BlockNumber, Header, }; use keyring::Sr25519Keyring; use frame_support::{ impl_outer_origin, impl_outer_dispatch, assert_ok, assert_err, parameter_types, traits::{OnInitialize, OnFinalize}, weights::DispatchInfo, }; use crate::parachains; use crate::registrar; use crate::slots; use session::{SessionHandler, SessionManager}; use staking::EraIndex; // result of as trie_db::NodeCodec>::hashed_null_node() const EMPTY_TRIE_ROOT: [u8; 32] = [ 3, 23, 10, 46, 117, 151, 183, 183, 227, 216, 76, 5, 57, 29, 19, 154, 98, 177, 87, 231, 135, 134, 216, 192, 130, 242, 157, 207, 76, 17, 19, 20 ]; impl_outer_origin! { pub enum Origin for Test { parachains } } impl_outer_dispatch! { pub enum Call for Test where origin: Origin { parachains::Parachains, staking::Staking, } } impl_opaque_keys! { pub struct TestSessionKeys { pub parachain_validator: super::Module, } } #[derive(Clone, Eq, PartialEq)] pub struct Test; parameter_types! { pub const BlockHashCount: u32 = 250; pub const MaximumBlockWeight: u32 = 4 * 1024 * 1024; pub const MaximumBlockLength: u32 = 4 * 1024 * 1024; pub const AvailableBlockRatio: Perbill = Perbill::from_percent(75); } impl system::Trait for Test { type Origin = Origin; type Call = Call; type Index = u64; type BlockNumber = BlockNumber; type Hash = H256; type Hashing = BlakeTwo256; type AccountId = u64; type Lookup = IdentityLookup; type Header = Header; type Event = (); type BlockHashCount = BlockHashCount; type MaximumBlockWeight = MaximumBlockWeight; type DbWeight = (); type BlockExecutionWeight = (); type ExtrinsicBaseWeight = (); type MaximumBlockLength = MaximumBlockLength; type AvailableBlockRatio = AvailableBlockRatio; type Version = (); type ModuleToIndex = (); type AccountData = balances::AccountData; type OnNewAccount = (); type OnKilledAccount = (); } impl system::offchain::SendTransactionTypes for Test where Call: From, { type OverarchingCall = Call; type Extrinsic = TestXt; } parameter_types! { pub const Period: BlockNumber = 1; pub const Offset: BlockNumber = 0; pub const DisabledValidatorsThreshold: Perbill = Perbill::from_percent(17); } /// Custom `SessionHandler` since we use `TestSessionKeys` as `Keys`. pub struct TestSessionHandler; impl SessionHandler for TestSessionHandler { const KEY_TYPE_IDS: &'static [KeyTypeId] = &[PARACHAIN_KEY_TYPE_ID]; fn on_genesis_session(_: &[(AId, Ks)]) {} fn on_new_session(_: bool, _: &[(AId, Ks)], _: &[(AId, Ks)]) {} fn on_before_session_ending() {} fn on_disabled(_: usize) {} } impl session::Trait for Test { type Event = (); type ValidatorId = u64; type ValidatorIdOf = staking::StashOf; type ShouldEndSession = session::PeriodicSessions; type NextSessionRotation = session::PeriodicSessions; type SessionManager = session::historical::NoteHistoricalRoot; type SessionHandler = TestSessionHandler; type Keys = TestSessionKeys; type DisabledValidatorsThreshold = DisabledValidatorsThreshold; } impl session::historical::Trait for Test { type FullIdentification = staking::Exposure; type FullIdentificationOf = staking::ExposureOf; } parameter_types! { pub const MinimumPeriod: u64 = 3; } impl timestamp::Trait for Test { type Moment = u64; type OnTimestampSet = (); type MinimumPeriod = MinimumPeriod; } mod time { use primitives::{Moment, BlockNumber}; pub const MILLISECS_PER_BLOCK: Moment = 6000; pub const EPOCH_DURATION_IN_BLOCKS: BlockNumber = 1 * HOURS; // These time units are defined in number of blocks. const MINUTES: BlockNumber = 60_000 / (MILLISECS_PER_BLOCK as BlockNumber); const HOURS: BlockNumber = MINUTES * 60; } parameter_types! { pub const EpochDuration: BlockNumber = time::EPOCH_DURATION_IN_BLOCKS; pub const ExpectedBlockTime: u64 = time::MILLISECS_PER_BLOCK; } impl babe::Trait for Test { type EpochDuration = EpochDuration; type ExpectedBlockTime = ExpectedBlockTime; // session module is the trigger type EpochChangeTrigger = babe::ExternalTrigger; } parameter_types! { pub const ExistentialDeposit: Balance = 1; } impl balances::Trait for Test { type Balance = u128; type DustRemoval = (); type Event = (); type ExistentialDeposit = ExistentialDeposit; type AccountStore = System; } pallet_staking_reward_curve::build! { const REWARD_CURVE: PiecewiseLinear<'static> = curve!( min_inflation: 0_025_000u64, max_inflation: 0_100_000, ideal_stake: 0_500_000, falloff: 0_050_000, max_piece_count: 40, test_precision: 0_005_000, ); } parameter_types! { pub const SessionsPerEra: sp_staking::SessionIndex = 3; pub const BondingDuration: staking::EraIndex = 3; pub const SlashDeferDuration: staking::EraIndex = 0; pub const AttestationPeriod: BlockNumber = 100; pub const RewardCurve: &'static PiecewiseLinear<'static> = &REWARD_CURVE; pub const MaxNominatorRewardedPerValidator: u32 = 64; pub const ElectionLookahead: BlockNumber = 0; pub const StakingUnsignedPriority: u64 = u64::max_value() / 2; } pub struct CurrencyToVoteHandler; impl Convert for CurrencyToVoteHandler { fn convert(x: u128) -> u128 { x } } impl Convert for CurrencyToVoteHandler { fn convert(x: u128) -> u64 { x.saturated_into() } } impl staking::Trait for Test { type RewardRemainder = (); type CurrencyToVote = CurrencyToVoteHandler; type Event = (); type Currency = Balances; type Slash = (); type Reward = (); type SessionsPerEra = SessionsPerEra; type BondingDuration = BondingDuration; type SlashDeferDuration = SlashDeferDuration; type SlashCancelOrigin = system::EnsureRoot; type SessionInterface = Self; type UnixTime = timestamp::Module; type RewardCurve = RewardCurve; type MaxNominatorRewardedPerValidator = MaxNominatorRewardedPerValidator; type NextNewSession = Session; type ElectionLookahead = ElectionLookahead; type Call = Call; type UnsignedPriority = StakingUnsignedPriority; } impl attestations::Trait for Test { type AttestationPeriod = AttestationPeriod; type ValidatorIdentities = ValidatorIdentities; type RewardAttestation = (); } parameter_types!{ pub const LeasePeriod: BlockNumber = 10; pub const EndingPeriod: BlockNumber = 3; } impl slots::Trait for Test { type Event = (); type Currency = Balances; type Parachains = registrar::Module; type EndingPeriod = EndingPeriod; type LeasePeriod = LeasePeriod; type Randomness = RandomnessCollectiveFlip; } parameter_types! { pub const ParathreadDeposit: Balance = 10; pub const QueueSize: usize = 2; pub const MaxRetries: u32 = 3; } impl registrar::Trait for Test { type Event = (); type Origin = Origin; type Currency = Balances; type ParathreadDeposit = ParathreadDeposit; type SwapAux = slots::Module; type QueueSize = QueueSize; type MaxRetries = MaxRetries; } impl offences::Trait for Test { type Event = (); type IdentificationTuple = session::historical::IdentificationTuple; type OnOffenceHandler = Staking; } parameter_types! { pub const MaxHeadDataSize: u32 = 100; pub const MaxCodeSize: u32 = 100; pub const ValidationUpgradeFrequency: BlockNumber = 10; pub const ValidationUpgradeDelay: BlockNumber = 2; pub const SlashPeriod: BlockNumber = 50; } // This is needed for a custom `AccountId` type which is `u64` in testing here. pub mod test_keys { use sp_core::{crypto::KeyTypeId, sr25519}; pub const KEY_TYPE: KeyTypeId = KeyTypeId(*b"test"); mod app { use sp_application_crypto::{app_crypto, sr25519}; use super::super::Parachains; app_crypto!(sr25519, super::KEY_TYPE); impl sp_runtime::traits::IdentifyAccount for Public { type AccountId = u64; fn into_account(self) -> Self::AccountId { Parachains::authorities().iter().position(|b| *b == self.0.clone().into()).unwrap() as u64 } } } pub type ReporterId = app::Public; pub struct ReporterAuthorityId; impl system::offchain::AppCrypto for ReporterAuthorityId { type RuntimeAppPublic = ReporterId; type GenericSignature = sr25519::Signature; type GenericPublic = sr25519::Public; } } impl Trait for Test { type AuthorityId = test_keys::ReporterAuthorityId; type Origin = Origin; type Call = Call; type ParachainCurrency = Balances; type BlockNumberConversion = sp_runtime::traits::Identity; type Randomness = RandomnessCollectiveFlip; type ActiveParachains = registrar::Module; type Registrar = registrar::Module; type MaxCodeSize = MaxCodeSize; type MaxHeadDataSize = MaxHeadDataSize; type ValidationUpgradeFrequency = ValidationUpgradeFrequency; type ValidationUpgradeDelay = ValidationUpgradeDelay; type SlashPeriod = SlashPeriod; type Proof = >::Proof; type IdentificationTuple = >::IdentificationTuple; type ReportOffence = Offences; type BlockHashConversion = sp_runtime::traits::Identity; type KeyOwnerProofSystem = Historical; } type Extrinsic = TestXt; impl system::offchain::CreateSignedTransaction for Test where Call: From, { fn create_transaction>( call: Call, _public: test_keys::ReporterId, _account: ::AccountId, nonce: ::Index, ) -> Option<(Call, ::SignaturePayload)> { Some((call, (nonce, ()))) } } impl system::offchain::SigningTypes for Test { type Public = test_keys::ReporterId; type Signature = sr25519::Signature; } type Parachains = Module; type Balances = balances::Module; type System = system::Module; type Offences = offences::Module; type Staking = staking::Module; type Session = session::Module; type Timestamp = timestamp::Module; type RandomnessCollectiveFlip = randomness_collective_flip::Module; type Registrar = registrar::Module; type Historical = session::historical::Module; fn new_test_ext(parachains: Vec<(ParaId, ValidationCode, HeadData)>) -> TestExternalities { use staking::StakerStatus; use babe::AuthorityId as BabeAuthorityId; let mut t = system::GenesisConfig::default().build_storage::().unwrap(); let authority_keys = [ Sr25519Keyring::Alice, Sr25519Keyring::Bob, Sr25519Keyring::Charlie, Sr25519Keyring::Dave, Sr25519Keyring::Eve, Sr25519Keyring::Ferdie, Sr25519Keyring::One, Sr25519Keyring::Two, ]; // stashes are the index. let session_keys: Vec<_> = authority_keys.iter().enumerate() .map(|(i, k)| (i as u64, i as u64, TestSessionKeys { parachain_validator: ValidatorId::from(k.public()), })) .collect(); let authorities: Vec<_> = authority_keys.iter().map(|k| ValidatorId::from(k.public())).collect(); let babe_authorities: Vec<_> = authority_keys.iter() .map(|k| BabeAuthorityId::from(k.public())) .map(|k| (k, 1)) .collect(); // controllers are the index + 1000 let stakers: Vec<_> = (0..authority_keys.len()).map(|i| ( i as u64, i as u64 + 1000, 10_000, StakerStatus::::Validator, )).collect(); let balances: Vec<_> = (0..authority_keys.len()).map(|i| (i as u64, 10_000_000)).collect(); GenesisConfig { authorities: authorities.clone(), }.assimilate_storage::(&mut t).unwrap(); registrar::GenesisConfig:: { parachains, _phdata: Default::default(), }.assimilate_storage(&mut t).unwrap(); session::GenesisConfig:: { keys: session_keys, }.assimilate_storage(&mut t).unwrap(); babe::GenesisConfig { authorities: babe_authorities, }.assimilate_storage::(&mut t).unwrap(); balances::GenesisConfig:: { balances, }.assimilate_storage(&mut t).unwrap(); staking::GenesisConfig:: { stakers, validator_count: 8, force_era: staking::Forcing::ForceNew, minimum_validator_count: 0, invulnerables: vec![], .. Default::default() }.assimilate_storage(&mut t).unwrap(); t.into() } fn set_heads(v: Vec) -> ParachainsCall { ParachainsCall::set_heads(v) } fn report_double_vote( report: DoubleVoteReport, ) -> Result, TransactionValidityError> { let inner = ParachainsCall::report_double_vote(report); let call = Call::Parachains(inner.clone()); ValidateDoubleVoteReports::(sp_std::marker::PhantomData) .validate(&0, &call, &DispatchInfo::default(), 0)?; Ok(inner) } // creates a template candidate which pins to correct relay-chain state. fn raw_candidate(para_id: ParaId) -> CandidateReceipt { let mut head_data = Parachains::parachain_head(¶_id).unwrap(); head_data.0.extend(para_id.encode()); CandidateReceipt { parachain_index: para_id, relay_parent: System::parent_hash(), head_data, collator: Default::default(), signature: Default::default(), pov_block_hash: Default::default(), global_validation: Parachains::global_validation_schedule(), local_validation: Parachains::current_local_validation_data(¶_id).unwrap(), commitments: CandidateCommitments::default(), } } // makes a blank attested candidate from a `CandidateReceipt`. fn make_blank_attested(candidate: CandidateReceipt) -> AttestedCandidate { let (candidate, _) = candidate.abridge(); AttestedCandidate { validity_votes: vec![], validator_indices: BitVec::new(), candidate, } } fn make_attestations(candidate: &mut AttestedCandidate) { let mut vote_implicit = false; let (duty_roster, _) = Parachains::calculate_duty_roster(); let candidate_hash = candidate.candidate.hash(); let authorities = Parachains::authorities(); let extract_key = |public: ValidatorId| { let mut raw_public = [0; 32]; raw_public.copy_from_slice(public.as_ref()); Sr25519Keyring::from_raw_public(raw_public).unwrap() }; let validation_entries = duty_roster.validator_duty.iter() .enumerate(); let mut validator_indices = BitVec::new(); for (idx, &duty) in validation_entries { if duty != Chain::Parachain(candidate.parachain_index()) { continue } vote_implicit = !vote_implicit; let key = extract_key(authorities[idx].clone()); let statement = if vote_implicit { Statement::Candidate(candidate_hash.clone()) } else { Statement::Valid(candidate_hash.clone()) }; let signing_context = Parachains::signing_context(); let payload = localized_payload(statement, &signing_context); let signature = key.sign(&payload[..]).into(); candidate.validity_votes.push(if vote_implicit { ValidityAttestation::Implicit(signature) } else { ValidityAttestation::Explicit(signature) }); if validator_indices.len() <= idx { validator_indices.resize(idx + 1, false); } validator_indices.set(idx, true); } candidate.validator_indices = validator_indices; } fn new_candidate_with_upward_messages( id: u32, upward_messages: Vec<(ParachainDispatchOrigin, Vec)> ) -> AttestedCandidate { let mut raw_candidate = raw_candidate(id.into()); raw_candidate.commitments.upward_messages = upward_messages.into_iter() .map(|x| UpwardMessage { origin: x.0, data: x.1 }) .collect(); make_blank_attested(raw_candidate) } fn start_session(session_index: SessionIndex) { let mut parent_hash = System::parent_hash(); for i in Session::current_index()..session_index { println!("session index {}", i); Staking::on_finalize(System::block_number()); System::set_block_number((i + 1).into()); Timestamp::set_timestamp(System::block_number() as primitives::Moment * 6000); // In order to be able to use `System::parent_hash()` in the tests // we need to first get it via `System::finalize` and then set it // the `System::initialize`. However, it is needed to be taken into // consideration that finalizing will prune some data in `System` // storage including old values `BlockHash` if that reaches above // `BlockHashCount` capacity. if System::block_number() > 1 { let hdr = System::finalize(); parent_hash = hdr.hash(); } System::initialize( &(i as BlockNumber + 1), &parent_hash, &Default::default(), &Default::default(), Default::default(), ); init_block(); } assert_eq!(Session::current_index(), session_index); } fn start_era(era_index: EraIndex) { start_session((era_index * 3).into()); assert_eq!(Staking::current_era(), Some(era_index)); } fn init_block() { println!("Initializing {}", System::block_number()); Session::on_initialize(System::block_number()); System::on_initialize(System::block_number()); Registrar::on_initialize(System::block_number()); Parachains::on_initialize(System::block_number()); } fn run_to_block(n: BlockNumber) { println!("Running until block {}", n); while System::block_number() < n { if System::block_number() > 1 { println!("Finalizing {}", System::block_number()); if !DidUpdate::get().is_some() { Parachains::set_heads(Origin::NONE, vec![]).unwrap(); } Parachains::on_finalize(System::block_number()); Registrar::on_finalize(System::block_number()); System::on_finalize(System::block_number()); } Staking::new_session(System::block_number() as u32); System::set_block_number(System::block_number() + 1); init_block(); } } fn queue_upward_messages(id: ParaId, upward_messages: &[UpwardMessage]) { NeedsDispatch::mutate(|nd| Parachains::queue_upward_messages(id, upward_messages, nd) ); } #[test] fn check_dispatch_upward_works() { let parachains = vec![ (0u32.into(), vec![].into(), vec![].into()), (1u32.into(), vec![].into(), vec![].into()), (2u32.into(), vec![].into(), vec![].into()), ]; new_test_ext(parachains.clone()).execute_with(|| { init_block(); queue_upward_messages(0.into(), &vec![ UpwardMessage { origin: ParachainDispatchOrigin::Parachain, data: vec![0; 4] } ]); queue_upward_messages(1.into(), &vec![ UpwardMessage { origin: ParachainDispatchOrigin::Parachain, data: vec![1; 4] } ]); let mut dispatched: Vec<(ParaId, ParachainDispatchOrigin, Vec)> = vec![]; let dummy = |id, origin, data: &[u8]| dispatched.push((id, origin, data.to_vec())); Parachains::dispatch_upward_messages(2, 3, dummy); assert_eq!(dispatched, vec![ (0.into(), ParachainDispatchOrigin::Parachain, vec![0; 4]) ]); assert!(::get(ParaId::from(0)).is_empty()); assert_eq!(::get(ParaId::from(1)).len(), 1); }); new_test_ext(parachains.clone()).execute_with(|| { init_block(); queue_upward_messages(0.into(), &vec![ UpwardMessage { origin: ParachainDispatchOrigin::Parachain, data: vec![0; 2] } ]); queue_upward_messages(1.into(), &vec![ UpwardMessage { origin: ParachainDispatchOrigin::Parachain, data: vec![1; 2] } ]); queue_upward_messages(2.into(), &vec![ UpwardMessage { origin: ParachainDispatchOrigin::Parachain, data: vec![2] } ]); let mut dispatched: Vec<(ParaId, ParachainDispatchOrigin, Vec)> = vec![]; let dummy = |id, origin, data: &[u8]| dispatched.push((id, origin, data.to_vec())); Parachains::dispatch_upward_messages(2, 3, dummy); assert_eq!(dispatched, vec![ (0.into(), ParachainDispatchOrigin::Parachain, vec![0; 2]), (2.into(), ParachainDispatchOrigin::Parachain, vec![2]) ]); assert!(::get(ParaId::from(0)).is_empty()); assert_eq!(::get(ParaId::from(1)).len(), 1); assert!(::get(ParaId::from(2)).is_empty()); }); new_test_ext(parachains.clone()).execute_with(|| { init_block(); queue_upward_messages(0.into(), &vec![ UpwardMessage { origin: ParachainDispatchOrigin::Parachain, data: vec![0; 2] } ]); queue_upward_messages(1.into(), &vec![ UpwardMessage { origin: ParachainDispatchOrigin::Parachain, data: vec![1; 2] } ]); queue_upward_messages(2.into(), &vec![ UpwardMessage { origin: ParachainDispatchOrigin::Parachain, data: vec![2] } ]); let mut dispatched: Vec<(ParaId, ParachainDispatchOrigin, Vec)> = vec![]; let dummy = |id, origin, data: &[u8]| dispatched.push((id, origin, data.to_vec())); Parachains::dispatch_upward_messages(2, 3, dummy); assert_eq!(dispatched, vec![ (0.into(), ParachainDispatchOrigin::Parachain, vec![0; 2]), (2.into(), ParachainDispatchOrigin::Parachain, vec![2]) ]); assert!(::get(ParaId::from(0)).is_empty()); assert_eq!(::get(ParaId::from(1)).len(), 1); assert!(::get(ParaId::from(2)).is_empty()); }); new_test_ext(parachains.clone()).execute_with(|| { init_block(); queue_upward_messages(0.into(), &vec![ UpwardMessage { origin: ParachainDispatchOrigin::Parachain, data: vec![0; 2] } ]); queue_upward_messages(1.into(), &vec![ UpwardMessage { origin: ParachainDispatchOrigin::Parachain, data: vec![1; 2] } ]); queue_upward_messages(2.into(), &vec![ UpwardMessage { origin: ParachainDispatchOrigin::Parachain, data: vec![2] } ]); let mut dispatched: Vec<(ParaId, ParachainDispatchOrigin, Vec)> = vec![]; let dummy = |id, origin, data: &[u8]| dispatched.push((id, origin, data.to_vec())); Parachains::dispatch_upward_messages(2, 3, dummy); assert_eq!(dispatched, vec![ (0.into(), ParachainDispatchOrigin::Parachain, vec![0; 2]), (2.into(), ParachainDispatchOrigin::Parachain, vec![2]), ]); assert!(::get(ParaId::from(0)).is_empty()); assert_eq!(::get(ParaId::from(1)).len(), 1); assert!(::get(ParaId::from(2)).is_empty()); }); } #[test] fn check_queue_upward_messages_works() { let parachains = vec![ (0u32.into(), vec![].into(), vec![].into()), ]; new_test_ext(parachains.clone()).execute_with(|| { run_to_block(2); let messages = vec![ UpwardMessage { origin: ParachainDispatchOrigin::Signed, data: vec![0] } ]; assert_ok!(Parachains::check_upward_messages(0.into(), &messages, 2, 3)); // all good. queue_upward_messages(0.into(), &vec![ UpwardMessage { origin: ParachainDispatchOrigin::Signed, data: vec![0] }, ]); let messages = vec![ UpwardMessage { origin: ParachainDispatchOrigin::Parachain, data: vec![1, 2] } ]; assert_ok!(Parachains::check_upward_messages(0.into(), &messages, 2, 3)); queue_upward_messages(0.into(), &messages); assert_eq!(::get(ParaId::from(0)), vec![ UpwardMessage { origin: ParachainDispatchOrigin::Signed, data: vec![0] }, UpwardMessage { origin: ParachainDispatchOrigin::Parachain, data: vec![1, 2] }, ]); }); } #[test] fn check_queue_full_upward_messages_fails() { let parachains = vec![ (0u32.into(), vec![].into(), vec![].into()), ]; new_test_ext(parachains.clone()).execute_with(|| { run_to_block(2); // oversize, but ok since it's just one and the queue is empty. let messages = vec![ UpwardMessage { origin: ParachainDispatchOrigin::Signed, data: vec![0; 4] }, ]; assert_ok!(Parachains::check_upward_messages(0.into(), &messages, 2, 3)); // oversize and bad since it's not just one. let messages = vec![ UpwardMessage { origin: ParachainDispatchOrigin::Signed, data: vec![0] }, UpwardMessage { origin: ParachainDispatchOrigin::Signed, data: vec![0; 4] }, ]; assert_err!( Parachains::check_upward_messages(0.into(), &messages, 2, 3), Error::::QueueFull ); // too many messages. let messages = vec![ UpwardMessage { origin: ParachainDispatchOrigin::Signed, data: vec![0] }, UpwardMessage { origin: ParachainDispatchOrigin::Signed, data: vec![1] }, UpwardMessage { origin: ParachainDispatchOrigin::Signed, data: vec![2] }, ]; assert_err!( Parachains::check_upward_messages(0.into(), &messages, 2, 3), Error::::QueueFull ); }); } #[test] fn check_queued_too_many_upward_messages_fails() { let parachains = vec![ (0u32.into(), vec![].into(), vec![].into()), ]; new_test_ext(parachains.clone()).execute_with(|| { run_to_block(2); // too many messages. queue_upward_messages(0.into(), &vec![ UpwardMessage { origin: ParachainDispatchOrigin::Signed, data: vec![0] }, ]); let messages = vec![ UpwardMessage { origin: ParachainDispatchOrigin::Signed, data: vec![1] }, UpwardMessage { origin: ParachainDispatchOrigin::Signed, data: vec![2] }, ]; assert_err!( Parachains::check_upward_messages(0.into(), &messages, 2, 3), Error::::QueueFull ); }); } #[test] fn check_queued_total_oversize_upward_messages_fails() { let parachains = vec![ (0u32.into(), vec![].into(), vec![].into()), ]; new_test_ext(parachains.clone()).execute_with(|| { run_to_block(2); // too much data. queue_upward_messages(0.into(), &vec![ UpwardMessage { origin: ParachainDispatchOrigin::Signed, data: vec![0, 1] }, ]); let messages = vec![ UpwardMessage { origin: ParachainDispatchOrigin::Signed, data: vec![2, 3] }, ]; assert_err!( Parachains::check_upward_messages(0.into(), &messages, 2, 3), Error::::QueueFull ); }); } #[test] fn check_queued_pre_jumbo_upward_messages_fails() { let parachains = vec![ (0u32.into(), vec![].into(), vec![].into()), ]; new_test_ext(parachains.clone()).execute_with(|| { run_to_block(2); // bad - already an oversize messages queued. queue_upward_messages(0.into(), &vec![ UpwardMessage { origin: ParachainDispatchOrigin::Signed, data: vec![0; 4] }, ]); let messages = vec![ UpwardMessage { origin: ParachainDispatchOrigin::Signed, data: vec![0] } ]; assert_err!( Parachains::check_upward_messages(0.into(), &messages, 2, 3), Error::::QueueFull ); }); } #[test] fn check_queued_post_jumbo_upward_messages_fails() { let parachains = vec![ (0u32.into(), vec![].into(), vec![].into()), ]; new_test_ext(parachains.clone()).execute_with(|| { run_to_block(2); // bad - oversized and already a message queued. queue_upward_messages(0.into(), &vec![ UpwardMessage { origin: ParachainDispatchOrigin::Signed, data: vec![0] }, ]); let messages = vec![ UpwardMessage { origin: ParachainDispatchOrigin::Signed, data: vec![0; 4] } ]; assert_err!( Parachains::check_upward_messages(0.into(), &messages, 2, 3), Error::::QueueFull ); }); } #[test] fn upward_queuing_works() { // That the list of egress queue roots is in ascending order by `ParaId`. let parachains = vec![ (0u32.into(), vec![].into(), vec![].into()), (1u32.into(), vec![].into(), vec![].into()), ]; new_test_ext(parachains.clone()).execute_with(|| { run_to_block(2); // parachain 0 is self let mut candidates = vec![ new_candidate_with_upward_messages(0, vec![ (ParachainDispatchOrigin::Signed, vec![1]), ]), new_candidate_with_upward_messages(1, vec![ (ParachainDispatchOrigin::Parachain, vec![2]), ]) ]; candidates.iter_mut().for_each(make_attestations); assert_ok!(Parachains::dispatch( set_heads(candidates), Origin::NONE, )); assert!(::get(ParaId::from(0)).is_empty()); assert!(::get(ParaId::from(1)).is_empty()); }); } #[test] fn active_parachains_should_work() { let parachains = vec![ (5u32.into(), vec![1,2,3].into(), vec![1].into()), (100u32.into(), vec![4,5,6].into(), vec![2].into()), ]; new_test_ext(parachains.clone()).execute_with(|| { run_to_block(2); assert_eq!(Parachains::active_parachains(), vec![(5u32.into(), None), (100u32.into(), None)]); assert_eq!(Parachains::parachain_code(ParaId::from(5u32)), Some(vec![1, 2, 3].into())); assert_eq!(Parachains::parachain_code(ParaId::from(100u32)), Some(vec![4, 5, 6].into())); }); } #[test] fn register_deregister() { let parachains = vec![ (5u32.into(), vec![1,2,3].into(), vec![1].into()), (100u32.into(), vec![4,5,6].into(), vec![2,].into()), ]; new_test_ext(parachains.clone()).execute_with(|| { run_to_block(2); assert_eq!(Parachains::active_parachains(), vec![(5u32.into(), None), (100u32.into(), None)]); assert_eq!(Parachains::parachain_code(ParaId::from(5u32)), Some(vec![1,2,3].into())); assert_eq!(Parachains::parachain_code(ParaId::from(100u32)), Some(vec![4,5,6].into())); assert_ok!(Registrar::register_para( Origin::ROOT, 99u32.into(), ParaInfo{scheduling: Scheduling::Always}, vec![7,8,9].into(), vec![1, 1, 1].into(), )); assert_ok!(Parachains::set_heads(Origin::NONE, vec![])); run_to_block(3); assert_eq!(Parachains::active_parachains(), vec![(5u32.into(), None), (99u32.into(), None), (100u32.into(), None)]); assert_eq!(Parachains::parachain_code(&ParaId::from(99u32)), Some(vec![7,8,9].into())); assert_ok!(Registrar::deregister_para(Origin::ROOT, 5u32.into())); assert_ok!(Parachains::set_heads(Origin::NONE, vec![])); // parachain still active this block. another block must pass before it's inactive. run_to_block(4); assert_eq!(Parachains::active_parachains(), vec![(99u32.into(), None), (100u32.into(), None)]); assert_eq!(Parachains::parachain_code(&ParaId::from(5u32)), None); }); } #[test] fn duty_roster_works() { let parachains = vec![ (0u32.into(), vec![].into(), vec![].into()), (1u32.into(), vec![].into(), vec![].into()), ]; new_test_ext(parachains.clone()).execute_with(|| { run_to_block(2); let check_roster = |duty_roster: &DutyRoster| { assert_eq!(duty_roster.validator_duty.len(), 8); for i in (0..2).map(ParaId::from) { assert_eq!(duty_roster.validator_duty.iter().filter(|&&j| j == Chain::Parachain(i)).count(), 3); } assert_eq!(duty_roster.validator_duty.iter().filter(|&&j| j == Chain::Relay).count(), 2); }; let duty_roster_0 = Parachains::calculate_duty_roster().0; check_roster(&duty_roster_0); System::initialize(&1, &H256::from([1; 32]), &Default::default(), &Default::default(), Default::default()); RandomnessCollectiveFlip::on_initialize(1); let duty_roster_1 = Parachains::calculate_duty_roster().0; check_roster(&duty_roster_1); assert_ne!(duty_roster_0, duty_roster_1); System::initialize(&2, &H256::from([2; 32]), &Default::default(), &Default::default(), Default::default()); RandomnessCollectiveFlip::on_initialize(2); let duty_roster_2 = Parachains::calculate_duty_roster().0; check_roster(&duty_roster_2); assert_ne!(duty_roster_0, duty_roster_2); assert_ne!(duty_roster_1, duty_roster_2); }); } #[test] fn unattested_candidate_is_rejected() { let parachains = vec![ (0u32.into(), vec![].into(), vec![].into()), (1u32.into(), vec![].into(), vec![].into()), ]; new_test_ext(parachains.clone()).execute_with(|| { run_to_block(2); let candidate = make_blank_attested(raw_candidate(0.into())); assert!(Parachains::dispatch(set_heads(vec![candidate]), Origin::NONE).is_err()); }) } #[test] fn attested_candidates_accepted_in_order() { let parachains = vec![ (0u32.into(), vec![].into(), vec![].into()), (1u32.into(), vec![].into(), vec![].into()), ]; new_test_ext(parachains.clone()).execute_with(|| { run_to_block(2); assert_eq!(Parachains::active_parachains().len(), 2); let mut candidate_a = make_blank_attested(raw_candidate(0.into())); let mut candidate_b = make_blank_attested(raw_candidate(1.into())); make_attestations(&mut candidate_a); make_attestations(&mut candidate_b); assert!(Parachains::dispatch( set_heads(vec![candidate_b.clone(), candidate_a.clone()]), Origin::NONE, ).is_err()); assert_ok!(Parachains::dispatch( set_heads(vec![candidate_a.clone(), candidate_b.clone()]), Origin::NONE, )); assert_eq!(Heads::get(&ParaId::from(0)), Some(candidate_a.candidate.head_data)); assert_eq!(Heads::get(&ParaId::from(1)), Some(candidate_b.candidate.head_data)); }); } #[test] fn duplicate_vote_is_rejected() { let parachains = vec![ (0u32.into(), vec![].into(), vec![].into()), (1u32.into(), vec![].into(), vec![].into()), ]; new_test_ext(parachains.clone()).execute_with(|| { run_to_block(2); let mut candidate = make_blank_attested(raw_candidate(0.into())); make_attestations(&mut candidate); let mut double_validity = candidate.clone(); double_validity.validity_votes.push(candidate.validity_votes[0].clone()); double_validity.validator_indices.push(true); assert!(Parachains::dispatch( set_heads(vec![double_validity]), Origin::NONE, ).is_err()); }); } #[test] fn validators_not_from_group_is_rejected() { let parachains = vec![ (0u32.into(), vec![].into(), vec![].into()), (1u32.into(), vec![].into(), vec![].into()), ]; new_test_ext(parachains.clone()).execute_with(|| { run_to_block(2); let mut candidate = make_blank_attested(raw_candidate(0.into())); make_attestations(&mut candidate); // Change the last vote index to make it not corresponding to the assigned group. assert!(candidate.validator_indices.pop().is_some()); candidate.validator_indices.append(&mut bitvec![0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]); assert!(Parachains::dispatch( set_heads(vec![candidate]), Origin::NONE, ).is_err()); }); } #[test] fn empty_trie_root_const_is_blake2_hashed_null_node() { let hashed_null_node = as trie_db::NodeCodec>::hashed_null_node(); assert_eq!(hashed_null_node, EMPTY_TRIE_ROOT.into()) } #[test] fn para_past_code_meta_gives_right_code() { let mut past_code = ParaPastCodeMeta::default(); assert_eq!(past_code.code_at(0u32), Some(UseCodeAt::Current)); past_code.note_replacement(10); assert_eq!(past_code.code_at(0), Some(UseCodeAt::ReplacedAt(10))); assert_eq!(past_code.code_at(10), Some(UseCodeAt::ReplacedAt(10))); assert_eq!(past_code.code_at(11), Some(UseCodeAt::Current)); past_code.note_replacement(20); assert_eq!(past_code.code_at(1), Some(UseCodeAt::ReplacedAt(10))); assert_eq!(past_code.code_at(10), Some(UseCodeAt::ReplacedAt(10))); assert_eq!(past_code.code_at(11), Some(UseCodeAt::ReplacedAt(20))); assert_eq!(past_code.code_at(20), Some(UseCodeAt::ReplacedAt(20))); assert_eq!(past_code.code_at(21), Some(UseCodeAt::Current)); past_code.last_pruned = Some(5); assert_eq!(past_code.code_at(1), None); assert_eq!(past_code.code_at(5), None); assert_eq!(past_code.code_at(6), Some(UseCodeAt::ReplacedAt(10))); } #[test] fn para_past_code_pruning_works_correctly() { let mut past_code = ParaPastCodeMeta::default(); past_code.note_replacement(10u32); past_code.note_replacement(20); past_code.note_replacement(30); let old = past_code.clone(); assert!(past_code.prune_up_to(9).collect::>().is_empty()); assert_eq!(old, past_code); assert_eq!(past_code.prune_up_to(10).collect::>(), vec![10]); assert_eq!(past_code, ParaPastCodeMeta { upgrade_times: vec![30, 20], last_pruned: Some(10), }); assert_eq!(past_code.prune_up_to(21).collect::>(), vec![20]); assert_eq!(past_code, ParaPastCodeMeta { upgrade_times: vec![30], last_pruned: Some(20), }); past_code.note_replacement(40); past_code.note_replacement(50); past_code.note_replacement(60); assert_eq!(past_code, ParaPastCodeMeta { upgrade_times: vec![60, 50, 40, 30], last_pruned: Some(20), }); assert_eq!(past_code.prune_up_to(60).collect::>(), vec![30, 40, 50, 60]); assert_eq!(past_code, ParaPastCodeMeta { upgrade_times: Vec::new(), last_pruned: Some(60), }); } #[test] fn para_past_code_pruning_in_initialize() { let parachains = vec![ (0u32.into(), vec![].into(), vec![].into()), (1u32.into(), vec![].into(), vec![].into()), ]; new_test_ext(parachains.clone()).execute_with(|| { let id = ParaId::from(0u32); let at_block: BlockNumber = 10; ::PastCode::insert(&(id, at_block), &ValidationCode(vec![1, 2, 3])); ::PastCodePruning::put(&vec![(id, at_block)]); { let mut code_meta = Parachains::past_code_meta(&id); code_meta.note_replacement(at_block); ::PastCodeMeta::insert(&id, &code_meta); } let pruned_at: BlockNumber = at_block + SlashPeriod::get() + 1; assert_eq!(::PastCode::get(&(id, at_block)), Some(vec![1, 2, 3].into())); run_to_block(pruned_at - 1); assert_eq!(::PastCode::get(&(id, at_block)), Some(vec![1, 2, 3].into())); assert_eq!(Parachains::past_code_meta(&id).most_recent_change(), Some(at_block)); run_to_block(pruned_at); assert!(::PastCode::get(&(id, at_block)).is_none()); assert!(Parachains::past_code_meta(&id).most_recent_change().is_none()); }); } #[test] fn note_past_code_sets_up_pruning_correctly() { let parachains = vec![ (0u32.into(), vec![].into(), vec![].into()), (1u32.into(), vec![].into(), vec![].into()), ]; new_test_ext(parachains.clone()).execute_with(|| { let id_a = ParaId::from(0u32); let id_b = ParaId::from(1u32); Parachains::note_past_code(id_a, 10, vec![1, 2, 3].into()); Parachains::note_past_code(id_b, 20, vec![4, 5, 6].into()); assert_eq!(Parachains::past_code_pruning_tasks(), vec![(id_a, 10), (id_b, 20)]); assert_eq!( Parachains::past_code_meta(&id_a), ParaPastCodeMeta { upgrade_times: vec![10], last_pruned: None, } ); assert_eq!( Parachains::past_code_meta(&id_b), ParaPastCodeMeta { upgrade_times: vec![20], last_pruned: None, } ); }); } #[test] fn code_upgrade_applied_after_delay() { let parachains = vec![ (0u32.into(), vec![1, 2, 3].into(), vec![].into()), ]; new_test_ext(parachains.clone()).execute_with(|| { let para_id = ParaId::from(0); let new_code = ValidationCode(vec![4, 5, 6]); run_to_block(2); assert_eq!(Parachains::active_parachains().len(), 1); assert_eq!(Parachains::parachain_code(¶_id), Some(vec![1, 2, 3].into())); let applied_after ={ let raw_candidate = raw_candidate(para_id); let applied_after = raw_candidate.local_validation.code_upgrade_allowed.unwrap(); let mut candidate_a = make_blank_attested(raw_candidate); candidate_a.candidate.commitments.new_validation_code = Some(new_code.clone()); // this parablock is in the context of block 1. assert_eq!(applied_after, 1 + ValidationUpgradeDelay::get()); make_attestations(&mut candidate_a); assert_ok!(Parachains::dispatch( set_heads(vec![candidate_a.clone()]), Origin::NONE, )); assert!(Parachains::past_code_meta(¶_id).most_recent_change().is_none()); assert_eq!(Parachains::code_upgrade_schedule(¶_id), Some(applied_after)); assert_eq!(::FutureCode::get(¶_id), new_code); assert_eq!(Parachains::parachain_code(¶_id), Some(vec![1, 2, 3].into())); applied_after }; run_to_block(applied_after); // the candidate is in the context of the parent of `applied_after`, // thus does not trigger the code upgrade. { let raw_candidate = raw_candidate(para_id); assert!(raw_candidate.local_validation.code_upgrade_allowed.is_none()); let mut candidate_a = make_blank_attested(raw_candidate); make_attestations(&mut candidate_a); assert_ok!(Parachains::dispatch( set_heads(vec![candidate_a.clone()]), Origin::NONE, )); assert!(Parachains::past_code_meta(¶_id).most_recent_change().is_none()); assert_eq!(Parachains::code_upgrade_schedule(¶_id), Some(applied_after)); assert_eq!(::FutureCode::get(¶_id), new_code); assert_eq!(Parachains::parachain_code(¶_id), Some(vec![1, 2, 3].into())); } run_to_block(applied_after + 1); // the candidate is in the context of `applied_after`, and triggers // the upgrade. { let raw_candidate = raw_candidate(para_id); assert!(raw_candidate.local_validation.code_upgrade_allowed.is_some()); let mut candidate_a = make_blank_attested(raw_candidate); make_attestations(&mut candidate_a); assert_ok!(Parachains::dispatch( set_heads(vec![candidate_a.clone()]), Origin::NONE, )); assert_eq!( Parachains::past_code_meta(¶_id).most_recent_change(), Some(applied_after), ); assert_eq!( ::PastCode::get(&(para_id, applied_after)), Some(vec![1, 2, 3,].into()), ); assert!(Parachains::code_upgrade_schedule(¶_id).is_none()); assert!(::FutureCode::get(¶_id).0.is_empty()); assert_eq!(Parachains::parachain_code(¶_id), Some(new_code)); } }); } #[test] fn code_upgrade_applied_after_delay_even_when_late() { let parachains = vec![ (0u32.into(), vec![1, 2, 3].into(), vec![].into()), ]; new_test_ext(parachains.clone()).execute_with(|| { let para_id = ParaId::from(0); let new_code = ValidationCode(vec![4, 5, 6]); run_to_block(2); assert_eq!(Parachains::active_parachains().len(), 1); assert_eq!(Parachains::parachain_code(¶_id), Some(vec![1, 2, 3].into())); let applied_after ={ let raw_candidate = raw_candidate(para_id); let applied_after = raw_candidate.local_validation.code_upgrade_allowed.unwrap(); let mut candidate_a = make_blank_attested(raw_candidate); candidate_a.candidate.commitments.new_validation_code = Some(new_code.clone()); // this parablock is in the context of block 1. assert_eq!(applied_after, 1 + ValidationUpgradeDelay::get()); make_attestations(&mut candidate_a); assert_ok!(Parachains::dispatch( set_heads(vec![candidate_a.clone()]), Origin::NONE, )); assert!(Parachains::past_code_meta(¶_id).most_recent_change().is_none()); assert_eq!(Parachains::code_upgrade_schedule(¶_id), Some(applied_after)); assert_eq!(::FutureCode::get(¶_id), new_code); assert_eq!(Parachains::parachain_code(¶_id), Some(vec![1, 2, 3].into())); applied_after }; run_to_block(applied_after + 1 + 4); { let raw_candidate = raw_candidate(para_id); assert!(raw_candidate.local_validation.code_upgrade_allowed.is_some()); let mut candidate_a = make_blank_attested(raw_candidate); make_attestations(&mut candidate_a); assert_ok!(Parachains::dispatch( set_heads(vec![candidate_a.clone()]), Origin::NONE, )); assert_eq!( Parachains::past_code_meta(¶_id).most_recent_change(), Some(applied_after + 4), ); assert_eq!( ::PastCode::get(&(para_id, applied_after + 4)), Some(vec![1, 2, 3,].into()), ); assert!(Parachains::code_upgrade_schedule(¶_id).is_none()); assert!(::FutureCode::get(¶_id).0.is_empty()); assert_eq!(Parachains::parachain_code(¶_id), Some(new_code)); } }); } #[test] fn submit_code_change_when_not_allowed_is_err() { let parachains = vec![ (0u32.into(), vec![1, 2, 3].into(), vec![].into()), ]; new_test_ext(parachains.clone()).execute_with(|| { let para_id = ParaId::from(0); let new_code = ValidationCode(vec![4, 5, 6]); run_to_block(2); { let raw_candidate = raw_candidate(para_id); let mut candidate_a = make_blank_attested(raw_candidate); candidate_a.candidate.commitments.new_validation_code = Some(new_code.clone()); make_attestations(&mut candidate_a); assert_ok!(Parachains::dispatch( set_heads(vec![candidate_a.clone()]), Origin::NONE, )); }; run_to_block(3); { let raw_candidate = raw_candidate(para_id); assert!(raw_candidate.local_validation.code_upgrade_allowed.is_none()); let mut candidate_a = make_blank_attested(raw_candidate); candidate_a.candidate.commitments.new_validation_code = Some(vec![1, 2, 3].into()); make_attestations(&mut candidate_a); assert_err!( Parachains::dispatch( set_heads(vec![candidate_a.clone()]), Origin::NONE, ), Error::::DisallowedCodeUpgrade, ); } }); } #[test] fn full_parachain_cleanup_storage() { let parachains = vec![ (0u32.into(), vec![1, 2, 3].into(), vec![].into()), ]; new_test_ext(parachains.clone()).execute_with(|| { let para_id = ParaId::from(0); let new_code = ValidationCode(vec![4, 5, 6]); run_to_block(2); { let raw_candidate = raw_candidate(para_id); let applied_after = raw_candidate.local_validation.code_upgrade_allowed.unwrap(); let mut candidate_a = make_blank_attested(raw_candidate); candidate_a.candidate.commitments.new_validation_code = Some(new_code.clone()); // this parablock is in the context of block 1. assert_eq!(applied_after, 1 + ValidationUpgradeDelay::get()); make_attestations(&mut candidate_a); assert_ok!(Parachains::dispatch( set_heads(vec![candidate_a.clone()]), Origin::NONE, )); assert!(Parachains::past_code_meta(¶_id).most_recent_change().is_none()); assert_eq!(Parachains::code_upgrade_schedule(¶_id), Some(applied_after)); assert_eq!(::FutureCode::get(¶_id), new_code); assert_eq!(Parachains::parachain_code(¶_id), Some(vec![1, 2, 3].into())); assert!(Parachains::past_code_pruning_tasks().is_empty()); }; Parachains::cleanup_para(para_id); // cleaning up the parachain should place the current parachain code // into the past code buffer & schedule cleanup. assert_eq!(Parachains::past_code_meta(¶_id).most_recent_change(), Some(2)); assert_eq!(::PastCode::get(&(para_id, 2)), Some(vec![1, 2, 3].into())); assert_eq!(Parachains::past_code_pruning_tasks(), vec![(para_id, 2)]); // any future upgrades haven't been used to validate yet, so those // are cleaned up immediately. assert!(Parachains::code_upgrade_schedule(¶_id).is_none()); assert!(::FutureCode::get(¶_id).0.is_empty()); assert!(Parachains::parachain_code(¶_id).is_none()); let cleaned_up_at = 2 + SlashPeriod::get() + 1; run_to_block(cleaned_up_at); // now the final cleanup: last past code cleaned up, and this triggers meta cleanup. assert_eq!(Parachains::past_code_meta(¶_id), Default::default()); assert!(::PastCode::get(&(para_id, 2)).is_none()); assert!(Parachains::past_code_pruning_tasks().is_empty()); }); } #[test] fn double_vote_candidate_and_valid_works() { let parachains = vec![ (1u32.into(), vec![].into(), vec![].into()), ]; let extract_key = |public: ValidatorId| { let mut raw_public = [0; 32]; raw_public.copy_from_slice(public.as_ref()); Sr25519Keyring::from_raw_public(raw_public).unwrap() }; // Test that a Candidate and Valid statements on the same candidate get slashed. new_test_ext(parachains.clone()).execute_with(|| { assert_eq!(Staking::current_era(), Some(0)); assert_eq!(Session::current_index(), 0); start_era(1); let candidate = raw_candidate(1.into()).abridge().0; let candidate_hash = candidate.hash(); let authorities = Parachains::authorities(); let authority_index = 0; let key = extract_key(authorities[authority_index].clone()); let statement_candidate = Statement::Candidate(candidate_hash.clone()); let statement_valid = Statement::Valid(candidate_hash.clone()); let signing_context = Parachains::signing_context(); let payload_1 = localized_payload(statement_candidate.clone(), &signing_context); let payload_2 = localized_payload(statement_valid.clone(), &signing_context); let signature_1 = key.sign(&payload_1[..]).into(); let signature_2 = key.sign(&payload_2[..]).into(); // Check that in the beginning the genesis balances are there. for i in 0..authorities.len() { assert_eq!(Balances::total_balance(&(i as u64)), 10_000_000); assert_eq!(Staking::slashable_balance_of(&(i as u64)), 10_000); assert_eq!( Staking::eras_stakers(1, i as u64), staking::Exposure { total: 10_000, own: 10_000, others: vec![], }, ); } let encoded_key = key.encode(); let proof = Historical::prove((PARACHAIN_KEY_TYPE_ID, &encoded_key[..])).unwrap(); let report = DoubleVoteReport { identity: ValidatorId::from(key.public()), first: (statement_candidate, signature_1), second: (statement_valid, signature_2), proof, signing_context, }; let inner = report_double_vote(report).unwrap(); assert_ok!(Parachains::dispatch(inner, Origin::signed(1))); start_era(2); // Check that the balance of 0-th validator is slashed 100%. assert_eq!(Balances::total_balance(&0), 10_000_000 - 10_000); assert_eq!(Staking::slashable_balance_of(&0), 0); assert_eq!( Staking::eras_stakers(2, 0), staking::Exposure { total: 0, own: 0, others: vec![], }, ); // Check that the balances of all other validators are left intact. for i in 1..authorities.len() { assert_eq!(Balances::total_balance(&(i as u64)), 10_000_000); assert_eq!(Staking::slashable_balance_of(&(i as u64)), 10_000); assert_eq!( Staking::eras_stakers(2, i as u64), staking::Exposure { total: 10_000, own: 10_000, others: vec![], }, ); } }); } #[test] fn double_vote_candidate_and_invalid_works() { let parachains = vec![ (1u32.into(), vec![].into(), vec![].into()), ]; let extract_key = |public: ValidatorId| { let mut raw_public = [0; 32]; raw_public.copy_from_slice(public.as_ref()); Sr25519Keyring::from_raw_public(raw_public).unwrap() }; // Test that a Candidate and Invalid statements on the same candidate get slashed. new_test_ext(parachains.clone()).execute_with(|| { start_era(1); let candidate = raw_candidate(1.into()).abridge().0; let candidate_hash = candidate.hash(); let authorities = Parachains::authorities(); let authority_index = 0; let key = extract_key(authorities[authority_index].clone()); let statement_candidate = Statement::Candidate(candidate_hash); let statement_invalid = Statement::Invalid(candidate_hash.clone()); let signing_context = Parachains::signing_context(); let payload_1 = localized_payload(statement_candidate.clone(), &signing_context); let payload_2 = localized_payload(statement_invalid.clone(), &signing_context); let signature_1 = key.sign(&payload_1[..]).into(); let signature_2 = key.sign(&payload_2[..]).into(); // Check that in the beginning the genesis balances are there. for i in 0..authorities.len() { assert_eq!(Balances::total_balance(&(i as u64)), 10_000_000); assert_eq!(Staking::slashable_balance_of(&(i as u64)), 10_000); assert_eq!( Staking::eras_stakers(1, i as u64), staking::Exposure { total: 10_000, own: 10_000, others: vec![], }, ); } let encoded_key = key.encode(); let proof = Historical::prove((PARACHAIN_KEY_TYPE_ID, &encoded_key[..])).unwrap(); let report = DoubleVoteReport { identity: ValidatorId::from(key.public()), first: (statement_candidate, signature_1), second: (statement_invalid, signature_2), proof, signing_context, }; assert_ok!(Parachains::dispatch( report_double_vote(report).unwrap(), Origin::signed(1), )); start_era(2); // Check that the balance of 0-th validator is slashed 100%. assert_eq!(Balances::total_balance(&0), 10_000_000 - 10_000); assert_eq!(Staking::slashable_balance_of(&0), 0); assert_eq!( Staking::eras_stakers(Staking::current_era().unwrap(), 0), staking::Exposure { total: 0, own: 0, others: vec![], }, ); // Check that the balances of all other validators are left intact. for i in 1..authorities.len() { assert_eq!(Balances::total_balance(&(i as u64)), 10_000_000); assert_eq!(Staking::slashable_balance_of(&(i as u64)), 10_000); assert_eq!( Staking::eras_stakers(2, i as u64), staking::Exposure { total: 10_000, own: 10_000, others: vec![], }, ); } }); } #[test] fn double_vote_valid_and_invalid_works() { let parachains = vec![ (1u32.into(), vec![].into(), vec![].into()), ]; let extract_key = |public: ValidatorId| { let mut raw_public = [0; 32]; raw_public.copy_from_slice(public.as_ref()); Sr25519Keyring::from_raw_public(raw_public).unwrap() }; // Test that an Invalid and Valid statements on the same candidate get slashed. new_test_ext(parachains.clone()).execute_with(|| { start_era(1); let candidate = raw_candidate(1.into()).abridge().0; let candidate_hash = candidate.hash(); let authorities = Parachains::authorities(); let authority_index = 0; let key = extract_key(authorities[authority_index].clone()); let statement_invalid = Statement::Invalid(candidate_hash.clone()); let statement_valid = Statement::Valid(candidate_hash.clone()); let signing_context = Parachains::signing_context(); let payload_1 = localized_payload(statement_invalid.clone(), &signing_context); let payload_2 = localized_payload(statement_valid.clone(), &signing_context); let signature_1 = key.sign(&payload_1[..]).into(); let signature_2 = key.sign(&payload_2[..]).into(); // Check that in the beginning the genesis balances are there. for i in 0..authorities.len() { assert_eq!(Balances::total_balance(&(i as u64)), 10_000_000); assert_eq!(Staking::slashable_balance_of(&(i as u64)), 10_000); assert_eq!( Staking::eras_stakers(1, i as u64), staking::Exposure { total: 10_000, own: 10_000, others: vec![], }, ); } let encoded_key = key.encode(); let proof = Historical::prove((PARACHAIN_KEY_TYPE_ID, &encoded_key[..])).unwrap(); let report = DoubleVoteReport { identity: ValidatorId::from(key.public()), first: (statement_invalid, signature_1), second: (statement_valid, signature_2), proof, signing_context, }; assert_ok!(Parachains::dispatch( report_double_vote(report).unwrap(), Origin::signed(1), )); start_era(2); // Check that the balance of 0-th validator is slashed 100%. assert_eq!(Balances::total_balance(&0), 10_000_000 - 10_000); assert_eq!(Staking::slashable_balance_of(&0), 0); assert_eq!( Staking::eras_stakers(2, 0), staking::Exposure { total: 0, own: 0, others: vec![], }, ); // Check that the balances of all other validators are left intact. for i in 1..authorities.len() { assert_eq!(Balances::total_balance(&(i as u64)), 10_000_000); assert_eq!(Staking::slashable_balance_of(&(i as u64)), 10_000); assert_eq!( Staking::eras_stakers(2, i as u64), staking::Exposure { total: 10_000, own: 10_000, others: vec![], }, ); } }); } // Check that submitting the same report twice errors. #[test] fn double_vote_submit_twice_works() { let parachains = vec![ (1u32.into(), vec![].into(), vec![].into()), ]; let extract_key = |public: ValidatorId| { let mut raw_public = [0; 32]; raw_public.copy_from_slice(public.as_ref()); Sr25519Keyring::from_raw_public(raw_public).unwrap() }; // Test that a Candidate and Valid statements on the same candidate get slashed. new_test_ext(parachains.clone()).execute_with(|| { assert_eq!(Staking::current_era(), Some(0)); assert_eq!(Session::current_index(), 0); start_era(1); let candidate = raw_candidate(1.into()).abridge().0; let candidate_hash = candidate.hash(); let authorities = Parachains::authorities(); let authority_index = 0; let key = extract_key(authorities[authority_index].clone()); let statement_candidate = Statement::Candidate(candidate_hash.clone()); let statement_valid = Statement::Valid(candidate_hash.clone()); let signing_context = Parachains::signing_context(); let payload_1 = localized_payload(statement_candidate.clone(), &signing_context); let payload_2 = localized_payload(statement_valid.clone(), &signing_context); let signature_1 = key.sign(&payload_1[..]).into(); let signature_2 = key.sign(&payload_2[..]).into(); // Check that in the beginning the genesis balances are there. for i in 0..authorities.len() { assert_eq!(Balances::total_balance(&(i as u64)), 10_000_000); assert_eq!(Staking::slashable_balance_of(&(i as u64)), 10_000); assert_eq!( Staking::eras_stakers(1, i as u64), staking::Exposure { total: 10_000, own: 10_000, others: vec![], }, ); } let encoded_key = key.encode(); let proof = Historical::prove((PARACHAIN_KEY_TYPE_ID, &encoded_key[..])).unwrap(); let report = DoubleVoteReport { identity: ValidatorId::from(key.public()), first: (statement_candidate, signature_1), second: (statement_valid, signature_2), proof, signing_context, }; assert_ok!(Parachains::dispatch( report_double_vote(report.clone()).unwrap(), Origin::signed(1), )); assert!(Parachains::dispatch( report_double_vote(report).unwrap(), Origin::signed(1), ).is_err() ); start_era(2); // Check that the balance of 0-th validator is slashed 100%. assert_eq!(Balances::total_balance(&0), 10_000_000 - 10_000); assert_eq!(Staking::slashable_balance_of(&0), 0); assert_eq!( Staking::eras_stakers(2, 0), staking::Exposure { total: 0, own: 0, others: vec![], }, ); // Check that the balances of all other validators are left intact. for i in 1..authorities.len() { assert_eq!(Balances::total_balance(&(i as u64)), 10_000_000); assert_eq!(Staking::slashable_balance_of(&(i as u64)), 10_000); assert_eq!( Staking::eras_stakers(2, i as u64), staking::Exposure { total: 10_000, own: 10_000, others: vec![], }, ); } }); } // Check that submitting invalid reports fail. #[test] fn double_vote_submit_invalid_works() { let parachains = vec![ (1u32.into(), vec![].into(), vec![].into()), ]; let extract_key = |public: ValidatorId| { let mut raw_public = [0; 32]; raw_public.copy_from_slice(public.as_ref()); Sr25519Keyring::from_raw_public(raw_public).unwrap() }; // Test that a Candidate and Valid statements on the same candidate get slashed. new_test_ext(parachains.clone()).execute_with(|| { assert_eq!(Staking::current_era(), Some(0)); assert_eq!(Session::current_index(), 0); start_era(1); let candidate = raw_candidate(1.into()).abridge().0; let candidate_hash = candidate.hash(); let authorities = Parachains::authorities(); let authority_1_index = 0; let authority_2_index = 1; let key_1 = extract_key(authorities[authority_1_index].clone()); let key_2 = extract_key(authorities[authority_2_index].clone()); let statement_candidate = Statement::Candidate(candidate_hash.clone()); let statement_valid = Statement::Valid(candidate_hash.clone()); let signing_context = Parachains::signing_context(); let payload_1 = localized_payload(statement_candidate.clone(), &signing_context); let payload_2 = localized_payload(statement_valid.clone(), &signing_context); let signature_1 = key_1.sign(&payload_1[..]).into(); let signature_2 = key_2.sign(&payload_2[..]).into(); let encoded_key = key_1.encode(); let proof = Historical::prove((PARACHAIN_KEY_TYPE_ID, &encoded_key[..])).unwrap(); let report = DoubleVoteReport { identity: ValidatorId::from(key_1.public()), first: (statement_candidate, signature_1), second: (statement_valid, signature_2), proof, signing_context, }; assert_eq!( report_double_vote(report.clone()), Err(TransactionValidityError::Invalid( InvalidTransaction::Custom(DoubleVoteValidityError::InvalidSignature as u8) ) ), ); }); } #[test] fn double_vote_proof_session_mismatch_fails() { let parachains = vec![ (1u32.into(), vec![].into(), vec![].into()), ]; let extract_key = |public: ValidatorId| { let mut raw_public = [0; 32]; raw_public.copy_from_slice(public.as_ref()); Sr25519Keyring::from_raw_public(raw_public).unwrap() }; // Test that submitting a report with a session mismatch between the `parent_hash` // and the proof itself fails. new_test_ext(parachains.clone()).execute_with(|| { assert_eq!(Staking::current_era(), Some(0)); assert_eq!(Session::current_index(), 0); start_era(1); let candidate = raw_candidate(1.into()).abridge().0; let candidate_hash = candidate.hash(); let authorities = Parachains::authorities(); let authority_index = 0; let key = extract_key(authorities[authority_index].clone()); let statement_candidate = Statement::Candidate(candidate_hash.clone()); let statement_valid = Statement::Valid(candidate_hash.clone()); let parent_hash = System::parent_hash(); let signing_context = SigningContext { session_index: Session::current_index() - 1, parent_hash, }; let payload_1 = localized_payload(statement_candidate.clone(), &signing_context); let payload_2 = localized_payload(statement_valid.clone(), &signing_context); let signature_1 = key.sign(&payload_1[..]).into(); let signature_2 = key.sign(&payload_2[..]).into(); // Check that in the beginning the genesis balances are there. for i in 0..authorities.len() { assert_eq!(Balances::total_balance(&(i as u64)), 10_000_000); assert_eq!(Staking::slashable_balance_of(&(i as u64)), 10_000); assert_eq!( Staking::eras_stakers(1, i as u64), staking::Exposure { total: 10_000, own: 10_000, others: vec![], }, ); } // Get the proof from another session. start_era(2); let encoded_key = key.encode(); let proof = Historical::prove((PARACHAIN_KEY_TYPE_ID, &encoded_key[..])).unwrap(); let report = DoubleVoteReport { identity: ValidatorId::from(key.public()), first: (statement_candidate, signature_1), second: (statement_valid, signature_2), proof, signing_context, }; assert!(report_double_vote(report.clone()).is_err()); start_era(3); // Check that the balances are unchanged. for i in 0..authorities.len() { assert_eq!(Balances::total_balance(&(i as u64)), 10_000_000); assert_eq!(Staking::slashable_balance_of(&(i as u64)), 10_000); assert_eq!( Staking::eras_stakers(1, i as u64), staking::Exposure { total: 10_000, own: 10_000, others: vec![], }, ); } }); } }