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.

568 lines
16 KiB

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