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) } } }