From ee31955969f71ebd2c2de33ee914e01e05ba727d Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Sat, 14 Jul 2018 16:10:20 +0200 Subject: [PATCH] Dual execution (#311) * Initial logic * Remove accidental file * Config * Remove accidental * Apply CLI config * Additional work. Sadly pointless. * Rearrange everything * Loop into CLI param * Implement dual execution * typo * fix tests. * Better docs * Fix bug * Add some tests * Report block information on consensus failure, tests * Fix test --- substrate/Cargo.lock | 1 + substrate/demo/executor/Cargo.toml | 1 + substrate/demo/executor/src/lib.rs | 72 +++++-- substrate/polkadot/cli/src/cli.yml | 6 +- substrate/polkadot/cli/src/lib.rs | 16 +- substrate/polkadot/service/src/components.rs | 10 +- substrate/polkadot/service/src/config.rs | 4 + substrate/polkadot/service/src/lib.rs | 8 +- substrate/substrate/client/db/src/lib.rs | 5 +- .../substrate/client/src/block_builder.rs | 7 +- .../substrate/client/src/call_executor.rs | 62 +++++- substrate/substrate/client/src/client.rs | 34 +++- substrate/substrate/client/src/genesis.rs | 20 +- .../client/src/light/call_executor.rs | 13 +- substrate/substrate/client/src/light/mod.rs | 4 +- .../substrate/executor/src/native_executor.rs | 55 +++-- substrate/substrate/executor/src/sandbox.rs | 10 +- .../substrate/executor/src/wasm_executor.rs | 57 +++--- .../substrate/runtime/consensus/src/lib.rs | 6 + substrate/substrate/state-machine/src/lib.rs | 188 ++++++++++++++++-- 20 files changed, 449 insertions(+), 130 deletions(-) diff --git a/substrate/Cargo.lock b/substrate/Cargo.lock index fa3160e8c2..1798a33151 100644 --- a/substrate/Cargo.lock +++ b/substrate/Cargo.lock @@ -337,6 +337,7 @@ dependencies = [ "substrate-executor 0.1.0", "substrate-keyring 0.1.0", "substrate-primitives 0.1.0", + "substrate-runtime-consensus 0.1.0", "substrate-runtime-io 0.1.0", "substrate-runtime-primitives 0.1.0", "substrate-runtime-staking 0.1.0", diff --git a/substrate/demo/executor/Cargo.toml b/substrate/demo/executor/Cargo.toml index 2cdc008839..c154a7c165 100644 --- a/substrate/demo/executor/Cargo.toml +++ b/substrate/demo/executor/Cargo.toml @@ -22,3 +22,4 @@ substrate-keyring = { path = "../../substrate/keyring" } substrate-runtime-primitives = { path = "../../substrate/runtime/primitives" } substrate-runtime-staking = { path = "../../substrate/runtime/staking" } substrate-runtime-system = { path = "../../substrate/runtime/system" } +substrate-runtime-consensus = { path = "../../substrate/runtime/consensus" } diff --git a/substrate/demo/executor/src/lib.rs b/substrate/demo/executor/src/lib.rs index 0b914b396c..51de45f8fb 100644 --- a/substrate/demo/executor/src/lib.rs +++ b/substrate/demo/executor/src/lib.rs @@ -32,6 +32,7 @@ extern crate triehash; #[cfg(test)] extern crate substrate_runtime_support as runtime_support; #[cfg(test)] extern crate substrate_runtime_staking as staking; #[cfg(test)] extern crate substrate_runtime_system as system; +#[cfg(test)] extern crate substrate_runtime_consensus as consensus; #[cfg(test)] #[macro_use] extern crate hex_literal; 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")); @@ -49,7 +50,7 @@ mod tests { use demo_primitives::{Hash, BlockNumber, AccountId}; use runtime_primitives::traits::Header as HeaderT; use runtime_primitives::{ApplyOutcome, ApplyError, ApplyResult, MaybeUnsigned}; - use {staking, system}; + use {staking, system, consensus}; use demo_runtime::{Header, Block, UncheckedExtrinsic, Extrinsic, Call, Concrete, Staking, BuildStorage, GenesisConfig, SessionConfig, StakingConfig, BareExtrinsic}; use ed25519::{Public, Pair}; @@ -104,9 +105,9 @@ mod tests { twox_128(&>::key_for(0)).to_vec() => vec![0u8; 32] ]; - let r = Executor::new().call(&mut t, BLOATY_CODE, "initialise_block", &vec![].and(&from_block_number(1u64))); + let r = Executor::new().call(&mut t, BLOATY_CODE, "initialise_block", &vec![].and(&from_block_number(1u64)), true).0; assert!(r.is_ok()); - let v = Executor::new().call(&mut t, BLOATY_CODE, "apply_extrinsic", &vec![].and(&xt())).unwrap(); + let v = Executor::new().call(&mut t, BLOATY_CODE, "apply_extrinsic", &vec![].and(&xt()), true).0.unwrap(); let r = ApplyResult::decode(&mut &v[..]).unwrap(); assert_eq!(r, Err(ApplyError::CantPay)); } @@ -123,9 +124,9 @@ mod tests { twox_128(&>::key_for(0)).to_vec() => vec![0u8; 32] ]; - let r = Executor::new().call(&mut t, COMPACT_CODE, "initialise_block", &vec![].and(&from_block_number(1u64))); + let r = Executor::new().call(&mut t, COMPACT_CODE, "initialise_block", &vec![].and(&from_block_number(1u64)), true).0; assert!(r.is_ok()); - let v = Executor::new().call(&mut t, COMPACT_CODE, "apply_extrinsic", &vec![].and(&xt())).unwrap(); + let v = Executor::new().call(&mut t, COMPACT_CODE, "apply_extrinsic", &vec![].and(&xt()), true).0.unwrap(); let r = ApplyResult::decode(&mut &v[..]).unwrap(); assert_eq!(r, Err(ApplyError::CantPay)); } @@ -142,9 +143,9 @@ mod tests { twox_128(&>::key_for(0)).to_vec() => vec![0u8; 32] ]; - let r = Executor::new().call(&mut t, COMPACT_CODE, "initialise_block", &vec![].and(&from_block_number(1u64))); + let r = Executor::new().call(&mut t, COMPACT_CODE, "initialise_block", &vec![].and(&from_block_number(1u64)), true).0; assert!(r.is_ok()); - let r = Executor::new().call(&mut t, COMPACT_CODE, "apply_extrinsic", &vec![].and(&xt())); + let r = Executor::new().call(&mut t, COMPACT_CODE, "apply_extrinsic", &vec![].and(&xt()), true).0; assert!(r.is_ok()); runtime_io::with_externalities(&mut t, || { @@ -165,9 +166,9 @@ mod tests { twox_128(&>::key_for(0)).to_vec() => vec![0u8; 32] ]; - let r = Executor::new().call(&mut t, BLOATY_CODE, "initialise_block", &vec![].and(&from_block_number(1u64))); + let r = Executor::new().call(&mut t, BLOATY_CODE, "initialise_block", &vec![].and(&from_block_number(1u64)), true).0; assert!(r.is_ok()); - let r = Executor::new().call(&mut t, BLOATY_CODE, "apply_extrinsic", &vec![].and(&xt())); + let r = Executor::new().call(&mut t, BLOATY_CODE, "apply_extrinsic", &vec![].and(&xt()), true).0; assert!(r.is_ok()); runtime_io::with_externalities(&mut t, || { @@ -271,18 +272,31 @@ mod tests { ) } + fn block1big() -> (Vec, Hash) { + construct_block( + 1, + [69u8; 32].into(), + hex!("d95fc2cf4541b97ed2cd381fe7a486af8aebad9ed0480c30e9cca184bb207e95").into(), + vec![BareExtrinsic { + signed: alice(), + index: 0, + function: Call::Consensus(consensus::Call::remark(vec![0; 60000])), + }] + ) + } + #[test] fn full_native_block_import_works() { let mut t = new_test_ext(); - Executor::new().call(&mut t, COMPACT_CODE, "execute_block", &block1().0).unwrap(); + Executor::new().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); }); - Executor::new().call(&mut t, COMPACT_CODE, "execute_block", &block2().0).unwrap(); + Executor::new().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); @@ -294,14 +308,14 @@ mod tests { fn full_wasm_block_import_works() { let mut t = new_test_ext(); - WasmExecutor.call(&mut t, COMPACT_CODE, "execute_block", &block1().0).unwrap(); + WasmExecutor{heap_pages: 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.call(&mut t, COMPACT_CODE, "execute_block", &block2().0).unwrap(); + WasmExecutor{heap_pages: 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); @@ -309,6 +323,30 @@ mod tests { }); } + #[test] + 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; + assert!(!r.is_ok()); + } + + #[test] + fn native_big_block_import_succeeds() { + let mut t = new_test_ext(); + + let r = Executor::new().call(&mut t, COMPACT_CODE, "execute_block", &block1big().0, true).0; + assert!(r.is_ok()); + } + + #[test] + fn native_big_block_import_fails_on_fallback() { + let mut t = new_test_ext(); + + let r = Executor::new().call(&mut t, COMPACT_CODE, "execute_block", &block1big().0, false).0; + assert!(!r.is_ok()); + } + #[test] fn panic_execution_gives_error() { let mut t: TestExternalities = map![ @@ -322,9 +360,9 @@ mod tests { ]; let foreign_code = include_bytes!("../../runtime/wasm/target/wasm32-unknown-unknown/release/demo_runtime.wasm"); - let r = WasmExecutor.call(&mut t, &foreign_code[..], "initialise_block", &vec![].and(&from_block_number(1u64))); + let r = WasmExecutor{heap_pages: 8}.call(&mut t, &foreign_code[..], "initialise_block", &vec![].and(&from_block_number(1u64)), true).0; assert!(r.is_ok()); - let r = WasmExecutor.call(&mut t, &foreign_code[..], "apply_extrinsic", &vec![].and(&xt())).unwrap(); + let r = WasmExecutor{heap_pages: 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)); } @@ -342,9 +380,9 @@ mod tests { ]; let foreign_code = include_bytes!("../../runtime/wasm/target/wasm32-unknown-unknown/release/demo_runtime.compact.wasm"); - let r = WasmExecutor.call(&mut t, &foreign_code[..], "initialise_block", &vec![].and(&from_block_number(1u64))); + let r = WasmExecutor{heap_pages: 8}.call(&mut t, &foreign_code[..], "initialise_block", &vec![].and(&from_block_number(1u64)), true).0; assert!(r.is_ok()); - let r = WasmExecutor.call(&mut t, &foreign_code[..], "apply_extrinsic", &vec![].and(&xt())).unwrap(); + let r = WasmExecutor{heap_pages: 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)); diff --git a/substrate/polkadot/cli/src/cli.yml b/substrate/polkadot/cli/src/cli.yml index bae81c5fd0..bb6f1defbf 100644 --- a/substrate/polkadot/cli/src/cli.yml +++ b/substrate/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/substrate/polkadot/cli/src/lib.rs b/substrate/polkadot/cli/src/lib.rs index 6c0f44bd17..d51ee7bed2 100644 --- a/substrate/polkadot/cli/src/lib.rs +++ b/substrate/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("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()), + }; + } + config.roles = role; { config.network.boot_nodes.extend(matches diff --git a/substrate/polkadot/service/src/components.rs b/substrate/polkadot/service/src/components.rs index b7b5c3c96a..44e16e5ff4 100644 --- a/substrate/polkadot/service/src/components.rs +++ b/substrate/polkadot/service/src/components.rs @@ -30,7 +30,7 @@ use polkadot_api; use polkadot_executor::Executor as LocalDispatch; use polkadot_network::NetworkService; use polkadot_primitives::{Block, BlockId, Hash}; -use state_machine; +use state_machine::{self, ExecutionStrategy}; use substrate_executor::NativeExecutor; use transaction_pool::{self, TransactionPool}; use tokio::runtime::TaskExecutor; @@ -50,7 +50,7 @@ pub trait Components { type Executor: 'static + client::CallExecutor + Send + Sync; /// Create client. - fn build_client(&self, settings: client_db::DatabaseSettings, executor: CodeExecutor, chain_spec: &ChainSpec) + fn build_client(&self, settings: client_db::DatabaseSettings, executor: CodeExecutor, chain_spec: &ChainSpec, execution_strategy: ExecutionStrategy) -> Result<(Arc>, Option>>), error::Error>; /// Create api. @@ -83,9 +83,9 @@ impl Components for FullComponents { type Api = Client; type Executor = client::LocalCallExecutor, NativeExecutor>; - fn build_client(&self, db_settings: client_db::DatabaseSettings, executor: CodeExecutor, chain_spec: &ChainSpec) + fn build_client(&self, db_settings: client_db::DatabaseSettings, executor: CodeExecutor, chain_spec: &ChainSpec, execution_strategy: ExecutionStrategy) -> Result<(Arc>, Option>>), error::Error> { - Ok((Arc::new(client_db::new_client(db_settings, executor, chain_spec)?), None)) + Ok((Arc::new(client_db::new_client(db_settings, executor, chain_spec, execution_strategy)?), None)) } fn build_api(&self, client: Arc>) -> Arc { @@ -142,7 +142,7 @@ impl Components for LightComponents { client::light::blockchain::Blockchain, network::OnDemand>, network::OnDemand>; - fn build_client(&self, db_settings: client_db::DatabaseSettings, executor: CodeExecutor, spec: &ChainSpec) + fn build_client(&self, db_settings: client_db::DatabaseSettings, executor: CodeExecutor, spec: &ChainSpec, _execution_strategy: ExecutionStrategy) -> Result<(Arc>, Option>>), error::Error> { let db_storage = client_db::light::LightStorage::new(db_settings)?; let light_blockchain = client::light::new_light_blockchain(db_storage); diff --git a/substrate/polkadot/service/src/config.rs b/substrate/polkadot/service/src/config.rs index 5b27331882..c9fdc9ced1 100644 --- a/substrate/polkadot/service/src/config.rs +++ b/substrate/polkadot/service/src/config.rs @@ -18,6 +18,7 @@ use transaction_pool; use chain_spec::ChainSpec; +pub use state_machine::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/substrate/polkadot/service/src/lib.rs b/substrate/polkadot/service/src/lib.rs index 7ac4f714f2..787e304c06 100644 --- a/substrate/polkadot/service/src/lib.rs +++ b/substrate/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. @@ -112,7 +112,7 @@ pub fn new_client(config: Configuration) -> Result Service let (signal, exit) = ::exit_future::signal(); // Create client - let executor = polkadot_executor::Executor::new(); + let executor = polkadot_executor::Executor::with_heap_pages(128); let mut keystore = Keystore::open(config.keystore_path.into())?; for seed in &config.keys { @@ -144,7 +144,7 @@ impl Service pruning: config.pruning, }; - let (client, on_demand) = components.build_client(db_settings, executor, &config.chain_spec)?; + let (client, on_demand) = components.build_client(db_settings, executor, &config.chain_spec, config.execution_strategy)?; let api = components.build_api(client.clone()); let best_header = client.best_block_header()?; diff --git a/substrate/substrate/client/db/src/lib.rs b/substrate/substrate/client/db/src/lib.rs index 222328ecd1..b1e5f98fa8 100644 --- a/substrate/substrate/client/db/src/lib.rs +++ b/substrate/substrate/client/db/src/lib.rs @@ -54,7 +54,7 @@ use runtime_primitives::traits::{Block as BlockT, Header as HeaderT, As, Hash, H use runtime_primitives::BuildStorage; use state_machine::backend::Backend as StateBackend; use executor::RuntimeInfo; -use state_machine::{CodeExecutor, TrieH256, DBValue}; +use state_machine::{CodeExecutor, TrieH256, DBValue, ExecutionStrategy}; use utils::{Meta, db_err, meta_keys, number_to_db_key, open_database, read_db, read_id, read_meta}; use state_db::StateDb; pub use state_db::PruningMode; @@ -79,6 +79,7 @@ pub fn new_client( settings: DatabaseSettings, executor: E, genesis_storage: S, + execution_strategy: ExecutionStrategy, ) -> Result, client::LocalCallExecutor, E>, Block>, client::error::Error> where Block: BlockT, @@ -89,7 +90,7 @@ pub fn new_client( { let backend = Arc::new(Backend::new(settings, FINALIZATION_WINDOW)?); let executor = client::LocalCallExecutor::new(backend.clone(), executor); - Ok(client::Client::new(backend, executor, genesis_storage)?) + Ok(client::Client::new(backend, executor, genesis_storage, execution_strategy)?) } mod columns { diff --git a/substrate/substrate/client/src/block_builder.rs b/substrate/substrate/client/src/block_builder.rs index 210fc5780c..326cd08d77 100644 --- a/substrate/substrate/client/src/block_builder.rs +++ b/substrate/substrate/client/src/block_builder.rs @@ -18,7 +18,7 @@ use std::vec::Vec; use codec::Slicable; -use state_machine; +use state_machine::{self, native_when_possible}; use runtime_primitives::traits::{Header as HeaderT, Hash, Block as BlockT, One, HashFor}; use runtime_primitives::generic::BlockId; use {backend, error, Client, CallExecutor}; @@ -69,7 +69,7 @@ impl BlockBuilder where Default::default() ); - executor.call_at_state(&state, &mut changes, "initialise_block", &header.encode())?; + executor.call_at_state(&state, &mut changes, "initialise_block", &header.encode(), native_when_possible())?; Ok(BlockBuilder { header, @@ -84,7 +84,7 @@ impl BlockBuilder where /// can be validly executed (by executing it); if it is invalid, it'll be returned along with /// the error. Otherwise, it will return a mutable reference to self (in order to chain). pub fn push(&mut self, xt: ::Extrinsic) -> error::Result<()> { - match self.executor.call_at_state(&self.state, &mut self.changes, "apply_extrinsic", &xt.encode()) { + match self.executor.call_at_state(&self.state, &mut self.changes, "apply_extrinsic", &xt.encode(), native_when_possible()) { Ok(_) => { self.extrinsics.push(xt); Ok(()) @@ -103,6 +103,7 @@ impl BlockBuilder where &mut self.changes, "finalise_block", &[], + native_when_possible(), )?; self.header = <::Header as Slicable>::decode(&mut &output[..]) .expect("Header came straight out of runtime so must be valid"); diff --git a/substrate/substrate/client/src/call_executor.rs b/substrate/substrate/client/src/call_executor.rs index 36d6ced1e5..61f067447f 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, Ext, Backend as StateBackend, CodeExecutor}; +use state_machine::{self, OverlayedChanges, Ext, Backend as StateBackend, + CodeExecutor, ExecutionManager, native_when_possible}; use runtime_io::Externalities; use executor::{RuntimeVersion, RuntimeInfo}; @@ -41,7 +42,11 @@ pub trait CallExecutor { /// Execute a call to a contract on top of state in a block of given hash. /// /// No changes are made. - fn call(&self, id: &BlockId, method: &str, call_data: &[u8]) -> Result; + fn call(&self, + id: &BlockId, + method: &str, + call_data: &[u8], + ) -> Result; /// Extract RuntimeVersion of given block /// @@ -51,12 +56,26 @@ pub trait CallExecutor { /// Execute a call to a contract on top of given state. /// /// No changes are made. - fn call_at_state(&self, state: &S, overlay: &mut OverlayedChanges, method: &str, call_data: &[u8]) -> Result<(Vec, S::Transaction), error::Error>; + fn call_at_state< + S: state_machine::Backend, + F: FnOnce(Result, Self::Error>, Result, Self::Error>) -> Result, Self::Error>, + >(&self, + state: &S, + overlay: &mut OverlayedChanges, + method: &str, + call_data: &[u8], + manager: ExecutionManager + ) -> Result<(Vec, S::Transaction), error::Error>; /// Execute a call to a contract on top of given state, gathering execution proof. /// /// No changes are made. - fn prove_at_state(&self, state: S, overlay: &mut OverlayedChanges, method: &str, call_data: &[u8]) -> Result<(Vec, Vec>), error::Error>; + fn prove_at_state(&self, + state: S, + overlay: &mut OverlayedChanges, + method: &str, + call_data: &[u8] + ) -> Result<(Vec, Vec>), error::Error>; /// Get runtime version if supported. fn native_runtime_version(&self) -> Option; @@ -94,9 +113,19 @@ impl CallExecutor for LocalCallExecutor { type Error = E::Error; - fn call(&self, id: &BlockId, method: &str, call_data: &[u8]) -> error::Result { + fn call(&self, + id: &BlockId, + method: &str, + call_data: &[u8], + ) -> error::Result { let mut changes = OverlayedChanges::default(); - let (return_data, _) = self.call_at_state(&self.backend.state_at(*id)?, &mut changes, method, call_data)?; + let (return_data, _) = self.call_at_state( + &self.backend.state_at(*id)?, + &mut changes, + method, + call_data, + native_when_possible(), + )?; Ok(CallResult{ return_data, changes }) } @@ -111,17 +140,32 @@ impl CallExecutor for LocalCallExecutor .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( + fn call_at_state< + S: state_machine::Backend, + F: FnOnce(Result, Self::Error>, Result, Self::Error>) -> Result, Self::Error>, + >(&self, + state: &S, + changes: &mut OverlayedChanges, + method: &str, + call_data: &[u8], + manager: ExecutionManager, + ) -> error::Result<(Vec, S::Transaction)> { + state_machine::execute_using_consensus_failure_handler( state, changes, &self.executor, method, call_data, + manager, ).map_err(Into::into) } - fn prove_at_state(&self, state: S, changes: &mut OverlayedChanges, method: &str, call_data: &[u8]) -> Result<(Vec, Vec>), error::Error> { + fn prove_at_state(&self, + state: S, + changes: &mut OverlayedChanges, + method: &str, + call_data: &[u8] + ) -> Result<(Vec, Vec>), error::Error> { state_machine::prove_execution( state, changes, diff --git a/substrate/substrate/client/src/client.rs b/substrate/substrate/client/src/client.rs index 5b1c6789d9..4262c357f8 100644 --- a/substrate/substrate/client/src/client.rs +++ b/substrate/substrate/client/src/client.rs @@ -25,7 +25,7 @@ use runtime_primitives::traits::{Block as BlockT, Header as HeaderT, Zero, One, use runtime_primitives::BuildStorage; use primitives::storage::{StorageKey, StorageData}; use codec::Slicable; -use state_machine::{self, Ext, OverlayedChanges, Backend as StateBackend, CodeExecutor}; +use state_machine::{self, Ext, OverlayedChanges, Backend as StateBackend, CodeExecutor, ExecutionStrategy, ExecutionManager}; use backend::{self, BlockImportOperation}; use blockchain::{self, Info as ChainInfo, Backend as ChainBackend, HeaderBackend as ChainHeaderBackend}; @@ -43,6 +43,7 @@ pub struct Client where Block: BlockT { import_notification_sinks: Mutex>>>, import_lock: Mutex<()>, importing_block: RwLock>, // holds the block hash currently being imported. TODO: replace this with block queue + execution_strategy: ExecutionStrategy, } /// A source of blockchain evenets. @@ -144,7 +145,7 @@ impl JustifiedHeader { /// Create an instance of in-memory client. pub fn new_in_mem( executor: E, - genesis_storage: S + genesis_storage: S, ) -> error::Result, LocalCallExecutor, E>, Block>> where E: CodeExecutor + RuntimeInfo, @@ -153,7 +154,7 @@ pub fn new_in_mem( { let backend = Arc::new(in_mem::Backend::new()); let executor = LocalCallExecutor::new(backend.clone(), executor); - Client::new(backend, executor, genesis_storage) + Client::new(backend, executor, genesis_storage, ExecutionStrategy::NativeWhenPossible) } impl Client where @@ -167,6 +168,7 @@ impl Client where backend: Arc, executor: E, build_genesis_storage: S, + execution_strategy: ExecutionStrategy, ) -> error::Result { if backend.blockchain().header(BlockId::Number(Zero::zero()))?.is_none() { let genesis_storage = build_genesis_storage.build_storage()?; @@ -183,6 +185,7 @@ impl Client where import_notification_sinks: Mutex::new(Vec::new()), import_lock: Mutex::new(()), importing_block: RwLock::new(None), + execution_strategy, }) } @@ -327,13 +330,30 @@ impl Client where let storage_update = match transaction.state()? { Some(transaction_state) => { let mut overlay = Default::default(); - let (_, storage_update) = self.executor.call_at_state( + let mut r = self.executor.call_at_state( transaction_state, &mut overlay, "execute_block", - &::new(header.clone(), body.clone().unwrap_or_default()).encode() - )?; - + &::new(header.clone(), body.clone().unwrap_or_default()).encode(), + match (origin, self.execution_strategy) { + (BlockOrigin::NetworkInitialSync, _) | (_, ExecutionStrategy::NativeWhenPossible) => + ExecutionManager::NativeWhenPossible, + (_, ExecutionStrategy::AlwaysWasm) => ExecutionManager::AlwaysWasm, + _ => ExecutionManager::Both(|wasm_result, native_result| { + warn!("Consensus error between wasm and native block execution at block {}", hash); + warn!(" Header {:?}", header); + warn!(" Native result {:?}", native_result); + warn!(" Wasm result {:?}", wasm_result); + telemetry!("block.execute.consensus_failure"; + "hash" => ?hash, + "origin" => ?origin, + "header" => ?header + ); + wasm_result + }), + }, + ); + let (_, storage_update) = r?; Some(storage_update) }, None => None, diff --git a/substrate/substrate/client/src/genesis.rs b/substrate/substrate/client/src/genesis.rs index fb1e9b9753..17f6823b1a 100644 --- a/substrate/substrate/client/src/genesis.rs +++ b/substrate/substrate/client/src/genesis.rs @@ -45,7 +45,7 @@ mod tests { use codec::{Slicable, Joiner}; use keyring::Keyring; use executor::WasmExecutor; - use state_machine::{execute, OverlayedChanges}; + use state_machine::{execute, OverlayedChanges, ExecutionStrategy}; use state_machine::backend::InMemory; use test_client; use test_client::runtime::genesismap::{GenesisConfig, additional_storage_with_genesis}; @@ -83,6 +83,7 @@ mod tests { &Executor::new(), "initialise_block", &header.encode(), + ExecutionStrategy::NativeWhenPossible, ).unwrap(); for tx in transactions.iter() { @@ -92,6 +93,7 @@ mod tests { &Executor::new(), "apply_extrinsic", &tx.encode(), + ExecutionStrategy::NativeWhenPossible, ).unwrap(); } @@ -100,7 +102,8 @@ mod tests { &mut overlay, &Executor::new(), "finalise_block", - &[] + &[], + ExecutionStrategy::NativeWhenPossible, ).unwrap(); header = Header::decode(&mut &ret_data[..]).unwrap(); println!("root after: {:?}", header.extrinsics_root); @@ -141,7 +144,8 @@ mod tests { &mut overlay, &Executor::new(), "execute_block", - &b1data + &b1data, + ExecutionStrategy::NativeWhenPossible, ).unwrap(); } @@ -161,9 +165,10 @@ mod tests { let _ = execute( &backend, &mut overlay, - &WasmExecutor, + &WasmExecutor { heap_pages: 8 }, "execute_block", - &b1data + &b1data, + ExecutionStrategy::NativeWhenPossible, ).unwrap(); } @@ -184,9 +189,10 @@ mod tests { let _ = execute( &backend, &mut overlay, - &Executor::new(), + &Executor::with_heap_pages(8), "execute_block", - &b1data + &b1data, + ExecutionStrategy::NativeWhenPossible, ).unwrap(); } } diff --git a/substrate/substrate/client/src/light/call_executor.rs b/substrate/substrate/client/src/light/call_executor.rs index 104e5e81cc..4498997241 100644 --- a/substrate/substrate/client/src/light/call_executor.rs +++ b/substrate/substrate/client/src/light/call_executor.rs @@ -22,7 +22,7 @@ use futures::{IntoFuture, Future}; use runtime_primitives::generic::BlockId; use runtime_primitives::traits::{Block as BlockT, Header as HeaderT}; -use state_machine::{Backend as StateBackend, CodeExecutor, OverlayedChanges, execution_proof_check}; +use state_machine::{Backend as StateBackend, CodeExecutor, OverlayedChanges, execution_proof_check, ExecutionManager}; use blockchain::Backend as ChainBackend; use call_executor::{CallExecutor, CallResult}; @@ -73,7 +73,16 @@ impl CallExecutor for RemoteCallExecutor .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)> { + fn call_at_state< + S: StateBackend, + H: FnOnce(Result, Self::Error>, Result, Self::Error>) -> Result, Self::Error> + >(&self, + _state: &S, + _changes: &mut OverlayedChanges, + _method: &str, + _call_data: &[u8], + _m: ExecutionManager + ) -> ClientResult<(Vec, S::Transaction)> { Err(ClientErrorKind::NotAvailableOnLightClient.into()) } diff --git a/substrate/substrate/client/src/light/mod.rs b/substrate/substrate/client/src/light/mod.rs index d55ab5f3ac..40b54cdd63 100644 --- a/substrate/substrate/client/src/light/mod.rs +++ b/substrate/substrate/client/src/light/mod.rs @@ -25,7 +25,7 @@ use std::sync::Arc; use runtime_primitives::BuildStorage; use runtime_primitives::traits::Block as BlockT; -use state_machine::CodeExecutor; +use state_machine::{CodeExecutor, ExecutionStrategy}; use client::Client; use error::Result as ClientResult; @@ -58,7 +58,7 @@ pub fn new_light( GS: BuildStorage, { let executor = RemoteCallExecutor::new(backend.blockchain().clone(), fetcher); - Client::new(backend, executor, genesis_storage) + Client::new(backend, executor, genesis_storage, ExecutionStrategy::NativeWhenPossible) } /// Create an instance of fetch data checker. diff --git a/substrate/substrate/executor/src/native_executor.rs b/substrate/substrate/executor/src/native_executor.rs index 0191c41ff5..7eef0e95cd 100644 --- a/substrate/substrate/executor/src/native_executor.rs +++ b/substrate/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() + let version = WasmExecutor{heap_pages: 8}.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) } }) } @@ -112,18 +111,26 @@ pub trait NativeExecutionDispatch { pub struct NativeExecutor { /// Dummy field to avoid the compiler complaining about us not using `D`. _dummy: ::std::marker::PhantomData, + /// The fallback executor in case native isn't available. + fallback: WasmExecutor, } impl NativeExecutor { - /// Create new instance. + /// 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 { // 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 { _dummy: Default::default(), + fallback: WasmExecutor{heap_pages}, } } } @@ -132,6 +139,7 @@ impl Clone for NativeExecutor { fn clone(&self) -> Self { NativeExecutor { _dummy: Default::default(), + fallback: self.fallback.clone(), } } } @@ -146,8 +154,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 } } } @@ -161,11 +169,15 @@ impl CodeExecutor for NativeExecutor Result> { + use_native: bool, + ) -> (Result>, bool) { 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) + match (use_native, fetch_cached_runtime_version(&mut c, ext, code, D::VERSION)) { + (_, Compatibility::NotCompatible(_, m)) | (_, Compatibility::InvalidVersion(m)) => + (self.fallback.call_in_wasm_module(ext, m, method, data), false), + (false, _) => + (self.fallback.call(ext, code, method, data, false).0, false), + _ => (D::dispatch(ext, method, data), true), } } } @@ -200,6 +212,9 @@ macro_rules! native_executor_instance { pub fn new() -> $crate::NativeExecutor<$name> { $crate::NativeExecutor::new() } + pub fn with_heap_pages(heap_pages: usize) -> $crate::NativeExecutor<$name> { + $crate::NativeExecutor::with_heap_pages(heap_pages) + } } } } diff --git a/substrate/substrate/executor/src/sandbox.rs b/substrate/substrate/executor/src/sandbox.rs index fd061bbe24..25da25cd3f 100644 --- a/substrate/substrate/executor/src/sandbox.rs +++ b/substrate/substrate/executor/src/sandbox.rs @@ -554,7 +554,7 @@ mod tests { "#).unwrap(); assert_eq!( - WasmExecutor.call(&mut ext, &test_code[..], "test_sandbox", &code).unwrap(), + WasmExecutor{heap_pages: 8}.call(&mut ext, &test_code[..], "test_sandbox", &code, true).0.unwrap(), vec![1], ); } @@ -575,7 +575,7 @@ mod tests { "#).unwrap(); assert_eq!( - WasmExecutor.call(&mut ext, &test_code[..], "test_sandbox", &code).unwrap(), + WasmExecutor{heap_pages: 8}.call(&mut ext, &test_code[..], "test_sandbox", &code, true).0.unwrap(), vec![0], ); } @@ -613,7 +613,7 @@ mod tests { "#).unwrap(); assert_eq!( - WasmExecutor.call(&mut ext, &test_code[..], "test_sandbox", &code).unwrap(), + WasmExecutor{heap_pages: 8}.call(&mut ext, &test_code[..], "test_sandbox", &code, true).0.unwrap(), vec![1], ); } @@ -647,7 +647,7 @@ mod tests { "#).unwrap(); assert_eq!( - WasmExecutor.call(&mut ext, &test_code[..], "test_sandbox_args", &code).unwrap(), + WasmExecutor{heap_pages: 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.call(&mut ext, &test_code[..], "test_sandbox_return_val", &code).unwrap(), + WasmExecutor{heap_pages: 8}.call(&mut ext, &test_code[..], "test_sandbox_return_val", &code, true).0.unwrap(), vec![1], ); } diff --git a/substrate/substrate/executor/src/wasm_executor.rs b/substrate/substrate/executor/src/wasm_executor.rs index 891f672456..ca213928c0 100644 --- a/substrate/substrate/executor/src/wasm_executor.rs +++ b/substrate/substrate/executor/src/wasm_executor.rs @@ -37,28 +37,28 @@ struct Heap { } impl Heap { - /// Construct new `Heap` struct. + /// Construct new `Heap` struct with a given number of pages. /// /// Returns `Err` if the heap couldn't allocate required /// number of pages. /// /// This could mean that wasm binary specifies memory /// limit and we are trying to allocate beyond that limit. - fn new(memory: &MemoryRef) -> Result { - const HEAP_SIZE_IN_PAGES: usize = 1024; - + fn new(memory: &MemoryRef, pages: usize) -> Result { let prev_page_count = memory - .grow(Pages(HEAP_SIZE_IN_PAGES)) + .grow(Pages(pages)) .map_err(|_| Error::from(ErrorKind::Runtime))?; Ok(Heap { end: Bytes::from(prev_page_count).0 as u32, }) } + fn allocate(&mut self, size: u32) -> u32 { let r = self.end; self.end += size; r } + fn deallocate(&mut self, _offset: u32) { } } @@ -73,10 +73,10 @@ struct FunctionExecutor<'e, E: Externalities + 'e> { } impl<'e, E: Externalities> FunctionExecutor<'e, E> { - fn new(m: MemoryRef, t: Option, e: &'e mut E) -> Result { + fn new(m: MemoryRef, heap_pages: usize, t: Option, e: &'e mut E) -> Result { Ok(FunctionExecutor { sandbox_store: sandbox::Store::new(), - heap: Heap::new(&m)?, + heap: Heap::new(&m, heap_pages)?, memory: m, table: t, ext: e, @@ -468,7 +468,10 @@ impl_function_executor!(this: FunctionExecutor<'e, E>, /// /// Executes the provided code in a sandboxed wasm runtime. #[derive(Debug, Default, Clone)] -pub struct WasmExecutor; +pub struct WasmExecutor { + /// The number of pages to allocate for the heap. + pub heap_pages: usize, +} impl WasmExecutor { @@ -502,7 +505,7 @@ impl WasmExecutor { .not_started_instance() .export_by_name("table") .and_then(|e| e.as_table().cloned()); - let mut fec = FunctionExecutor::new(memory.clone(), table, ext)?; + let mut fec = FunctionExecutor::new(memory.clone(), self.heap_pages, table, ext)?; // finish instantiation by running 'start' function (if any). let instance = intermediate_instance.run_start(&mut fec)?; @@ -540,9 +543,11 @@ impl CodeExecutor for WasmExecutor { code: &[u8], method: &str, data: &[u8], - ) -> Result> { - let module = Module::from_buffer(code)?; - self.call_in_wasm_module(ext, &module, method, data) + _use_native: bool + ) -> (Result>, bool) { + (Module::from_buffer(code).map_err(Into::into).and_then(|module| + self.call_in_wasm_module(ext, &module, method, data) + ), false) } } @@ -565,7 +570,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.call(&mut ext, &test_code[..], "test_empty_return", &[]).unwrap(); + let output = WasmExecutor{heap_pages: 8}.call(&mut ext, &test_code[..], "test_empty_return", &[], true).0.unwrap(); assert_eq!(output, vec![0u8; 0]); } @@ -574,10 +579,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.call(&mut ext, &test_code[..], "test_panic", &[]); + let output = WasmExecutor{heap_pages: 8}.call(&mut ext, &test_code[..], "test_panic", &[], true).0; assert!(output.is_err()); - let output = WasmExecutor.call(&mut ext, &test_code[..], "test_conditional_panic", &[2]); + let output = WasmExecutor{heap_pages: 8}.call(&mut ext, &test_code[..], "test_conditional_panic", &[2], true).0; assert!(output.is_err()); } @@ -587,7 +592,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.call(&mut ext, &test_code[..], "test_data_in", b"Hello world").unwrap(); + let output = WasmExecutor{heap_pages: 8}.call(&mut ext, &test_code[..], "test_data_in", b"Hello world", true).0.unwrap(); assert_eq!(output, b"all ok!".to_vec()); @@ -610,7 +615,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.call(&mut ext, &test_code[..], "test_clear_prefix", b"ab").unwrap(); + let output = WasmExecutor{heap_pages: 8}.call(&mut ext, &test_code[..], "test_clear_prefix", b"ab", true).0.unwrap(); assert_eq!(output, b"all ok!".to_vec()); @@ -627,11 +632,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.call(&mut ext, &test_code[..], "test_blake2_256", &[]).unwrap(), + WasmExecutor{heap_pages: 8}.call(&mut ext, &test_code[..], "test_blake2_256", &[], true).0.unwrap(), blake2_256(&b""[..]).encode() ); assert_eq!( - WasmExecutor.call(&mut ext, &test_code[..], "test_blake2_256", b"Hello world!").unwrap(), + WasmExecutor{heap_pages: 8}.call(&mut ext, &test_code[..], "test_blake2_256", b"Hello world!", true).0.unwrap(), blake2_256(&b"Hello world!"[..]).encode() ); } @@ -641,11 +646,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.call(&mut ext, &test_code[..], "test_twox_256", &[]).unwrap(), + WasmExecutor{heap_pages: 8}.call(&mut ext, &test_code[..], "test_twox_256", &[], true).0.unwrap(), FromHex::from_hex("99e9d85137db46ef4bbea33613baafd56f963c64b1f3685a4eb4abd67ff6203a").unwrap() ); assert_eq!( - WasmExecutor.call(&mut ext, &test_code[..], "test_twox_256", b"Hello world!").unwrap(), + WasmExecutor{heap_pages: 8}.call(&mut ext, &test_code[..], "test_twox_256", b"Hello world!", true).0.unwrap(), FromHex::from_hex("b27dfd7f223f177f2a13647b533599af0c07f68bda23d96d059da2b451a35a74").unwrap() ); } @@ -655,11 +660,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.call(&mut ext, &test_code[..], "test_twox_128", &[]).unwrap(), + WasmExecutor{heap_pages: 8}.call(&mut ext, &test_code[..], "test_twox_128", &[], true).0.unwrap(), FromHex::from_hex("99e9d85137db46ef4bbea33613baafd5").unwrap() ); assert_eq!( - WasmExecutor.call(&mut ext, &test_code[..], "test_twox_128", b"Hello world!").unwrap(), + WasmExecutor{heap_pages: 8}.call(&mut ext, &test_code[..], "test_twox_128", b"Hello world!", true).0.unwrap(), FromHex::from_hex("b27dfd7f223f177f2a13647b533599af").unwrap() ); } @@ -675,7 +680,7 @@ mod tests { calldata.extend_from_slice(sig.as_ref()); assert_eq!( - WasmExecutor.call(&mut ext, &test_code[..], "test_ed25519_verify", &calldata).unwrap(), + WasmExecutor{heap_pages: 8}.call(&mut ext, &test_code[..], "test_ed25519_verify", &calldata, true).0.unwrap(), vec![1] ); @@ -685,7 +690,7 @@ mod tests { calldata.extend_from_slice(other_sig.as_ref()); assert_eq!( - WasmExecutor.call(&mut ext, &test_code[..], "test_ed25519_verify", &calldata).unwrap(), + WasmExecutor{heap_pages: 8}.call(&mut ext, &test_code[..], "test_ed25519_verify", &calldata, true).0.unwrap(), vec![0] ); } @@ -695,7 +700,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.call(&mut ext, &test_code[..], "test_enumerated_trie_root", &[]).unwrap(), + WasmExecutor{heap_pages: 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() ); } diff --git a/substrate/substrate/runtime/consensus/src/lib.rs b/substrate/substrate/runtime/consensus/src/lib.rs index 30a1be0c94..cadbd9e865 100644 --- a/substrate/substrate/runtime/consensus/src/lib.rs +++ b/substrate/substrate/runtime/consensus/src/lib.rs @@ -69,6 +69,7 @@ decl_module! { #[cfg_attr(feature = "std", derive(Serialize, Deserialize))] pub enum Call where aux: T::PublicAux { fn report_misbehavior(aux, report: MisbehaviorReport) -> Result = 0; + fn remark(aux, remark: Vec) -> Result = 1; } #[cfg_attr(feature = "std", derive(Serialize, Deserialize))] @@ -104,6 +105,11 @@ impl Module { Ok(()) } + /// Make some on-chain remark. + fn remark(_aux: &T::PublicAux, _remark: Vec) -> Result { + Ok(()) + } + /// Set the current set of authorities' session keys. /// /// Called by `next_session` only. diff --git a/substrate/substrate/state-machine/src/lib.rs b/substrate/substrate/state-machine/src/lib.rs index e0119edeaa..38eb75d6ca 100644 --- a/substrate/substrate/state-machine/src/lib.rs +++ b/substrate/substrate/state-machine/src/lib.rs @@ -150,14 +150,57 @@ pub trait CodeExecutor: Sized + Send + Sync { /// Externalities error type. type Error: Error; - /// Call a given method in the runtime. + /// Call a given method in the runtime. Returns a tuple of the result (either the output data + /// or an execution error) together with a `bool`, which is true if native execution was used. fn call( &self, ext: &mut E, code: &[u8], method: &str, data: &[u8], - ) -> Result, Self::Error>; + use_native: bool + ) -> (Result, Self::Error>, bool); +} + +/// 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, +} + +/// Like `ExecutionStrategy` only it also stores a handler in case of consensus failure. +pub enum ExecutionManager { + /// 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). Call `F` in the case of any discrepency. + Both(F), +} + +impl<'a, F> From<&'a ExecutionManager> for ExecutionStrategy { + fn from(s: &'a ExecutionManager) -> Self { + match *s { + ExecutionManager::NativeWhenPossible => ExecutionStrategy::NativeWhenPossible, + ExecutionManager::AlwaysWasm => ExecutionStrategy::AlwaysWasm, + ExecutionManager::Both(_) => ExecutionStrategy::Both, + } + } +} + +/// Evaluate to ExecutionManager::NativeWhenPossible, without having to figure out the type. +pub fn native_when_possible() -> ExecutionManager, E>, Result, E>)->Result, E>> { + ExecutionManager::NativeWhenPossible +} + +/// Evaluate to ExecutionManager::NativeWhenPossible, without having to figure out the type. +pub fn always_wasm() -> ExecutionManager, E>, Result, E>)->Result, E>> { + ExecutionManager::AlwaysWasm } /// Execute a call using the given state backend, overlayed changes, and call executor. @@ -174,8 +217,46 @@ pub fn execute( exec: &Exec, method: &str, call_data: &[u8], -) -> Result<(Vec, B::Transaction), Box> -{ + strategy: ExecutionStrategy, +) -> Result<(Vec, B::Transaction), Box> { + execute_using_consensus_failure_handler( + backend, + overlay, + exec, + method, + call_data, + match strategy { + ExecutionStrategy::AlwaysWasm => ExecutionManager::AlwaysWasm, + ExecutionStrategy::NativeWhenPossible => ExecutionManager::NativeWhenPossible, + ExecutionStrategy::Both => ExecutionManager::Both(|wasm_result, native_result| { + warn!("Consensus error between wasm {:?} and native {:?}. Using wasm.", wasm_result, native_result); + wasm_result + }), + }, + ) +} + +/// Execute a call using the given state backend, overlayed changes, and call executor. +/// Produces a state-backend-specific "transaction" which can be used to apply the changes +/// to the backing store, such as the disk. +/// +/// On an error, no prospective changes are written to the overlay. +/// +/// Note: changes to code will be in place if this call is made again. For running partial +/// blocks (e.g. a transaction at a time), ensure a different method is used. +pub fn execute_using_consensus_failure_handler< + B: backend::Backend, + Exec: CodeExecutor, + Handler: FnOnce(Result, Exec::Error>, Result, Exec::Error>) -> Result, Exec::Error> +>( + backend: &B, + overlay: &mut OverlayedChanges, + exec: &Exec, + method: &str, + call_data: &[u8], + manager: ExecutionManager, +) -> Result<(Vec, B::Transaction), Box> { + let strategy: ExecutionStrategy = (&manager).into(); let result = { let mut externalities = ext::Ext::new(overlay, backend); // make a copy. @@ -183,12 +264,35 @@ pub fn execute( .ok_or(Box::new(ExecutionError::CodeEntryDoesNotExist) as Box)? .to_vec(); - exec.call( + let (result, was_native) = exec.call( &mut externalities, &code, method, call_data, - ).map(move |out| (out, externalities.transaction())) + // attempt to run native first, if we're not directed to run wasm only + strategy != ExecutionStrategy::AlwaysWasm, + ); + // run wasm separately if we did run native the first time and we're meant to run both + let result = if let (true, ExecutionManager::Both(on_consensus_failure)) = (was_native, manager) { + let (wasm_result, _) = exec.call( + &mut externalities, + &code, + method, + call_data, + false + ); + if (result.is_ok() && wasm_result.is_ok() && result.as_ref().unwrap() == wasm_result.as_ref().unwrap()) + || (result.is_err() && wasm_result.is_err() && format!("{}", result.as_ref().unwrap_err()) == format!("{}", wasm_result.as_ref().unwrap_err())) + { + result + } else { + // Consensus error. + on_consensus_failure(wasm_result, result) + } + } else { + result + }; + result.map(move |out| (out, externalities.transaction())) }; match result { @@ -218,12 +322,11 @@ pub fn prove_execution( exec: &Exec, method: &str, call_data: &[u8], -) -> Result<(Vec, Vec>, ::Transaction), Box> -{ +) -> Result<(Vec, Vec>, ::Transaction), Box> { let trie_backend = backend.try_into_trie_backend() .ok_or_else(|| Box::new(ExecutionError::UnableToGenerateProof) as Box)?; let proving_backend = proving_backend::ProvingBackend::new(trie_backend); - let (result, transaction) = execute(&proving_backend, overlay, exec, method, call_data)?; + let (result, transaction) = execute(&proving_backend, overlay, exec, method, call_data, ExecutionStrategy::NativeWhenPossible)?; let proof = proving_backend.extract_proof(); Ok((result, proof, transaction)) } @@ -236,10 +339,9 @@ pub fn execution_proof_check( exec: &Exec, method: &str, call_data: &[u8], -) -> Result<(Vec, memorydb::MemoryDB), Box> -{ +) -> Result<(Vec, memorydb::MemoryDB), Box> { let backend = proving_backend::create_proof_check_backend(root.into(), proof)?; - execute(&backend, overlay, exec, method, call_data) + execute(&backend, overlay, exec, method, call_data, ExecutionStrategy::NativeWhenPossible) } #[cfg(test)] @@ -248,7 +350,11 @@ mod tests { use super::backend::InMemory; use super::ext::Ext; - struct DummyCodeExecutor; + struct DummyCodeExecutor { + native_available: bool, + native_succeeds: bool, + fallback_succeeds: bool, + } impl CodeExecutor for DummyCodeExecutor { type Error = u8; @@ -259,8 +365,14 @@ mod tests { _code: &[u8], _method: &str, _data: &[u8], - ) -> Result, Self::Error> { - Ok(vec![ext.storage(b"value1").unwrap()[0] + ext.storage(b"value2").unwrap()[0]]) + use_native: bool + ) -> (Result, Self::Error>, bool) { + let using_native = use_native && self.native_available; + match (using_native, self.native_succeeds, self.fallback_succeeds) { + (true, true, _) | (false, _, true) => + (Ok(vec![ext.storage(b"value1").unwrap()[0] + ext.storage(b"value2").unwrap()[0]]), using_native), + _ => (Err(0), using_native), + } } } @@ -325,21 +437,59 @@ mod tests { #[test] fn execute_works() { - assert_eq!(execute(&trie_backend::tests::test_trie(), - &mut Default::default(), &DummyCodeExecutor, "test", &[]).unwrap().0, vec![66]); + assert_eq!(execute( + &trie_backend::tests::test_trie(), + &mut Default::default(), + &DummyCodeExecutor { + native_available: true, + native_succeeds: true, + fallback_succeeds: true, + }, + "test", + &[], + ExecutionStrategy::NativeWhenPossible + ).unwrap().0, vec![66]); + } + + #[test] + fn dual_execution_strategy_detects_consensus_failure() { + let mut consensus_failed = false; + assert!(execute_using_consensus_failure_handler( + &trie_backend::tests::test_trie(), + &mut Default::default(), + &DummyCodeExecutor { + native_available: true, + native_succeeds: true, + fallback_succeeds: false, + }, + "test", + &[], + ExecutionManager::Both(|we, _ne| { + consensus_failed = true; + println!("HELLO!"); + we + }), + ).is_err()); + assert!(consensus_failed); } #[test] fn prove_execution_and_proof_check_works() { + let executor = DummyCodeExecutor { + native_available: true, + native_succeeds: true, + fallback_succeeds: true, + }; + // fetch execution proof from 'remote' full node let remote_backend = trie_backend::tests::test_trie(); let remote_root = remote_backend.storage_root(::std::iter::empty()).0; let (remote_result, remote_proof, _) = prove_execution(remote_backend, - &mut Default::default(), &DummyCodeExecutor, "test", &[]).unwrap(); + &mut Default::default(), &executor, "test", &[]).unwrap(); // check proof locally let (local_result, _) = execution_proof_check(remote_root, remote_proof, - &mut Default::default(), &DummyCodeExecutor, "test", &[]).unwrap(); + &mut Default::default(), &executor, "test", &[]).unwrap(); // check that both results are correct assert_eq!(remote_result, vec![66]);