diff --git a/substrate/client/executor/runtime-test/src/lib.rs b/substrate/client/executor/runtime-test/src/lib.rs index c49b9e70b4f85711830f3e6ecec839e67949efb7..a8d329cdd9607a05a0d00cc3da2685970a0115f5 100644 --- a/substrate/client/executor/runtime-test/src/lib.rs +++ b/substrate/client/executor/runtime-test/src/lib.rs @@ -18,7 +18,23 @@ use sp_runtime::{print, traits::{BlakeTwo256, Hash}}; #[cfg(not(feature = "std"))] use sp_core::{ed25519, sr25519}; +extern "C" { + #[allow(dead_code)] + fn missing_external(); + + #[allow(dead_code)] + fn yet_another_missing_external(); +} + sp_core::wasm_export_functions! { + fn test_calling_missing_external() { + unsafe { missing_external() } + } + + fn test_calling_yet_another_missing_external() { + unsafe { yet_another_missing_external() } + } + fn test_data_in(input: Vec<u8>) -> Vec<u8> { print("set_storage"); storage::set(b"input", &input); diff --git a/substrate/client/executor/src/integration_tests/mod.rs b/substrate/client/executor/src/integration_tests/mod.rs index aefb52c7411bdaf77c7c5f38d8a83a1d7a1d5b11..2d39cac4145450425b172b7a6508d97fd128699b 100644 --- a/substrate/client/executor/src/integration_tests/mod.rs +++ b/substrate/client/executor/src/integration_tests/mod.rs @@ -32,6 +32,49 @@ use crate::WasmExecutionMethod; pub type TestExternalities = CoreTestExternalities<Blake2Hasher, u64>; +#[cfg(feature = "wasmtime")] +mod wasmtime_missing_externals { + use sp_wasm_interface::{Function, FunctionContext, HostFunctions, Result, Signature, Value}; + + pub struct WasmtimeHostFunctions; + + impl HostFunctions for WasmtimeHostFunctions { + fn host_functions() -> Vec<&'static dyn Function> { + vec![MISSING_EXTERNAL_FUNCTION, YET_ANOTHER_MISSING_EXTERNAL_FUNCTION] + } + } + + struct MissingExternalFunction(&'static str); + + impl Function for MissingExternalFunction { + fn name(&self) -> &str { self.0 } + + fn signature(&self) -> Signature { + Signature::new(vec![], None) + } + + fn execute( + &self, + _context: &mut dyn FunctionContext, + _args: &mut dyn Iterator<Item = Value>, + ) -> Result<Option<Value>> { + panic!("should not be called"); + } + } + + static MISSING_EXTERNAL_FUNCTION: &'static MissingExternalFunction = + &MissingExternalFunction("missing_external"); + static YET_ANOTHER_MISSING_EXTERNAL_FUNCTION: &'static MissingExternalFunction = + &MissingExternalFunction("yet_another_missing_external"); +} + +#[cfg(feature = "wasmtime")] +type HostFunctions = + (wasmtime_missing_externals::WasmtimeHostFunctions, sp_io::SubstrateHostFunctions); + +#[cfg(not(feature = "wasmtime"))] +type HostFunctions = sp_io::SubstrateHostFunctions; + fn call_in_wasm<E: Externalities>( function: &str, call_data: &[u8], @@ -40,13 +83,14 @@ fn call_in_wasm<E: Externalities>( code: &[u8], heap_pages: u64, ) -> crate::error::Result<Vec<u8>> { - crate::call_in_wasm::<E, sp_io::SubstrateHostFunctions>( + crate::call_in_wasm::<E, HostFunctions>( function, call_data, execution_method, ext, code, heap_pages, + true, ) } @@ -68,6 +112,44 @@ fn returning_should_work(wasm_method: WasmExecutionMethod) { assert_eq!(output, vec![0u8; 0]); } +#[test_case(WasmExecutionMethod::Interpreted)] +#[cfg_attr(feature = "wasmtime", test_case(WasmExecutionMethod::Compiled))] +#[should_panic(expected = "Function `missing_external` is only a stub. Calling a stub is not allowed.")] +#[cfg(not(feature = "wasmtime"))] +fn call_not_existing_function(wasm_method: WasmExecutionMethod) { + let mut ext = TestExternalities::default(); + let mut ext = ext.ext(); + let test_code = WASM_BINARY; + + call_in_wasm( + "test_calling_missing_external", + &[], + wasm_method, + &mut ext, + &test_code[..], + 8, + ).unwrap(); +} + +#[test_case(WasmExecutionMethod::Interpreted)] +#[cfg_attr(feature = "wasmtime", test_case(WasmExecutionMethod::Compiled))] +#[should_panic(expected = "Function `yet_another_missing_external` is only a stub. Calling a stub is not allowed.")] +#[cfg(not(feature = "wasmtime"))] +fn call_yet_another_not_existing_function(wasm_method: WasmExecutionMethod) { + let mut ext = TestExternalities::default(); + let mut ext = ext.ext(); + let test_code = WASM_BINARY; + + call_in_wasm( + "test_calling_yet_another_missing_external", + &[], + wasm_method, + &mut ext, + &test_code[..], + 8, + ).unwrap(); +} + #[test_case(WasmExecutionMethod::Interpreted)] #[cfg_attr(feature = "wasmtime", test_case(WasmExecutionMethod::Compiled))] fn panicking_should_work(wasm_method: WasmExecutionMethod) { diff --git a/substrate/client/executor/src/lib.rs b/substrate/client/executor/src/lib.rs index 78586e0fdc5cf2ed9126640ce9b56762c37e2427..1908eb3688ea357630740058ba4b574988b1aaec 100644 --- a/substrate/client/executor/src/lib.rs +++ b/substrate/client/executor/src/lib.rs @@ -67,12 +67,14 @@ pub fn call_in_wasm<E: Externalities, HF: sp_wasm_interface::HostFunctions>( ext: &mut E, code: &[u8], heap_pages: u64, + allow_missing_imports: bool, ) -> error::Result<Vec<u8>> { let mut instance = wasm_runtime::create_wasm_runtime_with_code( execution_method, heap_pages, code, HF::host_functions(), + allow_missing_imports, )?; instance.call(ext, function, call_data) } @@ -103,6 +105,7 @@ mod tests { &mut ext, &WASM_BINARY, 8, + true, ).unwrap(); assert_eq!(res, vec![0u8; 0]); } diff --git a/substrate/client/executor/src/wasm_runtime.rs b/substrate/client/executor/src/wasm_runtime.rs index cec7672b0286ef0d50bf8c62899c8b88f231c1b3..eef73097f6e7bbcfb6716175f07d16893fe706b6 100644 --- a/substrate/client/executor/src/wasm_runtime.rs +++ b/substrate/client/executor/src/wasm_runtime.rs @@ -191,10 +191,11 @@ pub fn create_wasm_runtime_with_code( heap_pages: u64, code: &[u8], host_functions: Vec<&'static dyn Function>, + allow_missing_imports: bool, ) -> Result<Box<dyn WasmRuntime>, WasmError> { match wasm_method { WasmExecutionMethod::Interpreted => - sc_executor_wasmi::create_instance(code, heap_pages, host_functions) + sc_executor_wasmi::create_instance(code, heap_pages, host_functions, allow_missing_imports) .map(|runtime| -> Box<dyn WasmRuntime> { Box::new(runtime) }), #[cfg(feature = "wasmtime")] WasmExecutionMethod::Compiled => @@ -212,7 +213,7 @@ fn create_versioned_wasm_runtime<E: Externalities>( let code = ext .original_storage(well_known_keys::CODE) .ok_or(WasmError::CodeNotFound)?; - let mut runtime = create_wasm_runtime_with_code(wasm_method, heap_pages, &code, host_functions)?; + let mut runtime = create_wasm_runtime_with_code(wasm_method, heap_pages, &code, host_functions, false)?; // Call to determine runtime version. let version_result = { diff --git a/substrate/client/executor/wasmi/src/lib.rs b/substrate/client/executor/wasmi/src/lib.rs index eebc49f75b29bb54cf4465bd29770bc705354b27..7c6141d6c835f9f8b3378cbab502e73e206a9b82 100644 --- a/substrate/client/executor/wasmi/src/lib.rs +++ b/substrate/client/executor/wasmi/src/lib.rs @@ -21,7 +21,7 @@ use sc_executor_common::{ sandbox, allocator, }; -use std::{str, mem}; +use std::{str, mem, cell::RefCell}; use wasmi::{ Module, ModuleInstance, MemoryInstance, MemoryRef, TableRef, ImportsBuilder, ModuleRef, memory_units::Pages, RuntimeValue::{I32, I64, self}, @@ -42,6 +42,8 @@ struct FunctionExecutor<'a> { memory: MemoryRef, table: Option<TableRef>, host_functions: &'a [&'static dyn Function], + allow_missing_imports: bool, + missing_functions: &'a [String], } impl<'a> FunctionExecutor<'a> { @@ -50,6 +52,8 @@ impl<'a> FunctionExecutor<'a> { heap_base: u32, t: Option<TableRef>, host_functions: &'a [&'static dyn Function], + allow_missing_imports: bool, + missing_functions: &'a [String], ) -> Result<Self, Error> { Ok(FunctionExecutor { sandbox_store: sandbox::Store::new(), @@ -57,6 +61,8 @@ impl<'a> FunctionExecutor<'a> { memory: m, table: t, host_functions, + allow_missing_imports, + missing_functions, }) } } @@ -269,14 +275,28 @@ impl<'a> Sandbox for FunctionExecutor<'a> { } } -struct Resolver<'a>(&'a[&'static dyn Function]); +struct Resolver<'a> { + host_functions: &'a[&'static dyn Function], + allow_missing_imports: bool, + missing_functions: RefCell<Vec<String>>, +} + +impl<'a> Resolver<'a> { + fn new(host_functions: &'a[&'static dyn Function], allow_missing_imports: bool) -> Resolver<'a> { + Resolver { + host_functions, + allow_missing_imports, + missing_functions: RefCell::new(Vec::new()), + } + } +} impl<'a> wasmi::ModuleImportResolver for Resolver<'a> { fn resolve_func(&self, name: &str, signature: &wasmi::Signature) -> std::result::Result<wasmi::FuncRef, wasmi::Error> { let signature = sp_wasm_interface::Signature::from(signature); - for (function_index, function) in self.0.iter().enumerate() { + for (function_index, function) in self.host_functions.iter().enumerate() { if name == function.name() { if signature == function.signature() { return Ok( @@ -295,9 +315,17 @@ impl<'a> wasmi::ModuleImportResolver for Resolver<'a> { } } - Err(wasmi::Error::Instantiation( - format!("Export {} not found", name), - )) + if self.allow_missing_imports { + trace!(target: "wasm-executor", "Could not find function `{}`, a stub will be provided instead.", name); + let id = self.missing_functions.borrow().len() + self.host_functions.len(); + self.missing_functions.borrow_mut().push(name.to_string()); + + Ok(wasmi::FuncInstance::alloc_host(signature.into(), id)) + } else { + Err(wasmi::Error::Instantiation( + format!("Export {} not found", name), + )) + } } } @@ -306,16 +334,23 @@ impl<'a> wasmi::Externals for FunctionExecutor<'a> { -> Result<Option<wasmi::RuntimeValue>, wasmi::Trap> { let mut args = args.as_ref().iter().copied().map(Into::into); - let function = self.host_functions.get(index).ok_or_else(|| - Error::from( - format!("Could not find host function with index: {}", index), - ) - )?; - - function.execute(self, &mut args) - .map_err(|msg| Error::FunctionExecution(function.name().to_string(), msg)) - .map_err(wasmi::Trap::from) - .map(|v| v.map(Into::into)) + + if let Some(function) = self.host_functions.get(index) { + function.execute(self, &mut args) + .map_err(|msg| Error::FunctionExecution(function.name().to_string(), msg)) + .map_err(wasmi::Trap::from) + .map(|v| v.map(Into::into)) + } else if self.allow_missing_imports + && index >= self.host_functions.len() + && index < self.host_functions.len() + self.missing_functions.len() + { + Err(Error::from(format!( + "Function `{}` is only a stub. Calling a stub is not allowed.", + self.missing_functions[index - self.host_functions.len()], + )).into()) + } else { + Err(Error::from(format!("Could not find host function with index: {}", index)).into()) + } } } @@ -351,6 +386,8 @@ fn call_in_wasm_module( method: &str, data: &[u8], host_functions: &[&'static dyn Function], + allow_missing_imports: bool, + missing_functions: &Vec<String>, ) -> Result<Vec<u8>, Error> { // extract a reference to a linear memory, optional reference to a table // and then initialize FunctionExecutor. @@ -360,7 +397,14 @@ fn call_in_wasm_module( .and_then(|e| e.as_table().cloned()); let heap_base = get_heap_base(module_instance)?; - let mut fec = FunctionExecutor::new(memory.clone(), heap_base, table, host_functions)?; + let mut fec = FunctionExecutor::new( + memory.clone(), + heap_base, + table, + host_functions, + allow_missing_imports, + missing_functions, + )?; // Write the call data let offset = fec.allocate_memory(data.len() as u32)?; @@ -397,8 +441,9 @@ fn instantiate_module( heap_pages: usize, module: &Module, host_functions: &[&'static dyn Function], -) -> Result<ModuleRef, Error> { - let resolver = Resolver(host_functions); + allow_missing_imports: bool, +) -> Result<(ModuleRef, Vec<String>), Error> { + let resolver = Resolver::new(host_functions, allow_missing_imports); // start module instantiation. Don't run 'start' function yet. let intermediate_instance = ModuleInstance::new( module, @@ -416,7 +461,7 @@ fn instantiate_module( // Runtime is not allowed to have the `start` function. Err(Error::RuntimeHasStartFn) } else { - Ok(intermediate_instance.assert_no_start()) + Ok((intermediate_instance.assert_no_start(), resolver.missing_functions.into_inner())) } } @@ -536,6 +581,11 @@ pub struct WasmiRuntime { state_snapshot: StateSnapshot, /// The host functions registered for this instance. host_functions: Vec<&'static dyn Function>, + /// Enable stub generation for functions that are not available in `host_functions`. + /// These stubs will error when the wasm blob tries to call them. + allow_missing_imports: bool, + /// List of missing functions detected during function resolution + missing_functions: Vec<String>, } impl WasmRuntime for WasmiRuntime { @@ -561,7 +611,15 @@ impl WasmRuntime for WasmiRuntime { error!(target: "wasm-executor", "snapshot restoration failed: {}", e); e })?; - call_in_wasm_module(ext, &self.instance, method, data, &self.host_functions) + call_in_wasm_module( + ext, + &self.instance, + method, + data, + &self.host_functions, + self.allow_missing_imports, + &self.missing_functions, + ) } } @@ -569,6 +627,7 @@ pub fn create_instance( code: &[u8], heap_pages: u64, host_functions: Vec<&'static dyn Function>, + allow_missing_imports: bool, ) -> Result<WasmiRuntime, WasmError> { let module = Module::from_buffer(&code).map_err(|_| WasmError::InvalidModule)?; @@ -579,8 +638,12 @@ pub fn create_instance( let data_segments = extract_data_segments(&code)?; // Instantiate this module. - let instance = instantiate_module(heap_pages as usize, &module, &host_functions) - .map_err(|e| WasmError::Instantiation(e.to_string()))?; + let (instance, missing_functions) = instantiate_module( + heap_pages as usize, + &module, + &host_functions, + allow_missing_imports, + ).map_err(|e| WasmError::Instantiation(e.to_string()))?; // Take state snapshot before executing anything. let state_snapshot = StateSnapshot::take(&instance, data_segments, heap_pages) @@ -595,6 +658,8 @@ pub fn create_instance( instance, state_snapshot, host_functions, + allow_missing_imports, + missing_functions, }) } diff --git a/substrate/primitives/runtime-interface/test/src/lib.rs b/substrate/primitives/runtime-interface/test/src/lib.rs index 683a7af2976f1cdd63125b29704b44c4e6cf3a93..35a93e21365ebf6aa271d7baf0fa07213b10f9f2 100644 --- a/substrate/primitives/runtime-interface/test/src/lib.rs +++ b/substrate/primitives/runtime-interface/test/src/lib.rs @@ -41,6 +41,7 @@ fn call_wasm_method<HF: HostFunctionsT>(method: &str) -> TestExternalities { &mut ext_ext, &WASM_BINARY[..], 8, + false, ).expect(&format!("Executes `{}`", method)); ext