feat(evm): restrict parallel precompile state access - #421
Conversation
nekomoto911
left a comment
There was a problem hiding this comment.
Summary
This PR correctly lands the gravity-reth side of grevm’s capability-restricted parallel precompile API (DynParallelPrecompile): user-transaction BLS PoP and randomness adapters stop receiving Alloy PrecompileInput / raw EvmInternals, while system-transaction mint (and system BLS) stay on explicit unrestricted DynPrecompile injection. Shared gas-aware handlers preserve existing OOG and output semantics, and the isolation test documents that executor-level custom precompiles already do not leak into transact_system_txn. Dominant residual risk is integration hygiene (unconditional grevm dependency on reth-evm, test-constant drift) rather than parallel-state corruption from these two pure precompiles.
Issue counts by severity
- bugs: 0
- suggestions: 2
- nits: 2
| reth-storage-api.workspace = true | ||
| reth-storage-errors.workspace = true | ||
| reth-trie-common.workspace = true | ||
| grevm.workspace = true |
There was a problem hiding this comment.
[suggestion] grevm is added as an unconditional dependency of reth-evm, which still advertises no_std (#![cfg_attr(not(feature = "std"), no_std)]) and is consumed with default-features = false from the workspace. grevm itself is std-only (rayon, parking_lot, dashmap, etc.), so any reth-evm build without std will pull a non-no_std crate. Parallel execution already leans on std types, but this widens the coupling: every serial/no_std-leaning consumer of the executor traits now depends on grevm solely for the DynParallelPrecompile type on ParallelExecutor.
Suggestion: Gate grevm behind the std feature (or an explicit parallel/grevm feature), and either feature-gate ParallelExecutor::apply_custom_precompiles / the grevm type import, or re-export the type only when that feature is enabled. At minimum, list dep:grevm under std so --no-default-features does not force grevm into the graph.
| RandomnessByHeightLookup, RANDOMNESS_BY_HEIGHT_LOOKUP_GAS, RANDOMNESS_BY_HEIGHT_RECENT_GAS, | ||
| }; | ||
| use hex_literal::hex; | ||
| use reth_evm::{ |
There was a problem hiding this comment.
[nit] Adapter tests hardcode const BLS_POP_VERIFY_GAS: u64 = 110_000 independently of POP_VERIFY_GAS in gravity-precompiles (which remains private). If the production gas constant changes, these tests can keep asserting the stale value and still pass against the adapter only by coincidence (or fail with a confusing mismatch instead of a single shared constant).
Suggestion: Export a public gas constant from gravity-precompiles (e.g. pub const BLS_POP_VERIFY_GAS / re-export POP_VERIFY_GAS) and use it in both the handler and adapter tests.
There was a problem hiding this comment.
Concrete fix (mirror the randomness gas constants already used in this test module):
- In
crates/gravity-precompiles/src/bls_pop_verify.rs, export the production constant:
// before
const POP_VERIFY_GAS: u64 = 110_000;
// after
pub const POP_VERIFY_GAS: u64 = 110_000;No other renames needed in the handler or unit tests inside that crate.
- In
custom_precompiles.rs#[cfg(test)]module, drop the local copy and import the shared constant:
// remove
const BLS_POP_VERIFY_GAS: u64 = 110_000;
// add (alongside the existing randomness imports)
use gravity_precompiles::bls_pop_verify::POP_VERIFY_GAS;- Replace every
BLS_POP_VERIFY_GASuse in the adapter tests withPOP_VERIFY_GAS(OOG boundary and successgas_usedasserts).
No Cargo.toml change is required — this crate already depends on gravity-precompiles. Optional follow-up: the same local 110_000 / POP_VERIFY_GAS redefinitions in gravity_system_tx_bls_replay_test.rs / gravity_bls_precompile_test.rs can import the public constant the same way.
| ] | ||
| metrics = ["std", "dep:metrics", "dep:reth-metrics"] | ||
| test-utils = [ | ||
| "grevm/test-utils", |
There was a problem hiding this comment.
[nit] test-utils = ["grevm/test-utils", ...] enables grevm’s test fixture stack (metrics-util, serde, etc.) for any crate that enables reth-evm/test-utils. This crate’s PR changes only need the DynParallelPrecompile type in library code; nothing in reth-evm appears to consume grevm’s test_utils module.
Suggestion: Drop "grevm/test-utils" from reth-evm’s test-utils feature unless a concrete reth-evm test needs it. Leave grevm test-utils enablement to crates that actually run grevm e2e fixtures.
| "MessagePack", | ||
| ] | ||
| allow-dbg-in-tests = true | ||
| disallowed-methods = [ |
There was a problem hiding this comment.
[suggestion] Workspace clippy now denies only alloy_evm::EvmInternals::db_mut. That matches the audit’s highest-risk escape hatch, but unrestricted Alloy precompiles (still used for system mint) retain other EvmInternals mutators (load_account_mut, sstore, etc.). The reason string frames this as a grevm custom-precompile rule, while the deny is workspace-wide and does not by itself force new user precompiles onto DynParallelPrecompile.
Suggestion: Keep the db_mut deny as defense in depth, but consider also documenting (or linting via a module-level convention) that any precompile registered through apply_custom_precompiles / grevm must be built as DynParallelPrecompile, not ad-hoc DynPrecompile. Optionally expand disallowed methods if future audits identify other journal-bypass entry points still reachable from Alloy PrecompileInput.
Implements the gravity-reth side of Galxe/gravity-audit#1089 and depends on Galxe/grevm#120.
Changes
ParallelExecutor::apply_custom_precompilesto acceptDynParallelPrecompile, migrates the BLS PoP and randomness user-transaction precompiles to restricted adapters, and keeps system-transaction precompiles explicit and isolated. Shared gas-aware handlers preserve existing output and out-of-gas semantics, while a Clippydisallowed_methodsrule prevents directEvmInternals::db_mut()use; adapter-boundary, gas-boundary, and system-transaction isolation tests cover the migration.