ethereum.forks.amsterdam.block_access_lists.builder

Implements the Block Access List builder that tracks all account and storage accesses during block execution and constructs the final [BlockAccessList].

The builder follows a two-phase approach:

  1. Collection Phase: During transaction execution, all state accesses are recorded via the tracking functions.

  2. Build Phase: After block execution, the accumulated data is sorted and encoded into the final deterministic format.

[BlockAccessList]: ref:ethereum.forks.amsterdam.block_access_lists.rlp_types.BlockAccessList # noqa: E501

AccountData

Account data stored in the builder during block execution.

This dataclass tracks all changes made to a single account throughout the execution of a block, organized by the type of change and the transaction index where it occurred.

38
@dataclass
class AccountData:

storage_changes

Mapping from storage slot to list of changes made to that slot. Each change includes the transaction index and new value.

48
    storage_changes: Dict[U256, List[StorageChange]] = field(
49
        default_factory=dict
50
    )

storage_reads

Set of storage slots that were read but not modified.

56
    storage_reads: Set[U256] = field(default_factory=set)

balance_changes

List of balance changes for this account, ordered by transaction index.

61
    balance_changes: List[BalanceChange] = field(default_factory=list)

nonce_changes

List of nonce changes for this account, ordered by transaction index.

66
    nonce_changes: List[NonceChange] = field(default_factory=list)

code_changes

List of code changes (contract deployments) for this account, ordered by transaction index.

71
    code_changes: List[CodeChange] = field(default_factory=list)

BlockAccessListBuilder

Builder for constructing [BlockAccessList] efficiently during transaction execution.

The builder accumulates all account and storage accesses during block execution and constructs a deterministic access list. Changes are tracked by address, field type, and transaction index to enable efficient reconstruction of state changes.

[BlockAccessList]: ref:ethereum.forks.amsterdam.block_access_lists.rlp_types.BlockAccessList # noqa: E501

78
@dataclass
class BlockAccessListBuilder:

accounts

Mapping from account address to its tracked changes during block execution.

92
    accounts: Dict[Address, AccountData] = field(default_factory=dict)

ensure_account

Ensure an account exists in the builder's tracking structure.

Creates an empty [AccountData] entry for the given address if it doesn't already exist. This function is idempotent and safe to call multiple times for the same address.

Parameters

builder : The block access list builder instance. address : The account address to ensure exists.

[AccountData] : ref:ethereum.forks.amsterdam.block_access_lists.builder.AccountData

def ensure_account(builder: BlockAccessListBuilder, ​​address: Address) -> None:
99
    """
100
    Ensure an account exists in the builder's tracking structure.
101
102
    Creates an empty [`AccountData`] entry for the given address if it
103
    doesn't already exist. This function is idempotent and safe to call
104
    multiple times for the same address.
105
106
    Parameters
107
    ----------
108
    builder :
109
        The block access list builder instance.
110
    address :
111
        The account address to ensure exists.
112
113
    [`AccountData`] :
114
        ref:ethereum.forks.amsterdam.block_access_lists.builder.AccountData
115
116
    """
117
    if address not in builder.accounts:
118
        builder.accounts[address] = AccountData()

add_storage_write

Add a storage write operation to the block access list.

Records a storage slot modification for a given address at a specific transaction index. If multiple writes occur to the same slot within the same transaction (same block_access_index), only the final value is kept.

Parameters

builder : The block access list builder instance. address : The account address whose storage is being modified. slot : The storage slot being written to. block_access_index : The block access index for this change (0 for pre-execution, 1..n for transactions, n+1 for post-execution). new_value : The new value being written to the storage slot.

def add_storage_write(builder: BlockAccessListBuilder, ​​address: Address, ​​slot: U256, ​​block_access_index: BlockAccessIndex, ​​new_value: U256) -> None:
128
    """
129
    Add a storage write operation to the block access list.
130
131
    Records a storage slot modification for a given address at a specific
132
    transaction index. If multiple writes occur to the same slot within the
133
    same transaction (same block_access_index), only the final value is kept.
134
135
    Parameters
136
    ----------
137
    builder :
138
        The block access list builder instance.
139
    address :
140
        The account address whose storage is being modified.
141
    slot :
142
        The storage slot being written to.
143
    block_access_index :
144
        The block access index for this change (0 for pre-execution,
145
        1..n for transactions, n+1 for post-execution).
146
    new_value :
147
        The new value being written to the storage slot.
148
149
    """
150
    ensure_account(builder, address)
151
152
    if slot not in builder.accounts[address].storage_changes:
153
        builder.accounts[address].storage_changes[slot] = []
154
155
    # Check if there's already an entry with the same block_access_index
156
    # If so, update it with the new value, keeping only the final write
157
    changes = builder.accounts[address].storage_changes[slot]
158
    for i, existing_change in enumerate(changes):
159
        if existing_change.block_access_index == block_access_index:
160
            # Update the existing entry with the new value
161
            changes[i] = StorageChange(
162
                block_access_index=block_access_index, new_value=new_value
163
            )
164
            return
165
166
    # No existing entry found, append new change
167
    change = StorageChange(
168
        block_access_index=block_access_index, new_value=new_value
169
    )
170
    builder.accounts[address].storage_changes[slot].append(change)

add_storage_read

Add a storage read operation to the block access list.

Records that a storage slot was read during execution. Storage slots that are both read and written will only appear in the storage changes list, not in the storage reads list, as per EIP-7928.

Parameters

builder : The block access list builder instance. address : The account address whose storage is being read. slot : The storage slot being read.

def add_storage_read(builder: BlockAccessListBuilder, ​​address: Address, ​​slot: U256) -> None:
176
    """
177
    Add a storage read operation to the block access list.
178
179
    Records that a storage slot was read during execution. Storage slots
180
    that are both read and written will only appear in the storage changes
181
    list, not in the storage reads list, as per [EIP-7928].
182
183
    Parameters
184
    ----------
185
    builder :
186
        The block access list builder instance.
187
    address :
188
        The account address whose storage is being read.
189
    slot :
190
        The storage slot being read.
191
192
    [EIP-7928]: https://eips.ethereum.org/EIPS/eip-7928
193
194
    """
195
    ensure_account(builder, address)
196
    builder.accounts[address].storage_reads.add(slot)

add_balance_change

Add a balance change to the block access list.

Records the post-transaction balance for an account after it has been modified. This includes changes from transfers, gas fees, block rewards, and any other balance-affecting operations.

Parameters

builder : The block access list builder instance. address : The account address whose balance changed. block_access_index : The block access index for this change (0 for pre-execution, 1..n for transactions, n+1 for post-execution). post_balance : The account balance after the change as U256.

def add_balance_change(builder: BlockAccessListBuilder, ​​address: Address, ​​block_access_index: BlockAccessIndex, ​​post_balance: U256) -> None:
205
    """
206
    Add a balance change to the block access list.
207
208
    Records the post-transaction balance for an account after it has been
209
    modified. This includes changes from transfers, gas fees, block rewards,
210
    and any other balance-affecting operations.
211
212
    Parameters
213
    ----------
214
    builder :
215
        The block access list builder instance.
216
    address :
217
        The account address whose balance changed.
218
    block_access_index :
219
        The block access index for this change (0 for pre-execution,
220
        1..n for transactions, n+1 for post-execution).
221
    post_balance :
222
        The account balance after the change as U256.
223
224
    """
225
    ensure_account(builder, address)
226
227
    # Balance value is already U256
228
    balance_value = post_balance
229
230
    # Check if we already have a balance change for this tx_index and update it
231
    # This ensures we only track the final balance per transaction
232
    existing_changes = builder.accounts[address].balance_changes
233
    for i, existing in enumerate(existing_changes):
234
        if existing.block_access_index == block_access_index:
235
            # Update the existing balance change with the new balance
236
            existing_changes[i] = BalanceChange(
237
                block_access_index=block_access_index,
238
                post_balance=balance_value,
239
            )
240
            return
241
242
    # No existing change for this tx_index, add a new one
