bitbucket.org/number571/tendermint@v0.8.14/docs/tutorials/go.md (about)

     1  <!---
     2  order: 1
     3  --->
     4  
     5  # Creating an application in Go
     6  
     7  ## Guide Assumptions
     8  
     9  This guide is designed for beginners who want to get started with a Tendermint
    10  Core application from scratch. It does not assume that you have any prior
    11  experience with Tendermint Core.
    12  
    13  Tendermint Core is Byzantine Fault Tolerant (BFT) middleware that takes a state
    14  transition machine - written in any programming language - and securely
    15  replicates it on many machines.
    16  
    17  Although Tendermint Core is written in the Golang programming language, prior
    18  knowledge of it is not required for this guide. You can learn it as we go due
    19  to it's simplicity. However, you may want to go through [Learn X in Y minutes
    20  Where X=Go](https://learnxinyminutes.com/docs/go/) first to familiarize
    21  yourself with the syntax.
    22  
    23  By following along with this guide, you'll create a Tendermint Core project
    24  called kvstore, a (very) simple distributed BFT key-value store.
    25  
    26  ## Built-in app vs external app
    27  
    28  To get maximum performance it is better to run your application alongside
    29  Tendermint Core. [Cosmos SDK](https://github.com/cosmos/cosmos-sdk) is written
    30  this way. Please refer to [Writing a built-in Tendermint Core application in
    31  Go](./go-built-in.md) guide for details.
    32  
    33  Having a separate application might give you better security guarantees as two
    34  processes would be communicating via established binary protocol. Tendermint
    35  Core will not have access to application's state.
    36  
    37  ## 1.1 Installing Go
    38  
    39  Please refer to [the official guide for installing
    40  Go](https://golang.org/doc/install).
    41  
    42  Verify that you have the latest version of Go installed:
    43  
    44  ```bash
    45  $ go version
    46  go version go1.16.x darwin/amd64
    47  ```
    48  
    49  ## 1.2 Creating a new Go project
    50  
    51  We'll start by creating a new Go project.
    52  
    53  ```bash
    54  mkdir kvstore
    55  cd kvstore
    56  ```
    57  
    58  Inside the example directory create a `main.go` file with the following content:
    59  
    60  ```go
    61  package main
    62  
    63  import (
    64   "fmt"
    65  )
    66  
    67  func main() {
    68   fmt.Println("Hello, Tendermint Core")
    69  }
    70  ```
    71  
    72  When run, this should print "Hello, Tendermint Core" to the standard output.
    73  
    74  ```bash
    75  go run main.go
    76  Hello, Tendermint Core
    77  ```
    78  
    79  ## 1.3 Writing a Tendermint Core application
    80  
    81  Tendermint Core communicates with the application through the Application
    82  BlockChain Interface (ABCI). All message types are defined in the [protobuf
    83  file](https://bitbucket.org/number571/tendermint/blob/master/proto/tendermint/abci/types.proto).
    84  This allows Tendermint Core to run applications written in any programming
    85  language.
    86  
    87  Create a file called `app.go` with the following content:
    88  
    89  ```go
    90  package main
    91  
    92  import (
    93   abcitypes "bitbucket.org/number571/tendermint/abci/types"
    94  )
    95  
    96  type KVStoreApplication struct {}
    97  
    98  var _ abcitypes.Application = (*KVStoreApplication)(nil)
    99  
   100  func NewKVStoreApplication() *KVStoreApplication {
   101   return &KVStoreApplication{}
   102  }
   103  
   104  func (KVStoreApplication) Info(req abcitypes.RequestInfo) abcitypes.ResponseInfo {
   105   return abcitypes.ResponseInfo{}
   106  }
   107  
   108  func (KVStoreApplication) DeliverTx(req abcitypes.RequestDeliverTx) abcitypes.ResponseDeliverTx {
   109   return abcitypes.ResponseDeliverTx{Code: 0}
   110  }
   111  
   112  func (KVStoreApplication) CheckTx(req abcitypes.RequestCheckTx) abcitypes.ResponseCheckTx {
   113   return abcitypes.ResponseCheckTx{Code: 0}
   114  }
   115  
   116  func (KVStoreApplication) Commit() abcitypes.ResponseCommit {
   117   return abcitypes.ResponseCommit{}
   118  }
   119  
   120  func (KVStoreApplication) Query(req abcitypes.RequestQuery) abcitypes.ResponseQuery {
   121   return abcitypes.ResponseQuery{Code: 0}
   122  }
   123  
   124  func (KVStoreApplication) InitChain(req abcitypes.RequestInitChain) abcitypes.ResponseInitChain {
   125   return abcitypes.ResponseInitChain{}
   126  }
   127  
   128  func (KVStoreApplication) BeginBlock(req abcitypes.RequestBeginBlock) abcitypes.ResponseBeginBlock {
   129   return abcitypes.ResponseBeginBlock{}
   130  }
   131  
   132  func (KVStoreApplication) EndBlock(req abcitypes.RequestEndBlock) abcitypes.ResponseEndBlock {
   133   return abcitypes.ResponseEndBlock{}
   134  }
   135  
   136  func (KVStoreApplication) ListSnapshots(abcitypes.RequestListSnapshots) abcitypes.ResponseListSnapshots {
   137   return abcitypes.ResponseListSnapshots{}
   138  }
   139  
   140  func (KVStoreApplication) OfferSnapshot(abcitypes.RequestOfferSnapshot) abcitypes.ResponseOfferSnapshot {
   141   return abcitypes.ResponseOfferSnapshot{}
   142  }
   143  
   144  func (KVStoreApplication) LoadSnapshotChunk(abcitypes.RequestLoadSnapshotChunk) abcitypes.ResponseLoadSnapshotChunk {
   145   return abcitypes.ResponseLoadSnapshotChunk{}
   146  }
   147  
   148  func (KVStoreApplication) ApplySnapshotChunk(abcitypes.RequestApplySnapshotChunk) abcitypes.ResponseApplySnapshotChunk {
   149   return abcitypes.ResponseApplySnapshotChunk{}
   150  }
   151  ```
   152  
   153  Now I will go through each method explaining when it's called and adding
   154  required business logic.
   155  
   156  ### 1.3.1 CheckTx
   157  
   158  When a new transaction is added to the Tendermint Core, it will ask the
   159  application to check it (validate the format, signatures, etc.).
   160  
   161  ```go
   162  import "bytes"
   163  
   164  func (app *KVStoreApplication) isValid(tx []byte) (code uint32) {
   165   // check format
   166   parts := bytes.Split(tx, []byte("="))
   167   if len(parts) != 2 {
   168    return 1
   169   }
   170  
   171   key, value := parts[0], parts[1]
   172  
   173   // check if the same key=value already exists
   174   err := app.db.View(func(txn *badger.Txn) error {
   175    item, err := txn.Get(key)
   176    if err != nil && err != badger.ErrKeyNotFound {
   177     return err
   178    }
   179    if err == nil {
   180     return item.Value(func(val []byte) error {
   181      if bytes.Equal(val, value) {
   182       code = 2
   183      }
   184      return nil
   185     })
   186    }
   187    return nil
   188   })
   189   if err != nil {
   190    panic(err)
   191   }
   192  
   193   return code
   194  }
   195  
   196  func (app *KVStoreApplication) CheckTx(req abcitypes.RequestCheckTx) abcitypes.ResponseCheckTx {
   197   code := app.isValid(req.Tx)
   198   return abcitypes.ResponseCheckTx{Code: code, GasWanted: 1}
   199  }
   200  ```
   201  
   202  Don't worry if this does not compile yet.
   203  
   204  If the transaction does not have a form of `{bytes}={bytes}`, we return `1`
   205  code. When the same key=value already exist (same key and value), we return `2`
   206  code. For others, we return a zero code indicating that they are valid.
   207  
   208  Note that anything with non-zero code will be considered invalid (`-1`, `100`,
   209  etc.) by Tendermint Core.
   210  
   211  Valid transactions will eventually be committed given they are not too big and
   212  have enough gas. To learn more about gas, check out ["the
   213  specification"](https://docs.tendermint.com/master/spec/abci/apps.html#gas).
   214  
   215  For the underlying key-value store we'll use
   216  [badger](https://github.com/dgraph-io/badger), which is an embeddable,
   217  persistent and fast key-value (KV) database.
   218  
   219  ```go
   220  import "github.com/dgraph-io/badger"
   221  
   222  type KVStoreApplication struct {
   223   db           *badger.DB
   224   currentBatch *badger.Txn
   225  }
   226  
   227  func NewKVStoreApplication(db *badger.DB) *KVStoreApplication {
   228   return &KVStoreApplication{
   229    db: db,
   230   }
   231  }
   232  ```
   233  
   234  ### 1.3.2 BeginBlock -> DeliverTx -> EndBlock -> Commit
   235  
   236  When Tendermint Core has decided on the block, it's transferred to the
   237  application in 3 parts: `BeginBlock`, one `DeliverTx` per transaction and
   238  `EndBlock` in the end. DeliverTx are being transferred asynchronously, but the
   239  responses are expected to come in order.
   240  
   241  ```go
   242  func (app *KVStoreApplication) BeginBlock(req abcitypes.RequestBeginBlock) abcitypes.ResponseBeginBlock {
   243   app.currentBatch = app.db.NewTransaction(true)
   244   return abcitypes.ResponseBeginBlock{}
   245  }
   246  ```
   247  
   248  Here we create a batch, which will store block's transactions.
   249  
   250  ```go
   251  func (app *KVStoreApplication) DeliverTx(req abcitypes.RequestDeliverTx) abcitypes.ResponseDeliverTx {
   252   code := app.isValid(req.Tx)
   253   if code != 0 {
   254    return abcitypes.ResponseDeliverTx{Code: code}
   255   }
   256  
   257   parts := bytes.Split(req.Tx, []byte("="))
   258   key, value := parts[0], parts[1]
   259  
   260   err := app.currentBatch.Set(key, value)
   261   if err != nil {
   262    panic(err)
   263   }
   264  
   265   return abcitypes.ResponseDeliverTx{Code: 0}
   266  }
   267  ```
   268  
   269  If the transaction is badly formatted or the same key=value already exist, we
   270  again return the non-zero code. Otherwise, we add it to the current batch.
   271  
   272  In the current design, a block can include incorrect transactions (those who
   273  passed CheckTx, but failed DeliverTx or transactions included by the proposer
   274  directly). This is done for performance reasons.
   275  
   276  Note we can't commit transactions inside the `DeliverTx` because in such case
   277  `Query`, which may be called in parallel, will return inconsistent data (i.e.
   278  it will report that some value already exist even when the actual block was not
   279  yet committed).
   280  
   281  `Commit` instructs the application to persist the new state.
   282  
   283  ```go
   284  func (app *KVStoreApplication) Commit() abcitypes.ResponseCommit {
   285   app.currentBatch.Commit()
   286   return abcitypes.ResponseCommit{Data: []byte{}}
   287  }
   288  ```
   289  
   290  ### 1.3.3 Query
   291  
   292  Now, when the client wants to know whenever a particular key/value exist, it
   293  will call Tendermint Core RPC `/abci_query` endpoint, which in turn will call
   294  the application's `Query` method.
   295  
   296  Applications are free to provide their own APIs. But by using Tendermint Core
   297  as a proxy, clients (including [light client
   298  package](https://godoc.org/bitbucket.org/number571/tendermint/light)) can leverage
   299  the unified API across different applications. Plus they won't have to call the
   300  otherwise separate Tendermint Core API for additional proofs.
   301  
   302  Note we don't include a proof here.
   303  
   304  ```go
   305  func (app *KVStoreApplication) Query(reqQuery abcitypes.RequestQuery) (resQuery abcitypes.ResponseQuery) {
   306   resQuery.Key = reqQuery.Data
   307   err := app.db.View(func(txn *badger.Txn) error {
   308    item, err := txn.Get(reqQuery.Data)
   309    if err != nil && err != badger.ErrKeyNotFound {
   310     return err
   311    }
   312    if err == badger.ErrKeyNotFound {
   313     resQuery.Log = "does not exist"
   314    } else {
   315     return item.Value(func(val []byte) error {
   316      resQuery.Log = "exists"
   317      resQuery.Value = val
   318      return nil
   319     })
   320    }
   321    return nil
   322   })
   323   if err != nil {
   324    panic(err)
   325   }
   326   return
   327  }
   328  ```
   329  
   330  The complete specification can be found
   331  [here](https://docs.tendermint.com/master/spec/abci/).
   332  
   333  ## 1.4 Starting an application and a Tendermint Core instances
   334  
   335  Put the following code into the "main.go" file:
   336  
   337  ```go
   338  package main
   339  
   340  import (
   341   "flag"
   342   "fmt"
   343   "os"
   344   "os/signal"
   345   "syscall"
   346  
   347   "github.com/dgraph-io/badger"
   348  
   349   abciserver "bitbucket.org/number571/tendermint/abci/server"
   350   "bitbucket.org/number571/tendermint/libs/log"
   351  )
   352  
   353  var socketAddr string
   354  
   355  func init() {
   356   flag.StringVar(&socketAddr, "socket-addr", "unix://example.sock", "Unix domain socket address")
   357  }
   358  
   359  func main() {
   360   db, err := badger.Open(badger.DefaultOptions("/tmp/badger"))
   361   if err != nil {
   362    fmt.Fprintf(os.Stderr, "failed to open badger db: %v", err)
   363    os.Exit(1)
   364   }
   365   defer db.Close()
   366   app := NewKVStoreApplication(db)
   367  
   368   flag.Parse()
   369  
   370   logger := log.NewTMLogger(log.NewSyncWriter(os.Stdout))
   371  
   372   server := abciserver.NewSocketServer(socketAddr, app)
   373   server.SetLogger(logger)
   374   if err := server.Start(); err != nil {
   375    fmt.Fprintf(os.Stderr, "error starting socket server: %v", err)
   376    os.Exit(1)
   377   }
   378   defer server.Stop()
   379  
   380   c := make(chan os.Signal, 1)
   381   signal.Notify(c, os.Interrupt, syscall.SIGTERM)
   382   <-c
   383   os.Exit(0)
   384  }
   385  ```
   386  
   387  This is a huge blob of code, so let's break it down into pieces.
   388  
   389  First, we initialize the Badger database and create an app instance:
   390  
   391  ```go
   392  db, err := badger.Open(badger.DefaultOptions("/tmp/badger"))
   393  if err != nil {
   394   fmt.Fprintf(os.Stderr, "failed to open badger db: %v", err)
   395   os.Exit(1)
   396  }
   397  defer db.Close()
   398  app := NewKVStoreApplication(db)
   399  ```
   400  
   401  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).
   402  This can be avoided by setting the truncate option to true, like this:
   403  
   404  ```go
   405  db, err := badger.Open(badger.DefaultOptions("/tmp/badger").WithTruncate(true))
   406  ```
   407  
   408  Then we start the ABCI server and add some signal handling to gracefully stop
   409  it upon receiving SIGTERM or Ctrl-C. Tendermint Core will act as a client,
   410  which connects to our server and send us transactions and other messages.
   411  
   412  ```go
   413  server := abciserver.NewSocketServer(socketAddr, app)
   414  server.SetLogger(logger)
   415  if err := server.Start(); err != nil {
   416   fmt.Fprintf(os.Stderr, "error starting socket server: %v", err)
   417   os.Exit(1)
   418  }
   419  defer server.Stop()
   420  
   421  c := make(chan os.Signal, 1)
   422  signal.Notify(c, os.Interrupt, syscall.SIGTERM)
   423  <-c
   424  os.Exit(0)
   425  ```
   426  
   427  ## 1.5 Getting Up and Running
   428  
   429  We are going to use [Go modules](https://github.com/golang/go/wiki/Modules) for
   430  dependency management.
   431  
   432  ```bash
   433  export GO111MODULE=on
   434  go mod init github.com/me/example
   435  ```
   436  
   437  This should create a `go.mod` file. The current tutorial only works with
   438  the master branch of Tendermint, so let's make sure we're using the latest version:
   439  
   440  ```sh
   441  go get bitbucket.org/number571/tendermint@master
   442  ```
   443  
   444  This will populate the `go.mod` with a release number followed by a hash for Tendermint.
   445  
   446  ```go
   447  module github.com/me/example
   448  
   449  go 1.16
   450  
   451  require (
   452   github.com/dgraph-io/badger v1.6.2
   453   bitbucket.org/number571/tendermint <vX>
   454  )
   455  ```
   456  
   457  Now we can build the binary:
   458  
   459  ```bash
   460  go build
   461  ```
   462  
   463  To create a default configuration, nodeKey and private validator files, let's
   464  execute `tendermint init validator`. But before we do that, we will need to install
   465  Tendermint Core. Please refer to [the official
   466  guide](https://docs.tendermint.com/master/introduction/install.html). If you're
   467  installing from source, don't forget to checkout the latest release (`git
   468  checkout vX.Y.Z`). Don't forget to check that the application uses the same
   469  major version.
   470  
   471  ```bash
   472  rm -rf /tmp/example
   473  TMHOME="/tmp/example" tendermint init validator
   474  
   475  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
   476  I[2019-07-16|18:20:36.481] Generated node key                           module=main path=/tmp/example/config/node_key.json
   477  I[2019-07-16|18:20:36.482] Generated genesis file                       module=main path=/tmp/example/config/genesis.json
   478  I[2019-07-16|18:20:36.483] Generated config                             module=main mode=validator
   479  ```
   480  
   481  Feel free to explore the generated files, which can be found at
   482  `/tmp/example/config` directory. Documentation on the config can be found
   483  [here](https://docs.tendermint.com/master/tendermint-core/configuration.html).
   484  
   485  We are ready to start our application:
   486  
   487  ```bash
   488  rm example.sock
   489  ./example
   490  
   491  badger 2019/07/16 18:25:11 INFO: All 0 tables opened in 0s
   492  badger 2019/07/16 18:25:11 INFO: Replaying file id: 0 at offset: 0
   493  badger 2019/07/16 18:25:11 INFO: Replay took: 300.4s
   494  I[2019-07-16|18:25:11.523] Starting ABCIServer                          impl=ABCIServ
   495  ```
   496  
   497  Then we need to start Tendermint Core and point it to our application. Staying
   498  within the application directory execute:
   499  
   500  ```bash
   501  TMHOME="/tmp/example" tendermint node --proxy-app=unix://example.sock
   502  
   503  I[2019-07-16|18:26:20.362] Version info                                 module=main software=0.32.1 block=10 p2p=7
   504  I[2019-07-16|18:26:20.383] Starting Node                                module=main impl=Node
   505  E[2019-07-16|18:26:20.392] Couldn't connect to any seeds                module=p2p
   506  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}}"
   507  I[2019-07-16|18:26:21.440] Executed block                               module=state height=1 validTxs=0 invalidTxs=0
   508  I[2019-07-16|18:26:21.446] Committed state                              module=state height=1 txs=0 appHash=
   509  ```
   510  
   511  This should start the full node and connect to our ABCI application.
   512  
   513  ```sh
   514  I[2019-07-16|18:25:11.525] Waiting for new connection...
   515  I[2019-07-16|18:26:20.329] Accepted a new connection
   516  I[2019-07-16|18:26:20.329] Waiting for new connection...
   517  I[2019-07-16|18:26:20.330] Accepted a new connection
   518  I[2019-07-16|18:26:20.330] Waiting for new connection...
   519  I[2019-07-16|18:26:20.330] Accepted a new connection
   520  ```
   521  
   522  Now open another tab in your terminal and try sending a transaction:
   523  
   524  ```json
   525  curl -s 'localhost:26657/broadcast_tx_commit?tx="tendermint=rocks"'
   526  {
   527    "jsonrpc": "2.0",
   528    "id": "",
   529    "result": {
   530      "check_tx": {
   531        "gasWanted": "1"
   532      },
   533      "deliver_tx": {},
   534      "hash": "CDD3C6DFA0A08CAEDF546F9938A2EEC232209C24AA0E4201194E0AFB78A2C2BB",
   535      "height": "33"
   536  }
   537  ```
   538  
   539  Response should contain the height where this transaction was committed.
   540  
   541  Now let's check if the given key now exists and its value:
   542  
   543  ```json
   544  curl -s 'localhost:26657/abci_query?data="tendermint"'
   545  {
   546    "jsonrpc": "2.0",
   547    "id": "",
   548    "result": {
   549      "response": {
   550        "log": "exists",
   551        "key": "dGVuZGVybWludA==",
   552        "value": "cm9ja3My"
   553      }
   554    }
   555  }
   556  ```
   557  
   558  "dGVuZGVybWludA==" and "cm9ja3M=" are the base64-encoding of the ASCII of
   559  "tendermint" and "rocks" accordingly.
   560  
   561  ## Outro
   562  
   563  I hope everything went smoothly and your first, but hopefully not the last,
   564  Tendermint Core application is up and running. If not, please [open an issue on
   565  Github](https://bitbucket.org/number571/tendermint/issues/new/choose). To dig
   566  deeper, read [the docs](https://docs.tendermint.com/master/).