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
126 changes: 55 additions & 71 deletions src/ethereum/cancun/fork.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
-------
Expand All @@ -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(
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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 <https://eips.ethereum.org/EIPS/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.
Expand Down
20 changes: 11 additions & 9 deletions src/ethereum/cancun/vm/gas.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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
-------
Expand All @@ -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.

Expand All @@ -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
)
2 changes: 1 addition & 1 deletion src/ethereum/cancun/vm/instructions/environment.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
43 changes: 20 additions & 23 deletions src/ethereum_spec_tools/evm_tools/t8n/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"):
Expand Down Expand Up @@ -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:
Expand Down