243
    change = BalanceChange(
244
        block_access_index=block_access_index, post_balance=balance_value
245
    )
246
    builder.accounts[address].balance_changes.append(change)

add_nonce_change

Add a nonce change to the block access list.

Records a nonce increment for an account. This occurs when an EOA sends a transaction or when a contract performs CREATE or CREATE2 operations.

Parameters

builder : The block access list builder instance. address : The account address whose nonce changed. block_access_index : The block access index for this change (0 for pre-execution, 1..n for transactions, n+1 for post-execution). new_nonce : The new nonce value after the change.

def add_nonce_change(builder: BlockAccessListBuilder, ​​address: Address, ​​block_access_index: BlockAccessIndex, ​​new_nonce: U64) -> None:
255
    """
256
    Add a nonce change to the block access list.
257
258
    Records a nonce increment for an account. This occurs when an EOA sends
259
    a transaction or when a contract performs [`CREATE`] or [`CREATE2`]
260
    operations.
261
262
    Parameters
263
    ----------
264
    builder :
265
        The block access list builder instance.
266
    address :
267
        The account address whose nonce changed.
268
    block_access_index :
269
        The block access index for this change (0 for pre-execution,
270
        1..n for transactions, n+1 for post-execution).
271
    new_nonce :
272
        The new nonce value after the change.
273
274
    [`CREATE`]: ref:ethereum.forks.amsterdam.vm.instructions.system.create
275
    [`CREATE2`]: ref:ethereum.forks.amsterdam.vm.instructions.system.create2
276
277
    """
278
    ensure_account(builder, address)
279
280
    # Check if we already have a nonce change for this tx_index and update it
281
    # This ensures we only track the final (highest) nonce per transaction
282
    existing_changes = builder.accounts[address].nonce_changes
283
    for i, existing in enumerate(existing_changes):
284
        if existing.block_access_index == block_access_index:
285
            # Keep the highest nonce value
286
            if new_nonce > existing.new_nonce:
287
                existing_changes[i] = NonceChange(
288
                    block_access_index=block_access_index, new_nonce=new_nonce
289
                )
290
            return
291
292
    # No existing change for this tx_index, add a new one
293
    change = NonceChange(
294
        block_access_index=block_access_index, new_nonce=new_nonce
295
    )
296
    builder.accounts[address].nonce_changes.append(change)

add_code_change

Add a code change to the block access list.

Records contract code deployment or modification. This typically occurs during contract creation via CREATE, CREATE2, or [SETCODE] operations.

Parameters

builder : The block access list builder instance. address : The account address receiving new code. block_access_index : The block access index for this change (0 for pre-execution, 1..n for transactions, n+1 for post-execution). new_code : The deployed contract bytecode.

def add_code_change(builder: BlockAccessListBuilder, ​​address: Address, ​​block_access_index: BlockAccessIndex, ​​new_code: Bytes) -> None:
305
    """
306
    Add a code change to the block access list.
307
308
    Records contract code deployment or modification. This typically occurs
309
    during contract creation via [`CREATE`], [`CREATE2`], or [`SETCODE`]
310
    operations.
311
312
    Parameters
313
    ----------
314
    builder :
315
        The block access list builder instance.
316
    address :
317
        The account address receiving new code.
318
    block_access_index :
319
        The block access index for this change (0 for pre-execution,
320
        1..n for transactions, n+1 for post-execution).
321
    new_code :
322
        The deployed contract bytecode.
323
324
    [`CREATE`]: ref:ethereum.forks.amsterdam.vm.instructions.system.create
325
    [`CREATE2`]: ref:ethereum.forks.amsterdam.vm.instructions.system.create2
326
327
    """
328
    ensure_account(builder, address)
329
330
    # Check if we already have a code change for this block_access_index
331
    # This handles the case of in-transaction selfdestructs where code is
332
    # first deployed and then cleared in the same transaction
333
    existing_changes = builder.accounts[address].code_changes
334
    for i, existing in enumerate(existing_changes):
335
        if existing.block_access_index == block_access_index:
336
            # Replace the existing code change with the new one
337
            # For selfdestructs, this ensures we only record the final state (empty code)
338
            existing_changes[i] = CodeChange(
339
                block_access_index=block_access_index, new_code=new_code
340
            )
341
            return
342
343
    # No existing change for this block_access_index, add a new one
344
    change = CodeChange(
345
        block_access_index=block_access_index, new_code=new_code
346
    )
347
    builder.accounts[address].code_changes.append(change)

add_touched_account

Add an account that was accessed but not modified.

Records that an account was accessed during execution without any state changes. This is used for operations like [EXTCODEHASH], [BALANCE], [EXTCODESIZE], and [EXTCODECOPY] that read account data without modifying it.

Parameters

builder : The block access list builder instance. address : The account address that was accessed.

[EXTCODEHASH] : ref:ethereum.forks.amsterdam.vm.instructions.environment.extcodehash [BALANCE] : ref:ethereum.forks.amsterdam.vm.instructions.environment.balance [EXTCODESIZE] : ref:ethereum.forks.amsterdam.vm.instructions.environment.extcodesize [EXTCODECOPY] : ref:ethereum.forks.amsterdam.vm.instructions.environment.extcodecopy

def add_touched_account(builder: BlockAccessListBuilder, ​​address: Address) -> None:
353
    """
354
    Add an account that was accessed but not modified.
355
356
    Records that an account was accessed during execution without any state
357
    changes. This is used for operations like [`EXTCODEHASH`], [`BALANCE`],
358
    [`EXTCODESIZE`], and [`EXTCODECOPY`] that read account data without
359
    modifying it.
360
361
    Parameters
362
    ----------
363
    builder :
364
        The block access list builder instance.
365
    address :
366
        The account address that was accessed.
367
368
    [`EXTCODEHASH`] :
369
        ref:ethereum.forks.amsterdam.vm.instructions.environment.extcodehash
370
    [`BALANCE`] :
371
        ref:ethereum.forks.amsterdam.vm.instructions.environment.balance
372
    [`EXTCODESIZE`] :
373
        ref:ethereum.forks.amsterdam.vm.instructions.environment.extcodesize
374
    [`EXTCODECOPY`] :
375
        ref:ethereum.forks.amsterdam.vm.instructions.environment.extcodecopy
376
377
    """
378
    ensure_account(builder, address)

_build_from_builder

Build the final [BlockAccessList] from a builder (internal helper).

Constructs a deterministic block access list by sorting all accumulated changes. The resulting list is ordered by:

  1. Account addresses (lexicographically)

  2. Within each account:

    • Storage slots (lexicographically)

    • Transaction indices (numerically) for each change type

Parameters

builder : The block access list builder containing all tracked changes.

Returns

block_access_list : The final sorted and encoded block access list.

[BlockAccessList]: ref:ethereum.forks.amsterdam.block_access_lists.rlp_types.BlockAccessList # noqa: E501

def _build_from_builder(builder: BlockAccessListBuilder) -> BlockAccessList:
384
    """
385
    Build the final [`BlockAccessList`] from a builder (internal helper).
386
387
    Constructs a deterministic block access list by sorting all accumulated
388
    changes. The resulting list is ordered by:
389
390
    1. Account addresses (lexicographically)
391
    2. Within each account:
392
       - Storage slots (lexicographically)
393
       - Transaction indices (numerically) for each change type
394
395
    Parameters
396
    ----------
397
    builder :
398
        The block access list builder containing all tracked changes.
399
400
    Returns
401
    -------
402
    block_access_list :
403
        The final sorted and encoded block access list.
404
405
    [`BlockAccessList`]: ref:ethereum.forks.amsterdam.block_access_lists.rlp_types.BlockAccessList  # noqa: E501
406
407
    """
408
    block_access_list: BlockAccessList = []
409
410
    for address, changes in builder.accounts.items():
411
        storage_changes = []
412
        for slot, slot_changes in changes.storage_changes.items():
413
            sorted_changes = tuple(
414
                sorted(slot_changes, key=lambda x: x.block_access_index)
415
            )
