mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-04-26 14:37:57 +00:00
12ce4f7d04
Closes https://github.com/paritytech/polkadot-sdk-docs/issues/55 - Changes 'current storage version' terminology to less ambiguous 'in-code storage version' (suggestion by @ggwpez) - Adds a new example pallet `pallet-example-single-block-migrations` - Adds a new reference doc to replace https://docs.substrate.io/maintain/runtime-upgrades/ (temporarily living in the pallet while we wait for developer hub PR to merge) - Adds documentation for the `storage_alias` macro - Improves `trait Hooks` docs - Improves `trait GetStorageVersion` docs - Update the suggested patterns for using `VersionedMigration`, so that version unchecked migrations are never exported - Prevents accidental usage of version unchecked migrations in runtimes https://github.com/paritytech/substrate/pull/14421#discussion_r1255467895 - Unversioned migration code is kept inside `mod version_unchecked`, versioned code is kept in `pub mod versioned` - It is necessary to use modules to limit visibility because the inner migration must be `pub`. See https://github.com/rust-lang/rust/issues/30905 and https://internals.rust-lang.org/t/lang-team-minutes-private-in-public-rules/4504/40 for more. ### todo - [x] move to reference docs to proper place within sdk-docs (now that https://github.com/paritytech/polkadot-sdk/pull/2102 is merged) - [x] prdoc --------- Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com> Co-authored-by: Juan <juangirini@gmail.com> Co-authored-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io> Co-authored-by: command-bot <> Co-authored-by: gupnik <nikhilgupta.iitk@gmail.com>
792 lines
24 KiB
Rust
792 lines
24 KiB
Rust
// This file is part of Substrate.
|
|
|
|
// Copyright (C) Parity Technologies (UK) Ltd.
|
|
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
|
|
|
|
// This program 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.
|
|
|
|
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
|
|
|
|
use crate::{
|
|
error::{Error, Result},
|
|
wasm_runtime::{RuntimeCache, WasmExecutionMethod},
|
|
RuntimeVersionOf,
|
|
};
|
|
|
|
use std::{
|
|
marker::PhantomData,
|
|
panic::{AssertUnwindSafe, UnwindSafe},
|
|
path::PathBuf,
|
|
sync::Arc,
|
|
};
|
|
|
|
use codec::Encode;
|
|
use sc_executor_common::{
|
|
runtime_blob::RuntimeBlob,
|
|
wasm_runtime::{
|
|
AllocationStats, HeapAllocStrategy, WasmInstance, WasmModule, DEFAULT_HEAP_ALLOC_STRATEGY,
|
|
},
|
|
};
|
|
use sp_core::traits::{CallContext, CodeExecutor, Externalities, RuntimeCode};
|
|
use sp_version::{GetNativeVersion, NativeVersion, RuntimeVersion};
|
|
use sp_wasm_interface::{ExtendedHostFunctions, HostFunctions};
|
|
|
|
/// Set up the externalities and safe calling environment to execute runtime calls.
|
|
///
|
|
/// If the inner closure panics, it will be caught and return an error.
|
|
pub fn with_externalities_safe<F, U>(ext: &mut dyn Externalities, f: F) -> Result<U>
|
|
where
|
|
F: UnwindSafe + FnOnce() -> U,
|
|
{
|
|
sp_externalities::set_and_run_with_externalities(ext, move || {
|
|
// Substrate uses custom panic hook that terminates process on panic. Disable
|
|
// termination for the native call.
|
|
let _guard = sp_panic_handler::AbortGuard::force_unwind();
|
|
std::panic::catch_unwind(f).map_err(|e| {
|
|
if let Some(err) = e.downcast_ref::<String>() {
|
|
Error::RuntimePanicked(err.clone())
|
|
} else if let Some(err) = e.downcast_ref::<&'static str>() {
|
|
Error::RuntimePanicked(err.to_string())
|
|
} else {
|
|
Error::RuntimePanicked("Unknown panic".into())
|
|
}
|
|
})
|
|
})
|
|
}
|
|
|
|
/// Delegate for dispatching a CodeExecutor call.
|
|
///
|
|
/// By dispatching we mean that we execute a runtime function specified by it's name.
|
|
pub trait NativeExecutionDispatch: Send + Sync {
|
|
/// Host functions for custom runtime interfaces that should be callable from within the runtime
|
|
/// besides the default Substrate runtime interfaces.
|
|
type ExtendHostFunctions: HostFunctions;
|
|
|
|
/// Dispatch a method in the runtime.
|
|
fn dispatch(method: &str, data: &[u8]) -> Option<Vec<u8>>;
|
|
|
|
/// Provide native runtime version.
|
|
fn native_version() -> NativeVersion;
|
|
}
|
|
|
|
fn unwrap_heap_pages(pages: Option<HeapAllocStrategy>) -> HeapAllocStrategy {
|
|
pages.unwrap_or_else(|| DEFAULT_HEAP_ALLOC_STRATEGY)
|
|
}
|
|
|
|
/// Builder for creating a [`WasmExecutor`] instance.
|
|
pub struct WasmExecutorBuilder<H> {
|
|
_phantom: PhantomData<H>,
|
|
method: WasmExecutionMethod,
|
|
onchain_heap_alloc_strategy: Option<HeapAllocStrategy>,
|
|
offchain_heap_alloc_strategy: Option<HeapAllocStrategy>,
|
|
ignore_onchain_heap_pages: bool,
|
|
max_runtime_instances: usize,
|
|
cache_path: Option<PathBuf>,
|
|
allow_missing_host_functions: bool,
|
|
runtime_cache_size: u8,
|
|
}
|
|
|
|
impl<H> WasmExecutorBuilder<H> {
|
|
/// Create a new instance of `Self`
|
|
///
|
|
/// - `method`: The wasm execution method that should be used by the executor.
|
|
pub fn new() -> Self {
|
|
Self {
|
|
_phantom: PhantomData,
|
|
method: WasmExecutionMethod::default(),
|
|
onchain_heap_alloc_strategy: None,
|
|
offchain_heap_alloc_strategy: None,
|
|
ignore_onchain_heap_pages: false,
|
|
max_runtime_instances: 2,
|
|
runtime_cache_size: 4,
|
|
allow_missing_host_functions: false,
|
|
cache_path: None,
|
|
}
|
|
}
|
|
|
|
/// Create the wasm executor with execution method that should be used by the executor.
|
|
pub fn with_execution_method(mut self, method: WasmExecutionMethod) -> Self {
|
|
self.method = method;
|
|
self
|
|
}
|
|
|
|
/// Create the wasm executor with the given number of `heap_alloc_strategy` for onchain runtime
|
|
/// calls.
|
|
pub fn with_onchain_heap_alloc_strategy(
|
|
mut self,
|
|
heap_alloc_strategy: HeapAllocStrategy,
|
|
) -> Self {
|
|
self.onchain_heap_alloc_strategy = Some(heap_alloc_strategy);
|
|
self
|
|
}
|
|
|
|
/// Create the wasm executor with the given number of `heap_alloc_strategy` for offchain runtime
|
|
/// calls.
|
|
pub fn with_offchain_heap_alloc_strategy(
|
|
mut self,
|
|
heap_alloc_strategy: HeapAllocStrategy,
|
|
) -> Self {
|
|
self.offchain_heap_alloc_strategy = Some(heap_alloc_strategy);
|
|
self
|
|
}
|
|
|
|
/// Create the wasm executor and follow/ignore onchain heap pages value.
|
|
///
|
|
/// By default this the onchain heap pages value is followed.
|
|
pub fn with_ignore_onchain_heap_pages(mut self, ignore_onchain_heap_pages: bool) -> Self {
|
|
self.ignore_onchain_heap_pages = ignore_onchain_heap_pages;
|
|
self
|
|
}
|
|
|
|
/// Create the wasm executor with the given maximum number of `instances`.
|
|
///
|
|
/// The number of `instances` defines how many different instances of a runtime the cache is
|
|
/// storing.
|
|
///
|
|
/// By default the maximum number of `instances` is `2`.
|
|
pub fn with_max_runtime_instances(mut self, instances: usize) -> Self {
|
|
self.max_runtime_instances = instances;
|
|
self
|
|
}
|
|
|
|
/// Create the wasm executor with the given `cache_path`.
|
|
///
|
|
/// The `cache_path` is A path to a directory where the executor can place its files for
|
|
/// purposes of caching. This may be important in cases when there are many different modules
|
|
/// with the compiled execution method is used.
|
|
///
|
|
/// By default there is no `cache_path` given.
|
|
pub fn with_cache_path(mut self, cache_path: impl Into<PathBuf>) -> Self {
|
|
self.cache_path = Some(cache_path.into());
|
|
self
|
|
}
|
|
|
|
/// Create the wasm executor and allow/forbid missing host functions.
|
|
///
|
|
/// If missing host functions are forbidden, the instantiation of a wasm blob will fail
|
|
/// for imported host functions that the executor is not aware of. If they are allowed,
|
|
/// a stub is generated that will return an error when being called while executing the wasm.
|
|
///
|
|
/// By default missing host functions are forbidden.
|
|
pub fn with_allow_missing_host_functions(mut self, allow: bool) -> Self {
|
|
self.allow_missing_host_functions = allow;
|
|
self
|
|
}
|
|
|
|
/// Create the wasm executor with the given `runtime_cache_size`.
|
|
///
|
|
/// Defines the number of different runtimes/instantiated wasm blobs the cache stores.
|
|
/// Runtimes/wasm blobs are differentiated based on the hash and the number of heap pages.
|
|
///
|
|
/// By default this value is set to `4`.
|
|
pub fn with_runtime_cache_size(mut self, runtime_cache_size: u8) -> Self {
|
|
self.runtime_cache_size = runtime_cache_size;
|
|
self
|
|
}
|
|
|
|
/// Build the configured [`WasmExecutor`].
|
|
pub fn build(self) -> WasmExecutor<H> {
|
|
WasmExecutor {
|
|
method: self.method,
|
|
default_offchain_heap_alloc_strategy: unwrap_heap_pages(
|
|
self.offchain_heap_alloc_strategy,
|
|
),
|
|
default_onchain_heap_alloc_strategy: unwrap_heap_pages(
|
|
self.onchain_heap_alloc_strategy,
|
|
),
|
|
ignore_onchain_heap_pages: self.ignore_onchain_heap_pages,
|
|
cache: Arc::new(RuntimeCache::new(
|
|
self.max_runtime_instances,
|
|
self.cache_path.clone(),
|
|
self.runtime_cache_size,
|
|
)),
|
|
cache_path: self.cache_path,
|
|
allow_missing_host_functions: self.allow_missing_host_functions,
|
|
phantom: PhantomData,
|
|
}
|
|
}
|
|
}
|
|
|
|
/// An abstraction over Wasm code executor. Supports selecting execution backend and
|
|
/// manages runtime cache.
|
|
pub struct WasmExecutor<H> {
|
|
/// Method used to execute fallback Wasm code.
|
|
method: WasmExecutionMethod,
|
|
/// The heap allocation strategy for onchain Wasm calls.
|
|
default_onchain_heap_alloc_strategy: HeapAllocStrategy,
|
|
/// The heap allocation strategy for offchain Wasm calls.
|
|
default_offchain_heap_alloc_strategy: HeapAllocStrategy,
|
|
/// Ignore onchain heap pages value.
|
|
ignore_onchain_heap_pages: bool,
|
|
/// WASM runtime cache.
|
|
cache: Arc<RuntimeCache>,
|
|
/// The path to a directory which the executor can leverage for a file cache, e.g. put there
|
|
/// compiled artifacts.
|
|
cache_path: Option<PathBuf>,
|
|
/// Ignore missing function imports.
|
|
allow_missing_host_functions: bool,
|
|
phantom: PhantomData<H>,
|
|
}
|
|
|
|
impl<H> Clone for WasmExecutor<H> {
|
|
fn clone(&self) -> Self {
|
|
Self {
|
|
method: self.method,
|
|
default_onchain_heap_alloc_strategy: self.default_onchain_heap_alloc_strategy,
|
|
default_offchain_heap_alloc_strategy: self.default_offchain_heap_alloc_strategy,
|
|
ignore_onchain_heap_pages: self.ignore_onchain_heap_pages,
|
|
cache: self.cache.clone(),
|
|
cache_path: self.cache_path.clone(),
|
|
allow_missing_host_functions: self.allow_missing_host_functions,
|
|
phantom: self.phantom,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<H> WasmExecutor<H>
|
|
where
|
|
H: HostFunctions,
|
|
{
|
|
/// Create new instance.
|
|
///
|
|
/// # Parameters
|
|
///
|
|
/// `method` - Method used to execute Wasm code.
|
|
///
|
|
/// `default_heap_pages` - Number of 64KB pages to allocate for Wasm execution. Internally this
|
|
/// will be mapped as [`HeapAllocStrategy::Static`] where `default_heap_pages` represent the
|
|
/// static number of heap pages to allocate. Defaults to `DEFAULT_HEAP_ALLOC_STRATEGY` if `None`
|
|
/// is provided.
|
|
///
|
|
/// `max_runtime_instances` - The number of runtime instances to keep in memory ready for reuse.
|
|
///
|
|
/// `cache_path` - A path to a directory where the executor can place its files for purposes of
|
|
/// caching. This may be important in cases when there are many different modules with the
|
|
/// compiled execution method is used.
|
|
///
|
|
/// `runtime_cache_size` - The capacity of runtime cache.
|
|
#[deprecated(note = "use `Self::builder` method instead of it")]
|
|
pub fn new(
|
|
method: WasmExecutionMethod,
|
|
default_heap_pages: Option<u64>,
|
|
max_runtime_instances: usize,
|
|
cache_path: Option<PathBuf>,
|
|
runtime_cache_size: u8,
|
|
) -> Self {
|
|
WasmExecutor {
|
|
method,
|
|
default_onchain_heap_alloc_strategy: unwrap_heap_pages(
|
|
default_heap_pages.map(|h| HeapAllocStrategy::Static { extra_pages: h as _ }),
|
|
),
|
|
default_offchain_heap_alloc_strategy: unwrap_heap_pages(
|
|
default_heap_pages.map(|h| HeapAllocStrategy::Static { extra_pages: h as _ }),
|
|
),
|
|
ignore_onchain_heap_pages: false,
|
|
cache: Arc::new(RuntimeCache::new(
|
|
max_runtime_instances,
|
|
cache_path.clone(),
|
|
runtime_cache_size,
|
|
)),
|
|
cache_path,
|
|
allow_missing_host_functions: false,
|
|
phantom: PhantomData,
|
|
}
|
|
}
|
|
|
|
/// Instantiate a builder for creating an instance of `Self`.
|
|
pub fn builder() -> WasmExecutorBuilder<H> {
|
|
WasmExecutorBuilder::new()
|
|
}
|
|
|
|
/// Ignore missing function imports if set true.
|
|
#[deprecated(note = "use `Self::builder` method instead of it")]
|
|
pub fn allow_missing_host_functions(&mut self, allow_missing_host_functions: bool) {
|
|
self.allow_missing_host_functions = allow_missing_host_functions
|
|
}
|
|
|
|
/// Execute the given closure `f` with the latest runtime (based on `runtime_code`).
|
|
///
|
|
/// The closure `f` is expected to return `Err(_)` when there happened a `panic!` in native code
|
|
/// while executing the runtime in Wasm. If a `panic!` occurred, the runtime is invalidated to
|
|
/// prevent any poisoned state. Native runtime execution does not need to report back
|
|
/// any `panic!`.
|
|
///
|
|
/// # Safety
|
|
///
|
|
/// `runtime` and `ext` are given as `AssertUnwindSafe` to the closure. As described above, the
|
|
/// runtime is invalidated on any `panic!` to prevent a poisoned state. `ext` is already
|
|
/// implicitly handled as unwind safe, as we store it in a global variable while executing the
|
|
/// native runtime.
|
|
pub fn with_instance<R, F>(
|
|
&self,
|
|
runtime_code: &RuntimeCode,
|
|
ext: &mut dyn Externalities,
|
|
heap_alloc_strategy: HeapAllocStrategy,
|
|
f: F,
|
|
) -> Result<R>
|
|
where
|
|
F: FnOnce(
|
|
AssertUnwindSafe<&dyn WasmModule>,
|
|
AssertUnwindSafe<&mut dyn WasmInstance>,
|
|
Option<&RuntimeVersion>,
|
|
AssertUnwindSafe<&mut dyn Externalities>,
|
|
) -> Result<Result<R>>,
|
|
{
|
|
match self.cache.with_instance::<H, _, _>(
|
|
runtime_code,
|
|
ext,
|
|
self.method,
|
|
heap_alloc_strategy,
|
|
self.allow_missing_host_functions,
|
|
|module, instance, version, ext| {
|
|
let module = AssertUnwindSafe(module);
|
|
let instance = AssertUnwindSafe(instance);
|
|
let ext = AssertUnwindSafe(ext);
|
|
f(module, instance, version, ext)
|
|
},
|
|
)? {
|
|
Ok(r) => r,
|
|
Err(e) => Err(e),
|
|
}
|
|
}
|
|
|
|
/// Perform a call into the given runtime.
|
|
///
|
|
/// The runtime is passed as a [`RuntimeBlob`]. The runtime will be instantiated with the
|
|
/// parameters this `WasmExecutor` was initialized with.
|
|
///
|
|
/// In case of problems with during creation of the runtime or instantiation, a `Err` is
|
|
/// returned. that describes the message.
|
|
#[doc(hidden)] // We use this function for tests across multiple crates.
|
|
pub fn uncached_call(
|
|
&self,
|
|
runtime_blob: RuntimeBlob,
|
|
ext: &mut dyn Externalities,
|
|
allow_missing_host_functions: bool,
|
|
export_name: &str,
|
|
call_data: &[u8],
|
|
) -> std::result::Result<Vec<u8>, Error> {
|
|
self.uncached_call_impl(
|
|
runtime_blob,
|
|
ext,
|
|
allow_missing_host_functions,
|
|
export_name,
|
|
call_data,
|
|
&mut None,
|
|
)
|
|
}
|
|
|
|
/// Same as `uncached_call`, except it also returns allocation statistics.
|
|
#[doc(hidden)] // We use this function in tests.
|
|
pub fn uncached_call_with_allocation_stats(
|
|
&self,
|
|
runtime_blob: RuntimeBlob,
|
|
ext: &mut dyn Externalities,
|
|
allow_missing_host_functions: bool,
|
|
export_name: &str,
|
|
call_data: &[u8],
|
|
) -> (std::result::Result<Vec<u8>, Error>, Option<AllocationStats>) {
|
|
let mut allocation_stats = None;
|
|
let result = self.uncached_call_impl(
|
|
runtime_blob,
|
|
ext,
|
|
allow_missing_host_functions,
|
|
export_name,
|
|
call_data,
|
|
&mut allocation_stats,
|
|
);
|
|
(result, allocation_stats)
|
|
}
|
|
|
|
fn uncached_call_impl(
|
|
&self,
|
|
runtime_blob: RuntimeBlob,
|
|
ext: &mut dyn Externalities,
|
|
allow_missing_host_functions: bool,
|
|
export_name: &str,
|
|
call_data: &[u8],
|
|
allocation_stats_out: &mut Option<AllocationStats>,
|
|
) -> std::result::Result<Vec<u8>, Error> {
|
|
let module = crate::wasm_runtime::create_wasm_runtime_with_code::<H>(
|
|
self.method,
|
|
self.default_onchain_heap_alloc_strategy,
|
|
runtime_blob,
|
|
allow_missing_host_functions,
|
|
self.cache_path.as_deref(),
|
|
)
|
|
.map_err(|e| format!("Failed to create module: {}", e))?;
|
|
|
|
let instance =
|
|
module.new_instance().map_err(|e| format!("Failed to create instance: {}", e))?;
|
|
|
|
let mut instance = AssertUnwindSafe(instance);
|
|
let mut ext = AssertUnwindSafe(ext);
|
|
let mut allocation_stats_out = AssertUnwindSafe(allocation_stats_out);
|
|
|
|
with_externalities_safe(&mut **ext, move || {
|
|
let (result, allocation_stats) =
|
|
instance.call_with_allocation_stats(export_name.into(), call_data);
|
|
**allocation_stats_out = allocation_stats;
|
|
result
|
|
})
|
|
.and_then(|r| r)
|
|
}
|
|
}
|
|
|
|
impl<H> sp_core::traits::ReadRuntimeVersion for WasmExecutor<H>
|
|
where
|
|
H: HostFunctions,
|
|
{
|
|
fn read_runtime_version(
|
|
&self,
|
|
wasm_code: &[u8],
|
|
ext: &mut dyn Externalities,
|
|
) -> std::result::Result<Vec<u8>, String> {
|
|
let runtime_blob = RuntimeBlob::uncompress_if_needed(wasm_code)
|
|
.map_err(|e| format!("Failed to create runtime blob: {:?}", e))?;
|
|
|
|
if let Some(version) = crate::wasm_runtime::read_embedded_version(&runtime_blob)
|
|
.map_err(|e| format!("Failed to read the static section: {:?}", e))
|
|
.map(|v| v.map(|v| v.encode()))?
|
|
{
|
|
return Ok(version)
|
|
}
|
|
|
|
// If the blob didn't have embedded runtime version section, we fallback to the legacy
|
|
// way of fetching the version: i.e. instantiating the given instance and calling
|
|
// `Core_version` on it.
|
|
|
|
self.uncached_call(
|
|
runtime_blob,
|
|
ext,
|
|
// If a runtime upgrade introduces new host functions that are not provided by
|
|
// the node, we should not fail at instantiation. Otherwise nodes that are
|
|
// updated could run this successfully and it could lead to a storage root
|
|
// mismatch when importing this block.
|
|
true,
|
|
"Core_version",
|
|
&[],
|
|
)
|
|
.map_err(|e| e.to_string())
|
|
}
|
|
}
|
|
|
|
impl<H> CodeExecutor for WasmExecutor<H>
|
|
where
|
|
H: HostFunctions,
|
|
{
|
|
type Error = Error;
|
|
|
|
fn call(
|
|
&self,
|
|
ext: &mut dyn Externalities,
|
|
runtime_code: &RuntimeCode,
|
|
method: &str,
|
|
data: &[u8],
|
|
context: CallContext,
|
|
) -> (Result<Vec<u8>>, bool) {
|
|
tracing::trace!(
|
|
target: "executor",
|
|
%method,
|
|
"Executing function",
|
|
);
|
|
|
|
let on_chain_heap_alloc_strategy = if self.ignore_onchain_heap_pages {
|
|
self.default_onchain_heap_alloc_strategy
|
|
} else {
|
|
runtime_code
|
|
.heap_pages
|
|
.map(|h| HeapAllocStrategy::Static { extra_pages: h as _ })
|
|
.unwrap_or_else(|| self.default_onchain_heap_alloc_strategy)
|
|
};
|
|
|
|
let heap_alloc_strategy = match context {
|
|
CallContext::Offchain => self.default_offchain_heap_alloc_strategy,
|
|
CallContext::Onchain => on_chain_heap_alloc_strategy,
|
|
};
|
|
|
|
let result = self.with_instance(
|
|
runtime_code,
|
|
ext,
|
|
heap_alloc_strategy,
|
|
|_, mut instance, _on_chain_version, mut ext| {
|
|
with_externalities_safe(&mut **ext, move || instance.call_export(method, data))
|
|
},
|
|
);
|
|
|
|
(result, false)
|
|
}
|
|
}
|
|
|
|
impl<H> RuntimeVersionOf for WasmExecutor<H>
|
|
where
|
|
H: HostFunctions,
|
|
{
|
|
fn runtime_version(
|
|
&self,
|
|
ext: &mut dyn Externalities,
|
|
runtime_code: &RuntimeCode,
|
|
) -> Result<RuntimeVersion> {
|
|
let on_chain_heap_pages = if self.ignore_onchain_heap_pages {
|
|
self.default_onchain_heap_alloc_strategy
|
|
} else {
|
|
runtime_code
|
|
.heap_pages
|
|
.map(|h| HeapAllocStrategy::Static { extra_pages: h as _ })
|
|
.unwrap_or_else(|| self.default_onchain_heap_alloc_strategy)
|
|
};
|
|
|
|
self.with_instance(
|
|
runtime_code,
|
|
ext,
|
|
on_chain_heap_pages,
|
|
|_module, _instance, version, _ext| {
|
|
Ok(version.cloned().ok_or_else(|| Error::ApiError("Unknown version".into())))
|
|
},
|
|
)
|
|
}
|
|
}
|
|
|
|
/// A generic `CodeExecutor` implementation that uses a delegate to determine wasm code equivalence
|
|
/// and dispatch to native code when possible, falling back on `WasmExecutor` when not.
|
|
pub struct NativeElseWasmExecutor<D: NativeExecutionDispatch> {
|
|
/// Native runtime version info.
|
|
native_version: NativeVersion,
|
|
/// Fallback wasm executor.
|
|
wasm:
|
|
WasmExecutor<ExtendedHostFunctions<sp_io::SubstrateHostFunctions, D::ExtendHostFunctions>>,
|
|
|
|
use_native: bool,
|
|
}
|
|
|
|
impl<D: NativeExecutionDispatch> NativeElseWasmExecutor<D> {
|
|
///
|
|
/// Create new instance.
|
|
///
|
|
/// # Parameters
|
|
///
|
|
/// `fallback_method` - Method used to execute fallback Wasm code.
|
|
///
|
|
/// `default_heap_pages` - Number of 64KB pages to allocate for Wasm execution. Internally this
|
|
/// will be mapped as [`HeapAllocStrategy::Static`] where `default_heap_pages` represent the
|
|
/// static number of heap pages to allocate. Defaults to `DEFAULT_HEAP_ALLOC_STRATEGY` if `None`
|
|
/// is provided.
|
|
///
|
|
/// `max_runtime_instances` - The number of runtime instances to keep in memory ready for reuse.
|
|
///
|
|
/// `runtime_cache_size` - The capacity of runtime cache.
|
|
#[deprecated(note = "use `Self::new_with_wasm_executor` method instead of it")]
|
|
pub fn new(
|
|
fallback_method: WasmExecutionMethod,
|
|
default_heap_pages: Option<u64>,
|
|
max_runtime_instances: usize,
|
|
runtime_cache_size: u8,
|
|
) -> Self {
|
|
let heap_pages = default_heap_pages.map_or(DEFAULT_HEAP_ALLOC_STRATEGY, |h| {
|
|
HeapAllocStrategy::Static { extra_pages: h as _ }
|
|
});
|
|
let wasm = WasmExecutor::builder()
|
|
.with_execution_method(fallback_method)
|
|
.with_onchain_heap_alloc_strategy(heap_pages)
|
|
.with_offchain_heap_alloc_strategy(heap_pages)
|
|
.with_max_runtime_instances(max_runtime_instances)
|
|
.with_runtime_cache_size(runtime_cache_size)
|
|
.build();
|
|
|
|
NativeElseWasmExecutor { native_version: D::native_version(), wasm, use_native: true }
|
|
}
|
|
|
|
/// Create a new instance using the given [`WasmExecutor`].
|
|
pub fn new_with_wasm_executor(
|
|
executor: WasmExecutor<
|
|
ExtendedHostFunctions<sp_io::SubstrateHostFunctions, D::ExtendHostFunctions>,
|
|
>,
|
|
) -> Self {
|
|
Self { native_version: D::native_version(), wasm: executor, use_native: true }
|
|
}
|
|
|
|
/// Disable to use native runtime when possible just behave like `WasmExecutor`.
|
|
///
|
|
/// Default to enabled.
|
|
pub fn disable_use_native(&mut self) {
|
|
self.use_native = false;
|
|
}
|
|
|
|
/// Ignore missing function imports if set true.
|
|
#[deprecated(note = "use `Self::new_with_wasm_executor` method instead of it")]
|
|
pub fn allow_missing_host_functions(&mut self, allow_missing_host_functions: bool) {
|
|
self.wasm.allow_missing_host_functions = allow_missing_host_functions
|
|
}
|
|
}
|
|
|
|
impl<D: NativeExecutionDispatch> RuntimeVersionOf for NativeElseWasmExecutor<D> {
|
|
fn runtime_version(
|
|
&self,
|
|
ext: &mut dyn Externalities,
|
|
runtime_code: &RuntimeCode,
|
|
) -> Result<RuntimeVersion> {
|
|
self.wasm.runtime_version(ext, runtime_code)
|
|
}
|
|
}
|
|
|
|
impl<D: NativeExecutionDispatch> GetNativeVersion for NativeElseWasmExecutor<D> {
|
|
fn native_version(&self) -> &NativeVersion {
|
|
&self.native_version
|
|
}
|
|
}
|
|
|
|
impl<D: NativeExecutionDispatch + 'static> CodeExecutor for NativeElseWasmExecutor<D> {
|
|
type Error = Error;
|
|
|
|
fn call(
|
|
&self,
|
|
ext: &mut dyn Externalities,
|
|
runtime_code: &RuntimeCode,
|
|
method: &str,
|
|
data: &[u8],
|
|
context: CallContext,
|
|
) -> (Result<Vec<u8>>, bool) {
|
|
let use_native = self.use_native;
|
|
|
|
tracing::trace!(
|
|
target: "executor",
|
|
function = %method,
|
|
"Executing function",
|
|
);
|
|
|
|
let on_chain_heap_alloc_strategy = if self.wasm.ignore_onchain_heap_pages {
|
|
self.wasm.default_onchain_heap_alloc_strategy
|
|
} else {
|
|
runtime_code
|
|
.heap_pages
|
|
.map(|h| HeapAllocStrategy::Static { extra_pages: h as _ })
|
|
.unwrap_or_else(|| self.wasm.default_onchain_heap_alloc_strategy)
|
|
};
|
|
|
|
let heap_alloc_strategy = match context {
|
|
CallContext::Offchain => self.wasm.default_offchain_heap_alloc_strategy,
|
|
CallContext::Onchain => on_chain_heap_alloc_strategy,
|
|
};
|
|
|
|
let mut used_native = false;
|
|
let result = self.wasm.with_instance(
|
|
runtime_code,
|
|
ext,
|
|
heap_alloc_strategy,
|
|
|_, mut instance, on_chain_version, mut ext| {
|
|
let on_chain_version =
|
|
on_chain_version.ok_or_else(|| Error::ApiError("Unknown version".into()))?;
|
|
|
|
let can_call_with =
|
|
on_chain_version.can_call_with(&self.native_version.runtime_version);
|
|
|
|
if use_native && can_call_with {
|
|
tracing::trace!(
|
|
target: "executor",
|
|
native = %self.native_version.runtime_version,
|
|
chain = %on_chain_version,
|
|
"Request for native execution succeeded",
|
|
);
|
|
|
|
used_native = true;
|
|
Ok(with_externalities_safe(&mut **ext, move || D::dispatch(method, data))?
|
|
.ok_or_else(|| Error::MethodNotFound(method.to_owned())))
|
|
} else {
|
|
if !can_call_with {
|
|
tracing::trace!(
|
|
target: "executor",
|
|
native = %self.native_version.runtime_version,
|
|
chain = %on_chain_version,
|
|
"Request for native execution failed",
|
|
);
|
|
}
|
|
|
|
with_externalities_safe(&mut **ext, move || instance.call_export(method, data))
|
|
}
|
|
},
|
|
);
|
|
(result, used_native)
|
|
}
|
|
}
|
|
|
|
impl<D: NativeExecutionDispatch> Clone for NativeElseWasmExecutor<D> {
|
|
fn clone(&self) -> Self {
|
|
NativeElseWasmExecutor {
|
|
native_version: D::native_version(),
|
|
wasm: self.wasm.clone(),
|
|
use_native: self.use_native,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<D: NativeExecutionDispatch> sp_core::traits::ReadRuntimeVersion for NativeElseWasmExecutor<D> {
|
|
fn read_runtime_version(
|
|
&self,
|
|
wasm_code: &[u8],
|
|
ext: &mut dyn Externalities,
|
|
) -> std::result::Result<Vec<u8>, String> {
|
|
self.wasm.read_runtime_version(wasm_code, ext)
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use sp_runtime_interface::runtime_interface;
|
|
|
|
#[runtime_interface]
|
|
trait MyInterface {
|
|
fn say_hello_world(data: &str) {
|
|
println!("Hello world from: {}", data);
|
|
}
|
|
}
|
|
|
|
pub struct MyExecutorDispatch;
|
|
|
|
impl NativeExecutionDispatch for MyExecutorDispatch {
|
|
type ExtendHostFunctions = (my_interface::HostFunctions, my_interface::HostFunctions);
|
|
|
|
fn dispatch(method: &str, data: &[u8]) -> Option<Vec<u8>> {
|
|
substrate_test_runtime::api::dispatch(method, data)
|
|
}
|
|
|
|
fn native_version() -> NativeVersion {
|
|
substrate_test_runtime::native_version()
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn native_executor_registers_custom_interface() {
|
|
let executor = NativeElseWasmExecutor::<MyExecutorDispatch>::new_with_wasm_executor(
|
|
WasmExecutor::builder().build(),
|
|
);
|
|
|
|
fn extract_host_functions<H>(
|
|
_: &WasmExecutor<H>,
|
|
) -> Vec<&'static dyn sp_wasm_interface::Function>
|
|
where
|
|
H: HostFunctions,
|
|
{
|
|
H::host_functions()
|
|
}
|
|
|
|
my_interface::HostFunctions::host_functions().iter().for_each(|function| {
|
|
assert_eq!(
|
|
extract_host_functions(&executor.wasm).iter().filter(|f| f == &function).count(),
|
|
2
|
|
);
|
|
});
|
|
|
|
my_interface::say_hello_world("hey");
|
|
}
|
|
}
|