Skip to content
Merged
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
69 changes: 50 additions & 19 deletions src/ethereum/forks/amsterdam/fork.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@
set_account_balance,
)
from .transactions import (
TX_MAX_GAS_LIMIT,
AccessListTransaction,
BlobTransaction,
FeeMarketTransaction,
Expand Down Expand Up @@ -276,10 +277,12 @@ def state_transition(chain: BlockChain, block: Block) -> None:
block_output.block_access_list
)

if block_output.block_gas_used != block.header.gas_used:
raise InvalidBlock(
f"{block_output.block_gas_used} != {block.header.gas_used}"
)
block_gas_used = max(
block_output.block_gas_used,
block_output.block_state_gas_used,
)
if block_gas_used != block.header.gas_used:
raise InvalidBlock(f"{block_gas_used} != {block.header.gas_used}")
if transactions_root != block.header.transactions_root:
raise InvalidBlock
if block_state_root != block.header.state_root:
Expand Down Expand Up @@ -490,11 +493,21 @@ def check_transaction(
is empty.

"""
gas_available = block_env.block_gas_limit - block_output.block_gas_used
# Both regular gas and state gas have their own limits
regular_gas_available = (
block_env.block_gas_limit - block_output.block_gas_used
)
state_gas_available = (
block_env.block_gas_limit - block_output.block_state_gas_used
)
blob_gas_available = MAX_BLOB_GAS_PER_BLOCK - block_output.blob_gas_used

if tx.gas > gas_available:
raise GasUsedExceedsLimitError("gas used exceeds limit")
# Regular gas is capped at TX_MAX_GAS_LIMIT; state gas can use all
# of tx.gas (gas_left can be drawn for state gas when reservoir is empty)
if min(TX_MAX_GAS_LIMIT, tx.gas) > regular_gas_available:
raise GasUsedExceedsLimitError("regular gas used exceeds limit")
if tx.gas > state_gas_available:
raise GasUsedExceedsLimitError("state gas used exceeds limit")

tx_blob_gas_used = calculate_total_blob_gas(tx)
if tx_blob_gas_used > blob_gas_available:
Expand Down Expand Up @@ -713,13 +726,16 @@ def process_unchecked_system_transaction(
origin=SYSTEM_ADDRESS,
gas_price=block_env.base_fee_per_gas,
gas=SYSTEM_TRANSACTION_GAS,
state_gas_reservoir=Uint(0),
access_list_addresses=set(),
access_list_storage_keys=set(),
state=system_tx_state,
blob_versioned_hashes=(),
authorizations=(),
index_in_block=None,
tx_hash=None,
intrinsic_regular_gas=Uint(0),
intrinsic_state_gas=Uint(0),
)

system_tx_message = Message(
Expand All @@ -728,6 +744,7 @@ def process_unchecked_system_transaction(
caller=SYSTEM_ADDRESS,
target=target_address,
gas=SYSTEM_TRANSACTION_GAS,
state_gas_reservoir=Uint(0),
value=U256(0),
data=data,
code=system_contract_code,
Expand Down Expand Up @@ -910,7 +927,9 @@ def process_transaction(
encode_transaction(tx),
)

intrinsic_gas, calldata_floor_gas_cost = validate_transaction(tx)
intrinsic = validate_transaction(tx, block_env.block_gas_limit)

intrinsic_gas = intrinsic.regular + intrinsic.state

(
sender,
Expand All @@ -933,7 +952,12 @@ def process_transaction(

effective_gas_fee = tx.gas * effective_gas_price

gas = tx.gas - intrinsic_gas
# Split execution gas into gas_left (capped by remaining regular gas
# budget) and state_gas_reservoir.
execution_gas = tx.gas - intrinsic_gas
regular_gas_budget = TX_MAX_GAS_LIMIT - intrinsic.regular
gas = min(regular_gas_budget, execution_gas)
state_gas_reservoir = Uint(execution_gas - gas)

increment_nonce(tx_state, sender)

Expand Down Expand Up @@ -967,13 +991,16 @@ def process_transaction(
origin=sender,
gas_price=effective_gas_price,
gas=gas,
state_gas_reservoir=state_gas_reservoir,
access_list_addresses=access_list_addresses,
access_list_storage_keys=access_list_storage_keys,
state=tx_state,
blob_versioned_hashes=blob_versioned_hashes,
authorizations=authorizations,
index_in_block=index,
tx_hash=get_transaction_hash(encode_transaction(tx)),
intrinsic_regular_gas=intrinsic.regular,
intrinsic_state_gas=intrinsic.state,
)

message = prepare_message(
Expand All @@ -984,26 +1011,24 @@ def process_transaction(

tx_output = process_message_call(message)

# For EIP-7623 we first calculate the execution_gas_used, which includes
# the execution gas refund.
tx_gas_used_before_refund = tx.gas - tx_output.gas_left
tx_gas_used_before_refund = (
tx.gas - tx_output.gas_left - tx_output.state_gas_left
)
tx_gas_refund = min(
tx_gas_used_before_refund // Uint(5), Uint(tx_output.refund_counter)
)
tx_gas_used_after_refund = tx_gas_used_before_refund - tx_gas_refund

# Transactions with less execution_gas_used than the floor pay at the
# floor cost.
tx_gas_used_after_refund = max(
tx_gas_used_after_refund, calldata_floor_gas_cost
)
tx_gas_used = max(tx_gas_used_after_refund, intrinsic.calldata_floor)

tx_gas_left = tx.gas - tx_gas_used_after_refund
tx_gas_left = tx.gas - tx_gas_used
gas_refund_amount = tx_gas_left * effective_gas_price

# For non-1559 transactions effective_gas_price == tx.gas_price
priority_fee_per_gas = effective_gas_price - block_env.base_fee_per_gas
transaction_fee = tx_gas_used_after_refund * priority_fee_per_gas
transaction_fee = tx_gas_used * priority_fee_per_gas

# refund gas
sender_balance_after_refund = get_account(tx_state, sender).balance + U256(
Expand All @@ -1024,11 +1049,17 @@ def process_transaction(
):
destroy_account(tx_state, block_env.coinbase)

block_output.block_gas_used += tx_gas_used_after_refund
tx_regular_gas = tx_env.intrinsic_regular_gas + tx_output.regular_gas_used
tx_state_gas = tx_env.intrinsic_state_gas + tx_output.state_gas_used
block_output.block_gas_used += max(
tx_regular_gas, intrinsic.calldata_floor
)
block_output.block_state_gas_used += tx_state_gas
block_output.blob_gas_used += tx_blob_gas_used

block_output.cumulative_gas_used += tx_gas_used
receipt = make_receipt(
tx, tx_output.error, block_output.block_gas_used, tx_output.logs
tx, tx_output.error, block_output.cumulative_gas_used, tx_output.logs
)

receipt_key = rlp.encode(Uint(index))
Expand Down
131 changes: 85 additions & 46 deletions src/ethereum/forks/amsterdam/transactions.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@

from .exceptions import (
InitCodeTooLargeError,
TransactionGasLimitExceededError,
TransactionTypeError,
)
from .fork_types import Authorization, VersionedHash
Expand All @@ -50,21 +49,35 @@
[EIP-7623]: https://eips.ethereum.org/EIPS/eip-7623
"""

GAS_TX_CREATE = Uint(32000)
"""
Additional gas cost for creating a new contract.
"""

GAS_TX_ACCESS_LIST_ADDRESS = Uint(2400)
TX_ACCESS_LIST_ADDRESS_COST = Uint(2400)
"""
Gas cost for including an address in the access list of a transaction.
"""

GAS_TX_ACCESS_LIST_STORAGE_KEY = Uint(1900)
TX_ACCESS_LIST_STORAGE_KEY_COST = Uint(1900)
"""
Gas cost for including a storage key in the access list of a transaction.
"""


@dataclass
class IntrinsicGasCost:
"""
Intrinsic gas costs for a transaction, split by gas type.

`regular`: `ethereum.base_types.Uint`
Regular execution gas (calldata, base cost, access list, etc.)
`state`: `ethereum.base_types.Uint`
State growth gas (account creation, storage set, authorization).
`calldata_floor`: `ethereum.base_types.Uint`
Minimum gas cost based on calldata size per [EIP-7623].
"""

regular: Uint
state: Uint
calldata_floor: Uint


TX_MAX_GAS_LIMIT = Uint(16_777_216)


Expand Down Expand Up @@ -526,7 +539,7 @@ def decode_transaction(tx: LegacyTransaction | Bytes) -> Transaction:
return tx


def validate_transaction(tx: Transaction) -> Tuple[Uint, Uint]:
def validate_transaction(tx: Transaction, gas_limit: Uint) -> IntrinsicGasCost:
"""
Verifies a transaction.

Expand All @@ -544,33 +557,39 @@ def validate_transaction(tx: Transaction) -> Tuple[Uint, Uint]:
Also, the code size of a contract creation transaction must be within
limits of the protocol.

This function takes a transaction as a parameter and returns the intrinsic
gas cost and the minimum calldata gas cost for the transaction after
validation. It throws an `InsufficientTransactionGasError` exception if
the transaction does not provide enough gas to cover the intrinsic cost,
and a `NonceOverflowError` exception if the nonce is greater than
`2**64 - 2`. It also raises an `InitCodeTooLargeError` if the code size of
a contract creation transaction exceeds the maximum allowed size.
This function takes a transaction and gas_limit as parameters and
returns the intrinsic gas costs for the transaction after validation.
It throws an `InsufficientTransactionGasError` exception if the
transaction does not provide enough gas to cover the intrinsic cost,
and a `NonceOverflowError` exception if the nonce overflows.
It also raises an `InitCodeTooLargeError` if the code
size of a contract creation transaction exceeds the maximum allowed
size.

[EIP-2681]: https://eips.ethereum.org/EIPS/eip-2681
[EIP-7623]: https://eips.ethereum.org/EIPS/eip-7623
"""
from .vm.interpreter import MAX_INIT_CODE_SIZE

intrinsic_gas, calldata_floor_gas_cost = calculate_intrinsic_cost(tx)
if max(intrinsic_gas, calldata_floor_gas_cost) > tx.gas:
intrinsic = calculate_intrinsic_cost(tx, gas_limit)
intrinsic_gas = intrinsic.regular + intrinsic.state
if max(intrinsic_gas, intrinsic.calldata_floor) > tx.gas:
raise InsufficientTransactionGasError("Insufficient gas")
if max(intrinsic.regular, intrinsic.calldata_floor) > TX_MAX_GAS_LIMIT:
raise InsufficientTransactionGasError(
"Intrinsic regular gas or calldata floor exceeds TX_MAX_GAS_LIMIT"
)
if U256(tx.nonce) >= U256(U64.MAX_VALUE):
raise NonceOverflowError("Nonce too high")
if tx.to == Bytes0(b"") and len(tx.data) > MAX_INIT_CODE_SIZE:
raise InitCodeTooLargeError("Code size too large")
if tx.gas > TX_MAX_GAS_LIMIT:
raise TransactionGasLimitExceededError("Gas limit too high")

return intrinsic_gas, calldata_floor_gas_cost
return intrinsic


def calculate_intrinsic_cost(tx: Transaction) -> Tuple[Uint, Uint]:
def calculate_intrinsic_cost(
tx: Transaction, gas_limit: Uint
) -> IntrinsicGasCost:
"""
Calculates the gas that is charged before execution is started.

Expand All @@ -591,12 +610,18 @@ def calculate_intrinsic_cost(tx: Transaction) -> Tuple[Uint, Uint]:
5. Cost for authorizations (if applicable)


This function takes a transaction as a parameter and returns the intrinsic
gas cost of the transaction and the minimum gas cost used by the
transaction based on the calldata size.
This function takes a transaction and gas_limit as parameters and
returns the intrinsic regular gas cost, intrinsic state gas cost, and the
minimum gas cost used by the transaction based on the calldata size.
"""
from .vm.eoa_delegation import GAS_AUTH_PER_EMPTY_ACCOUNT
from .vm.gas import init_code_cost
from .vm.gas import (
PER_AUTH_BASE_COST,
REGULAR_GAS_CREATE,
STATE_BYTES_PER_AUTH_BASE,
STATE_BYTES_PER_NEW_ACCOUNT,
init_code_cost,
state_gas_per_byte,
)

zero_bytes = 0
for byte in tx.data:
Expand All @@ -611,12 +636,15 @@ def calculate_intrinsic_cost(tx: Transaction) -> Tuple[Uint, Uint]:

data_cost = tokens_in_calldata * GAS_TX_DATA_TOKEN_STANDARD

cost_per_state_byte = state_gas_per_byte(gas_limit)

create_regular_gas = Uint(0)
create_state_gas = Uint(0)
if tx.to == Bytes0(b""):
create_cost = GAS_TX_CREATE + init_code_cost(ulen(tx.data))
else:
create_cost = Uint(0)
create_state_gas = STATE_BYTES_PER_NEW_ACCOUNT * cost_per_state_byte
create_regular_gas = REGULAR_GAS_CREATE + init_code_cost(ulen(tx.data))

access_list_cost = Uint(0)
access_list_gas = Uint(0)
if isinstance(
tx,
(
Expand All @@ -627,24 +655,35 @@ def calculate_intrinsic_cost(tx: Transaction) -> Tuple[Uint, Uint]:
),
):
for access in tx.access_list:
access_list_cost += GAS_TX_ACCESS_LIST_ADDRESS
access_list_cost += (
ulen(access.slots) * GAS_TX_ACCESS_LIST_STORAGE_KEY
access_list_gas += TX_ACCESS_LIST_ADDRESS_COST
access_list_gas += (
ulen(access.slots) * TX_ACCESS_LIST_STORAGE_KEY_COST
)

auth_cost = Uint(0)
auth_regular_gas = Uint(0)
auth_state_gas = Uint(0)
if isinstance(tx, SetCodeTransaction):
auth_cost += Uint(GAS_AUTH_PER_EMPTY_ACCOUNT * len(tx.authorizations))

return (
Uint(
GAS_TX_BASE
+ data_cost
+ create_cost
+ access_list_cost
+ auth_cost
),
calldata_floor_gas_cost,
auth_regular_gas = PER_AUTH_BASE_COST * ulen(tx.authorizations)
auth_state_gas = (
(STATE_BYTES_PER_NEW_ACCOUNT + STATE_BYTES_PER_AUTH_BASE)
* cost_per_state_byte
* ulen(tx.authorizations)
)

intrinsic_regular_gas = (
GAS_TX_BASE
+ data_cost
+ create_regular_gas
+ access_list_gas
+ auth_regular_gas
)

intrinsic_state_gas = create_state_gas + auth_state_gas

return IntrinsicGasCost(
regular=intrinsic_regular_gas,
state=intrinsic_state_gas,
calldata_floor=calldata_floor_gas_cost,
)


Expand Down
1 change: 1 addition & 0 deletions src/ethereum/forks/amsterdam/utils/message.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ def prepare_message(
caller=tx_env.origin,
target=tx.to,
gas=tx_env.gas,
state_gas_reservoir=tx_env.state_gas_reservoir,
value=tx.value,
data=msg_data,
code=code,
Expand Down
Loading