github.com/prysmaticlabs/prysm@v1.4.4/beacon-chain/sync/validate_beacon_attestation.go (about)

     1  package sync
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"reflect"
     7  	"strings"
     8  
     9  	"github.com/libp2p/go-libp2p-core/peer"
    10  	pubsub "github.com/libp2p/go-libp2p-pubsub"
    11  	types "github.com/prysmaticlabs/eth2-types"
    12  	"github.com/prysmaticlabs/prysm/beacon-chain/core/blocks"
    13  	"github.com/prysmaticlabs/prysm/beacon-chain/core/feed"
    14  	"github.com/prysmaticlabs/prysm/beacon-chain/core/feed/operation"
    15  	"github.com/prysmaticlabs/prysm/beacon-chain/core/helpers"
    16  	"github.com/prysmaticlabs/prysm/beacon-chain/p2p"
    17  	iface "github.com/prysmaticlabs/prysm/beacon-chain/state/interface"
    18  	eth "github.com/prysmaticlabs/prysm/proto/eth/v1alpha1"
    19  	"github.com/prysmaticlabs/prysm/shared/bytesutil"
    20  	"github.com/prysmaticlabs/prysm/shared/traceutil"
    21  	"go.opencensus.io/trace"
    22  )
    23  
    24  // Validation
    25  // - The block being voted for (attestation.data.beacon_block_root) passes validation.
    26  // - The attestation's committee index (attestation.data.index) is for the correct subnet.
    27  // - The attestation is unaggregated -- that is, it has exactly one participating validator (len(get_attesting_indices(state, attestation.data, attestation.aggregation_bits)) == 1).
    28  // - attestation.data.slot is within the last ATTESTATION_PROPAGATION_SLOT_RANGE slots (attestation.data.slot + ATTESTATION_PROPAGATION_SLOT_RANGE >= current_slot >= attestation.data.slot).
    29  // - The signature of attestation is valid.
    30  func (s *Service) validateCommitteeIndexBeaconAttestation(ctx context.Context, pid peer.ID, msg *pubsub.Message) pubsub.ValidationResult {
    31  	if pid == s.cfg.P2P.PeerID() {
    32  		return pubsub.ValidationAccept
    33  	}
    34  	// Attestation processing requires the target block to be present in the database, so we'll skip
    35  	// validating or processing attestations until fully synced.
    36  	if s.cfg.InitialSync.Syncing() {
    37  		return pubsub.ValidationIgnore
    38  	}
    39  	ctx, span := trace.StartSpan(ctx, "sync.validateCommitteeIndexBeaconAttestation")
    40  	defer span.End()
    41  
    42  	if msg.Topic == nil {
    43  		return pubsub.ValidationReject
    44  	}
    45  
    46  	// Override topic for decoding.
    47  	originalTopic := msg.Topic
    48  	format := p2p.GossipTypeMapping[reflect.TypeOf(&eth.Attestation{})]
    49  	msg.Topic = &format
    50  
    51  	m, err := s.decodePubsubMessage(msg)
    52  	if err != nil {
    53  		log.WithError(err).Debug("Could not decode message")
    54  		traceutil.AnnotateError(span, err)
    55  		return pubsub.ValidationReject
    56  	}
    57  	// Restore topic.
    58  	msg.Topic = originalTopic
    59  
    60  	att, ok := m.(*eth.Attestation)
    61  	if !ok {
    62  		return pubsub.ValidationReject
    63  	}
    64  
    65  	if err := helpers.ValidateNilAttestation(att); err != nil {
    66  		return pubsub.ValidationReject
    67  	}
    68  
    69  	// Broadcast the unaggregated attestation on a feed to notify other services in the beacon node
    70  	// of a received unaggregated attestation.
    71  	s.cfg.AttestationNotifier.OperationFeed().Send(&feed.Event{
    72  		Type: operation.UnaggregatedAttReceived,
    73  		Data: &operation.UnAggregatedAttReceivedData{
    74  			Attestation: att,
    75  		},
    76  	})
    77  
    78  	// Attestation's slot is within ATTESTATION_PROPAGATION_SLOT_RANGE and early attestation
    79  	// processing tolerance.
    80  	if err := helpers.ValidateAttestationTime(att.Data.Slot, s.cfg.Chain.GenesisTime(),
    81  		earlyAttestationProcessingTolerance); err != nil {
    82  		traceutil.AnnotateError(span, err)
    83  		return pubsub.ValidationIgnore
    84  	}
    85  	if err := helpers.ValidateSlotTargetEpoch(att.Data); err != nil {
    86  		return pubsub.ValidationReject
    87  	}
    88  
    89  	// Verify this the first attestation received for the participating validator for the slot.
    90  	if s.hasSeenCommitteeIndicesSlot(att.Data.Slot, att.Data.CommitteeIndex, att.AggregationBits) {
    91  		return pubsub.ValidationIgnore
    92  	}
    93  
    94  	// Reject an attestation if it references an invalid block.
    95  	if s.hasBadBlock(bytesutil.ToBytes32(att.Data.BeaconBlockRoot)) ||
    96  		s.hasBadBlock(bytesutil.ToBytes32(att.Data.Target.Root)) ||
    97  		s.hasBadBlock(bytesutil.ToBytes32(att.Data.Source.Root)) {
    98  		return pubsub.ValidationReject
    99  	}
   100  
   101  	// Verify the block being voted and the processed state is in DB and. The block should have passed validation if it's in the DB.
   102  	blockRoot := bytesutil.ToBytes32(att.Data.BeaconBlockRoot)
   103  	if !s.hasBlockAndState(ctx, blockRoot) {
   104  		// A node doesn't have the block, it'll request from peer while saving the pending attestation to a queue.
   105  		s.savePendingAtt(&eth.SignedAggregateAttestationAndProof{Message: &eth.AggregateAttestationAndProof{Aggregate: att}})
   106  		return pubsub.ValidationIgnore
   107  	}
   108  
   109  	if err := s.cfg.Chain.VerifyFinalizedConsistency(ctx, att.Data.BeaconBlockRoot); err != nil {
   110  		traceutil.AnnotateError(span, err)
   111  		return pubsub.ValidationReject
   112  	}
   113  	if err := s.cfg.Chain.VerifyLmdFfgConsistency(ctx, att); err != nil {
   114  		traceutil.AnnotateError(span, err)
   115  		return pubsub.ValidationReject
   116  	}
   117  
   118  	preState, err := s.cfg.Chain.AttestationPreState(ctx, att)
   119  	if err != nil {
   120  		log.WithError(err).Error("Could not to retrieve pre state")
   121  		traceutil.AnnotateError(span, err)
   122  		return pubsub.ValidationIgnore
   123  	}
   124  
   125  	validationRes := s.validateUnaggregatedAttTopic(ctx, att, preState, *originalTopic)
   126  	if validationRes != pubsub.ValidationAccept {
   127  		return validationRes
   128  	}
   129  
   130  	validationRes = s.validateUnaggregatedAttWithState(ctx, att, preState)
   131  	if validationRes != pubsub.ValidationAccept {
   132  		return validationRes
   133  	}
   134  
   135  	s.setSeenCommitteeIndicesSlot(att.Data.Slot, att.Data.CommitteeIndex, att.AggregationBits)
   136  
   137  	msg.ValidatorData = att
   138  
   139  	return pubsub.ValidationAccept
   140  }
   141  
   142  // This validates beacon unaggregated attestation has correct topic string.
   143  func (s *Service) validateUnaggregatedAttTopic(ctx context.Context, a *eth.Attestation, bs iface.ReadOnlyBeaconState, t string) pubsub.ValidationResult {
   144  	ctx, span := trace.StartSpan(ctx, "sync.validateUnaggregatedAttTopic")
   145  	defer span.End()
   146  
   147  	valCount, err := helpers.ActiveValidatorCount(bs, helpers.SlotToEpoch(a.Data.Slot))
   148  	if err != nil {
   149  		log.WithError(err).Error("Could not retrieve active validator count")
   150  		traceutil.AnnotateError(span, err)
   151  		return pubsub.ValidationIgnore
   152  	}
   153  	count := helpers.SlotCommitteeCount(valCount)
   154  	if uint64(a.Data.CommitteeIndex) > count {
   155  		return pubsub.ValidationReject
   156  	}
   157  	subnet := helpers.ComputeSubnetForAttestation(valCount, a)
   158  	format := p2p.GossipTypeMapping[reflect.TypeOf(&eth.Attestation{})]
   159  	digest, err := s.forkDigest()
   160  	if err != nil {
   161  		log.WithError(err).Error("Could not compute fork digest")
   162  		traceutil.AnnotateError(span, err)
   163  		return pubsub.ValidationIgnore
   164  	}
   165  	if !strings.HasPrefix(t, fmt.Sprintf(format, digest, subnet)) {
   166  		return pubsub.ValidationReject
   167  	}
   168  
   169  	return pubsub.ValidationAccept
   170  }
   171  
   172  // This validates beacon unaggregated attestation using the given state, the validation consists of bitfield length and count consistency
   173  // and signature verification.
   174  func (s *Service) validateUnaggregatedAttWithState(ctx context.Context, a *eth.Attestation, bs iface.ReadOnlyBeaconState) pubsub.ValidationResult {
   175  	ctx, span := trace.StartSpan(ctx, "sync.validateUnaggregatedAttWithState")
   176  	defer span.End()
   177  
   178  	committee, err := helpers.BeaconCommitteeFromState(bs, a.Data.Slot, a.Data.CommitteeIndex)
   179  	if err != nil {
   180  		traceutil.AnnotateError(span, err)
   181  		return pubsub.ValidationIgnore
   182  	}
   183  
   184  	// Verify number of aggregation bits matches the committee size.
   185  	if err := helpers.VerifyBitfieldLength(a.AggregationBits, uint64(len(committee))); err != nil {
   186  		return pubsub.ValidationReject
   187  	}
   188  
   189  	// Attestation must be unaggregated and the bit index must exist in the range of committee indices.
   190  	// Note: The Ethereum Beacon Chain spec suggests (len(get_attesting_indices(state, attestation.data, attestation.aggregation_bits)) == 1)
   191  	// however this validation can be achieved without use of get_attesting_indices which is an O(n) lookup.
   192  	if a.AggregationBits.Count() != 1 || a.AggregationBits.BitIndices()[0] >= len(committee) {
   193  		return pubsub.ValidationReject
   194  	}
   195  
   196  	if err := blocks.VerifyAttestationSignature(ctx, bs, a); err != nil {
   197  		log.WithError(err).Debug("Could not verify attestation")
   198  		traceutil.AnnotateError(span, err)
   199  		return pubsub.ValidationReject
   200  	}
   201  
   202  	return pubsub.ValidationAccept
   203  }
   204  
   205  // Returns true if the attestation was already seen for the participating validator for the slot.
   206  func (s *Service) hasSeenCommitteeIndicesSlot(slot types.Slot, committeeID types.CommitteeIndex, aggregateBits []byte) bool {
   207  	s.seenUnAggregatedAttestationLock.RLock()
   208  	defer s.seenUnAggregatedAttestationLock.RUnlock()
   209  	b := append(bytesutil.Bytes32(uint64(slot)), bytesutil.Bytes32(uint64(committeeID))...)
   210  	b = append(b, aggregateBits...)
   211  	_, seen := s.seenUnAggregatedAttestationCache.Get(string(b))
   212  	return seen
   213  }
   214  
   215  // Set committee's indices and slot as seen for incoming attestations.
   216  func (s *Service) setSeenCommitteeIndicesSlot(slot types.Slot, committeeID types.CommitteeIndex, aggregateBits []byte) {
   217  	s.seenUnAggregatedAttestationLock.Lock()
   218  	defer s.seenUnAggregatedAttestationLock.Unlock()
   219  	b := append(bytesutil.Bytes32(uint64(slot)), bytesutil.Bytes32(uint64(committeeID))...)
   220  	b = append(b, aggregateBits...)
   221  	s.seenUnAggregatedAttestationCache.Add(string(b), true)
   222  }
   223  
   224  // hasBlockAndState returns true if the beacon node knows about a block and associated state in the
   225  // database or cache.
   226  func (s *Service) hasBlockAndState(ctx context.Context, blockRoot [32]byte) bool {
   227  	hasStateSummary := s.cfg.DB.HasStateSummary(ctx, blockRoot)
   228  	hasState := hasStateSummary || s.cfg.DB.HasState(ctx, blockRoot)
   229  	hasBlock := s.cfg.Chain.HasInitSyncBlock(blockRoot) || s.cfg.DB.HasBlock(ctx, blockRoot)
   230  	return hasState && hasBlock
   231  }