From 4d19de4bd974ab191483d8bbfce6d23be36191f9 Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Tue, 6 Aug 2019 23:08:38 +0200 Subject: [PATCH] Track received attestations from recent blocks (#337) * record attestations in storage ringbuffer * remove some reliance on Aura * fix up test configuration * extract attestations stuff out to its own module * add dummy inherent * use double_map * fix a couple more compilation errors --- .../network/src/gossip/message_routing.rs | 265 ++++++++++++++++++ polkadot/primitives/src/lib.rs | 7 + polkadot/runtime/src/attestations.rs | 162 +++++++++++ polkadot/runtime/src/lib.rs | 18 +- polkadot/runtime/src/parachains.rs | 171 ++++++++--- polkadot/validation/src/lib.rs | 2 +- 6 files changed, 576 insertions(+), 49 deletions(-) create mode 100644 polkadot/network/src/gossip/message_routing.rs create mode 100644 polkadot/runtime/src/attestations.rs diff --git a/polkadot/network/src/gossip/message_routing.rs b/polkadot/network/src/gossip/message_routing.rs new file mode 100644 index 0000000000..38c72f2b5a --- /dev/null +++ b/polkadot/network/src/gossip/message_routing.rs @@ -0,0 +1,265 @@ +// Copyright 2019 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 . + +//! Data structures and synchronous logic for ICMP message gossip. + +use sr_primitives::traits::{BlakeTwo256, Hash as HashT}; +use polkadot_primitives::Hash; +use std::collections::{HashMap, HashSet}; +use substrate_client::error::Error as ClientError; +use super::{MAX_CHAIN_HEADS, GossipValidationResult, LeavesVec, ChainContext}; + +/// Construct a topic for a message queue root deterministically. +pub fn queue_topic(queue_root: Hash) -> Hash { + let mut v = queue_root.as_ref().to_vec(); + v.extend(b"message_queue"); + + BlakeTwo256::hash(&v[..]) +} + +/// A view of which queue roots are current for a given set of leaves. +#[derive(Default)] +pub struct View { + leaves: LeavesVec, + leaf_topics: HashMap>, // leaf_hash -> { topics } + expected_queues: HashMap, // topic -> queue-root +} + +impl View { + /// Update the set of current leaves. + pub fn update_leaves(&mut self, context: &T, new_leaves: I) + -> Result<(), ClientError> + where I: Iterator + { + let new_leaves = new_leaves.take(MAX_CHAIN_HEADS); + let old_leaves = { + let mut new = LeavesVec::new(); + for leaf in new_leaves { + new.push(leaf.clone()); + } + + std::mem::replace(&mut self.leaves, new) + }; + + let expected_queues = &mut self.expected_queues; + let leaves = &self.leaves; + self.leaf_topics.retain(|l, topics| { + if leaves.contains(l) { return true } + + // prune out all data about old leaves we don't follow anymore. + for topic in topics.iter() { + expected_queues.remove(topic); + } + false + }); + + let mut res = Ok(()); + + // add in new data about fresh leaves. + for new_leaf in &self.leaves { + if old_leaves.contains(new_leaf) { continue } + + let mut this_leaf_topics = HashSet::new(); + + let r = context.leaf_unrouted_roots(new_leaf, &mut |&queue_root| { + let topic = queue_topic(queue_root); + this_leaf_topics.insert(topic); + expected_queues.insert(topic, queue_root); + }); + + if r.is_err() { + res = r; + } + + self.leaf_topics.insert(*new_leaf, this_leaf_topics); + } + + res + } + + /// Validate an incoming message queue against this view. + pub fn validate_queue(&self, messages: &super::GossipParachainMessages) + -> (GossipValidationResult, i32) + { + let ostensible_topic = queue_topic(messages.queue_root); + if !self.is_topic_live(&ostensible_topic) { + (GossipValidationResult::Discard, super::cost::UNNEEDED_ICMP_MESSAGES) + } else if !messages.queue_root_is_correct() { + ( + GossipValidationResult::Discard, + super::cost::icmp_messages_root_mismatch(messages.messages.len()), + ) + } else { + ( + GossipValidationResult::ProcessAndKeep(ostensible_topic), + super::benefit::NEW_ICMP_MESSAGES, + ) + } + } + + /// Whether a message with given topic is live. + pub fn is_topic_live(&self, topic: &Hash) -> bool { + self.expected_queues.get(topic).is_some() + } + + /// Whether a message is allowed under the intersection of the given leaf-set + /// and our own. + pub fn allowed_intersecting(&self, other_leaves: &LeavesVec, topic: &Hash) -> bool { + for i in other_leaves { + for j in &self.leaves { + if i == j { + let leaf_topics = self.leaf_topics.get(i) + .expect("leaf_topics are mutated only in update_leaves; \ + we have an entry for each item in self.leaves; \ + i is in self.leaves; qed"); + + if leaf_topics.contains(topic) { + return true; + } + } + } + } + + false + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::tests::TestChainContext; + use crate::gossip::{Known, GossipParachainMessages}; + use polkadot_primitives::parachain::Message as ParachainMessage; + + fn hash(x: u8) -> Hash { + [x; 32].into() + } + + fn message_queue(from: u8, to: u8) -> Option<[[u8; 2]; 1]> { + if from == to { + None + } else { + Some([[from, to]]) + } + } + + fn message_queue_root(from: u8, to: u8) -> Option { + message_queue(from, to).map( + |q| polkadot_validation::message_queue_root(q.iter()) + ) + } + + fn check_roots(view: &View, i: u8, max: u8) -> bool { + for j in 0..max { + if let Some(messages) = message_queue(i, j) { + let queue_root = message_queue_root(i, j).unwrap(); + let messages = GossipParachainMessages { + queue_root, + messages: messages.iter().map(|m| ParachainMessage(m.to_vec())).collect(), + }; + + match view.validate_queue(&messages).0 { + GossipValidationResult::ProcessAndKeep(topic) => if topic != queue_topic(queue_root) { + return false + }, + _ => return false, + } + } + } + + true + } + + #[test] + fn update_leaves_none_in_common() { + let mut ctx = TestChainContext::default(); + let max = 5; + + for i in 0..max { + ctx.known_map.insert(hash(i as u8), Known::Leaf); + + let messages_out: Vec<_> = (0..max).filter_map(|j| message_queue_root(i, j)).collect(); + + if !messages_out.is_empty() { + ctx.ingress_roots.insert(hash(i as u8), messages_out); + } + } + + let mut view = View::default(); + view.update_leaves( + &ctx, + [hash(0), hash(1)].iter().cloned(), + ).unwrap(); + + assert!(check_roots(&view, 0, max)); + assert!(check_roots(&view, 1, max)); + + assert!(!check_roots(&view, 2, max)); + assert!(!check_roots(&view, 3, max)); + assert!(!check_roots(&view, 4, max)); + assert!(!check_roots(&view, 5, max)); + + view.update_leaves( + &ctx, + [hash(2), hash(3), hash(4)].iter().cloned(), + ).unwrap(); + + assert!(!check_roots(&view, 0, max)); + assert!(!check_roots(&view, 1, max)); + + assert!(check_roots(&view, 2, max)); + assert!(check_roots(&view, 3, max)); + assert!(check_roots(&view, 4, max)); + + assert!(!check_roots(&view, 5, max)); + } + + #[test] + fn update_leaves_overlapping() { + let mut ctx = TestChainContext::default(); + let max = 5; + + for i in 0..max { + ctx.known_map.insert(hash(i as u8), Known::Leaf); + + let messages_out: Vec<_> = (0..max).filter_map(|j| message_queue_root(i, j)).collect(); + + if !messages_out.is_empty() { + ctx.ingress_roots.insert(hash(i as u8), messages_out); + } + } + + let mut view = View::default(); + view.update_leaves( + &ctx, + [hash(0), hash(1), hash(2)].iter().cloned(), + ).unwrap(); + + view.update_leaves( + &ctx, + [hash(2), hash(3), hash(4)].iter().cloned(), + ).unwrap(); + + assert!(!check_roots(&view, 0, max)); + assert!(!check_roots(&view, 1, max)); + + assert!(check_roots(&view, 2, max)); + assert!(check_roots(&view, 3, max)); + assert!(check_roots(&view, 4, max)); + + assert!(!check_roots(&view, 5, max)); + } +} diff --git a/polkadot/primitives/src/lib.rs b/polkadot/primitives/src/lib.rs index 1ee2f6b532..02f5d53e40 100644 --- a/polkadot/primitives/src/lib.rs +++ b/polkadot/primitives/src/lib.rs @@ -76,6 +76,13 @@ pub type AuraPair = ed25519::Pair; /// The Ed25519 pub key of an session that belongs to an Aura authority of the chain. pub type AuraId = ed25519::Public; +/// The Parachain crypto scheme defined via the keypair type. +#[cfg(feature = "std")] +pub type ParachainPair = ed25519::Pair; + +/// The Ed25519 public key used to authenticate signatures on parachain data. +pub type ParachainPublic = ed25519::Public; + /// Header type. pub type Header = generic::Header; /// Block type. diff --git a/polkadot/runtime/src/attestations.rs b/polkadot/runtime/src/attestations.rs new file mode 100644 index 0000000000..c359a91e2b --- /dev/null +++ b/polkadot/runtime/src/attestations.rs @@ -0,0 +1,162 @@ +// Copyright 2019 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 . + +//! A module for tracking all attestations that fell on a given candidate receipt. +//! +//! In the future, it is planned that this module will handle dispute resolution +//! as well. + +use rstd::prelude::*; +use parity_codec::{Encode, Decode}; +use srml_support::{decl_storage, decl_module, ensure}; + +use primitives::{Hash, parachain::{AttestedCandidate, CandidateReceipt, Id as ParaId}}; +use {system, session::{self, SessionIndex}}; +use srml_support::{ + StorageValue, StorageMap, StorageDoubleMap, dispatch::Result, traits::Get, +}; + +use inherents::{ProvideInherent, InherentData, RuntimeString, MakeFatalError, InherentIdentifier}; +use system::ensure_none; + +/// Parachain blocks included in a recent relay-chain block. +#[derive(Encode, Decode)] +pub struct IncludedBlocks { + /// The actual relay chain block number where blocks were included. + pub actual_number: T::BlockNumber, + /// The session index at this block. + pub session: SessionIndex, + /// The randomness seed at this block. + pub random_seed: [u8; 32], + /// All parachain IDs active at this block. + pub active_parachains: Vec, + /// Hashes of the parachain candidates included at this block. + pub para_blocks: Vec, +} + +/// Attestations kept over time on a parachain block. +#[derive(Encode, Decode)] +pub struct BlockAttestations { + receipt: CandidateReceipt, + valid: Vec, // stash account ID of voter. + invalid: Vec, // stash account ID of voter. +} + +/// Additional attestations on a parachain block, after it was included. +#[derive(Encode, Decode, Clone, PartialEq)] +#[cfg_attr(feature = "std", derive(Debug))] +pub struct MoreAttestations; + +pub trait Trait: session::Trait { + /// How many blocks ago we're willing to accept attestations for. + type AttestationPeriod: Get; + + /// Get a list of the validators' underlying identities. + type ValidatorIdentities: Get>; +} + +decl_storage! { + trait Store for Module as Attestations { + /// A mapping from modular block number (n % AttestationPeriod) + /// to session index and the list of candidate hashes. + pub RecentParaBlocks: map T::BlockNumber => Option>; + + /// Attestations on a recent parachain block. + pub ParaBlockAttestations: double_map T::BlockNumber, blake2_128(Hash) => Option>; + + // Did we already have more attestations included in this block? + DidUpdate: bool; + } +} + +decl_module! { + /// Parachain-attestations module. + pub struct Module for enum Call where origin: ::Origin { + /// Provide candidate receipts for parachains, in ascending order by id. + fn more_attestations(origin, _more: MoreAttestations) -> Result { + ensure_none(origin)?; + ensure!(!::exists(), "More attestations can be added only once in a block."); + ::put(true); + + Ok(()) + } + + fn on_finalize(_n: T::BlockNumber) { + ::kill(); + } + } +} + +impl Module { + /// Update recent candidates to contain the already-checked parachain candidates. + pub(crate) fn note_included(heads: &[AttestedCandidate], para_blocks: IncludedBlocks) { + let attestation_period = T::AttestationPeriod::get(); + let mod_num = para_blocks.actual_number % attestation_period; + + // clear old entry that was in this place. + if let Some(old_entry) = >::take(&mod_num) { + >::remove_prefix(&old_entry.actual_number); + } + + let validators = T::ValidatorIdentities::get(); + + // make new entry. + for (head, hash) in heads.iter().zip(¶_blocks.para_blocks) { + let mut valid = Vec::new(); + let invalid = Vec::new(); + + for (auth_index, _) in head.validator_indices + .iter() + .enumerate() + .filter(|(_, bit)| *bit) + { + let stash_id = validators.get(auth_index) + .expect("auth_index checked to be within bounds in `check_candidates`; qed") + .clone(); + + valid.push(stash_id); + } + + let summary = BlockAttestations { + receipt: head.candidate().clone(), + valid, + invalid, + }; + + >::insert(¶_blocks.actual_number, hash, &summary); + } + + >::insert(&mod_num, ¶_blocks); + } +} + +/// An identifier for inherent data that provides after-the-fact attestations +/// on already included parachain blocks. +pub const MORE_ATTESTATIONS_IDENTIFIER: InherentIdentifier = *b"par-atts"; + +pub type InherentType = MoreAttestations; + +impl ProvideInherent for Module { + type Call = Call; + type Error = MakeFatalError; + const INHERENT_IDENTIFIER: InherentIdentifier = MORE_ATTESTATIONS_IDENTIFIER; + + fn create_inherent(data: &InherentData) -> Option { + data.get_data::(&MORE_ATTESTATIONS_IDENTIFIER) + .ok() + .and_then(|x| x.map(Call::more_attestations)) + } +} diff --git a/polkadot/runtime/src/lib.rs b/polkadot/runtime/src/lib.rs index 2e90f3f22a..99d8c01839 100644 --- a/polkadot/runtime/src/lib.rs +++ b/polkadot/runtime/src/lib.rs @@ -14,15 +14,16 @@ // You should have received a copy of the GNU General Public License // along with Polkadot. If not, see . -//! The Polkadot runtime. This can be compiled with ``#[no_std]`, ready for Wasm. +//! The Polkadot runtime. This can be compiled with `#[no_std]`, ready for Wasm. #![cfg_attr(not(feature = "std"), no_std)] // `construct_runtime!` does a lot of recursion and requires us to increase the limit to 256. #![recursion_limit="256"] +mod attestations; +mod claims; mod curated_grandpa; mod parachains; -mod claims; mod slot_range; mod slots; @@ -57,7 +58,8 @@ pub use staking::StakerStatus; pub use sr_primitives::BuildStorage; pub use timestamp::Call as TimestampCall; pub use balances::Call as BalancesCall; -pub use parachains::{Call as ParachainsCall, INHERENT_IDENTIFIER as PARACHAIN_INHERENT_IDENTIFIER}; +pub use attestations::{Call as AttestationsCall, MORE_ATTESTATIONS_IDENTIFIER}; +pub use parachains::{Call as ParachainsCall, NEW_HEADS_IDENTIFIER}; pub use sr_primitives::{Permill, Perbill}; pub use srml_support::StorageValue; @@ -244,6 +246,8 @@ parameter_types! { pub const MinimumDeposit: Balance = 100 * BUCKS; pub const EnactmentPeriod: BlockNumber = 30 * 24 * 60 * MINUTES; pub const CooloffPeriod: BlockNumber = 30 * 24 * 60 * MINUTES; + + pub const AttestationPeriod: BlockNumber = 60 * MINUTES * 3; } impl democracy::Trait for Runtime { @@ -343,6 +347,11 @@ impl finality_tracker::Trait for Runtime { type ReportLatency = ReportLatency; } +impl attestations::Trait for Runtime { + type AttestationPeriod = AttestationPeriod; + type ValidatorIdentities = parachains::ValidatorIdentities; +} + impl parachains::Trait for Runtime { type Origin = Origin; type Call = Call; @@ -392,6 +401,7 @@ construct_runtime!( CuratedGrandpa: curated_grandpa::{Module, Call, Config, Storage}, Treasury: treasury::{Module, Call, Storage, Event}, Parachains: parachains::{Module, Call, Storage, Config, Inherent, Origin}, + Attestations: attestations::{Module, Call, Storage}, Slots: slots::{Module, Call, Storage, Event}, Sudo: sudo, } @@ -481,7 +491,7 @@ impl_runtime_apis! { Aura::authorities() // only possible as long as parachain validator crypto === aura crypto } fn duty_roster() -> parachain::DutyRoster { - Parachains::calculate_duty_roster() + Parachains::calculate_duty_roster().0 } fn active_parachains() -> Vec { Parachains::active_parachains() diff --git a/polkadot/runtime/src/parachains.rs b/polkadot/runtime/src/parachains.rs index f891ac2444..b44cba656c 100644 --- a/polkadot/runtime/src/parachains.rs +++ b/polkadot/runtime/src/parachains.rs @@ -1,4 +1,4 @@ -// Copyright 2017 Parity Technologies (UK) Ltd. +// Copyright 2017-2019 Parity Technologies (UK) Ltd. // This file is part of Polkadot. // Polkadot is free software: you can redistribute it and/or modify @@ -18,19 +18,19 @@ use rstd::prelude::*; use rstd::collections::btree_map::BTreeMap; -use parity_codec::{Decode, HasCompact}; +use parity_codec::{Encode, Decode, HasCompact}; use srml_support::{decl_storage, decl_module, fail, ensure}; use sr_primitives::traits::{Hash as HashT, BlakeTwo256, Member, CheckedConversion, Saturating, One}; use sr_primitives::weights::SimpleDispatchInfo; -use primitives::{Hash, Balance, parachain::{ +use primitives::{Hash, Balance, ParachainPublic, parachain::{ self, Id as ParaId, Chain, DutyRoster, AttestedCandidate, Statement, AccountIdConversion, ParachainDispatchOrigin, UpwardMessage, BlockIngressRoots, }}; use {system, session}; use srml_support::{ StorageValue, StorageMap, storage::AppendableStorageMap, Parameter, Dispatchable, dispatch::Result, - traits::{Currency, WithdrawReason, ExistenceRequirement} + traits::{Currency, Get, WithdrawReason, ExistenceRequirement} }; #[cfg(feature = "std")] @@ -45,6 +45,7 @@ use sr_primitives::{StorageOverlay, ChildrenStorageOverlay}; use rstd::marker::PhantomData; use system::{ensure_none, ensure_root}; +use crate::attestations::{self, IncludedBlocks}; // ranges for iteration of general block number don't work, so this // is a utility to get around that. @@ -172,7 +173,16 @@ impl> ParachainCurrency for T where } } -pub trait Trait: session::Trait { +/// Interface to the persistent (stash) identities of the current validators. +pub struct ValidatorIdentities(rstd::marker::PhantomData); + +impl Get> for ValidatorIdentities { + fn get() -> Vec { + >::validators() + } +} + +pub trait Trait: attestations::Trait { /// The outer origin type. type Origin: From + From>; @@ -206,15 +216,17 @@ const WATERMARK_QUEUE_SIZE: usize = 20000; decl_storage! { trait Store for Module as Parachains { - // Vector of all parachain IDs. + /// All authorities' keys at the moment. + pub Authorities get(authorities) config(authorities): Vec; + /// Vector of all parachain IDs. pub Parachains get(active_parachains): Vec; - // The parachains registered at present. + /// The parachains registered at present. pub Code get(parachain_code): map ParaId => Option>; - // The heads of the parachains registered at present. + /// The heads of the parachains registered at present. pub Heads get(parachain_head): map ParaId => Option>; - // The watermark heights of the parachains registered at present. - // For every parachain, this is the block height from which all messages targeting - // that parachain have been processed. Can be `None` only if the parachain doesn't exist. + /// The watermark heights of the parachains registered at present. + /// For every parachain, this is the block height from which all messages targeting + /// that parachain have been processed. Can be `None` only if the parachain doesn't exist. pub Watermarks get(watermark): map ParaId => Option; /// Unrouted ingress. Maps (BlockNumber, to_chain) pairs to [(from_chain, egress_root)]. @@ -228,10 +240,10 @@ decl_storage! { pub RelayDispatchQueue: map 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. + /// second if the total length (in bytes) of the message payloads. pub RelayDispatchQueueSize: map ParaId => (u32, u32); - // Did the parachain heads get updated in this block? + /// Did the parachain heads get updated in this block? DidUpdate: bool; /// The next unused ParaId value. @@ -305,13 +317,15 @@ decl_module! { } } - Self::check_candidates(&heads)?; + let para_blocks = Self::check_candidates(&heads, &active_parachains)?; let current_number = >::block_number(); + >::note_included(&heads, para_blocks); + Self::update_routing( current_number, - &heads + &heads, ); Self::dispatch_upward_messages( @@ -354,8 +368,6 @@ fn majority_of(list_len: usize) -> usize { } fn localized_payload(statement: Statement, parent_hash: ::primitives::Hash) -> Vec { - use parity_codec::Encode; - let mut encoded = statement.encode(); encoded.extend(parent_hash.as_ref()); encoded @@ -512,10 +524,11 @@ impl Module { } /// Calculate the current block's duty roster using system's random seed. - pub fn calculate_duty_roster() -> DutyRoster { + /// 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(); - let validator_count = crate::Aura::authorities().len(); + let validator_count = Self::authorities().len(); let validators_per_parachain = if parachain_count != 0 { (validator_count - 1) / parachain_count } else { 0 }; let mut roles_val = (0..validator_count).map(|i| match i { @@ -545,6 +558,8 @@ impl Module { BlakeTwo256::hash(&seed.as_ref()[seed_off..]) }; + let orig_seed = seed.clone().to_fixed_bytes(); + // shuffle for i in 0..(validator_count - 1) { // 4 bytes of entropy used per cycle, 32 bytes entropy per hash @@ -566,9 +581,7 @@ impl Module { roles_val.swap(remaining - 1, val_index); } - DutyRoster { - validator_duty: roles_val, - } + (DutyRoster { validator_duty: roles_val, }, orig_seed) } /// Calculate the ingress to a specific parachain. @@ -639,7 +652,9 @@ impl Module { // check the attestations on these candidates. The candidates should have been checked // that each candidates' chain ID is valid. - fn check_candidates(attested_candidates: &[AttestedCandidate]) -> Result { + fn check_candidates(attested_candidates: &[AttestedCandidate], active_parachains: &[ParaId]) + -> rstd::result::Result, &'static str> + { use primitives::parachain::ValidityAttestation; use sr_primitives::traits::Verify; @@ -683,8 +698,8 @@ impl Module { } } - let authorities = super::Aura::authorities(); - let duty_roster = Self::calculate_duty_roster(); + 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 @@ -715,6 +730,7 @@ impl Module { 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) @@ -783,13 +799,21 @@ impl Module { ); } - ensure!( + para_block_hashes.push(candidate_hash.unwrap_or_else(|| candidate.candidate().hash())); + + ensure!( candidate.validity_votes.len() == expected_votes_len, "Extra untagged validity votes along with candidate" ); } - Ok(()) + Ok(IncludedBlocks { + actual_number: >::block_number(), + session: >::current_index(), + random_seed, + active_parachains: active_parachains.to_vec(), + para_blocks: para_block_hashes, + }) } /* @@ -809,17 +833,33 @@ impl Module { */ } -pub const INHERENT_IDENTIFIER: InherentIdentifier = *b"newheads"; +impl session::OneSessionHandler for Module { + type Key = ParachainPublic; + + 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) { } +} + +/// An identifier for inherent data that provides new minimally-attested +/// parachain heads. +pub const NEW_HEADS_IDENTIFIER: InherentIdentifier = *b"newheads"; pub type InherentType = Vec; impl ProvideInherent for Module { type Call = Call; type Error = MakeFatalError; - const INHERENT_IDENTIFIER: InherentIdentifier = INHERENT_IDENTIFIER; + const INHERENT_IDENTIFIER: InherentIdentifier = NEW_HEADS_IDENTIFIER; fn create_inherent(data: &InherentData) -> Option { - let data = data.get_data::(&INHERENT_IDENTIFIER) + let data = data.get_data::(&NEW_HEADS_IDENTIFIER) .expect("Parachain heads could not be decoded.") .expect("No parachain heads found in inherent data."); @@ -949,6 +989,7 @@ mod tests { parameter_types! { pub const SessionsPerEra: session::SessionIndex = 6; pub const BondingDuration: staking::EraIndex = 24 * 28; + pub const AttestationPeriod: u64 = 100; } impl staking::Trait for Test { @@ -964,6 +1005,11 @@ mod tests { type Time = timestamp::Module; } + impl attestations::Trait for Test { + type AttestationPeriod = AttestationPeriod; + type ValidatorIdentities = ValidatorIdentities; + } + impl Trait for Test { type Origin = Origin; type Call = Call; @@ -974,7 +1020,9 @@ mod tests { type System = system::Module; fn new_test_ext(parachains: Vec<(ParaId, Vec, Vec)>) -> TestExternalities { - let mut t = system::GenesisConfig::default().build_storage::().unwrap().0; + use staking::StakerStatus; + + let (mut t, mut c) = system::GenesisConfig::default().build_storage::().unwrap(); let authority_keys = [ Ed25519Keyring::Alice, Ed25519Keyring::Bob, @@ -986,16 +1034,51 @@ mod tests { Ed25519Keyring::Two, ]; - t.extend(session::GenesisConfig::{ - keys: vec![(1, UintAuthorityId(1))], - }.build_storage().unwrap().0); - t.extend(GenesisConfig::{ + // stashes are the index. + let session_keys: Vec<_> = authority_keys.iter().enumerate() + .map(|(i, _k)| (i as u64, UintAuthorityId(i as u64))) + .collect(); + + let authorities: Vec<_> = authority_keys.iter().map(|k| SessionKey::from(*k)).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(); + + session::GenesisConfig:: { + keys: session_keys, + }.assimilate_storage(&mut t, &mut c).unwrap(); + GenesisConfig:: { parachains, + authorities: authorities.clone(), _phdata: Default::default(), - }.build_storage().unwrap().0); - t.extend(aura::GenesisConfig::{ - authorities: authority_keys.iter().map(|k| SessionKey::from(*k)).collect(), - }.build_storage().unwrap().0); + }.assimilate_storage(&mut t, &mut c).unwrap(); + + aura::GenesisConfig:: { + authorities, + }.assimilate_storage(&mut t, &mut c).unwrap(); + + balances::GenesisConfig:: { + balances, + vesting: vec![], + }.assimilate_storage(&mut t, &mut c).unwrap(); + + staking::GenesisConfig:: { + current_era: 0, + stakers, + validator_count: 10, + minimum_validator_count: 8, + offline_slash: Perbill::from_percent(5), + offline_slash_grace: 0, + invulnerables: vec![], + }.assimilate_storage(&mut t, &mut c).unwrap(); + t.into() } @@ -1007,10 +1090,10 @@ mod tests { let mut vote_implicit = false; let parent_hash = crate::System::parent_hash(); - let duty_roster = Parachains::calculate_duty_roster(); + let (duty_roster, _) = Parachains::calculate_duty_roster(); let candidate_hash = candidate.candidate.hash(); - let authorities = crate::Aura::authorities(); + let authorities = Parachains::authorities(); let extract_key = |public: SessionKey| { Ed25519Keyring::from_raw_public(public.0).unwrap() }; @@ -1407,17 +1490,17 @@ mod tests { assert_eq!(duty_roster.validator_duty.iter().filter(|&&j| j == Chain::Relay).count(), 2); }; - let duty_roster_0 = Parachains::calculate_duty_roster(); + 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()); - let duty_roster_1 = Parachains::calculate_duty_roster(); + let duty_roster_1 = Parachains::calculate_duty_roster().0; check_roster(&duty_roster_1); assert!(duty_roster_0 != duty_roster_1); System::initialize(&2, &H256::from([2; 32]), &Default::default(), &Default::default()); - let duty_roster_2 = Parachains::calculate_duty_roster(); + let duty_roster_2 = Parachains::calculate_duty_roster().0; check_roster(&duty_roster_2); assert!(duty_roster_0 != duty_roster_2); assert!(duty_roster_1 != duty_roster_2); diff --git a/polkadot/validation/src/lib.rs b/polkadot/validation/src/lib.rs index 53b02fdae3..f5cb83b191 100644 --- a/polkadot/validation/src/lib.rs +++ b/polkadot/validation/src/lib.rs @@ -717,7 +717,7 @@ impl CreateProposal where let mut inherent_data = self.inherent_data .take() .expect("CreateProposal is not polled after finishing; qed"); - inherent_data.put_data(polkadot_runtime::PARACHAIN_INHERENT_IDENTIFIER, &candidates).map_err(Error::InherentError)?; + inherent_data.put_data(polkadot_runtime::NEW_HEADS_IDENTIFIER, &candidates).map_err(Error::InherentError)?; let runtime_api = self.client.runtime_api();