mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-04-28 16:47:57 +00:00
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:
@@ -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 = || {
|
||||
|
||||
@@ -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));
|
||||
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -138,3 +138,5 @@ impl Error {
|
||||
ErrorKind::Execution(e).into()
|
||||
}
|
||||
}
|
||||
|
||||
impl state_machine::Error for Error {}
|
||||
@@ -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(),
|
||||
|
||||
@@ -27,3 +27,7 @@ log = "0.3"
|
||||
[dev-dependencies]
|
||||
assert_matches = "1.1"
|
||||
wabt = "0.4"
|
||||
|
||||
[features]
|
||||
default = []
|
||||
wasm-extern-trace = []
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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],
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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<()> {
|
||||
|
||||
@@ -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> {
|
||||
|
||||
Reference in New Issue
Block a user