Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/ethereum/forks/amsterdam/block_access_lists/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
add_storage_write,
add_touched_account,
build_block_access_list,
validate_block_access_list_gas_limit,
)
from .rlp_utils import (
compute_block_access_list_hash,
Expand All @@ -28,4 +29,5 @@
"build_block_access_list",
"compute_block_access_list_hash",
"rlp_encode_block_access_list",
"validate_block_access_list_gas_limit",
]
57 changes: 56 additions & 1 deletion src/ethereum/forks/amsterdam/block_access_lists/builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,11 @@
from typing import TYPE_CHECKING, Dict, List, Set

from ethereum_types.bytes import Bytes
from ethereum_types.numeric import U64, U256
from ethereum_types.numeric import U64, U256, Uint

from ..exceptions import BlockAccessListGasLimitExceededError
from ..fork_types import Address
from ..transactions import TX_ACCESS_LIST_STORAGE_KEY_COST, TX_BASE_COST
from .rlp_types import (
AccountChanges,
BalanceChange,
Expand Down Expand Up @@ -517,3 +519,56 @@ def build_block_access_list(
add_code_change(builder, address, block_access_index, new_code)

return _build_from_builder(builder)


def validate_block_access_list_gas_limit(
block_access_list: BlockAccessList,
block_gas_limit: Uint,
tx_count: int,
) -> None:
"""
Validate that the block access list does not exceed the gas limit.

The constraint is:
``bal_items * ITEM_COST <= available_gas + system_allowance``

Parameters
----------
block_access_list :
The block access list to validate.
block_gas_limit :
The gas limit of the block.
tx_count :
The number of transactions in the block.

Raises
------
BlockAccessListGasLimitExceededError :
If the block access list gas cost exceeds the available gas.

"""
from ..fork import (
MAX_CONSOLIDATION_REQUESTS_PER_BLOCK,
MAX_WITHDRAWAL_REQUESTS_PER_BLOCK,
)
from ..vm.gas import GAS_WARM_ACCESS

total_storage_reads = sum(
len(account.storage_reads) for account in block_access_list
)
total_addresses = len(block_access_list)
bal_items = Uint(total_storage_reads + total_addresses)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

total_storage_reads cost TX_ACCESS_LIST_STORAGE_KEY_COST per item, and for address the param respectively to that for addresses (which is higher). The cost is thus underestimated here (which is fine, because overestimating cost will invalidate blocks faster)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah right - it's a trade-off between simplicity and accuracy. Since this is mainly needed for early rejections of BALs that have way too many items (e.g. 2x what is realistic; those would cause 2x the work which we aren't accounting for when setting the gas limit), to counter DoS, I tend leaning towards simplicity first.


item_cost = GAS_WARM_ACCESS + TX_ACCESS_LIST_STORAGE_KEY_COST
available_gas = block_gas_limit - Uint(tx_count) * TX_BASE_COST
system_allowance = (
Uint(15)
+ Uint(3)
* (
MAX_WITHDRAWAL_REQUESTS_PER_BLOCK
+ MAX_CONSOLIDATION_REQUESTS_PER_BLOCK
)
) * item_cost

if bal_items * item_cost > available_gas + system_allowance:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This assumes that all bal_items have a cost, but there are also free items, like the sender and receiver address. This will flag BALs which are valid as "too full"

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should also add tests with BALs near max-size (I think we already have this) to check if clients don't early-reject those because they are too large. We cant create a BAL which has the max size as the hard cap calculation makes some assumptions which are not true in practice (SSTORE/SLOAD without pushing items to stack for instance)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah good point!
I wouldn't say you get the sender and the receiver for free though - there's still the 21k gas costs which is above the 2000. The only truly "free" items are those from system contracts, if I'm not forgetting something here.

We should also add tests with BALs near max-size (I think we already have this) to check if clients don't early-reject those because they are too large. We cant create a BAL which has the max size as the hard cap calculation makes some assumptions which are not true in practice (SSTORE/SLOAD without pushing items to stack for instance)

The goal here was to not create any new practical constrain for block building. This means, as long as there is gas available during builders, we must ensure that the BAL item size isn't hit yet -> thus the very conservate 2000 gas per item + not having the system contract items impact it.
Re tests, yeah, we should definitely have test for this, e.g. create a block that is valid (thus it must obey the max item limit too) but as well have tests with invalid BALs that are slightly over the max-items to make sure those are rejected early on.

raise BlockAccessListGasLimitExceededError
10 changes: 10 additions & 0 deletions src/ethereum/forks/amsterdam/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -129,3 +129,13 @@ class TransactionGasLimitExceededError(InvalidTransaction):
Note that this is _not_ the exception thrown when bytecode execution runs
out of gas.
"""


class BlockAccessListGasLimitExceededError(Exception):
"""
The block access list exceeds the gas limit constraint.
Introduced in [EIP-7928].
[EIP-7928]: https://eips.ethereum.org/EIPS/eip-7928
"""
14 changes: 13 additions & 1 deletion src/ethereum/forks/amsterdam/fork.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,10 @@
)

from . import vm
from .block_access_lists.builder import build_block_access_list
from .block_access_lists.builder import (
build_block_access_list,
validate_block_access_list_gas_limit,
)
from .block_access_lists.rlp_utils import compute_block_access_list_hash
from .blocks import Block, Header, Log, Receipt, Withdrawal, encode_receipt
from .bloom import logs_bloom
Expand Down Expand Up @@ -125,6 +128,8 @@
HISTORY_STORAGE_ADDRESS = hex_to_address(
"0x0000F90827F1C53a10cb7A02335B175320002935"
)
MAX_WITHDRAWAL_REQUESTS_PER_BLOCK = Uint(16)
MAX_CONSOLIDATION_REQUESTS_PER_BLOCK = Uint(2)
MAX_BLOCK_SIZE = 10_485_760
SAFETY_MARGIN = 2_097_152
MAX_RLP_BLOCK_SIZE = MAX_BLOCK_SIZE - SAFETY_MARGIN
Expand Down Expand Up @@ -834,6 +839,13 @@ def apply_body(
block_env.state_changes
)

# Validate block access list gas limit constraint (EIP-7928)
validate_block_access_list_gas_limit(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should the t8n tool be updated to reflect this as well?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, we likely need to add this here so we can test this appropriately.

block_access_list=block_output.block_access_list,
block_gas_limit=block_env.block_gas_limit,
tx_count=len(transactions),
)

return block_output


Expand Down
Loading