Turning Existing WebAssembly Applications Into Distributed Programs

Ryan Swart
Sep 4 · 9 min read

Or, how to make an eternal Game of Life Simulation

One of the benefits of WebAssembly that we keep going on about is the ability to re-use existing code. There have been a couple of articles demonstrating how easy it is to build WebAssembly applications that run on the decentralized virtual machine/smart contract platform Wavelet, but it hasn’t been shown how easy it is to port existing applications yet.

In this article, we’ll take the great Rust and WebAssembly Book ’s Game of Life implementation, and run through the things you have to change in order to have it run on Wavelet, and end up with your own eternal cellular automata universe.

You can see a deployed version of what we’ll be building here and the final source code here.

Setup

We can use the exact same setup process as described in the Rust Wasm tutorial setup and hello world stages, with the minor difference of adding 2 dependencies to Cargo.toml, namely:

smart-contract = "0.2.0"
smart-contract-macros = "0.2.0"

We don’t need all of the things the setup includes, but we aim to stay as close as possible, so we will keep them in place.

Interfacing with Javascript

The first WebAssembly implementation detail the Rust Wasm tutorial goes into, detailed here, covers how JavaScript interfaces with WebAssembly and vice versa — this is different in Wavelet apps, and significantly changes how you write the frontend.

  • Rust Wasm uses the #[wasm_bindgen] macro to export references in a way that can be called from JavaScript, and prefers direct memory access to the WebAssembly VM for high-performance updates.
  • Wavelet uses the #[smart_contract] macro to make functions in a single struct accessible to the wavelet-client library. Direct memory access is not recommended, as the bottleneck for wavelet application performance is not at the serialization/deserialization level, but at the Wavelet consensus level.

Interfacing with Game of Life

The Book’s implementation uses a single array to store the universe in WebAssembly linear memory — we will do the same. We will also use the same string format to render the game. Direct memory access is also possible, but not explicitly supported, so we do not get the same easily importable bindings that we see in the Rust Wasm tutorial so it is slightly trickier to access.

Rust Implementation

This is the cool bit — we can copy all of the code directly! We only need to make two minor modifications — removing the #[wasm-bindgen] macros and adding a smart contract struct that will expose the Universe functions to wavelet:

Rendering with JavaScript

At this point, the implementation diverges significantly. In the Rust Wasm tutorial, they utilize wasm-pack’s bindings to directly import the compiled rust code into their HTML + Javascript frontend; we need to go through a few more steps; first to upload our compiled wasm to the distributed VM, and then writing the connector code that will instantiate the VM locally and synchronize it with the global state.

We will try and stay close to their implementation, and highlight the core differences.

First, we need to upload the contract so that we can access it in the frontend. We can follow the same steps as detailed in the decentralized-chat tutorial, just use the wasm-pack build command to generate your .wasm file, and use the wasm_game_of_life_bg.wasm file in the pkg folder in the root of your project.

To interact with the uploaded program in JavaScript, we need to add the wavelet-client & jsbi dependencies with npm i -s wavelet-client jsbi or yarn add wavelet-client. With these dependencies, we are good to start coding.

Our implementation is pretty simple, at only 40 lines of js and html.

The HTML stays pretty much the same, except that we import the index.js file instead of the bootstrap.js file:

The JavaScript code is a bit more verbose, compared to the 9 lines of code in the Rust Wasm implementation.

In the index.js file, we first import the wavelet-client and jsbi dependencies:

We then initialize the Wavelet client, load a wallet from a private key that will be used to sign the transactions that progress the state of the simulation, and configure the contract object that will allow us to interact with the deployed code.

We will render the resulting string to a simple preformatted code DOM element, so let's get a reference for future updates.

We need to initialize the contract, which will fetch its memory state from the distributed VM.

Once it is finalized, we can interact with it in two ways, either by calling functions or test ing them.

Calling functions execute them on the distributed VM, and changes the global state, and requires transaction and processing fees. As you can run a Wavelet network locally pretty easily, or get testnet PERLs for free, this will not cost you anything, but still incentivizes you to write efficient code. We recommend 10 million testnet PERLs at this point, as Wavelet hasn’t set its decimal precision yet — in reality this algorithm will require a tiny fraction of a mainnet PERL to run, resulting in a cost comparable to an AWS Lambda function execution.

Testing functions execute them on the local VM, using the VM’s current memory. It is free but does not result in any persistent changes in VM’s state.

We use test to call the render smart-contract function, which will return a string in the resulting logs. All serialization/deserialization happens through this log interface, similar to CLI applications:

We then set the textContent of the pre tag to be the resulting value, which will give us a visual representation of the Game of Life universe

Up until this point, we only get a single, static snapshot of the universe. We need to register to changes in state in order to get a dynamic world — changes in state in the distributed VM is known as “consensus rounds” — where a subset of nodes agree that a given set of transactions are legitimate.

