Skip to content
This repository was archived by the owner on Nov 15, 2023. It is now read-only.
Closed
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions core/executor/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,13 @@ mod wasm_executor;
mod native_executor;
mod sandbox;
mod allocator;
mod wasm_runtimes_cache;

pub mod error;
pub use wasmi;
pub use wasm_executor::WasmExecutor;
pub use native_executor::{with_native_environment, NativeExecutor, NativeExecutionDispatch};
pub use wasm_runtimes_cache::RuntimesCache;
pub use state_machine::Externalities;
pub use runtime_version::{RuntimeVersion, NativeVersion};
pub use parity_codec::Codec;
Expand Down
96 changes: 18 additions & 78 deletions core/executor/src/native_executor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,86 +14,23 @@
// You should have received a copy of the GNU General Public License
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.

use std::{borrow::BorrowMut, result, cell::{RefMut, RefCell}};
use std::{result, cell::RefCell, panic::UnwindSafe};
use crate::error::{Error, Result};
use state_machine::{CodeExecutor, Externalities};
use crate::wasm_executor::WasmExecutor;
use wasmi::{Module as WasmModule, ModuleRef as WasmModuleInstanceRef};
use runtime_version::{NativeVersion, RuntimeVersion};
use std::{collections::HashMap, panic::UnwindSafe};
use parity_codec::{Decode, Encode};
use crate::RuntimeInfo;
use primitives::{Blake2Hasher, NativeOrEncoded};
use primitives::storage::well_known_keys;
use log::trace;

/// Default num of pages for the heap
const DEFAULT_HEAP_PAGES: u64 = 1024;
use crate::RuntimesCache;

// For the internal Runtime Cache:
// Is it compatible enough to run this natively or do we need to fall back on the WasmModule

enum RuntimePreproc {
InvalidCode,
ValidCode(WasmModuleInstanceRef, Option<RuntimeVersion>),
}

type CacheType = HashMap<[u8; 32], RuntimePreproc>;

thread_local! {
static RUNTIMES_CACHE: RefCell<CacheType> = RefCell::new(HashMap::new());
}

/// fetch a runtime version from the cache or if there is no cached version yet, create
/// the runtime version entry for `code`, determines whether `Compatibility::IsCompatible`
/// can be used by comparing returned RuntimeVersion to `ref_version`
fn fetch_cached_runtime_version<'a, E: Externalities<Blake2Hasher>>(
wasm_executor: &WasmExecutor,
cache: &'a mut RefMut<CacheType>,
ext: &mut E,
default_heap_pages: Option<u64>,
) -> Result<(&'a WasmModuleInstanceRef, &'a Option<RuntimeVersion>)> {
let code_hash = match ext.original_storage_hash(well_known_keys::CODE) {
Some(code_hash) => code_hash,
None => return Err(Error::InvalidCode(vec![])),
};

let maybe_runtime_preproc = cache.borrow_mut().entry(code_hash.into())
.or_insert_with(|| {
let code = match ext.original_storage(well_known_keys::CODE) {
Some(code) => code,
None => return RuntimePreproc::InvalidCode,
};
let heap_pages = ext.storage(well_known_keys::HEAP_PAGES)
.and_then(|pages| u64::decode(&mut &pages[..]))
.or(default_heap_pages)
.unwrap_or(DEFAULT_HEAP_PAGES);
match WasmModule::from_buffer(code)
.map_err(|_| Error::InvalidCode(vec![]))
.and_then(|module| wasm_executor.prepare_module(ext, heap_pages as usize, &module))
{
Ok(module) => {
let version = wasm_executor.call_in_wasm_module(ext, &module, "Core_version", &[])
.ok()
.and_then(|v| RuntimeVersion::decode(&mut v.as_slice()));
RuntimePreproc::ValidCode(module, version)
}
Err(e) => {
trace!(target: "executor", "Invalid code presented to executor ({:?})", e);
RuntimePreproc::InvalidCode
}
}
});

match maybe_runtime_preproc {
RuntimePreproc::InvalidCode => {
let code = ext.original_storage(well_known_keys::CODE).unwrap_or(vec![]);
Err(Error::InvalidCode(code))
},
RuntimePreproc::ValidCode(m, v) => {
Ok((m, v))
}
}
static RUNTIMES_CACHE: RefCell<RuntimesCache> = RefCell::new(RuntimesCache::new());
}

fn safe_call<F, U>(f: F) -> Result<U>
Expand Down Expand Up @@ -176,10 +113,11 @@ impl<D: NativeExecutionDispatch> RuntimeInfo for NativeExecutor<D> {
&self,
ext: &mut E,
) -> Option<RuntimeVersion> {
RUNTIMES_CACHE.with(|c|
fetch_cached_runtime_version(&self.fallback, &mut c.borrow_mut(), ext, self.default_heap_pages)
RUNTIMES_CACHE.with(|cache| {
let cache = &mut cache.borrow_mut();
cache.fetch_runtime(&self.fallback, ext, self.default_heap_pages, None)
.ok()?.1.clone()
)
})
}
}

