-
Notifications
You must be signed in to change notification settings - Fork 212
feat(executor): add InspectorFactory support for EVM execution tracing #3225
base: main
Are you sure you want to change the base?
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This PR adds an InspectorFactory trait to enable custom EVM inspector injection during block execution, supporting external tooling for observing and tracing EVM execution. This is particularly useful for investigating derivation errors like the one described in #3108.
- Introduces an
InspectorFactorytrait pattern for creating fresh inspectors per block execution - Extends
StatelessL2BuilderandKonaExecutorwith optional inspector factory support via a new type parameterIF - Uses
create_evm_with_inspectorwhen a custom inspector is provided, falling back tocreate_evmotherwise
Reviewed changes
Copilot reviewed 8 out of 14 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
crates/proof/executor/src/builder/core.rs |
Adds InspectorFactory trait with comprehensive documentation and implements it for (). Adds IF type parameter to StatelessL2Builder and implements conditional inspector usage with unsafe code |
crates/proof/executor/src/builder/mod.rs |
Re-exports InspectorFactory trait |
crates/proof/executor/src/builder/env.rs |
Adds IF: InspectorFactory bound to impl block |
crates/proof/executor/src/builder/assemble.rs |
Adds IF: InspectorFactory bound to impl block |
crates/proof/executor/src/lib.rs |
Re-exports InspectorFactory trait |
crates/proof/executor/src/test_utils.rs |
Updates test fixtures to pass None::<()> for default inspector |
crates/proof/proof/src/executor.rs |
Adds IF type parameter to KonaExecutor and propagates inspector factory through execution pipeline |
crates/proof/proof-interop/src/consolidation.rs |
Updates SuperchainConsolidator with 'static bound on C and passes None::<()> at call site |
bin/client/src/single.rs |
Adds inspector_factory parameter to run() function with comprehensive trait bounds |
bin/client/src/kona.rs |
Passes None::<()> to run() function |
bin/client/src/interop/transition.rs |
Adds 'static bounds to generic parameters and passes None::<()> |
bin/client/src/interop/consolidate.rs |
Adds 'static bounds to generic parameters |
bin/host/src/single/cfg.rs |
Passes None::<()> to client run() function |
bin/host/src/interop/handler.rs |
Passes None::<()> to KonaExecutor::new() |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| pub const fn new( | ||
| rollup_config: &'a RollupConfig, | ||
| trie_provider: P, | ||
| trie_hinter: H, | ||
| evm_factory: Evm, | ||
| inner: Option<StatelessL2Builder<'a, P, H, Evm>>, | ||
| inner: Option<StatelessL2Builder<'a, P, H, Evm, IF>>, | ||
| inspector_factory: Option<IF>, | ||
| ) -> Self { | ||
| Self { rollup_config, trie_provider, trie_hinter, evm_factory, inner } | ||
| Self { rollup_config, trie_provider, trie_hinter, evm_factory, inner, inspector_factory } | ||
| } |
Copilot
AI
Dec 27, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Similar to the StatelessL2Builder::new() breaking change, KonaExecutor::new() now requires an additional inspector_factory parameter. This is a breaking API change for any external code.
The same backwards-compatibility approach suggested for StatelessL2Builder would apply here - consider providing both new() (without inspector) and new_with_inspector() methods to maintain backward compatibility.
| P: TrieDBProvider + Send + Sync + Clone, | ||
| H: TrieHinter + Send + Sync + Clone, | ||
| Evm: EvmFactory + Send + Sync + Clone, | ||
| IF: InspectorFactory, |
Copilot
AI
Dec 27, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The KonaExecutor struct is marked with #[derive(Debug)] but the IF type parameter only requires InspectorFactory bound in the struct definition (line 30). This could cause a compilation error if InspectorFactory implementors don't implement Debug.
Consider adding Debug to the bounds on line 30:
IF: InspectorFactory + Debug,This ensures the derived Debug implementation will work correctly.
| IF: InspectorFactory, | |
| IF: InspectorFactory + Debug, |
| P: TrieDBProvider, | ||
| H: TrieHinter, | ||
| Evm: EvmFactory, | ||
| IF: InspectorFactory, |
Copilot
AI
Dec 27, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Similar to KonaExecutor, the StatelessL2Builder struct is marked with #[derive(Debug)] but the IF type parameter only requires InspectorFactory bound (line 159). This could cause a compilation error if InspectorFactory implementors don't implement Debug.
Consider adding Debug to the bounds:
IF: InspectorFactory + Debug,This ensures the derived Debug implementation will work correctly.
| IF: InspectorFactory, | |
| IF: InspectorFactory + core::fmt::Debug, |
| let ex_result = match &self.inspector_factory { | ||
| // When no custom inspector factory is provided, use the standard create_evm(). | ||
| None => { | ||
| // Step 2. Create the executor, using the trie database. | ||
| let evm = self.factory.evm_factory().create_evm(&mut state, evm_env); | ||
|
|
||
| // Step 3. Execute the block containing the transactions within the payload attributes. | ||
| let executor = self.factory.create_executor(evm, ctx); | ||
| executor.execute_block(transactions.iter())? | ||
| } | ||
| // When a custom inspector factory is provided, we need to use raw pointers | ||
| // to work around the borrow checker's limitations with generic types. | ||
| // | ||
| // NOTE: With a generic IF::Inspector, Rust cannot prove that the mutable | ||
| // borrow of state ends when the EVM is dropped. We use raw pointers to work | ||
| // around this. This is safe because: | ||
| // 1. The state is valid for the entire duration of EVM usage | ||
| // 2. The EVM is consumed by execute_block before we access state again | ||
| // 3. No other references to state exist during EVM usage | ||
| Some(factory) => { | ||
| let state_ptr = &mut state as *mut _; | ||
| let inspector = factory.create(); | ||
|
|
||
| // Step 2. Create the executor, using the trie database. | ||
| // SAFETY: state is valid and not accessed elsewhere during EVM usage. | ||
| // The EVM is consumed by execute_block, ending the borrow. | ||
| let evm = self.factory.evm_factory().create_evm_with_inspector( | ||
| unsafe { &mut *state_ptr }, | ||
| evm_env, | ||
| inspector, | ||
| ); | ||
|
|
||
| // Step 3. Execute the block containing the transactions within the payload attributes. | ||
| let executor = self.factory.create_executor(evm, ctx); | ||
| executor.execute_block(transactions.iter())? | ||
| } |
Copilot
AI
Dec 27, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The new InspectorFactory feature lacks test coverage. While the existing code path with None::<()> uses the no-op inspector, there are no tests that verify:
- That a custom inspector factory actually gets called during block execution
- That the inspector receives the expected callbacks from the EVM
- That the inspector's state is properly scoped to a single block execution
- Edge cases like inspector errors or panics
Consider adding at least one test that uses a simple custom inspector (e.g., a call counter) to verify the integration works correctly. This would catch issues like the inspector not being created or not being passed to the EVM correctly.
| // When a custom inspector factory is provided, we need to use raw pointers | ||
| // to work around the borrow checker's limitations with generic types. | ||
| // | ||
| // NOTE: With a generic IF::Inspector, Rust cannot prove that the mutable | ||
| // borrow of state ends when the EVM is dropped. We use raw pointers to work | ||
| // around this. This is safe because: | ||
| // 1. The state is valid for the entire duration of EVM usage | ||
| // 2. The EVM is consumed by execute_block before we access state again | ||
| // 3. No other references to state exist during EVM usage | ||
| Some(factory) => { | ||
| let state_ptr = &mut state as *mut _; | ||
| let inspector = factory.create(); | ||
|
|
||
| // Step 2. Create the executor, using the trie database. | ||
| // SAFETY: state is valid and not accessed elsewhere during EVM usage. | ||
| // The EVM is consumed by execute_block, ending the borrow. | ||
| let evm = self.factory.evm_factory().create_evm_with_inspector( | ||
| unsafe { &mut *state_ptr }, | ||
| evm_env, | ||
| inspector, | ||
| ); | ||
|
|
||
| // Step 3. Execute the block containing the transactions within the payload attributes. | ||
| let executor = self.factory.create_executor(evm, ctx); | ||
| executor.execute_block(transactions.iter())? | ||
| } |
Copilot
AI
Dec 27, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This use of unsafe and raw pointers appears to be working around a borrow checker issue, but this pattern is concerning for maintainability and safety.
The comment claims this is "safe because... The EVM is consumed by execute_block before we access state again", but there's a potential issue: if execute_block panics or returns early via ?, the raw pointer could leave state in an invalid borrowed state, violating Rust's safety guarantees.
Consider refactoring to avoid unsafe code entirely. One approach would be to factor out the common code between the two branches and use a trait object or enum to abstract over the inspector type. For example:
enum MaybeInspector<I> {
None,
Some(I),
}Or use a wrapper that implements Inspector and conditionally delegates. This would eliminate the unsafe code while maintaining the same functionality.
Codecov Report❌ Patch coverage is
☔ View full report in Codecov by Sentry. |
Summary
InspectorFactorytrait to enable custom EVM inspector injection during block executionStatelessL2BuilderandKonaExecutorwith optional inspector factory supportcreate_evm_with_inspectorwhen a custom inspector is providedMotivation
This change enables external tooling to observe and trace EVM execution during block building. The
InspectorFactorypattern allows inspectors to be created fresh for each block execution, ensuring clean state and proper scoping.This is useful for investigating derivation errors like the one seen in #3108.
Changes
InspectorFactorytrait (kona-executor): Defines a factory pattern for creating EVM inspectors with full documentation on implementation requirementsStatelessL2Builder: AddedIFtype parameter andinspector_factoryfieldKonaExecutor: Extended to propagate inspector factory through the execution pipelineNone::<()>for default no-op inspector behaviorAPI