Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement EIP-7702 #1000

Merged
merged 2 commits into from
Sep 19, 2024
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
5 changes: 4 additions & 1 deletion src/ethereum/prague/blocks.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
BlobTransaction,
FeeMarketTransaction,
LegacyTransaction,
SetCodeTransaction,
Transaction,
)

Expand Down Expand Up @@ -124,6 +125,8 @@ def encode_receipt(tx: Transaction, receipt: Receipt) -> Union[Bytes, Receipt]:
return b"\x02" + rlp.encode(receipt)
elif isinstance(tx, BlobTransaction):
return b"\x03" + rlp.encode(receipt)
elif isinstance(tx, SetCodeTransaction):
return b"\x04" + rlp.encode(receipt)
else:
return receipt

Expand All @@ -133,7 +136,7 @@ def decode_receipt(receipt: Union[Bytes, Receipt]) -> Receipt:
Decodes a receipt.
"""
if isinstance(receipt, Bytes):
assert receipt[0] in (b"\x01", b"\x02", b"\x03")
assert receipt[0] in (1, 2, 3, 4)
return rlp.decode_to(Receipt, receipt[1:])
else:
return receipt
90 changes: 82 additions & 8 deletions src/ethereum/prague/fork.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
from . import vm
from .blocks import Block, Header, Log, Receipt, Withdrawal, encode_receipt
from .bloom import logs_bloom
from .fork_types import Address, Bloom, Root, VersionedHash
from .fork_types import Address, Authorization, Bloom, Root, VersionedHash
from .requests import (
parse_consolidation_requests_from_system_tx,
parse_deposit_requests_from_receipt,
Expand Down Expand Up @@ -55,6 +55,7 @@
BlobTransaction,
FeeMarketTransaction,
LegacyTransaction,
SetCodeTransaction,
Transaction,
decode_transaction,
encode_transaction,
Expand All @@ -63,6 +64,7 @@
from .utils.hexadecimal import hex_to_address
from .utils.message import prepare_message
from .vm import Message
from .vm.eoa_delegation import PER_EMPTY_ACCOUNT_COST, is_valid_delegation
from .vm.gas import (
calculate_blob_gas_price,
calculate_data_fee,
Expand Down Expand Up @@ -407,7 +409,9 @@ def check_transaction(

sender_account = get_account(state, sender_address)

if isinstance(tx, (FeeMarketTransaction, BlobTransaction)):
if isinstance(
tx, (FeeMarketTransaction, BlobTransaction, SetCodeTransaction)
):
if tx.max_fee_per_gas < tx.max_priority_fee_per_gas:
raise InvalidBlock
if tx.max_fee_per_gas < base_fee_per_gas:
Expand All @@ -426,8 +430,6 @@ def check_transaction(
max_gas_fee = tx.gas * tx.gas_price

if isinstance(tx, BlobTransaction):
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:
Expand All @@ -441,11 +443,22 @@ def check_transaction(
blob_versioned_hashes = tx.blob_versioned_hashes
else:
blob_versioned_hashes = ()

if isinstance(tx, (BlobTransaction, SetCodeTransaction)):
if not isinstance(tx.to, Address):
raise InvalidBlock

if isinstance(tx, SetCodeTransaction):
if not any(tx.authorizations):
raise InvalidBlock

if sender_account.nonce != tx.nonce:
raise InvalidBlock
if sender_account.balance < max_gas_fee + tx.value:
raise InvalidBlock
if sender_account.code != bytearray():
if sender_account.code != bytearray() and not is_valid_delegation(
sender_account.code
):
raise InvalidBlock

return sender_address, effective_gas_price, blob_versioned_hashes
Expand Down Expand Up @@ -589,6 +602,7 @@ def process_system_transaction(
accessed_addresses=set(),
accessed_storage_keys=set(),
parent_evm=None,
authorizations=(),
)

system_tx_env = vm.Environment(
Expand Down Expand Up @@ -919,13 +933,23 @@ def process_transaction(
preaccessed_storage_keys = set()
preaccessed_addresses.add(env.coinbase)
if isinstance(
tx, (AccessListTransaction, FeeMarketTransaction, BlobTransaction)
tx,
(
AccessListTransaction,
FeeMarketTransaction,
BlobTransaction,
SetCodeTransaction,
),
):
for address, keys in tx.access_list:
preaccessed_addresses.add(address)
for key in keys:
preaccessed_storage_keys.add((address, key))

authorizations: Tuple[Authorization, ...] = ()
if isinstance(tx, SetCodeTransaction):
authorizations = tx.authorizations

message = prepare_message(
sender,
tx.to,
Expand All @@ -935,6 +959,7 @@ def process_transaction(
env,
preaccessed_addresses=frozenset(preaccessed_addresses),
preaccessed_storage_keys=frozenset(preaccessed_storage_keys),
authorizations=authorizations,
)

output = process_message_call(message, env)
Expand Down Expand Up @@ -1014,13 +1039,25 @@ def calculate_intrinsic_cost(tx: Transaction) -> Uint:

access_list_cost = 0
if isinstance(
tx, (AccessListTransaction, FeeMarketTransaction, BlobTransaction)
tx,
(
AccessListTransaction,
FeeMarketTransaction,
BlobTransaction,
SetCodeTransaction,
),
):
for _address, keys in tx.access_list:
access_list_cost += TX_ACCESS_LIST_ADDRESS_COST
access_list_cost += len(keys) * TX_ACCESS_LIST_STORAGE_KEY_COST

return Uint(TX_BASE_COST + data_cost + create_cost + access_list_cost)
auth_cost = 0
if isinstance(tx, SetCodeTransaction):
auth_cost += PER_EMPTY_ACCOUNT_COST * len(tx.authorizations)

return Uint(
TX_BASE_COST + data_cost + create_cost + access_list_cost + auth_cost
)


def recover_sender(chain_id: U64, tx: Transaction) -> Address:
Expand Down Expand Up @@ -1076,6 +1113,10 @@ def recover_sender(chain_id: U64, tx: Transaction) -> Address:
public_key = secp256k1_recover(
r, s, tx.y_parity, signing_hash_4844(tx)
)
elif isinstance(tx, SetCodeTransaction):
public_key = secp256k1_recover(
r, s, tx.y_parity, signing_hash_7702(tx)
)
except InvalidSignature as e:
raise InvalidBlock from e

Expand Down Expand Up @@ -1240,6 +1281,39 @@ def signing_hash_4844(tx: BlobTransaction) -> Hash32:
)


def signing_hash_7702(tx: SetCodeTransaction) -> Hash32:
"""
Compute the hash of a transaction used in a EIP-7702 signature.

Parameters
----------
tx :
Transaction of interest.

Returns
-------
hash : `ethereum.crypto.hash.Hash32`
Hash of the transaction.
"""
return keccak256(
b"\x04"
+ rlp.encode(
(
tx.chain_id,
tx.nonce,
tx.max_priority_fee_per_gas,
tx.max_fee_per_gas,
tx.gas,
tx.to,
tx.value,
tx.data,
tx.access_list,
tx.authorizations,
)
)
)


def compute_header_hash(header: Header) -> Hash32:
"""
Computes the hash of a block header.
Expand Down
16 changes: 16 additions & 0 deletions src/ethereum/prague/fork_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

from .. import rlp
from ..base_types import (
U64,
U256,
Bytes,
Bytes20,
Expand Down Expand Up @@ -66,3 +67,18 @@ def encode_account(raw_account_data: Account, storage_root: Bytes) -> Bytes:
keccak256(raw_account_data.code),
)
)


@slotted_freezable
@dataclass
class Authorization:
"""
The authorization for a set code transaction.
"""

chain_id: U64
address: Address
nonce: U64
y_parity: U256
r: U256
s: U256
32 changes: 30 additions & 2 deletions src/ethereum/prague/transactions.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
from dataclasses import dataclass
from typing import Tuple, Union

