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:
2025-12-14 00:04:10 +03:00
parent 286de54384
commit 1c0e57d984
9084 changed files with 997839 additions and 997557 deletions
+809
View File
@@ -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),
}
}
+91
View File
@@ -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);
}
}