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