Skip to content

Releases: KaiCode2/evm-fork-cache

evm-fork-cache v0.4.0

Choose a tag to compare

@KaiCode2 KaiCode2 released this 05 Sep 15:38
v0.4.0
ab08a57

Promotes the 0.4.0 alpha cache/subscriber API to a normal pre-1.0 release. Includes exact canonical coverage and gap recovery, provider provenance, isolated preconfirmation snapshots, optional bounded raw JSON conversion, and cooperative simulation cancellation. Requires Rust 1.90 and alloy-transport-balancer 0.3.0.

Validation passed: full tests and doctests, feature isolation, Clippy, documentation, optimized benchmarks, scoped dependency/authoring checks, and registry-only packaged-source verification.

The September public Optimism raw-feed qualification did not pass its latency gate: canonical output arrived first in 81 of 100 matched pairs. The raw feature remains opt-in and requires consumer-specific source qualification; this release does not advertise a lead for that provider pair. Details and current resource-probe results are in docs/raw-json-flashblocks-acceptance.md. Dependency advisory scopes are recorded in SECURITY.md.

Published package: https://crates.io/crates/evm-fork-cache/0.4.0

evm-fork-cache v0.3.0

Choose a tag to compare

@KaiCode2 KaiCode2 released this 14 Jul 08:42
v0.3.0
c941ed3

Adds exact-hash prepared cold-start rounds, atomic cache patching, indexed reactive routing, transaction-aware owner reconciliation, and improved bulk storage planning. See CHANGELOG.md for complete notes.

evm-fork-cache 0.2.0

Choose a tag to compare

@KaiCode2 KaiCode2 released this 05 Jul 13:56
v0.2.0
147ecc1

[0.2.0] - 2026-07-05

Closes the top publication-review gaps (account-level freshness, strict
block-context, bounded derived_slots, conservative deep-reorg behavior) and
lands the full Phase-8 liveness plan (spec steps 1–6). Breaking changes are
grouped under Changed; they are permitted pre-1.0 (see the policy above).

Performance

The round's RPC-economics wins (each documented with numbers and a reproduction
path; see the README "Performance & honest trade-offs" section for the full,
hedged picture):

  • Bulk eth_call storage extraction is now the default loader. One
    state-override call reads thousands of slots at a flat ~26 CU instead of one
    point read each — e.g. 10,000 slots in 1 call vs 200,000 CU, and a full
    Uniswap V3 tick range (7,674 slots) in 2 calls / 52 CU (~2,952× cheaper),
    with automatic point-read fallback. Methodology + tables:
    docs/bulk-storage-extraction.md;
    reproduce with RPC_URL=… cargo run --release --example bulk_storage_bench.
  • Verified code-seed cold starts. Materializing a known contract set's code
    and balances is one bulk call settled against on-chain EXTCODEHASH rather
    than three reads per account: ~46× cheaper, ~25.6× faster for 20 contracts
    (48 ms vs ~1.2 s, 26 CU vs 1,200). Same doc + bench (scenario 11).
  • Proof economics. eth_getProof (no bulk substitute) is kept off the
    per-block path: root-gate probes fire on a cadence (default every 16 blocks →
    16× fewer probes by construction) and batch every gated account into one
    bounded fan-out (≈4.7–7.3× over serial across runs, live-measured for a
    50-account sweep — bounded by the concurrency cap, larger when per-proof
    latency is higher; reproduce with E2E_RPC_URL=… cargo test --test liveness_root_gate -- --ignored).

