Blockchain-flavored WASI

General purpose computation on the blockchain using Web Assembly System Interface (WASI).

Nick Hynes
Oasis Labs
Published in
9 min readMay 23, 2019

--

Blockchain WASI

While cloud computing has long brought cost and ease of use, switching from an on-prem solution to cloud has traditionally come with its own inherent risks including a degradation in security and a lack of auditability. These are areas that have the potential to be solved with new emerging technologies including Web Assembly, the Web Assembly System Interface, and blockchain.

In this blogpost we propose a mechanism for trustworthy, uncensorable, and autonomous cloud computation based on the combination of three emerging technologies: Web Assembly, the Web Assembly System Interface, and blockchain.

This is a technical architecture we are currently implementing into the Oasis platform now, that we hope to have complete in the coming months.

The Ingredients: Blockchain, Wasm, & WASI

Blockchain

From a high level, a blockchain is described by the trio of an indelible state, a set of rules for modifying state, and a mechanism for network participants to reach consensus on the canonical next state possibly in the face of Sybils or Byzantine nodes. There are a variety of options for each component and each component can be chosen independently. For instance, Bitcoin has UTXO+transactions+proof-of-work, Ethereum has accounts+smart contracts+proof-of-work (soon to be CBC Casper), and Oasis has accounts+confidential smart contracts+BFT.

Let’s focus in on the second component: the state transition function, and smart contracts in particular. In abstract, a smart contract is an arbitrary function from the current state to the next. Smart contracts give developers enormous flexibility in creating trustworthy, uncensorable backends for their distributed apps. Needless to say, it is a Good Thing™ if smart contracts are pleasant to write and efficient when executed in the platform’s runtime.

The most popular smart contract runtime is currently the Ethereum Virtual Machine (EVM) and is programmed (mostly) using Solidity. Though the EVM is Turing complete and technically as powerful as the JVM, Lua, or Python, it has a more limited range of use and thus lacks the tooling and performance optimizations enjoyed by non-blockchain runtimes. Of course, the EVM was designed with specific goals: most importantly, to provide a cross-platform, deterministic, sandboxed runtime with small bytecodes. However, blockchains are increasingly considered as platforms for general-purpose computation.

Fully capitalizing these platforms requires providing non-blockchain developers the standard tools and abstractions with which they are already familiar. Fortunately, there has, recently, been significant work in the web community on developing an efficient web runtime that shares many of the same goals! Enter Web Assembly, or Wasm for short.

Web Assembly (Wasm)

Web Assembly (Wasm) is a compact bytecode designed for efficient execution in the browser. It’s like JavaScript, but with better (or at least more predictable) performance.

The goals of Wasm are:

  • Cross-platform: users access the web from a variety of devices
  • Compactness: minimizing download size improves user experience
  • Sandboxing: a user’s computer should not be attackable from a web page
  • Performance: Wasm is closer to machine code than JS and should run faster than JS (at least before the JIT warms up)

The goals of the EVM are:

  • Cross-platform: Ethereum is a global computer and should run on a diversity of platforms
  • Compactness: binaries should be small since they’re stored on the Blockchain forever
  • Sandboxing: miners’ computers should not be compromised by smart contracts’ code
  • Determinism: honest miners must agree on the same next state, so a smart contract must yield identical results on each

Comparing Wasm to EVM, Wasm favors performance over determinism, but otherwise the two have similar goals. Despite this, the Wasm spec is completely deterministic except for the handling of floating point NaN, which can be handled by strengthening the spec such that the representation defined by the IEEE 754–2008 revision is canonical and adding runtime checks around floating point operations when running on architectures that do not comply with the revised specification. With this modification, Wasm is truly a drop-in replacement — at least at the operational level. What’s missing is a way to access blockchain state. In EVM, a smart contract can access state using dedicated opcodes like SLOAD and SSTORE that ask the runtime for external information. Although there's no equivalent in Wasm, a module can import functions from the environment in much the same way as an ES6 module. These imports are resolved by the Wasm runtime and can allow access to the outside world. All we would have to do, then, is export state manipulation functions from the Wasm runtime. And, indeed, that is exactly what is done in Ethereum-flavored Wasm (ewasm) and Parity's pwasm. The only drawback is that programs must be explicitly written to use the [ep]wasm exports. Better would be a consistent interface that could be used by programs both on and off of the blockchain. Fortunately, (again) the wider web and systems community has proposed a solution that is equally applicable to blockchain: WASI.

Web Assembly System Interface (WASI)

The Web Assembly System Interface (WASI) is an emerging standard that defines a set of runtime exports, or syscalls, that allow a Wasm module to interact with the host OS. It’s akin to how Node.js has POSIX syscalls (via libuv) and the browser has DOM “syscalls.” The goal of WASI is to enable write-once, run-anywhere development for non-browser platforms just as Emscripten does for the browser. One of the most compelling aspects of WASI is that it is backed by a burgeoning community. Rust already has a wasm32-wasi compile target and there are several runtimes which implement the spec; and more tooling and language support is on the way.

From an implementation perspective, WASI makes implementing a system interface much easier. Consider the following simple program in Rust that opens stdout, writes some text, and calls it a day. Ideally it would compile to Wasm and run in our runtime.

fn main() {
println!("Hello from Wasm world!");
}

Unfortunately, the vanilla wasm32-unknown-unknown target will not work since the -unknown OS does not have a filesystem backend. Until WASI, the only alternative was to use wasm32-unknown-emscriptien. Emscripten gets the job done but requires a hefty set of imports:

abort
abortOnCannotGrowMemory
memory
table
DYNAMICTOP_PTR
_abort
_dladdr
_emscripten_get_heap_size
_emscripten_memcpy_big
_emscripten_resize_heap
_getenv
_llvm_trap
_pthread_rwlock_rdlock
__Unwind_Backtrace
__Unwind_FindEnclosingFunction
__Unwind_GetIPInfo
__table_base
___setErrNo
___syscall4 # write
___syscall5 # open
___syscall6 # close
___syscall146 # fcntl64
___syscall221 # writev

More complicated programs could require any of the 350 available syscalls — many of which don’t cleanly map to a Wasm environment — and a potentially unbounded number of invoke/dynCalls.

Compiling with wasm32-wasi gives a much more tractable list of imports:

args_get
args_sizes_get
environ_get
environ_sizes_get
fd_fdstat_get
fd_prestat_dir_name
fd_prestat_get
fd_write
proc_exit

Moreover, the core specification has a total of 44 syscalls, and all of them have straightforward implementations. Thus, WASI offers a way to run mostly-unmodified programs in a sandboxed environment. If the goal is a blockchain that can run general-purpose programs (and take advantage of the larger ecosystem), Wasm and WASI are the way to go. In the remainder of this post, we discuss how to translate the semantics of POSIX-y WASI syscalls to the blockchain environment.

WASI for the Blockchain

The goal of WASI for the blockchain is to leverage the community and infrastructure that is rapidly developing around WASI. Compared to the alternative approach of creating yet another Wasm interface, WASI allows blockchain developers to tap the resources of the broader developer community. In this way, we can imagine a future in which blockchains will be simply another tool in a cloud developer’s toolbox.

A Minimal Set of Blockchain Exports

A smart contract executes a transaction by interacting with the blockchain platform. This section presents a concise interface that a blockchain-specific Wasm runtime could export to allow such interaction. Note that the interface looks like a C foreign function interface. And, because the runtime and contract can’t generally share memory directly, functions like read require two calls: one to get the size of the data and one to actually fetch the data into a pre-allocated buffer. Address and Hash are fixed-size byte arrays and can be passed directly.

The following functions are based on an account model of state, as is used by most modern smart-contract blockchains. An account is located at a particular address, has a balance of tokens, possibly some code, and account-local storage. Clients send calls to an address to transfer funds and/or invoke code. The code can update the account-local storage, call or create other contracts, emit read-only logs, and return data. The caller of the code is charged tokens, or gas, for each executed instruction.

The goal, now, is to express these functions in the language of WASI.

Blockchain Semantics via WASI Exports

The real benefit of WASI is that developers can use the standard library as they normally would. The compiler emits WASI syscalls that a blockchain runtime can interpret in a blockchain-specific manner.

Example

The following example of a tee-like program calls another contract, logs its output (after charging a service fee), and returns the data to the caller. The intention is to look like an idiomatic non-blockchain program since that is precisely what we want the blockchain to run. We can provide the unique benefits of blockchain simply by adding a few extra environment variables and slightly modifying the semantics of writing to files. Otherwise, the compiler will emit WASI syscalls for stdlib functions (e.g., sock_send for std::process:Command) which we can trap in the blockchain Wasm runtime and implement using blockchain semantics.

Let’s break it down a bit:

The first bit of WASI utility happens on line 4 with std::env::var("VALUE") which Rust translates to WASI environ_get and environ_sizes_get with key=VALUE. The Blockchain WASI runtime reads the key and returns the data provided by the value() blockchain intrinsic.

Next, on line 8, we see a similar procedure for std::env::args() where the runtime translates WASI args_get and args_sizes_get for the zeroth arg to the blockchainaddress() function.

Then, on lines 10–11, std::time::SystemTime::now() calls out to clock_time_get and clock_res_get with clockid_t=CLOCK_REALTIME. The blockchain WASI runtime then returns the block timestamp provided by the consensus protocol (monotonic but not strictly so).

Input is provided to the contract via stdin. We see this in action on lines 13–15. The std::io::stdin().read_to_end(...) is compiled to a fd_read to the default 0 file descriptor. In the background, the virtual filesystem provided to the blockchain WASI runtime services reads to stdin via the blockchain input() function.

In the block starting on line 18, the virtual filesystem again takes the stage. Essentially, the WASI fd_* functions are mapped to similar functions on the virtual filesystem which maintains a cache of file descriptor to blockchain storage mappings. The details can be found in the RFC. In this case, the goal is to write to account storage, which looks like writing to a regular file. Under the hood, the path is converted to a key during fd_open and the contents are stored in the Merkle key-value store during fd_write.

Finally, to top it all off is the mechanism for cross-contract calls, as shown starting on line 33. The WASI implementation of std::process::Command requires opening a file descriptor to a socket. The virtual filesystem takes note of this special file descriptor type so that when the following WASI sock_send and sock_recv are made, it can redirect the request to call and sock_recv (with sock_send being done by the callee’s virtual filesystem).

Not bad! We can get all of the functionalities of blockchain while writing what looks essentially like a normal program. Matching the syntax and semantics of the language certainly improves learnability and efficiency and hopefully, too, safety since the end result can be more easily audited by both the developer and the users.

Summary

Web Assembly is a versatile format for creating safe and efficient cross-platform programs. With the development of the Web Assembly System Interface, Wasm gains increased utility in non-web contexts. Once such context is the blockchain where the deterministic Wasm sandbox is virtually an ideal fit for smart contracts. By leveraging compiler support and tools built for Wasm and WASI, the blockchain becomes a powerful tool for high-integrity — and even confidential — general-purpose “cloud” computation.

Request for Comments (RFC)

In the same way that Wasm and WASI are proposed as standards, we’d like to have the same for the blockchain flavored interpretations. Please feel encouraged to provide feedback on our Blockchain WASI RFC!

--

--