Compare commits

...

2 Commits

Author SHA1 Message Date
Raul Jordan
0d60c2a125 reorganize receive file 2024-08-09 11:01:39 -05:00
Radosław Kapka
e011f05403 Electra committe validation for aggregate and proof (#14317)
* Electra committe validation for aggregate and proof

* review

* update comments
2024-08-08 16:32:19 +00:00
7 changed files with 91 additions and 32 deletions

View File

@@ -30,6 +30,10 @@ func (s *Service) receiveAttestations(ctx context.Context, indexedAttsChan chan
for {
select {
case att := <-indexedAttsChan:
if att == nil || att.SignatureValidationSet == nil {
log.Error("Received nil attestation or attestation with nil signature validation set")
continue
}
if !validateAttestationIntegrity(att) {
continue
}
@@ -38,6 +42,16 @@ func (s *Service) receiveAttestations(ctx context.Context, indexedAttsChan chan
log.WithError(err).Error("Could not get hash tree root of attestation")
continue
}
verified, err := att.SignatureValidationSet.Verify()
if err != nil {
log.WithError(err).Error("Could not verify attestation signature")
continue
}
if !verified {
log.Error("Attestation signature did not verify")
continue
}
// Verify the attestation signature using the signature batch.
attWrapper := &slashertypes.IndexedAttestationWrapper{
IndexedAttestation: att.IndexedAtt,
DataRoot: dataRoot,

View File

@@ -2,6 +2,7 @@ package types
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"
)
@@ -31,6 +32,7 @@ func (c ChunkKind) String() string {
// which doesn't work well with interface types.
type WrappedIndexedAtt struct {
ethpb.IndexedAtt
SignatureValidationSet *bls.SignatureBatch
}
// IndexedAttestationWrapper contains an indexed attestation with its

View File

@@ -132,7 +132,7 @@ func (s *Service) processAttestations(ctx context.Context, attestations []ethpb.
continue
}
valid, err := s.validateUnaggregatedAttWithState(ctx, aggregate, preState)
valid, _, err := s.validateUnaggregatedAttWithState(ctx, aggregate, preState)
if err != nil {
log.WithError(err).Debug("Pending unaggregated attestation failed validation")
continue

View File

@@ -266,16 +266,20 @@ func (s *Service) setAggregatorIndexEpochSeen(epoch primitives.Epoch, aggregator
// - [REJECT] The aggregate attestation has participants -- that is, len(get_attesting_indices(state, aggregate.data, aggregate.aggregation_bits)) >= 1.
// - [REJECT] The aggregator's validator index is within the committee --
// i.e. `aggregate_and_proof.aggregator_index in get_beacon_committee(state, aggregate.data.slot, aggregate.data.index)`.
//
// Post-Electra:
// - [REJECT] len(committee_indices) == 1, where committee_indices = get_committee_indices(aggregate).
// - [REJECT] aggregate.data.index == 0
func (s *Service) validateIndexInCommittee(ctx context.Context, bs state.ReadOnlyBeaconState, a ethpb.Att, validatorIndex primitives.ValidatorIndex) (pubsub.ValidationResult, error) {
ctx, span := trace.StartSpan(ctx, "sync.validateIndexInCommittee")
defer span.End()
_, result, err := s.validateCommitteeIndex(ctx, a, bs)
committeeIndex, _, result, err := s.validateCommitteeIndex(ctx, a, bs)
if result != pubsub.ValidationAccept {
return result, err
}
committee, result, err := s.validateBitLength(ctx, bs, a.GetData().Slot, a.GetData().CommitteeIndex, a.GetAggregationBits())
committee, result, err := s.validateBitLength(ctx, bs, a.GetData().Slot, committeeIndex, a.GetAggregationBits())
if result != pubsub.ValidationAccept {
return result, err
}

View File

@@ -44,9 +44,7 @@ func TestVerifyIndexInCommittee_CanVerify(t *testing.T) {
bf := bitfield.NewBitlist(validators / uint64(params.BeaconConfig().SlotsPerEpoch))
bf.SetBitAt(0, true)
att := &ethpb.Attestation{Data: &ethpb.AttestationData{
Target: &ethpb.Checkpoint{Epoch: 0}},
AggregationBits: bf}
att := &ethpb.Attestation{Data: &ethpb.AttestationData{}, AggregationBits: bf}
committee, err := helpers.BeaconCommitteeFromState(context.Background(), s, att.Data.Slot, att.Data.CommitteeIndex)
assert.NoError(t, err)
@@ -71,8 +69,7 @@ func TestVerifyIndexInCommittee_ExistsInBeaconCommittee(t *testing.T) {
s, _ := util.DeterministicGenesisState(t, validators)
require.NoError(t, s.SetSlot(params.BeaconConfig().SlotsPerEpoch))
att := &ethpb.Attestation{Data: &ethpb.AttestationData{
Target: &ethpb.Checkpoint{Epoch: 0}}}
att := &ethpb.Attestation{Data: &ethpb.AttestationData{}}
committee, err := helpers.BeaconCommitteeFromState(context.Background(), s, att.Data.Slot, att.Data.CommitteeIndex)
require.NoError(t, err)
@@ -107,6 +104,24 @@ func TestVerifyIndexInCommittee_ExistsInBeaconCommittee(t *testing.T) {
assert.Equal(t, pubsub.ValidationReject, result)
}
func TestVerifyIndexInCommittee_Electra(t *testing.T) {
ctx := context.Background()
s, _ := util.DeterministicGenesisStateElectra(t, 64)
service := &Service{}
cb := primitives.NewAttestationCommitteeBits()
cb.SetBitAt(0, true)
att := &ethpb.AttestationElectra{Data: &ethpb.AttestationData{}, CommitteeBits: cb}
committee, err := helpers.BeaconCommitteeFromState(context.Background(), s, att.Data.Slot, att.Data.CommitteeIndex)
require.NoError(t, err)
bl := bitfield.NewBitlist(uint64(len(committee)))
bl.SetBitAt(0, true)
att.AggregationBits = bl
result, err := service.validateIndexInCommittee(ctx, s, att, committee[0])
require.NoError(t, err)
assert.Equal(t, pubsub.ValidationAccept, result)
}
func TestVerifySelection_NotAnAggregator(t *testing.T) {
ctx := context.Background()
params.SetupTestConfigCleanup(t)

View File

@@ -20,6 +20,7 @@ import (
"github.com/prysmaticlabs/prysm/v5/beacon-chain/state"
"github.com/prysmaticlabs/prysm/v5/config/features"
"github.com/prysmaticlabs/prysm/v5/consensus-types/primitives"
"github.com/prysmaticlabs/prysm/v5/crypto/bls"
"github.com/prysmaticlabs/prysm/v5/encoding/bytesutil"
"github.com/prysmaticlabs/prysm/v5/monitoring/tracing"
eth "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1"
@@ -113,19 +114,17 @@ func (s *Service) validateCommitteeIndexBeaconAttestation(ctx context.Context, p
committeeIndex = data.CommitteeIndex
}
if !features.Get().EnableSlasher {
// Verify this the first attestation received for the participating validator for the slot.
if s.hasSeenCommitteeIndicesSlot(data.Slot, data.CommitteeIndex, att.GetAggregationBits()) {
return pubsub.ValidationIgnore, nil
}
// Verify this the first attestation received for the participating validator for the slot.
if s.hasSeenCommitteeIndicesSlot(data.Slot, data.CommitteeIndex, att.GetAggregationBits()) {
return pubsub.ValidationIgnore, nil
}
// Reject an attestation if it references an invalid block.
if s.hasBadBlock(bytesutil.ToBytes32(data.BeaconBlockRoot)) ||
s.hasBadBlock(bytesutil.ToBytes32(data.Target.Root)) ||
s.hasBadBlock(bytesutil.ToBytes32(data.Source.Root)) {
attBadBlockCount.Inc()
return pubsub.ValidationReject, errors.New("attestation data references bad block root")
}
// Reject an attestation if it references an invalid block.
if s.hasBadBlock(bytesutil.ToBytes32(data.BeaconBlockRoot)) ||
s.hasBadBlock(bytesutil.ToBytes32(data.Target.Root)) ||
s.hasBadBlock(bytesutil.ToBytes32(data.Source.Root)) {
attBadBlockCount.Inc()
return pubsub.ValidationReject, errors.New("attestation data references bad block root")
}
// Verify the block being voted and the processed state is in beaconDB and the block has passed validation if it's in the beaconDB.
@@ -171,7 +170,7 @@ func (s *Service) validateCommitteeIndexBeaconAttestation(ctx context.Context, p
return validationRes, err
}
validationRes, err = s.validateUnaggregatedAttWithState(ctx, att, preState)
validationRes, signatureValidationSet, err := s.validateUnaggregatedAttWithState(ctx, att, preState)
if validationRes != pubsub.ValidationAccept {
return validationRes, err
}
@@ -201,7 +200,9 @@ func (s *Service) validateCommitteeIndexBeaconAttestation(ctx context.Context, p
tracing.AnnotateError(span, err)
return
}
s.cfg.slasherAttestationsFeed.Send(&types.WrappedIndexedAtt{IndexedAtt: indexedAtt})
s.cfg.slasherAttestationsFeed.Send(&types.WrappedIndexedAtt{
IndexedAtt: indexedAtt, SignatureValidationSet: signatureValidationSet,
})
}()
}
@@ -217,7 +218,7 @@ func (s *Service) validateUnaggregatedAttTopic(ctx context.Context, a eth.Att, b
ctx, span := trace.StartSpan(ctx, "sync.validateUnaggregatedAttTopic")
defer span.End()
valCount, result, err := s.validateCommitteeIndex(ctx, a, bs)
_, valCount, result, err := s.validateCommitteeIndex(ctx, a, bs)
if result != pubsub.ValidationAccept {
return result, err
}
@@ -235,43 +236,63 @@ func (s *Service) validateUnaggregatedAttTopic(ctx context.Context, a eth.Att, b
return pubsub.ValidationAccept, nil
}
func (s *Service) validateCommitteeIndex(ctx context.Context, a eth.Att, bs state.ReadOnlyBeaconState) (uint64, pubsub.ValidationResult, error) {
func (s *Service) validateCommitteeIndex(
ctx context.Context,
a eth.Att,
bs state.ReadOnlyBeaconState,
) (primitives.CommitteeIndex, uint64, pubsub.ValidationResult, error) {
valCount, err := helpers.ActiveValidatorCount(ctx, bs, slots.ToEpoch(a.GetData().Slot))
if err != nil {
return 0, pubsub.ValidationIgnore, err
return 0, 0, pubsub.ValidationIgnore, err
}
count := helpers.SlotCommitteeCount(valCount)
if uint64(a.GetData().CommitteeIndex) > count {
return 0, pubsub.ValidationReject, errors.Errorf("committee index %d > %d", a.GetData().CommitteeIndex, count)
return 0, 0, pubsub.ValidationReject, errors.Errorf("committee index %d > %d", a.GetData().CommitteeIndex, count)
}
return valCount, pubsub.ValidationAccept, nil
var ci primitives.CommitteeIndex
if a.Version() >= version.Electra {
dataCi := a.GetData().CommitteeIndex
if dataCi != 0 {
return 0, 0, pubsub.ValidationReject, fmt.Errorf("committee index must be 0 but was %d", dataCi)
}
indices := helpers.CommitteeIndices(a.CommitteeBitsVal())
if len(indices) != 1 {
return 0, 0, pubsub.ValidationReject, fmt.Errorf("exactly 1 committee index must be set but %d were set", len(indices))
}
ci = indices[0]
} else {
ci = a.GetData().CommitteeIndex
}
return ci, valCount, pubsub.ValidationAccept, nil
}
// This validates beacon unaggregated attestation using the given state, the validation consists of bitfield length and count consistency
// and signature verification.
func (s *Service) validateUnaggregatedAttWithState(ctx context.Context, a eth.Att, bs state.ReadOnlyBeaconState) (pubsub.ValidationResult, error) {
func (s *Service) validateUnaggregatedAttWithState(ctx context.Context, a eth.Att, bs state.ReadOnlyBeaconState) (pubsub.ValidationResult, *bls.SignatureBatch, error) {
ctx, span := trace.StartSpan(ctx, "sync.validateUnaggregatedAttWithState")
defer span.End()
committee, result, err := s.validateBitLength(ctx, bs, a.GetData().Slot, a.GetData().CommitteeIndex, a.GetAggregationBits())
if result != pubsub.ValidationAccept {
return result, err
return result, nil, err
}
// Attestation must be unaggregated and the bit index must exist in the range of committee indices.
// Note: The Ethereum Beacon chain spec suggests (len(get_attesting_indices(state, attestation.data, attestation.aggregation_bits)) == 1)
// however this validation can be achieved without use of get_attesting_indices which is an O(n) lookup.
if a.GetAggregationBits().Count() != 1 || a.GetAggregationBits().BitIndices()[0] >= len(committee) {
return pubsub.ValidationReject, errors.New("attestation bitfield is invalid")
return pubsub.ValidationReject, nil, errors.New("attestation bitfield is invalid")
}
set, err := blocks.AttestationSignatureBatch(ctx, bs, []eth.Att{a})
if err != nil {
tracing.AnnotateError(span, err)
attBadSignatureBatchCount.Inc()
return pubsub.ValidationReject, err
return pubsub.ValidationReject, nil, err
}
return s.validateWithBatchVerifier(ctx, "attestation", set)
res, err := s.validateWithBatchVerifier(ctx, "attestation", set)
return res, set, err
}
func (s *Service) validateBitLength(

View File

@@ -11,6 +11,9 @@ import (
"go.opencensus.io/trace"
)
// validateCommitteeIndexElectra implements the following checks from the spec:
// - [REJECT] len(committee_indices) == 1, where committee_indices = get_committee_indices(attestation).
// - [REJECT] attestation.data.index == 0
func validateCommitteeIndexElectra(ctx context.Context, a *ethpb.AttestationElectra) (primitives.CommitteeIndex, pubsub.ValidationResult, error) {
_, span := trace.StartSpan(ctx, "sync.validateCommitteeIndexElectra")
defer span.End()