use crate::{
RuntimeInfo, error::{Error, Result},
wasm_runtime::{RuntimeCache, WasmExecutionMethod},
};
use std::{
collections::HashMap,
panic::{UnwindSafe, AssertUnwindSafe},
result,
sync::{Arc, atomic::{AtomicU64, Ordering}, mpsc},
path::PathBuf,
};
use sp_version::{NativeVersion, RuntimeVersion};
use codec::{Decode, Encode};
use sp_core::{
NativeOrEncoded,
traits::{
CodeExecutor, Externalities, RuntimeCode, MissingHostFunctions,
RuntimeSpawnExt, RuntimeSpawn,
},
};
use log::trace;
use sp_wasm_interface::{HostFunctions, Function};
use sc_executor_common::wasm_runtime::{WasmInstance, WasmModule, InvokeMethod};
use sp_externalities::ExternalitiesExt as _;
use sp_tasks::new_async_externalities;
const DEFAULT_HEAP_PAGES: u64 = 1024;
pub fn with_externalities_safe<F, U>(ext: &mut dyn Externalities, f: F) -> Result<U>
where F: UnwindSafe + FnOnce() -> U
{
sp_externalities::set_and_run_with_externalities(
ext,
move || {
let _guard = sp_panic_handler::AbortGuard::force_unwind();
std::panic::catch_unwind(f).map_err(|e| {
if let Some(err) = e.downcast_ref::<String>() {
Error::RuntimePanicked(err.clone())
} else if let Some(err) = e.downcast_ref::<&'static str>() {
Error::RuntimePanicked(err.to_string())
} else {
Error::RuntimePanicked("Unknown panic".into())
}
})
},
)
}
pub trait NativeExecutionDispatch: Send + Sync {
type ExtendHostFunctions: HostFunctions;
fn dispatch(ext: &mut dyn Externalities, method: &str, data: &[u8]) -> Result<Vec<u8>>;
fn native_version() -> NativeVersion;
}
#[derive(Clone)]
pub struct WasmExecutor {
method: WasmExecutionMethod,
default_heap_pages: u64,
host_functions: Arc<Vec<&'static dyn Function>>,
cache: Arc<RuntimeCache>,
max_runtime_instances: usize,
cache_path: Option<PathBuf>,
}
impl WasmExecutor {
pub fn new(
method: WasmExecutionMethod,
default_heap_pages: Option<u64>,
host_functions: Vec<&'static dyn Function>,
max_runtime_instances: usize,
cache_path: Option<PathBuf>,
) -> Self {
WasmExecutor {
method,
default_heap_pages: default_heap_pages.unwrap_or(DEFAULT_HEAP_PAGES),
host_functions: Arc::new(host_functions),
cache: Arc::new(RuntimeCache::new(max_runtime_instances, cache_path.clone())),
max_runtime_instances,
cache_path,
}
}
fn with_instance<R, F>(
&self,
runtime_code: &RuntimeCode,
ext: &mut dyn Externalities,
allow_missing_host_functions: bool,
f: F,
) -> Result<R>
where F: FnOnce(
AssertUnwindSafe<&Arc<dyn WasmModule>>,
AssertUnwindSafe<&dyn WasmInstance>,
Option<&RuntimeVersion>,
AssertUnwindSafe<&mut dyn Externalities>,
) -> Result<Result<R>>,
{
match self.cache.with_instance(
runtime_code,
ext,
self.method,
self.default_heap_pages,
&*self.host_functions,
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),
}
}
}
impl sp_core::traits::CallInWasm for WasmExecutor {
fn call_in_wasm(
&self,
wasm_code: &[u8],
code_hash: Option<Vec<u8>>,
method: &str,
call_data: &[u8],
ext: &mut dyn Externalities,
missing_host_functions: MissingHostFunctions,
) -> std::result::Result<Vec<u8>, String> {
let allow_missing_host_functions = missing_host_functions.allowed();
if let Some(hash) = code_hash {
let code = RuntimeCode {
code_fetcher: &sp_core::traits::WrappedRuntimeCode(wasm_code.into()),
hash,
heap_pages: None,
};
self.with_instance(&code, ext, allow_missing_host_functions, |module, instance, _, mut ext| {
with_externalities_safe(
&mut **ext,
move || {
RuntimeInstanceSpawn::register_on_externalities(module.clone());
instance.call_export(method, call_data)
}
)
}).map_err(|e| e.to_string())
} else {
let module = crate::wasm_runtime::create_wasm_runtime_with_code(
self.method,
self.default_heap_pages,
&wasm_code,
self.host_functions.to_vec(),
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 instance = AssertUnwindSafe(instance);
let mut ext = AssertUnwindSafe(ext);
let module = AssertUnwindSafe(module);
with_externalities_safe(
&mut **ext,
move || {
RuntimeInstanceSpawn::register_on_externalities(module.clone());
instance.call_export(method, call_data)
}
)
.and_then(|r| r)
.map_err(|e| e.to_string())
}
}
}
pub struct NativeExecutor<D> {
_dummy: std::marker::PhantomData<D>,
native_version: NativeVersion,
wasm: WasmExecutor,
}
impl<D: NativeExecutionDispatch> NativeExecutor<D> {
pub fn new(
fallback_method: WasmExecutionMethod,
default_heap_pages: Option<u64>,
max_runtime_instances: usize,
) -> Self {
let mut host_functions = D::ExtendHostFunctions::host_functions();
host_functions.extend(sp_io::SubstrateHostFunctions::host_functions());
let wasm_executor = WasmExecutor::new(
fallback_method,
default_heap_pages,
host_functions,
max_runtime_instances,
None,
);
NativeExecutor {
_dummy: Default::default(),
native_version: D::native_version(),
wasm: wasm_executor,
}
}
}
impl<D: NativeExecutionDispatch> RuntimeInfo for NativeExecutor<D> {
fn native_version(&self) -> &NativeVersion {
&self.native_version
}
fn runtime_version(
&self,
ext: &mut dyn Externalities,
runtime_code: &RuntimeCode,
) -> Result<RuntimeVersion> {
self.wasm.with_instance(
runtime_code,
ext,
false,
|_module, _instance, version, _ext|
Ok(version.cloned().ok_or_else(|| Error::ApiError("Unknown version".into()))),
)
}
}
pub struct RuntimeInstanceSpawn {
module: Arc<dyn WasmModule>,
tasks: parking_lot::Mutex<HashMap<u64, mpsc::Receiver<Vec<u8>>>>,
counter: AtomicU64,
scheduler: Box<dyn sp_core::traits::SpawnNamed>,
}
impl RuntimeSpawn for RuntimeInstanceSpawn {
fn spawn_call(&self, dispatcher_ref: u32, func: u32, data: Vec<u8>) -> u64 {
let new_handle = self.counter.fetch_add(1, Ordering::Relaxed);
let (sender, receiver) = mpsc::channel();
self.tasks.lock().insert(new_handle, receiver);
let module = self.module.clone();
let scheduler = self.scheduler.clone();
self.scheduler.spawn("executor-extra-runtime-instance", Box::pin(async move {
let module = AssertUnwindSafe(module);
let async_ext = match new_async_externalities(scheduler.clone()) {
Ok(val) => val,
Err(e) => {
log::error!(
target: "executor",
"Failed to setup externalities for async context: {}",
e,
);
return;
}
};
let mut async_ext = match async_ext.with_runtime_spawn(
Box::new(RuntimeInstanceSpawn::new(module.clone(), scheduler))
) {
Ok(val) => val,
Err(e) => {
log::error!(
target: "executor",
"Failed to setup runtime extension for async externalities: {}",
e,
);
return;
}
};
let result = with_externalities_safe(
&mut async_ext,
move || {
let instance = module.new_instance()
.expect("Failed to create new instance from module");
instance.call(
InvokeMethod::TableWithWrapper { dispatcher_ref, func },
&data[..],
).expect("Failed to invoke instance.")
}
);
match result {
Ok(output) => {
let _ = sender.send(output);
},
Err(error) => {
log::error!("Call error in spawned task: {:?}", error);
},
}
}));
new_handle
}
fn join(&self, handle: u64) -> Vec<u8> {
let receiver = self.tasks.lock().remove(&handle).expect("No task for the handle");
let output = receiver.recv().expect("Spawned task panicked for the handle");
output
}
}
impl RuntimeInstanceSpawn {
pub fn new(
module: Arc<dyn WasmModule>,
scheduler: Box<dyn sp_core::traits::SpawnNamed>,
) -> Self {
Self {
module,
scheduler,
counter: 0.into(),
tasks: HashMap::new().into(),
}
}
fn with_externalities_and_module(
module: Arc<dyn WasmModule>,
mut ext: &mut dyn Externalities,
) -> Option<Self> {
ext.extension::<sp_core::traits::TaskExecutorExt>()
.map(move |task_ext| Self::new(module, task_ext.clone()))
}
pub fn register_on_externalities(module: Arc<dyn WasmModule>) {
sp_externalities::with_externalities(
move |mut ext| {
if let Some(runtime_spawn) =
Self::with_externalities_and_module(module.clone(), ext)
{
if let Err(e) = ext.register_extension(
RuntimeSpawnExt(Box::new(runtime_spawn))
) {
trace!(
target: "executor",
"Failed to register `RuntimeSpawnExt` instance on externalities: {:?}",
e,
)
}
}
}
);
}
}
impl<D: NativeExecutionDispatch + 'static> CodeExecutor for NativeExecutor<D> {
type Error = Error;
fn call<
R: Decode + Encode + PartialEq,
NC: FnOnce() -> result::Result<R, String> + UnwindSafe,
>(
&self,
ext: &mut dyn Externalities,
runtime_code: &RuntimeCode,
method: &str,
data: &[u8],
use_native: bool,
native_call: Option<NC>,
) -> (Result<NativeOrEncoded<R>>, bool) {
let mut used_native = false;
let result = self.wasm.with_instance(
runtime_code,
ext,
false,
|module, instance, onchain_version, mut ext| {
let onchain_version = onchain_version.ok_or_else(
|| Error::ApiError("Unknown version".into())
)?;
let can_call_with = onchain_version.can_call_with(&self.native_version.runtime_version);
match (
use_native,
can_call_with,
native_call,
) {
(_, false, _) | (false, _, _) => {
if !can_call_with {
trace!(
target: "executor",
"Request for native execution failed (native: {}, chain: {})",
self.native_version.runtime_version,
onchain_version,
);
}
with_externalities_safe(
&mut **ext,
move || {
RuntimeInstanceSpawn::register_on_externalities(module.clone());
instance.call_export(method, data).map(NativeOrEncoded::Encoded)
}
)
},
(true, true, Some(call)) => {
trace!(
target: "executor",
"Request for native execution with native call succeeded \
(native: {}, chain: {}).",
self.native_version.runtime_version,
onchain_version,
);
used_native = true;
let res = with_externalities_safe(&mut **ext, move || (call)())
.and_then(|r| r
.map(NativeOrEncoded::Native)
.map_err(|s| Error::ApiError(s))
);
Ok(res)
}
_ => {
trace!(
target: "executor",
"Request for native execution succeeded (native: {}, chain: {})",
self.native_version.runtime_version,
onchain_version
);
used_native = true;
Ok(D::dispatch(&mut **ext, method, data).map(NativeOrEncoded::Encoded))
}
}
}
);
(result, used_native)
}
}
impl<D: NativeExecutionDispatch> Clone for NativeExecutor<D> {
fn clone(&self) -> Self {
NativeExecutor {
_dummy: Default::default(),
native_version: D::native_version(),
wasm: self.wasm.clone(),
}
}
}
impl<D: NativeExecutionDispatch> sp_core::traits::CallInWasm for NativeExecutor<D> {
fn call_in_wasm(
&self,
wasm_blob: &[u8],
code_hash: Option<Vec<u8>>,
method: &str,
call_data: &[u8],
ext: &mut dyn Externalities,
missing_host_functions: MissingHostFunctions,
) -> std::result::Result<Vec<u8>, String> {
self.wasm.call_in_wasm(wasm_blob, code_hash, method, call_data, ext, missing_host_functions)
}
}
#[macro_export]
macro_rules! native_executor_instance {
( $pub:vis $name:ident, $dispatcher:path, $version:path $(,)?) => {
$pub struct $name;
$crate::native_executor_instance!(IMPL $name, $dispatcher, $version, ());
};
( $pub:vis $name:ident, $dispatcher:path, $version:path, $custom_host_functions:ty $(,)?) => {
$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 dyn $crate::Externalities,
method: &str,
data: &[u8]
) -> $crate::error::Result<Vec<u8>> {
$crate::with_externalities_safe(ext, move || $dispatcher(method, data))?
.ok_or_else(|| $crate::error::Error::MethodNotFound(method.to_owned()))
}
fn native_version() -> $crate::NativeVersion {
$version()
}
}
}
}
#[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);
}
}
native_executor_instance!(
pub MyExecutor,
substrate_test_runtime::api::dispatch,
substrate_test_runtime::native_version,
(my_interface::HostFunctions, my_interface::HostFunctions),
);
#[test]
fn native_executor_registers_custom_interface() {
let executor = NativeExecutor::<MyExecutor>::new(
WasmExecutionMethod::Interpreted,
None,
8,
);
my_interface::HostFunctions::host_functions().iter().for_each(|function| {
assert_eq!(
executor.wasm.host_functions.iter().filter(|f| f == &function).count(),
2,
);
});
my_interface::say_hello_world("hey");
}
}