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 all 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
6 changes: 5 additions & 1 deletion polkadot/cli/src/cli.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -74,7 +74,7 @@ args:
- pruning:
long: pruning
value_name: PRUNING_MODE
help: Specify the pruning mode. (a number of blocks to keep or "archive"). Default is 256.
help: Specify the pruning mode, a number of blocks to keep or "archive". Default is 256.
takes_value: true
- name:
long: name
Expand All@@ -95,6 +95,10 @@ args:
value_name: TELEMETRY_URL
help: The URL of the telemetry server. Implies --telemetry
takes_value: true
- execution:
long: execution
value_name: STRATEGY
help: The means of execution used when calling into the runtime. Can be either wasm, native or both.
subcommands:
- build-spec:
about: Build a spec.json file, outputing to stdout
Expand Down
16 changes: 15 additions & 1 deletion polkadot/cli/src/lib.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -218,18 +218,32 @@ pub fn run<I, T, W>(args: I, worker: W) -> error::Result<()> where
if matches.is_present("collator") {
info!("Starting collator");
// TODO [rob]: collation node implementation
service::Role::FULL
// This isn't a thing. Different parachains will have their own collator executables and
// maybe link to libpolkadot to get a light-client.
service::Role::LIGHT
} else if matches.is_present("light") {
info!("Starting (light)");
config.execution_strategy = service::ExecutionStrategy::NativeWhenPossible;
service::Role::LIGHT
} else if matches.is_present("validator") || matches.is_present("dev") {
info!("Starting validator");
config.execution_strategy = service::ExecutionStrategy::Both;
service::Role::AUTHORITY
} else {
info!("Starting (heavy)");
config.execution_strategy = service::ExecutionStrategy::NativeWhenPossible;
service::Role::FULL
};

if let Some(s) = matches.value_of("pruning") {
config.execution_strategy = match s {
"both" => service::ExecutionStrategy::Both,
"native" => service::ExecutionStrategy::NativeWhenPossible,
"wasm" => service::ExecutionStrategy::AlwaysWasm,
_ => return Err(error::ErrorKind::Input("Invalid execution mode specified".to_owned()).into()),
};
}

config.roles = role;
{
config.network.boot_nodes.extend(matches
Expand Down
4 changes: 4 additions & 0 deletions polkadot/service/src/config.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -18,6 +18,7 @@

use transaction_pool;
use chain_spec::ChainSpec;
pub use substrate_executor::ExecutionStrategy;
pub use network::Role;
pub use network::NetworkConfiguration;
pub use client_db::PruningMode;
Expand All@@ -44,6 +45,8 @@ pub struct Configuration {
pub telemetry: Option<String>,
/// Node name.
pub name: String,
/// Execution strategy.
pub execution_strategy: ExecutionStrategy,
}

impl Configuration {
Expand All@@ -60,6 +63,7 @@ impl Configuration {
keys: Default::default(),
telemetry: Default::default(),
pruning: PruningMode::ArchiveAll,
execution_strategy: ExecutionStrategy::Both,
};
configuration.network.boot_nodes = configuration.chain_spec.boot_nodes().to_vec();
configuration
Expand Down
4 changes: 2 additions & 2 deletions polkadot/service/src/lib.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -73,7 +73,7 @@ use tokio::runtime::TaskExecutor;

pub use self::error::{ErrorKind, Error};
pub use self::components::{Components, FullComponents, LightComponents};
pub use config::{Configuration, Role, PruningMode};
pub use config::{Configuration, Role, PruningMode, ExecutionStrategy};
pub use chain_spec::ChainSpec;

/// Polkadot service.
Expand DownExpand Up@@ -109,7 +109,7 @@ pub fn new_client(config: Configuration) -> Result<Arc<Client<
path: config.database_path.into(),
pruning: config.pruning,
};
let executor = polkadot_executor::Executor::new();
let executor = polkadot_executor::Executor::with_default_strategy(config.execution_strategy);
let is_validator = (config.roles & Role::AUTHORITY) == Role::AUTHORITY;
let components = components::FullComponents { is_validator };
let (client, _) = components.build_client(db_settings, executor, &config.chain_spec)?;
Expand Down
1 change: 1 addition & 0 deletions substrate/client/src/client.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -326,6 +326,7 @@ impl<B, E, Block> Client<B, E, Block> where
&mut overlay,
"execute_block",
&<Block as BlockT>::new(header.clone(), body.clone().unwrap_or_default()).encode()
// TODO: intercept Err::ConsensusFailure, report failure wrt block and then accept wasm result.
)?;

Some(storage_update)
Expand Down
6 changes: 6 additions & 0 deletions substrate/executor/src/error.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -74,5 +74,11 @@ error_chain! {
description("invalid memory reference"),
display("Invalid memory reference"),
}

/// Consensus failure.
ConsensusFailure(wasm_result: Box<Result<Vec<u8>>>, native_result: Box<Result<Vec<u8>>>) {
description("consensus failure"),
display("Differing results from Wasm execution and native dispatch"),
}
}
}
2 changes: 1 addition & 1 deletion substrate/executor/src/lib.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -66,7 +66,7 @@ mod sandbox;

pub mod error;
pub use wasm_executor::WasmExecutor;
pub use native_executor::{with_native_environment, NativeExecutor, NativeExecutionDispatch};
pub use native_executor::{with_native_environment, NativeExecutor, NativeExecutionDispatch, ExecutionStrategy};
pub use state_machine::Externalities;
pub use runtime_version::RuntimeVersion;
pub use codec::Slicable;
Expand Down
87 changes: 66 additions & 21 deletions substrate/executor/src/native_executor.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -27,14 +27,14 @@ use parking_lot::{Mutex, MutexGuard};
use RuntimeInfo;

// For the internal Runtime Cache:
// Do we run this natively or use the given WasmModule
enum RunWith {
// Is it compatible enough to run this natively or do we need to fall back on the WasmModule
enum Compatibility {
InvalidVersion(WasmModule),
NativeRuntime(RuntimeVersion),
WasmRuntime(RuntimeVersion, WasmModule)
IsCompatible(RuntimeVersion),
NotCompatible(RuntimeVersion, WasmModule)
}

type CacheType = HashMap<u64, RunWith>;
type CacheType = HashMap<u64, Compatibility>;

lazy_static! {
static ref RUNTIMES_CACHE: Mutex<CacheType> = Mutex::new(HashMap::new());
Expand All@@ -51,29 +51,28 @@ fn gen_cache_key(code: &[u8]) -> u64 {
}

/// fetch a runtime version from the cache or if there is no cached version yet, create
/// the runtime version entry for `code`, determines whether `RunWith::NativeRuntime`
/// the runtime version entry for `code`, determines whether `Compatibility::IsCompatible`
/// can be used by by comparing returned RuntimeVersion to `ref_version`
fn fetch_cached_runtime_version<'a, E: Externalities>(
cache: &'a mut MutexGuard<CacheType>,
ext: &mut E,
code: &[u8],
ref_version: RuntimeVersion
) -> &'a RunWith {
) -> &'a Compatibility {
cache.entry(gen_cache_key(code))
.or_insert_with(|| {
let module = WasmModule::from_buffer(code).expect("all modules compiled with rustc are valid wasm code; qed");
let version = WasmExecutor.call_in_wasm_module(ext, &module, "version", &[]).ok()
.and_then(|v| RuntimeVersion::decode(&mut v.as_slice()));


if let Some(v) = version {
if ref_version.can_call_with(&v) {
RunWith::NativeRuntime(v)
Compatibility::IsCompatible(v)
} else {
RunWith::WasmRuntime(v, module)
Compatibility::NotCompatible(v, module)
}
} else {
RunWith::InvalidVersion(module)
Compatibility::InvalidVersion(module)
}
})
}
Expand DownExpand Up@@ -106,31 +105,78 @@ pub trait NativeExecutionDispatch {
const VERSION: RuntimeVersion;
}

/// Strategy for executing a call into the runtime.
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
pub enum ExecutionStrategy {
/// Execute with the native equivalent if it is compatible with the given wasm module; otherwise fall back to the wasm.
NativeWhenPossible,
/// Use the given wasm module.
AlwaysWasm,
/// Run with both the wasm and the native variant (if compatible). Report any discrepency as an error.
Both,
}

/// A generic `CodeExecutor` implementation that uses a delegate to determine wasm code equivalence
/// and dispatch to native code when possible, falling back on `WasmExecutor` when not.
#[derive(Debug)]
pub struct NativeExecutor<D: NativeExecutionDispatch + Sync + Send> {
/// The strategy for execution.
strategy: ExecutionStrategy,
/// Dummy field to avoid the compiler complaining about us not using `D`.
_dummy: ::std::marker::PhantomData<D>,
}

impl<D: NativeExecutionDispatch + Sync + Send> NativeExecutor<D> {
/// Create new instance.
pub fn new() -> Self {
pub fn new(strategy: ExecutionStrategy) -> Self {
// FIXME: set this entry at compile time
RUNTIMES_CACHE.lock().insert(
gen_cache_key(D::native_equivalent()),
RunWith::NativeRuntime(D::VERSION));
Compatibility::IsCompatible(D::VERSION));

NativeExecutor {
strategy,
_dummy: Default::default(),
}
}

fn call_with_strategy<E: Externalities>(
&self,
ext: &mut E,
code: &[u8],
method: &str,
data: &[u8],
strategy: ExecutionStrategy,
) -> Result<Vec<u8>> {
let mut c = RUNTIMES_CACHE.lock();
match (strategy, fetch_cached_runtime_version(&mut c, ext, code, D::VERSION)) {
(_, Compatibility::NotCompatible(_, m)) => WasmExecutor.call_in_wasm_module(ext, m, method, data),
(_, Compatibility::InvalidVersion(m)) => WasmExecutor.call_in_wasm_module(ext, m, method, data),
(ExecutionStrategy::AlwaysWasm, _) => WasmExecutor.call(ext, code, method, data),
(ExecutionStrategy::NativeWhenPossible, _) => D::dispatch(ext, method, data),
_ => {
// both
let w = WasmExecutor.call(ext, code, method, data);
let n = D::dispatch(ext, method, data);
let same_ok = if let (&Ok(ref w), &Ok(ref n)) = (&w, &n) {
w == n
} else { false };
if same_ok {
return w
}
if w.is_err() && n.is_err() && format!("{:?}", w) == format!("{:?}", n) {
return w
}
Err(ErrorKind::ConsensusFailure(Box::new(w), Box::new(n)).into())
}
}
}
}

impl<D: NativeExecutionDispatch + Sync + Send> Clone for NativeExecutor<D> {
fn clone(&self) -> Self {
NativeExecutor {
strategy: self.strategy,
_dummy: Default::default(),
}
}
Expand All@@ -146,8 +192,8 @@ impl<D: NativeExecutionDispatch + Sync + Send> RuntimeInfo for NativeExecutor<D>
) -> Option<RuntimeVersion> {
let mut c = RUNTIMES_CACHE.lock();
match fetch_cached_runtime_version(&mut c, ext, code, D::VERSION) {
RunWith::NativeRuntime(v) | RunWith::WasmRuntime(v, _) => Some(v.clone()),
RunWith::InvalidVersion(_m) => None
Compatibility::IsCompatible(v) | Compatibility::NotCompatible(v, _) => Some(v.clone()),
Compatibility::InvalidVersion(_m) => None
}
}
}
Expand All@@ -162,11 +208,7 @@ impl<D: NativeExecutionDispatch + Sync + Send> CodeExecutor for NativeExecutor<D
method: &str,
data: &[u8],
) -> Result<Vec<u8>> {
let mut c = RUNTIMES_CACHE.lock();
match fetch_cached_runtime_version(&mut c, ext, code, D::VERSION) {
RunWith::NativeRuntime(_v) => D::dispatch(ext, method, data),
RunWith::WasmRuntime(_, m) | RunWith::InvalidVersion(m) => WasmExecutor.call_in_wasm_module(ext, m, method, data)
}
self.call_with_strategy(ext, code, method, data, self.strategy)
}
}

