Skip to content
This repository was archived by the owner on Nov 6, 2020. It is now read-only.

WASM contracts MVP #5679

Merged
merged 70 commits into from
Jul 10, 2017
Merged

WASM contracts MVP #5679

merged 70 commits into from
Jul 10, 2017

Conversation

NikVolf
Copy link
Contributor

@NikVolf NikVolf commented May 22, 2017

  • If wasm contracts are enabled by the engine, evm executor will choose wasm interpreter to run the code if also the contract code starts with wasm program header.

  • Wasm contracts can be called, but cannot call other contracts in the MVP

  • Wasm contracts can suicide

  • Wasm contracts can read and update storage, allocate memory and use most of the rust std lib (the only implication of it is the resulting size of the contract)

  • Build process for the contracts and use of the state api both can be found in wasm-tests repo:
    https://github.com/paritytech/wasm-tests (the parity also runs tests against them, compiled). Rust wasm support is not very mature still, requires nightly and some linker args magic to produce result we need (contributions and suggestions to improve the above are welcome)

  • parity-wasm repo is the general purpose crate to serialize/deserialize wasm and also hosts (probably temporary) the interpreter

  • wasm-utils repo contains utilities to manipulate wasm for parity-specific targets (optimize symbols, substitute allocators, inject gas counter)

@NikVolf NikVolf added A3-inprogress ⏳ Pull request is in progress. No review needed at this stage. M4-core ⛓ Core client code / Rust. labels May 22, 2017
@@ -51,6 +51,8 @@ semver = "0.6"
stats = { path = "../util/stats" }
time = "0.1"
transient-hashmap = "0.4"
parity-wasm = { git = "http://github.com/nikvolf/parity-wasm" }
Copy link
Collaborator

Choose a reason for hiding this comment

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

Should use https for that.

interpreter::UserFunctions {
executor: runtime,
functions: vec![
interpreter::UserFunction {
name: "_storage_read".to_owned(),
params: vec![elements::ValueType::I32, elements::ValueType::I32],
result: Some(elements::ValueType::I32),
params: vec![I32, I32],
Copy link
Contributor

Choose a reason for hiding this comment

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

since these all seem to be statically known, why not have it take a &'static [ValueType] instead of requiring allocation on each one?

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, this optimisation can be made right away

@@ -66,6 +67,8 @@ pub enum Error {
MutableCallInStaticContext,
/// Likely to cause consensus issues.
Internal(String),
/// Wasm runtime error
Wasm(String),
Copy link
Contributor

Choose a reason for hiding this comment

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

Is there something better we can give here than String? Maybe a WasmError enum with some number of "proper" error variants and one Unknown(String) variant.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I would avoid to wrap parity_wasm::interpreter::Error, because it cannot be later meaningfully encoded as trace error and put in trace db, that's why it's encoded as string

Also I cannot imagine any code that would rely on handling specific wasm interpreter errors

Copy link
Contributor

@eira-fransham eira-fransham Jul 10, 2017

Choose a reason for hiding this comment

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

As long as it's strictly for human-readable messages then that's fine. If we ever finding ourself matching on it we can refactor to make it an enum.

}
}

fn native_bindings<'a>(runtime: &'a mut Runtime) -> interpreter::UserFunctions<'a> {
Copy link
Contributor

@eira-fransham eira-fransham Jul 10, 2017

Choose a reason for hiding this comment

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

Unnecessary lifetime parameters, you can just write

fn native_bindings(runtime: &mut Runtime) -> interpreter::UserFunctions { 

Copy link
Contributor Author

@NikVolf NikVolf Jul 10, 2017

Choose a reason for hiding this comment

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

not working, btw, since interpreter::UserFunctions has an explicit lifetime parameter
see https://gitlab.parity.io/parity/parity/-/jobs/59538

Copy link
Contributor

@eira-fransham eira-fransham Jul 10, 2017

Choose a reason for hiding this comment

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

It's not because UserFunctions has a lifetime parameter, it's because Runtime has a lifetime parameter. &mut Runtime has two elided lifetime parameters, so when you had the lifetime parameter to the function it elided one of them and not the other. You do need the parameters in this case, sorry. I didn't know Runtime took a lifetime parameter.

Basically, it desugars to:

fn native_bindings<'a, 'b>(runtime: &'a mut Runtime<'b>) -> interpreter::UserFunctions<'???>

Since there are two parameters to choose from, it doesn't know which to pass to UserFunctions.

Also, I think that the b"\0asm" change will fail, since b"\0asm" has type &[u8; 4]. You need to do *b"\0asm" It's easy to miss, I should have explicitly pointed it out in my original comment.

}
}