Expand All @@ -198,13 +136,15 @@ impl<D: NativeExecutionDispatch> CodeExecutor<Blake2Hasher> for NativeExecutor<D
data: &[u8],
use_native: bool,
native_call: Option<NC>,
) -> (Result<NativeOrEncoded<R>>, bool) {
RUNTIMES_CACHE.with(|c| {
let mut c = c.borrow_mut();
let (module, onchain_version) = match fetch_cached_runtime_version(
&self.fallback, &mut c, ext, self.default_heap_pages) {
Ok((module, onchain_version)) => (module, onchain_version),
Err(e) => return (Err(e), false),
) -> (Result<NativeOrEncoded<R>>, bool){
RUNTIMES_CACHE.with(|cache| {
let cache = &mut cache.borrow_mut();
let (module, onchain_version) = match cache.fetch_runtime(
&self.fallback, ext, self.default_heap_pages,
Some(&self.native_version.runtime_version)
) {
Ok((module, onchain_version)) => (module, onchain_version),
Err(e) => return (Err(e), false),
};
match (
use_native,
Expand All @@ -224,15 +164,15 @@ impl<D: NativeExecutionDispatch> CodeExecutor<Blake2Hasher> for NativeExecutor<D
);
(
self.fallback
.call_in_wasm_module(ext, module, method, data)
.call_in_wasm_module(ext, &module, method, data)
.map(NativeOrEncoded::Encoded),
false
)
}
(false, _, _) => {
(
self.fallback
.call_in_wasm_module(ext, module, method, data)
.call_in_wasm_module(ext, &module, method, data)
.map(NativeOrEncoded::Encoded),
false
)
Expand Down
9 changes: 0 additions & 9 deletions core/executor/src/wasm_executor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1290,8 +1290,6 @@ impl WasmExecutor {
.export_by_name("__indirect_function_table")
.and_then(|e| e.as_table().cloned());

let low = memory.lowest_used();
let used_mem = memory.used_size();
let mut fec = FunctionExecutor::new(memory.clone(), table, ext)?;
let parameters = create_parameters(&mut |data: &[u8]| {
let offset = fec.heap.allocate(data.len() as u32)?;
Expand All @@ -1315,13 +1313,6 @@ impl WasmExecutor {
},
};

// cleanup module instance for next use
let new_low = memory.lowest_used();
if new_low < low {
memory.zero(new_low as usize, (low - new_low) as usize)?;
memory.reset_lowest_used(low);
}
memory.with_direct_access_mut(|buf| buf.resize(used_mem.0, 0));
result
}

Expand Down
185 changes: 185 additions & 0 deletions core/executor/src/wasm_runtimes_cache.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
// 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/>.

//! Implements a cache for pre-created Wasm runtime module instances.

use crate::error::{Error, Result};
use crate::wasm_executor::WasmExecutor;
use log::trace;
use parity_codec::Decode;
use primitives::Blake2Hasher;
use primitives::storage::well_known_keys;
use runtime_version::RuntimeVersion;
use state_machine::Externalities;
use wasmi::{Module as WasmModule, ModuleRef as WasmModuleInstanceRef};

#[derive(Clone)]
enum RuntimePreproc {
InvalidCode,
ValidCode(WasmModuleInstanceRef, Option<RuntimeVersion>),
}

#[derive(Debug)]
enum Action {
Clone,
Invalid,
Reset,
}

/// Default num of pages for the heap
const DEFAULT_HEAP_PAGES: u64 = 1024;

/// Cache a runtime instances. When an instance is requested,
/// the template instance is cloned synchronously.
pub struct RuntimesCache {
template_instance: Option<RuntimePreproc>,
}

impl RuntimesCache {
/// Creates a new instance of a runtimes cache.
pub fn new() -> RuntimesCache {
RuntimesCache {
template_instance: None,
}
}

/// Fetch a runtime instance from a template instance. If there is no template
/// instance yet, initialization happens automatically.
///
/// # 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 "template instance", which will be cloned when this method
/// is called. The parameter is only needed to call into he Wasm module to
/// find out the `Core_version`.
///
/// `default_heap_pages` - Default number of 64KB pages to allocate for
/// Wasm execution. Defaults to `DEFAULT_HEAP_PAGES` if `None` is provided.
///
/// `maybe_requested_version` - If `Some(RuntimeVersion)` is provided the
/// template instance will be checked for compatibility. In case of
/// incompatibility the template instance will be reset.
///
/// # Return value
///
/// If no error occurred a `RuntimePreproc::ValidCode` is returned, containing
/// a wasmi `ModuleRef` with an optional `RuntimeVersion` (if the call
/// `Core_version` returned a version).
///
/// In case an error occurred `RuntimePreproc::InvalidCode` is returned with an
/// appropriate description.
pub fn fetch_runtime<E: Externalities<Blake2Hasher>>(
&mut self,
wasm_executor: &WasmExecutor,
ext: &mut E,
default_heap_pages: Option<u64>,
maybe_requested_version: Option<&RuntimeVersion>,
) -> Result<(WasmModuleInstanceRef, Option<RuntimeVersion>)> {
if let None = self.template_instance {
trace!(target: "runtimes_cache",
"no template instance found, creating now.");
let template =
self.create_wasm_instance(wasm_executor, ext, default_heap_pages);
self.template_instance = Some(template);
}

let action = match maybe_requested_version {
None => Action::Clone,
Some(ref version) => {
match self.template_instance {
None => Action::Clone,
Some(RuntimePreproc::InvalidCode) => Action::Invalid,
Some(RuntimePreproc::ValidCode(_, None)) => Action::Reset,
Some(RuntimePreproc::ValidCode(_, Some(ref template_version))) => {
if template_version.can_call_with(&version) {
Action::Clone
} else {
trace!(target: "runtimes_cache",
"resetting cache because new version received");
Action::Reset
}
},
}
},
};

let runtime_preproc = match action {
Action::Clone => {
self.template_instance
.clone()
.expect("if non-existent, template was created at beginning of function; qed")
},
Action::Reset => {
let new_template = self.create_wasm_instance(wasm_executor, ext, default_heap_pages);
self.template_instance = Some(new_template);
self.template_instance
.clone()
.expect("template was created right before; qed")
},
Action::Invalid => {
RuntimePreproc::InvalidCode
}
};

match runtime_preproc {
RuntimePreproc::InvalidCode => {
let code = ext.original_storage(well_known_keys::CODE).unwrap_or(vec![]);
Err(Error::InvalidCode(code))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe a little bit offtopic, but I wonder what is the point of returning the code here?

Copy link
Contributor Author

@cmichi cmichi Jun 28, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This error enum is defined here and requires the code, I just extracted this part from native_executor. Not sure about the reasoning, but since the original code (and mine as well) later outputs the code in a trace! this could have been a reason.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure about usability of this and I am going to remove this.

},
RuntimePreproc::ValidCode(r, v) => {
Ok((r, v))
}
}
}

fn create_wasm_instance<E: Externalities<Blake2Hasher>>(
&self,
wasm_executor: &WasmExecutor,
ext: &mut E,
default_heap_pages: Option<u64>,
) -> RuntimePreproc {
let code = match ext.original_storage(well_known_keys::CODE) {
Some(code) => code,
None => return RuntimePreproc::InvalidCode,
};

let heap_pages = ext.storage(well_known_keys::HEAP_PAGES)
.and_then(|pages| u64::decode(&mut &pages[..]))
.or(default_heap_pages)
.unwrap_or(DEFAULT_HEAP_PAGES);

match WasmModule::from_buffer(code)
.map_err(|_| Error::InvalidCode(vec![]))
.and_then(|module| wasm_executor.prepare_module(ext, heap_pages as usize, &module))
{
Ok(module) => {
let version = wasm_executor.call_in_wasm_module(ext, &module, "Core_version", &[])
.ok()
.and_then(|v| {
RuntimeVersion::decode(&mut v.as_slice())
});
RuntimePreproc::ValidCode(module, version)
}
Err(e) => {
trace!(target: "runtimes_cache", "Invalid code presented to executor ({:?})", e);
RuntimePreproc::InvalidCode
}
}
}
}
14 changes: 14 additions & 0 deletions core/sr-api-macros/tests/runtime_calls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

use test_client::{
prelude::*,
DefaultTestClientBuilderExt, TestClientBuilder,
runtime::{TestAPI, DecodeFails, Transfer, Header},
};
use runtime_primitives::{
Expand Down Expand Up @@ -191,3 +192,16 @@ fn record_proof_works() {
&block.encode(),
).expect("Executes block while using the proof backend");
}

#[test]
fn returns_mutable_static() {
let client = TestClientBuilder::new().set_execution_strategy(ExecutionStrategy::AlwaysWasm).build();
let runtime_api = client.runtime_api();
let block_id = BlockId::Number(client.info().chain.best_number);

let ret = runtime_api.returns_mutable_static(&block_id).unwrap();
assert_eq!(ret, 33);

let ret = runtime_api.returns_mutable_static(&block_id).unwrap();
assert_eq!(ret, 34);
}
Loading