Skip to content

Commit

Permalink
Merge pull request #935 from gurukamath/tx-includibility-cancun
Browse files Browse the repository at this point in the history
checks for a transaction in cancun
  • Loading branch information
gurukamath authored Apr 22, 2024
2 parents 744904e + 75337d6 commit 5ab5c6f
Show file tree
Hide file tree
Showing 4 changed files with 87 additions and 104 deletions.
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

0 comments on commit 5ab5c6f

Please sign in to comment.