EIP-2481: eth/66 request identifier
Introduces a request id for all requests of the eth protocol
Abstract
The eth
protocol defines various request and response commands that are used to exchange data between Ethereum nodes. For example, to ask a peer node for a specific set of headers, a node sends it the GetBlockHeaders
command.
Citing from the GetBlockHeaders
spec definition:
[block: {P, B_32}, maxHeaders: P, skip: P, reverse: P in {0, 1}]
Require peer to return a
BlockHeaders
message. Reply must contain a number of block headers, of rising number whenreverse
is0
, falling when1
,skip
blocks apart, beginning at blockblock
(denoted by either number or hash) in the canonical chain, and with at mostmaxHeaders
items.
The node that receives the GetBlockHeaders
command should answer it with the BlockHeaders
response command accordingly.
Citing from the BlockHeaders
spec definition:
[blockHeader_0, blockHeader_1, ...]
Reply to
GetBlockHeaders
. The items in the list (following the message ID) are block headers in the format described in the main Ethereum specification, previously asked for in a GetBlockHeaders message. This may validly contain no block headers if none of the requested block headers were found. The number of headers that can be requested in a single message may be subject to implementation-defined limits.
Let's consider a client making many simultaneous requests for GetBlockHeaders
to one of its peers. By nature it can not be guaranteed that the expected responses arrive in the same order as they were sent. For the client to associate the incoming responses to the correct requests it has to loop through all pending requests trying to match it with the incoming response based on its contents.
This can be particular tricky for responses that are ambiguous such as empty responses.
This EIP proposes to change the GetBlockHeaders
and the BlockHeaders
command to include a request_id
.
The request_id
is a 64-bit integer set by the client when it makes the request. On the responding side, the exact same request_id
from the incoming request is put back into the response object.
This change allows the requesting client to match incoming responses directly back to their pending requests without going through all of the pending requests to check if they might match based on the response data.
The selected request/response pair serves as an example for many similar request/response pairs in the eth
networking protocol.
Motivation
The lack of request identifiers in the request / response paris of the eth
protocol puts unnecessary burden of code complexity into every Ethereum client. It also makes the communication slightly less efficient. Another argument can be made that the addition of request identifiers makes the protocol more aligned with the les
protocol which does already defines request identifiers for each request / response pair.
Specification
Change the following message types in the eth
protocol:
GetBlockHeaders (0x03)
- Current (eth/65):
[block: {P, B_32}, maxHeaders: P, skip: P, reverse: P in {0, 1}]
- Then (eth/66):
[request_id: P, [block: {P, B_32}, maxHeaders: P, skip: P, reverse: P in {0, 1}]]
- Current (eth/65):
BlockHeaders (0x04)
- Current (eth/65):
[blockHeader_0, blockHeader_1, ...]
- Then (eth/66):
[request_id: P, [blockHeader_0, blockHeader_1, ...]]
- Current (eth/65):
GetBlockBodies (0x05)
- Current (eth/65):
[hash_0: B_32, hash_1: B_32, ...]
- Then (eth/66):
[request_id: P, [hash_0: B_32, hash_1: B_32, ...]]
- Current (eth/65):
GetPooledTransactions (0x09)
:- Current (eth/65)
[hash_0: B_32, hash_1: B_32, ...]
- Then (eth/66)
[request_id: P, [hash_0: B_32, hash_1: B_32, ...]]
- Current (eth/65)
PooledTransactions (0x0a)
:- Current (eth/65)
[[nonce: P, receivingAddress: B_20, value: P, ...], ...]
- Then (eth/66)
[request_id: P, [[nonce: P, receivingAddress: B_20, value: P, ...], ...]]
- Current (eth/65)
BlockBodies (0x06)
- Current (eth/65):
[hash_0: B_32, hash_1: B_32, ...]
- Then (eth/66):
[request_id: P, [hash_0: B_32, hash_1: B_32, ...]]
- Current (eth/65):
GetNodeData (0x0d)
- Current (eth/65):
[hash_0: B_32, hash_1: B_32, ...]
- Then (eth/66):
[request_id: P, [hash_0: B_32, hash_1: B_32, ...]]
- Current (eth/65):
NodeData (0x0e)
- Current (eth/65):
[value_0: B, value_1: B, ...]
- Then (eth/66):
[request_id: P, [value_0: B, value_1: B, ...]]
- Current (eth/65):
GetReceipts (0x0f)
- Current (eth/65):
[blockHash_0: B_32, blockHash_1: B_32, ...]
- Then (eth/66):
[request_id: P, [blockHash_0: B_32, blockHash_1: B_32, ...]]
- Current (eth/65):
Receipts (0x10)
- Current (eth/65):
[[receipt_0, receipt_1], ...]
- Then (eth/66):
[request_id: P, [[receipt_0, receipt_1], ...]]
- Current (eth/65):
To elaborate, each command is altered in the following way:
- Create a list with the
request_id
being the first element. - Make the second element the list that defines the whole command in the current scheme.
The request_id
has the following characteristics:
- 64 bit integer
- Doesn't need to be sequential (can be random)
- Does allow duplicates
Rationale
Q: The efficiency gains might encourage clients to flood their peers with too many simultaneous requests
Peers can always throttle or disconnect if they don't feel treated well. This is the same as today.
Q: If les
already defines the commands like this, why not just use the les
protocol?
In practice, peers that serve the les
protocol are much harder to find in the network. The reasons for this are varied but might boil down to client defaults, immature implementations or missing incentives.
Q: Networking works today, isn't this just adding bloat?
This is adding a single integer per command while at the same time reducing code complexity and improving networking efficiency. The addition seems justified.
Q: Why not demand request ids to be sequential?
Assuming request ids start always to count from 0
upon connection, things will become messy when
connections are lost and clients reconnect and start over with the same request ids that they had used
in the previous session.
Q: Why allow duplicate request ids?
The main benefit is flexibility and simplicity on the implementation side. Clients could decide to share the same ids across multiple different request types since they are naturally separated anyway. Clients could even decide to not rely on request ids at all, therefore using the same constant request id across all requests.
Q: Why choose a 64-bit integer for the request ids
64-bit integer were chosen to keep compatibility with the les
protocol.
Backwards Compatibility
This EIP extends the eth
protocol in a backwards incompatible way and requires rolling out a new version, eth/66
. However, devp2p
supports running multiple versions of the same wire protocol side-by-side, so rolling out eth/66
does not require client coordination, since non-updated clients can keep using eth/65
.
This EIP does not change the consensus engine, thus does not require a hard fork.
Test Cases
These testcases cover RLP-encoding of all the redefined messages types, where the rlp
portion is the rlp-encoding of the message defined in the data
portion.
Security Considerations
None
Copyright
Copyright and related rights waived via CC0.