Skip to content

rpc/ethapi, rpc/jsonrpc: rebuild the overridden precompile set per bundle in eth_callMany and debug_traceCallMany - #25343

Open
lupin012 wants to merge 3 commits into
mainfrom
lupin012/callmany_precompiles_per_bundle
Open

lupin012 wants to merge 3 commits into
mainfrom
lupin012/callmany_precompiles_per_bundle

Conversation

@lupin012

Copy link
Copy Markdown
Contributor

With a state override, eth_callMany and debug_traceCallMany build the precompile set once, from the base block's rules, and call SetPrecompiles with it on the EVM of every bundle. A bundle can move the block forward with a block override on time or number. If this crosses a fork, the bundle runs with the old fork's precompiles: for example, after Prague a call to 0x0b (BLS12-381 G1ADD) runs against an empty account.

Without a state override, activePrecompiles is nil and NewEVM already selects the bundle's own set. So the same request gave different results depending on whether it carried a state override.

Change

  • StateOverrides.Override is unchanged in behavior. Its precompile loop moved to overridePrecompiles.
  • New StateOverrides.ActivePrecompiles(rules): a fresh precompile set for rules, with the same code overrides and precompile moves applied, without touching the state. It returns nil when there are no overrides.
  • eth_callMany and debug_traceCallMany still call Override once, before the bundles, because it changes the state. Each bundle now gets its set from ActivePrecompiles(blockCtx.Rules(chainConfig)), after its block override.

Other clients

eth_callMany and debug_traceCallMany are not in execution-apis, so the reference is the behavior of the closest methods in other clients:

  • geth eth_call (doCall) applies the block overrides first and builds the precompile set from the overridden block context. eth_simulateV1 builds the set per block.
  • Nethermind runs debug_traceCallMany with overrides through its eth_simulateV1 executor, so every bundle uses the spec of its own block.

This change gives the same behavior: the precompile set follows the bundle's block.

Test

TestCallManyBundleCrossingForkUsesItsPrecompiles runs a bundle that moves past a Prague fork and calls 0x0b, for both methods, with and without a state override. Before the fix, only the two cases with a state override failed (empty output instead of the 128-byte result).

Note

#24741 changes the delete condition inside the loop that this PR moves to overridePrecompiles. Whichever merges second needs a small manual rebase.

…ndle in eth_callMany and debug_traceCallMany

With a state override, eth_callMany and debug_traceCallMany built the
precompile set once from the base block's rules and set it on the EVM of
every bundle. A bundle that moves the block past a fork with a block
override then ran with the old fork's precompiles: a call to a BLS12-381
precompile after Prague ran against an empty account. Without a state
override the EVM already picked the bundle's own set, so the same request
gave different results depending on the override.

Override still runs once, before the bundles, because it changes the
state. Each bundle now gets a fresh set for its own rules, with the same
code overrides and precompile moves applied.
@AskAlexSharov

Copy link
Copy Markdown
Collaborator

Looks good. Ship.

@lupin012
lupin012 marked this pull request as ready for review October 26, 2026 10:57
@lupin012
lupin012 requested a review from yperbasis as a code owner October 26, 2026 10:57

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟢 Approval recommended

Static review found no blocking issues in the focused change, and regression coverage addresses the reported failure; tests were not executed.

No open issues 🎉

What changed in this PR

This PR makes eth_callMany and debug_traceCallMany select overridden precompiles using each bundle’s fork rules, while preserving one-time state override application.

Changes:

  • Extracts shared precompile override logic and adds a state-independent ActivePrecompiles helper.
  • Rebuilds the precompile set after each bundle’s block overrides.
  • Adds Prague fork-crossing regression coverage for both methods, with and without state overrides.
File Description
rpc/​jsonrpc/​tracing.go Rebuilds overridden precompiles per tracing bundle.
rpc/​jsonrpc/​eth_callMany.go Selects precompiles using each bundle’s overridden block context.
rpc/​jsonrpc/​eth_callMany_test.go Tests fork-crossing precompile selection in both methods.
rpc/​ethapi/​state_overrides.go Separates precompile override logic from state mutation.

Review effort: Balanced

…compiles_per_bundle

# Conflicts:
#	rpc/ethapi/state_overrides.go
@yperbasis yperbasis added this to the 3.9.0 milestone Oct 27, 2026

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants