feat: Rebrand Polkadot/Substrate references to PezkuwiChain
This commit systematically rebrands various references from Parity Technologies' Polkadot/Substrate ecosystem to PezkuwiChain within the kurdistan-sdk. Key changes include: - Updated external repository URLs (zombienet-sdk, parity-db, parity-scale-codec, wasm-instrument) to point to pezkuwichain forks. - Modified internal documentation and code comments to reflect PezkuwiChain naming and structure. - Replaced direct references to with or specific paths within the for XCM, Pezkuwi, and other modules. - Cleaned up deprecated issue and PR references in various and files, particularly in and modules. - Adjusted image and logo URLs in documentation to point to PezkuwiChain assets. - Removed or rephrased comments related to external Polkadot/Substrate PRs and issues. This is a significant step towards fully customizing the SDK for the PezkuwiChain ecosystem.
This commit is contained in:
@@ -0,0 +1,809 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// 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 pezsc_executor_common::{
|
||||
runtime_blob::RuntimeBlob,
|
||||
wasm_runtime::{
|
||||
AllocationStats, HeapAllocStrategy, WasmInstance, WasmModule, DEFAULT_HEAP_ALLOC_STRATEGY,
|
||||
},
|
||||
};
|
||||
use pezsp_core::traits::{CallContext, CodeExecutor, Externalities, RuntimeCode};
|
||||
use pezsp_version::{GetNativeVersion, NativeVersion, RuntimeVersion};
|
||||
use pezsp_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,
|
||||
{
|
||||
pezsp_externalities::set_and_run_with_externalities(ext, move || {
|
||||
// Bizinikiwi uses custom panic hook that terminates process on panic. Disable
|
||||
// termination for the native call.
|
||||
let _guard = pezsp_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 Bizinikiwi 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 = pezsp_io::BizinikiwiHostFunctions> {
|
||||
_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 = pezsp_io::BizinikiwiHostFunctions> {
|
||||
/// 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 Default for WasmExecutor<pezsp_io::BizinikiwiHostFunctions> {
|
||||
fn default() -> Self {
|
||||
WasmExecutorBuilder::new().build()
|
||||
}
|
||||
}
|
||||
|
||||
impl<H> WasmExecutor<H> {
|
||||
/// 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
|
||||
}
|
||||
}
|
||||
|
||||
impl<H> WasmExecutor<H>
|
||||
where
|
||||
H: HostFunctions,
|
||||
{
|
||||
/// 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> pezsp_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.
|
||||
#[deprecated(
|
||||
note = "Native execution will be deprecated, please replace with `WasmExecutor`. Will be removed at end of 2024."
|
||||
)]
|
||||
pub struct NativeElseWasmExecutor<D: NativeExecutionDispatch> {
|
||||
/// Native runtime version info.
|
||||
native_version: NativeVersion,
|
||||
/// Fallback wasm executor.
|
||||
wasm:
|
||||
WasmExecutor<ExtendedHostFunctions<pezsp_io::BizinikiwiHostFunctions, D::ExtendHostFunctions>>,
|
||||
|
||||
use_native: bool,
|
||||
}
|
||||
|
||||
#[allow(deprecated)]
|
||||
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<pezsp_io::BizinikiwiHostFunctions, 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
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(deprecated)]
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(deprecated)]
|
||||
impl<D: NativeExecutionDispatch> GetNativeVersion for NativeElseWasmExecutor<D> {
|
||||
fn native_version(&self) -> &NativeVersion {
|
||||
&self.native_version
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(deprecated)]
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(deprecated)]
|
||||
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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(deprecated)]
|
||||
impl<D: NativeExecutionDispatch> pezsp_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 pezsp_runtime_interface::{pass_by::PassFatPointerAndRead, runtime_interface};
|
||||
|
||||
#[runtime_interface]
|
||||
trait MyInterface {
|
||||
fn say_hello_world(data: PassFatPointerAndRead<&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>> {
|
||||
bizinikiwi_test_runtime::api::dispatch(method, data)
|
||||
}
|
||||
|
||||
fn native_version() -> NativeVersion {
|
||||
bizinikiwi_test_runtime::native_version()
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[allow(deprecated)]
|
||||
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 pezsp_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");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,801 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// 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 assert_matches::assert_matches;
|
||||
use codec::{Decode, Encode};
|
||||
use pezsc_executor_common::{
|
||||
error::Error,
|
||||
runtime_blob::RuntimeBlob,
|
||||
wasm_runtime::{HeapAllocStrategy, WasmModule},
|
||||
};
|
||||
use pezsc_runtime_test::wasm_binary_unwrap;
|
||||
use pezsp_core::{
|
||||
ed25519, map,
|
||||
offchain::{testing, OffchainDbExt, OffchainWorkerExt},
|
||||
sr25519,
|
||||
traits::Externalities,
|
||||
Pair,
|
||||
};
|
||||
use pezsp_crypto_hashing::{blake2_128, blake2_256, sha2_256, twox_128, twox_256};
|
||||
use pezsp_runtime::traits::BlakeTwo256;
|
||||
use pezsp_state_machine::TestExternalities as CoreTestExternalities;
|
||||
use pezsp_trie::{LayoutV1 as Layout, TrieConfiguration};
|
||||
use std::sync::Arc;
|
||||
use tracing_subscriber::layer::SubscriberExt;
|
||||
|
||||
use crate::WasmExecutionMethod;
|
||||
|
||||
pub type TestExternalities = CoreTestExternalities<BlakeTwo256>;
|
||||
type HostFunctions = pezsp_io::BizinikiwiHostFunctions;
|
||||
|
||||
/// Simple macro that runs a given method as test with the available wasm execution methods.
|
||||
#[macro_export]
|
||||
macro_rules! test_wasm_execution {
|
||||
($method_name:ident) => {
|
||||
paste::item! {
|
||||
#[test]
|
||||
fn [<$method_name _compiled_recreate_instance_cow>]() {
|
||||
let _ = pezsp_tracing::try_init_simple();
|
||||
$method_name(WasmExecutionMethod::Compiled {
|
||||
instantiation_strategy: pezsc_executor_wasmtime::InstantiationStrategy::RecreateInstanceCopyOnWrite
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn [<$method_name _compiled_recreate_instance_vanilla>]() {
|
||||
let _ = pezsp_tracing::try_init_simple();
|
||||
$method_name(WasmExecutionMethod::Compiled {
|
||||
instantiation_strategy: pezsc_executor_wasmtime::InstantiationStrategy::RecreateInstance
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn [<$method_name _compiled_pooling_cow>]() {
|
||||
let _ = pezsp_tracing::try_init_simple();
|
||||
$method_name(WasmExecutionMethod::Compiled {
|
||||
instantiation_strategy: pezsc_executor_wasmtime::InstantiationStrategy::PoolingCopyOnWrite
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn [<$method_name _compiled_pooling_vanilla>]() {
|
||||
let _ = pezsp_tracing::try_init_simple();
|
||||
$method_name(WasmExecutionMethod::Compiled {
|
||||
instantiation_strategy: pezsc_executor_wasmtime::InstantiationStrategy::Pooling
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
fn call_in_wasm<E: Externalities>(
|
||||
function: &str,
|
||||
call_data: &[u8],
|
||||
execution_method: WasmExecutionMethod,
|
||||
ext: &mut E,
|
||||
) -> Result<Vec<u8>, Error> {
|
||||
let executor = crate::WasmExecutor::<HostFunctions>::builder()
|
||||
.with_execution_method(execution_method)
|
||||
.build();
|
||||
|
||||
executor.uncached_call(
|
||||
RuntimeBlob::uncompress_if_needed(wasm_binary_unwrap()).unwrap(),
|
||||
ext,
|
||||
true,
|
||||
function,
|
||||
call_data,
|
||||
)
|
||||
}
|
||||
|
||||
test_wasm_execution!(returning_should_work);
|
||||
fn returning_should_work(wasm_method: WasmExecutionMethod) {
|
||||
let mut ext = TestExternalities::default();
|
||||
let mut ext = ext.ext();
|
||||
|
||||
let output = call_in_wasm("test_empty_return", &[], wasm_method, &mut ext).unwrap();
|
||||
assert_eq!(output, vec![0u8; 0]);
|
||||
}
|
||||
|
||||
test_wasm_execution!(call_not_existing_function);
|
||||
fn call_not_existing_function(wasm_method: WasmExecutionMethod) {
|
||||
let mut ext = TestExternalities::default();
|
||||
let mut ext = ext.ext();
|
||||
|
||||
match call_in_wasm("test_calling_missing_external", &[], wasm_method, &mut ext).unwrap_err() {
|
||||
Error::AbortedDueToTrap(error) => {
|
||||
let expected = match wasm_method {
|
||||
WasmExecutionMethod::Compiled { .. } =>
|
||||
"call to a missing function env:missing_external",
|
||||
};
|
||||
assert_eq!(error.message, expected);
|
||||
},
|
||||
error => panic!("unexpected error: {:?}", error),
|
||||
}
|
||||
}
|
||||
|
||||
test_wasm_execution!(call_yet_another_not_existing_function);
|
||||
fn call_yet_another_not_existing_function(wasm_method: WasmExecutionMethod) {
|
||||
let mut ext = TestExternalities::default();
|
||||
let mut ext = ext.ext();
|
||||
|
||||
match call_in_wasm("test_calling_yet_another_missing_external", &[], wasm_method, &mut ext)
|
||||
.unwrap_err()
|
||||
{
|
||||
Error::AbortedDueToTrap(error) => {
|
||||
let expected = match wasm_method {
|
||||
WasmExecutionMethod::Compiled { .. } =>
|
||||
"call to a missing function env:yet_another_missing_external",
|
||||
};
|
||||
assert_eq!(error.message, expected);
|
||||
},
|
||||
error => panic!("unexpected error: {:?}", error),
|
||||
}
|
||||
}
|
||||
|
||||
test_wasm_execution!(panicking_should_work);
|
||||
fn panicking_should_work(wasm_method: WasmExecutionMethod) {
|
||||
let mut ext = TestExternalities::default();
|
||||
let mut ext = ext.ext();
|
||||
|
||||
let output = call_in_wasm("test_panic", &[], wasm_method, &mut ext);
|
||||
assert!(output.is_err());
|
||||
|
||||
let output = call_in_wasm("test_conditional_panic", &[0], wasm_method, &mut ext);
|
||||
assert_eq!(Decode::decode(&mut &output.unwrap()[..]), Ok(Vec::<u8>::new()));
|
||||
|
||||
let output = call_in_wasm("test_conditional_panic", &vec![2].encode(), wasm_method, &mut ext);
|
||||
assert!(output.is_err());
|
||||
}
|
||||
|
||||
test_wasm_execution!(storage_should_work);
|
||||
fn storage_should_work(wasm_method: WasmExecutionMethod) {
|
||||
let mut ext = TestExternalities::default();
|
||||
// Test value must be bigger than 32 bytes
|
||||
// to test the trie versioning.
|
||||
let value = vec![7u8; 60];
|
||||
|
||||
{
|
||||
let mut ext = ext.ext();
|
||||
ext.set_storage(b"foo".to_vec(), b"bar".to_vec());
|
||||
|
||||
let output = call_in_wasm("test_data_in", &value.encode(), wasm_method, &mut ext).unwrap();
|
||||
|
||||
assert_eq!(output, b"all ok!".to_vec().encode());
|
||||
}
|
||||
|
||||
let mut expected = TestExternalities::new(pezsp_core::storage::Storage {
|
||||
top: map![
|
||||
b"input".to_vec() => value,
|
||||
b"foo".to_vec() => b"bar".to_vec(),
|
||||
b"baz".to_vec() => b"bar".to_vec()
|
||||
],
|
||||
children_default: map![],
|
||||
});
|
||||
assert!(ext.eq(&mut expected));
|
||||
}
|
||||
|
||||
test_wasm_execution!(clear_prefix_should_work);
|
||||
fn clear_prefix_should_work(wasm_method: WasmExecutionMethod) {
|
||||
let mut ext = TestExternalities::default();
|
||||
{
|
||||
let mut ext = ext.ext();
|
||||
ext.set_storage(b"aaa".to_vec(), b"1".to_vec());
|
||||
ext.set_storage(b"aab".to_vec(), b"2".to_vec());
|
||||
ext.set_storage(b"aba".to_vec(), b"3".to_vec());
|
||||
ext.set_storage(b"abb".to_vec(), b"4".to_vec());
|
||||
ext.set_storage(b"bbb".to_vec(), b"5".to_vec());
|
||||
|
||||
// This will clear all entries which prefix is "ab".
|
||||
let output =
|
||||
call_in_wasm("test_clear_prefix", &b"ab".to_vec().encode(), wasm_method, &mut ext)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(output, b"all ok!".to_vec().encode());
|
||||
}
|
||||
|
||||
let mut expected = TestExternalities::new(pezsp_core::storage::Storage {
|
||||
top: map![
|
||||
b"aaa".to_vec() => b"1".to_vec(),
|
||||
b"aab".to_vec() => b"2".to_vec(),
|
||||
b"bbb".to_vec() => b"5".to_vec()
|
||||
],
|
||||
children_default: map![],
|
||||
});
|
||||
assert!(expected.eq(&mut ext));
|
||||
}
|
||||
|
||||
test_wasm_execution!(blake2_256_should_work);
|
||||
fn blake2_256_should_work(wasm_method: WasmExecutionMethod) {
|
||||
let mut ext = TestExternalities::default();
|
||||
let mut ext = ext.ext();
|
||||
assert_eq!(
|
||||
call_in_wasm("test_blake2_256", &[0], wasm_method, &mut ext,).unwrap(),
|
||||
blake2_256(b"").to_vec().encode(),
|
||||
);
|
||||
assert_eq!(
|
||||
call_in_wasm("test_blake2_256", &b"Hello world!".to_vec().encode(), wasm_method, &mut ext,)
|
||||
.unwrap(),
|
||||
blake2_256(b"Hello world!").to_vec().encode(),
|
||||
);
|
||||
}
|
||||
|
||||
test_wasm_execution!(blake2_128_should_work);
|
||||
fn blake2_128_should_work(wasm_method: WasmExecutionMethod) {
|
||||
let mut ext = TestExternalities::default();
|
||||
let mut ext = ext.ext();
|
||||
assert_eq!(
|
||||
call_in_wasm("test_blake2_128", &[0], wasm_method, &mut ext,).unwrap(),
|
||||
blake2_128(b"").to_vec().encode(),
|
||||
);
|
||||
assert_eq!(
|
||||
call_in_wasm("test_blake2_128", &b"Hello world!".to_vec().encode(), wasm_method, &mut ext,)
|
||||
.unwrap(),
|
||||
blake2_128(b"Hello world!").to_vec().encode(),
|
||||
);
|
||||
}
|
||||
|
||||
test_wasm_execution!(sha2_256_should_work);
|
||||
fn sha2_256_should_work(wasm_method: WasmExecutionMethod) {
|
||||
let mut ext = TestExternalities::default();
|
||||
let mut ext = ext.ext();
|
||||
assert_eq!(
|
||||
call_in_wasm("test_sha2_256", &[0], wasm_method, &mut ext,).unwrap(),
|
||||
sha2_256(b"").to_vec().encode(),
|
||||
);
|
||||
assert_eq!(
|
||||
call_in_wasm("test_sha2_256", &b"Hello world!".to_vec().encode(), wasm_method, &mut ext,)
|
||||
.unwrap(),
|
||||
sha2_256(b"Hello world!").to_vec().encode(),
|
||||
);
|
||||
}
|
||||
|
||||
test_wasm_execution!(twox_256_should_work);
|
||||
fn twox_256_should_work(wasm_method: WasmExecutionMethod) {
|
||||
let mut ext = TestExternalities::default();
|
||||
let mut ext = ext.ext();
|
||||
assert_eq!(
|
||||
call_in_wasm("test_twox_256", &[0], wasm_method, &mut ext,).unwrap(),
|
||||
twox_256(b"").to_vec().encode()
|
||||
);
|
||||
assert_eq!(
|
||||
call_in_wasm("test_twox_256", &b"Hello world!".to_vec().encode(), wasm_method, &mut ext,)
|
||||
.unwrap(),
|
||||
twox_256(b"Hello world!").to_vec().encode()
|
||||
);
|
||||
}
|
||||
|
||||
test_wasm_execution!(twox_128_should_work);
|
||||
fn twox_128_should_work(wasm_method: WasmExecutionMethod) {
|
||||
let mut ext = TestExternalities::default();
|
||||
let mut ext = ext.ext();
|
||||
assert_eq!(
|
||||
call_in_wasm("test_twox_128", &[0], wasm_method, &mut ext,).unwrap(),
|
||||
twox_128(b"").to_vec().encode()
|
||||
);
|
||||
assert_eq!(
|
||||
call_in_wasm("test_twox_128", &b"Hello world!".to_vec().encode(), wasm_method, &mut ext,)
|
||||
.unwrap(),
|
||||
twox_128(b"Hello world!").to_vec().encode()
|
||||
);
|
||||
}
|
||||
|
||||
test_wasm_execution!(ed25519_verify_should_work);
|
||||
fn ed25519_verify_should_work(wasm_method: WasmExecutionMethod) {
|
||||
let mut ext = TestExternalities::default();
|
||||
let mut ext = ext.ext();
|
||||
let key = ed25519::Pair::from_seed(&blake2_256(b"test"));
|
||||
let sig = key.sign(b"all ok!");
|
||||
let mut calldata = vec![];
|
||||
calldata.extend_from_slice(key.public().as_ref());
|
||||
calldata.extend_from_slice(sig.as_ref());
|
||||
|
||||
assert_eq!(
|
||||
call_in_wasm("test_ed25519_verify", &calldata.encode(), wasm_method, &mut ext,).unwrap(),
|
||||
true.encode(),
|
||||
);
|
||||
|
||||
let other_sig = key.sign(b"all is not ok!");
|
||||
let mut calldata = vec![];
|
||||
calldata.extend_from_slice(key.public().as_ref());
|
||||
calldata.extend_from_slice(other_sig.as_ref());
|
||||
|
||||
assert_eq!(
|
||||
call_in_wasm("test_ed25519_verify", &calldata.encode(), wasm_method, &mut ext,).unwrap(),
|
||||
false.encode(),
|
||||
);
|
||||
}
|
||||
|
||||
test_wasm_execution!(sr25519_verify_should_work);
|
||||
fn sr25519_verify_should_work(wasm_method: WasmExecutionMethod) {
|
||||
let mut ext = TestExternalities::default();
|
||||
let mut ext = ext.ext();
|
||||
let key = sr25519::Pair::from_seed(&blake2_256(b"test"));
|
||||
let sig = key.sign(b"all ok!");
|
||||
let mut calldata = vec![];
|
||||
calldata.extend_from_slice(key.public().as_ref());
|
||||
calldata.extend_from_slice(sig.as_ref());
|
||||
|
||||
assert_eq!(
|
||||
call_in_wasm("test_sr25519_verify", &calldata.encode(), wasm_method, &mut ext,).unwrap(),
|
||||
true.encode(),
|
||||
);
|
||||
|
||||
let other_sig = key.sign(b"all is not ok!");
|
||||
let mut calldata = vec![];
|
||||
calldata.extend_from_slice(key.public().as_ref());
|
||||
calldata.extend_from_slice(other_sig.as_ref());
|
||||
|
||||
assert_eq!(
|
||||
call_in_wasm("test_sr25519_verify", &calldata.encode(), wasm_method, &mut ext,).unwrap(),
|
||||
false.encode(),
|
||||
);
|
||||
}
|
||||
|
||||
test_wasm_execution!(ordered_trie_root_should_work);
|
||||
fn ordered_trie_root_should_work(wasm_method: WasmExecutionMethod) {
|
||||
let mut ext = TestExternalities::default();
|
||||
let trie_input = vec![b"zero".to_vec(), b"one".to_vec(), b"two".to_vec()];
|
||||
assert_eq!(
|
||||
call_in_wasm("test_ordered_trie_root", &[0], wasm_method, &mut ext.ext(),).unwrap(),
|
||||
Layout::<BlakeTwo256>::ordered_trie_root(trie_input.iter()).as_bytes().encode(),
|
||||
);
|
||||
}
|
||||
|
||||
test_wasm_execution!(offchain_index);
|
||||
fn offchain_index(wasm_method: WasmExecutionMethod) {
|
||||
let mut ext = TestExternalities::default();
|
||||
let (offchain, _state) = testing::TestOffchainExt::new();
|
||||
ext.register_extension(OffchainWorkerExt::new(offchain));
|
||||
call_in_wasm("test_offchain_index_set", &[0], wasm_method, &mut ext.ext()).unwrap();
|
||||
|
||||
use pezsp_core::offchain::OffchainOverlayedChange;
|
||||
let data = ext
|
||||
.overlayed_changes()
|
||||
.clone()
|
||||
.offchain_drain_committed()
|
||||
.find(|(k, _v)| k == &(pezsp_core::offchain::STORAGE_PREFIX.to_vec(), b"k".to_vec()));
|
||||
assert_eq!(data.map(|data| data.1), Some(OffchainOverlayedChange::SetValue(b"v".to_vec())));
|
||||
}
|
||||
|
||||
test_wasm_execution!(offchain_local_storage_should_work);
|
||||
fn offchain_local_storage_should_work(wasm_method: WasmExecutionMethod) {
|
||||
let mut ext = TestExternalities::default();
|
||||
let (offchain, state) = testing::TestOffchainExt::new();
|
||||
ext.register_extension(OffchainDbExt::new(offchain.clone()));
|
||||
ext.register_extension(OffchainWorkerExt::new(offchain));
|
||||
assert_eq!(
|
||||
call_in_wasm("test_offchain_local_storage", &[0], wasm_method, &mut ext.ext(),).unwrap(),
|
||||
true.encode(),
|
||||
);
|
||||
assert_eq!(state.read().persistent_storage.get(b"test"), Some(vec![]));
|
||||
}
|
||||
|
||||
test_wasm_execution!(offchain_http_should_work);
|
||||
fn offchain_http_should_work(wasm_method: WasmExecutionMethod) {
|
||||
let mut ext = TestExternalities::default();
|
||||
let (offchain, state) = testing::TestOffchainExt::new();
|
||||
ext.register_extension(OffchainWorkerExt::new(offchain));
|
||||
state.write().expect_request(testing::PendingRequest {
|
||||
method: "POST".into(),
|
||||
uri: "http://localhost:12345".into(),
|
||||
body: vec![1, 2, 3, 4],
|
||||
headers: vec![("X-Auth".to_owned(), "test".to_owned())],
|
||||
sent: true,
|
||||
response: Some(vec![1, 2, 3]),
|
||||
response_headers: vec![("X-Auth".to_owned(), "hello".to_owned())],
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
assert_eq!(
|
||||
call_in_wasm("test_offchain_http", &[0], wasm_method, &mut ext.ext(),).unwrap(),
|
||||
true.encode(),
|
||||
);
|
||||
}
|
||||
|
||||
test_wasm_execution!(should_trap_when_heap_exhausted);
|
||||
fn should_trap_when_heap_exhausted(wasm_method: WasmExecutionMethod) {
|
||||
let mut ext = TestExternalities::default();
|
||||
|
||||
let executor = crate::WasmExecutor::<HostFunctions>::builder()
|
||||
.with_execution_method(wasm_method)
|
||||
// `17` is the initial number of pages compiled into the binary.
|
||||
.with_onchain_heap_alloc_strategy(HeapAllocStrategy::Static { extra_pages: 17 })
|
||||
.build();
|
||||
|
||||
let err = executor
|
||||
.uncached_call(
|
||||
RuntimeBlob::uncompress_if_needed(wasm_binary_unwrap()).unwrap(),
|
||||
&mut ext.ext(),
|
||||
true,
|
||||
"test_allocate_vec",
|
||||
&16777216_u32.encode(),
|
||||
)
|
||||
.unwrap_err();
|
||||
|
||||
match err {
|
||||
Error::AbortedDueToTrap(error)
|
||||
if matches!(wasm_method, WasmExecutionMethod::Compiled { .. }) =>
|
||||
{
|
||||
assert_eq!(
|
||||
error.message,
|
||||
r#"host code panicked while being called by the runtime: Failed to allocate memory: "Allocator ran out of space""#
|
||||
);
|
||||
},
|
||||
error => panic!("unexpected error: {:?}", error),
|
||||
}
|
||||
}
|
||||
|
||||
fn mk_test_runtime(
|
||||
wasm_method: WasmExecutionMethod,
|
||||
pages: HeapAllocStrategy,
|
||||
) -> Box<dyn WasmModule> {
|
||||
let blob = RuntimeBlob::uncompress_if_needed(wasm_binary_unwrap())
|
||||
.expect("failed to create a runtime blob out of test runtime");
|
||||
|
||||
crate::wasm_runtime::create_wasm_runtime_with_code::<HostFunctions>(
|
||||
wasm_method,
|
||||
pages,
|
||||
blob,
|
||||
true,
|
||||
None,
|
||||
)
|
||||
.expect("failed to instantiate wasm runtime")
|
||||
}
|
||||
|
||||
test_wasm_execution!(returns_mutable_static);
|
||||
fn returns_mutable_static(wasm_method: WasmExecutionMethod) {
|
||||
let runtime =
|
||||
mk_test_runtime(wasm_method, HeapAllocStrategy::Dynamic { maximum_pages: Some(1024) });
|
||||
|
||||
let mut instance = runtime.new_instance().unwrap();
|
||||
let res = instance.call_export("returns_mutable_static", &[0]).unwrap();
|
||||
assert_eq!(33, u64::decode(&mut &res[..]).unwrap());
|
||||
|
||||
// We expect that every invocation will need to return the initial
|
||||
// value plus one. If the value increases more than that then it is
|
||||
// a sign that the wasm runtime preserves the memory content.
|
||||
let res = instance.call_export("returns_mutable_static", &[0]).unwrap();
|
||||
assert_eq!(33, u64::decode(&mut &res[..]).unwrap());
|
||||
}
|
||||
|
||||
test_wasm_execution!(returns_mutable_static_bss);
|
||||
fn returns_mutable_static_bss(wasm_method: WasmExecutionMethod) {
|
||||
let runtime =
|
||||
mk_test_runtime(wasm_method, HeapAllocStrategy::Dynamic { maximum_pages: Some(1024) });
|
||||
|
||||
let mut instance = runtime.new_instance().unwrap();
|
||||
let res = instance.call_export("returns_mutable_static_bss", &[0]).unwrap();
|
||||
assert_eq!(1, u64::decode(&mut &res[..]).unwrap());
|
||||
|
||||
// We expect that every invocation will need to return the initial
|
||||
// value plus one. If the value increases more than that then it is
|
||||
// a sign that the wasm runtime preserves the memory content.
|
||||
let res = instance.call_export("returns_mutable_static_bss", &[0]).unwrap();
|
||||
assert_eq!(1, u64::decode(&mut &res[..]).unwrap());
|
||||
}
|
||||
|
||||
// If we didn't restore the wasm instance properly, on a trap the stack pointer would not be
|
||||
// returned to its initial value and thus the stack space is going to be leaked.
|
||||
//
|
||||
// See https://github.com/pezkuwichain/kurdistan-sdk/issues/23 for details
|
||||
test_wasm_execution!(restoration_of_globals);
|
||||
fn restoration_of_globals(wasm_method: WasmExecutionMethod) {
|
||||
// Allocate 32 pages (of 65536 bytes) which gives the runtime 2048KB of heap to operate on
|
||||
// (plus some additional space unused from the initial pages requested by the wasm runtime
|
||||
// module).
|
||||
//
|
||||
// The fixture performs 2 allocations of 768KB and this theoretically gives 1536KB, however, due
|
||||
// to our allocator algorithm there are inefficiencies.
|
||||
const REQUIRED_MEMORY_PAGES: u32 = 32;
|
||||
|
||||
let runtime = mk_test_runtime(
|
||||
wasm_method,
|
||||
HeapAllocStrategy::Static { extra_pages: REQUIRED_MEMORY_PAGES },
|
||||
);
|
||||
let mut instance = runtime.new_instance().unwrap();
|
||||
|
||||
// On the first invocation we allocate approx. 768KB (75%) of stack and then trap.
|
||||
let res = instance.call_export("allocates_huge_stack_array", &true.encode());
|
||||
assert!(res.is_err());
|
||||
|
||||
// On the second invocation we allocate yet another 768KB (75%) of stack
|
||||
let res = instance.call_export("allocates_huge_stack_array", &false.encode());
|
||||
assert!(res.is_ok());
|
||||
}
|
||||
|
||||
test_wasm_execution!(parallel_execution);
|
||||
fn parallel_execution(wasm_method: WasmExecutionMethod) {
|
||||
let executor = Arc::new(
|
||||
crate::WasmExecutor::<HostFunctions>::builder()
|
||||
.with_execution_method(wasm_method)
|
||||
.build(),
|
||||
);
|
||||
let threads: Vec<_> = (0..8)
|
||||
.map(|_| {
|
||||
let executor = executor.clone();
|
||||
std::thread::spawn(move || {
|
||||
let mut ext = TestExternalities::default();
|
||||
let mut ext = ext.ext();
|
||||
assert_eq!(
|
||||
executor
|
||||
.uncached_call(
|
||||
RuntimeBlob::uncompress_if_needed(wasm_binary_unwrap()).unwrap(),
|
||||
&mut ext,
|
||||
true,
|
||||
"test_twox_128",
|
||||
&[0],
|
||||
)
|
||||
.unwrap(),
|
||||
array_bytes::hex2bytes_unchecked("99e9d85137db46ef4bbea33613baafd5").encode()
|
||||
);
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
|
||||
for t in threads.into_iter() {
|
||||
t.join().unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
test_wasm_execution!(wasm_tracing_should_work);
|
||||
fn wasm_tracing_should_work(wasm_method: WasmExecutionMethod) {
|
||||
use pezsc_tracing::{SpanDatum, TraceEvent};
|
||||
use std::sync::Mutex;
|
||||
|
||||
struct TestTraceHandler(Arc<Mutex<Vec<SpanDatum>>>);
|
||||
|
||||
impl pezsc_tracing::TraceHandler for TestTraceHandler {
|
||||
fn handle_span(&self, sd: &SpanDatum) {
|
||||
self.0.lock().unwrap().push(sd.clone());
|
||||
}
|
||||
|
||||
fn handle_event(&self, _event: &TraceEvent) {}
|
||||
}
|
||||
|
||||
let traces = Arc::new(Mutex::new(Vec::new()));
|
||||
let handler = TestTraceHandler(traces.clone());
|
||||
|
||||
// Create subscriber with wasm_tracing disabled
|
||||
let test_subscriber = tracing_subscriber::fmt()
|
||||
.finish()
|
||||
.with(pezsc_tracing::ProfilingLayer::new_with_handler(Box::new(handler), "default"));
|
||||
|
||||
let _guard = tracing::subscriber::set_default(test_subscriber);
|
||||
|
||||
let mut ext = TestExternalities::default();
|
||||
let mut ext = ext.ext();
|
||||
|
||||
let span_id =
|
||||
call_in_wasm("test_enter_span", Default::default(), wasm_method, &mut ext).unwrap();
|
||||
|
||||
let span_id = u64::decode(&mut &span_id[..]).unwrap();
|
||||
|
||||
assert!(span_id > 0);
|
||||
|
||||
call_in_wasm("test_exit_span", &span_id.encode(), wasm_method, &mut ext).unwrap();
|
||||
|
||||
// Check there is only the single trace
|
||||
let len = traces.lock().unwrap().len();
|
||||
assert_eq!(len, 1);
|
||||
|
||||
let span_datum = traces.lock().unwrap().pop().unwrap();
|
||||
let values = span_datum.values;
|
||||
assert_eq!(span_datum.target, "default");
|
||||
assert_eq!(span_datum.name, "");
|
||||
assert_eq!(values.bool_values.get("wasm").unwrap(), &true);
|
||||
|
||||
call_in_wasm("test_nested_spans", Default::default(), wasm_method, &mut ext).unwrap();
|
||||
let len = traces.lock().unwrap().len();
|
||||
assert_eq!(len, 2);
|
||||
}
|
||||
|
||||
test_wasm_execution!(allocate_two_gigabyte);
|
||||
fn allocate_two_gigabyte(wasm_method: WasmExecutionMethod) {
|
||||
let runtime = mk_test_runtime(wasm_method, HeapAllocStrategy::Dynamic { maximum_pages: None });
|
||||
|
||||
let mut instance = runtime.new_instance().unwrap();
|
||||
let res = instance.call_export("allocate_two_gigabyte", &[0]).unwrap();
|
||||
assert_eq!(10 * 1024 * 1024 * 205, u32::decode(&mut &res[..]).unwrap());
|
||||
}
|
||||
|
||||
test_wasm_execution!(memory_is_cleared_between_invocations);
|
||||
fn memory_is_cleared_between_invocations(wasm_method: WasmExecutionMethod) {
|
||||
// This is based on the code generated by compiling a runtime *without*
|
||||
// the `-C link-arg=--import-memory` using the following code and then
|
||||
// disassembling the resulting blob with `wasm-dis`:
|
||||
//
|
||||
// ```
|
||||
// #[no_mangle]
|
||||
// #[cfg(not(feature = "std"))]
|
||||
// pub fn returns_no_bss_mutable_static(_: *mut u8, _: usize) -> u64 {
|
||||
// static mut COUNTER: usize = 0;
|
||||
// let output = unsafe {
|
||||
// COUNTER += 1;
|
||||
// COUNTER as u64
|
||||
// };
|
||||
// pezsp_core::to_bizinikiwi_wasm_fn_return_value(&output)
|
||||
// }
|
||||
// ```
|
||||
//
|
||||
// This results in the BSS section to *not* be emitted, hence the executor has no way
|
||||
// of knowing about the `static` variable's existence, so this test will fail if the linear
|
||||
// memory is not properly cleared between invocations.
|
||||
let binary = wat::parse_str(r#"
|
||||
(module
|
||||
(type $i32_=>_i32 (func (param i32) (result i32)))
|
||||
(type $i32_i32_=>_i64 (func (param i32 i32) (result i64)))
|
||||
(import "env" "ext_allocator_malloc_version_1" (func $ext_allocator_malloc_version_1 (param i32) (result i32)))
|
||||
(global $__stack_pointer (mut i32) (i32.const 1048576))
|
||||
(global $global$1 i32 (i32.const 1048580))
|
||||
(global $global$2 i32 (i32.const 1048592))
|
||||
(memory $0 17)
|
||||
(export "memory" (memory $0))
|
||||
(export "returns_no_bss_mutable_static" (func $returns_no_bss_mutable_static))
|
||||
(export "__data_end" (global $global$1))
|
||||
(export "__heap_base" (global $global$2))
|
||||
(func $returns_no_bss_mutable_static (param $0 i32) (param $1 i32) (result i64)
|
||||
(local $2 i32)
|
||||
(local $3 i32)
|
||||
(i32.store offset=1048576
|
||||
(i32.const 0)
|
||||
(local.tee $2
|
||||
(i32.add
|
||||
(i32.load offset=1048576 (i32.const 0))
|
||||
(i32.const 1)
|
||||
)
|
||||
)
|
||||
)
|
||||
(i64.store
|
||||
(local.tee $3
|
||||
(call $ext_allocator_malloc_version_1 (i32.const 8))
|
||||
)
|
||||
(i64.extend_i32_u (local.get $2))
|
||||
)
|
||||
(i64.or
|
||||
(i64.extend_i32_u (local.get $3))
|
||||
(i64.const 34359738368)
|
||||
)
|
||||
)
|
||||
)"#).unwrap();
|
||||
|
||||
let runtime = crate::wasm_runtime::create_wasm_runtime_with_code::<HostFunctions>(
|
||||
wasm_method,
|
||||
HeapAllocStrategy::Dynamic { maximum_pages: Some(1024) },
|
||||
RuntimeBlob::uncompress_if_needed(&binary[..]).unwrap(),
|
||||
true,
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let mut instance = runtime.new_instance().unwrap();
|
||||
let res = instance.call_export("returns_no_bss_mutable_static", &[0]).unwrap();
|
||||
assert_eq!(1, u64::decode(&mut &res[..]).unwrap());
|
||||
|
||||
let res = instance.call_export("returns_no_bss_mutable_static", &[0]).unwrap();
|
||||
assert_eq!(1, u64::decode(&mut &res[..]).unwrap());
|
||||
}
|
||||
|
||||
test_wasm_execution!(return_i8);
|
||||
fn return_i8(wasm_method: WasmExecutionMethod) {
|
||||
let mut ext = TestExternalities::default();
|
||||
let mut ext = ext.ext();
|
||||
|
||||
assert_eq!(
|
||||
call_in_wasm("test_return_i8", &[], wasm_method, &mut ext).unwrap(),
|
||||
(-66_i8).encode()
|
||||
);
|
||||
}
|
||||
|
||||
test_wasm_execution!(take_i8);
|
||||
fn take_i8(wasm_method: WasmExecutionMethod) {
|
||||
let mut ext = TestExternalities::default();
|
||||
let mut ext = ext.ext();
|
||||
|
||||
call_in_wasm("test_take_i8", &(-66_i8).encode(), wasm_method, &mut ext).unwrap();
|
||||
}
|
||||
|
||||
test_wasm_execution!(abort_on_panic);
|
||||
fn abort_on_panic(wasm_method: WasmExecutionMethod) {
|
||||
let mut ext = TestExternalities::default();
|
||||
let mut ext = ext.ext();
|
||||
|
||||
match call_in_wasm("test_abort_on_panic", &[], wasm_method, &mut ext).unwrap_err() {
|
||||
Error::AbortedDueToPanic(error) => assert_eq!(error.message, "test_abort_on_panic called"),
|
||||
error => panic!("unexpected error: {:?}", error),
|
||||
}
|
||||
}
|
||||
|
||||
test_wasm_execution!(unreachable_intrinsic);
|
||||
fn unreachable_intrinsic(wasm_method: WasmExecutionMethod) {
|
||||
let mut ext = TestExternalities::default();
|
||||
let mut ext = ext.ext();
|
||||
|
||||
match call_in_wasm("test_unreachable_intrinsic", &[], wasm_method, &mut ext).unwrap_err() {
|
||||
Error::AbortedDueToTrap(error) => {
|
||||
let expected = match wasm_method {
|
||||
WasmExecutionMethod::Compiled { .. } =>
|
||||
"wasm trap: wasm `unreachable` instruction executed",
|
||||
};
|
||||
assert_eq!(error.message, expected);
|
||||
},
|
||||
error => panic!("unexpected error: {:?}", error),
|
||||
}
|
||||
}
|
||||
|
||||
test_wasm_execution!(return_value);
|
||||
fn return_value(wasm_method: WasmExecutionMethod) {
|
||||
let mut ext = TestExternalities::default();
|
||||
let mut ext = ext.ext();
|
||||
|
||||
assert_eq!(
|
||||
call_in_wasm("test_return_value", &[], wasm_method, &mut ext).unwrap(),
|
||||
(1234u64).encode()
|
||||
);
|
||||
}
|
||||
|
||||
test_wasm_execution!(return_huge_len);
|
||||
fn return_huge_len(wasm_method: WasmExecutionMethod) {
|
||||
let mut ext = TestExternalities::default();
|
||||
let mut ext = ext.ext();
|
||||
|
||||
match call_in_wasm("test_return_huge_len", &[], wasm_method, &mut ext).unwrap_err() {
|
||||
Error::OutputExceedsBounds => {
|
||||
assert_matches!(wasm_method, WasmExecutionMethod::Compiled { .. });
|
||||
},
|
||||
error => panic!("unexpected error: {:?}", error),
|
||||
}
|
||||
}
|
||||
|
||||
test_wasm_execution!(return_max_memory_offset);
|
||||
fn return_max_memory_offset(wasm_method: WasmExecutionMethod) {
|
||||
let mut ext = TestExternalities::default();
|
||||
let mut ext = ext.ext();
|
||||
|
||||
assert_eq!(
|
||||
call_in_wasm("test_return_max_memory_offset", &[], wasm_method, &mut ext).unwrap(),
|
||||
(u8::MAX).encode()
|
||||
);
|
||||
}
|
||||
|
||||
test_wasm_execution!(return_max_memory_offset_plus_one);
|
||||
fn return_max_memory_offset_plus_one(wasm_method: WasmExecutionMethod) {
|
||||
let mut ext = TestExternalities::default();
|
||||
let mut ext = ext.ext();
|
||||
|
||||
match call_in_wasm("test_return_max_memory_offset_plus_one", &[], wasm_method, &mut ext)
|
||||
.unwrap_err()
|
||||
{
|
||||
Error::OutputExceedsBounds => {
|
||||
assert_matches!(wasm_method, WasmExecutionMethod::Compiled { .. });
|
||||
},
|
||||
error => panic!("unexpected error: {:?}", error),
|
||||
}
|
||||
}
|
||||
|
||||
test_wasm_execution!(return_overflow);
|
||||
fn return_overflow(wasm_method: WasmExecutionMethod) {
|
||||
let mut ext = TestExternalities::default();
|
||||
let mut ext = ext.ext();
|
||||
|
||||
match call_in_wasm("test_return_overflow", &[], wasm_method, &mut ext).unwrap_err() {
|
||||
Error::OutputExceedsBounds => {
|
||||
assert_matches!(wasm_method, WasmExecutionMethod::Compiled { .. });
|
||||
},
|
||||
error => panic!("unexpected error: {:?}", error),
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// 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/>.
|
||||
|
||||
//! A crate that provides means of executing/dispatching calls into the runtime.
|
||||
//!
|
||||
//! There are a few responsibilities of this crate at the moment:
|
||||
//!
|
||||
//! - It provides an implementation of a common entrypoint for calling into the runtime, both
|
||||
//! wasm and compiled.
|
||||
//! - It defines the environment for the wasm execution, namely the host functions that are to be
|
||||
//! provided into the wasm runtime module.
|
||||
//! - It also provides the required infrastructure for executing the current wasm runtime (specified
|
||||
//! by the current value of `:code` in the provided externalities), i.e. interfacing with
|
||||
//! wasm engine used, instance cache.
|
||||
|
||||
#![warn(missing_docs)]
|
||||
|
||||
#[macro_use]
|
||||
mod executor;
|
||||
#[cfg(test)]
|
||||
mod integration_tests;
|
||||
mod wasm_runtime;
|
||||
|
||||
pub use codec::Codec;
|
||||
#[allow(deprecated)]
|
||||
pub use executor::NativeElseWasmExecutor;
|
||||
pub use executor::{with_externalities_safe, NativeExecutionDispatch, WasmExecutor};
|
||||
#[doc(hidden)]
|
||||
pub use pezsp_core::traits::Externalities;
|
||||
pub use pezsp_version::{NativeVersion, RuntimeVersion};
|
||||
#[doc(hidden)]
|
||||
pub use pezsp_wasm_interface;
|
||||
pub use pezsp_wasm_interface::HostFunctions;
|
||||
pub use wasm_runtime::{read_embedded_version, WasmExecutionMethod};
|
||||
|
||||
pub use pezsc_executor_common::{
|
||||
error,
|
||||
wasm_runtime::{HeapAllocStrategy, DEFAULT_HEAP_ALLOC_PAGES, DEFAULT_HEAP_ALLOC_STRATEGY},
|
||||
};
|
||||
pub use pezsc_executor_wasmtime::InstantiationStrategy as WasmtimeInstantiationStrategy;
|
||||
|
||||
/// Extracts the runtime version of a given runtime code.
|
||||
pub trait RuntimeVersionOf {
|
||||
/// Extract [`RuntimeVersion`] of the given `runtime_code`.
|
||||
fn runtime_version(
|
||||
&self,
|
||||
ext: &mut dyn Externalities,
|
||||
runtime_code: &pezsp_core::traits::RuntimeCode,
|
||||
) -> error::Result<RuntimeVersion>;
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use pezsc_executor_common::runtime_blob::RuntimeBlob;
|
||||
use pezsc_runtime_test::wasm_binary_unwrap;
|
||||
use pezsp_io::TestExternalities;
|
||||
|
||||
#[test]
|
||||
fn call_in_interpreted_wasm_works() {
|
||||
let mut ext = TestExternalities::default();
|
||||
let mut ext = ext.ext();
|
||||
|
||||
let executor = WasmExecutor::<pezsp_io::BizinikiwiHostFunctions>::builder().build();
|
||||
let res = executor
|
||||
.uncached_call(
|
||||
RuntimeBlob::uncompress_if_needed(wasm_binary_unwrap()).unwrap(),
|
||||
&mut ext,
|
||||
true,
|
||||
"test_empty_return",
|
||||
&[],
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(res, vec![0u8; 0]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,564 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// 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/>.
|
||||
|
||||
//! Traits and accessor functions for calling into the Bizinikiwi Wasm runtime.
|
||||
//!
|
||||
//! The primary means of accessing the runtimes is through a cache which saves the reusable
|
||||
//! components of the runtime that are expensive to initialize.
|
||||
|
||||
use crate::error::{Error, WasmError};
|
||||
|
||||
use codec::Decode;
|
||||
use parking_lot::Mutex;
|
||||
use pezsc_executor_common::{
|
||||
runtime_blob::RuntimeBlob,
|
||||
wasm_runtime::{HeapAllocStrategy, WasmInstance, WasmModule},
|
||||
};
|
||||
use schnellru::{ByLength, LruMap};
|
||||
use pezsp_core::traits::{Externalities, FetchRuntimeCode, RuntimeCode};
|
||||
use pezsp_version::RuntimeVersion;
|
||||
use pezsp_wasm_interface::HostFunctions;
|
||||
|
||||
use std::{
|
||||
panic::AssertUnwindSafe,
|
||||
path::{Path, PathBuf},
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
/// Specification of different methods of executing the runtime Wasm code.
|
||||
#[derive(Debug, PartialEq, Eq, Hash, Copy, Clone)]
|
||||
pub enum WasmExecutionMethod {
|
||||
/// Uses the Wasmtime compiled runtime.
|
||||
Compiled {
|
||||
/// The instantiation strategy to use.
|
||||
instantiation_strategy: pezsc_executor_wasmtime::InstantiationStrategy,
|
||||
},
|
||||
}
|
||||
|
||||
impl Default for WasmExecutionMethod {
|
||||
fn default() -> Self {
|
||||
Self::Compiled {
|
||||
instantiation_strategy: pezsc_executor_wasmtime::InstantiationStrategy::PoolingCopyOnWrite,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Hash, Clone)]
|
||||
struct VersionedRuntimeId {
|
||||
/// Runtime code hash.
|
||||
code_hash: Vec<u8>,
|
||||
/// Wasm runtime type.
|
||||
wasm_method: WasmExecutionMethod,
|
||||
/// The heap allocation strategy this runtime was created with.
|
||||
heap_alloc_strategy: HeapAllocStrategy,
|
||||
}
|
||||
|
||||
/// A Wasm runtime object along with its cached runtime version.
|
||||
struct VersionedRuntime {
|
||||
/// Shared runtime that can spawn instances.
|
||||
module: Box<dyn WasmModule>,
|
||||
/// Runtime version according to `Core_version` if any.
|
||||
version: Option<RuntimeVersion>,
|
||||
|
||||
// TODO: Remove this once the legacy instance reuse instantiation strategy
|
||||
// for `wasmtime` is gone, as this only makes sense with that particular strategy.
|
||||
/// Cached instance pool.
|
||||
instances: Vec<Mutex<Option<Box<dyn WasmInstance>>>>,
|
||||
}
|
||||
|
||||
impl VersionedRuntime {
|
||||
/// Run the given closure `f` with an instance of this runtime.
|
||||
fn with_instance<R, F>(&self, ext: &mut dyn Externalities, f: F) -> Result<R, Error>
|
||||
where
|
||||
F: FnOnce(
|
||||
&dyn WasmModule,
|
||||
&mut dyn WasmInstance,
|
||||
Option<&RuntimeVersion>,
|
||||
&mut dyn Externalities,
|
||||
) -> Result<R, Error>,
|
||||
{
|
||||
// Find a free instance
|
||||
let instance = self
|
||||
.instances
|
||||
.iter()
|
||||
.enumerate()
|
||||
.find_map(|(index, i)| i.try_lock().map(|i| (index, i)));
|
||||
|
||||
match instance {
|
||||
Some((index, mut locked)) => {
|
||||
let (mut instance, new_inst) = locked
|
||||
.take()
|
||||
.map(|r| Ok((r, false)))
|
||||
.unwrap_or_else(|| self.module.new_instance().map(|i| (i, true)))?;
|
||||
|
||||
let result = f(&*self.module, &mut *instance, self.version.as_ref(), ext);
|
||||
if let Err(e) = &result {
|
||||
if new_inst {
|
||||
tracing::warn!(
|
||||
target: "wasm-runtime",
|
||||
error = %e,
|
||||
"Fresh runtime instance failed",
|
||||
)
|
||||
} else {
|
||||
tracing::warn!(
|
||||
target: "wasm-runtime",
|
||||
error = %e,
|
||||
"Evicting failed runtime instance",
|
||||
);
|
||||
}
|
||||
} else {
|
||||
*locked = Some(instance);
|
||||
|
||||
if new_inst {
|
||||
tracing::debug!(
|
||||
target: "wasm-runtime",
|
||||
"Allocated WASM instance {}/{}",
|
||||
index + 1,
|
||||
self.instances.len(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
result
|
||||
},
|
||||
None => {
|
||||
tracing::warn!(target: "wasm-runtime", "Ran out of free WASM instances");
|
||||
|
||||
// Allocate a new instance
|
||||
let mut instance = self.module.new_instance()?;
|
||||
|
||||
f(&*self.module, &mut *instance, self.version.as_ref(), ext)
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Cache for the runtimes.
|
||||
///
|
||||
/// When an instance is requested for the first time it is added to this cache. Metadata is kept
|
||||
/// with the instance so that it can be efficiently reinitialized.
|
||||
///
|
||||
/// When using the Wasmi interpreter execution method, the metadata includes the initial memory and
|
||||
/// values of mutable globals. Follow-up requests to fetch a runtime return this one instance with
|
||||
/// the memory reset to the initial memory. So, one runtime instance is reused for every fetch
|
||||
/// request.
|
||||
///
|
||||
/// The size of cache is configurable via the cli option `--runtime-cache-size`.
|
||||
pub struct RuntimeCache {
|
||||
/// A cache of runtimes along with metadata.
|
||||
///
|
||||
/// Runtimes sorted by recent usage. The most recently used is at the front.
|
||||
runtimes: Mutex<LruMap<VersionedRuntimeId, Arc<VersionedRuntime>>>,
|
||||
/// The size of the instances cache for each runtime.
|
||||
max_runtime_instances: usize,
|
||||
cache_path: Option<PathBuf>,
|
||||
}
|
||||
|
||||
impl RuntimeCache {
|
||||
/// Creates a new instance of a runtimes cache.
|
||||
///
|
||||
/// `max_runtime_instances` specifies the number of instances per runtime preserved in an
|
||||
/// in-memory cache.
|
||||
///
|
||||
/// `cache_path` allows to specify an optional directory where the executor can store files
|
||||
/// for caching.
|
||||
///
|
||||
/// `runtime_cache_size` specifies the number of different runtimes versions preserved in an
|
||||
/// in-memory cache, must always be at least 1.
|
||||
pub fn new(
|
||||
max_runtime_instances: usize,
|
||||
cache_path: Option<PathBuf>,
|
||||
runtime_cache_size: u8,
|
||||
) -> RuntimeCache {
|
||||
let cap = ByLength::new(runtime_cache_size.max(1) as u32);
|
||||
RuntimeCache { runtimes: Mutex::new(LruMap::new(cap)), max_runtime_instances, cache_path }
|
||||
}
|
||||
|
||||
/// Prepares a WASM module instance and executes given function for it.
|
||||
///
|
||||
/// This uses internal cache to find available instance or create a new one.
|
||||
/// # Parameters
|
||||
///
|
||||
/// `runtime_code` - The runtime wasm code used setup the runtime.
|
||||
///
|
||||
/// `ext` - The externalities to access the state.
|
||||
///
|
||||
/// `wasm_method` - Type of WASM backend to use.
|
||||
///
|
||||
/// `heap_alloc_strategy` - The heap allocation strategy to use.
|
||||
///
|
||||
/// `allow_missing_func_imports` - Ignore missing function imports.
|
||||
///
|
||||
/// `f` - Function to execute.
|
||||
///
|
||||
/// `H` - A compile-time list of host functions to expose to the runtime.
|
||||
///
|
||||
/// # Returns result of `f` wrapped in an additional result.
|
||||
/// In case of failure one of two errors can be returned:
|
||||
///
|
||||
/// `Err::RuntimeConstruction` is returned for runtime construction issues.
|
||||
///
|
||||
/// `Error::InvalidMemoryReference` is returned if no memory export with the
|
||||
/// identifier `memory` can be found in the runtime.
|
||||
pub fn with_instance<'c, H, R, F>(
|
||||
&self,
|
||||
runtime_code: &'c RuntimeCode<'c>,
|
||||
ext: &mut dyn Externalities,
|
||||
wasm_method: WasmExecutionMethod,
|
||||
heap_alloc_strategy: HeapAllocStrategy,
|
||||
allow_missing_func_imports: bool,
|
||||
f: F,
|
||||
) -> Result<Result<R, Error>, Error>
|
||||
where
|
||||
H: HostFunctions,
|
||||
F: FnOnce(
|
||||
&dyn WasmModule,
|
||||
&mut dyn WasmInstance,
|
||||
Option<&RuntimeVersion>,
|
||||
&mut dyn Externalities,
|
||||
) -> Result<R, Error>,
|
||||
{
|
||||
let code_hash = &runtime_code.hash;
|
||||
|
||||
let versioned_runtime_id =
|
||||
VersionedRuntimeId { code_hash: code_hash.clone(), heap_alloc_strategy, wasm_method };
|
||||
|
||||
let mut runtimes = self.runtimes.lock(); // this must be released prior to calling f
|
||||
let versioned_runtime = if let Some(versioned_runtime) = runtimes.get(&versioned_runtime_id)
|
||||
{
|
||||
versioned_runtime.clone()
|
||||
} else {
|
||||
let code = runtime_code.fetch_runtime_code().ok_or(WasmError::CodeNotFound)?;
|
||||
|
||||
let time = std::time::Instant::now();
|
||||
|
||||
let result = create_versioned_wasm_runtime::<H>(
|
||||
&code,
|
||||
ext,
|
||||
wasm_method,
|
||||
heap_alloc_strategy,
|
||||
allow_missing_func_imports,
|
||||
self.max_runtime_instances,
|
||||
self.cache_path.as_deref(),
|
||||
);
|
||||
|
||||
match result {
|
||||
Ok(ref result) => {
|
||||
tracing::debug!(
|
||||
target: "wasm-runtime",
|
||||
"Prepared new runtime version {:?} in {} ms.",
|
||||
result.version,
|
||||
time.elapsed().as_millis(),
|
||||
);
|
||||
},
|
||||
Err(ref err) => {
|
||||
tracing::warn!(target: "wasm-runtime", error = ?err, "Cannot create a runtime");
|
||||
},
|
||||
}
|
||||
|
||||
let versioned_runtime = Arc::new(result?);
|
||||
|
||||
// Save new versioned wasm runtime in cache
|
||||
runtimes.insert(versioned_runtime_id, versioned_runtime.clone());
|
||||
|
||||
versioned_runtime
|
||||
};
|
||||
|
||||
// Lock must be released prior to calling f
|
||||
drop(runtimes);
|
||||
|
||||
Ok(versioned_runtime.with_instance(ext, f))
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a wasm runtime with the given `code`.
|
||||
pub fn create_wasm_runtime_with_code<H>(
|
||||
wasm_method: WasmExecutionMethod,
|
||||
heap_alloc_strategy: HeapAllocStrategy,
|
||||
blob: RuntimeBlob,
|
||||
allow_missing_func_imports: bool,
|
||||
cache_path: Option<&Path>,
|
||||
) -> Result<Box<dyn WasmModule>, WasmError>
|
||||
where
|
||||
H: HostFunctions,
|
||||
{
|
||||
if let Some(blob) = blob.as_polkavm_blob() {
|
||||
return pezsc_executor_polkavm::create_runtime::<H>(blob);
|
||||
}
|
||||
|
||||
match wasm_method {
|
||||
WasmExecutionMethod::Compiled { instantiation_strategy } =>
|
||||
pezsc_executor_wasmtime::create_runtime::<H>(
|
||||
blob,
|
||||
pezsc_executor_wasmtime::Config {
|
||||
allow_missing_func_imports,
|
||||
cache_path: cache_path.map(ToOwned::to_owned),
|
||||
semantics: pezsc_executor_wasmtime::Semantics {
|
||||
heap_alloc_strategy,
|
||||
instantiation_strategy,
|
||||
deterministic_stack_limit: None,
|
||||
canonicalize_nans: false,
|
||||
parallel_compilation: true,
|
||||
wasm_multi_value: false,
|
||||
wasm_bulk_memory: false,
|
||||
wasm_reference_types: false,
|
||||
wasm_simd: false,
|
||||
},
|
||||
},
|
||||
)
|
||||
.map(|runtime| -> Box<dyn WasmModule> { Box::new(runtime) }),
|
||||
}
|
||||
}
|
||||
|
||||
fn decode_version(mut version: &[u8]) -> Result<RuntimeVersion, WasmError> {
|
||||
Decode::decode(&mut version).map_err(|_| {
|
||||
WasmError::Instantiation(
|
||||
"failed to decode \"Core_version\" result using old runtime version".into(),
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
fn decode_runtime_apis(apis: &[u8]) -> Result<Vec<([u8; 8], u32)>, WasmError> {
|
||||
use pezsp_api::RUNTIME_API_INFO_SIZE;
|
||||
|
||||
apis.chunks(RUNTIME_API_INFO_SIZE)
|
||||
.map(|chunk| {
|
||||
// `chunk` can be less than `RUNTIME_API_INFO_SIZE` if the total length of `apis`
|
||||
// doesn't completely divide by `RUNTIME_API_INFO_SIZE`.
|
||||
<[u8; RUNTIME_API_INFO_SIZE]>::try_from(chunk)
|
||||
.map(pezsp_api::deserialize_runtime_api_info)
|
||||
.map_err(|_| WasmError::Other("a clipped runtime api info declaration".to_owned()))
|
||||
})
|
||||
.collect::<Result<Vec<_>, WasmError>>()
|
||||
}
|
||||
|
||||
/// Take the runtime blob and scan it for the custom wasm sections containing the version
|
||||
/// information and construct the `RuntimeVersion` from them.
|
||||
///
|
||||
/// If there are no such sections, it returns `None`. If there is an error during decoding those
|
||||
/// sections, `Err` will be returned.
|
||||
pub fn read_embedded_version(blob: &RuntimeBlob) -> Result<Option<RuntimeVersion>, WasmError> {
|
||||
if let Some(mut version_section) = blob.custom_section_contents("runtime_version") {
|
||||
let apis = blob
|
||||
.custom_section_contents("runtime_apis")
|
||||
.map(decode_runtime_apis)
|
||||
.transpose()?
|
||||
.map(Into::into);
|
||||
|
||||
let core_version = apis.as_ref().and_then(pezsp_version::core_version_from_apis);
|
||||
// We do not use `RuntimeVersion::decode` here because that `decode_version` relies on
|
||||
// presence of a special API in the `apis` field to treat the input as a non-legacy version.
|
||||
// However the structure found in the `runtime_version` always contain an empty `apis`
|
||||
// field. Therefore the version read will be mistakenly treated as an legacy one.
|
||||
let mut decoded_version = pezsp_version::RuntimeVersion::decode_with_version_hint(
|
||||
&mut version_section,
|
||||
core_version,
|
||||
)
|
||||
.map_err(|_| WasmError::Instantiation("failed to decode version section".into()))?;
|
||||
|
||||
if let Some(apis) = apis {
|
||||
decoded_version.apis = apis;
|
||||
}
|
||||
|
||||
Ok(Some(decoded_version))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
fn create_versioned_wasm_runtime<H>(
|
||||
code: &[u8],
|
||||
ext: &mut dyn Externalities,
|
||||
wasm_method: WasmExecutionMethod,
|
||||
heap_alloc_strategy: HeapAllocStrategy,
|
||||
allow_missing_func_imports: bool,
|
||||
max_instances: usize,
|
||||
cache_path: Option<&Path>,
|
||||
) -> Result<VersionedRuntime, WasmError>
|
||||
where
|
||||
H: HostFunctions,
|
||||
{
|
||||
// The incoming code may be actually compressed. We decompress it here and then work with
|
||||
// the uncompressed code from now on.
|
||||
let blob = pezsc_executor_common::runtime_blob::RuntimeBlob::uncompress_if_needed(code)?;
|
||||
|
||||
// Use the runtime blob to scan if there is any metadata embedded into the wasm binary
|
||||
// pertaining to runtime version. We do it before consuming the runtime blob for creating the
|
||||
// runtime.
|
||||
let mut version = read_embedded_version(&blob)?;
|
||||
|
||||
let runtime = create_wasm_runtime_with_code::<H>(
|
||||
wasm_method,
|
||||
heap_alloc_strategy,
|
||||
blob,
|
||||
allow_missing_func_imports,
|
||||
cache_path,
|
||||
)?;
|
||||
|
||||
// If the runtime blob doesn't embed the runtime version then use the legacy version query
|
||||
// mechanism: call the runtime.
|
||||
if version.is_none() {
|
||||
// Call to determine runtime version.
|
||||
let version_result = {
|
||||
// `ext` is already implicitly handled as unwind safe, as we store it in a global
|
||||
// variable.
|
||||
let mut ext = AssertUnwindSafe(ext);
|
||||
|
||||
// The following unwind safety assertion is OK because if the method call panics, the
|
||||
// runtime will be dropped.
|
||||
let runtime = AssertUnwindSafe(runtime.as_ref());
|
||||
crate::executor::with_externalities_safe(&mut **ext, move || {
|
||||
runtime.new_instance()?.call("Core_version".into(), &[])
|
||||
})
|
||||
.map_err(|_| WasmError::Instantiation("panic in call to get runtime version".into()))?
|
||||
};
|
||||
|
||||
if let Ok(version_buf) = version_result {
|
||||
version = Some(decode_version(&version_buf)?)
|
||||
}
|
||||
}
|
||||
|
||||
let mut instances = Vec::with_capacity(max_instances);
|
||||
instances.resize_with(max_instances, || Mutex::new(None));
|
||||
|
||||
Ok(VersionedRuntime { module: runtime, version, instances })
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
extern crate alloc;
|
||||
|
||||
use super::*;
|
||||
use alloc::borrow::Cow;
|
||||
use codec::Encode;
|
||||
use pezsp_api::{Core, RuntimeApiInfo};
|
||||
use pezsp_version::{create_apis_vec, RuntimeVersion};
|
||||
use pezsp_wasm_interface::HostFunctions;
|
||||
use bizinikiwi_test_runtime::Block;
|
||||
|
||||
#[derive(Encode)]
|
||||
pub struct OldRuntimeVersion {
|
||||
pub spec_name: Cow<'static, str>,
|
||||
pub impl_name: Cow<'static, str>,
|
||||
pub authoring_version: u32,
|
||||
pub spec_version: u32,
|
||||
pub impl_version: u32,
|
||||
pub apis: pezsp_version::ApisVec,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn host_functions_are_equal() {
|
||||
let host_functions = pezsp_io::BizinikiwiHostFunctions::host_functions();
|
||||
|
||||
let equal = &host_functions[..] == &host_functions[..];
|
||||
assert!(equal, "Host functions are not equal");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn old_runtime_version_decodes() {
|
||||
let old_runtime_version = OldRuntimeVersion {
|
||||
spec_name: "test".into(),
|
||||
impl_name: "test".into(),
|
||||
authoring_version: 1,
|
||||
spec_version: 1,
|
||||
impl_version: 1,
|
||||
apis: create_apis_vec!([(<dyn Core::<Block>>::ID, 1)]),
|
||||
};
|
||||
|
||||
let version = decode_version(&old_runtime_version.encode()).unwrap();
|
||||
assert_eq!(1, version.transaction_version);
|
||||
assert_eq!(0, version.system_version);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn old_runtime_version_decodes_fails_with_version_3() {
|
||||
let old_runtime_version = OldRuntimeVersion {
|
||||
spec_name: "test".into(),
|
||||
impl_name: "test".into(),
|
||||
authoring_version: 1,
|
||||
spec_version: 1,
|
||||
impl_version: 1,
|
||||
apis: create_apis_vec!([(<dyn Core::<Block>>::ID, 3)]),
|
||||
};
|
||||
|
||||
decode_version(&old_runtime_version.encode()).unwrap_err();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn new_runtime_version_decodes() {
|
||||
let old_runtime_version = RuntimeVersion {
|
||||
spec_name: "test".into(),
|
||||
impl_name: "test".into(),
|
||||
authoring_version: 1,
|
||||
spec_version: 1,
|
||||
impl_version: 1,
|
||||
apis: create_apis_vec!([(<dyn Core::<Block>>::ID, 3)]),
|
||||
transaction_version: 3,
|
||||
system_version: 4,
|
||||
};
|
||||
|
||||
let version = decode_version(&old_runtime_version.encode()).unwrap();
|
||||
assert_eq!(3, version.transaction_version);
|
||||
assert_eq!(0, version.system_version);
|
||||
|
||||
let old_runtime_version = RuntimeVersion {
|
||||
spec_name: "test".into(),
|
||||
impl_name: "test".into(),
|
||||
authoring_version: 1,
|
||||
spec_version: 1,
|
||||
impl_version: 1,
|
||||
apis: create_apis_vec!([(<dyn Core::<Block>>::ID, 4)]),
|
||||
transaction_version: 3,
|
||||
system_version: 4,
|
||||
};
|
||||
|
||||
let version = decode_version(&old_runtime_version.encode()).unwrap();
|
||||
assert_eq!(3, version.transaction_version);
|
||||
assert_eq!(4, version.system_version);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn embed_runtime_version_works() {
|
||||
let wasm = pezsp_maybe_compressed_blob::decompress(
|
||||
bizinikiwi_test_runtime::wasm_binary_unwrap(),
|
||||
pezsp_maybe_compressed_blob::CODE_BLOB_BOMB_LIMIT,
|
||||
)
|
||||
.expect("Decompressing works");
|
||||
let runtime_version = RuntimeVersion {
|
||||
spec_name: "test_replace".into(),
|
||||
impl_name: "test_replace".into(),
|
||||
authoring_version: 100,
|
||||
spec_version: 100,
|
||||
impl_version: 100,
|
||||
apis: create_apis_vec!([(<dyn Core::<Block>>::ID, 4)]),
|
||||
transaction_version: 100,
|
||||
system_version: 1,
|
||||
};
|
||||
|
||||
let embedded = pezsp_version::embed::embed_runtime_version(&wasm, runtime_version.clone())
|
||||
.expect("Embedding works");
|
||||
|
||||
let blob = RuntimeBlob::new(&embedded).expect("Embedded blob is valid");
|
||||
let read_version = read_embedded_version(&blob)
|
||||
.ok()
|
||||
.flatten()
|
||||
.expect("Reading embedded version works");
|
||||
|
||||
assert_eq!(runtime_version, read_version);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user