2. Java SDK 使用说明

本篇介绍:

1、环境依赖

2、sdk依赖使用

3、普通合约安装、调用

4、EVM合约安装、调用

5、更多的示例及全部接口

2.1. 基本概念定义

Java SDK定义了User、Node、ChainClient和ChainManager和SdkConfig几个类,分别介绍如下:

  • User: 表示链的一个用户信息,主要包括证书和private key,用来给要发送的交易payload签名或者给多签的payload签名。

  • Node:表示链的一个节点信息,它定义了节点的各种属性,如节点连接的RPC地址,连接所用的密钥信息等,一个ChainClient对象需要包含一个或多个Node,这样才能对过节点实现各种功能。

  • ChainClient:客户端开发最重要也是使用最多的类,代表逻辑上的一条链,所有客户端对链的操作接口都来自ChainClient。

  • ChainManager:负责管理所有创建过的链客户端,是一个链客户端管理类,避免用户同一条链客户端创建多个ChainClient。用户可以使用ChainManager来检查某条链客户端是否已经创建,或者直接创建某条链客户端,如果这条链客户端已经创建,ChainManager会直接返回之前已经创建过的。

  • SdkConfig: 创建ChainClient所需的配置类。

ChainClient对象给用户使用。数据结构定义如下:

public class User {
    // the organization id of the user
    private String orgId;
    // user's private key used to sign transaction
    private PrivateKey privateKey;
    // user's certificate
    private Certificate certificate;
    // user's private key used to sign transaction
    private PrivateKey tlsPrivateKey;
    // user's certificate
    private Certificate tlsCertificate;
    // the bytes of user's certificate
    private byte[] certBytes;
    // the hash of the cert
    private byte[] certHash;

    private CryptoSuite cryptoSuite;
}

public class Node {
    // node grpc address
    private String grpcUrl;
    // the organization's ca cert bytes
    private byte[][] tlsCertBytes;
    // the hostname in client certificate
    private String hostname;
    // TLS or PLAINTEXT
    private String negotiationType;
    // OPENSSL or JDK
    private String sslProvider;
    // node connect count
    private int connectCount;
}

public class ChainClient {
    // chainId is the identity of the chain
        private String chainId;
        // rpc connection Pool
        private ConnectionPool connectionPool;
        // archive config
        private ArchiveConfig archiveConfig;
        // the user used to sign transactions
        private User clientUser;
}

public class ChainManager {
    // chains' map
    private final Map<String, ChainClient> chains = new HashMap<>();
    // for singleton mode
    private static final ChainManager chainManager = new ChainManager();
}

2.2. 环境准备

2.2.1. 软件依赖

java

jdk 8, jdk 11,jdk17

下载地址:https://www.oracle.com/java/technologies/downloads/

若jdk版本较高,请在路径为cryto/ChainmakerX509CryptoSuite.java的代码中修改import sun.security.ec.CurveDBimport sun.security.util.CurveDB,若无法自动编译,也可直接下载jar:chainmaker-sdk-java-2.2.0.jar

若已安装,请通过命令查看版本:

$ java -version
java version "1.8.0_202"

2.2.2. 下载安装

$ git clone -b v2.3.3 --depth=1 https://git.chainmaker.org.cn/chainmaker/sdk-java.git

2.2.3. jar包依赖

需将sdk中依赖的jar包导入本地工程中, 同时,需将sdklib目录下的netty-tcnative-openssl-static-2.0.39.Final.jar包导入工程中,以便适配国密tls通信。 需要手动引入的jar包:io.netty.netty-handler 需要:4.1.53.Final - 4.1.65.Final,bcpkix-jdk15on 1.62+,grpc。 参考:https://git.chainmaker.org.cn/chainmaker/sdk-java-demo/-/tree/v2.3.2_online_opennet

2.3. 怎么使用SDK

2.3.1. maven中央仓库

<dependency>
    <groupId>org.chainmaker</groupId>
    <artifactId>chainmaker-sdk-java</artifactId>
    <version>2.3.3</version>
</dependency>

2.3.2. 应用demo

java sdk应用示例,请参考: sdk-java-demo

2.3.3. 示例代码

示例用法参考:src/test/java/org/chainmaker/sdk · v2.3.3 · chainmaker / sdk-java · GitLab

注: 下方文档示例可能过时,以gitlab示例为准。

evm和其他合约使用方法在构建参数时有区别。

evm的可参考示例:TestEvmContract

特别的:若要解析字符串数组,需要用新版本的4.x web3j abi,而非5.0的(4.9.7支持jdk8)

2.3.3.1. 以参数形式创建ChainClient

更多内容请参看:TestBase

public void initWithNoConfig() throws SdkException {
    byte[][] tlsCaCerts = new byte[][]{FileUtils.getResourceFileBytes(ORG1_CERT_PATH)};
    
    SdkConfig sdkConfig = new SdkConfig();
    ChainClientConfig chainClientConfig = new ChainClientConfig();
    sdkConfig.setChainClient(chainClientConfig);

    RpcClientConfig rpcClientConfig = new RpcClientConfig();
    rpcClientConfig.setMaxReceiveMessageSize(MAX_MESSAGE_SIZE);

    ArchiveConfig archiveConfig = new ArchiveConfig();
    archiveConfig.setDest(DEST);
    archiveConfig.setType(TYPE);
    archiveConfig.setSecretKey(SECRET_KEY);

    NodeConfig nodeConfig = new NodeConfig();
    nodeConfig.setTrustRootBytes(tlsCaCerts);
    nodeConfig.setTlsHostName(TLS_HOST_NAME1);
    nodeConfig.setEnableTls(true);
    nodeConfig.setNodeAddr(NODE_GRPC_URL1);
    nodeConfig.setConnCnt(CONNECT_COUNT);

    NodeConfig[] nodeConfigs = new NodeConfig[]{nodeConfig};

    chainManager = ChainManager.getInstance();
    chainClient = chainManager.getChainClient(CHAIN_ID);

    chainClientConfig.setOrgId(ORG_ID1);
    chainClientConfig.setChainId(CHAIN_ID);
    chainClientConfig.setUserKeyBytes(FileUtils.getResourceFileBytes(CLIENT1_TLS_KEY_PATH));
    chainClientConfig.setUserCrtBytes(FileUtils.getResourceFileBytes(CLIENT1_TLS_CERT_PATH));
    chainClientConfig.setUserSignKeyBytes(FileUtils.getResourceFileBytes(CLIENT1_KEY_PATH));
    chainClientConfig.setUserSignCrtBytes(FileUtils.getResourceFileBytes(CLIENT1_CERT_PATH));
    chainClientConfig.setRpcClient(rpcClientConfig);
    chainClientConfig.setArchive(archiveConfig);
    chainClientConfig.setNodes(nodeConfigs);

    if (chainClient == null) {
        chainClient = chainManager.createChainClient(sdkConfig);
    }
}

2.3.3.2. 以配置文件形式创建ChainClient

更多内容请参看:TestBase

public void init() throws IOException, SdkException {
    Yaml yaml = new Yaml();
    InputStream in = TestBase.class.getClassLoader().getResourceAsStream(SDK_CONFIG);

    SdkConfig sdkConfig;
    sdkConfig = yaml.loadAs(in, SdkConfig.class);
    assert in != null;
    in.close();

    for (NodeConfig nodeConfig : sdkConfig.getChain_client().getNodes()) {
        List<byte[]> tlsCaCertList = new ArrayList<>();
        for (String rootPath : nodeConfig.getTrustRootPaths()){
            List<String> filePathList = FileUtils.getFilesByPath(rootPath);
            for (String filePath : filePathList) {
                tlsCaCertList.add(FileUtils.getFileBytes(filePath));
            }
        }
        byte[][] tlsCaCerts = new byte[tlsCaCertList.size()][];
        tlsCaCertList.toArray(tlsCaCerts);
        nodeConfig.setTrustRootBytes(tlsCaCerts);
    }

    chainManager = ChainManager.getInstance();
    chainClient = chainManager.getChainClient(sdkConfig.getChain_client().getChainId());

    if (chainClient == null) {
        chainClient = chainManager.createChainClient(sdkConfig);
    }
}

2.3.3.3. 创建合约

更多内容请参看:TestUserContract TestEvmContract,evm需要abi编码,故需特殊处理

public void testCreateContract() throws IOException, InterruptedException, ExecutionException, TimeoutException {
    ResultOuterClass.TxResponse responseInfo = null;
    try {
        byte[] byteCode = FileUtils.getResourceFileBytes(CONTRACT_FILE_PATH);
 
        // 1. create payload
        Request.Payload payload = chainClient.createContractCreatePayload(CONTRACT_NAME, "1", byteCode,
                ContractOuterClass.RuntimeType.WASMER, null);
 
        //2. create payloads with endorsement
        Request.EndorsementEntry[] endorsementEntries = SdkUtils.getEndorsers(payload, new User[]{adminUser1, adminUser2, adminUser3});
 
        // 3. send request
        responseInfo = chainClient.sendContractManageRequest(payload, endorsementEntries, rpcCallTimeout, syncResultTimeout);
    } catch (SdkException e) {
        e.printStackTrace();
        Assert.fail(e.getMessage());
    }
    Assert.assertNotNull(responseInfo);
}

2.3.3.4. 调用合约

更多内容请参看:TestUserContract TestEvmContract,evm需要abi编码,故需特殊处理

public void testInvokeContract() throws Exception {
    ResultOuterClass.TxResponse responseInfo = null;
    try {
        responseInfo = chainClient.invokeContract(CONTRACT_NAME, INVOKE_CONTRACT_METHOD,
                null, null, rpcCallTimeout, syncResultTimeout);
    } catch (Exception e) {
        e.printStackTrace();
        Assert.fail(e.getMessage());
    }
    Assert.assertNotNull(responseInfo);
}

2.3.4. 更多示例和用法

更多示例和用法,请参看单元测试用例

功能 单测代码
基础配置 TestBase
用户合约 TestUserContract
EVM合约 TestEvmContract
系统合约 TestSystemContract
链配置 TestChainConfig
证书管理 TestBaseCertManage
消息订阅 TestSubscribe
线上多签 TestContractMultisign
公钥身份 TestPubkeyManage

2.4. 接口说明

请参看:《chainmaker-java-sdk》

2.5. 使用过程

客户端使用SDK的过程如下:

  1. 创建SdkConfig对象

  2. 获取ChainManager对象

  3. 使用ChainManager获取或创建链对象,创建ChainClient时需要将SdkConfig对象作为参数传入

  4. 调用ChainClient对象的接口进行操作

