Alert Source Discuss
⚠️ Draft Standards Track: Core

EIP-7792: 可验证日志

使 eth_getLogs 响应可验证的方案

Authors Etan Kissling (@etan-status), Gajinder Singh (@g11tech), Vitalik Buterin (@vbuterin)
Created 2024-10-21
Discussion Link https://ethereum-magicians.org/t/eip-7792-verifiable-logs/21424
Requires EIP-6466

摘要

本 EIP 定义了一种使 eth_getLogs JSON-RPC 响应可验证的方法。

动机

eth_getLogs 端点被钱包用来获取与账户或主题相关的交易历史。为了验证日志的正确性和完整性,钱包还必须获取所有区块头并检查其日志布隆。然而,由于其高误报率以及涉及不切实际的网络往返次数,该机制效率低下。本 EIP 定义了一种替代机制,以有效且增量地验证 eth_getLogs 响应的正确性和完整性。

规范

本文档中的关键词“必须”,“禁止”,“需要”,“应”,“不应”,“推荐”,“不推荐”,“可以”和“可选”应按照 RFC 2119 和 RFC 8174 中的描述进行解释。

配置

名称
LOG_CONTRACT_ADDRESS 0xfffffffffffffffffffffffffffffffffffffffe

日志累积

在执行完一个区块的所有交易后,所有发出的日志的承诺会被累积到 LOG_CONTRACT_ADDRESS 的存储中。该合约没有代码,其存储布局由三个 mapping 类型的槽组成。但是为了防止 EIP-158 清理,合约的 nonce 在第一次写入时被设置为 1

名称 类型
LOG_ADDRESS_STORAGE_SLOT 0 mapping(address => bytes32)
LOG_TOPICS_STORAGE_SLOT 1 mapping(bytes32 => bytes32)
LOG_ADDRESS_TOPICS_STORAGE_SLOT 2 mapping(bytes32 => bytes32)

关于每个日志来源的附加元数据被混合到每个 LogEntry 中。该定义使用 EIP-6466 中定义的 Log SSZ 类型。

class BlockMeta(Container):
    timestamp: uint64
    number: uint64

class LogMeta(Container):
    block: BlockMeta
    transaction_index: uint64

class Log(Container):
    address: ExecutionAddress
    topics: List[Bytes32, MAX_TOPICS_PER_LOG]
    data: ByteList[MAX_LOG_DATA_SIZE]

class LogEntry(Container):
    meta: LogMeta
    log: Log

hash_tree_root(LogEntry) 承诺随后作为 LOG_CONTRACT_ADDRESS 的一部分进行跟踪。

def accumulate_log(evm: Evm, entry_root: Bytes32, key: Bytes32):
    root = hashlib.sha256()
    root.update(entry_root)
    root.update(sload(evm.env.state, LOG_CONTRACT_ADDRESS, key))
    sstore(evm.env.state, LOG_CONTRACT_ADDRESS, key, root.digest())

def track_log(evm: Evm, entry: LogEntry) -> None:
    entry_root = entry.hash_tree_root()

    # 允许通过 `address` 过滤器进行验证
    key = keccak256(abi.encode(entry.log.address, LOG_ADDRESS_STORAGE_SLOT))
    accumulate_log(evm, entry_root, key)

    for topic in entry.log.topics:
        # 允许通过 `topics` 过滤器进行验证
        key = keccak256(abi.encode(topic, LOG_TOPICS_STORAGE_SLOT))
        accumulate_log(evm, entry_root, key)

        # 允许通过组合的 `address` + `topics` 过滤器进行验证
        key = keccak256(abi.encode(entry.log.address, topic))
        key = keccak256(abi.encode(key, LOG_ADDRESS_TOPICS_STORAGE_SLOT))
        accumulate_log(evm, entry_root, key)

JSON-RPC API

eth_getLogs 响应格式被扩展为包括:

  • blockTimestamp:QUANTITY - blockHash 引用的区块的时间戳字段

验证

对于 eth_getLogs(address, topics, fromBlock, toBlock),可以通过获取以下内容来验证响应数据的正确性和完整性:

  1. fromBlocktoBlock 区块头(根据其已知哈希值进行验证)
  2. fromBlockparentBlock 区块头(根据 fromBlock.parentHash 进行验证)
  3. 基于给定过滤器的 parentBlock 处的历史日志累加器(使用 eth_getProof 验证)
  4. 基于给定过滤器的 toBlock 处的日志累加器(使用 eth_getProof 验证)

从 (3) 中的历史日志累加器开始,每个响应条目以与上面的 accumulate_log 兼容的方式应用于它。如果日志累加器最终与 (4) 中的值匹配,则响应数据是正确的,并且可以信任从中得出的 LogEntry

理由

使 eth_getLogs 响应可验证增加了必要的安全属性,使钱包能够摆脱对受信任数据提供商的依赖,最终改善钱包的隐私保证,因为它不再受任何给定提供商的隐私政策的约束。

Gas 成本

此方案产生的 gas 成本远高于 Prague 版本的 LOG# 操作码产生的 gas 成本,主要是由于额外的 SLOAD / SSTORE 要求以及 SHA256 操作码相对于 KECCAK256 操作码的双倍成本。gas 成本的增加超过了删除日志布隆的节省。

如果即使在优化后,该机制的成本仍然过高,则可能需要将日志累加器移动到单独的优化数据结构(不在 state_root 中),或者移动到链下 zk 系统。即便如此,日志的 gas 成本仍应反映更新典型链外累加器的实际总成本,以防止日志垃圾邮件。

元数据中使用区块号/交易索引而不是哈希值

只要累加器存储在状态树中,它们就不能引用区块哈希,因为区块哈希会哈希到状态树上,从而产生循环依赖。如果使用外部系统,则可以包含哈希值,因为在这种情况下,状态根不受 IVC 的影响。

向后兼容性

仍然可以按原样处理来自受信任服务器的 eth_getLogs 响应,而无需验证它们。具有严格响应验证的客户端应用程序可能需要更新以允许额外的 blockTimestamp 字段。

安全注意事项

此方案重用了现有的 eth_getProof 和 SSZ Merkle 证明;它没有引入新的安全风险。

版权

根据 CC0 放弃版权和相关权利。

Citation

Please cite this document as:

Etan Kissling (@etan-status), Gajinder Singh (@g11tech), Vitalik Buterin (@vbuterin), "EIP-7792: 可验证日志 [DRAFT]," Ethereum Improvement Proposals, no. 7792, October 2024. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-7792.