Expand DownExpand Up@@ -198,7 +240,10 @@ macro_rules! native_executor_instance {

impl $name {
pub fn new() -> $crate::NativeExecutor<$name> {
$crate::NativeExecutor::new()
$crate::NativeExecutor::new($crate::ExecutionStrategy::NativeWhenPossible)
}
pub fn with_default_strategy(strategy: $crate::ExecutionStrategy) -> $crate::NativeExecutor<$name> {
$crate::NativeExecutor::new(strategy)
}
}
}
Expand Down
, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Allow execution with both native & wasm by gavofyork · Pull Request #309 · paritytech/substrate · GitHub
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 all 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
6 changes: 5 additions & 1 deletion polkadot/cli/src/cli.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -74,7 +74,7 @@ args:
- pruning:
long: pruning
value_name: PRUNING_MODE
help: Specify the pruning mode. (a number of blocks to keep or "archive"). Default is 256.
help: Specify the pruning mode, a number of blocks to keep or "archive". Default is 256.
takes_value: true
- name:
long: name
Expand All@@ -95,6 +95,10 @@ args:
value_name: TELEMETRY_URL
help: The URL of the telemetry server. Implies --telemetry
takes_value: true
- execution:
long: execution
value_name: STRATEGY
help: The means of execution used when calling into the runtime. Can be either wasm, native or both.
subcommands:
- build-spec:
about: Build a spec.json file, outputing to stdout
Expand Down
16 changes: 15 additions & 1 deletion polkadot/cli/src/lib.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -218,18 +218,32 @@ pub fn run<I, T, W>(args: I, worker: W) -> error::Result<()> where
if matches.is_present("collator") {
info!("Starting collator");
// TODO [rob]: collation node implementation
service::Role::FULL
// This isn't a thing. Different parachains will have their own collator executables and
// maybe link to libpolkadot to get a light-client.
service::Role::LIGHT
} else if matches.is_present("light") {
info!("Starting (light)");
config.execution_strategy = service::ExecutionStrategy::NativeWhenPossible;
service::Role::LIGHT
} else if matches.is_present("validator") || matches.is_present("dev") {
info!("Starting validator");
config.execution_strategy = service::ExecutionStrategy::Both;
service::Role::AUTHORITY
} else {
info!("Starting (heavy)");
config.execution_strategy = service::ExecutionStrategy::NativeWhenPossible;
service::Role::FULL
};

if let Some(s) = matches.value_of("pruning") {
config.execution_strategy = match s {
"both" => service::ExecutionStrategy::Both,
"native" => service::ExecutionStrategy::NativeWhenPossible,
"wasm" => service::ExecutionStrategy::AlwaysWasm,
_ => return Err(error::ErrorKind::Input("Invalid execution mode specified".to_owned()).into()),
};
}

config.roles = role;
{
config.network.boot_nodes.extend(matches
Expand Down
4 changes: 4 additions & 0 deletions polkadot/service/src/config.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -18,6 +18,7 @@

use transaction_pool;
use chain_spec::ChainSpec;
pub use substrate_executor::ExecutionStrategy;
pub use network::Role;
pub use network::NetworkConfiguration;
pub use client_db::PruningMode;
Expand All@@ -44,6 +45,8 @@ pub struct Configuration {
pub telemetry: Option<String>,
/// Node name.
pub name: String,
/// Execution strategy.
pub execution_strategy: ExecutionStrategy,
}

impl Configuration {
Expand All@@ -60,6 +63,7 @@ impl Configuration {
keys: Default::default(),
telemetry: Default::default(),
pruning: PruningMode::ArchiveAll,
execution_strategy: ExecutionStrategy::Both,
};
configuration.network.boot_nodes = configuration.chain_spec.boot_nodes().to_vec();
configuration
Expand Down
4 changes: 2 additions & 2 deletions polkadot/service/src/lib.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -73,7 +73,7 @@ use tokio::runtime::TaskExecutor;

pub use self::error::{ErrorKind, Error};
pub use self::components::{Components, FullComponents, LightComponents};
pub use config::{Configuration, Role, PruningMode};
pub use config::{Configuration, Role, PruningMode, ExecutionStrategy};
pub use chain_spec::ChainSpec;

/// Polkadot service.
Expand DownExpand Up@@ -109,7 +109,7 @@ pub fn new_client(config: Configuration) -> Result<Arc<Client<
path: config.database_path.into(),
pruning: config.pruning,
};
let executor = polkadot_executor::Executor::new();
let executor = polkadot_executor::Executor::with_default_strategy(config.execution_strategy);
let is_validator = (config.roles & Role::AUTHORITY) == Role::AUTHORITY;
let components = components::FullComponents { is_validator };
let (client, _) = components.build_client(db_settings, executor, &config.chain_spec)?;
Expand Down
1 change: 1 addition & 0 deletions substrate/client/src/client.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -326,6 +326,7 @@ impl<B, E, Block> Client<B, E, Block> where
&mut overlay,
"execute_block",
&<Block as BlockT>::new(header.clone(), body.clone().unwrap_or_default()).encode()
// TODO: intercept Err::ConsensusFailure, report failure wrt block and then accept wasm result.
)?;

Some(storage_update)
Expand Down
6 changes: 6 additions & 0 deletions substrate/executor/src/error.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -74,5 +74,11 @@ error_chain! {
description("invalid memory reference"),
display("Invalid memory reference"),
}

/// Consensus failure.
ConsensusFailure(wasm_result: Box<Result<Vec<u8>>>, native_result: Box<Result<Vec<u8>>>) {
description("consensus failure"),
display("Differing results from Wasm execution and native dispatch"),
}
}
}
2 changes: 1 addition & 1 deletion substrate/executor/src/lib.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -66,7 +66,7 @@ mod sandbox;

pub mod error;
pub use wasm_executor::WasmExecutor;
pub use native_executor::{with_native_environment, NativeExecutor, NativeExecutionDispatch};
pub use native_executor::{with_native_environment, NativeExecutor, NativeExecutionDispatch, ExecutionStrategy};
pub use state_machine::Externalities;
pub use runtime_version::RuntimeVersion;
pub use codec::Slicable;
Expand Down
87 changes: 66 additions & 21 deletions substrate/executor/src/native_executor.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -27,14 +27,14 @@ use parking_lot::{Mutex, MutexGuard};
use RuntimeInfo;

// For the internal Runtime Cache:
// Do we run this natively or use the given WasmModule
enum RunWith {
// Is it compatible enough to run this natively or do we need to fall back on the WasmModule
enum Compatibility {
InvalidVersion(WasmModule),
NativeRuntime(RuntimeVersion),
WasmRuntime(RuntimeVersion, WasmModule)
IsCompatible(RuntimeVersion),
NotCompatible(RuntimeVersion, WasmModule)
}

type CacheType = HashMap<u64, RunWith>;
type CacheType = HashMap<u64, Compatibility>;

lazy_static! {
static ref RUNTIMES_CACHE: Mutex<CacheType> = Mutex::new(HashMap::new());
Expand All@@ -51,29 +51,28 @@ fn gen_cache_key(code: &[u8]) -> u64 {
}

/// fetch a runtime version from the cache or if there is no cached version yet, create
/// the runtime version entry for `code`, determines whether `RunWith::NativeRuntime`
/// the runtime version entry for `code`, determines whether `Compatibility::IsCompatible`
/// can be used by by comparing returned RuntimeVersion to `ref_version`
fn fetch_cached_runtime_version<'a, E: Externalities>(
cache: &'a mut MutexGuard<CacheType>,
ext: &mut E,
code: &[u8],
ref_version: RuntimeVersion
) -> &'a RunWith {
) -> &'a Compatibility {
cache.entry(gen_cache_key(code))
.or_insert_with(|| {
let module = WasmModule::from_buffer(code).expect("all modules compiled with rustc are valid wasm code; qed");
let version = WasmExecutor.call_in_wasm_module(ext, &module, "version", &[]).ok()
.and_then(|v| RuntimeVersion::decode(&mut v.as_slice()));


if let Some(v) = version {
if ref_version.can_call_with(&v) {
RunWith::NativeRuntime(v)
Compatibility::IsCompatible(v)
} else {
RunWith::WasmRuntime(v, module)
Compatibility::NotCompatible(v, module)
}
} else {
RunWith::InvalidVersion(module)
Compatibility::InvalidVersion(module)
}
})
}
Expand DownExpand Up@@ -106,31 +105,78 @@ pub trait NativeExecutionDispatch {
const VERSION: RuntimeVersion;
}

/// Strategy for executing a call into the runtime.
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
pub enum ExecutionStrategy {
/// Execute with the native equivalent if it is compatible with the given wasm module; otherwise fall back to the wasm.
NativeWhenPossible,
/// Use the given wasm module.
AlwaysWasm,
/// Run with both the wasm and the native variant (if compatible). Report any discrepency as an error.
Both,
}

/// A generic `CodeExecutor` implementation that uses a delegate to determine wasm code equivalence
/// and dispatch to native code when possible, falling back on `WasmExecutor` when not.
#[derive(Debug)]
pub struct NativeExecutor<D: NativeExecutionDispatch + Sync + Send> {
/// The strategy for execution.
strategy: ExecutionStrategy,
/// Dummy field to avoid the compiler complaining about us not using `D`.
_dummy: ::std::marker::PhantomData<D>,
}

impl<D: NativeExecutionDispatch + Sync + Send> NativeExecutor<D> {
/// Create new instance.
pub fn new() -> Self {
pub fn new(strategy: ExecutionStrategy) -> Self {
// FIXME: set this entry at compile time
RUNTIMES_CACHE.lock().insert(
gen_cache_key(D::native_equivalent()),
RunWith::NativeRuntime(D::VERSION));
Compatibility::IsCompatible(D::VERSION));

NativeExecutor {
strategy,
_dummy: Default::default(),
}
}

fn call_with_strategy<E: Externalities>(
&self,
ext: &mut E,
code: &[u8],
method: &str,
data: &[u8],
strategy: ExecutionStrategy,
) -> Result<Vec<u8>> {
let mut c = RUNTIMES_CACHE.lock();
match (strategy, fetch_cached_runtime_version(&mut c, ext, code, D::VERSION)) {
(_, Compatibility::NotCompatible(_, m)) => WasmExecutor.call_in_wasm_module(ext, m, method, data),
(_, Compatibility::InvalidVersion(m)) => WasmExecutor.call_in_wasm_module(ext, m, method, data),
(ExecutionStrategy::AlwaysWasm, _) => WasmExecutor.call(ext, code, method, data),
(ExecutionStrategy::NativeWhenPossible, _) => D::dispatch(ext, method, data),
_ => {
// both
let w = WasmExecutor.call(ext, code, method, data);
let n = D::dispatch(ext, method, data);
let same_ok = if let (&Ok(ref w), &Ok(ref n)) = (&w, &n) {
w == n
} else { false };
if same_ok {
return w
}
if w.is_err() && n.is_err() && format!("{:?}", w) == format!("{:?}", n) {
return w
}
Err(ErrorKind::ConsensusFailure(Box::new(w), Box::new(n)).into())
}
}
}
}

impl<D: NativeExecutionDispatch + Sync + Send> Clone for NativeExecutor<D> {
fn clone(&self) -> Self {
NativeExecutor {
strategy: self.strategy,
_dummy: Default::default(),
}
}
Expand All@@ -146,8 +192,8 @@ impl<D: NativeExecutionDispatch + Sync + Send> RuntimeInfo for NativeExecutor<D>
) -> Option<RuntimeVersion> {
let mut c = RUNTIMES_CACHE.lock();
match fetch_cached_runtime_version(&mut c, ext, code, D::VERSION) {
RunWith::NativeRuntime(v) | RunWith::WasmRuntime(v, _) => Some(v.clone()),
RunWith::InvalidVersion(_m) => None
Compatibility::IsCompatible(v) | Compatibility::NotCompatible(v, _) => Some(v.clone()),
Compatibility::InvalidVersion(_m) => None
}
}
}
Expand All@@ -162,11 +208,7 @@ impl<D: NativeExecutionDispatch + Sync + Send> CodeExecutor for NativeExecutor<D
method: &str,
data: &[u8],
) -> Result<Vec<u8>> {
let mut c = RUNTIMES_CACHE.lock();
match fetch_cached_runtime_version(&mut c, ext, code, D::VERSION) {
RunWith::NativeRuntime(_v) => D::dispatch(ext, method, data),
RunWith::WasmRuntime(_, m) | RunWith::InvalidVersion(m) => WasmExecutor.call_in_wasm_module(ext, m, method, data)
}
self.call_with_strategy(ext, code, method, data, self.strategy)
}
}

