Non-deterministic functions used in smart contracts have the potential to make the smart contracts totally useless. In my previous blog – “Why Do We Need Blockchain?” – I used the example of supply chain to showcase the need for a Blockchain. In this post, we take a look at the same example of a supply chain implemented using Hyperledger fabric and how the non-deterministic function used in the smart contract (Chaincode) can wreak havoc.
Determinism in Blockchain:
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.
Determinism also refers to the fact that the same operation replayed on a different node at a different point in time should also produce the same results. For example, a new node that's been on-boarded to the network should be able to replay all the operations and end up in the correct ledger state, just like existing nodes.
The following are a few examples of non-deterministic operations on the Blockchain:
- Using a timestamp inside the smart contract and storing the timestamp on the ledger.
- Calling an external API (running outside the Blockchain) from the smart contract.
In this post, I will be explaining the impact of using a timestamp inside a smart contract in the supply chain scenario.
Hyperledger Smart Contracts:
Smart contracts in Hyperledger are called Chaincode. Chaincode is a program that can be written in Go or Java. All transactions on the ledger happen through Chaincode and manage business logic complied by the members of the network. We will not go into the details of the Chaincode and focus only on how a non-deterministic function can create problems with the consensus.
Use of Timestamp in the Chaincode:
In the supply chain example, we will be capturing the movement of the goods between various parties on the distributed ledger. A simplified representation of the ledger state, which stores information regarding the shipment of goods from the manufacturer to logistics, is provided below with some sample JSON.
Notice the "Timestamp" property that contains the date and time (including the milliseconds) on which the goods were shipped from the manufacturer to logistics. One of the easiest ways to fetch this timestamp in Go is to use the
time.Now() function. Writing this function inside the Chaincode will result in a non-deterministic operation. The Chaincode will execute on all nodes of the network and there is no guarantee that it will execute at the same time. Hence, different nodes can produce different results based on the time of execution. The JSON output depicted above corresponds to JSON created from the Chaincode execution on the “Manufacturer” node. Execution of the Chaincode on other nodes can create different JSON outputs for the "Timestamp" property based on the time of execution.
If each individual node updates their ledger state with this value, then the ledger state will not be consistent across nodes making the complete smart contract and the associated business functionality useless since the Hyperledger platform validates the ledger state consistency and halts further processing.
The optimal solution to capture a timestamp with milliseconds is to pass this information from the client application by invoking the Chaincode. Passing a timestamp as input to the smart contract will make sure that the same value is passed as the input to all the nodes executing the Chaincode, resulting in a deterministic operation.
The above example clearly illustrates the impact of using non-deterministic functions in smart contracts and how it can be avoided by using the same function in client applications invoking smart contracts.