Skip to content

feat!: replace anvil with reth-anvil - #17424

Draft
figtracer wants to merge 76 commits into
masterfrom
fig/reth-anvil
Draft

figtracer wants to merge 76 commits into
masterfrom
fig/reth-anvil

Conversation

@figtracer

@figtracer figtracer commented Oct 6, 2026 •

Copy link
Copy Markdown
Member

Replace crates/anvil with a reth SDK node that retains anvil's development controls, RPC methods, CLI, and fork support. reth-anvil provides the SDK and Ethereum implementation; reth-anvil-tempo implements the public extension API, and reth-anvil-cli owns the binary. Other networks belong in extensions; Optimism and Base remain ignored specifications. UPSTREAM.md records the remaining workarounds, including sender caching and receipt retries, which stay until the pinned dependencies contain the fixes. Refs paradigmxyz/reth#27816 (reopening #27757) and #9970.

Port the sfm-colab/anvil-reth prototype onto upstream reth v2.7.0 as a
workspace crate. The node runs on a temporary MDBX datadir and serves
the anvil_* namespace for impersonation, mining control, time control,
and block environment overrides. State writes, dumpState/loadState,
forking, and snapshot/revert are not ported yet.

foundry-core is pinned to the commit that drops the time crate pin,
because reth-node-core needs vergen and therefore time 0.3.51 or newer.
@socket-security

socket-security Bot commented Oct 6, 2026 •

Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Updatedcargo/​vergen@​10.0.0 ⏵ 10.0.37010093100100
Addedcargo/​jsonrpsee@​0.26.110010093100100
Addedcargo/​rocksdb@​0.24.010010093100100
Addedcargo/​hashbrown@​0.13.210010093100100
Addedcargo/​shlex@​1.3.010010093100100
Addedcargo/​time@​0.3.5510010093100100
Addedcargo/​toml@​0.9.12%2Bspec-1.1.010010093100100

View full report

…lock executor

Add anvil_setBalance, addBalance, setNonce, setCode, and setStorageAt. A write
lands in a read overlay served by a provider wrapper on latest and pending
state, so RPC reads and pool validation see it at once. The block executor
applies every queued write at the start of the next block, so the write enters
the block's state changes and state root, and the overlay entry drops once that
block is canonical. The provider wrapper needs a launcher that installs it, so
the engine launcher is mirrored with only the provider construction changed.
Add anvil_snapshot, anvil_revert, anvil_rollback, and evm_mine. The node now
drives block production itself instead of through reth's local miner: mining
requests return the mined header, so anvil_mine no longer polls the head, and
the engine never finalizes blocks, so a revert can rewind to any block. A
rewind drops the in-memory blocks above the target, removes the persisted
blocks above it, and emits a reorg notification so the RPC caches and the pool
follow; the pool rejects the dropped transactions so the reorg does not mine
them again. The safe and finalized tags follow anvil: one and two epochs behind
the head.
Add the `reth-anvil` binary with anvil's flags, `NodeConfig` with the
`with_*` builders and `spawn`/`try_spawn`, and an in-process `EthApi` that
calls the merged RPC module without a socket. The dev accounts come from
anvil's mnemonic generator and sign through the node's `eth_sendTransaction`.

Add anvil_reorg, anvil_reset, anvil_setMinGasPrice, anvil_setLoggingEnabled,
anvil_getLastBlockWallTime, the blob getters, the ERC20 deal and allowance
helpers, the hardhat aliases, and the genesis timestamp and chain id options.
Pin reth to the rev string the tempo crates use so Cargo resolves one copy.

Impersonated transactions record their sender in reth's sender recovery cache
so receipt lookups find them once paradigmxyz/reth#27757 lands.
@figtracer
figtracer marked this pull request as ready for review October 6, 2026 19:25
@figtracer
figtracer marked this pull request as draft October 6, 2026 19:26
Add `--fork-url` and the other fork flags. The local database starts at
the fork block: its header is the remote header, so the next local block
links to the remote chain, and the dev accounts keep their remote nonce
and code. Blocks, receipts, and transactions below the fork block come
from the remote endpoint on demand, with remote transactions in their own
transaction number space so `eth_getLogs` spans both sides.