Expand DownExpand Up@@ -198,7 +240,10 @@ macro_rules! native_executor_instance {

impl $name {
pub fn new() -> $crate::NativeExecutor<$name> {
$crate::NativeExecutor::new()
$crate::NativeExecutor::new($crate::ExecutionStrategy::NativeWhenPossible)
}
pub fn with_default_strategy(strategy: $crate::ExecutionStrategy) -> $crate::NativeExecutor<$name> {
$crate::NativeExecutor::new(strategy)
}
}
}
Expand Down
, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Allow execution with both native & wasm by gavofyork · Pull Request #309 · paritytech/substrate · GitHub
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 all 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
6 changes: 5 additions & 1 deletion polkadot/cli/src/cli.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -74,7 +74,7 @@ args:
- pruning:
long: pruning
value_name: PRUNING_MODE
help: Specify the pruning mode. (a number of blocks to keep or "archive"). Default is 256.
help: Specify the pruning mode, a number of blocks to keep or "archive". Default is 256.
takes_value: true
- name:
long: name
Expand All@@ -95,6 +95,10 @@ args:
value_name: TELEMETRY_URL
help: The URL of the telemetry server. Implies --telemetry
takes_value: true
- execution:
long: execution
value_name: STRATEGY
help: The means of execution used when calling into the runtime. Can be either wasm, native or both.
subcommands:
- build-spec:
about: Build a spec.json file, outputing to stdout
Expand Down
16 changes: 15 additions & 1 deletion polkadot/cli/src/lib.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -218,18 +218,32 @@ pub fn run<I, T, W>(args: I, worker: W) -> error::Result<()> where
if matches.is_present("collator") {
info!("Starting collator");
// TODO [rob]: collation node implementation
service::Role::FULL
// This isn't a thing. Different parachains will have their own collator executables and
// maybe link to libpolkadot to get a light-client.
service::Role::LIGHT
} else if matches.is_present("light") {
info!("Starting (light)");
config.execution_strategy = service::ExecutionStrategy::NativeWhenPossible;
service::Role::LIGHT
} else if matches.is_present("validator") || matches.is_present("dev") {
info!("Starting validator");
config.execution_strategy = service::ExecutionStrategy::Both;
service::Role::AUTHORITY
} else {
info!("Starting (heavy)");
config.execution_strategy = service::ExecutionStrategy::NativeWhenPossible;
service::Role::FULL
};

if let Some(s) = matches.value_of("pruning") {
config.execution_strategy = match s {
"both" => service::ExecutionStrategy::Both,
"native" => service::ExecutionStrategy::NativeWhenPossible,
"wasm" => service::ExecutionStrategy::AlwaysWasm,
_ => return Err(error::ErrorKind::Input("Invalid execution mode specified".to_owned()).into()),
};
}

config.roles = role;
{
config.network.boot_nodes.extend(matches
Expand Down
4 changes: 4 additions & 0 deletions polkadot/service/src/config.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -18,6 +18,7 @@

use transaction_pool;
use chain_spec::ChainSpec;
pub use substrate_executor::ExecutionStrategy;
pub use network::Role;
pub use network::NetworkConfiguration;
pub use client_db::PruningMode;
Expand All@@ -44,6 +45,8 @@ pub struct Configuration {
pub telemetry: Option<String>,
/// Node name.
pub name: String,
/// Execution strategy.
pub execution_strategy: ExecutionStrategy,
}

impl Configuration {
Expand All@@ -60,6 +63,7 @@ impl Configuration {
keys: Default::default(),
telemetry: Default::default(),
pruning: PruningMode::ArchiveAll,
execution_strategy: ExecutionStrategy::Both,
};
configuration.network.boot_nodes = configuration.chain_spec.boot_nodes().to_vec();
configuration
Expand Down
4 changes: 2 additions & 2 deletions polkadot/service/src/lib.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -73,7 +73,7 @@ use tokio::runtime::TaskExecutor;

pub use self::error::{ErrorKind, Error};
pub use self::components::{Components, FullComponents, LightComponents};
pub use config::{Configuration, Role, PruningMode};
pub use config::{Configuration, Role, PruningMode, ExecutionStrategy};
pub use chain_spec::ChainSpec;

/// Polkadot service.
Expand DownExpand Up@@ -109,7 +109,7 @@ pub fn new_client(config: Configuration) -> Result<Arc<Client<
path: config.database_path.into(),
pruning: config.pruning,
};
let executor = polkadot_executor::Executor::new();
let executor = polkadot_executor::Executor::with_default_strategy(config.execution_strategy);
let is_validator = (config.roles & Role::AUTHORITY) == Role::AUTHORITY;
let components = components::FullComponents { is_validator };
let (client, _) = components.build_client(db_settings, executor, &config.chain_spec)?;
Expand Down
1 change: 1 addition & 0 deletions substrate/client/src/client.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -326,6 +326,7 @@ impl<B, E, Block> Client<B, E, Block> where
&mut overlay,
"execute_block",
&<Block as BlockT>::new(header.clone(), body.clone().unwrap_or_default()).encode()
// TODO: intercept Err::ConsensusFailure, report failure wrt block and then accept wasm result.
)?;

Some(storage_update)
Expand Down
6 changes: 6 additions & 0 deletions substrate/executor/src/error.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -74,5 +74,11 @@ error_chain! {
description("invalid memory reference"),
display("Invalid memory reference"),
}

/// Consensus failure.
ConsensusFailure(wasm_result: Box<Result<Vec<u8>>>, native_result: Box<Result<Vec<u8>>>) {
description("consensus failure"),
display("Differing results from Wasm execution and native dispatch"),
}
}
}
2 changes: 1 addition & 1 deletion substrate/executor/src/lib.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -66,7 +66,7 @@ mod sandbox;

pub mod error;
pub use wasm_executor::WasmExecutor;
pub use native_executor::{with_native_environment, NativeExecutor, NativeExecutionDispatch};
pub use native_executor::{with_native_environment, NativeExecutor, NativeExecutionDispatch, ExecutionStrategy};
pub use state_machine::Externalities;
pub use runtime_version::RuntimeVersion;
pub use codec::Slicable;
Expand Down
87 changes: 66 additions & 21 deletions substrate/executor/src/native_executor.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -27,14 +27,14 @@ use parking_lot::{Mutex, MutexGuard};
use RuntimeInfo;

// For the internal Runtime Cache:
// Do we run this natively or use the given WasmModule
enum RunWith {
// Is it compatible enough to run this natively or do we need to fall back on the WasmModule
enum Compatibility {
InvalidVersion(WasmModule),
NativeRuntime(RuntimeVersion),
WasmRuntime(RuntimeVersion, WasmModule)
IsCompatible(RuntimeVersion),
NotCompatible(RuntimeVersion, WasmModule)
}

type CacheType = HashMap<u64, RunWith>;
type CacheType = HashMap<u64, Compatibility>;

lazy_static! {
static ref RUNTIMES_CACHE: Mutex<CacheType> = Mutex::new(HashMap::new());
Expand All@@ -51,29 +51,28 @@ fn gen_cache_key(code: &[u8]) -> u64 {
}

/// fetch a runtime version from the cache or if there is no cached version yet, create
/// the runtime version entry for `code`, determines whether `RunWith::NativeRuntime`
/// the runtime version entry for `code`, determines whether `Compatibility::IsCompatible`
/// can be used by by comparing returned RuntimeVersion to `ref_version`
fn fetch_cached_runtime_version<'a, E: Externalities>(
cache: &'a mut MutexGuard<CacheType>,
ext: &mut E,
code: &[u8],
ref_version: RuntimeVersion
) -> &'a RunWith {
) -> &'a Compatibility {
cache.entry(gen_cache_key(code))
.or_insert_with(|| {
let module = WasmModule::from_buffer(code).expect("all modules compiled with rustc are valid wasm code; qed");
let version = WasmExecutor.call_in_wasm_module(ext, &module, "version", &[]).ok()
.and_then(|v| RuntimeVersion::decode(&mut v.as_slice()));


if let Some(v) = version {
if ref_version.can_call_with(&v) {
RunWith::NativeRuntime(v)
Compatibility::IsCompatible(v)
} else {
RunWith::WasmRuntime(v, module)
Compatibility::NotCompatible(v, module)
}
} else {
RunWith::InvalidVersion(module)
Compatibility::InvalidVersion(module)
}
})
}
Expand DownExpand Up@@ -106,31 +105,78 @@ pub trait NativeExecutionDispatch {
const VERSION: RuntimeVersion;
}

/// Strategy for executing a call into the runtime.
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
pub enum ExecutionStrategy {
/// Execute with the native equivalent if it is compatible with the given wasm module; otherwise fall back to the wasm.
NativeWhenPossible,
/// Use the given wasm module.
AlwaysWasm,
/// Run with both the wasm and the native variant (if compatible). Report any discrepency as an error.
Both,
}

/// A generic `CodeExecutor` implementation that uses a delegate to determine wasm code equivalence
/// and dispatch to native code when possible, falling back on `WasmExecutor` when not.
#[derive(Debug)]
pub struct NativeExecutor<D: NativeExecutionDispatch + Sync + Send> {
/// The strategy for execution.
strategy: ExecutionStrategy,
/// Dummy field to avoid the compiler complaining about us not using `D`.
_dummy: ::std::marker::PhantomData<D>,
}

impl<D: NativeExecutionDispatch + Sync + Send> NativeExecutor<D> {
/// Create new instance.
pub fn new() -> Self {
pub fn new(strategy: ExecutionStrategy) -> Self {
// FIXME: set this entry at compile time
RUNTIMES_CACHE.lock().insert(
gen_cache_key(D::native_equivalent()),
RunWith::NativeRuntime(D::VERSION));
Compatibility::IsCompatible(D::VERSION));

NativeExecutor {
strategy,
_dummy: Default::default(),
}
}

fn call_with_strategy<E: Externalities>(
&self,
ext: &mut E,
code: &[u8],
method: &str,
data: &[u8],
strategy: ExecutionStrategy,
) -> Result<Vec<u8>> {
let mut c = RUNTIMES_CACHE.lock();
match (strategy, fetch_cached_runtime_version(&mut c, ext, code, D::VERSION)) {
(_, Compatibility::NotCompatible(_, m)) => WasmExecutor.call_in_wasm_module(ext, m, method, data),
(_, Compatibility::InvalidVersion(m)) => WasmExecutor.call_in_wasm_module(ext, m, method, data),
(ExecutionStrategy::AlwaysWasm, _) => WasmExecutor.call(ext, code, method, data),
(ExecutionStrategy::NativeWhenPossible, _) => D::dispatch(ext, method, data),
_ => {
// both
let w = WasmExecutor.call(ext, code, method, data);
let n = D::dispatch(ext, method, data);
let same_ok = if let (&Ok(ref w), &Ok(ref n)) = (&w, &n) {
w == n
} else { false };
if same_ok {
return w
}
if w.is_err() && n.is_err() && format!("{:?}", w) == format!("{:?}", n) {
return w
}
Err(ErrorKind::ConsensusFailure(Box::new(w), Box::new(n)).into())
}
}
}
}

impl<D: NativeExecutionDispatch + Sync + Send> Clone for NativeExecutor<D> {
fn clone(&self) -> Self {
NativeExecutor {
strategy: self.strategy,
_dummy: Default::default(),
}
}
Expand All@@ -146,8 +192,8 @@ impl<D: NativeExecutionDispatch + Sync + Send> RuntimeInfo for NativeExecutor<D>
) -> Option<RuntimeVersion> {
let mut c = RUNTIMES_CACHE.lock();
match fetch_cached_runtime_version(&mut c, ext, code, D::VERSION) {
RunWith::NativeRuntime(v) | RunWith::WasmRuntime(v, _) => Some(v.clone()),
RunWith::InvalidVersion(_m) => None
Compatibility::IsCompatible(v) | Compatibility::NotCompatible(v, _) => Some(v.clone()),
Compatibility::InvalidVersion(_m) => None
}
}
}
Expand All@@ -162,11 +208,7 @@ impl<D: NativeExecutionDispatch + Sync + Send> CodeExecutor for NativeExecutor<D
method: &str,
data: &[u8],
) -> Result<Vec<u8>> {
let mut c = RUNTIMES_CACHE.lock();
match fetch_cached_runtime_version(&mut c, ext, code, D::VERSION) {
RunWith::NativeRuntime(_v) => D::dispatch(ext, method, data),
RunWith::WasmRuntime(_, m) | RunWith::InvalidVersion(m) => WasmExecutor.call_in_wasm_module(ext, m, method, data)
}
self.call_with_strategy(ext, code, method, data, self.strategy)
}
}

