Solidity-事件日志

定义

事件是外部事件获取EVM内部状态变化的一个手段,使用 event 关键字定义事件,使用 emit 来触发定义的事件


// 定义事件

event Deposit(address indexed _from, uint _value);

// 触发事件

emit Deposit(msg.sender, value);

日志包含的内容有:

  • address:表示当前事件来自哪个合约。

  • topics:事件的主题, 定义事件参数时加上 indexed 来标记指定为主题, 每个事件最多可以有三个额外的 indexed 参数,因此最多有四个主题。

  • data: 事件的参数数据(非索引的参数数据)

每个事件的第一个主题默认是事件签名的哈希值, 是事件唯一性的关键,使得不同的事件即使名称相同,只要参数类型列表不同,其签名也会不同,从而确保了区块链上的事件可以被准确地识别和搜索

有索引的参数放在 topics 下,没有索引的参数放在 data 下,以太坊会为日志地址及主题创建 Bloom 过滤器,以便更快的对数据检索

事件操作

在合约内触发事件后,在外部可以通过一些手段获取或监听到该事件:

  • 通过交易收据获取事件: eth_gettransactionreceipt()

  • 使用过滤器获取过去事件: eth_getlogs()

  • 使用过滤器获取实时事件: 建立长链接, 使用 eth_subscribe 订阅

使用场景

  • 链上存储成本很高,一些变量定义如果不是被经常调用或者必须使用,就可以考虑使用事件来存储,能降低很多 Gas 成本

  • 用事件记录完整的交易历史

日志的底层存储

以太坊区块结构中区块头中有一个ReceiptHash 字段,代表交易回执的 Merkel 树结构的根节点哈希值, 日志就存储于交易回执数据中

// core/types/block.go
if len(receipts) == 0 {
    b.header.ReceiptHash = EmptyReceiptsHash
} else {
    b.header.ReceiptHash = DeriveSha(Receipts(receipts), hasher)
    b.header.Bloom = CreateBloom(receipts)
}

交易回执数据结构

// core/types/receipt.go
type Receipt struct {
	....
	Bloom             Bloom  `json:"logsBloom"         gencodec:"required"`
	Logs              []*Log `json:"logs"              gencodec:"required"`
	...
}
  • Logs: 交易事件日志集合
  • Bloom:是从 Logs 中提取的事件布隆过滤器,用于快速检测某主题的事件是否存在于 Logs 中,也就是上面提到的为没有加 indexed 关键字创建索引对应的数据日志创建的Bloom 过滤器,以便更快的对数据检索

Log 数据结构

type Log struct {
	// Consensus fields:
	// address of the contract that generated the event
	Address common.Address `json:"address" gencodec:"required"`
	// list of topics provided by the contract.
	Topics []common.Hash `json:"topics" gencodec:"required"`
	// supplied by the contract, usually ABI-encoded
	Data []byte `json:"data" gencodec:"required"`

	// Derived fields. These fields are filled in by the node
	// but not secured by consensus.
	// block in which the transaction was included
	BlockNumber uint64 `json:"blockNumber" rlp:"-"`
	// hash of the transaction
	TxHash common.Hash `json:"transactionHash" gencodec:"required" rlp:"-"`
	// index of the transaction in the block
	TxIndex uint `json:"transactionIndex" rlp:"-"`
	// hash of the block in which the transaction was included
	BlockHash common.Hash `json:"blockHash" rlp:"-"`
	// index of the log in the block
	Index uint `json:"logIndex" rlp:"-"`

	// The Removed field is true if this log was reverted due to a chain reorganisation.
	// You must pay attention to this field if you receive logs through a filter query.
	Removed bool `json:"removed" rlp:"-"`
}