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 1 commit
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
Prev Previous commit
Next Next commit
Replace runtime cache with synchronous clone
  • Loading branch information
cmichi committed Jun 25, 2019
commit ccba1effabacd4920b9a5fae5c525aa0015ed76e
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
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
}
}
}
}
Loading