Alert Source Discuss
⚠️ Draft Standards Track: Core

EIP-8347: Offline State Migration to the PBT

Convert state to the PBT off the consensus-critical path, distribute a verifiable snapshot, and swap the commitment at a single fork

Authors Carlos Perez (@CPerezz), Maria Silva (@misilva73), Kevaundray Wedderburn (@kevaundray)
Created 2026-07-23
Discussion Link https://ethereum-magicians.org/t/eip-8347-offline-state-migration-to-the-pbt/29089
Requires EIP-7928, EIP-8159, EIP-8297

Abstract

This EIP specifies an offline migration of Ethereum’s state from the hexary Merkle Patricia Trie (MPT) to the Partitioned Binary Tree (PBT) defined in EIP-8297.

The full state at a finalized anchor block ANCHOR_BLOCK is converted off the consensus-critical path and distributed as a byte-canonical, verifiable PBT snapshot. That snapshot is caught up to the chain tip by replaying Block-Level Access Lists (BALs) (EIP-7928), and made canonical at a single EL+CL hard fork PBT_ACTIVATION_FORK, with both trees maintained through a transition window until that fork is finalized.

This proposal defines ANCHOR_BLOCK, PBT_ACTIVATION_FORK, the PBT snapshot artifact, the dual-check verification procedure, and the shadow-root observability concept.

Motivation

Upgrading Ethereum’s state trie, such as the move to the PBT in EIP-8297, means migrating live mainnet state to a new commitment without splitting the chain. There are two broad ways to do it. The online overlay family (EIP-7748, EIP-7612) converts the state inside consensus while the chain keeps running. This EIP takes the offline path. It converts the state off the consensus-critical path and swaps the commitment at a single fork PBT_ACTIVATION_FORK.

Both paths can be made correct. The choice is a trade between two sets of costs. Going offline buys three things:

  • A smaller consensus change. PBT_ACTIVATION_FORK changes the state commitment and nothing else: at one block, one header field starts being computed from a different tree. No conversion cursor lives in the state, and no MPT traversal order becomes consensus-critical.
  • No hybrid proof verifiers. The canonical state root always commits to the entire state: the MPT root before PBT_ACTIVATION_FORK, the PBT root after. An overlay conversion commits only to the part already converted, so proving a key still held in the base MPT needs a second proof against the frozen MPT root. That cost falls on light clients, on-chain bridge contracts, and consumers of state proofs.
  • No in-consensus conversion for zkEVM provers. Provers prove plain execution before the swap and commit to the PBT root after. They never prove a conversion step.

In exchange it pays two costs:

  • More disk. A node holds both the MPT and the PBT through the transition window, roughly doubling state storage for that period.
  • No on-chain observability of the conversion. Conversion happens off-chain, so consensus does not see whether it was done correctly. This EIP recovers that visibility off consensus, through the shadow-commitment period.

The classic downsides of going offline are distribution trust and catch-up correctness. Both are removed by the dual-check verification and BAL-replay defined below. The trade is worked through in Why offline conversion instead of the online overlay.

Specification

The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “NOT RECOMMENDED”, “MAY”, and “OPTIONAL” in this document are to be interpreted as described in RFC 2119 and RFC 8174.

Parameters

Symbol Meaning Value
ANCHOR_BLOCK Anchor block whose state is converted; MUST be finalized and is identified by block hash TBD, selected during the mainnet window (post-rehearsal)
PBT_ACTIVATION_FORK The EL+CL hard fork at which the PBT becomes the canonical state commitment. This EIP names it but does not schedule it Set by the hard fork meta EIP that includes this proposal, after the shadow-commitment period
REANCHOR_CADENCE Re-anchoring cadence for late joiners, in blocks 50400 (~1 week at 12s slots)

Process overview

The sections below define the pieces of the migration: the preimages, the converter, the PBT snapshot artifact, the dual-check verification, BAL-replay, and the shadow commitment. This section shows how they fit into one lifecycle, which carries the network from an MPT-committed state to a PBT-committed state without conversion ever touching the consensus-critical path. Consensus runs on the MPT through every pre-swap phase; the commitment does not change until PBT_ACTIVATION_FORK.

