EIP-8188 Last-written block of accounts and slots (Last-Written Block for Accounts and Slots)

Hegota Declined for Inclusion · Scaling & data · EIP status Draft · created 2026-03-10

Records the number of the block in which each account and storage slot was last modified (last_written_block) as consensus state. It is updated only on writes, gas costs do not change, and it becomes a signal that clients can use for storage tiering.

Why it is needed

Ethereum's state, meaning all account balances and contract storage values, keeps growing, but nowhere in the protocol is it recorded when each piece of data was last modified. So node software (clients) relies on its own estimates such as caches and snapshots, and these give different results on different nodes, so they cannot be used as a consensus signal. If the last modified block is recorded at the consensus level, all nodes can identify recently modified active state by the same criterion. Based on this, clients can optimize by placing frequently changing data and long-unchanged data in different storage tiers. The goal is to reduce the cost of operations that repeatedly rewrite state, such as trie updates, state root computation, and compaction. This proposal covers only recording the metadata and does not change gas costs.

What changes

Today

After EIP-8188

Who is affected

In depth

Changes to account and slot encoding

The account encoding changes from RLP([nonce, balance, storageRoot, codeHash]) to RLP([nonce, balance, storageRoot, codeHash, last_written_block]), with a fifth field added. When decoding the existing format that has only 4 elements, last_written_block MUST be treated as 0. A storage slot changes from RLP(value), a single value, to the list RLP([value, last_written_block]). The two formats are distinguished by the RLP prefix byte. A single byte string has a prefix in the 0x80 range and a list in the 0xc0 range, so there is no collision, and slots in the existing format are also decoded with last_written_block = 0. State from before the fork keeps the existing encoding until it is first modified after the fork, so no separate tree transition is needed.

Block number updated only on writes

last_written_block is set to the current block_number when state is modified, and writing again within the same block changes nothing because it already has the same value. There are four slot rules. When SSTORE changes a non-zero value to a different value, both the slot's field and the owning account's field are updated (because storageRoot is recomputed). When a slot is cleared to 0, the slot is removed from the trie and only the account field is updated. Writing the same value again changes nothing. A new slot is created holding the current block number. On the account side, a balance transfer of a non-zero amount updates both the sender and the recipient, and nonce increments and new account creation are also updates. SELFDESTRUCT follows the balance transfer rules of EIP-6780. A contract not created in the same transaction is not removed, so when its balance changes, it updates itself and the recipient account, and if the recipient is itself, the balance does not change and no update is made. A contract created and destroyed in the same transaction is removed from the trie and does not have this field.

Reads do not update, and reverts roll back

Pure reads such as SLOAD, BALANCE, EXTCODESIZE, EXTCODECOPY, EXTCODEHASH, and call instructions that do not change state MUST NOT update last_written_block. There are three reasons. If reads changed the field, every old read would lead to a trie leaf rewrite and a state root recomputation, attaching write-level cost to reads. Also, data in the stable tier would be moved to the mutable tier by reads alone, creating write amplification and contradicting the goal of separating the mutable path. Finally, this field is consensus state reflected in the state root, so if reads changed it, EIP-214's guarantee that state does not change during STATICCALL would be broken. When a call frame reverts, the field is restored to its pre-frame value exactly like balance, nonce, and storage, and updates attached to rolled-back state changes MUST NOT be left behind.

Impact on state size

The field adds 5 bytes per account and 6 bytes per slot. The block number is currently 4 bytes, and RLP adds a 1-byte length prefix (0x84), making it 5 bytes. An account is already a list, so it grows by just those 5 bytes, but a slot wraps a single byte string into a list, so one more byte of list prefix is added, making it 6 bytes. A 4-byte field holds up to 2^32 - 1, about 4.29 billion blocks. At the current 12-second slot it overflows only after about 1,600 years, and even if slots shrink to 6 seconds, after about 800 years. Estimating from the state size in early 2026, 360 million accounts × 5 bytes ≈ 1.8GB and 1.5 billion slots × 6 bytes ≈ 9.0GB, for a total of about 10.8GB. This is a worst-case figure assuming all state is written at least once after the fork, and state not modified after the fork keeps the existing encoding and costs nothing. The original text notes that the actual impact must be quantified with per-client benchmarks.

Link to the EIP-8037 state creation cost

Because this proposal adds bytes to every state entry, it interacts with EIP-8037's per-byte state creation cost. EIP-8037 charges CPSB (cost per state byte) gas for each byte of new state, and its baseline values are STATE_BYTES_PER_NEW_ACCOUNT = 120, the disk size of a new account, and STATE_BYTES_PER_STORAGE_SET = 64 for a new slot. For creation gas to keep reflecting the actual disk growth, the two values SHOULD be raised to 125 and 70 respectively. If they are not raised, new accounts and slots are priced cheaper than their actual encoded size. On the security side, no new write operation is introduced, so the DoS risk stays bounded by the existing write gas rules, but the amount of data a write touches and the witness and proof size of updated entries grow.

What clients can do with this signal

The source describes three uses. First, clients can deterministically separate recently written state from state that has not been written for a long time and place them in different storage tiers. The mutable tier is tuned for write throughput, and the stable tier for compression and read throughput. Second, in the commit path that rehashes and stores the trie paths dirtied by each block, concentrating active state in a narrow key space improves spatial locality, so the per-block write cost scales with the size of the active set rather than the total state size. The effect is the same whether it comes from LSM tree compaction or B-tree copy-on-write page rewrites. Third, on node restart a client can restore its cache by preloading only the recently written entries. None of these is mandatory. The block number is recorded as is because coarser units, such as an update period, can be derived deterministically from a block number, but the reverse is not possible. This allows the metadata to be finalized before the pricing policy is decided, and the encoding does not need to change again if the policy changes later.

Status and Fork Decisions

The account and slot encodings change, which affects trie hashing and state root computation, so adoption requires a hard fork. The actual state tiering that differentiates gas costs is left to a separate proposal that uses this field. This proposal is in Draft status, so the specification may still change. On the fork side, it was removed from consideration for Hegota along with EIP-8358 and EIP-8115 on the 09.10 ACDE #245 call. All three received low ratings.

Original EIP · ← EIPs