diff --git a/docs/app-dev/abci-cli.md b/docs/app-dev/abci-cli.md index eed169ec7..50eecaac8 100644 --- a/docs/app-dev/abci-cli.md +++ b/docs/app-dev/abci-cli.md @@ -27,22 +27,28 @@ Usage: abci-cli [command] Available Commands: - batch Run a batch of abci commands against an application - check_tx Validate a tx - commit Commit the application state and return the Merkle root hash - console Start an interactive abci console for multiple commands - deliver_tx Deliver a new tx to the application - kvstore ABCI demo example - echo Have the application echo a message - help Help about any command - info Get some info about the application - query Query the application state + batch run a batch of abci commands against an application + check_tx validate a transaction + commit commit the application state and return the Merkle root hash + completion Generate the autocompletion script for the specified shell + console start an interactive ABCI console for multiple commands + deliver_tx deliver a new transaction to the application + echo have the application echo a message + help Help about any command + info get some info about the application + kvstore ABCI demo example + prepare_proposal prepare proposal + process_proposal process proposal + query query the application state + test run integration tests + version print ABCI console version Flags: - --abci string socket or grpc (default "socket") - --address string address of application socket (default "tcp://127.0.0.1:26658") - -h, --help help for abci-cli - -v, --verbose print the command and results as if it were a console session + --abci string either socket or grpc (default "socket") + --address string address of application socket (default "tcp://0.0.0.0:26658") + -h, --help help for abci-cli + --log_level string set the logger level (default "debug") + -v, --verbose print the command and results as if it were a console session Use "abci-cli [command] --help" for more information about a command. ``` @@ -58,47 +64,51 @@ purposes. We'll start a kvstore application, which was installed at the same time as `abci-cli` above. The kvstore just stores transactions in a merkle -tree. - -Its code can be found -[here](https://github.com/tendermint/tendermint/blob/v0.34.x/abci/cmd/abci-cli/abci-cli.go) -and looks like: +tree. Its code can be found +[here](https://github.com/tendermint/tendermint/blob/main/abci/cmd/abci-cli/abci-cli.go) +and looks like the following: ```go func cmdKVStore(cmd *cobra.Command, args []string) error { - logger := log.NewTMLogger(log.NewSyncWriter(os.Stdout)) + logger := log.NewTMLogger(log.NewSyncWriter(os.Stdout)) - // Create the application - in memory or persisted to disk - var app types.Application - if flagPersist == "" { - app = kvstore.NewKVStoreApplication() - } else { - app = kvstore.NewPersistentKVStoreApplication(flagPersist) - app.(*kvstore.PersistentKVStoreApplication).SetLogger(logger.With("module", "kvstore")) - } + // Create the application - in memory or persisted to disk + var app types.Application + if flagPersist == "" { + var err error + flagPersist, err = os.MkdirTemp("", "persistent_kvstore_tmp") + if err != nil { + return err + } + } + app = kvstore.NewPersistentKVStoreApplication(flagPersist) + app.(*kvstore.PersistentKVStoreApplication).SetLogger(logger.With("module", "kvstore")) - // Start the listener - srv, err := server.NewServer(flagAddrD, flagAbci, app) - if err != nil { - return err - } - srv.SetLogger(logger.With("module", "abci-server")) - if err := srv.Start(); err != nil { - return err - } + // Start the listener + srv, err := server.NewServer(flagAddress, flagAbci, app) + if err != nil { + return err + } + srv.SetLogger(logger.With("module", "abci-server")) + if err := srv.Start(); err != nil { + return err + } - // Stop upon receiving SIGTERM or CTRL-C. - tmos.TrapSignal(logger, func() { - // Cleanup - srv.Stop() - }) + // Stop upon receiving SIGTERM or CTRL-C. + tmos.TrapSignal(logger, func() { + // Cleanup + if err := srv.Stop(); err != nil { + logger.Error("Error while stopping server", "err", err) + } + }) - // Run forever. - select {} + // Run forever. + select {} } + ``` -Start by running: +Start the application by running: ```sh abci-cli kvstore @@ -163,32 +173,32 @@ Try running these commands: -> data: hello -> data.hex: 0x68656C6C6F -> info +> info -> code: OK -> data: {"size":0} -> data.hex: 0x7B2273697A65223A307D > prepare_proposal "abc" -> code: OK --> log: Succeeded. Tx: abc action: UNMODIFIED +-> log: Succeeded. Tx: abc > process_proposal "abc" -> code: OK -> status: ACCEPT -> commit +> commit -> code: OK -> data.hex: 0x0000000000000000 > deliver_tx "abc" -> code: OK -> info +> info -> code: OK -> data: {"size":1} -> data.hex: 0x7B2273697A65223A317D -> commit +> commit -> code: OK -> data.hex: 0x0200000000000000 @@ -204,7 +214,7 @@ Try running these commands: > deliver_tx "def=xyz" -> code: OK -> commit +> commit -> code: OK -> data.hex: 0x0400000000000000 @@ -219,11 +229,9 @@ Try running these commands: > prepare_proposal "preparedef" -> code: OK --> log: Succeeded. Tx: def action: ADDED --> code: OK --> log: Succeeded. Tx: preparedef action: REMOVED +-> log: Succeeded. Tx: replacedef -> process_proposal "def" +> process_proposal "replacedef" -> code: OK -> status: ACCEPT @@ -245,21 +253,21 @@ Try running these commands: Note that if we do `deliver_tx "abc"` it will store `(abc, abc)`, but if we do `deliver_tx "abc=efg"` it will store `(abc, efg)`. -Similarly, you could put the commands in a file and run +You could put the commands in a file and run `abci-cli --verbose batch < myfile`. + +Note that the `abci-cli` is designed strictly for testing and debugging. In a real +deployment, the role of sending messages is taken by Tendermint, which +connects to the app using three separate connections, each with its own +pattern of messages. + +For examples of running an ABCI app with Tendermint, see the +[getting started guide](./getting-started.md). + ## Bounties Want to write an app in your favorite language?! We'd be happy to add you to our [ecosystem](https://github.com/tendermint/awesome#ecosystem)! See [funding](https://github.com/interchainio/funding) opportunities from the [Interchain Foundation](https://interchain.io) for implementations in new languages and more. - -The `abci-cli` is designed strictly for testing and debugging. In a real -deployment, the role of sending messages is taken by Tendermint, which -connects to the app using three separate connections, each with its own -pattern of messages. - -For examples of running an ABCI app with -Tendermint, see the [getting started guide](./getting-started.md). -Next is the ABCI specification. diff --git a/docs/app-dev/getting-started.md b/docs/app-dev/getting-started.md index af1aff5ff..081ca8dc6 100644 --- a/docs/app-dev/getting-started.md +++ b/docs/app-dev/getting-started.md @@ -11,9 +11,10 @@ application you want to run. So, to run a complete blockchain that does something useful, you must start two programs: one is Tendermint Core, the other is your application, which can be written in any programming language. Recall from [the intro to -ABCI](../introduction/what-is-tendermint.md#abci-overview) that Tendermint Core handles all the p2p and consensus stuff, and just forwards transactions to the +ABCI](../introduction/what-is-tendermint.md#abci-overview) that Tendermint Core +handles all the p2p and consensus stuff, and just forwards transactions to the application when they need to be validated, or when they're ready to be -committed to a block. +executed and committed. In this guide, we show you some examples of how to run an application using Tendermint. @@ -22,7 +23,8 @@ using Tendermint. The first apps we will work with are written in Go. To install them, you need to [install Go](https://golang.org/doc/install), put -`$GOPATH/bin` in your `$PATH` and enable go modules with these instructions: +`$GOPATH/bin` in your `$PATH` and enable go modules. If you use `bash`, +follow these instructions: ```bash echo export GOPATH=\"\$HOME/go\" >> ~/.bash_profile @@ -31,17 +33,48 @@ echo export PATH=\"\$PATH:\$GOPATH/bin\" >> ~/.bash_profile Then run -```sh +```bash go get github.com/tendermint/tendermint cd $GOPATH/src/github.com/tendermint/tendermint make install_abci ``` -Now you should have the `abci-cli` installed; you'll notice the `kvstore` -command, an example application written -in Go. See below for an application written in JavaScript. +Now you should have the `abci-cli` installed; run `abci-cli` to see the list of commands: -Now, let's run some apps! +``` +Usage: + abci-cli [command] + +Available Commands: + batch run a batch of abci commands against an application + check_tx validate a transaction + commit commit the application state and return the Merkle root hash + completion Generate the autocompletion script for the specified shell + console start an interactive ABCI console for multiple commands + deliver_tx deliver a new transaction to the application + echo have the application echo a message + help Help about any command + info get some info about the application + kvstore ABCI demo example + prepare_proposal prepare proposal + process_proposal process proposal + query query the application state + test run integration tests + version print ABCI console version + +Flags: + --abci string either socket or grpc (default "socket") + --address string address of application socket (default "tcp://0.0.0.0:26658") + -h, --help help for abci-cli + --log_level string set the logger level (default "debug") + -v, --verbose print the command and results as if it were a console session + +Use "abci-cli [command] --help" for more information about a command. +``` + +You'll notice the `kvstore` command, an example application written in Go. + +Now, let's run an app! ## KVStore - A First Example @@ -68,7 +101,7 @@ tendermint node ``` If you have used Tendermint, you may want to reset the data for a new -blockchain by running `tendermint unsafe_reset_all`. Then you can run +blockchain by running `tendermint unsafe-reset-all`. Then you can run `tendermint node` to start Tendermint, and connect to the app. For more details, see [the guide on using Tendermint](../tendermint-core/using-tendermint.md). @@ -164,47 +197,3 @@ curl -s 'localhost:26657/abci_query?data="name"' Try some other transactions and queries to make sure everything is working! - - -## CounterJS - Example in Another Language - -We also want to run applications in another language - in this case, -we'll run a Javascript version of the `counter`. To run it, you'll need -to [install node](https://nodejs.org/en/download/). - -You'll also need to fetch the relevant repository, from -[here](https://github.com/tendermint/js-abci), then install it: - -```sh -git clone https://github.com/tendermint/js-abci.git -cd js-abci -npm install abci -``` - -Kill the previous `counter` and `tendermint` processes. Now run the app: - -```sh -node example/counter.js -``` - -In another window, reset and start `tendermint`: - -```sh -tendermint unsafe_reset_all -tendermint node -``` - -Once again, you should see blocks streaming by - but now, our -application is written in Javascript! Try sending some transactions, and -like before - the results should be the same: - -```sh -# ok -curl localhost:26657/broadcast_tx_commit?tx=0x00 -# invalid nonce -curl localhost:26657/broadcast_tx_commit?tx=0x05 -# ok -curl localhost:26657/broadcast_tx_commit?tx=0x01 -``` - -Neat, eh? diff --git a/docs/introduction/what-is-tendermint.md b/docs/introduction/what-is-tendermint.md index b22ca38ba..a35d39f94 100644 --- a/docs/introduction/what-is-tendermint.md +++ b/docs/introduction/what-is-tendermint.md @@ -6,7 +6,7 @@ order: 4 Tendermint is software for securely and consistently replicating an application on many machines. By securely, we mean that Tendermint works -even if up to 1/3 of machines fail in arbitrary ways. By consistently, +as long as less than 1/3 of machines fail in arbitrary ways. By consistently, we mean that every non-faulty machine sees the same transaction log and computes the same state. Secure and consistent replication is a fundamental problem in distributed systems; it plays a critical role in @@ -22,15 +22,14 @@ reformalization of BFT in a more modern setting, with emphasis on peer-to-peer networking and cryptographic authentication. The name derives from the way transactions are batched in blocks, where each block contains a cryptographic hash of the previous one, forming a -chain. In practice, the blockchain data structure actually optimizes BFT -design. +chain. Tendermint consists of two chief technical components: a blockchain consensus engine and a generic application interface. The consensus engine, called Tendermint Core, ensures that the same transactions are recorded on every machine in the same order. The application interface, -called the Application BlockChain Interface (ABCI), enables the -transactions to be processed in any programming language. Unlike other +called the Application BlockChain Interface (ABCI), delivers the transactions +to applications for processing. Unlike other blockchain and consensus solutions, which come pre-packaged with built in state machines (like a fancy key-value store, or a quirky scripting language), developers can use Tendermint for BFT state machine @@ -51,13 +50,13 @@ Hyperledger's Burrow. ### Zookeeper, etcd, consul -Zookeeper, etcd, and consul are all implementations of a key-value store -atop a classical, non-BFT consensus algorithm. Zookeeper uses a version -of Paxos called Zookeeper Atomic Broadcast, while etcd and consul use -the Raft consensus algorithm, which is much younger and simpler. A +Zookeeper, etcd, and consul are all implementations of key-value stores +atop a classical, non-BFT consensus algorithm. Zookeeper uses an +algorithm called Zookeeper Atomic Broadcast, while etcd and consul use +the Raft log replication algorithm. A typical cluster contains 3-5 machines, and can tolerate crash failures -in up to 1/2 of the machines, but even a single Byzantine fault can -destroy the system. +in less than 1/2 of the machines (e.g., 1 out of 3 or 2 out of 5), +but even a single Byzantine fault can jeopardize the whole system. Each offering provides a slightly different implementation of a featureful key-value store, but all are generally focused around @@ -66,8 +65,8 @@ configuration, service discovery, locking, leader-election, and so on. Tendermint is in essence similar software, but with two key differences: -- It is Byzantine Fault Tolerant, meaning it can only tolerate up to a - 1/3 of failures, but those failures can include arbitrary behavior - +- It is Byzantine Fault Tolerant, meaning it can only tolerate less than 1/3 + of machines failing, but those failures can include arbitrary behavior - including hacking and malicious attacks. - It does not specify a particular application, like a fancy key-value store. Instead, it focuses on arbitrary state machine replication, so developers can build @@ -106,8 +105,8 @@ docker containers, modules it calls "chaincode". It uses an implementation of [PBFT](http://pmg.csail.mit.edu/papers/osdi99.pdf). from a team at IBM that is [augmented to handle potentially non-deterministic -chaincode](https://drops.dagstuhl.de/opus/volltexte/2017/7093/pdf/LIPIcs-OPODIS-2016-24.pdf) It is -possible to implement this docker-based behavior as a ABCI app in +chaincode](https://drops.dagstuhl.de/opus/volltexte/2017/7093/pdf/LIPIcs-OPODIS-2016-24.pdf). +It is possible to implement this docker-based behavior as an ABCI app in Tendermint, though extending Tendermint to handle non-determinism remains for future work. @@ -143,24 +142,22 @@ in design and suffers from "spaghetti code". Another problem with monolithic design is that it limits you to the language of the blockchain stack (or vice versa). In the case of Ethereum which supports a Turing-complete bytecode virtual-machine, it -limits you to languages that compile down to that bytecode; today, those -are Serpent and Solidity. +limits you to languages that compile down to that bytecode; while the +[list](https://github.com/pirapira/awesome-ethereum-virtual-machine#programming-languages-that-compile-into-evm) +is growing, it is still very limited. In contrast, our approach is to decouple the consensus engine and P2P -layers from the details of the application state of the particular +layers from the details of the state of the particular blockchain application. We do this by abstracting away the details of the application to an interface, which is implemented as a socket protocol. -Thus we have an interface, the Application BlockChain Interface (ABCI), -and its primary implementation, the Tendermint Socket Protocol (TSP, or -Teaspoon). - ### Intro to ABCI -[Tendermint Core](https://github.com/tendermint/tendermint) (the -"consensus engine") communicates with the application via a socket -protocol that satisfies the ABCI. +[Tendermint Core](https://github.com/tendermint/tendermint), the +"consensus engine", communicates with the application via a socket +protocol that satisfies the ABCI, the Tendermint Socket Protocol +(TSP, or Teaspoon). To draw an analogy, lets talk about a well-known cryptocurrency, Bitcoin. Bitcoin is a cryptocurrency blockchain where each node @@ -180,7 +177,7 @@ The application will be responsible for - Allowing clients to query the UTXO database. Tendermint is able to decompose the blockchain design by offering a very -simple API (ie. the ABCI) between the application process and consensus +simple API (i.e. the ABCI) between the application process and consensus process. The ABCI consists of 3 primary message types that get delivered from the @@ -239,8 +236,7 @@ Solidity on Ethereum is a great language of choice for blockchain applications because, among other reasons, it is a completely deterministic programming language. However, it's also possible to create deterministic applications using existing popular languages like -Java, C++, Python, or Go. Game programmers and blockchain developers are -already familiar with creating deterministic programs by avoiding +Java, C++, Python, or Go, by avoiding sources of non-determinism such as: - random number generators (without deterministic seeding) @@ -271,14 +267,15 @@ committed in a chain, with one block at each **height**. A block may fail to be committed, in which case the protocol moves to the next **round**, and a new validator gets to propose a block for that height. Two stages of voting are required to successfully commit a block; we -call them **pre-vote** and **pre-commit**. A block is committed when -more than 2/3 of validators pre-commit for the same block in the same -round. +call them **pre-vote** and **pre-commit**. There is a picture of a couple doing the polka because validators are doing something like a polka dance. When more than two-thirds of the validators pre-vote for the same block, we call that a **polka**. Every pre-commit must be justified by a polka in the same round. +A block is committed when +more than 2/3 of validators pre-commit for the same block in the same +round. Validators may fail to commit a block for a number of reasons; the current proposer may be offline, or the network may be slow. Tendermint diff --git a/docs/tutorials/go-built-in.md b/docs/tutorials/go-built-in.md index 46a61c33b..70b2d0892 100644 --- a/docs/tutorials/go-built-in.md +++ b/docs/tutorials/go-built-in.md @@ -10,44 +10,45 @@ This guide is designed for beginners who want to get started with a Tendermint Core application from scratch. It does not assume that you have any prior experience with Tendermint Core. -Tendermint Core is Byzantine Fault Tolerant (BFT) middleware that takes a state -transition machine - written in any programming language - and securely -replicates it on many machines. +Tendermint Core is a service that provides a Byzantine Fault Tolerant consensus engine +for state-machine replication. The replicated state-machine, or "application", can be written +in any language that can send and receive protocol buffer messages in a client-server model. +Applications written in Go can also use Tendermint as a library and run the service in the same +process as the application. -Although Tendermint Core is written in the Golang programming language, prior -knowledge of it is not required for this guide. You can learn it as we go due -to it's simplicity. However, you may want to go through [Learn X in Y minutes -Where X=Go](https://learnxinyminutes.com/docs/go/) first to familiarize +By following along this tutorial you will create a Tendermint Core application called kvstore, +a (very) simple distributed BFT key-value store. +The application will be written in Go and +some understanding of the Go programming language is expected. +If you have never written Go, you may want to go through [Learn X in Y minutes +Where X=Go](https://learnxinyminutes.com/docs/go/) first, to familiarize yourself with the syntax. -By following along with this guide, you'll create a Tendermint Core project -called kvstore, a (very) simple distributed BFT key-value store. +Note: Please use the latest released version of this guide and of Tendermint. +We strongly advise against using unreleased commits for your development. -## Built-in app vs external app -Running your application inside the same process as Tendermint Core will give -you the best possible performance. +### Built-in app vs external app + +On the one hand, to get maximum performance you can run your application in +the same process as the Tendermint Core, as long as your application is written in Go. +[Cosmos SDK](https://github.com/cosmos/cosmos-sdk) is written +this way. +This is the approach followed in this tutorial. + +On the other hand, having a separate application might give you better security +guarantees as two processes would be communicating via established binary protocol. +Tendermint Core will not have access to application's state. +If that is the way you wish to proceed, use the [Creating an application in Go](./go.md) guide instead of this one. -For other languages, your application have to communicate with Tendermint Core -through a TCP, Unix domain socket or gRPC. ## 1.1 Installing Go -Please refer to [the official guide for installing -Go](https://golang.org/doc/install). - -Verify that you have the latest version of Go installed: +Verify that you have the latest version of Go installed (refer to the [official guide for installing Go](https://golang.org/doc/install)): ```bash $ go version -go version go1.13.1 darwin/amd64 -``` - -Make sure you have `$GOPATH` environment variable set: - -```bash -$ echo $GOPATH -/Users/melekes/go +go version go1.19.2 darwin/amd64 ``` ## 1.2 Creating a new Go project @@ -56,39 +57,72 @@ We'll start by creating a new Go project. ```bash mkdir kvstore -cd kvstore ``` -Inside the example directory create a `main.go` file with the following content: +Inside the example directory, create a `main.go` file with the following content: ```go package main import ( - "fmt" + "fmt" ) func main() { - fmt.Println("Hello, Tendermint Core") + fmt.Println("Hello, Tendermint Core") } ``` When run, this should print "Hello, Tendermint Core" to the standard output. ```bash +cd kvstore $ go run main.go Hello, Tendermint Core ``` +We are going to use [Go modules](https://github.com/golang/go/wiki/Modules) for +dependency management, so let's start by including a dependency on the latest version of +Tendermint. + +```bash +go mod init kvstore +go get github.com/tendermint/tendermint@latest +``` + +After running the above commands you will see two generated files, `go.mod` and `go.sum`. +The go.mod file should look similar to: + +```go +module github.com/me/example + +go 1.19 + +require ( + github.com/tendermint/tendermint v0.37.0 +) +``` + +As you write the kvstore application, you can rebuild the binary by +pulling any new dependencies and recompiling it. + +```sh +go get +go build +``` + ## 1.3 Writing a Tendermint Core application Tendermint Core communicates with the application through the Application -BlockChain Interface (ABCI). All message types are defined in the [protobuf +BlockChain Interface (ABCI). The messages exchanged through the interface are +defined in the ABCI [protobuf file](https://github.com/tendermint/tendermint/blob/main/proto/tendermint/abci/types.proto). -This allows Tendermint Core to run applications written in any programming -language. -Create a file called `app.go` with the following content: +We begin by creating the basic scaffolding for an ABCI application by +creating a new type, `KVStoreApplication`, which implements the +methods defined by the `abcitypes.Application` interface. + +Create a file called `app.go` with the following contents: ```go package main @@ -97,7 +131,7 @@ import ( abcitypes "github.com/tendermint/tendermint/abci/types" ) -type KVStoreApplication struct {} +type KVStoreApplication struct{} var _ abcitypes.Application = (*KVStoreApplication)(nil) @@ -105,285 +139,449 @@ func NewKVStoreApplication() *KVStoreApplication { return &KVStoreApplication{} } -func (KVStoreApplication) Info(req abcitypes.RequestInfo) abcitypes.ResponseInfo { +func (app *KVStoreApplication) Info(info abcitypes.RequestInfo) abcitypes.ResponseInfo { return abcitypes.ResponseInfo{} } -func (KVStoreApplication) DeliverTx(req abcitypes.RequestDeliverTx) abcitypes.ResponseDeliverTx { - return abcitypes.ResponseDeliverTx{Code: 0} +func (app *KVStoreApplication) Query(query abcitypes.RequestQuery) abcitypes.ResponseQuery { + return abcitypes.ResponseQuery{} } -func (KVStoreApplication) CheckTx(req abcitypes.RequestCheckTx) abcitypes.ResponseCheckTx { - return abcitypes.ResponseCheckTx{Code: 0} +func (app *KVStoreApplication) CheckTx(tx abcitypes.RequestCheckTx) abcitypes.ResponseCheckTx { + return abcitypes.ResponseCheckTx{} } -func (KVStoreApplication) Commit() abcitypes.ResponseCommit { - return abcitypes.ResponseCommit{} -} - -func (KVStoreApplication) Query(req abcitypes.RequestQuery) abcitypes.ResponseQuery { - return abcitypes.ResponseQuery{Code: 0} -} - -func (KVStoreApplication) InitChain(req abcitypes.RequestInitChain) abcitypes.ResponseInitChain { +func (app *KVStoreApplication) InitChain(chain abcitypes.RequestInitChain) abcitypes.ResponseInitChain { return abcitypes.ResponseInitChain{} } -func (KVStoreApplication) BeginBlock(req abcitypes.RequestBeginBlock) abcitypes.ResponseBeginBlock { +func (app *KVStoreApplication) PrepareProposal(proposal abcitypes.RequestPrepareProposal) abcitypes.ResponsePrepareProposal { + return abcitypes.ResponsePrepareProposal{} +} + +func (app *KVStoreApplication) ProcessProposal(proposal abcitypes.RequestProcessProposal) abcitypes.ResponseProcessProposal { + return abcitypes.ResponseProcessProposal{} +} + +func (app *KVStoreApplication) BeginBlock(block abcitypes.RequestBeginBlock) abcitypes.ResponseBeginBlock { return abcitypes.ResponseBeginBlock{} } -func (KVStoreApplication) EndBlock(req abcitypes.RequestEndBlock) abcitypes.ResponseEndBlock { +func (app *KVStoreApplication) DeliverTx(tx abcitypes.RequestDeliverTx) abcitypes.ResponseDeliverTx { + return abcitypes.ResponseDeliverTx{} +} + +func (app *KVStoreApplication) EndBlock(block abcitypes.RequestEndBlock) abcitypes.ResponseEndBlock { return abcitypes.ResponseEndBlock{} } -func (KVStoreApplication) ListSnapshots(abcitypes.RequestListSnapshots) abcitypes.ResponseListSnapshots { +func (app *KVStoreApplication) Commit() abcitypes.ResponseCommit { + return abcitypes.ResponseCommit{} +} + +func (app *KVStoreApplication) ListSnapshots(snapshots abcitypes.RequestListSnapshots) abcitypes.ResponseListSnapshots { return abcitypes.ResponseListSnapshots{} } -func (KVStoreApplication) OfferSnapshot(abcitypes.RequestOfferSnapshot) abcitypes.ResponseOfferSnapshot { +func (app *KVStoreApplication) OfferSnapshot(snapshot abcitypes.RequestOfferSnapshot) abcitypes.ResponseOfferSnapshot { return abcitypes.ResponseOfferSnapshot{} } -func (KVStoreApplication) LoadSnapshotChunk(abcitypes.RequestLoadSnapshotChunk) abcitypes.ResponseLoadSnapshotChunk { +func (app *KVStoreApplication) LoadSnapshotChunk(chunk abcitypes.RequestLoadSnapshotChunk) abcitypes.ResponseLoadSnapshotChunk { return abcitypes.ResponseLoadSnapshotChunk{} } -func (KVStoreApplication) ApplySnapshotChunk(abcitypes.RequestApplySnapshotChunk) abcitypes.ResponseApplySnapshotChunk { +func (app *KVStoreApplication) ApplySnapshotChunk(chunk abcitypes.RequestApplySnapshotChunk) abcitypes.ResponseApplySnapshotChunk { return abcitypes.ResponseApplySnapshotChunk{} } ``` -Now I will go through each method explaining when it's called and adding -required business logic. +The types used here are defined in the Tendermint library and were added as a dependency +to the project when you ran `go get`. If your IDE is not recognizing the types, go ahead and run the command again. -### 1.3.1 CheckTx +```bash +go get github.com/tendermint/tendermint@latest +``` -When a new transaction is added to the Tendermint Core, it will ask the -application to check it (validate the format, signatures, etc.). +Now go back to the `main.go` and modify the `main` function so it matches the following, +where an instance of the `KVStoreApplication` type is created. ```go -import "bytes" +func main() { + fmt.Println("Hello, Tendermint Core") -func (app *KVStoreApplication) isValid(tx []byte) (code uint32) { + _ = NewKVStoreApplication() +} +``` + +You can recompile and run the application now by running `go get` and `go build`, but it does +not do anything. +So let's revisit the code adding the logic needed to implement our minimal key/value store +and to start it along with the Tendermint Service. + + +### 1.3.1 Add a persistent data store +Our application will need to write its state out to persistent storage so that it +can stop and start without losing all of its data. + +For this tutorial, we will use [BadgerDB](https://github.com/dgraph-io/badger), a +a fast embedded key-value store. + +First, add Badger as a dependency of your go module using the `go get` command: + +`go get github.com/dgraph-io/badger/v3` + +Next, let's update the application and its constructor to receive a handle to the database, as follows: + +```go +type KVStoreApplication struct { + db *badger.DB + onGoingBlock *badger.Txn +} + +var _ abcitypes.Application = (*KVStoreApplication)(nil) + +func NewKVStoreApplication(db *badger.DB) *KVStoreApplication { + return &KVStoreApplication{db: db} +} +``` + +The `onGoingBlock` keeps track of the Badger transaction that will update the application's state when a block +is completed. Don't worry about it for now, we'll get to that later. + +Next, update the `import` stanza at the top to include the Badger library: + +```go +import( + "github.com/dgraph-io/badger/v3" + abcitypes "github.com/tendermint/tendermint/abci/types" +) +``` + +Finally, update the `main.go` file to invoke the updated constructor: + +```go + _ = NewKVStoreApplication(nil) +``` + +### 1.3.2 CheckTx +When Tendermint Core receives a new transaction from a client, or from another full node, Tendermint asks the application if +the transaction is acceptable, using the `CheckTx` method. +Invalid transactions will not be shared with other nodes and will not become part of any blocks and, therefore, will not be executed by the application. + +In our application, a transaction is a string with the form `key=value`, indicating a key and value to write to the store. + +The most basic validation check we can perform is to check if the transaction conforms to the `key=value` pattern. +For that, let's add the following helper method to app.go: + +```go +func (app *KVStoreApplication) isValid(tx []byte) uint32 { // check format parts := bytes.Split(tx, []byte("=")) if len(parts) != 2 { return 1 } - key, value := parts[0], parts[1] - - // check if the same key=value already exists - err := app.db.View(func(txn *badger.Txn) error { - item, err := txn.Get(key) - if err != nil && err != badger.ErrKeyNotFound { - return err - } - if err == nil { - return item.Value(func(val []byte) error { - if bytes.Equal(val, value) { - code = 2 - } - return nil - }) - } - return nil - }) - if err != nil { - panic(err) - } - - return code -} - -func (app *KVStoreApplication) CheckTx(req abcitypes.RequestCheckTx) abcitypes.ResponseCheckTx { - code := app.isValid(req.Tx) - return abcitypes.ResponseCheckTx{Code: code, GasWanted: 1} + return 0 } ``` -Don't worry if this does not compile yet. - -If the transaction does not have a form of `{bytes}={bytes}`, we return `1` -code. When the same key=value already exist (same key and value), we return `2` -code. For others, we return a zero code indicating that they are valid. - -Note that anything with non-zero code will be considered invalid (`-1`, `100`, -etc.) by Tendermint Core. - -Valid transactions will eventually be committed given they are not too big and -have enough gas. To learn more about gas, check out ["the -specification"](https://github.com/tendermint/tendermint/blob/main/spec/abci/abci++_app_requirements.md#gas). - -For the underlying key-value store we'll use -[badger](https://github.com/dgraph-io/badger), which is an embeddable, -persistent and fast key-value (KV) database. +Now you can rewrite the `CheckTx` method to use the helper function: ```go -import "github.com/dgraph-io/badger" - -type KVStoreApplication struct { - db *badger.DB - currentBatch *badger.Txn -} - -func NewKVStoreApplication(db *badger.DB) *KVStoreApplication { - return &KVStoreApplication{ - db: db, - } +func (app *KVStoreApplication) CheckTx(req abcitypes.RequestCheckTx) abcitypes.ResponseCheckTx { + code := app.isValid(req.Tx) + return abcitypes.ResponseCheckTx{Code: code} } ``` -### 1.3.2 BeginBlock -> DeliverTx -> EndBlock -> Commit +While this `CheckTx` is simple and only validates that the transaction is well-formed, +it is very common for `CheckTx` to make more complex use of the state of an application. +For example, you may refuse to overwrite an existing value, or you can associate +versions to the key/value pairs and allow the caller to specify a version to +perform a conditional update. -When Tendermint Core has decided on the block, it's transfered to the -application in 3 parts: `BeginBlock`, one `DeliverTx` per transaction and -`EndBlock` in the end. DeliverTx are being transfered asynchronously, but the -responses are expected to come in order. +Depending on the checks and on the conditions violated, the function may return +different values, but any response with a non-zero code will be considered invalid +by Tendermint. Our `CheckTx` logic returns 0 to Tendermint when a transaction passes +its validation checks. The specific value of the code is meaningless to Tendermint. +Non-zero codes are logged by Tendermint so applications can provide more specific +information on why the transaction was rejected. + +Note that `CheckTx` does not execute the transaction, it only verifies that that the transaction could be executed. We do not know yet if the rest of the network has agreed to accept this transaction into a block. + + +Finally, make sure to add the bytes package to the `import` stanza at the top of `app.go`: + +```go +import( + "bytes" + + "github.com/dgraph-io/badger/v3" + abcitypes "github.com/tendermint/tendermint/abci/types" +) +``` + + +### 1.3.3 BeginBlock -> DeliverTx -> EndBlock -> Commit + +When the Tendermint consensus engine has decided on the block, the block is transferred to the +application over three ABCI method calls: `BeginBlock`, `DeliverTx`, and `EndBlock`. + +- `BeginBlock` is called once to indicate to the application that it is about to +receive a block. +- `DeliverTx` is called repeatedly, once for each application transaction that was included in the block. +- `EndBlock` is called once to indicate to the application that no more transactions +will be delivered to the application in within this block. + +Note that, to implement these calls in our application we're going to make use of Badger's +transaction mechanism. We will always refer to these as Badger transactions, not to +confuse them with the transactions included in the blocks delivered by Tendermint, +the _application transactions_. + +First, let's create a new Badger transaction during `BeginBlock`. All application transactions in the +current block will be executed within this Badger transaction. +Then, return informing Tendermint that the application is ready to receive application transactions: ```go func (app *KVStoreApplication) BeginBlock(req abcitypes.RequestBeginBlock) abcitypes.ResponseBeginBlock { - app.currentBatch = app.db.NewTransaction(true) + app.onGoingBlock = app.db.NewTransaction(true) return abcitypes.ResponseBeginBlock{} } - ``` -Here we create a batch, which will store block's transactions. +Next, let's modify `DeliverTx` to add the `key` and `value` to the database transaction every time our application +receives a new application transaction through `RequestDeliverTx`. ```go func (app *KVStoreApplication) DeliverTx(req abcitypes.RequestDeliverTx) abcitypes.ResponseDeliverTx { - code := app.isValid(req.Tx) - if code != 0 { + if code := app.isValid(req.Tx); code != 0 { return abcitypes.ResponseDeliverTx{Code: code} } - parts := bytes.Split(req.Tx, []byte("=")) + parts := bytes.SplitN(req.Tx, []byte("="), 2) key, value := parts[0], parts[1] - err := app.currentBatch.Set(key, value) - if err != nil { - panic(err) + if err := app.onGoingBlock.Set(key, value); err != nil { + log.Panicf("Error reading database, unable to execute tx: %v", err) } return abcitypes.ResponseDeliverTx{Code: 0} } ``` -If the transaction is badly formatted or the same key=value already exist, we -again return the non-zero code. Otherwise, we add it to the current batch. +Note that we check the validity of the transaction _again_ during `DeliverTx`. +Transactions are not guaranteed to be valid when they are delivered to an +application, even if they were valid when they were proposed. +This can happen if the application state is used to determine transaction +validity. Application state may have changed between the initial execution of `CheckTx` +and the transaction delivery in `DeliverTx` in a way that rendered the transaction +no longer valid. -In the current design, a block can include incorrect transactions (those who -passed CheckTx, but failed DeliverTx or transactions included by the proposer -directly). This is done for performance reasons. +`EndBlock` is called to inform the application that the full block has been delivered +and give the application a chance to perform any other computation needed, before the +effects of the transactions become permanent. -Note we can't commit transactions inside the `DeliverTx` because in such case -`Query`, which may be called in parallel, will return inconsistent data (i.e. -it will report that some value already exist even when the actual block was not -yet committed). +Note that `EndBlock` **cannot** yet commit the Badger transaction we were building +in during `DeliverTx`. +Since other methods, such as `Query`, rely on a consistent view of the application's +state, the application should only update its state by committing the Badger transactions +when the full block has been delivered and the `Commit` method is invoked. -`Commit` instructs the application to persist the new state. +The `Commit` method tells the application to make permanent the effects of +the application transactions. +Let's update the method to terminate the pending Badger transaction and +persist the resulting state: ```go func (app *KVStoreApplication) Commit() abcitypes.ResponseCommit { - app.currentBatch.Commit() + if err := app.onGoingBlock.Commit(); err != nil { + log.Panicf("Error writing to database, unable to commit block: %v", err) + } return abcitypes.ResponseCommit{Data: []byte{}} } ``` -### 1.3.3 Query - -Now, when the client wants to know whenever a particular key/value exist, it -will call Tendermint Core RPC `/abci_query` endpoint, which in turn will call -the application's `Query` method. - -Applications are free to provide their own APIs. But by using Tendermint Core -as a proxy, clients (including [light client -package](https://godoc.org/github.com/tendermint/tendermint/light)) can leverage -the unified API across different applications. Plus they won't have to call the -otherwise separate Tendermint Core API for additional proofs. - -Note we don't include a proof here. +Finally, make sure to add the log library to the `import` stanza as well: ```go -func (app *KVStoreApplication) Query(reqQuery abcitypes.RequestQuery) (resQuery abcitypes.ResponseQuery) { - resQuery.Key = reqQuery.Data - err := app.db.View(func(txn *badger.Txn) error { - item, err := txn.Get(reqQuery.Data) - if err != nil && err != badger.ErrKeyNotFound { - return err +import ( + "bytes" + "log" + + "github.com/dgraph-io/badger/v3" + abcitypes "github.com/tendermint/tendermint/abci/types" +) +``` + +You may have noticed that the application we are writing will crash if it receives +an unexpected error from the Badger database during the `DeliverTx` or `Commit` methods. +This is not an accident. If the application received an error from the database, there +is no deterministic way for it to make progress so the only safe option is to terminate. + +### 1.3.4 Query +When a client tries to read some information from the `kvstore`, the request will be +handled in the `Query` method. To do this, let's rewrite the `Query` method in `app.go`: + +```go +func (app *KVStoreApplication) Query(req abcitypes.RequestQuery) abcitypes.ResponseQuery { + resp := abcitypes.ResponseQuery{Key: req.Data} + + dbErr := app.db.View(func(txn *badger.Txn) error { + item, err := txn.Get(req.Data) + if err != nil { + if err != badger.ErrKeyNotFound { + return err + } + resp.Log = "key does not exist" + return nil } - if err == badger.ErrKeyNotFound { - resQuery.Log = "does not exist" - } else { - return item.Value(func(val []byte) error { - resQuery.Log = "exists" - resQuery.Value = val - return nil - }) - } - return nil + + return item.Value(func(val []byte) error { + resp.Log = "exists" + resp.Value = val + return nil + }) }) - if err != nil { - panic(err) + if dbErr != nil { + log.Panicf("Error reading database, unable to verify tx: %v", dbErr) } - return + return resp } ``` -The complete specification can be found -[here](https://github.com/tendermint/tendermint/tree/main/spec/abci/). +Since it reads only committed data from the store, transactions that are part of a block +that is being processed are not reflected in the query result. + +### 1.3.5 PrepareProposal and ProcessProposal +`PrepareProposal` and `ProcessProposal` are methods introduced in Tendermint v0.37.0 +to give the application more control over the construction and processing of transaction blocks. + +When Tendermint Core sees that valid transactions (validated through `CheckTx`) are available to be +included in blocks, it groups some of these transactions and then gives the application a chance +to modify the group by invoking `PrepareProposal`. + +The application is free to modify the group before returning from the call. +For example, the application may reorder, add, or even remove transactions from the group to improve the +execution of the block once accepted. +In the following code, the application simply returns the unmodified group of transactions: + +```go +func (app *KVStoreApplication) PrepareProposal(proposal abcitypes.RequestPrepareProposal) abcitypes.ResponsePrepareProposal { + return abcitypes.ResponsePrepareProposal{Txs: proposal.Txs} +} +``` + +Once a proposed block is received by a node, the proposal is passed to the application to give +its blessing before voting to accept the proposal. + +This mechanism may be used for different reasons, for example to deal with blocks manipulated +by malicious nodes, in which case the block should not be considered valid. +The following code simply accepts all proposals: + +```go +func (app *KVStoreApplication) ProcessProposal(proposal abcitypes.RequestProcessProposal) abcitypes.ResponseProcessProposal { + return abcitypes.ResponseProcessProposal{Status: abcitypes.ResponseProcessProposal_ACCEPT} +} +``` ## 1.4 Starting an application and a Tendermint Core instance in the same process -Put the following code into the "main.go" file: +Now that we have the basic functionality of our application in place, let's put it all together inside of our main.go file. + +Change the contents of your `main.go` file to the following. + ```go package main import ( - "flag" - "fmt" - "os" - "os/signal" - "path/filepath" - "syscall" + "flag" + "fmt" + "github.com/tendermint/tendermint/p2p" + "github.com/tendermint/tendermint/privval" + "github.com/tendermint/tendermint/proxy" + "log" + "os" + "os/signal" + "path/filepath" + "syscall" - "github.com/dgraph-io/badger" - "github.com/spf13/viper" - - abci "github.com/tendermint/tendermint/abci/types" - cfg "github.com/tendermint/tendermint/config" - tmflags "github.com/tendermint/tendermint/libs/cli/flags" - "github.com/tendermint/tendermint/libs/log" - nm "github.com/tendermint/tendermint/node" - "github.com/tendermint/tendermint/p2p" - "github.com/tendermint/tendermint/privval" - "github.com/tendermint/tendermint/proxy" + "github.com/dgraph-io/badger/v3" + "github.com/spf13/viper" + cfg "github.com/tendermint/tendermint/config" + tmflags "github.com/tendermint/tendermint/libs/cli/flags" + tmlog "github.com/tendermint/tendermint/libs/log" + nm "github.com/tendermint/tendermint/node" ) -var configFile string +var homeDir string func init() { - flag.StringVar(&configFile, "config", "$HOME/.tendermint/config/config.toml", "Path to config.toml") + flag.StringVar(&homeDir, "tm-home", "", "Path to the tendermint config directory (if empty, uses $HOME/.tendermint)") } func main() { - db, err := badger.Open(badger.DefaultOptions("/tmp/badger")) - if err != nil { - fmt.Fprintf(os.Stderr, "failed to open badger db: %v", err) - os.Exit(1) + flag.Parse() + if homeDir == "" { + homeDir = os.ExpandEnv("$HOME/.tendermint") } - defer db.Close() + config := cfg.DefaultConfig() + + config.SetRoot(homeDir) + + viper.SetConfigFile(fmt.Sprintf("%s/%s", homeDir, "config/config.toml")) + if err := viper.ReadInConfig(); err != nil { + log.Fatalf("Reading config: %v", err) + } + if err := viper.Unmarshal(config); err != nil { + log.Fatalf("Decoding config: %v", err) + } + if err := config.ValidateBasic(); err != nil { + log.Fatalf("Invalid configuration data: %v", err) + } + + dbPath := filepath.Join(homeDir, "badger") + db, err := badger.Open(badger.DefaultOptions(dbPath)) + if err != nil { + log.Fatalf("Opening database: %v", err) + } + defer func() { + if err := db.Close(); err != nil { + log.Printf("Closing database: %v", err) + } + }() + app := NewKVStoreApplication(db) - flag.Parse() + pv := privval.LoadFilePV( + config.PrivValidatorKeyFile(), + config.PrivValidatorStateFile(), + ) - node, err := newTendermint(app, configFile) + nodeKey, err := p2p.LoadNodeKey(config.NodeKeyFile()) if err != nil { - fmt.Fprintf(os.Stderr, "%v", err) - os.Exit(2) + log.Fatalf("failed to load node's key: %v", err) + } + + logger := tmlog.NewTMLogger(tmlog.NewSyncWriter(os.Stdout)) + logger, err = tmflags.ParseLogLevel(config.LogLevel, logger, cfg.DefaultLogLevel) + if err != nil { + log.Fatalf("failed to parse log level: %v", err) + } + + node, err := nm.NewNode( + config, + pv, + nodeKey, + proxy.NewLocalClientCreator(app), + nm.DefaultGenesisDocProviderFunc(config), + nm.DefaultDBProvider, + nm.DefaultMetricsProvider(config.Instrumentation), + logger) + + if err != nil { + log.Fatalf("Creating node: %v", err) } node.Start() @@ -395,133 +593,46 @@ func main() { c := make(chan os.Signal, 1) signal.Notify(c, os.Interrupt, syscall.SIGTERM) <-c - os.Exit(0) -} - -func newTendermint(app abci.Application, configFile string) (*nm.Node, error) { - // read config - config := cfg.DefaultConfig() - config.RootDir = filepath.Dir(filepath.Dir(configFile)) - viper.SetConfigFile(configFile) - if err := viper.ReadInConfig(); err != nil { - return nil, fmt.Errorf("viper failed to read config file: %w", err) - } - if err := viper.Unmarshal(config); err != nil { - return nil, fmt.Errorf("viper failed to unmarshal config: %w", err) - } - if err := config.ValidateBasic(); err != nil { - return nil, fmt.Errorf("config is invalid: %w", err) - } - - // create logger - logger := log.NewTMLogger(log.NewSyncWriter(os.Stdout)) - var err error - logger, err = tmflags.ParseLogLevel(config.LogLevel, logger, cfg.DefaultLogLevel) - if err != nil { - return nil, fmt.Errorf("failed to parse log level: %w", err) - } - - // read private validator - pv := privval.LoadFilePV( - config.PrivValidatorKeyFile(), - config.PrivValidatorStateFile(), - ) - - // read node key - nodeKey, err := p2p.LoadNodeKey(config.NodeKeyFile()) - if err != nil { - return nil, fmt.Errorf("failed to load node's key: %w", err) - } - - // create node - node, err := nm.NewNode( - config, - pv, - nodeKey, - proxy.NewLocalClientCreator(app), - nm.DefaultGenesisDocProviderFunc(config), - nm.DefaultDBProvider, - nm.DefaultMetricsProvider(config.Instrumentation), - logger) - if err != nil { - return nil, fmt.Errorf("failed to create new Tendermint node: %w", err) - } - - return node, nil } ``` This is a huge blob of code, so let's break it down into pieces. -First, we initialize the Badger database and create an app instance: +First, we use [viper](https://github.com/spf13/viper) to load the Tendermint Core configuration files, which we will generate later: + ```go -db, err := badger.Open(badger.DefaultOptions("/tmp/badger")) -if err != nil { - fmt.Fprintf(os.Stderr, "failed to open badger db: %v", err) - os.Exit(1) -} -defer db.Close() -app := NewKVStoreApplication(db) + config := cfg.DefaultValidatorConfig() + + config.SetRoot(homeDir) + + viper.SetConfigFile(fmt.Sprintf("%s/%s", homeDir, "config/config.toml")) + if err := viper.ReadInConfig(); err != nil { + log.Fatalf("Reading config: %v", err) + } + if err := viper.Unmarshal(config); err != nil { + log.Fatalf("Decoding config: %v", err) + } + if err := config.ValidateBasic(); err != nil { + log.Fatalf("Invalid configuration data: %v", err) + } ``` -For **Windows** users, restarting this app will make badger throw an error as it requires value log to be truncated. For more information on this, visit [here](https://github.com/dgraph-io/badger/issues/744). -This can be avoided by setting the truncate option to true, like this: +Next, we initialize the Badger database and create an app instance. ```go -db, err := badger.Open(badger.DefaultOptions("/tmp/badger").WithTruncate(true)) -``` + dbPath := filepath.Join(homeDir, "badger") + db, err := badger.Open(badger.DefaultOptions(dbPath)) + if err != nil { + log.Fatalf("Opening database: %v", err) + } + defer func() { + if err := db.Close(); err != nil { + log.Fatalf("Closing database: %v", err) + } + }() -Then we use it to create a Tendermint Core `Node` instance: - -```go -flag.Parse() - -node, err := newTendermint(app, configFile) -if err != nil { - fmt.Fprintf(os.Stderr, "%v", err) - os.Exit(2) -} - -... - -// create node -node, err := nm.NewNode( - config, - pv, - nodeKey, - proxy.NewLocalClientCreator(app), - nm.DefaultGenesisDocProviderFunc(config), - nm.DefaultDBProvider, - nm.DefaultMetricsProvider(config.Instrumentation), - logger) -if err != nil { - return nil, fmt.Errorf("failed to create new Tendermint node: %w", err) -} -``` - -`NewNode` requires a few things including a configuration file, a private -validator, a node key and a few others in order to construct the full node. - -Note we use `proxy.NewLocalClientCreator` here to create a local client instead -of one communicating through a socket or gRPC. - -[viper](https://github.com/spf13/viper) is being used for reading the config, -which we will generate later using the `tendermint init` command. - -```go -config := cfg.DefaultConfig() -config.RootDir = filepath.Dir(filepath.Dir(configFile)) -viper.SetConfigFile(configFile) -if err := viper.ReadInConfig(); err != nil { - return nil, fmt.Errorf("viper failed to read config file: %w", err) -} -if err := viper.Unmarshal(config); err != nil { - return nil, fmt.Errorf("viper failed to unmarshal config: %w", err) -} -if err := config.ValidateBasic(); err != nil { - return nil, fmt.Errorf("config is invalid: %w", err) -} + app := NewKVStoreApplication(db) ``` We use `FilePV`, which is a private validator (i.e. thing which signs consensus @@ -529,147 +640,152 @@ messages). Normally, you would use `SignerRemote` to connect to an external [HSM](https://kb.certus.one/hsm.html). ```go -pv := privval.LoadFilePV( - config.PrivValidatorKeyFile(), - config.PrivValidatorStateFile(), -) - + pv := privval.LoadFilePV( + config.PrivValidatorKeyFile(), + config.PrivValidatorStateFile(), + ) ``` `nodeKey` is needed to identify the node in a p2p network. ```go -nodeKey, err := p2p.LoadNodeKey(config.NodeKeyFile()) -if err != nil { - return nil, fmt.Errorf("failed to load node's key: %w", err) -} + nodeKey, err := p2p.LoadNodeKey(config.NodeKeyFile()) + if err != nil { + return nil, fmt.Errorf("failed to load node's key: %w", err) + } ``` - -As for the logger, we use the build-in library, which provides a nice -abstraction over [go-kit's -logger](https://github.com/go-kit/kit/tree/master/log). +Now we have everything set up to run the Tendermint node. We construct +a node by passing it the configuration, the logger, a handle to our application and +the genesis information: ```go -logger := log.NewTMLogger(log.NewSyncWriter(os.Stdout)) -var err error -logger, err = tmflags.ParseLogLevel(config.LogLevel, logger, cfg.DefaultLogLevel()) -if err != nil { - return nil, fmt.Errorf("failed to parse log level: %w", err) -} + node, err := nm.NewNode( + config, + pv, + nodeKey, + proxy.NewLocalClientCreator(app), + nm.DefaultGenesisDocProviderFunc(config), + nm.DefaultDBProvider, + nm.DefaultMetricsProvider(config.Instrumentation), + logger) + + if err != nil { + log.Fatalf("Creating node: %v", err) + } ``` -Finally, we start the node and add some signal handling to gracefully stop it -upon receiving SIGTERM or Ctrl-C. +Finally, we start the node, i.e., the Tendermint Core service inside our application: ```go -node.Start() -defer func() { - node.Stop() - node.Wait() -}() - -c := make(chan os.Signal, 1) -signal.Notify(c, os.Interrupt, syscall.SIGTERM) -<-c -os.Exit(0) + node.Start() + defer func() { + node.Stop() + node.Wait() + }() ``` - -## 1.5 Getting Up and Running - -We are going to use [Go modules](https://github.com/golang/go/wiki/Modules) for -dependency management. - -```bash -go mod init github.com/me/example -go get github.com/tendermint/tendermint/@v0.34.0 -``` - -After running the above commands you will see two generated files, go.mod and go.sum. The go.mod file should look similar to: +The additional logic at the end of the file allows the program to catch SIGTERM. This means that the node can shut down gracefully when an operator tries to kill the program: ```go -module github.com/me/example - -go 1.15 - -require ( - github.com/dgraph-io/badger v1.6.2 - github.com/tendermint/tendermint v0.34.0 -) + c := make(chan os.Signal, 1) + signal.Notify(c, os.Interrupt, syscall.SIGTERM) + <-c ``` -Finally, we will build our binary: +## 1.5 Initializing and Running -```sh -go build -``` +Our application is almost ready to run, but first we'll need to populate the Tendermint Core configuration files. +The following command will create a `tendermint-home` directory in your project and add a basic set of configuration files in `tendermint-home/config/`. +For more information on what these files contain see [the configuration documentation](https://github.com/tendermint/tendermint/blob/v0.37.0/docs/nodes/configuration.md). -To create a default configuration, nodeKey and private validator files, let's -execute `tendermint init`. But before we do that, we will need to install -Tendermint Core. Please refer to [the official -guide](https://docs.tendermint.com/main/introduction/install.html). If you're -installing from source, don't forget to checkout the latest release (`git checkout vX.Y.Z`). +From the root of your project, run: ```bash -$ rm -rf /tmp/example -$ TMHOME="/tmp/example" tendermint init - -I[2019-07-16|18:40:36.480] Generated private validator module=main keyFile=/tmp/example/config/priv_validator_key.json stateFile=/tmp/example2/data/priv_validator_state.json -I[2019-07-16|18:40:36.481] Generated node key module=main path=/tmp/example/config/node_key.json -I[2019-07-16|18:40:36.482] Generated genesis file module=main path=/tmp/example/config/genesis.json +go run github.com/tendermint/tendermint/cmd/tendermint@v0.37.0 init --home /tmp/tendermint-home ``` -We are ready to start our application: +You should see an output similar to the following: ```bash -$ ./example -config "/tmp/example/config/config.toml" - -badger 2019/07/16 18:42:25 INFO: All 0 tables opened in 0s -badger 2019/07/16 18:42:25 INFO: Replaying file id: 0 at offset: 0 -badger 2019/07/16 18:42:25 INFO: Replay took: 695.227s -E[2019-07-16|18:42:25.818] Couldn't connect to any seeds module=p2p -I[2019-07-16|18:42:26.853] Executed block module=state height=1 validTxs=0 invalidTxs=0 -I[2019-07-16|18:42:26.865] Committed state module=state height=1 txs=0 appHash= +I[2022-11-09|09:06:34.444] Generated private validator module=main keyFile=/tmp/tendermint-home/config/priv_validator_key.json stateFile=/tmp/tendermint-home/data/priv_validator_state.json +I[2022-11-09|09:06:34.444] Generated node key module=main path=/tmp/tendermint-home/config/node_key.json +I[2022-11-09|09:06:34.444] Generated genesis file module=main path=/tmp/tendermint-home/config/genesis.json ``` -Now open another tab in your terminal and try sending a transaction: +Now rebuild the app: ```bash -$ curl -s 'localhost:26657/broadcast_tx_commit?tx="tendermint=rocks"' -{ - "jsonrpc": "2.0", - "id": "", - "result": { - "check_tx": { - "gasWanted": "1" - }, - "deliver_tx": {}, - "hash": "1B3C5A1093DB952C331B1749A21DCCBB0F6C7F4E0055CD04D16346472FC60EC6", - "height": "128" - } -} +go build -mod=mod # use -mod=mod to automatically refresh the dependencies ``` -Response should contain the height where this transaction was committed. +Everything is now in place to run your application. Run: -Now let's check if the given key now exists and its value: +```bash +./kvstore -tm-home /tmp/tendermint-home +``` + +The application will start and you should see a continuous output starting with: + +```bash +badger 2022/11/09 09:08:50 INFO: All 0 tables opened in 0s +badger 2022/11/09 09:08:50 INFO: Discard stats nextEmptySlot: 0 +badger 2022/11/09 09:08:50 INFO: Set nextTxnTs to 0 +I[2022-11-09|09:08:50.085] service start module=proxy msg="Starting multiAppConn service" impl=multiAppConn +I[2022-11-09|09:08:50.085] service start module=abci-client connection=query msg="Starting localClient service" impl=localClient +I[2022-11-09|09:08:50.085] service start module=abci-client connection=snapshot msg="Starting localClient service" impl=localClient +... +``` + +More importantly, the application using Tendermint Core is producing blocks 🎉🎉 and you can see this reflected in the log output in lines like this: + +```bash +I[2022-11-09|09:08:52.147] received proposal module=consensus proposal="Proposal{2/0 (F518444C0E348270436A73FD0F0B9DFEA758286BEB29482F1E3BEA75330E825C:1:C73D3D1273F2, -1) AD19AE292A45 @ 2022-11-09T12:08:52.143393Z}" +I[2022-11-09|09:08:52.152] received complete proposal block module=consensus height=2 hash=F518444C0E348270436A73FD0F0B9DFEA758286BEB29482F1E3BEA75330E825C +I[2022-11-09|09:08:52.160] finalizing commit of block module=consensus height=2 hash=F518444C0E348270436A73FD0F0B9DFEA758286BEB29482F1E3BEA75330E825C root= num_txs=0 +I[2022-11-09|09:08:52.167] executed block module=state height=2 num_valid_txs=0 num_invalid_txs=0 +I[2022-11-09|09:08:52.171] committed state module=state height=2 num_txs=0 app_hash= +``` + +The blocks, as you can see from the `num_valid_txs=0` part, are empty, but let's remedy that next. + + +## 1.6 Using the application + +Let's try submitting a transaction to our new application. +Open another terminal window and run the following curl command: + + +```bash +curl -s 'localhost:26657/broadcast_tx_commit?tx="tendermint=rocks"' +``` +If everything went well, you should see a response indicating which height the +transaction was included in the blockchain. + +Finally, let's make sure that transaction really was persisted by the application. +Run the following command: + +```bash +curl -s 'localhost:26657/abci_query?data="tendermint"' +``` + +Let's examine the response object that this request returns. +The request returns a `json` object with a `key` and `value` field set. ```json -$ curl -s 'localhost:26657/abci_query?data="tendermint"' -{ - "jsonrpc": "2.0", - "id": "", - "result": { - "response": { - "log": "exists", - "key": "dGVuZGVybWludA==", - "value": "cm9ja3M=" - } - } -} +... + "key": "dGVuZGVybWludA==", + "value": "cm9ja3M=", +... ``` -"dGVuZGVybWludA==" and "cm9ja3M=" are the base64-encoding of the ASCII of -"tendermint" and "rocks" accordingly. +Those values don't look like the `key` and `value` we sent to Tendermint. +What's going on here? + +The response contains a `base64` encoded representation of the data we submitted. +To get the original value out of this data, we can use the `base64` command line utility: + +```bash +echo cm9ja3M=" | base64 -d +``` ## Outro diff --git a/docs/tutorials/go.md b/docs/tutorials/go.md index 1af809a1b..4a1e3bfba 100644 --- a/docs/tutorials/go.md +++ b/docs/tutorials/go.md @@ -10,47 +10,42 @@ This guide is designed for beginners who want to get started with a Tendermint Core application from scratch. It does not assume that you have any prior experience with Tendermint Core. -Tendermint Core is Byzantine Fault Tolerant (BFT) middleware that takes a state -transition machine - written in any programming language - and securely -replicates it on many machines. +Tendermint Core is a service that provides a Byzantine Fault Tolerant consensus engine +for state-machine replication. The replicated state-machine, or "application", can be written +in any language that can send and receive protocol buffer messages in a client-server model. +Applications written in Go can also use Tendermint as a library and run the service in the same +process as the application. -Although Tendermint Core is written in the Golang programming language, prior -knowledge of it is not required for this guide. You can learn it as we go due -to it's simplicity. However, you may want to go through [Learn X in Y minutes -Where X=Go](https://learnxinyminutes.com/docs/go/) first to familiarize +By following along this tutorial you will create a Tendermint Core application called kvstore, +a (very) simple distributed BFT key-value store. +The application will be written in Go and +some understanding of the Go programming language is expected. +If you have never written Go, you may want to go through [Learn X in Y minutes +Where X=Go](https://learnxinyminutes.com/docs/go/) first, to familiarize yourself with the syntax. -By following along with this guide, you'll create a Tendermint Core project -called kvstore, a (very) simple distributed BFT key-value store. +Note: Please use the latest released version of this guide and of Tendermint. +We strongly advise against using unreleased commits for your development. -## Built-in app vs external app +### Built-in app vs external app +On the one hand, to get maximum performance you can run your application in +the same process as the Tendermint Core, as long as your application is written in Go. +[Cosmos SDK](https://github.com/cosmos/cosmos-sdk) is written +this way. +If that is the way you wish to proceed, use the [Creating a built-in application in Go](./go-built-in.md) guide instead of this one. -To get maximum performance it is better to run your application alongside -Tendermint Core. [Cosmos SDK](https://github.com/cosmos/cosmos-sdk) is written -this way. Please refer to [Writing a built-in Tendermint Core application in -Go](./go-built-in.md) guide for details. - -Having a separate application might give you better security guarantees as two -processes would be communicating via established binary protocol. Tendermint -Core will not have access to application's state. +On the other hand, having a separate application might give you better security +guarantees as two processes would be communicating via established binary protocol. +Tendermint Core will not have access to application's state. +This is the approach followed in this tutorial. ## 1.1 Installing Go -Please refer to [the official guide for installing -Go](https://golang.org/doc/install). - -Verify that you have the latest version of Go installed: +Verify that you have the latest version of Go installed (refer to the [official guide for installing Go](https://golang.org/doc/install)): ```bash $ go version -go version go1.15.x darwin/amd64 -``` - -Make sure you have `$GOPATH` environment variable set: - -```bash -echo $GOPATH -/Users/melekes/go +go version go1.19.2 darwin/amd64 ``` ## 1.2 Creating a new Go project @@ -59,39 +54,73 @@ We'll start by creating a new Go project. ```bash mkdir kvstore -cd kvstore ``` -Inside the example directory create a `main.go` file with the following content: +Inside the example directory, create a `main.go` file with the following content: ```go package main import ( - "fmt" + "fmt" ) func main() { - fmt.Println("Hello, Tendermint Core") + fmt.Println("Hello, Tendermint Core") } ``` When run, this should print "Hello, Tendermint Core" to the standard output. ```bash -go run main.go +cd kvstore +$ go run main.go Hello, Tendermint Core ``` +We are going to use [Go modules](https://github.com/golang/go/wiki/Modules) for +dependency management, so let's start by including a dependency on the latest version of +Tendermint. + +```bash +go mod init kvstore +go get github.com/tendermint/tendermint@latest +``` + +After running the above commands you will see two generated files, `go.mod` and `go.sum`. +The go.mod file should look similar to: + +```go +module github.com/me/example + +go 1.19 + +require ( + github.com/tendermint/tendermint v0.37.0 +) +``` + +As you write the kvstore application, you can rebuild the binary by +pulling any new dependencies and recompiling it. + +```sh +go get +go build +``` + + ## 1.3 Writing a Tendermint Core application Tendermint Core communicates with the application through the Application -BlockChain Interface (ABCI). All message types are defined in the [protobuf +BlockChain Interface (ABCI). The messages exchanged through the interface are +defined in the ABCI [protobuf file](https://github.com/tendermint/tendermint/blob/main/proto/tendermint/abci/types.proto). -This allows Tendermint Core to run applications written in any programming -language. -Create a file called `app.go` with the following content: +We begin by creating the basic scaffolding for an ABCI application by +creating a new type, `KVStoreApplication`, which implements the +methods defined by the `abcitypes.Application` interface. + +Create a file called `app.go` with the following contents: ```go package main @@ -100,7 +129,7 @@ import ( abcitypes "github.com/tendermint/tendermint/abci/types" ) -type KVStoreApplication struct {} +type KVStoreApplication struct{} var _ abcitypes.Application = (*KVStoreApplication)(nil) @@ -108,238 +137,358 @@ func NewKVStoreApplication() *KVStoreApplication { return &KVStoreApplication{} } -func (KVStoreApplication) Info(req abcitypes.RequestInfo) abcitypes.ResponseInfo { +func (app *KVStoreApplication) Info(info abcitypes.RequestInfo) abcitypes.ResponseInfo { return abcitypes.ResponseInfo{} } -func (KVStoreApplication) DeliverTx(req abcitypes.RequestDeliverTx) abcitypes.ResponseDeliverTx { - return abcitypes.ResponseDeliverTx{Code: 0} + +func (app *KVStoreApplication) Query(query abcitypes.RequestQuery) abcitypes.ResponseQuery { + return abcitypes.ResponseQuery{} } -func (KVStoreApplication) CheckTx(req abcitypes.RequestCheckTx) abcitypes.ResponseCheckTx { - return abcitypes.ResponseCheckTx{Code: 0} +func (app *KVStoreApplication) CheckTx(tx abcitypes.RequestCheckTx) abcitypes.ResponseCheckTx { + return abcitypes.ResponseCheckTx{} } -func (KVStoreApplication) Commit() abcitypes.ResponseCommit { - return abcitypes.ResponseCommit{} -} - -func (KVStoreApplication) Query(req abcitypes.RequestQuery) abcitypes.ResponseQuery { - return abcitypes.ResponseQuery{Code: 0} -} - -func (KVStoreApplication) InitChain(req abcitypes.RequestInitChain) abcitypes.ResponseInitChain { +func (app *KVStoreApplication) InitChain(chain abcitypes.RequestInitChain) abcitypes.ResponseInitChain { return abcitypes.ResponseInitChain{} } -func (KVStoreApplication) BeginBlock(req abcitypes.RequestBeginBlock) abcitypes.ResponseBeginBlock { +func (app *KVStoreApplication) PrepareProposal(proposal abcitypes.RequestPrepareProposal) abcitypes.ResponsePrepareProposal { + return abcitypes.ResponsePrepareProposal{} +} + +func (app *KVStoreApplication) ProcessProposal(proposal abcitypes.RequestProcessProposal) abcitypes.ResponseProcessProposal { + return abcitypes.ResponseProcessProposal{} +} + +func (app *KVStoreApplication) BeginBlock(block abcitypes.RequestBeginBlock) abcitypes.ResponseBeginBlock { return abcitypes.ResponseBeginBlock{} } -func (KVStoreApplication) EndBlock(req abcitypes.RequestEndBlock) abcitypes.ResponseEndBlock { +func (app *KVStoreApplication) DeliverTx(tx abcitypes.RequestDeliverTx) abcitypes.ResponseDeliverTx { + return abcitypes.ResponseDeliverTx{} +} + +func (app *KVStoreApplication) EndBlock(block abcitypes.RequestEndBlock) abcitypes.ResponseEndBlock { return abcitypes.ResponseEndBlock{} } -func (KVStoreApplication) ListSnapshots(abcitypes.RequestListSnapshots) abcitypes.ResponseListSnapshots { +func (app *KVStoreApplication) Commit() abcitypes.ResponseCommit { + return abcitypes.ResponseCommit{} +} + +func (app *KVStoreApplication) ListSnapshots(snapshots abcitypes.RequestListSnapshots) abcitypes.ResponseListSnapshots { return abcitypes.ResponseListSnapshots{} } -func (KVStoreApplication) OfferSnapshot(abcitypes.RequestOfferSnapshot) abcitypes.ResponseOfferSnapshot { +func (app *KVStoreApplication) OfferSnapshot(snapshot abcitypes.RequestOfferSnapshot) abcitypes.ResponseOfferSnapshot { return abcitypes.ResponseOfferSnapshot{} } -func (KVStoreApplication) LoadSnapshotChunk(abcitypes.RequestLoadSnapshotChunk) abcitypes.ResponseLoadSnapshotChunk { +func (app *KVStoreApplication) LoadSnapshotChunk(chunk abcitypes.RequestLoadSnapshotChunk) abcitypes.ResponseLoadSnapshotChunk { return abcitypes.ResponseLoadSnapshotChunk{} } -func (KVStoreApplication) ApplySnapshotChunk(abcitypes.RequestApplySnapshotChunk) abcitypes.ResponseApplySnapshotChunk { +func (app *KVStoreApplication) ApplySnapshotChunk(chunk abcitypes.RequestApplySnapshotChunk) abcitypes.ResponseApplySnapshotChunk { return abcitypes.ResponseApplySnapshotChunk{} } ``` -Now I will go through each method explaining when it's called and adding -required business logic. +The types used here are defined in the Tendermint library and were added as a dependency +to the project when you ran `go get`. If your IDE is not recognizing the types, go ahead and run the command again. -### 1.3.1 CheckTx +```bash +go get github.com/tendermint/tendermint@latest +``` + +Now go back to the `main.go` and modify the `main` function so it matches the following, +where an instance of the `KVStoreApplication` type is created. -When a new transaction is added to the Tendermint Core, it will ask the -application to check it (validate the format, signatures, etc.). ```go -import "bytes" +func main() { + fmt.Println("Hello, Tendermint Core") -func (app *KVStoreApplication) isValid(tx []byte) (code uint32) { + _ = NewKVStoreApplication() +} +``` + +You can recompile and run the application now by running `go get` and `go build`, but it does +not do anything. +So let's revisit the code adding the logic needed to implement our minimal key/value store +and to start it along with the Tendermint Service. + + +### 1.3.1 Add a persistent data store +Our application will need to write its state out to persistent storage so that it +can stop and start without losing all of its data. + +For this tutorial, we will use [BadgerDB](https://github.com/dgraph-io/badger), a +a fast embedded key-value store. + +First, add Badger as a dependency of your go module using the `go get` command: + +`go get github.com/dgraph-io/badger/v3` + +Next, let's update the application and its constructor to receive a handle to the database, as follows: + +```go +type KVStoreApplication struct { + db *badger.DB + onGoingBlock *badger.Txn +} + +var _ abcitypes.Application = (*KVStoreApplication)(nil) + +func NewKVStoreApplication(db *badger.DB) *KVStoreApplication { + return &KVStoreApplication{db: db} +} +``` + +The `onGoingBlock` keeps track of the Badger transaction that will update the application's state when a block +is completed. Don't worry about it for now, we'll get to that later. + +Next, update the `import` stanza at the top to include the Badger library: + +```go +import( + "github.com/dgraph-io/badger/v3" + abcitypes "github.com/tendermint/tendermint/abci/types" +) +``` + +Finally, update the `main.go` file to invoke the updated constructor: + +```go + _ = NewKVStoreApplication(nil) +``` + + +### 1.3.2 CheckTx +When Tendermint Core receives a new transaction from a client, Tendermint asks the application if +the transaction is acceptable, using the `CheckTx` method. + +In our application, a transaction is a string with the form `key=value`, indicating a key and value to write to the store. + +The most basic validation check we can perform is to check if the transaction conforms to the `key=value` pattern. +For that, let's add the following helper method to app.go: + +```go +func (app *KVStoreApplication) isValid(tx []byte) uint32 { // check format parts := bytes.Split(tx, []byte("=")) if len(parts) != 2 { return 1 } - key, value := parts[0], parts[1] - - // check if the same key=value already exists - err := app.db.View(func(txn *badger.Txn) error { - item, err := txn.Get(key) - if err != nil && err != badger.ErrKeyNotFound { - return err - } - if err == nil { - return item.Value(func(val []byte) error { - if bytes.Equal(val, value) { - code = 2 - } - return nil - }) - } - return nil - }) - if err != nil { - panic(err) - } - - return code -} - -func (app *KVStoreApplication) CheckTx(req abcitypes.RequestCheckTx) abcitypes.ResponseCheckTx { - code := app.isValid(req.Tx) - return abcitypes.ResponseCheckTx{Code: code, GasWanted: 1} + return 0 } ``` -Don't worry if this does not compile yet. - -If the transaction does not have a form of `{bytes}={bytes}`, we return `1` -code. When the same key=value already exist (same key and value), we return `2` -code. For others, we return a zero code indicating that they are valid. - -Note that anything with non-zero code will be considered invalid (`-1`, `100`, -etc.) by Tendermint Core. - -Valid transactions will eventually be committed given they are not too big and -have enough gas. To learn more about gas, check out ["the -specification"](https://github.com/tendermint/tendermint/blob/main/spec/abci/abci++_app_requirements.md#gas). - -For the underlying key-value store we'll use -[badger](https://github.com/dgraph-io/badger), which is an embeddable, -persistent and fast key-value (KV) database. +Now you can rewrite the `CheckTx` method to use the helper function: ```go -import "github.com/dgraph-io/badger" - -type KVStoreApplication struct { - db *badger.DB - currentBatch *badger.Txn -} - -func NewKVStoreApplication(db *badger.DB) *KVStoreApplication { - return &KVStoreApplication{ - db: db, - } +func (app *KVStoreApplication) CheckTx(req abcitypes.RequestCheckTx) abcitypes.ResponseCheckTx { + code := app.isValid(req.Tx) + return abcitypes.ResponseCheckTx{Code: code} } ``` -### 1.3.2 BeginBlock -> DeliverTx -> EndBlock -> Commit +While this `CheckTx` is simple and only validates that the transaction is well-formed, +it is very common for `CheckTx` to make more complex use of the state of an application. +For example, you may refuse to overwrite an existing value, or you can associate +versions to the key/value pairs and allow the caller to specify a version to +perform a conditional update. -When Tendermint Core has decided on the block, it's transferred to the -application in 3 parts: `BeginBlock`, one `DeliverTx` per transaction and -`EndBlock` in the end. DeliverTx are being transferred asynchronously, but the -responses are expected to come in order. +Depending on the checks and on the conditions violated, the function may return +different values, but any response with a non-zero code will be considered invalid +by Tendermint. Our `CheckTx` logic returns 0 to Tendermint when a transaction passes +its validation checks. The specific value of the code is meaningless to Tendermint. +Non-zero codes are logged by Tendermint so applications can provide more specific +information on why the transaction was rejected. + +Note that `CheckTx` does not execute the transaction, it only verifies that that the transaction could be executed. We do not know yet if the rest of the network has agreed to accept this transaction into a block. + + +Finally, make sure to add the bytes package to the `import` stanza at the top of `app.go`: + +```go +import( + "bytes" + + "github.com/dgraph-io/badger/v3" + abcitypes "github.com/tendermint/tendermint/abci/types" +) +``` + + +### 1.3.3 BeginBlock -> DeliverTx -> EndBlock -> Commit + +When the Tendermint consensus engine has decided on the block, the block is transferred to the +application over three ABCI method calls: `BeginBlock`, `DeliverTx`, and `EndBlock`. + +- `BeginBlock` is called once to indicate to the application that it is about to +receive a block. +- `DeliverTx` is called repeatedly, once for each application transaction that was included in the block. +- `EndBlock` is called once to indicate to the application that no more transactions +will be delivered to the application in within this block. + +Note that, to implement these calls in our application we're going to make use of Badger's +transaction mechanism. We will always refer to these as Badger transactions, not to +confuse them with the transactions included in the blocks delivered by Tendermint, +the _application transactions_. + +First, let's create a new Badger transaction during `BeginBlock`. All application transactions in the +current block will be executed within this Badger transaction. +Then, return informing Tendermint that the application is ready to receive application transactions: ```go func (app *KVStoreApplication) BeginBlock(req abcitypes.RequestBeginBlock) abcitypes.ResponseBeginBlock { - app.currentBatch = app.db.NewTransaction(true) + app.onGoingBlock = app.db.NewTransaction(true) return abcitypes.ResponseBeginBlock{} } - ``` -Here we create a batch, which will store block's transactions. +Next, let's modify `DeliverTx` to add the `key` and `value` to the database transaction every time our application +receives a new application transaction through `RequestDeliverTx`. ```go func (app *KVStoreApplication) DeliverTx(req abcitypes.RequestDeliverTx) abcitypes.ResponseDeliverTx { - code := app.isValid(req.Tx) - if code != 0 { + if code := app.isValid(req.Tx); code != 0 { return abcitypes.ResponseDeliverTx{Code: code} } - parts := bytes.Split(req.Tx, []byte("=")) + parts := bytes.SplitN(req.Tx, []byte("="), 2) key, value := parts[0], parts[1] - err := app.currentBatch.Set(key, value) - if err != nil { - panic(err) + if err := app.onGoingBlock.Set(key, value); err != nil { + log.Panicf("Error writing to database, unable to execute tx: %v", err) } return abcitypes.ResponseDeliverTx{Code: 0} } ``` -If the transaction is badly formatted or the same key=value already exist, we -again return the non-zero code. Otherwise, we add it to the current batch. +Note that we check the validity of the transaction _again_ during `DeliverTx`. +Transactions are not guaranteed to be valid when they are delivered to an +application, even if they were valid when they were proposed. +This can happen if the application state is used to determine transaction +validity. Application state may have changed between the initial execution of `CheckTx` +and the transaction delivery in `DeliverTx` in a way that rendered the transaction +no longer valid. -In the current design, a block can include incorrect transactions (those who -passed CheckTx, but failed DeliverTx or transactions included by the proposer -directly). This is done for performance reasons. +`EndBlock` is called to inform the application that the full block has been delivered +and give the application a chance to perform any other computation needed, before the +effects of the transactions become permanent. -Note we can't commit transactions inside the `DeliverTx` because in such case -`Query`, which may be called in parallel, will return inconsistent data (i.e. -it will report that some value already exist even when the actual block was not -yet committed). +Note that `EndBlock` **cannot** yet commit the Badger transaction we were building +in during `DeliverTx`. +Since other methods, such as `Query`, rely on a consistent view of the application's +state, the application should only update its state by committing the Badger transactions +when the full block has been delivered and the `Commit` method is invoked. -`Commit` instructs the application to persist the new state. +The `Commit` method tells the application to make permanent the effects of +the application transactions. +Let's update the method to terminate the pending Badger transaction and +persist the resulting state: ```go func (app *KVStoreApplication) Commit() abcitypes.ResponseCommit { - app.currentBatch.Commit() + if err := app.onGoingBlock.Commit(); err != nil { + log.Panicf("Error writing to database, unable to commit block: %v", err) + } return abcitypes.ResponseCommit{Data: []byte{}} } ``` -### 1.3.3 Query - -Now, when the client wants to know whenever a particular key/value exist, it -will call Tendermint Core RPC `/abci_query` endpoint, which in turn will call -the application's `Query` method. - -Applications are free to provide their own APIs. But by using Tendermint Core -as a proxy, clients (including [light client -package](https://godoc.org/github.com/tendermint/tendermint/light)) can leverage -the unified API across different applications. Plus they won't have to call the -otherwise separate Tendermint Core API for additional proofs. - -Note we don't include a proof here. +Finally, make sure to add the log library to the `import` stanza as well: ```go -func (app *KVStoreApplication) Query(reqQuery abcitypes.RequestQuery) (resQuery abcitypes.ResponseQuery) { - resQuery.Key = reqQuery.Data - err := app.db.View(func(txn *badger.Txn) error { - item, err := txn.Get(reqQuery.Data) - if err != nil && err != badger.ErrKeyNotFound { - return err +import ( + "bytes" + "log" + + "github.com/dgraph-io/badger/v3" + abcitypes "github.com/tendermint/tendermint/abci/types" +) +``` + +You may have noticed that the application we are writing will crash if it receives +an unexpected error from the Badger database during the `DeliverTx` or `Commit` methods. +This is not an accident. If the application received an error from the database, there +is no deterministic way for it to make progress so the only safe option is to terminate. + +### 1.3.4 Query +When a client tries to read some information from the `kvstore`, the request will be +handled in the `Query` method. To do this, let's rewrite the `Query` method in `app.go`: + +```go +func (app *KVStoreApplication) Query(req abcitypes.RequestQuery) abcitypes.ResponseQuery { + resp := abcitypes.ResponseQuery{Key: req.Data} + + dbErr := app.db.View(func(txn *badger.Txn) error { + item, err := txn.Get(req.Data) + if err != nil { + if err != badger.ErrKeyNotFound { + return err + } + resp.Log = "key does not exist" + return nil } - if err == badger.ErrKeyNotFound { - resQuery.Log = "does not exist" - } else { - return item.Value(func(val []byte) error { - resQuery.Log = "exists" - resQuery.Value = val - return nil - }) - } - return nil + + return item.Value(func(val []byte) error { + resp.Log = "exists" + resp.Value = val + return nil + }) }) - if err != nil { - panic(err) + if dbErr != nil { + log.Panicf("Error reading database, unable to execute query: %v", dbErr) } - return + return resp } ``` -The complete specification can be found -[here](https://github.com/tendermint/tendermint/tree/main/spec/abci/). +Since it reads only committed data from the store, transactions that are part of a block +that is being processed are not reflected in the query result. -## 1.4 Starting an application and a Tendermint Core instances +### 1.3.5 PrepareProposal and ProcessProposal +`PrepareProposal` and `ProcessProposal` are methods introduced in Tendermint v0.37.0 +to give the application more control over the construction and processing of transaction blocks. -Put the following code into the "main.go" file: +When Tendermint Core sees that valid transactions (validated through `CheckTx`) are available to be +included in blocks, it groups some of these transactions and then gives the application a chance +to modify the group by invoking `PrepareProposal`. + +The application is free to modify the group before returning from the call. +For example, the application may reorder, add, or even remove transactions from the group to improve the +execution of the block once accepted. +In the following code, the application simply returns the unmodified group of transactions: + +```go +func (app *KVStoreApplication) PrepareProposal(proposal abcitypes.RequestPrepareProposal) abcitypes.ResponsePrepareProposal { + return abcitypes.ResponsePrepareProposal{Txs: proposal.Txs} +} +``` + +Once a proposed block is received by a node, the proposal is passed to the application to give +its blessing before voting to accept the proposal. + +This mechanism may be used for different reasons, for example to deal with blocks manipulated +by malicious nodes, in which case the block should not be considered valid. +The following code simply accepts all proposals: + +```go +func (app *KVStoreApplication) ProcessProposal(proposal abcitypes.RequestProcessProposal) abcitypes.ResponseProcessProposal { + return abcitypes.ResponseProcessProposal{Status: abcitypes.ResponseProcessProposal_ACCEPT} +} +``` + +## 1.4 Starting an application and a Tendermint Core instance + +Now that we have the basic functionality of our application in place, let's put it all together inside of our `main.go` file. + +Change the contents of your `main.go` file to the following. ```go package main @@ -347,37 +496,49 @@ package main import ( "flag" "fmt" + abciserver "github.com/tendermint/tendermint/abci/server" + "log" "os" "os/signal" + "path/filepath" "syscall" - "github.com/dgraph-io/badger" - - abciserver "github.com/tendermint/tendermint/abci/server" - "github.com/tendermint/tendermint/libs/log" + "github.com/dgraph-io/badger/v3" + tmlog "github.com/tendermint/tendermint/libs/log" ) +var homeDir string var socketAddr string func init() { - flag.StringVar(&socketAddr, "socket-addr", "unix://example.sock", "Unix domain socket address") + flag.StringVar(&homeDir, "kv-home", "", "Path to the kvstore directory (if empty, uses $HOME/.kvstore)") + flag.StringVar(&socketAddr, "socket-addr", "unix://example.sock", "Unix domain socket address (if empty, uses \"unix://example.sock\"") } func main() { - db, err := badger.Open(badger.DefaultOptions("/tmp/badger")) - if err != nil { - fmt.Fprintf(os.Stderr, "failed to open badger db: %v", err) - os.Exit(1) + flag.Parse() + if homeDir == "" { + homeDir = os.ExpandEnv("$HOME/.kvstore") } - defer db.Close() + + dbPath := filepath.Join(homeDir, "badger") + db, err := badger.Open(badger.DefaultOptions(dbPath)) + if err != nil { + log.Fatalf("Opening database: %v", err) + } + defer func() { + if err := db.Close(); err != nil { + log.Fatalf("Closing database: %v", err) + } + }() + app := NewKVStoreApplication(db) - flag.Parse() - - logger := log.NewTMLogger(log.NewSyncWriter(os.Stdout)) + logger := tmlog.NewTMLogger(tmlog.NewSyncWriter(os.Stdout)) server := abciserver.NewSocketServer(socketAddr, app) server.SetLogger(logger) + if err := server.Start(); err != nil { fmt.Fprintf(os.Stderr, "error starting socket server: %v", err) os.Exit(1) @@ -387,7 +548,6 @@ func main() { c := make(chan os.Signal, 1) signal.Notify(c, os.Interrupt, syscall.SIGTERM) <-c - os.Exit(0) } ``` @@ -396,20 +556,18 @@ This is a huge blob of code, so let's break it down into pieces. First, we initialize the Badger database and create an app instance: ```go -db, err := badger.Open(badger.DefaultOptions("/tmp/badger")) -if err != nil { - fmt.Fprintf(os.Stderr, "failed to open badger db: %v", err) - os.Exit(1) -} -defer db.Close() -app := NewKVStoreApplication(db) -``` + dbPath := filepath.Join(homeDir, "badger") + db, err := badger.Open(badger.DefaultOptions(dbPath)) + if err != nil { + log.Fatalf("Opening database: %v", err) + } + defer func() { + if err := db.Close(); err != nil { + log.Fatalf("Closing database: %v", err) + } + }() -For **Windows** users, restarting this app will make badger throw an error as it requires value log to be truncated. For more information on this, visit [here](https://github.com/dgraph-io/badger/issues/744). -This can be avoided by setting the truncate option to true, like this: - -```go -db, err := badger.Open(badger.DefaultOptions("/tmp/badger").WithTruncate(true)) + app := NewKVStoreApplication(db) ``` Then we start the ABCI server and add some signal handling to gracefully stop @@ -417,143 +575,131 @@ it upon receiving SIGTERM or Ctrl-C. Tendermint Core will act as a client, which connects to our server and send us transactions and other messages. ```go -server := abciserver.NewSocketServer(socketAddr, app) -server.SetLogger(logger) -if err := server.Start(); err != nil { - fmt.Fprintf(os.Stderr, "error starting socket server: %v", err) - os.Exit(1) -} -defer server.Stop() + server := abciserver.NewSocketServer(socketAddr, app) + server.SetLogger(logger) -c := make(chan os.Signal, 1) -signal.Notify(c, os.Interrupt, syscall.SIGTERM) -<-c -os.Exit(0) + if err := server.Start(); err != nil { + fmt.Fprintf(os.Stderr, "error starting socket server: %v", err) + os.Exit(1) + } + defer server.Stop() + + c := make(chan os.Signal, 1) + signal.Notify(c, os.Interrupt, syscall.SIGTERM) + <-c ``` +## 1.5 Initializing and Running -## 1.5 Getting Up and Running +Our application is almost ready to run, but first we'll need to populate the Tendermint Core configuration files. +The following command will create a `tendermint-home` directory in your project and add a basic set of configuration files in `tendermint-home/config/`. +For more information on what these files contain see [the configuration documentation](https://github.com/tendermint/tendermint/blob/v0.37.0/docs/nodes/configuration.md). -We are going to use [Go modules](https://github.com/golang/go/wiki/Modules) for -dependency management. +From the root of your project, run: ```bash -go mod init github.com/me/example -go get github.com/tendermint/tendermint/@v0.34.0 +go run github.com/tendermint/tendermint/cmd/tendermint@v0.37.0 init --home /tmp/tendermint-home ``` -After running the above commands you will see two generated files, go.mod and go.sum. The go.mod file should look similar to: +You should see an output similar to the following: -```go -module github.com/me/example - -go 1.15 - -require ( - github.com/dgraph-io/badger v1.6.2 - github.com/tendermint/tendermint v0.34.0 -) +```bash +I[2022-11-09|09:06:34.444] Generated private validator module=main keyFile=/tmp/tendermint-home/config/priv_validator_key.json stateFile=/tmp/tendermint-home/data/priv_validator_state.json +I[2022-11-09|09:06:34.444] Generated node key module=main path=/tmp/tendermint-home/config/node_key.json +I[2022-11-09|09:06:34.444] Generated genesis file module=main path=/tmp/tendermint-home/config/genesis.json ``` -Finally, we will build our binary: +Now rebuild the app: + +```bash +go build -mod=mod # use -mod=mod to automatically refresh the dependencies +``` + +Everything is now in place to run your application. Run: + +```bash +./kvstore -kv-home /tmp/badger-home +``` + +The application will start and you should see an output similar to the following: + +```bash +badger 2022/11/09 17:01:28 INFO: All 0 tables opened in 0s +badger 2022/11/09 17:01:28 INFO: Discard stats nextEmptySlot: 0 +badger 2022/11/09 17:01:28 INFO: Set nextTxnTs to 0 +I[2022-11-09|17:01:28.726] service start msg="Starting ABCIServer service" impl=ABCIServer +I[2022-11-09|17:01:28.726] Waiting for new connection... +``` + +Then we need to start Tendermint Core service and point it to our application. +Open a new terminal window and cd to the same folder where the app is running. +Then execute the following command: + +```bash +go run github.com/tendermint/tendermint/cmd/tendermint@v0.37.0 node --home /tmp/tendermint-home --proxy_app=unix://example.sock +``` + +This should start the full node and connect to our ABCI application, which will be +reflected in the application output. ```sh -go build +I[2022-11-09|17:07:08.124] service start msg="Starting ABCIServer service" impl=ABCIServer +I[2022-11-09|17:07:08.124] Waiting for new connection... +I[2022-11-09|17:08:12.702] Accepted a new connection +I[2022-11-09|17:08:12.703] Waiting for new connection... +I[2022-11-09|17:08:12.703] Accepted a new connection +I[2022-11-09|17:08:12.703] Waiting for new connection... ``` -To create a default configuration, nodeKey and private validator files, let's -execute `tendermint init`. But before we do that, we will need to install -Tendermint Core. Please refer to [the official -guide](https://docs.tendermint.com/main/introduction/install.html). If you're -installing from source, don't forget to checkout the latest release (`git checkout vX.Y.Z`). +Also, the application using Tendermint Core is producing blocks 🎉🎉 and you can see this reflected in the log output of the service in lines like this: ```bash -rm -rf /tmp/example -TMHOME="/tmp/example" tendermint init - -I[2019-07-16|18:20:36.480] Generated private validator module=main keyFile=/tmp/example/config/priv_validator_key.json stateFile=/tmp/example2/data/priv_validator_state.json -I[2019-07-16|18:20:36.481] Generated node key module=main path=/tmp/example/config/node_key.json -I[2019-07-16|18:20:36.482] Generated genesis file module=main path=/tmp/example/config/genesis.json +I[2022-11-09|09:08:52.147] received proposal module=consensus proposal="Proposal{2/0 (F518444C0E348270436A73FD0F0B9DFEA758286BEB29482F1E3BEA75330E825C:1:C73D3D1273F2, -1) AD19AE292A45 @ 2022-11-09T12:08:52.143393Z}" +I[2022-11-09|09:08:52.152] received complete proposal block module=consensus height=2 hash=F518444C0E348270436A73FD0F0B9DFEA758286BEB29482F1E3BEA75330E825C +I[2022-11-09|09:08:52.160] finalizing commit of block module=consensus height=2 hash=F518444C0E348270436A73FD0F0B9DFEA758286BEB29482F1E3BEA75330E825C root= num_txs=0 +I[2022-11-09|09:08:52.167] executed block module=state height=2 num_valid_txs=0 num_invalid_txs=0 +I[2022-11-09|09:08:52.171] committed state module=state height=2 num_txs=0 app_hash= ``` -Feel free to explore the generated files, which can be found at -`/tmp/example/config` directory. Documentation on the config can be found -[here](https://docs.tendermint.com/main/tendermint-core/configuration.html). +The blocks, as you can see from the `num_valid_txs=0` part, are empty, but let's remedy that next. + +## 1.6 Using the application + +Let's try submitting a transaction to our new application. +Open another terminal window and run the following curl command: -We are ready to start our application: ```bash -rm example.sock -./example - -badger 2019/07/16 18:25:11 INFO: All 0 tables opened in 0s -badger 2019/07/16 18:25:11 INFO: Replaying file id: 0 at offset: 0 -badger 2019/07/16 18:25:11 INFO: Replay took: 300.4s -I[2019-07-16|18:25:11.523] Starting ABCIServer impl=ABCIServ -``` - -Then we need to start Tendermint Core and point it to our application. Staying -within the application directory execute: - -```bash -TMHOME="/tmp/example" tendermint node --proxy_app=unix://example.sock - -I[2019-07-16|18:26:20.362] Version info module=main software=0.32.1 block=10 p2p=7 -I[2019-07-16|18:26:20.383] Starting Node module=main impl=Node -E[2019-07-16|18:26:20.392] Couldn't connect to any seeds module=p2p -I[2019-07-16|18:26:20.394] Started node module=main nodeInfo="{ProtocolVersion:{P2P:7 Block:10 App:0} ID_:8dab80770ae8e295d4ce905d86af78c4ff634b79 ListenAddr:tcp://0.0.0.0:26656 Network:test-chain-nIO96P Version:0.32.1 Channels:4020212223303800 Moniker:app48.fun-box.ru Other:{TxIndex:on RPCAddress:tcp://127.0.0.1:26657}}" -I[2019-07-16|18:26:21.440] Executed block module=state height=1 validTxs=0 invalidTxs=0 -I[2019-07-16|18:26:21.446] Committed state module=state height=1 txs=0 appHash= -``` - -This should start the full node and connect to our ABCI application. - -```sh -I[2019-07-16|18:25:11.525] Waiting for new connection... -I[2019-07-16|18:26:20.329] Accepted a new connection -I[2019-07-16|18:26:20.329] Waiting for new connection... -I[2019-07-16|18:26:20.330] Accepted a new connection -I[2019-07-16|18:26:20.330] Waiting for new connection... -I[2019-07-16|18:26:20.330] Accepted a new connection -``` - -Now open another tab in your terminal and try sending a transaction: - -```json curl -s 'localhost:26657/broadcast_tx_commit?tx="tendermint=rocks"' -{ - "jsonrpc": "2.0", - "id": "", - "result": { - "check_tx": { - "gasWanted": "1" - }, - "deliver_tx": {}, - "hash": "CDD3C6DFA0A08CAEDF546F9938A2EEC232209C24AA0E4201194E0AFB78A2C2BB", - "height": "33" -} +``` +If everything went well, you should see a response indicating which height the +transaction was included in the blockchain. + +Finally, let's make sure that transaction really was persisted by the application. +Run the following command: + +```bash +curl -s 'localhost:26657/abci_query?data="tendermint"' ``` -Response should contain the height where this transaction was committed. - -Now let's check if the given key now exists and its value: +Let's examine the response object that this request returns. +The request returns a `json` object with a `key` and `value` field set. ```json -curl -s 'localhost:26657/abci_query?data="tendermint"' -{ - "jsonrpc": "2.0", - "id": "", - "result": { - "response": { - "log": "exists", - "key": "dGVuZGVybWludA==", - "value": "cm9ja3My" - } - } -} +... + "key": "dGVuZGVybWludA==", + "value": "cm9ja3M=", +... ``` -"dGVuZGVybWludA==" and "cm9ja3M=" are the base64-encoding of the ASCII of -"tendermint" and "rocks" accordingly. +Those values don't look like the `key` and `value` we sent to Tendermint. +What's going on here? + +The response contains a `base64` encoded representation of the data we submitted. +To get the original value out of this data, we can use the `base64` command line utility: + +```bash +echo cm9ja3M=" | base64 -d +``` ## Outro