The migration proceeds in five phases:

  1. Conversion (off-chain). A finalized block is designated as ANCHOR_BLOCK. Conversion produces two artifacts in two steps:
    1. Extract the preimages. The MPT is hash-keyed, so re-deriving its paths needs the raw account addresses and storage-slot keys. Keccak cannot be inverted, so the set comes from a client’s retained preimages, from EIP-7928 BALs, or from chain replay, never from the hashed paths themselves.
    2. Generate the PBT snapshot. The converter reads the state committed by ANCHOR_BLOCK’s stateRoot, together with the preimages, derives PBT keys per EIP-8297, and produces the PBT snapshot: a byte-canonical artifact holding the converted PBT.

    Both steps are pure functions of the state committed by ANCHOR_BLOCK’s stateRoot, so independent producers MUST emit bit-identical output, and both happen entirely off consensus.

  2. Distribution and verification. The PBT snapshot and preimages are published off-chain. Any node obtains them, either by downloading or by self-converting, and MUST run the dual-check verification: rebuild the PBT and confirm the claimed root (internal consistency), then re-hash the leaves under the MPT schema, using the preimages, and confirm they reproduce ANCHOR_BLOCK’s stateRoot (consensus anchoring). This lets a node reject a corrupted or malicious artifact without trusting the distributor, and no release-anchored value is required.

  3. Catch-up. ANCHOR_BLOCK lags the chain tip, and keeps lagging as new blocks are produced. A node brings its verified PBT snapshot forward by BAL-replay: applying the recorded writes of each subsequent block’s Block-Level Access List (EIP-7928) directly to the PBT, with no re-execution. BALs sit outside the block body and are fetched from peers over eth/71 (EIP-8159). Replay MUST outpace block production so the converted state converges to and then tracks the tip, and from here the node maintains the PBT alongside its MPT. To bound the replay gap for nodes joining late, distributors re-anchor on a fixed cadence, which also keeps the range a node has to replay inside the EIP-7928 retention window (see BAL availability).

  4. Shadow-commitment period. Attesters SHOULD publish a shadow root: the PBT root of each block’s post-state, signed with their validator key. Phases 2 and 3 are per-node onboarding, run on each node’s own schedule, and a node starts publishing as soon as its own PBT tracks the tip, so this phase overlaps the others rather than following a network-wide switch. Consensus still runs on the MPT throughout, so the root carries no weight yet; divergence is visible and harmless, and bugs are caught and fixed ahead of the swap rather than after. The length of this period is what builds confidence that the PBT root is correct across clients.

  5. Swap and transition window. At the coordinated EL+CL hard fork PBT_ACTIVATION_FORK, the PBT becomes the canonical state commitment. Nothing else about execution changes. The transition window runs from PBT_ACTIVATION_FORK activation until it is finalized. Through this window, nodes MUST maintain both trees and MUST keep the migration recoverable to the MPT. Only after finality MAY a node dispose of the MPT.

Actors and roles

Conversion and distribution are permissionless and untrusted. Because the conversion output is a pure function of the state committed by ANCHOR_BLOCK’s stateRoot, and because the dual-check verification lets any node reject a bad artifact without trusting its source, the identity of whoever produces or serves the PBT snapshot is irrelevant to correctness. Three roles are involved:

  • Producers run the converter to generate the PBT snapshot and preimages. Any party MAY be a producer: client teams, infrastructure providers, or a node self-converting from its own MPT. Self-conversion is only available to a node that still holds the MPT state at ANCHOR_BLOCK; a node that has pruned it (i.e. ANCHOR_BLOCK falls outside its retained-state window) MUST instead obtain the artifacts by download and run the dual-check. All correct producers emit bit-identical artifacts for a given anchor.
  • Distributors serve the artifacts off-chain over existing infrastructure (snap-sync-style peer-to-peer, era-style files, Content Delivery Networks (CDNs), or torrents). Any party MAY be a distributor; these transports already provide partial-download integrity and resumption, and the dual-check makes the full artifact safe to accept from an untrusted distributor.
  • Consumers are every node, validators included. A consumer MUST obtain the artifacts (by download or self-conversion), run the dual-check, and catch up to the tip by BAL-replay. Validators take on one extra duty, and it is observability-only: attesters SHOULD publish shadow roots, signed with their validator key.

No protocol rule requires validators to produce or serve the conversion. In practice, the network SHOULD rely on at least one reliable, well-known distribution source, so that nodes joining around PBT_ACTIVATION_FORK do not depend on chance availability. That source is expected to run conversion and serving at every re-anchor point through the shadow-commitment period. This is an operational commitment, not a consensus requirement.

Preimages

The MPT is hash-keyed, and most clients do not persist the plain account addresses and storage-slot keys behind the trie paths, so those keys cannot in general be recovered from a client’s own database. The PBT is hash-keyed too: EIP-8297 keys an account’s header stem by key_hash(address), and an overflow storage group by the pair key_hash(address) || key_hash(address || tree_index). A PBT leaf carries no preimage of its own, so holding the PBT does not give a node the keys behind it. Key preimages MUST therefore be distributed alongside the PBT snapshot. They serve two consumers: self-converters running on hash-keyed clients, and verifiers running the consensus-anchoring check, which re-hashes the leaves under the MPT schema and cannot run without them. Preimages MUST be extracted at ANCHOR_BLOCK, so the set corresponds exactly to the state committed by ANCHOR_BLOCK’s stateRoot.

Recovering the full set is the expensive part, and the anchor state alone does not yield it, since Keccak cannot be inverted. A preimage is either retained when the key is first seen, or recovered by replaying the chain. Block-Level Access Lists (EIP-7928) carry plain addresses and slot keys, so they yield preimages for every key touched since BAL activation, but the anchor state also holds long-dormant keys untouched since then. A complete set therefore needs a client-side preimage store or a full replay; BALs shorten the work without removing it.

The preimage file is a concatenation of per-account records with no framing between them. Each record is the RLP encoding of:

[address, [slotKey, slotKey, ...]]

that is, the 20-byte account address followed by the list of that account’s storage-slot keys. An account with no storage carries an empty list. RLP framing makes each record self-delimiting, so the file is parsed by decoding records sequentially until end of input. The file ends immediately after the final record. There is no terminator, padding, or trailing newline, and any trailing byte makes the file invalid.

The file MUST be byte-canonical, so independent producers emit bit-identical output. To achieve this, the encoding and ordering are fixed:

  • address MUST be encoded as exactly 20 bytes, including any leading zero bytes.
  • Each slotKey MUST be encoded as a canonical RLP integer: the 256-bit slot number in big-endian form, with all leading zero bytes removed. Slot 0 encodes as the empty string, slot 1 as a single byte. A leading zero byte makes the record invalid. Most slot keys are mapping-derived and fill all 32 bytes, but low-numbered slots are common and this drops their padding.
  • Records MUST be sorted by address ascending, compared as 20-byte big-endian (byte-lexicographic) values. Each address MUST appear at most once.
  • Within a record, slotKey entries MUST be sorted ascending by slot number, with no duplicates.

A verifier recovers the MPT account path as keccak256(address) and each storage path as keccak256(slotKey), where slotKey is first left-padded back to 32 bytes. This is what makes the consensus-anchoring check against ANCHOR_BLOCK’s header stateRoot possible.

Grouping slot keys under their account is deliberate. A globally deduplicated list would be smaller, but only slightly: the repetition sits in low-numbered slots, while most storage keys are mapping-derived and unique to one account. Grouping buys something better, namely that a converter can stream one account’s storage trie at a time rather than hold a global lookup table in memory.

The converter

The converter is a deterministic function from MPT state to PBT state. Given the state at ANCHOR_BLOCK (i.e., the MPT, the contract bytecode it references, and the preimages), it MUST:

  1. Scan the source (MPT) leaves, recovering each leaf’s full hashed path. A leaf node holds only the tail of that path, so the walk accumulates the rest from the branch indices and extension segments above it. The path is keccak256(address) in the account trie and keccak256(slotKey) in a storage trie, where slotKey is the 32-byte big-endian slot number.
  2. Index the preimages by keccak256(preimage), and require that this index and the set of leaf paths from step 1 match exactly, in both directions. A leaf path with no preimage means the set is incomplete. A preimage matching no leaf path means the set is not the one committed by ANCHOR_BLOCK’s stateRoot. The converter MUST reject either case. Per-leaf hash equality is not a check on its own, since a preimage is only ever found by hashing it. What is enforced is the exact match of the two sets.
  3. Derive PBT keys per EIP-8297 using the preimages. Most of an account’s state lands under its header stem in ACCOUNT_ZONE: basic data, code_hash, storage slots 0 through 63 at sub-indices HEADER_STORAGE_OFFSET..127, and code chunks 0 through 127 at sub-indices CODE_OFFSET..255. Only slots 64 and above take storage-zone keys.
  4. For each account with code, fetch the bytecode from the code store using the account’s code_hash, chunk it per EIP-8297, and emit the resulting code leaves. Chunks 0 through 127 are header-stem leaves of that account, as in step 3. Chunks 128 and above are overflow chunks in CODE_ZONE, content-addressed by code_hash, so accounts sharing bytecode share them: a converter MUST emit each overflow leaf exactly once, since a duplicate key would break byte-canonicality.
  5. Sort leaves by PBT key order (an external merge-sort for mainnet-scale state).
  6. Construct the tree bottom-up in a single sequential pass.

Independent, correct converters operating on the same state at ANCHOR_BLOCK MUST produce a bit-identical PBT root and a bit-identical canonical PBT snapshot. Sorting in PBT-key order lets PBT snapshot ingestion be a sequential bulk-load rather than random inserts.

Step 2 is not redundant with the dual-check. A missing preimage does surface there, as a missing PBT leaf and so a root mismatch, but only at the end of a full rebuild and without naming the key at fault; step 2 names it in one pass. A surplus preimage does not surface there at all: consensus anchoring rebuilds the MPT from the PBT leaves, so an entry matching no leaf is never read and the roots still agree. Step 2 is what holds the preimage file to the exact set committed by ANCHOR_BLOCK’s stateRoot, and so what makes it byte-canonical across producers.

A self-converter’s input MPT state is its own, whose correctness the node already established by executing the chain to ANCHOR_BLOCK and checking its computed state root against the header stateRoot. That input needs no separate provenance check; still holding the state at ANCHOR_BLOCK (see Actors and roles) is the only additional requirement.

PBT snapshot artifact

The conversion result is published as a PBT snapshot with the following properties:

  • Byte-canonical. Serialization is fully specified below, so independent producers MUST emit bit-identical output.
  • Sorted in PBT-key order to enable bulk ingestion.

The artifact is a small fixed header followed by the sorted leaf-record stream:

pbtRoot[32] | leafCount[8, big-endian] | leafRecord * leafCount

pbtRoot is the claimed PBT root, leafCount the number of leaf records, and each leafRecord is encoded as defined under Leaf record below. Both header fields are recomputed and checked by the dual-check.

The snapshot contains only the PBT leaves; it carries no intermediate (inner) tree nodes. Every node reconstructs those locally by hashing the leaves bottom-up during verification. This keeps the artifact minimal and forces each node to derive the tree itself rather than trust distributed inner hashes: the only inner value in the artifact is the claimed pbtRoot, which is recomputed from scratch and checked, never trusted.

The root of trust for the artifact is ANCHOR_BLOCK’s stateRoot, which a node obtains from its own header chain. A client release MAY additionally pin the pbtRoot of the designated swap anchor as a cross-check, but this is optional hardening and cannot cover the intermediate re-anchor snapshots, which are produced after the release ships.

How the artifact is split for transport is left to the distribution layer. The transports in use (snap-sync-style peer-to-peer, era-style files, CDNs, torrents) already provide partial-download integrity, parallelism, and resumption, and a distributor MAY additionally publish a chunk-hash index as a non-normative download accelerator. A distributor MAY also publish the tree’s inner nodes down to a fixed depth, as a second accelerator: the snapshot is sorted in PBT-key order, so a node holding them can hash each completed subtree as it arrives and check it against the published value, catching a peer serving bad leaves during the download rather than at the end of it. Neither accelerator is ever trusted, and neither affects correctness, because the dual-check re-derives the tree from the leaves and authenticates the reassembled artifact against ANCHOR_BLOCK’s stateRoot.

Leaf record

Each leaf of the EIP-8297 PBT is serialized as the RLP encoding of:

[key, value]

where key is the full PBT tree key and value its 32-byte leaf value. As in the preimage file, RLP framing makes each record self-delimiting and the encoding is pinned so that producers agree byte for byte:

  • key MUST be encoded at its full zone-determined length, including any leading zero bytes.
  • value MUST be encoded as a canonical RLP integer: the 32-byte leaf value in big-endian form, with all leading zero bytes removed. A zero value encodes as the empty string. A leading zero byte makes the record invalid. This is where most of the saving is. EIP-8297 packs version, code_size, nonce, and balance into the basic-data leaf, low-magnitude fields first. So a typical account header value carries a long run of leading zero bytes, as do small storage values.

A verifier left-pads value back to 32 bytes before hashing it into the tree; leaf hashing is defined over the full 32-byte value, unchanged from EIP-8297.

The leading zone byte of key fixes its length, so a decoder knows what it is reading and MUST reject a key whose length disagrees with its zone:

Zone byte Zone Key length, zone byte included
0x00 account header (basic data, code_hash, storage slots 0-63, code chunks 0-127) 34
0x01 code, content-addressed overflow chunks only 34
0xFF storage, slots 64 and above only 66

An account or code key is zone[1] | stem[32] | subindex[1], and a storage key is zone[1] | stem[64] | subindex[1], per EIP-8297. A decoder MUST also reject any key in the 0x02-0xFE range, which EIP-8297 reserves and no anchor-state leaf can occupy.

The trailing byte of every key is the sub-index. Two records belong to the same stem if and only if their keys are equal in all bytes except the sub-index. In other words, key[:-1] is identical. Because the PBT snapshot is sorted in PBT-key order, all records sharing a stem are contiguous.

Canonical digests

Both distributed files are byte-canonical, so each has a single digest that every correct producer reproduces for a given anchor:

  • snapshotDigest is keccak256 over the entire PBT snapshot byte stream, header included.
  • preimageDigest is keccak256 over the entire preimage file.

Neither is a root of trust; a node runs the dual-check whatever the digests say and however the bytes arrived. What the digests buy is cheap agreement ahead of an expensive download. Each is 32 bytes, so a node MAY compare them across several independent producers or distributors and decline a source that disagrees, rather than discovering the disagreement after reassembling and rebuilding the whole snapshot.

pbtRoot cannot serve this purpose. It commits to the leaf set rather than to the byte stream, and it can only be recomputed once every leaf is present, so it says nothing about a partial download. For the same reason it does not authenticate the optional chunk-hash index or the optional inner nodes above. A distributor publishing either SHOULD also publish a digest over it, so that it can be cross-checked the same way before it is relied on to localize a fault mid-download.

Verification (dual-check)

Any node MUST be able to verify a downloaded PBT snapshot without trusting the distribution source. This includes a fresh node with no prior state. Because the snapshot carries only leaves and no intermediate nodes, verification is also the step in which each node derives the full tree for itself. To verify, the node performs both checks:

  1. Internal PBT consistency. Rebuild the PBT from the PBT snapshot leaves: derive keys per EIP-8297, compute every intermediate node by hashing bottom-up, and verify that the resulting root matches the claimed PBT root. The intermediate nodes are a product of this step, not part of the distributed artifact.
  2. Consensus anchoring. Re-hash the PBT snapshot leaves under the MPT schema using the distributed preimages and verify the result against block ANCHOR_BLOCK’s header stateRoot. That covers every field the MPT commits: nonce, balance, storage, and code_hash.

    The MPT holds no code, so the re-hash alone leaves the code leaves and code_size unchecked. They are anchored instead to the code_hash it does cover. For each distinct code_hash, reassemble the bytecode from the 31-byte slices of that code’s chunk leaves in chunk order, truncate the result to code_size, and require its keccak256 to equal that code_hash. Then re-chunk the recovered bytecode per EIP-8297 and require the resulting leaves to equal the artifact’s code leaves byte for byte, which pins each chunk’s leading PUSHDATA count as well as its contents and its placement across the header stem and CODE_ZONE. A wrong code_size yields different bytes, so the same preimage pins it. version is the one remaining field the MPT does not commit; EIP-8297 sets it to zero on every header write, so every basic-data leaf MUST carry version == 0.

A PBT snapshot that fails either check MUST be rejected, and the node MUST re-obtain or re-convert.

Without the code limb of check 2, a snapshot carrying corrupted or wholly absent code leaves would pass both checks: internal consistency recomputes the root over whatever leaves it is given, and the MPT re-hash never reads them. A downloading node has no other source of bytecode, since only a self-converter holds a code store.

BAL-replay

