-
Notifications
You must be signed in to change notification settings - Fork 632
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(runtime): support more than one VM. #2853
Conversation
That's a pretty lengthy change. Do we want to postpone it post-phase-I? (we already have a branch for such changes, it is called post-phase-1). |
Yeah, we could, although if not implemented, phase-1 will be Wasmer only. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks good to me, and I don't think it is too big, and it adds redundancy to catch errors, so I consider this to be a safe change if it does not affect performance too much.
|
||
fn read_memory(&self, offset: u64, buffer: &mut [u8]) { | ||
let offset = offset as usize; | ||
unsafe { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it enforced by MemoryLike trait contract that we can use unsafe
here? Can we have comments around every unsafe
usage on why it is safe and minimize the block to the specific unsafe expression, so it is easier to see which method is exactly unsafe?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, added comments.
) { | ||
Ok(x) => x, | ||
Err(_err) => panic!("Cannot create memory for a contract call"), | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can replace this match
with .expect()
method.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure, it was just moving existing code, although.
e8474df
to
3cd17b2
Compare
It adds a lot of code, but it is not invasive to our current code -- me and @olonho discussed hiding Wasmtime behind the compilation flag for now and not add it to the genesis config, and not expose the environment variable. We have several goals with this code:
The first bullet point is something we would like to do ASAP, which we can achieve by hiding Wasmtime behind a compilation flag. |
Cool, I have no concerns pushing this to master. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good, let's replace env variable with compilation flag and get rid of anyhow.
|
||
impl IntoVMError for anyhow::Error { | ||
fn into_vm_error(self) -> VMError { | ||
// TODO: incorrect |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you please elaborate this comment?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removed obsolete comment.
@@ -16,6 +16,8 @@ This crate implements the specification of the interface that Near blockchain ex | |||
cached = "0.12" | |||
wasmer-runtime = { version = "=0.17.0", features = ["default-backend-singlepass"], default-features = false } | |||
wasmer-runtime-core = { version = "=0.17.0" } | |||
wasmtime = "0.17.0" | |||
anyhow = "1.0.19" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In general, we try to avoid crates unless they are necessary. anyhow
is a nice create but a renown author, but it is not necessary for here. Also, Rust community is still figuring out how to deal with errors nicely, so anyhow
but become unpopular as quickly as it became popular. I suggest we don't us it here to avoid pulling in more dependencies.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This crate as needed, IMO, as otherwise I cannot implement IntoVMError for anyhow::Error, and this is an error returned by wasmtime::Linker::instantiate and function runner.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sounds bad that wasmtime doesn't wrap and export it's own error type
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see, my original comment is probably wrong anyway, since wasmtime pulls in this dependency anyway.
|
||
impl Default for VMKind { | ||
fn default() -> Self { | ||
match env::var("NEAR_VM") { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I suggest we don't use env variables, but use a conditional compilation here. Then we can have two different implementations of Default depending whether feature is on or off.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you please elaborate why you find compilation option preferable here? IMO runtime flag gives us more flexibility, and I'd love to keep it, if possible.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think @nearmax means a combination of feature flag + env var. Compiled with both feature flag gives you ability to "override runtime with env var", but default feature flag: default_runtime_is_wasmer, another feature flag: default_runtime_is_wasmtime
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The problem is that we have validators that run our code and overall experiment with it. The general principle that we hold is that it should not be easy to run a node in a not-safe mode, just to avoid potential human error. Therefore various code parameters that are considered to be dangerous we hide behind compilation flags. In the case of wasmtime we don't want some validator to turn it on, because they think it is a safe configuration parameter, like timeout on our RPC, cache size, etc. There were cases in the blockchain world, even with some renowned blockchains, where validators would accidentally run a misconfigured node and get slashed, i.e. lose a lot of assets. Since wasmtime was not thoroughly studied by us, and it uses optimized compilation it is not safe to be used. We were even thinking going as far as protecting these kinds of features by three factors: compilation flag, command line argument, and special marker file -- meaning one would have to provide all three to enable it. Additionally, environment variables is not something that we use in nearcore, env variables make configuration dependencies hard to track (i.e. one cannot tell from the signature of the module's functions/structs what is the input, since env variable is a global statically accessible input).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Makes sense. Fixed the default selection to be compilation feature.
type VMResult<T> = ::std::result::Result<T, Trap>; | ||
$( | ||
#[allow(unused_parens)] | ||
pub fn $func( $( $arg_name: rust2wasm!($arg_type) ),* ) -> VMResult<($( rust2wasm!($returns)),*)> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Cool trick.
None => panic!("Error is not properly set"), | ||
} | ||
} else { | ||
// VMError::FunctionCallError(LinkError { msg: format!("{:#?}", trap) }) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we remove it here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
if self.i32_exit_status() == Some(239) { | ||
trap_to_error(&self) | ||
} else { | ||
// VMError::FunctionCallError(LinkError { msg: format!("{:#?}", self) }) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we remove it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
))) | ||
) | ||
); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would add safety if tests here were testing for all variants of FunctionCallError
enum. It might go into a different PR.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agree, we generally need to think about better coverage.
9c3b840
to
5f27d5c
Compare
|
||
impl Default for VMKind { | ||
fn default() -> Self { | ||
match env::var("NEAR_VM") { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The problem is that we have validators that run our code and overall experiment with it. The general principle that we hold is that it should not be easy to run a node in a not-safe mode, just to avoid potential human error. Therefore various code parameters that are considered to be dangerous we hide behind compilation flags. In the case of wasmtime we don't want some validator to turn it on, because they think it is a safe configuration parameter, like timeout on our RPC, cache size, etc. There were cases in the blockchain world, even with some renowned blockchains, where validators would accidentally run a misconfigured node and get slashed, i.e. lose a lot of assets. Since wasmtime was not thoroughly studied by us, and it uses optimized compilation it is not safe to be used. We were even thinking going as far as protecting these kinds of features by three factors: compilation flag, command line argument, and special marker file -- meaning one would have to provide all three to enable it. Additionally, environment variables is not something that we use in nearcore, env variables make configuration dependencies hard to track (i.e. one cannot tell from the signature of the module's functions/structs what is the input, since env variable is a global statically accessible input).
@@ -16,6 +16,8 @@ This crate implements the specification of the interface that Near blockchain ex | |||
cached = "0.12" | |||
wasmer-runtime = { version = "=0.17.0", features = ["default-backend-singlepass"], default-features = false } | |||
wasmer-runtime-core = { version = "=0.17.0" } | |||
wasmtime = "0.17.0" | |||
anyhow = "1.0.19" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see, my original comment is probably wrong anyway, since wasmtime pulls in this dependency anyway.
@@ -16,6 +16,8 @@ This crate implements the specification of the interface that Near blockchain ex | |||
cached = "0.12" | |||
wasmer-runtime = { version = "=0.17.0", features = ["default-backend-singlepass"], default-features = false } | |||
wasmer-runtime-core = { version = "=0.17.0" } | |||
wasmtime = { version = "0.17.0", features = ["lightbeam"], default-features = false } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also note, that I tried to use not so optimizing lightbeam backend.
Test plan --------- NEAR_VM=wasmtime cargo test --package near-vm-runner --test test_error_cases -- --nocapture NEAR_VM=wasmer cargo test --package near-vm-runner --test test_error_cases -- --nocapture
Added support for basic work with Wasmtime 0.17. Error handling and
gas metering works the same way on all tests.
Test plan
Made most contract test to execute with two VMs and then
cargo test --all