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

     1  package client
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"errors"
     7  	"fmt"
     8  	"strings"
     9  	"time"
    10  
    11  	types "github.com/prysmaticlabs/eth2-types"
    12  	"github.com/prysmaticlabs/go-bitfield"
    13  	"github.com/prysmaticlabs/prysm/beacon-chain/core/helpers"
    14  	ethpb "github.com/prysmaticlabs/prysm/proto/eth/v1alpha1"
    15  	validatorpb "github.com/prysmaticlabs/prysm/proto/validator/accounts/v2"
    16  	"github.com/prysmaticlabs/prysm/shared/bytesutil"
    17  	"github.com/prysmaticlabs/prysm/shared/featureconfig"
    18  	"github.com/prysmaticlabs/prysm/shared/hashutil"
    19  	"github.com/prysmaticlabs/prysm/shared/mputil"
    20  	"github.com/prysmaticlabs/prysm/shared/params"
    21  	"github.com/prysmaticlabs/prysm/shared/slotutil"
    22  	"github.com/prysmaticlabs/prysm/shared/timeutils"
    23  	"github.com/prysmaticlabs/prysm/shared/traceutil"
    24  	"github.com/prysmaticlabs/prysm/validator/client/iface"
    25  	"github.com/sirupsen/logrus"
    26  	"go.opencensus.io/trace"
    27  )
    28  
    29  // SubmitAttestation completes the validator client's attester responsibility at a given slot.
    30  // It fetches the latest beacon block head along with the latest canonical beacon state
    31  // information in order to sign the block and include information about the validator's
    32  // participation in voting on the block.
    33  func (v *validator) SubmitAttestation(ctx context.Context, slot types.Slot, pubKey [48]byte) {
    34  	ctx, span := trace.StartSpan(ctx, "validator.SubmitAttestation")
    35  	defer span.End()
    36  	span.AddAttributes(trace.StringAttribute("validator", fmt.Sprintf("%#x", pubKey)))
    37  
    38  	v.waitOneThirdOrValidBlock(ctx, slot)
    39  
    40  	var b strings.Builder
    41  	if err := b.WriteByte(byte(iface.RoleAttester)); err != nil {
    42  		log.WithError(err).Error("Could not write role byte for lock key")
    43  		traceutil.AnnotateError(span, err)
    44  		return
    45  	}
    46  	_, err := b.Write(pubKey[:])
    47  	if err != nil {
    48  		log.WithError(err).Error("Could not write pubkey bytes for lock key")
    49  		traceutil.AnnotateError(span, err)
    50  		return
    51  	}
    52  	lock := mputil.NewMultilock(b.String())
    53  	lock.Lock()
    54  	defer lock.Unlock()
    55  
    56  	fmtKey := fmt.Sprintf("%#x", pubKey[:])
    57  	log := log.WithField("pubKey", fmt.Sprintf("%#x", bytesutil.Trunc(pubKey[:]))).WithField("slot", slot)
    58  	duty, err := v.duty(pubKey)
    59  	if err != nil {
    60  		log.WithError(err).Error("Could not fetch validator assignment")
    61  		if v.emitAccountMetrics {
    62  			ValidatorAttestFailVec.WithLabelValues(fmtKey).Inc()
    63  		}
    64  		traceutil.AnnotateError(span, err)
    65  		return
    66  	}
    67  	if len(duty.Committee) == 0 {
    68  		log.Debug("Empty committee for validator duty, not attesting")
    69  		return
    70  	}
    71  
    72  	req := &ethpb.AttestationDataRequest{
    73  		Slot:           slot,
    74  		CommitteeIndex: duty.CommitteeIndex,
    75  	}
    76  	data, err := v.validatorClient.GetAttestationData(ctx, req)
    77  	if err != nil {
    78  		log.WithError(err).Error("Could not request attestation to sign at slot")
    79  		if v.emitAccountMetrics {
    80  			ValidatorAttestFailVec.WithLabelValues(fmtKey).Inc()
    81  		}
    82  		traceutil.AnnotateError(span, err)
    83  		return
    84  	}
    85  
    86  	indexedAtt := &ethpb.IndexedAttestation{
    87  		AttestingIndices: []uint64{uint64(duty.ValidatorIndex)},
    88  		Data:             data,
    89  	}
    90  
    91  	_, signingRoot, err := v.getDomainAndSigningRoot(ctx, indexedAtt.Data)
    92  	if err != nil {
    93  		log.WithError(err).Error("Could not get domain and signing root from attestation")
    94  		if v.emitAccountMetrics {
    95  			ValidatorAttestFailVec.WithLabelValues(fmtKey).Inc()
    96  		}
    97  		traceutil.AnnotateError(span, err)
    98  		return
    99  	}
   100  
   101  	sig, _, err := v.signAtt(ctx, pubKey, data)
   102  	if err != nil {
   103  		log.WithError(err).Error("Could not sign attestation")
   104  		if v.emitAccountMetrics {
   105  			ValidatorAttestFailVec.WithLabelValues(fmtKey).Inc()
   106  		}
   107  		traceutil.AnnotateError(span, err)
   108  		return
   109  	}
   110  
   111  	var indexInCommittee uint64
   112  	var found bool
   113  	for i, vID := range duty.Committee {
   114  		if vID == duty.ValidatorIndex {
   115  			indexInCommittee = uint64(i)
   116  			found = true
   117  			break
   118  		}
   119  	}
   120  	if !found {
   121  		log.Errorf("Validator ID %d not found in committee of %v", duty.ValidatorIndex, duty.Committee)
   122  		if v.emitAccountMetrics {
   123  			ValidatorAttestFailVec.WithLabelValues(fmtKey).Inc()
   124  		}
   125  		return
   126  	}
   127  
   128  	aggregationBitfield := bitfield.NewBitlist(uint64(len(duty.Committee)))
   129  	aggregationBitfield.SetBitAt(indexInCommittee, true)
   130  	attestation := &ethpb.Attestation{
   131  		Data:            data,
   132  		AggregationBits: aggregationBitfield,
   133  		Signature:       sig,
   134  	}
   135  
   136  	// Set the signature of the attestation and send it out to the beacon node.
   137  	indexedAtt.Signature = sig
   138  	if err := v.slashableAttestationCheck(ctx, indexedAtt, pubKey, signingRoot); err != nil {
   139  		log.WithError(err).Error("Failed attestation slashing protection check")
   140  		log.WithFields(
   141  			attestationLogFields(pubKey, indexedAtt),
   142  		).Debug("Attempted slashable attestation details")
   143  		traceutil.AnnotateError(span, err)
   144  		return
   145  	}
   146  	attResp, err := v.validatorClient.ProposeAttestation(ctx, attestation)
   147  	if err != nil {
   148  		log.WithError(err).Error("Could not submit attestation to beacon node")
   149  		if v.emitAccountMetrics {
   150  			ValidatorAttestFailVec.WithLabelValues(fmtKey).Inc()
   151  		}
   152  		traceutil.AnnotateError(span, err)
   153  		return
   154  	}
   155  
   156  	if err := v.saveAttesterIndexToData(data, duty.ValidatorIndex); err != nil {
   157  		log.WithError(err).Error("Could not save validator index for logging")
   158  		if v.emitAccountMetrics {
   159  			ValidatorAttestFailVec.WithLabelValues(fmtKey).Inc()
   160  		}
   161  		traceutil.AnnotateError(span, err)
   162  		return
   163  	}
   164  
   165  	span.AddAttributes(
   166  		trace.Int64Attribute("slot", int64(slot)),
   167  		trace.StringAttribute("attestationHash", fmt.Sprintf("%#x", attResp.AttestationDataRoot)),
   168  		trace.Int64Attribute("committeeIndex", int64(data.CommitteeIndex)),
   169  		trace.StringAttribute("blockRoot", fmt.Sprintf("%#x", data.BeaconBlockRoot)),
   170  		trace.Int64Attribute("justifiedEpoch", int64(data.Source.Epoch)),
   171  		trace.Int64Attribute("targetEpoch", int64(data.Target.Epoch)),
   172  		trace.StringAttribute("bitfield", fmt.Sprintf("%#x", aggregationBitfield)),
   173  	)
   174  
   175  	if v.emitAccountMetrics {
   176  		ValidatorAttestSuccessVec.WithLabelValues(fmtKey).Inc()
   177  		ValidatorAttestedSlotsGaugeVec.WithLabelValues(fmtKey).Set(float64(slot))
   178  	}
   179  }
   180  
   181  // Given the validator public key, this gets the validator assignment.
   182  func (v *validator) duty(pubKey [48]byte) (*ethpb.DutiesResponse_Duty, error) {
   183  	if v.duties == nil {
   184  		return nil, errors.New("no duties for validators")
   185  	}
   186  
   187  	for _, duty := range v.duties.Duties {
   188  		if bytes.Equal(pubKey[:], duty.PublicKey) {
   189  			return duty, nil
   190  		}
   191  	}
   192  
   193  	return nil, fmt.Errorf("pubkey %#x not in duties", bytesutil.Trunc(pubKey[:]))
   194  }
   195  
   196  // Given validator's public key, this function returns the signature of an attestation data and its signing root.
   197  func (v *validator) signAtt(ctx context.Context, pubKey [48]byte, data *ethpb.AttestationData) ([]byte, [32]byte, error) {
   198  	domain, root, err := v.getDomainAndSigningRoot(ctx, data)
   199  	if err != nil {
   200  		return nil, [32]byte{}, err
   201  	}
   202  
   203  	sig, err := v.keyManager.Sign(ctx, &validatorpb.SignRequest{
   204  		PublicKey:       pubKey[:],
   205  		SigningRoot:     root[:],
   206  		SignatureDomain: domain.SignatureDomain,
   207  		Object:          &validatorpb.SignRequest_AttestationData{AttestationData: data},
   208  	})
   209  	if err != nil {
   210  		return nil, [32]byte{}, err
   211  	}
   212  
   213  	return sig.Marshal(), root, nil
   214  }
   215  
   216  func (v *validator) getDomainAndSigningRoot(ctx context.Context, data *ethpb.AttestationData) (*ethpb.DomainResponse, [32]byte, error) {
   217  	domain, err := v.domainData(ctx, data.Target.Epoch, params.BeaconConfig().DomainBeaconAttester[:])
   218  	if err != nil {
   219  		return nil, [32]byte{}, err
   220  	}
   221  	root, err := helpers.ComputeSigningRoot(data, domain.SignatureDomain)
   222  	if err != nil {
   223  		return nil, [32]byte{}, err
   224  	}
   225  	return domain, root, nil
   226  }
   227  
   228  // For logging, this saves the last submitted attester index to its attestation data. The purpose of this
   229  // is to enhance attesting logs to be readable when multiple validator keys ran in a single client.
   230  func (v *validator) saveAttesterIndexToData(data *ethpb.AttestationData, index types.ValidatorIndex) error {
   231  	v.attLogsLock.Lock()
   232  	defer v.attLogsLock.Unlock()
   233  
   234  	h, err := hashutil.HashProto(data)
   235  	if err != nil {
   236  		return err
   237  	}
   238  
   239  	if v.attLogs[h] == nil {
   240  		v.attLogs[h] = &attSubmitted{data, []types.ValidatorIndex{}, []types.ValidatorIndex{}}
   241  	}
   242  	v.attLogs[h] = &attSubmitted{data, append(v.attLogs[h].attesterIndices, index), []types.ValidatorIndex{}}
   243  
   244  	return nil
   245  }
   246  
   247  // waitOneThirdOrValidBlock waits until (a) or (b) whichever comes first:
   248  //   (a) the validator has received a valid block that is the same slot as input slot
   249  //   (b) one-third of the slot has transpired (SECONDS_PER_SLOT / 3 seconds after the start of slot)
   250  func (v *validator) waitOneThirdOrValidBlock(ctx context.Context, slot types.Slot) {
   251  	ctx, span := trace.StartSpan(ctx, "validator.waitOneThirdOrValidBlock")
   252  	defer span.End()
   253  
   254  	// Don't need to wait if requested slot is the same as highest valid slot.
   255  	if slot <= v.highestValidSlot {
   256  		return
   257  	}
   258  
   259  	delay := slotutil.DivideSlotBy(3 /* a third of the slot duration */)
   260  	startTime := slotutil.SlotStartTime(v.genesisTime, slot)
   261  	finalTime := startTime.Add(delay)
   262  	wait := timeutils.Until(finalTime)
   263  	if wait <= 0 {
   264  		return
   265  	}
   266  	t := time.NewTimer(wait)
   267  	defer t.Stop()
   268  
   269  	bChannel := make(chan *ethpb.SignedBeaconBlock, 1)
   270  	sub := v.blockFeed.Subscribe(bChannel)
   271  	defer sub.Unsubscribe()
   272  
   273  	for {
   274  		select {
   275  		case b := <-bChannel:
   276  			if featureconfig.Get().AttestTimely {
   277  				if slot <= b.Block.Slot {
   278  					return
   279  				}
   280  			}
   281  		case <-ctx.Done():
   282  			traceutil.AnnotateError(span, ctx.Err())
   283  			return
   284  		case <-sub.Err():
   285  			log.Error("Subscriber closed, exiting goroutine")
   286  			return
   287  		case <-t.C:
   288  			return
   289  		}
   290  	}
   291  }
   292  
   293  func attestationLogFields(pubKey [48]byte, indexedAtt *ethpb.IndexedAttestation) logrus.Fields {
   294  	return logrus.Fields{
   295  		"attesterPublicKey": fmt.Sprintf("%#x", pubKey),
   296  		"attestationSlot":   indexedAtt.Data.Slot,
   297  		"committeeIndex":    indexedAtt.Data.CommitteeIndex,
   298  		"beaconBlockRoot":   fmt.Sprintf("%#x", indexedAtt.Data.BeaconBlockRoot),
   299  		"sourceEpoch":       indexedAtt.Data.Source.Epoch,
   300  		"sourceRoot":        fmt.Sprintf("%#x", indexedAtt.Data.Source.Root),
   301  		"targetEpoch":       indexedAtt.Data.Target.Epoch,
   302  		"targetRoot":        fmt.Sprintf("%#x", indexedAtt.Data.Target.Root),
   303  		"signature":         fmt.Sprintf("%#x", indexedAtt.Signature),
   304  	}
   305  }