We register to consensus events and synchronize the contract’s memory on each round. We then render the current state as before, and finally call the step function, which will advance the state of the universe by one tick.

That’s it! A working Game of Life simulation that can evolve for as long as there are nodes connected to the distributed VM.

Of course, it’s not a lot of fun yet, as it isn’t interactive — the Rust Wasm tutorial first showed how to implement canvas-based renderer before detailing how to toggling cell states — let's tackle that next.

Adding interactivity

In order to achieve this, we need to change both the Rust code and the HTML + JavaScript frontend.

Following the tutorial, we will first add a button to run and pause the simulation.

We add a button, just before the <pre> element in index.html:

We then add a variable in index.js to track whether the game should be running, and some logic to change the variable and check it when trying to advance the universe:

This should allow us to start/pause the simulation. One interesting aspect is if someone else is busy simulating the universe, the state will still update, so pressing play may show you a significantly different world on the next render.

You can choose to change the behavior by moving the rendering code that is in the consensus callback out of the if-statement.

Toggling Cells

This requires new functionality in the rust code; we can implement it in the same way as described in the tutorial, but need to expose the functionality via our contract.

Once we have made the changes, we need to re-deploy the contract and update the JavaScript code with the new contract address.

Our frontend implementation is pretty similar to the Rust Wasm tutorial implementation, except that we still use the <pre> tag implementation, and call the Wavelet contract instead of the imported Wasm code

:

And that’s it, now you are the omnipotent ruler of the universe, able to kill and create cells wherever you want!

Final steps

The final parts of the rust-wasm book cover time profiling, shrinking the .wasm file size, and publishing to npm. These steps are all relevant to Wavelet apps as well.

Time Profiling

You may have noticed that it costs a lot of testnet PERLs to simulate the universe, around 2 million per step! We can apply the same optimization detailed in the Rust Wasm tutorial to cut that down to around 700k PERLs.

Shrinking the .wasm Size

File size optimization also reduces the cost to deploy contracts, as well as improve load speeds for users, as they still have to download the binaries, so this is also valuable reading.

Publishing to npm

As we have already deployed our contract, there is no need to publish it to the npm registry, as anyone can access it given the deployment address. That being said, we aim to provide a contract registry, and possibly similar wrapper functions that will make it easier to find and use contracts in the future, so stay tuned!

Wavelet Considerations

Almost done! We currently have a working, optimized app, but it isn’t quite ready for production, as there is a pretty serious problem in the way we interface with Wavelet currently, namely how we handle the private key.

We currently hard-code the private key to a wallet in our app — this is bad practice as once we publish the code, anyone can access the wallet and drain its funds. As this is testnet, there won’t be a financial loss, but any published version will stop working once if the hard-coded wallet runs out of PERLs.

In order to avoid this, we need to make the wallet dynamic — there are a few ways to do this, namely:

  1. Randomly generate a wallet, and automatically fund it from a faucet.
  2. Have the users enter a private key to a wallet that they have funded themselves.
  3. Use a browser extension that provides a private key/wallet functionality

Wavelet does not yet have a browser extension — so we cannot use option 3. Option 2 would be the easiest to implement but would make our user’s lives more difficult. That leaves Option 1 — let’s see how we would go about doing so.

To generate a random wallet, we can call the handy generateNewWallet function.

This will give us a wallet that will be able to render the universe, but not simulate it or modify it.

In order to fill the wallets with testnet tokens, we can use our free faucet REST API to fill up the wallet.

The faucet provides 500k PERLs per request — which means we have to hit the faucet twice to simulate a single step. This isn’t ideal, because the faucet will rate limit us to one call every 10 seconds, and the Game of Life isn’t going to be much fun if we can only update it three times a minute!

Fortunately, Wavelet allows us to deposit gas into a smart contract, meaning that users don’t have to pay gas fees — only the much lower transaction fees of 2 PERLs. We can deposit a few billion PERLs for gas into the contract (join our discord and we’ll happily send you some), which will allow any user to advance the simulation 250k times.

You can deposit gas either through Lens, (the webapp used to upload the contract), or via the JavaScript client by calling a function with a deposit value set:

We also have to make sure that we decrease the gas values currently specified in the existing contract.call options - they are currently 1e7 to allow for enough gas to execute the expensive calls - we can reduce this to 1 PERL.

Now that we no longer store sensitive information in the code, and have a contract with a healthy amount of deposited gas, we are ready to launch the app to the public!

You can see our instance of the universe running here: https://ryanswart.github.io/decentralized-game-of-life and the final code we have implemented here: https://github.com/ryanswart/decentralized-game-of-life


Originally published at http://github.com.

Perlin

Powering the future of trade on top of the world’s fastest public ledger.

Ryan Swart

Written by

Theoretically, an engineer.

Perlin

Perlin

Powering the future of trade on top of the world’s fastest public ledger.

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade