From ec89dd61af46bd590c087cc0a8a86b856e391423 Mon Sep 17 00:00:00 2001 From: Etan Kissling Date: Wed, 12 Jun 2024 14:36:22 +0200 Subject: [PATCH] Update EIP-6493: Cleanups for latest `StableContainer` specs Merged by EIP-Bot. --- EIPS/eip-6493.md | 76 ++++++++++++++++---------------- assets/eip-6493/convert.py | 16 +++---- assets/eip-6493/convert_tests.py | 16 +++++++ assets/eip-6493/ssz_types.py | 70 ++++++++++++++--------------- assets/eip-6493/tx_hashes.py | 4 +- 5 files changed, 98 insertions(+), 84 deletions(-) diff --git a/EIPS/eip-6493.md b/EIPS/eip-6493.md index e3156e2eb3ec6..466c36eba1304 100644 --- a/EIPS/eip-6493.md +++ b/EIPS/eip-6493.md @@ -117,7 +117,7 @@ class TransactionPayload(StableContainer[MAX_TRANSACTION_PAYLOAD_FIELDS]): gas: Optional[uint64] to: Optional[ExecutionAddress] value: Optional[uint256] - input_: ByteList[MAX_CALLDATA_SIZE] + input_: Optional[ByteList[MAX_CALLDATA_SIZE]] # EIP-2930 access_list: Optional[List[AccessTuple, MAX_ACCESS_LIST_SIZE]] @@ -147,6 +147,10 @@ class BlobFeesPerGas(Profile[FeesPerGas]): regular: FeePerGas blob: FeePerGas +class EcdsaTransactionSignature(Profile[TransactionSignature]): + from_: Optional[ExecutionAddress] + ecdsa_signature: Optional[ByteVector[ECDSA_SIGNATURE_SIZE]] + class ReplayableTransactionPayload(Profile[TransactionPayload]): type_: TransactionType nonce: uint64 @@ -158,7 +162,7 @@ class ReplayableTransactionPayload(Profile[TransactionPayload]): class ReplayableTransaction(Container): payload: ReplayableTransactionPayload - signature: TransactionSignature + signature: EcdsaTransactionSignature class LegacyTransactionPayload(Profile[TransactionPayload]): type_: TransactionType @@ -172,7 +176,7 @@ class LegacyTransactionPayload(Profile[TransactionPayload]): class LegacyTransaction(Container): payload: LegacyTransactionPayload - signature: TransactionSignature + signature: EcdsaTransactionSignature class Eip2930TransactionPayload(Profile[TransactionPayload]): type_: TransactionType @@ -187,7 +191,7 @@ class Eip2930TransactionPayload(Profile[TransactionPayload]): class Eip2930Transaction(Container): payload: Eip2930TransactionPayload - signature: TransactionSignature + signature: EcdsaTransactionSignature class Eip1559TransactionPayload(Profile[TransactionPayload]): type_: TransactionType @@ -203,7 +207,7 @@ class Eip1559TransactionPayload(Profile[TransactionPayload]): class Eip1559Transaction(Container): payload: Eip1559TransactionPayload - signature: TransactionSignature + signature: EcdsaTransactionSignature class Eip4844TransactionPayload(Profile[TransactionPayload]): type_: TransactionType @@ -220,7 +224,7 @@ class Eip4844TransactionPayload(Profile[TransactionPayload]): class Eip4844Transaction(Container): payload: Eip4844TransactionPayload - signature: TransactionSignature + signature: EcdsaTransactionSignature class BasicTransactionPayload(Profile[TransactionPayload]): chain_id: ChainId @@ -235,7 +239,7 @@ class BasicTransactionPayload(Profile[TransactionPayload]): class BasicTransaction(Container): payload: BasicTransactionPayload - signature: TransactionSignature + signature: EcdsaTransactionSignature class BlobTransactionPayload(Profile[TransactionPayload]): chain_id: ChainId @@ -251,36 +255,33 @@ class BlobTransactionPayload(Profile[TransactionPayload]): class BlobTransaction(Container): payload: BlobTransactionPayload - signature: TransactionSignature + signature: EcdsaTransactionSignature -class AnyTransaction(OneOf[Transaction]): - @classmethod - def select_from_base(cls, value: Transaction) -> Type[Transaction]: - if value.payload.type_ is None: - if value.payload.blob_versioned_hashes is not None: - return BlobTransaction - return BasicTransaction +def select_transaction_profile(cls, value: Transaction) -> Type[Profile]: + if value.payload.type_ is None: + if value.payload.blob_versioned_hashes is not None: + return BlobTransaction + return BasicTransaction - if value.payload.type_ == TRANSACTION_TYPE_EIP4844: - return Eip4844Transaction + if value.payload.type_ == TRANSACTION_TYPE_EIP4844: + return Eip4844Transaction - if value.payload.type_ == TRANSACTION_TYPE_EIP1559: - return Eip1559Transaction + if value.payload.type_ == TRANSACTION_TYPE_EIP1559: + return Eip1559Transaction - if value.payload.type_ == TRANSACTION_TYPE_EIP2930: - return Eip2930Transaction + if value.payload.type_ == TRANSACTION_TYPE_EIP2930: + return Eip2930Transaction - if value.payload.chain_id is not None: - return LegacyTransaction + if value.payload.chain_id is not None: + return LegacyTransaction - return ReplayableTransaction + return ReplayableTransaction ``` Future specifications MAY: - Append fields to `TransactionPayload` and `TransactionSignature` -- Convert existing fields to `Optional` -- Define new `Profile` types and update `select_from_base` logic +- Adjust `Profile` types and update `select_transaction_profile` logic Such changes [do not affect](./eip-7495.md) how existing transactions serialize or merkleize. @@ -344,7 +345,7 @@ 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: AnyTransaction): +def validate_transaction(tx): ecdsa_validate_signature(tx.signature.ecdsa_signature) assert tx.signature.from_ == ecdsa_recover_from_address( tx.signature.ecdsa_signature, @@ -401,10 +402,10 @@ class Log(Container): class Receipt(StableContainer[MAX_RECEIPT_FIELDS]): root: Optional[Hash32] - gas_used: uint64 + gas_used: Optional[uint64] contract_address: Optional[ExecutionAddress] - logs_bloom: ByteVector[BYTES_PER_LOGS_BLOOM] - logs: List[Log, MAX_LOGS_PER_RECEIPT] + logs_bloom: Optional[ByteVector[BYTES_PER_LOGS_BLOOM]] + logs: Optional[List[Log, MAX_LOGS_PER_RECEIPT]] # EIP-658 status: Optional[boolean] @@ -427,20 +428,17 @@ class BasicReceipt(Profile[Receipt]): logs: List[Log, MAX_LOGS_PER_RECEIPT] status: boolean -class AnyReceipt(OneOf[Receipt]): - @classmethod - def select_from_base(cls, value: Receipt) -> Type[Receipt]: - if value.status is not None: - return BasicReceipt +def select_receipt_profile(value: Receipt) -> Type[Profile]: + if value.status is not None: + return BasicReceipt - return HomesteadReceipt + return HomesteadReceipt ``` Future specifications MAY: - Add fields to the end of `Receipt` -- Convert existing fields to `Optional` -- Define new `Profile` types and update `select_from_base` logic +- Adjust `Profile` types and update `select_receipt_profile` logic Such changes [do not affect](./eip-7495.md) how existing receipts serialize or merkleize. @@ -539,7 +537,7 @@ If other SSZ objects are being signed in the future, e.g., messages, it must be ### 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 `select_from_base` in `AnyTransaction`. +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_transaction_profile`. 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 ff7c5b114174f..8d6ed51d1eb4e 100644 --- a/assets/eip-6493/convert.py +++ b/assets/eip-6493/convert.py @@ -3,7 +3,7 @@ from rlp_types import * from ssz_types import * -def upgrade_rlp_transaction_to_ssz(pre_bytes: bytes) -> AnyTransaction: +def upgrade_rlp_transaction_to_ssz(pre_bytes: bytes): type_ = pre_bytes[0] if type_ == 0x03: # EIP-4844 @@ -39,7 +39,7 @@ def upgrade_rlp_transaction_to_ssz(pre_bytes: bytes) -> AnyTransaction: ), blob_versioned_hashes=pre.blob_versioned_hashes, ), - signature=TransactionSignature( + signature=EcdsaTransactionSignature( from_=from_, ecdsa_signature=ecdsa_signature, ), @@ -75,7 +75,7 @@ def upgrade_rlp_transaction_to_ssz(pre_bytes: bytes) -> AnyTransaction: regular=pre.max_priority_fee_per_gas, ), ), - signature=TransactionSignature( + signature=EcdsaTransactionSignature( from_=from_, ecdsa_signature=ecdsa_signature, ), @@ -108,7 +108,7 @@ def upgrade_rlp_transaction_to_ssz(pre_bytes: bytes) -> AnyTransaction: storage_keys=access_tuple[1] ) for access_tuple in pre.accessList], ), - signature=TransactionSignature( + signature=EcdsaTransactionSignature( from_=from_, ecdsa_signature=ecdsa_signature, ), @@ -138,7 +138,7 @@ def upgrade_rlp_transaction_to_ssz(pre_bytes: bytes) -> AnyTransaction: value=pre.value, input_=pre.data, ), - signature=TransactionSignature( + signature=EcdsaTransactionSignature( from_=from_, ecdsa_signature=ecdsa_signature, ), @@ -156,7 +156,7 @@ def upgrade_rlp_transaction_to_ssz(pre_bytes: bytes) -> AnyTransaction: value=pre.value, input_=pre.data, ), - signature=TransactionSignature( + signature=EcdsaTransactionSignature( from_=from_, ecdsa_signature=ecdsa_signature, ), @@ -179,7 +179,7 @@ def compute_contract_address(from_: ExecutionAddress, def upgrade_rlp_receipt_to_ssz(pre_bytes: bytes, prev_cumulative_gas_used: uint64, - transaction: AnyTransaction) -> AnyReceipt: + transaction): type_ = pre_bytes[0] if type_ in (0x03, 0x02, 0x01): # EIP-4844, EIP-1559, EIP-2930 @@ -224,7 +224,7 @@ def upgrade_rlp_receipt_to_ssz(pre_bytes: bytes, ) def upgrade_rlp_receipts_to_ssz(pre_bytes_list: PyList[bytes], - transactions: PyList[AnyTransaction]) -> PyList[AnyReceipt]: + transactions: PyList) -> PyList: receipts = [] cumulative_gas_used = 0 for i, pre_bytes in enumerate(pre_bytes_list): diff --git a/assets/eip-6493/convert_tests.py b/assets/eip-6493/convert_tests.py index cd0d505c6c91d..93ad463c035c1 100644 --- a/assets/eip-6493/convert_tests.py +++ b/assets/eip-6493/convert_tests.py @@ -75,3 +75,19 @@ class Test: stable_receipt = Receipt(backing=receipts[i].get_backing()) assert stable_receipt.encode_bytes() == tests[i].ssz_receipt_bytes + +transactions_stable = [Transaction(backing=transaction.get_backing()) for transaction in transactions] +assert select_transaction_profile(transactions_stable[0]) is ReplayableTransaction +assert select_transaction_profile(transactions_stable[1]) is ReplayableTransaction +assert select_transaction_profile(transactions_stable[2]) is LegacyTransaction +assert select_transaction_profile(transactions_stable[3]) is Eip2930Transaction +assert select_transaction_profile(transactions_stable[4]) is Eip1559Transaction +assert select_transaction_profile(transactions_stable[5]) is Eip1559Transaction + +receipts_stable = [Receipt(backing=receipt.get_backing()) for receipt in receipts] +assert select_receipt_profile(receipts_stable[0]) is HomesteadReceipt +assert select_receipt_profile(receipts_stable[1]) is BasicReceipt +assert select_receipt_profile(receipts_stable[2]) is BasicReceipt +assert select_receipt_profile(receipts_stable[3]) is BasicReceipt +assert select_receipt_profile(receipts_stable[4]) is BasicReceipt +assert select_receipt_profile(receipts_stable[5]) is BasicReceipt diff --git a/assets/eip-6493/ssz_types.py b/assets/eip-6493/ssz_types.py index 6c0f457024f3a..25ac152f09185 100644 --- a/assets/eip-6493/ssz_types.py +++ b/assets/eip-6493/ssz_types.py @@ -11,7 +11,7 @@ from remerkleable.complex import Container, List from rlp_types import Hash32 from secp256k1 import ECDSA, PublicKey -from stable_container import OneOf, Profile, StableContainer +from stable_container import Profile, StableContainer class TransactionType(uint8): pass @@ -65,7 +65,7 @@ class TransactionPayload(StableContainer[MAX_TRANSACTION_PAYLOAD_FIELDS]): gas: Optional[uint64] to: Optional[ExecutionAddress] value: Optional[uint256] - input_: ByteList[MAX_CALLDATA_SIZE] + input_: Optional[ByteList[MAX_CALLDATA_SIZE]] # EIP-2930 access_list: Optional[List[AccessTuple, MAX_ACCESS_LIST_SIZE]] @@ -91,6 +91,10 @@ class BlobFeesPerGas(Profile[FeesPerGas]): regular: FeePerGas blob: FeePerGas +class EcdsaTransactionSignature(Profile[TransactionSignature]): + from_: Optional[ExecutionAddress] + ecdsa_signature: Optional[ByteVector[ECDSA_SIGNATURE_SIZE]] + class ReplayableTransactionPayload(Profile[TransactionPayload]): type_: TransactionType nonce: uint64 @@ -102,7 +106,7 @@ class ReplayableTransactionPayload(Profile[TransactionPayload]): class ReplayableTransaction(Container): payload: ReplayableTransactionPayload - signature: TransactionSignature + signature: EcdsaTransactionSignature class LegacyTransactionPayload(Profile[TransactionPayload]): type_: TransactionType @@ -116,7 +120,7 @@ class LegacyTransactionPayload(Profile[TransactionPayload]): class LegacyTransaction(Container): payload: LegacyTransactionPayload - signature: TransactionSignature + signature: EcdsaTransactionSignature class Eip2930TransactionPayload(Profile[TransactionPayload]): type_: TransactionType @@ -131,7 +135,7 @@ class Eip2930TransactionPayload(Profile[TransactionPayload]): class Eip2930Transaction(Container): payload: Eip2930TransactionPayload - signature: TransactionSignature + signature: EcdsaTransactionSignature class Eip1559TransactionPayload(Profile[TransactionPayload]): type_: TransactionType @@ -147,7 +151,7 @@ class Eip1559TransactionPayload(Profile[TransactionPayload]): class Eip1559Transaction(Container): payload: Eip1559TransactionPayload - signature: TransactionSignature + signature: EcdsaTransactionSignature class Eip4844TransactionPayload(Profile[TransactionPayload]): type_: TransactionType @@ -164,7 +168,7 @@ class Eip4844TransactionPayload(Profile[TransactionPayload]): class Eip4844Transaction(Container): payload: Eip4844TransactionPayload - signature: TransactionSignature + signature: EcdsaTransactionSignature class BasicTransactionPayload(Profile[TransactionPayload]): chain_id: ChainId @@ -179,7 +183,7 @@ class BasicTransactionPayload(Profile[TransactionPayload]): class BasicTransaction(Container): payload: BasicTransactionPayload - signature: TransactionSignature + signature: EcdsaTransactionSignature class BlobTransactionPayload(Profile[TransactionPayload]): chain_id: ChainId @@ -195,29 +199,27 @@ class BlobTransactionPayload(Profile[TransactionPayload]): class BlobTransaction(Container): payload: BlobTransactionPayload - signature: TransactionSignature + signature: EcdsaTransactionSignature -class AnyTransaction(OneOf[Transaction]): - @classmethod - def select_from_base(cls, value: Transaction) -> Type[Transaction]: - if value.payload.type_ is None: - if value.payload.blob_versioned_hashes is not None: - return BlobTransaction - return BasicTransaction +def select_transaction_profile(value: Transaction) -> Type[Profile]: + if value.payload.type_ is None: + if value.payload.blob_versioned_hashes is not None: + return BlobTransaction + return BasicTransaction - if value.payload.type_ == TRANSACTION_TYPE_EIP4844: - return Eip4844Transaction + if value.payload.type_ == TRANSACTION_TYPE_EIP4844: + return Eip4844Transaction - if value.payload.type_ == TRANSACTION_TYPE_EIP1559: - return Eip1559Transaction + if value.payload.type_ == TRANSACTION_TYPE_EIP1559: + return Eip1559Transaction - if value.payload.type_ == TRANSACTION_TYPE_EIP2930: - return Eip2930Transaction + if value.payload.type_ == TRANSACTION_TYPE_EIP2930: + return Eip2930Transaction - if value.payload.chain_id is not None: - return LegacyTransaction + if value.payload.chain_id is not None: + return LegacyTransaction - return ReplayableTransaction + return ReplayableTransaction class Root(Bytes32): pass @@ -273,7 +275,7 @@ def ecdsa_recover_from_address(signature: ByteVector[ECDSA_SIGNATURE_SIZE], from tx_hashes import compute_sig_hash, compute_tx_hash -def validate_transaction(tx: AnyTransaction): +def validate_transaction(tx): ecdsa_validate_signature(tx.signature.ecdsa_signature) assert tx.signature.from_ == ecdsa_recover_from_address( tx.signature.ecdsa_signature, @@ -293,10 +295,10 @@ class Log(Container): class Receipt(StableContainer[MAX_RECEIPT_FIELDS]): root: Optional[Hash32] - gas_used: uint64 + gas_used: Optional[uint64] contract_address: Optional[ExecutionAddress] - logs_bloom: ByteVector[BYTES_PER_LOGS_BLOOM] - logs: List[Log, MAX_LOGS_PER_RECEIPT] + logs_bloom: Optional[ByteVector[BYTES_PER_LOGS_BLOOM]] + logs: Optional[List[Log, MAX_LOGS_PER_RECEIPT]] # EIP-658 status: Optional[boolean] @@ -315,10 +317,8 @@ class BasicReceipt(Profile[Receipt]): logs: List[Log, MAX_LOGS_PER_RECEIPT] status: boolean -class AnyReceipt(OneOf[Receipt]): - @classmethod - def select_from_base(cls, value: Receipt) -> Type[Receipt]: - if value.status is not None: - return BasicReceipt +def select_receipt_profile(value: Receipt) -> Type[Profile]: + if value.status is not None: + return BasicReceipt - return HomesteadReceipt + return HomesteadReceipt diff --git a/assets/eip-6493/tx_hashes.py b/assets/eip-6493/tx_hashes.py index 66503f9f255a6..c71a7450f7fe3 100644 --- a/assets/eip-6493/tx_hashes.py +++ b/assets/eip-6493/tx_hashes.py @@ -102,7 +102,7 @@ def recover_eip4844_rlp_transaction(tx: Eip4844Transaction) -> Eip4844RlpTransac signature_s=s, ) -def compute_sig_hash(tx: AnyTransaction) -> Hash32: +def compute_sig_hash(tx) -> Hash32: if ( isinstance(tx, BasicTransaction) or isinstance(tx, BlobTransaction) @@ -129,7 +129,7 @@ def compute_sig_hash(tx: AnyTransaction) -> Hash32: pre = recover_replayable_rlp_transaction(tx) return compute_legacy_sig_hash(pre) -def compute_tx_hash(tx: AnyTransaction) -> Hash32: +def compute_tx_hash(tx) -> Hash32: if ( isinstance(tx, BasicTransaction) or isinstance(tx, BlobTransaction)