Tendermint: Building a blockchain app from scratch
August 09, 2019
by Angus Hamill (software engineer at Artos Systems)
Artos Systems is part of the greater Aventus network, and receives funding from multiple sources including grants from the Aventus Protocol Foundation. Spearheading the development of our protocol, we thought we’d give you a brief update on their work for us.
Scalability. It’s a problem. Whether it’s hardware limitations or block sizes, response times or fees, scalability has been a serious thorn in the side of decentralised blockchains ever since their inception. So it will come as no surprise that for the year I’ve been with the Artos team, overcoming these issues in the Aventus protocol has been our main point of focus.
You see, what we need is a simple solution that’s able to produce merkle roots of transactions to be registered on the mainnet, while still being decentralised. And fast. Importantly, fast.
Naturally this has led to the exploration of sidechains, Tendermint being the obvious choice. With a high throughput and a dynamic validator set, Tendermint validators agree on the hash of the app state after each block. These hashes can be the merkle roots to register on the Ethereum mainnet. Why does this work for us? Well, using a merkle tree just 10 levels deep has allowed us to process over 1,000 transactions for the cost and at the speed of processing only one.
In my research I’ve found a great deal of information on what Tendermint is, its underlying cryptography and its use cases, but very little on how it has actually been used to create an application. Which is why I thought I would write my own short walkthrough on how we’ve been using Tendermint to create our proof of concept at Artos.
First, what is Tendermint?
Tendermint is what’s called a ‘blockchain engine’. Formed of the Tendermint Core and a generic application blockchain interface (ABCI), it is a tool which allows developers to skip the hardcore cryptography and move onto creating applications that can utilise the blockchain.
The Core itself is a consensus engine and is formed of two parts: a Byzantine Fault Tolerance consensus and a p2p protocol. Both very impressive, but the real beauty of Tendermint is in the ABCI. Where most blockchain stacks have a monolithic architecture — where everything from the smart contracts to the mempool to consensus are run from a single program, and apps are limited to the language of the stack itself — Tendermint is not.
Instead, it allows a developer to write an application in whatever language they so choose, and call on BFT consensus through their interface to agree on a shared state between nodes. Simply, it unlocks the power of blockchain and frees developers to work on the actual real-world implementation of new order apps.
Developing a proof of concept
Our aim was pretty straightforward. We wanted to build a simple application that can create and transfer assets (in this case, tickets), and output a merkle tree which consisted of all tickets in that block. This way all that’s needed is an address and a merkle proof to verify ownership of a ticket.
Beyond this, we needed to let the owner of the address resell any tickets they may have by signing the transfer using their associated private key.
For the sake of ease we opted to write the application in Golang. Tendermint’s reference apps already include a Golang server, meaning that we only had to write the application logic. A real time-saver.
So let’s take a look at how we did it.
Creating a ticket
The following is the structure of a ticket transaction and a breakdown of its constituent parts:
- Id and Nonce are the unique transaction identifiers.
- Id can be any number (decided at ticket creation) and Nonce increments with each resale.
- Details is some arbitrary string to attach more info, e.g. event time, seat number, etc.
- OwnerAddr is a standard Ethereum address.
- PrevOwnerProof only applies in the case of a resale. The ticket owner (OwnerAddr) signs a hash of the transaction which gave them ownership and is included as part of the new transaction.
The key here, when it comes to the transfer of tickets, is the Nonce, which ensures that signatures cannot be reused.
We also wanted to query the archival state so that users can validate the entire chain of resales for any given ticket. To do this we need the state of the ticket before and after the resell to verify that the resale was valid. Hence a ticket contains information on the most recent transaction as well as the block heights it was previously modified at.
Using this merkle tree library, the app state is as follows:
- size is the total number of tickets
- height is the current block height
- rootHash is the merkle root hash of the previous block
- tickets is a mapping of all ticketIds to their most recent state
- history is a map of block height to a snapshot of the state at that height
- tempTreeContent is a temporary store of every ticket to be included in the current block
Transferring a ticket
The most important element of the Tendermint ABCI is the DeliverTx() function. This function parses requests sent to the node, accepts or rejects them, and updates the state accordingly.
We call on the go-ethereum hexutil and crypto libraries for dealing with signatures.
The transaction is received in a JSON format and unmarshalled. Next, the OwnerAddr, Nonce and PrevOwnerProof are checked against the previous ticket (if there is one) before the state is updated. Only a valid transaction will actually update the state.
Tendermint also has an optional CheckTx() function which can verify the validity of a transaction before it reaches the mempool without updating the state. If CheckTx() determines that a transaction is invalid it won’t bother sending the information to its peers in the pool, preventing congestion.
Another important function is Commit(), which is run at the end of every block to update the app state, and prep the state for the new block.
If there are any transactions, this creates a merkle tree. The root hash can then be registered on the Ethereum mainnet and thus state transitions validated by a smart contract.
Next we have the Query() function, which queries the app state and makes all the above possible. As Tendermint nodes run all historical transactions onsync, Query() can be used to determine the merkle proof of any ticket at any block height, thereby proving the ownership of a ticket to any concerned party.
Finally, we use Tendermint’s built-in server in our main package to run the ABCI.
Running `tendermint node` while the application is live should create an automatic connection, allowing you to send transactions to the application via Tendermint’s JSON-RPC.
If you want to run this app yourself, the full source code is public and available here.
And there you have it. A ticketing application built on top of Tendermint’s prefabricated blockchain engine. Of course, these are still very early days and what we have created so far is fairly rudimentary. We are now working on creating a decentralised version which can incorporate additional functions such as setting permissions and roles within the network, multiple event support and zero-knowledge proofs for privacy.
Now the real hard work begins.
Angus is a software engineer at Artos, an Aventus Network partner, working on the blockchain engineering team.
He has previously worked in the banking sector and has a BSc in Mathematics from the University of Warwick. You can follow Angus on twitter @angusbeef