diff --git a/src/ethereum/cancun/fork.py b/src/ethereum/cancun/fork.py index 88f936359c..450f4a32e7 100644 --- a/src/ethereum/cancun/fork.py +++ b/src/ethereum/cancun/fork.py @@ -337,24 +337,30 @@ def validate_header(header: Header, parent_header: Header) -> None: def check_transaction( + state: State, tx: Transaction, - base_fee_per_gas: Uint, gas_available: Uint, chain_id: U64, + base_fee_per_gas: Uint, + excess_blob_gas: U64, ) -> Tuple[Address, Uint, Tuple[VersionedHash, ...]]: """ Check if the transaction is includable in the block. Parameters ---------- + state : + Current state. tx : The transaction. - base_fee_per_gas : - The block base fee. gas_available : The gas remaining in the block. chain_id : The ID of the current chain. + base_fee_per_gas : + The block base fee. + excess_blob_gas : + The excess blob gas. Returns ------- @@ -370,29 +376,59 @@ def check_transaction( InvalidBlock : If the transaction is not includable. """ - ensure(tx.gas <= gas_available, InvalidBlock) - sender_address = recover_sender(chain_id, tx) + if calculate_intrinsic_cost(tx) > tx.gas: + raise InvalidBlock + if tx.nonce >= 2**64 - 1: + raise InvalidBlock + if tx.to == Bytes0(b"") and len(tx.data) > 2 * MAX_CODE_SIZE: + raise InvalidBlock + + if tx.gas > gas_available: + raise InvalidBlock + + sender = recover_sender(chain_id, tx) + sender_account = get_account(state, sender) if isinstance(tx, (FeeMarketTransaction, BlobTransaction)): - ensure(tx.max_fee_per_gas >= tx.max_priority_fee_per_gas, InvalidBlock) - ensure(tx.max_fee_per_gas >= base_fee_per_gas, InvalidBlock) + if tx.max_fee_per_gas < tx.max_priority_fee_per_gas: + raise InvalidBlock + if tx.max_fee_per_gas < base_fee_per_gas: + raise InvalidBlock priority_fee_per_gas = min( tx.max_priority_fee_per_gas, tx.max_fee_per_gas - base_fee_per_gas, ) effective_gas_price = priority_fee_per_gas + base_fee_per_gas + max_gas_fee = tx.gas * tx.max_fee_per_gas else: - ensure(tx.gas_price >= base_fee_per_gas, InvalidBlock) + if tx.gas_price < base_fee_per_gas: + raise InvalidBlock effective_gas_price = tx.gas_price + max_gas_fee = tx.gas * tx.gas_price if isinstance(tx, BlobTransaction): - ensure(isinstance(tx.to, Address), InvalidBlock) + if not isinstance(tx.to, Address): + raise InvalidBlock + if len(tx.blob_versioned_hashes) == 0: + raise InvalidBlock + for blob_versioned_hash in tx.blob_versioned_hashes: + if blob_versioned_hash[0:1] != VERSIONED_HASH_VERSION_KZG: + raise InvalidBlock + + if tx.max_fee_per_blob_gas < calculate_blob_gas_price(excess_blob_gas): + raise InvalidBlock + + max_gas_fee += calculate_total_blob_gas(tx) * tx.max_fee_per_blob_gas blob_versioned_hashes = tx.blob_versioned_hashes else: blob_versioned_hashes = () - return sender_address, effective_gas_price, blob_versioned_hashes + ensure(sender_account.nonce == tx.nonce, InvalidBlock) + ensure(sender_account.balance >= max_gas_fee + tx.value, InvalidBlock) + ensure(sender_account.code == bytearray(), InvalidBlock) + + return sender, effective_gas_price, blob_versioned_hashes def make_receipt( @@ -602,7 +638,14 @@ def apply_body( sender_address, effective_gas_price, blob_versioned_hashes, - ) = check_transaction(tx, base_fee_per_gas, gas_available, chain_id) + ) = check_transaction( + state, + tx, + gas_available, + chain_id, + base_fee_per_gas, + excess_blob_gas, + ) env = vm.Environment( caller=sender_address, @@ -692,38 +735,14 @@ def process_transaction( logs : `Tuple[ethereum.blocks.Log, ...]` Logs generated during execution. """ - ensure(validate_transaction(tx), InvalidBlock) - sender = env.origin sender_account = get_account(env.state, sender) - if isinstance(tx, (FeeMarketTransaction, BlobTransaction)): - max_gas_fee = tx.gas * tx.max_fee_per_gas - else: - max_gas_fee = tx.gas * tx.gas_price - if isinstance(tx, BlobTransaction): - ensure(len(tx.blob_versioned_hashes) > 0, InvalidBlock) - for blob_versioned_hash in tx.blob_versioned_hashes: - ensure( - blob_versioned_hash[0:1] == VERSIONED_HASH_VERSION_KZG, - InvalidBlock, - ) - - ensure( - tx.max_fee_per_blob_gas >= calculate_blob_gas_price(env), - InvalidBlock, - ) - - max_gas_fee += calculate_total_blob_gas(tx) * tx.max_fee_per_blob_gas - blob_gas_fee = calculate_data_fee(env, tx) + blob_gas_fee = calculate_data_fee(env.excess_blob_gas, tx) else: blob_gas_fee = Uint(0) - ensure(sender_account.nonce == tx.nonce, InvalidBlock) - ensure(sender_account.balance >= max_gas_fee + tx.value, InvalidBlock) - ensure(sender_account.code == bytearray(), InvalidBlock) - effective_gas_fee = tx.gas * env.gas_price gas = tx.gas - calculate_intrinsic_cost(tx) @@ -795,41 +814,6 @@ def process_transaction( return total_gas_used, output.logs, output.error -def validate_transaction(tx: Transaction) -> bool: - """ - Verifies a transaction. - - The gas in a transaction gets used to pay for the intrinsic cost of - operations, therefore if there is insufficient gas then it would not - be possible to execute a transaction and it will be declared invalid. - - Additionally, the nonce of a transaction must not equal or exceed the - limit defined in `EIP-2681 `_. - In practice, defining the limit as ``2**64-1`` has no impact because - sending ``2**64-1`` transactions is improbable. It's not strictly - impossible though, ``2**64-1`` transactions is the entire capacity of the - Ethereum blockchain at 2022 gas limits for a little over 22 years. - - Parameters - ---------- - tx : - Transaction to validate. - - Returns - ------- - verified : `bool` - True if the transaction can be executed, or False otherwise. - """ - if calculate_intrinsic_cost(tx) > tx.gas: - return False - if tx.nonce >= 2**64 - 1: - return False - if tx.to == Bytes0(b"") and len(tx.data) > 2 * MAX_CODE_SIZE: - return False - - return True - - def calculate_intrinsic_cost(tx: Transaction) -> Uint: """ Calculates the gas that is charged before execution is started. diff --git a/src/ethereum/cancun/vm/gas.py b/src/ethereum/cancun/vm/gas.py index d19d7a2f16..a47fc6e921 100644 --- a/src/ethereum/cancun/vm/gas.py +++ b/src/ethereum/cancun/vm/gas.py @@ -20,7 +20,7 @@ from ..blocks import Header from ..transactions import BlobTransaction, Transaction -from . import Environment, Evm +from . import Evm from .exceptions import OutOfGasError GAS_JUMPDEST = Uint(1) @@ -312,14 +312,14 @@ def calculate_total_blob_gas(tx: Transaction) -> Uint: return Uint(0) -def calculate_blob_gas_price(env: Environment) -> Uint: +def calculate_blob_gas_price(excess_blob_gas: U64) -> Uint: """ Calculate the blob gasprice for a block. Parameters ---------- - env : - The execution environment. + excess_blob_gas : + The excess blob gas for the block. Returns ------- @@ -328,19 +328,19 @@ def calculate_blob_gas_price(env: Environment) -> Uint: """ return taylor_exponential( MIN_BLOB_GASPRICE, - Uint(env.excess_blob_gas), + Uint(excess_blob_gas), BLOB_GASPRICE_UPDATE_FRACTION, ) -def calculate_data_fee(env: Environment, tx: Transaction) -> Uint: +def calculate_data_fee(excess_blob_gas: U64, tx: Transaction) -> Uint: """ Calculate the blob data fee for a transaction. Parameters ---------- - env : - The execution environment. + excess_blob_gas : + The excess_blob_gas for the execution. tx : The transaction for which the blob data fee is to be calculated. @@ -349,4 +349,6 @@ def calculate_data_fee(env: Environment, tx: Transaction) -> Uint: data_fee: `Uint` The blob data fee. """ - return calculate_total_blob_gas(tx) * calculate_blob_gas_price(env) + return calculate_total_blob_gas(tx) * calculate_blob_gas_price( + excess_blob_gas + ) diff --git a/src/ethereum/cancun/vm/instructions/environment.py b/src/ethereum/cancun/vm/instructions/environment.py index fd5d2b1b4e..ca3caca6b4 100644 --- a/src/ethereum/cancun/vm/instructions/environment.py +++ b/src/ethereum/cancun/vm/instructions/environment.py @@ -583,7 +583,7 @@ def blob_base_fee(evm: Evm) -> None: charge_gas(evm, GAS_BASE) # OPERATION - blob_base_fee = calculate_blob_gas_price(evm.env) + blob_base_fee = calculate_blob_gas_price(evm.env.excess_blob_gas) push(evm.stack, U256(blob_base_fee)) # PROGRAM COUNTER diff --git a/src/ethereum_spec_tools/evm_tools/t8n/__init__.py b/src/ethereum_spec_tools/evm_tools/t8n/__init__.py index 74d5dd95f6..c50ae790f7 100644 --- a/src/ethereum_spec_tools/evm_tools/t8n/__init__.py +++ b/src/ethereum_spec_tools/evm_tools/t8n/__init__.py @@ -150,6 +150,19 @@ def check_transaction(self, tx: Any, gas_available: Any) -> Any: Implements the check_transaction function of the fork. The arguments to be passed are adjusted according to the fork. """ + # TODO: The current PR changes the signature of the check_transaction + # in cancun only. Once this is approved and ported over to the + # the other forks in PR #890, this function has to be updated. + # This is a temporary change to make the tool work for cancun. + if self.fork.is_after_fork("ethereum.cancun"): + return self.fork.check_transaction( + self.alloc.state, + tx, + gas_available, + self.chain_id, + self.env.base_fee_per_gas, + self.env.excess_blob_gas, + ) arguments = [tx] if self.fork.is_after_fork("ethereum.london"): @@ -184,48 +197,32 @@ def environment(self, tx: Any, gas_available: Any) -> Any: if self.fork.is_after_fork("ethereum.istanbul"): kw_arguments["chain_id"] = self.chain_id + check_tx_return = self.check_transaction(tx, gas_available) + if self.fork.is_after_fork("ethereum.cancun"): ( sender_address, effective_gas_price, blob_versioned_hashes, - ) = self.fork.check_transaction( - tx, - self.env.base_fee_per_gas, - gas_available, - self.chain_id, - ) + ) = check_tx_return kw_arguments["base_fee_per_gas"] = self.env.base_fee_per_gas kw_arguments["caller"] = kw_arguments["origin"] = sender_address kw_arguments["gas_price"] = effective_gas_price kw_arguments["blob_versioned_hashes"] = blob_versioned_hashes + kw_arguments["excess_blob_gas"] = self.env.excess_blob_gas + kw_arguments["transient_storage"] = self.fork.TransientStorage() elif self.fork.is_after_fork("ethereum.london"): - sender_address, effective_gas_price = self.fork.check_transaction( - tx, - self.env.base_fee_per_gas, - gas_available, - self.chain_id, - ) + sender_address, effective_gas_price = check_tx_return kw_arguments["base_fee_per_gas"] = self.env.base_fee_per_gas kw_arguments["caller"] = kw_arguments["origin"] = sender_address kw_arguments["gas_price"] = effective_gas_price - elif self.fork.is_after_fork("ethereum.spurious_dragon"): - sender_address = self.fork.check_transaction( - tx, gas_available, self.chain_id - ) - kw_arguments["caller"] = kw_arguments["origin"] = sender_address - kw_arguments["gas_price"] = tx.gas_price else: - sender_address = self.fork.check_transaction(tx, gas_available) + sender_address = check_tx_return kw_arguments["caller"] = kw_arguments["origin"] = sender_address kw_arguments["gas_price"] = tx.gas_price kw_arguments["traces"] = [] - if self.fork.is_after_fork("ethereum.cancun"): - kw_arguments["excess_blob_gas"] = self.env.excess_blob_gas - kw_arguments["transient_storage"] = self.fork.TransientStorage() - return self.fork.Environment(**kw_arguments) def tx_trie_set(self, trie: Any, index: Any, tx: Any) -> Any: