mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-13 14:01:06 +00:00
cleanup more tests and spaces (#3288)
* cleanup more tests and spaces * oops
This commit is contained in:
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,4 +1,4 @@
|
||||
// Copyright 2020 Parity Technologies (UK) Ltd.
|
||||
// Copyright 2020-2021 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Polkadot.
|
||||
|
||||
// Polkadot is free software: you can redistribute it and/or modify
|
||||
@@ -53,6 +53,9 @@ use std::path::PathBuf;
|
||||
|
||||
use async_trait::async_trait;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
const LOG_TARGET: &'static str = "parachain::candidate-validation";
|
||||
|
||||
/// Configuration for the candidate validation subsystem
|
||||
@@ -584,624 +587,3 @@ impl metrics::Metrics for Metrics {
|
||||
Ok(Metrics(Some(metrics)))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use polkadot_node_subsystem_test_helpers as test_helpers;
|
||||
use polkadot_primitives::v1::{HeadData, UpwardMessage};
|
||||
use sp_core::testing::TaskExecutor;
|
||||
use futures::executor;
|
||||
use assert_matches::assert_matches;
|
||||
use sp_keyring::Sr25519Keyring;
|
||||
|
||||
fn collator_sign(descriptor: &mut CandidateDescriptor, collator: Sr25519Keyring) {
|
||||
descriptor.collator = collator.public().into();
|
||||
let payload = polkadot_primitives::v1::collator_signature_payload(
|
||||
&descriptor.relay_parent,
|
||||
&descriptor.para_id,
|
||||
&descriptor.persisted_validation_data_hash,
|
||||
&descriptor.pov_hash,
|
||||
&descriptor.validation_code_hash,
|
||||
);
|
||||
|
||||
descriptor.signature = collator.sign(&payload[..]).into();
|
||||
assert!(descriptor.check_collator_signature().is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn correctly_checks_included_assumption() {
|
||||
let validation_data: PersistedValidationData = Default::default();
|
||||
let validation_code: ValidationCode = vec![1, 2, 3].into();
|
||||
|
||||
let persisted_validation_data_hash = validation_data.hash();
|
||||
let relay_parent = [2; 32].into();
|
||||
let para_id = 5.into();
|
||||
|
||||
let mut candidate = CandidateDescriptor::default();
|
||||
candidate.relay_parent = relay_parent;
|
||||
candidate.persisted_validation_data_hash = persisted_validation_data_hash;
|
||||
candidate.para_id = para_id;
|
||||
|
||||
let pool = TaskExecutor::new();
|
||||
let (mut ctx, mut ctx_handle) = test_helpers::make_subsystem_context(pool.clone());
|
||||
|
||||
let (check_fut, check_result) = check_assumption_validation_data(
|
||||
&mut ctx,
|
||||
&candidate,
|
||||
OccupiedCoreAssumption::Included,
|
||||
).remote_handle();
|
||||
|
||||
let test_fut = async move {
|
||||
assert_matches!(
|
||||
ctx_handle.recv().await,
|
||||
AllMessages::RuntimeApi(RuntimeApiMessage::Request(
|
||||
rp,
|
||||
RuntimeApiRequest::PersistedValidationData(
|
||||
p,
|
||||
OccupiedCoreAssumption::Included,
|
||||
tx
|
||||
),
|
||||
)) => {
|
||||
assert_eq!(rp, relay_parent);
|
||||
assert_eq!(p, para_id);
|
||||
|
||||
let _ = tx.send(Ok(Some(validation_data.clone())));
|
||||
}
|
||||
);
|
||||
|
||||
assert_matches!(
|
||||
ctx_handle.recv().await,
|
||||
AllMessages::RuntimeApi(RuntimeApiMessage::Request(
|
||||
rp,
|
||||
RuntimeApiRequest::ValidationCode(p, OccupiedCoreAssumption::Included, tx)
|
||||
)) => {
|
||||
assert_eq!(rp, relay_parent);
|
||||
assert_eq!(p, para_id);
|
||||
|
||||
let _ = tx.send(Ok(Some(validation_code.clone())));
|
||||
}
|
||||
);
|
||||
|
||||
assert_matches!(check_result.await.unwrap(), AssumptionCheckOutcome::Matches(o, v) => {
|
||||
assert_eq!(o, validation_data);
|
||||
assert_eq!(v, validation_code);
|
||||
});
|
||||
};
|
||||
|
||||
let test_fut = future::join(test_fut, check_fut);
|
||||
executor::block_on(test_fut);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn correctly_checks_timed_out_assumption() {
|
||||
let validation_data: PersistedValidationData = Default::default();
|
||||
let validation_code: ValidationCode = vec![1, 2, 3].into();
|
||||
|
||||
let persisted_validation_data_hash = validation_data.hash();
|
||||
let relay_parent = [2; 32].into();
|
||||
let para_id = 5.into();
|
||||
|
||||
let mut candidate = CandidateDescriptor::default();
|
||||
candidate.relay_parent = relay_parent;
|
||||
candidate.persisted_validation_data_hash = persisted_validation_data_hash;
|
||||
candidate.para_id = para_id;
|
||||
|
||||
let pool = TaskExecutor::new();
|
||||
let (mut ctx, mut ctx_handle) = test_helpers::make_subsystem_context(pool.clone());
|
||||
|
||||
let (check_fut, check_result) = check_assumption_validation_data(
|
||||
&mut ctx,
|
||||
&candidate,
|
||||
OccupiedCoreAssumption::TimedOut,
|
||||
).remote_handle();
|
||||
|
||||
let test_fut = async move {
|
||||
assert_matches!(
|
||||
ctx_handle.recv().await,
|
||||
AllMessages::RuntimeApi(RuntimeApiMessage::Request(
|
||||
rp,
|
||||
RuntimeApiRequest::PersistedValidationData(
|
||||
p,
|
||||
OccupiedCoreAssumption::TimedOut,
|
||||
tx
|
||||
),
|
||||
)) => {
|
||||
assert_eq!(rp, relay_parent);
|
||||
assert_eq!(p, para_id);
|
||||
|
||||
let _ = tx.send(Ok(Some(validation_data.clone())));
|
||||
}
|
||||
);
|
||||
|
||||
assert_matches!(
|
||||
ctx_handle.recv().await,
|
||||
AllMessages::RuntimeApi(RuntimeApiMessage::Request(
|
||||
rp,
|
||||
RuntimeApiRequest::ValidationCode(p, OccupiedCoreAssumption::TimedOut, tx)
|
||||
)) => {
|
||||
assert_eq!(rp, relay_parent);
|
||||
assert_eq!(p, para_id);
|
||||
|
||||
let _ = tx.send(Ok(Some(validation_code.clone())));
|
||||
}
|
||||
);
|
||||
|
||||
assert_matches!(check_result.await.unwrap(), AssumptionCheckOutcome::Matches(o, v) => {
|
||||
assert_eq!(o, validation_data);
|
||||
assert_eq!(v, validation_code);
|
||||
});
|
||||
};
|
||||
|
||||
let test_fut = future::join(test_fut, check_fut);
|
||||
executor::block_on(test_fut);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn check_is_bad_request_if_no_validation_data() {
|
||||
let validation_data: PersistedValidationData = Default::default();
|
||||
let persisted_validation_data_hash = validation_data.hash();
|
||||
let relay_parent = [2; 32].into();
|
||||
let para_id = 5.into();
|
||||
|
||||
let mut candidate = CandidateDescriptor::default();
|
||||
candidate.relay_parent = relay_parent;
|
||||
candidate.persisted_validation_data_hash = persisted_validation_data_hash;
|
||||
candidate.para_id = para_id;
|
||||
|
||||
let pool = TaskExecutor::new();
|
||||
let (mut ctx, mut ctx_handle) = test_helpers::make_subsystem_context(pool.clone());
|
||||
|
||||
let (check_fut, check_result) = check_assumption_validation_data(
|
||||
&mut ctx,
|
||||
&candidate,
|
||||
OccupiedCoreAssumption::Included,
|
||||
).remote_handle();
|
||||
|
||||
let test_fut = async move {
|
||||
assert_matches!(
|
||||
ctx_handle.recv().await,
|
||||
AllMessages::RuntimeApi(RuntimeApiMessage::Request(
|
||||
rp,
|
||||
RuntimeApiRequest::PersistedValidationData(
|
||||
p,
|
||||
OccupiedCoreAssumption::Included,
|
||||
tx
|
||||
),
|
||||
)) => {
|
||||
assert_eq!(rp, relay_parent);
|
||||
assert_eq!(p, para_id);
|
||||
|
||||
let _ = tx.send(Ok(None));
|
||||
}
|
||||
);
|
||||
|
||||
assert_matches!(check_result.await.unwrap(), AssumptionCheckOutcome::BadRequest);
|
||||
};
|
||||
|
||||
let test_fut = future::join(test_fut, check_fut);
|
||||
executor::block_on(test_fut);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn check_is_bad_request_if_no_validation_code() {
|
||||
let validation_data: PersistedValidationData = Default::default();
|
||||
let persisted_validation_data_hash = validation_data.hash();
|
||||
let relay_parent = [2; 32].into();
|
||||
let para_id = 5.into();
|
||||
|
||||
let mut candidate = CandidateDescriptor::default();
|
||||
candidate.relay_parent = relay_parent;
|
||||
candidate.persisted_validation_data_hash = persisted_validation_data_hash;
|
||||
candidate.para_id = para_id;
|
||||
|
||||
let pool = TaskExecutor::new();
|
||||
let (mut ctx, mut ctx_handle) = test_helpers::make_subsystem_context(pool.clone());
|
||||
|
||||
let (check_fut, check_result) = check_assumption_validation_data(
|
||||
&mut ctx,
|
||||
&candidate,
|
||||
OccupiedCoreAssumption::TimedOut,
|
||||
).remote_handle();
|
||||
|
||||
let test_fut = async move {
|
||||
assert_matches!(
|
||||
ctx_handle.recv().await,
|
||||
AllMessages::RuntimeApi(RuntimeApiMessage::Request(
|
||||
rp,
|
||||
RuntimeApiRequest::PersistedValidationData(
|
||||
p,
|
||||
OccupiedCoreAssumption::TimedOut,
|
||||
tx
|
||||
),
|
||||
)) => {
|
||||
assert_eq!(rp, relay_parent);
|
||||
assert_eq!(p, para_id);
|
||||
|
||||
let _ = tx.send(Ok(Some(validation_data.clone())));
|
||||
}
|
||||
);
|
||||
|
||||
assert_matches!(
|
||||
ctx_handle.recv().await,
|
||||
AllMessages::RuntimeApi(RuntimeApiMessage::Request(
|
||||
rp,
|
||||
RuntimeApiRequest::ValidationCode(p, OccupiedCoreAssumption::TimedOut, tx)
|
||||
)) => {
|
||||
assert_eq!(rp, relay_parent);
|
||||
assert_eq!(p, para_id);
|
||||
|
||||
let _ = tx.send(Ok(None));
|
||||
}
|
||||
);
|
||||
|
||||
assert_matches!(check_result.await.unwrap(), AssumptionCheckOutcome::BadRequest);
|
||||
};
|
||||
|
||||
let test_fut = future::join(test_fut, check_fut);
|
||||
executor::block_on(test_fut);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn check_does_not_match() {
|
||||
let validation_data: PersistedValidationData = Default::default();
|
||||
let relay_parent = [2; 32].into();
|
||||
let para_id = 5.into();
|
||||
|
||||
let mut candidate = CandidateDescriptor::default();
|
||||
candidate.relay_parent = relay_parent;
|
||||
candidate.persisted_validation_data_hash = [3; 32].into();
|
||||
candidate.para_id = para_id;
|
||||
|
||||
let pool = TaskExecutor::new();
|
||||
let (mut ctx, mut ctx_handle) = test_helpers::make_subsystem_context(pool.clone());
|
||||
|
||||
let (check_fut, check_result) = check_assumption_validation_data(
|
||||
&mut ctx,
|
||||
&candidate,
|
||||
OccupiedCoreAssumption::Included,
|
||||
).remote_handle();
|
||||
|
||||
let test_fut = async move {
|
||||
assert_matches!(
|
||||
ctx_handle.recv().await,
|
||||
AllMessages::RuntimeApi(RuntimeApiMessage::Request(
|
||||
rp,
|
||||
RuntimeApiRequest::PersistedValidationData(
|
||||
p,
|
||||
OccupiedCoreAssumption::Included,
|
||||
tx
|
||||
),
|
||||
)) => {
|
||||
assert_eq!(rp, relay_parent);
|
||||
assert_eq!(p, para_id);
|
||||
|
||||
let _ = tx.send(Ok(Some(validation_data.clone())));
|
||||
}
|
||||
);
|
||||
|
||||
assert_matches!(check_result.await.unwrap(), AssumptionCheckOutcome::DoesNotMatch);
|
||||
};
|
||||
|
||||
let test_fut = future::join(test_fut, check_fut);
|
||||
executor::block_on(test_fut);
|
||||
}
|
||||
|
||||
struct MockValidatorBackend {
|
||||
result: Result<WasmValidationResult, ValidationError>,
|
||||
}
|
||||
|
||||
impl MockValidatorBackend {
|
||||
fn with_hardcoded_result(result: Result<WasmValidationResult, ValidationError>) -> Self {
|
||||
Self {
|
||||
result,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl ValidationBackend for MockValidatorBackend {
|
||||
async fn validate_candidate(
|
||||
&mut self,
|
||||
_raw_validation_code: Vec<u8>,
|
||||
_params: ValidationParams
|
||||
) -> Result<WasmValidationResult, ValidationError> {
|
||||
self.result.clone()
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn candidate_validation_ok_is_ok() {
|
||||
let validation_data = PersistedValidationData { max_pov_size: 1024, ..Default::default() };
|
||||
|
||||
let pov = PoV { block_data: BlockData(vec![1; 32]) };
|
||||
let head_data = HeadData(vec![1, 1, 1]);
|
||||
let validation_code = ValidationCode(vec![2; 16]);
|
||||
|
||||
let mut descriptor = CandidateDescriptor::default();
|
||||
descriptor.pov_hash = pov.hash();
|
||||
descriptor.para_head = head_data.hash();
|
||||
descriptor.validation_code_hash = validation_code.hash();
|
||||
collator_sign(&mut descriptor, Sr25519Keyring::Alice);
|
||||
|
||||
let check = perform_basic_checks(
|
||||
&descriptor,
|
||||
validation_data.max_pov_size,
|
||||
&pov,
|
||||
&validation_code,
|
||||
);
|
||||
assert!(check.is_ok());
|
||||
|
||||
let validation_result = WasmValidationResult {
|
||||
head_data,
|
||||
new_validation_code: Some(vec![2, 2, 2].into()),
|
||||
upward_messages: Vec::new(),
|
||||
horizontal_messages: Vec::new(),
|
||||
processed_downward_messages: 0,
|
||||
hrmp_watermark: 0,
|
||||
};
|
||||
|
||||
let v = executor::block_on(validate_candidate_exhaustive(
|
||||
MockValidatorBackend::with_hardcoded_result(Ok(validation_result)),
|
||||
validation_data.clone(),
|
||||
validation_code,
|
||||
descriptor,
|
||||
Arc::new(pov),
|
||||
&Default::default(),
|
||||
))
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
|
||||
assert_matches!(v, ValidationResult::Valid(outputs, used_validation_data) => {
|
||||
assert_eq!(outputs.head_data, HeadData(vec![1, 1, 1]));
|
||||
assert_eq!(outputs.upward_messages, Vec::<UpwardMessage>::new());
|
||||
assert_eq!(outputs.horizontal_messages, Vec::new());
|
||||
assert_eq!(outputs.new_validation_code, Some(vec![2, 2, 2].into()));
|
||||
assert_eq!(outputs.hrmp_watermark, 0);
|
||||
assert_eq!(used_validation_data, validation_data);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn candidate_validation_bad_return_is_invalid() {
|
||||
let validation_data = PersistedValidationData { max_pov_size: 1024, ..Default::default() };
|
||||
|
||||
let pov = PoV { block_data: BlockData(vec![1; 32]) };
|
||||
let validation_code = ValidationCode(vec![2; 16]);
|
||||
|
||||
let mut descriptor = CandidateDescriptor::default();
|
||||
descriptor.pov_hash = pov.hash();
|
||||
descriptor.validation_code_hash = validation_code.hash();
|
||||
collator_sign(&mut descriptor, Sr25519Keyring::Alice);
|
||||
|
||||
let check = perform_basic_checks(
|
||||
&descriptor,
|
||||
validation_data.max_pov_size,
|
||||
&pov,
|
||||
&validation_code,
|
||||
);
|
||||
assert!(check.is_ok());
|
||||
|
||||
let v = executor::block_on(validate_candidate_exhaustive(
|
||||
MockValidatorBackend::with_hardcoded_result(
|
||||
Err(ValidationError::InvalidCandidate(WasmInvalidCandidate::AmbigiousWorkerDeath))
|
||||
),
|
||||
validation_data,
|
||||
validation_code,
|
||||
descriptor,
|
||||
Arc::new(pov),
|
||||
&Default::default(),
|
||||
))
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
|
||||
assert_matches!(v, ValidationResult::Invalid(InvalidCandidate::ExecutionError(_)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn candidate_validation_timeout_is_internal_error() {
|
||||
let validation_data = PersistedValidationData { max_pov_size: 1024, ..Default::default() };
|
||||
|
||||
let pov = PoV { block_data: BlockData(vec![1; 32]) };
|
||||
let validation_code = ValidationCode(vec![2; 16]);
|
||||
|
||||
let mut descriptor = CandidateDescriptor::default();
|
||||
descriptor.pov_hash = pov.hash();
|
||||
descriptor.validation_code_hash = validation_code.hash();
|
||||
collator_sign(&mut descriptor, Sr25519Keyring::Alice);
|
||||
|
||||
let check = perform_basic_checks(
|
||||
&descriptor,
|
||||
validation_data.max_pov_size,
|
||||
&pov,
|
||||
&validation_code,
|
||||
);
|
||||
assert!(check.is_ok());
|
||||
|
||||
let v = executor::block_on(validate_candidate_exhaustive(
|
||||
MockValidatorBackend::with_hardcoded_result(
|
||||
Err(ValidationError::InvalidCandidate(WasmInvalidCandidate::HardTimeout)),
|
||||
),
|
||||
validation_data,
|
||||
validation_code,
|
||||
descriptor,
|
||||
Arc::new(pov),
|
||||
&Default::default(),
|
||||
))
|
||||
.unwrap();
|
||||
|
||||
assert_matches!(v, Ok(ValidationResult::Invalid(InvalidCandidate::Timeout)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn candidate_validation_code_mismatch_is_invalid() {
|
||||
let validation_data = PersistedValidationData { max_pov_size: 1024, ..Default::default() };
|
||||
|
||||
let pov = PoV { block_data: BlockData(vec![1; 32]) };
|
||||
let validation_code = ValidationCode(vec![2; 16]);
|
||||
|
||||
let mut descriptor = CandidateDescriptor::default();
|
||||
descriptor.pov_hash = pov.hash();
|
||||
descriptor.validation_code_hash = ValidationCode(vec![1; 16]).hash();
|
||||
collator_sign(&mut descriptor, Sr25519Keyring::Alice);
|
||||
|
||||
let check = perform_basic_checks(
|
||||
&descriptor,
|
||||
validation_data.max_pov_size,
|
||||
&pov,
|
||||
&validation_code,
|
||||
);
|
||||
assert_matches!(check, Err(InvalidCandidate::CodeHashMismatch));
|
||||
|
||||
let v = executor::block_on(validate_candidate_exhaustive(
|
||||
MockValidatorBackend::with_hardcoded_result(
|
||||
Err(ValidationError::InvalidCandidate(WasmInvalidCandidate::HardTimeout)),
|
||||
),
|
||||
validation_data,
|
||||
validation_code,
|
||||
descriptor,
|
||||
Arc::new(pov),
|
||||
&Default::default(),
|
||||
))
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
|
||||
assert_matches!(v, ValidationResult::Invalid(InvalidCandidate::CodeHashMismatch));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn compressed_code_works() {
|
||||
let validation_data = PersistedValidationData { max_pov_size: 1024, ..Default::default() };
|
||||
let pov = PoV { block_data: BlockData(vec![1; 32]) };
|
||||
let head_data = HeadData(vec![1, 1, 1]);
|
||||
|
||||
let raw_code = vec![2u8; 16];
|
||||
let validation_code = sp_maybe_compressed_blob::compress(
|
||||
&raw_code,
|
||||
VALIDATION_CODE_BOMB_LIMIT,
|
||||
)
|
||||
.map(ValidationCode)
|
||||
.unwrap();
|
||||
|
||||
let mut descriptor = CandidateDescriptor::default();
|
||||
descriptor.pov_hash = pov.hash();
|
||||
descriptor.para_head = head_data.hash();
|
||||
descriptor.validation_code_hash = validation_code.hash();
|
||||
collator_sign(&mut descriptor, Sr25519Keyring::Alice);
|
||||
|
||||
let validation_result = WasmValidationResult {
|
||||
head_data,
|
||||
new_validation_code: None,
|
||||
upward_messages: Vec::new(),
|
||||
horizontal_messages: Vec::new(),
|
||||
processed_downward_messages: 0,
|
||||
hrmp_watermark: 0,
|
||||
};
|
||||
|
||||
let v = executor::block_on(validate_candidate_exhaustive(
|
||||
MockValidatorBackend::with_hardcoded_result(Ok(validation_result)),
|
||||
validation_data,
|
||||
validation_code,
|
||||
descriptor,
|
||||
Arc::new(pov),
|
||||
&Default::default(),
|
||||
))
|
||||
.unwrap();
|
||||
|
||||
assert_matches!(v, Ok(ValidationResult::Valid(_, _)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn code_decompression_failure_is_invalid() {
|
||||
let validation_data = PersistedValidationData { max_pov_size: 1024, ..Default::default() };
|
||||
let pov = PoV { block_data: BlockData(vec![1; 32]) };
|
||||
let head_data = HeadData(vec![1, 1, 1]);
|
||||
|
||||
let raw_code = vec![2u8; VALIDATION_CODE_BOMB_LIMIT + 1];
|
||||
let validation_code = sp_maybe_compressed_blob::compress(
|
||||
&raw_code,
|
||||
VALIDATION_CODE_BOMB_LIMIT + 1,
|
||||
)
|
||||
.map(ValidationCode)
|
||||
.unwrap();
|
||||
|
||||
let mut descriptor = CandidateDescriptor::default();
|
||||
descriptor.pov_hash = pov.hash();
|
||||
descriptor.para_head = head_data.hash();
|
||||
descriptor.validation_code_hash = validation_code.hash();
|
||||
collator_sign(&mut descriptor, Sr25519Keyring::Alice);
|
||||
|
||||
let validation_result = WasmValidationResult {
|
||||
head_data,
|
||||
new_validation_code: None,
|
||||
upward_messages: Vec::new(),
|
||||
horizontal_messages: Vec::new(),
|
||||
processed_downward_messages: 0,
|
||||
hrmp_watermark: 0,
|
||||
};
|
||||
|
||||
let v = executor::block_on(validate_candidate_exhaustive(
|
||||
MockValidatorBackend::with_hardcoded_result(Ok(validation_result)),
|
||||
validation_data,
|
||||
validation_code,
|
||||
descriptor,
|
||||
Arc::new(pov),
|
||||
&Default::default(),
|
||||
))
|
||||
.unwrap();
|
||||
|
||||
assert_matches!(
|
||||
v,
|
||||
Ok(ValidationResult::Invalid(InvalidCandidate::CodeDecompressionFailure))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pov_decompression_failure_is_invalid() {
|
||||
let validation_data = PersistedValidationData {
|
||||
max_pov_size: POV_BOMB_LIMIT as u32,
|
||||
..Default::default()
|
||||
};
|
||||
let head_data = HeadData(vec![1, 1, 1]);
|
||||
|
||||
let raw_block_data = vec![2u8; POV_BOMB_LIMIT + 1];
|
||||
let pov = sp_maybe_compressed_blob::compress(
|
||||
&raw_block_data,
|
||||
POV_BOMB_LIMIT + 1,
|
||||
)
|
||||
.map(|raw| PoV { block_data: BlockData(raw) })
|
||||
.unwrap();
|
||||
|
||||
let validation_code = ValidationCode(vec![2; 16]);
|
||||
|
||||
let mut descriptor = CandidateDescriptor::default();
|
||||
descriptor.pov_hash = pov.hash();
|
||||
descriptor.para_head = head_data.hash();
|
||||
descriptor.validation_code_hash = validation_code.hash();
|
||||
collator_sign(&mut descriptor, Sr25519Keyring::Alice);
|
||||
|
||||
let validation_result = WasmValidationResult {
|
||||
head_data,
|
||||
new_validation_code: None,
|
||||
upward_messages: Vec::new(),
|
||||
horizontal_messages: Vec::new(),
|
||||
processed_downward_messages: 0,
|
||||
hrmp_watermark: 0,
|
||||
};
|
||||
|
||||
let v = executor::block_on(validate_candidate_exhaustive(
|
||||
MockValidatorBackend::with_hardcoded_result(Ok(validation_result)),
|
||||
validation_data,
|
||||
validation_code,
|
||||
descriptor,
|
||||
Arc::new(pov),
|
||||
&Default::default(),
|
||||
))
|
||||
.unwrap();
|
||||
|
||||
assert_matches!(
|
||||
v,
|
||||
Ok(ValidationResult::Invalid(InvalidCandidate::PoVDecompressionFailure))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,633 @@
|
||||
// Copyright 2020-2021 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Polkadot.
|
||||
|
||||
// Polkadot is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Polkadot is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Polkadot. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use super::*;
|
||||
use polkadot_node_subsystem_test_helpers as test_helpers;
|
||||
use polkadot_primitives::v1::{HeadData, UpwardMessage};
|
||||
use sp_core::testing::TaskExecutor;
|
||||
use futures::executor;
|
||||
use assert_matches::assert_matches;
|
||||
use sp_keyring::Sr25519Keyring;
|
||||
|
||||
fn collator_sign(descriptor: &mut CandidateDescriptor, collator: Sr25519Keyring) {
|
||||
descriptor.collator = collator.public().into();
|
||||
let payload = polkadot_primitives::v1::collator_signature_payload(
|
||||
&descriptor.relay_parent,
|
||||
&descriptor.para_id,
|
||||
&descriptor.persisted_validation_data_hash,
|
||||
&descriptor.pov_hash,
|
||||
&descriptor.validation_code_hash,
|
||||
);
|
||||
|
||||
descriptor.signature = collator.sign(&payload[..]).into();
|
||||
assert!(descriptor.check_collator_signature().is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn correctly_checks_included_assumption() {
|
||||
let validation_data: PersistedValidationData = Default::default();
|
||||
let validation_code: ValidationCode = vec![1, 2, 3].into();
|
||||
|
||||
let persisted_validation_data_hash = validation_data.hash();
|
||||
let relay_parent = [2; 32].into();
|
||||
let para_id = 5.into();
|
||||
|
||||
let mut candidate = CandidateDescriptor::default();
|
||||
candidate.relay_parent = relay_parent;
|
||||
candidate.persisted_validation_data_hash = persisted_validation_data_hash;
|
||||
candidate.para_id = para_id;
|
||||
|
||||
let pool = TaskExecutor::new();
|
||||
let (mut ctx, mut ctx_handle) = test_helpers::make_subsystem_context(pool.clone());
|
||||
|
||||
let (check_fut, check_result) = check_assumption_validation_data(
|
||||
&mut ctx,
|
||||
&candidate,
|
||||
OccupiedCoreAssumption::Included,
|
||||
).remote_handle();
|
||||
|
||||
let test_fut = async move {
|
||||
assert_matches!(
|
||||
ctx_handle.recv().await,
|
||||
AllMessages::RuntimeApi(RuntimeApiMessage::Request(
|
||||
rp,
|
||||
RuntimeApiRequest::PersistedValidationData(
|
||||
p,
|
||||
OccupiedCoreAssumption::Included,
|
||||
tx
|
||||
),
|
||||
)) => {
|
||||
assert_eq!(rp, relay_parent);
|
||||
assert_eq!(p, para_id);
|
||||
|
||||
let _ = tx.send(Ok(Some(validation_data.clone())));
|
||||
}
|
||||
);
|
||||
|
||||
assert_matches!(
|
||||
ctx_handle.recv().await,
|
||||
AllMessages::RuntimeApi(RuntimeApiMessage::Request(
|
||||
rp,
|
||||
RuntimeApiRequest::ValidationCode(p, OccupiedCoreAssumption::Included, tx)
|
||||
)) => {
|
||||
assert_eq!(rp, relay_parent);
|
||||
assert_eq!(p, para_id);
|
||||
|
||||
let _ = tx.send(Ok(Some(validation_code.clone())));
|
||||
}
|
||||
);
|
||||
|
||||
assert_matches!(check_result.await.unwrap(), AssumptionCheckOutcome::Matches(o, v) => {
|
||||
assert_eq!(o, validation_data);
|
||||
assert_eq!(v, validation_code);
|
||||
});
|
||||
};
|
||||
|
||||
let test_fut = future::join(test_fut, check_fut);
|
||||
executor::block_on(test_fut);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn correctly_checks_timed_out_assumption() {
|
||||
let validation_data: PersistedValidationData = Default::default();
|
||||
let validation_code: ValidationCode = vec![1, 2, 3].into();
|
||||
|
||||
let persisted_validation_data_hash = validation_data.hash();
|
||||
let relay_parent = [2; 32].into();
|
||||
let para_id = 5.into();
|
||||
|
||||
let mut candidate = CandidateDescriptor::default();
|
||||
candidate.relay_parent = relay_parent;
|
||||
candidate.persisted_validation_data_hash = persisted_validation_data_hash;
|
||||
candidate.para_id = para_id;
|
||||
|
||||
let pool = TaskExecutor::new();
|
||||
let (mut ctx, mut ctx_handle) = test_helpers::make_subsystem_context(pool.clone());
|
||||
|
||||
let (check_fut, check_result) = check_assumption_validation_data(
|
||||
&mut ctx,
|
||||
&candidate,
|
||||
OccupiedCoreAssumption::TimedOut,
|
||||
).remote_handle();
|
||||
|
||||
let test_fut = async move {
|
||||
assert_matches!(
|
||||
ctx_handle.recv().await,
|
||||
AllMessages::RuntimeApi(RuntimeApiMessage::Request(
|
||||
rp,
|
||||
RuntimeApiRequest::PersistedValidationData(
|
||||
p,
|
||||
OccupiedCoreAssumption::TimedOut,
|
||||
tx
|
||||
),
|
||||
)) => {
|
||||
assert_eq!(rp, relay_parent);
|
||||
assert_eq!(p, para_id);
|
||||
|
||||
let _ = tx.send(Ok(Some(validation_data.clone())));
|
||||
}
|
||||
);
|
||||
|
||||
assert_matches!(
|
||||
ctx_handle.recv().await,
|
||||
AllMessages::RuntimeApi(RuntimeApiMessage::Request(
|
||||
rp,
|
||||
RuntimeApiRequest::ValidationCode(p, OccupiedCoreAssumption::TimedOut, tx)
|
||||
)) => {
|
||||
assert_eq!(rp, relay_parent);
|
||||
assert_eq!(p, para_id);
|
||||
|
||||
let _ = tx.send(Ok(Some(validation_code.clone())));
|
||||
}
|
||||
);
|
||||
|
||||
assert_matches!(check_result.await.unwrap(), AssumptionCheckOutcome::Matches(o, v) => {
|
||||
assert_eq!(o, validation_data);
|
||||
assert_eq!(v, validation_code);
|
||||
});
|
||||
};
|
||||
|
||||
let test_fut = future::join(test_fut, check_fut);
|
||||
executor::block_on(test_fut);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn check_is_bad_request_if_no_validation_data() {
|
||||
let validation_data: PersistedValidationData = Default::default();
|
||||
let persisted_validation_data_hash = validation_data.hash();
|
||||
let relay_parent = [2; 32].into();
|
||||
let para_id = 5.into();
|
||||
|
||||
let mut candidate = CandidateDescriptor::default();
|
||||
candidate.relay_parent = relay_parent;
|
||||
candidate.persisted_validation_data_hash = persisted_validation_data_hash;
|
||||
candidate.para_id = para_id;
|
||||
|
||||
let pool = TaskExecutor::new();
|
||||
let (mut ctx, mut ctx_handle) = test_helpers::make_subsystem_context(pool.clone());
|
||||
|
||||
let (check_fut, check_result) = check_assumption_validation_data(
|
||||
&mut ctx,
|
||||
&candidate,
|
||||
OccupiedCoreAssumption::Included,
|
||||
).remote_handle();
|
||||
|
||||
let test_fut = async move {
|
||||
assert_matches!(
|
||||
ctx_handle.recv().await,
|
||||
AllMessages::RuntimeApi(RuntimeApiMessage::Request(
|
||||
rp,
|
||||
RuntimeApiRequest::PersistedValidationData(
|
||||
p,
|
||||
OccupiedCoreAssumption::Included,
|
||||
tx
|
||||
),
|
||||
)) => {
|
||||
assert_eq!(rp, relay_parent);
|
||||
assert_eq!(p, para_id);
|
||||
|
||||
let _ = tx.send(Ok(None));
|
||||
}
|
||||
);
|
||||
|
||||
assert_matches!(check_result.await.unwrap(), AssumptionCheckOutcome::BadRequest);
|
||||
};
|
||||
|
||||
let test_fut = future::join(test_fut, check_fut);
|
||||
executor::block_on(test_fut);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn check_is_bad_request_if_no_validation_code() {
|
||||
let validation_data: PersistedValidationData = Default::default();
|
||||
let persisted_validation_data_hash = validation_data.hash();
|
||||
let relay_parent = [2; 32].into();
|
||||
let para_id = 5.into();
|
||||
|
||||
let mut candidate = CandidateDescriptor::default();
|
||||
candidate.relay_parent = relay_parent;
|
||||
candidate.persisted_validation_data_hash = persisted_validation_data_hash;
|
||||
candidate.para_id = para_id;
|
||||
|
||||
let pool = TaskExecutor::new();
|
||||
let (mut ctx, mut ctx_handle) = test_helpers::make_subsystem_context(pool.clone());
|
||||
|
||||
let (check_fut, check_result) = check_assumption_validation_data(
|
||||
&mut ctx,
|
||||
&candidate,
|
||||
OccupiedCoreAssumption::TimedOut,
|
||||
).remote_handle();
|
||||
|
||||
let test_fut = async move {
|
||||
assert_matches!(
|
||||
ctx_handle.recv().await,
|
||||
AllMessages::RuntimeApi(RuntimeApiMessage::Request(
|
||||
rp,
|
||||
RuntimeApiRequest::PersistedValidationData(
|
||||
p,
|
||||
OccupiedCoreAssumption::TimedOut,
|
||||
tx
|
||||
),
|
||||
)) => {
|
||||
assert_eq!(rp, relay_parent);
|
||||
assert_eq!(p, para_id);
|
||||
|
||||
let _ = tx.send(Ok(Some(validation_data.clone())));
|
||||
}
|
||||
);
|
||||
|
||||
assert_matches!(
|
||||
ctx_handle.recv().await,
|
||||
AllMessages::RuntimeApi(RuntimeApiMessage::Request(
|
||||
rp,
|
||||
RuntimeApiRequest::ValidationCode(p, OccupiedCoreAssumption::TimedOut, tx)
|
||||
)) => {
|
||||
assert_eq!(rp, relay_parent);
|
||||
assert_eq!(p, para_id);
|
||||
|
||||
let _ = tx.send(Ok(None));
|
||||
}
|
||||
);
|
||||
|
||||
assert_matches!(check_result.await.unwrap(), AssumptionCheckOutcome::BadRequest);
|
||||
};
|
||||
|
||||
let test_fut = future::join(test_fut, check_fut);
|
||||
executor::block_on(test_fut);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn check_does_not_match() {
|
||||
let validation_data: PersistedValidationData = Default::default();
|
||||
let relay_parent = [2; 32].into();
|
||||
let para_id = 5.into();
|
||||
|
||||
let mut candidate = CandidateDescriptor::default();
|
||||
candidate.relay_parent = relay_parent;
|
||||
candidate.persisted_validation_data_hash = [3; 32].into();
|
||||
candidate.para_id = para_id;
|
||||
|
||||
let pool = TaskExecutor::new();
|
||||
let (mut ctx, mut ctx_handle) = test_helpers::make_subsystem_context(pool.clone());
|
||||
|
||||
let (check_fut, check_result) = check_assumption_validation_data(
|
||||
&mut ctx,
|
||||
&candidate,
|
||||
OccupiedCoreAssumption::Included,
|
||||
).remote_handle();
|
||||
|
||||
let test_fut = async move {
|
||||
assert_matches!(
|
||||
ctx_handle.recv().await,
|
||||
AllMessages::RuntimeApi(RuntimeApiMessage::Request(
|
||||
rp,
|
||||
RuntimeApiRequest::PersistedValidationData(
|
||||
p,
|
||||
OccupiedCoreAssumption::Included,
|
||||
tx
|
||||
),
|
||||
)) => {
|
||||
assert_eq!(rp, relay_parent);
|
||||
assert_eq!(p, para_id);
|
||||
|
||||
let _ = tx.send(Ok(Some(validation_data.clone())));
|
||||
}
|
||||
);
|
||||
|
||||
assert_matches!(check_result.await.unwrap(), AssumptionCheckOutcome::DoesNotMatch);
|
||||
};
|
||||
|
||||
let test_fut = future::join(test_fut, check_fut);
|
||||
executor::block_on(test_fut);
|
||||
}
|
||||
|
||||
struct MockValidatorBackend {
|
||||
result: Result<WasmValidationResult, ValidationError>,
|
||||
}
|
||||
|
||||
impl MockValidatorBackend {
|
||||
fn with_hardcoded_result(result: Result<WasmValidationResult, ValidationError>) -> Self {
|
||||
Self {
|
||||
result,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl ValidationBackend for MockValidatorBackend {
|
||||
async fn validate_candidate(
|
||||
&mut self,
|
||||
_raw_validation_code: Vec<u8>,
|
||||
_params: ValidationParams
|
||||
) -> Result<WasmValidationResult, ValidationError> {
|
||||
self.result.clone()
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn candidate_validation_ok_is_ok() {
|
||||
let validation_data = PersistedValidationData { max_pov_size: 1024, ..Default::default() };
|
||||
|
||||
let pov = PoV { block_data: BlockData(vec![1; 32]) };
|
||||
let head_data = HeadData(vec![1, 1, 1]);
|
||||
let validation_code = ValidationCode(vec![2; 16]);
|
||||
|
||||
let mut descriptor = CandidateDescriptor::default();
|
||||
descriptor.pov_hash = pov.hash();
|
||||
descriptor.para_head = head_data.hash();
|
||||
descriptor.validation_code_hash = validation_code.hash();
|
||||
collator_sign(&mut descriptor, Sr25519Keyring::Alice);
|
||||
|
||||
let check = perform_basic_checks(
|
||||
&descriptor,
|
||||
validation_data.max_pov_size,
|
||||
&pov,
|
||||
&validation_code,
|
||||
);
|
||||
assert!(check.is_ok());
|
||||
|
||||
let validation_result = WasmValidationResult {
|
||||
head_data,
|
||||
new_validation_code: Some(vec![2, 2, 2].into()),
|
||||
upward_messages: Vec::new(),
|
||||
horizontal_messages: Vec::new(),
|
||||
processed_downward_messages: 0,
|
||||
hrmp_watermark: 0,
|
||||
};
|
||||
|
||||
let v = executor::block_on(validate_candidate_exhaustive(
|
||||
MockValidatorBackend::with_hardcoded_result(Ok(validation_result)),
|
||||
validation_data.clone(),
|
||||
validation_code,
|
||||
descriptor,
|
||||
Arc::new(pov),
|
||||
&Default::default(),
|
||||
))
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
|
||||
assert_matches!(v, ValidationResult::Valid(outputs, used_validation_data) => {
|
||||
assert_eq!(outputs.head_data, HeadData(vec![1, 1, 1]));
|
||||
assert_eq!(outputs.upward_messages, Vec::<UpwardMessage>::new());
|
||||
assert_eq!(outputs.horizontal_messages, Vec::new());
|
||||
assert_eq!(outputs.new_validation_code, Some(vec![2, 2, 2].into()));
|
||||
assert_eq!(outputs.hrmp_watermark, 0);
|
||||
assert_eq!(used_validation_data, validation_data);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn candidate_validation_bad_return_is_invalid() {
|
||||
let validation_data = PersistedValidationData { max_pov_size: 1024, ..Default::default() };
|
||||
|
||||
let pov = PoV { block_data: BlockData(vec![1; 32]) };
|
||||
let validation_code = ValidationCode(vec![2; 16]);
|
||||
|
||||
let mut descriptor = CandidateDescriptor::default();
|
||||
descriptor.pov_hash = pov.hash();
|
||||
descriptor.validation_code_hash = validation_code.hash();
|
||||
collator_sign(&mut descriptor, Sr25519Keyring::Alice);
|
||||
|
||||
let check = perform_basic_checks(
|
||||
&descriptor,
|
||||
validation_data.max_pov_size,
|
||||
&pov,
|
||||
&validation_code,
|
||||
);
|
||||
assert!(check.is_ok());
|
||||
|
||||
let v = executor::block_on(validate_candidate_exhaustive(
|
||||
MockValidatorBackend::with_hardcoded_result(
|
||||
Err(ValidationError::InvalidCandidate(WasmInvalidCandidate::AmbigiousWorkerDeath))
|
||||
),
|
||||
validation_data,
|
||||
validation_code,
|
||||
descriptor,
|
||||
Arc::new(pov),
|
||||
&Default::default(),
|
||||
))
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
|
||||
assert_matches!(v, ValidationResult::Invalid(InvalidCandidate::ExecutionError(_)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn candidate_validation_timeout_is_internal_error() {
|
||||
let validation_data = PersistedValidationData { max_pov_size: 1024, ..Default::default() };
|
||||
|
||||
let pov = PoV { block_data: BlockData(vec![1; 32]) };
|
||||
let validation_code = ValidationCode(vec![2; 16]);
|
||||
|
||||
let mut descriptor = CandidateDescriptor::default();
|
||||
descriptor.pov_hash = pov.hash();
|
||||
descriptor.validation_code_hash = validation_code.hash();
|
||||
collator_sign(&mut descriptor, Sr25519Keyring::Alice);
|
||||
|
||||
let check = perform_basic_checks(
|
||||
&descriptor,
|
||||
validation_data.max_pov_size,
|
||||
&pov,
|
||||
&validation_code,
|
||||
);
|
||||
assert!(check.is_ok());
|
||||
|
||||
let v = executor::block_on(validate_candidate_exhaustive(
|
||||
MockValidatorBackend::with_hardcoded_result(
|
||||
Err(ValidationError::InvalidCandidate(WasmInvalidCandidate::HardTimeout)),
|
||||
),
|
||||
validation_data,
|
||||
validation_code,
|
||||
descriptor,
|
||||
Arc::new(pov),
|
||||
&Default::default(),
|
||||
))
|
||||
.unwrap();
|
||||
|
||||
assert_matches!(v, Ok(ValidationResult::Invalid(InvalidCandidate::Timeout)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn candidate_validation_code_mismatch_is_invalid() {
|
||||
let validation_data = PersistedValidationData { max_pov_size: 1024, ..Default::default() };
|
||||
|
||||
let pov = PoV { block_data: BlockData(vec![1; 32]) };
|
||||
let validation_code = ValidationCode(vec![2; 16]);
|
||||
|
||||
let mut descriptor = CandidateDescriptor::default();
|
||||
descriptor.pov_hash = pov.hash();
|
||||
descriptor.validation_code_hash = ValidationCode(vec![1; 16]).hash();
|
||||
collator_sign(&mut descriptor, Sr25519Keyring::Alice);
|
||||
|
||||
let check = perform_basic_checks(
|
||||
&descriptor,
|
||||
validation_data.max_pov_size,
|
||||
&pov,
|
||||
&validation_code,
|
||||
);
|
||||
assert_matches!(check, Err(InvalidCandidate::CodeHashMismatch));
|
||||
|
||||
let v = executor::block_on(validate_candidate_exhaustive(
|
||||
MockValidatorBackend::with_hardcoded_result(
|
||||
Err(ValidationError::InvalidCandidate(WasmInvalidCandidate::HardTimeout)),
|
||||
),
|
||||
validation_data,
|
||||
validation_code,
|
||||
descriptor,
|
||||
Arc::new(pov),
|
||||
&Default::default(),
|
||||
))
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
|
||||
assert_matches!(v, ValidationResult::Invalid(InvalidCandidate::CodeHashMismatch));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn compressed_code_works() {
|
||||
let validation_data = PersistedValidationData { max_pov_size: 1024, ..Default::default() };
|
||||
let pov = PoV { block_data: BlockData(vec![1; 32]) };
|
||||
let head_data = HeadData(vec![1, 1, 1]);
|
||||
|
||||
let raw_code = vec![2u8; 16];
|
||||
let validation_code = sp_maybe_compressed_blob::compress(
|
||||
&raw_code,
|
||||
VALIDATION_CODE_BOMB_LIMIT,
|
||||
)
|
||||
.map(ValidationCode)
|
||||
.unwrap();
|
||||
|
||||
let mut descriptor = CandidateDescriptor::default();
|
||||
descriptor.pov_hash = pov.hash();
|
||||
descriptor.para_head = head_data.hash();
|
||||
descriptor.validation_code_hash = validation_code.hash();
|
||||
collator_sign(&mut descriptor, Sr25519Keyring::Alice);
|
||||
|
||||
let validation_result = WasmValidationResult {
|
||||
head_data,
|
||||
new_validation_code: None,
|
||||
upward_messages: Vec::new(),
|
||||
horizontal_messages: Vec::new(),
|
||||
processed_downward_messages: 0,
|
||||
hrmp_watermark: 0,
|
||||
};
|
||||
|
||||
let v = executor::block_on(validate_candidate_exhaustive(
|
||||
MockValidatorBackend::with_hardcoded_result(Ok(validation_result)),
|
||||
validation_data,
|
||||
validation_code,
|
||||
descriptor,
|
||||
Arc::new(pov),
|
||||
&Default::default(),
|
||||
))
|
||||
.unwrap();
|
||||
|
||||
assert_matches!(v, Ok(ValidationResult::Valid(_, _)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn code_decompression_failure_is_invalid() {
|
||||
let validation_data = PersistedValidationData { max_pov_size: 1024, ..Default::default() };
|
||||
let pov = PoV { block_data: BlockData(vec![1; 32]) };
|
||||
let head_data = HeadData(vec![1, 1, 1]);
|
||||
|
||||
let raw_code = vec![2u8; VALIDATION_CODE_BOMB_LIMIT + 1];
|
||||
let validation_code = sp_maybe_compressed_blob::compress(
|
||||
&raw_code,
|
||||
VALIDATION_CODE_BOMB_LIMIT + 1,
|
||||
)
|
||||
.map(ValidationCode)
|
||||
.unwrap();
|
||||
|
||||
let mut descriptor = CandidateDescriptor::default();
|
||||
descriptor.pov_hash = pov.hash();
|
||||
descriptor.para_head = head_data.hash();
|
||||
descriptor.validation_code_hash = validation_code.hash();
|
||||
collator_sign(&mut descriptor, Sr25519Keyring::Alice);
|
||||
|
||||
let validation_result = WasmValidationResult {
|
||||
head_data,
|
||||
new_validation_code: None,
|
||||
upward_messages: Vec::new(),
|
||||
horizontal_messages: Vec::new(),
|
||||
processed_downward_messages: 0,
|
||||
hrmp_watermark: 0,
|
||||
};
|
||||
|
||||
let v = executor::block_on(validate_candidate_exhaustive(
|
||||
MockValidatorBackend::with_hardcoded_result(Ok(validation_result)),
|
||||
validation_data,
|
||||
validation_code,
|
||||
descriptor,
|
||||
Arc::new(pov),
|
||||
&Default::default(),
|
||||
))
|
||||
.unwrap();
|
||||
|
||||
assert_matches!(
|
||||
v,
|
||||
Ok(ValidationResult::Invalid(InvalidCandidate::CodeDecompressionFailure))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pov_decompression_failure_is_invalid() {
|
||||
let validation_data = PersistedValidationData {
|
||||
max_pov_size: POV_BOMB_LIMIT as u32,
|
||||
..Default::default()
|
||||
};
|
||||
let head_data = HeadData(vec![1, 1, 1]);
|
||||
|
||||
let raw_block_data = vec![2u8; POV_BOMB_LIMIT + 1];
|
||||
let pov = sp_maybe_compressed_blob::compress(
|
||||
&raw_block_data,
|
||||
POV_BOMB_LIMIT + 1,
|
||||
)
|
||||
.map(|raw| PoV { block_data: BlockData(raw) })
|
||||
.unwrap();
|
||||
|
||||
let validation_code = ValidationCode(vec![2; 16]);
|
||||
|
||||
let mut descriptor = CandidateDescriptor::default();
|
||||
descriptor.pov_hash = pov.hash();
|
||||
descriptor.para_head = head_data.hash();
|
||||
descriptor.validation_code_hash = validation_code.hash();
|
||||
collator_sign(&mut descriptor, Sr25519Keyring::Alice);
|
||||
|
||||
let validation_result = WasmValidationResult {
|
||||
head_data,
|
||||
new_validation_code: None,
|
||||
upward_messages: Vec::new(),
|
||||
horizontal_messages: Vec::new(),
|
||||
processed_downward_messages: 0,
|
||||
hrmp_watermark: 0,
|
||||
};
|
||||
|
||||
let v = executor::block_on(validate_candidate_exhaustive(
|
||||
MockValidatorBackend::with_hardcoded_result(Ok(validation_result)),
|
||||
validation_data,
|
||||
validation_code,
|
||||
descriptor,
|
||||
Arc::new(pov),
|
||||
&Default::default(),
|
||||
))
|
||||
.unwrap();
|
||||
|
||||
assert_matches!(
|
||||
v,
|
||||
Ok(ValidationResult::Invalid(InvalidCandidate::PoVDecompressionFailure))
|
||||
);
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2020 Parity Technologies (UK) Ltd.
|
||||
// Copyright 2020-2021 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Polkadot.
|
||||
|
||||
// Polkadot is free software: you can redistribute it and/or modify
|
||||
@@ -38,6 +38,9 @@ use polkadot_primitives::v1::{Hash, SignedAvailabilityBitfield, SigningContext,
|
||||
use polkadot_node_network_protocol::{v1 as protocol_v1, PeerId, View, UnifiedReputationChange as Rep, OurView};
|
||||
use std::collections::{HashMap, HashSet};
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
const COST_SIGNATURE_INVALID: Rep = Rep::CostMajor("Bitfield signature invalid");
|
||||
const COST_VALIDATOR_INDEX_INVALID: Rep = Rep::CostMajor("Bitfield validator index invalid");
|
||||
const COST_MISSING_PEER_SESSION_KEY: Rep = Rep::CostMinor("Missing peer session key");
|
||||
@@ -809,718 +812,3 @@ impl metrics::Metrics for Metrics {
|
||||
Ok(Metrics(Some(metrics)))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use bitvec::bitvec;
|
||||
use futures::executor;
|
||||
use maplit::hashmap;
|
||||
use polkadot_primitives::v1::{Signed, AvailabilityBitfield, ValidatorIndex};
|
||||
use polkadot_node_subsystem_test_helpers::make_subsystem_context;
|
||||
use polkadot_node_subsystem_util::TimeoutExt;
|
||||
use sp_keystore::{SyncCryptoStorePtr, SyncCryptoStore};
|
||||
use sp_application_crypto::AppKey;
|
||||
use sp_keystore::testing::KeyStore;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use assert_matches::assert_matches;
|
||||
use polkadot_node_network_protocol::{view, ObservedRole, our_view};
|
||||
use polkadot_subsystem::jaeger;
|
||||
|
||||
macro_rules! launch {
|
||||
($fut:expr) => {
|
||||
$fut
|
||||
.timeout(Duration::from_millis(10))
|
||||
.await
|
||||
.expect("10ms is more than enough for sending messages.")
|
||||
};
|
||||
}
|
||||
|
||||
/// A very limited state, only interested in the relay parent of the
|
||||
/// given message, which must be signed by `validator` and a set of peers
|
||||
/// which are also only interested in that relay parent.
|
||||
fn prewarmed_state(
|
||||
validator: ValidatorId,
|
||||
signing_context: SigningContext,
|
||||
known_message: BitfieldGossipMessage,
|
||||
peers: Vec<PeerId>,
|
||||
) -> ProtocolState {
|
||||
let relay_parent = known_message.relay_parent.clone();
|
||||
ProtocolState {
|
||||
per_relay_parent: hashmap! {
|
||||
relay_parent.clone() =>
|
||||
PerRelayParentData {
|
||||
signing_context,
|
||||
validator_set: vec![validator.clone()],
|
||||
one_per_validator: hashmap! {
|
||||
validator.clone() => known_message.clone(),
|
||||
},
|
||||
message_received_from_peer: hashmap!{},
|
||||
message_sent_to_peer: hashmap!{},
|
||||
span: PerLeafSpan::new(Arc::new(jaeger::Span::Disabled), "test"),
|
||||
},
|
||||
},
|
||||
peer_views: peers
|
||||
.into_iter()
|
||||
.map(|peer| (peer, view!(relay_parent)))
|
||||
.collect(),
|
||||
view: our_view!(relay_parent),
|
||||
}
|
||||
}
|
||||
|
||||
fn state_with_view(
|
||||
view: OurView,
|
||||
relay_parent: Hash,
|
||||
) -> (ProtocolState, SigningContext, SyncCryptoStorePtr, ValidatorId) {
|
||||
let mut state = ProtocolState::default();
|
||||
|
||||
let signing_context = SigningContext {
|
||||
session_index: 1,
|
||||
parent_hash: relay_parent.clone(),
|
||||
};
|
||||
|
||||
let keystore : SyncCryptoStorePtr = Arc::new(KeyStore::new());
|
||||
let validator = SyncCryptoStore::sr25519_generate_new(&*keystore, ValidatorId::ID, None)
|
||||
.expect("generating sr25519 key not to fail");
|
||||
|
||||
state.per_relay_parent = view.iter().map(|relay_parent| {(
|
||||
relay_parent.clone(),
|
||||
PerRelayParentData {
|
||||
signing_context: signing_context.clone(),
|
||||
validator_set: vec![validator.clone().into()],
|
||||
one_per_validator: hashmap!{},
|
||||
message_received_from_peer: hashmap!{},
|
||||
message_sent_to_peer: hashmap!{},
|
||||
span: PerLeafSpan::new(Arc::new(jaeger::Span::Disabled), "test"),
|
||||
})
|
||||
}).collect();
|
||||
|
||||
state.view = view;
|
||||
|
||||
(state, signing_context, keystore, validator.into())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn receive_invalid_signature() {
|
||||
let _ = env_logger::builder()
|
||||
.filter(None, log::LevelFilter::Trace)
|
||||
.is_test(true)
|
||||
.try_init();
|
||||
|
||||
let hash_a: Hash = [0; 32].into();
|
||||
|
||||
let peer_a = PeerId::random();
|
||||
let peer_b = PeerId::random();
|
||||
assert_ne!(peer_a, peer_b);
|
||||
|
||||
let signing_context = SigningContext {
|
||||
session_index: 1,
|
||||
parent_hash: hash_a.clone(),
|
||||
};
|
||||
|
||||
// another validator not part of the validatorset
|
||||
let keystore : SyncCryptoStorePtr = Arc::new(KeyStore::new());
|
||||
let malicious = SyncCryptoStore::sr25519_generate_new(&*keystore, ValidatorId::ID, None)
|
||||
.expect("Malicious key created");
|
||||
let validator_0 = SyncCryptoStore::sr25519_generate_new(&*keystore, ValidatorId::ID, None)
|
||||
.expect("key created");
|
||||
let validator_1 = SyncCryptoStore::sr25519_generate_new(&*keystore, ValidatorId::ID, None)
|
||||
.expect("key created");
|
||||
|
||||
let payload = AvailabilityBitfield(bitvec![bitvec::order::Lsb0, u8; 1u8; 32]);
|
||||
let invalid_signed = executor::block_on(Signed::<AvailabilityBitfield>::sign(
|
||||
&keystore,
|
||||
payload.clone(),
|
||||
&signing_context,
|
||||
ValidatorIndex(0),
|
||||
&malicious.into(),
|
||||
)).ok().flatten().expect("should be signed");
|
||||
let invalid_signed_2 = executor::block_on(Signed::<AvailabilityBitfield>::sign(
|
||||
&keystore,
|
||||
payload.clone(),
|
||||
&signing_context,
|
||||
ValidatorIndex(1),
|
||||
&malicious.into(),
|
||||
)).ok().flatten().expect("should be signed");
|
||||
|
||||
let valid_signed = executor::block_on(Signed::<AvailabilityBitfield>::sign(
|
||||
&keystore,
|
||||
payload,
|
||||
&signing_context,
|
||||
ValidatorIndex(0),
|
||||
&validator_0.into(),
|
||||
)).ok().flatten().expect("should be signed");
|
||||
|
||||
let invalid_msg = BitfieldGossipMessage {
|
||||
relay_parent: hash_a.clone(),
|
||||
signed_availability: invalid_signed.clone(),
|
||||
};
|
||||
let invalid_msg_2 = BitfieldGossipMessage {
|
||||
relay_parent: hash_a.clone(),
|
||||
signed_availability: invalid_signed_2.clone(),
|
||||
};
|
||||
let valid_msg = BitfieldGossipMessage {
|
||||
relay_parent: hash_a.clone(),
|
||||
signed_availability: valid_signed.clone(),
|
||||
};
|
||||
|
||||
let pool = sp_core::testing::TaskExecutor::new();
|
||||
let (mut ctx, mut handle) =
|
||||
make_subsystem_context::<BitfieldDistributionMessage, _>(pool);
|
||||
|
||||
let mut state = prewarmed_state(
|
||||
validator_0.into(),
|
||||
signing_context.clone(),
|
||||
valid_msg,
|
||||
vec![peer_b.clone()],
|
||||
);
|
||||
state.per_relay_parent.get_mut(&hash_a)
|
||||
.unwrap()
|
||||
.validator_set
|
||||
.push(validator_1.into());
|
||||
|
||||
executor::block_on(async move {
|
||||
launch!(handle_network_msg(
|
||||
&mut ctx,
|
||||
&mut state,
|
||||
&Default::default(),
|
||||
NetworkBridgeEvent::PeerMessage(peer_b.clone(), invalid_msg.into_network_message()),
|
||||
));
|
||||
|
||||
// reputation doesn't change due to one_job_per_validator check
|
||||
assert!(handle.recv().timeout(Duration::from_millis(10)).await.is_none());
|
||||
|
||||
launch!(handle_network_msg(
|
||||
&mut ctx,
|
||||
&mut state,
|
||||
&Default::default(),
|
||||
NetworkBridgeEvent::PeerMessage(peer_b.clone(), invalid_msg_2.into_network_message()),
|
||||
));
|
||||
// reputation change due to invalid signature
|
||||
assert_matches!(
|
||||
handle.recv().await,
|
||||
AllMessages::NetworkBridge(
|
||||
NetworkBridgeMessage::ReportPeer(peer, rep)
|
||||
) => {
|
||||
assert_eq!(peer, peer_b);
|
||||
assert_eq!(rep, COST_SIGNATURE_INVALID)
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn receive_invalid_validator_index() {
|
||||
let _ = env_logger::builder()
|
||||
.filter(None, log::LevelFilter::Trace)
|
||||
.is_test(true)
|
||||
.try_init();
|
||||
|
||||
let hash_a: Hash = [0; 32].into();
|
||||
let hash_b: Hash = [1; 32].into(); // other
|
||||
|
||||
let peer_a = PeerId::random();
|
||||
let peer_b = PeerId::random();
|
||||
assert_ne!(peer_a, peer_b);
|
||||
|
||||
// validator 0 key pair
|
||||
let (mut state, signing_context, keystore, validator) = state_with_view(our_view![hash_a, hash_b], hash_a.clone());
|
||||
|
||||
state.peer_views.insert(peer_b.clone(), view![hash_a]);
|
||||
|
||||
let payload = AvailabilityBitfield(bitvec![bitvec::order::Lsb0, u8; 1u8; 32]);
|
||||
let signed = executor::block_on(Signed::<AvailabilityBitfield>::sign(
|
||||
&keystore,
|
||||
payload,
|
||||
&signing_context,
|
||||
ValidatorIndex(42),
|
||||
&validator,
|
||||
)).ok().flatten().expect("should be signed");
|
||||
|
||||
let msg = BitfieldGossipMessage {
|
||||
relay_parent: hash_a.clone(),
|
||||
signed_availability: signed.clone(),
|
||||
};
|
||||
|
||||
let pool = sp_core::testing::TaskExecutor::new();
|
||||
let (mut ctx, mut handle) =
|
||||
make_subsystem_context::<BitfieldDistributionMessage, _>(pool);
|
||||
|
||||
executor::block_on(async move {
|
||||
launch!(handle_network_msg(
|
||||
&mut ctx,
|
||||
&mut state,
|
||||
&Default::default(),
|
||||
NetworkBridgeEvent::PeerMessage(peer_b.clone(), msg.into_network_message()),
|
||||
));
|
||||
|
||||
// reputation change due to invalid validator index
|
||||
assert_matches!(
|
||||
handle.recv().await,
|
||||
AllMessages::NetworkBridge(
|
||||
NetworkBridgeMessage::ReportPeer(peer, rep)
|
||||
) => {
|
||||
assert_eq!(peer, peer_b);
|
||||
assert_eq!(rep, COST_VALIDATOR_INDEX_INVALID)
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn receive_duplicate_messages() {
|
||||
let _ = env_logger::builder()
|
||||
.filter(None, log::LevelFilter::Trace)
|
||||
.is_test(true)
|
||||
.try_init();
|
||||
|
||||
let hash_a: Hash = [0; 32].into();
|
||||
let hash_b: Hash = [1; 32].into();
|
||||
|
||||
let peer_a = PeerId::random();
|
||||
let peer_b = PeerId::random();
|
||||
assert_ne!(peer_a, peer_b);
|
||||
|
||||
// validator 0 key pair
|
||||
let (mut state, signing_context, keystore, validator) = state_with_view(our_view![hash_a, hash_b], hash_a.clone());
|
||||
|
||||
// create a signed message by validator 0
|
||||
let payload = AvailabilityBitfield(bitvec![bitvec::order::Lsb0, u8; 1u8; 32]);
|
||||
let signed_bitfield = executor::block_on(Signed::<AvailabilityBitfield>::sign(
|
||||
&keystore,
|
||||
payload,
|
||||
&signing_context,
|
||||
ValidatorIndex(0),
|
||||
&validator,
|
||||
)).ok().flatten().expect("should be signed");
|
||||
|
||||
let msg = BitfieldGossipMessage {
|
||||
relay_parent: hash_a.clone(),
|
||||
signed_availability: signed_bitfield.clone(),
|
||||
};
|
||||
|
||||
let pool = sp_core::testing::TaskExecutor::new();
|
||||
let (mut ctx, mut handle) =
|
||||
make_subsystem_context::<BitfieldDistributionMessage, _>(pool);
|
||||
|
||||
executor::block_on(async move {
|
||||
// send a first message
|
||||
launch!(handle_network_msg(
|
||||
&mut ctx,
|
||||
&mut state,
|
||||
&Default::default(),
|
||||
NetworkBridgeEvent::PeerMessage(
|
||||
peer_b.clone(),
|
||||
msg.clone().into_network_message(),
|
||||
),
|
||||
));
|
||||
|
||||
// none of our peers has any interest in any messages
|
||||
// so we do not receive a network send type message here
|
||||
// but only the one for the next subsystem
|
||||
assert_matches!(
|
||||
handle.recv().await,
|
||||
AllMessages::Provisioner(ProvisionerMessage::ProvisionableData(
|
||||
_,
|
||||
ProvisionableData::Bitfield(hash, signed)
|
||||
)) => {
|
||||
assert_eq!(hash, hash_a);
|
||||
assert_eq!(signed, signed_bitfield)
|
||||
}
|
||||
);
|
||||
|
||||
assert_matches!(
|
||||
handle.recv().await,
|
||||
AllMessages::NetworkBridge(
|
||||
NetworkBridgeMessage::ReportPeer(peer, rep)
|
||||
) => {
|
||||
assert_eq!(peer, peer_b);
|
||||
assert_eq!(rep, BENEFIT_VALID_MESSAGE_FIRST)
|
||||
}
|
||||
);
|
||||
|
||||
// let peer A send the same message again
|
||||
launch!(handle_network_msg(
|
||||
&mut ctx,
|
||||
&mut state,
|
||||
&Default::default(),
|
||||
NetworkBridgeEvent::PeerMessage(
|
||||
peer_a.clone(),
|
||||
msg.clone().into_network_message(),
|
||||
),
|
||||
));
|
||||
|
||||
assert_matches!(
|
||||
handle.recv().await,
|
||||
AllMessages::NetworkBridge(
|
||||
NetworkBridgeMessage::ReportPeer(peer, rep)
|
||||
) => {
|
||||
assert_eq!(peer, peer_a);
|
||||
assert_eq!(rep, BENEFIT_VALID_MESSAGE)
|
||||
}
|
||||
);
|
||||
|
||||
// let peer B send the initial message again
|
||||
launch!(handle_network_msg(
|
||||
&mut ctx,
|
||||
&mut state,
|
||||
&Default::default(),
|
||||
NetworkBridgeEvent::PeerMessage(
|
||||
peer_b.clone(),
|
||||
msg.clone().into_network_message(),
|
||||
),
|
||||
));
|
||||
|
||||
assert_matches!(
|
||||
handle.recv().await,
|
||||
AllMessages::NetworkBridge(
|
||||
NetworkBridgeMessage::ReportPeer(peer, rep)
|
||||
) => {
|
||||
assert_eq!(peer, peer_b);
|
||||
assert_eq!(rep, COST_PEER_DUPLICATE_MESSAGE)
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn do_not_relay_message_twice() {
|
||||
let _ = env_logger::builder()
|
||||
.filter(None, log::LevelFilter::Trace)
|
||||
.is_test(true)
|
||||
.try_init();
|
||||
|
||||
let hash = Hash::random();
|
||||
|
||||
let peer_a = PeerId::random();
|
||||
let peer_b = PeerId::random();
|
||||
assert_ne!(peer_a, peer_b);
|
||||
|
||||
// validator 0 key pair
|
||||
let (mut state, signing_context, keystore, validator) = state_with_view(our_view![hash], hash.clone());
|
||||
|
||||
// create a signed message by validator 0
|
||||
let payload = AvailabilityBitfield(bitvec![bitvec::order::Lsb0, u8; 1u8; 32]);
|
||||
let signed_bitfield = executor::block_on(Signed::<AvailabilityBitfield>::sign(
|
||||
&keystore,
|
||||
payload,
|
||||
&signing_context,
|
||||
ValidatorIndex(0),
|
||||
&validator,
|
||||
)).ok().flatten().expect("should be signed");
|
||||
|
||||
state.peer_views.insert(peer_b.clone(), view![hash]);
|
||||
state.peer_views.insert(peer_a.clone(), view![hash]);
|
||||
|
||||
let msg = BitfieldGossipMessage {
|
||||
relay_parent: hash.clone(),
|
||||
signed_availability: signed_bitfield.clone(),
|
||||
};
|
||||
|
||||
let pool = sp_core::testing::TaskExecutor::new();
|
||||
let (mut ctx, mut handle) =
|
||||
make_subsystem_context::<BitfieldDistributionMessage, _>(pool);
|
||||
|
||||
executor::block_on(async move {
|
||||
relay_message(
|
||||
&mut ctx,
|
||||
state.per_relay_parent.get_mut(&hash).unwrap(),
|
||||
&mut state.peer_views,
|
||||
validator.clone(),
|
||||
msg.clone(),
|
||||
).await;
|
||||
|
||||
assert_matches!(
|
||||
handle.recv().await,
|
||||
AllMessages::Provisioner(ProvisionerMessage::ProvisionableData(
|
||||
_,
|
||||
ProvisionableData::Bitfield(h, signed)
|
||||
)) => {
|
||||
assert_eq!(h, hash);
|
||||
assert_eq!(signed, signed_bitfield)
|
||||
}
|
||||
);
|
||||
|
||||
assert_matches!(
|
||||
handle.recv().await,
|
||||
AllMessages::NetworkBridge(
|
||||
NetworkBridgeMessage::SendValidationMessage(peers, send_msg),
|
||||
) => {
|
||||
assert_eq!(2, peers.len());
|
||||
assert!(peers.contains(&peer_a));
|
||||
assert!(peers.contains(&peer_b));
|
||||
assert_eq!(send_msg, msg.clone().into_validation_protocol());
|
||||
}
|
||||
);
|
||||
|
||||
// Relaying the message a second time shouldn't work.
|
||||
relay_message(
|
||||
&mut ctx,
|
||||
state.per_relay_parent.get_mut(&hash).unwrap(),
|
||||
&mut state.peer_views,
|
||||
validator.clone(),
|
||||
msg.clone(),
|
||||
).await;
|
||||
|
||||
assert_matches!(
|
||||
handle.recv().await,
|
||||
AllMessages::Provisioner(ProvisionerMessage::ProvisionableData(
|
||||
_,
|
||||
ProvisionableData::Bitfield(h, signed)
|
||||
)) => {
|
||||
assert_eq!(h, hash);
|
||||
assert_eq!(signed, signed_bitfield)
|
||||
}
|
||||
);
|
||||
|
||||
// There shouldn't be any other message
|
||||
assert!(handle.recv().timeout(Duration::from_millis(10)).await.is_none());
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn changing_view() {
|
||||
let _ = env_logger::builder()
|
||||
.filter(None, log::LevelFilter::Trace)
|
||||
.is_test(true)
|
||||
.try_init();
|
||||
|
||||
let hash_a: Hash = [0; 32].into();
|
||||
let hash_b: Hash = [1; 32].into();
|
||||
|
||||
let peer_a = PeerId::random();
|
||||
let peer_b = PeerId::random();
|
||||
assert_ne!(peer_a, peer_b);
|
||||
|
||||
// validator 0 key pair
|
||||
let (mut state, signing_context, keystore, validator) = state_with_view(our_view![hash_a, hash_b], hash_a.clone());
|
||||
|
||||
// create a signed message by validator 0
|
||||
let payload = AvailabilityBitfield(bitvec![bitvec::order::Lsb0, u8; 1u8; 32]);
|
||||
let signed_bitfield = executor::block_on(Signed::<AvailabilityBitfield>::sign(
|
||||
&keystore,
|
||||
payload,
|
||||
&signing_context,
|
||||
ValidatorIndex(0),
|
||||
&validator,
|
||||
)).ok().flatten().expect("should be signed");
|
||||
|
||||
let msg = BitfieldGossipMessage {
|
||||
relay_parent: hash_a.clone(),
|
||||
signed_availability: signed_bitfield.clone(),
|
||||
};
|
||||
|
||||
let pool = sp_core::testing::TaskExecutor::new();
|
||||
let (mut ctx, mut handle) =
|
||||
make_subsystem_context::<BitfieldDistributionMessage, _>(pool);
|
||||
|
||||
executor::block_on(async move {
|
||||
launch!(handle_network_msg(
|
||||
&mut ctx,
|
||||
&mut state,
|
||||
&Default::default(),
|
||||
NetworkBridgeEvent::PeerConnected(peer_b.clone(), ObservedRole::Full, None),
|
||||
));
|
||||
|
||||
// make peer b interested
|
||||
launch!(handle_network_msg(
|
||||
&mut ctx,
|
||||
&mut state,
|
||||
&Default::default(),
|
||||
NetworkBridgeEvent::PeerViewChange(peer_b.clone(), view![hash_a, hash_b]),
|
||||
));
|
||||
|
||||
assert!(state.peer_views.contains_key(&peer_b));
|
||||
|
||||
// recv a first message from the network
|
||||
launch!(handle_network_msg(
|
||||
&mut ctx,
|
||||
&mut state,
|
||||
&Default::default(),
|
||||
NetworkBridgeEvent::PeerMessage(
|
||||
peer_b.clone(),
|
||||
msg.clone().into_network_message(),
|
||||
),
|
||||
));
|
||||
|
||||
// gossip to the overseer
|
||||
assert_matches!(
|
||||
handle.recv().await,
|
||||
AllMessages::Provisioner(ProvisionerMessage::ProvisionableData(
|
||||
_,
|
||||
ProvisionableData::Bitfield(hash, signed)
|
||||
)) => {
|
||||
assert_eq!(hash, hash_a);
|
||||
assert_eq!(signed, signed_bitfield)
|
||||
}
|
||||
);
|
||||
|
||||
// reputation change for peer B
|
||||
assert_matches!(
|
||||
handle.recv().await,
|
||||
AllMessages::NetworkBridge(
|
||||
NetworkBridgeMessage::ReportPeer(peer, rep)
|
||||
) => {
|
||||
assert_eq!(peer, peer_b);
|
||||
assert_eq!(rep, BENEFIT_VALID_MESSAGE_FIRST)
|
||||
}
|
||||
);
|
||||
|
||||
launch!(handle_network_msg(
|
||||
&mut ctx,
|
||||
&mut state,
|
||||
&Default::default(),
|
||||
NetworkBridgeEvent::PeerViewChange(peer_b.clone(), view![]),
|
||||
));
|
||||
|
||||
assert!(state.peer_views.contains_key(&peer_b));
|
||||
assert_eq!(
|
||||
state.peer_views.get(&peer_b).expect("Must contain value for peer B"),
|
||||
&view![]
|
||||
);
|
||||
|
||||
// on rx of the same message, since we are not interested,
|
||||
// should give penalty
|
||||
launch!(handle_network_msg(
|
||||
&mut ctx,
|
||||
&mut state,
|
||||
&Default::default(),
|
||||
NetworkBridgeEvent::PeerMessage(
|
||||
peer_b.clone(),
|
||||
msg.clone().into_network_message(),
|
||||
),
|
||||
));
|
||||
|
||||
// reputation change for peer B
|
||||
assert_matches!(
|
||||
handle.recv().await,
|
||||
AllMessages::NetworkBridge(
|
||||
NetworkBridgeMessage::ReportPeer(peer, rep)
|
||||
) => {
|
||||
assert_eq!(peer, peer_b);
|
||||
assert_eq!(rep, COST_PEER_DUPLICATE_MESSAGE)
|
||||
}
|
||||
);
|
||||
|
||||
launch!(handle_network_msg(
|
||||
&mut ctx,
|
||||
&mut state,
|
||||
&Default::default(),
|
||||
NetworkBridgeEvent::PeerDisconnected(peer_b.clone()),
|
||||
));
|
||||
|
||||
// we are not interested in any peers at all anymore
|
||||
state.view = our_view![];
|
||||
|
||||
// on rx of the same message, since we are not interested,
|
||||
// should give penalty
|
||||
launch!(handle_network_msg(
|
||||
&mut ctx,
|
||||
&mut state,
|
||||
&Default::default(),
|
||||
NetworkBridgeEvent::PeerMessage(
|
||||
peer_a.clone(),
|
||||
msg.clone().into_network_message(),
|
||||
),
|
||||
));
|
||||
|
||||
// reputation change for peer B
|
||||
assert_matches!(
|
||||
handle.recv().await,
|
||||
AllMessages::NetworkBridge(
|
||||
NetworkBridgeMessage::ReportPeer(peer, rep)
|
||||
) => {
|
||||
assert_eq!(peer, peer_a);
|
||||
assert_eq!(rep, COST_NOT_IN_VIEW)
|
||||
}
|
||||
);
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn do_not_send_message_back_to_origin() {
|
||||
let _ = env_logger::builder()
|
||||
.filter(None, log::LevelFilter::Trace)
|
||||
.is_test(true)
|
||||
.try_init();
|
||||
|
||||
let hash: Hash = [0; 32].into();
|
||||
|
||||
let peer_a = PeerId::random();
|
||||
let peer_b = PeerId::random();
|
||||
assert_ne!(peer_a, peer_b);
|
||||
|
||||
// validator 0 key pair
|
||||
let (mut state, signing_context, keystore, validator) = state_with_view(our_view![hash], hash);
|
||||
|
||||
// create a signed message by validator 0
|
||||
let payload = AvailabilityBitfield(bitvec![bitvec::order::Lsb0, u8; 1u8; 32]);
|
||||
let signed_bitfield = executor::block_on(Signed::<AvailabilityBitfield>::sign(
|
||||
&keystore,
|
||||
payload,
|
||||
&signing_context,
|
||||
ValidatorIndex(0),
|
||||
&validator,
|
||||
)).ok().flatten().expect("should be signed");
|
||||
|
||||
state.peer_views.insert(peer_b.clone(), view![hash]);
|
||||
state.peer_views.insert(peer_a.clone(), view![hash]);
|
||||
|
||||
let msg = BitfieldGossipMessage {
|
||||
relay_parent: hash.clone(),
|
||||
signed_availability: signed_bitfield.clone(),
|
||||
};
|
||||
|
||||
let pool = sp_core::testing::TaskExecutor::new();
|
||||
let (mut ctx, mut handle) =
|
||||
make_subsystem_context::<BitfieldDistributionMessage, _>(pool);
|
||||
|
||||
executor::block_on(async move {
|
||||
// send a first message
|
||||
launch!(handle_network_msg(
|
||||
&mut ctx,
|
||||
&mut state,
|
||||
&Default::default(),
|
||||
NetworkBridgeEvent::PeerMessage(
|
||||
peer_b.clone(),
|
||||
msg.clone().into_network_message(),
|
||||
),
|
||||
));
|
||||
|
||||
assert_matches!(
|
||||
handle.recv().await,
|
||||
AllMessages::Provisioner(ProvisionerMessage::ProvisionableData(
|
||||
_,
|
||||
ProvisionableData::Bitfield(hash, signed)
|
||||
)) => {
|
||||
assert_eq!(hash, hash);
|
||||
assert_eq!(signed, signed_bitfield)
|
||||
}
|
||||
);
|
||||
|
||||
assert_matches!(
|
||||
handle.recv().await,
|
||||
AllMessages::NetworkBridge(
|
||||
NetworkBridgeMessage::SendValidationMessage(peers, send_msg),
|
||||
) => {
|
||||
assert_eq!(1, peers.len());
|
||||
assert!(peers.contains(&peer_a));
|
||||
assert_eq!(send_msg, msg.clone().into_validation_protocol());
|
||||
}
|
||||
);
|
||||
|
||||
assert_matches!(
|
||||
handle.recv().await,
|
||||
AllMessages::NetworkBridge(
|
||||
NetworkBridgeMessage::ReportPeer(peer, rep)
|
||||
) => {
|
||||
assert_eq!(peer, peer_b);
|
||||
assert_eq!(rep, BENEFIT_VALID_MESSAGE_FIRST)
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,726 @@
|
||||
// Copyright 2020-2021 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Polkadot.
|
||||
|
||||
// Polkadot is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Polkadot is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Polkadot. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use super::*;
|
||||
use bitvec::bitvec;
|
||||
use futures::executor;
|
||||
use maplit::hashmap;
|
||||
use polkadot_primitives::v1::{Signed, AvailabilityBitfield, ValidatorIndex};
|
||||
use polkadot_node_subsystem_test_helpers::make_subsystem_context;
|
||||
use polkadot_node_subsystem_util::TimeoutExt;
|
||||
use sp_keystore::{SyncCryptoStorePtr, SyncCryptoStore};
|
||||
use sp_application_crypto::AppKey;
|
||||
use sp_keystore::testing::KeyStore;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use assert_matches::assert_matches;
|
||||
use polkadot_node_network_protocol::{view, ObservedRole, our_view};
|
||||
use polkadot_subsystem::jaeger;
|
||||
|
||||
macro_rules! launch {
|
||||
($fut:expr) => {
|
||||
$fut
|
||||
.timeout(Duration::from_millis(10))
|
||||
.await
|
||||
.expect("10ms is more than enough for sending messages.")
|
||||
};
|
||||
}
|
||||
|
||||
/// A very limited state, only interested in the relay parent of the
|
||||
/// given message, which must be signed by `validator` and a set of peers
|
||||
/// which are also only interested in that relay parent.
|
||||
fn prewarmed_state(
|
||||
validator: ValidatorId,
|
||||
signing_context: SigningContext,
|
||||
known_message: BitfieldGossipMessage,
|
||||
peers: Vec<PeerId>,
|
||||
) -> ProtocolState {
|
||||
let relay_parent = known_message.relay_parent.clone();
|
||||
ProtocolState {
|
||||
per_relay_parent: hashmap! {
|
||||
relay_parent.clone() =>
|
||||
PerRelayParentData {
|
||||
signing_context,
|
||||
validator_set: vec![validator.clone()],
|
||||
one_per_validator: hashmap! {
|
||||
validator.clone() => known_message.clone(),
|
||||
},
|
||||
message_received_from_peer: hashmap!{},
|
||||
message_sent_to_peer: hashmap!{},
|
||||
span: PerLeafSpan::new(Arc::new(jaeger::Span::Disabled), "test"),
|
||||
},
|
||||
},
|
||||
peer_views: peers
|
||||
.into_iter()
|
||||
.map(|peer| (peer, view!(relay_parent)))
|
||||
.collect(),
|
||||
view: our_view!(relay_parent),
|
||||
}
|
||||
}
|
||||
|
||||
fn state_with_view(
|
||||
view: OurView,
|
||||
relay_parent: Hash,
|
||||
) -> (ProtocolState, SigningContext, SyncCryptoStorePtr, ValidatorId) {
|
||||
let mut state = ProtocolState::default();
|
||||
|
||||
let signing_context = SigningContext {
|
||||
session_index: 1,
|
||||
parent_hash: relay_parent.clone(),
|
||||
};
|
||||
|
||||
let keystore : SyncCryptoStorePtr = Arc::new(KeyStore::new());
|
||||
let validator = SyncCryptoStore::sr25519_generate_new(&*keystore, ValidatorId::ID, None)
|
||||
.expect("generating sr25519 key not to fail");
|
||||
|
||||
state.per_relay_parent = view.iter().map(|relay_parent| {(
|
||||
relay_parent.clone(),
|
||||
PerRelayParentData {
|
||||
signing_context: signing_context.clone(),
|
||||
validator_set: vec![validator.clone().into()],
|
||||
one_per_validator: hashmap!{},
|
||||
message_received_from_peer: hashmap!{},
|
||||
message_sent_to_peer: hashmap!{},
|
||||
span: PerLeafSpan::new(Arc::new(jaeger::Span::Disabled), "test"),
|
||||
})
|
||||
}).collect();
|
||||
|
||||
state.view = view;
|
||||
|
||||
(state, signing_context, keystore, validator.into())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn receive_invalid_signature() {
|
||||
let _ = env_logger::builder()
|
||||
.filter(None, log::LevelFilter::Trace)
|
||||
.is_test(true)
|
||||
.try_init();
|
||||
|
||||
let hash_a: Hash = [0; 32].into();
|
||||
|
||||
let peer_a = PeerId::random();
|
||||
let peer_b = PeerId::random();
|
||||
assert_ne!(peer_a, peer_b);
|
||||
|
||||
let signing_context = SigningContext {
|
||||
session_index: 1,
|
||||
parent_hash: hash_a.clone(),
|
||||
};
|
||||
|
||||
// another validator not part of the validatorset
|
||||
let keystore : SyncCryptoStorePtr = Arc::new(KeyStore::new());
|
||||
let malicious = SyncCryptoStore::sr25519_generate_new(&*keystore, ValidatorId::ID, None)
|
||||
.expect("Malicious key created");
|
||||
let validator_0 = SyncCryptoStore::sr25519_generate_new(&*keystore, ValidatorId::ID, None)
|
||||
.expect("key created");
|
||||
let validator_1 = SyncCryptoStore::sr25519_generate_new(&*keystore, ValidatorId::ID, None)
|
||||
.expect("key created");
|
||||
|
||||
let payload = AvailabilityBitfield(bitvec![bitvec::order::Lsb0, u8; 1u8; 32]);
|
||||
let invalid_signed = executor::block_on(Signed::<AvailabilityBitfield>::sign(
|
||||
&keystore,
|
||||
payload.clone(),
|
||||
&signing_context,
|
||||
ValidatorIndex(0),
|
||||
&malicious.into(),
|
||||
)).ok().flatten().expect("should be signed");
|
||||
let invalid_signed_2 = executor::block_on(Signed::<AvailabilityBitfield>::sign(
|
||||
&keystore,
|
||||
payload.clone(),
|
||||
&signing_context,
|
||||
ValidatorIndex(1),
|
||||
&malicious.into(),
|
||||
)).ok().flatten().expect("should be signed");
|
||||
|
||||
let valid_signed = executor::block_on(Signed::<AvailabilityBitfield>::sign(
|
||||
&keystore,
|
||||
payload,
|
||||
&signing_context,
|
||||
ValidatorIndex(0),
|
||||
&validator_0.into(),
|
||||
)).ok().flatten().expect("should be signed");
|
||||
|
||||
let invalid_msg = BitfieldGossipMessage {
|
||||
relay_parent: hash_a.clone(),
|
||||
signed_availability: invalid_signed.clone(),
|
||||
};
|
||||
let invalid_msg_2 = BitfieldGossipMessage {
|
||||
relay_parent: hash_a.clone(),
|
||||
signed_availability: invalid_signed_2.clone(),
|
||||
};
|
||||
let valid_msg = BitfieldGossipMessage {
|
||||
relay_parent: hash_a.clone(),
|
||||
signed_availability: valid_signed.clone(),
|
||||
};
|
||||
|
||||
let pool = sp_core::testing::TaskExecutor::new();
|
||||
let (mut ctx, mut handle) =
|
||||
make_subsystem_context::<BitfieldDistributionMessage, _>(pool);
|
||||
|
||||
let mut state = prewarmed_state(
|
||||
validator_0.into(),
|
||||
signing_context.clone(),
|
||||
valid_msg,
|
||||
vec![peer_b.clone()],
|
||||
);
|
||||
state.per_relay_parent.get_mut(&hash_a)
|
||||
.unwrap()
|
||||
.validator_set
|
||||
.push(validator_1.into());
|
||||
|
||||
executor::block_on(async move {
|
||||
launch!(handle_network_msg(
|
||||
&mut ctx,
|
||||
&mut state,
|
||||
&Default::default(),
|
||||
NetworkBridgeEvent::PeerMessage(peer_b.clone(), invalid_msg.into_network_message()),
|
||||
));
|
||||
|
||||
// reputation doesn't change due to one_job_per_validator check
|
||||
assert!(handle.recv().timeout(Duration::from_millis(10)).await.is_none());
|
||||
|
||||
launch!(handle_network_msg(
|
||||
&mut ctx,
|
||||
&mut state,
|
||||
&Default::default(),
|
||||
NetworkBridgeEvent::PeerMessage(peer_b.clone(), invalid_msg_2.into_network_message()),
|
||||
));
|
||||
// reputation change due to invalid signature
|
||||
assert_matches!(
|
||||
handle.recv().await,
|
||||
AllMessages::NetworkBridge(
|
||||
NetworkBridgeMessage::ReportPeer(peer, rep)
|
||||
) => {
|
||||
assert_eq!(peer, peer_b);
|
||||
assert_eq!(rep, COST_SIGNATURE_INVALID)
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn receive_invalid_validator_index() {
|
||||
let _ = env_logger::builder()
|
||||
.filter(None, log::LevelFilter::Trace)
|
||||
.is_test(true)
|
||||
.try_init();
|
||||
|
||||
let hash_a: Hash = [0; 32].into();
|
||||
let hash_b: Hash = [1; 32].into(); // other
|
||||
|
||||
let peer_a = PeerId::random();
|
||||
let peer_b = PeerId::random();
|
||||
assert_ne!(peer_a, peer_b);
|
||||
|
||||
// validator 0 key pair
|
||||
let (mut state, signing_context, keystore, validator) = state_with_view(our_view![hash_a, hash_b], hash_a.clone());
|
||||
|
||||
state.peer_views.insert(peer_b.clone(), view![hash_a]);
|
||||
|
||||
let payload = AvailabilityBitfield(bitvec![bitvec::order::Lsb0, u8; 1u8; 32]);
|
||||
let signed = executor::block_on(Signed::<AvailabilityBitfield>::sign(
|
||||
&keystore,
|
||||
payload,
|
||||
&signing_context,
|
||||
ValidatorIndex(42),
|
||||
&validator,
|
||||
)).ok().flatten().expect("should be signed");
|
||||
|
||||
let msg = BitfieldGossipMessage {
|
||||
relay_parent: hash_a.clone(),
|
||||
signed_availability: signed.clone(),
|
||||
};
|
||||
|
||||
let pool = sp_core::testing::TaskExecutor::new();
|
||||
let (mut ctx, mut handle) =
|
||||
make_subsystem_context::<BitfieldDistributionMessage, _>(pool);
|
||||
|
||||
executor::block_on(async move {
|
||||
launch!(handle_network_msg(
|
||||
&mut ctx,
|
||||
&mut state,
|
||||
&Default::default(),
|
||||
NetworkBridgeEvent::PeerMessage(peer_b.clone(), msg.into_network_message()),
|
||||
));
|
||||
|
||||
// reputation change due to invalid validator index
|
||||
assert_matches!(
|
||||
handle.recv().await,
|
||||
AllMessages::NetworkBridge(
|
||||
NetworkBridgeMessage::ReportPeer(peer, rep)
|
||||
) => {
|
||||
assert_eq!(peer, peer_b);
|
||||
assert_eq!(rep, COST_VALIDATOR_INDEX_INVALID)
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn receive_duplicate_messages() {
|
||||
let _ = env_logger::builder()
|
||||
.filter(None, log::LevelFilter::Trace)
|
||||
.is_test(true)
|
||||
.try_init();
|
||||
|
||||
let hash_a: Hash = [0; 32].into();
|
||||
let hash_b: Hash = [1; 32].into();
|
||||
|
||||
let peer_a = PeerId::random();
|
||||
let peer_b = PeerId::random();
|
||||
assert_ne!(peer_a, peer_b);
|
||||
|
||||
// validator 0 key pair
|
||||
let (mut state, signing_context, keystore, validator) = state_with_view(our_view![hash_a, hash_b], hash_a.clone());
|
||||
|
||||
// create a signed message by validator 0
|
||||
let payload = AvailabilityBitfield(bitvec![bitvec::order::Lsb0, u8; 1u8; 32]);
|
||||
let signed_bitfield = executor::block_on(Signed::<AvailabilityBitfield>::sign(
|
||||
&keystore,
|
||||
payload,
|
||||
&signing_context,
|
||||
ValidatorIndex(0),
|
||||
&validator,
|
||||
)).ok().flatten().expect("should be signed");
|
||||
|
||||
let msg = BitfieldGossipMessage {
|
||||
relay_parent: hash_a.clone(),
|
||||
signed_availability: signed_bitfield.clone(),
|
||||
};
|
||||
|
||||
let pool = sp_core::testing::TaskExecutor::new();
|
||||
let (mut ctx, mut handle) =
|
||||
make_subsystem_context::<BitfieldDistributionMessage, _>(pool);
|
||||
|
||||
executor::block_on(async move {
|
||||
// send a first message
|
||||
launch!(handle_network_msg(
|
||||
&mut ctx,
|
||||
&mut state,
|
||||
&Default::default(),
|
||||
NetworkBridgeEvent::PeerMessage(
|
||||
peer_b.clone(),
|
||||
msg.clone().into_network_message(),
|
||||
),
|
||||
));
|
||||
|
||||
// none of our peers has any interest in any messages
|
||||
// so we do not receive a network send type message here
|
||||
// but only the one for the next subsystem
|
||||
assert_matches!(
|
||||
handle.recv().await,
|
||||
AllMessages::Provisioner(ProvisionerMessage::ProvisionableData(
|
||||
_,
|
||||
ProvisionableData::Bitfield(hash, signed)
|
||||
)) => {
|
||||
assert_eq!(hash, hash_a);
|
||||
assert_eq!(signed, signed_bitfield)
|
||||
}
|
||||
);
|
||||
|
||||
assert_matches!(
|
||||
handle.recv().await,
|
||||
AllMessages::NetworkBridge(
|
||||
NetworkBridgeMessage::ReportPeer(peer, rep)
|
||||
) => {
|
||||
assert_eq!(peer, peer_b);
|
||||
assert_eq!(rep, BENEFIT_VALID_MESSAGE_FIRST)
|
||||
}
|
||||
);
|
||||
|
||||
// let peer A send the same message again
|
||||
launch!(handle_network_msg(
|
||||
&mut ctx,
|
||||
&mut state,
|
||||
&Default::default(),
|
||||
NetworkBridgeEvent::PeerMessage(
|
||||
peer_a.clone(),
|
||||
msg.clone().into_network_message(),
|
||||
),
|
||||
));
|
||||
|
||||
assert_matches!(
|
||||
handle.recv().await,
|
||||
AllMessages::NetworkBridge(
|
||||
NetworkBridgeMessage::ReportPeer(peer, rep)
|
||||
) => {
|
||||
assert_eq!(peer, peer_a);
|
||||
assert_eq!(rep, BENEFIT_VALID_MESSAGE)
|
||||
}
|
||||
);
|
||||
|
||||
// let peer B send the initial message again
|
||||
launch!(handle_network_msg(
|
||||
&mut ctx,
|
||||
&mut state,
|
||||
&Default::default(),
|
||||
NetworkBridgeEvent::PeerMessage(
|
||||
peer_b.clone(),
|
||||
msg.clone().into_network_message(),
|
||||
),
|
||||
));
|
||||
|
||||
assert_matches!(
|
||||
handle.recv().await,
|
||||
AllMessages::NetworkBridge(
|
||||
NetworkBridgeMessage::ReportPeer(peer, rep)
|
||||
) => {
|
||||
assert_eq!(peer, peer_b);
|
||||
assert_eq!(rep, COST_PEER_DUPLICATE_MESSAGE)
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn do_not_relay_message_twice() {
|
||||
let _ = env_logger::builder()
|
||||
.filter(None, log::LevelFilter::Trace)
|
||||
.is_test(true)
|
||||
.try_init();
|
||||
|
||||
let hash = Hash::random();
|
||||
|
||||
let peer_a = PeerId::random();
|
||||
let peer_b = PeerId::random();
|
||||
assert_ne!(peer_a, peer_b);
|
||||
|
||||
// validator 0 key pair
|
||||
let (mut state, signing_context, keystore, validator) = state_with_view(our_view![hash], hash.clone());
|
||||
|
||||
// create a signed message by validator 0
|
||||
let payload = AvailabilityBitfield(bitvec![bitvec::order::Lsb0, u8; 1u8; 32]);
|
||||
let signed_bitfield = executor::block_on(Signed::<AvailabilityBitfield>::sign(
|
||||
&keystore,
|
||||
payload,
|
||||
&signing_context,
|
||||
ValidatorIndex(0),
|
||||
&validator,
|
||||
)).ok().flatten().expect("should be signed");
|
||||
|
||||
state.peer_views.insert(peer_b.clone(), view![hash]);
|
||||
state.peer_views.insert(peer_a.clone(), view![hash]);
|
||||
|
||||
let msg = BitfieldGossipMessage {
|
||||
relay_parent: hash.clone(),
|
||||
signed_availability: signed_bitfield.clone(),
|
||||
};
|
||||
|
||||
let pool = sp_core::testing::TaskExecutor::new();
|
||||
let (mut ctx, mut handle) =
|
||||
make_subsystem_context::<BitfieldDistributionMessage, _>(pool);
|
||||
|
||||
executor::block_on(async move {
|
||||
relay_message(
|
||||
&mut ctx,
|
||||
state.per_relay_parent.get_mut(&hash).unwrap(),
|
||||
&mut state.peer_views,
|
||||
validator.clone(),
|
||||
msg.clone(),
|
||||
).await;
|
||||
|
||||
assert_matches!(
|
||||
handle.recv().await,
|
||||
AllMessages::Provisioner(ProvisionerMessage::ProvisionableData(
|
||||
_,
|
||||
ProvisionableData::Bitfield(h, signed)
|
||||
)) => {
|
||||
assert_eq!(h, hash);
|
||||
assert_eq!(signed, signed_bitfield)
|
||||
}
|
||||
);
|
||||
|
||||
assert_matches!(
|
||||
handle.recv().await,
|
||||
AllMessages::NetworkBridge(
|
||||
NetworkBridgeMessage::SendValidationMessage(peers, send_msg),
|
||||
) => {
|
||||
assert_eq!(2, peers.len());
|
||||
assert!(peers.contains(&peer_a));
|
||||
assert!(peers.contains(&peer_b));
|
||||
assert_eq!(send_msg, msg.clone().into_validation_protocol());
|
||||
}
|
||||
);
|
||||
|
||||
// Relaying the message a second time shouldn't work.
|
||||
relay_message(
|
||||
&mut ctx,
|
||||
state.per_relay_parent.get_mut(&hash).unwrap(),
|
||||
&mut state.peer_views,
|
||||
validator.clone(),
|
||||
msg.clone(),
|
||||
).await;
|
||||
|
||||
assert_matches!(
|
||||
handle.recv().await,
|
||||
AllMessages::Provisioner(ProvisionerMessage::ProvisionableData(
|
||||
_,
|
||||
ProvisionableData::Bitfield(h, signed)
|
||||
)) => {
|
||||
assert_eq!(h, hash);
|
||||
assert_eq!(signed, signed_bitfield)
|
||||
}
|
||||
);
|
||||
|
||||
// There shouldn't be any other message
|
||||
assert!(handle.recv().timeout(Duration::from_millis(10)).await.is_none());
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn changing_view() {
|
||||
let _ = env_logger::builder()
|
||||
.filter(None, log::LevelFilter::Trace)
|
||||
.is_test(true)
|
||||
.try_init();
|
||||
|
||||
let hash_a: Hash = [0; 32].into();
|
||||
let hash_b: Hash = [1; 32].into();
|
||||
|
||||
let peer_a = PeerId::random();
|
||||
let peer_b = PeerId::random();
|
||||
assert_ne!(peer_a, peer_b);
|
||||
|
||||
// validator 0 key pair
|
||||
let (mut state, signing_context, keystore, validator) = state_with_view(our_view![hash_a, hash_b], hash_a.clone());
|
||||
|
||||
// create a signed message by validator 0
|
||||
let payload = AvailabilityBitfield(bitvec![bitvec::order::Lsb0, u8; 1u8; 32]);
|
||||
let signed_bitfield = executor::block_on(Signed::<AvailabilityBitfield>::sign(
|
||||
&keystore,
|
||||
payload,
|
||||
&signing_context,
|
||||
ValidatorIndex(0),
|
||||
&validator,
|
||||
)).ok().flatten().expect("should be signed");
|
||||
|
||||
let msg = BitfieldGossipMessage {
|
||||
relay_parent: hash_a.clone(),
|
||||
signed_availability: signed_bitfield.clone(),
|
||||
};
|
||||
|
||||
let pool = sp_core::testing::TaskExecutor::new();
|
||||
let (mut ctx, mut handle) =
|
||||
make_subsystem_context::<BitfieldDistributionMessage, _>(pool);
|
||||
|
||||
executor::block_on(async move {
|
||||
launch!(handle_network_msg(
|
||||
&mut ctx,
|
||||
&mut state,
|
||||
&Default::default(),
|
||||
NetworkBridgeEvent::PeerConnected(peer_b.clone(), ObservedRole::Full, None),
|
||||
));
|
||||
|
||||
// make peer b interested
|
||||
launch!(handle_network_msg(
|
||||
&mut ctx,
|
||||
&mut state,
|
||||
&Default::default(),
|
||||
NetworkBridgeEvent::PeerViewChange(peer_b.clone(), view![hash_a, hash_b]),
|
||||
));
|
||||
|
||||
assert!(state.peer_views.contains_key(&peer_b));
|
||||
|
||||
// recv a first message from the network
|
||||
launch!(handle_network_msg(
|
||||
&mut ctx,
|
||||
&mut state,
|
||||
&Default::default(),
|
||||
NetworkBridgeEvent::PeerMessage(
|
||||
peer_b.clone(),
|
||||
msg.clone().into_network_message(),
|
||||
),
|
||||
));
|
||||
|
||||
// gossip to the overseer
|
||||
assert_matches!(
|
||||
handle.recv().await,
|
||||
AllMessages::Provisioner(ProvisionerMessage::ProvisionableData(
|
||||
_,
|
||||
ProvisionableData::Bitfield(hash, signed)
|
||||
)) => {
|
||||
assert_eq!(hash, hash_a);
|
||||
assert_eq!(signed, signed_bitfield)
|
||||
}
|
||||
);
|
||||
|
||||
// reputation change for peer B
|
||||
assert_matches!(
|
||||
handle.recv().await,
|
||||
AllMessages::NetworkBridge(
|
||||
NetworkBridgeMessage::ReportPeer(peer, rep)
|
||||
) => {
|
||||
assert_eq!(peer, peer_b);
|
||||
assert_eq!(rep, BENEFIT_VALID_MESSAGE_FIRST)
|
||||
}
|
||||
);
|
||||
|
||||
launch!(handle_network_msg(
|
||||
&mut ctx,
|
||||
&mut state,
|
||||
&Default::default(),
|
||||
NetworkBridgeEvent::PeerViewChange(peer_b.clone(), view![]),
|
||||
));
|
||||
|
||||
assert!(state.peer_views.contains_key(&peer_b));
|
||||
assert_eq!(
|
||||
state.peer_views.get(&peer_b).expect("Must contain value for peer B"),
|
||||
&view![]
|
||||
);
|
||||
|
||||
// on rx of the same message, since we are not interested,
|
||||
// should give penalty
|
||||
launch!(handle_network_msg(
|
||||
&mut ctx,
|
||||
&mut state,
|
||||
&Default::default(),
|
||||
NetworkBridgeEvent::PeerMessage(
|
||||
peer_b.clone(),
|
||||
msg.clone().into_network_message(),
|
||||
),
|
||||
));
|
||||
|
||||
// reputation change for peer B
|
||||
assert_matches!(
|
||||
handle.recv().await,
|
||||
AllMessages::NetworkBridge(
|
||||
NetworkBridgeMessage::ReportPeer(peer, rep)
|
||||
) => {
|
||||
assert_eq!(peer, peer_b);
|
||||
assert_eq!(rep, COST_PEER_DUPLICATE_MESSAGE)
|
||||
}
|
||||
);
|
||||
|
||||
launch!(handle_network_msg(
|
||||
&mut ctx,
|
||||
&mut state,
|
||||
&Default::default(),
|
||||
NetworkBridgeEvent::PeerDisconnected(peer_b.clone()),
|
||||
));
|
||||
|
||||
// we are not interested in any peers at all anymore
|
||||
state.view = our_view![];
|
||||
|
||||
// on rx of the same message, since we are not interested,
|
||||
// should give penalty
|
||||
launch!(handle_network_msg(
|
||||
&mut ctx,
|
||||
&mut state,
|
||||
&Default::default(),
|
||||
NetworkBridgeEvent::PeerMessage(
|
||||
peer_a.clone(),
|
||||
msg.clone().into_network_message(),
|
||||
),
|
||||
));
|
||||
|
||||
// reputation change for peer B
|
||||
assert_matches!(
|
||||
handle.recv().await,
|
||||
AllMessages::NetworkBridge(
|
||||
NetworkBridgeMessage::ReportPeer(peer, rep)
|
||||
) => {
|
||||
assert_eq!(peer, peer_a);
|
||||
assert_eq!(rep, COST_NOT_IN_VIEW)
|
||||
}
|
||||
);
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn do_not_send_message_back_to_origin() {
|
||||
let _ = env_logger::builder()
|
||||
.filter(None, log::LevelFilter::Trace)
|
||||
.is_test(true)
|
||||
.try_init();
|
||||
|
||||
let hash: Hash = [0; 32].into();
|
||||
|
||||
let peer_a = PeerId::random();
|
||||
let peer_b = PeerId::random();
|
||||
assert_ne!(peer_a, peer_b);
|
||||
|
||||
// validator 0 key pair
|
||||
let (mut state, signing_context, keystore, validator) = state_with_view(our_view![hash], hash);
|
||||
|
||||
// create a signed message by validator 0
|
||||
let payload = AvailabilityBitfield(bitvec![bitvec::order::Lsb0, u8; 1u8; 32]);
|
||||
let signed_bitfield = executor::block_on(Signed::<AvailabilityBitfield>::sign(
|
||||
&keystore,
|
||||
payload,
|
||||
&signing_context,
|
||||
ValidatorIndex(0),
|
||||
&validator,
|
||||
)).ok().flatten().expect("should be signed");
|
||||
|
||||
state.peer_views.insert(peer_b.clone(), view![hash]);
|
||||
state.peer_views.insert(peer_a.clone(), view![hash]);
|
||||
|
||||
let msg = BitfieldGossipMessage {
|
||||
relay_parent: hash.clone(),
|
||||
signed_availability: signed_bitfield.clone(),
|
||||
};
|
||||
|
||||
let pool = sp_core::testing::TaskExecutor::new();
|
||||
let (mut ctx, mut handle) =
|
||||
make_subsystem_context::<BitfieldDistributionMessage, _>(pool);
|
||||
|
||||
executor::block_on(async move {
|
||||
// send a first message
|
||||
launch!(handle_network_msg(
|
||||
&mut ctx,
|
||||
&mut state,
|
||||
&Default::default(),
|
||||
NetworkBridgeEvent::PeerMessage(
|
||||
peer_b.clone(),
|
||||
msg.clone().into_network_message(),
|
||||
),
|
||||
));
|
||||
|
||||
assert_matches!(
|
||||
handle.recv().await,
|
||||
AllMessages::Provisioner(ProvisionerMessage::ProvisionableData(
|
||||
_,
|
||||
ProvisionableData::Bitfield(hash, signed)
|
||||
)) => {
|
||||
assert_eq!(hash, hash);
|
||||
assert_eq!(signed, signed_bitfield)
|
||||
}
|
||||
);
|
||||
|
||||
assert_matches!(
|
||||
handle.recv().await,
|
||||
AllMessages::NetworkBridge(
|
||||
NetworkBridgeMessage::SendValidationMessage(peers, send_msg),
|
||||
) => {
|
||||
assert_eq!(1, peers.len());
|
||||
assert!(peers.contains(&peer_a));
|
||||
assert_eq!(send_msg, msg.clone().into_validation_protocol());
|
||||
}
|
||||
);
|
||||
|
||||
assert_matches!(
|
||||
handle.recv().await,
|
||||
AllMessages::NetworkBridge(
|
||||
NetworkBridgeMessage::ReportPeer(peer, rep)
|
||||
) => {
|
||||
assert_eq!(peer, peer_b);
|
||||
assert_eq!(rep, BENEFIT_VALID_MESSAGE_FIRST)
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
@@ -1252,11 +1252,11 @@ fn spread_event_to_subsystems_is_up_to_date() {
|
||||
AllMessages::GossipSupport(_) => unreachable!("Not interested in network events"),
|
||||
AllMessages::DisputeCoordinator(_) => unreachable!("Not interested in network events"),
|
||||
AllMessages::DisputeParticipation(_) => unreachable!("Not interetsed in network events"),
|
||||
// Add variants here as needed, `{ cnt += 1; }` for those that need to be
|
||||
// notified, `unreachable!()` for those that should not.
|
||||
}
|
||||
}
|
||||
assert_eq!(cnt, EXPECTED_COUNT);
|
||||
// Add variants here as needed, `{ cnt += 1; }` for those that need to be
|
||||
// notified, `unreachable!()` for those that should not.
|
||||
}
|
||||
}
|
||||
assert_eq!(cnt, EXPECTED_COUNT);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -103,7 +103,7 @@ impl From<[u8; 32]> for ValidationCodeHash {
|
||||
|
||||
impl sp_std::fmt::LowerHex for ValidationCodeHash {
|
||||
fn fmt(&self, f: &mut sp_std::fmt::Formatter<'_>) -> sp_std::fmt::Result {
|
||||
sp_std::fmt::LowerHex::fmt(&self.0, f)
|
||||
sp_std::fmt::LowerHex::fmt(&self.0, f)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -569,7 +569,7 @@ impl<T: Config> Module<T> {
|
||||
{
|
||||
Ok(i) => i,
|
||||
Err(_) => 0, // can only happen if rotations occur only once every u32::max(),
|
||||
// so functionally no difference in behavior.
|
||||
// so functionally no difference in behavior.
|
||||
};
|
||||
|
||||
let group_idx = (core.0 as usize + rotations_since_session_start as usize) % validator_groups.len();
|
||||
|
||||
Reference in New Issue
Block a user