State reads check whether the local chain wrote the account or slot,
through reth's history index and the in-memory blocks, and fall back to
foundry's cached fork database at the fork block otherwise. The engine
validates blocks against the local database directly, so the remote
reads the block builder made are copied into the local database before
the engine executes the block, with the fork block as their history
entry.

Reads block on the remote endpoint with `block_in_place` on a
multi-threaded runtime and with a plain wait elsewhere, and the fork
backends get their own providers so a blocked runtime cannot stall them.
`anvil_metadata` and `anvil_nodeInfo` report the fork, `anvil_setRpcUrl`
replaces the endpoint, and `anvil_reset` resets to the fork block.
Add anvil_dumpState and anvil_loadState, and the --state, --dump-state,
--load-state, and --state-interval flags. A dump walks the persisted
plain state, applies the in-memory blocks and the pending anvil state
writes on top, and records the head block environment and number. A node
started from a dump puts the accounts in genesis and starts at the dumped
block number with its timestamp, gas limit, and base fee, so the chain
continues where the dump left off. Loading at runtime applies the
accounts as anvil state writes, with nonces taking the higher value.

Also add a regression test for the EIP-8037 gas estimate from #17428:
reth executes a transfer at 21,000 gas before accepting that estimate,
so a transfer that creates an account on Amsterdam reports the state gas.
… methods

Add anvil_impersonateSignature, eth_sendUnsignedTransaction, and
personal_sign. A signature override attributes every raw transaction that
carries the signature to the chosen sender: the pool validator rebuilds
the transaction with that sender so the block builder executes it from
the right account, and execution and lookups use the recorded sender.
eth_sendUnsignedTransaction impersonates the `from` account for one send.

Serve every reth RPC namespace over HTTP and WebSocket, as anvil does,
which also provides erigon_getHeaderByNumber through the otterscan module.
Print the RPC methods served and the mined blocks and transactions to
stdout as anvil does, through an RPC middleware and a canonical state
listener. `--silent` turns the output off at startup and
anvil_setLoggingEnabled toggles it at runtime.

Serve the IPC endpoint when `--ipc` is set, and add `--order fifo`
through a pool ordering that ranks transactions by arrival, which
anvil_nodeInfo reports.

Add UPSTREAM.md: the list of copies and workarounds in this crate, the
reth hook that would replace each, and the gaps that remain.
Introduce the `AnvilNetwork` trait: a network names its reth node type,
its components and RPC add-ons with the anvil wrappers installed, and
its payload attributes builder. The node launch, the provider, the
launcher, the miner, the EVM wrapper, the pool validator, and the
`anvil_*` namespace are now generic over the network's primitives,
chain spec, payload, and RPC types, and the fork converts remote
responses through a `ForkNetwork` implementation. Ethereum is the first
network. `NodeConfig` carries the network selection with `with_networks`
and the `test_tempo` and `test_monad` constructors.
Apply the code size limit, the memory limit, EIP-3607, the block gas
limit check, and the EIP-7825 cap to every EVM environment, so block
execution, the pool, and RPC calls agree with anvil. Pass the memory
limit to reth's RPC call path, which sets its own. Pin the payload
builder's gas limit to the configured one, so blocks no longer drift
towards reth's default, and set it to `u64::MAX` when the block gas
limit is disabled.

Let the pool skip the balance check and the minimum priority fee, and
report an unlimited balance when the check is off, so a transaction
funded in the same block is mined. Replace `eth_sendTransaction` with a
version that treats a missing `to` as a contract creation, which reth
rejects. Use the anvil dev mnemonic by default.
Run Monad on reth's Ethereum node types with a `ConfigureEvm` built on
`monad-revm`. The EVM factory looks up the senders and EIP-7702
authorities of the parent and grandparent blocks for every EVM it
creates, and the block executor adds each transaction to the chain
context before it runs, so reserve balances behave as on Monad. The
environment carries Monad's code size, initcode size, and transaction
gas limits, so the pool enforces them too.

