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

Separate type for onchain attestation aggregates #3787

Draft
wants to merge 3 commits into
base: dev
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all 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
81 changes: 71 additions & 10 deletions specs/electra/beacon-chain.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
- [`Consolidation`](#consolidation)
- [`SignedConsolidation`](#signedconsolidation)
- [`PendingConsolidation`](#pendingconsolidation)
- [`OnchainAttestation`](#onchainattestation)
- [Modified Containers](#modified-containers)
- [`AttesterSlashing`](#attesterslashing)
- [Extended Containers](#extended-containers)
Expand All @@ -49,6 +50,8 @@
- [New `has_execution_withdrawal_credential`](#new-has_execution_withdrawal_credential)
- [Updated `is_fully_withdrawable_validator`](#updated-is_fully_withdrawable_validator)
- [Updated `is_partially_withdrawable_validator`](#updated-is_partially_withdrawable_validator)
- [New `compute_signing_attestation_data`](#new-compute_signing_attestation_data)
- [Modified `is_valid_indexed_attestation`](#modified-is_valid_indexed_attestation)
- [Misc](#misc-1)
- [`get_committee_indices`](#get_committee_indices)
- [`get_validator_max_effective_balance`](#get_validator_max_effective_balance)
Expand All @@ -58,7 +61,7 @@
- [New `get_consolidation_churn_limit`](#new-get_consolidation_churn_limit)
- [New `get_active_balance`](#new-get_active_balance)
- [New `get_pending_balance_to_withdraw`](#new-get_pending_balance_to_withdraw)
- [Modified `get_attesting_indices`](#modified-get_attesting_indices)
- [New `get_onchain_attesting_indices`](#new-get_onchain_attesting_indices)
- [Beacon state mutators](#beacon-state-mutators)
- [Updated `initiate_validator_exit`](#updated--initiate_validator_exit)
- [New `switch_to_compounding_validator`](#new-switch_to_compounding_validator)
Expand All @@ -83,7 +86,7 @@
- [Operations](#operations)
- [Modified `process_operations`](#modified-process_operations)
- [Attestations](#attestations)
- [Modified `process_attestation`](#modified-process_attestation)
- [New `process_onchain_attestation`](#new-process_onchain_attestation)
- [Deposits](#deposits)
- [Updated `apply_deposit`](#updated--apply_deposit)
- [New `is_valid_deposit_signature`](#new-is_valid_deposit_signature)
Expand Down Expand Up @@ -270,6 +273,18 @@ class PendingConsolidation(Container):
target_index: ValidatorIndex
```

#### `OnchainAttestation`

*Note*: The container is new in EIP7549.

```python
class OnchainAttestation(Container):
aggregation_bits: Bitlist[MAX_VALIDATORS_PER_COMMITTEE * MAX_COMMITTEES_PER_SLOT]
data: AttestationData
committee_bits: Bitvector[MAX_COMMITTEES_PER_SLOT]
signature: BLSSignature
Copy link
Contributor

Choose a reason for hiding this comment

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

I think we want to be consistent with the current Attestation by having committee_bits to be after the signature field

```

### Modified Containers

#### `AttesterSlashing`
Expand Down Expand Up @@ -312,7 +327,7 @@ class BeaconBlockBody(Container):
# Operations
proposer_slashings: List[ProposerSlashing, MAX_PROPOSER_SLASHINGS]
attester_slashings: List[AttesterSlashing, MAX_ATTESTER_SLASHINGS_ELECTRA] # [Modified in Electra:EIP7549]
attestations: List[Attestation, MAX_ATTESTATIONS_ELECTRA] # [Modified in Electra:EIP7549]
attestations: List[OnchainAttestation, MAX_ATTESTATIONS_ELECTRA] # [Modified in Electra:EIP7549]
deposits: List[Deposit, MAX_DEPOSITS]
voluntary_exits: List[SignedVoluntaryExit, MAX_VOLUNTARY_EXITS]
sync_aggregate: SyncAggregate
Expand Down Expand Up @@ -531,6 +546,47 @@ def is_partially_withdrawable_validator(validator: Validator, balance: Gwei) ->
)
```

#### New `compute_signing_attestation_data`

*Note:* This function constructs attestation data for the signing purpose.

```python
def compute_signing_attestation_data(data: AttestationData) -> AttestationData:
"""
Returns ``AttestationData`` with ``index`` set to ``0``.
"""
return AttestationData(
slot = indexed_attestation.data.slot,
index = CommitteeIndex(0),
beacon_block_root = indexed_attestation.data.beacon_block_root,
source = indexed_attestation.data.source,
target = indexed_attestation.data.target,
)
```

#### Modified `is_valid_indexed_attestation`

*Note*: This function is modified to verify signature over signing `AttestationData`.

```python
def is_valid_indexed_attestation(state: BeaconState, indexed_attestation: IndexedAttestation) -> bool:
"""
Check if ``indexed_attestation`` is not empty, has sorted and unique indices and has a valid aggregate signature.
"""
# Verify indices are sorted and unique
indices = indexed_attestation.attesting_indices
if len(indices) == 0 or not indices == sorted(set(indices)):
return False
# Verify aggregate signature
pubkeys = [state.validators[i].pubkey for i in indices]
domain = get_domain(state, DOMAIN_BEACON_ATTESTER, indexed_attestation.data.target.epoch)
# [New in Electra:EIP7549]
signing_data = compute_signing_attestation_data(indexed_attestation.data)
signing_root = compute_signing_root(signed_data, domain)
return bls.FastAggregateVerify(pubkeys, signing_root, indexed_attestation.signature)
```


### Misc

#### `get_committee_indices`
Expand Down Expand Up @@ -603,10 +659,10 @@ def get_pending_balance_to_withdraw(state: BeaconState, validator_index: Validat
)
```

#### Modified `get_attesting_indices`
#### New `get_onchain_attesting_indices`

```python
def get_attesting_indices(state: BeaconState, attestation: Attestation) -> Set[ValidatorIndex]:
def get_onchain_attesting_indices(state: BeaconState, attestation: OnchainAttestation) -> Set[ValidatorIndex]:
"""
Return the set of attesting indices corresponding to ``aggregation_bits`` and ``committee_bits``.
"""
Expand Down Expand Up @@ -1060,7 +1116,7 @@ def process_operations(state: BeaconState, body: BeaconBlockBody) -> None:

for_ops(body.proposer_slashings, process_proposer_slashing)
for_ops(body.attester_slashings, process_attester_slashing)
for_ops(body.attestations, process_attestation) # [Modified in Electra:EIP7549]
for_ops(body.attestations, process_onchain_attestation) # [Modified in Electra:EIP7549]
for_ops(body.deposits, process_deposit) # [Modified in Electra:EIP7251]
for_ops(body.voluntary_exits, process_voluntary_exit) # [Modified in Electra:EIP7251]
for_ops(body.bls_to_execution_changes, process_bls_to_execution_change)
Expand All @@ -1072,10 +1128,10 @@ def process_operations(state: BeaconState, body: BeaconBlockBody) -> None:

##### Attestations

###### Modified `process_attestation`
###### New `process_onchain_attestation`

```python
def process_attestation(state: BeaconState, attestation: Attestation) -> None:
def process_onchain_attestation(state: BeaconState, attestation: OnchainAttestation) -> 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)
Expand All @@ -1096,7 +1152,12 @@ def process_attestation(state: BeaconState, attestation: Attestation) -> None:
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))
indexed_attestation = IndexedAttestation(
attesting_indices=sorted(get_onchain_attesting_indices(state, attestation)),
data=attestation.data,
signature=attestation.signature,
)
assert is_valid_indexed_attestation(state, indexed_attestation)

# Update epoch participation flags
if data.target.epoch == get_current_epoch(state):
Expand All @@ -1105,7 +1166,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, attestation):
for index in get_onchain_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
25 changes: 0 additions & 25 deletions specs/electra/p2p-interface.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,6 @@ The specification of these changes continues in the same format as the network s
- [Modifications in Electra](#modifications-in-electra)
- [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 -->
Expand All @@ -32,28 +29,6 @@ Topics follow the same specification as in prior upgrades.

The `beacon_block` topic is modified to also support Electra blocks.

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

The specification around the creation, validation, and dissemination of messages has not changed from the Capella document unless explicitly noted here.

The derivation of the `message-id` remains stable.

#### Global topics

##### `beacon_aggregate_and_proof`

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

Choose a reason for hiding this comment

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

committee_bits is still used in compute_on_chain_aggregate below


The following validations are added:
* [REJECT] `len(committee_indices) == 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]`

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

Choose a reason for hiding this comment

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

the key thing of EIP-7549 is to have data.index = 0 so we should not remove this? same to above

36 changes: 16 additions & 20 deletions specs/electra/validator.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,7 @@
- [Deposits](#deposits)
- [Execution payload](#execution-payload)
- [Attesting](#attesting)
- [Construct attestation](#construct-attestation)
- [Attestation aggregation](#attestation-aggregation)
- [Construct aggregate](#construct-aggregate)
- [Modified aggregate signature](#modified-aggregate-signature)

<!-- END doctoc generated TOC please keep comment here to allow auto update -->
<!-- /TOC -->
Expand Down Expand Up @@ -72,14 +70,14 @@ Changed the max attester slashings size to `MAX_ATTESTER_SLASHINGS_ELECTRA`.
Changed the max attestations size to `MAX_ATTESTATIONS_ELECTRA`.

The network attestation aggregates contain only the assigned committee attestations.
Attestation aggregates received by the block proposer from the committee aggregators with disjoint `committee_bits` sets and equal `AttestationData` SHOULD be consolidated into a single `Attestation` object.
The proposer should run the following function to construct an on chain final aggregate form a list of network aggregates with equal `AttestationData`:
Attestation aggregates received by the block proposer from the committee aggregators with disjoint `committee_bits` sets and equal signing `AttestationData` SHOULD be consolidated into a single `OnchainAttestation` object.
The proposer should run the following function to construct an on chain final aggregate form a list of network aggregates with equal signing `AttestationData`:

```python
def compute_on_chain_aggregate(network_aggregates: Sequence[Attestation]) -> Attestation:
def compute_on_chain_aggregate(network_aggregates: Sequence[Attestation]) -> OnchainAttestation:
aggregates = sorted(network_aggregates, key=lambda a: get_committee_indices(a.committee_bits)[0])

data = aggregates[0].data
data = compute_signing_attestation_data(aggregates[0].data)
aggregation_bits = Bitlist[MAX_VALIDATORS_PER_COMMITTEE * MAX_COMMITTEES_PER_SLOT]()
for a in aggregates:
for b in a.aggregation_bits:
Expand All @@ -91,7 +89,7 @@ def compute_on_chain_aggregate(network_aggregates: Sequence[Attestation]) -> Att
committee_flags = [(index in committee_indices) for index in range(0, MAX_COMMITTEES_PER_SLOT)]
committee_bits = Bitvector[MAX_COMMITTEES_PER_SLOT](committee_flags)
Copy link

Choose a reason for hiding this comment

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

committee_bits is computed from Attestation.committee_bits. If that's true should we drop the checks in p2p-interface.md?


return Attestation(
return OnchainAttestation(
aggregation_bits=aggregation_bits,
data=data,
committee_bits=committee_bits,
Expand Down Expand Up @@ -150,18 +148,16 @@ def prepare_execution_payload(state: BeaconState,

## Attesting

### Construct attestation
##### Modified aggregate signature

- Set `attestation_data.index = 0`.
- Let `attestation.aggregation_bits` be a `Bitlist[MAX_VALIDATORS_PER_COMMITTEE * MAX_COMMITTEES_PER_SLOT]` of length `len(committee)`, where the bit of the index of the validator in the `committee` is set to `0b1`.
- Let `attestation.committee_bits` be a `Bitvector[MAX_COMMITTEES_PER_SLOT]`, where the bit at the index associated with the validator's committee is set to `0b1`.
*Note:* The `get_attestation_signature` method is modified to use signing attestation data.

*Note*: Calling `get_attesting_indices(state, attestation)` should return a list of length equal to 1, containing `validator_index`.
Set `attestation.signature = attestation_signature` where `attestation_signature` is obtained from:

## Attestation aggregation

### Construct aggregate

- Set `attestation_data.index = 0`.
- Let `aggregation_bits` be a `Bitlist[MAX_VALIDATORS_PER_COMMITTEE * MAX_COMMITTEES_PER_SLOT]` of length `len(committee)`, where each bit set from each individual attestation is set to `0b1`.
- Set `attestation.committee_bits = committee_bits`, where `committee_bits` has the same value as in each individual attestation.
```python
def get_attestation_signature(state: BeaconState, attestation_data: AttestationData, privkey: int) -> BLSSignature:
domain = get_domain(state, DOMAIN_BEACON_ATTESTER, attestation_data.target.epoch)
signing_data = compute_signing_attestation_data(attestation_data)
signing_root = compute_signing_root(signing_data, domain)
return bls.Sign(privkey, signing_root)
```