2.5.1. 使用示例

  1. 初始化,创建ChainClient

   public void init() {
       Yaml yaml = new Yaml();
       InputStream in = TestBase.class.getClassLoader().getResourceAsStream(SDK_CONFIG);

       SdkConfig sdkConfig;
       sdkConfig = yaml.loadAs(in, SdkConfig.class);
       assert in != null;
       in.close();

       for (NodeConfig nodeConfig : sdkConfig.getChain_client().getNodes()) {
           List<byte[]> tlsCaCertList = new ArrayList<>();
           for (String rootPath : nodeConfig.getTrustRootPaths()){
               List<String> filePathList = FileUtils.getFilesByPath(rootPath);
               for (String filePath : filePathList) {
                   tlsCaCertList.add(FileUtils.getFileBytes(filePath));
               }
           }
           byte[][] tlsCaCerts = new byte[tlsCaCertList.size()][];
           tlsCaCertList.toArray(tlsCaCerts);
           nodeConfig.setTrustRootBytes(tlsCaCerts);
       }

       chainManager = ChainManager.getInstance();
       chainClient = chainManager.getChainClient(sdkConfig.getChain_client().getChainId());

       if (chainClient == null) {
           chainClient = chainManager.createChainClient(sdkConfig);
       }
   }
  1. 创建合约

   public void testCreateContract() throws IOException, InterruptedException, ExecutionException, TimeoutException {
       ResultOuterClass.TxResponse responseInfo = null;
       try {
           byte[] byteCode = FileUtils.getResourceFileBytes(CONTRACT_FILE_PATH);

           // 1. create payload
           Request.Payload payload = chainClient.createContractCreatePayload(CONTRACT_NAME, "1", byteCode,
                   ContractOuterClass.RuntimeType.WASMER, null);

           //2. create payloads with endorsement
           Request.EndorsementEntry[] endorsementEntries = SdkUtils.getEndorsers(payload, new User[]{adminUser1, adminUser2, adminUser3});

           // 3. send request
           responseInfo = chainClient.sendContractManageRequest(payload, endorsementEntries, rpcCallTimeout, syncResultTimeout);
       } catch (SdkException e) {
           e.printStackTrace();
           Assert.fail(e.getMessage());
       }
       Assert.assertNotNull(responseInfo);
   }
  1. 调用合约

   public void testInvokeContract() throws Exception {
       ResultOuterClass.TxResponse responseInfo = null;
       try {
           responseInfo = chainClient.invokeContract(CONTRACT_NAME, INVOKE_CONTRACT_METHOD,
                   null, null, rpcCallTimeout, syncResultTimeout);
       } catch (Exception e) {
           e.printStackTrace();
           Assert.fail(e.getMessage());
       }
       Assert.assertNotNull(responseInfo);
   }

2.5.2. SDK Jar包引用方式

2.5.2.1. 源码编译

git clone --depth=1 https://git.chainmaker.org.cn/chainmaker/sdk-java.git
// 说明:需要使用openjdk 1.8.151+并提前安装gradle,也可以使用intelliJ IDEA打开项目进行编译
cd chainamker-sdk-java
./gradlew build

2.5.2.2. maven中央仓库

<dependency>
    <groupId>org.chainmaker</groupId>
    <artifactId>chainmaker-sdk-java</artifactId>
    <version>2.3.3</version>
</dependency>

2.5.2.3. 使用

2.5.2.3.1. 导入jar

导入jar包,这里使用IntelliJ为示例引用jar包,将编译好的jar包拷贝到需要使用sdk的项目下(一般可以在项目下建一个libs目录),然后打开IntelliJ IDEA->File->Project Structures,如下图点击“+”号,选择JARs or Directories,选中jar包点击open即可。

2.5.2.3.2. 依赖库

导入依赖jar包,需将sdk中依赖的jar包导入本地工程中,同时,需将sdklib目录下的netty-tcnative-openssl-static-2.0.39.Final.jar包导入工程中,以便适配国密tls通信。 tcnativejar包及动态库地址: tcnative

2.5.2.3.3. 引用

在要使用sdk的源文件里使用import引用sdk包,如下:

import org.chainmaker.sdk.*;
2.5.2.3.4. 应用demo

java sdk应用示例,请参考 sdk-java-demo



2.5.3. 密码机支持

2.5.3.1. 密码机介绍

项目 备注
密码机厂商 三未新安
密码机型号 SJJ1012-A

2.5.3.2. 引用方式

  • sdklib目录下的crypto_v5.3.3.8.jarswxajce_v5.3.3.8.jar包和swsds.ini文件导入当前项目的lib目录下,

  • 导入依赖jar包,需将sdk中依赖的crypto_v5.3.3.8.jarswxajce_v5.3.3.8.jar包导入本地工程中

  • lib目录下swsds.ini文件中与密码机相关的配置进行修改

