Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add ability to import methods guarded by protocol feature #3826

Merged
merged 3 commits into from
Jan 21, 2021
Merged
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
146 changes: 81 additions & 65 deletions runtime/near-vm-runner/src/imports.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use near_primitives::version::ProtocolVersion;
use near_vm_logic::VMLogic;

use std::ffi::c_void;
Expand All @@ -24,93 +25,105 @@ macro_rules! rust2wasm {
}

macro_rules! wrapped_imports {
( $( $func:ident < [ $( $arg_name:ident : $arg_type:ident ),* ] -> [ $( $returns:ident ),* ] >, )* ) => {
( $($(#[$feature_name:tt, $feature:ident])* $func:ident < [ $( $arg_name:ident : $arg_type:ident ),* ] -> [ $( $returns:ident ),* ] >, )* ) => {
pub mod wasmer_ext {
use near_vm_logic::VMLogic;
use wasmer_runtime::Ctx;
type VMResult<T> = ::std::result::Result<T, near_vm_logic::VMLogicError>;
$(
#[allow(unused_parens)]
pub fn $func( ctx: &mut Ctx, $( $arg_name: $arg_type ),* ) -> VMResult<($( $returns ),*)> {
let logic: &mut VMLogic<'_> = unsafe { &mut *(ctx.data as *mut VMLogic<'_>) };
logic.$func( $( $arg_name, )* )
}
)*
use near_vm_logic::VMLogic;
use wasmer_runtime::Ctx;
type VMResult<T> = ::std::result::Result<T, near_vm_logic::VMLogicError>;
$(
#[allow(unused_parens)]
$(#[cfg(feature = $feature_name)])*
pub fn $func( ctx: &mut Ctx, $( $arg_name: $arg_type ),* ) -> VMResult<($( $returns ),*)> {
let logic: &mut VMLogic<'_> = unsafe { &mut *(ctx.data as *mut VMLogic<'_>) };
logic.$func( $( $arg_name, )* )
}
)*
}

#[cfg(feature = "wasmtime_vm")]
pub mod wasmtime_ext {
use near_vm_logic::{VMLogic, VMLogicError};
use std::ffi::c_void;
use std::cell::{RefCell, UnsafeCell};
use wasmtime::Trap;
use near_vm_logic::{VMLogic, VMLogicError};
use std::ffi::c_void;
use std::cell::{RefCell, UnsafeCell};
use wasmtime::Trap;

thread_local! {
pub static CALLER_CONTEXT: UnsafeCell<*mut c_void> = UnsafeCell::new(0 as *mut c_void);
pub static EMBEDDER_ERROR: RefCell<Option<VMLogicError>> = RefCell::new(None);
}
thread_local! {
pub static CALLER_CONTEXT: UnsafeCell<*mut c_void> = UnsafeCell::new(0 as *mut c_void);
pub static EMBEDDER_ERROR: RefCell<Option<VMLogicError>> = RefCell::new(None);
}

type VMResult<T> = ::std::result::Result<T, Trap>;
$(
#[allow(unused_parens)]
#[cfg(feature = "wasmtime_vm")]
pub fn $func( $( $arg_name: rust2wasm!($arg_type) ),* ) -> VMResult<($( rust2wasm!($returns)),*)> {
let data = CALLER_CONTEXT.with(|caller_context| {
unsafe {
*caller_context.get()
}
});
let logic: &mut VMLogic<'_> = unsafe { &mut *(data as *mut VMLogic<'_>) };
match logic.$func( $( $arg_name as $arg_type, )* ) {
Ok(result) => Ok(result as ($( rust2wasm!($returns) ),* ) ),
Err(err) => {
// Wasmtime doesn't have proper mechanism for wrapping custom errors
// into traps. So, just store error into TLS and use special exit code here.
EMBEDDER_ERROR.with(|embedder_error| {
*embedder_error.borrow_mut() = Some(err)
});
Err(Trap::i32_exit(239))
type VMResult<T> = ::std::result::Result<T, Trap>;
$(
#[allow(unused_parens)]
#[cfg(all(feature = "wasmtime_vm" $(, feature = $feature_name)*))]
pub fn $func( $( $arg_name: rust2wasm!($arg_type) ),* ) -> VMResult<($( rust2wasm!($returns)),*)> {
let data = CALLER_CONTEXT.with(|caller_context| {
unsafe {
*caller_context.get()
}
});
let logic: &mut VMLogic<'_> = unsafe { &mut *(data as *mut VMLogic<'_>) };
match logic.$func( $( $arg_name as $arg_type, )* ) {
Ok(result) => Ok(result as ($( rust2wasm!($returns) ),* ) ),
Err(err) => {
// Wasmtime doesn't have proper mechanism for wrapping custom errors
// into traps. So, just store error into TLS and use special exit code here.
EMBEDDER_ERROR.with(|embedder_error| {
*embedder_error.borrow_mut() = Some(err)
});
Err(Trap::i32_exit(239))
}
}
}
}
)*
)*
}

pub(crate) fn build_wasmer(memory: wasmer_runtime::memory::Memory, logic: &mut VMLogic<'_>) ->
wasmer_runtime::ImportObject {
#[allow(unused_variables)]
pub(crate) fn build_wasmer(
memory: wasmer_runtime::memory::Memory,
logic: &mut VMLogic<'_>,
protocol_version: ProtocolVersion,
) -> wasmer_runtime::ImportObject {
let raw_ptr = logic as *mut _ as *mut c_void;
let import_reference = ImportReference(raw_ptr);
wasmer_runtime::imports! {
move || {
let dtor = (|_: *mut c_void| {}) as fn(*mut c_void);
(import_reference.0, dtor)
},
"env" => {
"memory" => memory,
$(
stringify!($func) => wasmer_runtime::func!(wasmer_ext::$func),
)*
},
}
let mut import_object = wasmer_runtime::ImportObject::new_with_data(move || {
let dtor = (|_: *mut c_void| {}) as fn(*mut c_void);
(import_reference.0, dtor)
});

let mut ns = wasmer_runtime_core::import::Namespace::new();
ns.insert("memory", memory);
$({
$(#[cfg(feature = $feature_name)])*
if true $(&& near_primitives::checked_feature!($feature_name, $feature, protocol_version))* {
ns.insert(stringify!($func), wasmer_runtime::func!(wasmer_ext::$func));
}
})*

import_object.register("env", ns);
import_object
}

#[cfg(feature = "wasmtime_vm")]
#[allow(unused_variables)]
pub(crate) fn link_wasmtime(
linker: &mut wasmtime::Linker,
memory: wasmtime::Memory,
raw_logic: *mut c_void,
) {
linker: &mut wasmtime::Linker,
memory: wasmtime::Memory,
raw_logic: *mut c_void,
protocol_version: ProtocolVersion,
) {
wasmtime_ext::CALLER_CONTEXT.with(|caller_context| {
unsafe {
*caller_context.get() = raw_logic
}
});
linker.define("env", "memory", memory).
expect("cannot define memory");
$(
linker.func("env", stringify!($func), wasmtime_ext::$func).
expect("cannot link external");
)*
linker.define("env", "memory", memory).expect("cannot define memory");
$({
$(#[cfg(feature = $feature_name)])*
if true $(&& near_primitives::checked_feature!($feature_name, $feature, protocol_version))* {
linker.func("env", stringify!($func), wasmtime_ext::$func).expect("cannot link external");
}
})*
}

#[cfg(feature = "wasmtime_vm")]
Expand Down Expand Up @@ -264,4 +277,7 @@ wrapped_imports! {
// ###############
validator_stake<[account_id_len: u64, account_id_ptr: u64, stake_ptr: u64] -> []>,
validator_total_stake<[stake_ptr: u64] -> []>,
// ###############
// An example to add a protocol feature guarded host method
// #["protocol_feature_evm", EVM] test_api<[a: u64] -> []>,
Copy link
Member

Choose a reason for hiding this comment

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

This example seems to be confusing, as evm is not executed in near-vm-runner. Should we use another information here?
Also, consider add a test to guarded api?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yes. The test is here: #3827

But I need to compile a contract with this import and check that it fails to run in the previous protocol version and runs in the new protocol version.

We can do this for a real new import. For example burn_near

}
2 changes: 1 addition & 1 deletion runtime/near-vm-runner/src/wasmer_runner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,7 @@ pub fn run_wasmer<'a>(
);
}

let import_object = imports::build_wasmer(memory_copy, &mut logic);
let import_object = imports::build_wasmer(memory_copy, &mut logic, current_protocol_version);

let method_name = match std::str::from_utf8(method_name) {
Ok(x) => x,
Expand Down
2 changes: 1 addition & 1 deletion runtime/near-vm-runner/src/wasmtime_runner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ pub mod wasmtime_runner {
// Unfortunately, due to the Wasmtime implementation we have to do tricks with the
// lifetimes of the logic instance and pass raw pointers here.
let raw_logic = &mut logic as *mut _ as *mut c_void;
imports::link_wasmtime(&mut linker, memory_copy, raw_logic);
imports::link_wasmtime(&mut linker, memory_copy, raw_logic, current_protocol_version);
let func_name = match str::from_utf8(method_name) {
Ok(name) => name,
Err(_) => {
Expand Down