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.