Does it make sense to declare separate SSZ type for every Tx type
Overall, even if you have separate SSZ types, youâd still need additional validation to check invariants. For example, to check that the from
address is correct. Or, to check that the blob_versioned_hashes match the blob in the wrapper. Or that the to
field is present for the blob transaction (itâs optional without using blob). Or that the max_fee_per_gas
is >= max_priority_fee_per_gas
. Note you may also need matching TransactionSignatureXyz
, and somehow check that it is compatible with the payload union.
Checking valid field combinations in EIP-6493 is ~20 lines, mostly to ensure that txns retain the limitations from RLP. It could be exhaustively tested. The full list of supported combos is:
TransactionPayloadReplayable
â original format, not locked to chain ID (no SSZ equivalent)
TransactionPayloadLegacyRlp
â optional to, no access list, no prio fee, no blob
TransactionPayloadEip2930Rlp
â optional to, yes access list, no prio fee, no blob
TransactionPayloadEip1559Rlp
â optional to, yes access list, yes prio fee, no blob
TransactionPayloadEip4844Rlp
â required to, yes access list, yes prio fee, yes blob
TransactionPayloadLegacySsz
â optional to, no access list, no prio fee, no blob
TransactionPayloadEip2930Ssz
â optional to, yes access list, no prio fee, no blob
TransactionPayloadEip1559Ssz
â optional to, yes access list, yes prio fee, no blob
TransactionPayloadEip4844Ssz
â required to, yes access list, yes prio fee, yes blob
Thereâs the problem of combinatorial explosion. For example, if you want a transaction that has a blob but no priority fee nor access list, and then another one wants no blob but wants a priority fee and no access list, and so on; thatâs 8 additional different âtx typesâ for features that donât have anything to do with each other. With future features such as multidimensional fees, CREATE2 transaction, different sig_hash mechanisms, and so on, one may want to move towards allowing the signer to pick the combo they want instead of being forced to select a type that supports a superset of whatâs needed and then having to trick around with empty lists and default values for all the features they donât want, like currently done in RLP.
Furthermore, youâd need some mechanism to transfer type information. For example, using an enum prefix similar to Union
. However, that leads to a requirement for the verifier to know about all the enum cases and their meaning. Because new types may be introduced in the future, verifiers canât become immutable. Thatâs the case even if they solely care about certain fields of the container; for example, only from
, to
, and value
, and ignore all other fields. On the other hand, with StableContainer
, that could be achieved with a followup proposal like a SparseView
that includes just the bitvectors, the requested 3 fields, plus a merkle proof. The merkle proof shape is statically determinable solely by the bitvectors and the requested fields regardless of tx type, which is not necessarily given by a Union
approach.
About StableUnion
, would be interested to understand what you mean there and how the differences to the StableContainer
are.
Iâd also like to better understand more type safe and less error-prone
arguments. In practice, implementations likely go for a single implementation that handles all transactions. Then, for each feature, check if it is used and, if it is, process it. The difference would be that with the TransactionPayloadXyz
jungle youâd need a Generics based implementation that generates another copy of the code for each individual type (feature combination), while with the StableContainer
approach youâd have a single function with runtime checks for all the features. Code size is smaller with the StableContainer
, while the Generics based implementation can exclude certain invalid field combinations (the 20-line check in EIP-6493) in the serialization library rather than its usage. Code size may also have implications on ZK logic based verifiers.