github.com/badrootd/celestia-core@v0.0.0-20240305091328-aa4207a4b25d/docs/guides/go.md (about)

     1  ---
     2  order: 4
     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 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  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.
    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  This is the approach followed in this tutorial.
    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.20.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 this version of
    84  CometBFT.
    85  
    86  ```bash
    87  go mod init kvstore
    88  go get github.com/cometbft/cometbft@v0.34.27
    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 github.com/me/example
    96  
    97  go 1.20
    98  
    99  require (
   100  	github.com/cometbft/cometbft v0.34.27
   101  )
   102  ```
   103  
   104  As you write the kvstore application, you can rebuild the binary by
   105  pulling any new dependencies and recompiling it.
   106  
   107  ```sh
   108  go get
   109  go build
   110  ```
   111  
   112  
   113  ## 1.3 Writing a CometBFT application
   114  
   115  CometBFT communicates with the application through the Application
   116  BlockChain Interface (ABCI). The messages exchanged through the interface are
   117  defined in the ABCI [protobuf
   118  file](https://github.com/cometbft/cometbft/blob/v0.34.x/proto/tendermint/abci/types.proto).
   119  
   120  We begin by creating the basic scaffolding for an ABCI application by
   121  creating a new type, `KVStoreApplication`, which implements the
   122  methods defined by the `abcitypes.Application` interface.
   123  
   124  Create a file called `app.go` with the following contents:
   125  
   126  ```go
   127  package main
   128  
   129  import (
   130  	abcitypes "github.com/cometbft/cometbft/abci/types"
   131  )
   132  
   133  type KVStoreApplication struct{}
   134  
   135  var _ abcitypes.Application = (*KVStoreApplication)(nil)
   136  
   137  func NewKVStoreApplication() *KVStoreApplication {
   138  	return &KVStoreApplication{}
   139  }
   140  
   141  func (app *KVStoreApplication) Info(info abcitypes.RequestInfo) abcitypes.ResponseInfo {
   142  	return abcitypes.ResponseInfo{}
   143  }
   144  
   145  func (app *KVStoreApplication) Query(query abcitypes.RequestQuery) abcitypes.ResponseQuery {
   146  	return abcitypes.ResponseQuery{}
   147  }
   148  
   149  func (app *KVStoreApplication) CheckTx(tx abcitypes.RequestCheckTx) abcitypes.ResponseCheckTx {
   150  	return abcitypes.ResponseCheckTx{}
   151  }
   152  
   153  func (app *KVStoreApplication) InitChain(chain abcitypes.RequestInitChain) abcitypes.ResponseInitChain {
   154  	return abcitypes.ResponseInitChain{}
   155  }
   156  
   157  func (app *KVStoreApplication) BeginBlock(block abcitypes.RequestBeginBlock) abcitypes.ResponseBeginBlock {
   158  	return abcitypes.ResponseBeginBlock{}
   159  }
   160  
   161  func (app *KVStoreApplication) DeliverTx(tx abcitypes.RequestDeliverTx) abcitypes.ResponseDeliverTx {
   162  	return abcitypes.ResponseDeliverTx{}
   163  }
   164  
   165  func (app *KVStoreApplication) EndBlock(block abcitypes.RequestEndBlock) abcitypes.ResponseEndBlock {
   166  	return abcitypes.ResponseEndBlock{}
   167  }
   168  
   169  func (app *KVStoreApplication) Commit() abcitypes.ResponseCommit {
   170  	return abcitypes.ResponseCommit{}
   171  }
   172  
   173  func (app *KVStoreApplication) ListSnapshots(snapshots abcitypes.RequestListSnapshots) abcitypes.ResponseListSnapshots {
   174  	return abcitypes.ResponseListSnapshots{}
   175  }
   176  
   177  func (app *KVStoreApplication) OfferSnapshot(snapshot abcitypes.RequestOfferSnapshot) abcitypes.ResponseOfferSnapshot {
   178  	return abcitypes.ResponseOfferSnapshot{}
   179  }
   180  
   181  func (app *KVStoreApplication) LoadSnapshotChunk(chunk abcitypes.RequestLoadSnapshotChunk) abcitypes.ResponseLoadSnapshotChunk {
   182  	return abcitypes.ResponseLoadSnapshotChunk{}
   183  }
   184  
   185  func (app *KVStoreApplication) ApplySnapshotChunk(chunk abcitypes.RequestApplySnapshotChunk) abcitypes.ResponseApplySnapshotChunk {
   186  	return abcitypes.ResponseApplySnapshotChunk{}
   187  }
   188  ```
   189  
   190  The types used here are defined in the CometBFT library and were added as a dependency
   191  to the project when you ran `go get`. If your IDE is not recognizing the types, go ahead and run the command again.
   192  
   193  ```bash
   194  go get github.com/cometbft/cometbft@v0.34.27
   195  ```
   196  
   197  Now go back to the `main.go` and modify the `main` function so it matches the following,
   198  where an instance of the `KVStoreApplication` type is created.
   199  
   200  ```go
   201  func main() {
   202      fmt.Println("Hello, CometBFT")
   203  
   204      _ = NewKVStoreApplication()
   205  }
   206  ```
   207  
   208  You can recompile and run the application now by running `go get` and `go build`, but it does
   209  not do anything.
   210  So let's revisit the code adding the logic needed to implement our minimal key/value store
   211  and to start it along with the CometBFT Service.
   212  
   213  
   214  ### 1.3.1 Add a persistent data store
   215  
   216  Our application will need to write its state out to persistent storage so that it
   217  can stop and start without losing all of its data.
   218  
   219  For this tutorial, we will use [BadgerDB](https://github.com/dgraph-io/badger), a
   220  a fast embedded key-value store.
   221  
   222  First, add Badger as a dependency of your go module using the `go get` command:
   223  
   224  `go get github.com/dgraph-io/badger/v3`
   225  
   226  Next, let's update the application and its constructor to receive a handle to the database, as follows:
   227  
   228  ```go
   229  type KVStoreApplication struct {
   230  	db           *badger.DB
   231  	onGoingBlock *badger.Txn
   232  }
   233  
   234  var _ abcitypes.Application = (*KVStoreApplication)(nil)
   235  
   236  func NewKVStoreApplication(db *badger.DB) *KVStoreApplication {
   237  	return &KVStoreApplication{db: db}
   238  }
   239  ```
   240  
   241  The `onGoingBlock` keeps track of the Badger transaction that will update the application's state when a block
   242  is completed. Don't worry about it for now, we'll get to that later.
   243  
   244  Next, update the `import` stanza at the top to include the Badger library:
   245  
   246  ```go
   247  import(
   248  	"github.com/dgraph-io/badger/v3"
   249  	abcitypes "github.com/cometbft/cometbft/abci/types"
   250  )
   251  ```
   252  
   253  Finally, update the `main.go` file to invoke the updated constructor:
   254  
   255  ```go
   256  	_ = NewKVStoreApplication(nil)
   257  ```
   258  
   259  ### 1.3.2 CheckTx
   260  
   261  When CometBFT receives a new transaction from a client, or from another full node,
   262  CometBFT asks the application if the transaction is acceptable, using the `CheckTx` method.
   263  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.
   264  
   265  In our application, a transaction is a string with the form `key=value`, indicating a key and value to write to the store.
   266  
   267  The most basic validation check we can perform is to check if the transaction conforms to the `key=value` pattern.
   268  For that, let's add the following helper method to app.go:
   269  
   270  ```go
   271  func (app *KVStoreApplication) isValid(tx []byte) uint32 {
   272  	// check format
   273  	parts := bytes.Split(tx, []byte("="))
   274  	if len(parts) != 2 {
   275  		return 1
   276  	}
   277  
   278  	return 0
   279  }
   280  ```
   281  
   282  Now you can rewrite the `CheckTx` method to use the helper function:
   283  
   284  ```go
   285  func (app *KVStoreApplication) CheckTx(req abcitypes.RequestCheckTx) abcitypes.ResponseCheckTx {
   286  	code := app.isValid(req.Tx)
   287  	return abcitypes.ResponseCheckTx{Code: code}
   288  }
   289  ```
   290  
   291  While this `CheckTx` is simple and only validates that the transaction is well-formed,
   292  it is very common for `CheckTx` to make more complex use of the state of an application.
   293  For example, you may refuse to overwrite an existing value, or you can associate
   294  versions to the key/value pairs and allow the caller to specify a version to
   295  perform a conditional update.
   296  
   297  Depending on the checks and on the conditions violated, the function may return
   298  different values, but any response with a non-zero code will be considered invalid
   299  by CometBFT. Our `CheckTx` logic returns 0 to CometBFT when a transaction passes
   300  its validation checks. The specific value of the code is meaningless to CometBFT.
   301  Non-zero codes are logged by CometBFT so applications can provide more specific
   302  information on why the transaction was rejected.
   303  
   304  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.
   305  
   306  
   307  Finally, make sure to add the bytes package to the `import` stanza at the top of `app.go`:
   308  
   309  ```go
   310  import(
   311  	"bytes"
   312  
   313  	"github.com/dgraph-io/badger/v3"
   314  	abcitypes "github.com/cometbft/cometbft/abci/types"
   315  )
   316  ```
   317  
   318  
   319  ### 1.3.3 BeginBlock -> DeliverTx -> EndBlock -> Commit
   320  
   321  When the CometBFT consensus engine has decided on the block, the block is transferred to the
   322  application over three ABCI method calls: `BeginBlock`, `DeliverTx`, and `EndBlock`.
   323  
   324  - `BeginBlock` is called once to indicate to the application that it is about to
   325  receive a block.
   326  - `DeliverTx` is called repeatedly, once for each application transaction that was included in the block.
   327  - `EndBlock` is called once to indicate to the application that no more transactions
   328  will be delivered to the application in within this block.
   329  
   330  Note that, to implement these calls in our application we're going to make use of Badger's
   331  transaction mechanism. We will always refer to these as Badger transactions, not to
   332  confuse them with the transactions included in the blocks delivered by CometBFT,
   333  the _application transactions_.
   334  
   335  First, let's create a new Badger transaction during `BeginBlock`. All application transactions in the
   336  current block will be executed within this Badger transaction.
   337  Then, return informing CometBFT that the application is ready to receive application transactions:
   338  
   339  ```go
   340  func (app *KVStoreApplication) BeginBlock(req abcitypes.RequestBeginBlock) abcitypes.ResponseBeginBlock {
   341  	app.onGoingBlock = app.db.NewTransaction(true)
   342  	return abcitypes.ResponseBeginBlock{}
   343  }
   344  ```
   345  
   346  Next, let's modify `DeliverTx` to add the `key` and `value` to the database transaction every time our application
   347  receives a new application transaction through `RequestDeliverTx`.
   348  
   349  ```go
   350  func (app *KVStoreApplication) DeliverTx(req abcitypes.RequestDeliverTx) abcitypes.ResponseDeliverTx {
   351  	if code := app.isValid(req.Tx); code != 0 {
   352  		return abcitypes.ResponseDeliverTx{Code: code}
   353  	}
   354  
   355  	parts := bytes.SplitN(req.Tx, []byte("="), 2)
   356  	key, value := parts[0], parts[1]
   357  
   358  	if err := app.onGoingBlock.Set(key, value); err != nil {
   359  		log.Panicf("Error writing to database, unable to execute tx: %v", err)
   360  	}
   361  
   362  	return abcitypes.ResponseDeliverTx{Code: 0}
   363  }
   364  ```
   365  
   366  Note that we check the validity of the transaction _again_ during `DeliverTx`.
   367  Transactions are not guaranteed to be valid when they are delivered to an
   368  application, even if they were valid when they were proposed.
   369  This can happen if the application state is used to determine transaction
   370  validity. Application state may have changed between the initial execution of `CheckTx`
   371  and the transaction delivery in `DeliverTx` in a way that rendered the transaction
   372  no longer valid.
   373  
   374  `EndBlock` is called to inform the application that the full block has been delivered
   375  and give the application a chance to perform any other computation needed, before the
   376  effects of the transactions become permanent.
   377  
   378  Note that `EndBlock` **cannot** yet commit the Badger transaction we were building
   379  in during `DeliverTx`.
   380  Since other methods, such as `Query`, rely on a consistent view of the application's
   381  state, the application should only update its state by committing the Badger transactions
   382  when the full block has been delivered and the `Commit` method is invoked.
   383  
   384  The `Commit` method tells the application to make permanent the effects of
   385  the application transactions.
   386  Let's update the method to terminate the pending Badger transaction and
   387  persist the resulting state:
   388  
   389  ```go
   390  func (app *KVStoreApplication) Commit() abcitypes.ResponseCommit {
   391  	if err := app.onGoingBlock.Commit(); err != nil {
   392  		log.Panicf("Error writing to database, unable to commit block: %v", err)
   393  	}
   394  	return abcitypes.ResponseCommit{Data: []byte{}}
   395  }
   396  ```
   397  
   398  Finally, make sure to add the log library to the `import` stanza as well:
   399  
   400  ```go
   401  import (
   402  	"bytes"
   403  	"log"
   404  
   405  	"github.com/dgraph-io/badger/v3"
   406  	abcitypes "github.com/cometbft/cometbft/abci/types"
   407  )
   408  ```
   409  
   410  You may have noticed that the application we are writing will crash if it receives
   411  an unexpected error from the Badger database during the `DeliverTx` or `Commit` methods.
   412  This is not an accident. If the application received an error from the database, there
   413  is no deterministic way for it to make progress so the only safe option is to terminate.
   414  
   415  ### 1.3.4 Query
   416  
   417  When a client tries to read some information from the `kvstore`, the request will be
   418  handled in the `Query` method. To do this, let's rewrite the `Query` method in `app.go`:
   419  
   420  ```go
   421  func (app *KVStoreApplication) Query(req abcitypes.RequestQuery) abcitypes.ResponseQuery {
   422  	resp := abcitypes.ResponseQuery{Key: req.Data}
   423  
   424  	dbErr := app.db.View(func(txn *badger.Txn) error {
   425  		item, err := txn.Get(req.Data)
   426  		if err != nil {
   427  			if err != badger.ErrKeyNotFound {
   428  				return err
   429  			}
   430  			resp.Log = "key does not exist"
   431  			return nil
   432  		}
   433  
   434  		return item.Value(func(val []byte) error {
   435  			resp.Log = "exists"
   436  			resp.Value = val
   437  			return nil
   438  		})
   439  	})
   440  	if dbErr != nil {
   441  		log.Panicf("Error reading database, unable to execute query: %v", dbErr)
   442  	}
   443  	return resp
   444  }
   445  ```
   446  
   447  Since it reads only committed data from the store, transactions that are part of a block
   448  that is being processed are not reflected in the query result.
   449  
   450  
   451  
   452  
   453  ## 1.4 Starting an application and a CometBFT instance
   454  
   455  Now that we have the basic functionality of our application in place, let's put it all together inside of our `main.go` file.
   456  
   457  Change the contents of your `main.go` file to the following.
   458  
   459  ```go
   460  package main
   461  
   462  import (
   463  	"flag"
   464  	"fmt"
   465  	abciserver "github.com/cometbft/cometbft/abci/server"
   466  	"log"
   467  	"os"
   468  	"os/signal"
   469  	"path/filepath"
   470  	"syscall"
   471  
   472  	"github.com/dgraph-io/badger/v3"
   473  	cmtlog "github.com/cometbft/cometbft/libs/log"
   474  )
   475  
   476  var homeDir string
   477  var socketAddr string
   478  
   479  func init() {
   480  	flag.StringVar(&homeDir, "kv-home", "", "Path to the kvstore directory (if empty, uses $HOME/.kvstore)")
   481  	flag.StringVar(&socketAddr, "socket-addr", "unix://example.sock", "Unix domain socket address (if empty, uses \"unix://example.sock\"")
   482  }
   483  
   484  func main() {
   485  	flag.Parse()
   486  	if homeDir == "" {
   487  		homeDir = os.ExpandEnv("$HOME/.kvstore")
   488  	}
   489  
   490  	dbPath := filepath.Join(homeDir, "badger")
   491  	db, err := badger.Open(badger.DefaultOptions(dbPath))
   492  	if err != nil {
   493  		log.Fatalf("Opening database: %v", err)
   494  	}
   495  	defer func() {
   496  		if err := db.Close(); err != nil {
   497  			log.Fatalf("Closing database: %v", err)
   498  		}
   499  	}()
   500  
   501  	app := NewKVStoreApplication(db)
   502  
   503  	logger := cmtlog.NewTMLogger(cmtlog.NewSyncWriter(os.Stdout))
   504  
   505  	server := abciserver.NewSocketServer(socketAddr, app)
   506  	server.SetLogger(logger)
   507  
   508  	if err := server.Start(); err != nil {
   509  		fmt.Fprintf(os.Stderr, "error starting socket server: %v", err)
   510  		os.Exit(1)
   511  	}
   512  	defer server.Stop()
   513  
   514  	c := make(chan os.Signal, 1)
   515  	signal.Notify(c, os.Interrupt, syscall.SIGTERM)
   516  	<-c
   517  }
   518  ```
   519  
   520  This is a huge blob of code, so let's break it down into pieces.
   521  
   522  First, we initialize the Badger database and create an app instance:
   523  
   524  ```go
   525  	dbPath := filepath.Join(homeDir, "badger")
   526  	db, err := badger.Open(badger.DefaultOptions(dbPath))
   527  	if err != nil {
   528  		log.Fatalf("Opening database: %v", err)
   529  	}
   530  	defer func() {
   531  		if err := db.Close(); err != nil {
   532  			log.Fatalf("Closing database: %v", err)
   533  		}
   534  	}()
   535  
   536  	app := NewKVStoreApplication(db)
   537  ```
   538  
   539  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).
   540  This can be avoided by setting the truncate option to true, like this:
   541  
   542  ```go
   543  	db, err := badger.Open(badger.DefaultOptions("/tmp/badger").WithTruncate(true))
   544  ```
   545  
   546  Then we start the ABCI server and add some signal handling to gracefully stop
   547  it upon receiving SIGTERM or Ctrl-C. CometBFT will act as a client,
   548  which connects to our server and send us transactions and other messages.
   549  
   550  ```go
   551  	server := abciserver.NewSocketServer(socketAddr, app)
   552  	server.SetLogger(logger)
   553  
   554  	if err := server.Start(); err != nil {
   555  		fmt.Fprintf(os.Stderr, "error starting socket server: %v", err)
   556  		os.Exit(1)
   557  	}
   558  	defer server.Stop()
   559  
   560  	c := make(chan os.Signal, 1)
   561  	signal.Notify(c, os.Interrupt, syscall.SIGTERM)
   562  	<-c
   563  ```
   564  
   565  ## 1.5 Initializing and Running
   566  
   567  Our application is almost ready to run, but first we'll need to populate the CometBFT configuration files.
   568  The following command will create a `cometbft-home` directory in your project and add a basic set of configuration files in `cometbft-home/config/`.
   569  For more information on what these files contain see [the configuration documentation](https://github.com/cometbft/cometbft/blob/v0.34.x/docs/core/configuration.md).
   570  
   571  From the root of your project, run:
   572  
   573  ```bash
   574  go run github.com/cometbft/cometbft/cmd/cometbft@v0.34.27 init --home /tmp/cometbft-home
   575  ```
   576  
   577  You should see an output similar to the following:
   578  
   579  ```bash
   580  I[2022-11-09|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
   581  I[2022-11-09|09:06:34.444] Generated node key                           module=main path=/tmp/cometbft-home/config/node_key.json
   582  I[2022-11-09|09:06:34.444] Generated genesis file                       module=main path=/tmp/cometbft-home/config/genesis.json
   583  ```
   584  
   585  Now rebuild the app:
   586  
   587  ```bash
   588  go build -mod=mod # use -mod=mod to automatically refresh the dependencies
   589  ```
   590  
   591  Everything is now in place to run your application. Run:
   592  
   593  ```bash
   594  ./kvstore -kv-home /tmp/badger-home
   595  ```
   596  
   597  The application will start and you should see an output similar to the following:
   598  
   599  ```bash
   600  badger 2022/11/09 17:01:28 INFO: All 0 tables opened in 0s
   601  badger 2022/11/09 17:01:28 INFO: Discard stats nextEmptySlot: 0
   602  badger 2022/11/09 17:01:28 INFO: Set nextTxnTs to 0
   603  I[2022-11-09|17:01:28.726] service start                                msg="Starting ABCIServer service" impl=ABCIServer
   604  I[2022-11-09|17:01:28.726] Waiting for new connection...
   605  ```
   606  
   607  Then we need to start CometBFT service and point it to our application.
   608  Open a new terminal window and cd to the same folder where the app is running.
   609  Then execute the following command:
   610  
   611  ```bash
   612  go run github.com/cometbft/cometbft/cmd/cometbft@v0.34.27 node --home /tmp/cometbft-home --proxy_app=unix://example.sock
   613  ```
   614  
   615  This should start the full node and connect to our ABCI application, which will be
   616  reflected in the application output.
   617  
   618  ```sh
   619  I[2022-11-09|17:07:08.124] service start                                msg="Starting ABCIServer service" impl=ABCIServer
   620  I[2022-11-09|17:07:08.124] Waiting for new connection...
   621  I[2022-11-09|17:08:12.702] Accepted a new connection
   622  I[2022-11-09|17:08:12.703] Waiting for new connection...
   623  I[2022-11-09|17:08:12.703] Accepted a new connection
   624  I[2022-11-09|17:08:12.703] Waiting for new connection...
   625  ```
   626  
   627  Also, the application using CometBFT Core is producing blocks  🎉🎉 and you can see this reflected in the log output of the service in lines like this:
   628  
   629  ```bash
   630  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}"
   631  I[2022-11-09|09:08:52.152] received complete proposal block             module=consensus height=2 hash=F518444C0E348270436A73FD0F0B9DFEA758286BEB29482F1E3BEA75330E825C
   632  I[2022-11-09|09:08:52.160] finalizing commit of block                   module=consensus height=2 hash=F518444C0E348270436A73FD0F0B9DFEA758286BEB29482F1E3BEA75330E825C root= num_txs=0
   633  I[2022-11-09|09:08:52.167] executed block                               module=state height=2 num_valid_txs=0 num_invalid_txs=0
   634  I[2022-11-09|09:08:52.171] committed state                              module=state height=2 num_txs=0 app_hash=
   635  ```
   636  
   637  The blocks, as you can see from the `num_valid_txs=0` part, are empty, but let's remedy that next.
   638  
   639  ## 1.6 Using the application
   640  
   641  Let's try submitting a transaction to our new application.
   642  Open another terminal window and run the following curl command:
   643  
   644  
   645  ```bash
   646  curl -s 'localhost:26657/broadcast_tx_commit?tx="cometbft=rocks"'
   647  ```
   648  
   649  If everything went well, you should see a response indicating which height the
   650  transaction was included in the blockchain.
   651  
   652  Finally, let's make sure that transaction really was persisted by the application.
   653  Run the following command:
   654  
   655  ```bash
   656  curl -s 'localhost:26657/abci_query?data="cometbft"'
   657  ```
   658  
   659  Let's examine the response object that this request returns.
   660  The request returns a `json` object with a `key` and `value` field set.
   661  
   662  ```json
   663  ...
   664  	"key": "dGVuZGVybWludA==",
   665  	"value": "cm9ja3M=",
   666  ...
   667  ```
   668  
   669  Those values don't look like the `key` and `value` we sent to CometBFT.
   670  What's going on here?
   671  
   672  The response contains a `base64` encoded representation of the data we submitted.
   673  To get the original value out of this data, we can use the `base64` command line utility:
   674  
   675  ```bash
   676  echo "cm9ja3M=" | base64 -d
   677  ```
   678  
   679  ## Outro
   680  
   681  I hope everything went smoothly and your first, but hopefully not the last,
   682  CometBFT application is up and running. If not, please [open an issue on
   683  Github](https://github.com/cometbft/cometbft/issues/new/choose).