diff --git a/polkadot/cli/src/cli.yml b/polkadot/cli/src/cli.yml index bae81c5fd0eb0..bb6f1defbf960 100644 --- a/polkadot/cli/src/cli.yml +++ b/polkadot/cli/src/cli.yml @@ -74,7 +74,7 @@ args: - pruning: long: pruning value_name: PRUNING_MODE - help: Specify the pruning mode. (a number of blocks to keep or "archive"). Default is 256. + help: Specify the pruning mode, a number of blocks to keep or "archive". Default is 256. takes_value: true - name: long: name @@ -95,6 +95,10 @@ args: value_name: TELEMETRY_URL help: The URL of the telemetry server. Implies --telemetry 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. subcommands: - build-spec: about: Build a spec.json file, outputing to stdout diff --git a/polkadot/cli/src/lib.rs b/polkadot/cli/src/lib.rs index 6c0f44bd17dd5..abf56c6def275 100644 --- a/polkadot/cli/src/lib.rs +++ b/polkadot/cli/src/lib.rs @@ -218,18 +218,32 @@ pub fn run(args: I, worker: W) -> error::Result<()> where if matches.is_present("collator") { info!("Starting collator"); // TODO [rob]: collation node implementation - service::Role::FULL + // This isn't a thing. Different parachains will have their own collator executables and + // maybe link to libpolkadot to get a light-client. + service::Role::LIGHT } else if matches.is_present("light") { info!("Starting (light)"); + config.execution_strategy = service::ExecutionStrategy::NativeWhenPossible; service::Role::LIGHT } else if matches.is_present("validator") || matches.is_present("dev") { info!("Starting validator"); + config.execution_strategy = service::ExecutionStrategy::Both; service::Role::AUTHORITY } else { info!("Starting (heavy)"); + config.execution_strategy = service::ExecutionStrategy::NativeWhenPossible; service::Role::FULL }; + if let Some(s) = matches.value_of("pruning") { + 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()), + }; + } + config.roles = role; { config.network.boot_nodes.extend(matches diff --git a/polkadot/service/src/config.rs b/polkadot/service/src/config.rs index 5b27331882a05..af9c54ee3fd13 100644 --- a/polkadot/service/src/config.rs +++ b/polkadot/service/src/config.rs @@ -18,6 +18,7 @@ use transaction_pool; use chain_spec::ChainSpec; +pub use substrate_executor::ExecutionStrategy; pub use network::Role; pub use network::NetworkConfiguration; pub use client_db::PruningMode; @@ -44,6 +45,8 @@ pub struct Configuration { pub telemetry: Option, /// Node name. pub name: String, + /// Execution strategy. + pub execution_strategy: ExecutionStrategy, } impl Configuration { @@ -60,6 +63,7 @@ impl Configuration { keys: Default::default(), telemetry: Default::default(), pruning: PruningMode::ArchiveAll, + execution_strategy: ExecutionStrategy::Both, }; configuration.network.boot_nodes = configuration.chain_spec.boot_nodes().to_vec(); configuration diff --git a/polkadot/service/src/lib.rs b/polkadot/service/src/lib.rs index 7ac4f714f2f8d..4b4df11d3b177 100644 --- a/polkadot/service/src/lib.rs +++ b/polkadot/service/src/lib.rs @@ -73,7 +73,7 @@ use tokio::runtime::TaskExecutor; pub use self::error::{ErrorKind, Error}; pub use self::components::{Components, FullComponents, LightComponents}; -pub use config::{Configuration, Role, PruningMode}; +pub use config::{Configuration, Role, PruningMode, ExecutionStrategy}; pub use chain_spec::ChainSpec; /// Polkadot service. @@ -109,7 +109,7 @@ pub fn new_client(config: Configuration) -> Result Client where &mut overlay, "execute_block", &::new(header.clone(), body.clone().unwrap_or_default()).encode() + // TODO: intercept Err::ConsensusFailure, report failure wrt block and then accept wasm result. )?; Some(storage_update) diff --git a/substrate/executor/src/error.rs b/substrate/executor/src/error.rs index c37c75b94737b..2d854d60a88de 100644 --- a/substrate/executor/src/error.rs +++ b/substrate/executor/src/error.rs @@ -74,5 +74,11 @@ error_chain! { description("invalid memory reference"), display("Invalid memory reference"), } + + /// Consensus failure. + ConsensusFailure(wasm_result: Box>>, native_result: Box>>) { + description("consensus failure"), + display("Differing results from Wasm execution and native dispatch"), + } } } diff --git a/substrate/executor/src/lib.rs b/substrate/executor/src/lib.rs index ddf631ba5f949..48515a7915ca6 100644 --- a/substrate/executor/src/lib.rs +++ b/substrate/executor/src/lib.rs @@ -66,7 +66,7 @@ mod sandbox; pub mod error; pub use wasm_executor::WasmExecutor; -pub use native_executor::{with_native_environment, NativeExecutor, NativeExecutionDispatch}; +pub use native_executor::{with_native_environment, NativeExecutor, NativeExecutionDispatch, ExecutionStrategy}; pub use state_machine::Externalities; pub use runtime_version::RuntimeVersion; pub use codec::Slicable; diff --git a/substrate/executor/src/native_executor.rs b/substrate/executor/src/native_executor.rs index 0191c41ff5f59..7559220441076 100644 --- a/substrate/executor/src/native_executor.rs +++ b/substrate/executor/src/native_executor.rs @@ -27,14 +27,14 @@ use parking_lot::{Mutex, MutexGuard}; use RuntimeInfo; // For the internal Runtime Cache: -// Do we run this natively or use the given WasmModule -enum RunWith { +// Is it compatible enough to run this natively or do we need to fall back on the WasmModule +enum Compatibility { InvalidVersion(WasmModule), - NativeRuntime(RuntimeVersion), - WasmRuntime(RuntimeVersion, WasmModule) + IsCompatible(RuntimeVersion), + NotCompatible(RuntimeVersion, WasmModule) } -type CacheType = HashMap; +type CacheType = HashMap; lazy_static! { static ref RUNTIMES_CACHE: Mutex = Mutex::new(HashMap::new()); @@ -51,29 +51,28 @@ fn gen_cache_key(code: &[u8]) -> u64 { } /// 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` +/// the runtime version entry for `code`, determines whether `Compatibility::IsCompatible` /// 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 { +) -> &'a Compatibility { 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) + Compatibility::IsCompatible(v) } else { - RunWith::WasmRuntime(v, module) + Compatibility::NotCompatible(v, module) } } else { - RunWith::InvalidVersion(module) + Compatibility::InvalidVersion(module) } }) } @@ -106,31 +105,78 @@ pub trait NativeExecutionDispatch { const VERSION: RuntimeVersion; } +/// Strategy for executing a call into the runtime. +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +pub enum ExecutionStrategy { + /// Execute with the native equivalent if it is compatible with the given wasm module; otherwise fall back to the wasm. + NativeWhenPossible, + /// Use the given wasm module. + AlwaysWasm, + /// Run with both the wasm and the native variant (if compatible). Report any discrepency as an error. + Both, +} + /// A generic `CodeExecutor` implementation that uses a delegate to determine wasm code equivalence /// and dispatch to native code when possible, falling back on `WasmExecutor` when not. #[derive(Debug)] pub struct NativeExecutor { + /// The strategy for execution. + strategy: ExecutionStrategy, /// Dummy field to avoid the compiler complaining about us not using `D`. _dummy: ::std::marker::PhantomData, } impl NativeExecutor { /// Create new instance. - pub fn new() -> Self { + pub fn new(strategy: ExecutionStrategy) -> Self { // FIXME: set this entry at compile time RUNTIMES_CACHE.lock().insert( gen_cache_key(D::native_equivalent()), - RunWith::NativeRuntime(D::VERSION)); + Compatibility::IsCompatible(D::VERSION)); NativeExecutor { + strategy, _dummy: Default::default(), } } + + fn call_with_strategy( + &self, + ext: &mut E, + code: &[u8], + method: &str, + data: &[u8], + strategy: ExecutionStrategy, + ) -> Result> { + let mut c = RUNTIMES_CACHE.lock(); + match (strategy, fetch_cached_runtime_version(&mut c, ext, code, D::VERSION)) { + (_, Compatibility::NotCompatible(_, m)) => WasmExecutor.call_in_wasm_module(ext, m, method, data), + (_, Compatibility::InvalidVersion(m)) => WasmExecutor.call_in_wasm_module(ext, m, method, data), + (ExecutionStrategy::AlwaysWasm, _) => WasmExecutor.call(ext, code, method, data), + (ExecutionStrategy::NativeWhenPossible, _) => D::dispatch(ext, method, data), + _ => { + // both + let w = WasmExecutor.call(ext, code, method, data); + let n = D::dispatch(ext, method, data); + let same_ok = if let (&Ok(ref w), &Ok(ref n)) = (&w, &n) { + w == n + } else { false }; + if same_ok { + return w + } + if w.is_err() && n.is_err() && format!("{:?}", w) == format!("{:?}", n) { + return w + } + Err(ErrorKind::ConsensusFailure(Box::new(w), Box::new(n)).into()) + } + } + } } impl Clone for NativeExecutor { fn clone(&self) -> Self { NativeExecutor { + strategy: self.strategy, _dummy: Default::default(), } } @@ -146,8 +192,8 @@ impl RuntimeInfo for NativeExecutor ) -> 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 + Compatibility::IsCompatible(v) | Compatibility::NotCompatible(v, _) => Some(v.clone()), + Compatibility::InvalidVersion(_m) => None } } } @@ -162,11 +208,7 @@ impl CodeExecutor for NativeExecutor Result> { - 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) - } + self.call_with_strategy(ext, code, method, data, self.strategy) } } @@ -198,7 +240,10 @@ macro_rules! native_executor_instance { impl $name { pub fn new() -> $crate::NativeExecutor<$name> { - $crate::NativeExecutor::new() + $crate::NativeExecutor::new($crate::ExecutionStrategy::NativeWhenPossible) + } + pub fn with_default_strategy(strategy: $crate::ExecutionStrategy) -> $crate::NativeExecutor<$name> { + $crate::NativeExecutor::new(strategy) } } }