diff --git a/node/core/backing/src/lib.rs b/node/core/backing/src/lib.rs index 12a4240ce649..5075eac48fb5 100644 --- a/node/core/backing/src/lib.rs +++ b/node/core/backing/src/lib.rs @@ -1,4 +1,4 @@ -// Copyright 2020 Parity Technologies (UK) Ltd. +// Copyright 2020-2021 Parity Technologies (UK) Ltd. // This file is part of Polkadot. // Polkadot is free software: you can redistribute it and/or modify @@ -67,6 +67,9 @@ use statement_table::{ }; use thiserror::Error; +#[cfg(test)] +mod tests; + const LOG_TARGET: &str = "parachain::candidate-backing"; /// Errors that can occur in candidate backing. @@ -1312,1558 +1315,3 @@ impl metrics::Metrics for Metrics { /// The candidate backing subsystem. pub type CandidateBackingSubsystem = polkadot_node_subsystem_util::JobSubsystem; - -#[cfg(test)] -mod tests { - use super::*; - use assert_matches::assert_matches; - use futures::{future, Future}; - use polkadot_primitives::v1::{GroupRotationInfo, HeadData, PersistedValidationData, ScheduledCore}; - use polkadot_subsystem::{ - messages::{RuntimeApiRequest, RuntimeApiMessage, CollatorProtocolMessage}, - ActiveLeavesUpdate, FromOverseer, OverseerSignal, ActivatedLeaf, LeafStatus, - }; - use polkadot_node_primitives::{InvalidCandidate, BlockData}; - use polkadot_node_subsystem_test_helpers as test_helpers; - use sp_keyring::Sr25519Keyring; - use sp_application_crypto::AppKey; - use sp_keystore::{CryptoStore, SyncCryptoStore}; - use statement_table::v1::Misbehavior; - use std::collections::HashMap; - use sp_tracing as _; - - fn validator_pubkeys(val_ids: &[Sr25519Keyring]) -> Vec { - val_ids.iter().map(|v| v.public().into()).collect() - } - - fn table_statement_to_primitive( - statement: TableStatement, - ) -> Statement { - match statement { - TableStatement::Seconded(committed_candidate_receipt) => Statement::Seconded(committed_candidate_receipt), - TableStatement::Valid(candidate_hash) => Statement::Valid(candidate_hash), - } - } - - struct TestState { - chain_ids: Vec, - keystore: SyncCryptoStorePtr, - validators: Vec, - validator_public: Vec, - validation_data: PersistedValidationData, - validator_groups: (Vec>, GroupRotationInfo), - availability_cores: Vec, - head_data: HashMap, - signing_context: SigningContext, - relay_parent: Hash, - } - - impl Default for TestState { - fn default() -> Self { - let chain_a = ParaId::from(1); - let chain_b = ParaId::from(2); - let thread_a = ParaId::from(3); - - let chain_ids = vec![chain_a, chain_b, thread_a]; - - let validators = vec![ - Sr25519Keyring::Alice, - Sr25519Keyring::Bob, - Sr25519Keyring::Charlie, - Sr25519Keyring::Dave, - Sr25519Keyring::Ferdie, - Sr25519Keyring::One, - ]; - - let keystore = Arc::new(sc_keystore::LocalKeystore::in_memory()); - // Make sure `Alice` key is in the keystore, so this mocked node will be a parachain validator. - SyncCryptoStore::sr25519_generate_new(&*keystore, ValidatorId::ID, Some(&validators[0].to_seed())) - .expect("Insert key into keystore"); - - let validator_public = validator_pubkeys(&validators); - - let validator_groups = vec![vec![2, 0, 3, 5], vec![1], vec![4]] - .into_iter().map(|g| g.into_iter().map(ValidatorIndex).collect()).collect(); - let group_rotation_info = GroupRotationInfo { - session_start_block: 0, - group_rotation_frequency: 100, - now: 1, - }; - - let thread_collator: CollatorId = Sr25519Keyring::Two.public().into(); - let availability_cores = vec![ - CoreState::Scheduled(ScheduledCore { - para_id: chain_a, - collator: None, - }), - CoreState::Scheduled(ScheduledCore { - para_id: chain_b, - collator: None, - }), - CoreState::Scheduled(ScheduledCore { - para_id: thread_a, - collator: Some(thread_collator.clone()), - }), - ]; - - let mut head_data = HashMap::new(); - head_data.insert(chain_a, HeadData(vec![4, 5, 6])); - - let relay_parent = Hash::repeat_byte(5); - - let signing_context = SigningContext { - session_index: 1, - parent_hash: relay_parent, - }; - - let validation_data = PersistedValidationData { - parent_head: HeadData(vec![7, 8, 9]), - relay_parent_number: Default::default(), - max_pov_size: 1024, - relay_parent_storage_root: Default::default(), - }; - - Self { - chain_ids, - keystore, - validators, - validator_public, - validator_groups: (validator_groups, group_rotation_info), - availability_cores, - head_data, - validation_data, - signing_context, - relay_parent, - } - } - } - - type VirtualOverseer = test_helpers::TestSubsystemContextHandle; - - fn test_harness>( - keystore: SyncCryptoStorePtr, - test: impl FnOnce(VirtualOverseer) -> T, - ) { - let pool = sp_core::testing::TaskExecutor::new(); - - let (context, virtual_overseer) = - test_helpers::make_subsystem_context(pool.clone()); - - let subsystem = CandidateBackingSubsystem::new( - pool.clone(), - keystore, - Metrics(None), - ).run(context); - - let test_fut = test(virtual_overseer); - - futures::pin_mut!(test_fut); - futures::pin_mut!(subsystem); - futures::executor::block_on(future::join(async move { - let mut virtual_overseer = test_fut.await; - virtual_overseer.send(FromOverseer::Signal( - OverseerSignal::Conclude, - )).await; - }, subsystem)); - } - - fn make_erasure_root(test: &TestState, pov: PoV) -> Hash { - let available_data = AvailableData { - validation_data: test.validation_data.clone(), - pov: Arc::new(pov), - }; - - let chunks = erasure_coding::obtain_chunks_v1(test.validators.len(), &available_data).unwrap(); - erasure_coding::branches(&chunks).root() - } - - #[derive(Default)] - struct TestCandidateBuilder { - para_id: ParaId, - head_data: HeadData, - pov_hash: Hash, - relay_parent: Hash, - erasure_root: Hash, - } - - impl TestCandidateBuilder { - fn build(self) -> CommittedCandidateReceipt { - CommittedCandidateReceipt { - descriptor: CandidateDescriptor { - para_id: self.para_id, - pov_hash: self.pov_hash, - relay_parent: self.relay_parent, - erasure_root: self.erasure_root, - ..Default::default() - }, - commitments: CandidateCommitments { - head_data: self.head_data, - ..Default::default() - }, - } - } - } - - // Tests that the subsystem performs actions that are requied on startup. - async fn test_startup( - virtual_overseer: &mut VirtualOverseer, - test_state: &TestState, - ) { - // Start work on some new parent. - virtual_overseer.send(FromOverseer::Signal( - OverseerSignal::ActiveLeaves(ActiveLeavesUpdate::start_work(ActivatedLeaf { - hash: test_state.relay_parent, - number: 1, - status: LeafStatus::Fresh, - span: Arc::new(jaeger::Span::Disabled), - }))) - ).await; - - // Check that subsystem job issues a request for a validator set. - assert_matches!( - virtual_overseer.recv().await, - AllMessages::RuntimeApi( - RuntimeApiMessage::Request(parent, RuntimeApiRequest::Validators(tx)) - ) if parent == test_state.relay_parent => { - tx.send(Ok(test_state.validator_public.clone())).unwrap(); - } - ); - - // Check that subsystem job issues a request for the validator groups. - assert_matches!( - virtual_overseer.recv().await, - AllMessages::RuntimeApi( - RuntimeApiMessage::Request(parent, RuntimeApiRequest::ValidatorGroups(tx)) - ) if parent == test_state.relay_parent => { - tx.send(Ok(test_state.validator_groups.clone())).unwrap(); - } - ); - - // Check that subsystem job issues a request for the session index for child. - assert_matches!( - virtual_overseer.recv().await, - AllMessages::RuntimeApi( - RuntimeApiMessage::Request(parent, RuntimeApiRequest::SessionIndexForChild(tx)) - ) if parent == test_state.relay_parent => { - tx.send(Ok(test_state.signing_context.session_index)).unwrap(); - } - ); - - // Check that subsystem job issues a request for the availability cores. - assert_matches!( - virtual_overseer.recv().await, - AllMessages::RuntimeApi( - RuntimeApiMessage::Request(parent, RuntimeApiRequest::AvailabilityCores(tx)) - ) if parent == test_state.relay_parent => { - tx.send(Ok(test_state.availability_cores.clone())).unwrap(); - } - ); - } - - // Test that a `CandidateBackingMessage::Second` issues validation work - // and in case validation is successful issues a `StatementDistributionMessage`. - #[test] - fn backing_second_works() { - let test_state = TestState::default(); - test_harness(test_state.keystore.clone(), |mut virtual_overseer| async move { - test_startup(&mut virtual_overseer, &test_state).await; - - let pov = PoV { - block_data: BlockData(vec![42, 43, 44]), - }; - - let expected_head_data = test_state.head_data.get(&test_state.chain_ids[0]).unwrap(); - - let pov_hash = pov.hash(); - let candidate = TestCandidateBuilder { - para_id: test_state.chain_ids[0], - relay_parent: test_state.relay_parent, - pov_hash, - head_data: expected_head_data.clone(), - erasure_root: make_erasure_root(&test_state, pov.clone()), - ..Default::default() - }.build(); - - let second = CandidateBackingMessage::Second( - test_state.relay_parent, - candidate.to_plain(), - pov.clone(), - ); - - virtual_overseer.send(FromOverseer::Communication{ msg: second }).await; - - - assert_matches!( - virtual_overseer.recv().await, - AllMessages::CandidateValidation( - CandidateValidationMessage::ValidateFromChainState( - c, - pov, - tx, - ) - ) if pov == pov && &c == candidate.descriptor() => { - tx.send(Ok( - ValidationResult::Valid(CandidateCommitments { - head_data: expected_head_data.clone(), - horizontal_messages: Vec::new(), - upward_messages: Vec::new(), - new_validation_code: None, - processed_downward_messages: 0, - hrmp_watermark: 0, - }, test_state.validation_data), - )).unwrap(); - } - ); - - assert_matches!( - virtual_overseer.recv().await, - AllMessages::AvailabilityStore( - AvailabilityStoreMessage::StoreAvailableData(candidate_hash, _, _, _, tx) - ) if candidate_hash == candidate.hash() => { - tx.send(Ok(())).unwrap(); - } - ); - - assert_matches!( - virtual_overseer.recv().await, - AllMessages::StatementDistribution( - StatementDistributionMessage::Share( - parent_hash, - _signed_statement, - ) - ) if parent_hash == test_state.relay_parent => {} - ); - - assert_matches!( - virtual_overseer.recv().await, - AllMessages::CollatorProtocol(CollatorProtocolMessage::Seconded(hash, statement)) => { - assert_eq!(test_state.relay_parent, hash); - assert_matches!(statement.payload(), Statement::Seconded(_)); - } - ); - - virtual_overseer.send(FromOverseer::Signal( - OverseerSignal::ActiveLeaves(ActiveLeavesUpdate::stop_work(test_state.relay_parent))) - ).await; - virtual_overseer - }); - } - - // Test that the candidate reaches quorum succesfully. - #[test] - fn backing_works() { - let test_state = TestState::default(); - test_harness(test_state.keystore.clone(), |mut virtual_overseer| async move { - test_startup(&mut virtual_overseer, &test_state).await; - - let pov = PoV { - block_data: BlockData(vec![1, 2, 3]), - }; - - let pov_hash = pov.hash(); - - let expected_head_data = test_state.head_data.get(&test_state.chain_ids[0]).unwrap(); - - let candidate_a = TestCandidateBuilder { - para_id: test_state.chain_ids[0], - relay_parent: test_state.relay_parent, - pov_hash, - head_data: expected_head_data.clone(), - erasure_root: make_erasure_root(&test_state, pov.clone()), - ..Default::default() - }.build(); - - let candidate_a_hash = candidate_a.hash(); - let public1 = CryptoStore::sr25519_generate_new( - &*test_state.keystore, - ValidatorId::ID, - Some(&test_state.validators[5].to_seed()), - ).await.expect("Insert key into keystore"); - let public2 = CryptoStore::sr25519_generate_new( - &*test_state.keystore, - ValidatorId::ID, - Some(&test_state.validators[2].to_seed()), - ).await.expect("Insert key into keystore"); - - let signed_a = SignedFullStatement::sign( - &test_state.keystore, - Statement::Seconded(candidate_a.clone()), - &test_state.signing_context, - ValidatorIndex(2), - &public2.into(), - ).await.ok().flatten().expect("should be signed"); - - let signed_b = SignedFullStatement::sign( - &test_state.keystore, - Statement::Valid(candidate_a_hash), - &test_state.signing_context, - ValidatorIndex(5), - &public1.into(), - ).await.ok().flatten().expect("should be signed"); - - let statement = CandidateBackingMessage::Statement(test_state.relay_parent, signed_a.clone()); - - virtual_overseer.send(FromOverseer::Communication{ msg: statement }).await; - - // Sending a `Statement::Seconded` for our assignment will start - // validation process. The first thing requested is the PoV. - assert_matches!( - virtual_overseer.recv().await, - AllMessages::AvailabilityDistribution( - AvailabilityDistributionMessage::FetchPoV { - relay_parent, - tx, - .. - } - ) if relay_parent == test_state.relay_parent => { - tx.send(pov.clone()).unwrap(); - } - ); - - // The next step is the actual request to Validation subsystem - // to validate the `Seconded` candidate. - assert_matches!( - virtual_overseer.recv().await, - AllMessages::CandidateValidation( - CandidateValidationMessage::ValidateFromChainState( - c, - pov, - tx, - ) - ) if pov == pov && &c == candidate_a.descriptor() => { - tx.send(Ok( - ValidationResult::Valid(CandidateCommitments { - head_data: expected_head_data.clone(), - upward_messages: Vec::new(), - horizontal_messages: Vec::new(), - new_validation_code: None, - processed_downward_messages: 0, - hrmp_watermark: 0, - }, test_state.validation_data), - )).unwrap(); - } - ); - - assert_matches!( - virtual_overseer.recv().await, - AllMessages::AvailabilityStore( - AvailabilityStoreMessage::StoreAvailableData(candidate_hash, _, _, _, tx) - ) if candidate_hash == candidate_a.hash() => { - tx.send(Ok(())).unwrap(); - } - ); - - assert_matches!( - virtual_overseer.recv().await, - AllMessages::StatementDistribution( - StatementDistributionMessage::Share(hash, _stmt) - ) => { - assert_eq!(test_state.relay_parent, hash); - } - ); - - let statement = CandidateBackingMessage::Statement( - test_state.relay_parent, - signed_b.clone(), - ); - - virtual_overseer.send(FromOverseer::Communication{ msg: statement }).await; - - assert_matches!( - virtual_overseer.recv().await, - AllMessages::Provisioner( - ProvisionerMessage::ProvisionableData( - _, - ProvisionableData::BackedCandidate(candidate_receipt) - ) - ) => { - assert_eq!(candidate_receipt, candidate_a.to_plain()); - } - ); - - virtual_overseer.send(FromOverseer::Signal( - OverseerSignal::ActiveLeaves(ActiveLeavesUpdate::stop_work(test_state.relay_parent))) - ).await; - virtual_overseer - }); - } - - #[test] - fn backing_works_while_validation_ongoing() { - let test_state = TestState::default(); - test_harness(test_state.keystore.clone(), |mut virtual_overseer| async move { - test_startup(&mut virtual_overseer, &test_state).await; - - let pov = PoV { - block_data: BlockData(vec![1, 2, 3]), - }; - - let pov_hash = pov.hash(); - - let expected_head_data = test_state.head_data.get(&test_state.chain_ids[0]).unwrap(); - - let candidate_a = TestCandidateBuilder { - para_id: test_state.chain_ids[0], - relay_parent: test_state.relay_parent, - pov_hash, - head_data: expected_head_data.clone(), - erasure_root: make_erasure_root(&test_state, pov.clone()), - ..Default::default() - }.build(); - - let candidate_a_hash = candidate_a.hash(); - let public1 = CryptoStore::sr25519_generate_new( - &*test_state.keystore, - ValidatorId::ID, - Some(&test_state.validators[5].to_seed()), - ).await.expect("Insert key into keystore"); - let public2 = CryptoStore::sr25519_generate_new( - &*test_state.keystore, - ValidatorId::ID, - Some(&test_state.validators[2].to_seed()), - ).await.expect("Insert key into keystore"); - let public3 = CryptoStore::sr25519_generate_new( - &*test_state.keystore, - ValidatorId::ID, - Some(&test_state.validators[3].to_seed()), - ).await.expect("Insert key into keystore"); - - let signed_a = SignedFullStatement::sign( - &test_state.keystore, - Statement::Seconded(candidate_a.clone()), - &test_state.signing_context, - ValidatorIndex(2), - &public2.into(), - ).await.ok().flatten().expect("should be signed"); - - let signed_b = SignedFullStatement::sign( - &test_state.keystore, - Statement::Valid(candidate_a_hash), - &test_state.signing_context, - ValidatorIndex(5), - &public1.into(), - ).await.ok().flatten().expect("should be signed"); - - let signed_c = SignedFullStatement::sign( - &test_state.keystore, - Statement::Valid(candidate_a_hash), - &test_state.signing_context, - ValidatorIndex(3), - &public3.into(), - ).await.ok().flatten().expect("should be signed"); - - let statement = CandidateBackingMessage::Statement(test_state.relay_parent, signed_a.clone()); - virtual_overseer.send(FromOverseer::Communication{ msg: statement }).await; - - // Sending a `Statement::Seconded` for our assignment will start - // validation process. The first thing requested is PoV from the - // `PoVDistribution`. - assert_matches!( - virtual_overseer.recv().await, - AllMessages::AvailabilityDistribution( - AvailabilityDistributionMessage::FetchPoV { - relay_parent, - tx, - .. - } - ) if relay_parent == test_state.relay_parent => { - tx.send(pov.clone()).unwrap(); - } - ); - - // The next step is the actual request to Validation subsystem - // to validate the `Seconded` candidate. - assert_matches!( - virtual_overseer.recv().await, - AllMessages::CandidateValidation( - CandidateValidationMessage::ValidateFromChainState( - c, - pov, - tx, - ) - ) if pov == pov && &c == candidate_a.descriptor() => { - // we never validate the candidate. our local node - // shouldn't issue any statements. - std::mem::forget(tx); - } - ); - - let statement = CandidateBackingMessage::Statement( - test_state.relay_parent, - signed_b.clone(), - ); - - virtual_overseer.send(FromOverseer::Communication{ msg: statement }).await; - - let statement = CandidateBackingMessage::Statement( - test_state.relay_parent, - signed_c.clone(), - ); - - virtual_overseer.send(FromOverseer::Communication{ msg: statement }).await; - - // Candidate gets backed entirely by other votes. - assert_matches!( - virtual_overseer.recv().await, - AllMessages::Provisioner( - ProvisionerMessage::ProvisionableData( - _, - ProvisionableData::BackedCandidate(CandidateReceipt { - descriptor, - .. - }) - ) - ) if descriptor == candidate_a.descriptor - ); - - let (tx, rx) = oneshot::channel(); - let msg = CandidateBackingMessage::GetBackedCandidates( - test_state.relay_parent, - vec![candidate_a.hash()], - tx, - ); - - virtual_overseer.send(FromOverseer::Communication{ msg }).await; - - let candidates = rx.await.unwrap(); - assert_eq!(1, candidates.len()); - assert_eq!(candidates[0].validity_votes.len(), 3); - - assert!(candidates[0].validity_votes.contains( - &ValidityAttestation::Implicit(signed_a.signature().clone()) - )); - assert!(candidates[0].validity_votes.contains( - &ValidityAttestation::Explicit(signed_b.signature().clone()) - )); - assert!(candidates[0].validity_votes.contains( - &ValidityAttestation::Explicit(signed_c.signature().clone()) - )); - assert_eq!( - candidates[0].validator_indices, - bitvec::bitvec![bitvec::order::Lsb0, u8; 1, 0, 1, 1], - ); - - virtual_overseer.send(FromOverseer::Signal( - OverseerSignal::ActiveLeaves(ActiveLeavesUpdate::stop_work(test_state.relay_parent))) - ).await; - virtual_overseer - }); - } - - // Issuing conflicting statements on the same candidate should - // be a misbehavior. - #[test] - fn backing_misbehavior_works() { - let test_state = TestState::default(); - test_harness(test_state.keystore.clone(), |mut virtual_overseer| async move { - test_startup(&mut virtual_overseer, &test_state).await; - - let pov = PoV { - block_data: BlockData(vec![1, 2, 3]), - }; - - let pov_hash = pov.hash(); - - let expected_head_data = test_state.head_data.get(&test_state.chain_ids[0]).unwrap(); - - let candidate_a = TestCandidateBuilder { - para_id: test_state.chain_ids[0], - relay_parent: test_state.relay_parent, - pov_hash, - erasure_root: make_erasure_root(&test_state, pov.clone()), - head_data: expected_head_data.clone(), - ..Default::default() - }.build(); - - let candidate_a_hash = candidate_a.hash(); - let public2 = CryptoStore::sr25519_generate_new( - &*test_state.keystore, - ValidatorId::ID, Some(&test_state.validators[2].to_seed()) - ).await.expect("Insert key into keystore"); - let seconded_2 = SignedFullStatement::sign( - &test_state.keystore, - Statement::Seconded(candidate_a.clone()), - &test_state.signing_context, - ValidatorIndex(2), - &public2.into(), - ).await.ok().flatten().expect("should be signed"); - - let valid_2 = SignedFullStatement::sign( - &test_state.keystore, - Statement::Valid(candidate_a_hash), - &test_state.signing_context, - ValidatorIndex(2), - &public2.into(), - ).await.ok().flatten().expect("should be signed"); - - let statement = CandidateBackingMessage::Statement(test_state.relay_parent, seconded_2.clone()); - - virtual_overseer.send(FromOverseer::Communication { msg: statement }).await; - - assert_matches!( - virtual_overseer.recv().await, - AllMessages::AvailabilityDistribution( - AvailabilityDistributionMessage::FetchPoV { - relay_parent, - tx, - .. - } - ) if relay_parent == test_state.relay_parent => { - tx.send(pov.clone()).unwrap(); - } - ); - - assert_matches!( - virtual_overseer.recv().await, - AllMessages::CandidateValidation( - CandidateValidationMessage::ValidateFromChainState( - c, - pov, - tx, - ) - ) if pov == pov && &c == candidate_a.descriptor() => { - tx.send(Ok( - ValidationResult::Valid(CandidateCommitments { - head_data: expected_head_data.clone(), - upward_messages: Vec::new(), - horizontal_messages: Vec::new(), - new_validation_code: None, - processed_downward_messages: 0, - hrmp_watermark: 0, - }, test_state.validation_data), - )).unwrap(); - } - ); - - assert_matches!( - virtual_overseer.recv().await, - AllMessages::AvailabilityStore( - AvailabilityStoreMessage::StoreAvailableData(candidate_hash, _, _, _, tx) - ) if candidate_hash == candidate_a.hash() => { - tx.send(Ok(())).unwrap(); - } - ); - - assert_matches!( - virtual_overseer.recv().await, - AllMessages::StatementDistribution( - StatementDistributionMessage::Share( - relay_parent, - signed_statement, - ) - ) if relay_parent == test_state.relay_parent => { - assert_eq!(*signed_statement.payload(), Statement::Valid(candidate_a_hash)); - } - ); - - // This `Valid` statement is redundant after the `Seconded` statement already sent. - let statement = CandidateBackingMessage::Statement(test_state.relay_parent, valid_2.clone()); - - virtual_overseer.send(FromOverseer::Communication { msg: statement }).await; - - assert_matches!( - virtual_overseer.recv().await, - AllMessages::Provisioner( - ProvisionerMessage::ProvisionableData( - _, - ProvisionableData::MisbehaviorReport( - relay_parent, - validator_index, - Misbehavior::ValidityDoubleVote(vdv), - ) - ) - ) if relay_parent == test_state.relay_parent => { - let ((t1, s1), (t2, s2)) = vdv.deconstruct::(); - let t1 = table_statement_to_primitive(t1); - let t2 = table_statement_to_primitive(t2); - - SignedFullStatement::new( - t1, - validator_index, - s1, - &test_state.signing_context, - &test_state.validator_public[validator_index.0 as usize], - ).expect("signature must be valid"); - - SignedFullStatement::new( - t2, - validator_index, - s2, - &test_state.signing_context, - &test_state.validator_public[validator_index.0 as usize], - ).expect("signature must be valid"); - } - ); - virtual_overseer - }); - } - - // Test that if we are asked to second an invalid candidate we - // can still second a valid one afterwards. - #[test] - fn backing_dont_second_invalid() { - let test_state = TestState::default(); - test_harness(test_state.keystore.clone(), |mut virtual_overseer| async move { - test_startup(&mut virtual_overseer, &test_state).await; - - let pov_block_a = PoV { - block_data: BlockData(vec![42, 43, 44]), - }; - - let pov_block_b = PoV { - block_data: BlockData(vec![45, 46, 47]), - }; - - let pov_hash_a = pov_block_a.hash(); - let pov_hash_b = pov_block_b.hash(); - - let expected_head_data = test_state.head_data.get(&test_state.chain_ids[0]).unwrap(); - - let candidate_a = TestCandidateBuilder { - para_id: test_state.chain_ids[0], - relay_parent: test_state.relay_parent, - pov_hash: pov_hash_a, - erasure_root: make_erasure_root(&test_state, pov_block_a.clone()), - ..Default::default() - }.build(); - - let candidate_b = TestCandidateBuilder { - para_id: test_state.chain_ids[0], - relay_parent: test_state.relay_parent, - pov_hash: pov_hash_b, - erasure_root: make_erasure_root(&test_state, pov_block_b.clone()), - head_data: expected_head_data.clone(), - ..Default::default() - }.build(); - - let second = CandidateBackingMessage::Second( - test_state.relay_parent, - candidate_a.to_plain(), - pov_block_a.clone(), - ); - - virtual_overseer.send(FromOverseer::Communication{ msg: second }).await; - - - assert_matches!( - virtual_overseer.recv().await, - AllMessages::CandidateValidation( - CandidateValidationMessage::ValidateFromChainState( - c, - pov, - tx, - ) - ) if pov == pov && &c == candidate_a.descriptor() => { - tx.send(Ok(ValidationResult::Invalid(InvalidCandidate::BadReturn))).unwrap(); - } - ); - - assert_matches!( - virtual_overseer.recv().await, - AllMessages::CollatorProtocol( - CollatorProtocolMessage::Invalid(parent_hash, c) - ) if parent_hash == test_state.relay_parent && c == candidate_a.to_plain() - ); - - let second = CandidateBackingMessage::Second( - test_state.relay_parent, - candidate_b.to_plain(), - pov_block_b.clone(), - ); - - virtual_overseer.send(FromOverseer::Communication{ msg: second }).await; - - assert_matches!( - virtual_overseer.recv().await, - AllMessages::CandidateValidation( - CandidateValidationMessage::ValidateFromChainState( - c, - pov, - tx, - ) - ) if pov == pov && &c == candidate_b.descriptor() => { - tx.send(Ok( - ValidationResult::Valid(CandidateCommitments { - head_data: expected_head_data.clone(), - upward_messages: Vec::new(), - horizontal_messages: Vec::new(), - new_validation_code: None, - processed_downward_messages: 0, - hrmp_watermark: 0, - }, test_state.validation_data), - )).unwrap(); - } - ); - - assert_matches!( - virtual_overseer.recv().await, - AllMessages::AvailabilityStore( - AvailabilityStoreMessage::StoreAvailableData(candidate_hash, _, _, _, tx) - ) if candidate_hash == candidate_b.hash() => { - tx.send(Ok(())).unwrap(); - } - ); - - assert_matches!( - virtual_overseer.recv().await, - AllMessages::StatementDistribution( - StatementDistributionMessage::Share( - parent_hash, - signed_statement, - ) - ) if parent_hash == test_state.relay_parent => { - assert_eq!(*signed_statement.payload(), Statement::Seconded(candidate_b)); - } - ); - - virtual_overseer.send(FromOverseer::Signal( - OverseerSignal::ActiveLeaves(ActiveLeavesUpdate::stop_work(test_state.relay_parent))) - ).await; - virtual_overseer - }); - } - - // Test that if we have already issued a statement (in this case `Invalid`) about a - // candidate we will not be issuing a `Seconded` statement on it. - #[test] - fn backing_second_after_first_fails_works() { - let test_state = TestState::default(); - test_harness(test_state.keystore.clone(), |mut virtual_overseer| async move { - test_startup(&mut virtual_overseer, &test_state).await; - - let pov = PoV { - block_data: BlockData(vec![42, 43, 44]), - }; - - let pov_hash = pov.hash(); - - let candidate = TestCandidateBuilder { - para_id: test_state.chain_ids[0], - relay_parent: test_state.relay_parent, - pov_hash, - erasure_root: make_erasure_root(&test_state, pov.clone()), - ..Default::default() - }.build(); - - let validator2 = CryptoStore::sr25519_generate_new( - &*test_state.keystore, - ValidatorId::ID, Some(&test_state.validators[2].to_seed()) - ).await.expect("Insert key into keystore"); - - let signed_a = SignedFullStatement::sign( - &test_state.keystore, - Statement::Seconded(candidate.clone()), - &test_state.signing_context, - ValidatorIndex(2), - &validator2.into(), - ).await.ok().flatten().expect("should be signed"); - - // Send in a `Statement` with a candidate. - let statement = CandidateBackingMessage::Statement( - test_state.relay_parent, - signed_a.clone(), - ); - - virtual_overseer.send(FromOverseer::Communication{ msg: statement }).await; - - // Subsystem requests PoV and requests validation. - assert_matches!( - virtual_overseer.recv().await, - AllMessages::AvailabilityDistribution( - AvailabilityDistributionMessage::FetchPoV { - relay_parent, - tx, - .. - } - ) if relay_parent == test_state.relay_parent => { - tx.send(pov.clone()).unwrap(); - } - ); - - - // Tell subsystem that this candidate is invalid. - assert_matches!( - virtual_overseer.recv().await, - AllMessages::CandidateValidation( - CandidateValidationMessage::ValidateFromChainState( - c, - pov, - tx, - ) - ) if pov == pov && &c == candidate.descriptor() => { - tx.send(Ok(ValidationResult::Invalid(InvalidCandidate::BadReturn))).unwrap(); - } - ); - - // Ask subsystem to `Second` a candidate that already has a statement issued about. - // This should emit no actions from subsystem. - let second = CandidateBackingMessage::Second( - test_state.relay_parent, - candidate.to_plain(), - pov.clone(), - ); - - virtual_overseer.send(FromOverseer::Communication{ msg: second }).await; - - let pov_to_second = PoV { - block_data: BlockData(vec![3, 2, 1]), - }; - - let pov_hash = pov_to_second.hash(); - - let candidate_to_second = TestCandidateBuilder { - para_id: test_state.chain_ids[0], - relay_parent: test_state.relay_parent, - pov_hash, - erasure_root: make_erasure_root(&test_state, pov_to_second.clone()), - ..Default::default() - }.build(); - - let second = CandidateBackingMessage::Second( - test_state.relay_parent, - candidate_to_second.to_plain(), - pov_to_second.clone(), - ); - - // In order to trigger _some_ actions from subsystem ask it to second another - // candidate. The only reason to do so is to make sure that no actions were - // triggered on the prev step. - virtual_overseer.send(FromOverseer::Communication{ msg: second }).await; - - assert_matches!( - virtual_overseer.recv().await, - AllMessages::CandidateValidation( - CandidateValidationMessage::ValidateFromChainState( - _, - pov, - _, - ) - ) => { - assert_eq!(&*pov, &pov_to_second); - } - ); - virtual_overseer - }); - } - - // That that if the validation of the candidate has failed this does not stop - // the work of this subsystem and so it is not fatal to the node. - #[test] - fn backing_works_after_failed_validation() { - let test_state = TestState::default(); - test_harness(test_state.keystore.clone(), |mut virtual_overseer| async move { - test_startup(&mut virtual_overseer, &test_state).await; - - let pov = PoV { - block_data: BlockData(vec![42, 43, 44]), - }; - - let pov_hash = pov.hash(); - - let candidate = TestCandidateBuilder { - para_id: test_state.chain_ids[0], - relay_parent: test_state.relay_parent, - pov_hash, - erasure_root: make_erasure_root(&test_state, pov.clone()), - ..Default::default() - }.build(); - - let public2 = CryptoStore::sr25519_generate_new( - &*test_state.keystore, - ValidatorId::ID, Some(&test_state.validators[2].to_seed()) - ).await.expect("Insert key into keystore"); - let signed_a = SignedFullStatement::sign( - &test_state.keystore, - Statement::Seconded(candidate.clone()), - &test_state.signing_context, - ValidatorIndex(2), - &public2.into(), - ).await.ok().flatten().expect("should be signed"); - - // Send in a `Statement` with a candidate. - let statement = CandidateBackingMessage::Statement( - test_state.relay_parent, - signed_a.clone(), - ); - - virtual_overseer.send(FromOverseer::Communication{ msg: statement }).await; - - // Subsystem requests PoV and requests validation. - assert_matches!( - virtual_overseer.recv().await, - AllMessages::AvailabilityDistribution( - AvailabilityDistributionMessage::FetchPoV { - relay_parent, - tx, - .. - } - ) if relay_parent == test_state.relay_parent => { - tx.send(pov.clone()).unwrap(); - } - ); - - // Tell subsystem that this candidate is invalid. - assert_matches!( - virtual_overseer.recv().await, - AllMessages::CandidateValidation( - CandidateValidationMessage::ValidateFromChainState( - c, - pov, - tx, - ) - ) if pov == pov && &c == candidate.descriptor() => { - tx.send(Err(ValidationFailed("Internal test error".into()))).unwrap(); - } - ); - - // Try to get a set of backable candidates to trigger _some_ action in the subsystem - // and check that it is still alive. - let (tx, rx) = oneshot::channel(); - let msg = CandidateBackingMessage::GetBackedCandidates( - test_state.relay_parent, - vec![candidate.hash()], - tx, - ); - - virtual_overseer.send(FromOverseer::Communication{ msg }).await; - assert_eq!(rx.await.unwrap().len(), 0); - virtual_overseer - }); - } - - // Test that a `CandidateBackingMessage::Second` issues validation work - // and in case validation is successful issues a `StatementDistributionMessage`. - #[test] - fn backing_doesnt_second_wrong_collator() { - let mut test_state = TestState::default(); - test_state.availability_cores[0] = CoreState::Scheduled(ScheduledCore { - para_id: ParaId::from(1), - collator: Some(Sr25519Keyring::Bob.public().into()), - }); - - test_harness(test_state.keystore.clone(), |mut virtual_overseer| async move { - test_startup(&mut virtual_overseer, &test_state).await; - - let pov = PoV { - block_data: BlockData(vec![42, 43, 44]), - }; - - let expected_head_data = test_state.head_data.get(&test_state.chain_ids[0]).unwrap(); - - let pov_hash = pov.hash(); - let candidate = TestCandidateBuilder { - para_id: test_state.chain_ids[0], - relay_parent: test_state.relay_parent, - pov_hash, - head_data: expected_head_data.clone(), - erasure_root: make_erasure_root(&test_state, pov.clone()), - ..Default::default() - }.build(); - - let second = CandidateBackingMessage::Second( - test_state.relay_parent, - candidate.to_plain(), - pov.clone(), - ); - - virtual_overseer.send(FromOverseer::Communication{ msg: second }).await; - - assert_matches!( - virtual_overseer.recv().await, - AllMessages::CollatorProtocol( - CollatorProtocolMessage::Invalid(parent, c) - ) if parent == test_state.relay_parent && c == candidate.to_plain() => { - } - ); - - virtual_overseer.send(FromOverseer::Signal( - OverseerSignal::ActiveLeaves(ActiveLeavesUpdate::stop_work(test_state.relay_parent))) - ).await; - virtual_overseer - }); - } - - #[test] - fn validation_work_ignores_wrong_collator() { - let mut test_state = TestState::default(); - test_state.availability_cores[0] = CoreState::Scheduled(ScheduledCore { - para_id: ParaId::from(1), - collator: Some(Sr25519Keyring::Bob.public().into()), - }); - - test_harness(test_state.keystore.clone(), |mut virtual_overseer| async move { - test_startup(&mut virtual_overseer, &test_state).await; - - let pov = PoV { - block_data: BlockData(vec![1, 2, 3]), - }; - - let pov_hash = pov.hash(); - - let expected_head_data = test_state.head_data.get(&test_state.chain_ids[0]).unwrap(); - - let candidate_a = TestCandidateBuilder { - para_id: test_state.chain_ids[0], - relay_parent: test_state.relay_parent, - pov_hash, - head_data: expected_head_data.clone(), - erasure_root: make_erasure_root(&test_state, pov.clone()), - ..Default::default() - }.build(); - - let public2 = CryptoStore::sr25519_generate_new( - &*test_state.keystore, - ValidatorId::ID, Some(&test_state.validators[2].to_seed()) - ).await.expect("Insert key into keystore"); - let seconding = SignedFullStatement::sign( - &test_state.keystore, - Statement::Seconded(candidate_a.clone()), - &test_state.signing_context, - ValidatorIndex(2), - &public2.into(), - ).await.ok().flatten().expect("should be signed"); - - let statement = CandidateBackingMessage::Statement( - test_state.relay_parent, - seconding.clone(), - ); - - virtual_overseer.send(FromOverseer::Communication{ msg: statement }).await; - - // The statement will be ignored because it has the wrong collator. - virtual_overseer.send(FromOverseer::Signal( - OverseerSignal::ActiveLeaves(ActiveLeavesUpdate::stop_work(test_state.relay_parent))) - ).await; - virtual_overseer - }); - } - - #[test] - fn candidate_backing_reorders_votes() { - use sp_core::Encode; - use std::convert::TryFrom; - - let para_id = ParaId::from(10); - let validators = vec![ - Sr25519Keyring::Alice, - Sr25519Keyring::Bob, - Sr25519Keyring::Charlie, - Sr25519Keyring::Dave, - Sr25519Keyring::Ferdie, - Sr25519Keyring::One, - ]; - - let validator_public = validator_pubkeys(&validators); - let validator_groups = { - let mut validator_groups = HashMap::new(); - validator_groups.insert(para_id, vec![0, 1, 2, 3, 4, 5].into_iter().map(ValidatorIndex).collect()); - validator_groups - }; - - let table_context = TableContext { - validator: None, - groups: validator_groups, - validators: validator_public.clone(), - }; - - let fake_attestation = |idx: u32| { - let candidate: CommittedCandidateReceipt = Default::default(); - let hash = candidate.hash(); - let mut data = vec![0; 64]; - data[0..32].copy_from_slice(hash.0.as_bytes()); - data[32..36].copy_from_slice(idx.encode().as_slice()); - - let sig = ValidatorSignature::try_from(data).unwrap(); - statement_table::generic::ValidityAttestation::Implicit(sig) - }; - - let attested = TableAttestedCandidate { - candidate: Default::default(), - validity_votes: vec![ - (ValidatorIndex(5), fake_attestation(5)), - (ValidatorIndex(3), fake_attestation(3)), - (ValidatorIndex(1), fake_attestation(1)), - ], - group_id: para_id, - }; - - let backed = table_attested_to_backed(attested, &table_context).unwrap(); - - let expected_bitvec = { - let mut validator_indices = BitVec::::with_capacity(6); - validator_indices.resize(6, false); - - validator_indices.set(1, true); - validator_indices.set(3, true); - validator_indices.set(5, true); - - validator_indices - }; - - // Should be in bitfield order, which is opposite to the order provided to the function. - let expected_attestations = vec![ - fake_attestation(1).into(), - fake_attestation(3).into(), - fake_attestation(5).into(), - ]; - - assert_eq!(backed.validator_indices, expected_bitvec); - assert_eq!(backed.validity_votes, expected_attestations); - } - - // Test whether we retry on failed PoV fetching. - #[test] - fn retry_works() { - // sp_tracing::try_init_simple(); - let test_state = TestState::default(); - test_harness(test_state.keystore.clone(), |mut virtual_overseer| async move { - test_startup(&mut virtual_overseer, &test_state).await; - - let pov = PoV { - block_data: BlockData(vec![42, 43, 44]), - }; - - let pov_hash = pov.hash(); - - let candidate = TestCandidateBuilder { - para_id: test_state.chain_ids[0], - relay_parent: test_state.relay_parent, - pov_hash, - erasure_root: make_erasure_root(&test_state, pov.clone()), - ..Default::default() - }.build(); - - let public2 = CryptoStore::sr25519_generate_new( - &*test_state.keystore, - ValidatorId::ID, Some(&test_state.validators[2].to_seed()) - ).await.expect("Insert key into keystore"); - let public3 = CryptoStore::sr25519_generate_new( - &*test_state.keystore, - ValidatorId::ID, - Some(&test_state.validators[3].to_seed()), - ).await.expect("Insert key into keystore"); - let public5 = CryptoStore::sr25519_generate_new( - &*test_state.keystore, - ValidatorId::ID, - Some(&test_state.validators[5].to_seed()), - ).await.expect("Insert key into keystore"); - let signed_a = SignedFullStatement::sign( - &test_state.keystore, - Statement::Seconded(candidate.clone()), - &test_state.signing_context, - ValidatorIndex(2), - &public2.into(), - ).await.ok().flatten().expect("should be signed"); - let signed_b = SignedFullStatement::sign( - &test_state.keystore, - Statement::Valid(candidate.hash()), - &test_state.signing_context, - ValidatorIndex(3), - &public3.into(), - ).await.ok().flatten().expect("should be signed"); - let signed_c = SignedFullStatement::sign( - &test_state.keystore, - Statement::Valid(candidate.hash()), - &test_state.signing_context, - ValidatorIndex(5), - &public5.into(), - ).await.ok().flatten().expect("should be signed"); - - // Send in a `Statement` with a candidate. - let statement = CandidateBackingMessage::Statement( - test_state.relay_parent, - signed_a.clone(), - ); - virtual_overseer.send(FromOverseer::Communication{ msg: statement }).await; - - // Subsystem requests PoV and requests validation. - // We cancel - should mean retry on next backing statement. - assert_matches!( - virtual_overseer.recv().await, - AllMessages::AvailabilityDistribution( - AvailabilityDistributionMessage::FetchPoV { - relay_parent, - tx, - .. - } - ) if relay_parent == test_state.relay_parent => { - std::mem::drop(tx); - } - ); - - let statement = CandidateBackingMessage::Statement( - test_state.relay_parent, - signed_b.clone(), - ); - virtual_overseer.send(FromOverseer::Communication{ msg: statement }).await; - - let statement = CandidateBackingMessage::Statement( - test_state.relay_parent, - signed_c.clone(), - ); - virtual_overseer.send(FromOverseer::Communication{ msg: statement }).await; - - // Not deterministic which message comes first: - for _ in 0u32..2 { - match virtual_overseer.recv().await { - AllMessages::Provisioner( - ProvisionerMessage::ProvisionableData( - _, - ProvisionableData::BackedCandidate(CandidateReceipt { - descriptor, - .. - }) - ) - ) => { - assert_eq!(descriptor, candidate.descriptor); - } - // Subsystem requests PoV and requests validation. - // We cancel once more: - AllMessages::AvailabilityDistribution( - AvailabilityDistributionMessage::FetchPoV { - relay_parent, - tx, - .. - } - ) if relay_parent == test_state.relay_parent => { - std::mem::drop(tx); - } - msg => { - assert!(false, "Unexpected message: {:?}", msg); - } - } - } - - // Subsystem requests PoV and requests validation. - // Now we pass. - assert_matches!( - virtual_overseer.recv().await, - AllMessages::AvailabilityDistribution( - AvailabilityDistributionMessage::FetchPoV { - relay_parent, - tx, - .. - } - ) if relay_parent == test_state.relay_parent => { - tx.send(pov.clone()).unwrap(); - } - ); - - assert_matches!( - virtual_overseer.recv().await, - AllMessages::CandidateValidation( - CandidateValidationMessage::ValidateFromChainState( - c, - pov, - _tx, - ) - ) if pov == pov && &c == candidate.descriptor() - ); - virtual_overseer - }); - } - - #[test] - fn observes_backing_even_if_not_validator() { - let test_state = TestState::default(); - let empty_keystore = Arc::new(sc_keystore::LocalKeystore::in_memory()); - test_harness(empty_keystore, |mut virtual_overseer| async move { - test_startup(&mut virtual_overseer, &test_state).await; - - let pov = PoV { - block_data: BlockData(vec![1, 2, 3]), - }; - - let pov_hash = pov.hash(); - - let expected_head_data = test_state.head_data.get(&test_state.chain_ids[0]).unwrap(); - - let candidate_a = TestCandidateBuilder { - para_id: test_state.chain_ids[0], - relay_parent: test_state.relay_parent, - pov_hash, - head_data: expected_head_data.clone(), - erasure_root: make_erasure_root(&test_state, pov.clone()), - ..Default::default() - }.build(); - - let candidate_a_hash = candidate_a.hash(); - let public0 = CryptoStore::sr25519_generate_new( - &*test_state.keystore, - ValidatorId::ID, - Some(&test_state.validators[0].to_seed()), - ).await.expect("Insert key into keystore"); - let public1 = CryptoStore::sr25519_generate_new( - &*test_state.keystore, - ValidatorId::ID, - Some(&test_state.validators[5].to_seed()), - ).await.expect("Insert key into keystore"); - let public2 = CryptoStore::sr25519_generate_new( - &*test_state.keystore, - ValidatorId::ID, - Some(&test_state.validators[2].to_seed()), - ).await.expect("Insert key into keystore"); - - // Produce a 3-of-5 quorum on the candidate. - - let signed_a = SignedFullStatement::sign( - &test_state.keystore, - Statement::Seconded(candidate_a.clone()), - &test_state.signing_context, - ValidatorIndex(0), - &public0.into(), - ).await.ok().flatten().expect("should be signed"); - - let signed_b = SignedFullStatement::sign( - &test_state.keystore, - Statement::Valid(candidate_a_hash), - &test_state.signing_context, - ValidatorIndex(5), - &public1.into(), - ).await.ok().flatten().expect("should be signed"); - - let signed_c = SignedFullStatement::sign( - &test_state.keystore, - Statement::Valid(candidate_a_hash), - &test_state.signing_context, - ValidatorIndex(2), - &public2.into(), - ).await.ok().flatten().expect("should be signed"); - - let statement = CandidateBackingMessage::Statement(test_state.relay_parent, signed_a.clone()); - - virtual_overseer.send(FromOverseer::Communication{ msg: statement }).await; - - let statement = CandidateBackingMessage::Statement( - test_state.relay_parent, - signed_b.clone(), - ); - - virtual_overseer.send(FromOverseer::Communication{ msg: statement }).await; - - let statement = CandidateBackingMessage::Statement( - test_state.relay_parent, - signed_c.clone(), - ); - - virtual_overseer.send(FromOverseer::Communication{ msg: statement }).await; - - assert_matches!( - virtual_overseer.recv().await, - AllMessages::Provisioner( - ProvisionerMessage::ProvisionableData( - _, - ProvisionableData::BackedCandidate(candidate_receipt) - ) - ) => { - assert_eq!(candidate_receipt, candidate_a.to_plain()); - } - ); - - virtual_overseer.send(FromOverseer::Signal( - OverseerSignal::ActiveLeaves(ActiveLeavesUpdate::stop_work(test_state.relay_parent))) - ).await; - virtual_overseer - }); - } -} diff --git a/node/core/backing/src/tests.rs b/node/core/backing/src/tests.rs new file mode 100644 index 000000000000..8aa4a3a9f3cc --- /dev/null +++ b/node/core/backing/src/tests.rs @@ -0,0 +1,1567 @@ +// Copyright 2020-2021 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 assert_matches::assert_matches; +use futures::{future, Future}; +use polkadot_primitives::v1::{GroupRotationInfo, HeadData, PersistedValidationData, ScheduledCore}; +use polkadot_subsystem::{ + messages::{RuntimeApiRequest, RuntimeApiMessage, CollatorProtocolMessage}, + ActiveLeavesUpdate, FromOverseer, OverseerSignal, ActivatedLeaf, LeafStatus, +}; +use polkadot_node_primitives::{InvalidCandidate, BlockData}; +use polkadot_node_subsystem_test_helpers as test_helpers; +use sp_keyring::Sr25519Keyring; +use sp_application_crypto::AppKey; +use sp_keystore::{CryptoStore, SyncCryptoStore}; +use statement_table::v1::Misbehavior; +use std::collections::HashMap; +use sp_tracing as _; + +fn validator_pubkeys(val_ids: &[Sr25519Keyring]) -> Vec { + val_ids.iter().map(|v| v.public().into()).collect() +} + +fn table_statement_to_primitive( + statement: TableStatement, +) -> Statement { + match statement { + TableStatement::Seconded(committed_candidate_receipt) => Statement::Seconded(committed_candidate_receipt), + TableStatement::Valid(candidate_hash) => Statement::Valid(candidate_hash), + } +} + +struct TestState { + chain_ids: Vec, + keystore: SyncCryptoStorePtr, + validators: Vec, + validator_public: Vec, + validation_data: PersistedValidationData, + validator_groups: (Vec>, GroupRotationInfo), + availability_cores: Vec, + head_data: HashMap, + signing_context: SigningContext, + relay_parent: Hash, +} + +impl Default for TestState { + fn default() -> Self { + let chain_a = ParaId::from(1); + let chain_b = ParaId::from(2); + let thread_a = ParaId::from(3); + + let chain_ids = vec![chain_a, chain_b, thread_a]; + + let validators = vec![ + Sr25519Keyring::Alice, + Sr25519Keyring::Bob, + Sr25519Keyring::Charlie, + Sr25519Keyring::Dave, + Sr25519Keyring::Ferdie, + Sr25519Keyring::One, + ]; + + let keystore = Arc::new(sc_keystore::LocalKeystore::in_memory()); + // Make sure `Alice` key is in the keystore, so this mocked node will be a parachain validator. + SyncCryptoStore::sr25519_generate_new(&*keystore, ValidatorId::ID, Some(&validators[0].to_seed())) + .expect("Insert key into keystore"); + + let validator_public = validator_pubkeys(&validators); + + let validator_groups = vec![vec![2, 0, 3, 5], vec![1], vec![4]] + .into_iter().map(|g| g.into_iter().map(ValidatorIndex).collect()).collect(); + let group_rotation_info = GroupRotationInfo { + session_start_block: 0, + group_rotation_frequency: 100, + now: 1, + }; + + let thread_collator: CollatorId = Sr25519Keyring::Two.public().into(); + let availability_cores = vec![ + CoreState::Scheduled(ScheduledCore { + para_id: chain_a, + collator: None, + }), + CoreState::Scheduled(ScheduledCore { + para_id: chain_b, + collator: None, + }), + CoreState::Scheduled(ScheduledCore { + para_id: thread_a, + collator: Some(thread_collator.clone()), + }), + ]; + + let mut head_data = HashMap::new(); + head_data.insert(chain_a, HeadData(vec![4, 5, 6])); + + let relay_parent = Hash::repeat_byte(5); + + let signing_context = SigningContext { + session_index: 1, + parent_hash: relay_parent, + }; + + let validation_data = PersistedValidationData { + parent_head: HeadData(vec![7, 8, 9]), + relay_parent_number: Default::default(), + max_pov_size: 1024, + relay_parent_storage_root: Default::default(), + }; + + Self { + chain_ids, + keystore, + validators, + validator_public, + validator_groups: (validator_groups, group_rotation_info), + availability_cores, + head_data, + validation_data, + signing_context, + relay_parent, + } + } +} + +type VirtualOverseer = test_helpers::TestSubsystemContextHandle; + +fn test_harness>( + keystore: SyncCryptoStorePtr, + test: impl FnOnce(VirtualOverseer) -> T, +) { + let pool = sp_core::testing::TaskExecutor::new(); + + let (context, virtual_overseer) = + test_helpers::make_subsystem_context(pool.clone()); + + let subsystem = CandidateBackingSubsystem::new( + pool.clone(), + keystore, + Metrics(None), + ).run(context); + + let test_fut = test(virtual_overseer); + + futures::pin_mut!(test_fut); + futures::pin_mut!(subsystem); + futures::executor::block_on(future::join(async move { + let mut virtual_overseer = test_fut.await; + virtual_overseer.send(FromOverseer::Signal( + OverseerSignal::Conclude, + )).await; + }, subsystem)); +} + +fn make_erasure_root(test: &TestState, pov: PoV) -> Hash { + let available_data = AvailableData { + validation_data: test.validation_data.clone(), + pov: Arc::new(pov), + }; + + let chunks = erasure_coding::obtain_chunks_v1(test.validators.len(), &available_data).unwrap(); + erasure_coding::branches(&chunks).root() +} + +#[derive(Default)] +struct TestCandidateBuilder { + para_id: ParaId, + head_data: HeadData, + pov_hash: Hash, + relay_parent: Hash, + erasure_root: Hash, +} + +impl TestCandidateBuilder { + fn build(self) -> CommittedCandidateReceipt { + CommittedCandidateReceipt { + descriptor: CandidateDescriptor { + para_id: self.para_id, + pov_hash: self.pov_hash, + relay_parent: self.relay_parent, + erasure_root: self.erasure_root, + ..Default::default() + }, + commitments: CandidateCommitments { + head_data: self.head_data, + ..Default::default() + }, + } + } +} + +// Tests that the subsystem performs actions that are requied on startup. +async fn test_startup( + virtual_overseer: &mut VirtualOverseer, + test_state: &TestState, +) { + // Start work on some new parent. + virtual_overseer.send(FromOverseer::Signal( + OverseerSignal::ActiveLeaves(ActiveLeavesUpdate::start_work(ActivatedLeaf { + hash: test_state.relay_parent, + number: 1, + status: LeafStatus::Fresh, + span: Arc::new(jaeger::Span::Disabled), + }))) + ).await; + + // Check that subsystem job issues a request for a validator set. + assert_matches!( + virtual_overseer.recv().await, + AllMessages::RuntimeApi( + RuntimeApiMessage::Request(parent, RuntimeApiRequest::Validators(tx)) + ) if parent == test_state.relay_parent => { + tx.send(Ok(test_state.validator_public.clone())).unwrap(); + } + ); + + // Check that subsystem job issues a request for the validator groups. + assert_matches!( + virtual_overseer.recv().await, + AllMessages::RuntimeApi( + RuntimeApiMessage::Request(parent, RuntimeApiRequest::ValidatorGroups(tx)) + ) if parent == test_state.relay_parent => { + tx.send(Ok(test_state.validator_groups.clone())).unwrap(); + } + ); + + // Check that subsystem job issues a request for the session index for child. + assert_matches!( + virtual_overseer.recv().await, + AllMessages::RuntimeApi( + RuntimeApiMessage::Request(parent, RuntimeApiRequest::SessionIndexForChild(tx)) + ) if parent == test_state.relay_parent => { + tx.send(Ok(test_state.signing_context.session_index)).unwrap(); + } + ); + + // Check that subsystem job issues a request for the availability cores. + assert_matches!( + virtual_overseer.recv().await, + AllMessages::RuntimeApi( + RuntimeApiMessage::Request(parent, RuntimeApiRequest::AvailabilityCores(tx)) + ) if parent == test_state.relay_parent => { + tx.send(Ok(test_state.availability_cores.clone())).unwrap(); + } + ); +} + +// Test that a `CandidateBackingMessage::Second` issues validation work +// and in case validation is successful issues a `StatementDistributionMessage`. +#[test] +fn backing_second_works() { + let test_state = TestState::default(); + test_harness(test_state.keystore.clone(), |mut virtual_overseer| async move { + test_startup(&mut virtual_overseer, &test_state).await; + + let pov = PoV { + block_data: BlockData(vec![42, 43, 44]), + }; + + let expected_head_data = test_state.head_data.get(&test_state.chain_ids[0]).unwrap(); + + let pov_hash = pov.hash(); + let candidate = TestCandidateBuilder { + para_id: test_state.chain_ids[0], + relay_parent: test_state.relay_parent, + pov_hash, + head_data: expected_head_data.clone(), + erasure_root: make_erasure_root(&test_state, pov.clone()), + ..Default::default() + }.build(); + + let second = CandidateBackingMessage::Second( + test_state.relay_parent, + candidate.to_plain(), + pov.clone(), + ); + + virtual_overseer.send(FromOverseer::Communication{ msg: second }).await; + + + assert_matches!( + virtual_overseer.recv().await, + AllMessages::CandidateValidation( + CandidateValidationMessage::ValidateFromChainState( + c, + pov, + tx, + ) + ) if pov == pov && &c == candidate.descriptor() => { + tx.send(Ok( + ValidationResult::Valid(CandidateCommitments { + head_data: expected_head_data.clone(), + horizontal_messages: Vec::new(), + upward_messages: Vec::new(), + new_validation_code: None, + processed_downward_messages: 0, + hrmp_watermark: 0, + }, test_state.validation_data), + )).unwrap(); + } + ); + + assert_matches!( + virtual_overseer.recv().await, + AllMessages::AvailabilityStore( + AvailabilityStoreMessage::StoreAvailableData(candidate_hash, _, _, _, tx) + ) if candidate_hash == candidate.hash() => { + tx.send(Ok(())).unwrap(); + } + ); + + assert_matches!( + virtual_overseer.recv().await, + AllMessages::StatementDistribution( + StatementDistributionMessage::Share( + parent_hash, + _signed_statement, + ) + ) if parent_hash == test_state.relay_parent => {} + ); + + assert_matches!( + virtual_overseer.recv().await, + AllMessages::CollatorProtocol(CollatorProtocolMessage::Seconded(hash, statement)) => { + assert_eq!(test_state.relay_parent, hash); + assert_matches!(statement.payload(), Statement::Seconded(_)); + } + ); + + virtual_overseer.send(FromOverseer::Signal( + OverseerSignal::ActiveLeaves(ActiveLeavesUpdate::stop_work(test_state.relay_parent))) + ).await; + virtual_overseer + }); +} + +// Test that the candidate reaches quorum succesfully. +#[test] +fn backing_works() { + let test_state = TestState::default(); + test_harness(test_state.keystore.clone(), |mut virtual_overseer| async move { + test_startup(&mut virtual_overseer, &test_state).await; + + let pov = PoV { + block_data: BlockData(vec![1, 2, 3]), + }; + + let pov_hash = pov.hash(); + + let expected_head_data = test_state.head_data.get(&test_state.chain_ids[0]).unwrap(); + + let candidate_a = TestCandidateBuilder { + para_id: test_state.chain_ids[0], + relay_parent: test_state.relay_parent, + pov_hash, + head_data: expected_head_data.clone(), + erasure_root: make_erasure_root(&test_state, pov.clone()), + ..Default::default() + }.build(); + + let candidate_a_hash = candidate_a.hash(); + let public1 = CryptoStore::sr25519_generate_new( + &*test_state.keystore, + ValidatorId::ID, + Some(&test_state.validators[5].to_seed()), + ).await.expect("Insert key into keystore"); + let public2 = CryptoStore::sr25519_generate_new( + &*test_state.keystore, + ValidatorId::ID, + Some(&test_state.validators[2].to_seed()), + ).await.expect("Insert key into keystore"); + + let signed_a = SignedFullStatement::sign( + &test_state.keystore, + Statement::Seconded(candidate_a.clone()), + &test_state.signing_context, + ValidatorIndex(2), + &public2.into(), + ).await.ok().flatten().expect("should be signed"); + + let signed_b = SignedFullStatement::sign( + &test_state.keystore, + Statement::Valid(candidate_a_hash), + &test_state.signing_context, + ValidatorIndex(5), + &public1.into(), + ).await.ok().flatten().expect("should be signed"); + + let statement = CandidateBackingMessage::Statement(test_state.relay_parent, signed_a.clone()); + + virtual_overseer.send(FromOverseer::Communication{ msg: statement }).await; + + // Sending a `Statement::Seconded` for our assignment will start + // validation process. The first thing requested is the PoV. + assert_matches!( + virtual_overseer.recv().await, + AllMessages::AvailabilityDistribution( + AvailabilityDistributionMessage::FetchPoV { + relay_parent, + tx, + .. + } + ) if relay_parent == test_state.relay_parent => { + tx.send(pov.clone()).unwrap(); + } + ); + + // The next step is the actual request to Validation subsystem + // to validate the `Seconded` candidate. + assert_matches!( + virtual_overseer.recv().await, + AllMessages::CandidateValidation( + CandidateValidationMessage::ValidateFromChainState( + c, + pov, + tx, + ) + ) if pov == pov && &c == candidate_a.descriptor() => { + tx.send(Ok( + ValidationResult::Valid(CandidateCommitments { + head_data: expected_head_data.clone(), + upward_messages: Vec::new(), + horizontal_messages: Vec::new(), + new_validation_code: None, + processed_downward_messages: 0, + hrmp_watermark: 0, + }, test_state.validation_data), + )).unwrap(); + } + ); + + assert_matches!( + virtual_overseer.recv().await, + AllMessages::AvailabilityStore( + AvailabilityStoreMessage::StoreAvailableData(candidate_hash, _, _, _, tx) + ) if candidate_hash == candidate_a.hash() => { + tx.send(Ok(())).unwrap(); + } + ); + + assert_matches!( + virtual_overseer.recv().await, + AllMessages::StatementDistribution( + StatementDistributionMessage::Share(hash, _stmt) + ) => { + assert_eq!(test_state.relay_parent, hash); + } + ); + + let statement = CandidateBackingMessage::Statement( + test_state.relay_parent, + signed_b.clone(), + ); + + virtual_overseer.send(FromOverseer::Communication{ msg: statement }).await; + + assert_matches!( + virtual_overseer.recv().await, + AllMessages::Provisioner( + ProvisionerMessage::ProvisionableData( + _, + ProvisionableData::BackedCandidate(candidate_receipt) + ) + ) => { + assert_eq!(candidate_receipt, candidate_a.to_plain()); + } + ); + + virtual_overseer.send(FromOverseer::Signal( + OverseerSignal::ActiveLeaves(ActiveLeavesUpdate::stop_work(test_state.relay_parent))) + ).await; + virtual_overseer + }); +} + +#[test] +fn backing_works_while_validation_ongoing() { + let test_state = TestState::default(); + test_harness(test_state.keystore.clone(), |mut virtual_overseer| async move { + test_startup(&mut virtual_overseer, &test_state).await; + + let pov = PoV { + block_data: BlockData(vec![1, 2, 3]), + }; + + let pov_hash = pov.hash(); + + let expected_head_data = test_state.head_data.get(&test_state.chain_ids[0]).unwrap(); + + let candidate_a = TestCandidateBuilder { + para_id: test_state.chain_ids[0], + relay_parent: test_state.relay_parent, + pov_hash, + head_data: expected_head_data.clone(), + erasure_root: make_erasure_root(&test_state, pov.clone()), + ..Default::default() + }.build(); + + let candidate_a_hash = candidate_a.hash(); + let public1 = CryptoStore::sr25519_generate_new( + &*test_state.keystore, + ValidatorId::ID, + Some(&test_state.validators[5].to_seed()), + ).await.expect("Insert key into keystore"); + let public2 = CryptoStore::sr25519_generate_new( + &*test_state.keystore, + ValidatorId::ID, + Some(&test_state.validators[2].to_seed()), + ).await.expect("Insert key into keystore"); + let public3 = CryptoStore::sr25519_generate_new( + &*test_state.keystore, + ValidatorId::ID, + Some(&test_state.validators[3].to_seed()), + ).await.expect("Insert key into keystore"); + + let signed_a = SignedFullStatement::sign( + &test_state.keystore, + Statement::Seconded(candidate_a.clone()), + &test_state.signing_context, + ValidatorIndex(2), + &public2.into(), + ).await.ok().flatten().expect("should be signed"); + + let signed_b = SignedFullStatement::sign( + &test_state.keystore, + Statement::Valid(candidate_a_hash), + &test_state.signing_context, + ValidatorIndex(5), + &public1.into(), + ).await.ok().flatten().expect("should be signed"); + + let signed_c = SignedFullStatement::sign( + &test_state.keystore, + Statement::Valid(candidate_a_hash), + &test_state.signing_context, + ValidatorIndex(3), + &public3.into(), + ).await.ok().flatten().expect("should be signed"); + + let statement = CandidateBackingMessage::Statement(test_state.relay_parent, signed_a.clone()); + virtual_overseer.send(FromOverseer::Communication{ msg: statement }).await; + + // Sending a `Statement::Seconded` for our assignment will start + // validation process. The first thing requested is PoV from the + // `PoVDistribution`. + assert_matches!( + virtual_overseer.recv().await, + AllMessages::AvailabilityDistribution( + AvailabilityDistributionMessage::FetchPoV { + relay_parent, + tx, + .. + } + ) if relay_parent == test_state.relay_parent => { + tx.send(pov.clone()).unwrap(); + } + ); + + // The next step is the actual request to Validation subsystem + // to validate the `Seconded` candidate. + assert_matches!( + virtual_overseer.recv().await, + AllMessages::CandidateValidation( + CandidateValidationMessage::ValidateFromChainState( + c, + pov, + tx, + ) + ) if pov == pov && &c == candidate_a.descriptor() => { + // we never validate the candidate. our local node + // shouldn't issue any statements. + std::mem::forget(tx); + } + ); + + let statement = CandidateBackingMessage::Statement( + test_state.relay_parent, + signed_b.clone(), + ); + + virtual_overseer.send(FromOverseer::Communication{ msg: statement }).await; + + let statement = CandidateBackingMessage::Statement( + test_state.relay_parent, + signed_c.clone(), + ); + + virtual_overseer.send(FromOverseer::Communication{ msg: statement }).await; + + // Candidate gets backed entirely by other votes. + assert_matches!( + virtual_overseer.recv().await, + AllMessages::Provisioner( + ProvisionerMessage::ProvisionableData( + _, + ProvisionableData::BackedCandidate(CandidateReceipt { + descriptor, + .. + }) + ) + ) if descriptor == candidate_a.descriptor + ); + + let (tx, rx) = oneshot::channel(); + let msg = CandidateBackingMessage::GetBackedCandidates( + test_state.relay_parent, + vec![candidate_a.hash()], + tx, + ); + + virtual_overseer.send(FromOverseer::Communication{ msg }).await; + + let candidates = rx.await.unwrap(); + assert_eq!(1, candidates.len()); + assert_eq!(candidates[0].validity_votes.len(), 3); + + assert!(candidates[0].validity_votes.contains( + &ValidityAttestation::Implicit(signed_a.signature().clone()) + )); + assert!(candidates[0].validity_votes.contains( + &ValidityAttestation::Explicit(signed_b.signature().clone()) + )); + assert!(candidates[0].validity_votes.contains( + &ValidityAttestation::Explicit(signed_c.signature().clone()) + )); + assert_eq!( + candidates[0].validator_indices, + bitvec::bitvec![bitvec::order::Lsb0, u8; 1, 0, 1, 1], + ); + + virtual_overseer.send(FromOverseer::Signal( + OverseerSignal::ActiveLeaves(ActiveLeavesUpdate::stop_work(test_state.relay_parent))) + ).await; + virtual_overseer + }); +} + +// Issuing conflicting statements on the same candidate should +// be a misbehavior. +#[test] +fn backing_misbehavior_works() { + let test_state = TestState::default(); + test_harness(test_state.keystore.clone(), |mut virtual_overseer| async move { + test_startup(&mut virtual_overseer, &test_state).await; + + let pov = PoV { + block_data: BlockData(vec![1, 2, 3]), + }; + + let pov_hash = pov.hash(); + + let expected_head_data = test_state.head_data.get(&test_state.chain_ids[0]).unwrap(); + + let candidate_a = TestCandidateBuilder { + para_id: test_state.chain_ids[0], + relay_parent: test_state.relay_parent, + pov_hash, + erasure_root: make_erasure_root(&test_state, pov.clone()), + head_data: expected_head_data.clone(), + ..Default::default() + }.build(); + + let candidate_a_hash = candidate_a.hash(); + let public2 = CryptoStore::sr25519_generate_new( + &*test_state.keystore, + ValidatorId::ID, Some(&test_state.validators[2].to_seed()) + ).await.expect("Insert key into keystore"); + let seconded_2 = SignedFullStatement::sign( + &test_state.keystore, + Statement::Seconded(candidate_a.clone()), + &test_state.signing_context, + ValidatorIndex(2), + &public2.into(), + ).await.ok().flatten().expect("should be signed"); + + let valid_2 = SignedFullStatement::sign( + &test_state.keystore, + Statement::Valid(candidate_a_hash), + &test_state.signing_context, + ValidatorIndex(2), + &public2.into(), + ).await.ok().flatten().expect("should be signed"); + + let statement = CandidateBackingMessage::Statement(test_state.relay_parent, seconded_2.clone()); + + virtual_overseer.send(FromOverseer::Communication { msg: statement }).await; + + assert_matches!( + virtual_overseer.recv().await, + AllMessages::AvailabilityDistribution( + AvailabilityDistributionMessage::FetchPoV { + relay_parent, + tx, + .. + } + ) if relay_parent == test_state.relay_parent => { + tx.send(pov.clone()).unwrap(); + } + ); + + assert_matches!( + virtual_overseer.recv().await, + AllMessages::CandidateValidation( + CandidateValidationMessage::ValidateFromChainState( + c, + pov, + tx, + ) + ) if pov == pov && &c == candidate_a.descriptor() => { + tx.send(Ok( + ValidationResult::Valid(CandidateCommitments { + head_data: expected_head_data.clone(), + upward_messages: Vec::new(), + horizontal_messages: Vec::new(), + new_validation_code: None, + processed_downward_messages: 0, + hrmp_watermark: 0, + }, test_state.validation_data), + )).unwrap(); + } + ); + + assert_matches!( + virtual_overseer.recv().await, + AllMessages::AvailabilityStore( + AvailabilityStoreMessage::StoreAvailableData(candidate_hash, _, _, _, tx) + ) if candidate_hash == candidate_a.hash() => { + tx.send(Ok(())).unwrap(); + } + ); + + assert_matches!( + virtual_overseer.recv().await, + AllMessages::StatementDistribution( + StatementDistributionMessage::Share( + relay_parent, + signed_statement, + ) + ) if relay_parent == test_state.relay_parent => { + assert_eq!(*signed_statement.payload(), Statement::Valid(candidate_a_hash)); + } + ); + + // This `Valid` statement is redundant after the `Seconded` statement already sent. + let statement = CandidateBackingMessage::Statement(test_state.relay_parent, valid_2.clone()); + + virtual_overseer.send(FromOverseer::Communication { msg: statement }).await; + + assert_matches!( + virtual_overseer.recv().await, + AllMessages::Provisioner( + ProvisionerMessage::ProvisionableData( + _, + ProvisionableData::MisbehaviorReport( + relay_parent, + validator_index, + Misbehavior::ValidityDoubleVote(vdv), + ) + ) + ) if relay_parent == test_state.relay_parent => { + let ((t1, s1), (t2, s2)) = vdv.deconstruct::(); + let t1 = table_statement_to_primitive(t1); + let t2 = table_statement_to_primitive(t2); + + SignedFullStatement::new( + t1, + validator_index, + s1, + &test_state.signing_context, + &test_state.validator_public[validator_index.0 as usize], + ).expect("signature must be valid"); + + SignedFullStatement::new( + t2, + validator_index, + s2, + &test_state.signing_context, + &test_state.validator_public[validator_index.0 as usize], + ).expect("signature must be valid"); + } + ); + virtual_overseer + }); +} + +// Test that if we are asked to second an invalid candidate we +// can still second a valid one afterwards. +#[test] +fn backing_dont_second_invalid() { + let test_state = TestState::default(); + test_harness(test_state.keystore.clone(), |mut virtual_overseer| async move { + test_startup(&mut virtual_overseer, &test_state).await; + + let pov_block_a = PoV { + block_data: BlockData(vec![42, 43, 44]), + }; + + let pov_block_b = PoV { + block_data: BlockData(vec![45, 46, 47]), + }; + + let pov_hash_a = pov_block_a.hash(); + let pov_hash_b = pov_block_b.hash(); + + let expected_head_data = test_state.head_data.get(&test_state.chain_ids[0]).unwrap(); + + let candidate_a = TestCandidateBuilder { + para_id: test_state.chain_ids[0], + relay_parent: test_state.relay_parent, + pov_hash: pov_hash_a, + erasure_root: make_erasure_root(&test_state, pov_block_a.clone()), + ..Default::default() + }.build(); + + let candidate_b = TestCandidateBuilder { + para_id: test_state.chain_ids[0], + relay_parent: test_state.relay_parent, + pov_hash: pov_hash_b, + erasure_root: make_erasure_root(&test_state, pov_block_b.clone()), + head_data: expected_head_data.clone(), + ..Default::default() + }.build(); + + let second = CandidateBackingMessage::Second( + test_state.relay_parent, + candidate_a.to_plain(), + pov_block_a.clone(), + ); + + virtual_overseer.send(FromOverseer::Communication{ msg: second }).await; + + + assert_matches!( + virtual_overseer.recv().await, + AllMessages::CandidateValidation( + CandidateValidationMessage::ValidateFromChainState( + c, + pov, + tx, + ) + ) if pov == pov && &c == candidate_a.descriptor() => { + tx.send(Ok(ValidationResult::Invalid(InvalidCandidate::BadReturn))).unwrap(); + } + ); + + assert_matches!( + virtual_overseer.recv().await, + AllMessages::CollatorProtocol( + CollatorProtocolMessage::Invalid(parent_hash, c) + ) if parent_hash == test_state.relay_parent && c == candidate_a.to_plain() + ); + + let second = CandidateBackingMessage::Second( + test_state.relay_parent, + candidate_b.to_plain(), + pov_block_b.clone(), + ); + + virtual_overseer.send(FromOverseer::Communication{ msg: second }).await; + + assert_matches!( + virtual_overseer.recv().await, + AllMessages::CandidateValidation( + CandidateValidationMessage::ValidateFromChainState( + c, + pov, + tx, + ) + ) if pov == pov && &c == candidate_b.descriptor() => { + tx.send(Ok( + ValidationResult::Valid(CandidateCommitments { + head_data: expected_head_data.clone(), + upward_messages: Vec::new(), + horizontal_messages: Vec::new(), + new_validation_code: None, + processed_downward_messages: 0, + hrmp_watermark: 0, + }, test_state.validation_data), + )).unwrap(); + } + ); + + assert_matches!( + virtual_overseer.recv().await, + AllMessages::AvailabilityStore( + AvailabilityStoreMessage::StoreAvailableData(candidate_hash, _, _, _, tx) + ) if candidate_hash == candidate_b.hash() => { + tx.send(Ok(())).unwrap(); + } + ); + + assert_matches!( + virtual_overseer.recv().await, + AllMessages::StatementDistribution( + StatementDistributionMessage::Share( + parent_hash, + signed_statement, + ) + ) if parent_hash == test_state.relay_parent => { + assert_eq!(*signed_statement.payload(), Statement::Seconded(candidate_b)); + } + ); + + virtual_overseer.send(FromOverseer::Signal( + OverseerSignal::ActiveLeaves(ActiveLeavesUpdate::stop_work(test_state.relay_parent))) + ).await; + virtual_overseer + }); +} + +// Test that if we have already issued a statement (in this case `Invalid`) about a +// candidate we will not be issuing a `Seconded` statement on it. +#[test] +fn backing_second_after_first_fails_works() { + let test_state = TestState::default(); + test_harness(test_state.keystore.clone(), |mut virtual_overseer| async move { + test_startup(&mut virtual_overseer, &test_state).await; + + let pov = PoV { + block_data: BlockData(vec![42, 43, 44]), + }; + + let pov_hash = pov.hash(); + + let candidate = TestCandidateBuilder { + para_id: test_state.chain_ids[0], + relay_parent: test_state.relay_parent, + pov_hash, + erasure_root: make_erasure_root(&test_state, pov.clone()), + ..Default::default() + }.build(); + + let validator2 = CryptoStore::sr25519_generate_new( + &*test_state.keystore, + ValidatorId::ID, Some(&test_state.validators[2].to_seed()) + ).await.expect("Insert key into keystore"); + + let signed_a = SignedFullStatement::sign( + &test_state.keystore, + Statement::Seconded(candidate.clone()), + &test_state.signing_context, + ValidatorIndex(2), + &validator2.into(), + ).await.ok().flatten().expect("should be signed"); + + // Send in a `Statement` with a candidate. + let statement = CandidateBackingMessage::Statement( + test_state.relay_parent, + signed_a.clone(), + ); + + virtual_overseer.send(FromOverseer::Communication{ msg: statement }).await; + + // Subsystem requests PoV and requests validation. + assert_matches!( + virtual_overseer.recv().await, + AllMessages::AvailabilityDistribution( + AvailabilityDistributionMessage::FetchPoV { + relay_parent, + tx, + .. + } + ) if relay_parent == test_state.relay_parent => { + tx.send(pov.clone()).unwrap(); + } + ); + + + // Tell subsystem that this candidate is invalid. + assert_matches!( + virtual_overseer.recv().await, + AllMessages::CandidateValidation( + CandidateValidationMessage::ValidateFromChainState( + c, + pov, + tx, + ) + ) if pov == pov && &c == candidate.descriptor() => { + tx.send(Ok(ValidationResult::Invalid(InvalidCandidate::BadReturn))).unwrap(); + } + ); + + // Ask subsystem to `Second` a candidate that already has a statement issued about. + // This should emit no actions from subsystem. + let second = CandidateBackingMessage::Second( + test_state.relay_parent, + candidate.to_plain(), + pov.clone(), + ); + + virtual_overseer.send(FromOverseer::Communication{ msg: second }).await; + + let pov_to_second = PoV { + block_data: BlockData(vec![3, 2, 1]), + }; + + let pov_hash = pov_to_second.hash(); + + let candidate_to_second = TestCandidateBuilder { + para_id: test_state.chain_ids[0], + relay_parent: test_state.relay_parent, + pov_hash, + erasure_root: make_erasure_root(&test_state, pov_to_second.clone()), + ..Default::default() + }.build(); + + let second = CandidateBackingMessage::Second( + test_state.relay_parent, + candidate_to_second.to_plain(), + pov_to_second.clone(), + ); + + // In order to trigger _some_ actions from subsystem ask it to second another + // candidate. The only reason to do so is to make sure that no actions were + // triggered on the prev step. + virtual_overseer.send(FromOverseer::Communication{ msg: second }).await; + + assert_matches!( + virtual_overseer.recv().await, + AllMessages::CandidateValidation( + CandidateValidationMessage::ValidateFromChainState( + _, + pov, + _, + ) + ) => { + assert_eq!(&*pov, &pov_to_second); + } + ); + virtual_overseer + }); +} + +// That that if the validation of the candidate has failed this does not stop +// the work of this subsystem and so it is not fatal to the node. +#[test] +fn backing_works_after_failed_validation() { + let test_state = TestState::default(); + test_harness(test_state.keystore.clone(), |mut virtual_overseer| async move { + test_startup(&mut virtual_overseer, &test_state).await; + + let pov = PoV { + block_data: BlockData(vec![42, 43, 44]), + }; + + let pov_hash = pov.hash(); + + let candidate = TestCandidateBuilder { + para_id: test_state.chain_ids[0], + relay_parent: test_state.relay_parent, + pov_hash, + erasure_root: make_erasure_root(&test_state, pov.clone()), + ..Default::default() + }.build(); + + let public2 = CryptoStore::sr25519_generate_new( + &*test_state.keystore, + ValidatorId::ID, Some(&test_state.validators[2].to_seed()) + ).await.expect("Insert key into keystore"); + let signed_a = SignedFullStatement::sign( + &test_state.keystore, + Statement::Seconded(candidate.clone()), + &test_state.signing_context, + ValidatorIndex(2), + &public2.into(), + ).await.ok().flatten().expect("should be signed"); + + // Send in a `Statement` with a candidate. + let statement = CandidateBackingMessage::Statement( + test_state.relay_parent, + signed_a.clone(), + ); + + virtual_overseer.send(FromOverseer::Communication{ msg: statement }).await; + + // Subsystem requests PoV and requests validation. + assert_matches!( + virtual_overseer.recv().await, + AllMessages::AvailabilityDistribution( + AvailabilityDistributionMessage::FetchPoV { + relay_parent, + tx, + .. + } + ) if relay_parent == test_state.relay_parent => { + tx.send(pov.clone()).unwrap(); + } + ); + + // Tell subsystem that this candidate is invalid. + assert_matches!( + virtual_overseer.recv().await, + AllMessages::CandidateValidation( + CandidateValidationMessage::ValidateFromChainState( + c, + pov, + tx, + ) + ) if pov == pov && &c == candidate.descriptor() => { + tx.send(Err(ValidationFailed("Internal test error".into()))).unwrap(); + } + ); + + // Try to get a set of backable candidates to trigger _some_ action in the subsystem + // and check that it is still alive. + let (tx, rx) = oneshot::channel(); + let msg = CandidateBackingMessage::GetBackedCandidates( + test_state.relay_parent, + vec![candidate.hash()], + tx, + ); + + virtual_overseer.send(FromOverseer::Communication{ msg }).await; + assert_eq!(rx.await.unwrap().len(), 0); + virtual_overseer + }); +} + +// Test that a `CandidateBackingMessage::Second` issues validation work +// and in case validation is successful issues a `StatementDistributionMessage`. +#[test] +fn backing_doesnt_second_wrong_collator() { + let mut test_state = TestState::default(); + test_state.availability_cores[0] = CoreState::Scheduled(ScheduledCore { + para_id: ParaId::from(1), + collator: Some(Sr25519Keyring::Bob.public().into()), + }); + + test_harness(test_state.keystore.clone(), |mut virtual_overseer| async move { + test_startup(&mut virtual_overseer, &test_state).await; + + let pov = PoV { + block_data: BlockData(vec![42, 43, 44]), + }; + + let expected_head_data = test_state.head_data.get(&test_state.chain_ids[0]).unwrap(); + + let pov_hash = pov.hash(); + let candidate = TestCandidateBuilder { + para_id: test_state.chain_ids[0], + relay_parent: test_state.relay_parent, + pov_hash, + head_data: expected_head_data.clone(), + erasure_root: make_erasure_root(&test_state, pov.clone()), + ..Default::default() + }.build(); + + let second = CandidateBackingMessage::Second( + test_state.relay_parent, + candidate.to_plain(), + pov.clone(), + ); + + virtual_overseer.send(FromOverseer::Communication{ msg: second }).await; + + assert_matches!( + virtual_overseer.recv().await, + AllMessages::CollatorProtocol( + CollatorProtocolMessage::Invalid(parent, c) + ) if parent == test_state.relay_parent && c == candidate.to_plain() => { + } + ); + + virtual_overseer.send(FromOverseer::Signal( + OverseerSignal::ActiveLeaves(ActiveLeavesUpdate::stop_work(test_state.relay_parent))) + ).await; + virtual_overseer + }); +} + +#[test] +fn validation_work_ignores_wrong_collator() { + let mut test_state = TestState::default(); + test_state.availability_cores[0] = CoreState::Scheduled(ScheduledCore { + para_id: ParaId::from(1), + collator: Some(Sr25519Keyring::Bob.public().into()), + }); + + test_harness(test_state.keystore.clone(), |mut virtual_overseer| async move { + test_startup(&mut virtual_overseer, &test_state).await; + + let pov = PoV { + block_data: BlockData(vec![1, 2, 3]), + }; + + let pov_hash = pov.hash(); + + let expected_head_data = test_state.head_data.get(&test_state.chain_ids[0]).unwrap(); + + let candidate_a = TestCandidateBuilder { + para_id: test_state.chain_ids[0], + relay_parent: test_state.relay_parent, + pov_hash, + head_data: expected_head_data.clone(), + erasure_root: make_erasure_root(&test_state, pov.clone()), + ..Default::default() + }.build(); + + let public2 = CryptoStore::sr25519_generate_new( + &*test_state.keystore, + ValidatorId::ID, Some(&test_state.validators[2].to_seed()) + ).await.expect("Insert key into keystore"); + let seconding = SignedFullStatement::sign( + &test_state.keystore, + Statement::Seconded(candidate_a.clone()), + &test_state.signing_context, + ValidatorIndex(2), + &public2.into(), + ).await.ok().flatten().expect("should be signed"); + + let statement = CandidateBackingMessage::Statement( + test_state.relay_parent, + seconding.clone(), + ); + + virtual_overseer.send(FromOverseer::Communication{ msg: statement }).await; + + // The statement will be ignored because it has the wrong collator. + virtual_overseer.send(FromOverseer::Signal( + OverseerSignal::ActiveLeaves(ActiveLeavesUpdate::stop_work(test_state.relay_parent))) + ).await; + virtual_overseer + }); +} + +#[test] +fn candidate_backing_reorders_votes() { + use sp_core::Encode; + use std::convert::TryFrom; + + let para_id = ParaId::from(10); + let validators = vec![ + Sr25519Keyring::Alice, + Sr25519Keyring::Bob, + Sr25519Keyring::Charlie, + Sr25519Keyring::Dave, + Sr25519Keyring::Ferdie, + Sr25519Keyring::One, + ]; + + let validator_public = validator_pubkeys(&validators); + let validator_groups = { + let mut validator_groups = HashMap::new(); + validator_groups.insert(para_id, vec![0, 1, 2, 3, 4, 5].into_iter().map(ValidatorIndex).collect()); + validator_groups + }; + + let table_context = TableContext { + validator: None, + groups: validator_groups, + validators: validator_public.clone(), + }; + + let fake_attestation = |idx: u32| { + let candidate: CommittedCandidateReceipt = Default::default(); + let hash = candidate.hash(); + let mut data = vec![0; 64]; + data[0..32].copy_from_slice(hash.0.as_bytes()); + data[32..36].copy_from_slice(idx.encode().as_slice()); + + let sig = ValidatorSignature::try_from(data).unwrap(); + statement_table::generic::ValidityAttestation::Implicit(sig) + }; + + let attested = TableAttestedCandidate { + candidate: Default::default(), + validity_votes: vec![ + (ValidatorIndex(5), fake_attestation(5)), + (ValidatorIndex(3), fake_attestation(3)), + (ValidatorIndex(1), fake_attestation(1)), + ], + group_id: para_id, + }; + + let backed = table_attested_to_backed(attested, &table_context).unwrap(); + + let expected_bitvec = { + let mut validator_indices = BitVec::::with_capacity(6); + validator_indices.resize(6, false); + + validator_indices.set(1, true); + validator_indices.set(3, true); + validator_indices.set(5, true); + + validator_indices + }; + + // Should be in bitfield order, which is opposite to the order provided to the function. + let expected_attestations = vec![ + fake_attestation(1).into(), + fake_attestation(3).into(), + fake_attestation(5).into(), + ]; + + assert_eq!(backed.validator_indices, expected_bitvec); + assert_eq!(backed.validity_votes, expected_attestations); +} + +// Test whether we retry on failed PoV fetching. +#[test] +fn retry_works() { + // sp_tracing::try_init_simple(); + let test_state = TestState::default(); + test_harness(test_state.keystore.clone(), |mut virtual_overseer| async move { + test_startup(&mut virtual_overseer, &test_state).await; + + let pov = PoV { + block_data: BlockData(vec![42, 43, 44]), + }; + + let pov_hash = pov.hash(); + + let candidate = TestCandidateBuilder { + para_id: test_state.chain_ids[0], + relay_parent: test_state.relay_parent, + pov_hash, + erasure_root: make_erasure_root(&test_state, pov.clone()), + ..Default::default() + }.build(); + + let public2 = CryptoStore::sr25519_generate_new( + &*test_state.keystore, + ValidatorId::ID, Some(&test_state.validators[2].to_seed()) + ).await.expect("Insert key into keystore"); + let public3 = CryptoStore::sr25519_generate_new( + &*test_state.keystore, + ValidatorId::ID, + Some(&test_state.validators[3].to_seed()), + ).await.expect("Insert key into keystore"); + let public5 = CryptoStore::sr25519_generate_new( + &*test_state.keystore, + ValidatorId::ID, + Some(&test_state.validators[5].to_seed()), + ).await.expect("Insert key into keystore"); + let signed_a = SignedFullStatement::sign( + &test_state.keystore, + Statement::Seconded(candidate.clone()), + &test_state.signing_context, + ValidatorIndex(2), + &public2.into(), + ).await.ok().flatten().expect("should be signed"); + let signed_b = SignedFullStatement::sign( + &test_state.keystore, + Statement::Valid(candidate.hash()), + &test_state.signing_context, + ValidatorIndex(3), + &public3.into(), + ).await.ok().flatten().expect("should be signed"); + let signed_c = SignedFullStatement::sign( + &test_state.keystore, + Statement::Valid(candidate.hash()), + &test_state.signing_context, + ValidatorIndex(5), + &public5.into(), + ).await.ok().flatten().expect("should be signed"); + + // Send in a `Statement` with a candidate. + let statement = CandidateBackingMessage::Statement( + test_state.relay_parent, + signed_a.clone(), + ); + virtual_overseer.send(FromOverseer::Communication{ msg: statement }).await; + + // Subsystem requests PoV and requests validation. + // We cancel - should mean retry on next backing statement. + assert_matches!( + virtual_overseer.recv().await, + AllMessages::AvailabilityDistribution( + AvailabilityDistributionMessage::FetchPoV { + relay_parent, + tx, + .. + } + ) if relay_parent == test_state.relay_parent => { + std::mem::drop(tx); + } + ); + + let statement = CandidateBackingMessage::Statement( + test_state.relay_parent, + signed_b.clone(), + ); + virtual_overseer.send(FromOverseer::Communication{ msg: statement }).await; + + let statement = CandidateBackingMessage::Statement( + test_state.relay_parent, + signed_c.clone(), + ); + virtual_overseer.send(FromOverseer::Communication{ msg: statement }).await; + + // Not deterministic which message comes first: + for _ in 0u32..2 { + match virtual_overseer.recv().await { + AllMessages::Provisioner( + ProvisionerMessage::ProvisionableData( + _, + ProvisionableData::BackedCandidate(CandidateReceipt { + descriptor, + .. + }) + ) + ) => { + assert_eq!(descriptor, candidate.descriptor); + } + // Subsystem requests PoV and requests validation. + // We cancel once more: + AllMessages::AvailabilityDistribution( + AvailabilityDistributionMessage::FetchPoV { + relay_parent, + tx, + .. + } + ) if relay_parent == test_state.relay_parent => { + std::mem::drop(tx); + } + msg => { + assert!(false, "Unexpected message: {:?}", msg); + } + } + } + + // Subsystem requests PoV and requests validation. + // Now we pass. + assert_matches!( + virtual_overseer.recv().await, + AllMessages::AvailabilityDistribution( + AvailabilityDistributionMessage::FetchPoV { + relay_parent, + tx, + .. + } + ) if relay_parent == test_state.relay_parent => { + tx.send(pov.clone()).unwrap(); + } + ); + + assert_matches!( + virtual_overseer.recv().await, + AllMessages::CandidateValidation( + CandidateValidationMessage::ValidateFromChainState( + c, + pov, + _tx, + ) + ) if pov == pov && &c == candidate.descriptor() + ); + virtual_overseer + }); +} + +#[test] +fn observes_backing_even_if_not_validator() { + let test_state = TestState::default(); + let empty_keystore = Arc::new(sc_keystore::LocalKeystore::in_memory()); + test_harness(empty_keystore, |mut virtual_overseer| async move { + test_startup(&mut virtual_overseer, &test_state).await; + + let pov = PoV { + block_data: BlockData(vec![1, 2, 3]), + }; + + let pov_hash = pov.hash(); + + let expected_head_data = test_state.head_data.get(&test_state.chain_ids[0]).unwrap(); + + let candidate_a = TestCandidateBuilder { + para_id: test_state.chain_ids[0], + relay_parent: test_state.relay_parent, + pov_hash, + head_data: expected_head_data.clone(), + erasure_root: make_erasure_root(&test_state, pov.clone()), + ..Default::default() + }.build(); + + let candidate_a_hash = candidate_a.hash(); + let public0 = CryptoStore::sr25519_generate_new( + &*test_state.keystore, + ValidatorId::ID, + Some(&test_state.validators[0].to_seed()), + ).await.expect("Insert key into keystore"); + let public1 = CryptoStore::sr25519_generate_new( + &*test_state.keystore, + ValidatorId::ID, + Some(&test_state.validators[5].to_seed()), + ).await.expect("Insert key into keystore"); + let public2 = CryptoStore::sr25519_generate_new( + &*test_state.keystore, + ValidatorId::ID, + Some(&test_state.validators[2].to_seed()), + ).await.expect("Insert key into keystore"); + + // Produce a 3-of-5 quorum on the candidate. + + let signed_a = SignedFullStatement::sign( + &test_state.keystore, + Statement::Seconded(candidate_a.clone()), + &test_state.signing_context, + ValidatorIndex(0), + &public0.into(), + ).await.ok().flatten().expect("should be signed"); + + let signed_b = SignedFullStatement::sign( + &test_state.keystore, + Statement::Valid(candidate_a_hash), + &test_state.signing_context, + ValidatorIndex(5), + &public1.into(), + ).await.ok().flatten().expect("should be signed"); + + let signed_c = SignedFullStatement::sign( + &test_state.keystore, + Statement::Valid(candidate_a_hash), + &test_state.signing_context, + ValidatorIndex(2), + &public2.into(), + ).await.ok().flatten().expect("should be signed"); + + let statement = CandidateBackingMessage::Statement(test_state.relay_parent, signed_a.clone()); + + virtual_overseer.send(FromOverseer::Communication{ msg: statement }).await; + + let statement = CandidateBackingMessage::Statement( + test_state.relay_parent, + signed_b.clone(), + ); + + virtual_overseer.send(FromOverseer::Communication{ msg: statement }).await; + + let statement = CandidateBackingMessage::Statement( + test_state.relay_parent, + signed_c.clone(), + ); + + virtual_overseer.send(FromOverseer::Communication{ msg: statement }).await; + + assert_matches!( + virtual_overseer.recv().await, + AllMessages::Provisioner( + ProvisionerMessage::ProvisionableData( + _, + ProvisionableData::BackedCandidate(candidate_receipt) + ) + ) => { + assert_eq!(candidate_receipt, candidate_a.to_plain()); + } + ); + + virtual_overseer.send(FromOverseer::Signal( + OverseerSignal::ActiveLeaves(ActiveLeavesUpdate::stop_work(test_state.relay_parent))) + ).await; + virtual_overseer + }); +} diff --git a/node/core/candidate-validation/src/lib.rs b/node/core/candidate-validation/src/lib.rs index b05ff9cabc32..245a37d3e5a9 100644 --- a/node/core/candidate-validation/src/lib.rs +++ b/node/core/candidate-validation/src/lib.rs @@ -1,4 +1,4 @@ -// Copyright 2020 Parity Technologies (UK) Ltd. +// Copyright 2020-2021 Parity Technologies (UK) Ltd. // This file is part of Polkadot. // Polkadot is free software: you can redistribute it and/or modify @@ -53,6 +53,9 @@ use std::path::PathBuf; use async_trait::async_trait; +#[cfg(test)] +mod tests; + const LOG_TARGET: &'static str = "parachain::candidate-validation"; /// Configuration for the candidate validation subsystem @@ -584,624 +587,3 @@ impl metrics::Metrics for Metrics { Ok(Metrics(Some(metrics))) } } - -#[cfg(test)] -mod tests { - use super::*; - use polkadot_node_subsystem_test_helpers as test_helpers; - use polkadot_primitives::v1::{HeadData, UpwardMessage}; - use sp_core::testing::TaskExecutor; - use futures::executor; - use assert_matches::assert_matches; - use sp_keyring::Sr25519Keyring; - - fn collator_sign(descriptor: &mut CandidateDescriptor, collator: Sr25519Keyring) { - descriptor.collator = collator.public().into(); - let payload = polkadot_primitives::v1::collator_signature_payload( - &descriptor.relay_parent, - &descriptor.para_id, - &descriptor.persisted_validation_data_hash, - &descriptor.pov_hash, - &descriptor.validation_code_hash, - ); - - descriptor.signature = collator.sign(&payload[..]).into(); - assert!(descriptor.check_collator_signature().is_ok()); - } - - #[test] - fn correctly_checks_included_assumption() { - let validation_data: PersistedValidationData = Default::default(); - let validation_code: ValidationCode = vec![1, 2, 3].into(); - - let persisted_validation_data_hash = validation_data.hash(); - let relay_parent = [2; 32].into(); - let para_id = 5.into(); - - let mut candidate = CandidateDescriptor::default(); - candidate.relay_parent = relay_parent; - candidate.persisted_validation_data_hash = persisted_validation_data_hash; - candidate.para_id = para_id; - - let pool = TaskExecutor::new(); - let (mut ctx, mut ctx_handle) = test_helpers::make_subsystem_context(pool.clone()); - - let (check_fut, check_result) = check_assumption_validation_data( - &mut ctx, - &candidate, - OccupiedCoreAssumption::Included, - ).remote_handle(); - - let test_fut = async move { - assert_matches!( - ctx_handle.recv().await, - AllMessages::RuntimeApi(RuntimeApiMessage::Request( - rp, - RuntimeApiRequest::PersistedValidationData( - p, - OccupiedCoreAssumption::Included, - tx - ), - )) => { - assert_eq!(rp, relay_parent); - assert_eq!(p, para_id); - - let _ = tx.send(Ok(Some(validation_data.clone()))); - } - ); - - assert_matches!( - ctx_handle.recv().await, - AllMessages::RuntimeApi(RuntimeApiMessage::Request( - rp, - RuntimeApiRequest::ValidationCode(p, OccupiedCoreAssumption::Included, tx) - )) => { - assert_eq!(rp, relay_parent); - assert_eq!(p, para_id); - - let _ = tx.send(Ok(Some(validation_code.clone()))); - } - ); - - assert_matches!(check_result.await.unwrap(), AssumptionCheckOutcome::Matches(o, v) => { - assert_eq!(o, validation_data); - assert_eq!(v, validation_code); - }); - }; - - let test_fut = future::join(test_fut, check_fut); - executor::block_on(test_fut); - } - - #[test] - fn correctly_checks_timed_out_assumption() { - let validation_data: PersistedValidationData = Default::default(); - let validation_code: ValidationCode = vec![1, 2, 3].into(); - - let persisted_validation_data_hash = validation_data.hash(); - let relay_parent = [2; 32].into(); - let para_id = 5.into(); - - let mut candidate = CandidateDescriptor::default(); - candidate.relay_parent = relay_parent; - candidate.persisted_validation_data_hash = persisted_validation_data_hash; - candidate.para_id = para_id; - - let pool = TaskExecutor::new(); - let (mut ctx, mut ctx_handle) = test_helpers::make_subsystem_context(pool.clone()); - - let (check_fut, check_result) = check_assumption_validation_data( - &mut ctx, - &candidate, - OccupiedCoreAssumption::TimedOut, - ).remote_handle(); - - let test_fut = async move { - assert_matches!( - ctx_handle.recv().await, - AllMessages::RuntimeApi(RuntimeApiMessage::Request( - rp, - RuntimeApiRequest::PersistedValidationData( - p, - OccupiedCoreAssumption::TimedOut, - tx - ), - )) => { - assert_eq!(rp, relay_parent); - assert_eq!(p, para_id); - - let _ = tx.send(Ok(Some(validation_data.clone()))); - } - ); - - assert_matches!( - ctx_handle.recv().await, - AllMessages::RuntimeApi(RuntimeApiMessage::Request( - rp, - RuntimeApiRequest::ValidationCode(p, OccupiedCoreAssumption::TimedOut, tx) - )) => { - assert_eq!(rp, relay_parent); - assert_eq!(p, para_id); - - let _ = tx.send(Ok(Some(validation_code.clone()))); - } - ); - - assert_matches!(check_result.await.unwrap(), AssumptionCheckOutcome::Matches(o, v) => { - assert_eq!(o, validation_data); - assert_eq!(v, validation_code); - }); - }; - - let test_fut = future::join(test_fut, check_fut); - executor::block_on(test_fut); - } - - #[test] - fn check_is_bad_request_if_no_validation_data() { - let validation_data: PersistedValidationData = Default::default(); - let persisted_validation_data_hash = validation_data.hash(); - let relay_parent = [2; 32].into(); - let para_id = 5.into(); - - let mut candidate = CandidateDescriptor::default(); - candidate.relay_parent = relay_parent; - candidate.persisted_validation_data_hash = persisted_validation_data_hash; - candidate.para_id = para_id; - - let pool = TaskExecutor::new(); - let (mut ctx, mut ctx_handle) = test_helpers::make_subsystem_context(pool.clone()); - - let (check_fut, check_result) = check_assumption_validation_data( - &mut ctx, - &candidate, - OccupiedCoreAssumption::Included, - ).remote_handle(); - - let test_fut = async move { - assert_matches!( - ctx_handle.recv().await, - AllMessages::RuntimeApi(RuntimeApiMessage::Request( - rp, - RuntimeApiRequest::PersistedValidationData( - p, - OccupiedCoreAssumption::Included, - tx - ), - )) => { - assert_eq!(rp, relay_parent); - assert_eq!(p, para_id); - - let _ = tx.send(Ok(None)); - } - ); - - assert_matches!(check_result.await.unwrap(), AssumptionCheckOutcome::BadRequest); - }; - - let test_fut = future::join(test_fut, check_fut); - executor::block_on(test_fut); - } - - #[test] - fn check_is_bad_request_if_no_validation_code() { - let validation_data: PersistedValidationData = Default::default(); - let persisted_validation_data_hash = validation_data.hash(); - let relay_parent = [2; 32].into(); - let para_id = 5.into(); - - let mut candidate = CandidateDescriptor::default(); - candidate.relay_parent = relay_parent; - candidate.persisted_validation_data_hash = persisted_validation_data_hash; - candidate.para_id = para_id; - - let pool = TaskExecutor::new(); - let (mut ctx, mut ctx_handle) = test_helpers::make_subsystem_context(pool.clone()); - - let (check_fut, check_result) = check_assumption_validation_data( - &mut ctx, - &candidate, - OccupiedCoreAssumption::TimedOut, - ).remote_handle(); - - let test_fut = async move { - assert_matches!( - ctx_handle.recv().await, - AllMessages::RuntimeApi(RuntimeApiMessage::Request( - rp, - RuntimeApiRequest::PersistedValidationData( - p, - OccupiedCoreAssumption::TimedOut, - tx - ), - )) => { - assert_eq!(rp, relay_parent); - assert_eq!(p, para_id); - - let _ = tx.send(Ok(Some(validation_data.clone()))); - } - ); - - assert_matches!( - ctx_handle.recv().await, - AllMessages::RuntimeApi(RuntimeApiMessage::Request( - rp, - RuntimeApiRequest::ValidationCode(p, OccupiedCoreAssumption::TimedOut, tx) - )) => { - assert_eq!(rp, relay_parent); - assert_eq!(p, para_id); - - let _ = tx.send(Ok(None)); - } - ); - - assert_matches!(check_result.await.unwrap(), AssumptionCheckOutcome::BadRequest); - }; - - let test_fut = future::join(test_fut, check_fut); - executor::block_on(test_fut); - } - - #[test] - fn check_does_not_match() { - let validation_data: PersistedValidationData = Default::default(); - let relay_parent = [2; 32].into(); - let para_id = 5.into(); - - let mut candidate = CandidateDescriptor::default(); - candidate.relay_parent = relay_parent; - candidate.persisted_validation_data_hash = [3; 32].into(); - candidate.para_id = para_id; - - let pool = TaskExecutor::new(); - let (mut ctx, mut ctx_handle) = test_helpers::make_subsystem_context(pool.clone()); - - let (check_fut, check_result) = check_assumption_validation_data( - &mut ctx, - &candidate, - OccupiedCoreAssumption::Included, - ).remote_handle(); - - let test_fut = async move { - assert_matches!( - ctx_handle.recv().await, - AllMessages::RuntimeApi(RuntimeApiMessage::Request( - rp, - RuntimeApiRequest::PersistedValidationData( - p, - OccupiedCoreAssumption::Included, - tx - ), - )) => { - assert_eq!(rp, relay_parent); - assert_eq!(p, para_id); - - let _ = tx.send(Ok(Some(validation_data.clone()))); - } - ); - - assert_matches!(check_result.await.unwrap(), AssumptionCheckOutcome::DoesNotMatch); - }; - - let test_fut = future::join(test_fut, check_fut); - executor::block_on(test_fut); - } - - struct MockValidatorBackend { - result: Result, - } - - impl MockValidatorBackend { - fn with_hardcoded_result(result: Result) -> Self { - Self { - result, - } - } - } - - #[async_trait] - impl ValidationBackend for MockValidatorBackend { - async fn validate_candidate( - &mut self, - _raw_validation_code: Vec, - _params: ValidationParams - ) -> Result { - self.result.clone() - } - } - - #[test] - fn candidate_validation_ok_is_ok() { - let validation_data = PersistedValidationData { max_pov_size: 1024, ..Default::default() }; - - let pov = PoV { block_data: BlockData(vec![1; 32]) }; - let head_data = HeadData(vec![1, 1, 1]); - let validation_code = ValidationCode(vec![2; 16]); - - let mut descriptor = CandidateDescriptor::default(); - descriptor.pov_hash = pov.hash(); - descriptor.para_head = head_data.hash(); - descriptor.validation_code_hash = validation_code.hash(); - collator_sign(&mut descriptor, Sr25519Keyring::Alice); - - let check = perform_basic_checks( - &descriptor, - validation_data.max_pov_size, - &pov, - &validation_code, - ); - assert!(check.is_ok()); - - let validation_result = WasmValidationResult { - head_data, - new_validation_code: Some(vec![2, 2, 2].into()), - upward_messages: Vec::new(), - horizontal_messages: Vec::new(), - processed_downward_messages: 0, - hrmp_watermark: 0, - }; - - let v = executor::block_on(validate_candidate_exhaustive( - MockValidatorBackend::with_hardcoded_result(Ok(validation_result)), - validation_data.clone(), - validation_code, - descriptor, - Arc::new(pov), - &Default::default(), - )) - .unwrap() - .unwrap(); - - assert_matches!(v, ValidationResult::Valid(outputs, used_validation_data) => { - assert_eq!(outputs.head_data, HeadData(vec![1, 1, 1])); - assert_eq!(outputs.upward_messages, Vec::::new()); - assert_eq!(outputs.horizontal_messages, Vec::new()); - assert_eq!(outputs.new_validation_code, Some(vec![2, 2, 2].into())); - assert_eq!(outputs.hrmp_watermark, 0); - assert_eq!(used_validation_data, validation_data); - }); - } - - #[test] - fn candidate_validation_bad_return_is_invalid() { - let validation_data = PersistedValidationData { max_pov_size: 1024, ..Default::default() }; - - let pov = PoV { block_data: BlockData(vec![1; 32]) }; - let validation_code = ValidationCode(vec![2; 16]); - - let mut descriptor = CandidateDescriptor::default(); - descriptor.pov_hash = pov.hash(); - descriptor.validation_code_hash = validation_code.hash(); - collator_sign(&mut descriptor, Sr25519Keyring::Alice); - - let check = perform_basic_checks( - &descriptor, - validation_data.max_pov_size, - &pov, - &validation_code, - ); - assert!(check.is_ok()); - - let v = executor::block_on(validate_candidate_exhaustive( - MockValidatorBackend::with_hardcoded_result( - Err(ValidationError::InvalidCandidate(WasmInvalidCandidate::AmbigiousWorkerDeath)) - ), - validation_data, - validation_code, - descriptor, - Arc::new(pov), - &Default::default(), - )) - .unwrap() - .unwrap(); - - assert_matches!(v, ValidationResult::Invalid(InvalidCandidate::ExecutionError(_))); - } - - #[test] - fn candidate_validation_timeout_is_internal_error() { - let validation_data = PersistedValidationData { max_pov_size: 1024, ..Default::default() }; - - let pov = PoV { block_data: BlockData(vec![1; 32]) }; - let validation_code = ValidationCode(vec![2; 16]); - - let mut descriptor = CandidateDescriptor::default(); - descriptor.pov_hash = pov.hash(); - descriptor.validation_code_hash = validation_code.hash(); - collator_sign(&mut descriptor, Sr25519Keyring::Alice); - - let check = perform_basic_checks( - &descriptor, - validation_data.max_pov_size, - &pov, - &validation_code, - ); - assert!(check.is_ok()); - - let v = executor::block_on(validate_candidate_exhaustive( - MockValidatorBackend::with_hardcoded_result( - Err(ValidationError::InvalidCandidate(WasmInvalidCandidate::HardTimeout)), - ), - validation_data, - validation_code, - descriptor, - Arc::new(pov), - &Default::default(), - )) - .unwrap(); - - assert_matches!(v, Ok(ValidationResult::Invalid(InvalidCandidate::Timeout))); - } - - #[test] - fn candidate_validation_code_mismatch_is_invalid() { - let validation_data = PersistedValidationData { max_pov_size: 1024, ..Default::default() }; - - let pov = PoV { block_data: BlockData(vec![1; 32]) }; - let validation_code = ValidationCode(vec![2; 16]); - - let mut descriptor = CandidateDescriptor::default(); - descriptor.pov_hash = pov.hash(); - descriptor.validation_code_hash = ValidationCode(vec![1; 16]).hash(); - collator_sign(&mut descriptor, Sr25519Keyring::Alice); - - let check = perform_basic_checks( - &descriptor, - validation_data.max_pov_size, - &pov, - &validation_code, - ); - assert_matches!(check, Err(InvalidCandidate::CodeHashMismatch)); - - let v = executor::block_on(validate_candidate_exhaustive( - MockValidatorBackend::with_hardcoded_result( - Err(ValidationError::InvalidCandidate(WasmInvalidCandidate::HardTimeout)), - ), - validation_data, - validation_code, - descriptor, - Arc::new(pov), - &Default::default(), - )) - .unwrap() - .unwrap(); - - assert_matches!(v, ValidationResult::Invalid(InvalidCandidate::CodeHashMismatch)); - } - - #[test] - fn compressed_code_works() { - let validation_data = PersistedValidationData { max_pov_size: 1024, ..Default::default() }; - let pov = PoV { block_data: BlockData(vec![1; 32]) }; - let head_data = HeadData(vec![1, 1, 1]); - - let raw_code = vec![2u8; 16]; - let validation_code = sp_maybe_compressed_blob::compress( - &raw_code, - VALIDATION_CODE_BOMB_LIMIT, - ) - .map(ValidationCode) - .unwrap(); - - let mut descriptor = CandidateDescriptor::default(); - descriptor.pov_hash = pov.hash(); - descriptor.para_head = head_data.hash(); - descriptor.validation_code_hash = validation_code.hash(); - collator_sign(&mut descriptor, Sr25519Keyring::Alice); - - let validation_result = WasmValidationResult { - head_data, - new_validation_code: None, - upward_messages: Vec::new(), - horizontal_messages: Vec::new(), - processed_downward_messages: 0, - hrmp_watermark: 0, - }; - - let v = executor::block_on(validate_candidate_exhaustive( - MockValidatorBackend::with_hardcoded_result(Ok(validation_result)), - validation_data, - validation_code, - descriptor, - Arc::new(pov), - &Default::default(), - )) - .unwrap(); - - assert_matches!(v, Ok(ValidationResult::Valid(_, _))); - } - - #[test] - fn code_decompression_failure_is_invalid() { - let validation_data = PersistedValidationData { max_pov_size: 1024, ..Default::default() }; - let pov = PoV { block_data: BlockData(vec![1; 32]) }; - let head_data = HeadData(vec![1, 1, 1]); - - let raw_code = vec![2u8; VALIDATION_CODE_BOMB_LIMIT + 1]; - let validation_code = sp_maybe_compressed_blob::compress( - &raw_code, - VALIDATION_CODE_BOMB_LIMIT + 1, - ) - .map(ValidationCode) - .unwrap(); - - let mut descriptor = CandidateDescriptor::default(); - descriptor.pov_hash = pov.hash(); - descriptor.para_head = head_data.hash(); - descriptor.validation_code_hash = validation_code.hash(); - collator_sign(&mut descriptor, Sr25519Keyring::Alice); - - let validation_result = WasmValidationResult { - head_data, - new_validation_code: None, - upward_messages: Vec::new(), - horizontal_messages: Vec::new(), - processed_downward_messages: 0, - hrmp_watermark: 0, - }; - - let v = executor::block_on(validate_candidate_exhaustive( - MockValidatorBackend::with_hardcoded_result(Ok(validation_result)), - validation_data, - validation_code, - descriptor, - Arc::new(pov), - &Default::default(), - )) - .unwrap(); - - assert_matches!( - v, - Ok(ValidationResult::Invalid(InvalidCandidate::CodeDecompressionFailure)) - ); - } - - #[test] - fn pov_decompression_failure_is_invalid() { - let validation_data = PersistedValidationData { - max_pov_size: POV_BOMB_LIMIT as u32, - ..Default::default() - }; - let head_data = HeadData(vec![1, 1, 1]); - - let raw_block_data = vec![2u8; POV_BOMB_LIMIT + 1]; - let pov = sp_maybe_compressed_blob::compress( - &raw_block_data, - POV_BOMB_LIMIT + 1, - ) - .map(|raw| PoV { block_data: BlockData(raw) }) - .unwrap(); - - let validation_code = ValidationCode(vec![2; 16]); - - let mut descriptor = CandidateDescriptor::default(); - descriptor.pov_hash = pov.hash(); - descriptor.para_head = head_data.hash(); - descriptor.validation_code_hash = validation_code.hash(); - collator_sign(&mut descriptor, Sr25519Keyring::Alice); - - let validation_result = WasmValidationResult { - head_data, - new_validation_code: None, - upward_messages: Vec::new(), - horizontal_messages: Vec::new(), - processed_downward_messages: 0, - hrmp_watermark: 0, - }; - - let v = executor::block_on(validate_candidate_exhaustive( - MockValidatorBackend::with_hardcoded_result(Ok(validation_result)), - validation_data, - validation_code, - descriptor, - Arc::new(pov), - &Default::default(), - )) - .unwrap(); - - assert_matches!( - v, - Ok(ValidationResult::Invalid(InvalidCandidate::PoVDecompressionFailure)) - ); - } -} diff --git a/node/core/candidate-validation/src/tests.rs b/node/core/candidate-validation/src/tests.rs new file mode 100644 index 000000000000..26ccdbb7cc9e --- /dev/null +++ b/node/core/candidate-validation/src/tests.rs @@ -0,0 +1,633 @@ +// Copyright 2020-2021 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 polkadot_node_subsystem_test_helpers as test_helpers; +use polkadot_primitives::v1::{HeadData, UpwardMessage}; +use sp_core::testing::TaskExecutor; +use futures::executor; +use assert_matches::assert_matches; +use sp_keyring::Sr25519Keyring; + +fn collator_sign(descriptor: &mut CandidateDescriptor, collator: Sr25519Keyring) { + descriptor.collator = collator.public().into(); + let payload = polkadot_primitives::v1::collator_signature_payload( + &descriptor.relay_parent, + &descriptor.para_id, + &descriptor.persisted_validation_data_hash, + &descriptor.pov_hash, + &descriptor.validation_code_hash, + ); + + descriptor.signature = collator.sign(&payload[..]).into(); + assert!(descriptor.check_collator_signature().is_ok()); +} + +#[test] +fn correctly_checks_included_assumption() { + let validation_data: PersistedValidationData = Default::default(); + let validation_code: ValidationCode = vec![1, 2, 3].into(); + + let persisted_validation_data_hash = validation_data.hash(); + let relay_parent = [2; 32].into(); + let para_id = 5.into(); + + let mut candidate = CandidateDescriptor::default(); + candidate.relay_parent = relay_parent; + candidate.persisted_validation_data_hash = persisted_validation_data_hash; + candidate.para_id = para_id; + + let pool = TaskExecutor::new(); + let (mut ctx, mut ctx_handle) = test_helpers::make_subsystem_context(pool.clone()); + + let (check_fut, check_result) = check_assumption_validation_data( + &mut ctx, + &candidate, + OccupiedCoreAssumption::Included, + ).remote_handle(); + + let test_fut = async move { + assert_matches!( + ctx_handle.recv().await, + AllMessages::RuntimeApi(RuntimeApiMessage::Request( + rp, + RuntimeApiRequest::PersistedValidationData( + p, + OccupiedCoreAssumption::Included, + tx + ), + )) => { + assert_eq!(rp, relay_parent); + assert_eq!(p, para_id); + + let _ = tx.send(Ok(Some(validation_data.clone()))); + } + ); + + assert_matches!( + ctx_handle.recv().await, + AllMessages::RuntimeApi(RuntimeApiMessage::Request( + rp, + RuntimeApiRequest::ValidationCode(p, OccupiedCoreAssumption::Included, tx) + )) => { + assert_eq!(rp, relay_parent); + assert_eq!(p, para_id); + + let _ = tx.send(Ok(Some(validation_code.clone()))); + } + ); + + assert_matches!(check_result.await.unwrap(), AssumptionCheckOutcome::Matches(o, v) => { + assert_eq!(o, validation_data); + assert_eq!(v, validation_code); + }); + }; + + let test_fut = future::join(test_fut, check_fut); + executor::block_on(test_fut); +} + +#[test] +fn correctly_checks_timed_out_assumption() { + let validation_data: PersistedValidationData = Default::default(); + let validation_code: ValidationCode = vec![1, 2, 3].into(); + + let persisted_validation_data_hash = validation_data.hash(); + let relay_parent = [2; 32].into(); + let para_id = 5.into(); + + let mut candidate = CandidateDescriptor::default(); + candidate.relay_parent = relay_parent; + candidate.persisted_validation_data_hash = persisted_validation_data_hash; + candidate.para_id = para_id; + + let pool = TaskExecutor::new(); + let (mut ctx, mut ctx_handle) = test_helpers::make_subsystem_context(pool.clone()); + + let (check_fut, check_result) = check_assumption_validation_data( + &mut ctx, + &candidate, + OccupiedCoreAssumption::TimedOut, + ).remote_handle(); + + let test_fut = async move { + assert_matches!( + ctx_handle.recv().await, + AllMessages::RuntimeApi(RuntimeApiMessage::Request( + rp, + RuntimeApiRequest::PersistedValidationData( + p, + OccupiedCoreAssumption::TimedOut, + tx + ), + )) => { + assert_eq!(rp, relay_parent); + assert_eq!(p, para_id); + + let _ = tx.send(Ok(Some(validation_data.clone()))); + } + ); + + assert_matches!( + ctx_handle.recv().await, + AllMessages::RuntimeApi(RuntimeApiMessage::Request( + rp, + RuntimeApiRequest::ValidationCode(p, OccupiedCoreAssumption::TimedOut, tx) + )) => { + assert_eq!(rp, relay_parent); + assert_eq!(p, para_id); + + let _ = tx.send(Ok(Some(validation_code.clone()))); + } + ); + + assert_matches!(check_result.await.unwrap(), AssumptionCheckOutcome::Matches(o, v) => { + assert_eq!(o, validation_data); + assert_eq!(v, validation_code); + }); + }; + + let test_fut = future::join(test_fut, check_fut); + executor::block_on(test_fut); +} + +#[test] +fn check_is_bad_request_if_no_validation_data() { + let validation_data: PersistedValidationData = Default::default(); + let persisted_validation_data_hash = validation_data.hash(); + let relay_parent = [2; 32].into(); + let para_id = 5.into(); + + let mut candidate = CandidateDescriptor::default(); + candidate.relay_parent = relay_parent; + candidate.persisted_validation_data_hash = persisted_validation_data_hash; + candidate.para_id = para_id; + + let pool = TaskExecutor::new(); + let (mut ctx, mut ctx_handle) = test_helpers::make_subsystem_context(pool.clone()); + + let (check_fut, check_result) = check_assumption_validation_data( + &mut ctx, + &candidate, + OccupiedCoreAssumption::Included, + ).remote_handle(); + + let test_fut = async move { + assert_matches!( + ctx_handle.recv().await, + AllMessages::RuntimeApi(RuntimeApiMessage::Request( + rp, + RuntimeApiRequest::PersistedValidationData( + p, + OccupiedCoreAssumption::Included, + tx + ), + )) => { + assert_eq!(rp, relay_parent); + assert_eq!(p, para_id); + + let _ = tx.send(Ok(None)); + } + ); + + assert_matches!(check_result.await.unwrap(), AssumptionCheckOutcome::BadRequest); + }; + + let test_fut = future::join(test_fut, check_fut); + executor::block_on(test_fut); +} + +#[test] +fn check_is_bad_request_if_no_validation_code() { + let validation_data: PersistedValidationData = Default::default(); + let persisted_validation_data_hash = validation_data.hash(); + let relay_parent = [2; 32].into(); + let para_id = 5.into(); + + let mut candidate = CandidateDescriptor::default(); + candidate.relay_parent = relay_parent; + candidate.persisted_validation_data_hash = persisted_validation_data_hash; + candidate.para_id = para_id; + + let pool = TaskExecutor::new(); + let (mut ctx, mut ctx_handle) = test_helpers::make_subsystem_context(pool.clone()); + + let (check_fut, check_result) = check_assumption_validation_data( + &mut ctx, + &candidate, + OccupiedCoreAssumption::TimedOut, + ).remote_handle(); + + let test_fut = async move { + assert_matches!( + ctx_handle.recv().await, + AllMessages::RuntimeApi(RuntimeApiMessage::Request( + rp, + RuntimeApiRequest::PersistedValidationData( + p, + OccupiedCoreAssumption::TimedOut, + tx + ), + )) => { + assert_eq!(rp, relay_parent); + assert_eq!(p, para_id); + + let _ = tx.send(Ok(Some(validation_data.clone()))); + } + ); + + assert_matches!( + ctx_handle.recv().await, + AllMessages::RuntimeApi(RuntimeApiMessage::Request( + rp, + RuntimeApiRequest::ValidationCode(p, OccupiedCoreAssumption::TimedOut, tx) + )) => { + assert_eq!(rp, relay_parent); + assert_eq!(p, para_id); + + let _ = tx.send(Ok(None)); + } + ); + + assert_matches!(check_result.await.unwrap(), AssumptionCheckOutcome::BadRequest); + }; + + let test_fut = future::join(test_fut, check_fut); + executor::block_on(test_fut); +} + +#[test] +fn check_does_not_match() { + let validation_data: PersistedValidationData = Default::default(); + let relay_parent = [2; 32].into(); + let para_id = 5.into(); + + let mut candidate = CandidateDescriptor::default(); + candidate.relay_parent = relay_parent; + candidate.persisted_validation_data_hash = [3; 32].into(); + candidate.para_id = para_id; + + let pool = TaskExecutor::new(); + let (mut ctx, mut ctx_handle) = test_helpers::make_subsystem_context(pool.clone()); + + let (check_fut, check_result) = check_assumption_validation_data( + &mut ctx, + &candidate, + OccupiedCoreAssumption::Included, + ).remote_handle(); + + let test_fut = async move { + assert_matches!( + ctx_handle.recv().await, + AllMessages::RuntimeApi(RuntimeApiMessage::Request( + rp, + RuntimeApiRequest::PersistedValidationData( + p, + OccupiedCoreAssumption::Included, + tx + ), + )) => { + assert_eq!(rp, relay_parent); + assert_eq!(p, para_id); + + let _ = tx.send(Ok(Some(validation_data.clone()))); + } + ); + + assert_matches!(check_result.await.unwrap(), AssumptionCheckOutcome::DoesNotMatch); + }; + + let test_fut = future::join(test_fut, check_fut); + executor::block_on(test_fut); +} + +struct MockValidatorBackend { + result: Result, +} + +impl MockValidatorBackend { + fn with_hardcoded_result(result: Result) -> Self { + Self { + result, + } + } +} + +#[async_trait] +impl ValidationBackend for MockValidatorBackend { + async fn validate_candidate( + &mut self, + _raw_validation_code: Vec, + _params: ValidationParams + ) -> Result { + self.result.clone() + } +} + +#[test] +fn candidate_validation_ok_is_ok() { + let validation_data = PersistedValidationData { max_pov_size: 1024, ..Default::default() }; + + let pov = PoV { block_data: BlockData(vec![1; 32]) }; + let head_data = HeadData(vec![1, 1, 1]); + let validation_code = ValidationCode(vec![2; 16]); + + let mut descriptor = CandidateDescriptor::default(); + descriptor.pov_hash = pov.hash(); + descriptor.para_head = head_data.hash(); + descriptor.validation_code_hash = validation_code.hash(); + collator_sign(&mut descriptor, Sr25519Keyring::Alice); + + let check = perform_basic_checks( + &descriptor, + validation_data.max_pov_size, + &pov, + &validation_code, + ); + assert!(check.is_ok()); + + let validation_result = WasmValidationResult { + head_data, + new_validation_code: Some(vec![2, 2, 2].into()), + upward_messages: Vec::new(), + horizontal_messages: Vec::new(), + processed_downward_messages: 0, + hrmp_watermark: 0, + }; + + let v = executor::block_on(validate_candidate_exhaustive( + MockValidatorBackend::with_hardcoded_result(Ok(validation_result)), + validation_data.clone(), + validation_code, + descriptor, + Arc::new(pov), + &Default::default(), + )) + .unwrap() + .unwrap(); + + assert_matches!(v, ValidationResult::Valid(outputs, used_validation_data) => { + assert_eq!(outputs.head_data, HeadData(vec![1, 1, 1])); + assert_eq!(outputs.upward_messages, Vec::::new()); + assert_eq!(outputs.horizontal_messages, Vec::new()); + assert_eq!(outputs.new_validation_code, Some(vec![2, 2, 2].into())); + assert_eq!(outputs.hrmp_watermark, 0); + assert_eq!(used_validation_data, validation_data); + }); +} + +#[test] +fn candidate_validation_bad_return_is_invalid() { + let validation_data = PersistedValidationData { max_pov_size: 1024, ..Default::default() }; + + let pov = PoV { block_data: BlockData(vec![1; 32]) }; + let validation_code = ValidationCode(vec![2; 16]); + + let mut descriptor = CandidateDescriptor::default(); + descriptor.pov_hash = pov.hash(); + descriptor.validation_code_hash = validation_code.hash(); + collator_sign(&mut descriptor, Sr25519Keyring::Alice); + + let check = perform_basic_checks( + &descriptor, + validation_data.max_pov_size, + &pov, + &validation_code, + ); + assert!(check.is_ok()); + + let v = executor::block_on(validate_candidate_exhaustive( + MockValidatorBackend::with_hardcoded_result( + Err(ValidationError::InvalidCandidate(WasmInvalidCandidate::AmbigiousWorkerDeath)) + ), + validation_data, + validation_code, + descriptor, + Arc::new(pov), + &Default::default(), + )) + .unwrap() + .unwrap(); + + assert_matches!(v, ValidationResult::Invalid(InvalidCandidate::ExecutionError(_))); +} + +#[test] +fn candidate_validation_timeout_is_internal_error() { + let validation_data = PersistedValidationData { max_pov_size: 1024, ..Default::default() }; + + let pov = PoV { block_data: BlockData(vec![1; 32]) }; + let validation_code = ValidationCode(vec![2; 16]); + + let mut descriptor = CandidateDescriptor::default(); + descriptor.pov_hash = pov.hash(); + descriptor.validation_code_hash = validation_code.hash(); + collator_sign(&mut descriptor, Sr25519Keyring::Alice); + + let check = perform_basic_checks( + &descriptor, + validation_data.max_pov_size, + &pov, + &validation_code, + ); + assert!(check.is_ok()); + + let v = executor::block_on(validate_candidate_exhaustive( + MockValidatorBackend::with_hardcoded_result( + Err(ValidationError::InvalidCandidate(WasmInvalidCandidate::HardTimeout)), + ), + validation_data, + validation_code, + descriptor, + Arc::new(pov), + &Default::default(), + )) + .unwrap(); + + assert_matches!(v, Ok(ValidationResult::Invalid(InvalidCandidate::Timeout))); +} + +#[test] +fn candidate_validation_code_mismatch_is_invalid() { + let validation_data = PersistedValidationData { max_pov_size: 1024, ..Default::default() }; + + let pov = PoV { block_data: BlockData(vec![1; 32]) }; + let validation_code = ValidationCode(vec![2; 16]); + + let mut descriptor = CandidateDescriptor::default(); + descriptor.pov_hash = pov.hash(); + descriptor.validation_code_hash = ValidationCode(vec![1; 16]).hash(); + collator_sign(&mut descriptor, Sr25519Keyring::Alice); + + let check = perform_basic_checks( + &descriptor, + validation_data.max_pov_size, + &pov, + &validation_code, + ); + assert_matches!(check, Err(InvalidCandidate::CodeHashMismatch)); + + let v = executor::block_on(validate_candidate_exhaustive( + MockValidatorBackend::with_hardcoded_result( + Err(ValidationError::InvalidCandidate(WasmInvalidCandidate::HardTimeout)), + ), + validation_data, + validation_code, + descriptor, + Arc::new(pov), + &Default::default(), + )) + .unwrap() + .unwrap(); + + assert_matches!(v, ValidationResult::Invalid(InvalidCandidate::CodeHashMismatch)); +} + +#[test] +fn compressed_code_works() { + let validation_data = PersistedValidationData { max_pov_size: 1024, ..Default::default() }; + let pov = PoV { block_data: BlockData(vec![1; 32]) }; + let head_data = HeadData(vec![1, 1, 1]); + + let raw_code = vec![2u8; 16]; + let validation_code = sp_maybe_compressed_blob::compress( + &raw_code, + VALIDATION_CODE_BOMB_LIMIT, + ) + .map(ValidationCode) + .unwrap(); + + let mut descriptor = CandidateDescriptor::default(); + descriptor.pov_hash = pov.hash(); + descriptor.para_head = head_data.hash(); + descriptor.validation_code_hash = validation_code.hash(); + collator_sign(&mut descriptor, Sr25519Keyring::Alice); + + let validation_result = WasmValidationResult { + head_data, + new_validation_code: None, + upward_messages: Vec::new(), + horizontal_messages: Vec::new(), + processed_downward_messages: 0, + hrmp_watermark: 0, + }; + + let v = executor::block_on(validate_candidate_exhaustive( + MockValidatorBackend::with_hardcoded_result(Ok(validation_result)), + validation_data, + validation_code, + descriptor, + Arc::new(pov), + &Default::default(), + )) + .unwrap(); + + assert_matches!(v, Ok(ValidationResult::Valid(_, _))); +} + +#[test] +fn code_decompression_failure_is_invalid() { + let validation_data = PersistedValidationData { max_pov_size: 1024, ..Default::default() }; + let pov = PoV { block_data: BlockData(vec![1; 32]) }; + let head_data = HeadData(vec![1, 1, 1]); + + let raw_code = vec![2u8; VALIDATION_CODE_BOMB_LIMIT + 1]; + let validation_code = sp_maybe_compressed_blob::compress( + &raw_code, + VALIDATION_CODE_BOMB_LIMIT + 1, + ) + .map(ValidationCode) + .unwrap(); + + let mut descriptor = CandidateDescriptor::default(); + descriptor.pov_hash = pov.hash(); + descriptor.para_head = head_data.hash(); + descriptor.validation_code_hash = validation_code.hash(); + collator_sign(&mut descriptor, Sr25519Keyring::Alice); + + let validation_result = WasmValidationResult { + head_data, + new_validation_code: None, + upward_messages: Vec::new(), + horizontal_messages: Vec::new(), + processed_downward_messages: 0, + hrmp_watermark: 0, + }; + + let v = executor::block_on(validate_candidate_exhaustive( + MockValidatorBackend::with_hardcoded_result(Ok(validation_result)), + validation_data, + validation_code, + descriptor, + Arc::new(pov), + &Default::default(), + )) + .unwrap(); + + assert_matches!( + v, + Ok(ValidationResult::Invalid(InvalidCandidate::CodeDecompressionFailure)) + ); +} + +#[test] +fn pov_decompression_failure_is_invalid() { + let validation_data = PersistedValidationData { + max_pov_size: POV_BOMB_LIMIT as u32, + ..Default::default() + }; + let head_data = HeadData(vec![1, 1, 1]); + + let raw_block_data = vec![2u8; POV_BOMB_LIMIT + 1]; + let pov = sp_maybe_compressed_blob::compress( + &raw_block_data, + POV_BOMB_LIMIT + 1, + ) + .map(|raw| PoV { block_data: BlockData(raw) }) + .unwrap(); + + let validation_code = ValidationCode(vec![2; 16]); + + let mut descriptor = CandidateDescriptor::default(); + descriptor.pov_hash = pov.hash(); + descriptor.para_head = head_data.hash(); + descriptor.validation_code_hash = validation_code.hash(); + collator_sign(&mut descriptor, Sr25519Keyring::Alice); + + let validation_result = WasmValidationResult { + head_data, + new_validation_code: None, + upward_messages: Vec::new(), + horizontal_messages: Vec::new(), + processed_downward_messages: 0, + hrmp_watermark: 0, + }; + + let v = executor::block_on(validate_candidate_exhaustive( + MockValidatorBackend::with_hardcoded_result(Ok(validation_result)), + validation_data, + validation_code, + descriptor, + Arc::new(pov), + &Default::default(), + )) + .unwrap(); + + assert_matches!( + v, + Ok(ValidationResult::Invalid(InvalidCandidate::PoVDecompressionFailure)) + ); +} diff --git a/node/network/bitfield-distribution/src/lib.rs b/node/network/bitfield-distribution/src/lib.rs index 67298ca750c0..1cbd82bc1096 100644 --- a/node/network/bitfield-distribution/src/lib.rs +++ b/node/network/bitfield-distribution/src/lib.rs @@ -1,4 +1,4 @@ -// Copyright 2020 Parity Technologies (UK) Ltd. +// Copyright 2020-2021 Parity Technologies (UK) Ltd. // This file is part of Polkadot. // Polkadot is free software: you can redistribute it and/or modify @@ -38,6 +38,9 @@ use polkadot_primitives::v1::{Hash, SignedAvailabilityBitfield, SigningContext, use polkadot_node_network_protocol::{v1 as protocol_v1, PeerId, View, UnifiedReputationChange as Rep, OurView}; use std::collections::{HashMap, HashSet}; +#[cfg(test)] +mod tests; + const COST_SIGNATURE_INVALID: Rep = Rep::CostMajor("Bitfield signature invalid"); const COST_VALIDATOR_INDEX_INVALID: Rep = Rep::CostMajor("Bitfield validator index invalid"); const COST_MISSING_PEER_SESSION_KEY: Rep = Rep::CostMinor("Missing peer session key"); @@ -809,718 +812,3 @@ impl metrics::Metrics for Metrics { Ok(Metrics(Some(metrics))) } } - - -#[cfg(test)] -mod test { - use super::*; - use bitvec::bitvec; - use futures::executor; - use maplit::hashmap; - use polkadot_primitives::v1::{Signed, AvailabilityBitfield, ValidatorIndex}; - use polkadot_node_subsystem_test_helpers::make_subsystem_context; - use polkadot_node_subsystem_util::TimeoutExt; - use sp_keystore::{SyncCryptoStorePtr, SyncCryptoStore}; - use sp_application_crypto::AppKey; - use sp_keystore::testing::KeyStore; - use std::sync::Arc; - use std::time::Duration; - use assert_matches::assert_matches; - use polkadot_node_network_protocol::{view, ObservedRole, our_view}; - use polkadot_subsystem::jaeger; - - macro_rules! launch { - ($fut:expr) => { - $fut - .timeout(Duration::from_millis(10)) - .await - .expect("10ms is more than enough for sending messages.") - }; - } - - /// A very limited state, only interested in the relay parent of the - /// given message, which must be signed by `validator` and a set of peers - /// which are also only interested in that relay parent. - fn prewarmed_state( - validator: ValidatorId, - signing_context: SigningContext, - known_message: BitfieldGossipMessage, - peers: Vec, - ) -> ProtocolState { - let relay_parent = known_message.relay_parent.clone(); - ProtocolState { - per_relay_parent: hashmap! { - relay_parent.clone() => - PerRelayParentData { - signing_context, - validator_set: vec![validator.clone()], - one_per_validator: hashmap! { - validator.clone() => known_message.clone(), - }, - message_received_from_peer: hashmap!{}, - message_sent_to_peer: hashmap!{}, - span: PerLeafSpan::new(Arc::new(jaeger::Span::Disabled), "test"), - }, - }, - peer_views: peers - .into_iter() - .map(|peer| (peer, view!(relay_parent))) - .collect(), - view: our_view!(relay_parent), - } - } - - fn state_with_view( - view: OurView, - relay_parent: Hash, - ) -> (ProtocolState, SigningContext, SyncCryptoStorePtr, ValidatorId) { - let mut state = ProtocolState::default(); - - let signing_context = SigningContext { - session_index: 1, - parent_hash: relay_parent.clone(), - }; - - let keystore : SyncCryptoStorePtr = Arc::new(KeyStore::new()); - let validator = SyncCryptoStore::sr25519_generate_new(&*keystore, ValidatorId::ID, None) - .expect("generating sr25519 key not to fail"); - - state.per_relay_parent = view.iter().map(|relay_parent| {( - relay_parent.clone(), - PerRelayParentData { - signing_context: signing_context.clone(), - validator_set: vec![validator.clone().into()], - one_per_validator: hashmap!{}, - message_received_from_peer: hashmap!{}, - message_sent_to_peer: hashmap!{}, - span: PerLeafSpan::new(Arc::new(jaeger::Span::Disabled), "test"), - }) - }).collect(); - - state.view = view; - - (state, signing_context, keystore, validator.into()) - } - - #[test] - fn receive_invalid_signature() { - let _ = env_logger::builder() - .filter(None, log::LevelFilter::Trace) - .is_test(true) - .try_init(); - - let hash_a: Hash = [0; 32].into(); - - let peer_a = PeerId::random(); - let peer_b = PeerId::random(); - assert_ne!(peer_a, peer_b); - - let signing_context = SigningContext { - session_index: 1, - parent_hash: hash_a.clone(), - }; - - // another validator not part of the validatorset - let keystore : SyncCryptoStorePtr = Arc::new(KeyStore::new()); - let malicious = SyncCryptoStore::sr25519_generate_new(&*keystore, ValidatorId::ID, None) - .expect("Malicious key created"); - let validator_0 = SyncCryptoStore::sr25519_generate_new(&*keystore, ValidatorId::ID, None) - .expect("key created"); - let validator_1 = SyncCryptoStore::sr25519_generate_new(&*keystore, ValidatorId::ID, None) - .expect("key created"); - - let payload = AvailabilityBitfield(bitvec![bitvec::order::Lsb0, u8; 1u8; 32]); - let invalid_signed = executor::block_on(Signed::::sign( - &keystore, - payload.clone(), - &signing_context, - ValidatorIndex(0), - &malicious.into(), - )).ok().flatten().expect("should be signed"); - let invalid_signed_2 = executor::block_on(Signed::::sign( - &keystore, - payload.clone(), - &signing_context, - ValidatorIndex(1), - &malicious.into(), - )).ok().flatten().expect("should be signed"); - - let valid_signed = executor::block_on(Signed::::sign( - &keystore, - payload, - &signing_context, - ValidatorIndex(0), - &validator_0.into(), - )).ok().flatten().expect("should be signed"); - - let invalid_msg = BitfieldGossipMessage { - relay_parent: hash_a.clone(), - signed_availability: invalid_signed.clone(), - }; - let invalid_msg_2 = BitfieldGossipMessage { - relay_parent: hash_a.clone(), - signed_availability: invalid_signed_2.clone(), - }; - let valid_msg = BitfieldGossipMessage { - relay_parent: hash_a.clone(), - signed_availability: valid_signed.clone(), - }; - - let pool = sp_core::testing::TaskExecutor::new(); - let (mut ctx, mut handle) = - make_subsystem_context::(pool); - - let mut state = prewarmed_state( - validator_0.into(), - signing_context.clone(), - valid_msg, - vec![peer_b.clone()], - ); - state.per_relay_parent.get_mut(&hash_a) - .unwrap() - .validator_set - .push(validator_1.into()); - - executor::block_on(async move { - launch!(handle_network_msg( - &mut ctx, - &mut state, - &Default::default(), - NetworkBridgeEvent::PeerMessage(peer_b.clone(), invalid_msg.into_network_message()), - )); - - // reputation doesn't change due to one_job_per_validator check - assert!(handle.recv().timeout(Duration::from_millis(10)).await.is_none()); - - launch!(handle_network_msg( - &mut ctx, - &mut state, - &Default::default(), - NetworkBridgeEvent::PeerMessage(peer_b.clone(), invalid_msg_2.into_network_message()), - )); - // reputation change due to invalid signature - assert_matches!( - handle.recv().await, - AllMessages::NetworkBridge( - NetworkBridgeMessage::ReportPeer(peer, rep) - ) => { - assert_eq!(peer, peer_b); - assert_eq!(rep, COST_SIGNATURE_INVALID) - } - ); - }); - } - - #[test] - fn receive_invalid_validator_index() { - let _ = env_logger::builder() - .filter(None, log::LevelFilter::Trace) - .is_test(true) - .try_init(); - - let hash_a: Hash = [0; 32].into(); - let hash_b: Hash = [1; 32].into(); // other - - let peer_a = PeerId::random(); - let peer_b = PeerId::random(); - assert_ne!(peer_a, peer_b); - - // validator 0 key pair - let (mut state, signing_context, keystore, validator) = state_with_view(our_view![hash_a, hash_b], hash_a.clone()); - - state.peer_views.insert(peer_b.clone(), view![hash_a]); - - let payload = AvailabilityBitfield(bitvec![bitvec::order::Lsb0, u8; 1u8; 32]); - let signed = executor::block_on(Signed::::sign( - &keystore, - payload, - &signing_context, - ValidatorIndex(42), - &validator, - )).ok().flatten().expect("should be signed"); - - let msg = BitfieldGossipMessage { - relay_parent: hash_a.clone(), - signed_availability: signed.clone(), - }; - - let pool = sp_core::testing::TaskExecutor::new(); - let (mut ctx, mut handle) = - make_subsystem_context::(pool); - - executor::block_on(async move { - launch!(handle_network_msg( - &mut ctx, - &mut state, - &Default::default(), - NetworkBridgeEvent::PeerMessage(peer_b.clone(), msg.into_network_message()), - )); - - // reputation change due to invalid validator index - assert_matches!( - handle.recv().await, - AllMessages::NetworkBridge( - NetworkBridgeMessage::ReportPeer(peer, rep) - ) => { - assert_eq!(peer, peer_b); - assert_eq!(rep, COST_VALIDATOR_INDEX_INVALID) - } - ); - }); - } - - #[test] - fn receive_duplicate_messages() { - let _ = env_logger::builder() - .filter(None, log::LevelFilter::Trace) - .is_test(true) - .try_init(); - - let hash_a: Hash = [0; 32].into(); - let hash_b: Hash = [1; 32].into(); - - let peer_a = PeerId::random(); - let peer_b = PeerId::random(); - assert_ne!(peer_a, peer_b); - - // validator 0 key pair - let (mut state, signing_context, keystore, validator) = state_with_view(our_view![hash_a, hash_b], hash_a.clone()); - - // create a signed message by validator 0 - let payload = AvailabilityBitfield(bitvec![bitvec::order::Lsb0, u8; 1u8; 32]); - let signed_bitfield = executor::block_on(Signed::::sign( - &keystore, - payload, - &signing_context, - ValidatorIndex(0), - &validator, - )).ok().flatten().expect("should be signed"); - - let msg = BitfieldGossipMessage { - relay_parent: hash_a.clone(), - signed_availability: signed_bitfield.clone(), - }; - - let pool = sp_core::testing::TaskExecutor::new(); - let (mut ctx, mut handle) = - make_subsystem_context::(pool); - - executor::block_on(async move { - // send a first message - launch!(handle_network_msg( - &mut ctx, - &mut state, - &Default::default(), - NetworkBridgeEvent::PeerMessage( - peer_b.clone(), - msg.clone().into_network_message(), - ), - )); - - // none of our peers has any interest in any messages - // so we do not receive a network send type message here - // but only the one for the next subsystem - assert_matches!( - handle.recv().await, - AllMessages::Provisioner(ProvisionerMessage::ProvisionableData( - _, - ProvisionableData::Bitfield(hash, signed) - )) => { - assert_eq!(hash, hash_a); - assert_eq!(signed, signed_bitfield) - } - ); - - assert_matches!( - handle.recv().await, - AllMessages::NetworkBridge( - NetworkBridgeMessage::ReportPeer(peer, rep) - ) => { - assert_eq!(peer, peer_b); - assert_eq!(rep, BENEFIT_VALID_MESSAGE_FIRST) - } - ); - - // let peer A send the same message again - launch!(handle_network_msg( - &mut ctx, - &mut state, - &Default::default(), - NetworkBridgeEvent::PeerMessage( - peer_a.clone(), - msg.clone().into_network_message(), - ), - )); - - assert_matches!( - handle.recv().await, - AllMessages::NetworkBridge( - NetworkBridgeMessage::ReportPeer(peer, rep) - ) => { - assert_eq!(peer, peer_a); - assert_eq!(rep, BENEFIT_VALID_MESSAGE) - } - ); - - // let peer B send the initial message again - launch!(handle_network_msg( - &mut ctx, - &mut state, - &Default::default(), - NetworkBridgeEvent::PeerMessage( - peer_b.clone(), - msg.clone().into_network_message(), - ), - )); - - assert_matches!( - handle.recv().await, - AllMessages::NetworkBridge( - NetworkBridgeMessage::ReportPeer(peer, rep) - ) => { - assert_eq!(peer, peer_b); - assert_eq!(rep, COST_PEER_DUPLICATE_MESSAGE) - } - ); - }); - } - - #[test] - fn do_not_relay_message_twice() { - let _ = env_logger::builder() - .filter(None, log::LevelFilter::Trace) - .is_test(true) - .try_init(); - - let hash = Hash::random(); - - let peer_a = PeerId::random(); - let peer_b = PeerId::random(); - assert_ne!(peer_a, peer_b); - - // validator 0 key pair - let (mut state, signing_context, keystore, validator) = state_with_view(our_view![hash], hash.clone()); - - // create a signed message by validator 0 - let payload = AvailabilityBitfield(bitvec![bitvec::order::Lsb0, u8; 1u8; 32]); - let signed_bitfield = executor::block_on(Signed::::sign( - &keystore, - payload, - &signing_context, - ValidatorIndex(0), - &validator, - )).ok().flatten().expect("should be signed"); - - state.peer_views.insert(peer_b.clone(), view![hash]); - state.peer_views.insert(peer_a.clone(), view![hash]); - - let msg = BitfieldGossipMessage { - relay_parent: hash.clone(), - signed_availability: signed_bitfield.clone(), - }; - - let pool = sp_core::testing::TaskExecutor::new(); - let (mut ctx, mut handle) = - make_subsystem_context::(pool); - - executor::block_on(async move { - relay_message( - &mut ctx, - state.per_relay_parent.get_mut(&hash).unwrap(), - &mut state.peer_views, - validator.clone(), - msg.clone(), - ).await; - - assert_matches!( - handle.recv().await, - AllMessages::Provisioner(ProvisionerMessage::ProvisionableData( - _, - ProvisionableData::Bitfield(h, signed) - )) => { - assert_eq!(h, hash); - assert_eq!(signed, signed_bitfield) - } - ); - - assert_matches!( - handle.recv().await, - AllMessages::NetworkBridge( - NetworkBridgeMessage::SendValidationMessage(peers, send_msg), - ) => { - assert_eq!(2, peers.len()); - assert!(peers.contains(&peer_a)); - assert!(peers.contains(&peer_b)); - assert_eq!(send_msg, msg.clone().into_validation_protocol()); - } - ); - - // Relaying the message a second time shouldn't work. - relay_message( - &mut ctx, - state.per_relay_parent.get_mut(&hash).unwrap(), - &mut state.peer_views, - validator.clone(), - msg.clone(), - ).await; - - assert_matches!( - handle.recv().await, - AllMessages::Provisioner(ProvisionerMessage::ProvisionableData( - _, - ProvisionableData::Bitfield(h, signed) - )) => { - assert_eq!(h, hash); - assert_eq!(signed, signed_bitfield) - } - ); - - // There shouldn't be any other message - assert!(handle.recv().timeout(Duration::from_millis(10)).await.is_none()); - }); - } - - #[test] - fn changing_view() { - let _ = env_logger::builder() - .filter(None, log::LevelFilter::Trace) - .is_test(true) - .try_init(); - - let hash_a: Hash = [0; 32].into(); - let hash_b: Hash = [1; 32].into(); - - let peer_a = PeerId::random(); - let peer_b = PeerId::random(); - assert_ne!(peer_a, peer_b); - - // validator 0 key pair - let (mut state, signing_context, keystore, validator) = state_with_view(our_view![hash_a, hash_b], hash_a.clone()); - - // create a signed message by validator 0 - let payload = AvailabilityBitfield(bitvec![bitvec::order::Lsb0, u8; 1u8; 32]); - let signed_bitfield = executor::block_on(Signed::::sign( - &keystore, - payload, - &signing_context, - ValidatorIndex(0), - &validator, - )).ok().flatten().expect("should be signed"); - - let msg = BitfieldGossipMessage { - relay_parent: hash_a.clone(), - signed_availability: signed_bitfield.clone(), - }; - - let pool = sp_core::testing::TaskExecutor::new(); - let (mut ctx, mut handle) = - make_subsystem_context::(pool); - - executor::block_on(async move { - launch!(handle_network_msg( - &mut ctx, - &mut state, - &Default::default(), - NetworkBridgeEvent::PeerConnected(peer_b.clone(), ObservedRole::Full, None), - )); - - // make peer b interested - launch!(handle_network_msg( - &mut ctx, - &mut state, - &Default::default(), - NetworkBridgeEvent::PeerViewChange(peer_b.clone(), view![hash_a, hash_b]), - )); - - assert!(state.peer_views.contains_key(&peer_b)); - - // recv a first message from the network - launch!(handle_network_msg( - &mut ctx, - &mut state, - &Default::default(), - NetworkBridgeEvent::PeerMessage( - peer_b.clone(), - msg.clone().into_network_message(), - ), - )); - - // gossip to the overseer - assert_matches!( - handle.recv().await, - AllMessages::Provisioner(ProvisionerMessage::ProvisionableData( - _, - ProvisionableData::Bitfield(hash, signed) - )) => { - assert_eq!(hash, hash_a); - assert_eq!(signed, signed_bitfield) - } - ); - - // reputation change for peer B - assert_matches!( - handle.recv().await, - AllMessages::NetworkBridge( - NetworkBridgeMessage::ReportPeer(peer, rep) - ) => { - assert_eq!(peer, peer_b); - assert_eq!(rep, BENEFIT_VALID_MESSAGE_FIRST) - } - ); - - launch!(handle_network_msg( - &mut ctx, - &mut state, - &Default::default(), - NetworkBridgeEvent::PeerViewChange(peer_b.clone(), view![]), - )); - - assert!(state.peer_views.contains_key(&peer_b)); - assert_eq!( - state.peer_views.get(&peer_b).expect("Must contain value for peer B"), - &view![] - ); - - // on rx of the same message, since we are not interested, - // should give penalty - launch!(handle_network_msg( - &mut ctx, - &mut state, - &Default::default(), - NetworkBridgeEvent::PeerMessage( - peer_b.clone(), - msg.clone().into_network_message(), - ), - )); - - // reputation change for peer B - assert_matches!( - handle.recv().await, - AllMessages::NetworkBridge( - NetworkBridgeMessage::ReportPeer(peer, rep) - ) => { - assert_eq!(peer, peer_b); - assert_eq!(rep, COST_PEER_DUPLICATE_MESSAGE) - } - ); - - launch!(handle_network_msg( - &mut ctx, - &mut state, - &Default::default(), - NetworkBridgeEvent::PeerDisconnected(peer_b.clone()), - )); - - // we are not interested in any peers at all anymore - state.view = our_view![]; - - // on rx of the same message, since we are not interested, - // should give penalty - launch!(handle_network_msg( - &mut ctx, - &mut state, - &Default::default(), - NetworkBridgeEvent::PeerMessage( - peer_a.clone(), - msg.clone().into_network_message(), - ), - )); - - // reputation change for peer B - assert_matches!( - handle.recv().await, - AllMessages::NetworkBridge( - NetworkBridgeMessage::ReportPeer(peer, rep) - ) => { - assert_eq!(peer, peer_a); - assert_eq!(rep, COST_NOT_IN_VIEW) - } - ); - - }); - } - - #[test] - fn do_not_send_message_back_to_origin() { - let _ = env_logger::builder() - .filter(None, log::LevelFilter::Trace) - .is_test(true) - .try_init(); - - let hash: Hash = [0; 32].into(); - - let peer_a = PeerId::random(); - let peer_b = PeerId::random(); - assert_ne!(peer_a, peer_b); - - // validator 0 key pair - let (mut state, signing_context, keystore, validator) = state_with_view(our_view![hash], hash); - - // create a signed message by validator 0 - let payload = AvailabilityBitfield(bitvec![bitvec::order::Lsb0, u8; 1u8; 32]); - let signed_bitfield = executor::block_on(Signed::::sign( - &keystore, - payload, - &signing_context, - ValidatorIndex(0), - &validator, - )).ok().flatten().expect("should be signed"); - - state.peer_views.insert(peer_b.clone(), view![hash]); - state.peer_views.insert(peer_a.clone(), view![hash]); - - let msg = BitfieldGossipMessage { - relay_parent: hash.clone(), - signed_availability: signed_bitfield.clone(), - }; - - let pool = sp_core::testing::TaskExecutor::new(); - let (mut ctx, mut handle) = - make_subsystem_context::(pool); - - executor::block_on(async move { - // send a first message - launch!(handle_network_msg( - &mut ctx, - &mut state, - &Default::default(), - NetworkBridgeEvent::PeerMessage( - peer_b.clone(), - msg.clone().into_network_message(), - ), - )); - - assert_matches!( - handle.recv().await, - AllMessages::Provisioner(ProvisionerMessage::ProvisionableData( - _, - ProvisionableData::Bitfield(hash, signed) - )) => { - assert_eq!(hash, hash); - assert_eq!(signed, signed_bitfield) - } - ); - - assert_matches!( - handle.recv().await, - AllMessages::NetworkBridge( - NetworkBridgeMessage::SendValidationMessage(peers, send_msg), - ) => { - assert_eq!(1, peers.len()); - assert!(peers.contains(&peer_a)); - assert_eq!(send_msg, msg.clone().into_validation_protocol()); - } - ); - - assert_matches!( - handle.recv().await, - AllMessages::NetworkBridge( - NetworkBridgeMessage::ReportPeer(peer, rep) - ) => { - assert_eq!(peer, peer_b); - assert_eq!(rep, BENEFIT_VALID_MESSAGE_FIRST) - } - ); - }); - } -} diff --git a/node/network/bitfield-distribution/src/tests.rs b/node/network/bitfield-distribution/src/tests.rs new file mode 100644 index 000000000000..70f848721488 --- /dev/null +++ b/node/network/bitfield-distribution/src/tests.rs @@ -0,0 +1,726 @@ +// Copyright 2020-2021 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 bitvec::bitvec; +use futures::executor; +use maplit::hashmap; +use polkadot_primitives::v1::{Signed, AvailabilityBitfield, ValidatorIndex}; +use polkadot_node_subsystem_test_helpers::make_subsystem_context; +use polkadot_node_subsystem_util::TimeoutExt; +use sp_keystore::{SyncCryptoStorePtr, SyncCryptoStore}; +use sp_application_crypto::AppKey; +use sp_keystore::testing::KeyStore; +use std::sync::Arc; +use std::time::Duration; +use assert_matches::assert_matches; +use polkadot_node_network_protocol::{view, ObservedRole, our_view}; +use polkadot_subsystem::jaeger; + +macro_rules! launch { + ($fut:expr) => { + $fut + .timeout(Duration::from_millis(10)) + .await + .expect("10ms is more than enough for sending messages.") + }; +} + +/// A very limited state, only interested in the relay parent of the +/// given message, which must be signed by `validator` and a set of peers +/// which are also only interested in that relay parent. +fn prewarmed_state( + validator: ValidatorId, + signing_context: SigningContext, + known_message: BitfieldGossipMessage, + peers: Vec, +) -> ProtocolState { + let relay_parent = known_message.relay_parent.clone(); + ProtocolState { + per_relay_parent: hashmap! { + relay_parent.clone() => + PerRelayParentData { + signing_context, + validator_set: vec![validator.clone()], + one_per_validator: hashmap! { + validator.clone() => known_message.clone(), + }, + message_received_from_peer: hashmap!{}, + message_sent_to_peer: hashmap!{}, + span: PerLeafSpan::new(Arc::new(jaeger::Span::Disabled), "test"), + }, + }, + peer_views: peers + .into_iter() + .map(|peer| (peer, view!(relay_parent))) + .collect(), + view: our_view!(relay_parent), + } +} + +fn state_with_view( + view: OurView, + relay_parent: Hash, +) -> (ProtocolState, SigningContext, SyncCryptoStorePtr, ValidatorId) { + let mut state = ProtocolState::default(); + + let signing_context = SigningContext { + session_index: 1, + parent_hash: relay_parent.clone(), + }; + + let keystore : SyncCryptoStorePtr = Arc::new(KeyStore::new()); + let validator = SyncCryptoStore::sr25519_generate_new(&*keystore, ValidatorId::ID, None) + .expect("generating sr25519 key not to fail"); + + state.per_relay_parent = view.iter().map(|relay_parent| {( + relay_parent.clone(), + PerRelayParentData { + signing_context: signing_context.clone(), + validator_set: vec![validator.clone().into()], + one_per_validator: hashmap!{}, + message_received_from_peer: hashmap!{}, + message_sent_to_peer: hashmap!{}, + span: PerLeafSpan::new(Arc::new(jaeger::Span::Disabled), "test"), + }) + }).collect(); + + state.view = view; + + (state, signing_context, keystore, validator.into()) +} + +#[test] +fn receive_invalid_signature() { + let _ = env_logger::builder() + .filter(None, log::LevelFilter::Trace) + .is_test(true) + .try_init(); + + let hash_a: Hash = [0; 32].into(); + + let peer_a = PeerId::random(); + let peer_b = PeerId::random(); + assert_ne!(peer_a, peer_b); + + let signing_context = SigningContext { + session_index: 1, + parent_hash: hash_a.clone(), + }; + + // another validator not part of the validatorset + let keystore : SyncCryptoStorePtr = Arc::new(KeyStore::new()); + let malicious = SyncCryptoStore::sr25519_generate_new(&*keystore, ValidatorId::ID, None) + .expect("Malicious key created"); + let validator_0 = SyncCryptoStore::sr25519_generate_new(&*keystore, ValidatorId::ID, None) + .expect("key created"); + let validator_1 = SyncCryptoStore::sr25519_generate_new(&*keystore, ValidatorId::ID, None) + .expect("key created"); + + let payload = AvailabilityBitfield(bitvec![bitvec::order::Lsb0, u8; 1u8; 32]); + let invalid_signed = executor::block_on(Signed::::sign( + &keystore, + payload.clone(), + &signing_context, + ValidatorIndex(0), + &malicious.into(), + )).ok().flatten().expect("should be signed"); + let invalid_signed_2 = executor::block_on(Signed::::sign( + &keystore, + payload.clone(), + &signing_context, + ValidatorIndex(1), + &malicious.into(), + )).ok().flatten().expect("should be signed"); + + let valid_signed = executor::block_on(Signed::::sign( + &keystore, + payload, + &signing_context, + ValidatorIndex(0), + &validator_0.into(), + )).ok().flatten().expect("should be signed"); + + let invalid_msg = BitfieldGossipMessage { + relay_parent: hash_a.clone(), + signed_availability: invalid_signed.clone(), + }; + let invalid_msg_2 = BitfieldGossipMessage { + relay_parent: hash_a.clone(), + signed_availability: invalid_signed_2.clone(), + }; + let valid_msg = BitfieldGossipMessage { + relay_parent: hash_a.clone(), + signed_availability: valid_signed.clone(), + }; + + let pool = sp_core::testing::TaskExecutor::new(); + let (mut ctx, mut handle) = + make_subsystem_context::(pool); + + let mut state = prewarmed_state( + validator_0.into(), + signing_context.clone(), + valid_msg, + vec![peer_b.clone()], + ); + state.per_relay_parent.get_mut(&hash_a) + .unwrap() + .validator_set + .push(validator_1.into()); + + executor::block_on(async move { + launch!(handle_network_msg( + &mut ctx, + &mut state, + &Default::default(), + NetworkBridgeEvent::PeerMessage(peer_b.clone(), invalid_msg.into_network_message()), + )); + + // reputation doesn't change due to one_job_per_validator check + assert!(handle.recv().timeout(Duration::from_millis(10)).await.is_none()); + + launch!(handle_network_msg( + &mut ctx, + &mut state, + &Default::default(), + NetworkBridgeEvent::PeerMessage(peer_b.clone(), invalid_msg_2.into_network_message()), + )); + // reputation change due to invalid signature + assert_matches!( + handle.recv().await, + AllMessages::NetworkBridge( + NetworkBridgeMessage::ReportPeer(peer, rep) + ) => { + assert_eq!(peer, peer_b); + assert_eq!(rep, COST_SIGNATURE_INVALID) + } + ); + }); +} + +#[test] +fn receive_invalid_validator_index() { + let _ = env_logger::builder() + .filter(None, log::LevelFilter::Trace) + .is_test(true) + .try_init(); + + let hash_a: Hash = [0; 32].into(); + let hash_b: Hash = [1; 32].into(); // other + + let peer_a = PeerId::random(); + let peer_b = PeerId::random(); + assert_ne!(peer_a, peer_b); + + // validator 0 key pair + let (mut state, signing_context, keystore, validator) = state_with_view(our_view![hash_a, hash_b], hash_a.clone()); + + state.peer_views.insert(peer_b.clone(), view![hash_a]); + + let payload = AvailabilityBitfield(bitvec![bitvec::order::Lsb0, u8; 1u8; 32]); + let signed = executor::block_on(Signed::::sign( + &keystore, + payload, + &signing_context, + ValidatorIndex(42), + &validator, + )).ok().flatten().expect("should be signed"); + + let msg = BitfieldGossipMessage { + relay_parent: hash_a.clone(), + signed_availability: signed.clone(), + }; + + let pool = sp_core::testing::TaskExecutor::new(); + let (mut ctx, mut handle) = + make_subsystem_context::(pool); + + executor::block_on(async move { + launch!(handle_network_msg( + &mut ctx, + &mut state, + &Default::default(), + NetworkBridgeEvent::PeerMessage(peer_b.clone(), msg.into_network_message()), + )); + + // reputation change due to invalid validator index + assert_matches!( + handle.recv().await, + AllMessages::NetworkBridge( + NetworkBridgeMessage::ReportPeer(peer, rep) + ) => { + assert_eq!(peer, peer_b); + assert_eq!(rep, COST_VALIDATOR_INDEX_INVALID) + } + ); + }); +} + +#[test] +fn receive_duplicate_messages() { + let _ = env_logger::builder() + .filter(None, log::LevelFilter::Trace) + .is_test(true) + .try_init(); + + let hash_a: Hash = [0; 32].into(); + let hash_b: Hash = [1; 32].into(); + + let peer_a = PeerId::random(); + let peer_b = PeerId::random(); + assert_ne!(peer_a, peer_b); + + // validator 0 key pair + let (mut state, signing_context, keystore, validator) = state_with_view(our_view![hash_a, hash_b], hash_a.clone()); + + // create a signed message by validator 0 + let payload = AvailabilityBitfield(bitvec![bitvec::order::Lsb0, u8; 1u8; 32]); + let signed_bitfield = executor::block_on(Signed::::sign( + &keystore, + payload, + &signing_context, + ValidatorIndex(0), + &validator, + )).ok().flatten().expect("should be signed"); + + let msg = BitfieldGossipMessage { + relay_parent: hash_a.clone(), + signed_availability: signed_bitfield.clone(), + }; + + let pool = sp_core::testing::TaskExecutor::new(); + let (mut ctx, mut handle) = + make_subsystem_context::(pool); + + executor::block_on(async move { + // send a first message + launch!(handle_network_msg( + &mut ctx, + &mut state, + &Default::default(), + NetworkBridgeEvent::PeerMessage( + peer_b.clone(), + msg.clone().into_network_message(), + ), + )); + + // none of our peers has any interest in any messages + // so we do not receive a network send type message here + // but only the one for the next subsystem + assert_matches!( + handle.recv().await, + AllMessages::Provisioner(ProvisionerMessage::ProvisionableData( + _, + ProvisionableData::Bitfield(hash, signed) + )) => { + assert_eq!(hash, hash_a); + assert_eq!(signed, signed_bitfield) + } + ); + + assert_matches!( + handle.recv().await, + AllMessages::NetworkBridge( + NetworkBridgeMessage::ReportPeer(peer, rep) + ) => { + assert_eq!(peer, peer_b); + assert_eq!(rep, BENEFIT_VALID_MESSAGE_FIRST) + } + ); + + // let peer A send the same message again + launch!(handle_network_msg( + &mut ctx, + &mut state, + &Default::default(), + NetworkBridgeEvent::PeerMessage( + peer_a.clone(), + msg.clone().into_network_message(), + ), + )); + + assert_matches!( + handle.recv().await, + AllMessages::NetworkBridge( + NetworkBridgeMessage::ReportPeer(peer, rep) + ) => { + assert_eq!(peer, peer_a); + assert_eq!(rep, BENEFIT_VALID_MESSAGE) + } + ); + + // let peer B send the initial message again + launch!(handle_network_msg( + &mut ctx, + &mut state, + &Default::default(), + NetworkBridgeEvent::PeerMessage( + peer_b.clone(), + msg.clone().into_network_message(), + ), + )); + + assert_matches!( + handle.recv().await, + AllMessages::NetworkBridge( + NetworkBridgeMessage::ReportPeer(peer, rep) + ) => { + assert_eq!(peer, peer_b); + assert_eq!(rep, COST_PEER_DUPLICATE_MESSAGE) + } + ); + }); +} + +#[test] +fn do_not_relay_message_twice() { + let _ = env_logger::builder() + .filter(None, log::LevelFilter::Trace) + .is_test(true) + .try_init(); + + let hash = Hash::random(); + + let peer_a = PeerId::random(); + let peer_b = PeerId::random(); + assert_ne!(peer_a, peer_b); + + // validator 0 key pair + let (mut state, signing_context, keystore, validator) = state_with_view(our_view![hash], hash.clone()); + + // create a signed message by validator 0 + let payload = AvailabilityBitfield(bitvec![bitvec::order::Lsb0, u8; 1u8; 32]); + let signed_bitfield = executor::block_on(Signed::::sign( + &keystore, + payload, + &signing_context, + ValidatorIndex(0), + &validator, + )).ok().flatten().expect("should be signed"); + + state.peer_views.insert(peer_b.clone(), view![hash]); + state.peer_views.insert(peer_a.clone(), view![hash]); + + let msg = BitfieldGossipMessage { + relay_parent: hash.clone(), + signed_availability: signed_bitfield.clone(), + }; + + let pool = sp_core::testing::TaskExecutor::new(); + let (mut ctx, mut handle) = + make_subsystem_context::(pool); + + executor::block_on(async move { + relay_message( + &mut ctx, + state.per_relay_parent.get_mut(&hash).unwrap(), + &mut state.peer_views, + validator.clone(), + msg.clone(), + ).await; + + assert_matches!( + handle.recv().await, + AllMessages::Provisioner(ProvisionerMessage::ProvisionableData( + _, + ProvisionableData::Bitfield(h, signed) + )) => { + assert_eq!(h, hash); + assert_eq!(signed, signed_bitfield) + } + ); + + assert_matches!( + handle.recv().await, + AllMessages::NetworkBridge( + NetworkBridgeMessage::SendValidationMessage(peers, send_msg), + ) => { + assert_eq!(2, peers.len()); + assert!(peers.contains(&peer_a)); + assert!(peers.contains(&peer_b)); + assert_eq!(send_msg, msg.clone().into_validation_protocol()); + } + ); + + // Relaying the message a second time shouldn't work. + relay_message( + &mut ctx, + state.per_relay_parent.get_mut(&hash).unwrap(), + &mut state.peer_views, + validator.clone(), + msg.clone(), + ).await; + + assert_matches!( + handle.recv().await, + AllMessages::Provisioner(ProvisionerMessage::ProvisionableData( + _, + ProvisionableData::Bitfield(h, signed) + )) => { + assert_eq!(h, hash); + assert_eq!(signed, signed_bitfield) + } + ); + + // There shouldn't be any other message + assert!(handle.recv().timeout(Duration::from_millis(10)).await.is_none()); + }); +} + +#[test] +fn changing_view() { + let _ = env_logger::builder() + .filter(None, log::LevelFilter::Trace) + .is_test(true) + .try_init(); + + let hash_a: Hash = [0; 32].into(); + let hash_b: Hash = [1; 32].into(); + + let peer_a = PeerId::random(); + let peer_b = PeerId::random(); + assert_ne!(peer_a, peer_b); + + // validator 0 key pair + let (mut state, signing_context, keystore, validator) = state_with_view(our_view![hash_a, hash_b], hash_a.clone()); + + // create a signed message by validator 0 + let payload = AvailabilityBitfield(bitvec![bitvec::order::Lsb0, u8; 1u8; 32]); + let signed_bitfield = executor::block_on(Signed::::sign( + &keystore, + payload, + &signing_context, + ValidatorIndex(0), + &validator, + )).ok().flatten().expect("should be signed"); + + let msg = BitfieldGossipMessage { + relay_parent: hash_a.clone(), + signed_availability: signed_bitfield.clone(), + }; + + let pool = sp_core::testing::TaskExecutor::new(); + let (mut ctx, mut handle) = + make_subsystem_context::(pool); + + executor::block_on(async move { + launch!(handle_network_msg( + &mut ctx, + &mut state, + &Default::default(), + NetworkBridgeEvent::PeerConnected(peer_b.clone(), ObservedRole::Full, None), + )); + + // make peer b interested + launch!(handle_network_msg( + &mut ctx, + &mut state, + &Default::default(), + NetworkBridgeEvent::PeerViewChange(peer_b.clone(), view![hash_a, hash_b]), + )); + + assert!(state.peer_views.contains_key(&peer_b)); + + // recv a first message from the network + launch!(handle_network_msg( + &mut ctx, + &mut state, + &Default::default(), + NetworkBridgeEvent::PeerMessage( + peer_b.clone(), + msg.clone().into_network_message(), + ), + )); + + // gossip to the overseer + assert_matches!( + handle.recv().await, + AllMessages::Provisioner(ProvisionerMessage::ProvisionableData( + _, + ProvisionableData::Bitfield(hash, signed) + )) => { + assert_eq!(hash, hash_a); + assert_eq!(signed, signed_bitfield) + } + ); + + // reputation change for peer B + assert_matches!( + handle.recv().await, + AllMessages::NetworkBridge( + NetworkBridgeMessage::ReportPeer(peer, rep) + ) => { + assert_eq!(peer, peer_b); + assert_eq!(rep, BENEFIT_VALID_MESSAGE_FIRST) + } + ); + + launch!(handle_network_msg( + &mut ctx, + &mut state, + &Default::default(), + NetworkBridgeEvent::PeerViewChange(peer_b.clone(), view![]), + )); + + assert!(state.peer_views.contains_key(&peer_b)); + assert_eq!( + state.peer_views.get(&peer_b).expect("Must contain value for peer B"), + &view![] + ); + + // on rx of the same message, since we are not interested, + // should give penalty + launch!(handle_network_msg( + &mut ctx, + &mut state, + &Default::default(), + NetworkBridgeEvent::PeerMessage( + peer_b.clone(), + msg.clone().into_network_message(), + ), + )); + + // reputation change for peer B + assert_matches!( + handle.recv().await, + AllMessages::NetworkBridge( + NetworkBridgeMessage::ReportPeer(peer, rep) + ) => { + assert_eq!(peer, peer_b); + assert_eq!(rep, COST_PEER_DUPLICATE_MESSAGE) + } + ); + + launch!(handle_network_msg( + &mut ctx, + &mut state, + &Default::default(), + NetworkBridgeEvent::PeerDisconnected(peer_b.clone()), + )); + + // we are not interested in any peers at all anymore + state.view = our_view![]; + + // on rx of the same message, since we are not interested, + // should give penalty + launch!(handle_network_msg( + &mut ctx, + &mut state, + &Default::default(), + NetworkBridgeEvent::PeerMessage( + peer_a.clone(), + msg.clone().into_network_message(), + ), + )); + + // reputation change for peer B + assert_matches!( + handle.recv().await, + AllMessages::NetworkBridge( + NetworkBridgeMessage::ReportPeer(peer, rep) + ) => { + assert_eq!(peer, peer_a); + assert_eq!(rep, COST_NOT_IN_VIEW) + } + ); + + }); +} + +#[test] +fn do_not_send_message_back_to_origin() { + let _ = env_logger::builder() + .filter(None, log::LevelFilter::Trace) + .is_test(true) + .try_init(); + + let hash: Hash = [0; 32].into(); + + let peer_a = PeerId::random(); + let peer_b = PeerId::random(); + assert_ne!(peer_a, peer_b); + + // validator 0 key pair + let (mut state, signing_context, keystore, validator) = state_with_view(our_view![hash], hash); + + // create a signed message by validator 0 + let payload = AvailabilityBitfield(bitvec![bitvec::order::Lsb0, u8; 1u8; 32]); + let signed_bitfield = executor::block_on(Signed::::sign( + &keystore, + payload, + &signing_context, + ValidatorIndex(0), + &validator, + )).ok().flatten().expect("should be signed"); + + state.peer_views.insert(peer_b.clone(), view![hash]); + state.peer_views.insert(peer_a.clone(), view![hash]); + + let msg = BitfieldGossipMessage { + relay_parent: hash.clone(), + signed_availability: signed_bitfield.clone(), + }; + + let pool = sp_core::testing::TaskExecutor::new(); + let (mut ctx, mut handle) = + make_subsystem_context::(pool); + + executor::block_on(async move { + // send a first message + launch!(handle_network_msg( + &mut ctx, + &mut state, + &Default::default(), + NetworkBridgeEvent::PeerMessage( + peer_b.clone(), + msg.clone().into_network_message(), + ), + )); + + assert_matches!( + handle.recv().await, + AllMessages::Provisioner(ProvisionerMessage::ProvisionableData( + _, + ProvisionableData::Bitfield(hash, signed) + )) => { + assert_eq!(hash, hash); + assert_eq!(signed, signed_bitfield) + } + ); + + assert_matches!( + handle.recv().await, + AllMessages::NetworkBridge( + NetworkBridgeMessage::SendValidationMessage(peers, send_msg), + ) => { + assert_eq!(1, peers.len()); + assert!(peers.contains(&peer_a)); + assert_eq!(send_msg, msg.clone().into_validation_protocol()); + } + ); + + assert_matches!( + handle.recv().await, + AllMessages::NetworkBridge( + NetworkBridgeMessage::ReportPeer(peer, rep) + ) => { + assert_eq!(peer, peer_b); + assert_eq!(rep, BENEFIT_VALID_MESSAGE_FIRST) + } + ); + }); +} diff --git a/node/network/bridge/src/tests.rs b/node/network/bridge/src/tests.rs index 2321b3bf5f80..736fed83c5ab 100644 --- a/node/network/bridge/src/tests.rs +++ b/node/network/bridge/src/tests.rs @@ -1252,11 +1252,11 @@ fn spread_event_to_subsystems_is_up_to_date() { AllMessages::GossipSupport(_) => unreachable!("Not interested in network events"), AllMessages::DisputeCoordinator(_) => unreachable!("Not interested in network events"), AllMessages::DisputeParticipation(_) => unreachable!("Not interetsed in network events"), - // Add variants here as needed, `{ cnt += 1; }` for those that need to be - // notified, `unreachable!()` for those that should not. - } - } - assert_eq!(cnt, EXPECTED_COUNT); + // Add variants here as needed, `{ cnt += 1; }` for those that need to be + // notified, `unreachable!()` for those that should not. + } + } + assert_eq!(cnt, EXPECTED_COUNT); } #[test] diff --git a/node/network/collator-protocol/src/collator_side/tests.rs b/node/network/collator-protocol/src/collator_side/tests.rs index 21bd7e0f83fb..1153ff5c73b9 100644 --- a/node/network/collator-protocol/src/collator_side/tests.rs +++ b/node/network/collator-protocol/src/collator_side/tests.rs @@ -26,768 +26,768 @@ use sp_keyring::Sr25519Keyring; use sp_runtime::traits::AppVerify; use polkadot_node_network_protocol::{ - our_view, - view, - request_response::request::IncomingRequest, + our_view, + view, + request_response::request::IncomingRequest, }; use polkadot_node_subsystem_util::TimeoutExt; use polkadot_primitives::v1::{AuthorityDiscoveryId, CandidateDescriptor, CollatorPair, GroupRotationInfo, ScheduledCore, SessionIndex, SessionInfo, ValidatorId, ValidatorIndex}; use polkadot_node_primitives::BlockData; use polkadot_subsystem::{ - jaeger, - messages::{RuntimeApiMessage, RuntimeApiRequest}, - ActiveLeavesUpdate, ActivatedLeaf, LeafStatus, + jaeger, + messages::{RuntimeApiMessage, RuntimeApiRequest}, + ActiveLeavesUpdate, ActivatedLeaf, LeafStatus, }; use polkadot_subsystem_testhelpers as test_helpers; #[derive(Default)] struct TestCandidateBuilder { - para_id: ParaId, - pov_hash: Hash, - relay_parent: Hash, - commitments_hash: Hash, + para_id: ParaId, + pov_hash: Hash, + relay_parent: Hash, + commitments_hash: Hash, } impl TestCandidateBuilder { - fn build(self) -> CandidateReceipt { - CandidateReceipt { - descriptor: CandidateDescriptor { - para_id: self.para_id, - pov_hash: self.pov_hash, - relay_parent: self.relay_parent, - ..Default::default() - }, - commitments_hash: self.commitments_hash, - } - } + fn build(self) -> CandidateReceipt { + CandidateReceipt { + descriptor: CandidateDescriptor { + para_id: self.para_id, + pov_hash: self.pov_hash, + relay_parent: self.relay_parent, + ..Default::default() + }, + commitments_hash: self.commitments_hash, + } + } } #[derive(Clone)] struct TestState { - para_id: ParaId, - validators: Vec, - session_info: SessionInfo, - group_rotation_info: GroupRotationInfo, - validator_peer_id: Vec, - relay_parent: Hash, - availability_core: CoreState, - local_peer_id: PeerId, - collator_pair: CollatorPair, - session_index: SessionIndex, + para_id: ParaId, + validators: Vec, + session_info: SessionInfo, + group_rotation_info: GroupRotationInfo, + validator_peer_id: Vec, + relay_parent: Hash, + availability_core: CoreState, + local_peer_id: PeerId, + collator_pair: CollatorPair, + session_index: SessionIndex, } fn validator_pubkeys(val_ids: &[Sr25519Keyring]) -> Vec { - val_ids.iter().map(|v| v.public().into()).collect() + val_ids.iter().map(|v| v.public().into()).collect() } fn validator_authority_id(val_ids: &[Sr25519Keyring]) -> Vec { - val_ids.iter().map(|v| v.public().into()).collect() + val_ids.iter().map(|v| v.public().into()).collect() } impl Default for TestState { - fn default() -> Self { - let para_id = ParaId::from(1); - - let validators = vec![ - Sr25519Keyring::Alice, - Sr25519Keyring::Bob, - Sr25519Keyring::Charlie, - Sr25519Keyring::Dave, - Sr25519Keyring::Ferdie, - ]; - - let validator_public = validator_pubkeys(&validators); - let discovery_keys = validator_authority_id(&validators); - - let validator_peer_id = std::iter::repeat_with(|| PeerId::random()) - .take(discovery_keys.len()) - .collect(); - - let validator_groups = vec![vec![2, 0, 4], vec![3, 2, 4]] - .into_iter().map(|g| g.into_iter().map(ValidatorIndex).collect()).collect(); - let group_rotation_info = GroupRotationInfo { - session_start_block: 0, - group_rotation_frequency: 100, - now: 1, - }; - - let availability_core = CoreState::Scheduled(ScheduledCore { - para_id, - collator: None, - }); - - let relay_parent = Hash::random(); - - let local_peer_id = PeerId::random(); - let collator_pair = CollatorPair::generate().0; - - Self { - para_id, - validators, - session_info: SessionInfo { - validators: validator_public, - discovery_keys, - validator_groups, - ..Default::default() - }, - group_rotation_info, - validator_peer_id, - relay_parent, - availability_core, - local_peer_id, - collator_pair, - session_index: 1, - } - } + fn default() -> Self { + let para_id = ParaId::from(1); + + let validators = vec![ + Sr25519Keyring::Alice, + Sr25519Keyring::Bob, + Sr25519Keyring::Charlie, + Sr25519Keyring::Dave, + Sr25519Keyring::Ferdie, + ]; + + let validator_public = validator_pubkeys(&validators); + let discovery_keys = validator_authority_id(&validators); + + let validator_peer_id = std::iter::repeat_with(|| PeerId::random()) + .take(discovery_keys.len()) + .collect(); + + let validator_groups = vec![vec![2, 0, 4], vec![3, 2, 4]] + .into_iter().map(|g| g.into_iter().map(ValidatorIndex).collect()).collect(); + let group_rotation_info = GroupRotationInfo { + session_start_block: 0, + group_rotation_frequency: 100, + now: 1, + }; + + let availability_core = CoreState::Scheduled(ScheduledCore { + para_id, + collator: None, + }); + + let relay_parent = Hash::random(); + + let local_peer_id = PeerId::random(); + let collator_pair = CollatorPair::generate().0; + + Self { + para_id, + validators, + session_info: SessionInfo { + validators: validator_public, + discovery_keys, + validator_groups, + ..Default::default() + }, + group_rotation_info, + validator_peer_id, + relay_parent, + availability_core, + local_peer_id, + collator_pair, + session_index: 1, + } + } } impl TestState { - fn current_group_validator_indices(&self) -> &[ValidatorIndex] { - &self.session_info.validator_groups[0] - } - - fn current_session_index(&self) -> SessionIndex { - self.session_index - } - - fn current_group_validator_peer_ids(&self) -> Vec { - self.current_group_validator_indices().iter().map(|i| self.validator_peer_id[i.0 as usize].clone()).collect() - } - - fn current_group_validator_authority_ids(&self) -> Vec { - self.current_group_validator_indices() - .iter() - .map(|i| self.session_info.discovery_keys[i.0 as usize].clone()) - .collect() - } - - /// Generate a new relay parent and inform the subsystem about the new view. - /// - /// If `merge_views == true` it means the subsystem will be informed that we are working on the old `relay_parent` - /// and the new one. - async fn advance_to_new_round(&mut self, virtual_overseer: &mut VirtualOverseer, merge_views: bool) { - let old_relay_parent = self.relay_parent; - - while self.relay_parent == old_relay_parent { - self.relay_parent.randomize(); - } - - let our_view = if merge_views { - our_view![old_relay_parent, self.relay_parent] - } else { - our_view![self.relay_parent] - }; - - overseer_send( - virtual_overseer, - CollatorProtocolMessage::NetworkBridgeUpdateV1(NetworkBridgeEvent::OurViewChange(our_view)), - ).await; - } + fn current_group_validator_indices(&self) -> &[ValidatorIndex] { + &self.session_info.validator_groups[0] + } + + fn current_session_index(&self) -> SessionIndex { + self.session_index + } + + fn current_group_validator_peer_ids(&self) -> Vec { + self.current_group_validator_indices().iter().map(|i| self.validator_peer_id[i.0 as usize].clone()).collect() + } + + fn current_group_validator_authority_ids(&self) -> Vec { + self.current_group_validator_indices() + .iter() + .map(|i| self.session_info.discovery_keys[i.0 as usize].clone()) + .collect() + } + + /// Generate a new relay parent and inform the subsystem about the new view. + /// + /// If `merge_views == true` it means the subsystem will be informed that we are working on the old `relay_parent` + /// and the new one. + async fn advance_to_new_round(&mut self, virtual_overseer: &mut VirtualOverseer, merge_views: bool) { + let old_relay_parent = self.relay_parent; + + while self.relay_parent == old_relay_parent { + self.relay_parent.randomize(); + } + + let our_view = if merge_views { + our_view![old_relay_parent, self.relay_parent] + } else { + our_view![self.relay_parent] + }; + + overseer_send( + virtual_overseer, + CollatorProtocolMessage::NetworkBridgeUpdateV1(NetworkBridgeEvent::OurViewChange(our_view)), + ).await; + } } type VirtualOverseer = test_helpers::TestSubsystemContextHandle; struct TestHarness { - virtual_overseer: VirtualOverseer, + virtual_overseer: VirtualOverseer, } fn test_harness>( - local_peer_id: PeerId, - collator_pair: CollatorPair, - test: impl FnOnce(TestHarness) -> T, + local_peer_id: PeerId, + collator_pair: CollatorPair, + test: impl FnOnce(TestHarness) -> T, ) { - let pool = sp_core::testing::TaskExecutor::new(); + let pool = sp_core::testing::TaskExecutor::new(); - let (context, virtual_overseer) = test_helpers::make_subsystem_context(pool.clone()); + let (context, virtual_overseer) = test_helpers::make_subsystem_context(pool.clone()); - let subsystem = run(context, local_peer_id, collator_pair, Metrics::default()); + let subsystem = run(context, local_peer_id, collator_pair, Metrics::default()); - let test_fut = test(TestHarness { virtual_overseer }); + let test_fut = test(TestHarness { virtual_overseer }); - futures::pin_mut!(test_fut); - futures::pin_mut!(subsystem); + futures::pin_mut!(test_fut); + futures::pin_mut!(subsystem); - executor::block_on(future::join(async move { - let mut overseer = test_fut.await; - overseer_signal(&mut overseer, OverseerSignal::Conclude).await; - }, subsystem)).1.unwrap(); + executor::block_on(future::join(async move { + let mut overseer = test_fut.await; + overseer_signal(&mut overseer, OverseerSignal::Conclude).await; + }, subsystem)).1.unwrap(); } const TIMEOUT: Duration = Duration::from_millis(100); async fn overseer_send( - overseer: &mut VirtualOverseer, - msg: CollatorProtocolMessage, + overseer: &mut VirtualOverseer, + msg: CollatorProtocolMessage, ) { - tracing::trace!(?msg, "sending message"); - overseer - .send(FromOverseer::Communication { msg }) - .timeout(TIMEOUT) - .await - .expect(&format!("{:?} is more than enough for sending messages.", TIMEOUT)); + tracing::trace!(?msg, "sending message"); + overseer + .send(FromOverseer::Communication { msg }) + .timeout(TIMEOUT) + .await + .expect(&format!("{:?} is more than enough for sending messages.", TIMEOUT)); } async fn overseer_recv( - overseer: &mut VirtualOverseer, + overseer: &mut VirtualOverseer, ) -> AllMessages { - let msg = overseer_recv_with_timeout(overseer, TIMEOUT) - .await - .expect(&format!("{:?} is more than enough to receive messages", TIMEOUT)); + let msg = overseer_recv_with_timeout(overseer, TIMEOUT) + .await + .expect(&format!("{:?} is more than enough to receive messages", TIMEOUT)); - tracing::trace!(?msg, "received message"); + tracing::trace!(?msg, "received message"); - msg + msg } async fn overseer_recv_with_timeout( - overseer: &mut VirtualOverseer, - timeout: Duration, + overseer: &mut VirtualOverseer, + timeout: Duration, ) -> Option { - tracing::trace!("waiting for message..."); - overseer - .recv() - .timeout(timeout) - .await + tracing::trace!("waiting for message..."); + overseer + .recv() + .timeout(timeout) + .await } async fn overseer_signal( - overseer: &mut VirtualOverseer, - signal: OverseerSignal, + overseer: &mut VirtualOverseer, + signal: OverseerSignal, ) { - overseer - .send(FromOverseer::Signal(signal)) - .timeout(TIMEOUT) - .await - .expect(&format!("{:?} is more than enough for sending signals.", TIMEOUT)); + overseer + .send(FromOverseer::Signal(signal)) + .timeout(TIMEOUT) + .await + .expect(&format!("{:?} is more than enough for sending signals.", TIMEOUT)); } // Setup the system by sending the `CollateOn`, `ActiveLeaves` and `OurViewChange` messages. async fn setup_system(virtual_overseer: &mut VirtualOverseer, test_state: &TestState) { - overseer_send( - virtual_overseer, - CollatorProtocolMessage::CollateOn(test_state.para_id), - ).await; - - overseer_signal( - virtual_overseer, - OverseerSignal::ActiveLeaves(ActiveLeavesUpdate { - activated: vec![ActivatedLeaf { - hash: test_state.relay_parent, - number: 1, - status: LeafStatus::Fresh, - span: Arc::new(jaeger::Span::Disabled), - }].into(), - deactivated: [][..].into(), - }), - ).await; - - overseer_send( - virtual_overseer, - CollatorProtocolMessage::NetworkBridgeUpdateV1( - NetworkBridgeEvent::OurViewChange(our_view![test_state.relay_parent]), - ), - ).await; + overseer_send( + virtual_overseer, + CollatorProtocolMessage::CollateOn(test_state.para_id), + ).await; + + overseer_signal( + virtual_overseer, + OverseerSignal::ActiveLeaves(ActiveLeavesUpdate { + activated: vec![ActivatedLeaf { + hash: test_state.relay_parent, + number: 1, + status: LeafStatus::Fresh, + span: Arc::new(jaeger::Span::Disabled), + }].into(), + deactivated: [][..].into(), + }), + ).await; + + overseer_send( + virtual_overseer, + CollatorProtocolMessage::NetworkBridgeUpdateV1( + NetworkBridgeEvent::OurViewChange(our_view![test_state.relay_parent]), + ), + ).await; } /// Result of [`distribute_collation`] struct DistributeCollation { - candidate: CandidateReceipt, - pov_block: PoV, + candidate: CandidateReceipt, + pov_block: PoV, } /// Create some PoV and distribute it. async fn distribute_collation( - virtual_overseer: &mut VirtualOverseer, - test_state: &TestState, - // whether or not we expect a connection request or not. - should_connect: bool, + virtual_overseer: &mut VirtualOverseer, + test_state: &TestState, + // whether or not we expect a connection request or not. + should_connect: bool, ) -> DistributeCollation { - // Now we want to distribute a PoVBlock - let pov_block = PoV { - block_data: BlockData(vec![42, 43, 44]), - }; - - let pov_hash = pov_block.hash(); - - let candidate = TestCandidateBuilder { - para_id: test_state.para_id, - relay_parent: test_state.relay_parent, - pov_hash, - ..Default::default() - }.build(); - - overseer_send( - virtual_overseer, - CollatorProtocolMessage::DistributeCollation(candidate.clone(), pov_block.clone(), None), - ).await; - - // obtain the availability cores. - assert_matches!( - overseer_recv(virtual_overseer).await, - AllMessages::RuntimeApi(RuntimeApiMessage::Request( - relay_parent, - RuntimeApiRequest::AvailabilityCores(tx) - )) => { - assert_eq!(relay_parent, test_state.relay_parent); - tx.send(Ok(vec![test_state.availability_core.clone()])).unwrap(); - } - ); - - // We don't know precisely what is going to come as session info might be cached: - loop { - match overseer_recv(virtual_overseer).await { - AllMessages::RuntimeApi(RuntimeApiMessage::Request( - relay_parent, - RuntimeApiRequest::SessionIndexForChild(tx), - )) => { - assert_eq!(relay_parent, test_state.relay_parent); - tx.send(Ok(test_state.current_session_index())).unwrap(); - } - - AllMessages::RuntimeApi(RuntimeApiMessage::Request( - relay_parent, - RuntimeApiRequest::SessionInfo(index, tx), - )) => { - assert_eq!(relay_parent, test_state.relay_parent); - assert_eq!(index, test_state.current_session_index()); - - tx.send(Ok(Some(test_state.session_info.clone()))).unwrap(); - } - - AllMessages::RuntimeApi(RuntimeApiMessage::Request( - relay_parent, - RuntimeApiRequest::ValidatorGroups(tx) - )) => { - assert_eq!(relay_parent, test_state.relay_parent); - tx.send(Ok(( - test_state.session_info.validator_groups.clone(), - test_state.group_rotation_info.clone(), - ))).unwrap(); - // This call is mandatory - we are done: - break; - } - other => - panic!("Unexpected message received: {:?}", other), - } - } - - if should_connect { - assert_matches!( - overseer_recv(virtual_overseer).await, - AllMessages::NetworkBridge( - NetworkBridgeMessage::ConnectToValidators { - .. - } - ) => {} - ); - } - - DistributeCollation { - candidate, - pov_block, - } + // Now we want to distribute a PoVBlock + let pov_block = PoV { + block_data: BlockData(vec![42, 43, 44]), + }; + + let pov_hash = pov_block.hash(); + + let candidate = TestCandidateBuilder { + para_id: test_state.para_id, + relay_parent: test_state.relay_parent, + pov_hash, + ..Default::default() + }.build(); + + overseer_send( + virtual_overseer, + CollatorProtocolMessage::DistributeCollation(candidate.clone(), pov_block.clone(), None), + ).await; + + // obtain the availability cores. + assert_matches!( + overseer_recv(virtual_overseer).await, + AllMessages::RuntimeApi(RuntimeApiMessage::Request( + relay_parent, + RuntimeApiRequest::AvailabilityCores(tx) + )) => { + assert_eq!(relay_parent, test_state.relay_parent); + tx.send(Ok(vec![test_state.availability_core.clone()])).unwrap(); + } + ); + + // We don't know precisely what is going to come as session info might be cached: + loop { + match overseer_recv(virtual_overseer).await { + AllMessages::RuntimeApi(RuntimeApiMessage::Request( + relay_parent, + RuntimeApiRequest::SessionIndexForChild(tx), + )) => { + assert_eq!(relay_parent, test_state.relay_parent); + tx.send(Ok(test_state.current_session_index())).unwrap(); + } + + AllMessages::RuntimeApi(RuntimeApiMessage::Request( + relay_parent, + RuntimeApiRequest::SessionInfo(index, tx), + )) => { + assert_eq!(relay_parent, test_state.relay_parent); + assert_eq!(index, test_state.current_session_index()); + + tx.send(Ok(Some(test_state.session_info.clone()))).unwrap(); + } + + AllMessages::RuntimeApi(RuntimeApiMessage::Request( + relay_parent, + RuntimeApiRequest::ValidatorGroups(tx) + )) => { + assert_eq!(relay_parent, test_state.relay_parent); + tx.send(Ok(( + test_state.session_info.validator_groups.clone(), + test_state.group_rotation_info.clone(), + ))).unwrap(); + // This call is mandatory - we are done: + break; + } + other => + panic!("Unexpected message received: {:?}", other), + } + } + + if should_connect { + assert_matches!( + overseer_recv(virtual_overseer).await, + AllMessages::NetworkBridge( + NetworkBridgeMessage::ConnectToValidators { + .. + } + ) => {} + ); + } + + DistributeCollation { + candidate, + pov_block, + } } /// Connect a peer async fn connect_peer( - virtual_overseer: &mut VirtualOverseer, - peer: PeerId, - authority_id: Option + virtual_overseer: &mut VirtualOverseer, + peer: PeerId, + authority_id: Option ) { - overseer_send( - virtual_overseer, - CollatorProtocolMessage::NetworkBridgeUpdateV1( - NetworkBridgeEvent::PeerConnected( - peer.clone(), - polkadot_node_network_protocol::ObservedRole::Authority, - authority_id, - ), - ), - ).await; - - overseer_send( - virtual_overseer, - CollatorProtocolMessage::NetworkBridgeUpdateV1( - NetworkBridgeEvent::PeerViewChange(peer, view![]), - ), - ).await; + overseer_send( + virtual_overseer, + CollatorProtocolMessage::NetworkBridgeUpdateV1( + NetworkBridgeEvent::PeerConnected( + peer.clone(), + polkadot_node_network_protocol::ObservedRole::Authority, + authority_id, + ), + ), + ).await; + + overseer_send( + virtual_overseer, + CollatorProtocolMessage::NetworkBridgeUpdateV1( + NetworkBridgeEvent::PeerViewChange(peer, view![]), + ), + ).await; } /// Disconnect a peer async fn disconnect_peer(virtual_overseer: &mut VirtualOverseer, peer: PeerId) { - overseer_send( - virtual_overseer, - CollatorProtocolMessage::NetworkBridgeUpdateV1(NetworkBridgeEvent::PeerDisconnected(peer)), - ).await; + overseer_send( + virtual_overseer, + CollatorProtocolMessage::NetworkBridgeUpdateV1(NetworkBridgeEvent::PeerDisconnected(peer)), + ).await; } /// Check that the next received message is a `Declare` message. async fn expect_declare_msg( - virtual_overseer: &mut VirtualOverseer, - test_state: &TestState, - peer: &PeerId, + virtual_overseer: &mut VirtualOverseer, + test_state: &TestState, + peer: &PeerId, ) { - assert_matches!( - overseer_recv(virtual_overseer).await, - AllMessages::NetworkBridge( - NetworkBridgeMessage::SendCollationMessage( - to, - protocol_v1::CollationProtocol::CollatorProtocol(wire_message), - ) - ) => { - assert_eq!(to[0], *peer); - assert_matches!( - wire_message, - protocol_v1::CollatorProtocolMessage::Declare( - collator_id, - para_id, - signature, - ) => { - assert!(signature.verify( - &*protocol_v1::declare_signature_payload(&test_state.local_peer_id), - &collator_id), - ); - assert_eq!(collator_id, test_state.collator_pair.public()); - assert_eq!(para_id, test_state.para_id); - } - ); - } - ); + assert_matches!( + overseer_recv(virtual_overseer).await, + AllMessages::NetworkBridge( + NetworkBridgeMessage::SendCollationMessage( + to, + protocol_v1::CollationProtocol::CollatorProtocol(wire_message), + ) + ) => { + assert_eq!(to[0], *peer); + assert_matches!( + wire_message, + protocol_v1::CollatorProtocolMessage::Declare( + collator_id, + para_id, + signature, + ) => { + assert!(signature.verify( + &*protocol_v1::declare_signature_payload(&test_state.local_peer_id), + &collator_id), + ); + assert_eq!(collator_id, test_state.collator_pair.public()); + assert_eq!(para_id, test_state.para_id); + } + ); + } + ); } /// Check that the next received message is a collation advertisement message. async fn expect_advertise_collation_msg( - virtual_overseer: &mut VirtualOverseer, - peer: &PeerId, - expected_relay_parent: Hash, + virtual_overseer: &mut VirtualOverseer, + peer: &PeerId, + expected_relay_parent: Hash, ) { - assert_matches!( - overseer_recv(virtual_overseer).await, - AllMessages::NetworkBridge( - NetworkBridgeMessage::SendCollationMessage( - to, - protocol_v1::CollationProtocol::CollatorProtocol(wire_message), - ) - ) => { - assert_eq!(to[0], *peer); - assert_matches!( - wire_message, - protocol_v1::CollatorProtocolMessage::AdvertiseCollation( - relay_parent, - ) => { - assert_eq!(relay_parent, expected_relay_parent); - } - ); - } - ); + assert_matches!( + overseer_recv(virtual_overseer).await, + AllMessages::NetworkBridge( + NetworkBridgeMessage::SendCollationMessage( + to, + protocol_v1::CollationProtocol::CollatorProtocol(wire_message), + ) + ) => { + assert_eq!(to[0], *peer); + assert_matches!( + wire_message, + protocol_v1::CollatorProtocolMessage::AdvertiseCollation( + relay_parent, + ) => { + assert_eq!(relay_parent, expected_relay_parent); + } + ); + } + ); } /// Send a message that the given peer's view changed. async fn send_peer_view_change(virtual_overseer: &mut VirtualOverseer, peer: &PeerId, hashes: Vec) { - overseer_send( - virtual_overseer, - CollatorProtocolMessage::NetworkBridgeUpdateV1( - NetworkBridgeEvent::PeerViewChange(peer.clone(), View::new(hashes, 0)), - ), - ).await; + overseer_send( + virtual_overseer, + CollatorProtocolMessage::NetworkBridgeUpdateV1( + NetworkBridgeEvent::PeerViewChange(peer.clone(), View::new(hashes, 0)), + ), + ).await; } #[test] fn advertise_and_send_collation() { - let mut test_state = TestState::default(); - let local_peer_id = test_state.local_peer_id.clone(); - let collator_pair = test_state.collator_pair.clone(); - - test_harness(local_peer_id, collator_pair, |test_harness| async move { - let mut virtual_overseer = test_harness.virtual_overseer; - - setup_system(&mut virtual_overseer, &test_state).await; - - let DistributeCollation { candidate, pov_block } = - distribute_collation(&mut virtual_overseer, &test_state, true).await; - - for (val, peer) in test_state.current_group_validator_authority_ids() - .into_iter() - .zip(test_state.current_group_validator_peer_ids()) - { - connect_peer(&mut virtual_overseer, peer.clone(), Some(val.clone())).await; - } - - // We declare to the connected validators that we are a collator. - // We need to catch all `Declare` messages to the validators we've - // previosly connected to. - for peer_id in test_state.current_group_validator_peer_ids() { - expect_declare_msg(&mut virtual_overseer, &test_state, &peer_id).await; - } - - let peer = test_state.current_group_validator_peer_ids()[0].clone(); - - // Send info about peer's view. - send_peer_view_change(&mut virtual_overseer, &peer, vec![test_state.relay_parent]).await; - - // The peer is interested in a leaf that we have a collation for; - // advertise it. - expect_advertise_collation_msg(&mut virtual_overseer, &peer, test_state.relay_parent).await; - - // Request a collation. - let (tx, rx) = oneshot::channel(); - overseer_send( - &mut virtual_overseer, - CollatorProtocolMessage::CollationFetchingRequest( - IncomingRequest::new( - peer, - CollationFetchingRequest { - relay_parent: test_state.relay_parent, - para_id: test_state.para_id, - }, - tx, - ) - ) - ).await; - - assert_matches!( - rx.await, - Ok(full_response) => { - let CollationFetchingResponse::Collation(receipt, pov): CollationFetchingResponse - = CollationFetchingResponse::decode( - &mut full_response.result - .expect("We should have a proper answer").as_ref() - ) - .expect("Decoding should work"); - assert_eq!(receipt, candidate); - assert_eq!(pov, pov_block); - } - ); - - let old_relay_parent = test_state.relay_parent; - test_state.advance_to_new_round(&mut virtual_overseer, false).await; - - let peer = test_state.validator_peer_id[2].clone(); - - // Re-request a collation. - let (tx, rx) = oneshot::channel(); - overseer_send( - &mut virtual_overseer, - CollatorProtocolMessage::CollationFetchingRequest( - IncomingRequest::new( - peer, - CollationFetchingRequest { - relay_parent: old_relay_parent, - para_id: test_state.para_id, - }, - tx, - ) - ) - ).await; - // Re-requesting collation should fail: - assert_matches!( - rx.await, - Err(_) => {} - ); - - assert!(overseer_recv_with_timeout(&mut virtual_overseer, TIMEOUT).await.is_none()); - - distribute_collation(&mut virtual_overseer, &test_state, true).await; - - // Send info about peer's view. - overseer_send( - &mut virtual_overseer, - CollatorProtocolMessage::NetworkBridgeUpdateV1( - NetworkBridgeEvent::PeerViewChange( - peer.clone(), - view![test_state.relay_parent], - ) - ) - ).await; - - expect_advertise_collation_msg(&mut virtual_overseer, &peer, test_state.relay_parent).await; - virtual_overseer - }); + let mut test_state = TestState::default(); + let local_peer_id = test_state.local_peer_id.clone(); + let collator_pair = test_state.collator_pair.clone(); + + test_harness(local_peer_id, collator_pair, |test_harness| async move { + let mut virtual_overseer = test_harness.virtual_overseer; + + setup_system(&mut virtual_overseer, &test_state).await; + + let DistributeCollation { candidate, pov_block } = + distribute_collation(&mut virtual_overseer, &test_state, true).await; + + for (val, peer) in test_state.current_group_validator_authority_ids() + .into_iter() + .zip(test_state.current_group_validator_peer_ids()) + { + connect_peer(&mut virtual_overseer, peer.clone(), Some(val.clone())).await; + } + + // We declare to the connected validators that we are a collator. + // We need to catch all `Declare` messages to the validators we've + // previosly connected to. + for peer_id in test_state.current_group_validator_peer_ids() { + expect_declare_msg(&mut virtual_overseer, &test_state, &peer_id).await; + } + + let peer = test_state.current_group_validator_peer_ids()[0].clone(); + + // Send info about peer's view. + send_peer_view_change(&mut virtual_overseer, &peer, vec![test_state.relay_parent]).await; + + // The peer is interested in a leaf that we have a collation for; + // advertise it. + expect_advertise_collation_msg(&mut virtual_overseer, &peer, test_state.relay_parent).await; + + // Request a collation. + let (tx, rx) = oneshot::channel(); + overseer_send( + &mut virtual_overseer, + CollatorProtocolMessage::CollationFetchingRequest( + IncomingRequest::new( + peer, + CollationFetchingRequest { + relay_parent: test_state.relay_parent, + para_id: test_state.para_id, + }, + tx, + ) + ) + ).await; + + assert_matches!( + rx.await, + Ok(full_response) => { + let CollationFetchingResponse::Collation(receipt, pov): CollationFetchingResponse + = CollationFetchingResponse::decode( + &mut full_response.result + .expect("We should have a proper answer").as_ref() + ) + .expect("Decoding should work"); + assert_eq!(receipt, candidate); + assert_eq!(pov, pov_block); + } + ); + + let old_relay_parent = test_state.relay_parent; + test_state.advance_to_new_round(&mut virtual_overseer, false).await; + + let peer = test_state.validator_peer_id[2].clone(); + + // Re-request a collation. + let (tx, rx) = oneshot::channel(); + overseer_send( + &mut virtual_overseer, + CollatorProtocolMessage::CollationFetchingRequest( + IncomingRequest::new( + peer, + CollationFetchingRequest { + relay_parent: old_relay_parent, + para_id: test_state.para_id, + }, + tx, + ) + ) + ).await; + // Re-requesting collation should fail: + assert_matches!( + rx.await, + Err(_) => {} + ); + + assert!(overseer_recv_with_timeout(&mut virtual_overseer, TIMEOUT).await.is_none()); + + distribute_collation(&mut virtual_overseer, &test_state, true).await; + + // Send info about peer's view. + overseer_send( + &mut virtual_overseer, + CollatorProtocolMessage::NetworkBridgeUpdateV1( + NetworkBridgeEvent::PeerViewChange( + peer.clone(), + view![test_state.relay_parent], + ) + ) + ).await; + + expect_advertise_collation_msg(&mut virtual_overseer, &peer, test_state.relay_parent).await; + virtual_overseer + }); } #[test] fn collators_declare_to_connected_peers() { - let test_state = TestState::default(); - let local_peer_id = test_state.local_peer_id.clone(); - let collator_pair = test_state.collator_pair.clone(); + let test_state = TestState::default(); + let local_peer_id = test_state.local_peer_id.clone(); + let collator_pair = test_state.collator_pair.clone(); - test_harness(local_peer_id, collator_pair, |test_harness| async move { - let mut virtual_overseer = test_harness.virtual_overseer; + test_harness(local_peer_id, collator_pair, |test_harness| async move { + let mut virtual_overseer = test_harness.virtual_overseer; - let peer = test_state.validator_peer_id[0].clone(); - let validator_id = test_state.current_group_validator_authority_ids()[0].clone(); + let peer = test_state.validator_peer_id[0].clone(); + let validator_id = test_state.current_group_validator_authority_ids()[0].clone(); - setup_system(&mut virtual_overseer, &test_state).await; + setup_system(&mut virtual_overseer, &test_state).await; - // A validator connected to us - connect_peer(&mut virtual_overseer, peer.clone(), Some(validator_id)).await; - expect_declare_msg(&mut virtual_overseer, &test_state, &peer).await; - virtual_overseer - }) + // A validator connected to us + connect_peer(&mut virtual_overseer, peer.clone(), Some(validator_id)).await; + expect_declare_msg(&mut virtual_overseer, &test_state, &peer).await; + virtual_overseer + }) } #[test] fn collations_are_only_advertised_to_validators_with_correct_view() { - let test_state = TestState::default(); - let local_peer_id = test_state.local_peer_id.clone(); - let collator_pair = test_state.collator_pair.clone(); + let test_state = TestState::default(); + let local_peer_id = test_state.local_peer_id.clone(); + let collator_pair = test_state.collator_pair.clone(); - test_harness(local_peer_id, collator_pair, |test_harness| async move { - let mut virtual_overseer = test_harness.virtual_overseer; + test_harness(local_peer_id, collator_pair, |test_harness| async move { + let mut virtual_overseer = test_harness.virtual_overseer; - let peer = test_state.current_group_validator_peer_ids()[0].clone(); - let validator_id = test_state.current_group_validator_authority_ids()[0].clone(); + let peer = test_state.current_group_validator_peer_ids()[0].clone(); + let validator_id = test_state.current_group_validator_authority_ids()[0].clone(); - let peer2 = test_state.current_group_validator_peer_ids()[1].clone(); - let validator_id2 = test_state.current_group_validator_authority_ids()[1].clone(); + let peer2 = test_state.current_group_validator_peer_ids()[1].clone(); + let validator_id2 = test_state.current_group_validator_authority_ids()[1].clone(); - setup_system(&mut virtual_overseer, &test_state).await; + setup_system(&mut virtual_overseer, &test_state).await; - // A validator connected to us - connect_peer(&mut virtual_overseer, peer.clone(), Some(validator_id)).await; + // A validator connected to us + connect_peer(&mut virtual_overseer, peer.clone(), Some(validator_id)).await; - // Connect the second validator - connect_peer(&mut virtual_overseer, peer2.clone(), Some(validator_id2)).await; + // Connect the second validator + connect_peer(&mut virtual_overseer, peer2.clone(), Some(validator_id2)).await; - expect_declare_msg(&mut virtual_overseer, &test_state, &peer).await; - expect_declare_msg(&mut virtual_overseer, &test_state, &peer2).await; + expect_declare_msg(&mut virtual_overseer, &test_state, &peer).await; + expect_declare_msg(&mut virtual_overseer, &test_state, &peer2).await; - // And let it tell us that it is has the same view. - send_peer_view_change(&mut virtual_overseer, &peer2, vec![test_state.relay_parent]).await; + // And let it tell us that it is has the same view. + send_peer_view_change(&mut virtual_overseer, &peer2, vec![test_state.relay_parent]).await; - distribute_collation(&mut virtual_overseer, &test_state, true).await; + distribute_collation(&mut virtual_overseer, &test_state, true).await; - expect_advertise_collation_msg(&mut virtual_overseer, &peer2, test_state.relay_parent).await; + expect_advertise_collation_msg(&mut virtual_overseer, &peer2, test_state.relay_parent).await; - // The other validator announces that it changed its view. - send_peer_view_change(&mut virtual_overseer, &peer, vec![test_state.relay_parent]).await; + // The other validator announces that it changed its view. + send_peer_view_change(&mut virtual_overseer, &peer, vec![test_state.relay_parent]).await; - // After changing the view we should receive the advertisement - expect_advertise_collation_msg(&mut virtual_overseer, &peer, test_state.relay_parent).await; - virtual_overseer - }) + // After changing the view we should receive the advertisement + expect_advertise_collation_msg(&mut virtual_overseer, &peer, test_state.relay_parent).await; + virtual_overseer + }) } #[test] fn collate_on_two_different_relay_chain_blocks() { - let mut test_state = TestState::default(); - let local_peer_id = test_state.local_peer_id.clone(); - let collator_pair = test_state.collator_pair.clone(); + let mut test_state = TestState::default(); + let local_peer_id = test_state.local_peer_id.clone(); + let collator_pair = test_state.collator_pair.clone(); - test_harness(local_peer_id, collator_pair, |test_harness| async move { - let mut virtual_overseer = test_harness.virtual_overseer; + test_harness(local_peer_id, collator_pair, |test_harness| async move { + let mut virtual_overseer = test_harness.virtual_overseer; - let peer = test_state.current_group_validator_peer_ids()[0].clone(); - let validator_id = test_state.current_group_validator_authority_ids()[0].clone(); + let peer = test_state.current_group_validator_peer_ids()[0].clone(); + let validator_id = test_state.current_group_validator_authority_ids()[0].clone(); - let peer2 = test_state.current_group_validator_peer_ids()[1].clone(); - let validator_id2 = test_state.current_group_validator_authority_ids()[1].clone(); + let peer2 = test_state.current_group_validator_peer_ids()[1].clone(); + let validator_id2 = test_state.current_group_validator_authority_ids()[1].clone(); - setup_system(&mut virtual_overseer, &test_state).await; + setup_system(&mut virtual_overseer, &test_state).await; - // A validator connected to us - connect_peer(&mut virtual_overseer, peer.clone(), Some(validator_id)).await; + // A validator connected to us + connect_peer(&mut virtual_overseer, peer.clone(), Some(validator_id)).await; - // Connect the second validator - connect_peer(&mut virtual_overseer, peer2.clone(), Some(validator_id2)).await; + // Connect the second validator + connect_peer(&mut virtual_overseer, peer2.clone(), Some(validator_id2)).await; - expect_declare_msg(&mut virtual_overseer, &test_state, &peer).await; - expect_declare_msg(&mut virtual_overseer, &test_state, &peer2).await; + expect_declare_msg(&mut virtual_overseer, &test_state, &peer).await; + expect_declare_msg(&mut virtual_overseer, &test_state, &peer2).await; - distribute_collation(&mut virtual_overseer, &test_state, true).await; + distribute_collation(&mut virtual_overseer, &test_state, true).await; - let old_relay_parent = test_state.relay_parent; + let old_relay_parent = test_state.relay_parent; - // Advance to a new round, while informing the subsystem that the old and the new relay parent are active. - test_state.advance_to_new_round(&mut virtual_overseer, true).await; + // Advance to a new round, while informing the subsystem that the old and the new relay parent are active. + test_state.advance_to_new_round(&mut virtual_overseer, true).await; - distribute_collation(&mut virtual_overseer, &test_state, true).await; + distribute_collation(&mut virtual_overseer, &test_state, true).await; - send_peer_view_change(&mut virtual_overseer, &peer, vec![old_relay_parent]).await; - expect_advertise_collation_msg(&mut virtual_overseer, &peer, old_relay_parent).await; + send_peer_view_change(&mut virtual_overseer, &peer, vec![old_relay_parent]).await; + expect_advertise_collation_msg(&mut virtual_overseer, &peer, old_relay_parent).await; - send_peer_view_change(&mut virtual_overseer, &peer2, vec![test_state.relay_parent]).await; + send_peer_view_change(&mut virtual_overseer, &peer2, vec![test_state.relay_parent]).await; - expect_advertise_collation_msg(&mut virtual_overseer, &peer2, test_state.relay_parent).await; - virtual_overseer - }) + expect_advertise_collation_msg(&mut virtual_overseer, &peer2, test_state.relay_parent).await; + virtual_overseer + }) } #[test] fn validator_reconnect_does_not_advertise_a_second_time() { - let test_state = TestState::default(); - let local_peer_id = test_state.local_peer_id.clone(); - let collator_pair = test_state.collator_pair.clone(); + let test_state = TestState::default(); + let local_peer_id = test_state.local_peer_id.clone(); + let collator_pair = test_state.collator_pair.clone(); - test_harness(local_peer_id, collator_pair, |test_harness| async move { - let mut virtual_overseer = test_harness.virtual_overseer; + test_harness(local_peer_id, collator_pair, |test_harness| async move { + let mut virtual_overseer = test_harness.virtual_overseer; - let peer = test_state.current_group_validator_peer_ids()[0].clone(); - let validator_id = test_state.current_group_validator_authority_ids()[0].clone(); + let peer = test_state.current_group_validator_peer_ids()[0].clone(); + let validator_id = test_state.current_group_validator_authority_ids()[0].clone(); - setup_system(&mut virtual_overseer, &test_state).await; + setup_system(&mut virtual_overseer, &test_state).await; - // A validator connected to us - connect_peer(&mut virtual_overseer, peer.clone(), Some(validator_id.clone())).await; - expect_declare_msg(&mut virtual_overseer, &test_state, &peer).await; + // A validator connected to us + connect_peer(&mut virtual_overseer, peer.clone(), Some(validator_id.clone())).await; + expect_declare_msg(&mut virtual_overseer, &test_state, &peer).await; - distribute_collation(&mut virtual_overseer, &test_state, true).await; + distribute_collation(&mut virtual_overseer, &test_state, true).await; - send_peer_view_change(&mut virtual_overseer, &peer, vec![test_state.relay_parent]).await; - expect_advertise_collation_msg(&mut virtual_overseer, &peer, test_state.relay_parent).await; + send_peer_view_change(&mut virtual_overseer, &peer, vec![test_state.relay_parent]).await; + expect_advertise_collation_msg(&mut virtual_overseer, &peer, test_state.relay_parent).await; - // Disconnect and reconnect directly - disconnect_peer(&mut virtual_overseer, peer.clone()).await; - connect_peer(&mut virtual_overseer, peer.clone(), Some(validator_id)).await; - expect_declare_msg(&mut virtual_overseer, &test_state, &peer).await; + // Disconnect and reconnect directly + disconnect_peer(&mut virtual_overseer, peer.clone()).await; + connect_peer(&mut virtual_overseer, peer.clone(), Some(validator_id)).await; + expect_declare_msg(&mut virtual_overseer, &test_state, &peer).await; - send_peer_view_change(&mut virtual_overseer, &peer, vec![test_state.relay_parent]).await; + send_peer_view_change(&mut virtual_overseer, &peer, vec![test_state.relay_parent]).await; - assert!(overseer_recv_with_timeout(&mut virtual_overseer, TIMEOUT).await.is_none()); - virtual_overseer - }) + assert!(overseer_recv_with_timeout(&mut virtual_overseer, TIMEOUT).await.is_none()); + virtual_overseer + }) } #[test] fn collators_reject_declare_messages() { - let test_state = TestState::default(); - let local_peer_id = test_state.local_peer_id.clone(); - let collator_pair = test_state.collator_pair.clone(); - let collator_pair2 = CollatorPair::generate().0; - - test_harness(local_peer_id, collator_pair, |test_harness| async move { - let mut virtual_overseer = test_harness.virtual_overseer; - - let peer = test_state.current_group_validator_peer_ids()[0].clone(); - let validator_id = test_state.current_group_validator_authority_ids()[0].clone(); - - setup_system(&mut virtual_overseer, &test_state).await; - - // A validator connected to us - connect_peer(&mut virtual_overseer, peer.clone(), Some(validator_id)).await; - expect_declare_msg(&mut virtual_overseer, &test_state, &peer).await; - - overseer_send( - &mut virtual_overseer, - CollatorProtocolMessage::NetworkBridgeUpdateV1( - NetworkBridgeEvent::PeerMessage( - peer.clone(), - protocol_v1::CollatorProtocolMessage::Declare( - collator_pair2.public(), - ParaId::from(5), - collator_pair2.sign(b"garbage"), - ), - ) - ) - ).await; - - assert_matches!( - overseer_recv(&mut virtual_overseer).await, - AllMessages::NetworkBridge(NetworkBridgeMessage::DisconnectPeer( - p, - PeerSet::Collation, - )) if p == peer - ); - virtual_overseer - }) + let test_state = TestState::default(); + let local_peer_id = test_state.local_peer_id.clone(); + let collator_pair = test_state.collator_pair.clone(); + let collator_pair2 = CollatorPair::generate().0; + + test_harness(local_peer_id, collator_pair, |test_harness| async move { + let mut virtual_overseer = test_harness.virtual_overseer; + + let peer = test_state.current_group_validator_peer_ids()[0].clone(); + let validator_id = test_state.current_group_validator_authority_ids()[0].clone(); + + setup_system(&mut virtual_overseer, &test_state).await; + + // A validator connected to us + connect_peer(&mut virtual_overseer, peer.clone(), Some(validator_id)).await; + expect_declare_msg(&mut virtual_overseer, &test_state, &peer).await; + + overseer_send( + &mut virtual_overseer, + CollatorProtocolMessage::NetworkBridgeUpdateV1( + NetworkBridgeEvent::PeerMessage( + peer.clone(), + protocol_v1::CollatorProtocolMessage::Declare( + collator_pair2.public(), + ParaId::from(5), + collator_pair2.sign(b"garbage"), + ), + ) + ) + ).await; + + assert_matches!( + overseer_recv(&mut virtual_overseer).await, + AllMessages::NetworkBridge(NetworkBridgeMessage::DisconnectPeer( + p, + PeerSet::Collation, + )) if p == peer + ); + virtual_overseer + }) } diff --git a/parachain/src/primitives.rs b/parachain/src/primitives.rs index ba89ee22e3fb..bf6d45ad01c8 100644 --- a/parachain/src/primitives.rs +++ b/parachain/src/primitives.rs @@ -103,7 +103,7 @@ impl From<[u8; 32]> for ValidationCodeHash { impl sp_std::fmt::LowerHex for ValidationCodeHash { fn fmt(&self, f: &mut sp_std::fmt::Formatter<'_>) -> sp_std::fmt::Result { - sp_std::fmt::LowerHex::fmt(&self.0, f) + sp_std::fmt::LowerHex::fmt(&self.0, f) } } diff --git a/runtime/parachains/src/scheduler.rs b/runtime/parachains/src/scheduler.rs index b105ce2de4fa..32c7e169e735 100644 --- a/runtime/parachains/src/scheduler.rs +++ b/runtime/parachains/src/scheduler.rs @@ -569,7 +569,7 @@ impl Module { { Ok(i) => i, Err(_) => 0, // can only happen if rotations occur only once every u32::max(), - // so functionally no difference in behavior. + // so functionally no difference in behavior. }; let group_idx = (core.0 as usize + rotations_since_session_start as usize) % validator_groups.len();