Java SDK

Web3SDK可以支持访问节点、查询节点状态、修改系统设置和发送交易等功能。该版本(2.0)的技术文档只适用Web3SDK 2.0及以上版本(与FISCO BCOS 2.0及以上版本适配),1.2.x版本的技术文档请查看Web3SDK 1.2.x版本技术文档

2.0+版本主要特性包括:

  • 提供调用FISCO BCOS JSON-RPC的Java API
  • 支持预编译(Precompiled)合约管理区块链
  • 支持链上信使协议为联盟链提供安全高效的消息信道
  • 支持使用国密算法发送交易

环境要求

重要

  • Java版本

JDK1.8 或者以上版本,推荐使用OracleJDK。

注意:CentOS的yum仓库的OpenJDK缺少JCE(Java Cryptography Extension),会导致JavaSDK无法正常连接区块链节点。

  • Java安装

参考 Java环境配置

  • FISCO BCOS区块链环境搭建

参考 FISCO BCOS安装教程

  • 网络连通性

检查Web3SDK连接的FISCO BCOS节点`channel_listen_port`是否能telnet通,若telnet不通,需要检查网络连通性和安全策略。

Java应用引入SDK

通过gradle或maven引入SDK到java应用

gradle:

  1. compile ('org.fisco-bcos:web3sdk:2.1.0')

maven:

  1. <dependency>
  2. <groupId>org.fisco-bcos</groupId>
  3. <artifactId>web3sdk</artifactId>
  4. <version>2.1.0</version>
  5. </dependency>

由于引入了以太坊的solidity编译器相关jar包,需要在Java应用的gradle配置文件build.gradle中添加以太坊的远程仓库。

  1. repositories {
  2. mavenCentral()
  3. maven { url "https://dl.bintray.com/ethereum/maven/" }
  4. }

注: 如果下载Web3SDK的依赖solcJ-all-0.4.25.jar速度过慢,可以参考这里进行下载。

配置SDK

FISCO BCOS节点证书配置

FISCO BCOS作为联盟链,其SDK连接区块链节点需要通过证书(ca.crt、sdk.crt)和私钥(sdk.key)进行双向认证。因此需要将节点所在目录nodes/${ip}/sdk下的ca.crtsdk.crtsdk.key文件拷贝到项目的资源目录,供SDK与节点建立连接时使用。(低于2.1版本的FISCO BCOS节点目录下只有node.crtnode.key,需将其重命名为sdk.crtsdk.key以兼容最新的SDK)

配置文件设置

Java应用的配置文件需要做相关配置。值得关注的是,FISCO BCOS 2.0+版本支持多群组功能,SDK需要配置群组的节点信息。将以Spring项目和Spring Boot项目为例,提供配置指引。

Spring项目配置

提供Spring项目中关于applicationContext.xml的配置下所示。

  1. <?xml version="1.0" encoding="UTF-8" ?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
  4. xmlns:tx="http://www.springframework.org/schema/tx" xmlns:aop="http://www.springframework.org/schema/aop"
  5. xmlns:context="http://www.springframework.org/schema/context"
  6. xsi:schemaLocation="http://www.springframework.org/schema/beans
  7. http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
  8. http://www.springframework.org/schema/tx
  9. http://www.springframework.org/schema/tx/spring-tx-2.5.xsd
  10. http://www.springframework.org/schema/aop
  11. http://www.springframework.org/schema/aop/spring-aop-2.5.xsd">
  12. <bean id="encryptType" class="org.fisco.bcos.web3j.crypto.EncryptType">
  13. <constructor-arg value="0"/> <!-- 0:standard 1:guomi -->
  14. </bean>
  15. <bean id="groupChannelConnectionsConfig" class="org.fisco.bcos.channel.handler.GroupChannelConnectionsConfig">
  16. <property name="caCert" value="ca.crt" />
  17. <property name="sslCert" value="sdk.crt" />
  18. <property name="sslKey" value="sdk.key" />
  19. <property name="allChannelConnections">
  20. <list> <!-- 每个群组需要配置一个bean,每个群组可以配置多个节点 -->
  21. <bean id="group1" class="org.fisco.bcos.channel.handler.ChannelConnections">
  22. <property name="groupId" value="1" /> <!-- 群组的groupID -->
  23. <property name="connectionsStr">
  24. <list>
  25. <value>127.0.0.1:20200</value> <!-- IP:channel_port -->
  26. <value>127.0.0.1:20201</value>
  27. </list>
  28. </property>
  29. </bean>
  30. <bean id="group2" class="org.fisco.bcos.channel.handler.ChannelConnections">
  31. <property name="groupId" value="2" /> <!-- 群组的groupID -->
  32. <property name="connectionsStr">
  33. <list>
  34. <value>127.0.0.1:20202</value>
  35. <value>127.0.0.1:20203</value>
  36. </list>
  37. </property>
  38. </bean>
  39. </list>
  40. </property>
  41. </bean>
  42. <bean id="channelService" class="org.fisco.bcos.channel.client.Service" depends-on="groupChannelConnectionsConfig">
  43. <property name="groupId" value="1" /> <!-- 配置连接群组1 -->
  44. <property name="agencyName" value="fisco" /> <!-- 配置机构名 -->
  45. <property name="allChannelConnections" ref="groupChannelConnectionsConfig"></property>
  46. </bean>
  47. </beans>

applicationContext.xml配置项详细说明:

  • encryptType: 国密算法开关(默认为0)
    • 0: 不使用国密算法发交易
    • 1: 使用国密算法发交易(开启国密功能,需要连接的区块链节点是国密节点,搭建国密版FISCO BCOS区块链参考这里)
  • groupChannelConnectionsConfig:
    • 配置待连接的群组,可以配置一个或多个群组,每个群组需要配置群组ID
    • 每个群组可以配置一个或多个节点,设置群组节点的配置文件config.ini[rpc]部分的channel_listen_ip(若节点小于v2.3.0版本,查看配置项listen_ip)和channel_listen_port
    • caCert用于配置链ca证书路径
    • sslCert用于配置SDK所使用的证书路径
    • sslKey用于配置SDK所使用的证书对应的私钥路径
  • channelService: 通过指定群组ID配置SDK实际连接的群组,指定的群组ID是groupChannelConnectionsConfig配置中的群组ID。SDK会与群组中配置的节点均建立连接,然后随机选择一个节点发送请求。

备注:刚下载项目时,有些插件可能没有安装,代码会报错。当你第一次在IDEA上使用lombok这个工具包时,请按以下步骤操作:

  • 进入setting->Plugins->Marketplace->选择安装Lombok plugin
  • 进入设置Setting-> Compiler -> Annotation Processors -> 勾选Enable annotation processing。

Spring Boot项目配置

提供Spring Boot项目中关于application.yml的配置如下所示。

  1. encrypt-type: # 0:普通, 1:国密
  2. encrypt-type: 0
  3. group-channel-connections-config:
  4. caCert: ca.crt
  5. sslCert: sdk.crt
  6. sslKey: sdk.key
  7. all-channel-connections:
  8. - group-id: 1 #group ID
  9. connections-str:
  10. # 若节点小于v2.3.0版本,查看配置项listen_ip:channel_listen_port
  11. - 127.0.0.1:20200 # node channel_listen_ip:channel_listen_port
  12. - 127.0.0.1:20201
  13. - group-id: 2
  14. connections-str:
  15. # 若节点小于v2.3.0版本,查看配置项listen_ip:channel_listen_port
  16. - 127.0.0.1:20202 # node channel_listen_ip:channel_listen_port
  17. - 127.0.0.1:20203
  18. channel-service:
  19. group-id: 1 # sdk实际连接的群组
  20. agency-name: fisco # 机构名称

application.yml配置项与applicationContext.xml配置项相对应,详细介绍参考applicationContext.xml配置说明。

使用SDK

Spring项目开发指引

调用SDK的API(参考Web3SDK API列表设置或查询相关的区块链数据。

调用SDK Web3j的API

加载配置文件,SDK与区块链节点建立连接,获取web3j对象,根据Web3j对象调用相关API。示例代码如下:

  1. //读取配置文件,SDK与区块链节点建立连接
  2. ApplicationContext context = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
  3. Service service = context.getBean(Service.class);
  4. service.run();
  5. ChannelEthereumService channelEthereumService = new ChannelEthereumService();
  6. channelEthereumService.setChannelService(service);
  7. //获取Web3j对象
  8. Web3j web3j = Web3j.build(channelEthereumService, service.getGroupId());
  9. //通过Web3j对象调用API接口getBlockNumber
  10. BigInteger blockNumber = web3j.getBlockNumber().send().getBlockNumber();
  11. System.out.println(blockNumber);

注: SDK处理交易超时时间默认为60秒,即60秒内没有收到交易响应,判断为超时。该值可以通过ChannelEthereumService进行设置,示例如下:

  1. // 设置交易超时时间为100000毫秒,即100秒
  2. channelEthereumService.setTimeout(100000);
调用SDK Precompiled的API

加载配置文件,SDK与区块链节点建立连接。获取SDK Precompiled Service对象,调用相关的API。示例代码如下:

  1. //读取配置文件,SDK与区块链节点建立连接,获取Web3j对象
  2. ApplicationContext context = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
  3. Service service = context.getBean(Service.class);
  4. service.run();
  5. ChannelEthereumService channelEthereumService = new ChannelEthereumService();
  6. channelEthereumService.setChannelService(service);
  7. Web3j web3j = Web3j.build(channelEthereumService, service.getGroupId());
  8. String privateKey = "b83261efa42895c38c6c2364ca878f43e77f3cddbc922bf57d0d48070f79feb6";
  9. //指定外部账户私钥,用于交易签名
  10. Credentials credentials = GenCredential.create(privateKey);
  11. //获取SystemConfigService对象
  12. SystemConfigService systemConfigService = new SystemConfigService(web3j, credentials);
  13. //通过SystemConfigService对象调用API接口setValueByKey
  14. String result = systemConfigService.setValueByKey("tx_count_limit", "2000");
  15. //通过Web3j对象调用API接口getSystemConfigByKey
  16. String value = web3j.getSystemConfigByKey("tx_count_limit").send().getSystemConfigByKey();
  17. System.out.println(value);
创建并使用指定外部账户

sdk发送交易需要一个外部账户,下面是随机创建一个外部账户的方法。

  1. //创建普通外部账户
  2. EncryptType.encryptType = 0;
  3. //创建国密外部账户,向国密区块链节点发送交易需要使用国密外部账户
  4. // EncryptType.encryptType = 1;
  5. Credentials credentials = GenCredential.create();
  6. //账户地址
  7. String address = credentials.getAddress();
  8. //账户私钥
  9. String privateKey = credentials.getEcKeyPair().getPrivateKey().toString(16);
  10. //账户公钥
  11. String publicKey = credentials.getEcKeyPair().getPublicKey().toString(16);

使用指定的外部账户

  1. //通过指定外部账户私钥使用指定的外部账户
  2. Credentials credentials = GenCredential.create(privateKey);
加载账户私钥文件

如果通过账户生成脚本get_accounts.sh生成了PEM或PKCS12格式的账户私钥文件(账户生成脚本的用法参考账户管理文档),则可以通过加载PEM或PKCS12账户私钥文件使用账户。加载私钥有两个类:P12Manager和PEMManager,其中,P12Manager用于加载PKCS12格式的私钥文件,PEMManager用于加载PEM格式的私钥文件。

  • P12Manager用法举例: 在applicationContext.xml中配置PKCS12账户的私钥文件路径和密码
  1. <bean id="p12" class="org.fisco.bcos.channel.client.P12Manager" init-method="load" >
  2. <property name="password" value="123456" />
  3. <property name="p12File" value="classpath:0x0fc3c4bb89bd90299db4c62be0174c4966286c00.p12" />
  4. </bean>

开发代码

  1. //加载Bean
  2. ApplicationContext context = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
  3. P12Manager p12 = context.getBean(P12Manager.class);
  4. //提供密码获取ECKeyPair,密码在生产p12账户文件时指定
  5. ECKeyPair p12KeyPair = p12.getECKeyPair(p12.getPassword());
  6. //以十六进制串输出私钥和公钥
  7. System.out.println("p12 privateKey: " + p12KeyPair.getPrivateKey().toString(16));
  8. System.out.println("p12 publicKey: " + p12KeyPair.getPublicKey().toString(16));
  9. //生成web3sdk使用的Credentials
  10. Credentials credentials = GenCredential.create(p12KeyPair.getPrivateKey().toString(16));
  11. System.out.println("p12 Address: " + credentials.getAddress());
  • PEMManager使用举例

在applicationContext.xml中配置PEM账户的私钥文件路径

  1. <bean id="pem" class="org.fisco.bcos.channel.client.PEMManager" init-method="load" >
  2. <property name="pemFile" value="classpath:0x0fc3c4bb89bd90299db4c62be0174c4966286c00.pem" />
  3. </bean>

使用代码加载

  1. //加载Bean
  2. ApplicationContext context = new ClassPathXmlApplicationContext("classpath:applicationContext-keystore-sample.xml");
  3. PEMManager pem = context.getBean(PEMManager.class);
  4. ECKeyPair pemKeyPair = pem.getECKeyPair();
  5. //以十六进制串输出私钥和公钥
  6. System.out.println("PEM privateKey: " + pemKeyPair.getPrivateKey().toString(16));
  7. System.out.println("PEM publicKey: " + pemKeyPair.getPublicKey().toString(16));
  8. //生成web3sdk使用的Credentials
  9. Credentials credentialsPEM = GenCredential.create(pemKeyPair.getPrivateKey().toString(16));
  10. System.out.println("PEM Address: " + credentialsPEM.getAddress());

通过SDK部署并调用合约

准备Java合约文件

控制台提供一个专门的编译合约工具,方便开发者将Solidity合约文件编译为Java合约文件,具体使用方式参考这里

部署并调用合约

SDK的核心功能是部署/加载合约,然后调用合约相关接口,实现相关业务功能。部署合约调用Java合约类的deploy方法,获取合约对象。通过合约对象可以调用getContractAddress方法获取部署合约的地址以及调用该合约的其他方法实现业务功能。如果合约已部署,则通过部署的合约地址可以调用load方法加载合约对象,然后调用该合约的相关方法。

  1. //读取配置文件,sdk与区块链节点建立连接,获取web3j对象
  2. ApplicationContext context = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
  3. Service service = context.getBean(Service.class);
  4. service.run();
  5. ChannelEthereumService channelEthereumService = new ChannelEthereumService();
  6. channelEthereumService.setChannelService(service);
  7. channelEthereumService.setTimeout(10000);
  8. Web3j web3j = Web3j.build(channelEthereumService, service.getGroupId());
  9. //准备部署和调用合约的参数
  10. BigInteger gasPrice = new BigInteger("300000000");
  11. BigInteger gasLimit = new BigInteger("300000000");
  12. String privateKey = "b83261efa42895c38c6c2364ca878f43e77f3cddbc922bf57d0d48070f79feb6";
  13. //指定外部账户私钥,用于交易签名
  14. Credentials credentials = GenCredential.create(privateKey);
  15. //部署合约
  16. YourSmartContract contract = YourSmartContract.deploy(web3j, credentials, new StaticGasProvider(gasPrice, gasLimit)).send();
  17. //根据合约地址加载合约
  18. //YourSmartContract contract = YourSmartContract.load(address, web3j, credentials, new StaticGasProvider(gasPrice, gasLimit));
  19. //调用合约方法发送交易
  20. TransactionReceipt transactionReceipt = contract.someMethod(<param1>, ...).send();
  21. //查询合约方法查询该合约的数据状态
  22. Type result = contract.someMethod(<param1>, ...).send();

Spring Boot项目开发指引

提供spring-boot-starter示例项目供参考。Spring Boot项目开发与Spring项目开发类似,其主要区别在于配置文件方式的差异。该示例项目提供相关的测试案例,具体描述参考示例项目的README文档。

SDK国密功能使用

  • 前置条件:FISCO BCOS区块链采用国密算法,搭建国密版的FISCO BCOS区块链请参考国密使用手册
  • 启用国密功能:applicationContext.xml/application.yml配置文件中将encryptType属性设置为1。
  • 加载私钥使用GenCredential类(适用于国密和非国密),Credential类只适用于加载非国密私钥。

国密版SDK调用API的方式与普通版SDK调用API的方式相同,其差异在于国密版SDK需要生成国密版的Java合约文件。编译国密版的Java合约文件参考这里

Web3SDK API

Web3SDK API主要分为Web3j API和Precompiled Service API。其中Web3j API可以查询区块链相关的状态,发送和查询交易信息;Precompiled Service API可以管理区块链相关配置以及实现特定功能。

Web3j API

Web3j API是由web3j对象调用的FISCO BCOS的RPC API,其API名称与RPC API相同,参考RPC API文档

Precompiled Service API

预编译合约是FISCO BCOS底层通过C++实现的一种高效智能合约。SDK已提供预编译合约对应的Java接口,控制台通过调用这些Java接口实现了相关的操作命令,体验控制台,参考控制台手册。SDK提供Precompiled对应的Service类,分别是分布式控制权限相关的PermissionService,CNS相关的CnsService,系统属性配置相关的SystemConfigService和节点类型配置相关ConsensusService。相关错误码请参考:Precompiled Service API 错误码

PermissionService

SDK提供对分布式控制权限的支持,PermissionService可以配置权限信息,其API如下:

  • public String grantUserTableManager(String tableName, String address): 根据用户表名和外部账户地址设置权限信息。
  • public String revokeUserTableManager(String tableName, String address): 根据用户表名和外部账户地址去除权限信息。
  • public List listUserTableManager(String tableName): 根据用户表名查询设置的权限记录列表(每条记录包含外部账户地址和生效块高)。
  • public String grantDeployAndCreateManager(String address): 增加外部账户地址的部署合约和创建用户表权限。
  • public String revokeDeployAndCreateManager(String address): 移除外部账户地址的部署合约和创建用户表权限。
  • public List listDeployAndCreateManager(): 查询拥有部署合约和创建用户表权限的权限记录列表。
  • public String grantPermissionManager(String address): 增加外部账户地址的管理权限的权限。
  • public String revokePermissionManager(String address): 移除外部账户地址的管理权限的权限。
  • public List listPermissionManager(): 查询拥有管理权限的权限记录列表。
  • public String grantNodeManager(String address): 增加外部账户地址的节点管理权限。
  • public String revokeNodeManager(String address): 移除外部账户地址的节点管理权限。
  • public List listNodeManager(): 查询拥有节点管理的权限记录列表。
  • public String grantCNSManager(String address): 增加外部账户地址的使用CNS权限。
  • public String revokeCNSManager(String address): 移除外部账户地址的使用CNS权限。
  • public List listCNSManager(): 查询拥有使用CNS的权限记录列表。
  • public String grantSysConfigManager(String address): 增加外部账户地址的系统参数管理权限。
  • public String revokeSysConfigManager(String address): 移除外部账户地址的系统参数管理权限。
  • public List listSysConfigManager(): 查询拥有系统参数管理的权限记录列表。

CnsService

SDK提供对CNS的支持。CnsService可以配置CNS信息,其API如下:

  • String registerCns(String name, String version, String address, String abi): 根据合约名、合约版本号、合约地址和合约abi注册CNS信息。
  • String getAddressByContractNameAndVersion(String contractNameAndVersion): 根据合约名和合约版本号(合约名和合约版本号用英文冒号连接)查询合约地址。若缺失合约版本号,默认使用合约最新版本。
  • List queryCnsByName(String name): 根据合约名查询CNS信息。
  • List queryCnsByNameAndVersion(String name, String version): 根据合约名和合约版本号查询CNS信息。

SystemConfigService

SDK提供对系统配置的支持。SystemConfigService可以配置系统属性值(目前支持tx_count_limit和tx_gas_limit属性的设置),其API如下:

  • String setValueByKey(String key, String value): 根据键设置对应的值(查询键对应的值,参考Web3j API中的getSystemConfigByKey接口)。

ConsensusService

SDK提供对节点类型配置的支持。ConsensusService可以设置节点类型,其API如下:

  • String addSealer(String nodeId): 根据节点NodeID设置对应节点为共识节点。
  • String addObserver(String nodeId): 根据节点NodeID设置对应节点为观察节点。
  • String removeNode(String nodeId): 根据节点NodeID设置对应节点为游离节点。

CRUDService

SDK提供对CRUD(增删改查)操作的支持。CRUDService可以创建表,对表进行增删改查操作,其API如下:

  • int createTable(Table table): 创建表,提供表对象。表对象需要设置其表名,主键字段名和其他字段名。其中,其他字段名是以英文逗号分隔拼接的字符串。返回创建表的状态值,返回为0则代表创建成功。
  • int insert(Table table, Entry entry): 插入记录,提供表对象和Entry对象。表对象需要设置表名和主键值;Entry是map对象,提供插入的字段名和字段值。返回插入的记录数。
  • int update(Table table, Entry entry, Condition condition): 更新记录,提供表对象,Entry对象和Condtion对象。表对象需要设置表名和主键值;Entry是map对象,提供更新的字段名和字段值;Condition对象是条件对象,可以设置更新的匹配条件。返回更新的记录数。
  • List> select(Table table, Condition condition): 查询记录,提供表对象和Condtion对象。表对象需要设置表名和主键值;Condition对象是条件对象,可以设置查询的匹配条件。返回查询的记录。
  • int remove(Table table, Condition condition): 移除记录,提供表对象和Condtion对象。表对象需要设置表名和主键值;Condition对象是条件对象,可以设置移除的匹配条件。返回移除的记录数。
  • Table desc(String tableName): 根据表名查询表的信息,主要包含表的主键和其他属性字段。返回表类型,主要包含表的主键字段名和其他属性字段名。

交易解析

FISCO BCOS的交易是一段发往区块链系统的请求数据,用于部署合约,调用合约接口,维护合约的生命周期以及管理资产,进行价值交换等。当交易确认后会产生交易回执,交易回执交易均保存在区块里,用于记录交易执行过程生成的信息,如结果码、日志、消耗的gas量等。用户可以使用交易哈希查询交易回执,判定交易是否完成。

交易回执包含三个关键字段,分别是input, output , logs:

字段类型描述
inputString交易输入的ABI编码十六进制字符串
outputString交易返回的ABI编码十六进制字符串
logsListevent log列表,保存交易的event信息

交易解析功能帮助用户解析这三个字段为json数据和java对象。

接口说明

代码包路径org.fisco.bcos.web3j.tx.txdecode,使用TransactionDecoderFactory工厂类建立交易解析对象TransactionDecoder,有两种方式:

  1. TransactionDecoder buildTransactionDecoder(String abi, String bin);

    abi:合约的ABI

    bin:合约bin,暂无使用,可以直接传入空字符串””

  2. TransactionDecoder buildTransactionDecoder(String contractName);

    contractName:合约名称,在应用的根目录下创建solidity目录,将交易相关的合约放在solidity目录,通过指定合约名获取交易解析对象

交易解析对象TransactionDecoder接口列表:

  1. String decodeInputReturnJson(String input)

    解析input,将结果封装为json字符串,json格式

    1. {"data":[{"name":"","type":"","data":} ... ],"function":"","methodID":""}

    function : 函数签名字符串

    methodID : 函数选择器

  2. InputAndOutputResult decodeInputReturnObject(String input)

    解析input,返回Object对象,InputAndOutputResult结构:

    1. public class InputAndOutputResult {
    2. private String function; // 函数签名
    3. private String methodID; // methodID
    4. private List<ResultEntity> result; // 返回列表
    5. }
    6. public class ResultEntity {
    7. private String name; // 字段名称, 解析output返回时,值为空字符串
    8. private String type; // 字段类型
    9. private Object data; // 字段值
    10. }
  3. String decodeOutputReturnJson(String input, String output)

    解析output,将结果封装为json字符串,格式同decodeInputReturnJson

  4. InputAndOutputResult decodeOutputReturnObject(String input, String output)

    解析output,返回java Object对象

  5. String decodeEventReturnJson(List<Log> logList)

    解析event列表,将结果封装为json字符串,json格式

    1. {"event1签名":[[{"name":"","type":"","data":}...]...],"event2签名":[[{"name":"","type":"","data":}...]...]...}
  6. Map<String, List<List<ResultEntity>>> decodeEventReturnObject(List<Log> logList)

    解析event列表,返回java Map对象,key为event签名字符串,List<ResultEntity>为交易中单个event参数列表,List<List<ResultEntity>>表示单个交易可以包含多个event

TransactionDecoder对input,output和event logs均分别提供返回json字符串和java对象的方法。json字符串方便客户端处理数据,java对象方便服务端处理数据。

示例

TxDecodeSample合约为例说明接口的使用:

  1. pragma solidity ^0.4.24;
  2. contract TxDecodeSample
  3. {
  4. event Event1(uint256 _u,int256 _i,bool _b,address _addr,bytes32 _bs32, string _s,bytes _bs);
  5. event Event2(uint256 _u,int256 _i,bool _b,address _addr,bytes32 _bs32, string _s,bytes _bs);
  6. function echo(uint256 _u,int256 _i,bool _b,address _addr,bytes32 _bs32, string _s,bytes _bs) public constant returns (uint256,int256,bool,address,bytes32,string,bytes)
  7. {
  8. Event1(_u, _i, _b, _addr, _bs32, _s, _bs);
  9. return (_u, _i, _b, _addr, _bs32, _s, _bs);
  10. }
  11. function do_event(uint256 _u,int256 _i,bool _b,address _addr,bytes32 _bs32, string _s,bytes _bs) public
  12. {
  13. Event1(_u, _i, _b, _addr, _bs32, _s, _bs);
  14. Event2(_u, _i, _b, _addr, _bs32, _s, _bs);
  15. }
  16. }

使用buildTransactionDecoder 创建TxDecodeSample合约的解析对象:

  1. // TxDecodeSample合约ABI
  2. String abi = "[{\"constant\":false,\"inputs\":[{\"name\":\"_u\",\"type\":\"uint256\"},{\"name\":\"_i\",\"type\":\"int256\"},{\"name\":\"_b\",\"type\":\"bool\"},{\"name\":\"_addr\",\"type\":\"address\"},{\"name\":\"_bs32\",\"type\":\"bytes32\"},{\"name\":\"_s\",\"type\":\"string\"},{\"name\":\"_bs\",\"type\":\"bytes\"}],\"name\":\"do_event\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"name\":\"_u\",\"type\":\"uint256\"},{\"indexed\":false,\"name\":\"_i\",\"type\":\"int256\"},{\"indexed\":false,\"name\":\"_b\",\"type\":\"bool\"},{\"indexed\":false,\"name\":\"_addr\",\"type\":\"address\"},{\"indexed\":false,\"name\":\"_bs32\",\"type\":\"bytes32\"},{\"indexed\":false,\"name\":\"_s\",\"type\":\"string\"},{\"indexed\":false,\"name\":\"_bs\",\"type\":\"bytes\"}],\"name\":\"Event1\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"name\":\"_u\",\"type\":\"uint256\"},{\"indexed\":false,\"name\":\"_i\",\"type\":\"int256\"},{\"indexed\":false,\"name\":\"_b\",\"type\":\"bool\"},{\"indexed\":false,\"name\":\"_addr\",\"type\":\"address\"},{\"indexed\":false,\"name\":\"_bs32\",\"type\":\"bytes32\"},{\"indexed\":false,\"name\":\"_s\",\"type\":\"string\"},{\"indexed\":false,\"name\":\"_bs\",\"type\":\"bytes\"}],\"name\":\"Event2\",\"type\":\"event\"},{\"constant\":true,\"inputs\":[{\"name\":\"_u\",\"type\":\"uint256\"},{\"name\":\"_i\",\"type\":\"int256\"},{\"name\":\"_b\",\"type\":\"bool\"},{\"name\":\"_addr\",\"type\":\"address\"},{\"name\":\"_bs32\",\"type\":\"bytes32\"},{\"name\":\"_s\",\"type\":\"string\"},{\"name\":\"_bs\",\"type\":\"bytes\"}],\"name\":\"echo\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256\"},{\"name\":\"\",\"type\":\"int256\"},{\"name\":\"\",\"type\":\"bool\"},{\"name\":\"\",\"type\":\"address\"},{\"name\":\"\",\"type\":\"bytes32\"},{\"name\":\"\",\"type\":\"string\"},{\"name\":\"\",\"type\":\"bytes\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"}]";
  3. String bin = "";
  4. TransactionDecoder txDecodeSampleDecoder = TransactionDecoderFactory.buildTransactionDecoder(abi, bin);

解析input

调用function echo(uint256 _u,int256 _i,bool _b,address _addr,bytes32 _bs32, string _s,bytes _bs) 接口,输入参数为[ 111111 -1111111 false 0x692a70d2e424a56d2c6c27aa97d1a86395877b3a abcdefghiabcdefghiabcdefghiabhji FISCO-BCOS nice ]

  1. // function echo(uint256 _u,int256 _i,bool _b,address _addr,bytes32 _bs32, string _s,bytes _bs)
  2. String input = "0x406d373b000000000000000000000000000000000000000000000000000000000001b207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffef0bb90000000000000000000000000000000000000000000000000000000000000000000000000000000000000000692a70d2e424a56d2c6c27aa97d1a86395877b3a6162636465666768696162636465666768696162636465666768696162686a6900000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000001400000000000000000000000000000000000000000000000000000000000000021e7aba0e9b1bce5b08fe4b8b8e5ad906c6a6a6b6c3b61646a73666b6c6a6c6b6a6c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000d736164666c6a6b6a6b6c6a6b6c00000000000000000000000000000000000000";
  3. String jsonResult = txDecodeSampleDecoder.decodeInputReturnJson(input);
  4. InputAndOutputResult objectResult = txDecodeSampleDecoder.decodeInputReturnObject(input);
  5. System.out.println("json => \n" + jsonResult);
  6. System.out.println("object => \n" + objectResult);

