The challenges and solutions for creating deterministic smart contracts
Practical guidelines for creating deterministic contracts
“In computer science, a deterministic algorithm is an algorithm which, given a particular input, will always produce the same output, with the underlying machine always passing through the same sequence of states.” – Wikipedia definition.
For smart contracts, determinism is very important. All operations on the Blockchain should be deterministic. Simply put, the same operation performed across different nodes should return the same result. A difference in results between the nodes for the same operation can lead to a failure in consensus, since storing this data on the ledger will lead to an inconsistent ledger state thereby making the whole smart contract useless.
Guidelines for writing deterministic smart contracts
Randomness is the key property you want to avoid when creating smart contracts. Using a pseudorandom number generator with a known seed, makes it possible to reproduce the same long sequence of seemingly random numbers over and over again. Note that some smart contracts (e.g. lottery smart contracts) require a non-deterministic behavior. In that case, the seed of the random number generator must be an always-changing value which is unpredictable.
Sometimes, it’s hard to spot randomness in certain programming languages. Often, map range iterators are non-deterministic. Let’s take a look at an example from a Hyperledger Fabric smart contract, also called chaincode.
Go maps documentation tells us this, “When iterating over a map with a range loop, the iteration order is not specified and is not guaranteed to be the same from one iteration to the next. Since the release of Go 1.0, the runtime has randomized map iteration order.”
Other languages like Java implement this pseudo randomness as well, the iteration order for HashMap is non-deterministic. If you want deterministic iteration, use LinkedHashMap.
Concurrency is the ability to allow multiple users to affect multiple transactions. Take a look at the following piece of code which introduces a concurrency problem in Hyperledger Fabric chaincode.
The code snippet performs a write action to the ledger twice, right after each other. First of all, the code gets appended for the same ‘key’. The concurrency problem here is that the second write action can finish before the first one has finished. This means that some nodes may have “data2” as the final result for the key being appended to the ledger, but some may have “data1” as the end result. This non-deterministic behavior will lead to transaction failure as the nodes can’t reach consensus.
Watch out when importing packages as they may hold many non-deterministic functionalities. Packages which communicate with the outside world (e.g. via an API), grant file access or can introduce any form of non-deterministic behavior should be avoided. The most common example of this type of packages is the “time” library.
For Hyperledger Fabric smart contracts, this should be avoided as it is highly unlikely, that every node will query the time at the exact same moment, data dependent on such an operation will not be consistently updated on the ledger.
Source image: DZone article on avoiding non-deterministic smart contracts.
The Hyperledger ChaincodeStub offers a function called GetTxTimestamp capable of returning a deterministic timestamp agreed by all nodes.
Oracles enabling deterministic API calls
A simple workaround is available, instead of the smart contract calling an external API, we use a trusted service which monitors the blockchain’s state and performs certain actions in response.
An oracle, in the context of blockchains and smart contracts, is an agent that finds and verifies real-world occurrences and submits this information to a blockchain to be used by smart contracts.
On the other side, this workaround requires a trusted entity to manage the interactions between the blockchain and the outside world. While this is technically possible, it undermines the goal of a decentralized system.
Extend your blockchain smart contracts with off-chain logic
From a purely technical standpoint, implementing all business rules into your smart contract seems to be the ideal solution. But is it feasible to re-implement hundreds or thousands of rules that have been tested and validated and that are known to work as expected? Doing so could be costly to the organization, and errors could easily be introduced in the re-implementation of these rules as smart contracts. Another possible drawback from re-implementing and deploying such rules as smart contracts is that if business folks are accustomed to updating and validating the rules through the IBM ODM user interface, they would lose this flexibility.
Extend the operational boundary of a smart contract to a third-party system (for example, IBM Operational Decision Manager, ODM). In this model, peers in the blockchain network invoke a third-party service co-located with them. The underlying assumption is that the outcome of the invocation is deterministic (Hyperledger Fabric example).
The Bottom Line
It’s definitely no easy task to create deterministic smart contracts. Especially imports, which are dangerous as they can contain calls to the outside world or unexpected sources of randomness. Even when testing a smart contract thoroughly, it’s hard to detect such non-deterministic behavior. Workarounds exist like off-chain logic or the usage of oracles, however, these solutions undermine the true meaning of decentralisation.