You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
doc(execution-validation): denylist.rs claims execution-level enforcement but the check is never called from EVM pre_execution #495
The module-level doc comment in crates/execution-validation/src/denylist.rs states:
Call from both mempool and execution/Revm.
In practice the is_denylisted helper is called only from the txpool validator (crates/execution-txpool/src/validator.rs:352).
It is not called from ArcEvmHandler::pre_execution, ArcEvm::before_frame_init, or anywhere else in the EVM execution path.
Reproduction (code trace)
crates/evm/src/handler.rsArcEvmHandler::pre_execution calls check_blocklist (which reads NativeCoinControl) then delegates to mainnet.pre_execution. No call to is_denylisted.
crates/evm/src/evm.rsArcEvm::before_frame_init performs the blocklist + EIP-7708 log path. No call to is_denylisted.
crates/execution-validation/src/denylist.rsis_denylisted is only imported in crates/execution-txpool/src/validator.rs.
ArcConsensus::validate_block_post_execution delegates to reth's standard function with no denylist check.
Impact
The docstring creates a false expectation that a denylisted address is blocked at execution time (for example, during engine API block import) as well as at the mempool level.
Under the current code, a node operator who bypasses the mempool (direct engine-API payload or a custom block proposal) could execute a transaction from or to a denylisted address.
For Arc's current trust model (Circle-operated, permissioned validator set) this may be an accepted design choice.
If so, the docstring should be corrected. If execution-level enforcement is intended, handler.rspre_execution is the natural place to call is_denylisted for the sender and (for value-bearing calls) the recipient.
Suggested fix (documentation path)
--- a/crates/execution-validation/src/denylist.rs+++ b/crates/execution-validation/src/denylist.rs-//! Call from both mempool and execution/Revm.+//! Currently called only from mempool (txpool) validation.+//! Execution-level enforcement is not implemented; the current trust model+//! relies on the permissioned validator set to exclude denylisted transactions+//! from proposed blocks.
Or, if execution-level enforcement is desired, ArcEvmHandler::pre_execution would need to call is_denylisted for tx.caller() (and tx.to() for value-bearing transactions) before delegating to the mainnet handler.
One design boundary worth making explicit is whether this denylist is a txpool-admission filter or a consensus/execution invariant.
If it is txpool-only, update the module-level comment and docs to say that local transaction-pool filtering does not guarantee rejection of transactions in imported blocks. If it must be consensus-enforced, put the check on a common block-validation/execution path rather than only a local submission path, and test both locally built payloads and imported blocks. It would also help to define whether the rule covers senders, recipients, contract creation, and internal calls. That keeps validators from applying different block-validity rules based on local txpool policy.
Good point on the design boundary. To add to this: the current check_blocklist in ArcEvmHandler::pre_execution (handler.rs) reads from NativeCoinControl storage -- a separate enforcement path from is_denylisted. So there are actually two distinct denylist mechanisms running in parallel, with different call sites and different storage backends. Making that separation explicit in the docs (or collapsing them into one path) would also prevent future confusion about which list is being enforced where.
Activity
osr21 commentedon Oct 29, 2026
One design boundary worth making explicit is whether this denylist is a txpool-admission filter or a consensus/execution invariant.
If it is txpool-only, update the module-level comment and docs to say that local transaction-pool filtering does not guarantee rejection of transactions in imported blocks. If it must be consensus-enforced, put the check on a common block-validation/execution path rather than only a local submission path, and test both locally built payloads and imported blocks. It would also help to define whether the rule covers senders, recipients, contract creation, and internal calls. That keeps validators from applying different block-validity rules based on local txpool policy.
forumevi commentedon Oct 30, 2026
Good point on the design boundary. To add to this: the current check_blocklist in ArcEvmHandler::pre_execution (handler.rs) reads from NativeCoinControl storage -- a separate enforcement path from is_denylisted. So there are actually two distinct denylist mechanisms running in parallel, with different call sites and different storage backends. Making that separation explicit in the docs (or collapsing them into one path) would also prevent future confusion about which list is being enforced where.