---
eip: 8189
title: snap/2 - BAL-Based State Healing
description: Replaces trie node healing with block access list based state catch-up in the snap protocol
author: Toni Wahrstätter (@nerolation), Gary Rong (@rjl493456442)
discussions-to: https://ethereum-magicians.org/t/eip-8189-snap-2-bal-based-state-healing/27953
status: Draft
type: Standards Track
category: Networking
created: 2026-03-06
requires: 7928
---

## Abstract

This EIP upgrades the `snap` protocol from version 1 to version 2, removing `GetTrieNodes` (0x06) / `TrieNodes` (0x07) and adding `GetBlockAccessLists` (0x08) / `BlockAccessLists` (0x09) to enable BAL-based state healing during snap sync.

## Motivation

[EIP-7928](./eip-7928.md) introduces block-level access lists (BALs) that capture all state changes per block, committed to block headers via `block-access-list-hash`. With BALs available, the snap sync healing phase — which iteratively fetches individual Merkle trie nodes via `GetTrieNodes` to resolve state inconsistencies — can be replaced entirely.

Instead of discovering and fetching trie nodes across many round trips, a syncing node downloads BALs for the blocks that advanced during sync and applies state diffs sequentially. Each BAL is verified against its header commitment. The set of blocks to catch up is known upfront, eliminating iterative discovery.

## Specification

### Protocol Version

The protocol version is incremented from `snap/1` to `snap/2`. Peers negotiate the version during the RLPx capability handshake.

### Removed Messages

| Message | ID | Reason |
|---------|----|--------|
| `GetTrieNodes` | 0x06 | Replaced by BAL-based healing |
| `TrieNodes` | 0x07 | Replaced by BAL-based healing |

### New Messages

#### GetBlockAccessLists (0x08)

`[request-id: P, [blockhash₁: B_32, blockhash₂: B_32, ...], bytes: P]`

Request BALs for the given block hashes. `bytes` is the maximum cumulative size of the BAL data the client is willing to receive in a single response. The number of hashes per request is subject to implementation-defined limits.

BALs are only available for blocks after [EIP-7928](./eip-7928.md) activation and within the retention period defined therein.

Notes:

- Nodes **must** always respond to the query.
- If the node does not have the BAL for a requested block hash, it **must** return the RLP empty string (`0x80`) at that position.
- The responding node is allowed to return **fewer** entries than requested (serving the `bytes` soft limit or Quality of Service (QoS) limits), truncating from the tail. Returned entries **must** preserve request order.
- Nodes **should** retain BALs for orphaned (non-canonical) blocks within the retention period defined in [EIP-7928](./eip-7928.md). Since requests are keyed by block hash, orphaned BALs can be served the same way as canonical ones. Retaining them enables syncing nodes to recover from reorgs past the pivot block without restarting sync.

#### BlockAccessLists (0x09)

`[request-id: P, [block-access-list₁, block-access-list₂, ...]]`

Response to `GetBlockAccessLists`. Each element corresponds positionally to a block hash from the request. The RLP empty string (`0x80`) is returned for blocks where the BAL is unavailable.

The responding node **should** respect the `bytes` limit from the request. In the absence of a specific limit, the recommended soft limit for `BlockAccessLists` responses is 2 MiB.

### Unchanged Messages

Messages 0x00–0x05 (`GetAccountRange`, `AccountRange`, `GetStorageRanges`, `StorageRanges`, `GetByteCodes`, `ByteCodes`) are unchanged from snap/1.

### Validation

Received BALs **must** be validated by computing `keccak256(rlp.encode(bal))` and comparing against the `block-access-list-hash` in the corresponding block header. See [EIP-7928](./eip-7928.md) for the BAL encoding format.

### Synchronization Algorithm

snap/2 replaces trie healing with BAL-based catch-up:

1. Download headers and identify the chain head.
2. Pick a pivot block P sufficiently behind the chain head (e.g., HEAD-64) to reduce the likelihood of P being reorged while remaining recent enough that serving peers still hold its state in memory.
3. Bulk download state at P via `GetAccountRange`, `GetStorageRanges`, `GetByteCodes`.
4. If P becomes stale during bulk download (step 3) — i.e., serving peers no longer hold state at P — the syncing node fetches BALs for blocks P+1 through P' via `GetBlockAccessLists` (where P' is the new pivot), applies the diffs locally, and continues bulk download at P'.
5. While state is being downloaded, the chain advances and the pivot switches from P to a new block P+K (typically K is 64). Because serving peers may no longer retain the state at P once the pivot advances, the syncing node fetches BALs for blocks P+1 through P+K via `GetBlockAccessLists`, applies the state diffs locally, and immediately uses P+K as the new sync target. Each BAL is verified against its header hash before application.
6. If the pivot advances further during step 5, repeat for the newly produced blocks.
7. Recompute and verify the state root against the latest header.

If the chain reorgs past the pivot block P, let W be the common ancestor of the old and new canonical chains. The syncing node collects BALs for blocks W+1 through P on the old fork, identifies state entries mutated on the old fork but not on the new fork, deletes those entries locally, and re-fetches them via `GetAccountRange` / `GetStorageRanges`. It then applies BALs from W+1 forward on the new canonical chain and continues sync with a new pivot. If the required orphaned BALs are unavailable, the node must discard state and restart sync.

## Rationale

### Separate Protocol vs. eth/71

This follows the established snap design philosophy. `GetByteCodes` was duplicated from `eth` into `snap` for the same reason:

> *This functionality was duplicated into `snap` from `eth/65` to permit `eth` long term to become a chain maintenance protocol only and move synchronization primitives out into satellite protocols only.*

Synchronization primitives belong in satellite protocols. `snap` is optional and independently versioned; duplicating BAL exchange here avoids coupling sync functionality to `eth`.

### Removing GetTrieNodes

BAL-based healing makes trie node fetching for state reconciliation unnecessary. Removing these messages avoids maintaining two healing mechanisms.

## Backwards Compatibility

This EIP requires rolling out a new protocol version, snap/2. Older clients continue using snap/1. snap/2 is only meaningful for post-Amsterdam blocks, since the `block-access-list-hash` header field ([EIP-7928](./eip-7928.md)) is absent for earlier blocks.

For a node synchronizing data, if both snap/1 and snap/2 are supported, it should use either snap/1 or snap/2 for state sync; running both simultaneously is not recommended. Once the synchronization phase is complete, the node can serve requests for both protocols.

## Security Considerations

### Amplification

A `GetBlockAccessLists` request can trigger responses significantly larger than the request. Implementations **should** apply rate limiting and respect the 2 MiB soft response limit.

### Unavailable Data

Peers returning empty entries for available blocks may be misbehaving or may have pruned data legitimately. Implementations should track peer reliability and deprioritize unreliable peers.

### Application Order

BALs **must** be applied in strict block order with each BAL hash verified before application. Incorrect ordering or wrong-fork BALs produce an invalid state root, detected during the final state root verification.

## Copyright

Copyright and related rights waived via [CC0](../LICENSE.md).