Expand DownExpand Up@@ -198,7 +240,10 @@ macro_rules! native_executor_instance {

impl $name {
pub fn new() -> $crate::NativeExecutor<$name> {
$crate::NativeExecutor::new()
$crate::NativeExecutor::new($crate::ExecutionStrategy::NativeWhenPossible)
}
pub fn with_default_strategy(strategy: $crate::ExecutionStrategy) -> $crate::NativeExecutor<$name> {
$crate::NativeExecutor::new(strategy)
}
}
}
Expand Down
, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Allow execution with both native & wasm by gavofyork · Pull Request #309 · paritytech/substrate · GitHub
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 all 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
6 changes: 5 additions & 1 deletion polkadot/cli/src/cli.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -74,7 +74,7 @@ args:
- pruning:
long: pruning
value_name: PRUNING_MODE
help: Specify the pruning mode. (a number of blocks to keep or "archive"). Default is 256.
help: Specify the pruning mode, a number of blocks to keep or "archive". Default is 256.
takes_value: true
- name:
long: name
Expand All@@ -95,6 +95,10 @@ args:
value_name: TELEMETRY_URL
help: The URL of the telemetry server. Implies --telemetry
takes_value: true
- execution:
long: execution
value_name: STRATEGY
help: The means of execution used when calling into the runtime. Can be either wasm, native or both.
subcommands:
- build-spec:
about: Build a spec.json file, outputing to stdout
Expand Down
16 changes: 15 additions & 1 deletion polkadot/cli/src/lib.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -218,18 +218,32 @@ pub fn run<I, T, W>(args: I, worker: W) -> error::Result<()> where
if matches.is_present("collator") {
info!("Starting collator");
// TODO [rob]: collation node implementation
service::Role::FULL
// This isn't a thing. Different parachains will have their own collator executables and
// maybe link to libpolkadot to get a light-client.
service::Role::LIGHT
} else if matches.is_present("light") {
info!("Starting (light)");
config.execution_strategy = service::ExecutionStrategy::NativeWhenPossible;
service::Role::LIGHT
} else if matches.is_present("validator") || matches.is_present("dev") {
info!("Starting validator");
config.execution_strategy = service::ExecutionStrategy::Both;
service::Role::AUTHORITY
} else {
info!("Starting (heavy)");
config.execution_strategy = service::ExecutionStrategy::NativeWhenPossible;
service::Role::FULL
};

if let Some(s) = matches.value_of("pruning") {
config.execution_strategy = match s {
"both" => service::ExecutionStrategy::Both,
"native" => service::ExecutionStrategy::NativeWhenPossible,
"wasm" => service::ExecutionStrategy::AlwaysWasm,
_ => return Err(error::ErrorKind::Input("Invalid execution mode specified".to_owned()).into()),
};
}

config.roles = role;
{
config.network.boot_nodes.extend(matches
Expand Down
4 changes: 4 additions & 0 deletions polkadot/service/src/config.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -18,6 +18,7 @@

use transaction_pool;
use chain_spec::ChainSpec;
pub use substrate_executor::ExecutionStrategy;
pub use network::Role;
pub use network::NetworkConfiguration;
pub use client_db::PruningMode;
Expand All@@ -44,6 +45,8 @@ pub struct Configuration {
pub telemetry: Option<String>,
/// Node name.
pub name: String,
/// Execution strategy.
pub execution_strategy: ExecutionStrategy,
}

impl Configuration {
Expand All@@ -60,6 +63,7 @@ impl Configuration {
keys: Default::default(),
telemetry: Default::default(),
pruning: PruningMode::ArchiveAll,
execution_strategy: ExecutionStrategy::Both,
};
configuration.network.boot_nodes = configuration.chain_spec.boot_nodes().to_vec();
configuration
Expand Down
4 changes: 2 additions & 2 deletions polkadot/service/src/lib.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -73,7 +73,7 @@ use tokio::runtime::TaskExecutor;

pub use self::error::{ErrorKind, Error};
pub use self::components::{Components, FullComponents, LightComponents};
pub use config::{Configuration, Role, PruningMode};
pub use config::{Configuration, Role, PruningMode, ExecutionStrategy};
pub use chain_spec::ChainSpec;

/// Polkadot service.
Expand DownExpand Up@@ -109,7 +109,7 @@ pub fn new_client(config: Configuration) -> Result<Arc<Client<
path: config.database_path.into(),
pruning: config.pruning,
};
let executor = polkadot_executor::Executor::new();
let executor = polkadot_executor::Executor::with_default_strategy(config.execution_strategy);
let is_validator = (config.roles & Role::AUTHORITY) == Role::AUTHORITY;
let components = components::FullComponents { is_validator };
let (client, _) = components.build_client(db_settings, executor, &config.chain_spec)?;
Expand Down
1 change: 1 addition & 0 deletions substrate/client/src/client.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -326,6 +326,7 @@ impl<B, E, Block> Client<B, E, Block> where
&mut overlay,
"execute_block",
&<Block as BlockT>::new(header.clone(), body.clone().unwrap_or_default()).encode()
// TODO: intercept Err::ConsensusFailure, report failure wrt block and then accept wasm result.
)?;

Some(storage_update)
Expand Down
6 changes: 6 additions & 0 deletions substrate/executor/src/error.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -74,5 +74,11 @@ error_chain! {
description("invalid memory reference"),
display("Invalid memory reference"),
}

/// Consensus failure.
ConsensusFailure(wasm_result: Box<Result<Vec<u8>>>, native_result: Box<Result<Vec<u8>>>) {
description("consensus failure"),
display("Differing results from Wasm execution and native dispatch"),
}
}
}
2 changes: 1 addition & 1 deletion substrate/executor/src/lib.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -66,7 +66,7 @@ mod sandbox;

pub mod error;
pub use wasm_executor::WasmExecutor;
pub use native_executor::{with_native_environment, NativeExecutor, NativeExecutionDispatch};
pub use native_executor::{with_native_environment, NativeExecutor, NativeExecutionDispatch, ExecutionStrategy};
pub use state_machine::Externalities;
pub use runtime_version::RuntimeVersion;
pub use codec::Slicable;
Expand Down
87 changes: 66 additions & 21 deletions substrate/executor/src/native_executor.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -27,14 +27,14 @@ use parking_lot::{Mutex, MutexGuard};
use RuntimeInfo;

// For the internal Runtime Cache:
// Do we run this natively or use the given WasmModule
enum RunWith {
// Is it compatible enough to run this natively or do we need to fall back on the WasmModule
enum Compatibility {
InvalidVersion(WasmModule),
NativeRuntime(RuntimeVersion),
WasmRuntime(RuntimeVersion, WasmModule)
IsCompatible(RuntimeVersion),
NotCompatible(RuntimeVersion, WasmModule)
}

type CacheType = HashMap<u64, RunWith>;
type CacheType = HashMap<u64, Compatibility>;

lazy_static! {
static ref RUNTIMES_CACHE: Mutex<CacheType> = Mutex::new(HashMap::new());
Expand All@@ -51,29 +51,28 @@ fn gen_cache_key(code: &[u8]) -> u64 {
}

/// fetch a runtime version from the cache or if there is no cached version yet, create
/// the runtime version entry for `code`, determines whether `RunWith::NativeRuntime`
/// the runtime version entry for `code`, determines whether `Compatibility::IsCompatible`
/// can be used by by comparing returned RuntimeVersion to `ref_version`
fn fetch_cached_runtime_version<'a, E: Externalities>(
cache: &'a mut MutexGuard<CacheType>,
ext: &mut E,
code: &[u8],
ref_version: RuntimeVersion
) -> &'a RunWith {
) -> &'a Compatibility {
cache.entry(gen_cache_key(code))
.or_insert_with(|| {
let module = WasmModule::from_buffer(code).expect("all modules compiled with rustc are valid wasm code; qed");
let version = WasmExecutor.call_in_wasm_module(ext, &module, "version", &[]).ok()
.and_then(|v| RuntimeVersion::decode(&mut v.as_slice()));


if let Some(v) = version {
if ref_version.can_call_with(&v) {
RunWith::NativeRuntime(v)
Compatibility::IsCompatible(v)
} else {
RunWith::WasmRuntime(v, module)
Compatibility::NotCompatible(v, module)
}
} else {
RunWith::InvalidVersion(module)
Compatibility::InvalidVersion(module)
}
})
}
Expand DownExpand Up@@ -106,31 +105,78 @@ pub trait NativeExecutionDispatch {
const VERSION: RuntimeVersion;
}

/// Strategy for executing a call into the runtime.
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
pub enum ExecutionStrategy {
/// Execute with the native equivalent if it is compatible with the given wasm module; otherwise fall back to the wasm.
NativeWhenPossible,
/// Use the given wasm module.
AlwaysWasm,
/// Run with both the wasm and the native variant (if compatible). Report any discrepency as an error.
Both,
}

/// A generic `CodeExecutor` implementation that uses a delegate to determine wasm code equivalence
/// and dispatch to native code when possible, falling back on `WasmExecutor` when not.
#[derive(Debug)]
pub struct NativeExecutor<D: NativeExecutionDispatch + Sync + Send> {
/// The strategy for execution.
strategy: ExecutionStrategy,
/// Dummy field to avoid the compiler complaining about us not using `D`.
_dummy: ::std::marker::PhantomData<D>,
}

impl<D: NativeExecutionDispatch + Sync + Send> NativeExecutor<D> {
/// Create new instance.
pub fn new() -> Self {
pub fn new(strategy: ExecutionStrategy) -> Self {
// FIXME: set this entry at compile time
RUNTIMES_CACHE.lock().insert(
gen_cache_key(D::native_equivalent()),
RunWith::NativeRuntime(D::VERSION));
Compatibility::IsCompatible(D::VERSION));

NativeExecutor {
strategy,
_dummy: Default::default(),
}
}

fn call_with_strategy<E: Externalities>(
&self,
ext: &mut E,
code: &[u8],
method: &str,
data: &[u8],
strategy: ExecutionStrategy,
) -> Result<Vec<u8>> {
let mut c = RUNTIMES_CACHE.lock();
match (strategy, fetch_cached_runtime_version(&mut c, ext, code, D::VERSION)) {
(_, Compatibility::NotCompatible(_, m)) => WasmExecutor.call_in_wasm_module(ext, m, method, data),
(_, Compatibility::InvalidVersion(m)) => WasmExecutor.call_in_wasm_module(ext, m, method, data),
(ExecutionStrategy::AlwaysWasm, _) => WasmExecutor.call(ext, code, method, data),
(ExecutionStrategy::NativeWhenPossible, _) => D::dispatch(ext, method, data),
_ => {
// both
let w = WasmExecutor.call(ext, code, method, data);
let n = D::dispatch(ext, method, data);
let same_ok = if let (&Ok(ref w), &Ok(ref n)) = (&w, &n) {
w == n
} else { false };
if same_ok {
return w
}
if w.is_err() && n.is_err() && format!("{:?}", w) == format!("{:?}", n) {
return w
}
Err(ErrorKind::ConsensusFailure(Box::new(w), Box::new(n)).into())
}
}
}
}

impl<D: NativeExecutionDispatch + Sync + Send> Clone for NativeExecutor<D> {
fn clone(&self) -> Self {
NativeExecutor {
strategy: self.strategy,
_dummy: Default::default(),
}
}
Expand All@@ -146,8 +192,8 @@ impl<D: NativeExecutionDispatch + Sync + Send> RuntimeInfo for NativeExecutor<D>
) -> Option<RuntimeVersion> {
let mut c = RUNTIMES_CACHE.lock();
match fetch_cached_runtime_version(&mut c, ext, code, D::VERSION) {
RunWith::NativeRuntime(v) | RunWith::WasmRuntime(v, _) => Some(v.clone()),
RunWith::InvalidVersion(_m) => None
Compatibility::IsCompatible(v) | Compatibility::NotCompatible(v, _) => Some(v.clone()),
Compatibility::InvalidVersion(_m) => None
}
}
}
Expand All@@ -162,11 +208,7 @@ impl<D: NativeExecutionDispatch + Sync + Send> CodeExecutor for NativeExecutor<D
method: &str,
data: &[u8],
) -> Result<Vec<u8>> {
let mut c = RUNTIMES_CACHE.lock();
match fetch_cached_runtime_version(&mut c, ext, code, D::VERSION) {
RunWith::NativeRuntime(_v) => D::dispatch(ext, method, data),
RunWith::WasmRuntime(_, m) | RunWith::InvalidVersion(m) => WasmExecutor.call_in_wasm_module(ext, m, method, data)
}
self.call_with_strategy(ext, code, method, data, self.strategy)
}
}

