github.com/number571/tendermint@v0.34.11-gost/docs/tutorials/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 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 To get maximum performance it is better to run your application alongside 29 Tendermint Core. [Cosmos SDK](https://github.com/cosmos/cosmos-sdk) is written 30 this way. Please refer to [Writing a built-in Tendermint Core application in 31 Go](./go-built-in.md) guide for details. 32 33 Having a separate application might give you better security guarantees as two 34 processes would be communicating via established binary protocol. Tendermint 35 Core will not have access to application's state. 36 37 ## 1.1 Installing Go 38 39 Please refer to [the official guide for installing 40 Go](https://golang.org/doc/install). 41 42 Verify that you have the latest version of Go installed: 43 44 ```bash 45 $ go version 46 go version go1.16.x darwin/amd64 47 ``` 48 49 ## 1.2 Creating a new Go project 50 51 We'll start by creating a new Go project. 52 53 ```bash 54 mkdir kvstore 55 cd kvstore 56 ``` 57 58 Inside the example directory create a `main.go` file with the following content: 59 60 ```go 61 package main 62 63 import ( 64 "fmt" 65 ) 66 67 func main() { 68 fmt.Println("Hello, Tendermint Core") 69 } 70 ``` 71 72 When run, this should print "Hello, Tendermint Core" to the standard output. 73 74 ```bash 75 go run main.go 76 Hello, Tendermint Core 77 ``` 78 79 ## 1.3 Writing a Tendermint Core application 80 81 Tendermint Core communicates with the application through the Application 82 BlockChain Interface (ABCI). All message types are defined in the [protobuf 83 file](https://github.com/number571/tendermint/blob/master/proto/tendermint/abci/types.proto). 84 This allows Tendermint Core to run applications written in any programming 85 language. 86 87 Create a file called `app.go` with the following content: 88 89 ```go 90 package main 91 92 import ( 93 abcitypes "github.com/number571/tendermint/abci/types" 94 ) 95 96 type KVStoreApplication struct {} 97 98 var _ abcitypes.Application = (*KVStoreApplication)(nil) 99 100 func NewKVStoreApplication() *KVStoreApplication { 101 return &KVStoreApplication{} 102 } 103 104 func (KVStoreApplication) Info(req abcitypes.RequestInfo) abcitypes.ResponseInfo { 105 return abcitypes.ResponseInfo{} 106 } 107 108 func (KVStoreApplication) DeliverTx(req abcitypes.RequestDeliverTx) abcitypes.ResponseDeliverTx { 109 return abcitypes.ResponseDeliverTx{Code: 0} 110 } 111 112 func (KVStoreApplication) CheckTx(req abcitypes.RequestCheckTx) abcitypes.ResponseCheckTx { 113 return abcitypes.ResponseCheckTx{Code: 0} 114 } 115 116 func (KVStoreApplication) Commit() abcitypes.ResponseCommit { 117 return abcitypes.ResponseCommit{} 118 } 119 120 func (KVStoreApplication) Query(req abcitypes.RequestQuery) abcitypes.ResponseQuery { 121 return abcitypes.ResponseQuery{Code: 0} 122 } 123 124 func (KVStoreApplication) InitChain(req abcitypes.RequestInitChain) abcitypes.ResponseInitChain { 125 return abcitypes.ResponseInitChain{} 126 } 127 128 func (KVStoreApplication) BeginBlock(req abcitypes.RequestBeginBlock) abcitypes.ResponseBeginBlock { 129 return abcitypes.ResponseBeginBlock{} 130 } 131 132 func (KVStoreApplication) EndBlock(req abcitypes.RequestEndBlock) abcitypes.ResponseEndBlock { 133 return abcitypes.ResponseEndBlock{} 134 } 135 136 func (KVStoreApplication) ListSnapshots(abcitypes.RequestListSnapshots) abcitypes.ResponseListSnapshots { 137 return abcitypes.ResponseListSnapshots{} 138 } 139 140 func (KVStoreApplication) OfferSnapshot(abcitypes.RequestOfferSnapshot) abcitypes.ResponseOfferSnapshot { 141 return abcitypes.ResponseOfferSnapshot{} 142 } 143 144 func (KVStoreApplication) LoadSnapshotChunk(abcitypes.RequestLoadSnapshotChunk) abcitypes.ResponseLoadSnapshotChunk { 145 return abcitypes.ResponseLoadSnapshotChunk{} 146 } 147 148 func (KVStoreApplication) ApplySnapshotChunk(abcitypes.RequestApplySnapshotChunk) abcitypes.ResponseApplySnapshotChunk { 149 return abcitypes.ResponseApplySnapshotChunk{} 150 } 151 ``` 152 153 Now I will go through each method explaining when it's called and adding 154 required business logic. 155 156 ### 1.3.1 CheckTx 157 158 When a new transaction is added to the Tendermint Core, it will ask the 159 application to check it (validate the format, signatures, etc.). 160 161 ```go 162 import "bytes" 163 164 func (app *KVStoreApplication) isValid(tx []byte) (code uint32) { 165 // check format 166 parts := bytes.Split(tx, []byte("=")) 167 if len(parts) != 2 { 168 return 1 169 } 170 171 key, value := parts[0], parts[1] 172 173 // check if the same key=value already exists 174 err := app.db.View(func(txn *badger.Txn) error { 175 item, err := txn.Get(key) 176 if err != nil && err != badger.ErrKeyNotFound { 177 return err 178 } 179 if err == nil { 180 return item.Value(func(val []byte) error { 181 if bytes.Equal(val, value) { 182 code = 2 183 } 184 return nil 185 }) 186 } 187 return nil 188 }) 189 if err != nil { 190 panic(err) 191 } 192 193 return code 194 } 195 196 func (app *KVStoreApplication) CheckTx(req abcitypes.RequestCheckTx) abcitypes.ResponseCheckTx { 197 code := app.isValid(req.Tx) 198 return abcitypes.ResponseCheckTx{Code: code, GasWanted: 1} 199 } 200 ``` 201 202 Don't worry if this does not compile yet. 203 204 If the transaction does not have a form of `{bytes}={bytes}`, we return `1` 205 code. When the same key=value already exist (same key and value), we return `2` 206 code. For others, we return a zero code indicating that they are valid. 207 208 Note that anything with non-zero code will be considered invalid (`-1`, `100`, 209 etc.) by Tendermint Core. 210 211 Valid transactions will eventually be committed given they are not too big and 212 have enough gas. To learn more about gas, check out ["the 213 specification"](https://docs.tendermint.com/master/spec/abci/apps.html#gas). 214 215 For the underlying key-value store we'll use 216 [badger](https://github.com/dgraph-io/badger), which is an embeddable, 217 persistent and fast key-value (KV) database. 218 219 ```go 220 import "github.com/dgraph-io/badger" 221 222 type KVStoreApplication struct { 223 db *badger.DB 224 currentBatch *badger.Txn 225 } 226 227 func NewKVStoreApplication(db *badger.DB) *KVStoreApplication { 228 return &KVStoreApplication{ 229 db: db, 230 } 231 } 232 ``` 233 234 ### 1.3.2 BeginBlock -> DeliverTx -> EndBlock -> Commit 235 236 When Tendermint Core has decided on the block, it's transferred to the 237 application in 3 parts: `BeginBlock`, one `DeliverTx` per transaction and 238 `EndBlock` in the end. DeliverTx are being transferred asynchronously, but the 239 responses are expected to come in order. 240 241 ```go 242 func (app *KVStoreApplication) BeginBlock(req abcitypes.RequestBeginBlock) abcitypes.ResponseBeginBlock { 243 app.currentBatch = app.db.NewTransaction(true) 244 return abcitypes.ResponseBeginBlock{} 245 } 246 ``` 247 248 Here we create a batch, which will store block's transactions. 249 250 ```go 251 func (app *KVStoreApplication) DeliverTx(req abcitypes.RequestDeliverTx) abcitypes.ResponseDeliverTx { 252 code := app.isValid(req.Tx) 253 if code != 0 { 254 return abcitypes.ResponseDeliverTx{Code: code} 255 } 256 257 parts := bytes.Split(req.Tx, []byte("=")) 258 key, value := parts[0], parts[1] 259 260 err := app.currentBatch.Set(key, value) 261 if err != nil { 262 panic(err) 263 } 264 265 return abcitypes.ResponseDeliverTx{Code: 0} 266 } 267 ``` 268 269 If the transaction is badly formatted or the same key=value already exist, we 270 again return the non-zero code. Otherwise, we add it to the current batch. 271 272 In the current design, a block can include incorrect transactions (those who 273 passed CheckTx, but failed DeliverTx or transactions included by the proposer 274 directly). This is done for performance reasons. 275 276 Note we can't commit transactions inside the `DeliverTx` because in such case 277 `Query`, which may be called in parallel, will return inconsistent data (i.e. 278 it will report that some value already exist even when the actual block was not 279 yet committed). 280 281 `Commit` instructs the application to persist the new state. 282 283 ```go 284 func (app *KVStoreApplication) Commit() abcitypes.ResponseCommit { 285 app.currentBatch.Commit() 286 return abcitypes.ResponseCommit{Data: []byte{}} 287 } 288 ``` 289 290 ### 1.3.3 Query 291 292 Now, when the client wants to know whenever a particular key/value exist, it 293 will call Tendermint Core RPC `/abci_query` endpoint, which in turn will call 294 the application's `Query` method. 295 296 Applications are free to provide their own APIs. But by using Tendermint Core 297 as a proxy, clients (including [light client 298 package](https://godoc.org/github.com/number571/tendermint/light)) can leverage 299 the unified API across different applications. Plus they won't have to call the 300 otherwise separate Tendermint Core API for additional proofs. 301 302 Note we don't include a proof here. 303 304 ```go 305 func (app *KVStoreApplication) Query(reqQuery abcitypes.RequestQuery) (resQuery abcitypes.ResponseQuery) { 306 resQuery.Key = reqQuery.Data 307 err := app.db.View(func(txn *badger.Txn) error { 308 item, err := txn.Get(reqQuery.Data) 309 if err != nil && err != badger.ErrKeyNotFound { 310 return err 311 } 312 if err == badger.ErrKeyNotFound { 313 resQuery.Log = "does not exist" 314 } else { 315 return item.Value(func(val []byte) error { 316 resQuery.Log = "exists" 317 resQuery.Value = val 318 return nil 319 }) 320 } 321 return nil 322 }) 323 if err != nil { 324 panic(err) 325 } 326 return 327 } 328 ``` 329 330 The complete specification can be found 331 [here](https://docs.tendermint.com/master/spec/abci/). 332 333 ## 1.4 Starting an application and a Tendermint Core instances 334 335 Put the following code into the "main.go" file: 336 337 ```go 338 package main 339 340 import ( 341 "flag" 342 "fmt" 343 "os" 344 "os/signal" 345 "syscall" 346 347 "github.com/dgraph-io/badger" 348 349 abciserver "github.com/number571/tendermint/abci/server" 350 "github.com/number571/tendermint/libs/log" 351 ) 352 353 var socketAddr string 354 355 func init() { 356 flag.StringVar(&socketAddr, "socket-addr", "unix://example.sock", "Unix domain socket address") 357 } 358 359 func main() { 360 db, err := badger.Open(badger.DefaultOptions("/tmp/badger")) 361 if err != nil { 362 fmt.Fprintf(os.Stderr, "failed to open badger db: %v", err) 363 os.Exit(1) 364 } 365 defer db.Close() 366 app := NewKVStoreApplication(db) 367 368 flag.Parse() 369 370 logger := log.NewTMLogger(log.NewSyncWriter(os.Stdout)) 371 372 server := abciserver.NewSocketServer(socketAddr, app) 373 server.SetLogger(logger) 374 if err := server.Start(); err != nil { 375 fmt.Fprintf(os.Stderr, "error starting socket server: %v", err) 376 os.Exit(1) 377 } 378 defer server.Stop() 379 380 c := make(chan os.Signal, 1) 381 signal.Notify(c, os.Interrupt, syscall.SIGTERM) 382 <-c 383 os.Exit(0) 384 } 385 ``` 386 387 This is a huge blob of code, so let's break it down into pieces. 388 389 First, we initialize the Badger database and create an app instance: 390 391 ```go 392 db, err := badger.Open(badger.DefaultOptions("/tmp/badger")) 393 if err != nil { 394 fmt.Fprintf(os.Stderr, "failed to open badger db: %v", err) 395 os.Exit(1) 396 } 397 defer db.Close() 398 app := NewKVStoreApplication(db) 399 ``` 400 401 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). 402 This can be avoided by setting the truncate option to true, like this: 403 404 ```go 405 db, err := badger.Open(badger.DefaultOptions("/tmp/badger").WithTruncate(true)) 406 ``` 407 408 Then we start the ABCI server and add some signal handling to gracefully stop 409 it upon receiving SIGTERM or Ctrl-C. Tendermint Core will act as a client, 410 which connects to our server and send us transactions and other messages. 411 412 ```go 413 server := abciserver.NewSocketServer(socketAddr, app) 414 server.SetLogger(logger) 415 if err := server.Start(); err != nil { 416 fmt.Fprintf(os.Stderr, "error starting socket server: %v", err) 417 os.Exit(1) 418 } 419 defer server.Stop() 420 421 c := make(chan os.Signal, 1) 422 signal.Notify(c, os.Interrupt, syscall.SIGTERM) 423 <-c 424 os.Exit(0) 425 ``` 426 427 ## 1.5 Getting Up and Running 428 429 We are going to use [Go modules](https://github.com/golang/go/wiki/Modules) for 430 dependency management. 431 432 ```bash 433 export GO111MODULE=on 434 go mod init github.com/me/example 435 ``` 436 437 This should create a `go.mod` file. The current tutorial only works with 438 the master branch of Tendermint, so let's make sure we're using the latest version: 439 440 ```sh 441 go get github.com/number571/tendermint@master 442 ``` 443 444 This will populate the `go.mod` with a release number followed by a hash for Tendermint. 445 446 ```go 447 module github.com/me/example 448 449 go 1.16 450 451 require ( 452 github.com/dgraph-io/badger v1.6.2 453 github.com/number571/tendermint <vX> 454 ) 455 ``` 456 457 Now we can build the binary: 458 459 ```bash 460 go build 461 ``` 462 463 To create a default configuration, nodeKey and private validator files, let's 464 execute `tendermint init validator`. But before we do that, we will need to install 465 Tendermint Core. Please refer to [the official 466 guide](https://docs.tendermint.com/master/introduction/install.html). If you're 467 installing from source, don't forget to checkout the latest release (`git 468 checkout vX.Y.Z`). Don't forget to check that the application uses the same 469 major version. 470 471 ```bash 472 rm -rf /tmp/example 473 TMHOME="/tmp/example" tendermint init validator 474 475 I[2019-07-16|18:20:36.480] Generated private validator module=main keyFile=/tmp/example/config/priv_validator_key.json stateFile=/tmp/example2/data/priv_validator_state.json 476 I[2019-07-16|18:20:36.481] Generated node key module=main path=/tmp/example/config/node_key.json 477 I[2019-07-16|18:20:36.482] Generated genesis file module=main path=/tmp/example/config/genesis.json 478 I[2019-07-16|18:20:36.483] Generated config module=main mode=validator 479 ``` 480 481 Feel free to explore the generated files, which can be found at 482 `/tmp/example/config` directory. Documentation on the config can be found 483 [here](https://docs.tendermint.com/master/tendermint-core/configuration.html). 484 485 We are ready to start our application: 486 487 ```bash 488 rm example.sock 489 ./example 490 491 badger 2019/07/16 18:25:11 INFO: All 0 tables opened in 0s 492 badger 2019/07/16 18:25:11 INFO: Replaying file id: 0 at offset: 0 493 badger 2019/07/16 18:25:11 INFO: Replay took: 300.4s 494 I[2019-07-16|18:25:11.523] Starting ABCIServer impl=ABCIServ 495 ``` 496 497 Then we need to start Tendermint Core and point it to our application. Staying 498 within the application directory execute: 499 500 ```bash 501 TMHOME="/tmp/example" tendermint node --proxy-app=unix://example.sock 502 503 I[2019-07-16|18:26:20.362] Version info module=main software=0.32.1 block=10 p2p=7 504 I[2019-07-16|18:26:20.383] Starting Node module=main impl=Node 505 E[2019-07-16|18:26:20.392] Couldn't connect to any seeds module=p2p 506 I[2019-07-16|18:26:20.394] Started node module=main nodeInfo="{ProtocolVersion:{P2P:7 Block:10 App:0} ID_:8dab80770ae8e295d4ce905d86af78c4ff634b79 ListenAddr:tcp://0.0.0.0:26656 Network:test-chain-nIO96P Version:0.32.1 Channels:4020212223303800 Moniker:app48.fun-box.ru Other:{TxIndex:on RPCAddress:tcp://127.0.0.1:26657}}" 507 I[2019-07-16|18:26:21.440] Executed block module=state height=1 validTxs=0 invalidTxs=0 508 I[2019-07-16|18:26:21.446] Committed state module=state height=1 txs=0 appHash= 509 ``` 510 511 This should start the full node and connect to our ABCI application. 512 513 ```sh 514 I[2019-07-16|18:25:11.525] Waiting for new connection... 515 I[2019-07-16|18:26:20.329] Accepted a new connection 516 I[2019-07-16|18:26:20.329] Waiting for new connection... 517 I[2019-07-16|18:26:20.330] Accepted a new connection 518 I[2019-07-16|18:26:20.330] Waiting for new connection... 519 I[2019-07-16|18:26:20.330] Accepted a new connection 520 ``` 521 522 Now open another tab in your terminal and try sending a transaction: 523 524 ```json 525 curl -s 'localhost:26657/broadcast_tx_commit?tx="tendermint=rocks"' 526 { 527 "jsonrpc": "2.0", 528 "id": "", 529 "result": { 530 "check_tx": { 531 "gasWanted": "1" 532 }, 533 "deliver_tx": {}, 534 "hash": "CDD3C6DFA0A08CAEDF546F9938A2EEC232209C24AA0E4201194E0AFB78A2C2BB", 535 "height": "33" 536 } 537 ``` 538 539 Response should contain the height where this transaction was committed. 540 541 Now let's check if the given key now exists and its value: 542 543 ```json 544 curl -s 'localhost:26657/abci_query?data="tendermint"' 545 { 546 "jsonrpc": "2.0", 547 "id": "", 548 "result": { 549 "response": { 550 "log": "exists", 551 "key": "dGVuZGVybWludA==", 552 "value": "cm9ja3My" 553 } 554 } 555 } 556 ``` 557 558 "dGVuZGVybWludA==" and "cm9ja3M=" are the base64-encoding of the ASCII of 559 "tendermint" and "rocks" accordingly. 560 561 ## Outro 562 563 I hope everything went smoothly and your first, but hopefully not the last, 564 Tendermint Core application is up and running. If not, please [open an issue on 565 Github](https://github.com/number571/tendermint/issues/new/choose). To dig 566 deeper, read [the docs](https://docs.tendermint.com/master/).