Skip to content

Access Lists

Source: docs/design/access-lists.md

Status: Draft Issue: chain-5alwd Date: 2026-02-03

Access lists let a transaction declare which accounts and storage keys it expects to touch. The chain can use these hints to prefetch state, charge lower gas for declared (warm) keys, and improve parallel scheduling. The current codebase already contains AccessList types, validation rules, and strict-access checks behind a feature flag. This document formalizes the semantics and activation plan.

  1. Reduce disk I/O and latency by prefetching declared state.
  2. Provide a deterministic warm/cold gas model for declared accesses.
  3. Enable strict access enforcement (reject undeclared accesses) when activated via a hard fork.
  4. Improve parallel execution scheduling with declared conflicts.
  5. Keep transaction format stable (reuse TxBodyV1.access_list).
  • Full stateless execution or witness generation (future work).
  • Automatic access list inference inside the VM (client-side tooling only).
  • Unbounded range/prefix expansion that can DoS validators.
  • TxBodyV1 includes access_list: AccessList and it is part of the signed payload.
  • AccessList::validate() enforces size and descriptor rules.
  • TracedStateBackend can track touched keys and enforce strict access (ASHEN_STRICT_ACCESS).
  • state_keys_from_access_list derives a concrete key set for prefetch.
  • prefetch_access_list_keys issues read requests to warm the hot cache.
  • Gas constants for access list accounting exist in src/core/execution/gas.rs.

No new transaction type is introduced. We keep TxBodyV1.access_list and formalize its semantics:

  • accounts: declares per-account metadata accesses (balance, nonce, code, metadata) for each address.
  • storage: declares storage descriptors per contract address.
  • AccessDescriptor variants:
    • Exact: a specific 32-byte key.
    • Prefix: a prefix (1-32 bytes).
    • Range: inclusive/exclusive bounds.
  • AccessMode indicates Read / Write / ReadWrite intent for scheduling and enforcement policies.

Versioning note: we do not add an access-list version or format flag in v1. Because the chain is greenfield (no DEPLOYED flag), we can adjust TxBodyV1 pre-launch without adding a version flag. After deployment, incompatible changes must use a new TxBody::V2.

Validation happens at admission time and prior to execution:

  1. AccessList::validate() must pass or the tx is rejected with InvalidAccessList.
  2. All addresses referenced by accounts and storage entries must exist in state at admission time, except when the transaction is explicitly creating the address (deploy or first-fund transfer).
  3. If strict access is enabled, any touched key not covered by the access list causes execution to revert with UndeclaredStateAccess.
  4. Prefix/range descriptors are valid for access checks and scheduling, and they should also drive bounded prefetch scans.

The gas model has three parts:

  1. Declaration cost: charge per declared account and descriptor to prevent DoS via huge access lists.
  2. Warm vs cold storage: accesses covered by the access list are charged at warm rates; undeclared keys are charged at cold rates with a punitive multiplier to discourage missing access lists.
  3. Prefetch accounting: prefetching declared keys incurs a small per-key cost (observational today, enforceable after activation).

Descriptor pricing guidance:

  • Exact descriptors: 2_000 gas per descriptor.
  • Prefix / Range: 3_000 gas per descriptor (slightly higher than Exact).
  • Warm access for Exact uses the warm schedule from gas-v1.
  • Warm access for Prefix / Range uses warm * 1.25 (rounded up), still below cold pricing.
  • Undeclared access uses cold pricing with a punitive multiplier of 2.0.

These are starting values; tune after benchmarks.

Activation plan:

  • Phase 0 (current): log-only access list gas and prefetch costs.
  • Phase 1: enforce declaration costs and warm/cold price distinction.
  • Phase 2: enable strict access rejection for undeclared keys.

Prefetching should be opportunistic and bounded:

  • Use state_keys_from_access_list to derive concrete keys.
  • Prefetch should also honor Prefix and Range descriptors via bounded scans (limit by keys or bytes). Truncate when the per-tx budget is hit.
  • Prefetch at block construction time so the block builder pays for the I/O and can price it via gas.
  • Prefetching should be disabled when the backend lacks a hot cache.
  • Cap prefetch per transaction to prevent a single tx from exhausting I/O. Initial defaults:
    • max_prefetch_keys_per_tx = 512
    • max_prefetch_bytes_per_tx = 256 KiB
    • max_prefetch_scan_keys_per_descriptor = 64
  • Prefetch should warm the hot cache (CachedJournalStateBackend).
  • If the cache is full, respect its eviction policy (LRU) and never bypass capacity limits.
  • Consider cache TTL / height-based expiry for prefetched keys to avoid long-lived cache pollution.
  • When chain-5hsc lands, prefetch can target the cached backend directly and rely on its size/eviction rules.

Clients should be able to request access list generation during simulation:

  • SimulationOptions.generate_access_list = true triggers tracing of touched keys and returns a GeneratedAccessList with an estimated gas savings.
  • Expose this via an RPC method analogous to eth_createAccessList, e.g. tx_createAccessList.
  • The access list is advisory; signing clients decide whether to include it. The RPC response should include the estimated gas savings.

Declared access lists enable scheduler optimizations:

  • Use descriptor overlap checks to precompute conflicts.
  • Prefer scheduling non-overlapping transactions in parallel.
  • AccessMode can inform read-only vs write conflicts.

This integrates with the Block-STM design doc (docs/design/block-stm.md).

Access list enforcement is consensus-critical and must be activated at a specific block height:

  1. Add a access_list_activation_height config in consensus parameters.
  2. Because the chain is not live, set this at launch time (TBD).
  3. After activation: declaration costs + warm/cold pricing enforced.
  4. Strict access rejection can be enabled later (separate height or config).
  1. Finalize activation height(s) once launch planning is fixed.
  2. Confirm the initial prefetch caps after a quick I/O benchmark pass.
  3. Decide whether strict access should be a separate hard fork or a config toggle post-launch.
  1. docs/design/access-lists.md (this document)
  2. Transaction format notes for access list semantics
  3. Gas schedule delta for warm/cold pricing and declaration costs
  4. RPC spec for access list generation and return type
  5. Activation plan (block height and phased rollout)