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.
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.
- Rust Wasm uses the
- Wavelet uses the
#[smart_contract]macro to make functions in a single struct accessible to the
wavelet-clientlibrary. 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.
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:
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.
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
The HTML stays pretty much the same, except that we import the
index.js file instead of the
index.js file, we first import the
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.
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
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.
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
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.
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.
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!
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!
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:
- Randomly generate a wallet, and automatically fund it from a faucet.
- Have the users enter a private key to a wallet that they have funded themselves.
- 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
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.
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.