docs: "Writing a Tendermint Core application in Java (gRPC)" guide (#3887)

* add abci grpc java guide

* fix grammar and spelling
This commit is contained in:
Ivan Kushmantsev
2019-08-12 14:39:11 +04:00
committed by Anton Kaliaev
parent c962567814
commit 2e39418124
2 changed files with 631 additions and 32 deletions

View File

@ -22,9 +22,9 @@ If you use Golang, you can run your app and Tendermint Core in the same process
[Cosmos SDK](https://github.com/cosmos/cosmos-sdk) is written this way.
Please refer to [Writing a built-in Tendermint Core application in Go](./go-built-in.md) guide for details.
If you choose another language, like we did in this guide, you have to write a separate app using
either plain socket or gRPC. This guide will show you how to build external applicationg
using RPC server.
If you choose another language, like we did in this guide, you have to write a separate app,
which will communicate with Tendermint Core via a socket (UNIX or TCP) or gRPC.
This guide will show you how to build external application using RPC server.
Having a separate application might give you better security guarantees as two
processes would be communicating via established binary protocol. Tendermint
@ -34,7 +34,7 @@ Core will not have access to application's state.
Please refer to [the Oracle's guide for installing JDK](https://www.oracle.com/technetwork/java/javase/downloads/index.html).
Verify that you have installed Java successully:
Verify that you have installed Java successfully:
```sh
$ java -version
@ -69,7 +69,7 @@ Inside the example directory run:
```sh
gradle init --dsl groovy --package io.example --project-name example --type kotlin-application
```
That Gradle command will create project structure for you:
This will create a new project for you. The tree of files should look like:
```sh
$ tree
.
@ -114,7 +114,7 @@ language.
### 1.3.1 Compile .proto files
Add folowing to the top of `build.gradle`:
Add the following piece to the top of the `build.gradle`:
```groovy
buildscript {
repositories {
@ -126,14 +126,14 @@ buildscript {
}
```
Enable protobuf plugin in `plugins` section of `build.gradle`:
Enable the protobuf plugin in the `plugins` section of the `build.gradle`:
```groovy
plugins {
id 'com.google.protobuf' version '0.8.8'
}
```
Add following to `build.gradle`:
Add the following code to `build.gradle`:
```groovy
protobuf {
protoc {
@ -152,10 +152,10 @@ protobuf {
}
```
Now your project is ready to compile `*.proto` files.
Now we should be ready to compile the `*.proto` files.
Copy necessary .proto files to your project:
Copy the necessary `.proto` files to your project:
```sh
mkdir -p \
$KVSTORE_HOME/src/main/proto/github.com/tendermint/tendermint/abci/types \
@ -173,7 +173,7 @@ cp $GOPATH/src/github.com/gogo/protobuf/gogoproto/gogo.proto \
$KVSTORE_HOME/src/main/proto/github.com/gogo/protobuf/gogoproto/gogo.proto
```
Add dependency to `build.gradle`:
Add these dependencies to `build.gradle`:
```groovy
dependencies {
implementation 'io.grpc:grpc-protobuf:1.22.1'
@ -186,7 +186,7 @@ To generate all protobuf-type classes run:
```sh
./gradlew generateProto
```
It will produce java classes to `build/generated/`:
To verify that everything went smoothly, you can inspect the `build/generated/` directory:
```sh
$ tree build/generated/
build/generated/
@ -211,11 +211,10 @@ build/generated/
### 1.3.2 Implementing ABCI
As you can see there is a generated file `$KVSTORE_HOME/build/generated/source/proto/main/grpc/types/ABCIApplicationGrpc.java`.
which contains an abstract class `ABCIApplicationImplBase`. This class fully describes the ABCI interface.
All you need is implement this interface.
The resulting `$KVSTORE_HOME/build/generated/source/proto/main/grpc/types/ABCIApplicationGrpc.java` file
contains the abstract class `ABCIApplicationImplBase`, which is an interface we'll need to implement.
Create file `$KVSTORE_HOME/src/main/kotlin/io/example/KVStoreApp.kt` with following context:
Create `$KVSTORE_HOME/src/main/kotlin/io/example/KVStoreApp.kt` file with the following content:
```kotlin
package io.example
@ -296,7 +295,7 @@ For the underlying key-value store we'll use
`build.gradle`:
```groovy
dependencies {
implementation "org.jetbrains.xodus:xodus-environment:1.3.91"
implementation 'org.jetbrains.xodus:xodus-environment:1.3.91'
}
```
@ -316,14 +315,21 @@ class KVStoreApp(
private var store: Store? = null
...
private fun getPersistedValue(k: ByteArray): ByteArray? {
return env.computeInReadonlyTransaction { txn ->
val store = env.openStore("store", StoreConfig.WITHOUT_DUPLICATES, txn)
store.get(txn, ArrayByteIterable(k))?.bytesUnsafe
}
}
}
```
### 1.3.4 BeginBlock -> DeliverTx -> EndBlock -> Commit
When Tendermint Core has decided on the block, it's transfered to the
When Tendermint Core has decided on the block, it's transferred to the
application in 3 parts: `BeginBlock`, one `DeliverTx` per transaction and
`EndBlock` in the end. DeliverTx are being transfered asynchronously, but the
`EndBlock` in the end. `DeliverTx` are being transferred asynchronously, but the
responses are expected to come in order.
```kotlin
@ -335,7 +341,7 @@ override fun beginBlock(req: RequestBeginBlock, responseObserver: StreamObserver
responseObserver.onCompleted()
}
```
Here we start new transaction, which will store block's transactions, and open corresponding store.
Here we begin a new transaction, which will accumulate the block's transactions and open the corresponding store.
```kotlin
override fun deliverTx(req: RequestDeliverTx, responseObserver: StreamObserver<ResponseDeliverTx>) {
@ -355,10 +361,10 @@ override fun deliverTx(req: RequestDeliverTx, responseObserver: StreamObserver<R
```
If the transaction is badly formatted or the same key=value already exist, we
again return the non-zero code. Otherwise, we add it to the storage.
again return the non-zero code. Otherwise, we add it to the store.
In the current design, a block can include incorrect transactions (those who
passed CheckTx, but failed DeliverTx or transactions included by the proposer
passed `CheckTx`, but failed `DeliverTx` or transactions included by the proposer
directly). This is done for performance reasons.
Note we can't commit transactions inside the `DeliverTx` because in such case
@ -408,13 +414,6 @@ override fun query(req: RequestQuery, responseObserver: StreamObserver<ResponseQ
responseObserver.onNext(builder.build())
responseObserver.onCompleted()
}
private fun getPersistedValue(k: ByteArray): ByteArray? {
return env.computeInReadonlyTransaction { txn ->
val store = env.openStore("store", StoreConfig.WITHOUT_DUPLICATES, txn)
store.get(txn, ArrayByteIterable(k))?.bytesUnsafe
}
}
```
The complete specification can be found
@ -440,10 +439,10 @@ fun main() {
```
It is the entry point of the application.
Here we create special object `Environment` which knows where to store state of the application.
Then we create and srart gRPC server to handle Tendermint's requests.
Here we create a special object `Environment`, which knows where to store the application state.
Then we create and start the gRPC server to handle Tendermint Core requests.
Create file `$KVSTORE_HOME/src/main/kotlin/io/example/GrpcServer.kt`:
Create `$KVSTORE_HOME/src/main/kotlin/io/example/GrpcServer.kt` file with the following content:
```kotlin
package io.example