本文档详细介绍了以太坊线协议(ETH),该协议用于在节点之间交换以太坊区块链信息。内容涵盖了协议的基本操作,包括链同步、状态同步(快速同步/快照同步)、区块传播(在PoW网络中)以及交易交换。此外,文档还深入探讨了交易和区块的编码与验证规则, 以及各种协议消息的格式和功能,例如Status、NewBlockHashes和Transactions等,还提供了协议各个版本的变更日志。
'eth' 是 RLPx 传输协议上的一个协议,它促进了对等体之间以太坊区块链信息的交换。当前的协议版本是 eth/69。有关过去协议版本中的更改列表,请参见文档末尾。
一旦建立连接,必须发送 Status 消息。在接收到对等体的 Status 消息之后,以太坊会话即被激活,并且可以发送任何其他消息。
在一个会话中,可以执行两个高级任务:链同步和交易交换。这些任务使用不相交的协议消息集,并且客户端通常在所有对等连接上将它们作为并发活动执行。
客户端实现应强制限制协议消息的大小。底层 RLPx 传输将单个消息的大小限制为 16.7 MiB。eth 协议的实际限制较低,通常为 10 MiB。如果收到的消息大于限制,则应断开对等体的连接。
除了对接收消息的硬性限制之外,客户端还应对其发送的请求和响应施加“软”限制。建议的软限制因消息类型而异。限制请求和响应可确保并发活动(例如,区块同步和交易交换)在同一对等连接上顺利进行。
该链是通过从其他对等体下载获得的。通常期望节点响应整个历史范围内的标头、主体和收据的请求。但是,由于主网的规模很大,因此决定必须以 eth 协议之外的其他方式获取早期的链历史记录,因为并非所有节点都可能永久存储完整的主网历史记录并提供对它的随机访问。因此,该协议还提供了声明对等体可用的区块范围的方法。对于以太坊主网,客户端实现必须通过根本不同步历史记录或通过其他方式同步历史记录来处理 eth 协议中缺少历史记录的问题。
由于以太坊共识发生在“执行链”之外,因此该协议中没有内置机制来确定规范链的头。假定每个节点都以某种方式知道规范链,无论是通过与共识节点通信,还是通过充当以太坊共识协议的轻客户端。
有了已知的链头,同步通常按以下方式进行:节点将首先使用 GetBlockHeaders 从头向下获取到创世区块的标头。可以通过首先使用 skip
参数获取标头的“骨架”结构,然后使用多个对等体填充间隙来并行化此过程。
下载标头后,客户端可以使用 GetBlockBodies 继续获取完整的区块。由于所有区块哈希都从标头链中得知,因此这也可以利用多个对等体。必须针对标头验证检索到的区块主体。然后,EVM 执行检索到的区块以获取收据和状态。
协议版本 eth/63 到 eth/66 还允许同步状态树。从版本 eth/67 开始,以太坊状态树不再可以使用 eth 协议检索,状态下载由辅助 snap protocol 代替提供。
状态同步通常通过下载如上的区块标头链进行。请求区块主体与“链同步”部分中相同,但是不执行交易,仅验证其“数据有效性”。客户端选择链头附近的区块(“支点区块”)并下载该区块的状态。
由于状态同步不执行交易,因此节点在同步状态后将没有任何可用的执行收据。为了完全参与 p2p 协议,还需要在使用 GetReceipts 消息的状态同步期间下载收据。
注意:在 PoW 到 PoS 的过渡 (The Merge) 之后,区块传播不再由“eth”协议处理。以下文本仅适用于 PoW 和 PoA (clique) 网络。区块传播消息(NewBlock,NewBlockHashes...)将在将来的版本中从协议中删除。
新挖出的区块必须中继到所有节点。这通过区块传播来实现,这是一个两步过程。当从对等体收到 NewBlock 公告消息时,客户端首先验证区块的基本标头有效性,检查工作量证明值是否有效。然后,它使用 NewBlock 消息将该区块发送到一小部分连接的对等体(通常是对等体总数的平方根)。
在标头有效性检查之后,客户端通过执行区块中包含的所有交易来将区块导入到其本地链中,从而计算区块的“后状态”。区块的 state-root
哈希必须与计算出的后状态根匹配。一旦完全处理了该区块并认为其有效,客户端就会向所有先前未通知过的对等体发送有关该区块的 NewBlockHashes 消息。如果这些对等体无法通过其他任何人的 NewBlock 接收到完整的区块,则他们可能会在以后请求完整的区块。
节点绝不应将区块公告发送回先前已公告同一区块的对等体。这通常是通过记住最近中继到或来自每个对等体的大量区块哈希来实现的。
如果该区块不是客户端当前最新区块的直接后继区块,则接收到区块公告也可能会触发链同步。
所有节点都必须交换待处理的交易,以便将它们中继给矿工,矿工会选择将它们包含到区块链中。客户端实现会跟踪“交易池”中待处理的交易集。该池受到客户端特定的限制,并且可以包含许多(即数千个)交易。
当建立新的对等连接时,需要同步双方的交易池。最初,双方都应发送 NewPooledTransactionHashes 消息,其中包含本地池中的所有交易哈希,以开始交换。
收到 NewPooledTransactionHashes 公告后,客户端会过滤收到的集合,收集其自己的本地池中尚没有的交易哈希。然后,它可以使用 GetPooledTransactions 消息请求交易。
当新交易出现在客户端的池中时,它应使用 Transactions 和 NewPooledTransactionHashes 消息将其传播到网络。Transactions 消息中继完整的交易对象,并且通常发送给一小部分随机连接的对等体。所有其他对等体都会收到交易哈希的通知,并且如果他们不知道完整的交易对象,则可以请求该对象。向一小部分对等体传播完整的交易通常可以确保所有节点都收到该交易,而无需请求该交易。
节点绝不应将交易发送回它可以确定已经知道该交易的对等体(无论是先前发送的,还是最初从该对等体获知的)。这通常是通过记住对等体最近中继的一组交易哈希来实现的。
对等体交换的交易对象有两种编码方式。在本规范的定义中,我们使用标识符 txₙ
来引用任一编码的交易。
tx = {legacy-tx, typed-tx}
未类型化的旧版交易以 RLP 列表的形式给出。
legacy-tx = [
nonce: P,
gas-price: P,
gas-limit: P,
recipient: {B_0, B_20},
value: P,
data: B,
V: P,
R: P,
S: P,
]
EIP-2718 类型化交易被编码为 RLP 字节数组,其中第一个字节是交易类型(tx-type
),其余字节是不透明的类型特定数据。
typed-tx = tx-type || tx-data
交易在收到时必须经过验证。有效性取决于以太坊链状态。本规范关注的特定有效性类型不是交易是否可以被 EVM 成功执行,而仅仅是它是否可以临时存储在本地池中并与其他对等方交换。
交易根据以下规则进行验证。虽然类型化交易的编码是不透明的,但假定它们的 tx-data
提供了 nonce
、gas-price
、gas-limit
的值,并且可以从它们的签名中确定交易的发送方帐户。
tx-type
。即使在交易可以包含在区块中之前,也可以认为已定义的交易类型是有效的。实现应断开发送未知类型交易的对等体的连接。gas-limit
必须涵盖交易的“固有 gas”。gas-limit * gas-price + value
)。nonce
必须大于或等于发送方帐户的当前 nonce。实现可以强制执行其他交易验证规则。例如,通常的做法是拒绝大于 128 kB 的编码交易。
除非另有说明,否则实现不得因发送无效交易而断开对等体的连接,而应直接丢弃它们。这是因为对等体可能在略有不同的验证规则下运行。
以太坊区块标头的编码方式如下:
header = [
parent-hash: B_32,
ommers-hash: B_32,
coinbase: B_20,
state-root: B_32,
txs-root: B_32,
receipts-root: B_32,
bloom: B_256,
difficulty: P,
number: P,
gas-limit: P,
gas-used: P,
time: P,
extradata: B,
mix-digest: B_32,
block-nonce: B_8,
basefee-per-gas: P,
withdrawals-root: B_32,
blob-gas-used: P,
excess-blob-gas: P,
parent-beacon-root: B_32,
requests-hash: B_32,
]
在某些协议消息中,交易和 ommers 列表一起作为称为“区块主体”的单个项目进行中继。
block-body = [transactions, ommers, withdrawals]
transactions = [tx₁, tx₂, ...]
ommers = [header₁, header₂, ...]
withdrawals = [withdrawal₁, withdrawal₂, ...]
为了进行区块传播(现在已失效),完整的区块被中继为一个组合项目:
block = [header, transactions, ommers]
区块标头的有效性取决于使用它们的上下文。对于单个区块标头,只能验证工作量证明印章(mix-digest
,block-nonce
)的有效性。当标头用于扩展客户端的本地链时,或者在链同步期间按顺序处理多个标头时,适用以下规则:
parent-hash
与前一个标头的哈希匹配。difficulty
,gas-limit
和 time
的值是否在 Yellow Paper 中给出的协议规则范围内。gas-used
标头字段必须小于或等于 gas-limit
。basefee-per-gas
。对于较早的区块,必须不存在该字段。此规则由 EIP-1559 添加。ommers-hash
必须为空的 keccak256 哈希,因为不存在 ommer 标头。withdrawals-root
。对于分叉之前的区块,该字段必须不存在。此规则由 EIP-4895 添加。blob-gas-used
,excess-blob-gas
,parent-beacon-root
必须存在于 Cancun fork 之后的标头中,而对于较早的区块则不存在。此规则由 EIP-4844 和 EIP-4788 添加。requests-hash
必须存在于 Prague fork 之后的标头中,而对于较早的区块则不存在。此规则由 EIP-7685 添加。对于完整的区块,我们区分区块的 EVM 状态转换的有效性与区块的(较弱的)“数据有效性”。本规范未处理状态转换规则的定义。出于立即 block propagation 和 state synchronization 的目的,我们需要区块的数据有效性。
要确定区块的数据有效性,请使用以下规则。实现应断开发送无效区块的对等体的连接。
header
必须有效。transactions
必须在区块编号处有效,才能包含到链中。这意味着,除了先前给出的交易验证规则之外,还需要验证是否允许在区块编号处使用 tx-type
,并且交易 gas 的验证必须考虑区块编号。gas-limit
之和不得超过区块的 gas-limit
。txs-root
验证区块的 transactions
。withdrawals-root
验证区块主体的 withdrawals
。请注意,由于它们是由共识层注入到区块中的,因此无法对提款进行进一步的验证。ommers
列表最多可以包含两个标头。keccak256(ommers)
必须与区块标头的 ommers-hash
匹配。ommers
列表中包含的标头必须是有效的标头。它们的区块编号不得大于它们包含在其中的区块的区块编号。ommer 标头的父哈希必须引用其包括区块的深度为 7 或更小的祖先,并且不得包含在该祖先集中包含的任何较早的区块中。收据是交易的 EVM 状态转换的输出。它们可用于同步链,而无需重新执行交易。所有收据都具有相同的编码,而与交易类型无关。
receiptₙ = [
tx-type: P,
post-state-or-status: B,
cumulative-gas: P,
logs: [log₁, log₂, ...],
]
logₙ = [
address: B_20,
topics: [topic₁: B, topic₂: B, ...],
data: B,
]
在以太坊 Wire Protocol 中,收据始终作为区块中包含的所有收据的完整列表进行传输。还假定包含收据的区块是有效的并且是已知的。当对等方收到区块收据列表时,必须通过计算列表的 merkle trie 哈希并将其与区块的 receipts-root
进行比较来验证该列表。请注意,为了执行此验证,需要将收据重新编码为以太坊共识协议使用的格式,并且必须重新计算其 bloom 过滤器。
由于收据的有效列表由 EVM 状态转换确定,因此无需在本规范中为收据定义任何进一步的有效性规则。
在大多数消息中,消息数据列表的第一个元素是 request-id
。对于请求,这是由请求对等方选择的 64 位整数值。响应对等方必须在响应消息的 request-id
元素中镜像该值。
[vsn: P, networkid: P, genesis: B_32, forkid, earliest: P, latest: P, latestHash: B_32]
这是初始消息,用于通知对等方有关本地节点状态和配置的信息。该消息应在建立连接后立即发送,并在任何其他 eth 协议消息之前发送。
vsn
:当前协议版本networkid
:标识区块链的整数,请参见下表genesis
:创世区块的哈希forkid
:一个 EIP-2124 分叉标识符,编码为 [fork-hash, fork-next]
。Status 消息还宣布了可用的区块范围。有关更多信息,请参见 BlockRangeUpdate。
earliest
:最早可用的完整区块的编号latest
:最新可用的完整区块编号latestHash
:最新可用的完整区块的哈希下表列出了常见的网络 ID 及其对应的网络。还存在未列出的其他 ID,即客户端不应要求使用任何特定的网络 ID。请注意,网络 ID 可能与用于交易重放攻击预防的 EIP-155 链 ID 相对应,也可能不对应。
ID | chain |
---|---|
0 | Olympic (已弃用) |
1 | Frontier (现在是主网) |
2 | Morden 测试网 (已弃用) |
3 | Ropsten 测试网 (已弃用) |
4 | Rinkeby 测试网 (已弃用) |
5 | Goerli 测试网 |
有关社区策划的链 ID 列表, 请参见 <https://chainid.network>。
[[blockhash₁: B_32, number₁: P], [blockhash₂: B_32, number₂: P], ...]
指定一个或多个已出现在网络上的新区块。为了提供最大的帮助,节点应将它们可能不知道的所有区块告知对等方。包括发送对等体可以合理地认为知道的哈希值(因为该节点本身已经通过 NewBlockHashes 公告了对哈希值的了解)被认为是不好的形式,并且可能会降低发送节点的声誉。包括发送节点稍后拒绝通过后续的 GetBlockHeaders 消息来兑现的哈希值被认为是不好的形式,并且可能会降低发送节点的声誉。
[tx₁, tx₂, ...]
指定对等方应确保包含在其交易队列中的交易。列表中的项目是主要以太坊规范中描述的格式的交易。Transactions 消息必须包含至少一项(新)交易,不鼓励使用空的 Transactions 消息,并且可能会导致断开连接。
节点不得在同一会话中将同一交易重新发送给对等方,也不得将交易中继给从其接收该交易的对等方。在实践中,这通常是通过保留每个对等方的 bloom 过滤器或已发送或接收的交易哈希集来实现的。
[request-id: P, [startblock: {P, B_32}, limit: P, skip: P, reverse: {0, 1}]]
要求对等方返回 BlockHeaders 消息。当 reverse
为 0
时,响应必须包含按升序排列的许多区块头,当 reverse
为 1
时,响应必须包含按降序排列的区块头,区块头之间间隔 skip
个区块,从规范链中区块 startblock
(用编号或哈希表示)开始,最多包含 limit
项。
[request-id: P, [header₁, header₂, ...]]
这是对 GetBlockHeaders 的响应,其中包含请求的标头。如果未找到任何请求的区块标头,则标头列表可能为空。可以在单个消息中请求的标头数量可能会受到实现定义的限制。
BlockHeaders 响应的建议软限制为 2 MiB。
[request-id: P, [blockhash₁: B_32, blockhash₂: B_32, ...]]
此消息按哈希请求区块主体数据。可以在单个消息中请求的区块数量可能会受到实现定义的限制。
[request-id: P, [block-body₁, block-body₂, ...]]
这是对 GetBlockBodies 的响应。列表中的项目包含请求的区块的主体数据。如果没有任何请求的区块可用,则该列表可能为空。
BlockBodies 响应的建议软限制为 2 MiB。
[block, td: P]
指定对等方应知道的单个完整区块。td
是区块的总难度,即直到并包括此区块的所有区块难度的总和。
[txtypes: B, [txsize₁: P, txsize₂: P, ...], [txhash₁: B_32, txhash₂: B_32, ...]]
此消息宣布一个或多个已出现在网络中且尚未包含在区块中的交易。消息有效负载描述了交易列表,但请注意,它被编码为三个单独的元素。
txtypes
元素是一个字节数组,其中包含已宣布的 transaction types。其他两个有效负载元素是指已宣布交易的大小和哈希。所有三个有效负载元素必须包含相同数量的项目。
txsizeₙ
是指类型化交易的“共识编码”的长度,即类型化交易的 tx-type || tx-data
的字节大小,以及非类型化旧版交易的 RLP 编码的 legacy-tx
的大小。
此消息的建议软限制为 4096 个项目(约 150 KiB)。
为了提供最大的帮助,节点应将它们可能不知道的所有交易告知对等方。但是,节点应仅宣布远程对等体可以合理地认为不知道的交易的哈希,但是返回更多交易胜过在池中存在 nonce 间隙。
[request-id: P, [txhash₁: B_32, txhash₂: B_32, ...]]
此消息按哈希从接收方的交易池中请求交易。
GetPooledTransactions 请求的建议软限制为 256 个哈希 (8 KiB)。接收方可以对响应强制执行任意限制(大小或服务时间),这不得被视为协议违规。
[request-id: P, [tx₁, tx₂...]]
这是对 GetPooledTransactions 的响应,用于从本地池返回被请求的交易。列表中的项目是主要以太坊规范中描述的格式的交易。
交易必须与请求中的顺序相同,但是可以跳过不可用的交易。这样,如果达到响应大小限制,请求者将知道要再次请求哪些哈希(从上次返回的交易开始的所有内容)以及要假定哪些哈希不可用(上次返回的交易之前的所有间隙)。
允许首先通过 NewPooledTransactionHashes 宣布交易,然后拒绝通过 PooledTransactions 提供服务。当交易包含在区块中(并在公告和请求之间的池中删除)时,可能会出现这种情况。
当没有哈希与池中的交易匹配时,对等方可以回复一个空列表。
[request-id: P, [blockhash₁: B_32, blockhash₂: B_32, ...]]
要求对等方返回 Receipts 消息,该消息包含给定区块哈希的收据。可以在单个消息中请求的收据数量可能会受到实现定义的限制。
[request-id: P, [[receipt₁, receipt₂], ...]]
这是对 GetReceipts 的响应,提供所请求的区块收据。响应列表中的每个元素都对应于 GetReceipts 请求的区块哈希,并且必须包含区块的完整收据列表。
Receipts 响应的建议软限制为 2 MiB。
[earliest: P, latest: P, latestHash: B_32]
这是关于对等方上可用区块范围的更改的通知。
通过此消息,对等方宣布所有区块 b
(其中 earliest >= b >= latest
)都可通过 GetBlockBodies 获得,并且这些区块的收据也可通过 GetReceipts 获得。假定标头始终可用于从创世区块开始的完整区块范围。
无需为节点头区块的每次更新发送通知。建议大约每两分钟发送一次更新。客户端应验证收到的更新。
earliest > latest
,则应断开对等方的连接。版本 69 更改了 Status 消息,以包含有关可用区块范围的信息。添加了新的 BlockRangeUpdate 消息,以通知对等方有关区块范围的更改。Receipts 消息已更改,以简化类型化收据的编码并删除 bloom 过滤器。
版本 68 更改了 NewPooledTransactionHashes 消息,以包含已宣布交易的类型和大小。在此更新之前,消息有效负载只是哈希列表:[txhash₁: B_32, txhash₂: B_32, ...]
。
EIP-4895 添加了 PoS 验证器提款,该提款更改了区块标头的定义,以包含 withdrawals-root
,以及区块主体以包含 withdrawals
列表。没有为此更改创建新的 wire protocol 版本,因为它只是一个向后兼容的区块有效性规则添加。
版本 67 删除了 GetNodeData 和 NodeData 消息。
[request-id: P, [hash₁: B_32, hash₂: B_32, ...]]
[request-id: P, [value₁: B, value₂: B, ...]]
版本 66 在消息 GetBlockHeaders,BlockHeaders,GetBlockBodies,BlockBodies,GetPooledTransactions,PooledTransactions,GetNodeData,NodeData,GetReceipts,Receipts 中添加了 request-id
元素。
当 EIP-2718 引入类型化交易时,客户端实现者决定接受 wire protocol 中的新交易和收据格式,而无需增加协议版本。此规范更新还添加了所有共识对象的编码定义,而不是引用 Yellow Paper。
版本 65 改进了交易交换,引入了三个额外的消息:NewPooledTransactionHashes,GetPooledTransactions 和 PooledTransactions。
在版本 65 之前,对等方始终交换完整的交易对象。随着以太坊主网上活动和交易规模的增加,用于交易交换的网络带宽已成为节点运营商的重大负担。该更新通过采用类似于区块传播的两层交易广播系统来减少所需的带宽。
版本 64 更改了 Status 消息,以包含 EIP-2124 ForkID。这样,对等方无需同步区块链即可确定链执行规则的相互兼容性。
版本 63 添加了 GetNodeData,NodeData,GetReceipts 和 Receipts 消息,这些消息允许同步交易执行结果。
在版本 62 中,扩展了 NewBlockHashes 消息,以包含区块哈希以及区块编号。Status 中的区块编号已删除。消息 GetBlockHashes (0x03),BlockHashes (0x04),GetBlocks (0x05) 和 Blocks (0x06) 由提取区块标头和主体的消息替换。BlockHashesFromNumber (0x08) 消息已删除。
重新分配/删除的消息代码的先前编码为:
[hash: B_32, max-blocks: P]
[hash₁: B_32, hash₂: B_32, ...]
[hash₁: B_32, hash₂: B_32, ...]
[[header, transactions, ommers], ...]
[number: P, max-blocks: P]
版本 61 添加了 BlockHashesFromNumber (0x08) 消息,该消息可用于按升序顺序请求区块。它还在 Status 消息中添加了最新的区块编号。
版本号 60 以下的版本号在以太坊 PoC 开发阶段使用。
0x00
for PoC-10x01
for PoC-20x07
for PoC-30x09
for PoC-40x17
for PoC-50x1c
for PoC-6
- 原文链接: github.com/ethereum/devp...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!