Skip to content

Commit

Permalink
Merge pull request #3649 from ethereum/eip7549-aggregate
Browse files Browse the repository at this point in the history
EIP-7549: Add EIP7549 aggregation logic in testing tool
  • Loading branch information
ralexstokes authored Apr 5, 2024
2 parents 78da657 + a56bd85 commit f8cbd8d
Show file tree
Hide file tree
Showing 4 changed files with 127 additions and 46 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
MINIMAL,
)
from eth2spec.test.helpers.attestations import (
get_valid_attestation_at_slot,
get_valid_attestations_at_slot,
)
from eth2spec.test.helpers.block import (
build_empty_block_for_next_slot,
Expand Down Expand Up @@ -109,7 +109,7 @@ def test_should_override_forkchoice_update__true(spec, state):
# Fill a slot with attestations to its parent
block = build_empty_block_for_next_slot(spec, state)
parent_block_slot = block.slot - 1
block.body.attestations = get_valid_attestation_at_slot(
block.body.attestations = get_valid_attestations_at_slot(
state,
spec,
parent_block_slot,
Expand All @@ -135,7 +135,7 @@ def test_should_override_forkchoice_update__true(spec, state):
# Add attestations to the parent block
temp_state = state.copy()
next_slot(spec, temp_state)
attestations = get_valid_attestation_at_slot(
attestations = get_valid_attestations_at_slot(
temp_state,
spec,
slot_to_attest=temp_state.slot - 1,
Expand Down
153 changes: 117 additions & 36 deletions tests/core/pyspec/eth2spec/test/helpers/attestations.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,6 @@ def build_attestation_data(spec, state, slot, index, beacon_block_root=None, sha
target=spec.Checkpoint(epoch=spec.compute_epoch_at_slot(slot), root=epoch_boundary_root),
)

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


Expand All @@ -95,27 +94,21 @@ def get_valid_attestation(spec,
filter_participant_set=None,
beacon_block_root=None,
signed=False):
# If filter_participant_set filters everything, the attestation has 0 participants, and cannot be signed.
# Thus strictly speaking invalid when no participant is added later.
"""
Return a valid attestation at `slot` and committee index `index`.
If filter_participant_set filters everything, the attestation has 0 participants, and cannot be signed.
Thus strictly speaking invalid when no participant is added later.
"""
if slot is None:
slot = state.slot
if index is None:
index = 0

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

beacon_committee = spec.get_beacon_committee(state, slot, index)
attestation = spec.Attestation(data=attestation_data)

if is_post_eip7549(spec):
# will fill aggregation_bits later
attestation = spec.Attestation(data=attestation_data)
else:
committee_size = len(beacon_committee)
aggregation_bits = Bitlist[spec.MAX_VALIDATORS_PER_COMMITTEE](*([0] * committee_size))
attestation = spec.Attestation(
aggregation_bits=aggregation_bits,
data=attestation_data,
)
# fill the attestation with (optionally filtered) participants, and optionally sign it
fill_aggregate_attestation(spec, state, attestation, signed=signed,
filter_participant_set=filter_participant_set, committee_index=index)
Expand All @@ -132,7 +125,7 @@ def sign_aggregate_attestation(spec, state, attestation_data, participants: List
spec,
state,
attestation_data,
privkey
privkey,
)
)
return bls.Aggregate(signatures)
Expand Down Expand Up @@ -180,11 +173,16 @@ def fill_aggregate_attestation(spec, state, attestation, committee_index, signed
if filter_participant_set is not None:
participants = filter_participant_set(participants)

# initialize `aggregation_bits`
if is_post_eip7549(spec):
attestation.committee_bits = spec.Bitvector[spec.MAX_COMMITTEES_PER_SLOT]()
attestation.committee_bits[committee_index] = True
attestation.aggregation_bits = get_empty_eip7549_aggregation_bits(
spec, state, attestation.committee_bits, attestation.data.slot)
else:
committee_size = len(beacon_committee)
attestation.aggregation_bits = Bitlist[spec.MAX_VALIDATORS_PER_COMMITTEE](*([0] * committee_size))

# fill in the `aggregation_bits`
for i in range(len(beacon_committee)):
if is_post_eip7549(spec):
offset = get_eip7549_aggregation_bits_offset(
Expand All @@ -205,15 +203,17 @@ def add_attestations_to_state(spec, state, attestations, slot):
spec.process_attestation(state, attestation)


def get_valid_attestation_at_slot(state, spec, slot_to_attest, participation_fn=None, beacon_block_root=None):
def get_valid_attestations_at_slot(state, spec, slot_to_attest, participation_fn=None, beacon_block_root=None):
"""
Return attestations at slot `slot_to_attest`.
"""
committees_per_slot = spec.get_committee_count_per_slot(state, spec.compute_epoch_at_slot(slot_to_attest))
for index in range(committees_per_slot):
def participants_filter(comm):
if participation_fn is None:
return comm
else:
return participation_fn(state.slot, index, comm)
# if spec.fork == SHARDING: TODO: add shard data to attestation, include shard headers in block
yield get_valid_attestation(
spec,
state,
Expand All @@ -225,6 +225,78 @@ def participants_filter(comm):
)


def _get_aggregate_committee_indices(spec, attestations):
"""
Aggregate all unique committee indices from the given attestations.
"""
all_committee_indices = set()
for attestation in attestations:
committee_indices = spec.get_committee_indices(attestation.committee_bits)
assert len(committee_indices) == 1
all_committee_indices.add(committee_indices[0])

return all_committee_indices


def _aggregate_aggregation_bits_and_signatures(spec, state, slot, aggregate, attestations):
"""
Aggregate the aggregation bits and signatures from the attestations,
incorporating the calculation of aggregation bits offset directly.
"""
# initialize aggregation bits for the aggregate attestation
aggregate.aggregation_bits = get_empty_eip7549_aggregation_bits(
spec, state, aggregate.committee_bits, slot)

signatures = []

offset = 0
attestations = sorted(attestations, key=lambda att: spec.get_committee_indices(att.committee_bits)[0])
for attestation in attestations:
# retrieve the single committee index for the attestation.
committee_index = spec.get_committee_indices(attestation.committee_bits)[0]

# update the aggregate's aggregation bits based on each attestation.
for i, bit in enumerate(attestation.aggregation_bits):
aggregate.aggregation_bits[offset + i] = bit

# collect signatures for aggregation.
signatures.append(attestation.signature)

# update offset
committee = spec.get_beacon_committee(state, slot, committee_index)
offset += len(committee)

# aggregate signatures from all attestations.
aggregate.signature = bls.Aggregate(signatures)


def get_valid_attestation_at_slot(state, spec, slot_to_attest, participation_fn=None, beacon_block_root=None):
"""
Return the aggregate attestation post EIP-7549.
Note: this EIP supports dense packing of on-chain aggregates so we can just return a single `Attestation`.
"""
assert is_post_eip7549(spec)
attestations = list(get_valid_attestations_at_slot(
state, spec, slot_to_attest,
participation_fn=participation_fn,
beacon_block_root=beacon_block_root,
))
if not attestations:
return None

# initialize the aggregate attestation.
aggregate = spec.Attestation(data=attestations[0].data)

# fill in committee_bits
all_committee_indices = _get_aggregate_committee_indices(spec, attestations)
for committee_index in all_committee_indices:
aggregate.committee_bits[committee_index] = True

_aggregate_aggregation_bits_and_signatures(spec, state, slot_to_attest, aggregate, attestations)

return aggregate


def next_slots_with_attestations(spec,
state,
slot_count,
Expand All @@ -249,6 +321,26 @@ def next_slots_with_attestations(spec,
return state, signed_blocks, post_state


def _add_valid_attestations(spec, state, block, slot_to_attest, participation_fn=None):
if is_post_eip7549(spec):
attestation = get_valid_attestation_at_slot(
state,
spec,
slot_to_attest,
participation_fn=participation_fn,
)
block.body.attestations.append(attestation)
else:
attestations = get_valid_attestations_at_slot(
state,
spec,
slot_to_attest,
participation_fn=participation_fn,
)
for attestation in attestations:
block.body.attestations.append(attestation)


def next_epoch_with_attestations(spec,
state,
fill_cur_epoch,
Expand Down Expand Up @@ -281,24 +373,10 @@ def state_transition_with_full_block(spec,
if fill_cur_epoch and state.slot >= spec.MIN_ATTESTATION_INCLUSION_DELAY:
slot_to_attest = state.slot - spec.MIN_ATTESTATION_INCLUSION_DELAY + 1
if slot_to_attest >= spec.compute_start_slot_at_epoch(spec.get_current_epoch(state)):
attestations = get_valid_attestation_at_slot(
state,
spec,
slot_to_attest,
participation_fn=participation_fn
)
for attestation in attestations:
block.body.attestations.append(attestation)
_add_valid_attestations(spec, state, block, slot_to_attest, participation_fn=participation_fn)
if fill_prev_epoch and state.slot >= spec.SLOTS_PER_EPOCH:
slot_to_attest = state.slot - spec.SLOTS_PER_EPOCH + 1
attestations = get_valid_attestation_at_slot(
state,
spec,
slot_to_attest,
participation_fn=participation_fn
)
for attestation in attestations:
block.body.attestations.append(attestation)
_add_valid_attestations(spec, state, block, slot_to_attest, participation_fn=participation_fn)
if sync_aggregate is not None:
block.body.sync_aggregate = sync_aggregate

Expand All @@ -319,7 +397,7 @@ def state_transition_with_full_attestations_block(spec, state, fill_cur_epoch, f
slots = state.slot % spec.SLOTS_PER_EPOCH
for slot_offset in range(slots):
target_slot = state.slot - slot_offset
attestations += get_valid_attestation_at_slot(
attestations += get_valid_attestations_at_slot(
state,
spec,
target_slot,
Expand All @@ -330,7 +408,7 @@ def state_transition_with_full_attestations_block(spec, state, fill_cur_epoch, f
slots = spec.SLOTS_PER_EPOCH - state.slot % spec.SLOTS_PER_EPOCH
for slot_offset in range(1, slots):
target_slot = state.slot - (state.slot % spec.SLOTS_PER_EPOCH) - slot_offset
attestations += get_valid_attestation_at_slot(
attestations += get_valid_attestations_at_slot(
state,
spec,
target_slot,
Expand Down Expand Up @@ -423,6 +501,9 @@ def get_empty_eip7549_aggregation_bits(spec, state, committee_bits, slot):


def get_eip7549_aggregation_bits_offset(spec, state, slot, committee_bits, committee_index):
"""
Calculate the offset for the aggregation bits based on the committee index.
"""
committee_indices = spec.get_committee_indices(committee_bits)
assert committee_index in committee_indices
offset = 0
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
with_altair_and_later,
)
from eth2spec.test.helpers.attestations import (
get_valid_attestation_at_slot,
get_valid_attestations_at_slot,
)
from eth2spec.test.helpers.block import (
build_empty_block_for_next_slot,
Expand Down Expand Up @@ -101,7 +101,7 @@ def test_basic_is_parent_root(spec, state):
# Fill a slot with attestations to its parent
block = build_empty_block_for_next_slot(spec, state)
parent_block_slot = block.slot - 1
block.body.attestations = get_valid_attestation_at_slot(
block.body.attestations = get_valid_attestations_at_slot(
state,
spec,
parent_block_slot,
Expand All @@ -128,7 +128,7 @@ def test_basic_is_parent_root(spec, state):
slot = state.slot

# Add attestations to the parent block
attestations = get_valid_attestation_at_slot(
attestations = get_valid_attestations_at_slot(
state,
spec,
slot_to_attest=slot - 1,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from eth2spec.test.helpers.attestations import (
state_transition_with_full_block,
get_valid_attestation,
get_valid_attestation_at_slot,
get_valid_attestations_at_slot,
)
from eth2spec.test.helpers.block import (
build_empty_block,
Expand Down Expand Up @@ -218,7 +218,7 @@ def _run_delayed_justification(spec, state, attemped_reorg, is_justifying_previo
# add attestations of y
temp_state = state.copy()
next_slot(spec, temp_state)
attestations_for_y = list(get_valid_attestation_at_slot(temp_state, spec, signed_block_y.message.slot))
attestations_for_y = list(get_valid_attestations_at_slot(temp_state, spec, signed_block_y.message.slot))
current_time = temp_state.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time
on_tick_and_append_step(spec, store, current_time, test_steps)
yield from add_attestations(spec, store, attestations_for_y, test_steps)
Expand Down Expand Up @@ -345,10 +345,10 @@ def _run_include_votes_of_another_empty_chain(spec, state, enough_ffg, is_justif
# create 2/3 votes for the empty chain
attestations_for_y = []
# target_is_current = not is_justifying_previous_epoch
attestations = list(get_valid_attestation_at_slot(state, spec, state_a.slot))
attestations = list(get_valid_attestations_at_slot(state, spec, state_a.slot))
attestations_for_y.append(attestations)
for state in states_of_empty_chain:
attestations = list(get_valid_attestation_at_slot(state, spec, state.slot))
attestations = list(get_valid_attestations_at_slot(state, spec, state.slot))
attestations_for_y.append(attestations)

state = state_a.copy()
Expand Down

0 comments on commit f8cbd8d

Please sign in to comment.