// Copyright 2020 Parity Technologies (UK) Ltd. // This file is part of Polkadot. // Polkadot is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // Polkadot is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License // along with Polkadot. If not, see . //! The Candidate Validation subsystem. //! //! This handles incoming requests from other subsystems to validate candidates //! according to a validation function. This delegates validation to an underlying //! pool of processes used for execution of the Wasm. use polkadot_subsystem::{ Subsystem, SubsystemContext, SpawnedSubsystem, SubsystemResult, FromOverseer, OverseerSignal, }; use polkadot_subsystem::messages::{ AllMessages, CandidateValidationMessage, RuntimeApiMessage, ValidationFailed, RuntimeApiRequest, }; use polkadot_subsystem::errors::RuntimeApiError; use polkadot_node_primitives::{ValidationResult, ValidationOutputs, InvalidCandidate}; use polkadot_primitives::v1::{ ValidationCode, OmittedValidationData, PoV, CandidateDescriptor, LocalValidationData, GlobalValidationData, OccupiedCoreAssumption, Hash, validation_data_hash, }; use polkadot_parachain::wasm_executor::{self, ValidationPool, ExecutionMode, ValidationError, InvalidCandidate as WasmInvalidCandidate}; use polkadot_parachain::primitives::{ValidationResult as WasmValidationResult, ValidationParams}; use parity_scale_codec::Encode; use sp_core::traits::SpawnNamed; use futures::channel::oneshot; use futures::prelude::*; use std::sync::Arc; /// The candidate validation subsystem. pub struct CandidateValidationSubsystem(S); impl CandidateValidationSubsystem { /// Create a new `CandidateValidationSubsystem` with the given task spawner. pub fn new(spawn: S) -> Self { CandidateValidationSubsystem(spawn) } } impl Subsystem for CandidateValidationSubsystem where C: SubsystemContext, S: SpawnNamed + Clone + 'static, { fn start(self, ctx: C) -> SpawnedSubsystem { SpawnedSubsystem { name: "candidate-validation-subsystem", future: run(ctx, self.0).map(|_| ()).boxed(), } } } async fn run( mut ctx: impl SubsystemContext, spawn: impl SpawnNamed + Clone + 'static, ) -> SubsystemResult<()> { let pool = ValidationPool::new(); loop { match ctx.recv().await? { FromOverseer::Signal(OverseerSignal::ActiveLeaves(_)) => {} FromOverseer::Signal(OverseerSignal::BlockFinalized(_)) => {} FromOverseer::Signal(OverseerSignal::Conclude) => return Ok(()), FromOverseer::Communication { msg } => match msg { CandidateValidationMessage::ValidateFromChainState( descriptor, pov, response_sender, ) => { let res = spawn_validate_from_chain_state( &mut ctx, Some(pool.clone()), descriptor, pov, spawn.clone(), ).await; match res { Ok(x) => { let _ = response_sender.send(x); } Err(e)=> return Err(e), } } CandidateValidationMessage::ValidateFromExhaustive( omitted_validation, validation_code, descriptor, pov, response_sender, ) => { let res = spawn_validate_exhaustive( &mut ctx, Some(pool.clone()), omitted_validation, validation_code, descriptor, pov, spawn.clone(), ).await; match res { Ok(x) => if let Err(_e) = response_sender.send(x) { log::warn!( target: "candidate_validation", "Requester of candidate validation dropped", ) }, Err(e)=> return Err(e), } } } } } } async fn runtime_api_request( ctx: &mut impl SubsystemContext, relay_parent: Hash, request: RuntimeApiRequest, receiver: oneshot::Receiver>, ) -> SubsystemResult> { ctx.send_message( AllMessages::RuntimeApi(RuntimeApiMessage::Request( relay_parent, request, )) ).await?; receiver.await.map_err(Into::into) } #[derive(Debug)] enum AssumptionCheckOutcome { Matches(OmittedValidationData, ValidationCode), DoesNotMatch, BadRequest, } async fn check_assumption_validation_data( ctx: &mut impl SubsystemContext, descriptor: &CandidateDescriptor, global_validation_data: &GlobalValidationData, assumption: OccupiedCoreAssumption, ) -> SubsystemResult { let local_validation_data = { let (tx, rx) = oneshot::channel(); let d = runtime_api_request( ctx, descriptor.relay_parent, RuntimeApiRequest::LocalValidationData( descriptor.para_id, assumption, tx, ), rx, ).await?; match d { Ok(None) | Err(_) => { return Ok(AssumptionCheckOutcome::BadRequest); } Ok(Some(d)) => d, } }; let validation_data_hash = validation_data_hash( &global_validation_data, &local_validation_data, ); SubsystemResult::Ok(if descriptor.validation_data_hash == validation_data_hash { let omitted_validation = OmittedValidationData { global_validation: global_validation_data.clone(), local_validation: local_validation_data, }; let (code_tx, code_rx) = oneshot::channel(); let validation_code = runtime_api_request( ctx, descriptor.relay_parent, RuntimeApiRequest::ValidationCode( descriptor.para_id, OccupiedCoreAssumption::Included, code_tx, ), code_rx, ).await?; match validation_code { Ok(None) | Err(_) => AssumptionCheckOutcome::BadRequest, Ok(Some(v)) => AssumptionCheckOutcome::Matches(omitted_validation, v), } } else { AssumptionCheckOutcome::DoesNotMatch }) } async fn spawn_validate_from_chain_state( ctx: &mut impl SubsystemContext, validation_pool: Option, descriptor: CandidateDescriptor, pov: Arc, spawn: impl SpawnNamed + 'static, ) -> SubsystemResult> { // The candidate descriptor has a `validation_data_hash` which corresponds to // one of up to two possible values that we can derive from the state of the // relay-parent. We can fetch these values by getting the `global_validation_data`, // and both `local_validation_data` based on the different `OccupiedCoreAssumption`s. let global_validation_data = { let (tx, rx) = oneshot::channel(); let res = runtime_api_request( ctx, descriptor.relay_parent, RuntimeApiRequest::GlobalValidationData(tx), rx, ).await?; match res { Ok(g) => g, Err(e) => { log::warn!( target: "candidate_validation", "Error making runtime API request: {:?}", e, ); return Ok(Err(ValidationFailed("Error making API request".into()))); } } }; match check_assumption_validation_data( ctx, &descriptor, &global_validation_data, OccupiedCoreAssumption::Included, ).await? { AssumptionCheckOutcome::Matches(omitted_validation, validation_code) => { return spawn_validate_exhaustive( ctx, validation_pool, omitted_validation, validation_code, descriptor, pov, spawn, ).await; } AssumptionCheckOutcome::DoesNotMatch => {}, AssumptionCheckOutcome::BadRequest => return Ok(Err(ValidationFailed("Bad request".into()))), } match check_assumption_validation_data( ctx, &descriptor, &global_validation_data, OccupiedCoreAssumption::TimedOut, ).await? { AssumptionCheckOutcome::Matches(omitted_validation, validation_code) => { return spawn_validate_exhaustive( ctx, validation_pool, omitted_validation, validation_code, descriptor, pov, spawn, ).await; } AssumptionCheckOutcome::DoesNotMatch => {}, AssumptionCheckOutcome::BadRequest => return Ok(Err(ValidationFailed("Bad request".into()))), } // 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 // is not based on the relay parent and is thus invalid. Ok(Ok(ValidationResult::Invalid(InvalidCandidate::BadParent))) } async fn spawn_validate_exhaustive( ctx: &mut impl SubsystemContext, validation_pool: Option, omitted_validation: OmittedValidationData, validation_code: ValidationCode, descriptor: CandidateDescriptor, pov: Arc, spawn: impl SpawnNamed + 'static, ) -> SubsystemResult> { let (tx, rx) = oneshot::channel(); let fut = async move { let res = validate_candidate_exhaustive::( validation_pool, omitted_validation, validation_code, descriptor, pov, spawn, ); let _ = tx.send(res); }; ctx.spawn("blocking-candidate-validation-task", fut.boxed()).await?; rx.await.map_err(Into::into) } /// Does basic checks of a candidate. Provide the encoded PoV-block. Returns `Ok` if basic checks /// are passed, `Err` otherwise. fn perform_basic_checks( candidate: &CandidateDescriptor, max_block_data_size: Option, pov: &PoV, ) -> Result<(), InvalidCandidate> { let encoded_pov = pov.encode(); let hash = pov.hash(); if let Some(max_size) = max_block_data_size { if encoded_pov.len() as u64 > max_size { return Err(InvalidCandidate::ParamsTooLarge(encoded_pov.len() as u64)); } } if hash != candidate.pov_hash { return Err(InvalidCandidate::HashMismatch); } if let Err(()) = candidate.check_collator_signature() { return Err(InvalidCandidate::BadSignature); } Ok(()) } /// Check the result of Wasm execution against the constraints given by the relay-chain. /// /// Returns `Ok(())` if checks pass, error otherwise. fn check_wasm_result_against_constraints( global_validation_data: &GlobalValidationData, _local_validation_data: &LocalValidationData, result: &WasmValidationResult, ) -> Result<(), InvalidCandidate> { if result.head_data.0.len() > global_validation_data.max_head_data_size as _ { return Err(InvalidCandidate::HeadDataTooLarge(result.head_data.0.len() as u64)) } if let Some(ref code) = result.new_validation_code { if code.0.len() > global_validation_data.max_code_size as _ { return Err(InvalidCandidate::NewCodeTooLarge(code.0.len() as u64)) } } Ok(()) } trait ValidationBackend { type Arg; fn validate( arg: Self::Arg, validation_code: &ValidationCode, params: ValidationParams, spawn: S, ) -> Result; } struct RealValidationBackend; impl ValidationBackend for RealValidationBackend { type Arg = Option; fn validate( pool: Option, validation_code: &ValidationCode, params: ValidationParams, spawn: S, ) -> Result { let execution_mode = pool.as_ref() .map(ExecutionMode::Remote) .unwrap_or(ExecutionMode::Local); wasm_executor::validate_candidate( &validation_code.0, params, execution_mode, spawn, ) } } /// Validates the candidate from exhaustive parameters. /// /// Sends the result of validation on the channel once complete. fn validate_candidate_exhaustive( backend_arg: B::Arg, omitted_validation: OmittedValidationData, validation_code: ValidationCode, descriptor: CandidateDescriptor, pov: Arc, spawn: S, ) -> Result { if let Err(e) = perform_basic_checks(&descriptor, None, &*pov) { return Ok(ValidationResult::Invalid(e)) } let OmittedValidationData { global_validation, local_validation } = omitted_validation; let params = ValidationParams { parent_head: local_validation.parent_head.clone(), block_data: pov.block_data.clone(), max_code_size: global_validation.max_code_size, max_head_data_size: global_validation.max_head_data_size, relay_chain_height: global_validation.block_number, code_upgrade_allowed: local_validation.code_upgrade_allowed, }; match B::validate(backend_arg, &validation_code, params, spawn) { Err(ValidationError::InvalidCandidate(WasmInvalidCandidate::Timeout)) => 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) => { let post_check_result = check_wasm_result_against_constraints( &global_validation, &local_validation, &res, ); Ok(match post_check_result { Ok(()) => ValidationResult::Valid(ValidationOutputs { head_data: res.head_data, global_validation_data: global_validation, local_validation_data: local_validation, upward_messages: res.upward_messages, fees: 0, new_validation_code: res.new_validation_code, }), Err(e) => ValidationResult::Invalid(e), }) } } } #[cfg(test)] mod tests { use super::*; use polkadot_subsystem::test_helpers; use polkadot_primitives::v1::{HeadData, BlockData}; use sp_core::testing::TaskExecutor; use futures::executor; use assert_matches::assert_matches; use sp_keyring::Sr25519Keyring; struct MockValidationBackend; struct MockValidationArg { result: Result, } impl ValidationBackend for MockValidationBackend { type Arg = MockValidationArg; fn validate( arg: Self::Arg, _validation_code: &ValidationCode, _params: ValidationParams, _spawn: S, ) -> Result { arg.result } } 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.validation_data_hash, &descriptor.pov_hash, ); descriptor.signature = collator.sign(&payload[..]).into(); assert!(descriptor.check_collator_signature().is_ok()); } #[test] fn correctly_checks_included_assumption() { let local_validation_data = LocalValidationData::default(); let global_validation_data = GlobalValidationData::default(); let validation_code: ValidationCode = vec![1, 2, 3].into(); let validation_data_hash = validation_data_hash(&global_validation_data, &local_validation_data); let relay_parent = [2; 32].into(); let para_id = 5.into(); let mut candidate = CandidateDescriptor::default(); candidate.relay_parent = relay_parent; candidate.validation_data_hash = 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, &global_validation_data, OccupiedCoreAssumption::Included, ).remote_handle(); let global_validation_data = global_validation_data.clone(); let test_fut = async move { assert_matches!( ctx_handle.recv().await, AllMessages::RuntimeApi(RuntimeApiMessage::Request( rp, RuntimeApiRequest::LocalValidationData(p, OccupiedCoreAssumption::Included, tx) )) => { assert_eq!(rp, relay_parent); assert_eq!(p, para_id); let _ = tx.send(Ok(Some(local_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, OmittedValidationData { local_validation: local_validation_data, global_validation: global_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 local_validation_data = LocalValidationData::default(); let global_validation_data = GlobalValidationData::default(); let validation_code: ValidationCode = vec![1, 2, 3].into(); let validation_data_hash = validation_data_hash(&global_validation_data, &local_validation_data); let relay_parent = [2; 32].into(); let para_id = 5.into(); let mut candidate = CandidateDescriptor::default(); candidate.relay_parent = relay_parent; candidate.validation_data_hash = 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, &global_validation_data, OccupiedCoreAssumption::TimedOut, ).remote_handle(); let global_validation_data = global_validation_data.clone(); let test_fut = async move { assert_matches!( ctx_handle.recv().await, AllMessages::RuntimeApi(RuntimeApiMessage::Request( rp, RuntimeApiRequest::LocalValidationData(p, OccupiedCoreAssumption::TimedOut, tx) )) => { assert_eq!(rp, relay_parent); assert_eq!(p, para_id); let _ = tx.send(Ok(Some(local_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, OmittedValidationData { local_validation: local_validation_data, global_validation: global_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 local_validation_data = LocalValidationData::default(); let global_validation_data = GlobalValidationData::default(); let validation_data_hash = validation_data_hash(&global_validation_data, &local_validation_data); let relay_parent = [2; 32].into(); let para_id = 5.into(); let mut candidate = CandidateDescriptor::default(); candidate.relay_parent = relay_parent; candidate.validation_data_hash = 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, &global_validation_data, OccupiedCoreAssumption::Included, ).remote_handle(); let test_fut = async move { assert_matches!( ctx_handle.recv().await, AllMessages::RuntimeApi(RuntimeApiMessage::Request( rp, RuntimeApiRequest::LocalValidationData(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 local_validation_data = LocalValidationData::default(); let global_validation_data = GlobalValidationData::default(); let validation_data_hash = validation_data_hash(&global_validation_data, &local_validation_data); let relay_parent = [2; 32].into(); let para_id = 5.into(); let mut candidate = CandidateDescriptor::default(); candidate.relay_parent = relay_parent; candidate.validation_data_hash = 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, &global_validation_data, OccupiedCoreAssumption::TimedOut, ).remote_handle(); let test_fut = async move { assert_matches!( ctx_handle.recv().await, AllMessages::RuntimeApi(RuntimeApiMessage::Request( rp, RuntimeApiRequest::LocalValidationData(p, OccupiedCoreAssumption::TimedOut, tx) )) => { assert_eq!(rp, relay_parent); assert_eq!(p, para_id); let _ = tx.send(Ok(Some(local_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(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 local_validation_data = LocalValidationData::default(); let global_validation_data = GlobalValidationData::default(); let relay_parent = [2; 32].into(); let para_id = 5.into(); let mut candidate = CandidateDescriptor::default(); candidate.relay_parent = relay_parent; candidate.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, &global_validation_data, OccupiedCoreAssumption::Included, ).remote_handle(); let test_fut = async move { assert_matches!( ctx_handle.recv().await, AllMessages::RuntimeApi(RuntimeApiMessage::Request( rp, RuntimeApiRequest::LocalValidationData(p, OccupiedCoreAssumption::Included, tx) )) => { assert_eq!(rp, relay_parent); assert_eq!(p, para_id); let _ = tx.send(Ok(Some(local_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); } #[test] fn candidate_validation_ok_is_ok() { let mut omitted_validation = OmittedValidationData { local_validation: Default::default(), global_validation: Default::default(), }; omitted_validation.global_validation.max_head_data_size = 1024; omitted_validation.global_validation.max_code_size = 1024; let pov = PoV { block_data: BlockData(vec![1; 32]) }; let mut descriptor = CandidateDescriptor::default(); descriptor.pov_hash = pov.hash(); collator_sign(&mut descriptor, Sr25519Keyring::Alice); assert!(perform_basic_checks(&descriptor, Some(1024), &pov).is_ok()); let validation_result = WasmValidationResult { head_data: HeadData(vec![1, 1, 1]), new_validation_code: Some(vec![2, 2, 2].into()), upward_messages: Vec::new(), processed_downward_messages: 0, }; assert!(check_wasm_result_against_constraints( &omitted_validation.global_validation, &omitted_validation.local_validation, &validation_result, ).is_ok()); let v = validate_candidate_exhaustive::( MockValidationArg { result: Ok(validation_result) }, omitted_validation.clone(), vec![1, 2, 3].into(), descriptor, Arc::new(pov), TaskExecutor::new(), ).unwrap(); assert_matches!(v, ValidationResult::Valid(outputs) => { assert_eq!(outputs.head_data, HeadData(vec![1, 1, 1])); assert_eq!(outputs.global_validation_data, omitted_validation.global_validation); assert_eq!(outputs.local_validation_data, omitted_validation.local_validation); assert_eq!(outputs.upward_messages, Vec::new()); assert_eq!(outputs.fees, 0); assert_eq!(outputs.new_validation_code, Some(vec![2, 2, 2].into())); }); } #[test] fn candidate_validation_bad_return_is_invalid() { let mut omitted_validation = OmittedValidationData { local_validation: Default::default(), global_validation: Default::default(), }; omitted_validation.global_validation.max_head_data_size = 1024; omitted_validation.global_validation.max_code_size = 1024; let pov = PoV { block_data: BlockData(vec![1; 32]) }; let mut descriptor = CandidateDescriptor::default(); descriptor.pov_hash = pov.hash(); collator_sign(&mut descriptor, Sr25519Keyring::Alice); assert!(perform_basic_checks(&descriptor, Some(1024), &pov).is_ok()); let validation_result = WasmValidationResult { head_data: HeadData(vec![1, 1, 1]), new_validation_code: Some(vec![2, 2, 2].into()), upward_messages: Vec::new(), processed_downward_messages: 0, }; assert!(check_wasm_result_against_constraints( &omitted_validation.global_validation, &omitted_validation.local_validation, &validation_result, ).is_ok()); let v = validate_candidate_exhaustive::( MockValidationArg { result: Err(ValidationError::InvalidCandidate( WasmInvalidCandidate::BadReturn )) }, omitted_validation.clone(), vec![1, 2, 3].into(), descriptor, Arc::new(pov), TaskExecutor::new(), ).unwrap(); assert_matches!(v, ValidationResult::Invalid(InvalidCandidate::BadReturn)); } #[test] fn candidate_validation_timeout_is_internal_error() { let mut omitted_validation = OmittedValidationData { local_validation: Default::default(), global_validation: Default::default(), }; omitted_validation.global_validation.max_head_data_size = 1024; omitted_validation.global_validation.max_code_size = 1024; let pov = PoV { block_data: BlockData(vec![1; 32]) }; let mut descriptor = CandidateDescriptor::default(); descriptor.pov_hash = pov.hash(); collator_sign(&mut descriptor, Sr25519Keyring::Alice); assert!(perform_basic_checks(&descriptor, Some(1024), &pov).is_ok()); let validation_result = WasmValidationResult { head_data: HeadData(vec![1, 1, 1]), new_validation_code: Some(vec![2, 2, 2].into()), upward_messages: Vec::new(), processed_downward_messages: 0, }; assert!(check_wasm_result_against_constraints( &omitted_validation.global_validation, &omitted_validation.local_validation, &validation_result, ).is_ok()); let v = validate_candidate_exhaustive::( MockValidationArg { result: Err(ValidationError::InvalidCandidate( WasmInvalidCandidate::Timeout )) }, omitted_validation.clone(), vec![1, 2, 3].into(), descriptor, Arc::new(pov), TaskExecutor::new(), ); assert_matches!(v, Ok(ValidationResult::Invalid(InvalidCandidate::Timeout))); } }