State transition from the anchor to the tip is performed by replaying Block-Level Access Lists (EIP-7928) without re-execution. For each block after ANCHOR_BLOCK, per-entry translation rules apply the recorded writes to the PBT:

  • Balance and nonce changes are applied to the account header’s basic-data leaf, and storage writes are applied as PBT leaf writes. Code changes are applied as described under replay deletion semantics.
  • Accounts are almost never deleted, and replay needs no account-deletion marker in the BAL. An account’s nonce only ever increases and cannot be cleared, and every path that would zero its balance bumps that nonce, EIP-7702 delegation included, so a funded account does not silently vanish. EIP-6780 leaves one path open: it confines SELFDESTRUCT to the transaction that created the account, and an address that already holds a balance is still created by CREATE/CREATE2. So a balance-only account present at ANCHOR_BLOCK can be deployed and destroyed in one later transaction, and is then gone from the MPT. Replay reaches that case from the account’s own post-state rather than from a BAL marker, under emptiness below.
  • Replay SHOULD be batched over a range of blocks, coalescing repeated writes to the same key so that each key is written once with its final value. Only the end state of the replayed range is needed, so writing intermediate values is wasted work. Batching MUST keep the replay rate above steady-state block production, so that a converted PBT snapshot converges to the tip and then tracks it.

Replay deletion semantics

Replay MUST preserve one invariant: the PBT obtained by replaying from ANCHOR_BLOCK to a block B MUST be bit-identical to the PBT obtained by converting the MPT state at B directly. Both routes are in use at once. Re-anchoring publishes freshly converted snapshots at intermediate blocks, and the shadow commitment compares roots reported by nodes that reached the same block by different routes. Divergence between the two would look like a conversion bug when it is not one.

The MPT carries no entry for a zero-valued storage slot, no code at all, and no empty account, so holding that invariant forces replay to delete leaves that a direct conversion would never have produced. Deletion is therefore migration-only: it applies from ANCHOR_BLOCK until PBT_ACTIVATION_FORK, and it governs replay, never execution.

  • Storage. A replayed storage write of zero MUST NOT insert a leaf: if a leaf for that key exists it MUST be deleted, and if none exists the write is a no-op. This holds wherever the slot lives, in the account’s header stem for slots 0 through 63 or in the storage zone above that; deleting a header-stem storage leaf never removes the stem, which always retains its basic-data and code_hash leaves. EIP-7928 records the zeroing of a live slot as a write, and an unchanged write to an already-zero slot as a read. So only the first case arises in practice. The second is stated for completeness.
  • Code. A code change MUST recompute the chunk leaves for the new code. Deletion is scoped to the header chunk leaves, which EIP-8297 holds in the account’s own stem at sub-indices CODE_OFFSET..255, covering code chunks 0 through 127: replay MUST delete every header chunk leaf of the previous code that the new code does not cover, since code that shrinks otherwise leaves stale trailing chunks behind. Overflow chunks MUST NOT be deleted. They live in CODE_ZONE, content-addressed by code_hash, so contracts with identical bytecode share them, and removing them would strip code from an account that still runs it. Nor does replay ever need to: deployed code is immutable, since EIP-6780 confines SELFDESTRUCT to the transaction that created the account, and a delegation indicator is 23 bytes, so it occupies chunk 0 and never overflows. Every code change replay sees is therefore either a creation, which only adds leaves, or a delegation write confined to the header stem. Clearing an EIP-7702 delegation is the sharpest case: code returns to empty, so the header chunk leaf at sub-index CODE_OFFSET holding the indicator MUST be deleted, while the code_hash and basic-data leaves are updated rather than deleted, since a codeless account still commits to the Keccak hash of empty bytecode and to a code_size of zero.
  • Emptiness. If, after a block’s writes are applied, an account’s basic-data leaf would hold nonce == 0, balance == 0, and code_size == 0, the account is empty, and EIP-7523 leaves no empty account in the MPT. A direct conversion at that block therefore produces no leaves for it, so replay MUST delete its header stem together with any storage-zone leaves in its bucket, which the zone layout groups under the single shared prefix STORAGE_ZONE || key_hash(address). Its CODE_ZONE overflow chunks are content-addressed and MUST NOT be deleted, as above. This is the only case in which replay removes an account, and the EIP-6780 path above is what reaches it: EIP-7928 records the destroyed account’s balance change to zero but no nonce or code change, and records the storage it touched as reads, so replay never inserted storage for it in the first place. The trigger is exact in both directions, since an empty account is never present in the MPT and a non-empty one is never absent from it.
  • Canonicalization. A BranchNode in EIP-8297 always has two children, so deleting a leaf can leave a branch holding one. Replay MUST then merge the surviving sibling into that branch, the exact inverse of the split EIP-8297 performs on insertion: a leaf sibling replaces the branch outright, and a branch sibling with prefix q replaces it with prefix p || b || q, where p is the removed branch’s prefix and b is the bit that selected the sibling. Deleting the last remaining leaf leaves the empty tree, whose hash is [0x00] * 32. Without this step, two nodes holding the same key/value set would commit to different roots.

At PBT_ACTIVATION_FORK these semantics end. From activation, EIP-8297 governs: writing zero stores a zero-valued leaf, distinct from an absent key, and execution never removes entries from the tree. Nothing is rewritten at the fork; the activated tree is whatever replay produced for the last pre-activation block. So when EIP-8297 says implementations never need delete logic, that holds for execution: the delete-and-merge path above is required by migration replay alone.

Reorg handling