Expand DownExpand Up@@ -198,7 +240,10 @@ macro_rules! native_executor_instance {

impl $name {
pub fn new() -> $crate::NativeExecutor<$name> {
$crate::NativeExecutor::new()
$crate::NativeExecutor::new($crate::ExecutionStrategy::NativeWhenPossible)
}
pub fn with_default_strategy(strategy: $crate::ExecutionStrategy) -> $crate::NativeExecutor<$name> {
$crate::NativeExecutor::new(strategy)
}
}
}
Expand Down
, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ' Allow execution with both native & wasm by gavofyork · Pull Request #309 · paritytech/substrate · GitHub
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 all 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
6 changes: 5 additions & 1 deletion polkadot/cli/src/cli.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -74,7 +74,7 @@ args:
- pruning:
long: pruning
value_name: PRUNING_MODE
help: Specify the pruning mode. (a number of blocks to keep or "archive"). Default is 256.
help: Specify the pruning mode, a number of blocks to keep or "archive". Default is 256.
takes_value: true
- name:
long: name
Expand All@@ -95,6 +95,10 @@ args:
value_name: TELEMETRY_URL
help: The URL of the telemetry server. Implies --telemetry
takes_value: true
- execution:
long: execution
value_name: STRATEGY
help: The means of execution used when calling into the runtime. Can be either wasm, native or both.
subcommands:
- build-spec:
about: Build a spec.json file, outputing to stdout
Expand Down
16 changes: 15 additions & 1 deletion polkadot/cli/src/lib.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -218,18 +218,32 @@ pub fn run<I, T, W>(args: I, worker: W) -> error::Result<()> where
if matches.is_present("collator") {
info!("Starting collator");
// TODO [rob]: collation node implementation
service::Role::FULL
// This isn't a thing. Different parachains will have their own collator executables and
// maybe link to libpolkadot to get a light-client.
service::Role::LIGHT
} else if matches.is_present("light") {
info!("Starting (light)");
config.execution_strategy = service::ExecutionStrategy::NativeWhenPossible;
service::Role::LIGHT
} else if matches.is_present("validator") || matches.is_present("dev") {
info!("Starting validator");
config.execution_strategy = service::ExecutionStrategy::Both;
service::Role::AUTHORITY
} else {
info!("Starting (heavy)");
config.execution_strategy = service::ExecutionStrategy::NativeWhenPossible;
service::Role::FULL
};

if let Some(s) = matches.value_of("pruning") {
config.execution_strategy = match s {
"both" => service::ExecutionStrategy::Both,
"native" => service::ExecutionStrategy::NativeWhenPossible,
"wasm" => service::ExecutionStrategy::AlwaysWasm,
_ => return Err(error::ErrorKind::Input("Invalid execution mode specified".to_owned()).into()),
};
}

config.roles = role;
{
config.network.boot_nodes.extend(matches
Expand Down
4 changes: 4 additions & 0 deletions polkadot/service/src/config.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -18,6 +18,7 @@

use transaction_pool;
use chain_spec::ChainSpec;
pub use substrate_executor::ExecutionStrategy;
pub use network::Role;
pub use network::NetworkConfiguration;
pub use client_db::PruningMode;
Expand All@@ -44,6 +45,8 @@ pub struct Configuration {
pub telemetry: Option<String>,
/// Node name.
pub name: String,
/// Execution strategy.
pub execution_strategy: ExecutionStrategy,
}

impl Configuration {
Expand All@@ -60,6 +63,7 @@ impl Configuration {
keys: Default::default(),
telemetry: Default::default(),
pruning: PruningMode::ArchiveAll,
execution_strategy: ExecutionStrategy::Both,
};
configuration.network.boot_nodes = configuration.chain_spec.boot_nodes().to_vec();
configuration
Expand Down
4 changes: 2 additions & 2 deletions polkadot/service/src/lib.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -73,7 +73,7 @@ use tokio::runtime::TaskExecutor;

pub use self::error::{ErrorKind, Error};
pub use self::components::{Components, FullComponents, LightComponents};
pub use config::{Configuration, Role, PruningMode};
pub use config::{Configuration, Role, PruningMode, ExecutionStrategy};
pub use chain_spec::ChainSpec;

/// Polkadot service.
Expand DownExpand Up@@ -109,7 +109,7 @@ pub fn new_client(config: Configuration) -> Result<Arc<Client<
path: config.database_path.into(),
pruning: config.pruning,
};
let executor = polkadot_executor::Executor::new();
let executor = polkadot_executor::Executor::with_default_strategy(config.execution_strategy);
let is_validator = (config.roles & Role::AUTHORITY) == Role::AUTHORITY;
let components = components::FullComponents { is_validator };
let (client, _) = components.build_client(db_settings, executor, &config.chain_spec)?;
Expand Down
1 change: 1 addition & 0 deletions substrate/client/src/client.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -326,6 +326,7 @@ impl<B, E, Block> Client<B, E, Block> where
&mut overlay,
"execute_block",
&<Block as BlockT>::new(header.clone(), body.clone().unwrap_or_default()).encode()
// TODO: intercept Err::ConsensusFailure, report failure wrt block and then accept wasm result.
)?;

Some(storage_update)
Expand Down
6 changes: 6 additions & 0 deletions substrate/executor/src/error.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -74,5 +74,11 @@ error_chain! {
description("invalid memory reference"),
display("Invalid memory reference"),
}

/// Consensus failure.
ConsensusFailure(wasm_result: Box<Result<Vec<u8>>>, native_result: Box<Result<Vec<u8>>>) {
description("consensus failure"),
display("Differing results from Wasm execution and native dispatch"),
}
}
}
2 changes: 1 addition & 1 deletion substrate/executor/src/lib.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -66,7 +66,7 @@ mod sandbox;

pub mod error;
pub use wasm_executor::WasmExecutor;
pub use native_executor::{with_native_environment, NativeExecutor, NativeExecutionDispatch};
pub use native_executor::{with_native_environment, NativeExecutor, NativeExecutionDispatch, ExecutionStrategy};
pub use state_machine::Externalities;
pub use runtime_version::RuntimeVersion;
pub use codec::Slicable;
Expand Down
87 changes: 66 additions & 21 deletions substrate/executor/src/native_executor.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -27,14 +27,14 @@ use parking_lot::{Mutex, MutexGuard};
use RuntimeInfo;

// For the internal Runtime Cache:
// Do we run this natively or use the given WasmModule
enum RunWith {
// Is it compatible enough to run this natively or do we need to fall back on the WasmModule
enum Compatibility {
InvalidVersion(WasmModule),
NativeRuntime(RuntimeVersion),
WasmRuntime(RuntimeVersion, WasmModule)
IsCompatible(RuntimeVersion),
NotCompatible(RuntimeVersion, WasmModule)
}

type CacheType = HashMap<u64, RunWith>;
type CacheType = HashMap<u64, Compatibility>;

lazy_static! {
static ref RUNTIMES_CACHE: Mutex<CacheType> = Mutex::new(HashMap::new());
Expand All@@ -51,29 +51,28 @@ fn gen_cache_key(code: &[u8]) -> u64 {
}

/// fetch a runtime version from the cache or if there is no cached version yet, create
/// the runtime version entry for `code`, determines whether `RunWith::NativeRuntime`
/// the runtime version entry for `code`, determines whether `Compatibility::IsCompatible`
/// can be used by by comparing returned RuntimeVersion to `ref_version`
fn fetch_cached_runtime_version<'a, E: Externalities>(
cache: &'a mut MutexGuard<CacheType>,
ext: &mut E,
code: &[u8],
ref_version: RuntimeVersion
) -> &'a RunWith {
) -> &'a Compatibility {
cache.entry(gen_cache_key(code))
.or_insert_with(|| {
let module = WasmModule::from_buffer(code).expect("all modules compiled with rustc are valid wasm code; qed");
let version = WasmExecutor.call_in_wasm_module(ext, &module, "version", &[]).ok()
.and_then(|v| RuntimeVersion::decode(&mut v.as_slice()));


if let Some(v) = version {
if ref_version.can_call_with(&v) {
RunWith::NativeRuntime(v)
Compatibility::IsCompatible(v)
} else {
RunWith::WasmRuntime(v, module)
Compatibility::NotCompatible(v, module)
}
} else {
RunWith::InvalidVersion(module)
Compatibility::InvalidVersion(module)
}
})
}
Expand DownExpand Up@@ -106,31 +105,78 @@ pub trait NativeExecutionDispatch {
const VERSION: RuntimeVersion;
}

/// Strategy for executing a call into the runtime.
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
pub enum ExecutionStrategy {
/// Execute with the native equivalent if it is compatible with the given wasm module; otherwise fall back to the wasm.
NativeWhenPossible,
/// Use the given wasm module.
AlwaysWasm,
/// Run with both the wasm and the native variant (if compatible). Report any discrepency as an error.
Both,
}

/// A generic `CodeExecutor` implementation that uses a delegate to determine wasm code equivalence
/// and dispatch to native code when possible, falling back on `WasmExecutor` when not.
#[derive(Debug)]
pub struct NativeExecutor<D: NativeExecutionDispatch + Sync + Send> {
/// The strategy for execution.
strategy: ExecutionStrategy,
/// Dummy field to avoid the compiler complaining about us not using `D`.
_dummy: ::std::marker::PhantomData<D>,
}

impl<D: NativeExecutionDispatch + Sync + Send> NativeExecutor<D> {
/// Create new instance.
pub fn new() -> Self {
pub fn new(strategy: ExecutionStrategy) -> Self {
// FIXME: set this entry at compile time
RUNTIMES_CACHE.lock().insert(
gen_cache_key(D::native_equivalent()),
RunWith::NativeRuntime(D::VERSION));
Compatibility::IsCompatible(D::VERSION));

NativeExecutor {
strategy,
_dummy: Default::default(),
}
}

fn call_with_strategy<E: Externalities>(
&self,
ext: &mut E,
code: &[u8],
method: &str,
data: &[u8],
strategy: ExecutionStrategy,
) -> Result<Vec<u8>> {
let mut c = RUNTIMES_CACHE.lock();
match (strategy, fetch_cached_runtime_version(&mut c, ext, code, D::VERSION)) {
(_, Compatibility::NotCompatible(_, m)) => WasmExecutor.call_in_wasm_module(ext, m, method, data),
(_, Compatibility::InvalidVersion(m)) => WasmExecutor.call_in_wasm_module(ext, m, method, data),
(ExecutionStrategy::AlwaysWasm, _) => WasmExecutor.call(ext, code, method, data),
(ExecutionStrategy::NativeWhenPossible, _) => D::dispatch(ext, method, data),
_ => {
// both
let w = WasmExecutor.call(ext, code, method, data);
let n = D::dispatch(ext, method, data);
let same_ok = if let (&Ok(ref w), &Ok(ref n)) = (&w, &n) {
w == n
} else { false };
if same_ok {
return w
}
if w.is_err() && n.is_err() && format!("{:?}", w) == format!("{:?}", n) {
return w
}
Err(ErrorKind::ConsensusFailure(Box::new(w), Box::new(n)).into())
}
}
}
}

impl<D: NativeExecutionDispatch + Sync + Send> Clone for NativeExecutor<D> {
fn clone(&self) -> Self {
NativeExecutor {
strategy: self.strategy,
_dummy: Default::default(),
}
}
Expand All@@ -146,8 +192,8 @@ impl<D: NativeExecutionDispatch + Sync + Send> RuntimeInfo for NativeExecutor<D>
) -> Option<RuntimeVersion> {
let mut c = RUNTIMES_CACHE.lock();
match fetch_cached_runtime_version(&mut c, ext, code, D::VERSION) {
RunWith::NativeRuntime(v) | RunWith::WasmRuntime(v, _) => Some(v.clone()),
RunWith::InvalidVersion(_m) => None
Compatibility::IsCompatible(v) | Compatibility::NotCompatible(v, _) => Some(v.clone()),
Compatibility::InvalidVersion(_m) => None
}
}
}
Expand All@@ -162,11 +208,7 @@ impl<D: NativeExecutionDispatch + Sync + Send> CodeExecutor for NativeExecutor<D
method: &str,
data: &[u8],
) -> Result<Vec<u8>> {
let mut c = RUNTIMES_CACHE.lock();
match fetch_cached_runtime_version(&mut c, ext, code, D::VERSION) {
RunWith::NativeRuntime(_v) => D::dispatch(ext, method, data),
RunWith::WasmRuntime(_, m) | RunWith::InvalidVersion(m) => WasmExecutor.call_in_wasm_module(ext, m, method, data)
}
self.call_with_strategy(ext, code, method, data, self.strategy)
}
}

Expand DownExpand Up@@ -198,7 +240,10 @@ macro_rules! native_executor_instance {

impl $name {
pub fn new() -> $crate::NativeExecutor<$name> {
$crate::NativeExecutor::new()
$crate::NativeExecutor::new($crate::ExecutionStrategy::NativeWhenPossible)
}
pub fn with_default_strategy(strategy: $crate::ExecutionStrategy) -> $crate::NativeExecutor<$name> {
$crate::NativeExecutor::new(strategy)
}
}
}
Expand Down
, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Allow execution with both native & wasm by gavofyork · Pull Request #309 · paritytech/substrate · GitHub
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 all 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
6 changes: 5 additions & 1 deletion polkadot/cli/src/cli.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -74,7 +74,7 @@ args:
- pruning:
long: pruning
value_name: PRUNING_MODE
help: Specify the pruning mode. (a number of blocks to keep or "archive"). Default is 256.
help: Specify the pruning mode, a number of blocks to keep or "archive". Default is 256.
takes_value: true
- name:
long: name
Expand All@@ -95,6 +95,10 @@ args:
value_name: TELEMETRY_URL
help: The URL of the telemetry server. Implies --telemetry
takes_value: true
- execution:
long: execution
value_name: STRATEGY
help: The means of execution used when calling into the runtime. Can be either wasm, native or both.
subcommands:
- build-spec:
about: Build a spec.json file, outputing to stdout
Expand Down
16 changes: 15 additions & 1 deletion polkadot/cli/src/lib.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -218,18 +218,32 @@ pub fn run<I, T, W>(args: I, worker: W) -> error::Result<()> where
if matches.is_present("collator") {
info!("Starting collator");
// TODO [rob]: collation node implementation
service::Role::FULL
// This isn't a thing. Different parachains will have their own collator executables and
// maybe link to libpolkadot to get a light-client.
service::Role::LIGHT
} else if matches.is_present("light") {
info!("Starting (light)");
config.execution_strategy = service::ExecutionStrategy::NativeWhenPossible;
service::Role::LIGHT
} else if matches.is_present("validator") || matches.is_present("dev") {
info!("Starting validator");
config.execution_strategy = service::ExecutionStrategy::Both;
service::Role::AUTHORITY
} else {
info!("Starting (heavy)");
config.execution_strategy = service::ExecutionStrategy::NativeWhenPossible;
service::Role::FULL
};

if let Some(s) = matches.value_of("pruning") {
config.execution_strategy = match s {
"both" => service::ExecutionStrategy::Both,
"native" => service::ExecutionStrategy::NativeWhenPossible,
"wasm" => service::ExecutionStrategy::AlwaysWasm,
_ => return Err(error::ErrorKind::Input("Invalid execution mode specified".to_owned()).into()),
};
}

config.roles = role;
{
config.network.boot_nodes.extend(matches
Expand Down
4 changes: 4 additions & 0 deletions polkadot/service/src/config.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -18,6 +18,7 @@

use transaction_pool;
use chain_spec::ChainSpec;
pub use substrate_executor::ExecutionStrategy;
pub use network::Role;
pub use network::NetworkConfiguration;
pub use client_db::PruningMode;
Expand All@@ -44,6 +45,8 @@ pub struct Configuration {
pub telemetry: Option<String>,
/// Node name.
pub name: String,
/// Execution strategy.
pub execution_strategy: ExecutionStrategy,
}

impl Configuration {
Expand All@@ -60,6 +63,7 @@ impl Configuration {
keys: Default::default(),
telemetry: Default::default(),
pruning: PruningMode::ArchiveAll,
execution_strategy: ExecutionStrategy::Both,
};
configuration.network.boot_nodes = configuration.chain_spec.boot_nodes().to_vec();
configuration
Expand Down
4 changes: 2 additions & 2 deletions polkadot/service/src/lib.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -73,7 +73,7 @@ use tokio::runtime::TaskExecutor;

pub use self::error::{ErrorKind, Error};
pub use self::components::{Components, FullComponents, LightComponents};
pub use config::{Configuration, Role, PruningMode};
pub use config::{Configuration, Role, PruningMode, ExecutionStrategy};
pub use chain_spec::ChainSpec;

/// Polkadot service.
Expand DownExpand Up@@ -109,7 +109,7 @@ pub fn new_client(config: Configuration) -> Result<Arc<Client<
path: config.database_path.into(),
pruning: config.pruning,
};
let executor = polkadot_executor::Executor::new();
let executor = polkadot_executor::Executor::with_default_strategy(config.execution_strategy);
let is_validator = (config.roles & Role::AUTHORITY) == Role::AUTHORITY;
let components = components::FullComponents { is_validator };
let (client, _) = components.build_client(db_settings, executor, &config.chain_spec)?;
Expand Down
1 change: 1 addition & 0 deletions substrate/client/src/client.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -326,6 +326,7 @@ impl<B, E, Block> Client<B, E, Block> where
&mut overlay,
"execute_block",
&<Block as BlockT>::new(header.clone(), body.clone().unwrap_or_default()).encode()
// TODO: intercept Err::ConsensusFailure, report failure wrt block and then accept wasm result.
)?;

Some(storage_update)
Expand Down
6 changes: 6 additions & 0 deletions substrate/executor/src/error.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -74,5 +74,11 @@ error_chain! {
description("invalid memory reference"),
display("Invalid memory reference"),
}

/// Consensus failure.
ConsensusFailure(wasm_result: Box<Result<Vec<u8>>>, native_result: Box<Result<Vec<u8>>>) {
description("consensus failure"),
display("Differing results from Wasm execution and native dispatch"),
}
}
}
2 changes: 1 addition & 1 deletion substrate/executor/src/lib.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -66,7 +66,7 @@ mod sandbox;

pub mod error;
pub use wasm_executor::WasmExecutor;
pub use native_executor::{with_native_environment, NativeExecutor, NativeExecutionDispatch};
pub use native_executor::{with_native_environment, NativeExecutor, NativeExecutionDispatch, ExecutionStrategy};
pub use state_machine::Externalities;
pub use runtime_version::RuntimeVersion;
pub use codec::Slicable;
Expand Down
87 changes: 66 additions & 21 deletions substrate/executor/src/native_executor.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -27,14 +27,14 @@ use parking_lot::{Mutex, MutexGuard};
use RuntimeInfo;

// For the internal Runtime Cache:
// Do we run this natively or use the given WasmModule
enum RunWith {
// Is it compatible enough to run this natively or do we need to fall back on the WasmModule
enum Compatibility {
InvalidVersion(WasmModule),
NativeRuntime(RuntimeVersion),
WasmRuntime(RuntimeVersion, WasmModule)
IsCompatible(RuntimeVersion),
NotCompatible(RuntimeVersion, WasmModule)
}

type CacheType = HashMap<u64, RunWith>;
type CacheType = HashMap<u64, Compatibility>;

lazy_static! {
static ref RUNTIMES_CACHE: Mutex<CacheType> = Mutex::new(HashMap::new());
Expand All@@ -51,29 +51,28 @@ fn gen_cache_key(code: &[u8]) -> u64 {
}

/// fetch a runtime version from the cache or if there is no cached version yet, create
/// the runtime version entry for `code`, determines whether `RunWith::NativeRuntime`
/// the runtime version entry for `code`, determines whether `Compatibility::IsCompatible`
/// can be used by by comparing returned RuntimeVersion to `ref_version`
fn fetch_cached_runtime_version<'a, E: Externalities>(
cache: &'a mut MutexGuard<CacheType>,
ext: &mut E,
code: &[u8],
ref_version: RuntimeVersion
) -> &'a RunWith {
) -> &'a Compatibility {
cache.entry(gen_cache_key(code))
.or_insert_with(|| {
let module = WasmModule::from_buffer(code).expect("all modules compiled with rustc are valid wasm code; qed");
let version = WasmExecutor.call_in_wasm_module(ext, &module, "version", &[]).ok()
.and_then(|v| RuntimeVersion::decode(&mut v.as_slice()));


if let Some(v) = version {
if ref_version.can_call_with(&v) {
RunWith::NativeRuntime(v)
Compatibility::IsCompatible(v)
} else {
RunWith::WasmRuntime(v, module)
Compatibility::NotCompatible(v, module)
}
} else {
RunWith::InvalidVersion(module)
Compatibility::InvalidVersion(module)
}
})
}
Expand DownExpand Up@@ -106,31 +105,78 @@ pub trait NativeExecutionDispatch {
const VERSION: RuntimeVersion;
}

/// Strategy for executing a call into the runtime.
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
pub enum ExecutionStrategy {
/// Execute with the native equivalent if it is compatible with the given wasm module; otherwise fall back to the wasm.
NativeWhenPossible,
/// Use the given wasm module.
AlwaysWasm,
/// Run with both the wasm and the native variant (if compatible). Report any discrepency as an error.
Both,
}

/// A generic `CodeExecutor` implementation that uses a delegate to determine wasm code equivalence
/// and dispatch to native code when possible, falling back on `WasmExecutor` when not.
#[derive(Debug)]
pub struct NativeExecutor<D: NativeExecutionDispatch + Sync + Send> {
/// The strategy for execution.
strategy: ExecutionStrategy,
/// Dummy field to avoid the compiler complaining about us not using `D`.
_dummy: ::std::marker::PhantomData<D>,
}

impl<D: NativeExecutionDispatch + Sync + Send> NativeExecutor<D> {
/// Create new instance.
pub fn new() -> Self {
pub fn new(strategy: ExecutionStrategy) -> Self {
// FIXME: set this entry at compile time
RUNTIMES_CACHE.lock().insert(
gen_cache_key(D::native_equivalent()),
RunWith::NativeRuntime(D::VERSION));
Compatibility::IsCompatible(D::VERSION));

NativeExecutor {
strategy,
_dummy: Default::default(),
}
}

fn call_with_strategy<E: Externalities>(
&self,
ext: &mut E,
code: &[u8],
method: &str,
data: &[u8],
strategy: ExecutionStrategy,
) -> Result<Vec<u8>> {
let mut c = RUNTIMES_CACHE.lock();
match (strategy, fetch_cached_runtime_version(&mut c, ext, code, D::VERSION)) {
(_, Compatibility::NotCompatible(_, m)) => WasmExecutor.call_in_wasm_module(ext, m, method, data),
(_, Compatibility::InvalidVersion(m)) => WasmExecutor.call_in_wasm_module(ext, m, method, data),
(ExecutionStrategy::AlwaysWasm, _) => WasmExecutor.call(ext, code, method, data),
(ExecutionStrategy::NativeWhenPossible, _) => D::dispatch(ext, method, data),
_ => {
// both
let w = WasmExecutor.call(ext, code, method, data);
let n = D::dispatch(ext, method, data);
let same_ok = if let (&Ok(ref w), &Ok(ref n)) = (&w, &n) {
w == n
} else { false };
if same_ok {
return w
}
if w.is_err() && n.is_err() && format!("{:?}", w) == format!("{:?}", n) {
return w
}
Err(ErrorKind::ConsensusFailure(Box::new(w), Box::new(n)).into())
}
}
}
}

impl<D: NativeExecutionDispatch + Sync + Send> Clone for NativeExecutor<D> {
fn clone(&self) -> Self {
NativeExecutor {
strategy: self.strategy,
_dummy: Default::default(),
}
}
Expand All@@ -146,8 +192,8 @@ impl<D: NativeExecutionDispatch + Sync + Send> RuntimeInfo for NativeExecutor<D>
) -> Option<RuntimeVersion> {
let mut c = RUNTIMES_CACHE.lock();
match fetch_cached_runtime_version(&mut c, ext, code, D::VERSION) {
RunWith::NativeRuntime(v) | RunWith::WasmRuntime(v, _) => Some(v.clone()),
RunWith::InvalidVersion(_m) => None
Compatibility::IsCompatible(v) | Compatibility::NotCompatible(v, _) => Some(v.clone()),
Compatibility::InvalidVersion(_m) => None
}
}
}
Expand All@@ -162,11 +208,7 @@ impl<D: NativeExecutionDispatch + Sync + Send> CodeExecutor for NativeExecutor<D
method: &str,
data: &[u8],
) -> Result<Vec<u8>> {
let mut c = RUNTIMES_CACHE.lock();
match fetch_cached_runtime_version(&mut c, ext, code, D::VERSION) {
RunWith::NativeRuntime(_v) => D::dispatch(ext, method, data),
RunWith::WasmRuntime(_, m) | RunWith::InvalidVersion(m) => WasmExecutor.call_in_wasm_module(ext, m, method, data)
}
self.call_with_strategy(ext, code, method, data, self.strategy)
}
}

Expand DownExpand Up@@ -198,7 +240,10 @@ macro_rules! native_executor_instance {

impl $name {
pub fn new() -> $crate::NativeExecutor<$name> {
$crate::NativeExecutor::new()
$crate::NativeExecutor::new($crate::ExecutionStrategy::NativeWhenPossible)
}
pub fn with_default_strategy(strategy: $crate::ExecutionStrategy) -> $crate::NativeExecutor<$name> {
$crate::NativeExecutor::new(strategy)
}
}
}
Expand Down
, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Allow execution with both native & wasm by gavofyork · Pull Request #309 · paritytech/substrate · GitHub
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 all 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
6 changes: 5 additions & 1 deletion polkadot/cli/src/cli.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -74,7 +74,7 @@ args:
- pruning:
long: pruning
value_name: PRUNING_MODE
help: Specify the pruning mode. (a number of blocks to keep or "archive"). Default is 256.
help: Specify the pruning mode, a number of blocks to keep or "archive". Default is 256.
takes_value: true
- name:
long: name
Expand All@@ -95,6 +95,10 @@ args:
value_name: TELEMETRY_URL
help: The URL of the telemetry server. Implies --telemetry
takes_value: true
- execution:
long: execution
value_name: STRATEGY
help: The means of execution used when calling into the runtime. Can be either wasm, native or both.
subcommands:
- build-spec:
about: Build a spec.json file, outputing to stdout
Expand Down
16 changes: 15 additions & 1 deletion polkadot/cli/src/lib.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -218,18 +218,32 @@ pub fn run<I, T, W>(args: I, worker: W) -> error::Result<()> where
if matches.is_present("collator") {
info!("Starting collator");
// TODO [rob]: collation node implementation
service::Role::FULL
// This isn't a thing. Different parachains will have their own collator executables and
// maybe link to libpolkadot to get a light-client.
service::Role::LIGHT
} else if matches.is_present("light") {
info!("Starting (light)");
config.execution_strategy = service::ExecutionStrategy::NativeWhenPossible;
service::Role::LIGHT
} else if matches.is_present("validator") || matches.is_present("dev") {
info!("Starting validator");
config.execution_strategy = service::ExecutionStrategy::Both;
service::Role::AUTHORITY
} else {
info!("Starting (heavy)");
config.execution_strategy = service::ExecutionStrategy::NativeWhenPossible;
service::Role::FULL
};

if let Some(s) = matches.value_of("pruning") {
config.execution_strategy = match s {
"both" => service::ExecutionStrategy::Both,
"native" => service::ExecutionStrategy::NativeWhenPossible,
"wasm" => service::ExecutionStrategy::AlwaysWasm,
_ => return Err(error::ErrorKind::Input("Invalid execution mode specified".to_owned()).into()),
};
}

config.roles = role;
{
config.network.boot_nodes.extend(matches
Expand Down
4 changes: 4 additions & 0 deletions polkadot/service/src/config.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -18,6 +18,7 @@

use transaction_pool;
use chain_spec::ChainSpec;
pub use substrate_executor::ExecutionStrategy;
pub use network::Role;
pub use network::NetworkConfiguration;
pub use client_db::PruningMode;
Expand All@@ -44,6 +45,8 @@ pub struct Configuration {
pub telemetry: Option<String>,
/// Node name.
pub name: String,
/// Execution strategy.
pub execution_strategy: ExecutionStrategy,
}

impl Configuration {
Expand All@@ -60,6 +63,7 @@ impl Configuration {
keys: Default::default(),
telemetry: Default::default(),
pruning: PruningMode::ArchiveAll,
execution_strategy: ExecutionStrategy::Both,
};
configuration.network.boot_nodes = configuration.chain_spec.boot_nodes().to_vec();
configuration
Expand Down
4 changes: 2 additions & 2 deletions polkadot/service/src/lib.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -73,7 +73,7 @@ use tokio::runtime::TaskExecutor;

pub use self::error::{ErrorKind, Error};
pub use self::components::{Components, FullComponents, LightComponents};
pub use config::{Configuration, Role, PruningMode};
pub use config::{Configuration, Role, PruningMode, ExecutionStrategy};
pub use chain_spec::ChainSpec;

/// Polkadot service.
Expand DownExpand Up@@ -109,7 +109,7 @@ pub fn new_client(config: Configuration) -> Result<Arc<Client<
path: config.database_path.into(),
pruning: config.pruning,
};
let executor = polkadot_executor::Executor::new();
let executor = polkadot_executor::Executor::with_default_strategy(config.execution_strategy);
let is_validator = (config.roles & Role::AUTHORITY) == Role::AUTHORITY;
let components = components::FullComponents { is_validator };
let (client, _) = components.build_client(db_settings, executor, &config.chain_spec)?;
Expand Down
1 change: 1 addition & 0 deletions substrate/client/src/client.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -326,6 +326,7 @@ impl<B, E, Block> Client<B, E, Block> where
&mut overlay,
"execute_block",
&<Block as BlockT>::new(header.clone(), body.clone().unwrap_or_default()).encode()
// TODO: intercept Err::ConsensusFailure, report failure wrt block and then accept wasm result.
)?;

Some(storage_update)
Expand Down
6 changes: 6 additions & 0 deletions substrate/executor/src/error.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -74,5 +74,11 @@ error_chain! {
description("invalid memory reference"),
display("Invalid memory reference"),
}

/// Consensus failure.
ConsensusFailure(wasm_result: Box<Result<Vec<u8>>>, native_result: Box<Result<Vec<u8>>>) {
description("consensus failure"),
display("Differing results from Wasm execution and native dispatch"),
}
}
}
2 changes: 1 addition & 1 deletion substrate/executor/src/lib.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -66,7 +66,7 @@ mod sandbox;

pub mod error;
pub use wasm_executor::WasmExecutor;
pub use native_executor::{with_native_environment, NativeExecutor, NativeExecutionDispatch};
pub use native_executor::{with_native_environment, NativeExecutor, NativeExecutionDispatch, ExecutionStrategy};
pub use state_machine::Externalities;
pub use runtime_version::RuntimeVersion;
pub use codec::Slicable;
Expand Down
87 changes: 66 additions & 21 deletions substrate/executor/src/native_executor.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -27,14 +27,14 @@ use parking_lot::{Mutex, MutexGuard};
use RuntimeInfo;

// For the internal Runtime Cache:
// Do we run this natively or use the given WasmModule
enum RunWith {
// Is it compatible enough to run this natively or do we need to fall back on the WasmModule
enum Compatibility {
InvalidVersion(WasmModule),
NativeRuntime(RuntimeVersion),
WasmRuntime(RuntimeVersion, WasmModule)
IsCompatible(RuntimeVersion),
NotCompatible(RuntimeVersion, WasmModule)
}

type CacheType = HashMap<u64, RunWith>;
type CacheType = HashMap<u64, Compatibility>;

lazy_static! {
static ref RUNTIMES_CACHE: Mutex<CacheType> = Mutex::new(HashMap::new());
Expand All@@ -51,29 +51,28 @@ fn gen_cache_key(code: &[u8]) -> u64 {
}

/// fetch a runtime version from the cache or if there is no cached version yet, create
/// the runtime version entry for `code`, determines whether `RunWith::NativeRuntime`
/// the runtime version entry for `code`, determines whether `Compatibility::IsCompatible`
/// can be used by by comparing returned RuntimeVersion to `ref_version`
fn fetch_cached_runtime_version<'a, E: Externalities>(
cache: &'a mut MutexGuard<CacheType>,
ext: &mut E,
code: &[u8],
ref_version: RuntimeVersion
) -> &'a RunWith {
) -> &'a Compatibility {
cache.entry(gen_cache_key(code))
.or_insert_with(|| {
let module = WasmModule::from_buffer(code).expect("all modules compiled with rustc are valid wasm code; qed");
let version = WasmExecutor.call_in_wasm_module(ext, &module, "version", &[]).ok()
.and_then(|v| RuntimeVersion::decode(&mut v.as_slice()));


if let Some(v) = version {
if ref_version.can_call_with(&v) {
RunWith::NativeRuntime(v)
Compatibility::IsCompatible(v)
} else {
RunWith::WasmRuntime(v, module)
Compatibility::NotCompatible(v, module)
}
} else {
RunWith::InvalidVersion(module)
Compatibility::InvalidVersion(module)
}
})
}
Expand DownExpand Up@@ -106,31 +105,78 @@ pub trait NativeExecutionDispatch {
const VERSION: RuntimeVersion;
}

/// Strategy for executing a call into the runtime.
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
pub enum ExecutionStrategy {
/// Execute with the native equivalent if it is compatible with the given wasm module; otherwise fall back to the wasm.
NativeWhenPossible,
/// Use the given wasm module.
AlwaysWasm,
/// Run with both the wasm and the native variant (if compatible). Report any discrepency as an error.
Both,
}

/// A generic `CodeExecutor` implementation that uses a delegate to determine wasm code equivalence
/// and dispatch to native code when possible, falling back on `WasmExecutor` when not.
#[derive(Debug)]
pub struct NativeExecutor<D: NativeExecutionDispatch + Sync + Send> {
/// The strategy for execution.
strategy: ExecutionStrategy,
/// Dummy field to avoid the compiler complaining about us not using `D`.
_dummy: ::std::marker::PhantomData<D>,
}

impl<D: NativeExecutionDispatch + Sync + Send> NativeExecutor<D> {
/// Create new instance.
pub fn new() -> Self {
pub fn new(strategy: ExecutionStrategy) -> Self {
// FIXME: set this entry at compile time
RUNTIMES_CACHE.lock().insert(
gen_cache_key(D::native_equivalent()),
RunWith::NativeRuntime(D::VERSION));
Compatibility::IsCompatible(D::VERSION));

NativeExecutor {
strategy,
_dummy: Default::default(),
}
}

fn call_with_strategy<E: Externalities>(
&self,
ext: &mut E,
code: &[u8],
method: &str,
data: &[u8],
strategy: ExecutionStrategy,
) -> Result<Vec<u8>> {
let mut c = RUNTIMES_CACHE.lock();
match (strategy, fetch_cached_runtime_version(&mut c, ext, code, D::VERSION)) {
(_, Compatibility::NotCompatible(_, m)) => WasmExecutor.call_in_wasm_module(ext, m, method, data),
(_, Compatibility::InvalidVersion(m)) => WasmExecutor.call_in_wasm_module(ext, m, method, data),
(ExecutionStrategy::AlwaysWasm, _) => WasmExecutor.call(ext, code, method, data),
(ExecutionStrategy::NativeWhenPossible, _) => D::dispatch(ext, method, data),
_ => {
// both
let w = WasmExecutor.call(ext, code, method, data);
let n = D::dispatch(ext, method, data);
let same_ok = if let (&Ok(ref w), &Ok(ref n)) = (&w, &n) {
w == n
} else { false };
if same_ok {
return w
}
if w.is_err() && n.is_err() && format!("{:?}", w) == format!("{:?}", n) {
return w
}
Err(ErrorKind::ConsensusFailure(Box::new(w), Box::new(n)).into())
}
}
}
}

impl<D: NativeExecutionDispatch + Sync + Send> Clone for NativeExecutor<D> {
fn clone(&self) -> Self {
NativeExecutor {
strategy: self.strategy,
_dummy: Default::default(),
}
}
Expand All@@ -146,8 +192,8 @@ impl<D: NativeExecutionDispatch + Sync + Send> RuntimeInfo for NativeExecutor<D>
) -> Option<RuntimeVersion> {
let mut c = RUNTIMES_CACHE.lock();
match fetch_cached_runtime_version(&mut c, ext, code, D::VERSION) {
RunWith::NativeRuntime(v) | RunWith::WasmRuntime(v, _) => Some(v.clone()),
RunWith::InvalidVersion(_m) => None
Compatibility::IsCompatible(v) | Compatibility::NotCompatible(v, _) => Some(v.clone()),
Compatibility::InvalidVersion(_m) => None
}
}
}
Expand All@@ -162,11 +208,7 @@ impl<D: NativeExecutionDispatch + Sync + Send> CodeExecutor for NativeExecutor<D
method: &str,
data: &[u8],
) -> Result<Vec<u8>> {
let mut c = RUNTIMES_CACHE.lock();
match fetch_cached_runtime_version(&mut c, ext, code, D::VERSION) {
RunWith::NativeRuntime(_v) => D::dispatch(ext, method, data),
RunWith::WasmRuntime(_, m) | RunWith::InvalidVersion(m) => WasmExecutor.call_in_wasm_module(ext, m, method, data)
}
self.call_with_strategy(ext, code, method, data, self.strategy)
}
}

