Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Attnet revamp: Subnet backbone structure based on beacon nodes #3312

Merged
merged 7 commits into from
May 10, 2023
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 27 additions & 9 deletions specs/phase0/validator.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,10 +88,12 @@ All terminology, constants, functions, and protocol mechanics defined in the [Ph

| Name | Value | Unit | Duration |
| - | - | :-: | :-: |
| `TARGET_AGGREGATORS_PER_COMMITTEE` | `2**4` (= 16) | validators | |
| `RANDOM_SUBNETS_PER_VALIDATOR` | `2**0` (= 1) | subnets | |
| `EPOCHS_PER_RANDOM_SUBNET_SUBSCRIPTION` | `2**8` (= 256) | epochs | ~27 hours |
| `TARGET_AGGREGATORS_PER_COMMITTEE` | `2**4` (= 16) | validators |
| `EPOCHS_PER_SUBNET_SUBSCRIPTION` | `2**8` (= 256) | epochs | ~27 hours |
| `ATTESTATION_SUBNET_COUNT` | `64` | The number of attestation subnets used in the gossipsub protocol. |
| `ATTESTATION_SUBNET_EXTRA_BITS` | `0` | The number of extra bits of a NodeId to use when mapping to a subscribed subnet |
| `SUBNETS_PER_NODE` | `2` | The number of long-lived subnets a beacon node should be subscribed to. |
| `ATTESTATION_SUBNET_PREFIX_BITS` | `(ceillog2(ATTESTATION_SUBNET_COUNT) + ATTESTATION_SUBNET_EXTRA_BITS)` | |

## Containers

Expand Down Expand Up @@ -606,15 +608,31 @@ def get_aggregate_and_proof_signature(state: BeaconState,

## Phase 0 attestation subnet stability

Because Phase 0 does not have shards and thus does not have Shard Committees, there is no stable backbone to the attestation subnets (`beacon_attestation_{subnet_id}`). To provide this stability, each validator must:
Because Phase 0 does not have shards and thus does not have Shard Committees, there is no stable backbone to the attestation subnets (`beacon_attestation_{subnet_id}`). To provide this stability, each beacon node should:

* Randomly select and remain subscribed to `RANDOM_SUBNETS_PER_VALIDATOR` attestation subnets
* 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
* Remain subscribed to `SUBNETS_PER_NODE` for `EPOCHS_PER_SUBNET_SUBSCRIPTION` epochs.
* Maintain advertisement of the selected subnets in their node's ENR `attnets` entry by setting the selected `subnet_id` bits to `True` (e.g. `ENR["attnets"][subnet_id] = True`) for all persistent attestation subnets.
* Select these subnets based on their node-id as specified by the following `compute_subscribed_subnets(node_id,epoch)` function.
hwwhww marked this conversation as resolved.
Show resolved Hide resolved

*Note*: Short lived beacon committee assignments should not be added in into the ENR `attnets` entry.
```python
def compute_subscribed_subnet(node_id: int, epoch: Epoch, index: int) -> int:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the maximum size of node_id?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe it was assumed UInt256. Not sure though what's the most appropriate type for pyspec

node_id_prefix = node_id >> (256 - int(ATTESTATION_SUBNET_PREFIX_BITS))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was thinking about adding assert spec.ATTESTATION_SUBNET_PREFIX_BITS <= 256 in test_config_invariants.py unittest. But before that, should 256 be parameterized?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense to add NODE_ID_SIZE = 32 and derive bit count from it?

node_offset = node_id % EPOCHS_PER_SUBNET_SUBSCRIPTION
AgeManning marked this conversation as resolved.
Show resolved Hide resolved
permutation_seed = hash(uint_to_bytes(uint64((epoch + node_offset) // EPOCHS_PER_SUBNET_SUBSCRIPTION)))
permutated_prefix = compute_shuffled_index(
node_id_prefix,
1 << int(ATTESTATION_SUBNET_PREFIX_BITS),
permutation_seed,
)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@AgeManning I added three castings (int() and uint64()) since remerkleable doesn't allow some mix int & unit operations.

return (permutated_prefix + index) % ATTESTATION_SUBNET_COUNT
```

```python
def compute_subscribed_subnets(node_id: int, epoch: Epoch) -> Sequence[int]:
return [compute_subscribed_subnet(node_id, epoch, idx) for idx in range(SUBNETS_PER_NODE)]
hwwhww marked this conversation as resolved.
Show resolved Hide resolved
```

*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.
*Note*: When preparing for a hard fork, a validator must select and subscribe to subnets of the future fork versioning at least `EPOCHS_PER_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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ def test_time(spec, state):
@with_all_phases
@spec_state_test
def test_networking(spec, state):
assert spec.RANDOM_SUBNETS_PER_VALIDATOR <= spec.ATTESTATION_SUBNET_COUNT
assert spec.SUBNETS_PER_NODE <= spec.ATTESTATION_SUBNET_COUNT


@with_all_phases
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
import random

from eth2spec.test.context import (
single_phase,
spec_state_test,
always_bls, with_phases, with_all_phases,
spec_test,
always_bls,
with_phases,
with_all_phases,
)
from eth2spec.test.helpers.constants import PHASE0
from eth2spec.test.helpers.attestations import build_attestation_data, get_valid_attestation
Expand Down Expand Up @@ -476,3 +482,34 @@ def test_get_aggregate_and_proof_signature(spec, state):
privkey=privkey,
pubkey=pubkey,
)


def run_compute_subscribed_subnets_arguments(spec, rng=random.Random(1111)):
node_id = rng.randint(0, 2**40 - 1) # try VALIDATOR_REGISTRY_LIMIT
epoch = rng.randint(0, 2**64 - 1)
subnets = spec.compute_subscribed_subnets(node_id, epoch)
assert len(subnets) == spec.SUBNETS_PER_NODE


@with_all_phases
@spec_test
@single_phase
def test_compute_subscribed_subnets_random_1(spec):
rng = random.Random(1111)
run_compute_subscribed_subnets_arguments(spec, rng)


@with_all_phases
@spec_test
@single_phase
def test_compute_subscribed_subnets_random_2(spec):
rng = random.Random(2222)
run_compute_subscribed_subnets_arguments(spec, rng)


@with_all_phases
@spec_test
@single_phase
def test_compute_subscribed_subnets_random_3(spec):
rng = random.Random(3333)
run_compute_subscribed_subnets_arguments(spec, rng)