// This file is part of Substrate. // Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License // along with this program. If not, see . use crate::{ error::{Error, Result}, wasm_runtime::{RuntimeCache, WasmExecutionMethod}, RuntimeVersionOf, }; use std::{ marker::PhantomData, panic::{AssertUnwindSafe, UnwindSafe}, path::PathBuf, sync::Arc, }; use codec::Encode; use sc_executor_common::{ runtime_blob::RuntimeBlob, wasm_runtime::{ AllocationStats, HeapAllocStrategy, WasmInstance, WasmModule, DEFAULT_HEAP_ALLOC_STRATEGY, }, }; use sp_core::traits::{CallContext, CodeExecutor, Externalities, RuntimeCode}; use sp_version::{GetNativeVersion, NativeVersion, RuntimeVersion}; use sp_wasm_interface::{ExtendedHostFunctions, HostFunctions}; /// Set up the externalities and safe calling environment to execute runtime calls. /// /// If the inner closure panics, it will be caught and return an error. pub fn with_externalities_safe(ext: &mut dyn Externalities, f: F) -> Result where F: UnwindSafe + FnOnce() -> U, { sp_externalities::set_and_run_with_externalities(ext, move || { // Substrate uses custom panic hook that terminates process on panic. Disable // termination for the native call. let _guard = sp_panic_handler::AbortGuard::force_unwind(); std::panic::catch_unwind(f).map_err(|e| { if let Some(err) = e.downcast_ref::() { Error::RuntimePanicked(err.clone()) } else if let Some(err) = e.downcast_ref::<&'static str>() { Error::RuntimePanicked(err.to_string()) } else { Error::RuntimePanicked("Unknown panic".into()) } }) }) } /// Delegate for dispatching a CodeExecutor call. /// /// By dispatching we mean that we execute a runtime function specified by it's name. pub trait NativeExecutionDispatch: Send + Sync { /// Host functions for custom runtime interfaces that should be callable from within the runtime /// besides the default Substrate runtime interfaces. type ExtendHostFunctions: HostFunctions; /// Dispatch a method in the runtime. fn dispatch(method: &str, data: &[u8]) -> Option>; /// Provide native runtime version. fn native_version() -> NativeVersion; } fn unwrap_heap_pages(pages: Option) -> HeapAllocStrategy { pages.unwrap_or_else(|| DEFAULT_HEAP_ALLOC_STRATEGY) } /// Builder for creating a [`WasmExecutor`] instance. pub struct WasmExecutorBuilder { _phantom: PhantomData, method: WasmExecutionMethod, onchain_heap_alloc_strategy: Option, offchain_heap_alloc_strategy: Option, ignore_onchain_heap_pages: bool, max_runtime_instances: usize, cache_path: Option, allow_missing_host_functions: bool, runtime_cache_size: u8, } impl WasmExecutorBuilder { /// 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) -> 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 { 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 { /// 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, /// The path to a directory which the executor can leverage for a file cache, e.g. put there /// compiled artifacts. cache_path: Option, /// Ignore missing function imports. allow_missing_host_functions: bool, phantom: PhantomData, } impl Clone for WasmExecutor { 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 WasmExecutor where H: HostFunctions, { /// Create new instance. /// /// # Parameters /// /// `method` - Method used to execute Wasm code. /// /// `default_heap_pages` - Number of 64KB pages to allocate for Wasm execution. Internally this /// will be mapped as [`HeapAllocStrategy::Static`] where `default_heap_pages` represent the /// static number of heap pages to allocate. Defaults to `DEFAULT_HEAP_ALLOC_STRATEGY` if `None` /// is provided. /// /// `max_runtime_instances` - The number of runtime instances to keep in memory ready for reuse. /// /// `cache_path` - A path to a directory where the executor can place its files for purposes of /// caching. This may be important in cases when there are many different modules with the /// compiled execution method is used. /// /// `runtime_cache_size` - The capacity of runtime cache. #[deprecated(note = "use `Self::builder` method instead of it")] pub fn new( method: WasmExecutionMethod, default_heap_pages: Option, max_runtime_instances: usize, cache_path: Option, 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 { WasmExecutorBuilder::new() } /// Ignore missing function imports if set true. #[deprecated(note = "use `Self::builder` method instead of it")] pub fn allow_missing_host_functions(&mut self, allow_missing_host_functions: bool) { self.allow_missing_host_functions = allow_missing_host_functions } /// Execute the given closure `f` with the latest runtime (based on `runtime_code`). /// /// The closure `f` is expected to return `Err(_)` when there happened a `panic!` in native code /// while executing the runtime in Wasm. If a `panic!` occurred, the runtime is invalidated to /// prevent any poisoned state. Native runtime execution does not need to report back /// any `panic!`. /// /// # Safety /// /// `runtime` and `ext` are given as `AssertUnwindSafe` to the closure. As described above, the /// runtime is invalidated on any `panic!` to prevent a poisoned state. `ext` is already /// implicitly handled as unwind safe, as we store it in a global variable while executing the /// native runtime. pub fn with_instance( &self, runtime_code: &RuntimeCode, ext: &mut dyn Externalities, heap_alloc_strategy: HeapAllocStrategy, f: F, ) -> Result where F: FnOnce( AssertUnwindSafe<&dyn WasmModule>, AssertUnwindSafe<&mut dyn WasmInstance>, Option<&RuntimeVersion>, AssertUnwindSafe<&mut dyn Externalities>, ) -> Result>, { match self.cache.with_instance::( 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, 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, Error>, Option) { 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, ) -> std::result::Result, Error> { let module = crate::wasm_runtime::create_wasm_runtime_with_code::( 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 sp_core::traits::ReadRuntimeVersion for WasmExecutor where H: HostFunctions, { fn read_runtime_version( &self, wasm_code: &[u8], ext: &mut dyn Externalities, ) -> std::result::Result, 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 CodeExecutor for WasmExecutor where H: HostFunctions, { type Error = Error; fn call( &self, ext: &mut dyn Externalities, runtime_code: &RuntimeCode, method: &str, data: &[u8], context: CallContext, ) -> (Result>, 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 RuntimeVersionOf for WasmExecutor where H: HostFunctions, { fn runtime_version( &self, ext: &mut dyn Externalities, runtime_code: &RuntimeCode, ) -> Result { let on_chain_heap_pages = if self.ignore_onchain_heap_pages { self.default_onchain_heap_alloc_strategy } else { runtime_code .heap_pages .map(|h| HeapAllocStrategy::Static { extra_pages: h as _ }) .unwrap_or_else(|| self.default_onchain_heap_alloc_strategy) }; self.with_instance( runtime_code, ext, on_chain_heap_pages, |_module, _instance, version, _ext| { Ok(version.cloned().ok_or_else(|| Error::ApiError("Unknown version".into()))) }, ) } } /// A generic `CodeExecutor` implementation that uses a delegate to determine wasm code equivalence /// and dispatch to native code when possible, falling back on `WasmExecutor` when not. pub struct NativeElseWasmExecutor { /// Native runtime version info. native_version: NativeVersion, /// Fallback wasm executor. wasm: WasmExecutor>, use_native: bool, } impl NativeElseWasmExecutor { /// /// 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, 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, >, ) -> Self { Self { native_version: D::native_version(), wasm: executor, use_native: true } } /// Disable to use native runtime when possible just behave like `WasmExecutor`. /// /// Default to enabled. pub fn disable_use_native(&mut self) { self.use_native = false; } /// Ignore missing function imports if set true. #[deprecated(note = "use `Self::new_with_wasm_executor` method instead of it")] pub fn allow_missing_host_functions(&mut self, allow_missing_host_functions: bool) { self.wasm.allow_missing_host_functions = allow_missing_host_functions } } impl RuntimeVersionOf for NativeElseWasmExecutor { fn runtime_version( &self, ext: &mut dyn Externalities, runtime_code: &RuntimeCode, ) -> Result { self.wasm.runtime_version(ext, runtime_code) } } impl GetNativeVersion for NativeElseWasmExecutor { fn native_version(&self) -> &NativeVersion { &self.native_version } } impl CodeExecutor for NativeElseWasmExecutor { type Error = Error; fn call( &self, ext: &mut dyn Externalities, runtime_code: &RuntimeCode, method: &str, data: &[u8], context: CallContext, ) -> (Result>, bool) { let use_native = self.use_native; tracing::trace!( target: "executor", function = %method, "Executing function", ); let on_chain_heap_alloc_strategy = if self.wasm.ignore_onchain_heap_pages { self.wasm.default_onchain_heap_alloc_strategy } else { runtime_code .heap_pages .map(|h| HeapAllocStrategy::Static { extra_pages: h as _ }) .unwrap_or_else(|| self.wasm.default_onchain_heap_alloc_strategy) }; let heap_alloc_strategy = match context { CallContext::Offchain => self.wasm.default_offchain_heap_alloc_strategy, CallContext::Onchain => on_chain_heap_alloc_strategy, }; let mut used_native = false; let result = self.wasm.with_instance( runtime_code, ext, heap_alloc_strategy, |_, mut instance, on_chain_version, mut ext| { let on_chain_version = on_chain_version.ok_or_else(|| Error::ApiError("Unknown version".into()))?; let can_call_with = on_chain_version.can_call_with(&self.native_version.runtime_version); if use_native && can_call_with { tracing::trace!( target: "executor", native = %self.native_version.runtime_version, chain = %on_chain_version, "Request for native execution succeeded", ); used_native = true; Ok(with_externalities_safe(&mut **ext, move || D::dispatch(method, data))? .ok_or_else(|| Error::MethodNotFound(method.to_owned()))) } else { if !can_call_with { tracing::trace!( target: "executor", native = %self.native_version.runtime_version, chain = %on_chain_version, "Request for native execution failed", ); } with_externalities_safe(&mut **ext, move || instance.call_export(method, data)) } }, ); (result, used_native) } } impl Clone for NativeElseWasmExecutor { fn clone(&self) -> Self { NativeElseWasmExecutor { native_version: D::native_version(), wasm: self.wasm.clone(), use_native: self.use_native, } } } impl sp_core::traits::ReadRuntimeVersion for NativeElseWasmExecutor { fn read_runtime_version( &self, wasm_code: &[u8], ext: &mut dyn Externalities, ) -> std::result::Result, String> { self.wasm.read_runtime_version(wasm_code, ext) } } #[cfg(test)] mod tests { use super::*; use sp_runtime_interface::runtime_interface; #[runtime_interface] trait MyInterface { fn say_hello_world(data: &str) { println!("Hello world from: {}", data); } } pub struct MyExecutorDispatch; impl NativeExecutionDispatch for MyExecutorDispatch { type ExtendHostFunctions = (my_interface::HostFunctions, my_interface::HostFunctions); fn dispatch(method: &str, data: &[u8]) -> Option> { substrate_test_runtime::api::dispatch(method, data) } fn native_version() -> NativeVersion { substrate_test_runtime::native_version() } } #[test] fn native_executor_registers_custom_interface() { let executor = NativeElseWasmExecutor::::new_with_wasm_executor( WasmExecutor::builder().build(), ); fn extract_host_functions( _: &WasmExecutor, ) -> Vec<&'static dyn sp_wasm_interface::Function> where H: HostFunctions, { H::host_functions() } my_interface::HostFunctions::host_functions().iter().for_each(|function| { assert_eq!( extract_host_functions(&executor.wasm).iter().filter(|f| f == &function).count(), 2 ); }); my_interface::say_hello_world("hey"); } }