From 1c2aff5b4d15d83274bdf751125ccd2b5f7ce4ac Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Mon, 10 Feb 2020 10:14:12 +0100 Subject: [PATCH] Include parent head in `CandidateReceipt` (#826) * runtime: candidate receipt must pass parent head * construct parachain candidates using correct parent_head * validate that the parent header is correct in candidate receipt * fix test fallout * bump runtime versions --- polkadot/availability-store/src/store.rs | 3 ++ polkadot/network/src/collator_pool.rs | 2 + polkadot/network/src/gossip.rs | 1 + polkadot/network/src/tests/mod.rs | 1 + polkadot/primitives/src/parachain.rs | 2 + polkadot/runtime/common/src/parachains.rs | 28 ++++++++++++++ polkadot/runtime/common/src/registrar.rs | 5 ++- polkadot/runtime/polkadot/src/lib.rs | 2 +- polkadot/validation/src/collation.rs | 37 ++++++++++++++----- polkadot/validation/src/shared_table/mod.rs | 22 +++++++---- .../validation/src/validation_service/mod.rs | 3 +- 11 files changed, 86 insertions(+), 20 deletions(-) diff --git a/polkadot/availability-store/src/store.rs b/polkadot/availability-store/src/store.rs index b763e43a1b..6df777278f 100644 --- a/polkadot/availability-store/src/store.rs +++ b/polkadot/availability-store/src/store.rs @@ -459,6 +459,7 @@ mod tests { collator: Default::default(), signature: Default::default(), head_data: Default::default(), + parent_head: Default::default(), egress_queue_roots: Vec::new(), fees: 0, block_data_hash: block_data_1.hash(), @@ -471,6 +472,7 @@ mod tests { collator: Default::default(), signature: Default::default(), head_data: Default::default(), + parent_head: Default::default(), egress_queue_roots: Vec::new(), fees: 0, block_data_hash: block_data_2.hash(), @@ -575,6 +577,7 @@ mod tests { collator: Default::default(), signature: Default::default(), head_data: Default::default(), + parent_head: Default::default(), egress_queue_roots: Vec::new(), fees: 0, block_data_hash: block_data.hash(), diff --git a/polkadot/network/src/collator_pool.rs b/polkadot/network/src/collator_pool.rs index d07cfdc536..892de0b516 100644 --- a/polkadot/network/src/collator_pool.rs +++ b/polkadot/network/src/collator_pool.rs @@ -285,6 +285,7 @@ mod tests { collator: primary.clone().into(), signature: Default::default(), head_data: HeadData(vec![1, 2, 3]), + parent_head: HeadData(vec![]), egress_queue_roots: vec![], fees: 0, block_data_hash: [3; 32].into(), @@ -314,6 +315,7 @@ mod tests { collator: primary, signature: Default::default(), head_data: HeadData(vec![1, 2, 3]), + parent_head: HeadData(vec![]), egress_queue_roots: vec![], fees: 0, block_data_hash: [3; 32].into(), diff --git a/polkadot/network/src/gossip.rs b/polkadot/network/src/gossip.rs index 93154bb16b..825f1633d2 100644 --- a/polkadot/network/src/gossip.rs +++ b/polkadot/network/src/gossip.rs @@ -906,6 +906,7 @@ mod tests { parachain_index: 5.into(), collator: [255; 32].unchecked_into(), head_data: HeadData(vec![9, 9, 9]), + parent_head: HeadData(vec![]), signature: Default::default(), egress_queue_roots: Vec::new(), fees: 1_000_000, diff --git a/polkadot/network/src/tests/mod.rs b/polkadot/network/src/tests/mod.rs index 05122202f5..a1720344e0 100644 --- a/polkadot/network/src/tests/mod.rs +++ b/polkadot/network/src/tests/mod.rs @@ -173,6 +173,7 @@ fn fetches_from_those_with_knowledge() { parachain_index: 5.into(), collator: [255; 32].unchecked_into(), head_data: HeadData(vec![9, 9, 9]), + parent_head: HeadData(vec![]), signature: Default::default(), egress_queue_roots: Vec::new(), fees: 1_000_000, diff --git a/polkadot/primitives/src/parachain.rs b/polkadot/primitives/src/parachain.rs index 6f77b41589..eb359f388f 100644 --- a/polkadot/primitives/src/parachain.rs +++ b/polkadot/primitives/src/parachain.rs @@ -292,6 +292,8 @@ pub struct CandidateReceipt { pub signature: CollatorSignature, /// The head-data pub head_data: HeadData, + /// The parent head-data. + pub parent_head: HeadData, /// Egress queue roots. Must be sorted lexicographically (ascending) /// by parachain ID. pub egress_queue_roots: Vec<(Id, Hash)>, diff --git a/polkadot/runtime/common/src/parachains.rs b/polkadot/runtime/common/src/parachains.rs index 8458d8692a..d9b7bd4770 100644 --- a/polkadot/runtime/common/src/parachains.rs +++ b/polkadot/runtime/common/src/parachains.rs @@ -231,6 +231,8 @@ decl_error! { InvalidSignature, /// Extra untagged validity votes along with candidate. UntaggedVotes, + /// Wrong parent head for parachain receipt. + ParentMismatch, } } @@ -781,6 +783,14 @@ impl Module { let validator_group = validator_groups.group_for(para_id) .ok_or(Error::::NoValidatorGroup)?; + let actual_head = Self::parachain_head(¶_id) + .map(primitives::parachain::HeadData); + + ensure!( + actual_head.as_ref() == Some(&candidate.candidate.parent_head), + Error::::ParentMismatch, + ); + ensure!( candidate.validity_votes.len() >= majority_of(validator_group.len()), Error::::NotEnoughValidityVotes, @@ -1287,6 +1297,7 @@ mod tests { collator: Default::default(), signature: Default::default(), head_data: HeadData(vec![1, 2, 3]), + parent_head: HeadData(vec![]), egress_queue_roots, fees: 0, block_data_hash: Default::default(), @@ -1308,6 +1319,7 @@ mod tests { collator: Default::default(), signature: Default::default(), head_data: HeadData(vec![1, 2, 3]), + parent_head: HeadData(vec![]), egress_queue_roots: vec![], fees: 0, block_data_hash: Default::default(), @@ -1718,6 +1730,7 @@ mod tests { collator: Default::default(), signature: Default::default(), head_data: HeadData(vec![1, 2, 3]), + parent_head: HeadData(vec![]), egress_queue_roots: vec![], fees: 0, block_data_hash: Default::default(), @@ -1750,6 +1763,7 @@ mod tests { collator: Default::default(), signature: Default::default(), head_data: HeadData(vec![1, 2, 3]), + parent_head: HeadData(vec![]), egress_queue_roots: vec![], fees: 0, block_data_hash: Default::default(), @@ -1766,6 +1780,7 @@ mod tests { collator: Default::default(), signature: Default::default(), head_data: HeadData(vec![2, 3, 4]), + parent_head: HeadData(vec![]), egress_queue_roots: vec![], fees: 0, block_data_hash: Default::default(), @@ -1806,6 +1821,7 @@ mod tests { collator: Default::default(), signature: Default::default(), head_data: HeadData(vec![1, 2, 3]), + parent_head: HeadData(vec![]), egress_queue_roots: vec![], fees: 0, block_data_hash: Default::default(), @@ -1844,6 +1860,7 @@ mod tests { collator: Default::default(), signature: Default::default(), head_data: HeadData(vec![1, 2, 3]), + parent_head: HeadData(vec![]), egress_queue_roots: vec![], fees: 0, block_data_hash: Default::default(), @@ -1878,10 +1895,18 @@ mod tests { assert_eq!(Parachains::ingress(ParaId::from(99), None), Some(Vec::new())); init_block(); + for i in 1..10 { run_to_block(i); let from_a = vec![(1.into(), [i as u8; 32].into())]; + + let parent_head = HeadData(if i == 1 { + vec![] + } else { + vec![1, 2, 3] + }); + let mut candidate_a = AttestedCandidate { validity_votes: vec![], validator_indices: BitVec::new(), @@ -1890,6 +1915,7 @@ mod tests { collator: Default::default(), signature: Default::default(), head_data: HeadData(vec![1, 2, 3]), + parent_head: parent_head.clone(), egress_queue_roots: from_a.clone(), fees: 0, block_data_hash: Default::default(), @@ -1907,6 +1933,7 @@ mod tests { collator: Default::default(), signature: Default::default(), head_data: HeadData(vec![1, 2, 3]), + parent_head, egress_queue_roots: from_b.clone(), fees: 0, block_data_hash: Default::default(), @@ -1969,6 +1996,7 @@ mod tests { collator: Default::default(), signature: Default::default(), head_data: HeadData(vec![1, 2, 3]), + parent_head: HeadData(vec![4, 5, 6]), egress_queue_roots: Vec::new(), fees: 0, block_data_hash: Default::default(), diff --git a/polkadot/runtime/common/src/registrar.rs b/polkadot/runtime/common/src/registrar.rs index 992409fbcd..21fde017ce 100644 --- a/polkadot/runtime/common/src/registrar.rs +++ b/polkadot/runtime/common/src/registrar.rs @@ -836,13 +836,14 @@ mod tests { LOWEST_USER_ID + i } - fn attest(id: ParaId, collator: &CollatorPair, head_data: &[u8], block_data: &[u8]) -> AttestedCandidate { + fn attest(id: ParaId, collator: &CollatorPair, head_data: &[u8], parent_head: &[u8], block_data: &[u8]) -> AttestedCandidate { let block_data_hash = BlakeTwo256::hash(block_data); let candidate = CandidateReceipt { parachain_index: id, collator: collator.public(), signature: block_data_hash.using_encoded(|d| collator.sign(d)), head_data: HeadData(head_data.to_vec()), + parent_head: HeadData(parent_head.to_vec()), egress_queue_roots: vec![], fees: 0, block_data_hash, @@ -1117,7 +1118,7 @@ mod tests { (user_id(0), Some((col.clone(), Retriable::WithRetries(0)))) ]); assert_ok!(Parachains::set_heads(Origin::NONE, vec![ - attest(user_id(0), &Sr25519Keyring::One.pair().into(), &[3; 3], &[0; 0]) + attest(user_id(0), &Sr25519Keyring::One.pair().into(), &[3; 3], &[3; 3], &[0; 0]) ])); run_to_block(6); diff --git a/polkadot/runtime/polkadot/src/lib.rs b/polkadot/runtime/polkadot/src/lib.rs index bdb6aa4988..7b41734d77 100644 --- a/polkadot/runtime/polkadot/src/lib.rs +++ b/polkadot/runtime/polkadot/src/lib.rs @@ -78,7 +78,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: create_runtime_str!("polkadot"), impl_name: create_runtime_str!("parity-polkadot"), authoring_version: 2, - spec_version: 1001, + spec_version: 1002, impl_version: 0, apis: RUNTIME_API_VERSIONS, }; diff --git a/polkadot/validation/src/collation.rs b/polkadot/validation/src/collation.rs index 6adc87db71..1cfebc101a 100644 --- a/polkadot/validation/src/collation.rs +++ b/polkadot/validation/src/collation.rs @@ -68,7 +68,7 @@ pub async fn collation_fetch( collators: C, client: Arc

, max_block_data_size: Option, -) -> Result<(Collation, OutgoingMessages, Balance),C::Error> +) -> Result<(Collation, OutgoingMessages, HeadData, Balance),C::Error> where P::Api: ParachainHost, C: Collators + Unpin, @@ -89,8 +89,8 @@ pub async fn collation_fetch( ); match res { - Ok((messages, fees)) => { - return Ok((collation, messages, fees)) + Ok((messages, parent_head, fees)) => { + return Ok((collation, messages, parent_head, fees)) } Err(e) => { debug!("Failed to validate parachain due to API error: {}", e); @@ -148,6 +148,10 @@ pub enum Error { /// Candidate block collation info doesn't match candidate receipt. #[display(fmt = "Got receipt mismatch for candidate {:?}", candidate)] CandidateReceiptMismatch { candidate: Hash }, + /// The parent header given in the candidate did not match current relay-chain + /// state. + #[display(fmt = "Got unexpected parachain parent.")] + ParentMismatch { expected: HeadData, got: HeadData }, } impl std::error::Error for Error { @@ -406,7 +410,7 @@ pub fn validate_incoming( // A utility function that implements most of the collation validation logic. // // Reused by `validate_collation` and `validate_receipt`. -// Returns outgoing messages and fees charged for later reuse. +// Returns outgoing messages, parent nead data, and fees charged for later reuse. fn do_validation

( client: &P, relay_parent: &BlockId, @@ -417,7 +421,7 @@ fn do_validation

( head_data: &HeadData, queue_roots: &Vec<(ParaId, Hash)>, upward_messages: &Vec, -) -> Result<(OutgoingMessages, Balance), Error> where +) -> Result<(OutgoingMessages, HeadData, Balance), Error> where P: ProvideRuntimeApi, P::Api: ParachainHost, { @@ -443,7 +447,7 @@ fn do_validation

( validate_incoming(&roots, &pov_block.ingress)?; let params = ValidationParams { - parent_head: chain_status.head_data.0, + parent_head: chain_status.head_data.0.clone(), block_data: pov_block.block_data.0.clone(), ingress: pov_block.ingress.0.iter() .flat_map(|&(source, ref messages)| { @@ -471,7 +475,7 @@ fn do_validation

( fees_charged )?; - Ok((messages, fees)) + Ok((messages, chain_status.head_data, fees)) } else { Err(Error::WrongHeadData { expected: head_data.0.clone(), @@ -491,6 +495,7 @@ fn do_validation

( /// encoding's root returning both for re-use. pub fn produce_receipt_and_chunks( n_validators: usize, + parent_head: HeadData, pov: &PoVBlock, messages: &OutgoingMessages, fees: Balance, @@ -523,6 +528,7 @@ pub fn produce_receipt_and_chunks( collator: info.collator.clone(), signature: info.signature.clone(), head_data: info.head_data.clone(), + parent_head, egress_queue_roots: info.egress_queue_roots.clone(), fees, block_data_hash: info.block_data_hash.clone(), @@ -547,7 +553,7 @@ pub fn validate_receipt

( P: ProvideRuntimeApi, P::Api: ParachainHost, { - let (messages, _fees) = do_validation( + let (messages, parent_head, _fees) = do_validation( client, relay_parent, pov_block, @@ -559,12 +565,20 @@ pub fn validate_receipt

( &receipt.upward_messages, )?; + if parent_head != receipt.parent_head { + return Err(Error::ParentMismatch { + expected: receipt.parent_head.clone(), + got: parent_head, + }); + } + let api = client.runtime_api(); let validators = api.validators(&relay_parent)?; let n_validators = validators.len(); let (validated_receipt, chunks) = produce_receipt_and_chunks( n_validators, + parent_head, pov_block, &messages, receipt.fees, @@ -582,6 +596,7 @@ pub fn validate_receipt

( } /// Check whether a given collation is valid. Returns `Ok` on success, error otherwise. +/// Returns outgoing messages, parent head-data, and fees. /// /// This assumes that basic validity checks have been done: /// - Block data hash is the same as linked in collation info. @@ -590,7 +605,7 @@ pub fn validate_collation

( relay_parent: &BlockId, collation: &Collation, max_block_data_size: Option, -) -> Result<(OutgoingMessages, Balance), Error> where +) -> Result<(OutgoingMessages, HeadData, Balance), Error> where P: ProvideRuntimeApi, P::Api: ParachainHost, { @@ -698,6 +713,7 @@ mod tests { collator: Default::default(), signature: Default::default(), head_data: HeadData(Vec::new()), + parent_head: HeadData(Vec::new()), egress_queue_roots: Vec::new(), fees: 0, block_data_hash: Default::default(), @@ -717,6 +733,7 @@ mod tests { collator: Default::default(), signature: Default::default(), head_data: HeadData(Vec::new()), + parent_head: HeadData(Vec::new()), egress_queue_roots: Vec::new(), fees: 0, block_data_hash: Default::default(), @@ -735,6 +752,7 @@ mod tests { collator: Default::default(), signature: Default::default(), head_data: HeadData(Vec::new()), + parent_head: HeadData(Vec::new()), egress_queue_roots: Vec::new(), fees: 0, block_data_hash: Default::default(), @@ -753,6 +771,7 @@ mod tests { collator: Default::default(), signature: Default::default(), head_data: HeadData(Vec::new()), + parent_head: HeadData(Vec::new()), egress_queue_roots: Vec::new(), fees: 0, block_data_hash: Default::default(), diff --git a/polkadot/validation/src/shared_table/mod.rs b/polkadot/validation/src/shared_table/mod.rs index a6c22d6274..69d6e7fd1e 100644 --- a/polkadot/validation/src/shared_table/mod.rs +++ b/polkadot/validation/src/shared_table/mod.rs @@ -576,7 +576,9 @@ mod tests { use super::*; use sp_keyring::Sr25519Keyring; use primitives::crypto::UncheckedInto; - use polkadot_primitives::parachain::{AvailableMessages, BlockData, ConsolidatedIngress, Collation}; + use polkadot_primitives::parachain::{ + AvailableMessages, BlockData, ConsolidatedIngress, Collation, HeadData, + }; use polkadot_erasure_coding::{self as erasure}; use availability_store::ProvideGossipMessages; use futures::future; @@ -662,7 +664,8 @@ mod tests { parachain_index: para_id, collator: [1; 32].unchecked_into(), signature: Default::default(), - head_data: ::polkadot_primitives::parachain::HeadData(vec![1, 2, 3, 4]), + head_data: HeadData(vec![1, 2, 3, 4]), + parent_head: HeadData(vec![]), egress_queue_roots: Vec::new(), fees: 1_000_000, block_data_hash: [2; 32].into(), @@ -718,7 +721,8 @@ mod tests { parachain_index: para_id, collator: [1; 32].unchecked_into(), signature: Default::default(), - head_data: ::polkadot_primitives::parachain::HeadData(vec![1, 2, 3, 4]), + head_data: HeadData(vec![1, 2, 3, 4]), + parent_head: HeadData(vec![]), egress_queue_roots: Vec::new(), fees: 1_000_000, block_data_hash: [2; 32].into(), @@ -755,7 +759,8 @@ mod tests { parachain_index: para_id, collator: [1; 32].unchecked_into(), signature: Default::default(), - head_data: ::polkadot_primitives::parachain::HeadData(vec![1, 2, 3, 4]), + head_data: HeadData(vec![1, 2, 3, 4]), + parent_head: HeadData(vec![]), egress_queue_roots: Vec::new(), fees: 1_000_000, block_data_hash, @@ -819,7 +824,8 @@ mod tests { parachain_index: para_id, collator: [1; 32].unchecked_into(), signature: Default::default(), - head_data: ::polkadot_primitives::parachain::HeadData(vec![1, 2, 3, 4]), + head_data: HeadData(vec![1, 2, 3, 4]), + parent_head: HeadData(vec![]), egress_queue_roots: Vec::new(), fees: 1_000_000, block_data_hash: [2; 32].into(), @@ -902,7 +908,8 @@ mod tests { parachain_index: para_id, collator: [1; 32].unchecked_into(), signature: Default::default(), - head_data: ::polkadot_primitives::parachain::HeadData(vec![1, 2, 3, 4]), + head_data: HeadData(vec![1, 2, 3, 4]), + parent_head: HeadData(vec![]), egress_queue_roots: Vec::new(), fees: 1_000_000, block_data_hash: [2; 32].into(), @@ -969,7 +976,8 @@ mod tests { parachain_index: para_id, collator: [1; 32].unchecked_into(), signature: Default::default(), - head_data: ::polkadot_primitives::parachain::HeadData(vec![1, 2, 3, 4]), + head_data: HeadData(vec![1, 2, 3, 4]), + parent_head: HeadData(vec![]), egress_queue_roots: Vec::new(), fees: 1_000_000, block_data_hash: [2; 32].into(), diff --git a/polkadot/validation/src/validation_service/mod.rs b/polkadot/validation/src/validation_service/mod.rs index fcf053d141..8139d562d3 100644 --- a/polkadot/validation/src/validation_service/mod.rs +++ b/polkadot/validation/src/validation_service/mod.rs @@ -396,10 +396,11 @@ impl ParachainValidationInstances where max_block_data_size, ).await { Ok(collation_work) => { - let (collation, outgoing_targeted, fees_charged) = collation_work; + let (collation, outgoing_targeted, parent_head, fees_charged) = collation_work; match crate::collation::produce_receipt_and_chunks( authorities_num, + parent_head, &collation.pov, &outgoing_targeted, fees_charged,