github.com/number571/tendermint@v0.34.11-gost/docs/tutorials/kotlin.md (about) 1 <!--- 2 order: 4 3 ---> 4 5 # Creating an application in Kotlin 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 (your application) - written in any programming language - and securely 15 replicates it on many machines. 16 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. The application (which should 19 implementing the blockchain interface (ABCI)) will be written in Kotlin. 20 21 This guide assumes that you are not new to JVM world. If you are new please see [JVM Minimal Survival Guide](https://hadihariri.com/2013/12/29/jvm-minimal-survival-guide-for-the-dotnet-developer/#java-the-language-java-the-ecosystem-java-the-jvm) and [Gradle Docs](https://docs.gradle.org/current/userguide/userguide.html). 22 23 ## Built-in app vs external app 24 25 If you use Golang, you can run your app and Tendermint Core in the same process to get maximum performance. 26 [Cosmos SDK](https://github.com/cosmos/cosmos-sdk) is written this way. 27 Please refer to [Writing a built-in Tendermint Core application in Go](./go-built-in.md) guide for details. 28 29 If you choose another language, like we did in this guide, you have to write a separate app, 30 which will communicate with Tendermint Core via a socket (UNIX or TCP) or gRPC. 31 This guide will show you how to build external application using RPC server. 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 Java and Gradle 38 39 Please refer to [the Oracle's guide for installing JDK](https://www.oracle.com/technetwork/java/javase/downloads/index.html). 40 41 Verify that you have installed Java successfully: 42 43 ```bash 44 java -version 45 java version "1.8.0_162" 46 Java(TM) SE Runtime Environment (build 1.8.0_162-b12) 47 Java HotSpot(TM) 64-Bit Server VM (build 25.162-b12, mixed mode) 48 ``` 49 50 You can choose any version of Java higher or equal to 8. 51 In my case it is Java SE Development Kit 8. 52 53 Make sure you have `$JAVA_HOME` environment variable set: 54 55 ```bash 56 echo $JAVA_HOME 57 /Library/Java/JavaVirtualMachines/jdk1.8.0_162.jdk/Contents/Home 58 ``` 59 60 For Gradle installation, please refer to [their official guide](https://gradle.org/install/). 61 62 ## 1.2 Creating a new Kotlin project 63 64 We'll start by creating a new Gradle project. 65 66 ```bash 67 export KVSTORE_HOME=~/kvstore 68 mkdir $KVSTORE_HOME 69 cd $KVSTORE_HOME 70 ``` 71 72 Inside the example directory run: 73 74 ```bash 75 gradle init --dsl groovy --package io.example --project-name example --type kotlin-application 76 ``` 77 78 This will create a new project for you. The tree of files should look like: 79 80 ```bash 81 tree 82 . 83 |-- build.gradle 84 |-- gradle 85 | `-- wrapper 86 | |-- gradle-wrapper.jar 87 | `-- gradle-wrapper.properties 88 |-- gradlew 89 |-- gradlew.bat 90 |-- settings.gradle 91 `-- src 92 |-- main 93 | |-- kotlin 94 | | `-- io 95 | | `-- example 96 | | `-- App.kt 97 | `-- resources 98 `-- test 99 |-- kotlin 100 | `-- io 101 | `-- example 102 | `-- AppTest.kt 103 `-- resources 104 ``` 105 106 When run, this should print "Hello world." to the standard output. 107 108 ```bash 109 ./gradlew run 110 > Task :run 111 Hello world. 112 ``` 113 114 ## 1.3 Writing a Tendermint Core application 115 116 Tendermint Core communicates with the application through the Application 117 BlockChain Interface (ABCI). All message types are defined in the [protobuf 118 file](https://github.com/number571/tendermint/blob/master/proto/tendermint/abci/types.proto). 119 This allows Tendermint Core to run applications written in any programming 120 language. 121 122 ### 1.3.1 Compile .proto files 123 124 Add the following piece to the top of the `build.gradle`: 125 126 ```groovy 127 buildscript { 128 repositories { 129 mavenCentral() 130 } 131 dependencies { 132 classpath 'com.google.protobuf:protobuf-gradle-plugin:0.8.8' 133 } 134 } 135 ``` 136 137 Enable the protobuf plugin in the `plugins` section of the `build.gradle`: 138 139 ```groovy 140 plugins { 141 id 'com.google.protobuf' version '0.8.8' 142 } 143 ``` 144 145 Add the following code to `build.gradle`: 146 147 ```groovy 148 protobuf { 149 protoc { 150 artifact = "com.google.protobuf:protoc:3.7.1" 151 } 152 plugins { 153 grpc { 154 artifact = 'io.grpc:protoc-gen-grpc-java:1.22.1' 155 } 156 } 157 generateProtoTasks { 158 all()*.plugins { 159 grpc {} 160 } 161 } 162 } 163 ``` 164 165 Now we should be ready to compile the `*.proto` files. 166 167 Copy the necessary `.proto` files to your project: 168 169 ```bash 170 mkdir -p \ 171 $KVSTORE_HOME/src/main/proto/github.com/number571/tendermint/proto/tendermint/abci \ 172 $KVSTORE_HOME/src/main/proto/github.com/number571/tendermint/proto/tendermint/version \ 173 $KVSTORE_HOME/src/main/proto/github.com/number571/tendermint/proto/tendermint/types \ 174 $KVSTORE_HOME/src/main/proto/github.com/number571/tendermint/proto/tendermint/crypto \ 175 $KVSTORE_HOME/src/main/proto/github.com/number571/tendermint/proto/tendermint/libs \ 176 $KVSTORE_HOME/src/main/proto/github.com/gogo/protobuf/gogoproto 177 178 cp $GOPATH/src/github.com/number571/tendermint/proto/tendermint/abci/types.proto \ 179 $KVSTORE_HOME/src/main/proto/github.com/number571/tendermint/proto/tendermint/abci/types.proto 180 cp $GOPATH/src/github.com/number571/tendermint/proto/tendermint/version/types.proto \ 181 $KVSTORE_HOME/src/main/proto/github.com/number571/tendermint/proto/tendermint/version/types.proto 182 cp $GOPATH/src/github.com/number571/tendermint/proto/tendermint/types/types.proto \ 183 $KVSTORE_HOME/src/main/proto/github.com/number571/tendermint/proto/tendermint/types/types.proto 184 cp $GOPATH/src/github.com/number571/tendermint/proto/tendermint/types/evidence.proto \ 185 $KVSTORE_HOME/src/main/proto/github.com/number571/tendermint/proto/tendermint/types/evidence.proto 186 cp $GOPATH/src/github.com/number571/tendermint/proto/tendermint/types/params.proto \ 187 $KVSTORE_HOME/src/main/proto/github.com/number571/tendermint/proto/tendermint/types/params.proto 188 cp $GOPATH/src/github.com/number571/tendermint/proto/tendermint/crypto/proof.proto \ 189 $KVSTORE_HOME/src/main/proto/github.com/number571/tendermint/proto/tendermint/crypto/proof.proto 190 cp $GOPATH/src/github.com/number571/tendermint/proto/tendermint/crypto/keys.proto \ 191 $KVSTORE_HOME/src/main/proto/github.com/number571/tendermint/proto/tendermint/crypto/keys.proto 192 cp $GOPATH/src/github.com/number571/tendermint/proto/tendermint/libs/bits/types.proto \ 193 $KVSTORE_HOME/src/main/proto/github.com/number571/tendermint/proto/tendermint/libs/bits/types.proto 194 cp $GOPATH/src/github.com/gogo/protobuf/gogoproto/gogo.proto \ 195 $KVSTORE_HOME/src/main/proto/github.com/gogo/protobuf/gogoproto/gogo.proto 196 ``` 197 198 Add these dependencies to `build.gradle`: 199 200 ```groovy 201 dependencies { 202 implementation 'io.grpc:grpc-protobuf:1.22.1' 203 implementation 'io.grpc:grpc-netty-shaded:1.22.1' 204 implementation 'io.grpc:grpc-stub:1.22.1' 205 } 206 ``` 207 208 To generate all protobuf-type classes run: 209 210 ```bash 211 ./gradlew generateProto 212 ``` 213 214 To verify that everything went smoothly, you can inspect the `build/generated/` directory: 215 216 ```bash 217 tree build/generated/ 218 build/generated/ 219 `-- source 220 `-- proto 221 `-- main 222 |-- grpc 223 | `-- types 224 | `-- ABCIApplicationGrpc.java 225 `-- java 226 |-- com 227 | `-- google 228 | `-- protobuf 229 | `-- GoGoProtos.java 230 |-- common 231 | `-- Types.java 232 |-- merkle 233 | `-- Merkle.java 234 `-- types 235 `-- Types.java 236 ``` 237 238 ### 1.3.2 Implementing ABCI 239 240 The resulting `$KVSTORE_HOME/build/generated/source/proto/main/grpc/types/ABCIApplicationGrpc.java` file 241 contains the abstract class `ABCIApplicationImplBase`, which is an interface we'll need to implement. 242 243 Create `$KVSTORE_HOME/src/main/kotlin/io/example/KVStoreApp.kt` file with the following content: 244 245 ```kotlin 246 package io.example 247 248 import io.grpc.stub.StreamObserver 249 import types.ABCIApplicationGrpc 250 import types.Types.* 251 252 class KVStoreApp : ABCIApplicationGrpc.ABCIApplicationImplBase() { 253 254 // methods implementation 255 256 } 257 ``` 258 259 Now I will go through each method of `ABCIApplicationImplBase` explaining when it's called and adding 260 required business logic. 261 262 ### 1.3.3 CheckTx 263 264 When a new transaction is added to the Tendermint Core, it will ask the 265 application to check it (validate the format, signatures, etc.). 266 267 ```kotlin 268 override fun checkTx(req: RequestCheckTx, responseObserver: StreamObserver<ResponseCheckTx>) { 269 val code = req.tx.validate() 270 val resp = ResponseCheckTx.newBuilder() 271 .setCode(code) 272 .setGasWanted(1) 273 .build() 274 responseObserver.onNext(resp) 275 responseObserver.onCompleted() 276 } 277 278 private fun ByteString.validate(): Int { 279 val parts = this.split('=') 280 if (parts.size != 2) { 281 return 1 282 } 283 val key = parts[0] 284 val value = parts[1] 285 286 // check if the same key=value already exists 287 val stored = getPersistedValue(key) 288 if (stored != null && stored.contentEquals(value)) { 289 return 2 290 } 291 292 return 0 293 } 294 295 private fun ByteString.split(separator: Char): List<ByteArray> { 296 val arr = this.toByteArray() 297 val i = (0 until this.size()).firstOrNull { arr[it] == separator.toByte() } 298 ?: return emptyList() 299 return listOf( 300 this.substring(0, i).toByteArray(), 301 this.substring(i + 1).toByteArray() 302 ) 303 } 304 ``` 305 306 Don't worry if this does not compile yet. 307 308 If the transaction does not have a form of `{bytes}={bytes}`, we return `1` 309 code. When the same key=value already exist (same key and value), we return `2` 310 code. For others, we return a zero code indicating that they are valid. 311 312 Note that anything with non-zero code will be considered invalid (`-1`, `100`, 313 etc.) by Tendermint Core. 314 315 Valid transactions will eventually be committed given they are not too big and 316 have enough gas. To learn more about gas, check out ["the 317 specification"](https://docs.tendermint.com/master/spec/abci/apps.html#gas). 318 319 For the underlying key-value store we'll use 320 [JetBrains Xodus](https://github.com/JetBrains/xodus), which is a transactional schema-less embedded high-performance database written in Java. 321 322 `build.gradle`: 323 324 ```groovy 325 dependencies { 326 implementation 'org.jetbrains.xodus:xodus-environment:1.3.91' 327 } 328 ``` 329 330 ```kotlin 331 ... 332 import jetbrains.exodus.ArrayByteIterable 333 import jetbrains.exodus.env.Environment 334 import jetbrains.exodus.env.Store 335 import jetbrains.exodus.env.StoreConfig 336 import jetbrains.exodus.env.Transaction 337 338 class KVStoreApp( 339 private val env: Environment 340 ) : ABCIApplicationGrpc.ABCIApplicationImplBase() { 341 342 private var txn: Transaction? = null 343 private var store: Store? = null 344 345 ... 346 347 private fun getPersistedValue(k: ByteArray): ByteArray? { 348 return env.computeInReadonlyTransaction { txn -> 349 val store = env.openStore("store", StoreConfig.WITHOUT_DUPLICATES, txn) 350 store.get(txn, ArrayByteIterable(k))?.bytesUnsafe 351 } 352 } 353 } 354 ``` 355 356 ### 1.3.4 BeginBlock -> DeliverTx -> EndBlock -> Commit 357 358 When Tendermint Core has decided on the block, it's transferred to the 359 application in 3 parts: `BeginBlock`, one `DeliverTx` per transaction and 360 `EndBlock` in the end. `DeliverTx` are being transferred asynchronously, but the 361 responses are expected to come in order. 362 363 ```kotlin 364 override fun beginBlock(req: RequestBeginBlock, responseObserver: StreamObserver<ResponseBeginBlock>) { 365 txn = env.beginTransaction() 366 store = env.openStore("store", StoreConfig.WITHOUT_DUPLICATES, txn!!) 367 val resp = ResponseBeginBlock.newBuilder().build() 368 responseObserver.onNext(resp) 369 responseObserver.onCompleted() 370 } 371 ``` 372 373 Here we begin a new transaction, which will accumulate the block's transactions and open the corresponding store. 374 375 ```kotlin 376 override fun deliverTx(req: RequestDeliverTx, responseObserver: StreamObserver<ResponseDeliverTx>) { 377 val code = req.tx.validate() 378 if (code == 0) { 379 val parts = req.tx.split('=') 380 val key = ArrayByteIterable(parts[0]) 381 val value = ArrayByteIterable(parts[1]) 382 store!!.put(txn!!, key, value) 383 } 384 val resp = ResponseDeliverTx.newBuilder() 385 .setCode(code) 386 .build() 387 responseObserver.onNext(resp) 388 responseObserver.onCompleted() 389 } 390 ``` 391 392 If the transaction is badly formatted or the same key=value already exist, we 393 again return the non-zero code. Otherwise, we add it to the store. 394 395 In the current design, a block can include incorrect transactions (those who 396 passed `CheckTx`, but failed `DeliverTx` or transactions included by the proposer 397 directly). This is done for performance reasons. 398 399 Note we can't commit transactions inside the `DeliverTx` because in such case 400 `Query`, which may be called in parallel, will return inconsistent data (i.e. 401 it will report that some value already exist even when the actual block was not 402 yet committed). 403 404 `Commit` instructs the application to persist the new state. 405 406 ```kotlin 407 override fun commit(req: RequestCommit, responseObserver: StreamObserver<ResponseCommit>) { 408 txn!!.commit() 409 val resp = ResponseCommit.newBuilder() 410 .setData(ByteString.copyFrom(ByteArray(8))) 411 .build() 412 responseObserver.onNext(resp) 413 responseObserver.onCompleted() 414 } 415 ``` 416 417 ### 1.3.5 Query 418 419 Now, when the client wants to know whenever a particular key/value exist, it 420 will call Tendermint Core RPC `/abci_query` endpoint, which in turn will call 421 the application's `Query` method. 422 423 Applications are free to provide their own APIs. But by using Tendermint Core 424 as a proxy, clients (including [light client 425 package](https://godoc.org/github.com/number571/tendermint/light)) can leverage 426 the unified API across different applications. Plus they won't have to call the 427 otherwise separate Tendermint Core API for additional proofs. 428 429 Note we don't include a proof here. 430 431 ```kotlin 432 override fun query(req: RequestQuery, responseObserver: StreamObserver<ResponseQuery>) { 433 val k = req.data.toByteArray() 434 val v = getPersistedValue(k) 435 val builder = ResponseQuery.newBuilder() 436 if (v == null) { 437 builder.log = "does not exist" 438 } else { 439 builder.log = "exists" 440 builder.key = ByteString.copyFrom(k) 441 builder.value = ByteString.copyFrom(v) 442 } 443 responseObserver.onNext(builder.build()) 444 responseObserver.onCompleted() 445 } 446 ``` 447 448 The complete specification can be found 449 [here](https://docs.tendermint.com/master/spec/abci/). 450 451 ## 1.4 Starting an application and a Tendermint Core instances 452 453 Put the following code into the `$KVSTORE_HOME/src/main/kotlin/io/example/App.kt` file: 454 455 ```kotlin 456 package io.example 457 458 import jetbrains.exodus.env.Environments 459 460 fun main() { 461 Environments.newInstance("tmp/storage").use { env -> 462 val app = KVStoreApp(env) 463 val server = GrpcServer(app, 26658) 464 server.start() 465 server.blockUntilShutdown() 466 } 467 } 468 ``` 469 470 It is the entry point of the application. 471 Here we create a special object `Environment`, which knows where to store the application state. 472 Then we create and start the gRPC server to handle Tendermint Core requests. 473 474 Create `$KVSTORE_HOME/src/main/kotlin/io/example/GrpcServer.kt` file with the following content: 475 476 ```kotlin 477 package io.example 478 479 import io.grpc.BindableService 480 import io.grpc.ServerBuilder 481 482 class GrpcServer( 483 private val service: BindableService, 484 private val port: Int 485 ) { 486 private val server = ServerBuilder 487 .forPort(port) 488 .addService(service) 489 .build() 490 491 fun start() { 492 server.start() 493 println("gRPC server started, listening on $port") 494 Runtime.getRuntime().addShutdownHook(object : Thread() { 495 override fun run() { 496 println("shutting down gRPC server since JVM is shutting down") 497 this@GrpcServer.stop() 498 println("server shut down") 499 } 500 }) 501 } 502 503 fun stop() { 504 server.shutdown() 505 } 506 507 /** 508 * Await termination on the main thread since the grpc library uses daemon threads. 509 */ 510 fun blockUntilShutdown() { 511 server.awaitTermination() 512 } 513 514 } 515 ``` 516 517 ## 1.5 Getting Up and Running 518 519 To create a default configuration, nodeKey and private validator files, let's 520 execute `tendermint init validator`. But before we do that, we will need to install 521 Tendermint Core. 522 523 ```bash 524 rm -rf /tmp/example 525 cd $GOPATH/src/github.com/number571/tendermint 526 make install 527 TMHOME="/tmp/example" tendermint init validator 528 529 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 530 I[2019-07-16|18:20:36.481] Generated node key module=main path=/tmp/example/config/node_key.json 531 I[2019-07-16|18:20:36.482] Generated genesis file module=main path=/tmp/example/config/genesis.json 532 I[2019-07-16|18:20:36.482] Generated config module=main mode=validator 533 ``` 534 535 Feel free to explore the generated files, which can be found at 536 `/tmp/example/config` directory. Documentation on the config can be found 537 [here](https://docs.tendermint.com/master/tendermint-core/configuration.html). 538 539 We are ready to start our application: 540 541 ```bash 542 ./gradlew run 543 544 gRPC server started, listening on 26658 545 ``` 546 547 Then we need to start Tendermint Core and point it to our application. Staying 548 within the application directory execute: 549 550 ```bash 551 TMHOME="/tmp/example" tendermint node --abci grpc --proxy-app tcp://127.0.0.1:26658 552 553 I[2019-07-28|15:44:53.632] Version info module=main software=0.32.1 block=10 p2p=7 554 I[2019-07-28|15:44:53.677] Starting Node module=main impl=Node 555 I[2019-07-28|15:44:53.681] Started node module=main nodeInfo="{ProtocolVersion:{P2P:7 Block:10 App:0} ID_:7639e2841ccd47d5ae0f5aad3011b14049d3f452 ListenAddr:tcp://0.0.0.0:26656 Network:test-chain-Nhl3zk Version:0.32.1 Channels:4020212223303800 Moniker:Ivans-MacBook-Pro.local Other:{TxIndex:on RPCAddress:tcp://127.0.0.1:26657}}" 556 I[2019-07-28|15:44:54.801] Executed block module=state height=8 validTxs=0 invalidTxs=0 557 I[2019-07-28|15:44:54.814] Committed state module=state height=8 txs=0 appHash=0000000000000000 558 ``` 559 560 Now open another tab in your terminal and try sending a transaction: 561 562 ```bash 563 curl -s 'localhost:26657/broadcast_tx_commit?tx="tendermint=rocks"' 564 { 565 "jsonrpc": "2.0", 566 "id": "", 567 "result": { 568 "check_tx": { 569 "gasWanted": "1" 570 }, 571 "deliver_tx": {}, 572 "hash": "CDD3C6DFA0A08CAEDF546F9938A2EEC232209C24AA0E4201194E0AFB78A2C2BB", 573 "height": "33" 574 } 575 ``` 576 577 Response should contain the height where this transaction was committed. 578 579 Now let's check if the given key now exists and its value: 580 581 ```bash 582 curl -s 'localhost:26657/abci_query?data="tendermint"' 583 { 584 "jsonrpc": "2.0", 585 "id": "", 586 "result": { 587 "response": { 588 "log": "exists", 589 "key": "dGVuZGVybWludA==", 590 "value": "cm9ja3My" 591 } 592 } 593 } 594 ``` 595 596 `dGVuZGVybWludA==` and `cm9ja3M=` are the base64-encoding of the ASCII of `tendermint` and `rocks` accordingly. 597 598 ## Outro 599 600 I hope everything went smoothly and your first, but hopefully not the last, 601 Tendermint Core application is up and running. If not, please [open an issue on 602 Github](https://github.com/number571/tendermint/issues/new/choose). To dig 603 deeper, read [the docs](https://docs.tendermint.com/master/). 604 605 The full source code of this example project can be found [here](https://github.com/climber73/tendermint-abci-grpc-kotlin).