After releasing the first Beta version of Wavelet one month ago, we have been continuously optimizing both the protocol design and the implementation to make it faster, more secure, and more developer-friendly.
Based on our internal discussion and community feedback, various important features and fixes were implemented in Wavelet v0.1.1, including:
- crowdsourcing gas fees for smart contracts,
- AssemblyScript smart contract support,
- CLI autocompletion, and
- more secure transaction seed rules.
New features and major fixes
- Implement all that is necessary to enable autocomplete for CLI input across ledger data. (#120)
- Changed to urfave/cli for the shell, added autocompletion, some 80 col changes. (#121)
- Ledger state syncing/pull missing transactions — minor cleanup and improvements. (#125)
- Fixed seed conditions and added tests for applying transactions. (#126)
- Prefer values over heap-allocated values, simplify memory model, and fix appending parent transaction seeds to transactions. (#132)
- Crowdsourced gas fees for smart contracts. (#133)
- Validate WASM code for smart contracts before writing it into account state. (#135)
- Call the start function of a smart contract (if exists) at initialization. (#145)
- Improve autocompletion indexing using a radix tree. (#149)
- Improve nops broadcasting. (#150)
- Preserve the globals of smart contracts into account state. (#151)
Crowdsourced Gas Fees
When a smart contract is invoked by an on-chain transaction, there’s a cost for executing the contract and the cost needs to be paid by some account. The cost is called gas and the virtual currency paid is the gas fee.
Usually, the account that sends the contract-calling transaction has to pay the gas fee. But that’s not the only choice to making sure gas fees are covered. For example — a contract’s user can participate in crowdsourcing gas fees by transferring money into the contract’s gas balance, and legitimate users, once identified, can have their gas fees deducted from the crowdsourced gas balance instead of paying by themselves.
Wavelet v0.1.1 implements crowdsourcing, and we hope the flexible payment method for gas fees can benefit more contract developers and users.
Rust compiles to WebAssembly through LLVM, while AssemblyScript uses its own code generation backend. So there are quite some differences in the generated code. To support the patterns used by AssemblyScript, we made a few changes to Wavelet’s smart contract engine.
Calling start function at initialization
According to the WebAssembly specs, a start function is the “initializer” function that should be called when a module is loaded.
All global variables in Rust are either statically initialized at compilation time or lazily initialized — This means that there’s no need for a start function. However, AssemblyScript initializes all non-trivial global variables in the start function. The routines for calling the start function were added to Wavelet in v0.1.1.
Globals in WebAssembly are similar to global variables in higher-level programming languages. The set of globals and the linear memory compose the persistent state that needs to be preserved across smart contract invocations.
However, only the linear memory was preserved prior to Wavelet v0.1.1. This didn’t cause any problem for Rust/LLVM smart contracts because LLVM only uses globals for the linear stack pointer, which does not need to be preserved across invocations. AssemblyScript uses globals also for language-level global variables, so we need to preserve globals too. In v0.1.1, globals are saved under a different key in account state and loaded back at every contract invocation, which fixes the issue.
There are two ways to interact with a running Wavelet ledger: through the HTTP API, and directly through the command line interface (CLI).
In v0.1.1, we added autocompletion support into the CLI. That means you can enter a prefix of commands, options, and even transaction IDs, and just press TAB to have it autocompleted.
Reducing allocations and expressing immutability in Go
Performance is important for Wavelet. Wavelet is written in Go, a high-level language with a garbage collector and automatic escape analysis, and therefore we need to work with these two language features properly to achieve the best performance.
Meanwhile, syntactical cleanness is also important to ensure that our codebase can be properly maintained. Therefore we pass some objects by value to express “immutability”, although Go does not have a good enough optimizer to eliminate all the memory copies.
We did a lot of benchmarks and analysis to balance between performance and cleanness, and the work resulted in PR #132.
Improving the safety of transaction seed rules
As introduced in previous posts, critical transaction is a fundamental part of our consensus protocol. The seed of a transaction is a 256-bit value used to determine the criticalness of a transaction in a similar way as to how PoW hashes work — counting the number of prefix zeros.
Prior to v0.1.1, the seed of a transaction was calculated by:
By adding some additional constraints on the sender ID (minimum stake), this algorithm is resistant to “mining”. You can only try new seeds by sending new transactions, for which you need to pay transaction fees.
Although looking good at first glance, this introduces a problem that can be exploited to perform DoS attacks on the network. Once a seed is found, the seed can then be used to create an infinite amount of critical transactions at no cost, as long as they have the same parents.
One possible way to fix the issue is to add a short hash of transaction content into the process of calculating the seed, so the transaction seed is calculated by:
This increases the difficulty of reusing a critical seed and is implemented in v0.1.1 with 8-bit short hashes. With a sufficient size for short hashes, the PoW resistance property of transaction seeds is still preserved, and the pressure from critical seed reusing can be offloaded from the consensus protocol in case of malicious activity.
What’s coming soon?
- Full state/transaction syncing support.
- Account nonces for strict transaction order support and secure forwarding.