diff --git a/lib/src/transactions/validate.rs b/lib/src/transactions/validate.rs index 406d77e531..5d6fdb2e5a 100644 --- a/lib/src/transactions/validate.rs +++ b/lib/src/transactions/validate.rs @@ -17,49 +17,13 @@ //! Runtime call to obtain the transactions validity status. -use crate::{ - executor::{host, runtime_host, storage_diff}, - header, util, -}; +use crate::util; use alloc::{borrow::ToOwned as _, vec::Vec}; use core::{iter, num::NonZeroU64}; -pub use runtime_host::{Nibble, TrieEntryVersion}; - mod tests; -/// Configuration for a transaction validation process. -pub struct Config<'a, TTx> { - /// Runtime used to get the validate the transaction. Must be built using the Wasm code found - /// at the `:code` key of the block storage. - pub runtime: host::HostVmPrototype, - - /// Header of the block to verify the transaction against, in SCALE encoding. - /// The runtime of this block must be the one in [`Config::runtime`]. - pub scale_encoded_header: &'a [u8], - - /// Number of bytes used to encode the block number in the header. - pub block_number_bytes: usize, - - /// SCALE-encoded transaction. - pub scale_encoded_transaction: TTx, - - /// Source of the transaction. - /// - /// This information is passed to the runtime, which might perform some additional - /// verifications if the source isn't trusted. - pub source: TransactionSource, - - /// Maximum log level of the runtime. - /// - /// > **Note**: This value is opaque from the point of the view of the client, and the runtime - /// > is free to interpret it the way it wants. However, usually values are: `0` for - /// > "off", `1` for "error", `2` for "warn", `3` for "info", `4` for "debug", - /// > and `5` for "trace". - pub max_log_level: u32, -} - /// Source of the transaction. #[derive(Debug, Copy, Clone, PartialEq, Eq)] pub enum TransactionSource { @@ -192,33 +156,6 @@ pub enum UnknownTransaction { Custom(u8), } -/// Problem encountered during a call to [`validate_transaction`]. -#[derive(Debug, derive_more::Display, Clone)] -pub enum Error { - /// Error while decoding the block header against which to make the call. - #[display(fmt = "Failed to decode block header: {_0}")] - InvalidHeader(header::Error), - /// Transaction validation API version unrecognized. - UnknownApiVersion, - /// Error while starting the Wasm virtual machine. - #[display(fmt = "{_0}")] - WasmStart(host::StartErr), - /// Error while running the Wasm virtual machine. - #[display(fmt = "{_0}")] - WasmVmReadWrite(runtime_host::ErrorDetail), - /// Error while running the Wasm virtual machine. - #[display(fmt = "{_0}")] - WasmVmReadOnly(runtime_host::ErrorDetail), - /// Error while decoding the output of the runtime. - OutputDecodeError(DecodeError), - /// The list of provided tags ([`ValidTransaction::provides`]) is empty. It is mandatory for - /// the runtime to always provide a non-empty list of tags. This error is consequently a bug - /// in the runtime. - EmptyProvidedTags, - /// Runtime called a forbidden host function. - ForbiddenHostCall, -} - /// Error that can happen during the decoding. #[derive(Debug, derive_more::Display, Clone)] pub struct DecodeError(); @@ -289,479 +226,6 @@ pub fn decode_validate_transaction_return_value( } } -/// Validates a transaction by calling `TaggedTransactionQueue_validate_transaction`. -pub fn validate_transaction( - config: Config + Clone> + Clone>, -) -> Query { - // The parameters of the function, and whether to call `Core_initialize_block` beforehand, - // depend on the API version. - let api_version = config - .runtime - .runtime_version() - .decode() - .apis - .find_version("TaggedTransactionQueue"); - - match api_version { - Some(2) => { - // In version 2, we need to call `Core_initialize_block` beforehand. - - // The `Core_initialize_block` function called below expects a partially-initialized - // SCALE-encoded header. Importantly, passing the entire header will lead to different code - // paths in the runtime and not match what Substrate does. - let decoded_header = - match header::decode(config.scale_encoded_header, config.block_number_bytes) { - Ok(h) => h, - Err(err) => { - return Query::Finished { - result: Err(Error::InvalidHeader(err)), - virtual_machine: config.runtime, - } - } - }; - - // Start the call to `Core_initialize_block`. - let vm = runtime_host::run(runtime_host::Config { - virtual_machine: config.runtime, - function_to_call: "Core_initialize_block", - parameter: header::HeaderRef { - parent_hash: &decoded_header.hash(config.block_number_bytes), - number: decoded_header.number + 1, - extrinsics_root: &[0; 32], - state_root: &[0; 32], - digest: header::DigestRef::empty(), - } - .scale_encoding(config.block_number_bytes), - storage_main_trie_changes: storage_diff::TrieDiff::empty(), - max_log_level: config.max_log_level, - calculate_trie_changes: false, - }); - - // Information used later, after `Core_initialize_block` is done. - let stage1 = Stage1 { - transaction_source: config.source, - scale_encoded_transaction: config.scale_encoded_transaction.fold( - Vec::new(), - |mut a, b| { - a.extend_from_slice(b.as_ref()); - a - }, - ), - max_log_level: config.max_log_level, - }; - - match vm { - Ok(vm) => Query::from_step1(vm, stage1), - Err((err, virtual_machine)) => Query::Finished { - result: Err(Error::WasmStart(err)), - virtual_machine, - }, - } - } - Some(3) => { - // In version 3, we don't need to call `Core_initialize_block`. - - let vm = runtime_host::run(runtime_host::Config { - virtual_machine: config.runtime, - function_to_call: VALIDATION_FUNCTION_NAME, - parameter: validate_transaction_runtime_parameters_v3( - config.scale_encoded_transaction, - config.source, - &header::hash_from_scale_encoded_header(config.scale_encoded_header), - ), - storage_main_trie_changes: storage_diff::TrieDiff::empty(), - max_log_level: config.max_log_level, - calculate_trie_changes: false, - }); - - match vm { - Ok(vm) => Query::from_step2(vm, Stage2 {}), - Err((err, virtual_machine)) => Query::Finished { - result: Err(Error::WasmStart(err)), - virtual_machine, - }, - } - } - _ => Query::Finished { - result: Err(Error::UnknownApiVersion), - virtual_machine: config.runtime, - }, - } -} - -/// Current state of the operation. -#[must_use] -pub enum Query { - /// Validating the transaction is over. - Finished { - /// Outcome of the verification. - /// - /// The outer `Result` contains an error if the runtime call has failed, while the inner - /// `Result` contains an error if the transaction is invalid. - result: Result, Error>, - /// Virtual machine initially passed through the configuration. - virtual_machine: host::HostVmPrototype, - }, - /// Loading a storage value is required in order to continue. - StorageGet(StorageGet), - /// Obtaining the Merkle value of the closest descendant of a trie node is required in order - /// to continue. - ClosestDescendantMerkleValue(ClosestDescendantMerkleValue), - /// Fetching the key that follows a given one is required in order to continue. - NextKey(NextKey), -} - -impl Query { - /// Cancels execution of the virtual machine and returns back the prototype. - pub fn into_prototype(self) -> host::HostVmPrototype { - match self { - Query::Finished { - virtual_machine, .. - } => virtual_machine, - Query::StorageGet(StorageGet(StorageGetInner::Stage1(inner, _))) => { - runtime_host::RuntimeHostVm::StorageGet(inner).into_prototype() - } - Query::StorageGet(StorageGet(StorageGetInner::Stage2(inner, _))) => { - runtime_host::RuntimeHostVm::StorageGet(inner).into_prototype() - } - Query::ClosestDescendantMerkleValue(ClosestDescendantMerkleValue( - MerkleValueInner::Stage1(inner, _), - )) => runtime_host::RuntimeHostVm::ClosestDescendantMerkleValue(inner).into_prototype(), - Query::ClosestDescendantMerkleValue(ClosestDescendantMerkleValue( - MerkleValueInner::Stage2(inner, _), - )) => runtime_host::RuntimeHostVm::ClosestDescendantMerkleValue(inner).into_prototype(), - Query::NextKey(NextKey(NextKeyInner::Stage1(inner, _))) => { - runtime_host::RuntimeHostVm::NextKey(inner).into_prototype() - } - Query::NextKey(NextKey(NextKeyInner::Stage2(inner, _))) => { - runtime_host::RuntimeHostVm::NextKey(inner).into_prototype() - } - } - } - - fn from_step1(mut inner: runtime_host::RuntimeHostVm, info: Stage1) -> Self { - loop { - break match inner { - runtime_host::RuntimeHostVm::Finished(Ok(success)) => { - // No output expected from `Core_initialize_block`. - if !success.virtual_machine.value().as_ref().is_empty() { - return Query::Finished { - result: Err(Error::OutputDecodeError(DecodeError())), - virtual_machine: success.virtual_machine.into_prototype(), - }; - } - - let vm = runtime_host::run(runtime_host::Config { - virtual_machine: success.virtual_machine.into_prototype(), - function_to_call: VALIDATION_FUNCTION_NAME, - parameter: validate_transaction_runtime_parameters_v2( - iter::once(info.scale_encoded_transaction), - info.transaction_source, - ), - storage_main_trie_changes: success.storage_changes.into_main_trie_diff(), - max_log_level: info.max_log_level, - calculate_trie_changes: false, - }); - - match vm { - Ok(vm) => Query::from_step2(vm, Stage2 {}), - Err((err, virtual_machine)) => Query::Finished { - result: Err(Error::WasmStart(err)), - virtual_machine, - }, - } - } - runtime_host::RuntimeHostVm::Finished(Err(err)) => Query::Finished { - result: Err(Error::WasmVmReadWrite(err.detail)), - virtual_machine: err.prototype, - }, - runtime_host::RuntimeHostVm::StorageGet(i) => { - Query::StorageGet(StorageGet(StorageGetInner::Stage1(i, info))) - } - runtime_host::RuntimeHostVm::ClosestDescendantMerkleValue(inner) => { - Query::ClosestDescendantMerkleValue(ClosestDescendantMerkleValue( - MerkleValueInner::Stage1(inner, info), - )) - } - runtime_host::RuntimeHostVm::NextKey(inner) => { - Query::NextKey(NextKey(NextKeyInner::Stage1(inner, info))) - } - runtime_host::RuntimeHostVm::SignatureVerification(sig) => { - inner = sig.verify_and_resume(); - continue; - } - runtime_host::RuntimeHostVm::OffchainStorageSet(req) => { - // Ignore the offchain storage write. - inner = req.resume(); - continue; - } - runtime_host::RuntimeHostVm::Offchain(ctx) => Query::Finished { - result: Err(Error::ForbiddenHostCall), - virtual_machine: ctx.into_prototype(), - }, - runtime_host::RuntimeHostVm::LogEmit(req) => { - // Generated logs are ignored. - inner = req.resume(); - continue; - } - }; - } - } - - fn from_step2(mut inner: runtime_host::RuntimeHostVm, info: Stage2) -> Self { - loop { - break match inner { - runtime_host::RuntimeHostVm::Finished(Ok(success)) => { - // This decoding is done in multiple steps in order to solve borrow checking - // errors. - let result = { - let output = success.virtual_machine.value(); - decode_validate_transaction_return_value(output.as_ref()) - .map_err(Error::OutputDecodeError) - }; - - let result = match result { - Ok(res) => { - if let Ok(res) = res.as_ref() { - if res.provides.is_empty() { - return Query::Finished { - result: Err(Error::EmptyProvidedTags), - virtual_machine: success.virtual_machine.into_prototype(), - }; - } - } - res - } - Err(err) => { - return Query::Finished { - result: Err(err), - virtual_machine: success.virtual_machine.into_prototype(), - } - } - }; - - Query::Finished { - result: Ok(result), - virtual_machine: success.virtual_machine.into_prototype(), - } - } - runtime_host::RuntimeHostVm::Finished(Err(err)) => Query::Finished { - result: Err(Error::WasmVmReadOnly(err.detail)), - virtual_machine: err.prototype, - }, - runtime_host::RuntimeHostVm::StorageGet(i) => { - Query::StorageGet(StorageGet(StorageGetInner::Stage2(i, info))) - } - runtime_host::RuntimeHostVm::ClosestDescendantMerkleValue(inner) => { - Query::ClosestDescendantMerkleValue(ClosestDescendantMerkleValue( - MerkleValueInner::Stage2(inner, info), - )) - } - runtime_host::RuntimeHostVm::NextKey(inner) => { - Query::NextKey(NextKey(NextKeyInner::Stage2(inner, info))) - } - runtime_host::RuntimeHostVm::SignatureVerification(sig) => { - inner = sig.verify_and_resume(); - continue; - } - runtime_host::RuntimeHostVm::OffchainStorageSet(req) => { - // Ignore the offchain storage write. - inner = req.resume(); - continue; - } - runtime_host::RuntimeHostVm::Offchain(ctx) => Query::Finished { - result: Err(Error::ForbiddenHostCall), - virtual_machine: ctx.into_prototype(), - }, - runtime_host::RuntimeHostVm::LogEmit(req) => { - // Generated logs are ignored. - inner = req.resume(); - continue; - } - }; - } - } -} - -struct Stage1 { - /// Same value as [`Config::source`]. - transaction_source: TransactionSource, - /// Same value as [`Config::scale_encoded_transaction`]. - scale_encoded_transaction: Vec, - /// Same value as [`Config::max_log_level`]. - max_log_level: u32, -} - -struct Stage2 {} - -/// Loading a storage value is required in order to continue. -#[must_use] -pub struct StorageGet(StorageGetInner); - -enum StorageGetInner { - Stage1(runtime_host::StorageGet, Stage1), - Stage2(runtime_host::StorageGet, Stage2), -} - -impl StorageGet { - /// Returns the key whose value must be passed to [`StorageGet::inject_value`]. - pub fn key(&'_ self) -> impl AsRef<[u8]> + '_ { - match &self.0 { - StorageGetInner::Stage1(inner, _) => either::Left(inner.key()), - StorageGetInner::Stage2(inner, _) => either::Right(inner.key()), - } - } - - /// If `Some`, read from the given child trie. If `None`, read from the main trie. - pub fn child_trie(&'_ self) -> Option + '_> { - match &self.0 { - StorageGetInner::Stage1(inner, _) => inner.child_trie().map(either::Left), - StorageGetInner::Stage2(inner, _) => inner.child_trie().map(either::Right), - } - } - - /// Injects the corresponding storage value. - pub fn inject_value( - self, - value: Option<(impl Iterator>, TrieEntryVersion)>, - ) -> Query { - match self.0 { - StorageGetInner::Stage1(inner, stage) => { - Query::from_step1(inner.inject_value(value), stage) - } - StorageGetInner::Stage2(inner, stage) => { - Query::from_step2(inner.inject_value(value), stage) - } - } - } -} - -/// Obtaining the Merkle value of the closest descendant of a trie node is required in order -/// to continue. -#[must_use] -pub struct ClosestDescendantMerkleValue(MerkleValueInner); - -enum MerkleValueInner { - Stage1(runtime_host::ClosestDescendantMerkleValue, Stage1), - Stage2(runtime_host::ClosestDescendantMerkleValue, Stage2), -} - -impl ClosestDescendantMerkleValue { - /// Returns the key whose closest descendant Merkle value must be passed to - /// [`ClosestDescendantMerkleValue::inject_merkle_value`]. - pub fn key(&'_ self) -> impl Iterator + '_ { - match &self.0 { - MerkleValueInner::Stage1(inner, _) => either::Left(inner.key()), - MerkleValueInner::Stage2(inner, _) => either::Right(inner.key()), - } - } - - /// If `Some`, read from the given child trie. If `None`, read from the main trie. - pub fn child_trie(&'_ self) -> Option + '_> { - match &self.0 { - MerkleValueInner::Stage1(inner, _) => inner.child_trie().map(either::Left), - MerkleValueInner::Stage2(inner, _) => inner.child_trie().map(either::Right), - } - } - - /// Indicate that the value is unknown and resume the calculation. - /// - /// This function be used if you are unaware of the Merkle value. The algorithm will perform - /// the calculation of this Merkle value manually, which takes more time. - pub fn resume_unknown(self) -> Query { - match self.0 { - MerkleValueInner::Stage1(inner, stage1) => { - Query::from_step1(inner.resume_unknown(), stage1) - } - MerkleValueInner::Stage2(inner, stage2) => { - Query::from_step2(inner.resume_unknown(), stage2) - } - } - } - - /// Injects the corresponding Merkle value. - /// - /// `None` can be passed if there is no descendant or, in the case of a child trie read, in - /// order to indicate that the child trie does not exist. - pub fn inject_merkle_value(self, merkle_value: Option<&[u8]>) -> Query { - match self.0 { - MerkleValueInner::Stage1(inner, stage1) => { - Query::from_step1(inner.inject_merkle_value(merkle_value), stage1) - } - MerkleValueInner::Stage2(inner, stage2) => { - Query::from_step2(inner.inject_merkle_value(merkle_value), stage2) - } - } - } -} - -/// Fetching the key that follows a given one is required in order to continue. -#[must_use] -pub struct NextKey(NextKeyInner); - -enum NextKeyInner { - Stage1(runtime_host::NextKey, Stage1), - Stage2(runtime_host::NextKey, Stage2), -} - -impl NextKey { - /// Returns the key whose next key must be passed back. - pub fn key(&'_ self) -> impl Iterator + '_ { - match &self.0 { - NextKeyInner::Stage1(inner, _) => either::Left(inner.key()), - NextKeyInner::Stage2(inner, _) => either::Right(inner.key()), - } - } - - /// If `Some`, read from the given child trie. If `None`, read from the main trie. - pub fn child_trie(&'_ self) -> Option + '_> { - match &self.0 { - NextKeyInner::Stage1(inner, _) => inner.child_trie().map(either::Left), - NextKeyInner::Stage2(inner, _) => inner.child_trie().map(either::Right), - } - } - - /// If `true`, then the provided value must the one superior or equal to the requested key. - /// If `false`, then the provided value must be strictly superior to the requested key. - pub fn or_equal(&self) -> bool { - match &self.0 { - NextKeyInner::Stage1(inner, _) => inner.or_equal(), - NextKeyInner::Stage2(inner, _) => inner.or_equal(), - } - } - - /// If `true`, then the search must include both branch nodes and storage nodes. If `false`, - /// the search only covers storage nodes. - pub fn branch_nodes(&self) -> bool { - match &self.0 { - NextKeyInner::Stage1(inner, _) => inner.branch_nodes(), - NextKeyInner::Stage2(inner, _) => inner.branch_nodes(), - } - } - - /// Returns the prefix the next key must start with. If the next key doesn't start with the - /// given prefix, then `None` should be provided. - pub fn prefix(&'_ self) -> impl Iterator + '_ { - match &self.0 { - NextKeyInner::Stage1(inner, _) => either::Left(inner.prefix()), - NextKeyInner::Stage2(inner, _) => either::Right(inner.prefix()), - } - } - - /// Injects the key. - /// - /// # Panic - /// - /// Panics if the key passed as parameter isn't strictly superior to the requested key. - /// - pub fn inject_key(self, key: Option>) -> Query { - match self.0 { - NextKeyInner::Stage1(inner, stage1) => Query::from_step1(inner.inject_key(key), stage1), - NextKeyInner::Stage2(inner, stage2) => Query::from_step2(inner.inject_key(key), stage2), - } - } -} - // `nom` parser functions can be found below. fn transaction_validity( @@ -792,7 +256,8 @@ fn valid_transaction(bytes: &[u8]) -> nom::IResult<&[u8], ValidTransaction> { nom::sequence::tuple(( nom::number::streaming::le_u64, tags, - tags, + // TODO: maybe show by strong typing the fact that the provide tags are never empty + nom::combinator::verify(tags, |provides: &Vec>| !provides.is_empty()), nom::combinator::map_opt(nom::number::streaming::le_u64, NonZeroU64::new), util::nom_bool_decode, )), diff --git a/lib/src/transactions/validate/tests.rs b/lib/src/transactions/validate/tests.rs index 638df03156..fc2181deec 100644 --- a/lib/src/transactions/validate/tests.rs +++ b/lib/src/transactions/validate/tests.rs @@ -17,7 +17,11 @@ #![cfg(test)] -use crate::{executor, header, trie::proof_decode}; +use crate::{ + executor::{self, runtime_host}, + header, + trie::proof_decode, +}; use core::iter; #[test] @@ -43,27 +47,32 @@ fn validate_from_proof() { let main_trie_root = header::decode(&scale_encoded_header, 4).unwrap().state_root; - let mut validation_in_progress = super::validate_transaction(super::Config { - runtime, - scale_encoded_header: &scale_encoded_header, - block_number_bytes: 4, - scale_encoded_transaction: iter::once(&hex::decode(test.transaction_bytes).unwrap()), - source: super::TransactionSource::External, + let mut validation_in_progress = runtime_host::run(runtime_host::Config { + virtual_machine: runtime, + function_to_call: super::VALIDATION_FUNCTION_NAME, + parameter: super::validate_transaction_runtime_parameters_v3( + iter::once(&hex::decode(test.transaction_bytes).unwrap()), + super::TransactionSource::External, + &header::hash_from_scale_encoded_header(&scale_encoded_header), + ), + storage_main_trie_changes: Default::default(), max_log_level: 0, - }); + calculate_trie_changes: false, + }) + .unwrap(); loop { match validation_in_progress { - super::Query::Finished { result: Ok(_), .. } => return, // Success, - super::Query::Finished { result: Err(_), .. } => panic!(), - super::Query::StorageGet(get) => { + runtime_host::RuntimeHostVm::Finished(Ok(_)) => return, // Success, + runtime_host::RuntimeHostVm::Finished(Err(_)) => panic!(), + runtime_host::RuntimeHostVm::StorageGet(get) => { let value = call_proof .storage_value(main_trie_root, get.key().as_ref()) .unwrap(); validation_in_progress = get.inject_value(value.map(|(val, ver)| (iter::once(val), ver))); } - super::Query::NextKey(nk) => { + runtime_host::RuntimeHostVm::NextKey(nk) => { let next_key = call_proof .next_key( main_trie_root, @@ -75,12 +84,20 @@ fn validate_from_proof() { .unwrap(); validation_in_progress = nk.inject_key(next_key); } - super::Query::ClosestDescendantMerkleValue(mv) => { + runtime_host::RuntimeHostVm::ClosestDescendantMerkleValue(mv) => { let value = call_proof .closest_descendant_merkle_value(main_trie_root, mv.key()) .unwrap(); validation_in_progress = mv.inject_merkle_value(value); } + runtime_host::RuntimeHostVm::SignatureVerification(r) => { + validation_in_progress = r.verify_and_resume() + } + runtime_host::RuntimeHostVm::LogEmit(r) => validation_in_progress = r.resume(), + runtime_host::RuntimeHostVm::OffchainStorageSet(r) => { + validation_in_progress = r.resume() + } + runtime_host::RuntimeHostVm::Offchain(_) => panic!(), } } } diff --git a/light-base/src/transactions_service.rs b/light-base/src/transactions_service.rs index 34077b8008..b18c6f9a6a 100644 --- a/light-base/src/transactions_service.rs +++ b/light-base/src/transactions_service.rs @@ -89,6 +89,7 @@ use futures_util::stream::FuturesUnordered; use futures_util::{future, FutureExt as _, StreamExt as _}; use itertools::Itertools as _; use smoldot::{ + executor::{host, runtime_host}, header, informant::HashDisplay, libp2p::peer_id::PeerId, @@ -353,9 +354,18 @@ pub enum ValidateTransactionError { /// Error during the network request. #[display(fmt = "{_0}")] Call(runtime_service::RuntimeCallError), - /// Error during the validation runtime call. + /// Transaction validation API version unrecognized. + UnknownApiVersion, + /// Error while starting the Wasm virtual machine. #[display(fmt = "{_0}")] - Validation(validate::Error), + WasmStart(host::StartErr), + /// Error while running the Wasm virtual machine. + #[display(fmt = "{_0}")] + WasmExecution(runtime_host::ErrorDetail), + /// Error while decoding the output of the runtime. + OutputDecodeError(validate::DecodeError), + /// Runtime called a forbidden host function. + ForbiddenHostCall, } #[derive(Debug, Clone)] @@ -1346,6 +1356,7 @@ async fn validate_transaction( } }; + // TODO: move somewhere else? log::debug!( target: log_target, "TxValidations <= Start(tx={}, block={}, block_height={})", @@ -1364,7 +1375,6 @@ async fn validate_transaction( let (runtime_call_lock, runtime) = runtime_lock .start( validate::VALIDATION_FUNCTION_NAME, - // TODO: don't hardcode v3 but determine parameters dynamically from the runtime validate::validate_transaction_runtime_parameters_v3( iter::once(scale_encoded_transaction.as_ref()), source, @@ -1379,43 +1389,66 @@ async fn validate_transaction( .map_err(InvalidOrError::ValidateError) .map_err(ValidationError::InvalidOrError)?; - let mut validation_in_progress = validate::validate_transaction(validate::Config { - runtime, - scale_encoded_header: block_scale_encoded_header, - block_number_bytes: relay_chain_sync.block_number_bytes(), - scale_encoded_transaction: iter::once(scale_encoded_transaction), - source, + if runtime + .runtime_version() + .decode() + .apis + .find_version("TaggedTransactionQueue") + != Some(3) + { + return Err(ValidationError::InvalidOrError( + InvalidOrError::ValidateError(ValidateTransactionError::UnknownApiVersion), + )); + } + + let mut validation_in_progress = match runtime_host::run(runtime_host::Config { + virtual_machine: runtime, + function_to_call: validate::VALIDATION_FUNCTION_NAME, + parameter: validate::validate_transaction_runtime_parameters_v3( + iter::once(scale_encoded_transaction.as_ref()), + source, + &block_hash, + ), + storage_main_trie_changes: Default::default(), max_log_level: 0, - }); + calculate_trie_changes: false, + }) { + Ok(r) => r, + Err((err, virtual_machine)) => { + runtime_call_lock.unlock(virtual_machine); + return Err(ValidationError::InvalidOrError( + InvalidOrError::ValidateError(ValidateTransactionError::WasmStart(err)), + )); + } + }; loop { match validation_in_progress { - validate::Query::Finished { - result: Ok(Ok(success)), - virtual_machine, - } => { - runtime_call_lock.unlock(virtual_machine); - break Ok(success); - } - validate::Query::Finished { - result: Ok(Err(invalid)), - virtual_machine, - } => { - runtime_call_lock.unlock(virtual_machine); - break Err(ValidationError::InvalidOrError(InvalidOrError::Invalid( - invalid, - ))); + runtime_host::RuntimeHostVm::Finished(Ok(success)) => { + let decode_result = validate::decode_validate_transaction_return_value( + success.virtual_machine.value().as_ref(), + ); + runtime_call_lock.unlock(success.virtual_machine.into_prototype()); + return match decode_result { + Ok(Ok(decoded)) => Ok(decoded), + Ok(Err(err)) => Err(ValidationError::InvalidOrError(InvalidOrError::Invalid( + err, + ))), + Err(err) => Err(ValidationError::InvalidOrError( + InvalidOrError::ValidateError(ValidateTransactionError::OutputDecodeError( + err, + )), + )), + }; } - validate::Query::Finished { - result: Err(error), - virtual_machine, - } => { - runtime_call_lock.unlock(virtual_machine); - break Err(ValidationError::InvalidOrError( - InvalidOrError::ValidateError(ValidateTransactionError::Validation(error)), + runtime_host::RuntimeHostVm::Finished(Err(err)) => { + return Err(ValidationError::InvalidOrError( + InvalidOrError::ValidateError(ValidateTransactionError::WasmExecution( + err.detail, + )), )); } - validate::Query::StorageGet(get) => { + runtime_host::RuntimeHostVm::StorageGet(get) => { let storage_value = { let child_trie = get.child_trie(); runtime_call_lock @@ -1424,7 +1457,8 @@ async fn validate_transaction( let storage_value = match storage_value { Ok(v) => v, Err(err) => { - runtime_call_lock.unlock(validate::Query::StorageGet(get).into_prototype()); + runtime_call_lock + .unlock(runtime_host::RuntimeHostVm::StorageGet(get).into_prototype()); return Err(ValidationError::InvalidOrError( InvalidOrError::ValidateError(ValidateTransactionError::Call(err)), )); @@ -1433,7 +1467,7 @@ async fn validate_transaction( validation_in_progress = get.inject_value(storage_value.map(|(val, vers)| (iter::once(val), vers))); } - validate::Query::ClosestDescendantMerkleValue(mv) => { + runtime_host::RuntimeHostVm::ClosestDescendantMerkleValue(mv) => { let merkle_value = { let child_trie = mv.child_trie(); runtime_call_lock.closest_descendant_merkle_value( @@ -1445,7 +1479,8 @@ async fn validate_transaction( Ok(v) => v, Err(err) => { runtime_call_lock.unlock( - validate::Query::ClosestDescendantMerkleValue(mv).into_prototype(), + runtime_host::RuntimeHostVm::ClosestDescendantMerkleValue(mv) + .into_prototype(), ); return Err(ValidationError::InvalidOrError( InvalidOrError::ValidateError(ValidateTransactionError::Call(err)), @@ -1454,7 +1489,7 @@ async fn validate_transaction( }; validation_in_progress = mv.inject_merkle_value(merkle_value); } - validate::Query::NextKey(nk) => { + runtime_host::RuntimeHostVm::NextKey(nk) => { let next_key = { let child_trie = nk.child_trie(); runtime_call_lock.next_key( @@ -1468,7 +1503,8 @@ async fn validate_transaction( let next_key = match next_key { Ok(v) => v, Err(err) => { - runtime_call_lock.unlock(validate::Query::NextKey(nk).into_prototype()); + runtime_call_lock + .unlock(runtime_host::RuntimeHostVm::NextKey(nk).into_prototype()); return Err(ValidationError::InvalidOrError( InvalidOrError::ValidateError(ValidateTransactionError::Call(err)), )); @@ -1476,6 +1512,22 @@ async fn validate_transaction( }; validation_in_progress = nk.inject_key(next_key); } + runtime_host::RuntimeHostVm::SignatureVerification(r) => { + validation_in_progress = r.verify_and_resume(); + } + runtime_host::RuntimeHostVm::LogEmit(r) => { + // Logs are ignored. + validation_in_progress = r.resume() + } + runtime_host::RuntimeHostVm::Offchain(_) => { + return Err(ValidationError::InvalidOrError( + InvalidOrError::ValidateError(ValidateTransactionError::ForbiddenHostCall), + )); + } + runtime_host::RuntimeHostVm::OffchainStorageSet(r) => { + // Ignore offset storage writes. + validation_in_progress = r.resume(); + } } } }