The pool rejects blob transactions and checks only the fee at the
effective gas price, as Monad does. `anvil_nodeInfo` reports the network
and the Monad hardfork, and the chain spec activates the Ethereum
hardfork the Monad hardfork is based on.
The in-process `instance_id` kept the id the node started with, while `anvil_reset` rotates it. Workspace tests compare the two across a reset.
Run the RPC server in front of the reth node instead of inside it.
Every method forwards to the module of the current node, so the node
can be replaced while the endpoint, the open connections, and the
in-process API keep working. `anvil_reset` to another fork endpoint or
block, and the new `anvil_setChainId`, stop the node and launch one
from the updated config; `setChainId` carries the state and the height
over through a state dump. A failed launch falls back to the previous
config.

Set the chain id of the dev wallets with `with_chain_id`, so a node with
a custom chain id signs transactions.
Add `eth_sendTransactionSync`, `eth_resend`, `eth_sendRawTransactionConditional`,
`eth_requestAccounts`, and `eth_networkId`, the `hardhat_*` state dump
aliases, and the `tenderly_*` balance aliases. Register `eth_config`,
which reth adds only to its transport modules. A test checks that every
method anvil serves is registered, except the Tempo helpers and the
opcode gas traces.

Add the network flags to the CLI with anvil's chain-id inference and
hardfork normalization, and honour `--allow-origin`, `--no-cors`, and
`--no-request-size-limit` in the RPC server.
Resolve the transaction on the fork endpoint, fork at the block before
it, and replay the transactions up to and including it into the first
local block before the node serves requests. The replayed block keeps
the remote block's timestamp, fee recipient, gas limit, base fee, and
randomness, and the pool takes the transactions in arrival order while
it builds, so the block matches the remote prefix.
Keep the writes a block applied after it is canonical, so a replay of
that block applies them again, and serve them in the state at the block
they were written at, as anvil writes into the head state. Tracing a
transaction that calls code set with `anvil_setCode`, and tools that
fork at the parent block to replay a transaction, see the same state as
with anvil.

Default to the latest hardfork for a chain id without a schedule, as
anvil does, instead of mainnet's schedule. Drop the in-process block
lookup's numeric block parameter in favour of a block number or tag, and
accept anvil's request type in `send_transaction`, so the workspace tests
compile against this crate unchanged.

`UPSTREAM.md` lists the differences that running cast's tests against
this node showed.
Reth gives calls and estimates without a gas limit 50M gas. Anvil gives them the block gas limit, and contracts that check the gas they got see the difference.
Tempo runs on Tempo's node types, executor, and RPC, with the anvil
executor wrapper, a pool built over the wrapped EVM config, and a
sequential dev block builder. The genesis seeds Tempo's precompiles,
fee tokens, dev balances, keychain keys, and fee token liquidity, as
anvil does, and a fork keeps the remote Tempo state.

The anvil namespace gains the Tempo methods: dealTIP20, setFeeToken,
setValidatorFeeToken, and setFeeAmmLiquidity. eth_signRawTransaction
and raw transactions with the sponsorship placeholder use the node's
fee payer. Anvil's Tempo tests move to reth-anvil; 103 of 167 pass.
Call batches keep no create target when sent or simulated, balances
and fork accounts skip Tempo's native balance placeholder, the pool
validates against anvil's clock through a pool-only EVM config and sees
anvil state writes before the next block, the next base fee follows the
chain spec's rule, and eth_config lists Tempo's precompiles. Tempo-only
requests fail on other networks, and Tempo fee token shortfalls report
as anvil reports them. 136 of 156 Tempo tests pass.
Tempo now passes anvil's Tempo tests: the txpool views key nonce lanes
as anvil does, calls and estimates run with the request's nonce, lane
nonces resolve from storage, signing fills Tempo fees and lane nonces,
sponsored simulations keep their signed gas, impersonated senders are
known to block execution, the block builder follows gas limit
overrides, and the base fee and coinbase follow anvil across resets.
Six tests stay ignored for gaps in Tempo's crates, which UPSTREAM.md
lists with the hook that would close each.

