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
This commit is contained in:
Robert Habermeier
2019-08-06 23:08:38 +02:00
committed by GitHub
parent 8c4d882407
commit 4d19de4bd9
6 changed files with 576 additions and 49 deletions
@@ -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 <http://www.gnu.org/licenses/>.
//! 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<Hash, HashSet<Hash>>, // leaf_hash -> { topics }
expected_queues: HashMap<Hash, Hash>, // topic -> queue-root
}
impl View {
/// Update the set of current leaves.
pub fn update_leaves<T: ChainContext + ?Sized, I>(&mut self, context: &T, new_leaves: I)
-> Result<(), ClientError>
where I: Iterator<Item=Hash>
{
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<Hash>, 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<Hash> {
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));
}
}
+7
View File
@@ -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<BlockNumber, BlakeTwo256>;
/// Block type.
+162
View File
@@ -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 <http://www.gnu.org/licenses/>.
//! 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<T: Trait> {
/// 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<ParaId>,
/// Hashes of the parachain candidates included at this block.
pub para_blocks: Vec<Hash>,
}
/// Attestations kept over time on a parachain block.
#[derive(Encode, Decode)]
pub struct BlockAttestations<T: Trait> {
receipt: CandidateReceipt,
valid: Vec<T::AccountId>, // stash account ID of voter.
invalid: Vec<T::AccountId>, // 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<Self::BlockNumber>;
/// Get a list of the validators' underlying identities.
type ValidatorIdentities: Get<Vec<Self::AccountId>>;
}
decl_storage! {
trait Store for Module<T: Trait> 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<IncludedBlocks<T>>;
/// Attestations on a recent parachain block.
pub ParaBlockAttestations: double_map T::BlockNumber, blake2_128(Hash) => Option<BlockAttestations<T>>;
// Did we already have more attestations included in this block?
DidUpdate: bool;
}
}
decl_module! {
/// Parachain-attestations module.
pub struct Module<T: Trait> for enum Call where origin: <T as system::Trait>::Origin {
/// Provide candidate receipts for parachains, in ascending order by id.
fn more_attestations(origin, _more: MoreAttestations) -> Result {
ensure_none(origin)?;
ensure!(!<DidUpdate>::exists(), "More attestations can be added only once in a block.");
<DidUpdate>::put(true);
Ok(())
}
fn on_finalize(_n: T::BlockNumber) {
<DidUpdate>::kill();
}
}
}
impl<T: Trait> Module<T> {
/// Update recent candidates to contain the already-checked parachain candidates.
pub(crate) fn note_included(heads: &[AttestedCandidate], para_blocks: IncludedBlocks<T>) {
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) = <RecentParaBlocks<T>>::take(&mod_num) {
<ParaBlockAttestations<T>>::remove_prefix(&old_entry.actual_number);
}
let validators = T::ValidatorIdentities::get();
// make new entry.
for (head, hash) in heads.iter().zip(&para_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,
};
<ParaBlockAttestations<T>>::insert(&para_blocks.actual_number, hash, &summary);
}
<RecentParaBlocks<T>>::insert(&mod_num, &para_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<T: Trait> ProvideInherent for Module<T> {
type Call = Call<T>;
type Error = MakeFatalError<RuntimeString>;
const INHERENT_IDENTIFIER: InherentIdentifier = MORE_ATTESTATIONS_IDENTIFIER;
fn create_inherent(data: &InherentData) -> Option<Self::Call> {
data.get_data::<InherentType>(&MORE_ATTESTATIONS_IDENTIFIER)
.ok()
.and_then(|x| x.map(Call::more_attestations))
}
}
+14 -4
View File
@@ -14,15 +14,16 @@
// You should have received a copy of the GNU General Public License
// along with Polkadot. If not, see <http://www.gnu.org/licenses/>.
//! 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<Runtime>;
}
impl parachains::Trait for Runtime {
type Origin = Origin;
type Call = Call;
@@ -392,6 +401,7 @@ construct_runtime!(
CuratedGrandpa: curated_grandpa::{Module, Call, Config<T>, Storage},
Treasury: treasury::{Module, Call, Storage, Event<T>},
Parachains: parachains::{Module, Call, Storage, Config<T>, Inherent, Origin},
Attestations: attestations::{Module, Call, Storage},
Slots: slots::{Module, Call, Storage, Event<T>},
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<parachain::Id> {
Parachains::active_parachains()
+127 -44
View File
@@ -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<AccountId, T: Currency<AccountId>> ParachainCurrency<AccountId> for T where
}
}
pub trait Trait: session::Trait {
/// Interface to the persistent (stash) identities of the current validators.
pub struct ValidatorIdentities<T>(rstd::marker::PhantomData<T>);
impl<T: session::Trait> Get<Vec<T::ValidatorId>> for ValidatorIdentities<T> {
fn get() -> Vec<T::ValidatorId> {
<session::Module<T>>::validators()
}
}
pub trait Trait: attestations::Trait {
/// The outer origin type.
type Origin: From<Origin> + From<system::RawOrigin<Self::AccountId>>;
@@ -206,15 +216,17 @@ const WATERMARK_QUEUE_SIZE: usize = 20000;
decl_storage! {
trait Store for Module<T: Trait> as Parachains {
// Vector of all parachain IDs.
/// All authorities' keys at the moment.
pub Authorities get(authorities) config(authorities): Vec<ParachainPublic>;
/// Vector of all parachain IDs.
pub Parachains get(active_parachains): Vec<ParaId>;
// The parachains registered at present.
/// The parachains registered at present.
pub Code get(parachain_code): map ParaId => Option<Vec<u8>>;
// The heads of the parachains registered at present.
/// The heads of the parachains registered at present.
pub Heads get(parachain_head): map ParaId => Option<Vec<u8>>;
// 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<T::BlockNumber>;
/// Unrouted ingress. Maps (BlockNumber, to_chain) pairs to [(from_chain, egress_root)].
@@ -228,10 +240,10 @@ decl_storage! {
pub RelayDispatchQueue: map ParaId => Vec<UpwardMessage>;
/// 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 = <system::Module<T>>::block_number();
<attestations::Module<T>>::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<u8> {
use parity_codec::Encode;
let mut encoded = statement.encode();
encoded.extend(parent_hash.as_ref());
encoded
@@ -512,10 +524,11 @@ impl<T: Trait> Module<T> {
}
/// 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<T: Trait> Module<T> {
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<T: Trait> Module<T> {
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<T: Trait> Module<T> {
// 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<IncludedBlocks<T>, &'static str>
{
use primitives::parachain::ValidityAttestation;
use sr_primitives::traits::Verify;
@@ -683,8 +698,8 @@ impl<T: Trait> Module<T> {
}
}
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<Chain>, where each
// item corresponds to the same position in the session keys, into
@@ -715,6 +730,7 @@ impl<T: Trait> Module<T> {
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<T: Trait> Module<T> {
);
}
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: <system::Module<T>>::block_number(),
session: <session::Module<T>>::current_index(),
random_seed,
active_parachains: active_parachains.to_vec(),
para_blocks: para_block_hashes,
})
}
/*
@@ -809,17 +833,33 @@ impl<T: Trait> Module<T> {
*/
}
pub const INHERENT_IDENTIFIER: InherentIdentifier = *b"newheads";
impl<T: Trait> session::OneSessionHandler<T::AccountId> for Module<T> {
type Key = ParachainPublic;
fn on_new_session<'a, I: 'a>(changed: bool, validators: I, _queued: I)
where I: Iterator<Item=(&'a T::AccountId, Self::Key)>
{
if changed {
<Self as Store>::Authorities::put(&validators.map(|(_, key)| key).collect::<Vec<_>>())
}
}
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<AttestedCandidate>;
impl<T: Trait> ProvideInherent for Module<T> {
type Call = Call<T>;
type Error = MakeFatalError<RuntimeString>;
const INHERENT_IDENTIFIER: InherentIdentifier = INHERENT_IDENTIFIER;
const INHERENT_IDENTIFIER: InherentIdentifier = NEW_HEADS_IDENTIFIER;
fn create_inherent(data: &InherentData) -> Option<Self::Call> {
let data = data.get_data::<InherentType>(&INHERENT_IDENTIFIER)
let data = data.get_data::<InherentType>(&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<Test>;
}
impl attestations::Trait for Test {
type AttestationPeriod = AttestationPeriod;
type ValidatorIdentities = ValidatorIdentities<Test>;
}
impl Trait for Test {
type Origin = Origin;
type Call = Call;
@@ -974,7 +1020,9 @@ mod tests {
type System = system::Module<Test>;
fn new_test_ext(parachains: Vec<(ParaId, Vec<u8>, Vec<u8>)>) -> TestExternalities<Blake2Hasher> {
let mut t = system::GenesisConfig::default().build_storage::<Test>().unwrap().0;
use staking::StakerStatus;
let (mut t, mut c) = system::GenesisConfig::default().build_storage::<Test>().unwrap();
let authority_keys = [
Ed25519Keyring::Alice,
Ed25519Keyring::Bob,
@@ -986,16 +1034,51 @@ mod tests {
Ed25519Keyring::Two,
];
t.extend(session::GenesisConfig::<Test>{
keys: vec![(1, UintAuthorityId(1))],
}.build_storage().unwrap().0);
t.extend(GenesisConfig::<Test>{
// 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::<u64>::Validator,
)).collect();
let balances: Vec<_> = (0..authority_keys.len()).map(|i| (i as u64, 10_000_000)).collect();
session::GenesisConfig::<Test> {
keys: session_keys,
}.assimilate_storage(&mut t, &mut c).unwrap();
GenesisConfig::<Test> {
parachains,
authorities: authorities.clone(),
_phdata: Default::default(),
}.build_storage().unwrap().0);
t.extend(aura::GenesisConfig::<Test>{
authorities: authority_keys.iter().map(|k| SessionKey::from(*k)).collect(),
}.build_storage().unwrap().0);
}.assimilate_storage(&mut t, &mut c).unwrap();
aura::GenesisConfig::<Test> {
authorities,
}.assimilate_storage(&mut t, &mut c).unwrap();
balances::GenesisConfig::<Test> {
balances,
vesting: vec![],
}.assimilate_storage(&mut t, &mut c).unwrap();
staking::GenesisConfig::<Test> {
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);
+1 -1
View File
@@ -717,7 +717,7 @@ impl<C, TxApi> CreateProposal<C, TxApi> 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();