Skip to content

Random balance-helper addresses amplify upstream RPC reads on Anvil forks #1334

Closed as not planned

Description

@eho-send

Describe the bug

Rundler's balance helper uses a fresh random address for every get_balances call. When Rundler points to a local Anvil fork, applying the helper's code override requires loading that address's original account state. Each new address causes another uncached lookup against the fork's upstream RPC.

In a local Base fork, this contributed to repeated upstream rate-limit errors during chain synchronization and prevented us from verifying user-operation settlement. A 90-second log sample contained 1,324 account/rate-limit failures mentioning 1,237 distinct addresses. These are error-log counts, not a count of underlying upstream RPC requests.

Rundler was connected to local Anvil through a local HTTP proxy. The request path was:

Rundler -> local proxy -> local Base Anvil -> upstream Base RPC for uncached fork state

To reproduce

The following describes the environment where this was observed; a standalone reproduction has not yet been reduced:

  1. Run Base Anvil as a fork at a fixed Base block, with local block production enabled (2-second block time in this run).
  2. Point Rundler v0.11.0 at that local node and exercise chain tracking with watched account balances.
  3. Observe get_balances calls during load_block_summary, especially while chain tracking is catching up.
  4. With an upstream RPC that rate-limits this traffic, observe errors for continually changing helper addresses and repeated chain-sync failures. Successfully loading one address does not warm the next randomly selected address.

The rate-limit threshold is provider-dependent. The fresh-account reads are the underlying amplification concern even when a provider has enough capacity to serve them.

Expected behavior

Repeated balance reads on a local fork should not continually introduce new upstream account lookups solely for disposable helper contracts. Reads of actual uncached chain state remain expected for a fork.

Logs

Sanitized error observed during the run:

-32603: failed to get account for <random helper address>:
server returned an error response:
error code -32097: Rate limit reached

The associated tracing spans included:

rundler_provider::alloy::evm::get_balances
rundler_pool::chain::load_block_summary
load_block_summaries
sync_to_block

Runtime evidence above is from September 7, 2026. The original containers are no longer available for a fresh measurement.

Additional context

  • Rundler: v0.11.0, image digest sha256:ffdc5fca2d5566152af6fe96f88c50a4cf22ce88852bb626c591d3e8ea426178.
  • Base Anvil: ghcr.io/base/base-anvil:main@sha256:68efb5875e8f7c1239b1da3abdec13032a1000970fd1ddb1617efe6d7c16b1e3. The image manifest identifies Base Anvil revision 9df661bc39c6fb96ef28bfd5ff5d345931f133d3, using alloy-evm 0.38.0.
  • Source inspection on September 8, 2026: Rundler's crates/provider/src/alloy/evm.rs is byte-identical between v0.11.0 and main revision f926dd52eeae2a07923b6b2bc5fe1eff2097e2a9.

Relevant implementation:

  • get_balances in Rundler creates Address::random() and supplies a code-only AccountOverride before calling GetBalances. get_gas_used and get_code_hash use the same random-address pattern; the runtime evidence here is specifically from get_balances.
  • Rundler chain tracking loads block summaries concurrently, with a maximum concurrency of 64. Balance-read failures propagate into chain-sync retries, which can amplify the traffic.
  • Base Anvil's call implementation applies the overrides through Alloy's apply_state_overrides.
  • apply_account_override in alloy-evm 0.38.0 unconditionally calls db.basic(account) before applying the supplied fields. Supplying additional override fields alone would therefore not avoid that initial read in this implementation.

This reports an interaction between Rundler's disposable addresses and the fork backend's override handling. No helper-address reuse patch or other proposed fix has been tested.

Activity

  1. github-actions commented on Oct 9, 2026

    @github-actions

    This issue is stale because it has been open 30 days with no activity. Remove stale label or comment or this will be closed in 5 days.

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions