From 8988cef720a1c6566e9ad5ce097a461b7ad9c158 Mon Sep 17 00:00:00 2001 From: Sergey Shulepov Date: Mon, 29 Nov 2021 18:48:10 +0000 Subject: [PATCH] Add pre-check tests --- node/core/candidate-validation/src/tests.rs | 163 ++++++++++++++++++++ node/subsystem-types/src/messages.rs | 2 +- 2 files changed, 164 insertions(+), 1 deletion(-) diff --git a/node/core/candidate-validation/src/tests.rs b/node/core/candidate-validation/src/tests.rs index c55df9750237..74d6fe13db62 100644 --- a/node/core/candidate-validation/src/tests.rs +++ b/node/core/candidate-validation/src/tests.rs @@ -631,3 +631,166 @@ fn pov_decompression_failure_is_invalid() { assert_matches!(v, Ok(ValidationResult::Invalid(InvalidCandidate::PoVDecompressionFailure))); } + +struct MockPreCheckBackend { + result: Result<(), PrepareError>, +} + +impl MockPreCheckBackend { + fn with_hardcoded_result(result: Result<(), PrepareError>) -> Self { + Self { result } + } +} + +#[async_trait] +impl ValidationBackend for MockPreCheckBackend { + async fn validate_candidate( + &mut self, + _raw_validation_code: Vec, + _timeout: Duration, + _params: ValidationParams, + ) -> Result { + unreachable!() + } + + async fn precheck_pvf(&mut self, _pvf: Pvf) -> Result<(), PrepareError> { + self.result.clone() + } +} + +#[test] +fn precheck_works() { + let relay_parent = [3; 32].into(); + let validation_code = ValidationCode(vec![3; 16]); + let validation_code_hash = validation_code.hash(); + + let pool = TaskExecutor::new(); + let (mut ctx, mut ctx_handle) = + test_helpers::make_subsystem_context::(pool.clone()); + + let (check_fut, check_result) = precheck_pvf( + ctx.sender(), + MockPreCheckBackend::with_hardcoded_result(Ok(())), + relay_parent, + validation_code_hash, + ) + .remote_handle(); + + let test_fut = async move { + assert_matches!( + ctx_handle.recv().await, + AllMessages::RuntimeApi(RuntimeApiMessage::Request( + rp, + RuntimeApiRequest::ValidationCodeByHash( + vch, + tx + ), + )) => { + assert_eq!(vch, validation_code_hash); + assert_eq!(rp, relay_parent); + + let _ = tx.send(Ok(Some(validation_code.clone()))); + } + ); + assert_matches!(check_result.await, PreCheckOutcome::Valid); + }; + + let test_fut = future::join(test_fut, check_fut); + executor::block_on(test_fut); +} + +#[test] +fn precheck_invalid_pvf_blob_compression() { + let relay_parent = [3; 32].into(); + + 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 validation_code_hash = validation_code.hash(); + + let pool = TaskExecutor::new(); + let (mut ctx, mut ctx_handle) = + test_helpers::make_subsystem_context::(pool.clone()); + + let (check_fut, check_result) = precheck_pvf( + ctx.sender(), + MockPreCheckBackend::with_hardcoded_result(Ok(())), + relay_parent, + validation_code_hash, + ) + .remote_handle(); + + let test_fut = async move { + assert_matches!( + ctx_handle.recv().await, + AllMessages::RuntimeApi(RuntimeApiMessage::Request( + rp, + RuntimeApiRequest::ValidationCodeByHash( + vch, + tx + ), + )) => { + assert_eq!(vch, validation_code_hash); + assert_eq!(rp, relay_parent); + + let _ = tx.send(Ok(Some(validation_code.clone()))); + } + ); + assert_matches!(check_result.await, PreCheckOutcome::Invalid); + }; + + let test_fut = future::join(test_fut, check_fut); + executor::block_on(test_fut); +} + +#[test] +fn precheck_properly_classifies_outcomes() { + let inner = |prepare_result, precheck_outcome| { + let relay_parent = [3; 32].into(); + let validation_code = ValidationCode(vec![3; 16]); + let validation_code_hash = validation_code.hash(); + + let pool = TaskExecutor::new(); + let (mut ctx, mut ctx_handle) = + test_helpers::make_subsystem_context::(pool.clone()); + + let (check_fut, check_result) = precheck_pvf( + ctx.sender(), + MockPreCheckBackend::with_hardcoded_result(prepare_result), + relay_parent, + validation_code_hash, + ) + .remote_handle(); + + let test_fut = async move { + assert_matches!( + ctx_handle.recv().await, + AllMessages::RuntimeApi(RuntimeApiMessage::Request( + rp, + RuntimeApiRequest::ValidationCodeByHash( + vch, + tx + ), + )) => { + assert_eq!(vch, validation_code_hash); + assert_eq!(rp, relay_parent); + + let _ = tx.send(Ok(Some(validation_code.clone()))); + } + ); + assert_eq!(check_result.await, precheck_outcome); + }; + + let test_fut = future::join(test_fut, check_fut); + executor::block_on(test_fut); + }; + + inner(Err(PrepareError::Prevalidation("foo".to_owned())), PreCheckOutcome::Invalid); + inner(Err(PrepareError::Preparation("bar".to_owned())), PreCheckOutcome::Invalid); + inner(Err(PrepareError::Panic("baz".to_owned())), PreCheckOutcome::Invalid); + + inner(Err(PrepareError::TimedOut), PreCheckOutcome::Failed); + inner(Err(PrepareError::DidNotMakeIt), PreCheckOutcome::Failed); +} diff --git a/node/subsystem-types/src/messages.rs b/node/subsystem-types/src/messages.rs index c9ebdb3b2e08..6be22c2b5bbe 100644 --- a/node/subsystem-types/src/messages.rs +++ b/node/subsystem-types/src/messages.rs @@ -94,7 +94,7 @@ impl BoundToRelayParent for CandidateBackingMessage { pub struct ValidationFailed(pub String); /// The outcome of the candidate-validation's PVF pre-check request. -#[derive(Debug)] +#[derive(Debug, PartialEq)] pub enum PreCheckOutcome { /// The PVF has been compiled successfully within the given constraints. Valid,