mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-04-26 06:27:58 +00:00
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:
Generated
+1
@@ -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",
|
||||
|
||||
@@ -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" }
|
||||
|
||||
@@ -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));
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()?;
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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]);
|
||||
|
||||
Reference in New Issue
Block a user