Part 1 - Intro
In the last few months we have been working on making progress on Zephyr Web Wallet (think “MetaMask for Zcash”) project. Here we present some of the highlights of our work and technical investigations.
Our strategy for creation of Web wallet core code was to adopt the codebase of Aditya’s wonderful zecwallet-lite-cli codebase and port it so that it is able to run as WASM application in the browser. This WASM-compiled module in turn would serve as the bridge to communicate with the Zcash blockchain and perform low-level blockchain operations. This way it is possible to do separation of responsibilities and the Web wallet UI will be just a simple web-app wrapped as Chrome/Firefox extension.
At the core of a webwallet there is a blockchain client implemented as WASM-module and hence executable within browsers. Luckily there are toolchains (eg. compilers) which are able to compile Rust code into WASM. However, as we set out on our journey we have encountered a number of hurdles which we had to overcome.
Part 2 - Challenges
Some of those hurdles were related to difference in architectures between the native Rust app (eg. running on Linux) and an app running within browser WASM-environment. Some of the first challenges were related to limited networking stack on WASM (eg. lack of access to TCP-sockets) and hence RPC communication couldn’t happen directly to LightwalletD daemon. Another set of challenges came from the fact that some library dependencies were not compatible with the WASM environment.
Most of the challenges we had to overcome were related to WASM being a different execution environment vs native OS apps. Whilst WASM is definitely the way for the future, its ecosystem is still in it’s “adolescent” stage. There’s many promising ways to solve any issue, but not always it’s clear which one of them is the most reliable path.
Running tasks concurrently is not as easy in WASM. This is because standard OS-threads do not exist in WASM environment and hence core Rust concurrency primitives like std::thread::spawn()
cannot be used. To find a workaround we tried different approaches, but couldn’t yet find a stable way to do this withing crashing the browser
After hitting some WASM-concurrency stability issues we decided to focus on single-threaded approach for the moment (albeit it makes block syncing and scanning take annoyingly long time).
Another discovery was related to memory limitations in the browsers. Actually there is no hard limits on max memory per browser tab (eg. Chrome tab), but what we’ve discovered is that wallet-syncing may take up to 4Gb of RAM when syncing/scanning blocks and that can make browsers behave unstable. And browser can simply terminate the tab or crash the process. We have achieved stable runs by ensuring that OS had enough of RAM so that swap-file wasn’t used (or was very small). Seems that presence of decently sized swap file would increase chance of browser killing the WASM tab
Here’s some examples of RAM usage spiking in the browser tab which executes WASM-syncing
and another one
and then memory returns to normal
Part 3 - Living with Sapling (for the moment)
Another challenge was presented by the fact that Zcash ecosystem advanced and NU5 upgrade went live last year. This upgrade introduced protocol which is quite different from the Sapling codebase which we originally forked from Aditya’s wonderful zecwallet-lite-cli. To deal with a lot of moving parts (wasm-stability, concurrencty stability, networking, etc), we decided to test our WASM-codebase against Sapling version of the blockchain.
Eventually we set up the zcashd-v5.0.0 synced up to the pre-NU5 block 1,624,964
(where NU5 is 1,687,104
) and then we run the node with -noconnect
flag which still allows JSON-RPC protocol, but does not try to sync up with the rest of the network. This way we were able to set up a Sapling-sandbox for testing stability against some fixed target. This is a bit of a “hack”, but it allowed us to deal with uncertainty and allowed establishing a stable baseline.
Part 4 - Establishing baseline
So currently we have established a baseline, a PoC that the code can be ported to WASM and perform scan, and sync with the blockchain and find transactions.
Previously the only other WASM-code which worked with Zcash blockchain was a legacy ~5 year old version of ZecWalletLite).
Below one can find example screenshot of successful scanning of the blockchain for a test transaction (from the wallet initialized with out test-seed). You can see that for example transaction df2c041ca...
was indeed detected and it matches with the info reported by the latest ZecWalletLite…
And matching transaction
Part 5 - Next Steps
Our next set of priorities looks like this:
a) introduce some* concurrency (can be related to block verification or to block scanning)
b) merge upstream changes from Aditya’s wallet (probably pre NU5)
c) test more on pre-NU5 blockchain (eg. on a small network of 3 own nodes)
d) upgrade to NU5 (which can be done by merging NU5 commits from Aditya’s upstream)
*there are some ways to introduce concurrency with Rust/WASM which we are looking at.
Overall we’re very positive about reaching this milestone (baseline), as we have managed to get past the most drudging and uncertain work of figuring out whether it can work at all. (despite the fact that it took us longer than we expected to get to this point). And now we have more specific goals to move things ahead.
Thank you!
Here are links to previous two updates: