ethereum.forks.amsterdam.block_access_lists
Block access lists (BALs), originally defined in EIP-7928, record all accounts and storage locations accessed during block execution along with their post-execution values.
BALs enable parallel disk reads, parallel transaction validation, parallel state root computation, and applying state updates without executing bytecode.
See BlockAccessList for more detail.
StorageChange ¶
In a SlotChanges, represents a single change in an Account's
storage slot.
| 31 | @final |
|---|
| 32 | @slotted_freezable |
|---|
| 33 | @dataclass |
|---|
class StorageChange:
block_access_index¶
Position within the set of all changes in a Block.
| 43 | block_access_index: BlockAccessIndex |
|---|
new_value¶
Value of an Account's storage slot after this change has been applied.
| 50 | new_value: U256 |
|---|
BalanceChange ¶
In a BlockAccessList, represents a change in an Account's
balance.
| 58 | @final |
|---|
| 59 | @slotted_freezable |
|---|
| 60 | @dataclass |
|---|
class BalanceChange:
block_access_index¶
Position within the set of all changes in a Block.
| 70 | block_access_index: BlockAccessIndex |
|---|
post_balance¶
Balance of an Account after this change has been applied.
| 77 | post_balance: U256 |
|---|
NonceChange ¶
In a BlockAccessList, represents a change in an Account's
nonce.
| 85 | @final |
|---|
| 86 | @slotted_freezable |
|---|
| 87 | @dataclass |
|---|
class NonceChange:
block_access_index¶
Position within the set of all changes in a Block.
| 97 | block_access_index: BlockAccessIndex |
|---|
new_nonce¶
Nonce of an Account after this change has been applied.
| 104 | new_nonce: U64 |
|---|
CodeChange ¶
In a BlockAccessList, represents a change in an Account's
code.
| 112 | @final |
|---|
| 113 | @slotted_freezable |
|---|
| 114 | @dataclass |
|---|
class CodeChange:
block_access_index¶
Position within the set of all changes in a Block.
| 124 | block_access_index: BlockAccessIndex |
|---|
new_code¶
Code of an Account after this change has been applied.
| 131 | new_code: Bytes |
|---|
SlotChanges ¶
In a BlockAccessList, represents a change in an Account's
storage.
| 139 | @final |
|---|
| 140 | @slotted_freezable |
|---|
| 141 | @dataclass |
|---|
class SlotChanges:
slot¶
Location within an Account's storage that has been modified.
| 151 | slot: U256 |
|---|
changes¶
Sequence of changes that have been made to one particular storage slot.
| 158 | changes: Tuple[StorageChange, ...] |
|---|
AccountChanges ¶
All changes for a single Account, grouped by field type.
| 164 | @final |
|---|
| 165 | @slotted_freezable |
|---|
| 166 | @dataclass |
|---|
class AccountChanges:
address¶
Address of the account containing these changes.
| 174 | address: Address |
|---|
storage_changes¶
Writes to the storage of the associated Account.
| 179 | storage_changes: Tuple[SlotChanges, ...] |
|---|
storage_reads¶
Storage slots of the associated Account that have been read but not
changed.
| 186 | storage_reads: Tuple[U256, ...] |
|---|
balance_changes¶
Writes to the balance of the associated Account.
| 194 | balance_changes: Tuple[BalanceChange, ...] |
|---|
nonce_changes¶
Writes to the nonce of the associated Account.
| 201 | nonce_changes: Tuple[NonceChange, ...] |
|---|
code_changes¶
Writes to the code of the associated Account.
| 208 | code_changes: Tuple[CodeChange, ...] |
|---|
BlockAccessList¶
List of state changes recorded across a Block.
The hash of a block's access list is included in its Header, though the
access list itself is not included in the block body.
A BlockAccessList includes, for example, the targets of:
BALANCE,EXTCODESIZE,EXTCODECOPY, andEXTCODEHASHinstructions;the call family of instructions even if they revert;
the create family of instructions if the target is accessed;
etc.
| 216 | BlockAccessList: TypeAlias = List[AccountChanges] |
|---|
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.
| 242 | @final |
|---|
| 243 | @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.
| 253 | storage_changes: Dict[U256, List[StorageChange]] = field( |
|---|---|
| 254 | default_factory=dict |
| 255 | ) |
storage_reads¶
Set of storage slots that were read but not modified.
| 261 | storage_reads: Set[U256] = field(default_factory=set) |
|---|
balance_changes¶
List of balance changes for this account, ordered by transaction index.
| 266 | balance_changes: List[BalanceChange] = field(default_factory=list) |
|---|
nonce_changes¶
List of nonce changes for this account, ordered by transaction index.
| 271 | nonce_changes: List[NonceChange] = field(default_factory=list) |
|---|
code_changes¶
List of code changes (contract deployments) for this account, ordered by transaction index.
| 276 | 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.
The builder follows a two-phase approach:
Collection Phase: During transaction execution, all state accesses are recorded via the tracking functions.
Build Phase: After block execution, the accumulated data is sorted and encoded into the final deterministic format.
| 283 | @final |
|---|
| 284 | @dataclass |
|---|
class BlockAccessListBuilder:
block_access_index¶
Current block access index. Set by the caller before each
incorporate_tx_into_block call (0 for system txs, i+1 for the
i-th user tx, N+1 for post-execution operations).
| 305 | block_access_index: BlockAccessIndex = BlockAccessIndex(0) |
|---|
accounts¶
Mapping from account address to its tracked changes during block execution.
| 314 | accounts: Dict[Address, AccountData] = field(default_factory=dict) |
|---|
index_start_accounts¶
Account as it was when a block access index began, captured the first time that index writes it. Several system calls share one index, so their writes are netted against this rather than against each other.
| 319 | index_start_accounts: Dict[ |
|---|---|
| 320 | Tuple[BlockAccessIndex, Address], Optional[Account] |
| 321 | ] = field(default_factory=dict) |
index_start_storage¶
Storage value as it was when a block access index began, captured the first time that index writes the slot.
| 328 | index_start_storage: Dict[ |
|---|---|
| 329 | Tuple[BlockAccessIndex, Address, Bytes32], U256 |
| 330 | ] = 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.
def ensure_account(builder: BlockAccessListBuilder, address: Address) -> None:
| 338 | <snip> |
|---|---|
| 347 | if address not in builder.accounts: |
| 348 | 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.
def add_storage_write(builder: BlockAccessListBuilder, address: Address, slot: U256, block_access_index: BlockAccessIndex, new_value: U256) -> None:
| 358 | <snip> |
|---|---|
| 365 | ensure_account(builder, address) |
| 366 | |
| 367 | if slot not in builder.accounts[address].storage_changes: |
| 368 | builder.accounts[address].storage_changes[slot] = [] |
| 369 | |
| 370 | # Check if there's already an entry with the same block_access_index |
| 371 | # If so, update it with the new value, keeping only the final write |
| 372 | changes = builder.accounts[address].storage_changes[slot] |
| 373 | for i, existing_change in enumerate(changes): |
| 374 | if existing_change.block_access_index == block_access_index: |
| 375 | # Update the existing entry with the new value |
| 376 | changes[i] = StorageChange( |
| 377 | block_access_index=block_access_index, new_value=new_value |
| 378 | ) |
| 379 | return |
| 380 | |
| 381 | # No existing entry found, append new change |
| 382 | change = StorageChange( |
| 383 | block_access_index=block_access_index, new_value=new_value |
| 384 | ) |
| 385 | 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].
def add_storage_read(builder: BlockAccessListBuilder, address: Address, slot: U256) -> None:
| 391 | <snip> |
|---|---|
| 398 | ensure_account(builder, address) |
| 399 | 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.
def add_balance_change(builder: BlockAccessListBuilder, address: Address, block_access_index: BlockAccessIndex, post_balance: U256) -> None:
| 408 | <snip> |
|---|---|
| 415 | ensure_account(builder, address) |
| 416 | |
| 417 | # Balance value is already U256 |
| 418 | balance_value = post_balance |
| 419 | |
| 420 | # Check if we already have a balance change for this tx_index and update it |
| 421 | # This ensures we only track the final balance per transaction |
| 422 | existing_changes = builder.accounts[address].balance_changes |
| 423 | for i, existing in enumerate(existing_changes): |
| 424 | if existing.block_access_index == block_access_index: |
| 425 | # Update the existing balance change with the new balance |
| 426 | existing_changes[i] = BalanceChange( |
| 427 | block_access_index=block_access_index, |
| 428 | post_balance=balance_value, |
| 429 | ) |
| 430 | return |
| 431 | |
| 432 | # No existing change for this tx_index, add a new one |
| 433 | change = BalanceChange( |
| 434 | block_access_index=block_access_index, post_balance=balance_value |
| 435 | ) |
| 436 | builder.accounts[address].balance_changes.append(change) |
add_nonce_change ¶
def add_nonce_change(builder: BlockAccessListBuilder, address: Address, block_access_index: BlockAccessIndex, new_nonce: U64) -> None:
| 445 | <snip> |
|---|---|
| 455 | ensure_account(builder, address) |
| 456 | |
| 457 | # Check if we already have a nonce change for this tx_index and update it |
| 458 | # This ensures we only track the final (highest) nonce per transaction |
| 459 | existing_changes = builder.accounts[address].nonce_changes |
| 460 | for i, existing in enumerate(existing_changes): |
| 461 | if existing.block_access_index == block_access_index: |
| 462 | # Keep the highest nonce value |
| 463 | if new_nonce > existing.new_nonce: |
| 464 | existing_changes[i] = NonceChange( |
| 465 | block_access_index=block_access_index, new_nonce=new_nonce |
| 466 | ) |
| 467 | return |
| 468 | |
| 469 | # No existing change for this tx_index, add a new one |
| 470 | change = NonceChange( |
| 471 | block_access_index=block_access_index, new_nonce=new_nonce |
| 472 | ) |
| 473 | 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
SetCodeTransaction operations.
def add_code_change(builder: BlockAccessListBuilder, address: Address, block_access_index: BlockAccessIndex, new_code: Bytes) -> None:
| 482 | <snip> |
|---|---|
| 493 | ensure_account(builder, address) |
| 494 | |
| 495 | # Check if we already have a code change for this block_access_index |
| 496 | # This handles the case of in-transaction selfdestructs where code is |
| 497 | # first deployed and then cleared in the same transaction |
| 498 | existing_changes = builder.accounts[address].code_changes |
| 499 | for i, existing in enumerate(existing_changes): |
| 500 | if existing.block_access_index == block_access_index: |
| 501 | # Replace the existing code change with the new one |
| 502 | # For selfdestructs, this ensures we only record the final |
| 503 | # state (empty code) |
| 504 | existing_changes[i] = CodeChange( |
| 505 | block_access_index=block_access_index, new_code=new_code |
| 506 | ) |
| 507 | return |
| 508 | |
| 509 | # No existing change for this block_access_index, add a new one |
| 510 | change = CodeChange( |
| 511 | block_access_index=block_access_index, new_code=new_code |
| 512 | ) |
| 513 | builder.accounts[address].code_changes.append(change) |
remove_storage_write ¶
Drop the storage change recorded for a slot at a block access index.
Called when a later write at the same index restores the value the slot held when the index began: nothing changed over the index as a whole, so the slot is left to surface as a read.
def remove_storage_write(builder: BlockAccessListBuilder, address: Address, slot: U256, block_access_index: BlockAccessIndex) -> None:
| 522 | <snip> |
|---|---|
| 529 | if address not in builder.accounts: |
| 530 | return |
| 531 | storage_changes = builder.accounts[address].storage_changes |
| 532 | if slot not in storage_changes: |
| 533 | return |
| 534 | storage_changes[slot] = [ |
| 535 | change |
| 536 | for change in storage_changes[slot] |
| 537 | if change.block_access_index != block_access_index |
| 538 | ] |
| 539 | if not storage_changes[slot]: |
| 540 | del storage_changes[slot] |
remove_balance_change ¶
Drop the balance change recorded for an account at a block access index.
Called when a later write at the same index restores the balance the account held when the index began.
def remove_balance_change(builder: BlockAccessListBuilder, address: Address, block_access_index: BlockAccessIndex) -> None:
| 548 | <snip> |
|---|---|
| 554 | if address not in builder.accounts: |
| 555 | return |
| 556 | account = builder.accounts[address] |
| 557 | account.balance_changes = [ |
| 558 | change |
| 559 | for change in account.balance_changes |
| 560 | if change.block_access_index != block_access_index |
| 561 | ] |
remove_nonce_change ¶
Drop the nonce change recorded for an account at a block access index.
Called when a later write at the same index leaves the nonce where the index found it.
def remove_nonce_change(builder: BlockAccessListBuilder, address: Address, block_access_index: BlockAccessIndex) -> None:
| 569 | <snip> |
|---|---|
| 575 | if address not in builder.accounts: |
| 576 | return |
| 577 | account = builder.accounts[address] |
| 578 | account.nonce_changes = [ |
| 579 | change |
| 580 | for change in account.nonce_changes |
| 581 | if change.block_access_index != block_access_index |
| 582 | ] |
remove_code_change ¶
Drop the code change recorded for an account at a block access index.
Called when a later write at the same index restores the code the account held when the index began.
def remove_code_change(builder: BlockAccessListBuilder, address: Address, block_access_index: BlockAccessIndex) -> None:
| 590 | <snip> |
|---|---|
| 596 | if address not in builder.accounts: |
| 597 | return |
| 598 | account = builder.accounts[address] |
| 599 | account.code_changes = [ |
| 600 | change |
| 601 | for change in account.code_changes |
| 602 | if change.block_access_index != block_access_index |
| 603 | ] |
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.
def add_touched_account(builder: BlockAccessListBuilder, address: Address) -> None:
| 609 | <snip> # noqa: E501 |
|---|---|
| 622 | 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:
Account addresses (lexicographically)
Within each account:
Storage slots (lexicographically)
Transaction indices (numerically) for each change type
Addresses, storage slots, and block access indices are unique. Storage reads that also appear in storage changes are excluded.
def _build_from_builder(builder: BlockAccessListBuilder) -> BlockAccessList:
| 628 | <snip> # noqa: E501 |
|---|---|
| 644 | block_access_list: BlockAccessList = [] |
| 645 | |
| 646 | for address, changes in builder.accounts.items(): |
| 647 | storage_changes = [] |
| 648 | for slot, slot_changes in changes.storage_changes.items(): |
| 649 | sorted_changes = tuple( |
| 650 | sorted(slot_changes, key=lambda x: x.block_access_index) |
| 651 | ) |
| 652 | storage_changes.append( |
| 653 | SlotChanges(slot=slot, changes=sorted_changes) |
| 654 | ) |
| 655 | |
| 656 | storage_reads = [] |
| 657 | for slot in changes.storage_reads: |
| 658 | if slot not in changes.storage_changes: |
| 659 | storage_reads.append(slot) |
| 660 | |
| 661 | balance_changes = tuple( |
| 662 | sorted(changes.balance_changes, key=lambda x: x.block_access_index) |
| 663 | ) |
| 664 | nonce_changes = tuple( |
| 665 | sorted(changes.nonce_changes, key=lambda x: x.block_access_index) |
| 666 | ) |
| 667 | code_changes = tuple( |
| 668 | sorted(changes.code_changes, key=lambda x: x.block_access_index) |
| 669 | ) |
| 670 | |
| 671 | storage_changes.sort(key=lambda x: x.slot) |
| 672 | storage_reads.sort() |
| 673 | |
| 674 | account_change = AccountChanges( |
| 675 | address=address, |
| 676 | storage_changes=tuple(storage_changes), |
| 677 | storage_reads=tuple(storage_reads), |
| 678 | balance_changes=balance_changes, |
| 679 | nonce_changes=nonce_changes, |
| 680 | code_changes=code_changes, |
| 681 | ) |
| 682 | |
| 683 | block_access_list.append(account_change) |
| 684 | |
| 685 | block_access_list.sort(key=lambda x: x.address) |
| 686 | |
| 687 | return block_access_list |
_get_pre_tx_account ¶
Look up an account in cumulative state, falling back to pre_state.
The cumulative account state (pre_tx_accounts) should contain state up
to (but not including) the current transaction.
Returns None if the address does not exist.
_get_pre_tx_storage ¶
Look up a storage value in cumulative state, falling back to pre_state.
Returns 0 if not set, or if the storage at address was wiped
earlier in the block.
def _get_pre_tx_storage(block_state: BlockState, address: Address, key: Bytes32) -> U256:
| 713 | <snip> |
|---|---|
| 719 | if address in block_state.storage_writes: |
| 720 | if key in block_state.storage_writes[address]: |
| 721 | return block_state.storage_writes[address][key] |
| 722 | if address in block_state.storage_clears: |
| 723 | return U256(0) |
| 724 | return block_state.pre_state.get_storage(address, key) |
_index_start_account ¶
Return the account as it was when the current block access index began.
The cumulative block state holds that value until the first write at this index is merged, so it is captured on first use and reused by later writes at the same index.
def _index_start_account(builder: BlockAccessListBuilder, block_state: BlockState, address: Address) -> Optional[Account]:
| 732 | <snip> |
|---|---|
| 739 | key = (builder.block_access_index, address) |
| 740 | if key not in builder.index_start_accounts: |
| 741 | builder.index_start_accounts[key] = _get_pre_tx_account( |
| 742 | block_state.account_writes, block_state.pre_state, address |
| 743 | ) |
| 744 | return builder.index_start_accounts[key] |
_index_start_storage ¶
Return a storage value as it was when the current block access index began, captured on first use in the same way as accounts.
def _index_start_storage(builder: BlockAccessListBuilder, block_state: BlockState, address: Address, key: Bytes32) -> U256:
| 753 | <snip> |
|---|---|
| 757 | index_key = (builder.block_access_index, address, key) |
| 758 | if index_key not in builder.index_start_storage: |
| 759 | builder.index_start_storage[index_key] = _get_pre_tx_storage( |
| 760 | block_state, address, key |
| 761 | ) |
| 762 | return builder.index_start_storage[index_key] |
update_builder_from_tx ¶
Update the BAL builder with changes from a single transaction.
Compare the transaction's writes against the state at the start of the current block access index to extract balance, nonce, code, and storage changes. A write that leaves a value where the index found it records no change, and drops one recorded earlier at the same index by another transaction sharing it, such as a second system call.
Must be called before the transaction's writes are merged into the block state.
def update_builder_from_tx(builder: BlockAccessListBuilder, tx_state: TransactionState) -> None:
| 769 | <snip> |
|---|---|
| 781 | block_state = tx_state.parent |
| 782 | idx = builder.block_access_index |
| 783 | |
| 784 | for address, post_account in tx_state.account_writes.items(): |
| 785 | pre_account = _index_start_account(builder, block_state, address) |
| 786 | |
| 787 | pre_balance = pre_account.balance if pre_account else U256(0) |
| 788 | post_balance = post_account.balance if post_account else U256(0) |
| 789 | if pre_balance != post_balance: |
| 790 | add_balance_change(builder, address, idx, post_balance) |
| 791 | else: |
| 792 | remove_balance_change(builder, address, idx) |
| 793 | |
| 794 | pre_nonce = pre_account.nonce if pre_account else Uint(0) |
| 795 | post_nonce = post_account.nonce if post_account else Uint(0) |
| 796 | if pre_nonce != post_nonce: |
| 797 | add_nonce_change(builder, address, idx, U64(post_nonce)) |
| 798 | else: |
| 799 | remove_nonce_change(builder, address, idx) |
| 800 | |
| 801 | pre_code_hash = ( |
| 802 | pre_account.code_hash if pre_account else EMPTY_CODE_HASH |
| 803 | ) |
| 804 | post_code_hash = ( |
| 805 | post_account.code_hash if post_account else EMPTY_CODE_HASH |
| 806 | ) |
| 807 | if pre_code_hash != post_code_hash: |
| 808 | post_code = get_code(tx_state, post_code_hash) |
| 809 | add_code_change(builder, address, idx, post_code) |
| 810 | else: |
| 811 | remove_code_change(builder, address, idx) |
| 812 | |
| 813 | for address, slots in tx_state.storage_writes.items(): |
| 814 | for key, post_value in slots.items(): |
| 815 | pre_value = _index_start_storage( |
| 816 | builder, block_state, address, key |
| 817 | ) |
| 818 | # Convert slot from internal Bytes32 format to U256 for BAL. |
| 819 | # EIP-7928 uses U256 as it's more space-efficient in RLP. |
| 820 | u256_slot = U256.from_be_bytes(key) |
| 821 | if pre_value != post_value: |
| 822 | add_storage_write(builder, address, u256_slot, idx, post_value) |
| 823 | else: |
| 824 | remove_storage_write(builder, address, u256_slot, idx) |
build_block_access_list ¶
Build a BlockAccessList from the builder and block state.
Feed accumulated reads from the block state into the builder, then produce the final sorted and encoded block access list.
def build_block_access_list(builder: BlockAccessListBuilder, block_state: BlockState) -> BlockAccessList:
| 831 | <snip> # noqa: E501 |
|---|---|
| 839 | # Add storage reads (convert Bytes32 to U256 for BAL encoding) |
| 840 | for address, slot in block_state.storage_reads: |
| 841 | add_storage_read(builder, address, U256.from_be_bytes(slot)) |
| 842 | |
| 843 | # Add touched addresses |
| 844 | for address in block_state.account_reads: |
| 845 | add_touched_account(builder, address) |
| 846 | |
| 847 | return _build_from_builder(builder) |
hash_block_access_list ¶
Compute the hash of a Block Access List.
def hash_block_access_list(block_access_list: BlockAccessList) -> Hash32:
| 853 | <snip> |
|---|---|
| 856 | return keccak256(rlp.encode(block_access_list)) |
validate_block_access_list_gas_limit ¶
Validate that the block access list does not exceed the gas limit.
The total number of items (addresses + unique storage keys) must not
exceed block_gas_limit // GAS_BLOCK_ACCESS_LIST_ITEM.
def validate_block_access_list_gas_limit(block_access_list: BlockAccessList, block_gas_limit: Uint) -> None:
| 863 | <snip> |
|---|---|
| 869 | from .vm.gas import GasCosts |
| 870 | |
| 871 | bal_items = Uint(0) |
| 872 | for account in block_access_list: |
| 873 | # Count each address as one item |
| 874 | bal_items += Uint(1) |
| 875 | |
| 876 | # Collect unique storage keys across both |
| 877 | # reads and writes |
| 878 | unique_slots: Set[U256] = set() |
| 879 | for slot_change in account.storage_changes: |
| 880 | unique_slots.add(slot_change.slot) |
| 881 | for slot in account.storage_reads: |
| 882 | unique_slots.add(slot) |
| 883 | |
| 884 | # Count each unique storage key as one item |
| 885 | bal_items += ulen(unique_slots) |
| 886 | |
| 887 | if bal_items > block_gas_limit // GasCosts.BLOCK_ACCESS_LIST_ITEM: |
| 888 | raise BlockAccessListGasLimitExceededError( |
| 889 | f"Block access list exceeds gas limit, {bal_items} items " |
| 890 | f"exceeds limit of " |
| 891 | f"{block_gas_limit // GasCosts.BLOCK_ACCESS_LIST_ITEM}." |
| 892 | ) |