diff --git a/Cargo.lock b/Cargo.lock index 7ae68589e5..ee95404c61 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -930,6 +930,7 @@ dependencies = [ name = "cumulus-network" version = "0.1.0" dependencies = [ + "cumulus-test-runtime", "futures 0.3.4", "log 0.4.8", "parity-scale-codec", @@ -938,10 +939,14 @@ dependencies = [ "polkadot-network", "polkadot-primitives", "polkadot-statement-table", + "polkadot-test-runtime-client", "polkadot-validation", + "sc-client-api", "sp-api", "sp-blockchain", "sp-consensus", + "sp-core", + "sp-keyring", "sp-runtime", ] @@ -2530,7 +2535,7 @@ dependencies = [ [[package]] name = "kusama-runtime" version = "0.7.32-dev" -source = "git+https://github.com/paritytech/polkadot?branch=cumulus-branch#14c03fc7331e9ff82dc70c97e8a7dc377cb62dc5" +source = "git+https://github.com/paritytech/polkadot?branch=cumulus-branch#5e772180c49772aa3627098eace1d5d9351af745" dependencies = [ "bitvec", "frame-executive", @@ -4371,7 +4376,7 @@ checksum = "feb3b2b1033b8a60b4da6ee470325f887758c95d5320f52f9ce0df055a55940e" [[package]] name = "polkadot-availability-store" version = "0.7.32-dev" -source = "git+https://github.com/paritytech/polkadot?branch=cumulus-branch#14c03fc7331e9ff82dc70c97e8a7dc377cb62dc5" +source = "git+https://github.com/paritytech/polkadot?branch=cumulus-branch#5e772180c49772aa3627098eace1d5d9351af745" dependencies = [ "derive_more 0.99.5", "exit-future 0.2.0", @@ -4398,7 +4403,7 @@ dependencies = [ [[package]] name = "polkadot-cli" version = "0.7.32-dev" -source = "git+https://github.com/paritytech/polkadot?branch=cumulus-branch#14c03fc7331e9ff82dc70c97e8a7dc377cb62dc5" +source = "git+https://github.com/paritytech/polkadot?branch=cumulus-branch#5e772180c49772aa3627098eace1d5d9351af745" dependencies = [ "frame-benchmarking-cli", "futures 0.3.4", @@ -4420,7 +4425,7 @@ dependencies = [ [[package]] name = "polkadot-collator" version = "0.7.32-dev" -source = "git+https://github.com/paritytech/polkadot?branch=cumulus-branch#14c03fc7331e9ff82dc70c97e8a7dc377cb62dc5" +source = "git+https://github.com/paritytech/polkadot?branch=cumulus-branch#5e772180c49772aa3627098eace1d5d9351af745" dependencies = [ "futures 0.3.4", "futures-timer 2.0.2", @@ -4447,7 +4452,7 @@ dependencies = [ [[package]] name = "polkadot-erasure-coding" version = "0.7.32-dev" -source = "git+https://github.com/paritytech/polkadot?branch=cumulus-branch#14c03fc7331e9ff82dc70c97e8a7dc377cb62dc5" +source = "git+https://github.com/paritytech/polkadot?branch=cumulus-branch#5e772180c49772aa3627098eace1d5d9351af745" dependencies = [ "derive_more 0.15.0", "parity-scale-codec", @@ -4460,7 +4465,7 @@ dependencies = [ [[package]] name = "polkadot-network" version = "0.7.32-dev" -source = "git+https://github.com/paritytech/polkadot?branch=cumulus-branch#14c03fc7331e9ff82dc70c97e8a7dc377cb62dc5" +source = "git+https://github.com/paritytech/polkadot?branch=cumulus-branch#5e772180c49772aa3627098eace1d5d9351af745" dependencies = [ "arrayvec 0.4.12", "bytes 0.5.4", @@ -4487,7 +4492,7 @@ dependencies = [ [[package]] name = "polkadot-parachain" version = "0.7.32-dev" -source = "git+https://github.com/paritytech/polkadot?branch=cumulus-branch#14c03fc7331e9ff82dc70c97e8a7dc377cb62dc5" +source = "git+https://github.com/paritytech/polkadot?branch=cumulus-branch#5e772180c49772aa3627098eace1d5d9351af745" dependencies = [ "derive_more 0.99.5", "log 0.4.8", @@ -4507,7 +4512,7 @@ dependencies = [ [[package]] name = "polkadot-primitives" version = "0.7.32-dev" -source = "git+https://github.com/paritytech/polkadot?branch=cumulus-branch#14c03fc7331e9ff82dc70c97e8a7dc377cb62dc5" +source = "git+https://github.com/paritytech/polkadot?branch=cumulus-branch#5e772180c49772aa3627098eace1d5d9351af745" dependencies = [ "bitvec", "parity-scale-codec", @@ -4527,7 +4532,7 @@ dependencies = [ [[package]] name = "polkadot-rpc" version = "0.7.32-dev" -source = "git+https://github.com/paritytech/polkadot?branch=cumulus-branch#14c03fc7331e9ff82dc70c97e8a7dc377cb62dc5" +source = "git+https://github.com/paritytech/polkadot?branch=cumulus-branch#5e772180c49772aa3627098eace1d5d9351af745" dependencies = [ "jsonrpc-core", "pallet-transaction-payment-rpc", @@ -4545,7 +4550,7 @@ dependencies = [ [[package]] name = "polkadot-runtime" version = "0.7.32-dev" -source = "git+https://github.com/paritytech/polkadot?branch=cumulus-branch#14c03fc7331e9ff82dc70c97e8a7dc377cb62dc5" +source = "git+https://github.com/paritytech/polkadot?branch=cumulus-branch#5e772180c49772aa3627098eace1d5d9351af745" dependencies = [ "bitvec", "frame-executive", @@ -4604,7 +4609,7 @@ dependencies = [ [[package]] name = "polkadot-runtime-common" version = "0.7.32-dev" -source = "git+https://github.com/paritytech/polkadot?branch=cumulus-branch#14c03fc7331e9ff82dc70c97e8a7dc377cb62dc5" +source = "git+https://github.com/paritytech/polkadot?branch=cumulus-branch#5e772180c49772aa3627098eace1d5d9351af745" dependencies = [ "bitvec", "frame-support", @@ -4634,7 +4639,7 @@ dependencies = [ [[package]] name = "polkadot-service" version = "0.7.32-dev" -source = "git+https://github.com/paritytech/polkadot?branch=cumulus-branch#14c03fc7331e9ff82dc70c97e8a7dc377cb62dc5" +source = "git+https://github.com/paritytech/polkadot?branch=cumulus-branch#5e772180c49772aa3627098eace1d5d9351af745" dependencies = [ "frame-benchmarking", "frame-system-rpc-runtime-api", @@ -4692,17 +4697,88 @@ dependencies = [ [[package]] name = "polkadot-statement-table" version = "0.7.32-dev" -source = "git+https://github.com/paritytech/polkadot?branch=cumulus-branch#14c03fc7331e9ff82dc70c97e8a7dc377cb62dc5" +source = "git+https://github.com/paritytech/polkadot?branch=cumulus-branch#5e772180c49772aa3627098eace1d5d9351af745" dependencies = [ "parity-scale-codec", "polkadot-primitives", "sp-core", ] +[[package]] +name = "polkadot-test-runtime" +version = "0.7.32-dev" +source = "git+https://github.com/paritytech/polkadot?branch=cumulus-branch#5e772180c49772aa3627098eace1d5d9351af745" +dependencies = [ + "bitvec", + "frame-executive", + "frame-support", + "frame-system", + "frame-system-rpc-runtime-api", + "log 0.3.9", + "pallet-authorship", + "pallet-babe", + "pallet-balances", + "pallet-finality-tracker", + "pallet-grandpa", + "pallet-indices", + "pallet-nicks", + "pallet-offences", + "pallet-randomness-collective-flip", + "pallet-session", + "pallet-staking", + "pallet-staking-reward-curve", + "pallet-timestamp", + "pallet-transaction-payment", + "pallet-transaction-payment-rpc-runtime-api", + "pallet-vesting", + "parity-scale-codec", + "polkadot-parachain", + "polkadot-primitives", + "polkadot-runtime-common", + "rustc-hex", + "serde", + "serde_derive", + "sp-api", + "sp-block-builder", + "sp-consensus-babe", + "sp-core", + "sp-inherents", + "sp-io", + "sp-runtime", + "sp-session", + "sp-staking", + "sp-std", + "sp-transaction-pool", + "sp-version", + "substrate-wasm-builder-runner 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "polkadot-test-runtime-client" +version = "2.0.0" +source = "git+https://github.com/paritytech/polkadot?branch=cumulus-branch#5e772180c49772aa3627098eace1d5d9351af745" +dependencies = [ + "futures 0.3.4", + "pallet-timestamp", + "parity-scale-codec", + "polkadot-primitives", + "polkadot-runtime-common", + "polkadot-test-runtime", + "sc-block-builder", + "sc-client-api", + "sc-consensus", + "sc-service", + "sp-api", + "sp-blockchain", + "sp-core", + "sp-runtime", + "substrate-test-client", +] + [[package]] name = "polkadot-validation" version = "0.7.32-dev" -source = "git+https://github.com/paritytech/polkadot?branch=cumulus-branch#14c03fc7331e9ff82dc70c97e8a7dc377cb62dc5" +source = "git+https://github.com/paritytech/polkadot?branch=cumulus-branch#5e772180c49772aa3627098eace1d5d9351af745" dependencies = [ "bitvec", "derive_more 0.14.1", @@ -8512,7 +8588,7 @@ dependencies = [ [[package]] name = "westend-runtime" version = "0.7.32-dev" -source = "git+https://github.com/paritytech/polkadot?branch=cumulus-branch#14c03fc7331e9ff82dc70c97e8a7dc377cb62dc5" +source = "git+https://github.com/paritytech/polkadot?branch=cumulus-branch#5e772180c49772aa3627098eace1d5d9351af745" dependencies = [ "bitvec", "frame-executive", diff --git a/collator/Cargo.toml b/collator/Cargo.toml index 27bee2a6df..2a1e13678f 100644 --- a/collator/Cargo.toml +++ b/collator/Cargo.toml @@ -6,6 +6,7 @@ edition = "2018" [dependencies] # Substrate dependencies +sp-blockchain = { git = "https://github.com/paritytech/substrate", branch = "cumulus-branch" } sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "cumulus-branch" } sp-core = { git = "https://github.com/paritytech/substrate", branch = "cumulus-branch" } sp-consensus = { git = "https://github.com/paritytech/substrate", branch = "cumulus-branch" } diff --git a/collator/src/lib.rs b/collator/src/lib.rs index e9f5d87b32..724d351926 100644 --- a/collator/src/lib.rs +++ b/collator/src/lib.rs @@ -25,6 +25,7 @@ use cumulus_primitives::{ }; use cumulus_runtime::ParachainBlockData; +use sp_blockchain::HeaderBackend; use sp_consensus::{ BlockImport, BlockImportParams, BlockOrigin, Environment, Error as ConsensusError, ForkChoiceStrategy, Proposal, Proposer, RecordProof, @@ -350,14 +351,15 @@ where polkadot_network: impl CollatorNetwork + Clone + 'static, ) -> Result where - PClient: ProvideRuntimeApi + Send + Sync + BlockchainEvents + 'static, + PClient: ProvideRuntimeApi + BlockchainEvents + HeaderBackend + + Send + Sync + 'static, PClient::Api: RuntimeApiCollection, >::StateBackend: StateBackend>, Spawner: Spawn + Clone + Send + Sync + 'static, Extrinsic: codec::Codec + Send + Sync + 'static, { self.delayed_block_announce_validator.set( - Box::new(JustifiedBlockAnnounceValidator::new(Vec::new(), polkadot_client.clone())), + Box::new(JustifiedBlockAnnounceValidator::new(polkadot_client.clone())), ); let follow = diff --git a/network/Cargo.toml b/network/Cargo.toml index 0465fcd1d7..100d55ff7f 100644 --- a/network/Cargo.toml +++ b/network/Cargo.toml @@ -24,3 +24,14 @@ codec = { package = "parity-scale-codec", version = "1.3.0", features = [ "deriv futures = { version = "0.3.1", features = ["compat"] } log = "0.4.8" parking_lot = "0.10.2" + +[dev-dependencies] +cumulus-test-runtime = { path = "../test/runtime" } + +# substrate deps +sc-client-api = { git = "https://github.com/paritytech/substrate", branch = "cumulus-branch" } +sp-core = { git = "https://github.com/paritytech/substrate", branch = "cumulus-branch" } +sp-keyring = { git = "https://github.com/paritytech/substrate", branch = "cumulus-branch" } + +# polkadot deps +polkadot-test-runtime-client = { git = "https://github.com/paritytech/polkadot", branch = "cumulus-branch" } diff --git a/network/src/lib.rs b/network/src/lib.rs index c000525a37..c16afe85d1 100644 --- a/network/src/lib.rs +++ b/network/src/lib.rs @@ -18,15 +18,18 @@ //! //! Contains message send between collators and logic to process them. +#[cfg(test)] +mod tests; + use sp_api::ProvideRuntimeApi; -use sp_blockchain::Error as ClientError; +use sp_blockchain::{Error as ClientError, HeaderBackend}; use sp_consensus::block_validation::{BlockAnnounceValidator, Validation}; use sp_runtime::{generic::BlockId, traits::Block as BlockT}; use polkadot_collator::Network as CollatorNetwork; use polkadot_network::legacy::gossip::{GossipMessage, GossipStatement}; use polkadot_primitives::{ - parachain::{ParachainHost, ValidatorId}, + parachain::ParachainHost, Block as PBlock, Hash as PHash, }; use polkadot_statement_table::{SignedStatement, Statement}; @@ -49,15 +52,13 @@ use parking_lot::Mutex; /// /// Note: if no justification is provided the annouce is considered valid. pub struct JustifiedBlockAnnounceValidator { - authorities: Vec, phantom: PhantomData, polkadot_client: Arc

, } impl JustifiedBlockAnnounceValidator { - pub fn new(authorities: Vec, polkadot_client: Arc

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

) -> Self { Self { - authorities, phantom: Default::default(), polkadot_client, } @@ -66,7 +67,7 @@ impl JustifiedBlockAnnounceValidator { impl BlockAnnounceValidator for JustifiedBlockAnnounceValidator where - P: ProvideRuntimeApi, + P: ProvideRuntimeApi + HeaderBackend, P::Api: ParachainHost, { fn validate( @@ -106,14 +107,42 @@ where }, } = gossip_statement; - let signing_context = self - .polkadot_client - .runtime_api() - .signing_context(&BlockId::Hash(relay_chain_leaf)) + // Check that the relay chain parent of the block is the relay chain head + let best_number = self.polkadot_client.info().best_number; + + match self.polkadot_client.number(relay_chain_leaf) { + Err(err) => { + return Err(Box::new(ClientError::Backend(format!( + "could not find block number for {}: {}", + relay_chain_leaf, err, + )))); + }, + Ok(Some(x)) if x == best_number => {}, + Ok(None) => { + return Err(Box::new(ClientError::UnknownBlock(relay_chain_leaf.to_string()))); + }, + Ok(Some(_)) => { + trace!( + target: "cumulus-network", + "validation failed because the relay chain parent ({}) is not the relay chain \ + head ({})", + relay_chain_leaf, best_number, + ); + + return Ok(Validation::Failure); + }, + } + + 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) .map_err(|e| Box::new(ClientError::Msg(format!("{:?}", e))) as Box<_>)?; // Check that the signer is a legit validator. - let signer = self.authorities.get(sender as usize).ok_or_else(|| { + let authorities = runtime_api.validators(&runtime_api_block_id) + .map_err(|e| Box::new(ClientError::Msg(format!("{:?}", e))) as Box<_>)?; + let signer = authorities.get(sender as usize).ok_or_else(|| { Box::new(ClientError::BadJustification( "block accounced justification signer is a validator index out of bound" .to_string(), diff --git a/network/src/tests.rs b/network/src/tests.rs new file mode 100644 index 0000000000..56e4689d81 --- /dev/null +++ b/network/src/tests.rs @@ -0,0 +1,468 @@ +// Copyright 2020 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 . + +use super::*; +use cumulus_test_runtime::{Block, Header}; +use polkadot_primitives::{ + parachain::{ + AbridgedCandidateReceipt, Chain, CollatorId, DutyRoster, GlobalValidationSchedule, + Id as ParaId, LocalValidationData, ParachainHost, Retriable, SigningContext, + ValidationCode, ValidatorId, + }, + Block as PBlock, Hash as PHash, Header as PHeader, +}; +use polkadot_test_runtime_client::{ + DefaultTestClientBuilderExt, TestClient, TestClientBuilder, TestClientBuilderExt, +}; +use polkadot_validation::{sign_table_statement, Statement}; +use sp_api::{ApiRef, ProvideRuntimeApi}; +use sp_blockchain::{Error as ClientError, HeaderBackend}; +use sp_consensus::block_validation::BlockAnnounceValidator; +use sp_core::H256; +use sp_keyring::Sr25519Keyring; +use sp_runtime::traits::{Block as BlockT, NumberFor, Zero}; + +fn make_validator() -> JustifiedBlockAnnounceValidator { + let (validator, _client) = make_validator_and_client(); + + validator +} + +fn make_validator_and_client() -> ( + JustifiedBlockAnnounceValidator, + Arc, +) { + let builder = TestClientBuilder::new(); + let client = Arc::new(TestApi::new(Arc::new(builder.build()))); + + (JustifiedBlockAnnounceValidator::new(client.clone()), client) +} + +fn default_header() -> Header { + Header { + number: Default::default(), + digest: Default::default(), + extrinsics_root: Default::default(), + parent_hash: Default::default(), + state_root: Default::default(), + } +} + +fn make_gossip_message_and_header( + client: Arc, + relay_chain_leaf: H256, +) -> (GossipMessage, Header) { + let key = Sr25519Keyring::Alice.pair().into(); + let signing_context = client + .runtime_api() + .signing_context(&BlockId::Hash(relay_chain_leaf)) + .unwrap(); + let header = default_header(); + let candidate_receipt = AbridgedCandidateReceipt { + head_data: header.encode().into(), + ..AbridgedCandidateReceipt::default() + }; + let statement = Statement::Candidate(candidate_receipt); + let signature = sign_table_statement(&statement, &key, &signing_context); + let sender = 0; + let gossip_statement = GossipStatement { + relay_chain_leaf, + signed_statement: SignedStatement { + statement, + signature, + sender, + }, + }; + + (GossipMessage::Statement(gossip_statement), header) +} + +#[test] +fn valid_if_no_data() { + let mut validator = make_validator(); + let header = default_header(); + + assert!( + matches!(validator.validate(&header, &[]), Ok(Validation::Success)), + "validating without data is always a success", + ); +} + +#[test] +fn check_gossip_message_is_valid() { + let mut validator = make_validator(); + let header = default_header(); + let res = validator.validate(&header, &[0x42]).err(); + + assert!( + res.is_some(), + "only data that are gossip message are allowed" + ); + assert!(matches!( + *res.unwrap().downcast::().unwrap(), + ClientError::BadJustification(x) if x.contains("must be a gossip message") + )); +} + +#[test] +fn check_relay_parent_is_head() { + let (mut validator, client) = make_validator_and_client(); + let relay_chain_leaf = H256::zero(); + let (gossip_message, header) = make_gossip_message_and_header(client, relay_chain_leaf); + let data = gossip_message.encode(); + let res = validator.validate(&header, data.as_slice()); + + assert_eq!( + res.unwrap(), + Validation::Failure, + "validation fails if the relay chain parent is not the relay chain head", + ); +} + +#[test] +fn check_relay_parent_actually_exists() { + let (mut validator, client) = make_validator_and_client(); + let relay_chain_leaf = H256::from_low_u64_be(42); + let (gossip_message, header) = make_gossip_message_and_header(client, relay_chain_leaf); + let data = gossip_message.encode(); + let res = validator.validate(&header, data.as_slice()).err(); + + assert!( + res.is_some(), + "validation should fail if the relay chain leaf does not exist" + ); + assert!(matches!( + *res.unwrap().downcast::().unwrap(), + ClientError::UnknownBlock(_) + )); +} + +#[test] +fn check_relay_parent_fails_if_cannot_retrieve_number() { + let (mut validator, client) = make_validator_and_client(); + let relay_chain_leaf = H256::from_low_u64_be(0xdead); + let (gossip_message, header) = make_gossip_message_and_header(client, relay_chain_leaf); + let data = gossip_message.encode(); + let res = validator.validate(&header, data.as_slice()).err(); + + assert!( + res.is_some(), + "validation should fail if the relay chain leaf does not exist" + ); + assert!(matches!( + *res.unwrap().downcast::().unwrap(), + ClientError::Backend(_) + )); +} + +#[test] +fn check_signer_is_legit_validator() { + let (mut validator, client) = make_validator_and_client(); + let relay_chain_leaf = H256::from_low_u64_be(1); + + let key = Sr25519Keyring::Alice.pair().into(); + let signing_context = client + .runtime_api() + .signing_context(&BlockId::Hash(relay_chain_leaf)) + .unwrap(); + let header = default_header(); + let candidate_receipt = AbridgedCandidateReceipt { + head_data: header.encode().into(), + ..AbridgedCandidateReceipt::default() + }; + let statement = Statement::Candidate(candidate_receipt); + let signature = sign_table_statement(&statement, &key, &signing_context); + let sender = 1; + let gossip_statement = GossipStatement { + relay_chain_leaf, + signed_statement: SignedStatement { + statement, + signature, + sender, + }, + }; + let gossip_message = GossipMessage::Statement(gossip_statement); + + let data = gossip_message.encode(); + let res = validator.validate(&header, data.as_slice()).err(); + + assert!( + res.is_some(), + "validation should fail if the signer is not a legit validator" + ); + assert!(matches!( + *res.unwrap().downcast::().unwrap(), + ClientError::BadJustification(x) if x.contains("signer is a validator") + )); +} + +#[test] +fn check_statement_is_correctly_signed() { + let (mut validator, client) = make_validator_and_client(); + let header = default_header(); + let relay_chain_leaf = H256::from_low_u64_be(1); + + let key = Sr25519Keyring::Alice.pair().into(); + let signing_context = client + .runtime_api() + .signing_context(&BlockId::Hash(relay_chain_leaf)) + .unwrap(); + let mut candidate_receipt = AbridgedCandidateReceipt::default(); + let statement = Statement::Candidate(candidate_receipt.clone()); + let signature = sign_table_statement(&statement, &key, &signing_context); + + // alterate statement so the signature doesn't match anymore + candidate_receipt = AbridgedCandidateReceipt { + parachain_index: candidate_receipt.parachain_index + 1, + ..candidate_receipt + }; + let statement = Statement::Candidate(candidate_receipt); + + let sender = 0; + let gossip_statement = GossipStatement { + relay_chain_leaf, + signed_statement: SignedStatement { + statement, + signature, + sender, + }, + }; + let gossip_message = GossipMessage::Statement(gossip_statement); + + let data = gossip_message.encode(); + let res = validator.validate(&header, data.as_slice()).err(); + + assert!( + res.is_some(), + "validation should fail if the statement is not correctly signed" + ); + assert!(matches!( + *res.unwrap().downcast::().unwrap(), + ClientError::BadJustification(x) if x.contains("signature is invalid") + )); +} + +#[test] +fn check_statement_is_a_candidate_message() { + let (mut validator, client) = make_validator_and_client(); + let header = default_header(); + let relay_chain_leaf = H256::from_low_u64_be(1); + + let key = Sr25519Keyring::Alice.pair().into(); + let signing_context = client + .runtime_api() + .signing_context(&BlockId::Hash(relay_chain_leaf)) + .unwrap(); + let statement = Statement::Valid(H256::zero()); + let signature = sign_table_statement(&statement, &key, &signing_context); + let sender = 0; + let gossip_statement = GossipStatement { + relay_chain_leaf, + signed_statement: SignedStatement { + statement, + signature, + sender, + }, + }; + let gossip_message = GossipMessage::Statement(gossip_statement); + + let data = gossip_message.encode(); + let res = validator.validate(&header, data.as_slice()).err(); + + assert!( + res.is_some(), + "validation should fail if the statement is not a candidate message" + ); + assert!(matches!( + *res.unwrap().downcast::().unwrap(), + ClientError::BadJustification(x) if x.contains("must be a candidate statement") + )); +} + +#[test] +fn check_header_match_candidate_receipt_header() { + let (mut validator, client) = make_validator_and_client(); + let relay_chain_leaf = H256::from_low_u64_be(1); + + let key = Sr25519Keyring::Alice.pair().into(); + let signing_context = client + .runtime_api() + .signing_context(&BlockId::Hash(relay_chain_leaf)) + .unwrap(); + let header = default_header(); + let candidate_receipt = AbridgedCandidateReceipt::default(); + let statement = Statement::Candidate(candidate_receipt); + let signature = sign_table_statement(&statement, &key, &signing_context); + let sender = 0; + let gossip_statement = GossipStatement { + relay_chain_leaf, + signed_statement: SignedStatement { + statement, + signature, + sender, + }, + }; + let gossip_message = GossipMessage::Statement(gossip_statement); + + let data = gossip_message.encode(); + let res = validator.validate(&header, data.as_slice()).err(); + + assert!( + res.is_some(), + "validation should fail if the header in the candidate_receipt doesn't \ + match the header provided" + ); + assert!(matches!( + *res.unwrap().downcast::().unwrap(), + ClientError::BadJustification(x) if x.contains("header does not match") + )); +} + +#[derive(Default)] +struct ApiData { + validators: Vec, + duties: Vec, + active_parachains: Vec<(ParaId, Option<(CollatorId, Retriable)>)>, +} + +#[derive(Clone)] +struct TestApi { + data: Arc>, + client: Arc, +} + +impl TestApi { + fn new(client: Arc) -> Self { + Self { + client, + data: Arc::new(Mutex::new(ApiData { + validators: vec![Sr25519Keyring::Alice.public().into()], + ..Default::default() + })), + } + } +} + +#[derive(Default)] +struct RuntimeApi { + data: Arc>, +} + +impl ProvideRuntimeApi for TestApi { + type Api = RuntimeApi; + + fn runtime_api<'a>(&'a self) -> ApiRef<'a, Self::Api> { + RuntimeApi { + data: self.data.clone(), + } + .into() + } +} + +sp_api::mock_impl_runtime_apis! { + impl ParachainHost for RuntimeApi { + type Error = sp_blockchain::Error; + + fn validators(&self) -> Vec { + self.data.lock().validators.clone() + } + + fn duty_roster(&self) -> DutyRoster { + DutyRoster { + validator_duty: self.data.lock().duties.clone(), + } + } + + fn active_parachains(&self) -> Vec<(ParaId, Option<(CollatorId, Retriable)>)> { + self.data.lock().active_parachains.clone() + } + + fn parachain_code(_: ParaId) -> Option { + Some(ValidationCode(Vec::new())) + } + + fn global_validation_schedule() -> GlobalValidationSchedule { + Default::default() + } + + fn local_validation_data(_: ParaId) -> Option { + Some(Default::default()) + } + + fn get_heads(_: Vec<::Extrinsic>) -> Option> { + Some(Vec::new()) + } + + fn signing_context() -> SigningContext { + SigningContext { + session_index: Default::default(), + parent_hash: Default::default(), + } + } + } +} + +/// Blockchain database header backend. Does not perform any validation. +impl HeaderBackend for TestApi { + fn header( + &self, + _id: BlockId, + ) -> std::result::Result, sp_blockchain::Error> { + Ok(None) + } + + fn info(&self) -> sc_client_api::blockchain::Info { + let best_hash = H256::from_low_u64_be(1); + + sc_client_api::blockchain::Info { + best_hash, + best_number: 1, + finalized_hash: Default::default(), + finalized_number: Zero::zero(), + genesis_hash: Default::default(), + number_leaves: Default::default(), + } + } + + fn status( + &self, + _id: BlockId, + ) -> std::result::Result { + Ok(sc_client_api::blockchain::BlockStatus::Unknown) + } + + fn number( + &self, + hash: PHash, + ) -> std::result::Result>, sp_blockchain::Error> { + if hash == H256::zero() { + Ok(Some(0)) + } else if hash == H256::from_low_u64_be(1) { + Ok(Some(1)) + } else if hash == H256::from_low_u64_be(0xdead) { + Err(sp_blockchain::Error::Backend("dead".to_string())) + } else { + Ok(None) + } + } + + fn hash( + &self, + _number: NumberFor, + ) -> std::result::Result, sp_blockchain::Error> { + Ok(None) + } +} diff --git a/test/parachain/tests/integration_test.rs b/test/parachain/tests/integration_test.rs index 9aad67bee5..023a3912cd 100644 --- a/test/parachain/tests/integration_test.rs +++ b/test/parachain/tests/integration_test.rs @@ -362,38 +362,6 @@ async fn integration_test() { jsonrpsee::raw::RawClient::new(transport_client_cumulus_charlie); wait_for_blocks(4, &mut client_cumulus_charlie).await; - - // run cumulus dave - let cumulus_dave_dir = tempdir().unwrap(); - let mut cumulus_dave = Command::new(cargo_bin("cumulus-test-parachain-collator")) - .stdout(Stdio::piped()) - .stderr(Stdio::piped()) - .arg("--base-path") - .arg(cumulus_dave_dir.path()) - .arg("--unsafe-rpc-expose") - .arg("--rpc-port=27018") - .arg("--port=27118") - .arg("--") - .arg(format!( - "--bootnodes=/ip4/127.0.0.1/tcp/27115/p2p/{}", - polkadot_alice_id - )) - .arg(format!( - "--bootnodes=/ip4/127.0.0.1/tcp/27116/p2p/{}", - polkadot_bob_id - )) - .arg("--dave") - .spawn() - .unwrap(); - let cumulus_dave_helper = ChildHelper::new("cumulus-dave", &mut cumulus_dave); - wait_for_tcp("127.0.0.1:27018").await; - - // connect rpc client to cumulus - let transport_client_cumulus_dave = - jsonrpsee::transport::http::HttpTransportClient::new("http://127.0.0.1:27018"); - let mut client_cumulus_dave = jsonrpsee::raw::RawClient::new(transport_client_cumulus_dave); - - wait_for_blocks(4, &mut client_cumulus_dave).await; } .fuse();