diff --git a/Cargo.lock b/Cargo.lock index ee95404c61..e92f6eeabf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -930,6 +930,7 @@ dependencies = [ name = "cumulus-network" version = "0.1.0" dependencies = [ + "cumulus-primitives", "cumulus-test-runtime", "futures 0.3.4", "log 0.4.8", diff --git a/collator/src/lib.rs b/collator/src/lib.rs index 724d351926..03d0d78d0e 100644 --- a/collator/src/lib.rs +++ b/collator/src/lib.rs @@ -20,7 +20,7 @@ use cumulus_network::{ DelayedBlockAnnounceValidator, JustifiedBlockAnnounceValidator, WaitToAnnounce, }; use cumulus_primitives::{ - inherents::VALIDATION_FUNCTION_PARAMS_IDENTIFIER as VFP_IDENT, + HeadData, inherents::VALIDATION_FUNCTION_PARAMS_IDENTIFIER as VFP_IDENT, validation_function_params::ValidationFunctionParams, }; use cumulus_runtime::ParachainBlockData; @@ -56,12 +56,6 @@ use std::{fmt::Debug, marker::PhantomData, sync::Arc, time::Duration, pin::Pin}; use parking_lot::Mutex; -/// The head data of the parachain, stored in the relay chain. -#[derive(Decode, Encode, Debug)] -struct HeadData { - header: Block::Header, -} - /// The implementation of the Cumulus `Collator`. pub struct Collator { proposer_factory: Arc>, @@ -340,7 +334,10 @@ where + Sync + 'static, Backend: sc_client_api::Backend + 'static, - Client: Finalizer + UsageProvider + Send + Sync + 'static, + Client: Finalizer + UsageProvider + HeaderBackend + + Send + + Sync + + 'static, { type ParachainContext = Collator; @@ -359,7 +356,7 @@ where Extrinsic: codec::Codec + Send + Sync + 'static, { self.delayed_block_announce_validator.set( - Box::new(JustifiedBlockAnnounceValidator::new(polkadot_client.clone())), + Box::new(JustifiedBlockAnnounceValidator::new(polkadot_client.clone(), self.para_id)), ); let follow = diff --git a/network/Cargo.toml b/network/Cargo.toml index 100d55ff7f..71559b627d 100644 --- a/network/Cargo.toml +++ b/network/Cargo.toml @@ -19,6 +19,9 @@ polkadot-statement-table = { git = "https://github.com/paritytech/polkadot", bra polkadot-validation = { git = "https://github.com/paritytech/polkadot", branch = "cumulus-branch" } polkadot-network = { git = "https://github.com/paritytech/polkadot", branch = "cumulus-branch" } +# cumulus deps +cumulus-primitives = { path = "../primitives" } + # other deps codec = { package = "parity-scale-codec", version = "1.3.0", features = [ "derive" ] } futures = { version = "0.3.1", features = ["compat"] } diff --git a/network/src/lib.rs b/network/src/lib.rs index c16afe85d1..e7ace02eff 100644 --- a/network/src/lib.rs +++ b/network/src/lib.rs @@ -24,17 +24,19 @@ mod tests; use sp_api::ProvideRuntimeApi; use sp_blockchain::{Error as ClientError, HeaderBackend}; use sp_consensus::block_validation::{BlockAnnounceValidator, Validation}; -use sp_runtime::{generic::BlockId, traits::Block as BlockT}; +use sp_runtime::{generic::BlockId, traits::{Block as BlockT, Header as HeaderT}}; use polkadot_collator::Network as CollatorNetwork; use polkadot_network::legacy::gossip::{GossipMessage, GossipStatement}; use polkadot_primitives::{ - parachain::ParachainHost, + parachain::{ParachainHost, Id as ParaId}, Block as PBlock, Hash as PHash, }; use polkadot_statement_table::{SignedStatement, Statement}; use polkadot_validation::check_statement; +use cumulus_primitives::HeadData; + use codec::{Decode, Encode}; use futures::{pin_mut, select, StreamExt}; use futures::channel::oneshot; @@ -54,13 +56,15 @@ use parking_lot::Mutex; pub struct JustifiedBlockAnnounceValidator { phantom: PhantomData, polkadot_client: Arc

, + para_id: ParaId, } impl JustifiedBlockAnnounceValidator { - pub fn new(polkadot_client: Arc

) -> Self { + pub fn new(polkadot_client: Arc

, para_id: ParaId) -> Self { Self { phantom: Default::default(), polkadot_client, + para_id, } } } @@ -75,9 +79,34 @@ where header: &B::Header, mut data: &[u8], ) -> Result> { - // If no data is provided the announce is valid. + let runtime_api = self.polkadot_client.runtime_api(); + let polkadot_info = self.polkadot_client.info(); + if data.is_empty() { - return Ok(Validation::Success); + // Check if block is equal or higher than best (this requires a justification) + let runtime_api_block_id = BlockId::Hash(polkadot_info.best_hash); + let block_number = header.number(); + + let local_validation_data = runtime_api + .local_validation_data(&runtime_api_block_id, self.para_id) + .map_err(|e| Box::new(ClientError::Msg(format!("{:?}", e))) as Box<_>)? + .ok_or_else(|| { + Box::new(ClientError::Msg("Could not find parachain head in relay chain".into())) as Box<_> + })?; + let parent_head = HeadData::::decode(&mut &local_validation_data.parent_head.0[..]) + .map_err(|e| Box::new(ClientError::Msg(format!("Failed to decode parachain head: {:?}", e))) as Box<_>)?; + let known_best_number = parent_head.header.number(); + + return Ok(if block_number >= known_best_number { + trace!( + target: "cumulus-network", + "validation failed because a justification is needed if the block at the top of the chain." + ); + + Validation::Failure + } else { + Validation::Success + }); } // Check data is a gossip message. @@ -108,7 +137,7 @@ where } = gossip_statement; // Check that the relay chain parent of the block is the relay chain head - let best_number = self.polkadot_client.info().best_number; + let best_number = polkadot_info.best_number; match self.polkadot_client.number(relay_chain_leaf) { Err(err) => { @@ -133,7 +162,6 @@ where }, } - let runtime_api = self.polkadot_client.runtime_api(); let runtime_api_block_id = BlockId::Hash(relay_chain_leaf); let signing_context = runtime_api .signing_context(&runtime_api_block_id) diff --git a/network/src/tests.rs b/network/src/tests.rs index 56e4689d81..fcc9c1b356 100644 --- a/network/src/tests.rs +++ b/network/src/tests.rs @@ -48,12 +48,15 @@ fn make_validator_and_client() -> ( let builder = TestClientBuilder::new(); let client = Arc::new(TestApi::new(Arc::new(builder.build()))); - (JustifiedBlockAnnounceValidator::new(client.clone()), client) + ( + JustifiedBlockAnnounceValidator::new(client.clone(), ParaId::from(56)), + client, + ) } fn default_header() -> Header { Header { - number: Default::default(), + number: 1, digest: Default::default(), extrinsics_root: Default::default(), parent_hash: Default::default(), @@ -91,13 +94,34 @@ fn make_gossip_message_and_header( } #[test] -fn valid_if_no_data() { +fn valid_if_no_data_and_less_than_best_known_number() { let mut validator = make_validator(); - let header = default_header(); + let header = Header { + number: 0, + ..default_header() + }; + let res = validator.validate(&header, &[]); - assert!( - matches!(validator.validate(&header, &[]), Ok(Validation::Success)), - "validating without data is always a success", + assert_eq!( + res.unwrap(), + Validation::Success, + "validating without data with block number < best known number is always a success", + ); +} + +#[test] +fn invalid_if_no_data_exceeds_best_known_number() { + let mut validator = make_validator(); + let header = Header { + number: 1, + ..default_header() + }; + let res = validator.validate(&header, &[]); + + assert_eq!( + res.unwrap(), + Validation::Failure, + "validation fails if no justification and block number >= best known number", ); } @@ -399,7 +423,10 @@ sp_api::mock_impl_runtime_apis! { } fn local_validation_data(_: ParaId) -> Option { - Some(Default::default()) + Some(LocalValidationData { + parent_head: HeadData:: { header: default_header() }.encode().into(), + ..Default::default() + }) } fn get_heads(_: Vec<::Extrinsic>) -> Option> { diff --git a/primitives/src/lib.rs b/primitives/src/lib.rs index b885018499..aec46911c9 100644 --- a/primitives/src/lib.rs +++ b/primitives/src/lib.rs @@ -20,6 +20,9 @@ pub mod validation_function_params; +use codec::{Decode, Encode}; +use sp_runtime::traits::Block as BlockT; + /// Identifiers and types related to Cumulus Inherents pub mod inherents { use sp_inherents::InherentIdentifier; @@ -64,3 +67,9 @@ pub trait UpwardMessageSender { /// Returns an error if sending failed. fn send_upward_message(msg: &()) -> Result<(), ()>; } + +/// The head data of the parachain, stored in the relay chain. +#[derive(Decode, Encode, Debug)] +pub struct HeadData { + pub header: Block::Header, +}