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-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",
+1
View File
@@ -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" }
+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_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(&<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());
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(&<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());
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(&<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());
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(&<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());
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<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]
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));
+5 -1
View File
@@ -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
+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") {
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
+5 -5
View File
@@ -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<Block> + 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<Client<Self::Backend, Self::Executor, Block>>, Option<Arc<OnDemand<Block, NetworkService>>>), error::Error>;
/// Create api.
@@ -83,9 +83,9 @@ impl Components for FullComponents {
type Api = Client<Self::Backend, Self::Executor, Block>;
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> {
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> {
@@ -142,7 +142,7 @@ impl Components for LightComponents {
client::light::blockchain::Blockchain<client_db::light::LightStorage<Block>, 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> {
let db_storage = client_db::light::LightStorage::new(db_settings)?;
let light_blockchain = client::light::new_light_blockchain(db_storage);
+4
View File
@@ -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<String>,
/// 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
+4 -4
View File
@@ -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<Arc<Client<
let executor = polkadot_executor::Executor::new();
let is_validator = (config.roles & Role::AUTHORITY) == Role::AUTHORITY;
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)
}
@@ -126,7 +126,7 @@ impl<Components> Service<Components>
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<Components> Service<Components>
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()?;
+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 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<E, S, Block>(
settings: DatabaseSettings,
executor: E,
genesis_storage: S,
execution_strategy: ExecutionStrategy,
) -> Result<client::Client<Backend<Block>, client::LocalCallExecutor<Backend<Block>, E>, Block>, client::error::Error>
where
Block: BlockT,
@@ -89,7 +90,7 @@ pub fn new_client<E, S, Block>(
{
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 {
@@ -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<B, E, Block> BlockBuilder<B, E, Block> 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<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
/// 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<()> {
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<B, E, Block> BlockBuilder<B, E, Block> where
&mut self.changes,
"finalise_block",
&[],
native_when_possible(),
)?;
self.header = <<Block as BlockT>::Header as Slicable>::decode(&mut &output[..])
.expect("Header came straight out of runtime so must be valid");
@@ -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<B: BlockT> {
/// 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<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
///
@@ -51,12 +56,26 @@ pub trait CallExecutor<B: BlockT> {
/// Execute a call to a contract on top of given state.
///
/// 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.
///
/// 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.
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;
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 (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<B, E, Block> CallExecutor<Block> for LocalCallExecutor<B, E>
.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)> {
state_machine::execute(
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,
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,
changes,
&self.executor,
method,
call_data,
manager,
).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,
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 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<B, E, Block> where Block: BlockT {
import_notification_sinks: Mutex<Vec<mpsc::UnboundedSender<BlockImportNotification<Block>>>>,
import_lock: Mutex<()>,
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.
@@ -144,7 +145,7 @@ impl<Block: BlockT> JustifiedHeader<Block> {
/// Create an instance of in-memory client.
pub fn new_in_mem<E, Block, S>(
executor: E,
genesis_storage: S
genesis_storage: S,
) -> error::Result<Client<in_mem::Backend<Block>, LocalCallExecutor<in_mem::Backend<Block>, E>, Block>>
where
E: CodeExecutor + RuntimeInfo,
@@ -153,7 +154,7 @@ pub fn new_in_mem<E, Block, S>(
{
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<B, E, Block> Client<B, E, Block> where
@@ -167,6 +168,7 @@ impl<B, E, Block> Client<B, E, Block> where
backend: Arc<B>,
executor: E,
build_genesis_storage: S,
execution_strategy: ExecutionStrategy,
) -> error::Result<Self> {
if backend.blockchain().header(BlockId::Number(Zero::zero()))?.is_none() {
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_lock: Mutex::new(()),
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()? {
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",
&<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)
},
None => None,
+13 -7
View File
@@ -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();
}
}
@@ -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<B, F, Block> CallExecutor<Block> for RemoteCallExecutor<B, F>
.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())
}
+2 -2
View File
@@ -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<B, S, F, GS>(
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.
@@ -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<u64, RunWith>;
type CacheType = HashMap<u64, Compatibility>;
lazy_static! {
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
/// 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<CacheType>,
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<D: NativeExecutionDispatch + Sync + Send> {
/// Dummy field to avoid the compiler complaining about us not using `D`.
_dummy: ::std::marker::PhantomData<D>,
/// The fallback executor in case native isn't available.
fallback: WasmExecutor,
}
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 {
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<D: NativeExecutionDispatch + Sync + Send> Clone for NativeExecutor<D> {
fn clone(&self) -> Self {
NativeExecutor {
_dummy: Default::default(),
fallback: self.fallback.clone(),
}
}
}
@@ -146,8 +154,8 @@ impl<D: NativeExecutionDispatch + Sync + Send> RuntimeInfo for NativeExecutor<D>
) -> Option<RuntimeVersion> {
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<D: NativeExecutionDispatch + Sync + Send> CodeExecutor for NativeExecutor<D
code: &[u8],
method: &str,
data: &[u8],
) -> Result<Vec<u8>> {
use_native: bool,
) -> (Result<Vec<u8>>, 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)
}
}
}
}
+5 -5
View File
@@ -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],
);
}
@@ -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<Self> {
const HEAP_SIZE_IN_PAGES: usize = 1024;
fn new(memory: &MemoryRef, pages: usize) -> Result<Self> {
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<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 {
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<Vec<u8>> {
let module = Module::from_buffer(code)?;
self.call_in_wasm_module(ext, &module, method, data)
_use_native: bool
) -> (Result<Vec<u8>>, 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()
);
}
@@ -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<T::Hash, T::BlockNumber>) -> Result = 0;
fn remark(aux, remark: Vec<u8>) -> Result = 1;
}
#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
@@ -104,6 +105,11 @@ impl<T: Trait> Module<T> {
Ok(())
}
/// Make some on-chain remark.
fn remark(_aux: &T::PublicAux, _remark: Vec<u8>) -> Result {
Ok(())
}
/// Set the current set of authorities' session keys.
///
/// Called by `next_session` only.
+169 -19
View File
@@ -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<E: Externalities>(
&self,
ext: &mut E,
code: &[u8],
method: &str,
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.
@@ -174,8 +217,46 @@ pub fn execute<B: backend::Backend, Exec: CodeExecutor>(
exec: &Exec,
method: &str,
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 mut externalities = ext::Ext::new(overlay, backend);
// make a copy.
@@ -183,12 +264,35 @@ pub fn execute<B: backend::Backend, Exec: CodeExecutor>(
.ok_or(Box::new(ExecutionError::CodeEntryDoesNotExist) as Box<Error>)?
.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<B: TryIntoTrieBackend, Exec: CodeExecutor>(
exec: &Exec,
method: &str,
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()
.ok_or_else(|| Box::new(ExecutionError::UnableToGenerateProof) as Box<Error>)?;
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: CodeExecutor>(
exec: &Exec,
method: &str,
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)?;
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<Vec<u8>, Self::Error> {
Ok(vec![ext.storage(b"value1").unwrap()[0] + ext.storage(b"value2").unwrap()[0]])
use_native: bool
) -> (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]
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]);