diff --git a/node/core/backing/src/lib.rs b/node/core/backing/src/lib.rs index 97be31107756..69eb84efe4c6 100644 --- a/node/core/backing/src/lib.rs +++ b/node/core/backing/src/lib.rs @@ -32,11 +32,10 @@ use polkadot_primitives::v1::{ CommittedCandidateReceipt, BackedCandidate, Id as ParaId, ValidatorId, ValidatorIndex, SigningContext, PoV, CandidateDescriptor, AvailableData, ValidatorSignature, Hash, CandidateReceipt, - CandidateCommitments, CoreState, CoreIndex, CollatorId, + CandidateCommitments, CoreState, CoreIndex, CollatorId, ValidationOutputs, }; use polkadot_node_primitives::{ - FromTableMisbehavior, Statement, SignedFullStatement, MisbehaviorReport, - ValidationOutputs, ValidationResult, + FromTableMisbehavior, Statement, SignedFullStatement, MisbehaviorReport, ValidationResult, }; use polkadot_subsystem::{ messages::{ @@ -287,7 +286,7 @@ impl CandidateBackingJob { let candidate_hash = candidate.hash(); let statement = match valid { - ValidationResult::Valid(outputs) => { + ValidationResult::Valid(outputs, validation_data) => { // make PoV available for later distribution. Send data to the availability // store to keep. Sign and dispatch `valid` statement to network if we // have not seconded the given candidate. @@ -296,6 +295,7 @@ impl CandidateBackingJob { // the collator, do not make available and report the collator. let commitments_check = self.make_pov_available( pov, + validation_data, outputs, |commitments| if commitments.hash() == candidate.commitments_hash { Ok(CommittedCandidateReceipt { @@ -510,10 +510,11 @@ impl CandidateBackingJob { let v = self.request_candidate_validation(descriptor, pov.clone()).await?; let statement = match v { - ValidationResult::Valid(outputs) => { + ValidationResult::Valid(outputs, validation_data) => { // If validation produces a new set of commitments, we vote the candidate as invalid. let commitments_check = self.make_pov_available( (&*pov).clone(), + validation_data, outputs, |commitments| if commitments == expected_commitments { Ok(()) @@ -652,12 +653,13 @@ impl CandidateBackingJob { async fn make_pov_available( &mut self, pov: PoV, + validation_data: polkadot_primitives::v1::PersistedValidationData, outputs: ValidationOutputs, with_commitments: impl FnOnce(CandidateCommitments) -> Result, ) -> Result, Error> { let available_data = AvailableData { pov, - validation_data: outputs.validation_data, + validation_data, }; let chunks = erasure_coding::obtain_chunks_v1( @@ -1147,12 +1149,11 @@ mod tests { ) if pov == pov && &c == candidate.descriptor() => { tx.send(Ok( ValidationResult::Valid(ValidationOutputs { - validation_data: test_state.validation_data.persisted, head_data: expected_head_data.clone(), upward_messages: Vec::new(), fees: Default::default(), new_validation_code: None, - }), + }, test_state.validation_data.persisted), )).unwrap(); } ); @@ -1267,12 +1268,11 @@ mod tests { ) if pov == pov && &c == candidate_a.descriptor() => { tx.send(Ok( ValidationResult::Valid(ValidationOutputs { - validation_data: test_state.validation_data.persisted, head_data: expected_head_data.clone(), upward_messages: Vec::new(), fees: Default::default(), new_validation_code: None, - }), + }, test_state.validation_data.persisted), )).unwrap(); } ); @@ -1406,12 +1406,11 @@ mod tests { ) if pov == pov && &c == candidate_a.descriptor() => { tx.send(Ok( ValidationResult::Valid(ValidationOutputs { - validation_data: test_state.validation_data.persisted, head_data: expected_head_data.clone(), upward_messages: Vec::new(), fees: Default::default(), new_validation_code: None, - }), + }, test_state.validation_data.persisted), )).unwrap(); } ); @@ -1562,12 +1561,11 @@ mod tests { ) if pov == pov && &c == candidate_b.descriptor() => { tx.send(Ok( ValidationResult::Valid(ValidationOutputs { - validation_data: test_state.validation_data.persisted, head_data: expected_head_data.clone(), upward_messages: Vec::new(), fees: Default::default(), new_validation_code: None, - }), + }, test_state.validation_data.persisted), )).unwrap(); } ); diff --git a/node/core/candidate-selection/src/lib.rs b/node/core/candidate-selection/src/lib.rs index 667963d8afb2..f4ed605ef3bf 100644 --- a/node/core/candidate-selection/src/lib.rs +++ b/node/core/candidate-selection/src/lib.rs @@ -343,7 +343,10 @@ async fn candidate_is_valid_inner( CandidateValidationMessage::ValidateFromChainState(candidate_descriptor, pov, tx), )) .await?; - Ok(std::matches!(rx.await, Ok(Ok(ValidationResult::Valid(_))))) + Ok(std::matches!( + rx.await, + Ok(Ok(ValidationResult::Valid(_, _))) + )) } async fn second_candidate( @@ -445,8 +448,7 @@ delegated_subsystem!(CandidateSelectionJob((), Metrics) <- ToJob as CandidateSel mod tests { use super::*; use futures::lock::Mutex; - use polkadot_node_primitives::ValidationOutputs; - use polkadot_primitives::v1::{BlockData, HeadData, PersistedValidationData}; + use polkadot_primitives::v1::{BlockData, HeadData, PersistedValidationData, ValidationOutputs}; use sp_core::crypto::Public; fn test_harness( @@ -478,7 +480,7 @@ mod tests { postconditions(job, job_result); } - fn default_validation_outputs() -> ValidationOutputs { + fn default_validation_outputs_and_data() -> (ValidationOutputs, polkadot_primitives::v1::PersistedValidationData) { let head_data: Vec = (0..32).rev().cycle().take(256).collect(); let parent_head_data = head_data .iter() @@ -486,17 +488,19 @@ mod tests { .map(|x| x.saturating_sub(1)) .collect(); - ValidationOutputs { - head_data: HeadData(head_data), - validation_data: PersistedValidationData { + ( + ValidationOutputs { + head_data: HeadData(head_data), + upward_messages: Vec::new(), + fees: 0, + new_validation_code: None, + }, + PersistedValidationData { parent_head: HeadData(parent_head_data), block_number: 123, hrmp_mqc_heads: Vec::new(), }, - upward_messages: Vec::new(), - fees: 0, - new_validation_code: None, - } + ) } /// when nothing is seconded so far, the collation is fetched and seconded @@ -556,8 +560,9 @@ mod tests { assert_eq!(got_candidate_descriptor, candidate_receipt.descriptor); assert_eq!(got_pov.as_ref(), &pov); + let (outputs, data) = default_validation_outputs_and_data(); return_sender - .send(Ok(ValidationResult::Valid(default_validation_outputs()))) + .send(Ok(ValidationResult::Valid(outputs, data))) .unwrap(); } FromJob::Backing(CandidateBackingMessage::Second( diff --git a/node/core/candidate-validation/src/lib.rs b/node/core/candidate-validation/src/lib.rs index 273df04c4dd5..33c696d2db6d 100644 --- a/node/core/candidate-validation/src/lib.rs +++ b/node/core/candidate-validation/src/lib.rs @@ -32,10 +32,10 @@ use polkadot_node_subsystem_util::{ metrics::{self, prometheus}, }; use polkadot_subsystem::errors::RuntimeApiError; -use polkadot_node_primitives::{ValidationResult, ValidationOutputs, InvalidCandidate}; +use polkadot_node_primitives::{ValidationResult, InvalidCandidate}; use polkadot_primitives::v1::{ - ValidationCode, PoV, CandidateDescriptor, ValidationData, PersistedValidationData, - TransientValidationData, OccupiedCoreAssumption, Hash, + ValidationCode, PoV, CandidateDescriptor, PersistedValidationData, + OccupiedCoreAssumption, Hash, ValidationOutputs, }; use polkadot_parachain::wasm_executor::{ self, ValidationPool, ExecutionMode, ValidationError, @@ -72,7 +72,7 @@ impl Metrics { fn on_validation_event(&self, event: &Result) { if let Some(metrics) = &self.0 { match event { - Ok(ValidationResult::Valid(_)) => { + Ok(ValidationResult::Valid(_, _)) => { metrics.validation_requests.with_label_values(&["valid"]).inc(); }, Ok(ValidationResult::Invalid(_)) => { @@ -161,7 +161,6 @@ async fn run( } CandidateValidationMessage::ValidateFromExhaustive( persisted_validation_data, - transient_validation_data, validation_code, descriptor, pov, @@ -171,7 +170,6 @@ async fn run( &mut ctx, execution_mode.clone(), persisted_validation_data, - transient_validation_data, validation_code, descriptor, pov, @@ -214,7 +212,7 @@ async fn runtime_api_request( #[derive(Debug)] enum AssumptionCheckOutcome { - Matches(ValidationData, ValidationCode), + Matches(PersistedValidationData, ValidationCode), DoesNotMatch, BadRequest, } @@ -229,7 +227,7 @@ async fn check_assumption_validation_data( let d = runtime_api_request( ctx, descriptor.relay_parent, - RuntimeApiRequest::FullValidationData( + RuntimeApiRequest::PersistedValidationData( descriptor.para_id, assumption, tx, @@ -245,7 +243,7 @@ async fn check_assumption_validation_data( } }; - let persisted_validation_data_hash = validation_data.persisted.hash(); + let persisted_validation_data_hash = validation_data.hash(); SubsystemResult::Ok(if descriptor.persisted_validation_data_hash == persisted_validation_data_hash { let (code_tx, code_rx) = oneshot::channel(); @@ -269,70 +267,100 @@ async fn check_assumption_validation_data( }) } -async fn spawn_validate_from_chain_state( +async fn find_assumed_validation_data( ctx: &mut impl SubsystemContext, - execution_mode: ExecutionMode, - descriptor: CandidateDescriptor, - pov: Arc, - spawn: impl SpawnNamed + 'static, -) -> SubsystemResult> { + descriptor: &CandidateDescriptor, +) -> SubsystemResult { // The candidate descriptor has a `persisted_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 persisted validation data // based on the different `OccupiedCoreAssumption`s. - match check_assumption_validation_data( - ctx, - &descriptor, + + const ASSUMPTIONS: &[OccupiedCoreAssumption] = &[ OccupiedCoreAssumption::Included, - ).await? { - AssumptionCheckOutcome::Matches(validation_data, validation_code) => { - return spawn_validate_exhaustive( - ctx, - execution_mode, - validation_data.persisted, - Some(validation_data.transient), - validation_code, - descriptor, - pov, - spawn, - ).await; - } - AssumptionCheckOutcome::DoesNotMatch => {}, - AssumptionCheckOutcome::BadRequest => return Ok(Err(ValidationFailed("Bad request".into()))), + OccupiedCoreAssumption::TimedOut, + // TODO: Why don't we check `Free`? The guide assumes there are only two possible assumptions. + // + // Source that info and leave a comment here. + ]; + + // Consider running these checks in parallel to reduce validation latency. + for assumption in ASSUMPTIONS { + let outcome = check_assumption_validation_data(ctx, descriptor, *assumption).await?; + + let () = match outcome { + AssumptionCheckOutcome::Matches(_, _) => return Ok(outcome), + AssumptionCheckOutcome::BadRequest => return Ok(outcome), + AssumptionCheckOutcome::DoesNotMatch => continue, + }; } - match check_assumption_validation_data( + Ok(AssumptionCheckOutcome::DoesNotMatch) +} + +async fn spawn_validate_from_chain_state( + ctx: &mut impl SubsystemContext, + execution_mode: ExecutionMode, + descriptor: CandidateDescriptor, + pov: Arc, + spawn: impl SpawnNamed + 'static, +) -> SubsystemResult> { + let (validation_data, validation_code) = + match find_assumed_validation_data(ctx, &descriptor).await? { + AssumptionCheckOutcome::Matches(validation_data, validation_code) => { + (validation_data, validation_code) + } + AssumptionCheckOutcome::DoesNotMatch => { + // 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 persisted_validation_data_hash in the descriptor + // is not based on the relay parent and is thus invalid. + return Ok(Ok(ValidationResult::Invalid(InvalidCandidate::BadParent))); + } + AssumptionCheckOutcome::BadRequest => { + return Ok(Err(ValidationFailed("Assumption Check: Bad request".into()))); + } + }; + + let validation_result = spawn_validate_exhaustive( ctx, - &descriptor, - OccupiedCoreAssumption::TimedOut, - ).await? { - AssumptionCheckOutcome::Matches(validation_data, validation_code) => { - return spawn_validate_exhaustive( - ctx, - execution_mode, - validation_data.persisted, - Some(validation_data.transient), - validation_code, - descriptor, - pov, - spawn, - ).await; + execution_mode, + validation_data, + validation_code, + descriptor.clone(), + pov, + spawn, + ) + .await; + + if let Ok(Ok(ValidationResult::Valid(ref outputs, _))) = validation_result { + let (tx, rx) = oneshot::channel(); + match runtime_api_request( + ctx, + descriptor.relay_parent, + RuntimeApiRequest::CheckValidationOutputs(descriptor.para_id, outputs.clone(), tx), + rx, + ) + .await? + { + Ok(true) => {} + Ok(false) => { + return Ok(Ok(ValidationResult::Invalid( + InvalidCandidate::InvalidOutputs, + ))); + } + Err(_) => { + return Ok(Err(ValidationFailed("Check Validation Outputs: Bad request".into()))); + } } - 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 persisted_validation_data_hash in the descriptor - // is not based on the relay parent and is thus invalid. - Ok(Ok(ValidationResult::Invalid(InvalidCandidate::BadParent))) + validation_result } async fn spawn_validate_exhaustive( ctx: &mut impl SubsystemContext, execution_mode: ExecutionMode, persisted_validation_data: PersistedValidationData, - transient_validation_data: Option, validation_code: ValidationCode, descriptor: CandidateDescriptor, pov: Arc, @@ -343,7 +371,6 @@ async fn spawn_validate_exhaustive( let res = validate_candidate_exhaustive::( execution_mode, persisted_validation_data, - transient_validation_data, validation_code, descriptor, pov, @@ -384,30 +411,6 @@ fn perform_basic_checks( 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( - transient_params: &TransientValidationData, - result: &WasmValidationResult, -) -> Result<(), InvalidCandidate> { - if result.head_data.0.len() > transient_params.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 transient_params.code_upgrade_allowed.is_none() { - return Err(InvalidCandidate::CodeUpgradeNotAllowed) - } - - if code.0.len() > transient_params.max_code_size as _ { - return Err(InvalidCandidate::NewCodeTooLarge(code.0.len() as u64)) - } - } - - Ok(()) -} - trait ValidationBackend { type Arg; @@ -445,7 +448,6 @@ impl ValidationBackend for RealValidationBackend { fn validate_candidate_exhaustive( backend_arg: B::Arg, persisted_validation_data: PersistedValidationData, - transient_validation_data: Option, validation_code: ValidationCode, descriptor: CandidateDescriptor, pov: Arc, @@ -477,25 +479,13 @@ fn validate_candidate_exhaustive( Ok(ValidationResult::Invalid(InvalidCandidate::ExecutionError(e.to_string()))), Err(ValidationError::Internal(e)) => Err(ValidationFailed(e.to_string())), Ok(res) => { - let post_check_result = if let Some(transient) = transient_validation_data { - check_wasm_result_against_constraints( - &transient, - &res, - ) - } else { - Ok(()) + let outputs = ValidationOutputs { + head_data: res.head_data, + upward_messages: res.upward_messages, + fees: 0, + new_validation_code: res.new_validation_code, }; - - Ok(match post_check_result { - Ok(()) => ValidationResult::Valid(ValidationOutputs { - head_data: res.head_data, - validation_data: persisted_validation_data, - upward_messages: res.upward_messages, - fees: 0, - new_validation_code: res.new_validation_code, - }), - Err(e) => ValidationResult::Invalid(e), - }) + Ok(ValidationResult::Valid(outputs, persisted_validation_data)) } } } @@ -544,10 +534,10 @@ mod tests { #[test] fn correctly_checks_included_assumption() { - let validation_data: ValidationData = Default::default(); + let validation_data: PersistedValidationData = Default::default(); let validation_code: ValidationCode = vec![1, 2, 3].into(); - let persisted_validation_data_hash = validation_data.persisted.hash(); + let persisted_validation_data_hash = validation_data.hash(); let relay_parent = [2; 32].into(); let para_id = 5.into(); @@ -570,7 +560,11 @@ mod tests { ctx_handle.recv().await, AllMessages::RuntimeApi(RuntimeApiMessage::Request( rp, - RuntimeApiRequest::FullValidationData(p, OccupiedCoreAssumption::Included, tx) + RuntimeApiRequest::PersistedValidationData( + p, + OccupiedCoreAssumption::Included, + tx + ), )) => { assert_eq!(rp, relay_parent); assert_eq!(p, para_id); @@ -604,10 +598,10 @@ mod tests { #[test] fn correctly_checks_timed_out_assumption() { - let validation_data: ValidationData = Default::default(); + let validation_data: PersistedValidationData = Default::default(); let validation_code: ValidationCode = vec![1, 2, 3].into(); - let persisted_validation_data_hash = validation_data.persisted.hash(); + let persisted_validation_data_hash = validation_data.hash(); let relay_parent = [2; 32].into(); let para_id = 5.into(); @@ -630,7 +624,11 @@ mod tests { ctx_handle.recv().await, AllMessages::RuntimeApi(RuntimeApiMessage::Request( rp, - RuntimeApiRequest::FullValidationData(p, OccupiedCoreAssumption::TimedOut, tx) + RuntimeApiRequest::PersistedValidationData( + p, + OccupiedCoreAssumption::TimedOut, + tx + ), )) => { assert_eq!(rp, relay_parent); assert_eq!(p, para_id); @@ -664,8 +662,8 @@ mod tests { #[test] fn check_is_bad_request_if_no_validation_data() { - let validation_data: ValidationData = Default::default(); - let persisted_validation_data_hash = validation_data.persisted.hash(); + 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(); @@ -688,7 +686,11 @@ mod tests { ctx_handle.recv().await, AllMessages::RuntimeApi(RuntimeApiMessage::Request( rp, - RuntimeApiRequest::FullValidationData(p, OccupiedCoreAssumption::Included, tx) + RuntimeApiRequest::PersistedValidationData( + p, + OccupiedCoreAssumption::Included, + tx + ), )) => { assert_eq!(rp, relay_parent); assert_eq!(p, para_id); @@ -706,8 +708,8 @@ mod tests { #[test] fn check_is_bad_request_if_no_validation_code() { - let validation_data: ValidationData = Default::default(); - let persisted_validation_data_hash = validation_data.persisted.hash(); + 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(); @@ -730,7 +732,11 @@ mod tests { ctx_handle.recv().await, AllMessages::RuntimeApi(RuntimeApiMessage::Request( rp, - RuntimeApiRequest::FullValidationData(p, OccupiedCoreAssumption::TimedOut, tx) + RuntimeApiRequest::PersistedValidationData( + p, + OccupiedCoreAssumption::TimedOut, + tx + ), )) => { assert_eq!(rp, relay_parent); assert_eq!(p, para_id); @@ -761,7 +767,7 @@ mod tests { #[test] fn check_does_not_match() { - let validation_data: ValidationData = Default::default(); + let validation_data: PersistedValidationData = Default::default(); let relay_parent = [2; 32].into(); let para_id = 5.into(); @@ -784,7 +790,11 @@ mod tests { ctx_handle.recv().await, AllMessages::RuntimeApi(RuntimeApiMessage::Request( rp, - RuntimeApiRequest::FullValidationData(p, OccupiedCoreAssumption::Included, tx) + RuntimeApiRequest::PersistedValidationData( + p, + OccupiedCoreAssumption::Included, + tx + ), )) => { assert_eq!(rp, relay_parent); assert_eq!(p, para_id); @@ -802,10 +812,7 @@ mod tests { #[test] fn candidate_validation_ok_is_ok() { - let mut validation_data: ValidationData = Default::default(); - validation_data.transient.max_head_data_size = 1024; - validation_data.transient.max_code_size = 1024; - validation_data.transient.code_upgrade_allowed = Some(20); + let validation_data: PersistedValidationData = Default::default(); let pov = PoV { block_data: BlockData(vec![1; 32]) }; @@ -822,37 +829,27 @@ mod tests { processed_downward_messages: 0, }; - assert!(check_wasm_result_against_constraints( - &validation_data.transient, - &validation_result, - ).is_ok()); - let v = validate_candidate_exhaustive::( MockValidationArg { result: Ok(validation_result) }, - validation_data.persisted.clone(), - Some(validation_data.transient), + validation_data.clone(), vec![1, 2, 3].into(), descriptor, Arc::new(pov), TaskExecutor::new(), ).unwrap(); - assert_matches!(v, ValidationResult::Valid(outputs) => { + assert_matches!(v, ValidationResult::Valid(outputs, used_validation_data) => { assert_eq!(outputs.head_data, HeadData(vec![1, 1, 1])); - assert_eq!(outputs.validation_data, validation_data.persisted); assert_eq!(outputs.upward_messages, Vec::new()); assert_eq!(outputs.fees, 0); assert_eq!(outputs.new_validation_code, Some(vec![2, 2, 2].into())); + assert_eq!(used_validation_data, validation_data); }); } #[test] fn candidate_validation_bad_return_is_invalid() { - let mut validation_data: ValidationData = Default::default(); - - validation_data.transient.max_head_data_size = 1024; - validation_data.transient.max_code_size = 1024; - validation_data.transient.code_upgrade_allowed = Some(20); + let validation_data: PersistedValidationData = Default::default(); let pov = PoV { block_data: BlockData(vec![1; 32]) }; @@ -862,26 +859,13 @@ mod tests { 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( - &validation_data.transient, - &validation_result, - ).is_ok()); - let v = validate_candidate_exhaustive::( MockValidationArg { result: Err(ValidationError::InvalidCandidate( WasmInvalidCandidate::BadReturn )) }, - validation_data.persisted, - Some(validation_data.transient), + validation_data, vec![1, 2, 3].into(), descriptor, Arc::new(pov), @@ -891,14 +875,9 @@ mod tests { assert_matches!(v, ValidationResult::Invalid(InvalidCandidate::BadReturn)); } - #[test] fn candidate_validation_timeout_is_internal_error() { - let mut validation_data: ValidationData = Default::default(); - - validation_data.transient.max_head_data_size = 1024; - validation_data.transient.max_code_size = 1024; - validation_data.transient.code_upgrade_allowed = Some(20); + let validation_data: PersistedValidationData = Default::default(); let pov = PoV { block_data: BlockData(vec![1; 32]) }; @@ -908,26 +887,13 @@ mod tests { 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( - &validation_data.transient, - &validation_result, - ).is_ok()); - let v = validate_candidate_exhaustive::( MockValidationArg { result: Err(ValidationError::InvalidCandidate( WasmInvalidCandidate::Timeout )) }, - validation_data.persisted, - Some(validation_data.transient), + validation_data, vec![1, 2, 3].into(), descriptor, Arc::new(pov), @@ -936,49 +902,4 @@ mod tests { assert_matches!(v, Ok(ValidationResult::Invalid(InvalidCandidate::Timeout))); } - - #[test] - fn candidate_validation_ok_does_not_validate_outputs_if_no_transient() { - let mut validation_data: ValidationData = Default::default(); - validation_data.transient.max_head_data_size = 1; - validation_data.transient.max_code_size = 1; - - 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( - &validation_data.transient, - &validation_result, - ).is_err()); - - let v = validate_candidate_exhaustive::( - MockValidationArg { result: Ok(validation_result) }, - validation_data.persisted.clone(), - None, - 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.validation_data, validation_data.persisted); - assert_eq!(outputs.upward_messages, Vec::new()); - assert_eq!(outputs.fees, 0); - assert_eq!(outputs.new_validation_code, Some(vec![2, 2, 2].into())); - }); - } } diff --git a/node/core/runtime-api/src/lib.rs b/node/core/runtime-api/src/lib.rs index d73f4910b28a..4378903b3bff 100644 --- a/node/core/runtime-api/src/lib.rs +++ b/node/core/runtime-api/src/lib.rs @@ -114,6 +114,8 @@ fn make_runtime_api_request( query!(persisted_validation_data(para, assumption), sender), Request::FullValidationData(para, assumption, sender) => query!(full_validation_data(para, assumption), sender), + Request::CheckValidationOutputs(para, commitments, sender) => + query!(check_validation_outputs(para, commitments), sender), Request::SessionIndexForChild(sender) => query!(session_index_for_child(), sender), Request::ValidationCode(para, assumption, sender) => query!(validation_code(para, assumption), sender), @@ -186,6 +188,7 @@ mod tests { validation_data: HashMap, session_index_for_child: SessionIndex, validation_code: HashMap, + validation_outputs_results: HashMap, candidate_pending_availability: HashMap, candidate_events: Vec, } @@ -237,6 +240,19 @@ mod tests { self.validation_data.get(¶).map(|l| l.clone()) } + fn check_validation_outputs( + &self, + para_id: ParaId, + _commitments: polkadot_primitives::v1::ValidationOutputs, + ) -> bool { + self.validation_outputs_results + .get(¶_id) + .cloned() + .expect( + "`check_validation_outputs` called but the expected result hasn't been supplied" + ) + } + fn session_index_for_child(&self) -> SessionIndex { self.session_index_for_child.clone() } @@ -415,6 +431,60 @@ mod tests { futures::executor::block_on(future::join(subsystem_task, test_task)); } + #[test] + fn requests_check_validation_outputs() { + let (ctx, mut ctx_handle) = test_helpers::make_subsystem_context(TaskExecutor::new()); + let mut runtime_api = MockRuntimeApi::default(); + let relay_parent = [1; 32].into(); + let para_a = 5.into(); + let para_b = 6.into(); + let commitments = polkadot_primitives::v1::ValidationOutputs::default(); + + runtime_api.validation_outputs_results.insert(para_a, false); + runtime_api.validation_outputs_results.insert(para_b, true); + + let subsystem = RuntimeApiSubsystem::new(runtime_api.clone(), Metrics(None)); + let subsystem_task = run(ctx, subsystem).map(|x| x.unwrap()); + let test_task = async move { + let (tx, rx) = oneshot::channel(); + + ctx_handle.send(FromOverseer::Communication { + msg: RuntimeApiMessage::Request( + relay_parent, + Request::CheckValidationOutputs( + para_a, + commitments.clone(), + tx, + ), + ) + }).await; + assert_eq!( + rx.await.unwrap().unwrap(), + runtime_api.validation_outputs_results[¶_a], + ); + + let (tx, rx) = oneshot::channel(); + ctx_handle.send(FromOverseer::Communication { + msg: RuntimeApiMessage::Request( + relay_parent, + Request::CheckValidationOutputs( + para_b, + commitments, + tx, + ), + ) + }).await; + assert_eq!( + rx.await.unwrap().unwrap(), + runtime_api.validation_outputs_results[¶_b], + ); + + ctx_handle.send(FromOverseer::Signal(OverseerSignal::Conclude)).await; + }; + + futures::executor::block_on(future::join(subsystem_task, test_task)); + } + #[test] fn requests_session_index_for_child() { let (ctx, mut ctx_handle) = test_helpers::make_subsystem_context(TaskExecutor::new()); diff --git a/node/primitives/src/lib.rs b/node/primitives/src/lib.rs index e961112be1c7..a593c6918f31 100644 --- a/node/primitives/src/lib.rs +++ b/node/primitives/src/lib.rs @@ -26,7 +26,7 @@ use polkadot_primitives::v1::{ Hash, CommittedCandidateReceipt, CandidateReceipt, CompactStatement, EncodeAs, Signed, SigningContext, ValidatorIndex, ValidatorId, UpwardMessage, Balance, ValidationCode, PersistedValidationData, ValidationData, - HeadData, PoV, CollatorPair, Id as ParaId, + HeadData, PoV, CollatorPair, Id as ParaId, ValidationOutputs, }; use polkadot_statement_table::{ generic::{ @@ -114,26 +114,13 @@ pub struct FromTableMisbehavior { pub key: ValidatorId, } -/// Outputs of validating a candidate. -#[derive(Debug)] -pub struct ValidationOutputs { - /// The head-data produced by validation. - pub head_data: HeadData, - /// The persisted validation data. - pub validation_data: PersistedValidationData, - /// Upward messages to the relay chain. - pub upward_messages: Vec, - /// Fees paid to the validators of the relay-chain. - pub fees: Balance, - /// The new validation code submitted by the execution, if any. - pub new_validation_code: Option, -} - /// Candidate invalidity details #[derive(Debug)] pub enum InvalidCandidate { /// Failed to execute.`validate_block`. This includes function panicking. ExecutionError(String), + /// Validation outputs check doesn't pass. + InvalidOutputs, /// Execution timeout. Timeout, /// Validation input is over the limit. @@ -148,19 +135,14 @@ pub enum InvalidCandidate { HashMismatch, /// Bad collator signature. BadSignature, - /// Output code is too large - NewCodeTooLarge(u64), - /// Head-data is over the limit. - HeadDataTooLarge(u64), - /// Code upgrade triggered but not allowed. - CodeUpgradeNotAllowed, } /// Result of the validation of the candidate. #[derive(Debug)] pub enum ValidationResult { - /// Candidate is valid. The validation process yields these outputs. - Valid(ValidationOutputs), + /// Candidate is valid. The validation process yields these outputs and the persisted validation + /// data used to form inputs. + Valid(ValidationOutputs, PersistedValidationData), /// Candidate is invalid. Invalid(InvalidCandidate), } diff --git a/node/subsystem/src/messages.rs b/node/subsystem/src/messages.rs index 22e9f097ff71..27c81f474505 100644 --- a/node/subsystem/src/messages.rs +++ b/node/subsystem/src/messages.rs @@ -36,7 +36,7 @@ use polkadot_primitives::v1::{ CollatorId, CommittedCandidateReceipt, CoreState, ErasureChunk, GroupRotationInfo, Hash, Id as ParaId, OccupiedCoreAssumption, PersistedValidationData, PoV, SessionIndex, SignedAvailabilityBitfield, - TransientValidationData, ValidationCode, ValidatorId, ValidationData, + ValidationCode, ValidatorId, ValidationData, ValidatorIndex, ValidatorSignature, }; use std::sync::Arc; @@ -115,6 +115,8 @@ pub enum CandidateValidationMessage { /// from the runtime API of the chain, based on the `relay_parent` /// of the `CandidateDescriptor`. /// + /// This will also perform checking of validation outputs against the acceptance criteria. + /// /// If there is no state available which can provide this data or the core for /// the para is not free at the relay-parent, an error is returned. ValidateFromChainState( @@ -125,11 +127,14 @@ pub enum CandidateValidationMessage { /// Validate a candidate with provided, exhaustive parameters for validation. /// /// Explicitly provide the `PersistedValidationData` and `ValidationCode` so this can do full - /// validation without needing to access the state of the relay-chain. Optionally provide the - /// `TransientValidationData` for further checks on the outputs. + /// validation without needing to access the state of the relay-chain. + /// + /// This request doesn't involve acceptance criteria checking, therefore only useful for the + /// cases where the validity of the candidate is established. This is the case for the typical + /// use-case: secondary checkers would use this request relying on the full prior checks + /// performed by the relay-chain. ValidateFromExhaustive( PersistedValidationData, - Option, ValidationCode, CandidateDescriptor, Arc, @@ -142,7 +147,7 @@ impl CandidateValidationMessage { pub fn relay_parent(&self) -> Option { match self { Self::ValidateFromChainState(_, _, _) => None, - Self::ValidateFromExhaustive(_, _, _, _, _, _) => None, + Self::ValidateFromExhaustive(_, _, _, _, _) => None, } } } @@ -396,6 +401,12 @@ pub enum RuntimeApiRequest { OccupiedCoreAssumption, RuntimeApiSender>, ), + /// Sends back `true` if the validation outputs pass all acceptance criteria checks. + CheckValidationOutputs( + ParaId, + polkadot_primitives::v1::ValidationOutputs, + RuntimeApiSender, + ), /// Get the session index that a child of the block will have. SessionIndexForChild(RuntimeApiSender), /// Get the validation code for a para, taking the given `OccupiedCoreAssumption`, which diff --git a/primitives/src/v1.rs b/primitives/src/v1.rs index b7f0b8427392..b0118ba32593 100644 --- a/primitives/src/v1.rs +++ b/primitives/src/v1.rs @@ -303,6 +303,20 @@ pub struct TransientValidationData { pub code_upgrade_allowed: Option, } +/// Outputs of validating a candidate. +#[derive(Encode, Decode)] +#[cfg_attr(feature = "std", derive(Clone, Debug, Default))] +pub struct ValidationOutputs { + /// The head-data produced by validation. + pub head_data: HeadData, + /// Upward messages to the relay chain. + pub upward_messages: Vec, + /// Fees paid to the validators of the relay-chain. + pub fees: Balance, + /// The new validation code submitted by the execution, if any. + pub new_validation_code: Option, +} + /// Commitments made in a `CandidateReceipt`. Many of these are outputs of validation. #[derive(PartialEq, Eq, Clone, Encode, Decode)] #[cfg_attr(feature = "std", derive(Debug, Default, Hash))] @@ -666,6 +680,10 @@ sp_api::decl_runtime_apis! { fn persisted_validation_data(para_id: Id, assumption: OccupiedCoreAssumption) -> Option>; + // TODO: Adding a Runtime API should be backwards compatible... right? + /// Checks if the given validation outputs pass the acceptance criteria. + fn check_validation_outputs(para_id: Id, outputs: ValidationOutputs) -> bool; + /// Returns the session index expected at a child of the block. /// /// This can be used to instantiate a `SigningContext`. @@ -689,7 +707,7 @@ sp_api::decl_runtime_apis! { fn candidate_events() -> Vec>; /// Get the `AuthorityDiscoveryId`s corresponding to the given `ValidatorId`s. - /// Currently this request is limited to validators in the current session. + /// Currently this request is limited to validators in the current session. /// /// We assume that every validator runs authority discovery, /// which would allow us to establish point-to-point connection to given validators. diff --git a/roadmap/implementers-guide/src/node/utility/candidate-validation.md b/roadmap/implementers-guide/src/node/utility/candidate-validation.md index 19108bd78e81..c34672368c32 100644 --- a/roadmap/implementers-guide/src/node/utility/candidate-validation.md +++ b/roadmap/implementers-guide/src/node/utility/candidate-validation.md @@ -24,7 +24,7 @@ Upon receiving a validation request, the first thing the candidate validation su ### Determining Parameters -For a [`CandidateValidationMessage`][CVM]`::ValidateFromExhaustive`, these parameters are exhaustively provided. The [`TransientValidationData`](../../types/candidate.md#transientvalidationdata) is optional, and is used to perform further checks on the outputs of validation. +For a [`CandidateValidationMessage`][CVM]`::ValidateFromExhaustive`, these parameters are exhaustively provided. For a [`CandidateValidationMessage`][CVM]`::ValidateFromChainState`, some more work needs to be done. Due to the uncertainty of Availability Cores (implemented in the [`Scheduler`](../../runtime/scheduler.md) module of the runtime), a candidate at a particular relay-parent and for a particular para may have two different valid validation-data to be executed under depending on what is assumed to happen if the para is occupying a core at the onset of the new block. This is encoded as an `OccupiedCoreAssumption` in the runtime API. @@ -40,9 +40,8 @@ Once we have all parameters, we can spin up a background task to perform the val * The collator signature is valid * The PoV provided matches the `pov_hash` field of the descriptor -After that, we can invoke the validation function. Lastly, if available, we do some final checks on the output using the `TransientValidationData`: - * The produced head-data is no larger than the maximum allowed. - * The produced code upgrade, if any, is no larger than the maximum allowed, and a code upgrade was allowed to be signaled. - * The amount and size of produced upward messages is not too large. +### Checking Validation Outputs + +If we can assume the presence of the relay-chain state (that is, during processing [`CandidateValidationMessage`][CVM]`::ValidateFromChainState`) we can run all the checks that the relay-chain would run at the inclusion time thus confirming that the candidate will be accepted. [CVM]: ../../types/overseer-protocol.md#validationrequesttype diff --git a/roadmap/implementers-guide/src/types/overseer-protocol.md b/roadmap/implementers-guide/src/types/overseer-protocol.md index ef6d8473438b..d25553e03aaa 100644 --- a/roadmap/implementers-guide/src/types/overseer-protocol.md +++ b/roadmap/implementers-guide/src/types/overseer-protocol.md @@ -348,6 +348,12 @@ enum RuntimeApiRequest { OccupiedCoreAssumption, ResponseChannel>, ), + /// Sends back `true` if the commitments pass all acceptance criteria checks. + CheckValidationOutputs( + ParaId, + CandidateCommitments, + RuntimeApiSender, + ), /// Get information about all availability cores. AvailabilityCores(ResponseChannel>), /// Get a committed candidate receipt for all candidates pending availability. @@ -392,39 +398,52 @@ Various modules request that the [Candidate Validation subsystem](../node/utilit /// Result of the validation of the candidate. enum ValidationResult { - /// Candidate is valid, and here are the outputs. In practice, this should be a shared type - /// so that validation caching can be done. - Valid(ValidationOutputs), + /// Candidate is valid, and here are the outputs and the validation data used to form inputs. + /// In practice, this should be a shared type so that validation caching can be done. + Valid(ValidationOutputs, PersistedValidationData), /// Candidate is invalid. Invalid, } -/// Messages issued to the candidate validation subsystem. +/// Messages received by the Validation subsystem. /// /// ## Validation Requests /// /// Validation requests made to the subsystem should return an error only on internal error. -/// Otherwise, they should return either `Ok(ValidationResult::Valid(_))` or `Ok(ValidationResult::Invalid)`. -enum CandidateValidationMessage { - /// Validate a candidate with provided parameters. This will implicitly attempt to gather the - /// `OmittedValidationData` and `ValidationCode` from the runtime API of the chain, - /// based on the `relay_parent` of the `CandidateDescriptor`. +/// Otherwise, they should return either `Ok(ValidationResult::Valid(_))` +/// or `Ok(ValidationResult::Invalid)`. +#[derive(Debug)] +pub enum CandidateValidationMessage { + /// Validate a candidate with provided parameters using relay-chain state. + /// + /// This will implicitly attempt to gather the `PersistedValidationData` and `ValidationCode` + /// from the runtime API of the chain, based on the `relay_parent` + /// of the `CandidateDescriptor`. + /// + /// This will also perform checking of validation outputs against the acceptance criteria. /// /// If there is no state available which can provide this data or the core for /// the para is not free at the relay-parent, an error is returned. - ValidateFromChainState(CandidateDescriptor, PoV, ResponseChannel>), - - /// Validate a candidate with provided parameters. Explicitly provide the `PersistedValidationData` - /// and `ValidationCode` so this can do full validation without needing to access the state of - /// the relay-chain. Optionally provide the `TransientValidationData` which will lead to checks - /// on the output. + ValidateFromChainState( + CandidateDescriptor, + Arc, + oneshot::Sender>, + ), + /// Validate a candidate with provided, exhaustive parameters for validation. + /// + /// Explicitly provide the `PersistedValidationData` and `ValidationCode` so this can do full + /// validation without needing to access the state of the relay-chain. + /// + /// This request doesn't involve acceptance criteria checking, therefore only useful for the + /// cases where the validity of the candidate is established. This is the case for the typical + /// use-case: secondary checkers would use this request relying on the full prior checks + /// performed by the relay-chain. ValidateFromExhaustive( PersistedValidationData, - Option, ValidationCode, CandidateDescriptor, - PoV, - ResponseChannel>, + Arc, + oneshot::Sender>, ), } ``` diff --git a/runtime/kusama/src/lib.rs b/runtime/kusama/src/lib.rs index 57f42168363a..c17c6e8b17ea 100644 --- a/runtime/kusama/src/lib.rs +++ b/runtime/kusama/src/lib.rs @@ -1108,6 +1108,12 @@ sp_api::impl_runtime_apis! { -> Option> { None } + fn check_validation_outputs( + _: Id, + _: primitives::v1::ValidationOutputs, + ) -> bool { + false + } fn session_index_for_child() -> SessionIndex { 0 diff --git a/runtime/parachains/src/inclusion.rs b/runtime/parachains/src/inclusion.rs index 85f5d836a8a4..d10d3475292a 100644 --- a/runtime/parachains/src/inclusion.rs +++ b/runtime/parachains/src/inclusion.rs @@ -131,8 +131,12 @@ decl_error! { WrongCollator, /// Scheduled cores out of order. ScheduledOutOfOrder, + /// Head data exceeds the configured maximum. + HeadDataTooLarge, /// Code upgrade prematurely. PrematureCodeUpgrade, + /// Output code is too large + NewCodeTooLarge, /// Candidate not in parent context. CandidateNotInParentContext, /// The bitfield contains a bit relating to an unassigned availability core. @@ -345,9 +349,7 @@ impl Module { candidates: Vec>, scheduled: Vec, group_validators: impl Fn(GroupIndex) -> Option>, - ) - -> Result, DispatchError> - { + ) -> Result, DispatchError> { ensure!(candidates.len() <= scheduled.len(), Error::::UnscheduledCandidate); if scheduled.is_empty() { @@ -356,9 +358,7 @@ impl Module { let validators = Validators::get(); let parent_hash = >::parent_hash(); - let config = >::config(); - let now = >::block_number(); - let relay_parent_number = now - One::one(); + let check_cx = CandidateCheckContext::::new(); // do all checks before writing storage. let core_indices = { @@ -400,27 +400,17 @@ impl Module { candidate.descriptor().relay_parent == parent_hash, Error::::CandidateNotInParentContext, ); - - // if any, the code upgrade attempt is allowed. - let valid_upgrade_attempt = - candidate.candidate.commitments.new_validation_code.is_none() || - >::last_code_upgrade(para_id, true) - .map_or( - true, - |last| last <= relay_parent_number && - relay_parent_number.saturating_sub(last) - >= config.validation_upgrade_frequency, - ); - - ensure!( - valid_upgrade_attempt, - Error::::PrematureCodeUpgrade, - ); ensure!( candidate.descriptor().check_collator_signature().is_ok(), Error::::NotCollatorSigned, ); + check_cx.check_validation_outputs( + para_id, + &candidate.candidate.commitments.head_data, + &candidate.candidate.commitments.new_validation_code, + )?; + for (i, assignment) in scheduled[skip..].iter().enumerate() { check_assignment_in_order(assignment)?; @@ -498,7 +488,7 @@ impl Module { false, Error::::UnscheduledCandidate, ); - }; + } // check remainder of scheduled cores, if any. for assignment in scheduled[skip..].iter() { @@ -530,8 +520,8 @@ impl Module { core, descriptor, availability_votes, - relay_parent_number, - backed_in_number: now, + relay_parent_number: check_cx.relay_parent_number, + backed_in_number: check_cx.now, }); ::insert(¶_id, commitments); } @@ -539,6 +529,20 @@ impl Module { Ok(core_indices) } + /// Run the acceptance criteria checks on the given candidate commitments. + /// + /// Returns an 'Err` if any of the checks doesn't pass. + pub(crate) fn check_validation_outputs( + para_id: ParaId, + validation_outputs: primitives::v1::ValidationOutputs, + ) -> Result<(), DispatchError> { + CandidateCheckContext::::new().check_validation_outputs( + para_id, + &validation_outputs.head_data, + &validation_outputs.new_validation_code, + ) + } + fn enact_candidate( relay_parent_number: T::BlockNumber, receipt: CommittedCandidateReceipt, @@ -654,6 +658,57 @@ const fn availability_threshold(n_validators: usize) -> usize { threshold } +/// A collection of data required for checking a candidate. +struct CandidateCheckContext { + config: configuration::HostConfiguration, + now: T::BlockNumber, + relay_parent_number: T::BlockNumber, +} + +impl CandidateCheckContext { + fn new() -> Self { + let now = >::block_number(); + Self { + config: >::config(), + now, + relay_parent_number: now - One::one(), + } + } + + /// Check the given outputs after candidate validation on whether it passes the acceptance + /// criteria. + fn check_validation_outputs( + &self, + para_id: ParaId, + head_data: &HeadData, + new_validation_code: &Option, + ) -> Result<(), DispatchError> { + ensure!( + head_data.0.len() <= self.config.max_head_data_size as _, + Error::::HeadDataTooLarge + ); + + // if any, the code upgrade attempt is allowed. + if let Some(new_validation_code) = new_validation_code { + let valid_upgrade_attempt = >::last_code_upgrade(para_id, true) + .map_or(true, |last| { + last <= self.relay_parent_number + && self.relay_parent_number.saturating_sub(last) + >= self.config.validation_upgrade_frequency + }); + ensure!(valid_upgrade_attempt, Error::::PrematureCodeUpgrade); + ensure!( + new_validation_code.0.len() <= self.config.max_code_size as _, + Error::::NewCodeTooLarge + ); + } + + // TODO: messaging acceptance criteria rules will go here. + + Ok(()) + } +} + #[cfg(test)] mod tests { use super::*; @@ -682,6 +737,7 @@ mod tests { fn default_config() -> HostConfiguration { let mut config = HostConfiguration::default(); config.parathread_cores = 1; + config.max_code_size = 3; config } diff --git a/runtime/parachains/src/runtime_api_impl/v1.rs b/runtime/parachains/src/runtime_api_impl/v1.rs index 2c85a9e91792..e75e94e1e647 100644 --- a/runtime/parachains/src/runtime_api_impl/v1.rs +++ b/runtime/parachains/src/runtime_api_impl/v1.rs @@ -216,6 +216,15 @@ pub fn persisted_validation_data( ) } +/// Implementation for the `check_validation_outputs` function of the runtime API. +pub fn check_validation_outputs( + para_id: ParaId, + outputs: primitives::v1::ValidationOutputs, +) -> bool { + // we strip detailed information from error here for the sake of simplicity of runtime API. + >::check_validation_outputs(para_id, outputs).is_ok() +} + /// Implementation for the `session_index_for_child` function of the runtime API. pub fn session_index_for_child() -> SessionIndex { // Just returns the session index from `inclusion`. Runtime APIs follow @@ -268,7 +277,7 @@ where } /// Get the `AuthorityDiscoveryId`s corresponding to the given `ValidatorId`s. -/// Currently this request is limited to validators in the current session. +/// Currently this request is limited to validators in the current session. /// /// We assume that every validator runs authority discovery, /// which would allow us to establish point-to-point connection to given validators. diff --git a/runtime/polkadot/src/lib.rs b/runtime/polkadot/src/lib.rs index d905a8c201d9..4e1ad3aa62e4 100644 --- a/runtime/polkadot/src/lib.rs +++ b/runtime/polkadot/src/lib.rs @@ -1105,6 +1105,10 @@ sp_api::impl_runtime_apis! { None } + fn check_validation_outputs(_: Id, _: primitives::v1::ValidationOutputs) -> bool { + false + } + fn session_index_for_child() -> SessionIndex { 0 } diff --git a/runtime/rococo-v1/src/lib.rs b/runtime/rococo-v1/src/lib.rs index c0b2e3bfb2ff..3c7f4fc32dc2 100644 --- a/runtime/rococo-v1/src/lib.rs +++ b/runtime/rococo-v1/src/lib.rs @@ -191,6 +191,13 @@ sp_api::impl_runtime_apis! { runtime_api_impl::persisted_validation_data::(para_id, assumption) } + fn check_validation_outputs( + para_id: Id, + outputs: primitives::v1::ValidationOutputs, + ) -> bool { + runtime_api_impl::check_validation_outputs::(para_id, outputs) + } + fn session_index_for_child() -> SessionIndex { runtime_api_impl::session_index_for_child::() } diff --git a/runtime/test-runtime/src/lib.rs b/runtime/test-runtime/src/lib.rs index 342841b2732c..ee98b272c74b 100644 --- a/runtime/test-runtime/src/lib.rs +++ b/runtime/test-runtime/src/lib.rs @@ -620,6 +620,13 @@ sp_api::impl_runtime_apis! { runtime_impl::persisted_validation_data::(para_id, assumption) } + fn check_validation_outputs( + para_id: ParaId, + outputs: primitives::v1::ValidationOutputs, + ) -> bool { + runtime_impl::check_validation_outputs::(para_id, outputs) + } + fn session_index_for_child() -> SessionIndex { runtime_impl::session_index_for_child::() } diff --git a/runtime/westend/src/lib.rs b/runtime/westend/src/lib.rs index 2efd3644574f..9646da4f8c31 100644 --- a/runtime/westend/src/lib.rs +++ b/runtime/westend/src/lib.rs @@ -821,6 +821,13 @@ sp_api::impl_runtime_apis! { None } + fn check_validation_outputs( + _: Id, + _: primitives::v1::ValidationOutputs + ) -> bool { + false + } + fn session_index_for_child() -> SessionIndex { 0 }