Expand DownExpand Up@@ -198,7 +240,10 @@ macro_rules! native_executor_instance {

impl $name {
pub fn new() -> $crate::NativeExecutor<$name> {
$crate::NativeExecutor::new()
$crate::NativeExecutor::new($crate::ExecutionStrategy::NativeWhenPossible)
}
pub fn with_default_strategy(strategy: $crate::ExecutionStrategy) -> $crate::NativeExecutor<$name> {
$crate::NativeExecutor::new(strategy)
}
}
}
Expand Down
, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })(); Allow execution with both native & wasm by gavofyork · Pull Request #309 · paritytech/substrate · GitHub
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 all 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
6 changes: 5 additions & 1 deletion polkadot/cli/src/cli.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -74,7 +74,7 @@ args:
- pruning:
long: pruning
value_name: PRUNING_MODE
help: Specify the pruning mode. (a number of blocks to keep or "archive"). Default is 256.
help: Specify the pruning mode, a number of blocks to keep or "archive". Default is 256.
takes_value: true
- name:
long: name
Expand All@@ -95,6 +95,10 @@ args:
value_name: TELEMETRY_URL
help: The URL of the telemetry server. Implies --telemetry
takes_value: true
- execution:
long: execution
value_name: STRATEGY
help: The means of execution used when calling into the runtime. Can be either wasm, native or both.
subcommands:
- build-spec:
about: Build a spec.json file, outputing to stdout
Expand Down
16 changes: 15 additions & 1 deletion polkadot/cli/src/lib.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -218,18 +218,32 @@ pub fn run<I, T, W>(args: I, worker: W) -> error::Result<()> where
if matches.is_present("collator") {
info!("Starting collator");
// TODO [rob]: collation node implementation
service::Role::FULL
// This isn't a thing. Different parachains will have their own collator executables and
// maybe link to libpolkadot to get a light-client.
service::Role::LIGHT
} else if matches.is_present("light") {
info!("Starting (light)");
config.execution_strategy = service::ExecutionStrategy::NativeWhenPossible;
service::Role::LIGHT
} else if matches.is_present("validator") || matches.is_present("dev") {
info!("Starting validator");
config.execution_strategy = service::ExecutionStrategy::Both;
service::Role::AUTHORITY
} else {
info!("Starting (heavy)");
config.execution_strategy = service::ExecutionStrategy::NativeWhenPossible;
service::Role::FULL
};

if let Some(s) = matches.value_of("pruning") {
config.execution_strategy = match s {
"both" => service::ExecutionStrategy::Both,
"native" => service::ExecutionStrategy::NativeWhenPossible,
"wasm" => service::ExecutionStrategy::AlwaysWasm,
_ => return Err(error::ErrorKind::Input("Invalid execution mode specified".to_owned()).into()),
};
}

config.roles = role;
{
config.network.boot_nodes.extend(matches
Expand Down
4 changes: 4 additions & 0 deletions polkadot/service/src/config.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -18,6 +18,7 @@

use transaction_pool;
use chain_spec::ChainSpec;
pub use substrate_executor::ExecutionStrategy;
pub use network::Role;
pub use network::NetworkConfiguration;
pub use client_db::PruningMode;
Expand All@@ -44,6 +45,8 @@ pub struct Configuration {
pub telemetry: Option<String>,
/// Node name.
pub name: String,
/// Execution strategy.
pub execution_strategy: ExecutionStrategy,
}

impl Configuration {
Expand All@@ -60,6 +63,7 @@ impl Configuration {
keys: Default::default(),
telemetry: Default::default(),
pruning: PruningMode::ArchiveAll,
execution_strategy: ExecutionStrategy::Both,
};
configuration.network.boot_nodes = configuration.chain_spec.boot_nodes().to_vec();
configuration
Expand Down
4 changes: 2 additions & 2 deletions polkadot/service/src/lib.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -73,7 +73,7 @@ use tokio::runtime::TaskExecutor;

pub use self::error::{ErrorKind, Error};
pub use self::components::{Components, FullComponents, LightComponents};
pub use config::{Configuration, Role, PruningMode};
pub use config::{Configuration, Role, PruningMode, ExecutionStrategy};
pub use chain_spec::ChainSpec;

/// Polkadot service.
Expand DownExpand Up@@ -109,7 +109,7 @@ pub fn new_client(config: Configuration) -> Result<Arc<Client<
path: config.database_path.into(),
pruning: config.pruning,
};
let executor = polkadot_executor::Executor::new();
let executor = polkadot_executor::Executor::with_default_strategy(config.execution_strategy);
let is_validator = (config.roles & Role::AUTHORITY) == Role::AUTHORITY;
let components = components::FullComponents { is_validator };
let (client, _) = components.build_client(db_settings, executor, &config.chain_spec)?;
Expand Down
1 change: 1 addition & 0 deletions substrate/client/src/client.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -326,6 +326,7 @@ impl<B, E, Block> Client<B, E, Block> where
&mut overlay,
"execute_block",
&<Block as BlockT>::new(header.clone(), body.clone().unwrap_or_default()).encode()
// TODO: intercept Err::ConsensusFailure, report failure wrt block and then accept wasm result.
)?;

Some(storage_update)
Expand Down
6 changes: 6 additions & 0 deletions substrate/executor/src/error.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -74,5 +74,11 @@ error_chain! {
description("invalid memory reference"),
display("Invalid memory reference"),
}

/// Consensus failure.
ConsensusFailure(wasm_result: Box<Result<Vec<u8>>>, native_result: Box<Result<Vec<u8>>>) {
description("consensus failure"),
display("Differing results from Wasm execution and native dispatch"),
}
}
}
2 changes: 1 addition & 1 deletion substrate/executor/src/lib.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -66,7 +66,7 @@ mod sandbox;

pub mod error;
pub use wasm_executor::WasmExecutor;
pub use native_executor::{with_native_environment, NativeExecutor, NativeExecutionDispatch};
pub use native_executor::{with_native_environment, NativeExecutor, NativeExecutionDispatch, ExecutionStrategy};
pub use state_machine::Externalities;
pub use runtime_version::RuntimeVersion;
pub use codec::Slicable;
Expand Down
87 changes: 66 additions & 21 deletions substrate/executor/src/native_executor.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -27,14 +27,14 @@ use parking_lot::{Mutex, MutexGuard};
use RuntimeInfo;

// For the internal Runtime Cache:
// Do we run this natively or use the given WasmModule
enum RunWith {
// Is it compatible enough to run this natively or do we need to fall back on the WasmModule
enum Compatibility {
InvalidVersion(WasmModule),
NativeRuntime(RuntimeVersion),
WasmRuntime(RuntimeVersion, WasmModule)
IsCompatible(RuntimeVersion),
NotCompatible(RuntimeVersion, WasmModule)
}

type CacheType = HashMap<u64, RunWith>;
type CacheType = HashMap<u64, Compatibility>;

lazy_static! {
static ref RUNTIMES_CACHE: Mutex<CacheType> = Mutex::new(HashMap::new());
Expand All@@ -51,29 +51,28 @@ fn gen_cache_key(code: &[u8]) -> u64 {
}

/// fetch a runtime version from the cache or if there is no cached version yet, create
/// the runtime version entry for `code`, determines whether `RunWith::NativeRuntime`
/// the runtime version entry for `code`, determines whether `Compatibility::IsCompatible`
/// can be used by by comparing returned RuntimeVersion to `ref_version`
fn fetch_cached_runtime_version<'a, E: Externalities>(
cache: &'a mut MutexGuard<CacheType>,
ext: &mut E,
code: &[u8],
ref_version: RuntimeVersion
) -> &'a RunWith {
) -> &'a Compatibility {
cache.entry(gen_cache_key(code))
.or_insert_with(|| {
let module = WasmModule::from_buffer(code).expect("all modules compiled with rustc are valid wasm code; qed");
let version = WasmExecutor.call_in_wasm_module(ext, &module, "version", &[]).ok()
.and_then(|v| RuntimeVersion::decode(&mut v.as_slice()));


if let Some(v) = version {
if ref_version.can_call_with(&v) {
RunWith::NativeRuntime(v)
Compatibility::IsCompatible(v)
} else {
RunWith::WasmRuntime(v, module)
Compatibility::NotCompatible(v, module)
}
} else {
RunWith::InvalidVersion(module)
Compatibility::InvalidVersion(module)
}
})
}
Expand DownExpand Up@@ -106,31 +105,78 @@ pub trait NativeExecutionDispatch {
const VERSION: RuntimeVersion;
}

/// Strategy for executing a call into the runtime.
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
pub enum ExecutionStrategy {
/// Execute with the native equivalent if it is compatible with the given wasm module; otherwise fall back to the wasm.
NativeWhenPossible,
/// Use the given wasm module.
AlwaysWasm,
/// Run with both the wasm and the native variant (if compatible). Report any discrepency as an error.
Both,
}

/// A generic `CodeExecutor` implementation that uses a delegate to determine wasm code equivalence
/// and dispatch to native code when possible, falling back on `WasmExecutor` when not.
#[derive(Debug)]
pub struct NativeExecutor<D: NativeExecutionDispatch + Sync + Send> {
/// The strategy for execution.
strategy: ExecutionStrategy,
/// Dummy field to avoid the compiler complaining about us not using `D`.
_dummy: ::std::marker::PhantomData<D>,
}

impl<D: NativeExecutionDispatch + Sync + Send> NativeExecutor<D> {
/// Create new instance.
pub fn new() -> Self {
pub fn new(strategy: ExecutionStrategy) -> Self {
// FIXME: set this entry at compile time
RUNTIMES_CACHE.lock().insert(
gen_cache_key(D::native_equivalent()),
RunWith::NativeRuntime(D::VERSION));
Compatibility::IsCompatible(D::VERSION));

NativeExecutor {
strategy,
_dummy: Default::default(),
}
}

fn call_with_strategy<E: Externalities>(
&self,
ext: &mut E,
code: &[u8],
method: &str,
data: &[u8],
strategy: ExecutionStrategy,
) -> Result<Vec<u8>> {
let mut c = RUNTIMES_CACHE.lock();
match (strategy, fetch_cached_runtime_version(&mut c, ext, code, D::VERSION)) {
(_, Compatibility::NotCompatible(_, m)) => WasmExecutor.call_in_wasm_module(ext, m, method, data),
(_, Compatibility::InvalidVersion(m)) => WasmExecutor.call_in_wasm_module(ext, m, method, data),
(ExecutionStrategy::AlwaysWasm, _) => WasmExecutor.call(ext, code, method, data),
(ExecutionStrategy::NativeWhenPossible, _) => D::dispatch(ext, method, data),
_ => {
// both
let w = WasmExecutor.call(ext, code, method, data);
let n = D::dispatch(ext, method, data);
let same_ok = if let (&Ok(ref w), &Ok(ref n)) = (&w, &n) {
w == n
} else { false };
if same_ok {
return w
}
if w.is_err() && n.is_err() && format!("{:?}", w) == format!("{:?}", n) {
return w
}
Err(ErrorKind::ConsensusFailure(Box::new(w), Box::new(n)).into())
}
}
}
}

impl<D: NativeExecutionDispatch + Sync + Send> Clone for NativeExecutor<D> {
fn clone(&self) -> Self {
NativeExecutor {
strategy: self.strategy,
_dummy: Default::default(),
}
}
Expand All@@ -146,8 +192,8 @@ impl<D: NativeExecutionDispatch + Sync + Send> RuntimeInfo for NativeExecutor<D>
) -> Option<RuntimeVersion> {
let mut c = RUNTIMES_CACHE.lock();
match fetch_cached_runtime_version(&mut c, ext, code, D::VERSION) {
RunWith::NativeRuntime(v) | RunWith::WasmRuntime(v, _) => Some(v.clone()),
RunWith::InvalidVersion(_m) => None
Compatibility::IsCompatible(v) | Compatibility::NotCompatible(v, _) => Some(v.clone()),
Compatibility::InvalidVersion(_m) => None
}
}
}
Expand All@@ -162,11 +208,7 @@ impl<D: NativeExecutionDispatch + Sync + Send> CodeExecutor for NativeExecutor<D
method: &str,
data: &[u8],
) -> Result<Vec<u8>> {
let mut c = RUNTIMES_CACHE.lock();
match fetch_cached_runtime_version(&mut c, ext, code, D::VERSION) {
RunWith::NativeRuntime(_v) => D::dispatch(ext, method, data),
RunWith::WasmRuntime(_, m) | RunWith::InvalidVersion(m) => WasmExecutor.call_in_wasm_module(ext, m, method, data)
}
self.call_with_strategy(ext, code, method, data, self.strategy)
}
}

Expand DownExpand Up@@ -198,7 +240,10 @@ macro_rules! native_executor_instance {

impl $name {
pub fn new() -> $crate::NativeExecutor<$name> {
$crate::NativeExecutor::new()
$crate::NativeExecutor::new($crate::ExecutionStrategy::NativeWhenPossible)
}
pub fn with_default_strategy(strategy: $crate::ExecutionStrategy) -> $crate::NativeExecutor<$name> {
$crate::NativeExecutor::new(strategy)
}
}
}
Expand Down