diff --git a/substrate/Cargo.lock b/substrate/Cargo.lock index 0e28bd5d36..46df075448 100644 --- a/substrate/Cargo.lock +++ b/substrate/Cargo.lock @@ -5760,6 +5760,7 @@ dependencies = [ "substrate-runtime-test 2.0.0", "substrate-serializer 2.0.0", "substrate-state-machine 2.0.0", + "substrate-test-runtime 2.0.0", "substrate-trie 2.0.0", "substrate-wasm-interface 2.0.0", "test-case 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/substrate/client/executor/Cargo.toml b/substrate/client/executor/Cargo.toml index df023d9aa9..3eb6be9a6b 100644 --- a/substrate/client/executor/Cargo.toml +++ b/substrate/client/executor/Cargo.toml @@ -37,13 +37,17 @@ assert_matches = "1.3.0" wabt = "0.9.2" hex-literal = "0.2.1" runtime-test = { package = "substrate-runtime-test", path = "runtime-test" } +test-runtime = { package = "substrate-test-runtime", path = "../../test/utils/runtime" } +runtime-interface = { package = "substrate-runtime-interface", path = "../../primitives/runtime-interface" } client-api = { package = "substrate-client-api", path = "../api" } substrate-offchain = { path = "../offchain/" } state_machine = { package = "substrate-state-machine", path = "../../primitives/state-machine" } test-case = "0.3.3" [features] -default = [] +default = [ "std" ] +# This crate does not have `no_std` support, we just require this for tests +std = [] wasm-extern-trace = [] wasmtime = [ "cranelift-codegen", diff --git a/substrate/client/executor/src/native_executor.rs b/substrate/client/executor/src/native_executor.rs index 3b33ee514d..23ea17a0c4 100644 --- a/substrate/client/executor/src/native_executor.rs +++ b/substrate/client/executor/src/native_executor.rs @@ -41,7 +41,8 @@ const DEFAULT_HEAP_PAGES: u64 = 1024; pub(crate) fn safe_call(f: F) -> Result where F: UnwindSafe + FnOnce() -> U { - // Substrate uses custom panic hook that terminates process on panic. Disable termination for the native call. + // Substrate uses custom panic hook that terminates process on panic. Disable + // termination for the native call. let _guard = panic_handler::AbortGuard::force_unwind(); std::panic::catch_unwind(f).map_err(|_| Error::Runtime) } @@ -59,6 +60,10 @@ pub fn with_native_environment(ext: &mut dyn Externalities, f: F) -> Resul /// /// 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. /// /// If the method with the specified name doesn't exist then `Err` is returned. @@ -99,6 +104,9 @@ impl NativeExecutor { crate::deprecated_host_interface::SubstrateExternals::host_functions(), ); + // Add the custom host functions provided by the user. + host_functions.extend(D::ExtendHostFunctions::host_functions()); + NativeExecutor { _dummy: Default::default(), fallback_method, @@ -259,17 +267,65 @@ impl CodeExecutor for NativeExecutor { } /// Implements a `NativeExecutionDispatch` for provided parameters. +/// +/// # Example +/// +/// ``` +/// substrate_executor::native_executor_instance!( +/// pub MyExecutor, +/// test_runtime::api::dispatch, +/// test_runtime::native_version, +/// ); +/// ``` +/// +/// # With custom host functions +/// +/// When you want to use custom runtime interfaces from within your runtime, you need to make the +/// executor aware of the host functions for these interfaces. +/// +/// ``` +/// # use runtime_interface::runtime_interface; +/// +/// #[runtime_interface] +/// trait MyInterface { +/// fn say_hello_world(data: &str) { +/// println!("Hello world from: {}", data); +/// } +/// } +/// +/// substrate_executor::native_executor_instance!( +/// pub MyExecutor, +/// test_runtime::api::dispatch, +/// test_runtime::native_version, +/// my_interface::HostFunctions, +/// ); +/// ``` +/// +/// When you have multiple interfaces, you can give the host functions as a tuple e.g.: +/// `(my_interface::HostFunctions, my_interface2::HostFunctions)` +/// #[macro_export] macro_rules! native_executor_instance { ( $pub:vis $name:ident, $dispatcher:path, $version:path $(,)?) => { - /// A unit struct which implements `NativeExecutionDispatch` feeding in the hard-coded runtime. + /// A unit struct which implements `NativeExecutionDispatch` feeding in the + /// hard-coded runtime. $pub struct $name; - $crate::native_executor_instance!(IMPL $name, $dispatcher, $version); + $crate::native_executor_instance!(IMPL $name, $dispatcher, $version, ()); }; - (IMPL $name:ident, $dispatcher:path, $version:path) => { + ( $pub:vis $name:ident, $dispatcher:path, $version:path, $custom_host_functions:ty $(,)?) => { + /// A unit struct which implements `NativeExecutionDispatch` feeding in the + /// hard-coded runtime. + $pub struct $name; + $crate::native_executor_instance!( + IMPL $name, $dispatcher, $version, $custom_host_functions + ); + }; + (IMPL $name:ident, $dispatcher:path, $version:path, $custom_host_functions:ty) => { impl $crate::NativeExecutionDispatch for $name { + type ExtendHostFunctions = $custom_host_functions; + fn dispatch( - ext: &mut $crate::Externalities, + ext: &mut dyn $crate::Externalities, method: &str, data: &[u8] ) -> $crate::error::Result> { @@ -283,3 +339,34 @@ macro_rules! native_executor_instance { } } } + +#[cfg(test)] +mod tests { + use super::*; + use runtime_interface::runtime_interface; + + #[runtime_interface] + trait MyInterface { + fn say_hello_world(data: &str) { + println!("Hello world from: {}", data); + } + } + + native_executor_instance!( + pub MyExecutor, + test_runtime::api::dispatch, + test_runtime::native_version, + (my_interface::HostFunctions, my_interface::HostFunctions), + ); + + #[test] + fn native_executor_registers_custom_interface() { + let executor = NativeExecutor::::new(WasmExecutionMethod::Interpreted, None); + my_interface::HostFunctions::host_functions().iter().for_each(|function| { + assert_eq!( + executor.host_functions.iter().filter(|f| f == &function).count(), + 2, + ); + }); + } +}