合约事件推送
功能简介
合约事件推送功能提供了合约事件的异步推送机制,客户端向节点发送注册请求,在请求中携带客户端关注的合约事件的参数,节点根据请求参数对请求区块范围的Event Log
进行过滤,将结果分次推送给客户端。
交互协议
客户端与节点的交互基于Channel
协议。交互分为三个阶段:注册请求,节点回复,Event Log数据推送。
注册请求
客户端向节点发送Event推送的注册请求:
// request sample:
{
"fromBlock": "latest",
"toBlock": "latest",
"addresses": [
"0xca5ed56862869c25da0bdf186e634aac6c6361ee"
],
"topics": [
"0x91c95f04198617c60eaf2180fbca88fc192db379657df0e412a9f7dd4ebbe95d"
],
"groupID": "1",
"filterID": "bb31e4ec086c48e18f21cb994e2e5967"
}
- filerID:字符串类型,每次请求唯一,标记一次注册任务
- groupID:字符串类型,群组ID
- fromBlock:整形字符串,初始区块。“latest” 当前块高
- toBlock:整形字符串,最终区块。“latest” 处理至当前块高时,继续等待新区块
- addresses:字符串或者字符串数组:字符串表示单个合约地址,数组为多个合约地址,数组可以为空
- topics:字符串类型或者数组类型:字符串表示单个topic,数组为多个topic,数组可以为空
节点回复
节点接受客户端注册请求时,会对请求参数进行校验,将是否成功接受该注册请求结果回复给客户端。
// response sample:
{
"filterID": "bb31e4ec086c48e18f21cb994e2e5967",
"result": 0
}
- filterID:字符串类型,每次请求唯一,标记一次注册任务
- result:整形,返回结果。0成功,其余为失败状态码
Event Log数据推送
节点验证客户端注册请求成功之后,根据客户端请求参数条件,向客户端推送Event
的Log
数据。
// event log push sample:
{
"filterID": "bb31e4ec086c48e18f21cb994e2e5967",
"result": 0,
"logs": [
]
}
- filterID:字符串类型,每次请求唯一,标记一次注册任务
- result:整形 0:
Event Log
数据推送 1:推送完成。客户端一次注册请求对应节点的数据推送会有多次(请求区块范围比较大或者等待新的区块),result
字段为1时说明节点推送已经结束 - logs:Log对象数组,result为0时有效
Java SDK教程
注册接口
Java SDK中org.fisco.bcos.channel.client.Service
类提供合约事件的注册接口,用户可以调用registerEventLogFilter
向节点发送注册请求,并设置回调函数。
public void registerEventLogFilter(EventLogUserParams params, EventLogPushCallback callback);
params
注册参数
事件回调请求注册的参数:
public class EventLogUserParams {
private String fromBlock;
private String toBlock;
private List<String> addresses;
private List<Object> topics;
}
callback
回调对象
public abstract class EventLogPushCallback {
public void onPushEventLog(int status, List<LogResult> logs);
}
status
回调返回状态:
0 : 正常推送,此时logs为节点推送的Event日志
1 : 推送完成,执行区间的区块都已经处理
-41000 : 参数无效,客户端验证参数错误返回
-41001 : 参数错误,节点验证参数错误返回
-41002 : 群组不存在
-41003 : 请求错误的区块区间
-41004 : 节点推送数据格式错误
-41005 : 请求发送超时
-41006 : 其他错误
logs
表示回调的Event Log
对象列表,status为0有效
public class LogResult {
private List<EventResultEntity> logParams;
private Log log;
}
// Log对象
public class Log {
private String logIndex;
private String transactionIndex;
private String transactionHash;
private String blockHash;
private String blockNumber;
private String address;
private String data;
private String type;
private List<String> topics;
}
Log log
:Log对象
List<EventResultEntity> logParams
:默认值null
,可以在子类中解析Log的data
字段,将结果保存入logParams
[参考交易解析]
- 实现回调对象
Java SDK默认实现的回调类ServiceEventLogPushCallback
,将status
与logs
在日志中打印,用户可以通过继承ServiceEventLogPushCallback
类,重写onPushEventLog
接口,实现自己的回调逻辑处理。
class MyEventLogPushCallBack extends ServiceEventLogPushCallback {
@Override
public void onPushEventLog(int status, List<LogResult> logs) {
// ADD CODE
}
}
注意:onPushEventLog
接口多次回调的logs
有重复的可能性,可以根据Log
对象中的blockNumber,transactionIndex,logIndex
进行去重
topic工具
org.fisco.bcos.channel.event.filter.TopicTools
提供将各种类型参数转换为对应topic的工具,用户设置EventLogUserParams
的topics
参数可以使用。
class TopicTools {
// int1/uint1~uint1/uint256
public static String integerToTopic(BigInteger i)
// bool
public static String boolToTopic(boolean b)
// address
public static String addressToTopic(String s)
// string
public static String stringToTopic(String s)
// bytes
public static String bytesToTopic(byte[] b)
// byte1~byte32
public static String byteNToTopic(byte[] b)
}
Solidity To Java
为了简化使用,solidity
合约生成对应的Java
合约代码时,为每个Event
生成两个重载的同名接口,接口命名规则: register
+ Event名称 + EventLogFilter
。
这里以Asset
合约的TransferEvent
为例说明
contract Asset {
event TransferEvent(int256 ret, string indexed from_account, string indexed to_account, uint256 indexed amount)
function transfer(string from_account, string to_account, uint256 amount) public returns(int256) {
// 结果
int result = 0;
// 其他逻辑,省略
// TransferEvent 保存结果以及接口参数
TransferEvent(result, from_account, to_account, amount);
}
}
将Asset.sol
生成对应Java
合约文件[将solidity合约生成对应的Java调用文件]
class Asset {
// 其他生成代码 省略
public void registerTransferEventEventLogFilter(EventLogPushWithDecodeCallback callback);
public void registerTransferEventEventLogFilter(String fromBlock, String toBlock, List<String> otherTopics, EventLogPushWithDecodeCallback callback);
}
registerTransferEventEventLogFilter
这两个接口对org.fisco.bcos.channel.client.Service.registerEventLogFilter
进行了封装,调用等价于将registerEventLogFilter
的params
参数设置为:
EventLogUserParams params = new EventLogUserParams();
// fromBlock, 无参数设置为“latest”
params.setFromBlock(fromBlock); // params.setFromBlock("latest");
// toBlock, 无参数设置为“latest”
params.setToBlock(toBlock); // params.setToBlock("latest");
// addresses,设置为Java合约对象的地址
// 当前java合约对象为:Asset asset
ArrayList<String> addresses = new ArrayList<String>();
addresses.add(asset.getContractedAddress());
params.setAddresses(addresses);
// topics, topic0设置为Event接口对应的topic
ArrayList<Object> topics = new ArrayList<>();
topics.add(TopicTools.stringToTopic("TransferEvent(int256,string,string,uint256)"));
// 其他topic设置, 没有则忽略
topics.addAll(otherTopics);
可以看出,在关注指定地址特定合约的某个Event
,使用生成的Java合约对象中的接口,更加简单方便。
EventLogPushWithDecodeCallback
EventLogPushWithDecodeCallback
与ServiceEventLogPushCallback
相同,是EventLogPushCallback
的子类,区别在于:
ServiceEventLogPushCallback
回调接口onPushEventLog(int status, List<LogResult> logs)
LogResult
成员logParams
为空,用户需要使用Log
数据时需要解析数据EventLogPushWithDecodeCallback
作为Asset
对象的成员,可以根据其保存的ABI
成员构造对应Event
的解析工具,解析返回的Log
数据,解析结果保存在logParams
中。
示例
这里以Asset
合约为例,给出合约事件推送的一些场景供用户参考。
- 场景1:将链上所有/最新的Event回调至客户端
// 其他初始化逻辑,省略
// 参数设置
EventLogUserParams params = new EventLogUserParams();
// 全部Event fromBlock设置为"1"
params.setFromBlock("1");
// 最新Event fromBlock设置为"latest"
// params.setFromBlock("latest");
// toBlock设置为"latest",处理至最新区块继续等待新的区块
params.setToBlock("latest");
// addresses设置为空数组,匹配所有的合约地址
params.setAddresses(new ArrayList<String>());
// topics设置为空数组,匹配所有的Event
params.setTopics(new ArrayList<Object>());
// 回调,用户可以替换为自己实现的类的回调对象
ServiceEventLogPushCallback callback = new ServiceEventLogPushCallback();
service.registerEventLogFilter(params, callback);
- 场景2: 将
Asset
合约最新的TransferEvent
事件回调至客户端
// 其他初始化逻辑,省略
// 设置参数
EventLogUserParams params = new EventLogUserParams();
// 从最新区块开始,fromBlock设置为"latest"
params.setFromBlock("latest");
// toBlock设置为"latest",处理至最新区块继续等待新的区块
params.setToBlock("latest");
// addresses设置为空数组,匹配所有的合约地址
params.setAddresses(new ArrayList<String>());
// topic0,TransferEvent(int256,string,string,uint256)
ArrayList<Object> topics = new ArrayList<>();
topics.add(TopicTools.stringToTopic("TransferEvent(int256,string,string,uint256)"));
params.setTopics(topics);
// 回调,用户可以替换为自己实现的类的回调对象
ServiceEventLogPushCallback callback = new ServiceEventLogPushCallback();
service.registerEventLogFilter(params, callback);
- 场景3: 将指定地址的
Asset
合约最新的TransferEvent
事件回调至客户端
合约地址: String addr = "0x06922a844c542df030a2a2be8f835892db99f324";
方案1.
// 其他初始化逻辑,省略
String addr = "0x06922a844c542df030a2a2be8f835892db99f324";
// 设置参数
EventLogUserParams params = new EventLogUserParams();
// 从最新区块开始,fromBlock设置为"latest"
params.setFromBlock("latest");
// toBlock设置为"latest",处理至最新块并继续等待共识出块
params.setToBlock("latest");
// 合约地址
ArrayList<String> addresses = new ArrayList<String>();
addresses.add(addr);
params.setAddresses(addresses);
// topic0,匹配 TransferEvent(int256,string,string,uint256) 事件
ArrayList<Object> topics = new ArrayList<>();
topics.add(TopicTools.stringToTopic("TransferEvent(int256,string,uint256)"));
params.setTopics(topics);
ServiceEventLogPushCallback callback = new ServiceEventLogPushCallback();
service.registerEventLogFilter(params, callback);
方案2.
// 其他初始化逻辑,省略
String addr = "0x06922a844c542df030a2a2be8f835892db99f324";
// 构造Asset合约对象
Asset asset = Asset.load(addr, ... );
EventLogPushWithDecodeCallback callback = new EventLogPushWithDecodeCallback();
asset.registerTransferEventEventLogFilter(callback);
- 场景4: 将指定地址的
Asset
合约所有TransferEvent
事件回调至客户端
合约地址: String addr = "0x06922a844c542df030a2a2be8f835892db99f324";
方案1:
// 其他初始化逻辑,省略
// 设置参数
EventLogUserParams params = new EventLogUserParams();
// 从最初区块开始,fromBlock设置为"1"
params.setFromBlock("1");
// toBlock设置为"latest",处理至最新块并继续等待共识出块
params.setToBlock("latest");
// 设置合约地址
ArrayList<String> addresses = new ArrayList<String>();
addresses.add(addr);
params.setAddresses(addresses);
// TransferEvent(int256,string,string,uint256) 转换为topic
ArrayList<Object> topics = new ArrayList<>();
topics.add(TopicTools.stringToTopic("TransferEvent(int256,string,string,uint256)"));
params.setTopics(topics);
ServiceEventLogPushCallback callback = new ServiceEventLogPushCallback();
service.registerEventLogFilter(params, callback);
方案2.
// 其他初始化逻辑,省略
Asset asset = Asset.load(addr, ... );
// 设置区块范围
String fromBlock = "1";
String toBlock = "latest";
// 参数topic为空
ArrayList<Object> otherTopics = new ArrayList<>();
EventLogPushWithDecodeCallback callback = new EventLogPushWithDecodeCallback();
asset.registerTransferEventEventLogFilter(fromBlock,toBlock,otherTopics,callback);
- 场景5: 将
Asset
指定合约指定账户转账的所有事件回调至客户端
合约地址: String addr = "0x06922a844c542df030a2a2be8f835892db99f324"
转账账户: String fromAccount = "account"
方案1:
// 其他初始化逻辑,省略
String addr = "0x06922a844c542df030a2a2be8f835892db99f324";
String fromAccount = "account";
// 参数
EventLogUserParams params = new EventLogUserParams();
// 从最初区块开始,fromBlock设置为"1"
params.setFromBlock("1");
// toBlock设置为"latest"
params.setToBlock("latest");
// 设置合约地址
ArrayList<String> addresses = new ArrayList<String>();
addresses.add(addr);
params.setAddresses(addresses);
// 设置topic
ArrayList<Object> topics = new ArrayList<>();
// TransferEvent(int256,string,string,uint256) 转换为topic
topics.add(TopicTools.stringToTopic("TransferEvent(int256,string,string,uint256)"));
// 转账账户 fromAccount转换为topic
topics.add(TopicTools.stringToTopic(fromAccount));
params.setTopics(topics);
ServiceEventLogPushCallback callback = new ServiceEventLogPushCallback();
service.registerEventLogFilter(params, callback);
方案2.
// 其他初始化逻辑,省略
String addr = "0x06922a844c542df030a2a2be8f835892db99f324";
String fromAccount = "account";
// 加载合约地址,生成Java合约对象
Asset asset = Asset.load(addr, ... );
// 回调函数
EventLogPushWithDecodeCallback callback = new EventLogPushWithDecodeCallback();
// 设置区块范围
String fromBlock = "1";
String toBlock = "latest";
// 参数topic
ArrayList<Object> otherTopics = new ArrayList<>();
// 转账账户 fromAccount转换为topic
otherTopics.add(TopicTools.stringToTopic(fromAccount));
asset.registerRegisterEventEventLogFilter(fromBlock,toBlock,otherTopics,callback);