Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .config/zepter.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ workflows:

"--left-side-outside-workspace=ignore",
# Auxiliary flags:
"--ignore-missing-propagate=reth-evm/test-utils:grevm/test-utils",
"--ignore-missing-propagate=reth-evm-ethereum/test-utils:grevm/test-utils",
"--offline",
"--locked",
Expand Down
3 changes: 2 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,7 @@ rustdoc.all = "warn"
# rust.unnameable-types = "warn"

[workspace.lints.clippy]
disallowed_methods = "deny"
# These are some of clippy's nursery (i.e., experimental) lints that we like.
# By default, nursery lints are allowed. Some of the lints below have made good
# suggestions which we fixed. The others didn't have any findings, so we can
Expand Down Expand Up @@ -451,7 +452,7 @@ reth-provider = { path = "crates/storage/provider" }
reth-prune = { path = "crates/prune/prune" }
reth-prune-types = { path = "crates/prune/types", default-features = false }
reth-revm = { path = "crates/revm", default-features = false }
grevm = { package = "grevm", git = "https://github.com/Galxe/grevm", rev = "dcd1460b20d9f3d793c8309c28b0b7401be91523" }
grevm = { package = "grevm", git = "https://github.com/Galxe/grevm", rev = "25b71edcac1b94be7fc5ee0e4f20e5fcce6908ad" }
reth-rpc = { path = "crates/rpc/rpc" }
reth-rpc-api = { path = "crates/rpc/rpc-api" }
reth-rpc-api-testing-util = { path = "crates/rpc/rpc-testing-util" }
Expand Down
3 changes: 3 additions & 0 deletions clippy.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,6 @@ doc-valid-idents = [
"MessagePack",
]
allow-dbg-in-tests = true
disallowed-methods = [

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[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.

{ path = "alloy_evm::EvmInternals::db_mut", reason = "grevm custom precompiles must access EVM state through the journal-aware capability facade" },
]
71 changes: 67 additions & 4 deletions crates/ethereum/evm/src/parallel_execute.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,10 @@ use alloy_evm::{
};
use alloy_primitives::{map::HashMap, Address};
use gravity_primitives::get_gravity_config;
use grevm::{GrevmConfig, ParallelBundleState, ParallelState, Scheduler, TxExecutionOutcome};
use grevm::{
DynParallelPrecompile, GrevmConfig, ParallelBundleState, ParallelState, Scheduler,
TxExecutionOutcome,
};
use reth_chainspec::{EthChainSpec, EthereumHardfork, EthereumHardforks, Hardforks};
use reth_ethereum_primitives::{Block, EthPrimitives, Receipt};
use reth_evm::{
Expand Down Expand Up @@ -53,8 +56,8 @@ pub struct GrevmExecutor<DB, EvmConfig, ChainSpec> {
state: Option<ParallelState<DB>>,
/// System caller for executing system calls.
system_caller: SystemCaller<Arc<ChainSpec>>,
/// Custom precompiled contracts to inject into the EVM.
custom_precompiles: Option<Arc<Vec<(Address, DynPrecompile)>>>,
/// Capability-restricted custom precompiles available to user transactions.
custom_precompiles: Option<Arc<Vec<(Address, DynParallelPrecompile)>>>,
/// Block-scoped grevm execution policy.
grevm_config: GrevmConfig,
}
Expand Down Expand Up @@ -387,7 +390,10 @@ where
.map_err(|e| BlockExecutionError::msg(alloc::format!("basic {address}: {e:?}")))
}

fn apply_custom_precompiles(&mut self, custom_precompiles: Arc<Vec<(Address, DynPrecompile)>>) {
fn apply_custom_precompiles(
&mut self,
custom_precompiles: Arc<Vec<(Address, DynParallelPrecompile)>>,
) {
self.custom_precompiles = Some(custom_precompiles);
}
}
Expand Down Expand Up @@ -493,6 +499,7 @@ mod tests {
eip7685::EMPTY_REQUESTS_HASH,
};
use alloy_primitives::{keccak256, Bytes, B256, U256};
use core::sync::atomic::{AtomicUsize, Ordering};
use reth_chainspec::{
ChainHardforks, ChainSpec, ChainSpecBuilder, ForkCondition, GravityHardfork, MAINNET,
};
Expand All @@ -503,6 +510,7 @@ mod tests {
bytecode::Bytecode,
context::TxEnv,
database::{CacheDB, EmptyDB},
precompile::{PrecompileId, PrecompileOutput},
primitives::TxKind,
state::{Account, AccountInfo, AccountStatus},
};
Expand Down Expand Up @@ -875,6 +883,61 @@ mod tests {
assert_eq!(info.nonce, 2, "SYSTEM_CALLER nonce must reflect both system txs");
}

/// Executor-level custom precompiles are scoped to Grevm's user-transaction scheduler.
/// System transactions only see the Alloy precompiles explicitly supplied for that call.
#[test]
fn executor_custom_precompile_does_not_leak_into_system_transactions() {
let chain_spec = alpha_active_chainspec(1);
let chain_id = chain_spec.chain().id();
let evm_config = EthEvmConfig::new(chain_spec.clone());
let evm_env = evm_config
.evm_env(&alpha_block_header(1))
.expect("system transaction EVM environment must build");
let precompile_address = Address::with_last_byte(0xfe);

let calls = Arc::new(AtomicUsize::new(0));
let calls_in_precompile = calls.clone();
let custom_precompile = DynParallelPrecompile::new(
PrecompileId::custom("system-precompile-isolation-sentinel"),
move |input| {
calls_in_precompile.fetch_add(1, Ordering::SeqCst);
Ok(PrecompileOutput::new(0, Bytes::new(), input.reservoir()))
},
);

let mut executor = GrevmExecutor::new(chain_spec, &evm_config, seeded_db(U256::ZERO, 0));
executor.apply_custom_precompiles(Arc::new(vec![(
precompile_address,
custom_precompile.clone(),
)]));

let mut implicit_tx = system_tx_env(0, chain_id);
implicit_tx.kind = TxKind::Call(precompile_address);
executor
.transact_system_txn(evm_env.clone(), Vec::new(), implicit_tx)
.expect("system transaction without explicit precompile must succeed");
assert_eq!(
calls.load(Ordering::SeqCst),
0,
"executor-level custom precompile must not be visible to system transactions"
);

let mut explicit_tx = system_tx_env(1, chain_id);
explicit_tx.kind = TxKind::Call(precompile_address);
executor
.transact_system_txn(
evm_env,
vec![(precompile_address, custom_precompile.to_alloy())],
explicit_tx,
)
.expect("system transaction with explicit precompile must succeed");
assert_eq!(
calls.load(Ordering::SeqCst),
1,
"explicit system precompile must execute exactly once"
);
}

// --- U-7: fee归零 + 余额不动 + coinbase 不收 tip (matrix §1.2) ---

/// `u7`: under Alpha gate, SYSTEM_CALLER balance is preserved bit-for-bit
Expand Down
5 changes: 5 additions & 0 deletions crates/evm/evm/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ license.workspace = true
homepage.workspace = true
repository.workspace = true

[package.metadata.zepter.propagate-feature]
ignore = ["grevm"]

[lints]
workspace = true

Expand All @@ -19,6 +22,7 @@ reth-primitives-traits.workspace = true
reth-storage-api.workspace = true
reth-storage-errors.workspace = true
reth-trie-common.workspace = true
grevm = { workspace = true, optional = true }

revm.workspace = true

Expand All @@ -42,6 +46,7 @@ reth-ethereum-primitives.workspace = true
default = ["std"]
std = [
"dep:rayon",
"dep:grevm",
"reth-primitives-traits/std",
"alloy-eips/std",
"alloy-primitives/std",
Expand Down
2 changes: 1 addition & 1 deletion crates/evm/evm/src/either.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

use crate::{execute::Executor, Database, OnStateHook};

use alloc::sync::Arc;
use alloc::{sync::Arc, vec::Vec};
use alloy_evm::{precompiles::DynPrecompile, EvmEnv};
use alloy_primitives::Address;
pub use futures_util::future::Either;
Expand Down
7 changes: 6 additions & 1 deletion crates/evm/evm/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@
extern crate alloc;

use crate::execute::{BasicBlockBuilder, Executor};
use alloc::{boxed::Box, vec::Vec};
#[cfg(feature = "std")]
use alloc::boxed::Box;
use alloc::vec::Vec;
use alloy_eips::eip4895::Withdrawals;
use alloy_evm::{
block::{BlockExecutorFactory, BlockExecutorFor},
Expand All @@ -41,7 +43,9 @@ use revm::{
pub mod either;
/// EVM environment configuration.
pub mod execute;
#[cfg(feature = "std")]
pub mod parallel_execute;
#[cfg(feature = "std")]
use parallel_execute::ParallelExecutor;

mod aliases;
Expand Down Expand Up @@ -494,6 +498,7 @@ pub trait ConfigureEvm: Clone + Debug + Send + Sync + Unpin {
}

/// Returns a new [`ParallelExecutor`].
#[cfg(feature = "std")]
fn parallel_executor<'a, DB: ParallelDatabase + 'a>(
&self,
db: DB,
Expand Down
9 changes: 5 additions & 4 deletions crates/evm/evm/src/noop.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
//! Helpers for testing.

use crate::{
parallel_execute::ParallelExecutor, BlockExecutionError, ConfigureEvm, EvmEnvFor,
ParallelDatabase,
};
#[cfg(feature = "std")]
use crate::{parallel_execute::ParallelExecutor, BlockExecutionError, ParallelDatabase};
use crate::{ConfigureEvm, EvmEnvFor};
#[cfg(feature = "std")]
use alloc::boxed::Box;
use reth_primitives_traits::{BlockTy, HeaderTy, SealedBlock, SealedHeader};

Expand Down Expand Up @@ -74,6 +74,7 @@ where
self.inner().context_for_next_block(parent, attributes)
}

#[cfg(feature = "std")]
fn parallel_executor<'a, DB: ParallelDatabase + 'a>(
&self,
db: DB,
Expand Down
27 changes: 20 additions & 7 deletions crates/evm/evm/src/parallel_execute.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use std::sync::Arc;
use crate::execute::Executor;
use alloy_evm::{precompiles::DynPrecompile, Database, EvmEnv};
use alloy_primitives::Address;
use grevm::DynParallelPrecompile;
use reth_execution_types::{BlockExecutionOutput, BlockExecutionResult};
use reth_primitives_traits::{NodePrimitives, RecoveredBlock};
use revm::{
Expand Down Expand Up @@ -61,7 +62,8 @@ pub trait ParallelExecutor {
/// external bridging.
///
/// `precompiles` allows callers to inject custom precompiles (e.g. mint, BLS) for this
/// specific transaction, in addition to any executor-level custom precompiles.
/// specific transaction. Executor-level user precompiles are intentionally not installed on
/// this system-transaction path.
fn transact_system_txn(
&mut self,
evm_env: EvmEnv,
Expand All @@ -88,11 +90,15 @@ pub trait ParallelExecutor {
/// [`apply_state_change`](Self::apply_state_change).
fn basic(&mut self, address: Address) -> Result<Option<AccountInfo>, Self::Error>;

/// Applies custom precompiled contracts to the executor.
/// Applies capability-restricted custom precompiles to user transaction execution.
///
/// These precompiles will be available during transaction execution alongside
/// the standard Ethereum precompiles.
fn apply_custom_precompiles(&mut self, custom_precompiles: Arc<Vec<(Address, DynPrecompile)>>);
/// Grevm-backed executors must only accept [`DynParallelPrecompile`] here. Adapting an
/// unrestricted Alloy `DynPrecompile` would expose raw EVM internals and allow speculative
/// execution to bypass journal-aware state tracking.
fn apply_custom_precompiles(
&mut self,
custom_precompiles: Arc<Vec<(Address, DynParallelPrecompile)>>,
);
}

/// Wraps a [`Executor`] to provide a [`ParallelExecutor`] implementation.
Expand Down Expand Up @@ -150,7 +156,14 @@ impl<DB: Database, T: Executor<DB>> ParallelExecutor for WrapExecutor<DB, T> {
}

#[inline]
fn apply_custom_precompiles(&mut self, custom_precompiles: Arc<Vec<(Address, DynPrecompile)>>) {
self.0.apply_custom_precompiles(custom_precompiles);
fn apply_custom_precompiles(
&mut self,
custom_precompiles: Arc<Vec<(Address, DynParallelPrecompile)>>,
) {
let custom_precompiles = custom_precompiles
.iter()
.map(|(address, precompile)| (*address, precompile.to_alloy()))
.collect();
self.0.apply_custom_precompiles(Arc::new(custom_precompiles));
}
}
45 changes: 39 additions & 6 deletions crates/gravity-precompiles/src/bls_pop_verify.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@ const EXPECTED_INPUT_LEN: usize = BLS_PUBKEY_LEN + BLS_POP_LEN;
///
/// At ~0.05 gas/ns (mid-range for pairing ops): 2,160,000 ns × 0.05 ≈ 108,000 gas.
/// Rounded to 110,000 to align with EIP-2537 BLS pairing (2 pairs) pricing.
const POP_VERIFY_GAS: u64 = 110_000;
/// Flat gas charged by the BLS proof-of-possession precompile.
pub const POP_VERIFY_GAS: u64 = 110_000;

/// Domain separation tag for BLS `PoP` verification
/// Matches the IETF standard for BLS12-381 `PoP`
Expand All @@ -61,13 +62,17 @@ pub fn create_bls_pop_verify_precompile() -> DynPrecompile {
let precompile_id = PrecompileId::custom("bls_pop_verify");

(precompile_id, move |input: PrecompileInput<'_>| -> PrecompileResult {
bls_pop_verify_handler(input)
bls_pop_verify_handler(input.data, input.gas)
})
.into()
}

/// BLS `PoP` verification handler
fn bls_pop_verify_handler(input: PrecompileInput<'_>) -> PrecompileResult {
/// Executes BLS `PoP` verification with the precompile's complete gas semantics.
///
/// This is the shared entry point for EVM adapters. It checks the forwarded gas before running the
/// comparatively expensive verification and delegates input processing to
/// [`bls_pop_verify_handler_raw`].
pub fn bls_pop_verify_handler(data: &[u8], gas: u64) -> PrecompileResult {
// Charge-gas check before running the verification logic. This precompile
// charges a flat `POP_VERIFY_GAS`, and the EVM dispatcher executes
// `assert!(record_cost(gas_used))` after receiving `Ok(gas_used)`: if the
Expand All @@ -76,10 +81,10 @@ fn bls_pop_verify_handler(input: PrecompileInput<'_>) -> PrecompileResult {
// network-wide halt triggerable by any user transaction). Return OutOfGas
// early so the dispatcher takes the normal PrecompileOOG branch instead of
// panicking.
if POP_VERIFY_GAS > input.gas {
if POP_VERIFY_GAS > gas {
return Ok(PrecompileOutput::halt(PrecompileHalt::OutOfGas, 0));
}
bls_pop_verify_handler_raw(input.data)
bls_pop_verify_handler_raw(data)
}

/// Core BLS `PoP` verification logic operating on raw input bytes.
Expand Down Expand Up @@ -241,4 +246,32 @@ mod tests {
let result = bls_pop_verify_handler_raw(&input_data).unwrap();
assert_eq!(result.bytes[31], 0, "Invalid PoP should return false");
}

#[test]
fn test_precompile_gas_aware_handler_boundary() {
let (pubkey, pop) = generate_test_keypair();
let mut input_data = Vec::with_capacity(EXPECTED_INPUT_LEN);
input_data.extend_from_slice(&pubkey);
input_data.extend_from_slice(&pop);

let out_of_gas = bls_pop_verify_handler(&input_data, POP_VERIFY_GAS - 1).unwrap();
assert!(matches!(
out_of_gas.status,
revm::precompile::PrecompileStatus::Halt(PrecompileHalt::OutOfGas)
));
assert_eq!(out_of_gas.gas_used, 0);

let exact_gas = bls_pop_verify_handler(&input_data, POP_VERIFY_GAS).unwrap();
assert!(exact_gas.is_success());
assert_eq!(exact_gas.gas_used, POP_VERIFY_GAS);
assert_eq!(exact_gas.bytes[31], 1);

// At the exact gas threshold, input validation runs and preserves its existing halt.
let invalid_input = bls_pop_verify_handler(&[], POP_VERIFY_GAS).unwrap();
assert!(matches!(
invalid_input.status,
revm::precompile::PrecompileStatus::Halt(PrecompileHalt::Other(_))
));
assert_eq!(invalid_input.gas_used, 0);
}
}
Loading
Loading