From 4937fa9b5860c145b74519b88065e6db97aeac27 Mon Sep 17 00:00:00 2001 From: Age Manning Date: Sun, 8 Sep 2019 22:55:55 +1000 Subject: [PATCH 01/10] Network specification update --- specs/networking/p2p-interface.md | 53 +++++++++++++++++++++---------- 1 file changed, 36 insertions(+), 17 deletions(-) diff --git a/specs/networking/p2p-interface.md b/specs/networking/p2p-interface.md index c9bab8406..64f8a3ce8 100644 --- a/specs/networking/p2p-interface.md +++ b/specs/networking/p2p-interface.md @@ -113,7 +113,7 @@ This section outlines constants that are used in this spec. | Name | Value | Description | |---|---|---| -| `REQ_RESP_MAX_SIZE` | `TODO` | The maximum size of uncompressed req/resp messages that clients will allow. | +| `REQ_RESP_MAX_SIZE` | `2**22` (4194304, 4 MiB) | The maximum size of uncompressed req/resp messages that clients will allow. | | `SSZ_MAX_LIST_SIZE` | `TODO` | The maximum size of SSZ-encoded variable lists. | | `GOSSIP_MAX_SIZE` | `2**20` (= 1048576, 1 MiB) | The maximum size of uncompressed gossip messages. | | `SHARD_SUBNET_COUNT` | `TODO` | The number of shard subnets used in the gossipsub protocol. | @@ -301,7 +301,7 @@ Here, `result` represents the 1-byte response code. The token of the negotiated protocol ID specifies the type of encoding to be used for the req/resp interaction. Two values are possible at this time: -- `ssz`: the contents are [SSZ-encoded](#ssz-encoding). This encoding type MUST be supported by all clients. For objects containing a single field, only the field is SSZ-encoded not a container with a single field. For example, the `BeaconBlocks` response would be an SSZ-encoded list of `BeaconBlock`s. All SSZ-Lists in the Req/Resp domain will have a maximum list size of `SSZ_MAX_LIST_SIZE`. +- `ssz`: the contents are [SSZ-encoded](../simple-serialize.md). This encoding type MUST be supported by all clients. For objects containing a single field, only the field is SSZ-encoded not a container with a single field. For example, the `BeaconBlocksByRange` response would be an SSZ-encoded list of `BeaconBlock`s. All SSZ-Lists in the Req/Resp domain will have a maximum list size of `SSZ_MAX_LIST_SIZE`. - `ssz_snappy`: The contents are SSZ-encoded and then compressed with [Snappy](https://github.com/google/snappy). MAY be supported in the interoperability testnet; MUST be supported in mainnet. #### SSZ-encoding strategy (with or without Snappy) @@ -318,10 +318,10 @@ The [SimpleSerialize (SSZ) specification](../simple-serialize.md) outlines how o **Protocol ID:** ``/eth2/beacon_chain/req/hello/1/`` -**Content**: +Request, Response Content: ``` ( - fork_version: bytes4 + head_fork_version: bytes4 finalized_root: bytes32 finalized_epoch: uint64 head_root: bytes32 @@ -330,26 +330,28 @@ The [SimpleSerialize (SSZ) specification](../simple-serialize.md) outlines how o ``` The fields are: -- `fork_version`: The beacon_state `Fork` version. +- `head_fork_version`: The beacon_state `Fork` version. - `finalized_root`: The latest finalized root the node knows about. - `finalized_epoch`: The latest finalized epoch the node knows about. - `head_root`: The block hash tree root corresponding to the head of the chain as seen by the sending node. - `head_slot`: The slot corresponding to the `head_root`. -Clients exchange hello messages upon connection, forming a two-phase handshake. The first message the initiating client sends MUST be the hello message. In response, the receiving client MUST respond with its own hello message. +The dialing client MUST send a `Hello` request upon connection. + +This should be encoded as an SSZ-container. Clients SHOULD immediately disconnect from one another following the handshake above under the following conditions: -1. If `fork_version` doesn’t match the local fork version, since the client’s chain is on another fork. `fork_version` can also be used to segregate testnets. +1. If `head_fork_version` doesn’t match the expected fork version at the epoch of the `head_slot`, since the client’s chain is on another fork. `head_fork_version` can also be used to segregate testnets. 2. If the (`finalized_root`, `finalized_epoch`) shared by the peer is not in the client's chain at the expected epoch. For example, if Peer 1 sends (root, epoch) of (A, 5) and Peer 2 sends (B, 3) but Peer 1 has root C at epoch 3, then Peer 1 would disconnect because it knows that their chains are irreparably disjoint. -Once the handshake completes, the client with the lower `finalized_epoch` or `head_slot` (if the clients have equal `finalized_epoch`s) SHOULD request beacon blocks from its counterparty via the `BeaconBlocks` request. +Once the handshake completes, the client with the lower `finalized_epoch` or `head_slot` (if the clients have equal `finalized_epoch`s) SHOULD request beacon blocks from its counterparty via the `BeaconBlocksByRange` request. #### Goodbye **Protocol ID:** ``/eth2/beacon_chain/req/goodbye/1/`` -**Content:** +Request, Response Content: ``` ( reason: uint64 @@ -365,11 +367,13 @@ Clients MAY use reason codes above `128` to indicate alternative, erroneous requ The range `[4, 127]` is RESERVED for future usage. -#### BeaconBlocks +This should not be encoded as an SSZ-container. -**Protocol ID:** `/eth2/beacon_chain/req/beacon_blocks/1/` +#### BeaconBlocksByRange -Request Content +**Protocol ID:** `/eth2/beacon_chain/req/beacon_blocks_by_range/1/` + +Request Content: ``` ( head_block_root: HashTreeRoot @@ -388,15 +392,24 @@ Response Content: Requests count beacon blocks from the peer starting from `start_slot` on the chain defined by `head_block_root`. The response MUST contain no more than count blocks. `step` defines the slot increment between blocks. For example, requesting blocks starting at `start_slot` 2 with a step value of 2 would return the blocks at [2, 4, 6, …]. In cases where a slot is empty for a given slot number, no block is returned. For example, if slot 4 were empty in the previous example, the returned array would contain [2, 6, …]. A step value of 1 returns all blocks on the range `[start_slot, start_slot + count)`. -`BeaconBlocks` is primarily used to sync historical blocks. +The request is encoded as an SSZ-container, the response is not encoded as an +SSZ container. + +`BeaconBlocksByRange` is primarily used to sync historical blocks. Clients MUST support requesting blocks since the start of the weak subjectivity period and up to the given `head_block_root`. Clients MUST support `head_block_root` values since the latest finalized epoch. -#### RecentBeaconBlocks +Clients MUST respond with at least one block, if they have it. -**Protocol ID:** `/eth2/beacon_chain/req/recent_beacon_blocks/1/` +Clients MUST order blocks by increasing slot number. + +Clients MAY respond with fewer blocks than requested, for example when the size of the response would exceed `REQ_RESP_MAX_SIZE` or `SSZ_MAX_LIST_SIZE`. + +#### BeaconBlocksByRoot + +**Protocol ID:** `/eth2/beacon_chain/req/beacon_blocks_by_root/1/` Request Content: @@ -414,12 +427,18 @@ Response Content: ) ``` -Requests blocks by their block roots. The response is a list of `BeaconBlock` with the same length as the request. Blocks are returned in order of the request and any missing/unknown blocks are left empty (SSZ null `BeaconBlock`). +Requests blocks by their block roots. The response is a list of `BeaconBlock` whose length is less or equal to the number of requested blocks. It may be less in the case that the responding peer is missing blocks. -`RecentBeaconBlocks` is primarily used to recover recent blocks (ex. when receiving a block or attestation whose parent is unknown). +`BeaconBlocksByRoot` is primarily used to recover recent blocks (ex. when receiving a block or attestation whose parent is unknown). + +Both the request and the response should not be encoded as an SSZ-container. Clients MUST support requesting blocks since the latest finalized epoch. +Clients MUST respond with at least one block, if they have it. + +Clients MAY respond with fewer blocks than requested, for example when the size of the response would exceed `REQ_RESP_MAX_SIZE` or `SSZ_MAX_LIST_SIZE`. + ## The discovery domain: discv5 Discovery Version 5 ([discv5](https://github.com/ethereum/devp2p/blob/master/discv5/discv5.md)) is used for peer discovery, both in the interoperability testnet and mainnet. From 3a79ad53637be372a4dc021c61e1069948ef3bd1 Mon Sep 17 00:00:00 2001 From: Age Manning Date: Mon, 9 Sep 2019 05:38:06 +1000 Subject: [PATCH 02/10] Adds chunked responses to the RPC --- specs/networking/p2p-interface.md | 36 +++++++++++++++++++------------ 1 file changed, 22 insertions(+), 14 deletions(-) diff --git a/specs/networking/p2p-interface.md b/specs/networking/p2p-interface.md index 64f8a3ce8..a72843ba6 100644 --- a/specs/networking/p2p-interface.md +++ b/specs/networking/p2p-interface.md @@ -231,15 +231,16 @@ Request/response messages MUST adhere to the encoding specified in the protocol ``` request ::= | -response ::= | | +response ::= + +response_chunk ::= | | result ::= “0” | “1” | “2” | [“128” ... ”255”] ``` The encoding-dependent header may carry metadata or assertions such as the encoded payload length, for integrity and attack proofing purposes. Because req/resp streams are single-use and stream closures implicitly delimit the boundaries, it is not strictly necessary to length-prefix payloads; however, certain encodings like SSZ do, for added security. -`encoded-payload` has a maximum byte size of `REQ_RESP_MAX_SIZE`. +A `response` is formed by one or more `response_chunk`. The exact request determines whether a response consists of a single `response_chunk` or many. Responses that consist of a single SSZ-list of objects (such as `BlocksByRange` and `BlocksByRoot`) will send each list item as a `response_chunk`. The total `response` has a maximum uncompressed byte size of `REQ_RESP_MAX_SIZE`. -Clients MUST ensure the payload size is less than or equal to `REQ_RESP_MAX_SIZE`; if not, they SHOULD reset the stream immediately. Clients tracking peer reputation MAY decrement the score of the misbehaving peer under this circumstance. +Clients MUST ensure the total `response` is less than or equal to `REQ_RESP_MAX_SIZE`; if not, they SHOULD reset the stream immediately. Clients tracking peer reputation MAY decrement the score of the misbehaving peer under this circumstance. #### Requesting side @@ -247,7 +248,7 @@ Once a new stream with the protocol ID for the request type has been negotiated, The requester MUST close the write side of the stream once it finishes writing the request message—at this point, the stream will be half-closed. -The requester MUST wait a maximum of `TTFB_TIMEOUT` for the first response byte to arrive (time to first byte—or TTFB—timeout). On that happening, the requester will allow further `RESP_TIMEOUT` to receive the full response. +The requester MUST wait a maximum of `TTFB_TIMEOUT` for the first response byte to arrive (time to first byte—or TTFB—timeout). On that happening, the requester will allow further `RESP_TIMEOUT` to receive the full response. For requests consisting of many `response_chunk` the requester should read from the stream until either; a) An error is received by one of the chunks, b) The responder closes the stream or c) `REQ_RESP_MAX_SIZE` bytes have been read. Requests that have a single `response_chunk` and a length-prefix, requesters can read the exact number of bytes defined by the length-prefix before closing the stream. If any of these timeouts fire, the requester SHOULD reset the stream and deem the req/resp operation to have failed. @@ -260,14 +261,14 @@ The responder MUST: 1. Use the encoding strategy to read the optional header. 2. If there are any length assertions for length `N`, it should read exactly `N` bytes from the stream, at which point an EOF should arise (no more bytes). Should this not be the case, it should be treated as a failure. 3. Deserialize the expected type, and process the request. -4. Write the response (result, optional header, payload). +4. Write the response which may consist of one or many `response_chunks` (result, optional header, payload). 5. Close their write side of the stream. At this point, the stream will be fully closed. If steps (1), (2), or (3) fail due to invalid, malformed, or inconsistent data, the responder MUST respond in error. Clients tracking peer reputation MAY record such failures, as well as unexpected events, e.g. early stream resets. The entire request should be read in no more than `RESP_TIMEOUT`. Upon a timeout, the responder SHOULD reset the stream. -The responder SHOULD send a response promptly, starting with a **single-byte** response code which determines the contents of the response (`result` particle in the BNF grammar above). +The responder SHOULD send a response promptly, starting with a **single-byte** response code which determines the contents of the `response_chunk` (`result` particle in the BNF grammar above). It can have one of the following values, encoded as a single unsigned byte: @@ -289,7 +290,8 @@ The `ErrorMessage` schema is: *Note*: The String type is encoded as UTF-8 bytes without NULL terminator when SSZ-encoded. As the `ErrorMessage` is not an SSZ-container, only the UTF-8 bytes will be sent when SSZ-encoded. -A response therefore has the form: +A response therefore has the form of 1 or more `response_chunk` which look +like: ``` +--------+--------+--------+--------+--------+--------+ | result | header (opt) | encoded_response | @@ -310,7 +312,11 @@ The [SimpleSerialize (SSZ) specification](../simple-serialize.md) outlines how o **Encoding-dependent header:** Req/Resp protocols using the `ssz` or `ssz_snappy` encoding strategies MUST prefix all encoded and compressed (if applicable) payloads with an unsigned [protobuf varint](https://developers.google.com/protocol-buffers/docs/encoding#varints). -*Note*: Parameters defined as `[]VariableName` are SSZ-encoded containerless vectors. +All messages that have a single field, are not encoded as SSZ containers. + +Responses that are SSZ list objects (for example `[]BeaconBlocks`) send their +constituents individually as `response_chunk`. For example, the +`[]BeaconBlocks` response send many `response_chunk`s with each payload being a `BeaconBlock`. ### Messages @@ -338,7 +344,8 @@ The fields are: The dialing client MUST send a `Hello` request upon connection. -This should be encoded as an SSZ-container. +This should be encoded as an SSZ-container. The response consists of a single +`response_chunk`. Clients SHOULD immediately disconnect from one another following the handshake above under the following conditions: @@ -367,7 +374,8 @@ Clients MAY use reason codes above `128` to indicate alternative, erroneous requ The range `[4, 127]` is RESERVED for future usage. -This should not be encoded as an SSZ-container. +This should not be encoded as an SSZ-container. The response consists of a +single `response_chunk`. #### BeaconBlocksByRange @@ -392,8 +400,8 @@ Response Content: Requests count beacon blocks from the peer starting from `start_slot` on the chain defined by `head_block_root`. The response MUST contain no more than count blocks. `step` defines the slot increment between blocks. For example, requesting blocks starting at `start_slot` 2 with a step value of 2 would return the blocks at [2, 4, 6, …]. In cases where a slot is empty for a given slot number, no block is returned. For example, if slot 4 were empty in the previous example, the returned array would contain [2, 6, …]. A step value of 1 returns all blocks on the range `[start_slot, start_slot + count)`. -The request is encoded as an SSZ-container, the response is not encoded as an -SSZ container. +The request is encoded as an SSZ-container. The response is sent as many +`response_chunk` with each chunk consisting of a single `BeaconBlock`. `BeaconBlocksByRange` is primarily used to sync historical blocks. @@ -431,13 +439,13 @@ Requests blocks by their block roots. The response is a list of `BeaconBlock` wh `BeaconBlocksByRoot` is primarily used to recover recent blocks (ex. when receiving a block or attestation whose parent is unknown). -Both the request and the response should not be encoded as an SSZ-container. +The request is not encoded as an SSZ-container. The response is sent as many `response_chunk` with each chunk consisting of a single `BeaconBlock`. Clients MUST support requesting blocks since the latest finalized epoch. Clients MUST respond with at least one block, if they have it. -Clients MAY respond with fewer blocks than requested, for example when the size of the response would exceed `REQ_RESP_MAX_SIZE` or `SSZ_MAX_LIST_SIZE`. +Clients MAY respond with fewer blocks than requested, for example when the size of the uncompressed response would exceed `REQ_RESP_MAX_SIZE` or `SSZ_MAX_LIST_SIZE`. ## The discovery domain: discv5 From acb86e881791e8fe7e180adabd77195068c5475b Mon Sep 17 00:00:00 2001 From: Age Manning Date: Mon, 9 Sep 2019 05:45:42 +1000 Subject: [PATCH 03/10] Apply Danny's suggestions --- specs/networking/p2p-interface.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/specs/networking/p2p-interface.md b/specs/networking/p2p-interface.md index a72843ba6..c51320b5a 100644 --- a/specs/networking/p2p-interface.md +++ b/specs/networking/p2p-interface.md @@ -113,7 +113,7 @@ This section outlines constants that are used in this spec. | Name | Value | Description | |---|---|---| -| `REQ_RESP_MAX_SIZE` | `2**22` (4194304, 4 MiB) | The maximum size of uncompressed req/resp messages that clients will allow. | +| `REQ_RESP_MAX_SIZE` | `2**20` (1048576, 1 MiB) | The maximum size of uncompressed req/resp messages that clients will allow. | | `SSZ_MAX_LIST_SIZE` | `TODO` | The maximum size of SSZ-encoded variable lists. | | `GOSSIP_MAX_SIZE` | `2**20` (= 1048576, 1 MiB) | The maximum size of uncompressed gossip messages. | | `SHARD_SUBNET_COUNT` | `TODO` | The number of shard subnets used in the gossipsub protocol. | @@ -244,7 +244,7 @@ Clients MUST ensure the total `response` is less than or equal to `REQ_RESP_MAX_ #### Requesting side -Once a new stream with the protocol ID for the request type has been negotiated, the full request message should be sent immediately. It should be encoded according to the encoding strategy. +Once a new stream with the protocol ID for the request type has been negotiated, the full request message should be sent immediately. It MUST be encoded according to the encoding strategy. The requester MUST close the write side of the stream once it finishes writing the request message—at this point, the stream will be half-closed. @@ -344,7 +344,7 @@ The fields are: The dialing client MUST send a `Hello` request upon connection. -This should be encoded as an SSZ-container. The response consists of a single +This MUST be encoded as an SSZ-container. The response consists of a single `response_chunk`. Clients SHOULD immediately disconnect from one another following the handshake above under the following conditions: @@ -374,7 +374,7 @@ Clients MAY use reason codes above `128` to indicate alternative, erroneous requ The range `[4, 127]` is RESERVED for future usage. -This should not be encoded as an SSZ-container. The response consists of a +This MUST be encoded as a single SSZ-field. The response consists of a single `response_chunk`. #### BeaconBlocksByRange @@ -400,7 +400,7 @@ Response Content: Requests count beacon blocks from the peer starting from `start_slot` on the chain defined by `head_block_root`. The response MUST contain no more than count blocks. `step` defines the slot increment between blocks. For example, requesting blocks starting at `start_slot` 2 with a step value of 2 would return the blocks at [2, 4, 6, …]. In cases where a slot is empty for a given slot number, no block is returned. For example, if slot 4 were empty in the previous example, the returned array would contain [2, 6, …]. A step value of 1 returns all blocks on the range `[start_slot, start_slot + count)`. -The request is encoded as an SSZ-container. The response is sent as many +The request MUST be encoded as an SSZ-container. The response is sent as many `response_chunk` with each chunk consisting of a single `BeaconBlock`. `BeaconBlocksByRange` is primarily used to sync historical blocks. @@ -439,7 +439,7 @@ Requests blocks by their block roots. The response is a list of `BeaconBlock` wh `BeaconBlocksByRoot` is primarily used to recover recent blocks (ex. when receiving a block or attestation whose parent is unknown). -The request is not encoded as an SSZ-container. The response is sent as many `response_chunk` with each chunk consisting of a single `BeaconBlock`. +The request MUST be encoded as an SSZ-field. The response is sent as many `response_chunk` with each chunk consisting of a single `BeaconBlock`. Clients MUST support requesting blocks since the latest finalized epoch. From cc12e29b25dab10e45a43fd0441e0a0abe71979a Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Sun, 8 Sep 2019 14:57:53 -0600 Subject: [PATCH 04/10] cleanup response_chunk refactor --- specs/networking/p2p-interface.md | 30 +++++++++++++++++------------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/specs/networking/p2p-interface.md b/specs/networking/p2p-interface.md index c51320b5a..45c2361ca 100644 --- a/specs/networking/p2p-interface.md +++ b/specs/networking/p2p-interface.md @@ -248,7 +248,7 @@ Once a new stream with the protocol ID for the request type has been negotiated, The requester MUST close the write side of the stream once it finishes writing the request message—at this point, the stream will be half-closed. -The requester MUST wait a maximum of `TTFB_TIMEOUT` for the first response byte to arrive (time to first byte—or TTFB—timeout). On that happening, the requester will allow further `RESP_TIMEOUT` to receive the full response. For requests consisting of many `response_chunk` the requester should read from the stream until either; a) An error is received by one of the chunks, b) The responder closes the stream or c) `REQ_RESP_MAX_SIZE` bytes have been read. Requests that have a single `response_chunk` and a length-prefix, requesters can read the exact number of bytes defined by the length-prefix before closing the stream. +The requester MUST wait a maximum of `TTFB_TIMEOUT` for the first response byte to arrive (time to first byte—or TTFB—timeout). On that happening, the requester allows a further `RESP_TIMEOUT` to receive the full response. For requests consisting of many `response_chunk` the requester SHOULD read from the stream until either; a) An error is received in one of the chunks, b) The responder closes the stream or c) `REQ_RESP_MAX_SIZE` bytes have been read. For requests consisting of a single `response_chunk` and a length-prefix, the requester should read the exact number of bytes defined by the length-prefix before closing the stream. If any of these timeouts fire, the requester SHOULD reset the stream and deem the req/resp operation to have failed. @@ -261,7 +261,7 @@ The responder MUST: 1. Use the encoding strategy to read the optional header. 2. If there are any length assertions for length `N`, it should read exactly `N` bytes from the stream, at which point an EOF should arise (no more bytes). Should this not be the case, it should be treated as a failure. 3. Deserialize the expected type, and process the request. -4. Write the response which may consist of one or many `response_chunks` (result, optional header, payload). +4. Write the response which may consist of one or many `response_chunk` (result, optional header, payload). 5. Close their write side of the stream. At this point, the stream will be fully closed. If steps (1), (2), or (3) fail due to invalid, malformed, or inconsistent data, the responder MUST respond in error. Clients tracking peer reputation MAY record such failures, as well as unexpected events, e.g. early stream resets. @@ -290,8 +290,7 @@ The `ErrorMessage` schema is: *Note*: The String type is encoded as UTF-8 bytes without NULL terminator when SSZ-encoded. As the `ErrorMessage` is not an SSZ-container, only the UTF-8 bytes will be sent when SSZ-encoded. -A response therefore has the form of 1 or more `response_chunk` which look -like: +A response therefore has the form of one or more `response_chunk`, each structured as follows: ``` +--------+--------+--------+--------+--------+--------+ | result | header (opt) | encoded_response | @@ -312,11 +311,11 @@ The [SimpleSerialize (SSZ) specification](../simple-serialize.md) outlines how o **Encoding-dependent header:** Req/Resp protocols using the `ssz` or `ssz_snappy` encoding strategies MUST prefix all encoded and compressed (if applicable) payloads with an unsigned [protobuf varint](https://developers.google.com/protocol-buffers/docs/encoding#varints). -All messages that have a single field, are not encoded as SSZ containers. +All messages that contain only a single field MUST be encoded directly as the type of that field and MUST NOT be encoded as an SSZ container. Responses that are SSZ list objects (for example `[]BeaconBlocks`) send their constituents individually as `response_chunk`. For example, the -`[]BeaconBlocks` response send many `response_chunk`s with each payload being a `BeaconBlock`. +`[]BeaconBlocks` response type sends one or more `response_chunk`s. Each successful `response_chunk` has a single `BeaconBlock` payload. ### Messages @@ -344,8 +343,9 @@ The fields are: The dialing client MUST send a `Hello` request upon connection. -This MUST be encoded as an SSZ-container. The response consists of a single -`response_chunk`. +The request/response MUST be encoded as an SSZ-container. + +The response MUST consist of a single `response_chunk`. Clients SHOULD immediately disconnect from one another following the handshake above under the following conditions: @@ -374,8 +374,9 @@ Clients MAY use reason codes above `128` to indicate alternative, erroneous requ The range `[4, 127]` is RESERVED for future usage. -This MUST be encoded as a single SSZ-field. The response consists of a -single `response_chunk`. +The request/response MUST be encoded as a single SSZ-field. + +The response MUST consist of a single `response_chunk`. #### BeaconBlocksByRange @@ -400,8 +401,9 @@ Response Content: Requests count beacon blocks from the peer starting from `start_slot` on the chain defined by `head_block_root`. The response MUST contain no more than count blocks. `step` defines the slot increment between blocks. For example, requesting blocks starting at `start_slot` 2 with a step value of 2 would return the blocks at [2, 4, 6, …]. In cases where a slot is empty for a given slot number, no block is returned. For example, if slot 4 were empty in the previous example, the returned array would contain [2, 6, …]. A step value of 1 returns all blocks on the range `[start_slot, start_slot + count)`. -The request MUST be encoded as an SSZ-container. The response is sent as many -`response_chunk` with each chunk consisting of a single `BeaconBlock`. +The request MUST be encoded as an SSZ-container. + +The response MUST consist of at least one `response_chunk` and MAY consist of many. Each successful `response_chunk` MUST contain a single `BeaconBlock` payload. `BeaconBlocksByRange` is primarily used to sync historical blocks. @@ -439,7 +441,9 @@ Requests blocks by their block roots. The response is a list of `BeaconBlock` wh `BeaconBlocksByRoot` is primarily used to recover recent blocks (ex. when receiving a block or attestation whose parent is unknown). -The request MUST be encoded as an SSZ-field. The response is sent as many `response_chunk` with each chunk consisting of a single `BeaconBlock`. +The request MUST be encoded as an SSZ-field. + +The response MUST consist of at least one `response_chunk` and MAY consist of many. Each successful `response_chunk` MUST contain a single `BeaconBlock` payload. Clients MUST support requesting blocks since the latest finalized epoch. From b743deb0612c741fc4546c12db52cfcc5338bc87 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Sun, 8 Sep 2019 15:03:25 -0600 Subject: [PATCH 05/10] cleanup max size vars --- specs/networking/p2p-interface.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/specs/networking/p2p-interface.md b/specs/networking/p2p-interface.md index 45c2361ca..90b9b16a0 100644 --- a/specs/networking/p2p-interface.md +++ b/specs/networking/p2p-interface.md @@ -113,9 +113,9 @@ This section outlines constants that are used in this spec. | Name | Value | Description | |---|---|---| -| `REQ_RESP_MAX_SIZE` | `2**20` (1048576, 1 MiB) | The maximum size of uncompressed req/resp messages that clients will allow. | +| `GOSSIP_MAX_SIZE` | `2**20` (= 1048576, 1 MiB) | The maximum allowed size of uncompressed gossip messages. | +| `REQ_RESP_MAX_SIZE` | `2**20` (1048576, 1 MiB) | The maximum allowed size of uncompressed req/resp messages. | | `SSZ_MAX_LIST_SIZE` | `TODO` | The maximum size of SSZ-encoded variable lists. | -| `GOSSIP_MAX_SIZE` | `2**20` (= 1048576, 1 MiB) | The maximum size of uncompressed gossip messages. | | `SHARD_SUBNET_COUNT` | `TODO` | The number of shard subnets used in the gossipsub protocol. | | `TTFB_TIMEOUT` | `5s` | The maximum time to wait for first byte of request response (time-to-first-byte). | | `RESP_TIMEOUT` | `10s` | The maximum time for complete response transfer. | From 3ead8981099d56633e14a40922991d5960342a1d Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Sun, 8 Sep 2019 15:31:22 -0600 Subject: [PATCH 06/10] p2p spec copy cleanups --- specs/networking/p2p-interface.md | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/specs/networking/p2p-interface.md b/specs/networking/p2p-interface.md index 90b9b16a0..40228708e 100644 --- a/specs/networking/p2p-interface.md +++ b/specs/networking/p2p-interface.md @@ -238,17 +238,17 @@ result ::= “0” | “1” | “2” | [“128” ... ”255”] The encoding-dependent header may carry metadata or assertions such as the encoded payload length, for integrity and attack proofing purposes. Because req/resp streams are single-use and stream closures implicitly delimit the boundaries, it is not strictly necessary to length-prefix payloads; however, certain encodings like SSZ do, for added security. -A `response` is formed by one or more `response_chunk`. The exact request determines whether a response consists of a single `response_chunk` or many. Responses that consist of a single SSZ-list of objects (such as `BlocksByRange` and `BlocksByRoot`) will send each list item as a `response_chunk`. The total `response` has a maximum uncompressed byte size of `REQ_RESP_MAX_SIZE`. +A `response` is formed by one or more `response_chunk`s. The exact request determines whether a response consists of a single `response_chunk` or possibly many. Responses that consist of a single SSZ-list (such as `BlocksByRange` and `BlocksByRoot`) send each list item as a `response_chunk`. All other response types (non-Lists) send a single `response_chunk`. The total `response` has a maximum uncompressed byte size of `REQ_RESP_MAX_SIZE`. Clients MUST ensure the total `response` is less than or equal to `REQ_RESP_MAX_SIZE`; if not, they SHOULD reset the stream immediately. Clients tracking peer reputation MAY decrement the score of the misbehaving peer under this circumstance. #### Requesting side -Once a new stream with the protocol ID for the request type has been negotiated, the full request message should be sent immediately. It MUST be encoded according to the encoding strategy. +Once a new stream with the protocol ID for the request type has been negotiated, the full request message SHOULD be sent immediately. The request MUST be encoded according to the encoding strategy. -The requester MUST close the write side of the stream once it finishes writing the request message—at this point, the stream will be half-closed. +The requester MUST close the write side of the stream once it finishes writing the request message. At this point, the stream will be half-closed. -The requester MUST wait a maximum of `TTFB_TIMEOUT` for the first response byte to arrive (time to first byte—or TTFB—timeout). On that happening, the requester allows a further `RESP_TIMEOUT` to receive the full response. For requests consisting of many `response_chunk` the requester SHOULD read from the stream until either; a) An error is received in one of the chunks, b) The responder closes the stream or c) `REQ_RESP_MAX_SIZE` bytes have been read. For requests consisting of a single `response_chunk` and a length-prefix, the requester should read the exact number of bytes defined by the length-prefix before closing the stream. +The requester MUST wait a maximum of `TTFB_TIMEOUT` for the first response byte to arrive (time to first byte—or TTFB—timeout). On that happening, the requester allows a further `RESP_TIMEOUT` to receive the full response. For responses consisting of potentially many `response_chunk`s (an SSZ-list) the requester SHOULD read from the stream until either; a) An error result is received in one of the chunks, b) The responder closes the stream or c) `REQ_RESP_MAX_SIZE` bytes have been read. For requests consisting of a single `response_chunk` and a length-prefix, the requester should read the exact number of bytes defined by the length-prefix before closing the stream. If any of these timeouts fire, the requester SHOULD reset the stream and deem the req/resp operation to have failed. @@ -261,7 +261,7 @@ The responder MUST: 1. Use the encoding strategy to read the optional header. 2. If there are any length assertions for length `N`, it should read exactly `N` bytes from the stream, at which point an EOF should arise (no more bytes). Should this not be the case, it should be treated as a failure. 3. Deserialize the expected type, and process the request. -4. Write the response which may consist of one or many `response_chunk` (result, optional header, payload). +4. Write the response which may consist of one or more `response_chunk`s (result, optional header, payload). 5. Close their write side of the stream. At this point, the stream will be fully closed. If steps (1), (2), or (3) fail due to invalid, malformed, or inconsistent data, the responder MUST respond in error. Clients tracking peer reputation MAY record such failures, as well as unexpected events, e.g. early stream resets. @@ -290,7 +290,7 @@ The `ErrorMessage` schema is: *Note*: The String type is encoded as UTF-8 bytes without NULL terminator when SSZ-encoded. As the `ErrorMessage` is not an SSZ-container, only the UTF-8 bytes will be sent when SSZ-encoded. -A response therefore has the form of one or more `response_chunk`, each structured as follows: +A response therefore has the form of one or more `response_chunk`s, each structured as follows: ``` +--------+--------+--------+--------+--------+--------+ | result | header (opt) | encoded_response | @@ -313,9 +313,9 @@ The [SimpleSerialize (SSZ) specification](../simple-serialize.md) outlines how o All messages that contain only a single field MUST be encoded directly as the type of that field and MUST NOT be encoded as an SSZ container. -Responses that are SSZ list objects (for example `[]BeaconBlocks`) send their -constituents individually as `response_chunk`. For example, the -`[]BeaconBlocks` response type sends one or more `response_chunk`s. Each successful `response_chunk` has a single `BeaconBlock` payload. +Responses that are SSZ-lists (for example `[]BeaconBlocks`) send their +constituents individually as `response_chunk`s. For example, the +`[]BeaconBlocks` response type sends one or more `response_chunk`s. Each _successful_ `response_chunk` contains a single `BeaconBlock` payload. ### Messages @@ -403,7 +403,7 @@ Requests count beacon blocks from the peer starting from `start_slot` on the cha The request MUST be encoded as an SSZ-container. -The response MUST consist of at least one `response_chunk` and MAY consist of many. Each successful `response_chunk` MUST contain a single `BeaconBlock` payload. +The response MUST consist of at least one `response_chunk` and MAY consist of many. Each _successful_ `response_chunk` MUST contain a single `BeaconBlock` payload. `BeaconBlocksByRange` is primarily used to sync historical blocks. @@ -437,13 +437,13 @@ Response Content: ) ``` -Requests blocks by their block roots. The response is a list of `BeaconBlock` whose length is less or equal to the number of requested blocks. It may be less in the case that the responding peer is missing blocks. +Requests blocks by their block roots. The response is a list of `BeaconBlock` whose length is less than or equal to the number of requested blocks. It may be less in the case that the responding peer is missing blocks. -`BeaconBlocksByRoot` is primarily used to recover recent blocks (ex. when receiving a block or attestation whose parent is unknown). +`BeaconBlocksByRoot` is primarily used to recover recent blocks (e.g. when receiving a block or attestation whose parent is unknown). The request MUST be encoded as an SSZ-field. -The response MUST consist of at least one `response_chunk` and MAY consist of many. Each successful `response_chunk` MUST contain a single `BeaconBlock` payload. +The response MUST consist of at least one `response_chunk` and MAY consist of many. Each _successful_ `response_chunk` MUST contain a single `BeaconBlock` payload. Clients MUST support requesting blocks since the latest finalized epoch. From 4a7d8a4e480786f27390ea7cb8f580977338bb33 Mon Sep 17 00:00:00 2001 From: Age Manning Date: Wed, 11 Sep 2019 06:06:22 +1000 Subject: [PATCH 07/10] Applies github suggestions --- specs/networking/p2p-interface.md | 39 ++++++++++++++++--------------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/specs/networking/p2p-interface.md b/specs/networking/p2p-interface.md index 40228708e..d97be2b24 100644 --- a/specs/networking/p2p-interface.md +++ b/specs/networking/p2p-interface.md @@ -114,8 +114,9 @@ This section outlines constants that are used in this spec. | Name | Value | Description | |---|---|---| | `GOSSIP_MAX_SIZE` | `2**20` (= 1048576, 1 MiB) | The maximum allowed size of uncompressed gossip messages. | -| `REQ_RESP_MAX_SIZE` | `2**20` (1048576, 1 MiB) | The maximum allowed size of uncompressed req/resp messages. | -| `SSZ_MAX_LIST_SIZE` | `TODO` | The maximum size of SSZ-encoded variable lists. | +| `REQ_RESP_MAX_SIZE` | `2**20` (1048576, 1 MiB) | The maximum allowed size of uncompressed req/resp chunked responses. | +| `MAX_REQUESTED_BLOCKS` | `20` | The maximum allowed number of blocks that can +be requested. | | `SHARD_SUBNET_COUNT` | `TODO` | The number of shard subnets used in the gossipsub protocol. | | `TTFB_TIMEOUT` | `5s` | The maximum time to wait for first byte of request response (time-to-first-byte). | | `RESP_TIMEOUT` | `10s` | The maximum time for complete response transfer. | @@ -238,9 +239,9 @@ result ::= “0” | “1” | “2” | [“128” ... ”255”] The encoding-dependent header may carry metadata or assertions such as the encoded payload length, for integrity and attack proofing purposes. Because req/resp streams are single-use and stream closures implicitly delimit the boundaries, it is not strictly necessary to length-prefix payloads; however, certain encodings like SSZ do, for added security. -A `response` is formed by one or more `response_chunk`s. The exact request determines whether a response consists of a single `response_chunk` or possibly many. Responses that consist of a single SSZ-list (such as `BlocksByRange` and `BlocksByRoot`) send each list item as a `response_chunk`. All other response types (non-Lists) send a single `response_chunk`. The total `response` has a maximum uncompressed byte size of `REQ_RESP_MAX_SIZE`. +A `response` is formed by one or more `response_chunk`s. The exact request determines whether a response consists of a single `response_chunk` or possibly many. Responses that consist of a single SSZ-list (such as `BlocksByRange` and `BlocksByRoot`) send each list item as a `response_chunk`. All other response types (non-Lists) send a single `response_chunk`. The encoded-payload of a `response_chunk` has a maximum uncompressed byte size of `REQ_RESP_MAX_SIZE`. -Clients MUST ensure the total `response` is less than or equal to `REQ_RESP_MAX_SIZE`; if not, they SHOULD reset the stream immediately. Clients tracking peer reputation MAY decrement the score of the misbehaving peer under this circumstance. +Clients MUST ensure the each encoded payload of a `response_chunk` is less than or equal to `REQ_RESP_MAX_SIZE`; if not, they SHOULD reset the stream immediately. Clients tracking peer reputation MAY decrement the score of the misbehaving peer under this circumstance. #### Requesting side @@ -248,7 +249,7 @@ Once a new stream with the protocol ID for the request type has been negotiated, The requester MUST close the write side of the stream once it finishes writing the request message. At this point, the stream will be half-closed. -The requester MUST wait a maximum of `TTFB_TIMEOUT` for the first response byte to arrive (time to first byte—or TTFB—timeout). On that happening, the requester allows a further `RESP_TIMEOUT` to receive the full response. For responses consisting of potentially many `response_chunk`s (an SSZ-list) the requester SHOULD read from the stream until either; a) An error result is received in one of the chunks, b) The responder closes the stream or c) `REQ_RESP_MAX_SIZE` bytes have been read. For requests consisting of a single `response_chunk` and a length-prefix, the requester should read the exact number of bytes defined by the length-prefix before closing the stream. +The requester MUST wait a maximum of `TTFB_TIMEOUT` for the first response byte to arrive (time to first byte—or TTFB—timeout). On that happening, the requester allows a further `RESP_TIMEOUT` to receive the full response. For responses consisting of potentially many `response_chunk`s (an SSZ-list) the requester SHOULD read from the stream until either; a) An error result is received in one of the chunks, b) The responder closes the stream, c) More than `REQ_RESP_MAX_SIZE` bytes have been read for a single `response_chunk` payload or d) More than the maximum number of requested chunks are read. For requests consisting of a single `response_chunk` and a length-prefix, the requester should read the exact number of bytes defined by the length-prefix before closing the stream. If any of these timeouts fire, the requester SHOULD reset the stream and deem the req/resp operation to have failed. @@ -268,9 +269,9 @@ If steps (1), (2), or (3) fail due to invalid, malformed, or inconsistent data, The entire request should be read in no more than `RESP_TIMEOUT`. Upon a timeout, the responder SHOULD reset the stream. -The responder SHOULD send a response promptly, starting with a **single-byte** response code which determines the contents of the `response_chunk` (`result` particle in the BNF grammar above). +The responder SHOULD send a `response_chunk` promptly. Chunks start with a **single-byte** response code which determines the contents of the `response_chunk` (`result` particle in the BNF grammar above). -It can have one of the following values, encoded as a single unsigned byte: +The response code can have one of the following values, encoded as a single unsigned byte: - 0: **Success** -- a normal response follows, with contents matching the expected message schema and encoding specified in the request. - 1: **InvalidRequest** -- the contents of the request are semantically invalid, or the payload is malformed, or could not be understood. The response payload adheres to the `ErrorMessage` schema (described below). @@ -302,7 +303,7 @@ Here, `result` represents the 1-byte response code. The token of the negotiated protocol ID specifies the type of encoding to be used for the req/resp interaction. Two values are possible at this time: -- `ssz`: the contents are [SSZ-encoded](../simple-serialize.md). This encoding type MUST be supported by all clients. For objects containing a single field, only the field is SSZ-encoded not a container with a single field. For example, the `BeaconBlocksByRange` response would be an SSZ-encoded list of `BeaconBlock`s. All SSZ-Lists in the Req/Resp domain will have a maximum list size of `SSZ_MAX_LIST_SIZE`. +- `ssz`: the contents are [SSZ-encoded](../simple-serialize.md). This encoding type MUST be supported by all clients. For objects containing a single field, only the field is SSZ-encoded not a container with a single field. For example, the `BeaconBlocksByRoot` request is an SSZ-encoded list of `HashTreeRoots`'s. - `ssz_snappy`: The contents are SSZ-encoded and then compressed with [Snappy](https://github.com/google/snappy). MAY be supported in the interoperability testnet; MUST be supported in mainnet. #### SSZ-encoding strategy (with or without Snappy) @@ -319,9 +320,9 @@ constituents individually as `response_chunk`s. For example, the ### Messages -#### Hello +#### Status -**Protocol ID:** ``/eth2/beacon_chain/req/hello/1/`` +**Protocol ID:** ``/eth2/beacon_chain/req/status/1/`` Request, Response Content: ``` @@ -341,7 +342,7 @@ The fields are: - `head_root`: The block hash tree root corresponding to the head of the chain as seen by the sending node. - `head_slot`: The slot corresponding to the `head_root`. -The dialing client MUST send a `Hello` request upon connection. +The dialing client MUST send a `Status` request upon connection. The request/response MUST be encoded as an SSZ-container. @@ -349,7 +350,7 @@ The response MUST consist of a single `response_chunk`. Clients SHOULD immediately disconnect from one another following the handshake above under the following conditions: -1. If `head_fork_version` doesn’t match the expected fork version at the epoch of the `head_slot`, since the client’s chain is on another fork. `head_fork_version` can also be used to segregate testnets. +1. If `head_fork_version` does not match the expected fork version at the epoch of the `head_slot`, since the client’s chain is on another fork. `head_fork_version` can also be used to segregate testnets. 2. If the (`finalized_root`, `finalized_epoch`) shared by the peer is not in the client's chain at the expected epoch. For example, if Peer 1 sends (root, epoch) of (A, 5) and Peer 2 sends (B, 3) but Peer 1 has root C at epoch 3, then Peer 1 would disconnect because it knows that their chains are irreparably disjoint. Once the handshake completes, the client with the lower `finalized_epoch` or `head_slot` (if the clients have equal `finalized_epoch`s) SHOULD request beacon blocks from its counterparty via the `BeaconBlocksByRange` request. @@ -361,7 +362,7 @@ Once the handshake completes, the client with the lower `finalized_epoch` or `he Request, Response Content: ``` ( - reason: uint64 + uint64 ) ``` Client MAY send goodbye messages upon disconnection. The reason field MAY be one of the following values: @@ -395,7 +396,7 @@ Request Content: Response Content: ``` ( - blocks: []BeaconBlock + []BeaconBlock ) ``` @@ -403,6 +404,8 @@ Requests count beacon blocks from the peer starting from `start_slot` on the cha The request MUST be encoded as an SSZ-container. +The `count` MUST not exceed MAX_REQUESTED_BLOCKS. + The response MUST consist of at least one `response_chunk` and MAY consist of many. Each _successful_ `response_chunk` MUST contain a single `BeaconBlock` payload. `BeaconBlocksByRange` is primarily used to sync historical blocks. @@ -415,8 +418,6 @@ Clients MUST respond with at least one block, if they have it. Clients MUST order blocks by increasing slot number. -Clients MAY respond with fewer blocks than requested, for example when the size of the response would exceed `REQ_RESP_MAX_SIZE` or `SSZ_MAX_LIST_SIZE`. - #### BeaconBlocksByRoot **Protocol ID:** `/eth2/beacon_chain/req/beacon_blocks_by_root/1/` @@ -441,7 +442,9 @@ Requests blocks by their block roots. The response is a list of `BeaconBlock` wh `BeaconBlocksByRoot` is primarily used to recover recent blocks (e.g. when receiving a block or attestation whose parent is unknown). -The request MUST be encoded as an SSZ-field. +The length of `block_roots` MUST not exceed MAX_REQUESTED_BLOCKS. + +The request MUST be encoded as an SSZ-field. The response MUST consist of at least one `response_chunk` and MAY consist of many. Each _successful_ `response_chunk` MUST contain a single `BeaconBlock` payload. @@ -449,8 +452,6 @@ Clients MUST support requesting blocks since the latest finalized epoch. Clients MUST respond with at least one block, if they have it. -Clients MAY respond with fewer blocks than requested, for example when the size of the uncompressed response would exceed `REQ_RESP_MAX_SIZE` or `SSZ_MAX_LIST_SIZE`. - ## The discovery domain: discv5 Discovery Version 5 ([discv5](https://github.com/ethereum/devp2p/blob/master/discv5/discv5.md)) is used for peer discovery, both in the interoperability testnet and mainnet. From 9fa720b994668df2a9e886295cb0a6b55f71e858 Mon Sep 17 00:00:00 2001 From: Age Manning Date: Wed, 11 Sep 2019 23:23:14 +1000 Subject: [PATCH 08/10] Removes a max chunk count and corrects timeout for chunked responses --- specs/networking/p2p-interface.md | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/specs/networking/p2p-interface.md b/specs/networking/p2p-interface.md index d97be2b24..e33026be3 100644 --- a/specs/networking/p2p-interface.md +++ b/specs/networking/p2p-interface.md @@ -115,8 +115,6 @@ This section outlines constants that are used in this spec. |---|---|---| | `GOSSIP_MAX_SIZE` | `2**20` (= 1048576, 1 MiB) | The maximum allowed size of uncompressed gossip messages. | | `REQ_RESP_MAX_SIZE` | `2**20` (1048576, 1 MiB) | The maximum allowed size of uncompressed req/resp chunked responses. | -| `MAX_REQUESTED_BLOCKS` | `20` | The maximum allowed number of blocks that can -be requested. | | `SHARD_SUBNET_COUNT` | `TODO` | The number of shard subnets used in the gossipsub protocol. | | `TTFB_TIMEOUT` | `5s` | The maximum time to wait for first byte of request response (time-to-first-byte). | | `RESP_TIMEOUT` | `10s` | The maximum time for complete response transfer. | @@ -249,7 +247,7 @@ Once a new stream with the protocol ID for the request type has been negotiated, The requester MUST close the write side of the stream once it finishes writing the request message. At this point, the stream will be half-closed. -The requester MUST wait a maximum of `TTFB_TIMEOUT` for the first response byte to arrive (time to first byte—or TTFB—timeout). On that happening, the requester allows a further `RESP_TIMEOUT` to receive the full response. For responses consisting of potentially many `response_chunk`s (an SSZ-list) the requester SHOULD read from the stream until either; a) An error result is received in one of the chunks, b) The responder closes the stream, c) More than `REQ_RESP_MAX_SIZE` bytes have been read for a single `response_chunk` payload or d) More than the maximum number of requested chunks are read. For requests consisting of a single `response_chunk` and a length-prefix, the requester should read the exact number of bytes defined by the length-prefix before closing the stream. +The requester MUST wait a maximum of `TTFB_TIMEOUT` for the first response byte to arrive (time to first byte—or TTFB—timeout). On that happening, the requester allows a further `RESP_TIMEOUT` for each subsequent `response_chunk` received. For responses consisting of potentially many `response_chunk`s (an SSZ-list) the requester SHOULD read from the stream until either; a) An error result is received in one of the chunks, b) The responder closes the stream, c) More than `REQ_RESP_MAX_SIZE` bytes have been read for a single `response_chunk` payload or d) More than the maximum number of requested chunks are read. For requests consisting of a single `response_chunk` and a length-prefix, the requester should read the exact number of bytes defined by the length-prefix before closing the stream. If any of these timeouts fire, the requester SHOULD reset the stream and deem the req/resp operation to have failed. @@ -404,8 +402,6 @@ Requests count beacon blocks from the peer starting from `start_slot` on the cha The request MUST be encoded as an SSZ-container. -The `count` MUST not exceed MAX_REQUESTED_BLOCKS. - The response MUST consist of at least one `response_chunk` and MAY consist of many. Each _successful_ `response_chunk` MUST contain a single `BeaconBlock` payload. `BeaconBlocksByRange` is primarily used to sync historical blocks. @@ -442,8 +438,6 @@ Requests blocks by their block roots. The response is a list of `BeaconBlock` wh `BeaconBlocksByRoot` is primarily used to recover recent blocks (e.g. when receiving a block or attestation whose parent is unknown). -The length of `block_roots` MUST not exceed MAX_REQUESTED_BLOCKS. - The request MUST be encoded as an SSZ-field. The response MUST consist of at least one `response_chunk` and MAY consist of many. Each _successful_ `response_chunk` MUST contain a single `BeaconBlock` payload. From 8bb9354e65590c1e7f7df9d90d985d5e1c31e06b Mon Sep 17 00:00:00 2001 From: Age Manning Date: Fri, 13 Sep 2019 02:20:59 +1000 Subject: [PATCH 09/10] Renames REQ_RESP_MAX_SIZE to MAX_CHUNK_SIZE --- specs/networking/p2p-interface.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/specs/networking/p2p-interface.md b/specs/networking/p2p-interface.md index e33026be3..46663fff1 100644 --- a/specs/networking/p2p-interface.md +++ b/specs/networking/p2p-interface.md @@ -114,7 +114,7 @@ This section outlines constants that are used in this spec. | Name | Value | Description | |---|---|---| | `GOSSIP_MAX_SIZE` | `2**20` (= 1048576, 1 MiB) | The maximum allowed size of uncompressed gossip messages. | -| `REQ_RESP_MAX_SIZE` | `2**20` (1048576, 1 MiB) | The maximum allowed size of uncompressed req/resp chunked responses. | +| `MAX_CHUNK_SIZE` | `2**20` (1048576, 1 MiB) | The maximum allowed size of uncompressed req/resp chunked responses. | | `SHARD_SUBNET_COUNT` | `TODO` | The number of shard subnets used in the gossipsub protocol. | | `TTFB_TIMEOUT` | `5s` | The maximum time to wait for first byte of request response (time-to-first-byte). | | `RESP_TIMEOUT` | `10s` | The maximum time for complete response transfer. | @@ -237,9 +237,9 @@ result ::= “0” | “1” | “2” | [“128” ... ”255”] The encoding-dependent header may carry metadata or assertions such as the encoded payload length, for integrity and attack proofing purposes. Because req/resp streams are single-use and stream closures implicitly delimit the boundaries, it is not strictly necessary to length-prefix payloads; however, certain encodings like SSZ do, for added security. -A `response` is formed by one or more `response_chunk`s. The exact request determines whether a response consists of a single `response_chunk` or possibly many. Responses that consist of a single SSZ-list (such as `BlocksByRange` and `BlocksByRoot`) send each list item as a `response_chunk`. All other response types (non-Lists) send a single `response_chunk`. The encoded-payload of a `response_chunk` has a maximum uncompressed byte size of `REQ_RESP_MAX_SIZE`. +A `response` is formed by one or more `response_chunk`s. The exact request determines whether a response consists of a single `response_chunk` or possibly many. Responses that consist of a single SSZ-list (such as `BlocksByRange` and `BlocksByRoot`) send each list item as a `response_chunk`. All other response types (non-Lists) send a single `response_chunk`. The encoded-payload of a `response_chunk` has a maximum uncompressed byte size of `MAX_CHUNK_SIZE`. -Clients MUST ensure the each encoded payload of a `response_chunk` is less than or equal to `REQ_RESP_MAX_SIZE`; if not, they SHOULD reset the stream immediately. Clients tracking peer reputation MAY decrement the score of the misbehaving peer under this circumstance. +Clients MUST ensure the each encoded payload of a `response_chunk` is less than or equal to `MAX_CHUNK_SIZE`; if not, they SHOULD reset the stream immediately. Clients tracking peer reputation MAY decrement the score of the misbehaving peer under this circumstance. #### Requesting side @@ -247,7 +247,7 @@ Once a new stream with the protocol ID for the request type has been negotiated, The requester MUST close the write side of the stream once it finishes writing the request message. At this point, the stream will be half-closed. -The requester MUST wait a maximum of `TTFB_TIMEOUT` for the first response byte to arrive (time to first byte—or TTFB—timeout). On that happening, the requester allows a further `RESP_TIMEOUT` for each subsequent `response_chunk` received. For responses consisting of potentially many `response_chunk`s (an SSZ-list) the requester SHOULD read from the stream until either; a) An error result is received in one of the chunks, b) The responder closes the stream, c) More than `REQ_RESP_MAX_SIZE` bytes have been read for a single `response_chunk` payload or d) More than the maximum number of requested chunks are read. For requests consisting of a single `response_chunk` and a length-prefix, the requester should read the exact number of bytes defined by the length-prefix before closing the stream. +The requester MUST wait a maximum of `TTFB_TIMEOUT` for the first response byte to arrive (time to first byte—or TTFB—timeout). On that happening, the requester allows a further `RESP_TIMEOUT` for each subsequent `response_chunk` received. For responses consisting of potentially many `response_chunk`s (an SSZ-list) the requester SHOULD read from the stream until either; a) An error result is received in one of the chunks, b) The responder closes the stream, c) More than `MAX_CHUNK_SIZE` bytes have been read for a single `response_chunk` payload or d) More than the maximum number of requested chunks are read. For requests consisting of a single `response_chunk` and a length-prefix, the requester should read the exact number of bytes defined by the length-prefix before closing the stream. If any of these timeouts fire, the requester SHOULD reset the stream and deem the req/resp operation to have failed. @@ -267,7 +267,7 @@ If steps (1), (2), or (3) fail due to invalid, malformed, or inconsistent data, The entire request should be read in no more than `RESP_TIMEOUT`. Upon a timeout, the responder SHOULD reset the stream. -The responder SHOULD send a `response_chunk` promptly. Chunks start with a **single-byte** response code which determines the contents of the `response_chunk` (`result` particle in the BNF grammar above). +The responder SHOULD send a `response_chunk` promptly. Chunks start with a **single-byte** response code which determines the contents of the `response_chunk` (`result` particle in the BNF grammar above). For multiple chunks, only the last chunk is allowed to have a non-zero error code (i.e. The chunk stream is terminated once an error occurs). The response code can have one of the following values, encoded as a single unsigned byte: @@ -708,7 +708,7 @@ Disadvantages include: * Harder to stream as length must be known up-front * Additional code path required to verify length -In some protocols, adding a length prefix serves as a form of DoS protection against very long messages, allowing the client to abort if an overlong message is about to be sent. In this protocol, we are globally limiting message sizes using `REQ_RESP_MAX_SIZE`, thus the length prefix does not afford any additional protection. +In some protocols, adding a length prefix serves as a form of DoS protection against very long messages, allowing the client to abort if an overlong message is about to be sent. In this protocol, we are globally limiting message sizes using `MAX_CHUNK_SIZE`, thus the length prefix does not afford any additional protection. [Protobuf varint](https://developers.google.com/protocol-buffers/docs/encoding#varints) is an efficient technique to encode variable-length ints. Instead of reserving a fixed-size field of as many bytes as necessary to convey the maximum possible value, this field is elastic in exchange for 1-bit overhead per byte. From 4b2596dbad4a68bd09e4b844637102eb347c0ec3 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Mon, 16 Sep 2019 08:59:04 -0500 Subject: [PATCH 10/10] ensure BeaconBlocksByRoot requests are lists rather than containers --- specs/networking/p2p-interface.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/specs/networking/p2p-interface.md b/specs/networking/p2p-interface.md index 46663fff1..4f71ed6d9 100644 --- a/specs/networking/p2p-interface.md +++ b/specs/networking/p2p-interface.md @@ -422,7 +422,7 @@ Request Content: ``` ( - block_roots: []HashTreeRoot + []HashTreeRoot ) ``` @@ -430,7 +430,7 @@ Response Content: ``` ( - blocks: []BeaconBlock + []BeaconBlock ) ```