mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-09 20:11:09 +00:00
Fair reusing of wasm runtime instances (#3011)
* Add test from original bug report Original is from @pepyakin in 3d7b27f3421818e8d6de568e02fbc2947a06246b. I adapted it to work with the latest master. * No longer cleanup module instance * Replace runtime cache with synchronous clone * Fix test * Preserve initial runtime memory and restore it on fetch * Remove leftover comment * Fix style * Improve variable naming * Replace get_into() with get() * Handle missing memory export better * Return earlier when creating runtime first time * Improve comments * fmt * Fix #2967. * Eradicate `code` from `Error::InvalidCode` * tidy * A state snapshot doc. * Store multiple runtimes by hash. * Get rid of deref. * Docs * Use Self for instantiate_module * REVERT ME * Should be ok * Commit * Remove dbg * Use fast-memory's erase * Clean and undo hacks. * Introduce a dedicated error for heap_base * Ban the start function. * Clean, docs and refactor * Add rustflags. * Update Cargo.lock * Apply Basti's suggestions Co-Authored-By: Bastian Köcher <bkchr@users.noreply.github.com> * Rename allocates_huge_stack_array * Extend TestClientBuilder with set_heap_pages * Update the test. * Update core/executor/src/wasm_executor.rs Co-Authored-By: Bastian Köcher <bkchr@users.noreply.github.com> * Update core/executor/src/wasm_runtimes_cache.rs Co-Authored-By: Bastian Köcher <bkchr@users.noreply.github.com> * Update core/executor/src/error.rs Co-Authored-By: Bastian Köcher <bkchr@users.noreply.github.com> * Update core/executor/src/error.rs Co-Authored-By: Bastian Köcher <bkchr@users.noreply.github.com> * Fix tests. * Update cargo-lock * Use wasmi master * Use master wasmi * Move tests. * Use wasmi crates.io * Update Cargo.lock * Fix build.rs * Bump runtime version * Revert initial_heap_pages renaming * Bump wasmi up to 0.5.0 * Bump runtime version * Don't restore an instance every now and then * Update core/executor/src/wasm_runtimes_cache.rs Co-Authored-By: DemiMarie-parity <48690212+DemiMarie-parity@users.noreply.github.com> * Propagate error in CacheError * Clarify the get_heap_base call in instantiation * Supply --export=__heap_base See https://reviews.llvm.org/D62744 Co-authored-by: Jim Posen <jim.posen@gmail.com> * Bump version. * Use combinators for segments. * Fix build.rs * Fix build.rs for runtime-test
This commit is contained in:
Generated
+10
-5
@@ -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"
|
||||
|
||||
@@ -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::<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::<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::<Block>(state_root);
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -14,14 +14,17 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
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",
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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<u8> = 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 {
|
||||
|
||||
@@ -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<u8>),
|
||||
#[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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -14,86 +14,20 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
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<RuntimeVersion>),
|
||||
}
|
||||
|
||||
type CacheType = HashMap<[u8; 32], RuntimePreproc>;
|
||||
use crate::RuntimesCache;
|
||||
|
||||
thread_local! {
|
||||
static RUNTIMES_CACHE: RefCell<CacheType> = 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<Blake2Hasher>>(
|
||||
wasm_executor: &WasmExecutor,
|
||||
cache: &'a mut RefMut<CacheType>,
|
||||
ext: &mut E,
|
||||
default_heap_pages: Option<u64>,
|
||||
) -> Result<(&'a WasmModuleInstanceRef, &'a Option<RuntimeVersion>)> {
|
||||
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<RuntimesCache> = RefCell::new(RuntimesCache::new());
|
||||
}
|
||||
|
||||
fn safe_call<F, U>(f: F) -> Result<U>
|
||||
@@ -140,7 +74,7 @@ pub struct NativeExecutor<D> {
|
||||
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<u64>,
|
||||
}
|
||||
|
||||
@@ -151,7 +85,7 @@ impl<D: NativeExecutionDispatch> NativeExecutor<D> {
|
||||
_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<D: NativeExecutionDispatch> RuntimeInfo for NativeExecutor<D> {
|
||||
&self,
|
||||
ext: &mut E,
|
||||
) -> Option<RuntimeVersion> {
|
||||
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<D: NativeExecutionDispatch> CodeExecutor<Blake2Hasher> for NativeExecutor<D
|
||||
data: &[u8],
|
||||
use_native: bool,
|
||||
native_call: Option<NC>,
|
||||
) -> (Result<NativeOrEncoded<R>>, 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<NativeOrEncoded<R>>, 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<D: NativeExecutionDispatch> CodeExecutor<Blake2Hasher> for NativeExecutor<D
|
||||
.map_or_else(||"<None>".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
|
||||
)
|
||||
}
|
||||
|
||||
@@ -56,10 +56,10 @@ struct FunctionExecutor<'e, E: Externalities<Blake2Hasher> + 'e> {
|
||||
}
|
||||
|
||||
impl<'e, E: Externalities<Blake2Hasher>> FunctionExecutor<'e, E> {
|
||||
fn new(m: MemoryRef, t: Option<TableRef>, e: &'e mut E) -> Result<Self> {
|
||||
fn new(m: MemoryRef, heap_base: u32, t: Option<TableRef>, e: &'e mut E) -> Result<Self> {
|
||||
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<Vec<u8>> {
|
||||
let module = ::wasmi::Module::from_buffer(code)?;
|
||||
let module = self.prepare_module(ext, heap_pages, &module)?;
|
||||
let module = Self::instantiate_module::<E>(heap_pages, &module)?;
|
||||
self.call_in_wasm_module(ext, &module, method, data)
|
||||
}
|
||||
|
||||
@@ -1292,7 +1292,7 @@ impl WasmExecutor {
|
||||
filter_result: FR,
|
||||
) -> Result<R> {
|
||||
let module = wasmi::Module::from_buffer(code)?;
|
||||
let module = self.prepare_module(ext, heap_pages, &module)?;
|
||||
let module = Self::instantiate_module::<E>(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<u32> {
|
||||
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<E: Externalities<Blake2Hasher>>(
|
||||
&self,
|
||||
@@ -1359,10 +1375,9 @@ impl WasmExecutor {
|
||||
let table: Option<TableRef> = 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<E: Externalities<Blake2Hasher>>(
|
||||
&self,
|
||||
ext: &mut E,
|
||||
pub fn instantiate_module<E: Externalities<Blake2Hasher>>(
|
||||
heap_pages: usize,
|
||||
module: &Module,
|
||||
) -> Result<ModuleRef>
|
||||
{
|
||||
) -> Result<ModuleRef> {
|
||||
// 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::<E>::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<TableRef> = 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())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! 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<RuntimeVersion>,
|
||||
/// 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<R, F>(&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<RuntimeVersion> {
|
||||
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<u8>)>,
|
||||
/// The list of all global mutable variables of the module in their sequential order.
|
||||
global_mut_values: Vec<RuntimeValue>,
|
||||
heap_pages: u32,
|
||||
}
|
||||
|
||||
impl StateSnapshot {
|
||||
// Returns `None` if instance is not valid.
|
||||
fn take(
|
||||
module_instance: &WasmModuleInstanceRef,
|
||||
data_segments: Vec<DataSegment>,
|
||||
heap_pages: u32,
|
||||
) -> Option<Self> {
|
||||
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::<Option<Vec<_>>>()?;
|
||||
|
||||
// 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<Rc<CachedRuntime>, 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<RuntimeVersion>)` 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<E: Externalities<Blake2Hasher>>(
|
||||
&mut self,
|
||||
wasm_executor: &WasmExecutor,
|
||||
ext: &mut E,
|
||||
default_heap_pages: Option<u64>,
|
||||
) -> Result<Rc<CachedRuntime>, 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<Rc<CachedRuntime>, 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<E: Externalities<Blake2Hasher>>(
|
||||
wasm_executor: &WasmExecutor,
|
||||
ext: &mut E,
|
||||
default_heap_pages: Option<u64>,
|
||||
) -> Result<Rc<CachedRuntime>, 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::<E>(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<Vec<DataSegment>> {
|
||||
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)
|
||||
}
|
||||
@@ -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 }
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
use test_client::{
|
||||
prelude::*,
|
||||
DefaultTestClientBuilderExt, TestClientBuilder,
|
||||
runtime::{TestAPI, DecodeFails, Transfer, Header},
|
||||
};
|
||||
use runtime_primitives::{
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -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" }
|
||||
|
||||
@@ -14,14 +14,22 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
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",
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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<u64>,
|
||||
}
|
||||
|
||||
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 = <<<runtime::Block as BlockT>::Header as HeaderT>::Hashing as HashT>::trie_root(
|
||||
storage.clone().into_iter()
|
||||
@@ -145,6 +146,9 @@ pub trait TestClientBuilderExt<B>: 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<B> {
|
||||
self.build_with_longest_chain().0
|
||||
@@ -160,6 +164,11 @@ impl<B> TestClientBuilderExt<B> for TestClientBuilder<
|
||||
> where
|
||||
B: client::backend::Backend<runtime::Block, Blake2Hasher>,
|
||||
{
|
||||
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<B> TestClientBuilderExt<B> 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<u64>) -> 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,
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -28,6 +28,7 @@ pub struct GenesisConfig {
|
||||
pub changes_trie_config: Option<ChangesTrieConfiguration>,
|
||||
pub authorities: Vec<AuthorityId>,
|
||||
pub balances: Vec<(AccountId, u64)>,
|
||||
pub heap_pages_override: Option<u64>,
|
||||
}
|
||||
|
||||
impl GenesisConfig {
|
||||
@@ -35,7 +36,8 @@ impl GenesisConfig {
|
||||
support_changes_trie: bool,
|
||||
authorities: Vec<AuthorityId>,
|
||||
endowed_accounts: Vec<AccountId>,
|
||||
balance: u64
|
||||
balance: u64,
|
||||
heap_pages_override: Option<u64>,
|
||||
) -> 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 {
|
||||
|
||||
@@ -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<u8>;
|
||||
/// 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<u8>;
|
||||
/// 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<u8> {
|
||||
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<u8> {
|
||||
// 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());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -14,8 +14,14 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
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",
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -14,14 +14,17 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
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",
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user