mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-05-30 14:01:02 +00:00
Sort out validation errors (#1516)
* Sort out validation errors * Typo * Fixed wasm/android build * Fixed bad merge
This commit is contained in:
@@ -305,7 +305,7 @@ impl CandidateBackingJob {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ValidationResult::Invalid => {
|
ValidationResult::Invalid(_reason) => {
|
||||||
// no need to issue a statement about this if we aren't seconding it.
|
// no need to issue a statement about this if we aren't seconding it.
|
||||||
//
|
//
|
||||||
// there's an infinite amount of garbage out there. no need to acknowledge
|
// there's an infinite amount of garbage out there. no need to acknowledge
|
||||||
@@ -497,7 +497,7 @@ impl CandidateBackingJob {
|
|||||||
Err(()) => Statement::Invalid(candidate_hash),
|
Err(()) => Statement::Invalid(candidate_hash),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ValidationResult::Invalid => {
|
ValidationResult::Invalid(_reason) => {
|
||||||
Statement::Invalid(candidate_hash)
|
Statement::Invalid(candidate_hash)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -826,6 +826,7 @@ mod tests {
|
|||||||
messages::RuntimeApiRequest,
|
messages::RuntimeApiRequest,
|
||||||
ActiveLeavesUpdate, FromOverseer, OverseerSignal,
|
ActiveLeavesUpdate, FromOverseer, OverseerSignal,
|
||||||
};
|
};
|
||||||
|
use polkadot_node_primitives::InvalidCandidate;
|
||||||
use sp_keyring::Sr25519Keyring;
|
use sp_keyring::Sr25519Keyring;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
@@ -1461,7 +1462,7 @@ mod tests {
|
|||||||
tx,
|
tx,
|
||||||
)
|
)
|
||||||
) if pov == pov && &c == candidate_a.descriptor() => {
|
) if pov == pov && &c == candidate_a.descriptor() => {
|
||||||
tx.send(Ok(ValidationResult::Invalid)).unwrap();
|
tx.send(Ok(ValidationResult::Invalid(InvalidCandidate::BadReturn))).unwrap();
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -1597,7 +1598,7 @@ mod tests {
|
|||||||
tx,
|
tx,
|
||||||
)
|
)
|
||||||
) if pov == pov && &c == candidate.descriptor() => {
|
) if pov == pov && &c == candidate.descriptor() => {
|
||||||
tx.send(Ok(ValidationResult::Invalid)).unwrap();
|
tx.send(Ok(ValidationResult::Invalid(InvalidCandidate::BadReturn))).unwrap();
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -1729,7 +1730,7 @@ mod tests {
|
|||||||
tx,
|
tx,
|
||||||
)
|
)
|
||||||
) if pov == pov && &c == candidate.descriptor() => {
|
) if pov == pov && &c == candidate.descriptor() => {
|
||||||
tx.send(Err(ValidationFailed)).unwrap();
|
tx.send(Err(ValidationFailed("Internal test error".into()))).unwrap();
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -28,12 +28,13 @@ use polkadot_subsystem::messages::{
|
|||||||
AllMessages, CandidateValidationMessage, RuntimeApiMessage, ValidationFailed, RuntimeApiRequest,
|
AllMessages, CandidateValidationMessage, RuntimeApiMessage, ValidationFailed, RuntimeApiRequest,
|
||||||
};
|
};
|
||||||
use polkadot_subsystem::errors::RuntimeApiError;
|
use polkadot_subsystem::errors::RuntimeApiError;
|
||||||
use polkadot_node_primitives::{ValidationResult, ValidationOutputs};
|
use polkadot_node_primitives::{ValidationResult, ValidationOutputs, InvalidCandidate};
|
||||||
use polkadot_primitives::v1::{
|
use polkadot_primitives::v1::{
|
||||||
ValidationCode, OmittedValidationData, PoV, CandidateDescriptor, LocalValidationData,
|
ValidationCode, OmittedValidationData, PoV, CandidateDescriptor, LocalValidationData,
|
||||||
GlobalValidationData, OccupiedCoreAssumption, Hash, validation_data_hash,
|
GlobalValidationData, OccupiedCoreAssumption, Hash, validation_data_hash,
|
||||||
};
|
};
|
||||||
use polkadot_parachain::wasm_executor::{self, ValidationPool, ExecutionMode};
|
use polkadot_parachain::wasm_executor::{self, ValidationPool, ExecutionMode, ValidationError,
|
||||||
|
InvalidCandidate as WasmInvalidCandidate};
|
||||||
use polkadot_parachain::primitives::{ValidationResult as WasmValidationResult, ValidationParams};
|
use polkadot_parachain::primitives::{ValidationResult as WasmValidationResult, ValidationParams};
|
||||||
|
|
||||||
use parity_scale_codec::Encode;
|
use parity_scale_codec::Encode;
|
||||||
@@ -241,7 +242,7 @@ async fn spawn_validate_from_chain_state(
|
|||||||
e,
|
e,
|
||||||
);
|
);
|
||||||
|
|
||||||
return Ok(Err(ValidationFailed));
|
return Ok(Err(ValidationFailed("Error making API request".into())));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -264,7 +265,7 @@ async fn spawn_validate_from_chain_state(
|
|||||||
).await;
|
).await;
|
||||||
}
|
}
|
||||||
AssumptionCheckOutcome::DoesNotMatch => {},
|
AssumptionCheckOutcome::DoesNotMatch => {},
|
||||||
AssumptionCheckOutcome::BadRequest => return Ok(Err(ValidationFailed)),
|
AssumptionCheckOutcome::BadRequest => return Ok(Err(ValidationFailed("Bad request".into()))),
|
||||||
}
|
}
|
||||||
|
|
||||||
match check_assumption_validation_data(
|
match check_assumption_validation_data(
|
||||||
@@ -285,13 +286,13 @@ async fn spawn_validate_from_chain_state(
|
|||||||
).await;
|
).await;
|
||||||
}
|
}
|
||||||
AssumptionCheckOutcome::DoesNotMatch => {},
|
AssumptionCheckOutcome::DoesNotMatch => {},
|
||||||
AssumptionCheckOutcome::BadRequest => return Ok(Err(ValidationFailed)),
|
AssumptionCheckOutcome::BadRequest => return Ok(Err(ValidationFailed("Bad request".into()))),
|
||||||
}
|
}
|
||||||
|
|
||||||
// If neither the assumption of the occupied core having the para included or the assumption
|
// If neither the assumption of the occupied core having the para included or the assumption
|
||||||
// of the occupied core timing out are valid, then the validation_data_hash in the descriptor
|
// of the occupied core timing out are valid, then the validation_data_hash in the descriptor
|
||||||
// is not based on the relay parent and is thus invalid.
|
// is not based on the relay parent and is thus invalid.
|
||||||
Ok(Ok(ValidationResult::Invalid))
|
Ok(Ok(ValidationResult::Invalid(InvalidCandidate::BadParent)))
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn spawn_validate_exhaustive(
|
async fn spawn_validate_exhaustive(
|
||||||
@@ -321,52 +322,52 @@ async fn spawn_validate_exhaustive(
|
|||||||
rx.await.map_err(Into::into)
|
rx.await.map_err(Into::into)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Does basic checks of a candidate. Provide the encoded PoV-block. Returns `true` if basic checks
|
/// Does basic checks of a candidate. Provide the encoded PoV-block. Returns `Ok` if basic checks
|
||||||
/// are passed, false otherwise.
|
/// are passed, `Err` otherwise.
|
||||||
fn passes_basic_checks(
|
fn perform_basic_checks(
|
||||||
candidate: &CandidateDescriptor,
|
candidate: &CandidateDescriptor,
|
||||||
max_block_data_size: Option<u64>,
|
max_block_data_size: Option<u64>,
|
||||||
pov: &PoV,
|
pov: &PoV,
|
||||||
) -> bool {
|
) -> Result<(), InvalidCandidate> {
|
||||||
let encoded_pov = pov.encode();
|
let encoded_pov = pov.encode();
|
||||||
let hash = pov.hash();
|
let hash = pov.hash();
|
||||||
|
|
||||||
if let Some(max_size) = max_block_data_size {
|
if let Some(max_size) = max_block_data_size {
|
||||||
if encoded_pov.len() as u64 > max_size {
|
if encoded_pov.len() as u64 > max_size {
|
||||||
return false;
|
return Err(InvalidCandidate::ParamsTooLarge(encoded_pov.len() as u64));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if hash != candidate.pov_hash {
|
if hash != candidate.pov_hash {
|
||||||
return false;
|
return Err(InvalidCandidate::HashMismatch);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Err(()) = candidate.check_collator_signature() {
|
if let Err(()) = candidate.check_collator_signature() {
|
||||||
return false;
|
return Err(InvalidCandidate::BadSignature);
|
||||||
}
|
}
|
||||||
|
|
||||||
true
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Check the result of Wasm execution against the constraints given by the relay-chain.
|
/// Check the result of Wasm execution against the constraints given by the relay-chain.
|
||||||
///
|
///
|
||||||
/// Returns `true` if checks pass, false otherwise.
|
/// Returns `Ok(())` if checks pass, error otherwise.
|
||||||
fn check_wasm_result_against_constraints(
|
fn check_wasm_result_against_constraints(
|
||||||
global_validation_data: &GlobalValidationData,
|
global_validation_data: &GlobalValidationData,
|
||||||
_local_validation_data: &LocalValidationData,
|
_local_validation_data: &LocalValidationData,
|
||||||
result: &WasmValidationResult,
|
result: &WasmValidationResult,
|
||||||
) -> bool {
|
) -> Result<(), InvalidCandidate> {
|
||||||
if result.head_data.0.len() > global_validation_data.max_head_data_size as _ {
|
if result.head_data.0.len() > global_validation_data.max_head_data_size as _ {
|
||||||
return false
|
return Err(InvalidCandidate::HeadDataTooLarge(result.head_data.0.len() as u64))
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(ref code) = result.new_validation_code {
|
if let Some(ref code) = result.new_validation_code {
|
||||||
if code.0.len() > global_validation_data.max_code_size as _ {
|
if code.0.len() > global_validation_data.max_code_size as _ {
|
||||||
return false
|
return Err(InvalidCandidate::NewCodeTooLarge(code.0.len() as u64))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
true
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
trait ValidationBackend {
|
trait ValidationBackend {
|
||||||
@@ -377,7 +378,7 @@ trait ValidationBackend {
|
|||||||
validation_code: &ValidationCode,
|
validation_code: &ValidationCode,
|
||||||
params: ValidationParams,
|
params: ValidationParams,
|
||||||
spawn: S,
|
spawn: S,
|
||||||
) -> Result<WasmValidationResult, wasm_executor::Error>;
|
) -> Result<WasmValidationResult, ValidationError>;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct RealValidationBackend;
|
struct RealValidationBackend;
|
||||||
@@ -390,7 +391,7 @@ impl ValidationBackend for RealValidationBackend {
|
|||||||
validation_code: &ValidationCode,
|
validation_code: &ValidationCode,
|
||||||
params: ValidationParams,
|
params: ValidationParams,
|
||||||
spawn: S,
|
spawn: S,
|
||||||
) -> Result<WasmValidationResult, wasm_executor::Error> {
|
) -> Result<WasmValidationResult, ValidationError> {
|
||||||
let execution_mode = pool.as_ref()
|
let execution_mode = pool.as_ref()
|
||||||
.map(ExecutionMode::Remote)
|
.map(ExecutionMode::Remote)
|
||||||
.unwrap_or(ExecutionMode::Local);
|
.unwrap_or(ExecutionMode::Local);
|
||||||
@@ -415,8 +416,8 @@ fn validate_candidate_exhaustive<B: ValidationBackend, S: SpawnNamed + 'static>(
|
|||||||
pov: Arc<PoV>,
|
pov: Arc<PoV>,
|
||||||
spawn: S,
|
spawn: S,
|
||||||
) -> Result<ValidationResult, ValidationFailed> {
|
) -> Result<ValidationResult, ValidationFailed> {
|
||||||
if !passes_basic_checks(&descriptor, None, &*pov) {
|
if let Err(e) = perform_basic_checks(&descriptor, None, &*pov) {
|
||||||
return Ok(ValidationResult::Invalid);
|
return Ok(ValidationResult::Invalid(e))
|
||||||
}
|
}
|
||||||
|
|
||||||
let OmittedValidationData { global_validation, local_validation } = omitted_validation;
|
let OmittedValidationData { global_validation, local_validation } = omitted_validation;
|
||||||
@@ -431,26 +432,36 @@ fn validate_candidate_exhaustive<B: ValidationBackend, S: SpawnNamed + 'static>(
|
|||||||
};
|
};
|
||||||
|
|
||||||
match B::validate(backend_arg, &validation_code, params, spawn) {
|
match B::validate(backend_arg, &validation_code, params, spawn) {
|
||||||
Err(wasm_executor::Error::BadReturn) => Ok(ValidationResult::Invalid),
|
Err(ValidationError::InvalidCandidate(WasmInvalidCandidate::Timeout)) =>
|
||||||
Err(_) => Err(ValidationFailed),
|
Ok(ValidationResult::Invalid(InvalidCandidate::Timeout)),
|
||||||
|
Err(ValidationError::InvalidCandidate(WasmInvalidCandidate::ParamsTooLarge(l))) =>
|
||||||
|
Ok(ValidationResult::Invalid(InvalidCandidate::ParamsTooLarge(l as u64))),
|
||||||
|
Err(ValidationError::InvalidCandidate(WasmInvalidCandidate::CodeTooLarge(l))) =>
|
||||||
|
Ok(ValidationResult::Invalid(InvalidCandidate::CodeTooLarge(l as u64))),
|
||||||
|
Err(ValidationError::InvalidCandidate(WasmInvalidCandidate::BadReturn)) =>
|
||||||
|
Ok(ValidationResult::Invalid(InvalidCandidate::BadReturn)),
|
||||||
|
Err(ValidationError::InvalidCandidate(WasmInvalidCandidate::WasmExecutor(e))) =>
|
||||||
|
Ok(ValidationResult::Invalid(InvalidCandidate::ExecutionError(e.to_string()))),
|
||||||
|
Err(ValidationError::InvalidCandidate(WasmInvalidCandidate::ExternalWasmExecutor(e))) =>
|
||||||
|
Ok(ValidationResult::Invalid(InvalidCandidate::ExecutionError(e.to_string()))),
|
||||||
|
Err(ValidationError::Internal(e)) => Err(ValidationFailed(e.to_string())),
|
||||||
Ok(res) => {
|
Ok(res) => {
|
||||||
let passes_post_checks = check_wasm_result_against_constraints(
|
let post_check_result = check_wasm_result_against_constraints(
|
||||||
&global_validation,
|
&global_validation,
|
||||||
&local_validation,
|
&local_validation,
|
||||||
&res,
|
&res,
|
||||||
);
|
);
|
||||||
|
|
||||||
Ok(if passes_post_checks {
|
Ok(match post_check_result {
|
||||||
ValidationResult::Valid(ValidationOutputs {
|
Ok(()) => ValidationResult::Valid(ValidationOutputs {
|
||||||
head_data: res.head_data,
|
head_data: res.head_data,
|
||||||
global_validation_data: global_validation,
|
global_validation_data: global_validation,
|
||||||
local_validation_data: local_validation,
|
local_validation_data: local_validation,
|
||||||
upward_messages: res.upward_messages,
|
upward_messages: res.upward_messages,
|
||||||
fees: 0,
|
fees: 0,
|
||||||
new_validation_code: res.new_validation_code,
|
new_validation_code: res.new_validation_code,
|
||||||
})
|
}),
|
||||||
} else {
|
Err(e) => ValidationResult::Invalid(e),
|
||||||
ValidationResult::Invalid
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -469,7 +480,7 @@ mod tests {
|
|||||||
struct MockValidationBackend;
|
struct MockValidationBackend;
|
||||||
|
|
||||||
struct MockValidationArg {
|
struct MockValidationArg {
|
||||||
result: Result<WasmValidationResult, wasm_executor::Error>,
|
result: Result<WasmValidationResult, ValidationError>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ValidationBackend for MockValidationBackend {
|
impl ValidationBackend for MockValidationBackend {
|
||||||
@@ -480,7 +491,7 @@ mod tests {
|
|||||||
_validation_code: &ValidationCode,
|
_validation_code: &ValidationCode,
|
||||||
_params: ValidationParams,
|
_params: ValidationParams,
|
||||||
_spawn: S,
|
_spawn: S,
|
||||||
) -> Result<WasmValidationResult, wasm_executor::Error> {
|
) -> Result<WasmValidationResult, ValidationError> {
|
||||||
arg.result
|
arg.result
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -795,7 +806,7 @@ mod tests {
|
|||||||
descriptor.pov_hash = pov.hash();
|
descriptor.pov_hash = pov.hash();
|
||||||
collator_sign(&mut descriptor, Sr25519Keyring::Alice);
|
collator_sign(&mut descriptor, Sr25519Keyring::Alice);
|
||||||
|
|
||||||
assert!(passes_basic_checks(&descriptor, Some(1024), &pov));
|
assert!(perform_basic_checks(&descriptor, Some(1024), &pov).is_ok());
|
||||||
|
|
||||||
let validation_result = WasmValidationResult {
|
let validation_result = WasmValidationResult {
|
||||||
head_data: HeadData(vec![1, 1, 1]),
|
head_data: HeadData(vec![1, 1, 1]),
|
||||||
@@ -808,7 +819,7 @@ mod tests {
|
|||||||
&omitted_validation.global_validation,
|
&omitted_validation.global_validation,
|
||||||
&omitted_validation.local_validation,
|
&omitted_validation.local_validation,
|
||||||
&validation_result,
|
&validation_result,
|
||||||
));
|
).is_ok());
|
||||||
|
|
||||||
let v = validate_candidate_exhaustive::<MockValidationBackend, _>(
|
let v = validate_candidate_exhaustive::<MockValidationBackend, _>(
|
||||||
MockValidationArg { result: Ok(validation_result) },
|
MockValidationArg { result: Ok(validation_result) },
|
||||||
@@ -845,7 +856,7 @@ mod tests {
|
|||||||
descriptor.pov_hash = pov.hash();
|
descriptor.pov_hash = pov.hash();
|
||||||
collator_sign(&mut descriptor, Sr25519Keyring::Alice);
|
collator_sign(&mut descriptor, Sr25519Keyring::Alice);
|
||||||
|
|
||||||
assert!(passes_basic_checks(&descriptor, Some(1024), &pov));
|
assert!(perform_basic_checks(&descriptor, Some(1024), &pov).is_ok());
|
||||||
|
|
||||||
let validation_result = WasmValidationResult {
|
let validation_result = WasmValidationResult {
|
||||||
head_data: HeadData(vec![1, 1, 1]),
|
head_data: HeadData(vec![1, 1, 1]),
|
||||||
@@ -858,10 +869,14 @@ mod tests {
|
|||||||
&omitted_validation.global_validation,
|
&omitted_validation.global_validation,
|
||||||
&omitted_validation.local_validation,
|
&omitted_validation.local_validation,
|
||||||
&validation_result,
|
&validation_result,
|
||||||
));
|
).is_ok());
|
||||||
|
|
||||||
let v = validate_candidate_exhaustive::<MockValidationBackend, _>(
|
let v = validate_candidate_exhaustive::<MockValidationBackend, _>(
|
||||||
MockValidationArg { result: Err(wasm_executor::Error::BadReturn) },
|
MockValidationArg {
|
||||||
|
result: Err(ValidationError::InvalidCandidate(
|
||||||
|
WasmInvalidCandidate::BadReturn
|
||||||
|
))
|
||||||
|
},
|
||||||
omitted_validation.clone(),
|
omitted_validation.clone(),
|
||||||
vec![1, 2, 3].into(),
|
vec![1, 2, 3].into(),
|
||||||
descriptor,
|
descriptor,
|
||||||
@@ -869,7 +884,7 @@ mod tests {
|
|||||||
TaskExecutor::new(),
|
TaskExecutor::new(),
|
||||||
).unwrap();
|
).unwrap();
|
||||||
|
|
||||||
assert_matches!(v, ValidationResult::Invalid);
|
assert_matches!(v, ValidationResult::Invalid(InvalidCandidate::BadReturn));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -889,7 +904,7 @@ mod tests {
|
|||||||
descriptor.pov_hash = pov.hash();
|
descriptor.pov_hash = pov.hash();
|
||||||
collator_sign(&mut descriptor, Sr25519Keyring::Alice);
|
collator_sign(&mut descriptor, Sr25519Keyring::Alice);
|
||||||
|
|
||||||
assert!(passes_basic_checks(&descriptor, Some(1024), &pov));
|
assert!(perform_basic_checks(&descriptor, Some(1024), &pov).is_ok());
|
||||||
|
|
||||||
let validation_result = WasmValidationResult {
|
let validation_result = WasmValidationResult {
|
||||||
head_data: HeadData(vec![1, 1, 1]),
|
head_data: HeadData(vec![1, 1, 1]),
|
||||||
@@ -902,10 +917,14 @@ mod tests {
|
|||||||
&omitted_validation.global_validation,
|
&omitted_validation.global_validation,
|
||||||
&omitted_validation.local_validation,
|
&omitted_validation.local_validation,
|
||||||
&validation_result,
|
&validation_result,
|
||||||
));
|
).is_ok());
|
||||||
|
|
||||||
let v = validate_candidate_exhaustive::<MockValidationBackend, _>(
|
let v = validate_candidate_exhaustive::<MockValidationBackend, _>(
|
||||||
MockValidationArg { result: Err(wasm_executor::Error::Timeout) },
|
MockValidationArg {
|
||||||
|
result: Err(ValidationError::InvalidCandidate(
|
||||||
|
WasmInvalidCandidate::Timeout
|
||||||
|
))
|
||||||
|
},
|
||||||
omitted_validation.clone(),
|
omitted_validation.clone(),
|
||||||
vec![1, 2, 3].into(),
|
vec![1, 2, 3].into(),
|
||||||
descriptor,
|
descriptor,
|
||||||
@@ -913,6 +932,6 @@ mod tests {
|
|||||||
TaskExecutor::new(),
|
TaskExecutor::new(),
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_matches!(v, Err(ValidationFailed));
|
assert_matches!(v, Ok(ValidationResult::Invalid(InvalidCandidate::Timeout)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -129,13 +129,38 @@ pub struct ValidationOutputs {
|
|||||||
pub new_validation_code: Option<ValidationCode>,
|
pub new_validation_code: Option<ValidationCode>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Candidate invalidity details
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum InvalidCandidate {
|
||||||
|
/// Failed to execute.`validate_block`. This includes function panicking.
|
||||||
|
ExecutionError(String),
|
||||||
|
/// Execution timeout.
|
||||||
|
Timeout,
|
||||||
|
/// Validation input is over the limit.
|
||||||
|
ParamsTooLarge(u64),
|
||||||
|
/// Code size is over the limit.
|
||||||
|
CodeTooLarge(u64),
|
||||||
|
/// Validation function returned invalid data.
|
||||||
|
BadReturn,
|
||||||
|
/// Invalid relay chain parent.
|
||||||
|
BadParent,
|
||||||
|
/// POV hash does not match.
|
||||||
|
HashMismatch,
|
||||||
|
/// Bad collator signature.
|
||||||
|
BadSignature,
|
||||||
|
/// Output code is too large
|
||||||
|
NewCodeTooLarge(u64),
|
||||||
|
/// Head-data is over the limit.
|
||||||
|
HeadDataTooLarge(u64),
|
||||||
|
}
|
||||||
|
|
||||||
/// Result of the validation of the candidate.
|
/// Result of the validation of the candidate.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum ValidationResult {
|
pub enum ValidationResult {
|
||||||
/// Candidate is valid. The validation process yields these outputs.
|
/// Candidate is valid. The validation process yields these outputs.
|
||||||
Valid(ValidationOutputs),
|
Valid(ValidationOutputs),
|
||||||
/// Candidate is invalid.
|
/// Candidate is invalid.
|
||||||
Invalid,
|
Invalid(InvalidCandidate),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::convert::TryFrom<FromTableMisbehavior> for MisbehaviorReport {
|
impl std::convert::TryFrom<FromTableMisbehavior> for MisbehaviorReport {
|
||||||
|
|||||||
@@ -87,9 +87,9 @@ impl CandidateBackingMessage {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Blanket error for validation failing.
|
/// Blanket error for validation failing for internal reasons.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct ValidationFailed;
|
pub struct ValidationFailed(pub String);
|
||||||
|
|
||||||
/// Messages received by the Validation subsystem.
|
/// Messages received by the Validation subsystem.
|
||||||
///
|
///
|
||||||
|
|||||||
@@ -69,9 +69,18 @@ pub enum ExecutionMode<'a> {
|
|||||||
RemoteTest(&'a ValidationPool),
|
RemoteTest(&'a ValidationPool),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Error type for the wasm executor
|
|
||||||
#[derive(Debug, derive_more::Display, derive_more::From)]
|
#[derive(Debug, derive_more::Display, derive_more::From)]
|
||||||
pub enum Error {
|
/// Candidate validation error.
|
||||||
|
pub enum ValidationError {
|
||||||
|
/// Validation failed due to internal reasons. The candidate might still be valid.
|
||||||
|
Internal(InternalError),
|
||||||
|
/// Candidate is invalid.
|
||||||
|
InvalidCandidate(InvalidCandidate),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Error type that indicates invalid candidate.
|
||||||
|
#[derive(Debug, derive_more::Display, derive_more::From)]
|
||||||
|
pub enum InvalidCandidate {
|
||||||
/// Wasm executor error.
|
/// Wasm executor error.
|
||||||
#[display(fmt = "WASM executor error: {:?}", _0)]
|
#[display(fmt = "WASM executor error: {:?}", _0)]
|
||||||
WasmExecutor(sc_executor::error::Error),
|
WasmExecutor(sc_executor::error::Error),
|
||||||
@@ -82,30 +91,37 @@ pub enum Error {
|
|||||||
/// Code size it too large.
|
/// Code size it too large.
|
||||||
#[display(fmt = "WASM code is {} bytes, max allowed is {}", _0, MAX_CODE_MEM)]
|
#[display(fmt = "WASM code is {} bytes, max allowed is {}", _0, MAX_CODE_MEM)]
|
||||||
CodeTooLarge(usize),
|
CodeTooLarge(usize),
|
||||||
/// Bad return data or type.
|
/// Error decoding returned data.
|
||||||
#[display(fmt = "Validation function returned invalid data.")]
|
#[display(fmt = "Validation function returned invalid data.")]
|
||||||
BadReturn,
|
BadReturn,
|
||||||
#[display(fmt = "Validation function timeout.")]
|
#[display(fmt = "Validation function timeout.")]
|
||||||
Timeout,
|
Timeout,
|
||||||
|
#[display(fmt = "External WASM execution error: {}", _0)]
|
||||||
|
ExternalWasmExecutor(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Host error during candidate validation. This does not indicate an invalid candidate.
|
||||||
|
#[derive(Debug, derive_more::Display, derive_more::From)]
|
||||||
|
pub enum InternalError {
|
||||||
#[display(fmt = "IO error: {}", _0)]
|
#[display(fmt = "IO error: {}", _0)]
|
||||||
Io(std::io::Error),
|
Io(std::io::Error),
|
||||||
#[display(fmt = "System error: {}", _0)]
|
#[display(fmt = "System error: {}", _0)]
|
||||||
System(Box<dyn std::error::Error + Send>),
|
System(Box<dyn std::error::Error + Send>),
|
||||||
#[display(fmt = "WASM worker error: {}", _0)]
|
|
||||||
External(String),
|
|
||||||
#[display(fmt = "Shared memory error: {}", _0)]
|
#[display(fmt = "Shared memory error: {}", _0)]
|
||||||
#[cfg(not(any(target_os = "android", target_os = "unknown")))]
|
#[cfg(not(any(target_os = "android", target_os = "unknown")))]
|
||||||
SharedMem(shared_memory::SharedMemError),
|
SharedMem(shared_memory::SharedMemError),
|
||||||
|
#[display(fmt = "WASM worker error: {}", _0)]
|
||||||
|
WasmWorker(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::error::Error for Error {
|
impl std::error::Error for ValidationError {
|
||||||
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
|
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
|
||||||
match self {
|
match self {
|
||||||
Error::WasmExecutor(ref err) => Some(err),
|
ValidationError::Internal(InternalError::Io(ref err)) => Some(err),
|
||||||
Error::Io(ref err) => Some(err),
|
ValidationError::Internal(InternalError::System(ref err)) => Some(&**err),
|
||||||
Error::System(ref err) => Some(&**err),
|
|
||||||
#[cfg(not(any(target_os = "android", target_os = "unknown")))]
|
#[cfg(not(any(target_os = "android", target_os = "unknown")))]
|
||||||
Error::SharedMem(ref err) => Some(err),
|
ValidationError::Internal(InternalError::SharedMem(ref err)) => Some(err),
|
||||||
|
ValidationError::InvalidCandidate(InvalidCandidate::WasmExecutor(ref err)) => Some(err),
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -119,7 +135,7 @@ pub fn validate_candidate(
|
|||||||
params: ValidationParams,
|
params: ValidationParams,
|
||||||
options: ExecutionMode<'_>,
|
options: ExecutionMode<'_>,
|
||||||
spawner: impl SpawnNamed + 'static,
|
spawner: impl SpawnNamed + 'static,
|
||||||
) -> Result<ValidationResult, Error> {
|
) -> Result<ValidationResult, ValidationError> {
|
||||||
match options {
|
match options {
|
||||||
ExecutionMode::Local => {
|
ExecutionMode::Local => {
|
||||||
validate_candidate_internal(validation_code, ¶ms.encode(), spawner)
|
validate_candidate_internal(validation_code, ¶ms.encode(), spawner)
|
||||||
@@ -133,15 +149,19 @@ pub fn validate_candidate(
|
|||||||
pool.validate_candidate(validation_code, params, true)
|
pool.validate_candidate(validation_code, params, true)
|
||||||
},
|
},
|
||||||
#[cfg(any(target_os = "android", target_os = "unknown"))]
|
#[cfg(any(target_os = "android", target_os = "unknown"))]
|
||||||
ExecutionMode::Remote(pool) =>
|
ExecutionMode::Remote(_pool) =>
|
||||||
Err(Error::System(Box::<dyn std::error::Error + Send + Sync>::from(
|
Err(ValidationError::Internal(InternalError::System(
|
||||||
|
Box::<dyn std::error::Error + Send + Sync>::from(
|
||||||
"Remote validator not available".to_string()
|
"Remote validator not available".to_string()
|
||||||
) as Box<_>)),
|
) as Box<_>
|
||||||
|
))),
|
||||||
#[cfg(any(target_os = "android", target_os = "unknown"))]
|
#[cfg(any(target_os = "android", target_os = "unknown"))]
|
||||||
ExecutionMode::RemoteTest(pool) =>
|
ExecutionMode::RemoteTest(_pool) =>
|
||||||
Err(Error::System(Box::<dyn std::error::Error + Send + Sync>::from(
|
Err(ValidationError::Internal(InternalError::System(
|
||||||
|
Box::<dyn std::error::Error + Send + Sync>::from(
|
||||||
"Remote validator not available".to_string()
|
"Remote validator not available".to_string()
|
||||||
) as Box<_>)),
|
) as Box<_>
|
||||||
|
))),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -155,7 +175,7 @@ pub fn validate_candidate_internal(
|
|||||||
validation_code: &[u8],
|
validation_code: &[u8],
|
||||||
encoded_call_data: &[u8],
|
encoded_call_data: &[u8],
|
||||||
spawner: impl SpawnNamed + 'static,
|
spawner: impl SpawnNamed + 'static,
|
||||||
) -> Result<ValidationResult, Error> {
|
) -> Result<ValidationResult, ValidationError> {
|
||||||
let mut extensions = Extensions::new();
|
let mut extensions = Extensions::new();
|
||||||
extensions.register(sp_core::traits::TaskExecutorExt::new(spawner));
|
extensions.register(sp_core::traits::TaskExecutorExt::new(spawner));
|
||||||
|
|
||||||
@@ -175,9 +195,10 @@ pub fn validate_candidate_internal(
|
|||||||
encoded_call_data,
|
encoded_call_data,
|
||||||
&mut ext,
|
&mut ext,
|
||||||
sp_core::traits::MissingHostFunctions::Allow,
|
sp_core::traits::MissingHostFunctions::Allow,
|
||||||
)?;
|
).map_err(|e| ValidationError::InvalidCandidate(e.into()))?;
|
||||||
|
|
||||||
ValidationResult::decode(&mut &res[..]).map_err(|_| Error::BadReturn.into())
|
ValidationResult::decode(&mut &res[..])
|
||||||
|
.map_err(|_| ValidationError::InvalidCandidate(InvalidCandidate::BadReturn).into())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The validation externalities that will panic on any storage related access. They just provide
|
/// The validation externalities that will panic on any storage related access. They just provide
|
||||||
|
|||||||
@@ -19,7 +19,8 @@
|
|||||||
use std::{process, env, sync::Arc, sync::atomic};
|
use std::{process, env, sync::Arc, sync::atomic};
|
||||||
use codec::{Decode, Encode};
|
use codec::{Decode, Encode};
|
||||||
use crate::primitives::{ValidationParams, ValidationResult};
|
use crate::primitives::{ValidationParams, ValidationResult};
|
||||||
use super::{validate_candidate_internal, Error, MAX_CODE_MEM, MAX_RUNTIME_MEM};
|
use super::{validate_candidate_internal, ValidationError, InvalidCandidate, InternalError,
|
||||||
|
MAX_CODE_MEM, MAX_RUNTIME_MEM};
|
||||||
use shared_memory::{SharedMem, SharedMemConf, EventState, WriteLockable, EventWait, EventSet};
|
use shared_memory::{SharedMem, SharedMemConf, EventState, WriteLockable, EventWait, EventSet};
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
use log::{debug, trace};
|
use log::{debug, trace};
|
||||||
@@ -88,7 +89,7 @@ impl ValidationPool {
|
|||||||
validation_code: &[u8],
|
validation_code: &[u8],
|
||||||
params: ValidationParams,
|
params: ValidationParams,
|
||||||
test_mode: bool,
|
test_mode: bool,
|
||||||
) -> Result<ValidationResult, Error> {
|
) -> Result<ValidationResult, ValidationError> {
|
||||||
for host in self.hosts.iter() {
|
for host in self.hosts.iter() {
|
||||||
if let Some(mut host) = host.try_lock() {
|
if let Some(mut host) = host.try_lock() {
|
||||||
return host.validate_candidate(validation_code, params, test_mode);
|
return host.validate_candidate(validation_code, params, test_mode);
|
||||||
@@ -165,7 +166,10 @@ pub fn run_worker(mem_id: &str) -> Result<(), String> {
|
|||||||
|
|
||||||
match result {
|
match result {
|
||||||
Ok(r) => ValidationResultHeader::Ok(r),
|
Ok(r) => ValidationResultHeader::Ok(r),
|
||||||
Err(e) => ValidationResultHeader::Error(e.to_string()),
|
Err(ValidationError::Internal(e)) =>
|
||||||
|
ValidationResultHeader::Error(WorkerValidationError::InternalError(e.to_string())),
|
||||||
|
Err(ValidationError::InvalidCandidate(e)) =>
|
||||||
|
ValidationResultHeader::Error(WorkerValidationError::ValidationError(e.to_string())),
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let mut data: &mut[u8] = &mut **slice;
|
let mut data: &mut[u8] = &mut **slice;
|
||||||
@@ -186,9 +190,15 @@ struct ValidationHeader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Encode, Decode, Debug)]
|
#[derive(Encode, Decode, Debug)]
|
||||||
pub enum ValidationResultHeader {
|
enum WorkerValidationError {
|
||||||
|
InternalError(String),
|
||||||
|
ValidationError(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Encode, Decode, Debug)]
|
||||||
|
enum ValidationResultHeader {
|
||||||
Ok(ValidationResult),
|
Ok(ValidationResult),
|
||||||
Error(String),
|
Error(WorkerValidationError),
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe impl Send for ValidationHost {}
|
unsafe impl Send for ValidationHost {}
|
||||||
@@ -209,7 +219,7 @@ impl Drop for ValidationHost {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl ValidationHost {
|
impl ValidationHost {
|
||||||
fn create_memory() -> Result<SharedMem, Error> {
|
fn create_memory() -> Result<SharedMem, InternalError> {
|
||||||
let mem_size = MAX_RUNTIME_MEM + MAX_CODE_MEM + 1024;
|
let mem_size = MAX_RUNTIME_MEM + MAX_CODE_MEM + 1024;
|
||||||
let mem_config = SharedMemConf::default()
|
let mem_config = SharedMemConf::default()
|
||||||
.set_size(mem_size)
|
.set_size(mem_size)
|
||||||
@@ -221,7 +231,7 @@ impl ValidationHost {
|
|||||||
Ok(mem_config.create()?)
|
Ok(mem_config.create()?)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn start_worker(&mut self, test_mode: bool) -> Result<(), Error> {
|
fn start_worker(&mut self, test_mode: bool) -> Result<(), InternalError> {
|
||||||
if let Some(ref mut worker) = self.worker {
|
if let Some(ref mut worker) = self.worker {
|
||||||
// Check if still alive
|
// Check if still alive
|
||||||
if let Ok(None) = worker.try_wait() {
|
if let Ok(None) = worker.try_wait() {
|
||||||
@@ -257,9 +267,9 @@ impl ValidationHost {
|
|||||||
validation_code: &[u8],
|
validation_code: &[u8],
|
||||||
params: ValidationParams,
|
params: ValidationParams,
|
||||||
test_mode: bool,
|
test_mode: bool,
|
||||||
) -> Result<ValidationResult, Error> {
|
) -> Result<ValidationResult, ValidationError> {
|
||||||
if validation_code.len() > MAX_CODE_MEM {
|
if validation_code.len() > MAX_CODE_MEM {
|
||||||
return Err(Error::CodeTooLarge(validation_code.len()));
|
return Err(ValidationError::InvalidCandidate(InvalidCandidate::CodeTooLarge(validation_code.len())));
|
||||||
}
|
}
|
||||||
// First, check if need to spawn the child process
|
// First, check if need to spawn the child process
|
||||||
self.start_worker(test_mode)?;
|
self.start_worker(test_mode)?;
|
||||||
@@ -267,7 +277,8 @@ impl ValidationHost {
|
|||||||
.expect("memory is always `Some` after `start_worker` completes successfully");
|
.expect("memory is always `Some` after `start_worker` completes successfully");
|
||||||
{
|
{
|
||||||
// Put data in shared mem
|
// Put data in shared mem
|
||||||
let data: &mut[u8] = &mut **memory.wlock_as_slice(0)?;
|
let data: &mut[u8] = &mut **memory.wlock_as_slice(0)
|
||||||
|
.map_err(|e|ValidationError::Internal(e.into()))?;
|
||||||
let (mut header_buf, rest) = data.split_at_mut(1024);
|
let (mut header_buf, rest) = data.split_at_mut(1024);
|
||||||
let (code, rest) = rest.split_at_mut(MAX_CODE_MEM);
|
let (code, rest) = rest.split_at_mut(MAX_CODE_MEM);
|
||||||
let (code, _) = code.split_at_mut(validation_code.len());
|
let (code, _) = code.split_at_mut(validation_code.len());
|
||||||
@@ -275,7 +286,7 @@ impl ValidationHost {
|
|||||||
code[..validation_code.len()].copy_from_slice(validation_code);
|
code[..validation_code.len()].copy_from_slice(validation_code);
|
||||||
let encoded_params = params.encode();
|
let encoded_params = params.encode();
|
||||||
if encoded_params.len() >= MAX_RUNTIME_MEM {
|
if encoded_params.len() >= MAX_RUNTIME_MEM {
|
||||||
return Err(Error::ParamsTooLarge(MAX_RUNTIME_MEM));
|
return Err(ValidationError::InvalidCandidate(InvalidCandidate::ParamsTooLarge(MAX_RUNTIME_MEM)));
|
||||||
}
|
}
|
||||||
call_data[..encoded_params.len()].copy_from_slice(&encoded_params);
|
call_data[..encoded_params.len()].copy_from_slice(&encoded_params);
|
||||||
|
|
||||||
@@ -288,7 +299,8 @@ impl ValidationHost {
|
|||||||
}
|
}
|
||||||
|
|
||||||
debug!("{} Signaling candidate", self.id);
|
debug!("{} Signaling candidate", self.id);
|
||||||
memory.set(Event::CandidateReady as usize, EventState::Signaled)?;
|
memory.set(Event::CandidateReady as usize, EventState::Signaled)
|
||||||
|
.map_err(|e| ValidationError::Internal(e.into()))?;
|
||||||
|
|
||||||
debug!("{} Waiting for results", self.id);
|
debug!("{} Waiting for results", self.id);
|
||||||
match memory.wait(Event::ResultReady as usize, shared_memory::Timeout::Sec(EXECUTION_TIMEOUT_SEC as usize)) {
|
match memory.wait(Event::ResultReady as usize, shared_memory::Timeout::Sec(EXECUTION_TIMEOUT_SEC as usize)) {
|
||||||
@@ -297,22 +309,27 @@ impl ValidationHost {
|
|||||||
if let Some(mut worker) = self.worker.take() {
|
if let Some(mut worker) = self.worker.take() {
|
||||||
worker.kill().ok();
|
worker.kill().ok();
|
||||||
}
|
}
|
||||||
return Err(Error::Timeout.into());
|
return Err(ValidationError::InvalidCandidate(InvalidCandidate::Timeout));
|
||||||
}
|
}
|
||||||
Ok(()) => {}
|
Ok(()) => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
debug!("{} Reading results", self.id);
|
debug!("{} Reading results", self.id);
|
||||||
let data: &[u8] = &**memory.wlock_as_slice(0)?;
|
let data: &[u8] = &**memory.wlock_as_slice(0)
|
||||||
|
.map_err(|e| ValidationError::Internal(e.into()))?;
|
||||||
let (header_buf, _) = data.split_at(1024);
|
let (header_buf, _) = data.split_at(1024);
|
||||||
let mut header_buf: &[u8] = header_buf;
|
let mut header_buf: &[u8] = header_buf;
|
||||||
let header = ValidationResultHeader::decode(&mut header_buf).unwrap();
|
let header = ValidationResultHeader::decode(&mut header_buf).unwrap();
|
||||||
match header {
|
match header {
|
||||||
ValidationResultHeader::Ok(result) => Ok(result),
|
ValidationResultHeader::Ok(result) => Ok(result),
|
||||||
ValidationResultHeader::Error(message) => {
|
ValidationResultHeader::Error(WorkerValidationError::InternalError(e)) => {
|
||||||
debug!("{} Validation error: {}", self.id, message);
|
debug!("{} Internal validation error: {}", self.id, e);
|
||||||
Err(Error::External(message).into())
|
Err(ValidationError::Internal(InternalError::WasmWorker(e)))
|
||||||
|
},
|
||||||
|
ValidationResultHeader::Error(WorkerValidationError::ValidationError(e)) => {
|
||||||
|
debug!("{} External validation error: {}", self.id, e);
|
||||||
|
Err(ValidationError::InvalidCandidate(InvalidCandidate::ExternalWasmExecutor(e)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
use crate::adder;
|
use crate::adder;
|
||||||
use parachain::{
|
use parachain::{
|
||||||
primitives::{BlockData, ValidationParams},
|
primitives::{BlockData, ValidationParams},
|
||||||
wasm_executor::EXECUTION_TIMEOUT_SEC,
|
wasm_executor::{ValidationError, InvalidCandidate, EXECUTION_TIMEOUT_SEC},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -40,7 +40,7 @@ fn terminates_on_timeout() {
|
|||||||
sp_core::testing::TaskExecutor::new(),
|
sp_core::testing::TaskExecutor::new(),
|
||||||
);
|
);
|
||||||
match result {
|
match result {
|
||||||
Err(parachain::wasm_executor::Error::Timeout) => {},
|
Err(ValidationError::InvalidCandidate(InvalidCandidate::Timeout)) => {},
|
||||||
r => panic!("{:?}", r),
|
r => panic!("{:?}", r),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ pub enum Error {
|
|||||||
/// Consensus error
|
/// Consensus error
|
||||||
Consensus(consensus::error::Error),
|
Consensus(consensus::error::Error),
|
||||||
/// A wasm-validation error.
|
/// A wasm-validation error.
|
||||||
WasmValidation(parachain::wasm_executor::Error),
|
WasmValidation(parachain::wasm_executor::ValidationError),
|
||||||
/// An I/O error.
|
/// An I/O error.
|
||||||
Io(std::io::Error),
|
Io(std::io::Error),
|
||||||
/// An error in the availability erasure-coding.
|
/// An error in the availability erasure-coding.
|
||||||
|
|||||||
Reference in New Issue
Block a user