github.com/vipernet-xyz/tm@v0.34.24/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/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/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://github.com/vipernet-xyz/tm/blob/v0.34.x/spec/abci/apps.md#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/vipernet-xyz/tm/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://github.com/vipernet-xyz/tm/tree/v0.34.x/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/vipernet-xyz/tm
   552  $ make install
   553  $ TMHOME="/tmp/example" tendermint init
   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  ```
   559  
   560  Feel free to explore the generated files, which can be found at
   561  `/tmp/example/config` directory. Documentation on the config can be found
   562  [here](https://docs.tendermint.com/v0.34/tendermint-core/configuration.html).
   563  
   564  We are ready to start our application:
   565  
   566  ```bash
   567  ./gradlew run
   568  
   569  gRPC server started, listening on 26658
   570  ```
   571  
   572  Then we need to start Tendermint Core and point it to our application. Staying
   573  within the application directory execute:
   574  
   575  ```bash
   576  $ TMHOME="/tmp/example" tendermint node --abci grpc --proxy_app tcp://127.0.0.1:26658
   577  
   578  I[2019-07-28|15:44:53.632] Version info                                 module=main software=0.32.1 block=10 p2p=7
   579  I[2019-07-28|15:44:53.677] Starting Node                                module=main impl=Node
   580  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}}"
   581  I[2019-07-28|15:44:54.801] Executed block                               module=state height=8 validTxs=0 invalidTxs=0
   582  I[2019-07-28|15:44:54.814] Committed state                              module=state height=8 txs=0 appHash=0000000000000000
   583  ```
   584  
   585  Now open another tab in your terminal and try sending a transaction:
   586  
   587  ```bash
   588  $ curl -s 'localhost:26657/broadcast_tx_commit?tx="tendermint=rocks"'
   589  {
   590    "jsonrpc": "2.0",
   591    "id": "",
   592    "result": {
   593      "check_tx": {
   594        "gasWanted": "1"
   595      },
   596      "deliver_tx": {},
   597      "hash": "CDD3C6DFA0A08CAEDF546F9938A2EEC232209C24AA0E4201194E0AFB78A2C2BB",
   598      "height": "33"
   599  }
   600  ```
   601  
   602  Response should contain the height where this transaction was committed.
   603  
   604  Now let's check if the given key now exists and its value:
   605  
   606  ```bash
   607  $ curl -s 'localhost:26657/abci_query?data="tendermint"'
   608  {
   609    "jsonrpc": "2.0",
   610    "id": "",
   611    "result": {
   612      "response": {
   613        "log": "exists",
   614        "key": "dGVuZGVybWludA==",
   615        "value": "cm9ja3My"
   616      }
   617    }
   618  }
   619  ```
   620  
   621  `dGVuZGVybWludA==` and `cm9ja3M=` are the base64-encoding of the ASCII of `tendermint` and `rocks` accordingly.
   622  
   623  ## Outro
   624  
   625  I hope everything went smoothly and your first, but hopefully not the last,
   626  Tendermint Core application is up and running. If not, please [open an issue on
   627  Github](https://github.com/vipernet-xyz/tm/issues/new/choose). To dig
   628  deeper, read [the docs](https://docs.tendermint.com/v0.34/).
   629  
   630  The full source code of this example project can be found [here](https://github.com/climber73/tendermint-abci-grpc-java).