Added

  • Mid-lifecycle adapter register/unregister (ReactiveEngine). A new
    ReactiveEngine binds a ReactiveRuntime to an EventSubscriber and drives
    handler lifecycle as one operation, for consumers that add and drop adapters
    while the cache runs (e.g. register an AMM on a PoolCreated event, drop it
    later). register_handler updates runtime routing and subscriber interests
    together and, once ingestion has journaled a canonical block, backfills the
    new handler from that block automatically — closing the discovery→subscription
    gap with no caller bookkeeping (register_handler_with_backfill for deeper
    history, register_handler_live_only to opt out; sync_handler_interests
    bootstraps a pre-populated runtime). unregister_handler removes routing and
    transport for that handler only; the runtime adds last_canonical_block(),
    pending_resyncs(), cancel_pending_resyncs(address), handler_ids(),
    contains_handler, handler_interests, and unregister_handler for the full
    teardown recipe (unregister + untrack_account + cancel_pending_resyncs;
    cache eviction stays explicit). Under the hood the new
    InterestOwnerSubscriber trait lets a subscriber add/remove interests keyed by
    a stable per-adapter HandlerId without disturbing unrelated owners; growing
    an owner's filter set carries its delivery anchor across the change and
    self-heals the gap, identical filters across owners share one subscription, and
    retired filters release their bookkeeping. SubscriberBackfill describes the
    anchor for owner-scoped get_logs backfill (ReactiveEngineError /
    ReactiveEngineRegisterError are the engine's typed errors). All existing
    runtime/subscriber APIs are unchanged; register_interests remains the
    full-replacement setup path.
  • Queryable cache health + metrics. ReactiveRuntime::health() -> CacheHealth
    (Healthy / Degraded / Unhealthy) and metrics() -> CacheMetricsSnapshot
    (atomic counters: deep_reorgs, reorgs_recovered, resync_requests/_failures,
    missed_ranges, coverage_gaps, pending_contamination). ReactiveReport::Health
    transition reports and a caller-invoked reset_health().
  • Conservative deep-reorg self-heal + missed-range detection. A forward gap in
    the canonical block sequence (block N → N+k) is no longer silently accepted:
    it emits ReactiveReport::MissedBlockRange (+ ResyncReason::MissedBlockRange)
    and escalates health, while still applying the arriving block. Repeated trust-loss
    events (deep reorg beyond the journal, or a missed range) escalate
    Healthy → Degraded → Unhealthy (a "stop until rebuilt" signal).
  • Account/root fetcher seam. EvmCache::account_proof_fetcher /
    set_account_proof_fetcher (AccountProofFetchFn over eth_getProof, returning
    AccountProof); ResyncTarget::Account resyncs now resolve through it
    (materializing so cold accounts are not silently skipped).
  • Bulk storage extraction (bulk_storage module) — now the default storage
    loader.
    Every provider-backed cache ships a StorageBatchFetchFn that
    loads thousands of slots per eth_call by overriding target code with
    Dedaub's 23-byte extractor
    (storage survives a code override), plus a Multicall3-dispatch path that
    spans many contracts in one call — with the classic point-read fetcher as
    automatic fallback (tiny requests, providers without override support —
    which also latch the fetcher to point reads after consecutive failures —
    and precompile targets). Public surface:
    bulk_call_storage_fetcher[_with_fallback], the async core
    fetch_slots_bulk, BulkCallConfig (chunking, concurrency,
    CallDispatch::CallMany for Erigon-lineage endpoints where one 20-CU
    eth_callMany request carries the whole batch), planned_call_count,
    StorageFetchStrategy + EvmCacheBuilder::{storage_fetch_strategy, bulk_call_config} (opt-out / tuning), point_read_storage_fetcher (the
    extracted classic path), and both Shanghai (PUSH0) and pre-Shanghai
    extractor variants — all executed against revm in the offline test suite.
    Measured on Alchemy: 10,000 slots in one 26-CU call (vs 200,000 CU as point
    reads); a full Uniswap V3 pool tick range — 7,674 slots — in 2 calls /
    52 CU; 100 contracts × 30 slots in one call
    (benchmarks + limitations).
    StorageFetchError and RuntimeError now derive Clone so one
    chunk-level failure can be reported per affected slot.
  • Custom storage programs + companion extractors + prewarm.
    StorageProgram / run_storage_program[s] inject caller-supplied bytecode
    via the same override transport, enabling data-dependent extraction
    derived in-EVM (a worked one-shot Uniswap V3 observation-ring loader — zero
    calldata — ships in the benchmark example and revm tests);
    fetch_account_fields_bulk (BALANCE + EXTCODEHASH for many accounts in
    one call) and fetch_block_context (seven env words in one call) ride the
    same mechanism; EvmCache::prewarm_slots bulk-loads a declared working set
    into layer 2 through the installed fetcher, returning a PrewarmReport.
  • Verified code seeding & local etch. Adapters can push runtime bytecode
    into the cache instead of paying an eth_getCode (plus the lazy backend's
    balance/nonce round trips) per address, with per-address trust marks
    (CodeSeedState; absence of a mark = RPC-origin) persisted to
    code_seeds.bin — saved before bytecodes.bin and pruned on load, so an
    unverified claim can never masquerade as chain-fetched across restarts.
    seed_account_code[_with] records a canonical claim (Pending);
    verify_code_seeds() settles the whole pending set against on-chain
    EXTCODEHASH in one eth_call through the new AccountFieldsFetchFn
    seam (default-wired to fetch_account_fields_bulk), marking matches
    Verified durably (real balance patched from the same response) and
    purging contradicted claims for refetch — CodeVerifyReport buckets
    mismatched / not_deployed / codeless / unverifiable (the last is
    fail-safe: transport failures keep seeds Pending). Chain-fetched code
    beats templates: an equal-hash seed over RPC-origin code verifies
    instantly with zero RPC; a conflicting one errors
    (CacheError::CodeSeedConflict) without touching the cache.
    etch_account_code is the raw-bytes deliberate divergence sibling
    (Etched, never verified); override_account_code* and deploy_contract
    targets now also record Etched, making etched_accounts() the single
    local-divergence health surface. The cold-start driver gained a
    verify_code phase that runs first — no discover sim executes over an
    unverified claim — recording ColdStartResults.code_verifications and
    guarded by ColdStartError::NoAccountFieldsFetcher for pending-bearing
    rounds. Spec: docs/verified-code-seeding-spec.md.
  • Strict block context + engine-driven env refresh. BlockContextRequirements
    (strict/lenient/per-field) + BlockContextError;
    EvmCacheBuilder::strict_block_context / block_context_requirements and a
    fallible try_build (EvmCache::new stays infallible + lenient);
    EvmCache::advance_block(header) refreshes the full block env
    (number/basefee/coinbase/prevrandao/gas-limit/timestamp) with the same strict
    validation, driven from the reactive canonical-header path; new
    coinbase() / prevrandao() / block_gas_limit() getters.
  • Validity stamping (opt-in). ReactiveRuntime::enable_freshness_stamping()
    stamps canonical event-derived writes Validity::ValidThrough(N) so
    event-maintained slots stop being needlessly re-verified.
  • storageHash root gate + TrackingPolicy + RootGateCadence.
    TrackingPolicy (Slots / WholeAccount / Scalars) + track_account;
    the root gate emits ReactiveReport::CoverageGap (+
    ResyncReason::RootMoved) when a tracked account's storage root moves with
    no covering decoder, and Scalars tracks native balance/nonce/code (which
    do not move the storage...
Read more