[HSM1]
# 密码机ip
ip=192.168.1.240 
port=8008
passwd=11111111

2.5.3.3. 配置修改

2.5.3.3.1. 配置文件方式修改
  • sdk_config.yml(将pkcs11.enable设置为true)

  pkcs11:
    enabled: true # pkcs11 is not used by default
2.5.3.3.2. ChainClientConfig方式修改
  • sdk_config.yml(将pkcs11.enable设置为true)

    ChainClientConfig chainClientConfig = new ChainClientConfig();
    Pkcs11Config pkcs11Config = new Pkcs11Config();
    pkcs11Config.setEnabled(true);
    chainClientConfig.setPkcs11(pkcs11Config);
2.5.3.3.3. 用户配置
  • 配置用户时,将pkcs11Enable参数设置为true

    adminUser = new User(ORG_ID3, FileUtils.getResourceFileBytes(ADMIN3_KEY_PATH),
         FileUtils.getResourceFileBytes(ADMIN3_CERT_PATH),
         FileUtils.getResourceFileBytes(ADMIN3_TLS_KEY_PATH),
         FileUtils.getResourceFileBytes(ADMIN3_TLS_CERT_PATH), true);

2.5.3.4. 归档中心支持

2.5.3.4.1. 配置文件修改
  • sdk_config.yml(将archive_center_query_first设置为true)

  archive:
    # mysql archivecenter 归档中心 数据归档链外存储相关配置
    type: "archivecenter"
    dest: "root:123456:localhost:3306"
    secret_key: xxx
  # 如果为true且归档中心配置打开,那么查询数据优先从归档中心查询
  # 如果是false,则不访问归档中心,只访问链数据
  archive_center_query_first: false
  # 归档中心
  archive_center_config:
    chain_genesis_hash: a23a0c6fd402010efbc31741b2a868c9e6558471fda1e8d1f16673ef683e06db
    archive_center_http_url: http://127.0.0.1:13119
    request_second_limit: 10
    rpc_address: 127.0.0.1:13120
    tls_enable: false
    tls:
      server_name: archiveserver1.tls.wx-org.chainmaker.org
      priv_key_file: src/test/resources/archivecenter/archiveclient1.tls.key
      cert_file: src/test/resources/archivecenter/archiveclient1.tls.crt
      trust_ca_list:
        - src/test/resources/archivecenter/ca
    max_send_msg_size: 200
    max_recv_msg_size: 200  

2.5.3.5. 低配模式支持

  • 如果使用低配模式,获取到rpc连接会立即释放

   ##连接池配置
  connPool:
    # 是否使用低配
    enableLowProfile: false
    # 最大连接数
    maxTotal: 100
    # 最少空闲连接
    minIdle: 5
    #最大空闲连接
    maxIdle: 20
    #连接空闲最小保活时间,默认即为-1,单位:ms
    #当空闲的时间大于这个值时,强制移除该空闲对象
    minEvictableIdleTime: -1
    #连接空闲最小保活时间,默认即为30分钟(1800000),单位:ms
    #当对象的空闲时间超过这个值,并且当前空闲对象的数量大于最小空闲数量(minIdle)时,执行移除操作
    softMinEvictableIdleTime: 1800000
    #回收空闲线程的执行周期,单位毫秒。默认值5分钟(300000) ,-1 表示不启用线程回收资源,单位:ms
    timeBetweenEvictionRuns: 300000
    #没有空闲连接时,获取连接是否阻塞
    blockWhenExhausted: true
    #当没有空闲连接时,获取连接阻塞等待时间,单位:ms
    maxWaitMillis: 11000