416
            storage_changes.append(
417
                SlotChanges(slot=slot, changes=sorted_changes)
418
            )
419
420
        storage_reads = []
421
        for slot in changes.storage_reads:
422
            if slot not in changes.storage_changes:
423
                storage_reads.append(slot)
424
425
        balance_changes = tuple(
426
            sorted(changes.balance_changes, key=lambda x: x.block_access_index)
427
        )
428
        nonce_changes = tuple(
429
            sorted(changes.nonce_changes, key=lambda x: x.block_access_index)
430
        )
431
        code_changes = tuple(
432
            sorted(changes.code_changes, key=lambda x: x.block_access_index)
433
        )
434
435
        storage_changes.sort(key=lambda x: x.slot)
436
        storage_reads.sort()
437
438
        account_change = AccountChanges(
439
            address=address,
440
            storage_changes=tuple(storage_changes),
441
            storage_reads=tuple(storage_reads),
442
            balance_changes=balance_changes,
443
            nonce_changes=nonce_changes,
444
            code_changes=code_changes,
445
        )
446
447
        block_access_list.append(account_change)
448
449
    block_access_list.sort(key=lambda x: x.address)
450
451
    return block_access_list

build_block_access_list

Build a [BlockAccessList] from a StateChanges frame.

Converts the accumulated state changes from the frame-based architecture into the final deterministic BlockAccessList format.

Parameters

state_changes : The block-level StateChanges frame containing all changes from the block.

Returns

block_access_list : The final sorted and encoded block access list.

[BlockAccessList]: ref:ethereum.forks.amsterdam.block_access_lists.rlp_types.BlockAccessList # noqa: E501 [StateChanges]: ref:ethereum.forks.amsterdam.state_tracker.StateChanges

def build_block_access_list(state_changes: "StateChanges") -> BlockAccessList:
457
    """
458
    Build a [`BlockAccessList`] from a StateChanges frame.
459
460
    Converts the accumulated state changes from the frame-based architecture
461
    into the final deterministic BlockAccessList format.
462
463
    Parameters
464
    ----------
465
    state_changes :
466
        The block-level StateChanges frame containing all changes from the block.
467
468
    Returns
469
    -------
470
    block_access_list :
471
        The final sorted and encoded block access list.
472
473
    [`BlockAccessList`]: ref:ethereum.forks.amsterdam.block_access_lists.rlp_types.BlockAccessList  # noqa: E501
474
    [`StateChanges`]: ref:ethereum.forks.amsterdam.state_tracker.StateChanges
475
476
    """
477
    builder = BlockAccessListBuilder()
478
479
    # Add all touched addresses
480
    for address in state_changes.touched_addresses:
481
        add_touched_account(builder, address)
482
483
    # Add all storage reads
484
    for address, slot in state_changes.storage_reads:
485
        add_storage_read(builder, address, U256(int.from_bytes(slot)))
486
487
    # Add all storage writes
488
    # Net-zero filtering happens at transaction commit time, not here.
489
    # At block level, we track ALL writes at their respective indices.
490
    for (
491
        address,
492
        slot,
493
        block_access_index,
494
    ), value in state_changes.storage_writes.items():
495
        u256_slot = U256(int.from_bytes(slot))
496
        add_storage_write(
497
            builder, address, u256_slot, block_access_index, value
498
        )
499
500
    # Add all balance changes (balance_changes is keyed by (address, index))
501
    for (
502
        address,
503
        block_access_index,
504
    ), new_balance in state_changes.balance_changes.items():
505
        add_balance_change(builder, address, block_access_index, new_balance)
506
507
    # Add all nonce changes
508
    for address, block_access_index, new_nonce in state_changes.nonce_changes:
509
        add_nonce_change(builder, address, block_access_index, new_nonce)
510
511
    # Add all code changes
512
    # Filtering happens at transaction level in eoa_delegation.py
513
    for (
514
        address,
515
        block_access_index,
516
    ), new_code in state_changes.code_changes.items():
517
        add_code_change(builder, address, block_access_index, new_code)
518
519
    return _build_from_builder(builder)