kona-executor:OP Stack 无状态区块执行器源码深度解析
【免费下载链接】optimismOptimism is Ethereum, scaled.项目地址: https://gitcode.com/GitHub_Trending/op/optimism
本篇技术指南以 Optimism 仓库中 rust/kona/crates/proof/executor/README.md 为纲,深入剖析kona-executor这一no_std无状态区块执行器:它以 kona-mpt 的TrieDB为状态后端,在完全不维护完整 L2 状态的前提下执行 OP Stack 区块并产出可验证的区块头与输出根(output root),是 Kona 故障证明(fault proof)体系中"链下可复现执行"的关键一环。读完本文,你将掌握其架构分层、StatelessL2Builder的建块流程、基于 Merkle 证明的TrieDB设计、EIP-1559 与硬分叉参数处理、区块封印逻辑以及它在kona-driver中的实际接线方式。
一、模块定位:一句话背后的大设计
原文档用一句话定义了整个 crate 的本质:
A
no_stdimplementation of a stateless block executor for the OP stack, backed bykona-mpt'sTrieDB.
这句话包含三个关键限定词,分别对应三类核心技术决策:
no_std:在 src/lib.rs 中通过#![cfg_attr(not(any(test, feature = "test-utils")), no_std)]强制开启,仅在测试或启用test-utilsfeature 时才引入标准库。这意味着该执行器可以被编译进无法依赖操作系统资源的故障证明虚拟机(如 Cannon/MIPS 或 zkVM)中运行。- stateless(无状态):传统执行引擎(如 op-geth)维护完整的状态数据库;
kona-executor只持有"受信任的父区块 state root + 按需获取的 Merkle 证明 + witness 预映像",边执行边重建所需状态。 - backed by
kona-mpt'sTrieDB:状态访问全部落到 kona-mpt 提供的递归内存版十六进制 MPT(支持检索、插入、删除与根哈希计算),执行器本身不关心 trie 节点如何存储,只通过TrieProvider按哈希取节点预映像。
该 crate 的依赖也在 Cargo.toml 中给出了佐证:工作区依赖kona-mpt、kona-genesis(提供RollupConfig)、kona-protocol,配合revm/op-revm(EVM 执行)、alloy-op-evm(OP 交易类型与收据)、op-alloy-rpc-types-engine(OpPayloadAttributes)。crate 版本为0.4.0。
二、核心类型:StatelessL2Builder与四个泛型
模块入口 src/lib.rs 对外只导出少量高内聚类型:
StatelessL2Builder、BlockBuildingOutcome、compute_receipts_root(来自builder模块);TrieDB、TrieDBProvider、NoopTrieDBProvider(来自db模块);- 完整错误体系
ExecutorError、ExecutorResult、TrieDBError、TrieDBResult、Eip1559ValidationError。
2.1 结构体定义
StatelessL2Builder定义在 src/builder/core.rs,携带四个泛型参数,每个都对应一条可插拔的扩展点:
| 泛型 | Trait 约束 | 职责 |
|---|---|---|
P | TrieDBProvider | 按哈希获取 trie 节点、合约字节码、区块头预映像 |
H | TrieHinter | 向 host 发送 hint,预取执行所需的 witness 数据 |
Evm | EvmFactory<Spec = OpSpecId, BlockEnv = BlockEnv> | 创建 EVM 执行环境的工厂 |
R | OpReceiptBuilder<Transaction = OpTxEnvelope> | 决定收据信封形态(OP Stack 默认OpAlloyReceiptBuilder;如 Celo 的 CIP-64 收据可自行注入) |
结构体内部持有三个字段:
config: &'a RollupConfig—— 链参数与各硬分叉激活高度;trie_db: TrieDB<P, H>—— 无状态状态访问的唯一入口;factory: OpBlockExecutorFactory<R, RollupConfig, Evm>—— OP 专属的区块执行器工厂,理解 OP 交易类型、系统调用与状态管理。
2.2 构造与生命周期
let builder = StatelessL2Builder::new( &rollup_config, // 链参数与分叉激活高度 evm_factory, // EVM 工厂(如 OpEvmFactory::<OpTx>::default()) receipt_builder, // 收据构建器(如 OpAlloyReceiptBuilder::default()) trie_provider, // trie 数据提供者(P) trie_hinter, // witness 提示器(H) parent_header, // 父区块的 Sealed<Header> );new会基于父区块头的state_root构造一个"盲化"(blinded)根节点,见 src/builder/core.rs。由于执行器无状态,"安全头(safe head)推进"就意味着用新父头重建一个新 builder——这一生命周期设计在第五节与 kona-driver 的集成中体现得最明显。
三、TrieDB:把 Merkle 证明当作数据库
TrieDB位于 src/db/mod.rs,是 crate 的地基。它实现 revm 的Databasetrait,让 EVM 在无状态环境下执行时,每次状态访问都落到"按需、可验证"的 trie 查询上。
3.1 三个内部结构
root_node: TrieNode—— 当前 state root 对应的根节点,创建时通过TrieNode::new_blinded(parent_block_header.state_root)盲化;storage_roots: HashMap<Address, TrieNode>—— 各账户存储 trie 的内存缓存;parent_block_header: Sealed<Header>+fetcher: F(TrieDBProvider)+hinter: H(TrieHinter)。
3.2 关键行为(对应Database实现)
basic(address):先通过get_trie_account查询账户。查询前会调用hinter.hint_account_proof(address, parent_hash)向 host 发出账户证明 hint(src/db/mod.rs);随后用keccak256(address)的 nibbles 在根节点上open路径,取回 RLP 编码的TrieAccount并解码。账户的 storage root 会被插入storage_roots缓存。code_by_hash(code_hash):委托fetcher.bytecode_by_hash取合约字节码。storage(address, index):先 hint 存储证明,再从账户缓存中的 storage trie 打开keccak256(slot)路径读取值;槽位不存在返回U256::ZERO。block_hash(number):利用BLOCK_HASH_HISTORY限制(256 个区块),从父头沿parent_hash链式回溯,通过fetcher.header_by_hash取历史头,直至目标区块号(src/db/mod.rs)。state_root(&bundle):区块执行完成后,把 revm 的BundleState变更集应用到 trie,再对根节点blind()重新计算 state root(src/db/mod.rs)。
3.3 确定性保证与存储修剪
update_accounts在应用变更集前会先对哈希后的地址与存储槽位排序,保证多次运行结果完全一致(这是故障证明可复现性的硬性要求)。对存储槽位,若present_value归零则在 trie 中删除该节点(change_storage),否则插入新值(src/db/mod.rs)。db/mod.rs末尾的单元测试覆盖了账户销毁(Destroyed/DestroyedChanged/DestroyedAgain)在 state root 中的去留语义,例如"被销毁账户不应出现在 trie 中"与"销毁后重建的账户必须重新插入"。
3.4 构造示例(源码 doctest 摘录)
TrieDB的文档示例(src/db/mod.rs)展示了最小接线方式:
use kona_executor::{NoopTrieDBProvider, TrieDB}; use kona_mpt::NoopTrieHinter; let mock_parent_block_header = Header::default(); let trie_db = TrieDB::new(mock_parent_block_header.seal_slow(), NoopTrieDBProvider, NoopTrieHinter); let executor_factory = OpBlockExecutorFactory::new( OpAlloyReceiptBuilder::default(), OpChainHardforks::op_mainnet(), OpEvmFactory::<alloy_op_evm::OpTx>::default(), ); let mut state = State::builder().with_database(trie_db).with_bundle_update().build(); let evm = executor_factory.evm_factory().create_evm(&mut state, EvmEnv::default()); let executor = executor_factory.create_executor(evm, OpBlockExecutionCtx::default()); // 执行区块交易... state.merge_transitions(BundleRetention::Reverts); let bundle = state.take_bundle(); let state_root = state.database.state_root(&bundle).expect("Failed to compute state root");3.5 Provider 与 Hinter 抽象
TrieDBProvider(src/db/traits.rs)在kona-mpt的TrieProvider::trie_node_by_hash之上追加bytecode_by_hash与header_by_hash两个方法;NoopTrieDBProvider是测试用的空实现。TrieHinter(kona-mpt 的 traits)定义了hint_trie_node、hint_account_proof、hint_storage_proof、hint_execution_witness四个 hint 接口——在故障证明场景下,host 侧预取数据、客户端(guest)侧通过 hint 驱动,是 Cannon 预映像预言机模式的核心。
四、build_block:无状态建块的完整流水线
StatelessL2Builder::build_block(attrs: OpPayloadAttributes) -> ExecutorResult<BlockBuildingOutcome<R::Receipt>>是建块主入口(src/builder/core.rs),源码注释将流程拆为四步:
- 环境准备:调用
active_base_fee_params依据父头时间戳选择当前生效的 EIP-1559 参数,再由evm_env组装EvmEnv<OpSpecId>(gas limit、base fee、suggested fee recipient、prev_randao、时间戳等)。 - Witness 预取(hint):调用
hinter.hint_execution_witness(parent_hash, &attrs)让 host 把执行该 payload 所需的全部预映像灌入 preimage store。该功能是实验性的——hint 失败不会中止建块,而是回退到"按需获取预映像"。 - 执行:用
State::builder().with_database(&mut trie_db).with_bundle_update()构建 revm 状态层;OpBlockExecutorFactory创建执行器;对 payload 内交易做签名恢复(recovered_transactions_with_encoded);调用parse_post_exec_payload_from_transactions解析 SDM(Sequencer Deposit Message)post-exec 交易并按PostExecMode::Verify校验;随后executor.execute_block(transactions.iter())完成区块执行。 - 封印(seal):
state.merge_transitions(BundleRetention::Reverts)合并状态变更,take_bundle取出BundleState,调用seal_block计算各类 root 并组装新区块头;最后trie_db.set_parent_block_header(header.clone())推进父头,为下一个区块做准备。
整个流程中大量使用info!日志(target: "block_builder")输出区块号、时间戳、gas、交易数与最终 state root 等可观测指标,便于故障排查与复现对照。
五、EVM 环境与 EIP-1559 参数推导
src/builder/env.rs 集中了环境相关的分叉逻辑,是理解 OP 硬分叉(Canyon/Holocene/Jovian)如何影响建块的窗口。
5.1active_base_fee_params的选择逻辑
按父头时间戳从新到旧匹配:
- Jovian 已激活:从父头
extra_data解码 Jovian 版 EIP-1559 参数,同时返回min_base_fee(Jovian 起 base fee 存在下限); - Holocene 已激活(Jovian 未激活):从父头
extra_data解码 Holocene 版参数,min_base_fee恒为 0; - Canyon 已激活:使用
config.chain_op_config.post_canyon_params(); - 其余:使用
pre_canyon_params()。
5.2 下一个区块 base fee 的计算
next_block_base_fee在 Jovian 激活后引入特殊处理:取max(blob_gas_used, gas_used)作为分母输入计算下一个 base fee,且结果不得低于min_base_fee(Jovian 之前 min-base-fee 为 0,该钳制是空操作)。
5.3 Holocene/Jovian extraData 编解码
src/util.rs 中的decode_holocene_eip_1559_params_block_header、decode_jovian_eip_1559_params_block_header以及对应的 encode 函数,负责与op-alloy-consensus的decode_holocene_extra_data/decode_jovian_extra_data对接。其单元测试验证了:
- Holocene 版本号字节为
0x00,Jovian 为0x01; - 分母(denominator)或弹性(elasticity)为零时解码必须失败(避免除零);
- 长度非法、版本号错误的 extraData 必须报错;
- payload 中
eip_1559_params缺失时报MissingEIP1559Params;为全零时回退编码 Canyon 默认参数。
六、区块封印:seal_block与 output root
src/builder/assemble.rs 负责把执行结果固化为区块头,并计算 L2 输出根。
6.1 区块头字段计算
seal_block依据硬分叉激活状态逐项计算(src/builder/assemble.rs):
state_root:trie_db.state_root(&bundle)基于变更集重算;transactions_root:ordered_trie_with_encoder按交易原始字节构造(对空交易列表直接 panic——源码注释明确指出这是"严重的协议违规");receipts_root:compute_receipts_root按收据构建器生成;withdrawals_root:Isthmus 激活时取 L2 到 L1 消息传递者(L2_TO_L1_MESSAGE_PASSER)账户的 storage root;Canyon 激活时取EMPTY_ROOT_HASH;否则为None;- blob 字段:Jovian 激活写入真实
blob_gas_used且excess_blob_gas = 0;Ecotone 激活但 Jovian 未激活时两者都置 0; extra_data:Holocene/Jovian 激活时编码 EIP-1559 参数写入;requests_hash:Isthmus 激活时为EMPTY_REQUESTS_HASH(EIP-7685 空请求哈希)。
6.2compute_receipts_root的 Regolith 特殊编码
compute_receipts_root复刻了 op-geth/op-erigon 在Regolith 激活后、Canyon 激活前的收据 trie 编码:该阶段 deposit 收据的编码省略 deposit nonce。实现通过OpReceiptBuilder::strip_deposit_nonce委托给各链的收据构建器——OP Stack 实现覆写该行为,其他链(如 Celo)继承为空操作(src/builder/assemble.rs)。
6.3compute_output_root与 L2 输出根
output_root = keccak256(version_byte .. payload) payload = state_root .. withdrawal_storage_root .. latest_block_hashcompute_output_root通过OutputRoot::from_parts(parent_header.state_root, storage_root, parent_header.seal())构造并哈希,其中storage_root来自消息传递者账户(优先读TrieDB缓存,否则回源 trie 查询)。这正是 L1 上 L2OutputOracle / 输出根提案所引用的承诺值。
七、错误处理模型
src/errors.rs 提供了三级错误体系,覆盖整个执行生命周期:
ExecutorError:建块/执行错误,按类别分:- 输入校验:
MissingGasLimit、MissingTransactions、MissingEIP1559Params(Holocene 后)、MissingParentBeaconBlockRoot(Dencun 后); - 执行:
BlockGasLimitExceeded、UnsupportedTransactionType、ExecutionError(包装 EVM 级BlockExecutionError); - 数据完整性:
Recovery(签名恢复失败)、RLPError、TrieDBError、InvalidPostExecPayload(SDM post-exec 校验失败)、InvalidExtraData(EIP-1559 参数解码失败)。
- 输入校验:
TrieDBError:trie 操作错误,包括RootNotBlinded、MissingAccountInfo、TrieNode(包装TrieNodeError)、Provider;并实现了 revm 的DBErrorMarker,可直接作为数据库错误传播。Eip1559ValidationError:仅包装EIP1559ParamError的透明错误。
八、测试策略:真实区块 Fixture 驱动的回归测试
kona-executor的测试思路极具工程参考价值:用真实 OP 主网区块作为 fixture,验证无状态执行与有状态执行结果完全一致。
8.1 Fixture 结构
testdata/ 下存放 8 个block-*.tar.gz归档(区块号从 26207960 到 26211680),每个归档内包含:
kv/:RocksDB 键值库(以 Snappy 压缩),存放 trie 节点/字节码/区块头预映像;fixture.json:反序列化后的ExecutorTestFixture(含rollup_config、parent_header、executing_payload及期望的区块哈希)。
8.2 测试工具链
src/test_utils.rs(由test-utilsfeature 或 dev-dependencies 启用)提供:
load_test_fixture:解包归档、打开 RocksDB、解析 fixture;execute_loaded_fixture:用真实配置构造StatelessL2Builder并执行 payload(可选 SDM 激活覆盖);run_test_fixture:执行 fixture 并断言产出的区块哈希与期望值一致。
8.3 关键测试用例
src/builder/core/tests.rs 中的test_statelessly_execute_block通过rstest遍历全部testdata/*.tar.gz完成端到端无状态执行校验;此外还针对 SDM post-exec 交易做了详尽的负向测试:SDM 未激活时拒绝 post-exec 交易、区块号不匹配、重复 post-exec 交易、合法空 payload 不改变状态与 gas 等。
九、在故障证明体系中的真实用途:KonaExecutor
kona-executor不是孤立组件。在 rust/kona/crates/proof/proof/src/executor.rs 中,KonaExecutor包装了StatelessL2Builder并实现kona_driver::Executortrait,从而接入故障证明的派生驱动循环:
update_safe_head(header):由于执行器无状态,每次 safe head 推进就重建一个StatelessL2Builder(executor.rs);execute_payload(attributes):委托给内部 builder 的build_block,未初始化时报ExecutorError::MissingExecutor(executor.rs);wait_until_ready为空操作——因为无状态执行器无需等待任何状态同步。
这种"每次执行都从父 state root 出发、依赖 witness 重建状态"的模型,正是故障证明双方(proposer 与 challenger)能在互不共享状态的情况下对同一区块头达成一致执行结果的根本保证。proof-interop的 consolidation 流程(proof-interop/src/consolidation.rs)同样复用了该执行器。
十、总结
kona-executor用"盲化 MPT 根节点 + 按需证明拉取 + revm 状态层包装"三件套,把传统有状态执行器压缩成了可放进no_std证明环境中的确定性函数:输入(RollupConfig, 父头, OpPayloadAttributes, witness),输出(Sealed<Header>, BlockExecutionResult)。它既支撑了 Kona 客户端对 safe head 的链上推进,也为 fault proof 提供了可复现、可验证的区块执行语义。若要进一步深入,建议按以下顺序阅读源码:
- src/db/mod.rs —— 理解"证明即数据库";
- src/builder/core.rs —— 掌握四步建块流水线;
- src/builder/assemble.rs —— 学习分叉感知的区块头组装;
- src/builder/core/tests.rs 与 testdata/ —— 借鉴真实区块回归测试的构造方法。
【免费下载链接】optimismOptimism is Ethereum, scaled.项目地址: https://gitcode.com/GitHub_Trending/op/optimism
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考