2.5.3.5.1. 注意事项
  • http模式,sdk不支持归档注册,不支持归档,需要使用归档服务进行注册和归档

  • chain_genesis_hash为链的第一个区块的hash值,可以通过如下方式获取

    ChainmakerBlock.BlockInfo blockInfo = null;
    try {
        blockInfo = chainClient.getBlockByHeight(0, false, rpcCallTimeout);
    } catch (SdkException e) {
        e.printStackTrace();
    }
    System.out.println(Hex.toHexString(blockInfo.getBlock().getHeader().getBlockHash().toByteArray()));
  • grpc模式支持tls开启,开关状态和归档中心服务保持一致

  • 优先级

    • 若设置type为mysql,则使用mysql模式

    • 若设置type为archivecenter,则会先判断rpc连接是否存在,存在会使用rpc模式

    • 若rpc连接不存在,判断http连接是否存在,存在会使用http模式

    • 若两者都没有,则不会使用归档中心

2.6. changelog

按发布时间倒序

2.6.1. v2.3.3

| 概要 | 描述 | |—————–|—————————————————————————–| | 增加自定义签名算法接口实现 | 增加自定义签名算法接口实现 | | 优化获取国密公钥地址 | 优化获取国密公钥地址 | | 优化根据配置文件地址获取公司钥 | 优先使用相对路径,然后在获取resources里面的文件 | | 增加同步获取交易结果rpc实现 | 增加同步获取交易结果rpc实现, 使用enable_send_request_sync和enable_tx_result_dispatcher进行判断 | | 增加低配模式 | 根据enable_low_profile判断是否使用低配模式,低配模式获取到连接之后直接返回到连接池 |

2.6.2. v2.3.1.4

概要 描述
修复同步订阅模式下死循环问题 订阅失败重连时区块高度超过当前链高度
优化日志打印 某些错误信息以WARN堆栈形式打印
修复失败节点识别问题 节点识别从url改成url+tlshostname
修复web3j解析字符串数组失败问题 将web3j-abi降级成jdk8支持的最新版4.9.7

2.6.3. v2.3.2

概要 描述
支持归档中心 增加归档中心,若archive_center_query_first为true,则可以设置归档中心,访问归档服务的数据
国密支持jdk8,jdk11,jdk17 在支持jdk8,jdk11的同时,进行了版本升级,支持jdk17
依赖包升级 grpc等版本升级,使用可以参考build.gradle内依赖
新增异常类型 可以根据异常类型进行判断,确认是否需要重试等操作
新增代付者处理 包含发送合约管理请求,发起多签请求等
新增gas limit限制 包含发起多签投票以及发起多签请求构建payload的操作
新增修改链基本配置操作 包含链的default_gas_price, install_base_gas,install_gas_price

2.6.4. v2.3.1.3

概要 描述
连接池使用方式改为先进先出 在配置多个节点后,可达到负载均衡上链效果
修复连接关闭但未释放问题 修复连接异常关闭未从连接池销毁,导致所有连接不可用问题
有条件的负载订阅 可以通过修改配置文件中的node顺序,达到负载均衡的效果

2.6.5. v2.3.1.2

概要 描述
连接池频繁关闭/创建优化 添加连接池参数:softMinEvictableIdleTime,修改默认扫描时间5分钟。修改默认配置生产可用。
连接异常关闭后续处理 连接异常关闭之后,不返回连接到连接池,直接对连接进行销毁处理
订阅获取交易结果写入Map 订阅修改交易结果写入map,修改mapValue类型为对象,invoke注册key,如果有key才会进行写入,拿到结果进行注销
修复ManagedChannel allocation site连接未正常关闭错误 在将对象设置为null之前,关闭该对象。
连接节点超时时间加长 连接超时时间默认从3秒改成10秒

2.7. v2.3.2升级说明

  • 由于2.3.2版本升级包含依赖版本升级,所以在使用时,需要升级依赖版本,可以参考sdk-demo

  • 其他依赖升级可以根据使用进行修改,参考java-sdk中build.gradle

  • 若不需要归档中心,可以继续使用v2.3.1.3中sdk_config.yaml,若需要使用归档中心,则可以增加对应的归档中心配置。