diff --git a/substrate/Cargo.lock b/substrate/Cargo.lock index 865dc1c2cf..515c2e01ef 100644 --- a/substrate/Cargo.lock +++ b/substrate/Cargo.lock @@ -3620,7 +3620,7 @@ dependencies = [ "sr-std 2.0.0", "substrate-primitives 2.0.0", "wabt 0.7.4 (registry+https://github.com/rust-lang/crates.io-index)", - "wasmi 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)", + "wasmi 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -4470,6 +4470,7 @@ dependencies = [ "libsecp256k1 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", "parity-codec 4.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "parity-wasm 0.31.3 (registry+https://github.com/rust-lang/crates.io-index)", "parking_lot 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", "sr-io 2.0.0", "sr-version 2.0.0", @@ -4483,7 +4484,7 @@ dependencies = [ "substrate-trie 2.0.0", "tiny-keccak 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)", "wabt 0.7.4 (registry+https://github.com/rust-lang/crates.io-index)", - "wasmi 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)", + "wasmi 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -4690,7 +4691,7 @@ dependencies = [ "substrate-serializer 2.0.0", "tiny-bip39 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", "twox-hash 1.4.2 (registry+https://github.com/rust-lang/crates.io-index)", - "wasmi 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)", + "wasmi 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", "zeroize 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -4905,6 +4906,7 @@ dependencies = [ "substrate-keyring 2.0.0", "substrate-offchain-primitives 2.0.0", "substrate-primitives 2.0.0", + "substrate-state-machine 2.0.0", "substrate-test-runtime-client 2.0.0", "substrate-trie 2.0.0", "substrate-wasm-builder-runner 1.0.2", @@ -5731,10 +5733,13 @@ dependencies = [ [[package]] name = "wasmi" -version = "0.4.5" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ + "libc 0.2.59 (registry+https://github.com/rust-lang/crates.io-index)", "memory_units 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "num-rational 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", "parity-wasm 0.31.3 (registry+https://github.com/rust-lang/crates.io-index)", "wasmi-validation 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -6387,7 +6392,7 @@ dependencies = [ "checksum wasm-bindgen-shared 0.2.47 (registry+https://github.com/rust-lang/crates.io-index)" = "15de16ddb30cfd424a87598b30021491bae1607d32e52056979865c98b7913b4" "checksum wasm-bindgen-webidl 0.2.47 (registry+https://github.com/rust-lang/crates.io-index)" = "21724123084234fff2f986018b790afc5d6f45c9a3903025e6c55d0068cb7d15" "checksum wasm-timer 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "3d6101df9a5987df809216bdda7289f52b58128e6b6a6546e9ee3e6b632b4921" -"checksum wasmi 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)" = "aebbaef470840d157a5c47c8c49f024da7b1b80e90ff729ca982b2b80447e78b" +"checksum wasmi 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "48437c526d40a6a593c50c5367dac825b8d6a04411013e866eca66123fb56faa" "checksum wasmi-validation 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ab380192444b3e8522ae79c0a1976e42a82920916ccdfbce3def89f456ea33f3" "checksum web-sys 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)" = "22306ce642c58266cb5c5938150194911322bc179aa895146076217410ddbc82" "checksum webpki 0.19.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4f7e1cd7900a3a6b65a3e8780c51a3e6b59c0e2c55c6dc69578c288d69f7d082" diff --git a/substrate/core/client/src/genesis.rs b/substrate/core/client/src/genesis.rs index 3451e1e9b1..de2563045e 100644 --- a/substrate/core/client/src/genesis.rs +++ b/substrate/core/client/src/genesis.rs @@ -149,7 +149,8 @@ mod tests { let mut storage = GenesisConfig::new(false, vec![Sr25519Keyring::One.into(), Sr25519Keyring::Two.into()], vec![AccountKeyring::One.into(), AccountKeyring::Two.into()], - 1000 + 1000, + None, ).genesis_map(); let state_root = BlakeTwo256::trie_root(storage.clone().into_iter()); let block = construct_genesis_block::(state_root); @@ -178,7 +179,8 @@ mod tests { let mut storage = GenesisConfig::new(false, vec![Sr25519Keyring::One.into(), Sr25519Keyring::Two.into()], vec![AccountKeyring::One.into(), AccountKeyring::Two.into()], - 1000 + 1000, + None, ).genesis_map(); let state_root = BlakeTwo256::trie_root(storage.clone().into_iter()); let block = construct_genesis_block::(state_root); @@ -207,7 +209,8 @@ mod tests { let mut storage = GenesisConfig::new(false, vec![Sr25519Keyring::One.into(), Sr25519Keyring::Two.into()], vec![AccountKeyring::One.into(), AccountKeyring::Two.into()], - 68 + 68, + None, ).genesis_map(); let state_root = BlakeTwo256::trie_root(storage.clone().into_iter()); let block = construct_genesis_block::(state_root); diff --git a/substrate/core/executor/Cargo.toml b/substrate/core/executor/Cargo.toml index 50afe391db..23205e1e40 100644 --- a/substrate/core/executor/Cargo.toml +++ b/substrate/core/executor/Cargo.toml @@ -14,7 +14,8 @@ serializer = { package = "substrate-serializer", path = "../serializer" } state_machine = { package = "substrate-state-machine", path = "../state-machine" } runtime_version = { package = "sr-version", path = "../sr-version" } panic-handler = { package = "substrate-panic-handler", path = "../panic-handler" } -wasmi = { version = "0.4.3" } +wasmi = "0.5.0" +parity-wasm = "0.31" byteorder = "1.3" lazy_static = "1.3" parking_lot = "0.8.0" diff --git a/substrate/core/executor/runtime-test/build.rs b/substrate/core/executor/runtime-test/build.rs index a8a5e1cba5..86bc3ad7fa 100644 --- a/substrate/core/executor/runtime-test/build.rs +++ b/substrate/core/executor/runtime-test/build.rs @@ -14,14 +14,17 @@ // You should have received a copy of the GNU General Public License // along with Substrate. If not, see . -use wasm_builder_runner::{build_current_project, WasmBuilderSource}; +use wasm_builder_runner::{build_current_project_with_rustflags, WasmBuilderSource}; fn main() { - build_current_project( + build_current_project_with_rustflags( "wasm_binary.rs", WasmBuilderSource::CratesOrPath { path: "../../utils/wasm-builder", version: "1.0.4", }, + // This instructs LLD to export __heap_base as a global variable, which is used by the + // external memory allocator. + "-Clink-arg=--export=__heap_base", ); } diff --git a/substrate/core/executor/src/allocator.rs b/substrate/core/executor/src/allocator.rs index 504b96987f..4cc8174f70 100644 --- a/substrate/core/executor/src/allocator.rs +++ b/substrate/core/executor/src/allocator.rs @@ -53,22 +53,13 @@ impl FreeingBumpHeapAllocator { /// /// # Arguments /// - /// * `ptr_offset` - The pointers returned by `allocate()` start from this - /// offset on. The pointer offset needs to be aligned to a multiple of 8, - /// hence a padding might be added to align `ptr_offset` properly. - /// - /// * `heap_size` - The size available to this heap instance (in bytes) for - /// allocating memory. - /// - /// * `heap` - A `MemoryRef` to the available `MemoryInstance` which is - /// used as the heap. - /// - pub fn new(mem: MemoryRef) -> Self { + /// - `mem` - reference to the linear memory instance on which this allocator operates. + /// - `heap_base` - the offset from the beginning of the linear memory where the heap starts. + pub fn new(mem: MemoryRef, heap_base: u32) -> Self { let current_size: Bytes = mem.current_size().into(); let current_size = current_size.0 as u32; - let used_size = mem.used_size().0 as u32; - let mut ptr_offset = used_size; + let mut ptr_offset = heap_base; let padding = ptr_offset % ALIGNMENT; if padding != 0 { ptr_offset += ALIGNMENT - padding; @@ -195,16 +186,11 @@ mod tests { const PAGE_SIZE: u32 = 65536; - fn set_offset(mem: MemoryRef, offset: usize) { - let offset: Vec = vec![255; offset]; - mem.set(0, &offset).unwrap(); - } - #[test] fn should_allocate_properly() { // given let mem = MemoryInstance::alloc(Pages(1), None).unwrap(); - let mut heap = FreeingBumpHeapAllocator::new(mem); + let mut heap = FreeingBumpHeapAllocator::new(mem, 0); // when let ptr = heap.allocate(1).unwrap(); @@ -217,8 +203,7 @@ mod tests { fn should_always_align_pointers_to_multiples_of_8() { // given let mem = MemoryInstance::alloc(Pages(1), None).unwrap(); - set_offset(mem.clone(), 13); - let mut heap = FreeingBumpHeapAllocator::new(mem); + let mut heap = FreeingBumpHeapAllocator::new(mem, 13); // when let ptr = heap.allocate(1).unwrap(); @@ -233,7 +218,7 @@ mod tests { fn should_increment_pointers_properly() { // given let mem = MemoryInstance::alloc(Pages(1), None).unwrap(); - let mut heap = FreeingBumpHeapAllocator::new(mem); + let mut heap = FreeingBumpHeapAllocator::new(mem, 0); // when let ptr1 = heap.allocate(1).unwrap(); @@ -256,7 +241,7 @@ mod tests { fn should_free_properly() { // given let mem = MemoryInstance::alloc(Pages(1), None).unwrap(); - let mut heap = FreeingBumpHeapAllocator::new(mem); + let mut heap = FreeingBumpHeapAllocator::new(mem, 0); let ptr1 = heap.allocate(1).unwrap(); // the prefix of 8 bytes is prepended to the pointer assert_eq!(ptr1, 8); @@ -278,9 +263,8 @@ mod tests { fn should_deallocate_and_reallocate_properly() { // given let mem = MemoryInstance::alloc(Pages(1), None).unwrap(); - set_offset(mem.clone(), 13); let padded_offset = 16; - let mut heap = FreeingBumpHeapAllocator::new(mem); + let mut heap = FreeingBumpHeapAllocator::new(mem, 13); let ptr1 = heap.allocate(1).unwrap(); // the prefix of 8 bytes is prepended to the pointer @@ -306,7 +290,7 @@ mod tests { fn should_build_linked_list_of_free_areas_properly() { // given let mem = MemoryInstance::alloc(Pages(1), None).unwrap(); - let mut heap = FreeingBumpHeapAllocator::new(mem); + let mut heap = FreeingBumpHeapAllocator::new(mem, 0); let ptr1 = heap.allocate(8).unwrap(); let ptr2 = heap.allocate(8).unwrap(); @@ -333,8 +317,7 @@ mod tests { fn should_not_allocate_if_too_large() { // given let mem = MemoryInstance::alloc(Pages(1), Some(Pages(1))).unwrap(); - set_offset(mem.clone(), 13); - let mut heap = FreeingBumpHeapAllocator::new(mem); + let mut heap = FreeingBumpHeapAllocator::new(mem, 13); // when let ptr = heap.allocate(PAGE_SIZE - 13); @@ -353,7 +336,7 @@ mod tests { fn should_not_allocate_if_full() { // given let mem = MemoryInstance::alloc(Pages(1), Some(Pages(1))).unwrap(); - let mut heap = FreeingBumpHeapAllocator::new(mem); + let mut heap = FreeingBumpHeapAllocator::new(mem, 0); let ptr1 = heap.allocate((PAGE_SIZE / 2) - 8).unwrap(); assert_eq!(ptr1, 8); @@ -376,7 +359,7 @@ mod tests { // given let pages_needed = (MAX_POSSIBLE_ALLOCATION as usize / PAGE_SIZE as usize) + 1; let mem = MemoryInstance::alloc(Pages(pages_needed), Some(Pages(pages_needed))).unwrap(); - let mut heap = FreeingBumpHeapAllocator::new(mem); + let mut heap = FreeingBumpHeapAllocator::new(mem, 0); // when let ptr = heap.allocate(MAX_POSSIBLE_ALLOCATION).unwrap(); @@ -389,7 +372,7 @@ mod tests { fn should_not_allocate_if_requested_size_too_large() { // given let mem = MemoryInstance::alloc(Pages(1), None).unwrap(); - let mut heap = FreeingBumpHeapAllocator::new(mem); + let mut heap = FreeingBumpHeapAllocator::new(mem, 0); // when let ptr = heap.allocate(MAX_POSSIBLE_ALLOCATION + 1); @@ -408,8 +391,7 @@ mod tests { fn should_include_prefixes_in_total_heap_size() { // given let mem = MemoryInstance::alloc(Pages(1), None).unwrap(); - set_offset(mem.clone(), 1); - let mut heap = FreeingBumpHeapAllocator::new(mem); + let mut heap = FreeingBumpHeapAllocator::new(mem, 1); // when // an item size of 16 must be used then @@ -423,8 +405,7 @@ mod tests { fn should_calculate_total_heap_size_to_zero() { // given let mem = MemoryInstance::alloc(Pages(1), None).unwrap(); - set_offset(mem.clone(), 13); - let mut heap = FreeingBumpHeapAllocator::new(mem); + let mut heap = FreeingBumpHeapAllocator::new(mem, 13); // when let ptr = heap.allocate(42).unwrap(); @@ -439,8 +420,7 @@ mod tests { fn should_calculate_total_size_of_zero() { // given let mem = MemoryInstance::alloc(Pages(1), None).unwrap(); - set_offset(mem.clone(), 19); - let mut heap = FreeingBumpHeapAllocator::new(mem); + let mut heap = FreeingBumpHeapAllocator::new(mem, 19); // when for _ in 1..10 { diff --git a/substrate/core/executor/src/error.rs b/substrate/core/executor/src/error.rs index a81fc1b148..7b8926d8e9 100644 --- a/substrate/core/executor/src/error.rs +++ b/substrate/core/executor/src/error.rs @@ -38,8 +38,8 @@ pub enum Error { #[display(fmt="Method not found: '{}'", _0)] MethodNotFound(String), /// Code is invalid (expected single byte) - #[display(fmt="Invalid Code: {:?}", _0)] - InvalidCode(Vec), + #[display(fmt="Invalid Code")] + InvalidCode, /// Could not get runtime version. #[display(fmt="On-chain runtime does not specify version")] VersionInvalid, @@ -58,6 +58,13 @@ pub enum Error { /// Invalid memory reference. #[display(fmt="Invalid memory reference")] InvalidMemoryReference, + /// The runtime must provide a global named `__heap_base` of type i32 for specifying where the + /// allocator is allowed to place its data. + #[display(fmt="The runtime doesn't provide a global named `__heap_base`")] + HeapBaseNotFoundOrInvalid, + /// The runtime WebAssembly module is not allowed to have the `start` function. + #[display(fmt="The runtime has the `start` function")] + RuntimeHasStartFn, /// Some other error occurred Other(&'static str), /// Some error occurred in the allocator diff --git a/substrate/core/executor/src/lib.rs b/substrate/core/executor/src/lib.rs index fa7cc71eea..a6147a320a 100644 --- a/substrate/core/executor/src/lib.rs +++ b/substrate/core/executor/src/lib.rs @@ -35,11 +35,13 @@ mod wasm_executor; mod native_executor; mod sandbox; mod allocator; +mod wasm_runtimes_cache; pub mod error; pub use wasmi; pub use wasm_executor::WasmExecutor; pub use native_executor::{with_native_environment, NativeExecutor, NativeExecutionDispatch}; +pub use wasm_runtimes_cache::RuntimesCache; pub use state_machine::Externalities; pub use runtime_version::{RuntimeVersion, NativeVersion}; pub use parity_codec::Codec; diff --git a/substrate/core/executor/src/native_executor.rs b/substrate/core/executor/src/native_executor.rs index e4a65c811b..f295671209 100644 --- a/substrate/core/executor/src/native_executor.rs +++ b/substrate/core/executor/src/native_executor.rs @@ -14,86 +14,20 @@ // You should have received a copy of the GNU General Public License // along with Substrate. If not, see . -use std::{borrow::BorrowMut, result, cell::{RefMut, RefCell}}; +use std::{result, cell::RefCell, panic::UnwindSafe}; use crate::error::{Error, Result}; use state_machine::{CodeExecutor, Externalities}; use crate::wasm_executor::WasmExecutor; -use wasmi::{Module as WasmModule, ModuleRef as WasmModuleInstanceRef}; use runtime_version::{NativeVersion, RuntimeVersion}; -use std::{collections::HashMap, panic::UnwindSafe}; use parity_codec::{Decode, Encode}; use crate::RuntimeInfo; use primitives::{Blake2Hasher, NativeOrEncoded}; -use primitives::storage::well_known_keys; use log::trace; -/// Default num of pages for the heap -const DEFAULT_HEAP_PAGES: u64 = 1024; - -// For the internal Runtime Cache: -// Is it compatible enough to run this natively or do we need to fall back on the WasmModule - -enum RuntimePreproc { - InvalidCode, - ValidCode(WasmModuleInstanceRef, Option), -} - -type CacheType = HashMap<[u8; 32], RuntimePreproc>; +use crate::RuntimesCache; thread_local! { - static RUNTIMES_CACHE: RefCell = RefCell::new(HashMap::new()); -} - -/// fetch a runtime version from the cache or if there is no cached version yet, create -/// the runtime version entry for `code`, determines whether `Compatibility::IsCompatible` -/// can be used by comparing returned RuntimeVersion to `ref_version` -fn fetch_cached_runtime_version<'a, E: Externalities>( - wasm_executor: &WasmExecutor, - cache: &'a mut RefMut, - ext: &mut E, - default_heap_pages: Option, -) -> Result<(&'a WasmModuleInstanceRef, &'a Option)> { - let code_hash = match ext.original_storage_hash(well_known_keys::CODE) { - Some(code_hash) => code_hash, - None => return Err(Error::InvalidCode(vec![])), - }; - - let maybe_runtime_preproc = cache.borrow_mut().entry(code_hash.into()) - .or_insert_with(|| { - let code = match ext.original_storage(well_known_keys::CODE) { - Some(code) => code, - None => return RuntimePreproc::InvalidCode, - }; - let heap_pages = ext.storage(well_known_keys::HEAP_PAGES) - .and_then(|pages| u64::decode(&mut &pages[..])) - .or(default_heap_pages) - .unwrap_or(DEFAULT_HEAP_PAGES); - match WasmModule::from_buffer(code) - .map_err(|_| Error::InvalidCode(vec![])) - .and_then(|module| wasm_executor.prepare_module(ext, heap_pages as usize, &module)) - { - Ok(module) => { - let version = wasm_executor.call_in_wasm_module(ext, &module, "Core_version", &[]) - .ok() - .and_then(|v| RuntimeVersion::decode(&mut v.as_slice())); - RuntimePreproc::ValidCode(module, version) - } - Err(e) => { - trace!(target: "executor", "Invalid code presented to executor ({:?})", e); - RuntimePreproc::InvalidCode - } - } - }); - - match maybe_runtime_preproc { - RuntimePreproc::InvalidCode => { - let code = ext.original_storage(well_known_keys::CODE).unwrap_or(vec![]); - Err(Error::InvalidCode(code)) - }, - RuntimePreproc::ValidCode(m, v) => { - Ok((m, v)) - } - } + static RUNTIMES_CACHE: RefCell = RefCell::new(RuntimesCache::new()); } fn safe_call(f: F) -> Result @@ -140,7 +74,7 @@ pub struct NativeExecutor { fallback: WasmExecutor, /// Native runtime version info. native_version: NativeVersion, - /// The default number of 64KB pages to allocate for Wasm execution. + /// The number of 64KB pages to allocate for Wasm execution. default_heap_pages: Option, } @@ -151,7 +85,7 @@ impl NativeExecutor { _dummy: Default::default(), fallback: WasmExecutor::new(), native_version: D::native_version(), - default_heap_pages, + default_heap_pages: default_heap_pages, } } } @@ -176,10 +110,11 @@ impl RuntimeInfo for NativeExecutor { &self, ext: &mut E, ) -> Option { - RUNTIMES_CACHE.with(|c| - fetch_cached_runtime_version(&self.fallback, &mut c.borrow_mut(), ext, self.default_heap_pages) - .ok()?.1.clone() - ) + RUNTIMES_CACHE.with(|cache| { + let cache = &mut cache.borrow_mut(); + cache.fetch_runtime(&self.fallback, ext, self.default_heap_pages) + .ok()?.version().clone() + }) } } @@ -198,14 +133,16 @@ impl CodeExecutor for NativeExecutor, - ) -> (Result>, bool) { - RUNTIMES_CACHE.with(|c| { - let mut c = c.borrow_mut(); - let (module, onchain_version) = match fetch_cached_runtime_version( - &self.fallback, &mut c, ext, self.default_heap_pages) { - Ok((module, onchain_version)) => (module, onchain_version), - Err(e) => return (Err(e), false), + ) -> (Result>, bool){ + RUNTIMES_CACHE.with(|cache| { + let cache = &mut cache.borrow_mut(); + let cached_runtime = match cache.fetch_runtime( + &self.fallback, ext, self.default_heap_pages, + ) { + Ok(cached_runtime) => cached_runtime, + Err(e) => return (Err(e), false), }; + let onchain_version = cached_runtime.version(); match ( use_native, onchain_version @@ -223,17 +160,21 @@ impl CodeExecutor for NativeExecutor".into(), |v| format!("{}", v)) ); ( - self.fallback - .call_in_wasm_module(ext, module, method, data) - .map(NativeOrEncoded::Encoded), + cached_runtime.with(|module| + self.fallback + .call_in_wasm_module(ext, module, method, data) + .map(NativeOrEncoded::Encoded) + ), false ) } (false, _, _) => { ( - self.fallback - .call_in_wasm_module(ext, module, method, data) - .map(NativeOrEncoded::Encoded), + cached_runtime.with(|module| + self.fallback + .call_in_wasm_module(ext, module, method, data) + .map(NativeOrEncoded::Encoded) + ), false ) } diff --git a/substrate/core/executor/src/wasm_executor.rs b/substrate/core/executor/src/wasm_executor.rs index 30d5ccd542..1575447dbb 100644 --- a/substrate/core/executor/src/wasm_executor.rs +++ b/substrate/core/executor/src/wasm_executor.rs @@ -56,10 +56,10 @@ struct FunctionExecutor<'e, E: Externalities + 'e> { } impl<'e, E: Externalities> FunctionExecutor<'e, E> { - fn new(m: MemoryRef, t: Option, e: &'e mut E) -> Result { + fn new(m: MemoryRef, heap_base: u32, t: Option, e: &'e mut E) -> Result { Ok(FunctionExecutor { sandbox_store: sandbox::Store::new(), - heap: allocator::FreeingBumpHeapAllocator::new(m.clone()), + heap: allocator::FreeingBumpHeapAllocator::new(m.clone(), heap_base), memory: m, table: t, ext: e, @@ -1270,7 +1270,7 @@ impl WasmExecutor { data: &[u8], ) -> Result> { let module = ::wasmi::Module::from_buffer(code)?; - let module = self.prepare_module(ext, heap_pages, &module)?; + let module = Self::instantiate_module::(heap_pages, &module)?; self.call_in_wasm_module(ext, &module, method, data) } @@ -1292,7 +1292,7 @@ impl WasmExecutor { filter_result: FR, ) -> Result { let module = wasmi::Module::from_buffer(code)?; - let module = self.prepare_module(ext, heap_pages, &module)?; + let module = Self::instantiate_module::(heap_pages, &module)?; self.call_in_wasm_module_with_custom_signature( ext, &module, @@ -1311,6 +1311,22 @@ impl WasmExecutor { .clone()) } + /// Find the global named `__heap_base` in the given wasm module instance and + /// tries to get its value. + fn get_heap_base(module: &ModuleRef) -> Result { + let heap_base_val = module + .export_by_name("__heap_base") + .ok_or_else(|| Error::HeapBaseNotFoundOrInvalid)? + .as_global() + .ok_or_else(|| Error::HeapBaseNotFoundOrInvalid)? + .get(); + + Ok(match heap_base_val { + wasmi::RuntimeValue::I32(v) => v as u32, + _ => return Err(Error::HeapBaseNotFoundOrInvalid), + }) + } + /// Call a given method in the given wasm-module runtime. pub fn call_in_wasm_module>( &self, @@ -1359,10 +1375,9 @@ impl WasmExecutor { let table: Option = module_instance .export_by_name("__indirect_function_table") .and_then(|e| e.as_table().cloned()); + let heap_base = Self::get_heap_base(module_instance)?; - let low = memory.lowest_used(); - let used_mem = memory.used_size(); - let mut fec = FunctionExecutor::new(memory.clone(), table, ext)?; + let mut fec = FunctionExecutor::new(memory.clone(), heap_base, table, ext)?; let parameters = create_parameters(&mut |data: &[u8]| { let offset = fec.heap.allocate(data.len() as u32)?; memory.set(offset, &data)?; @@ -1385,24 +1400,14 @@ impl WasmExecutor { }, }; - // cleanup module instance for next use - let new_low = memory.lowest_used(); - if new_low < low { - memory.zero(new_low as usize, (low - new_low) as usize)?; - memory.reset_lowest_used(low); - } - memory.with_direct_access_mut(|buf| buf.resize(used_mem.0, 0)); result } /// Prepare module instance - pub fn prepare_module>( - &self, - ext: &mut E, + pub fn instantiate_module>( heap_pages: usize, module: &Module, - ) -> Result - { + ) -> Result { // start module instantiation. Don't run 'start' function yet. let intermediate_instance = ModuleInstance::new( module, @@ -1410,18 +1415,19 @@ impl WasmExecutor { .with_resolver("env", FunctionExecutor::::resolver()) )?; - // extract a reference to a linear memory, optional reference to a table - // and then initialize FunctionExecutor. + // Verify that the module has the heap base global variable. + let _ = Self::get_heap_base(intermediate_instance.not_started_instance())?; + + // Extract a reference to a linear memory. let memory = Self::get_mem_instance(intermediate_instance.not_started_instance())?; memory.grow(Pages(heap_pages)).map_err(|_| Error::Runtime)?; - let table: Option = intermediate_instance - .not_started_instance() - .export_by_name("__indirect_function_table") - .and_then(|e| e.as_table().cloned()); - let mut fec = FunctionExecutor::new(memory.clone(), table, ext)?; - // finish instantiation by running 'start' function (if any). - Ok(intermediate_instance.run_start(&mut fec)?) + if intermediate_instance.has_start() { + // Runtime is not allowed to have the `start` function. + Err(Error::RuntimeHasStartFn) + } else { + Ok(intermediate_instance.assert_no_start()) + } } } diff --git a/substrate/core/executor/src/wasm_runtimes_cache.rs b/substrate/core/executor/src/wasm_runtimes_cache.rs new file mode 100644 index 0000000000..a1540204c7 --- /dev/null +++ b/substrate/core/executor/src/wasm_runtimes_cache.rs @@ -0,0 +1,331 @@ +// Copyright 2019 Parity Technologies (UK) Ltd. +// This file is part of Substrate. + +// Substrate 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. + +// Substrate 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 Substrate. If not, see . + +//! Implements a cache for pre-created Wasm runtime module instances. + +use crate::error::Error; +use crate::wasm_executor::WasmExecutor; +use log::{trace, warn}; +use parity_codec::Decode; +use parity_wasm::elements::{deserialize_buffer, DataSegment, Instruction, Module as RawModule}; +use primitives::storage::well_known_keys; +use primitives::Blake2Hasher; +use runtime_version::RuntimeVersion; +use state_machine::Externalities; +use std::collections::hash_map::{Entry, HashMap}; +use std::mem; +use std::rc::Rc; +use wasmi::{Module as WasmModule, ModuleRef as WasmModuleInstanceRef, RuntimeValue}; + +#[derive(Debug)] +enum CacheError { + CodeNotFound, + ApplySnapshotFailed, + InvalidModule, + CantDeserializeWasm, + Instantiation(Error), +} + +/// A runtime along with its version and initial state snapshot. +#[derive(Clone)] +pub struct CachedRuntime { + /// A wasm module instance. + instance: WasmModuleInstanceRef, + /// Runtime version according to `Core_version`. + /// + /// Can be `None` if the runtime doesn't expose this function. + version: Option, + /// The snapshot of the instance's state taken just after the instantiation. + state_snapshot: StateSnapshot, +} + +impl CachedRuntime { + /// Perform an operation with the clean version of the runtime wasm instance. + pub fn with(&self, f: F) -> R + where + F: FnOnce(&WasmModuleInstanceRef) -> R, + { + self.state_snapshot.apply(&self.instance).expect( + "applying the snapshot can only fail if the passed instance is different + from the one that was used for creation of the snapshot; + we use the snapshot that is directly associated with the instance; + thus the snapshot was created using the instance; + qed", + ); + f(&self.instance) + } + + /// Returns the version of this cached runtime. + /// + /// Returns `None` if the runtime doesn't provide the information or there was an error + /// while fetching it. + pub fn version(&self) -> Option { + self.version.clone() + } +} + +/// A state snapshot of an instance taken just after instantiation. +/// +/// It is used for restoring the state of the module after execution. +#[derive(Clone)] +struct StateSnapshot { + /// The offset and the content of the memory segments that should be used to restore the snapshot + data_segments: Vec<(u32, Vec)>, + /// The list of all global mutable variables of the module in their sequential order. + global_mut_values: Vec, + heap_pages: u32, +} + +impl StateSnapshot { + // Returns `None` if instance is not valid. + fn take( + module_instance: &WasmModuleInstanceRef, + data_segments: Vec, + heap_pages: u32, + ) -> Option { + let prepared_segments = data_segments + .into_iter() + .map(|mut segment| { + // Just replace contents of the segment since the segments will be discarded later + // anyway. + let contents = mem::replace(segment.value_mut(), vec![]); + + let init_expr = segment.offset().code(); + // [op, End] + if init_expr.len() != 2 { + return None; + } + let offset = match init_expr[0] { + Instruction::I32Const(v) => v as u32, + Instruction::GetGlobal(idx) => { + let global_val = module_instance.globals().get(idx as usize)?.get(); + match global_val { + RuntimeValue::I32(v) => v as u32, + _ => return None, + } + } + _ => return None, + }; + + Some((offset, contents)) + }) + .collect::>>()?; + + // Collect all values of mutable globals. + let global_mut_values = module_instance + .globals() + .iter() + .filter(|g| g.is_mutable()) + .map(|g| g.get()) + .collect(); + + Some(Self { + data_segments: prepared_segments, + global_mut_values, + heap_pages, + }) + } + + /// Reset the runtime instance to the initial version by restoring + /// the preserved memory and globals. + /// + /// Returns `Err` if applying the snapshot is failed. + fn apply(&self, instance: &WasmModuleInstanceRef) -> Result<(), CacheError> { + let memory = instance + .export_by_name("memory") + .ok_or(CacheError::ApplySnapshotFailed)? + .as_memory() + .cloned() + .ok_or(CacheError::ApplySnapshotFailed)?; + + // First, erase the memory and copy the data segments into it. + memory + .erase() + .map_err(|_| CacheError::ApplySnapshotFailed)?; + for (offset, contents) in &self.data_segments { + memory + .set(*offset, contents) + .map_err(|_| CacheError::ApplySnapshotFailed)?; + } + + // Second, restore the values of mutable globals. + for (global_ref, global_val) in instance + .globals() + .iter() + .filter(|g| g.is_mutable()) + .zip(self.global_mut_values.iter()) + { + // the instance should be the same as used for preserving and + // we iterate the same way it as we do it for preserving values that means that the + // types should be the same and all the values are mutable. So no error is expected/ + global_ref + .set(*global_val) + .map_err(|_| CacheError::ApplySnapshotFailed)?; + } + Ok(()) + } +} + +/// Default num of pages for the heap +const DEFAULT_HEAP_PAGES: u64 = 1024; + +/// Cache for the runtimes. +/// +/// When an instance is requested for the first time it is added to this +/// cache. Furthermore its initial memory and values of mutable globals are preserved here. 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. +/// +/// For now the cache grows indefinitely, but that should be fine for now since runtimes can only be +/// upgraded rarely and there are no other ways to make the node to execute some other runtime. +pub struct RuntimesCache { + /// A cache of runtime instances along with metadata, ready to be reused. + /// + /// Instances are keyed by the hash of their code. + instances: HashMap<[u8; 32], Result, CacheError>>, +} + +impl RuntimesCache { + /// Creates a new instance of a runtimes cache. + pub fn new() -> RuntimesCache { + RuntimesCache { + instances: HashMap::new(), + } + } + + /// Fetches an instance of the runtime. + /// + /// On first use we create a new runtime instance, save it to the cache + /// and persist its initial memory. + /// + /// Each subsequent request will return this instance, with its memory restored + /// to the persisted initial memory. Thus, we reuse one single runtime instance + /// for every `fetch_runtime` invocation. + /// + /// # Parameters + /// + /// `wasm_executor`- Rust wasm executor. Executes the provided code in a + /// sandboxed Wasm runtime. + /// + /// `ext` - Externalities to use for the runtime. This is used for setting + /// up an initial runtime instance. The parameter is only needed for calling + /// into the Wasm module to find out the `Core_version`. + /// + /// `default_heap_pages` - Number of 64KB pages to allocate for Wasm execution. + /// Defaults to `DEFAULT_HEAP_PAGES` if `None` is provided. + /// + /// # Return value + /// + /// If no error occurred a tuple `(wasmi::ModuleRef, Option)` is + /// returned. `RuntimeVersion` is contained if the call to `Core_version` returned + /// a version. + /// + /// In case of failure one of two errors can be returned: + /// + /// `Err::InvalidCode` is returned for runtime code issues. + /// + /// `Error::InvalidMemoryReference` is returned if no memory export with the + /// identifier `memory` can be found in the runtime. + pub fn fetch_runtime>( + &mut self, + wasm_executor: &WasmExecutor, + ext: &mut E, + default_heap_pages: Option, + ) -> Result, Error> { + let code_hash = ext + .original_storage_hash(well_known_keys::CODE) + .ok_or(Error::InvalidCode)?; + + // This is direct result from fighting with borrowck. + let handle_result = + |cached_result: &Result, CacheError>| match *cached_result { + Err(_) => Err(Error::InvalidCode), + Ok(ref cached_runtime) => Ok(Rc::clone(cached_runtime)), + }; + + match self.instances.entry(code_hash.into()) { + Entry::Occupied(o) => handle_result(o.get()), + Entry::Vacant(v) => { + trace!(target: "runtimes_cache", "no instance found in cache, creating now."); + let result = Self::create_wasm_instance(wasm_executor, ext, default_heap_pages); + if let Err(ref err) = result { + warn!(target: "runtimes_cache", "cannot create a runtime: {:?}", err); + } + handle_result(v.insert(result)) + } + } + } + + fn create_wasm_instance>( + wasm_executor: &WasmExecutor, + ext: &mut E, + default_heap_pages: Option, + ) -> Result, CacheError> { + let code = ext + .original_storage(well_known_keys::CODE) + .ok_or(CacheError::CodeNotFound)?; + let module = WasmModule::from_buffer(&code).map_err(|_| CacheError::InvalidModule)?; + + // Extract the data segments from the wasm code. + // + // A return of this error actually indicates that there is a problem in logic, since + // we just loaded and validated the `module` above. + let data_segments = extract_data_segments(&code).ok_or(CacheError::CantDeserializeWasm)?; + + let heap_pages = ext + .storage(well_known_keys::HEAP_PAGES) + .and_then(|pages| u64::decode(&mut &pages[..])) + .or(default_heap_pages) + .unwrap_or(DEFAULT_HEAP_PAGES); + + // Instantiate this module. + let instance = WasmExecutor::instantiate_module::(heap_pages as usize, &module) + .map_err(CacheError::Instantiation)?; + + // Take state snapshot before executing anything. + let state_snapshot = StateSnapshot::take(&instance, data_segments, heap_pages as u32) + .expect( + "`take` returns `Err` if the module is not valid; + we already loaded module above, thus the `Module` is proven to be valid at this point; + qed + ", + ); + + let version = wasm_executor + .call_in_wasm_module(ext, &instance, "Core_version", &[]) + .ok() + .and_then(|v| RuntimeVersion::decode(&mut v.as_slice())); + Ok(Rc::new(CachedRuntime { + instance, + version, + state_snapshot, + })) + } +} + +/// Extract the data segments from the given wasm code. +/// +/// Returns `Err` if the given wasm code cannot be deserialized. +fn extract_data_segments(wasm_code: &[u8]) -> Option> { + let raw_module: RawModule = deserialize_buffer(wasm_code).ok()?; + let segments = raw_module + .data_section() + .map(|ds| ds.entries()) + .unwrap_or(&[]) + .to_vec(); + Some(segments) +} diff --git a/substrate/core/primitives/Cargo.toml b/substrate/core/primitives/Cargo.toml index 1d55f82fab..fc18251f40 100644 --- a/substrate/core/primitives/Cargo.toml +++ b/substrate/core/primitives/Cargo.toml @@ -13,7 +13,7 @@ twox-hash = { version = "1.2.0", optional = true } byteorder = { version = "1.3.1", default-features = false } primitive-types = { version = "0.4.0", default-features = false, features = ["codec"] } impl-serde = { version = "0.1", optional = true } -wasmi = { version = "0.4.3", optional = true } +wasmi = { version = "0.5.0", optional = true } hash-db = { version = "0.14.0", default-features = false } hash256-std-hasher = { version = "0.14.0", default-features = false } ed25519-dalek = { version = "1.0.0-pre.1", optional = true } diff --git a/substrate/core/sr-api-macros/tests/runtime_calls.rs b/substrate/core/sr-api-macros/tests/runtime_calls.rs index fec6015835..83a4f58095 100644 --- a/substrate/core/sr-api-macros/tests/runtime_calls.rs +++ b/substrate/core/sr-api-macros/tests/runtime_calls.rs @@ -16,6 +16,7 @@ use test_client::{ prelude::*, + DefaultTestClientBuilderExt, TestClientBuilder, runtime::{TestAPI, DecodeFails, Transfer, Header}, }; use runtime_primitives::{ diff --git a/substrate/core/sr-sandbox/Cargo.toml b/substrate/core/sr-sandbox/Cargo.toml index 748bc54362..5e3aabfd23 100755 --- a/substrate/core/sr-sandbox/Cargo.toml +++ b/substrate/core/sr-sandbox/Cargo.toml @@ -9,7 +9,7 @@ edition = "2018" rustc_version = "0.2" [dependencies] -wasmi = { version = "0.4.3", optional = true } +wasmi = { version = "0.5.0", optional = true } primitives = { package = "substrate-primitives", path = "../primitives", default-features = false } rstd = { package = "sr-std", path = "../sr-std", default-features = false } codec = { package = "parity-codec", version = "4.1.1", default-features = false } diff --git a/substrate/core/test-runtime/Cargo.toml b/substrate/core/test-runtime/Cargo.toml index f2bf974faf..6302753950 100644 --- a/substrate/core/test-runtime/Cargo.toml +++ b/substrate/core/test-runtime/Cargo.toml @@ -32,6 +32,7 @@ srml-system = { path = "../../srml/system", default-features = false } [dev-dependencies] substrate-executor = { path = "../executor" } substrate-test-runtime-client = { path = "./client" } +state_machine = { package = "substrate-state-machine", path = "../state-machine" } [build-dependencies] wasm-builder-runner = { package = "substrate-wasm-builder-runner", version = "1.0.2", path = "../utils/wasm-builder-runner" } diff --git a/substrate/core/test-runtime/build.rs b/substrate/core/test-runtime/build.rs index f543f68ccd..e412123b94 100644 --- a/substrate/core/test-runtime/build.rs +++ b/substrate/core/test-runtime/build.rs @@ -14,14 +14,22 @@ // You should have received a copy of the GNU General Public License // along with Substrate. If not, see . -use wasm_builder_runner::{build_current_project, WasmBuilderSource}; +use wasm_builder_runner::{build_current_project_with_rustflags, WasmBuilderSource}; fn main() { - build_current_project( + build_current_project_with_rustflags( "wasm_binary.rs", WasmBuilderSource::CratesOrPath { path: "../utils/wasm-builder", version: "1.0.4", }, + // Note that we set the stack-size to 1MB explicitly even though it is set + // to this value by default. This is because some of our tests (`restoration_of_globals`) + // depend on the stack-size. + // + // The --export=__heap_base instructs LLD to export __heap_base as a global variable, which + // is used by the external memory allocator. + "-Clink-arg=-zstack-size=1048576 \ + -Clink-arg=--export=__heap_base", ); } diff --git a/substrate/core/test-runtime/client/src/lib.rs b/substrate/core/test-runtime/client/src/lib.rs index 0f43911168..4383a80e68 100644 --- a/substrate/core/test-runtime/client/src/lib.rs +++ b/substrate/core/test-runtime/client/src/lib.rs @@ -95,11 +95,12 @@ pub type LightExecutor = client::light::call_executor::RemoteOrLocalCallExecutor #[derive(Default)] pub struct GenesisParameters { support_changes_trie: bool, + heap_pages_override: Option, } impl generic_test_client::GenesisInit for GenesisParameters { fn genesis_storage(&self) -> (StorageOverlay, ChildrenStorageOverlay) { - let mut storage = genesis_config(self.support_changes_trie).genesis_map(); + let mut storage = genesis_config(self.support_changes_trie, self.heap_pages_override).genesis_map(); let state_root = <<::Header as HeaderT>::Hashing as HashT>::trie_root( storage.clone().into_iter() @@ -145,6 +146,9 @@ pub trait TestClientBuilderExt: Sized { /// Enable or disable support for changes trie in genesis. fn set_support_changes_trie(self, support_changes_trie: bool) -> Self; + /// Override the default value for Wasm heap pages. + fn set_heap_pages(self, heap_pages: u64) -> Self; + /// Build the test client. fn build(self) -> Client { self.build_with_longest_chain().0 @@ -160,6 +164,11 @@ impl TestClientBuilderExt for TestClientBuilder< > where B: client::backend::Backend, { + fn set_heap_pages(mut self, heap_pages: u64) -> Self { + self.genesis_init_mut().heap_pages_override = Some(heap_pages); + self + } + fn set_support_changes_trie(mut self, support_changes_trie: bool) -> Self { self.genesis_init_mut().support_changes_trie = support_changes_trie; self @@ -170,17 +179,20 @@ impl TestClientBuilderExt for TestClientBuilder< } } -fn genesis_config(support_changes_trie: bool) -> GenesisConfig { - GenesisConfig::new(support_changes_trie, vec![ - Sr25519Keyring::Alice.into(), - Sr25519Keyring::Bob.into(), - Sr25519Keyring::Charlie.into(), - ], vec![ - AccountKeyring::Alice.into(), - AccountKeyring::Bob.into(), - AccountKeyring::Charlie.into(), - ], - 1000 +fn genesis_config(support_changes_trie: bool, heap_pages_override: Option) -> GenesisConfig { + GenesisConfig::new( + support_changes_trie, + vec![ + Sr25519Keyring::Alice.into(), + Sr25519Keyring::Bob.into(), + Sr25519Keyring::Charlie.into(), + ], vec![ + AccountKeyring::Alice.into(), + AccountKeyring::Bob.into(), + AccountKeyring::Charlie.into(), + ], + 1000, + heap_pages_override, ) } diff --git a/substrate/core/test-runtime/src/genesismap.rs b/substrate/core/test-runtime/src/genesismap.rs index 21d7aae0a1..8e0a423c92 100644 --- a/substrate/core/test-runtime/src/genesismap.rs +++ b/substrate/core/test-runtime/src/genesismap.rs @@ -28,6 +28,7 @@ pub struct GenesisConfig { pub changes_trie_config: Option, pub authorities: Vec, pub balances: Vec<(AccountId, u64)>, + pub heap_pages_override: Option, } impl GenesisConfig { @@ -35,7 +36,8 @@ impl GenesisConfig { support_changes_trie: bool, authorities: Vec, endowed_accounts: Vec, - balance: u64 + balance: u64, + heap_pages_override: Option, ) -> Self { GenesisConfig { changes_trie_config: match support_changes_trie { @@ -44,6 +46,7 @@ impl GenesisConfig { }, authorities: authorities.clone(), balances: endowed_accounts.into_iter().map(|a| (a, balance)).collect(), + heap_pages_override, } } @@ -54,7 +57,10 @@ impl GenesisConfig { .map(|(k, v)| (blake2_256(&k[..])[..].to_vec(), v.to_vec())) .chain(vec![ (well_known_keys::CODE.into(), wasm_runtime), - (well_known_keys::HEAP_PAGES.into(), vec![].and(&(16 as u64))), + ( + well_known_keys::HEAP_PAGES.into(), + vec![].and(&(self.heap_pages_override.unwrap_or(16 as u64))), + ), ].into_iter()) .collect(); if let Some(ref changes_trie_config) = self.changes_trie_config { diff --git a/substrate/core/test-runtime/src/lib.rs b/substrate/core/test-runtime/src/lib.rs index ceb9ed737b..cf3ac363c0 100644 --- a/substrate/core/test-runtime/src/lib.rs +++ b/substrate/core/test-runtime/src/lib.rs @@ -256,6 +256,8 @@ cfg_if! { fn use_trie() -> u64; fn benchmark_indirect_call() -> u64; fn benchmark_direct_call() -> u64; + fn returns_mutable_static() -> u64; + fn allocates_huge_stack_array(trap: bool) -> Vec; /// Returns the initialized block number. fn get_block_number() -> u64; /// Takes and returns the initialized block number. @@ -287,6 +289,8 @@ cfg_if! { fn use_trie() -> u64; fn benchmark_indirect_call() -> u64; fn benchmark_direct_call() -> u64; + fn returns_mutable_static() -> u64; + fn allocates_huge_stack_array(trap: bool) -> Vec; /// Returns the initialized block number. fn get_block_number() -> u64; /// Takes and returns the initialized block number. @@ -404,6 +408,11 @@ fn code_using_trie() -> u64 { iter_pairs.len() as u64 } +#[cfg(not(feature = "std"))] +/// Mutable static variables should be always observed to have +/// the initialized value at the start of a runtime call. +static mut MUTABLE_STATIC: u64 = 32; + cfg_if! { if #[cfg(feature = "std")] { impl_runtime_apis! { @@ -509,6 +518,14 @@ cfg_if! { (0..1000).fold(0, |p, i| p + benchmark_add_one(i)) } + fn returns_mutable_static() -> u64 { + unimplemented!("is not expected to be invoked from non-wasm builds"); + } + + fn allocates_huge_stack_array(_trap: bool) -> Vec { + unimplemented!("is not expected to be invoked from non-wasm builds"); + } + fn get_block_number() -> u64 { system::get_block_number().expect("Block number is initialized") } @@ -665,6 +682,41 @@ cfg_if! { (0..10000).fold(0, |p, i| p + benchmark_add_one(i)) } + fn returns_mutable_static() -> u64 { + unsafe { + MUTABLE_STATIC += 1; + MUTABLE_STATIC + } + } + + fn allocates_huge_stack_array(trap: bool) -> Vec { + // Allocate a stack frame that is approx. 75% of the stack (assuming it is 1MB). + // This will just decrease (stacks in wasm32-u-u grow downwards) the stack + // pointer. This won't trap on the current compilers. + let mut data = [0u8; 1024 * 768]; + + // Then make sure we actually write something to it. + // + // If: + // 1. the stack area is placed at the beginning of the linear memory space, and + // 2. the stack pointer points to out-of-bounds area, and + // 3. a write is performed around the current stack pointer. + // + // then a trap should happen. + // + for (i, v) in data.iter_mut().enumerate() { + *v = i as u8; // deliberate truncation + } + + if trap { + // There is a small chance of this to be pulled up in theory. In practice + // the probability of that is rather low. + panic!() + } + + data.to_vec() + } + fn get_block_number() -> u64 { system::get_block_number().expect("Block number is initialized") } @@ -715,3 +767,64 @@ cfg_if! { } } } + +#[cfg(test)] +mod tests { + use substrate_test_runtime_client::{ + prelude::*, + DefaultTestClientBuilderExt, TestClientBuilder, + runtime::TestAPI, + }; + use runtime_primitives::{ + generic::BlockId, + traits::ProvideRuntimeApi, + }; + use state_machine::ExecutionStrategy; + + #[test] + fn returns_mutable_static() { + let client = TestClientBuilder::new().set_execution_strategy(ExecutionStrategy::AlwaysWasm).build(); + let runtime_api = client.runtime_api(); + let block_id = BlockId::Number(client.info().chain.best_number); + + let ret = runtime_api.returns_mutable_static(&block_id).unwrap(); + assert_eq!(ret, 33); + + // 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 ret = runtime_api.returns_mutable_static(&block_id).unwrap(); + assert_eq!(ret, 33); + } + + // 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/paritytech/substrate/issues/2967 for details + #[test] + fn restoration_of_globals() { + // 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: u64 = 32; + + let client = TestClientBuilder::new() + .set_execution_strategy(ExecutionStrategy::AlwaysWasm) + .set_heap_pages(REQUIRED_MEMORY_PAGES) + .build(); + let runtime_api = client.runtime_api(); + let block_id = BlockId::Number(client.info().chain.best_number); + + // On the first invocation we allocate approx. 768KB (75%) of stack and then trap. + let ret = runtime_api.allocates_huge_stack_array(&block_id, true); + assert!(ret.is_err()); + + // On the second invocation we allocate yet another 768KB (75%) of stack + let ret = runtime_api.allocates_huge_stack_array(&block_id, false); + assert!(ret.is_ok()); + } + +} diff --git a/substrate/node-template/runtime/build.rs b/substrate/node-template/runtime/build.rs index ccf58b138f..7000c602e8 100644 --- a/substrate/node-template/runtime/build.rs +++ b/substrate/node-template/runtime/build.rs @@ -14,8 +14,14 @@ // You should have received a copy of the GNU General Public License // along with Substrate. If not, see . -use wasm_builder_runner::{build_current_project, WasmBuilderSource}; +use wasm_builder_runner::{build_current_project_with_rustflags, WasmBuilderSource}; fn main() { - build_current_project("wasm_binary.rs", WasmBuilderSource::Crates("1.0.4")); + build_current_project_with_rustflags( + "wasm_binary.rs", + WasmBuilderSource::Crates("1.0.4"), + // This instructs LLD to export __heap_base as a global variable, which is used by the + // external memory allocator. + "-Clink-arg=--export=__heap_base", + ); } diff --git a/substrate/node/executor/src/lib.rs b/substrate/node/executor/src/lib.rs index 9b9352090e..f858f4e2a8 100644 --- a/substrate/node/executor/src/lib.rs +++ b/substrate/node/executor/src/lib.rs @@ -22,6 +22,7 @@ #[cfg(feature = "benchmarks")] extern crate test; pub use substrate_executor::NativeExecutor; +pub use substrate_executor::RuntimesCache; use substrate_executor::native_executor_instance; // Declare an instance of the native executor named `Executor`. Include the wasm binary as the diff --git a/substrate/node/runtime/build.rs b/substrate/node/runtime/build.rs index 39aecacb20..a5f22fd017 100644 --- a/substrate/node/runtime/build.rs +++ b/substrate/node/runtime/build.rs @@ -14,14 +14,17 @@ // You should have received a copy of the GNU General Public License // along with Substrate. If not, see . -use wasm_builder_runner::{build_current_project, WasmBuilderSource}; +use wasm_builder_runner::{build_current_project_with_rustflags, WasmBuilderSource}; fn main() { - build_current_project( + build_current_project_with_rustflags( "wasm_binary.rs", WasmBuilderSource::CratesOrPath { path: "../../core/utils/wasm-builder", version: "1.0.4", }, + // This instructs LLD to export __heap_base as a global variable, which is used by the + // external memory allocator. + "-Clink-arg=--export=__heap_base", ); }