From 52b45ab9de7a08c5a218a44d417aa7ba31e213ea Mon Sep 17 00:00:00 2001 From: Jacek Sieka Date: Mon, 17 Feb 2020 10:03:32 +0100 Subject: [PATCH 1/8] Add fork version to topic Gossipsub peers are separate from the ETH2 RPC protocol, and thus cannot rely on the application-level `Status` negotiation to establish if they're on the same network. Segregating gossipsub topics by fork version decouples RPC from gossip further and allows peers to more easily listen only to the traffic of the network they're interested in, without further negotiation. --- specs/phase0/p2p-interface.md | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/specs/phase0/p2p-interface.md b/specs/phase0/p2p-interface.md index 7ad8759d3..40c9055ac 100644 --- a/specs/phase0/p2p-interface.md +++ b/specs/phase0/p2p-interface.md @@ -216,7 +216,17 @@ The following gossipsub [parameters](https://github.com/libp2p/specs/tree/master ### Topics and messages -Topics are plain UTF-8 strings and are encoded on the wire as determined by protobuf (gossipsub messages are enveloped in protobuf messages). Topic strings have form: `/eth2/TopicName/TopicEncoding`. This defines both the type of data being sent on the topic and how the data field of the message is encoded. +Topics are plain UTF-8 strings and are encoded on the wire as determined by protobuf (gossipsub messages are enveloped in protobuf messages). Topic strings have form: `/eth2/ForkVersion/Name/Encoding`. This defines both the type of data being sent on the topic and how the data field of the message is encoded. + +- `ForkVersion` - the hex-encoded bytes from `state.fork.current_version` of the head state of the client, as also seen in `Status.head_fork_version`. +- `Name` - see table below +- `Encoding` - the encoding strategy describes a specific representation of bytes that will be transmitted over the wire. See the [Encodings](#Encoding-strategies) section for further details. + +The fork version is hex-encoded using the following scheme: +```python + ForkVersion = ''.join('{:02x}'.format(x) for x in state.fork.current_version) +``` +For example, the fork version `[0, 1, 2, 10]` will be encoded as `0001020a`. Each gossipsub [message](https://github.com/libp2p/go-libp2p-pubsub/blob/master/pb/rpc.proto#L17-L24) has a maximum size of `GOSSIP_MAX_SIZE`. Clients MUST reject (fail validation) messages that are over this size limit. Likewise, clients MUST NOT emit or propagate messages larger than this limit. @@ -229,7 +239,7 @@ where `base64` is the [URL-safe base64 alphabet](https://tools.ietf.org/html/rfc The payload is carried in the `data` field of a gossipsub message, and varies depending on the topic: -| Topic | Message Type | +| Name | Message Type | |------------------------------------------------|-------------------------| | beacon_block | SignedBeaconBlock | | beacon_aggregate_and_proof | SignedAggregateAndProof | From 61f661b3ecbc3e2895c4400a9f1362ae43fb6441 Mon Sep 17 00:00:00 2001 From: Jacek Sieka Date: Thu, 20 Feb 2020 08:23:09 +0100 Subject: [PATCH 2/8] Update specs/phase0/p2p-interface.md Co-Authored-By: Danny Ryan --- specs/phase0/p2p-interface.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/phase0/p2p-interface.md b/specs/phase0/p2p-interface.md index 40c9055ac..c5607c0c5 100644 --- a/specs/phase0/p2p-interface.md +++ b/specs/phase0/p2p-interface.md @@ -226,7 +226,7 @@ The fork version is hex-encoded using the following scheme: ```python ForkVersion = ''.join('{:02x}'.format(x) for x in state.fork.current_version) ``` -For example, the fork version `[0, 1, 2, 10]` will be encoded as `0001020a`. +For example, the fork version `Version('0x0001020a')` will be encoded as `0001020a`. Each gossipsub [message](https://github.com/libp2p/go-libp2p-pubsub/blob/master/pb/rpc.proto#L17-L24) has a maximum size of `GOSSIP_MAX_SIZE`. Clients MUST reject (fail validation) messages that are over this size limit. Likewise, clients MUST NOT emit or propagate messages larger than this limit. From 38323d8186793ba1cbbfa6c8000a4a4b60aa97b9 Mon Sep 17 00:00:00 2001 From: Jacek Sieka Date: Sun, 1 Mar 2020 17:17:29 +0100 Subject: [PATCH 3/8] Add faq --- specs/phase0/p2p-interface.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/specs/phase0/p2p-interface.md b/specs/phase0/p2p-interface.md index c5607c0c5..85d9a1e9f 100644 --- a/specs/phase0/p2p-interface.md +++ b/specs/phase0/p2p-interface.md @@ -763,9 +763,9 @@ For future extensibility with almost zero overhead now (besides the extra bytes ### How do we upgrade gossip channels (e.g. changes in encoding, compression)? -Changing gossipsub/broadcasts requires a coordinated upgrade where all clients start publishing to the new topic together, for example during a hard fork. +Changing gossipsub/broadcasts requires a coordinated upgrade where all clients start publishing to the new topic together, during a hard fork. -One can envision a two-phase deployment as well where clients start listening to the new topic in the first phase then start publishing some time later, letting the traffic naturally move over to the new topic. +When a node is preparing for upcoming tasks (e.g. validator duty lookahead) on a gossipsub topic, the node should join the topic of the future epoch in which the task is to occur in addition to listening to the topics for the current epoch. ### Why must all clients use the same gossip topic instead of one negotiated between each peer pair? From 415544bf038315c82d40035c79a3872c38efec75 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Tue, 10 Mar 2020 14:41:33 -0600 Subject: [PATCH 4/8] modify gossip topics to use ForkDigest --- specs/phase0/beacon-chain.md | 26 +++++++++++++++++++++- specs/phase0/p2p-interface.md | 42 +++++++++++------------------------ 2 files changed, 38 insertions(+), 30 deletions(-) diff --git a/specs/phase0/beacon-chain.md b/specs/phase0/beacon-chain.md index ce29ceda3..489e9b55f 100644 --- a/specs/phase0/beacon-chain.md +++ b/specs/phase0/beacon-chain.md @@ -24,6 +24,7 @@ - [Containers](#containers) - [Misc dependencies](#misc-dependencies) - [`Fork`](#fork) + - [`ForkData`](#forkdata) - [`Checkpoint`](#checkpoint) - [`Validator`](#validator) - [`AttestationData`](#attestationdata) @@ -75,6 +76,7 @@ - [`compute_epoch_at_slot`](#compute_epoch_at_slot) - [`compute_start_slot_at_epoch`](#compute_start_slot_at_epoch) - [`compute_activation_exit_epoch`](#compute_activation_exit_epoch) + - [`compute_fork_data_root`](#compute_fork_data_root) - [`compute_domain`](#compute_domain) - [`compute_signing_root`](#compute_signing_root) - [Beacon state accessors](#beacon-state-accessors) @@ -286,6 +288,14 @@ class Fork(Container): epoch: Epoch # Epoch of latest fork ``` +#### `ForkData` + +```python +class ForkData(Container): + current_version: Version + genesis_root: Root +``` + #### `Checkpoint` ```python @@ -794,6 +804,19 @@ def compute_activation_exit_epoch(epoch: Epoch) -> Epoch: return Epoch(epoch + 1 + MAX_SEED_LOOKAHEAD) ``` +#### `compute_fork_data_root` + +```python +def compute_fork_data_root(current_version: Version, genesis_validators_root: Root) -> Root: + """ + Return the fork digest for the ``current_fork_version`` and ``genesis_validators_root`` + """ + return hash_tree_root(ForkData( + current_version=current_version, + genesis_validators_root=genesis_validators_root, + )) +``` + #### `compute_domain` ```python @@ -805,7 +828,8 @@ def compute_domain(domain_type: DomainType, fork_version: Optional[Version]=None fork_version = GENESIS_FORK_VERSION if genesis_root is None: genesis_root = Root() # all bytes zero by default - return Domain(domain_type + fork_version + genesis_root[:24]) + fork_data_root = compute_fork_data_root(fork_version, genesis_root) + return Domain(domain_type + fork_data_root[:28]) ``` #### `compute_signing_root` diff --git a/specs/phase0/p2p-interface.md b/specs/phase0/p2p-interface.md index 7e7d38341..8b6423277 100644 --- a/specs/phase0/p2p-interface.md +++ b/specs/phase0/p2p-interface.md @@ -219,18 +219,14 @@ The following gossipsub [parameters](https://github.com/libp2p/specs/tree/master ### Topics and messages -Topics are plain UTF-8 strings and are encoded on the wire as determined by protobuf (gossipsub messages are enveloped in protobuf messages). Topic strings have form: `/eth2/ForkVersion/Name/Encoding`. This defines both the type of data being sent on the topic and how the data field of the message is encoded. +Topics are plain UTF-8 strings and are encoded on the wire as determined by protobuf (gossipsub messages are enveloped in protobuf messages). Topic strings have form: `/eth2/ForkDigest/Name/Encoding`. This defines both the type of data being sent on the topic and how the data field of the message is encoded. -- `ForkVersion` - the hex-encoded bytes from `state.fork.current_version` of the head state of the client, as also seen in `Status.head_fork_version`. +- `ForkDigest` - the hex-encoded bytes of `ForkDigest(compute_fork_data_root(current_fork_version, genesis_validators_root)[:4])` where + - `current_fork_version` is the fork version of the epoch of the message to be sent on the topic + - `genesis_validators_root` is the static `Root` found in `state.genesis_validators_root` - `Name` - see table below - `Encoding` - the encoding strategy describes a specific representation of bytes that will be transmitted over the wire. See the [Encodings](#Encoding-strategies) section for further details. -The fork version is hex-encoded using the following scheme: -```python - ForkVersion = ''.join('{:02x}'.format(x) for x in state.fork.current_version) -``` -For example, the fork version `Version('0x0001020a')` will be encoded as `0001020a`. - Each gossipsub [message](https://github.com/libp2p/go-libp2p-pubsub/blob/master/pb/rpc.proto#L17-L24) has a maximum size of `GOSSIP_MAX_SIZE`. Clients MUST reject (fail validation) messages that are over this size limit. Likewise, clients MUST NOT emit or propagate messages larger than this limit. The `message-id` of a gossipsub message MUST be: @@ -260,7 +256,7 @@ When processing incoming gossip, clients MAY descore or disconnect peers who fai #### Global topics -There are two primary global topics used to propagate beacon blocks and aggregate attestations to all nodes on the network. Their `TopicName`s are: +There are two primary global topics used to propagate beacon blocks and aggregate attestations to all nodes on the network. Their `Name`s are: - `beacon_block` - This topic is used solely for propagating new signed beacon blocks to all nodes on the networks. Signed blocks are sent in their entirety. The following validations MUST pass before forwarding the `signed_beacon_block` on the network - The block is not from a future slot (with a `MAXIMUM_GOSSIP_CLOCK_DISPARITY` allowance) -- i.e. validate that `signed_beacon_block.message.slot <= current_slot` (a client MAY queue future blocks for processing at the appropriate slot). @@ -278,7 +274,7 @@ There are two primary global topics used to propagate beacon blocks and aggregat - The aggregator signature, `signed_aggregate_and_proof.signature`, is valid. - The signature of `aggregate` is valid. -Additional global topics are used to propagate lower frequency validator messages. Their `TopicName`s are: +Additional global topics are used to propagate lower frequency validator messages. Their `Name`s are: - `voluntary_exit` - This topic is used solely for propagating signed voluntary validator exits to proposers on the network. Signed voluntary exits are sent in their entirety. The following validations MUST pass before forwarding the `signed_voluntary_exit` on to the network - The voluntary exit is the first valid voluntary exit received for the validator with index `signed_voluntary_exit.message.validator_index`. @@ -293,7 +289,7 @@ Additional global topics are used to propagate lower frequency validator message #### Attestation subnets -Attestation subnets are used to propagate unaggregated attestations to subsections of the network. Their `TopicName`s are: +Attestation subnets are used to propagate unaggregated attestations to subsections of the network. Their `Name`s are: - `committee_index{subnet_id}_beacon_attestation` - These topics are used to propagate unaggregated attestations to the subnet `subnet_id` (typically beacon and persistent committees) to be aggregated before being gossiped to `beacon_aggregate_and_proof`. The following validations MUST pass before forwarding the `attestation` on the subnet. - The attestation's committee index (`attestation.data.index`) is for the correct subnet. @@ -654,25 +650,11 @@ ENRs MUST carry a generic `eth2` key with an 16-byte value of the node's current |:-------------|:--------------------| | `eth2` | SSZ `ENRForkID` | -First we define `current_fork_data` as the following SSZ encoded object - -``` -( - current_fork_version: Version - genesis_validators_root: Root -) -``` - -where - -* `current_fork_version` is the fork version at the node's current epoch defined by the wall-clock time (not necessarily the epoch to which the node is sync) -* `genesis_validators_root` is the static `Root` found in `state.genesis_validators_root` - Specifically, the value of the `eth2` key MUST be the following SSZ encoded object (`ENRForkID`) ``` ( - current_fork_digest: ForkDigest + fork_digest: ForkDigest next_fork_version: Version next_fork_epoch: Epoch ) @@ -680,13 +662,15 @@ Specifically, the value of the `eth2` key MUST be the following SSZ encoded obje where the fields of `ENRForkID` are defined as -* `current_fork_digest` is `ForkDigest(hash_tree_root(current_fork_data)[:4])` +* `fork_digest` is `ForkDigest(compute_fork_data_root(current_fork_version, genesis_validators_root)[:4])` where + * `current_fork_version` is the fork version at the node's current epoch defined by the wall-clock time (not necessarily the epoch to which the node is sync) + * `genesis_validators_root` is the static `Root` found in `state.genesis_validators_root` * `next_fork_version` is the fork version corresponding to the next planned hard fork at a future epoch. If no future fork is planned, set `next_fork_version = current_fork_version` to signal this fact * `next_fork_epoch` is the epoch at which the next fork is planned and the `current_fork_version` will be updated. If no future fork is planned, set `next_fork_epoch = FAR_FUTURE_EPOCH` to signal this fact -Clients SHOULD connect to peers with `current_fork_digest`, `next_fork_version`, and `next_fork_epoch` that match local values. +Clients SHOULD connect to peers with `fork_digest`, `next_fork_version`, and `next_fork_epoch` that match local values. -Clients MAY connect to peers with the same `current_fork_version` but a different `next_fork_version`/`next_fork_epoch`. Unless `ENRForkID` is manually updated to matching prior to the earlier `next_fork_epoch` of the two clients, these type of connecting clients will be unable to successfully interact starting at the earlier `next_fork_epoch`. +Clients MAY connect to peers with the same `fork_digest` but a different `next_fork_version`/`next_fork_epoch`. Unless `ENRForkID` is manually updated to matching prior to the earlier `next_fork_epoch` of the two clients, these type of connecting clients will be unable to successfully interact starting at the earlier `next_fork_epoch`. ##### General capabilities From fccd3ab1ce13bc54f35bbe760637176d8e71f973 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Tue, 10 Mar 2020 15:04:44 -0600 Subject: [PATCH 5/8] clarify hex-encoded bytes string representation for fork digest in gossip topic Co-Authored-By: Diederik Loerakker --- specs/phase0/p2p-interface.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/phase0/p2p-interface.md b/specs/phase0/p2p-interface.md index 8b6423277..580e42183 100644 --- a/specs/phase0/p2p-interface.md +++ b/specs/phase0/p2p-interface.md @@ -221,7 +221,7 @@ The following gossipsub [parameters](https://github.com/libp2p/specs/tree/master Topics are plain UTF-8 strings and are encoded on the wire as determined by protobuf (gossipsub messages are enveloped in protobuf messages). Topic strings have form: `/eth2/ForkDigest/Name/Encoding`. This defines both the type of data being sent on the topic and how the data field of the message is encoded. -- `ForkDigest` - the hex-encoded bytes of `ForkDigest(compute_fork_data_root(current_fork_version, genesis_validators_root)[:4])` where +- `ForkDigest` - the lowercase hex-encoded (no "0x" prefix) bytes of `ForkDigest(compute_fork_data_root(current_fork_version, genesis_validators_root)[:4])` where - `current_fork_version` is the fork version of the epoch of the message to be sent on the topic - `genesis_validators_root` is the static `Root` found in `state.genesis_validators_root` - `Name` - see table below From baee6731241b21244ec5f9ca44567b96108cfe0d Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Tue, 10 Mar 2020 15:09:15 -0600 Subject: [PATCH 6/8] add note about preparing for subnet backbone for forks --- specs/phase0/validator.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/specs/phase0/validator.md b/specs/phase0/validator.md index 009816c2e..b74a57486 100644 --- a/specs/phase0/validator.md +++ b/specs/phase0/validator.md @@ -519,6 +519,8 @@ Because Phase 0 does not have shards and thus does not have Shard Committees, th * Maintain advertisement of the randomly selected subnets in their node's ENR `attnets` entry by setting the randomly selected `subnet_id` bits to `True` (e.g. `ENR["attnets"][subnet_id] = True`) for all persistent attestation subnets * Set the lifetime of each random subscription to a random number of epochs between `EPOCHS_PER_RANDOM_SUBNET_SUBSCRIPTION` and `2 * EPOCHS_PER_RANDOM_SUBNET_SUBSCRIPTION]`. At the end of life for a subscription, select a new random subnet, update subnet subscriptions, and publish an updated ENR +*Note*: When preparing for a hard fork, a validator must select and subscribe to random subnets of the future fork versioning at least `EPOCHS_PER_RANDOM_SUBNET_SUBSCRIPTION` epochs in advance of the fork. These new subnets for the fork are maintained in addition to those for the current fork until the fork occurs. After the fork occurs, let the subnets from the previous fork reach end of life with no replacements. + ## How to avoid slashing "Slashing" is the burning of some amount of validator funds and immediate ejection from the active validator set. In Phase 0, there are two ways in which funds can be slashed: [proposer slashing](#proposer-slashing) and [attester slashing](#attester-slashing). Although being slashed has serious repercussions, it is simple enough to avoid being slashed all together by remaining _consistent_ with respect to the messages a validator has previously signed. From 0881e21dc5461af1d04b5bea6b2729c03791b72a Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Wed, 11 Mar 2020 11:51:31 -0600 Subject: [PATCH 7/8] cleanup gossip topic fork digest based on PR feedback --- setup.py | 2 +- specs/phase0/beacon-chain.md | 26 ++++++++++++++++++++------ specs/phase0/p2p-interface.md | 34 ++++++++++++++++++---------------- 3 files changed, 39 insertions(+), 23 deletions(-) diff --git a/setup.py b/setup.py index 6dd4de861..5edff0164 100644 --- a/setup.py +++ b/setup.py @@ -106,7 +106,7 @@ SSZObject = TypeVar('SSZObject', bound=View) PHASE1_IMPORTS = '''from eth2spec.phase0 import spec as phase0 from eth2spec.config.config_util import apply_constants_config from typing import ( - Any, Dict, Set, Sequence, NewType, Tuple, TypeVar, Callable, Optional + Any, Dict, Set, Sequence, NewType, Tuple, TypeVar, Callable ) from dataclasses import ( diff --git a/specs/phase0/beacon-chain.md b/specs/phase0/beacon-chain.md index 489e9b55f..b14da493b 100644 --- a/specs/phase0/beacon-chain.md +++ b/specs/phase0/beacon-chain.md @@ -77,6 +77,7 @@ - [`compute_start_slot_at_epoch`](#compute_start_slot_at_epoch) - [`compute_activation_exit_epoch`](#compute_activation_exit_epoch) - [`compute_fork_data_root`](#compute_fork_data_root) + - [`compute_fork_digest`](#compute_fork_digest) - [`compute_domain`](#compute_domain) - [`compute_signing_root`](#compute_signing_root) - [Beacon state accessors](#beacon-state-accessors) @@ -293,7 +294,7 @@ class Fork(Container): ```python class ForkData(Container): current_version: Version - genesis_root: Root + genesis_validators_root: Root ``` #### `Checkpoint` @@ -809,7 +810,8 @@ def compute_activation_exit_epoch(epoch: Epoch) -> Epoch: ```python def compute_fork_data_root(current_version: Version, genesis_validators_root: Root) -> Root: """ - Return the fork digest for the ``current_fork_version`` and ``genesis_validators_root`` + Return the 32-byte fork data root for the ``current_version`` and ``genesis_validators_root``. + This is used primarily in signature domains to avoid collisions across forks/chains. """ return hash_tree_root(ForkData( current_version=current_version, @@ -817,18 +819,30 @@ def compute_fork_data_root(current_version: Version, genesis_validators_root: Ro )) ``` +#### `compute_fork_digest` + +```python +def compute_fork_digest(current_version: Version, genesis_validators_root: Root) -> Root: + """ + Return the 4-byte fork digest for the ``current_version`` and ``genesis_validators_root``. + This is a digest primarily used for domain separation on the p2p layer. + 4-bytes suffices for practical separation of forks/chains. + """ + return ForkDigest(compute_fork_data_root(current_version, genesis_validators_root)[:4]) +``` + #### `compute_domain` ```python -def compute_domain(domain_type: DomainType, fork_version: Optional[Version]=None, genesis_root: Root=None) -> Domain: +def compute_domain(domain_type: DomainType, fork_version: Version=None, genesis_validators_root: Root=None) -> Domain: """ Return the domain for the ``domain_type`` and ``fork_version``. """ if fork_version is None: fork_version = GENESIS_FORK_VERSION - if genesis_root is None: - genesis_root = Root() # all bytes zero by default - fork_data_root = compute_fork_data_root(fork_version, genesis_root) + if genesis_validators_root is None: + genesis_validators_root = Root() # all bytes zero by default + fork_data_root = compute_fork_data_root(fork_version, genesis_validators_root) return Domain(domain_type + fork_data_root[:28]) ``` diff --git a/specs/phase0/p2p-interface.md b/specs/phase0/p2p-interface.md index 580e42183..5a4464f5c 100644 --- a/specs/phase0/p2p-interface.md +++ b/specs/phase0/p2p-interface.md @@ -219,9 +219,9 @@ The following gossipsub [parameters](https://github.com/libp2p/specs/tree/master ### Topics and messages -Topics are plain UTF-8 strings and are encoded on the wire as determined by protobuf (gossipsub messages are enveloped in protobuf messages). Topic strings have form: `/eth2/ForkDigest/Name/Encoding`. This defines both the type of data being sent on the topic and how the data field of the message is encoded. +Topics are plain UTF-8 strings and are encoded on the wire as determined by protobuf (gossipsub messages are enveloped in protobuf messages). Topic strings have form: `/eth2/ForkDigestValue/Name/Encoding`. This defines both the type of data being sent on the topic and how the data field of the message is encoded. -- `ForkDigest` - the lowercase hex-encoded (no "0x" prefix) bytes of `ForkDigest(compute_fork_data_root(current_fork_version, genesis_validators_root)[:4])` where +- `ForkDigestValue` - the lowercase hex-encoded (no "0x" prefix) bytes of `compute_fork_digest(current_fork_version, genesis_validators_root)` where - `current_fork_version` is the fork version of the epoch of the message to be sent on the topic - `genesis_validators_root` is the static `Root` found in `state.genesis_validators_root` - `Name` - see table below @@ -423,7 +423,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/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 `Bytes32`'s. +- `ssz`: the contents are [SSZ-encoded](../../ssz/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 `Root`'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) @@ -475,16 +475,18 @@ constituents individually as `response_chunk`s. For example, the Request, Response Content: ``` ( - head_fork_version: Bytes4 - finalized_root: Bytes32 - finalized_epoch: uint64 - head_root: Bytes32 - head_slot: uint64 + fork_digest: ForkDigest + finalized_root: Root + finalized_epoch: Epoch + head_root: Root + head_slot: Slot ) ``` The fields are, as seen by the client at the time of sending the message: -- `head_fork_version`: The beacon_state `Fork` version. +- `fork_digest`: The node's `ForkDigest` (`compute_fork_digest(current_fork_version, genesis_validators_root)`) where + - `current_fork_version` is the fork version at the node's current epoch defined by the wall-clock time (not necessarily the epoch to which the node is sync) + - `genesis_validators_root` is the static `Root` found in `state.genesis_validators_root` - `finalized_root`: `state.finalized_checkpoint.root` for the state corresponding to the head block. - `finalized_epoch`: `state.finalized_checkpoint.epoch` for the state corresponding to the head block. - `head_root`: The hash_tree_root root of the current head block. @@ -498,7 +500,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` 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. +1. If `fork_digest` does not match the node's local `fork_digest`, since the client’s chain is on another fork. 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. @@ -536,7 +538,7 @@ The response MUST consist of a single `response_chunk`. Request Content: ``` ( - start_slot: uint64 + start_slot: Slot count: uint64 step: uint64 ) @@ -575,7 +577,7 @@ Request Content: ``` ( - []Bytes32 + []Root ) ``` @@ -662,7 +664,7 @@ Specifically, the value of the `eth2` key MUST be the following SSZ encoded obje where the fields of `ENRForkID` are defined as -* `fork_digest` is `ForkDigest(compute_fork_data_root(current_fork_version, genesis_validators_root)[:4])` where +* `fork_digest` is `compute_fork_digest(current_fork_version, genesis_validators_root)` where * `current_fork_version` is the fork version at the node's current epoch defined by the wall-clock time (not necessarily the epoch to which the node is sync) * `genesis_validators_root` is the static `Root` found in `state.genesis_validators_root` * `next_fork_version` is the fork version corresponding to the next planned hard fork at a future epoch. If no future fork is planned, set `next_fork_version = current_fork_version` to signal this fact @@ -670,7 +672,7 @@ where the fields of `ENRForkID` are defined as Clients SHOULD connect to peers with `fork_digest`, `next_fork_version`, and `next_fork_epoch` that match local values. -Clients MAY connect to peers with the same `fork_digest` but a different `next_fork_version`/`next_fork_epoch`. Unless `ENRForkID` is manually updated to matching prior to the earlier `next_fork_epoch` of the two clients, these type of connecting clients will be unable to successfully interact starting at the earlier `next_fork_epoch`. +Clients MAY connect to peers with the same `fork_digest` but a different `next_fork_version`/`next_fork_epoch`. Unless `ENRForkID` is manually updated to matching prior to the earlier `next_fork_epoch` of the two clients, these connecting clients will be unable to successfully interact starting at the earlier `next_fork_epoch`. ##### General capabilities @@ -897,9 +899,9 @@ Although this method will be sufficient for early phases of Eth2, we aim to use Fork versions are to be manually updated (likely via incrementing) at each hard fork. This is to provide native domain separation for signatures as well as to aid in usefulness for identitying peers (via ENRs) and versioning network protocols (e.g. using fork version to naturally version gossipsub topics). -`BeaconState.genesis_validators_root` is mixed into signature and ENR fork domains to aid in the ease of domain separation between chains. This allows fork versions to safely be reused across chains except for the case of contentious forks using the same genesis. In these cases, extra care should be taken to isolate fork versions (e.g. flip a high order bit in all future versions of one of the chains). +`BeaconState.genesis_validators_root` is mixed into signature and ENR fork domains (`ForkDigest`) to aid in the ease of domain separation between chains. This allows fork versions to safely be reused across chains except for the case of contentious forks using the same genesis. In these cases, extra care should be taken to isolate fork versions (e.g. flip a high order bit in all future versions of one of the chains). -A node locally stores all previous and future planned fork versions along with the each fork epoch. This allows for handling sync starting from past forks/epochs and for connections to safely be made with peers syncing from past forks/epochs. +A node locally stores all previous and future planned fork versions along with the each fork epoch. This allows for handling sync and processing messages starting from past forks/epochs. ## Req/Resp From 4bcdf91e8b1cf0e36d26b30ddf6d52144b9eb2f1 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Wed, 11 Mar 2020 13:24:30 -0600 Subject: [PATCH 8/8] Apply suggestions from code review PR feedback Co-Authored-By: Hsiao-Wei Wang --- specs/phase0/beacon-chain.md | 2 +- specs/phase0/validator.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/specs/phase0/beacon-chain.md b/specs/phase0/beacon-chain.md index b14da493b..5b5a13f93 100644 --- a/specs/phase0/beacon-chain.md +++ b/specs/phase0/beacon-chain.md @@ -822,7 +822,7 @@ def compute_fork_data_root(current_version: Version, genesis_validators_root: Ro #### `compute_fork_digest` ```python -def compute_fork_digest(current_version: Version, genesis_validators_root: Root) -> Root: +def compute_fork_digest(current_version: Version, genesis_validators_root: Root) -> ForkDigest: """ Return the 4-byte fork digest for the ``current_version`` and ``genesis_validators_root``. This is a digest primarily used for domain separation on the p2p layer. diff --git a/specs/phase0/validator.md b/specs/phase0/validator.md index b74a57486..d082ed04e 100644 --- a/specs/phase0/validator.md +++ b/specs/phase0/validator.md @@ -519,7 +519,7 @@ Because Phase 0 does not have shards and thus does not have Shard Committees, th * Maintain advertisement of the randomly selected subnets in their node's ENR `attnets` entry by setting the randomly selected `subnet_id` bits to `True` (e.g. `ENR["attnets"][subnet_id] = True`) for all persistent attestation subnets * Set the lifetime of each random subscription to a random number of epochs between `EPOCHS_PER_RANDOM_SUBNET_SUBSCRIPTION` and `2 * EPOCHS_PER_RANDOM_SUBNET_SUBSCRIPTION]`. At the end of life for a subscription, select a new random subnet, update subnet subscriptions, and publish an updated ENR -*Note*: When preparing for a hard fork, a validator must select and subscribe to random subnets of the future fork versioning at least `EPOCHS_PER_RANDOM_SUBNET_SUBSCRIPTION` epochs in advance of the fork. These new subnets for the fork are maintained in addition to those for the current fork until the fork occurs. After the fork occurs, let the subnets from the previous fork reach end of life with no replacements. +*Note*: When preparing for a hard fork, a validator must select and subscribe to random subnets of the future fork versioning at least `EPOCHS_PER_RANDOM_SUBNET_SUBSCRIPTION` epochs in advance of the fork. These new subnets for the fork are maintained in addition to those for the current fork until the fork occurs. After the fork occurs, let the subnets from the previous fork reach the end of life with no replacements. ## How to avoid slashing