mirror of
https://github.com/tendermint/tendermint.git
synced 2026-04-25 02:00:32 +00:00
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:
@@ -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.
|
|
||||||
|
|||||||
@@ -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?
|
|
||||||
|
|||||||
@@ -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
@@ -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
|
||||||
@@ -100,7 +129,7 @@ import (
|
|||||||
abcitypes "github.com/tendermint/tendermint/abci/types"
|
abcitypes "github.com/tendermint/tendermint/abci/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
type KVStoreApplication struct {}
|
type KVStoreApplication struct{}
|
||||||
|
|
||||||
var _ abcitypes.Application = (*KVStoreApplication)(nil)
|
var _ abcitypes.Application = (*KVStoreApplication)(nil)
|
||||||
|
|
||||||
@@ -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,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:
|
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")
|
||||||
if err != nil {
|
db, err := badger.Open(badger.DefaultOptions(dbPath))
|
||||||
fmt.Fprintf(os.Stderr, "failed to open badger db: %v", err)
|
if err != nil {
|
||||||
os.Exit(1)
|
log.Fatalf("Opening database: %v", err)
|
||||||
}
|
}
|
||||||
defer db.Close()
|
defer func() {
|
||||||
app := NewKVStoreApplication(db)
|
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).
|
app := NewKVStoreApplication(db)
|
||||||
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
|
||||||
@@ -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.
|
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)
|
||||||
}
|
}
|
||||||
defer server.Stop()
|
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
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user