github.com/vipernet-xyz/tm@v0.34.24/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/vipernet-xyz/tm/blob/v0.34.x/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/vipernet-xyz/tm/proto/tendermint/abci \
   172    $KVSTORE_HOME/src/main/proto/github.com/vipernet-xyz/tm/proto/tendermint/version \
   173    $KVSTORE_HOME/src/main/proto/github.com/vipernet-xyz/tm/proto/tendermint/types \
   174    $KVSTORE_HOME/src/main/proto/github.com/vipernet-xyz/tm/proto/tendermint/crypto \
   175    $KVSTORE_HOME/src/main/proto/github.com/vipernet-xyz/tm/proto/tendermint/libs \
   176    $KVSTORE_HOME/src/main/proto/github.com/gogo/protobuf/gogoproto
   177  
   178  cp $GOPATH/src/github.com/vipernet-xyz/tm/proto/tendermint/abci/types.proto \
   179     $KVSTORE_HOME/src/main/proto/github.com/vipernet-xyz/tm/proto/tendermint/abci/types.proto
   180  cp $GOPATH/src/github.com/vipernet-xyz/tm/proto/tendermint/version/version.proto \
   181     $KVSTORE_HOME/src/main/proto/github.com/vipernet-xyz/tm/proto/tendermint/version/version.proto
   182  cp $GOPATH/src/github.com/vipernet-xyz/tm/proto/tendermint/types/types.proto \
   183     $KVSTORE_HOME/src/main/proto/github.com/vipernet-xyz/tm/proto/tendermint/types/types.proto
   184  cp $GOPATH/src/github.com/vipernet-xyz/tm/proto/tendermint/types/evidence.proto \
   185     $KVSTORE_HOME/src/main/proto/github.com/vipernet-xyz/tm/proto/tendermint/types/evidence.proto
   186  cp $GOPATH/src/github.com/vipernet-xyz/tm/proto/tendermint/types/params.proto \
   187     $KVSTORE_HOME/src/main/proto/github.com/vipernet-xyz/tm/proto/tendermint/types/params.proto
   188  cp $GOPATH/src/github.com/vipernet-xyz/tm/proto/tendermint/crypto/merkle.proto \
   189     $KVSTORE_HOME/src/main/proto/github.com/vipernet-xyz/tm/proto/tendermint/crypto/merkle.proto
   190  cp $GOPATH/src/github.com/vipernet-xyz/tm/proto/tendermint/crypto/keys.proto \
   191     $KVSTORE_HOME/src/main/proto/github.com/vipernet-xyz/tm/proto/tendermint/crypto/keys.proto
   192  cp $GOPATH/src/github.com/vipernet-xyz/tm/proto/tendermint/libs/types.proto \
   193     $KVSTORE_HOME/src/main/proto/github.com/vipernet-xyz/tm/proto/tendermint/libs/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://github.com/vipernet-xyz/tm/blob/v0.34.x/spec/abci/apps.md#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/vipernet-xyz/tm/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://github.com/vipernet-xyz/tm/tree/v0.34.x/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`. 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/vipernet-xyz/tm
   526  make install
   527  TMHOME="/tmp/example" tendermint init
   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  ```
   533  
   534  Feel free to explore the generated files, which can be found at
   535  `/tmp/example/config` directory. Documentation on the config can be found
   536  [here](https://docs.tendermint.com/v0.34/tendermint-core/configuration.html).
   537  
   538  We are ready to start our application:
   539  
   540  ```bash
   541  ./gradlew run
   542  
   543  gRPC server started, listening on 26658
   544  ```
   545  
   546  Then we need to start Tendermint Core and point it to our application. Staying
   547  within the application directory execute:
   548  
   549  ```bash
   550  TMHOME="/tmp/example" tendermint node --abci grpc --proxy_app tcp://127.0.0.1:26658
   551  
   552  I[2019-07-28|15:44:53.632] Version info                                 module=main software=0.32.1 block=10 p2p=7
   553  I[2019-07-28|15:44:53.677] Starting Node                                module=main impl=Node
   554  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}}"
   555  I[2019-07-28|15:44:54.801] Executed block                               module=state height=8 validTxs=0 invalidTxs=0
   556  I[2019-07-28|15:44:54.814] Committed state                              module=state height=8 txs=0 appHash=0000000000000000
   557  ```
   558  
   559  Now open another tab in your terminal and try sending a transaction:
   560  
   561  ```bash
   562  curl -s 'localhost:26657/broadcast_tx_commit?tx="tendermint=rocks"'
   563  {
   564    "jsonrpc": "2.0",
   565    "id": "",
   566    "result": {
   567      "check_tx": {
   568        "gasWanted": "1"
   569      },
   570      "deliver_tx": {},
   571      "hash": "CDD3C6DFA0A08CAEDF546F9938A2EEC232209C24AA0E4201194E0AFB78A2C2BB",
   572      "height": "33"
   573  }
   574  ```
   575  
   576  Response should contain the height where this transaction was committed.
   577  
   578  Now let's check if the given key now exists and its value:
   579  
   580  ```bash
   581  curl -s 'localhost:26657/abci_query?data="tendermint"'
   582  {
   583    "jsonrpc": "2.0",
   584    "id": "",
   585    "result": {
   586      "response": {
   587        "log": "exists",
   588        "key": "dGVuZGVybWludA==",
   589        "value": "cm9ja3My"
   590      }
   591    }
   592  }
   593  ```
   594  
   595  `dGVuZGVybWludA==` and `cm9ja3M=` are the base64-encoding of the ASCII of `tendermint` and `rocks` accordingly.
   596  
   597  ## Outro
   598  
   599  I hope everything went smoothly and your first, but hopefully not the last,
   600  Tendermint Core application is up and running. If not, please [open an issue on
   601  Github](https://github.com/vipernet-xyz/tm/issues/new/choose). To dig
   602  deeper, read [the docs](https://docs.tendermint.com/v0.34/).
   603  
   604  The full source code of this example project can be found [here](https://github.com/climber73/tendermint-abci-grpc-kotlin).