github.com/KYVENetwork/cometbft/v38@v38.0.3/docs/guides/go-built-in.md (about)

     1  ---
     2  order: 2
     3  ---
     4  
     5  # Creating a built-in application in Go
     6  
     7  ## Guide Assumptions
     8  
     9  This guide is designed for beginners who want to get started with a CometBFT
    10  application from scratch. It does not assume that you have any prior
    11  experience with CometBFT.
    12  
    13  CometBFT is a service that provides a Byzantine Fault Tolerant consensus engine
    14  for state-machine replication. The replicated state-machine, or "application", can be written
    15  in any language that can send and receive protocol buffer messages in a client-server model.
    16  Applications written in Go can also use CometBFT as a library and run the service in the same
    17  process as the application.
    18  
    19  By following along this tutorial you will create a CometBFT application called kvstore,
    20  a (very) simple distributed BFT key-value store.
    21  The application will be written in Go and
    22  some understanding of the Go programming language is expected.
    23  If you have never written Go, you may want to go through [Learn X in Y minutes
    24  Where X=Go](https://learnxinyminutes.com/docs/go/) first, to familiarize
    25  yourself with the syntax.
    26  
    27  Note: Please use the latest released version of this guide and of CometBFT.
    28  We strongly advise against using unreleased commits for your development.
    29  
    30  ### Built-in app vs external app
    31  
    32  On the one hand, to get maximum performance you can run your application in
    33  the same process as the CometBFT, as long as your application is written in Go.
    34  [Cosmos SDK](https://github.com/cosmos/cosmos-sdk) is written
    35  this way.
    36  This is the approach followed in this tutorial.
    37  
    38  On the other hand, having a separate application might give you better security
    39  guarantees as two processes would be communicating via established binary protocol.
    40  CometBFT will not have access to application's state.
    41  If that is the way you wish to proceed, use the [Creating an application in Go](./go.md) guide instead of this one.
    42  
    43  ## 1.1 Installing Go
    44  
    45  Verify that you have the latest version of Go installed (refer to the [official guide for installing Go](https://golang.org/doc/install)):
    46  
    47  ```bash
    48  $ go version
    49  go version go1.21.1 darwin/amd64
    50  ```
    51  
    52  ## 1.2 Creating a new Go project
    53  
    54  We'll start by creating a new Go project.
    55  
    56  ```bash
    57  mkdir kvstore
    58  ```
    59  
    60  Inside the example directory, create a `main.go` file with the following content:
    61  
    62  ```go
    63  package main
    64  
    65  import (
    66      "fmt"
    67  )
    68  
    69  func main() {
    70      fmt.Println("Hello, CometBFT")
    71  }
    72  ```
    73  
    74  When run, this should print "Hello, CometBFT" to the standard output.
    75  
    76  ```bash
    77  cd kvstore
    78  $ go run main.go
    79  Hello, CometBFT
    80  ```
    81  
    82  We are going to use [Go modules](https://github.com/golang/go/wiki/Modules) for
    83  dependency management, so let's start by including a dependency on the latest version of
    84  CometBFT, `v0.38.0` in this example.
    85  
    86  ```bash
    87  go mod init kvstore
    88  go get github.com/cometbft/cometbft@v0.38.0
    89  ```
    90  
    91  After running the above commands you will see two generated files, `go.mod` and `go.sum`.
    92  The go.mod file should look similar to:
    93  
    94  ```go
    95  module kvstore
    96  
    97  go 1.21.1
    98  
    99  require (
   100  github.com/cometbft/cometbft v0.38.0
   101  )
   102  ```
   103  
   104  XXX: CometBFT `v0.38.0` uses a slightly outdated `gogoproto` library, which
   105  may fail to compile with newer Go versions. To avoid any compilation errors,
   106  upgrade `gogoproto` manually:
   107  
   108  ```bash
   109  go get github.com/cosmos/gogoproto@v1.4.11
   110  ```
   111  
   112  As you write the kvstore application, you can rebuild the binary by
   113  pulling any new dependencies and recompiling it.
   114  
   115  ```bash
   116  go get
   117  go build
   118  ```
   119  
   120  ## 1.3 Writing a CometBFT application
   121  
   122  CometBFT communicates with the application through the Application
   123  BlockChain Interface (ABCI). The messages exchanged through the interface are
   124  defined in the ABCI [protobuf
   125  file](https://github.com/KYVENetwork/cometbft/v38/blob/v0.38.x/proto/tendermint/abci/types.proto).
   126  
   127  We begin by creating the basic scaffolding for an ABCI application by
   128  creating a new type, `KVStoreApplication`, which implements the
   129  methods defined by the `abcitypes.Application` interface.
   130  
   131  Create a file called `app.go` with the following contents:
   132  
   133  ```go
   134  package main
   135  
   136  import (
   137      abcitypes "github.com/KYVENetwork/cometbft/v38/abci/types"
   138      "context"
   139  )
   140  
   141  type KVStoreApplication struct{}
   142  
   143  var _ abcitypes.Application = (*KVStoreApplication)(nil)
   144  
   145  func NewKVStoreApplication() *KVStoreApplication {
   146      return &KVStoreApplication{}
   147  }
   148  
   149  func (app *KVStoreApplication) Info(_ context.Context, info *abcitypes.RequestInfo) (*abcitypes.ResponseInfo, error) {
   150      return &abcitypes.ResponseInfo{}, nil
   151  }
   152  
   153  func (app *KVStoreApplication) Query(_ context.Context, req *abcitypes.RequestQuery) (*abcitypes.ResponseQuery, error) {
   154      return &abcitypes.ResponseQuery{}, nil
   155  }
   156  
   157  func (app *KVStoreApplication) CheckTx(_ context.Context, check *abcitypes.RequestCheckTx) (*abcitypes.ResponseCheckTx, error) {
   158      return &abcitypes.ResponseCheckTx{}, nil
   159  }
   160  
   161  func (app *KVStoreApplication) InitChain(_ context.Context, chain *abcitypes.RequestInitChain) (*abcitypes.ResponseInitChain, error) {
   162      return &abcitypes.ResponseInitChain{}, nil
   163  }
   164  
   165  func (app *KVStoreApplication) PrepareProposal(_ context.Context, proposal *abcitypes.RequestPrepareProposal) (*abcitypes.ResponsePrepareProposal, error) {
   166      return &abcitypes.ResponsePrepareProposal{}, nil
   167  }
   168  
   169  func (app *KVStoreApplication) ProcessProposal(_ context.Context, proposal *abcitypes.RequestProcessProposal) (*abcitypes.ResponseProcessProposal, error) {
   170      return &abcitypes.ResponseProcessProposal{}, nil
   171  }
   172  
   173  func (app *KVStoreApplication) FinalizeBlock(_ context.Context, req *abcitypes.RequestFinalizeBlock) (*abcitypes.ResponseFinalizeBlock, error) {
   174      return &abcitypes.ResponseFinalizeBlock{}, nil
   175  }
   176  
   177  func (app KVStoreApplication) Commit(_ context.Context, commit *abcitypes.RequestCommit) (*abcitypes.ResponseCommit, error) {
   178      return &abcitypes.ResponseCommit{}, nil
   179  }
   180  
   181  func (app *KVStoreApplication) ListSnapshots(_ context.Context, snapshots *abcitypes.RequestListSnapshots) (*abcitypes.ResponseListSnapshots, error) {
   182      return &abcitypes.ResponseListSnapshots{}, nil
   183  }
   184  
   185  func (app *KVStoreApplication) OfferSnapshot(_ context.Context, snapshot *abcitypes.RequestOfferSnapshot) (*abcitypes.ResponseOfferSnapshot, error) {
   186      return &abcitypes.ResponseOfferSnapshot{}, nil
   187  }
   188  
   189  func (app *KVStoreApplication) LoadSnapshotChunk(_ context.Context, chunk *abcitypes.RequestLoadSnapshotChunk) (*abcitypes.ResponseLoadSnapshotChunk, error) {
   190      return &abcitypes.ResponseLoadSnapshotChunk{}, nil
   191  }
   192  
   193  func (app *KVStoreApplication) ApplySnapshotChunk(_ context.Context, chunk *abcitypes.RequestApplySnapshotChunk) (*abcitypes.ResponseApplySnapshotChunk, error) {
   194      return &abcitypes.ResponseApplySnapshotChunk{Result: abcitypes.ResponseApplySnapshotChunk_ACCEPT}, nil
   195  }
   196  
   197  func (app KVStoreApplication) ExtendVote(_ context.Context, extend *abcitypes.RequestExtendVote) (*abcitypes.ResponseExtendVote, error) {
   198      return &abcitypes.ResponseExtendVote{}, nil
   199  }
   200  
   201  func (app *KVStoreApplication) VerifyVoteExtension(_ context.Context, verify *abcitypes.RequestVerifyVoteExtension) (*abcitypes.ResponseVerifyVoteExtension, error) {
   202      return &abcitypes.ResponseVerifyVoteExtension{}, nil
   203  }
   204  ```
   205  
   206  The types used here are defined in the CometBFT library and were added as a dependency
   207  to the project when you ran `go get`. If your IDE is not recognizing the types, go ahead and run the command again.
   208  
   209  ```bash
   210  go get github.com/cometbft/cometbft@v0.38.0
   211  ```
   212  
   213  Now go back to the `main.go` and modify the `main` function so it matches the following,
   214  where an instance of the `KVStoreApplication` type is created.
   215  
   216  ```go
   217  func main() {
   218      fmt.Println("Hello, CometBFT")
   219  
   220      _ = NewKVStoreApplication()
   221  }
   222  ```
   223  
   224  You can recompile and run the application now by running `go get` and `go build`, but it does
   225  not do anything.
   226  So let's revisit the code adding the logic needed to implement our minimal key/value store
   227  and to start it along with the CometBFT Service.
   228  
   229  ### 1.3.1 Add a persistent data store
   230  
   231  Our application will need to write its state out to persistent storage so that it
   232  can stop and start without losing all of its data.
   233  
   234  For this tutorial, we will use [BadgerDB](https://github.com/dgraph-io/badger), a
   235  fast embedded key-value store.
   236  
   237  First, add Badger as a dependency of your go module using the `go get` command:
   238  
   239  `go get github.com/dgraph-io/badger/v3`
   240  
   241  Next, let's update the application and its constructor to receive a handle to the database, as follows:
   242  
   243  ```go
   244  type KVStoreApplication struct {
   245      db           *badger.DB
   246      onGoingBlock *badger.Txn
   247  }
   248  
   249  var _ abcitypes.Application = (*KVStoreApplication)(nil)
   250  
   251  func NewKVStoreApplication(db *badger.DB) *KVStoreApplication {
   252      return &KVStoreApplication{db: db}
   253  }
   254  ```
   255  
   256  The `onGoingBlock` keeps track of the Badger transaction that will update the application's state when a block
   257  is completed. Don't worry about it for now, we'll get to that later.
   258  
   259  Next, update the `import` stanza at the top to include the Badger library:
   260  
   261  ```go
   262  import(
   263      "github.com/dgraph-io/badger/v3"
   264      abcitypes "github.com/KYVENetwork/cometbft/v38/abci/types"
   265  )
   266  ```
   267  
   268  Finally, update the `main.go` file to invoke the updated constructor:
   269  
   270  ```go
   271      _ = NewKVStoreApplication(nil)
   272  ```
   273  
   274  ### 1.3.2 CheckTx
   275  
   276  When CometBFT receives a new transaction from a client, or from another full node,
   277  CometBFT asks the application if the transaction is acceptable, using the `CheckTx` method.
   278  Invalid transactions will not be shared with other nodes and will not become part of any blocks and, therefore, will not be executed by the application.
   279  
   280  In our application, a transaction is a string with the form `key=value`, indicating a key and value to write to the store.
   281  
   282  The most basic validation check we can perform is to check if the transaction conforms to the `key=value` pattern.
   283  For that, let's add the following helper method to app.go:
   284  
   285  ```go
   286  func (app *KVStoreApplication) isValid(tx []byte) uint32 {
   287      // check format
   288      parts := bytes.Split(tx, []byte("="))
   289      if len(parts) != 2 {
   290          return 1
   291      }
   292  
   293      return 0
   294  }
   295  ```
   296  
   297  Now you can rewrite the `CheckTx` method to use the helper function:
   298  
   299  ```go
   300  func (app *KVStoreApplication) CheckTx(_ context.Context, check *abcitypes.RequestCheckTx) (*abcitypes.ResponseCheckTx, error) {
   301      code := app.isValid(check.Tx)
   302      return &abcitypes.ResponseCheckTx{Code: code}, nil
   303  }
   304  ```
   305  
   306  While this `CheckTx` is simple and only validates that the transaction is well-formed,
   307  it is very common for `CheckTx` to make more complex use of the state of an application.
   308  For example, you may refuse to overwrite an existing value, or you can associate
   309  versions to the key/value pairs and allow the caller to specify a version to
   310  perform a conditional update.
   311  
   312  Depending on the checks and on the conditions violated, the function may return
   313  different values, but any response with a non-zero code will be considered invalid
   314  by CometBFT. Our `CheckTx` logic returns 0 to CometBFT when a transaction passes
   315  its validation checks. The specific value of the code is meaningless to CometBFT.
   316  Non-zero codes are logged by CometBFT so applications can provide more specific
   317  information on why the transaction was rejected.
   318  
   319  Note that `CheckTx` does not execute the transaction, it only verifies 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.
   320  
   321  Finally, make sure to add the `bytes` package to the `import` stanza at the top of `app.go`:
   322  
   323  ```go
   324  import(
   325      "bytes"
   326  
   327      "github.com/dgraph-io/badger/v3"
   328      abcitypes "github.com/KYVENetwork/cometbft/v38/abci/types"
   329  )
   330  ```
   331  
   332  ### 1.3.3 FinalizeBlock
   333  
   334  When the CometBFT consensus engine has decided on the block, the block is transferred to the
   335  application via `FinalizeBlock`.
   336  `FinalizeBlock` is an ABCI method introduced in CometBFT `v0.38.0`. This replaces the functionality provided previously (pre-`v0.38.0`) by the combination of ABCI methods `BeginBlock`, `DeliverTx`, and `EndBlock`. `FinalizeBlock`'s parameters are an aggregation of those in `BeginBlock`, `DeliverTx`, and `EndBlock`.
   337  
   338  This method is responsible for executing the block and returning a response to the consensus engine.
   339  Providing a single `FinalizeBlock` method to signal the finalization of a block simplifies the ABCI interface and increases flexibility in the execution pipeline.
   340  
   341  The `FinalizeBlock` method executes the block, including any necessary transaction processing and state updates, and returns a `ResponseFinalizeBlock` object which contains any necessary information about the executed block.
   342  
   343  **Note:** `FinalizeBlock` only prepares the update to be made and does not change the state of the application. The state change is actually committed in a later stage i.e. in `commit` phase.
   344  
   345  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 CometBFT, the _application transactions_.
   346  
   347  First, let's create a new Badger transaction during `FinalizeBlock`. All application transactions in the current block will be executed within this Badger transaction.
   348  Next, let's modify `FinalizeBlock` to add the `key` and `value` to the Badger transaction every time our application processes a new application transaction from the list received through `RequestFinalizeBlock`.
   349  
   350  Note that we check the validity of the transaction _again_ during `FinalizeBlock`.
   351  
   352  ```go
   353  func (app *KVStoreApplication) FinalizeBlock(_ context.Context, req *abcitypes.RequestFinalizeBlock) (*abcitypes.ResponseFinalizeBlock, error) {
   354      var txs = make([]*abcitypes.ExecTxResult, len(req.Txs))
   355  
   356      app.onGoingBlock = app.db.NewTransaction(true)
   357      for i, tx := range req.Txs {
   358          if code := app.isValid(tx); code != 0 {
   359              log.Printf("Error: invalid transaction index %v", i)
   360              txs[i] = &abcitypes.ExecTxResult{Code: code}
   361          } else {
   362              parts := bytes.SplitN(tx, []byte("="), 2)
   363              key, value := parts[0], parts[1]
   364              log.Printf("Adding key %s with value %s", key, value)
   365  
   366              if err := app.onGoingBlock.Set(key, value); err != nil {
   367                  log.Panicf("Error writing to database, unable to execute tx: %v", err)
   368              }
   369  
   370              log.Printf("Successfully added key %s with value %s", key, value)
   371  
   372              txs[i] = &abcitypes.ExecTxResult{}
   373          }
   374      }
   375  
   376      return &abcitypes.ResponseFinalizeBlock{
   377        TxResults:        txs,
   378      }, nil
   379  }
   380  ```
   381  
   382  Transactions are not guaranteed to be valid when they are delivered to an application, even if they were valid when they were proposed.
   383  
   384  This can happen if the application state is used to determine transaction validity.
   385  The application state may have changed between the initial execution of `CheckTx` and the transaction delivery in `FinalizeBlock` in a way that rendered the transaction no longer valid.
   386  
   387  **Note** that `FinalizeBlock` cannot yet commit the Badger transaction we were building during the block execution.
   388  
   389  Other methods, such as `Query`, rely on a consistent view of the application's state, the application should only update its state by committing the Badger transactions when the full block has been delivered and the Commit method is invoked.
   390  
   391  The `Commit` method tells the application to make permanent the effects of
   392  the application transactions.
   393  Let's update the method to terminate the pending Badger transaction and
   394  persist the resulting state:
   395  
   396  ```go
   397  func (app KVStoreApplication) Commit(_ context.Context, commit *abcitypes.RequestCommit) (*abcitypes.ResponseCommit, error) {
   398      return &abcitypes.ResponseCommit{}, app.onGoingBlock.Commit()
   399  }
   400  ```
   401  
   402  Finally, make sure to add the log library to the `import` stanza as well:
   403  
   404  ```go
   405  import (
   406      "bytes"
   407      "log"
   408  
   409      "github.com/dgraph-io/badger/v3"
   410      abcitypes "github.com/KYVENetwork/cometbft/v38/abci/types"
   411  )
   412  ```
   413  
   414  You may have noticed that the application we are writing will crash if it receives
   415  an unexpected error from the Badger database during the `FinalizeBlock` or `Commit` methods.
   416  This is not an accident. If the application received an error from the database, there
   417  is no deterministic way for it to make progress so the only safe option is to terminate.
   418  Once the application is restarted, the transactions in the block that failed execution will
   419  be re-executed and should succeed if the Badger error was transient.
   420  
   421  ### 1.3.4 Query
   422  
   423  When a client tries to read some information from the `kvstore`, the request will be
   424  handled in the `Query` method. To do this, let's rewrite the `Query` method in `app.go`:
   425  
   426  ```go
   427  func (app *KVStoreApplication) Query(_ context.Context, req *abcitypes.RequestQuery) (*abcitypes.ResponseQuery, error) {
   428      resp := abcitypes.ResponseQuery{Key: req.Data}
   429  
   430      dbErr := app.db.View(func(txn *badger.Txn) error {
   431          item, err := txn.Get(req.Data)
   432          if err != nil {
   433              if err != badger.ErrKeyNotFound {
   434                  return err
   435              }
   436              resp.Log = "key does not exist"
   437              return nil
   438          }
   439  
   440          return item.Value(func(val []byte) error {
   441              resp.Log = "exists"
   442              resp.Value = val
   443              return nil
   444          })
   445      })
   446      if dbErr != nil {
   447          log.Panicf("Error reading database, unable to execute query: %v", dbErr)
   448      }
   449      return &resp, nil
   450  }
   451  ```
   452  
   453  Since it reads only committed data from the store, transactions that are part of a block
   454  that is being processed are not reflected in the query result.
   455  
   456  ### 1.3.5 PrepareProposal and ProcessProposal
   457  
   458  `PrepareProposal` and `ProcessProposal` are methods introduced in CometBFT v0.37.0
   459  to give the application more control over the construction and processing of transaction blocks.
   460  
   461  When CometBFT sees that valid transactions (validated through `CheckTx`) are available to be
   462  included in blocks, it groups some of these transactions and then gives the application a chance
   463  to modify the group by invoking `PrepareProposal`.
   464  
   465  The application is free to modify the group before returning from the call, as long as the resulting set
   466  does not use more bytes than `RequestPrepareProposal.max_tx_bytes`
   467  For example, the application may reorder, add, or even remove transactions from the group to improve the
   468  execution of the block once accepted.
   469  
   470  In the following code, the application simply returns the unmodified group of transactions:
   471  
   472  ```go
   473  func (app *KVStoreApplication) PrepareProposal(_ context.Context, proposal *abcitypes.RequestPrepareProposal) (*abcitypes.ResponsePrepareProposal, error) {
   474      return &abcitypes.ResponsePrepareProposal{Txs: proposal.Txs}, nil
   475  }
   476  ```
   477  
   478  Once a proposed block is received by a node, the proposal is passed to the application to give
   479  its blessing before voting to accept the proposal.
   480  
   481  This mechanism may be used for different reasons, for example to deal with blocks manipulated
   482  by malicious nodes, in which case the block should not be considered valid.
   483  
   484  The following code simply accepts all proposals:
   485  
   486  ```go
   487  func (app *KVStoreApplication) ProcessProposal(_ context.Context, proposal *abcitypes.RequestProcessProposal) (*abcitypes.ResponseProcessProposal, error) {
   488      return &abcitypes.ResponseProcessProposal{Status: abcitypes.ResponseProcessProposal_ACCEPT}, nil
   489  }
   490  ```
   491  
   492  ## 1.4 Starting an application and a CometBFT instance in the same process
   493  
   494  Now that we have the basic functionality of our application in place, let's put
   495  it all together inside of our `main.go` file.
   496  
   497  Change the contents of your `main.go` file to the following.
   498  
   499  ```go
   500  package main
   501  
   502  import (
   503      "flag"
   504      "fmt"
   505      "github.com/KYVENetwork/cometbft/v38/p2p"
   506      "github.com/KYVENetwork/cometbft/v38/privval"
   507      "github.com/KYVENetwork/cometbft/v38/proxy"
   508      "log"
   509      "os"
   510      "os/signal"
   511      "path/filepath"
   512      "syscall"
   513  
   514      "github.com/dgraph-io/badger/v3"
   515      "github.com/spf13/viper"
   516      cfg "github.com/KYVENetwork/cometbft/v38/config"
   517      cmtflags "github.com/KYVENetwork/cometbft/v38/libs/cli/flags"
   518      cmtlog "github.com/KYVENetwork/cometbft/v38/libs/log"
   519      nm "github.com/KYVENetwork/cometbft/v38/node"
   520  )
   521  
   522  var homeDir string
   523  
   524  func init() {
   525      flag.StringVar(&homeDir, "cmt-home", "", "Path to the CometBFT config directory (if empty, uses $HOME/.cometbft)")
   526  }
   527  
   528  func main() {
   529      flag.Parse()
   530      if homeDir == "" {
   531          homeDir = os.ExpandEnv("$HOME/.cometbft")
   532      }
   533  
   534      config := cfg.DefaultConfig()
   535      config.SetRoot(homeDir)
   536      viper.SetConfigFile(fmt.Sprintf("%s/%s", homeDir, "config/config.toml"))
   537  
   538      if err := viper.ReadInConfig(); err != nil {
   539          log.Fatalf("Reading config: %v", err)
   540      }
   541      if err := viper.Unmarshal(config); err != nil {
   542          log.Fatalf("Decoding config: %v", err)
   543      }
   544      if err := config.ValidateBasic(); err != nil {
   545          log.Fatalf("Invalid configuration data: %v", err)
   546      }
   547      dbPath := filepath.Join(homeDir, "badger")
   548      db, err := badger.Open(badger.DefaultOptions(dbPath))
   549  
   550      if err != nil {
   551          log.Fatalf("Opening database: %v", err)
   552      }
   553      defer func() {
   554          if err := db.Close(); err != nil {
   555              log.Printf("Closing database: %v", err)
   556          }
   557      }()
   558  
   559      app := NewKVStoreApplication(db)
   560  
   561      pv := privval.LoadFilePV(
   562          config.PrivValidatorKeyFile(),
   563          config.PrivValidatorStateFile(),
   564      )
   565  
   566      nodeKey, err := p2p.LoadNodeKey(config.NodeKeyFile())
   567      if err != nil {
   568          log.Fatalf("failed to load node's key: %v", err)
   569      }
   570  
   571      logger := cmtlog.NewTMLogger(cmtlog.NewSyncWriter(os.Stdout))
   572      logger, err = cmtflags.ParseLogLevel(config.LogLevel, logger, cfg.DefaultLogLevel)
   573  
   574      if err != nil {
   575          log.Fatalf("failed to parse log level: %v", err)
   576      }
   577  
   578      node, err := nm.NewNode(
   579          config,
   580          pv,
   581          nodeKey,
   582          proxy.NewLocalClientCreator(app),
   583          nm.DefaultGenesisDocProviderFunc(config),
   584          nm.DefaultDBProvider,
   585          nm.DefaultMetricsProvider(config.Instrumentation),
   586          logger,
   587      )
   588  
   589      if err != nil {
   590          log.Fatalf("Creating node: %v", err)
   591      }
   592  
   593      node.Start()
   594      defer func() {
   595          node.Stop()
   596          node.Wait()
   597      }()
   598  
   599      c := make(chan os.Signal, 1)
   600      signal.Notify(c, os.Interrupt, syscall.SIGTERM)
   601      <-c
   602  }
   603  ```
   604  
   605  This is a huge blob of code, so let's break it down into pieces.
   606  
   607  First, we use [viper](https://github.com/spf13/viper) to load the CometBFT configuration files, which we will generate later:
   608  
   609  ```go
   610  config := cfg.DefaultValidatorConfig()
   611  
   612  config.SetRoot(homeDir)
   613  
   614  viper.SetConfigFile(fmt.Sprintf("%s/%s", homeDir, "config/config.toml"))
   615  if err := viper.ReadInConfig(); err != nil {
   616      log.Fatalf("Reading config: %v", err)
   617  }
   618  if err := viper.Unmarshal(config); err != nil {
   619      log.Fatalf("Decoding config: %v", err)
   620  }
   621  if err := config.ValidateBasic(); err != nil {
   622      log.Fatalf("Invalid configuration data: %v", err)
   623  }
   624  ```
   625  
   626  Next, we initialize the Badger database and create an app instance.
   627  
   628  ```go
   629  dbPath := filepath.Join(homeDir, "badger")
   630  db, err := badger.Open(badger.DefaultOptions(dbPath))
   631  if err != nil {
   632      log.Fatalf("Opening database: %v", err)
   633  }
   634  defer func() {
   635      if err := db.Close(); err != nil {
   636          log.Fatalf("Closing database: %v", err)
   637      }
   638  }()
   639  
   640  app := NewKVStoreApplication(db)
   641  ```
   642  
   643  We use `FilePV`, which is a private validator (i.e. thing which signs consensus
   644  messages). Normally, you would use `SignerRemote` to connect to an external
   645  [HSM](https://kb.certus.one/hsm.html).
   646  
   647  ```go
   648  pv := privval.LoadFilePV(
   649      config.PrivValidatorKeyFile(),
   650      config.PrivValidatorStateFile(),
   651  )
   652  ```
   653  
   654  `nodeKey` is needed to identify the node in a p2p network.
   655  
   656  ```go
   657  nodeKey, err := p2p.LoadNodeKey(config.NodeKeyFile())
   658  if err != nil {
   659      return nil, fmt.Errorf("failed to load node's key: %w", err)
   660  }
   661  ```
   662  
   663  Now we have everything set up to run the CometBFT node. We construct
   664  a node by passing it the configuration, the logger, a handle to our application and
   665  the genesis information:
   666  
   667  ```go
   668  node, err := nm.NewNode(
   669      config,
   670      pv,
   671      nodeKey,
   672      proxy.NewLocalClientCreator(app),
   673      nm.DefaultGenesisDocProviderFunc(config),
   674      nm.DefaultDBProvider,
   675      nm.DefaultMetricsProvider(config.Instrumentation),
   676      logger)
   677  
   678  if err != nil {
   679      log.Fatalf("Creating node: %v", err)
   680  }
   681  ```
   682  
   683  Finally, we start the node, i.e., the CometBFT service inside our application:
   684  
   685  ```go
   686  node.Start()
   687  defer func() {
   688      node.Stop()
   689      node.Wait()
   690  }()
   691  ```
   692  
   693  The additional logic at the end of the file allows the program to catch SIGTERM. This means that the node can shut down gracefully when an operator tries to kill the program:
   694  
   695  ```go
   696  c := make(chan os.Signal, 1)
   697  signal.Notify(c, os.Interrupt, syscall.SIGTERM)
   698  <-c
   699  ```
   700  
   701  ## 1.5 Initializing and Running
   702  
   703  Our application is almost ready to run, but first we'll need to populate the CometBFT configuration files.
   704  The following command will create a `cometbft-home` directory in your project and add a basic set of configuration files in `cometbft-home/config/`.
   705  For more information on what these files contain see [the configuration documentation](https://github.com/KYVENetwork/cometbft/v38/blob/v0.38.x/docs/core/configuration.md).
   706  
   707  From the root of your project, run:
   708  
   709  ```bash
   710  go run github.com/KYVENetwork/cometbft/v38/cmd/cometbft@v0.38.0 init --home /tmp/cometbft-home
   711  ```
   712  
   713  You should see an output similar to the following:
   714  
   715  ```bash
   716  I[2023-25-04|09:06:34.444] Generated private validator                  module=main keyFile=/tmp/cometbft-home/config/priv_validator_key.json stateFile=/tmp/cometbft-home/data/priv_validator_state.json
   717  I[2023-25-04|09:06:34.444] Generated node key                           module=main path=/tmp/cometbft-home/config/node_key.json
   718  I[2023-25-04|09:06:34.444] Generated genesis file                       module=main path=/tmp/cometbft-home/config/genesis.json
   719  ```
   720  
   721  Now rebuild the app:
   722  
   723  ```bash
   724  go build -mod=mod # use -mod=mod to automatically refresh the dependencies
   725  ```
   726  
   727  Everything is now in place to run your application. Run:
   728  
   729  ```bash
   730  ./kvstore -cmt-home /tmp/cometbft-home
   731  ```
   732  
   733  The application will start and you should see a continuous output starting with:
   734  
   735  ```bash
   736  badger 2023-04-25 09:08:50 INFO: All 0 tables opened in 0s
   737  badger 2023-04-25 09:08:50 INFO: Discard stats nextEmptySlot: 0
   738  badger 2023-04-25 09:08:50 INFO: Set nextTxnTs to 0
   739  I[2023-04-25|09:08:50.085] service start                                module=proxy msg="Starting multiAppConn service" impl=multiAppConn
   740  I[2023-04-25|09:08:50.085] service start                                module=abci-client connection=query msg="Starting localClient service" impl=localClient
   741  I[2023-04-25|09:08:50.085] service start                                module=abci-client connection=snapshot msg="Starting localClient service" impl=localClient
   742  ...
   743  ```
   744  
   745  More importantly, the application using CometBFT is producing blocks  🎉🎉 and you can see this reflected in the log output in lines like this:
   746  
   747  ```bash
   748  I[2023-04-25|09:08:52.147] received proposal                            module=consensus proposal="Proposal{2/0 (F518444C0E348270436A73FD0F0B9DFEA758286BEB29482F1E3BEA75330E825C:1:C73D3D1273F2, -1) AD19AE292A45 @ 2023-04-25T12:08:52.143393Z}"
   749  I[2023-04-25|09:08:52.152] received complete proposal block             module=consensus height=2 hash=F518444C0E348270436A73FD0F0B9DFEA758286BEB29482F1E3BEA75330E825C
   750  I[2023-04-25|09:08:52.160] finalizing commit of block                   module=consensus height=2 hash=F518444C0E348270436A73FD0F0B9DFEA758286BEB29482F1E3BEA75330E825C root= num_txs=0
   751  I[2023-04-25|09:08:52.167] executed block                               module=state height=2 num_valid_txs=0 num_invalid_txs=0
   752  I[2023-04-25|09:08:52.171] committed state                              module=state height=2 num_txs=0 app_hash=
   753  ```
   754  
   755  The blocks, as you can see from the `num_valid_txs=0` part, are empty, but let's remedy that next.
   756  
   757  ## 1.6 Using the application
   758  
   759  Let's try submitting a transaction to our new application.
   760  Open another terminal window and run the following curl command:
   761  
   762  ```bash
   763  curl -s 'localhost:26657/broadcast_tx_commit?tx="cometbft=rocks"'
   764  ```
   765  
   766  If everything went well, you should see a response indicating which height the
   767  transaction was included in the blockchain.
   768  
   769  Finally, let's make sure that transaction really was persisted by the application.
   770  Run the following command:
   771  
   772  ```bash
   773  curl -s 'localhost:26657/abci_query?data="cometbft"'
   774  ```
   775  
   776  Let's examine the response object that this request returns.
   777  The request returns a `json` object with a `key` and `value` field set.
   778  
   779  ```json
   780  ...
   781      "key": "dGVuZGVybWludA==",
   782      "value": "cm9ja3M=",
   783  ...
   784  ```
   785  
   786  Those values don't look like the `key` and `value` we sent to CometBFT.
   787  What's going on here?
   788  
   789  The response contains a `base64` encoded representation of the data we submitted.
   790  To get the original value out of this data, we can use the `base64` command line utility:
   791  
   792  ```bash
   793  echo "cm9ja3M=" | base64 -d
   794  ```
   795  
   796  ## Outro
   797  
   798  Hope you could run everything smoothly. If you have any difficulties running through this tutorial, reach out to us via [discord](https://discord.com/invite/cosmosnetwork) or open a new [issue](https://github.com/KYVENetwork/cometbft/v38/issues/new/choose) on Github.