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

EIP-7549: Move committee index outside Attestation #3559

Merged
merged 7 commits into from
Mar 14, 2024
Merged
Show file tree
Hide file tree
Changes from 4 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
4 changes: 2 additions & 2 deletions pysetup/spec_builders/phase0.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,8 +98,8 @@ def wrapper(*args, **kw): # type: ignore

_get_attesting_indices = get_attesting_indices
get_attesting_indices = cache_this(
lambda state, data, bits: (
lambda state, attestation: (
state.randao_mixes.hash_tree_root(),
state.validators.hash_tree_root(), data.hash_tree_root(), bits.hash_tree_root()
state.validators.hash_tree_root(), attestation.hash_tree_root()
),
_get_attesting_indices, lru_size=SLOTS_PER_EPOCH * MAX_COMMITTEES_PER_SLOT * 3)'''
4 changes: 2 additions & 2 deletions specs/_features/custody_game/beacon-chain.md
Original file line number Diff line number Diff line change
Expand Up @@ -401,7 +401,7 @@ def process_chunk_challenge(state: BeaconState, challenge: CustodyChunkChallenge
# Verify responder is slashable
assert is_slashable_validator(responder, get_current_epoch(state))
# Verify the responder participated in the attestation
attesters = get_attesting_indices(state, challenge.attestation.data, challenge.attestation.aggregation_bits)
attesters = get_attesting_indices(state, challenge)
assert challenge.responder_index in attesters
# Verify shard transition is correctly given
assert hash_tree_root(challenge.shard_transition) == challenge.attestation.data.shard_transition_root
Expand Down Expand Up @@ -594,7 +594,7 @@ def process_custody_slashing(state: BeaconState, signed_custody_slashing: Signed
assert len(custody_slashing.data) == shard_transition.shard_block_lengths[custody_slashing.data_index]
assert hash_tree_root(custody_slashing.data) == shard_transition.shard_data_roots[custody_slashing.data_index]
# Verify existence and participation of claimed malefactor
attesters = get_attesting_indices(state, attestation.data, attestation.aggregation_bits)
attesters = get_attesting_indices(state, attestation)
assert custody_slashing.malefactor_index in attesters

# Verify the malefactor custody key
Expand Down
146 changes: 146 additions & 0 deletions specs/_features/eip7549/beacon-chain.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
# EIP-7549 -- The Beacon Chain

## Table of contents

<!-- TOC -->
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->

- [Introduction](#introduction)
- [Preset](#preset)
- [Containers](#containers)
- [Modified containers](#modified-containers)
- [Attestation](#attestation)
- [IndexedAttestation](#indexedattestation)
- [Helper functions](#helper-functions)
- [Misc](#misc)
- [`get_committee_indices`](#get_committee_indices)
- [Beacon state accessors](#beacon-state-accessors)
- [New `get_committee_attesters`](#new-get_committee_attesters)
- [Modified `get_attesting_indices`](#modified-get_attesting_indices)
- [Block processing](#block-processing)
- [Modified `process_attestation`](#modified-process_attestation)

<!-- END doctoc generated TOC please keep comment here to allow auto update -->
<!-- /TOC -->

## Introduction

This is the beacon chain specification to move the attestation committee index outside of the signed message. For motivation, refer to [EIP-7549](https://eips.ethereum.org/EIPS/eip-7549).

*Note:* This specification is built upon [Deneb](../../deneb/beacon_chain.md) and is under active development.

## Preset

| Name | Value | Description |
| - | - | - |
| `MAX_ATTESTER_SLASHINGS` | `2**0` (= 1) |
| `MAX_ATTESTATIONS` | `2**3` (= 8) |

## Containers

### Modified containers

#### Attestation

```python
class Attestation(Container):
aggregation_bits: List[Bitlist[MAX_VALIDATORS_PER_COMMITTEE], MAX_COMMITTEES_PER_SLOT] # [Modified in EIP7549]
data: AttestationData
committee_bits: Bitvector[MAX_COMMITTEES_PER_SLOT] # [New in EIP7549]
signature: BLSSignature
```

#### IndexedAttestation

```python
class IndexedAttestation(Container):
attesting_indices: List[ValidatorIndex, MAX_VALIDATORS_PER_COMMITTEE * MAX_COMMITTEES_PER_SLOT] # [Modified in EIP7549]
data: AttestationData
signature: BLSSignature
```

## Helper functions

### Misc

#### `get_committee_indices`

```python
def get_committee_indices(commitee_bits: Bitvector) -> List[CommitteeIndex]:
return [CommitteeIndex(index) for bit, index in enumerate(commitee_bits) if bit]
```

### Beacon state accessors

#### New `get_committee_attesters`

```python
def get_committee_attesters(state: BeaconState, attesting_bits: Bitlist, index: CommitteeIndex) -> Set[ValidatorIndex]:
hwwhww marked this conversation as resolved.
Show resolved Hide resolved
committee = get_beacon_committee(state, data.slot, index)
hwwhww marked this conversation as resolved.
Show resolved Hide resolved
return set(index for i, index in enumerate(committee) if attesting_bits[i])
```

#### Modified `get_attesting_indices`

```python
def get_attesting_indices(state: BeaconState, attestation: Attestation) -> Set[ValidatorIndex]:
"""
Return the set of attesting indices corresponding to ``aggregation_bits`` and ``committee_bits``.
"""

output = set()
committee_indices = get_committee_indices(attestation.committee_bits)
for index in committee_indices:
attesting_bits = attestation.attesting_bits[index]
dapplion marked this conversation as resolved.
Show resolved Hide resolved
committee_attesters = get_committee_attesters(state, attesting_bits, index)
output = output.union(committee_attesters)

return output
```

### Block processing

#### Modified `process_attestation`

```python
def process_attestation(state: BeaconState, attestation: Attestation) -> None:
data = attestation.data
assert data.target.epoch in (get_previous_epoch(state), get_current_epoch(state))
assert data.target.epoch == compute_epoch_at_slot(data.slot)
assert data.slot + MIN_ATTESTATION_INCLUSION_DELAY <= state.slot
Copy link
Contributor

Choose a reason for hiding this comment

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

Why is the max age removed here? (state.slot <= data.slot + SLOTS_PER_EPOCH)

https://github.com/ethereum/consensus-specs/blob/dev/specs/phase0/beacon-chain.md#attestations

assert data.slot + MIN_ATTESTATION_INCLUSION_DELAY <= state.slot <= data.slot + SLOTS_PER_EPOCH

Copy link
Collaborator

Choose a reason for hiding this comment

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

It is initially removed in #3360

Copy link
Contributor

Choose a reason for hiding this comment

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

Oh, thanks for the pointer! Have missed the Deneb specs while reviewing.


# [Modified in EIP7549]
assert data.index == 0
committee_indices = get_committee_indices(attestation.committee_bits)
assert len(committee_indices) > 0
dapplion marked this conversation as resolved.
Show resolved Hide resolved
dapplion marked this conversation as resolved.
Show resolved Hide resolved
assert len(committee_indices) == len(attestation.aggregation_bits)
for index in committee_indices:
assert index < get_committee_count_per_slot(state, data.target.epoch)
committee = get_beacon_committee(state, data.slot, index)
assert len(attestation.aggregation_bits[index]) == len(committee)

# Participation flag indices
participation_flag_indices = get_attestation_participation_flag_indices(state, data, state.slot - data.slot)

# Verify signature
assert is_valid_indexed_attestation(state, get_indexed_attestation(state, attestation))

# Update epoch participation flags
if data.target.epoch == get_current_epoch(state):
epoch_participation = state.current_epoch_participation
else:
epoch_participation = state.previous_epoch_participation

proposer_reward_numerator = 0
for index in get_attesting_indices(state, attestation):
for flag_index, weight in enumerate(PARTICIPATION_FLAG_WEIGHTS):
if flag_index in participation_flag_indices and not has_flag(epoch_participation[index], flag_index):
epoch_participation[index] = add_flag(epoch_participation[index], flag_index)
proposer_reward_numerator += get_base_reward(state, index) * weight

# Reward proposer
proposer_reward_denominator = (WEIGHT_DENOMINATOR - PROPOSER_WEIGHT) * WEIGHT_DENOMINATOR // PROPOSER_WEIGHT
proposer_reward = Gwei(proposer_reward_numerator // proposer_reward_denominator)
increase_balance(state, get_beacon_proposer_index(state), proposer_reward)
```
54 changes: 54 additions & 0 deletions specs/_features/eip7549/p2p-interface.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# EIP-7549 -- Networking

This document contains the consensus-layer networking specification for EIP-7549.

The specification of these changes continues in the same format as the network specifications of previous upgrades, and assumes them as pre-requisite.

## Table of contents

<!-- TOC -->
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->

- [Modifications in EIP-7549](#modifications-in-eip-7549)
- [The gossip domain: gossipsub](#the-gossip-domain-gossipsub)
- [Topics and messages](#topics-and-messages)
- [Global topics](#global-topics)
- [`beacon_aggregate_and_proof`](#beacon_aggregate_and_proof)
- [`beacon_attestation_{subnet_id}`](#beacon_attestation_subnet_id)

<!-- END doctoc generated TOC please keep comment here to allow auto update -->
<!-- /TOC -->

## Modifications in EIP-7549

### The gossip domain: gossipsub

#### Topics and messages

The `beacon_aggregate_and_proof` and `beacon_attestation_{subnet_id}` topics are modified to support the gossip of a new attestation type.

##### Global topics

###### `beacon_aggregate_and_proof`

*[Modified in EIP7549]*

The following convenience variables are re-defined
- `index = get_committee_indices(aggregate.committee_bits)[0]`
- `aggregation_bits = aggregate.aggregation_bits[0]`

The following validations are added:
* [REJECT] `len(committee_indices) == len(aggregate.attestation_bits) == 1`, where `committee_indices = get_committee_indices(aggregate)`.
* [REJECT] `aggregate.data.index == 0`

###### `beacon_attestation_{subnet_id}`

The following convenience variables are re-defined
- `index = get_committee_indices(attestation.committee_bits)[0]`
- `aggregation_bits = attestation.aggregation_bits[0]`

The following validations are added:
* [REJECT] `len(committee_indices) == len(attestation.attestation_bits) == 1`, where `committee_indices = get_committee_indices(attestation)`.
* [REJECT] `attestation.data.index == 0`

49 changes: 49 additions & 0 deletions specs/_features/eip7549/validator.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# Deneb -- Honest Validator

## Table of contents

<!-- TOC -->
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->

- [Modifications in EIP-7549](#modifications-in-eip-7549)
- [Block proposal](#block-proposal)
- [Constructing the `BeaconBlockBody`](#constructing-the-beaconblockbody)
- [Attestations](#attestations)
- [Attesting](#attesting)
- [Construct attestation](#construct-attestation)
- [Attestation aggregation](#attestation-aggregation)
- [Construct aggregate](#construct-aggregate)

<!-- END doctoc generated TOC please keep comment here to allow auto update -->
<!-- /TOC -->

## Modifications in EIP-7549

### Block proposal

#### Constructing the `BeaconBlockBody`

##### Attestations

Attestations received from aggregators with disjoint `committee_bits` sets and equal `AttestationData` SHOULD be consolidated into a single `Attestation` object.

### Attesting

#### Construct attestation

- Set `attestation_data.index = 0`.
- Let `aggregation_bits` be a `Bitlist[MAX_VALIDATORS_PER_COMMITTEE]` of length `len(committee)`, where the bit of the index of the validator in the `committee` is set to `0b1`.
- Set `attestation.aggregation_bits = [aggregation_bits]`, a list of length 1
dapplion marked this conversation as resolved.
Show resolved Hide resolved

*Note*: Calling `get_attesting_indices(state, attestation)` should return a list of length equal to 1, containing `validator_index`.

### Attestation aggregation

#### Construct aggregate

- Set `attestation_data.index = 0`.
- Let `aggregation_bits` be a `Bitlist[MAX_VALIDATORS_PER_COMMITTEE]` of length `len(committee)`, where each bit set from each individual attestation is set to `0b1`.
- Set `attestation.aggregation_bits = [aggregation_bits]`, a list of length 1
dapplion marked this conversation as resolved.
Show resolved Hide resolved


2 changes: 1 addition & 1 deletion specs/altair/beacon-chain.md
Original file line number Diff line number Diff line change
Expand Up @@ -496,7 +496,7 @@ def process_attestation(state: BeaconState, attestation: Attestation) -> None:
epoch_participation = state.previous_epoch_participation

proposer_reward_numerator = 0
for index in get_attesting_indices(state, data, attestation.aggregation_bits):
for index in get_attesting_indices(state, attestation):
for flag_index, weight in enumerate(PARTICIPATION_FLAG_WEIGHTS):
if flag_index in participation_flag_indices and not has_flag(epoch_participation[index], flag_index):
epoch_participation[index] = add_flag(epoch_participation[index], flag_index)
Expand Down
2 changes: 1 addition & 1 deletion specs/altair/fork.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ def translate_participation(state: BeaconState, pending_attestations: Sequence[p

# Apply flags to all attesting validators
epoch_participation = state.previous_epoch_participation
for index in get_attesting_indices(state, data, attestation.aggregation_bits):
for index in get_attesting_indices(state, attestation):
for flag_index in participation_flag_indices:
epoch_participation[index] = add_flag(epoch_participation[index], flag_index)

Expand Down
2 changes: 1 addition & 1 deletion specs/deneb/beacon-chain.md
Original file line number Diff line number Diff line change
Expand Up @@ -337,7 +337,7 @@ def process_attestation(state: BeaconState, attestation: Attestation) -> None:
epoch_participation = state.previous_epoch_participation

proposer_reward_numerator = 0
for index in get_attesting_indices(state, data, attestation.aggregation_bits):
for index in get_attesting_indices(state, attestation):
for flag_index, weight in enumerate(PARTICIPATION_FLAG_WEIGHTS):
if flag_index in participation_flag_indices and not has_flag(epoch_participation[index], flag_index):
epoch_participation[index] = add_flag(epoch_participation[index], flag_index)
Expand Down
14 changes: 6 additions & 8 deletions specs/phase0/beacon-chain.md
Original file line number Diff line number Diff line change
Expand Up @@ -1086,7 +1086,7 @@ def get_indexed_attestation(state: BeaconState, attestation: Attestation) -> Ind
"""
Return the indexed attestation corresponding to ``attestation``.
"""
attesting_indices = get_attesting_indices(state, attestation.data, attestation.aggregation_bits)
attesting_indices = get_attesting_indices(state, attestation)

return IndexedAttestation(
attesting_indices=sorted(attesting_indices),
Expand All @@ -1098,14 +1098,12 @@ def get_indexed_attestation(state: BeaconState, attestation: Attestation) -> Ind
#### `get_attesting_indices`

```python
def get_attesting_indices(state: BeaconState,
data: AttestationData,
bits: Bitlist[MAX_VALIDATORS_PER_COMMITTEE]) -> Set[ValidatorIndex]:
def get_attesting_indices(state: BeaconState, attestation: Attestation) -> Set[ValidatorIndex]:
"""
Return the set of attesting indices corresponding to ``data`` and ``bits``.
"""
committee = get_beacon_committee(state, data.slot, data.index)
return set(index for i, index in enumerate(committee) if bits[i])
committee = get_beacon_committee(state, attestation.data.slot, attestation.data.index)
return set(index for i, index in enumerate(committee) if attestation.aggregation_bits[i])
```

### Beacon state mutators
Expand Down Expand Up @@ -1343,7 +1341,7 @@ def get_unslashed_attesting_indices(state: BeaconState,
attestations: Sequence[PendingAttestation]) -> Set[ValidatorIndex]:
output = set() # type: Set[ValidatorIndex]
for a in attestations:
output = output.union(get_attesting_indices(state, a.data, a.aggregation_bits))
output = output.union(get_attesting_indices(state, a))
return set(filter(lambda index: not state.validators[index].slashed, output))
```

Expand Down Expand Up @@ -1516,7 +1514,7 @@ def get_inclusion_delay_deltas(state: BeaconState) -> Tuple[Sequence[Gwei], Sequ
for index in get_unslashed_attesting_indices(state, matching_source_attestations):
attestation = min([
a for a in matching_source_attestations
if index in get_attesting_indices(state, a.data, a.aggregation_bits)
if index in get_attesting_indices(state, a)
], key=lambda a: a.inclusion_delay)
rewards[attestation.proposer_index] += get_proposer_reward(state, index)
max_attester_reward = Gwei(get_base_reward(state, index) - get_proposer_reward(state, index))
Expand Down
Loading
Loading