From e75d7d8fdad09e8f654aa14d70c54837f048cfbd Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Mon, 16 Jul 2018 20:59:10 +0200 Subject: [PATCH] Adaptive heap size (#328) * heap-size is a CLI arg, make it 512 by default * Fix tests * Adaptive heap size. * Allow storage_exists --- substrate/demo/cli/src/lib.rs | 5 +- substrate/demo/executor/src/lib.rs | 23 +-- substrate/polkadot/api/src/full.rs | 2 +- substrate/polkadot/cli/src/cli.yml | 20 ++ substrate/polkadot/cli/src/lib.rs | 24 +++ substrate/substrate/client/src/error.rs | 2 + substrate/substrate/client/src/genesis.rs | 8 +- .../client/src/light/call_executor.rs | 5 +- substrate/substrate/executor/Cargo.toml | 4 + substrate/substrate/executor/src/error.rs | 17 ++ .../substrate/executor/src/native_executor.rs | 28 +-- substrate/substrate/executor/src/sandbox.rs | 10 +- .../substrate/executor/src/wasm_executor.rs | 184 +++++++++++++----- substrate/substrate/runtime-io/with_std.rs | 7 + substrate/substrate/runtime-io/without_std.rs | 10 + substrate/substrate/service/src/config.rs | 6 + substrate/substrate/service/src/lib.rs | 5 +- .../substrate/state-machine/src/backend.rs | 11 ++ substrate/substrate/state-machine/src/ext.rs | 7 + substrate/substrate/state-machine/src/lib.rs | 104 ++++++---- .../state-machine/src/trie_backend.rs | 2 + .../substrate/test-client/src/client_ext.rs | 5 +- substrate/substrate/test-client/src/lib.rs | 8 +- 23 files changed, 360 insertions(+), 137 deletions(-) diff --git a/substrate/demo/cli/src/lib.rs b/substrate/demo/cli/src/lib.rs index 24fd088a10..e90b59cdc8 100644 --- a/substrate/demo/cli/src/lib.rs +++ b/substrate/demo/cli/src/lib.rs @@ -54,6 +54,7 @@ use demo_runtime::{Block, BlockId, UncheckedExtrinsic, GenesisConfig, TimestampConfig}; use futures::{Future, Sink, Stream}; use tokio::runtime::Runtime; +use demo_executor::NativeExecutor; struct DummyPool; impl extrinsic_pool::api::ExtrinsicPool for DummyPool { @@ -107,7 +108,7 @@ pub fn run(args: I) -> error::Result<()> where init_logger(log_pattern); // Create client - let executor = demo_executor::Executor::with_heap_pages(8); + let executor = NativeExecutor::with_heap_pages(8, 8); let god_key = hex!["3d866ec8a9190c8343c2fc593d21d8a6d0c5c4763aaab2349de3a6111d64d124"]; let genesis_config = GenesisConfig { @@ -163,7 +164,7 @@ pub fn run(args: I) -> error::Result<()> where }), }; - let client = Arc::new(client::new_in_mem::<_, Block, _>(executor, genesis_config)?); + let client = Arc::new(client::new_in_mem::, Block, _>(executor, genesis_config)?); let mut runtime = Runtime::new()?; let _rpc_servers = { let handler = || { diff --git a/substrate/demo/executor/src/lib.rs b/substrate/demo/executor/src/lib.rs index 4775194e65..8a47fbf69c 100644 --- a/substrate/demo/executor/src/lib.rs +++ b/substrate/demo/executor/src/lib.rs @@ -35,13 +35,14 @@ extern crate triehash; #[cfg(test)] extern crate substrate_runtime_consensus as consensus; #[cfg(test)] #[macro_use] extern crate hex_literal; +pub use substrate_executor::NativeExecutor; native_executor_instance!(pub Executor, demo_runtime::api::dispatch, demo_runtime::VERSION, include_bytes!("../../runtime/wasm/target/wasm32-unknown-unknown/release/demo_runtime.compact.wasm")); #[cfg(test)] mod tests { use runtime_io; use super::Executor; - use substrate_executor::WasmExecutor; + use substrate_executor::{WasmExecutor, NativeExecutionDispatch}; use codec::{Encode, Decode, Joiner}; use keyring::Keyring; use runtime_support::{Hashable, StorageValue, StorageMap}; @@ -94,7 +95,7 @@ mod tests { } fn executor() -> ::substrate_executor::NativeExecutor { - Executor::with_heap_pages(8) + ::substrate_executor::NativeExecutor::with_heap_pages(8, 8) } #[test] @@ -312,14 +313,14 @@ mod tests { fn full_wasm_block_import_works() { let mut t = new_test_ext(); - WasmExecutor{heap_pages: 8}.call(&mut t, COMPACT_CODE, "execute_block", &block1().0, true).0.unwrap(); + WasmExecutor::new(8, 8).call(&mut t, COMPACT_CODE, "execute_block", &block1().0, true).0.unwrap(); runtime_io::with_externalities(&mut t, || { assert_eq!(Staking::voting_balance(&alice()), 41); assert_eq!(Staking::voting_balance(&bob()), 69); }); - WasmExecutor{heap_pages: 8}.call(&mut t, COMPACT_CODE, "execute_block", &block2().0, true).0.unwrap(); + WasmExecutor::new(8, 8).call(&mut t, COMPACT_CODE, "execute_block", &block2().0, true).0.unwrap(); runtime_io::with_externalities(&mut t, || { assert_eq!(Staking::voting_balance(&alice()), 30); @@ -331,7 +332,7 @@ mod tests { fn wasm_big_block_import_fails() { let mut t = new_test_ext(); - let r = WasmExecutor{heap_pages: 8}.call(&mut t, COMPACT_CODE, "execute_block", &block1big().0, true).0; + let r = WasmExecutor::new(8, 8).call(&mut t, COMPACT_CODE, "execute_block", &block1big().0, true).0; assert!(!r.is_ok()); } @@ -339,7 +340,7 @@ mod tests { fn native_big_block_import_succeeds() { let mut t = new_test_ext(); - let r = Executor::with_heap_pages(8).call(&mut t, COMPACT_CODE, "execute_block", &block1big().0, true).0; + let r = Executor::with_heap_pages(8, 8).call(&mut t, COMPACT_CODE, "execute_block", &block1big().0, true).0; assert!(r.is_ok()); } @@ -347,7 +348,7 @@ mod tests { fn native_big_block_import_fails_on_fallback() { let mut t = new_test_ext(); - let r = Executor::with_heap_pages(8).call(&mut t, COMPACT_CODE, "execute_block", &block1big().0, false).0; + let r = Executor::with_heap_pages(8, 8).call(&mut t, COMPACT_CODE, "execute_block", &block1big().0, false).0; assert!(!r.is_ok()); } @@ -364,9 +365,9 @@ mod tests { ]; let foreign_code = include_bytes!("../../runtime/wasm/target/wasm32-unknown-unknown/release/demo_runtime.wasm"); - let r = WasmExecutor{heap_pages: 8}.call(&mut t, &foreign_code[..], "initialise_block", &vec![].and(&from_block_number(1u64)), true).0; + let r = WasmExecutor::new(8, 8).call(&mut t, &foreign_code[..], "initialise_block", &vec![].and(&from_block_number(1u64)), true).0; assert!(r.is_ok()); - let r = WasmExecutor{heap_pages: 8}.call(&mut t, &foreign_code[..], "apply_extrinsic", &vec![].and(&xt()), true).0.unwrap(); + let r = WasmExecutor::new(8, 8).call(&mut t, &foreign_code[..], "apply_extrinsic", &vec![].and(&xt()), true).0.unwrap(); let r = ApplyResult::decode(&mut &r[..]).unwrap(); assert_eq!(r, Err(ApplyError::CantPay)); } @@ -384,9 +385,9 @@ mod tests { ]; let foreign_code = include_bytes!("../../runtime/wasm/target/wasm32-unknown-unknown/release/demo_runtime.compact.wasm"); - let r = WasmExecutor{heap_pages: 8}.call(&mut t, &foreign_code[..], "initialise_block", &vec![].and(&from_block_number(1u64)), true).0; + let r = WasmExecutor::new(8, 8).call(&mut t, &foreign_code[..], "initialise_block", &vec![].and(&from_block_number(1u64)), true).0; assert!(r.is_ok()); - let r = WasmExecutor{heap_pages: 8}.call(&mut t, &foreign_code[..], "apply_extrinsic", &vec![].and(&xt()), true).0.unwrap(); + let r = WasmExecutor::new(8, 8).call(&mut t, &foreign_code[..], "apply_extrinsic", &vec![].and(&xt()), true).0.unwrap(); let r = ApplyResult::decode(&mut &r[..]).unwrap(); assert_eq!(r, Ok(ApplyOutcome::Success)); diff --git a/substrate/polkadot/api/src/full.rs b/substrate/polkadot/api/src/full.rs index 7a5aeb31e7..caf9a89072 100644 --- a/substrate/polkadot/api/src/full.rs +++ b/substrate/polkadot/api/src/full.rs @@ -200,7 +200,7 @@ mod tests { timestamp: Some(Default::default()), }; - ::client::new_in_mem(LocalDispatch::with_heap_pages(8), genesis_config).unwrap() + ::client::new_in_mem(LocalDispatch::with_heap_pages(8, 8), genesis_config).unwrap() } #[test] diff --git a/substrate/polkadot/cli/src/cli.yml b/substrate/polkadot/cli/src/cli.yml index bb6f1defbf..ad4eb51ab6 100644 --- a/substrate/polkadot/cli/src/cli.yml +++ b/substrate/polkadot/cli/src/cli.yml @@ -99,6 +99,14 @@ args: long: execution value_name: STRATEGY help: The means of execution used when calling into the runtime. Can be either wasm, native or both. + - min-heap-pages: + long: min-heap-pages + value_name: COUNT + help: The number of 64KB pages to allocate for Wasm execution initially. + - max-heap-pages: + long: max-heap-pages + value_name: COUNT + help: The maximum number of 64KB pages to ever allocate for Wasm execution. Don't alter this unless you know what you're doing. subcommands: - build-spec: about: Build a spec.json file, outputing to stdout @@ -162,3 +170,15 @@ subcommands: value_name: PATH help: Specify custom base path. takes_value: true + - execution: + long: execution + value_name: STRATEGY + help: The means of execution used when calling into the runtime. Can be either wasm, native or both. + - min-heap-pages: + long: min-heap-pages + value_name: COUNT + help: The number of 64KB pages to allocate for Wasm execution initially. + - max-heap-pages: + long: max-heap-pages + value_name: COUNT + help: The maximum number of 64KB pages to ever allocate for Wasm execution. Don't alter this unless you know what you're doing. diff --git a/substrate/polkadot/cli/src/lib.rs b/substrate/polkadot/cli/src/lib.rs index 791010b128..9cc7e3020d 100644 --- a/substrate/polkadot/cli/src/lib.rs +++ b/substrate/polkadot/cli/src/lib.rs @@ -237,6 +237,13 @@ pub fn run(args: I, worker: W) -> error::Result<()> where service::Roles::FULL }; + if let Some(v) = matches.value_of("min-heap-pages") { + config.min_heap_pages = v.parse().map_err(|_| "Invalid --min-heap-pages argument")?; + } + if let Some(v) = matches.value_of("max-heap-pages") { + config.max_heap_pages = v.parse().map_err(|_| "Invalid --max-heap-pages argument")?; + } + if let Some(s) = matches.value_of("execution") { config.execution_strategy = match s { "both" => service::ExecutionStrategy::Both, @@ -393,6 +400,23 @@ fn import_blocks(matches: &clap::ArgMatches, exit: E) -> error::Result<()> let base_path = base_path(matches); let mut config = service::Configuration::default_with_spec(spec); config.database_path = db_path(&base_path, config.chain_spec.id()).to_string_lossy().into(); + + if let Some(v) = matches.value_of("min-heap-pages") { + config.min_heap_pages = v.parse().map_err(|_| "Invalid --min-heap-pages argument")?; + } + if let Some(v) = matches.value_of("max-heap-pages") { + config.max_heap_pages = v.parse().map_err(|_| "Invalid --max-heap-pages argument")?; + } + + if let Some(s) = matches.value_of("execution") { + config.execution_strategy = match s { + "both" => service::ExecutionStrategy::Both, + "native" => service::ExecutionStrategy::NativeWhenPossible, + "wasm" => service::ExecutionStrategy::AlwaysWasm, + _ => return Err(error::ErrorKind::Input("Invalid execution mode specified".to_owned()).into()), + }; + } + let client = service::new_client(config)?; let (exit_send, exit_recv) = std::sync::mpsc::channel(); diff --git a/substrate/substrate/client/src/error.rs b/substrate/substrate/client/src/error.rs index 0b3e627ee6..5d991b00a1 100644 --- a/substrate/substrate/client/src/error.rs +++ b/substrate/substrate/client/src/error.rs @@ -138,3 +138,5 @@ impl Error { ErrorKind::Execution(e).into() } } + +impl state_machine::Error for Error {} \ No newline at end of file diff --git a/substrate/substrate/client/src/genesis.rs b/substrate/substrate/client/src/genesis.rs index 8593357fde..c24b506e16 100644 --- a/substrate/substrate/client/src/genesis.rs +++ b/substrate/substrate/client/src/genesis.rs @@ -44,7 +44,7 @@ mod tests { use super::*; use codec::{Encode, Decode, Joiner}; use keyring::Keyring; - use executor::WasmExecutor; + use executor::{WasmExecutor, NativeExecutionDispatch}; use state_machine::{execute, OverlayedChanges, ExecutionStrategy}; use state_machine::backend::InMemory; use test_client; @@ -55,7 +55,7 @@ mod tests { native_executor_instance!(Executor, test_client::runtime::api::dispatch, test_client::runtime::VERSION, include_bytes!("../../test-runtime/wasm/target/wasm32-unknown-unknown/release/substrate_test_runtime.compact.wasm")); fn executor() -> ::executor::NativeExecutor { - Executor::with_heap_pages(8) + NativeExecutionDispatch::with_heap_pages(8, 8) } fn construct_block(backend: &InMemory, number: BlockNumber, parent_hash: Hash, state_root: Hash, txs: Vec) -> (Vec, Hash) { @@ -169,7 +169,7 @@ mod tests { let _ = execute( &backend, &mut overlay, - &WasmExecutor { heap_pages: 8 }, + &WasmExecutor::new(8, 8), "execute_block", &b1data, ExecutionStrategy::NativeWhenPossible, @@ -193,7 +193,7 @@ mod tests { let _ = execute( &backend, &mut overlay, - &Executor::with_heap_pages(8), + &Executor::with_heap_pages(8, 8), "execute_block", &b1data, ExecutionStrategy::NativeWhenPossible, diff --git a/substrate/substrate/client/src/light/call_executor.rs b/substrate/substrate/client/src/light/call_executor.rs index 9c94d4fdb6..40d8665510 100644 --- a/substrate/substrate/client/src/light/call_executor.rs +++ b/substrate/substrate/client/src/light/call_executor.rs @@ -140,6 +140,7 @@ fn do_check_execution_proof( #[cfg(test)] mod tests { use test_client; + use executor::NativeExecutionDispatch; use super::*; #[test] @@ -152,9 +153,9 @@ mod tests { // 'fetch' execution proof from remote node let remote_execution_proof = remote_client.execution_proof(&remote_block_id, "authorities", &[]).unwrap().1; - + // check remote execution proof locally - let local_executor = test_client::NativeExecutor::with_heap_pages(8); + let local_executor = test_client::LocalExecutor::with_heap_pages(8, 8); do_check_execution_proof(remote_block_storage_root.into(), &local_executor, &RemoteCallRequest { block: test_client::runtime::Hash::default(), method: "authorities".into(), diff --git a/substrate/substrate/executor/Cargo.toml b/substrate/substrate/executor/Cargo.toml index 959482bd24..8d537dd3dc 100644 --- a/substrate/substrate/executor/Cargo.toml +++ b/substrate/substrate/executor/Cargo.toml @@ -27,3 +27,7 @@ log = "0.3" [dev-dependencies] assert_matches = "1.1" wabt = "0.4" + +[features] +default = [] +wasm-extern-trace = [] \ No newline at end of file diff --git a/substrate/substrate/executor/src/error.rs b/substrate/substrate/executor/src/error.rs index c37c75b947..8cdb157c58 100644 --- a/substrate/substrate/executor/src/error.rs +++ b/substrate/substrate/executor/src/error.rs @@ -16,6 +16,7 @@ //! Rust executor possible errors. +use state_machine; use serializer; use wasmi; @@ -74,5 +75,21 @@ error_chain! { description("invalid memory reference"), display("Invalid memory reference"), } + + /// Retry, please. + PleaseRetry { + description("retry needed"), + display("Retry needed"), + } + } +} + +impl state_machine::Error for Error { + fn needs_retry(&self) -> bool { + if let ErrorKind::PleaseRetry = self.0 { + true + } else { + false + } } } diff --git a/substrate/substrate/executor/src/native_executor.rs b/substrate/substrate/executor/src/native_executor.rs index d08e56dd2f..13d19b5447 100644 --- a/substrate/substrate/executor/src/native_executor.rs +++ b/substrate/substrate/executor/src/native_executor.rs @@ -62,7 +62,7 @@ fn fetch_cached_runtime_version<'a, E: Externalities>( cache.entry(gen_cache_key(code)) .or_insert_with(|| { let module = WasmModule::from_buffer(code).expect("all modules compiled with rustc are valid wasm code; qed"); - let version = WasmExecutor{heap_pages: 8}.call_in_wasm_module(ext, &module, "version", &[]).ok() + let version = WasmExecutor::new(8, 8).call_in_wasm_module(ext, &module, "version", &[]).ok() .and_then(|v| RuntimeVersion::decode(&mut v.as_slice())); if let Some(v) = version { @@ -103,6 +103,11 @@ pub trait NativeExecutionDispatch: Send + Sync { /// Get native runtime version. const VERSION: RuntimeVersion; + + /// Construct corresponding `NativeExecutor` with given `heap_pages`. + fn with_heap_pages(min_heap_pages: usize, max_heap_pages: usize) -> NativeExecutor where Self: Sized { + NativeExecutor::with_heap_pages(min_heap_pages, max_heap_pages) + } } /// A generic `CodeExecutor` implementation that uses a delegate to determine wasm code equivalence @@ -116,13 +121,8 @@ pub struct NativeExecutor { } impl NativeExecutor { - /// Create new instance with 128 pages for the wasm fallback's heap. - pub fn new() -> Self { - Self::with_heap_pages(128) - } - /// Create new instance with specific number of pages for wasm fallback's heap. - pub fn with_heap_pages(heap_pages: usize) -> Self { + pub fn with_heap_pages(min_heap_pages: usize, max_heap_pages: usize) -> Self { // FIXME: set this entry at compile time RUNTIMES_CACHE.lock().insert( gen_cache_key(D::native_equivalent()), @@ -130,17 +130,11 @@ impl NativeExecutor { NativeExecutor { _dummy: Default::default(), - fallback: WasmExecutor{heap_pages}, + fallback: WasmExecutor::new(min_heap_pages, max_heap_pages), } } } -impl Default for NativeExecutor { - fn default() -> Self { - Self::new() - } -} - impl Clone for NativeExecutor { fn clone(&self) -> Self { NativeExecutor { @@ -212,11 +206,9 @@ macro_rules! native_executor_instance { $crate::with_native_environment(ext, move || $dispatcher(method, data))? .ok_or_else(|| $crate::error::ErrorKind::MethodNotFound(method.to_owned()).into()) } - } - impl $name { - pub fn with_heap_pages(heap_pages: usize) -> $crate::NativeExecutor<$name> { - $crate::NativeExecutor::with_heap_pages(heap_pages) + fn with_heap_pages(min_heap_pages: usize, max_heap_pages: usize) -> $crate::NativeExecutor<$name> { + $crate::NativeExecutor::with_heap_pages(min_heap_pages, max_heap_pages) } } } diff --git a/substrate/substrate/executor/src/sandbox.rs b/substrate/substrate/executor/src/sandbox.rs index 2557144e12..ec0f2e75bb 100644 --- a/substrate/substrate/executor/src/sandbox.rs +++ b/substrate/substrate/executor/src/sandbox.rs @@ -554,7 +554,7 @@ mod tests { "#).unwrap(); assert_eq!( - WasmExecutor{heap_pages: 8}.call(&mut ext, &test_code[..], "test_sandbox", &code, true).0.unwrap(), + WasmExecutor::new(8, 8).call(&mut ext, &test_code[..], "test_sandbox", &code, true).0.unwrap(), vec![1], ); } @@ -575,7 +575,7 @@ mod tests { "#).unwrap(); assert_eq!( - WasmExecutor{heap_pages: 8}.call(&mut ext, &test_code[..], "test_sandbox", &code, true).0.unwrap(), + WasmExecutor::new(8, 8).call(&mut ext, &test_code[..], "test_sandbox", &code, true).0.unwrap(), vec![0], ); } @@ -613,7 +613,7 @@ mod tests { "#).unwrap(); assert_eq!( - WasmExecutor{heap_pages: 8}.call(&mut ext, &test_code[..], "test_sandbox", &code, true).0.unwrap(), + WasmExecutor::new(8, 8).call(&mut ext, &test_code[..], "test_sandbox", &code, true).0.unwrap(), vec![1], ); } @@ -647,7 +647,7 @@ mod tests { "#).unwrap(); assert_eq!( - WasmExecutor{heap_pages: 8}.call(&mut ext, &test_code[..], "test_sandbox_args", &code, true).0.unwrap(), + WasmExecutor::new(8, 8).call(&mut ext, &test_code[..], "test_sandbox_args", &code, true).0.unwrap(), vec![1], ); } @@ -669,7 +669,7 @@ mod tests { "#).unwrap(); assert_eq!( - WasmExecutor{heap_pages: 8}.call(&mut ext, &test_code[..], "test_sandbox_return_val", &code, true).0.unwrap(), + WasmExecutor::new(8, 8).call(&mut ext, &test_code[..], "test_sandbox_return_val", &code, true).0.unwrap(), vec![1], ); } diff --git a/substrate/substrate/executor/src/wasm_executor.rs b/substrate/substrate/executor/src/wasm_executor.rs index 33b7148311..78e10f46ee 100644 --- a/substrate/substrate/executor/src/wasm_executor.rs +++ b/substrate/substrate/executor/src/wasm_executor.rs @@ -17,6 +17,7 @@ //! Rust implementation of Substrate contracts. use std::cmp::Ordering; +use parking_lot::Mutex; use std::collections::HashMap; use wasmi::{ Module, ModuleInstance, MemoryInstance, MemoryRef, TableRef, ImportsBuilder, @@ -63,6 +64,15 @@ impl Heap { } } +#[cfg(feature="wasm-extern-trace")] +macro_rules! debug_trace { + ( $( $x:tt )* ) => ( trace!( $( $x )* ) ) +} +#[cfg(not(feature="wasm-extern-trace"))] +macro_rules! debug_trace { + ( $( $x:tt )* ) => () +} + struct FunctionExecutor<'e, E: Externalities + 'e> { sandbox_store: sandbox::Store, heap: Heap, @@ -161,52 +171,57 @@ impl_function_executor!(this: FunctionExecutor<'e, E>, ext_memcpy(dest: *mut u8, src: *const u8, count: usize) -> *mut u8 => { this.memory.copy_nonoverlapping(src as usize, dest as usize, count as usize) .map_err(|_| UserError("Invalid attempt to copy_nonoverlapping in ext_memcpy"))?; - trace!(target: "runtime-io", "memcpy {} from {}, {} bytes", dest, src, count); + debug_trace!(target: "runtime-io", "memcpy {} from {}, {} bytes", dest, src, count); Ok(dest) }, ext_memmove(dest: *mut u8, src: *const u8, count: usize) -> *mut u8 => { this.memory.copy(src as usize, dest as usize, count as usize) .map_err(|_| UserError("Invalid attempt to copy in ext_memmove"))?; - trace!(target: "runtime-io", "memmove {} from {}, {} bytes", dest, src, count); + debug_trace!(target: "runtime-io", "memmove {} from {}, {} bytes", dest, src, count); Ok(dest) }, ext_memset(dest: *mut u8, val: u32, count: usize) -> *mut u8 => { - trace!(target: "runtime-io", "memset {} with {}, {} bytes", dest, val, count); + debug_trace!(target: "runtime-io", "memset {} with {}, {} bytes", dest, val, count); this.memory.clear(dest as usize, val as u8, count as usize) .map_err(|_| UserError("Invalid attempt to clear in ext_memset"))?; Ok(dest) }, ext_malloc(size: usize) -> *mut u8 => { let r = this.heap.allocate(size); - trace!(target: "runtime-io", "malloc {} bytes at {}", size, r); + debug_trace!(target: "runtime-io", "malloc {} bytes at {}", size, r); Ok(r) }, ext_free(addr: *mut u8) => { this.heap.deallocate(addr); - trace!(target: "runtime-io", "free {}", addr); + debug_trace!(target: "runtime-io", "free {}", addr); Ok(()) }, ext_set_storage(key_data: *const u8, key_len: u32, value_data: *const u8, value_len: u32) => { let key = this.memory.get(key_data, key_len as usize).map_err(|_| UserError("Invalid attempt to determine key in ext_set_storage"))?; let value = this.memory.get(value_data, value_len as usize).map_err(|_| UserError("Invalid attempt to determine value in ext_set_storage"))?; - if let Some(preimage) = this.hash_lookup.get(&key) { - trace!(target: "wasm-trace", "*** Setting storage: %{} -> {} [k={}]", ascii_format(&preimage), HexDisplay::from(&value), HexDisplay::from(&key)); + if let Some(_preimage) = this.hash_lookup.get(&key) { + debug_trace!(target: "wasm-trace", "*** Setting storage: %{} -> {} [k={}]", ascii_format(&_preimage), HexDisplay::from(&value), HexDisplay::from(&key)); } else { - trace!(target: "wasm-trace", "*** Setting storage: {} -> {} [k={}]", ascii_format(&key), HexDisplay::from(&value), HexDisplay::from(&key)); + debug_trace!(target: "wasm-trace", "*** Setting storage: {} -> {} [k={}]", ascii_format(&key), HexDisplay::from(&value), HexDisplay::from(&key)); } this.ext.set_storage(key, value); Ok(()) }, ext_clear_storage(key_data: *const u8, key_len: u32) => { let key = this.memory.get(key_data, key_len as usize).map_err(|_| UserError("Invalid attempt to determine key in ext_clear_storage"))?; - if let Some(preimage) = this.hash_lookup.get(&key) { - trace!(target: "wasm-trace", "*** Clearing storage: %{} [k={}]", ascii_format(&preimage), HexDisplay::from(&key)); - } else { - trace!(target: "wasm-trace", "*** Clearing storage: {} [k={}]", ascii_format(&key), HexDisplay::from(&key)); - } + debug_trace!(target: "wasm-trace", "*** Clearing storage: {} [k={}]", + if let Some(_preimage) = this.hash_lookup.get(&key) { + format!("%{}", ascii_format(&_preimage)) + } else { + format!(" {}", ascii_format(&key)) + }, HexDisplay::from(&key)); this.ext.clear_storage(&key); Ok(()) }, + ext_exists_storage(key_data: *const u8, key_len: u32) -> u32 => { + let key = this.memory.get(key_data, key_len as usize).map_err(|_| UserError("Invalid attempt to determine key in ext_exists_storage"))?; + Ok(if this.ext.exists_storage(&key) { 1 } else { 0 }) + }, ext_clear_prefix(prefix_data: *const u8, prefix_len: u32) => { let prefix = this.memory.get(prefix_data, prefix_len as usize).map_err(|_| UserError("Invalid attempt to determine prefix in ext_clear_prefix"))?; this.ext.clear_prefix(&prefix); @@ -217,11 +232,19 @@ impl_function_executor!(this: FunctionExecutor<'e, E>, let key = this.memory.get(key_data, key_len as usize).map_err(|_| UserError("Invalid attempt to determine key in ext_get_allocated_storage"))?; let maybe_value = this.ext.storage(&key); - if let Some(preimage) = this.hash_lookup.get(&key) { - trace!(target: "wasm-trace", " Getting storage: %{} == {} [k={}]", ascii_format(&preimage), if let Some(ref b) = maybe_value { format!("{}", HexDisplay::from(b)) } else { "".to_owned() }, HexDisplay::from(&key)); - } else { - trace!(target: "wasm-trace", " Getting storage: {} == {} [k={}]", ascii_format(&key), if let Some(ref b) = maybe_value { format!("{}", HexDisplay::from(b)) } else { "".to_owned() }, HexDisplay::from(&key)); - } + debug_trace!(target: "wasm-trace", "*** Getting storage: {} == {} [k={}]", + if let Some(_preimage) = this.hash_lookup.get(&key) { + format!("%{}", ascii_format(&_preimage)) + } else { + format!(" {}", ascii_format(&key)) + }, + if let Some(ref b) = maybe_value { + format!("{}", HexDisplay::from(b)) + } else { + "".to_owned() + }, + HexDisplay::from(&key) + ); if let Some(value) = maybe_value { let offset = this.heap.allocate(value.len() as u32) as u32; @@ -239,11 +262,20 @@ impl_function_executor!(this: FunctionExecutor<'e, E>, ext_get_storage_into(key_data: *const u8, key_len: u32, value_data: *mut u8, value_len: u32, value_offset: u32) -> u32 => { let key = this.memory.get(key_data, key_len as usize).map_err(|_| UserError("Invalid attempt to get key in ext_get_storage_into"))?; let maybe_value = this.ext.storage(&key); - if let Some(preimage) = this.hash_lookup.get(&key) { - trace!(target: "wasm-trace", " Getting storage: %{} == {} [k={}]", ascii_format(&preimage), if let Some(ref b) = maybe_value { format!("{}", HexDisplay::from(b)) } else { "".to_owned() }, HexDisplay::from(&key)); - } else { - trace!(target: "wasm-trace", " Getting storage: {} == {} [k={}]", ascii_format(&key), if let Some(ref b) = maybe_value { format!("{}", HexDisplay::from(b)) } else { "".to_owned() }, HexDisplay::from(&key)); - } + debug_trace!(target: "wasm-trace", "*** Getting storage: {} == {} [k={}]", + if let Some(_preimage) = this.hash_lookup.get(&key) { + format!("%{}", ascii_format(&_preimage)) + } else { + format!(" {}", ascii_format(&key)) + }, + if let Some(ref b) = maybe_value { + format!("{}", HexDisplay::from(b)) + } else { + "".to_owned() + }, + HexDisplay::from(&key) + ); + if let Some(value) = maybe_value { let value = &value[value_offset as usize..]; let written = ::std::cmp::min(value_len as usize, value.len()); @@ -279,17 +311,20 @@ impl_function_executor!(this: FunctionExecutor<'e, E>, ext_twox_128(data: *const u8, len: u32, out: *mut u8) => { let result = if len == 0 { let hashed = twox_128(&[0u8; 0]); - trace!(target: "xxhash", "XXhash: '' -> {}", HexDisplay::from(&hashed)); + debug_trace!(target: "xxhash", "XXhash: '' -> {}", HexDisplay::from(&hashed)); this.hash_lookup.insert(hashed.to_vec(), vec![]); hashed } else { let key = this.memory.get(data, len as usize).map_err(|_| UserError("Invalid attempt to get key in ext_twox_128"))?; let hashed_key = twox_128(&key); - if let Ok(skey) = ::std::str::from_utf8(&key) { - trace!(target: "xxhash", "XXhash: {} -> {}", skey, HexDisplay::from(&hashed_key)); - } else { - trace!(target: "xxhash", "XXhash: {} -> {}", HexDisplay::from(&key), HexDisplay::from(&hashed_key)); - } + debug_trace!(target: "xxhash", "XXhash: {} -> {}", + if let Ok(_skey) = ::std::str::from_utf8(&key) { + _skey.to_owned() + } else { + format!("{}", HexDisplay::from(&key)) + }, + HexDisplay::from(&hashed_key) + ); this.hash_lookup.insert(hashed_key.to_vec(), key); hashed_key }; @@ -449,14 +484,40 @@ impl_function_executor!(this: FunctionExecutor<'e, E>, /// Wasm rust executor for contracts. /// /// Executes the provided code in a sandboxed wasm runtime. -#[derive(Debug, Default, Clone)] +#[derive(Debug)] pub struct WasmExecutor { - /// The number of pages to allocate for the heap. - pub heap_pages: usize, + /// The min number of pages to allocate for the heap. + pub min_heap_pages: usize, + /// The max number of pages to allocate for the heap. + pub max_heap_pages: usize, + + try_heap_pages: Mutex<(usize, usize)>, } +impl Clone for WasmExecutor { + fn clone(&self) -> Self { + WasmExecutor { + min_heap_pages: self.min_heap_pages, + max_heap_pages: self.max_heap_pages, + try_heap_pages: Mutex::new((self.min_heap_pages, 0)), + } + } +} + +// Number of executions to continue with the old heap_pages before reducing to the next lowest POT. +const DECAY_TIMEOUT: usize = 16; + impl WasmExecutor { + /// Create a new instance. + pub fn new(min_heap_pages: usize, max_heap_pages: usize) -> Self { + WasmExecutor { + min_heap_pages, + max_heap_pages, + try_heap_pages: Mutex::new((min_heap_pages, 0)), + } + } + /// Call a given method in the given wasm-module runtime. pub fn call_in_wasm_module( &self, @@ -487,7 +548,9 @@ impl WasmExecutor { .not_started_instance() .export_by_name("__indirect_function_table") .and_then(|e| e.as_table().cloned()); - let mut fec = FunctionExecutor::new(memory.clone(), self.heap_pages, table, ext)?; + + let mut try_heap_pages = self.try_heap_pages.lock(); + let mut fec = FunctionExecutor::new(memory.clone(), try_heap_pages.0, table, ext)?; // finish instantiation by running 'start' function (if any). let instance = intermediate_instance.run_start(&mut fec)?; @@ -496,14 +559,37 @@ impl WasmExecutor { let offset = fec.heap.allocate(size); memory.set(offset, &data).expect("heap always gives a sensible offset to write"); - let returned = instance.invoke_export( + let result = instance.invoke_export( method, &[ I32(offset as i32), I32(size as i32) ], &mut fec - )?; + ); + + let returned = match result { + Ok(x) => x, + Err(_) if try_heap_pages.0 < self.max_heap_pages => { + let old = try_heap_pages.0; + *try_heap_pages = ((old * 2).min(self.max_heap_pages), DECAY_TIMEOUT); + trace!(target: "wasm-executor", "Shrunk heap size too small at {} pages. Retrying with {}", old, try_heap_pages.0); + return Err(ErrorKind::PleaseRetry.into()) + } + Err(e) => { + trace!(target: "wasm-executor", "Failed to execute code with {} pages", try_heap_pages.0); + return Err(e.into()) + }, + }; + + let decay_timeout = try_heap_pages.1; + if decay_timeout == 0 { + if try_heap_pages.0 > self.min_heap_pages { + *try_heap_pages = (self.min_heap_pages.max(try_heap_pages.0 - 1), DECAY_TIMEOUT); + } + } else { + try_heap_pages.1 -= 1; + } if let Some(I64(r)) = returned { let offset = r as u32; @@ -552,7 +638,7 @@ mod tests { let mut ext = TestExternalities::default(); let test_code = include_bytes!("../wasm/target/wasm32-unknown-unknown/release/runtime_test.compact.wasm"); - let output = WasmExecutor{heap_pages: 8}.call(&mut ext, &test_code[..], "test_empty_return", &[], true).0.unwrap(); + let output = WasmExecutor::new(8, 8).call(&mut ext, &test_code[..], "test_empty_return", &[], true).0.unwrap(); assert_eq!(output, vec![0u8; 0]); } @@ -561,10 +647,10 @@ mod tests { let mut ext = TestExternalities::default(); let test_code = include_bytes!("../wasm/target/wasm32-unknown-unknown/release/runtime_test.compact.wasm"); - let output = WasmExecutor{heap_pages: 8}.call(&mut ext, &test_code[..], "test_panic", &[], true).0; + let output = WasmExecutor::new(8, 8).call(&mut ext, &test_code[..], "test_panic", &[], true).0; assert!(output.is_err()); - let output = WasmExecutor{heap_pages: 8}.call(&mut ext, &test_code[..], "test_conditional_panic", &[2], true).0; + let output = WasmExecutor::new(8, 8).call(&mut ext, &test_code[..], "test_conditional_panic", &[2], true).0; assert!(output.is_err()); } @@ -574,7 +660,7 @@ mod tests { ext.set_storage(b"foo".to_vec(), b"bar".to_vec()); let test_code = include_bytes!("../wasm/target/wasm32-unknown-unknown/release/runtime_test.compact.wasm"); - let output = WasmExecutor{heap_pages: 8}.call(&mut ext, &test_code[..], "test_data_in", b"Hello world", true).0.unwrap(); + let output = WasmExecutor::new(8, 8).call(&mut ext, &test_code[..], "test_data_in", b"Hello world", true).0.unwrap(); assert_eq!(output, b"all ok!".to_vec()); @@ -597,7 +683,7 @@ mod tests { let test_code = include_bytes!("../wasm/target/wasm32-unknown-unknown/release/runtime_test.compact.wasm"); // This will clear all entries which prefix is "ab". - let output = WasmExecutor{heap_pages: 8}.call(&mut ext, &test_code[..], "test_clear_prefix", b"ab", true).0.unwrap(); + let output = WasmExecutor::new(8, 8).call(&mut ext, &test_code[..], "test_clear_prefix", b"ab", true).0.unwrap(); assert_eq!(output, b"all ok!".to_vec()); @@ -614,11 +700,11 @@ mod tests { let mut ext = TestExternalities::default(); let test_code = include_bytes!("../wasm/target/wasm32-unknown-unknown/release/runtime_test.compact.wasm"); assert_eq!( - WasmExecutor{heap_pages: 8}.call(&mut ext, &test_code[..], "test_blake2_256", &[], true).0.unwrap(), + WasmExecutor::new(8, 8).call(&mut ext, &test_code[..], "test_blake2_256", &[], true).0.unwrap(), blake2_256(&b""[..]).encode() ); assert_eq!( - WasmExecutor{heap_pages: 8}.call(&mut ext, &test_code[..], "test_blake2_256", b"Hello world!", true).0.unwrap(), + WasmExecutor::new(8, 8).call(&mut ext, &test_code[..], "test_blake2_256", b"Hello world!", true).0.unwrap(), blake2_256(&b"Hello world!"[..]).encode() ); } @@ -628,11 +714,11 @@ mod tests { let mut ext = TestExternalities::default(); let test_code = include_bytes!("../wasm/target/wasm32-unknown-unknown/release/runtime_test.compact.wasm"); assert_eq!( - WasmExecutor{heap_pages: 8}.call(&mut ext, &test_code[..], "test_twox_256", &[], true).0.unwrap(), + WasmExecutor::new(8, 8).call(&mut ext, &test_code[..], "test_twox_256", &[], true).0.unwrap(), FromHex::from_hex("99e9d85137db46ef4bbea33613baafd56f963c64b1f3685a4eb4abd67ff6203a").unwrap() ); assert_eq!( - WasmExecutor{heap_pages: 8}.call(&mut ext, &test_code[..], "test_twox_256", b"Hello world!", true).0.unwrap(), + WasmExecutor::new(8, 8).call(&mut ext, &test_code[..], "test_twox_256", b"Hello world!", true).0.unwrap(), FromHex::from_hex("b27dfd7f223f177f2a13647b533599af0c07f68bda23d96d059da2b451a35a74").unwrap() ); } @@ -642,11 +728,11 @@ mod tests { let mut ext = TestExternalities::default(); let test_code = include_bytes!("../wasm/target/wasm32-unknown-unknown/release/runtime_test.compact.wasm"); assert_eq!( - WasmExecutor{heap_pages: 8}.call(&mut ext, &test_code[..], "test_twox_128", &[], true).0.unwrap(), + WasmExecutor::new(8, 8).call(&mut ext, &test_code[..], "test_twox_128", &[], true).0.unwrap(), FromHex::from_hex("99e9d85137db46ef4bbea33613baafd5").unwrap() ); assert_eq!( - WasmExecutor{heap_pages: 8}.call(&mut ext, &test_code[..], "test_twox_128", b"Hello world!", true).0.unwrap(), + WasmExecutor::new(8, 8).call(&mut ext, &test_code[..], "test_twox_128", b"Hello world!", true).0.unwrap(), FromHex::from_hex("b27dfd7f223f177f2a13647b533599af").unwrap() ); } @@ -662,7 +748,7 @@ mod tests { calldata.extend_from_slice(sig.as_ref()); assert_eq!( - WasmExecutor{heap_pages: 8}.call(&mut ext, &test_code[..], "test_ed25519_verify", &calldata, true).0.unwrap(), + WasmExecutor::new(8, 8).call(&mut ext, &test_code[..], "test_ed25519_verify", &calldata, true).0.unwrap(), vec![1] ); @@ -672,7 +758,7 @@ mod tests { calldata.extend_from_slice(other_sig.as_ref()); assert_eq!( - WasmExecutor{heap_pages: 8}.call(&mut ext, &test_code[..], "test_ed25519_verify", &calldata, true).0.unwrap(), + WasmExecutor::new(8, 8).call(&mut ext, &test_code[..], "test_ed25519_verify", &calldata, true).0.unwrap(), vec![0] ); } @@ -682,7 +768,7 @@ mod tests { let mut ext = TestExternalities::default(); let test_code = include_bytes!("../wasm/target/wasm32-unknown-unknown/release/runtime_test.compact.wasm"); assert_eq!( - WasmExecutor{heap_pages: 8}.call(&mut ext, &test_code[..], "test_enumerated_trie_root", &[], true).0.unwrap(), + WasmExecutor::new(8, 8).call(&mut ext, &test_code[..], "test_enumerated_trie_root", &[], true).0.unwrap(), ordered_trie_root(vec![b"zero".to_vec(), b"one".to_vec(), b"two".to_vec()]).0.encode() ); } diff --git a/substrate/substrate/runtime-io/with_std.rs b/substrate/substrate/runtime-io/with_std.rs index 4d600ca4cb..329d4609f4 100644 --- a/substrate/substrate/runtime-io/with_std.rs +++ b/substrate/substrate/runtime-io/with_std.rs @@ -68,6 +68,13 @@ pub fn clear_storage(key: &[u8]) { ); } +/// Clear the storage of some particular key. +pub fn exists_storage(key: &[u8]) { + ext::with(|ext| + ext.exists_storage(key) + ); +} + /// Clear the storage entries key of which starts with the given prefix. pub fn clear_prefix(prefix: &[u8]) { ext::with(|ext| diff --git a/substrate/substrate/runtime-io/without_std.rs b/substrate/substrate/runtime-io/without_std.rs index 5f96ea88e7..e72e09a635 100644 --- a/substrate/substrate/runtime-io/without_std.rs +++ b/substrate/substrate/runtime-io/without_std.rs @@ -56,6 +56,7 @@ extern "C" { fn ext_print_num(value: u64); fn ext_set_storage(key_data: *const u8, key_len: u32, value_data: *const u8, value_len: u32); fn ext_clear_storage(key_data: *const u8, key_len: u32); + fn ext_exists_storage(key_data: *const u8, key_len: u32) -> u32; fn ext_clear_prefix(prefix_data: *const u8, prefix_len: u32); fn ext_get_allocated_storage(key_data: *const u8, key_len: u32, written_out: *mut u32) -> *mut u8; fn ext_get_storage_into(key_data: *const u8, key_len: u32, value_data: *mut u8, value_len: u32, value_offset: u32) -> u32; @@ -100,6 +101,15 @@ pub fn clear_storage(key: &[u8]) { } } +/// Clear the storage of some particular key. +pub fn exists_storage(key: &[u8]) { + unsafe { + ext_exists_storage( + key.as_ptr(), key.len() as u32 + ) != 0; + } +} + /// Clear the storage entries key of which starts with the given prefix. pub fn clear_prefix(prefix: &[u8]) { unsafe { diff --git a/substrate/substrate/service/src/config.rs b/substrate/substrate/service/src/config.rs index 58a3db7052..5a294d22e5 100644 --- a/substrate/substrate/service/src/config.rs +++ b/substrate/substrate/service/src/config.rs @@ -49,6 +49,10 @@ pub struct Configuration { pub name: String, /// Execution strategy. pub execution_strategy: ExecutionStrategy, + /// Minimum number of heap pages to allocate for Wasm execution. + pub min_heap_pages: usize, + /// Maximum number of heap pages to allocate for Wasm execution. + pub max_heap_pages: usize, } impl Configuration { @@ -66,6 +70,8 @@ impl Configuration { telemetry: Default::default(), pruning: PruningMode::ArchiveAll, execution_strategy: ExecutionStrategy::Both, + min_heap_pages: 8, + max_heap_pages: 512, }; configuration.network.boot_nodes = configuration.chain_spec.boot_nodes().to_vec(); configuration diff --git a/substrate/substrate/service/src/lib.rs b/substrate/substrate/service/src/lib.rs index 311b256e1a..bbd8bd1778 100644 --- a/substrate/substrate/service/src/lib.rs +++ b/substrate/substrate/service/src/lib.rs @@ -57,6 +57,7 @@ use network::ManageNetwork; use runtime_primitives::traits::{Header, As}; use exit_future::Signal; use tokio::runtime::TaskExecutor; +use substrate_executor::NativeExecutor; pub use self::error::{ErrorKind, Error}; pub use config::{Configuration, Roles, PruningMode}; @@ -83,7 +84,7 @@ pub struct Service { pub fn new_client(config: Configuration) -> Result>>, error::Error> { - let executor = components::CodeExecutor::::default(); + let executor = NativeExecutor::with_heap_pages(config.min_heap_pages, config.max_heap_pages); let (client, _) = components::FullComponents::::build_client( &config, executor, @@ -105,7 +106,7 @@ impl Service let (signal, exit) = ::exit_future::signal(); // Create client - let executor = components::CodeExecutor::::default(); + let executor = NativeExecutor::with_heap_pages(config.min_heap_pages, config.max_heap_pages); let mut keystore = Keystore::open(config.keystore_path.as_str().into())?; for seed in &config.keys { diff --git a/substrate/substrate/state-machine/src/backend.rs b/substrate/substrate/state-machine/src/backend.rs index 457a40dcc5..1d3b2108f9 100644 --- a/substrate/substrate/state-machine/src/backend.rs +++ b/substrate/substrate/state-machine/src/backend.rs @@ -35,6 +35,11 @@ pub trait Backend: TryIntoTrieBackend { /// Get keyed storage associated with specific address, or None if there is nothing associated. fn storage(&self, key: &[u8]) -> Result>, Self::Error>; + /// true if a key exists in storage. + fn exists_storage(&self, key: &[u8]) -> Result { + Ok(self.storage(key)?.is_some()) + } + /// Retrieve all entries keys of which start with the given prefix and /// call `f` for each of those keys. fn for_keys_with_prefix(&self, prefix: &[u8], f: F); @@ -101,6 +106,8 @@ impl From, Vec>> for InMemory { } } +impl super::Error for Void {} + impl Backend for InMemory { type Error = Void; type Transaction = Vec<(Vec, Option>)>; @@ -109,6 +116,10 @@ impl Backend for InMemory { Ok(self.inner.get(key).map(Clone::clone)) } + fn exists_storage(&self, key: &[u8]) -> Result { + Ok(self.inner.get(key).is_some()) + } + fn for_keys_with_prefix(&self, prefix: &[u8], f: F) { self.inner.keys().filter(|key| key.starts_with(prefix)).map(|k| &**k).for_each(f); } diff --git a/substrate/substrate/state-machine/src/ext.rs b/substrate/substrate/state-machine/src/ext.rs index 5c5b9b5736..cd177b1984 100644 --- a/substrate/substrate/state-machine/src/ext.rs +++ b/substrate/substrate/state-machine/src/ext.rs @@ -107,6 +107,13 @@ impl<'a, B: 'a> Externalities for Ext<'a, B> self.backend.storage(key).expect("Externalities not allowed to fail within runtime")) } + fn exists_storage(&self, key: &[u8]) -> bool { + match self.overlay.storage(key) { + Some(x) => x.is_some(), + _ => self.backend.exists_storage(key).expect("Externalities not allowed to fail within runtime"), + } + } + fn place_storage(&mut self, key: Vec, value: Option>) { self.mark_dirty(); self.overlay.set_storage(key, value); diff --git a/substrate/substrate/state-machine/src/lib.rs b/substrate/substrate/state-machine/src/lib.rs index 3b01db3250..7a69dc2c21 100644 --- a/substrate/substrate/state-machine/src/lib.rs +++ b/substrate/substrate/state-machine/src/lib.rs @@ -95,8 +95,12 @@ impl OverlayedChanges { /// State Machine Error bound. /// /// This should reflect WASM error type bound for future compatibility. -pub trait Error: 'static + fmt::Debug + fmt::Display + Send {} -impl Error for E where E: 'static + fmt::Debug + fmt::Display + Send {} +pub trait Error: 'static + fmt::Debug + fmt::Display + Send { + /// Error implies execution should be retried. + fn needs_retry(&self) -> bool { false } +} + +impl Error for ExecutionError {} /// Externalities Error. /// @@ -132,6 +136,11 @@ pub trait Externalities { self.place_storage(key.to_vec(), None); } + /// Clear a storage entry (`key`) of current contract being called (effective immediately). + fn exists_storage(&self, key: &[u8]) -> bool { + self.storage(key).is_some() + } + /// Clear storage entries which keys are start with the given prefix. fn clear_prefix(&mut self, prefix: &[u8]); @@ -257,46 +266,65 @@ pub fn execute_using_consensus_failure_handler< manager: ExecutionManager, ) -> Result<(Vec, B::Transaction), Box> { let strategy: ExecutionStrategy = (&manager).into(); + + // make a copy. + let code = ext::Ext::new(overlay, backend).storage(b":code") + .ok_or(Box::new(ExecutionError::CodeEntryDoesNotExist) as Box)? + .to_vec(); + let result = { - let maybe_orig_prospective = - if let &ExecutionManager::Both(_) = &manager { Some(overlay.prospective.clone()) } else { None }; + let mut orig_prospective = overlay.prospective.clone(); - let ((result, was_native), code, delta) = { - let mut externalities = ext::Ext::new(overlay, backend); - // make a copy. - let code = externalities.storage(b":code") - .ok_or(Box::new(ExecutionError::CodeEntryDoesNotExist) as Box)? - .to_vec(); + let (result, was_native, delta) = loop { + let ((result, was_native), delta) = { + let mut externalities = ext::Ext::new(overlay, backend); + ( + exec.call( + &mut externalities, + &code, + method, + call_data, + // attempt to run native first, if we're not directed to run wasm only + strategy != ExecutionStrategy::AlwaysWasm, + ), + externalities.transaction() + ) + }; - ( - exec.call( - &mut externalities, - &code, - method, - call_data, - // attempt to run native first, if we're not directed to run wasm only - strategy != ExecutionStrategy::AlwaysWasm, - ), - code, - externalities.transaction() - ) + if result.as_ref().err().map_or(false, |e| e.needs_retry()) { + overlay.prospective = orig_prospective.clone(); + } else { + break (result, was_native, delta) + } }; - - // run wasm separately if we did run native the first time and we're meant to run both - let (result, delta) = if let (true, ExecutionManager::Both(on_consensus_failure), Some(mut orig_prospective)) = - (was_native, manager, maybe_orig_prospective) - { - ::std::mem::swap(&mut orig_prospective, &mut overlay.prospective); - let mut externalities = ext::Ext::new(overlay, backend); - let (wasm_result, _) = exec.call( - &mut externalities, - &code, - method, - call_data, - false - ); - let wasm_delta = externalities.transaction(); + // run wasm separately if we did run native the first time and we're meant to run both + let (result, delta) = if let (true, ExecutionManager::Both(on_consensus_failure)) = + (was_native, manager) + { + overlay.prospective = orig_prospective.clone(); + + let (wasm_result, wasm_delta) = loop { + let ((result, _), delta) = { + let mut externalities = ext::Ext::new(overlay, backend); + ( + exec.call( + &mut externalities, + &code, + method, + call_data, + false, + ), + externalities.transaction() + ) + }; + + if result.as_ref().err().map_or(false, |e| e.needs_retry()) { + overlay.prospective = orig_prospective.clone(); + } else { + break (result, delta) + } + }; if (result.is_ok() && wasm_result.is_ok() && result.as_ref().unwrap() == wasm_result.as_ref().unwrap()/* && delta == wasm_delta*/) || (result.is_err() && wasm_result.is_err() && format!("{}", result.as_ref().unwrap_err()) == format!("{}", wasm_result.as_ref().unwrap_err())) @@ -393,6 +421,8 @@ mod tests { } } + impl Error for u8 {} + #[test] fn overlayed_storage_works() { let mut overlayed = OverlayedChanges::default(); diff --git a/substrate/substrate/state-machine/src/trie_backend.rs b/substrate/substrate/state-machine/src/trie_backend.rs index 628bb04fbf..098d0f6df3 100644 --- a/substrate/substrate/state-machine/src/trie_backend.rs +++ b/substrate/substrate/state-machine/src/trie_backend.rs @@ -82,6 +82,8 @@ impl TrieBackend { } } +impl super::Error for String {} + impl Backend for TrieBackend { type Error = String; type Transaction = MemoryDB; diff --git a/substrate/substrate/test-client/src/client_ext.rs b/substrate/substrate/test-client/src/client_ext.rs index cce646bd9c..be1e9dfe55 100644 --- a/substrate/substrate/test-client/src/client_ext.rs +++ b/substrate/substrate/test-client/src/client_ext.rs @@ -20,9 +20,10 @@ use client::{self, Client}; use keyring::Keyring; use runtime_primitives::StorageMap; use runtime::genesismap::{GenesisConfig, additional_storage_with_genesis}; +use executor::NativeExecutor; use runtime; use bft; -use {Backend, Executor, NativeExecutor}; +use {Backend, Executor}; /// Extension trait for a test client. pub trait TestClient { @@ -38,7 +39,7 @@ pub trait TestClient { impl TestClient for Client { fn new_for_tests() -> Self { - client::new_in_mem(NativeExecutor::with_heap_pages(8), genesis_storage()).unwrap() + client::new_in_mem(NativeExecutor::with_heap_pages(8, 8), genesis_storage()).unwrap() } fn justify_and_import(&self, origin: client::BlockOrigin, block: runtime::Block) -> client::error::Result<()> { diff --git a/substrate/substrate/test-client/src/lib.rs b/substrate/substrate/test-client/src/lib.rs index 267581bc8f..d50b49b3c7 100644 --- a/substrate/substrate/test-client/src/lib.rs +++ b/substrate/substrate/test-client/src/lib.rs @@ -34,21 +34,21 @@ mod client_ext; pub use client_ext::TestClient; -mod native_executor { +mod local_executor { #![allow(missing_docs)] use super::runtime; - native_executor_instance!(pub NativeExecutor, runtime::api::dispatch, runtime::VERSION, include_bytes!("../../test-runtime/wasm/target/wasm32-unknown-unknown/release/substrate_test_runtime.compact.wasm")); + native_executor_instance!(pub LocalExecutor, runtime::api::dispatch, runtime::VERSION, include_bytes!("../../test-runtime/wasm/target/wasm32-unknown-unknown/release/substrate_test_runtime.compact.wasm")); } /// Native executor used for tests. -pub use self::native_executor::NativeExecutor; +pub use local_executor::LocalExecutor; /// Test client database backend. pub type Backend = client::in_mem::Backend; /// Test client executor. -pub type Executor = client::LocalCallExecutor>; +pub type Executor = client::LocalCallExecutor>; /// Creates new client instance used for tests. pub fn new() -> client::Client {