diff --git a/Cargo.lock b/Cargo.lock index 0396556fa5105..eb03db6381eca 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2162,7 +2162,9 @@ dependencies = [ "ed25519 0.1.0", "error-chain 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)", "hex-literal 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", + "parking_lot 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", "rustc-hex 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.64 (registry+https://github.com/rust-lang/crates.io-index)", "serde_derive 1.0.64 (registry+https://github.com/rust-lang/crates.io-index)", @@ -2173,6 +2175,7 @@ dependencies = [ "substrate-serializer 0.1.0", "substrate-state-machine 0.1.0", "triehash 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "twox-hash 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "wabt 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "wasmi 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", ] diff --git a/substrate/executor/Cargo.toml b/substrate/executor/Cargo.toml index 9e04e1ede34b6..e127b0d36ea6e 100644 --- a/substrate/executor/Cargo.toml +++ b/substrate/executor/Cargo.toml @@ -19,6 +19,9 @@ byteorder = "1.1" rustc-hex = "1.0.0" triehash = "0.1.0" hex-literal = "0.1.0" +twox-hash = "1.1.0" +lazy_static = "1.0" +parking_lot = "*" log = "0.3" [dev-dependencies] diff --git a/substrate/executor/src/lib.rs b/substrate/executor/src/lib.rs index 2983cf9f54e33..352106a74d2eb 100644 --- a/substrate/executor/src/lib.rs +++ b/substrate/executor/src/lib.rs @@ -41,8 +41,13 @@ extern crate wasmi; extern crate byteorder; extern crate rustc_hex; extern crate triehash; +extern crate parking_lot; +extern crate twox_hash; #[macro_use] extern crate log; +#[macro_use] +extern crate lazy_static; + #[macro_use] extern crate error_chain; diff --git a/substrate/executor/src/native_executor.rs b/substrate/executor/src/native_executor.rs index 5625082604c12..72ba2d33a727d 100644 --- a/substrate/executor/src/native_executor.rs +++ b/substrate/executor/src/native_executor.rs @@ -17,10 +17,35 @@ use error::{Error, ErrorKind, Result}; use state_machine::{CodeExecutor, Externalities}; use wasm_executor::WasmExecutor; +use wasmi::Module as WasmModule; use runtime_version::RuntimeVersion; +use std::collections::HashMap; use codec::Slicable; +use twox_hash::XxHash; +use std::hash::Hasher; +use parking_lot::Mutex; use RuntimeInfo; +// For the internal Runtime Cache +enum RunWith { + NativeRuntime, + WasmRuntime(WasmModule) +} + +lazy_static! { + static ref RUNTIMES_CACHE: Mutex> = Mutex::new(HashMap::new()); +} + +// helper function to generate low-over-head caching_keys +// it is asserted that part of the audit process that any potential on-chain code change +// will have done is to ensure that the two-x hash is different to that of any other +// :code value from the same chain +fn gen_cache_key(code: &[u8]) -> u64 { + let mut h = XxHash::with_seed(0); + h.write(code); + h.finish() +} + fn safe_call(f: F) -> Result where F: ::std::panic::UnwindSafe + FnOnce() -> U { @@ -60,6 +85,11 @@ pub struct NativeExecutor { impl NativeExecutor { /// Create new instance. pub fn new() -> Self { + // FIXME: set this entry at compile time + RUNTIMES_CACHE.lock().insert( + gen_cache_key(D::native_equivalent()), + RunWith::NativeRuntime); + NativeExecutor { _dummy: Default::default(), } @@ -88,17 +118,24 @@ impl CodeExecutor for NativeExecutor Result> { - if code == D::native_equivalent() { - // call native - D::dispatch(ext, method, data) - } else { - let version = WasmExecutor.call(ext, code, "version", &[])?; - let version = RuntimeVersion::decode(&mut version.as_slice()); - if version.map_or(false, |v| D::VERSION.can_call_with(&v)) { - return D::dispatch(ext, method, data) - } - // call into wasm. - WasmExecutor.call(ext, code, method, data) + let mut cache = RUNTIMES_CACHE.lock(); + let r = cache.entry(gen_cache_key(code)) + .or_insert_with(|| { + let wasm_module = WasmModule::from_buffer(code) + .expect("all modules compiled with rustc are valid wasm code; qed"); + + if WasmExecutor.call_in_wasm_module(ext, &wasm_module, "version", &[]).ok() + .and_then(|version| RuntimeVersion::decode(&mut version.as_slice())) + .map_or(false, |v| D::VERSION.can_call_with(&v)) + { + RunWith::NativeRuntime + } else { + RunWith::WasmRuntime(wasm_module) + } + }); + match r { + RunWith::NativeRuntime => D::dispatch(ext, method, data), + RunWith::WasmRuntime(module) => WasmExecutor.call_in_wasm_module(ext, &module, method, data) } } } diff --git a/substrate/executor/src/wasm_executor.rs b/substrate/executor/src/wasm_executor.rs index f66b81bedf918..f48a3a93bc042 100644 --- a/substrate/executor/src/wasm_executor.rs +++ b/substrate/executor/src/wasm_executor.rs @@ -468,21 +468,19 @@ impl_function_executor!(this: FunctionExecutor<'e, E>, #[derive(Debug, Default, Clone)] pub struct WasmExecutor; -impl CodeExecutor for WasmExecutor { - type Error = Error; +impl WasmExecutor { - fn call( + /// Call a given method in the given wasm-module runtime. + pub fn call_in_wasm_module( &self, ext: &mut E, - code: &[u8], + module: &Module, method: &str, data: &[u8], ) -> Result> { - let module = Module::from_buffer(code).expect("all modules compiled with rustc are valid wasm code; qed"); - // start module instantiation. Don't run 'start' function yet. let intermediate_instance = ModuleInstance::new( - &module, + module, &ImportsBuilder::new() .with_resolver("env", FunctionExecutor::::resolver()) )?; @@ -492,6 +490,8 @@ impl CodeExecutor for WasmExecutor { let memory = intermediate_instance .not_started_instance() .export_by_name("memory") + // TODO: with code coming from the blockchain it isn't strictly been compiled with rustc anymore. + // these assumptions are probably not true anymore .expect("all modules compiled with rustc should have an export named 'memory'; qed") .as_memory() .expect("in module generated by rustc export named 'memory' should be a memory; qed") @@ -529,6 +529,21 @@ impl CodeExecutor for WasmExecutor { } } +impl CodeExecutor for WasmExecutor { + type Error = Error; + + fn call( + &self, + ext: &mut E, + code: &[u8], + method: &str, + data: &[u8], + ) -> Result> { + let module = Module::from_buffer(code)?; + self.call_in_wasm_module(ext, &module, method, data) + } +} + #[cfg(test)] mod tests { use super::*;