Adaptive heap size (#328)

* heap-size is a CLI arg, make it 512 by default

* Fix tests

* Adaptive heap size.

* Allow storage_exists
This commit is contained in:
Gav Wood
2018-07-16 20:59:10 +02:00
committed by GitHub
parent bb8987df28
commit e75d7d8fda
23 changed files with 360 additions and 137 deletions
+3 -2
View File
@@ -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<UncheckedExtrinsic, BlockId, Hash> for DummyPool {
@@ -107,7 +108,7 @@ pub fn run<I, T>(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<I, T>(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::<NativeExecutor<demo_executor::Executor>, Block, _>(executor, genesis_config)?);
let mut runtime = Runtime::new()?;
let _rpc_servers = {
let handler = || {
+12 -11
View File
@@ -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> {
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));
+1 -1
View File
@@ -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]
+20
View File
@@ -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.
+24
View File
@@ -237,6 +237,13 @@ pub fn run<I, T, W>(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<E>(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();
+2
View File
@@ -138,3 +138,5 @@ impl Error {
ErrorKind::Execution(e).into()
}
}
impl state_machine::Error for Error {}
+4 -4
View File
@@ -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> {
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<Transfer>) -> (Vec<u8>, 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,
@@ -140,6 +140,7 @@ fn do_check_execution_proof<Hash, E>(
#[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(),
+4
View File
@@ -27,3 +27,7 @@ log = "0.3"
[dev-dependencies]
assert_matches = "1.1"
wabt = "0.4"
[features]
default = []
wasm-extern-trace = []
+17
View File
@@ -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
}
}
}
@@ -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<Self> 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<D: NativeExecutionDispatch> {
}
impl<D: NativeExecutionDispatch> NativeExecutor<D> {
/// 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<D: NativeExecutionDispatch> NativeExecutor<D> {
NativeExecutor {
_dummy: Default::default(),
fallback: WasmExecutor{heap_pages},
fallback: WasmExecutor::new(min_heap_pages, max_heap_pages),
}
}
}
impl<D: NativeExecutionDispatch> Default for NativeExecutor<D> {
fn default() -> Self {
Self::new()
}
}
impl<D: NativeExecutionDispatch> Clone for NativeExecutor<D> {
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)
}
}
}
+5 -5
View File
@@ -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],
);
}
+135 -49
View File
@@ -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 { "<empty>".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 { "<empty>".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 {
"<empty>".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 { "<empty>".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 { "<empty>".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 {
"<empty>".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<E: Externalities>(
&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()
);
}
@@ -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|
@@ -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 {
@@ -49,6 +49,10 @@ pub struct Configuration<G: Serialize + DeserializeOwned + BuildStorage> {
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<G: Serialize + DeserializeOwned + BuildStorage> Configuration<G> {
@@ -66,6 +70,8 @@ impl<G: Serialize + DeserializeOwned + BuildStorage> Configuration<G> {
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
+3 -2
View File
@@ -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<Components: components::Components> {
pub fn new_client<Factory: components::ServiceFactory>(config: Configuration<Factory::Genesis>)
-> Result<Arc<ComponentClient<components::FullComponents<Factory>>>, error::Error>
{
let executor = components::CodeExecutor::<Factory>::default();
let executor = NativeExecutor::with_heap_pages(config.min_heap_pages, config.max_heap_pages);
let (client, _) = components::FullComponents::<Factory>::build_client(
&config,
executor,
@@ -105,7 +106,7 @@ impl<Components> Service<Components>
let (signal, exit) = ::exit_future::signal();
// Create client
let executor = components::CodeExecutor::<Components::Factory>::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 {
@@ -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<Option<Vec<u8>>, Self::Error>;
/// true if a key exists in storage.
fn exists_storage(&self, key: &[u8]) -> Result<bool, Self::Error> {
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<F: FnMut(&[u8])>(&self, prefix: &[u8], f: F);
@@ -101,6 +106,8 @@ impl From<HashMap<Vec<u8>, Vec<u8>>> for InMemory {
}
}
impl super::Error for Void {}
impl Backend for InMemory {
type Error = Void;
type Transaction = Vec<(Vec<u8>, Option<Vec<u8>>)>;
@@ -109,6 +116,10 @@ impl Backend for InMemory {
Ok(self.inner.get(key).map(Clone::clone))
}
fn exists_storage(&self, key: &[u8]) -> Result<bool, Self::Error> {
Ok(self.inner.get(key).is_some())
}
fn for_keys_with_prefix<F: FnMut(&[u8])>(&self, prefix: &[u8], f: F) {
self.inner.keys().filter(|key| key.starts_with(prefix)).map(|k| &**k).for_each(f);
}
@@ -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<u8>, value: Option<Vec<u8>>) {
self.mark_dirty();
self.overlay.set_storage(key, value);
+67 -37
View File
@@ -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<E> 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<Handler>,
) -> Result<(Vec<u8>, B::Transaction), Box<Error>> {
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<Error>)?
.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<Error>)?
.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();
@@ -82,6 +82,8 @@ impl TrieBackend {
}
}
impl super::Error for String {}
impl Backend for TrieBackend {
type Error = String;
type Transaction = MemoryDB;
@@ -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<Backend, Executor, runtime::Block> {
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<()> {
+4 -4
View File
@@ -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<runtime::Block>;
/// Test client executor.
pub type Executor = client::LocalCallExecutor<Backend, executor::NativeExecutor<NativeExecutor>>;
pub type Executor = client::LocalCallExecutor<Backend, executor::NativeExecutor<LocalExecutor>>;
/// Creates new client instance used for tests.
pub fn new() -> client::Client<Backend, Executor, runtime::Block> {