Merge pull request #1615 from ethereum/subnet-validations
Add subnet validations for DoS resistance
This commit is contained in:
commit
a9fae27379
|
@ -143,6 +143,8 @@ DOMAIN_BEACON_ATTESTER: 0x01000000
|
|||
DOMAIN_RANDAO: 0x02000000
|
||||
DOMAIN_DEPOSIT: 0x03000000
|
||||
DOMAIN_VOLUNTARY_EXIT: 0x04000000
|
||||
DOMAIN_SELECTION_PROOF: 0x05000000
|
||||
DOMAIN_AGGREGATE_AND_PROOF: 0x06000000
|
||||
# Phase 1
|
||||
DOMAIN_SHARD_PROPOSAL: 0x80000000
|
||||
DOMAIN_SHARD_COMMITTEE: 0x81000000
|
||||
|
|
|
@ -142,6 +142,8 @@ DOMAIN_BEACON_ATTESTER: 0x01000000
|
|||
DOMAIN_RANDAO: 0x02000000
|
||||
DOMAIN_DEPOSIT: 0x03000000
|
||||
DOMAIN_VOLUNTARY_EXIT: 0x04000000
|
||||
DOMAIN_SELECTION_PROOF: 0x05000000
|
||||
DOMAIN_AGGREGATE_AND_PROOF: 0x06000000
|
||||
# Phase 1
|
||||
DOMAIN_SHARD_PROPOSAL: 0x80000000
|
||||
DOMAIN_SHARD_COMMITTEE: 0x81000000
|
||||
|
|
|
@ -253,11 +253,14 @@ The following values are (non-configurable) constants used throughout the specif
|
|||
|
||||
| Name | Value |
|
||||
| - | - |
|
||||
| `DOMAIN_BEACON_PROPOSER` | `DomainType('0x00000000')` |
|
||||
| `DOMAIN_BEACON_ATTESTER` | `DomainType('0x01000000')` |
|
||||
| `DOMAIN_RANDAO` | `DomainType('0x02000000')` |
|
||||
| `DOMAIN_DEPOSIT` | `DomainType('0x03000000')` |
|
||||
| `DOMAIN_VOLUNTARY_EXIT` | `DomainType('0x04000000')` |
|
||||
| `DOMAIN_BEACON_PROPOSER` | `DomainType('0x00000000')` |
|
||||
| `DOMAIN_BEACON_ATTESTER` | `DomainType('0x01000000')` |
|
||||
| `DOMAIN_RANDAO` | `DomainType('0x02000000')` |
|
||||
| `DOMAIN_DEPOSIT` | `DomainType('0x03000000')` |
|
||||
| `DOMAIN_VOLUNTARY_EXIT` | `DomainType('0x04000000')` |
|
||||
| `DOMAIN_SELECTION_PROOF` | `DomainType('0x05000000')` |
|
||||
| `DOMAIN_AGGREGATE_AND_PROOF` | `DomainType('0x06000000')` |
|
||||
|
||||
|
||||
## Containers
|
||||
|
||||
|
|
|
@ -229,15 +229,15 @@ 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 |
|
||||
|------------------------------------------------|----------------------|
|
||||
| beacon_block | SignedBeaconBlock |
|
||||
| beacon_aggregate_and_proof | AggregateAndProof |
|
||||
| beacon_attestation\* | Attestation |
|
||||
| committee_index{subnet_id}\_beacon_attestation | Attestation |
|
||||
| voluntary_exit | SignedVoluntaryExit |
|
||||
| proposer_slashing | ProposerSlashing |
|
||||
| attester_slashing | AttesterSlashing |
|
||||
| Topic | Message Type |
|
||||
|------------------------------------------------|-------------------------|
|
||||
| beacon_block | SignedBeaconBlock |
|
||||
| beacon_aggregate_and_proof | SignedAggregateAndProof |
|
||||
| beacon_attestation\* | Attestation |
|
||||
| committee_index{subnet_id}\_beacon_attestation | Attestation |
|
||||
| voluntary_exit | SignedVoluntaryExit |
|
||||
| proposer_slashing | ProposerSlashing |
|
||||
| attester_slashing | AttesterSlashing |
|
||||
|
||||
Clients MUST reject (fail validation) messages containing an incorrect type, or invalid payload.
|
||||
|
||||
|
@ -250,16 +250,19 @@ When processing incoming gossip, clients MAY descore or disconnect peers who fai
|
|||
There are two primary global topics used to propagate beacon blocks and aggregate attestations to all nodes on the network. Their `TopicName`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 proposer signature, `signed_beacon_block.signature` is valid.
|
||||
- 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).
|
||||
- `beacon_aggregate_and_proof` - This topic is used to propagate aggregated attestations (as `AggregateAndProof`s) to subscribing nodes (typically validators) to be included in future blocks. The following validations MUST pass before forwarding the `aggregate_and_proof` on the network.
|
||||
- The aggregate attestation defined by `hash_tree_root(aggregate_and_proof.aggregate)` has _not_ already been seen (via aggregate gossip, within a block, or through the creation of an equivalent aggregate locally).
|
||||
- The block being voted for (`aggregate_and_proof.aggregate.data.beacon_block_root`) passes validation.
|
||||
- `aggregate_and_proof.aggregate.data.slot` is within the last `ATTESTATION_PROPAGATION_SLOT_RANGE` slots (with a `MAXIMUM_GOSSIP_CLOCK_DISPARITY` allowance) -- i.e. `aggregate_and_proof.aggregate.data.slot + ATTESTATION_PROPAGATION_SLOT_RANGE >= current_slot >= aggregate_and_proof.aggregate.data.slot`.
|
||||
- The validator index is within the aggregate's committee -- i.e. `aggregate_and_proof.aggregator_index in get_attesting_indices(state, aggregate_and_proof.aggregate.data, aggregate_and_proof.aggregate.aggregation_bits)`.
|
||||
- `aggregate_and_proof.selection_proof` selects the validator as an aggregator for the slot -- i.e. `is_aggregator(state, aggregate_and_proof.aggregate.data.slot, aggregate_and_proof.aggregate.data.index, aggregate_and_proof.selection_proof)` returns `True`.
|
||||
- The `aggregate_and_proof.selection_proof` is a valid signature of the `aggregate_and_proof.aggregate.data.slot` by the validator with index `aggregate_and_proof.aggregator_index`.
|
||||
- The signature of `aggregate_and_proof.aggregate` is valid.
|
||||
- The block is the first block received for the proposer for the slot, `signed_beacon_block.message.slot`.
|
||||
- The proposer signature, `signed_beacon_block.signature`, is valid.
|
||||
- `beacon_aggregate_and_proof` - This topic is used to propagate aggregated attestations (as `SignedAggregateAndProof`s) to subscribing nodes (typically validators) to be included in future blocks. The following validations MUST pass before forwarding the `signed_aggregate_and_proof` on the network. (We define the following for convenience -- `aggregate_and_proof = signed_aggregate_and_proof.message` and `aggregate = aggregate_and_proof.aggregate`)
|
||||
- `aggregate.data.slot` is within the last `ATTESTATION_PROPAGATION_SLOT_RANGE` slots (with a `MAXIMUM_GOSSIP_CLOCK_DISPARITY` allowance) -- i.e. `aggregate.data.slot + ATTESTATION_PROPAGATION_SLOT_RANGE >= current_slot >= aggregate.data.slot` (a client MAY queue future aggregates for processing at the appropriate slot).
|
||||
- The aggregate attestation defined by `hash_tree_root(aggregate)` has _not_ already been seen (via aggregate gossip, within a block, or through the creation of an equivalent aggregate locally).
|
||||
- The `aggregate` is the first aggregate received for the aggregator with index `aggregate_and_proof.aggregator_index` for the slot `aggregate.data.slot`.
|
||||
- The block being voted for (`aggregate.data.beacon_block_root`) passes validation.
|
||||
- `aggregate_and_proof.selection_proof` selects the validator as an aggregator for the slot -- i.e. `is_aggregator(state, aggregate.data.slot, aggregate.data.index, aggregate_and_proof.selection_proof)` returns `True`.
|
||||
- The aggregator's validator index is within the aggregate's committee -- i.e. `aggregate_and_proof.aggregator_index in get_attesting_indices(state, aggregate.data, aggregate.aggregation_bits)`.
|
||||
- The `aggregate_and_proof.selection_proof` is a valid signature of the `aggregate.data.slot` by the validator with index `aggregate_and_proof.aggregator_index`.
|
||||
- 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:
|
||||
|
||||
|
@ -273,9 +276,10 @@ Attestation subnets are used to propagate unaggregated attestations to subsectio
|
|||
|
||||
- `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.
|
||||
- `attestation.data.slot` is within the last `ATTESTATION_PROPAGATION_SLOT_RANGE` slots (within a `MAXIMUM_GOSSIP_CLOCK_DISPARITY` allowance) -- i.e. `attestation.data.slot + ATTESTATION_PROPAGATION_SLOT_RANGE >= current_slot >= attestation.data.slot` (a client MAY queue future attestations for processing at the appropriate slot).
|
||||
- The attestation is unaggregated -- that is, it has exactly one participating validator (`len([bit for bit in attestation.aggregation_bits if bit == 0b1]) == 1`).
|
||||
- The attestation is the first attestation received for the participating validator for the slot, `attestation.data.slot`.
|
||||
- The block being voted for (`attestation.data.beacon_block_root`) passes validation.
|
||||
- `attestation.data.slot` is within the last `ATTESTATION_PROPAGATION_SLOT_RANGE` slots (within a `MAXIMUM_GOSSIP_CLOCK_DISPARITY` allowance) -- i.e. `attestation.data.slot + ATTESTATION_PROPAGATION_SLOT_RANGE >= current_slot >= attestation.data.slot`.
|
||||
- The signature of `attestation` is valid.
|
||||
|
||||
#### Interop
|
||||
|
|
|
@ -59,6 +59,7 @@
|
|||
- [Aggregate signature](#aggregate-signature-1)
|
||||
- [Broadcast aggregate](#broadcast-aggregate)
|
||||
- [`AggregateAndProof`](#aggregateandproof)
|
||||
- [`SignedAggregateAndProof`](#signedaggregateandproof)
|
||||
- [Phase 0 attestation subnet stability](#phase-0-attestation-subnet-stability)
|
||||
- [How to avoid slashing](#how-to-avoid-slashing)
|
||||
- [Proposer slashing](#proposer-slashing)
|
||||
|
@ -423,7 +424,7 @@ A validator is selected to aggregate based upon the return value of `is_aggregat
|
|||
|
||||
```python
|
||||
def get_slot_signature(state: BeaconState, slot: Slot, privkey: int) -> BLSSignature:
|
||||
domain = get_domain(state, DOMAIN_BEACON_ATTESTER, compute_epoch_at_slot(slot))
|
||||
domain = get_domain(state, DOMAIN_SELECTION_PROOF, compute_epoch_at_slot(slot))
|
||||
signing_root = compute_signing_root(slot, domain)
|
||||
return bls.Sign(privkey, signing_root)
|
||||
```
|
||||
|
@ -461,9 +462,37 @@ def get_aggregate_signature(attestations: Sequence[Attestation]) -> BLSSignature
|
|||
|
||||
#### Broadcast aggregate
|
||||
|
||||
If the validator is selected to aggregate (`is_aggregator`), then they broadcast their best aggregate to the global aggregate channel (`beacon_aggregate_and_proof`) two-thirds of the way through the `slot`-that is, `SECONDS_PER_SLOT * 2 / 3` seconds after the start of `slot`.
|
||||
If the validator is selected to aggregate (`is_aggregator`), then they broadcast their best aggregate as a `SignedAggregateAndProof` to the global aggregate channel (`beacon_aggregate_and_proof`) two-thirds of the way through the `slot`-that is, `SECONDS_PER_SLOT * 2 / 3` seconds after the start of `slot`.
|
||||
|
||||
Aggregate attestations are broadcast as `AggregateAndProof` objects to prove to the gossip channel that the validator has been selected as an aggregator.
|
||||
Selection proofs are provided in `AggregateAndProof` to prove to the gossip channel that the validator has been selected as an aggregator.
|
||||
|
||||
`AggregateAndProof` messages are signed by the aggregator and broadcast inside of `SignedAggregateAndProof` objects to prevent a class of DoS attacks and message forgeries.
|
||||
|
||||
First, `aggregate_and_proof = get_aggregate_and_proof(state, validator_index, aggregate_attestation, privkey)` is constructed.
|
||||
|
||||
```python
|
||||
def get_aggregate_and_proof(state: BeaconState,
|
||||
aggregator_index: ValidatorIndex,
|
||||
aggregate: Attestation,
|
||||
privkey: int) -> AggregateAndProof:
|
||||
return AggregateAndProof(
|
||||
aggregator_index=aggregator_index,
|
||||
aggregate=aggregate,
|
||||
selection_proof=get_slot_signature(state, aggregate.data.slot, privkey),
|
||||
)
|
||||
```
|
||||
|
||||
Then `signed_aggregate_and_proof = SignedAggregateAndProof(message=aggregate_and_proof, signature=signature)` is constructed and broadast. Where `signature` is obtained from:
|
||||
|
||||
```python
|
||||
def get_aggregate_and_proof_signature(state: BeaconState,
|
||||
aggregate_and_proof: AggregateAndProof,
|
||||
privkey: int) -> BLSSignature:
|
||||
aggregate = aggregate_and_proof.aggregate
|
||||
domain = get_domain(state, DOMAIN_AGGREGATE_AND_PROOF, compute_epoch_at_slot(aggregate.data.slot))
|
||||
signing_root = compute_signing_root(aggregate_and_proof, domain)
|
||||
return bls.Sign(privkey, signing_root)
|
||||
```
|
||||
|
||||
##### `AggregateAndProof`
|
||||
|
||||
|
@ -474,10 +503,13 @@ class AggregateAndProof(Container):
|
|||
selection_proof: BLSSignature
|
||||
```
|
||||
|
||||
Where
|
||||
* `aggregator_index` is the validator's `ValidatorIndex`.
|
||||
* `aggregate` is the `aggregate_attestation` constructed in the previous section.
|
||||
* `selection_proof` is the signature of the slot (`get_slot_signature()`).
|
||||
##### `SignedAggregateAndProof`
|
||||
|
||||
```python
|
||||
class SignedAggregateAndProof(Container):
|
||||
message: AggregateAndProof
|
||||
signature: BLSSignature
|
||||
```
|
||||
|
||||
## Phase 0 attestation subnet stability
|
||||
|
||||
|
|
Loading…
Reference in New Issue