github.com/number571/tendermint@v0.34.11-gost/docs/tutorials/java.md (about) 1 <!--- 2 order: 3 3 ---> 4 5 # Creating an application in Java 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 Java. 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 "12.0.2" 2019-07-16 46 Java(TM) SE Runtime Environment (build 12.0.2+10) 47 Java HotSpot(TM) 64-Bit Server VM (build 12.0.2+10, mixed mode, sharing) 48 ``` 49 50 You can choose any version of Java higher or equal to 8. 51 This guide is written using Java SE Development Kit 12. 52 53 Make sure you have `$JAVA_HOME` environment variable set: 54 55 ```bash 56 $ echo $JAVA_HOME 57 /Library/Java/JavaVirtualMachines/jdk-12.0.2.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 Java 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 java-application --test-framework junit 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 | |-- java 94 | | `-- io 95 | | `-- example 96 | | `-- App.java 97 | `-- resources 98 `-- test 99 |-- java 100 | `-- io 101 | `-- example 102 | `-- AppTest.java 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/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 | |-- proof 233 | | `-- Proof.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/java/io/example/KVStoreApp.java` file with the following content: 244 245 ```java 246 package io.example; 247 248 import io.grpc.stub.StreamObserver; 249 import types.ABCIApplicationGrpc; 250 import types.Types.*; 251 252 class KVStoreApp extends 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 ```java 268 @Override 269 public void checkTx(RequestCheckTx req, StreamObserver<ResponseCheckTx> responseObserver) { 270 var tx = req.getTx(); 271 int code = validate(tx); 272 var resp = ResponseCheckTx.newBuilder() 273 .setCode(code) 274 .setGasWanted(1) 275 .build(); 276 responseObserver.onNext(resp); 277 responseObserver.onCompleted(); 278 } 279 280 private int validate(ByteString tx) { 281 List<byte[]> parts = split(tx, '='); 282 if (parts.size() != 2) { 283 return 1; 284 } 285 byte[] key = parts.get(0); 286 byte[] value = parts.get(1); 287 288 // check if the same key=value already exists 289 var stored = getPersistedValue(key); 290 if (stored != null && Arrays.equals(stored, value)) { 291 return 2; 292 } 293 294 return 0; 295 } 296 297 private List<byte[]> split(ByteString tx, char separator) { 298 var arr = tx.toByteArray(); 299 int i; 300 for (i = 0; i < tx.size(); i++) { 301 if (arr[i] == (byte)separator) { 302 break; 303 } 304 } 305 if (i == tx.size()) { 306 return Collections.emptyList(); 307 } 308 return List.of( 309 tx.substring(0, i).toByteArray(), 310 tx.substring(i + 1).toByteArray() 311 ); 312 } 313 ``` 314 315 Don't worry if this does not compile yet. 316 317 If the transaction does not have a form of `{bytes}={bytes}`, we return `1` 318 code. When the same key=value already exist (same key and value), we return `2` 319 code. For others, we return a zero code indicating that they are valid. 320 321 Note that anything with non-zero code will be considered invalid (`-1`, `100`, 322 etc.) by Tendermint Core. 323 324 Valid transactions will eventually be committed given they are not too big and 325 have enough gas. To learn more about gas, check out ["the 326 specification"](https://docs.tendermint.com/master/spec/abci/apps.html#gas). 327 328 For the underlying key-value store we'll use 329 [JetBrains Xodus](https://github.com/JetBrains/xodus), which is a transactional schema-less embedded high-performance database written in Java. 330 331 `build.gradle`: 332 333 ```groovy 334 dependencies { 335 implementation 'org.jetbrains.xodus:xodus-environment:1.3.91' 336 } 337 ``` 338 339 ```java 340 ... 341 import jetbrains.exodus.ArrayByteIterable; 342 import jetbrains.exodus.ByteIterable; 343 import jetbrains.exodus.env.Environment; 344 import jetbrains.exodus.env.Store; 345 import jetbrains.exodus.env.StoreConfig; 346 import jetbrains.exodus.env.Transaction; 347 348 class KVStoreApp extends ABCIApplicationGrpc.ABCIApplicationImplBase { 349 private Environment env; 350 private Transaction txn = null; 351 private Store store = null; 352 353 KVStoreApp(Environment env) { 354 this.env = env; 355 } 356 357 ... 358 359 private byte[] getPersistedValue(byte[] k) { 360 return env.computeInReadonlyTransaction(txn -> { 361 var store = env.openStore("store", StoreConfig.WITHOUT_DUPLICATES, txn); 362 ByteIterable byteIterable = store.get(txn, new ArrayByteIterable(k)); 363 if (byteIterable == null) { 364 return null; 365 } 366 return byteIterable.getBytesUnsafe(); 367 }); 368 } 369 } 370 ``` 371 372 ### 1.3.4 BeginBlock -> DeliverTx -> EndBlock -> Commit 373 374 When Tendermint Core has decided on the block, it's transferred to the 375 application in 3 parts: `BeginBlock`, one `DeliverTx` per transaction and 376 `EndBlock` in the end. `DeliverTx` are being transferred asynchronously, but the 377 responses are expected to come in order. 378 379 ```java 380 @Override 381 public void beginBlock(RequestBeginBlock req, StreamObserver<ResponseBeginBlock> responseObserver) { 382 txn = env.beginTransaction(); 383 store = env.openStore("store", StoreConfig.WITHOUT_DUPLICATES, txn); 384 var resp = ResponseBeginBlock.newBuilder().build(); 385 responseObserver.onNext(resp); 386 responseObserver.onCompleted(); 387 } 388 ``` 389 390 Here we begin a new transaction, which will accumulate the block's transactions and open the corresponding store. 391 392 ```java 393 @Override 394 public void deliverTx(RequestDeliverTx req, StreamObserver<ResponseDeliverTx> responseObserver) { 395 var tx = req.getTx(); 396 int code = validate(tx); 397 if (code == 0) { 398 List<byte[]> parts = split(tx, '='); 399 var key = new ArrayByteIterable(parts.get(0)); 400 var value = new ArrayByteIterable(parts.get(1)); 401 store.put(txn, key, value); 402 } 403 var resp = ResponseDeliverTx.newBuilder() 404 .setCode(code) 405 .build(); 406 responseObserver.onNext(resp); 407 responseObserver.onCompleted(); 408 } 409 ``` 410 411 If the transaction is badly formatted or the same key=value already exist, we 412 again return the non-zero code. Otherwise, we add it to the store. 413 414 In the current design, a block can include incorrect transactions (those who 415 passed `CheckTx`, but failed `DeliverTx` or transactions included by the proposer 416 directly). This is done for performance reasons. 417 418 Note we can't commit transactions inside the `DeliverTx` because in such case 419 `Query`, which may be called in parallel, will return inconsistent data (i.e. 420 it will report that some value already exist even when the actual block was not 421 yet committed). 422 423 `Commit` instructs the application to persist the new state. 424 425 ```java 426 @Override 427 public void commit(RequestCommit req, StreamObserver<ResponseCommit> responseObserver) { 428 txn.commit(); 429 var resp = ResponseCommit.newBuilder() 430 .setData(ByteString.copyFrom(new byte[8])) 431 .build(); 432 responseObserver.onNext(resp); 433 responseObserver.onCompleted(); 434 } 435 ``` 436 437 ### 1.3.5 Query 438 439 Now, when the client wants to know whenever a particular key/value exist, it 440 will call Tendermint Core RPC `/abci_query` endpoint, which in turn will call 441 the application's `Query` method. 442 443 Applications are free to provide their own APIs. But by using Tendermint Core 444 as a proxy, clients (including [light client 445 package](https://godoc.org/github.com/number571/tendermint/light)) can leverage 446 the unified API across different applications. Plus they won't have to call the 447 otherwise separate Tendermint Core API for additional proofs. 448 449 Note we don't include a proof here. 450 451 ```java 452 @Override 453 public void query(RequestQuery req, StreamObserver<ResponseQuery> responseObserver) { 454 var k = req.getData().toByteArray(); 455 var v = getPersistedValue(k); 456 var builder = ResponseQuery.newBuilder(); 457 if (v == null) { 458 builder.setLog("does not exist"); 459 } else { 460 builder.setLog("exists"); 461 builder.setKey(ByteString.copyFrom(k)); 462 builder.setValue(ByteString.copyFrom(v)); 463 } 464 responseObserver.onNext(builder.build()); 465 responseObserver.onCompleted(); 466 } 467 ``` 468 469 The complete specification can be found 470 [here](https://docs.tendermint.com/master/spec/abci/). 471 472 ## 1.4 Starting an application and a Tendermint Core instances 473 474 Put the following code into the `$KVSTORE_HOME/src/main/java/io/example/App.java` file: 475 476 ```java 477 package io.example; 478 479 import jetbrains.exodus.env.Environment; 480 import jetbrains.exodus.env.Environments; 481 482 import java.io.IOException; 483 484 public class App { 485 public static void main(String[] args) throws IOException, InterruptedException { 486 try (Environment env = Environments.newInstance("tmp/storage")) { 487 var app = new KVStoreApp(env); 488 var server = new GrpcServer(app, 26658); 489 server.start(); 490 server.blockUntilShutdown(); 491 } 492 } 493 } 494 ``` 495 496 It is the entry point of the application. 497 Here we create a special object `Environment`, which knows where to store the application state. 498 Then we create and start the gRPC server to handle Tendermint Core requests. 499 500 Create the `$KVSTORE_HOME/src/main/java/io/example/GrpcServer.java` file with the following content: 501 502 ```java 503 package io.example; 504 505 import io.grpc.BindableService; 506 import io.grpc.Server; 507 import io.grpc.ServerBuilder; 508 509 import java.io.IOException; 510 511 class GrpcServer { 512 private Server server; 513 514 GrpcServer(BindableService service, int port) { 515 this.server = ServerBuilder.forPort(port) 516 .addService(service) 517 .build(); 518 } 519 520 void start() throws IOException { 521 server.start(); 522 System.out.println("gRPC server started, listening on $port"); 523 Runtime.getRuntime().addShutdownHook(new Thread(() -> { 524 System.out.println("shutting down gRPC server since JVM is shutting down"); 525 GrpcServer.this.stop(); 526 System.out.println("server shut down"); 527 })); 528 } 529 530 private void stop() { 531 server.shutdown(); 532 } 533 534 /** 535 * Await termination on the main thread since the grpc library uses daemon threads. 536 */ 537 void blockUntilShutdown() throws InterruptedException { 538 server.awaitTermination(); 539 } 540 } 541 ``` 542 543 ## 1.5 Getting Up and Running 544 545 To create a default configuration, nodeKey and private validator files, let's 546 execute `tendermint init`. But before we do that, we will need to install 547 Tendermint Core. 548 549 ```bash 550 $ rm -rf /tmp/example 551 $ cd $GOPATH/src/github.com/number571/tendermint 552 $ make install 553 $ TMHOME="/tmp/example" tendermint init validator 554 555 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 556 I[2019-07-16|18:20:36.481] Generated node key module=main path=/tmp/example/config/node_key.json 557 I[2019-07-16|18:20:36.482] Generated genesis file module=main path=/tmp/example/config/genesis.json 558 I[2019-07-16|18:20:36.483] Generated config module=main mode=validator 559 ``` 560 561 Feel free to explore the generated files, which can be found at 562 `/tmp/example/config` directory. Documentation on the config can be found 563 [here](https://docs.tendermint.com/master/tendermint-core/configuration.html). 564 565 We are ready to start our application: 566 567 ```bash 568 ./gradlew run 569 570 gRPC server started, listening on 26658 571 ``` 572 573 Then we need to start Tendermint Core and point it to our application. Staying 574 within the application directory execute: 575 576 ```bash 577 $ TMHOME="/tmp/example" tendermint node --abci grpc --proxy-app tcp://127.0.0.1:26658 578 579 I[2019-07-28|15:44:53.632] Version info module=main software=0.32.1 block=10 p2p=7 580 I[2019-07-28|15:44:53.677] Starting Node module=main impl=Node 581 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}}" 582 I[2019-07-28|15:44:54.801] Executed block module=state height=8 validTxs=0 invalidTxs=0 583 I[2019-07-28|15:44:54.814] Committed state module=state height=8 txs=0 appHash=0000000000000000 584 ``` 585 586 Now open another tab in your terminal and try sending a transaction: 587 588 ```bash 589 $ curl -s 'localhost:26657/broadcast_tx_commit?tx="tendermint=rocks"' 590 { 591 "jsonrpc": "2.0", 592 "id": "", 593 "result": { 594 "check_tx": { 595 "gasWanted": "1" 596 }, 597 "deliver_tx": {}, 598 "hash": "CDD3C6DFA0A08CAEDF546F9938A2EEC232209C24AA0E4201194E0AFB78A2C2BB", 599 "height": "33" 600 } 601 ``` 602 603 Response should contain the height where this transaction was committed. 604 605 Now let's check if the given key now exists and its value: 606 607 ```bash 608 $ curl -s 'localhost:26657/abci_query?data="tendermint"' 609 { 610 "jsonrpc": "2.0", 611 "id": "", 612 "result": { 613 "response": { 614 "log": "exists", 615 "key": "dGVuZGVybWludA==", 616 "value": "cm9ja3My" 617 } 618 } 619 } 620 ``` 621 622 `dGVuZGVybWludA==` and `cm9ja3M=` are the base64-encoding of the ASCII of `tendermint` and `rocks` accordingly. 623 624 ## Outro 625 626 I hope everything went smoothly and your first, but hopefully not the last, 627 Tendermint Core application is up and running. If not, please [open an issue on 628 Github](https://github.com/number571/tendermint/issues/new/choose). To dig 629 deeper, read [the docs](https://docs.tendermint.com/master/). 630 631 The full source code of this example project can be found [here](https://github.com/climber73/tendermint-abci-grpc-java).