mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-05-09 09:27:59 +00:00
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:
committed by
GitHub
parent
8c4d882407
commit
4d19de4bd9
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
|
||||
@@ -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(¶_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(¶_blocks.actual_number, hash, &summary);
|
||||
}
|
||||
|
||||
<RecentParaBlocks<T>>::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<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,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()
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user