diff --git a/polkadot/.gitlab-ci.yml b/polkadot/.gitlab-ci.yml
index 11a9e42d80..726d9879b4 100644
--- a/polkadot/.gitlab-ci.yml
+++ b/polkadot/.gitlab-ci.yml
@@ -208,7 +208,7 @@ generate-impl-guide:
<<: *rules-test
<<: *docker-env
image:
- name: michaelfbryan/mdbook-docker-image:latest
+ name: michaelfbryan/mdbook-docker-image:v0.4.4
entrypoint: [""]
script:
- mdbook build roadmap/implementers-guide
diff --git a/polkadot/Cargo.lock b/polkadot/Cargo.lock
index 7ae0baa81e..3f44da84ad 100644
--- a/polkadot/Cargo.lock
+++ b/polkadot/Cargo.lock
@@ -5153,6 +5153,7 @@ version = "0.1.0"
dependencies = [
"assert_matches",
"futures 0.3.12",
+ "futures-timer 3.0.2",
"lru",
"maplit",
"parity-scale-codec",
@@ -5166,6 +5167,7 @@ dependencies = [
"rand 0.8.3",
"sc-keystore",
"sc-network",
+ "smallvec 1.6.1",
"sp-application-crypto",
"sp-core",
"sp-keyring",
diff --git a/polkadot/node/network/availability-distribution/Cargo.toml b/polkadot/node/network/availability-distribution/Cargo.toml
index 5483d87def..fc0f2574e4 100644
--- a/polkadot/node/network/availability-distribution/Cargo.toml
+++ b/polkadot/node/network/availability-distribution/Cargo.toml
@@ -29,5 +29,7 @@ sp-keyring = { git = "https://github.com/paritytech/substrate", branch = "master
sp-tracing = { git = "https://github.com/paritytech/substrate", branch = "master" }
sc-keystore = { git = "https://github.com/paritytech/substrate", branch = "master" }
sc-network = { git = "https://github.com/paritytech/substrate", branch = "master" }
+futures-timer = "3.0.2"
assert_matches = "1.4.0"
maplit = "1.0"
+smallvec = "1.6.1"
diff --git a/polkadot/node/network/availability-distribution/src/error.rs b/polkadot/node/network/availability-distribution/src/error.rs
index dbe3ad56db..3206d20c82 100644
--- a/polkadot/node/network/availability-distribution/src/error.rs
+++ b/polkadot/node/network/availability-distribution/src/error.rs
@@ -57,6 +57,10 @@ pub enum Error {
/// We tried accessing a session that was not cached.
#[error("Session is not cached.")]
NoSuchCachedSession,
+
+ /// We tried reporting bad validators, although we are not a validator ourselves.
+ #[error("Not a validator.")]
+ NotAValidator,
/// Requester stream exhausted.
#[error("Erasure chunk requester stream exhausted")]
diff --git a/polkadot/node/network/availability-distribution/src/lib.rs b/polkadot/node/network/availability-distribution/src/lib.rs
index 4e8683a092..a447204880 100644
--- a/polkadot/node/network/availability-distribution/src/lib.rs
+++ b/polkadot/node/network/availability-distribution/src/lib.rs
@@ -43,6 +43,9 @@ mod metrics;
/// Prometheus `Metrics` for availability distribution.
pub use metrics::Metrics;
+#[cfg(test)]
+mod tests;
+
const LOG_TARGET: &'static str = "availability_distribution";
/// The availability distribution subsystem.
diff --git a/polkadot/node/network/availability-distribution/src/requester/fetch_task/tests.rs b/polkadot/node/network/availability-distribution/src/requester/fetch_task/tests.rs
index b425485056..1f12000621 100644
--- a/polkadot/node/network/availability-distribution/src/requester/fetch_task/tests.rs
+++ b/polkadot/node/network/availability-distribution/src/requester/fetch_task/tests.rs
@@ -15,7 +15,7 @@
// along with Polkadot. If not, see .
use std::collections::HashMap;
-use std::sync::Arc;
+
use parity_scale_codec::Encode;
@@ -23,15 +23,15 @@ use futures::channel::{mpsc, oneshot};
use futures::{executor, Future, FutureExt, StreamExt, select};
use futures::task::{Poll, Context, noop_waker};
-use polkadot_erasure_coding::{obtain_chunks_v1 as obtain_chunks, branches};
use sc_network as network;
use sp_keyring::Sr25519Keyring;
-use polkadot_primitives::v1::{AvailableData, BlockData, CandidateHash, HeadData, PersistedValidationData, PoV, ValidatorIndex};
+use polkadot_primitives::v1::{BlockData, CandidateHash, PoV, ValidatorIndex};
use polkadot_node_network_protocol::request_response::v1;
use polkadot_subsystem::messages::AllMessages;
use crate::metrics::Metrics;
+use crate::tests::mock::get_valid_chunk_data;
use super::*;
#[test]
@@ -74,7 +74,10 @@ fn task_does_not_accept_invalid_chunk() {
#[test]
fn task_stores_valid_chunk() {
let (mut task, rx) = get_test_running_task();
- let (root_hash, chunk) = get_valid_chunk_data();
+ let pov = PoV {
+ block_data: BlockData(vec![45, 46, 47]),
+ };
+ let (root_hash, chunk) = get_valid_chunk_data(pov);
task.erasure_root = root_hash;
task.request.index = chunk.index;
@@ -107,7 +110,10 @@ fn task_stores_valid_chunk() {
#[test]
fn task_does_not_accept_wrongly_indexed_chunk() {
let (mut task, rx) = get_test_running_task();
- let (root_hash, chunk) = get_valid_chunk_data();
+ let pov = PoV {
+ block_data: BlockData(vec![45, 46, 47]),
+ };
+ let (root_hash, chunk) = get_valid_chunk_data(pov);
task.erasure_root = root_hash;
task.request.index = ValidatorIndex(chunk.index.0+1);
@@ -137,7 +143,10 @@ fn task_does_not_accept_wrongly_indexed_chunk() {
#[test]
fn task_stores_valid_chunk_if_there_is_one() {
let (mut task, rx) = get_test_running_task();
- let (root_hash, chunk) = get_valid_chunk_data();
+ let pov = PoV {
+ block_data: BlockData(vec![45, 46, 47]),
+ };
+ let (root_hash, chunk) = get_valid_chunk_data(pov);
task.erasure_root = root_hash;
task.request.index = chunk.index;
@@ -287,29 +296,3 @@ fn get_test_running_task() -> (RunningTask, mpsc::Receiver) {
)
}
-fn get_valid_chunk_data() -> (Hash, ErasureChunk) {
- let fake_validator_count = 10;
- let persisted = PersistedValidationData {
- parent_head: HeadData(vec![7, 8, 9]),
- relay_parent_number: Default::default(),
- max_pov_size: 1024,
- relay_parent_storage_root: Default::default(),
- };
- let pov_block = PoV {
- block_data: BlockData(vec![45, 46, 47]),
- };
- let available_data = AvailableData {
- validation_data: persisted, pov: Arc::new(pov_block),
- };
- let chunks = obtain_chunks(fake_validator_count, &available_data).unwrap();
- let branches = branches(chunks.as_ref());
- let root = branches.root();
- let chunk = branches.enumerate()
- .map(|(index, (proof, chunk))| ErasureChunk {
- chunk: chunk.to_vec(),
- index: ValidatorIndex(index as _),
- proof,
- })
- .next().expect("There really should be 10 chunks.");
- (root, chunk)
-}
diff --git a/polkadot/node/network/availability-distribution/src/session_cache.rs b/polkadot/node/network/availability-distribution/src/session_cache.rs
index 395d2ae783..15f5cad3c7 100644
--- a/polkadot/node/network/availability-distribution/src/session_cache.rs
+++ b/polkadot/node/network/availability-distribution/src/session_cache.rs
@@ -54,7 +54,10 @@ pub struct SessionCache {
/// to get any existing cache entry, before fetching new information, as we should not mess up
/// the order of validators in `SessionInfo::validator_groups`. (We want live TCP connections
/// wherever possible.)
- session_info_cache: LruCache,
+ ///
+ /// We store `None` in case we are not a validator, so we won't do needless fetches for non
+ /// validator nodes.
+ session_info_cache: LruCache>,
/// Key store for determining whether we are a validator and what `ValidatorIndex` we have.
keystore: SyncCryptoStorePtr,
@@ -134,19 +137,31 @@ impl SessionCache {
}
};
- if let Some(info) = self.session_info_cache.get(&session_index) {
- return Ok(Some(with_info(info)));
+ if let Some(o_info) = self.session_info_cache.get(&session_index) {
+ tracing::trace!(target: LOG_TARGET, session_index, "Got session from lru");
+ if let Some(info) = o_info {
+ return Ok(Some(with_info(info)));
+ } else {
+ // Info was cached - we are not a validator: return early:
+ return Ok(None)
+ }
}
if let Some(info) = self
.query_info_from_runtime(ctx, parent, session_index)
.await?
{
+ tracing::trace!(target: LOG_TARGET, session_index, "Calling `with_info`");
let r = with_info(&info);
- self.session_info_cache.put(session_index, info);
- return Ok(Some(r));
+ tracing::trace!(target: LOG_TARGET, session_index, "Storing session info in lru!");
+ self.session_info_cache.put(session_index, Some(info));
+ Ok(Some(r))
+ } else {
+ // Avoid needless fetches if we are not a validator:
+ self.session_info_cache.put(session_index, None);
+ tracing::trace!(target: LOG_TARGET, session_index, "No session info found!");
+ Ok(None)
}
- Ok(None)
}
/// Variant of `report_bad` that never fails, but just logs errors.
@@ -172,7 +187,9 @@ impl SessionCache {
let session = self
.session_info_cache
.get_mut(&report.session_index)
- .ok_or(Error::NoSuchCachedSession)?;
+ .ok_or(Error::NoSuchCachedSession)?
+ .as_mut()
+ .ok_or(Error::NotAValidator)?;
let group = session
.validator_groups
.get_mut(report.group_index.0 as usize)
@@ -194,6 +211,8 @@ impl SessionCache {
/// We need to pass in the relay parent for our call to `request_session_info_ctx`. We should
/// actually don't need that: I suppose it is used for internal caching based on relay parents,
/// which we don't use here. It should not do any harm though.
+ ///
+ /// Returns: `None` if not a validator.
async fn query_info_from_runtime(
&self,
ctx: &mut Context,
diff --git a/polkadot/node/network/availability-distribution/src/tests.rs b/polkadot/node/network/availability-distribution/src/tests.rs
deleted file mode 100644
index 473e478994..0000000000
--- a/polkadot/node/network/availability-distribution/src/tests.rs
+++ /dev/null
@@ -1,1278 +0,0 @@
-// 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 assert_matches::assert_matches;
-use polkadot_erasure_coding::{branches, obtain_chunks_v1 as obtain_chunks};
-use polkadot_node_network_protocol::{view, ObservedRole, our_view};
-use polkadot_node_subsystem_util::TimeoutExt;
-use polkadot_primitives::v1::{
- AvailableData, BlockData, CandidateCommitments, CandidateDescriptor, GroupIndex,
- GroupRotationInfo, HeadData, OccupiedCore, PersistedValidationData, PoV, ScheduledCore, Id as ParaId,
- CommittedCandidateReceipt,
-};
-use polkadot_subsystem_testhelpers as test_helpers;
-
-use futures::{executor, future, Future};
-use sc_keystore::LocalKeystore;
-use sp_application_crypto::AppKey;
-use sp_keystore::{SyncCryptoStore, SyncCryptoStorePtr};
-use sp_keyring::Sr25519Keyring;
-use std::{sync::Arc, time::Duration};
-use maplit::hashmap;
-
-macro_rules! view {
- ( $( $hash:expr ),* $(,)? ) => {
- // Finalized number unimportant for availability distribution.
- View::new(vec![ $( $hash.clone() ),* ], 0)
- };
-}
-
-fn chunk_protocol_message(
- message: AvailabilityGossipMessage,
-) -> protocol_v1::AvailabilityDistributionMessage {
- protocol_v1::AvailabilityDistributionMessage::Chunk(
- message.candidate_hash,
- message.erasure_chunk,
- )
-}
-
-fn make_per_candidate() -> PerCandidate {
- PerCandidate {
- live_in: HashSet::new(),
- message_vault: HashMap::new(),
- received_messages: HashMap::new(),
- sent_messages: HashMap::new(),
- validators: Vec::new(),
- validator_index: None,
- descriptor: Default::default(),
- span: jaeger::Span::Disabled,
- }
-}
-
-struct TestHarness {
- virtual_overseer: test_helpers::TestSubsystemContextHandle,
-}
-
-fn test_harness>(
- keystore: SyncCryptoStorePtr,
- test_fx: impl FnOnce(TestHarness) -> T,
-) -> ProtocolState {
- sp_tracing::try_init_simple();
-
- let pool = sp_core::testing::TaskExecutor::new();
- let (context, virtual_overseer) = test_helpers::make_subsystem_context(pool.clone());
-
- let subsystem = AvailabilityDistributionSubsystem::new(keystore, Default::default());
- let mut state = ProtocolState::default();
- {
- let subsystem = subsystem.run_inner(context, &mut state);
-
- let test_fut = test_fx(TestHarness { virtual_overseer });
-
- futures::pin_mut!(test_fut);
- futures::pin_mut!(subsystem);
-
- executor::block_on(future::select(test_fut, subsystem));
- }
-
- state
-}
-
-async fn overseer_send(
- overseer: &mut test_helpers::TestSubsystemContextHandle,
- msg: impl Into,
-) {
- let msg = msg.into();
- tracing::trace!(msg = ?msg, "sending message");
- overseer.send(FromOverseer::Communication { msg }).await
-}
-
-async fn overseer_recv(
- overseer: &mut test_helpers::TestSubsystemContextHandle,
-) -> AllMessages {
- tracing::trace!("waiting for message ...");
- let msg = overseer.recv().await;
- tracing::trace!(msg = ?msg, "received message");
- msg
-}
-
-fn occupied_core_from_candidate(receipt: &CommittedCandidateReceipt) -> CoreState {
- CoreState::Occupied(OccupiedCore {
- next_up_on_available: None,
- occupied_since: 0,
- time_out_at: 5,
- next_up_on_time_out: None,
- availability: Default::default(),
- group_responsible: GroupIndex::from(0),
- candidate_hash: receipt.hash(),
- candidate_descriptor: receipt.descriptor().clone(),
- })
-}
-
-#[derive(Clone)]
-struct TestState {
- chain_ids: Vec,
- validators: Vec,
- validator_public: Vec,
- validator_groups: (Vec>, GroupRotationInfo),
- head_data: HashMap,
- keystore: SyncCryptoStorePtr,
- relay_parent: Hash,
- ancestors: Vec,
- availability_cores: Vec,
- persisted_validation_data: PersistedValidationData,
- candidates: Vec,
- pov_blocks: Vec,
-}
-
-fn validator_pubkeys(val_ids: &[Sr25519Keyring]) -> Vec {
- val_ids.iter().map(|v| v.public().into()).collect()
-}
-
-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 validators = vec![
- Sr25519Keyring::Ferdie, // <- this node, role: validator
- Sr25519Keyring::Alice,
- Sr25519Keyring::Bob,
- Sr25519Keyring::Charlie,
- Sr25519Keyring::Dave,
- ];
-
- let keystore: SyncCryptoStorePtr = Arc::new(LocalKeystore::in_memory());
-
- SyncCryptoStore::sr25519_generate_new(
- &*keystore,
- ValidatorId::ID,
- Some(&validators[0].to_seed()),
- )
- .expect("Insert key into keystore");
-
- let validator_public = validator_pubkeys(&validators);
-
- let validator_groups = vec![vec![2, 0, 4], vec![1], vec![3]];
- let group_rotation_info = GroupRotationInfo {
- session_start_block: 0,
- group_rotation_frequency: 100,
- now: 1,
- };
- let validator_groups = (validator_groups, group_rotation_info);
-
- let availability_cores = vec![
- CoreState::Scheduled(ScheduledCore {
- para_id: chain_ids[0],
- collator: None,
- }),
- CoreState::Scheduled(ScheduledCore {
- para_id: chain_ids[1],
- collator: None,
- }),
- ];
-
- let mut head_data = HashMap::new();
- head_data.insert(chain_a, HeadData(vec![4, 5, 6]));
- head_data.insert(chain_b, HeadData(vec![7, 8, 9]));
-
- let ancestors = vec![
- Hash::repeat_byte(0x44),
- Hash::repeat_byte(0x33),
- Hash::repeat_byte(0x22),
- ];
- let relay_parent = Hash::repeat_byte(0x05);
-
- let persisted_validation_data = PersistedValidationData {
- parent_head: HeadData(vec![7, 8, 9]),
- relay_parent_number: Default::default(),
- max_pov_size: 1024,
- relay_parent_storage_root: Default::default(),
- };
-
- let pov_block_a = PoV {
- block_data: BlockData(vec![42, 43, 44]),
- };
-
- let pov_block_b = PoV {
- block_data: BlockData(vec![45, 46, 47]),
- };
-
- let candidates = vec![
- TestCandidateBuilder {
- para_id: chain_ids[0],
- relay_parent: relay_parent,
- pov_hash: pov_block_a.hash(),
- erasure_root: make_erasure_root(persisted_validation_data.clone(), validators.len(), pov_block_a.clone()),
- head_data: head_data.get(&chain_ids[0]).unwrap().clone(),
- ..Default::default()
- }
- .build(),
- TestCandidateBuilder {
- para_id: chain_ids[1],
- relay_parent: relay_parent,
- pov_hash: pov_block_b.hash(),
- erasure_root: make_erasure_root(persisted_validation_data.clone(), validators.len(), pov_block_b.clone()),
- head_data: head_data.get(&chain_ids[1]).unwrap().clone(),
- ..Default::default()
- }
- .build(),
- ];
-
- let pov_blocks = vec![pov_block_a, pov_block_b];
-
- Self {
- chain_ids,
- keystore,
- validators,
- validator_public,
- validator_groups,
- availability_cores,
- head_data,
- persisted_validation_data,
- relay_parent,
- ancestors,
- candidates,
- pov_blocks,
- }
- }
-}
-
-fn make_available_data(validation_data: PersistedValidationData, pov: PoV) -> AvailableData {
- AvailableData {
- validation_data,
- pov: Arc::new(pov),
- }
-}
-
-fn make_erasure_root(peristed: PersistedValidationData, validator_count: usize, pov: PoV) -> Hash {
- let available_data = make_available_data(peristed, pov);
-
- let chunks = obtain_chunks(validator_count, &available_data).unwrap();
- branches(&chunks).root()
-}
-
-fn make_erasure_chunks(peristed: PersistedValidationData, validator_count: usize, pov: PoV) -> Vec {
- let available_data = make_available_data(peristed, pov);
-
- derive_erasure_chunks_with_proofs(validator_count, &available_data)
-}
-
-fn make_valid_availability_gossip(
- test: &TestState,
- candidate: usize,
- erasure_chunk_index: u32,
-) -> AvailabilityGossipMessage {
- let erasure_chunks = make_erasure_chunks(
- test.persisted_validation_data.clone(),
- test.validator_public.len(),
- test.pov_blocks[candidate].clone(),
- );
-
- let erasure_chunk: ErasureChunk = erasure_chunks
- .get(erasure_chunk_index as usize)
- .expect("Must be valid or input is oob")
- .clone();
-
- AvailabilityGossipMessage {
- candidate_hash: test.candidates[candidate].hash(),
- erasure_chunk,
- }
-}
-
-#[derive(Default)]
-struct TestCandidateBuilder {
- para_id: ParaId,
- head_data: HeadData,
- pov_hash: Hash,
- relay_parent: Hash,
- erasure_root: Hash,
-}
-
-impl TestCandidateBuilder {
- fn build(self) -> CommittedCandidateReceipt {
- CommittedCandidateReceipt {
- descriptor: CandidateDescriptor {
- para_id: self.para_id,
- pov_hash: self.pov_hash,
- relay_parent: self.relay_parent,
- erasure_root: self.erasure_root,
- ..Default::default()
- },
- commitments: CandidateCommitments {
- head_data: self.head_data,
- ..Default::default()
- },
- }
- }
-}
-
-#[test]
-fn helper_integrity() {
- let test_state = TestState::default();
-
- let message = make_valid_availability_gossip(
- &test_state,
- 0,
- 2,
- );
-
- let root = &test_state.candidates[0].descriptor.erasure_root;
-
- let anticipated_hash = branch_hash(
- root,
- &message.erasure_chunk.proof,
- dbg!(message.erasure_chunk.index as usize),
- )
- .expect("Must be able to derive branch hash");
- assert_eq!(
- anticipated_hash,
- BlakeTwo256::hash(&message.erasure_chunk.chunk)
- );
-}
-
-fn derive_erasure_chunks_with_proofs(
- n_validators: usize,
- available_data: &AvailableData,
-) -> Vec {
- let chunks: Vec> = obtain_chunks(n_validators, available_data).unwrap();
-
- // create proofs for each erasure chunk
- let branches = branches(chunks.as_ref());
-
- let erasure_chunks = branches
- .enumerate()
- .map(|(index, (proof, chunk))| ErasureChunk {
- chunk: chunk.to_vec(),
- index: index as _,
- proof,
- })
- .collect::>();
-
- erasure_chunks
-}
-
-async fn expect_chunks_network_message(
- virtual_overseer: &mut test_helpers::TestSubsystemContextHandle,
- peers: &[Vec],
- candidates: &[CandidateHash],
- chunks: &[ErasureChunk],
-) {
- if chunks.is_empty() { return }
-
- assert_matches!(
- overseer_recv(virtual_overseer).await,
- AllMessages::NetworkBridge(
- NetworkBridgeMessage::SendValidationMessages(msgs)
- ) => {
- assert_eq!(msgs.len(), chunks.len());
- for (send_peers, msg) in msgs {
- assert_matches!(
- msg,
- protocol_v1::ValidationProtocol::AvailabilityDistribution(
- protocol_v1::AvailabilityDistributionMessage::Chunk(send_candidate, send_chunk)
- ) => {
- let i = chunks.iter().position(|c| c == &send_chunk).unwrap();
- assert!(candidates.contains(&send_candidate), format!("Could not find candidate: {:?}", send_candidate));
- assert_eq!(&peers[i], &send_peers);
- }
- );
- }
- }
- )
-}
-
-async fn change_our_view(
- virtual_overseer: &mut test_helpers::TestSubsystemContextHandle,
- view: OurView,
- validator_public: &[ValidatorId],
- ancestors: Vec,
- session_per_relay_parent: HashMap,
- availability_cores_per_relay_parent: HashMap>,
- data_availability: HashMap,
- chunk_data_per_candidate: HashMap,
- send_chunks_to: HashMap>,
-) {
- overseer_send(virtual_overseer, NetworkBridgeEvent::OurViewChange(view.clone())).await;
-
- // obtain the validators per relay parent
- assert_matches!(
- overseer_recv(virtual_overseer).await,
- AllMessages::RuntimeApi(RuntimeApiMessage::Request(
- relay_parent,
- RuntimeApiRequest::Validators(tx),
- )) => {
- assert!(view.contains(&relay_parent));
- tx.send(Ok(validator_public.to_vec())).unwrap();
- }
- );
-
- // query of k ancestors, we only provide one
- assert_matches!(
- overseer_recv(virtual_overseer).await,
- AllMessages::ChainApi(ChainApiMessage::Ancestors {
- hash: relay_parent,
- k,
- response_channel: tx,
- }) => {
- assert!(view.contains(&relay_parent));
- assert_eq!(k, AvailabilityDistributionSubsystem::K + 1);
- tx.send(Ok(ancestors.clone())).unwrap();
- }
- );
-
- for _ in 0..session_per_relay_parent.len() {
- assert_matches!(
- overseer_recv(virtual_overseer).await,
- AllMessages::RuntimeApi(RuntimeApiMessage::Request(
- relay_parent,
- RuntimeApiRequest::SessionIndexForChild(tx)
- )) => {
- let index = session_per_relay_parent.get(&relay_parent)
- .expect(&format!("Session index for relay parent {:?} does not exist", relay_parent));
- tx.send(Ok(*index)).unwrap();
- }
- );
- }
-
- for _ in 0..availability_cores_per_relay_parent.len() {
- assert_matches!(
- overseer_recv(virtual_overseer).await,
- AllMessages::RuntimeApi(RuntimeApiMessage::Request(
- relay_parent,
- RuntimeApiRequest::AvailabilityCores(tx)
- )) => {
- let cores = availability_cores_per_relay_parent.get(&relay_parent)
- .expect(&format!("Availability core for relay parent {:?} does not exist", relay_parent));
-
- tx.send(Ok(cores.clone())).unwrap();
- }
- );
- }
-
- let mut send_peers = Vec::new();
- let mut send_chunks = Vec::new();
- let mut candidates = Vec::new();
- for _ in 0..data_availability.len() {
- let (available, candidate_hash) = assert_matches!(
- overseer_recv(virtual_overseer).await,
- AllMessages::AvailabilityStore(
- AvailabilityStoreMessage::QueryDataAvailability(
- candidate_hash,
- tx,
- )
- ) => {
- let available = data_availability.get(&candidate_hash)
- .expect(&format!("No data availability for candidate {:?}", candidate_hash));
-
- tx.send(*available).unwrap();
- (available, candidate_hash)
- }
- );
-
- if !available {
- continue;
- }
-
- candidates.push(candidate_hash);
- if let Some((pov, persisted)) = chunk_data_per_candidate.get(&candidate_hash) {
- let chunks = make_erasure_chunks(persisted.clone(), validator_public.len(), pov.clone());
-
- for _ in 0..chunks.len() {
- let chunk = assert_matches!(
- overseer_recv(virtual_overseer).await,
- AllMessages::AvailabilityStore(
- AvailabilityStoreMessage::QueryChunk(
- candidate_hash,
- index,
- tx,
- )
- ) => {
- tracing::trace!("Query chunk {} for candidate {:?}", index, candidate_hash);
- let chunk = chunks[index as usize].clone();
- tx.send(Some(chunk.clone())).unwrap();
- chunk
- }
- );
-
- if let Some(peers) = send_chunks_to.get(&candidate_hash) {
- send_peers.push(peers.clone());
- send_chunks.push(chunk);
- }
- }
-
- }
- }
-
- expect_chunks_network_message(virtual_overseer, &send_peers, &candidates, &send_chunks).await;
-}
-
-async fn setup_peer_with_view(
- virtual_overseer: &mut test_helpers::TestSubsystemContextHandle,
- peer: PeerId,
- view: View,
-) {
- overseer_send(virtual_overseer, NetworkBridgeEvent::PeerConnected(peer.clone(), ObservedRole::Full)).await;
-
- overseer_send(virtual_overseer, NetworkBridgeEvent::PeerViewChange(peer, view)).await;
-}
-
-async fn peer_send_message(
- virtual_overseer: &mut test_helpers::TestSubsystemContextHandle,
- peer: PeerId,
- message: AvailabilityGossipMessage,
- expected_reputation_change: Rep,
-) {
- overseer_send(virtual_overseer, NetworkBridgeEvent::PeerMessage(peer.clone(), chunk_protocol_message(message))).await;
-
- assert_matches!(
- overseer_recv(virtual_overseer).await,
- AllMessages::NetworkBridge(
- NetworkBridgeMessage::ReportPeer(
- rep_peer,
- rep,
- )
- ) => {
- assert_eq!(peer, rep_peer);
- assert_eq!(expected_reputation_change, rep);
- }
- );
-}
-
-#[test]
-fn check_views() {
- let test_state = TestState::default();
-
- let peer_a = PeerId::random();
- let peer_a_2 = peer_a.clone();
- let peer_b = PeerId::random();
- let peer_b_2 = peer_b.clone();
- assert_ne!(&peer_a, &peer_b);
-
- let keystore = test_state.keystore.clone();
- let current = test_state.relay_parent;
- let ancestors = test_state.ancestors.clone();
-
- let state = test_harness(keystore, move |test_harness| async move {
- let mut virtual_overseer = test_harness.virtual_overseer;
-
- let TestState {
- validator_public,
- relay_parent: current,
- ancestors,
- candidates,
- pov_blocks,
- ..
- } = test_state.clone();
-
- let genesis = Hash::repeat_byte(0xAA);
- change_our_view(
- &mut virtual_overseer,
- our_view![current],
- &validator_public,
- vec![ancestors[0], genesis],
- hashmap! { current => 1, genesis => 1 },
- hashmap! {
- ancestors[0] => vec![
- occupied_core_from_candidate(&candidates[0]),
- occupied_core_from_candidate(&candidates[1]),
- ],
- current => vec![
- CoreState::Occupied(OccupiedCore {
- next_up_on_available: None,
- occupied_since: 0,
- time_out_at: 10,
- next_up_on_time_out: None,
- availability: Default::default(),
- group_responsible: GroupIndex::from(0),
- candidate_hash: candidates[0].hash(),
- candidate_descriptor: candidates[0].descriptor().clone(),
- }),
- CoreState::Free,
- CoreState::Free,
- CoreState::Occupied(OccupiedCore {
- next_up_on_available: None,
- occupied_since: 1,
- time_out_at: 7,
- next_up_on_time_out: None,
- availability: Default::default(),
- group_responsible: GroupIndex::from(0),
- candidate_hash: candidates[1].hash(),
- candidate_descriptor: candidates[1].descriptor().clone(),
- }),
- CoreState::Free,
- CoreState::Free,
- ]
- },
- hashmap! {
- candidates[0].hash() => true,
- candidates[1].hash() => false,
- },
- hashmap! {
- candidates[0].hash() => (pov_blocks[0].clone(), test_state.persisted_validation_data.clone()),
- },
- hashmap! {},
- ).await;
-
- // setup peer a with interest in current
- setup_peer_with_view(&mut virtual_overseer, peer_a.clone(), view![current]).await;
-
- // setup peer b with interest in ancestor
- setup_peer_with_view(&mut virtual_overseer, peer_b.clone(), view![ancestors[0]]).await;
- });
-
- assert_matches! {
- state,
- ProtocolState {
- peer_views,
- view,
- ..
- } => {
- assert_eq!(
- peer_views,
- hashmap! {
- peer_a_2 => view![current],
- peer_b_2 => view![ancestors[0]],
- },
- );
- assert_eq!(view, our_view![current]);
- }
- };
-}
-
-#[test]
-fn reputation_verification() {
- let test_state = TestState::default();
-
- let peer_a = PeerId::random();
- let peer_b = PeerId::random();
- assert_ne!(&peer_a, &peer_b);
-
- let keystore = test_state.keystore.clone();
-
- test_harness(keystore, move |test_harness| async move {
- let mut virtual_overseer = test_harness.virtual_overseer;
-
- let TestState {
- relay_parent: current,
- validator_public,
- ancestors,
- candidates,
- pov_blocks,
- ..
- } = test_state.clone();
-
- let valid = make_valid_availability_gossip(
- &test_state,
- 0,
- 2,
- );
-
- change_our_view(
- &mut virtual_overseer,
- our_view![current],
- &validator_public,
- vec![ancestors[0]],
- hashmap! { current => 1 },
- hashmap! {
- current => vec![
- occupied_core_from_candidate(&candidates[0]),
- occupied_core_from_candidate(&candidates[1]),
- ],
- },
- hashmap! { candidates[0].hash() => true, candidates[1].hash() => false },
- hashmap! { candidates[0].hash() => (pov_blocks[0].clone(), test_state.persisted_validation_data.clone())},
- hashmap! {},
- ).await;
-
- // valid (first, from b)
- peer_send_message(&mut virtual_overseer, peer_b.clone(), valid.clone(), BENEFIT_VALID_MESSAGE).await;
-
- // valid (duplicate, from b)
- peer_send_message(&mut virtual_overseer, peer_b.clone(), valid.clone(), COST_PEER_DUPLICATE_MESSAGE).await;
-
- // valid (second, from a)
- peer_send_message(&mut virtual_overseer, peer_a.clone(), valid.clone(), BENEFIT_VALID_MESSAGE).await;
-
- // send the a message again, so we should detect the duplicate
- peer_send_message(&mut virtual_overseer, peer_a.clone(), valid.clone(), COST_PEER_DUPLICATE_MESSAGE).await;
-
- // peer b sends a message before we have the view
- // setup peer a with interest in parent x
- overseer_send(&mut virtual_overseer, NetworkBridgeEvent::PeerDisconnected(peer_b.clone())).await;
-
- overseer_send(&mut virtual_overseer, NetworkBridgeEvent::PeerConnected(peer_b.clone(), ObservedRole::Full)).await;
-
- {
- // send another message
- let valid = make_valid_availability_gossip(&test_state, 1, 2);
-
- // Make peer a and b listen on `current`
- overseer_send(&mut virtual_overseer, NetworkBridgeEvent::PeerViewChange(peer_a.clone(), view![current])).await;
-
- let mut chunks = make_erasure_chunks(
- test_state.persisted_validation_data.clone(),
- validator_public.len(),
- pov_blocks[0].clone(),
- );
-
- // Both peers send us this chunk already
- chunks.remove(2);
-
- let send_peers = chunks.iter().map(|_| vec![peer_a.clone()]).collect::>();
- expect_chunks_network_message(&mut virtual_overseer, &send_peers, &[candidates[0].hash()], &chunks).await;
-
- overseer_send(&mut virtual_overseer, NetworkBridgeEvent::PeerViewChange(peer_b.clone(), view![current])).await;
-
- let send_peers = chunks.iter().map(|_| vec![peer_b.clone()]).collect::>();
- expect_chunks_network_message(&mut virtual_overseer, &send_peers, &[candidates[0].hash()], &chunks).await;
-
- peer_send_message(&mut virtual_overseer, peer_a.clone(), valid.clone(), BENEFIT_VALID_MESSAGE_FIRST).await;
-
- expect_chunks_network_message(
- &mut virtual_overseer,
- &[vec![peer_b.clone()]],
- &[candidates[1].hash()],
- &[valid.erasure_chunk.clone()],
- ).await;
-
- // Let B send the same message
- peer_send_message(&mut virtual_overseer, peer_b.clone(), valid.clone(), BENEFIT_VALID_MESSAGE).await;
- }
- });
-}
-
-#[test]
-fn not_a_live_candidate_is_detected() {
- let test_state = TestState::default();
-
- let peer_a = PeerId::random();
-
- let keystore = test_state.keystore.clone();
-
- test_harness(keystore, move |test_harness| async move {
- let mut virtual_overseer = test_harness.virtual_overseer;
-
- let TestState {
- relay_parent: current,
- validator_public,
- ancestors,
- candidates,
- pov_blocks,
- ..
- } = test_state.clone();
-
- change_our_view(
- &mut virtual_overseer,
- our_view![current],
- &validator_public,
- vec![ancestors[0]],
- hashmap! { current => 1 },
- hashmap! {
- current => vec![
- occupied_core_from_candidate(&candidates[0]),
- ],
- },
- hashmap! { candidates[0].hash() => true },
- hashmap! { candidates[0].hash() => (pov_blocks[0].clone(), test_state.persisted_validation_data.clone())},
- hashmap! {},
- ).await;
-
- let valid = make_valid_availability_gossip(
- &test_state,
- 1,
- 1,
- );
-
- peer_send_message(&mut virtual_overseer, peer_a.clone(), valid.clone(), COST_NOT_A_LIVE_CANDIDATE).await;
- });
-}
-
-#[test]
-fn peer_change_view_before_us() {
- let test_state = TestState::default();
-
- let peer_a = PeerId::random();
-
- let keystore = test_state.keystore.clone();
-
- test_harness(keystore, move |test_harness| async move {
- let mut virtual_overseer = test_harness.virtual_overseer;
-
- let TestState {
- relay_parent: current,
- validator_public,
- ancestors,
- candidates,
- pov_blocks,
- ..
- } = test_state.clone();
-
- setup_peer_with_view(&mut virtual_overseer, peer_a.clone(), view![current]).await;
-
- change_our_view(
- &mut virtual_overseer,
- our_view![current],
- &validator_public,
- vec![ancestors[0]],
- hashmap! { current => 1 },
- hashmap! {
- current => vec![
- occupied_core_from_candidate(&candidates[0]),
- ],
- },
- hashmap! { candidates[0].hash() => true },
- hashmap! { candidates[0].hash() => (pov_blocks[0].clone(), test_state.persisted_validation_data.clone())},
- hashmap! { candidates[0].hash() => vec![peer_a.clone()] },
- ).await;
-
- let valid = make_valid_availability_gossip(
- &test_state,
- 0,
- 0,
- );
-
- // We send peer a all the chunks of candidate0, so we just benefit him for sending a valid message
- peer_send_message(&mut virtual_overseer, peer_a.clone(), valid.clone(), BENEFIT_VALID_MESSAGE).await;
- });
-}
-
-#[test]
-fn candidate_chunks_are_put_into_message_vault_when_candidate_is_first_seen() {
- let test_state = TestState::default();
-
- let peer_a = PeerId::random();
-
- let keystore = test_state.keystore.clone();
-
- test_harness(keystore, move |test_harness| async move {
- let mut virtual_overseer = test_harness.virtual_overseer;
-
- let TestState {
- relay_parent: current,
- validator_public,
- ancestors,
- candidates,
- pov_blocks,
- ..
- } = test_state.clone();
-
- change_our_view(
- &mut virtual_overseer,
- our_view![ancestors[0]],
- &validator_public,
- vec![ancestors[1]],
- hashmap! { ancestors[0] => 1 },
- hashmap! {
- ancestors[0] => vec![
- occupied_core_from_candidate(&candidates[0]),
- ],
- },
- hashmap! { candidates[0].hash() => true },
- hashmap! { candidates[0].hash() => (pov_blocks[0].clone(), test_state.persisted_validation_data.clone())},
- hashmap! {},
- ).await;
-
- change_our_view(
- &mut virtual_overseer,
- our_view![current],
- &validator_public,
- vec![ancestors[0]],
- hashmap! { current => 1 },
- hashmap! {
- current => vec![
- occupied_core_from_candidate(&candidates[0]),
- ],
- },
- hashmap! { candidates[0].hash() => true },
- hashmap! {},
- hashmap! {},
- ).await;
-
- // Let peera connect, we should send him all the chunks of the candidate
- setup_peer_with_view(&mut virtual_overseer, peer_a.clone(), view![current]).await;
-
- let chunks = make_erasure_chunks(
- test_state.persisted_validation_data.clone(),
- validator_public.len(),
- pov_blocks[0].clone(),
- );
- let send_peers = chunks.iter().map(|_| vec![peer_a.clone()]).collect::>();
- expect_chunks_network_message(
- &mut virtual_overseer,
- &send_peers,
- &[candidates[0].hash()],
- &chunks,
- ).await;
- });
-}
-
-#[test]
-fn k_ancestors_in_session() {
- let pool = sp_core::testing::TaskExecutor::new();
- let (mut ctx, mut virtual_overseer) =
- test_helpers::make_subsystem_context::(pool);
-
- const DATA: &[(Hash, SessionIndex)] = &[
- (Hash::repeat_byte(0x32), 3), // relay parent
- (Hash::repeat_byte(0x31), 3), // grand parent
- (Hash::repeat_byte(0x30), 3), // great ...
- (Hash::repeat_byte(0x20), 2),
- (Hash::repeat_byte(0x12), 1),
- (Hash::repeat_byte(0x11), 1),
- (Hash::repeat_byte(0x10), 1),
- ];
- const K: usize = 5;
-
- const EXPECTED: &[Hash] = &[DATA[1].0, DATA[2].0];
-
- let test_fut = async move {
- assert_matches!(
- overseer_recv(&mut virtual_overseer).await,
- AllMessages::ChainApi(ChainApiMessage::Ancestors {
- hash: relay_parent,
- k,
- response_channel: tx,
- }) => {
- assert_eq!(k, K+1);
- assert_eq!(relay_parent, DATA[0].0);
- tx.send(Ok(DATA[1..=k].into_iter().map(|x| x.0).collect::>())).unwrap();
- }
- );
-
- // query the desired session index of the relay parent
- assert_matches!(
- overseer_recv(&mut virtual_overseer).await,
- AllMessages::RuntimeApi(RuntimeApiMessage::Request(
- relay_parent,
- RuntimeApiRequest::SessionIndexForChild(tx),
- )) => {
- assert_eq!(relay_parent, DATA[0].0);
- let session: SessionIndex = DATA[0].1;
- tx.send(Ok(session)).unwrap();
- }
- );
-
- // query ancestors
- for i in 2usize..=(EXPECTED.len() + 1 + 1) {
- assert_matches!(
- overseer_recv(&mut virtual_overseer).await,
- AllMessages::RuntimeApi(RuntimeApiMessage::Request(
- relay_parent,
- RuntimeApiRequest::SessionIndexForChild(tx),
- )) => {
- // query is for ancestor_parent
- let x = &DATA[i];
- assert_eq!(relay_parent, x.0);
- // but needs to yield ancestor_parent's child's session index
- let x = &DATA[i-1];
- tx.send(Ok(x.1)).unwrap();
- }
- );
- }
- };
-
- let sut = async move {
- let ancestors = query_up_to_k_ancestors_in_same_session(&mut ctx, DATA[0].0, K)
- .await
- .unwrap();
- assert_eq!(ancestors, EXPECTED.to_vec());
- };
-
- futures::pin_mut!(test_fut);
- futures::pin_mut!(sut);
-
- executor::block_on(future::join(test_fut, sut).timeout(Duration::from_millis(1000)));
-}
-
-#[test]
-fn clean_up_receipts_cache_unions_ancestors_and_view() {
- let mut state = ProtocolState::default();
-
- let hash_a = [0u8; 32].into();
- let hash_b = [1u8; 32].into();
- let hash_c = [2u8; 32].into();
- let hash_d = [3u8; 32].into();
-
- state.live_under.insert(hash_a, HashSet::new());
- state.live_under.insert(hash_b, HashSet::new());
- state.live_under.insert(hash_c, HashSet::new());
- state.live_under.insert(hash_d, HashSet::new());
-
- state.per_relay_parent.insert(hash_a, PerRelayParent {
- ancestors: vec![hash_b],
- live_candidates: HashSet::new(),
- span: PerLeafSpan::new(Arc::new(jaeger::Span::Disabled), "test"),
- });
-
- state.per_relay_parent.insert(hash_c, PerRelayParent {
- ancestors: Vec::new(),
- live_candidates: HashSet::new(),
- span: PerLeafSpan::new(Arc::new(jaeger::Span::Disabled), "test"),
- });
-
- state.clean_up_live_under_cache();
-
- assert_eq!(state.live_under.len(), 3);
- assert!(state.live_under.contains_key(&hash_a));
- assert!(state.live_under.contains_key(&hash_b));
- assert!(state.live_under.contains_key(&hash_c));
- assert!(!state.live_under.contains_key(&hash_d));
-}
-
-#[test]
-fn remove_relay_parent_only_removes_per_candidate_if_final() {
- let mut state = ProtocolState::default();
-
- let hash_a = [0u8; 32].into();
- let hash_b = [1u8; 32].into();
-
- let candidate_hash_a = CandidateHash([46u8; 32].into());
-
- state.per_relay_parent.insert(hash_a, PerRelayParent {
- ancestors: vec![],
- live_candidates: std::iter::once(candidate_hash_a).collect(),
- span: PerLeafSpan::new(Arc::new(jaeger::Span::Disabled), "test"),
- });
-
- state.per_relay_parent.insert(hash_b, PerRelayParent {
- ancestors: vec![],
- live_candidates: std::iter::once(candidate_hash_a).collect(),
- span: PerLeafSpan::new(Arc::new(jaeger::Span::Disabled), "test"),
- });
-
- state.per_candidate.insert(candidate_hash_a, {
- let mut per_candidate = make_per_candidate();
- per_candidate.live_in = vec![hash_a, hash_b].into_iter().collect();
- per_candidate
- });
-
- state.remove_relay_parent(&hash_a);
-
- assert!(!state.per_relay_parent.contains_key(&hash_a));
- assert!(!state.per_candidate.get(&candidate_hash_a).unwrap().live_in.contains(&hash_a));
- assert!(state.per_candidate.get(&candidate_hash_a).unwrap().live_in.contains(&hash_b));
-
- state.remove_relay_parent(&hash_b);
-
- assert!(!state.per_relay_parent.contains_key(&hash_b));
- assert!(!state.per_candidate.contains_key(&candidate_hash_a));
-}
-
-#[test]
-fn add_relay_parent_includes_all_live_candidates() {
- let relay_parent = [0u8; 32].into();
-
- let mut state = ProtocolState::default();
-
- let ancestor_a = [1u8; 32].into();
-
- let candidate_hash_a = CandidateHash([10u8; 32].into());
- let candidate_hash_b = CandidateHash([11u8; 32].into());
-
- state.per_candidate.insert(candidate_hash_b, make_per_candidate());
-
- let candidates = vec![
- (candidate_hash_a, FetchedLiveCandidate::Fresh(Default::default())),
- (candidate_hash_b, FetchedLiveCandidate::Cached),
- ].into_iter().collect();
-
- state.add_relay_parent(
- relay_parent,
- Vec::new(),
- None,
- candidates,
- vec![ancestor_a],
- PerLeafSpan::new(Arc::new(jaeger::Span::Disabled), "test"),
- );
-
- assert!(
- state.per_candidate.get(&candidate_hash_a).unwrap().live_in.contains(&relay_parent)
- );
- assert!(
- state.per_candidate.get(&candidate_hash_b).unwrap().live_in.contains(&relay_parent)
- );
-
- let per_relay_parent = state.per_relay_parent.get(&relay_parent).unwrap();
-
- assert!(per_relay_parent.live_candidates.contains(&candidate_hash_a));
- assert!(per_relay_parent.live_candidates.contains(&candidate_hash_b));
-}
-
-#[test]
-fn query_pending_availability_at_pulls_from_and_updates_receipts() {
- let hash_a = [0u8; 32].into();
- let hash_b = [1u8; 32].into();
-
- let para_a = ParaId::from(1);
- let para_b = ParaId::from(2);
- let para_c = ParaId::from(3);
-
- let make_candidate = |para_id| {
- let mut candidate = CommittedCandidateReceipt::default();
- candidate.descriptor.para_id = para_id;
- candidate.descriptor.relay_parent = [69u8; 32].into();
- candidate
- };
-
- let candidate_a = make_candidate(para_a);
- let candidate_b = make_candidate(para_b);
- let candidate_c = make_candidate(para_c);
-
- let candidate_hash_a = candidate_a.hash();
- let candidate_hash_b = candidate_b.hash();
- let candidate_hash_c = candidate_c.hash();
-
- // receipts has an initial entry for hash_a but not hash_b.
- let mut receipts = HashMap::new();
- receipts.insert(hash_a, vec![candidate_hash_a, candidate_hash_b].into_iter().collect());
-
- let pool = sp_core::testing::TaskExecutor::new();
-
- let (mut ctx, mut virtual_overseer) =
- test_helpers::make_subsystem_context::(pool);
-
- let test_fut = async move {
- let live_candidates = query_pending_availability_at(
- &mut ctx,
- vec![hash_a, hash_b],
- &mut receipts,
- ).await.unwrap();
-
- // although 'b' is cached from the perspective of hash_a, it gets overwritten when we query what's happening in
- //
- assert_eq!(live_candidates.len(), 3);
- assert_matches!(live_candidates.get(&candidate_hash_a).unwrap(), FetchedLiveCandidate::Cached);
- assert_matches!(live_candidates.get(&candidate_hash_b).unwrap(), FetchedLiveCandidate::Cached);
- assert_matches!(live_candidates.get(&candidate_hash_c).unwrap(), FetchedLiveCandidate::Fresh(_));
-
- assert!(receipts.get(&hash_b).unwrap().contains(&candidate_hash_b));
- assert!(receipts.get(&hash_b).unwrap().contains(&candidate_hash_c));
- };
-
- let answer = async move {
- // hash_a should be answered out of cache, so we should just have
- // queried for hash_b.
- assert_matches!(
- overseer_recv(&mut virtual_overseer).await,
- AllMessages::RuntimeApi(
- RuntimeApiMessage::Request(
- r,
- RuntimeApiRequest::AvailabilityCores(tx),
- )
- ) if r == hash_b => {
- let _ = tx.send(Ok(vec![
- CoreState::Occupied(OccupiedCore {
- next_up_on_available: None,
- occupied_since: 0,
- time_out_at: 0,
- next_up_on_time_out: None,
- availability: Default::default(),
- group_responsible: GroupIndex::from(0),
- candidate_hash: candidate_hash_b,
- candidate_descriptor: candidate_b.descriptor.clone(),
- }),
- CoreState::Occupied(OccupiedCore {
- next_up_on_available: None,
- occupied_since: 0,
- time_out_at: 0,
- next_up_on_time_out: None,
- availability: Default::default(),
- group_responsible: GroupIndex::from(0),
- candidate_hash: candidate_hash_c,
- candidate_descriptor: candidate_c.descriptor.clone(),
- }),
- ]));
- }
- );
- };
-
- futures::pin_mut!(test_fut);
- futures::pin_mut!(answer);
-
- executor::block_on(future::join(test_fut, answer));
-}
-
-#[test]
-fn new_peer_gets_all_chunks_send() {
- let test_state = TestState::default();
-
- let peer_a = PeerId::random();
- let peer_b = PeerId::random();
- assert_ne!(&peer_a, &peer_b);
-
- let keystore = test_state.keystore.clone();
-
- test_harness(keystore, move |test_harness| async move {
- let mut virtual_overseer = test_harness.virtual_overseer;
-
- let TestState {
- relay_parent: current,
- validator_public,
- ancestors,
- candidates,
- pov_blocks,
- ..
- } = test_state.clone();
-
- let valid = make_valid_availability_gossip(
- &test_state,
- 1,
- 2,
- );
-
- change_our_view(
- &mut virtual_overseer,
- our_view![current],
- &validator_public,
- vec![ancestors[0]],
- hashmap! { current => 1 },
- hashmap! {
- current => vec![
- occupied_core_from_candidate(&candidates[0]),
- occupied_core_from_candidate(&candidates[1])
- ],
- },
- hashmap! { candidates[0].hash() => true, candidates[1].hash() => false },
- hashmap! { candidates[0].hash() => (pov_blocks[0].clone(), test_state.persisted_validation_data.clone())},
- hashmap! {},
- ).await;
-
- peer_send_message(&mut virtual_overseer, peer_b.clone(), valid.clone(), BENEFIT_VALID_MESSAGE_FIRST).await;
-
- setup_peer_with_view(&mut virtual_overseer, peer_a.clone(), view![current]).await;
-
- let mut chunks = make_erasure_chunks(
- test_state.persisted_validation_data.clone(),
- validator_public.len(),
- pov_blocks[0].clone(),
- );
-
- chunks.push(valid.erasure_chunk);
-
- let send_peers = chunks.iter().map(|_| vec![peer_a.clone()]).collect::>();
-
- expect_chunks_network_message(
- &mut virtual_overseer,
- &send_peers,
- &[candidates[0].hash(), candidates[1].hash()],
- &chunks,
- ).await;
- });
-}
diff --git a/polkadot/node/network/availability-distribution/src/tests/mock.rs b/polkadot/node/network/availability-distribution/src/tests/mock.rs
new file mode 100644
index 0000000000..9d9896d8fd
--- /dev/null
+++ b/polkadot/node/network/availability-distribution/src/tests/mock.rs
@@ -0,0 +1,148 @@
+// Copyright 2021 Parity Technologies (UK) Ltd.
+// This file is part of Polkadot.
+
+// Polkadot is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// Polkadot is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with Polkadot. If not, see .
+
+
+//! Helper functions and tools to generate mock data useful for testing this subsystem.
+
+use std::sync::Arc;
+
+use sp_keyring::Sr25519Keyring;
+
+use polkadot_erasure_coding::{branches, obtain_chunks_v1 as obtain_chunks};
+use polkadot_primitives::v1::{AvailableData, BlockData, CandidateCommitments, CandidateDescriptor,
+ CandidateHash, CommittedCandidateReceipt, ErasureChunk, GroupIndex, Hash, HeadData, Id
+ as ParaId, OccupiedCore, PersistedValidationData, PoV, SessionInfo,
+ ValidatorIndex
+};
+
+/// Create dummy session info with two validator groups.
+pub fn make_session_info() -> SessionInfo {
+ let validators = vec![
+ Sr25519Keyring::Ferdie, // <- this node, role: validator
+ Sr25519Keyring::Alice,
+ Sr25519Keyring::Bob,
+ Sr25519Keyring::Charlie,
+ Sr25519Keyring::Dave,
+ Sr25519Keyring::Eve,
+ Sr25519Keyring::One,
+ ];
+
+ let validator_groups: Vec> = [vec![5, 0, 3], vec![1, 6, 2, 4]]
+ .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,
+ // Not used values:
+ validators: validators.iter().map(|k| k.public().into()).collect(),
+ 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,
+ }
+}
+
+/// Builder for constructing occupied cores.
+///
+/// Takes all the values we care about and fills the rest with dummy values on `build`.
+pub struct OccupiedCoreBuilder {
+ pub group_responsible: GroupIndex,
+ pub para_id: ParaId,
+ pub relay_parent: Hash,
+}
+
+impl OccupiedCoreBuilder {
+ pub fn build(self) -> (OccupiedCore, (CandidateHash, ErasureChunk)) {
+ let pov = PoV {
+ block_data: BlockData(vec![45, 46, 47]),
+ };
+ let pov_hash = pov.hash();
+ let (erasure_root, chunk) = get_valid_chunk_data(pov.clone());
+ let candidate_receipt = TestCandidateBuilder {
+ para_id: self.para_id,
+ pov_hash,
+ relay_parent: self.relay_parent,
+ erasure_root,
+ ..Default::default()
+ }.build();
+ let core = OccupiedCore {
+ next_up_on_available: None,
+ occupied_since: 0,
+ time_out_at: 0,
+ next_up_on_time_out: None,
+ availability: Default::default(),
+ group_responsible: self.group_responsible,
+ candidate_hash: candidate_receipt.hash(),
+ candidate_descriptor: candidate_receipt.descriptor().clone(),
+ };
+ (core, (candidate_receipt.hash(), chunk))
+ }
+}
+
+#[derive(Default)]
+pub struct TestCandidateBuilder {
+ para_id: ParaId,
+ head_data: HeadData,
+ pov_hash: Hash,
+ relay_parent: Hash,
+ erasure_root: Hash,
+}
+
+impl TestCandidateBuilder {
+ pub fn build(self) -> CommittedCandidateReceipt {
+ CommittedCandidateReceipt {
+ descriptor: CandidateDescriptor {
+ para_id: self.para_id,
+ pov_hash: self.pov_hash,
+ relay_parent: self.relay_parent,
+ erasure_root: self.erasure_root,
+ ..Default::default()
+ },
+ commitments: CandidateCommitments {
+ head_data: self.head_data,
+ ..Default::default()
+ },
+ }
+ }
+}
+
+pub fn get_valid_chunk_data(pov: PoV) -> (Hash, ErasureChunk) {
+ let fake_validator_count = 10;
+ let persisted = PersistedValidationData {
+ parent_head: HeadData(vec![7, 8, 9]),
+ relay_parent_number: Default::default(),
+ max_pov_size: 1024,
+ relay_parent_storage_root: Default::default(),
+ };
+ let available_data = AvailableData {
+ validation_data: persisted, pov: Arc::new(pov),
+ };
+ let chunks = obtain_chunks(fake_validator_count, &available_data).unwrap();
+ let branches = branches(chunks.as_ref());
+ let root = branches.root();
+ let chunk = branches.enumerate()
+ .map(|(index, (proof, chunk))| ErasureChunk {
+ chunk: chunk.to_vec(),
+ index: ValidatorIndex(index as _),
+ proof,
+ })
+ .next().expect("There really should be 10 chunks.");
+ (root, chunk)
+}
diff --git a/polkadot/node/network/availability-distribution/src/tests/mod.rs b/polkadot/node/network/availability-distribution/src/tests/mod.rs
new file mode 100644
index 0000000000..be9d4c7d1e
--- /dev/null
+++ b/polkadot/node/network/availability-distribution/src/tests/mod.rs
@@ -0,0 +1,63 @@
+// Copyright 2021 Parity Technologies (UK) Ltd.
+// This file is part of Polkadot.
+
+// Polkadot is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// Polkadot is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with Polkadot. If not, see .
+
+use futures::{executor, future, Future};
+
+use sp_keystore::SyncCryptoStorePtr;
+
+use polkadot_subsystem_testhelpers as test_helpers;
+
+use super::*;
+
+mod state;
+/// State for test harnesses.
+use state::{TestState, TestHarness};
+
+/// Mock data useful for testing.
+pub(crate) mod mock;
+
+fn test_harness>(
+ keystore: SyncCryptoStorePtr,
+ test_fx: impl FnOnce(TestHarness) -> T,
+) {
+ sp_tracing::try_init_simple();
+
+ let pool = sp_core::testing::TaskExecutor::new();
+ let (context, virtual_overseer) = test_helpers::make_subsystem_context(pool.clone());
+
+ let subsystem = AvailabilityDistributionSubsystem::new(keystore, Default::default());
+ {
+ let subsystem = subsystem.run(context);
+
+ let test_fut = test_fx(TestHarness { virtual_overseer, pool });
+
+ futures::pin_mut!(test_fut);
+ futures::pin_mut!(subsystem);
+
+ executor::block_on(future::select(test_fut, subsystem));
+ }
+}
+
+/// Simple basic check, whether the subsystem works as expected.
+///
+/// Exceptional cases are tested as unit tests in `fetch_task`.
+#[test]
+fn check_basic() {
+ let state = TestState::default();
+ test_harness(state.keystore.clone(), move |harness| {
+ state.run(harness)
+ });
+}
diff --git a/polkadot/node/network/availability-distribution/src/tests/state.rs b/polkadot/node/network/availability-distribution/src/tests/state.rs
new file mode 100644
index 0000000000..1d96dd1adf
--- /dev/null
+++ b/polkadot/node/network/availability-distribution/src/tests/state.rs
@@ -0,0 +1,317 @@
+// Copyright 2021 Parity Technologies (UK) Ltd.
+// This file is part of Polkadot.
+
+// Polkadot is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// Polkadot is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with Polkadot. If not, see .
+
+use std::{collections::{HashMap, HashSet}, sync::Arc, time::Duration};
+
+use polkadot_node_subsystem_util::TimeoutExt;
+use polkadot_subsystem_testhelpers::TestSubsystemContextHandle;
+use smallvec::smallvec;
+
+use futures::{FutureExt, channel::oneshot, SinkExt, channel::mpsc, StreamExt};
+use futures_timer::Delay;
+
+use sc_keystore::LocalKeystore;
+use sp_application_crypto::AppKey;
+use sp_keystore::{SyncCryptoStore, SyncCryptoStorePtr};
+use sp_keyring::Sr25519Keyring;
+use sp_core::{traits::SpawnNamed, testing::TaskExecutor};
+use sc_network as network;
+use sc_network::config as netconfig;
+
+use polkadot_subsystem::{ActiveLeavesUpdate, FromOverseer, OverseerSignal, messages::{AllMessages,
+ AvailabilityDistributionMessage, AvailabilityStoreMessage, NetworkBridgeMessage, RuntimeApiMessage,
+ RuntimeApiRequest}
+};
+use polkadot_primitives::v1::{CandidateHash, CoreState, ErasureChunk, GroupIndex, Hash, Id
+ as ParaId, ScheduledCore, SessionInfo, ValidatorId,
+ ValidatorIndex
+};
+use polkadot_node_network_protocol::{jaeger,
+ request_response::{IncomingRequest, OutgoingRequest, Requests, v1}
+};
+use polkadot_subsystem_testhelpers as test_helpers;
+use test_helpers::SingleItemSink;
+
+use super::mock::{make_session_info, OccupiedCoreBuilder, };
+use crate::LOG_TARGET;
+
+pub struct TestHarness {
+ pub virtual_overseer: test_helpers::TestSubsystemContextHandle,
+ pub pool: TaskExecutor,
+}
+
+/// TestState for mocking execution of this subsystem.
+///
+/// The `Default` instance provides data, which makes the system succeed by providing a couple of
+/// valid occupied cores. You can tune the data before calling `TestState::run`. E.g. modify some
+/// chunks to be invalid, the test will then still pass if you remove that chunk from
+/// `valid_chunks`.
+#[derive(Clone)]
+pub struct TestState {
+ // Simulated relay chain heads:
+ pub relay_chain: Vec,
+ pub chunks: HashMap<(CandidateHash, ValidatorIndex), ErasureChunk>,
+ /// All chunks that are valid and should be accepted.
+ pub valid_chunks: HashSet<(CandidateHash, ValidatorIndex)>,
+ pub session_info: SessionInfo,
+ /// Cores per relay chain block.
+ pub cores: HashMap>,
+ pub keystore: SyncCryptoStorePtr,
+}
+
+impl Default for TestState {
+ fn default() -> Self {
+ let relay_chain: Vec<_> = (1u8..10).map(Hash::repeat_byte).collect();
+ let chain_a = ParaId::from(1);
+ let chain_b = ParaId::from(2);
+
+ let chain_ids = vec![chain_a, chain_b];
+
+ let keystore: SyncCryptoStorePtr = Arc::new(LocalKeystore::in_memory());
+
+ let session_info = make_session_info();
+
+ SyncCryptoStore::sr25519_generate_new(
+ &*keystore,
+ ValidatorId::ID,
+ Some(&Sr25519Keyring::Ferdie.to_seed()),
+ )
+ .expect("Insert key into keystore");
+
+ let (cores, chunks) = {
+ let mut cores = HashMap::new();
+ let mut chunks = HashMap::new();
+
+ cores.insert(relay_chain[0],
+ vec![
+ CoreState::Scheduled(ScheduledCore {
+ para_id: chain_ids[0],
+ collator: None,
+ }),
+ CoreState::Scheduled(ScheduledCore {
+ para_id: chain_ids[1],
+ collator: None,
+ }),
+ ]
+ );
+
+ let heads = {
+ let mut advanced = relay_chain.iter();
+ advanced.next();
+ relay_chain.iter().zip(advanced)
+ };
+ for (relay_parent, relay_child) in heads {
+ let (p_cores, p_chunks): (Vec<_>, Vec<_>) = chain_ids.iter().enumerate()
+ .map(|(i, para_id)| {
+ let (core, chunk) = OccupiedCoreBuilder {
+ group_responsible: GroupIndex(i as _),
+ para_id: *para_id,
+ relay_parent: relay_parent.clone(),
+ }.build();
+ (CoreState::Occupied(core), chunk)
+ }
+ )
+ .unzip();
+ cores.insert(relay_child.clone(), p_cores);
+ // Skip chunks for our own group (won't get fetched):
+ let mut chunks_other_groups = p_chunks.into_iter();
+ chunks_other_groups.next();
+ for (validator_index, chunk) in chunks_other_groups {
+ chunks.insert((validator_index, chunk.index), chunk);
+ }
+ }
+ (cores, chunks)
+ };
+ Self {
+ relay_chain,
+ valid_chunks: chunks.clone().keys().map(Clone::clone).collect(),
+ chunks,
+ session_info,
+ cores,
+ keystore,
+ }
+ }
+}
+
+impl TestState {
+
+ /// Run, but fail after some timeout.
+ pub async fn run(self, harness: TestHarness) {
+ // Make sure test won't run forever.
+ let f = self.run_inner(harness.pool, harness.virtual_overseer).timeout(Duration::from_secs(10));
+ assert!(f.await.is_some(), "Test ran into timeout");
+ }
+
+ /// Run tests with the given mock values in `TestState`.
+ ///
+ /// This will simply advance through the simulated chain and examines whether the subsystem
+ /// behaves as expected: It will succeed if all valid chunks of other backing groups get stored
+ /// and no other.
+ async fn run_inner(self, executor: TaskExecutor, virtual_overseer: TestSubsystemContextHandle) {
+ // We skip genesis here (in reality ActiveLeavesUpdate can also skip a block:
+ let updates = {
+ let mut advanced = self.relay_chain.iter();
+ advanced.next();
+ self
+ .relay_chain.iter().zip(advanced)
+ .map(|(old, new)| ActiveLeavesUpdate {
+ activated: smallvec![(new.clone(), Arc::new(jaeger::Span::Disabled))],
+ deactivated: smallvec![old.clone()],
+ }).collect::>()
+ };
+
+ // We should be storing all valid chunks during execution:
+ //
+ // Test will fail if this does not happen until timeout.
+ let mut remaining_stores = self.valid_chunks.len();
+
+ let TestSubsystemContextHandle { tx, mut rx } = virtual_overseer;
+
+ // Spawning necessary as incoming queue can only hold a single item, we don't want to dead
+ // lock ;-)
+ let update_tx = tx.clone();
+ executor.spawn("Sending active leaves updates", async move {
+ for update in updates {
+ overseer_signal(
+ update_tx.clone(),
+ OverseerSignal::ActiveLeaves(update)
+ ).await;
+ // We need to give the subsystem a little time to do its job, otherwise it will
+ // cancel jobs as obsolete:
+ Delay::new(Duration::from_millis(20)).await;
+ }
+ }.boxed()
+ );
+
+ while remaining_stores > 0
+ {
+ tracing::trace!(target: LOG_TARGET, remaining_stores, "Stores left to go");
+ let msg = overseer_recv(&mut rx).await;
+ match msg {
+ AllMessages::NetworkBridge(NetworkBridgeMessage::SendRequests(reqs)) => {
+ for req in reqs {
+ // Forward requests:
+ let in_req = to_incoming_req(&executor, req);
+
+ executor.spawn("Request forwarding",
+ overseer_send(
+ tx.clone(),
+ AvailabilityDistributionMessage::AvailabilityFetchingRequest(in_req)
+ ).boxed()
+ );
+ }
+ }
+ AllMessages::AvailabilityStore(AvailabilityStoreMessage::QueryChunk(candidate_hash, validator_index, tx)) => {
+ let chunk = self.chunks.get(&(candidate_hash, validator_index));
+ tx.send(chunk.map(Clone::clone))
+ .expect("Receiver is expected to be alive");
+ }
+ AllMessages::AvailabilityStore(AvailabilityStoreMessage::StoreChunk{candidate_hash, chunk, tx, ..}) => {
+ assert!(
+ self.valid_chunks.contains(&(candidate_hash, chunk.index)),
+ "Only valid chunks should ever get stored."
+ );
+ tx.send(Ok(()))
+ .expect("Receiver is expected to be alive");
+ tracing::trace!(target: LOG_TARGET, "'Stored' fetched chunk.");
+ remaining_stores -= 1;
+ }
+ AllMessages::RuntimeApi(RuntimeApiMessage::Request(hash, req)) => {
+ match req {
+ RuntimeApiRequest::SessionIndexForChild(tx) => {
+ // Always session index 1 for now:
+ tx.send(Ok(1))
+ .expect("Receiver should still be alive");
+ }
+ RuntimeApiRequest::SessionInfo(_, tx) => {
+ tx.send(Ok(Some(self.session_info.clone())))
+ .expect("Receiver should be alive.");
+ }
+ RuntimeApiRequest::AvailabilityCores(tx) => {
+ tracing::trace!(target: LOG_TARGET, cores= ?self.cores[&hash], hash = ?hash, "Sending out cores for hash");
+ tx.send(Ok(self.cores[&hash].clone()))
+ .expect("Receiver should still be alive");
+ }
+ _ => {
+ panic!("Unexpected runtime request: {:?}", req);
+ }
+ }
+ }
+ _ => {
+ panic!("Unexpected message received: {:?}", msg);
+ }
+ }
+ }
+ }
+}
+
+
+
+async fn overseer_signal(
+ mut tx: SingleItemSink>,
+ msg: impl Into,
+) {
+ let msg = msg.into();
+ tracing::trace!(target: LOG_TARGET, msg = ?msg, "sending message");
+ tx.send(FromOverseer::Signal(msg))
+ .await
+ .expect("Test subsystem no longer live");
+}
+
+async fn overseer_send(
+ mut tx: SingleItemSink>,
+ msg: impl Into,
+) {
+ let msg = msg.into();
+ tracing::trace!(target: LOG_TARGET, msg = ?msg, "sending message");
+ tx.send(FromOverseer::Communication { msg }).await
+ .expect("Test subsystem no longer live");
+ tracing::trace!(target: LOG_TARGET, "sent message");
+}
+
+
+async fn overseer_recv(
+ rx: &mut mpsc::UnboundedReceiver,
+) -> AllMessages {
+ tracing::trace!(target: LOG_TARGET, "waiting for message ...");
+ rx.next().await.expect("Test subsystem no longer live")
+}
+
+fn to_incoming_req(
+ executor: &TaskExecutor,
+ outgoing: Requests
+) -> IncomingRequest {
+ match outgoing {
+ Requests::AvailabilityFetching(OutgoingRequest { payload, pending_response, .. }) => {
+ let (tx, rx): (oneshot::Sender, oneshot::Receiver<_>)
+ = oneshot::channel();
+ executor.spawn("Message forwarding", async {
+ let response = rx.await;
+ let payload = response.expect("Unexpected canceled request").result;
+ pending_response.send(payload.map_err(|_| network::RequestFailure::Refused))
+ .expect("Sending response is expected to work");
+ }.boxed()
+ );
+
+ IncomingRequest::new(
+ // We don't really care:
+ network::PeerId::random(),
+ payload,
+ tx
+ )
+ }
+ }
+}
diff --git a/polkadot/node/subsystem-test-helpers/src/lib.rs b/polkadot/node/subsystem-test-helpers/src/lib.rs
index 512d761afe..71d9631fd8 100644
--- a/polkadot/node/subsystem-test-helpers/src/lib.rs
+++ b/polkadot/node/subsystem-test-helpers/src/lib.rs
@@ -51,6 +51,13 @@ enum SinkState {
/// The sink half of a single-item sink that does not resolve until the item has been read.
pub struct SingleItemSink(Arc>>);
+// Derive clone not possible, as it puts `Clone` constraint on `T` which is not sensible here.
+impl Clone for SingleItemSink {
+ fn clone(&self) -> Self {
+ Self(self.0.clone())
+ }
+}
+
/// The stream half of a single-item sink.
pub struct SingleItemStream(Arc>>);
@@ -213,8 +220,14 @@ impl SubsystemContext
/// A handle for interacting with the subsystem context.
pub struct TestSubsystemContextHandle {
- tx: SingleItemSink>,
- rx: mpsc::UnboundedReceiver,
+ /// Direct access to sender of messages.
+ ///
+ /// Useful for shared ownership situations (one can have multiple senders, but only one
+ /// receiver.
+ pub tx: SingleItemSink>,
+
+ /// Direct access to the receiver.
+ pub rx: mpsc::UnboundedReceiver,
}
impl TestSubsystemContextHandle {