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(runtime): support more than one VM. #2853

Merged
merged 13 commits into from
Jun 24, 2020
Merged

feat(runtime): support more than one VM. #2853

merged 13 commits into from
Jun 24, 2020

Conversation

olonho
Copy link
Contributor

@olonho olonho commented Jun 15, 2020

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

@gitpod-io
Copy link

gitpod-io bot commented Jun 15, 2020

@SkidanovAlex
Copy link
Collaborator

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).

@olonho
Copy link
Contributor Author

olonho commented Jun 16, 2020

Yeah, we could, although if not implemented, phase-1 will be Wasmer only.

Copy link
Collaborator

@frol frol left a 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 {
Copy link
Collaborator

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?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, added comments.

Comment on lines 221 to 214
) {
Ok(x) => x,
Err(_err) => panic!("Cannot create memory for a contract call"),
};
Copy link
Collaborator

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.

Copy link
Contributor Author

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.

@MaksymZavershynskyi
Copy link
Contributor

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).

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:

  • Verify Wasmer correctness by comparing it against Wasmtime in our tests -- something that is very important to do ASAP, because we had encountered Wasmer bugs in the past;
  • Have Wasmtime as a backup runtime -- if we find some critical issue with Wasmer, we would like to be able to switch to Wasmtime without being delayed by R&D;
  • In a long-run we want to run Wasmer and Wasmtime side-by-side in production, and if node encounters a situation when they disagree it should panic. This will add a significant level of safety to our runtime.

The first bullet point is something we would like to do ASAP, which we can achieve by hiding Wasmtime behind a compilation flag.

@SkidanovAlex
Copy link
Collaborator

Cool, I have no concerns pushing this to master.

Copy link
Contributor

@MaksymZavershynskyi MaksymZavershynskyi left a 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
Copy link
Contributor

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?

Copy link
Contributor Author

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"
Copy link
Contributor

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.

Copy link
Contributor Author

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.

Copy link
Member

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

Copy link
Contributor

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") {
Copy link
Contributor

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.

Copy link
Contributor Author

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.

Copy link
Member

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

Copy link
Contributor

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).

Copy link
Contributor Author

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)),*)> {
Copy link
Contributor

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) })
Copy link
Contributor

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?

Copy link
Contributor Author

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) })
Copy link
Contributor

Choose a reason for hiding this comment

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

Should we remove it?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

)))
)
);
});
Copy link
Contributor

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.

Copy link
Contributor Author

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.

@olonho olonho force-pushed the wasmtime branch 3 times, most recently from 9c3b840 to 5f27d5c Compare June 20, 2020 08:41

impl Default for VMKind {
fn default() -> Self {
match env::var("NEAR_VM") {
Copy link
Contributor

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"
Copy link
Contributor

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 }
Copy link
Contributor Author

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.

deny.toml Show resolved Hide resolved
runtime/near-vm-runner/src/imports.rs Show resolved Hide resolved
runtime/near-vm-runner/src/runner.rs Show resolved Hide resolved
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
@olonho olonho merged commit 3e5cf61 into master Jun 24, 2020
@olonho olonho deleted the wasmtime branch June 24, 2020 07:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants