EIP-2481: eth/66 request identifier

Introduces a request id for all requests of the eth protocol


Metadata
Status: FinalStandards Track: NetworkingCreated: 2020-01-17
Authors
Christoph Burgdorf (@cburgdorf)
Requires

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 when reverse is 0, falling when 1, skip blocks apart, beginning at block block (denoted by either number or hash) in the canonical chain, and with at most maxHeaders 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}]]
  • BlockHeaders (0x04)
    • Current (eth/65): [blockHeader_0, blockHeader_1, ...]
    • Then (eth/66): [request_id: P, [blockHeader_0, blockHeader_1, ...]]
  • 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, ...]]
  • 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, ...]]
  • 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, ...], ...]]
  • 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, ...]]
  • 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, ...]]
  • NodeData (0x0e)
    • Current (eth/65): [value_0: B, value_1: B, ...]
    • Then (eth/66): [request_id: P, [value_0: B, value_1: B, ...]]
  • 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, ...]]
  • Receipts (0x10)
    • Current (eth/65): [[receipt_0, receipt_1], ...]
    • Then (eth/66): [request_id: P, [[receipt_0, receipt_1], ...]]

To elaborate, each command is altered in the following way:

  1. Create a list with the request_id being the first element.
  2. 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.