EIP-6404: SSZ 交易
将 RLP 交易迁移到 SSZ
Authors | Etan Kissling (@etan-status), Gajinder Singh (@g11tech), Vitalik Buterin (@vbuterin) |
---|---|
Created | 2023-01-30 |
Discussion Link | https://ethereum-magicians.org/t/eip-6404-ssz-transactions/12783 |
Requires | EIP-155, EIP-1559, EIP-2718, EIP-2930, EIP-4844, EIP-5793, EIP-7495, EIP-7702 |
Table of Contents
摘要
本 EIP 定义了 EIP-2718 递归长度前缀(RLP)交易到 简单序列化(SSZ) 的迁移过程。
动机
RLP 交易存在许多缺点:
-
线性哈希: RLP 交易的签名哈希(
sig_hash
)和唯一标识符(tx_hash
)是通过对其序列化进行线性 keccak256 哈希计算得出的。即使只对部分数据感兴趣,线性哈希也需要完整的交易数据存在,包括可能很大的 calldata 或访问列表。这也适用于基于sig_hash
计算交易的from
地址时。 -
低效的包含证明: 支持执行区块头的
transactions_root
的 Merkle-Patricia Trie (MPT) 是由序列化的交易所构建的,在内部将前缀添加到交易数据中,然后再将其 keccak256 哈希到 MPT 中。由于此前缀,链上没有对tx_hash
的承诺,并且包含证明需要完整的交易数据存在。 -
不兼容的表示: 作为共识
ExecutionPayload
的一部分,交易的 RLP 序列化使用 SSZ merkleization 进行哈希处理。这些 SSZ 哈希与tx_hash
和 MPTtransactions_root
都不兼容。 -
不可扩展性: 交易类型无法通过可选功能进行扩展。假设从一开始就存在 EIP-4844 blob 交易,那么 EIP-2930 访问列表和 EIP-1559 优先级费用等新功能将需要两种新的交易类型,以同时扩展基本交易类型和 blob 交易类型。
-
技术债务: 所有处理 RLP 交易的客户端应用程序和智能合约都必须正确处理诸如缺少前缀字节的
LegacyTransaction
、不一致的chain_id
和v
/y_parity
语义以及在其他字段之间引入max_priority_fee_per_gas
等注意事项,而不是在末尾引入。由于现有的交易类型往往会永久保持有效,因此这种技术债务会随着时间的推移而累积。 -
不适当的不透明性: 共识层将 RLP 交易数据视为不透明的,但需要根据交易
blob_versioned_hashes
验证共识blob_kzg_commitments
,从而导致比必要的引擎 API 更复杂。
本 EIP 定义了一种无损转换机制,用于规范跨共识层和执行层的交易表示,同时保留对处理 RLP 交易类型的支持。
规范
本文档中的关键词“必须(MUST)”、“禁止(MUST NOT)”、“必需(REQUIRED)”、“应(SHALL)”、“不应(SHALL NOT)”、“应该(SHOULD)”、“不应该(SHOULD NOT)”、“推荐(RECOMMENDED)”、“不推荐(NOT RECOMMENDED)”、“可以(MAY)”和“可选(OPTIONAL)”按照 RFC 2119 和 RFC 8174 中的描述进行解释。
现有定义
为了参考,此处复制了本文档中使用的现有规范中的定义。
名称 | 值 |
---|---|
MAX_TRANSACTIONS_PER_PAYLOAD |
uint64(2**20) (= 1,048,576) |
BYTES_PER_FIELD_ELEMENT |
uint64(32) |
FIELD_ELEMENTS_PER_BLOB |
uint64(4096) |
MAX_BLOB_COMMITMENTS_PER_BLOCK |
uint64(2**12) (= 4,096) |
名称 | SSZ 等效项 |
---|---|
Hash32 |
Bytes32 |
ExecutionAddress |
Bytes20 |
VersionedHash |
Bytes32 |
KZGCommitment |
Bytes48 |
KZGProof |
Bytes48 |
Blob |
ByteVector[BYTES_PER_FIELD_ELEMENT * FIELD_ELEMENTS_PER_BLOB] |
ExecutionSignature
容器
签名使用其原生的、不透明的表示形式,并扩展为链上对签名地址的承诺。
名称 | 值 | 描述 |
---|---|---|
SECP256K1_SIGNATURE_SIZE |
32 + 32 + 1 (= 65) |
secp256k1 ECDSA 签名的字节长度 |
MAX_EXECUTION_SIGNATURE_FIELDS |
uint64(2**3) (= 8) |
ExecutionSignature 未来可能增长到的最大字段数 |
class ExecutionSignature(StableContainer[MAX_EXECUTION_SIGNATURE_FIELDS]):
secp256k1: Optional[ByteVector[SECP256K1_SIGNATURE_SIZE]]
class Secp256k1ExecutionSignature(Profile[ExecutionSignature]):
secp256k1: ByteVector[SECP256K1_SIGNATURE_SIZE]
def secp256k1_pack(r: uint256, s: uint256, y_parity: uint8) -> ByteVector[SECP256K1_SIGNATURE_SIZE]:
return r.to_bytes(32, 'big') + s.to_bytes(32, 'big') + bytes([y_parity])
def secp256k1_unpack(signature: ByteVector[SECP256K1_SIGNATURE_SIZE]) -> tuple[uint256, uint256, uint8]:
r = uint256.from_bytes(signature[0:32], 'big')
s = uint256.from_bytes(signature[32:64], 'big')
y_parity = signature[64]
return (r, s, y_parity)
def secp256k1_validate(signature: ByteVector[SECP256K1_SIGNATURE_SIZE]):
SECP256K1N = 0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141
r, s, y_parity = secp256k1_unpack(signature)
assert 0 < r < SECP256K1N
assert 0 < s <= SECP256K1N // 2
assert y_parity in (0, 1)
def secp256k1_recover_signer(signature: ByteVector[SECP256K1_SIGNATURE_SIZE],
sig_hash: Hash32) -> ExecutionAddress:
ecdsa = ECDSA()
recover_sig = ecdsa.ecdsa_recoverable_deserialize(signature[0:64], signature[64])
public_key = PublicKey(ecdsa.ecdsa_recover(sig_hash, recover_sig, raw=True))
uncompressed = public_key.serialize(compressed=False)
return ExecutionAddress(keccak(uncompressed[1:])[12:])
Transaction
容器
所有交易都表示为单个、标准化的 SSZ 容器。该定义使用 EIP-7495 中定义的 StableContainer[N]
SSZ 类型和 Optional[T]
。
名称 | 值 | 描述 |
---|---|---|
MAX_FEES_PER_GAS_FIELDS |
uint64(2**4) (= 16) |
FeesPerGas 未来可能增长到的最大字段数 |
MAX_CALLDATA_SIZE |
uint64(2**24) (= 16,777,216) |
交易的最大 input calldata 字节长度 |
MAX_ACCESS_LIST_STORAGE_KEYS |
uint64(2**19) (= 524,288) |
访问元组中存储键的最大数量 |
MAX_ACCESS_LIST_SIZE |
uint64(2**19) (= 524,288) |
access_list 中访问元组的最大数量 |
MAX_AUTHORIZATION_PAYLOAD_FIELDS |
uint64(2**4) (= 16) |
AuthorizationPayload 未来可能增长到的最大字段数 |
MAX_AUTHORIZATION_LIST_SIZE |
uint64(2**16) (= 65,536) |
authorization_list 中授权的最大数量 |
MAX_TRANSACTION_PAYLOAD_FIELDS |
uint64(2**5) (= 32) |
TransactionPayload 未来可能增长到的最大字段数 |
名称 | SSZ 等效项 | 描述 |
---|---|---|
TransactionType |
uint8 |
EIP-2718 交易类型,范围 [0x00, 0x7F] |
ChainId |
uint64 |
EIP-155 链 ID |
FeePerGas |
uint256 |
每单位 gas 的费用 |
GasAmount |
uint64 |
以 gas 为单位的数量 |
class FeesPerGas(StableContainer[MAX_FEES_PER_GAS_FIELDS]):
regular: Optional[FeePerGas]
# EIP-4844
blob: Optional[FeePerGas]
class AccessTuple(Container):
address: ExecutionAddress
storage_keys: List[Hash32, MAX_ACCESS_LIST_STORAGE_KEYS]
class AuthorizationPayload(StableContainer[MAX_AUTHORIZATION_PAYLOAD_FIELDS]):
magic: Optional[TransactionType]
chain_id: Optional[ChainId]
address: Optional[ExecutionAddress]
nonce: Optional[uint64]
class Authorization(Container):
payload: AuthorizationPayload
signature: ExecutionSignature
class TransactionPayload(StableContainer[MAX_TRANSACTION_PAYLOAD_FIELDS]):
# EIP-2718
type_: Optional[TransactionType]
# EIP-155
chain_id: Optional[ChainId]
nonce: Optional[uint64]
max_fees_per_gas: Optional[FeesPerGas]
gas: Optional[GasAmount]
to: Optional[ExecutionAddress]
value: Optional[uint256]
input_: Optional[ByteList[MAX_CALLDATA_SIZE]]
# EIP-2930
access_list: Optional[List[AccessTuple, MAX_ACCESS_LIST_SIZE]]
# EIP-1559
max_priority_fees_per_gas: Optional[FeesPerGas]
# EIP-4844
blob_versioned_hashes: Optional[List[VersionedHash, MAX_BLOB_COMMITMENTS_PER_BLOCK]]
# EIP-7702
authorization_list: Optional[List[Authorization, MAX_AUTHORIZATION_LIST_SIZE]]
class Transaction(Container):
payload: TransactionPayload
signature: ExecutionSignature
Transaction
配置文件
EIP-7495 Profile
定义为有效交易提供了类型安全。它们原始的 RLP TransactionType
被保留,以便在必要时可以恢复它们原始的 RLP 表示形式以及关联的 sig_hash
和 tx_hash
值。
class BasicFeesPerGas(Profile[FeesPerGas]):
regular: FeePerGas
class BlobFeesPerGas(Profile[FeesPerGas]):
regular: FeePerGas
blob: FeePerGas
class RlpLegacyTransactionPayload(Profile[TransactionPayload]):
type_: TransactionType
chain_id: Optional[ChainId]
nonce: uint64
max_fees_per_gas: BasicFeesPerGas
gas: GasAmount
to: Optional[ExecutionAddress]
value: uint256
input_: ByteList[MAX_CALLDATA_SIZE]
class RlpLegacyTransaction(Container):
payload: RlpLegacyTransactionPayload
signature: Secp256k1ExecutionSignature
class RlpAccessListTransactionPayload(Profile[TransactionPayload]):
type_: TransactionType
chain_id: ChainId
nonce: uint64
max_fees_per_gas: BasicFeesPerGas
gas: GasAmount
to: Optional[ExecutionAddress]
value: uint256
input_: ByteList[MAX_CALLDATA_SIZE]
access_list: List[AccessTuple, MAX_ACCESS_LIST_SIZE]
class RlpAccessListTransaction(Container):
payload: RlpAccessListTransactionPayload
signature: Secp256k1ExecutionSignature
class RlpFeeMarketTransactionPayload(Profile[TransactionPayload]):
type_: TransactionType
chain_id: ChainId
nonce: uint64
max_fees_per_gas: BasicFeesPerGas
gas: GasAmount
to: Optional[ExecutionAddress]
value: uint256
input_: ByteList[MAX_CALLDATA_SIZE]
access_list: List[AccessTuple, MAX_ACCESS_LIST_SIZE]
max_priority_fees_per_gas: BasicFeesPerGas
class RlpFeeMarketTransaction(Container):
payload: RlpFeeMarketTransactionPayload
signature: Secp256k1ExecutionSignature
class RlpBlobTransactionPayload(Profile[TransactionPayload]):
type_: TransactionType
chain_id: ChainId
nonce: uint64
max_fees_per_gas: BlobFeesPerGas
gas: GasAmount
to: ExecutionAddress
value: uint256
input_: ByteList[MAX_CALLDATA_SIZE]
access_list: List[AccessTuple, MAX_ACCESS_LIST_SIZE]
max_priority_fees_per_gas: BlobFeesPerGas
blob_versioned_hashes: List[VersionedHash, MAX_BLOB_COMMITMENTS_PER_BLOCK]
class RlpBlobTransaction(Container):
payload: RlpBlobTransactionPayload
signature: Secp256k1ExecutionSignature
class RlpSetCodeAuthorizationPayload(Profile[AuthorizationPayload]):
magic: TransactionType
chain_id: Optional[ChainId]
address: ExecutionAddress
nonce: uint64
class RlpSetCodeAuthorization(Container):
payload: RlpSetCodeAuthorizationPayload
signature: Secp256k1ExecutionSignature
class RlpSetCodeTransactionPayload(Profile[TransactionPayload]):
type_: TransactionType
chain_id: ChainId
nonce: uint64
max_fees_per_gas: BasicFeesPerGas
gas: GasAmount
to: ExecutionAddress
value: uint256
input_: ByteList[MAX_CALLDATA_SIZE]
access_list: List[AccessTuple, MAX_ACCESS_LIST_SIZE]
max_priority_fees_per_gas: BasicFeesPerGas
authorization_list: List[RlpSetCodeAuthorization, MAX_AUTHORIZATION_LIST_SIZE]
class RlpSetCodeTransaction(Container):
payload: RlpSetCodeTransactionPayload
signature: Secp256k1ExecutionSignature
提供了帮助程序来识别标准化 Transaction
的 EIP-7495 Profile
。类型系统确保存在 Profile
的所有必需字段,并且不存在排除的字段。
LEGACY_TX_TYPE = TransactionType(0x00)
ACCESS_LIST_TX_TYPE = TransactionType(0x01)
FEE_MARKET_TX_TYPE = TransactionType(0x02)
BLOB_TX_TYPE = TransactionType(0x03)
SET_CODE_TX_TYPE = TransactionType(0x04)
SET_CODE_TX_MAGIC = TransactionType(0x05)
def identify_authorization_profile(auth: Authorization) -> Type[Profile]:
if auth.payload.magic == SET_CODE_TX_MAGIC:
if auth.payload.chain_id == 0:
raise Exception(f'Unsupported chain ID in Set Code RLP authorization: {auth}')
return RlpSetCodeAuthorization
raise Exception(f'Unsupported authorization: {auth}')
def identify_transaction_profile(tx: Transaction) -> Type[Profile]:
if tx.payload.type_ == SET_CODE_TX_TYPE:
for auth in tx.payload.authorization_list or []:
auth = identify_authorization_profile(auth).from_base(auth)
if not isinstance(auth, RlpSetCodeAuthorization):
raise Exception(f'Unsupported authorization in Set Code RLP transaction: {tx}')
return RlpSetCodeTransaction
if tx.payload.type_ == BLOB_TX_TYPE:
if (tx.payload.max_priority_fees_per_gas or FeesPerGas()).blob != 0:
raise Exception(f'Unsupported blob priority fee in Blob RLP transaction: {tx}')
return RlpBlobTransaction
if tx.payload.type_ == FEE_MARKET_TX_TYPE:
return RlpFeeMarketTransaction
if tx.payload.type_ == ACCESS_LIST_TX_TYPE:
return RlpAccessListTransaction
if tx.payload.type_ == LEGACY_TX_TYPE:
return RlpLegacyTransaction
raise Exception(f'Unsupported transaction: {tx}')
要获取交易的 from
地址、其标识符或授权的 authority
地址,请参阅 EIP 资产 以获取 compute_sig_hash
、compute_tx_hash
和 compute_auth_hash
的定义,这些定义考虑了各种交易类型。
执行区块头更改
执行区块头的 txs-root
从 MPT 转换为 SSZ。
transactions = List[Transaction, MAX_TRANSACTIONS_PER_PAYLOAD](
tx_0, tx_1, tx_2, ...)
block_header.transactions_root = transactions.hash_tree_root()
引擎 API
在引擎 API 中,采用此 EIP 的 ExecutionPayload
版本中 transactions
字段的结构从 Array of DATA
更改为 Array of TransactionV1
。
TransactionV1
定义为映射到 SSZ Transaction
类型,如下所示:
payload
:TransactionPayloadV1
- 包含TransactionPayloadV1
结构字段的OBJECT
signature
:ExecutionSignatureV1
- 包含ExecutionSignatureV1
结构字段的OBJECT
TransactionPayloadV1
定义为映射到 SSZ TransactionPayload
StableContainer
,如下所示:
type
:QUANTITY|null
, 8 位或null
chainId
:QUANTITY|null
, 256 位或null
nonce
:QUANTITY|null
, 64 位或null
maxFeesPerGas
:FeesPerGasV1|null
- 包含FeesPerGasV1
结构字段的OBJECT
或null
gas
:QUANTITY|null
, 64 位或null
to
:DATA|null
, 20 字节或null
value
:QUANTITY|null
, 256 位或null
input
:DATA|null
,0 到MAX_CALLDATA_SIZE
字节或null
accessList
:Array of AccessTupleV1
- 0 到MAX_ACCESS_LIST_SIZE
OBJECT
条目,每个条目包含AccessTupleV1
结构的字段,或null
maxPriorityFeesPerGas
:FeesPerGasV1|null
- 包含FeesPerGasV1
结构字段的OBJECT
或null
blobVersionedHashes
:Array of DATA|null
- 0 到MAX_BLOB_COMMITMENTS_PER_BLOCK
DATA
条目,每个条目包含 32 字节,或null
authorizationList
:Array of AuthorizationV1
- 0 到MAX_AUTHORIZATION_LIST_SIZE
OBJECT
条目,每个条目包含AuthorizationV1
结构的字段,或null
FeesPerGasV1
定义为映射到 SSZ FeesPerGas
StableContainer
,如下所示:
regular
:QUANTITY|null
, 256 位或null
blob
:QUANTITY|null
, 256 位或null
AccessTupleV1
定义为映射到 SSZ AccessTuple
Container
,如下所示:
address
:DATA
, 20 字节storageKeys
:Array of DATA
- 0 到MAX_ACCESS_LIST_STORAGE_KEYS
DATA
条目,每个条目包含 32 字节
AuthorizationV1
定义为映射到 SSZ Authorization
Container
,如下所示:
payload
:AuthorizationPayloadV1
- 包含AuthorizationPayloadV1
结构字段的OBJECT
signature
:ExecutionSignatureV1
- 包含ExecutionSignatureV1
结构字段的OBJECT
AuthorizationPayloadV1
定义为映射到 SSZ AuthorizationPayload
StableContainer
,如下所示:
magic
:QUANTITY|null
, 8 位或null
chainId
:QUANTITY|null
, 256 位或null
address
:DATA|null
, 20 字节或null
nonce
:QUANTITY|null
, 64 位或null
ExecutionSignatureV1
定义为映射到 SSZ ExecutionSignature
StableContainer
,如下所示:
secp256k1
:DATA|null
, 65 字节或null
共识 ExecutionPayload
更改
构建共识 ExecutionPayload
时,transactions
列表不再是不透明的,而是使用新的 Transaction
类型。
class ExecutionPayload(Container):
...
transactions: List[Transaction, MAX_TRANSACTIONS_PER_PAYLOAD]
...
SSZ PooledTransaction
容器
在交易 gossip 响应(PooledTransactions
)期间,每个 Transaction
都被包装到 PooledTransaction
中。
名称 | 值 | 描述 |
---|---|---|
MAX_POOLED_TRANSACTION_FIELDS |
uint64(2**3) (= 8) |
PooledTransaction 未来可能增长到的最大字段数 |
class BlobData(Container):
blobs: List[Blob, MAX_BLOB_COMMITMENTS_PER_BLOCK]
commitments: List[KZGCommitment, MAX_BLOB_COMMITMENTS_PER_BLOCK]
proofs: List[KZGProof, MAX_BLOB_COMMITMENTS_PER_BLOCK]
class PooledTransaction(StableContainer[MAX_POOLED_TRANSACTION_FIELDS]):
tx: Optional[Transaction]
blob_data: Optional[BlobData]
EIP-4844 中定义的其他验证约束也适用于定义 tx.payload.blob_versioned_hashes
或 blob_data
的交易。
交易 gossip 公告
交易 gossip 公告(NewPooledTransactionHashes
)中 types
元素 的语义已更改为匹配 ssz(PooledTransaction.active_fields())
。与基本交易相比,获取 blob 交易的单独控制流被保留。
请注意,此更改将具有 blob_data
的 PooledTransaction
的 active_fields
映射到 0x03
,这与 blob RLP 交易之前的 BLOB_TX_TYPE
前缀一致。
网络
通过 Ethereum Wire Protocol 交换 SSZ 交易时,使用以下 EIP-2718 兼容信封:
名称 | 值 | 描述 |
---|---|---|
SSZ_TX_TYPE |
TransactionType(0x1f) |
端点特定的 SSZ 对象 |
Transaction
:SSZ_TX_TYPE || snappyFramed(ssz(Transaction))
PooledTransaction
:SSZ_TX_TYPE || snappyFramed(ssz(PooledTransaction))
对象使用 SSZ 进行编码,并使用 Snappy 框架格式进行压缩,从而匹配共识对象的编码,如共识网络规范 中定义的那样。作为编码的一部分,会发出未压缩的对象长度;每个对象的建议强制执行的限制是 MAX_CHUNK_SIZE
字节。
实现应该继续支持接受 RLP 交易到其交易池中。但是,此类交易必须转换为 SSZ 才能包含到 ExecutionPayload
中。请参阅 EIP 资产 以获取从 RLP 转换为 SSZ 的参考实现,以及相应的测试用例。原始的 sig_hash
和 tx_hash
在整个转换过程中都将保留。
理由
在执行区块内切换到单个、统一且向前兼容的交易格式,可以降低客户端应用程序和智能合约的实现复杂性。使用 SSZ 可以更容易地实现未来的用例,例如交易包含证明或将可验证的 calldata 的各个区块提交给智能合约。
还解决了各种协议效率低下的问题。虽然在 RLP 系统下交易数据被哈希多次,包括 (1) sig_hash
,(2) tx_hash
,(3) MPT 内部哈希和 (4) SSZ 内部哈希,但标准化的表示形式减少了哈希计数。此外,如果共识 blob_kzg_commitments
未根据交易 blob_versioned_hashes
进行验证,则共识层实现可以提前删除无效区块,并且不再需要查询执行层以进行区块哈希验证。
向后兼容性
依赖于区块头中已替换的 MPT transactions_root
的应用程序需要迁移到 SSZ transactions_root
。
虽然链上没有对 tx_hash
的承诺,但它已广泛用于 JSON-RPC 和 Ethereum Wire Protocol 中,以唯一标识交易。tx_hash
在从 RLP 转换为 SSZ 的过程中保持稳定。
从 RLP 交易到 SSZ 的转换是无损的。原始的 RLP sig_hash
和 tx_hash
可以从 SSZ 表示形式中恢复。
安全考虑
无
版权
通过 CC0 放弃版权及相关权利。
Citation
Please cite this document as:
Etan Kissling (@etan-status), Gajinder Singh (@g11tech), Vitalik Buterin (@vbuterin), "EIP-6404: SSZ 交易 [DRAFT]," Ethereum Improvement Proposals, no. 6404, January 2023. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-6404.