Skip to content

Commit ab8815b

Browse files
committed
Merge branch 'dev' into init-electra
2 parents e64afbc + 93dba67 commit ab8815b

File tree

9 files changed

+407
-90
lines changed

9 files changed

+407
-90
lines changed

specs/_features/eip7251/beacon-chain.md

Lines changed: 200 additions & 39 deletions
Large diffs are not rendered by default.

specs/_features/eip7251/fork.md

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -129,11 +129,9 @@ def upgrade_to_eip7251(pre: deneb.BeaconState) -> BeaconState:
129129
)
130130

131131
# Ensure early adopters of compounding credentials go through the activation churn
132-
queue_excess_active_balance(post)
133132
for index, validator in enumerate(post.validators):
134133
if has_compounding_withdrawal_credential(validator):
135-
queue_excess_active_balance(post, index)
134+
queue_excess_active_balance(post, ValidatorIndex(index))
136135

137136
return post
138137
```
139-
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
# EIP-7251 -- Honest Validator
2+
3+
## Table of contents
4+
5+
<!-- TOC -->
6+
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
7+
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
8+
9+
- [Introduction](#introduction)
10+
- [Prerequisites](#prerequisites)
11+
- [Beacon chain responsibilities](#beacon-chain-responsibilities)
12+
- [Block and sidecar proposal](#block-and-sidecar-proposal)
13+
- [Constructing the `BeaconBlockBody`](#constructing-the-beaconblockbody)
14+
- [ExecutionPayload](#executionpayload)
15+
16+
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
17+
<!-- /TOC -->
18+
19+
## Introduction
20+
21+
This document represents the changes to be made in the code of an "honest validator".
22+
23+
## Prerequisites
24+
25+
This document is an extension of the [Deneb -- Honest Validator](../deneb/validator.md) guide.
26+
All behaviors and definitions defined in this document, and documents it extends, carry over unless explicitly noted or overridden.
27+
28+
All terminology, constants, functions, and protocol mechanics defined in the updated [Beacon Chain doc of EIP-7251](./beacon-chain.md) are requisite for this document and used throughout.
29+
Please see related Beacon Chain doc before continuing and use them as a reference throughout.
30+
31+
## Beacon chain responsibilities
32+
33+
All validator responsibilities remain unchanged other than those noted below.
34+
35+
### Block and sidecar proposal
36+
37+
#### Constructing the `BeaconBlockBody`
38+
39+
##### ExecutionPayload
40+
41+
`prepare_execution_payload` is updated from the Deneb specs.
42+
43+
*Note*: In this section, `state` is the state of the slot for the block proposal _without_ the block yet applied.
44+
That is, `state` is the `previous_state` processed through any empty slots up to the assigned slot using `process_slots(previous_state, slot)`.
45+
46+
*Note*: The only change to `prepare_execution_payload` is the new definition of `get_expected_withdrawals`.
47+
48+
```python
49+
def prepare_execution_payload(state: BeaconState,
50+
safe_block_hash: Hash32,
51+
finalized_block_hash: Hash32,
52+
suggested_fee_recipient: ExecutionAddress,
53+
execution_engine: ExecutionEngine) -> Optional[PayloadId]:
54+
# Verify consistency of the parent hash with respect to the previous execution payload header
55+
parent_hash = state.latest_execution_payload_header.block_hash
56+
57+
# Set the forkchoice head and initiate the payload build process
58+
withdrawals, _ = get_expected_withdrawals(state) # [Modified in EIP-7251]
59+
60+
payload_attributes = PayloadAttributes(
61+
timestamp=compute_timestamp_at_slot(state, state.slot),
62+
prev_randao=get_randao_mix(state, get_current_epoch(state)),
63+
suggested_fee_recipient=suggested_fee_recipient,
64+
withdrawals=withdrawals,
65+
parent_beacon_block_root=hash_tree_root(state.latest_block_header),
66+
)
67+
return execution_engine.notify_forkchoice_updated(
68+
head_block_hash=parent_hash,
69+
safe_block_hash=safe_block_hash,
70+
finalized_block_hash=finalized_block_hash,
71+
payload_attributes=payload_attributes,
72+
)
73+
```

specs/electra/beacon-chain.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -260,7 +260,11 @@ def process_deposit_receipt(state: BeaconState, deposit_receipt: DepositReceipt)
260260
```python
261261
def process_execution_layer_exit(state: BeaconState, execution_layer_exit: ExecutionLayerExit) -> None:
262262
validator_pubkeys = [v.pubkey for v in state.validators]
263-
validator_index = ValidatorIndex(validator_pubkeys.index(execution_layer_exit.validator_pubkey))
263+
# Verify pubkey exists
264+
pubkey_to_exit = execution_layer_exit.validator_pubkey
265+
if pubkey_to_exit not in validator_pubkeys:
266+
return
267+
validator_index = ValidatorIndex(validator_pubkeys.index(pubkey_to_exit))
264268
validator = state.validators[validator_index]
265269