Rollback MUST be performed by discarding state, never by reversing writes. EIP-7928 records post-values only (new_value, post_balance, new_nonce), so a BAL cannot be inverted: undoing a replayed block would require the values it overwrote, and they do not appear in the BAL.

  • While catching up. The node’s PBT head lags the tip. If a reorg’s common ancestor is at or above that head, no replayed block is affected and replay simply continues along the new canonical chain when it reaches the reorganized range.
  • While tracking the tip. A node MUST be able to roll its PBT back to any block within the unfinalized window. It SHOULD do so by holding the PBT of the most recent finalized block as a durable base and treating each block after it as a discardable layer, discarding layers back to the common ancestor on a reorg and replaying the new branch from there. The work is bounded by the reorg depth, which cannot exceed the distance to finality, and replay performs no execution, so recovering that distance is cheap. This is the discipline a client already applies to unfinalized MPT state, so no migration-specific rollback machinery is required.
  • Across an anchor. ANCHOR_BLOCK and every re-anchor point MUST be finalized before their artifacts are produced, so a reorg across an anchor requires a finality violation and sits outside normal operation. Were one to occur, every snapshot anchored at or after the reverted block is invalid, and the migration MUST be re-anchored to a block on the new canonical chain.
  • Across the swap. A reorg spanning PBT_ACTIVATION_FORK rewinds to a pre-activation block, where the canonical commitment is the MPT root again. The PBT for the new pre-activation branch is produced by ordinary replay, and the transition window requires both trees to be maintained precisely so that this stays possible.

Shadow roots MUST be keyed by block hash rather than by height. Two nodes reporting for the same height on opposite sides of a reorg hold different states and correctly report different roots. Keying by height would misread that as divergence.

BAL availability

BALs are not part of the block body, so a node cannot recover them from block data it already holds. A node obtains the BALs it needs for replay from peers over the eth/71 protocol defined in EIP-8159, using GetBlockAccessLists / BlockAccessLists. That protocol is the transport this migration relies on; no migration-specific wire protocol or gossip is introduced.

Replay therefore depends on BALs being available for the range a node actually has to cover, and EIP-8159 lets a peer answer with an empty entry for a block whose BAL it has pruned. Re-anchoring bounds that range: a joining node starts from the most recent published anchor, never an older one, so the required range is at most REANCHOR_CADENCE plus the lag between a re-anchor point and the publication of its artifacts (the point must be finalized first, and conversion takes time).

REANCHOR_CADENCE MUST be chosen so that this bound stays inside the EIP-7928 retention window, which is the weak subjectivity period (3533 epochs, roughly 113000 blocks). At the value specified here, 50400 blocks (~1 week), two full cadences fit inside that window. Ordinary EIP-7928 retention thus already keeps every BAL a node could need available over eth/71, and this EIP imposes no retention requirement of its own.

Re-anchoring

The BAL-replay gap between an anchor and the tip grows without bound as the chain advances, so a node that begins catch-up long after conversion would have to replay the entire elapsed chain. To bound that gap, a distributor re-runs the conversion at successive finalized anchors on a fixed cadence and publishes a fresh snapshot and preimage set for each. Re-anchor points are the blocks whose height is that of ANCHOR_BLOCK plus n * REANCHOR_CADENCE for n = 1, 2, ..., and each MUST be finalized before its artifacts are produced. The cadence also bounds how many BALs a node needs, which is what keeps replay inside the EIP-7928 retention window (see BAL availability).

Each re-anchored snapshot is a self-contained artifact anchored to its own block’s stateRoot, verified by the same dual-check, with no dependency on earlier anchors. A joining node selects the most recent re-anchor at or below finality, verifies it, and BAL-replays only from that anchor forward. Through the shadow-commitment period the reliable distribution source SHOULD run conversion and serving at every re-anchor point, so that a node joining at any time has a recent snapshot to start from.

Shadow commitment

Through the pre-swap period, consensus still runs on the MPT. In that period attesters SHOULD compute the shadow root of each block, the PBT root of that block’s post-state, and publish it signed with their validator key. Conversion correctness then becomes observable per block, in public, and traceable to whoever reported it.

Shadow roots travel in an out-of-consensus telemetry sidecar, and publication is never a block-validity condition. A missing or late shadow root counts against a coverage metric rather than as a divergence, which is what keeps the signal readable. The sidecar is expected to ship enabled by default in consensus-layer clients, so coverage comes from ordinary validator operation rather than from an opt-in program; like the reliable distribution source, that is an operational expectation, not a consensus requirement.

Wire format, aggregation scheme, publication timing, and any EL-to-CL plumbing are out of scope here. They are left to a companion EIP, planned but not yet published; nothing about them is settled elsewhere, and this EIP assumes nothing about them.

Activation

At PBT_ACTIVATION_FORK the PBT becomes the canonical state commitment. The change is confined to which tree produces the value in the existing stateRoot header field.

  • No payload-structure change. No header field is added, removed, reordered, or retyped. stateRoot keeps its name, position, and 32-byte type; before activation it holds the MPT root, and from activation the PBT root computed per EIP-8297.
  • The boundary is a block, not a range. The activation block is the first whose post-state is committed as a PBT root, and its parent’s stateRoot is an MPT root. No block commits to both, and nothing before activation is rewritten.
  • The consensus layer treats the root opaquely. ExecutionPayload.state_root is 32 opaque bytes to the beacon chain, which never interprets or recomputes it, so the consensus-layer change is fork scheduling alone. No new payload field is introduced either, so this EIP requires no engine-API method or version change; whether the fork mints a new version for other reasons is left to the meta EIP that schedules it.
  • Shadow roots end here. From activation the PBT root is the canonical stateRoot, validated by consensus on every block, so the sidecar has nothing left to observe. Attesters SHOULD stop publishing shadow roots at activation, and clients MAY remove the sidecar in a later release.

Transition window

Both the MPT and the PBT MUST be maintained from the activation of the PBT at PBT_ACTIVATION_FORK until PBT_ACTIVATION_FORK is finalized. Only after finality MAY a node dispose of the MPT. Until the activation release deploys, the migration MUST remain fully recoverable to the MPT.

Rationale

Why offline conversion instead of the online overlay

EIP-7748 and EIP-7612 convert the state inside consensus, behind a boundary that moves forward one block at a time. Both that online overlay and this offline design can be made correct, so the choice is about which costs to pay. This EIP takes the offline path. It pays more disk and gives up on-chain observability of the conversion, and in return gets a smaller consensus change, whole-state proofs throughout, and no conversion inside any zkEVM proof. Each of the five points is worked through below.

A smaller consensus change. An overlay’s read rule is simple: try the overlay tree, fall back to the base MPT. The cost is the cursor that drives the conversion to completion, because it lives in the state. EIP-7748 tracks which account is being converted, how far into it the conversion has got, and whether it has finished, advancing by CONVERSION_STRIDE units every block. So every client must agree on the cursor bit for bit at every block, the order of MPT traversal becomes consensus-critical, avoiding stale writes over fresher values becomes a correctness condition that surfaces only as a wrong state root, and the stride has to be chosen for long reorgs, which rewind the cursor with the rest of the state. PBT_ACTIVATION_FORK adds none of that: no conversion state, no per-block schedule, no ordering requirement, just one header field computed from a different tree at one block.

No hybrid proof verifiers. Either way a new tree changes proof formats, and either way the header carries exactly one root, since EIP-7612 also swaps the header state root at its fork. The difference is how much that root covers. Offline, it covers the whole state at every block. During an overlay conversion it covers only the part converted so far, so proving a key still held in the base MPT takes a second proof against the frozen MPT root, which no header carries and which the verifier must pin out of band; proving absence takes both. So an on-chain bridge verifier needs three deployments under an overlay against two switching at a known block: an MPT verifier, a hybrid verifier carrying an out-of-band root, then a PBT verifier. The middle one is the expensive one, the only one that is not a plain tree lookup, built for a bounded window and then maintained anyway.

No in-consensus conversion for zkEVM provers. Block validity proofs are expected to be live by swap time. In an overlay, a prover must prove the conversion steps for every block in the window on top of proving normal execution. Offline conversion never enters a proof: provers keep proving plain MPT execution until PBT_ACTIVATION_FORK, then commit to the PBT root. How large the avoided cost is depends on how heavy a conversion step is to prove and on how mature zkEVM proving is by then, but the point is structural: only the online design pays it at all.

More disk. This is the main cost of going offline. An overlay can delete converted entries from the old tree as it goes, so it stores about one tree throughout. Offline holds both through the transition window, an extra ~300 GB at mainnet scale. That cost is temporary and confined to converting nodes, and holding the MPT is what keeps the migration recoverable until finality (see Transition window). A node that cannot spare the disk can snap-sync the PBT at PBT_ACTIVATION_FORK instead of self-converting.

No on-chain observability of the conversion. This is the second cost. An overlay computes the new root in consensus, so its correctness is observed on-chain as it proceeds; offline conversion is not. This EIP recovers that visibility off consensus, through the shadow-commitment period. The substitute is weaker, because publication is not enforced: what gets observed is coverage over voluntary reports, not a validity condition every node must satisfy. One residual exposure remains, a converter bug confined to the final pre-fork blocks, whose activated PBT root consensus never validates. It is addressed under Unvalidated-flip input.

Why an anchor block and a single fork

Pinning conversion to one finalized block ANCHOR_BLOCK makes the conversion output a pure function of committed state. So independent producers can be required to agree bit-for-bit, and a fresh node can anchor trust in ANCHOR_BLOCK’s stateRoot without trusting any distributor. Collapsing activation into one fork PBT_ACTIVATION_FORK keeps the consensus change small and the swap semantics trivial (commitment only). That is what makes comprehensive rehearsal and full recoverability achievable.

Why BAL-replay for catch-up

The conversion-node lineage this design descends from relied on ad-hoc catch-up messages. Replaying EIP-7928 Block-Level Access Lists instead applies per-block writes directly and needs no re-execution. BALs are already produced for other reasons and are already exchanged between peers by eth/71 (EIP-8159), so catch-up reuses an existing wire protocol instead of adding a migration-specific one. BALs do live outside the block body and are prunable, but re-anchoring keeps the range a node must replay inside the EIP-7928 retention window, so no extra retention rule is needed: see BAL availability.

Replay does need one thing execution does not: the ability to delete a leaf, specified under replay deletion semantics. It follows from the invariant the whole verification story rests on, that converting the MPT at a block and replaying BALs onto an earlier snapshot must land on the same tree. Confining deletion to the pre-activation window keeps it out of the execution path, where EIP-8297’s insert-and-update-only model still holds.

Why shadow roots, and why publication is not a consensus rule

