github.com/prysmaticlabs/prysm@v1.4.4/validator/client/aggregate.go (about)

     1  package client
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"time"
     7  
     8  	types "github.com/prysmaticlabs/eth2-types"
     9  	"github.com/prysmaticlabs/prysm/beacon-chain/core/helpers"
    10  	ethpb "github.com/prysmaticlabs/prysm/proto/eth/v1alpha1"
    11  	validatorpb "github.com/prysmaticlabs/prysm/proto/validator/accounts/v2"
    12  	"github.com/prysmaticlabs/prysm/shared/bls"
    13  	"github.com/prysmaticlabs/prysm/shared/params"
    14  	"github.com/prysmaticlabs/prysm/shared/slotutil"
    15  	"github.com/prysmaticlabs/prysm/shared/timeutils"
    16  	"github.com/prysmaticlabs/prysm/shared/traceutil"
    17  	"go.opencensus.io/trace"
    18  	"google.golang.org/grpc/codes"
    19  	"google.golang.org/grpc/status"
    20  )
    21  
    22  // SubmitAggregateAndProof submits the validator's signed slot signature to the beacon node
    23  // via gRPC. Beacon node will verify the slot signature and determine if the validator is also
    24  // an aggregator. If yes, then beacon node will broadcast aggregated signature and
    25  // proof on the validator's behalf.
    26  func (v *validator) SubmitAggregateAndProof(ctx context.Context, slot types.Slot, pubKey [48]byte) {
    27  	ctx, span := trace.StartSpan(ctx, "validator.SubmitAggregateAndProof")
    28  	defer span.End()
    29  
    30  	span.AddAttributes(trace.StringAttribute("validator", fmt.Sprintf("%#x", pubKey)))
    31  	fmtKey := fmt.Sprintf("%#x", pubKey[:])
    32  
    33  	duty, err := v.duty(pubKey)
    34  	if err != nil {
    35  		log.Errorf("Could not fetch validator assignment: %v", err)
    36  		if v.emitAccountMetrics {
    37  			ValidatorAggFailVec.WithLabelValues(fmtKey).Inc()
    38  		}
    39  		return
    40  	}
    41  
    42  	// Avoid sending beacon node duplicated aggregation requests.
    43  	k := validatorSubscribeKey(slot, duty.CommitteeIndex)
    44  	v.aggregatedSlotCommitteeIDCacheLock.Lock()
    45  	if v.aggregatedSlotCommitteeIDCache.Contains(k) {
    46  		v.aggregatedSlotCommitteeIDCacheLock.Unlock()
    47  		return
    48  	}
    49  	v.aggregatedSlotCommitteeIDCache.Add(k, true)
    50  	v.aggregatedSlotCommitteeIDCacheLock.Unlock()
    51  
    52  	slotSig, err := v.signSlotWithSelectionProof(ctx, pubKey, slot)
    53  	if err != nil {
    54  		log.Errorf("Could not sign slot: %v", err)
    55  		if v.emitAccountMetrics {
    56  			ValidatorAggFailVec.WithLabelValues(fmtKey).Inc()
    57  		}
    58  		return
    59  	}
    60  
    61  	// As specified in spec, an aggregator should wait until two thirds of the way through slot
    62  	// to broadcast the best aggregate to the global aggregate channel.
    63  	// https://github.com/ethereum/eth2.0-specs/blob/v0.9.3/specs/validator/0_beacon-chain-validator.md#broadcast-aggregate
    64  	v.waitToSlotTwoThirds(ctx, slot)
    65  
    66  	res, err := v.validatorClient.SubmitAggregateSelectionProof(ctx, &ethpb.AggregateSelectionRequest{
    67  		Slot:           slot,
    68  		CommitteeIndex: duty.CommitteeIndex,
    69  		PublicKey:      pubKey[:],
    70  		SlotSignature:  slotSig,
    71  	})
    72  	if err != nil {
    73  		s, ok := status.FromError(err)
    74  		if ok && s.Code() == codes.NotFound {
    75  			log.WithField("slot", slot).WithError(err).Warn("No attestations to aggregate")
    76  		} else {
    77  			log.WithField("slot", slot).WithError(err).Error("Could not submit slot signature to beacon node")
    78  			if v.emitAccountMetrics {
    79  				ValidatorAggFailVec.WithLabelValues(fmtKey).Inc()
    80  			}
    81  		}
    82  
    83  		return
    84  	}
    85  
    86  	sig, err := v.aggregateAndProofSig(ctx, pubKey, res.AggregateAndProof)
    87  	if err != nil {
    88  		log.Errorf("Could not sign aggregate and proof: %v", err)
    89  		return
    90  	}
    91  	_, err = v.validatorClient.SubmitSignedAggregateSelectionProof(ctx, &ethpb.SignedAggregateSubmitRequest{
    92  		SignedAggregateAndProof: &ethpb.SignedAggregateAttestationAndProof{
    93  			Message:   res.AggregateAndProof,
    94  			Signature: sig,
    95  		},
    96  	})
    97  	if err != nil {
    98  		log.Errorf("Could not submit signed aggregate and proof to beacon node: %v", err)
    99  		if v.emitAccountMetrics {
   100  			ValidatorAggFailVec.WithLabelValues(fmtKey).Inc()
   101  		}
   102  		return
   103  	}
   104  
   105  	if err := v.addIndicesToLog(duty); err != nil {
   106  		log.Errorf("Could not add aggregator indices to logs: %v", err)
   107  		if v.emitAccountMetrics {
   108  			ValidatorAggFailVec.WithLabelValues(fmtKey).Inc()
   109  		}
   110  		return
   111  	}
   112  	if v.emitAccountMetrics {
   113  		ValidatorAggSuccessVec.WithLabelValues(fmtKey).Inc()
   114  	}
   115  
   116  }
   117  
   118  // Signs input slot with domain selection proof. This is used to create the signature for aggregator selection.
   119  func (v *validator) signSlotWithSelectionProof(ctx context.Context, pubKey [48]byte, slot types.Slot) (signature []byte, error error) {
   120  	domain, err := v.domainData(ctx, helpers.SlotToEpoch(slot), params.BeaconConfig().DomainSelectionProof[:])
   121  	if err != nil {
   122  		return nil, err
   123  	}
   124  
   125  	var sig bls.Signature
   126  	sszUint := types.SSZUint64(slot)
   127  	root, err := helpers.ComputeSigningRoot(&sszUint, domain.SignatureDomain)
   128  	if err != nil {
   129  		return nil, err
   130  	}
   131  	sig, err = v.keyManager.Sign(ctx, &validatorpb.SignRequest{
   132  		PublicKey:       pubKey[:],
   133  		SigningRoot:     root[:],
   134  		SignatureDomain: domain.SignatureDomain,
   135  		Object:          &validatorpb.SignRequest_Slot{Slot: slot},
   136  	})
   137  	if err != nil {
   138  		return nil, err
   139  	}
   140  
   141  	return sig.Marshal(), nil
   142  }
   143  
   144  // waitToSlotTwoThirds waits until two third through the current slot period
   145  // such that any attestations from this slot have time to reach the beacon node
   146  // before creating the aggregated attestation.
   147  func (v *validator) waitToSlotTwoThirds(ctx context.Context, slot types.Slot) {
   148  	ctx, span := trace.StartSpan(ctx, "validator.waitToSlotTwoThirds")
   149  	defer span.End()
   150  
   151  	oneThird := slotutil.DivideSlotBy(3 /* one third of slot duration */)
   152  	twoThird := oneThird + oneThird
   153  	delay := twoThird
   154  
   155  	startTime := slotutil.SlotStartTime(v.genesisTime, slot)
   156  	finalTime := startTime.Add(delay)
   157  	wait := timeutils.Until(finalTime)
   158  	if wait <= 0 {
   159  		return
   160  	}
   161  	t := time.NewTimer(wait)
   162  	defer t.Stop()
   163  	select {
   164  	case <-ctx.Done():
   165  		traceutil.AnnotateError(span, ctx.Err())
   166  		return
   167  	case <-t.C:
   168  		return
   169  	}
   170  }
   171  
   172  // This returns the signature of validator signing over aggregate and
   173  // proof object.
   174  func (v *validator) aggregateAndProofSig(ctx context.Context, pubKey [48]byte, agg *ethpb.AggregateAttestationAndProof) ([]byte, error) {
   175  	d, err := v.domainData(ctx, helpers.SlotToEpoch(agg.Aggregate.Data.Slot), params.BeaconConfig().DomainAggregateAndProof[:])
   176  	if err != nil {
   177  		return nil, err
   178  	}
   179  	var sig bls.Signature
   180  	root, err := helpers.ComputeSigningRoot(agg, d.SignatureDomain)
   181  	if err != nil {
   182  		return nil, err
   183  	}
   184  	sig, err = v.keyManager.Sign(ctx, &validatorpb.SignRequest{
   185  		PublicKey:       pubKey[:],
   186  		SigningRoot:     root[:],
   187  		SignatureDomain: d.SignatureDomain,
   188  		Object:          &validatorpb.SignRequest_AggregateAttestationAndProof{AggregateAttestationAndProof: agg},
   189  	})
   190  	if err != nil {
   191  		return nil, err
   192  	}
   193  
   194  	return sig.Marshal(), nil
   195  }
   196  
   197  func (v *validator) addIndicesToLog(duty *ethpb.DutiesResponse_Duty) error {
   198  	v.attLogsLock.Lock()
   199  	defer v.attLogsLock.Unlock()
   200  
   201  	for _, log := range v.attLogs {
   202  		if duty.CommitteeIndex == log.data.CommitteeIndex {
   203  			log.aggregatorIndices = append(log.aggregatorIndices, duty.ValidatorIndex)
   204  		}
   205  	}
   206  
   207  	return nil
   208  }