You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

653 lines
19 KiB

  1. <!---
  2. order: 2
  3. --->
  4. # Creating a built-in application in Go
  5. ## Guide assumptions
  6. This guide is designed for beginners who want to get started with a Tendermint
  7. Core application from scratch. It does not assume that you have any prior
  8. experience with Tendermint Core.
  9. Tendermint Core is a service that provides a Byzantine fault tolerant consensus engine
  10. for state-machine replication. The replicated state-machine, or "application", can be written
  11. in any language that can send and receive protocol buffer messages.
  12. This tutorial is written for Go and uses Tendermint as a library, but applications not
  13. written in Go can use Tendermint to drive state-machine replication in a client-server
  14. model.
  15. This tutorial expects some understanding of the Go programming language.
  16. If you have never written Go, you may want to go through [Learn X in Y minutes
  17. Where X=Go](https://learnxinyminutes.com/docs/go/) first to familiarize
  18. yourself with the syntax.
  19. By following along with this guide, you'll create a Tendermint Core application
  20. called kvstore, a (very) simple distributed BFT key-value store.
  21. > Note: please use a released version of Tendermint with this guide. The guides will work with the latest released version.
  22. > Be aware that they may not apply to unreleased changes on master.
  23. > We strongly advise against using unreleased commits for your development.
  24. ## 1.1 Installing Go
  25. Please refer to [the official guide for installing
  26. Go](https://golang.org/doc/install).
  27. Verify that you have the latest version of Go installed:
  28. ```bash
  29. $ go version
  30. go version go1.17.5 darwin/amd64
  31. ```
  32. Note that the exact patch number may differ as Go releases come out.
  33. ## 1.2 Creating a new Go project
  34. We'll start by creating a new Go project.
  35. ```bash
  36. mkdir kvstore
  37. cd kvstore
  38. go mod init github.com/<github_username>/<repo_name>
  39. ```
  40. Inside the example directory create a `main.go` file with the following content:
  41. > Note: there is no need to clone or fork Tendermint in this tutorial.
  42. ```go
  43. package main
  44. import (
  45. "fmt"
  46. )
  47. func main() {
  48. fmt.Println("Hello, Tendermint Core")
  49. }
  50. ```
  51. When run, this should print "Hello, Tendermint Core" to the standard output.
  52. ```bash
  53. $ go run main.go
  54. Hello, Tendermint Core
  55. ```
  56. ## 1.3 Writing a Tendermint Core application
  57. Tendermint Core communicates with an application through the Application
  58. BlockChain Interface (ABCI) protocol. All of the message types Tendermint uses for
  59. communicating with the application can be found in the ABCI [protobuf
  60. file](https://github.com/tendermint/spec/blob/b695d30aae69933bc0e630da14949207d18ae02c/proto/tendermint/abci/types.proto).
  61. We will begin by creating the basic scaffolding for an ABCI application in
  62. a new `app.go` file. The first step is to create a new type, `KVStoreApplication`
  63. with methods that implement the abci `Application` interface.
  64. Create a file called `app.go` and add the following contents:
  65. ```go
  66. package main
  67. import (
  68. abcitypes "github.com/tendermint/tendermint/abci/types"
  69. )
  70. type KVStoreApplication struct {}
  71. var _ abcitypes.Application = (*KVStoreApplication)(nil)
  72. func NewKVStoreApplication() *KVStoreApplication {
  73. return &KVStoreApplication{}
  74. }
  75. func (app *KVStoreApplication) Info(req abcitypes.RequestInfo) abcitypes.ResponseInfo {
  76. return abcitypes.ResponseInfo{}
  77. }
  78. func (app *KVStoreApplication) DeliverTx(req abcitypes.RequestDeliverTx) abcitypes.ResponseDeliverTx {
  79. return abcitypes.ResponseDeliverTx{Code: 0}
  80. }
  81. func (app *KVStoreApplication) CheckTx(req abcitypes.RequestCheckTx) abcitypes.ResponseCheckTx {
  82. return abcitypes.ResponseCheckTx{Code: 0}
  83. }
  84. func (app *KVStoreApplication) Commit() abcitypes.ResponseCommit {
  85. return abcitypes.ResponseCommit{}
  86. }
  87. func (app *KVStoreApplication) Query(req abcitypes.RequestQuery) abcitypes.ResponseQuery {
  88. return abcitypes.ResponseQuery{Code: 0}
  89. }
  90. func (app *KVStoreApplication) InitChain(req abcitypes.RequestInitChain) abcitypes.ResponseInitChain {
  91. return abcitypes.ResponseInitChain{}
  92. }
  93. func (app *KVStoreApplication) BeginBlock(req abcitypes.RequestBeginBlock) abcitypes.ResponseBeginBlock {
  94. return abcitypes.ResponseBeginBlock{}
  95. }
  96. func (app *KVStoreApplication) EndBlock(req abcitypes.RequestEndBlock) abcitypes.ResponseEndBlock {
  97. return abcitypes.ResponseEndBlock{}
  98. }
  99. func (app *KVStoreApplication) ListSnapshots(abcitypes.RequestListSnapshots) abcitypes.ResponseListSnapshots {
  100. return abcitypes.ResponseListSnapshots{}
  101. }
  102. func (app *KVStoreApplication) OfferSnapshot(abcitypes.RequestOfferSnapshot) abcitypes.ResponseOfferSnapshot {
  103. return abcitypes.ResponseOfferSnapshot{}
  104. }
  105. func (app *KVStoreApplication) LoadSnapshotChunk(abcitypes.RequestLoadSnapshotChunk) abcitypes.ResponseLoadSnapshotChunk {
  106. return abcitypes.ResponseLoadSnapshotChunk{}
  107. }
  108. func (app *KVStoreApplication) ApplySnapshotChunk(abcitypes.RequestApplySnapshotChunk) abcitypes.ResponseApplySnapshotChunk {
  109. return abcitypes.ResponseApplySnapshotChunk{}
  110. }
  111. ```
  112. ### 1.3.1 Add a persistent data store
  113. Our application will need to write its state out to persistent storage so that it
  114. can stop and start without losing all of its data.
  115. For this tutorial, we will use [BadgerDB](https://github.com/dgraph-io/badger).
  116. Badger is a fast embedded key-value store.
  117. First, add Badger as a dependency of your go module using the `go get` command:
  118. `go get github.com/dgraph-io/badger/v3`
  119. Next, let's update the application and its constructor to receive a handle to the
  120. database.
  121. Update the application struct as follows:
  122. ```go
  123. type KVStoreApplication struct {
  124. db *badger.DB
  125. pendingBlock *badger.Txn
  126. }
  127. ```
  128. And change the constructor to set the appropriate field when creating the application:
  129. ```go
  130. func NewKVStoreApplication(db *badger.DB) *KVStoreApplication {
  131. return &KVStoreApplication{db: db}
  132. }
  133. ```
  134. The `pendingBlock` keeps track of the transactions that will update the application's
  135. state when a block is completed. Don't worry about it for now, we'll get to that later.
  136. Finally, update the `import` stanza at the top to include the `Badger` library:
  137. ```go
  138. import(
  139. "github.com/dgraph-io/badger/v3"
  140. abcitypes "github.com/tendermint/tendermint/abci/types"
  141. )
  142. ```
  143. ### 1.3.1 CheckTx
  144. When Tendermint Core receives a new transaction, Tendermint asks the application
  145. if the transaction is acceptable. In our new application, let's implement some
  146. basic validation for the transactions it will receive.
  147. For our KV store application, a transaction is a string with the form `key=value`,
  148. indicating a key and value to write to the store.
  149. Add the following helper method to `app.go`:
  150. ```go
  151. func (app *KVStoreApplication) validateTx(tx []byte) uint32 {
  152. parts := bytes.SplitN(tx, []byte("="), 2)
  153. // check that the transaction is not malformed
  154. if len(parts) != 2 || len(parts[0]) == 0 {
  155. return 1
  156. }
  157. return 0
  158. }
  159. ```
  160. And call it from within your `CheckTx` method:
  161. ```go
  162. func (app *KVStoreApplication) CheckTx(req abcitypes.RequestCheckTx) abcitypes.ResponseCheckTx {
  163. code := app.validateTx(req.Tx)
  164. return abcitypes.ResponseCheckTx{Code: code}
  165. }
  166. ```
  167. Any response with a non-zero code will be considered invalid by Tendermint.
  168. Our `CheckTx` logic returns `0` to Tendermint when a transaction passes
  169. its validation checks. The specific value of the code is meaningless to Tendermint.
  170. Non-zero codes are logged by Tendermint so applications can provide more specific
  171. information on why the transaction was rejected.
  172. Note that `CheckTx` _does not execute_ the transaction, it only verifies that that the
  173. transaction _could_ be executed. We do not know yet if the rest of the network has
  174. agreed to accept this transaction into a block.
  175. Finally, make sure to add the `bytes` package to the your import stanza
  176. at the top of `app.go`:
  177. ```go
  178. import(
  179. "bytes"
  180. "github.com/dgraph-io/badger/v3"
  181. abcitypes "github.com/tendermint/tendermint/abci/types"
  182. )
  183. ```
  184. While this `CheckTx` is simple and only validates that the transaction is well-formed,
  185. it is very common for `CheckTx` to make more complex use of the state of an application.
  186. ### 1.3.2 BeginBlock -> DeliverTx -> EndBlock -> Commit
  187. When the Tendermint consensus engine has decided on the block, the block is transferred to the
  188. application over three ABCI method calls: `BeginBlock`, `DeliverTx`, and `EndBlock`.
  189. `BeginBlock` is called once to indicate to the application that it is about to
  190. receive a block.
  191. `DeliverTx` is called repeatedly, once for each `Tx` that was included in the block.
  192. `EndBlock` is called once to indicate to the application that no more transactions
  193. will be delivered to the application.
  194. To implement these calls in our application we're going to make use of Badger's
  195. transaction mechanism. Bagder uses the term _transaction_ in the context of databases,
  196. be careful not to confuse it with _blockchain transactions_.
  197. First, let's create a new Badger `Txn` during `BeginBlock`:
  198. ```go
  199. func (app *KVStoreApplication) BeginBlock(req abcitypes.RequestBeginBlock) abcitypes.ResponseBeginBlock {
  200. app.pendingBlock = app.db.NewTransaction(true)
  201. return abcitypes.ResponseBeginBlock{}
  202. }
  203. ```
  204. Next, let's modify `DeliverTx` to add the `key` and `value` to the database `Txn` every time our application
  205. receives a new `RequestDeliverTx`.
  206. ```go
  207. func (app *KVStoreApplication) DeliverTx(req abcitypes.RequestDeliverTx) abcitypes.ResponseDeliverTx {
  208. if code := app.validateTx(req.Tx); code != 0 {
  209. return abcitypes.ResponseDeliverTx{Code: code}
  210. }
  211. parts := bytes.SplitN(req.Tx, []byte("="), 2)
  212. key, value := parts[0], parts[1]
  213. if err := app.pendingBlock.Set(key, value); err != nil {
  214. log.Panicf("Error reading database, unable to verify tx: %v", err)
  215. }
  216. return abcitypes.ResponseDeliverTx{Code: 0}
  217. }
  218. ```
  219. Note that we check the validity of the transaction _again_ during `DeliverTx`.
  220. Transactions are not guaranteed to be valid when they are delivered to an
  221. application. This can happen if the application state is used to determine transaction
  222. validity. Application state may have changed between when the `CheckTx` was initially
  223. called and when the transaction was delivered in `DeliverTx` in a way that rendered
  224. the transaction no longer valid.
  225. Also note that we don't commit the Badger `Txn` we are building during `DeliverTx`.
  226. Other methods, such as `Query`, rely on a consistent view of the application's state.
  227. The application should only update its state when the full block has been delivered.
  228. The `Commit` method indicates that the full block has been delivered. During `Commit`,
  229. the application should persist the pending `Txn`.
  230. Let's modify our `Commit` method to persist the new state to the database:
  231. ```go
  232. func (app *KVStoreApplication) Commit() abcitypes.ResponseCommit {
  233. if err := app.pendingBlock.Commit(); err != nil {
  234. log.Panicf("Error writing to database, unable to commit block: %v", err)
  235. }
  236. return abcitypes.ResponseCommit{Data: []byte{}}
  237. }
  238. ```
  239. Finally, make sure to add the `log` library to the `import` stanza as well:
  240. ```go
  241. import (
  242. "bytes"
  243. "log"
  244. "github.com/dgraph-io/badger/v3"
  245. abcitypes "github.com/tendermint/tendermint/abci/types"
  246. )
  247. ```
  248. You may have noticed that the application we are writing will _crash_ if it receives an
  249. unexpected error from the database during the `DeliverTx` or `Commit` methods.
  250. This is not an accident. If the application received an error from the database,
  251. there is no deterministic way for it to make progress so the only safe option is to terminate.
  252. ### 1.3.3 Query Method
  253. We'll want to be able to determine if a transaction was committed to the state-machine.
  254. To do this, let's implement the `Query` method in `app.go`:
  255. ```go
  256. func (app *KVStoreApplication) Query(req abcitypes.RequestQuery) abcitypes.ResponseQuery {
  257. resp := abcitypes.ResponseQuery{Key: req.Data}
  258. dbErr := app.db.View(func(txn *badger.Txn) error {
  259. item, err := txn.Get(req.Data)
  260. if err != nil {
  261. if err != badger.ErrKeyNotFound {
  262. return err
  263. }
  264. resp.Log = "key does not exist"
  265. return nil
  266. }
  267. return item.Value(func(val []byte) error {
  268. resp.Log = "exists"
  269. resp.Value = val
  270. return nil
  271. })
  272. })
  273. if dbErr != nil {
  274. log.Panicf("Error reading database, unable to verify tx: %v", dbErr)
  275. }
  276. return resp
  277. }
  278. ```
  279. ## 1.3.4 Additional Methods
  280. You'll notice that we left several methods unchanged. Specifically, we have yet
  281. to implement the `Info` and `InitChain` methods and we did not implement
  282. any of the `*Snapthot` methods. These methods are all important for running Tendermint
  283. applications in production but are not required for getting a very simple application
  284. up and running.
  285. To better understand these methods and why they are useful, check out the Tendermint
  286. [specification on ABCI](https://github.com/tendermint/spec/tree/20b2abb5f9a83c2d9d97b53e555e4ea5a6bd7dc4/spec/abci).
  287. ## 1.4 Starting an application and a Tendermint Core instance in the same process
  288. Now that we have the basic functionality of our application in place, let's put it
  289. all together inside of our `main.go` file.
  290. Add the following code to your `main.go` file:
  291. ```go
  292. package main
  293. import (
  294. "flag"
  295. "fmt"
  296. "log"
  297. "os"
  298. "os/signal"
  299. "path/filepath"
  300. "syscall"
  301. "github.com/dgraph-io/badger/v3"
  302. "github.com/spf13/viper"
  303. abciclient "github.com/tendermint/tendermint/abci/client"
  304. cfg "github.com/tendermint/tendermint/config"
  305. tmlog "github.com/tendermint/tendermint/libs/log"
  306. nm "github.com/tendermint/tendermint/node"
  307. "github.com/tendermint/tendermint/types"
  308. )
  309. var homeDir string
  310. func init() {
  311. flag.StringVar(&homeDir, "tm-home", "", "Path to the tendermint config directory (if empty, uses $HOME/.tendermint)")
  312. }
  313. func main() {
  314. flag.Parse()
  315. if homeDir == "" {
  316. homeDir = os.ExpandEnv("$HOME/.tendermint")
  317. }
  318. config := cfg.DefaultValidatorConfig()
  319. config.SetRoot(homeDir)
  320. viper.SetConfigFile(fmt.Sprintf("%s/%s", homeDir, "config/config.toml"))
  321. if err := viper.ReadInConfig(); err != nil {
  322. log.Fatalf("Reading config: %v", err)
  323. }
  324. if err := viper.Unmarshal(config); err != nil {
  325. log.Fatalf("Decoding config: %v", err)
  326. }
  327. if err := config.ValidateBasic(); err != nil {
  328. log.Fatalf("Invalid configuration data: %v", err)
  329. }
  330. gf, err := types.GenesisDocFromFile(config.GenesisFile())
  331. if err != nil {
  332. log.Fatalf("Loading genesis document: %v", err)
  333. }
  334. dbPath := filepath.Join(homeDir, "badger")
  335. db, err := badger.Open(badger.DefaultOptions(dbPath))
  336. if err != nil {
  337. log.Fatalf("Opening database: %v", err)
  338. }
  339. defer func() {
  340. if err := db.Close(); err != nil {
  341. log.Fatalf("Closing database: %v", err)
  342. }
  343. }()
  344. app := NewKVStoreApplication(db)
  345. acc := abciclient.NewLocalCreator(app)
  346. logger := tmlog.MustNewDefaultLogger(tmlog.LogFormatPlain, tmlog.LogLevelInfo, false)
  347. node, err := nm.New(config, logger, acc, gf)
  348. if err != nil {
  349. log.Fatalf("Creating node: %v", err)
  350. }
  351. node.Start()
  352. defer func() {
  353. node.Stop()
  354. node.Wait()
  355. }()
  356. c := make(chan os.Signal, 1)
  357. signal.Notify(c, os.Interrupt, syscall.SIGTERM)
  358. <-c
  359. }
  360. ```
  361. This is a huge blob of code, so let's break down what it's doing.
  362. First, we load in the Tendermint Core configuration files:
  363. ```go
  364. ...
  365. config := cfg.DefaultValidatorConfig()
  366. config.SetRoot(homeDir)
  367. viper.SetConfigFile(fmt.Sprintf("%s/%s", homeDir, "config/config.toml"))
  368. if err := viper.ReadInConfig(); err != nil {
  369. log.Fatalf("Reading config: %v", err)
  370. }
  371. if err := viper.Unmarshal(config); err != nil {
  372. log.Fatalf("Decoding config: %v", err)
  373. }
  374. if err := config.ValidateBasic(); err != nil {
  375. log.Fatalf("Invalid configuration data: %v", err)
  376. }
  377. gf, err := types.GenesisDocFromFile(config.GenesisFile())
  378. if err != nil {
  379. log.Fatalf("Loading genesis document: %v", err)
  380. }
  381. ...
  382. ```
  383. Next, we create a database handle and use it to construct our ABCI application:
  384. ```go
  385. ...
  386. dbPath := filepath.Join(homeDir, "badger")
  387. db, err := badger.Open(badger.DefaultOptions(dbPath).WithTruncate(true))
  388. if err != nil {
  389. log.Fatalf("Opening database: %v", err)
  390. }
  391. defer func() {
  392. if err := db.Close(); err != nil {
  393. log.Fatalf("Error closing database: %v", err)
  394. }
  395. }()
  396. app := NewKVStoreApplication(db)
  397. acc := abciclient.NewLocalCreator(app)
  398. ...
  399. ```
  400. Then we construct a logger:
  401. ```go
  402. ...
  403. logger := tmlog.MustNewDefaultLogger(tmlog.LogFormatPlain, tmlog.LogLevelInfo, false)
  404. ...
  405. ```
  406. Now we have everything setup to run the Tendermint node. We construct
  407. a node by passing it the configuration, the logger, a handle to our application and
  408. the genesis file:
  409. ```go
  410. ...
  411. node, err := nm.New(config, logger, acc, gf)
  412. if err != nil {
  413. log.Fatalf("Creating node: %v", err)
  414. }
  415. ...
  416. ```
  417. Finally, we start the node:
  418. ```go
  419. ...
  420. node.Start()
  421. defer func() {
  422. node.Stop()
  423. node.Wait()
  424. }()
  425. ...
  426. ```
  427. The additional logic at the end of the file allows the program to catch `SIGTERM`.
  428. This means that the node can shutdown gracefully when an operator tries to kill the program:
  429. ```go
  430. ...
  431. c := make(chan os.Signal, 1)
  432. signal.Notify(c, os.Interrupt, syscall.SIGTERM)
  433. <-c
  434. ...
  435. ```
  436. ## 1.5 Getting Up and Running
  437. Our application is almost ready to run.
  438. Let's install the latest release version of the Tendermint library.
  439. From inside of the project directory, run:
  440. ```sh
  441. go get github.com/tendermint/tendermint@v0.35.0
  442. ```
  443. Next, we'll need to populate the Tendermint Core configuration files.
  444. This command will create a `tendermint-home` directory in your project and add a basic set of configuration
  445. files in `tendermint-home/config/`. For more information on what these files contain
  446. see [the configuration documentation](https://github.com/tendermint/tendermint/blob/v0.35.0/docs/nodes/configuration.md).
  447. From the root of your project, run:
  448. ```bash
  449. go run github.com/tendermint/tendermint/cmd/tendermint@v0.35.0 init validator --home ./tendermint-home
  450. ```
  451. Next, build the application:
  452. ```bash
  453. go build -mod=mod -o my-app # use -mod=mod to automatically update go.sum
  454. ```
  455. Everything is now in place to run your application.
  456. Run:
  457. ```bash
  458. $ ./my-app -tm-home ./tendermint-home
  459. ```
  460. The application will begin producing blocks and you can see this reflected in
  461. the log output.
  462. You now have successfully started running an application using Tendermint Core 🎉🎉.
  463. ## 1.6 Using the application
  464. Your application is now running and emitting logs to the terminal.
  465. Now it's time to see what this application can do!
  466. Let's try submitting a transaction to our new application.
  467. Open another terminal window and run the following curl command:
  468. ```bash
  469. $ curl -s 'localhost:26657/broadcast_tx_commit?tx="tendermint=rocks"'
  470. ```
  471. If everything went well, you should see a response indicating which height the
  472. transaction was included in the blockchain.
  473. Finally, let's make sure that transaction really was persisted by the application.
  474. Run the following command:
  475. ```bash
  476. $ curl -s 'localhost:26657/abci_query?data="tendermint"'
  477. ```
  478. Let's examine the response object that this request returns.
  479. The request returns a `json` object with a `key` and `value` field set.
  480. ```json
  481. ...
  482. "key": "dGVuZGVybWludA==",
  483. "value": "cm9ja3M=",
  484. ...
  485. ```
  486. Those values don't look like the `key` and `value` we sent to Tendermint,
  487. what's going on here?
  488. The response contain a `base64` encoded representation of the data we submitted.
  489. To get the original value out of this data, we can use the `base64` command line utility.
  490. Run:
  491. ```
  492. echo cm9ja3M=" | base64 -d
  493. ```
  494. ## Outro
  495. I hope everything went smoothly and your first, but hopefully not the last,
  496. Tendermint Core application is up and running. If not, please [open an issue on
  497. Github](https://github.com/tendermint/tendermint/issues/new/choose). To dig
  498. deeper, read [the docs](https://docs.tendermint.com/master/).