From 5f1b903bdf4db1a7b6e5e037667522c213620af2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Kapka?= Date: Tue, 7 May 2024 22:48:23 +0900 Subject: [PATCH] EIP-7549 beacon spec (#13946) * EIP-7549 beacon spec * reviews * change signature of AttestingIndices --- beacon-chain/blockchain/process_block.go | 2 +- beacon-chain/core/altair/attestation.go | 2 +- beacon-chain/core/altair/attestation_test.go | 4 +- beacon-chain/core/altair/upgrade.go | 2 +- beacon-chain/core/altair/upgrade_test.go | 2 +- beacon-chain/core/blocks/attestation.go | 59 ++++++--- beacon-chain/core/blocks/attestation_test.go | 81 +++++++++++- beacon-chain/core/epoch/epoch_processing.go | 2 +- .../core/epoch/precompute/attestation.go | 2 +- .../core/epoch/precompute/attestation_test.go | 4 +- beacon-chain/core/helpers/beacon_committee.go | 15 +++ .../core/helpers/beacon_committee_test.go | 9 ++ .../core/transition/transition_test.go | 2 +- beacon-chain/monitor/process_attestation.go | 2 +- .../rpc/prysm/v1alpha1/debug/block.go | 2 +- .../v1alpha1/validator/aggregator_test.go | 4 +- .../prysm/v1alpha1/validator/proposer_test.go | 2 +- .../sync/pending_attestations_queue_test.go | 6 +- .../sync/validate_aggregate_proof_test.go | 10 +- proto/prysm/v1alpha1/attestation/BUILD.bazel | 2 + .../v1alpha1/attestation/attestation_utils.go | 116 ++++++++++++++---- .../attestation/attestation_utils_test.go | 66 ++++++++-- 22 files changed, 320 insertions(+), 76 deletions(-) diff --git a/beacon-chain/blockchain/process_block.go b/beacon-chain/blockchain/process_block.go index 3f8c94cced..5ced8b1623 100644 --- a/beacon-chain/blockchain/process_block.go +++ b/beacon-chain/blockchain/process_block.go @@ -370,7 +370,7 @@ func (s *Service) handleBlockAttestations(ctx context.Context, blk interfaces.Re if err != nil { return err } - indices, err := attestation.AttestingIndices(a.GetAggregationBits(), committee) + indices, err := attestation.AttestingIndices(a, committee) if err != nil { return err } diff --git a/beacon-chain/core/altair/attestation.go b/beacon-chain/core/altair/attestation.go index 8b123c91ca..336edf57a7 100644 --- a/beacon-chain/core/altair/attestation.go +++ b/beacon-chain/core/altair/attestation.go @@ -70,7 +70,7 @@ func ProcessAttestationNoVerifySignature( if err != nil { return nil, err } - indices, err := attestation.AttestingIndices(att.GetAggregationBits(), committee) + indices, err := attestation.AttestingIndices(att, committee) if err != nil { return nil, err } diff --git a/beacon-chain/core/altair/attestation_test.go b/beacon-chain/core/altair/attestation_test.go index d548c5c4e7..2665edac85 100644 --- a/beacon-chain/core/altair/attestation_test.go +++ b/beacon-chain/core/altair/attestation_test.go @@ -215,7 +215,7 @@ func TestProcessAttestations_OK(t *testing.T) { committee, err := helpers.BeaconCommitteeFromState(context.Background(), beaconState, att.Data.Slot, att.Data.CommitteeIndex) require.NoError(t, err) - attestingIndices, err := attestation.AttestingIndices(att.AggregationBits, committee) + attestingIndices, err := attestation.AttestingIndices(att, committee) require.NoError(t, err) sigs := make([]bls.Signature, len(attestingIndices)) for i, indice := range attestingIndices { @@ -273,7 +273,7 @@ func TestProcessAttestationNoVerify_SourceTargetHead(t *testing.T) { committee, err := helpers.BeaconCommitteeFromState(context.Background(), beaconState, att.Data.Slot, att.Data.CommitteeIndex) require.NoError(t, err) - indices, err := attestation.AttestingIndices(att.AggregationBits, committee) + indices, err := attestation.AttestingIndices(att, committee) require.NoError(t, err) for _, index := range indices { has, err := altair.HasValidatorFlag(p[index], params.BeaconConfig().TimelyHeadFlagIndex) diff --git a/beacon-chain/core/altair/upgrade.go b/beacon-chain/core/altair/upgrade.go index 5557c5ce40..3b3e33ce89 100644 --- a/beacon-chain/core/altair/upgrade.go +++ b/beacon-chain/core/altair/upgrade.go @@ -158,7 +158,7 @@ func TranslateParticipation(ctx context.Context, state state.BeaconState, atts [ if err != nil { return nil, err } - indices, err := attestation.AttestingIndices(att.AggregationBits, committee) + indices, err := attestation.AttestingIndices(att, committee) if err != nil { return nil, err } diff --git a/beacon-chain/core/altair/upgrade_test.go b/beacon-chain/core/altair/upgrade_test.go index 3860edfc5b..12060a6f74 100644 --- a/beacon-chain/core/altair/upgrade_test.go +++ b/beacon-chain/core/altair/upgrade_test.go @@ -55,7 +55,7 @@ func TestTranslateParticipation(t *testing.T) { committee, err := helpers.BeaconCommitteeFromState(ctx, s, pendingAtts[0].Data.Slot, pendingAtts[0].Data.CommitteeIndex) require.NoError(t, err) - indices, err := attestation.AttestingIndices(pendingAtts[0].AggregationBits, committee) + indices, err := attestation.AttestingIndices(pendingAtts[0], committee) require.NoError(t, err) for _, index := range indices { has, err := altair.HasValidatorFlag(participation[index], params.BeaconConfig().TimelySourceFlagIndex) diff --git a/beacon-chain/core/blocks/attestation.go b/beacon-chain/core/blocks/attestation.go index a704bf2605..2afafcb6a1 100644 --- a/beacon-chain/core/blocks/attestation.go +++ b/beacon-chain/core/blocks/attestation.go @@ -107,22 +107,53 @@ func VerifyAttestationNoVerifySignature( return err } c := helpers.SlotCommitteeCount(activeValidatorCount) - if uint64(att.GetData().CommitteeIndex) >= c { - return fmt.Errorf("committee index %d >= committee count %d", att.GetData().CommitteeIndex, c) - } - if err := helpers.VerifyAttestationBitfieldLengths(ctx, beaconState, att); err != nil { - return errors.Wrap(err, "could not verify attestation bitfields") - } + var indexedAtt ethpb.IndexedAtt - // Verify attesting indices are correct. - committee, err := helpers.BeaconCommitteeFromState(ctx, beaconState, att.GetData().Slot, att.GetData().CommitteeIndex) - if err != nil { - return err - } - indexedAtt, err := attestation.ConvertToIndexed(ctx, att, committee) - if err != nil { - return err + if att.Version() < version.Electra { + if uint64(att.GetData().CommitteeIndex) >= c { + return fmt.Errorf("committee index %d >= committee count %d", att.GetData().CommitteeIndex, c) + } + + if err = helpers.VerifyAttestationBitfieldLengths(ctx, beaconState, att); err != nil { + return errors.Wrap(err, "could not verify attestation bitfields") + } + + // Verify attesting indices are correct. + committee, err := helpers.BeaconCommitteeFromState(ctx, beaconState, att.GetData().Slot, att.GetData().CommitteeIndex) + if err != nil { + return err + } + indexedAtt, err = attestation.ConvertToIndexed(ctx, att, committee) + if err != nil { + return err + } + } else { + if att.GetData().CommitteeIndex != 0 { + return errors.New("committee index must be 0 post-Electra") + } + + committeeIndices := att.GetCommitteeBitsVal().BitIndices() + committees := make([][]primitives.ValidatorIndex, len(committeeIndices)) + participantsCount := 0 + var err error + for i, ci := range committeeIndices { + if uint64(ci) >= c { + return fmt.Errorf("committee index %d >= committee count %d", ci, c) + } + committees[i], err = helpers.BeaconCommitteeFromState(ctx, beaconState, att.GetData().Slot, primitives.CommitteeIndex(ci)) + if err != nil { + return err + } + participantsCount += len(committees[i]) + } + if att.GetAggregationBits().Len() != uint64(participantsCount) { + return fmt.Errorf("aggregation bits count %d is different than participant count %d", att.GetAggregationBits().Len(), participantsCount) + } + indexedAtt, err = attestation.ConvertToIndexed(ctx, att, committees...) + if err != nil { + return err + } } return attestation.IsValidAttestationIndices(ctx, indexedAtt) diff --git a/beacon-chain/core/blocks/attestation_test.go b/beacon-chain/core/blocks/attestation_test.go index 3565e3ceb2..10ca47b178 100644 --- a/beacon-chain/core/blocks/attestation_test.go +++ b/beacon-chain/core/blocks/attestation_test.go @@ -45,7 +45,7 @@ func TestProcessAggregatedAttestation_OverlappingBits(t *testing.T) { committee, err := helpers.BeaconCommitteeFromState(context.Background(), beaconState, att1.Data.Slot, att1.Data.CommitteeIndex) require.NoError(t, err) - attestingIndices1, err := attestation.AttestingIndices(att1.AggregationBits, committee) + attestingIndices1, err := attestation.AttestingIndices(att1, committee) require.NoError(t, err) sigs := make([]bls.Signature, len(attestingIndices1)) for i, indice := range attestingIndices1 { @@ -67,7 +67,7 @@ func TestProcessAggregatedAttestation_OverlappingBits(t *testing.T) { committee, err = helpers.BeaconCommitteeFromState(context.Background(), beaconState, att2.Data.Slot, att2.Data.CommitteeIndex) require.NoError(t, err) - attestingIndices2, err := attestation.AttestingIndices(att2.AggregationBits, committee) + attestingIndices2, err := attestation.AttestingIndices(att2, committee) require.NoError(t, err) sigs = make([]bls.Signature, len(attestingIndices2)) for i, indice := range attestingIndices2 { @@ -222,6 +222,83 @@ func TestVerifyAttestationNoVerifySignature_BadAttIdx(t *testing.T) { require.ErrorContains(t, "committee index 100 >= committee count 1", err) } +func TestVerifyAttestationNoVerifySignature_Electra(t *testing.T) { + var mockRoot [32]byte + copy(mockRoot[:], "hello-world") + var zeroSig [fieldparams.BLSSignatureLength]byte + + beaconState, _ := util.DeterministicGenesisState(t, 100) + err := beaconState.SetSlot(beaconState.Slot() + params.BeaconConfig().MinAttestationInclusionDelay) + require.NoError(t, err) + ckp := beaconState.CurrentJustifiedCheckpoint() + copy(ckp.Root, "hello-world") + require.NoError(t, beaconState.SetCurrentJustifiedCheckpoint(ckp)) + require.NoError(t, beaconState.AppendCurrentEpochAttestations(ðpb.PendingAttestation{})) + + t.Run("ok", func(t *testing.T) { + aggBits := bitfield.NewBitlist(3) + aggBits.SetBitAt(1, true) + committeeBits := bitfield.NewBitvector64() + committeeBits.SetBitAt(0, true) + att := ðpb.AttestationElectra{ + Data: ðpb.AttestationData{ + Source: ðpb.Checkpoint{Epoch: 0, Root: mockRoot[:]}, + Target: ðpb.Checkpoint{Epoch: 0, Root: make([]byte, 32)}, + }, + AggregationBits: aggBits, + CommitteeBits: committeeBits, + } + att.Signature = zeroSig[:] + err = blocks.VerifyAttestationNoVerifySignature(context.TODO(), beaconState, att) + assert.NoError(t, err) + }) + t.Run("non-zero committee index", func(t *testing.T) { + att := ðpb.AttestationElectra{ + Data: ðpb.AttestationData{ + Source: ðpb.Checkpoint{Epoch: 0, Root: mockRoot[:]}, + Target: ðpb.Checkpoint{Epoch: 0, Root: make([]byte, 32)}, + CommitteeIndex: 1, + }, + AggregationBits: bitfield.NewBitlist(1), + CommitteeBits: bitfield.NewBitvector64(), + } + err = blocks.VerifyAttestationNoVerifySignature(context.TODO(), beaconState, att) + assert.ErrorContains(t, "committee index must be 0 post-Electra", err) + }) + t.Run("index of committee too big", func(t *testing.T) { + aggBits := bitfield.NewBitlist(3) + committeeBits := bitfield.NewBitvector64() + committeeBits.SetBitAt(63, true) + att := ðpb.AttestationElectra{ + Data: ðpb.AttestationData{ + Source: ðpb.Checkpoint{Epoch: 0, Root: mockRoot[:]}, + Target: ðpb.Checkpoint{Epoch: 0, Root: make([]byte, 32)}, + }, + AggregationBits: aggBits, + CommitteeBits: committeeBits, + } + att.Signature = zeroSig[:] + err = blocks.VerifyAttestationNoVerifySignature(context.TODO(), beaconState, att) + assert.ErrorContains(t, "committee index 63 >= committee count 1", err) + }) + t.Run("wrong aggregation bits count", func(t *testing.T) { + aggBits := bitfield.NewBitlist(123) + committeeBits := bitfield.NewBitvector64() + committeeBits.SetBitAt(0, true) + att := ðpb.AttestationElectra{ + Data: ðpb.AttestationData{ + Source: ðpb.Checkpoint{Epoch: 0, Root: mockRoot[:]}, + Target: ðpb.Checkpoint{Epoch: 0, Root: make([]byte, 32)}, + }, + AggregationBits: aggBits, + CommitteeBits: committeeBits, + } + att.Signature = zeroSig[:] + err = blocks.VerifyAttestationNoVerifySignature(context.TODO(), beaconState, att) + assert.ErrorContains(t, "aggregation bits count 123 is different than participant count 3", err) + }) +} + func TestConvertToIndexed_OK(t *testing.T) { helpers.ClearCache() validators := make([]*ethpb.Validator, 2*params.BeaconConfig().SlotsPerEpoch) diff --git a/beacon-chain/core/epoch/epoch_processing.go b/beacon-chain/core/epoch/epoch_processing.go index 48980c6d5e..5119e5059f 100644 --- a/beacon-chain/core/epoch/epoch_processing.go +++ b/beacon-chain/core/epoch/epoch_processing.go @@ -474,7 +474,7 @@ func UnslashedAttestingIndices(ctx context.Context, state state.ReadOnlyBeaconSt if err != nil { return nil, err } - attestingIndices, err := attestation.AttestingIndices(att.AggregationBits, committee) + attestingIndices, err := attestation.AttestingIndices(att, committee) if err != nil { return nil, err } diff --git a/beacon-chain/core/epoch/precompute/attestation.go b/beacon-chain/core/epoch/precompute/attestation.go index 48087d89db..a510fbb83e 100644 --- a/beacon-chain/core/epoch/precompute/attestation.go +++ b/beacon-chain/core/epoch/precompute/attestation.go @@ -58,7 +58,7 @@ func ProcessAttestations( if err != nil { return nil, nil, err } - indices, err := attestation.AttestingIndices(a.AggregationBits, committee) + indices, err := attestation.AttestingIndices(a, committee) if err != nil { return nil, nil, err } diff --git a/beacon-chain/core/epoch/precompute/attestation_test.go b/beacon-chain/core/epoch/precompute/attestation_test.go index 1801afa9f4..90404586af 100644 --- a/beacon-chain/core/epoch/precompute/attestation_test.go +++ b/beacon-chain/core/epoch/precompute/attestation_test.go @@ -211,7 +211,7 @@ func TestProcessAttestations(t *testing.T) { committee, err := helpers.BeaconCommitteeFromState(context.Background(), beaconState, att1.Data.Slot, att1.Data.CommitteeIndex) require.NoError(t, err) - indices, err := attestation.AttestingIndices(att1.AggregationBits, committee) + indices, err := attestation.AttestingIndices(att1, committee) require.NoError(t, err) for _, i := range indices { if !pVals[i].IsPrevEpochAttester { @@ -220,7 +220,7 @@ func TestProcessAttestations(t *testing.T) { } committee, err = helpers.BeaconCommitteeFromState(context.Background(), beaconState, att2.Data.Slot, att2.Data.CommitteeIndex) require.NoError(t, err) - indices, err = attestation.AttestingIndices(att2.AggregationBits, committee) + indices, err = attestation.AttestingIndices(att2, committee) require.NoError(t, err) for _, i := range indices { assert.Equal(t, true, pVals[i].IsPrevEpochAttester, "Not a prev epoch attester") diff --git a/beacon-chain/core/helpers/beacon_committee.go b/beacon-chain/core/helpers/beacon_committee.go index 9cb3f11c67..86134ca4d0 100644 --- a/beacon-chain/core/helpers/beacon_committee.go +++ b/beacon-chain/core/helpers/beacon_committee.go @@ -295,6 +295,21 @@ func ShuffledIndices(s state.ReadOnlyBeaconState, epoch primitives.Epoch) ([]pri return UnshuffleList(indices, seed) } +// CommitteeIndices return beacon committee indices corresponding to bits that are set on the argument bitfield. +// +// Spec pseudocode definition: +// +// def get_committee_indices(committee_bits: Bitvector) -> Sequence[CommitteeIndex]: +// return [CommitteeIndex(index) for index, bit in enumerate(committee_bits) if bit] +func CommitteeIndices(committeeBits bitfield.Bitfield) []primitives.CommitteeIndex { + indices := committeeBits.BitIndices() + committeeIndices := make([]primitives.CommitteeIndex, len(indices)) + for i, ix := range indices { + committeeIndices[i] = primitives.CommitteeIndex(uint64(ix)) + } + return committeeIndices +} + // UpdateCommitteeCache gets called at the beginning of every epoch to cache the committee shuffled indices // list with committee index and epoch number. It caches the shuffled indices for the input epoch. func UpdateCommitteeCache(ctx context.Context, state state.ReadOnlyBeaconState, e primitives.Epoch) error { diff --git a/beacon-chain/core/helpers/beacon_committee_test.go b/beacon-chain/core/helpers/beacon_committee_test.go index 5de5d1bf4e..1402c20739 100644 --- a/beacon-chain/core/helpers/beacon_committee_test.go +++ b/beacon-chain/core/helpers/beacon_committee_test.go @@ -699,3 +699,12 @@ func TestPrecomputeProposerIndices_Ok(t *testing.T) { } assert.DeepEqual(t, wantedProposerIndices, proposerIndices, "Did not precompute proposer indices correctly") } + +func TestCommitteeIndices(t *testing.T) { + bitfield := bitfield.NewBitvector4() + bitfield.SetBitAt(0, true) + bitfield.SetBitAt(1, true) + bitfield.SetBitAt(3, true) + indices := helpers.CommitteeIndices(bitfield) + assert.DeepEqual(t, []primitives.CommitteeIndex{0, 1, 3}, indices) +} diff --git a/beacon-chain/core/transition/transition_test.go b/beacon-chain/core/transition/transition_test.go index 1d31c4127d..2a5bc346dc 100644 --- a/beacon-chain/core/transition/transition_test.go +++ b/beacon-chain/core/transition/transition_test.go @@ -311,7 +311,7 @@ func createFullBlockWithOperations(t *testing.T) (state.BeaconState, committee, err := helpers.BeaconCommitteeFromState(context.Background(), beaconState, blockAtt.Data.Slot, blockAtt.Data.CommitteeIndex) assert.NoError(t, err) - attestingIndices, err := attestation.AttestingIndices(blockAtt.AggregationBits, committee) + attestingIndices, err := attestation.AttestingIndices(blockAtt, committee) require.NoError(t, err) assert.NoError(t, err) hashTreeRoot, err = signing.ComputeSigningRoot(blockAtt.Data, domain) diff --git a/beacon-chain/monitor/process_attestation.go b/beacon-chain/monitor/process_attestation.go index 25b90b741b..9f596eea61 100644 --- a/beacon-chain/monitor/process_attestation.go +++ b/beacon-chain/monitor/process_attestation.go @@ -38,7 +38,7 @@ func attestingIndices(ctx context.Context, state state.BeaconState, att interfac if err != nil { return nil, err } - return attestation.AttestingIndices(att.GetAggregationBits(), committee) + return attestation.AttestingIndices(att, committee) } // logMessageTimelyFlagsForIndex returns the log message with performance info for the attestation (head, source, target) diff --git a/beacon-chain/rpc/prysm/v1alpha1/debug/block.go b/beacon-chain/rpc/prysm/v1alpha1/debug/block.go index fe2c7facd8..808d66b05e 100644 --- a/beacon-chain/rpc/prysm/v1alpha1/debug/block.go +++ b/beacon-chain/rpc/prysm/v1alpha1/debug/block.go @@ -79,7 +79,7 @@ func (ds *Server) GetInclusionSlot(ctx context.Context, req *pbrpc.InclusionSlot if err != nil { return nil, status.Errorf(codes.Internal, "Could not get committee: %v", err) } - indices, err := attestation.AttestingIndices(a.GetAggregationBits(), c) + indices, err := attestation.AttestingIndices(a, c) if err != nil { return nil, err } diff --git a/beacon-chain/rpc/prysm/v1alpha1/validator/aggregator_test.go b/beacon-chain/rpc/prysm/v1alpha1/validator/aggregator_test.go index ef72ed17ea..b1cc8100ed 100644 --- a/beacon-chain/rpc/prysm/v1alpha1/validator/aggregator_test.go +++ b/beacon-chain/rpc/prysm/v1alpha1/validator/aggregator_test.go @@ -227,7 +227,7 @@ func generateAtt(state state.ReadOnlyBeaconState, index uint64, privKeys []bls.S if err != nil { return nil, err } - attestingIndices, err := attestation.AttestingIndices(att.AggregationBits, committee) + attestingIndices, err := attestation.AttestingIndices(att, committee) if err != nil { return nil, err } @@ -266,7 +266,7 @@ func generateUnaggregatedAtt(state state.ReadOnlyBeaconState, index uint64, priv if err != nil { return nil, err } - attestingIndices, err := attestation.AttestingIndices(att.AggregationBits, committee) + attestingIndices, err := attestation.AttestingIndices(att, committee) if err != nil { return nil, err } diff --git a/beacon-chain/rpc/prysm/v1alpha1/validator/proposer_test.go b/beacon-chain/rpc/prysm/v1alpha1/validator/proposer_test.go index 796cdf1d1a..9e1144fcf1 100644 --- a/beacon-chain/rpc/prysm/v1alpha1/validator/proposer_test.go +++ b/beacon-chain/rpc/prysm/v1alpha1/validator/proposer_test.go @@ -2440,7 +2440,7 @@ func TestProposer_FilterAttestation(t *testing.T) { }) committee, err := helpers.BeaconCommitteeFromState(context.Background(), st, atts[i].GetData().Slot, atts[i].GetData().CommitteeIndex) assert.NoError(t, err) - attestingIndices, err := attestation.AttestingIndices(atts[i].GetAggregationBits(), committee) + attestingIndices, err := attestation.AttestingIndices(atts[i], committee) require.NoError(t, err) assert.NoError(t, err) domain, err := signing.Domain(st.Fork(), 0, params.BeaconConfig().DomainBeaconAttester, params.BeaconConfig().ZeroHash[:]) diff --git a/beacon-chain/sync/pending_attestations_queue_test.go b/beacon-chain/sync/pending_attestations_queue_test.go index 3f32b8fa8d..b99e15d5ff 100644 --- a/beacon-chain/sync/pending_attestations_queue_test.go +++ b/beacon-chain/sync/pending_attestations_queue_test.go @@ -82,7 +82,7 @@ func TestProcessPendingAtts_HasBlockSaveUnAggregatedAtt(t *testing.T) { committee, err := helpers.BeaconCommitteeFromState(context.Background(), beaconState, att.Data.Slot, att.Data.CommitteeIndex) assert.NoError(t, err) - attestingIndices, err := attestation.AttestingIndices(att.AggregationBits, committee) + attestingIndices, err := attestation.AttestingIndices(att, committee) require.NoError(t, err) attesterDomain, err := signing.Domain(beaconState.Fork(), 0, params.BeaconConfig().DomainBeaconAttester, beaconState.GenesisValidatorsRoot()) require.NoError(t, err) @@ -205,7 +205,7 @@ func TestProcessPendingAtts_NoBroadcastWithBadSignature(t *testing.T) { } committee, err := helpers.BeaconCommitteeFromState(context.Background(), s, att.Data.Slot, att.Data.CommitteeIndex) assert.NoError(t, err) - attestingIndices, err := attestation.AttestingIndices(att.AggregationBits, committee) + attestingIndices, err := attestation.AttestingIndices(att, committee) require.NoError(t, err) attesterDomain, err := signing.Domain(s.Fork(), 0, params.BeaconConfig().DomainBeaconAttester, s.GenesisValidatorsRoot()) require.NoError(t, err) @@ -285,7 +285,7 @@ func TestProcessPendingAtts_HasBlockSaveAggregatedAtt(t *testing.T) { committee, err := helpers.BeaconCommitteeFromState(context.Background(), beaconState, att.Data.Slot, att.Data.CommitteeIndex) assert.NoError(t, err) - attestingIndices, err := attestation.AttestingIndices(att.AggregationBits, committee) + attestingIndices, err := attestation.AttestingIndices(att, committee) require.NoError(t, err) attesterDomain, err := signing.Domain(beaconState.Fork(), 0, params.BeaconConfig().DomainBeaconAttester, beaconState.GenesisValidatorsRoot()) require.NoError(t, err) diff --git a/beacon-chain/sync/validate_aggregate_proof_test.go b/beacon-chain/sync/validate_aggregate_proof_test.go index fe141e47cf..1c4da525bf 100644 --- a/beacon-chain/sync/validate_aggregate_proof_test.go +++ b/beacon-chain/sync/validate_aggregate_proof_test.go @@ -50,7 +50,7 @@ func TestVerifyIndexInCommittee_CanVerify(t *testing.T) { committee, err := helpers.BeaconCommitteeFromState(context.Background(), s, att.Data.Slot, att.Data.CommitteeIndex) assert.NoError(t, err) - indices, err := attestation.AttestingIndices(att.AggregationBits, committee) + indices, err := attestation.AttestingIndices(att, committee) require.NoError(t, err) result, err := service.validateIndexInCommittee(ctx, s, att, primitives.ValidatorIndex(indices[0])) require.NoError(t, err) @@ -354,7 +354,7 @@ func TestValidateAggregateAndProof_CanValidate(t *testing.T) { committee, err := helpers.BeaconCommitteeFromState(context.Background(), beaconState, att.Data.Slot, att.Data.CommitteeIndex) assert.NoError(t, err) - attestingIndices, err := attestation.AttestingIndices(att.AggregationBits, committee) + attestingIndices, err := attestation.AttestingIndices(att, committee) require.NoError(t, err) assert.NoError(t, err) attesterDomain, err := signing.Domain(beaconState.Fork(), 0, params.BeaconConfig().DomainBeaconAttester, beaconState.GenesisValidatorsRoot()) @@ -458,7 +458,7 @@ func TestVerifyIndexInCommittee_SeenAggregatorEpoch(t *testing.T) { committee, err := helpers.BeaconCommitteeFromState(context.Background(), beaconState, att.Data.Slot, att.Data.CommitteeIndex) require.NoError(t, err) - attestingIndices, err := attestation.AttestingIndices(att.AggregationBits, committee) + attestingIndices, err := attestation.AttestingIndices(att, committee) require.NoError(t, err) attesterDomain, err := signing.Domain(beaconState.Fork(), 0, params.BeaconConfig().DomainBeaconAttester, beaconState.GenesisValidatorsRoot()) require.NoError(t, err) @@ -577,7 +577,7 @@ func TestValidateAggregateAndProof_BadBlock(t *testing.T) { committee, err := helpers.BeaconCommitteeFromState(context.Background(), beaconState, att.Data.Slot, att.Data.CommitteeIndex) assert.NoError(t, err) - attestingIndices, err := attestation.AttestingIndices(att.AggregationBits, committee) + attestingIndices, err := attestation.AttestingIndices(att, committee) require.NoError(t, err) assert.NoError(t, err) attesterDomain, err := signing.Domain(beaconState.Fork(), 0, params.BeaconConfig().DomainBeaconAttester, beaconState.GenesisValidatorsRoot()) @@ -668,7 +668,7 @@ func TestValidateAggregateAndProof_RejectWhenAttEpochDoesntEqualTargetEpoch(t *t committee, err := helpers.BeaconCommitteeFromState(context.Background(), beaconState, att.Data.Slot, att.Data.CommitteeIndex) assert.NoError(t, err) - attestingIndices, err := attestation.AttestingIndices(att.AggregationBits, committee) + attestingIndices, err := attestation.AttestingIndices(att, committee) require.NoError(t, err) assert.NoError(t, err) attesterDomain, err := signing.Domain(beaconState.Fork(), 0, params.BeaconConfig().DomainBeaconAttester, beaconState.GenesisValidatorsRoot()) diff --git a/proto/prysm/v1alpha1/attestation/BUILD.bazel b/proto/prysm/v1alpha1/attestation/BUILD.bazel index 9c45df7466..7277285cc7 100644 --- a/proto/prysm/v1alpha1/attestation/BUILD.bazel +++ b/proto/prysm/v1alpha1/attestation/BUILD.bazel @@ -12,6 +12,7 @@ go_library( "//consensus-types/primitives:go_default_library", "//crypto/bls:go_default_library", "//proto/prysm/v1alpha1:go_default_library", + "//runtime/version:go_default_library", "@com_github_pkg_errors//:go_default_library", "@com_github_prysmaticlabs_go_bitfield//:go_default_library", "@io_opencensus_go//trace:go_default_library", @@ -25,6 +26,7 @@ go_test( ":go_default_library", "//config/fieldparams:go_default_library", "//config/params:go_default_library", + "//consensus-types/interfaces:go_default_library", "//consensus-types/primitives:go_default_library", "//proto/prysm/v1alpha1:go_default_library", "//testing/assert:go_default_library", diff --git a/proto/prysm/v1alpha1/attestation/attestation_utils.go b/proto/prysm/v1alpha1/attestation/attestation_utils.go index bfa2bf64aa..432d496efb 100644 --- a/proto/prysm/v1alpha1/attestation/attestation_utils.go +++ b/proto/prysm/v1alpha1/attestation/attestation_utils.go @@ -6,6 +6,7 @@ import ( "bytes" "context" "fmt" + "slices" "sort" "github.com/pkg/errors" @@ -16,6 +17,7 @@ import ( "github.com/prysmaticlabs/prysm/v5/consensus-types/primitives" "github.com/prysmaticlabs/prysm/v5/crypto/bls" ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1" + "github.com/prysmaticlabs/prysm/v5/runtime/version" "go.opencensus.io/trace" ) @@ -38,8 +40,8 @@ import ( // data=attestation.data, // signature=attestation.signature, // ) -func ConvertToIndexed(ctx context.Context, attestation interfaces.Attestation, committee []primitives.ValidatorIndex) (ethpb.IndexedAtt, error) { - attIndices, err := AttestingIndices(attestation.GetAggregationBits(), committee) +func ConvertToIndexed(ctx context.Context, attestation interfaces.Attestation, committees ...[]primitives.ValidatorIndex) (ethpb.IndexedAtt, error) { + attIndices, err := AttestingIndices(attestation, committees...) if err != nil { return nil, err } @@ -55,32 +57,62 @@ func ConvertToIndexed(ctx context.Context, attestation interfaces.Attestation, c return inAtt, err } -// AttestingIndices returns the attesting participants indices from the attestation data. The -// committee is provided as an argument rather than a imported implementation from the spec definition. -// Having the committee as an argument allows for re-use of beacon committees when possible. +// AttestingIndices returns the attesting participants indices from the attestation data. +// Committees are provided as an argument rather than an imported implementation from the spec definition. +// Having committees as an argument allows for re-use of beacon committees when possible. // -// Spec pseudocode definition: +// Spec pseudocode definition (Electra version): // -// def get_attesting_indices(state: BeaconState, -// data: AttestationData, -// bits: Bitlist[MAX_VALIDATORS_PER_COMMITTEE]) -> 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]) -func AttestingIndices(bf bitfield.Bitfield, committee []primitives.ValidatorIndex) ([]uint64, error) { - if bf.Len() != uint64(len(committee)) { - return nil, fmt.Errorf("bitfield length %d is not equal to committee length %d", bf.Len(), len(committee)) +// 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[ValidatorIndex] = set() +// committee_indices = get_committee_indices(attestation.committee_bits) +// committee_offset = 0 +// for index in committee_indices: +// committee = get_beacon_committee(state, attestation.data.slot, index) +// committee_attesters = set( +// index for i, index in enumerate(committee) if attestation.aggregation_bits[committee_offset + i]) +// output = output.union(committee_attesters) +// +// committee_offset += len(committee) +// +// return output +func AttestingIndices(att interfaces.Attestation, committees ...[]primitives.ValidatorIndex) ([]uint64, error) { + if len(committees) == 0 { + return []uint64{}, nil } - indices := make([]uint64, 0, bf.Count()) - p := bf.BitIndices() - for _, idx := range p { - if idx < len(committee) { - indices = append(indices, uint64(committee[idx])) + + aggBits := att.GetAggregationBits() + + if att.Version() < version.Electra { + return attestingIndicesPhase0(aggBits, committees[0]) + } + + committeesLen := 0 + for _, c := range committees { + committeesLen += len(c) + } + if aggBits.Len() != uint64(committeesLen) { + return nil, fmt.Errorf("bitfield length %d is not equal to committee length %d", aggBits.Len(), committeesLen) + } + + attesters := make([]uint64, 0, aggBits.Count()) + committeeOffset := 0 + for _, c := range committees { + committeeAttesters := make([]uint64, 0, len(c)) + for i, vi := range c { + if aggBits.BitAt(uint64(committeeOffset + i)) { + committeeAttesters = append(committeeAttesters, uint64(vi)) + } } + attesters = append(attesters, committeeAttesters...) + committeeOffset += len(c) } - return indices, nil + + slices.Sort(attesters) + return slices.Compact(attesters), nil } // VerifyIndexedAttestationSig this helper function performs the last part of the @@ -157,8 +189,16 @@ func IsValidAttestationIndices(ctx context.Context, indexedAttestation ethpb.Ind if len(indices) == 0 { return errors.New("expected non-empty attesting indices") } - if uint64(len(indices)) > params.BeaconConfig().MaxValidatorsPerCommittee { - return fmt.Errorf("validator indices count exceeds MAX_VALIDATORS_PER_COMMITTEE, %d > %d", len(indices), params.BeaconConfig().MaxValidatorsPerCommittee) + if indexedAttestation.Version() < version.Electra { + maxLength := params.BeaconConfig().MaxValidatorsPerCommittee + if uint64(len(indices)) > maxLength { + return fmt.Errorf("validator indices count exceeds MAX_VALIDATORS_PER_COMMITTEE, %d > %d", len(indices), maxLength) + } + } else { + maxLength := params.BeaconConfig().MaxValidatorsPerCommittee * params.BeaconConfig().MaxCommitteesPerSlot + if uint64(len(indices)) > maxLength { + return fmt.Errorf("validator indices count exceeds MAX_VALIDATORS_PER_COMMITTEE * MAX_COMMITTEES_PER_SLOT, %d > %d", len(indices), maxLength) + } } for i := 1; i < len(indices); i++ { if indices[i-1] >= indices[i] { @@ -204,3 +244,29 @@ func CheckPointIsEqual(checkPt1, checkPt2 *ethpb.Checkpoint) bool { } return true } + +// attestingIndicesPhase0 returns the attesting participants indices from the attestation data. +// Committees are provided as an argument rather than an imported implementation from the spec definition. +// Having committees as an argument allows for re-use of beacon committees when possible. +// +// Spec pseudocode definition (Phase0 version): +// +// 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, attestation.data.slot, attestation.data.index) +// return set(index for i, index in enumerate(committee) if attestation.aggregation_bits[i]) +func attestingIndicesPhase0(aggBits bitfield.Bitlist, committee []primitives.ValidatorIndex) ([]uint64, error) { + if aggBits.Len() != uint64(len(committee)) { + return nil, fmt.Errorf("bitfield length %d is not equal to committee length %d", aggBits.Len(), len(committee)) + } + indices := make([]uint64, 0, aggBits.Count()) + p := aggBits.BitIndices() + for _, idx := range p { + if idx < len(committee) { + indices = append(indices, uint64(committee[idx])) + } + } + return indices, nil +} diff --git a/proto/prysm/v1alpha1/attestation/attestation_utils_test.go b/proto/prysm/v1alpha1/attestation/attestation_utils_test.go index ecb36c3985..d24a331909 100644 --- a/proto/prysm/v1alpha1/attestation/attestation_utils_test.go +++ b/proto/prysm/v1alpha1/attestation/attestation_utils_test.go @@ -7,6 +7,7 @@ import ( "github.com/prysmaticlabs/go-bitfield" fieldparams "github.com/prysmaticlabs/prysm/v5/config/fieldparams" "github.com/prysmaticlabs/prysm/v5/config/params" + "github.com/prysmaticlabs/prysm/v5/consensus-types/interfaces" "github.com/prysmaticlabs/prysm/v5/consensus-types/primitives" eth "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1" "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1/attestation" @@ -16,8 +17,8 @@ import ( func TestAttestingIndices(t *testing.T) { type args struct { - bf bitfield.Bitfield - committee []primitives.ValidatorIndex + att interfaces.Attestation + committees [][]primitives.ValidatorIndex } tests := []struct { name string @@ -28,31 +29,63 @@ func TestAttestingIndices(t *testing.T) { { name: "Full committee attested", args: args{ - bf: bitfield.Bitlist{0b1111}, - committee: []primitives.ValidatorIndex{0, 1, 2}, + att: ð.Attestation{AggregationBits: bitfield.Bitlist{0b1111}}, + committees: [][]primitives.ValidatorIndex{{0, 1, 2}}, }, want: []uint64{0, 1, 2}, }, { name: "Partial committee attested", args: args{ - bf: bitfield.Bitlist{0b1101}, - committee: []primitives.ValidatorIndex{0, 1, 2}, + att: ð.Attestation{AggregationBits: bitfield.Bitlist{0b1101}}, + committees: [][]primitives.ValidatorIndex{{0, 1, 2}}, }, want: []uint64{0, 2}, }, { name: "Invalid bit length", args: args{ - bf: bitfield.Bitlist{0b11111}, - committee: []primitives.ValidatorIndex{0, 1, 2}, + att: ð.Attestation{AggregationBits: bitfield.Bitlist{0b11111}}, + committees: [][]primitives.ValidatorIndex{{0, 1, 2}}, }, err: "bitfield length 4 is not equal to committee length 3", }, + { + name: "Electra - Full committee attested", + args: args{ + att: ð.AttestationElectra{AggregationBits: bitfield.Bitlist{0b11111}}, + committees: [][]primitives.ValidatorIndex{{0, 1}, {2, 3}}, + }, + want: []uint64{0, 1, 2, 3}, + }, + { + name: "Electra - Partial committee attested", + args: args{ + att: ð.AttestationElectra{AggregationBits: bitfield.Bitlist{0b10110}}, + committees: [][]primitives.ValidatorIndex{{0, 1}, {2, 3}}, + }, + want: []uint64{1, 2}, + }, + { + name: "Electra - Invalid bit length", + args: args{ + att: ð.AttestationElectra{AggregationBits: bitfield.Bitlist{0b111111}}, + committees: [][]primitives.ValidatorIndex{{0, 1}, {2, 3}}, + }, + err: "bitfield length 5 is not equal to committee length 4", + }, + { + name: "Electra - No duplicates", + args: args{ + att: ð.AttestationElectra{AggregationBits: bitfield.Bitlist{0b11111}}, + committees: [][]primitives.ValidatorIndex{{0, 1}, {0, 1}}, + }, + want: []uint64{0, 1}, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got, err := attestation.AttestingIndices(tt.args.bf, tt.args.committee) + got, err := attestation.AttestingIndices(tt.args.att, tt.args.committees...) if tt.err == "" { require.NoError(t, err) assert.DeepEqual(t, tt.want, got) @@ -66,7 +99,7 @@ func TestAttestingIndices(t *testing.T) { func TestIsValidAttestationIndices(t *testing.T) { tests := []struct { name string - att *eth.IndexedAttestation + att eth.IndexedAtt wantedErr string }{ { @@ -142,6 +175,17 @@ func TestIsValidAttestationIndices(t *testing.T) { Signature: make([]byte, fieldparams.BLSSignatureLength), }, }, + { + name: "Electra - Greater than max validators per slot", + att: ð.IndexedAttestationElectra{ + AttestingIndices: make([]uint64, params.BeaconConfig().MaxValidatorsPerCommittee*params.BeaconConfig().MaxCommitteesPerSlot+1), + Data: ð.AttestationData{ + Target: ð.Checkpoint{}, + }, + Signature: make([]byte, fieldparams.BLSSignatureLength), + }, + wantedErr: "indices count exceeds", + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -161,7 +205,7 @@ func BenchmarkAttestingIndices_PartialCommittee(b *testing.B) { b.ResetTimer() for i := 0; i < b.N; i++ { - _, err := attestation.AttestingIndices(bf, committee) + _, err := attestation.AttestingIndices(ð.Attestation{AggregationBits: bf}, committee) require.NoError(b, err) } }