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)
,可以通过获取以下内容来验证响应数据的正确性和完整性:
fromBlock
和toBlock
区块头(根据其已知哈希值进行验证)fromBlock
的parentBlock
区块头(根据fromBlock.parentHash
进行验证)- 基于给定过滤器的
parentBlock
处的历史日志累加器(使用eth_getProof
验证) - 基于给定过滤器的
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.