以太坊交易信息中的logs和topics概念

发布时间 2023-11-23 14:29:08作者: 若-飞

1. 看一个logs和topics的交易记录:

Topics中的 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef是啥意思呢?

 

1.1.合约交易关键字段解释

{
  "jsonrpc": "2.0",   //RPC版本号,2.0
  "id": 1,      //RPC请求编号
  "result": {  //调用结果,为交易收据,主要包含如下字段:
    "blockHash": "0xb0d0e3b6c5e59b7b3e7e16701f6d6cb0c3c93487415b03839e88b3f7a241c528",  // 区块哈希
    "blockNumber": "0xd19505",   // 区块高度
    "contractAddress": null,    //合约地址
    "cumulativeGasUsed": "0x6c847e",  //当前交易执行后累计花费的gas总值
    "effectiveGasPrice": "0x274daee580",  //当前交易预计使用的gas总值
    "from": "0xb8262c6a2dcabd92a77df1d5bd074afd07fc5829",   //当前交易发送者的地址
    "gasUsed": "0xa169",    //执行当前这个交易单独花费的gas
    "logs": [   //这个交易产生的日志对象数组
      {
        "address": "0xdac17f958d2ee523a2206206994597c13d831ec7",   //当前交易被调用的合约地址
        "topics": [
          "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",  //keccak(Transfer(address,address,uint256)), //合约事件签名哈希值,对事件的字符做keccak散列运算
          "0x000000000000000000000000b8262c6a2dcabd92a77df1d5bd074afd07fc5829",  //当前交易from的地址
          "0x000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec7"   //当前交易to的地址
        ], 
        "data": "0x000000000000000000000000000000000000000000000000000000016512c902",     //包含日志的非索引参数
        "blockNumber": "0xd19505",    // 区块高度
        "transactionHash": "0xae2a33da8396a6bc40e874b0f32b9967113a3dbf071ab1290c44c62d86873d36",   //交易的哈希值,32字节
        "transactionIndex": "0x71"   //交易在区块里面的序号,当交易为pending时为空
        "blockHash": "0xb0d0e3b6c5e59b7b3e7e16701f6d6cb0c3c93487415b03839e88b3f7a241c528",
        "logIndex": "0xa0",    //块中日志索引位置的整数,当交易为pending时日志为空。
        "removed": false   //当由于链重组而删除日志时,为True。 如果它是一个有效的日志则为False。  
      }
    ],
    //bloom过滤器,当交易为pending时日志为空
    "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000010000000000000000000000000000000000000000000000000000000008000000000080000000000000000000000000000000000000000000000000000000000000000000200000000000000010000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000080000000000000000000000000000000000000004000000002000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000200",
    "status": "0x1",   //交易事务状态,1(成功)或0(失败)
    "to": "0xdac17f958d2ee523a2206206994597c13d831ec7",   //当前交易被调用的合约地址
    "transactionHash": "0xae2a33da8396a6bc40e874b0f32b9967113a3dbf071ab1290c44c62d86873d36",  //交易的哈希值,32字节
    "transactionIndex": "0x71",  //交易在区块里面的序号,当交易为pending时为空
    "type": "0x2"  
  }
}

2.以太坊上的日志:

EVM当前有5个操作码用于触发事件日志:LOG0,LOG1 LOG2 LOG3 和 LOG4。

这些操作码可用于创建“日志记录”。日志记录就是用于描述智能合约中的事件,例如代币转移、所有权变更等。

以太坊黄皮书 -拜占庭版本69351d5(2018-12-10)

每个日志记录都包含“主题(topics)”和“数据”。主题是32字节(256位)的“词”,用于描述事件中发生的事情。不同的操作码(LOG0…LOG4)来描述需要包含在日志记录中的主题数。例如,“ LOG1”包括“一个主题”,而“ LOG4”包括“四个主题”。因此,单个日志记录中可以包含的最大主题数是四个

3. 以太坊日志记录中的主题

日志记录的第一部分由一组主题组成。这些主题用于描述事件。第一个主题通常为事件名称及其参数类型*(uint256,string等)*签名(keccak256哈希)。一个例外是触发“匿名事件”没有事件签名。由于主题只能容纳32个字节的数据,因此无法将数组或字符串等(可能超过 32 个字节)的内容用作主题。而是应将其作为数据包括在日志记录中,而不是作为主题。如果要尝试包含大于32个字节的主题,则该主题需要被hash计算。因此,仅当你知道原始输入时,才可以知道此哈希表示的内容(译者注: 因为hash计算不可逆)。

总之,主题应该仅用于需要(压缩)搜索查询(例如:地址)的数据。可以将主题视为事件的索引键,它们都映射到相同的值,接下来将讨论。

3.1 概念

Topics[]是一个数组

  • Topics[0] :指向特定的事件,是事件的签名,如ERC-20 Transfer方法:
sha3('Transfer(address,address,uint256)') => 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef
  • Topics[1] :是address类型的from参数补齐64位结果(合约交易中真正的from地址)
  • Topics[2] :是address类型to参数补齐64位的结果(合约交易中真正的to地址)

由于value没有indexed标记,因此放在data中,将value的值转化为16进制并补齐64位

3.2 indexed

Transfer事件的第一个和第二个参数被标记为indexed, 因此他们的值被放在 topics array 中. 由于value参数没有标记为indexed, 所以value值被放在data字段. 如果事件中有多个字段未标记为indexed, 那么他们的值都会被记录在data字段中。

事件规则

  • topic[0] : keccak("Transfer(address,address,uint256)"),对事件的字符做keccak散列运算
  • topic[1] : address类型from参数补齐64位
  • topic[2] : address类型to参数补齐64位

data: 没有indexed标记的value的值转化为16进制,并补齐64位

注意:

事件中的参数类型需要写成完整的,如Transfer(address,address,uint)就不行,需要将uint改为uint256。

4. 以太坊日志记录中的数据

日志记录的第二部分包含额外的数据。主题和数据在一起组成日志记录,主题和数据每自有其优点和缺点。例如,主题是可搜索的,但数据却不能。而数据比主题“便宜得多”。此外,尽管主题最多有 4 个(限制在4 * 32字节),但数据却没有限制,这意味着它可以包括大量或复杂数据,例如数组或字符串。因此,事件数据(如果有)可以视为

让我们看一些示例,看看主题,数据和日志记录是如何使用的。

 

5. 触发事件

以下实现了ERC20的代币合约,使用了Transfer事件:

由于这不是匿名事件,因此第一个主题将包括事件签名

我们刚才看到的那串数据就是这个签名。

现在,让我们看一下此Solidity事件的参数(from to value):

由于前两个参数声明为indexed,因此被视为主题。最后一个参数没有 indexed ,它将作为数据(而不是单独的主题)。这意味着我们可以进行这样的搜索:查找所有从地址0x0000...(搜索条件)到地址0x0000…(搜索条件)的转账日志,或者是“所有转账到地址0x0000…(搜索条件)的日志”,但没法搜索“转账金额为x(搜索条件)的转账。我们知道了此事件将具有3个主题,这意味着此日志记录操作将使用LOG3操作码。

现在,我们只需要了解如何包含数据(即最后的参数)即可。LOG3需要5个参数:

LOG3(memoryStart, memoryLength, topic1, topic2, topic3)

通过以下方式从内存中读取事件数据:

memory[memoryStart...(memoryStart + memoryLength)]

幸运的是,像SolidityVyperBamboo这样的高级智能合约程序设计语言将为我们处理将事件数据写入内存的过程,我们可以在触发日志时直接将数据作为参数传递。

 

6. 总结

topics就是一组函数的签名:

这个网站可以找到函数对应的签名:https://www.4byte.directory/event-signatures/?bytes_signature=&page=1