输出:

  1. json =>
  2. {
  3. "function": "echo(uint256,int256,bool,address,bytes32,string,bytes)",
  4. "methodID": "0x406d373b",
  5. "result": [
  6. {
  7. "name": "_u",
  8. "type": "uint256",
  9. "data": 111111
  10. },
  11. {
  12. "name": "_i",
  13. "type": "int256",
  14. "data": -1111111
  15. },
  16. {
  17. "name": "_b",
  18. "type": "bool",
  19. "data": false
  20. },
  21. {
  22. "name": "_addr",
  23. "type": "address",
  24. "data": "0x692a70d2e424a56d2c6c27aa97d1a86395877b3a"
  25. },
  26. {
  27. "name": "_bs32",
  28. "type": "bytes32",
  29. "data": "abcdefghiabcdefghiabcdefghiabhji"
  30. },
  31. {
  32. "name": "_s",
  33. "type": "string",
  34. "data": "FISCO-BCOS"
  35. },
  36. {
  37. "name": "_bs",
  38. "type": "bytes",
  39. "data": "nice"
  40. }
  41. ]
  42. }
  43. object =>
  44. InputAndOutputResult[
  45. function=echo(uint256,
  46. int256,
  47. bool,
  48. address,
  49. bytes32,
  50. string,
  51. bytes),
  52. methodID=0x406d373b,
  53. result=[
  54. ResultEntity[
  55. name=_u,
  56. type=uint256,
  57. data=111111
  58. ],
  59. ResultEntity[
  60. name=_i,
  61. type=int256,
  62. data=-1111111
  63. ],
  64. ResultEntity[
  65. name=_b,
  66. type=bool,
  67. data=false
  68. ],
  69. ResultEntity[
  70. name=_addr,
  71. type=address,
  72. data=0x692a70d2e424a56d2c6c27aa97d1a86395877b3a
  73. ],
  74. ResultEntity[
  75. name=_bs32,
  76. type=bytes32,
  77. data=abcdefghiabcdefghiabcdefghiabhji
  78. ],
  79. ResultEntity[
  80. name=_s,
  81. type=string,
  82. data=FISCO-BCOS
  83. ],
  84. ResultEntity[
  85. name=_bs,
  86. type=bytes,
  87. data=nice
  88. ]
  89. ]
  90. ]