The offline model’s weak point is that consensus does not observe conversion correctness before the swap. Having attesters publish signed per-block PBT roots through the pre-swap period restores that observability: divergence from the expected PBT root becomes visible and attributable while it is still harmless, so it can be caught and fixed before PBT_ACTIVATION_FORK rather than after. Attesters are the right source because they are the set that validates blocks, so the measurement covers the validating majority rather than whoever happens to build, and the validator signature is what makes a single report attributable.

Publication nonetheless stays a SHOULD and out of consensus. Enforcing it as a block-validity rule would make every validator compute the PBT post-state root for every block just to decide whether the block is valid. That puts PBT construction back on the consensus-critical path, the exact property the offline model exists to avoid, and it brings back the two-trees-consensus-live attack surface. Cross-client agreement therefore accumulates by observation rather than by enforcement, at the cost of coverage being a measured quantity instead of a guaranteed one.

That much is settled here; the wire specifics are not, and are left to the planned companion EIP. There is one narrow case where hard enforcement may still be warranted, the final blocks before PBT_ACTIVATION_FORK, discussed under Unvalidated-flip input.

Backwards Compatibility

PBT_ACTIVATION_FORK changes the state commitment only, so it is backwards compatible at the application layer:

  • Contracts. No change. Contracts continue to address storage by 256-bit slot number via SLOAD/SSTORE; PBT key derivation runs inside the client, below the EVM. No Solidity/Yul changes are required.
  • Execution semantics. Gas costs, opcode behavior, and transaction validity are unchanged across the swap itself; the commitment swap alone alters no observable execution result. PBT state access nonetheless performs differently from the MPT (e.g. chunk-granular code access, stem warm/cold behavior), so state-access gas costs are repriced in a separate gas-repricing EIP, planned but not yet published. That repricing must not be bundled into the commitment-only PBT_ACTIVATION_FORK; witness/statelessness pricing is likewise out of scope, as this migration does not introduce statelessness. The repricing fork’s timing depends on benchmarks of PBT versus MPT access performance:
    • If PBT access is more expensive than the MPT, the repricing is a price increase and must activate at a fork before PBT_ACTIVATION_FORK. Running the PBT under the MPT-calibrated (lower) prices would leave the now-costlier operations underpriced, which is a denial-of-service surface.
    • If PBT access is cheaper than the MPT, the repricing is a price decrease and may activate at a fork after PBT_ACTIVATION_FORK, since charging the higher MPT-calibrated prices for cheaper PBT operations only overcharges and carries no denial-of-service risk.

The migration is not transparent to systems that depend on the state structure itself:

  • On-chain proof consumers. The migration removes the per-account storage_root and changes the tree hash. So contracts and services that verify Merkle proofs of Ethereum state (bridges, light-client verifiers, eth_getProof consumers) require coordinated upgrades. This is the longest-lead-time dependency of the migration. No published EIP covers it, and it is expected to be taken up separately.
  • Node operators. Nodes must obtain and verify the PBT snapshot (or self-convert) and must retain both trees through the transition window. BAL retention is unaffected: re-anchoring keeps catch-up inside the existing EIP-7928 window (see BAL availability).

Test Cases

Security Considerations

Unvalidated-flip input

At PBT_ACTIVATION_FORK, the PBT root activated for the final pre-fork block has not itself been validated by consensus. So a converter bug affecting only those final blocks could be activated. There are two mitigations. One is to hard-enforce the shadow root for the final blocks before PBT_ACTIVATION_FORK. That is the one place where making it a validity condition may be worth the cost. The other is to rely on sustained cross-client agreement accumulated over the shadow-commitment period. A correlated all-client conversion bug would be undetectable under either the online or the offline model, so this is not unique to this proposal.

Shadow-root coverage

Because publication is voluntary, how much of the validating set reports depends on the telemetry sidecar shipping enabled by default rather than on enforcement. Readiness assessment must therefore treat coverage as a measured quantity, and read a low-coverage period as weak evidence rather than as evidence of agreement. What is at stake is coverage, not representativeness: the reports come from the set that validates blocks, not only from the parties that build them. Pre-swap divergence itself stays harmless and self-detectable, since consensus still runs on the MPT.

Distribution trust

The PBT snapshot is large (~100+ GB) and distributed off-chain, which in principle is a vector for serving corrupted state. The dual-check neutralizes it: the root of trust is ANCHOR_BLOCK’s stateRoot, taken from the node’s own header chain, so a fabricated but internally self-consistent package (fake leaves matching a fake pbtRoot) fails consensus anchoring, and fabricated code leaves fail its code limb. Integrity checking on partial downloads is left to the distribution transport (e.g. torrent piece hashes, Transport Layer Security (TLS), CDN checksums), letting a node drop a bad segment on arrival rather than after full reassembly; a malicious distributor serving altered bytes that pass those transport checks is still caught once the artifact is reassembled.

Recoverability

A fault discovered during or immediately after the swap is recovered by falling back to the MPT, which the transition window keeps available until PBT_ACTIVATION_FORK is finalized.

Copyright and related rights waived via CC0.

Citation

Please cite this document as:

Carlos Perez (@CPerezz), Maria Silva (@misilva73), Kevaundray Wedderburn (@kevaundray), "EIP-8347: Offline State Migration to the PBT [DRAFT]," Ethereum Improvement Proposals, no. 8347, July 2026. Available: https://eips.ethereum.org/EIPS/eip-8347.