Designing and building utopian distributed computing solutions; using WebAssembly (Wasm)
… and a way we go!
This article demonstrates how end-users, and machines alike, can summon up answers from Wasm functions over the web using nothing more than HTTP requests. For the more adventurous readers out there, this article also demonstrates how to write and deploy your very own Wasm executable on the same infrastructure.
Background
A recent article surmised that whilst Wasm has indeed gained popularity on the client-side, Wasm has also recently become a serious contender in relation to server-side technologies and services.
Expanding on this idea, another article suggests that the future of distributed computing will see traditional microservices being transitioned towards Wasm infrastructure. There are many reasons for this. One being that Wasm allows the logic of individual discrete functions to be written and shared amongst the most amount of source-code languages, and native hardware.
Inevitably then, this means that Wasm will be applicable to the most amount of applications, even if its presence is behind the scenes; serving each application by efficiently executing a well managed set of discrete functions.
Of course, this utopian distributed computing model requires some foundations upon which we can build our own customised business and enterprise software.
With these lofty, yet achievable goals in mind, let’s now take a look at a some new and exciting Wasm infrastructure that will help us along this path.
SecondState have recently built a suite of software that facilitates, among other things, server-side Wasm deployment and execution.
The following diagram shows a few of the components which make up this system. Namely:
- SSVMRPC — A Remote Procedure Call (RPC) implementation (written in Rust) which facilitates both Wasm code-deployment and Wasm code-execution interactions with SecondState’s stateless Virtual Machine (SSVM)
- SSVMContainer — A Rust application that sits between incoming requests from the network and the SSVM. This application handles the deployment of Wasm applications and manages the execution of services (callable functions inside the Wasm application). It also manages application state, given that SSVM performs stateless execution.
- SSVM — a high performance, hardware optimised, stateless, stack-based Wasm Virtual Machine. SSVM can execute Wasm binaries, but is also highly optimised for both AI and Blockchain specific applications.
An overview of the infrastructure
One of the key benefits of SecondState’s infrastructure, is that you don’t actually have to think about, or know how the internals work. In fact to execute server-side Wasm, using this system, you just need to be able to make a simple HTTP request.
We will demonstrate “Calling an application’s function” very shortly, but hang in there for a couple of minutes while we take a quick technical dive.
Technical dive
This section will show you how to create and deploy your own Wasm executable on the SecondState infrastructure. For this application creation & deployment demonstration, we will use Rust on an Ubuntu OS.
Let’s get started!
Install Rust
sudo apt-get update
sudo apt-get -y upgrade
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
source $HOME/.cargo/env
Create a new application
cd ~
cargo new --lib add
cd add
Set Wasm specific system configuration
Add the following to the Cargo.toml file
[lib]
name = "add_lib"
path = "src/lib.rs"
crate-type =["cdylib"]
Write the source code
Open a new file at src/lib.rs
and add the following code
#[no_mangle]
pub extern fn add_two_numbers(_x: i32, _y: i32) -> i32{
_x + _y
}
Wasm system config
rustup target add wasm32-wasi
rustup override set nightly
Compile to Wasm
cargo build --release --target=wasm32-wasi
The above compilation will create a new Wasm file at target/wasm32-wasi/release/add_lib.wasm
. This is the file that we will deploy on SecondState’s Wasm infrastructure.
As you will see in a minute, we will be following this particular HTTP POST specification when deploying this application.
A quick word about compiled Wasm files
WAT
If you would like to see the textual representation (known as “WebAssembly Text format” or “WAT” for short) of your (newly created) Wasm application, you can install the incredibly useful wabt toolkit.
Simply run the following command; converting from wasm
to wat
.
./wasm2wat ~/add/target/wasm32-wasi/release/add_lib.wasm -o ~/add/target/wasm32-wasi/release/add_lib.wat
The textual representation (WAT) will look like this.
(module
(type (;0;) (func (param i32 i32) (result i32)))
(func $add_two_numbers (type 0) (param i32 i32) (result i32)
local.get 1
local.get 0
i32.add)
(table (;0;) 1 1 funcref)
(memory (;0;) 16)
(global (;0;) (mut i32) (i32.const 1048576))
(global (;1;) i32 (i32.const 1048576))
(global (;2;) i32 (i32.const 1048576))
(export "memory" (memory 0))
(export "__data_end" (global 1))
(export "__heap_base" (global 2))
(export "add_two_numbers" (func $add_two_numbers)))
Wasm
You will notice that the original add_lib.wasm
file is not able to be easily viewed, as it is an executable binary file. You will also notice that without any optimisations, the Wasm file (generated by default Rust compilation) is around 1800000 bytes. This is large in terms of Wasm.
We will be a xxd
command to convert the Wasm file to hex (for use in the HTTP POST’s JSON data). But, before you do that, I highly recommend shrinking the original Wasm file.
xxd -p target/wasm32-wasi/release/add_lib.wasm | tr -d $'\n'
One very easy way to shrink the Wasm executable is to use the awesome wabt toolkit again. Except this time we will convert back the other way i.e. convert the wat
file (that we just created) back to wasm
like this.
./wat2wasm ~/add/target/wasm32-wasi/release/add_lib.wat -o ~/add/target/wasm32-wasi/release/add_lib.wasm
It is now safe to perform the above xxd
command :)
Just for your interest, the overall result of these wabt conversions sees the Wasm executable size go from the original 1800000 bytes to just 4000 bytes. The new hexadecimal representation of the Wasm executable file (after the xxd
command) now looks, literally, like this …
0061736d0100000001070160027f7f017f030201000405017001010105030100100619037f01418080c0000b7f00418080c0000b7f00418080c0000b073704066d656d6f727902000a5f5f646174615f656e6403010b5f5f686561705f6261736503020f6164645f74776f5f6e756d6265727300000a09010700200120006a0b
Pretty manageable in terms of cut and paste now, right?
Deploying your application
This hexadecimal dump of the Wasm file needs a quick tweak. We need to add a 0x
to the beginning of the Wasm hex string before we deploy our application.
Here is an example of how we deploy the above Wasm application via Curl.
Curl
Note the 0x
, that we manually added, at the start of the bytecode!
curl --header "Content-Type: application/json" \
--request POST \
--data '{
"request": {
"application": {
"storage": "file_system",
"bytecode": "0x0061736d0100000001070160027f7f017f030201000405017001010105030100100619037f01418080c0000b7f00418080c0000b7f00418080c0000b073704066d656d6f727902000a5f5f646174615f656e6403010b5f5f686561705f6261736503020f6164645f74776f5f6e756d6265727300000a09010700200120006a0b","name": "Add"}}}' \
http://13.54.168.1:8080/deploy_wasm_application
Postman — GUI HTTP client
Here is the equivalent JSON that you would pass in, if you were using a GUI HTTP client to make the POST request.
Again, note the 0x
, that we manually added, at the start of the bytecode!
{
"request": {
"application": {
"storage": "file_system",
"bytecode": "0x0061736d0100000001070160027f7f017f030201000405017001010105030100100619037f01418080c0000b7f00418080c0000b7f00418080c0000b073704066d656d6f727902000a5f5f646174615f656e6403010b5f5f686561705f6261736503020f6164645f74776f5f6e756d6265727300000a09010700200120006a0b",
"name": "Add"
}
}
}
Response
{"response":{"application":{"name":"Add","uuid":"0xa9d57ac0f5046512"},"status":"success"}}
Application successfully deployed
When the application deploys, it will return a unique identifier i.e. 0xa9d57ac0f5046512 . You will need to remember/save this identifier for when you are calling an application’s function, in the future.
That wraps up the technical dive section, let’s take a look at how we can call an application’s functions over HTTP.
Calling an application’s function
The calling of an application’s function is not limited to just users. This article explains how to call Wasm functions via Curl etc. because this is a great way to roll up your sleeves and understand the details of the requests and responses.
The reality is that most of the time these functions will be called programatically by machines. At the very least, they will be constructed programatically behind a web browser or phone app and will be executed via end user “clicks”.
Let’s now have a go at calling an application’s function.
Copy and paste the following curl command into your terminal (or if you prefer, you can use a Graphical User Interface like Postman to perform this HTTP request for you).
Command line — Curl syntax example
Don’t be overwhelmed by the --data
below. It is actually pretty straight forward (see the HTTP POST specification for more information). Essentially, we are just calling the function add_two_numbers
and passing in two numbers ["2", "2"]
; expecting a return value of "4"
curl --header "Content-Type: application/json" \
--request POST \
--data '{"request": {"application": {"storage": "file_system", "uuid": "0xa9d57ac0f5046512"},"function": {"name": "add_two_numbers", "arguments": ["2", "2"],"argument_types": ["i32", "i32"], "return_types": ["i32"]},"modules": ["rust"] }}' \
http://13.54.168.1:8080/execute_wasm_function
GUI — Postman JSON example
{
"request": {
"application": {
"storage": "file_system",
"uuid": "0xa9d57ac0f5046512"
},
"function": {
"name": "add_two_numbers",
"arguments": ["2", "2"],
"argument_types": ["i32", "i32"],
"return_types": ["i32"]
},
"modules": ["rust"]
}
}
Response
Either of the above methods will produce a result object, as shown below. You will notice the return value "return_value":["4"]
which is correct.
{
"result": {
"error_message": "",
"gas": 0,
"gas_used": 6,
"return_value": [
"4"
],
"status": "Succeeded",
"vm_snapshot": {
"global": [
[
0,
"0x0000000000100000"
],
[
1,
"0x0000000000100000"
],
[
2,
"0x0000000000100000"
]
]
}
},
"service_name": "0xa9d57ac0f5046512_1578786333_add_two_numbers",
"uuid": "0xa9d57ac0f5046512"
}
You may have also noticed that there is a vm_snapshot
section in the return data. What is this vm_snapshot
?
VM Snapshot
VM Snapshot is data which is produced by SecondState’s Virtual Machine(SSVM), itself.
It is important to remember that the SSVM itself is stateless. Each call to this system invokes a new clean SSVM instance.
The vm_snapshot
data allows the overarching system to store the last known state of the SSVM. By storing this vm_snapshot
information, we are able to ensure that SSVM can pick up where it last left off. Using this approach, the last known state of SSVM can be restored during the next execution.
We mentioned at the beginning of this article that an end-user does not really need to understand the inner workings of the system. Simply put, if an end-user repeatedly calls a function, all of the vm_snapshot
(VM state) will be handled by the system on their behalf.
Stateful Wasm execution as a service
This article has demonstrated a simple stateful Wasm execution environment; one where human callers or machines can interact, with the logic of each discrete Wasm function. Simply using HTTP POST requests via the web.
Naturally, this demonstration only used simple application functions line add_two_numbers
but of course you are free to write any logic to suit your needs.
If you have any questions or would like any assistance in relation to the technology in this article please get in touch with SecondState via the web site or GitHub.