解析output

调用function echo(uint256 _u,int256 _i,bool _b,address _addr,bytes32 _bs32, string _s,bytes _bs) 接口,输入参数为[ 111111 -1111111 false 0x692a70d2e424a56d2c6c27aa97d1a86395877b3a abcdefghiabcdefghiabcdefghiabhji FISCO-BCOS nice ],echo接口直接将输入返回,因此返回与输入相同

  1. // function echo(uint256 _u,int256 _i,bool _b,address _addr,bytes32 _bs32, string _s,bytes _bs) public constant returns (uint256,int256,bool,address,bytes32,string,bytes)
  2. String input = 0x406d373b000000000000000000000000000000000000000000000000000000000001b207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffef0bb90000000000000000000000000000000000000000000000000000000000000000000000000000000000000000692a70d2e424a56d2c6c27aa97d1a86395877b3a6162636465666768696162636465666768696162636465666768696162686a6900000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000001400000000000000000000000000000000000000000000000000000000000000021e7aba0e9b1bce5b08fe4b8b8e5ad906c6a6a6b6c3b61646a73666b6c6a6c6b6a6c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000d736164666c6a6b6a6b6c6a6b6c00000000000000000000000000000000000000”;
  3. String output = "“0x000000000000000000000000000000000000000000000000000000000001b207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffef0bb90000000000000000000000000000000000000000000000000000000000000000000000000000000000000000692a70d2e424a56d2c6c27aa97d1a86395877b3a6162636465666768696162636465666768696162636465666768696162686a6900000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000001400000000000000000000000000000000000000000000000000000000000000021e7aba0e9b1bce5b08fe4b8b8e5ad906c6a6a6b6c3b61646a73666b6c6a6c6b6a6c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000d736164666c6a6b6a6b6c6a6b6c00000000000000000000000000000000000000";
  4. String jsonResult = txDecodeSampleDecoder.decodeOutputReturnJson(input, output);
  5. InputAndOutputResult objectResult = txDecodeSampleDecoder.decodeOutputReturnObject(input, output);
  6. System.out.println("json => \n" + jsonResult);
  7. System.out.println("object => \n" + objectResult);

结果:

  1. json =>
  2. {
  3. "function": "echo(uint256,int256,bool,address,bytes32,string,bytes)",
  4. "methodID": "0x406d373b",
  5. "result": [
  6. {
  7. "name": "",
  8. "type": "uint256",
  9. "data": 111111
  10. },
  11. {
  12. "name": "",
  13. "type": "int256",
  14. "data": -1111111
  15. },
  16. {
  17. "name": "",
  18. "type": "bool",
  19. "data": false
  20. },
  21. {
  22. "name": "",
  23. "type": "address",
  24. "data": "0x692a70d2e424a56d2c6c27aa97d1a86395877b3a"
  25. },
  26. {
  27. "name": "",
  28. "type": "bytes32",
  29. "data": "abcdefghiabcdefghiabcdefghiabhji"
  30. },
  31. {
  32. "name": "",
  33. "type": "string",
  34. "data": "FISCO-BCOS"
  35. },
  36. {
  37. "name": "",
  38. "type": "bytes",
  39. "data": "nice"
  40. }
  41. ]
  42. }
  43. object =>
  44. InputAndOutputResult[
  45. function=echo(uint256,
  46. int256,
  47. bool,
  48. address,
  49. bytes32,
  50. string,
  51. bytes),
  52. methodID=0x406d373b,
  53. result=[
  54. ResultEntity[
  55. name=,
  56. type=uint256,
  57. data=111111
  58. ],
  59. ResultEntity[
  60. name=,
  61. type=int256,
  62. data=-1111111
  63. ],
  64. ResultEntity[
  65. name=,
  66. type=bool,
  67. data=false
  68. ],
  69. ResultEntity[
  70. name=,
  71. type=address,
  72. data=0x692a70d2e424a56d2c6c27aa97d1a86395877b3a
  73. ],
  74. ResultEntity[
  75. name=,
  76. type=bytes32,
  77. data=abcdefghiabcdefghiabcdefghiabhji
  78. ],
  79. ResultEntity[
  80. name=,
  81. type=string,
  82. data=FISCO-BCOS
  83. ],
  84. ResultEntity[
  85. name=,
  86. type=bytes,
  87. data=nice
  88. ]
  89. ]
  90. ]

解析event logs

调用function do_event(uint256 _u,int256 _i,bool _b,address _addr,bytes32 _bs32, string _s,bytes _bs) 接口,输入参数为[ 111111 -1111111 false 0x692a70d2e424a56d2c6c27aa97d1a86395877b3a abcdefghiabcdefghiabcdefghiabhji FISCO-BCOS nice ],解析交易中的logs

  1. // transactionReceipt为调用do_event接口的交易回执
  2. String jsonResult = txDecodeSampleDecoder.decodeEventReturnJson(transactionReceipt.getLogs());
  3. String mapResult = txDecodeSampleDecoder.decodeEventReturnJson(transactionReceipt.getLogs());
  4. System.out.println("json => \n" + jsonResult);
  5. System.out.println("map => \n" + mapResult);