fn h256_at(&self, ptr: WasmPtr) -> Result<H256, interpreter::Error> {
Copy link
Contributor

Choose a reason for hiding this comment

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

Can this lead to type confusion bugs? Can we have a WasmPtr carry a PhantomData to ensure that we deserialize to the correct type? If not, can we mark these functions as 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.

It's perfectly safe, because it works on bound-checked memory api

Copy link
Contributor

Choose a reason for hiding this comment

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

Bound-checked, sure, but that doesn't mean that it won't cause type confusion. We're for all intents and purposes doing unchecked mem::transmute to synthesise these values. It's not going to access uninitialized memory but it may cause us to interpret data as the wrong type.

self.memory.set(d_ptr, &d_buf)?;

// write call args to memory
self.memory.set(args_ptr, &call_args.address)?;
Copy link
Contributor

Choose a reason for hiding this comment

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

Hard-coded byte offsets here - can we use mem::size_of_value or abstract this some other way?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

No, it's crossplatform offset, we cannot rely on Rust memory layout, despite it may happen to have same value.

Copy link
Contributor

Choose a reason for hiding this comment

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

Ok, can we at least extract it to be more self-documenting, or use debug_assert_eq!(mem::size_of_value(something), 20) to make sure that we're not truncating data? Also, we can use non-target-dependent datatypes in a #[repr(C)] struct, which also has the benefit of allowing us to blit it to memory in a single call (even if it requires doing mem::transmute::<Foo, [u8; 100]>(my_struct) or whatever).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

ok, let's chat about this and WasmPtr later in more detail
I will update in following prs if the chat will produce some useful outcome

Copy link
Contributor

Choose a reason for hiding this comment

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

Sounds like a great idea. Don't get me wrong, the code looks good, I'm just pointing to some areas that might deserve a second look. There's nothing that I would argue against merging because of.

"_debug" => {
self.debug_log(context)
},
"gas" => {
Copy link
Contributor

Choose a reason for hiding this comment

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

Should this have an underscore like the rest? (I don't know, but it just sticks out so I thought I'd mention 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.

I think we'd better keep it this way for now
to distinguish injected functions from runtime
"gas" function is unique in a way it cannot be called ever directly

Copy link
Contributor

Choose a reason for hiding this comment

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

What happens if you compile a C file with a function called gas to wasm and run it as a contract? If we want it to be impossible (for some value of "impossible") to accidentally link a file with a conflicting name we can use non-ASCII or unprintable characters, like \0 or 💩.

Obviously if defining gas and compiling to wasm does nothing then you can just give it whatever name you like, I don't actually know the semantics here I'm just pointing out something that might cause a problem.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

You cannot have named functions in webassembly, only imports or exports are named
And if C program will import gas function explicitly, it will be invalid as a contract and will fail instantly, before even running (as wasm module validation fails when there are duplicated imports, there should be rule about it in webassembly official testsuite)

Copy link
Contributor

Choose a reason for hiding this comment

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

Sweet, LGTM then.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Still worth adding a explicit test for this case, thanks

@@ -34,6 +34,8 @@ pub use types::executed::{Executed, ExecutionResult};
/// Maybe something like here: `https://github.com/ethereum/libethereum/blob/4db169b8504f2b87f7d5a481819cfb959fc65f6c/libethereum/ExtVM.cpp`
const STACK_SIZE_PER_DEPTH: usize = 24*1024;

const WASM_MAGIC_NUMBER: [u8; 4] = [0x00, 0x61, 0x73, 0x6d];
Copy link
Contributor

Choose a reason for hiding this comment

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

This is just personal taste but if a byte array is supposed to be ASCII I'd rather write this as *b"\0asm" instead. It doesn't matter, but it might be something to keep in mind going forward.

@arkpar arkpar added A8-looksgood 🦄 Pull request is reviewed well. and removed A0-pleasereview 🤓 Pull request needs code review. labels Jul 10, 2017
@@ -34,7 +34,7 @@ pub use types::executed::{Executed, ExecutionResult};
/// Maybe something like here: `https://github.com/ethereum/libethereum/blob/4db169b8504f2b87f7d5a481819cfb959fc65f6c/libethereum/ExtVM.cpp`
const STACK_SIZE_PER_DEPTH: usize = 24*1024;

const WASM_MAGIC_NUMBER: [u8; 4] = b"\0asm";
const WASM_MAGIC_NUMBER: &'static [u8; 4] = b"\0asm";
Copy link
Contributor

@eira-fransham eira-fransham Jul 10, 2017

Choose a reason for hiding this comment

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

Alternatively (it doesn't matter in this case, but it might in the future):

const WASM_MAGIC_NUMBER: [u8; 4] = *b"\0asm";

Note the deref operator in the RHS. Also, Rust will automatically infer 'static in const and static items (so you can write &[u8; 4] for the type).

@gavofyork gavofyork merged commit 62210fb into master Jul 10, 2017
@gavofyork gavofyork deleted the wasm-mvp branch July 10, 2017 15:42
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
A8-looksgood 🦄 Pull request is reviewed well. M4-core ⛓ Core client code / Rust.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants