docs: updates go.md and go-built-in.md as part of issue 9272 (#9688)

* Updates the go.md and go-built-in.md tutorials. This is heavily based on the latest version of the tutorial from branch v0.35.0-rc0
* Includes section for Prepare and ProcessProposal
* Updates output of abci-cli example
* Removes broken example in JS
* Fixes mentions to 1/3 and 2/3 and other small edits
This commit is contained in:
Lasaro Camargos
2022-11-18 12:06:56 -03:00
committed by GitHub
parent 2b4436d1b4
commit f9bfdf4ce2
5 changed files with 1148 additions and 892 deletions

View File

@@ -27,21 +27,27 @@ Usage:
abci-cli [command] abci-cli [command]
Available Commands: Available Commands:
batch Run a batch of abci commands against an application batch run a batch of abci commands against an application
check_tx Validate a tx check_tx validate a transaction
commit Commit the application state and return the Merkle root hash commit commit the application state and return the Merkle root hash
console Start an interactive abci console for multiple commands completion Generate the autocompletion script for the specified shell
deliver_tx Deliver a new tx to the application console start an interactive ABCI console for multiple commands
kvstore ABCI demo example deliver_tx deliver a new transaction to the application
echo Have the application echo a message echo have the application echo a message
help Help about any command help Help about any command
info Get some info about the application info get some info about the application
query Query the application state 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: Flags:
--abci string socket or grpc (default "socket") --abci string either socket or grpc (default "socket")
--address string address of application socket (default "tcp://127.0.0.1:26658") --address string address of application socket (default "tcp://0.0.0.0:26658")
-h, --help help for abci-cli -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 -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. Use "abci-cli [command] --help" for more information about a command.
@@ -58,11 +64,9 @@ purposes.
We'll start a kvstore application, which was installed at the same time 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 as `abci-cli` above. The kvstore just stores transactions in a merkle
tree. tree. Its code can be found
[here](https://github.com/tendermint/tendermint/blob/main/abci/cmd/abci-cli/abci-cli.go)
Its code can be found and looks like the following:
[here](https://github.com/tendermint/tendermint/blob/v0.34.x/abci/cmd/abci-cli/abci-cli.go)
and looks like:
```go ```go
func cmdKVStore(cmd *cobra.Command, args []string) error { func cmdKVStore(cmd *cobra.Command, args []string) error {
@@ -71,14 +75,17 @@ func cmdKVStore(cmd *cobra.Command, args []string) error {
// Create the application - in memory or persisted to disk // Create the application - in memory or persisted to disk
var app types.Application var app types.Application
if flagPersist == "" { if flagPersist == "" {
app = kvstore.NewKVStoreApplication() var err error
} else { flagPersist, err = os.MkdirTemp("", "persistent_kvstore_tmp")
if err != nil {
return err
}
}
app = kvstore.NewPersistentKVStoreApplication(flagPersist) app = kvstore.NewPersistentKVStoreApplication(flagPersist)
app.(*kvstore.PersistentKVStoreApplication).SetLogger(logger.With("module", "kvstore")) app.(*kvstore.PersistentKVStoreApplication).SetLogger(logger.With("module", "kvstore"))
}
// Start the listener // Start the listener
srv, err := server.NewServer(flagAddrD, flagAbci, app) srv, err := server.NewServer(flagAddress, flagAbci, app)
if err != nil { if err != nil {
return err return err
} }
@@ -90,15 +97,18 @@ func cmdKVStore(cmd *cobra.Command, args []string) error {
// Stop upon receiving SIGTERM or CTRL-C. // Stop upon receiving SIGTERM or CTRL-C.
tmos.TrapSignal(logger, func() { tmos.TrapSignal(logger, func() {
// Cleanup // Cleanup
srv.Stop() if err := srv.Stop(); err != nil {
logger.Error("Error while stopping server", "err", err)
}
}) })
// Run forever. // Run forever.
select {} select {}
} }
``` ```
Start by running: Start the application by running:
```sh ```sh
abci-cli kvstore abci-cli kvstore
@@ -170,7 +180,7 @@ Try running these commands:
> prepare_proposal "abc" > prepare_proposal "abc"
-> code: OK -> code: OK
-> log: Succeeded. Tx: abc action: UNMODIFIED -> log: Succeeded. Tx: abc
> process_proposal "abc" > process_proposal "abc"
-> code: OK -> code: OK
@@ -219,11 +229,9 @@ Try running these commands:
> prepare_proposal "preparedef" > prepare_proposal "preparedef"
-> code: OK -> code: OK
-> log: Succeeded. Tx: def action: ADDED -> log: Succeeded. Tx: replacedef
-> code: OK
-> log: Succeeded. Tx: preparedef action: REMOVED
> process_proposal "def" > process_proposal "replacedef"
-> code: OK -> code: OK
-> status: ACCEPT -> 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 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)`. 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`. `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 ## Bounties
Want to write an app in your favorite language?! We'd be happy 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)! to add you to our [ecosystem](https://github.com/tendermint/awesome#ecosystem)!
See [funding](https://github.com/interchainio/funding) opportunities from the See [funding](https://github.com/interchainio/funding) opportunities from the
[Interchain Foundation](https://interchain.io) for implementations in new languages and more. [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.

View File

@@ -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, something useful, you must start two programs: one is Tendermint Core,
the other is your application, which can be written in any programming the other is your application, which can be written in any programming
language. Recall from [the intro to 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 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 In this guide, we show you some examples of how to run an application
using Tendermint. using Tendermint.
@@ -22,7 +23,8 @@ using Tendermint.
The first apps we will work with are written in Go. To install them, you 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 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 ```bash
echo export GOPATH=\"\$HOME/go\" >> ~/.bash_profile echo export GOPATH=\"\$HOME/go\" >> ~/.bash_profile
@@ -31,17 +33,48 @@ echo export PATH=\"\$PATH:\$GOPATH/bin\" >> ~/.bash_profile
Then run Then run
```sh ```bash
go get github.com/tendermint/tendermint go get github.com/tendermint/tendermint
cd $GOPATH/src/github.com/tendermint/tendermint cd $GOPATH/src/github.com/tendermint/tendermint
make install_abci make install_abci
``` ```
Now you should have the `abci-cli` installed; you'll notice the `kvstore` Now you should have the `abci-cli` installed; run `abci-cli` to see the list of commands:
command, an example application written
in Go. See below for an application written in JavaScript.
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 ## 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 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 `tendermint node` to start Tendermint, and connect to the app. For more
details, see [the guide on using Tendermint](../tendermint-core/using-tendermint.md). 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 Try some other transactions and queries to make sure everything is
working! 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?

View File

@@ -6,7 +6,7 @@ order: 4
Tendermint is software for securely and consistently replicating an Tendermint is software for securely and consistently replicating an
application on many machines. By securely, we mean that Tendermint works 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 we mean that every non-faulty machine sees the same transaction log and
computes the same state. Secure and consistent replication is a computes the same state. Secure and consistent replication is a
fundamental problem in distributed systems; it plays a critical role in 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 peer-to-peer networking and cryptographic authentication. The name
derives from the way transactions are batched in blocks, where each derives from the way transactions are batched in blocks, where each
block contains a cryptographic hash of the previous one, forming a block contains a cryptographic hash of the previous one, forming a
chain. In practice, the blockchain data structure actually optimizes BFT chain.
design.
Tendermint consists of two chief technical components: a blockchain Tendermint consists of two chief technical components: a blockchain
consensus engine and a generic application interface. The consensus consensus engine and a generic application interface. The consensus
engine, called Tendermint Core, ensures that the same transactions are engine, called Tendermint Core, ensures that the same transactions are
recorded on every machine in the same order. The application interface, recorded on every machine in the same order. The application interface,
called the Application BlockChain Interface (ABCI), enables the called the Application BlockChain Interface (ABCI), delivers the transactions
transactions to be processed in any programming language. Unlike other to applications for processing. Unlike other
blockchain and consensus solutions, which come pre-packaged with built blockchain and consensus solutions, which come pre-packaged with built
in state machines (like a fancy key-value store, or a quirky scripting in state machines (like a fancy key-value store, or a quirky scripting
language), developers can use Tendermint for BFT state machine language), developers can use Tendermint for BFT state machine
@@ -51,13 +50,13 @@ Hyperledger's Burrow.
### Zookeeper, etcd, consul ### Zookeeper, etcd, consul
Zookeeper, etcd, and consul are all implementations of a key-value store Zookeeper, etcd, and consul are all implementations of key-value stores
atop a classical, non-BFT consensus algorithm. Zookeeper uses a version atop a classical, non-BFT consensus algorithm. Zookeeper uses an
of Paxos called Zookeeper Atomic Broadcast, while etcd and consul use algorithm called Zookeeper Atomic Broadcast, while etcd and consul use
the Raft consensus algorithm, which is much younger and simpler. A the Raft log replication algorithm. A
typical cluster contains 3-5 machines, and can tolerate crash failures 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 in less than 1/2 of the machines (e.g., 1 out of 3 or 2 out of 5),
destroy the system. but even a single Byzantine fault can jeopardize the whole system.
Each offering provides a slightly different implementation of a Each offering provides a slightly different implementation of a
featureful key-value store, but all are generally focused around 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: Tendermint is in essence similar software, but with two key differences:
- It is Byzantine Fault Tolerant, meaning it can only tolerate up to a - It is Byzantine Fault Tolerant, meaning it can only tolerate less than 1/3
1/3 of failures, but those failures can include arbitrary behavior - of machines failing, but those failures can include arbitrary behavior -
including hacking and malicious attacks. - It does not specify a including hacking and malicious attacks. - It does not specify a
particular application, like a fancy key-value store. Instead, it particular application, like a fancy key-value store. Instead, it
focuses on arbitrary state machine replication, so developers can build 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). implementation of [PBFT](http://pmg.csail.mit.edu/papers/osdi99.pdf).
from a team at IBM that is [augmented to handle potentially from a team at IBM that is [augmented to handle potentially
non-deterministic non-deterministic
chaincode](https://drops.dagstuhl.de/opus/volltexte/2017/7093/pdf/LIPIcs-OPODIS-2016-24.pdf) It is chaincode](https://drops.dagstuhl.de/opus/volltexte/2017/7093/pdf/LIPIcs-OPODIS-2016-24.pdf).
possible to implement this docker-based behavior as a ABCI app in It is possible to implement this docker-based behavior as an ABCI app in
Tendermint, though extending Tendermint to handle non-determinism Tendermint, though extending Tendermint to handle non-determinism
remains for future work. 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 Another problem with monolithic design is that it limits you to the
language of the blockchain stack (or vice versa). In the case of language of the blockchain stack (or vice versa). In the case of
Ethereum which supports a Turing-complete bytecode virtual-machine, it Ethereum which supports a Turing-complete bytecode virtual-machine, it
limits you to languages that compile down to that bytecode; today, those limits you to languages that compile down to that bytecode; while the
are Serpent and Solidity. [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 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 blockchain application. We do this by abstracting away the details of
the application to an interface, which is implemented as a socket the application to an interface, which is implemented as a socket
protocol. 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 ### Intro to ABCI
[Tendermint Core](https://github.com/tendermint/tendermint) (the [Tendermint Core](https://github.com/tendermint/tendermint), the
"consensus engine") communicates with the application via a socket "consensus engine", communicates with the application via a socket
protocol that satisfies the ABCI. protocol that satisfies the ABCI, the Tendermint Socket Protocol
(TSP, or Teaspoon).
To draw an analogy, lets talk about a well-known cryptocurrency, To draw an analogy, lets talk about a well-known cryptocurrency,
Bitcoin. Bitcoin is a cryptocurrency blockchain where each node 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. - Allowing clients to query the UTXO database.
Tendermint is able to decompose the blockchain design by offering a very 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. process.
The ABCI consists of 3 primary message types that get delivered from the 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 applications because, among other reasons, it is a completely
deterministic programming language. However, it's also possible to deterministic programming language. However, it's also possible to
create deterministic applications using existing popular languages like create deterministic applications using existing popular languages like
Java, C++, Python, or Go. Game programmers and blockchain developers are Java, C++, Python, or Go, by avoiding
already familiar with creating deterministic programs by avoiding
sources of non-determinism such as: sources of non-determinism such as:
- random number generators (without deterministic seeding) - 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 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. **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 Two stages of voting are required to successfully commit a block; we
call them **pre-vote** and **pre-commit**. A block is committed when call them **pre-vote** and **pre-commit**.
more than 2/3 of validators pre-commit for the same block in the same
round.
There is a picture of a couple doing the polka because validators are 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 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 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. 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 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 current proposer may be offline, or the network may be slow. Tendermint

File diff suppressed because it is too large Load Diff

View File

@@ -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 Core application from scratch. It does not assume that you have any prior
experience with Tendermint Core. experience with Tendermint Core.
Tendermint Core is Byzantine Fault Tolerant (BFT) middleware that takes a state Tendermint Core is a service that provides a Byzantine Fault Tolerant consensus engine
transition machine - written in any programming language - and securely for state-machine replication. The replicated state-machine, or "application", can be written
replicates it on many machines. 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 By following along this tutorial you will create a Tendermint Core application called kvstore,
knowledge of it is not required for this guide. You can learn it as we go due a (very) simple distributed BFT key-value store.
to it's simplicity. However, you may want to go through [Learn X in Y minutes The application will be written in Go and
Where X=Go](https://learnxinyminutes.com/docs/go/) first to familiarize 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. yourself with the syntax.
By following along with this guide, you'll create a Tendermint Core project Note: Please use the latest released version of this guide and of Tendermint.
called kvstore, a (very) simple distributed BFT key-value store. 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 On the other hand, having a separate application might give you better security
Tendermint Core. [Cosmos SDK](https://github.com/cosmos/cosmos-sdk) is written guarantees as two processes would be communicating via established binary protocol.
this way. Please refer to [Writing a built-in Tendermint Core application in Tendermint Core will not have access to application's state.
Go](./go-built-in.md) guide for details. This is the approach followed in this tutorial.
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.
## 1.1 Installing Go ## 1.1 Installing Go
Please refer to [the official guide for installing Verify that you have the latest version of Go installed (refer to the [official guide for installing Go](https://golang.org/doc/install)):
Go](https://golang.org/doc/install).
Verify that you have the latest version of Go installed:
```bash ```bash
$ go version $ go version
go version go1.15.x darwin/amd64 go version go1.19.2 darwin/amd64
```
Make sure you have `$GOPATH` environment variable set:
```bash
echo $GOPATH
/Users/melekes/go
``` ```
## 1.2 Creating a new Go project ## 1.2 Creating a new Go project
@@ -59,10 +54,9 @@ We'll start by creating a new Go project.
```bash ```bash
mkdir kvstore 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 ```go
package main package main
@@ -79,19 +73,54 @@ func main() {
When run, this should print "Hello, Tendermint Core" to the standard output. When run, this should print "Hello, Tendermint Core" to the standard output.
```bash ```bash
go run main.go cd kvstore
$ go run main.go
Hello, Tendermint Core 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 ## 1.3 Writing a Tendermint Core application
Tendermint Core communicates with the application through the 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). 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 ```go
package main package main
@@ -108,238 +137,358 @@ func NewKVStoreApplication() *KVStoreApplication {
return &KVStoreApplication{} return &KVStoreApplication{}
} }
func (KVStoreApplication) Info(req abcitypes.RequestInfo) abcitypes.ResponseInfo { func (app *KVStoreApplication) Info(info abcitypes.RequestInfo) abcitypes.ResponseInfo {
return 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 { func (app *KVStoreApplication) CheckTx(tx abcitypes.RequestCheckTx) abcitypes.ResponseCheckTx {
return abcitypes.ResponseCheckTx{Code: 0} return abcitypes.ResponseCheckTx{}
} }
func (KVStoreApplication) Commit() abcitypes.ResponseCommit { func (app *KVStoreApplication) InitChain(chain abcitypes.RequestInitChain) abcitypes.ResponseInitChain {
return abcitypes.ResponseCommit{}
}
func (KVStoreApplication) Query(req abcitypes.RequestQuery) abcitypes.ResponseQuery {
return abcitypes.ResponseQuery{Code: 0}
}
func (KVStoreApplication) InitChain(req abcitypes.RequestInitChain) abcitypes.ResponseInitChain {
return 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{} 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{} 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{} return abcitypes.ResponseListSnapshots{}
} }
func (KVStoreApplication) OfferSnapshot(abcitypes.RequestOfferSnapshot) abcitypes.ResponseOfferSnapshot { func (app *KVStoreApplication) OfferSnapshot(snapshot abcitypes.RequestOfferSnapshot) abcitypes.ResponseOfferSnapshot {
return abcitypes.ResponseOfferSnapshot{} return abcitypes.ResponseOfferSnapshot{}
} }
func (KVStoreApplication) LoadSnapshotChunk(abcitypes.RequestLoadSnapshotChunk) abcitypes.ResponseLoadSnapshotChunk { func (app *KVStoreApplication) LoadSnapshotChunk(chunk abcitypes.RequestLoadSnapshotChunk) abcitypes.ResponseLoadSnapshotChunk {
return abcitypes.ResponseLoadSnapshotChunk{} return abcitypes.ResponseLoadSnapshotChunk{}
} }
func (KVStoreApplication) ApplySnapshotChunk(abcitypes.RequestApplySnapshotChunk) abcitypes.ResponseApplySnapshotChunk { func (app *KVStoreApplication) ApplySnapshotChunk(chunk abcitypes.RequestApplySnapshotChunk) abcitypes.ResponseApplySnapshotChunk {
return abcitypes.ResponseApplySnapshotChunk{} return abcitypes.ResponseApplySnapshotChunk{}
} }
``` ```
Now I will go through each method explaining when it's called and adding The types used here are defined in the Tendermint library and were added as a dependency
required business logic. 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 ```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 // check format
parts := bytes.Split(tx, []byte("=")) parts := bytes.Split(tx, []byte("="))
if len(parts) != 2 { if len(parts) != 2 {
return 1 return 1
} }
key, value := parts[0], parts[1] return 0
// 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}
} }
``` ```
Don't worry if this does not compile yet. Now you can rewrite the `CheckTx` method to use the helper function:
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.
```go ```go
import "github.com/dgraph-io/badger" func (app *KVStoreApplication) CheckTx(req abcitypes.RequestCheckTx) abcitypes.ResponseCheckTx {
code := app.isValid(req.Tx)
type KVStoreApplication struct { return abcitypes.ResponseCheckTx{Code: code}
db *badger.DB
currentBatch *badger.Txn
}
func NewKVStoreApplication(db *badger.DB) *KVStoreApplication {
return &KVStoreApplication{
db: db,
}
} }
``` ```
### 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 Depending on the checks and on the conditions violated, the function may return
application in 3 parts: `BeginBlock`, one `DeliverTx` per transaction and different values, but any response with a non-zero code will be considered invalid
`EndBlock` in the end. DeliverTx are being transferred asynchronously, but the by Tendermint. Our `CheckTx` logic returns 0 to Tendermint when a transaction passes
responses are expected to come in order. 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 ```go
func (app *KVStoreApplication) BeginBlock(req abcitypes.RequestBeginBlock) abcitypes.ResponseBeginBlock { func (app *KVStoreApplication) BeginBlock(req abcitypes.RequestBeginBlock) abcitypes.ResponseBeginBlock {
app.currentBatch = app.db.NewTransaction(true) app.onGoingBlock = app.db.NewTransaction(true)
return abcitypes.ResponseBeginBlock{} 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 ```go
func (app *KVStoreApplication) DeliverTx(req abcitypes.RequestDeliverTx) abcitypes.ResponseDeliverTx { func (app *KVStoreApplication) DeliverTx(req abcitypes.RequestDeliverTx) abcitypes.ResponseDeliverTx {
code := app.isValid(req.Tx) if code := app.isValid(req.Tx); code != 0 {
if code != 0 {
return abcitypes.ResponseDeliverTx{Code: code} return abcitypes.ResponseDeliverTx{Code: code}
} }
parts := bytes.Split(req.Tx, []byte("=")) parts := bytes.SplitN(req.Tx, []byte("="), 2)
key, value := parts[0], parts[1] key, value := parts[0], parts[1]
err := app.currentBatch.Set(key, value) if err := app.onGoingBlock.Set(key, value); err != nil {
if err != nil { log.Panicf("Error writing to database, unable to execute tx: %v", err)
panic(err)
} }
return abcitypes.ResponseDeliverTx{Code: 0} return abcitypes.ResponseDeliverTx{Code: 0}
} }
``` ```
If the transaction is badly formatted or the same key=value already exist, we Note that we check the validity of the transaction _again_ during `DeliverTx`.
again return the non-zero code. Otherwise, we add it to the current batch. 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 `EndBlock` is called to inform the application that the full block has been delivered
passed CheckTx, but failed DeliverTx or transactions included by the proposer and give the application a chance to perform any other computation needed, before the
directly). This is done for performance reasons. effects of the transactions become permanent.
Note we can't commit transactions inside the `DeliverTx` because in such case Note that `EndBlock` **cannot** yet commit the Badger transaction we were building
`Query`, which may be called in parallel, will return inconsistent data (i.e. in during `DeliverTx`.
it will report that some value already exist even when the actual block was not Since other methods, such as `Query`, rely on a consistent view of the application's
yet committed). 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 ```go
func (app *KVStoreApplication) Commit() abcitypes.ResponseCommit { 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{}} return abcitypes.ResponseCommit{Data: []byte{}}
} }
``` ```
### 1.3.3 Query Finally, make sure to add the log library to the `import` stanza as well:
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.
```go ```go
func (app *KVStoreApplication) Query(reqQuery abcitypes.RequestQuery) (resQuery abcitypes.ResponseQuery) { import (
resQuery.Key = reqQuery.Data "bytes"
err := app.db.View(func(txn *badger.Txn) error { "log"
item, err := txn.Get(reqQuery.Data)
if err != nil && err != badger.ErrKeyNotFound { "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 return err
} }
if err == badger.ErrKeyNotFound { resp.Log = "key does not exist"
resQuery.Log = "does not exist" return nil
} else { }
return item.Value(func(val []byte) error { return item.Value(func(val []byte) error {
resQuery.Log = "exists" resp.Log = "exists"
resQuery.Value = val resp.Value = val
return nil return nil
}) })
}
return nil
}) })
if err != nil { if dbErr != nil {
panic(err) log.Panicf("Error reading database, unable to execute query: %v", dbErr)
} }
return return resp
} }
``` ```
The complete specification can be found Since it reads only committed data from the store, transactions that are part of a block
[here](https://github.com/tendermint/tendermint/tree/main/spec/abci/). 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 ```go
package main package main
@@ -347,37 +496,49 @@ package main
import ( import (
"flag" "flag"
"fmt" "fmt"
abciserver "github.com/tendermint/tendermint/abci/server"
"log"
"os" "os"
"os/signal" "os/signal"
"path/filepath"
"syscall" "syscall"
"github.com/dgraph-io/badger" "github.com/dgraph-io/badger/v3"
tmlog "github.com/tendermint/tendermint/libs/log"
abciserver "github.com/tendermint/tendermint/abci/server"
"github.com/tendermint/tendermint/libs/log"
) )
var homeDir string
var socketAddr string var socketAddr string
func init() { 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() { func main() {
db, err := badger.Open(badger.DefaultOptions("/tmp/badger")) flag.Parse()
if err != nil { if homeDir == "" {
fmt.Fprintf(os.Stderr, "failed to open badger db: %v", err) homeDir = os.ExpandEnv("$HOME/.kvstore")
os.Exit(1)
} }
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) app := NewKVStoreApplication(db)
flag.Parse() logger := tmlog.NewTMLogger(tmlog.NewSyncWriter(os.Stdout))
logger := log.NewTMLogger(log.NewSyncWriter(os.Stdout))
server := abciserver.NewSocketServer(socketAddr, app) server := abciserver.NewSocketServer(socketAddr, app)
server.SetLogger(logger) server.SetLogger(logger)
if err := server.Start(); err != nil { if err := server.Start(); err != nil {
fmt.Fprintf(os.Stderr, "error starting socket server: %v", err) fmt.Fprintf(os.Stderr, "error starting socket server: %v", err)
os.Exit(1) os.Exit(1)
@@ -387,7 +548,6 @@ func main() {
c := make(chan os.Signal, 1) c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt, syscall.SIGTERM) signal.Notify(c, os.Interrupt, syscall.SIGTERM)
<-c <-c
os.Exit(0)
} }
``` ```
@@ -396,22 +556,20 @@ 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 initialize the Badger database and create an app instance:
```go ```go
db, err := badger.Open(badger.DefaultOptions("/tmp/badger")) dbPath := filepath.Join(homeDir, "badger")
db, err := badger.Open(badger.DefaultOptions(dbPath))
if err != nil { if err != nil {
fmt.Fprintf(os.Stderr, "failed to open badger db: %v", err) log.Fatalf("Opening database: %v", err)
os.Exit(1)
} }
defer db.Close() defer func() {
if err := db.Close(); err != nil {
log.Fatalf("Closing database: %v", err)
}
}()
app := NewKVStoreApplication(db) app := NewKVStoreApplication(db)
``` ```
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))
```
Then we start the ABCI server and add some signal handling to gracefully stop Then we start the ABCI server and add some signal handling to gracefully stop
it upon receiving SIGTERM or Ctrl-C. Tendermint Core will act as a client, 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. which connects to our server and send us transactions and other messages.
@@ -419,6 +577,7 @@ which connects to our server and send us transactions and other messages.
```go ```go
server := abciserver.NewSocketServer(socketAddr, app) server := abciserver.NewSocketServer(socketAddr, app)
server.SetLogger(logger) server.SetLogger(logger)
if err := server.Start(); err != nil { if err := server.Start(); err != nil {
fmt.Fprintf(os.Stderr, "error starting socket server: %v", err) fmt.Fprintf(os.Stderr, "error starting socket server: %v", err)
os.Exit(1) os.Exit(1)
@@ -428,132 +587,119 @@ defer server.Stop()
c := make(chan os.Signal, 1) c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt, syscall.SIGTERM) signal.Notify(c, os.Interrupt, syscall.SIGTERM)
<-c <-c
os.Exit(0)
``` ```
## 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 From the root of your project, run:
dependency management.
```bash ```bash
go mod init github.com/me/example go run github.com/tendermint/tendermint/cmd/tendermint@v0.37.0 init --home /tmp/tendermint-home
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: You should see an output similar to the following:
```go ```bash
module github.com/me/example 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
go 1.15 I[2022-11-09|09:06:34.444] Generated genesis file module=main path=/tmp/tendermint-home/config/genesis.json
require (
github.com/dgraph-io/badger v1.6.2
github.com/tendermint/tendermint v0.34.0
)
``` ```
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 ```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 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:
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`).
```bash ```bash
rm -rf /tmp/example 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}"
TMHOME="/tmp/example" tendermint init 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[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[2022-11-09|09:08:52.167] executed block module=state height=2 num_valid_txs=0 num_invalid_txs=0
I[2019-07-16|18:20:36.481] Generated node key module=main path=/tmp/example/config/node_key.json I[2022-11-09|09:08:52.171] committed state module=state height=2 num_txs=0 app_hash=
I[2019-07-16|18:20:36.482] Generated genesis file module=main path=/tmp/example/config/genesis.json
``` ```
Feel free to explore the generated files, which can be found at The blocks, as you can see from the `num_valid_txs=0` part, are empty, but let's remedy that next.
`/tmp/example/config` directory. Documentation on the config can be found
[here](https://docs.tendermint.com/main/tendermint-core/configuration.html). ## 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 ```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"' curl -s 'localhost:26657/broadcast_tx_commit?tx="tendermint=rocks"'
{ ```
"jsonrpc": "2.0", If everything went well, you should see a response indicating which height the
"id": "", transaction was included in the blockchain.
"result": {
"check_tx": { Finally, let's make sure that transaction really was persisted by the application.
"gasWanted": "1" Run the following command:
},
"deliver_tx": {}, ```bash
"hash": "CDD3C6DFA0A08CAEDF546F9938A2EEC232209C24AA0E4201194E0AFB78A2C2BB", curl -s 'localhost:26657/abci_query?data="tendermint"'
"height": "33"
}
``` ```
Response should contain the height where this transaction was committed. Let's examine the response object that this request returns.
The request returns a `json` object with a `key` and `value` field set.
Now let's check if the given key now exists and its value:
```json ```json
curl -s 'localhost:26657/abci_query?data="tendermint"' ...
{
"jsonrpc": "2.0",
"id": "",
"result": {
"response": {
"log": "exists",
"key": "dGVuZGVybWludA==", "key": "dGVuZGVybWludA==",
"value": "cm9ja3My" "value": "cm9ja3M=",
} ...
}
}
``` ```
"dGVuZGVybWludA==" and "cm9ja3M=" are the base64-encoding of the ASCII of Those values don't look like the `key` and `value` we sent to Tendermint.
"tendermint" and "rocks" accordingly. 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 ## Outro