Skip to main content

Module warm_code_cache

Module warm_code_cache 

Source
Expand description

Cross-block warm cache for immutable/long-TTL account data (bytecode + account existence) — the persistent layer underneath the per-block CacheDB. Caches basic_ref + code_by_hash_ref with a per-entry TTL; forwards storage_ref + block_hash_ref untouched. Cross-block warm cache for immutable/long-TTL account data — bytecode + account existence (the basic_ref + code_by_hash_ref rows). Sits underneath the per-block CacheDB in the BlockSimHandle DB stack so the contract code-load RPCs stop repeating every block while the per-block CacheDB (overrides + mutable storage, fresh each block, drops at EOB) is preserved.

§What this caches (the staleness line)

Only the immutable rows:

  • basic_ref — AccountInfo FOR CONTRACTS ONLY. An EOA’s nonce/balance mutate every block, so EOA rows (empty code hash) are never cached — they fall through to the per-block CacheDB + RPC path fresh at each block build. (A cached contract’s AccountInfo byte-RARELY mutates: contract nonces advance only via CREATE*; a stale contract nonce does not gate frame-replay validation the way a sender nonce does, and the TTL refresh bounds that edge.)
  • code_by_hash_ref — Bytecode keyed by code_hash (immutable by hash).

storage_ref + block_hash_ref are forwarded UNCHECKED to the inner Db (the mutable-per-block row — V2 reserves slot 8 / V3 slot0+ticks change every block there’s a swap; the chain block hash is per-chain, not per-block-snapshot). Caching these would re-introduce the K-invariant / LOK stale-state-divergence reverts the option-B path hit.

§The TTL (a safety net, not the primary correctness mechanism)

Each entry records the block it was loaded at; an entry read at block B is a miss when B - loaded_block > WARM_CODE_CACHE_TTL_BLOCKS. A re-fetch on miss re-caches with loaded_block = B.

In the bot’s domain every cached contract is immutable (EIP-1967 proxies upgrade via the implementation storage slot — uncached; the injected executor is per-block via stateOverrides, never read here). The TTL is a belt-and-suspenders net against:

  • EIP-6780-disabled selfdestruct edge cases (pre-Cancun metamorphic CREATE2-redeploy-to-same-address: a contract that selfdestructs then redeploys with different bytecode at the same address would be stale). EIP-6780 (Cancun, mainnet since March 2024) disables code-deletion on selfdestruct unless the contract was created in the same tx — so the vector is effectively closed for any persistent contract.
  • Future protocol changes to code mutability.

§Composition

CacheDB (per-block: overrides + mutable storage, fresh each block, drops at EOB)
  → WarmCodeCache (persistent: bytecode + account-existence, per-entry TTL'd)
       ↳ holds Arc<RwLock<WarmCodeCacheInner>> cloned from the engine owner
       ↳ the WarmCodeCache<Db> wrapper value itself is per-block
   → BotStateDb (forwarding storage fallback)
     → WrapDatabaseAsync<AlloyDB> (RPC cold-miss)

§Ownership

The wrapper value (WarmCodeCache<Db>) is per-block — constructed in BlockSimHandle::build, holds the per-block Db, drops at end of block. Only the inner map (Arc<RwLock<WarmCodeCacheInner>>) persists across blocks — cloned from the engine owner (PyArbitrageEngine / the standalone Rust Bot). This preserves the per-block CacheDB discipline while sharing the bytecode cache across blocks.

Structs§

WarmCodeCache
A cross-block warm DatabaseRef wrapper — caches basic_ref + code_by_hash_ref (the immutable rows) with a per-entry TTL, forwards storage_ref + block_hash_ref to the inner Db (the mutable-per-block row, never cached).
WarmCodeCacheInner
The cross-block persistent inner state — bytecode + account-existence maps, each entry tagged with the block it was loaded at for per-entry TTL expiry.

Constants§

WARM_CODE_CACHE_TTL_BLOCKS
The default per-entry TTL in blocks — ≈ 1 day of mainnet blocks (12s block time → ~7200 blocks/hour → ~10 000 ≈ 1.4 days).