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
This commit is contained in:
Gav Wood
2018-07-14 16:10:20 +02:00
committed by GitHub
parent 0665cfa9fc
commit ee31955969
20 changed files with 449 additions and 130 deletions
+1
View File
@@ -337,6 +337,7 @@ dependencies = [
"substrate-executor 0.1.0", "substrate-executor 0.1.0",
"substrate-keyring 0.1.0", "substrate-keyring 0.1.0",
"substrate-primitives 0.1.0", "substrate-primitives 0.1.0",
"substrate-runtime-consensus 0.1.0",
"substrate-runtime-io 0.1.0", "substrate-runtime-io 0.1.0",
"substrate-runtime-primitives 0.1.0", "substrate-runtime-primitives 0.1.0",
"substrate-runtime-staking 0.1.0", "substrate-runtime-staking 0.1.0",
+1
View File
@@ -22,3 +22,4 @@ substrate-keyring = { path = "../../substrate/keyring" }
substrate-runtime-primitives = { path = "../../substrate/runtime/primitives" } substrate-runtime-primitives = { path = "../../substrate/runtime/primitives" }
substrate-runtime-staking = { path = "../../substrate/runtime/staking" } substrate-runtime-staking = { path = "../../substrate/runtime/staking" }
substrate-runtime-system = { path = "../../substrate/runtime/system" } substrate-runtime-system = { path = "../../substrate/runtime/system" }
substrate-runtime-consensus = { path = "../../substrate/runtime/consensus" }
+55 -17
View File
@@ -32,6 +32,7 @@ extern crate triehash;
#[cfg(test)] extern crate substrate_runtime_support as runtime_support; #[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_staking as staking;
#[cfg(test)] extern crate substrate_runtime_system as system; #[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; #[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")); 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 demo_primitives::{Hash, BlockNumber, AccountId};
use runtime_primitives::traits::Header as HeaderT; use runtime_primitives::traits::Header as HeaderT;
use runtime_primitives::{ApplyOutcome, ApplyError, ApplyResult, MaybeUnsigned}; use runtime_primitives::{ApplyOutcome, ApplyError, ApplyResult, MaybeUnsigned};
use {staking, system}; use {staking, system, consensus};
use demo_runtime::{Header, Block, UncheckedExtrinsic, Extrinsic, Call, Concrete, Staking, use demo_runtime::{Header, Block, UncheckedExtrinsic, Extrinsic, Call, Concrete, Staking,
BuildStorage, GenesisConfig, SessionConfig, StakingConfig, BareExtrinsic}; BuildStorage, GenesisConfig, SessionConfig, StakingConfig, BareExtrinsic};
use ed25519::{Public, Pair}; use ed25519::{Public, Pair};
@@ -104,9 +105,9 @@ mod tests {
twox_128(&<system::BlockHash<Concrete>>::key_for(0)).to_vec() => vec![0u8; 32] twox_128(&<system::BlockHash<Concrete>>::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()); 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(); let r = ApplyResult::decode(&mut &v[..]).unwrap();
assert_eq!(r, Err(ApplyError::CantPay)); assert_eq!(r, Err(ApplyError::CantPay));
} }
@@ -123,9 +124,9 @@ mod tests {
twox_128(&<system::BlockHash<Concrete>>::key_for(0)).to_vec() => vec![0u8; 32] twox_128(&<system::BlockHash<Concrete>>::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()); 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(); let r = ApplyResult::decode(&mut &v[..]).unwrap();
assert_eq!(r, Err(ApplyError::CantPay)); assert_eq!(r, Err(ApplyError::CantPay));
} }
@@ -142,9 +143,9 @@ mod tests {
twox_128(&<system::BlockHash<Concrete>>::key_for(0)).to_vec() => vec![0u8; 32] twox_128(&<system::BlockHash<Concrete>>::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()); 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()); assert!(r.is_ok());
runtime_io::with_externalities(&mut t, || { runtime_io::with_externalities(&mut t, || {
@@ -165,9 +166,9 @@ mod tests {
twox_128(&<system::BlockHash<Concrete>>::key_for(0)).to_vec() => vec![0u8; 32] twox_128(&<system::BlockHash<Concrete>>::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()); 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()); assert!(r.is_ok());
runtime_io::with_externalities(&mut t, || { runtime_io::with_externalities(&mut t, || {
@@ -271,18 +272,31 @@ mod tests {
) )
} }
fn block1big() -> (Vec<u8>, 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] #[test]
fn full_native_block_import_works() { fn full_native_block_import_works() {
let mut t = new_test_ext(); 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, || { runtime_io::with_externalities(&mut t, || {
assert_eq!(Staking::voting_balance(&alice()), 41); assert_eq!(Staking::voting_balance(&alice()), 41);
assert_eq!(Staking::voting_balance(&bob()), 69); 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, || { runtime_io::with_externalities(&mut t, || {
assert_eq!(Staking::voting_balance(&alice()), 30); assert_eq!(Staking::voting_balance(&alice()), 30);
@@ -294,14 +308,14 @@ mod tests {
fn full_wasm_block_import_works() { fn full_wasm_block_import_works() {
let mut t = new_test_ext(); 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, || { runtime_io::with_externalities(&mut t, || {
assert_eq!(Staking::voting_balance(&alice()), 41); assert_eq!(Staking::voting_balance(&alice()), 41);
assert_eq!(Staking::voting_balance(&bob()), 69); 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, || { runtime_io::with_externalities(&mut t, || {
assert_eq!(Staking::voting_balance(&alice()), 30); 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] #[test]
fn panic_execution_gives_error() { fn panic_execution_gives_error() {
let mut t: TestExternalities = map![ 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 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()); 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(); let r = ApplyResult::decode(&mut &r[..]).unwrap();
assert_eq!(r, Err(ApplyError::CantPay)); 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 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()); 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(); let r = ApplyResult::decode(&mut &r[..]).unwrap();
assert_eq!(r, Ok(ApplyOutcome::Success)); assert_eq!(r, Ok(ApplyOutcome::Success));
+5 -1
View File
@@ -74,7 +74,7 @@ args:
- pruning: - pruning:
long: pruning long: pruning
value_name: PRUNING_MODE 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 takes_value: true
- name: - name:
long: name long: name
@@ -95,6 +95,10 @@ args:
value_name: TELEMETRY_URL value_name: TELEMETRY_URL
help: The URL of the telemetry server. Implies --telemetry help: The URL of the telemetry server. Implies --telemetry
takes_value: true 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: subcommands:
- build-spec: - build-spec:
about: Build a spec.json file, outputing to stdout about: Build a spec.json file, outputing to stdout
+15 -1
View File
@@ -218,18 +218,32 @@ pub fn run<I, T, W>(args: I, worker: W) -> error::Result<()> where
if matches.is_present("collator") { if matches.is_present("collator") {
info!("Starting collator"); info!("Starting collator");
// TODO [rob]: collation node implementation // 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") { } else if matches.is_present("light") {
info!("Starting (light)"); info!("Starting (light)");
config.execution_strategy = service::ExecutionStrategy::NativeWhenPossible;
service::Role::LIGHT service::Role::LIGHT
} else if matches.is_present("validator") || matches.is_present("dev") { } else if matches.is_present("validator") || matches.is_present("dev") {
info!("Starting validator"); info!("Starting validator");
config.execution_strategy = service::ExecutionStrategy::Both;
service::Role::AUTHORITY service::Role::AUTHORITY
} else { } else {
info!("Starting (heavy)"); info!("Starting (heavy)");
config.execution_strategy = service::ExecutionStrategy::NativeWhenPossible;
service::Role::FULL 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.roles = role;
{ {
config.network.boot_nodes.extend(matches config.network.boot_nodes.extend(matches
+5 -5
View File
@@ -30,7 +30,7 @@ use polkadot_api;
use polkadot_executor::Executor as LocalDispatch; use polkadot_executor::Executor as LocalDispatch;
use polkadot_network::NetworkService; use polkadot_network::NetworkService;
use polkadot_primitives::{Block, BlockId, Hash}; use polkadot_primitives::{Block, BlockId, Hash};
use state_machine; use state_machine::{self, ExecutionStrategy};
use substrate_executor::NativeExecutor; use substrate_executor::NativeExecutor;
use transaction_pool::{self, TransactionPool}; use transaction_pool::{self, TransactionPool};
use tokio::runtime::TaskExecutor; use tokio::runtime::TaskExecutor;
@@ -50,7 +50,7 @@ pub trait Components {
type Executor: 'static + client::CallExecutor<Block> + Send + Sync; type Executor: 'static + client::CallExecutor<Block> + Send + Sync;
/// Create client. /// 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<Client<Self::Backend, Self::Executor, Block>>, Option<Arc<OnDemand<Block, NetworkService>>>), error::Error>; -> Result<(Arc<Client<Self::Backend, Self::Executor, Block>>, Option<Arc<OnDemand<Block, NetworkService>>>), error::Error>;
/// Create api. /// Create api.
@@ -83,9 +83,9 @@ impl Components for FullComponents {
type Api = Client<Self::Backend, Self::Executor, Block>; type Api = Client<Self::Backend, Self::Executor, Block>;
type Executor = client::LocalCallExecutor<client_db::Backend<Block>, NativeExecutor<LocalDispatch>>; type Executor = client::LocalCallExecutor<client_db::Backend<Block>, NativeExecutor<LocalDispatch>>;
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<client::Client<Self::Backend, Self::Executor, Block>>, Option<Arc<OnDemand<Block, NetworkService>>>), error::Error> { -> Result<(Arc<client::Client<Self::Backend, Self::Executor, Block>>, Option<Arc<OnDemand<Block, NetworkService>>>), 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<client::Client<Self::Backend, Self::Executor, Block>>) -> Arc<Self::Api> { fn build_api(&self, client: Arc<client::Client<Self::Backend, Self::Executor, Block>>) -> Arc<Self::Api> {
@@ -142,7 +142,7 @@ impl Components for LightComponents {
client::light::blockchain::Blockchain<client_db::light::LightStorage<Block>, network::OnDemand<Block, NetworkService>>, client::light::blockchain::Blockchain<client_db::light::LightStorage<Block>, network::OnDemand<Block, NetworkService>>,
network::OnDemand<Block, NetworkService>>; network::OnDemand<Block, NetworkService>>;
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<client::Client<Self::Backend, Self::Executor, Block>>, Option<Arc<OnDemand<Block, NetworkService>>>), error::Error> { -> Result<(Arc<client::Client<Self::Backend, Self::Executor, Block>>, Option<Arc<OnDemand<Block, NetworkService>>>), error::Error> {
let db_storage = client_db::light::LightStorage::new(db_settings)?; let db_storage = client_db::light::LightStorage::new(db_settings)?;
let light_blockchain = client::light::new_light_blockchain(db_storage); let light_blockchain = client::light::new_light_blockchain(db_storage);
+4
View File
@@ -18,6 +18,7 @@
use transaction_pool; use transaction_pool;
use chain_spec::ChainSpec; use chain_spec::ChainSpec;
pub use state_machine::ExecutionStrategy;
pub use network::Role; pub use network::Role;
pub use network::NetworkConfiguration; pub use network::NetworkConfiguration;
pub use client_db::PruningMode; pub use client_db::PruningMode;
@@ -44,6 +45,8 @@ pub struct Configuration {
pub telemetry: Option<String>, pub telemetry: Option<String>,
/// Node name. /// Node name.
pub name: String, pub name: String,
/// Execution strategy.
pub execution_strategy: ExecutionStrategy,
} }
impl Configuration { impl Configuration {
@@ -60,6 +63,7 @@ impl Configuration {
keys: Default::default(), keys: Default::default(),
telemetry: Default::default(), telemetry: Default::default(),
pruning: PruningMode::ArchiveAll, pruning: PruningMode::ArchiveAll,
execution_strategy: ExecutionStrategy::Both,
}; };
configuration.network.boot_nodes = configuration.chain_spec.boot_nodes().to_vec(); configuration.network.boot_nodes = configuration.chain_spec.boot_nodes().to_vec();
configuration configuration
+4 -4
View File
@@ -73,7 +73,7 @@ use tokio::runtime::TaskExecutor;
pub use self::error::{ErrorKind, Error}; pub use self::error::{ErrorKind, Error};
pub use self::components::{Components, FullComponents, LightComponents}; 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; pub use chain_spec::ChainSpec;
/// Polkadot service. /// Polkadot service.
@@ -112,7 +112,7 @@ pub fn new_client(config: Configuration) -> Result<Arc<Client<
let executor = polkadot_executor::Executor::new(); let executor = polkadot_executor::Executor::new();
let is_validator = (config.roles & Role::AUTHORITY) == Role::AUTHORITY; let is_validator = (config.roles & Role::AUTHORITY) == Role::AUTHORITY;
let components = components::FullComponents { is_validator }; let components = components::FullComponents { is_validator };
let (client, _) = components.build_client(db_settings, executor, &config.chain_spec)?; let (client, _) = components.build_client(db_settings, executor, &config.chain_spec, config.execution_strategy)?;
Ok(client) Ok(client)
} }
@@ -126,7 +126,7 @@ impl<Components> Service<Components>
let (signal, exit) = ::exit_future::signal(); let (signal, exit) = ::exit_future::signal();
// Create client // 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())?; let mut keystore = Keystore::open(config.keystore_path.into())?;
for seed in &config.keys { for seed in &config.keys {
@@ -144,7 +144,7 @@ impl<Components> Service<Components>
pruning: config.pruning, 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 api = components.build_api(client.clone());
let best_header = client.best_block_header()?; let best_header = client.best_block_header()?;
+3 -2
View File
@@ -54,7 +54,7 @@ use runtime_primitives::traits::{Block as BlockT, Header as HeaderT, As, Hash, H
use runtime_primitives::BuildStorage; use runtime_primitives::BuildStorage;
use state_machine::backend::Backend as StateBackend; use state_machine::backend::Backend as StateBackend;
use executor::RuntimeInfo; 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 utils::{Meta, db_err, meta_keys, number_to_db_key, open_database, read_db, read_id, read_meta};
use state_db::StateDb; use state_db::StateDb;
pub use state_db::PruningMode; pub use state_db::PruningMode;
@@ -79,6 +79,7 @@ pub fn new_client<E, S, Block>(
settings: DatabaseSettings, settings: DatabaseSettings,
executor: E, executor: E,
genesis_storage: S, genesis_storage: S,
execution_strategy: ExecutionStrategy,
) -> Result<client::Client<Backend<Block>, client::LocalCallExecutor<Backend<Block>, E>, Block>, client::error::Error> ) -> Result<client::Client<Backend<Block>, client::LocalCallExecutor<Backend<Block>, E>, Block>, client::error::Error>
where where
Block: BlockT, Block: BlockT,
@@ -89,7 +90,7 @@ pub fn new_client<E, S, Block>(
{ {
let backend = Arc::new(Backend::new(settings, FINALIZATION_WINDOW)?); let backend = Arc::new(Backend::new(settings, FINALIZATION_WINDOW)?);
let executor = client::LocalCallExecutor::new(backend.clone(), executor); 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 { mod columns {
@@ -18,7 +18,7 @@
use std::vec::Vec; use std::vec::Vec;
use codec::Slicable; 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::traits::{Header as HeaderT, Hash, Block as BlockT, One, HashFor};
use runtime_primitives::generic::BlockId; use runtime_primitives::generic::BlockId;
use {backend, error, Client, CallExecutor}; use {backend, error, Client, CallExecutor};
@@ -69,7 +69,7 @@ impl<B, E, Block> BlockBuilder<B, E, Block> where
Default::default() 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 { Ok(BlockBuilder {
header, header,
@@ -84,7 +84,7 @@ impl<B, E, Block> BlockBuilder<B, E, Block> where
/// can be validly executed (by executing it); if it is invalid, it'll be returned along with /// 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). /// the error. Otherwise, it will return a mutable reference to self (in order to chain).
pub fn push(&mut self, xt: <Block as BlockT>::Extrinsic) -> error::Result<()> { pub fn push(&mut self, xt: <Block as BlockT>::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(_) => { Ok(_) => {
self.extrinsics.push(xt); self.extrinsics.push(xt);
Ok(()) Ok(())
@@ -103,6 +103,7 @@ impl<B, E, Block> BlockBuilder<B, E, Block> where
&mut self.changes, &mut self.changes,
"finalise_block", "finalise_block",
&[], &[],
native_when_possible(),
)?; )?;
self.header = <<Block as BlockT>::Header as Slicable>::decode(&mut &output[..]) self.header = <<Block as BlockT>::Header as Slicable>::decode(&mut &output[..])
.expect("Header came straight out of runtime so must be valid"); .expect("Header came straight out of runtime so must be valid");
@@ -17,7 +17,8 @@
use std::sync::Arc; use std::sync::Arc;
use runtime_primitives::generic::BlockId; use runtime_primitives::generic::BlockId;
use runtime_primitives::traits::Block as BlockT; 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 runtime_io::Externalities;
use executor::{RuntimeVersion, RuntimeInfo}; use executor::{RuntimeVersion, RuntimeInfo};
@@ -41,7 +42,11 @@ pub trait CallExecutor<B: BlockT> {
/// Execute a call to a contract on top of state in a block of given hash. /// Execute a call to a contract on top of state in a block of given hash.
/// ///
/// No changes are made. /// No changes are made.
fn call(&self, id: &BlockId<B>, method: &str, call_data: &[u8]) -> Result<CallResult, error::Error>; fn call(&self,
id: &BlockId<B>,
method: &str,
call_data: &[u8],
) -> Result<CallResult, error::Error>;
/// Extract RuntimeVersion of given block /// Extract RuntimeVersion of given block
/// ///
@@ -51,12 +56,26 @@ pub trait CallExecutor<B: BlockT> {
/// Execute a call to a contract on top of given state. /// Execute a call to a contract on top of given state.
/// ///
/// No changes are made. /// No changes are made.
fn call_at_state<S: state_machine::Backend>(&self, state: &S, overlay: &mut OverlayedChanges, method: &str, call_data: &[u8]) -> Result<(Vec<u8>, S::Transaction), error::Error>; fn call_at_state<
S: state_machine::Backend,
F: FnOnce(Result<Vec<u8>, Self::Error>, Result<Vec<u8>, Self::Error>) -> Result<Vec<u8>, Self::Error>,
>(&self,
state: &S,
overlay: &mut OverlayedChanges,
method: &str,
call_data: &[u8],
manager: ExecutionManager<F>
) -> Result<(Vec<u8>, S::Transaction), error::Error>;
/// Execute a call to a contract on top of given state, gathering execution proof. /// Execute a call to a contract on top of given state, gathering execution proof.
/// ///
/// No changes are made. /// No changes are made.
fn prove_at_state<S: state_machine::Backend>(&self, state: S, overlay: &mut OverlayedChanges, method: &str, call_data: &[u8]) -> Result<(Vec<u8>, Vec<Vec<u8>>), error::Error>; fn prove_at_state<S: state_machine::Backend>(&self,
state: S,
overlay: &mut OverlayedChanges,
method: &str,
call_data: &[u8]
) -> Result<(Vec<u8>, Vec<Vec<u8>>), error::Error>;
/// Get runtime version if supported. /// Get runtime version if supported.
fn native_runtime_version(&self) -> Option<RuntimeVersion>; fn native_runtime_version(&self) -> Option<RuntimeVersion>;
@@ -94,9 +113,19 @@ impl<B, E, Block> CallExecutor<Block> for LocalCallExecutor<B, E>
{ {
type Error = E::Error; type Error = E::Error;
fn call(&self, id: &BlockId<Block>, method: &str, call_data: &[u8]) -> error::Result<CallResult> { fn call(&self,
id: &BlockId<Block>,
method: &str,
call_data: &[u8],
) -> error::Result<CallResult> {
let mut changes = OverlayedChanges::default(); 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 }) Ok(CallResult{ return_data, changes })
} }
@@ -111,17 +140,32 @@ impl<B, E, Block> CallExecutor<Block> for LocalCallExecutor<B, E>
.ok_or(error::ErrorKind::VersionInvalid.into()) .ok_or(error::ErrorKind::VersionInvalid.into())
} }
fn call_at_state<S: state_machine::Backend>(&self, state: &S, changes: &mut OverlayedChanges, method: &str, call_data: &[u8]) -> error::Result<(Vec<u8>, S::Transaction)> { fn call_at_state<
state_machine::execute( S: state_machine::Backend,
F: FnOnce(Result<Vec<u8>, Self::Error>, Result<Vec<u8>, Self::Error>) -> Result<Vec<u8>, Self::Error>,
>(&self,
state: &S,
changes: &mut OverlayedChanges,
method: &str,
call_data: &[u8],
manager: ExecutionManager<F>,
) -> error::Result<(Vec<u8>, S::Transaction)> {
state_machine::execute_using_consensus_failure_handler(
state, state,
changes, changes,
&self.executor, &self.executor,
method, method,
call_data, call_data,
manager,
).map_err(Into::into) ).map_err(Into::into)
} }
fn prove_at_state<S: state_machine::Backend>(&self, state: S, changes: &mut OverlayedChanges, method: &str, call_data: &[u8]) -> Result<(Vec<u8>, Vec<Vec<u8>>), error::Error> { fn prove_at_state<S: state_machine::Backend>(&self,
state: S,
changes: &mut OverlayedChanges,
method: &str,
call_data: &[u8]
) -> Result<(Vec<u8>, Vec<Vec<u8>>), error::Error> {
state_machine::prove_execution( state_machine::prove_execution(
state, state,
changes, changes,
+27 -7
View File
@@ -25,7 +25,7 @@ use runtime_primitives::traits::{Block as BlockT, Header as HeaderT, Zero, One,
use runtime_primitives::BuildStorage; use runtime_primitives::BuildStorage;
use primitives::storage::{StorageKey, StorageData}; use primitives::storage::{StorageKey, StorageData};
use codec::Slicable; 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 backend::{self, BlockImportOperation};
use blockchain::{self, Info as ChainInfo, Backend as ChainBackend, HeaderBackend as ChainHeaderBackend}; use blockchain::{self, Info as ChainInfo, Backend as ChainBackend, HeaderBackend as ChainHeaderBackend};
@@ -43,6 +43,7 @@ pub struct Client<B, E, Block> where Block: BlockT {
import_notification_sinks: Mutex<Vec<mpsc::UnboundedSender<BlockImportNotification<Block>>>>, import_notification_sinks: Mutex<Vec<mpsc::UnboundedSender<BlockImportNotification<Block>>>>,
import_lock: Mutex<()>, import_lock: Mutex<()>,
importing_block: RwLock<Option<Block::Hash>>, // holds the block hash currently being imported. TODO: replace this with block queue importing_block: RwLock<Option<Block::Hash>>, // holds the block hash currently being imported. TODO: replace this with block queue
execution_strategy: ExecutionStrategy,
} }
/// A source of blockchain evenets. /// A source of blockchain evenets.
@@ -144,7 +145,7 @@ impl<Block: BlockT> JustifiedHeader<Block> {
/// Create an instance of in-memory client. /// Create an instance of in-memory client.
pub fn new_in_mem<E, Block, S>( pub fn new_in_mem<E, Block, S>(
executor: E, executor: E,
genesis_storage: S genesis_storage: S,
) -> error::Result<Client<in_mem::Backend<Block>, LocalCallExecutor<in_mem::Backend<Block>, E>, Block>> ) -> error::Result<Client<in_mem::Backend<Block>, LocalCallExecutor<in_mem::Backend<Block>, E>, Block>>
where where
E: CodeExecutor + RuntimeInfo, E: CodeExecutor + RuntimeInfo,
@@ -153,7 +154,7 @@ pub fn new_in_mem<E, Block, S>(
{ {
let backend = Arc::new(in_mem::Backend::new()); let backend = Arc::new(in_mem::Backend::new());
let executor = LocalCallExecutor::new(backend.clone(), executor); let executor = LocalCallExecutor::new(backend.clone(), executor);
Client::new(backend, executor, genesis_storage) Client::new(backend, executor, genesis_storage, ExecutionStrategy::NativeWhenPossible)
} }
impl<B, E, Block> Client<B, E, Block> where impl<B, E, Block> Client<B, E, Block> where
@@ -167,6 +168,7 @@ impl<B, E, Block> Client<B, E, Block> where
backend: Arc<B>, backend: Arc<B>,
executor: E, executor: E,
build_genesis_storage: S, build_genesis_storage: S,
execution_strategy: ExecutionStrategy,
) -> error::Result<Self> { ) -> error::Result<Self> {
if backend.blockchain().header(BlockId::Number(Zero::zero()))?.is_none() { if backend.blockchain().header(BlockId::Number(Zero::zero()))?.is_none() {
let genesis_storage = build_genesis_storage.build_storage()?; let genesis_storage = build_genesis_storage.build_storage()?;
@@ -183,6 +185,7 @@ impl<B, E, Block> Client<B, E, Block> where
import_notification_sinks: Mutex::new(Vec::new()), import_notification_sinks: Mutex::new(Vec::new()),
import_lock: Mutex::new(()), import_lock: Mutex::new(()),
importing_block: RwLock::new(None), importing_block: RwLock::new(None),
execution_strategy,
}) })
} }
@@ -327,13 +330,30 @@ impl<B, E, Block> Client<B, E, Block> where
let storage_update = match transaction.state()? { let storage_update = match transaction.state()? {
Some(transaction_state) => { Some(transaction_state) => {
let mut overlay = Default::default(); let mut overlay = Default::default();
let (_, storage_update) = self.executor.call_at_state( let mut r = self.executor.call_at_state(
transaction_state, transaction_state,
&mut overlay, &mut overlay,
"execute_block", "execute_block",
&<Block as BlockT>::new(header.clone(), body.clone().unwrap_or_default()).encode() &<Block as BlockT>::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) Some(storage_update)
}, },
None => None, None => None,
+13 -7
View File
@@ -45,7 +45,7 @@ mod tests {
use codec::{Slicable, Joiner}; use codec::{Slicable, Joiner};
use keyring::Keyring; use keyring::Keyring;
use executor::WasmExecutor; use executor::WasmExecutor;
use state_machine::{execute, OverlayedChanges}; use state_machine::{execute, OverlayedChanges, ExecutionStrategy};
use state_machine::backend::InMemory; use state_machine::backend::InMemory;
use test_client; use test_client;
use test_client::runtime::genesismap::{GenesisConfig, additional_storage_with_genesis}; use test_client::runtime::genesismap::{GenesisConfig, additional_storage_with_genesis};
@@ -83,6 +83,7 @@ mod tests {
&Executor::new(), &Executor::new(),
"initialise_block", "initialise_block",
&header.encode(), &header.encode(),
ExecutionStrategy::NativeWhenPossible,
).unwrap(); ).unwrap();
for tx in transactions.iter() { for tx in transactions.iter() {
@@ -92,6 +93,7 @@ mod tests {
&Executor::new(), &Executor::new(),
"apply_extrinsic", "apply_extrinsic",
&tx.encode(), &tx.encode(),
ExecutionStrategy::NativeWhenPossible,
).unwrap(); ).unwrap();
} }
@@ -100,7 +102,8 @@ mod tests {
&mut overlay, &mut overlay,
&Executor::new(), &Executor::new(),
"finalise_block", "finalise_block",
&[] &[],
ExecutionStrategy::NativeWhenPossible,
).unwrap(); ).unwrap();
header = Header::decode(&mut &ret_data[..]).unwrap(); header = Header::decode(&mut &ret_data[..]).unwrap();
println!("root after: {:?}", header.extrinsics_root); println!("root after: {:?}", header.extrinsics_root);
@@ -141,7 +144,8 @@ mod tests {
&mut overlay, &mut overlay,
&Executor::new(), &Executor::new(),
"execute_block", "execute_block",
&b1data &b1data,
ExecutionStrategy::NativeWhenPossible,
).unwrap(); ).unwrap();
} }
@@ -161,9 +165,10 @@ mod tests {
let _ = execute( let _ = execute(
&backend, &backend,
&mut overlay, &mut overlay,
&WasmExecutor, &WasmExecutor { heap_pages: 8 },
"execute_block", "execute_block",
&b1data &b1data,
ExecutionStrategy::NativeWhenPossible,
).unwrap(); ).unwrap();
} }
@@ -184,9 +189,10 @@ mod tests {
let _ = execute( let _ = execute(
&backend, &backend,
&mut overlay, &mut overlay,
&Executor::new(), &Executor::with_heap_pages(8),
"execute_block", "execute_block",
&b1data &b1data,
ExecutionStrategy::NativeWhenPossible,
).unwrap(); ).unwrap();
} }
} }
@@ -22,7 +22,7 @@ use futures::{IntoFuture, Future};
use runtime_primitives::generic::BlockId; use runtime_primitives::generic::BlockId;
use runtime_primitives::traits::{Block as BlockT, Header as HeaderT}; 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 blockchain::Backend as ChainBackend;
use call_executor::{CallExecutor, CallResult}; use call_executor::{CallExecutor, CallResult};
@@ -73,7 +73,16 @@ impl<B, F, Block> CallExecutor<Block> for RemoteCallExecutor<B, F>
.ok_or_else(|| ClientErrorKind::VersionInvalid.into()) .ok_or_else(|| ClientErrorKind::VersionInvalid.into())
} }
fn call_at_state<S: StateBackend>(&self, _state: &S, _changes: &mut OverlayedChanges, _method: &str, _call_data: &[u8]) -> ClientResult<(Vec<u8>, S::Transaction)> { fn call_at_state<
S: StateBackend,
H: FnOnce(Result<Vec<u8>, Self::Error>, Result<Vec<u8>, Self::Error>) -> Result<Vec<u8>, Self::Error>
>(&self,
_state: &S,
_changes: &mut OverlayedChanges,
_method: &str,
_call_data: &[u8],
_m: ExecutionManager<H>
) -> ClientResult<(Vec<u8>, S::Transaction)> {
Err(ClientErrorKind::NotAvailableOnLightClient.into()) Err(ClientErrorKind::NotAvailableOnLightClient.into())
} }
+2 -2
View File
@@ -25,7 +25,7 @@ use std::sync::Arc;
use runtime_primitives::BuildStorage; use runtime_primitives::BuildStorage;
use runtime_primitives::traits::Block as BlockT; use runtime_primitives::traits::Block as BlockT;
use state_machine::CodeExecutor; use state_machine::{CodeExecutor, ExecutionStrategy};
use client::Client; use client::Client;
use error::Result as ClientResult; use error::Result as ClientResult;
@@ -58,7 +58,7 @@ pub fn new_light<B, S, F, GS>(
GS: BuildStorage, GS: BuildStorage,
{ {
let executor = RemoteCallExecutor::new(backend.blockchain().clone(), fetcher); 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. /// Create an instance of fetch data checker.
@@ -27,14 +27,14 @@ use parking_lot::{Mutex, MutexGuard};
use RuntimeInfo; use RuntimeInfo;
// For the internal Runtime Cache: // For the internal Runtime Cache:
// Do we run this natively or use the given WasmModule // Is it compatible enough to run this natively or do we need to fall back on the WasmModule
enum RunWith { enum Compatibility {
InvalidVersion(WasmModule), InvalidVersion(WasmModule),
NativeRuntime(RuntimeVersion), IsCompatible(RuntimeVersion),
WasmRuntime(RuntimeVersion, WasmModule) NotCompatible(RuntimeVersion, WasmModule)
} }
type CacheType = HashMap<u64, RunWith>; type CacheType = HashMap<u64, Compatibility>;
lazy_static! { lazy_static! {
static ref RUNTIMES_CACHE: Mutex<CacheType> = Mutex::new(HashMap::new()); static ref RUNTIMES_CACHE: Mutex<CacheType> = 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 /// 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` /// can be used by by comparing returned RuntimeVersion to `ref_version`
fn fetch_cached_runtime_version<'a, E: Externalities>( fn fetch_cached_runtime_version<'a, E: Externalities>(
cache: &'a mut MutexGuard<CacheType>, cache: &'a mut MutexGuard<CacheType>,
ext: &mut E, ext: &mut E,
code: &[u8], code: &[u8],
ref_version: RuntimeVersion ref_version: RuntimeVersion
) -> &'a RunWith { ) -> &'a Compatibility {
cache.entry(gen_cache_key(code)) cache.entry(gen_cache_key(code))
.or_insert_with(|| { .or_insert_with(|| {
let module = WasmModule::from_buffer(code).expect("all modules compiled with rustc are valid wasm code; qed"); 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())); .and_then(|v| RuntimeVersion::decode(&mut v.as_slice()));
if let Some(v) = version { if let Some(v) = version {
if ref_version.can_call_with(&v) { if ref_version.can_call_with(&v) {
RunWith::NativeRuntime(v) Compatibility::IsCompatible(v)
} else { } else {
RunWith::WasmRuntime(v, module) Compatibility::NotCompatible(v, module)
} }
} else { } else {
RunWith::InvalidVersion(module) Compatibility::InvalidVersion(module)
} }
}) })
} }
@@ -112,18 +111,26 @@ pub trait NativeExecutionDispatch {
pub struct NativeExecutor<D: NativeExecutionDispatch + Sync + Send> { pub struct NativeExecutor<D: NativeExecutionDispatch + Sync + Send> {
/// Dummy field to avoid the compiler complaining about us not using `D`. /// Dummy field to avoid the compiler complaining about us not using `D`.
_dummy: ::std::marker::PhantomData<D>, _dummy: ::std::marker::PhantomData<D>,
/// The fallback executor in case native isn't available.
fallback: WasmExecutor,
} }
impl<D: NativeExecutionDispatch + Sync + Send> NativeExecutor<D> { impl<D: NativeExecutionDispatch + Sync + Send> NativeExecutor<D> {
/// Create new instance. /// Create new instance with 128 pages for the wasm fallback's heap.
pub fn new() -> Self { 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 // FIXME: set this entry at compile time
RUNTIMES_CACHE.lock().insert( RUNTIMES_CACHE.lock().insert(
gen_cache_key(D::native_equivalent()), gen_cache_key(D::native_equivalent()),
RunWith::NativeRuntime(D::VERSION)); Compatibility::IsCompatible(D::VERSION));
NativeExecutor { NativeExecutor {
_dummy: Default::default(), _dummy: Default::default(),
fallback: WasmExecutor{heap_pages},
} }
} }
} }
@@ -132,6 +139,7 @@ impl<D: NativeExecutionDispatch + Sync + Send> Clone for NativeExecutor<D> {
fn clone(&self) -> Self { fn clone(&self) -> Self {
NativeExecutor { NativeExecutor {
_dummy: Default::default(), _dummy: Default::default(),
fallback: self.fallback.clone(),
} }
} }
} }
@@ -146,8 +154,8 @@ impl<D: NativeExecutionDispatch + Sync + Send> RuntimeInfo for NativeExecutor<D>
) -> Option<RuntimeVersion> { ) -> Option<RuntimeVersion> {
let mut c = RUNTIMES_CACHE.lock(); let mut c = RUNTIMES_CACHE.lock();
match fetch_cached_runtime_version(&mut c, ext, code, D::VERSION) { match fetch_cached_runtime_version(&mut c, ext, code, D::VERSION) {
RunWith::NativeRuntime(v) | RunWith::WasmRuntime(v, _) => Some(v.clone()), Compatibility::IsCompatible(v) | Compatibility::NotCompatible(v, _) => Some(v.clone()),
RunWith::InvalidVersion(_m) => None Compatibility::InvalidVersion(_m) => None
} }
} }
} }
@@ -161,11 +169,15 @@ impl<D: NativeExecutionDispatch + Sync + Send> CodeExecutor for NativeExecutor<D
code: &[u8], code: &[u8],
method: &str, method: &str,
data: &[u8], data: &[u8],
) -> Result<Vec<u8>> { use_native: bool,
) -> (Result<Vec<u8>>, bool) {
let mut c = RUNTIMES_CACHE.lock(); let mut c = RUNTIMES_CACHE.lock();
match fetch_cached_runtime_version(&mut c, ext, code, D::VERSION) { match (use_native, fetch_cached_runtime_version(&mut c, ext, code, D::VERSION)) {
RunWith::NativeRuntime(_v) => D::dispatch(ext, method, data), (_, Compatibility::NotCompatible(_, m)) | (_, Compatibility::InvalidVersion(m)) =>
RunWith::WasmRuntime(_, m) | RunWith::InvalidVersion(m) => WasmExecutor.call_in_wasm_module(ext, m, method, data) (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> { pub fn new() -> $crate::NativeExecutor<$name> {
$crate::NativeExecutor::new() $crate::NativeExecutor::new()
} }
pub fn with_heap_pages(heap_pages: usize) -> $crate::NativeExecutor<$name> {
$crate::NativeExecutor::with_heap_pages(heap_pages)
}
} }
} }
} }
+5 -5
View File
@@ -554,7 +554,7 @@ mod tests {
"#).unwrap(); "#).unwrap();
assert_eq!( 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], vec![1],
); );
} }
@@ -575,7 +575,7 @@ mod tests {
"#).unwrap(); "#).unwrap();
assert_eq!( 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], vec![0],
); );
} }
@@ -613,7 +613,7 @@ mod tests {
"#).unwrap(); "#).unwrap();
assert_eq!( 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], vec![1],
); );
} }
@@ -647,7 +647,7 @@ mod tests {
"#).unwrap(); "#).unwrap();
assert_eq!( 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], vec![1],
); );
} }
@@ -669,7 +669,7 @@ mod tests {
"#).unwrap(); "#).unwrap();
assert_eq!( 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], vec![1],
); );
} }
@@ -37,28 +37,28 @@ struct Heap {
} }
impl 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 /// Returns `Err` if the heap couldn't allocate required
/// number of pages. /// number of pages.
/// ///
/// This could mean that wasm binary specifies memory /// This could mean that wasm binary specifies memory
/// limit and we are trying to allocate beyond that limit. /// limit and we are trying to allocate beyond that limit.
fn new(memory: &MemoryRef) -> Result<Self> { fn new(memory: &MemoryRef, pages: usize) -> Result<Self> {
const HEAP_SIZE_IN_PAGES: usize = 1024;
let prev_page_count = memory let prev_page_count = memory
.grow(Pages(HEAP_SIZE_IN_PAGES)) .grow(Pages(pages))
.map_err(|_| Error::from(ErrorKind::Runtime))?; .map_err(|_| Error::from(ErrorKind::Runtime))?;
Ok(Heap { Ok(Heap {
end: Bytes::from(prev_page_count).0 as u32, end: Bytes::from(prev_page_count).0 as u32,
}) })
} }
fn allocate(&mut self, size: u32) -> u32 { fn allocate(&mut self, size: u32) -> u32 {
let r = self.end; let r = self.end;
self.end += size; self.end += size;
r r
} }
fn deallocate(&mut self, _offset: u32) { fn deallocate(&mut self, _offset: u32) {
} }
} }
@@ -73,10 +73,10 @@ struct FunctionExecutor<'e, E: Externalities + 'e> {
} }
impl<'e, E: Externalities> FunctionExecutor<'e, E> { impl<'e, E: Externalities> FunctionExecutor<'e, E> {
fn new(m: MemoryRef, t: Option<TableRef>, e: &'e mut E) -> Result<Self> { fn new(m: MemoryRef, heap_pages: usize, t: Option<TableRef>, e: &'e mut E) -> Result<Self> {
Ok(FunctionExecutor { Ok(FunctionExecutor {
sandbox_store: sandbox::Store::new(), sandbox_store: sandbox::Store::new(),
heap: Heap::new(&m)?, heap: Heap::new(&m, heap_pages)?,
memory: m, memory: m,
table: t, table: t,
ext: e, ext: e,
@@ -468,7 +468,10 @@ impl_function_executor!(this: FunctionExecutor<'e, E>,
/// ///
/// Executes the provided code in a sandboxed wasm runtime. /// Executes the provided code in a sandboxed wasm runtime.
#[derive(Debug, Default, Clone)] #[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 { impl WasmExecutor {
@@ -502,7 +505,7 @@ impl WasmExecutor {
.not_started_instance() .not_started_instance()
.export_by_name("table") .export_by_name("table")
.and_then(|e| e.as_table().cloned()); .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). // finish instantiation by running 'start' function (if any).
let instance = intermediate_instance.run_start(&mut fec)?; let instance = intermediate_instance.run_start(&mut fec)?;
@@ -540,9 +543,11 @@ impl CodeExecutor for WasmExecutor {
code: &[u8], code: &[u8],
method: &str, method: &str,
data: &[u8], data: &[u8],
) -> Result<Vec<u8>> { _use_native: bool
let module = Module::from_buffer(code)?; ) -> (Result<Vec<u8>>, bool) {
self.call_in_wasm_module(ext, &module, method, data) (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 mut ext = TestExternalities::default();
let test_code = include_bytes!("../wasm/target/wasm32-unknown-unknown/release/runtime_test.compact.wasm"); 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]); assert_eq!(output, vec![0u8; 0]);
} }
@@ -574,10 +579,10 @@ mod tests {
let mut ext = TestExternalities::default(); let mut ext = TestExternalities::default();
let test_code = include_bytes!("../wasm/target/wasm32-unknown-unknown/release/runtime_test.compact.wasm"); 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()); 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()); assert!(output.is_err());
} }
@@ -587,7 +592,7 @@ mod tests {
ext.set_storage(b"foo".to_vec(), b"bar".to_vec()); 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 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()); 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"); let test_code = include_bytes!("../wasm/target/wasm32-unknown-unknown/release/runtime_test.compact.wasm");
// This will clear all entries which prefix is "ab". // 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()); assert_eq!(output, b"all ok!".to_vec());
@@ -627,11 +632,11 @@ mod tests {
let mut ext = TestExternalities::default(); let mut ext = TestExternalities::default();
let test_code = include_bytes!("../wasm/target/wasm32-unknown-unknown/release/runtime_test.compact.wasm"); let test_code = include_bytes!("../wasm/target/wasm32-unknown-unknown/release/runtime_test.compact.wasm");
assert_eq!( 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() blake2_256(&b""[..]).encode()
); );
assert_eq!( 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() blake2_256(&b"Hello world!"[..]).encode()
); );
} }
@@ -641,11 +646,11 @@ mod tests {
let mut ext = TestExternalities::default(); let mut ext = TestExternalities::default();
let test_code = include_bytes!("../wasm/target/wasm32-unknown-unknown/release/runtime_test.compact.wasm"); let test_code = include_bytes!("../wasm/target/wasm32-unknown-unknown/release/runtime_test.compact.wasm");
assert_eq!( 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() FromHex::from_hex("99e9d85137db46ef4bbea33613baafd56f963c64b1f3685a4eb4abd67ff6203a").unwrap()
); );
assert_eq!( 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() FromHex::from_hex("b27dfd7f223f177f2a13647b533599af0c07f68bda23d96d059da2b451a35a74").unwrap()
); );
} }
@@ -655,11 +660,11 @@ mod tests {
let mut ext = TestExternalities::default(); let mut ext = TestExternalities::default();
let test_code = include_bytes!("../wasm/target/wasm32-unknown-unknown/release/runtime_test.compact.wasm"); let test_code = include_bytes!("../wasm/target/wasm32-unknown-unknown/release/runtime_test.compact.wasm");
assert_eq!( 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() FromHex::from_hex("99e9d85137db46ef4bbea33613baafd5").unwrap()
); );
assert_eq!( 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() FromHex::from_hex("b27dfd7f223f177f2a13647b533599af").unwrap()
); );
} }
@@ -675,7 +680,7 @@ mod tests {
calldata.extend_from_slice(sig.as_ref()); calldata.extend_from_slice(sig.as_ref());
assert_eq!( 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] vec![1]
); );
@@ -685,7 +690,7 @@ mod tests {
calldata.extend_from_slice(other_sig.as_ref()); calldata.extend_from_slice(other_sig.as_ref());
assert_eq!( 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] vec![0]
); );
} }
@@ -695,7 +700,7 @@ mod tests {
let mut ext = TestExternalities::default(); let mut ext = TestExternalities::default();
let test_code = include_bytes!("../wasm/target/wasm32-unknown-unknown/release/runtime_test.compact.wasm"); let test_code = include_bytes!("../wasm/target/wasm32-unknown-unknown/release/runtime_test.compact.wasm");
assert_eq!( 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() ordered_trie_root(vec![b"zero".to_vec(), b"one".to_vec(), b"two".to_vec()]).0.encode()
); );
} }
@@ -69,6 +69,7 @@ decl_module! {
#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] #[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
pub enum Call where aux: T::PublicAux { pub enum Call where aux: T::PublicAux {
fn report_misbehavior(aux, report: MisbehaviorReport<T::Hash, T::BlockNumber>) -> Result = 0; fn report_misbehavior(aux, report: MisbehaviorReport<T::Hash, T::BlockNumber>) -> Result = 0;
fn remark(aux, remark: Vec<u8>) -> Result = 1;
} }
#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] #[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
@@ -104,6 +105,11 @@ impl<T: Trait> Module<T> {
Ok(()) Ok(())
} }
/// Make some on-chain remark.
fn remark(_aux: &T::PublicAux, _remark: Vec<u8>) -> Result {
Ok(())
}
/// Set the current set of authorities' session keys. /// Set the current set of authorities' session keys.
/// ///
/// Called by `next_session` only. /// Called by `next_session` only.
+169 -19
View File
@@ -150,14 +150,57 @@ pub trait CodeExecutor: Sized + Send + Sync {
/// Externalities error type. /// Externalities error type.
type Error: Error; 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<E: Externalities>( fn call<E: Externalities>(
&self, &self,
ext: &mut E, ext: &mut E,
code: &[u8], code: &[u8],
method: &str, method: &str,
data: &[u8], data: &[u8],
) -> Result<Vec<u8>, Self::Error>; use_native: bool
) -> (Result<Vec<u8>, 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<F> {
/// 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<F>> for ExecutionStrategy {
fn from(s: &'a ExecutionManager<F>) -> 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<E>() -> ExecutionManager<fn(Result<Vec<u8>, E>, Result<Vec<u8>, E>)->Result<Vec<u8>, E>> {
ExecutionManager::NativeWhenPossible
}
/// Evaluate to ExecutionManager::NativeWhenPossible, without having to figure out the type.
pub fn always_wasm<E>() -> ExecutionManager<fn(Result<Vec<u8>, E>, Result<Vec<u8>, E>)->Result<Vec<u8>, E>> {
ExecutionManager::AlwaysWasm
} }
/// Execute a call using the given state backend, overlayed changes, and call executor. /// Execute a call using the given state backend, overlayed changes, and call executor.
@@ -174,8 +217,46 @@ pub fn execute<B: backend::Backend, Exec: CodeExecutor>(
exec: &Exec, exec: &Exec,
method: &str, method: &str,
call_data: &[u8], call_data: &[u8],
) -> Result<(Vec<u8>, B::Transaction), Box<Error>> strategy: ExecutionStrategy,
{ ) -> Result<(Vec<u8>, B::Transaction), Box<Error>> {
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<Vec<u8>, Exec::Error>, Result<Vec<u8>, Exec::Error>) -> Result<Vec<u8>, Exec::Error>
>(
backend: &B,
overlay: &mut OverlayedChanges,
exec: &Exec,
method: &str,
call_data: &[u8],
manager: ExecutionManager<Handler>,
) -> Result<(Vec<u8>, B::Transaction), Box<Error>> {
let strategy: ExecutionStrategy = (&manager).into();
let result = { let result = {
let mut externalities = ext::Ext::new(overlay, backend); let mut externalities = ext::Ext::new(overlay, backend);
// make a copy. // make a copy.
@@ -183,12 +264,35 @@ pub fn execute<B: backend::Backend, Exec: CodeExecutor>(
.ok_or(Box::new(ExecutionError::CodeEntryDoesNotExist) as Box<Error>)? .ok_or(Box::new(ExecutionError::CodeEntryDoesNotExist) as Box<Error>)?
.to_vec(); .to_vec();
exec.call( let (result, was_native) = exec.call(
&mut externalities, &mut externalities,
&code, &code,
method, method,
call_data, 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 { match result {
@@ -218,12 +322,11 @@ pub fn prove_execution<B: TryIntoTrieBackend, Exec: CodeExecutor>(
exec: &Exec, exec: &Exec,
method: &str, method: &str,
call_data: &[u8], call_data: &[u8],
) -> Result<(Vec<u8>, Vec<Vec<u8>>, <TrieBackend as Backend>::Transaction), Box<Error>> ) -> Result<(Vec<u8>, Vec<Vec<u8>>, <TrieBackend as Backend>::Transaction), Box<Error>> {
{
let trie_backend = backend.try_into_trie_backend() let trie_backend = backend.try_into_trie_backend()
.ok_or_else(|| Box::new(ExecutionError::UnableToGenerateProof) as Box<Error>)?; .ok_or_else(|| Box::new(ExecutionError::UnableToGenerateProof) as Box<Error>)?;
let proving_backend = proving_backend::ProvingBackend::new(trie_backend); 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(); let proof = proving_backend.extract_proof();
Ok((result, proof, transaction)) Ok((result, proof, transaction))
} }
@@ -236,10 +339,9 @@ pub fn execution_proof_check<Exec: CodeExecutor>(
exec: &Exec, exec: &Exec,
method: &str, method: &str,
call_data: &[u8], call_data: &[u8],
) -> Result<(Vec<u8>, memorydb::MemoryDB), Box<Error>> ) -> Result<(Vec<u8>, memorydb::MemoryDB), Box<Error>> {
{
let backend = proving_backend::create_proof_check_backend(root.into(), proof)?; 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)] #[cfg(test)]
@@ -248,7 +350,11 @@ mod tests {
use super::backend::InMemory; use super::backend::InMemory;
use super::ext::Ext; use super::ext::Ext;
struct DummyCodeExecutor; struct DummyCodeExecutor {
native_available: bool,
native_succeeds: bool,
fallback_succeeds: bool,
}
impl CodeExecutor for DummyCodeExecutor { impl CodeExecutor for DummyCodeExecutor {
type Error = u8; type Error = u8;
@@ -259,8 +365,14 @@ mod tests {
_code: &[u8], _code: &[u8],
_method: &str, _method: &str,
_data: &[u8], _data: &[u8],
) -> Result<Vec<u8>, Self::Error> { use_native: bool
Ok(vec![ext.storage(b"value1").unwrap()[0] + ext.storage(b"value2").unwrap()[0]]) ) -> (Result<Vec<u8>, 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] #[test]
fn execute_works() { fn execute_works() {
assert_eq!(execute(&trie_backend::tests::test_trie(), assert_eq!(execute(
&mut Default::default(), &DummyCodeExecutor, "test", &[]).unwrap().0, vec![66]); &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] #[test]
fn prove_execution_and_proof_check_works() { 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 // fetch execution proof from 'remote' full node
let remote_backend = trie_backend::tests::test_trie(); let remote_backend = trie_backend::tests::test_trie();
let remote_root = remote_backend.storage_root(::std::iter::empty()).0; let remote_root = remote_backend.storage_root(::std::iter::empty()).0;
let (remote_result, remote_proof, _) = prove_execution(remote_backend, let (remote_result, remote_proof, _) = prove_execution(remote_backend,
&mut Default::default(), &DummyCodeExecutor, "test", &[]).unwrap(); &mut Default::default(), &executor, "test", &[]).unwrap();
// check proof locally // check proof locally
let (local_result, _) = execution_proof_check(remote_root, remote_proof, 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 // check that both results are correct
assert_eq!(remote_result, vec![66]); assert_eq!(remote_result, vec![66]);