Stress testing using Java SDK in FISCO BCOS blockchain system

Introduction to Java SDK Demo of FISCO BCOS

  • Java SDK Demo is a collection of benchmark tests based on Java SDK by the FISCO BCOS team, which can perform stress testing on FISCO BCOS nodes. Java SDK Demo provides a contract compilation function that can convert Solidity contract files into Java contract files. In addition, it also provides stress test sample programs for transfer contracts, CRUD contracts, and AMOP functions.

Environment preparation

  • The test program in Java SDK Demo can be run in an environment where JDK 1.8 ~ JDK 14 is deployed. Please make sure that the required version of JDK is installed before executing the test program. Take installing OpenJDK 11 in Ubuntu system as an example:
# Install open JDK 11
sudo apt install openjdk-11-jdk
# Verify Java version
java --version
# Output the following:
# openjdk 11.0.10 2021-01-19
# OpenJDK Runtime Environment (build 11.0.10 + 9-Ubuntu-0ubuntu1.20.04)
# OpenJDK 64-Bit Server VM (build 11.0.10 + 9-Ubuntu-0ubuntu1.20.04, mixed mode, sharing)

Compile source code

# Download source code
git clone https://github.com/FISCO-BCOS/java-sdk-demo
cd java-sdk-demo
# Switch to version 2.0
git checkout main-2.0
# Compile source code
./gradlew build
#When the network cannot access GitHub, please download the source code from the main-2.0 branch at https://gitee.com/FISCO-BCOS/java-sdk-demo.

Configuration Demo

  • Before using the Java SDK Demo, you need to copy the certificate and configure the blockchain node port. My blockchain environment is a local four-node alliance chain configured in accordance with the FISCO BCOS development document. The following operations are based on this environment.
cd dist
# Copy the certificate (assuming the SDK certificate is located in the ~/fisco/nodes/127.0.0.1/sdk directory, please change the path according to the actual situation)
cp -r ~/fisco/nodes/127.0.0.1/sdk/* conf

#Copy configuration file
# Note:
# The default channel port of the FISCO BCOS blockchain system is 20200. If you modify this port, please modify the [network.peers] configuration option in config.toml simultaneously.
cp conf/config-example.toml conf/config.toml
  • Since my blockchain environment has four nodes, the network.peers configuration under java-sdk-demo/dist/conf/config.toml should be modified

Execute stress test program

  • Java SDK Demo provides a series of stress testing programs, including serial transfer contract stress testing, parallel transfer contract stress testing, AMOP stress testing, etc. The specific usage methods are as follows:
# Enter the dist directory
cd dist

# Copy the sol file that needs to be converted into java code to the dist/contracts/solidity path
# Convert sol, where ${packageName} is the generated java code package path
#The generated java code is located in the /dist/contracts/sdk/java directory
java -cp "apps/*:lib/*:conf/" org.fisco.bcos.sdk.demo.codegen.DemoSolcToJava ${packageName}

# Stress test serial transfer contract:
# count: Total transaction volume of stress test
# tps: Stress test QPS
# groupId: group ID of stress test
java -cp 'conf/:lib/*:apps/*' org.fisco.bcos.sdk.demo.perf.PerformanceOk [count] [tps] [groupId]

# Stress test parallel transfer contract
#--------------------------
# Add an account based on the Solidity parallel contract parallelok:
# groupID: group ID of stress test
# count: Total transaction volume of stress test
# tps: Stress test QPS
# file: save the file name of the generated account
java -cp 'conf/:lib/*:apps/*' org.fisco.bcos.sdk.demo.perf.ParallelOkPerf [parallelok] [groupID] [add] [count] [tps] [file]
#Add account based on Precompiled parallel contract precompiled
# (Parameter meanings are the same as above)
java -cp 'conf/:lib/*:apps/*' org.fisco.bcos.sdk.demo.perf.ParallelOkPerf [precompiled] [groupID] [add] [count] [tps] [file]
#--------------------------
# Initiate transfer transaction stress test based on Solidity parallel contract parallelok
# groupID: group ID of stress test
# count: Total transaction volume of stress test
# tps: QPS of stress test
# file: transfer user file
java -cp 'conf/:lib/*:apps/*' org.fisco.bcos.sdk.demo.perf.ParallelOkPerf [parallelok] [groupID] [transfer] [count] [tps] [file]
# Initiate transfer stress test based on Precompiled parallel contract Precompiled
java -cp 'conf/:lib/*:apps/*' org.fisco.bcos.sdk.demo.perf.ParallelOkPerf [precompiled] [groupID] [transfer] [count] [tps] [file]


#CRUD contract stress test
# Stress test CRUD insert
# count: Total transaction volume of stress test
# tps: Stress test QPS
# groupId: stress testing group
java -cp 'conf/:lib/*:apps/*' org.fisco.bcos.sdk.demo.perf.PerformanceTable [insert] [count] [tps] [groupId]
# Stress test CRUD update
# (parameter explanation is the same as above)
java -cp 'conf/:lib/*:apps/*' org.fisco.bcos.sdk.demo.perf.PerformanceTable [update] [count] [tps] [groupId]
# Stress test CRUD remove
# (parameter explanation is the same as above)
java -cp 'conf/:lib/*:apps/*' org.fisco.bcos.sdk.demo.perf.PerformanceTable [remove] [count] [tps] [groupId]
# Stress test CRUD query
# (parameter explanation is the same as above)
java -cp 'conf/:lib/*:apps/*' org.fisco.bcos.sdk.demo.perf.PerformanceTable [query] [count] [tps] [groupId]

Stress testing custom contracts

  • In actual production, we need to stress test the smart contracts we define, so we need to add the smart contracts we wrote to the Java SDK Demo, and also write the corresponding stress test program to test the functions specified in the contract.
  • The development of custom contracts for stress testing is all carried out in the Java SDK Demo. We need to develop three files, namely:
    1. Smart contract to be stress tested: HelloWorld.sol
    2. The Java class compiled by the smart contract with stress testing: HelloWorld.java (You can use the script of the FISCO BCOS console to automatically convert the smart contract into Java code. For specific operation methods, see the introduction of the Java SDK in the FISCO BCOS system development document)
    3. Stress test program: PerformanceHelloWorld.java

Project storage path

  • The storage paths of the above three files are as follows

    java-sdk-demo/src/main/java/org/fisco/bcos/sdk/demo
                                                  ├── contract
                                                  │ ├── HelloWorld.java
                                                  │ └── sol
                                                  │ └── HelloWorld.sol
                                                  └── perf
                                                      └──PerformanceHelloWorld.java
    

Take the HelloWorld contract as an example to develop a stress testing program

  • The writing method of the stress test program can be modified according to the sample program provided in the Java SDK Demo. Here is a stress test program written based on the HelloWorld contract. What is tested is the set method in the HelloWorld contract.

    1. HelloWorld.sol

      pragma solidity>=0.4.24 <0.6.11;
      
      contract HelloWorld {
          string name;
      
          constructor() public {
              name = "Hello, World!";
          }
      
          function get() public view returns (string memory) {
              return name;
          }
      
          function set(string memory n) public {
              name = n;
          }
      }
      
    2. HelloWorld.java (Use a script to automatically generate based on HelloWorld.sol. After the generation, you need to modify the first line of the file to package org.fisco.bcos.sdk.demo.contract, which will not be shown here)

    3. PerformanceHelloWorld.java

      /**
       * Copyright 2014-2020 [fisco-dev]
       *
       * <p>Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
       * except in compliance with the License. You may obtain a copy of the License at
       *
       * <p>http://www.apache.org/licenses/LICENSE-2.0
       *
       * <p>Unless required by applicable law or agreed to in writing, software distributed under the
       * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
       * express or implied. See the License for the specific language governing permissions and
       * limitations under the License.
       */
      package org.fisco.bcos.sdk.demo.perf;
      
      import com.google.common.util.concurrent.RateLimiter;
      import java.math.BigInteger;
      import java.net.URL;
      import java.util.concurrent.atomic.AtomicInteger;
      import org.fisco.bcos.sdk.BcosSDK;
      import org.fisco.bcos.sdk.BcosSDKException;
      import org.fisco.bcos.sdk.client.Client;
      import org.fisco.bcos.sdk.demo.contract.HelloWorld;
      import org.fisco.bcos.sdk.demo.perf.callback.PerformanceCallback;
      import org.fisco.bcos.sdk.demo.perf.collector.PerformanceCollector;
      import org.fisco.bcos.sdk.model.ConstantConfig;
      import org.fisco.bcos.sdk.model.TransactionReceipt;
      import org.fisco.bcos.sdk.transaction.model.exception.ContractException;
      import org.fisco.bcos.sdk.utils.ThreadPoolService;
      import org.slf4j.Logger;
      import org.slf4j.LoggerFactory;
      
      public class PerformanceHelloWorld {<!-- -->
          private static Logger logger = LoggerFactory.getLogger(PerformanceHelloWorld.class);
          private static AtomicInteger sentTransactions = new AtomicInteger(0);
      
          private static void Usage() {<!-- -->
              System.out.println(" Usage:");
              System.out.println(
                      " \t java -cp 'conf/:lib/*:apps/*' org.fisco.bcos.sdk.demo.perf.PerformanceHelloWorld [count] [tps] [groupId].");
          }
      
          public static void main(String[] args) {<!-- -->
              try {<!-- -->
                  String configFileName = ConstantConfig.CONFIG_FILE_NAME;
                  URL configUrl = PerformanceOk.class.getClassLoader().getResource(configFileName);
      
                  if (configUrl == null) {<!-- -->
                      System.out.println("The configFile " + configFileName + " doesn't exist!");
                      return;
                  }
                  if (args.length < 3) {<!-- -->
                      Usage();
                      return;
                  }
                  Integer count = Integer.valueOf(args[0]);
                  Integer qps = Integer.valueOf(args[1]);
                  Integer groupId = Integer.valueOf(args[2]);
                  System.out.println(
                          "====== PerformanceOk trans, count: "
                                   +count
                                   + ", qps:"
                                   + qps
                                   + ", groupId: "
                                   + groupId);
      
                  String configFile = configUrl.getPath();
                  BcosSDK sdk = BcosSDK.build(configFile);
      
                  // build the client
                  Client client = sdk.getClient(groupId);
      
                  // deploy the HelloWorld
                  System.out.println("====== Deploy Ok ====== ");
                  HelloWorld helloWorld = HelloWorld.deploy(client, client.getCryptoSuite().getCryptoKeyPair());
                  System.out.println(
                          "====== Deploy HelloWorld succ, address: " + helloWorld.getContractAddress() + " ====== ");
      
                  PerformanceCollector collector = new PerformanceCollector();
                  collector.setTotal(count);
                  RateLimiter limiter = RateLimiter.create(qps);
                  Integer area = count / 10;
                  final Integer total = count;
      
                  System.out.println("====== PerformanceOk trans start ======");
      
                  ThreadPoolService threadPoolService =
                          new ThreadPoolService(
                                  "PerformanceHelloWorld",
                                  sdk.getConfig().getThreadPoolConfig().getMaxBlockingQueueSize());
      
                  for (Integer i = 0; i < count; + + i) {<!-- -->
                      limiter.acquire();
                      threadPoolService
                              .getThreadPool()
                              .execute(
                                      new Runnable() {<!-- -->
                                          @Override
                                          public void run() {<!-- -->
                                              PerformanceCallback callback = new PerformanceCallback();
                                              callback.setTimeout(0);
                                              callback.setCollector(collector);
                                              try {<!-- -->
                                                  helloWorld.set("Hello, fisco", callback);
                                              } catch (Exception e) {<!-- -->
                                                  TransactionReceipt receipt = new TransactionReceipt();
                                                  receipt.setStatus("-1");
                                                  callback.onResponse(receipt);
                                                  logger.info(e.getMessage());
                                              }
                                              int current = sentTransactions.incrementAndGet();
                                              if (current >= area & amp; & amp; ((current % area) == 0)) {<!-- -->
                                                  System.out.println(
                                                          "Already sent: "
                                                                   +current
                                                                   + "/"
                                                                   + total
                                                                   + " transactions");
                                              }
                                          }
                                      });
                  }
                  // wait to collect all the receipts
                  while (!collector.getReceived().equals(count)) {<!-- -->
                      Thread.sleep(1000);
                  }
                  threadPoolService.stop();
                  System.exit(0);
              } catch (BcosSDKException | ContractException | InterruptedException e) {<!-- -->
                  System.out.println(
                          "====== PerformanceOk test failed, error message: " + e.getMessage());
                  System.exit(0);
              }
          }
      }
      

Stress test of HelloWorld contract

  • After placing the above three files into the corresponding directories, perform the following operations to stress test the HelloWorld contract (if it prompts that the main class cannot be found, you can recompile the project and then enter the dist directory for execution)

    cd /java-sdk-demo/dist
    #200000 represents the total test volume of 200000. 10000 represents 10000 requests sent to the blockchain system per second. 1 represents the groupId of the blockchain system.
    java -cp 'conf/:lib/*:apps/*' org.fisco.bcos.sdk.demo.perf.PerformanceHelloWorld 200000 10000 1
    
  • The stress test results are as follows. You can see that the TPS of the local four-node blockchain system for the set method in the HelloWorld contract is 1797.67.