Skip to content

perf(cold-start): eth_createAccessList two-shot warming for fast first boot - #32

Closed
KaiCode2 wants to merge 2 commits into
perf/curve-cold-startfrom
perf/access-list-discovery
Closed

perf(cold-start): eth_createAccessList two-shot warming for fast first boot#32
KaiCode2 wants to merge 2 commits into
perf/curve-cold-startfrom
perf/access-list-discovery

Conversation

@KaiCode2

@KaiCode2 KaiCode2 commented Jul 7, 2026

Copy link
Copy Markdown
Owner

Stacked on #31 (perf/curve-cold-start) — review/merge that first.

Problem

#31 made repeat warms of layout-free pools (Curve/Balancer) fast, but the
first-ever boot of an unknown pool still ran the get_dy / getPoolTokens
discover call in local revm over a cold cache — faulting each SLOAD one-at-a-time
over RPC. That serial faulting dominates a cold first boot.

Change

Two-shot cold warming: derive the read-set with one eth_createAccessList,
bulk-load it through the EvmCache, then run the discover warm (no serial
faulting).

  • Generic cache primitive moved upstream: evm-fork-cache now has a
    cache-owned access-list read-set prewarm primitive on branch
    feat/access-list-read-set-prewarm, commit
    0c9af358d90c87e6452d5b3f1e252822043269f2. This branch is pinned to that
    exact commit until the cache crate publishes the primitive.
  • cold_start_many primes fallback pools whose planner declares a discover
    call — generic over Curve/Balancer and future discover-based adapters, with
    zero new adapter trait surface. The per-pool cold-start then runs warm and
    finalizes as usual.
  • AdapterRegistry::cold_start_primed(pool, cache, policy) is the single-pool
    async entry point. It no longer asks users to pass a separate provider/RPC; it
    uses the provider/fetchers already installed on the EvmCache.
  • Automatic + graceful: default-on, no new API on the happy path. A provider
    without eth_createAccessList, a reverted call, or any per-pool failure
    transparently falls back to local discovery. Opt out with
    with_access_list_discovery(false).
  • No correctness risk: the access list only prewarms; the local discover
    stays authoritative and self-heals an incomplete list.

Measured

Paid endpoint rerun requested in review:

set -a; source /Users/kaialdag/Desktop/dev/egg/crates/evm-amm-state/.env; set +a
CURVE_PHASES_ITERS=5 cargo run --release --example curve_cold_start_phases

Run used the paid Alchemy mainnet E2E_RPC_URL from .env with the example's
gzip-enabled HTTP client. Tricrypto2, block 25_481_590, 5 iterations:

  • discovery cold_start: 717.8 ms median (min..max 608.1..792.6)
  • access-list first boot: 483.6 ms median (min..max 396.4..933.5), 1.5× faster than local discovery
  • verify-only cold_start: 116.9 ms median (min..max 96.4..132.7)
  • cold_start_many known read-set: 110.7 ms median (min..max 75.3..142.2)

The benchmark section in docs/benchmarks.md has been refreshed with these
paid-RPC numbers.

Interop / API notes

  • The gasPrice = pinned block baseFee rule and null-tolerant
    eth_createAccessList response decoding now live in evm-fork-cache, not in
    this AMM crate.
  • The local serde dependency added only for lenient access-list decoding was
    removed.
  • cold_start_many still accepts a provider because its known-read-set path runs
    explicit storage programs; the new access-list discovery path itself uses the
    cache-owned provider/fetcher primitive.

Verification

  • cargo fmt --check
  • cargo check --all-targets
  • cargo clippy --all-targets --no-deps -- -D warnings
  • cargo test --test cold_start_adoption
  • set -a; source /Users/kaialdag/Desktop/dev/egg/crates/evm-amm-state/.env; set +a; cargo test --test access_list_discovery_rpc -- --ignored --nocapture
  • set -a; source /Users/kaialdag/Desktop/dev/egg/crates/evm-amm-state/.env; set +a; CURVE_PHASES_ITERS=5 cargo run --release --example curve_cold_start_phases

KaiCode2 and others added 2 commits July 7, 2026 15:55
…t boot

A layout-free pool's (Curve/Balancer) first cold start ran the get_dy / getPoolTokens discover call in local revm over a cold cache, faulting each SLOAD one-at-a-time over RPC. This adds a two-shot fast path: derive the read-set with one eth_createAccessList, bulk-load it, then run the discover warm (no serial faulting). Measured Tricrypto2 first boot ~728ms -> ~431ms (~1.7x) on a public endpoint; the gap widens on higher-latency providers.

- cold_start_many primes any fallback pool whose planner declares a discover call (generic: Curve/Balancer + any future discover-based adapter) before the per-pool cold-start, which then runs warm. New AdapterRegistry::cold_start_primed is the single-pool async entry (delegates to cold_start_many). Opt out via with_access_list_discovery(false).

- Graceful fallback: unsupported provider / reverted call / per-pool failure -> local discovery. The access list only prewarms; the local discover stays authoritative and self-heals an incomplete list -> no correctness risk.

- Interop fixes: gasPrice = the PINNED block's baseFee (the latest suggested price can sit below a historical block's baseFee); null-tolerant deserialization (geth returns storageKeys: null for touched-but-storageless accounts). Adds serde (already in the graph via alloy/revm).

Tests: offline mock proves priming eliminates serial faults + graceful fallback; env-gated access_list_discovery_rpc asserts primed == local read-set for real 3pool/Tricrypto2; curve_cold_start_phases gains an access-list-first-boot row. 219 tests pass; clippy/doc/all-features clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@KaiCode2
KaiCode2 deleted the branch perf/curve-cold-start July 8, 2026 09:37
@KaiCode2 KaiCode2 closed this Jul 8, 2026
@KaiCode2
KaiCode2 deleted the perf/access-list-discovery branch July 8, 2026 10:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant