事件

日志和过滤器(Logs and Filtering)

在区块链的应用程序中经常用到日志和过滤器,因为它可以对索引数据进行高效查询,并且针对不在链上的数据可以提供低成本的数据存储。

这些可以结合提供者事件API(Provider Event API) 以及合约事件API(Contract Event API) 一起使用。

合约事件API也提供了高级方法去计算和查询数据,这种方式要优先于低级的过滤器。

过滤器

当合约创建一条日志时,它可以包含了4条可索引的数据。这些索引数据经过哈希加密,且包含在一个Bloom Filter中, Bloom Filter是一种允许高效过滤的数据结构。

因此,过滤器最多可对应4个主题集(topic-set),其中每个主题集指向一个条件,该条件必须匹配该位置的索引日志主题(即每个条件都是AND语句连接在一起的)。

如果一个主题集为 null, 则该位置的日志主题 不过滤 且与 任何值 都匹配。

如果一个主题集是单个主题,则该位置的日志主题 必须 匹配 这个主题

如果主题集是一个数组,则该位置的日志主题必须匹配其中 任何一个 主题(即每个条件都是OR语句连接在一起的)。

虽然一开始听起来有点复杂绕口,但通过以下例子可以很好地帮助你理解。

主题集匹配日志 
[ A ]topic[0] = A 
[ A, null ] 
[ null, B ]topic[1] = B 
[ null, [ B ] ] 
[ null, [ B ], null ] 
[ A, B ](topic[0] = A) AND (topic[1] = B) 
[ A, [ B ] ] 
[ A, [ B ], null ] 
[ [ A, B ] ](topic[0] = A) OR (topic[0] = B) 
[ [ A, B ], null ] 
[ [ A, B ], [ C, D ] ][ (topic[0] = A) OR (topic[0] = B) ] AND [ (topic[1] = C) OR (topic[1] = D) ] 
匹配日志的例子 
ERC-20 转账过滤的例子
// 手动创建ERC-20过滤器的简短实例 // 转账事件 // 大多数用户通常使用过合约的API来过滤计算,因为它更简单,但这是为那些 // 好奇的人提供一个例证 // 如下是等价于合约API的例子 // ERC-20: // Transfer(address indexed src, address indexed dst, uint val) // // -------------------^ // ----------------------------------------^ // // 注意到只有 *src* and *dst* 被 *索引*, 因此只有这些符合过滤条件 // // 并且,注意到在Solidity事件中使用第一个主题识别事件名称,对于转账函数如下: // id("Transfer(address,address,uint256)") // // 其他要注意的地方 // - 一个主题必须是 32 字节; 添加的类型要尽可能短 // 列出所有token *来自于* 我的地址的转账 filter = { address: tokenAddress, topics: [ id("Transfer(address,address,uint256)"), hexZeroPad(myAddress, 32) ] }; // 列出所有token *转给* 我的地址的转账 filter = { address: tokenAddress, topics: [ id("Transfer(address,address,uint256)"), null, hexZeroPad(myAddress, 32) ] }; // 列出所有token *转给* 我的地址或者我的其他地址的转账 filter = { address: tokenAddress, topics: [ id("Transfer(address,address,uint256)"), null, [ hexZeroPad(myAddress, 32), hexZeroPad(myOtherAddress, 32), ] ] };

在这里解释一下,为了简化, 合约的API如下:

ERC-20 合约过滤例子
abi = [ "event Transfer(address indexed src, address indexed dst, uint val)" ]; contract = new Contract(tokenAddress, abi, provider); // 列出所有token *来自于* 我的地址的转账 contract.filters.Transfer(myAddress) // { // address: '0x6B175474E89094C44Da98b954EedeAC495271d0F', // topics: [ // '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef', // '0x0000000000000000000000008ba1f109551bd432803012645ac136ddd64dba72' // ] // } // 列出所有token *转给* 我的地址的转账 contract.filters.Transfer(null, myAddress) // { // address: '0x6B175474E89094C44Da98b954EedeAC495271d0F', // topics: [ // '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef', // null, // '0x0000000000000000000000008ba1f109551bd432803012645ac136ddd64dba72' // ] // } // 列出所有token *来自于* 我的或者我其他的地址的转账 contract.filters.Transfer(myAddress, otherAddress) // { // address: '0x6B175474E89094C44Da98b954EedeAC495271d0F', // topics: [ // '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef', // '0x0000000000000000000000008ba1f109551bd432803012645ac136ddd64dba72', // '0x000000000000000000000000ea517d5a070e6705cc5467858681ed953d285eb9' // ] // } // 列出所有token *转给* 我的地址或者我的其他地址的转账 contract.filters.Transfer(null, [ myAddress, otherAddress ]) // { // address: '0x6B175474E89094C44Da98b954EedeAC495271d0F', // topics: [ // '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef', // null, // [ // '0x0000000000000000000000008ba1f109551bd432803012645ac136ddd64dba72', // '0x000000000000000000000000ea517d5a070e6705cc5467858681ed953d285eb9' // ] // ] // }

Solidity 主题

这是一个在Solidity中关于“事件是如何计算”的简要不全面的概述。

这可能超出了大多数开发人员的了解范围,但对于那些希望了解更多底层技术的人来说,可能会很感兴趣。

Solidity 提供了两种类型的事件,匿名和非匿名。默认值是非匿名的,大多数开发人员不需要关注匿名事件。

对于非匿名事件,最多可以索引3个主题(而不是4个),因为保留第一个主题是事件本身的签名。这允许非匿名事件总是通过其事件签名进行过滤。

这个主题哈希值总是在索引数据的第一个位置中,通过规范化事件签名并取其keccak256哈希来计算。

对于匿名事件,最多可以索引4个主题,并且没有签名的主题哈希,因此此事件不能通过事件签名进行过滤。

每个额外索引属性(主题)的处理取决于其长度是固定的还是动态的。

对于固定长度的类型 (例如uint, bytes5),这些全部都恰好是32字节,较短的类型用零填充,数字值类型(numeric values)填充在左边,数据值类型(data values)填充在右边, 这些直接包含在32个字节的数据里面。

对于动态类型 (例如string, uint256[]) , 这些值通过 keccak256 哈希(hashed),并且使用这个哈希值。

因为动态类型是被哈希加密的,所以在解析事件时应该记录一些重要的结果。主要是其事件丢失了初始值,因此,一个主题(topics)匹配给定的字符串(string)是可能的,但如果两者不匹配,就没有办法确定最初的值是什么。

如果开发人员需要一个字符串值既能被过滤也能被读取,那么这个值必须在签名中被包含两次,一次是索引的,一次是非索引的(例如 someEvent(string indexed searchBy, string clearText))。

有关更详细的描述,请参阅 Solidity事件文档

其他内容? 即将补充

将会详细阐述字符串和事件的细节,如何过滤和保留值。