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();