from ethereum.exceptions import InvalidBlock

from .. import rlp
from ..base_types import (
U64,
Expand All @@ -16,8 +18,7 @@
Uint,
slotted_freezable,
)
from ..exceptions import InvalidBlock
from .fork_types import Address, VersionedHash
from .fork_types import Address, Authorization, VersionedHash

TX_BASE_COST = 21000
TX_DATA_COST_PER_NON_ZERO = 16
Expand Down Expand Up @@ -109,11 +110,34 @@ class BlobTransaction:
s: U256


@slotted_freezable
@dataclass
class SetCodeTransaction:
"""
The transaction type added in EIP-7702.
"""

chain_id: U64
nonce: U64
max_priority_fee_per_gas: Uint
max_fee_per_gas: Uint
gas: Uint
to: Address
value: U256
data: Bytes
access_list: Tuple[Tuple[Address, Tuple[Bytes32, ...]], ...]
authorizations: Tuple[Authorization, ...]
y_parity: U256
r: U256
s: U256


Transaction = Union[
LegacyTransaction,
AccessListTransaction,
FeeMarketTransaction,
BlobTransaction,
SetCodeTransaction,
]


Expand All @@ -129,6 +153,8 @@ def encode_transaction(tx: Transaction) -> Union[LegacyTransaction, Bytes]:
return b"\x02" + rlp.encode(tx)
elif isinstance(tx, BlobTransaction):
return b"\x03" + rlp.encode(tx)
elif isinstance(tx, SetCodeTransaction):
return b"\x04" + rlp.encode(tx)
else:
raise Exception(f"Unable to encode transaction of type {type(tx)}")

Expand All @@ -144,6 +170,8 @@ def decode_transaction(tx: Union[LegacyTransaction, Bytes]) -> Transaction:
return rlp.decode_to(FeeMarketTransaction, tx[1:])
elif tx[0] == 3:
return rlp.decode_to(BlobTransaction, tx[1:])
elif tx[0] == 4:
return rlp.decode_to(SetCodeTransaction, tx[1:])
else:
raise InvalidBlock
else:
Expand Down
6 changes: 5 additions & 1 deletion src/ethereum/prague/utils/message.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

from ethereum.base_types import U256, Bytes, Bytes0, Bytes32, Uint

from ..fork_types import Address
from ..fork_types import Address, Authorization
from ..state import get_account
from ..vm import Environment, Message
from ..vm.precompiled_contracts.mapping import PRE_COMPILED_CONTRACTS
Expand All @@ -37,6 +37,7 @@ def prepare_message(
preaccessed_storage_keys: FrozenSet[
Tuple[(Address, Bytes32)]
] = frozenset(),
authorizations: Tuple[Authorization, ...] = (),
) -> Message:
"""
Execute a transaction against the provided environment.
Expand Down Expand Up @@ -69,6 +70,8 @@ def prepare_message(
preaccessed_storage_keys:
Storage keys that should be marked as accessed prior to the message
call
authorizations:
Authorizations that should be applied to the message call.

Returns
-------
Expand Down Expand Up @@ -112,4 +115,5 @@ def prepare_message(
accessed_addresses=accessed_addresses,
accessed_storage_keys=set(preaccessed_storage_keys),
parent_evm=None,
authorizations=authorizations,
)
3 changes: 2 additions & 1 deletion src/ethereum/prague/vm/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
from ethereum.crypto.hash import Hash32

from ..blocks import Log
from ..fork_types import Address, VersionedHash
from ..fork_types import Address, Authorization, VersionedHash
from ..state import State, TransientStorage, account_exists_and_is_empty
from .precompiled_contracts import RIPEMD160_ADDRESS

Expand Down Expand Up @@ -71,6 +71,7 @@ class Message:
accessed_addresses: Set[Address]
accessed_storage_keys: Set[Tuple[Address, Bytes32]]
parent_evm: Optional["Evm"]
authorizations: Tuple[Authorization, ...]


@dataclass
Expand Down
Loading
Loading