github.com/number571/tendermint@v0.34.11-gost/docs/tutorials/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 Tendermint 10 Core application from scratch. It does not assume that you have any prior 11 experience with Tendermint Core. 12 13 Tendermint Core is Byzantine Fault Tolerant (BFT) middleware that takes a state 14 transition machine - written in any programming language - and securely 15 replicates it on many machines. 16 17 Although Tendermint Core is written in the Golang programming language, prior 18 knowledge of it is not required for this guide. You can learn it as we go due 19 to it's simplicity. However, you may want to go through [Learn X in Y minutes 20 Where X=Go](https://learnxinyminutes.com/docs/go/) first to familiarize 21 yourself with the syntax. 22 23 By following along with this guide, you'll create a Tendermint Core project 24 called kvstore, a (very) simple distributed BFT key-value store. 25 26 ## Built-in app vs external app 27 28 Running your application inside the same process as Tendermint Core will give 29 you the best possible performance. 30 31 For other languages, your application have to communicate with Tendermint Core 32 through a TCP, Unix domain socket or gRPC. 33 34 ## 1.1 Installing Go 35 36 Please refer to [the official guide for installing 37 Go](https://golang.org/doc/install). 38 39 Verify that you have the latest version of Go installed: 40 41 ```bash 42 $ go version 43 go version go1.16.x darwin/amd64 44 ``` 45 46 ## 1.2 Creating a new Go project 47 48 We'll start by creating a new Go project. 49 50 ```bash 51 mkdir kvstore 52 cd kvstore 53 ``` 54 55 Inside the example directory create a `main.go` file with the following content: 56 57 ```go 58 package main 59 60 import ( 61 "fmt" 62 ) 63 64 func main() { 65 fmt.Println("Hello, Tendermint Core") 66 } 67 ``` 68 69 When run, this should print "Hello, Tendermint Core" to the standard output. 70 71 ```bash 72 $ go run main.go 73 Hello, Tendermint Core 74 ``` 75 76 ## 1.3 Writing a Tendermint Core application 77 78 Tendermint Core communicates with the application through the Application 79 BlockChain Interface (ABCI). All message types are defined in the [protobuf 80 file](https://github.com/number571/tendermint/blob/master/proto/tendermint/abci/types.proto). 81 This allows Tendermint Core to run applications written in any programming 82 language. 83 84 Create a file called `app.go` with the following content: 85 86 ```go 87 package main 88 89 import ( 90 abcitypes "github.com/number571/tendermint/abci/types" 91 ) 92 93 type KVStoreApplication struct {} 94 95 var _ abcitypes.Application = (*KVStoreApplication)(nil) 96 97 func NewKVStoreApplication() *KVStoreApplication { 98 return &KVStoreApplication{} 99 } 100 101 func (KVStoreApplication) Info(req abcitypes.RequestInfo) abcitypes.ResponseInfo { 102 return abcitypes.ResponseInfo{} 103 } 104 105 func (KVStoreApplication) DeliverTx(req abcitypes.RequestDeliverTx) abcitypes.ResponseDeliverTx { 106 return abcitypes.ResponseDeliverTx{Code: 0} 107 } 108 109 func (KVStoreApplication) CheckTx(req abcitypes.RequestCheckTx) abcitypes.ResponseCheckTx { 110 return abcitypes.ResponseCheckTx{Code: 0} 111 } 112 113 func (KVStoreApplication) Commit() abcitypes.ResponseCommit { 114 return abcitypes.ResponseCommit{} 115 } 116 117 func (KVStoreApplication) Query(req abcitypes.RequestQuery) abcitypes.ResponseQuery { 118 return abcitypes.ResponseQuery{Code: 0} 119 } 120 121 func (KVStoreApplication) InitChain(req abcitypes.RequestInitChain) abcitypes.ResponseInitChain { 122 return abcitypes.ResponseInitChain{} 123 } 124 125 func (KVStoreApplication) BeginBlock(req abcitypes.RequestBeginBlock) abcitypes.ResponseBeginBlock { 126 return abcitypes.ResponseBeginBlock{} 127 } 128 129 func (KVStoreApplication) EndBlock(req abcitypes.RequestEndBlock) abcitypes.ResponseEndBlock { 130 return abcitypes.ResponseEndBlock{} 131 } 132 133 func (KVStoreApplication) ListSnapshots(abcitypes.RequestListSnapshots) abcitypes.ResponseListSnapshots { 134 return abcitypes.ResponseListSnapshots{} 135 } 136 137 func (KVStoreApplication) OfferSnapshot(abcitypes.RequestOfferSnapshot) abcitypes.ResponseOfferSnapshot { 138 return abcitypes.ResponseOfferSnapshot{} 139 } 140 141 func (KVStoreApplication) LoadSnapshotChunk(abcitypes.RequestLoadSnapshotChunk) abcitypes.ResponseLoadSnapshotChunk { 142 return abcitypes.ResponseLoadSnapshotChunk{} 143 } 144 145 func (KVStoreApplication) ApplySnapshotChunk(abcitypes.RequestApplySnapshotChunk) abcitypes.ResponseApplySnapshotChunk { 146 return abcitypes.ResponseApplySnapshotChunk{} 147 } 148 ``` 149 150 Now I will go through each method explaining when it's called and adding 151 required business logic. 152 153 ### 1.3.1 CheckTx 154 155 When a new transaction is added to the Tendermint Core, it will ask the 156 application to check it (validate the format, signatures, etc.). 157 158 ```go 159 import "bytes" 160 161 func (app *KVStoreApplication) isValid(tx []byte) (code uint32) { 162 // check format 163 parts := bytes.Split(tx, []byte("=")) 164 if len(parts) != 2 { 165 return 1 166 } 167 168 key, value := parts[0], parts[1] 169 170 // check if the same key=value already exists 171 err := app.db.View(func(txn *badger.Txn) error { 172 item, err := txn.Get(key) 173 if err != nil && err != badger.ErrKeyNotFound { 174 return err 175 } 176 if err == nil { 177 return item.Value(func(val []byte) error { 178 if bytes.Equal(val, value) { 179 code = 2 180 } 181 return nil 182 }) 183 } 184 return nil 185 }) 186 if err != nil { 187 panic(err) 188 } 189 190 return code 191 } 192 193 func (app *KVStoreApplication) CheckTx(req abcitypes.RequestCheckTx) abcitypes.ResponseCheckTx { 194 code := app.isValid(req.Tx) 195 return abcitypes.ResponseCheckTx{Code: code, GasWanted: 1} 196 } 197 ``` 198 199 Don't worry if this does not compile yet. 200 201 If the transaction does not have a form of `{bytes}={bytes}`, we return `1` 202 code. When the same key=value already exist (same key and value), we return `2` 203 code. For others, we return a zero code indicating that they are valid. 204 205 Note that anything with non-zero code will be considered invalid (`-1`, `100`, 206 etc.) by Tendermint Core. 207 208 Valid transactions will eventually be committed given they are not too big and 209 have enough gas. To learn more about gas, check out ["the 210 specification"](https://docs.tendermint.com/master/spec/abci/apps.html#gas). 211 212 For the underlying key-value store we'll use 213 [badger](https://github.com/dgraph-io/badger), which is an embeddable, 214 persistent and fast key-value (KV) database. 215 216 ```go 217 import "github.com/dgraph-io/badger" 218 219 type KVStoreApplication struct { 220 db *badger.DB 221 currentBatch *badger.Txn 222 } 223 224 func NewKVStoreApplication(db *badger.DB) *KVStoreApplication { 225 return &KVStoreApplication{ 226 db: db, 227 } 228 } 229 ``` 230 231 ### 1.3.2 BeginBlock -> DeliverTx -> EndBlock -> Commit 232 233 When Tendermint Core has decided on the block, it's transfered to the 234 application in 3 parts: `BeginBlock`, one `DeliverTx` per transaction and 235 `EndBlock` in the end. DeliverTx are being transfered asynchronously, but the 236 responses are expected to come in order. 237 238 ```go 239 func (app *KVStoreApplication) BeginBlock(req abcitypes.RequestBeginBlock) abcitypes.ResponseBeginBlock { 240 app.currentBatch = app.db.NewTransaction(true) 241 return abcitypes.ResponseBeginBlock{} 242 } 243 244 ``` 245 246 Here we create a batch, which will store block's transactions. 247 248 ```go 249 func (app *KVStoreApplication) DeliverTx(req abcitypes.RequestDeliverTx) abcitypes.ResponseDeliverTx { 250 code := app.isValid(req.Tx) 251 if code != 0 { 252 return abcitypes.ResponseDeliverTx{Code: code} 253 } 254 255 parts := bytes.Split(req.Tx, []byte("=")) 256 key, value := parts[0], parts[1] 257 258 err := app.currentBatch.Set(key, value) 259 if err != nil { 260 panic(err) 261 } 262 263 return abcitypes.ResponseDeliverTx{Code: 0} 264 } 265 ``` 266 267 If the transaction is badly formatted or the same key=value already exist, we 268 again return the non-zero code. Otherwise, we add it to the current batch. 269 270 In the current design, a block can include incorrect transactions (those who 271 passed CheckTx, but failed DeliverTx or transactions included by the proposer 272 directly). This is done for performance reasons. 273 274 Note we can't commit transactions inside the `DeliverTx` because in such case 275 `Query`, which may be called in parallel, will return inconsistent data (i.e. 276 it will report that some value already exist even when the actual block was not 277 yet committed). 278 279 `Commit` instructs the application to persist the new state. 280 281 ```go 282 func (app *KVStoreApplication) Commit() abcitypes.ResponseCommit { 283 app.currentBatch.Commit() 284 return abcitypes.ResponseCommit{Data: []byte{}} 285 } 286 ``` 287 288 ### 1.3.3 Query 289 290 Now, when the client wants to know whenever a particular key/value exist, it 291 will call Tendermint Core RPC `/abci_query` endpoint, which in turn will call 292 the application's `Query` method. 293 294 Applications are free to provide their own APIs. But by using Tendermint Core 295 as a proxy, clients (including [light client 296 package](https://godoc.org/github.com/number571/tendermint/light)) can leverage 297 the unified API across different applications. Plus they won't have to call the 298 otherwise separate Tendermint Core API for additional proofs. 299 300 Note we don't include a proof here. 301 302 ```go 303 func (app *KVStoreApplication) Query(reqQuery abcitypes.RequestQuery) (resQuery abcitypes.ResponseQuery) { 304 resQuery.Key = reqQuery.Data 305 err := app.db.View(func(txn *badger.Txn) error { 306 item, err := txn.Get(reqQuery.Data) 307 if err != nil && err != badger.ErrKeyNotFound { 308 return err 309 } 310 if err == badger.ErrKeyNotFound { 311 resQuery.Log = "does not exist" 312 } else { 313 return item.Value(func(val []byte) error { 314 resQuery.Log = "exists" 315 resQuery.Value = val 316 return nil 317 }) 318 } 319 return nil 320 }) 321 if err != nil { 322 panic(err) 323 } 324 return 325 } 326 ``` 327 328 The complete specification can be found 329 [here](https://docs.tendermint.com/master/spec/abci/). 330 331 ## 1.4 Starting an application and a Tendermint Core instance in the same process 332 333 Put the following code into the "main.go" file: 334 335 ```go 336 package main 337 338 import ( 339 "flag" 340 "fmt" 341 "os" 342 "os/signal" 343 "path/filepath" 344 "syscall" 345 346 "github.com/dgraph-io/badger" 347 "github.com/spf13/viper" 348 349 abci "github.com/number571/tendermint/abci/types" 350 cfg "github.com/number571/tendermint/config" 351 tmflags "github.com/number571/tendermint/libs/cli/flags" 352 "github.com/number571/tendermint/libs/log" 353 nm "github.com/number571/tendermint/node" 354 "github.com/number571/tendermint/internal/p2p" 355 "github.com/number571/tendermint/privval" 356 "github.com/number571/tendermint/proxy" 357 ) 358 359 var configFile string 360 361 func init() { 362 flag.StringVar(&configFile, "config", "$HOME/.tendermint/config/config.toml", "Path to config.toml") 363 } 364 365 func main() { 366 db, err := badger.Open(badger.DefaultOptions("/tmp/badger")) 367 if err != nil { 368 fmt.Fprintf(os.Stderr, "failed to open badger db: %v", err) 369 os.Exit(1) 370 } 371 defer db.Close() 372 app := NewKVStoreApplication(db) 373 374 flag.Parse() 375 376 node, err := newTendermint(app, configFile) 377 if err != nil { 378 fmt.Fprintf(os.Stderr, "%v", err) 379 os.Exit(2) 380 } 381 382 node.Start() 383 defer func() { 384 node.Stop() 385 node.Wait() 386 }() 387 388 c := make(chan os.Signal, 1) 389 signal.Notify(c, os.Interrupt, syscall.SIGTERM) 390 <-c 391 os.Exit(0) 392 } 393 394 func newTendermint(app abci.Application, configFile string) (*nm.Node, error) { 395 // read config 396 config := cfg.DefaultValidatorConfig() 397 config.RootDir = filepath.Dir(filepath.Dir(configFile)) 398 viper.SetConfigFile(configFile) 399 if err := viper.ReadInConfig(); err != nil { 400 return nil, fmt.Errorf("viper failed to read config file: %w", err) 401 } 402 if err := viper.Unmarshal(config); err != nil { 403 return nil, fmt.Errorf("viper failed to unmarshal config: %w", err) 404 } 405 if err := config.ValidateBasic(); err != nil { 406 return nil, fmt.Errorf("config is invalid: %w", err) 407 } 408 409 // create logger 410 logger := log.NewTMLogger(log.NewSyncWriter(os.Stdout)) 411 var err error 412 logger, err = tmflags.ParseLogLevel(config.LogLevel, logger, cfg.DefaultLogLevel) 413 if err != nil { 414 return nil, fmt.Errorf("failed to parse log level: %w", err) 415 } 416 417 // read private validator 418 pv := privval.LoadFilePV( 419 config.PrivValidatorKeyFile(), 420 config.PrivValidatorStateFile(), 421 ) 422 423 // read node key 424 nodeKey, err := p2p.LoadNodeKey(config.NodeKeyFile()) 425 if err != nil { 426 return nil, fmt.Errorf("failed to load node's key: %w", err) 427 } 428 429 // create node 430 node, err := nm.NewNode( 431 config, 432 pv, 433 nodeKey, 434 proxy.NewLocalClientCreator(app), 435 nm.DefaultGenesisDocProviderFunc(config), 436 nm.DefaultDBProvider, 437 nm.DefaultMetricsProvider(config.Instrumentation), 438 logger) 439 if err != nil { 440 return nil, fmt.Errorf("failed to create new Tendermint node: %w", err) 441 } 442 443 return node, nil 444 } 445 ``` 446 447 This is a huge blob of code, so let's break it down into pieces. 448 449 First, we initialize the Badger database and create an app instance: 450 451 ```go 452 db, err := badger.Open(badger.DefaultOptions("/tmp/badger")) 453 if err != nil { 454 fmt.Fprintf(os.Stderr, "failed to open badger db: %v", err) 455 os.Exit(1) 456 } 457 defer db.Close() 458 app := NewKVStoreApplication(db) 459 ``` 460 461 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). 462 This can be avoided by setting the truncate option to true, like this: 463 464 ```go 465 db, err := badger.Open(badger.DefaultOptions("/tmp/badger").WithTruncate(true)) 466 ``` 467 468 Then we use it to create a Tendermint Core `Node` instance: 469 470 ```go 471 flag.Parse() 472 473 node, err := newTendermint(app, configFile) 474 if err != nil { 475 fmt.Fprintf(os.Stderr, "%v", err) 476 os.Exit(2) 477 } 478 479 ... 480 481 // create node 482 node, err := nm.NewNode( 483 config, 484 pv, 485 nodeKey, 486 proxy.NewLocalClientCreator(app), 487 nm.DefaultGenesisDocProviderFunc(config), 488 nm.DefaultDBProvider, 489 nm.DefaultMetricsProvider(config.Instrumentation), 490 logger) 491 if err != nil { 492 return nil, fmt.Errorf("failed to create new Tendermint node: %w", err) 493 } 494 ``` 495 496 `NewNode` requires a few things including a configuration file, a private 497 validator, a node key and a few others in order to construct the full node. 498 499 Note we use `proxy.NewLocalClientCreator` here to create a local client instead 500 of one communicating through a socket or gRPC. 501 502 [viper](https://github.com/spf13/viper) is being used for reading the config, 503 which we will generate later using the `tendermint init` command. 504 505 ```go 506 config := cfg.DefaultValidatorConfig() 507 config.RootDir = filepath.Dir(filepath.Dir(configFile)) 508 viper.SetConfigFile(configFile) 509 if err := viper.ReadInConfig(); err != nil { 510 return nil, fmt.Errorf("viper failed to read config file: %w", err) 511 } 512 if err := viper.Unmarshal(config); err != nil { 513 return nil, fmt.Errorf("viper failed to unmarshal config: %w", err) 514 } 515 if err := config.ValidateBasic(); err != nil { 516 return nil, fmt.Errorf("config is invalid: %w", err) 517 } 518 ``` 519 520 We use `FilePV`, which is a private validator (i.e. thing which signs consensus 521 messages). Normally, you would use `SignerRemote` to connect to an external 522 [HSM](https://kb.certus.one/hsm.html). 523 524 ```go 525 pv := privval.LoadFilePV( 526 config.PrivValidatorKeyFile(), 527 config.PrivValidatorStateFile(), 528 ) 529 530 ``` 531 532 `nodeKey` is needed to identify the node in a p2p network. 533 534 ```go 535 nodeKey, err := p2p.LoadNodeKey(config.NodeKeyFile()) 536 if err != nil { 537 return nil, fmt.Errorf("failed to load node's key: %w", err) 538 } 539 ``` 540 541 As for the logger, we use the build-in library, which provides a nice 542 abstraction over [go-kit's 543 logger](https://github.com/go-kit/kit/tree/master/log). 544 545 ```go 546 logger := log.NewTMLogger(log.NewSyncWriter(os.Stdout)) 547 var err error 548 logger, err = tmflags.ParseLogLevel(config.LogLevel, logger, cfg.DefaultLogLevel()) 549 if err != nil { 550 return nil, fmt.Errorf("failed to parse log level: %w", err) 551 } 552 ``` 553 554 Finally, we start the node and add some signal handling to gracefully stop it 555 upon receiving SIGTERM or Ctrl-C. 556 557 ```go 558 node.Start() 559 defer func() { 560 node.Stop() 561 node.Wait() 562 }() 563 564 c := make(chan os.Signal, 1) 565 signal.Notify(c, os.Interrupt, syscall.SIGTERM) 566 <-c 567 os.Exit(0) 568 ``` 569 570 ## 1.5 Getting Up and Running 571 572 We are going to use [Go modules](https://github.com/golang/go/wiki/Modules) for 573 dependency management. 574 575 ```bash 576 export GO111MODULE=on 577 go mod init github.com/me/example 578 ``` 579 580 This should create a `go.mod` file. The current tutorial only works with 581 the master branch of Tendermint. so let's make sure we're using the latest version: 582 583 ```sh 584 go get github.com/number571/tendermint@master 585 ``` 586 587 This will populate the `go.mod` with a release number followed by a hash for Tendermint. 588 589 ```go 590 module github.com/me/example 591 592 go 1.15 593 594 require ( 595 github.com/dgraph-io/badger v1.6.2 596 github.com/number571/tendermint <vX> 597 ) 598 ``` 599 600 Now we can build the binary: 601 602 ```bash 603 go build 604 ``` 605 606 To create a default configuration, nodeKey and private validator files, let's 607 execute `tendermint init validator`. But before we do that, we will need to install 608 Tendermint Core. Please refer to [the official 609 guide](https://docs.tendermint.com/master/introduction/install.html). If you're 610 installing from source, don't forget to checkout the latest release (`git 611 checkout vX.Y.Z`). Don't forget to check that the application uses the same 612 major version. 613 614 ```bash 615 $ rm -rf /tmp/example 616 $ TMHOME="/tmp/example" tendermint init validator 617 618 I[2019-07-16|18:40:36.480] Generated private validator module=main keyFile=/tmp/example/config/priv_validator_key.json stateFile=/tmp/example2/data/priv_validator_state.json 619 I[2019-07-16|18:40:36.481] Generated node key module=main path=/tmp/example/config/node_key.json 620 I[2019-07-16|18:40:36.482] Generated genesis file module=main path=/tmp/example/config/genesis.json 621 I[2019-07-16|18:40:36.483] Generated config module=main mode=validator 622 ``` 623 624 We are ready to start our application: 625 626 ```bash 627 $ ./example -config "/tmp/example/config/config.toml" 628 629 badger 2019/07/16 18:42:25 INFO: All 0 tables opened in 0s 630 badger 2019/07/16 18:42:25 INFO: Replaying file id: 0 at offset: 0 631 badger 2019/07/16 18:42:25 INFO: Replay took: 695.227s 632 E[2019-07-16|18:42:25.818] Couldn't connect to any seeds module=p2p 633 I[2019-07-16|18:42:26.853] Executed block module=state height=1 validTxs=0 invalidTxs=0 634 I[2019-07-16|18:42:26.865] Committed state module=state height=1 txs=0 appHash= 635 ``` 636 637 Now open another tab in your terminal and try sending a transaction: 638 639 ```bash 640 $ curl -s 'localhost:26657/broadcast_tx_commit?tx="tendermint=rocks"' 641 { 642 "jsonrpc": "2.0", 643 "id": "", 644 "result": { 645 "check_tx": { 646 "gasWanted": "1" 647 }, 648 "deliver_tx": {}, 649 "hash": "1B3C5A1093DB952C331B1749A21DCCBB0F6C7F4E0055CD04D16346472FC60EC6", 650 "height": "128" 651 } 652 } 653 ``` 654 655 Response should contain the height where this transaction was committed. 656 657 Now let's check if the given key now exists and its value: 658 659 ```json 660 $ curl -s 'localhost:26657/abci_query?data="tendermint"' 661 { 662 "jsonrpc": "2.0", 663 "id": "", 664 "result": { 665 "response": { 666 "log": "exists", 667 "key": "dGVuZGVybWludA==", 668 "value": "cm9ja3M=" 669 } 670 } 671 } 672 ``` 673 674 "dGVuZGVybWludA==" and "cm9ja3M=" are the base64-encoding of the ASCII of 675 "tendermint" and "rocks" accordingly. 676 677 ## Outro 678 679 I hope everything went smoothly and your first, but hopefully not the last, 680 Tendermint Core application is up and running. If not, please [open an issue on 681 Github](https://github.com/number571/tendermint/issues/new/choose). To dig 682 deeper, read [the docs](https://docs.tendermint.com/master/).