结果:

  1. json =>
  2. {
  3. "Event1(uint256,int256,bool,address,bytes32,string,bytes)": [
  4. [
  5. {
  6. "name": "_u",
  7. "type": "uint256",
  8. "data": 111111,
  9. "indexed": false
  10. },
  11. {
  12. "name": "_i",
  13. "type": "int256",
  14. "data": -1111111,
  15. "indexed": false
  16. },
  17. {
  18. "name": "_b",
  19. "type": "bool",
  20. "data": false,
  21. "indexed": false
  22. },
  23. {
  24. "name": "_addr",
  25. "type": "address",
  26. "data": "0x692a70d2e424a56d2c6c27aa97d1a86395877b3a",
  27. "indexed": false
  28. },
  29. {
  30. "name": "_bs32",
  31. "type": "bytes32",
  32. "data": "abcdefghiabcdefghiabcdefghiabhji",
  33. "indexed": false
  34. },
  35. {
  36. "name": "_s",
  37. "type": "string",
  38. "data": "Fisco Bcos",
  39. "indexed": false
  40. },
  41. {
  42. "name": "_bs",
  43. "type": "bytes",
  44. "data": "sadfljkjkljkl",
  45. "indexed": false
  46. }
  47. ]
  48. ],
  49. "Event2(uint256,int256,bool,address,bytes32,string,bytes)": [
  50. [
  51. {
  52. "name": "_u",
  53. "type": "uint256",
  54. "data": 111111,
  55. "indexed": false
  56. },
  57. {
  58. "name": "_i",
  59. "type": "int256",
  60. "data": -1111111,
  61. "indexed": false
  62. },
  63. {
  64. "name": "_b",
  65. "type": "bool",
  66. "data": false,
  67. "indexed": false
  68. },
  69. {
  70. "name": "_addr",
  71. "type": "address",
  72. "data": "0x692a70d2e424a56d2c6c27aa97d1a86395877b3a",
  73. "indexed": false
  74. },
  75. {
  76. "name": "_bs32",
  77. "type": "bytes32",
  78. "data": "abcdefghiabcdefghiabcdefghiabhji",
  79. "indexed": false
  80. },
  81. {
  82. "name": "_s",
  83. "type": "string",
  84. "data": "FISCO-BCOS",
  85. "indexed": false
  86. },
  87. {
  88. "name": "_bs",
  89. "type": "bytes",
  90. "data": "nice",
  91. "indexed": false
  92. }
  93. ]
  94. ]
  95. }
  96. map =>
  97. {
  98. Event1(uint256,
  99. int256,
  100. bool,
  101. address,
  102. bytes32,
  103. string,
  104. bytes)=[
  105. [
  106. ResultEntity[
  107. name=_u,
  108. type=uint256,
  109. data=111111
  110. ],
  111. ResultEntity[
  112. name=_i,
  113. type=int256,
  114. data=-1111111
  115. ],
  116. ResultEntity[
  117. name=_b,
  118. type=bool,
  119. data=false
  120. ],
  121. ResultEntity[
  122. name=_addr,
  123. type=address,
  124. data=0x692a70d2e424a56d2c6c27aa97d1a86395877b3a
  125. ],
  126. ResultEntity[
  127. name=_bs32,
  128. type=bytes32,
  129. data=abcdefghiabcdefghiabcdefghiabhji
  130. ],
  131. ResultEntity[
  132. name=_s,
  133. type=string,
  134. data=FISCO-BCOS
  135. ],
  136. ResultEntity[
  137. name=_bs,
  138. type=bytes,
  139. data=nice
  140. ]
  141. ]
  142. ],
  143. Event2(uint256,
  144. int256,
  145. bool,
  146. address,
  147. bytes32,
  148. string,
  149. bytes)=[
  150. [
  151. ResultEntity[
  152. name=_u,
  153. type=uint256,
  154. data=111111
  155. ],
  156. ResultEntity[
  157. name=_i,
  158. type=int256,
  159. data=-1111111
  160. ],
  161. ResultEntity[
  162. name=_b,
  163. type=bool,
  164. data=false
  165. ],
  166. ResultEntity[
  167. name=_addr,
  168. type=address,
  169. data=0x692a70d2e424a56d2c6c27aa97d1a86395877b3a
  170. ],
  171. ResultEntity[
  172. name=_bs32,
  173. type=bytes32,
  174. data=abcdefghiabcdefghiabcdefghiabhji
  175. ],
  176. ResultEntity[
  177. name=_s,
  178. type=string,
  179. data=FISCO-BCOS
  180. ],
  181. ResultEntity[
  182. name=_bs,
  183. type=bytes,
  184. data=nices
  185. ]
  186. ]
  187. ]
  188. }

合约事件推送

功能简介

合约事件推送功能提供了合约事件的异步推送机制,客户端向节点发送注册请求,在请求中携带客户端关注的合约事件的参数,节点根据请求参数对请求区块范围的Event Log进行过滤,将结果分次推送给客户端。

交互协议

客户端与节点的交互基于Channel协议。交互分为三个阶段:注册请求,节点回复,Event Log数据推送。

注册请求

客户端向节点发送Event推送的注册请求:

  1. // request sample:
  2. {
  3. "fromBlock": "latest",
  4. "toBlock": "latest",
  5. "addresses": [
  6. "0xca5ed56862869c25da0bdf186e634aac6c6361ee"
  7. ],
  8. "topics": [
  9. "0x91c95f04198617c60eaf2180fbca88fc192db379657df0e412a9f7dd4ebbe95d"
  10. ],
  11. "groupID": "1",
  12. "filterID": "bb31e4ec086c48e18f21cb994e2e5967"
  13. }
  • filerID:字符串类型,每次请求唯一,标记一次注册任务
  • groupID:字符串类型,群组ID
  • fromBlock:整形字符串,初始区块。“latest” 当前块高
  • toBlock:整形字符串,最终区块。“latest” 处理至当前块高时,继续等待新区块
  • addresses:字符串或者字符串数组:字符串表示单个合约地址,数组为多个合约地址,数组可以为空
  • topics:字符串类型或者数组类型:字符串表示单个topic,数组为多个topic,数组可以为空

节点回复

节点接受客户端注册请求时,会对请求参数进行校验,将是否成功接受该注册请求结果回复给客户端。

  1. // response sample:
  2. {
  3. "filterID": "bb31e4ec086c48e18f21cb994e2e5967",
  4. "result": 0
  5. }
  • filterID:字符串类型,每次请求唯一,标记一次注册任务
  • result:整形,返回结果。0成功,其余为失败状态码

Event Log数据推送

节点验证客户端注册请求成功之后,根据客户端请求参数条件,向客户端推送EventLog数据。

  1. // event log push sample:
  2. {
  3. "filterID": "bb31e4ec086c48e18f21cb994e2e5967",
  4. "result": 0,
  5. "logs": [
  6. ]
  7. }
  • filterID:字符串类型,每次请求唯一,标记一次注册任务
  • result:整形 0:Event Log数据推送 1:推送完成。客户端一次注册请求对应节点的数据推送会有多次(请求区块范围比较大或者等待新的区块),result字段为1时说明节点推送已经结束
  • logs:Log对象数组,result为0时有效

Java SDK教程

注册接口

Java SDK中org.fisco.bcos.channel.client.Service类提供合约事件的注册接口,用户可以调用registerEventLogFilter向节点发送注册请求,并设置回调函数。

  1. public void registerEventLogFilter(EventLogUserParams params, EventLogPushCallback callback);
params注册参数

事件回调请求注册的参数:

  1. public class EventLogUserParams {
  2. private String fromBlock;
  3. private String toBlock;
  4. private List<String> addresses;
  5. private List<Object> topics;
  6. }