docs/networks.md explains how a network team adds its network, with
the Optimism and Base specifics.
The tests move from anvil, ignored until the Optimism and Base networks
run, so the teams that add them start from anvil's behavior. The base
feature gains anvil's activation admin option.
The anvil binary now builds from reth-anvil, and the old anvil crates
are deleted. reth-anvil gains anvil's build features (jemalloc,
mimalloc, tracy-allocator, asm-keccak, js-tracer, cli), runs Tempo by
default, and the release feature lists include tempo.

Optimism and Base do not run yet; their teams add them with
crates/reth-anvil/docs/networks.md, from anvil's tests, which reth-anvil
keeps as ignored specs. A fork probes anvil_nodeInfo for up to 2s, once
per launch, since a busy reth-anvil endpoint answers more slowly than
anvil did.
Revert messages decode the revert data with foundry's RevertDecoder, as
anvil's do: Tempo's API named precompile errors by a selector that
several precompiles share, and reth printed custom errors without
their selector. CI names the cast package with its version, since
Tempo's node brings another crate named cast.
@figtracer figtracer changed the title feat(reth-anvil): add reth dev node with the anvil namespace feat!: replace anvil with reth-anvil Oct 8, 2026
The interval mining task requested a block every interval without
waiting for it. When a block took longer than the interval to build,
the requests queued in the miner, and a switch to manual mining still
mined them. The task now waits for each block, so no request queues.
Tempo rejected a state dump with a block environment. A dump block now
keeps its header as a FoundryHeader, so a Tempo header keeps its Tempo
fields. A new ForkNetwork::dump_header hook converts it to the node's
header type, and the block keeps its dumped hash. Tempo then loads a
dump as Ethereum does: alone, or above a fork block. An Ethereum dump
on Tempo shows Tempo headers with the Ethereum hashes, as anvil does.

anvil_loadState with blocks now keeps the accounts the dump does not
name, as anvil loads a dump over its state.

This also vendors TempoEthApi as AnvilTempoEthApi. It builds pending
blocks, reports native balances, and gives each simulated
expiring-nonce call its own hash. This removes the eth_getBalance
override and enables four Tempo tests that were ignored.
anvil_impersonateSignature did not apply to EIP-7702 authorities, on
any network, or to ecrecover on Tempo. Two tests were ignored for this.

alloy-evm recovers authorities when it builds the transaction
environment, through alloy's crypto functions. When a node first sets
a signature override, it now installs an alloy CryptoProvider that
recovers an overridden signature to its address. The override then
applies to block execution, calls, traces, and replays. The provider is
process-wide: in one process, the overrides of every node apply.

For a network whose EVM factory anvil cannot wrap, AnvilEvmConfig now
installs the ecrecover override on the EVMs it creates, so Tempo gets
it. Monad recovers ancestor authorities through the same functions.
Pending block. Reth reuses a pending block for a second and keys it on
the parent alone. The node now drops it after a change to the pool,
the state, or the next block's environment. Calls, estimates, and
access lists at pending run with the pending block's state changes as
state overrides, because reth runs them on the latest state.
eth_estimateGas without a block estimates on the pending block, as
anvil does.

Otterscan. ots_getInternalOperations now includes the top-level
operation, and ots_getBlockTransactions pages from the first
transaction. ots_searchTransactionsBefore and
ots_searchTransactionsAfter, which reth does not implement, now work.

Fees. A blob call without a blob fee cap runs at a zero blob base fee
in eth_call and trace_call, as in geth. On Arbitrum chains, the pool
and the EVM accept a priority fee above the fee cap.

This enables nine tests that were ignored.
The pool validator accepts some transactions itself: an impersonated
sender, a signature override, a sender with code, and, on Arbitrum, a
tip above the fee cap. For a blob transaction this dropped the sidecar
from the blob store, so the block builder never included it. A blob
transaction from a dev account with code on a mainnet fork never got
mined. The validator now keeps the sidecar.

--prune-history had no effect, because reth keeps the full history. An
RPC middleware now answers a state read at a block below the retention
window with anvil's BlockOutOfRangeError.

