mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-13 11:41:04 +00:00
Refactor NativeExecutor to support multiple Wasm execution methods (#3677)
* executor: Move definitions of externals out of wasm_executor module. * executor: Create WasmRuntime trait. This will be used to decouple the runtime cache from wasmi execution. * executor: Remove WasmExecutor and move methods to wasmi_execution. These will now be crate-internal functions and there is no need for the struct. * executor: Set default default_heap_pages in NativeExecutor. * cli: CLI configuration for Wasm execution method. * executor: Remove wasmi-specific code from wasm_runtime. * Respond to review comments.
This commit is contained in:
@@ -380,6 +380,7 @@ impl<'a> ParseAndPrepareImport<'a> {
|
|||||||
Exit: IntoExit
|
Exit: IntoExit
|
||||||
{
|
{
|
||||||
let mut config = create_config_with_db_path(spec_factory, &self.params.shared_params, self.version)?;
|
let mut config = create_config_with_db_path(spec_factory, &self.params.shared_params, self.version)?;
|
||||||
|
config.wasm_method = self.params.wasm_method.into();
|
||||||
config.execution_strategies = ExecutionStrategies {
|
config.execution_strategies = ExecutionStrategies {
|
||||||
importing: self.params.execution.into(),
|
importing: self.params.execution.into(),
|
||||||
other: self.params.execution.into(),
|
other: self.params.execution.into(),
|
||||||
@@ -692,6 +693,8 @@ where
|
|||||||
service::Roles::FULL
|
service::Roles::FULL
|
||||||
};
|
};
|
||||||
|
|
||||||
|
config.wasm_method = cli.wasm_method.into();
|
||||||
|
|
||||||
let exec = cli.execution_strategies;
|
let exec = cli.execution_strategies;
|
||||||
let exec_all_or = |strat: params::ExecutionStrategy| exec.execution.unwrap_or(strat).into();
|
let exec_all_or = |strat: params::ExecutionStrategy| exec.execution.unwrap_or(strat).into();
|
||||||
config.execution_strategies = ExecutionStrategies {
|
config.execution_strategies = ExecutionStrategies {
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ use crate::traits::{AugmentClap, GetLogFilter};
|
|||||||
|
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use structopt::{StructOpt, clap::{arg_enum, App, AppSettings, _clap_count_exprs, SubCommand, Arg}};
|
use structopt::{StructOpt, clap::{arg_enum, App, AppSettings, _clap_count_exprs, SubCommand, Arg}};
|
||||||
use client;
|
|
||||||
|
|
||||||
pub use crate::execution_strategy::ExecutionStrategy;
|
pub use crate::execution_strategy::ExecutionStrategy;
|
||||||
|
|
||||||
@@ -44,6 +43,24 @@ impl Into<client::ExecutionStrategy> for ExecutionStrategy {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
arg_enum! {
|
||||||
|
/// How to execute Wasm runtime code
|
||||||
|
#[allow(missing_docs)]
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum WasmExecutionMethod {
|
||||||
|
// Uses an interpreter.
|
||||||
|
Interpreted,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Into<service::config::WasmExecutionMethod> for WasmExecutionMethod {
|
||||||
|
fn into(self) -> service::config::WasmExecutionMethod {
|
||||||
|
match self {
|
||||||
|
WasmExecutionMethod::Interpreted => service::config::WasmExecutionMethod::Interpreted,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
arg_enum! {
|
arg_enum! {
|
||||||
/// Whether off-chain workers are enabled.
|
/// Whether off-chain workers are enabled.
|
||||||
#[allow(missing_docs)]
|
#[allow(missing_docs)]
|
||||||
@@ -404,6 +421,18 @@ pub struct RunCmd {
|
|||||||
)]
|
)]
|
||||||
pub offchain_worker: OffchainWorkerEnabled,
|
pub offchain_worker: OffchainWorkerEnabled,
|
||||||
|
|
||||||
|
/// Method for executing Wasm runtime code.
|
||||||
|
#[structopt(
|
||||||
|
long = "wasm-execution",
|
||||||
|
value_name = "METHOD",
|
||||||
|
raw(
|
||||||
|
possible_values = "&WasmExecutionMethod::variants()",
|
||||||
|
case_insensitive = "true",
|
||||||
|
default_value = r#""Interpreted""#
|
||||||
|
)
|
||||||
|
)]
|
||||||
|
pub wasm_method: WasmExecutionMethod,
|
||||||
|
|
||||||
#[allow(missing_docs)]
|
#[allow(missing_docs)]
|
||||||
#[structopt(flatten)]
|
#[structopt(flatten)]
|
||||||
pub execution_strategies: ExecutionStrategies,
|
pub execution_strategies: ExecutionStrategies,
|
||||||
@@ -651,6 +680,18 @@ pub struct ImportBlocksCmd {
|
|||||||
#[structopt(flatten)]
|
#[structopt(flatten)]
|
||||||
pub shared_params: SharedParams,
|
pub shared_params: SharedParams,
|
||||||
|
|
||||||
|
/// Method for executing Wasm runtime code.
|
||||||
|
#[structopt(
|
||||||
|
long = "wasm-execution",
|
||||||
|
value_name = "METHOD",
|
||||||
|
raw(
|
||||||
|
possible_values = "&WasmExecutionMethod::variants()",
|
||||||
|
case_insensitive = "true",
|
||||||
|
default_value = r#""Interpreted""#
|
||||||
|
)
|
||||||
|
)]
|
||||||
|
pub wasm_method: WasmExecutionMethod,
|
||||||
|
|
||||||
/// The means of execution used when calling into the runtime while importing blocks.
|
/// The means of execution used when calling into the runtime while importing blocks.
|
||||||
#[structopt(
|
#[structopt(
|
||||||
long = "execution",
|
long = "execution",
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ mod tests {
|
|||||||
);
|
);
|
||||||
|
|
||||||
fn executor() -> executor::NativeExecutor<Executor> {
|
fn executor() -> executor::NativeExecutor<Executor> {
|
||||||
executor::NativeExecutor::new(None)
|
executor::NativeExecutor::new(executor::WasmExecutionMethod::Interpreted, None)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn construct_block(
|
fn construct_block(
|
||||||
|
|||||||
@@ -49,7 +49,7 @@
|
|||||||
//! use substrate_client::{Client, in_mem::Backend, LocalCallExecutor};
|
//! use substrate_client::{Client, in_mem::Backend, LocalCallExecutor};
|
||||||
//! use primitives::Blake2Hasher;
|
//! use primitives::Blake2Hasher;
|
||||||
//! use sr_primitives::{StorageOverlay, ChildrenStorageOverlay};
|
//! use sr_primitives::{StorageOverlay, ChildrenStorageOverlay};
|
||||||
//! use executor::NativeExecutor;
|
//! use executor::{NativeExecutor, WasmExecutionMethod};
|
||||||
//!
|
//!
|
||||||
//! // In this example, we're using the `Block` and `RuntimeApi` types from the
|
//! // In this example, we're using the `Block` and `RuntimeApi` types from the
|
||||||
//! // `substrate-test-runtime-client` crate. These types are automatically generated when
|
//! // `substrate-test-runtime-client` crate. These types are automatically generated when
|
||||||
@@ -62,7 +62,7 @@
|
|||||||
//! backend.clone(),
|
//! backend.clone(),
|
||||||
//! LocalCallExecutor::new(
|
//! LocalCallExecutor::new(
|
||||||
//! backend.clone(),
|
//! backend.clone(),
|
||||||
//! NativeExecutor::<LocalExecutor>::new(None),
|
//! NativeExecutor::<LocalExecutor>::new(WasmExecutionMethod::Interpreted, None),
|
||||||
//! None,
|
//! None,
|
||||||
//! ),
|
//! ),
|
||||||
//! // This parameter provides the storage for the chain genesis.
|
//! // This parameter provides the storage for the chain genesis.
|
||||||
|
|||||||
@@ -304,7 +304,7 @@ mod tests {
|
|||||||
use consensus::BlockOrigin;
|
use consensus::BlockOrigin;
|
||||||
use primitives::offchain::NeverOffchainExt;
|
use primitives::offchain::NeverOffchainExt;
|
||||||
use test_client::{self, runtime::{Header, Digest, Block}, ClientExt, TestClient};
|
use test_client::{self, runtime::{Header, Digest, Block}, ClientExt, TestClient};
|
||||||
use executor::NativeExecutor;
|
use executor::{NativeExecutor, WasmExecutionMethod};
|
||||||
use crate::backend::{Backend, NewBlockState};
|
use crate::backend::{Backend, NewBlockState};
|
||||||
use crate::in_mem::Backend as InMemBackend;
|
use crate::in_mem::Backend as InMemBackend;
|
||||||
use super::*;
|
use super::*;
|
||||||
@@ -399,6 +399,10 @@ mod tests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn local_executor() -> NativeExecutor<test_client::LocalExecutor> {
|
||||||
|
NativeExecutor::new(WasmExecutionMethod::Interpreted, None)
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn execution_proof_is_generated_and_checked() {
|
fn execution_proof_is_generated_and_checked() {
|
||||||
fn execute(remote_client: &TestClient, at: u64, method: &'static str) -> (Vec<u8>, Vec<u8>) {
|
fn execute(remote_client: &TestClient, at: u64, method: &'static str) -> (Vec<u8>, Vec<u8>) {
|
||||||
@@ -413,8 +417,7 @@ mod tests {
|
|||||||
).unwrap();
|
).unwrap();
|
||||||
|
|
||||||
// check remote execution proof locally
|
// check remote execution proof locally
|
||||||
let local_executor = NativeExecutor::<test_client::LocalExecutor>::new(None);
|
let local_result = check_execution_proof(&local_executor(), &RemoteCallRequest {
|
||||||
let local_result = check_execution_proof(&local_executor, &RemoteCallRequest {
|
|
||||||
block: test_client::runtime::Hash::default(),
|
block: test_client::runtime::Hash::default(),
|
||||||
header: remote_header,
|
header: remote_header,
|
||||||
method: method.into(),
|
method: method.into(),
|
||||||
@@ -437,9 +440,8 @@ mod tests {
|
|||||||
).unwrap();
|
).unwrap();
|
||||||
|
|
||||||
// check remote execution proof locally
|
// check remote execution proof locally
|
||||||
let local_executor = NativeExecutor::<test_client::LocalExecutor>::new(None);
|
|
||||||
let execution_result = check_execution_proof_with_make_header(
|
let execution_result = check_execution_proof_with_make_header(
|
||||||
&local_executor,
|
&local_executor(),
|
||||||
&RemoteCallRequest {
|
&RemoteCallRequest {
|
||||||
block: test_client::runtime::Hash::default(),
|
block: test_client::runtime::Hash::default(),
|
||||||
header: remote_header,
|
header: remote_header,
|
||||||
|
|||||||
@@ -503,7 +503,7 @@ pub mod tests {
|
|||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
use codec::Decode;
|
use codec::Decode;
|
||||||
use crate::client::tests::prepare_client_with_key_changes;
|
use crate::client::tests::prepare_client_with_key_changes;
|
||||||
use executor::{self, NativeExecutor};
|
use executor::{NativeExecutor, WasmExecutionMethod};
|
||||||
use crate::error::Error as ClientError;
|
use crate::error::Error as ClientError;
|
||||||
use test_client::{
|
use test_client::{
|
||||||
self, ClientExt, blockchain::HeaderBackend, AccountKeyring,
|
self, ClientExt, blockchain::HeaderBackend, AccountKeyring,
|
||||||
@@ -563,12 +563,16 @@ pub mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type TestChecker = LightDataChecker<
|
type TestChecker = LightDataChecker<
|
||||||
executor::NativeExecutor<test_client::LocalExecutor>,
|
NativeExecutor<test_client::LocalExecutor>,
|
||||||
Blake2Hasher,
|
Blake2Hasher,
|
||||||
Block,
|
Block,
|
||||||
DummyStorage,
|
DummyStorage,
|
||||||
>;
|
>;
|
||||||
|
|
||||||
|
fn local_executor() -> NativeExecutor<test_client::LocalExecutor> {
|
||||||
|
NativeExecutor::new(WasmExecutionMethod::Interpreted, None)
|
||||||
|
}
|
||||||
|
|
||||||
fn prepare_for_read_proof_check() -> (TestChecker, Header, Vec<Vec<u8>>, u32) {
|
fn prepare_for_read_proof_check() -> (TestChecker, Header, Vec<Vec<u8>>, u32) {
|
||||||
// prepare remote client
|
// prepare remote client
|
||||||
let remote_client = test_client::new();
|
let remote_client = test_client::new();
|
||||||
@@ -596,8 +600,10 @@ pub mod tests {
|
|||||||
None,
|
None,
|
||||||
crate::backend::NewBlockState::Final,
|
crate::backend::NewBlockState::Final,
|
||||||
).unwrap();
|
).unwrap();
|
||||||
let local_executor = NativeExecutor::<test_client::LocalExecutor>::new(None);
|
let local_checker = LightDataChecker::new(
|
||||||
let local_checker = LightDataChecker::new(Arc::new(DummyBlockchain::new(DummyStorage::new())), local_executor);
|
Arc::new(DummyBlockchain::new(DummyStorage::new())),
|
||||||
|
local_executor()
|
||||||
|
);
|
||||||
(local_checker, remote_block_header, remote_read_proof, heap_pages)
|
(local_checker, remote_block_header, remote_read_proof, heap_pages)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -636,8 +642,10 @@ pub mod tests {
|
|||||||
None,
|
None,
|
||||||
crate::backend::NewBlockState::Final,
|
crate::backend::NewBlockState::Final,
|
||||||
).unwrap();
|
).unwrap();
|
||||||
let local_executor = NativeExecutor::<test_client::LocalExecutor>::new(None);
|
let local_checker = LightDataChecker::new(
|
||||||
let local_checker = LightDataChecker::new(Arc::new(DummyBlockchain::new(DummyStorage::new())), local_executor);
|
Arc::new(DummyBlockchain::new(DummyStorage::new())),
|
||||||
|
local_executor(),
|
||||||
|
);
|
||||||
(local_checker, remote_block_header, remote_read_proof, child_value)
|
(local_checker, remote_block_header, remote_read_proof, child_value)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -662,8 +670,10 @@ pub mod tests {
|
|||||||
if insert_cht {
|
if insert_cht {
|
||||||
local_storage.insert_cht_root(1, local_cht_root);
|
local_storage.insert_cht_root(1, local_cht_root);
|
||||||
}
|
}
|
||||||
let local_executor = NativeExecutor::<test_client::LocalExecutor>::new(None);
|
let local_checker = LightDataChecker::new(
|
||||||
let local_checker = LightDataChecker::new(Arc::new(DummyBlockchain::new(DummyStorage::new())), local_executor);
|
Arc::new(DummyBlockchain::new(DummyStorage::new())),
|
||||||
|
local_executor(),
|
||||||
|
);
|
||||||
(local_checker, local_cht_root, remote_block_header, remote_header_proof)
|
(local_checker, local_cht_root, remote_block_header, remote_header_proof)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -744,7 +754,7 @@ pub mod tests {
|
|||||||
let (remote_client, local_roots, test_cases) = prepare_client_with_key_changes();
|
let (remote_client, local_roots, test_cases) = prepare_client_with_key_changes();
|
||||||
let local_checker = TestChecker::new(
|
let local_checker = TestChecker::new(
|
||||||
Arc::new(DummyBlockchain::new(DummyStorage::new())),
|
Arc::new(DummyBlockchain::new(DummyStorage::new())),
|
||||||
NativeExecutor::<test_client::LocalExecutor>::new(None)
|
local_executor(),
|
||||||
);
|
);
|
||||||
let local_checker = &local_checker as &dyn FetchChecker<Block>;
|
let local_checker = &local_checker as &dyn FetchChecker<Block>;
|
||||||
let max = remote_client.info().chain.best_number;
|
let max = remote_client.info().chain.best_number;
|
||||||
@@ -813,7 +823,7 @@ pub mod tests {
|
|||||||
local_storage.changes_tries_cht_roots.insert(0, local_cht_root);
|
local_storage.changes_tries_cht_roots.insert(0, local_cht_root);
|
||||||
let local_checker = TestChecker::new(
|
let local_checker = TestChecker::new(
|
||||||
Arc::new(DummyBlockchain::new(local_storage)),
|
Arc::new(DummyBlockchain::new(local_storage)),
|
||||||
NativeExecutor::<test_client::LocalExecutor>::new(None)
|
local_executor(),
|
||||||
);
|
);
|
||||||
|
|
||||||
// check proof on local client
|
// check proof on local client
|
||||||
@@ -842,7 +852,7 @@ pub mod tests {
|
|||||||
let (remote_client, local_roots, test_cases) = prepare_client_with_key_changes();
|
let (remote_client, local_roots, test_cases) = prepare_client_with_key_changes();
|
||||||
let local_checker = TestChecker::new(
|
let local_checker = TestChecker::new(
|
||||||
Arc::new(DummyBlockchain::new(DummyStorage::new())),
|
Arc::new(DummyBlockchain::new(DummyStorage::new())),
|
||||||
NativeExecutor::<test_client::LocalExecutor>::new(None)
|
local_executor(),
|
||||||
);
|
);
|
||||||
let local_checker = &local_checker as &dyn FetchChecker<Block>;
|
let local_checker = &local_checker as &dyn FetchChecker<Block>;
|
||||||
let max = remote_client.info().chain.best_number;
|
let max = remote_client.info().chain.best_number;
|
||||||
@@ -924,7 +934,7 @@ pub mod tests {
|
|||||||
// fails when changes trie CHT is missing from the local db
|
// fails when changes trie CHT is missing from the local db
|
||||||
let local_checker = TestChecker::new(
|
let local_checker = TestChecker::new(
|
||||||
Arc::new(DummyBlockchain::new(DummyStorage::new())),
|
Arc::new(DummyBlockchain::new(DummyStorage::new())),
|
||||||
NativeExecutor::<test_client::LocalExecutor>::new(None)
|
local_executor(),
|
||||||
);
|
);
|
||||||
assert!(local_checker.check_changes_tries_proof(4, &remote_proof.roots,
|
assert!(local_checker.check_changes_tries_proof(4, &remote_proof.roots,
|
||||||
remote_proof.roots_proof.clone()).is_err());
|
remote_proof.roots_proof.clone()).is_err());
|
||||||
@@ -934,7 +944,7 @@ pub mod tests {
|
|||||||
local_storage.changes_tries_cht_roots.insert(0, local_cht_root);
|
local_storage.changes_tries_cht_roots.insert(0, local_cht_root);
|
||||||
let local_checker = TestChecker::new(
|
let local_checker = TestChecker::new(
|
||||||
Arc::new(DummyBlockchain::new(local_storage)),
|
Arc::new(DummyBlockchain::new(local_storage)),
|
||||||
NativeExecutor::<test_client::LocalExecutor>::new(None)
|
local_executor(),
|
||||||
);
|
);
|
||||||
assert!(local_checker.check_changes_tries_proof(4, &remote_proof.roots, vec![]).is_err());
|
assert!(local_checker.check_changes_tries_proof(4, &remote_proof.roots, vec![]).is_err());
|
||||||
}
|
}
|
||||||
@@ -948,7 +958,7 @@ pub mod tests {
|
|||||||
|
|
||||||
let local_checker = TestChecker::new(
|
let local_checker = TestChecker::new(
|
||||||
Arc::new(DummyBlockchain::new(DummyStorage::new())),
|
Arc::new(DummyBlockchain::new(DummyStorage::new())),
|
||||||
NativeExecutor::<test_client::LocalExecutor>::new(None)
|
local_executor(),
|
||||||
);
|
);
|
||||||
|
|
||||||
let body_request = RemoteBodyRequest {
|
let body_request = RemoteBodyRequest {
|
||||||
@@ -971,7 +981,7 @@ pub mod tests {
|
|||||||
|
|
||||||
let local_checker = TestChecker::new(
|
let local_checker = TestChecker::new(
|
||||||
Arc::new(DummyBlockchain::new(DummyStorage::new())),
|
Arc::new(DummyBlockchain::new(DummyStorage::new())),
|
||||||
NativeExecutor::<test_client::LocalExecutor>::new(None)
|
local_executor(),
|
||||||
);
|
);
|
||||||
|
|
||||||
let body_request = RemoteBodyRequest {
|
let body_request = RemoteBodyRequest {
|
||||||
|
|||||||
@@ -98,3 +98,18 @@ impl From<String> for Error {
|
|||||||
Error::Other(err)
|
Error::Other(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Type for errors occurring during Wasm runtime construction.
|
||||||
|
#[derive(Debug, derive_more::Display)]
|
||||||
|
pub enum WasmError {
|
||||||
|
/// Code could not be read from the state.
|
||||||
|
CodeNotFound,
|
||||||
|
/// Failure to reinitialize runtime instance from snapshot.
|
||||||
|
ApplySnapshotFailed,
|
||||||
|
/// Wasm code failed validation.
|
||||||
|
InvalidModule,
|
||||||
|
/// Wasm code could not be deserialized.
|
||||||
|
CantDeserializeWasm,
|
||||||
|
/// Instantiation error.
|
||||||
|
Instantiation(Error),
|
||||||
|
}
|
||||||
|
|||||||
+38
-765
@@ -14,33 +14,20 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
|
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
//! Wasm interface module.
|
//! Definition and implementation of the Substrate Wasm host interface.
|
||||||
//!
|
//!
|
||||||
//! This module defines and implements the wasm part of Substrate Host Interface and provides
|
//! These are the host functions callable from within the Substrate runtime.
|
||||||
//! an interface for calling into the wasm runtime.
|
|
||||||
|
|
||||||
|
use crate::error::{Error, Result};
|
||||||
|
|
||||||
|
use codec::Encode;
|
||||||
use std::{convert::TryFrom, str, panic};
|
use std::{convert::TryFrom, str, panic};
|
||||||
use tiny_keccak;
|
|
||||||
use secp256k1;
|
|
||||||
|
|
||||||
use wasmi::{
|
|
||||||
Module, ModuleInstance, MemoryInstance, MemoryRef, TableRef, ImportsBuilder, ModuleRef,
|
|
||||||
memory_units::Pages, RuntimeValue::{I32, I64, self},
|
|
||||||
};
|
|
||||||
use super::{sandbox, allocator, error::{Error, Result}};
|
|
||||||
use codec::{Encode, Decode};
|
|
||||||
use primitives::{
|
use primitives::{
|
||||||
blake2_128, blake2_256, twox_64, twox_128, twox_256, ed25519, sr25519, Pair, crypto::KeyTypeId,
|
blake2_128, blake2_256, twox_64, twox_128, twox_256, ed25519, sr25519, Blake2Hasher, Pair,
|
||||||
offchain, sandbox as sandbox_primitives, Blake2Hasher,
|
crypto::KeyTypeId, offchain,
|
||||||
traits::Externalities,
|
|
||||||
};
|
|
||||||
use trie::TrieConfiguration;
|
|
||||||
use trie::trie_types::Layout;
|
|
||||||
use log::trace;
|
|
||||||
use wasm_interface::{
|
|
||||||
FunctionContext, HostFunctions, Pointer, WordSize, Sandbox, MemoryId, PointerType,
|
|
||||||
Result as WResult,
|
|
||||||
};
|
};
|
||||||
|
use trie::{TrieConfiguration, trie_types::Layout};
|
||||||
|
use wasm_interface::{FunctionContext, Pointer, PointerType, Result as WResult, WordSize};
|
||||||
|
|
||||||
#[cfg(feature="wasm-extern-trace")]
|
#[cfg(feature="wasm-extern-trace")]
|
||||||
macro_rules! debug_trace {
|
macro_rules! debug_trace {
|
||||||
@@ -52,317 +39,7 @@ macro_rules! debug_trace {
|
|||||||
( $( $x:tt )* ) => ()
|
( $( $x:tt )* ) => ()
|
||||||
}
|
}
|
||||||
|
|
||||||
struct FunctionExecutor {
|
pub struct SubstrateExternals;
|
||||||
sandbox_store: sandbox::Store<wasmi::FuncRef>,
|
|
||||||
heap: allocator::FreeingBumpHeapAllocator,
|
|
||||||
memory: MemoryRef,
|
|
||||||
table: Option<TableRef>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FunctionExecutor {
|
|
||||||
fn new(m: MemoryRef, heap_base: u32, t: Option<TableRef>) -> Result<Self> {
|
|
||||||
Ok(FunctionExecutor {
|
|
||||||
sandbox_store: sandbox::Store::new(),
|
|
||||||
heap: allocator::FreeingBumpHeapAllocator::new(heap_base),
|
|
||||||
memory: m,
|
|
||||||
table: t,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl sandbox::SandboxCapabilities for FunctionExecutor {
|
|
||||||
type SupervisorFuncRef = wasmi::FuncRef;
|
|
||||||
|
|
||||||
fn store(&self) -> &sandbox::Store<Self::SupervisorFuncRef> {
|
|
||||||
&self.sandbox_store
|
|
||||||
}
|
|
||||||
fn store_mut(&mut self) -> &mut sandbox::Store<Self::SupervisorFuncRef> {
|
|
||||||
&mut self.sandbox_store
|
|
||||||
}
|
|
||||||
fn allocate(&mut self, len: WordSize) -> Result<Pointer<u8>> {
|
|
||||||
let heap = &mut self.heap;
|
|
||||||
self.memory.with_direct_access_mut(|mem| {
|
|
||||||
heap.allocate(mem, len)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
fn deallocate(&mut self, ptr: Pointer<u8>) -> Result<()> {
|
|
||||||
let heap = &mut self.heap;
|
|
||||||
self.memory.with_direct_access_mut(|mem| {
|
|
||||||
heap.deallocate(mem, ptr)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
fn write_memory(&mut self, ptr: Pointer<u8>, data: &[u8]) -> Result<()> {
|
|
||||||
self.memory.set(ptr.into(), data).map_err(Into::into)
|
|
||||||
}
|
|
||||||
fn read_memory(&self, ptr: Pointer<u8>, len: WordSize) -> Result<Vec<u8>> {
|
|
||||||
self.memory.get(ptr.into(), len as usize).map_err(Into::into)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn invoke(
|
|
||||||
&mut self,
|
|
||||||
dispatch_thunk: &Self::SupervisorFuncRef,
|
|
||||||
invoke_args_ptr: Pointer<u8>,
|
|
||||||
invoke_args_len: WordSize,
|
|
||||||
state: u32,
|
|
||||||
func_idx: sandbox::SupervisorFuncIndex,
|
|
||||||
) -> Result<i64>
|
|
||||||
{
|
|
||||||
let result = wasmi::FuncInstance::invoke(
|
|
||||||
dispatch_thunk,
|
|
||||||
&[
|
|
||||||
RuntimeValue::I32(u32::from(invoke_args_ptr) as i32),
|
|
||||||
RuntimeValue::I32(invoke_args_len as i32),
|
|
||||||
RuntimeValue::I32(state as i32),
|
|
||||||
RuntimeValue::I32(usize::from(func_idx) as i32),
|
|
||||||
],
|
|
||||||
self,
|
|
||||||
);
|
|
||||||
match result {
|
|
||||||
Ok(Some(RuntimeValue::I64(val))) => Ok(val),
|
|
||||||
Ok(_) => return Err("Supervisor function returned unexpected result!".into()),
|
|
||||||
Err(err) => Err(Error::Trap(err)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FunctionContext for FunctionExecutor {
|
|
||||||
fn read_memory_into(&self, address: Pointer<u8>, dest: &mut [u8]) -> WResult<()> {
|
|
||||||
self.memory.get_into(address.into(), dest).map_err(|e| format!("{:?}", e))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn write_memory(&mut self, address: Pointer<u8>, data: &[u8]) -> WResult<()> {
|
|
||||||
self.memory.set(address.into(), data).map_err(|e| format!("{:?}", e))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn allocate_memory(&mut self, size: WordSize) -> WResult<Pointer<u8>> {
|
|
||||||
let heap = &mut self.heap;
|
|
||||||
self.memory.with_direct_access_mut(|mem| {
|
|
||||||
heap.allocate(mem, size).map_err(|e| format!("{:?}", e))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn deallocate_memory(&mut self, ptr: Pointer<u8>) -> WResult<()> {
|
|
||||||
let heap = &mut self.heap;
|
|
||||||
self.memory.with_direct_access_mut(|mem| {
|
|
||||||
heap.deallocate(mem, ptr).map_err(|e| format!("{:?}", e))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn sandbox(&mut self) -> &mut dyn Sandbox {
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Sandbox for FunctionExecutor {
|
|
||||||
fn memory_get(
|
|
||||||
&self,
|
|
||||||
memory_id: MemoryId,
|
|
||||||
offset: WordSize,
|
|
||||||
buf_ptr: Pointer<u8>,
|
|
||||||
buf_len: WordSize,
|
|
||||||
) -> WResult<u32> {
|
|
||||||
let sandboxed_memory = self.sandbox_store.memory(memory_id).map_err(|e| format!("{:?}", e))?;
|
|
||||||
|
|
||||||
match MemoryInstance::transfer(
|
|
||||||
&sandboxed_memory,
|
|
||||||
offset as usize,
|
|
||||||
&self.memory,
|
|
||||||
buf_ptr.into(),
|
|
||||||
buf_len as usize,
|
|
||||||
) {
|
|
||||||
Ok(()) => Ok(sandbox_primitives::ERR_OK),
|
|
||||||
Err(_) => Ok(sandbox_primitives::ERR_OUT_OF_BOUNDS),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn memory_set(
|
|
||||||
&mut self,
|
|
||||||
memory_id: MemoryId,
|
|
||||||
offset: WordSize,
|
|
||||||
val_ptr: Pointer<u8>,
|
|
||||||
val_len: WordSize,
|
|
||||||
) -> WResult<u32> {
|
|
||||||
let sandboxed_memory = self.sandbox_store.memory(memory_id).map_err(|e| format!("{:?}", e))?;
|
|
||||||
|
|
||||||
match MemoryInstance::transfer(
|
|
||||||
&self.memory,
|
|
||||||
val_ptr.into(),
|
|
||||||
&sandboxed_memory,
|
|
||||||
offset as usize,
|
|
||||||
val_len as usize,
|
|
||||||
) {
|
|
||||||
Ok(()) => Ok(sandbox_primitives::ERR_OK),
|
|
||||||
Err(_) => Ok(sandbox_primitives::ERR_OUT_OF_BOUNDS),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn memory_teardown(&mut self, memory_id: MemoryId) -> WResult<()> {
|
|
||||||
self.sandbox_store.memory_teardown(memory_id).map_err(|e| format!("{:?}", e))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn memory_new(
|
|
||||||
&mut self,
|
|
||||||
initial: u32,
|
|
||||||
maximum: u32,
|
|
||||||
) -> WResult<MemoryId> {
|
|
||||||
self.sandbox_store.new_memory(initial, maximum).map_err(|e| format!("{:?}", e))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn invoke(
|
|
||||||
&mut self,
|
|
||||||
instance_id: u32,
|
|
||||||
export_name: &str,
|
|
||||||
args: &[u8],
|
|
||||||
return_val: Pointer<u8>,
|
|
||||||
return_val_len: WordSize,
|
|
||||||
state: u32,
|
|
||||||
) -> WResult<u32> {
|
|
||||||
trace!(target: "sr-sandbox", "invoke, instance_idx={}", instance_id);
|
|
||||||
|
|
||||||
// Deserialize arguments and convert them into wasmi types.
|
|
||||||
let args = Vec::<sandbox_primitives::TypedValue>::decode(&mut &args[..])
|
|
||||||
.map_err(|_| "Can't decode serialized arguments for the invocation")?
|
|
||||||
.into_iter()
|
|
||||||
.map(Into::into)
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
|
|
||||||
let instance = self.sandbox_store.instance(instance_id).map_err(|e| format!("{:?}", e))?;
|
|
||||||
let result = instance.invoke(export_name, &args, self, state);
|
|
||||||
|
|
||||||
match result {
|
|
||||||
Ok(None) => Ok(sandbox_primitives::ERR_OK),
|
|
||||||
Ok(Some(val)) => {
|
|
||||||
// Serialize return value and write it back into the memory.
|
|
||||||
sandbox_primitives::ReturnValue::Value(val.into()).using_encoded(|val| {
|
|
||||||
if val.len() > return_val_len as usize {
|
|
||||||
Err("Return value buffer is too small")?;
|
|
||||||
}
|
|
||||||
self.write_memory(return_val, val).map_err(|_| "Return value buffer is OOB")?;
|
|
||||||
Ok(sandbox_primitives::ERR_OK)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
Err(_) => Ok(sandbox_primitives::ERR_EXECUTION),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn instance_teardown(&mut self, instance_id: u32) -> WResult<()> {
|
|
||||||
self.sandbox_store.instance_teardown(instance_id).map_err(|e| format!("{:?}", e))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn instance_new(
|
|
||||||
&mut self,
|
|
||||||
dispatch_thunk_id: u32,
|
|
||||||
wasm: &[u8],
|
|
||||||
raw_env_def: &[u8],
|
|
||||||
state: u32,
|
|
||||||
) -> WResult<u32> {
|
|
||||||
// Extract a dispatch thunk from instance's table by the specified index.
|
|
||||||
let dispatch_thunk = {
|
|
||||||
let table = self.table.as_ref()
|
|
||||||
.ok_or_else(|| "Runtime doesn't have a table; sandbox is unavailable")?;
|
|
||||||
table.get(dispatch_thunk_id)
|
|
||||||
.map_err(|_| "dispatch_thunk_idx is out of the table bounds")?
|
|
||||||
.ok_or_else(|| "dispatch_thunk_idx points on an empty table entry")?
|
|
||||||
.clone()
|
|
||||||
};
|
|
||||||
|
|
||||||
let instance_idx_or_err_code =
|
|
||||||
match sandbox::instantiate(self, dispatch_thunk, wasm, raw_env_def, state) {
|
|
||||||
Ok(instance_idx) => instance_idx,
|
|
||||||
Err(sandbox::InstantiationError::StartTrapped) =>
|
|
||||||
sandbox_primitives::ERR_EXECUTION,
|
|
||||||
Err(_) => sandbox_primitives::ERR_MODULE,
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(instance_idx_or_err_code as u32)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
trait WritePrimitive<T: PointerType> {
|
|
||||||
fn write_primitive(&mut self, ptr: Pointer<T>, t: T) -> WResult<()>;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl WritePrimitive<u32> for &mut dyn FunctionContext {
|
|
||||||
fn write_primitive(&mut self, ptr: Pointer<u32>, t: u32) -> WResult<()> {
|
|
||||||
let r = t.to_le_bytes();
|
|
||||||
self.write_memory(ptr.cast(), &r)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
trait ReadPrimitive<T: PointerType> {
|
|
||||||
fn read_primitive(&self, offset: Pointer<T>) -> WResult<T>;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ReadPrimitive<u32> for &mut dyn FunctionContext {
|
|
||||||
fn read_primitive(&self, ptr: Pointer<u32>) -> WResult<u32> {
|
|
||||||
let mut r = [0u8; 4];
|
|
||||||
self.read_memory_into(ptr.cast(), &mut r)?;
|
|
||||||
Ok(u32::from_le_bytes(r))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn deadline_to_timestamp(deadline: u64) -> Option<offchain::Timestamp> {
|
|
||||||
if deadline == 0 {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
Some(offchain::Timestamp::from_unix_millis(deadline))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FunctionExecutor {
|
|
||||||
fn resolver() -> &'static dyn wasmi::ModuleImportResolver {
|
|
||||||
struct Resolver;
|
|
||||||
impl wasmi::ModuleImportResolver for Resolver {
|
|
||||||
fn resolve_func(&self, name: &str, signature: &wasmi::Signature)
|
|
||||||
-> std::result::Result<wasmi::FuncRef, wasmi::Error>
|
|
||||||
{
|
|
||||||
let signature = wasm_interface::Signature::from(signature);
|
|
||||||
|
|
||||||
if let Some((index, func)) = SubstrateExternals::functions().iter()
|
|
||||||
.enumerate()
|
|
||||||
.find(|f| name == f.1.name())
|
|
||||||
{
|
|
||||||
if signature == func.signature() {
|
|
||||||
Ok(wasmi::FuncInstance::alloc_host(signature.into(), index))
|
|
||||||
} else {
|
|
||||||
Err(wasmi::Error::Instantiation(
|
|
||||||
format!(
|
|
||||||
"Invalid signature for function `{}` expected `{:?}`, got `{:?}`",
|
|
||||||
func.name(),
|
|
||||||
signature,
|
|
||||||
func.signature(),
|
|
||||||
)
|
|
||||||
))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Err(wasmi::Error::Instantiation(
|
|
||||||
format!("Export {} not found", name),
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
&Resolver
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl wasmi::Externals for FunctionExecutor {
|
|
||||||
fn invoke_index(&mut self, index: usize, args: wasmi::RuntimeArgs)
|
|
||||||
-> std::result::Result<Option<wasmi::RuntimeValue>, wasmi::Trap>
|
|
||||||
{
|
|
||||||
let mut args = args.as_ref().iter().copied().map(Into::into);
|
|
||||||
let function = SubstrateExternals::functions().get(index).ok_or_else(||
|
|
||||||
Error::from(
|
|
||||||
format!("Could not find host function with index: {}", index),
|
|
||||||
)
|
|
||||||
)?;
|
|
||||||
|
|
||||||
function.execute(self, &mut args)
|
|
||||||
.map_err(Error::FunctionExecution)
|
|
||||||
.map_err(wasmi::Trap::from)
|
|
||||||
.map(|v| v.map(Into::into))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
struct SubstrateExternals;
|
|
||||||
|
|
||||||
impl_wasm_host_interface! {
|
impl_wasm_host_interface! {
|
||||||
impl SubstrateExternals where context {
|
impl SubstrateExternals where context {
|
||||||
@@ -1400,6 +1077,29 @@ impl_wasm_host_interface! {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
trait WritePrimitive<T: PointerType> {
|
||||||
|
fn write_primitive(&mut self, ptr: Pointer<T>, t: T) -> WResult<()>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WritePrimitive<u32> for &mut dyn FunctionContext {
|
||||||
|
fn write_primitive(&mut self, ptr: Pointer<u32>, t: u32) -> WResult<()> {
|
||||||
|
let r = t.to_le_bytes();
|
||||||
|
self.write_memory(ptr.cast(), &r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
trait ReadPrimitive<T: PointerType> {
|
||||||
|
fn read_primitive(&self, offset: Pointer<T>) -> WResult<T>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ReadPrimitive<u32> for &mut dyn FunctionContext {
|
||||||
|
fn read_primitive(&self, ptr: Pointer<u32>) -> WResult<u32> {
|
||||||
|
let mut r = [0u8; 4];
|
||||||
|
self.read_memory_into(ptr.cast(), &mut r)?;
|
||||||
|
Ok(u32::from_le_bytes(r))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Execute closure that access external storage.
|
/// Execute closure that access external storage.
|
||||||
///
|
///
|
||||||
/// All panics that happen within closure are captured and transformed into
|
/// All panics that happen within closure are captured and transformed into
|
||||||
@@ -1419,438 +1119,11 @@ fn with_external_storage<T, F>(f: F) -> std::result::Result<T, String>
|
|||||||
.map_err(|err| format!("{}", err))
|
.map_err(|err| format!("{}", err))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Wasm rust executor for contracts.
|
fn deadline_to_timestamp(deadline: u64) -> Option<offchain::Timestamp> {
|
||||||
///
|
if deadline == 0 {
|
||||||
/// Executes the provided code in a sandboxed wasm runtime.
|
None
|
||||||
#[derive(Debug, Clone)]
|
} else {
|
||||||
pub struct WasmExecutor;
|
Some(offchain::Timestamp::from_unix_millis(deadline))
|
||||||
|
|
||||||
impl WasmExecutor {
|
|
||||||
/// Create a new instance.
|
|
||||||
pub fn new() -> Self {
|
|
||||||
WasmExecutor
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Call a given method in the given code.
|
|
||||||
///
|
|
||||||
/// Signature of this method needs to be `(I32, I32) -> I64`.
|
|
||||||
///
|
|
||||||
/// This should be used for tests only.
|
|
||||||
pub fn call<E: Externalities<Blake2Hasher>>(
|
|
||||||
&self,
|
|
||||||
ext: &mut E,
|
|
||||||
heap_pages: usize,
|
|
||||||
code: &[u8],
|
|
||||||
method: &str,
|
|
||||||
data: &[u8],
|
|
||||||
) -> Result<Vec<u8>> {
|
|
||||||
let module = wasmi::Module::from_buffer(code)?;
|
|
||||||
let module = Self::instantiate_module(heap_pages, &module)?;
|
|
||||||
|
|
||||||
self.call_in_wasm_module(ext, &module, method, data)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Call a given method with a custom signature in the given code.
|
|
||||||
///
|
|
||||||
/// This should be used for tests only.
|
|
||||||
pub fn call_with_custom_signature<
|
|
||||||
E: Externalities<Blake2Hasher>,
|
|
||||||
F: FnOnce(&mut dyn FnMut(&[u8]) -> Result<u32>) -> Result<Vec<RuntimeValue>>,
|
|
||||||
FR: FnOnce(Option<RuntimeValue>, &MemoryRef) -> Result<Option<R>>,
|
|
||||||
R,
|
|
||||||
>(
|
|
||||||
&self,
|
|
||||||
ext: &mut E,
|
|
||||||
heap_pages: usize,
|
|
||||||
code: &[u8],
|
|
||||||
method: &str,
|
|
||||||
create_parameters: F,
|
|
||||||
filter_result: FR,
|
|
||||||
) -> Result<R> {
|
|
||||||
let module = wasmi::Module::from_buffer(code)?;
|
|
||||||
let module = Self::instantiate_module(heap_pages, &module)?;
|
|
||||||
|
|
||||||
self.call_in_wasm_module_with_custom_signature(
|
|
||||||
ext,
|
|
||||||
&module,
|
|
||||||
method,
|
|
||||||
create_parameters,
|
|
||||||
filter_result,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_mem_instance(module: &ModuleRef) -> Result<MemoryRef> {
|
|
||||||
Ok(module
|
|
||||||
.export_by_name("memory")
|
|
||||||
.ok_or_else(|| Error::InvalidMemoryReference)?
|
|
||||||
.as_memory()
|
|
||||||
.ok_or_else(|| Error::InvalidMemoryReference)?
|
|
||||||
.clone())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Find the global named `__heap_base` in the given wasm module instance and
|
|
||||||
/// tries to get its value.
|
|
||||||
fn get_heap_base(module: &ModuleRef) -> Result<u32> {
|
|
||||||
let heap_base_val = module
|
|
||||||
.export_by_name("__heap_base")
|
|
||||||
.ok_or_else(|| Error::HeapBaseNotFoundOrInvalid)?
|
|
||||||
.as_global()
|
|
||||||
.ok_or_else(|| Error::HeapBaseNotFoundOrInvalid)?
|
|
||||||
.get();
|
|
||||||
|
|
||||||
match heap_base_val {
|
|
||||||
wasmi::RuntimeValue::I32(v) => Ok(v as u32),
|
|
||||||
_ => Err(Error::HeapBaseNotFoundOrInvalid),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Call a given method in the given wasm-module runtime.
|
|
||||||
pub fn call_in_wasm_module<E: Externalities<Blake2Hasher>>(
|
|
||||||
&self,
|
|
||||||
ext: &mut E,
|
|
||||||
module_instance: &ModuleRef,
|
|
||||||
method: &str,
|
|
||||||
data: &[u8],
|
|
||||||
) -> Result<Vec<u8>> {
|
|
||||||
self.call_in_wasm_module_with_custom_signature(
|
|
||||||
ext,
|
|
||||||
module_instance,
|
|
||||||
method,
|
|
||||||
|alloc| {
|
|
||||||
let offset = alloc(data)?;
|
|
||||||
Ok(vec![I32(offset as i32), I32(data.len() as i32)])
|
|
||||||
},
|
|
||||||
|res, memory| {
|
|
||||||
if let Some(I64(r)) = res {
|
|
||||||
let offset = r as u32;
|
|
||||||
let length = (r as u64 >> 32) as usize;
|
|
||||||
memory.get(offset, length).map_err(|_| Error::Runtime).map(Some)
|
|
||||||
} else {
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Call a given method in the given wasm-module runtime.
|
|
||||||
fn call_in_wasm_module_with_custom_signature<
|
|
||||||
E: Externalities<Blake2Hasher>,
|
|
||||||
F: FnOnce(&mut dyn FnMut(&[u8]) -> Result<u32>) -> Result<Vec<RuntimeValue>>,
|
|
||||||
FR: FnOnce(Option<RuntimeValue>, &MemoryRef) -> Result<Option<R>>,
|
|
||||||
R,
|
|
||||||
>(
|
|
||||||
&self,
|
|
||||||
ext: &mut E,
|
|
||||||
module_instance: &ModuleRef,
|
|
||||||
method: &str,
|
|
||||||
create_parameters: F,
|
|
||||||
filter_result: FR,
|
|
||||||
) -> Result<R> {
|
|
||||||
// extract a reference to a linear memory, optional reference to a table
|
|
||||||
// and then initialize FunctionExecutor.
|
|
||||||
let memory = Self::get_mem_instance(module_instance)?;
|
|
||||||
let table: Option<TableRef> = module_instance
|
|
||||||
.export_by_name("__indirect_function_table")
|
|
||||||
.and_then(|e| e.as_table().cloned());
|
|
||||||
let heap_base = Self::get_heap_base(module_instance)?;
|
|
||||||
|
|
||||||
let mut fec = FunctionExecutor::new(
|
|
||||||
memory.clone(),
|
|
||||||
heap_base,
|
|
||||||
table,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
let parameters = create_parameters(&mut |data: &[u8]| {
|
|
||||||
let offset = fec.allocate_memory(data.len() as u32)?;
|
|
||||||
fec.write_memory(offset, data).map(|_| offset.into()).map_err(Into::into)
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let result = runtime_io::with_externalities(
|
|
||||||
ext,
|
|
||||||
|| module_instance.invoke_export(method, ¶meters, &mut fec),
|
|
||||||
);
|
|
||||||
|
|
||||||
match result {
|
|
||||||
Ok(val) => match filter_result(val, &memory)? {
|
|
||||||
Some(val) => Ok(val),
|
|
||||||
None => Err(Error::InvalidReturn),
|
|
||||||
},
|
|
||||||
Err(e) => {
|
|
||||||
trace!(
|
|
||||||
target: "wasm-executor",
|
|
||||||
"Failed to execute code with {} pages",
|
|
||||||
memory.current_size().0
|
|
||||||
);
|
|
||||||
Err(e.into())
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Prepare module instance
|
|
||||||
pub fn instantiate_module(
|
|
||||||
heap_pages: usize,
|
|
||||||
module: &Module,
|
|
||||||
) -> Result<ModuleRef> {
|
|
||||||
// start module instantiation. Don't run 'start' function yet.
|
|
||||||
let intermediate_instance = ModuleInstance::new(
|
|
||||||
module,
|
|
||||||
&ImportsBuilder::new()
|
|
||||||
.with_resolver("env", FunctionExecutor::resolver())
|
|
||||||
)?;
|
|
||||||
|
|
||||||
// Verify that the module has the heap base global variable.
|
|
||||||
let _ = Self::get_heap_base(intermediate_instance.not_started_instance())?;
|
|
||||||
|
|
||||||
// Extract a reference to a linear memory.
|
|
||||||
let memory = Self::get_mem_instance(intermediate_instance.not_started_instance())?;
|
|
||||||
memory.grow(Pages(heap_pages)).map_err(|_| Error::Runtime)?;
|
|
||||||
|
|
||||||
if intermediate_instance.has_start() {
|
|
||||||
// Runtime is not allowed to have the `start` function.
|
|
||||||
Err(Error::RuntimeHasStartFn)
|
|
||||||
} else {
|
|
||||||
Ok(intermediate_instance.assert_no_start())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
use codec::Encode;
|
|
||||||
|
|
||||||
use state_machine::TestExternalities as CoreTestExternalities;
|
|
||||||
use hex_literal::hex;
|
|
||||||
use primitives::map;
|
|
||||||
use runtime_test::WASM_BINARY;
|
|
||||||
use substrate_offchain::testing;
|
|
||||||
|
|
||||||
type TestExternalities<H> = CoreTestExternalities<H, u64>;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn returning_should_work() {
|
|
||||||
let mut ext = TestExternalities::default();
|
|
||||||
let test_code = WASM_BINARY;
|
|
||||||
|
|
||||||
let output = WasmExecutor::new().call(&mut ext, 8, &test_code[..], "test_empty_return", &[]).unwrap();
|
|
||||||
assert_eq!(output, vec![0u8; 0]);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn panicking_should_work() {
|
|
||||||
let mut ext = TestExternalities::default();
|
|
||||||
let test_code = WASM_BINARY;
|
|
||||||
|
|
||||||
let output = WasmExecutor::new().call(&mut ext, 8, &test_code[..], "test_panic", &[]);
|
|
||||||
assert!(output.is_err());
|
|
||||||
|
|
||||||
let output = WasmExecutor::new().call(&mut ext, 8, &test_code[..], "test_conditional_panic", &[]);
|
|
||||||
assert_eq!(output.unwrap(), vec![0u8; 0]);
|
|
||||||
|
|
||||||
let output = WasmExecutor::new().call(&mut ext, 8, &test_code[..], "test_conditional_panic", &[2]);
|
|
||||||
assert!(output.is_err());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn storage_should_work() {
|
|
||||||
let mut ext = TestExternalities::default();
|
|
||||||
ext.set_storage(b"foo".to_vec(), b"bar".to_vec());
|
|
||||||
let test_code = WASM_BINARY;
|
|
||||||
|
|
||||||
let output = WasmExecutor::new().call(&mut ext, 8, &test_code[..], "test_data_in", b"Hello world").unwrap();
|
|
||||||
|
|
||||||
assert_eq!(output, b"all ok!".to_vec());
|
|
||||||
|
|
||||||
let expected = TestExternalities::new((map![
|
|
||||||
b"input".to_vec() => b"Hello world".to_vec(),
|
|
||||||
b"foo".to_vec() => b"bar".to_vec(),
|
|
||||||
b"baz".to_vec() => b"bar".to_vec()
|
|
||||||
], map![]));
|
|
||||||
assert_eq!(ext, expected);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn clear_prefix_should_work() {
|
|
||||||
let mut ext = TestExternalities::default();
|
|
||||||
ext.set_storage(b"aaa".to_vec(), b"1".to_vec());
|
|
||||||
ext.set_storage(b"aab".to_vec(), b"2".to_vec());
|
|
||||||
ext.set_storage(b"aba".to_vec(), b"3".to_vec());
|
|
||||||
ext.set_storage(b"abb".to_vec(), b"4".to_vec());
|
|
||||||
ext.set_storage(b"bbb".to_vec(), b"5".to_vec());
|
|
||||||
let test_code = WASM_BINARY;
|
|
||||||
|
|
||||||
// This will clear all entries which prefix is "ab".
|
|
||||||
let output = WasmExecutor::new().call(&mut ext, 8, &test_code[..], "test_clear_prefix", b"ab").unwrap();
|
|
||||||
|
|
||||||
assert_eq!(output, b"all ok!".to_vec());
|
|
||||||
|
|
||||||
let expected = TestExternalities::new((map![
|
|
||||||
b"aaa".to_vec() => b"1".to_vec(),
|
|
||||||
b"aab".to_vec() => b"2".to_vec(),
|
|
||||||
b"bbb".to_vec() => b"5".to_vec()
|
|
||||||
], map![]));
|
|
||||||
assert_eq!(expected, ext);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn blake2_256_should_work() {
|
|
||||||
let mut ext = TestExternalities::default();
|
|
||||||
let test_code = WASM_BINARY;
|
|
||||||
assert_eq!(
|
|
||||||
WasmExecutor::new().call(&mut ext, 8, &test_code[..], "test_blake2_256", &[]).unwrap(),
|
|
||||||
blake2_256(&b""[..]).encode()
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
WasmExecutor::new().call(&mut ext, 8, &test_code[..], "test_blake2_256", b"Hello world!").unwrap(),
|
|
||||||
blake2_256(&b"Hello world!"[..]).encode()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn blake2_128_should_work() {
|
|
||||||
let mut ext = TestExternalities::default();
|
|
||||||
let test_code = WASM_BINARY;
|
|
||||||
assert_eq!(
|
|
||||||
WasmExecutor::new().call(&mut ext, 8, &test_code[..], "test_blake2_128", &[]).unwrap(),
|
|
||||||
blake2_128(&b""[..]).encode()
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
WasmExecutor::new().call(&mut ext, 8, &test_code[..], "test_blake2_128", b"Hello world!").unwrap(),
|
|
||||||
blake2_128(&b"Hello world!"[..]).encode()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn twox_256_should_work() {
|
|
||||||
let mut ext = TestExternalities::default();
|
|
||||||
let test_code = WASM_BINARY;
|
|
||||||
assert_eq!(
|
|
||||||
WasmExecutor::new().call(&mut ext, 8, &test_code[..], "test_twox_256", &[]).unwrap(),
|
|
||||||
hex!("99e9d85137db46ef4bbea33613baafd56f963c64b1f3685a4eb4abd67ff6203a"),
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
WasmExecutor::new().call(&mut ext, 8, &test_code[..], "test_twox_256", b"Hello world!").unwrap(),
|
|
||||||
hex!("b27dfd7f223f177f2a13647b533599af0c07f68bda23d96d059da2b451a35a74"),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn twox_128_should_work() {
|
|
||||||
let mut ext = TestExternalities::default();
|
|
||||||
let test_code = WASM_BINARY;
|
|
||||||
assert_eq!(
|
|
||||||
WasmExecutor::new().call(&mut ext, 8, &test_code[..], "test_twox_128", &[]).unwrap(),
|
|
||||||
hex!("99e9d85137db46ef4bbea33613baafd5")
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
WasmExecutor::new().call(&mut ext, 8, &test_code[..], "test_twox_128", b"Hello world!").unwrap(),
|
|
||||||
hex!("b27dfd7f223f177f2a13647b533599af")
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn ed25519_verify_should_work() {
|
|
||||||
let mut ext = TestExternalities::<Blake2Hasher>::default();
|
|
||||||
let test_code = WASM_BINARY;
|
|
||||||
let key = ed25519::Pair::from_seed(&blake2_256(b"test"));
|
|
||||||
let sig = key.sign(b"all ok!");
|
|
||||||
let mut calldata = vec![];
|
|
||||||
calldata.extend_from_slice(key.public().as_ref());
|
|
||||||
calldata.extend_from_slice(sig.as_ref());
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
WasmExecutor::new().call(&mut ext, 8, &test_code[..], "test_ed25519_verify", &calldata).unwrap(),
|
|
||||||
vec![1]
|
|
||||||
);
|
|
||||||
|
|
||||||
let other_sig = key.sign(b"all is not ok!");
|
|
||||||
let mut calldata = vec![];
|
|
||||||
calldata.extend_from_slice(key.public().as_ref());
|
|
||||||
calldata.extend_from_slice(other_sig.as_ref());
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
WasmExecutor::new().call(&mut ext, 8, &test_code[..], "test_ed25519_verify", &calldata).unwrap(),
|
|
||||||
vec![0]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn sr25519_verify_should_work() {
|
|
||||||
let mut ext = TestExternalities::<Blake2Hasher>::default();
|
|
||||||
let test_code = WASM_BINARY;
|
|
||||||
let key = sr25519::Pair::from_seed(&blake2_256(b"test"));
|
|
||||||
let sig = key.sign(b"all ok!");
|
|
||||||
let mut calldata = vec![];
|
|
||||||
calldata.extend_from_slice(key.public().as_ref());
|
|
||||||
calldata.extend_from_slice(sig.as_ref());
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
WasmExecutor::new().call(&mut ext, 8, &test_code[..], "test_sr25519_verify", &calldata).unwrap(),
|
|
||||||
vec![1]
|
|
||||||
);
|
|
||||||
|
|
||||||
let other_sig = key.sign(b"all is not ok!");
|
|
||||||
let mut calldata = vec![];
|
|
||||||
calldata.extend_from_slice(key.public().as_ref());
|
|
||||||
calldata.extend_from_slice(other_sig.as_ref());
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
WasmExecutor::new().call(&mut ext, 8, &test_code[..], "test_sr25519_verify", &calldata).unwrap(),
|
|
||||||
vec![0]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn ordered_trie_root_should_work() {
|
|
||||||
let mut ext = TestExternalities::<Blake2Hasher>::default();
|
|
||||||
let trie_input = vec![b"zero".to_vec(), b"one".to_vec(), b"two".to_vec()];
|
|
||||||
let test_code = WASM_BINARY;
|
|
||||||
assert_eq!(
|
|
||||||
WasmExecutor::new().call(&mut ext, 8, &test_code[..], "test_ordered_trie_root", &[]).unwrap(),
|
|
||||||
Layout::<Blake2Hasher>::ordered_trie_root(trie_input.iter()).as_fixed_bytes().encode()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn offchain_local_storage_should_work() {
|
|
||||||
use substrate_client::backend::OffchainStorage;
|
|
||||||
|
|
||||||
let mut ext = TestExternalities::<Blake2Hasher>::default();
|
|
||||||
let (offchain, state) = testing::TestOffchainExt::new();
|
|
||||||
ext.set_offchain_externalities(offchain);
|
|
||||||
let test_code = WASM_BINARY;
|
|
||||||
assert_eq!(
|
|
||||||
WasmExecutor::new().call(&mut ext, 8, &test_code[..], "test_offchain_local_storage", &[]).unwrap(),
|
|
||||||
vec![0]
|
|
||||||
);
|
|
||||||
assert_eq!(state.read().persistent_storage.get(b"", b"test"), Some(vec![]));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn offchain_http_should_work() {
|
|
||||||
let mut ext = TestExternalities::<Blake2Hasher>::default();
|
|
||||||
let (offchain, state) = testing::TestOffchainExt::new();
|
|
||||||
ext.set_offchain_externalities(offchain);
|
|
||||||
state.write().expect_request(
|
|
||||||
0,
|
|
||||||
testing::PendingRequest {
|
|
||||||
method: "POST".into(),
|
|
||||||
uri: "http://localhost:12345".into(),
|
|
||||||
body: vec![1, 2, 3, 4],
|
|
||||||
headers: vec![("X-Auth".to_owned(), "test".to_owned())],
|
|
||||||
sent: true,
|
|
||||||
response: Some(vec![1, 2, 3]),
|
|
||||||
response_headers: vec![("X-Auth".to_owned(), "hello".to_owned())],
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
let test_code = WASM_BINARY;
|
|
||||||
assert_eq!(
|
|
||||||
WasmExecutor::new().call(&mut ext, 8, &test_code[..], "test_offchain_http", &[]).unwrap(),
|
|
||||||
vec![0]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -31,24 +31,24 @@
|
|||||||
|
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
mod wasm_utils;
|
mod wasm_utils;
|
||||||
mod wasm_executor;
|
mod wasmi_execution;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
mod native_executor;
|
mod native_executor;
|
||||||
mod sandbox;
|
mod sandbox;
|
||||||
mod allocator;
|
mod allocator;
|
||||||
mod wasm_runtimes_cache;
|
mod host_interface;
|
||||||
|
mod wasm_runtime;
|
||||||
|
|
||||||
pub mod error;
|
pub mod error;
|
||||||
pub use wasmi;
|
pub use wasmi;
|
||||||
pub use wasm_executor::WasmExecutor;
|
|
||||||
pub use native_executor::{with_native_environment, NativeExecutor, NativeExecutionDispatch};
|
pub use native_executor::{with_native_environment, NativeExecutor, NativeExecutionDispatch};
|
||||||
pub use wasm_runtimes_cache::RuntimesCache;
|
|
||||||
pub use runtime_version::{RuntimeVersion, NativeVersion};
|
pub use runtime_version::{RuntimeVersion, NativeVersion};
|
||||||
pub use codec::Codec;
|
pub use codec::Codec;
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
pub use primitives::{Blake2Hasher, traits::Externalities};
|
pub use primitives::{Blake2Hasher, traits::Externalities};
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
pub use wasm_interface;
|
pub use wasm_interface;
|
||||||
|
pub use wasm_runtime::WasmExecutionMethod;
|
||||||
|
|
||||||
/// Provides runtime information.
|
/// Provides runtime information.
|
||||||
pub trait RuntimeInfo {
|
pub trait RuntimeInfo {
|
||||||
|
|||||||
@@ -16,19 +16,20 @@
|
|||||||
|
|
||||||
use std::{result, cell::RefCell, panic::UnwindSafe};
|
use std::{result, cell::RefCell, panic::UnwindSafe};
|
||||||
use crate::error::{Error, Result};
|
use crate::error::{Error, Result};
|
||||||
use crate::wasm_executor::WasmExecutor;
|
use crate::wasm_runtime::{RuntimesCache, WasmExecutionMethod, WasmRuntime};
|
||||||
|
use crate::RuntimeInfo;
|
||||||
use runtime_version::{NativeVersion, RuntimeVersion};
|
use runtime_version::{NativeVersion, RuntimeVersion};
|
||||||
use codec::{Decode, Encode};
|
use codec::{Decode, Encode};
|
||||||
use crate::RuntimeInfo;
|
|
||||||
use primitives::{Blake2Hasher, NativeOrEncoded, traits::{CodeExecutor, Externalities}};
|
use primitives::{Blake2Hasher, NativeOrEncoded, traits::{CodeExecutor, Externalities}};
|
||||||
use log::{trace, warn};
|
use log::{trace, warn};
|
||||||
|
|
||||||
use crate::RuntimesCache;
|
|
||||||
|
|
||||||
thread_local! {
|
thread_local! {
|
||||||
static RUNTIMES_CACHE: RefCell<RuntimesCache> = RefCell::new(RuntimesCache::new());
|
static RUNTIMES_CACHE: RefCell<RuntimesCache> = RefCell::new(RuntimesCache::new());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Default num of pages for the heap
|
||||||
|
const DEFAULT_HEAP_PAGES: u64 = 1024;
|
||||||
|
|
||||||
fn safe_call<F, U>(f: F) -> Result<U>
|
fn safe_call<F, U>(f: F) -> Result<U>
|
||||||
where F: UnwindSafe + FnOnce() -> U
|
where F: UnwindSafe + FnOnce() -> U
|
||||||
{
|
{
|
||||||
@@ -65,31 +66,52 @@ pub trait NativeExecutionDispatch: Send + Sync {
|
|||||||
pub struct NativeExecutor<D> {
|
pub struct NativeExecutor<D> {
|
||||||
/// Dummy field to avoid the compiler complaining about us not using `D`.
|
/// Dummy field to avoid the compiler complaining about us not using `D`.
|
||||||
_dummy: ::std::marker::PhantomData<D>,
|
_dummy: ::std::marker::PhantomData<D>,
|
||||||
/// The fallback executor in case native isn't available.
|
/// Method used to execute fallback Wasm code.
|
||||||
fallback: WasmExecutor,
|
fallback_method: WasmExecutionMethod,
|
||||||
/// Native runtime version info.
|
/// Native runtime version info.
|
||||||
native_version: NativeVersion,
|
native_version: NativeVersion,
|
||||||
/// The number of 64KB pages to allocate for Wasm execution.
|
/// The number of 64KB pages to allocate for Wasm execution.
|
||||||
default_heap_pages: Option<u64>,
|
default_heap_pages: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<D: NativeExecutionDispatch> NativeExecutor<D> {
|
impl<D: NativeExecutionDispatch> NativeExecutor<D> {
|
||||||
/// Create new instance.
|
/// Create new instance.
|
||||||
pub fn new(default_heap_pages: Option<u64>) -> Self {
|
///
|
||||||
|
/// # Parameters
|
||||||
|
///
|
||||||
|
/// `fallback_method` - Method used to execute fallback Wasm code.
|
||||||
|
///
|
||||||
|
/// `default_heap_pages` - Number of 64KB pages to allocate for Wasm execution.
|
||||||
|
/// Defaults to `DEFAULT_HEAP_PAGES` if `None` is provided.
|
||||||
|
pub fn new(fallback_method: WasmExecutionMethod, default_heap_pages: Option<u64>) -> Self {
|
||||||
NativeExecutor {
|
NativeExecutor {
|
||||||
_dummy: Default::default(),
|
_dummy: Default::default(),
|
||||||
fallback: WasmExecutor::new(),
|
fallback_method,
|
||||||
native_version: D::native_version(),
|
native_version: D::native_version(),
|
||||||
default_heap_pages: default_heap_pages,
|
default_heap_pages: default_heap_pages.unwrap_or(DEFAULT_HEAP_PAGES),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn with_runtime<E, R>(
|
||||||
|
&self,
|
||||||
|
ext: &mut E,
|
||||||
|
f: impl for <'a> FnOnce(&'a mut dyn WasmRuntime, &'a mut E) -> Result<R>
|
||||||
|
) -> Result<R>
|
||||||
|
where E: Externalities<Blake2Hasher>
|
||||||
|
{
|
||||||
|
RUNTIMES_CACHE.with(|cache| {
|
||||||
|
let mut cache = cache.borrow_mut();
|
||||||
|
let runtime = cache.fetch_runtime(ext, self.fallback_method, self.default_heap_pages)?;
|
||||||
|
f(runtime, ext)
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<D: NativeExecutionDispatch> Clone for NativeExecutor<D> {
|
impl<D: NativeExecutionDispatch> Clone for NativeExecutor<D> {
|
||||||
fn clone(&self) -> Self {
|
fn clone(&self) -> Self {
|
||||||
NativeExecutor {
|
NativeExecutor {
|
||||||
_dummy: Default::default(),
|
_dummy: Default::default(),
|
||||||
fallback: self.fallback.clone(),
|
fallback_method: self.fallback_method,
|
||||||
native_version: D::native_version(),
|
native_version: D::native_version(),
|
||||||
default_heap_pages: self.default_heap_pages,
|
default_heap_pages: self.default_heap_pages,
|
||||||
}
|
}
|
||||||
@@ -105,17 +127,13 @@ impl<D: NativeExecutionDispatch> RuntimeInfo for NativeExecutor<D> {
|
|||||||
&self,
|
&self,
|
||||||
ext: &mut E,
|
ext: &mut E,
|
||||||
) -> Option<RuntimeVersion> {
|
) -> Option<RuntimeVersion> {
|
||||||
RUNTIMES_CACHE.with(|cache| {
|
match self.with_runtime(ext, |runtime, _ext| Ok(runtime.version())) {
|
||||||
let cache = &mut cache.borrow_mut();
|
Ok(version) => version,
|
||||||
|
Err(e) => {
|
||||||
match cache.fetch_runtime(&self.fallback, ext, self.default_heap_pages) {
|
warn!(target: "executor", "Failed to fetch runtime: {:?}", e);
|
||||||
Ok(runtime) => runtime.version(),
|
None
|
||||||
Err(e) => {
|
|
||||||
warn!(target: "executor", "Failed to fetch runtime: {:?}", e);
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -135,15 +153,9 @@ impl<D: NativeExecutionDispatch> CodeExecutor<Blake2Hasher> for NativeExecutor<D
|
|||||||
use_native: bool,
|
use_native: bool,
|
||||||
native_call: Option<NC>,
|
native_call: Option<NC>,
|
||||||
) -> (Result<NativeOrEncoded<R>>, bool){
|
) -> (Result<NativeOrEncoded<R>>, bool){
|
||||||
RUNTIMES_CACHE.with(|cache| {
|
let mut used_native = false;
|
||||||
let cache = &mut cache.borrow_mut();
|
let result = self.with_runtime(ext, |runtime, ext| {
|
||||||
let cached_runtime = match cache.fetch_runtime(
|
let onchain_version = runtime.version();
|
||||||
&self.fallback, ext, self.default_heap_pages,
|
|
||||||
) {
|
|
||||||
Ok(cached_runtime) => cached_runtime,
|
|
||||||
Err(e) => return (Err(e), false),
|
|
||||||
};
|
|
||||||
let onchain_version = cached_runtime.version();
|
|
||||||
match (
|
match (
|
||||||
use_native,
|
use_native,
|
||||||
onchain_version
|
onchain_version
|
||||||
@@ -160,25 +172,9 @@ impl<D: NativeExecutionDispatch> CodeExecutor<Blake2Hasher> for NativeExecutor<D
|
|||||||
.as_ref()
|
.as_ref()
|
||||||
.map_or_else(||"<None>".into(), |v| format!("{}", v))
|
.map_or_else(||"<None>".into(), |v| format!("{}", v))
|
||||||
);
|
);
|
||||||
(
|
runtime.call(ext, method, data).map(NativeOrEncoded::Encoded)
|
||||||
cached_runtime.with(|module|
|
|
||||||
self.fallback
|
|
||||||
.call_in_wasm_module(ext, module, method, data)
|
|
||||||
.map(NativeOrEncoded::Encoded)
|
|
||||||
),
|
|
||||||
false
|
|
||||||
)
|
|
||||||
}
|
|
||||||
(false, _, _) => {
|
|
||||||
(
|
|
||||||
cached_runtime.with(|module|
|
|
||||||
self.fallback
|
|
||||||
.call_in_wasm_module(ext, module, method, data)
|
|
||||||
.map(NativeOrEncoded::Encoded)
|
|
||||||
),
|
|
||||||
false
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
(false, _, _) => runtime.call(ext, method, data).map(NativeOrEncoded::Encoded),
|
||||||
(true, true, Some(call)) => {
|
(true, true, Some(call)) => {
|
||||||
trace!(
|
trace!(
|
||||||
target: "executor",
|
target: "executor",
|
||||||
@@ -188,11 +184,13 @@ impl<D: NativeExecutionDispatch> CodeExecutor<Blake2Hasher> for NativeExecutor<D
|
|||||||
.as_ref()
|
.as_ref()
|
||||||
.map_or_else(||"<None>".into(), |v| format!("{}", v))
|
.map_or_else(||"<None>".into(), |v| format!("{}", v))
|
||||||
);
|
);
|
||||||
(
|
|
||||||
with_native_environment(ext, move || (call)())
|
used_native = true;
|
||||||
.and_then(|r| r.map(NativeOrEncoded::Native).map_err(|s| Error::ApiError(s.to_string()))),
|
with_native_environment(ext, move || (call)())
|
||||||
true
|
.and_then(|r| r
|
||||||
)
|
.map(NativeOrEncoded::Native)
|
||||||
|
.map_err(|s| Error::ApiError(s.to_string()))
|
||||||
|
)
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
trace!(
|
trace!(
|
||||||
@@ -201,10 +199,13 @@ impl<D: NativeExecutionDispatch> CodeExecutor<Blake2Hasher> for NativeExecutor<D
|
|||||||
self.native_version.runtime_version,
|
self.native_version.runtime_version,
|
||||||
onchain_version.as_ref().map_or_else(||"<None>".into(), |v| format!("{}", v))
|
onchain_version.as_ref().map_or_else(||"<None>".into(), |v| format!("{}", v))
|
||||||
);
|
);
|
||||||
(D::dispatch(ext, method, data).map(NativeOrEncoded::Encoded), true)
|
|
||||||
|
used_native = true;
|
||||||
|
D::dispatch(ext, method, data).map(NativeOrEncoded::Encoded)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
|
(result, used_native)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -586,14 +586,27 @@ impl<FR> Store<FR> {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use primitives::{Blake2Hasher};
|
use primitives::{Blake2Hasher, traits::Externalities};
|
||||||
use crate::wasm_executor::WasmExecutor;
|
use crate::wasm_runtime::WasmRuntime;
|
||||||
|
use crate::wasmi_execution;
|
||||||
use state_machine::TestExternalities as CoreTestExternalities;
|
use state_machine::TestExternalities as CoreTestExternalities;
|
||||||
use wabt;
|
use wabt;
|
||||||
use runtime_test::WASM_BINARY;
|
use runtime_test::WASM_BINARY;
|
||||||
|
|
||||||
type TestExternalities<H> = CoreTestExternalities<H, u64>;
|
type TestExternalities<H> = CoreTestExternalities<H, u64>;
|
||||||
|
|
||||||
|
fn call_wasm<E: Externalities<Blake2Hasher>>(
|
||||||
|
ext: &mut E,
|
||||||
|
heap_pages: u64,
|
||||||
|
code: &[u8],
|
||||||
|
method: &str,
|
||||||
|
data: &[u8],
|
||||||
|
) -> Result<Vec<u8>> {
|
||||||
|
let mut instance = wasmi_execution::create_instance(ext, code, heap_pages)
|
||||||
|
.map_err(|err| err.to_string())?;
|
||||||
|
instance.call(ext, method, data)
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn sandbox_should_work() {
|
fn sandbox_should_work() {
|
||||||
let mut ext = TestExternalities::<Blake2Hasher>::default();
|
let mut ext = TestExternalities::<Blake2Hasher>::default();
|
||||||
@@ -621,7 +634,7 @@ mod tests {
|
|||||||
"#).unwrap();
|
"#).unwrap();
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
WasmExecutor::new().call(&mut ext, 8, &test_code[..], "test_sandbox", &code).unwrap(),
|
call_wasm(&mut ext, 8, &test_code[..], "test_sandbox", &code).unwrap(),
|
||||||
vec![1],
|
vec![1],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -642,7 +655,7 @@ mod tests {
|
|||||||
"#).unwrap();
|
"#).unwrap();
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
WasmExecutor::new().call(&mut ext, 8, &test_code[..], "test_sandbox", &code).unwrap(),
|
call_wasm(&mut ext, 8, &test_code[..], "test_sandbox", &code).unwrap(),
|
||||||
vec![0],
|
vec![0],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -662,7 +675,7 @@ mod tests {
|
|||||||
)
|
)
|
||||||
"#).unwrap();
|
"#).unwrap();
|
||||||
|
|
||||||
let res = WasmExecutor::new().call(&mut ext, 8, &test_code[..], "test_exhaust_heap", &code);
|
let res = call_wasm(&mut ext, 8, &test_code[..], "test_exhaust_heap", &code);
|
||||||
assert_eq!(res.is_err(), true);
|
assert_eq!(res.is_err(), true);
|
||||||
if let Err(err) = res {
|
if let Err(err) = res {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@@ -708,7 +721,7 @@ mod tests {
|
|||||||
"#).unwrap();
|
"#).unwrap();
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
WasmExecutor::new().call(&mut ext, 8, &test_code[..], "test_sandbox", &code).unwrap(),
|
call_wasm(&mut ext, 8, &test_code[..], "test_sandbox", &code).unwrap(),
|
||||||
vec![1],
|
vec![1],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -742,7 +755,7 @@ mod tests {
|
|||||||
"#).unwrap();
|
"#).unwrap();
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
WasmExecutor::new().call(&mut ext, 8, &test_code[..], "test_sandbox_args", &code).unwrap(),
|
call_wasm(&mut ext, 8, &test_code[..], "test_sandbox_args", &code).unwrap(),
|
||||||
vec![1],
|
vec![1],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -764,7 +777,7 @@ mod tests {
|
|||||||
"#).unwrap();
|
"#).unwrap();
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
WasmExecutor::new().call(&mut ext, 8, &test_code[..], "test_sandbox_return_val", &code).unwrap(),
|
call_wasm(&mut ext, 8, &test_code[..], "test_sandbox_return_val", &code).unwrap(),
|
||||||
vec![1],
|
vec![1],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -784,7 +797,7 @@ mod tests {
|
|||||||
"#).unwrap();
|
"#).unwrap();
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
WasmExecutor::new().call(&mut ext, 8, &test_code[..], "test_sandbox_instantiate", &code).unwrap(),
|
call_wasm(&mut ext, 8, &test_code[..], "test_sandbox_instantiate", &code).unwrap(),
|
||||||
vec![1],
|
vec![1],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -798,7 +811,7 @@ mod tests {
|
|||||||
let code = &[0, 0, 0, 0, 1, 0, 0, 0];
|
let code = &[0, 0, 0, 0, 1, 0, 0, 0];
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
WasmExecutor::new().call(&mut ext, 8, &test_code[..], "test_sandbox_instantiate", code).unwrap(),
|
call_wasm(&mut ext, 8, &test_code[..], "test_sandbox_instantiate", code).unwrap(),
|
||||||
vec![1],
|
vec![1],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -821,7 +834,7 @@ mod tests {
|
|||||||
"#).unwrap();
|
"#).unwrap();
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
WasmExecutor::new().call(&mut ext, 8, &test_code[..], "test_sandbox_instantiate", &code).unwrap(),
|
call_wasm(&mut ext, 8, &test_code[..], "test_sandbox_instantiate", &code).unwrap(),
|
||||||
vec![0],
|
vec![0],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -845,7 +858,7 @@ mod tests {
|
|||||||
"#).unwrap();
|
"#).unwrap();
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
WasmExecutor::new().call(&mut ext, 8, &test_code[..], "test_sandbox_instantiate", &code).unwrap(),
|
call_wasm(&mut ext, 8, &test_code[..], "test_sandbox_instantiate", &code).unwrap(),
|
||||||
vec![2],
|
vec![2],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,173 @@
|
|||||||
|
// Copyright 2019 Parity Technologies (UK) Ltd.
|
||||||
|
// This file is part of Substrate.
|
||||||
|
|
||||||
|
// Substrate is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// Substrate is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
//! Traits and accessor functions for calling into the Substrate Wasm runtime.
|
||||||
|
//!
|
||||||
|
//! The primary means of accessing the runtimes is through a cache which saves the reusable
|
||||||
|
//! components of the runtime that are expensive to initialize.
|
||||||
|
|
||||||
|
use crate::error::{Error, WasmError};
|
||||||
|
use crate::wasmi_execution;
|
||||||
|
use log::{trace, warn};
|
||||||
|
use codec::Decode;
|
||||||
|
use primitives::{storage::well_known_keys, Blake2Hasher, traits::Externalities};
|
||||||
|
use runtime_version::RuntimeVersion;
|
||||||
|
use std::{collections::hash_map::{Entry, HashMap}};
|
||||||
|
|
||||||
|
/// The Substrate Wasm runtime.
|
||||||
|
pub trait WasmRuntime {
|
||||||
|
/// Attempt to update the number of heap pages available during execution.
|
||||||
|
///
|
||||||
|
/// Returns false if the update cannot be applied. The function is guaranteed to return true if
|
||||||
|
/// the heap pages would not change from its current value.
|
||||||
|
fn update_heap_pages(&mut self, heap_pages: u64) -> bool;
|
||||||
|
|
||||||
|
/// Call a method in the Substrate runtime by name. Returns the encoded result on success.
|
||||||
|
fn call(&mut self, ext: &mut dyn Externalities<Blake2Hasher>, method: &str, data: &[u8])
|
||||||
|
-> Result<Vec<u8>, Error>;
|
||||||
|
|
||||||
|
/// Returns the version of this runtime.
|
||||||
|
///
|
||||||
|
/// Returns `None` if the runtime doesn't provide the information or there was an error
|
||||||
|
/// while fetching it.
|
||||||
|
fn version(&self) -> Option<RuntimeVersion>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Specification of different methods of executing the runtime Wasm code.
|
||||||
|
#[derive(Debug, PartialEq, Eq, Hash, Copy, Clone)]
|
||||||
|
pub enum WasmExecutionMethod {
|
||||||
|
/// Uses the Wasmi interpreter.
|
||||||
|
Interpreted,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Cache for the runtimes.
|
||||||
|
///
|
||||||
|
/// When an instance is requested for the first time it is added to this cache. Metadata is kept
|
||||||
|
/// with the instance so that it can be efficiently reinitialized.
|
||||||
|
///
|
||||||
|
/// When using the Wasmi interpreter execution method, the metadata includes the initial memory and
|
||||||
|
/// values of mutable globals. Follow-up requests to fetch a runtime return this one instance with
|
||||||
|
/// the memory reset to the initial memory. So, one runtime instance is reused for every fetch
|
||||||
|
/// request.
|
||||||
|
///
|
||||||
|
/// For now the cache grows indefinitely, but that should be fine for now since runtimes can only be
|
||||||
|
/// upgraded rarely and there are no other ways to make the node to execute some other runtime.
|
||||||
|
pub struct RuntimesCache {
|
||||||
|
/// A cache of runtime instances along with metadata, ready to be reused.
|
||||||
|
///
|
||||||
|
/// Instances are keyed by the Wasm execution method and the hash of their code.
|
||||||
|
instances: HashMap<(WasmExecutionMethod, [u8; 32]), Result<Box<dyn WasmRuntime>, WasmError>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RuntimesCache {
|
||||||
|
/// Creates a new instance of a runtimes cache.
|
||||||
|
pub fn new() -> RuntimesCache {
|
||||||
|
RuntimesCache {
|
||||||
|
instances: HashMap::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Fetches an instance of the runtime.
|
||||||
|
///
|
||||||
|
/// On first use we create a new runtime instance, save it to the cache
|
||||||
|
/// and persist its initial memory.
|
||||||
|
///
|
||||||
|
/// Each subsequent request will return this instance, with its memory restored
|
||||||
|
/// to the persisted initial memory. Thus, we reuse one single runtime instance
|
||||||
|
/// for every `fetch_runtime` invocation.
|
||||||
|
///
|
||||||
|
/// # Parameters
|
||||||
|
///
|
||||||
|
/// `ext` - Externalities to use for the runtime. This is used for setting
|
||||||
|
/// up an initial runtime instance. The parameter is only needed for calling
|
||||||
|
/// into the Wasm module to find out the `Core_version`.
|
||||||
|
///
|
||||||
|
/// `default_heap_pages` - Number of 64KB pages to allocate for Wasm execution.
|
||||||
|
///
|
||||||
|
/// # Return value
|
||||||
|
///
|
||||||
|
/// If no error occurred a tuple `(wasmi::ModuleRef, Option<RuntimeVersion>)` is
|
||||||
|
/// returned. `RuntimeVersion` is contained if the call to `Core_version` returned
|
||||||
|
/// a version.
|
||||||
|
///
|
||||||
|
/// In case of failure one of two errors can be returned:
|
||||||
|
///
|
||||||
|
/// `Err::InvalidCode` is returned for runtime code issues.
|
||||||
|
///
|
||||||
|
/// `Error::InvalidMemoryReference` is returned if no memory export with the
|
||||||
|
/// identifier `memory` can be found in the runtime.
|
||||||
|
pub fn fetch_runtime<E: Externalities<Blake2Hasher>>(
|
||||||
|
&mut self,
|
||||||
|
ext: &mut E,
|
||||||
|
wasm_method: WasmExecutionMethod,
|
||||||
|
default_heap_pages: u64,
|
||||||
|
) -> Result<&mut (dyn WasmRuntime + 'static), Error> {
|
||||||
|
let code_hash = ext
|
||||||
|
.original_storage_hash(well_known_keys::CODE)
|
||||||
|
.ok_or(Error::InvalidCode("`CODE` not found in storage.".into()))?;
|
||||||
|
|
||||||
|
let heap_pages = ext
|
||||||
|
.storage(well_known_keys::HEAP_PAGES)
|
||||||
|
.and_then(|pages| u64::decode(&mut &pages[..]).ok())
|
||||||
|
.unwrap_or(default_heap_pages);
|
||||||
|
|
||||||
|
let result = match self.instances.entry((wasm_method, code_hash.into())) {
|
||||||
|
Entry::Occupied(o) => {
|
||||||
|
let result = o.into_mut();
|
||||||
|
if let Ok(ref mut cached_runtime) = result {
|
||||||
|
if !cached_runtime.update_heap_pages(heap_pages) {
|
||||||
|
trace!(
|
||||||
|
target: "runtimes_cache",
|
||||||
|
"heap_pages were changed. Reinstantiating the instance"
|
||||||
|
);
|
||||||
|
*result = create_wasm_runtime(ext, wasm_method, heap_pages);
|
||||||
|
if let Err(ref err) = result {
|
||||||
|
warn!(target: "runtimes_cache", "cannot create a runtime: {:?}", err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result
|
||||||
|
},
|
||||||
|
Entry::Vacant(v) => {
|
||||||
|
trace!(target: "runtimes_cache", "no instance found in cache, creating now.");
|
||||||
|
let result = create_wasm_runtime(ext, wasm_method, heap_pages);
|
||||||
|
if let Err(ref err) = result {
|
||||||
|
warn!(target: "runtimes_cache", "cannot create a runtime: {:?}", err);
|
||||||
|
}
|
||||||
|
v.insert(result)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
result.as_mut()
|
||||||
|
.map(|runtime| runtime.as_mut())
|
||||||
|
.map_err(|ref e| Error::InvalidCode(format!("{:?}", e)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_wasm_runtime<E: Externalities<Blake2Hasher>>(
|
||||||
|
ext: &mut E,
|
||||||
|
wasm_method: WasmExecutionMethod,
|
||||||
|
heap_pages: u64,
|
||||||
|
) -> Result<Box<dyn WasmRuntime>, WasmError> {
|
||||||
|
let code = ext
|
||||||
|
.original_storage(well_known_keys::CODE)
|
||||||
|
.ok_or(WasmError::CodeNotFound)?;
|
||||||
|
match wasm_method {
|
||||||
|
WasmExecutionMethod::Interpreted =>
|
||||||
|
wasmi_execution::create_instance(ext, &code, heap_pages)
|
||||||
|
.map(|runtime| -> Box<dyn WasmRuntime> { Box::new(runtime) }),
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,353 +0,0 @@
|
|||||||
// Copyright 2019 Parity Technologies (UK) Ltd.
|
|
||||||
// This file is part of Substrate.
|
|
||||||
|
|
||||||
// Substrate is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
|
|
||||||
// Substrate is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU General Public License for more details.
|
|
||||||
|
|
||||||
// You should have received a copy of the GNU General Public License
|
|
||||||
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
//! Implements a cache for pre-created Wasm runtime module instances.
|
|
||||||
|
|
||||||
use crate::error::Error;
|
|
||||||
use crate::wasm_executor::WasmExecutor;
|
|
||||||
use log::{trace, warn};
|
|
||||||
use codec::Decode;
|
|
||||||
use parity_wasm::elements::{deserialize_buffer, DataSegment, Instruction, Module as RawModule};
|
|
||||||
use primitives::{storage::well_known_keys, Blake2Hasher, traits::Externalities};
|
|
||||||
use runtime_version::RuntimeVersion;
|
|
||||||
use std::{collections::hash_map::{Entry, HashMap}, mem, rc::Rc};
|
|
||||||
use wasmi::{Module as WasmModule, ModuleRef as WasmModuleInstanceRef, RuntimeValue};
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
enum CacheError {
|
|
||||||
CodeNotFound,
|
|
||||||
ApplySnapshotFailed,
|
|
||||||
InvalidModule,
|
|
||||||
CantDeserializeWasm,
|
|
||||||
Instantiation(Error),
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A runtime along with its version and initial state snapshot.
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct CachedRuntime {
|
|
||||||
/// A wasm module instance.
|
|
||||||
instance: WasmModuleInstanceRef,
|
|
||||||
/// Runtime version according to `Core_version`.
|
|
||||||
///
|
|
||||||
/// Can be `None` if the runtime doesn't expose this function.
|
|
||||||
version: Option<RuntimeVersion>,
|
|
||||||
/// The snapshot of the instance's state taken just after the instantiation.
|
|
||||||
state_snapshot: StateSnapshot,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl CachedRuntime {
|
|
||||||
/// Perform an operation with the clean version of the runtime wasm instance.
|
|
||||||
pub fn with<R, F>(&self, f: F) -> R
|
|
||||||
where
|
|
||||||
F: FnOnce(&WasmModuleInstanceRef) -> R,
|
|
||||||
{
|
|
||||||
self.state_snapshot.apply(&self.instance).expect(
|
|
||||||
"applying the snapshot can only fail if the passed instance is different
|
|
||||||
from the one that was used for creation of the snapshot;
|
|
||||||
we use the snapshot that is directly associated with the instance;
|
|
||||||
thus the snapshot was created using the instance;
|
|
||||||
qed",
|
|
||||||
);
|
|
||||||
f(&self.instance)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the version of this cached runtime.
|
|
||||||
///
|
|
||||||
/// Returns `None` if the runtime doesn't provide the information or there was an error
|
|
||||||
/// while fetching it.
|
|
||||||
pub fn version(&self) -> Option<RuntimeVersion> {
|
|
||||||
self.version.clone()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A state snapshot of an instance taken just after instantiation.
|
|
||||||
///
|
|
||||||
/// It is used for restoring the state of the module after execution.
|
|
||||||
#[derive(Clone)]
|
|
||||||
struct StateSnapshot {
|
|
||||||
/// The offset and the content of the memory segments that should be used to restore the snapshot
|
|
||||||
data_segments: Vec<(u32, Vec<u8>)>,
|
|
||||||
/// The list of all global mutable variables of the module in their sequential order.
|
|
||||||
global_mut_values: Vec<RuntimeValue>,
|
|
||||||
heap_pages: u64,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl StateSnapshot {
|
|
||||||
// Returns `None` if instance is not valid.
|
|
||||||
fn take(
|
|
||||||
module_instance: &WasmModuleInstanceRef,
|
|
||||||
data_segments: Vec<DataSegment>,
|
|
||||||
heap_pages: u64,
|
|
||||||
) -> Option<Self> {
|
|
||||||
let prepared_segments = data_segments
|
|
||||||
.into_iter()
|
|
||||||
.map(|mut segment| {
|
|
||||||
// Just replace contents of the segment since the segments will be discarded later
|
|
||||||
// anyway.
|
|
||||||
let contents = mem::replace(segment.value_mut(), vec![]);
|
|
||||||
|
|
||||||
let init_expr = match segment.offset() {
|
|
||||||
Some(offset) => offset.code(),
|
|
||||||
// Return if the segment is passive
|
|
||||||
None => return None
|
|
||||||
};
|
|
||||||
|
|
||||||
// [op, End]
|
|
||||||
if init_expr.len() != 2 {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
let offset = match init_expr[0] {
|
|
||||||
Instruction::I32Const(v) => v as u32,
|
|
||||||
Instruction::GetGlobal(idx) => {
|
|
||||||
let global_val = module_instance.globals().get(idx as usize)?.get();
|
|
||||||
match global_val {
|
|
||||||
RuntimeValue::I32(v) => v as u32,
|
|
||||||
_ => return None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => return None,
|
|
||||||
};
|
|
||||||
|
|
||||||
Some((offset, contents))
|
|
||||||
})
|
|
||||||
.collect::<Option<Vec<_>>>()?;
|
|
||||||
|
|
||||||
// Collect all values of mutable globals.
|
|
||||||
let global_mut_values = module_instance
|
|
||||||
.globals()
|
|
||||||
.iter()
|
|
||||||
.filter(|g| g.is_mutable())
|
|
||||||
.map(|g| g.get())
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
Some(Self {
|
|
||||||
data_segments: prepared_segments,
|
|
||||||
global_mut_values,
|
|
||||||
heap_pages,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Reset the runtime instance to the initial version by restoring
|
|
||||||
/// the preserved memory and globals.
|
|
||||||
///
|
|
||||||
/// Returns `Err` if applying the snapshot is failed.
|
|
||||||
fn apply(&self, instance: &WasmModuleInstanceRef) -> Result<(), CacheError> {
|
|
||||||
let memory = instance
|
|
||||||
.export_by_name("memory")
|
|
||||||
.ok_or(CacheError::ApplySnapshotFailed)?
|
|
||||||
.as_memory()
|
|
||||||
.cloned()
|
|
||||||
.ok_or(CacheError::ApplySnapshotFailed)?;
|
|
||||||
|
|
||||||
// First, erase the memory and copy the data segments into it.
|
|
||||||
memory
|
|
||||||
.erase()
|
|
||||||
.map_err(|_| CacheError::ApplySnapshotFailed)?;
|
|
||||||
for (offset, contents) in &self.data_segments {
|
|
||||||
memory
|
|
||||||
.set(*offset, contents)
|
|
||||||
.map_err(|_| CacheError::ApplySnapshotFailed)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Second, restore the values of mutable globals.
|
|
||||||
for (global_ref, global_val) in instance
|
|
||||||
.globals()
|
|
||||||
.iter()
|
|
||||||
.filter(|g| g.is_mutable())
|
|
||||||
.zip(self.global_mut_values.iter())
|
|
||||||
{
|
|
||||||
// the instance should be the same as used for preserving and
|
|
||||||
// we iterate the same way it as we do it for preserving values that means that the
|
|
||||||
// types should be the same and all the values are mutable. So no error is expected/
|
|
||||||
global_ref
|
|
||||||
.set(*global_val)
|
|
||||||
.map_err(|_| CacheError::ApplySnapshotFailed)?;
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Default num of pages for the heap
|
|
||||||
const DEFAULT_HEAP_PAGES: u64 = 1024;
|
|
||||||
|
|
||||||
/// Cache for the runtimes.
|
|
||||||
///
|
|
||||||
/// When an instance is requested for the first time it is added to this
|
|
||||||
/// cache. Furthermore its initial memory and values of mutable globals are preserved here. Follow-up
|
|
||||||
/// requests to fetch a runtime return this one instance with the memory
|
|
||||||
/// reset to the initial memory. So, one runtime instance is reused for
|
|
||||||
/// every fetch request.
|
|
||||||
///
|
|
||||||
/// For now the cache grows indefinitely, but that should be fine for now since runtimes can only be
|
|
||||||
/// upgraded rarely and there are no other ways to make the node to execute some other runtime.
|
|
||||||
pub struct RuntimesCache {
|
|
||||||
/// A cache of runtime instances along with metadata, ready to be reused.
|
|
||||||
///
|
|
||||||
/// Instances are keyed by the hash of their code.
|
|
||||||
instances: HashMap<[u8; 32], Result<Rc<CachedRuntime>, CacheError>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl RuntimesCache {
|
|
||||||
/// Creates a new instance of a runtimes cache.
|
|
||||||
pub fn new() -> RuntimesCache {
|
|
||||||
RuntimesCache {
|
|
||||||
instances: HashMap::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Fetches an instance of the runtime.
|
|
||||||
///
|
|
||||||
/// On first use we create a new runtime instance, save it to the cache
|
|
||||||
/// and persist its initial memory.
|
|
||||||
///
|
|
||||||
/// Each subsequent request will return this instance, with its memory restored
|
|
||||||
/// to the persisted initial memory. Thus, we reuse one single runtime instance
|
|
||||||
/// for every `fetch_runtime` invocation.
|
|
||||||
///
|
|
||||||
/// # Parameters
|
|
||||||
///
|
|
||||||
/// `wasm_executor`- Rust wasm executor. Executes the provided code in a
|
|
||||||
/// sandboxed Wasm runtime.
|
|
||||||
///
|
|
||||||
/// `ext` - Externalities to use for the runtime. This is used for setting
|
|
||||||
/// up an initial runtime instance. The parameter is only needed for calling
|
|
||||||
/// into the Wasm module to find out the `Core_version`.
|
|
||||||
///
|
|
||||||
/// `default_heap_pages` - Number of 64KB pages to allocate for Wasm execution.
|
|
||||||
/// Defaults to `DEFAULT_HEAP_PAGES` if `None` is provided.
|
|
||||||
///
|
|
||||||
/// # Return value
|
|
||||||
///
|
|
||||||
/// If no error occurred a tuple `(wasmi::ModuleRef, Option<RuntimeVersion>)` is
|
|
||||||
/// returned. `RuntimeVersion` is contained if the call to `Core_version` returned
|
|
||||||
/// a version.
|
|
||||||
///
|
|
||||||
/// In case of failure one of two errors can be returned:
|
|
||||||
///
|
|
||||||
/// `Err::InvalidCode` is returned for runtime code issues.
|
|
||||||
///
|
|
||||||
/// `Error::InvalidMemoryReference` is returned if no memory export with the
|
|
||||||
/// identifier `memory` can be found in the runtime.
|
|
||||||
pub fn fetch_runtime<E: Externalities<Blake2Hasher>>(
|
|
||||||
&mut self,
|
|
||||||
wasm_executor: &WasmExecutor,
|
|
||||||
ext: &mut E,
|
|
||||||
default_heap_pages: Option<u64>,
|
|
||||||
) -> Result<Rc<CachedRuntime>, Error> {
|
|
||||||
let code_hash = ext
|
|
||||||
.original_storage_hash(well_known_keys::CODE)
|
|
||||||
.ok_or(Error::InvalidCode("`CODE` not found in storage.".into()))?;
|
|
||||||
|
|
||||||
let heap_pages = ext
|
|
||||||
.storage(well_known_keys::HEAP_PAGES)
|
|
||||||
.and_then(|pages| u64::decode(&mut &pages[..]).ok())
|
|
||||||
.or(default_heap_pages)
|
|
||||||
.unwrap_or(DEFAULT_HEAP_PAGES);
|
|
||||||
|
|
||||||
// This is direct result from fighting with borrowck.
|
|
||||||
let handle_result =
|
|
||||||
|cached_result: &Result<Rc<CachedRuntime>, CacheError>| match *cached_result {
|
|
||||||
Err(ref e) => Err(Error::InvalidCode(format!("{:?}", e))),
|
|
||||||
Ok(ref cached_runtime) => Ok(Rc::clone(cached_runtime)),
|
|
||||||
};
|
|
||||||
|
|
||||||
match self.instances.entry(code_hash.into()) {
|
|
||||||
Entry::Occupied(mut o) => {
|
|
||||||
let result = o.get_mut();
|
|
||||||
if let Ok(ref cached_runtime) = result {
|
|
||||||
if cached_runtime.state_snapshot.heap_pages != heap_pages {
|
|
||||||
trace!(
|
|
||||||
target: "runtimes_cache",
|
|
||||||
"heap_pages were changed. Reinstantiating the instance"
|
|
||||||
);
|
|
||||||
*result = Self::create_wasm_instance(wasm_executor, ext, heap_pages);
|
|
||||||
if let Err(ref err) = result {
|
|
||||||
warn!(target: "runtimes_cache", "cannot create a runtime: {:?}", err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
handle_result(result)
|
|
||||||
},
|
|
||||||
Entry::Vacant(v) => {
|
|
||||||
trace!(target: "runtimes_cache", "no instance found in cache, creating now.");
|
|
||||||
let result = Self::create_wasm_instance(
|
|
||||||
wasm_executor,
|
|
||||||
ext,
|
|
||||||
heap_pages,
|
|
||||||
);
|
|
||||||
if let Err(ref err) = result {
|
|
||||||
warn!(target: "runtimes_cache", "cannot create a runtime: {:?}", err);
|
|
||||||
}
|
|
||||||
handle_result(v.insert(result))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn create_wasm_instance<E: Externalities<Blake2Hasher>>(
|
|
||||||
wasm_executor: &WasmExecutor,
|
|
||||||
ext: &mut E,
|
|
||||||
heap_pages: u64,
|
|
||||||
) -> Result<Rc<CachedRuntime>, CacheError> {
|
|
||||||
let code = ext
|
|
||||||
.original_storage(well_known_keys::CODE)
|
|
||||||
.ok_or(CacheError::CodeNotFound)?;
|
|
||||||
let module = WasmModule::from_buffer(&code).map_err(|_| CacheError::InvalidModule)?;
|
|
||||||
|
|
||||||
// Extract the data segments from the wasm code.
|
|
||||||
//
|
|
||||||
// A return of this error actually indicates that there is a problem in logic, since
|
|
||||||
// we just loaded and validated the `module` above.
|
|
||||||
let data_segments = extract_data_segments(&code)?;
|
|
||||||
|
|
||||||
// Instantiate this module.
|
|
||||||
let instance = WasmExecutor::instantiate_module(heap_pages as usize, &module)
|
|
||||||
.map_err(CacheError::Instantiation)?;
|
|
||||||
|
|
||||||
// Take state snapshot before executing anything.
|
|
||||||
let state_snapshot = StateSnapshot::take(&instance, data_segments, heap_pages)
|
|
||||||
.expect(
|
|
||||||
"`take` returns `Err` if the module is not valid;
|
|
||||||
we already loaded module above, thus the `Module` is proven to be valid at this point;
|
|
||||||
qed
|
|
||||||
",
|
|
||||||
);
|
|
||||||
|
|
||||||
let version = wasm_executor
|
|
||||||
.call_in_wasm_module(ext, &instance, "Core_version", &[])
|
|
||||||
.ok()
|
|
||||||
.and_then(|v| RuntimeVersion::decode(&mut v.as_slice()).ok());
|
|
||||||
Ok(Rc::new(CachedRuntime {
|
|
||||||
instance,
|
|
||||||
version,
|
|
||||||
state_snapshot,
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Extract the data segments from the given wasm code.
|
|
||||||
///
|
|
||||||
/// Returns `Err` if the given wasm code cannot be deserialized.
|
|
||||||
fn extract_data_segments(wasm_code: &[u8]) -> Result<Vec<DataSegment>, CacheError> {
|
|
||||||
let raw_module: RawModule = deserialize_buffer(wasm_code)
|
|
||||||
.map_err(|_| CacheError::CantDeserializeWasm)?;
|
|
||||||
|
|
||||||
let segments = raw_module
|
|
||||||
.data_section()
|
|
||||||
.map(|ds| ds.entries())
|
|
||||||
.unwrap_or(&[])
|
|
||||||
.to_vec();
|
|
||||||
Ok(segments)
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,902 @@
|
|||||||
|
// Copyright 2017-2019 Parity Technologies (UK) Ltd.
|
||||||
|
// This file is part of Substrate.
|
||||||
|
|
||||||
|
// Substrate is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// Substrate is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
//! Implementation of a Wasm runtime using the Wasmi interpreter.
|
||||||
|
|
||||||
|
use std::{str, mem};
|
||||||
|
use wasmi::{
|
||||||
|
Module, ModuleInstance, MemoryInstance, MemoryRef, TableRef, ImportsBuilder, ModuleRef,
|
||||||
|
memory_units::Pages, RuntimeValue::{I32, I64, self},
|
||||||
|
};
|
||||||
|
use crate::error::{Error, WasmError};
|
||||||
|
use codec::{Encode, Decode};
|
||||||
|
use primitives::{sandbox as sandbox_primitives, Blake2Hasher, traits::Externalities};
|
||||||
|
use crate::host_interface::SubstrateExternals;
|
||||||
|
use crate::sandbox;
|
||||||
|
use crate::allocator;
|
||||||
|
use crate::wasm_runtime::WasmRuntime;
|
||||||
|
use log::trace;
|
||||||
|
use parity_wasm::elements::{deserialize_buffer, DataSegment, Instruction, Module as RawModule};
|
||||||
|
use runtime_version::RuntimeVersion;
|
||||||
|
use wasm_interface::{
|
||||||
|
FunctionContext, HostFunctions, Pointer, WordSize, Sandbox, MemoryId, Result as WResult,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct FunctionExecutor {
|
||||||
|
sandbox_store: sandbox::Store<wasmi::FuncRef>,
|
||||||
|
heap: allocator::FreeingBumpHeapAllocator,
|
||||||
|
memory: MemoryRef,
|
||||||
|
table: Option<TableRef>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FunctionExecutor {
|
||||||
|
fn new(m: MemoryRef, heap_base: u32, t: Option<TableRef>) -> Result<Self, Error> {
|
||||||
|
Ok(FunctionExecutor {
|
||||||
|
sandbox_store: sandbox::Store::new(),
|
||||||
|
heap: allocator::FreeingBumpHeapAllocator::new(heap_base),
|
||||||
|
memory: m,
|
||||||
|
table: t,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl sandbox::SandboxCapabilities for FunctionExecutor {
|
||||||
|
type SupervisorFuncRef = wasmi::FuncRef;
|
||||||
|
|
||||||
|
fn store(&self) -> &sandbox::Store<Self::SupervisorFuncRef> {
|
||||||
|
&self.sandbox_store
|
||||||
|
}
|
||||||
|
fn store_mut(&mut self) -> &mut sandbox::Store<Self::SupervisorFuncRef> {
|
||||||
|
&mut self.sandbox_store
|
||||||
|
}
|
||||||
|
fn allocate(&mut self, len: WordSize) -> Result<Pointer<u8>, Error> {
|
||||||
|
let heap = &mut self.heap;
|
||||||
|
self.memory.with_direct_access_mut(|mem| {
|
||||||
|
heap.allocate(mem, len)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
fn deallocate(&mut self, ptr: Pointer<u8>) -> Result<(), Error> {
|
||||||
|
let heap = &mut self.heap;
|
||||||
|
self.memory.with_direct_access_mut(|mem| {
|
||||||
|
heap.deallocate(mem, ptr)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
fn write_memory(&mut self, ptr: Pointer<u8>, data: &[u8]) -> Result<(), Error> {
|
||||||
|
self.memory.set(ptr.into(), data).map_err(Into::into)
|
||||||
|
}
|
||||||
|
fn read_memory(&self, ptr: Pointer<u8>, len: WordSize) -> Result<Vec<u8>, Error> {
|
||||||
|
self.memory.get(ptr.into(), len as usize).map_err(Into::into)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn invoke(
|
||||||
|
&mut self,
|
||||||
|
dispatch_thunk: &Self::SupervisorFuncRef,
|
||||||
|
invoke_args_ptr: Pointer<u8>,
|
||||||
|
invoke_args_len: WordSize,
|
||||||
|
state: u32,
|
||||||
|
func_idx: sandbox::SupervisorFuncIndex,
|
||||||
|
) -> Result<i64, Error>
|
||||||
|
{
|
||||||
|
let result = wasmi::FuncInstance::invoke(
|
||||||
|
dispatch_thunk,
|
||||||
|
&[
|
||||||
|
RuntimeValue::I32(u32::from(invoke_args_ptr) as i32),
|
||||||
|
RuntimeValue::I32(invoke_args_len as i32),
|
||||||
|
RuntimeValue::I32(state as i32),
|
||||||
|
RuntimeValue::I32(usize::from(func_idx) as i32),
|
||||||
|
],
|
||||||
|
self,
|
||||||
|
);
|
||||||
|
match result {
|
||||||
|
Ok(Some(RuntimeValue::I64(val))) => Ok(val),
|
||||||
|
Ok(_) => return Err("Supervisor function returned unexpected result!".into()),
|
||||||
|
Err(err) => Err(Error::Trap(err)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FunctionContext for FunctionExecutor {
|
||||||
|
fn read_memory_into(&self, address: Pointer<u8>, dest: &mut [u8]) -> WResult<()> {
|
||||||
|
self.memory.get_into(address.into(), dest).map_err(|e| format!("{:?}", e))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_memory(&mut self, address: Pointer<u8>, data: &[u8]) -> WResult<()> {
|
||||||
|
self.memory.set(address.into(), data).map_err(|e| format!("{:?}", e))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn allocate_memory(&mut self, size: WordSize) -> WResult<Pointer<u8>> {
|
||||||
|
let heap = &mut self.heap;
|
||||||
|
self.memory.with_direct_access_mut(|mem| {
|
||||||
|
heap.allocate(mem, size).map_err(|e| format!("{:?}", e))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn deallocate_memory(&mut self, ptr: Pointer<u8>) -> WResult<()> {
|
||||||
|
let heap = &mut self.heap;
|
||||||
|
self.memory.with_direct_access_mut(|mem| {
|
||||||
|
heap.deallocate(mem, ptr).map_err(|e| format!("{:?}", e))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sandbox(&mut self) -> &mut dyn Sandbox {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Sandbox for FunctionExecutor {
|
||||||
|
fn memory_get(
|
||||||
|
&self,
|
||||||
|
memory_id: MemoryId,
|
||||||
|
offset: WordSize,
|
||||||
|
buf_ptr: Pointer<u8>,
|
||||||
|
buf_len: WordSize,
|
||||||
|
) -> WResult<u32> {
|
||||||
|
let sandboxed_memory = self.sandbox_store.memory(memory_id).map_err(|e| format!("{:?}", e))?;
|
||||||
|
|
||||||
|
match MemoryInstance::transfer(
|
||||||
|
&sandboxed_memory,
|
||||||
|
offset as usize,
|
||||||
|
&self.memory,
|
||||||
|
buf_ptr.into(),
|
||||||
|
buf_len as usize,
|
||||||
|
) {
|
||||||
|
Ok(()) => Ok(sandbox_primitives::ERR_OK),
|
||||||
|
Err(_) => Ok(sandbox_primitives::ERR_OUT_OF_BOUNDS),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn memory_set(
|
||||||
|
&mut self,
|
||||||
|
memory_id: MemoryId,
|
||||||
|
offset: WordSize,
|
||||||
|
val_ptr: Pointer<u8>,
|
||||||
|
val_len: WordSize,
|
||||||
|
) -> WResult<u32> {
|
||||||
|
let sandboxed_memory = self.sandbox_store.memory(memory_id).map_err(|e| format!("{:?}", e))?;
|
||||||
|
|
||||||
|
match MemoryInstance::transfer(
|
||||||
|
&self.memory,
|
||||||
|
val_ptr.into(),
|
||||||
|
&sandboxed_memory,
|
||||||
|
offset as usize,
|
||||||
|
val_len as usize,
|
||||||
|
) {
|
||||||
|
Ok(()) => Ok(sandbox_primitives::ERR_OK),
|
||||||
|
Err(_) => Ok(sandbox_primitives::ERR_OUT_OF_BOUNDS),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn memory_teardown(&mut self, memory_id: MemoryId) -> WResult<()> {
|
||||||
|
self.sandbox_store.memory_teardown(memory_id).map_err(|e| format!("{:?}", e))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn memory_new(
|
||||||
|
&mut self,
|
||||||
|
initial: u32,
|
||||||
|
maximum: u32,
|
||||||
|
) -> WResult<MemoryId> {
|
||||||
|
self.sandbox_store.new_memory(initial, maximum).map_err(|e| format!("{:?}", e))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn invoke(
|
||||||
|
&mut self,
|
||||||
|
instance_id: u32,
|
||||||
|
export_name: &str,
|
||||||
|
args: &[u8],
|
||||||
|
return_val: Pointer<u8>,
|
||||||
|
return_val_len: WordSize,
|
||||||
|
state: u32,
|
||||||
|
) -> WResult<u32> {
|
||||||
|
trace!(target: "sr-sandbox", "invoke, instance_idx={}", instance_id);
|
||||||
|
|
||||||
|
// Deserialize arguments and convert them into wasmi types.
|
||||||
|
let args = Vec::<sandbox_primitives::TypedValue>::decode(&mut &args[..])
|
||||||
|
.map_err(|_| "Can't decode serialized arguments for the invocation")?
|
||||||
|
.into_iter()
|
||||||
|
.map(Into::into)
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
let instance = self.sandbox_store.instance(instance_id).map_err(|e| format!("{:?}", e))?;
|
||||||
|
let result = instance.invoke(export_name, &args, self, state);
|
||||||
|
|
||||||
|
match result {
|
||||||
|
Ok(None) => Ok(sandbox_primitives::ERR_OK),
|
||||||
|
Ok(Some(val)) => {
|
||||||
|
// Serialize return value and write it back into the memory.
|
||||||
|
sandbox_primitives::ReturnValue::Value(val.into()).using_encoded(|val| {
|
||||||
|
if val.len() > return_val_len as usize {
|
||||||
|
Err("Return value buffer is too small")?;
|
||||||
|
}
|
||||||
|
self.write_memory(return_val, val).map_err(|_| "Return value buffer is OOB")?;
|
||||||
|
Ok(sandbox_primitives::ERR_OK)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
Err(_) => Ok(sandbox_primitives::ERR_EXECUTION),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn instance_teardown(&mut self, instance_id: u32) -> WResult<()> {
|
||||||
|
self.sandbox_store.instance_teardown(instance_id).map_err(|e| format!("{:?}", e))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn instance_new(
|
||||||
|
&mut self,
|
||||||
|
dispatch_thunk_id: u32,
|
||||||
|
wasm: &[u8],
|
||||||
|
raw_env_def: &[u8],
|
||||||
|
state: u32,
|
||||||
|
) -> WResult<u32> {
|
||||||
|
// Extract a dispatch thunk from instance's table by the specified index.
|
||||||
|
let dispatch_thunk = {
|
||||||
|
let table = self.table.as_ref()
|
||||||
|
.ok_or_else(|| "Runtime doesn't have a table; sandbox is unavailable")?;
|
||||||
|
table.get(dispatch_thunk_id)
|
||||||
|
.map_err(|_| "dispatch_thunk_idx is out of the table bounds")?
|
||||||
|
.ok_or_else(|| "dispatch_thunk_idx points on an empty table entry")?
|
||||||
|
.clone()
|
||||||
|
};
|
||||||
|
|
||||||
|
let instance_idx_or_err_code =
|
||||||
|
match sandbox::instantiate(self, dispatch_thunk, wasm, raw_env_def, state) {
|
||||||
|
Ok(instance_idx) => instance_idx,
|
||||||
|
Err(sandbox::InstantiationError::StartTrapped) =>
|
||||||
|
sandbox_primitives::ERR_EXECUTION,
|
||||||
|
Err(_) => sandbox_primitives::ERR_MODULE,
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(instance_idx_or_err_code as u32)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FunctionExecutor {
|
||||||
|
fn resolver() -> &'static dyn wasmi::ModuleImportResolver {
|
||||||
|
struct Resolver;
|
||||||
|
impl wasmi::ModuleImportResolver for Resolver {
|
||||||
|
fn resolve_func(&self, name: &str, signature: &wasmi::Signature)
|
||||||
|
-> std::result::Result<wasmi::FuncRef, wasmi::Error>
|
||||||
|
{
|
||||||
|
let signature = wasm_interface::Signature::from(signature);
|
||||||
|
|
||||||
|
if let Some((index, func)) = SubstrateExternals::functions().iter()
|
||||||
|
.enumerate()
|
||||||
|
.find(|f| name == f.1.name())
|
||||||
|
{
|
||||||
|
if signature == func.signature() {
|
||||||
|
Ok(wasmi::FuncInstance::alloc_host(signature.into(), index))
|
||||||
|
} else {
|
||||||
|
Err(wasmi::Error::Instantiation(
|
||||||
|
format!(
|
||||||
|
"Invalid signature for function `{}` expected `{:?}`, got `{:?}`",
|
||||||
|
func.name(),
|
||||||
|
signature,
|
||||||
|
func.signature(),
|
||||||
|
)
|
||||||
|
))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Err(wasmi::Error::Instantiation(
|
||||||
|
format!("Export {} not found", name),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&Resolver
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl wasmi::Externals for FunctionExecutor {
|
||||||
|
fn invoke_index(&mut self, index: usize, args: wasmi::RuntimeArgs)
|
||||||
|
-> Result<Option<wasmi::RuntimeValue>, wasmi::Trap>
|
||||||
|
{
|
||||||
|
let mut args = args.as_ref().iter().copied().map(Into::into);
|
||||||
|
let function = SubstrateExternals::functions().get(index).ok_or_else(||
|
||||||
|
Error::from(
|
||||||
|
format!("Could not find host function with index: {}", index),
|
||||||
|
)
|
||||||
|
)?;
|
||||||
|
|
||||||
|
function.execute(self, &mut args)
|
||||||
|
.map_err(Error::FunctionExecution)
|
||||||
|
.map_err(wasmi::Trap::from)
|
||||||
|
.map(|v| v.map(Into::into))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_mem_instance(module: &ModuleRef) -> Result<MemoryRef, Error> {
|
||||||
|
Ok(module
|
||||||
|
.export_by_name("memory")
|
||||||
|
.ok_or_else(|| Error::InvalidMemoryReference)?
|
||||||
|
.as_memory()
|
||||||
|
.ok_or_else(|| Error::InvalidMemoryReference)?
|
||||||
|
.clone())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Find the global named `__heap_base` in the given wasm module instance and
|
||||||
|
/// tries to get its value.
|
||||||
|
fn get_heap_base(module: &ModuleRef) -> Result<u32, Error> {
|
||||||
|
let heap_base_val = module
|
||||||
|
.export_by_name("__heap_base")
|
||||||
|
.ok_or_else(|| Error::HeapBaseNotFoundOrInvalid)?
|
||||||
|
.as_global()
|
||||||
|
.ok_or_else(|| Error::HeapBaseNotFoundOrInvalid)?
|
||||||
|
.get();
|
||||||
|
|
||||||
|
match heap_base_val {
|
||||||
|
wasmi::RuntimeValue::I32(v) => Ok(v as u32),
|
||||||
|
_ => Err(Error::HeapBaseNotFoundOrInvalid),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Call a given method in the given wasm-module runtime.
|
||||||
|
fn call_in_wasm_module(
|
||||||
|
ext: &mut dyn Externalities<Blake2Hasher>,
|
||||||
|
module_instance: &ModuleRef,
|
||||||
|
method: &str,
|
||||||
|
data: &[u8],
|
||||||
|
) -> Result<Vec<u8>, Error> {
|
||||||
|
call_in_wasm_module_with_custom_signature(
|
||||||
|
ext,
|
||||||
|
module_instance,
|
||||||
|
method,
|
||||||
|
|alloc| {
|
||||||
|
let offset = alloc(data)?;
|
||||||
|
Ok(vec![I32(offset as i32), I32(data.len() as i32)])
|
||||||
|
},
|
||||||
|
|res, memory| {
|
||||||
|
if let Some(I64(r)) = res {
|
||||||
|
let offset = r as u32;
|
||||||
|
let length = (r as u64 >> 32) as usize;
|
||||||
|
memory.get(offset, length).map_err(|_| Error::Runtime).map(Some)
|
||||||
|
} else {
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Call a given method in the given wasm-module runtime.
|
||||||
|
fn call_in_wasm_module_with_custom_signature<
|
||||||
|
F: FnOnce(&mut dyn FnMut(&[u8]) -> Result<u32, Error>) -> Result<Vec<RuntimeValue>, Error>,
|
||||||
|
FR: FnOnce(Option<RuntimeValue>, &MemoryRef) -> Result<Option<R>, Error>,
|
||||||
|
R,
|
||||||
|
>(
|
||||||
|
ext: &mut dyn Externalities<Blake2Hasher>,
|
||||||
|
module_instance: &ModuleRef,
|
||||||
|
method: &str,
|
||||||
|
create_parameters: F,
|
||||||
|
filter_result: FR,
|
||||||
|
) -> Result<R, Error> {
|
||||||
|
// extract a reference to a linear memory, optional reference to a table
|
||||||
|
// and then initialize FunctionExecutor.
|
||||||
|
let memory = get_mem_instance(module_instance)?;
|
||||||
|
let table: Option<TableRef> = module_instance
|
||||||
|
.export_by_name("__indirect_function_table")
|
||||||
|
.and_then(|e| e.as_table().cloned());
|
||||||
|
let heap_base = get_heap_base(module_instance)?;
|
||||||
|
|
||||||
|
let mut fec = FunctionExecutor::new(
|
||||||
|
memory.clone(),
|
||||||
|
heap_base,
|
||||||
|
table,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let parameters = create_parameters(&mut |data: &[u8]| {
|
||||||
|
let offset = fec.allocate_memory(data.len() as u32)?;
|
||||||
|
fec.write_memory(offset, data).map(|_| offset.into()).map_err(Into::into)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let result = runtime_io::with_externalities(
|
||||||
|
ext,
|
||||||
|
|| module_instance.invoke_export(method, ¶meters, &mut fec),
|
||||||
|
);
|
||||||
|
|
||||||
|
match result {
|
||||||
|
Ok(val) => match filter_result(val, &memory)? {
|
||||||
|
Some(val) => Ok(val),
|
||||||
|
None => Err(Error::InvalidReturn),
|
||||||
|
},
|
||||||
|
Err(e) => {
|
||||||
|
trace!(
|
||||||
|
target: "wasm-executor",
|
||||||
|
"Failed to execute code with {} pages",
|
||||||
|
memory.current_size().0
|
||||||
|
);
|
||||||
|
Err(e.into())
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Prepare module instance
|
||||||
|
fn instantiate_module(
|
||||||
|
heap_pages: usize,
|
||||||
|
module: &Module,
|
||||||
|
) -> Result<ModuleRef, Error> {
|
||||||
|
// start module instantiation. Don't run 'start' function yet.
|
||||||
|
let intermediate_instance = ModuleInstance::new(
|
||||||
|
module,
|
||||||
|
&ImportsBuilder::new()
|
||||||
|
.with_resolver("env", FunctionExecutor::resolver())
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// Verify that the module has the heap base global variable.
|
||||||
|
let _ = get_heap_base(intermediate_instance.not_started_instance())?;
|
||||||
|
|
||||||
|
// Extract a reference to a linear memory.
|
||||||
|
let memory = get_mem_instance(intermediate_instance.not_started_instance())?;
|
||||||
|
memory.grow(Pages(heap_pages)).map_err(|_| Error::Runtime)?;
|
||||||
|
|
||||||
|
if intermediate_instance.has_start() {
|
||||||
|
// Runtime is not allowed to have the `start` function.
|
||||||
|
Err(Error::RuntimeHasStartFn)
|
||||||
|
} else {
|
||||||
|
Ok(intermediate_instance.assert_no_start())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A state snapshot of an instance taken just after instantiation.
|
||||||
|
///
|
||||||
|
/// It is used for restoring the state of the module after execution.
|
||||||
|
#[derive(Clone)]
|
||||||
|
struct StateSnapshot {
|
||||||
|
/// The offset and the content of the memory segments that should be used to restore the snapshot
|
||||||
|
data_segments: Vec<(u32, Vec<u8>)>,
|
||||||
|
/// The list of all global mutable variables of the module in their sequential order.
|
||||||
|
global_mut_values: Vec<RuntimeValue>,
|
||||||
|
heap_pages: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl StateSnapshot {
|
||||||
|
// Returns `None` if instance is not valid.
|
||||||
|
fn take(
|
||||||
|
module_instance: &ModuleRef,
|
||||||
|
data_segments: Vec<DataSegment>,
|
||||||
|
heap_pages: u64,
|
||||||
|
) -> Option<Self> {
|
||||||
|
let prepared_segments = data_segments
|
||||||
|
.into_iter()
|
||||||
|
.map(|mut segment| {
|
||||||
|
// Just replace contents of the segment since the segments will be discarded later
|
||||||
|
// anyway.
|
||||||
|
let contents = mem::replace(segment.value_mut(), vec![]);
|
||||||
|
|
||||||
|
let init_expr = match segment.offset() {
|
||||||
|
Some(offset) => offset.code(),
|
||||||
|
// Return if the segment is passive
|
||||||
|
None => return None
|
||||||
|
};
|
||||||
|
|
||||||
|
// [op, End]
|
||||||
|
if init_expr.len() != 2 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let offset = match init_expr[0] {
|
||||||
|
Instruction::I32Const(v) => v as u32,
|
||||||
|
Instruction::GetGlobal(idx) => {
|
||||||
|
let global_val = module_instance.globals().get(idx as usize)?.get();
|
||||||
|
match global_val {
|
||||||
|
RuntimeValue::I32(v) => v as u32,
|
||||||
|
_ => return None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => return None,
|
||||||
|
};
|
||||||
|
|
||||||
|
Some((offset, contents))
|
||||||
|
})
|
||||||
|
.collect::<Option<Vec<_>>>()?;
|
||||||
|
|
||||||
|
// Collect all values of mutable globals.
|
||||||
|
let global_mut_values = module_instance
|
||||||
|
.globals()
|
||||||
|
.iter()
|
||||||
|
.filter(|g| g.is_mutable())
|
||||||
|
.map(|g| g.get())
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
Some(Self {
|
||||||
|
data_segments: prepared_segments,
|
||||||
|
global_mut_values,
|
||||||
|
heap_pages,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reset the runtime instance to the initial version by restoring
|
||||||
|
/// the preserved memory and globals.
|
||||||
|
///
|
||||||
|
/// Returns `Err` if applying the snapshot is failed.
|
||||||
|
fn apply(&self, instance: &ModuleRef) -> Result<(), WasmError> {
|
||||||
|
let memory = instance
|
||||||
|
.export_by_name("memory")
|
||||||
|
.ok_or(WasmError::ApplySnapshotFailed)?
|
||||||
|
.as_memory()
|
||||||
|
.cloned()
|
||||||
|
.ok_or(WasmError::ApplySnapshotFailed)?;
|
||||||
|
|
||||||
|
// First, erase the memory and copy the data segments into it.
|
||||||
|
memory
|
||||||
|
.erase()
|
||||||
|
.map_err(|_| WasmError::ApplySnapshotFailed)?;
|
||||||
|
for (offset, contents) in &self.data_segments {
|
||||||
|
memory
|
||||||
|
.set(*offset, contents)
|
||||||
|
.map_err(|_| WasmError::ApplySnapshotFailed)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Second, restore the values of mutable globals.
|
||||||
|
for (global_ref, global_val) in instance
|
||||||
|
.globals()
|
||||||
|
.iter()
|
||||||
|
.filter(|g| g.is_mutable())
|
||||||
|
.zip(self.global_mut_values.iter())
|
||||||
|
{
|
||||||
|
// the instance should be the same as used for preserving and
|
||||||
|
// we iterate the same way it as we do it for preserving values that means that the
|
||||||
|
// types should be the same and all the values are mutable. So no error is expected/
|
||||||
|
global_ref
|
||||||
|
.set(*global_val)
|
||||||
|
.map_err(|_| WasmError::ApplySnapshotFailed)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A runtime along with its version and initial state snapshot.
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct WasmiRuntime {
|
||||||
|
/// A wasm module instance.
|
||||||
|
instance: ModuleRef,
|
||||||
|
/// Runtime version according to `Core_version`.
|
||||||
|
///
|
||||||
|
/// Can be `None` if the runtime doesn't expose this function.
|
||||||
|
version: Option<RuntimeVersion>,
|
||||||
|
/// The snapshot of the instance's state taken just after the instantiation.
|
||||||
|
state_snapshot: StateSnapshot,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WasmiRuntime {
|
||||||
|
/// Perform an operation with the clean version of the runtime wasm instance.
|
||||||
|
fn with<R, F>(&self, f: F) -> R
|
||||||
|
where
|
||||||
|
F: FnOnce(&ModuleRef) -> R,
|
||||||
|
{
|
||||||
|
self.state_snapshot.apply(&self.instance).expect(
|
||||||
|
"applying the snapshot can only fail if the passed instance is different
|
||||||
|
from the one that was used for creation of the snapshot;
|
||||||
|
we use the snapshot that is directly associated with the instance;
|
||||||
|
thus the snapshot was created using the instance;
|
||||||
|
qed",
|
||||||
|
);
|
||||||
|
f(&self.instance)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WasmRuntime for WasmiRuntime {
|
||||||
|
fn update_heap_pages(&mut self, heap_pages: u64) -> bool {
|
||||||
|
self.state_snapshot.heap_pages == heap_pages
|
||||||
|
}
|
||||||
|
|
||||||
|
fn call(&mut self, ext: &mut dyn Externalities<Blake2Hasher>, method: &str, data: &[u8])
|
||||||
|
-> Result<Vec<u8>, Error>
|
||||||
|
{
|
||||||
|
self.with(|module| {
|
||||||
|
call_in_wasm_module(ext, module, method, data)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn version(&self) -> Option<RuntimeVersion> {
|
||||||
|
self.version.clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn create_instance<E: Externalities<Blake2Hasher>>(ext: &mut E, code: &[u8], heap_pages: u64)
|
||||||
|
-> Result<WasmiRuntime, WasmError>
|
||||||
|
{
|
||||||
|
let module = Module::from_buffer(&code).map_err(|_| WasmError::InvalidModule)?;
|
||||||
|
|
||||||
|
// Extract the data segments from the wasm code.
|
||||||
|
//
|
||||||
|
// A return of this error actually indicates that there is a problem in logic, since
|
||||||
|
// we just loaded and validated the `module` above.
|
||||||
|
let data_segments = extract_data_segments(&code)?;
|
||||||
|
|
||||||
|
// Instantiate this module.
|
||||||
|
let instance = instantiate_module(heap_pages as usize, &module)
|
||||||
|
.map_err(WasmError::Instantiation)?;
|
||||||
|
|
||||||
|
// Take state snapshot before executing anything.
|
||||||
|
let state_snapshot = StateSnapshot::take(&instance, data_segments, heap_pages)
|
||||||
|
.expect(
|
||||||
|
"`take` returns `Err` if the module is not valid;
|
||||||
|
we already loaded module above, thus the `Module` is proven to be valid at this point;
|
||||||
|
qed
|
||||||
|
",
|
||||||
|
);
|
||||||
|
|
||||||
|
let version = call_in_wasm_module(ext, &instance, "Core_version", &[])
|
||||||
|
.ok()
|
||||||
|
.and_then(|v| RuntimeVersion::decode(&mut v.as_slice()).ok());
|
||||||
|
Ok(WasmiRuntime {
|
||||||
|
instance,
|
||||||
|
version,
|
||||||
|
state_snapshot,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Extract the data segments from the given wasm code.
|
||||||
|
///
|
||||||
|
/// Returns `Err` if the given wasm code cannot be deserialized.
|
||||||
|
fn extract_data_segments(wasm_code: &[u8]) -> Result<Vec<DataSegment>, WasmError> {
|
||||||
|
let raw_module: RawModule = deserialize_buffer(wasm_code)
|
||||||
|
.map_err(|_| WasmError::CantDeserializeWasm)?;
|
||||||
|
|
||||||
|
let segments = raw_module
|
||||||
|
.data_section()
|
||||||
|
.map(|ds| ds.entries())
|
||||||
|
.unwrap_or(&[])
|
||||||
|
.to_vec();
|
||||||
|
Ok(segments)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
use state_machine::TestExternalities as CoreTestExternalities;
|
||||||
|
use hex_literal::hex;
|
||||||
|
use primitives::{blake2_128, blake2_256, ed25519, sr25519, map, Pair};
|
||||||
|
use runtime_test::WASM_BINARY;
|
||||||
|
use substrate_offchain::testing;
|
||||||
|
use trie::{TrieConfiguration, trie_types::Layout};
|
||||||
|
|
||||||
|
type TestExternalities<H> = CoreTestExternalities<H, u64>;
|
||||||
|
|
||||||
|
fn call<E: Externalities<Blake2Hasher>>(
|
||||||
|
ext: &mut E,
|
||||||
|
heap_pages: u64,
|
||||||
|
code: &[u8],
|
||||||
|
method: &str,
|
||||||
|
data: &[u8],
|
||||||
|
) -> Result<Vec<u8>, Error> {
|
||||||
|
let mut instance = create_instance(ext, code, heap_pages)
|
||||||
|
.map_err(|err| err.to_string())?;
|
||||||
|
instance.call(ext, method, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn returning_should_work() {
|
||||||
|
let mut ext = TestExternalities::default();
|
||||||
|
let test_code = WASM_BINARY;
|
||||||
|
|
||||||
|
let output = call(&mut ext, 8, &test_code[..], "test_empty_return", &[]).unwrap();
|
||||||
|
assert_eq!(output, vec![0u8; 0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn panicking_should_work() {
|
||||||
|
let mut ext = TestExternalities::default();
|
||||||
|
let test_code = WASM_BINARY;
|
||||||
|
|
||||||
|
let output = call(&mut ext, 8, &test_code[..], "test_panic", &[]);
|
||||||
|
assert!(output.is_err());
|
||||||
|
|
||||||
|
let output = call(&mut ext, 8, &test_code[..], "test_conditional_panic", &[]);
|
||||||
|
assert_eq!(output.unwrap(), vec![0u8; 0]);
|
||||||
|
|
||||||
|
let output = call(&mut ext, 8, &test_code[..], "test_conditional_panic", &[2]);
|
||||||
|
assert!(output.is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn storage_should_work() {
|
||||||
|
let mut ext = TestExternalities::default();
|
||||||
|
ext.set_storage(b"foo".to_vec(), b"bar".to_vec());
|
||||||
|
let test_code = WASM_BINARY;
|
||||||
|
|
||||||
|
let output = call(&mut ext, 8, &test_code[..], "test_data_in", b"Hello world").unwrap();
|
||||||
|
|
||||||
|
assert_eq!(output, b"all ok!".to_vec());
|
||||||
|
|
||||||
|
let expected = TestExternalities::new((map![
|
||||||
|
b"input".to_vec() => b"Hello world".to_vec(),
|
||||||
|
b"foo".to_vec() => b"bar".to_vec(),
|
||||||
|
b"baz".to_vec() => b"bar".to_vec()
|
||||||
|
], map![]));
|
||||||
|
assert_eq!(ext, expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn clear_prefix_should_work() {
|
||||||
|
let mut ext = TestExternalities::default();
|
||||||
|
ext.set_storage(b"aaa".to_vec(), b"1".to_vec());
|
||||||
|
ext.set_storage(b"aab".to_vec(), b"2".to_vec());
|
||||||
|
ext.set_storage(b"aba".to_vec(), b"3".to_vec());
|
||||||
|
ext.set_storage(b"abb".to_vec(), b"4".to_vec());
|
||||||
|
ext.set_storage(b"bbb".to_vec(), b"5".to_vec());
|
||||||
|
let test_code = WASM_BINARY;
|
||||||
|
|
||||||
|
// This will clear all entries which prefix is "ab".
|
||||||
|
let output = call(&mut ext, 8, &test_code[..], "test_clear_prefix", b"ab").unwrap();
|
||||||
|
|
||||||
|
assert_eq!(output, b"all ok!".to_vec());
|
||||||
|
|
||||||
|
let expected = TestExternalities::new((map![
|
||||||
|
b"aaa".to_vec() => b"1".to_vec(),
|
||||||
|
b"aab".to_vec() => b"2".to_vec(),
|
||||||
|
b"bbb".to_vec() => b"5".to_vec()
|
||||||
|
], map![]));
|
||||||
|
assert_eq!(expected, ext);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn blake2_256_should_work() {
|
||||||
|
let mut ext = TestExternalities::default();
|
||||||
|
let test_code = WASM_BINARY;
|
||||||
|
assert_eq!(
|
||||||
|
call(&mut ext, 8, &test_code[..], "test_blake2_256", &[]).unwrap(),
|
||||||
|
blake2_256(&b""[..]).encode()
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
call(&mut ext, 8, &test_code[..], "test_blake2_256", b"Hello world!").unwrap(),
|
||||||
|
blake2_256(&b"Hello world!"[..]).encode()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn blake2_128_should_work() {
|
||||||
|
let mut ext = TestExternalities::default();
|
||||||
|
let test_code = WASM_BINARY;
|
||||||
|
assert_eq!(
|
||||||
|
call(&mut ext, 8, &test_code[..], "test_blake2_128", &[]).unwrap(),
|
||||||
|
blake2_128(&b""[..]).encode()
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
call(&mut ext, 8, &test_code[..], "test_blake2_128", b"Hello world!").unwrap(),
|
||||||
|
blake2_128(&b"Hello world!"[..]).encode()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn twox_256_should_work() {
|
||||||
|
let mut ext = TestExternalities::default();
|
||||||
|
let test_code = WASM_BINARY;
|
||||||
|
assert_eq!(
|
||||||
|
call(&mut ext, 8, &test_code[..], "test_twox_256", &[]).unwrap(),
|
||||||
|
hex!("99e9d85137db46ef4bbea33613baafd56f963c64b1f3685a4eb4abd67ff6203a"),
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
call(&mut ext, 8, &test_code[..], "test_twox_256", b"Hello world!").unwrap(),
|
||||||
|
hex!("b27dfd7f223f177f2a13647b533599af0c07f68bda23d96d059da2b451a35a74"),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn twox_128_should_work() {
|
||||||
|
let mut ext = TestExternalities::default();
|
||||||
|
let test_code = WASM_BINARY;
|
||||||
|
assert_eq!(
|
||||||
|
call(&mut ext, 8, &test_code[..], "test_twox_128", &[]).unwrap(),
|
||||||
|
hex!("99e9d85137db46ef4bbea33613baafd5")
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
call(&mut ext, 8, &test_code[..], "test_twox_128", b"Hello world!").unwrap(),
|
||||||
|
hex!("b27dfd7f223f177f2a13647b533599af")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn ed25519_verify_should_work() {
|
||||||
|
let mut ext = TestExternalities::<Blake2Hasher>::default();
|
||||||
|
let test_code = WASM_BINARY;
|
||||||
|
let key = ed25519::Pair::from_seed(&blake2_256(b"test"));
|
||||||
|
let sig = key.sign(b"all ok!");
|
||||||
|
let mut calldata = vec![];
|
||||||
|
calldata.extend_from_slice(key.public().as_ref());
|
||||||
|
calldata.extend_from_slice(sig.as_ref());
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
call(&mut ext, 8, &test_code[..], "test_ed25519_verify", &calldata).unwrap(),
|
||||||
|
vec![1]
|
||||||
|
);
|
||||||
|
|
||||||
|
let other_sig = key.sign(b"all is not ok!");
|
||||||
|
let mut calldata = vec![];
|
||||||
|
calldata.extend_from_slice(key.public().as_ref());
|
||||||
|
calldata.extend_from_slice(other_sig.as_ref());
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
call(&mut ext, 8, &test_code[..], "test_ed25519_verify", &calldata).unwrap(),
|
||||||
|
vec![0]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn sr25519_verify_should_work() {
|
||||||
|
let mut ext = TestExternalities::<Blake2Hasher>::default();
|
||||||
|
let test_code = WASM_BINARY;
|
||||||
|
let key = sr25519::Pair::from_seed(&blake2_256(b"test"));
|
||||||
|
let sig = key.sign(b"all ok!");
|
||||||
|
let mut calldata = vec![];
|
||||||
|
calldata.extend_from_slice(key.public().as_ref());
|
||||||
|
calldata.extend_from_slice(sig.as_ref());
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
call(&mut ext, 8, &test_code[..], "test_sr25519_verify", &calldata).unwrap(),
|
||||||
|
vec![1]
|
||||||
|
);
|
||||||
|
|
||||||
|
let other_sig = key.sign(b"all is not ok!");
|
||||||
|
let mut calldata = vec![];
|
||||||
|
calldata.extend_from_slice(key.public().as_ref());
|
||||||
|
calldata.extend_from_slice(other_sig.as_ref());
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
call(&mut ext, 8, &test_code[..], "test_sr25519_verify", &calldata).unwrap(),
|
||||||
|
vec![0]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn ordered_trie_root_should_work() {
|
||||||
|
let mut ext = TestExternalities::<Blake2Hasher>::default();
|
||||||
|
let trie_input = vec![b"zero".to_vec(), b"one".to_vec(), b"two".to_vec()];
|
||||||
|
let test_code = WASM_BINARY;
|
||||||
|
assert_eq!(
|
||||||
|
call(&mut ext, 8, &test_code[..], "test_ordered_trie_root", &[]).unwrap(),
|
||||||
|
Layout::<Blake2Hasher>::ordered_trie_root(trie_input.iter()).as_fixed_bytes().encode()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn offchain_local_storage_should_work() {
|
||||||
|
use substrate_client::backend::OffchainStorage;
|
||||||
|
|
||||||
|
let mut ext = TestExternalities::<Blake2Hasher>::default();
|
||||||
|
let (offchain, state) = testing::TestOffchainExt::new();
|
||||||
|
ext.set_offchain_externalities(offchain);
|
||||||
|
let test_code = WASM_BINARY;
|
||||||
|
assert_eq!(
|
||||||
|
call(&mut ext, 8, &test_code[..], "test_offchain_local_storage", &[]).unwrap(),
|
||||||
|
vec![0]
|
||||||
|
);
|
||||||
|
assert_eq!(state.read().persistent_storage.get(b"", b"test"), Some(vec![]));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn offchain_http_should_work() {
|
||||||
|
let mut ext = TestExternalities::<Blake2Hasher>::default();
|
||||||
|
let (offchain, state) = testing::TestOffchainExt::new();
|
||||||
|
ext.set_offchain_externalities(offchain);
|
||||||
|
state.write().expect_request(
|
||||||
|
0,
|
||||||
|
testing::PendingRequest {
|
||||||
|
method: "POST".into(),
|
||||||
|
uri: "http://localhost:12345".into(),
|
||||||
|
body: vec![1, 2, 3, 4],
|
||||||
|
headers: vec![("X-Auth".to_owned(), "test".to_owned())],
|
||||||
|
sent: true,
|
||||||
|
response: Some(vec![1, 2, 3]),
|
||||||
|
response_headers: vec![("X-Auth".to_owned(), "hello".to_owned())],
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
let test_code = WASM_BINARY;
|
||||||
|
assert_eq!(
|
||||||
|
call(&mut ext, 8, &test_code[..], "test_offchain_http", &[]).unwrap(),
|
||||||
|
vec![0]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -167,7 +167,10 @@ where TGen: RuntimeGenesis, TCSExt: Extension {
|
|||||||
pruning: config.pruning.clone(),
|
pruning: config.pruning.clone(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let executor = NativeExecutor::<TExecDisp>::new(config.default_heap_pages);
|
let executor = NativeExecutor::<TExecDisp>::new(
|
||||||
|
config.wasm_method,
|
||||||
|
config.default_heap_pages,
|
||||||
|
);
|
||||||
|
|
||||||
let fork_blocks = config.chain_spec
|
let fork_blocks = config.chain_spec
|
||||||
.extensions()
|
.extensions()
|
||||||
@@ -239,7 +242,10 @@ where TGen: RuntimeGenesis, TCSExt: Extension {
|
|||||||
pruning: config.pruning.clone(),
|
pruning: config.pruning.clone(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let executor = NativeExecutor::<TExecDisp>::new(config.default_heap_pages);
|
let executor = NativeExecutor::<TExecDisp>::new(
|
||||||
|
config.wasm_method,
|
||||||
|
config.default_heap_pages,
|
||||||
|
);
|
||||||
|
|
||||||
let db_storage = client_db::light::LightStorage::new(db_settings)?;
|
let db_storage = client_db::light::LightStorage::new(db_settings)?;
|
||||||
let light_blockchain = client::light::new_light_blockchain(db_storage);
|
let light_blockchain = client::light::new_light_blockchain(db_storage);
|
||||||
|
|||||||
@@ -19,6 +19,7 @@
|
|||||||
pub use client::ExecutionStrategies;
|
pub use client::ExecutionStrategies;
|
||||||
pub use client_db::PruningMode;
|
pub use client_db::PruningMode;
|
||||||
pub use network::config::{ExtTransport, NetworkConfiguration, Roles};
|
pub use network::config::{ExtTransport, NetworkConfiguration, Roles};
|
||||||
|
pub use substrate_executor::WasmExecutionMethod;
|
||||||
|
|
||||||
use std::{path::PathBuf, net::SocketAddr};
|
use std::{path::PathBuf, net::SocketAddr};
|
||||||
use transaction_pool;
|
use transaction_pool;
|
||||||
@@ -60,6 +61,8 @@ pub struct Configuration<C, G, E = NoExtension> {
|
|||||||
pub custom: C,
|
pub custom: C,
|
||||||
/// Node name.
|
/// Node name.
|
||||||
pub name: String,
|
pub name: String,
|
||||||
|
/// Wasm execution method.
|
||||||
|
pub wasm_method: WasmExecutionMethod,
|
||||||
/// Execution strategies.
|
/// Execution strategies.
|
||||||
pub execution_strategies: ExecutionStrategies,
|
pub execution_strategies: ExecutionStrategies,
|
||||||
/// RPC over HTTP binding address. `None` if disabled.
|
/// RPC over HTTP binding address. `None` if disabled.
|
||||||
@@ -116,6 +119,7 @@ impl<C, G, E> Configuration<C, G, E> where
|
|||||||
state_cache_child_ratio: Default::default(),
|
state_cache_child_ratio: Default::default(),
|
||||||
custom: Default::default(),
|
custom: Default::default(),
|
||||||
pruning: PruningMode::default(),
|
pruning: PruningMode::default(),
|
||||||
|
wasm_method: WasmExecutionMethod::Interpreted,
|
||||||
execution_strategies: Default::default(),
|
execution_strategies: Default::default(),
|
||||||
rpc_http: None,
|
rpc_http: None,
|
||||||
rpc_ws: None,
|
rpc_ws: None,
|
||||||
|
|||||||
@@ -178,6 +178,7 @@ fn node_config<G, E: Clone> (
|
|||||||
chain_spec: (*spec).clone(),
|
chain_spec: (*spec).clone(),
|
||||||
custom: Default::default(),
|
custom: Default::default(),
|
||||||
name: format!("Node {}", index),
|
name: format!("Node {}", index),
|
||||||
|
wasm_method: service::config::WasmExecutionMethod::Interpreted,
|
||||||
execution_strategies: Default::default(),
|
execution_strategies: Default::default(),
|
||||||
rpc_http: None,
|
rpc_http: None,
|
||||||
rpc_ws: None,
|
rpc_ws: None,
|
||||||
|
|||||||
@@ -186,7 +186,7 @@ fn record_proof_works() {
|
|||||||
|
|
||||||
// Use the proof backend to execute `execute_block`.
|
// Use the proof backend to execute `execute_block`.
|
||||||
let mut overlay = Default::default();
|
let mut overlay = Default::default();
|
||||||
let executor = NativeExecutor::<LocalExecutor>::new(None);
|
let executor = NativeExecutor::<LocalExecutor>::new(WasmExecutionMethod::Interpreted, None);
|
||||||
execution_proof_check_on_trie_backend(
|
execution_proof_check_on_trie_backend(
|
||||||
&backend,
|
&backend,
|
||||||
&mut overlay,
|
&mut overlay,
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ pub use client::{ExecutionStrategies, blockchain, backend, self};
|
|||||||
pub use client_db::{Backend, self};
|
pub use client_db::{Backend, self};
|
||||||
pub use client_ext::ClientExt;
|
pub use client_ext::ClientExt;
|
||||||
pub use consensus;
|
pub use consensus;
|
||||||
pub use executor::{NativeExecutor, self};
|
pub use executor::{NativeExecutor, WasmExecutionMethod, self};
|
||||||
pub use keyring::{
|
pub use keyring::{
|
||||||
AccountKeyring,
|
AccountKeyring,
|
||||||
ed25519::Keyring as Ed25519Keyring,
|
ed25519::Keyring as Ed25519Keyring,
|
||||||
@@ -198,7 +198,7 @@ impl<Executor, Backend, G: GenesisInit> TestClientBuilder<Executor, Backend, G>
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<E, Backend, G: GenesisInit> TestClientBuilder<
|
impl<E, Backend, G: GenesisInit> TestClientBuilder<
|
||||||
client::LocalCallExecutor<Backend, executor::NativeExecutor<E>>,
|
client::LocalCallExecutor<Backend, NativeExecutor<E>>,
|
||||||
Backend,
|
Backend,
|
||||||
G,
|
G,
|
||||||
> {
|
> {
|
||||||
@@ -209,18 +209,20 @@ impl<E, Backend, G: GenesisInit> TestClientBuilder<
|
|||||||
) -> (
|
) -> (
|
||||||
client::Client<
|
client::Client<
|
||||||
Backend,
|
Backend,
|
||||||
client::LocalCallExecutor<Backend, executor::NativeExecutor<E>>,
|
client::LocalCallExecutor<Backend, NativeExecutor<E>>,
|
||||||
Block,
|
Block,
|
||||||
RuntimeApi
|
RuntimeApi
|
||||||
>,
|
>,
|
||||||
client::LongestChain<Backend, Block>,
|
client::LongestChain<Backend, Block>,
|
||||||
) where
|
) where
|
||||||
I: Into<Option<executor::NativeExecutor<E>>>,
|
I: Into<Option<NativeExecutor<E>>>,
|
||||||
E: executor::NativeExecutionDispatch,
|
E: executor::NativeExecutionDispatch,
|
||||||
Backend: client::backend::Backend<Block, Blake2Hasher>,
|
Backend: client::backend::Backend<Block, Blake2Hasher>,
|
||||||
Block: BlockT<Hash=<Blake2Hasher as Hasher>::Out>,
|
Block: BlockT<Hash=<Blake2Hasher as Hasher>::Out>,
|
||||||
{
|
{
|
||||||
let executor = executor.into().unwrap_or_else(|| executor::NativeExecutor::new(None));
|
let executor = executor.into().unwrap_or_else(||
|
||||||
|
NativeExecutor::new(WasmExecutionMethod::Interpreted, None)
|
||||||
|
);
|
||||||
let executor = LocalCallExecutor::new(self.backend.clone(), executor, self.keystore.take());
|
let executor = LocalCallExecutor::new(self.backend.clone(), executor, self.keystore.take());
|
||||||
|
|
||||||
self.build_with_executor(executor)
|
self.build_with_executor(executor)
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ pub mod prelude {
|
|||||||
// Client structs
|
// Client structs
|
||||||
pub use super::{
|
pub use super::{
|
||||||
TestClient, TestClientBuilder, Backend, LightBackend,
|
TestClient, TestClientBuilder, Backend, LightBackend,
|
||||||
Executor, LightExecutor, LocalExecutor, NativeExecutor,
|
Executor, LightExecutor, LocalExecutor, NativeExecutor, WasmExecutionMethod,
|
||||||
};
|
};
|
||||||
// Keyring
|
// Keyring
|
||||||
pub use super::{AccountKeyring, Sr25519Keyring};
|
pub use super::{AccountKeyring, Sr25519Keyring};
|
||||||
@@ -261,7 +261,7 @@ pub fn new_light() -> (
|
|||||||
let storage = client_db::light::LightStorage::new_test();
|
let storage = client_db::light::LightStorage::new_test();
|
||||||
let blockchain = Arc::new(client::light::blockchain::Blockchain::new(storage));
|
let blockchain = Arc::new(client::light::blockchain::Blockchain::new(storage));
|
||||||
let backend = Arc::new(LightBackend::new(blockchain.clone()));
|
let backend = Arc::new(LightBackend::new(blockchain.clone()));
|
||||||
let executor = NativeExecutor::new(None);
|
let executor = NativeExecutor::new(WasmExecutionMethod::Interpreted, None);
|
||||||
let local_call_executor = client::LocalCallExecutor::new(backend.clone(), executor, None);
|
let local_call_executor = client::LocalCallExecutor::new(backend.clone(), executor, None);
|
||||||
let call_executor = LightExecutor::new(
|
let call_executor = LightExecutor::new(
|
||||||
backend.clone(),
|
backend.clone(),
|
||||||
|
|||||||
@@ -322,8 +322,19 @@ mod tests {
|
|||||||
use runtime_io::{with_externalities, TestExternalities};
|
use runtime_io::{with_externalities, TestExternalities};
|
||||||
use substrate_test_runtime_client::{AccountKeyring, Sr25519Keyring};
|
use substrate_test_runtime_client::{AccountKeyring, Sr25519Keyring};
|
||||||
use crate::{Header, Transfer, WASM_BINARY};
|
use crate::{Header, Transfer, WASM_BINARY};
|
||||||
use primitives::{Blake2Hasher, map};
|
use primitives::{Blake2Hasher, NeverNativeValue, map, traits::CodeExecutor};
|
||||||
use substrate_executor::WasmExecutor;
|
use substrate_executor::{NativeExecutor, WasmExecutionMethod, native_executor_instance};
|
||||||
|
|
||||||
|
// Declare an instance of the native executor dispatch for the test runtime.
|
||||||
|
native_executor_instance!(
|
||||||
|
NativeDispatch,
|
||||||
|
crate::api::dispatch,
|
||||||
|
crate::native_version
|
||||||
|
);
|
||||||
|
|
||||||
|
fn executor() -> NativeExecutor<NativeDispatch> {
|
||||||
|
NativeExecutor::new(WasmExecutionMethod::Interpreted, None)
|
||||||
|
}
|
||||||
|
|
||||||
fn new_test_ext() -> TestExternalities<Blake2Hasher> {
|
fn new_test_ext() -> TestExternalities<Blake2Hasher> {
|
||||||
let authorities = vec![
|
let authorities = vec![
|
||||||
@@ -331,13 +342,19 @@ mod tests {
|
|||||||
Sr25519Keyring::Bob.to_raw_public(),
|
Sr25519Keyring::Bob.to_raw_public(),
|
||||||
Sr25519Keyring::Charlie.to_raw_public()
|
Sr25519Keyring::Charlie.to_raw_public()
|
||||||
];
|
];
|
||||||
TestExternalities::new((map![
|
TestExternalities::new_with_code(
|
||||||
twox_128(b"latest").to_vec() => vec![69u8; 32],
|
WASM_BINARY,
|
||||||
twox_128(b"sys:auth").to_vec() => authorities.encode(),
|
(
|
||||||
blake2_256(&AccountKeyring::Alice.to_raw_public().to_keyed_vec(b"balance:")).to_vec() => {
|
map![
|
||||||
vec![111u8, 0, 0, 0, 0, 0, 0, 0]
|
twox_128(b"latest").to_vec() => vec![69u8; 32],
|
||||||
}
|
twox_128(b"sys:auth").to_vec() => authorities.encode(),
|
||||||
], map![]))
|
blake2_256(&AccountKeyring::Alice.to_raw_public().to_keyed_vec(b"balance:")).to_vec() => {
|
||||||
|
vec![111u8, 0, 0, 0, 0, 0, 0, 0]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
map![],
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn block_import_works<F>(block_executor: F) where F: Fn(Block, &mut TestExternalities<Blake2Hasher>) {
|
fn block_import_works<F>(block_executor: F) where F: Fn(Block, &mut TestExternalities<Blake2Hasher>) {
|
||||||
@@ -370,7 +387,13 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn block_import_works_wasm() {
|
fn block_import_works_wasm() {
|
||||||
block_import_works(|b, ext| {
|
block_import_works(|b, ext| {
|
||||||
WasmExecutor::new().call(ext, 8, &WASM_BINARY, "Core_execute_block", &b.encode()).unwrap();
|
executor().call::<_, NeverNativeValue, fn() -> _>(
|
||||||
|
ext,
|
||||||
|
"Core_execute_block",
|
||||||
|
&b.encode(),
|
||||||
|
false,
|
||||||
|
None,
|
||||||
|
).0.unwrap();
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -458,7 +481,13 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn block_import_with_transaction_works_wasm() {
|
fn block_import_with_transaction_works_wasm() {
|
||||||
block_import_with_transaction_works(|b, ext| {
|
block_import_with_transaction_works(|b, ext| {
|
||||||
WasmExecutor::new().call(ext, 8, &WASM_BINARY, "Core_execute_block", &b.encode()).unwrap();
|
executor().call::<_, NeverNativeValue, fn() -> _>(
|
||||||
|
ext,
|
||||||
|
"Core_execute_block",
|
||||||
|
&b.encode(),
|
||||||
|
false,
|
||||||
|
None,
|
||||||
|
).0.unwrap();
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,7 +22,6 @@
|
|||||||
#[cfg(feature = "benchmarks")] extern crate test;
|
#[cfg(feature = "benchmarks")] extern crate test;
|
||||||
|
|
||||||
pub use substrate_executor::NativeExecutor;
|
pub use substrate_executor::NativeExecutor;
|
||||||
pub use substrate_executor::RuntimesCache;
|
|
||||||
use substrate_executor::native_executor_instance;
|
use substrate_executor::native_executor_instance;
|
||||||
|
|
||||||
// Declare an instance of the native executor named `Executor`. Include the wasm binary as the
|
// Declare an instance of the native executor named `Executor`. Include the wasm binary as the
|
||||||
@@ -53,6 +52,7 @@ mod tests {
|
|||||||
transaction_validity::InvalidTransaction, weights::{WeightMultiplier, GetDispatchInfo},
|
transaction_validity::InvalidTransaction, weights::{WeightMultiplier, GetDispatchInfo},
|
||||||
};
|
};
|
||||||
use contracts::ContractAddressFor;
|
use contracts::ContractAddressFor;
|
||||||
|
use substrate_executor::{NativeExecutor, WasmExecutionMethod};
|
||||||
use system::{EventRecord, Phase};
|
use system::{EventRecord, Phase};
|
||||||
use node_runtime::{
|
use node_runtime::{
|
||||||
Header, Block, UncheckedExtrinsic, CheckedExtrinsic, Call, Runtime, Balances, BuildStorage,
|
Header, Block, UncheckedExtrinsic, CheckedExtrinsic, Call, Runtime, Balances, BuildStorage,
|
||||||
@@ -117,8 +117,8 @@ mod tests {
|
|||||||
Header::new(n, Default::default(), Default::default(), [69; 32].into(), Default::default())
|
Header::new(n, Default::default(), Default::default(), [69; 32].into(), Default::default())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn executor() -> ::substrate_executor::NativeExecutor<Executor> {
|
fn executor() -> NativeExecutor<Executor> {
|
||||||
substrate_executor::NativeExecutor::new(None)
|
NativeExecutor::new(WasmExecutionMethod::Interpreted, None)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_heap_pages<E: Externalities<Blake2Hasher>>(ext: &mut E, heap_pages: u64) {
|
fn set_heap_pages<E: Externalities<Blake2Hasher>>(ext: &mut E, heap_pages: u64) {
|
||||||
|
|||||||
Reference in New Issue
Block a user