diff --git a/polkadot/network/src/gossip.rs b/polkadot/network/src/gossip.rs index 443b45d5e7684d504be56b07bf5c9103d3af1b0d..90ca08bd014d0efa36cc0679484cdb42c76c1230 100644 --- a/polkadot/network/src/gossip.rs +++ b/polkadot/network/src/gossip.rs @@ -22,7 +22,7 @@ use substrate_network::consensus_gossip::{ ValidatorContext, }; use polkadot_validation::SignedStatement; -use polkadot_primitives::{Block, Hash, SessionKey}; +use polkadot_primitives::{Block, Hash, SessionKey, parachain::ValidatorIndex}; use codec::Decode; use std::collections::HashMap; @@ -120,19 +120,26 @@ impl RegisteredMessageValidator { } } -// data needed for validating gossip. +/// The data needed for validating gossip. pub(crate) struct MessageValidationData { /// The authorities at a block. pub(crate) authorities: Vec<SessionKey>, + /// Mapping from validator index to `SessionKey`. + pub(crate) index_mapping: HashMap<ValidatorIndex, SessionKey>, } impl MessageValidationData { fn check_statement(&self, relay_parent: &Hash, statement: &SignedStatement) -> bool { - self.authorities.contains(&statement.sender) && + let sender = match self.index_mapping.get(&statement.sender) { + Some(val) => val, + None => return false, + }; + + self.authorities.contains(&sender) && ::polkadot_validation::check_statement( &statement.statement, &statement.signature, - statement.sender.clone(), + sender.clone(), relay_parent, ) } diff --git a/polkadot/network/src/router.rs b/polkadot/network/src/router.rs index e82be09ab673decf8f53de8a83041894b1dcf220..dad32c8a9113fc208ff426aba67a0beb82de74d1 100644 --- a/polkadot/network/src/router.rs +++ b/polkadot/network/src/router.rs @@ -27,10 +27,9 @@ use sr_primitives::traits::{ProvideRuntimeApi, BlakeTwo256, Hash as HashT}; use polkadot_validation::{ SharedTable, TableRouter, SignedStatement, GenericStatement, ParachainWork, Outgoing, Validated }; -use polkadot_primitives::{Block, Hash, SessionKey}; -use polkadot_primitives::parachain::{ - Extrinsic, CandidateReceipt, ParachainHost, Id as ParaId, Message, - Collation, PoVBlock, +use polkadot_primitives::{Block, Hash}; +use polkadot_primitives::parachain::{Extrinsic, CandidateReceipt, ParachainHost, Id as ParaId, Message, + ValidatorIndex, Collation, PoVBlock, }; use gossip::RegisteredMessageValidator; @@ -163,12 +162,14 @@ impl<P: ProvideRuntimeApi + Send + Sync + 'static, E, N, T> Router<P, E, N, T> w ); // dispatch future work as necessary. for (producer, statement) in producers.into_iter().zip(statements) { - self.fetcher.knowledge().lock().note_statement(statement.sender, &statement.statement); + if let Some(sender) = self.table.index_to_id(statement.sender) { + self.fetcher.knowledge().lock().note_statement(sender, &statement.statement); - if let Some(work) = producer.map(|p| self.create_work(c_hash, p)) { - trace!(target: "validation", "driving statement work to completion"); - let work = work.select2(self.fetcher.exit().clone()).then(|_| Ok(())); - self.fetcher.executor().spawn(work); + if let Some(work) = producer.map(|p| self.create_work(c_hash, p)) { + trace!(target: "validation", "driving statement work to completion"); + let work = work.select2(self.fetcher.exit().clone()).then(|_| Ok(())); + self.fetcher.executor().spawn(work); + } } } } @@ -270,8 +271,8 @@ impl<P, E, N: NetworkService, T> Drop for Router<P, E, N, T> { // A unique trace for valid statements issued by a validator. #[derive(Hash, PartialEq, Eq, Clone, Debug)] enum StatementTrace { - Valid(SessionKey, Hash), - Invalid(SessionKey, Hash), + Valid(ValidatorIndex, Hash), + Invalid(ValidatorIndex, Hash), } // helper for deferring statements whose associated candidate is unknown. @@ -325,19 +326,17 @@ impl DeferredStatements { #[cfg(test)] mod tests { use super::*; - use substrate_primitives::crypto::UncheckedInto; - use polkadot_primitives::parachain::ValidatorId; #[test] fn deferred_statements_works() { let mut deferred = DeferredStatements::new(); let hash = [1; 32].into(); let sig = Default::default(); - let sender: ValidatorId = [255; 32].unchecked_into(); + let sender_index = 0; let statement = SignedStatement { statement: GenericStatement::Valid(hash), - sender: sender.clone(), + sender: sender_index, signature: sig, }; @@ -358,7 +357,7 @@ mod tests { assert_eq!(traces.len(), 1); assert_eq!(signed[0].clone(), statement); - assert_eq!(traces[0].clone(), StatementTrace::Valid(sender, hash)); + assert_eq!(traces[0].clone(), StatementTrace::Valid(sender_index, hash)); } // after draining diff --git a/polkadot/network/src/tests/validation.rs b/polkadot/network/src/tests/validation.rs index 3724d051bfd5fd16726b54cb598e2522e5d504d5..73762763c0924e8e734b95eda80331abf9448f49 100644 --- a/polkadot/network/src/tests/validation.rs +++ b/polkadot/network/src/tests/validation.rs @@ -401,6 +401,7 @@ fn make_table(data: &ApiData, local_key: &AuthorityKeyring, parent_hash: Hash) - ).unwrap(); Arc::new(SharedTable::new( + data.validators.as_slice(), group_info, Arc::new(local_key.pair()), parent_hash, diff --git a/polkadot/network/src/validation.rs b/polkadot/network/src/validation.rs index 038b1c2b6b55f814371bfc187ebfbcdeb319d238..5b48e0e02fd6fdb45f28cf22aaa1b4c03719e652 100644 --- a/polkadot/network/src/validation.rs +++ b/polkadot/network/src/validation.rs @@ -24,10 +24,7 @@ use substrate_network::Context as NetContext; use substrate_network::consensus_gossip::{TopicNotification, MessageRecipient as GossipMessageRecipient}; use polkadot_validation::{Network as ParachainNetwork, SharedTable, Collators, Statement, GenericStatement}; use polkadot_primitives::{Block, BlockId, Hash, SessionKey}; -use polkadot_primitives::parachain::{ - Id as ParaId, Collation, Extrinsic, ParachainHost, Message, CandidateReceipt, - CollatorId, ValidatorId, PoVBlock, -}; +use polkadot_primitives::parachain::{Id as ParaId, Collation, Extrinsic, ParachainHost, Message, CandidateReceipt, CollatorId, ValidatorId, PoVBlock, ValidatorIndex}; use codec::{Encode, Decode}; use futures::prelude::*; @@ -195,13 +192,21 @@ impl<P, E, N, T> ValidationNetwork<P, E, N, T> where let task_executor = self.executor.clone(); let exit = self.exit.clone(); let message_validator = self.message_validator.clone(); + let index_mapping = params.authorities + .iter() + .enumerate() + .map(|(i, k)| (i as ValidatorIndex, k.clone())) + .collect(); let (tx, rx) = oneshot::channel(); self.network.with_spec(move |spec, ctx| { // before requesting messages, note live consensus session. message_validator.note_session( parent_hash, - MessageValidationData { authorities: params.authorities.clone() }, + MessageValidationData { + authorities: params.authorities.clone(), + index_mapping, + }, ); let session = spec.new_validation_session(ctx, params); diff --git a/polkadot/primitives/src/parachain.rs b/polkadot/primitives/src/parachain.rs index 5183f408f9bcf0631485a562ba0218b9eafac177..756c3e2a0bb6abf10864c496356fdbf312fe84fc 100644 --- a/polkadot/primitives/src/parachain.rs +++ b/polkadot/primitives/src/parachain.rs @@ -38,6 +38,9 @@ pub type CollatorSignature = ed25519::Signature; /// so we define it to be the same type as `SessionKey`. In the future it may have different crypto. pub type ValidatorId = super::SessionKey; +/// Index of the validator is used as a lightweight replacement of the `ValidatorId` when appropriate. +pub type ValidatorIndex = u32; + /// Signature with which parachain validators sign blocks. /// /// For now we assert that parachain validator set is exactly equivalent to the (Aura) authority set, and @@ -279,7 +282,7 @@ pub struct AttestedCandidate { /// The candidate data. pub candidate: CandidateReceipt, /// Validity attestations. - pub validity_votes: Vec<(ValidatorId, ValidityAttestation)>, + pub validity_votes: Vec<(ValidatorIndex, ValidityAttestation)>, } impl AttestedCandidate { diff --git a/polkadot/runtime/src/parachains.rs b/polkadot/runtime/src/parachains.rs index 336c08cacb76ff67157af13fd53c61d98a18a945..a390566bd65f0aaf88555631e9ac1a3b13446f7c 100644 --- a/polkadot/runtime/src/parachains.rs +++ b/polkadot/runtime/src/parachains.rs @@ -393,9 +393,10 @@ impl<T: Trait> Module<T> { // track which voters have voted already, 1 bit per authority. let mut track_voters = bitvec![0; authorities.len()]; - for (auth_id, validity_attestation) in &candidate.validity_votes { + for (auth_index, validity_attestation) in &candidate.validity_votes { + let auth_index = *auth_index as usize; // protect against double-votes. - match validator_group.iter().find(|&(idx, _)| &authorities[*idx] == auth_id) { + match validator_group.iter().find(|&(idx, _)| *idx == auth_index) { None => return Err("Attesting validator not on this chain's validation duty."), Some(&(idx, _)) => { if track_voters.get(idx) { @@ -427,7 +428,7 @@ impl<T: Trait> Module<T> { }; ensure!( - sig.verify(&payload[..], &auth_id), + sig.verify(&payload[..], &authorities[auth_index]), "Candidate validity attestation signature is bad." ); } @@ -479,7 +480,7 @@ mod tests { use substrate_trie::NodeCodec; use sr_primitives::{generic, BuildStorage}; use sr_primitives::traits::{BlakeTwo256, IdentityLookup}; - use primitives::{parachain::{CandidateReceipt, HeadData, ValidityAttestation}, SessionKey}; + use primitives::{parachain::{CandidateReceipt, HeadData, ValidityAttestation, ValidatorIndex}, SessionKey}; use keyring::{AuthorityKeyring, AccountKeyring}; use {consensus, timestamp}; @@ -590,7 +591,7 @@ mod tests { let payload = localized_payload(statement, parent_hash); let signature = key.sign(&payload[..]).into(); - candidate.validity_votes.push((authorities[idx].clone(), if vote_implicit { + candidate.validity_votes.push((idx as ValidatorIndex, if vote_implicit { ValidityAttestation::Implicit(signature) } else { ValidityAttestation::Explicit(signature) diff --git a/polkadot/statement-table/src/generic.rs b/polkadot/statement-table/src/generic.rs index 2389efdbdf76c93d6b3815e79af852e3503bbcfd..c7aa9bf93d324be854bdf2001e05835343a92930 100644 --- a/polkadot/statement-table/src/generic.rs +++ b/polkadot/statement-table/src/generic.rs @@ -51,7 +51,7 @@ pub trait Context { /// Members are meant to submit candidates and vote on validity. fn is_member_of(&self, authority: &Self::AuthorityId, group: &Self::GroupId) -> bool; - // requisite number of votes for validity from a group. + /// requisite number of votes for validity from a group. fn requisite_votes(&self, group: &Self::GroupId) -> usize; } diff --git a/polkadot/statement-table/src/lib.rs b/polkadot/statement-table/src/lib.rs index c5304c780186cc3523ef1f5dbcfb833857f1cc86..85f5dbe50b069e25c1134945b76d9334db8f244a 100644 --- a/polkadot/statement-table/src/lib.rs +++ b/polkadot/statement-table/src/lib.rs @@ -26,7 +26,7 @@ pub mod generic; pub use generic::Table; use primitives::parachain::{ - Id, CandidateReceipt, Statement as PrimitiveStatement, ValidatorSignature, ValidatorId + Id, CandidateReceipt, Statement as PrimitiveStatement, ValidatorSignature, ValidatorIndex, }; use primitives::Hash; @@ -34,10 +34,10 @@ use primitives::Hash; pub type Statement = generic::Statement<CandidateReceipt, Hash>; /// Signed statements about candidates. -pub type SignedStatement = generic::SignedStatement<CandidateReceipt, Hash, ValidatorId, ValidatorSignature>; +pub type SignedStatement = generic::SignedStatement<CandidateReceipt, Hash, ValidatorIndex, ValidatorSignature>; /// Kinds of misbehavior, along with proof. -pub type Misbehavior = generic::Misbehavior<CandidateReceipt, Hash, ValidatorId, ValidatorSignature>; +pub type Misbehavior = generic::Misbehavior<CandidateReceipt, Hash, ValidatorIndex, ValidatorSignature>; /// A summary of import of a statement. pub type Summary = generic::Summary<Hash, Id>; @@ -46,14 +46,14 @@ pub type Summary = generic::Summary<Hash, Id>; pub trait Context { /// Whether a authority is a member of a group. /// Members are meant to submit candidates and vote on validity. - fn is_member_of(&self, authority: &ValidatorId, group: &Id) -> bool; + fn is_member_of(&self, authority: ValidatorIndex, group: &Id) -> bool; - // requisite number of votes for validity from a group. + /// requisite number of votes for validity from a group. fn requisite_votes(&self, group: &Id) -> usize; } impl<C: Context> generic::Context for C { - type AuthorityId = ValidatorId; + type AuthorityId = ValidatorIndex; type Digest = Hash; type GroupId = Id; type Signature = ValidatorSignature; @@ -67,8 +67,8 @@ impl<C: Context> generic::Context for C { candidate.parachain_index.clone() } - fn is_member_of(&self, authority: &ValidatorId, group: &Id) -> bool { - Context::is_member_of(self, authority, group) + fn is_member_of(&self, authority: &Self::AuthorityId, group: &Id) -> bool { + Context::is_member_of(self, *authority, group) } fn requisite_votes(&self, group: &Id) -> usize { diff --git a/polkadot/validation/src/lib.rs b/polkadot/validation/src/lib.rs index 9c0dd9b5430f0d22c82f1f24239a2b53d6ca9618..64753e5e2b52f8c125dfcfae768163981bb6337c 100644 --- a/polkadot/validation/src/lib.rs +++ b/polkadot/validation/src/lib.rs @@ -187,9 +187,9 @@ pub trait Network { #[derive(Debug, Clone, Default)] pub struct GroupInfo { /// Authorities meant to check validity of candidates. - pub validity_guarantors: HashSet<SessionKey>, + validity_guarantors: HashSet<SessionKey>, /// Number of votes needed for validity. - pub needed_validity: usize, + needed_validity: usize, } /// Sign a table statement against a parent hash. @@ -347,7 +347,7 @@ impl<C, N, P> ParachainValidation<C, N, P> where debug!(target: "validation", "Active parachains: {:?}", active_parachains); - let table = Arc::new(SharedTable::new(group_info, sign_with.clone(), parent_hash, self.extrinsic_store.clone())); + let table = Arc::new(SharedTable::new(authorities, group_info, sign_with.clone(), parent_hash, self.extrinsic_store.clone())); let router = self.network.communication_for( table.clone(), outgoing, diff --git a/polkadot/validation/src/shared_table/mod.rs b/polkadot/validation/src/shared_table/mod.rs index 2f54331a7ef5a36d03c6a1ce2e25627a12ab682a..ccddaba95596d9c8cd399ece3d36abaf0025bccf 100644 --- a/polkadot/validation/src/shared_table/mod.rs +++ b/polkadot/validation/src/shared_table/mod.rs @@ -23,9 +23,8 @@ use std::sync::Arc; use extrinsic_store::{Data, Store as ExtrinsicStore}; use table::{self, Table, Context as TableContextTrait}; use polkadot_primitives::{Block, BlockId, Hash, SessionKey}; -use polkadot_primitives::parachain::{ - Id as ParaId, Collation, Extrinsic, CandidateReceipt, - AttestedCandidate, ParachainHost, PoVBlock +use polkadot_primitives::parachain::{Id as ParaId, Collation, Extrinsic, CandidateReceipt, + AttestedCandidate, ParachainHost, PoVBlock, ValidatorIndex, }; use parking_lot::Mutex; @@ -46,11 +45,17 @@ struct TableContext { parent_hash: Hash, key: Arc<ed25519::Pair>, groups: HashMap<ParaId, GroupInfo>, + index_mapping: HashMap<ValidatorIndex, SessionKey>, } impl table::Context for TableContext { - fn is_member_of(&self, authority: &SessionKey, group: &ParaId) -> bool { - self.groups.get(group).map_or(false, |g| g.validity_guarantors.contains(authority)) + fn is_member_of(&self, authority: ValidatorIndex, group: &ParaId) -> bool { + let key = match self.index_mapping.get(&authority) { + Some(val) => val, + None => return false, + }; + + self.groups.get(group).map_or(false, |g| g.validity_guarantors.get(&key).is_some()) } fn requisite_votes(&self, group: &ParaId) -> usize { @@ -63,13 +68,23 @@ impl TableContext { self.key.public().into() } + fn local_index(&self) -> ValidatorIndex { + let id = self.local_id(); + self + .index_mapping + .iter() + .find(|(_, k)| k == &&id) + .map(|(i, _)| *i) + .unwrap() + } + fn sign_statement(&self, statement: table::Statement) -> table::SignedStatement { let signature = ::sign_table_statement(&statement, &self.key, &self.parent_hash).into(); table::SignedStatement { statement, signature, - sender: self.local_id(), + sender: self.local_index(), } } } @@ -131,9 +146,9 @@ impl SharedTableInner { self.update_trackers(&summary.candidate, context); - let local_id = context.local_id(); + let local_index = context.local_index(); - let para_member = context.is_member_of(&local_id, &summary.group_id); + let para_member = context.is_member_of(local_index, &summary.group_id); let digest = &summary.candidate; @@ -375,13 +390,15 @@ impl SharedTable { /// Provide the key to sign with, and the parent hash of the relay chain /// block being built. pub fn new( + authorities: &[ed25519::Public], groups: HashMap<ParaId, GroupInfo>, key: Arc<ed25519::Pair>, parent_hash: Hash, extrinsic_store: ExtrinsicStore, ) -> Self { - SharedTable { - context: Arc::new(TableContext { groups, key, parent_hash }), + let index_mapping = authorities.iter().enumerate().map(|(i, k)| (i as ValidatorIndex, k.clone())).collect(); + Self { + context: Arc::new(TableContext { groups, key, parent_hash, index_mapping, }), inner: Arc::new(Mutex::new(SharedTableInner { table: Table::default(), validated: HashMap::new(), @@ -513,7 +530,7 @@ impl SharedTable { } /// Get all witnessed misbehavior. - pub fn get_misbehavior(&self) -> HashMap<SessionKey, table::Misbehavior> { + pub fn get_misbehavior(&self) -> HashMap<ValidatorIndex, table::Misbehavior> { self.inner.lock().table.get_misbehavior().clone() } @@ -534,6 +551,11 @@ impl SharedTable { rx } + + /// Returns id of the validator corresponding to the given index. + pub fn index_to_id(&self, index: ValidatorIndex) -> Option<SessionKey> { + self.context.index_mapping.get(&index).cloned() + } } #[cfg(test)] @@ -577,13 +599,15 @@ mod tests { let validity_other_key = AuthorityKeyring::Bob.pair(); let validity_other = validity_other_key.public(); + let validity_other_index = 1; groups.insert(para_id, GroupInfo { - validity_guarantors: [local_id, validity_other.clone()].iter().cloned().collect(), + validity_guarantors: [local_id.clone(), validity_other.clone()].iter().cloned().collect(), needed_validity: 2, }); let shared_table = SharedTable::new( + &[local_id, validity_other], groups, local_key.clone(), parent_hash, @@ -607,7 +631,7 @@ mod tests { let signed_statement = ::table::generic::SignedStatement { statement: candidate_statement, signature: signature.into(), - sender: validity_other, + sender: validity_other_index, }; shared_table.import_remote_statement( @@ -628,13 +652,15 @@ mod tests { let validity_other_key = AuthorityKeyring::Bob.pair(); let validity_other = validity_other_key.public(); + let validity_other_index = 1; groups.insert(para_id, GroupInfo { - validity_guarantors: [local_id, validity_other.clone()].iter().cloned().collect(), + validity_guarantors: [local_id.clone(), validity_other.clone()].iter().cloned().collect(), needed_validity: 1, }); let shared_table = SharedTable::new( + &[local_id, validity_other], groups, local_key.clone(), parent_hash, @@ -658,7 +684,7 @@ mod tests { let signed_statement = ::table::generic::SignedStatement { statement: candidate_statement, signature: signature.into(), - sender: validity_other, + sender: validity_other_index, }; shared_table.import_remote_statement( @@ -758,13 +784,15 @@ mod tests { let validity_other_key = AuthorityKeyring::Bob.pair(); let validity_other = validity_other_key.public(); + let validity_other_index = 1; groups.insert(para_id, GroupInfo { - validity_guarantors: [local_id, validity_other.clone()].iter().cloned().collect(), + validity_guarantors: [local_id.clone(), validity_other.clone()].iter().cloned().collect(), needed_validity: 1, }); let shared_table = SharedTable::new( + &[local_id, validity_other], groups, local_key.clone(), parent_hash, @@ -789,7 +817,7 @@ mod tests { let signed_statement = ::table::generic::SignedStatement { statement: candidate_statement, signature: signature.into(), - sender: validity_other, + sender: validity_other_index, }; let _a = shared_table.import_remote_statement( @@ -823,11 +851,12 @@ mod tests { let validity_other = validity_other_key.public(); groups.insert(para_id, GroupInfo { - validity_guarantors: [local_id, validity_other].iter().cloned().collect(), + validity_guarantors: [local_id.clone(), validity_other.clone()].iter().cloned().collect(), needed_validity: 1, }); let shared_table = SharedTable::new( + &[local_id, validity_other], groups, local_key.clone(), parent_hash, @@ -861,4 +890,28 @@ mod tests { assert!(a.is_none()); } + + #[test] + fn index_mapping_from_authorities() { + let authorities_set: &[&[_]] = &[ + &[], + &[AuthorityKeyring::Alice.pair().public()], + &[AuthorityKeyring::Alice.pair().public(), AuthorityKeyring::Bob.pair().public()], + &[AuthorityKeyring::Bob.pair().public(), AuthorityKeyring::Alice.pair().public()], + &[AuthorityKeyring::Alice.pair().public(), AuthorityKeyring::Bob.pair().public(), AuthorityKeyring::Charlie.pair().public()], + &[AuthorityKeyring::Charlie.pair().public(), AuthorityKeyring::Bob.pair().public(), AuthorityKeyring::Alice.pair().public()], + ]; + + for authorities in authorities_set { + let shared_table = SharedTable::new( + authorities, + HashMap::new(), + Arc::new(AuthorityKeyring::Alice.pair()), + Default::default(), + ExtrinsicStore::new_in_memory(), + ); + let expected_mapping = authorities.iter().enumerate().map(|(i, k)| (i as ValidatorIndex, k.clone())).collect(); + assert_eq!(shared_table.context.index_mapping, expected_mapping); + } + } }