From 490083248c758734a14689cf0ea586b0a7fe0502 Mon Sep 17 00:00:00 2001 From: Benjamin Kampmann Date: Fri, 6 Jul 2018 12:57:44 +0200 Subject: [PATCH] Cache the RuntimeVersion for code blocks, too (#283) * Refactor to provide performance improvements for RuntimeVersion lookup, too. Provides a new runtime_version function which, given a specific , returns the runtime version for that block, either cached or by executing the block and caching the result. Exposes that function through all clients to make most use of that performance improvements. --- .../substrate/client/src/call_executor.rs | 19 ++++- substrate/substrate/client/src/client.rs | 7 +- .../client/src/light/call_executor.rs | 7 ++ substrate/substrate/executor/src/error.rs | 6 ++ substrate/substrate/executor/src/lib.rs | 7 ++ .../substrate/executor/src/native_executor.rs | 80 +++++++++++++------ 6 files changed, 94 insertions(+), 32 deletions(-) diff --git a/substrate/substrate/client/src/call_executor.rs b/substrate/substrate/client/src/call_executor.rs index 498190cc6a..36d6ced1e5 100644 --- a/substrate/substrate/client/src/call_executor.rs +++ b/substrate/substrate/client/src/call_executor.rs @@ -17,7 +17,8 @@ use std::sync::Arc; use runtime_primitives::generic::BlockId; use runtime_primitives::traits::Block as BlockT; -use state_machine::{self, OverlayedChanges, Backend as StateBackend, CodeExecutor}; +use state_machine::{self, OverlayedChanges, Ext, Backend as StateBackend, CodeExecutor}; +use runtime_io::Externalities; use executor::{RuntimeVersion, RuntimeInfo}; use backend; @@ -42,6 +43,11 @@ pub trait CallExecutor { /// No changes are made. fn call(&self, id: &BlockId, method: &str, call_data: &[u8]) -> Result; + /// Extract RuntimeVersion of given block + /// + /// No changes are made. + fn runtime_version(&self, id: &BlockId) -> Result; + /// Execute a call to a contract on top of given state. /// /// No changes are made. @@ -94,6 +100,17 @@ impl CallExecutor for LocalCallExecutor Ok(CallResult{ return_data, changes }) } + fn runtime_version(&self, id: &BlockId) -> error::Result { + let mut overlay = OverlayedChanges::default(); + let state = self.backend.state_at(*id)?; + let mut externalities = Ext::new(&mut overlay, &state); + let code = externalities.storage(b":code").ok_or(error::ErrorKind::VersionInvalid)? + .to_vec(); + + self.executor.runtime_version(&mut externalities, &code) + .ok_or(error::ErrorKind::VersionInvalid.into()) + } + fn call_at_state(&self, state: &S, changes: &mut OverlayedChanges, method: &str, call_data: &[u8]) -> error::Result<(Vec, S::Transaction)> { state_machine::execute( state, diff --git a/substrate/substrate/client/src/client.rs b/substrate/substrate/client/src/client.rs index e63933d897..1438e66ead 100644 --- a/substrate/substrate/client/src/client.rs +++ b/substrate/substrate/client/src/client.rs @@ -216,13 +216,10 @@ impl Client where .ok_or(error::ErrorKind::AuthLenInvalid.into())) } - /// Get the set of authorities at a given block. + /// Get the RuntimeVersion at a given block. pub fn runtime_version_at(&self, id: &BlockId) -> error::Result { // TODO: Post Poc-2 return an error if version is missing - Ok(self.executor.call(id, "version", &[]) - .and_then(|r| RuntimeVersion::decode(&mut &r.return_data[..]) - .ok_or(error::ErrorKind::VersionInvalid.into())) - .unwrap_or_default()) + self.executor.runtime_version(id) } /// Get call executor reference. diff --git a/substrate/substrate/client/src/light/call_executor.rs b/substrate/substrate/client/src/light/call_executor.rs index a052d7eee3..104e5e81cc 100644 --- a/substrate/substrate/client/src/light/call_executor.rs +++ b/substrate/substrate/client/src/light/call_executor.rs @@ -29,6 +29,7 @@ use call_executor::{CallExecutor, CallResult}; use error::{Error as ClientError, ErrorKind as ClientErrorKind, Result as ClientResult}; use light::fetcher::{Fetcher, RemoteCallRequest}; use executor::RuntimeVersion; +use codec::Slicable; /// Call executor that executes methods on remote node, querying execution proof /// and checking proof by re-executing locally. @@ -66,6 +67,12 @@ impl CallExecutor for RemoteCallExecutor }).into_future().wait() } + fn runtime_version(&self, id: &BlockId) -> ClientResult { + let call_result = self.call(id, "version", &[])?; + RuntimeVersion::decode(&mut call_result.return_data.as_slice()) + .ok_or_else(|| ClientErrorKind::VersionInvalid.into()) + } + fn call_at_state(&self, _state: &S, _changes: &mut OverlayedChanges, _method: &str, _call_data: &[u8]) -> ClientResult<(Vec, S::Transaction)> { Err(ClientErrorKind::NotAvailableOnLightClient.into()) } diff --git a/substrate/substrate/executor/src/error.rs b/substrate/substrate/executor/src/error.rs index 639d23c075..c37c75b947 100644 --- a/substrate/substrate/executor/src/error.rs +++ b/substrate/substrate/executor/src/error.rs @@ -39,6 +39,12 @@ error_chain! { display("Invalid Code: {:?}", c), } + /// Cound not get runtime version. + VersionInvalid { + description("Runtime version error"), + display("On-chain runtime does not specify version"), + } + /// Externalities have failed. Externalities { description("externalities failure"), diff --git a/substrate/substrate/executor/src/lib.rs b/substrate/substrate/executor/src/lib.rs index 352106a74d..ddf631ba5f 100644 --- a/substrate/substrate/executor/src/lib.rs +++ b/substrate/substrate/executor/src/lib.rs @@ -75,4 +75,11 @@ pub use codec::Slicable; pub trait RuntimeInfo { /// Native runtime information if any. const NATIVE_VERSION: Option; + + /// Extract RuntimeVersion of given :code block + fn runtime_version ( + &self, + ext: &mut E, + code: &[u8] + ) -> Option; } diff --git a/substrate/substrate/executor/src/native_executor.rs b/substrate/substrate/executor/src/native_executor.rs index 724c44299f..0191c41ff5 100644 --- a/substrate/substrate/executor/src/native_executor.rs +++ b/substrate/substrate/executor/src/native_executor.rs @@ -23,17 +23,21 @@ use std::collections::HashMap; use codec::Slicable; use twox_hash::XxHash; use std::hash::Hasher; -use parking_lot::Mutex; +use parking_lot::{Mutex, MutexGuard}; use RuntimeInfo; -// For the internal Runtime Cache +// For the internal Runtime Cache: +// Do we run this natively or use the given WasmModule enum RunWith { - NativeRuntime, - WasmRuntime(WasmModule) + InvalidVersion(WasmModule), + NativeRuntime(RuntimeVersion), + WasmRuntime(RuntimeVersion, WasmModule) } +type CacheType = HashMap; + lazy_static! { - static ref RUNTIMES_CACHE: Mutex> = Mutex::new(HashMap::new()); + static ref RUNTIMES_CACHE: Mutex = Mutex::new(HashMap::new()); } // helper function to generate low-over-head caching_keys @@ -46,6 +50,34 @@ fn gen_cache_key(code: &[u8]) -> u64 { h.finish() } +/// fetch a runtime version from the cache or if there is no cached version yet, create +/// the runtime version entry for `code`, determines whether `RunWith::NativeRuntime` +/// can be used by by comparing returned RuntimeVersion to `ref_version` +fn fetch_cached_runtime_version<'a, E: Externalities>( + cache: &'a mut MutexGuard, + ext: &mut E, + code: &[u8], + ref_version: RuntimeVersion +) -> &'a RunWith { + 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.call_in_wasm_module(ext, &module, "version", &[]).ok() + .and_then(|v| RuntimeVersion::decode(&mut v.as_slice())); + + + if let Some(v) = version { + if ref_version.can_call_with(&v) { + RunWith::NativeRuntime(v) + } else { + RunWith::WasmRuntime(v, module) + } + } else { + RunWith::InvalidVersion(module) + } + }) +} + fn safe_call(f: F) -> Result where F: ::std::panic::UnwindSafe + FnOnce() -> U { @@ -88,7 +120,7 @@ impl NativeExecutor { // FIXME: set this entry at compile time RUNTIMES_CACHE.lock().insert( gen_cache_key(D::native_equivalent()), - RunWith::NativeRuntime); + RunWith::NativeRuntime(D::VERSION)); NativeExecutor { _dummy: Default::default(), @@ -106,6 +138,18 @@ impl Clone for NativeExecutor { impl RuntimeInfo for NativeExecutor { const NATIVE_VERSION: Option = Some(D::VERSION); + + fn runtime_version( + &self, + ext: &mut E, + code: &[u8], + ) -> Option { + let mut c = RUNTIMES_CACHE.lock(); + match fetch_cached_runtime_version(&mut c, ext, code, D::VERSION) { + RunWith::NativeRuntime(v) | RunWith::WasmRuntime(v, _) => Some(v.clone()), + RunWith::InvalidVersion(_m) => None + } + } } impl CodeExecutor for NativeExecutor { @@ -118,26 +162,10 @@ impl CodeExecutor for NativeExecutor Result> { - 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"); - - // Missing version export is allowed in Poc-2 for Poc-1 compatibility. - // TODO: return an error on missing version. - 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) + let mut c = RUNTIMES_CACHE.lock(); + match fetch_cached_runtime_version(&mut c, ext, code, D::VERSION) { + RunWith::NativeRuntime(_v) => D::dispatch(ext, method, data), + RunWith::WasmRuntime(_, m) | RunWith::InvalidVersion(m) => WasmExecutor.call_in_wasm_module(ext, m, method, data) } } }