20. The Business Logic

In the previous chapter, I explained that the business logic of a Tendermint blockchain network is encapsulated in an Application BlockChain Interface (ABCI) application. So, as a developer, you just need to write an application to control how the network processes and validates transactions. Each ABCI application is a blockchain. Here are some examples:

  • The Binance decentralized exchange is an application blockchain designed for crypto exchange operations. It is built on top of Tendermint.

  • The basecoin application creates a blockchain network with a native cryptocurrency. You can extend the token to support your own cryptocurrency features by forking the project. See https://github.com/tendermint/basecoin.

  • The ETGate application is built on basecoin and enables token exchanges between Ethereum and Tendermint blockchains. See https://github.com/mossid/etgate.

  • The ethermint application allows you to run the Ethereum Virtual Machine (EVM) as an ABCI application on top of a Tendermint blockchain. This creates an Ethereum blockchain but with Tendermint’s Byzantine fault tolerant (BFT) validators as opposed to PoW miners. See https://github.com/tendermint/ethermint.

  • The Plasma Cash is a layer 2 network implementation for Ethereum based on the Tendermint engine. It is a blockchain that connects to the Ethereum network via smart contracts. The Plasma Cash side chain allows for high-speed transactions that are impossible on Ethereum.

  • The merkleeyes application creates a blockchain network that records transactions on a Merkle tree. It simulates a journaled data store. The insert/remove operations on the tree are recorded as transactions on the blockchain, and the current data on the tree can be queried from the blockchain’s query application programming interface (API) as well. See https://github.com/tendermint/merkleeyes.

  • The CyberMiles application is a fully fledged ABCI application that incorporates delegated proof of stake (DPoS), on-chain governance, security features, and an enhanced EVM into a single ABCI application.

In this chapter, we explore the ABCI protocol and create a simple ABCI application. We will also discuss application frameworks that are built on top of the ABCI, such as the Cosmos software development kit (SDK).

The Protocol

You already learned how a Tendermint network works at a high level. In this section, we will look into the detailed mechanism, including the message exchange between Tendermint Core, which manages the blockchain, and the ABCI application, which manages the application-specific logic.

The ABCI protocol specifies the request/response communication between the Tendermint Core software and the ABCI application. By default, the ABCI application listens on TCP port 46658. Tendermint Core sends messages to the ABCI application and acts on the responses (Figure 20.1). The protocol defines several kinds of messages. They follow the Tendermint Core and ABCI application interaction flow outlined in the previous chapter.

image

Figure 20-1 Tendermint ABCI messages in the consensus flow

Consensus on the Block

The first type of message is the CheckTx message. When the node receives a transaction request (via port 46657, which Tendermint Core listens on by default), it forwards the transaction to the ABCI application in a CheckTx message for preliminary validation. The ABCI application has its own logic to parse, process, and validate the transaction and then return a result. If the CheckTx result is okay, the Tendermint node will broadcast and synchronize the transaction to all nodes in the blockchain network.

It is important to note that every Tendermint node keeps its own pool of transactions that successfully passed the node’s CheckTx. It is known as the node’s mempool. The nodes on the network could each have a different set of transactions in the mempool. When a node proposes a new block, it packages together transactions in its own mempool. When the block is accepted by the network (i.e., consensus), all transactions in the block will be removed from all node mempools in the network. Figure 20.2 outlines the consensus flow of the new block.

image

Figure 20-2 Tendermint consensus for each block

When a Tendermint network reaches consensus on a new block, the nodes are only agreeing on the block’s structure and its cryptographic validity with regard to the previous blocks on the blockchain. The nodes actually have no idea about the validity of the transactions inside the block. To reach consensus on the results of the transactions inside the block, we will need the app hash from the commit message, which we will see next.

Consensus on the Transactions

The second, and most important type of message, is the DeliverTx message. At fixed-time intervals, all validator nodes in the network will reach consensus and determine the next block to be added to the blockchain. This new block contains all valid transactions submitted to the network during the time interval, and it is broadcast to all nodes on the network. Each node will run all the transactions in the block to the node’s local ABCI application instance. Each transaction is embedded in a DeliverTx message. The block starts with a StartBlock message to the ABCI, followed by a series of DeliverTx messages for all transactions in the block, and ends with an EndBlock message.

The ABCI application processes the DeliverTx messages in the order they are received. The ABCI application maintains its own database and updates the database as it processes the transactions (e.g., the database could be a ledger for user accounts, and each transaction moves funds between accounts). Since all nodes process the same set of transactions in the same order, once they are done, the ABCI applications on all nodes should have the same persistent state (i.e., their database content should be synchronized).

Note

It is possible that the DeliverTx message to the ABCI application could return a failure result. Since the network validators have already reached consensus on the block, the blockchain will annotate the failed transaction in this block in the block header.

A critical requirement of the ABCI application is that it must be deterministic. When it processes a set of transactions, it must reach the same results, in terms of the success/failure of each transaction and the overall application state, every time regardless of which node did the processing. That means the ACBI application logic should not have any dependency on random numbers, timestamps, and so on.

The ABCI application does not save to a database after each DeliverTx message. Instead, it processes the entire block of transactions and saves only at the end when it sees a Commit message. The Commit message should return the current state of the node, such as the node’s database hash, known as the app hash. The blockchain will stop working altogether if two-thirds of the validator nodes cannot agree on an app hash at the Commit of any block. If a node returns an app hash that is different than most nodes, this node is deemed corrupt and will not be able to participate in future consensus voting.

Getting Information

Finally, the ABCI protocol supports a third type of message, the Query message, which allows the Tendermint Core node to query the persistent state of the ABCI application. As mentioned, the ABCI application could maintain its own database, and the data stored in the database (i.e., its state) is determined by the history of transactions validated by the ABCI application. The blockchain node could query this database by issuing a query message.

A Sample Application

In the next sections, let’s get into the details by building an ABCI application. The application keeps track of a series of facts by their sources and stores the tallies in a database. The facts are submitted by external applications to any node on the blockchain. If a fact is accepted by the application, it will be recorded in the blockchain as a transaction. We will implement this application in the Java and GO languages.

Once the blockchain (Tendermint Core) and the facts ABCI application are running, you can send a series of facts as transactions to the blockchain. Each fact contains a source and a statement. Recall that Tendermint Core listens at port 46657 for transactions submitted to the blockchain.

curl -s 'localhost:46657/broadcast_tx_commit?tx="Michael:True%20fact"'
{
  "jsonrpc": "2.0",
  "id": "",
  "result": {
    "check_tx": {
      "code": 0,
      "data": "",
      "log": ""
    },
    "deliver_tx": {
      "code": 0,
      "data": "",
      "log": ""
    },
    "hash": "2A02B575181CEB71F03AF9715B236472D75025C2",
    "height": 18
  },
  "error": ""
}

As mentioned in the previous chapter, there are several ways to send the transaction data (it could be any byte array in the tx parameter field).

  • /broadcast_tx_commit: This is the message we used. It waits until the blockchain has validated the transaction and is added into a new block. When this message returns, you will be able to see both CheckTx and DeliverTx results.

  • /broadcast_tx_async: This message sends the transaction data to a blockchain node and does not wait for the blockchain’s response.

  • /broadcast_tx_sync: This message sends the transaction data to a blockchain node and waits for the CheckTx to run. This message returns the CheckTx result.

On the facts application console, you can see the transactions are processed and validated. Notice that there are both CheckTx and DeliverTx messages on all nodes. While the transaction is sent to only one node, the node broadcasts the transaction to all nodes once it passes the CheckTx message. So, each node will see this transaction, check it, save it to the mempool, and process it again when the new consensus block containing this transaction is received.

Commit 0 items
Check tx : Michael:True fact
The source is : Michael
The statement is : True fact
The fact is in the right format!
Deliver tx : Michael:True fact
The source is : Michael
The statement is : True fact
The count in this block is : 1
The fact is validated by this node!
Commit 1 items

You can also query the blockchain for the current application state. The ABCI application returns a tally of facts by sources. Notice that the actual fact statements are stored in the blockchain as transactions, and the ABCI application stores only the tally in its data store. The value field in the response is a Base64-encoded string of the response text in the log field.

curl -s 'localhost:46657/abci_query?data="all"'
{
  "jsonrpc": "2.0",
  "id": "",
  "result": {
    "response": {
      "code": 0,
      "index": 0,
      "key": "",
      "value": "4A696D3A312C4D69636861656C3A32",
      "proof": "",
      "height": 0,
      "log": "Jim:1,Michael:2"
    }
  },
  "error": ""
}

Next, let’s look into how to implement this simple facts ABCI application. We will discuss both Java and GO language implementations. You can just choose a language you are most comfortable with.

Java Implementation

The Java application is built on the jTendermint library. When the application starts up, it listens on ABCI’s default TCP port 46658 to receive transactions from the Tendermint Core software running on the same node.

For the sake of simplicity, we will not use an external relational database to store the application state. Instead, we instantiate a global hash table in the application as the data store. The hash table key is a unique source of the facts, and the value is the number of facts associated with this source. The downside, of course, is that the application state is lost if the application crashes. When the application starts, it starts a socket server to listen for messages from the blockchain.

public final class FactsApp
        implements IDeliverTx, ICheckTx, ICommit, IQuery {

    public static Hashtable<String, Integer> db;
    public static Hashtable<String, Integer> cache;

    private TSocket socket;

    public static void main(String[] args) throws Exception {
        new FactsApp ();
    }

    public FactsApp () throws InterruptedException {
        socket = new TSocket();
        socket.registerListener(this);

        // Init the database
        db = new Hashtable <String, Integer> ();
        cache = new Hashtable <String, Integer> ();

        Thread t = new Thread(socket::start);
        t.setName("Facts App Thread");
        t.start();
        while (true) {
            Thread.sleep(1000L);
        }
   }
   ... ...
}

The ResponseCheckTx method handles the CheckTx messages from Tendermint Core. As you probably recall, the CheckTx message is sent when the blockchain node receives a transaction request. The ABCI application simply parses the fact from the message into a source element and a statement element. If the message parses successfully, the ABCI application returns ok, and the transaction is broadcast and synchronized to all nodes on the network. For brevity, I removed the statements to log messages to the facts application console, which you saw in the previous section.

public ResponseCheckTx requestCheckTx (RequestCheckTx req) {
    ByteString tx = req.getTx();
    String payload = tx.toStringUtf8();
    if (payload == null || payload.isEmpty()) {
        return ResponseCheckTx.newBuilder()
            .setCode(CodeType.BAD)
            .setLog("payload is empty").build();
    }
    String [] parts = payload.split(":", 2);
    String source = "";
    String statement = "";
    try {
        source = parts[0].trim();
        statement = parts[1].trim();
        if (source.isEmpty() || statement.isEmpty()) {
            throw new Exception("Payload parsing error");
        }
    } catch (Exception e) {
        return ResponseCheckTx.newBuilder()
            .setCode(CodeType.BAD)
            .setLog(e.getMessage()).build();
    }

    return ResponseCheckTx.newBuilder().setCode(CodeType.OK).build();
}

Note

The CheckTx message in this example is simplistic. In most applications, the CheckTx message handler method will use the application’s current database state to check the transaction. The application state (i.e., the app hash) is updated by the last block’s Commit message. The CheckTx method should never modify the application state.

Next, after the network reaches consensus on the next block, each node will send transactions in this block as a series of DeliverTx messages to the ABCI application. The ResponseDeliverTx method handles the DeliverTx messages. It again parses the fact in the message and then tallies by the source in a temporary cache. Since all nodes will see the same set of DeliverTx messages in the same order, they should update the application’s database in sequence. That is, a second DeliverTx is working off the database after changes have been made by the first DeliverTx. However, the DeliverTx itself should update only a temporary (often in-memory) replicate of the database, and the changes are flushed to the permanent (often on-disk) database at Commit. This is not only efficient but also ensures the application’s database state is always set at the Commit state of the last block. In this simple example, however, our application database is in memory and the DeliverTx processing does not depend on the current state of the database.

public ResponseDeliverTx receivedDeliverTx (RequestDeliverTx req) {
    ByteString tx = req.getTx();
    String payload = tx.toStringUtf8();
    if (payload == null || payload.isEmpty()) {
        return ResponseDeliverTx.newBuilder()
            .setCode(CodeType.BAD)
            .setLog("payload is empty").build();
    }
    String [] parts = payload.split(":", 2);
    String source = "";
    String statement = "";
    try {
        source = parts[0].trim();
        statement = parts[1].trim();
        if (source.isEmpty() || statement.isEmpty()) {
            throw new Exception("Payload parsing error");
        }
    } catch (Exception e) {
        return ResponseDeliverTx.newBuilder()
            .setCode(CodeType.BAD)
            .setLog(e.getMessage()).build();
    }

    // In the DeliverTx message handler,
    // we will only count facts in this block.
    if (cache.containsKey(source)) {
        int count = cache.get(source);
        cache.put(source, count++);
    } else {
        cache.put(source, 1);
    }

    return ResponseDeliverTx.newBuilder().setCode(CodeType.OK).build();
}

When the ABCI application sees the Commit message, it saves all the temporary tallies to the Hashtable-based data store. It returns the hash code of the data store as the app hash. All nodes have to agree on the app hash after committing this block. If a node returns a different app hash than other nodes, it is deemed corrupt and will not be allowed to participate in the future consensus.

public ResponseCommit requestCommit (RequestCommit requestCommit) {
    Set<String> keys = cache.keySet();
    for (String source: keys) {
        if (db.containsKey(source)) {
            db.put(source, cache.get(source) + db.get(source));
        } else {
            db.put(source, cache.get(source));
        }
    }
    cache.clear();

    return ResponseCommit.newBuilder()
      .setData(ByteString.copyFromUtf8(
      String.valueOf(db.hashCode()))).build();
}

Finally, an external application can query the blockchain for the application state. In this case, a Query message will be passed from Tendermint Core to the application. The ResponseQuery method handles this message and returns the tallies for all sources from the data store.

public ResponseQuery requestQuery (RequestQuery req) {
  String query = req.getData().toStringUtf8();

  if (query.equalsIgnoreCase("all")) {
    StringBuffer buf = new StringBuffer ();
    String prefix = "";
    Set<String> keys = db.keySet();
    for (String source: keys) {
      buf.append(prefix);
      prefix = ",";
      buf.append(source).append(":").append(db.get(source));
    }
    return ResponseQuery.newBuilder().setCode(CodeType.OK).setValue(
            ByteString.copyFromUtf8((buf.toString()))
    ).setLog(buf.toString()).build();
  }

  if (query.startsWith("Source")) {
    String keyword = query.substring(6).trim();
    if (db.containsKey(keyword)) {
      return ResponseQuery.newBuilder().setCode(CodeType.OK).setValue(
          ByteString.copyFromUtf8(db.get(keyword).toString())
      ).setLog(db.get(keyword).toString()).build();
    }
  }

  return ResponseQuery.newBuilder()
      .setCode(CodeType.BadNonce).setLog("Invalid query").build();
}

The blockchain itself stores the validated source : statement data submitted by external applications. The ABCI application stores the tallies of facts based on sources, and the tallies are synchronized across all nodes since the ABCI application on all nodes run the same set of transactions that get written into the blockchain.

We use Maven to build an executable binary of the application. You can review the pom.xml file in the source code repository to see how to build the executable JAR file.

$ mvn clean package

You can run the ABCI application from command line and it will automatically connect to a Tendermint Core instance running on the same node.

$ java –jar facts-1.0.jar

GO Implementation

Tendermint itself is built on the GO programming language. It is not surprising that GO is a well-supported language platform to build ABCI applications. The main method in the application listens for ABCI messages on port 46658.

package main

import (
  "flag"
  "os"
  "strings"
  "bytes"
  "strconv"
  "github.com/tendermint/abci/example/code"
  "github.com/tendermint/abci/server"
  "github.com/tendermint/abci/types"
  cmn "github.com/tendermint/tmlibs/common"
  "github.com/tendermint/tmlibs/log"
)

func main() {
  addrPtr := flag.String("addr", "tcp://0.0.0.0:46658", "Listen address")
  abciPtr := flag.String("abci", "socket", "socket | grpc")
  flag.Parse()

  logger := log.NewTMLogger(log.NewSyncWriter(os.Stdout))

  var app types.Application
  app = NewFactsApplication()
  // Start the listener
  srv, err := server.NewServer(*addrPtr, *abciPtr, app)
  if err != nil {
    logger.Error(err.Error())
    os.Exit(1)
  }
  srv.SetLogger(logger.With("module", "abci-server"))
  if err := srv.Start(); err != nil {
    logger.Error(err.Error())
    os.Exit(1)
  }

  // Wait forever
  cmn.TrapSignal(func() {
    // Cleanup
    srv.Stop()
  })
}

Similar to the Java application, we will use an in-memory map to store the application state (i.e., the facts tallies) for simplicity.

type FactsApplication struct {
  types.BaseApplication

  db map[string]int
  cache map[string]int
}

func NewFactsApplication() *FactsApplication {
  db := make(map[string]int)
  cache := make(map[string]int)
  return &FactsApplication{db: db, cache: cache}
}

The CheckTx method handles the CheckTx messages from Tendermint Core. As you probably recall, the CheckTx message is sent when the blockchain node receives a transaction request. The ABCI application simply parses the fact from the message into a source element and a statement element. If the message parses successfully, the ABCI application returns okay, and the transaction is broadcast and synchronized to all nodes on the network.

func (app *FactsApplication) CheckTx (tx []byte) types.ResponseCheckTx {
  parts := strings.Split(string(tx), ":")
  source := strings.TrimSpace(parts[0])
  statement := strings.TrimSpace(parts[1])
  if (len(source) == 0) || (len(statement) == 0) {
    return types.ResponseCheckTx{
        Code:code.CodeTypeEncodingError,
        Log:"Empty Input"
    }
  }
  return types.ResponseCheckTx{Code: code.CodeTypeOK}
}

Note

The CheckTx message in this example is simplistic. In most applications, the CheckTx message handler method will use the application’s current database state to check that the transaction is valid. The application state (i.e., the app hash) is updated by the last block’s Commit message. The CheckTx method should never modify the application state.

Next, after the network reaches consensus on the next block, each node will send transactions in this block as a series of DeliverTx messages to the ABCI application. The DeliverTx method handles the DeliverTx messages. It again parses the fact in the message and then tallies by the source in a temporary cache. Since all nodes will see the same set of DeliverTx messages in the same order, they should update the application’s database in sequence. That is, a second DeliverTx is working off the database after changes have been made by the first DeliverTx. However, the DeliverTx itself should update only a temporary (often in-memory) replicate of the database, and the changes are flushed to the permanent (often on-disk) database at Commit. This is not only efficient but also ensures the application’s database state is always set at the Commit state of the last block.

func (app *FactsApplication) DeliverTx (tx []byte) types.ResponseDeliverTx {
  parts := strings.Split(string(tx), ":")
  source := strings.TrimSpace(parts[0])
  statement := strings.TrimSpace(parts[1])
  if (len(source) == 0) || (len(statement) == 0) {
    return types.ResponseDeliverTx{
      Code:code.CodeTypeEncodingError,
      Log:"Empty Input"
    }
  }

  if val, ok := app.cache[source]; ok {
    app.cache[source] = val + 1
  } else {
    app.cache[source] = 1
  }
  return types.ResponseDeliverTx{Code: code.CodeTypeOK}
}

When the ABCI application sees the Commit message, it saves all the temporary tallies to the map-based data store. It returns the hash of the total count of entries in the data store as the app hash. All nodes have to agree on the app hash after committing this block. If a node returns a different app hash than other nodes, it is deemed corrupt and will not be allowed to participate in the future consensus.

func (app *FactsApplication) Commit() types.ResponseCommit {
  for source, v := range app.cache {
    if val, ok := app.db[source]; ok {
      app.db[source] = val + v
    } else {
      app.db[source] = v
    }
  }
  app.cache = make(map[string]int)

  hash := make([]byte, 8)
  binary.BigEndian.PutUint64(hash, uint64(totalCount))
  return types.ResponseCommit{Data: hash}
}

Finally, an external application can query the blockchain for the application state. In this case, a Query message will be passed from Tendermint Core to the application. The Query method handles this message and returns the tallies for all sources from the data store.

func (app *FactsApplication) Query (reqQuery types.RequestQuery)
                                   (resQuery types.ResponseQuery) {
  query := string(reqQuery.Data)

  if (strings.EqualFold(query, "all")) {
    var buffer bytes.Buffer
    var prefix = ""
    for source, v := range app.db {
      buffer.WriteString(prefix)
      prefix = ","
      buffer.WriteString(source)
      buffer.WriteString(":")
      buffer.WriteString(strconv.Itoa(v))
    }
    resQuery.Value = buffer.Bytes()
    resQuery.Log = buffer.String()
  }

  if (strings.HasPrefix(query, "Source")) {
    source := query[6:len(query)]
    if val, ok := app.db[source]; ok {
      resQuery.Value = []byte(strconv.Itoa(val))
      resQuery.Log = string(val)
    }
  }

  return
}

The blockchain itself stores the validated source : statement data submitted by external applications. The ABCI application stores the tallies of facts based on sources, and the tallies are synchronized across all nodes since the ABCI application on all nodes run the same set of transactions that get written into the blockchain.

We use the default tools to compile and build the GO application.

$ go build

You can run the application from command line and it will automatically connect to a Tendermint Core instance running on the same node.

$ ./facts

The Cosmos SDK

Tendermint provides a flexible framework for building business logic on top of its consensus engine. However, as you have seen, we have to write the entire application logic from scratch using ABCI. From Tendermint’s point of view, the application data is simply a byte array. For many blockchain applications, they require the same set of baseline functionalities, such as user accounts/address management, token issuance, and PoS-style staking. It is tedious and error prone for developers to write those components over and over again for all Tendermint-based blockchains. That gives rise to application frameworks on top of the ABCI for common business components. The Cosmos SDK is one such component library for Tendermint. It is written on the GO language. The Cosmos Hub project itself is built on the Cosmos SDK.

The Cosmos SDK is still evolving, and its technical details are beyond the scope of this book. I recommend you visit the Cosmos SDK web site for the latest documentation and tutorials (https://github.com/cosmos/cosmos-sdk). In this section, I will provide a high-level introduction to the design and functions of the SDK. The SDK provides built-in support for basic infrastructure needed for most ABCI applications.

  • The SDK allows developers to easily create and maintain any number of key-value data stores known as KVStore. Those data stores are used to manage application state data during CheckTx and DeliverTx operations. For example, DeliverTx needs to process all transactions in the block on a cached copy of the blockchain state and Commit those changes when the processing is successfully finished.

  • The SDK provides a data marshalling and unmarshalling library called go-amino. It allows byte array data in transactions to be easily converted to GO objects back and forth.

  • The SDK provides a router object to route all messages from a Remote Procedure Call (RPC) connector to different modules in the SDK for further processing. The router is set up in the way that allows the messages to be processed by multiple modules in any specified order.

In your Cosmos SDK application, you will configure the router for incoming messages. The following is an example from the Cosmos SDK tutorial:

app.Router().
    AddRoute(bank.RouterKey, bank.NewHandler(app.bankKeeper)).
    AddRoute(staking.RouterKey, staking.NewHandler(app.stakingKeeper)).
    ... ...

Incoming messages in transactions are first processed by the bank module and then the staking module. app.bankKeeper is a callback method implemented by the application developer to process events emitted from the bank module. For example, it could respond to events when one user transfers funds to another. The Cosmos SDK provides a library of modules. Currently, most of them are related to handling crypto tokens.

  • The auth module checks and validates signatures in transactions.

  • The bank module manages user accounts and addresses for holding crypto tokens.

  • The mint module manages minting and issues crypto tokens during the operation of the blockchain.

  • The staking module manages how users could stake their tokens to support network security in a proof-of-stake (PoS) manner.

  • The distribution module manages how the staking award (interest on staking) is distributed to users.

  • The slashing module manages how to punish users who staked misbehaving actors in the system.

  • The ibc module manages the cross-chain asset exchange protocol supported by the Cosmos Hub.

As of April 2019, the Cosmos SDK implements basic functionalities for a generic PoS blockchain. It does not yet support any virtual machine functionalities. To support programmable blockchains, the Cosmos SDK road map calls for incorporating virtual machines as modules to process transactions. The future of Cosmos SDK is bright.

Conclusion

In this chapter, we explored the ABCI protocol and demonstrated how to build blockchain applications. Those ABCI applications allow the blockchain to offload much of the computationally intensive tasks. Developers can now write applications with complex transactional logic in a highly efficient manner. An important space to watch is the development of the Cosmos SDK, which could dramatically simplify the development of Tendermint-based application blockchains.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
52.14.121.242