callback回调对象
  1. public abstract class EventLogPushCallback {
  2. public void onPushEventLog(int status, List<LogResult> logs);
  3. }
  • status 回调返回状态:
  1. 0 : 正常推送,此时logs为节点推送的Event日志
  2. 1 : 推送完成,执行区间的区块都已经处理
  3. -41000 : 参数无效,客户端验证参数错误返回
  4. -41001 : 参数错误,节点验证参数错误返回
  5. -41002 : 群组不存在
  6. -41003 : 请求错误的区块区间
  7. -41004 : 节点推送数据格式错误
  8. -41005 : 请求发送超时
  9. -41006 : 其他错误
  • logs表示回调的Event Log对象列表,status为0有效
  1. public class LogResult {
  2. private List<EventResultEntity> logParams;
  3. private Log log;
  4. }
  5. // Log对象
  6. public class Log {
  7. private String logIndex;
  8. private String transactionIndex;
  9. private String transactionHash;
  10. private String blockHash;
  11. private String blockNumber;
  12. private String address;
  13. private String data;
  14. private String type;
  15. private List<String> topics;
  16. }

Log log:Log对象

List<EventResultEntity> logParams:默认值null,可以在子类中解析Log的data字段,将结果保存入logParams [参考交易解析]

  • 实现回调对象

Java SDK默认实现的回调类ServiceEventLogPushCallback,将statuslogs在日志中打印,用户可以通过继承ServiceEventLogPushCallback类,重写onPushEventLog接口,实现自己的回调逻辑处理。

  1. class MyEventLogPushCallBack extends ServiceEventLogPushCallback {
  2. @Override
  3. public void onPushEventLog(int status, List<LogResult> logs) {
  4. // ADD CODE
  5. }
  6. }

注意:onPushEventLog接口多次回调的logs有重复的可能性,可以根据Log对象中的blockNumber,transactionIndex,logIndex进行去重

topic工具

org.fisco.bcos.channel.event.filter.TopicTools提供将各种类型参数转换为对应topic的工具,用户设置EventLogUserParamstopics参数可以使用。

  1. class TopicTools {
  2. // int1/uint1~uint1/uint256
  3. public static String integerToTopic(BigInteger i)
  4. // bool
  5. public static String boolToTopic(boolean b)
  6. // address
  7. public static String addressToTopic(String s)
  8. // string
  9. public static String stringToTopic(String s)
  10. // bytes
  11. public static String bytesToTopic(byte[] b)
  12. // byte1~byte32
  13. public static String byteNToTopic(byte[] b)
  14. }

Solidity To Java

为了简化使用,solidity合约生成对应的Java合约代码时,为每个Event生成两个重载的同名接口,接口命名规则: register + Event名称 + EventLogFilter

这里以Asset合约的TransferEvent为例说明

  1. contract Asset {
  2. event TransferEvent(int256 ret, string indexed from_account, string indexed to_account, uint256 indexed amount)
  3. function transfer(string from_account, string to_account, uint256 amount) public returns(int256) {
  4. // 结果
  5. int result = 0;
  6. // 其他逻辑,省略
  7. // TransferEvent 保存结果以及接口参数
  8. TransferEvent(result, from_account, to_account, amount);
  9. }
  10. }

Asset.sol生成对应Java合约文件[将solidity合约生成对应的Java调用文件]

  1. class Asset {
  2. // 其他生成代码 省略
  3. public void registerTransferEventEventLogFilter(EventLogPushWithDecodeCallback callback);
  4. public void registerTransferEventEventLogFilter(String fromBlock, String toBlock, List<String> otherTopics, EventLogPushWithDecodeCallback callback);
  5. }
registerTransferEventEventLogFilter

这两个接口对org.fisco.bcos.channel.client.Service.registerEventLogFilter进行了封装,调用等价于将registerEventLogFilterparams参数设置为:

  1. EventLogUserParams params = new EventLogUserParams();
  2. // fromBlock, 无参数设置为“latest”
  3. params.setFromBlock(fromBlock); // params.setFromBlock("latest");
  4. // toBlock, 无参数设置为“latest”
  5. params.setToBlock(toBlock); // params.setToBlock("latest");
  6. // addresses,设置为Java合约对象的地址
  7. // 当前java合约对象为:Asset asset
  8. ArrayList<String> addresses = new ArrayList<String>();
  9. addresses.add(asset.getContractedAddress());
  10. params.setAddresses(addresses);
  11. // topics, topic0设置为Event接口对应的topic
  12. ArrayList<Object> topics = new ArrayList<>();
  13. topics.add(TopicTools.stringToTopic("TransferEvent(int256,string,string,uint256)"));
  14. // 其他topic设置, 没有则忽略
  15. topics.addAll(otherTopics);

可以看出,在关注指定地址特定合约的某个Event,使用生成的Java合约对象中的接口,更加简单方便。

EventLogPushWithDecodeCallback

EventLogPushWithDecodeCallbackServiceEventLogPushCallback相同,是EventLogPushCallback的子类,区别在于:

  • ServiceEventLogPushCallback回调接口onPushEventLog(int status, List<LogResult> logs) LogResult成员logParams为空,用户需要使用Log数据时需要解析数据
  • EventLogPushWithDecodeCallback作为Asset对象的成员,可以根据其保存的ABI成员构造对应Event的解析工具,解析返回的Log数据,解析结果保存在logParams中。

示例

这里以Asset合约为例,给出合约事件推送的一些场景供用户参考。

  • 场景1:将链上所有/最新的Event回调至客户端
  1. // 其他初始化逻辑,省略
  2. // 参数设置
  3. EventLogUserParams params = new EventLogUserParams();
  4. // 全部Event fromBlock设置为"1"
  5. params.setFromBlock("1");
  6. // 最新Event fromBlock设置为"latest"
  7. // params.setFromBlock("latest");
  8. // toBlock设置为"latest",处理至最新区块继续等待新的区块
  9. params.setToBlock("latest");
  10. // addresses设置为空数组,匹配所有的合约地址
  11. params.setAddresses(new ArrayList<String>());
  12. // topics设置为空数组,匹配所有的Event
  13. params.setTopics(new ArrayList<Object>());
  14. // 回调,用户可以替换为自己实现的类的回调对象
  15. ServiceEventLogPushCallback callback = new ServiceEventLogPushCallback();
  16. service.registerEventLogFilter(params, callback);
  • 场景2: 将Asset合约最新的TransferEvent事件回调至客户端
  1. // 其他初始化逻辑,省略
  2. // 设置参数
  3. EventLogUserParams params = new EventLogUserParams();
  4. // 从最新区块开始,fromBlock设置为"latest"
  5. params.setFromBlock("latest");
  6. // toBlock设置为"latest",处理至最新区块继续等待新的区块
  7. params.setToBlock("latest");
  8. // addresses设置为空数组,匹配所有的合约地址
  9. params.setAddresses(new ArrayList<String>());
  10. // topic0,TransferEvent(int256,string,string,uint256)
  11. ArrayList<Object> topics = new ArrayList<>();
  12. topics.add(TopicTools.stringToTopic("TransferEvent(int256,string,string,uint256)"));
  13. params.setTopics(topics);
  14. // 回调,用户可以替换为自己实现的类的回调对象
  15. ServiceEventLogPushCallback callback = new ServiceEventLogPushCallback();
  16. service.registerEventLogFilter(params, callback);
  • 场景3: 将指定地址的Asset合约最新的TransferEvent事件回调至客户端

合约地址: String addr = "0x06922a844c542df030a2a2be8f835892db99f324";

