diff --git a/polkadot/node/collation-generation/src/lib.rs b/polkadot/node/collation-generation/src/lib.rs index 466cb2bed7..e1893b6fb1 100644 --- a/polkadot/node/collation-generation/src/lib.rs +++ b/polkadot/node/collation-generation/src/lib.rs @@ -508,361 +508,4 @@ impl metrics::Metrics for Metrics { } #[cfg(test)] -mod tests { - mod handle_new_activations { - use super::super::*; - use futures::{ - lock::Mutex, - task::{Context as FuturesContext, Poll}, - Future, - }; - use polkadot_node_primitives::{Collation, CollationResult, BlockData, PoV, POV_BOMB_LIMIT}; - use polkadot_node_subsystem::messages::{ - AllMessages, RuntimeApiMessage, RuntimeApiRequest, - }; - use polkadot_node_subsystem_test_helpers::{ - subsystem_test_harness, TestSubsystemContextHandle, - }; - use polkadot_primitives::v1::{ - CollatorPair, Id as ParaId, PersistedValidationData, ScheduledCore, ValidationCode, - }; - use std::pin::Pin; - - fn test_collation() -> Collation { - Collation { - upward_messages: Default::default(), - horizontal_messages: Default::default(), - new_validation_code: Default::default(), - head_data: Default::default(), - proof_of_validity: PoV { - block_data: BlockData(Vec::new()), - }, - processed_downward_messages: Default::default(), - hrmp_watermark: Default::default(), - } - } - - fn test_collation_compressed() -> Collation { - let mut collation = test_collation(); - let compressed = PoV { - block_data: BlockData(sp_maybe_compressed_blob::compress( - &collation.proof_of_validity.block_data.0, - POV_BOMB_LIMIT, - ).unwrap()) - }; - collation.proof_of_validity = compressed; - collation - } - - fn test_validation_data() -> PersistedValidationData { - let mut persisted_validation_data: PersistedValidationData = Default::default(); - persisted_validation_data.max_pov_size = 1024; - persisted_validation_data - } - - // Box + Unpin + Send - struct TestCollator; - - impl Future for TestCollator { - type Output = Option; - - fn poll(self: Pin<&mut Self>, _cx: &mut FuturesContext) -> Poll { - Poll::Ready(Some(CollationResult { collation: test_collation(), result_sender: None })) - } - } - - impl Unpin for TestCollator {} - - fn test_config>(para_id: Id) -> Arc { - Arc::new(CollationGenerationConfig { - key: CollatorPair::generate().0, - collator: Box::new(|_: Hash, _vd: &PersistedValidationData| { - TestCollator.boxed() - }), - para_id: para_id.into(), - }) - } - - fn scheduled_core_for>(para_id: Id) -> ScheduledCore { - ScheduledCore { - para_id: para_id.into(), - collator: None, - } - } - - #[test] - fn requests_availability_per_relay_parent() { - let activated_hashes: Vec = vec![ - [1; 32].into(), - [4; 32].into(), - [9; 32].into(), - [16; 32].into(), - ]; - - let requested_availability_cores = Arc::new(Mutex::new(Vec::new())); - - let overseer_requested_availability_cores = requested_availability_cores.clone(); - let overseer = |mut handle: TestSubsystemContextHandle| async move { - loop { - match handle.try_recv().await { - None => break, - Some(AllMessages::RuntimeApi(RuntimeApiMessage::Request(hash, RuntimeApiRequest::AvailabilityCores(tx)))) => { - overseer_requested_availability_cores.lock().await.push(hash); - tx.send(Ok(vec![])).unwrap(); - } - Some(AllMessages::RuntimeApi(RuntimeApiMessage::Request(_hash, RuntimeApiRequest::Validators(tx)))) => { - tx.send(Ok(vec![Default::default(); 3])).unwrap(); - } - Some(msg) => panic!("didn't expect any other overseer requests given no availability cores; got {:?}", msg), - } - } - }; - - let (tx, _rx) = mpsc::channel(0); - - let subsystem_activated_hashes = activated_hashes.clone(); - subsystem_test_harness(overseer, |mut ctx| async move { - handle_new_activations( - test_config(123u32), - subsystem_activated_hashes, - &mut ctx, - Metrics(None), - &tx, - ) - .await - .unwrap(); - }); - - let mut requested_availability_cores = Arc::try_unwrap(requested_availability_cores) - .expect("overseer should have shut down by now") - .into_inner(); - requested_availability_cores.sort(); - - assert_eq!(requested_availability_cores, activated_hashes); - } - - #[test] - fn requests_validation_data_for_scheduled_matches() { - let activated_hashes: Vec = vec![ - Hash::repeat_byte(1), - Hash::repeat_byte(4), - Hash::repeat_byte(9), - Hash::repeat_byte(16), - ]; - - let requested_validation_data = Arc::new(Mutex::new(Vec::new())); - - let overseer_requested_validation_data = requested_validation_data.clone(); - let overseer = |mut handle: TestSubsystemContextHandle| async move { - loop { - match handle.try_recv().await { - None => break, - Some(AllMessages::RuntimeApi(RuntimeApiMessage::Request( - hash, - RuntimeApiRequest::AvailabilityCores(tx), - ))) => { - tx.send(Ok(vec![ - CoreState::Free, - // this is weird, see explanation below - CoreState::Scheduled(scheduled_core_for( - (hash.as_fixed_bytes()[0] * 4) as u32, - )), - CoreState::Scheduled(scheduled_core_for( - (hash.as_fixed_bytes()[0] * 5) as u32, - )), - ])) - .unwrap(); - } - Some(AllMessages::RuntimeApi(RuntimeApiMessage::Request( - hash, - RuntimeApiRequest::PersistedValidationData( - _para_id, - _occupied_core_assumption, - tx, - ), - ))) => { - overseer_requested_validation_data - .lock() - .await - .push(hash); - tx.send(Ok(Default::default())).unwrap(); - } - Some(AllMessages::RuntimeApi(RuntimeApiMessage::Request( - _hash, - RuntimeApiRequest::Validators(tx), - ))) => { - tx.send(Ok(vec![Default::default(); 3])).unwrap(); - } - Some(msg) => { - panic!("didn't expect any other overseer requests; got {:?}", msg) - } - } - } - }; - - let (tx, _rx) = mpsc::channel(0); - - subsystem_test_harness(overseer, |mut ctx| async move { - handle_new_activations(test_config(16), activated_hashes, &mut ctx, Metrics(None), &tx) - .await - .unwrap(); - }); - - let requested_validation_data = Arc::try_unwrap(requested_validation_data) - .expect("overseer should have shut down by now") - .into_inner(); - - // the only activated hash should be from the 4 hash: - // each activated hash generates two scheduled cores: one with its value * 4, one with its value * 5 - // given that the test configuration has a para_id of 16, there's only one way to get that value: with the 4 - // hash. - assert_eq!(requested_validation_data, vec![[4; 32].into()]); - } - - #[test] - fn sends_distribute_collation_message() { - let activated_hashes: Vec = vec![ - Hash::repeat_byte(1), - Hash::repeat_byte(4), - Hash::repeat_byte(9), - Hash::repeat_byte(16), - ]; - - let overseer = |mut handle: TestSubsystemContextHandle| async move { - loop { - match handle.try_recv().await { - None => break, - Some(AllMessages::RuntimeApi(RuntimeApiMessage::Request( - hash, - RuntimeApiRequest::AvailabilityCores(tx), - ))) => { - tx.send(Ok(vec![ - CoreState::Free, - // this is weird, see explanation below - CoreState::Scheduled(scheduled_core_for( - (hash.as_fixed_bytes()[0] * 4) as u32, - )), - CoreState::Scheduled(scheduled_core_for( - (hash.as_fixed_bytes()[0] * 5) as u32, - )), - ])) - .unwrap(); - } - Some(AllMessages::RuntimeApi(RuntimeApiMessage::Request( - _hash, - RuntimeApiRequest::PersistedValidationData( - _para_id, - _occupied_core_assumption, - tx, - ), - ))) => { - tx.send(Ok(Some(test_validation_data()))).unwrap(); - } - Some(AllMessages::RuntimeApi(RuntimeApiMessage::Request( - _hash, - RuntimeApiRequest::Validators(tx), - ))) => { - tx.send(Ok(vec![Default::default(); 3])).unwrap(); - } - Some(AllMessages::RuntimeApi(RuntimeApiMessage::Request( - _hash, - RuntimeApiRequest::ValidationCode( - _para_id, - OccupiedCoreAssumption::Free, - tx, - ), - ))) => { - tx.send(Ok(Some(ValidationCode(vec![1, 2, 3])))).unwrap(); - } - Some(msg) => { - panic!("didn't expect any other overseer requests; got {:?}", msg) - } - } - } - }; - - let config = test_config(16); - let subsystem_config = config.clone(); - - let (tx, rx) = mpsc::channel(0); - - // empty vec doesn't allocate on the heap, so it's ok we throw it away - let sent_messages = Arc::new(Mutex::new(Vec::new())); - let subsystem_sent_messages = sent_messages.clone(); - subsystem_test_harness(overseer, |mut ctx| async move { - handle_new_activations(subsystem_config, activated_hashes, &mut ctx, Metrics(None), &tx) - .await - .unwrap(); - - std::mem::drop(tx); - - // collect all sent messages - *subsystem_sent_messages.lock().await = rx.collect().await; - }); - - let sent_messages = Arc::try_unwrap(sent_messages) - .expect("subsystem should have shut down by now") - .into_inner(); - - // we expect a single message to be sent, containing a candidate receipt. - // we don't care too much about the commitments_hash right now, but let's ensure that we've calculated the - // correct descriptor - let expect_pov_hash = test_collation_compressed().proof_of_validity.hash(); - let expect_validation_data_hash = test_validation_data().hash(); - let expect_relay_parent = Hash::repeat_byte(4); - let expect_validation_code_hash = ValidationCode(vec![1, 2, 3]).hash(); - let expect_payload = collator_signature_payload( - &expect_relay_parent, - &config.para_id, - &expect_validation_data_hash, - &expect_pov_hash, - &expect_validation_code_hash, - ); - let expect_descriptor = CandidateDescriptor { - signature: config.key.sign(&expect_payload), - para_id: config.para_id, - relay_parent: expect_relay_parent, - collator: config.key.public(), - persisted_validation_data_hash: expect_validation_data_hash, - pov_hash: expect_pov_hash, - erasure_root: Default::default(), // this isn't something we're checking right now - para_head: test_collation().head_data.hash(), - validation_code_hash: expect_validation_code_hash, - }; - - assert_eq!(sent_messages.len(), 1); - match &sent_messages[0] { - AllMessages::CollatorProtocol(CollatorProtocolMessage::DistributeCollation( - CandidateReceipt { descriptor, .. }, - _pov, - .. - )) => { - // signature generation is non-deterministic, so we can't just assert that the - // expected descriptor is correct. What we can do is validate that the produced - // descriptor has a valid signature, then just copy in the generated signature - // and check the rest of the fields for equality. - assert!(CollatorPair::verify( - &descriptor.signature, - &collator_signature_payload( - &descriptor.relay_parent, - &descriptor.para_id, - &descriptor.persisted_validation_data_hash, - &descriptor.pov_hash, - &descriptor.validation_code_hash, - ) - .as_ref(), - &descriptor.collator, - )); - let expect_descriptor = { - let mut expect_descriptor = expect_descriptor; - expect_descriptor.signature = descriptor.signature.clone(); - expect_descriptor.erasure_root = descriptor.erasure_root.clone(); - expect_descriptor - }; - assert_eq!(descriptor, &expect_descriptor); - } - _ => panic!("received wrong message type"), - } - } - } -} +mod tests; diff --git a/polkadot/node/collation-generation/src/tests.rs b/polkadot/node/collation-generation/src/tests.rs new file mode 100644 index 0000000000..7f13205c52 --- /dev/null +++ b/polkadot/node/collation-generation/src/tests.rs @@ -0,0 +1,372 @@ +// Copyright 2020 Parity Technologies (UK) Ltd. +// This file is part of Polkadot. + +// Polkadot is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Polkadot is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Polkadot. If not, see . + +mod handle_new_activations { + use super::super::*; + use futures::{ + lock::Mutex, + task::{Context as FuturesContext, Poll}, + Future, + }; + use polkadot_node_primitives::{Collation, CollationResult, BlockData, PoV, POV_BOMB_LIMIT}; + use polkadot_node_subsystem::messages::{ + AllMessages, RuntimeApiMessage, RuntimeApiRequest, + }; + use polkadot_node_subsystem_test_helpers::{ + subsystem_test_harness, TestSubsystemContextHandle, + }; + use polkadot_primitives::v1::{ + CollatorPair, Id as ParaId, PersistedValidationData, ScheduledCore, ValidationCode, + }; + use std::pin::Pin; + + fn test_collation() -> Collation { + Collation { + upward_messages: Default::default(), + horizontal_messages: Default::default(), + new_validation_code: Default::default(), + head_data: Default::default(), + proof_of_validity: PoV { + block_data: BlockData(Vec::new()), + }, + processed_downward_messages: Default::default(), + hrmp_watermark: Default::default(), + } + } + + fn test_collation_compressed() -> Collation { + let mut collation = test_collation(); + let compressed = PoV { + block_data: BlockData(sp_maybe_compressed_blob::compress( + &collation.proof_of_validity.block_data.0, + POV_BOMB_LIMIT, + ).unwrap()) + }; + collation.proof_of_validity = compressed; + collation + } + + fn test_validation_data() -> PersistedValidationData { + let mut persisted_validation_data: PersistedValidationData = Default::default(); + persisted_validation_data.max_pov_size = 1024; + persisted_validation_data + } + + // Box + Unpin + Send + struct TestCollator; + + impl Future for TestCollator { + type Output = Option; + + fn poll(self: Pin<&mut Self>, _cx: &mut FuturesContext) -> Poll { + Poll::Ready(Some(CollationResult { collation: test_collation(), result_sender: None })) + } + } + + impl Unpin for TestCollator {} + + fn test_config>(para_id: Id) -> Arc { + Arc::new(CollationGenerationConfig { + key: CollatorPair::generate().0, + collator: Box::new(|_: Hash, _vd: &PersistedValidationData| { + TestCollator.boxed() + }), + para_id: para_id.into(), + }) + } + + fn scheduled_core_for>(para_id: Id) -> ScheduledCore { + ScheduledCore { + para_id: para_id.into(), + collator: None, + } + } + + #[test] + fn requests_availability_per_relay_parent() { + let activated_hashes: Vec = vec![ + [1; 32].into(), + [4; 32].into(), + [9; 32].into(), + [16; 32].into(), + ]; + + let requested_availability_cores = Arc::new(Mutex::new(Vec::new())); + + let overseer_requested_availability_cores = requested_availability_cores.clone(); + let overseer = |mut handle: TestSubsystemContextHandle| async move { + loop { + match handle.try_recv().await { + None => break, + Some(AllMessages::RuntimeApi(RuntimeApiMessage::Request(hash, RuntimeApiRequest::AvailabilityCores(tx)))) => { + overseer_requested_availability_cores.lock().await.push(hash); + tx.send(Ok(vec![])).unwrap(); + } + Some(AllMessages::RuntimeApi(RuntimeApiMessage::Request(_hash, RuntimeApiRequest::Validators(tx)))) => { + tx.send(Ok(vec![Default::default(); 3])).unwrap(); + } + Some(msg) => panic!("didn't expect any other overseer requests given no availability cores; got {:?}", msg), + } + } + }; + + let (tx, _rx) = mpsc::channel(0); + + let subsystem_activated_hashes = activated_hashes.clone(); + subsystem_test_harness(overseer, |mut ctx| async move { + handle_new_activations( + test_config(123u32), + subsystem_activated_hashes, + &mut ctx, + Metrics(None), + &tx, + ) + .await + .unwrap(); + }); + + let mut requested_availability_cores = Arc::try_unwrap(requested_availability_cores) + .expect("overseer should have shut down by now") + .into_inner(); + requested_availability_cores.sort(); + + assert_eq!(requested_availability_cores, activated_hashes); + } + + #[test] + fn requests_validation_data_for_scheduled_matches() { + let activated_hashes: Vec = vec![ + Hash::repeat_byte(1), + Hash::repeat_byte(4), + Hash::repeat_byte(9), + Hash::repeat_byte(16), + ]; + + let requested_validation_data = Arc::new(Mutex::new(Vec::new())); + + let overseer_requested_validation_data = requested_validation_data.clone(); + let overseer = |mut handle: TestSubsystemContextHandle| async move { + loop { + match handle.try_recv().await { + None => break, + Some(AllMessages::RuntimeApi(RuntimeApiMessage::Request( + hash, + RuntimeApiRequest::AvailabilityCores(tx), + ))) => { + tx.send(Ok(vec![ + CoreState::Free, + // this is weird, see explanation below + CoreState::Scheduled(scheduled_core_for( + (hash.as_fixed_bytes()[0] * 4) as u32, + )), + CoreState::Scheduled(scheduled_core_for( + (hash.as_fixed_bytes()[0] * 5) as u32, + )), + ])) + .unwrap(); + } + Some(AllMessages::RuntimeApi(RuntimeApiMessage::Request( + hash, + RuntimeApiRequest::PersistedValidationData( + _para_id, + _occupied_core_assumption, + tx, + ), + ))) => { + overseer_requested_validation_data + .lock() + .await + .push(hash); + tx.send(Ok(Default::default())).unwrap(); + } + Some(AllMessages::RuntimeApi(RuntimeApiMessage::Request( + _hash, + RuntimeApiRequest::Validators(tx), + ))) => { + tx.send(Ok(vec![Default::default(); 3])).unwrap(); + } + Some(msg) => { + panic!("didn't expect any other overseer requests; got {:?}", msg) + } + } + } + }; + + let (tx, _rx) = mpsc::channel(0); + + subsystem_test_harness(overseer, |mut ctx| async move { + handle_new_activations(test_config(16), activated_hashes, &mut ctx, Metrics(None), &tx) + .await + .unwrap(); + }); + + let requested_validation_data = Arc::try_unwrap(requested_validation_data) + .expect("overseer should have shut down by now") + .into_inner(); + + // the only activated hash should be from the 4 hash: + // each activated hash generates two scheduled cores: one with its value * 4, one with its value * 5 + // given that the test configuration has a para_id of 16, there's only one way to get that value: with the 4 + // hash. + assert_eq!(requested_validation_data, vec![[4; 32].into()]); + } + + #[test] + fn sends_distribute_collation_message() { + let activated_hashes: Vec = vec![ + Hash::repeat_byte(1), + Hash::repeat_byte(4), + Hash::repeat_byte(9), + Hash::repeat_byte(16), + ]; + + let overseer = |mut handle: TestSubsystemContextHandle| async move { + loop { + match handle.try_recv().await { + None => break, + Some(AllMessages::RuntimeApi(RuntimeApiMessage::Request( + hash, + RuntimeApiRequest::AvailabilityCores(tx), + ))) => { + tx.send(Ok(vec![ + CoreState::Free, + // this is weird, see explanation below + CoreState::Scheduled(scheduled_core_for( + (hash.as_fixed_bytes()[0] * 4) as u32, + )), + CoreState::Scheduled(scheduled_core_for( + (hash.as_fixed_bytes()[0] * 5) as u32, + )), + ])) + .unwrap(); + } + Some(AllMessages::RuntimeApi(RuntimeApiMessage::Request( + _hash, + RuntimeApiRequest::PersistedValidationData( + _para_id, + _occupied_core_assumption, + tx, + ), + ))) => { + tx.send(Ok(Some(test_validation_data()))).unwrap(); + } + Some(AllMessages::RuntimeApi(RuntimeApiMessage::Request( + _hash, + RuntimeApiRequest::Validators(tx), + ))) => { + tx.send(Ok(vec![Default::default(); 3])).unwrap(); + } + Some(AllMessages::RuntimeApi(RuntimeApiMessage::Request( + _hash, + RuntimeApiRequest::ValidationCode( + _para_id, + OccupiedCoreAssumption::Free, + tx, + ), + ))) => { + tx.send(Ok(Some(ValidationCode(vec![1, 2, 3])))).unwrap(); + } + Some(msg) => { + panic!("didn't expect any other overseer requests; got {:?}", msg) + } + } + } + }; + + let config = test_config(16); + let subsystem_config = config.clone(); + + let (tx, rx) = mpsc::channel(0); + + // empty vec doesn't allocate on the heap, so it's ok we throw it away + let sent_messages = Arc::new(Mutex::new(Vec::new())); + let subsystem_sent_messages = sent_messages.clone(); + subsystem_test_harness(overseer, |mut ctx| async move { + handle_new_activations(subsystem_config, activated_hashes, &mut ctx, Metrics(None), &tx) + .await + .unwrap(); + + std::mem::drop(tx); + + // collect all sent messages + *subsystem_sent_messages.lock().await = rx.collect().await; + }); + + let sent_messages = Arc::try_unwrap(sent_messages) + .expect("subsystem should have shut down by now") + .into_inner(); + + // we expect a single message to be sent, containing a candidate receipt. + // we don't care too much about the commitments_hash right now, but let's ensure that we've calculated the + // correct descriptor + let expect_pov_hash = test_collation_compressed().proof_of_validity.hash(); + let expect_validation_data_hash = test_validation_data().hash(); + let expect_relay_parent = Hash::repeat_byte(4); + let expect_validation_code_hash = ValidationCode(vec![1, 2, 3]).hash(); + let expect_payload = collator_signature_payload( + &expect_relay_parent, + &config.para_id, + &expect_validation_data_hash, + &expect_pov_hash, + &expect_validation_code_hash, + ); + let expect_descriptor = CandidateDescriptor { + signature: config.key.sign(&expect_payload), + para_id: config.para_id, + relay_parent: expect_relay_parent, + collator: config.key.public(), + persisted_validation_data_hash: expect_validation_data_hash, + pov_hash: expect_pov_hash, + erasure_root: Default::default(), // this isn't something we're checking right now + para_head: test_collation().head_data.hash(), + validation_code_hash: expect_validation_code_hash, + }; + + assert_eq!(sent_messages.len(), 1); + match &sent_messages[0] { + AllMessages::CollatorProtocol(CollatorProtocolMessage::DistributeCollation( + CandidateReceipt { descriptor, .. }, + _pov, + .. + )) => { + // signature generation is non-deterministic, so we can't just assert that the + // expected descriptor is correct. What we can do is validate that the produced + // descriptor has a valid signature, then just copy in the generated signature + // and check the rest of the fields for equality. + assert!(CollatorPair::verify( + &descriptor.signature, + &collator_signature_payload( + &descriptor.relay_parent, + &descriptor.para_id, + &descriptor.persisted_validation_data_hash, + &descriptor.pov_hash, + &descriptor.validation_code_hash, + ) + .as_ref(), + &descriptor.collator, + )); + let expect_descriptor = { + let mut expect_descriptor = expect_descriptor; + expect_descriptor.signature = descriptor.signature.clone(); + expect_descriptor.erasure_root = descriptor.erasure_root.clone(); + expect_descriptor + }; + assert_eq!(descriptor, &expect_descriptor); + } + _ => panic!("received wrong message type"), + } + } +} diff --git a/polkadot/node/core/runtime-api/src/lib.rs b/polkadot/node/core/runtime-api/src/lib.rs index b8927564f9..caddd0a80d 100644 --- a/polkadot/node/core/runtime-api/src/lib.rs +++ b/polkadot/node/core/runtime-api/src/lib.rs @@ -413,818 +413,4 @@ impl metrics::Metrics for Metrics { } #[cfg(test)] -mod tests { - use super::*; - - use polkadot_primitives::v1::{ - ValidatorId, ValidatorIndex, GroupRotationInfo, CoreState, PersistedValidationData, - Id as ParaId, OccupiedCoreAssumption, SessionIndex, ValidationCode, - CommittedCandidateReceipt, CandidateEvent, InboundDownwardMessage, - BlockNumber, InboundHrmpMessage, SessionInfo, AuthorityDiscoveryId, - }; - use polkadot_node_subsystem_test_helpers as test_helpers; - use sp_core::testing::TaskExecutor; - use std::{collections::{HashMap, BTreeMap}, sync::{Arc, Mutex}}; - use futures::channel::oneshot; - use polkadot_node_primitives::{ - BabeEpoch, BabeEpochConfiguration, BabeAllowedSlots, - }; - - #[derive(Default, Clone)] - struct MockRuntimeApi { - authorities: Vec, - validators: Vec, - validator_groups: Vec>, - availability_cores: Vec, - availability_cores_wait: Arc>, - validation_data: HashMap, - session_index_for_child: SessionIndex, - session_info: HashMap, - validation_code: HashMap, - historical_validation_code: HashMap>, - validation_outputs_results: HashMap, - candidate_pending_availability: HashMap, - candidate_events: Vec, - dmq_contents: HashMap>, - hrmp_channels: HashMap>>, - babe_epoch: Option, - } - - impl ProvideRuntimeApi for MockRuntimeApi { - type Api = Self; - - fn runtime_api<'a>(&'a self) -> sp_api::ApiRef<'a, Self::Api> { - self.clone().into() - } - } - - sp_api::mock_impl_runtime_apis! { - impl ParachainHost for MockRuntimeApi { - fn validators(&self) -> Vec { - self.validators.clone() - } - - fn validator_groups(&self) -> (Vec>, GroupRotationInfo) { - ( - self.validator_groups.clone(), - GroupRotationInfo { - session_start_block: 1, - group_rotation_frequency: 100, - now: 10, - }, - ) - } - - fn availability_cores(&self) -> Vec { - let _ = self.availability_cores_wait.lock().unwrap(); - self.availability_cores.clone() - } - - fn persisted_validation_data( - &self, - para: ParaId, - _assumption: OccupiedCoreAssumption, - ) -> Option { - self.validation_data.get(¶).cloned() - } - - fn check_validation_outputs( - &self, - para_id: ParaId, - _commitments: polkadot_primitives::v1::CandidateCommitments, - ) -> bool { - self.validation_outputs_results - .get(¶_id) - .cloned() - .expect( - "`check_validation_outputs` called but the expected result hasn't been supplied" - ) - } - - fn session_index_for_child(&self) -> SessionIndex { - self.session_index_for_child.clone() - } - - fn session_info(&self, index: SessionIndex) -> Option { - self.session_info.get(&index).cloned() - } - - fn validation_code( - &self, - para: ParaId, - _assumption: OccupiedCoreAssumption, - ) -> Option { - self.validation_code.get(¶).map(|c| c.clone()) - } - - fn historical_validation_code( - &self, - para: ParaId, - at: BlockNumber, - ) -> Option { - self.historical_validation_code.get(¶).and_then(|h_code| { - h_code.iter() - .take_while(|(changed_at, _)| changed_at <= &at) - .last() - .map(|(_, code)| code.clone()) - }) - } - - fn candidate_pending_availability( - &self, - para: ParaId, - ) -> Option { - self.candidate_pending_availability.get(¶).map(|c| c.clone()) - } - - fn candidate_events(&self) -> Vec { - self.candidate_events.clone() - } - - fn dmq_contents( - &self, - recipient: ParaId, - ) -> Vec { - self.dmq_contents.get(&recipient).map(|q| q.clone()).unwrap_or_default() - } - - fn inbound_hrmp_channels_contents( - &self, - recipient: ParaId - ) -> BTreeMap> { - self.hrmp_channels.get(&recipient).map(|q| q.clone()).unwrap_or_default() - } - - fn validation_code_by_hash( - &self, - _hash: Hash, - ) -> Option { - unreachable!("not used in tests"); - } - } - - impl BabeApi for MockRuntimeApi { - fn configuration(&self) -> sp_consensus_babe::BabeGenesisConfiguration { - unimplemented!() - } - - fn current_epoch_start(&self) -> sp_consensus_babe::Slot { - self.babe_epoch.as_ref().unwrap().start_slot - } - - fn current_epoch(&self) -> BabeEpoch { - self.babe_epoch.as_ref().unwrap().clone() - } - - fn next_epoch(&self) -> BabeEpoch { - unimplemented!() - } - - fn generate_key_ownership_proof( - _slot: sp_consensus_babe::Slot, - _authority_id: sp_consensus_babe::AuthorityId, - ) -> Option { - None - } - - fn submit_report_equivocation_unsigned_extrinsic( - _equivocation_proof: sp_consensus_babe::EquivocationProof, - _key_owner_proof: sp_consensus_babe::OpaqueKeyOwnershipProof, - ) -> Option<()> { - None - } - } - - impl AuthorityDiscoveryApi for MockRuntimeApi { - fn authorities(&self) -> Vec { - self.authorities.clone() - } - } - } - - #[test] - fn requests_authorities() { - let (ctx, mut ctx_handle) = test_helpers::make_subsystem_context(TaskExecutor::new()); - let runtime_api = Arc::new(MockRuntimeApi::default()); - let relay_parent = [1; 32].into(); - let spawner = sp_core::testing::TaskExecutor::new(); - - let subsystem = RuntimeApiSubsystem::new(runtime_api.clone(), Metrics(None), spawner); - let subsystem_task = run(ctx, subsystem).map(|x| x.unwrap()); - let test_task = async move { - let (tx, rx) = oneshot::channel(); - - ctx_handle.send(FromOverseer::Communication { - msg: RuntimeApiMessage::Request(relay_parent, Request::Authorities(tx)) - }).await; - - assert_eq!(rx.await.unwrap().unwrap(), runtime_api.authorities); - - ctx_handle.send(FromOverseer::Signal(OverseerSignal::Conclude)).await; - }; - - futures::executor::block_on(future::join(subsystem_task, test_task)); - } - - #[test] - fn requests_validators() { - let (ctx, mut ctx_handle) = test_helpers::make_subsystem_context(TaskExecutor::new()); - let runtime_api = Arc::new(MockRuntimeApi::default()); - let relay_parent = [1; 32].into(); - let spawner = sp_core::testing::TaskExecutor::new(); - - let subsystem = RuntimeApiSubsystem::new(runtime_api.clone(), Metrics(None), spawner); - let subsystem_task = run(ctx, subsystem).map(|x| x.unwrap()); - let test_task = async move { - let (tx, rx) = oneshot::channel(); - - ctx_handle.send(FromOverseer::Communication { - msg: RuntimeApiMessage::Request(relay_parent, Request::Validators(tx)) - }).await; - - assert_eq!(rx.await.unwrap().unwrap(), runtime_api.validators); - - ctx_handle.send(FromOverseer::Signal(OverseerSignal::Conclude)).await; - }; - - futures::executor::block_on(future::join(subsystem_task, test_task)); - } - - #[test] - fn requests_validator_groups() { - let (ctx, mut ctx_handle) = test_helpers::make_subsystem_context(TaskExecutor::new()); - let runtime_api = Arc::new(MockRuntimeApi::default()); - let relay_parent = [1; 32].into(); - let spawner = sp_core::testing::TaskExecutor::new(); - - let subsystem = RuntimeApiSubsystem::new(runtime_api.clone(), Metrics(None), spawner); - let subsystem_task = run(ctx, subsystem).map(|x| x.unwrap()); - let test_task = async move { - let (tx, rx) = oneshot::channel(); - - ctx_handle.send(FromOverseer::Communication { - msg: RuntimeApiMessage::Request(relay_parent, Request::ValidatorGroups(tx)) - }).await; - - assert_eq!(rx.await.unwrap().unwrap().0, runtime_api.validator_groups); - - ctx_handle.send(FromOverseer::Signal(OverseerSignal::Conclude)).await; - }; - - futures::executor::block_on(future::join(subsystem_task, test_task)); - } - - #[test] - fn requests_availability_cores() { - let (ctx, mut ctx_handle) = test_helpers::make_subsystem_context(TaskExecutor::new()); - let runtime_api = Arc::new(MockRuntimeApi::default()); - let relay_parent = [1; 32].into(); - let spawner = sp_core::testing::TaskExecutor::new(); - - let subsystem = RuntimeApiSubsystem::new(runtime_api.clone(), Metrics(None), spawner); - let subsystem_task = run(ctx, subsystem).map(|x| x.unwrap()); - let test_task = async move { - let (tx, rx) = oneshot::channel(); - - ctx_handle.send(FromOverseer::Communication { - msg: RuntimeApiMessage::Request(relay_parent, Request::AvailabilityCores(tx)) - }).await; - - assert_eq!(rx.await.unwrap().unwrap(), runtime_api.availability_cores); - - ctx_handle.send(FromOverseer::Signal(OverseerSignal::Conclude)).await; - }; - - futures::executor::block_on(future::join(subsystem_task, test_task)); - } - - #[test] - fn requests_persisted_validation_data() { - let (ctx, mut ctx_handle) = test_helpers::make_subsystem_context(TaskExecutor::new()); - let relay_parent = [1; 32].into(); - let para_a = 5.into(); - let para_b = 6.into(); - let spawner = sp_core::testing::TaskExecutor::new(); - - let mut runtime_api = MockRuntimeApi::default(); - runtime_api.validation_data.insert(para_a, Default::default()); - let runtime_api = Arc::new(runtime_api); - - let subsystem = RuntimeApiSubsystem::new(runtime_api.clone(), Metrics(None), spawner); - let subsystem_task = run(ctx, subsystem).map(|x| x.unwrap()); - let test_task = async move { - let (tx, rx) = oneshot::channel(); - - ctx_handle.send(FromOverseer::Communication { - msg: RuntimeApiMessage::Request( - relay_parent, - Request::PersistedValidationData(para_a, OccupiedCoreAssumption::Included, tx) - ), - }).await; - - assert_eq!(rx.await.unwrap().unwrap(), Some(Default::default())); - - let (tx, rx) = oneshot::channel(); - ctx_handle.send(FromOverseer::Communication { - msg: RuntimeApiMessage::Request( - relay_parent, - Request::PersistedValidationData(para_b, OccupiedCoreAssumption::Included, tx) - ), - }).await; - - assert_eq!(rx.await.unwrap().unwrap(), None); - - ctx_handle.send(FromOverseer::Signal(OverseerSignal::Conclude)).await; - }; - - futures::executor::block_on(future::join(subsystem_task, test_task)); - } - - #[test] - fn requests_check_validation_outputs() { - let (ctx, mut ctx_handle) = test_helpers::make_subsystem_context(TaskExecutor::new()); - let mut runtime_api = MockRuntimeApi::default(); - let relay_parent = [1; 32].into(); - let para_a = 5.into(); - let para_b = 6.into(); - let commitments = polkadot_primitives::v1::CandidateCommitments::default(); - let spawner = sp_core::testing::TaskExecutor::new(); - - runtime_api.validation_outputs_results.insert(para_a, false); - runtime_api.validation_outputs_results.insert(para_b, true); - - let runtime_api = Arc::new(runtime_api); - - let subsystem = RuntimeApiSubsystem::new(runtime_api.clone(), Metrics(None), spawner); - let subsystem_task = run(ctx, subsystem).map(|x| x.unwrap()); - let test_task = async move { - let (tx, rx) = oneshot::channel(); - - ctx_handle.send(FromOverseer::Communication { - msg: RuntimeApiMessage::Request( - relay_parent, - Request::CheckValidationOutputs( - para_a, - commitments.clone(), - tx, - ), - ) - }).await; - assert_eq!( - rx.await.unwrap().unwrap(), - runtime_api.validation_outputs_results[¶_a], - ); - - let (tx, rx) = oneshot::channel(); - ctx_handle.send(FromOverseer::Communication { - msg: RuntimeApiMessage::Request( - relay_parent, - Request::CheckValidationOutputs( - para_b, - commitments, - tx, - ), - ) - }).await; - assert_eq!( - rx.await.unwrap().unwrap(), - runtime_api.validation_outputs_results[¶_b], - ); - - ctx_handle.send(FromOverseer::Signal(OverseerSignal::Conclude)).await; - }; - - futures::executor::block_on(future::join(subsystem_task, test_task)); - } - - #[test] - fn requests_session_index_for_child() { - let (ctx, mut ctx_handle) = test_helpers::make_subsystem_context(TaskExecutor::new()); - let runtime_api = Arc::new(MockRuntimeApi::default()); - let relay_parent = [1; 32].into(); - let spawner = sp_core::testing::TaskExecutor::new(); - - let subsystem = RuntimeApiSubsystem::new(runtime_api.clone(), Metrics(None), spawner); - let subsystem_task = run(ctx, subsystem).map(|x| x.unwrap()); - let test_task = async move { - let (tx, rx) = oneshot::channel(); - - ctx_handle.send(FromOverseer::Communication { - msg: RuntimeApiMessage::Request(relay_parent, Request::SessionIndexForChild(tx)) - }).await; - - assert_eq!(rx.await.unwrap().unwrap(), runtime_api.session_index_for_child); - - ctx_handle.send(FromOverseer::Signal(OverseerSignal::Conclude)).await; - }; - - futures::executor::block_on(future::join(subsystem_task, test_task)); - } - - #[test] - fn requests_session_info() { - let (ctx, mut ctx_handle) = test_helpers::make_subsystem_context(TaskExecutor::new()); - let mut runtime_api = MockRuntimeApi::default(); - let session_index = 1; - runtime_api.session_info.insert(session_index, Default::default()); - let runtime_api = Arc::new(runtime_api); - let spawner = sp_core::testing::TaskExecutor::new(); - - let relay_parent = [1; 32].into(); - - let subsystem = RuntimeApiSubsystem::new(runtime_api.clone(), Metrics(None), spawner); - let subsystem_task = run(ctx, subsystem).map(|x| x.unwrap()); - let test_task = async move { - let (tx, rx) = oneshot::channel(); - - ctx_handle.send(FromOverseer::Communication { - msg: RuntimeApiMessage::Request(relay_parent, Request::SessionInfo(session_index, tx)) - }).await; - - assert_eq!(rx.await.unwrap().unwrap(), Some(Default::default())); - - ctx_handle.send(FromOverseer::Signal(OverseerSignal::Conclude)).await; - }; - - futures::executor::block_on(future::join(subsystem_task, test_task)); - } - - #[test] - fn requests_validation_code() { - let (ctx, mut ctx_handle) = test_helpers::make_subsystem_context(TaskExecutor::new()); - - let relay_parent = [1; 32].into(); - let para_a = 5.into(); - let para_b = 6.into(); - let spawner = sp_core::testing::TaskExecutor::new(); - - let mut runtime_api = MockRuntimeApi::default(); - runtime_api.validation_code.insert(para_a, Default::default()); - let runtime_api = Arc::new(runtime_api); - - let subsystem = RuntimeApiSubsystem::new(runtime_api.clone(), Metrics(None), spawner); - let subsystem_task = run(ctx, subsystem).map(|x| x.unwrap()); - let test_task = async move { - let (tx, rx) = oneshot::channel(); - - ctx_handle.send(FromOverseer::Communication { - msg: RuntimeApiMessage::Request( - relay_parent, - Request::ValidationCode(para_a, OccupiedCoreAssumption::Included, tx) - ), - }).await; - - assert_eq!(rx.await.unwrap().unwrap(), Some(Default::default())); - - let (tx, rx) = oneshot::channel(); - ctx_handle.send(FromOverseer::Communication { - msg: RuntimeApiMessage::Request( - relay_parent, - Request::ValidationCode(para_b, OccupiedCoreAssumption::Included, tx) - ), - }).await; - - assert_eq!(rx.await.unwrap().unwrap(), None); - - ctx_handle.send(FromOverseer::Signal(OverseerSignal::Conclude)).await; - }; - - futures::executor::block_on(future::join(subsystem_task, test_task)); - } - - #[test] - fn requests_candidate_pending_availability() { - let (ctx, mut ctx_handle) = test_helpers::make_subsystem_context(TaskExecutor::new()); - let relay_parent = [1; 32].into(); - let para_a = 5.into(); - let para_b = 6.into(); - let spawner = sp_core::testing::TaskExecutor::new(); - - let mut runtime_api = MockRuntimeApi::default(); - runtime_api.candidate_pending_availability.insert(para_a, Default::default()); - let runtime_api = Arc::new(runtime_api); - - let subsystem = RuntimeApiSubsystem::new(runtime_api.clone(), Metrics(None), spawner); - let subsystem_task = run(ctx, subsystem).map(|x| x.unwrap()); - let test_task = async move { - let (tx, rx) = oneshot::channel(); - - ctx_handle.send(FromOverseer::Communication { - msg: RuntimeApiMessage::Request( - relay_parent, - Request::CandidatePendingAvailability(para_a, tx), - ) - }).await; - - assert_eq!(rx.await.unwrap().unwrap(), Some(Default::default())); - - let (tx, rx) = oneshot::channel(); - - ctx_handle.send(FromOverseer::Communication { - msg: RuntimeApiMessage::Request( - relay_parent, - Request::CandidatePendingAvailability(para_b, tx), - ) - }).await; - - assert_eq!(rx.await.unwrap().unwrap(), None); - - ctx_handle.send(FromOverseer::Signal(OverseerSignal::Conclude)).await; - }; - - futures::executor::block_on(future::join(subsystem_task, test_task)); - } - - #[test] - fn requests_candidate_events() { - let (ctx, mut ctx_handle) = test_helpers::make_subsystem_context(TaskExecutor::new()); - let runtime_api = Arc::new(MockRuntimeApi::default()); - let relay_parent = [1; 32].into(); - let spawner = sp_core::testing::TaskExecutor::new(); - - let subsystem = RuntimeApiSubsystem::new(runtime_api.clone(), Metrics(None), spawner); - let subsystem_task = run(ctx, subsystem).map(|x| x.unwrap()); - let test_task = async move { - let (tx, rx) = oneshot::channel(); - - ctx_handle.send(FromOverseer::Communication { - msg: RuntimeApiMessage::Request(relay_parent, Request::CandidateEvents(tx)) - }).await; - - assert_eq!(rx.await.unwrap().unwrap(), runtime_api.candidate_events); - - ctx_handle.send(FromOverseer::Signal(OverseerSignal::Conclude)).await; - }; - - futures::executor::block_on(future::join(subsystem_task, test_task)); - } - - #[test] - fn requests_dmq_contents() { - let (ctx, mut ctx_handle) = test_helpers::make_subsystem_context(TaskExecutor::new()); - - let relay_parent = [1; 32].into(); - let para_a = 5.into(); - let para_b = 6.into(); - let spawner = sp_core::testing::TaskExecutor::new(); - - let runtime_api = Arc::new({ - let mut runtime_api = MockRuntimeApi::default(); - - runtime_api.dmq_contents.insert(para_a, vec![]); - runtime_api.dmq_contents.insert( - para_b, - vec![InboundDownwardMessage { - sent_at: 228, - msg: b"Novus Ordo Seclorum".to_vec(), - }], - ); - - runtime_api - }); - - let subsystem = RuntimeApiSubsystem::new(runtime_api.clone(), Metrics(None), spawner); - let subsystem_task = run(ctx, subsystem).map(|x| x.unwrap()); - let test_task = async move { - let (tx, rx) = oneshot::channel(); - ctx_handle - .send(FromOverseer::Communication { - msg: RuntimeApiMessage::Request(relay_parent, Request::DmqContents(para_a, tx)), - }) - .await; - assert_eq!(rx.await.unwrap().unwrap(), vec![]); - - let (tx, rx) = oneshot::channel(); - ctx_handle - .send(FromOverseer::Communication { - msg: RuntimeApiMessage::Request(relay_parent, Request::DmqContents(para_b, tx)), - }) - .await; - assert_eq!( - rx.await.unwrap().unwrap(), - vec![InboundDownwardMessage { - sent_at: 228, - msg: b"Novus Ordo Seclorum".to_vec(), - }] - ); - - ctx_handle - .send(FromOverseer::Signal(OverseerSignal::Conclude)) - .await; - }; - futures::executor::block_on(future::join(subsystem_task, test_task)); - } - - #[test] - fn requests_inbound_hrmp_channels_contents() { - let (ctx, mut ctx_handle) = test_helpers::make_subsystem_context(TaskExecutor::new()); - - let relay_parent = [1; 32].into(); - let para_a = 99.into(); - let para_b = 66.into(); - let para_c = 33.into(); - let spawner = sp_core::testing::TaskExecutor::new(); - - let para_b_inbound_channels = [ - (para_a, vec![]), - ( - para_c, - vec![InboundHrmpMessage { - sent_at: 1, - data: "๐™€=๐™ˆ๐˜พยฒ".as_bytes().to_owned(), - }], - ), - ] - .iter() - .cloned() - .collect::>(); - - let runtime_api = Arc::new({ - let mut runtime_api = MockRuntimeApi::default(); - - runtime_api.hrmp_channels.insert(para_a, BTreeMap::new()); - runtime_api - .hrmp_channels - .insert(para_b, para_b_inbound_channels.clone()); - - runtime_api - }); - - let subsystem = RuntimeApiSubsystem::new(runtime_api.clone(), Metrics(None), spawner); - let subsystem_task = run(ctx, subsystem).map(|x| x.unwrap()); - let test_task = async move { - let (tx, rx) = oneshot::channel(); - ctx_handle - .send(FromOverseer::Communication { - msg: RuntimeApiMessage::Request( - relay_parent, - Request::InboundHrmpChannelsContents(para_a, tx), - ), - }) - .await; - assert_eq!(rx.await.unwrap().unwrap(), BTreeMap::new()); - - let (tx, rx) = oneshot::channel(); - ctx_handle - .send(FromOverseer::Communication { - msg: RuntimeApiMessage::Request( - relay_parent, - Request::InboundHrmpChannelsContents(para_b, tx), - ), - }) - .await; - assert_eq!(rx.await.unwrap().unwrap(), para_b_inbound_channels,); - - ctx_handle - .send(FromOverseer::Signal(OverseerSignal::Conclude)) - .await; - }; - futures::executor::block_on(future::join(subsystem_task, test_task)); - } - - #[test] - fn requests_historical_code() { - let (ctx, mut ctx_handle) = test_helpers::make_subsystem_context(TaskExecutor::new()); - - let para_a = 5.into(); - let para_b = 6.into(); - let spawner = sp_core::testing::TaskExecutor::new(); - - let runtime_api = Arc::new({ - let mut runtime_api = MockRuntimeApi::default(); - - runtime_api.historical_validation_code.insert( - para_a, - vec![(1, vec![1, 2, 3].into()), (10, vec![4, 5, 6].into())], - ); - - runtime_api.historical_validation_code.insert( - para_b, - vec![(5, vec![7, 8, 9].into())], - ); - - runtime_api - }); - let relay_parent = [1; 32].into(); - - let subsystem = RuntimeApiSubsystem::new(runtime_api, Metrics(None), spawner); - let subsystem_task = run(ctx, subsystem).map(|x| x.unwrap()); - let test_task = async move { - { - let (tx, rx) = oneshot::channel(); - ctx_handle.send(FromOverseer::Communication { - msg: RuntimeApiMessage::Request( - relay_parent, - Request::HistoricalValidationCode(para_a, 5, tx), - ) - }).await; - - assert_eq!(rx.await.unwrap().unwrap(), Some(ValidationCode::from(vec![1, 2, 3]))); - } - - { - let (tx, rx) = oneshot::channel(); - ctx_handle.send(FromOverseer::Communication { - msg: RuntimeApiMessage::Request( - relay_parent, - Request::HistoricalValidationCode(para_a, 10, tx), - ) - }).await; - - assert_eq!(rx.await.unwrap().unwrap(), Some(ValidationCode::from(vec![4, 5, 6]))); - } - - { - let (tx, rx) = oneshot::channel(); - ctx_handle.send(FromOverseer::Communication { - msg: RuntimeApiMessage::Request( - relay_parent, - Request::HistoricalValidationCode(para_b, 1, tx), - ) - }).await; - - assert!(rx.await.unwrap().unwrap().is_none()); - } - - ctx_handle.send(FromOverseer::Signal(OverseerSignal::Conclude)).await; - }; - - futures::executor::block_on(future::join(subsystem_task, test_task)); - } - - #[test] - fn multiple_requests_in_parallel_are_working() { - let (ctx, mut ctx_handle) = test_helpers::make_subsystem_context(TaskExecutor::new()); - let runtime_api = Arc::new(MockRuntimeApi::default()); - let relay_parent = [1; 32].into(); - let spawner = sp_core::testing::TaskExecutor::new(); - let mutex = runtime_api.availability_cores_wait.clone(); - - let subsystem = RuntimeApiSubsystem::new(runtime_api.clone(), Metrics(None), spawner); - let subsystem_task = run(ctx, subsystem).map(|x| x.unwrap()); - let test_task = async move { - // Make all requests block until we release this mutex. - let lock = mutex.lock().unwrap(); - - let mut receivers = Vec::new(); - - for _ in 0..MAX_PARALLEL_REQUESTS * 10 { - let (tx, rx) = oneshot::channel(); - - ctx_handle.send(FromOverseer::Communication { - msg: RuntimeApiMessage::Request(relay_parent, Request::AvailabilityCores(tx)) - }).await; - - receivers.push(rx); - } - - let join = future::join_all(receivers); - - drop(lock); - - join.await - .into_iter() - .for_each(|r| assert_eq!(r.unwrap().unwrap(), runtime_api.availability_cores)); - - ctx_handle.send(FromOverseer::Signal(OverseerSignal::Conclude)).await; - }; - - futures::executor::block_on(future::join(subsystem_task, test_task)); - } - - #[test] - fn request_babe_epoch() { - let (ctx, mut ctx_handle) = test_helpers::make_subsystem_context(TaskExecutor::new()); - let mut runtime_api = MockRuntimeApi::default(); - let epoch = BabeEpoch { - epoch_index: 100, - start_slot: sp_consensus_babe::Slot::from(1000), - duration: 10, - authorities: Vec::new(), - randomness: [1u8; 32], - config: BabeEpochConfiguration { - c: (1, 4), - allowed_slots: BabeAllowedSlots::PrimarySlots, - }, - }; - runtime_api.babe_epoch = Some(epoch.clone()); - let runtime_api = Arc::new(runtime_api); - let relay_parent = [1; 32].into(); - let spawner = sp_core::testing::TaskExecutor::new(); - - let subsystem = RuntimeApiSubsystem::new(runtime_api.clone(), Metrics(None), spawner); - let subsystem_task = run(ctx, subsystem).map(|x| x.unwrap()); - let test_task = async move { - let (tx, rx) = oneshot::channel(); - - ctx_handle.send(FromOverseer::Communication { - msg: RuntimeApiMessage::Request(relay_parent, Request::CurrentBabeEpoch(tx)) - }).await; - - assert_eq!(rx.await.unwrap().unwrap(), epoch); - ctx_handle.send(FromOverseer::Signal(OverseerSignal::Conclude)).await; - }; - - futures::executor::block_on(future::join(subsystem_task, test_task)); - } -} +mod tests; diff --git a/polkadot/node/core/runtime-api/src/tests.rs b/polkadot/node/core/runtime-api/src/tests.rs new file mode 100644 index 0000000000..3e5dbaa98f --- /dev/null +++ b/polkadot/node/core/runtime-api/src/tests.rs @@ -0,0 +1,829 @@ +// Copyright 2020 Parity Technologies (UK) Ltd. +// This file is part of Polkadot. + +// Polkadot is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Polkadot is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Polkadot. If not, see . + +use super::*; + +use polkadot_primitives::v1::{ + ValidatorId, ValidatorIndex, GroupRotationInfo, CoreState, PersistedValidationData, + Id as ParaId, OccupiedCoreAssumption, SessionIndex, ValidationCode, + CommittedCandidateReceipt, CandidateEvent, InboundDownwardMessage, + BlockNumber, InboundHrmpMessage, SessionInfo, AuthorityDiscoveryId, +}; +use polkadot_node_subsystem_test_helpers as test_helpers; +use sp_core::testing::TaskExecutor; +use std::{collections::{HashMap, BTreeMap}, sync::{Arc, Mutex}}; +use futures::channel::oneshot; +use polkadot_node_primitives::{ + BabeEpoch, BabeEpochConfiguration, BabeAllowedSlots, +}; + +#[derive(Default, Clone)] +struct MockRuntimeApi { + authorities: Vec, + validators: Vec, + validator_groups: Vec>, + availability_cores: Vec, + availability_cores_wait: Arc>, + validation_data: HashMap, + session_index_for_child: SessionIndex, + session_info: HashMap, + validation_code: HashMap, + historical_validation_code: HashMap>, + validation_outputs_results: HashMap, + candidate_pending_availability: HashMap, + candidate_events: Vec, + dmq_contents: HashMap>, + hrmp_channels: HashMap>>, + babe_epoch: Option, +} + +impl ProvideRuntimeApi for MockRuntimeApi { + type Api = Self; + + fn runtime_api<'a>(&'a self) -> sp_api::ApiRef<'a, Self::Api> { + self.clone().into() + } +} + +sp_api::mock_impl_runtime_apis! { + impl ParachainHost for MockRuntimeApi { + fn validators(&self) -> Vec { + self.validators.clone() + } + + fn validator_groups(&self) -> (Vec>, GroupRotationInfo) { + ( + self.validator_groups.clone(), + GroupRotationInfo { + session_start_block: 1, + group_rotation_frequency: 100, + now: 10, + }, + ) + } + + fn availability_cores(&self) -> Vec { + let _ = self.availability_cores_wait.lock().unwrap(); + self.availability_cores.clone() + } + + fn persisted_validation_data( + &self, + para: ParaId, + _assumption: OccupiedCoreAssumption, + ) -> Option { + self.validation_data.get(¶).cloned() + } + + fn check_validation_outputs( + &self, + para_id: ParaId, + _commitments: polkadot_primitives::v1::CandidateCommitments, + ) -> bool { + self.validation_outputs_results + .get(¶_id) + .cloned() + .expect( + "`check_validation_outputs` called but the expected result hasn't been supplied" + ) + } + + fn session_index_for_child(&self) -> SessionIndex { + self.session_index_for_child.clone() + } + + fn session_info(&self, index: SessionIndex) -> Option { + self.session_info.get(&index).cloned() + } + + fn validation_code( + &self, + para: ParaId, + _assumption: OccupiedCoreAssumption, + ) -> Option { + self.validation_code.get(¶).map(|c| c.clone()) + } + + fn historical_validation_code( + &self, + para: ParaId, + at: BlockNumber, + ) -> Option { + self.historical_validation_code.get(¶).and_then(|h_code| { + h_code.iter() + .take_while(|(changed_at, _)| changed_at <= &at) + .last() + .map(|(_, code)| code.clone()) + }) + } + + fn candidate_pending_availability( + &self, + para: ParaId, + ) -> Option { + self.candidate_pending_availability.get(¶).map(|c| c.clone()) + } + + fn candidate_events(&self) -> Vec { + self.candidate_events.clone() + } + + fn dmq_contents( + &self, + recipient: ParaId, + ) -> Vec { + self.dmq_contents.get(&recipient).map(|q| q.clone()).unwrap_or_default() + } + + fn inbound_hrmp_channels_contents( + &self, + recipient: ParaId + ) -> BTreeMap> { + self.hrmp_channels.get(&recipient).map(|q| q.clone()).unwrap_or_default() + } + + fn validation_code_by_hash( + &self, + _hash: Hash, + ) -> Option { + unreachable!("not used in tests"); + } + } + + impl BabeApi for MockRuntimeApi { + fn configuration(&self) -> sp_consensus_babe::BabeGenesisConfiguration { + unimplemented!() + } + + fn current_epoch_start(&self) -> sp_consensus_babe::Slot { + self.babe_epoch.as_ref().unwrap().start_slot + } + + fn current_epoch(&self) -> BabeEpoch { + self.babe_epoch.as_ref().unwrap().clone() + } + + fn next_epoch(&self) -> BabeEpoch { + unimplemented!() + } + + fn generate_key_ownership_proof( + _slot: sp_consensus_babe::Slot, + _authority_id: sp_consensus_babe::AuthorityId, + ) -> Option { + None + } + + fn submit_report_equivocation_unsigned_extrinsic( + _equivocation_proof: sp_consensus_babe::EquivocationProof, + _key_owner_proof: sp_consensus_babe::OpaqueKeyOwnershipProof, + ) -> Option<()> { + None + } + } + + impl AuthorityDiscoveryApi for MockRuntimeApi { + fn authorities(&self) -> Vec { + self.authorities.clone() + } + } +} + +#[test] +fn requests_authorities() { + let (ctx, mut ctx_handle) = test_helpers::make_subsystem_context(TaskExecutor::new()); + let runtime_api = Arc::new(MockRuntimeApi::default()); + let relay_parent = [1; 32].into(); + let spawner = sp_core::testing::TaskExecutor::new(); + + let subsystem = RuntimeApiSubsystem::new(runtime_api.clone(), Metrics(None), spawner); + let subsystem_task = run(ctx, subsystem).map(|x| x.unwrap()); + let test_task = async move { + let (tx, rx) = oneshot::channel(); + + ctx_handle.send(FromOverseer::Communication { + msg: RuntimeApiMessage::Request(relay_parent, Request::Authorities(tx)) + }).await; + + assert_eq!(rx.await.unwrap().unwrap(), runtime_api.authorities); + + ctx_handle.send(FromOverseer::Signal(OverseerSignal::Conclude)).await; + }; + + futures::executor::block_on(future::join(subsystem_task, test_task)); +} + +#[test] +fn requests_validators() { + let (ctx, mut ctx_handle) = test_helpers::make_subsystem_context(TaskExecutor::new()); + let runtime_api = Arc::new(MockRuntimeApi::default()); + let relay_parent = [1; 32].into(); + let spawner = sp_core::testing::TaskExecutor::new(); + + let subsystem = RuntimeApiSubsystem::new(runtime_api.clone(), Metrics(None), spawner); + let subsystem_task = run(ctx, subsystem).map(|x| x.unwrap()); + let test_task = async move { + let (tx, rx) = oneshot::channel(); + + ctx_handle.send(FromOverseer::Communication { + msg: RuntimeApiMessage::Request(relay_parent, Request::Validators(tx)) + }).await; + + assert_eq!(rx.await.unwrap().unwrap(), runtime_api.validators); + + ctx_handle.send(FromOverseer::Signal(OverseerSignal::Conclude)).await; + }; + + futures::executor::block_on(future::join(subsystem_task, test_task)); +} + +#[test] +fn requests_validator_groups() { + let (ctx, mut ctx_handle) = test_helpers::make_subsystem_context(TaskExecutor::new()); + let runtime_api = Arc::new(MockRuntimeApi::default()); + let relay_parent = [1; 32].into(); + let spawner = sp_core::testing::TaskExecutor::new(); + + let subsystem = RuntimeApiSubsystem::new(runtime_api.clone(), Metrics(None), spawner); + let subsystem_task = run(ctx, subsystem).map(|x| x.unwrap()); + let test_task = async move { + let (tx, rx) = oneshot::channel(); + + ctx_handle.send(FromOverseer::Communication { + msg: RuntimeApiMessage::Request(relay_parent, Request::ValidatorGroups(tx)) + }).await; + + assert_eq!(rx.await.unwrap().unwrap().0, runtime_api.validator_groups); + + ctx_handle.send(FromOverseer::Signal(OverseerSignal::Conclude)).await; + }; + + futures::executor::block_on(future::join(subsystem_task, test_task)); +} + +#[test] +fn requests_availability_cores() { + let (ctx, mut ctx_handle) = test_helpers::make_subsystem_context(TaskExecutor::new()); + let runtime_api = Arc::new(MockRuntimeApi::default()); + let relay_parent = [1; 32].into(); + let spawner = sp_core::testing::TaskExecutor::new(); + + let subsystem = RuntimeApiSubsystem::new(runtime_api.clone(), Metrics(None), spawner); + let subsystem_task = run(ctx, subsystem).map(|x| x.unwrap()); + let test_task = async move { + let (tx, rx) = oneshot::channel(); + + ctx_handle.send(FromOverseer::Communication { + msg: RuntimeApiMessage::Request(relay_parent, Request::AvailabilityCores(tx)) + }).await; + + assert_eq!(rx.await.unwrap().unwrap(), runtime_api.availability_cores); + + ctx_handle.send(FromOverseer::Signal(OverseerSignal::Conclude)).await; + }; + + futures::executor::block_on(future::join(subsystem_task, test_task)); +} + +#[test] +fn requests_persisted_validation_data() { + let (ctx, mut ctx_handle) = test_helpers::make_subsystem_context(TaskExecutor::new()); + let relay_parent = [1; 32].into(); + let para_a = 5.into(); + let para_b = 6.into(); + let spawner = sp_core::testing::TaskExecutor::new(); + + let mut runtime_api = MockRuntimeApi::default(); + runtime_api.validation_data.insert(para_a, Default::default()); + let runtime_api = Arc::new(runtime_api); + + let subsystem = RuntimeApiSubsystem::new(runtime_api.clone(), Metrics(None), spawner); + let subsystem_task = run(ctx, subsystem).map(|x| x.unwrap()); + let test_task = async move { + let (tx, rx) = oneshot::channel(); + + ctx_handle.send(FromOverseer::Communication { + msg: RuntimeApiMessage::Request( + relay_parent, + Request::PersistedValidationData(para_a, OccupiedCoreAssumption::Included, tx) + ), + }).await; + + assert_eq!(rx.await.unwrap().unwrap(), Some(Default::default())); + + let (tx, rx) = oneshot::channel(); + ctx_handle.send(FromOverseer::Communication { + msg: RuntimeApiMessage::Request( + relay_parent, + Request::PersistedValidationData(para_b, OccupiedCoreAssumption::Included, tx) + ), + }).await; + + assert_eq!(rx.await.unwrap().unwrap(), None); + + ctx_handle.send(FromOverseer::Signal(OverseerSignal::Conclude)).await; + }; + + futures::executor::block_on(future::join(subsystem_task, test_task)); +} + +#[test] +fn requests_check_validation_outputs() { + let (ctx, mut ctx_handle) = test_helpers::make_subsystem_context(TaskExecutor::new()); + let mut runtime_api = MockRuntimeApi::default(); + let relay_parent = [1; 32].into(); + let para_a = 5.into(); + let para_b = 6.into(); + let commitments = polkadot_primitives::v1::CandidateCommitments::default(); + let spawner = sp_core::testing::TaskExecutor::new(); + + runtime_api.validation_outputs_results.insert(para_a, false); + runtime_api.validation_outputs_results.insert(para_b, true); + + let runtime_api = Arc::new(runtime_api); + + let subsystem = RuntimeApiSubsystem::new(runtime_api.clone(), Metrics(None), spawner); + let subsystem_task = run(ctx, subsystem).map(|x| x.unwrap()); + let test_task = async move { + let (tx, rx) = oneshot::channel(); + + ctx_handle.send(FromOverseer::Communication { + msg: RuntimeApiMessage::Request( + relay_parent, + Request::CheckValidationOutputs( + para_a, + commitments.clone(), + tx, + ), + ) + }).await; + assert_eq!( + rx.await.unwrap().unwrap(), + runtime_api.validation_outputs_results[¶_a], + ); + + let (tx, rx) = oneshot::channel(); + ctx_handle.send(FromOverseer::Communication { + msg: RuntimeApiMessage::Request( + relay_parent, + Request::CheckValidationOutputs( + para_b, + commitments, + tx, + ), + ) + }).await; + assert_eq!( + rx.await.unwrap().unwrap(), + runtime_api.validation_outputs_results[¶_b], + ); + + ctx_handle.send(FromOverseer::Signal(OverseerSignal::Conclude)).await; + }; + + futures::executor::block_on(future::join(subsystem_task, test_task)); +} + +#[test] +fn requests_session_index_for_child() { + let (ctx, mut ctx_handle) = test_helpers::make_subsystem_context(TaskExecutor::new()); + let runtime_api = Arc::new(MockRuntimeApi::default()); + let relay_parent = [1; 32].into(); + let spawner = sp_core::testing::TaskExecutor::new(); + + let subsystem = RuntimeApiSubsystem::new(runtime_api.clone(), Metrics(None), spawner); + let subsystem_task = run(ctx, subsystem).map(|x| x.unwrap()); + let test_task = async move { + let (tx, rx) = oneshot::channel(); + + ctx_handle.send(FromOverseer::Communication { + msg: RuntimeApiMessage::Request(relay_parent, Request::SessionIndexForChild(tx)) + }).await; + + assert_eq!(rx.await.unwrap().unwrap(), runtime_api.session_index_for_child); + + ctx_handle.send(FromOverseer::Signal(OverseerSignal::Conclude)).await; + }; + + futures::executor::block_on(future::join(subsystem_task, test_task)); +} + +#[test] +fn requests_session_info() { + let (ctx, mut ctx_handle) = test_helpers::make_subsystem_context(TaskExecutor::new()); + let mut runtime_api = MockRuntimeApi::default(); + let session_index = 1; + runtime_api.session_info.insert(session_index, Default::default()); + let runtime_api = Arc::new(runtime_api); + let spawner = sp_core::testing::TaskExecutor::new(); + + let relay_parent = [1; 32].into(); + + let subsystem = RuntimeApiSubsystem::new(runtime_api.clone(), Metrics(None), spawner); + let subsystem_task = run(ctx, subsystem).map(|x| x.unwrap()); + let test_task = async move { + let (tx, rx) = oneshot::channel(); + + ctx_handle.send(FromOverseer::Communication { + msg: RuntimeApiMessage::Request(relay_parent, Request::SessionInfo(session_index, tx)) + }).await; + + assert_eq!(rx.await.unwrap().unwrap(), Some(Default::default())); + + ctx_handle.send(FromOverseer::Signal(OverseerSignal::Conclude)).await; + }; + + futures::executor::block_on(future::join(subsystem_task, test_task)); +} + +#[test] +fn requests_validation_code() { + let (ctx, mut ctx_handle) = test_helpers::make_subsystem_context(TaskExecutor::new()); + + let relay_parent = [1; 32].into(); + let para_a = 5.into(); + let para_b = 6.into(); + let spawner = sp_core::testing::TaskExecutor::new(); + + let mut runtime_api = MockRuntimeApi::default(); + runtime_api.validation_code.insert(para_a, Default::default()); + let runtime_api = Arc::new(runtime_api); + + let subsystem = RuntimeApiSubsystem::new(runtime_api.clone(), Metrics(None), spawner); + let subsystem_task = run(ctx, subsystem).map(|x| x.unwrap()); + let test_task = async move { + let (tx, rx) = oneshot::channel(); + + ctx_handle.send(FromOverseer::Communication { + msg: RuntimeApiMessage::Request( + relay_parent, + Request::ValidationCode(para_a, OccupiedCoreAssumption::Included, tx) + ), + }).await; + + assert_eq!(rx.await.unwrap().unwrap(), Some(Default::default())); + + let (tx, rx) = oneshot::channel(); + ctx_handle.send(FromOverseer::Communication { + msg: RuntimeApiMessage::Request( + relay_parent, + Request::ValidationCode(para_b, OccupiedCoreAssumption::Included, tx) + ), + }).await; + + assert_eq!(rx.await.unwrap().unwrap(), None); + + ctx_handle.send(FromOverseer::Signal(OverseerSignal::Conclude)).await; + }; + + futures::executor::block_on(future::join(subsystem_task, test_task)); +} + +#[test] +fn requests_candidate_pending_availability() { + let (ctx, mut ctx_handle) = test_helpers::make_subsystem_context(TaskExecutor::new()); + let relay_parent = [1; 32].into(); + let para_a = 5.into(); + let para_b = 6.into(); + let spawner = sp_core::testing::TaskExecutor::new(); + + let mut runtime_api = MockRuntimeApi::default(); + runtime_api.candidate_pending_availability.insert(para_a, Default::default()); + let runtime_api = Arc::new(runtime_api); + + let subsystem = RuntimeApiSubsystem::new(runtime_api.clone(), Metrics(None), spawner); + let subsystem_task = run(ctx, subsystem).map(|x| x.unwrap()); + let test_task = async move { + let (tx, rx) = oneshot::channel(); + + ctx_handle.send(FromOverseer::Communication { + msg: RuntimeApiMessage::Request( + relay_parent, + Request::CandidatePendingAvailability(para_a, tx), + ) + }).await; + + assert_eq!(rx.await.unwrap().unwrap(), Some(Default::default())); + + let (tx, rx) = oneshot::channel(); + + ctx_handle.send(FromOverseer::Communication { + msg: RuntimeApiMessage::Request( + relay_parent, + Request::CandidatePendingAvailability(para_b, tx), + ) + }).await; + + assert_eq!(rx.await.unwrap().unwrap(), None); + + ctx_handle.send(FromOverseer::Signal(OverseerSignal::Conclude)).await; + }; + + futures::executor::block_on(future::join(subsystem_task, test_task)); +} + +#[test] +fn requests_candidate_events() { + let (ctx, mut ctx_handle) = test_helpers::make_subsystem_context(TaskExecutor::new()); + let runtime_api = Arc::new(MockRuntimeApi::default()); + let relay_parent = [1; 32].into(); + let spawner = sp_core::testing::TaskExecutor::new(); + + let subsystem = RuntimeApiSubsystem::new(runtime_api.clone(), Metrics(None), spawner); + let subsystem_task = run(ctx, subsystem).map(|x| x.unwrap()); + let test_task = async move { + let (tx, rx) = oneshot::channel(); + + ctx_handle.send(FromOverseer::Communication { + msg: RuntimeApiMessage::Request(relay_parent, Request::CandidateEvents(tx)) + }).await; + + assert_eq!(rx.await.unwrap().unwrap(), runtime_api.candidate_events); + + ctx_handle.send(FromOverseer::Signal(OverseerSignal::Conclude)).await; + }; + + futures::executor::block_on(future::join(subsystem_task, test_task)); +} + +#[test] +fn requests_dmq_contents() { + let (ctx, mut ctx_handle) = test_helpers::make_subsystem_context(TaskExecutor::new()); + + let relay_parent = [1; 32].into(); + let para_a = 5.into(); + let para_b = 6.into(); + let spawner = sp_core::testing::TaskExecutor::new(); + + let runtime_api = Arc::new({ + let mut runtime_api = MockRuntimeApi::default(); + + runtime_api.dmq_contents.insert(para_a, vec![]); + runtime_api.dmq_contents.insert( + para_b, + vec![InboundDownwardMessage { + sent_at: 228, + msg: b"Novus Ordo Seclorum".to_vec(), + }], + ); + + runtime_api + }); + + let subsystem = RuntimeApiSubsystem::new(runtime_api.clone(), Metrics(None), spawner); + let subsystem_task = run(ctx, subsystem).map(|x| x.unwrap()); + let test_task = async move { + let (tx, rx) = oneshot::channel(); + ctx_handle + .send(FromOverseer::Communication { + msg: RuntimeApiMessage::Request(relay_parent, Request::DmqContents(para_a, tx)), + }) + .await; + assert_eq!(rx.await.unwrap().unwrap(), vec![]); + + let (tx, rx) = oneshot::channel(); + ctx_handle + .send(FromOverseer::Communication { + msg: RuntimeApiMessage::Request(relay_parent, Request::DmqContents(para_b, tx)), + }) + .await; + assert_eq!( + rx.await.unwrap().unwrap(), + vec![InboundDownwardMessage { + sent_at: 228, + msg: b"Novus Ordo Seclorum".to_vec(), + }] + ); + + ctx_handle + .send(FromOverseer::Signal(OverseerSignal::Conclude)) + .await; + }; + futures::executor::block_on(future::join(subsystem_task, test_task)); +} + +#[test] +fn requests_inbound_hrmp_channels_contents() { + let (ctx, mut ctx_handle) = test_helpers::make_subsystem_context(TaskExecutor::new()); + + let relay_parent = [1; 32].into(); + let para_a = 99.into(); + let para_b = 66.into(); + let para_c = 33.into(); + let spawner = sp_core::testing::TaskExecutor::new(); + + let para_b_inbound_channels = [ + (para_a, vec![]), + ( + para_c, + vec![InboundHrmpMessage { + sent_at: 1, + data: "๐™€=๐™ˆ๐˜พยฒ".as_bytes().to_owned(), + }], + ), + ] + .iter() + .cloned() + .collect::>(); + + let runtime_api = Arc::new({ + let mut runtime_api = MockRuntimeApi::default(); + + runtime_api.hrmp_channels.insert(para_a, BTreeMap::new()); + runtime_api + .hrmp_channels + .insert(para_b, para_b_inbound_channels.clone()); + + runtime_api + }); + + let subsystem = RuntimeApiSubsystem::new(runtime_api.clone(), Metrics(None), spawner); + let subsystem_task = run(ctx, subsystem).map(|x| x.unwrap()); + let test_task = async move { + let (tx, rx) = oneshot::channel(); + ctx_handle + .send(FromOverseer::Communication { + msg: RuntimeApiMessage::Request( + relay_parent, + Request::InboundHrmpChannelsContents(para_a, tx), + ), + }) + .await; + assert_eq!(rx.await.unwrap().unwrap(), BTreeMap::new()); + + let (tx, rx) = oneshot::channel(); + ctx_handle + .send(FromOverseer::Communication { + msg: RuntimeApiMessage::Request( + relay_parent, + Request::InboundHrmpChannelsContents(para_b, tx), + ), + }) + .await; + assert_eq!(rx.await.unwrap().unwrap(), para_b_inbound_channels,); + + ctx_handle + .send(FromOverseer::Signal(OverseerSignal::Conclude)) + .await; + }; + futures::executor::block_on(future::join(subsystem_task, test_task)); +} + +#[test] +fn requests_historical_code() { + let (ctx, mut ctx_handle) = test_helpers::make_subsystem_context(TaskExecutor::new()); + + let para_a = 5.into(); + let para_b = 6.into(); + let spawner = sp_core::testing::TaskExecutor::new(); + + let runtime_api = Arc::new({ + let mut runtime_api = MockRuntimeApi::default(); + + runtime_api.historical_validation_code.insert( + para_a, + vec![(1, vec![1, 2, 3].into()), (10, vec![4, 5, 6].into())], + ); + + runtime_api.historical_validation_code.insert( + para_b, + vec![(5, vec![7, 8, 9].into())], + ); + + runtime_api + }); + let relay_parent = [1; 32].into(); + + let subsystem = RuntimeApiSubsystem::new(runtime_api, Metrics(None), spawner); + let subsystem_task = run(ctx, subsystem).map(|x| x.unwrap()); + let test_task = async move { + { + let (tx, rx) = oneshot::channel(); + ctx_handle.send(FromOverseer::Communication { + msg: RuntimeApiMessage::Request( + relay_parent, + Request::HistoricalValidationCode(para_a, 5, tx), + ) + }).await; + + assert_eq!(rx.await.unwrap().unwrap(), Some(ValidationCode::from(vec![1, 2, 3]))); + } + + { + let (tx, rx) = oneshot::channel(); + ctx_handle.send(FromOverseer::Communication { + msg: RuntimeApiMessage::Request( + relay_parent, + Request::HistoricalValidationCode(para_a, 10, tx), + ) + }).await; + + assert_eq!(rx.await.unwrap().unwrap(), Some(ValidationCode::from(vec![4, 5, 6]))); + } + + { + let (tx, rx) = oneshot::channel(); + ctx_handle.send(FromOverseer::Communication { + msg: RuntimeApiMessage::Request( + relay_parent, + Request::HistoricalValidationCode(para_b, 1, tx), + ) + }).await; + + assert!(rx.await.unwrap().unwrap().is_none()); + } + + ctx_handle.send(FromOverseer::Signal(OverseerSignal::Conclude)).await; + }; + + futures::executor::block_on(future::join(subsystem_task, test_task)); +} + +#[test] +fn multiple_requests_in_parallel_are_working() { + let (ctx, mut ctx_handle) = test_helpers::make_subsystem_context(TaskExecutor::new()); + let runtime_api = Arc::new(MockRuntimeApi::default()); + let relay_parent = [1; 32].into(); + let spawner = sp_core::testing::TaskExecutor::new(); + let mutex = runtime_api.availability_cores_wait.clone(); + + let subsystem = RuntimeApiSubsystem::new(runtime_api.clone(), Metrics(None), spawner); + let subsystem_task = run(ctx, subsystem).map(|x| x.unwrap()); + let test_task = async move { + // Make all requests block until we release this mutex. + let lock = mutex.lock().unwrap(); + + let mut receivers = Vec::new(); + + for _ in 0..MAX_PARALLEL_REQUESTS * 10 { + let (tx, rx) = oneshot::channel(); + + ctx_handle.send(FromOverseer::Communication { + msg: RuntimeApiMessage::Request(relay_parent, Request::AvailabilityCores(tx)) + }).await; + + receivers.push(rx); + } + + let join = future::join_all(receivers); + + drop(lock); + + join.await + .into_iter() + .for_each(|r| assert_eq!(r.unwrap().unwrap(), runtime_api.availability_cores)); + + ctx_handle.send(FromOverseer::Signal(OverseerSignal::Conclude)).await; + }; + + futures::executor::block_on(future::join(subsystem_task, test_task)); +} + +#[test] +fn request_babe_epoch() { + let (ctx, mut ctx_handle) = test_helpers::make_subsystem_context(TaskExecutor::new()); + let mut runtime_api = MockRuntimeApi::default(); + let epoch = BabeEpoch { + epoch_index: 100, + start_slot: sp_consensus_babe::Slot::from(1000), + duration: 10, + authorities: Vec::new(), + randomness: [1u8; 32], + config: BabeEpochConfiguration { + c: (1, 4), + allowed_slots: BabeAllowedSlots::PrimarySlots, + }, + }; + runtime_api.babe_epoch = Some(epoch.clone()); + let runtime_api = Arc::new(runtime_api); + let relay_parent = [1; 32].into(); + let spawner = sp_core::testing::TaskExecutor::new(); + + let subsystem = RuntimeApiSubsystem::new(runtime_api.clone(), Metrics(None), spawner); + let subsystem_task = run(ctx, subsystem).map(|x| x.unwrap()); + let test_task = async move { + let (tx, rx) = oneshot::channel(); + + ctx_handle.send(FromOverseer::Communication { + msg: RuntimeApiMessage::Request(relay_parent, Request::CurrentBabeEpoch(tx)) + }).await; + + assert_eq!(rx.await.unwrap().unwrap(), epoch); + ctx_handle.send(FromOverseer::Signal(OverseerSignal::Conclude)).await; + }; + + futures::executor::block_on(future::join(subsystem_task, test_task)); +} diff --git a/polkadot/node/network/bridge/src/lib.rs b/polkadot/node/network/bridge/src/lib.rs index a1a78d1f64..8618d06d14 100644 --- a/polkadot/node/network/bridge/src/lib.rs +++ b/polkadot/node/network/bridge/src/lib.rs @@ -1136,1304 +1136,4 @@ async fn dispatch_collation_events_to_all( #[cfg(test)] -mod tests { - use super::*; - use futures::executor; - use futures::stream::BoxStream; - use futures::channel::oneshot; - - use std::borrow::Cow; - use std::collections::HashSet; - use std::pin::Pin; - use std::sync::atomic::{AtomicBool, Ordering}; - use async_trait::async_trait; - use parking_lot::Mutex; - use assert_matches::assert_matches; - - use sc_network::{Event as NetworkEvent, IfDisconnected}; - - use polkadot_subsystem::{jaeger, ActiveLeavesUpdate, FromOverseer, OverseerSignal, LeafStatus}; - use polkadot_subsystem::messages::{ - ApprovalDistributionMessage, - BitfieldDistributionMessage, - StatementDistributionMessage - }; - use polkadot_node_subsystem_test_helpers::{ - SingleItemSink, SingleItemStream, TestSubsystemContextHandle, - }; - use polkadot_node_subsystem_util::metered; - use polkadot_node_network_protocol::view; - use sc_network::Multiaddr; - use sc_network::config::RequestResponseConfig; - use sp_keyring::Sr25519Keyring; - use polkadot_primitives::v1::AuthorityDiscoveryId; - use polkadot_node_network_protocol::{ObservedRole, request_response::request::Requests}; - - use crate::network::{Network, NetworkAction}; - use crate::validator_discovery::AuthorityDiscovery; - - // The subsystem's view of the network - only supports a single call to `event_stream`. - #[derive(Clone)] - struct TestNetwork { - net_events: Arc>>>, - action_tx: metered::UnboundedMeteredSender, - _req_configs: Vec, - } - - #[derive(Clone)] - struct TestAuthorityDiscovery; - - // The test's view of the network. This receives updates from the subsystem in the form - // of `NetworkAction`s. - struct TestNetworkHandle { - action_rx: metered::UnboundedMeteredReceiver, - net_tx: SingleItemSink, - } - - fn new_test_network(req_configs: Vec) -> ( - TestNetwork, - TestNetworkHandle, - TestAuthorityDiscovery, - ) { - let (net_tx, net_rx) = polkadot_node_subsystem_test_helpers::single_item_sink(); - let (action_tx, action_rx) = metered::unbounded(); - - ( - TestNetwork { - net_events: Arc::new(Mutex::new(Some(net_rx))), - action_tx, - _req_configs: req_configs, - }, - TestNetworkHandle { - action_rx, - net_tx, - }, - TestAuthorityDiscovery, - ) - } - - #[async_trait] - impl Network for TestNetwork { - fn event_stream(&mut self) -> BoxStream<'static, NetworkEvent> { - self.net_events.lock() - .take() - .expect("Subsystem made more than one call to `event_stream`") - .boxed() - } - - async fn add_to_peers_set(&mut self, _protocol: Cow<'static, str>, _: HashSet) -> Result<(), String> { - Ok(()) - } - - async fn remove_from_peers_set(&mut self, _protocol: Cow<'static, str>, _: HashSet) -> Result<(), String> { - Ok(()) - } - - fn action_sink<'a>(&'a mut self) - -> Pin + Send + 'a>> - { - Box::pin((&mut self.action_tx).sink_map_err(Into::into)) - } - - async fn start_request(&self, _: &mut AD, _: Requests, _: IfDisconnected) { - } - } - - #[async_trait] - impl validator_discovery::AuthorityDiscovery for TestAuthorityDiscovery { - async fn get_addresses_by_authority_id(&mut self, _authority: AuthorityDiscoveryId) -> Option> { - None - } - - async fn get_authority_id_by_peer_id(&mut self, _peer_id: PeerId) -> Option { - None - } - } - - impl TestNetworkHandle { - // Get the next network action. - async fn next_network_action(&mut self) -> NetworkAction { - self.action_rx.next().await.expect("subsystem concluded early") - } - - // Wait for the next N network actions. - async fn next_network_actions(&mut self, n: usize) -> Vec { - let mut v = Vec::with_capacity(n); - for _ in 0..n { - v.push(self.next_network_action().await); - } - - v - } - - async fn connect_peer(&mut self, peer: PeerId, peer_set: PeerSet, role: ObservedRole) { - self.send_network_event(NetworkEvent::NotificationStreamOpened { - remote: peer, - protocol: peer_set.into_protocol_name(), - negotiated_fallback: None, - role: role.into(), - }).await; - } - - async fn disconnect_peer(&mut self, peer: PeerId, peer_set: PeerSet) { - self.send_network_event(NetworkEvent::NotificationStreamClosed { - remote: peer, - protocol: peer_set.into_protocol_name(), - }).await; - } - - async fn peer_message(&mut self, peer: PeerId, peer_set: PeerSet, message: Vec) { - self.send_network_event(NetworkEvent::NotificationsReceived { - remote: peer, - messages: vec![(peer_set.into_protocol_name(), message.into())], - }).await; - } - - async fn send_network_event(&mut self, event: NetworkEvent) { - self.net_tx.send(event).await.expect("subsystem concluded early"); - } - } - - /// Assert that the given actions contain the given `action`. - fn assert_network_actions_contains(actions: &[NetworkAction], action: &NetworkAction) { - if !actions.iter().any(|x| x == action) { - panic!("Could not find `{:?}` in `{:?}`", action, actions); - } - } - - #[derive(Clone)] - struct TestSyncOracle { - flag: Arc, - done_syncing_sender: Arc>>>, - } - - struct TestSyncOracleHandle { - done_syncing_receiver: oneshot::Receiver<()>, - flag: Arc, - } - - impl TestSyncOracleHandle { - fn set_done(&self) { - self.flag.store(false, Ordering::SeqCst); - } - - async fn await_mode_switch(self) { - let _ = self.done_syncing_receiver.await; - } - } - - impl SyncOracle for TestSyncOracle { - fn is_major_syncing(&mut self) -> bool { - let is_major_syncing = self.flag.load(Ordering::SeqCst); - - if !is_major_syncing { - if let Some(sender) = self.done_syncing_sender.lock().take() { - let _ = sender.send(()); - } - } - - is_major_syncing - } - - fn is_offline(&mut self) -> bool { - unimplemented!("not used in network bridge") - } - } - - // val - result of `is_major_syncing`. - fn make_sync_oracle(val: bool) -> (TestSyncOracle, TestSyncOracleHandle) { - let (tx, rx) = oneshot::channel(); - let flag = Arc::new(AtomicBool::new(val)); - - ( - TestSyncOracle { - flag: flag.clone(), - done_syncing_sender: Arc::new(Mutex::new(Some(tx))), - }, - TestSyncOracleHandle { - flag, - done_syncing_receiver: rx, - } - ) - } - - fn done_syncing_oracle() -> Box { - let (oracle, _) = make_sync_oracle(false); - Box::new(oracle) - } - - type VirtualOverseer = TestSubsystemContextHandle; - - struct TestHarness { - network_handle: TestNetworkHandle, - virtual_overseer: VirtualOverseer, - } - - fn test_harness>( - sync_oracle: Box, - test: impl FnOnce(TestHarness) -> T, - ) { - let pool = sp_core::testing::TaskExecutor::new(); - let (request_multiplexer, req_configs) = RequestMultiplexer::new(); - let (mut network, network_handle, discovery) = new_test_network(req_configs); - let (context, virtual_overseer) = polkadot_node_subsystem_test_helpers::make_subsystem_context(pool); - let network_stream = network.event_stream(); - - let bridge = NetworkBridge { - network_service: network, - authority_discovery_service: discovery, - request_multiplexer, - metrics: Metrics(None), - sync_oracle, - }; - - let network_bridge = run_network( - bridge, - context, - network_stream, - ) - .map_err(|_| panic!("subsystem execution failed")) - .map(|_| ()); - - let test_fut = test(TestHarness { - network_handle, - virtual_overseer, - }); - - futures::pin_mut!(test_fut); - futures::pin_mut!(network_bridge); - - let _ = executor::block_on(future::join(async move { - let mut virtual_overseer = test_fut.await; - virtual_overseer.send(FromOverseer::Signal(OverseerSignal::Conclude)).await; - }, network_bridge)); - } - - async fn assert_sends_validation_event_to_all( - event: NetworkBridgeEvent, - virtual_overseer: &mut TestSubsystemContextHandle, - ) { - // Ordering must match the enum variant order - // in `AllMessages`. - assert_matches!( - virtual_overseer.recv().await, - AllMessages::StatementDistribution( - StatementDistributionMessage::NetworkBridgeUpdateV1(e) - ) if e == event.focus().expect("could not focus message") - ); - - assert_matches!( - virtual_overseer.recv().await, - AllMessages::BitfieldDistribution( - BitfieldDistributionMessage::NetworkBridgeUpdateV1(e) - ) if e == event.focus().expect("could not focus message") - ); - - assert_matches!( - virtual_overseer.recv().await, - AllMessages::ApprovalDistribution( - ApprovalDistributionMessage::NetworkBridgeUpdateV1(e) - ) if e == event.focus().expect("could not focus message") - ); - } - - async fn assert_sends_collation_event_to_all( - event: NetworkBridgeEvent, - virtual_overseer: &mut TestSubsystemContextHandle, - ) { - assert_matches!( - virtual_overseer.recv().await, - AllMessages::CollatorProtocol( - CollatorProtocolMessage::NetworkBridgeUpdateV1(e) - ) if e == event.focus().expect("could not focus message") - ) - } - - #[test] - fn send_our_view_upon_connection() { - let (oracle, handle) = make_sync_oracle(false); - test_harness(Box::new(oracle), |test_harness| async move { - let TestHarness { - mut network_handle, - mut virtual_overseer, - } = test_harness; - - let peer = PeerId::random(); - - let head = Hash::repeat_byte(1); - virtual_overseer.send( - FromOverseer::Signal(OverseerSignal::ActiveLeaves( - ActiveLeavesUpdate::start_work(ActivatedLeaf { - hash: head, - number: 1, - status: LeafStatus::Fresh, - span: Arc::new(jaeger::Span::Disabled), - }) - )) - ).await; - - handle.await_mode_switch().await; - - network_handle.connect_peer(peer.clone(), PeerSet::Validation, ObservedRole::Full).await; - network_handle.connect_peer(peer.clone(), PeerSet::Collation, ObservedRole::Full).await; - - let view = view![head]; - let actions = network_handle.next_network_actions(2).await; - assert_network_actions_contains( - &actions, - &NetworkAction::WriteNotification( - peer.clone(), - PeerSet::Validation, - WireMessage::::ViewUpdate( - view.clone(), - ).encode(), - ), - ); - assert_network_actions_contains( - &actions, - &NetworkAction::WriteNotification( - peer.clone(), - PeerSet::Collation, - WireMessage::::ViewUpdate( - view.clone(), - ).encode(), - ), - ); - virtual_overseer - }); - } - - #[test] - fn sends_view_updates_to_peers() { - let (oracle, handle) = make_sync_oracle(false); - test_harness(Box::new(oracle), |test_harness| async move { - let TestHarness { mut network_handle, mut virtual_overseer } = test_harness; - - let peer_a = PeerId::random(); - let peer_b = PeerId::random(); - - virtual_overseer.send( - FromOverseer::Signal(OverseerSignal::ActiveLeaves( - ActiveLeavesUpdate { - activated: Default::default(), - deactivated: Default::default(), - } - )) - ).await; - - handle.await_mode_switch().await; - - network_handle.connect_peer( - peer_a.clone(), - PeerSet::Validation, - ObservedRole::Full, - ).await; - network_handle.connect_peer( - peer_b.clone(), - PeerSet::Collation, - ObservedRole::Full, - ).await; - - let actions = network_handle.next_network_actions(2).await; - let wire_message = WireMessage::::ViewUpdate( - View::default(), - ).encode(); - - assert_network_actions_contains( - &actions, - &NetworkAction::WriteNotification( - peer_a, - PeerSet::Validation, - wire_message.clone(), - ), - ); - - assert_network_actions_contains( - &actions, - &NetworkAction::WriteNotification( - peer_b, - PeerSet::Collation, - wire_message.clone(), - ), - ); - - let hash_a = Hash::repeat_byte(1); - - virtual_overseer.send( - FromOverseer::Signal(OverseerSignal::ActiveLeaves( - ActiveLeavesUpdate::start_work(ActivatedLeaf { - hash: hash_a, - number: 1, - status: LeafStatus::Fresh, - span: Arc::new(jaeger::Span::Disabled), - }) - )) - ).await; - - let actions = network_handle.next_network_actions(2).await; - let wire_message = WireMessage::::ViewUpdate( - view![hash_a] - ).encode(); - - assert_network_actions_contains( - &actions, - &NetworkAction::WriteNotification( - peer_a, - PeerSet::Validation, - wire_message.clone(), - ), - ); - - assert_network_actions_contains( - &actions, - &NetworkAction::WriteNotification( - peer_b, - PeerSet::Collation, - wire_message.clone(), - ), - ); - virtual_overseer - }); - } - - #[test] - fn do_not_send_view_update_until_synced() { - let (oracle, handle) = make_sync_oracle(true); - test_harness(Box::new(oracle), |test_harness| async move { - let TestHarness { mut network_handle, mut virtual_overseer } = test_harness; - - let peer_a = PeerId::random(); - let peer_b = PeerId::random(); - - network_handle.connect_peer( - peer_a.clone(), - PeerSet::Validation, - ObservedRole::Full, - ).await; - network_handle.connect_peer( - peer_b.clone(), - PeerSet::Collation, - ObservedRole::Full, - ).await; - - { - let actions = network_handle.next_network_actions(2).await; - let wire_message = WireMessage::::ViewUpdate( - View::default(), - ).encode(); - - assert_network_actions_contains( - &actions, - &NetworkAction::WriteNotification( - peer_a, - PeerSet::Validation, - wire_message.clone(), - ), - ); - - assert_network_actions_contains( - &actions, - &NetworkAction::WriteNotification( - peer_b, - PeerSet::Collation, - wire_message.clone(), - ), - ); - } - - let hash_a = Hash::repeat_byte(1); - let hash_b = Hash::repeat_byte(1); - - virtual_overseer.send( - FromOverseer::Signal(OverseerSignal::ActiveLeaves( - ActiveLeavesUpdate::start_work(ActivatedLeaf { - hash: hash_a, - number: 1, - status: LeafStatus::Fresh, - span: Arc::new(jaeger::Span::Disabled), - }) - )) - ).await; - - // delay until the previous update has certainly been processed. - futures_timer::Delay::new(std::time::Duration::from_millis(100)).await; - - handle.set_done(); - - virtual_overseer.send( - FromOverseer::Signal(OverseerSignal::ActiveLeaves( - ActiveLeavesUpdate::start_work(ActivatedLeaf { - hash: hash_b, - number: 1, - status: LeafStatus::Fresh, - span: Arc::new(jaeger::Span::Disabled), - }) - )) - ).await; - - handle.await_mode_switch().await; - - // There should be a mode switch only for the second view update. - { - let actions = network_handle.next_network_actions(2).await; - let wire_message = WireMessage::::ViewUpdate( - view![hash_a, hash_b] - ).encode(); - - assert_network_actions_contains( - &actions, - &NetworkAction::WriteNotification( - peer_a, - PeerSet::Validation, - wire_message.clone(), - ), - ); - - assert_network_actions_contains( - &actions, - &NetworkAction::WriteNotification( - peer_b, - PeerSet::Collation, - wire_message.clone(), - ), - ); - } - virtual_overseer - }); - } - - #[test] - fn do_not_send_view_update_when_only_finalized_block_changed() { - test_harness(done_syncing_oracle(), |test_harness| async move { - let TestHarness { mut network_handle, mut virtual_overseer } = test_harness; - - let peer_a = PeerId::random(); - let peer_b = PeerId::random(); - - network_handle.connect_peer( - peer_a.clone(), - PeerSet::Validation, - ObservedRole::Full, - ).await; - network_handle.connect_peer( - peer_b.clone(), - PeerSet::Validation, - ObservedRole::Full, - ).await; - - let hash_a = Hash::repeat_byte(1); - - virtual_overseer.send(FromOverseer::Signal(OverseerSignal::BlockFinalized(Hash::random(), 5))).await; - - // Send some empty active leaves update - // - // This should not trigger a view update to our peers. - virtual_overseer.send( - FromOverseer::Signal(OverseerSignal::ActiveLeaves(ActiveLeavesUpdate::default())) - ).await; - - // This should trigger the view update to our peers. - virtual_overseer.send( - FromOverseer::Signal(OverseerSignal::ActiveLeaves( - ActiveLeavesUpdate::start_work(ActivatedLeaf { - hash: hash_a, - number: 1, - status: LeafStatus::Fresh, - span: Arc::new(jaeger::Span::Disabled), - }) - )) - ).await; - - let actions = network_handle.next_network_actions(4).await; - let wire_message = WireMessage::::ViewUpdate( - View::new(vec![hash_a], 5) - ).encode(); - - assert_network_actions_contains( - &actions, - &NetworkAction::WriteNotification( - peer_a, - PeerSet::Validation, - wire_message.clone(), - ), - ); - - assert_network_actions_contains( - &actions, - &NetworkAction::WriteNotification( - peer_b, - PeerSet::Validation, - wire_message.clone(), - ), - ); - virtual_overseer - }); - } - - #[test] - fn peer_view_updates_sent_via_overseer() { - test_harness(done_syncing_oracle(), |test_harness| async move { - let TestHarness { - mut network_handle, - mut virtual_overseer, - } = test_harness; - - let peer = PeerId::random(); - - network_handle.connect_peer(peer.clone(), PeerSet::Validation, ObservedRole::Full).await; - - let view = view![Hash::repeat_byte(1)]; - - assert_matches!( - virtual_overseer.recv().await, - AllMessages::StatementDistribution( - StatementDistributionMessage::StatementFetchingReceiver(_) - ) - ); - - // bridge will inform about all connected peers. - { - assert_sends_validation_event_to_all( - NetworkBridgeEvent::PeerConnected(peer.clone(), ObservedRole::Full, None), - &mut virtual_overseer, - ).await; - - assert_sends_validation_event_to_all( - NetworkBridgeEvent::PeerViewChange(peer.clone(), View::default()), - &mut virtual_overseer, - ).await; - } - - network_handle.peer_message( - peer.clone(), - PeerSet::Validation, - WireMessage::::ViewUpdate( - view.clone(), - ).encode(), - ).await; - - assert_sends_validation_event_to_all( - NetworkBridgeEvent::PeerViewChange(peer.clone(), view), - &mut virtual_overseer, - ).await; - virtual_overseer - }); - } - - #[test] - fn peer_messages_sent_via_overseer() { - test_harness(done_syncing_oracle(), |test_harness| async move { - let TestHarness { - mut network_handle, - mut virtual_overseer, - } = test_harness; - - let peer = PeerId::random(); - - network_handle.connect_peer( - peer.clone(), - PeerSet::Validation, - ObservedRole::Full, - ).await; - - assert_matches!( - virtual_overseer.recv().await, - AllMessages::StatementDistribution( - StatementDistributionMessage::StatementFetchingReceiver(_) - ) - ); - - // bridge will inform about all connected peers. - { - assert_sends_validation_event_to_all( - NetworkBridgeEvent::PeerConnected(peer.clone(), ObservedRole::Full, None), - &mut virtual_overseer, - ).await; - - assert_sends_validation_event_to_all( - NetworkBridgeEvent::PeerViewChange(peer.clone(), View::default()), - &mut virtual_overseer, - ).await; - } - - let approval_distribution_message = protocol_v1::ApprovalDistributionMessage::Approvals( - Vec::new() - ); - - let message = protocol_v1::ValidationProtocol::ApprovalDistribution( - approval_distribution_message.clone(), - ); - - network_handle.peer_message( - peer.clone(), - PeerSet::Validation, - WireMessage::ProtocolMessage(message.clone()).encode(), - ).await; - - network_handle.disconnect_peer(peer.clone(), PeerSet::Validation).await; - - // Approval distribution message comes first, and the message is only sent to that subsystem. - // then a disconnection event arises that is sent to all validation networking subsystems. - - assert_matches!( - virtual_overseer.recv().await, - AllMessages::ApprovalDistribution( - ApprovalDistributionMessage::NetworkBridgeUpdateV1( - NetworkBridgeEvent::PeerMessage(p, m) - ) - ) => { - assert_eq!(p, peer); - assert_eq!(m, approval_distribution_message); - } - ); - - assert_sends_validation_event_to_all( - NetworkBridgeEvent::PeerDisconnected(peer), - &mut virtual_overseer, - ).await; - virtual_overseer - }); - } - - #[test] - fn peer_disconnect_from_just_one_peerset() { - test_harness(done_syncing_oracle(), |test_harness| async move { - let TestHarness { - mut network_handle, - mut virtual_overseer, - } = test_harness; - - let peer = PeerId::random(); - - network_handle.connect_peer(peer.clone(), PeerSet::Validation, ObservedRole::Full).await; - network_handle.connect_peer(peer.clone(), PeerSet::Collation, ObservedRole::Full).await; - - assert_matches!( - virtual_overseer.recv().await, - AllMessages::StatementDistribution( - StatementDistributionMessage::StatementFetchingReceiver(_) - ) - ); - - // bridge will inform about all connected peers. - { - assert_sends_validation_event_to_all( - NetworkBridgeEvent::PeerConnected(peer.clone(), ObservedRole::Full, None), - &mut virtual_overseer, - ).await; - - assert_sends_validation_event_to_all( - NetworkBridgeEvent::PeerViewChange(peer.clone(), View::default()), - &mut virtual_overseer, - ).await; - } - - { - assert_sends_collation_event_to_all( - NetworkBridgeEvent::PeerConnected(peer.clone(), ObservedRole::Full, None), - &mut virtual_overseer, - ).await; - - assert_sends_collation_event_to_all( - NetworkBridgeEvent::PeerViewChange(peer.clone(), View::default()), - &mut virtual_overseer, - ).await; - } - - network_handle.disconnect_peer(peer.clone(), PeerSet::Validation).await; - - assert_sends_validation_event_to_all( - NetworkBridgeEvent::PeerDisconnected(peer.clone()), - &mut virtual_overseer, - ).await; - - // to show that we're still connected on the collation protocol, send a view update. - - let hash_a = Hash::repeat_byte(1); - - virtual_overseer.send( - FromOverseer::Signal(OverseerSignal::ActiveLeaves( - ActiveLeavesUpdate::start_work(ActivatedLeaf { - hash: hash_a, - number: 1, - status: LeafStatus::Fresh, - span: Arc::new(jaeger::Span::Disabled), - }) - )) - ).await; - - let actions = network_handle.next_network_actions(3).await; - let wire_message = WireMessage::::ViewUpdate( - view![hash_a] - ).encode(); - - assert_network_actions_contains( - &actions, - &NetworkAction::WriteNotification( - peer.clone(), - PeerSet::Collation, - wire_message.clone(), - ), - ); - virtual_overseer - }); - } - - #[test] - fn relays_collation_protocol_messages() { - test_harness(done_syncing_oracle(), |test_harness| async move { - let TestHarness { - mut network_handle, - mut virtual_overseer, - } = test_harness; - - let peer_a = PeerId::random(); - let peer_b = PeerId::random(); - - assert_matches!( - virtual_overseer.recv().await, - AllMessages::StatementDistribution( - StatementDistributionMessage::StatementFetchingReceiver(_) - ) - ); - - network_handle.connect_peer(peer_a.clone(), PeerSet::Validation, ObservedRole::Full).await; - network_handle.connect_peer(peer_b.clone(), PeerSet::Collation, ObservedRole::Full).await; - - // bridge will inform about all connected peers. - { - assert_sends_validation_event_to_all( - NetworkBridgeEvent::PeerConnected(peer_a.clone(), ObservedRole::Full, None), - &mut virtual_overseer, - ).await; - - assert_sends_validation_event_to_all( - NetworkBridgeEvent::PeerViewChange(peer_a.clone(), View::default()), - &mut virtual_overseer, - ).await; - } - - { - assert_sends_collation_event_to_all( - NetworkBridgeEvent::PeerConnected(peer_b.clone(), ObservedRole::Full, None), - &mut virtual_overseer, - ).await; - - assert_sends_collation_event_to_all( - NetworkBridgeEvent::PeerViewChange(peer_b.clone(), View::default()), - &mut virtual_overseer, - ).await; - } - - // peer A gets reported for sending a collation message. - - let collator_protocol_message = protocol_v1::CollatorProtocolMessage::Declare( - Sr25519Keyring::Alice.public().into(), - Default::default(), - Default::default(), - ); - - let message = protocol_v1::CollationProtocol::CollatorProtocol( - collator_protocol_message.clone() - ); - - network_handle.peer_message( - peer_a.clone(), - PeerSet::Collation, - WireMessage::ProtocolMessage(message.clone()).encode(), - ).await; - - let actions = network_handle.next_network_actions(3).await; - assert_network_actions_contains( - &actions, - &NetworkAction::ReputationChange( - peer_a.clone(), - UNCONNECTED_PEERSET_COST, - ), - ); - - // peer B has the message relayed. - - network_handle.peer_message( - peer_b.clone(), - PeerSet::Collation, - WireMessage::ProtocolMessage(message.clone()).encode(), - ).await; - - assert_matches!( - virtual_overseer.recv().await, - AllMessages::CollatorProtocol( - CollatorProtocolMessage::NetworkBridgeUpdateV1( - NetworkBridgeEvent::PeerMessage(p, m) - ) - ) => { - assert_eq!(p, peer_b); - assert_eq!(m, collator_protocol_message); - } - ); - virtual_overseer - }); - } - - #[test] - fn different_views_on_different_peer_sets() { - test_harness(done_syncing_oracle(), |test_harness| async move { - let TestHarness { - mut network_handle, - mut virtual_overseer, - } = test_harness; - - let peer = PeerId::random(); - - network_handle.connect_peer(peer.clone(), PeerSet::Validation, ObservedRole::Full).await; - network_handle.connect_peer(peer.clone(), PeerSet::Collation, ObservedRole::Full).await; - - assert_matches!( - virtual_overseer.recv().await, - AllMessages::StatementDistribution( - StatementDistributionMessage::StatementFetchingReceiver(_) - ) - ); - - // bridge will inform about all connected peers. - { - assert_sends_validation_event_to_all( - NetworkBridgeEvent::PeerConnected(peer.clone(), ObservedRole::Full, None), - &mut virtual_overseer, - ).await; - - assert_sends_validation_event_to_all( - NetworkBridgeEvent::PeerViewChange(peer.clone(), View::default()), - &mut virtual_overseer, - ).await; - } - - { - assert_sends_collation_event_to_all( - NetworkBridgeEvent::PeerConnected(peer.clone(), ObservedRole::Full, None), - &mut virtual_overseer, - ).await; - - assert_sends_collation_event_to_all( - NetworkBridgeEvent::PeerViewChange(peer.clone(), View::default()), - &mut virtual_overseer, - ).await; - } - - let view_a = view![Hash::repeat_byte(1)]; - let view_b = view![Hash::repeat_byte(2)]; - - network_handle.peer_message( - peer.clone(), - PeerSet::Validation, - WireMessage::::ViewUpdate(view_a.clone()).encode(), - ).await; - - network_handle.peer_message( - peer.clone(), - PeerSet::Collation, - WireMessage::::ViewUpdate(view_b.clone()).encode(), - ).await; - - assert_sends_validation_event_to_all( - NetworkBridgeEvent::PeerViewChange(peer.clone(), view_a.clone()), - &mut virtual_overseer, - ).await; - - assert_sends_collation_event_to_all( - NetworkBridgeEvent::PeerViewChange(peer.clone(), view_b.clone()), - &mut virtual_overseer, - ).await; - virtual_overseer - }); - } - - #[test] - fn sent_views_include_finalized_number_update() { - test_harness(done_syncing_oracle(), |test_harness| async move { - let TestHarness { mut network_handle, mut virtual_overseer } = test_harness; - - let peer_a = PeerId::random(); - - network_handle.connect_peer( - peer_a.clone(), - PeerSet::Validation, - ObservedRole::Full, - ).await; - - let hash_a = Hash::repeat_byte(1); - let hash_b = Hash::repeat_byte(2); - - virtual_overseer.send( - FromOverseer::Signal(OverseerSignal::BlockFinalized(hash_a, 1)) - ).await; - virtual_overseer.send( - FromOverseer::Signal(OverseerSignal::ActiveLeaves( - ActiveLeavesUpdate::start_work(ActivatedLeaf { - hash: hash_b, - number: 1, - status: LeafStatus::Fresh, - span: Arc::new(jaeger::Span::Disabled), - }) - )) - ).await; - - let actions = network_handle.next_network_actions(2).await; - let wire_message = WireMessage::::ViewUpdate( - View::new(vec![hash_b], 1) - ).encode(); - - assert_network_actions_contains( - &actions, - &NetworkAction::WriteNotification( - peer_a.clone(), - PeerSet::Validation, - wire_message.clone(), - ), - ); - virtual_overseer - }); - } - - #[test] - fn view_finalized_number_can_not_go_down() { - test_harness(done_syncing_oracle(), |test_harness| async move { - let TestHarness { mut network_handle, virtual_overseer } = test_harness; - - let peer_a = PeerId::random(); - - network_handle.connect_peer( - peer_a.clone(), - PeerSet::Validation, - ObservedRole::Full, - ).await; - - network_handle.peer_message( - peer_a.clone(), - PeerSet::Validation, - WireMessage::::ViewUpdate( - View::new(vec![Hash::repeat_byte(0x01)], 1), - ).encode(), - ).await; - - network_handle.peer_message( - peer_a.clone(), - PeerSet::Validation, - WireMessage::::ViewUpdate( - View::new(vec![], 0), - ).encode(), - ).await; - - let actions = network_handle.next_network_actions(2).await; - assert_network_actions_contains( - &actions, - &NetworkAction::ReputationChange( - peer_a.clone(), - MALFORMED_VIEW_COST, - ), - ); - virtual_overseer - }); - } - - #[test] - fn send_messages_to_peers() { - test_harness(done_syncing_oracle(), |test_harness| async move { - let TestHarness { - mut network_handle, - mut virtual_overseer, - } = test_harness; - - let peer = PeerId::random(); - - network_handle.connect_peer(peer.clone(), PeerSet::Validation, ObservedRole::Full).await; - network_handle.connect_peer(peer.clone(), PeerSet::Collation, ObservedRole::Full).await; - - assert_matches!( - virtual_overseer.recv().await, - AllMessages::StatementDistribution( - StatementDistributionMessage::StatementFetchingReceiver(_) - ) - ); - - // bridge will inform about all connected peers. - { - assert_sends_validation_event_to_all( - NetworkBridgeEvent::PeerConnected(peer.clone(), ObservedRole::Full, None), - &mut virtual_overseer, - ).await; - - assert_sends_validation_event_to_all( - NetworkBridgeEvent::PeerViewChange(peer.clone(), View::default()), - &mut virtual_overseer, - ).await; - } - - { - assert_sends_collation_event_to_all( - NetworkBridgeEvent::PeerConnected(peer.clone(), ObservedRole::Full, None), - &mut virtual_overseer, - ).await; - - assert_sends_collation_event_to_all( - NetworkBridgeEvent::PeerViewChange(peer.clone(), View::default()), - &mut virtual_overseer, - ).await; - } - - // consume peer view changes - { - let _peer_view_changes = network_handle.next_network_actions(2).await; - } - - // send a validation protocol message. - - { - let approval_distribution_message = protocol_v1::ApprovalDistributionMessage::Approvals( - Vec::new() - ); - - let message = protocol_v1::ValidationProtocol::ApprovalDistribution( - approval_distribution_message.clone(), - ); - - virtual_overseer.send(FromOverseer::Communication { - msg: NetworkBridgeMessage::SendValidationMessage( - vec![peer.clone()], - message.clone(), - ) - }).await; - - assert_eq!( - network_handle.next_network_action().await, - NetworkAction::WriteNotification( - peer.clone(), - PeerSet::Validation, - WireMessage::ProtocolMessage(message).encode(), - ) - ); - } - - // send a collation protocol message. - - { - let collator_protocol_message = protocol_v1::CollatorProtocolMessage::Declare( - Sr25519Keyring::Alice.public().into(), - Default::default(), - Default::default(), - ); - - let message = protocol_v1::CollationProtocol::CollatorProtocol( - collator_protocol_message.clone() - ); - - virtual_overseer.send(FromOverseer::Communication { - msg: NetworkBridgeMessage::SendCollationMessage( - vec![peer.clone()], - message.clone(), - ) - }).await; - - assert_eq!( - network_handle.next_network_action().await, - NetworkAction::WriteNotification( - peer.clone(), - PeerSet::Collation, - WireMessage::ProtocolMessage(message).encode(), - ) - ); - } - virtual_overseer - }); - } - - #[test] - fn spread_event_to_subsystems_is_up_to_date() { - // Number of subsystems expected to be interested in a network event, - // and hence the network event broadcasted to. - const EXPECTED_COUNT: usize = 3; - - let mut cnt = 0_usize; - for msg in AllMessages::dispatch_iter(NetworkBridgeEvent::PeerDisconnected(PeerId::random())) { - match msg { - AllMessages::CandidateValidation(_) => unreachable!("Not interested in network events"), - AllMessages::CandidateBacking(_) => unreachable!("Not interested in network events"), - AllMessages::ChainApi(_) => unreachable!("Not interested in network events"), - AllMessages::CollatorProtocol(_) => unreachable!("Not interested in network events"), - AllMessages::StatementDistribution(_) => { cnt += 1; } - AllMessages::AvailabilityDistribution(_) => unreachable!("Not interested in network events"), - AllMessages::AvailabilityRecovery(_) => unreachable!("Not interested in network events"), - AllMessages::BitfieldDistribution(_) => { cnt += 1; } - AllMessages::BitfieldSigning(_) => unreachable!("Not interested in network events"), - AllMessages::Provisioner(_) => unreachable!("Not interested in network events"), - AllMessages::RuntimeApi(_) => unreachable!("Not interested in network events"), - AllMessages::AvailabilityStore(_) => unreachable!("Not interested in network events"), - AllMessages::NetworkBridge(_) => unreachable!("Not interested in network events"), - AllMessages::CollationGeneration(_) => unreachable!("Not interested in network events"), - AllMessages::ApprovalVoting(_) => unreachable!("Not interested in network events"), - AllMessages::ApprovalDistribution(_) => { cnt += 1; } - AllMessages::GossipSupport(_) => unreachable!("Not interested 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); - } - - #[test] - fn our_view_updates_decreasing_order_and_limited_to_max() { - test_harness(done_syncing_oracle(), |test_harness| async move { - let TestHarness { - mut virtual_overseer, - .. - } = test_harness; - - - // to show that we're still connected on the collation protocol, send a view update. - - let hashes = (0..MAX_VIEW_HEADS * 3).map(|i| Hash::repeat_byte(i as u8)); - - virtual_overseer.send( - FromOverseer::Signal(OverseerSignal::ActiveLeaves( - // These are in reverse order, so the subsystem must sort internally to - // get the correct view. - ActiveLeavesUpdate { - activated: hashes.enumerate().map(|(i, h)| ActivatedLeaf { - hash: h, - number: i as _, - status: LeafStatus::Fresh, - span: Arc::new(jaeger::Span::Disabled), - }).rev().collect(), - deactivated: Default::default(), - } - )) - ).await; - - let view_heads = (MAX_VIEW_HEADS * 2 .. MAX_VIEW_HEADS * 3).rev() - .map(|i| (Hash::repeat_byte(i as u8), Arc::new(jaeger::Span::Disabled)) ); - - let our_view = OurView::new( - view_heads, - 0, - ); - - assert_matches!( - virtual_overseer.recv().await, - AllMessages::StatementDistribution( - StatementDistributionMessage::StatementFetchingReceiver(_) - ) - ); - - assert_sends_validation_event_to_all( - NetworkBridgeEvent::OurViewChange(our_view.clone()), - &mut virtual_overseer, - ).await; - - assert_sends_collation_event_to_all( - NetworkBridgeEvent::OurViewChange(our_view), - &mut virtual_overseer, - ).await; - virtual_overseer - }); - } -} +mod tests; diff --git a/polkadot/node/network/bridge/src/tests.rs b/polkadot/node/network/bridge/src/tests.rs new file mode 100644 index 0000000000..4fceeb212c --- /dev/null +++ b/polkadot/node/network/bridge/src/tests.rs @@ -0,0 +1,1315 @@ +// Copyright 2020 Parity Technologies (UK) Ltd. +// This file is part of Polkadot. + +// Polkadot is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Polkadot is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Polkadot. If not, see . + +use super::*; +use futures::executor; +use futures::stream::BoxStream; +use futures::channel::oneshot; + +use std::borrow::Cow; +use std::collections::HashSet; +use std::pin::Pin; +use std::sync::atomic::{AtomicBool, Ordering}; +use async_trait::async_trait; +use parking_lot::Mutex; +use assert_matches::assert_matches; + +use sc_network::{Event as NetworkEvent, IfDisconnected}; + +use polkadot_subsystem::{jaeger, ActiveLeavesUpdate, FromOverseer, OverseerSignal, LeafStatus}; +use polkadot_subsystem::messages::{ + ApprovalDistributionMessage, + BitfieldDistributionMessage, + StatementDistributionMessage +}; +use polkadot_node_subsystem_test_helpers::{ + SingleItemSink, SingleItemStream, TestSubsystemContextHandle, +}; +use polkadot_node_subsystem_util::metered; +use polkadot_node_network_protocol::view; +use sc_network::Multiaddr; +use sc_network::config::RequestResponseConfig; +use sp_keyring::Sr25519Keyring; +use polkadot_primitives::v1::AuthorityDiscoveryId; +use polkadot_node_network_protocol::{ObservedRole, request_response::request::Requests}; + +use crate::network::{Network, NetworkAction}; +use crate::validator_discovery::AuthorityDiscovery; + +// The subsystem's view of the network - only supports a single call to `event_stream`. +#[derive(Clone)] +struct TestNetwork { + net_events: Arc>>>, + action_tx: metered::UnboundedMeteredSender, + _req_configs: Vec, +} + +#[derive(Clone)] +struct TestAuthorityDiscovery; + +// The test's view of the network. This receives updates from the subsystem in the form +// of `NetworkAction`s. +struct TestNetworkHandle { + action_rx: metered::UnboundedMeteredReceiver, + net_tx: SingleItemSink, +} + +fn new_test_network(req_configs: Vec) -> ( + TestNetwork, + TestNetworkHandle, + TestAuthorityDiscovery, +) { + let (net_tx, net_rx) = polkadot_node_subsystem_test_helpers::single_item_sink(); + let (action_tx, action_rx) = metered::unbounded(); + + ( + TestNetwork { + net_events: Arc::new(Mutex::new(Some(net_rx))), + action_tx, + _req_configs: req_configs, + }, + TestNetworkHandle { + action_rx, + net_tx, + }, + TestAuthorityDiscovery, + ) +} + +#[async_trait] +impl Network for TestNetwork { + fn event_stream(&mut self) -> BoxStream<'static, NetworkEvent> { + self.net_events.lock() + .take() + .expect("Subsystem made more than one call to `event_stream`") + .boxed() + } + + async fn add_to_peers_set(&mut self, _protocol: Cow<'static, str>, _: HashSet) -> Result<(), String> { + Ok(()) + } + + async fn remove_from_peers_set(&mut self, _protocol: Cow<'static, str>, _: HashSet) -> Result<(), String> { + Ok(()) + } + + fn action_sink<'a>(&'a mut self) + -> Pin + Send + 'a>> + { + Box::pin((&mut self.action_tx).sink_map_err(Into::into)) + } + + async fn start_request(&self, _: &mut AD, _: Requests, _: IfDisconnected) { + } +} + +#[async_trait] +impl validator_discovery::AuthorityDiscovery for TestAuthorityDiscovery { + async fn get_addresses_by_authority_id(&mut self, _authority: AuthorityDiscoveryId) -> Option> { + None + } + + async fn get_authority_id_by_peer_id(&mut self, _peer_id: PeerId) -> Option { + None + } +} + +impl TestNetworkHandle { + // Get the next network action. + async fn next_network_action(&mut self) -> NetworkAction { + self.action_rx.next().await.expect("subsystem concluded early") + } + + // Wait for the next N network actions. + async fn next_network_actions(&mut self, n: usize) -> Vec { + let mut v = Vec::with_capacity(n); + for _ in 0..n { + v.push(self.next_network_action().await); + } + + v + } + + async fn connect_peer(&mut self, peer: PeerId, peer_set: PeerSet, role: ObservedRole) { + self.send_network_event(NetworkEvent::NotificationStreamOpened { + remote: peer, + protocol: peer_set.into_protocol_name(), + negotiated_fallback: None, + role: role.into(), + }).await; + } + + async fn disconnect_peer(&mut self, peer: PeerId, peer_set: PeerSet) { + self.send_network_event(NetworkEvent::NotificationStreamClosed { + remote: peer, + protocol: peer_set.into_protocol_name(), + }).await; + } + + async fn peer_message(&mut self, peer: PeerId, peer_set: PeerSet, message: Vec) { + self.send_network_event(NetworkEvent::NotificationsReceived { + remote: peer, + messages: vec![(peer_set.into_protocol_name(), message.into())], + }).await; + } + + async fn send_network_event(&mut self, event: NetworkEvent) { + self.net_tx.send(event).await.expect("subsystem concluded early"); + } +} + +/// Assert that the given actions contain the given `action`. +fn assert_network_actions_contains(actions: &[NetworkAction], action: &NetworkAction) { + if !actions.iter().any(|x| x == action) { + panic!("Could not find `{:?}` in `{:?}`", action, actions); + } +} + +#[derive(Clone)] +struct TestSyncOracle { + flag: Arc, + done_syncing_sender: Arc>>>, +} + +struct TestSyncOracleHandle { + done_syncing_receiver: oneshot::Receiver<()>, + flag: Arc, +} + +impl TestSyncOracleHandle { + fn set_done(&self) { + self.flag.store(false, Ordering::SeqCst); + } + + async fn await_mode_switch(self) { + let _ = self.done_syncing_receiver.await; + } +} + +impl SyncOracle for TestSyncOracle { + fn is_major_syncing(&mut self) -> bool { + let is_major_syncing = self.flag.load(Ordering::SeqCst); + + if !is_major_syncing { + if let Some(sender) = self.done_syncing_sender.lock().take() { + let _ = sender.send(()); + } + } + + is_major_syncing + } + + fn is_offline(&mut self) -> bool { + unimplemented!("not used in network bridge") + } +} + +// val - result of `is_major_syncing`. +fn make_sync_oracle(val: bool) -> (TestSyncOracle, TestSyncOracleHandle) { + let (tx, rx) = oneshot::channel(); + let flag = Arc::new(AtomicBool::new(val)); + + ( + TestSyncOracle { + flag: flag.clone(), + done_syncing_sender: Arc::new(Mutex::new(Some(tx))), + }, + TestSyncOracleHandle { + flag, + done_syncing_receiver: rx, + } + ) +} + +fn done_syncing_oracle() -> Box { + let (oracle, _) = make_sync_oracle(false); + Box::new(oracle) +} + +type VirtualOverseer = TestSubsystemContextHandle; + +struct TestHarness { + network_handle: TestNetworkHandle, + virtual_overseer: VirtualOverseer, +} + +fn test_harness>( + sync_oracle: Box, + test: impl FnOnce(TestHarness) -> T, +) { + let pool = sp_core::testing::TaskExecutor::new(); + let (request_multiplexer, req_configs) = RequestMultiplexer::new(); + let (mut network, network_handle, discovery) = new_test_network(req_configs); + let (context, virtual_overseer) = polkadot_node_subsystem_test_helpers::make_subsystem_context(pool); + let network_stream = network.event_stream(); + + let bridge = NetworkBridge { + network_service: network, + authority_discovery_service: discovery, + request_multiplexer, + metrics: Metrics(None), + sync_oracle, + }; + + let network_bridge = run_network( + bridge, + context, + network_stream, + ) + .map_err(|_| panic!("subsystem execution failed")) + .map(|_| ()); + + let test_fut = test(TestHarness { + network_handle, + virtual_overseer, + }); + + futures::pin_mut!(test_fut); + futures::pin_mut!(network_bridge); + + let _ = executor::block_on(future::join(async move { + let mut virtual_overseer = test_fut.await; + virtual_overseer.send(FromOverseer::Signal(OverseerSignal::Conclude)).await; + }, network_bridge)); +} + +async fn assert_sends_validation_event_to_all( + event: NetworkBridgeEvent, + virtual_overseer: &mut TestSubsystemContextHandle, +) { + // Ordering must match the enum variant order + // in `AllMessages`. + assert_matches!( + virtual_overseer.recv().await, + AllMessages::StatementDistribution( + StatementDistributionMessage::NetworkBridgeUpdateV1(e) + ) if e == event.focus().expect("could not focus message") + ); + + assert_matches!( + virtual_overseer.recv().await, + AllMessages::BitfieldDistribution( + BitfieldDistributionMessage::NetworkBridgeUpdateV1(e) + ) if e == event.focus().expect("could not focus message") + ); + + assert_matches!( + virtual_overseer.recv().await, + AllMessages::ApprovalDistribution( + ApprovalDistributionMessage::NetworkBridgeUpdateV1(e) + ) if e == event.focus().expect("could not focus message") + ); +} + +async fn assert_sends_collation_event_to_all( + event: NetworkBridgeEvent, + virtual_overseer: &mut TestSubsystemContextHandle, +) { + assert_matches!( + virtual_overseer.recv().await, + AllMessages::CollatorProtocol( + CollatorProtocolMessage::NetworkBridgeUpdateV1(e) + ) if e == event.focus().expect("could not focus message") + ) +} + +#[test] +fn send_our_view_upon_connection() { + let (oracle, handle) = make_sync_oracle(false); + test_harness(Box::new(oracle), |test_harness| async move { + let TestHarness { + mut network_handle, + mut virtual_overseer, + } = test_harness; + + let peer = PeerId::random(); + + let head = Hash::repeat_byte(1); + virtual_overseer.send( + FromOverseer::Signal(OverseerSignal::ActiveLeaves( + ActiveLeavesUpdate::start_work(ActivatedLeaf { + hash: head, + number: 1, + status: LeafStatus::Fresh, + span: Arc::new(jaeger::Span::Disabled), + }) + )) + ).await; + + handle.await_mode_switch().await; + + network_handle.connect_peer(peer.clone(), PeerSet::Validation, ObservedRole::Full).await; + network_handle.connect_peer(peer.clone(), PeerSet::Collation, ObservedRole::Full).await; + + let view = view![head]; + let actions = network_handle.next_network_actions(2).await; + assert_network_actions_contains( + &actions, + &NetworkAction::WriteNotification( + peer.clone(), + PeerSet::Validation, + WireMessage::::ViewUpdate( + view.clone(), + ).encode(), + ), + ); + assert_network_actions_contains( + &actions, + &NetworkAction::WriteNotification( + peer.clone(), + PeerSet::Collation, + WireMessage::::ViewUpdate( + view.clone(), + ).encode(), + ), + ); + virtual_overseer + }); +} + +#[test] +fn sends_view_updates_to_peers() { + let (oracle, handle) = make_sync_oracle(false); + test_harness(Box::new(oracle), |test_harness| async move { + let TestHarness { mut network_handle, mut virtual_overseer } = test_harness; + + let peer_a = PeerId::random(); + let peer_b = PeerId::random(); + + virtual_overseer.send( + FromOverseer::Signal(OverseerSignal::ActiveLeaves( + ActiveLeavesUpdate { + activated: Default::default(), + deactivated: Default::default(), + } + )) + ).await; + + handle.await_mode_switch().await; + + network_handle.connect_peer( + peer_a.clone(), + PeerSet::Validation, + ObservedRole::Full, + ).await; + network_handle.connect_peer( + peer_b.clone(), + PeerSet::Collation, + ObservedRole::Full, + ).await; + + let actions = network_handle.next_network_actions(2).await; + let wire_message = WireMessage::::ViewUpdate( + View::default(), + ).encode(); + + assert_network_actions_contains( + &actions, + &NetworkAction::WriteNotification( + peer_a, + PeerSet::Validation, + wire_message.clone(), + ), + ); + + assert_network_actions_contains( + &actions, + &NetworkAction::WriteNotification( + peer_b, + PeerSet::Collation, + wire_message.clone(), + ), + ); + + let hash_a = Hash::repeat_byte(1); + + virtual_overseer.send( + FromOverseer::Signal(OverseerSignal::ActiveLeaves( + ActiveLeavesUpdate::start_work(ActivatedLeaf { + hash: hash_a, + number: 1, + status: LeafStatus::Fresh, + span: Arc::new(jaeger::Span::Disabled), + }) + )) + ).await; + + let actions = network_handle.next_network_actions(2).await; + let wire_message = WireMessage::::ViewUpdate( + view![hash_a] + ).encode(); + + assert_network_actions_contains( + &actions, + &NetworkAction::WriteNotification( + peer_a, + PeerSet::Validation, + wire_message.clone(), + ), + ); + + assert_network_actions_contains( + &actions, + &NetworkAction::WriteNotification( + peer_b, + PeerSet::Collation, + wire_message.clone(), + ), + ); + virtual_overseer + }); +} + +#[test] +fn do_not_send_view_update_until_synced() { + let (oracle, handle) = make_sync_oracle(true); + test_harness(Box::new(oracle), |test_harness| async move { + let TestHarness { mut network_handle, mut virtual_overseer } = test_harness; + + let peer_a = PeerId::random(); + let peer_b = PeerId::random(); + + network_handle.connect_peer( + peer_a.clone(), + PeerSet::Validation, + ObservedRole::Full, + ).await; + network_handle.connect_peer( + peer_b.clone(), + PeerSet::Collation, + ObservedRole::Full, + ).await; + + { + let actions = network_handle.next_network_actions(2).await; + let wire_message = WireMessage::::ViewUpdate( + View::default(), + ).encode(); + + assert_network_actions_contains( + &actions, + &NetworkAction::WriteNotification( + peer_a, + PeerSet::Validation, + wire_message.clone(), + ), + ); + + assert_network_actions_contains( + &actions, + &NetworkAction::WriteNotification( + peer_b, + PeerSet::Collation, + wire_message.clone(), + ), + ); + } + + let hash_a = Hash::repeat_byte(1); + let hash_b = Hash::repeat_byte(1); + + virtual_overseer.send( + FromOverseer::Signal(OverseerSignal::ActiveLeaves( + ActiveLeavesUpdate::start_work(ActivatedLeaf { + hash: hash_a, + number: 1, + status: LeafStatus::Fresh, + span: Arc::new(jaeger::Span::Disabled), + }) + )) + ).await; + + // delay until the previous update has certainly been processed. + futures_timer::Delay::new(std::time::Duration::from_millis(100)).await; + + handle.set_done(); + + virtual_overseer.send( + FromOverseer::Signal(OverseerSignal::ActiveLeaves( + ActiveLeavesUpdate::start_work(ActivatedLeaf { + hash: hash_b, + number: 1, + status: LeafStatus::Fresh, + span: Arc::new(jaeger::Span::Disabled), + }) + )) + ).await; + + handle.await_mode_switch().await; + + // There should be a mode switch only for the second view update. + { + let actions = network_handle.next_network_actions(2).await; + let wire_message = WireMessage::::ViewUpdate( + view![hash_a, hash_b] + ).encode(); + + assert_network_actions_contains( + &actions, + &NetworkAction::WriteNotification( + peer_a, + PeerSet::Validation, + wire_message.clone(), + ), + ); + + assert_network_actions_contains( + &actions, + &NetworkAction::WriteNotification( + peer_b, + PeerSet::Collation, + wire_message.clone(), + ), + ); + } + virtual_overseer + }); +} + +#[test] +fn do_not_send_view_update_when_only_finalized_block_changed() { + test_harness(done_syncing_oracle(), |test_harness| async move { + let TestHarness { mut network_handle, mut virtual_overseer } = test_harness; + + let peer_a = PeerId::random(); + let peer_b = PeerId::random(); + + network_handle.connect_peer( + peer_a.clone(), + PeerSet::Validation, + ObservedRole::Full, + ).await; + network_handle.connect_peer( + peer_b.clone(), + PeerSet::Validation, + ObservedRole::Full, + ).await; + + let hash_a = Hash::repeat_byte(1); + + virtual_overseer.send(FromOverseer::Signal(OverseerSignal::BlockFinalized(Hash::random(), 5))).await; + + // Send some empty active leaves update + // + // This should not trigger a view update to our peers. + virtual_overseer.send( + FromOverseer::Signal(OverseerSignal::ActiveLeaves(ActiveLeavesUpdate::default())) + ).await; + + // This should trigger the view update to our peers. + virtual_overseer.send( + FromOverseer::Signal(OverseerSignal::ActiveLeaves( + ActiveLeavesUpdate::start_work(ActivatedLeaf { + hash: hash_a, + number: 1, + status: LeafStatus::Fresh, + span: Arc::new(jaeger::Span::Disabled), + }) + )) + ).await; + + let actions = network_handle.next_network_actions(4).await; + let wire_message = WireMessage::::ViewUpdate( + View::new(vec![hash_a], 5) + ).encode(); + + assert_network_actions_contains( + &actions, + &NetworkAction::WriteNotification( + peer_a, + PeerSet::Validation, + wire_message.clone(), + ), + ); + + assert_network_actions_contains( + &actions, + &NetworkAction::WriteNotification( + peer_b, + PeerSet::Validation, + wire_message.clone(), + ), + ); + virtual_overseer + }); +} + +#[test] +fn peer_view_updates_sent_via_overseer() { + test_harness(done_syncing_oracle(), |test_harness| async move { + let TestHarness { + mut network_handle, + mut virtual_overseer, + } = test_harness; + + let peer = PeerId::random(); + + network_handle.connect_peer(peer.clone(), PeerSet::Validation, ObservedRole::Full).await; + + let view = view![Hash::repeat_byte(1)]; + + assert_matches!( + virtual_overseer.recv().await, + AllMessages::StatementDistribution( + StatementDistributionMessage::StatementFetchingReceiver(_) + ) + ); + + // bridge will inform about all connected peers. + { + assert_sends_validation_event_to_all( + NetworkBridgeEvent::PeerConnected(peer.clone(), ObservedRole::Full, None), + &mut virtual_overseer, + ).await; + + assert_sends_validation_event_to_all( + NetworkBridgeEvent::PeerViewChange(peer.clone(), View::default()), + &mut virtual_overseer, + ).await; + } + + network_handle.peer_message( + peer.clone(), + PeerSet::Validation, + WireMessage::::ViewUpdate( + view.clone(), + ).encode(), + ).await; + + assert_sends_validation_event_to_all( + NetworkBridgeEvent::PeerViewChange(peer.clone(), view), + &mut virtual_overseer, + ).await; + virtual_overseer + }); +} + +#[test] +fn peer_messages_sent_via_overseer() { + test_harness(done_syncing_oracle(), |test_harness| async move { + let TestHarness { + mut network_handle, + mut virtual_overseer, + } = test_harness; + + let peer = PeerId::random(); + + network_handle.connect_peer( + peer.clone(), + PeerSet::Validation, + ObservedRole::Full, + ).await; + + assert_matches!( + virtual_overseer.recv().await, + AllMessages::StatementDistribution( + StatementDistributionMessage::StatementFetchingReceiver(_) + ) + ); + + // bridge will inform about all connected peers. + { + assert_sends_validation_event_to_all( + NetworkBridgeEvent::PeerConnected(peer.clone(), ObservedRole::Full, None), + &mut virtual_overseer, + ).await; + + assert_sends_validation_event_to_all( + NetworkBridgeEvent::PeerViewChange(peer.clone(), View::default()), + &mut virtual_overseer, + ).await; + } + + let approval_distribution_message = protocol_v1::ApprovalDistributionMessage::Approvals( + Vec::new() + ); + + let message = protocol_v1::ValidationProtocol::ApprovalDistribution( + approval_distribution_message.clone(), + ); + + network_handle.peer_message( + peer.clone(), + PeerSet::Validation, + WireMessage::ProtocolMessage(message.clone()).encode(), + ).await; + + network_handle.disconnect_peer(peer.clone(), PeerSet::Validation).await; + + // Approval distribution message comes first, and the message is only sent to that subsystem. + // then a disconnection event arises that is sent to all validation networking subsystems. + + assert_matches!( + virtual_overseer.recv().await, + AllMessages::ApprovalDistribution( + ApprovalDistributionMessage::NetworkBridgeUpdateV1( + NetworkBridgeEvent::PeerMessage(p, m) + ) + ) => { + assert_eq!(p, peer); + assert_eq!(m, approval_distribution_message); + } + ); + + assert_sends_validation_event_to_all( + NetworkBridgeEvent::PeerDisconnected(peer), + &mut virtual_overseer, + ).await; + virtual_overseer + }); +} + +#[test] +fn peer_disconnect_from_just_one_peerset() { + test_harness(done_syncing_oracle(), |test_harness| async move { + let TestHarness { + mut network_handle, + mut virtual_overseer, + } = test_harness; + + let peer = PeerId::random(); + + network_handle.connect_peer(peer.clone(), PeerSet::Validation, ObservedRole::Full).await; + network_handle.connect_peer(peer.clone(), PeerSet::Collation, ObservedRole::Full).await; + + assert_matches!( + virtual_overseer.recv().await, + AllMessages::StatementDistribution( + StatementDistributionMessage::StatementFetchingReceiver(_) + ) + ); + + // bridge will inform about all connected peers. + { + assert_sends_validation_event_to_all( + NetworkBridgeEvent::PeerConnected(peer.clone(), ObservedRole::Full, None), + &mut virtual_overseer, + ).await; + + assert_sends_validation_event_to_all( + NetworkBridgeEvent::PeerViewChange(peer.clone(), View::default()), + &mut virtual_overseer, + ).await; + } + + { + assert_sends_collation_event_to_all( + NetworkBridgeEvent::PeerConnected(peer.clone(), ObservedRole::Full, None), + &mut virtual_overseer, + ).await; + + assert_sends_collation_event_to_all( + NetworkBridgeEvent::PeerViewChange(peer.clone(), View::default()), + &mut virtual_overseer, + ).await; + } + + network_handle.disconnect_peer(peer.clone(), PeerSet::Validation).await; + + assert_sends_validation_event_to_all( + NetworkBridgeEvent::PeerDisconnected(peer.clone()), + &mut virtual_overseer, + ).await; + + // to show that we're still connected on the collation protocol, send a view update. + + let hash_a = Hash::repeat_byte(1); + + virtual_overseer.send( + FromOverseer::Signal(OverseerSignal::ActiveLeaves( + ActiveLeavesUpdate::start_work(ActivatedLeaf { + hash: hash_a, + number: 1, + status: LeafStatus::Fresh, + span: Arc::new(jaeger::Span::Disabled), + }) + )) + ).await; + + let actions = network_handle.next_network_actions(3).await; + let wire_message = WireMessage::::ViewUpdate( + view![hash_a] + ).encode(); + + assert_network_actions_contains( + &actions, + &NetworkAction::WriteNotification( + peer.clone(), + PeerSet::Collation, + wire_message.clone(), + ), + ); + virtual_overseer + }); +} + +#[test] +fn relays_collation_protocol_messages() { + test_harness(done_syncing_oracle(), |test_harness| async move { + let TestHarness { + mut network_handle, + mut virtual_overseer, + } = test_harness; + + let peer_a = PeerId::random(); + let peer_b = PeerId::random(); + + assert_matches!( + virtual_overseer.recv().await, + AllMessages::StatementDistribution( + StatementDistributionMessage::StatementFetchingReceiver(_) + ) + ); + + network_handle.connect_peer(peer_a.clone(), PeerSet::Validation, ObservedRole::Full).await; + network_handle.connect_peer(peer_b.clone(), PeerSet::Collation, ObservedRole::Full).await; + + // bridge will inform about all connected peers. + { + assert_sends_validation_event_to_all( + NetworkBridgeEvent::PeerConnected(peer_a.clone(), ObservedRole::Full, None), + &mut virtual_overseer, + ).await; + + assert_sends_validation_event_to_all( + NetworkBridgeEvent::PeerViewChange(peer_a.clone(), View::default()), + &mut virtual_overseer, + ).await; + } + + { + assert_sends_collation_event_to_all( + NetworkBridgeEvent::PeerConnected(peer_b.clone(), ObservedRole::Full, None), + &mut virtual_overseer, + ).await; + + assert_sends_collation_event_to_all( + NetworkBridgeEvent::PeerViewChange(peer_b.clone(), View::default()), + &mut virtual_overseer, + ).await; + } + + // peer A gets reported for sending a collation message. + + let collator_protocol_message = protocol_v1::CollatorProtocolMessage::Declare( + Sr25519Keyring::Alice.public().into(), + Default::default(), + Default::default(), + ); + + let message = protocol_v1::CollationProtocol::CollatorProtocol( + collator_protocol_message.clone() + ); + + network_handle.peer_message( + peer_a.clone(), + PeerSet::Collation, + WireMessage::ProtocolMessage(message.clone()).encode(), + ).await; + + let actions = network_handle.next_network_actions(3).await; + assert_network_actions_contains( + &actions, + &NetworkAction::ReputationChange( + peer_a.clone(), + UNCONNECTED_PEERSET_COST, + ), + ); + + // peer B has the message relayed. + + network_handle.peer_message( + peer_b.clone(), + PeerSet::Collation, + WireMessage::ProtocolMessage(message.clone()).encode(), + ).await; + + assert_matches!( + virtual_overseer.recv().await, + AllMessages::CollatorProtocol( + CollatorProtocolMessage::NetworkBridgeUpdateV1( + NetworkBridgeEvent::PeerMessage(p, m) + ) + ) => { + assert_eq!(p, peer_b); + assert_eq!(m, collator_protocol_message); + } + ); + virtual_overseer + }); +} + +#[test] +fn different_views_on_different_peer_sets() { + test_harness(done_syncing_oracle(), |test_harness| async move { + let TestHarness { + mut network_handle, + mut virtual_overseer, + } = test_harness; + + let peer = PeerId::random(); + + network_handle.connect_peer(peer.clone(), PeerSet::Validation, ObservedRole::Full).await; + network_handle.connect_peer(peer.clone(), PeerSet::Collation, ObservedRole::Full).await; + + assert_matches!( + virtual_overseer.recv().await, + AllMessages::StatementDistribution( + StatementDistributionMessage::StatementFetchingReceiver(_) + ) + ); + + // bridge will inform about all connected peers. + { + assert_sends_validation_event_to_all( + NetworkBridgeEvent::PeerConnected(peer.clone(), ObservedRole::Full, None), + &mut virtual_overseer, + ).await; + + assert_sends_validation_event_to_all( + NetworkBridgeEvent::PeerViewChange(peer.clone(), View::default()), + &mut virtual_overseer, + ).await; + } + + { + assert_sends_collation_event_to_all( + NetworkBridgeEvent::PeerConnected(peer.clone(), ObservedRole::Full, None), + &mut virtual_overseer, + ).await; + + assert_sends_collation_event_to_all( + NetworkBridgeEvent::PeerViewChange(peer.clone(), View::default()), + &mut virtual_overseer, + ).await; + } + + let view_a = view![Hash::repeat_byte(1)]; + let view_b = view![Hash::repeat_byte(2)]; + + network_handle.peer_message( + peer.clone(), + PeerSet::Validation, + WireMessage::::ViewUpdate(view_a.clone()).encode(), + ).await; + + network_handle.peer_message( + peer.clone(), + PeerSet::Collation, + WireMessage::::ViewUpdate(view_b.clone()).encode(), + ).await; + + assert_sends_validation_event_to_all( + NetworkBridgeEvent::PeerViewChange(peer.clone(), view_a.clone()), + &mut virtual_overseer, + ).await; + + assert_sends_collation_event_to_all( + NetworkBridgeEvent::PeerViewChange(peer.clone(), view_b.clone()), + &mut virtual_overseer, + ).await; + virtual_overseer + }); +} + +#[test] +fn sent_views_include_finalized_number_update() { + test_harness(done_syncing_oracle(), |test_harness| async move { + let TestHarness { mut network_handle, mut virtual_overseer } = test_harness; + + let peer_a = PeerId::random(); + + network_handle.connect_peer( + peer_a.clone(), + PeerSet::Validation, + ObservedRole::Full, + ).await; + + let hash_a = Hash::repeat_byte(1); + let hash_b = Hash::repeat_byte(2); + + virtual_overseer.send( + FromOverseer::Signal(OverseerSignal::BlockFinalized(hash_a, 1)) + ).await; + virtual_overseer.send( + FromOverseer::Signal(OverseerSignal::ActiveLeaves( + ActiveLeavesUpdate::start_work(ActivatedLeaf { + hash: hash_b, + number: 1, + status: LeafStatus::Fresh, + span: Arc::new(jaeger::Span::Disabled), + }) + )) + ).await; + + let actions = network_handle.next_network_actions(2).await; + let wire_message = WireMessage::::ViewUpdate( + View::new(vec![hash_b], 1) + ).encode(); + + assert_network_actions_contains( + &actions, + &NetworkAction::WriteNotification( + peer_a.clone(), + PeerSet::Validation, + wire_message.clone(), + ), + ); + virtual_overseer + }); +} + +#[test] +fn view_finalized_number_can_not_go_down() { + test_harness(done_syncing_oracle(), |test_harness| async move { + let TestHarness { mut network_handle, virtual_overseer } = test_harness; + + let peer_a = PeerId::random(); + + network_handle.connect_peer( + peer_a.clone(), + PeerSet::Validation, + ObservedRole::Full, + ).await; + + network_handle.peer_message( + peer_a.clone(), + PeerSet::Validation, + WireMessage::::ViewUpdate( + View::new(vec![Hash::repeat_byte(0x01)], 1), + ).encode(), + ).await; + + network_handle.peer_message( + peer_a.clone(), + PeerSet::Validation, + WireMessage::::ViewUpdate( + View::new(vec![], 0), + ).encode(), + ).await; + + let actions = network_handle.next_network_actions(2).await; + assert_network_actions_contains( + &actions, + &NetworkAction::ReputationChange( + peer_a.clone(), + MALFORMED_VIEW_COST, + ), + ); + virtual_overseer + }); +} + +#[test] +fn send_messages_to_peers() { + test_harness(done_syncing_oracle(), |test_harness| async move { + let TestHarness { + mut network_handle, + mut virtual_overseer, + } = test_harness; + + let peer = PeerId::random(); + + network_handle.connect_peer(peer.clone(), PeerSet::Validation, ObservedRole::Full).await; + network_handle.connect_peer(peer.clone(), PeerSet::Collation, ObservedRole::Full).await; + + assert_matches!( + virtual_overseer.recv().await, + AllMessages::StatementDistribution( + StatementDistributionMessage::StatementFetchingReceiver(_) + ) + ); + + // bridge will inform about all connected peers. + { + assert_sends_validation_event_to_all( + NetworkBridgeEvent::PeerConnected(peer.clone(), ObservedRole::Full, None), + &mut virtual_overseer, + ).await; + + assert_sends_validation_event_to_all( + NetworkBridgeEvent::PeerViewChange(peer.clone(), View::default()), + &mut virtual_overseer, + ).await; + } + + { + assert_sends_collation_event_to_all( + NetworkBridgeEvent::PeerConnected(peer.clone(), ObservedRole::Full, None), + &mut virtual_overseer, + ).await; + + assert_sends_collation_event_to_all( + NetworkBridgeEvent::PeerViewChange(peer.clone(), View::default()), + &mut virtual_overseer, + ).await; + } + + // consume peer view changes + { + let _peer_view_changes = network_handle.next_network_actions(2).await; + } + + // send a validation protocol message. + + { + let approval_distribution_message = protocol_v1::ApprovalDistributionMessage::Approvals( + Vec::new() + ); + + let message = protocol_v1::ValidationProtocol::ApprovalDistribution( + approval_distribution_message.clone(), + ); + + virtual_overseer.send(FromOverseer::Communication { + msg: NetworkBridgeMessage::SendValidationMessage( + vec![peer.clone()], + message.clone(), + ) + }).await; + + assert_eq!( + network_handle.next_network_action().await, + NetworkAction::WriteNotification( + peer.clone(), + PeerSet::Validation, + WireMessage::ProtocolMessage(message).encode(), + ) + ); + } + + // send a collation protocol message. + + { + let collator_protocol_message = protocol_v1::CollatorProtocolMessage::Declare( + Sr25519Keyring::Alice.public().into(), + Default::default(), + Default::default(), + ); + + let message = protocol_v1::CollationProtocol::CollatorProtocol( + collator_protocol_message.clone() + ); + + virtual_overseer.send(FromOverseer::Communication { + msg: NetworkBridgeMessage::SendCollationMessage( + vec![peer.clone()], + message.clone(), + ) + }).await; + + assert_eq!( + network_handle.next_network_action().await, + NetworkAction::WriteNotification( + peer.clone(), + PeerSet::Collation, + WireMessage::ProtocolMessage(message).encode(), + ) + ); + } + virtual_overseer + }); +} + +#[test] +fn spread_event_to_subsystems_is_up_to_date() { + // Number of subsystems expected to be interested in a network event, + // and hence the network event broadcasted to. + const EXPECTED_COUNT: usize = 3; + + let mut cnt = 0_usize; + for msg in AllMessages::dispatch_iter(NetworkBridgeEvent::PeerDisconnected(PeerId::random())) { + match msg { + AllMessages::CandidateValidation(_) => unreachable!("Not interested in network events"), + AllMessages::CandidateBacking(_) => unreachable!("Not interested in network events"), + AllMessages::ChainApi(_) => unreachable!("Not interested in network events"), + AllMessages::CollatorProtocol(_) => unreachable!("Not interested in network events"), + AllMessages::StatementDistribution(_) => { cnt += 1; } + AllMessages::AvailabilityDistribution(_) => unreachable!("Not interested in network events"), + AllMessages::AvailabilityRecovery(_) => unreachable!("Not interested in network events"), + AllMessages::BitfieldDistribution(_) => { cnt += 1; } + AllMessages::BitfieldSigning(_) => unreachable!("Not interested in network events"), + AllMessages::Provisioner(_) => unreachable!("Not interested in network events"), + AllMessages::RuntimeApi(_) => unreachable!("Not interested in network events"), + AllMessages::AvailabilityStore(_) => unreachable!("Not interested in network events"), + AllMessages::NetworkBridge(_) => unreachable!("Not interested in network events"), + AllMessages::CollationGeneration(_) => unreachable!("Not interested in network events"), + AllMessages::ApprovalVoting(_) => unreachable!("Not interested in network events"), + AllMessages::ApprovalDistribution(_) => { cnt += 1; } + AllMessages::GossipSupport(_) => unreachable!("Not interested 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); +} + +#[test] +fn our_view_updates_decreasing_order_and_limited_to_max() { + test_harness(done_syncing_oracle(), |test_harness| async move { + let TestHarness { + mut virtual_overseer, + .. + } = test_harness; + + + // to show that we're still connected on the collation protocol, send a view update. + + let hashes = (0..MAX_VIEW_HEADS * 3).map(|i| Hash::repeat_byte(i as u8)); + + virtual_overseer.send( + FromOverseer::Signal(OverseerSignal::ActiveLeaves( + // These are in reverse order, so the subsystem must sort internally to + // get the correct view. + ActiveLeavesUpdate { + activated: hashes.enumerate().map(|(i, h)| ActivatedLeaf { + hash: h, + number: i as _, + status: LeafStatus::Fresh, + span: Arc::new(jaeger::Span::Disabled), + }).rev().collect(), + deactivated: Default::default(), + } + )) + ).await; + + let view_heads = (MAX_VIEW_HEADS * 2 .. MAX_VIEW_HEADS * 3).rev() + .map(|i| (Hash::repeat_byte(i as u8), Arc::new(jaeger::Span::Disabled)) ); + + let our_view = OurView::new( + view_heads, + 0, + ); + + assert_matches!( + virtual_overseer.recv().await, + AllMessages::StatementDistribution( + StatementDistributionMessage::StatementFetchingReceiver(_) + ) + ); + + assert_sends_validation_event_to_all( + NetworkBridgeEvent::OurViewChange(our_view.clone()), + &mut virtual_overseer, + ).await; + + assert_sends_collation_event_to_all( + NetworkBridgeEvent::OurViewChange(our_view), + &mut virtual_overseer, + ).await; + virtual_overseer + }); +} diff --git a/polkadot/node/network/collator-protocol/src/collator_side.rs b/polkadot/node/network/collator-protocol/src/collator_side/mod.rs similarity index 51% rename from polkadot/node/network/collator-protocol/src/collator_side.rs rename to polkadot/node/network/collator-protocol/src/collator_side/mod.rs index 04abc51029..4e1d3bdbd0 100644 --- a/polkadot/node/network/collator-protocol/src/collator_side.rs +++ b/polkadot/node/network/collator-protocol/src/collator_side/mod.rs @@ -868,782 +868,4 @@ pub(crate) async fn run( } #[cfg(test)] -mod tests { - use super::*; - - use std::{sync::Arc, time::Duration}; - - use assert_matches::assert_matches; - use futures::{executor, future, Future}; - - use sp_core::{crypto::Pair, Decode}; - use sp_keyring::Sr25519Keyring; - use sp_runtime::traits::AppVerify; - - use polkadot_node_network_protocol::{ - 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, - }; - use polkadot_subsystem_testhelpers as test_helpers; - - #[derive(Default)] - struct TestCandidateBuilder { - 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, - } - } - } - - #[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, - } - - fn validator_pubkeys(val_ids: &[Sr25519Keyring]) -> Vec { - 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() - } - - 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, - } - } - } - - 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; - } - } - - type VirtualOverseer = test_helpers::TestSubsystemContextHandle; - - struct TestHarness { - virtual_overseer: VirtualOverseer, - } - - fn test_harness>( - local_peer_id: PeerId, - collator_pair: CollatorPair, - test: impl FnOnce(TestHarness) -> T, - ) { - let pool = sp_core::testing::TaskExecutor::new(); - - let (context, virtual_overseer) = test_helpers::make_subsystem_context(pool.clone()); - - let subsystem = run(context, local_peer_id, collator_pair, Metrics::default()); - - let test_fut = test(TestHarness { virtual_overseer }); - - 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(); - } - - const TIMEOUT: Duration = Duration::from_millis(100); - - async fn overseer_send( - 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)); - } - - async fn overseer_recv( - overseer: &mut VirtualOverseer, - ) -> AllMessages { - let msg = overseer_recv_with_timeout(overseer, TIMEOUT) - .await - .expect(&format!("{:?} is more than enough to receive messages", TIMEOUT)); - - tracing::trace!(?msg, "received message"); - - msg - } - - async fn overseer_recv_with_timeout( - overseer: &mut VirtualOverseer, - timeout: Duration, - ) -> Option { - tracing::trace!("waiting for message..."); - overseer - .recv() - .timeout(timeout) - .await - } - - async fn overseer_signal( - overseer: &mut VirtualOverseer, - signal: OverseerSignal, - ) { - 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; - } - - /// Result of [`distribute_collation`] - struct DistributeCollation { - 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, - ) -> 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, - } - } - - /// Connect a peer - async fn connect_peer( - 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; - } - - /// Disconnect a peer - async fn disconnect_peer(virtual_overseer: &mut VirtualOverseer, peer: PeerId) { - 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, - ) { - 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, - ) { - 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; - } - - #[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 - }); - } - - #[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(); - - 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(); - - 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 - }) - } - - #[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(); - - 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 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; - - // 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; - - 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; - - distribute_collation(&mut virtual_overseer, &test_state, true).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; - - // 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(); - - 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 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; - - // 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; - - 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; - - 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; - - 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, &peer2, vec![test_state.relay_parent]).await; - - 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(); - - 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.clone())).await; - expect_declare_msg(&mut virtual_overseer, &test_state, &peer).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; - - // 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; - - 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 - }) - } -} +mod tests; diff --git a/polkadot/node/network/collator-protocol/src/collator_side/tests.rs b/polkadot/node/network/collator-protocol/src/collator_side/tests.rs new file mode 100644 index 0000000000..21bd7e0f83 --- /dev/null +++ b/polkadot/node/network/collator-protocol/src/collator_side/tests.rs @@ -0,0 +1,793 @@ +// Copyright 2020 Parity Technologies (UK) Ltd. +// This file is part of Polkadot. + +// Polkadot is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Polkadot is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Polkadot. If not, see . + +use super::*; + +use std::{sync::Arc, time::Duration}; + +use assert_matches::assert_matches; +use futures::{executor, future, Future}; + +use sp_core::{crypto::Pair, Decode}; +use sp_keyring::Sr25519Keyring; +use sp_runtime::traits::AppVerify; + +use polkadot_node_network_protocol::{ + 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, +}; +use polkadot_subsystem_testhelpers as test_helpers; + +#[derive(Default)] +struct TestCandidateBuilder { + 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, + } + } +} + +#[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, +} + +fn validator_pubkeys(val_ids: &[Sr25519Keyring]) -> Vec { + 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() +} + +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, + } + } +} + +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; + } +} + +type VirtualOverseer = test_helpers::TestSubsystemContextHandle; + +struct TestHarness { + virtual_overseer: VirtualOverseer, +} + +fn test_harness>( + local_peer_id: PeerId, + collator_pair: CollatorPair, + test: impl FnOnce(TestHarness) -> T, +) { + let pool = sp_core::testing::TaskExecutor::new(); + + let (context, virtual_overseer) = test_helpers::make_subsystem_context(pool.clone()); + + let subsystem = run(context, local_peer_id, collator_pair, Metrics::default()); + + let test_fut = test(TestHarness { virtual_overseer }); + + 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(); +} + +const TIMEOUT: Duration = Duration::from_millis(100); + +async fn overseer_send( + 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)); +} + +async fn overseer_recv( + overseer: &mut VirtualOverseer, +) -> AllMessages { + let msg = overseer_recv_with_timeout(overseer, TIMEOUT) + .await + .expect(&format!("{:?} is more than enough to receive messages", TIMEOUT)); + + tracing::trace!(?msg, "received message"); + + msg +} + +async fn overseer_recv_with_timeout( + overseer: &mut VirtualOverseer, + timeout: Duration, +) -> Option { + tracing::trace!("waiting for message..."); + overseer + .recv() + .timeout(timeout) + .await +} + +async fn overseer_signal( + overseer: &mut VirtualOverseer, + signal: OverseerSignal, +) { + 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; +} + +/// Result of [`distribute_collation`] +struct DistributeCollation { + 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, +) -> 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, + } +} + +/// Connect a peer +async fn connect_peer( + 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; +} + +/// Disconnect a peer +async fn disconnect_peer(virtual_overseer: &mut VirtualOverseer, peer: PeerId) { + 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, +) { + 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, +) { + 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; +} + +#[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 + }); +} + +#[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(); + + 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(); + + 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 + }) +} + +#[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(); + + 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 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; + + // 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; + + 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; + + distribute_collation(&mut virtual_overseer, &test_state, true).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; + + // 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(); + + 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 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; + + // 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; + + 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; + + 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; + + 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, &peer2, vec![test_state.relay_parent]).await; + + 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(); + + 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.clone())).await; + expect_declare_msg(&mut virtual_overseer, &test_state, &peer).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; + + // 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; + + 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 + }) +} diff --git a/polkadot/node/network/collator-protocol/src/validator_side/mod.rs b/polkadot/node/network/collator-protocol/src/validator_side/mod.rs index e9c9ececf5..a9b6ebe5d5 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/mod.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/mod.rs @@ -1246,1297 +1246,4 @@ where } #[cfg(test)] -mod tests { - use super::*; - use std::{iter, time::Duration}; - use std::sync::Arc; - use futures::{executor, future, Future}; - use sp_core::{crypto::Pair, Encode}; - use sp_keystore::SyncCryptoStore; - use sp_keystore::testing::KeyStore as TestKeyStore; - use sp_keyring::Sr25519Keyring; - use assert_matches::assert_matches; - - use polkadot_primitives::v1::{ - CollatorPair, ValidatorId, ValidatorIndex, CoreState, CandidateDescriptor, - GroupRotationInfo, ScheduledCore, OccupiedCore, GroupIndex, - }; - use polkadot_node_primitives::BlockData; - use polkadot_node_subsystem_util::TimeoutExt; - use polkadot_subsystem_testhelpers as test_helpers; - use polkadot_subsystem::messages::{RuntimeApiMessage, RuntimeApiRequest}; - use polkadot_node_network_protocol::{our_view, ObservedRole, - request_response::Requests - }; - - const ACTIVITY_TIMEOUT: Duration = Duration::from_millis(50); - const DECLARE_TIMEOUT: Duration = Duration::from_millis(25); - - #[derive(Clone)] - struct TestState { - chain_ids: Vec, - relay_parent: Hash, - collators: Vec, - validators: Vec, - validator_public: Vec, - validator_groups: Vec>, - group_rotation_info: GroupRotationInfo, - cores: Vec, - } - - impl Default for TestState { - fn default() -> Self { - let chain_a = ParaId::from(1); - let chain_b = ParaId::from(2); - - let chain_ids = vec![chain_a, chain_b]; - let relay_parent = Hash::repeat_byte(0x05); - let collators = iter::repeat(()) - .map(|_| CollatorPair::generate().0) - .take(4) - .collect(); - - let validators = vec![ - Sr25519Keyring::Alice, - Sr25519Keyring::Bob, - Sr25519Keyring::Charlie, - Sr25519Keyring::Dave, - Sr25519Keyring::Eve, - ]; - - let validator_public = validators.iter().map(|k| k.public().into()).collect(); - let validator_groups = vec![ - vec![ValidatorIndex(0), ValidatorIndex(1)], - vec![ValidatorIndex(2), ValidatorIndex(3)], - vec![ValidatorIndex(4)], - ]; - - let group_rotation_info = GroupRotationInfo { - session_start_block: 0, - group_rotation_frequency: 1, - now: 0, - }; - - let cores = vec![ - CoreState::Scheduled(ScheduledCore { - para_id: chain_ids[0], - collator: None, - }), - CoreState::Free, - CoreState::Occupied(OccupiedCore { - next_up_on_available: None, - occupied_since: 0, - time_out_at: 1, - next_up_on_time_out: None, - availability: Default::default(), - group_responsible: GroupIndex(0), - candidate_hash: Default::default(), - candidate_descriptor: { - let mut d = CandidateDescriptor::default(); - d.para_id = chain_ids[1]; - - d - }, - }), - ]; - - Self { - chain_ids, - relay_parent, - collators, - validators, - validator_public, - validator_groups, - group_rotation_info, - cores, - } - } - } - - type VirtualOverseer = test_helpers::TestSubsystemContextHandle; - - struct TestHarness { - virtual_overseer: VirtualOverseer, - } - - fn test_harness>(test: impl FnOnce(TestHarness) -> T) { - let _ = env_logger::builder() - .is_test(true) - .filter( - Some("polkadot_collator_protocol"), - log::LevelFilter::Trace, - ) - .filter( - Some(LOG_TARGET), - log::LevelFilter::Trace, - ) - .try_init(); - - let pool = sp_core::testing::TaskExecutor::new(); - - let (context, virtual_overseer) = test_helpers::make_subsystem_context(pool.clone()); - - let keystore = TestKeyStore::new(); - keystore.sr25519_generate_new( - polkadot_primitives::v1::PARACHAIN_KEY_TYPE_ID, - Some(&Sr25519Keyring::Alice.to_seed()), - ).unwrap(); - - let subsystem = run( - context, - Arc::new(keystore), - crate::CollatorEvictionPolicy { - inactive_collator: ACTIVITY_TIMEOUT, - undeclared: DECLARE_TIMEOUT, - }, - Metrics::default(), - ); - - let test_fut = test(TestHarness { virtual_overseer }); - - 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(); - } - - const TIMEOUT: Duration = Duration::from_millis(200); - - async fn overseer_send( - overseer: &mut VirtualOverseer, - msg: CollatorProtocolMessage, - ) { - tracing::trace!("Sending message:\n{:?}", &msg); - overseer - .send(FromOverseer::Communication { msg }) - .timeout(TIMEOUT) - .await - .expect(&format!("{:?} is enough for sending messages.", TIMEOUT)); - } - - async fn overseer_recv( - overseer: &mut VirtualOverseer, - ) -> AllMessages { - let msg = overseer_recv_with_timeout(overseer, TIMEOUT) - .await - .expect(&format!("{:?} is enough to receive messages.", TIMEOUT)); - - tracing::trace!("Received message:\n{:?}", &msg); - - msg - } - - async fn overseer_recv_with_timeout( - overseer: &mut VirtualOverseer, - timeout: Duration, - ) -> Option { - tracing::trace!("Waiting for message..."); - overseer - .recv() - .timeout(timeout) - .await - } - - async fn overseer_signal( - overseer: &mut VirtualOverseer, - signal: OverseerSignal, - ) { - overseer - .send(FromOverseer::Signal(signal)) - .timeout(TIMEOUT) - .await - .expect(&format!("{:?} is more than enough for sending signals.", TIMEOUT)); - } - - async fn respond_to_core_info_queries( - virtual_overseer: &mut VirtualOverseer, - test_state: &TestState, - ) { - assert_matches!( - overseer_recv(virtual_overseer).await, - AllMessages::RuntimeApi(RuntimeApiMessage::Request( - _, - RuntimeApiRequest::Validators(tx), - )) => { - let _ = tx.send(Ok(test_state.validator_public.clone())); - } - ); - - assert_matches!( - overseer_recv(virtual_overseer).await, - AllMessages::RuntimeApi(RuntimeApiMessage::Request( - _, - RuntimeApiRequest::ValidatorGroups(tx), - )) => { - let _ = tx.send(Ok(( - test_state.validator_groups.clone(), - test_state.group_rotation_info.clone(), - ))); - } - ); - - assert_matches!( - overseer_recv(virtual_overseer).await, - AllMessages::RuntimeApi(RuntimeApiMessage::Request( - _, - RuntimeApiRequest::AvailabilityCores(tx), - )) => { - let _ = tx.send(Ok(test_state.cores.clone())); - } - ); - } - - // As we receive a relevant advertisement act on it and issue a collation request. - #[test] - fn act_on_advertisement() { - let test_state = TestState::default(); - - test_harness(|test_harness| async move { - let TestHarness { - mut virtual_overseer, - } = test_harness; - - let pair = CollatorPair::generate().0; - tracing::trace!("activating"); - - overseer_send( - &mut virtual_overseer, - CollatorProtocolMessage::NetworkBridgeUpdateV1( - NetworkBridgeEvent::OurViewChange(our_view![test_state.relay_parent]) - ) - ).await; - - respond_to_core_info_queries(&mut virtual_overseer, &test_state).await; - - let peer_b = PeerId::random(); - - overseer_send( - &mut virtual_overseer, - CollatorProtocolMessage::NetworkBridgeUpdateV1( - NetworkBridgeEvent::PeerConnected( - peer_b, - ObservedRole::Full, - None, - ), - ) - ).await; - - overseer_send( - &mut virtual_overseer, - CollatorProtocolMessage::NetworkBridgeUpdateV1( - NetworkBridgeEvent::PeerMessage( - peer_b.clone(), - protocol_v1::CollatorProtocolMessage::Declare( - pair.public(), - test_state.chain_ids[0], - pair.sign(&protocol_v1::declare_signature_payload(&peer_b)), - ) - ) - ) - ).await; - - overseer_send( - &mut virtual_overseer, - CollatorProtocolMessage::NetworkBridgeUpdateV1( - NetworkBridgeEvent::PeerMessage( - peer_b.clone(), - protocol_v1::CollatorProtocolMessage::AdvertiseCollation( - test_state.relay_parent, - ) - ) - ) - ).await; - - assert_matches!( - overseer_recv(&mut virtual_overseer).await, - AllMessages::NetworkBridge(NetworkBridgeMessage::SendRequests(reqs, IfDisconnected::ImmediateError) - ) => { - let req = reqs.into_iter().next() - .expect("There should be exactly one request"); - match req { - Requests::CollationFetching(req) => { - let payload = req.payload; - assert_eq!(payload.relay_parent, test_state.relay_parent); - assert_eq!(payload.para_id, test_state.chain_ids[0]); - } - _ => panic!("Unexpected request"), - } - }); - - virtual_overseer - }); - } - - // Test that other subsystems may modify collators' reputations. - #[test] - fn collator_reporting_works() { - let test_state = TestState::default(); - - test_harness(|test_harness| async move { - let TestHarness { - mut virtual_overseer, - } = test_harness; - - overseer_send( - &mut virtual_overseer, - CollatorProtocolMessage::NetworkBridgeUpdateV1( - NetworkBridgeEvent::OurViewChange(our_view![test_state.relay_parent]) - ) - ).await; - - respond_to_core_info_queries(&mut virtual_overseer, &test_state).await; - - let peer_b = PeerId::random(); - let peer_c = PeerId::random(); - - overseer_send( - &mut virtual_overseer, - CollatorProtocolMessage::NetworkBridgeUpdateV1( - NetworkBridgeEvent::PeerConnected( - peer_b, - ObservedRole::Full, - None, - ), - ) - ).await; - - overseer_send( - &mut virtual_overseer, - CollatorProtocolMessage::NetworkBridgeUpdateV1( - NetworkBridgeEvent::PeerConnected( - peer_c, - ObservedRole::Full, - None, - ), - ) - ).await; - - overseer_send( - &mut virtual_overseer, - CollatorProtocolMessage::NetworkBridgeUpdateV1( - NetworkBridgeEvent::PeerMessage( - peer_b.clone(), - protocol_v1::CollatorProtocolMessage::Declare( - test_state.collators[0].public(), - test_state.chain_ids[0], - test_state.collators[0].sign(&protocol_v1::declare_signature_payload(&peer_b)), - ), - ) - ) - ).await; - - overseer_send( - &mut virtual_overseer, - CollatorProtocolMessage::NetworkBridgeUpdateV1( - NetworkBridgeEvent::PeerMessage( - peer_c.clone(), - protocol_v1::CollatorProtocolMessage::Declare( - test_state.collators[1].public(), - test_state.chain_ids[0], - test_state.collators[1].sign(&protocol_v1::declare_signature_payload(&peer_c)), - ), - ) - ) - ).await; - - overseer_send( - &mut virtual_overseer, - CollatorProtocolMessage::ReportCollator(test_state.collators[0].public()), - ).await; - - assert_matches!( - overseer_recv(&mut virtual_overseer).await, - AllMessages::NetworkBridge( - NetworkBridgeMessage::ReportPeer(peer, rep), - ) => { - assert_eq!(peer, peer_b); - assert_eq!(rep, COST_REPORT_BAD); - } - ); - - virtual_overseer - }); - } - - // Test that we verify the signatures on `Declare` and `AdvertiseCollation` messages. - #[test] - fn collator_authentication_verification_works() { - let test_state = TestState::default(); - - test_harness(|test_harness| async move { - let TestHarness { - mut virtual_overseer, - } = test_harness; - - let peer_b = PeerId::random(); - - overseer_send( - &mut virtual_overseer, - CollatorProtocolMessage::NetworkBridgeUpdateV1( - NetworkBridgeEvent::PeerConnected( - peer_b, - ObservedRole::Full, - None, - ), - ) - ).await; - - // the peer sends a declare message but sign the wrong payload - overseer_send( - &mut virtual_overseer, - CollatorProtocolMessage::NetworkBridgeUpdateV1(NetworkBridgeEvent::PeerMessage( - peer_b.clone(), - protocol_v1::CollatorProtocolMessage::Declare( - test_state.collators[0].public(), - test_state.chain_ids[0], - test_state.collators[0].sign(&[42]), - ), - )), - ) - .await; - - // it should be reported for sending a message with an invalid signature - assert_matches!( - overseer_recv(&mut virtual_overseer).await, - AllMessages::NetworkBridge( - NetworkBridgeMessage::ReportPeer(peer, rep), - ) => { - assert_eq!(peer, peer_b); - assert_eq!(rep, COST_INVALID_SIGNATURE); - } - ); - virtual_overseer - }); - } - - // A test scenario that takes the following steps - // - Two collators connect, declare themselves and advertise a collation relevant to - // our view. - // - This results subsystem acting upon these advertisements and issuing two messages to - // the CandidateBacking subsystem. - // - CandidateBacking requests both of the collations. - // - Collation protocol requests these collations. - // - The collations are sent to it. - // - Collations are fetched correctly. - #[test] - fn fetch_collations_works() { - let test_state = TestState::default(); - - test_harness(|test_harness| async move { - let TestHarness { - mut virtual_overseer, - } = test_harness; - - overseer_send( - &mut virtual_overseer, - CollatorProtocolMessage::NetworkBridgeUpdateV1( - NetworkBridgeEvent::OurViewChange(our_view![test_state.relay_parent]) - ), - ).await; - - respond_to_core_info_queries(&mut virtual_overseer, &test_state).await; - - let peer_b = PeerId::random(); - let peer_c = PeerId::random(); - - overseer_send( - &mut virtual_overseer, - CollatorProtocolMessage::NetworkBridgeUpdateV1( - NetworkBridgeEvent::PeerConnected( - peer_b, - ObservedRole::Full, - None, - ), - ) - ).await; - - overseer_send( - &mut virtual_overseer, - CollatorProtocolMessage::NetworkBridgeUpdateV1( - NetworkBridgeEvent::PeerConnected( - peer_c, - ObservedRole::Full, - None, - ), - ) - ).await; - - overseer_send( - &mut virtual_overseer, - CollatorProtocolMessage::NetworkBridgeUpdateV1( - NetworkBridgeEvent::PeerMessage( - peer_b.clone(), - protocol_v1::CollatorProtocolMessage::Declare( - test_state.collators[0].public(), - test_state.chain_ids[0], - test_state.collators[0].sign(&protocol_v1::declare_signature_payload(&peer_b)), - ) - ) - ) - ).await; - - overseer_send( - &mut virtual_overseer, - CollatorProtocolMessage::NetworkBridgeUpdateV1( - NetworkBridgeEvent::PeerMessage( - peer_c.clone(), - protocol_v1::CollatorProtocolMessage::Declare( - test_state.collators[1].public(), - test_state.chain_ids[0], - test_state.collators[1].sign(&protocol_v1::declare_signature_payload(&peer_c)), - ) - ) - ) - ).await; - - overseer_send( - &mut virtual_overseer, - CollatorProtocolMessage::NetworkBridgeUpdateV1( - NetworkBridgeEvent::PeerMessage( - peer_b.clone(), - protocol_v1::CollatorProtocolMessage::AdvertiseCollation( - test_state.relay_parent, - ) - ) - ) - ).await; - - overseer_send( - &mut virtual_overseer, - CollatorProtocolMessage::NetworkBridgeUpdateV1( - NetworkBridgeEvent::PeerMessage( - peer_c.clone(), - protocol_v1::CollatorProtocolMessage::AdvertiseCollation( - test_state.relay_parent, - ) - ) - ) - ).await; - - let response_channel = assert_matches!( - overseer_recv(&mut virtual_overseer).await, - AllMessages::NetworkBridge(NetworkBridgeMessage::SendRequests(reqs, IfDisconnected::ImmediateError) - ) => { - let req = reqs.into_iter().next() - .expect("There should be exactly one request"); - match req { - Requests::CollationFetching(req) => { - let payload = req.payload; - assert_eq!(payload.relay_parent, test_state.relay_parent); - assert_eq!(payload.para_id, test_state.chain_ids[0]); - req.pending_response - } - _ => panic!("Unexpected request"), - } - }); - - let mut candidate_a = CandidateReceipt::default(); - candidate_a.descriptor.para_id = test_state.chain_ids[0]; - candidate_a.descriptor.relay_parent = test_state.relay_parent; - response_channel.send(Ok( - CollationFetchingResponse::Collation( - candidate_a.clone(), - PoV { - block_data: BlockData(vec![]), - }, - ).encode() - )).expect("Sending response should succeed"); - - let _ = assert_matches!( - overseer_recv(&mut virtual_overseer).await, - AllMessages::NetworkBridge(NetworkBridgeMessage::SendRequests(reqs, IfDisconnected::ImmediateError) - ) => { - let req = reqs.into_iter().next() - .expect("There should be exactly one request"); - match req { - Requests::CollationFetching(req) => { - let payload = req.payload; - assert_eq!(payload.relay_parent, test_state.relay_parent); - assert_eq!(payload.para_id, test_state.chain_ids[0]); - req.pending_response - } - _ => panic!("Unexpected request"), - } - }); - - virtual_overseer - }); - } - - #[test] - fn inactive_disconnected() { - let test_state = TestState::default(); - - test_harness(|test_harness| async move { - let TestHarness { - mut virtual_overseer, - } = test_harness; - - let pair = CollatorPair::generate().0; - - let hash_a = test_state.relay_parent; - - overseer_send( - &mut virtual_overseer, - CollatorProtocolMessage::NetworkBridgeUpdateV1( - NetworkBridgeEvent::OurViewChange(our_view![hash_a]) - ) - ).await; - - respond_to_core_info_queries(&mut virtual_overseer, &test_state).await; - - let peer_b = PeerId::random(); - - overseer_send( - &mut virtual_overseer, - CollatorProtocolMessage::NetworkBridgeUpdateV1( - NetworkBridgeEvent::PeerConnected( - peer_b.clone(), - ObservedRole::Full, - None, - ) - ) - ).await; - - overseer_send( - &mut virtual_overseer, - CollatorProtocolMessage::NetworkBridgeUpdateV1( - NetworkBridgeEvent::PeerMessage( - peer_b.clone(), - protocol_v1::CollatorProtocolMessage::Declare( - pair.public(), - test_state.chain_ids[0], - pair.sign(&protocol_v1::declare_signature_payload(&peer_b)), - ) - ) - ) - ).await; - - overseer_send( - &mut virtual_overseer, - CollatorProtocolMessage::NetworkBridgeUpdateV1( - NetworkBridgeEvent::PeerMessage( - peer_b.clone(), - protocol_v1::CollatorProtocolMessage::AdvertiseCollation( - test_state.relay_parent, - ) - ) - ) - ).await; - - let _ = assert_matches!( - overseer_recv(&mut virtual_overseer).await, - AllMessages::NetworkBridge(NetworkBridgeMessage::SendRequests(reqs, IfDisconnected::ImmediateError) - ) => { - let req = reqs.into_iter().next() - .expect("There should be exactly one request"); - match req { - Requests::CollationFetching(req) => { - let payload = req.payload; - assert_eq!(payload.relay_parent, test_state.relay_parent); - assert_eq!(payload.para_id, test_state.chain_ids[0]); - req.pending_response - } - _ => panic!("Unexpected request"), - } - }); - - Delay::new(ACTIVITY_TIMEOUT * 3).await; - - assert_matches!( - overseer_recv(&mut virtual_overseer).await, - AllMessages::NetworkBridge(NetworkBridgeMessage::ReportPeer( - peer, - rep, - )) => { - assert_eq!(peer, peer_b); - assert_eq!(rep, COST_REQUEST_TIMED_OUT); - } - ); - - assert_matches!( - overseer_recv(&mut virtual_overseer).await, - AllMessages::NetworkBridge(NetworkBridgeMessage::DisconnectPeer( - peer, - peer_set, - )) => { - assert_eq!(peer, peer_b); - assert_eq!(peer_set, PeerSet::Collation); - } - ); - virtual_overseer - }); - } - - #[test] - fn activity_extends_life() { - let test_state = TestState::default(); - - test_harness(|test_harness| async move { - let TestHarness { - mut virtual_overseer, - } = test_harness; - - let pair = CollatorPair::generate().0; - - let hash_a = test_state.relay_parent; - let hash_b = Hash::repeat_byte(1); - let hash_c = Hash::repeat_byte(2); - - overseer_send( - &mut virtual_overseer, - CollatorProtocolMessage::NetworkBridgeUpdateV1( - NetworkBridgeEvent::OurViewChange(our_view![hash_a, hash_b, hash_c]) - ) - ).await; - - // 3 heads, 3 times. - respond_to_core_info_queries(&mut virtual_overseer, &test_state).await; - respond_to_core_info_queries(&mut virtual_overseer, &test_state).await; - respond_to_core_info_queries(&mut virtual_overseer, &test_state).await; - - let peer_b = PeerId::random(); - - overseer_send( - &mut virtual_overseer, - CollatorProtocolMessage::NetworkBridgeUpdateV1( - NetworkBridgeEvent::PeerConnected( - peer_b.clone(), - ObservedRole::Full, - None, - ) - ) - ).await; - - overseer_send( - &mut virtual_overseer, - CollatorProtocolMessage::NetworkBridgeUpdateV1( - NetworkBridgeEvent::PeerMessage( - peer_b.clone(), - protocol_v1::CollatorProtocolMessage::Declare( - pair.public(), - test_state.chain_ids[0], - pair.sign(&protocol_v1::declare_signature_payload(&peer_b)), - ) - ) - ) - ).await; - - Delay::new(ACTIVITY_TIMEOUT * 2 / 3).await; - - overseer_send( - &mut virtual_overseer, - CollatorProtocolMessage::NetworkBridgeUpdateV1( - NetworkBridgeEvent::PeerMessage( - peer_b.clone(), - protocol_v1::CollatorProtocolMessage::AdvertiseCollation( - hash_a, - ) - ) - ) - ).await; - - assert_matches!( - overseer_recv(&mut virtual_overseer).await, - AllMessages::NetworkBridge(NetworkBridgeMessage::SendRequests(reqs, IfDisconnected::ImmediateError) - ) => { - let req = reqs.into_iter().next() - .expect("There should be exactly one request"); - match req { - Requests::CollationFetching(req) => { - let payload = req.payload; - assert_eq!(payload.relay_parent, hash_a); - assert_eq!(payload.para_id, test_state.chain_ids[0]); - } - _ => panic!("Unexpected request"), - } - }); - - Delay::new(ACTIVITY_TIMEOUT * 2 / 3).await; - - overseer_send( - &mut virtual_overseer, - CollatorProtocolMessage::NetworkBridgeUpdateV1( - NetworkBridgeEvent::PeerMessage( - peer_b.clone(), - protocol_v1::CollatorProtocolMessage::AdvertiseCollation( - hash_b - ) - ) - ) - ).await; - - assert_matches!( - overseer_recv(&mut virtual_overseer).await, - AllMessages::NetworkBridge(NetworkBridgeMessage::ReportPeer( - peer, - rep, - )) => { - assert_eq!(peer, peer_b); - assert_eq!(rep, COST_REQUEST_TIMED_OUT); - } - ); - - assert_matches!( - overseer_recv(&mut virtual_overseer).await, - AllMessages::NetworkBridge(NetworkBridgeMessage::SendRequests(reqs, IfDisconnected::ImmediateError) - ) => { - let req = reqs.into_iter().next() - .expect("There should be exactly one request"); - match req { - Requests::CollationFetching(req) => { - let payload = req.payload; - assert_eq!(payload.relay_parent, hash_b); - assert_eq!(payload.para_id, test_state.chain_ids[0]); - } - _ => panic!("Unexpected request"), - } - }); - - Delay::new(ACTIVITY_TIMEOUT * 2 / 3).await; - - overseer_send( - &mut virtual_overseer, - CollatorProtocolMessage::NetworkBridgeUpdateV1( - NetworkBridgeEvent::PeerMessage( - peer_b.clone(), - protocol_v1::CollatorProtocolMessage::AdvertiseCollation( - hash_c, - ) - ) - ) - ).await; - - assert_matches!( - overseer_recv(&mut virtual_overseer).await, - AllMessages::NetworkBridge(NetworkBridgeMessage::ReportPeer( - peer, - rep, - )) => { - assert_eq!(peer, peer_b); - assert_eq!(rep, COST_REQUEST_TIMED_OUT); - } - ); - - assert_matches!( - overseer_recv(&mut virtual_overseer).await, - AllMessages::NetworkBridge(NetworkBridgeMessage::SendRequests(reqs, IfDisconnected::ImmediateError) - ) => { - let req = reqs.into_iter().next() - .expect("There should be exactly one request"); - match req { - Requests::CollationFetching(req) => { - let payload = req.payload; - assert_eq!(payload.relay_parent, hash_c); - assert_eq!(payload.para_id, test_state.chain_ids[0]); - } - _ => panic!("Unexpected request"), - } - }); - - Delay::new(ACTIVITY_TIMEOUT * 3 / 2).await; - - assert_matches!( - overseer_recv(&mut virtual_overseer).await, - AllMessages::NetworkBridge(NetworkBridgeMessage::ReportPeer( - peer, - rep, - )) => { - assert_eq!(peer, peer_b); - assert_eq!(rep, COST_REQUEST_TIMED_OUT); - } - ); - - assert_matches!( - overseer_recv(&mut virtual_overseer).await, - AllMessages::NetworkBridge(NetworkBridgeMessage::DisconnectPeer( - peer, - peer_set, - )) => { - assert_eq!(peer, peer_b); - assert_eq!(peer_set, PeerSet::Collation); - } - ); - virtual_overseer - }); - } - - #[test] - fn disconnect_if_no_declare() { - let test_state = TestState::default(); - - test_harness(|test_harness| async move { - let TestHarness { - mut virtual_overseer, - } = test_harness; - - overseer_send( - &mut virtual_overseer, - CollatorProtocolMessage::NetworkBridgeUpdateV1( - NetworkBridgeEvent::OurViewChange(our_view![test_state.relay_parent]) - ) - ).await; - - respond_to_core_info_queries(&mut virtual_overseer, &test_state).await; - - let peer_b = PeerId::random(); - - overseer_send( - &mut virtual_overseer, - CollatorProtocolMessage::NetworkBridgeUpdateV1( - NetworkBridgeEvent::PeerConnected( - peer_b.clone(), - ObservedRole::Full, - None, - ) - ) - ).await; - - assert_matches!( - overseer_recv(&mut virtual_overseer).await, - AllMessages::NetworkBridge(NetworkBridgeMessage::DisconnectPeer( - peer, - peer_set, - )) => { - assert_eq!(peer, peer_b); - assert_eq!(peer_set, PeerSet::Collation); - } - ); - virtual_overseer - }) - } - - #[test] - fn disconnect_if_wrong_declare() { - let test_state = TestState::default(); - - test_harness(|test_harness| async move { - let TestHarness { - mut virtual_overseer, - } = test_harness; - - let pair = CollatorPair::generate().0; - - overseer_send( - &mut virtual_overseer, - CollatorProtocolMessage::NetworkBridgeUpdateV1( - NetworkBridgeEvent::OurViewChange(our_view![test_state.relay_parent]) - ) - ).await; - - respond_to_core_info_queries(&mut virtual_overseer, &test_state).await; - - let peer_b = PeerId::random(); - - overseer_send( - &mut virtual_overseer, - CollatorProtocolMessage::NetworkBridgeUpdateV1( - NetworkBridgeEvent::PeerConnected( - peer_b.clone(), - ObservedRole::Full, - None, - ) - ) - ).await; - - overseer_send( - &mut virtual_overseer, - CollatorProtocolMessage::NetworkBridgeUpdateV1( - NetworkBridgeEvent::PeerMessage( - peer_b.clone(), - protocol_v1::CollatorProtocolMessage::Declare( - pair.public(), - ParaId::from(69), - pair.sign(&protocol_v1::declare_signature_payload(&peer_b)), - ) - ) - ) - ).await; - - assert_matches!( - overseer_recv(&mut virtual_overseer).await, - AllMessages::NetworkBridge(NetworkBridgeMessage::ReportPeer( - peer, - rep, - )) => { - assert_eq!(peer, peer_b); - assert_eq!(rep, COST_UNNEEDED_COLLATOR); - } - ); - - assert_matches!( - overseer_recv(&mut virtual_overseer).await, - AllMessages::NetworkBridge(NetworkBridgeMessage::DisconnectPeer( - peer, - peer_set, - )) => { - assert_eq!(peer, peer_b); - assert_eq!(peer_set, PeerSet::Collation); - } - ); - virtual_overseer - }) - } - - #[test] - fn view_change_clears_old_collators() { - let mut test_state = TestState::default(); - - test_harness(|test_harness| async move { - let TestHarness { - mut virtual_overseer, - } = test_harness; - - let pair = CollatorPair::generate().0; - - overseer_send( - &mut virtual_overseer, - CollatorProtocolMessage::NetworkBridgeUpdateV1( - NetworkBridgeEvent::OurViewChange(our_view![test_state.relay_parent]) - ) - ).await; - - respond_to_core_info_queries(&mut virtual_overseer, &test_state).await; - - let peer_b = PeerId::random(); - - overseer_send( - &mut virtual_overseer, - CollatorProtocolMessage::NetworkBridgeUpdateV1( - NetworkBridgeEvent::PeerConnected( - peer_b.clone(), - ObservedRole::Full, - None, - ) - ) - ).await; - - overseer_send( - &mut virtual_overseer, - CollatorProtocolMessage::NetworkBridgeUpdateV1( - NetworkBridgeEvent::PeerMessage( - peer_b.clone(), - protocol_v1::CollatorProtocolMessage::Declare( - pair.public(), - test_state.chain_ids[0], - pair.sign(&protocol_v1::declare_signature_payload(&peer_b)), - ) - ) - ) - ).await; - - let hash_b = Hash::repeat_byte(69); - - overseer_send( - &mut virtual_overseer, - CollatorProtocolMessage::NetworkBridgeUpdateV1( - NetworkBridgeEvent::OurViewChange(our_view![hash_b]) - ) - ).await; - - test_state.group_rotation_info = test_state.group_rotation_info.bump_rotation(); - respond_to_core_info_queries(&mut virtual_overseer, &test_state).await; - - assert_matches!( - overseer_recv(&mut virtual_overseer).await, - AllMessages::NetworkBridge(NetworkBridgeMessage::DisconnectPeer( - peer, - peer_set, - )) => { - assert_eq!(peer, peer_b); - assert_eq!(peer_set, PeerSet::Collation); - } - ); - virtual_overseer - }) - } - - // A test scenario that takes the following steps - // - Two collators connect, declare themselves and advertise a collation relevant to - // our view. - // - This results subsystem acting upon these advertisements and issuing two messages to - // the CandidateBacking subsystem. - // - CandidateBacking requests both of the collations. - // - Collation protocol requests these collations. - // - The collations are sent to it. - // - Collations are fetched correctly. - #[test] - fn seconding_works() { - let test_state = TestState::default(); - - test_harness(|test_harness| async move { - let TestHarness { - mut virtual_overseer, - } = test_harness; - - overseer_send( - &mut virtual_overseer, - CollatorProtocolMessage::NetworkBridgeUpdateV1( - NetworkBridgeEvent::OurViewChange(our_view![test_state.relay_parent]) - ), - ).await; - - respond_to_core_info_queries(&mut virtual_overseer, &test_state).await; - - let peer_b = PeerId::random(); - let peer_c = PeerId::random(); - - overseer_send( - &mut virtual_overseer, - CollatorProtocolMessage::NetworkBridgeUpdateV1( - NetworkBridgeEvent::PeerConnected( - peer_b, - ObservedRole::Full, - None, - ), - ) - ).await; - - overseer_send( - &mut virtual_overseer, - CollatorProtocolMessage::NetworkBridgeUpdateV1( - NetworkBridgeEvent::PeerConnected( - peer_c, - ObservedRole::Full, - None, - ), - ) - ).await; - - overseer_send( - &mut virtual_overseer, - CollatorProtocolMessage::NetworkBridgeUpdateV1( - NetworkBridgeEvent::PeerMessage( - peer_b.clone(), - protocol_v1::CollatorProtocolMessage::Declare( - test_state.collators[0].public(), - test_state.chain_ids[0], - test_state.collators[0].sign(&protocol_v1::declare_signature_payload(&peer_b)), - ) - ) - ) - ).await; - - overseer_send( - &mut virtual_overseer, - CollatorProtocolMessage::NetworkBridgeUpdateV1( - NetworkBridgeEvent::PeerMessage( - peer_c.clone(), - protocol_v1::CollatorProtocolMessage::Declare( - test_state.collators[1].public(), - test_state.chain_ids[0], - test_state.collators[1].sign(&protocol_v1::declare_signature_payload(&peer_c)), - ) - ) - ) - ).await; - - overseer_send( - &mut virtual_overseer, - CollatorProtocolMessage::NetworkBridgeUpdateV1( - NetworkBridgeEvent::PeerMessage( - peer_b.clone(), - protocol_v1::CollatorProtocolMessage::AdvertiseCollation( - test_state.relay_parent, - ) - ) - ) - ).await; - - overseer_send( - &mut virtual_overseer, - CollatorProtocolMessage::NetworkBridgeUpdateV1( - NetworkBridgeEvent::PeerMessage( - peer_c.clone(), - protocol_v1::CollatorProtocolMessage::AdvertiseCollation( - test_state.relay_parent, - ) - ) - ) - ).await; - - let response_channel = assert_matches!( - overseer_recv(&mut virtual_overseer).await, - AllMessages::NetworkBridge(NetworkBridgeMessage::SendRequests(reqs, IfDisconnected::ImmediateError) - ) => { - let req = reqs.into_iter().next() - .expect("There should be exactly one request"); - match req { - Requests::CollationFetching(req) => { - let payload = req.payload; - assert_eq!(payload.relay_parent, test_state.relay_parent); - assert_eq!(payload.para_id, test_state.chain_ids[0]); - req.pending_response - } - _ => panic!("Unexpected request"), - } - }); - - let mut candidate_a = CandidateReceipt::default(); - // Memoize PoV data to ensure we receive the right one - let pov = PoV { - block_data: BlockData(vec![1, 2, 3, 4, 5]), - }; - candidate_a.descriptor.para_id = test_state.chain_ids[0]; - candidate_a.descriptor.relay_parent = test_state.relay_parent; - response_channel.send(Ok( - CollationFetchingResponse::Collation( - candidate_a.clone(), - pov.clone(), - ).encode() - )).expect("Sending response should succeed"); - - let response_channel = assert_matches!( - overseer_recv(&mut virtual_overseer).await, - AllMessages::NetworkBridge(NetworkBridgeMessage::SendRequests(reqs, IfDisconnected::ImmediateError) - ) => { - let req = reqs.into_iter().next() - .expect("There should be exactly one request"); - match req { - Requests::CollationFetching(req) => { - let payload = req.payload; - assert_eq!(payload.relay_parent, test_state.relay_parent); - assert_eq!(payload.para_id, test_state.chain_ids[0]); - req.pending_response - } - _ => panic!("Unexpected request"), - } - }); - - assert_matches!( - overseer_recv(&mut virtual_overseer).await, - AllMessages::CandidateBacking(CandidateBackingMessage::Second(relay_parent, candidate_receipt, incoming_pov) - ) => { - assert_eq!(relay_parent, test_state.relay_parent); - assert_eq!(candidate_receipt.descriptor.para_id, test_state.chain_ids[0]); - assert_eq!(incoming_pov, pov); - }); - - let mut candidate_b = CandidateReceipt::default(); - candidate_b.descriptor.para_id = test_state.chain_ids[0]; - candidate_b.descriptor.relay_parent = test_state.relay_parent; - - // Send second collation to ensure first collation gets seconded - response_channel.send(Ok( - CollationFetchingResponse::Collation( - candidate_b.clone(), - PoV { - block_data: BlockData(vec![]), - }, - ).encode() - )).expect("Sending response should succeed after seconding"); - - // Ensure we don't receive any message related to candidate backing - // All Peers should get disconnected after successful Candidate Backing Message - assert_matches!( - overseer_recv(&mut virtual_overseer).await, - AllMessages::NetworkBridge(NetworkBridgeMessage::DisconnectPeer(_, _) - ) => {}); - - virtual_overseer - }); - } -} +mod tests; diff --git a/polkadot/node/network/collator-protocol/src/validator_side/tests.rs b/polkadot/node/network/collator-protocol/src/validator_side/tests.rs new file mode 100644 index 0000000000..6ab0b1eb28 --- /dev/null +++ b/polkadot/node/network/collator-protocol/src/validator_side/tests.rs @@ -0,0 +1,1308 @@ +// Copyright 2020 Parity Technologies (UK) Ltd. +// This file is part of Polkadot. + +// Polkadot is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Polkadot is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Polkadot. If not, see . + +use super::*; +use std::{iter, time::Duration}; +use std::sync::Arc; +use futures::{executor, future, Future}; +use sp_core::{crypto::Pair, Encode}; +use sp_keystore::SyncCryptoStore; +use sp_keystore::testing::KeyStore as TestKeyStore; +use sp_keyring::Sr25519Keyring; +use assert_matches::assert_matches; + +use polkadot_primitives::v1::{ + CollatorPair, ValidatorId, ValidatorIndex, CoreState, CandidateDescriptor, + GroupRotationInfo, ScheduledCore, OccupiedCore, GroupIndex, +}; +use polkadot_node_primitives::BlockData; +use polkadot_node_subsystem_util::TimeoutExt; +use polkadot_subsystem_testhelpers as test_helpers; +use polkadot_subsystem::messages::{RuntimeApiMessage, RuntimeApiRequest}; +use polkadot_node_network_protocol::{our_view, ObservedRole, + request_response::Requests +}; + +const ACTIVITY_TIMEOUT: Duration = Duration::from_millis(50); +const DECLARE_TIMEOUT: Duration = Duration::from_millis(25); + +#[derive(Clone)] +struct TestState { + chain_ids: Vec, + relay_parent: Hash, + collators: Vec, + validators: Vec, + validator_public: Vec, + validator_groups: Vec>, + group_rotation_info: GroupRotationInfo, + cores: Vec, +} + +impl Default for TestState { + fn default() -> Self { + let chain_a = ParaId::from(1); + let chain_b = ParaId::from(2); + + let chain_ids = vec![chain_a, chain_b]; + let relay_parent = Hash::repeat_byte(0x05); + let collators = iter::repeat(()) + .map(|_| CollatorPair::generate().0) + .take(4) + .collect(); + + let validators = vec![ + Sr25519Keyring::Alice, + Sr25519Keyring::Bob, + Sr25519Keyring::Charlie, + Sr25519Keyring::Dave, + Sr25519Keyring::Eve, + ]; + + let validator_public = validators.iter().map(|k| k.public().into()).collect(); + let validator_groups = vec![ + vec![ValidatorIndex(0), ValidatorIndex(1)], + vec![ValidatorIndex(2), ValidatorIndex(3)], + vec![ValidatorIndex(4)], + ]; + + let group_rotation_info = GroupRotationInfo { + session_start_block: 0, + group_rotation_frequency: 1, + now: 0, + }; + + let cores = vec![ + CoreState::Scheduled(ScheduledCore { + para_id: chain_ids[0], + collator: None, + }), + CoreState::Free, + CoreState::Occupied(OccupiedCore { + next_up_on_available: None, + occupied_since: 0, + time_out_at: 1, + next_up_on_time_out: None, + availability: Default::default(), + group_responsible: GroupIndex(0), + candidate_hash: Default::default(), + candidate_descriptor: { + let mut d = CandidateDescriptor::default(); + d.para_id = chain_ids[1]; + + d + }, + }), + ]; + + Self { + chain_ids, + relay_parent, + collators, + validators, + validator_public, + validator_groups, + group_rotation_info, + cores, + } + } +} + +type VirtualOverseer = test_helpers::TestSubsystemContextHandle; + +struct TestHarness { + virtual_overseer: VirtualOverseer, +} + +fn test_harness>(test: impl FnOnce(TestHarness) -> T) { + let _ = env_logger::builder() + .is_test(true) + .filter( + Some("polkadot_collator_protocol"), + log::LevelFilter::Trace, + ) + .filter( + Some(LOG_TARGET), + log::LevelFilter::Trace, + ) + .try_init(); + + let pool = sp_core::testing::TaskExecutor::new(); + + let (context, virtual_overseer) = test_helpers::make_subsystem_context(pool.clone()); + + let keystore = TestKeyStore::new(); + keystore.sr25519_generate_new( + polkadot_primitives::v1::PARACHAIN_KEY_TYPE_ID, + Some(&Sr25519Keyring::Alice.to_seed()), + ).unwrap(); + + let subsystem = run( + context, + Arc::new(keystore), + crate::CollatorEvictionPolicy { + inactive_collator: ACTIVITY_TIMEOUT, + undeclared: DECLARE_TIMEOUT, + }, + Metrics::default(), + ); + + let test_fut = test(TestHarness { virtual_overseer }); + + 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(); +} + +const TIMEOUT: Duration = Duration::from_millis(200); + +async fn overseer_send( + overseer: &mut VirtualOverseer, + msg: CollatorProtocolMessage, +) { + tracing::trace!("Sending message:\n{:?}", &msg); + overseer + .send(FromOverseer::Communication { msg }) + .timeout(TIMEOUT) + .await + .expect(&format!("{:?} is enough for sending messages.", TIMEOUT)); +} + +async fn overseer_recv( + overseer: &mut VirtualOverseer, +) -> AllMessages { + let msg = overseer_recv_with_timeout(overseer, TIMEOUT) + .await + .expect(&format!("{:?} is enough to receive messages.", TIMEOUT)); + + tracing::trace!("Received message:\n{:?}", &msg); + + msg +} + +async fn overseer_recv_with_timeout( + overseer: &mut VirtualOverseer, + timeout: Duration, +) -> Option { + tracing::trace!("Waiting for message..."); + overseer + .recv() + .timeout(timeout) + .await +} + +async fn overseer_signal( + overseer: &mut VirtualOverseer, + signal: OverseerSignal, +) { + overseer + .send(FromOverseer::Signal(signal)) + .timeout(TIMEOUT) + .await + .expect(&format!("{:?} is more than enough for sending signals.", TIMEOUT)); +} + +async fn respond_to_core_info_queries( + virtual_overseer: &mut VirtualOverseer, + test_state: &TestState, +) { + assert_matches!( + overseer_recv(virtual_overseer).await, + AllMessages::RuntimeApi(RuntimeApiMessage::Request( + _, + RuntimeApiRequest::Validators(tx), + )) => { + let _ = tx.send(Ok(test_state.validator_public.clone())); + } + ); + + assert_matches!( + overseer_recv(virtual_overseer).await, + AllMessages::RuntimeApi(RuntimeApiMessage::Request( + _, + RuntimeApiRequest::ValidatorGroups(tx), + )) => { + let _ = tx.send(Ok(( + test_state.validator_groups.clone(), + test_state.group_rotation_info.clone(), + ))); + } + ); + + assert_matches!( + overseer_recv(virtual_overseer).await, + AllMessages::RuntimeApi(RuntimeApiMessage::Request( + _, + RuntimeApiRequest::AvailabilityCores(tx), + )) => { + let _ = tx.send(Ok(test_state.cores.clone())); + } + ); +} + +// As we receive a relevant advertisement act on it and issue a collation request. +#[test] +fn act_on_advertisement() { + let test_state = TestState::default(); + + test_harness(|test_harness| async move { + let TestHarness { + mut virtual_overseer, + } = test_harness; + + let pair = CollatorPair::generate().0; + tracing::trace!("activating"); + + overseer_send( + &mut virtual_overseer, + CollatorProtocolMessage::NetworkBridgeUpdateV1( + NetworkBridgeEvent::OurViewChange(our_view![test_state.relay_parent]) + ) + ).await; + + respond_to_core_info_queries(&mut virtual_overseer, &test_state).await; + + let peer_b = PeerId::random(); + + overseer_send( + &mut virtual_overseer, + CollatorProtocolMessage::NetworkBridgeUpdateV1( + NetworkBridgeEvent::PeerConnected( + peer_b, + ObservedRole::Full, + None, + ), + ) + ).await; + + overseer_send( + &mut virtual_overseer, + CollatorProtocolMessage::NetworkBridgeUpdateV1( + NetworkBridgeEvent::PeerMessage( + peer_b.clone(), + protocol_v1::CollatorProtocolMessage::Declare( + pair.public(), + test_state.chain_ids[0], + pair.sign(&protocol_v1::declare_signature_payload(&peer_b)), + ) + ) + ) + ).await; + + overseer_send( + &mut virtual_overseer, + CollatorProtocolMessage::NetworkBridgeUpdateV1( + NetworkBridgeEvent::PeerMessage( + peer_b.clone(), + protocol_v1::CollatorProtocolMessage::AdvertiseCollation( + test_state.relay_parent, + ) + ) + ) + ).await; + + assert_matches!( + overseer_recv(&mut virtual_overseer).await, + AllMessages::NetworkBridge(NetworkBridgeMessage::SendRequests(reqs, IfDisconnected::ImmediateError) + ) => { + let req = reqs.into_iter().next() + .expect("There should be exactly one request"); + match req { + Requests::CollationFetching(req) => { + let payload = req.payload; + assert_eq!(payload.relay_parent, test_state.relay_parent); + assert_eq!(payload.para_id, test_state.chain_ids[0]); + } + _ => panic!("Unexpected request"), + } + }); + + virtual_overseer + }); +} + +// Test that other subsystems may modify collators' reputations. +#[test] +fn collator_reporting_works() { + let test_state = TestState::default(); + + test_harness(|test_harness| async move { + let TestHarness { + mut virtual_overseer, + } = test_harness; + + overseer_send( + &mut virtual_overseer, + CollatorProtocolMessage::NetworkBridgeUpdateV1( + NetworkBridgeEvent::OurViewChange(our_view![test_state.relay_parent]) + ) + ).await; + + respond_to_core_info_queries(&mut virtual_overseer, &test_state).await; + + let peer_b = PeerId::random(); + let peer_c = PeerId::random(); + + overseer_send( + &mut virtual_overseer, + CollatorProtocolMessage::NetworkBridgeUpdateV1( + NetworkBridgeEvent::PeerConnected( + peer_b, + ObservedRole::Full, + None, + ), + ) + ).await; + + overseer_send( + &mut virtual_overseer, + CollatorProtocolMessage::NetworkBridgeUpdateV1( + NetworkBridgeEvent::PeerConnected( + peer_c, + ObservedRole::Full, + None, + ), + ) + ).await; + + overseer_send( + &mut virtual_overseer, + CollatorProtocolMessage::NetworkBridgeUpdateV1( + NetworkBridgeEvent::PeerMessage( + peer_b.clone(), + protocol_v1::CollatorProtocolMessage::Declare( + test_state.collators[0].public(), + test_state.chain_ids[0], + test_state.collators[0].sign(&protocol_v1::declare_signature_payload(&peer_b)), + ), + ) + ) + ).await; + + overseer_send( + &mut virtual_overseer, + CollatorProtocolMessage::NetworkBridgeUpdateV1( + NetworkBridgeEvent::PeerMessage( + peer_c.clone(), + protocol_v1::CollatorProtocolMessage::Declare( + test_state.collators[1].public(), + test_state.chain_ids[0], + test_state.collators[1].sign(&protocol_v1::declare_signature_payload(&peer_c)), + ), + ) + ) + ).await; + + overseer_send( + &mut virtual_overseer, + CollatorProtocolMessage::ReportCollator(test_state.collators[0].public()), + ).await; + + assert_matches!( + overseer_recv(&mut virtual_overseer).await, + AllMessages::NetworkBridge( + NetworkBridgeMessage::ReportPeer(peer, rep), + ) => { + assert_eq!(peer, peer_b); + assert_eq!(rep, COST_REPORT_BAD); + } + ); + + virtual_overseer + }); +} + +// Test that we verify the signatures on `Declare` and `AdvertiseCollation` messages. +#[test] +fn collator_authentication_verification_works() { + let test_state = TestState::default(); + + test_harness(|test_harness| async move { + let TestHarness { + mut virtual_overseer, + } = test_harness; + + let peer_b = PeerId::random(); + + overseer_send( + &mut virtual_overseer, + CollatorProtocolMessage::NetworkBridgeUpdateV1( + NetworkBridgeEvent::PeerConnected( + peer_b, + ObservedRole::Full, + None, + ), + ) + ).await; + + // the peer sends a declare message but sign the wrong payload + overseer_send( + &mut virtual_overseer, + CollatorProtocolMessage::NetworkBridgeUpdateV1(NetworkBridgeEvent::PeerMessage( + peer_b.clone(), + protocol_v1::CollatorProtocolMessage::Declare( + test_state.collators[0].public(), + test_state.chain_ids[0], + test_state.collators[0].sign(&[42]), + ), + )), + ) + .await; + + // it should be reported for sending a message with an invalid signature + assert_matches!( + overseer_recv(&mut virtual_overseer).await, + AllMessages::NetworkBridge( + NetworkBridgeMessage::ReportPeer(peer, rep), + ) => { + assert_eq!(peer, peer_b); + assert_eq!(rep, COST_INVALID_SIGNATURE); + } + ); + virtual_overseer + }); +} + +// A test scenario that takes the following steps +// - Two collators connect, declare themselves and advertise a collation relevant to +// our view. +// - This results subsystem acting upon these advertisements and issuing two messages to +// the CandidateBacking subsystem. +// - CandidateBacking requests both of the collations. +// - Collation protocol requests these collations. +// - The collations are sent to it. +// - Collations are fetched correctly. +#[test] +fn fetch_collations_works() { + let test_state = TestState::default(); + + test_harness(|test_harness| async move { + let TestHarness { + mut virtual_overseer, + } = test_harness; + + overseer_send( + &mut virtual_overseer, + CollatorProtocolMessage::NetworkBridgeUpdateV1( + NetworkBridgeEvent::OurViewChange(our_view![test_state.relay_parent]) + ), + ).await; + + respond_to_core_info_queries(&mut virtual_overseer, &test_state).await; + + let peer_b = PeerId::random(); + let peer_c = PeerId::random(); + + overseer_send( + &mut virtual_overseer, + CollatorProtocolMessage::NetworkBridgeUpdateV1( + NetworkBridgeEvent::PeerConnected( + peer_b, + ObservedRole::Full, + None, + ), + ) + ).await; + + overseer_send( + &mut virtual_overseer, + CollatorProtocolMessage::NetworkBridgeUpdateV1( + NetworkBridgeEvent::PeerConnected( + peer_c, + ObservedRole::Full, + None, + ), + ) + ).await; + + overseer_send( + &mut virtual_overseer, + CollatorProtocolMessage::NetworkBridgeUpdateV1( + NetworkBridgeEvent::PeerMessage( + peer_b.clone(), + protocol_v1::CollatorProtocolMessage::Declare( + test_state.collators[0].public(), + test_state.chain_ids[0], + test_state.collators[0].sign(&protocol_v1::declare_signature_payload(&peer_b)), + ) + ) + ) + ).await; + + overseer_send( + &mut virtual_overseer, + CollatorProtocolMessage::NetworkBridgeUpdateV1( + NetworkBridgeEvent::PeerMessage( + peer_c.clone(), + protocol_v1::CollatorProtocolMessage::Declare( + test_state.collators[1].public(), + test_state.chain_ids[0], + test_state.collators[1].sign(&protocol_v1::declare_signature_payload(&peer_c)), + ) + ) + ) + ).await; + + overseer_send( + &mut virtual_overseer, + CollatorProtocolMessage::NetworkBridgeUpdateV1( + NetworkBridgeEvent::PeerMessage( + peer_b.clone(), + protocol_v1::CollatorProtocolMessage::AdvertiseCollation( + test_state.relay_parent, + ) + ) + ) + ).await; + + overseer_send( + &mut virtual_overseer, + CollatorProtocolMessage::NetworkBridgeUpdateV1( + NetworkBridgeEvent::PeerMessage( + peer_c.clone(), + protocol_v1::CollatorProtocolMessage::AdvertiseCollation( + test_state.relay_parent, + ) + ) + ) + ).await; + + let response_channel = assert_matches!( + overseer_recv(&mut virtual_overseer).await, + AllMessages::NetworkBridge(NetworkBridgeMessage::SendRequests(reqs, IfDisconnected::ImmediateError) + ) => { + let req = reqs.into_iter().next() + .expect("There should be exactly one request"); + match req { + Requests::CollationFetching(req) => { + let payload = req.payload; + assert_eq!(payload.relay_parent, test_state.relay_parent); + assert_eq!(payload.para_id, test_state.chain_ids[0]); + req.pending_response + } + _ => panic!("Unexpected request"), + } + }); + + let mut candidate_a = CandidateReceipt::default(); + candidate_a.descriptor.para_id = test_state.chain_ids[0]; + candidate_a.descriptor.relay_parent = test_state.relay_parent; + response_channel.send(Ok( + CollationFetchingResponse::Collation( + candidate_a.clone(), + PoV { + block_data: BlockData(vec![]), + }, + ).encode() + )).expect("Sending response should succeed"); + + let _ = assert_matches!( + overseer_recv(&mut virtual_overseer).await, + AllMessages::NetworkBridge(NetworkBridgeMessage::SendRequests(reqs, IfDisconnected::ImmediateError) + ) => { + let req = reqs.into_iter().next() + .expect("There should be exactly one request"); + match req { + Requests::CollationFetching(req) => { + let payload = req.payload; + assert_eq!(payload.relay_parent, test_state.relay_parent); + assert_eq!(payload.para_id, test_state.chain_ids[0]); + req.pending_response + } + _ => panic!("Unexpected request"), + } + }); + + virtual_overseer + }); +} + +#[test] +fn inactive_disconnected() { + let test_state = TestState::default(); + + test_harness(|test_harness| async move { + let TestHarness { + mut virtual_overseer, + } = test_harness; + + let pair = CollatorPair::generate().0; + + let hash_a = test_state.relay_parent; + + overseer_send( + &mut virtual_overseer, + CollatorProtocolMessage::NetworkBridgeUpdateV1( + NetworkBridgeEvent::OurViewChange(our_view![hash_a]) + ) + ).await; + + respond_to_core_info_queries(&mut virtual_overseer, &test_state).await; + + let peer_b = PeerId::random(); + + overseer_send( + &mut virtual_overseer, + CollatorProtocolMessage::NetworkBridgeUpdateV1( + NetworkBridgeEvent::PeerConnected( + peer_b.clone(), + ObservedRole::Full, + None, + ) + ) + ).await; + + overseer_send( + &mut virtual_overseer, + CollatorProtocolMessage::NetworkBridgeUpdateV1( + NetworkBridgeEvent::PeerMessage( + peer_b.clone(), + protocol_v1::CollatorProtocolMessage::Declare( + pair.public(), + test_state.chain_ids[0], + pair.sign(&protocol_v1::declare_signature_payload(&peer_b)), + ) + ) + ) + ).await; + + overseer_send( + &mut virtual_overseer, + CollatorProtocolMessage::NetworkBridgeUpdateV1( + NetworkBridgeEvent::PeerMessage( + peer_b.clone(), + protocol_v1::CollatorProtocolMessage::AdvertiseCollation( + test_state.relay_parent, + ) + ) + ) + ).await; + + let _ = assert_matches!( + overseer_recv(&mut virtual_overseer).await, + AllMessages::NetworkBridge(NetworkBridgeMessage::SendRequests(reqs, IfDisconnected::ImmediateError) + ) => { + let req = reqs.into_iter().next() + .expect("There should be exactly one request"); + match req { + Requests::CollationFetching(req) => { + let payload = req.payload; + assert_eq!(payload.relay_parent, test_state.relay_parent); + assert_eq!(payload.para_id, test_state.chain_ids[0]); + req.pending_response + } + _ => panic!("Unexpected request"), + } + }); + + Delay::new(ACTIVITY_TIMEOUT * 3).await; + + assert_matches!( + overseer_recv(&mut virtual_overseer).await, + AllMessages::NetworkBridge(NetworkBridgeMessage::ReportPeer( + peer, + rep, + )) => { + assert_eq!(peer, peer_b); + assert_eq!(rep, COST_REQUEST_TIMED_OUT); + } + ); + + assert_matches!( + overseer_recv(&mut virtual_overseer).await, + AllMessages::NetworkBridge(NetworkBridgeMessage::DisconnectPeer( + peer, + peer_set, + )) => { + assert_eq!(peer, peer_b); + assert_eq!(peer_set, PeerSet::Collation); + } + ); + virtual_overseer + }); +} + +#[test] +fn activity_extends_life() { + let test_state = TestState::default(); + + test_harness(|test_harness| async move { + let TestHarness { + mut virtual_overseer, + } = test_harness; + + let pair = CollatorPair::generate().0; + + let hash_a = test_state.relay_parent; + let hash_b = Hash::repeat_byte(1); + let hash_c = Hash::repeat_byte(2); + + overseer_send( + &mut virtual_overseer, + CollatorProtocolMessage::NetworkBridgeUpdateV1( + NetworkBridgeEvent::OurViewChange(our_view![hash_a, hash_b, hash_c]) + ) + ).await; + + // 3 heads, 3 times. + respond_to_core_info_queries(&mut virtual_overseer, &test_state).await; + respond_to_core_info_queries(&mut virtual_overseer, &test_state).await; + respond_to_core_info_queries(&mut virtual_overseer, &test_state).await; + + let peer_b = PeerId::random(); + + overseer_send( + &mut virtual_overseer, + CollatorProtocolMessage::NetworkBridgeUpdateV1( + NetworkBridgeEvent::PeerConnected( + peer_b.clone(), + ObservedRole::Full, + None, + ) + ) + ).await; + + overseer_send( + &mut virtual_overseer, + CollatorProtocolMessage::NetworkBridgeUpdateV1( + NetworkBridgeEvent::PeerMessage( + peer_b.clone(), + protocol_v1::CollatorProtocolMessage::Declare( + pair.public(), + test_state.chain_ids[0], + pair.sign(&protocol_v1::declare_signature_payload(&peer_b)), + ) + ) + ) + ).await; + + Delay::new(ACTIVITY_TIMEOUT * 2 / 3).await; + + overseer_send( + &mut virtual_overseer, + CollatorProtocolMessage::NetworkBridgeUpdateV1( + NetworkBridgeEvent::PeerMessage( + peer_b.clone(), + protocol_v1::CollatorProtocolMessage::AdvertiseCollation( + hash_a, + ) + ) + ) + ).await; + + assert_matches!( + overseer_recv(&mut virtual_overseer).await, + AllMessages::NetworkBridge(NetworkBridgeMessage::SendRequests(reqs, IfDisconnected::ImmediateError) + ) => { + let req = reqs.into_iter().next() + .expect("There should be exactly one request"); + match req { + Requests::CollationFetching(req) => { + let payload = req.payload; + assert_eq!(payload.relay_parent, hash_a); + assert_eq!(payload.para_id, test_state.chain_ids[0]); + } + _ => panic!("Unexpected request"), + } + }); + + Delay::new(ACTIVITY_TIMEOUT * 2 / 3).await; + + overseer_send( + &mut virtual_overseer, + CollatorProtocolMessage::NetworkBridgeUpdateV1( + NetworkBridgeEvent::PeerMessage( + peer_b.clone(), + protocol_v1::CollatorProtocolMessage::AdvertiseCollation( + hash_b + ) + ) + ) + ).await; + + assert_matches!( + overseer_recv(&mut virtual_overseer).await, + AllMessages::NetworkBridge(NetworkBridgeMessage::ReportPeer( + peer, + rep, + )) => { + assert_eq!(peer, peer_b); + assert_eq!(rep, COST_REQUEST_TIMED_OUT); + } + ); + + assert_matches!( + overseer_recv(&mut virtual_overseer).await, + AllMessages::NetworkBridge(NetworkBridgeMessage::SendRequests(reqs, IfDisconnected::ImmediateError) + ) => { + let req = reqs.into_iter().next() + .expect("There should be exactly one request"); + match req { + Requests::CollationFetching(req) => { + let payload = req.payload; + assert_eq!(payload.relay_parent, hash_b); + assert_eq!(payload.para_id, test_state.chain_ids[0]); + } + _ => panic!("Unexpected request"), + } + }); + + Delay::new(ACTIVITY_TIMEOUT * 2 / 3).await; + + overseer_send( + &mut virtual_overseer, + CollatorProtocolMessage::NetworkBridgeUpdateV1( + NetworkBridgeEvent::PeerMessage( + peer_b.clone(), + protocol_v1::CollatorProtocolMessage::AdvertiseCollation( + hash_c, + ) + ) + ) + ).await; + + assert_matches!( + overseer_recv(&mut virtual_overseer).await, + AllMessages::NetworkBridge(NetworkBridgeMessage::ReportPeer( + peer, + rep, + )) => { + assert_eq!(peer, peer_b); + assert_eq!(rep, COST_REQUEST_TIMED_OUT); + } + ); + + assert_matches!( + overseer_recv(&mut virtual_overseer).await, + AllMessages::NetworkBridge(NetworkBridgeMessage::SendRequests(reqs, IfDisconnected::ImmediateError) + ) => { + let req = reqs.into_iter().next() + .expect("There should be exactly one request"); + match req { + Requests::CollationFetching(req) => { + let payload = req.payload; + assert_eq!(payload.relay_parent, hash_c); + assert_eq!(payload.para_id, test_state.chain_ids[0]); + } + _ => panic!("Unexpected request"), + } + }); + + Delay::new(ACTIVITY_TIMEOUT * 3 / 2).await; + + assert_matches!( + overseer_recv(&mut virtual_overseer).await, + AllMessages::NetworkBridge(NetworkBridgeMessage::ReportPeer( + peer, + rep, + )) => { + assert_eq!(peer, peer_b); + assert_eq!(rep, COST_REQUEST_TIMED_OUT); + } + ); + + assert_matches!( + overseer_recv(&mut virtual_overseer).await, + AllMessages::NetworkBridge(NetworkBridgeMessage::DisconnectPeer( + peer, + peer_set, + )) => { + assert_eq!(peer, peer_b); + assert_eq!(peer_set, PeerSet::Collation); + } + ); + virtual_overseer + }); +} + +#[test] +fn disconnect_if_no_declare() { + let test_state = TestState::default(); + + test_harness(|test_harness| async move { + let TestHarness { + mut virtual_overseer, + } = test_harness; + + overseer_send( + &mut virtual_overseer, + CollatorProtocolMessage::NetworkBridgeUpdateV1( + NetworkBridgeEvent::OurViewChange(our_view![test_state.relay_parent]) + ) + ).await; + + respond_to_core_info_queries(&mut virtual_overseer, &test_state).await; + + let peer_b = PeerId::random(); + + overseer_send( + &mut virtual_overseer, + CollatorProtocolMessage::NetworkBridgeUpdateV1( + NetworkBridgeEvent::PeerConnected( + peer_b.clone(), + ObservedRole::Full, + None, + ) + ) + ).await; + + assert_matches!( + overseer_recv(&mut virtual_overseer).await, + AllMessages::NetworkBridge(NetworkBridgeMessage::DisconnectPeer( + peer, + peer_set, + )) => { + assert_eq!(peer, peer_b); + assert_eq!(peer_set, PeerSet::Collation); + } + ); + virtual_overseer + }) +} + +#[test] +fn disconnect_if_wrong_declare() { + let test_state = TestState::default(); + + test_harness(|test_harness| async move { + let TestHarness { + mut virtual_overseer, + } = test_harness; + + let pair = CollatorPair::generate().0; + + overseer_send( + &mut virtual_overseer, + CollatorProtocolMessage::NetworkBridgeUpdateV1( + NetworkBridgeEvent::OurViewChange(our_view![test_state.relay_parent]) + ) + ).await; + + respond_to_core_info_queries(&mut virtual_overseer, &test_state).await; + + let peer_b = PeerId::random(); + + overseer_send( + &mut virtual_overseer, + CollatorProtocolMessage::NetworkBridgeUpdateV1( + NetworkBridgeEvent::PeerConnected( + peer_b.clone(), + ObservedRole::Full, + None, + ) + ) + ).await; + + overseer_send( + &mut virtual_overseer, + CollatorProtocolMessage::NetworkBridgeUpdateV1( + NetworkBridgeEvent::PeerMessage( + peer_b.clone(), + protocol_v1::CollatorProtocolMessage::Declare( + pair.public(), + ParaId::from(69), + pair.sign(&protocol_v1::declare_signature_payload(&peer_b)), + ) + ) + ) + ).await; + + assert_matches!( + overseer_recv(&mut virtual_overseer).await, + AllMessages::NetworkBridge(NetworkBridgeMessage::ReportPeer( + peer, + rep, + )) => { + assert_eq!(peer, peer_b); + assert_eq!(rep, COST_UNNEEDED_COLLATOR); + } + ); + + assert_matches!( + overseer_recv(&mut virtual_overseer).await, + AllMessages::NetworkBridge(NetworkBridgeMessage::DisconnectPeer( + peer, + peer_set, + )) => { + assert_eq!(peer, peer_b); + assert_eq!(peer_set, PeerSet::Collation); + } + ); + virtual_overseer + }) +} + +#[test] +fn view_change_clears_old_collators() { + let mut test_state = TestState::default(); + + test_harness(|test_harness| async move { + let TestHarness { + mut virtual_overseer, + } = test_harness; + + let pair = CollatorPair::generate().0; + + overseer_send( + &mut virtual_overseer, + CollatorProtocolMessage::NetworkBridgeUpdateV1( + NetworkBridgeEvent::OurViewChange(our_view![test_state.relay_parent]) + ) + ).await; + + respond_to_core_info_queries(&mut virtual_overseer, &test_state).await; + + let peer_b = PeerId::random(); + + overseer_send( + &mut virtual_overseer, + CollatorProtocolMessage::NetworkBridgeUpdateV1( + NetworkBridgeEvent::PeerConnected( + peer_b.clone(), + ObservedRole::Full, + None, + ) + ) + ).await; + + overseer_send( + &mut virtual_overseer, + CollatorProtocolMessage::NetworkBridgeUpdateV1( + NetworkBridgeEvent::PeerMessage( + peer_b.clone(), + protocol_v1::CollatorProtocolMessage::Declare( + pair.public(), + test_state.chain_ids[0], + pair.sign(&protocol_v1::declare_signature_payload(&peer_b)), + ) + ) + ) + ).await; + + let hash_b = Hash::repeat_byte(69); + + overseer_send( + &mut virtual_overseer, + CollatorProtocolMessage::NetworkBridgeUpdateV1( + NetworkBridgeEvent::OurViewChange(our_view![hash_b]) + ) + ).await; + + test_state.group_rotation_info = test_state.group_rotation_info.bump_rotation(); + respond_to_core_info_queries(&mut virtual_overseer, &test_state).await; + + assert_matches!( + overseer_recv(&mut virtual_overseer).await, + AllMessages::NetworkBridge(NetworkBridgeMessage::DisconnectPeer( + peer, + peer_set, + )) => { + assert_eq!(peer, peer_b); + assert_eq!(peer_set, PeerSet::Collation); + } + ); + virtual_overseer + }) +} + +// A test scenario that takes the following steps +// - Two collators connect, declare themselves and advertise a collation relevant to +// our view. +// - This results subsystem acting upon these advertisements and issuing two messages to +// the CandidateBacking subsystem. +// - CandidateBacking requests both of the collations. +// - Collation protocol requests these collations. +// - The collations are sent to it. +// - Collations are fetched correctly. +#[test] +fn seconding_works() { + let test_state = TestState::default(); + + test_harness(|test_harness| async move { + let TestHarness { + mut virtual_overseer, + } = test_harness; + + overseer_send( + &mut virtual_overseer, + CollatorProtocolMessage::NetworkBridgeUpdateV1( + NetworkBridgeEvent::OurViewChange(our_view![test_state.relay_parent]) + ), + ).await; + + respond_to_core_info_queries(&mut virtual_overseer, &test_state).await; + + let peer_b = PeerId::random(); + let peer_c = PeerId::random(); + + overseer_send( + &mut virtual_overseer, + CollatorProtocolMessage::NetworkBridgeUpdateV1( + NetworkBridgeEvent::PeerConnected( + peer_b, + ObservedRole::Full, + None, + ), + ) + ).await; + + overseer_send( + &mut virtual_overseer, + CollatorProtocolMessage::NetworkBridgeUpdateV1( + NetworkBridgeEvent::PeerConnected( + peer_c, + ObservedRole::Full, + None, + ), + ) + ).await; + + overseer_send( + &mut virtual_overseer, + CollatorProtocolMessage::NetworkBridgeUpdateV1( + NetworkBridgeEvent::PeerMessage( + peer_b.clone(), + protocol_v1::CollatorProtocolMessage::Declare( + test_state.collators[0].public(), + test_state.chain_ids[0], + test_state.collators[0].sign(&protocol_v1::declare_signature_payload(&peer_b)), + ) + ) + ) + ).await; + + overseer_send( + &mut virtual_overseer, + CollatorProtocolMessage::NetworkBridgeUpdateV1( + NetworkBridgeEvent::PeerMessage( + peer_c.clone(), + protocol_v1::CollatorProtocolMessage::Declare( + test_state.collators[1].public(), + test_state.chain_ids[0], + test_state.collators[1].sign(&protocol_v1::declare_signature_payload(&peer_c)), + ) + ) + ) + ).await; + + overseer_send( + &mut virtual_overseer, + CollatorProtocolMessage::NetworkBridgeUpdateV1( + NetworkBridgeEvent::PeerMessage( + peer_b.clone(), + protocol_v1::CollatorProtocolMessage::AdvertiseCollation( + test_state.relay_parent, + ) + ) + ) + ).await; + + overseer_send( + &mut virtual_overseer, + CollatorProtocolMessage::NetworkBridgeUpdateV1( + NetworkBridgeEvent::PeerMessage( + peer_c.clone(), + protocol_v1::CollatorProtocolMessage::AdvertiseCollation( + test_state.relay_parent, + ) + ) + ) + ).await; + + let response_channel = assert_matches!( + overseer_recv(&mut virtual_overseer).await, + AllMessages::NetworkBridge(NetworkBridgeMessage::SendRequests(reqs, IfDisconnected::ImmediateError) + ) => { + let req = reqs.into_iter().next() + .expect("There should be exactly one request"); + match req { + Requests::CollationFetching(req) => { + let payload = req.payload; + assert_eq!(payload.relay_parent, test_state.relay_parent); + assert_eq!(payload.para_id, test_state.chain_ids[0]); + req.pending_response + } + _ => panic!("Unexpected request"), + } + }); + + let mut candidate_a = CandidateReceipt::default(); + // Memoize PoV data to ensure we receive the right one + let pov = PoV { + block_data: BlockData(vec![1, 2, 3, 4, 5]), + }; + candidate_a.descriptor.para_id = test_state.chain_ids[0]; + candidate_a.descriptor.relay_parent = test_state.relay_parent; + response_channel.send(Ok( + CollationFetchingResponse::Collation( + candidate_a.clone(), + pov.clone(), + ).encode() + )).expect("Sending response should succeed"); + + let response_channel = assert_matches!( + overseer_recv(&mut virtual_overseer).await, + AllMessages::NetworkBridge(NetworkBridgeMessage::SendRequests(reqs, IfDisconnected::ImmediateError) + ) => { + let req = reqs.into_iter().next() + .expect("There should be exactly one request"); + match req { + Requests::CollationFetching(req) => { + let payload = req.payload; + assert_eq!(payload.relay_parent, test_state.relay_parent); + assert_eq!(payload.para_id, test_state.chain_ids[0]); + req.pending_response + } + _ => panic!("Unexpected request"), + } + }); + + assert_matches!( + overseer_recv(&mut virtual_overseer).await, + AllMessages::CandidateBacking(CandidateBackingMessage::Second(relay_parent, candidate_receipt, incoming_pov) + ) => { + assert_eq!(relay_parent, test_state.relay_parent); + assert_eq!(candidate_receipt.descriptor.para_id, test_state.chain_ids[0]); + assert_eq!(incoming_pov, pov); + }); + + let mut candidate_b = CandidateReceipt::default(); + candidate_b.descriptor.para_id = test_state.chain_ids[0]; + candidate_b.descriptor.relay_parent = test_state.relay_parent; + + // Send second collation to ensure first collation gets seconded + response_channel.send(Ok( + CollationFetchingResponse::Collation( + candidate_b.clone(), + PoV { + block_data: BlockData(vec![]), + }, + ).encode() + )).expect("Sending response should succeed after seconding"); + + // Ensure we don't receive any message related to candidate backing + // All Peers should get disconnected after successful Candidate Backing Message + assert_matches!( + overseer_recv(&mut virtual_overseer).await, + AllMessages::NetworkBridge(NetworkBridgeMessage::DisconnectPeer(_, _) + ) => {}); + + virtual_overseer + }); +} diff --git a/polkadot/node/network/statement-distribution/src/lib.rs b/polkadot/node/network/statement-distribution/src/lib.rs index e5e36c144d..ba7b8483a7 100644 --- a/polkadot/node/network/statement-distribution/src/lib.rs +++ b/polkadot/node/network/statement-distribution/src/lib.rs @@ -2046,1699 +2046,4 @@ impl metrics::Metrics for Metrics { } #[cfg(test)] -mod tests { - use std::time::Duration; - use parity_scale_codec::{Decode, Encode}; - use super::*; - use std::sync::Arc; - use sp_keyring::Sr25519Keyring; - use sp_application_crypto::{AppKey, sr25519::Pair, Pair as TraitPair}; - use polkadot_node_primitives::Statement; - use polkadot_primitives::v1::{CommittedCandidateReceipt, ValidationCode, SessionInfo}; - use assert_matches::assert_matches; - use futures::executor::{self, block_on}; - use futures_timer::Delay; - use sp_keystore::{CryptoStore, SyncCryptoStorePtr, SyncCryptoStore}; - use sc_keystore::LocalKeystore; - use polkadot_node_network_protocol::{view, ObservedRole, request_response::Recipient}; - use polkadot_subsystem::{ - jaeger, ActivatedLeaf, messages::{RuntimeApiMessage, RuntimeApiRequest}, LeafStatus, - }; - use polkadot_node_network_protocol::request_response::{ - Requests, - v1::{ - StatementFetchingRequest, - StatementFetchingResponse, - }, - }; - - #[test] - fn active_head_accepts_only_2_seconded_per_validator() { - let validators = vec![ - Sr25519Keyring::Alice.public().into(), - Sr25519Keyring::Bob.public().into(), - Sr25519Keyring::Charlie.public().into(), - ]; - let parent_hash: Hash = [1; 32].into(); - - let session_index = 1; - let signing_context = SigningContext { - parent_hash, - session_index, - }; - - let candidate_a = { - let mut c = CommittedCandidateReceipt::default(); - c.descriptor.relay_parent = parent_hash; - c.descriptor.para_id = 1.into(); - c - }; - - let candidate_b = { - let mut c = CommittedCandidateReceipt::default(); - c.descriptor.relay_parent = parent_hash; - c.descriptor.para_id = 2.into(); - c - }; - - let candidate_c = { - let mut c = CommittedCandidateReceipt::default(); - c.descriptor.relay_parent = parent_hash; - c.descriptor.para_id = 3.into(); - c - }; - - let mut head_data = ActiveHeadData::new( - validators, - session_index, - PerLeafSpan::new(Arc::new(jaeger::Span::Disabled), "test"), - ); - - let keystore: SyncCryptoStorePtr = Arc::new(LocalKeystore::in_memory()); - let alice_public = SyncCryptoStore::sr25519_generate_new( - &*keystore, ValidatorId::ID, Some(&Sr25519Keyring::Alice.to_seed()) - ).unwrap(); - let bob_public = SyncCryptoStore::sr25519_generate_new( - &*keystore, ValidatorId::ID, Some(&Sr25519Keyring::Bob.to_seed()) - ).unwrap(); - - // note A - let a_seconded_val_0 = block_on(SignedFullStatement::sign( - &keystore, - Statement::Seconded(candidate_a.clone()), - &signing_context, - ValidatorIndex(0), - &alice_public.into(), - )).ok().flatten().expect("should be signed"); - assert!(head_data.check_useful_or_unknown(&a_seconded_val_0.clone().into()).is_ok()); - let noted = head_data.note_statement(a_seconded_val_0.clone()); - - assert_matches!(noted, NotedStatement::Fresh(_)); - - // note A (duplicate) - assert_eq!( - head_data.check_useful_or_unknown(&a_seconded_val_0.clone().into()), - Err(DeniedStatement::UsefulButKnown), - ); - let noted = head_data.note_statement(a_seconded_val_0); - - assert_matches!(noted, NotedStatement::UsefulButKnown); - - // note B - let statement = block_on(SignedFullStatement::sign( - &keystore, - Statement::Seconded(candidate_b.clone()), - &signing_context, - ValidatorIndex(0), - &alice_public.into(), - )).ok().flatten().expect("should be signed"); - assert!(head_data.check_useful_or_unknown(&statement.clone().into()).is_ok()); - let noted = head_data.note_statement(statement); - assert_matches!(noted, NotedStatement::Fresh(_)); - - // note C (beyond 2 - ignored) - let statement = block_on(SignedFullStatement::sign( - &keystore, - Statement::Seconded(candidate_c.clone()), - &signing_context, - ValidatorIndex(0), - &alice_public.into(), - )).ok().flatten().expect("should be signed"); - assert_eq!( - head_data.check_useful_or_unknown(&statement.clone().into()), - Err(DeniedStatement::NotUseful), - ); - let noted = head_data.note_statement(statement); - assert_matches!(noted, NotedStatement::NotUseful); - - // note B (new validator) - let statement = block_on(SignedFullStatement::sign( - &keystore, - Statement::Seconded(candidate_b.clone()), - &signing_context, - ValidatorIndex(1), - &bob_public.into(), - )).ok().flatten().expect("should be signed"); - assert!(head_data.check_useful_or_unknown(&statement.clone().into()).is_ok()); - let noted = head_data.note_statement(statement); - assert_matches!(noted, NotedStatement::Fresh(_)); - - // note C (new validator) - let statement = block_on(SignedFullStatement::sign( - &keystore, - Statement::Seconded(candidate_c.clone()), - &signing_context, - ValidatorIndex(1), - &bob_public.into(), - )).ok().flatten().expect("should be signed"); - assert!(head_data.check_useful_or_unknown(&statement.clone().into()).is_ok()); - let noted = head_data.note_statement(statement); - assert_matches!(noted, NotedStatement::Fresh(_)); - } - - #[test] - fn note_local_works() { - let hash_a = CandidateHash([1; 32].into()); - let hash_b = CandidateHash([2; 32].into()); - - let mut per_peer_tracker = VcPerPeerTracker::default(); - per_peer_tracker.note_local(hash_a.clone()); - per_peer_tracker.note_local(hash_b.clone()); - - assert!(per_peer_tracker.local_observed.contains(&hash_a)); - assert!(per_peer_tracker.local_observed.contains(&hash_b)); - - assert!(!per_peer_tracker.remote_observed.contains(&hash_a)); - assert!(!per_peer_tracker.remote_observed.contains(&hash_b)); - } - - #[test] - fn note_remote_works() { - let hash_a = CandidateHash([1; 32].into()); - let hash_b = CandidateHash([2; 32].into()); - let hash_c = CandidateHash([3; 32].into()); - - let mut per_peer_tracker = VcPerPeerTracker::default(); - assert!(per_peer_tracker.note_remote(hash_a.clone())); - assert!(per_peer_tracker.note_remote(hash_b.clone())); - assert!(!per_peer_tracker.note_remote(hash_c.clone())); - - assert!(per_peer_tracker.remote_observed.contains(&hash_a)); - assert!(per_peer_tracker.remote_observed.contains(&hash_b)); - assert!(!per_peer_tracker.remote_observed.contains(&hash_c)); - - assert!(!per_peer_tracker.local_observed.contains(&hash_a)); - assert!(!per_peer_tracker.local_observed.contains(&hash_b)); - assert!(!per_peer_tracker.local_observed.contains(&hash_c)); - } - - #[test] - fn per_peer_relay_parent_knowledge_send() { - let mut knowledge = PeerRelayParentKnowledge::default(); - - let hash_a = CandidateHash([1; 32].into()); - - // Sending an un-pinned statement should not work and should have no effect. - assert!(!knowledge.can_send(&(CompactStatement::Valid(hash_a), ValidatorIndex(0)))); - assert!(!knowledge.is_known_candidate(&hash_a)); - assert!(knowledge.sent_statements.is_empty()); - assert!(knowledge.received_statements.is_empty()); - assert!(knowledge.seconded_counts.is_empty()); - assert!(knowledge.received_message_count.is_empty()); - - // Make the peer aware of the candidate. - assert_eq!(knowledge.send(&(CompactStatement::Seconded(hash_a), ValidatorIndex(0))), true); - assert_eq!(knowledge.send(&(CompactStatement::Seconded(hash_a), ValidatorIndex(1))), false); - assert!(knowledge.is_known_candidate(&hash_a)); - assert_eq!(knowledge.sent_statements.len(), 2); - assert!(knowledge.received_statements.is_empty()); - assert_eq!(knowledge.seconded_counts.len(), 2); - assert!(knowledge.received_message_count.get(&hash_a).is_none()); - - // And now it should accept the dependent message. - assert_eq!(knowledge.send(&(CompactStatement::Valid(hash_a), ValidatorIndex(0))), false); - assert!(knowledge.is_known_candidate(&hash_a)); - assert_eq!(knowledge.sent_statements.len(), 3); - assert!(knowledge.received_statements.is_empty()); - assert_eq!(knowledge.seconded_counts.len(), 2); - assert!(knowledge.received_message_count.get(&hash_a).is_none()); - } - - #[test] - fn cant_send_after_receiving() { - let mut knowledge = PeerRelayParentKnowledge::default(); - - let hash_a = CandidateHash([1; 32].into()); - assert!(knowledge.check_can_receive(&(CompactStatement::Seconded(hash_a), ValidatorIndex(0)), 3).is_ok()); - assert!(knowledge.receive(&(CompactStatement::Seconded(hash_a), ValidatorIndex(0)), 3).unwrap()); - assert!(!knowledge.can_send(&(CompactStatement::Seconded(hash_a), ValidatorIndex(0)))); - } - - #[test] - fn per_peer_relay_parent_knowledge_receive() { - let mut knowledge = PeerRelayParentKnowledge::default(); - - let hash_a = CandidateHash([1; 32].into()); - - assert_eq!( - knowledge.check_can_receive(&(CompactStatement::Valid(hash_a), ValidatorIndex(0)), 3), - Err(COST_UNEXPECTED_STATEMENT), - ); - assert_eq!( - knowledge.receive(&(CompactStatement::Valid(hash_a), ValidatorIndex(0)), 3), - Err(COST_UNEXPECTED_STATEMENT), - ); - - assert!(knowledge.check_can_receive(&(CompactStatement::Seconded(hash_a), ValidatorIndex(0)), 3).is_ok()); - assert_eq!( - knowledge.receive(&(CompactStatement::Seconded(hash_a), ValidatorIndex(0)), 3), - Ok(true), - ); - - // Push statements up to the flood limit. - assert!(knowledge.check_can_receive(&(CompactStatement::Valid(hash_a), ValidatorIndex(1)), 3).is_ok()); - assert_eq!( - knowledge.receive(&(CompactStatement::Valid(hash_a), ValidatorIndex(1)), 3), - Ok(false), - ); - - assert!(knowledge.is_known_candidate(&hash_a)); - assert_eq!(*knowledge.received_message_count.get(&hash_a).unwrap(), 2); - - assert!(knowledge.check_can_receive(&(CompactStatement::Valid(hash_a), ValidatorIndex(2)), 3).is_ok()); - assert_eq!( - knowledge.receive(&(CompactStatement::Valid(hash_a), ValidatorIndex(2)), 3), - Ok(false), - ); - - assert_eq!(*knowledge.received_message_count.get(&hash_a).unwrap(), 3); - - assert_eq!( - knowledge.check_can_receive(&(CompactStatement::Valid(hash_a), ValidatorIndex(7)), 3), - Err(COST_APPARENT_FLOOD), - ); - assert_eq!( - knowledge.receive(&(CompactStatement::Valid(hash_a), ValidatorIndex(7)), 3), - Err(COST_APPARENT_FLOOD), - ); - - assert_eq!(*knowledge.received_message_count.get(&hash_a).unwrap(), 3); - assert_eq!(knowledge.received_statements.len(), 3); // number of prior `Ok`s. - - // Now make sure that the seconding limit is respected. - let hash_b = CandidateHash([2; 32].into()); - let hash_c = CandidateHash([3; 32].into()); - - assert!(knowledge.check_can_receive(&(CompactStatement::Seconded(hash_b), ValidatorIndex(0)), 3).is_ok()); - assert_eq!( - knowledge.receive(&(CompactStatement::Seconded(hash_b), ValidatorIndex(0)), 3), - Ok(true), - ); - - assert_eq!( - knowledge.check_can_receive(&(CompactStatement::Seconded(hash_c), ValidatorIndex(0)), 3), - Err(COST_UNEXPECTED_STATEMENT), - ); - assert_eq!( - knowledge.receive(&(CompactStatement::Seconded(hash_c), ValidatorIndex(0)), 3), - Err(COST_UNEXPECTED_STATEMENT), - ); - - // Last, make sure that already-known statements are disregarded. - assert_eq!( - knowledge.check_can_receive(&(CompactStatement::Valid(hash_a), ValidatorIndex(2)), 3), - Err(COST_DUPLICATE_STATEMENT), - ); - assert_eq!( - knowledge.receive(&(CompactStatement::Valid(hash_a), ValidatorIndex(2)), 3), - Err(COST_DUPLICATE_STATEMENT), - ); - - assert_eq!( - knowledge.check_can_receive(&(CompactStatement::Seconded(hash_b), ValidatorIndex(0)), 3), - Err(COST_DUPLICATE_STATEMENT), - ); - assert_eq!( - knowledge.receive(&(CompactStatement::Seconded(hash_b), ValidatorIndex(0)), 3), - Err(COST_DUPLICATE_STATEMENT), - ); - } - - #[test] - fn peer_view_update_sends_messages() { - let hash_a = Hash::repeat_byte(1); - let hash_b = Hash::repeat_byte(2); - let hash_c = Hash::repeat_byte(3); - - let candidate = { - let mut c = CommittedCandidateReceipt::default(); - c.descriptor.relay_parent = hash_c; - c.descriptor.para_id = 1.into(); - c - }; - let candidate_hash = candidate.hash(); - - let old_view = view![hash_a, hash_b]; - let new_view = view![hash_b, hash_c]; - - let mut active_heads = HashMap::new(); - let validators = vec![ - Sr25519Keyring::Alice.public().into(), - Sr25519Keyring::Bob.public().into(), - Sr25519Keyring::Charlie.public().into(), - ]; - - let session_index = 1; - let signing_context = SigningContext { - parent_hash: hash_c, - session_index, - }; - - let keystore: SyncCryptoStorePtr = Arc::new(LocalKeystore::in_memory()); - - let alice_public = SyncCryptoStore::sr25519_generate_new( - &*keystore, ValidatorId::ID, Some(&Sr25519Keyring::Alice.to_seed()) - ).unwrap(); - let bob_public = SyncCryptoStore::sr25519_generate_new( - &*keystore, ValidatorId::ID, Some(&Sr25519Keyring::Bob.to_seed()) - ).unwrap(); - let charlie_public = SyncCryptoStore::sr25519_generate_new( - &*keystore, ValidatorId::ID, Some(&Sr25519Keyring::Charlie.to_seed()) - ).unwrap(); - - let new_head_data = { - let mut data = ActiveHeadData::new( - validators, - session_index, - PerLeafSpan::new(Arc::new(jaeger::Span::Disabled), "test"), - ); - - let statement = block_on(SignedFullStatement::sign( - &keystore, - Statement::Seconded(candidate.clone()), - &signing_context, - ValidatorIndex(0), - &alice_public.into(), - )).ok().flatten().expect("should be signed"); - assert!(data.check_useful_or_unknown(&statement.clone().into()).is_ok()); - let noted = data.note_statement(statement); - - assert_matches!(noted, NotedStatement::Fresh(_)); - - let statement = block_on(SignedFullStatement::sign( - &keystore, - Statement::Valid(candidate_hash), - &signing_context, - ValidatorIndex(1), - &bob_public.into(), - )).ok().flatten().expect("should be signed"); - assert!(data.check_useful_or_unknown(&statement.clone().into()).is_ok()); - let noted = data.note_statement(statement); - - assert_matches!(noted, NotedStatement::Fresh(_)); - - let statement = block_on(SignedFullStatement::sign( - &keystore, - Statement::Valid(candidate_hash), - &signing_context, - ValidatorIndex(2), - &charlie_public.into(), - )).ok().flatten().expect("should be signed"); - assert!(data.check_useful_or_unknown(&statement.clone().into()).is_ok()); - let noted = data.note_statement(statement); - assert_matches!(noted, NotedStatement::Fresh(_)); - - data - }; - - active_heads.insert(hash_c, new_head_data); - - let mut peer_data = PeerData { - view: old_view, - view_knowledge: { - let mut k = HashMap::new(); - - k.insert(hash_a, Default::default()); - k.insert(hash_b, Default::default()); - - k - }, - maybe_authority: None, - }; - - let pool = sp_core::testing::TaskExecutor::new(); - let (mut ctx, mut handle) = - polkadot_node_subsystem_test_helpers - ::make_subsystem_context - ::(pool); - let peer = PeerId::random(); - - executor::block_on(async move { - update_peer_view_and_send_unlocked( - peer.clone(), - &mut peer_data, - &mut ctx, - &active_heads, - new_view.clone(), - &Default::default(), - ).await; - - assert_eq!(peer_data.view, new_view); - assert!(!peer_data.view_knowledge.contains_key(&hash_a)); - assert!(peer_data.view_knowledge.contains_key(&hash_b)); - - let c_knowledge = peer_data.view_knowledge.get(&hash_c).unwrap(); - - assert!(c_knowledge.is_known_candidate(&candidate_hash)); - assert!(c_knowledge.sent_statements.contains( - &(CompactStatement::Seconded(candidate_hash), ValidatorIndex(0)) - )); - assert!(c_knowledge.sent_statements.contains( - &(CompactStatement::Valid(candidate_hash), ValidatorIndex(1)) - )); - assert!(c_knowledge.sent_statements.contains( - &(CompactStatement::Valid(candidate_hash), ValidatorIndex(2)) - )); - - // now see if we got the 3 messages from the active head data. - let active_head = active_heads.get(&hash_c).unwrap(); - - // semi-fragile because hashmap iterator ordering is undefined, but in practice - // it will not change between runs of the program. - for statement in active_head.statements_about(candidate_hash) { - let message = handle.recv().await; - let expected_to = vec![peer.clone()]; - let expected_payload - = statement_message(hash_c, statement.statement.clone()); - - assert_matches!( - message, - AllMessages::NetworkBridge(NetworkBridgeMessage::SendValidationMessage( - to, - payload, - )) => { - assert_eq!(to, expected_to); - assert_eq!(payload, expected_payload) - } - ) - } - }); - } - - #[test] - fn circulated_statement_goes_to_all_peers_with_view() { - let hash_a = Hash::repeat_byte(1); - let hash_b = Hash::repeat_byte(2); - let hash_c = Hash::repeat_byte(3); - - let candidate = { - let mut c = CommittedCandidateReceipt::default(); - c.descriptor.relay_parent = hash_b; - c.descriptor.para_id = 1.into(); - c - }; - - let peer_a = PeerId::random(); - let peer_b = PeerId::random(); - let peer_c = PeerId::random(); - - let peer_a_view = view![hash_a]; - let peer_b_view = view![hash_a, hash_b]; - let peer_c_view = view![hash_b, hash_c]; - - let session_index = 1; - - let peer_data_from_view = |view: View| PeerData { - view: view.clone(), - view_knowledge: view.iter().map(|v| (v.clone(), Default::default())).collect(), - maybe_authority: None, - }; - - let mut peer_data: HashMap<_, _> = vec![ - (peer_a.clone(), peer_data_from_view(peer_a_view)), - (peer_b.clone(), peer_data_from_view(peer_b_view)), - (peer_c.clone(), peer_data_from_view(peer_c_view)), - ].into_iter().collect(); - - let pool = sp_core::testing::TaskExecutor::new(); - let (mut ctx, mut handle) = - polkadot_node_subsystem_test_helpers - ::make_subsystem_context - ::(pool); - - executor::block_on(async move { - let signing_context = SigningContext { - parent_hash: hash_b, - session_index, - }; - - let keystore: SyncCryptoStorePtr = Arc::new(LocalKeystore::in_memory()); - let alice_public = CryptoStore::sr25519_generate_new( - &*keystore, ValidatorId::ID, Some(&Sr25519Keyring::Alice.to_seed()) - ).await.unwrap(); - - let statement = SignedFullStatement::sign( - &keystore, - Statement::Seconded(candidate), - &signing_context, - ValidatorIndex(0), - &alice_public.into(), - ).await.ok().flatten().expect("should be signed"); - - let comparator = StoredStatementComparator { - compact: statement.payload().to_compact(), - validator_index: ValidatorIndex(0), - signature: statement.signature().clone() - }; - let statement = StoredStatement { - comparator: &comparator, - statement: &statement, - }; - - let needs_dependents = circulate_statement( - &mut peer_data, - &mut ctx, - hash_b, - statement, - Vec::new(), - ).await; - - { - assert_eq!(needs_dependents.len(), 2); - assert!(needs_dependents.contains(&peer_b)); - assert!(needs_dependents.contains(&peer_c)); - } - - let fingerprint = (statement.compact().clone(), ValidatorIndex(0)); - - assert!( - peer_data.get(&peer_b).unwrap() - .view_knowledge.get(&hash_b).unwrap() - .sent_statements.contains(&fingerprint), - ); - - assert!( - peer_data.get(&peer_c).unwrap() - .view_knowledge.get(&hash_b).unwrap() - .sent_statements.contains(&fingerprint), - ); - - let message = handle.recv().await; - assert_matches!( - message, - AllMessages::NetworkBridge(NetworkBridgeMessage::SendValidationMessage( - to, - payload, - )) => { - assert_eq!(to.len(), 2); - assert!(to.contains(&peer_b)); - assert!(to.contains(&peer_c)); - - assert_eq!( - payload, - statement_message(hash_b, statement.statement.clone()), - ); - } - ) - }); - } - - #[test] - fn receiving_from_one_sends_to_another_and_to_candidate_backing() { - let hash_a = Hash::repeat_byte(1); - - let candidate = { - let mut c = CommittedCandidateReceipt::default(); - c.descriptor.relay_parent = hash_a; - c.descriptor.para_id = 1.into(); - c - }; - - let peer_a = PeerId::random(); - let peer_b = PeerId::random(); - - let validators = vec![ - Sr25519Keyring::Alice.pair(), - Sr25519Keyring::Bob.pair(), - Sr25519Keyring::Charlie.pair(), - ]; - - let session_info = make_session_info(validators, vec![]); - - let session_index = 1; - - let pool = sp_core::testing::TaskExecutor::new(); - let (ctx, mut handle) = polkadot_node_subsystem_test_helpers::make_subsystem_context(pool); - - let bg = async move { - let s = StatementDistribution { metrics: Default::default(), keystore: Arc::new(LocalKeystore::in_memory()) }; - s.run(ctx).await.unwrap(); - }; - - let test_fut = async move { - // register our active heads. - handle.send(FromOverseer::Signal(OverseerSignal::ActiveLeaves(ActiveLeavesUpdate { - activated: vec![ActivatedLeaf { - hash: hash_a, - number: 1, - status: LeafStatus::Fresh, - span: Arc::new(jaeger::Span::Disabled), - }].into(), - deactivated: vec![].into(), - }))).await; - - assert_matches!( - handle.recv().await, - AllMessages::RuntimeApi( - RuntimeApiMessage::Request(r, RuntimeApiRequest::SessionIndexForChild(tx)) - ) - if r == hash_a - => { - let _ = tx.send(Ok(session_index)); - } - ); - - assert_matches!( - handle.recv().await, - AllMessages::RuntimeApi( - RuntimeApiMessage::Request(r, RuntimeApiRequest::SessionInfo(sess_index, tx)) - ) - if r == hash_a && sess_index == session_index - => { - let _ = tx.send(Ok(Some(session_info))); - } - ); - - // notify of peers and view - handle.send(FromOverseer::Communication { - msg: StatementDistributionMessage::NetworkBridgeUpdateV1( - NetworkBridgeEvent::PeerConnected(peer_a.clone(), ObservedRole::Full, None) - ) - }).await; - - handle.send(FromOverseer::Communication { - msg: StatementDistributionMessage::NetworkBridgeUpdateV1( - NetworkBridgeEvent::PeerConnected(peer_b.clone(), ObservedRole::Full, None) - ) - }).await; - - handle.send(FromOverseer::Communication { - msg: StatementDistributionMessage::NetworkBridgeUpdateV1( - NetworkBridgeEvent::PeerViewChange(peer_a.clone(), view![hash_a]) - ) - }).await; - - handle.send(FromOverseer::Communication { - msg: StatementDistributionMessage::NetworkBridgeUpdateV1( - NetworkBridgeEvent::PeerViewChange(peer_b.clone(), view![hash_a]) - ) - }).await; - - // receive a seconded statement from peer A. it should be propagated onwards to peer B and to - // candidate backing. - let statement = { - let signing_context = SigningContext { - parent_hash: hash_a, - session_index, - }; - - let keystore: SyncCryptoStorePtr = Arc::new(LocalKeystore::in_memory()); - let alice_public = CryptoStore::sr25519_generate_new( - &*keystore, ValidatorId::ID, Some(&Sr25519Keyring::Alice.to_seed()) - ).await.unwrap(); - - SignedFullStatement::sign( - &keystore, - Statement::Seconded(candidate), - &signing_context, - ValidatorIndex(0), - &alice_public.into(), - ).await.ok().flatten().expect("should be signed") - }; - - handle.send(FromOverseer::Communication { - msg: StatementDistributionMessage::NetworkBridgeUpdateV1( - NetworkBridgeEvent::PeerMessage( - peer_a.clone(), - protocol_v1::StatementDistributionMessage::Statement(hash_a, statement.clone().into()), - ) - ) - }).await; - - assert_matches!( - handle.recv().await, - AllMessages::NetworkBridge( - NetworkBridgeMessage::ReportPeer(p, r) - ) if p == peer_a && r == BENEFIT_VALID_STATEMENT_FIRST => {} - ); - - assert_matches!( - handle.recv().await, - AllMessages::CandidateBacking( - CandidateBackingMessage::Statement(r, s) - ) if r == hash_a && s == statement => {} - ); - - assert_matches!( - handle.recv().await, - AllMessages::NetworkBridge( - NetworkBridgeMessage::SendValidationMessage( - recipients, - protocol_v1::ValidationProtocol::StatementDistribution( - protocol_v1::StatementDistributionMessage::Statement(r, s) - ), - ) - ) => { - assert_eq!(recipients, vec![peer_b.clone()]); - assert_eq!(r, hash_a); - assert_eq!(s, statement.into()); - } - ); - handle.send(FromOverseer::Signal(OverseerSignal::Conclude)).await; - }; - - futures::pin_mut!(test_fut); - futures::pin_mut!(bg); - - executor::block_on(future::join(test_fut, bg)); - } - - #[test] - fn receiving_large_statement_from_one_sends_to_another_and_to_candidate_backing() { - sp_tracing::try_init_simple(); - let hash_a = Hash::repeat_byte(1); - let hash_b = Hash::repeat_byte(2); - - let candidate = { - let mut c = CommittedCandidateReceipt::default(); - c.descriptor.relay_parent = hash_a; - c.descriptor.para_id = 1.into(); - c.commitments.new_validation_code = Some(ValidationCode(vec![1,2,3])); - c - }; - - let peer_a = PeerId::random(); // Alice - let peer_b = PeerId::random(); // Bob - let peer_c = PeerId::random(); // Charlie - let peer_bad = PeerId::random(); // No validator - - let validators = vec![ - Sr25519Keyring::Alice.pair(), - Sr25519Keyring::Bob.pair(), - Sr25519Keyring::Charlie.pair(), - // We: - Sr25519Keyring::Ferdie.pair(), - ]; - - let session_info = make_session_info( - validators, - vec![vec![0,1,2,4], vec![3]] - ); - - let session_index = 1; - - let pool = sp_core::testing::TaskExecutor::new(); - let (ctx, mut handle) = polkadot_node_subsystem_test_helpers::make_subsystem_context(pool); - - let bg = async move { - let s = StatementDistribution { metrics: Default::default(), keystore: make_ferdie_keystore()}; - s.run(ctx).await.unwrap(); - }; - - let (mut tx_reqs, rx_reqs) = mpsc::channel(1); - - let test_fut = async move { - handle.send(FromOverseer::Communication { - msg: StatementDistributionMessage::StatementFetchingReceiver(rx_reqs) - }).await; - - // register our active heads. - handle.send(FromOverseer::Signal(OverseerSignal::ActiveLeaves(ActiveLeavesUpdate { - activated: vec![ActivatedLeaf { - hash: hash_a, - number: 1, - status: LeafStatus::Fresh, - span: Arc::new(jaeger::Span::Disabled), - }].into(), - deactivated: vec![].into(), - }))).await; - - assert_matches!( - handle.recv().await, - AllMessages::RuntimeApi( - RuntimeApiMessage::Request(r, RuntimeApiRequest::SessionIndexForChild(tx)) - ) - if r == hash_a - => { - let _ = tx.send(Ok(session_index)); - } - ); - - assert_matches!( - handle.recv().await, - AllMessages::RuntimeApi( - RuntimeApiMessage::Request(r, RuntimeApiRequest::SessionInfo(sess_index, tx)) - ) - if r == hash_a && sess_index == session_index - => { - let _ = tx.send(Ok(Some(session_info))); - } - ); - - // notify of peers and view - handle.send(FromOverseer::Communication { - msg: StatementDistributionMessage::NetworkBridgeUpdateV1( - NetworkBridgeEvent::PeerConnected( - peer_a.clone(), - ObservedRole::Full, - Some(Sr25519Keyring::Alice.public().into()) - ) - ) - }).await; - - handle.send(FromOverseer::Communication { - msg: StatementDistributionMessage::NetworkBridgeUpdateV1( - NetworkBridgeEvent::PeerConnected( - peer_b.clone(), - ObservedRole::Full, - Some(Sr25519Keyring::Bob.public().into()) - ) - ) - }).await; - handle.send(FromOverseer::Communication { - msg: StatementDistributionMessage::NetworkBridgeUpdateV1( - NetworkBridgeEvent::PeerConnected( - peer_c.clone(), - ObservedRole::Full, - Some(Sr25519Keyring::Charlie.public().into()) - ) - ) - }).await; - handle.send(FromOverseer::Communication { - msg: StatementDistributionMessage::NetworkBridgeUpdateV1( - NetworkBridgeEvent::PeerConnected(peer_bad.clone(), ObservedRole::Full, None) - ) - }).await; - - handle.send(FromOverseer::Communication { - msg: StatementDistributionMessage::NetworkBridgeUpdateV1( - NetworkBridgeEvent::PeerViewChange(peer_a.clone(), view![hash_a]) - ) - }).await; - - handle.send(FromOverseer::Communication { - msg: StatementDistributionMessage::NetworkBridgeUpdateV1( - NetworkBridgeEvent::PeerViewChange(peer_b.clone(), view![hash_a]) - ) - }).await; - handle.send(FromOverseer::Communication { - msg: StatementDistributionMessage::NetworkBridgeUpdateV1( - NetworkBridgeEvent::PeerViewChange(peer_c.clone(), view![hash_a]) - ) - }).await; - handle.send(FromOverseer::Communication { - msg: StatementDistributionMessage::NetworkBridgeUpdateV1( - NetworkBridgeEvent::PeerViewChange(peer_bad.clone(), view![hash_a]) - ) - }).await; - - // receive a seconded statement from peer A, which does not provide the request data, - // then get that data from peer C. It should be propagated onwards to peer B and to - // candidate backing. - let statement = { - let signing_context = SigningContext { - parent_hash: hash_a, - session_index, - }; - - let keystore: SyncCryptoStorePtr = Arc::new(LocalKeystore::in_memory()); - let alice_public = CryptoStore::sr25519_generate_new( - &*keystore, ValidatorId::ID, Some(&Sr25519Keyring::Alice.to_seed()) - ).await.unwrap(); - - SignedFullStatement::sign( - &keystore, - Statement::Seconded(candidate.clone()), - &signing_context, - ValidatorIndex(0), - &alice_public.into(), - ).await.ok().flatten().expect("should be signed") - }; - - let metadata = - protocol_v1::StatementDistributionMessage::Statement(hash_a, statement.clone().into()).get_metadata(); - - handle.send(FromOverseer::Communication { - msg: StatementDistributionMessage::NetworkBridgeUpdateV1( - NetworkBridgeEvent::PeerMessage( - peer_a.clone(), - protocol_v1::StatementDistributionMessage::LargeStatement(metadata.clone()), - ) - ) - }).await; - - assert_matches!( - handle.recv().await, - AllMessages::NetworkBridge( - NetworkBridgeMessage::SendRequests( - mut reqs, IfDisconnected::ImmediateError - ) - ) => { - let reqs = reqs.pop().unwrap(); - let outgoing = match reqs { - Requests::StatementFetching(outgoing) => outgoing, - _ => panic!("Unexpected request"), - }; - let req = outgoing.payload; - assert_eq!(req.relay_parent, metadata.relay_parent); - assert_eq!(req.candidate_hash, metadata.candidate_hash); - assert_eq!(outgoing.peer, Recipient::Peer(peer_a)); - // Just drop request - should trigger error. - } - ); - - // There is a race between request handler asking for more peers and processing of the - // coming `PeerMessage`s, we want the request handler to ask first here for better test - // coverage: - Delay::new(Duration::from_millis(20)).await; - - handle.send(FromOverseer::Communication { - msg: StatementDistributionMessage::NetworkBridgeUpdateV1( - NetworkBridgeEvent::PeerMessage( - peer_c.clone(), - protocol_v1::StatementDistributionMessage::LargeStatement(metadata.clone()), - ) - ) - }).await; - - // Malicious peer: - handle.send(FromOverseer::Communication { - msg: StatementDistributionMessage::NetworkBridgeUpdateV1( - NetworkBridgeEvent::PeerMessage( - peer_bad.clone(), - protocol_v1::StatementDistributionMessage::LargeStatement(metadata.clone()), - ) - ) - }).await; - - // Let c fail once too: - assert_matches!( - handle.recv().await, - AllMessages::NetworkBridge( - NetworkBridgeMessage::SendRequests( - mut reqs, IfDisconnected::ImmediateError - ) - ) => { - let reqs = reqs.pop().unwrap(); - let outgoing = match reqs { - Requests::StatementFetching(outgoing) => outgoing, - _ => panic!("Unexpected request"), - }; - let req = outgoing.payload; - assert_eq!(req.relay_parent, metadata.relay_parent); - assert_eq!(req.candidate_hash, metadata.candidate_hash); - assert_eq!(outgoing.peer, Recipient::Peer(peer_c)); - } - ); - - // a fails again: - assert_matches!( - handle.recv().await, - AllMessages::NetworkBridge( - NetworkBridgeMessage::SendRequests( - mut reqs, IfDisconnected::ImmediateError - ) - ) => { - let reqs = reqs.pop().unwrap(); - let outgoing = match reqs { - Requests::StatementFetching(outgoing) => outgoing, - _ => panic!("Unexpected request"), - }; - let req = outgoing.payload; - assert_eq!(req.relay_parent, metadata.relay_parent); - assert_eq!(req.candidate_hash, metadata.candidate_hash); - // On retry, we should have reverse order: - assert_eq!(outgoing.peer, Recipient::Peer(peer_a)); - } - ); - - // Send invalid response (all other peers have been tried now): - assert_matches!( - handle.recv().await, - AllMessages::NetworkBridge( - NetworkBridgeMessage::SendRequests( - mut reqs, IfDisconnected::ImmediateError - ) - ) => { - let reqs = reqs.pop().unwrap(); - let outgoing = match reqs { - Requests::StatementFetching(outgoing) => outgoing, - _ => panic!("Unexpected request"), - }; - let req = outgoing.payload; - assert_eq!(req.relay_parent, metadata.relay_parent); - assert_eq!(req.candidate_hash, metadata.candidate_hash); - assert_eq!(outgoing.peer, Recipient::Peer(peer_bad)); - let bad_candidate = { - let mut bad = candidate.clone(); - bad.descriptor.para_id = 0xeadbeaf.into(); - bad - }; - let response = StatementFetchingResponse::Statement(bad_candidate); - outgoing.pending_response.send(Ok(response.encode())).unwrap(); - } - ); - - // Should get punished and never tried again: - assert_matches!( - handle.recv().await, - AllMessages::NetworkBridge( - NetworkBridgeMessage::ReportPeer(p, r) - ) if p == peer_bad && r == COST_WRONG_HASH => {} - ); - - // a is tried again (retried in reverse order): - assert_matches!( - handle.recv().await, - AllMessages::NetworkBridge( - NetworkBridgeMessage::SendRequests( - mut reqs, IfDisconnected::ImmediateError - ) - ) => { - let reqs = reqs.pop().unwrap(); - let outgoing = match reqs { - Requests::StatementFetching(outgoing) => outgoing, - _ => panic!("Unexpected request"), - }; - let req = outgoing.payload; - assert_eq!(req.relay_parent, metadata.relay_parent); - assert_eq!(req.candidate_hash, metadata.candidate_hash); - // On retry, we should have reverse order: - assert_eq!(outgoing.peer, Recipient::Peer(peer_a)); - } - ); - - // c succeeds now: - assert_matches!( - handle.recv().await, - AllMessages::NetworkBridge( - NetworkBridgeMessage::SendRequests( - mut reqs, IfDisconnected::ImmediateError - ) - ) => { - let reqs = reqs.pop().unwrap(); - let outgoing = match reqs { - Requests::StatementFetching(outgoing) => outgoing, - _ => panic!("Unexpected request"), - }; - let req = outgoing.payload; - assert_eq!(req.relay_parent, metadata.relay_parent); - assert_eq!(req.candidate_hash, metadata.candidate_hash); - // On retry, we should have reverse order: - assert_eq!(outgoing.peer, Recipient::Peer(peer_c)); - let response = StatementFetchingResponse::Statement(candidate.clone()); - outgoing.pending_response.send(Ok(response.encode())).unwrap(); - } - ); - - assert_matches!( - handle.recv().await, - AllMessages::NetworkBridge( - NetworkBridgeMessage::ReportPeer(p, r) - ) if p == peer_a && r == COST_FETCH_FAIL => {} - ); - - assert_matches!( - handle.recv().await, - AllMessages::NetworkBridge( - NetworkBridgeMessage::ReportPeer(p, r) - ) if p == peer_c && r == BENEFIT_VALID_RESPONSE => {} - ); - - assert_matches!( - handle.recv().await, - AllMessages::NetworkBridge( - NetworkBridgeMessage::ReportPeer(p, r) - ) if p == peer_a && r == BENEFIT_VALID_STATEMENT_FIRST => {} - ); - - assert_matches!( - handle.recv().await, - AllMessages::CandidateBacking( - CandidateBackingMessage::Statement(r, s) - ) if r == hash_a && s == statement => {} - ); - - - // Now messages should go out: - assert_matches!( - handle.recv().await, - AllMessages::NetworkBridge( - NetworkBridgeMessage::SendValidationMessage( - mut recipients, - protocol_v1::ValidationProtocol::StatementDistribution( - protocol_v1::StatementDistributionMessage::LargeStatement(meta) - ), - ) - ) => { - tracing::debug!( - target: LOG_TARGET, - ?recipients, - "Recipients received" - ); - recipients.sort(); - let mut expected = vec![peer_b, peer_c, peer_bad]; - expected.sort(); - assert_eq!(recipients, expected); - assert_eq!(meta.relay_parent, hash_a); - assert_eq!(meta.candidate_hash, statement.payload().candidate_hash()); - assert_eq!(meta.signed_by, statement.validator_index()); - assert_eq!(&meta.signature, statement.signature()); - } - ); - - // Now that it has the candidate it should answer requests accordingly (even after a - // failed request): - - // Failing request first (wrong relay parent hash): - let (pending_response, response_rx) = oneshot::channel(); - let inner_req = StatementFetchingRequest { - relay_parent: hash_b, - candidate_hash: metadata.candidate_hash, - }; - let req = sc_network::config::IncomingRequest { - peer: peer_b, - payload: inner_req.encode(), - pending_response, - }; - tx_reqs.send(req).await.unwrap(); - assert_matches!( - response_rx.await.unwrap().result, - Err(()) => {} - ); - - // Another failing request (peer_a never received a statement from us, so it is not - // allowed to request the data): - let (pending_response, response_rx) = oneshot::channel(); - let inner_req = StatementFetchingRequest { - relay_parent: metadata.relay_parent, - candidate_hash: metadata.candidate_hash, - }; - let req = sc_network::config::IncomingRequest { - peer: peer_a, - payload: inner_req.encode(), - pending_response, - }; - tx_reqs.send(req).await.unwrap(); - assert_matches!( - response_rx.await.unwrap().result, - Err(()) => {} - ); - - // And now the succeding request from peer_b: - let (pending_response, response_rx) = oneshot::channel(); - let inner_req = StatementFetchingRequest { - relay_parent: metadata.relay_parent, - candidate_hash: metadata.candidate_hash, - }; - let req = sc_network::config::IncomingRequest { - peer: peer_b, - payload: inner_req.encode(), - pending_response, - }; - tx_reqs.send(req).await.unwrap(); - let StatementFetchingResponse::Statement(committed) = - Decode::decode(&mut response_rx.await.unwrap().result.unwrap().as_ref()).unwrap(); - assert_eq!(committed, candidate); - - handle.send(FromOverseer::Signal(OverseerSignal::Conclude)).await; - }; - - futures::pin_mut!(test_fut); - futures::pin_mut!(bg); - - executor::block_on(future::join(test_fut, bg)); - } - - #[test] - fn share_prioritizes_backing_group() { - sp_tracing::try_init_simple(); - let hash_a = Hash::repeat_byte(1); - - let candidate = { - let mut c = CommittedCandidateReceipt::default(); - c.descriptor.relay_parent = hash_a; - c.descriptor.para_id = 1.into(); - c.commitments.new_validation_code = Some(ValidationCode(vec![1,2,3])); - c - }; - - let peer_a = PeerId::random(); // Alice - let peer_b = PeerId::random(); // Bob - let peer_c = PeerId::random(); // Charlie - let peer_bad = PeerId::random(); // No validator - let peer_other_group = PeerId::random(); //Ferdie - - let mut validators = vec![ - Sr25519Keyring::Alice.pair(), - Sr25519Keyring::Bob.pair(), - Sr25519Keyring::Charlie.pair(), - // other group - Sr25519Keyring::Dave.pair(), - // We: - Sr25519Keyring::Ferdie.pair(), - ]; - - // Strictly speaking we only need MIN_GOSSIP_PEERS - 3 to make sure only priority peers - // will be served, but by using a larger value we test for overflow errors: - let dummy_count = MIN_GOSSIP_PEERS; - - // We artificially inflate our group, so there won't be any free slots for other peers. (We - // want to test that our group is prioritized): - let dummy_pairs: Vec<_> = std::iter::repeat_with(|| Pair::generate().0).take(dummy_count).collect(); - let dummy_peers: Vec<_> = std::iter::repeat_with(|| PeerId::random()).take(dummy_count).collect(); - - validators = validators.into_iter().chain(dummy_pairs.clone()).collect(); - - let mut first_group = vec![0,1,2,4]; - first_group.append(&mut (0..dummy_count as u32).map(|v| v + 5).collect()); - let session_info = make_session_info( - validators, - vec![first_group, vec![3]] - ); - - let session_index = 1; - - let pool = sp_core::testing::TaskExecutor::new(); - let (ctx, mut handle) = polkadot_node_subsystem_test_helpers::make_subsystem_context(pool); - - let bg = async move { - let s = StatementDistribution { metrics: Default::default(), keystore: make_ferdie_keystore()}; - s.run(ctx).await.unwrap(); - }; - - let (mut tx_reqs, rx_reqs) = mpsc::channel(1); - - let test_fut = async move { - handle.send(FromOverseer::Communication { - msg: StatementDistributionMessage::StatementFetchingReceiver(rx_reqs) - }).await; - - // register our active heads. - handle.send(FromOverseer::Signal(OverseerSignal::ActiveLeaves(ActiveLeavesUpdate { - activated: vec![ActivatedLeaf { - hash: hash_a, - number: 1, - status: LeafStatus::Fresh, - span: Arc::new(jaeger::Span::Disabled), - }].into(), - deactivated: vec![].into(), - }))).await; - - assert_matches!( - handle.recv().await, - AllMessages::RuntimeApi( - RuntimeApiMessage::Request(r, RuntimeApiRequest::SessionIndexForChild(tx)) - ) - if r == hash_a - => { - let _ = tx.send(Ok(session_index)); - } - ); - - assert_matches!( - handle.recv().await, - AllMessages::RuntimeApi( - RuntimeApiMessage::Request(r, RuntimeApiRequest::SessionInfo(sess_index, tx)) - ) - if r == hash_a && sess_index == session_index - => { - let _ = tx.send(Ok(Some(session_info))); - } - ); - - // notify of dummy peers and view - for (peer, pair) in dummy_peers.clone().into_iter().zip(dummy_pairs) { - handle.send(FromOverseer::Communication { - msg: StatementDistributionMessage::NetworkBridgeUpdateV1( - NetworkBridgeEvent::PeerConnected( - peer, - ObservedRole::Full, - Some(pair.public().into()), - ) - ) - }).await; - - handle.send(FromOverseer::Communication { - msg: StatementDistributionMessage::NetworkBridgeUpdateV1( - NetworkBridgeEvent::PeerViewChange(peer, view![hash_a]) - ) - }).await; - } - - // notify of peers and view - handle.send(FromOverseer::Communication { - msg: StatementDistributionMessage::NetworkBridgeUpdateV1( - NetworkBridgeEvent::PeerConnected( - peer_a.clone(), - ObservedRole::Full, - Some(Sr25519Keyring::Alice.public().into()) - ) - ) - }).await; - handle.send(FromOverseer::Communication { - msg: StatementDistributionMessage::NetworkBridgeUpdateV1( - NetworkBridgeEvent::PeerConnected( - peer_b.clone(), - ObservedRole::Full, - Some(Sr25519Keyring::Bob.public().into()) - ) - ) - }).await; - handle.send(FromOverseer::Communication { - msg: StatementDistributionMessage::NetworkBridgeUpdateV1( - NetworkBridgeEvent::PeerConnected( - peer_c.clone(), - ObservedRole::Full, - Some(Sr25519Keyring::Charlie.public().into()) - ) - ) - }).await; - handle.send(FromOverseer::Communication { - msg: StatementDistributionMessage::NetworkBridgeUpdateV1( - NetworkBridgeEvent::PeerConnected(peer_bad.clone(), ObservedRole::Full, None) - ) - }).await; - handle.send(FromOverseer::Communication { - msg: StatementDistributionMessage::NetworkBridgeUpdateV1( - NetworkBridgeEvent::PeerConnected( - peer_other_group.clone(), - ObservedRole::Full, - Some(Sr25519Keyring::Dave.public().into()) - ) - ) - }).await; - - handle.send(FromOverseer::Communication { - msg: StatementDistributionMessage::NetworkBridgeUpdateV1( - NetworkBridgeEvent::PeerViewChange(peer_a.clone(), view![hash_a]) - ) - }).await; - - handle.send(FromOverseer::Communication { - msg: StatementDistributionMessage::NetworkBridgeUpdateV1( - NetworkBridgeEvent::PeerViewChange(peer_b.clone(), view![hash_a]) - ) - }).await; - handle.send(FromOverseer::Communication { - msg: StatementDistributionMessage::NetworkBridgeUpdateV1( - NetworkBridgeEvent::PeerViewChange(peer_c.clone(), view![hash_a]) - ) - }).await; - handle.send(FromOverseer::Communication { - msg: StatementDistributionMessage::NetworkBridgeUpdateV1( - NetworkBridgeEvent::PeerViewChange(peer_bad.clone(), view![hash_a]) - ) - }).await; - handle.send(FromOverseer::Communication { - msg: StatementDistributionMessage::NetworkBridgeUpdateV1( - NetworkBridgeEvent::PeerViewChange(peer_other_group.clone(), view![hash_a]) - ) - }).await; - - // receive a seconded statement from peer A, which does not provide the request data, - // then get that data from peer C. It should be propagated onwards to peer B and to - // candidate backing. - let statement = { - let signing_context = SigningContext { - parent_hash: hash_a, - session_index, - }; - - let keystore: SyncCryptoStorePtr = Arc::new(LocalKeystore::in_memory()); - let ferdie_public = CryptoStore::sr25519_generate_new( - &*keystore, ValidatorId::ID, Some(&Sr25519Keyring::Ferdie.to_seed()) - ).await.unwrap(); - - SignedFullStatement::sign( - &keystore, - Statement::Seconded(candidate.clone()), - &signing_context, - ValidatorIndex(4), - &ferdie_public.into(), - ).await.ok().flatten().expect("should be signed") - }; - - let metadata = - protocol_v1::StatementDistributionMessage::Statement(hash_a, statement.clone().into()).get_metadata(); - - handle.send(FromOverseer::Communication { - msg: StatementDistributionMessage::Share(hash_a, statement.clone()) - }).await; - - // Messages should go out: - assert_matches!( - handle.recv().await, - AllMessages::NetworkBridge( - NetworkBridgeMessage::SendValidationMessage( - mut recipients, - protocol_v1::ValidationProtocol::StatementDistribution( - protocol_v1::StatementDistributionMessage::LargeStatement(meta) - ), - ) - ) => { - tracing::debug!( - target: LOG_TARGET, - ?recipients, - "Recipients received" - ); - recipients.sort(); - // We expect only our backing group to be the recipients, du to the inflated - // test group above: - let mut expected: Vec<_> = vec![peer_a, peer_b, peer_c].into_iter().chain(dummy_peers).collect(); - expected.sort(); - assert_eq!(recipients.len(), expected.len()); - assert_eq!(recipients, expected); - assert_eq!(meta.relay_parent, hash_a); - assert_eq!(meta.candidate_hash, statement.payload().candidate_hash()); - assert_eq!(meta.signed_by, statement.validator_index()); - assert_eq!(&meta.signature, statement.signature()); - } - ); - - // Now that it has the candidate it should answer requests accordingly: - - let (pending_response, response_rx) = oneshot::channel(); - let inner_req = StatementFetchingRequest { - relay_parent: metadata.relay_parent, - candidate_hash: metadata.candidate_hash, - }; - let req = sc_network::config::IncomingRequest { - peer: peer_b, - payload: inner_req.encode(), - pending_response, - }; - tx_reqs.send(req).await.unwrap(); - let StatementFetchingResponse::Statement(committed) = - Decode::decode(&mut response_rx.await.unwrap().result.unwrap().as_ref()).unwrap(); - assert_eq!(committed, candidate); - - handle.send(FromOverseer::Signal(OverseerSignal::Conclude)).await; - }; - - futures::pin_mut!(test_fut); - futures::pin_mut!(bg); - - executor::block_on(future::join(test_fut, bg)); - } - - #[test] - fn peer_cant_flood_with_large_statements() { - sp_tracing::try_init_simple(); - let hash_a = Hash::repeat_byte(1); - - let candidate = { - let mut c = CommittedCandidateReceipt::default(); - c.descriptor.relay_parent = hash_a; - c.descriptor.para_id = 1.into(); - c.commitments.new_validation_code = Some(ValidationCode(vec![1,2,3])); - c - }; - - let peer_a = PeerId::random(); // Alice - - let validators = vec![ - Sr25519Keyring::Alice.pair(), - Sr25519Keyring::Bob.pair(), - Sr25519Keyring::Charlie.pair(), - // other group - Sr25519Keyring::Dave.pair(), - // We: - Sr25519Keyring::Ferdie.pair(), - ]; - - let first_group = vec![0,1,2,4]; - let session_info = make_session_info( - validators, - vec![first_group, vec![3]] - ); - - let session_index = 1; - - let pool = sp_core::testing::TaskExecutor::new(); - let (ctx, mut handle) = polkadot_node_subsystem_test_helpers::make_subsystem_context(pool); - - let bg = async move { - let s = StatementDistribution { metrics: Default::default(), keystore: make_ferdie_keystore()}; - s.run(ctx).await.unwrap(); - }; - - let (_, rx_reqs) = mpsc::channel(1); - - let test_fut = async move { - handle.send(FromOverseer::Communication { - msg: StatementDistributionMessage::StatementFetchingReceiver(rx_reqs) - }).await; - - // register our active heads. - handle.send(FromOverseer::Signal(OverseerSignal::ActiveLeaves(ActiveLeavesUpdate { - activated: vec![ActivatedLeaf { - hash: hash_a, - number: 1, - status: LeafStatus::Fresh, - span: Arc::new(jaeger::Span::Disabled), - }].into(), - deactivated: vec![].into(), - }))).await; - - assert_matches!( - handle.recv().await, - AllMessages::RuntimeApi( - RuntimeApiMessage::Request(r, RuntimeApiRequest::SessionIndexForChild(tx)) - ) - if r == hash_a - => { - let _ = tx.send(Ok(session_index)); - } - ); - - assert_matches!( - handle.recv().await, - AllMessages::RuntimeApi( - RuntimeApiMessage::Request(r, RuntimeApiRequest::SessionInfo(sess_index, tx)) - ) - if r == hash_a && sess_index == session_index - => { - let _ = tx.send(Ok(Some(session_info))); - } - ); - - // notify of peers and view - handle.send(FromOverseer::Communication { - msg: StatementDistributionMessage::NetworkBridgeUpdateV1( - NetworkBridgeEvent::PeerConnected( - peer_a.clone(), - ObservedRole::Full, - Some(Sr25519Keyring::Alice.public().into()) - ) - ) - }).await; - - handle.send(FromOverseer::Communication { - msg: StatementDistributionMessage::NetworkBridgeUpdateV1( - NetworkBridgeEvent::PeerViewChange(peer_a.clone(), view![hash_a]) - ) - }).await; - - // receive a seconded statement from peer A. - let statement = { - let signing_context = SigningContext { - parent_hash: hash_a, - session_index, - }; - - let keystore: SyncCryptoStorePtr = Arc::new(LocalKeystore::in_memory()); - let alice_public = CryptoStore::sr25519_generate_new( - &*keystore, ValidatorId::ID, Some(&Sr25519Keyring::Alice.to_seed()) - ).await.unwrap(); - - SignedFullStatement::sign( - &keystore, - Statement::Seconded(candidate.clone()), - &signing_context, - ValidatorIndex(0), - &alice_public.into(), - ).await.ok().flatten().expect("should be signed") - }; - - let metadata = - protocol_v1::StatementDistributionMessage::Statement(hash_a, statement.clone().into()).get_metadata(); - - for _ in 0..MAX_LARGE_STATEMENTS_PER_SENDER + 1 { - handle.send(FromOverseer::Communication { - msg: StatementDistributionMessage::NetworkBridgeUpdateV1( - NetworkBridgeEvent::PeerMessage( - peer_a.clone(), - protocol_v1::StatementDistributionMessage::LargeStatement(metadata.clone()), - ) - ) - }).await; - } - - // We should try to fetch the data and punish the peer (but we don't know what comes - // first): - let mut requested = false; - let mut punished = false; - for _ in 0..2 { - match handle.recv().await { - AllMessages::NetworkBridge( - NetworkBridgeMessage::SendRequests( - mut reqs, IfDisconnected::ImmediateError - ) - ) => { - let reqs = reqs.pop().unwrap(); - let outgoing = match reqs { - Requests::StatementFetching(outgoing) => outgoing, - _ => panic!("Unexpected request"), - }; - let req = outgoing.payload; - assert_eq!(req.relay_parent, metadata.relay_parent); - assert_eq!(req.candidate_hash, metadata.candidate_hash); - assert_eq!(outgoing.peer, Recipient::Peer(peer_a)); - // Just drop request - should trigger error. - requested = true; - } - - AllMessages::NetworkBridge( - NetworkBridgeMessage::ReportPeer(p, r) - ) if p == peer_a && r == COST_APPARENT_FLOOD => { - punished = true; - } - - m => panic!("Unexpected message: {:?}", m), - } - } - assert!(requested, "large data has not been requested."); - assert!(punished, "Peer should have been punished for flooding."); - - handle.send(FromOverseer::Signal(OverseerSignal::Conclude)).await; - }; - - futures::pin_mut!(test_fut); - futures::pin_mut!(bg); - - executor::block_on(future::join(test_fut, bg)); - } - - fn make_session_info(validators: Vec, groups: Vec>) -> SessionInfo { - - let validator_groups: Vec> = groups - .iter().map(|g| g.into_iter().map(|v| ValidatorIndex(*v)).collect()).collect(); - - SessionInfo { - discovery_keys: validators.iter().map(|k| k.public().into()).collect(), - // Not used: - n_cores: validator_groups.len() as u32, - validator_groups, - validators: validators.iter().map(|k| k.public().into()).collect(), - // Not used values: - assignment_keys: Vec::new(), - zeroth_delay_tranche_width: 0, - relay_vrf_modulo_samples: 0, - n_delay_tranches: 0, - no_show_slots: 0, - needed_approvals: 0, - } - } - - pub fn make_ferdie_keystore() -> SyncCryptoStorePtr { - let keystore: SyncCryptoStorePtr = Arc::new(LocalKeystore::in_memory()); - SyncCryptoStore::sr25519_generate_new( - &*keystore, - ValidatorId::ID, - Some(&Sr25519Keyring::Ferdie.to_seed()), - ) - .expect("Insert key into keystore"); - keystore - } -} +mod tests; diff --git a/polkadot/node/network/statement-distribution/src/tests.rs b/polkadot/node/network/statement-distribution/src/tests.rs new file mode 100644 index 0000000000..26438ed477 --- /dev/null +++ b/polkadot/node/network/statement-distribution/src/tests.rs @@ -0,0 +1,1710 @@ +// Copyright 2020 Parity Technologies (UK) Ltd. +// This file is part of Polkadot. + +// Polkadot is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Polkadot is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Polkadot. If not, see . + +use std::time::Duration; +use parity_scale_codec::{Decode, Encode}; +use super::*; +use std::sync::Arc; +use sp_keyring::Sr25519Keyring; +use sp_application_crypto::{AppKey, sr25519::Pair, Pair as TraitPair}; +use polkadot_node_primitives::Statement; +use polkadot_primitives::v1::{CommittedCandidateReceipt, ValidationCode, SessionInfo}; +use assert_matches::assert_matches; +use futures::executor::{self, block_on}; +use futures_timer::Delay; +use sp_keystore::{CryptoStore, SyncCryptoStorePtr, SyncCryptoStore}; +use sc_keystore::LocalKeystore; +use polkadot_node_network_protocol::{view, ObservedRole, request_response::Recipient}; +use polkadot_subsystem::{ + jaeger, ActivatedLeaf, messages::{RuntimeApiMessage, RuntimeApiRequest}, LeafStatus, +}; +use polkadot_node_network_protocol::request_response::{ + Requests, + v1::{ + StatementFetchingRequest, + StatementFetchingResponse, + }, +}; + +#[test] +fn active_head_accepts_only_2_seconded_per_validator() { + let validators = vec![ + Sr25519Keyring::Alice.public().into(), + Sr25519Keyring::Bob.public().into(), + Sr25519Keyring::Charlie.public().into(), + ]; + let parent_hash: Hash = [1; 32].into(); + + let session_index = 1; + let signing_context = SigningContext { + parent_hash, + session_index, + }; + + let candidate_a = { + let mut c = CommittedCandidateReceipt::default(); + c.descriptor.relay_parent = parent_hash; + c.descriptor.para_id = 1.into(); + c + }; + + let candidate_b = { + let mut c = CommittedCandidateReceipt::default(); + c.descriptor.relay_parent = parent_hash; + c.descriptor.para_id = 2.into(); + c + }; + + let candidate_c = { + let mut c = CommittedCandidateReceipt::default(); + c.descriptor.relay_parent = parent_hash; + c.descriptor.para_id = 3.into(); + c + }; + + let mut head_data = ActiveHeadData::new( + validators, + session_index, + PerLeafSpan::new(Arc::new(jaeger::Span::Disabled), "test"), + ); + + let keystore: SyncCryptoStorePtr = Arc::new(LocalKeystore::in_memory()); + let alice_public = SyncCryptoStore::sr25519_generate_new( + &*keystore, ValidatorId::ID, Some(&Sr25519Keyring::Alice.to_seed()) + ).unwrap(); + let bob_public = SyncCryptoStore::sr25519_generate_new( + &*keystore, ValidatorId::ID, Some(&Sr25519Keyring::Bob.to_seed()) + ).unwrap(); + + // note A + let a_seconded_val_0 = block_on(SignedFullStatement::sign( + &keystore, + Statement::Seconded(candidate_a.clone()), + &signing_context, + ValidatorIndex(0), + &alice_public.into(), + )).ok().flatten().expect("should be signed"); + assert!(head_data.check_useful_or_unknown(&a_seconded_val_0.clone().into()).is_ok()); + let noted = head_data.note_statement(a_seconded_val_0.clone()); + + assert_matches!(noted, NotedStatement::Fresh(_)); + + // note A (duplicate) + assert_eq!( + head_data.check_useful_or_unknown(&a_seconded_val_0.clone().into()), + Err(DeniedStatement::UsefulButKnown), + ); + let noted = head_data.note_statement(a_seconded_val_0); + + assert_matches!(noted, NotedStatement::UsefulButKnown); + + // note B + let statement = block_on(SignedFullStatement::sign( + &keystore, + Statement::Seconded(candidate_b.clone()), + &signing_context, + ValidatorIndex(0), + &alice_public.into(), + )).ok().flatten().expect("should be signed"); + assert!(head_data.check_useful_or_unknown(&statement.clone().into()).is_ok()); + let noted = head_data.note_statement(statement); + assert_matches!(noted, NotedStatement::Fresh(_)); + + // note C (beyond 2 - ignored) + let statement = block_on(SignedFullStatement::sign( + &keystore, + Statement::Seconded(candidate_c.clone()), + &signing_context, + ValidatorIndex(0), + &alice_public.into(), + )).ok().flatten().expect("should be signed"); + assert_eq!( + head_data.check_useful_or_unknown(&statement.clone().into()), + Err(DeniedStatement::NotUseful), + ); + let noted = head_data.note_statement(statement); + assert_matches!(noted, NotedStatement::NotUseful); + + // note B (new validator) + let statement = block_on(SignedFullStatement::sign( + &keystore, + Statement::Seconded(candidate_b.clone()), + &signing_context, + ValidatorIndex(1), + &bob_public.into(), + )).ok().flatten().expect("should be signed"); + assert!(head_data.check_useful_or_unknown(&statement.clone().into()).is_ok()); + let noted = head_data.note_statement(statement); + assert_matches!(noted, NotedStatement::Fresh(_)); + + // note C (new validator) + let statement = block_on(SignedFullStatement::sign( + &keystore, + Statement::Seconded(candidate_c.clone()), + &signing_context, + ValidatorIndex(1), + &bob_public.into(), + )).ok().flatten().expect("should be signed"); + assert!(head_data.check_useful_or_unknown(&statement.clone().into()).is_ok()); + let noted = head_data.note_statement(statement); + assert_matches!(noted, NotedStatement::Fresh(_)); +} + +#[test] +fn note_local_works() { + let hash_a = CandidateHash([1; 32].into()); + let hash_b = CandidateHash([2; 32].into()); + + let mut per_peer_tracker = VcPerPeerTracker::default(); + per_peer_tracker.note_local(hash_a.clone()); + per_peer_tracker.note_local(hash_b.clone()); + + assert!(per_peer_tracker.local_observed.contains(&hash_a)); + assert!(per_peer_tracker.local_observed.contains(&hash_b)); + + assert!(!per_peer_tracker.remote_observed.contains(&hash_a)); + assert!(!per_peer_tracker.remote_observed.contains(&hash_b)); +} + +#[test] +fn note_remote_works() { + let hash_a = CandidateHash([1; 32].into()); + let hash_b = CandidateHash([2; 32].into()); + let hash_c = CandidateHash([3; 32].into()); + + let mut per_peer_tracker = VcPerPeerTracker::default(); + assert!(per_peer_tracker.note_remote(hash_a.clone())); + assert!(per_peer_tracker.note_remote(hash_b.clone())); + assert!(!per_peer_tracker.note_remote(hash_c.clone())); + + assert!(per_peer_tracker.remote_observed.contains(&hash_a)); + assert!(per_peer_tracker.remote_observed.contains(&hash_b)); + assert!(!per_peer_tracker.remote_observed.contains(&hash_c)); + + assert!(!per_peer_tracker.local_observed.contains(&hash_a)); + assert!(!per_peer_tracker.local_observed.contains(&hash_b)); + assert!(!per_peer_tracker.local_observed.contains(&hash_c)); +} + +#[test] +fn per_peer_relay_parent_knowledge_send() { + let mut knowledge = PeerRelayParentKnowledge::default(); + + let hash_a = CandidateHash([1; 32].into()); + + // Sending an un-pinned statement should not work and should have no effect. + assert!(!knowledge.can_send(&(CompactStatement::Valid(hash_a), ValidatorIndex(0)))); + assert!(!knowledge.is_known_candidate(&hash_a)); + assert!(knowledge.sent_statements.is_empty()); + assert!(knowledge.received_statements.is_empty()); + assert!(knowledge.seconded_counts.is_empty()); + assert!(knowledge.received_message_count.is_empty()); + + // Make the peer aware of the candidate. + assert_eq!(knowledge.send(&(CompactStatement::Seconded(hash_a), ValidatorIndex(0))), true); + assert_eq!(knowledge.send(&(CompactStatement::Seconded(hash_a), ValidatorIndex(1))), false); + assert!(knowledge.is_known_candidate(&hash_a)); + assert_eq!(knowledge.sent_statements.len(), 2); + assert!(knowledge.received_statements.is_empty()); + assert_eq!(knowledge.seconded_counts.len(), 2); + assert!(knowledge.received_message_count.get(&hash_a).is_none()); + + // And now it should accept the dependent message. + assert_eq!(knowledge.send(&(CompactStatement::Valid(hash_a), ValidatorIndex(0))), false); + assert!(knowledge.is_known_candidate(&hash_a)); + assert_eq!(knowledge.sent_statements.len(), 3); + assert!(knowledge.received_statements.is_empty()); + assert_eq!(knowledge.seconded_counts.len(), 2); + assert!(knowledge.received_message_count.get(&hash_a).is_none()); +} + +#[test] +fn cant_send_after_receiving() { + let mut knowledge = PeerRelayParentKnowledge::default(); + + let hash_a = CandidateHash([1; 32].into()); + assert!(knowledge.check_can_receive(&(CompactStatement::Seconded(hash_a), ValidatorIndex(0)), 3).is_ok()); + assert!(knowledge.receive(&(CompactStatement::Seconded(hash_a), ValidatorIndex(0)), 3).unwrap()); + assert!(!knowledge.can_send(&(CompactStatement::Seconded(hash_a), ValidatorIndex(0)))); +} + +#[test] +fn per_peer_relay_parent_knowledge_receive() { + let mut knowledge = PeerRelayParentKnowledge::default(); + + let hash_a = CandidateHash([1; 32].into()); + + assert_eq!( + knowledge.check_can_receive(&(CompactStatement::Valid(hash_a), ValidatorIndex(0)), 3), + Err(COST_UNEXPECTED_STATEMENT), + ); + assert_eq!( + knowledge.receive(&(CompactStatement::Valid(hash_a), ValidatorIndex(0)), 3), + Err(COST_UNEXPECTED_STATEMENT), + ); + + assert!(knowledge.check_can_receive(&(CompactStatement::Seconded(hash_a), ValidatorIndex(0)), 3).is_ok()); + assert_eq!( + knowledge.receive(&(CompactStatement::Seconded(hash_a), ValidatorIndex(0)), 3), + Ok(true), + ); + + // Push statements up to the flood limit. + assert!(knowledge.check_can_receive(&(CompactStatement::Valid(hash_a), ValidatorIndex(1)), 3).is_ok()); + assert_eq!( + knowledge.receive(&(CompactStatement::Valid(hash_a), ValidatorIndex(1)), 3), + Ok(false), + ); + + assert!(knowledge.is_known_candidate(&hash_a)); + assert_eq!(*knowledge.received_message_count.get(&hash_a).unwrap(), 2); + + assert!(knowledge.check_can_receive(&(CompactStatement::Valid(hash_a), ValidatorIndex(2)), 3).is_ok()); + assert_eq!( + knowledge.receive(&(CompactStatement::Valid(hash_a), ValidatorIndex(2)), 3), + Ok(false), + ); + + assert_eq!(*knowledge.received_message_count.get(&hash_a).unwrap(), 3); + + assert_eq!( + knowledge.check_can_receive(&(CompactStatement::Valid(hash_a), ValidatorIndex(7)), 3), + Err(COST_APPARENT_FLOOD), + ); + assert_eq!( + knowledge.receive(&(CompactStatement::Valid(hash_a), ValidatorIndex(7)), 3), + Err(COST_APPARENT_FLOOD), + ); + + assert_eq!(*knowledge.received_message_count.get(&hash_a).unwrap(), 3); + assert_eq!(knowledge.received_statements.len(), 3); // number of prior `Ok`s. + + // Now make sure that the seconding limit is respected. + let hash_b = CandidateHash([2; 32].into()); + let hash_c = CandidateHash([3; 32].into()); + + assert!(knowledge.check_can_receive(&(CompactStatement::Seconded(hash_b), ValidatorIndex(0)), 3).is_ok()); + assert_eq!( + knowledge.receive(&(CompactStatement::Seconded(hash_b), ValidatorIndex(0)), 3), + Ok(true), + ); + + assert_eq!( + knowledge.check_can_receive(&(CompactStatement::Seconded(hash_c), ValidatorIndex(0)), 3), + Err(COST_UNEXPECTED_STATEMENT), + ); + assert_eq!( + knowledge.receive(&(CompactStatement::Seconded(hash_c), ValidatorIndex(0)), 3), + Err(COST_UNEXPECTED_STATEMENT), + ); + + // Last, make sure that already-known statements are disregarded. + assert_eq!( + knowledge.check_can_receive(&(CompactStatement::Valid(hash_a), ValidatorIndex(2)), 3), + Err(COST_DUPLICATE_STATEMENT), + ); + assert_eq!( + knowledge.receive(&(CompactStatement::Valid(hash_a), ValidatorIndex(2)), 3), + Err(COST_DUPLICATE_STATEMENT), + ); + + assert_eq!( + knowledge.check_can_receive(&(CompactStatement::Seconded(hash_b), ValidatorIndex(0)), 3), + Err(COST_DUPLICATE_STATEMENT), + ); + assert_eq!( + knowledge.receive(&(CompactStatement::Seconded(hash_b), ValidatorIndex(0)), 3), + Err(COST_DUPLICATE_STATEMENT), + ); +} + +#[test] +fn peer_view_update_sends_messages() { + let hash_a = Hash::repeat_byte(1); + let hash_b = Hash::repeat_byte(2); + let hash_c = Hash::repeat_byte(3); + + let candidate = { + let mut c = CommittedCandidateReceipt::default(); + c.descriptor.relay_parent = hash_c; + c.descriptor.para_id = 1.into(); + c + }; + let candidate_hash = candidate.hash(); + + let old_view = view![hash_a, hash_b]; + let new_view = view![hash_b, hash_c]; + + let mut active_heads = HashMap::new(); + let validators = vec![ + Sr25519Keyring::Alice.public().into(), + Sr25519Keyring::Bob.public().into(), + Sr25519Keyring::Charlie.public().into(), + ]; + + let session_index = 1; + let signing_context = SigningContext { + parent_hash: hash_c, + session_index, + }; + + let keystore: SyncCryptoStorePtr = Arc::new(LocalKeystore::in_memory()); + + let alice_public = SyncCryptoStore::sr25519_generate_new( + &*keystore, ValidatorId::ID, Some(&Sr25519Keyring::Alice.to_seed()) + ).unwrap(); + let bob_public = SyncCryptoStore::sr25519_generate_new( + &*keystore, ValidatorId::ID, Some(&Sr25519Keyring::Bob.to_seed()) + ).unwrap(); + let charlie_public = SyncCryptoStore::sr25519_generate_new( + &*keystore, ValidatorId::ID, Some(&Sr25519Keyring::Charlie.to_seed()) + ).unwrap(); + + let new_head_data = { + let mut data = ActiveHeadData::new( + validators, + session_index, + PerLeafSpan::new(Arc::new(jaeger::Span::Disabled), "test"), + ); + + let statement = block_on(SignedFullStatement::sign( + &keystore, + Statement::Seconded(candidate.clone()), + &signing_context, + ValidatorIndex(0), + &alice_public.into(), + )).ok().flatten().expect("should be signed"); + assert!(data.check_useful_or_unknown(&statement.clone().into()).is_ok()); + let noted = data.note_statement(statement); + + assert_matches!(noted, NotedStatement::Fresh(_)); + + let statement = block_on(SignedFullStatement::sign( + &keystore, + Statement::Valid(candidate_hash), + &signing_context, + ValidatorIndex(1), + &bob_public.into(), + )).ok().flatten().expect("should be signed"); + assert!(data.check_useful_or_unknown(&statement.clone().into()).is_ok()); + let noted = data.note_statement(statement); + + assert_matches!(noted, NotedStatement::Fresh(_)); + + let statement = block_on(SignedFullStatement::sign( + &keystore, + Statement::Valid(candidate_hash), + &signing_context, + ValidatorIndex(2), + &charlie_public.into(), + )).ok().flatten().expect("should be signed"); + assert!(data.check_useful_or_unknown(&statement.clone().into()).is_ok()); + let noted = data.note_statement(statement); + assert_matches!(noted, NotedStatement::Fresh(_)); + + data + }; + + active_heads.insert(hash_c, new_head_data); + + let mut peer_data = PeerData { + view: old_view, + view_knowledge: { + let mut k = HashMap::new(); + + k.insert(hash_a, Default::default()); + k.insert(hash_b, Default::default()); + + k + }, + maybe_authority: None, + }; + + let pool = sp_core::testing::TaskExecutor::new(); + let (mut ctx, mut handle) = + polkadot_node_subsystem_test_helpers + ::make_subsystem_context + ::(pool); + let peer = PeerId::random(); + + executor::block_on(async move { + update_peer_view_and_send_unlocked( + peer.clone(), + &mut peer_data, + &mut ctx, + &active_heads, + new_view.clone(), + &Default::default(), + ).await; + + assert_eq!(peer_data.view, new_view); + assert!(!peer_data.view_knowledge.contains_key(&hash_a)); + assert!(peer_data.view_knowledge.contains_key(&hash_b)); + + let c_knowledge = peer_data.view_knowledge.get(&hash_c).unwrap(); + + assert!(c_knowledge.is_known_candidate(&candidate_hash)); + assert!(c_knowledge.sent_statements.contains( + &(CompactStatement::Seconded(candidate_hash), ValidatorIndex(0)) + )); + assert!(c_knowledge.sent_statements.contains( + &(CompactStatement::Valid(candidate_hash), ValidatorIndex(1)) + )); + assert!(c_knowledge.sent_statements.contains( + &(CompactStatement::Valid(candidate_hash), ValidatorIndex(2)) + )); + + // now see if we got the 3 messages from the active head data. + let active_head = active_heads.get(&hash_c).unwrap(); + + // semi-fragile because hashmap iterator ordering is undefined, but in practice + // it will not change between runs of the program. + for statement in active_head.statements_about(candidate_hash) { + let message = handle.recv().await; + let expected_to = vec![peer.clone()]; + let expected_payload + = statement_message(hash_c, statement.statement.clone()); + + assert_matches!( + message, + AllMessages::NetworkBridge(NetworkBridgeMessage::SendValidationMessage( + to, + payload, + )) => { + assert_eq!(to, expected_to); + assert_eq!(payload, expected_payload) + } + ) + } + }); +} + +#[test] +fn circulated_statement_goes_to_all_peers_with_view() { + let hash_a = Hash::repeat_byte(1); + let hash_b = Hash::repeat_byte(2); + let hash_c = Hash::repeat_byte(3); + + let candidate = { + let mut c = CommittedCandidateReceipt::default(); + c.descriptor.relay_parent = hash_b; + c.descriptor.para_id = 1.into(); + c + }; + + let peer_a = PeerId::random(); + let peer_b = PeerId::random(); + let peer_c = PeerId::random(); + + let peer_a_view = view![hash_a]; + let peer_b_view = view![hash_a, hash_b]; + let peer_c_view = view![hash_b, hash_c]; + + let session_index = 1; + + let peer_data_from_view = |view: View| PeerData { + view: view.clone(), + view_knowledge: view.iter().map(|v| (v.clone(), Default::default())).collect(), + maybe_authority: None, + }; + + let mut peer_data: HashMap<_, _> = vec![ + (peer_a.clone(), peer_data_from_view(peer_a_view)), + (peer_b.clone(), peer_data_from_view(peer_b_view)), + (peer_c.clone(), peer_data_from_view(peer_c_view)), + ].into_iter().collect(); + + let pool = sp_core::testing::TaskExecutor::new(); + let (mut ctx, mut handle) = + polkadot_node_subsystem_test_helpers + ::make_subsystem_context + ::(pool); + + executor::block_on(async move { + let signing_context = SigningContext { + parent_hash: hash_b, + session_index, + }; + + let keystore: SyncCryptoStorePtr = Arc::new(LocalKeystore::in_memory()); + let alice_public = CryptoStore::sr25519_generate_new( + &*keystore, ValidatorId::ID, Some(&Sr25519Keyring::Alice.to_seed()) + ).await.unwrap(); + + let statement = SignedFullStatement::sign( + &keystore, + Statement::Seconded(candidate), + &signing_context, + ValidatorIndex(0), + &alice_public.into(), + ).await.ok().flatten().expect("should be signed"); + + let comparator = StoredStatementComparator { + compact: statement.payload().to_compact(), + validator_index: ValidatorIndex(0), + signature: statement.signature().clone() + }; + let statement = StoredStatement { + comparator: &comparator, + statement: &statement, + }; + + let needs_dependents = circulate_statement( + &mut peer_data, + &mut ctx, + hash_b, + statement, + Vec::new(), + ).await; + + { + assert_eq!(needs_dependents.len(), 2); + assert!(needs_dependents.contains(&peer_b)); + assert!(needs_dependents.contains(&peer_c)); + } + + let fingerprint = (statement.compact().clone(), ValidatorIndex(0)); + + assert!( + peer_data.get(&peer_b).unwrap() + .view_knowledge.get(&hash_b).unwrap() + .sent_statements.contains(&fingerprint), + ); + + assert!( + peer_data.get(&peer_c).unwrap() + .view_knowledge.get(&hash_b).unwrap() + .sent_statements.contains(&fingerprint), + ); + + let message = handle.recv().await; + assert_matches!( + message, + AllMessages::NetworkBridge(NetworkBridgeMessage::SendValidationMessage( + to, + payload, + )) => { + assert_eq!(to.len(), 2); + assert!(to.contains(&peer_b)); + assert!(to.contains(&peer_c)); + + assert_eq!( + payload, + statement_message(hash_b, statement.statement.clone()), + ); + } + ) + }); +} + +#[test] +fn receiving_from_one_sends_to_another_and_to_candidate_backing() { + let hash_a = Hash::repeat_byte(1); + + let candidate = { + let mut c = CommittedCandidateReceipt::default(); + c.descriptor.relay_parent = hash_a; + c.descriptor.para_id = 1.into(); + c + }; + + let peer_a = PeerId::random(); + let peer_b = PeerId::random(); + + let validators = vec![ + Sr25519Keyring::Alice.pair(), + Sr25519Keyring::Bob.pair(), + Sr25519Keyring::Charlie.pair(), + ]; + + let session_info = make_session_info(validators, vec![]); + + let session_index = 1; + + let pool = sp_core::testing::TaskExecutor::new(); + let (ctx, mut handle) = polkadot_node_subsystem_test_helpers::make_subsystem_context(pool); + + let bg = async move { + let s = StatementDistribution { metrics: Default::default(), keystore: Arc::new(LocalKeystore::in_memory()) }; + s.run(ctx).await.unwrap(); + }; + + let test_fut = async move { + // register our active heads. + handle.send(FromOverseer::Signal(OverseerSignal::ActiveLeaves(ActiveLeavesUpdate { + activated: vec![ActivatedLeaf { + hash: hash_a, + number: 1, + status: LeafStatus::Fresh, + span: Arc::new(jaeger::Span::Disabled), + }].into(), + deactivated: vec![].into(), + }))).await; + + assert_matches!( + handle.recv().await, + AllMessages::RuntimeApi( + RuntimeApiMessage::Request(r, RuntimeApiRequest::SessionIndexForChild(tx)) + ) + if r == hash_a + => { + let _ = tx.send(Ok(session_index)); + } + ); + + assert_matches!( + handle.recv().await, + AllMessages::RuntimeApi( + RuntimeApiMessage::Request(r, RuntimeApiRequest::SessionInfo(sess_index, tx)) + ) + if r == hash_a && sess_index == session_index + => { + let _ = tx.send(Ok(Some(session_info))); + } + ); + + // notify of peers and view + handle.send(FromOverseer::Communication { + msg: StatementDistributionMessage::NetworkBridgeUpdateV1( + NetworkBridgeEvent::PeerConnected(peer_a.clone(), ObservedRole::Full, None) + ) + }).await; + + handle.send(FromOverseer::Communication { + msg: StatementDistributionMessage::NetworkBridgeUpdateV1( + NetworkBridgeEvent::PeerConnected(peer_b.clone(), ObservedRole::Full, None) + ) + }).await; + + handle.send(FromOverseer::Communication { + msg: StatementDistributionMessage::NetworkBridgeUpdateV1( + NetworkBridgeEvent::PeerViewChange(peer_a.clone(), view![hash_a]) + ) + }).await; + + handle.send(FromOverseer::Communication { + msg: StatementDistributionMessage::NetworkBridgeUpdateV1( + NetworkBridgeEvent::PeerViewChange(peer_b.clone(), view![hash_a]) + ) + }).await; + + // receive a seconded statement from peer A. it should be propagated onwards to peer B and to + // candidate backing. + let statement = { + let signing_context = SigningContext { + parent_hash: hash_a, + session_index, + }; + + let keystore: SyncCryptoStorePtr = Arc::new(LocalKeystore::in_memory()); + let alice_public = CryptoStore::sr25519_generate_new( + &*keystore, ValidatorId::ID, Some(&Sr25519Keyring::Alice.to_seed()) + ).await.unwrap(); + + SignedFullStatement::sign( + &keystore, + Statement::Seconded(candidate), + &signing_context, + ValidatorIndex(0), + &alice_public.into(), + ).await.ok().flatten().expect("should be signed") + }; + + handle.send(FromOverseer::Communication { + msg: StatementDistributionMessage::NetworkBridgeUpdateV1( + NetworkBridgeEvent::PeerMessage( + peer_a.clone(), + protocol_v1::StatementDistributionMessage::Statement(hash_a, statement.clone().into()), + ) + ) + }).await; + + assert_matches!( + handle.recv().await, + AllMessages::NetworkBridge( + NetworkBridgeMessage::ReportPeer(p, r) + ) if p == peer_a && r == BENEFIT_VALID_STATEMENT_FIRST => {} + ); + + assert_matches!( + handle.recv().await, + AllMessages::CandidateBacking( + CandidateBackingMessage::Statement(r, s) + ) if r == hash_a && s == statement => {} + ); + + assert_matches!( + handle.recv().await, + AllMessages::NetworkBridge( + NetworkBridgeMessage::SendValidationMessage( + recipients, + protocol_v1::ValidationProtocol::StatementDistribution( + protocol_v1::StatementDistributionMessage::Statement(r, s) + ), + ) + ) => { + assert_eq!(recipients, vec![peer_b.clone()]); + assert_eq!(r, hash_a); + assert_eq!(s, statement.into()); + } + ); + handle.send(FromOverseer::Signal(OverseerSignal::Conclude)).await; + }; + + futures::pin_mut!(test_fut); + futures::pin_mut!(bg); + + executor::block_on(future::join(test_fut, bg)); +} + +#[test] +fn receiving_large_statement_from_one_sends_to_another_and_to_candidate_backing() { + sp_tracing::try_init_simple(); + let hash_a = Hash::repeat_byte(1); + let hash_b = Hash::repeat_byte(2); + + let candidate = { + let mut c = CommittedCandidateReceipt::default(); + c.descriptor.relay_parent = hash_a; + c.descriptor.para_id = 1.into(); + c.commitments.new_validation_code = Some(ValidationCode(vec![1,2,3])); + c + }; + + let peer_a = PeerId::random(); // Alice + let peer_b = PeerId::random(); // Bob + let peer_c = PeerId::random(); // Charlie + let peer_bad = PeerId::random(); // No validator + + let validators = vec![ + Sr25519Keyring::Alice.pair(), + Sr25519Keyring::Bob.pair(), + Sr25519Keyring::Charlie.pair(), + // We: + Sr25519Keyring::Ferdie.pair(), + ]; + + let session_info = make_session_info( + validators, + vec![vec![0,1,2,4], vec![3]] + ); + + let session_index = 1; + + let pool = sp_core::testing::TaskExecutor::new(); + let (ctx, mut handle) = polkadot_node_subsystem_test_helpers::make_subsystem_context(pool); + + let bg = async move { + let s = StatementDistribution { metrics: Default::default(), keystore: make_ferdie_keystore()}; + s.run(ctx).await.unwrap(); + }; + + let (mut tx_reqs, rx_reqs) = mpsc::channel(1); + + let test_fut = async move { + handle.send(FromOverseer::Communication { + msg: StatementDistributionMessage::StatementFetchingReceiver(rx_reqs) + }).await; + + // register our active heads. + handle.send(FromOverseer::Signal(OverseerSignal::ActiveLeaves(ActiveLeavesUpdate { + activated: vec![ActivatedLeaf { + hash: hash_a, + number: 1, + status: LeafStatus::Fresh, + span: Arc::new(jaeger::Span::Disabled), + }].into(), + deactivated: vec![].into(), + }))).await; + + assert_matches!( + handle.recv().await, + AllMessages::RuntimeApi( + RuntimeApiMessage::Request(r, RuntimeApiRequest::SessionIndexForChild(tx)) + ) + if r == hash_a + => { + let _ = tx.send(Ok(session_index)); + } + ); + + assert_matches!( + handle.recv().await, + AllMessages::RuntimeApi( + RuntimeApiMessage::Request(r, RuntimeApiRequest::SessionInfo(sess_index, tx)) + ) + if r == hash_a && sess_index == session_index + => { + let _ = tx.send(Ok(Some(session_info))); + } + ); + + // notify of peers and view + handle.send(FromOverseer::Communication { + msg: StatementDistributionMessage::NetworkBridgeUpdateV1( + NetworkBridgeEvent::PeerConnected( + peer_a.clone(), + ObservedRole::Full, + Some(Sr25519Keyring::Alice.public().into()) + ) + ) + }).await; + + handle.send(FromOverseer::Communication { + msg: StatementDistributionMessage::NetworkBridgeUpdateV1( + NetworkBridgeEvent::PeerConnected( + peer_b.clone(), + ObservedRole::Full, + Some(Sr25519Keyring::Bob.public().into()) + ) + ) + }).await; + handle.send(FromOverseer::Communication { + msg: StatementDistributionMessage::NetworkBridgeUpdateV1( + NetworkBridgeEvent::PeerConnected( + peer_c.clone(), + ObservedRole::Full, + Some(Sr25519Keyring::Charlie.public().into()) + ) + ) + }).await; + handle.send(FromOverseer::Communication { + msg: StatementDistributionMessage::NetworkBridgeUpdateV1( + NetworkBridgeEvent::PeerConnected(peer_bad.clone(), ObservedRole::Full, None) + ) + }).await; + + handle.send(FromOverseer::Communication { + msg: StatementDistributionMessage::NetworkBridgeUpdateV1( + NetworkBridgeEvent::PeerViewChange(peer_a.clone(), view![hash_a]) + ) + }).await; + + handle.send(FromOverseer::Communication { + msg: StatementDistributionMessage::NetworkBridgeUpdateV1( + NetworkBridgeEvent::PeerViewChange(peer_b.clone(), view![hash_a]) + ) + }).await; + handle.send(FromOverseer::Communication { + msg: StatementDistributionMessage::NetworkBridgeUpdateV1( + NetworkBridgeEvent::PeerViewChange(peer_c.clone(), view![hash_a]) + ) + }).await; + handle.send(FromOverseer::Communication { + msg: StatementDistributionMessage::NetworkBridgeUpdateV1( + NetworkBridgeEvent::PeerViewChange(peer_bad.clone(), view![hash_a]) + ) + }).await; + + // receive a seconded statement from peer A, which does not provide the request data, + // then get that data from peer C. It should be propagated onwards to peer B and to + // candidate backing. + let statement = { + let signing_context = SigningContext { + parent_hash: hash_a, + session_index, + }; + + let keystore: SyncCryptoStorePtr = Arc::new(LocalKeystore::in_memory()); + let alice_public = CryptoStore::sr25519_generate_new( + &*keystore, ValidatorId::ID, Some(&Sr25519Keyring::Alice.to_seed()) + ).await.unwrap(); + + SignedFullStatement::sign( + &keystore, + Statement::Seconded(candidate.clone()), + &signing_context, + ValidatorIndex(0), + &alice_public.into(), + ).await.ok().flatten().expect("should be signed") + }; + + let metadata = + protocol_v1::StatementDistributionMessage::Statement(hash_a, statement.clone().into()).get_metadata(); + + handle.send(FromOverseer::Communication { + msg: StatementDistributionMessage::NetworkBridgeUpdateV1( + NetworkBridgeEvent::PeerMessage( + peer_a.clone(), + protocol_v1::StatementDistributionMessage::LargeStatement(metadata.clone()), + ) + ) + }).await; + + assert_matches!( + handle.recv().await, + AllMessages::NetworkBridge( + NetworkBridgeMessage::SendRequests( + mut reqs, IfDisconnected::ImmediateError + ) + ) => { + let reqs = reqs.pop().unwrap(); + let outgoing = match reqs { + Requests::StatementFetching(outgoing) => outgoing, + _ => panic!("Unexpected request"), + }; + let req = outgoing.payload; + assert_eq!(req.relay_parent, metadata.relay_parent); + assert_eq!(req.candidate_hash, metadata.candidate_hash); + assert_eq!(outgoing.peer, Recipient::Peer(peer_a)); + // Just drop request - should trigger error. + } + ); + + // There is a race between request handler asking for more peers and processing of the + // coming `PeerMessage`s, we want the request handler to ask first here for better test + // coverage: + Delay::new(Duration::from_millis(20)).await; + + handle.send(FromOverseer::Communication { + msg: StatementDistributionMessage::NetworkBridgeUpdateV1( + NetworkBridgeEvent::PeerMessage( + peer_c.clone(), + protocol_v1::StatementDistributionMessage::LargeStatement(metadata.clone()), + ) + ) + }).await; + + // Malicious peer: + handle.send(FromOverseer::Communication { + msg: StatementDistributionMessage::NetworkBridgeUpdateV1( + NetworkBridgeEvent::PeerMessage( + peer_bad.clone(), + protocol_v1::StatementDistributionMessage::LargeStatement(metadata.clone()), + ) + ) + }).await; + + // Let c fail once too: + assert_matches!( + handle.recv().await, + AllMessages::NetworkBridge( + NetworkBridgeMessage::SendRequests( + mut reqs, IfDisconnected::ImmediateError + ) + ) => { + let reqs = reqs.pop().unwrap(); + let outgoing = match reqs { + Requests::StatementFetching(outgoing) => outgoing, + _ => panic!("Unexpected request"), + }; + let req = outgoing.payload; + assert_eq!(req.relay_parent, metadata.relay_parent); + assert_eq!(req.candidate_hash, metadata.candidate_hash); + assert_eq!(outgoing.peer, Recipient::Peer(peer_c)); + } + ); + + // a fails again: + assert_matches!( + handle.recv().await, + AllMessages::NetworkBridge( + NetworkBridgeMessage::SendRequests( + mut reqs, IfDisconnected::ImmediateError + ) + ) => { + let reqs = reqs.pop().unwrap(); + let outgoing = match reqs { + Requests::StatementFetching(outgoing) => outgoing, + _ => panic!("Unexpected request"), + }; + let req = outgoing.payload; + assert_eq!(req.relay_parent, metadata.relay_parent); + assert_eq!(req.candidate_hash, metadata.candidate_hash); + // On retry, we should have reverse order: + assert_eq!(outgoing.peer, Recipient::Peer(peer_a)); + } + ); + + // Send invalid response (all other peers have been tried now): + assert_matches!( + handle.recv().await, + AllMessages::NetworkBridge( + NetworkBridgeMessage::SendRequests( + mut reqs, IfDisconnected::ImmediateError + ) + ) => { + let reqs = reqs.pop().unwrap(); + let outgoing = match reqs { + Requests::StatementFetching(outgoing) => outgoing, + _ => panic!("Unexpected request"), + }; + let req = outgoing.payload; + assert_eq!(req.relay_parent, metadata.relay_parent); + assert_eq!(req.candidate_hash, metadata.candidate_hash); + assert_eq!(outgoing.peer, Recipient::Peer(peer_bad)); + let bad_candidate = { + let mut bad = candidate.clone(); + bad.descriptor.para_id = 0xeadbeaf.into(); + bad + }; + let response = StatementFetchingResponse::Statement(bad_candidate); + outgoing.pending_response.send(Ok(response.encode())).unwrap(); + } + ); + + // Should get punished and never tried again: + assert_matches!( + handle.recv().await, + AllMessages::NetworkBridge( + NetworkBridgeMessage::ReportPeer(p, r) + ) if p == peer_bad && r == COST_WRONG_HASH => {} + ); + + // a is tried again (retried in reverse order): + assert_matches!( + handle.recv().await, + AllMessages::NetworkBridge( + NetworkBridgeMessage::SendRequests( + mut reqs, IfDisconnected::ImmediateError + ) + ) => { + let reqs = reqs.pop().unwrap(); + let outgoing = match reqs { + Requests::StatementFetching(outgoing) => outgoing, + _ => panic!("Unexpected request"), + }; + let req = outgoing.payload; + assert_eq!(req.relay_parent, metadata.relay_parent); + assert_eq!(req.candidate_hash, metadata.candidate_hash); + // On retry, we should have reverse order: + assert_eq!(outgoing.peer, Recipient::Peer(peer_a)); + } + ); + + // c succeeds now: + assert_matches!( + handle.recv().await, + AllMessages::NetworkBridge( + NetworkBridgeMessage::SendRequests( + mut reqs, IfDisconnected::ImmediateError + ) + ) => { + let reqs = reqs.pop().unwrap(); + let outgoing = match reqs { + Requests::StatementFetching(outgoing) => outgoing, + _ => panic!("Unexpected request"), + }; + let req = outgoing.payload; + assert_eq!(req.relay_parent, metadata.relay_parent); + assert_eq!(req.candidate_hash, metadata.candidate_hash); + // On retry, we should have reverse order: + assert_eq!(outgoing.peer, Recipient::Peer(peer_c)); + let response = StatementFetchingResponse::Statement(candidate.clone()); + outgoing.pending_response.send(Ok(response.encode())).unwrap(); + } + ); + + assert_matches!( + handle.recv().await, + AllMessages::NetworkBridge( + NetworkBridgeMessage::ReportPeer(p, r) + ) if p == peer_a && r == COST_FETCH_FAIL => {} + ); + + assert_matches!( + handle.recv().await, + AllMessages::NetworkBridge( + NetworkBridgeMessage::ReportPeer(p, r) + ) if p == peer_c && r == BENEFIT_VALID_RESPONSE => {} + ); + + assert_matches!( + handle.recv().await, + AllMessages::NetworkBridge( + NetworkBridgeMessage::ReportPeer(p, r) + ) if p == peer_a && r == BENEFIT_VALID_STATEMENT_FIRST => {} + ); + + assert_matches!( + handle.recv().await, + AllMessages::CandidateBacking( + CandidateBackingMessage::Statement(r, s) + ) if r == hash_a && s == statement => {} + ); + + + // Now messages should go out: + assert_matches!( + handle.recv().await, + AllMessages::NetworkBridge( + NetworkBridgeMessage::SendValidationMessage( + mut recipients, + protocol_v1::ValidationProtocol::StatementDistribution( + protocol_v1::StatementDistributionMessage::LargeStatement(meta) + ), + ) + ) => { + tracing::debug!( + target: LOG_TARGET, + ?recipients, + "Recipients received" + ); + recipients.sort(); + let mut expected = vec![peer_b, peer_c, peer_bad]; + expected.sort(); + assert_eq!(recipients, expected); + assert_eq!(meta.relay_parent, hash_a); + assert_eq!(meta.candidate_hash, statement.payload().candidate_hash()); + assert_eq!(meta.signed_by, statement.validator_index()); + assert_eq!(&meta.signature, statement.signature()); + } + ); + + // Now that it has the candidate it should answer requests accordingly (even after a + // failed request): + + // Failing request first (wrong relay parent hash): + let (pending_response, response_rx) = oneshot::channel(); + let inner_req = StatementFetchingRequest { + relay_parent: hash_b, + candidate_hash: metadata.candidate_hash, + }; + let req = sc_network::config::IncomingRequest { + peer: peer_b, + payload: inner_req.encode(), + pending_response, + }; + tx_reqs.send(req).await.unwrap(); + assert_matches!( + response_rx.await.unwrap().result, + Err(()) => {} + ); + + // Another failing request (peer_a never received a statement from us, so it is not + // allowed to request the data): + let (pending_response, response_rx) = oneshot::channel(); + let inner_req = StatementFetchingRequest { + relay_parent: metadata.relay_parent, + candidate_hash: metadata.candidate_hash, + }; + let req = sc_network::config::IncomingRequest { + peer: peer_a, + payload: inner_req.encode(), + pending_response, + }; + tx_reqs.send(req).await.unwrap(); + assert_matches!( + response_rx.await.unwrap().result, + Err(()) => {} + ); + + // And now the succeding request from peer_b: + let (pending_response, response_rx) = oneshot::channel(); + let inner_req = StatementFetchingRequest { + relay_parent: metadata.relay_parent, + candidate_hash: metadata.candidate_hash, + }; + let req = sc_network::config::IncomingRequest { + peer: peer_b, + payload: inner_req.encode(), + pending_response, + }; + tx_reqs.send(req).await.unwrap(); + let StatementFetchingResponse::Statement(committed) = + Decode::decode(&mut response_rx.await.unwrap().result.unwrap().as_ref()).unwrap(); + assert_eq!(committed, candidate); + + handle.send(FromOverseer::Signal(OverseerSignal::Conclude)).await; + }; + + futures::pin_mut!(test_fut); + futures::pin_mut!(bg); + + executor::block_on(future::join(test_fut, bg)); +} + +#[test] +fn share_prioritizes_backing_group() { + sp_tracing::try_init_simple(); + let hash_a = Hash::repeat_byte(1); + + let candidate = { + let mut c = CommittedCandidateReceipt::default(); + c.descriptor.relay_parent = hash_a; + c.descriptor.para_id = 1.into(); + c.commitments.new_validation_code = Some(ValidationCode(vec![1,2,3])); + c + }; + + let peer_a = PeerId::random(); // Alice + let peer_b = PeerId::random(); // Bob + let peer_c = PeerId::random(); // Charlie + let peer_bad = PeerId::random(); // No validator + let peer_other_group = PeerId::random(); //Ferdie + + let mut validators = vec![ + Sr25519Keyring::Alice.pair(), + Sr25519Keyring::Bob.pair(), + Sr25519Keyring::Charlie.pair(), + // other group + Sr25519Keyring::Dave.pair(), + // We: + Sr25519Keyring::Ferdie.pair(), + ]; + + // Strictly speaking we only need MIN_GOSSIP_PEERS - 3 to make sure only priority peers + // will be served, but by using a larger value we test for overflow errors: + let dummy_count = MIN_GOSSIP_PEERS; + + // We artificially inflate our group, so there won't be any free slots for other peers. (We + // want to test that our group is prioritized): + let dummy_pairs: Vec<_> = std::iter::repeat_with(|| Pair::generate().0).take(dummy_count).collect(); + let dummy_peers: Vec<_> = std::iter::repeat_with(|| PeerId::random()).take(dummy_count).collect(); + + validators = validators.into_iter().chain(dummy_pairs.clone()).collect(); + + let mut first_group = vec![0,1,2,4]; + first_group.append(&mut (0..dummy_count as u32).map(|v| v + 5).collect()); + let session_info = make_session_info( + validators, + vec![first_group, vec![3]] + ); + + let session_index = 1; + + let pool = sp_core::testing::TaskExecutor::new(); + let (ctx, mut handle) = polkadot_node_subsystem_test_helpers::make_subsystem_context(pool); + + let bg = async move { + let s = StatementDistribution { metrics: Default::default(), keystore: make_ferdie_keystore()}; + s.run(ctx).await.unwrap(); + }; + + let (mut tx_reqs, rx_reqs) = mpsc::channel(1); + + let test_fut = async move { + handle.send(FromOverseer::Communication { + msg: StatementDistributionMessage::StatementFetchingReceiver(rx_reqs) + }).await; + + // register our active heads. + handle.send(FromOverseer::Signal(OverseerSignal::ActiveLeaves(ActiveLeavesUpdate { + activated: vec![ActivatedLeaf { + hash: hash_a, + number: 1, + status: LeafStatus::Fresh, + span: Arc::new(jaeger::Span::Disabled), + }].into(), + deactivated: vec![].into(), + }))).await; + + assert_matches!( + handle.recv().await, + AllMessages::RuntimeApi( + RuntimeApiMessage::Request(r, RuntimeApiRequest::SessionIndexForChild(tx)) + ) + if r == hash_a + => { + let _ = tx.send(Ok(session_index)); + } + ); + + assert_matches!( + handle.recv().await, + AllMessages::RuntimeApi( + RuntimeApiMessage::Request(r, RuntimeApiRequest::SessionInfo(sess_index, tx)) + ) + if r == hash_a && sess_index == session_index + => { + let _ = tx.send(Ok(Some(session_info))); + } + ); + + // notify of dummy peers and view + for (peer, pair) in dummy_peers.clone().into_iter().zip(dummy_pairs) { + handle.send(FromOverseer::Communication { + msg: StatementDistributionMessage::NetworkBridgeUpdateV1( + NetworkBridgeEvent::PeerConnected( + peer, + ObservedRole::Full, + Some(pair.public().into()), + ) + ) + }).await; + + handle.send(FromOverseer::Communication { + msg: StatementDistributionMessage::NetworkBridgeUpdateV1( + NetworkBridgeEvent::PeerViewChange(peer, view![hash_a]) + ) + }).await; + } + + // notify of peers and view + handle.send(FromOverseer::Communication { + msg: StatementDistributionMessage::NetworkBridgeUpdateV1( + NetworkBridgeEvent::PeerConnected( + peer_a.clone(), + ObservedRole::Full, + Some(Sr25519Keyring::Alice.public().into()) + ) + ) + }).await; + handle.send(FromOverseer::Communication { + msg: StatementDistributionMessage::NetworkBridgeUpdateV1( + NetworkBridgeEvent::PeerConnected( + peer_b.clone(), + ObservedRole::Full, + Some(Sr25519Keyring::Bob.public().into()) + ) + ) + }).await; + handle.send(FromOverseer::Communication { + msg: StatementDistributionMessage::NetworkBridgeUpdateV1( + NetworkBridgeEvent::PeerConnected( + peer_c.clone(), + ObservedRole::Full, + Some(Sr25519Keyring::Charlie.public().into()) + ) + ) + }).await; + handle.send(FromOverseer::Communication { + msg: StatementDistributionMessage::NetworkBridgeUpdateV1( + NetworkBridgeEvent::PeerConnected(peer_bad.clone(), ObservedRole::Full, None) + ) + }).await; + handle.send(FromOverseer::Communication { + msg: StatementDistributionMessage::NetworkBridgeUpdateV1( + NetworkBridgeEvent::PeerConnected( + peer_other_group.clone(), + ObservedRole::Full, + Some(Sr25519Keyring::Dave.public().into()) + ) + ) + }).await; + + handle.send(FromOverseer::Communication { + msg: StatementDistributionMessage::NetworkBridgeUpdateV1( + NetworkBridgeEvent::PeerViewChange(peer_a.clone(), view![hash_a]) + ) + }).await; + + handle.send(FromOverseer::Communication { + msg: StatementDistributionMessage::NetworkBridgeUpdateV1( + NetworkBridgeEvent::PeerViewChange(peer_b.clone(), view![hash_a]) + ) + }).await; + handle.send(FromOverseer::Communication { + msg: StatementDistributionMessage::NetworkBridgeUpdateV1( + NetworkBridgeEvent::PeerViewChange(peer_c.clone(), view![hash_a]) + ) + }).await; + handle.send(FromOverseer::Communication { + msg: StatementDistributionMessage::NetworkBridgeUpdateV1( + NetworkBridgeEvent::PeerViewChange(peer_bad.clone(), view![hash_a]) + ) + }).await; + handle.send(FromOverseer::Communication { + msg: StatementDistributionMessage::NetworkBridgeUpdateV1( + NetworkBridgeEvent::PeerViewChange(peer_other_group.clone(), view![hash_a]) + ) + }).await; + + // receive a seconded statement from peer A, which does not provide the request data, + // then get that data from peer C. It should be propagated onwards to peer B and to + // candidate backing. + let statement = { + let signing_context = SigningContext { + parent_hash: hash_a, + session_index, + }; + + let keystore: SyncCryptoStorePtr = Arc::new(LocalKeystore::in_memory()); + let ferdie_public = CryptoStore::sr25519_generate_new( + &*keystore, ValidatorId::ID, Some(&Sr25519Keyring::Ferdie.to_seed()) + ).await.unwrap(); + + SignedFullStatement::sign( + &keystore, + Statement::Seconded(candidate.clone()), + &signing_context, + ValidatorIndex(4), + &ferdie_public.into(), + ).await.ok().flatten().expect("should be signed") + }; + + let metadata = + protocol_v1::StatementDistributionMessage::Statement(hash_a, statement.clone().into()).get_metadata(); + + handle.send(FromOverseer::Communication { + msg: StatementDistributionMessage::Share(hash_a, statement.clone()) + }).await; + + // Messages should go out: + assert_matches!( + handle.recv().await, + AllMessages::NetworkBridge( + NetworkBridgeMessage::SendValidationMessage( + mut recipients, + protocol_v1::ValidationProtocol::StatementDistribution( + protocol_v1::StatementDistributionMessage::LargeStatement(meta) + ), + ) + ) => { + tracing::debug!( + target: LOG_TARGET, + ?recipients, + "Recipients received" + ); + recipients.sort(); + // We expect only our backing group to be the recipients, du to the inflated + // test group above: + let mut expected: Vec<_> = vec![peer_a, peer_b, peer_c].into_iter().chain(dummy_peers).collect(); + expected.sort(); + assert_eq!(recipients.len(), expected.len()); + assert_eq!(recipients, expected); + assert_eq!(meta.relay_parent, hash_a); + assert_eq!(meta.candidate_hash, statement.payload().candidate_hash()); + assert_eq!(meta.signed_by, statement.validator_index()); + assert_eq!(&meta.signature, statement.signature()); + } + ); + + // Now that it has the candidate it should answer requests accordingly: + + let (pending_response, response_rx) = oneshot::channel(); + let inner_req = StatementFetchingRequest { + relay_parent: metadata.relay_parent, + candidate_hash: metadata.candidate_hash, + }; + let req = sc_network::config::IncomingRequest { + peer: peer_b, + payload: inner_req.encode(), + pending_response, + }; + tx_reqs.send(req).await.unwrap(); + let StatementFetchingResponse::Statement(committed) = + Decode::decode(&mut response_rx.await.unwrap().result.unwrap().as_ref()).unwrap(); + assert_eq!(committed, candidate); + + handle.send(FromOverseer::Signal(OverseerSignal::Conclude)).await; + }; + + futures::pin_mut!(test_fut); + futures::pin_mut!(bg); + + executor::block_on(future::join(test_fut, bg)); +} + +#[test] +fn peer_cant_flood_with_large_statements() { + sp_tracing::try_init_simple(); + let hash_a = Hash::repeat_byte(1); + + let candidate = { + let mut c = CommittedCandidateReceipt::default(); + c.descriptor.relay_parent = hash_a; + c.descriptor.para_id = 1.into(); + c.commitments.new_validation_code = Some(ValidationCode(vec![1,2,3])); + c + }; + + let peer_a = PeerId::random(); // Alice + + let validators = vec![ + Sr25519Keyring::Alice.pair(), + Sr25519Keyring::Bob.pair(), + Sr25519Keyring::Charlie.pair(), + // other group + Sr25519Keyring::Dave.pair(), + // We: + Sr25519Keyring::Ferdie.pair(), + ]; + + let first_group = vec![0,1,2,4]; + let session_info = make_session_info( + validators, + vec![first_group, vec![3]] + ); + + let session_index = 1; + + let pool = sp_core::testing::TaskExecutor::new(); + let (ctx, mut handle) = polkadot_node_subsystem_test_helpers::make_subsystem_context(pool); + + let bg = async move { + let s = StatementDistribution { metrics: Default::default(), keystore: make_ferdie_keystore()}; + s.run(ctx).await.unwrap(); + }; + + let (_, rx_reqs) = mpsc::channel(1); + + let test_fut = async move { + handle.send(FromOverseer::Communication { + msg: StatementDistributionMessage::StatementFetchingReceiver(rx_reqs) + }).await; + + // register our active heads. + handle.send(FromOverseer::Signal(OverseerSignal::ActiveLeaves(ActiveLeavesUpdate { + activated: vec![ActivatedLeaf { + hash: hash_a, + number: 1, + status: LeafStatus::Fresh, + span: Arc::new(jaeger::Span::Disabled), + }].into(), + deactivated: vec![].into(), + }))).await; + + assert_matches!( + handle.recv().await, + AllMessages::RuntimeApi( + RuntimeApiMessage::Request(r, RuntimeApiRequest::SessionIndexForChild(tx)) + ) + if r == hash_a + => { + let _ = tx.send(Ok(session_index)); + } + ); + + assert_matches!( + handle.recv().await, + AllMessages::RuntimeApi( + RuntimeApiMessage::Request(r, RuntimeApiRequest::SessionInfo(sess_index, tx)) + ) + if r == hash_a && sess_index == session_index + => { + let _ = tx.send(Ok(Some(session_info))); + } + ); + + // notify of peers and view + handle.send(FromOverseer::Communication { + msg: StatementDistributionMessage::NetworkBridgeUpdateV1( + NetworkBridgeEvent::PeerConnected( + peer_a.clone(), + ObservedRole::Full, + Some(Sr25519Keyring::Alice.public().into()) + ) + ) + }).await; + + handle.send(FromOverseer::Communication { + msg: StatementDistributionMessage::NetworkBridgeUpdateV1( + NetworkBridgeEvent::PeerViewChange(peer_a.clone(), view![hash_a]) + ) + }).await; + + // receive a seconded statement from peer A. + let statement = { + let signing_context = SigningContext { + parent_hash: hash_a, + session_index, + }; + + let keystore: SyncCryptoStorePtr = Arc::new(LocalKeystore::in_memory()); + let alice_public = CryptoStore::sr25519_generate_new( + &*keystore, ValidatorId::ID, Some(&Sr25519Keyring::Alice.to_seed()) + ).await.unwrap(); + + SignedFullStatement::sign( + &keystore, + Statement::Seconded(candidate.clone()), + &signing_context, + ValidatorIndex(0), + &alice_public.into(), + ).await.ok().flatten().expect("should be signed") + }; + + let metadata = + protocol_v1::StatementDistributionMessage::Statement(hash_a, statement.clone().into()).get_metadata(); + + for _ in 0..MAX_LARGE_STATEMENTS_PER_SENDER + 1 { + handle.send(FromOverseer::Communication { + msg: StatementDistributionMessage::NetworkBridgeUpdateV1( + NetworkBridgeEvent::PeerMessage( + peer_a.clone(), + protocol_v1::StatementDistributionMessage::LargeStatement(metadata.clone()), + ) + ) + }).await; + } + + // We should try to fetch the data and punish the peer (but we don't know what comes + // first): + let mut requested = false; + let mut punished = false; + for _ in 0..2 { + match handle.recv().await { + AllMessages::NetworkBridge( + NetworkBridgeMessage::SendRequests( + mut reqs, IfDisconnected::ImmediateError + ) + ) => { + let reqs = reqs.pop().unwrap(); + let outgoing = match reqs { + Requests::StatementFetching(outgoing) => outgoing, + _ => panic!("Unexpected request"), + }; + let req = outgoing.payload; + assert_eq!(req.relay_parent, metadata.relay_parent); + assert_eq!(req.candidate_hash, metadata.candidate_hash); + assert_eq!(outgoing.peer, Recipient::Peer(peer_a)); + // Just drop request - should trigger error. + requested = true; + } + + AllMessages::NetworkBridge( + NetworkBridgeMessage::ReportPeer(p, r) + ) if p == peer_a && r == COST_APPARENT_FLOOD => { + punished = true; + } + + m => panic!("Unexpected message: {:?}", m), + } + } + assert!(requested, "large data has not been requested."); + assert!(punished, "Peer should have been punished for flooding."); + + handle.send(FromOverseer::Signal(OverseerSignal::Conclude)).await; + }; + + futures::pin_mut!(test_fut); + futures::pin_mut!(bg); + + executor::block_on(future::join(test_fut, bg)); +} + +fn make_session_info(validators: Vec, groups: Vec>) -> SessionInfo { + + let validator_groups: Vec> = groups + .iter().map(|g| g.into_iter().map(|v| ValidatorIndex(*v)).collect()).collect(); + + SessionInfo { + discovery_keys: validators.iter().map(|k| k.public().into()).collect(), + // Not used: + n_cores: validator_groups.len() as u32, + validator_groups, + validators: validators.iter().map(|k| k.public().into()).collect(), + // Not used values: + assignment_keys: Vec::new(), + zeroth_delay_tranche_width: 0, + relay_vrf_modulo_samples: 0, + n_delay_tranches: 0, + no_show_slots: 0, + needed_approvals: 0, + } +} + +pub fn make_ferdie_keystore() -> SyncCryptoStorePtr { + let keystore: SyncCryptoStorePtr = Arc::new(LocalKeystore::in_memory()); + SyncCryptoStore::sr25519_generate_new( + &*keystore, + ValidatorId::ID, + Some(&Sr25519Keyring::Ferdie.to_seed()), + ) + .expect("Insert key into keystore"); + keystore +} diff --git a/polkadot/node/overseer/src/lib.rs b/polkadot/node/overseer/src/lib.rs index 1e731283db..2463e545fd 100644 --- a/polkadot/node/overseer/src/lib.rs +++ b/polkadot/node/overseer/src/lib.rs @@ -2198,1142 +2198,4 @@ fn spawn( } #[cfg(test)] -mod tests { - use std::sync::atomic; - use std::collections::HashMap; - use futures::{executor, pin_mut, select, FutureExt, pending}; - - use polkadot_primitives::v1::{CollatorPair, CandidateHash}; - use polkadot_subsystem::{messages::RuntimeApiRequest, messages::NetworkBridgeEvent, jaeger}; - use polkadot_node_primitives::{CollationResult, CollationGenerationConfig, PoV, BlockData}; - use polkadot_node_network_protocol::{PeerId, UnifiedReputationChange}; - use polkadot_node_subsystem_util::metered; - - use sp_core::crypto::Pair as _; - use assert_matches::assert_matches; - - use super::*; - - struct TestSubsystem1(metered::MeteredSender); - - impl Subsystem for TestSubsystem1 - where C: SubsystemContext - { - fn start(self, mut ctx: C) -> SpawnedSubsystem { - let mut sender = self.0; - SpawnedSubsystem { - name: "test-subsystem-1", - future: Box::pin(async move { - let mut i = 0; - loop { - match ctx.recv().await { - Ok(FromOverseer::Communication { .. }) => { - let _ = sender.send(i).await; - i += 1; - continue; - } - Ok(FromOverseer::Signal(OverseerSignal::Conclude)) => return Ok(()), - Err(_) => return Ok(()), - _ => (), - } - } - }), - } - } - } - - struct TestSubsystem2(metered::MeteredSender); - - impl Subsystem for TestSubsystem2 - where C: SubsystemContext - { - fn start(self, mut ctx: C) -> SpawnedSubsystem { - let sender = self.0.clone(); - SpawnedSubsystem { - name: "test-subsystem-2", - future: Box::pin(async move { - let _sender = sender; - let mut c: usize = 0; - loop { - if c < 10 { - let (tx, _) = oneshot::channel(); - ctx.send_message( - AllMessages::CandidateValidation( - CandidateValidationMessage::ValidateFromChainState( - Default::default(), - PoV { - block_data: BlockData(Vec::new()), - }.into(), - tx, - ) - ) - ).await; - c += 1; - continue; - } - match ctx.try_recv().await { - Ok(Some(FromOverseer::Signal(OverseerSignal::Conclude))) => { - break; - } - Ok(Some(_)) => { - continue; - } - Err(_) => return Ok(()), - _ => (), - } - pending!(); - } - - Ok(()) - }), - } - } - } - - struct ReturnOnStart; - - impl Subsystem for ReturnOnStart - where C: SubsystemContext - { - fn start(self, mut _ctx: C) -> SpawnedSubsystem { - SpawnedSubsystem { - name: "test-subsystem-4", - future: Box::pin(async move { - // Do nothing and exit. - Ok(()) - }), - } - } - } - - struct MockSupportsParachains; - - impl HeadSupportsParachains for MockSupportsParachains { - fn head_supports_parachains(&self, _head: &Hash) -> bool { - true - } - } - - // Checks that a minimal configuration of two jobs can run and exchange messages. - #[test] - fn overseer_works() { - let spawner = sp_core::testing::TaskExecutor::new(); - - executor::block_on(async move { - let (s1_tx, s1_rx) = metered::channel::(64); - let (s2_tx, s2_rx) = metered::channel::(64); - - let mut s1_rx = s1_rx.fuse(); - let mut s2_rx = s2_rx.fuse(); - - let all_subsystems = AllSubsystems::<()>::dummy() - .replace_candidate_validation(TestSubsystem1(s1_tx)) - .replace_candidate_backing(TestSubsystem2(s2_tx)); - - let (overseer, mut handler) = Overseer::new( - vec![], - all_subsystems, - None, - MockSupportsParachains, - spawner, - ).unwrap(); - let overseer_fut = overseer.run().fuse(); - - pin_mut!(overseer_fut); - - let mut s1_results = Vec::new(); - let mut s2_results = Vec::new(); - - loop { - select! { - _ = overseer_fut => break, - s1_next = s1_rx.next() => { - match s1_next { - Some(msg) => { - s1_results.push(msg); - if s1_results.len() == 10 { - handler.stop().await; - } - } - None => break, - } - }, - s2_next = s2_rx.next() => { - match s2_next { - Some(_) => s2_results.push(s2_next), - None => break, - } - }, - complete => break, - } - } - - assert_eq!(s1_results, (0..10).collect::>()); - }); - } - - // Checks activated/deactivated metrics are updated properly. - #[test] - fn overseer_metrics_work() { - let spawner = sp_core::testing::TaskExecutor::new(); - - executor::block_on(async move { - let first_block_hash = [1; 32].into(); - let second_block_hash = [2; 32].into(); - let third_block_hash = [3; 32].into(); - - let first_block = BlockInfo { - hash: first_block_hash, - parent_hash: [0; 32].into(), - number: 1, - }; - let second_block = BlockInfo { - hash: second_block_hash, - parent_hash: first_block_hash, - number: 2, - }; - let third_block = BlockInfo { - hash: third_block_hash, - parent_hash: second_block_hash, - number: 3, - }; - - let all_subsystems = AllSubsystems::<()>::dummy(); - let registry = prometheus::Registry::new(); - let (overseer, mut handler) = Overseer::new( - vec![first_block], - all_subsystems, - Some(®istry), - MockSupportsParachains, - spawner, - ).unwrap(); - let overseer_fut = overseer.run().fuse(); - - pin_mut!(overseer_fut); - - handler.block_imported(second_block).await; - handler.block_imported(third_block).await; - handler.send_msg(AllMessages::CandidateValidation(test_candidate_validation_msg())).await; - handler.stop().await; - - select! { - res = overseer_fut => { - assert!(res.is_ok()); - let metrics = extract_metrics(®istry); - assert_eq!(metrics["activated"], 3); - assert_eq!(metrics["deactivated"], 2); - assert_eq!(metrics["relayed"], 1); - }, - complete => (), - } - }); - } - - fn extract_metrics(registry: &prometheus::Registry) -> HashMap<&'static str, u64> { - let gather = registry.gather(); - assert_eq!(gather[0].get_name(), "parachain_activated_heads_total"); - assert_eq!(gather[1].get_name(), "parachain_deactivated_heads_total"); - assert_eq!(gather[2].get_name(), "parachain_messages_relayed_total"); - let activated = gather[0].get_metric()[0].get_counter().get_value() as u64; - let deactivated = gather[1].get_metric()[0].get_counter().get_value() as u64; - let relayed = gather[2].get_metric()[0].get_counter().get_value() as u64; - let mut result = HashMap::new(); - result.insert("activated", activated); - result.insert("deactivated", deactivated); - result.insert("relayed", relayed); - result - } - - // Spawn a subsystem that immediately exits. - // - // Should immediately conclude the overseer itself. - #[test] - fn overseer_ends_on_subsystem_exit() { - let spawner = sp_core::testing::TaskExecutor::new(); - - executor::block_on(async move { - let all_subsystems = AllSubsystems::<()>::dummy() - .replace_candidate_backing(ReturnOnStart); - let (overseer, _handle) = Overseer::new( - vec![], - all_subsystems, - None, - MockSupportsParachains, - spawner, - ).unwrap(); - - overseer.run().await.unwrap(); - }) - } - - struct TestSubsystem5(metered::MeteredSender); - - impl Subsystem for TestSubsystem5 - where C: SubsystemContext - { - fn start(self, mut ctx: C) -> SpawnedSubsystem { - let mut sender = self.0.clone(); - - SpawnedSubsystem { - name: "test-subsystem-5", - future: Box::pin(async move { - loop { - match ctx.try_recv().await { - Ok(Some(FromOverseer::Signal(OverseerSignal::Conclude))) => break, - Ok(Some(FromOverseer::Signal(s))) => { - sender.send(s).await.unwrap(); - continue; - }, - Ok(Some(_)) => continue, - Err(_) => break, - _ => (), - } - pending!(); - } - - Ok(()) - }), - } - } - } - - struct TestSubsystem6(metered::MeteredSender); - - impl Subsystem for TestSubsystem6 - where C: SubsystemContext - { - fn start(self, mut ctx: C) -> SpawnedSubsystem { - let mut sender = self.0.clone(); - - SpawnedSubsystem { - name: "test-subsystem-6", - future: Box::pin(async move { - loop { - match ctx.try_recv().await { - Ok(Some(FromOverseer::Signal(OverseerSignal::Conclude))) => break, - Ok(Some(FromOverseer::Signal(s))) => { - sender.send(s).await.unwrap(); - continue; - }, - Ok(Some(_)) => continue, - Err(_) => break, - _ => (), - } - pending!(); - } - - Ok(()) - }), - } - } - } - - // Tests that starting with a defined set of leaves and receiving - // notifications on imported blocks triggers expected `StartWork` and `StopWork` heartbeats. - #[test] - fn overseer_start_stop_works() { - let spawner = sp_core::testing::TaskExecutor::new(); - - executor::block_on(async move { - let first_block_hash = [1; 32].into(); - let second_block_hash = [2; 32].into(); - let third_block_hash = [3; 32].into(); - - let first_block = BlockInfo { - hash: first_block_hash, - parent_hash: [0; 32].into(), - number: 1, - }; - let second_block = BlockInfo { - hash: second_block_hash, - parent_hash: first_block_hash, - number: 2, - }; - let third_block = BlockInfo { - hash: third_block_hash, - parent_hash: second_block_hash, - number: 3, - }; - - let (tx_5, mut rx_5) = metered::channel(64); - let (tx_6, mut rx_6) = metered::channel(64); - let all_subsystems = AllSubsystems::<()>::dummy() - .replace_candidate_validation(TestSubsystem5(tx_5)) - .replace_candidate_backing(TestSubsystem6(tx_6)); - let (overseer, mut handler) = Overseer::new( - vec![first_block], - all_subsystems, - None, - MockSupportsParachains, - spawner, - ).unwrap(); - - let overseer_fut = overseer.run().fuse(); - pin_mut!(overseer_fut); - - let mut ss5_results = Vec::new(); - let mut ss6_results = Vec::new(); - - handler.block_imported(second_block).await; - handler.block_imported(third_block).await; - - let expected_heartbeats = vec![ - OverseerSignal::ActiveLeaves(ActiveLeavesUpdate::start_work(ActivatedLeaf { - hash: first_block_hash, - number: 1, - status: LeafStatus::Fresh, - span: Arc::new(jaeger::Span::Disabled), - })), - OverseerSignal::ActiveLeaves(ActiveLeavesUpdate { - activated: [ActivatedLeaf { - hash: second_block_hash, - number: 2, - status: LeafStatus::Fresh, - span: Arc::new(jaeger::Span::Disabled), - }].as_ref().into(), - deactivated: [first_block_hash].as_ref().into(), - }), - OverseerSignal::ActiveLeaves(ActiveLeavesUpdate { - activated: [ActivatedLeaf { - hash: third_block_hash, - number: 3, - status: LeafStatus::Fresh, - span: Arc::new(jaeger::Span::Disabled), - }].as_ref().into(), - deactivated: [second_block_hash].as_ref().into(), - }), - ]; - - loop { - select! { - res = overseer_fut => { - assert!(res.is_ok()); - break; - }, - res = rx_5.next() => { - if let Some(res) = res { - ss5_results.push(res); - } - } - res = rx_6.next() => { - if let Some(res) = res { - ss6_results.push(res); - } - } - complete => break, - } - - if ss5_results.len() == expected_heartbeats.len() && - ss6_results.len() == expected_heartbeats.len() { - handler.stop().await; - } - } - - assert_eq!(ss5_results, expected_heartbeats); - assert_eq!(ss6_results, expected_heartbeats); - }); - } - - // Tests that starting with a defined set of leaves and receiving - // notifications on imported blocks triggers expected `StartWork` and `StopWork` heartbeats. - #[test] - fn overseer_finalize_works() { - let spawner = sp_core::testing::TaskExecutor::new(); - - executor::block_on(async move { - let first_block_hash = [1; 32].into(); - let second_block_hash = [2; 32].into(); - let third_block_hash = [3; 32].into(); - - let first_block = BlockInfo { - hash: first_block_hash, - parent_hash: [0; 32].into(), - number: 1, - }; - let second_block = BlockInfo { - hash: second_block_hash, - parent_hash: [42; 32].into(), - number: 2, - }; - let third_block = BlockInfo { - hash: third_block_hash, - parent_hash: second_block_hash, - number: 3, - }; - - let (tx_5, mut rx_5) = metered::channel(64); - let (tx_6, mut rx_6) = metered::channel(64); - - let all_subsystems = AllSubsystems::<()>::dummy() - .replace_candidate_validation(TestSubsystem5(tx_5)) - .replace_candidate_backing(TestSubsystem6(tx_6)); - - // start with two forks of different height. - let (overseer, mut handler) = Overseer::new( - vec![first_block, second_block], - all_subsystems, - None, - MockSupportsParachains, - spawner, - ).unwrap(); - - let overseer_fut = overseer.run().fuse(); - pin_mut!(overseer_fut); - - let mut ss5_results = Vec::new(); - let mut ss6_results = Vec::new(); - - // this should stop work on both forks we started with earlier. - handler.block_finalized(third_block).await; - - let expected_heartbeats = vec![ - OverseerSignal::ActiveLeaves(ActiveLeavesUpdate { - activated: [ - ActivatedLeaf { - hash: first_block_hash, - number: 1, - status: LeafStatus::Fresh, - span: Arc::new(jaeger::Span::Disabled), - }, - ActivatedLeaf { - hash: second_block_hash, - number: 2, - status: LeafStatus::Fresh, - span: Arc::new(jaeger::Span::Disabled), - }, - ].as_ref().into(), - ..Default::default() - }), - OverseerSignal::ActiveLeaves(ActiveLeavesUpdate { - deactivated: [first_block_hash, second_block_hash].as_ref().into(), - ..Default::default() - }), - OverseerSignal::BlockFinalized(third_block_hash, 3), - ]; - - loop { - select! { - res = overseer_fut => { - assert!(res.is_ok()); - break; - }, - res = rx_5.next() => { - if let Some(res) = res { - ss5_results.push(res); - } - } - res = rx_6.next() => { - if let Some(res) = res { - ss6_results.push(res); - } - } - complete => break, - } - - if ss5_results.len() == expected_heartbeats.len() && ss6_results.len() == expected_heartbeats.len() { - handler.stop().await; - } - } - - assert_eq!(ss5_results.len(), expected_heartbeats.len()); - assert_eq!(ss6_results.len(), expected_heartbeats.len()); - - // Notifications on finality for multiple blocks at once - // may be received in different orders. - for expected in expected_heartbeats { - assert!(ss5_results.contains(&expected)); - assert!(ss6_results.contains(&expected)); - } - }); - } - - #[test] - fn do_not_send_empty_leaves_update_on_block_finalization() { - let spawner = sp_core::testing::TaskExecutor::new(); - - executor::block_on(async move { - let imported_block = BlockInfo { - hash: Hash::random(), - parent_hash: Hash::random(), - number: 1, - }; - - let finalized_block = BlockInfo { - hash: Hash::random(), - parent_hash: Hash::random(), - number: 1, - }; - - let (tx_5, mut rx_5) = metered::channel(64); - - let all_subsystems = AllSubsystems::<()>::dummy() - .replace_candidate_backing(TestSubsystem6(tx_5)); - - let (overseer, mut handler) = Overseer::new( - Vec::new(), - all_subsystems, - None, - MockSupportsParachains, - spawner, - ).unwrap(); - - let overseer_fut = overseer.run().fuse(); - pin_mut!(overseer_fut); - - let mut ss5_results = Vec::new(); - - handler.block_finalized(finalized_block.clone()).await; - handler.block_imported(imported_block.clone()).await; - - let expected_heartbeats = vec![ - OverseerSignal::ActiveLeaves(ActiveLeavesUpdate { - activated: [ - ActivatedLeaf { - hash: imported_block.hash, - number: imported_block.number, - status: LeafStatus::Fresh, - span: Arc::new(jaeger::Span::Disabled) - } - ].as_ref().into(), - ..Default::default() - }), - OverseerSignal::BlockFinalized(finalized_block.hash, 1), - ]; - - loop { - select! { - res = overseer_fut => { - assert!(res.is_ok()); - break; - }, - res = rx_5.next() => { - if let Some(res) = dbg!(res) { - ss5_results.push(res); - } - } - } - - if ss5_results.len() == expected_heartbeats.len() { - handler.stop().await; - } - } - - assert_eq!(ss5_results.len(), expected_heartbeats.len()); - - for expected in expected_heartbeats { - assert!(ss5_results.contains(&expected)); - } - }); - } - - // Tests that duplicate leaves have an attached 'Stale' status. - #[test] - fn overseer_stale_detection() { - let spawner = sp_core::testing::TaskExecutor::new(); - - executor::block_on(async move { - let a1_hash = [1; 32].into(); - let b1_hash = [2; 32].into(); - - let a2_hash = [3; 32].into(); - let b2_hash = [4; 32].into(); - - let first_block = BlockInfo { - hash: a1_hash, - parent_hash: [0; 32].into(), - number: 1, - }; - let second_block = BlockInfo { - hash: b1_hash, - parent_hash: [0; 32].into(), - number: 1, - }; - - let third_block = BlockInfo { - hash: a2_hash, - parent_hash: a1_hash, - number: 2, - }; - - let fourth_block = BlockInfo { - hash: b2_hash, - parent_hash: b1_hash, - number: 2, - }; - - let (tx_5, mut rx_5) = metered::channel(64); - let (tx_6, mut rx_6) = metered::channel(64); - let all_subsystems = AllSubsystems::<()>::dummy() - .replace_candidate_validation(TestSubsystem5(tx_5)) - .replace_candidate_backing(TestSubsystem6(tx_6)); - - let (overseer, mut handler) = Overseer::new( - vec![first_block.clone()], - all_subsystems, - None, - MockSupportsParachains, - spawner, - ).unwrap(); - - let overseer_fut = overseer.run().fuse(); - pin_mut!(overseer_fut); - - let mut ss5_results = Vec::new(); - let mut ss6_results = Vec::new(); - - handler.block_imported(second_block.clone()).await; - - // import the second block of each chain to deactivate the heads. - handler.block_imported(third_block).await; - handler.block_imported(fourth_block).await; - - // import the first blocks again (emulating a revert) - handler.block_imported(first_block).await; - handler.block_imported(second_block).await; - - let expected_heartbeats = vec![ - OverseerSignal::ActiveLeaves(ActiveLeavesUpdate::start_work(ActivatedLeaf { - hash: a1_hash, - number: 1, - status: LeafStatus::Fresh, - span: Arc::new(jaeger::Span::Disabled), - })), - OverseerSignal::ActiveLeaves(ActiveLeavesUpdate::start_work(ActivatedLeaf { - hash: b1_hash, - number: 1, - status: LeafStatus::Fresh, - span: Arc::new(jaeger::Span::Disabled), - })), - OverseerSignal::ActiveLeaves(ActiveLeavesUpdate { - activated: [ActivatedLeaf { - hash: a2_hash, - number: 2, - status: LeafStatus::Fresh, - span: Arc::new(jaeger::Span::Disabled), - }].as_ref().into(), - deactivated: [a1_hash].as_ref().into(), - }), - OverseerSignal::ActiveLeaves(ActiveLeavesUpdate { - activated: [ActivatedLeaf { - hash: b2_hash, - number: 2, - status: LeafStatus::Fresh, - span: Arc::new(jaeger::Span::Disabled), - }].as_ref().into(), - deactivated: [b1_hash].as_ref().into(), - }), - OverseerSignal::ActiveLeaves(ActiveLeavesUpdate::start_work(ActivatedLeaf { - hash: a1_hash, - number: 1, - status: LeafStatus::Stale, - span: Arc::new(jaeger::Span::Disabled), - })), - OverseerSignal::ActiveLeaves(ActiveLeavesUpdate::start_work(ActivatedLeaf { - hash: b1_hash, - number: 1, - status: LeafStatus::Stale, - span: Arc::new(jaeger::Span::Disabled), - })), - ]; - - loop { - select! { - res = overseer_fut => { - assert!(res.is_ok()); - break; - }, - res = rx_5.next() => { - if let Some(res) = res { - ss5_results.push(res); - } - } - res = rx_6.next() => { - if let Some(res) = res { - ss6_results.push(res); - } - } - complete => break, - } - - if ss5_results.len() == expected_heartbeats.len() && - ss6_results.len() == expected_heartbeats.len() { - handler.stop().await; - } - } - - assert_eq!(ss5_results, expected_heartbeats); - assert_eq!(ss6_results, expected_heartbeats); - }); - } - - #[derive(Clone)] - struct CounterSubsystem { - stop_signals_received: Arc, - signals_received: Arc, - msgs_received: Arc, - } - - impl CounterSubsystem { - fn new( - stop_signals_received: Arc, - signals_received: Arc, - msgs_received: Arc, - ) -> Self { - Self { - stop_signals_received, - signals_received, - msgs_received, - } - } - } - - impl Subsystem for CounterSubsystem - where - C: SubsystemContext, - M: Send, - { - fn start(self, mut ctx: C) -> SpawnedSubsystem { - SpawnedSubsystem { - name: "counter-subsystem", - future: Box::pin(async move { - loop { - match ctx.try_recv().await { - Ok(Some(FromOverseer::Signal(OverseerSignal::Conclude))) => { - self.stop_signals_received.fetch_add(1, atomic::Ordering::SeqCst); - break; - }, - Ok(Some(FromOverseer::Signal(_))) => { - self.signals_received.fetch_add(1, atomic::Ordering::SeqCst); - continue; - }, - Ok(Some(FromOverseer::Communication { .. })) => { - self.msgs_received.fetch_add(1, atomic::Ordering::SeqCst); - continue; - }, - Err(_) => (), - _ => (), - } - pending!(); - } - - Ok(()) - }), - } - } - } - - fn test_candidate_validation_msg() -> CandidateValidationMessage { - let (sender, _) = oneshot::channel(); - let pov = Arc::new(PoV { block_data: BlockData(Vec::new()) }); - CandidateValidationMessage::ValidateFromChainState(Default::default(), pov, sender) - } - - fn test_candidate_backing_msg() -> CandidateBackingMessage { - let (sender, _) = oneshot::channel(); - CandidateBackingMessage::GetBackedCandidates(Default::default(), Vec::new(), sender) - } - - fn test_chain_api_msg() -> ChainApiMessage { - let (sender, _) = oneshot::channel(); - ChainApiMessage::FinalizedBlockNumber(sender) - } - - fn test_collator_generation_msg() -> CollationGenerationMessage { - CollationGenerationMessage::Initialize(CollationGenerationConfig { - key: CollatorPair::generate().0, - collator: Box::new(|_, _| TestCollator.boxed()), - para_id: Default::default(), - }) - } - struct TestCollator; - - impl Future for TestCollator { - type Output = Option; - - fn poll(self: Pin<&mut Self>, _cx: &mut futures::task::Context) -> Poll { - panic!("at the Disco") - } - } - - impl Unpin for TestCollator {} - - fn test_collator_protocol_msg() -> CollatorProtocolMessage { - CollatorProtocolMessage::CollateOn(Default::default()) - } - - fn test_network_bridge_event() -> NetworkBridgeEvent { - NetworkBridgeEvent::PeerDisconnected(PeerId::random()) - } - - fn test_statement_distribution_msg() -> StatementDistributionMessage { - StatementDistributionMessage::NetworkBridgeUpdateV1(test_network_bridge_event()) - } - - fn test_availability_recovery_msg() -> AvailabilityRecoveryMessage { - let (sender, _) = oneshot::channel(); - AvailabilityRecoveryMessage::RecoverAvailableData( - Default::default(), - Default::default(), - None, - sender, - ) - } - - fn test_bitfield_distribution_msg() -> BitfieldDistributionMessage { - BitfieldDistributionMessage::NetworkBridgeUpdateV1(test_network_bridge_event()) - } - - fn test_provisioner_msg() -> ProvisionerMessage { - let (sender, _) = oneshot::channel(); - ProvisionerMessage::RequestInherentData(Default::default(), sender) - } - - fn test_runtime_api_msg() -> RuntimeApiMessage { - let (sender, _) = oneshot::channel(); - RuntimeApiMessage::Request(Default::default(), RuntimeApiRequest::Validators(sender)) - } - - fn test_availability_store_msg() -> AvailabilityStoreMessage { - let (sender, _) = oneshot::channel(); - AvailabilityStoreMessage::QueryAvailableData(CandidateHash(Default::default()), sender) - } - - fn test_network_bridge_msg() -> NetworkBridgeMessage { - NetworkBridgeMessage::ReportPeer(PeerId::random(), UnifiedReputationChange::BenefitMinor("")) - } - - fn test_approval_distribution_msg() -> ApprovalDistributionMessage { - ApprovalDistributionMessage::NewBlocks(Default::default()) - } - - fn test_approval_voting_msg() -> ApprovalVotingMessage { - let (sender, _) = oneshot::channel(); - ApprovalVotingMessage::ApprovedAncestor(Default::default(), 0, sender) - } - - // Checks that `stop`, `broadcast_signal` and `broadcast_message` are implemented correctly. - #[test] - fn overseer_all_subsystems_receive_signals_and_messages() { - const NUM_SUBSYSTEMS: usize = 17; - // -3 for BitfieldSigning, GossipSupport and AvailabilityDistribution - const NUM_SUBSYSTEMS_MESSAGED: usize = NUM_SUBSYSTEMS - 3; - - let spawner = sp_core::testing::TaskExecutor::new(); - executor::block_on(async move { - let stop_signals_received = Arc::new(atomic::AtomicUsize::new(0)); - let signals_received = Arc::new(atomic::AtomicUsize::new(0)); - let msgs_received = Arc::new(atomic::AtomicUsize::new(0)); - - let subsystem = CounterSubsystem::new( - stop_signals_received.clone(), - signals_received.clone(), - msgs_received.clone(), - ); - - let all_subsystems = AllSubsystems { - candidate_validation: subsystem.clone(), - candidate_backing: subsystem.clone(), - collation_generation: subsystem.clone(), - collator_protocol: subsystem.clone(), - statement_distribution: subsystem.clone(), - availability_distribution: subsystem.clone(), - availability_recovery: subsystem.clone(), - bitfield_signing: subsystem.clone(), - bitfield_distribution: subsystem.clone(), - provisioner: subsystem.clone(), - runtime_api: subsystem.clone(), - availability_store: subsystem.clone(), - network_bridge: subsystem.clone(), - chain_api: subsystem.clone(), - approval_distribution: subsystem.clone(), - approval_voting: subsystem.clone(), - gossip_support: subsystem.clone(), - }; - let (overseer, mut handler) = Overseer::new( - vec![], - all_subsystems, - None, - MockSupportsParachains, - spawner, - ).unwrap(); - let overseer_fut = overseer.run().fuse(); - - pin_mut!(overseer_fut); - - // send a signal to each subsystem - handler.block_imported(BlockInfo { - hash: Default::default(), - parent_hash: Default::default(), - number: Default::default(), - }).await; - - // send a msg to each subsystem - // except for BitfieldSigning and GossipSupport as the messages are not instantiable - handler.send_msg(AllMessages::CandidateValidation(test_candidate_validation_msg())).await; - handler.send_msg(AllMessages::CandidateBacking(test_candidate_backing_msg())).await; - handler.send_msg(AllMessages::CollationGeneration(test_collator_generation_msg())).await; - handler.send_msg(AllMessages::CollatorProtocol(test_collator_protocol_msg())).await; - handler.send_msg(AllMessages::StatementDistribution(test_statement_distribution_msg())).await; - handler.send_msg(AllMessages::AvailabilityRecovery(test_availability_recovery_msg())).await; - // handler.send_msg(AllMessages::BitfieldSigning(test_bitfield_signing_msg())).await; - // handler.send_msg(AllMessages::GossipSupport(test_bitfield_signing_msg())).await; - handler.send_msg(AllMessages::BitfieldDistribution(test_bitfield_distribution_msg())).await; - handler.send_msg(AllMessages::Provisioner(test_provisioner_msg())).await; - handler.send_msg(AllMessages::RuntimeApi(test_runtime_api_msg())).await; - handler.send_msg(AllMessages::AvailabilityStore(test_availability_store_msg())).await; - handler.send_msg(AllMessages::NetworkBridge(test_network_bridge_msg())).await; - handler.send_msg(AllMessages::ChainApi(test_chain_api_msg())).await; - handler.send_msg(AllMessages::ApprovalDistribution(test_approval_distribution_msg())).await; - handler.send_msg(AllMessages::ApprovalVoting(test_approval_voting_msg())).await; - - // Wait until all subsystems have received. Otherwise the messages might race against - // the conclude signal. - loop { - match (&mut overseer_fut).timeout(Duration::from_millis(100)).await { - None => { - let r = msgs_received.load(atomic::Ordering::SeqCst); - if r < NUM_SUBSYSTEMS_MESSAGED { - Delay::new(Duration::from_millis(100)).await; - } else if r > NUM_SUBSYSTEMS_MESSAGED { - panic!("too many messages received??"); - } else { - break - } - } - Some(_) => panic!("exited too early"), - } - } - - // send a stop signal to each subsystems - handler.stop().await; - - let res = overseer_fut.await; - assert_eq!(stop_signals_received.load(atomic::Ordering::SeqCst), NUM_SUBSYSTEMS); - assert_eq!(signals_received.load(atomic::Ordering::SeqCst), NUM_SUBSYSTEMS); - assert_eq!(msgs_received.load(atomic::Ordering::SeqCst), NUM_SUBSYSTEMS_MESSAGED); - - assert!(res.is_ok()); - }); - } - - #[test] - fn context_holds_onto_message_until_enough_signals_received() { - let (candidate_validation_bounded_tx, _) = metered::channel(CHANNEL_CAPACITY); - let (candidate_backing_bounded_tx, _) = metered::channel(CHANNEL_CAPACITY); - let (statement_distribution_bounded_tx, _) = metered::channel(CHANNEL_CAPACITY); - let (availability_distribution_bounded_tx, _) = metered::channel(CHANNEL_CAPACITY); - let (availability_recovery_bounded_tx, _) = metered::channel(CHANNEL_CAPACITY); - let (bitfield_signing_bounded_tx, _) = metered::channel(CHANNEL_CAPACITY); - let (bitfield_distribution_bounded_tx, _) = metered::channel(CHANNEL_CAPACITY); - let (provisioner_bounded_tx, _) = metered::channel(CHANNEL_CAPACITY); - let (runtime_api_bounded_tx, _) = metered::channel(CHANNEL_CAPACITY); - let (availability_store_bounded_tx, _) = metered::channel(CHANNEL_CAPACITY); - let (network_bridge_bounded_tx, _) = metered::channel(CHANNEL_CAPACITY); - let (chain_api_bounded_tx, _) = metered::channel(CHANNEL_CAPACITY); - let (collator_protocol_bounded_tx, _) = metered::channel(CHANNEL_CAPACITY); - let (collation_generation_bounded_tx, _) = metered::channel(CHANNEL_CAPACITY); - let (approval_distribution_bounded_tx, _) = metered::channel(CHANNEL_CAPACITY); - let (approval_voting_bounded_tx, _) = metered::channel(CHANNEL_CAPACITY); - let (gossip_support_bounded_tx, _) = metered::channel(CHANNEL_CAPACITY); - - let (candidate_validation_unbounded_tx, _) = metered::unbounded(); - let (candidate_backing_unbounded_tx, _) = metered::unbounded(); - let (statement_distribution_unbounded_tx, _) = metered::unbounded(); - let (availability_distribution_unbounded_tx, _) = metered::unbounded(); - let (availability_recovery_unbounded_tx, _) = metered::unbounded(); - let (bitfield_signing_unbounded_tx, _) = metered::unbounded(); - let (bitfield_distribution_unbounded_tx, _) = metered::unbounded(); - let (provisioner_unbounded_tx, _) = metered::unbounded(); - let (runtime_api_unbounded_tx, _) = metered::unbounded(); - let (availability_store_unbounded_tx, _) = metered::unbounded(); - let (network_bridge_unbounded_tx, _) = metered::unbounded(); - let (chain_api_unbounded_tx, _) = metered::unbounded(); - let (collator_protocol_unbounded_tx, _) = metered::unbounded(); - let (collation_generation_unbounded_tx, _) = metered::unbounded(); - let (approval_distribution_unbounded_tx, _) = metered::unbounded(); - let (approval_voting_unbounded_tx, _) = metered::unbounded(); - let (gossip_support_unbounded_tx, _) = metered::unbounded(); - - let channels_out = ChannelsOut { - candidate_validation: candidate_validation_bounded_tx.clone(), - candidate_backing: candidate_backing_bounded_tx.clone(), - statement_distribution: statement_distribution_bounded_tx.clone(), - availability_distribution: availability_distribution_bounded_tx.clone(), - availability_recovery: availability_recovery_bounded_tx.clone(), - bitfield_signing: bitfield_signing_bounded_tx.clone(), - bitfield_distribution: bitfield_distribution_bounded_tx.clone(), - provisioner: provisioner_bounded_tx.clone(), - runtime_api: runtime_api_bounded_tx.clone(), - availability_store: availability_store_bounded_tx.clone(), - network_bridge: network_bridge_bounded_tx.clone(), - chain_api: chain_api_bounded_tx.clone(), - collator_protocol: collator_protocol_bounded_tx.clone(), - collation_generation: collation_generation_bounded_tx.clone(), - approval_distribution: approval_distribution_bounded_tx.clone(), - approval_voting: approval_voting_bounded_tx.clone(), - gossip_support: gossip_support_bounded_tx.clone(), - - candidate_validation_unbounded: candidate_validation_unbounded_tx.clone(), - candidate_backing_unbounded: candidate_backing_unbounded_tx.clone(), - statement_distribution_unbounded: statement_distribution_unbounded_tx.clone(), - availability_distribution_unbounded: availability_distribution_unbounded_tx.clone(), - availability_recovery_unbounded: availability_recovery_unbounded_tx.clone(), - bitfield_signing_unbounded: bitfield_signing_unbounded_tx.clone(), - bitfield_distribution_unbounded: bitfield_distribution_unbounded_tx.clone(), - provisioner_unbounded: provisioner_unbounded_tx.clone(), - runtime_api_unbounded: runtime_api_unbounded_tx.clone(), - availability_store_unbounded: availability_store_unbounded_tx.clone(), - network_bridge_unbounded: network_bridge_unbounded_tx.clone(), - chain_api_unbounded: chain_api_unbounded_tx.clone(), - collator_protocol_unbounded: collator_protocol_unbounded_tx.clone(), - collation_generation_unbounded: collation_generation_unbounded_tx.clone(), - approval_distribution_unbounded: approval_distribution_unbounded_tx.clone(), - approval_voting_unbounded: approval_voting_unbounded_tx.clone(), - gossip_support_unbounded: gossip_support_unbounded_tx.clone(), - }; - - let (mut signal_tx, signal_rx) = metered::channel(CHANNEL_CAPACITY); - let (mut bounded_tx, bounded_rx) = metered::channel(CHANNEL_CAPACITY); - let (unbounded_tx, unbounded_rx) = metered::unbounded(); - let (to_overseer_tx, _to_overseer_rx) = metered::unbounded(); - - let mut ctx = OverseerSubsystemContext::<()>::new_unmetered( - signal_rx, - stream::select(bounded_rx, unbounded_rx), - channels_out, - to_overseer_tx, - ); - - assert_eq!(ctx.signals_received.load(), 0); - - let test_fut = async move { - signal_tx.send(OverseerSignal::Conclude).await.unwrap(); - assert_matches!(ctx.recv().await.unwrap(), FromOverseer::Signal(OverseerSignal::Conclude)); - - assert_eq!(ctx.signals_received.load(), 1); - bounded_tx.send(MessagePacket { - signals_received: 2, - message: (), - }).await.unwrap(); - unbounded_tx.unbounded_send(MessagePacket { - signals_received: 2, - message: (), - }).unwrap(); - - match poll!(ctx.recv()) { - Poll::Pending => {} - Poll::Ready(_) => panic!("ready too early"), - }; - - assert!(ctx.pending_incoming.is_some()); - - signal_tx.send(OverseerSignal::Conclude).await.unwrap(); - assert_matches!(ctx.recv().await.unwrap(), FromOverseer::Signal(OverseerSignal::Conclude)); - assert_matches!(ctx.recv().await.unwrap(), FromOverseer::Communication { msg: () }); - assert_matches!(ctx.recv().await.unwrap(), FromOverseer::Communication { msg: () }); - assert!(ctx.pending_incoming.is_none()); - }; - - futures::executor::block_on(test_fut); - } -} +mod tests; diff --git a/polkadot/node/overseer/src/tests.rs b/polkadot/node/overseer/src/tests.rs new file mode 100644 index 0000000000..6e5383b21c --- /dev/null +++ b/polkadot/node/overseer/src/tests.rs @@ -0,0 +1,1153 @@ +// Copyright 2020 Parity Technologies (UK) Ltd. +// This file is part of Polkadot. + +// Polkadot is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Polkadot is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Polkadot. If not, see . + +use std::sync::atomic; +use std::collections::HashMap; +use futures::{executor, pin_mut, select, FutureExt, pending}; + +use polkadot_primitives::v1::{CollatorPair, CandidateHash}; +use polkadot_subsystem::{messages::RuntimeApiRequest, messages::NetworkBridgeEvent, jaeger}; +use polkadot_node_primitives::{CollationResult, CollationGenerationConfig, PoV, BlockData}; +use polkadot_node_network_protocol::{PeerId, UnifiedReputationChange}; +use polkadot_node_subsystem_util::metered; + +use sp_core::crypto::Pair as _; +use assert_matches::assert_matches; + +use super::*; + +struct TestSubsystem1(metered::MeteredSender); + +impl Subsystem for TestSubsystem1 + where C: SubsystemContext +{ + fn start(self, mut ctx: C) -> SpawnedSubsystem { + let mut sender = self.0; + SpawnedSubsystem { + name: "test-subsystem-1", + future: Box::pin(async move { + let mut i = 0; + loop { + match ctx.recv().await { + Ok(FromOverseer::Communication { .. }) => { + let _ = sender.send(i).await; + i += 1; + continue; + } + Ok(FromOverseer::Signal(OverseerSignal::Conclude)) => return Ok(()), + Err(_) => return Ok(()), + _ => (), + } + } + }), + } + } +} + +struct TestSubsystem2(metered::MeteredSender); + +impl Subsystem for TestSubsystem2 + where C: SubsystemContext +{ + fn start(self, mut ctx: C) -> SpawnedSubsystem { + let sender = self.0.clone(); + SpawnedSubsystem { + name: "test-subsystem-2", + future: Box::pin(async move { + let _sender = sender; + let mut c: usize = 0; + loop { + if c < 10 { + let (tx, _) = oneshot::channel(); + ctx.send_message( + AllMessages::CandidateValidation( + CandidateValidationMessage::ValidateFromChainState( + Default::default(), + PoV { + block_data: BlockData(Vec::new()), + }.into(), + tx, + ) + ) + ).await; + c += 1; + continue; + } + match ctx.try_recv().await { + Ok(Some(FromOverseer::Signal(OverseerSignal::Conclude))) => { + break; + } + Ok(Some(_)) => { + continue; + } + Err(_) => return Ok(()), + _ => (), + } + pending!(); + } + + Ok(()) + }), + } + } +} + +struct ReturnOnStart; + +impl Subsystem for ReturnOnStart + where C: SubsystemContext +{ + fn start(self, mut _ctx: C) -> SpawnedSubsystem { + SpawnedSubsystem { + name: "test-subsystem-4", + future: Box::pin(async move { + // Do nothing and exit. + Ok(()) + }), + } + } +} + +struct MockSupportsParachains; + +impl HeadSupportsParachains for MockSupportsParachains { + fn head_supports_parachains(&self, _head: &Hash) -> bool { + true + } +} + +// Checks that a minimal configuration of two jobs can run and exchange messages. +#[test] +fn overseer_works() { + let spawner = sp_core::testing::TaskExecutor::new(); + + executor::block_on(async move { + let (s1_tx, s1_rx) = metered::channel::(64); + let (s2_tx, s2_rx) = metered::channel::(64); + + let mut s1_rx = s1_rx.fuse(); + let mut s2_rx = s2_rx.fuse(); + + let all_subsystems = AllSubsystems::<()>::dummy() + .replace_candidate_validation(TestSubsystem1(s1_tx)) + .replace_candidate_backing(TestSubsystem2(s2_tx)); + + let (overseer, mut handler) = Overseer::new( + vec![], + all_subsystems, + None, + MockSupportsParachains, + spawner, + ).unwrap(); + let overseer_fut = overseer.run().fuse(); + + pin_mut!(overseer_fut); + + let mut s1_results = Vec::new(); + let mut s2_results = Vec::new(); + + loop { + select! { + _ = overseer_fut => break, + s1_next = s1_rx.next() => { + match s1_next { + Some(msg) => { + s1_results.push(msg); + if s1_results.len() == 10 { + handler.stop().await; + } + } + None => break, + } + }, + s2_next = s2_rx.next() => { + match s2_next { + Some(_) => s2_results.push(s2_next), + None => break, + } + }, + complete => break, + } + } + + assert_eq!(s1_results, (0..10).collect::>()); + }); +} + +// Checks activated/deactivated metrics are updated properly. +#[test] +fn overseer_metrics_work() { + let spawner = sp_core::testing::TaskExecutor::new(); + + executor::block_on(async move { + let first_block_hash = [1; 32].into(); + let second_block_hash = [2; 32].into(); + let third_block_hash = [3; 32].into(); + + let first_block = BlockInfo { + hash: first_block_hash, + parent_hash: [0; 32].into(), + number: 1, + }; + let second_block = BlockInfo { + hash: second_block_hash, + parent_hash: first_block_hash, + number: 2, + }; + let third_block = BlockInfo { + hash: third_block_hash, + parent_hash: second_block_hash, + number: 3, + }; + + let all_subsystems = AllSubsystems::<()>::dummy(); + let registry = prometheus::Registry::new(); + let (overseer, mut handler) = Overseer::new( + vec![first_block], + all_subsystems, + Some(®istry), + MockSupportsParachains, + spawner, + ).unwrap(); + let overseer_fut = overseer.run().fuse(); + + pin_mut!(overseer_fut); + + handler.block_imported(second_block).await; + handler.block_imported(third_block).await; + handler.send_msg(AllMessages::CandidateValidation(test_candidate_validation_msg())).await; + handler.stop().await; + + select! { + res = overseer_fut => { + assert!(res.is_ok()); + let metrics = extract_metrics(®istry); + assert_eq!(metrics["activated"], 3); + assert_eq!(metrics["deactivated"], 2); + assert_eq!(metrics["relayed"], 1); + }, + complete => (), + } + }); +} + +fn extract_metrics(registry: &prometheus::Registry) -> HashMap<&'static str, u64> { + let gather = registry.gather(); + assert_eq!(gather[0].get_name(), "parachain_activated_heads_total"); + assert_eq!(gather[1].get_name(), "parachain_deactivated_heads_total"); + assert_eq!(gather[2].get_name(), "parachain_messages_relayed_total"); + let activated = gather[0].get_metric()[0].get_counter().get_value() as u64; + let deactivated = gather[1].get_metric()[0].get_counter().get_value() as u64; + let relayed = gather[2].get_metric()[0].get_counter().get_value() as u64; + let mut result = HashMap::new(); + result.insert("activated", activated); + result.insert("deactivated", deactivated); + result.insert("relayed", relayed); + result +} + +// Spawn a subsystem that immediately exits. +// +// Should immediately conclude the overseer itself. +#[test] +fn overseer_ends_on_subsystem_exit() { + let spawner = sp_core::testing::TaskExecutor::new(); + + executor::block_on(async move { + let all_subsystems = AllSubsystems::<()>::dummy() + .replace_candidate_backing(ReturnOnStart); + let (overseer, _handle) = Overseer::new( + vec![], + all_subsystems, + None, + MockSupportsParachains, + spawner, + ).unwrap(); + + overseer.run().await.unwrap(); + }) +} + +struct TestSubsystem5(metered::MeteredSender); + +impl Subsystem for TestSubsystem5 + where C: SubsystemContext +{ + fn start(self, mut ctx: C) -> SpawnedSubsystem { + let mut sender = self.0.clone(); + + SpawnedSubsystem { + name: "test-subsystem-5", + future: Box::pin(async move { + loop { + match ctx.try_recv().await { + Ok(Some(FromOverseer::Signal(OverseerSignal::Conclude))) => break, + Ok(Some(FromOverseer::Signal(s))) => { + sender.send(s).await.unwrap(); + continue; + }, + Ok(Some(_)) => continue, + Err(_) => break, + _ => (), + } + pending!(); + } + + Ok(()) + }), + } + } +} + +struct TestSubsystem6(metered::MeteredSender); + +impl Subsystem for TestSubsystem6 + where C: SubsystemContext +{ + fn start(self, mut ctx: C) -> SpawnedSubsystem { + let mut sender = self.0.clone(); + + SpawnedSubsystem { + name: "test-subsystem-6", + future: Box::pin(async move { + loop { + match ctx.try_recv().await { + Ok(Some(FromOverseer::Signal(OverseerSignal::Conclude))) => break, + Ok(Some(FromOverseer::Signal(s))) => { + sender.send(s).await.unwrap(); + continue; + }, + Ok(Some(_)) => continue, + Err(_) => break, + _ => (), + } + pending!(); + } + + Ok(()) + }), + } + } +} + +// Tests that starting with a defined set of leaves and receiving +// notifications on imported blocks triggers expected `StartWork` and `StopWork` heartbeats. +#[test] +fn overseer_start_stop_works() { + let spawner = sp_core::testing::TaskExecutor::new(); + + executor::block_on(async move { + let first_block_hash = [1; 32].into(); + let second_block_hash = [2; 32].into(); + let third_block_hash = [3; 32].into(); + + let first_block = BlockInfo { + hash: first_block_hash, + parent_hash: [0; 32].into(), + number: 1, + }; + let second_block = BlockInfo { + hash: second_block_hash, + parent_hash: first_block_hash, + number: 2, + }; + let third_block = BlockInfo { + hash: third_block_hash, + parent_hash: second_block_hash, + number: 3, + }; + + let (tx_5, mut rx_5) = metered::channel(64); + let (tx_6, mut rx_6) = metered::channel(64); + let all_subsystems = AllSubsystems::<()>::dummy() + .replace_candidate_validation(TestSubsystem5(tx_5)) + .replace_candidate_backing(TestSubsystem6(tx_6)); + let (overseer, mut handler) = Overseer::new( + vec![first_block], + all_subsystems, + None, + MockSupportsParachains, + spawner, + ).unwrap(); + + let overseer_fut = overseer.run().fuse(); + pin_mut!(overseer_fut); + + let mut ss5_results = Vec::new(); + let mut ss6_results = Vec::new(); + + handler.block_imported(second_block).await; + handler.block_imported(third_block).await; + + let expected_heartbeats = vec![ + OverseerSignal::ActiveLeaves(ActiveLeavesUpdate::start_work(ActivatedLeaf { + hash: first_block_hash, + number: 1, + status: LeafStatus::Fresh, + span: Arc::new(jaeger::Span::Disabled), + })), + OverseerSignal::ActiveLeaves(ActiveLeavesUpdate { + activated: [ActivatedLeaf { + hash: second_block_hash, + number: 2, + status: LeafStatus::Fresh, + span: Arc::new(jaeger::Span::Disabled), + }].as_ref().into(), + deactivated: [first_block_hash].as_ref().into(), + }), + OverseerSignal::ActiveLeaves(ActiveLeavesUpdate { + activated: [ActivatedLeaf { + hash: third_block_hash, + number: 3, + status: LeafStatus::Fresh, + span: Arc::new(jaeger::Span::Disabled), + }].as_ref().into(), + deactivated: [second_block_hash].as_ref().into(), + }), + ]; + + loop { + select! { + res = overseer_fut => { + assert!(res.is_ok()); + break; + }, + res = rx_5.next() => { + if let Some(res) = res { + ss5_results.push(res); + } + } + res = rx_6.next() => { + if let Some(res) = res { + ss6_results.push(res); + } + } + complete => break, + } + + if ss5_results.len() == expected_heartbeats.len() && + ss6_results.len() == expected_heartbeats.len() { + handler.stop().await; + } + } + + assert_eq!(ss5_results, expected_heartbeats); + assert_eq!(ss6_results, expected_heartbeats); + }); +} + +// Tests that starting with a defined set of leaves and receiving +// notifications on imported blocks triggers expected `StartWork` and `StopWork` heartbeats. +#[test] +fn overseer_finalize_works() { + let spawner = sp_core::testing::TaskExecutor::new(); + + executor::block_on(async move { + let first_block_hash = [1; 32].into(); + let second_block_hash = [2; 32].into(); + let third_block_hash = [3; 32].into(); + + let first_block = BlockInfo { + hash: first_block_hash, + parent_hash: [0; 32].into(), + number: 1, + }; + let second_block = BlockInfo { + hash: second_block_hash, + parent_hash: [42; 32].into(), + number: 2, + }; + let third_block = BlockInfo { + hash: third_block_hash, + parent_hash: second_block_hash, + number: 3, + }; + + let (tx_5, mut rx_5) = metered::channel(64); + let (tx_6, mut rx_6) = metered::channel(64); + + let all_subsystems = AllSubsystems::<()>::dummy() + .replace_candidate_validation(TestSubsystem5(tx_5)) + .replace_candidate_backing(TestSubsystem6(tx_6)); + + // start with two forks of different height. + let (overseer, mut handler) = Overseer::new( + vec![first_block, second_block], + all_subsystems, + None, + MockSupportsParachains, + spawner, + ).unwrap(); + + let overseer_fut = overseer.run().fuse(); + pin_mut!(overseer_fut); + + let mut ss5_results = Vec::new(); + let mut ss6_results = Vec::new(); + + // this should stop work on both forks we started with earlier. + handler.block_finalized(third_block).await; + + let expected_heartbeats = vec![ + OverseerSignal::ActiveLeaves(ActiveLeavesUpdate { + activated: [ + ActivatedLeaf { + hash: first_block_hash, + number: 1, + status: LeafStatus::Fresh, + span: Arc::new(jaeger::Span::Disabled), + }, + ActivatedLeaf { + hash: second_block_hash, + number: 2, + status: LeafStatus::Fresh, + span: Arc::new(jaeger::Span::Disabled), + }, + ].as_ref().into(), + ..Default::default() + }), + OverseerSignal::ActiveLeaves(ActiveLeavesUpdate { + deactivated: [first_block_hash, second_block_hash].as_ref().into(), + ..Default::default() + }), + OverseerSignal::BlockFinalized(third_block_hash, 3), + ]; + + loop { + select! { + res = overseer_fut => { + assert!(res.is_ok()); + break; + }, + res = rx_5.next() => { + if let Some(res) = res { + ss5_results.push(res); + } + } + res = rx_6.next() => { + if let Some(res) = res { + ss6_results.push(res); + } + } + complete => break, + } + + if ss5_results.len() == expected_heartbeats.len() && ss6_results.len() == expected_heartbeats.len() { + handler.stop().await; + } + } + + assert_eq!(ss5_results.len(), expected_heartbeats.len()); + assert_eq!(ss6_results.len(), expected_heartbeats.len()); + + // Notifications on finality for multiple blocks at once + // may be received in different orders. + for expected in expected_heartbeats { + assert!(ss5_results.contains(&expected)); + assert!(ss6_results.contains(&expected)); + } + }); +} + +#[test] +fn do_not_send_empty_leaves_update_on_block_finalization() { + let spawner = sp_core::testing::TaskExecutor::new(); + + executor::block_on(async move { + let imported_block = BlockInfo { + hash: Hash::random(), + parent_hash: Hash::random(), + number: 1, + }; + + let finalized_block = BlockInfo { + hash: Hash::random(), + parent_hash: Hash::random(), + number: 1, + }; + + let (tx_5, mut rx_5) = metered::channel(64); + + let all_subsystems = AllSubsystems::<()>::dummy() + .replace_candidate_backing(TestSubsystem6(tx_5)); + + let (overseer, mut handler) = Overseer::new( + Vec::new(), + all_subsystems, + None, + MockSupportsParachains, + spawner, + ).unwrap(); + + let overseer_fut = overseer.run().fuse(); + pin_mut!(overseer_fut); + + let mut ss5_results = Vec::new(); + + handler.block_finalized(finalized_block.clone()).await; + handler.block_imported(imported_block.clone()).await; + + let expected_heartbeats = vec![ + OverseerSignal::ActiveLeaves(ActiveLeavesUpdate { + activated: [ + ActivatedLeaf { + hash: imported_block.hash, + number: imported_block.number, + status: LeafStatus::Fresh, + span: Arc::new(jaeger::Span::Disabled) + } + ].as_ref().into(), + ..Default::default() + }), + OverseerSignal::BlockFinalized(finalized_block.hash, 1), + ]; + + loop { + select! { + res = overseer_fut => { + assert!(res.is_ok()); + break; + }, + res = rx_5.next() => { + if let Some(res) = dbg!(res) { + ss5_results.push(res); + } + } + } + + if ss5_results.len() == expected_heartbeats.len() { + handler.stop().await; + } + } + + assert_eq!(ss5_results.len(), expected_heartbeats.len()); + + for expected in expected_heartbeats { + assert!(ss5_results.contains(&expected)); + } + }); +} + +// Tests that duplicate leaves have an attached 'Stale' status. +#[test] +fn overseer_stale_detection() { + let spawner = sp_core::testing::TaskExecutor::new(); + + executor::block_on(async move { + let a1_hash = [1; 32].into(); + let b1_hash = [2; 32].into(); + + let a2_hash = [3; 32].into(); + let b2_hash = [4; 32].into(); + + let first_block = BlockInfo { + hash: a1_hash, + parent_hash: [0; 32].into(), + number: 1, + }; + let second_block = BlockInfo { + hash: b1_hash, + parent_hash: [0; 32].into(), + number: 1, + }; + + let third_block = BlockInfo { + hash: a2_hash, + parent_hash: a1_hash, + number: 2, + }; + + let fourth_block = BlockInfo { + hash: b2_hash, + parent_hash: b1_hash, + number: 2, + }; + + let (tx_5, mut rx_5) = metered::channel(64); + let (tx_6, mut rx_6) = metered::channel(64); + let all_subsystems = AllSubsystems::<()>::dummy() + .replace_candidate_validation(TestSubsystem5(tx_5)) + .replace_candidate_backing(TestSubsystem6(tx_6)); + + let (overseer, mut handler) = Overseer::new( + vec![first_block.clone()], + all_subsystems, + None, + MockSupportsParachains, + spawner, + ).unwrap(); + + let overseer_fut = overseer.run().fuse(); + pin_mut!(overseer_fut); + + let mut ss5_results = Vec::new(); + let mut ss6_results = Vec::new(); + + handler.block_imported(second_block.clone()).await; + + // import the second block of each chain to deactivate the heads. + handler.block_imported(third_block).await; + handler.block_imported(fourth_block).await; + + // import the first blocks again (emulating a revert) + handler.block_imported(first_block).await; + handler.block_imported(second_block).await; + + let expected_heartbeats = vec![ + OverseerSignal::ActiveLeaves(ActiveLeavesUpdate::start_work(ActivatedLeaf { + hash: a1_hash, + number: 1, + status: LeafStatus::Fresh, + span: Arc::new(jaeger::Span::Disabled), + })), + OverseerSignal::ActiveLeaves(ActiveLeavesUpdate::start_work(ActivatedLeaf { + hash: b1_hash, + number: 1, + status: LeafStatus::Fresh, + span: Arc::new(jaeger::Span::Disabled), + })), + OverseerSignal::ActiveLeaves(ActiveLeavesUpdate { + activated: [ActivatedLeaf { + hash: a2_hash, + number: 2, + status: LeafStatus::Fresh, + span: Arc::new(jaeger::Span::Disabled), + }].as_ref().into(), + deactivated: [a1_hash].as_ref().into(), + }), + OverseerSignal::ActiveLeaves(ActiveLeavesUpdate { + activated: [ActivatedLeaf { + hash: b2_hash, + number: 2, + status: LeafStatus::Fresh, + span: Arc::new(jaeger::Span::Disabled), + }].as_ref().into(), + deactivated: [b1_hash].as_ref().into(), + }), + OverseerSignal::ActiveLeaves(ActiveLeavesUpdate::start_work(ActivatedLeaf { + hash: a1_hash, + number: 1, + status: LeafStatus::Stale, + span: Arc::new(jaeger::Span::Disabled), + })), + OverseerSignal::ActiveLeaves(ActiveLeavesUpdate::start_work(ActivatedLeaf { + hash: b1_hash, + number: 1, + status: LeafStatus::Stale, + span: Arc::new(jaeger::Span::Disabled), + })), + ]; + + loop { + select! { + res = overseer_fut => { + assert!(res.is_ok()); + break; + }, + res = rx_5.next() => { + if let Some(res) = res { + ss5_results.push(res); + } + } + res = rx_6.next() => { + if let Some(res) = res { + ss6_results.push(res); + } + } + complete => break, + } + + if ss5_results.len() == expected_heartbeats.len() && + ss6_results.len() == expected_heartbeats.len() { + handler.stop().await; + } + } + + assert_eq!(ss5_results, expected_heartbeats); + assert_eq!(ss6_results, expected_heartbeats); + }); +} + +#[derive(Clone)] +struct CounterSubsystem { + stop_signals_received: Arc, + signals_received: Arc, + msgs_received: Arc, +} + +impl CounterSubsystem { + fn new( + stop_signals_received: Arc, + signals_received: Arc, + msgs_received: Arc, + ) -> Self { + Self { + stop_signals_received, + signals_received, + msgs_received, + } + } +} + +impl Subsystem for CounterSubsystem + where + C: SubsystemContext, + M: Send, +{ + fn start(self, mut ctx: C) -> SpawnedSubsystem { + SpawnedSubsystem { + name: "counter-subsystem", + future: Box::pin(async move { + loop { + match ctx.try_recv().await { + Ok(Some(FromOverseer::Signal(OverseerSignal::Conclude))) => { + self.stop_signals_received.fetch_add(1, atomic::Ordering::SeqCst); + break; + }, + Ok(Some(FromOverseer::Signal(_))) => { + self.signals_received.fetch_add(1, atomic::Ordering::SeqCst); + continue; + }, + Ok(Some(FromOverseer::Communication { .. })) => { + self.msgs_received.fetch_add(1, atomic::Ordering::SeqCst); + continue; + }, + Err(_) => (), + _ => (), + } + pending!(); + } + + Ok(()) + }), + } + } +} + +fn test_candidate_validation_msg() -> CandidateValidationMessage { + let (sender, _) = oneshot::channel(); + let pov = Arc::new(PoV { block_data: BlockData(Vec::new()) }); + CandidateValidationMessage::ValidateFromChainState(Default::default(), pov, sender) +} + +fn test_candidate_backing_msg() -> CandidateBackingMessage { + let (sender, _) = oneshot::channel(); + CandidateBackingMessage::GetBackedCandidates(Default::default(), Vec::new(), sender) +} + +fn test_chain_api_msg() -> ChainApiMessage { + let (sender, _) = oneshot::channel(); + ChainApiMessage::FinalizedBlockNumber(sender) +} + +fn test_collator_generation_msg() -> CollationGenerationMessage { + CollationGenerationMessage::Initialize(CollationGenerationConfig { + key: CollatorPair::generate().0, + collator: Box::new(|_, _| TestCollator.boxed()), + para_id: Default::default(), + }) +} +struct TestCollator; + +impl Future for TestCollator { + type Output = Option; + + fn poll(self: Pin<&mut Self>, _cx: &mut futures::task::Context) -> Poll { + panic!("at the Disco") + } +} + +impl Unpin for TestCollator {} + +fn test_collator_protocol_msg() -> CollatorProtocolMessage { + CollatorProtocolMessage::CollateOn(Default::default()) +} + +fn test_network_bridge_event() -> NetworkBridgeEvent { + NetworkBridgeEvent::PeerDisconnected(PeerId::random()) +} + +fn test_statement_distribution_msg() -> StatementDistributionMessage { + StatementDistributionMessage::NetworkBridgeUpdateV1(test_network_bridge_event()) +} + +fn test_availability_recovery_msg() -> AvailabilityRecoveryMessage { + let (sender, _) = oneshot::channel(); + AvailabilityRecoveryMessage::RecoverAvailableData( + Default::default(), + Default::default(), + None, + sender, + ) +} + +fn test_bitfield_distribution_msg() -> BitfieldDistributionMessage { + BitfieldDistributionMessage::NetworkBridgeUpdateV1(test_network_bridge_event()) +} + +fn test_provisioner_msg() -> ProvisionerMessage { + let (sender, _) = oneshot::channel(); + ProvisionerMessage::RequestInherentData(Default::default(), sender) +} + +fn test_runtime_api_msg() -> RuntimeApiMessage { + let (sender, _) = oneshot::channel(); + RuntimeApiMessage::Request(Default::default(), RuntimeApiRequest::Validators(sender)) +} + +fn test_availability_store_msg() -> AvailabilityStoreMessage { + let (sender, _) = oneshot::channel(); + AvailabilityStoreMessage::QueryAvailableData(CandidateHash(Default::default()), sender) +} + +fn test_network_bridge_msg() -> NetworkBridgeMessage { + NetworkBridgeMessage::ReportPeer(PeerId::random(), UnifiedReputationChange::BenefitMinor("")) +} + +fn test_approval_distribution_msg() -> ApprovalDistributionMessage { + ApprovalDistributionMessage::NewBlocks(Default::default()) +} + +fn test_approval_voting_msg() -> ApprovalVotingMessage { + let (sender, _) = oneshot::channel(); + ApprovalVotingMessage::ApprovedAncestor(Default::default(), 0, sender) +} + +// Checks that `stop`, `broadcast_signal` and `broadcast_message` are implemented correctly. +#[test] +fn overseer_all_subsystems_receive_signals_and_messages() { + const NUM_SUBSYSTEMS: usize = 17; + // -3 for BitfieldSigning, GossipSupport and AvailabilityDistribution + const NUM_SUBSYSTEMS_MESSAGED: usize = NUM_SUBSYSTEMS - 3; + + let spawner = sp_core::testing::TaskExecutor::new(); + executor::block_on(async move { + let stop_signals_received = Arc::new(atomic::AtomicUsize::new(0)); + let signals_received = Arc::new(atomic::AtomicUsize::new(0)); + let msgs_received = Arc::new(atomic::AtomicUsize::new(0)); + + let subsystem = CounterSubsystem::new( + stop_signals_received.clone(), + signals_received.clone(), + msgs_received.clone(), + ); + + let all_subsystems = AllSubsystems { + candidate_validation: subsystem.clone(), + candidate_backing: subsystem.clone(), + collation_generation: subsystem.clone(), + collator_protocol: subsystem.clone(), + statement_distribution: subsystem.clone(), + availability_distribution: subsystem.clone(), + availability_recovery: subsystem.clone(), + bitfield_signing: subsystem.clone(), + bitfield_distribution: subsystem.clone(), + provisioner: subsystem.clone(), + runtime_api: subsystem.clone(), + availability_store: subsystem.clone(), + network_bridge: subsystem.clone(), + chain_api: subsystem.clone(), + approval_distribution: subsystem.clone(), + approval_voting: subsystem.clone(), + gossip_support: subsystem.clone(), + }; + let (overseer, mut handler) = Overseer::new( + vec![], + all_subsystems, + None, + MockSupportsParachains, + spawner, + ).unwrap(); + let overseer_fut = overseer.run().fuse(); + + pin_mut!(overseer_fut); + + // send a signal to each subsystem + handler.block_imported(BlockInfo { + hash: Default::default(), + parent_hash: Default::default(), + number: Default::default(), + }).await; + + // send a msg to each subsystem + // except for BitfieldSigning and GossipSupport as the messages are not instantiable + handler.send_msg(AllMessages::CandidateValidation(test_candidate_validation_msg())).await; + handler.send_msg(AllMessages::CandidateBacking(test_candidate_backing_msg())).await; + handler.send_msg(AllMessages::CollationGeneration(test_collator_generation_msg())).await; + handler.send_msg(AllMessages::CollatorProtocol(test_collator_protocol_msg())).await; + handler.send_msg(AllMessages::StatementDistribution(test_statement_distribution_msg())).await; + handler.send_msg(AllMessages::AvailabilityRecovery(test_availability_recovery_msg())).await; + // handler.send_msg(AllMessages::BitfieldSigning(test_bitfield_signing_msg())).await; + // handler.send_msg(AllMessages::GossipSupport(test_bitfield_signing_msg())).await; + handler.send_msg(AllMessages::BitfieldDistribution(test_bitfield_distribution_msg())).await; + handler.send_msg(AllMessages::Provisioner(test_provisioner_msg())).await; + handler.send_msg(AllMessages::RuntimeApi(test_runtime_api_msg())).await; + handler.send_msg(AllMessages::AvailabilityStore(test_availability_store_msg())).await; + handler.send_msg(AllMessages::NetworkBridge(test_network_bridge_msg())).await; + handler.send_msg(AllMessages::ChainApi(test_chain_api_msg())).await; + handler.send_msg(AllMessages::ApprovalDistribution(test_approval_distribution_msg())).await; + handler.send_msg(AllMessages::ApprovalVoting(test_approval_voting_msg())).await; + + // Wait until all subsystems have received. Otherwise the messages might race against + // the conclude signal. + loop { + match (&mut overseer_fut).timeout(Duration::from_millis(100)).await { + None => { + let r = msgs_received.load(atomic::Ordering::SeqCst); + if r < NUM_SUBSYSTEMS_MESSAGED { + Delay::new(Duration::from_millis(100)).await; + } else if r > NUM_SUBSYSTEMS_MESSAGED { + panic!("too many messages received??"); + } else { + break + } + } + Some(_) => panic!("exited too early"), + } + } + + // send a stop signal to each subsystems + handler.stop().await; + + let res = overseer_fut.await; + assert_eq!(stop_signals_received.load(atomic::Ordering::SeqCst), NUM_SUBSYSTEMS); + assert_eq!(signals_received.load(atomic::Ordering::SeqCst), NUM_SUBSYSTEMS); + assert_eq!(msgs_received.load(atomic::Ordering::SeqCst), NUM_SUBSYSTEMS_MESSAGED); + + assert!(res.is_ok()); + }); +} + +#[test] +fn context_holds_onto_message_until_enough_signals_received() { + let (candidate_validation_bounded_tx, _) = metered::channel(CHANNEL_CAPACITY); + let (candidate_backing_bounded_tx, _) = metered::channel(CHANNEL_CAPACITY); + let (statement_distribution_bounded_tx, _) = metered::channel(CHANNEL_CAPACITY); + let (availability_distribution_bounded_tx, _) = metered::channel(CHANNEL_CAPACITY); + let (availability_recovery_bounded_tx, _) = metered::channel(CHANNEL_CAPACITY); + let (bitfield_signing_bounded_tx, _) = metered::channel(CHANNEL_CAPACITY); + let (bitfield_distribution_bounded_tx, _) = metered::channel(CHANNEL_CAPACITY); + let (provisioner_bounded_tx, _) = metered::channel(CHANNEL_CAPACITY); + let (runtime_api_bounded_tx, _) = metered::channel(CHANNEL_CAPACITY); + let (availability_store_bounded_tx, _) = metered::channel(CHANNEL_CAPACITY); + let (network_bridge_bounded_tx, _) = metered::channel(CHANNEL_CAPACITY); + let (chain_api_bounded_tx, _) = metered::channel(CHANNEL_CAPACITY); + let (collator_protocol_bounded_tx, _) = metered::channel(CHANNEL_CAPACITY); + let (collation_generation_bounded_tx, _) = metered::channel(CHANNEL_CAPACITY); + let (approval_distribution_bounded_tx, _) = metered::channel(CHANNEL_CAPACITY); + let (approval_voting_bounded_tx, _) = metered::channel(CHANNEL_CAPACITY); + let (gossip_support_bounded_tx, _) = metered::channel(CHANNEL_CAPACITY); + + let (candidate_validation_unbounded_tx, _) = metered::unbounded(); + let (candidate_backing_unbounded_tx, _) = metered::unbounded(); + let (statement_distribution_unbounded_tx, _) = metered::unbounded(); + let (availability_distribution_unbounded_tx, _) = metered::unbounded(); + let (availability_recovery_unbounded_tx, _) = metered::unbounded(); + let (bitfield_signing_unbounded_tx, _) = metered::unbounded(); + let (bitfield_distribution_unbounded_tx, _) = metered::unbounded(); + let (provisioner_unbounded_tx, _) = metered::unbounded(); + let (runtime_api_unbounded_tx, _) = metered::unbounded(); + let (availability_store_unbounded_tx, _) = metered::unbounded(); + let (network_bridge_unbounded_tx, _) = metered::unbounded(); + let (chain_api_unbounded_tx, _) = metered::unbounded(); + let (collator_protocol_unbounded_tx, _) = metered::unbounded(); + let (collation_generation_unbounded_tx, _) = metered::unbounded(); + let (approval_distribution_unbounded_tx, _) = metered::unbounded(); + let (approval_voting_unbounded_tx, _) = metered::unbounded(); + let (gossip_support_unbounded_tx, _) = metered::unbounded(); + + let channels_out = ChannelsOut { + candidate_validation: candidate_validation_bounded_tx.clone(), + candidate_backing: candidate_backing_bounded_tx.clone(), + statement_distribution: statement_distribution_bounded_tx.clone(), + availability_distribution: availability_distribution_bounded_tx.clone(), + availability_recovery: availability_recovery_bounded_tx.clone(), + bitfield_signing: bitfield_signing_bounded_tx.clone(), + bitfield_distribution: bitfield_distribution_bounded_tx.clone(), + provisioner: provisioner_bounded_tx.clone(), + runtime_api: runtime_api_bounded_tx.clone(), + availability_store: availability_store_bounded_tx.clone(), + network_bridge: network_bridge_bounded_tx.clone(), + chain_api: chain_api_bounded_tx.clone(), + collator_protocol: collator_protocol_bounded_tx.clone(), + collation_generation: collation_generation_bounded_tx.clone(), + approval_distribution: approval_distribution_bounded_tx.clone(), + approval_voting: approval_voting_bounded_tx.clone(), + gossip_support: gossip_support_bounded_tx.clone(), + + candidate_validation_unbounded: candidate_validation_unbounded_tx.clone(), + candidate_backing_unbounded: candidate_backing_unbounded_tx.clone(), + statement_distribution_unbounded: statement_distribution_unbounded_tx.clone(), + availability_distribution_unbounded: availability_distribution_unbounded_tx.clone(), + availability_recovery_unbounded: availability_recovery_unbounded_tx.clone(), + bitfield_signing_unbounded: bitfield_signing_unbounded_tx.clone(), + bitfield_distribution_unbounded: bitfield_distribution_unbounded_tx.clone(), + provisioner_unbounded: provisioner_unbounded_tx.clone(), + runtime_api_unbounded: runtime_api_unbounded_tx.clone(), + availability_store_unbounded: availability_store_unbounded_tx.clone(), + network_bridge_unbounded: network_bridge_unbounded_tx.clone(), + chain_api_unbounded: chain_api_unbounded_tx.clone(), + collator_protocol_unbounded: collator_protocol_unbounded_tx.clone(), + collation_generation_unbounded: collation_generation_unbounded_tx.clone(), + approval_distribution_unbounded: approval_distribution_unbounded_tx.clone(), + approval_voting_unbounded: approval_voting_unbounded_tx.clone(), + gossip_support_unbounded: gossip_support_unbounded_tx.clone(), + }; + + let (mut signal_tx, signal_rx) = metered::channel(CHANNEL_CAPACITY); + let (mut bounded_tx, bounded_rx) = metered::channel(CHANNEL_CAPACITY); + let (unbounded_tx, unbounded_rx) = metered::unbounded(); + let (to_overseer_tx, _to_overseer_rx) = metered::unbounded(); + + let mut ctx = OverseerSubsystemContext::<()>::new_unmetered( + signal_rx, + stream::select(bounded_rx, unbounded_rx), + channels_out, + to_overseer_tx, + ); + + assert_eq!(ctx.signals_received.load(), 0); + + let test_fut = async move { + signal_tx.send(OverseerSignal::Conclude).await.unwrap(); + assert_matches!(ctx.recv().await.unwrap(), FromOverseer::Signal(OverseerSignal::Conclude)); + + assert_eq!(ctx.signals_received.load(), 1); + bounded_tx.send(MessagePacket { + signals_received: 2, + message: (), + }).await.unwrap(); + unbounded_tx.unbounded_send(MessagePacket { + signals_received: 2, + message: (), + }).unwrap(); + + match poll!(ctx.recv()) { + Poll::Pending => {} + Poll::Ready(_) => panic!("ready too early"), + }; + + assert!(ctx.pending_incoming.is_some()); + + signal_tx.send(OverseerSignal::Conclude).await.unwrap(); + assert_matches!(ctx.recv().await.unwrap(), FromOverseer::Signal(OverseerSignal::Conclude)); + assert_matches!(ctx.recv().await.unwrap(), FromOverseer::Communication { msg: () }); + assert_matches!(ctx.recv().await.unwrap(), FromOverseer::Communication { msg: () }); + assert!(ctx.pending_incoming.is_none()); + }; + + futures::executor::block_on(test_fut); +} diff --git a/polkadot/node/subsystem-util/src/lib.rs b/polkadot/node/subsystem-util/src/lib.rs index 27433a3baa..ced1aaeda3 100644 --- a/polkadot/node/subsystem-util/src/lib.rs +++ b/polkadot/node/subsystem-util/src/lib.rs @@ -861,248 +861,4 @@ impl futures::Stream for Metronome } #[cfg(test)] -mod tests { - use super::*; - use executor::block_on; - use thiserror::Error; - use polkadot_node_jaeger as jaeger; - use polkadot_node_subsystem::{ - messages::{AllMessages, CollatorProtocolMessage}, ActiveLeavesUpdate, FromOverseer, OverseerSignal, - SpawnedSubsystem, ActivatedLeaf, LeafStatus, - }; - use assert_matches::assert_matches; - use futures::{channel::mpsc, executor, StreamExt, future, Future, FutureExt, SinkExt}; - use polkadot_primitives::v1::Hash; - use polkadot_node_subsystem_test_helpers::{self as test_helpers, make_subsystem_context}; - use std::{pin::Pin, sync::{Arc, atomic::{AtomicUsize, Ordering}}, time::Duration}; - - // basic usage: in a nutshell, when you want to define a subsystem, just focus on what its jobs do; - // you can leave the subsystem itself to the job manager. - - // for purposes of demonstration, we're going to whip up a fake subsystem. - // this will 'select' candidates which are pre-loaded in the job - - // job structs are constructed within JobTrait::run - // most will want to retain the sender and receiver, as well as whatever other data they like - struct FakeCollatorProtocolJob { - receiver: mpsc::Receiver, - } - - // Error will mostly be a wrapper to make the try operator more convenient; - // deriving From implementations for most variants is recommended. - // It must implement Debug for logging. - #[derive(Debug, Error)] - enum Error { - #[error(transparent)] - Sending(#[from]mpsc::SendError), - } - - impl JobTrait for FakeCollatorProtocolJob { - type ToJob = CollatorProtocolMessage; - type Error = Error; - type RunArgs = bool; - type Metrics = (); - - const NAME: &'static str = "FakeCollatorProtocolJob"; - - /// Run a job for the parent block indicated - // - // this function is in charge of creating and executing the job's main loop - fn run( - _: Hash, - _: Arc, - run_args: Self::RunArgs, - _metrics: Self::Metrics, - receiver: mpsc::Receiver, - mut sender: JobSender, - ) -> Pin> + Send>> { - async move { - let job = FakeCollatorProtocolJob { receiver }; - - if run_args { - sender.send_message(CollatorProtocolMessage::Invalid( - Default::default(), - Default::default(), - ).into()).await; - } - - // it isn't necessary to break run_loop into its own function, - // but it's convenient to separate the concerns in this way - job.run_loop().await - } - .boxed() - } - } - - impl FakeCollatorProtocolJob { - async fn run_loop(mut self) -> Result<(), Error> { - loop { - match self.receiver.next().await { - Some(_csm) => { - unimplemented!("we'd report the collator to the peer set manager here, but that's not implemented yet"); - } - None => break, - } - } - - Ok(()) - } - } - - // with the job defined, it's straightforward to get a subsystem implementation. - type FakeCollatorProtocolSubsystem = - JobSubsystem; - - // this type lets us pretend to be the overseer - type OverseerHandle = test_helpers::TestSubsystemContextHandle; - - fn test_harness>( - run_args: bool, - test: impl FnOnce(OverseerHandle) -> T, - ) { - let _ = env_logger::builder() - .is_test(true) - .filter( - None, - log::LevelFilter::Trace, - ) - .try_init(); - - let pool = sp_core::testing::TaskExecutor::new(); - let (context, overseer_handle) = make_subsystem_context(pool.clone()); - - let subsystem = FakeCollatorProtocolSubsystem::new( - pool, - run_args, - (), - ).run(context); - let test_future = test(overseer_handle); - - futures::pin_mut!(subsystem, test_future); - - executor::block_on(async move { - future::join(subsystem, test_future) - .timeout(Duration::from_secs(2)) - .await - .expect("test timed out instead of completing") - }); - } - - #[test] - fn starting_and_stopping_job_works() { - let relay_parent: Hash = [0; 32].into(); - - test_harness(true, |mut overseer_handle| async move { - overseer_handle - .send(FromOverseer::Signal(OverseerSignal::ActiveLeaves( - ActiveLeavesUpdate::start_work(ActivatedLeaf { - hash: relay_parent, - number: 1, - status: LeafStatus::Fresh, - span: Arc::new(jaeger::Span::Disabled), - }), - ))) - .await; - assert_matches!( - overseer_handle.recv().await, - AllMessages::CollatorProtocol(_) - ); - overseer_handle - .send(FromOverseer::Signal(OverseerSignal::ActiveLeaves( - ActiveLeavesUpdate::stop_work(relay_parent), - ))) - .await; - - overseer_handle - .send(FromOverseer::Signal(OverseerSignal::Conclude)) - .await; - }); - } - - #[test] - fn sending_to_a_non_running_job_do_not_stop_the_subsystem() { - let relay_parent = Hash::repeat_byte(0x01); - - test_harness(true, |mut overseer_handle| async move { - overseer_handle - .send(FromOverseer::Signal(OverseerSignal::ActiveLeaves( - ActiveLeavesUpdate::start_work(ActivatedLeaf { - hash: relay_parent, - number: 1, - status: LeafStatus::Fresh, - span: Arc::new(jaeger::Span::Disabled), - }), - ))) - .await; - - // send to a non running job - overseer_handle - .send(FromOverseer::Communication { - msg: Default::default(), - }) - .await; - - // the subsystem is still alive - assert_matches!( - overseer_handle.recv().await, - AllMessages::CollatorProtocol(_) - ); - - overseer_handle - .send(FromOverseer::Signal(OverseerSignal::Conclude)) - .await; - }); - } - - #[test] - fn test_subsystem_impl_and_name_derivation() { - let pool = sp_core::testing::TaskExecutor::new(); - let (context, _) = make_subsystem_context::(pool.clone()); - - let SpawnedSubsystem { name, .. } = - FakeCollatorProtocolSubsystem::new(pool, false, ()).start(context); - assert_eq!(name, "FakeCollatorProtocol"); - } - - - #[test] - fn tick_tack_metronome() { - let n = Arc::new(AtomicUsize::default()); - - let (tick, mut block) = mpsc::unbounded(); - - let metronome = { - let n = n.clone(); - let stream = Metronome::new(Duration::from_millis(137_u64)); - stream.for_each(move |_res| { - let _ = n.fetch_add(1, Ordering::Relaxed); - let mut tick = tick.clone(); - async move { - tick.send(()).await.expect("Test helper channel works. qed"); - } - }).fuse() - }; - - let f2 = async move { - block.next().await; - assert_eq!(n.load(Ordering::Relaxed), 1_usize); - block.next().await; - assert_eq!(n.load(Ordering::Relaxed), 2_usize); - block.next().await; - assert_eq!(n.load(Ordering::Relaxed), 3_usize); - block.next().await; - assert_eq!(n.load(Ordering::Relaxed), 4_usize); - }.fuse(); - - futures::pin_mut!(f2); - futures::pin_mut!(metronome); - - block_on(async move { - // futures::join!(metronome, f2) - futures::select!( - _ = metronome => unreachable!("Metronome never stops. qed"), - _ = f2 => (), - ) - }); - } -} +mod tests; diff --git a/polkadot/node/subsystem-util/src/tests.rs b/polkadot/node/subsystem-util/src/tests.rs new file mode 100644 index 0000000000..40b87fff7c --- /dev/null +++ b/polkadot/node/subsystem-util/src/tests.rs @@ -0,0 +1,259 @@ +// Copyright 2020 Parity Technologies (UK) Ltd. +// This file is part of Polkadot. + +// Polkadot is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Polkadot is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Polkadot. If not, see . + +use super::*; +use executor::block_on; +use thiserror::Error; +use polkadot_node_jaeger as jaeger; +use polkadot_node_subsystem::{ + messages::{AllMessages, CollatorProtocolMessage}, ActiveLeavesUpdate, FromOverseer, OverseerSignal, + SpawnedSubsystem, ActivatedLeaf, LeafStatus, +}; +use assert_matches::assert_matches; +use futures::{channel::mpsc, executor, StreamExt, future, Future, FutureExt, SinkExt}; +use polkadot_primitives::v1::Hash; +use polkadot_node_subsystem_test_helpers::{self as test_helpers, make_subsystem_context}; +use std::{pin::Pin, sync::{Arc, atomic::{AtomicUsize, Ordering}}, time::Duration}; + +// basic usage: in a nutshell, when you want to define a subsystem, just focus on what its jobs do; +// you can leave the subsystem itself to the job manager. + +// for purposes of demonstration, we're going to whip up a fake subsystem. +// this will 'select' candidates which are pre-loaded in the job + +// job structs are constructed within JobTrait::run +// most will want to retain the sender and receiver, as well as whatever other data they like +struct FakeCollatorProtocolJob { + receiver: mpsc::Receiver, +} + +// Error will mostly be a wrapper to make the try operator more convenient; +// deriving From implementations for most variants is recommended. +// It must implement Debug for logging. +#[derive(Debug, Error)] +enum Error { + #[error(transparent)] + Sending(#[from]mpsc::SendError), +} + +impl JobTrait for FakeCollatorProtocolJob { + type ToJob = CollatorProtocolMessage; + type Error = Error; + type RunArgs = bool; + type Metrics = (); + + const NAME: &'static str = "FakeCollatorProtocolJob"; + + /// Run a job for the parent block indicated + // + // this function is in charge of creating and executing the job's main loop + fn run( + _: Hash, + _: Arc, + run_args: Self::RunArgs, + _metrics: Self::Metrics, + receiver: mpsc::Receiver, + mut sender: JobSender, + ) -> Pin> + Send>> { + async move { + let job = FakeCollatorProtocolJob { receiver }; + + if run_args { + sender.send_message(CollatorProtocolMessage::Invalid( + Default::default(), + Default::default(), + ).into()).await; + } + + // it isn't necessary to break run_loop into its own function, + // but it's convenient to separate the concerns in this way + job.run_loop().await + } + .boxed() + } +} + +impl FakeCollatorProtocolJob { + async fn run_loop(mut self) -> Result<(), Error> { + loop { + match self.receiver.next().await { + Some(_csm) => { + unimplemented!("we'd report the collator to the peer set manager here, but that's not implemented yet"); + } + None => break, + } + } + + Ok(()) + } +} + +// with the job defined, it's straightforward to get a subsystem implementation. +type FakeCollatorProtocolSubsystem = + JobSubsystem; + +// this type lets us pretend to be the overseer +type OverseerHandle = test_helpers::TestSubsystemContextHandle; + +fn test_harness>( + run_args: bool, + test: impl FnOnce(OverseerHandle) -> T, +) { + let _ = env_logger::builder() + .is_test(true) + .filter( + None, + log::LevelFilter::Trace, + ) + .try_init(); + + let pool = sp_core::testing::TaskExecutor::new(); + let (context, overseer_handle) = make_subsystem_context(pool.clone()); + + let subsystem = FakeCollatorProtocolSubsystem::new( + pool, + run_args, + (), + ).run(context); + let test_future = test(overseer_handle); + + futures::pin_mut!(subsystem, test_future); + + executor::block_on(async move { + future::join(subsystem, test_future) + .timeout(Duration::from_secs(2)) + .await + .expect("test timed out instead of completing") + }); +} + +#[test] +fn starting_and_stopping_job_works() { + let relay_parent: Hash = [0; 32].into(); + + test_harness(true, |mut overseer_handle| async move { + overseer_handle + .send(FromOverseer::Signal(OverseerSignal::ActiveLeaves( + ActiveLeavesUpdate::start_work(ActivatedLeaf { + hash: relay_parent, + number: 1, + status: LeafStatus::Fresh, + span: Arc::new(jaeger::Span::Disabled), + }), + ))) + .await; + assert_matches!( + overseer_handle.recv().await, + AllMessages::CollatorProtocol(_) + ); + overseer_handle + .send(FromOverseer::Signal(OverseerSignal::ActiveLeaves( + ActiveLeavesUpdate::stop_work(relay_parent), + ))) + .await; + + overseer_handle + .send(FromOverseer::Signal(OverseerSignal::Conclude)) + .await; + }); +} + +#[test] +fn sending_to_a_non_running_job_do_not_stop_the_subsystem() { + let relay_parent = Hash::repeat_byte(0x01); + + test_harness(true, |mut overseer_handle| async move { + overseer_handle + .send(FromOverseer::Signal(OverseerSignal::ActiveLeaves( + ActiveLeavesUpdate::start_work(ActivatedLeaf { + hash: relay_parent, + number: 1, + status: LeafStatus::Fresh, + span: Arc::new(jaeger::Span::Disabled), + }), + ))) + .await; + + // send to a non running job + overseer_handle + .send(FromOverseer::Communication { + msg: Default::default(), + }) + .await; + + // the subsystem is still alive + assert_matches!( + overseer_handle.recv().await, + AllMessages::CollatorProtocol(_) + ); + + overseer_handle + .send(FromOverseer::Signal(OverseerSignal::Conclude)) + .await; + }); +} + +#[test] +fn test_subsystem_impl_and_name_derivation() { + let pool = sp_core::testing::TaskExecutor::new(); + let (context, _) = make_subsystem_context::(pool.clone()); + + let SpawnedSubsystem { name, .. } = + FakeCollatorProtocolSubsystem::new(pool, false, ()).start(context); + assert_eq!(name, "FakeCollatorProtocol"); +} + + +#[test] +fn tick_tack_metronome() { + let n = Arc::new(AtomicUsize::default()); + + let (tick, mut block) = mpsc::unbounded(); + + let metronome = { + let n = n.clone(); + let stream = Metronome::new(Duration::from_millis(137_u64)); + stream.for_each(move |_res| { + let _ = n.fetch_add(1, Ordering::Relaxed); + let mut tick = tick.clone(); + async move { + tick.send(()).await.expect("Test helper channel works. qed"); + } + }).fuse() + }; + + let f2 = async move { + block.next().await; + assert_eq!(n.load(Ordering::Relaxed), 1_usize); + block.next().await; + assert_eq!(n.load(Ordering::Relaxed), 2_usize); + block.next().await; + assert_eq!(n.load(Ordering::Relaxed), 3_usize); + block.next().await; + assert_eq!(n.load(Ordering::Relaxed), 4_usize); + }.fuse(); + + futures::pin_mut!(f2); + futures::pin_mut!(metronome); + + block_on(async move { + // futures::join!(metronome, f2) + futures::select!( + _ = metronome => unreachable!("Metronome never stops. qed"), + _ = f2 => (), + ) + }); +}