github.com/KYVENetwork/cometbft/v38@v38.0.3/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.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{Code: code}, 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 195 return &abcitypes.ResponseApplySnapshotChunk{Result: abcitypes.ResponseApplySnapshotChunk_ACCEPT}, nil 196 } 197 198 func (app KVStoreApplication) ExtendVote(_ context.Context, extend *abcitypes.RequestExtendVote) (*abcitypes.ResponseExtendVote, error) { 199 return &abcitypes.ResponseExtendVote{}, nil 200 } 201 202 func (app *KVStoreApplication) VerifyVoteExtension(_ context.Context, verify *abcitypes.RequestVerifyVoteExtension) (*abcitypes.ResponseVerifyVoteExtension, error) { 203 return &abcitypes.ResponseVerifyVoteExtension{}, nil 204 } 205 ``` 206 207 The types used here are defined in the CometBFT library and were added as a dependency 208 to the project when you ran `go get`. If your IDE is not recognizing the types, go ahead and run the command again. 209 210 ```bash 211 go get github.com/cometbft/cometbft@v0.38.0 212 ``` 213 214 Now go back to the `main.go` and modify the `main` function so it matches the following, 215 where an instance of the `KVStoreApplication` type is created. 216 217 ```go 218 func main() { 219 fmt.Println("Hello, CometBFT") 220 221 _ = NewKVStoreApplication() 222 } 223 ``` 224 225 You can recompile and run the application now by running `go get` and `go build`, but it does 226 not do anything. 227 So let's revisit the code adding the logic needed to implement our minimal key/value store 228 and to start it along with the CometBFT Service. 229 230 ### 1.3.1 Add a persistent data store 231 232 Our application will need to write its state out to persistent storage so that it 233 can stop and start without losing all of its data. 234 235 For this tutorial, we will use [BadgerDB](https://github.com/dgraph-io/badger), a 236 a fast embedded key-value store. 237 238 First, add Badger as a dependency of your go module using the `go get` command: 239 240 `go get github.com/dgraph-io/badger/v3` 241 242 Next, let's update the application and its constructor to receive a handle to the database, as follows: 243 244 ```go 245 type KVStoreApplication struct { 246 db *badger.DB 247 onGoingBlock *badger.Txn 248 } 249 250 var _ abcitypes.Application = (*KVStoreApplication)(nil) 251 252 func NewKVStoreApplication(db *badger.DB) *KVStoreApplication { 253 return &KVStoreApplication{db: db} 254 } 255 ``` 256 257 The `onGoingBlock` keeps track of the Badger transaction that will update the application's state when a block 258 is completed. Don't worry about it for now, we'll get to that later. 259 260 Next, update the `import` stanza at the top to include the Badger library: 261 262 ```go 263 import( 264 "github.com/dgraph-io/badger/v3" 265 abcitypes "github.com/KYVENetwork/cometbft/v38/abci/types" 266 ) 267 ``` 268 269 Finally, update the `main.go` file to invoke the updated constructor: 270 271 ```go 272 _ = NewKVStoreApplication(nil) 273 ``` 274 275 ### 1.3.2 CheckTx 276 277 When CometBFT receives a new transaction from a client, or from another full node, 278 CometBFT asks the application if the transaction is acceptable, using the `CheckTx` method. 279 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. 280 281 In our application, a transaction is a string with the form `key=value`, indicating a key and value to write to the store. 282 283 The most basic validation check we can perform is to check if the transaction conforms to the `key=value` pattern. 284 For that, let's add the following helper method to app.go: 285 286 ```go 287 func (app *KVStoreApplication) isValid(tx []byte) uint32 { 288 // check format 289 parts := bytes.Split(tx, []byte("=")) 290 if len(parts) != 2 { 291 return 1 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 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 the `FinalizeBlock` method. 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`. 337 `FinalizeBlock`'s parameters are an aggregation of those in `BeginBlock`, `DeliverTx`, and `EndBlock`. 338 339 This method is responsible for executing the block and returning a response to the consensus engine. 340 Providing a single `FinalizeBlock` method to signal the finalization of a block simplifies the ABCI interface and increases flexibility in the execution pipeline. 341 342 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. 343 344 **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. 345 346 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_. 347 348 First, let's create a new Badger transaction during `FinalizeBlock`. All application transactions in the current block will be executed within this Badger transaction. 349 Next, let's modify `FinalizeBlock` to add the `key` and `value` to the database transaction every time our application processes a new application transaction from the list received through `RequestFinalizeBlock`. 350 351 Note that we check the validity of the transaction _again_ during `FinalizeBlock`. 352 353 ```go 354 func (app *KVStoreApplication) FinalizeBlock(_ context.Context, req *abcitypes.RequestFinalizeBlock) (*abcitypes.ResponseFinalizeBlock, error) { 355 var txs = make([]*abcitypes.ExecTxResult, len(req.Txs)) 356 357 app.onGoingBlock = app.db.NewTransaction(true) 358 for i, tx := range req.Txs { 359 if code := app.isValid(tx); code != 0 { 360 log.Printf("Error in tx in if") 361 txs[i] = &abcitypes.ExecTxResult{Code: code} 362 } else { 363 parts := bytes.SplitN(tx, []byte("="), 2) 364 key, value := parts[0], parts[1] 365 log.Printf("Adding key %s with value %s", key, value) 366 367 if err := app.onGoingBlock.Set(key, value); err != nil { 368 log.Panicf("Error writing to database, unable to execute tx: %v", err) 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. 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. 385 386 **Note** that `FinalizeBlock` cannot yet commit the Badger transaction we were building during the block execution. 387 388 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. 389 390 The `Commit` method tells the application to make permanent the effects of the application transactions. 391 Let's update the method to terminate the pending Badger transaction and persist the resulting state: 392 393 ```go 394 func (app KVStoreApplication) Commit(_ context.Context, commit *abcitypes.RequestCommit) (*abcitypes.ResponseCommit, error) { 395 return &abcitypes.ResponseCommit{}, app.onGoingBlock.Commit() 396 } 397 ``` 398 399 Finally, make sure to add the log library to the `import` stanza as well: 400 401 ```go 402 import ( 403 "bytes" 404 "log" 405 406 "github.com/dgraph-io/badger/v3" 407 abcitypes "github.com/KYVENetwork/cometbft/v38/abci/types" 408 ) 409 ``` 410 411 You may have noticed that the application we are writing will crash if it receives 412 an unexpected error from the Badger database during the `FinalizeBlock` or `Commit` methods. 413 This is not an accident. If the application received an error from the database, there 414 is no deterministic way for it to make progress so the only safe option is to terminate. 415 416 ### 1.3.4 Query 417 418 When a client tries to read some information from the `kvstore`, the request will be 419 handled in the `Query` method. To do this, let's rewrite the `Query` method in `app.go`: 420 421 ```go 422 func (app *KVStoreApplication) Query(_ context.Context, req *abcitypes.RequestQuery) (*abcitypes.ResponseQuery, error) { 423 resp := abcitypes.ResponseQuery{Key: req.Data} 424 425 dbErr := app.db.View(func(txn *badger.Txn) error { 426 item, err := txn.Get(req.Data) 427 if err != nil { 428 if err != badger.ErrKeyNotFound { 429 return err 430 } 431 resp.Log = "key does not exist" 432 return nil 433 } 434 435 return item.Value(func(val []byte) error { 436 resp.Log = "exists" 437 resp.Value = val 438 return nil 439 }) 440 }) 441 if dbErr != nil { 442 log.Panicf("Error reading database, unable to execute query: %v", dbErr) 443 } 444 return &resp, nil 445 } 446 ``` 447 448 Since it reads only committed data from the store, transactions that are part of a block 449 that is being processed are not reflected in the query result. 450 451 ### 1.3.5 PrepareProposal and ProcessProposal 452 453 `PrepareProposal` and `ProcessProposal` are methods introduced in CometBFT v0.37.0 454 to give the application more control over the construction and processing of transaction blocks. 455 456 When CometBFT sees that valid transactions (validated through `CheckTx`) are available to be 457 included in blocks, it groups some of these transactions and then gives the application a chance 458 to modify the group by invoking `PrepareProposal`. 459 460 The application is free to modify the group before returning from the call, as long as the resulting set 461 does not use more bytes than `RequestPrepareProposal.max_tx_bytes`. 462 For example, the application may reorder, add, or even remove transactions from the group to improve the 463 execution of the block once accepted. 464 465 In the following code, the application simply returns the unmodified group of transactions: 466 467 ```go 468 func (app *KVStoreApplication) PrepareProposal(_ context.Context, proposal *abcitypes.RequestPrepareProposal) (*abcitypes.ResponsePrepareProposal, error) { 469 return &abcitypes.ResponsePrepareProposal{Txs: proposal.Txs}, nil 470 } 471 ``` 472 473 Once a proposed block is received by a node, the proposal is passed to the 474 application to determine its validity before voting to accept the proposal. 475 476 This mechanism may be used for different reasons, for example to deal with blocks manipulated 477 by malicious nodes, in which case the block should not be considered valid. 478 479 The following code simply accepts all proposals: 480 481 ```go 482 func (app *KVStoreApplication) ProcessProposal(_ context.Context, proposal *abcitypes.RequestProcessProposal) (*abcitypes.ResponseProcessProposal, error) { 483 return &abcitypes.ResponseProcessProposal{Status: abcitypes.ResponseProcessProposal_ACCEPT}, nil 484 } 485 ``` 486 487 ## 1.4 Starting an application and a CometBFT instance 488 489 Now that we have the basic functionality of our application in place, let's put 490 it all together inside of our `main.go` file. 491 492 Change the contents of your `main.go` file to the following. 493 494 ```go 495 package main 496 497 import ( 498 "flag" 499 "fmt" 500 abciserver "github.com/KYVENetwork/cometbft/v38/abci/server" 501 "log" 502 "os" 503 "os/signal" 504 "path/filepath" 505 "syscall" 506 507 "github.com/dgraph-io/badger/v3" 508 cmtlog "github.com/KYVENetwork/cometbft/v38/libs/log" 509 ) 510 511 var homeDir string 512 var socketAddr string 513 514 func init() { 515 flag.StringVar(&homeDir, "kv-home", "", "Path to the kvstore directory (if empty, uses $HOME/.kvstore)") 516 flag.StringVar(&socketAddr, "socket-addr", "unix://example.sock", "Unix domain socket address (if empty, uses \"unix://example.sock\"") 517 } 518 519 func main() { 520 flag.Parse() 521 if homeDir == "" { 522 homeDir = os.ExpandEnv("$HOME/.kvstore") 523 } 524 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 531 defer func() { 532 if err := db.Close(); err != nil { 533 log.Fatalf("Closing database: %v", err) 534 } 535 }() 536 537 app := NewKVStoreApplication(db) 538 logger := cmtlog.NewTMLogger(cmtlog.NewSyncWriter(os.Stdout)) 539 540 server := abciserver.NewSocketServer(socketAddr, app) 541 server.SetLogger(logger) 542 543 if err := server.Start(); err != nil { 544 fmt.Fprintf(os.Stderr, "error starting socket server: %v", err) 545 546 os.Exit(1) 547 } 548 defer server.Stop() 549 550 c := make(chan os.Signal, 1) 551 signal.Notify(c, os.Interrupt, syscall.SIGTERM) 552 <-c 553 } 554 ``` 555 556 This is a huge blob of code, so let's break it down into pieces. 557 558 First, we initialize the Badger database and create an app instance: 559 560 ```go 561 dbPath := filepath.Join(homeDir, "badger") 562 db, err := badger.Open(badger.DefaultOptions(dbPath)) 563 if err != nil { 564 log.Fatalf("Opening database: %v", err) 565 } 566 defer func() { 567 if err := db.Close(); err != nil { 568 log.Fatalf("Closing database: %v", err) 569 } 570 }() 571 572 app := NewKVStoreApplication(db) 573 ``` 574 575 Then we start the ABCI server and add some signal handling to gracefully stop 576 it upon receiving SIGTERM or Ctrl-C. CometBFT will act as a client, 577 which connects to our server and send us transactions and other messages. 578 579 ```go 580 server := abciserver.NewSocketServer(socketAddr, app) 581 server.SetLogger(logger) 582 583 if err := server.Start(); err != nil { 584 fmt.Fprintf(os.Stderr, "error starting socket server: %v", err) 585 os.Exit(1) 586 } 587 defer server.Stop() 588 589 c := make(chan os.Signal, 1) 590 signal.Notify(c, os.Interrupt, syscall.SIGTERM) 591 <-c 592 ``` 593 594 ## 1.5 Initializing and Running 595 596 Our application is almost ready to run, but first we'll need to populate the CometBFT configuration files. 597 The following command will create a `cometbft-home` directory in your project and add a basic set of configuration files in `cometbft-home/config/`. 598 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). 599 600 From the root of your project, run: 601 602 ```bash 603 go run github.com/KYVENetwork/cometbft/v38/cmd/cometbft@v0.38.0 init --home /tmp/cometbft-home 604 ``` 605 606 You should see an output similar to the following: 607 608 ```bash 609 I[2023-04-25|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 610 I[2023-04-25|09:06:34.444] Generated node key module=main path=/tmp/cometbft-home/config/node_key.json 611 I[2023-04-25|09:06:34.444] Generated genesis file module=main path=/tmp/cometbft-home/config/genesis.json 612 ``` 613 614 Now rebuild the app: 615 616 ```bash 617 go build -mod=mod # use -mod=mod to automatically refresh the dependencies 618 ``` 619 620 Everything is now in place to run your application. Run: 621 622 ```bash 623 ./kvstore -kv-home /tmp/badger-home 624 ``` 625 626 The application will start and you should see an output similar to the following: 627 628 ```bash 629 badger 2023-04-25 17:01:28 INFO: All 0 tables opened in 0s 630 badger 2023-04-25 17:01:28 INFO: Discard stats nextEmptySlot: 0 631 badger 2023-04-25 17:01:28 INFO: Set nextTxnTs to 0 632 I[2023-04-25|17:01:28.726] service start msg="Starting ABCIServer service" impl=ABCIServer 633 I[2023-04-25|17:01:28.726] Waiting for new connection... 634 ``` 635 636 Then we need to start CometBFT service and point it to our application. 637 Open a new terminal window and cd to the same folder where the app is running. 638 Then execute the following command: 639 640 ```bash 641 go run github.com/KYVENetwork/cometbft/v38/cmd/cometbft@v0.38.0 node --home /tmp/cometbft-home --proxy_app=unix://example.sock 642 ``` 643 644 This should start the full node and connect to our ABCI application, which will be 645 reflected in the application output. 646 647 ```sh 648 I[2023-04-25|17:07:08.124] service start msg="Starting ABCIServer service" impl=ABCIServer 649 I[2023-04-25|17:07:08.124] Waiting for new connection... 650 I[2023-04-25|17:08:12.702] Accepted a new connection 651 I[2023-04-25|17:08:12.703] Waiting for new connection... 652 I[2023-04-25|17:08:12.703] Accepted a new connection 653 I[2023-04-25|17:08:12.703] Waiting for new connection... 654 ``` 655 656 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: 657 658 ```bash 659 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}" 660 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}" 661 I[2023-04-25|09:08:52.152] received complete proposal block module=consensus height=2 hash=F518444C0E348270436A73FD0F0B9DFEA758286BEB29482F1E3BEA75330E825C 662 I[2023-04-25|09:08:52.160] finalizing commit of block module=consensus height=2 hash=F518444C0E348270436A73FD0F0B9DFEA758286BEB29482F1E3BEA75330E825C root= num_txs=0 663 I[2023-04-25|09:08:52.167] executed block module=state height=2 num_valid_txs=0 num_invalid_txs=0 664 I[2023-04-25|09:08:52.171] committed state module=state height=2 num_txs=0 app_hash= 665 ``` 666 667 The blocks, as you can see from the `num_valid_txs=0` part, are empty, but let's remedy that next. 668 669 ## 1.6 Using the application 670 671 Let's try submitting a transaction to our new application. 672 Open another terminal window and run the following curl command: 673 674 ```bash 675 curl -s 'localhost:26657/broadcast_tx_commit?tx="cometbft=rocks"' 676 ``` 677 678 If everything went well, you should see a response indicating which height the 679 transaction was included in the blockchain. 680 681 Finally, let's make sure that transaction really was persisted by the application. 682 Run the following command: 683 684 ```bash 685 curl -s 'localhost:26657/abci_query?data="cometbft"' 686 ``` 687 688 Let's examine the response object that this request returns. 689 The request returns a `json` object with a `key` and `value` field set. 690 691 ```json 692 ... 693 "key": "dGVuZGVybWludA==", 694 "value": "cm9ja3M=", 695 ... 696 ``` 697 698 Those values don't look like the `key` and `value` we sent to CometBFT. 699 What's going on here? 700 701 The response contains a `base64` encoded representation of the data we submitted. 702 To get the original value out of this data, we can use the `base64` command line utility: 703 704 ```bash 705 echo "cm9ja3M=" | base64 -d 706 ``` 707 708 ## Outro 709 710 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.