Event 机制

Event 基础

Log是虚拟机的重要功能之一,用于在虚拟机运行合约过程中,输出特定二进制数据并记录在交易回执(transaction-info / transaction-receipt)中。基于Log功能实现了 Event(事件)机制,可帮助开发人员确认,检查和快速检索智能合约的特定状态。本文介绍 Event 机制基础并介绍如何解码Event Log 。

Solidity Event

Solidity中通过event关键字定义事件。通过emit关键字触发并记录事件。Event不止是特定事件名称,还可以包含若干参数。

contract EventExampleContract {
    event Transfer(address indexed toAddress, uint256 amount);
    constructor() payable public{}
    function contractTransfer(address toAddress, uint256 amount){
        toAddress.transfer(amount);
        emit Transfer(toAddress, amount);
    }
}

如上代码:

  • event Transfer定义了一个Transfer事件,其中的参数:toAddress 表示转账目标地址;amount代表转账的数额
  • emit Transfer(toAddress, amount) 在这段代码上触发同名的Event

代码规范中一般要求 Event 名字首字母大写,区别于对应函数。例如事件Transfer和transfer函数。

Event结构

Solidity使用LOG 指令在交易回执(transaction-info / transaction-receipt)中记录事件信息。关键代码在TransactionInfo的log字段,相关 protobuf 定义如下:

message Log {
    bytes address = 1;
    repeated bytes topics = 2;
    bytes data = 3;
}

message TransactionInfo {
  // A list of LOG represent list of events in a transaction
  repeated Log log = 8;
}

其中 topics 字段表示事件的主题,例如Transfer(...)。同时,所有标记为indexed 的参数也依次在 topics 字段列出。
data 字段表示事件的其他非 indexed 参数,例如:amount。

使用 topics 用来保存 indexed 参数,区块链储存一般会选择使用 LevelDB 或 RockDB 等 key-value 储存引擎。这些引擎一般都支持 prefix-scan 的操作,既可以快速检索 Transfer 事件,又可以快速检索某一特定 toAddress 的 Transfer 事件。

Event 解码示例

上述 Transfer 事件的ABI定义为:

{
  "anonymous": false,
  "inputs": [
    {
      "indexed": true,
      "name": "toAddress",
      "type": "address"
    },
    {
      "indexed": false,
      "name": "amount",
      "type": "uint256"
    }
  ],
  "name": "Transfer",
  "type": "event"
}

使用 gettransactioninfobyid API 获取到如下结果:

log:
  address:
    289C4D540B32C7BC56953E55631F8B141EB86434 # contract address in 20-byte format
  data:
    0000000000000000000000000000000000000000000000000000000000000001
  topics:
    69ca02dd4edd7bf0a4abb9ed3b7af3f14778db5d61921c7dc7cd545266326de2  # topics[0]
    000000000000000000000000E552F6487585C2B58BC2C9BB4492BC1F17132CD0  # topics[1]

与 ABI 一起对照可以得到:

  • topics[0] 69ca02dd4edd7bf0a4abb9ed3b7af3f14778db5d61921c7dc7cd545266326de2 即 keccak256("event(address,uint256)"), 表示事件本身
  • topics[1] 000000000000000000000000E552F6487585C2B58BC2C9BB4492BC1F17132CD0 是第一个 indexed 参数 toAddress。需要注意的是,为了于 EVM 兼容,这里使用了兼容地址格式,即去掉地址的前缀 0x46,成为20字节地址。
  • data 字段是 0000000000000000000000000000000000000000000000000000000000000001 。代表 amount=1 (uint256 类型)。当存在多个非 indexed 参数时,按照 ABI 编码规则依次列出。