合约事件推送

功能简介

合约事件推送功能提供了合约事件的异步推送机制,客户端向节点发送注册请求,在请求中携带客户端关注的合约事件的参数,节点根据请求参数对请求区块范围的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);