方案1.

  1. // 其他初始化逻辑,省略
  2. String addr = "0x06922a844c542df030a2a2be8f835892db99f324";
  3. // 设置参数
  4. EventLogUserParams params = new EventLogUserParams();
  5. // 从最新区块开始,fromBlock设置为"latest"
  6. params.setFromBlock("latest");
  7. // toBlock设置为"latest",处理至最新块并继续等待共识出块
  8. params.setToBlock("latest");
  9. // 合约地址
  10. ArrayList<String> addresses = new ArrayList<String>();
  11. addresses.add(addr);
  12. params.setAddresses(addresses);
  13. // topic0,匹配 TransferEvent(int256,string,string,uint256) 事件
  14. ArrayList<Object> topics = new ArrayList<>();
  15. topics.add(TopicTools.stringToTopic("TransferEvent(int256,string,uint256)"));
  16. params.setTopics(topics);
  17. ServiceEventLogPushCallback callback = new ServiceEventLogPushCallback();
  18. service.registerEventLogFilter(params, callback);

方案2.

  1. // 其他初始化逻辑,省略
  2. String addr = "0x06922a844c542df030a2a2be8f835892db99f324";
  3. // 构造Asset合约对象
  4. Asset asset = Asset.load(addr, ... );
  5. EventLogPushWithDecodeCallback callback = new EventLogPushWithDecodeCallback();
  6. asset.registerTransferEventEventLogFilter(callback);
  • 场景4: 将指定地址的Asset合约所有TransferEvent事件回调至客户端

合约地址: String addr = "0x06922a844c542df030a2a2be8f835892db99f324";

方案1:

  1. // 其他初始化逻辑,省略
  2. // 设置参数
  3. EventLogUserParams params = new EventLogUserParams();
  4. // 从最初区块开始,fromBlock设置为"1"
  5. params.setFromBlock("1");
  6. // toBlock设置为"latest",处理至最新块并继续等待共识出块
  7. params.setToBlock("latest");
  8. // 设置合约地址
  9. ArrayList<String> addresses = new ArrayList<String>();
  10. addresses.add(addr);
  11. params.setAddresses(addresses);
  12. // TransferEvent(int256,string,string,uint256) 转换为topic
  13. ArrayList<Object> topics = new ArrayList<>();
  14. topics.add(TopicTools.stringToTopic("TransferEvent(int256,string,string,uint256)"));
  15. params.setTopics(topics);
  16. ServiceEventLogPushCallback callback = new ServiceEventLogPushCallback();
  17. service.registerEventLogFilter(params, callback);

方案2.

  1. // 其他初始化逻辑,省略
  2. Asset asset = Asset.load(addr, ... );
  3. // 设置区块范围
  4. String fromBlock = "1";
  5. String toBlock = "latest";
  6. // 参数topic为空
  7. ArrayList<Object> otherTopics = new ArrayList<>();
  8. EventLogPushWithDecodeCallback callback = new EventLogPushWithDecodeCallback();
  9. asset.registerTransferEventEventLogFilter(fromBlock,toBlock,otherTopics,callback);
  • 场景5: 将Asset指定合约指定账户转账的所有事件回调至客户端

合约地址: String addr = "0x06922a844c542df030a2a2be8f835892db99f324"

转账账户: String fromAccount = "account"

方案1:

  1. // 其他初始化逻辑,省略
  2. String addr = "0x06922a844c542df030a2a2be8f835892db99f324";
  3. String fromAccount = "account";
  4. // 参数
  5. EventLogUserParams params = new EventLogUserParams();
  6. // 从最初区块开始,fromBlock设置为"1"
  7. params.setFromBlock("1");
  8. // toBlock设置为"latest"
  9. params.setToBlock("latest");
  10. // 设置合约地址
  11. ArrayList<String> addresses = new ArrayList<String>();
  12. addresses.add(addr);
  13. params.setAddresses(addresses);
  14. // 设置topic
  15. ArrayList<Object> topics = new ArrayList<>();
  16. // TransferEvent(int256,string,string,uint256) 转换为topic
  17. topics.add(TopicTools.stringToTopic("TransferEvent(int256,string,string,uint256)"));
  18. // 转账账户 fromAccount转换为topic
  19. topics.add(TopicTools.stringToTopic(fromAccount));
  20. params.setTopics(topics);
  21. ServiceEventLogPushCallback callback = new ServiceEventLogPushCallback();
  22. service.registerEventLogFilter(params, callback);

方案2.

  1. // 其他初始化逻辑,省略
  2. String addr = "0x06922a844c542df030a2a2be8f835892db99f324";
  3. String fromAccount = "account";
  4. // 加载合约地址,生成Java合约对象
  5. Asset asset = Asset.load(addr, ... );
  6. // 回调函数
  7. EventLogPushWithDecodeCallback callback = new EventLogPushWithDecodeCallback();
  8. // 设置区块范围
  9. String fromBlock = "1";
  10. String toBlock = "latest";
  11. // 参数topic
  12. ArrayList<Object> otherTopics = new ArrayList<>();
  13. // 转账账户 fromAccount转换为topic
  14. otherTopics.add(TopicTools.stringToTopic(fromAccount));
  15. asset.registerRegisterEventEventLogFilter(fromBlock,toBlock,otherTopics,callback);

附录:JavaSDK启动异常场景

  • Failed to connect to the node. Please check the node status and the console configuration.
    比较旧的SDK版本的提示,建议将JavaSDK版本升级至2.2.2或者以上(修改gradle.build或者maven配置文件中web3sdk的版本号),可以获取更准确友好的提示,然后参考下面的错误提示解决问题。
  • Failed to initialize the SSLContext: class path resource [ca.crt] cannot be opened because it does not exist.
    无法加载到证书文件,证书文件没有正确拷贝至conf目录,可以参考控制台安装流程,拷贝证书文件至conf目录下。
  • Failed to initialize the SSLContext: Input stream not contain valid certificates.
    加载证书文件失败,CentOS系统使用OpenJDK的错误,参考CentOS环境安装JDK章节重新安装OracleJDK。
  • Failed to connect to nodes: [connection timed out: /192.0.0.1:20200]
    连接超时,节点的网络不可达,请检查提示的IP是否配置错误,或者,当前JavaSDK运行环境与节点的环境网络确实不通,可以咨询运维人员解决网络不通的问题。
  • Failed to connect to nodes: [拒绝连接: /127.0.0.1:20200]
    拒绝连接,无法连接对端的端口,可以使用telnet命令检查端口是否连通,可能原因:
    1. 节点未启动,端口处于未监听状态,启动节点即可。
    2. 节点监听127.0.0.1的网段,监听127.0.0.1网络只能本机的客户端才可以连接,控制台位于不同服务器时无法连接节点,将节点配置文件config.ini中的channel_listen_ip修改为控制台连接节点使用的网段IP,或者将其修改为0.0.0.0
    3. 错误的端口配置,配置的端口并不是节点监听的channel端口,修改连接端口为节点config.ini配置的channel_listen_port的值。
      注意:控制台(或者JavaSDK)连接节点时使用Channel端口,并不是RPC端口,Channel端口在节点配置文件中通过channel_listen_ip字段配置,RPC端口通过jsonrpc_listen_port字段配置,注意区分,RPC默认从8545开始分配, Channel端口默认从20200开始分配。
  • Failed to connect to nodes: [ ssl handshake failed:/127.0.0.1:20233] 与节点ssl握手失败,可能原因:
    1. 拷贝了错误的证书,检查拷贝的证书是否正确。
    2. 端口配置错误,连接其他服务正在监听的端口,检查连接端口是否为节点channel_listen_port端口。
    3. JDK版本问题,推荐使用1.8以及以上的OracleJDK,参考CentOS环境安装JDK章节安装OracleJDK。
  • Failed to connect to [127.0.0.1:20233, 127.0.0.1:20234, 127.0.0.1:20235] ,groupId: 1 ,caCert: classpath:ca.crt ,sslKey: classpath:sdk.key ,sslCrt: classpath:sdk.crt ,java version: 1.8.0_231.
    其他未知的错误,需要查看日志文件分析具体错误。