From d89b671b835e1df03c89a88ed147d5e14aa88913 Mon Sep 17 00:00:00 2001 From: Etan Kissling Date: Wed, 22 Nov 2023 12:45:55 +0100 Subject: [PATCH] Update EIP-6493: Use `Variant[S]` for type safety Merged by EIP-Bot. --- EIPS/eip-6493.md | 187 ++++++++++++++++++++++++++++++----- assets/eip-6493/convert.py | 73 +++++++++----- assets/eip-6493/rlp_types.py | 42 ++++---- assets/eip-6493/ssz_types.py | 174 +++++++++++++++++++++++++++----- assets/eip-6493/tx_hashes.py | 110 ++++++++++++--------- 5 files changed, 443 insertions(+), 143 deletions(-) diff --git a/EIPS/eip-6493.md b/EIPS/eip-6493.md index 649d088a006664..43fbb15d70b9e4 100644 --- a/EIPS/eip-6493.md +++ b/EIPS/eip-6493.md @@ -121,36 +121,145 @@ class TransactionSignature(StableContainer[MAX_TRANSACTION_SIGNATURE_FIELDS]): class SignedTransaction(Container): payload: TransactionPayload signature: TransactionSignature +``` + +Valid transaction types can be defined using [EIP-7495](./eip-7495.md) `Variant`. + +```python +class ReplayableTransactionPayload(Variant[TransactionPayload]): + nonce: uint64 + max_fee_per_gas: uint256 + gas: uint64 + to: Optional[ExecutionAddress] + value: uint256 + input_: ByteList[MAX_CALLDATA_SIZE] + +class ReplayableSignedTransaction(SignedTransaction): + payload: ReplayableTransactionPayload + signature: TransactionSignature + +class LegacyTransactionPayload(Variant[TransactionPayload]): + nonce: uint64 + max_fee_per_gas: uint256 + gas: uint64 + to: Optional[ExecutionAddress] + value: uint256 + input_: ByteList[MAX_CALLDATA_SIZE] + type_: TransactionType + +class LegacySignedTransaction(SignedTransaction): + payload: LegacyTransactionPayload + signature: TransactionSignature + +class Eip2930TransactionPayload(Variant[TransactionPayload]): + nonce: uint64 + max_fee_per_gas: uint256 + gas: uint64 + to: Optional[ExecutionAddress] + value: uint256 + input_: ByteList[MAX_CALLDATA_SIZE] + type_: TransactionType + access_list: List[AccessTuple, MAX_ACCESS_LIST_SIZE] + +class Eip2930SignedTransaction(SignedTransaction): + payload: Eip2930TransactionPayload + signature: TransactionSignature + +class Eip1559TransactionPayload(Variant[TransactionPayload]): + nonce: uint64 + max_fee_per_gas: uint256 + gas: uint64 + to: Optional[ExecutionAddress] + value: uint256 + input_: ByteList[MAX_CALLDATA_SIZE] + type_: TransactionType + access_list: List[AccessTuple, MAX_ACCESS_LIST_SIZE] + max_priority_fee_per_gas: uint256 + +class Eip1559SignedTransaction(SignedTransaction): + payload: Eip1559TransactionPayload + signature: TransactionSignature + +class Eip4844TransactionPayload(Variant[TransactionPayload]): + nonce: uint64 + max_fee_per_gas: uint256 + gas: uint64 + to: ExecutionAddress + value: uint256 + input_: ByteList[MAX_CALLDATA_SIZE] + type_: TransactionType + access_list: List[AccessTuple, MAX_ACCESS_LIST_SIZE] + max_priority_fee_per_gas: uint256 + max_fee_per_blob_gas: uint256 + blob_versioned_hashes: List[VersionedHash, MAX_BLOB_COMMITMENTS_PER_BLOCK] + +class Eip4844SignedTransaction(SignedTransaction): + payload: Eip4844TransactionPayload + signature: TransactionSignature + +class BasicTransactionPayload(Variant[TransactionPayload]): + nonce: uint64 + max_fee_per_gas: uint256 + gas: uint64 + to: Optional[ExecutionAddress] + value: uint256 + input_: ByteList[MAX_CALLDATA_SIZE] + type_: TransactionType + access_list: List[AccessTuple, MAX_ACCESS_LIST_SIZE] + max_priority_fee_per_gas: uint256 + +class BasicSignedTransaction(SignedTransaction): + payload: BasicTransactionPayload + signature: TransactionSignature + +class BlobTransactionPayload(Variant[TransactionPayload]): + nonce: uint64 + max_fee_per_gas: uint256 + gas: uint64 + to: ExecutionAddress + value: uint256 + input_: ByteList[MAX_CALLDATA_SIZE] + type_: TransactionType + access_list: List[AccessTuple, MAX_ACCESS_LIST_SIZE] + max_priority_fee_per_gas: uint256 + max_fee_per_blob_gas: uint256 + blob_versioned_hashes: List[VersionedHash, MAX_BLOB_COMMITMENTS_PER_BLOCK] -def check_transaction_supported(tx: SignedTransaction): - if tx.payload.max_fee_per_blob_gas is not None: - assert tx.payload.blob_versioned_hashes is not None - assert tx.payload.max_priority_fee_per_gas is not None - assert tx.payload.to is not None - else: - assert tx.payload.blob_versioned_hashes is None - - if tx.payload.max_priority_fee_per_gas is not None: - assert tx.payload.access_list is not None - - if tx.payload.type_ != TRANSACTION_TYPE_SSZ: - if tx.payload.max_fee_per_blob_gas is not None: - assert tx.payload.type_ == TRANSACTION_TYPE_EIP4844 - elif tx.payload.max_priority_fee_per_gas is not None: - assert tx.payload.type_ == TRANSACTION_TYPE_EIP1559 - elif tx.payload.access_list is not None: - assert tx.payload.type_ == TRANSACTION_TYPE_EIP2930 - else: - assert tx.payload.type_ == TRANSACTION_TYPE_LEGACY or tx.payload.type_ is None +class BlobSignedTransaction(SignedTransaction): + payload: BlobTransactionPayload + signature: TransactionSignature + +class AnySignedTransaction(OneOf[SignedTransaction]): + @classmethod + def select_variant(cls, value: SignedTransaction) -> Type[SignedTransaction]: + if value.payload.type_ == TRANSACTION_TYPE_SSZ: + if value.payload.blob_versioned_hashes is not None: + return BlobSignedTransaction + return BasicSignedTransaction + + if value.payload.type_ == TRANSACTION_TYPE_EIP4844: + return Eip4844SignedTransaction + + if value.payload.type_ == TRANSACTION_TYPE_EIP1559: + return Eip1559SignedTransaction + + if value.payload.type_ == TRANSACTION_TYPE_EIP2930: + return Eip2930SignedTransaction + + if value.payload.type_ == TRANSACTION_TYPE_LEGACY: + return LegacySignedTransaction + + assert value.payload.type_ is None + return ReplayableSignedTransaction ``` Future specifications MAY: - Add fields to the end of `TransactionPayload` and `TransactionSignature` - Convert existing fields to `Optional` -- Relax the validation rules in `check_transaction_supported` +- Define new `Variant` types and update `select_variant` logic -Such changes [do not affect](./eip-7495.md) how existing transactions serialize, merkleize, or validate. +Such changes [do not affect](./eip-7495.md) how existing transactions serialize or merkleize. ![Transaction merkleization](../assets/eip-6493/transaction.png) @@ -226,9 +335,8 @@ def ecdsa_recover_from_address(signature: ByteVector[ECDSA_SIGNATURE_SIZE], uncompressed = public_key.serialize(compressed=False) return ExecutionAddress(keccak(uncompressed[1:])[12:]) -def validate_transaction(tx: SignedTransaction, +def validate_transaction(tx: AnySignedTransaction, chain_id: ChainId): - check_transaction_supported(tx) ecdsa_validate_signature(tx.signature.ecdsa_signature) assert tx.signature.from_ == ecdsa_recover_from_address( tx.signature.ecdsa_signature, @@ -294,12 +402,39 @@ class Receipt(StableContainer[MAX_RECEIPT_FIELDS]): status: Optional[boolean] ``` +Valid receipt types can be defined using [EIP-7495](./eip-7495.md) `Variant`. + +```python +class HomesteadReceipt(Variant[Receipt]): + root: Hash32 + gas_used: uint64 + contract_address: Optional[ExecutionAddress] + logs_bloom: ByteVector[BYTES_PER_LOGS_BLOOM] + logs: List[Log, MAX_LOGS_PER_RECEIPT] + +class BasicReceipt(Variant[Receipt]): + gas_used: uint64 + contract_address: Optional[ExecutionAddress] + logs_bloom: ByteVector[BYTES_PER_LOGS_BLOOM] + logs: List[Log, MAX_LOGS_PER_RECEIPT] + status: boolean + +class AnyReceipt(OneOf[Receipt]): + @classmethod + def select_variant(cls, value: Receipt) -> Type[Receipt]: + if value.status is not None: + return BasicReceipt + + return HomesteadReceipt +``` + Future specifications MAY: - Add fields to the end of `Receipt` - Convert existing fields to `Optional` +- Define new `Variant` types and update `select_variant` logic -Such changes [do not affect](./eip-7495.md) how existing receipts serialize, merkleize, or validate. +Such changes [do not affect](./eip-7495.md) how existing receipts serialize or merkleize. ![Receipt merkleization](../assets/eip-6493/receipt.png) @@ -360,7 +495,7 @@ Mixing the chain ID into the `TransactionDomainData` further allows dropping the ### What about EIP-2718 transaction types? -All SSZ transactions (including future ones) share the single [EIP-2718](./eip-2718.md) transaction type `TRANSACTION_TYPE_SSZ`. Future features can introduce new optional fields as well as new allowed combination of optional fields, as determined by `check_transaction_supported`. +All SSZ transactions (including future ones) share the single [EIP-2718](./eip-2718.md) transaction type `TRANSACTION_TYPE_SSZ`. Future features can introduce new optional fields as well as new allowed combination of optional fields, as determined by `select_variant` in `AnySignedTransaction`. This also reduces combinatorial explosion; for example, the `access_list` property could be made optional for all SSZ transactions without having to double the number of defined transaction types. diff --git a/assets/eip-6493/convert.py b/assets/eip-6493/convert.py index be390a3c97a288..c59cdcd272163d 100644 --- a/assets/eip-6493/convert.py +++ b/assets/eip-6493/convert.py @@ -4,11 +4,11 @@ from ssz_types import * def upgrade_rlp_transaction_to_ssz(pre_bytes: bytes, - chain_id: ChainId) -> SignedTransaction: + chain_id: ChainId) -> AnySignedTransaction: type_ = pre_bytes[0] if type_ == 0x03: # EIP-4844 - pre = decode(pre_bytes[1:], Eip4844SignedTransaction) + pre = decode(pre_bytes[1:], Eip4844SignedRlpTransaction) assert pre.chain_id == chain_id assert pre.signature_y_parity in (0, 1) @@ -19,8 +19,8 @@ def upgrade_rlp_transaction_to_ssz(pre_bytes: bytes, ) from_ = ecdsa_recover_from_address(ecdsa_signature, compute_eip4844_sig_hash(pre)) - return SignedTransaction( - payload=TransactionPayload( + return Eip4844SignedTransaction( + payload=Eip4844TransactionPayload( nonce=pre.nonce, max_fee_per_gas=pre.max_fee_per_gas, gas=pre.gas_limit, @@ -43,7 +43,7 @@ def upgrade_rlp_transaction_to_ssz(pre_bytes: bytes, ) if type_ == 0x02: # EIP-1559 - pre = decode(pre_bytes[1:], Eip1559SignedTransaction) + pre = decode(pre_bytes[1:], Eip1559SignedRlpTransaction) assert pre.chain_id == chain_id assert pre.signature_y_parity in (0, 1) @@ -54,8 +54,8 @@ def upgrade_rlp_transaction_to_ssz(pre_bytes: bytes, ) from_ = ecdsa_recover_from_address(ecdsa_signature, compute_eip1559_sig_hash(pre)) - return SignedTransaction( - payload=TransactionPayload( + return Eip1559SignedTransaction( + payload=Eip1559TransactionPayload( nonce=pre.nonce, max_fee_per_gas=pre.max_fee_per_gas, gas=pre.gas_limit, @@ -76,7 +76,7 @@ def upgrade_rlp_transaction_to_ssz(pre_bytes: bytes, ) if type_ == 0x01: # EIP-2930 - pre = decode(pre_bytes[1:], Eip2930SignedTransaction) + pre = decode(pre_bytes[1:], Eip2930SignedRlpTransaction) assert pre.chainId == chain_id assert pre.signatureYParity in (0, 1) @@ -87,8 +87,8 @@ def upgrade_rlp_transaction_to_ssz(pre_bytes: bytes, ) from_ = ecdsa_recover_from_address(ecdsa_signature, compute_eip2930_sig_hash(pre)) - return SignedTransaction( - payload=TransactionPayload( + return Eip2930SignedTransaction( + payload=Eip2930TransactionPayload( nonce=pre.nonce, max_fee_per_gas=pre.gasPrice, gas=pre.gasLimit, @@ -108,7 +108,7 @@ def upgrade_rlp_transaction_to_ssz(pre_bytes: bytes, ) if 0xc0 <= type_ <= 0xfe: # Legacy - pre = decode(pre_bytes, LegacySignedTransaction) + pre = decode(pre_bytes, LegacySignedRlpTransaction) if pre.v not in (27, 28): # EIP-155 assert pre.v in (2 * chain_id + 35, 2 * chain_id + 36) @@ -119,15 +119,31 @@ def upgrade_rlp_transaction_to_ssz(pre_bytes: bytes, ) from_ = ecdsa_recover_from_address(ecdsa_signature, compute_legacy_sig_hash(pre)) - return SignedTransaction( - payload=TransactionPayload( + if (pre.v not in (27, 28)): + return LegacySignedTransaction( + payload=LegacyTransactionPayload( + nonce=pre.nonce, + max_fee_per_gas=pre.gasprice, + gas=pre.startgas, + to=ExecutionAddress(pre.to) if len(pre.to) > 0 else None, + value=pre.value, + input_=pre.data, + type_=TRANSACTION_TYPE_LEGACY, + ), + signature=TransactionSignature( + from_=from_, + ecdsa_signature=ecdsa_signature, + ), + ) + + return ReplayableSignedTransaction( + payload=ReplayableTransactionPayload( nonce=pre.nonce, max_fee_per_gas=pre.gasprice, gas=pre.startgas, to=ExecutionAddress(pre.to) if len(pre.to) > 0 else None, value=pre.value, input_=pre.data, - type_=TRANSACTION_TYPE_LEGACY if (pre.v not in (27, 28)) else None, ), signature=TransactionSignature( from_=from_, @@ -152,7 +168,7 @@ def compute_contract_address(from_: ExecutionAddress, def upgrade_rlp_receipt_to_ssz(pre_bytes: bytes, prev_cumulative_gas_used: uint64, - transaction: SignedTransaction) -> Receipt: + transaction: AnySignedTransaction) -> AnyReceipt: type_ = pre_bytes[0] if type_ in (0x03, 0x02, 0x01): # EIP-4844, EIP-1559, EIP-2930 @@ -162,14 +178,26 @@ def upgrade_rlp_receipt_to_ssz(pre_bytes: bytes, else: assert False - if len(pre.post_state_or_status) == 32: - root = pre.post_state_or_status - status = None - else: - root = None + if len(pre.post_state_or_status) != 32: status = len(pre.post_state_or_status) > 0 and pre.post_state_or_status[0] != 0 - return Receipt( + return BasicReceipt( + gas_used=pre.cumulative_gas_used - prev_cumulative_gas_used, + contract_address=compute_contract_address( + transaction.signature.from_, + transaction.payload.nonce, + ) if transaction.payload.to is None else None, + logs_bloom=pre.logs_bloom, + logs=[Log( + address=log[0], + topics=log[1], + data=log[2], + ) for log in pre.logs], + status=status, + ) + + root = pre.post_state_or_status + return HomesteadReceipt( root=root, gas_used=pre.cumulative_gas_used - prev_cumulative_gas_used, contract_address=compute_contract_address( @@ -182,12 +210,11 @@ def upgrade_rlp_receipt_to_ssz(pre_bytes: bytes, topics=log[1], data=log[2], ) for log in pre.logs], - status=status, ) def upgrade_rlp_receipts_to_ssz(pre_bytes_list: PyList[bytes], chain_id: ChainId, - transactions: PyList[SignedTransaction]) -> PyList[Receipt]: + transactions: PyList[AnySignedTransaction]) -> PyList[AnyReceipt]: receipts = [] cumulative_gas_used = 0 for i, pre_bytes in enumerate(pre_bytes_list): diff --git a/assets/eip-6493/rlp_types.py b/assets/eip-6493/rlp_types.py index 4b705e64ca15c2..5c5be6791e70a7 100644 --- a/assets/eip-6493/rlp_types.py +++ b/assets/eip-6493/rlp_types.py @@ -6,7 +6,7 @@ class Hash32(Bytes32): pass -class LegacyTransaction(Serializable): +class LegacyRlpTransaction(Serializable): fields = ( ('nonce', big_endian_int), ('gasprice', big_endian_int), @@ -16,7 +16,7 @@ class LegacyTransaction(Serializable): ('data', binary), ) -class LegacySignedTransaction(Serializable): +class LegacySignedRlpTransaction(Serializable): fields = ( ('nonce', big_endian_int), ('gasprice', big_endian_int), @@ -29,9 +29,9 @@ class LegacySignedTransaction(Serializable): ('s', big_endian_int), ) -def compute_legacy_sig_hash(tx: LegacySignedTransaction) -> Hash32: +def compute_legacy_sig_hash(tx: LegacySignedRlpTransaction) -> Hash32: if tx.v not in (27, 28): # EIP-155 - return Hash32(keccak(encode(LegacySignedTransaction( + return Hash32(keccak(encode(LegacySignedRlpTransaction( nonce=tx.nonce, gasprice=tx.gasprice, startgas=tx.startgas, @@ -43,7 +43,7 @@ def compute_legacy_sig_hash(tx: LegacySignedTransaction) -> Hash32: s=0, )))) else: - return Hash32(keccak(encode(LegacyTransaction( + return Hash32(keccak(encode(LegacyRlpTransaction( nonce=tx.nonce, gasprice=tx.gasprice, startgas=tx.startgas, @@ -52,10 +52,10 @@ def compute_legacy_sig_hash(tx: LegacySignedTransaction) -> Hash32: data=tx.data, )))) -def compute_legacy_tx_hash(tx: LegacySignedTransaction) -> Hash32: +def compute_legacy_tx_hash(tx: LegacySignedRlpTransaction) -> Hash32: return Hash32(keccak(encode(tx))) -class Eip2930Transaction(Serializable): +class Eip2930RlpTransaction(Serializable): fields = ( ('chainId', big_endian_int), ('nonce', big_endian_int), @@ -70,7 +70,7 @@ class Eip2930Transaction(Serializable): ]))), ) -class Eip2930SignedTransaction(Serializable): +class Eip2930SignedRlpTransaction(Serializable): fields = ( ('chainId', big_endian_int), ('nonce', big_endian_int), @@ -88,8 +88,8 @@ class Eip2930SignedTransaction(Serializable): ('signatureS', big_endian_int), ) -def compute_eip2930_sig_hash(tx: Eip2930SignedTransaction) -> Hash32: - return Hash32(keccak(bytes([0x01]) + encode(Eip2930Transaction( +def compute_eip2930_sig_hash(tx: Eip2930SignedRlpTransaction) -> Hash32: + return Hash32(keccak(bytes([0x01]) + encode(Eip2930RlpTransaction( chainId=tx.chainId, nonce=tx.nonce, gasPrice=tx.gasPrice, @@ -100,10 +100,10 @@ def compute_eip2930_sig_hash(tx: Eip2930SignedTransaction) -> Hash32: accessList=tx.accessList, )))) -def compute_eip2930_tx_hash(tx: Eip2930SignedTransaction) -> Hash32: +def compute_eip2930_tx_hash(tx: Eip2930SignedRlpTransaction) -> Hash32: return Hash32(keccak(bytes([0x01]) + encode(tx))) -class Eip1559Transaction(Serializable): +class Eip1559RlpTransaction(Serializable): fields = ( ('chain_id', big_endian_int), ('nonce', big_endian_int), @@ -119,7 +119,7 @@ class Eip1559Transaction(Serializable): ]))), ) -class Eip1559SignedTransaction(Serializable): +class Eip1559SignedRlpTransaction(Serializable): fields = ( ('chain_id', big_endian_int), ('nonce', big_endian_int), @@ -138,8 +138,8 @@ class Eip1559SignedTransaction(Serializable): ('signature_s', big_endian_int), ) -def compute_eip1559_sig_hash(tx: Eip1559SignedTransaction) -> Hash32: - return Hash32(keccak(bytes([0x02]) + encode(Eip1559Transaction( +def compute_eip1559_sig_hash(tx: Eip1559SignedRlpTransaction) -> Hash32: + return Hash32(keccak(bytes([0x02]) + encode(Eip1559RlpTransaction( chain_id=tx.chain_id, nonce=tx.nonce, max_priority_fee_per_gas=tx.max_priority_fee_per_gas, @@ -151,10 +151,10 @@ def compute_eip1559_sig_hash(tx: Eip1559SignedTransaction) -> Hash32: access_list=tx.access_list, )))) -def compute_eip1559_tx_hash(tx: Eip1559SignedTransaction) -> Hash32: +def compute_eip1559_tx_hash(tx: Eip1559SignedRlpTransaction) -> Hash32: return Hash32(keccak(bytes([0x02]) + encode(tx))) -class Eip4844Transaction(Serializable): +class Eip4844RlpTransaction(Serializable): fields = ( ('chain_id', big_endian_int), ('nonce', big_endian_int), @@ -172,7 +172,7 @@ class Eip4844Transaction(Serializable): ('blob_versioned_hashes', CountableList(Binary(32, 32))), ) -class Eip4844SignedTransaction(Serializable): +class Eip4844SignedRlpTransaction(Serializable): fields = ( ('chain_id', big_endian_int), ('nonce', big_endian_int), @@ -193,8 +193,8 @@ class Eip4844SignedTransaction(Serializable): ('signature_s', big_endian_int), ) -def compute_eip4844_sig_hash(tx: Eip4844SignedTransaction) -> Hash32: - return Hash32(keccak(bytes([0x03]) + encode(Eip4844Transaction( +def compute_eip4844_sig_hash(tx: Eip4844SignedRlpTransaction) -> Hash32: + return Hash32(keccak(bytes([0x03]) + encode(Eip4844RlpTransaction( chain_id=tx.chain_id, nonce=tx.nonce, max_priority_fee_per_gas=tx.max_priority_fee_per_gas, @@ -208,7 +208,7 @@ def compute_eip4844_sig_hash(tx: Eip4844SignedTransaction) -> Hash32: blob_versioned_hashes=tx.blob_versioned_hashes, )))) -def compute_eip4844_tx_hash(tx: Eip4844SignedTransaction) -> Hash32: +def compute_eip4844_tx_hash(tx: Eip4844SignedRlpTransaction) -> Hash32: return Hash32(keccak(bytes([0x03]) + encode(tx))) class RlpReceipt(Serializable): diff --git a/assets/eip-6493/ssz_types.py b/assets/eip-6493/ssz_types.py index f10d2c1867846a..8273d8705575a3 100644 --- a/assets/eip-6493/ssz_types.py +++ b/assets/eip-6493/ssz_types.py @@ -4,14 +4,14 @@ path.append(current_dir) path.append(current_dir + '/../eip-7495') -from typing import Optional +from typing import Optional, Type from eth_hash.auto import keccak from remerkleable.basic import boolean, uint8, uint64, uint256 from remerkleable.byte_arrays import ByteList, ByteVector, Bytes32 from remerkleable.complex import Container, List from rlp_types import Hash32 from secp256k1 import ECDSA, PublicKey -from stable_container import StableContainer +from stable_container import OneOf, StableContainer, Variant, Variant class TransactionType(uint8): pass @@ -69,26 +69,131 @@ class SignedTransaction(Container): payload: TransactionPayload signature: TransactionSignature -def check_transaction_supported(tx: SignedTransaction): - if tx.payload.max_fee_per_blob_gas is not None: - assert tx.payload.blob_versioned_hashes is not None - assert tx.payload.max_priority_fee_per_gas is not None - assert tx.payload.to is not None - else: - assert tx.payload.blob_versioned_hashes is None - - if tx.payload.max_priority_fee_per_gas is not None: - assert tx.payload.access_list is not None - - if tx.payload.type_ != TRANSACTION_TYPE_SSZ: - if tx.payload.max_fee_per_blob_gas is not None: - assert tx.payload.type_ == TRANSACTION_TYPE_EIP4844 - elif tx.payload.max_priority_fee_per_gas is not None: - assert tx.payload.type_ == TRANSACTION_TYPE_EIP1559 - elif tx.payload.access_list is not None: - assert tx.payload.type_ == TRANSACTION_TYPE_EIP2930 - else: - assert tx.payload.type_ == TRANSACTION_TYPE_LEGACY or tx.payload.type_ is None +class ReplayableTransactionPayload(Variant[TransactionPayload]): + nonce: uint64 + max_fee_per_gas: uint256 + gas: uint64 + to: Optional[ExecutionAddress] + value: uint256 + input_: ByteList[MAX_CALLDATA_SIZE] + +class ReplayableSignedTransaction(SignedTransaction): + payload: ReplayableTransactionPayload + signature: TransactionSignature + +class LegacyTransactionPayload(Variant[TransactionPayload]): + nonce: uint64 + max_fee_per_gas: uint256 + gas: uint64 + to: Optional[ExecutionAddress] + value: uint256 + input_: ByteList[MAX_CALLDATA_SIZE] + type_: TransactionType + +class LegacySignedTransaction(SignedTransaction): + payload: LegacyTransactionPayload + signature: TransactionSignature + +class Eip2930TransactionPayload(Variant[TransactionPayload]): + nonce: uint64 + max_fee_per_gas: uint256 + gas: uint64 + to: Optional[ExecutionAddress] + value: uint256 + input_: ByteList[MAX_CALLDATA_SIZE] + type_: TransactionType + access_list: List[AccessTuple, MAX_ACCESS_LIST_SIZE] + +class Eip2930SignedTransaction(SignedTransaction): + payload: Eip2930TransactionPayload + signature: TransactionSignature + +class Eip1559TransactionPayload(Variant[TransactionPayload]): + nonce: uint64 + max_fee_per_gas: uint256 + gas: uint64 + to: Optional[ExecutionAddress] + value: uint256 + input_: ByteList[MAX_CALLDATA_SIZE] + type_: TransactionType + access_list: List[AccessTuple, MAX_ACCESS_LIST_SIZE] + max_priority_fee_per_gas: uint256 + +class Eip1559SignedTransaction(SignedTransaction): + payload: Eip1559TransactionPayload + signature: TransactionSignature + +class Eip4844TransactionPayload(Variant[TransactionPayload]): + nonce: uint64 + max_fee_per_gas: uint256 + gas: uint64 + to: ExecutionAddress + value: uint256 + input_: ByteList[MAX_CALLDATA_SIZE] + type_: TransactionType + access_list: List[AccessTuple, MAX_ACCESS_LIST_SIZE] + max_priority_fee_per_gas: uint256 + max_fee_per_blob_gas: uint256 + blob_versioned_hashes: List[VersionedHash, MAX_BLOB_COMMITMENTS_PER_BLOCK] + +class Eip4844SignedTransaction(SignedTransaction): + payload: Eip4844TransactionPayload + signature: TransactionSignature + +class BasicTransactionPayload(Variant[TransactionPayload]): + nonce: uint64 + max_fee_per_gas: uint256 + gas: uint64 + to: Optional[ExecutionAddress] + value: uint256 + input_: ByteList[MAX_CALLDATA_SIZE] + type_: TransactionType + access_list: List[AccessTuple, MAX_ACCESS_LIST_SIZE] + max_priority_fee_per_gas: uint256 + +class BasicSignedTransaction(SignedTransaction): + payload: BasicTransactionPayload + signature: TransactionSignature + +class BlobTransactionPayload(Variant[TransactionPayload]): + nonce: uint64 + max_fee_per_gas: uint256 + gas: uint64 + to: ExecutionAddress + value: uint256 + input_: ByteList[MAX_CALLDATA_SIZE] + type_: TransactionType + access_list: List[AccessTuple, MAX_ACCESS_LIST_SIZE] + max_priority_fee_per_gas: uint256 + max_fee_per_blob_gas: uint256 + blob_versioned_hashes: List[VersionedHash, MAX_BLOB_COMMITMENTS_PER_BLOCK] + +class BlobSignedTransaction(SignedTransaction): + payload: BlobTransactionPayload + signature: TransactionSignature + +class AnySignedTransaction(OneOf[SignedTransaction]): + @classmethod + def select_variant(cls, value: SignedTransaction) -> Type[SignedTransaction]: + if value.payload.type_ == TRANSACTION_TYPE_SSZ: + if value.payload.blob_versioned_hashes is not None: + return BlobSignedTransaction + return BasicSignedTransaction + + if value.payload.type_ == TRANSACTION_TYPE_EIP4844: + return Eip4844SignedTransaction + + if value.payload.type_ == TRANSACTION_TYPE_EIP1559: + return Eip1559SignedTransaction + + if value.payload.type_ == TRANSACTION_TYPE_EIP2930: + return Eip2930SignedTransaction + + if value.payload.type_ == TRANSACTION_TYPE_LEGACY: + return LegacySignedTransaction + + assert value.payload.type_ is None + return ReplayableSignedTransaction class Root(Bytes32): pass @@ -152,9 +257,8 @@ def ecdsa_recover_from_address(signature: ByteVector[ECDSA_SIGNATURE_SIZE], from tx_hashes import compute_sig_hash, compute_tx_hash -def validate_transaction(tx: SignedTransaction, +def validate_transaction(tx: AnySignedTransaction, chain_id: ChainId): - check_transaction_supported(tx) ecdsa_validate_signature(tx.signature.ecdsa_signature) assert tx.signature.from_ == ecdsa_recover_from_address( tx.signature.ecdsa_signature, @@ -181,3 +285,25 @@ class Receipt(StableContainer[MAX_RECEIPT_FIELDS]): # EIP-658 status: Optional[boolean] + +class HomesteadReceipt(Variant[Receipt]): + root: Hash32 + gas_used: uint64 + contract_address: Optional[ExecutionAddress] + logs_bloom: ByteVector[BYTES_PER_LOGS_BLOOM] + logs: List[Log, MAX_LOGS_PER_RECEIPT] + +class BasicReceipt(Variant[Receipt]): + gas_used: uint64 + contract_address: Optional[ExecutionAddress] + logs_bloom: ByteVector[BYTES_PER_LOGS_BLOOM] + logs: List[Log, MAX_LOGS_PER_RECEIPT] + status: boolean + +class AnyReceipt(OneOf[Receipt]): + @classmethod + def select_variant(cls, value: Receipt) -> Type[Receipt]: + if value.status is not None: + return BasicReceipt + + return HomesteadReceipt diff --git a/assets/eip-6493/tx_hashes.py b/assets/eip-6493/tx_hashes.py index 2bfe05a7139c50..88020ecb57c7d8 100644 --- a/assets/eip-6493/tx_hashes.py +++ b/assets/eip-6493/tx_hashes.py @@ -1,17 +1,12 @@ from rlp_types import * from ssz_types import * -def recover_legacy_transaction(tx: SignedTransaction, - chain_id: ChainId) -> LegacySignedTransaction: - assert tx.payload.type_ == TRANSACTION_TYPE_LEGACY or tx.payload.type_ is None - +def recover_replayable_rlp_transaction(tx: ReplayableSignedTransaction, + chain_id: ChainId) -> LegacySignedRlpTransaction: y_parity, r, s = ecdsa_unpack_signature(tx.signature.ecdsa_signature) - if tx.payload.type_ == TRANSACTION_TYPE_LEGACY: # EIP-155 - v = uint256(1 if y_parity else 0) + 35 + chain_id * 2 - else: - v = uint256(1 if y_parity else 0) + 27 + v = uint256(1 if y_parity else 0) + 27 - return LegacySignedTransaction( + return LegacySignedRlpTransaction( nonce=tx.payload.nonce, gasprice=tx.payload.max_fee_per_gas, startgas=tx.payload.gas, @@ -23,13 +18,28 @@ def recover_legacy_transaction(tx: SignedTransaction, s=s, ) -def recover_eip2930_transaction(tx: SignedTransaction, - chain_id: ChainId) -> Eip2930SignedTransaction: - assert tx.payload.type_ == TRANSACTION_TYPE_EIP2930 +def recover_legacy_rlp_transaction(tx: LegacySignedTransaction, + chain_id: ChainId) -> LegacySignedRlpTransaction: + y_parity, r, s = ecdsa_unpack_signature(tx.signature.ecdsa_signature) + v = uint256(1 if y_parity else 0) + 35 + chain_id * 2 + return LegacySignedRlpTransaction( + nonce=tx.payload.nonce, + gasprice=tx.payload.max_fee_per_gas, + startgas=tx.payload.gas, + to=bytes(tx.payload.to if tx.payload.to is not None else []), + value=tx.payload.value, + data=tx.payload.input_, + v=v, + r=r, + s=s, + ) + +def recover_eip2930_rlp_transaction(tx: Eip2930SignedTransaction, + chain_id: ChainId) -> Eip2930SignedRlpTransaction: y_parity, r, s = ecdsa_unpack_signature(tx.signature.ecdsa_signature) - return Eip2930SignedTransaction( + return Eip2930SignedRlpTransaction( chainId=chain_id, nonce=tx.payload.nonce, gasPrice=tx.payload.max_fee_per_gas, @@ -46,13 +56,11 @@ def recover_eip2930_transaction(tx: SignedTransaction, signatureS=s, ) -def recover_eip1559_transaction(tx: SignedTransaction, - chain_id: ChainId) -> Eip1559SignedTransaction: - assert tx.payload.type_ == TRANSACTION_TYPE_EIP1559 - +def recover_eip1559_rlp_transaction(tx: Eip1559SignedTransaction, + chain_id: ChainId) -> Eip1559SignedRlpTransaction: y_parity, r, s = ecdsa_unpack_signature(tx.signature.ecdsa_signature) - return Eip1559SignedTransaction( + return Eip1559SignedRlpTransaction( chain_id=chain_id, nonce=tx.payload.nonce, max_priority_fee_per_gas=tx.payload.max_priority_fee_per_gas, @@ -70,13 +78,11 @@ def recover_eip1559_transaction(tx: SignedTransaction, signature_s=s, ) -def recover_eip4844_transaction(tx: SignedTransaction, - chain_id: ChainId) -> Eip4844SignedTransaction: - assert tx.payload.type_ == TRANSACTION_TYPE_EIP4844 - +def recover_eip4844_rlp_transaction(tx: Eip4844SignedTransaction, + chain_id: ChainId) -> Eip4844SignedRlpTransaction: y_parity, r, s = ecdsa_unpack_signature(tx.signature.ecdsa_signature) - return Eip4844SignedTransaction( + return Eip4844SignedRlpTransaction( chain_id=chain_id, nonce=tx.payload.nonce, max_priority_fee_per_gas=tx.payload.max_priority_fee_per_gas, @@ -96,52 +102,58 @@ def recover_eip4844_transaction(tx: SignedTransaction, signature_s=s, ) -def compute_sig_hash(tx: SignedTransaction, +def compute_sig_hash(tx: AnySignedTransaction, chain_id: ChainId) -> Hash32: - type_ = tx.payload.type_ - - if type_ == TRANSACTION_TYPE_SSZ: + if ( + isinstance(tx, BasicSignedTransaction) or + isinstance(tx, BlobSignedTransaction) + ): return compute_ssz_sig_hash(tx.payload, chain_id) - if type_ == TRANSACTION_TYPE_EIP4844: - pre = recover_eip4844_transaction(tx, chain_id) + if isinstance(tx, Eip4844SignedTransaction): + pre = recover_eip4844_rlp_transaction(tx, chain_id) return compute_eip4844_sig_hash(pre) - if type_ == TRANSACTION_TYPE_EIP1559: - pre = recover_eip1559_transaction(tx, chain_id) + if isinstance(tx, Eip1559SignedTransaction): + pre = recover_eip1559_rlp_transaction(tx, chain_id) return compute_eip1559_sig_hash(pre) - if type_ == TRANSACTION_TYPE_EIP2930: - pre = recover_eip2930_transaction(tx, chain_id) + if isinstance(tx, Eip2930SignedTransaction): + pre = recover_eip2930_rlp_transaction(tx, chain_id) return compute_eip2930_sig_hash(pre) - if type_ == TRANSACTION_TYPE_LEGACY or type_ is None: - pre = recover_legacy_transaction(tx, chain_id) + if isinstance(tx, LegacySignedTransaction): + pre = recover_legacy_rlp_transaction(tx, chain_id) return compute_legacy_sig_hash(pre) - assert False + assert isinstance(tx, ReplayableSignedTransaction) + pre = recover_replayable_rlp_transaction(tx, chain_id) + return compute_legacy_sig_hash(pre) -def compute_tx_hash(tx: SignedTransaction, +def compute_tx_hash(tx: AnySignedTransaction, chain_id: ChainId) -> Hash32: - type_ = tx.payload.type_ - - if type_ == TRANSACTION_TYPE_SSZ: + if ( + isinstance(tx, BasicSignedTransaction) or + isinstance(tx, BlobSignedTransaction) + ): return compute_ssz_tx_hash(tx.payload, chain_id) - if type_ == TRANSACTION_TYPE_EIP4844: - pre = recover_eip4844_transaction(tx, chain_id) + if isinstance(tx, Eip4844SignedTransaction): + pre = recover_eip4844_rlp_transaction(tx, chain_id) return compute_eip4844_tx_hash(pre) - if type_ == TRANSACTION_TYPE_EIP1559: - pre = recover_eip1559_transaction(tx, chain_id) + if isinstance(tx, Eip1559SignedTransaction): + pre = recover_eip1559_rlp_transaction(tx, chain_id) return compute_eip1559_tx_hash(pre) - if type_ == TRANSACTION_TYPE_EIP2930: - pre = recover_eip2930_transaction(tx, chain_id) + if isinstance(tx, Eip2930SignedTransaction): + pre = recover_eip2930_rlp_transaction(tx, chain_id) return compute_eip2930_tx_hash(pre) - if type_ == TRANSACTION_TYPE_LEGACY or type_ is None: - pre = recover_legacy_transaction(tx, chain_id) + if isinstance(tx, LegacySignedTransaction): + pre = recover_legacy_rlp_transaction(tx, chain_id) return compute_legacy_tx_hash(pre) - assert False + assert isinstance(tx, ReplayableSignedTransaction) + pre = recover_replayable_rlp_transaction(tx, chain_id) + return compute_legacy_tx_hash(pre)