diff --git a/Cargo.lock b/Cargo.lock index 4f8cb28e8eec..233b8b8b57df 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3956,6 +3956,7 @@ dependencies = [ "sp-inherents 2.0.0-alpha.5 (git+https://github.com/paritytech/substrate)", "sp-runtime 2.0.0-alpha.5 (git+https://github.com/paritytech/substrate)", "sp-serializer 2.0.0-alpha.5 (git+https://github.com/paritytech/substrate)", + "sp-staking 2.0.0-alpha.5 (git+https://github.com/paritytech/substrate)", "sp-std 2.0.0-alpha.5 (git+https://github.com/paritytech/substrate)", "sp-trie 2.0.0-alpha.5 (git+https://github.com/paritytech/substrate)", "sp-version 2.0.0-alpha.5 (git+https://github.com/paritytech/substrate)", diff --git a/network/src/legacy/gossip/attestation.rs b/network/src/legacy/gossip/attestation.rs index b6023437cae0..7f6de6586e63 100644 --- a/network/src/legacy/gossip/attestation.rs +++ b/network/src/legacy/gossip/attestation.rs @@ -131,9 +131,13 @@ impl View { /// /// This will be pruned later on a call to `prune_old_leaves`, when this leaf /// is not a leaf anymore. - pub(super) fn new_local_leaf(&mut self, relay_chain_leaf: Hash, validation_data: MessageValidationData) { + pub(super) fn new_local_leaf( + &mut self, + validation_data: MessageValidationData, + ) { + let relay_chain_leaf = validation_data.signing_context.parent_hash.clone(); self.leaf_work.push(( - relay_chain_leaf, + validation_data.signing_context.parent_hash.clone(), LeafView { validation_data, knowledge: Default::default(), @@ -207,7 +211,6 @@ impl View { // validate signature. let res = view.validation_data.check_statement( - &message.relay_chain_leaf, &message.signed_statement, ); diff --git a/network/src/legacy/gossip/mod.rs b/network/src/legacy/gossip/mod.rs index df13cfc2590e..561873c990a4 100644 --- a/network/src/legacy/gossip/mod.rs +++ b/network/src/legacy/gossip/mod.rs @@ -60,7 +60,7 @@ use sc_network_gossip::{ use polkadot_validation::{SignedStatement}; use polkadot_primitives::{Block, Hash}; use polkadot_primitives::parachain::{ - ParachainHost, ValidatorId, ErasureChunk as PrimitiveChunk + ParachainHost, ValidatorId, ErasureChunk as PrimitiveChunk, SigningContext, }; use polkadot_erasure_coding::{self as erasure}; use codec::{Decode, Encode}; @@ -377,13 +377,12 @@ impl RegisteredMessageValidator { /// relevant to this leaf. pub(crate) fn new_local_leaf( &self, - relay_chain_leaf: Hash, validation: MessageValidationData, ) -> NewLeafActions { // add an entry in attestation_view // prune any entries from attestation_view which are no longer leaves let mut inner = self.inner.inner.write(); - inner.attestation_view.new_local_leaf(relay_chain_leaf, validation); + inner.attestation_view.new_local_leaf(validation); let mut actions = Vec::new(); @@ -460,11 +459,13 @@ impl GossipService for RegisteredMessageValidator { pub(crate) struct MessageValidationData { /// The authorities' parachain validation keys at a block. pub(crate) authorities: Vec, + /// The signing context. + pub(crate) signing_context: SigningContext, } impl MessageValidationData { // check a statement's signature. - fn check_statement(&self, relay_chain_leaf: &Hash, statement: &SignedStatement) -> Result<(), ()> { + fn check_statement(&self, statement: &SignedStatement) -> Result<(), ()> { let sender = match self.authorities.get(statement.sender as usize) { Some(val) => val, None => return Err(()), @@ -475,7 +476,7 @@ impl MessageValidationData { &statement.statement, &statement.signature, sender.clone(), - relay_chain_leaf, + &self.signing_context, ); if good { @@ -826,7 +827,9 @@ mod tests { let topic_c = attestation_topic(hash_c); // topic_a is in all 3 views -> succeed - validator.inner.write().attestation_view.new_local_leaf(hash_a, MessageValidationData::default()); + let mut validation_data = MessageValidationData::default(); + validation_data.signing_context.parent_hash = hash_a; + validator.inner.write().attestation_view.new_local_leaf(validation_data); // topic_b is in the neighbor's view but not ours -> fail // topic_c is not in either -> fail @@ -937,7 +940,9 @@ mod tests { } }); let encoded = statement.encode(); - validator.inner.write().attestation_view.new_local_leaf(hash_a, MessageValidationData::default()); + let mut validation_data = MessageValidationData::default(); + validation_data.signing_context.parent_hash = hash_a; + validator.inner.write().attestation_view.new_local_leaf(validation_data); { let mut message_allowed = validator.message_allowed(); diff --git a/network/src/protocol/mod.rs b/network/src/protocol/mod.rs index f78d84c39289..b63fa563867e 100644 --- a/network/src/protocol/mod.rs +++ b/network/src/protocol/mod.rs @@ -162,7 +162,6 @@ impl NetworkServiceOps for PolkadotNetworkService { trait GossipOps: Clone + Send + crate::legacy::GossipService + 'static { fn new_local_leaf( &self, - relay_parent: Hash, validation_data: crate::legacy::gossip::MessageValidationData, ) -> crate::legacy::gossip::NewLeafActions; @@ -177,10 +176,12 @@ trait GossipOps: Clone + Send + crate::legacy::GossipService + 'static { impl GossipOps for RegisteredMessageValidator { fn new_local_leaf( &self, - relay_parent: Hash, validation_data: crate::legacy::gossip::MessageValidationData, ) -> crate::legacy::gossip::NewLeafActions { - RegisteredMessageValidator::new_local_leaf(self, relay_parent, validation_data) + RegisteredMessageValidator::new_local_leaf( + self, + validation_data, + ) } fn register_availability_store( @@ -804,7 +805,6 @@ impl Worker where authorities: Vec, ) { // glue: let gossip know about our new local leaf. - let relay_parent = table.consensus_parent_hash().clone(); let (signal, exit) = exit_future::signal(); let key = table.session_key(); @@ -814,19 +814,20 @@ impl Worker where } } + let signing_context = table.signing_context().clone(); + let relay_parent = signing_context.parent_hash.clone(); let new_leaf_actions = self.gossip_handle.new_local_leaf( - relay_parent, - crate::legacy::gossip::MessageValidationData { authorities }, + crate::legacy::gossip::MessageValidationData { authorities, signing_context }, ); new_leaf_actions.perform(&self.gossip_handle); self.protocol_handler.consensus_instances.insert( - relay_parent, + relay_parent.clone(), ConsensusNetworkingInstance { statement_table: table.clone(), - relay_parent, - attestation_topic: crate::legacy::gossip::attestation_topic(relay_parent), + relay_parent: relay_parent.clone(), + attestation_topic: crate::legacy::gossip::attestation_topic(relay_parent.clone()), _drop_signal: signal, }, ); @@ -1324,7 +1325,7 @@ impl ParachainNetwork for Service { ) -> Self::BuildTableRouter { let authorities = authorities.to_vec(); let mut sender = self.sender.clone(); - let relay_parent = table.consensus_parent_hash().clone(); + let relay_parent = table.signing_context().parent_hash.clone(); Box::pin(async move { sender.send( diff --git a/network/src/protocol/tests.rs b/network/src/protocol/tests.rs index 652f1cff01a6..a114ccb37e32 100644 --- a/network/src/protocol/tests.rs +++ b/network/src/protocol/tests.rs @@ -20,7 +20,7 @@ use polkadot_primitives::{Block, Header, BlockId}; use polkadot_primitives::parachain::{ Id as ParaId, Chain, DutyRoster, ParachainHost, ValidatorId, Retriable, CollatorId, AbridgedCandidateReceipt, - GlobalValidationSchedule, LocalValidationData, ErasureChunk, + GlobalValidationSchedule, LocalValidationData, ErasureChunk, SigningContext, }; use polkadot_validation::SharedTable; @@ -114,7 +114,6 @@ impl crate::legacy::GossipService for MockGossip { impl GossipOps for MockGossip { fn new_local_leaf( &self, - _relay_parent: Hash, _validation_data: crate::legacy::gossip::MessageValidationData, ) -> crate::legacy::gossip::NewLeafActions { crate::legacy::gossip::NewLeafActions::new() @@ -294,6 +293,22 @@ impl ParachainHost for RuntimeApi { ) -> ClientResult>>> { Ok(NativeOrEncoded::Native(Some(Vec::new()))) } + + fn ParachainHost_signing_context_runtime_api_impl( + &self, + _at: &BlockId, + _: ExecutionContext, + _: Option<()>, + _: Vec, + ) -> ClientResult> { + Ok(NativeOrEncoded::Native( + SigningContext { + session_index: Default::default(), + parent_hash: Default::default(), + } + ) + ) + } } impl super::Service { @@ -389,11 +404,15 @@ fn consensus_instances_cleaned_up() { let relay_parent = [0; 32].into(); let authorities = Vec::new(); + let signing_context = SigningContext { + session_index: Default::default(), + parent_hash: relay_parent, + }; let table = Arc::new(SharedTable::new( Vec::new(), HashMap::new(), None, - relay_parent, + signing_context, AvailabilityStore::new_in_memory(service.clone()), None, )); diff --git a/primitives/Cargo.toml b/primitives/Cargo.toml index 1fbe50a85d68..d491828a449a 100644 --- a/primitives/Cargo.toml +++ b/primitives/Cargo.toml @@ -13,6 +13,7 @@ application-crypto = { package = "sp-application-crypto", git = "https://github. sp-api = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } sp-version = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } sp-std = { package = "sp-std", git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +sp-staking = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } runtime_primitives = { package = "sp-runtime", git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } polkadot-parachain = { path = "../parachain", default-features = false } trie = { package = "sp-trie", git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } @@ -33,6 +34,7 @@ std = [ "sp-api/std", "sp-std/std", "sp-version/std", + "sp-staking/std", "runtime_primitives/std", "serde", "polkadot-parachain/std", diff --git a/primitives/src/parachain.rs b/primitives/src/parachain.rs index 5e558667ac81..f65af30efba5 100644 --- a/primitives/src/parachain.rs +++ b/primitives/src/parachain.rs @@ -608,6 +608,15 @@ pub enum ValidityAttestation { Explicit(ValidatorSignature), } +/// A type returned by runtime with current session index and a parent hash. +#[derive(Clone, Eq, PartialEq, Default, Decode, Encode, RuntimeDebug)] +pub struct SigningContext { + /// Current session index. + pub session_index: sp_staking::SessionIndex, + /// Hash of the parent. + pub parent_hash: Hash, +} + /// An attested candidate. This is submitted to the relay chain by a block author. #[derive(Clone, PartialEq, Decode, Encode, RuntimeDebug)] pub struct AttestedCandidate { @@ -655,7 +664,7 @@ impl FeeSchedule { sp_api::decl_runtime_apis! { /// The API for querying the state of parachains on-chain. - #[api_version(2)] + #[api_version(3)] pub trait ParachainHost { /// Get the current validators. fn validators() -> Vec; @@ -673,6 +682,8 @@ sp_api::decl_runtime_apis! { /// Extract the abridged head that was set in the extrinsics. fn get_heads(extrinsics: Vec<::Extrinsic>) -> Option>; + /// Get a `SigningContext` with current `SessionIndex` and parent hash. + fn signing_context() -> SigningContext; } } diff --git a/runtime/common/src/parachains.rs b/runtime/common/src/parachains.rs index 81dffe736ad4..4ba82ea21f33 100644 --- a/runtime/common/src/parachains.rs +++ b/runtime/common/src/parachains.rs @@ -44,7 +44,7 @@ use primitives::{ UpwardMessage, ValidatorId, ActiveParas, CollatorId, Retriable, OmittedValidationData, CandidateReceipt, GlobalValidationSchedule, AbridgedCandidateReceipt, LocalValidationData, ValidityAttestation, NEW_HEADS_IDENTIFIER, PARACHAIN_KEY_TYPE_ID, - ValidatorSignature, + ValidatorSignature, SigningContext, }, }; use frame_support::{ @@ -121,7 +121,7 @@ pub struct ValidatorIdentities(sp_std::marker::PhantomData); /// `Hash` - a type of a hash used in the runtime. #[derive(RuntimeDebug, Encode, Decode)] #[derive(Clone, Eq, PartialEq)] -pub struct DoubleVoteReport { +pub struct DoubleVoteReport { /// Identity of the double-voter. pub identity: ValidatorId, /// First vote of the double-vote. @@ -130,11 +130,11 @@ pub struct DoubleVoteReport { pub second: (Statement, ValidatorSignature), /// Proof that the validator with `identity` id was actually a validator at `parent_hash`. pub proof: Proof, - /// Parent hash of the block this offence was commited. - pub parent_hash: Hash, + /// A `SigningContext` with a session and a parent hash of the moment this offence was commited. + pub signing_context: SigningContext, } -impl> DoubleVoteReport { +impl DoubleVoteReport { fn verify>( &self, ) -> Result<(), DoubleVoteValidityError> { @@ -145,9 +145,21 @@ impl> DoubleVoteReport> DoubleVoteReport Result<(), DoubleVoteValidityError> { - let payload = localized_payload(vote.0.clone(), parent_hash); + let payload = localized_payload(vote.0.clone(), signing_context); if !vote.1.verify(&payload[..], authority) { return Err(DoubleVoteValidityError::InvalidSignature); @@ -203,7 +215,7 @@ impl GetSessionNumber for session::historical::Proof { } } -pub trait Trait: attestations::Trait + session::historical::Trait + staking::Trait { +pub trait Trait: attestations::Trait + session::historical::Trait { /// The outer origin type. type Origin: From + From>; @@ -253,6 +265,9 @@ pub trait Trait: attestations::Trait + session::historical::Trait + staking::Tra Self::IdentificationTuple, DoubleVoteOffence >; + + /// A type that converts the opaque hash type to exact one. + type BlockHashConversion: Convert; } /// Origin for the parachains module. @@ -332,18 +347,6 @@ decl_storage! { /// /// `None` if not yet updated. pub DidUpdate: Option>; - - /// The mapping from parent block hashes to session indexes. - /// - /// Used for double vote report validation. - pub ParentToSessionIndex get(fn session_at_block): - map hasher(twox_64_concat) T::Hash => SessionIndex; - - /// The era that is active currently. - /// - /// Changes with the `ActiveEra` from `staking`. Upon these changes `ParentToSessionIndex` - /// is pruned. - ActiveEra get(fn active_era): Option; } add_extra_genesis { config(authorities): Vec; @@ -484,7 +487,6 @@ decl_module! { origin, report: DoubleVoteReport< )>>::Proof, - T::Hash, >, ) -> DispatchResult { let reporter = ensure_signed(origin)?; @@ -519,26 +521,6 @@ decl_module! { fn on_initialize() -> Weight { ::DidUpdate::kill(); - let current_session = >::current_index(); - let parent_hash = >::parent_hash(); - - match Self::active_era() { - Some(era) => { - if let Some(active_era) = >::current_era() { - if era != active_era { - ::ActiveEra::put(active_era); - >::remove_all(); - } - } - } - None => { - if let Some(active_era) = >::current_era() { - ::ActiveEra::set(Some(active_era)); - } - } - } - >::insert(parent_hash, current_session); - SimpleDispatchInfo::default().weigh_data(()) } @@ -552,9 +534,12 @@ fn majority_of(list_len: usize) -> usize { list_len / 2 + list_len % 2 } -fn localized_payload>(statement: Statement, parent_hash: H) -> Vec { +fn localized_payload( + statement: Statement, + signing_context: &SigningContext, +) -> Vec { let mut encoded = statement.encode(); - encoded.extend(parent_hash.as_ref()); + signing_context.using_encoded(|s| encoded.extend(s)); encoded } @@ -576,6 +561,17 @@ impl Module { ::remove(id); } + /// Get a `SigningContext` with a current `SessionIndex` and parent hash. + pub fn signing_context() -> SigningContext { + let session_index = >::current_index(); + let parent_hash = >::parent_hash(); + + SigningContext { + session_index, + parent_hash: T::BlockHashConversion::convert(parent_hash), + } + } + /// Dispatch some messages from a parachain. fn dispatch_message( id: ParaId, @@ -912,7 +908,8 @@ impl Module { let sorted_validators = make_sorted_duties(&duty_roster.validator_duty); let parent_hash = >::parent_hash(); - let localized_payload = |statement: Statement| localized_payload(statement, parent_hash); + let signing_context = Self::signing_context(); + let localized_payload = |statement: Statement| localized_payload(statement, &signing_context); let mut validator_groups = GroupedDutyIter::new(&sorted_validators[..]); @@ -1160,9 +1157,8 @@ impl SignedExtension for ValidateDoubleVoteReports wh if let Some(local_call) = call.is_sub_type() { if let Call::report_double_vote(report) = local_call { let validators = >::validators(); - let parent_hash = report.parent_hash; - let expected_session = Module::::session_at_block(parent_hash); + let expected_session = report.signing_context.session_index; let session = report.proof.session(); if session != expected_session { @@ -1467,6 +1463,7 @@ mod tests { type Proof = )>>::Proof; type IdentificationTuple = )>>::IdentificationTuple; type ReportOffence = Offences; + type BlockHashConversion = sp_runtime::traits::Identity; type KeyOwnerProofSystem = Historical; } @@ -1559,7 +1556,7 @@ mod tests { } fn report_double_vote( - report: DoubleVoteReport::Hash>, + report: DoubleVoteReport, ) -> Result, TransactionValidityError> { let inner = ParachainsCall::report_double_vote(report); let call = Call::Parachains(inner.clone()); @@ -1604,7 +1601,6 @@ mod tests { fn make_attestations(candidate: &mut AttestedCandidate) { let mut vote_implicit = false; - let parent_hash = >::parent_hash(); let (duty_roster, _) = Parachains::calculate_duty_roster(); let candidate_hash = candidate.candidate.hash(); @@ -1632,7 +1628,8 @@ mod tests { Statement::Valid(candidate_hash.clone()) }; - let payload = localized_payload(statement, parent_hash); + let signing_context = Parachains::signing_context(); + let payload = localized_payload(statement, &signing_context); let signature = key.sign(&payload[..]).into(); candidate.validity_votes.push(if vote_implicit { @@ -2209,10 +2206,10 @@ mod tests { let statement_candidate = Statement::Candidate(candidate_hash.clone()); let statement_valid = Statement::Valid(candidate_hash.clone()); - let parent_hash = System::parent_hash(); - let payload_1 = localized_payload(statement_candidate.clone(), parent_hash); - let payload_2 = localized_payload(statement_valid.clone(), parent_hash); + let signing_context = Parachains::signing_context(); + let payload_1 = localized_payload(statement_candidate.clone(), &signing_context); + let payload_2 = localized_payload(statement_valid.clone(), &signing_context); let signature_1 = key.sign(&payload_1[..]).into(); let signature_2 = key.sign(&payload_2[..]).into(); @@ -2240,7 +2237,7 @@ mod tests { first: (statement_candidate, signature_1), second: (statement_valid, signature_2), proof, - parent_hash, + signing_context, }; let inner = report_double_vote(report).unwrap(); @@ -2304,10 +2301,10 @@ mod tests { let statement_candidate = Statement::Candidate(candidate_hash); let statement_invalid = Statement::Invalid(candidate_hash.clone()); - let parent_hash = System::parent_hash(); - let payload_1 = localized_payload(statement_candidate.clone(), parent_hash); - let payload_2 = localized_payload(statement_invalid.clone(), parent_hash); + let signing_context = Parachains::signing_context(); + let payload_1 = localized_payload(statement_candidate.clone(), &signing_context); + let payload_2 = localized_payload(statement_invalid.clone(), &signing_context); let signature_1 = key.sign(&payload_1[..]).into(); let signature_2 = key.sign(&payload_2[..]).into(); @@ -2335,7 +2332,7 @@ mod tests { first: (statement_candidate, signature_1), second: (statement_invalid, signature_2), proof, - parent_hash, + signing_context, }; assert_ok!(Parachains::dispatch( @@ -2401,10 +2398,10 @@ mod tests { let statement_invalid = Statement::Invalid(candidate_hash.clone()); let statement_valid = Statement::Valid(candidate_hash.clone()); - let parent_hash = System::parent_hash(); - let payload_1 = localized_payload(statement_invalid.clone(), parent_hash); - let payload_2 = localized_payload(statement_valid.clone(), parent_hash); + let signing_context = Parachains::signing_context(); + let payload_1 = localized_payload(statement_invalid.clone(), &signing_context); + let payload_2 = localized_payload(statement_valid.clone(), &signing_context); let signature_1 = key.sign(&payload_1[..]).into(); let signature_2 = key.sign(&payload_2[..]).into(); @@ -2432,7 +2429,7 @@ mod tests { first: (statement_invalid, signature_1), second: (statement_valid, signature_2), proof, - parent_hash, + signing_context, }; assert_ok!(Parachains::dispatch( @@ -2501,10 +2498,10 @@ mod tests { let statement_candidate = Statement::Candidate(candidate_hash.clone()); let statement_valid = Statement::Valid(candidate_hash.clone()); - let parent_hash = System::parent_hash(); - let payload_1 = localized_payload(statement_candidate.clone(), parent_hash); - let payload_2 = localized_payload(statement_valid.clone(), parent_hash); + let signing_context = Parachains::signing_context(); + let payload_1 = localized_payload(statement_candidate.clone(), &signing_context); + let payload_2 = localized_payload(statement_valid.clone(), &signing_context); let signature_1 = key.sign(&payload_1[..]).into(); let signature_2 = key.sign(&payload_2[..]).into(); @@ -2532,7 +2529,7 @@ mod tests { first: (statement_candidate, signature_1), second: (statement_valid, signature_2), proof, - parent_hash, + signing_context, }; assert_ok!(Parachains::dispatch( @@ -2609,10 +2606,10 @@ mod tests { let statement_candidate = Statement::Candidate(candidate_hash.clone()); let statement_valid = Statement::Valid(candidate_hash.clone()); - let parent_hash = System::parent_hash(); - let payload_1 = localized_payload(statement_candidate.clone(), parent_hash); - let payload_2 = localized_payload(statement_valid.clone(), parent_hash); + let signing_context = Parachains::signing_context(); + let payload_1 = localized_payload(statement_candidate.clone(), &signing_context); + let payload_2 = localized_payload(statement_valid.clone(), &signing_context); let signature_1 = key_1.sign(&payload_1[..]).into(); let signature_2 = key_2.sign(&payload_2[..]).into(); @@ -2625,7 +2622,7 @@ mod tests { first: (statement_candidate, signature_1), second: (statement_valid, signature_2), proof, - parent_hash, + signing_context, }; assert_eq!( @@ -2669,8 +2666,12 @@ mod tests { let statement_valid = Statement::Valid(candidate_hash.clone()); let parent_hash = System::parent_hash(); - let payload_1 = localized_payload(statement_candidate.clone(), parent_hash); - let payload_2 = localized_payload(statement_valid.clone(), parent_hash); + let signing_context = SigningContext { + session_index: Session::current_index() - 1, + parent_hash, + }; + let payload_1 = localized_payload(statement_candidate.clone(), &signing_context); + let payload_2 = localized_payload(statement_valid.clone(), &signing_context); let signature_1 = key.sign(&payload_1[..]).into(); let signature_2 = key.sign(&payload_2[..]).into(); @@ -2700,7 +2701,7 @@ mod tests { first: (statement_candidate, signature_1), second: (statement_valid, signature_2), proof, - parent_hash, + signing_context, }; assert!(report_double_vote(report.clone()).is_err()); @@ -2723,29 +2724,4 @@ mod tests { } }); } - - #[test] - fn double_vote_hash_to_session_gets_pruned() { - let parachains = vec![ - (1u32.into(), vec![], vec![]), - ]; - - // Test that `ParentToSessionIndex` is pruned upon eras turn. - new_test_ext(parachains.clone()).execute_with(|| { - start_era(1); - - let parent_hash_1 = System::parent_hash(); - // The mapping should know about the session at this block. - assert_eq!(Parachains::session_at_block(parent_hash_1), 3); - - start_era(2); - - let parent_hash_2 = System::parent_hash(); - - // Info about a block from pevious era is removed. - assert_eq!(Parachains::session_at_block(parent_hash_1), 0); - // Info about a block form this era is present. - assert_eq!(Parachains::session_at_block(parent_hash_2), 6); - }); - } } diff --git a/runtime/common/src/registrar.rs b/runtime/common/src/registrar.rs index 122ca318a3a6..2745c9a93b89 100644 --- a/runtime/common/src/registrar.rs +++ b/runtime/common/src/registrar.rs @@ -827,6 +827,7 @@ mod tests { type KeyOwnerProofSystem = session::historical::Module; type IdentificationTuple = )>>::IdentificationTuple; type ReportOffence = (); + type BlockHashConversion = sp_runtime::traits::Identity; } parameter_types! { @@ -973,7 +974,7 @@ mod tests { }; let (candidate, _) = candidate.abridge(); let candidate_hash = candidate.hash(); - let payload = (Statement::Valid(candidate_hash), System::parent_hash()).encode(); + let payload = (Statement::Valid(candidate_hash), session::Module::::current_index(), System::parent_hash()).encode(); let roster = Parachains::calculate_duty_roster().0.validator_duty; AttestedCandidate { candidate, diff --git a/runtime/kusama/src/lib.rs b/runtime/kusama/src/lib.rs index a1b4dcce8c06..57fe89e3997a 100644 --- a/runtime/kusama/src/lib.rs +++ b/runtime/kusama/src/lib.rs @@ -25,7 +25,7 @@ use sp_core::u32_trait::{_1, _2, _3, _4, _5}; use codec::{Encode, Decode}; use primitives::{ AccountId, AccountIndex, Balance, BlockNumber, Hash, Nonce, Signature, Moment, - parachain::{self, ActiveParas, AbridgedCandidateReceipt}, ValidityError, + parachain::{self, ActiveParas, AbridgedCandidateReceipt, SigningContext}, ValidityError, }; use runtime_common::{attestations, claims, parachains, registrar, slots, impls::{CurrencyToVoteHandler, TargetedFeeAdjustment, ToAuthor}, @@ -496,6 +496,7 @@ impl parachains::Trait for Runtime { type KeyOwnerProofSystem = session::historical::Module; type IdentificationTuple = )>>::IdentificationTuple; type ReportOffence = Offences; + type BlockHashConversion = sp_runtime::traits::Identity; } parameter_types! { @@ -816,6 +817,9 @@ sp_api::impl_runtime_apis! { Err(_) => None, }) } + fn signing_context() -> SigningContext { + Parachains::signing_context() + } } impl fg_primitives::GrandpaApi for Runtime { diff --git a/runtime/polkadot/src/lib.rs b/runtime/polkadot/src/lib.rs index 8c3fe5d74271..c33ef0fcf742 100644 --- a/runtime/polkadot/src/lib.rs +++ b/runtime/polkadot/src/lib.rs @@ -31,7 +31,7 @@ use sp_core::u32_trait::{_1, _2, _3, _4, _5}; use codec::{Encode, Decode}; use primitives::{ AccountId, AccountIndex, Balance, BlockNumber, Hash, Nonce, Signature, Moment, - parachain::{self, ActiveParas, AbridgedCandidateReceipt}, ValidityError, + parachain::{self, ActiveParas, AbridgedCandidateReceipt, SigningContext}, ValidityError, }; use sp_runtime::{ create_runtime_str, generic, impl_opaque_keys, @@ -504,6 +504,7 @@ impl parachains::Trait for Runtime { type KeyOwnerProofSystem = session::historical::Module; type IdentificationTuple = )>>::IdentificationTuple; type ReportOffence = Offences; + type BlockHashConversion = sp_runtime::traits::Identity; } parameter_types! { @@ -738,6 +739,9 @@ sp_api::impl_runtime_apis! { Err(_) => None, }) } + fn signing_context() -> SigningContext { + Parachains::signing_context() + } } impl fg_primitives::GrandpaApi for Runtime { diff --git a/runtime/test-runtime/src/lib.rs b/runtime/test-runtime/src/lib.rs index 36dbeb60c2b6..13cea99069e0 100644 --- a/runtime/test-runtime/src/lib.rs +++ b/runtime/test-runtime/src/lib.rs @@ -24,7 +24,7 @@ use rstd::prelude::*; use codec::{Encode, Decode}; use primitives::{ AccountId, AccountIndex, Balance, BlockNumber, Hash as HashT, Nonce, Signature, Moment, - parachain::{self, ActiveParas, AbridgedCandidateReceipt}, ValidityError, + parachain::{self, ActiveParas, AbridgedCandidateReceipt, SigningContext}, ValidityError, }; use runtime_common::{attestations, claims, parachains, registrar, slots, impls::{CurrencyToVoteHandler, TargetedFeeAdjustment}, @@ -319,6 +319,7 @@ impl parachains::Trait for Runtime { Self::KeyOwnerProofSystem as KeyOwnerProofSystem<(KeyTypeId, Vec)> >::IdentificationTuple; type ReportOffence = Offences; + type BlockHashConversion = sp_runtime::traits::Identity; } impl offences::Trait for Runtime { @@ -536,6 +537,9 @@ sp_api::impl_runtime_apis! { Err(_) => None, }) } + fn signing_context() -> SigningContext { + Parachains::signing_context() + } } impl fg_primitives::GrandpaApi for Runtime { diff --git a/validation/src/lib.rs b/validation/src/lib.rs index 07d4bd905cd4..65fb6c1d4684 100644 --- a/validation/src/lib.rs +++ b/validation/src/lib.rs @@ -34,12 +34,11 @@ use std::{ sync::Arc, }; use codec::Encode; -use polkadot_primitives::Hash; use polkadot_primitives::parachain::{ Id as ParaId, Chain, DutyRoster, AbridgedCandidateReceipt, Statement as PrimitiveStatement, PoVBlock, ErasureChunk, ValidatorSignature, ValidatorIndex, - ValidatorPair, ValidatorId, + ValidatorPair, ValidatorId, SigningContext, }; use primitives::Pair; @@ -135,9 +134,13 @@ pub struct GroupInfo { /// Sign a table statement against a parent hash. /// The actual message signed is the encoded statement concatenated with the /// parent hash. -pub fn sign_table_statement(statement: &Statement, key: &ValidatorPair, parent_hash: &Hash) -> ValidatorSignature { +pub fn sign_table_statement( + statement: &Statement, + key: &ValidatorPair, + signing_context: &SigningContext, +) -> ValidatorSignature { let mut encoded = PrimitiveStatement::from(statement).encode(); - encoded.extend(parent_hash.as_ref()); + encoded.extend(signing_context.encode()); key.sign(&encoded) } @@ -147,12 +150,12 @@ pub fn check_statement( statement: &Statement, signature: &ValidatorSignature, signer: ValidatorId, - parent_hash: &Hash, + signing_context: &SigningContext, ) -> bool { use runtime_primitives::traits::AppVerify; let mut encoded = PrimitiveStatement::from(statement).encode(); - encoded.extend(parent_hash.as_ref()); + encoded.extend(signing_context.encode()); signature.verify(&encoded[..], &signer) } @@ -214,11 +217,19 @@ mod tests { fn sign_and_check_statement() { let statement: Statement = GenericStatement::Valid([1; 32].into()); let parent_hash = [2; 32].into(); - - let sig = sign_table_statement(&statement, &Sr25519Keyring::Alice.pair().into(), &parent_hash); - - assert!(check_statement(&statement, &sig, Sr25519Keyring::Alice.public().into(), &parent_hash)); - assert!(!check_statement(&statement, &sig, Sr25519Keyring::Alice.public().into(), &[0xff; 32].into())); - assert!(!check_statement(&statement, &sig, Sr25519Keyring::Bob.public().into(), &parent_hash)); + let signing_context = SigningContext { + session_index: Default::default(), + parent_hash, + }; + + let sig = sign_table_statement(&statement, &Sr25519Keyring::Alice.pair().into(), &signing_context); + + let wrong_signing_context = SigningContext { + session_index: Default::default(), + parent_hash: [0xff; 32].into(), + }; + assert!(check_statement(&statement, &sig, Sr25519Keyring::Alice.public().into(), &signing_context)); + assert!(!check_statement(&statement, &sig, Sr25519Keyring::Alice.public().into(), &wrong_signing_context)); + assert!(!check_statement(&statement, &sig, Sr25519Keyring::Bob.public().into(), &signing_context)); } } diff --git a/validation/src/shared_table/mod.rs b/validation/src/shared_table/mod.rs index 2b6a8e62271b..e9ae7ab5db57 100644 --- a/validation/src/shared_table/mod.rs +++ b/validation/src/shared_table/mod.rs @@ -25,7 +25,7 @@ use table::{self, Table, Context as TableContextTrait}; use polkadot_primitives::{Block, Hash}; use polkadot_primitives::parachain::{ Id as ParaId, AbridgedCandidateReceipt, ValidatorPair, ValidatorId, - AttestedCandidate, ParachainHost, PoVBlock, ValidatorIndex, + AttestedCandidate, ParachainHost, PoVBlock, ValidatorIndex, SigningContext, }; use parking_lot::Mutex; @@ -48,7 +48,7 @@ pub use table::{SignedStatement, Statement}; pub use table::generic::Statement as GenericStatement; struct TableContext { - parent_hash: Hash, + signing_context: SigningContext, key: Option>, groups: HashMap, validators: Vec, @@ -87,7 +87,12 @@ impl TableContext { fn sign_statement(&self, statement: table::Statement) -> Option { self.local_index().and_then(move |sender| self.key.as_ref() - .map(|key| crate::sign_table_statement(&statement, key, &self.parent_hash).into()) + .map(|key| crate::sign_table_statement( + &statement, + key, + &self.signing_context, + ).into() + ) .map(move |signature| table::SignedStatement { statement, signature, sender }) ) } @@ -189,7 +194,7 @@ impl SharedTableInner { work.map(|work| ParachainWork { availability_store: self.availability_store.clone(), - relay_parent: context.parent_hash.clone(), + relay_parent: context.signing_context.parent_hash.clone(), work, max_block_data_size, n_validators: context.validators.len(), @@ -408,12 +413,12 @@ impl SharedTable { validators: Vec, groups: HashMap, key: Option>, - parent_hash: Hash, + signing_context: SigningContext, availability_store: AvailabilityStore, max_block_data_size: Option, ) -> Self { SharedTable { - context: Arc::new(TableContext { groups, key, parent_hash, validators: validators.clone(), }), + context: Arc::new(TableContext { groups, key, signing_context, validators: validators.clone(), }), max_block_data_size, inner: Arc::new(Mutex::new(SharedTableInner { table: Table::default(), @@ -425,8 +430,8 @@ impl SharedTable { } /// Get the parent hash this table should hold statements localized to. - pub fn consensus_parent_hash(&self) -> &Hash { - &self.context.parent_hash + pub fn signing_context(&self) -> &SigningContext { + &self.context.signing_context } /// Get the local validator session key. @@ -654,7 +659,11 @@ mod tests { let mut groups = HashMap::new(); let para_id = ParaId::from(1); - let parent_hash = Default::default(); + let parent_hash = Hash::default(); + let signing_context = SigningContext { + session_index: Default::default(), + parent_hash: parent_hash.clone(), + }; let local_key = Sr25519Keyring::Alice.pair(); let local_id: ValidatorId = local_key.public().into(); @@ -673,7 +682,7 @@ mod tests { [local_id, validity_other].to_vec(), groups, Some(local_key.clone()), - parent_hash, + signing_context.clone(), AvailabilityStore::new_in_memory(DummyErasureNetworking), None, ); @@ -684,7 +693,11 @@ mod tests { let candidate_statement = GenericStatement::Candidate(candidate); - let signature = crate::sign_table_statement(&candidate_statement, &validity_other_key.into(), &parent_hash); + let signature = crate::sign_table_statement( + &candidate_statement, + &validity_other_key.into(), + &signing_context, + ); let signed_statement = ::table::generic::SignedStatement { statement: candidate_statement, signature: signature.into(), @@ -702,7 +715,11 @@ mod tests { let mut groups = HashMap::new(); let para_id = ParaId::from(1); - let parent_hash = Default::default(); + let parent_hash = Hash::default(); + let signing_context = SigningContext { + session_index: Default::default(), + parent_hash: parent_hash.clone(), + }; let local_key = Sr25519Keyring::Alice.pair(); let local_id: ValidatorId = local_key.public().into(); @@ -721,7 +738,7 @@ mod tests { [local_id, validity_other].to_vec(), groups, Some(local_key.clone()), - parent_hash, + signing_context.clone(), AvailabilityStore::new_in_memory(DummyErasureNetworking), None, ); @@ -732,7 +749,11 @@ mod tests { let candidate_statement = GenericStatement::Candidate(candidate); - let signature = crate::sign_table_statement(&candidate_statement, &validity_other_key.into(), &parent_hash); + let signature = crate::sign_table_statement( + &candidate_statement, + &validity_other_key.into(), + &signing_context, + ); let signed_statement = ::table::generic::SignedStatement { statement: candidate_statement, signature: signature.into(), @@ -874,7 +895,11 @@ mod tests { let mut groups = HashMap::new(); let para_id = ParaId::from(1); - let parent_hash = Default::default(); + let parent_hash = Hash::default(); + let signing_context = SigningContext { + session_index: Default::default(), + parent_hash: parent_hash.clone(), + }; let local_key = Sr25519Keyring::Alice.pair(); let local_id: ValidatorId = local_key.public().into(); @@ -893,7 +918,7 @@ mod tests { [local_id, validity_other].to_vec(), groups, Some(local_key.clone()), - parent_hash, + signing_context.clone(), AvailabilityStore::new_in_memory(DummyErasureNetworking), None, ); @@ -905,7 +930,11 @@ mod tests { let candidate_hash = candidate.hash(); let candidate_statement = GenericStatement::Candidate(candidate); - let signature = crate::sign_table_statement(&candidate_statement, &validity_other_key.into(), &parent_hash); + let signature = crate::sign_table_statement( + &candidate_statement, + &validity_other_key.into(), + &signing_context, + ); let signed_statement = ::table::generic::SignedStatement { statement: candidate_statement, signature: signature.into(), @@ -934,7 +963,11 @@ mod tests { let para_id = ParaId::from(1); let pov_block = pov_block_with_data(vec![1, 2, 3]); - let parent_hash = Default::default(); + let parent_hash = Hash::default(); + let signing_context = SigningContext { + session_index: Default::default(), + parent_hash: parent_hash.clone(), + }; let local_key = Sr25519Keyring::Alice.pair(); let local_id: ValidatorId = local_key.public().into(); @@ -952,7 +985,7 @@ mod tests { [local_id, validity_other].to_vec(), groups, Some(local_key.clone()), - parent_hash, + signing_context.clone(), AvailabilityStore::new_in_memory(DummyErasureNetworking), None, ); diff --git a/validation/src/validation_service/mod.rs b/validation/src/validation_service/mod.rs index 649c98234e10..e4208d695ad9 100644 --- a/validation/src/validation_service/mod.rs +++ b/validation/src/validation_service/mod.rs @@ -36,7 +36,7 @@ use consensus::SelectChain; use futures::{future::ready, prelude::*, task::{Spawn, SpawnExt}}; use polkadot_primitives::{Block, Hash, BlockId}; use polkadot_primitives::parachain::{ - Chain, ParachainHost, Id as ParaId, ValidatorIndex, ValidatorId, ValidatorPair, + Chain, ParachainHost, Id as ParaId, ValidatorIndex, ValidatorId, ValidatorPair, SigningContext, }; use babe_primitives::BabeApi; use keystore::KeyStorePtr; @@ -44,7 +44,7 @@ use sp_api::{ApiExt, ProvideRuntimeApi}; use runtime_primitives::traits::HashFor; use availability_store::Store as AvailabilityStore; -use log::{warn, error, info, debug}; +use log::{warn, error, info, debug, trace}; use super::{Network, Collators, SharedTable, TableRouter}; use crate::Error; @@ -334,11 +334,29 @@ impl ParachainValidationInstances where } } + let api = self.client.runtime_api(); + + let signing_context = if api.has_api_with::, _>( + &BlockId::hash(parent_hash), + |version| version >= 3, + )? { + api.signing_context(&id)? + } else { + trace!( + target: "validation", + "Expected runtime with ParachainHost version >= 3", + ); + SigningContext { + session_index: 0, + parent_hash, + } + }; + let table = Arc::new(SharedTable::new( validators.clone(), group_info, sign_with, - parent_hash, + signing_context, self.availability_store.clone(), max_block_data_size, ));