266270
# Verify withdrawal credentials

tests/core/pyspec/eth2spec/test/bellatrix/fork_choice/test_should_override_forkchoice_update.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
MINIMAL,
88
)
99
from eth2spec.test.helpers.attestations import (
10-
get_valid_attestation_at_slot,
10+
get_valid_attestations_at_slot,
1111
)
1212
from eth2spec.test.helpers.block import (
1313
build_empty_block_for_next_slot,
@@ -109,7 +109,7 @@ def test_should_override_forkchoice_update__true(spec, state):
109109
# Fill a slot with attestations to its parent
110110
block = build_empty_block_for_next_slot(spec, state)
111111
parent_block_slot = block.slot - 1
112-
block.body.attestations = get_valid_attestation_at_slot(
112+
block.body.attestations = get_valid_attestations_at_slot(
113113
state,
114114
spec,
115115
parent_block_slot,
@@ -135,7 +135,7 @@ def test_should_override_forkchoice_update__true(spec, state):
135135
# Add attestations to the parent block
136136
temp_state = state.copy()
137137
next_slot(spec, temp_state)
138-
attestations = get_valid_attestation_at_slot(
138+
attestations = get_valid_attestations_at_slot(
139139
temp_state,
140140
spec,
141141
slot_to_attest=temp_state.slot - 1,

tests/core/pyspec/eth2spec/test/helpers/attestations.py

Lines changed: 117 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,6 @@ def build_attestation_data(spec, state, slot, index, beacon_block_root=None, sha
8484
target=spec.Checkpoint(epoch=spec.compute_epoch_at_slot(slot), root=epoch_boundary_root),
8585
)
8686

87-
# if spec.fork == SHARDING # TODO: add extra data for shard voting
8887
return data
8988

9089

@@ -95,27 +94,21 @@ def get_valid_attestation(spec,
9594
filter_participant_set=None,
9695
beacon_block_root=None,
9796
signed=False):
98-
# If filter_participant_set filters everything, the attestation has 0 participants, and cannot be signed.
99-
# Thus strictly speaking invalid when no participant is added later.
97+
"""
98+
Return a valid attestation at `slot` and committee index `index`.
99+
100+
If filter_participant_set filters everything, the attestation has 0 participants, and cannot be signed.
101+
Thus strictly speaking invalid when no participant is added later.
102+
"""
100103
if slot is None:
101104
slot = state.slot
102105
if index is None:
103106
index = 0
104107

105108
attestation_data = build_attestation_data(spec, state, slot=slot, index=index, beacon_block_root=beacon_block_root)
106109

107-
beacon_committee = spec.get_beacon_committee(state, slot, index)
110+
attestation = spec.Attestation(data=attestation_data)
108111

109-
if is_post_eip7549(spec):
110-
# will fill aggregation_bits later
111-
attestation = spec.Attestation(data=attestation_data)
112-
else:
113-
committee_size = len(beacon_committee)
114-
aggregation_bits = Bitlist[spec.MAX_VALIDATORS_PER_COMMITTEE](*([0] * committee_size))
115-
attestation = spec.Attestation(
116-
aggregation_bits=aggregation_bits,
117-
data=attestation_data,
118-
)
119112
# fill the attestation with (optionally filtered) participants, and optionally sign it
120113
fill_aggregate_attestation(spec, state, attestation, signed=signed,
121114
filter_participant_set=filter_participant_set, committee_index=index)
@@ -132,7 +125,7 @@ def sign_aggregate_attestation(spec, state, attestation_data, participants: List
132125
spec,
133126
state,
134127
attestation_data,
135-
privkey
128+
privkey,
136129
)
137130
)
138131
return bls.Aggregate(signatures)
@@ -180,11 +173,16 @@ def fill_aggregate_attestation(spec, state, attestation, committee_index, signed
180173
if filter_participant_set is not None:
181174
participants = filter_participant_set(participants)
182175

176+
# initialize `aggregation_bits`
183177
if is_post_eip7549(spec):
184-
attestation.committee_bits = spec.Bitvector[spec.MAX_COMMITTEES_PER_SLOT]()
185178
attestation.committee_bits[committee_index] = True
186179
attestation.aggregation_bits = get_empty_eip7549_aggregation_bits(
187180
spec, state, attestation.committee_bits, attestation.data.slot)
181+
else:
182+
committee_size = len(beacon_committee)
183+
attestation.aggregation_bits = Bitlist[spec.MAX_VALIDATORS_PER_COMMITTEE](*([0] * committee_size))
184+
185+
# fill in the `aggregation_bits`
188186
for i in range(len(beacon_committee)):
189187
if is_post_eip7549(spec):
190188
offset = get_eip7549_aggregation_bits_offset(
@@ -205,15 +203,17 @@ def add_attestations_to_state(spec, state, attestations, slot):
205203
spec.process_attestation(state, attestation)
206204

207205

208-
def get_valid_attestation_at_slot(state, spec, slot_to_attest, participation_fn=None, beacon_block_root=None):
206+
def get_valid_attestations_at_slot(state, spec, slot_to_attest, participation_fn=None, beacon_block_root=None):
207+
"""
208+
Return attestations at slot `slot_to_attest`.
209+
"""
209210
committees_per_slot = spec.get_committee_count_per_slot(state, spec.compute_epoch_at_slot(slot_to_attest))
210211
for index in range(committees_per_slot):
211212
def participants_filter(comm):
212213
if participation_fn is None:
213214
return comm
214215
else:
215216
return participation_fn(state.slot, index, comm)
216-
# if spec.fork == SHARDING: TODO: add shard data to attestation, include shard headers in block
217217
yield get_valid_attestation(
218218
spec,
219219
state,
@@ -225,6 +225,78 @@ def participants_filter(comm):
225225
)
226226

227227

228+
def _get_aggregate_committee_indices(spec, attestations):
229+
"""
230+
Aggregate all unique committee indices from the given attestations.
231+
"""
232+
all_committee_indices = set()
233+
for attestation in attestations:
234+
committee_indices = spec.get_committee_indices(attestation.committee_bits)
235+
assert len(committee_indices) == 1
236+
all_committee_indices.add(committee_indices[0])
237+
238+
return all_committee_indices
239+
240+
241+
def _aggregate_aggregation_bits_and_signatures(spec, state, slot, aggregate, attestations):
242+
"""
243+
Aggregate the aggregation bits and signatures from the attestations,
244+
incorporating the calculation of aggregation bits offset directly.
245+
"""
246+
# initialize aggregation bits for the aggregate attestation
247+
aggregate.aggregation_bits = get_empty_eip7549_aggregation_bits(
248+
spec, state, aggregate.committee_bits, slot)
249+
250+
signatures = []
251+
252+
offset = 0
253+
attestations = sorted(attestations, key=lambda att: spec.get_committee_indices(att.committee_bits)[0])
254+
for attestation in attestations:
255+
# retrieve the single committee index for the attestation.
256+
committee_index = spec.get_committee_indices(attestation.committee_bits)[0]
257+
258+
# update the aggregate's aggregation bits based on each attestation.
259+
for i, bit in enumerate(attestation.aggregation_bits):
260+
aggregate.aggregation_bits[offset + i] = bit
261+
262+
# collect signatures for aggregation.
263+
signatures.append(attestation.signature)
264+
265+
# update offset
266+
committee = spec.get_beacon_committee(state, slot, committee_index)
267+
offset += len(committee)
268+
269+
# aggregate signatures from all attestations.
270+
aggregate.signature = bls.Aggregate(signatures)
271+
272+
273+
def get_valid_attestation_at_slot(state, spec, slot_to_attest, participation_fn=None, beacon_block_root=None):
274+
"""
275+
Return the aggregate attestation post EIP-7549.
276+
Note: this EIP supports dense packing of on-chain aggregates so we can just return a single `Attestation`.
277+
"""
278+
assert is_post_eip7549(spec)
279+
attestations = list(get_valid_attestations_at_slot(
280+
state, spec, slot_to_attest,
281+
participation_fn=participation_fn,
282+
beacon_block_root=beacon_block_root,
283+
))
284+
if not attestations:
285+
return None
286+
287+
# initialize the aggregate attestation.
288+
aggregate = spec.Attestation(data=attestations[0].data)
289+
290+
# fill in committee_bits
291+
all_committee_indices = _get_aggregate_committee_indices(spec, attestations)
292+
for committee_index in all_committee_indices:
293+
aggregate.committee_bits[committee_index] = True
294+
295+
_aggregate_aggregation_bits_and_signatures(spec, state, slot_to_attest, aggregate, attestations)
296+
297+
return aggregate
298+
299+
228300
def next_slots_with_attestations(spec,
229301
state,
230302
slot_count,
@@ -249,6 +321,26 @@ def next_slots_with_attestations(spec,
249321
return state, signed_blocks, post_state
250322

251323

324+
def _add_valid_attestations(spec, state, block, slot_to_attest, participation_fn=None):
325+
if is_post_eip7549(spec):
326+
attestation = get_valid_attestation_at_slot(
327+
state,
328+
spec,
329+
slot_to_attest,
330+
participation_fn=participation_fn,
331+
)
332+
block.body.attestations.append(attestation)
333+
else:
334+
attestations = get_valid_attestations_at_slot(
335+
state,
336+
spec,
337+
slot_to_attest,
338+
participation_fn=participation_fn,
339+
)
340+
for attestation in attestations:
341+
block.body.attestations.append(attestation)
342+
343+
252344
def next_epoch_with_attestations(spec,
253345
state,
254346
fill_cur_epoch,
@@ -281,24 +373,10 @@ def state_transition_with_full_block(spec,
281373
if fill_cur_epoch and state.slot >= spec.MIN_ATTESTATION_INCLUSION_DELAY:
282374
slot_to_attest = state.slot - spec.MIN_ATTESTATION_INCLUSION_DELAY + 1
283375
if slot_to_attest >= spec.compute_start_slot_at_epoch(spec.get_current_epoch(state)):
284-
attestations = get_valid_attestation_at_slot(
285-
state,
286-
spec,
287-
slot_to_attest,
288-
participation_fn=participation_fn
289-
)
290-
for attestation in attestations:
291-
block.body.attestations.append(attestation)
376+
_add_valid_attestations(spec, state, block, slot_to_attest, participation_fn=participation_fn)
292377
if fill_prev_epoch and state.slot >= spec.SLOTS_PER_EPOCH:
293378
slot_to_attest = state.slot - spec.SLOTS_PER_EPOCH + 1
294-
attestations = get_valid_attestation_at_slot(
295-
state,
296-
spec,
297-
slot_to_attest,
298-
participation_fn=participation_fn
299-
)
300-
for attestation in attestations:
301-
block.body.attestations.append(attestation)
379+
_add_valid_attestations(spec, state, block, slot_to_attest, participation_fn=participation_fn)
302380
if sync_aggregate is not None:
303381
block.body.sync_aggregate = sync_aggregate
304382

@@ -319,7 +397,7 @@ def state_transition_with_full_attestations_block(spec, state, fill_cur_epoch, f
319397
slots = state.slot % spec.SLOTS_PER_EPOCH
320398
for slot_offset in range(slots):
321399
target_slot = state.slot - slot_offset
322-
attestations += get_valid_attestation_at_slot(
400+
attestations += get_valid_attestations_at_slot(
323401
state,
324402
spec,
325403
target_slot,
@@ -330,7 +408,7 @@ def state_transition_with_full_attestations_block(spec, state, fill_cur_epoch, f
330408
slots = spec.SLOTS_PER_EPOCH - state.slot % spec.SLOTS_PER_EPOCH
331409
for slot_offset in range(1, slots):
332410
target_slot = state.slot - (state.slot % spec.SLOTS_PER_EPOCH) - slot_offset
333-
attestations += get_valid_attestation_at_slot(
411+
attestations += get_valid_attestations_at_slot(
334412
state,
335413
spec,
336414
target_slot,
@@ -423,6 +501,9 @@ def get_empty_eip7549_aggregation_bits(spec, state, committee_bits, slot):
423501

424502

425503
def get_eip7549_aggregation_bits_offset(spec, state, slot, committee_bits, committee_index):
504+
"""
505+
Calculate the offset for the aggregation bits based on the committee index.
506+
"""
426507
committee_indices = spec.get_committee_indices(committee_bits)
427508
assert committee_index in committee_indices
428509
offset = 0

tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_registry_updates.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ def test_activation_queue_sorting(spec, state):
114114
assert state.validators[mock_activations - 2].activation_epoch == spec.FAR_FUTURE_EPOCH
115115
# the one at churn_limit did not make it, it was out-prioritized
116116
assert state.validators[churn_limit].activation_epoch == spec.FAR_FUTURE_EPOCH
117-
# but the the one in front of the above did
117+
# but the one in front of the above did
118118
assert state.validators[churn_limit - 1].activation_epoch != spec.FAR_FUTURE_EPOCH
119119

120120

0 commit comments

Comments
 (0)