This enables four tests that were ignored.
A blob call without a blob fee cap now gets a zero cap, so it builds as
a blob transaction. Without validation, a zero cap runs at the block's
blob base fee, and the response shows the zero cap. A request with a
sidecar gets its blob hashes also when it sends an empty hash list. The
blob gas of a simulated block is checked against the hardfork's
per-block limit. A cap below the blob base fee is reported with
anvil's message.

This enables four tests that were ignored. Two stay ignored, because
anvil charges no blob fee without validation. One stays ignored,
because reth-anvil schedules no hardfork after the configured one.
reth-anvil now runs Ethereum and Tempo. Networks other than Ethereum
belong in extensions that their teams own, on the AnvilNetwork API.

The reth-anvil side (the Monad network, its tests, the monad feature,
the pool's gas-only balance rule and blob rejection) landed with the
previous commit by mistake. This commit removes the anvil/monad feature
from cast, forge, script, and the EVM, and the cast tests that spawned
a Monad anvil node. cast, forge, and the EVM keep their own Monad
support.
crates/anvil stays deleted. Ported from master's anvil changes: the
configurable transaction coalescing window (#17448), the fork URL
redaction in anvil_nodeInfo (#17421), and the start of the priced-call
rule (#17419): free calls still drop their fee fields, priced calls no
longer do. The state gas estimate (#17432) needs no port. Master's new
Base tests are left out, because Base is out of scope.

Not done yet: reth runs every call with the base fee check off, so a
priced call below the base fee still succeeds; reth-anvil needs an
explicit check in eth_call, eth_estimateGas, trace_call, and
trace_callMany. Two more tests fail after the merge:
gas::test_estimate_gas_transfers_across_hardforks and
eip4844::estimate_gas_reserves_blob_fee.
Reject priced calls, estimates, and traces below the execution block base fee. Use the pending environment for pending calls and retain free calls and pre-London behavior.

Refine gas estimates below 21,000 after EIP-2780, enforce the requested gas allowance, and populate blob hashes from sidecars. Cover block selection and fee boundaries across all four RPC methods.
Use the configured endpoint for the fee history comparison. Node info exposes a redacted URL that must not be used to connect to the upstream node.
Try one gas below a small estimate before refining the full error range. Exact transfer estimates need one probe, while Amsterdam contract calls still get the exact gas limit.

Remove extra probes that delayed batch transaction submission past the coalescing window. Keep the requested gas allowance check after refinement.
Move the Tempo network, native request adapters, RPC methods, and tests into reth-anvil-tempo. Put the anvil binary in reth-anvil-cli, which selects the installed Ethereum or Tempo network.

Expose the network components and marker-based adapter traits so external crates can implement the SDK for upstream types. Register typed network RPC methods through the network hook and retain them after reset.

Keep Tempo RPC generic over node components so the compiler does not expand the full concrete node type in every handler.
Return a JSON-RPC parse error for unmatched IPC delimiters and resume
decoding later requests on the same connection. Keep reth's dispatch,
subscription limits, and connection lifecycle in a local adapter until
its IPC server accepts a configurable codec.

Cover malformed and split frames, leading whitespace, and connection
recovery. Document the adapter and the upstream hook that replaces it.
Restore the remaining Ethereum regression cases across blob calls,
fork replay, pending blocks, and traces after chain-id changes. Keep
Arbitrum execution numbers and historical execution rules in state
dumps, and apply BPO blob schedules without upgrading the configured EVM.

Permit sidecarless blob transactions only for impersonated senders,
while retaining normal pool validation and native payload accounting.
Give each node an owned runtime and close it on shutdown or failed
startup so retained handles cannot leave its executor running.

Select omitted nonces from a stable chain head under the sender lock
to avoid stale nonces when automining updates the pool between reads.
Enable the affected integration tests, add regression coverage, and
record the remaining upstream workarounds.
Merge origin/master at 71a072d. Retain the shared CIP-64 transaction
primitives and keep the legacy Anvil crates deleted. Leave Celo execution
to network extensions and describe the shared encoding support accurately.

Preserve the no-default-features request builder and the Ethereum reorg
request types when resolving the merge.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

1 participant