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

     1  package client
     2  
     3  // Validator client proposer functions.
     4  import (
     5  	"context"
     6  	"fmt"
     7  	"time"
     8  
     9  	"github.com/pkg/errors"
    10  	types "github.com/prysmaticlabs/eth2-types"
    11  	"github.com/prysmaticlabs/prysm/beacon-chain/core/helpers"
    12  	ethpb "github.com/prysmaticlabs/prysm/proto/eth/v1alpha1"
    13  	validatorpb "github.com/prysmaticlabs/prysm/proto/validator/accounts/v2"
    14  	"github.com/prysmaticlabs/prysm/shared/bls"
    15  	"github.com/prysmaticlabs/prysm/shared/bytesutil"
    16  	"github.com/prysmaticlabs/prysm/shared/mputil"
    17  	"github.com/prysmaticlabs/prysm/shared/params"
    18  	"github.com/prysmaticlabs/prysm/shared/rand"
    19  	"github.com/prysmaticlabs/prysm/shared/timeutils"
    20  	"github.com/prysmaticlabs/prysm/validator/client/iface"
    21  	"github.com/sirupsen/logrus"
    22  	"go.opencensus.io/trace"
    23  	"google.golang.org/protobuf/types/known/emptypb"
    24  )
    25  
    26  type signingFunc func(context.Context, *validatorpb.SignRequest) (bls.Signature, error)
    27  
    28  const domainDataErr = "could not get domain data"
    29  const signingRootErr = "could not get signing root"
    30  const signExitErr = "could not sign voluntary exit proposal"
    31  
    32  // ProposeBlock proposes a new beacon block for a given slot. This method collects the
    33  // previous beacon block, any pending deposits, and ETH1 data from the beacon
    34  // chain node to construct the new block. The new block is then processed with
    35  // the state root computation, and finally signed by the validator before being
    36  // sent back to the beacon node for broadcasting.
    37  func (v *validator) ProposeBlock(ctx context.Context, slot types.Slot, pubKey [48]byte) {
    38  	if slot == 0 {
    39  		log.Debug("Assigned to genesis slot, skipping proposal")
    40  		return
    41  	}
    42  	lock := mputil.NewMultilock(fmt.Sprint(iface.RoleProposer), string(pubKey[:]))
    43  	lock.Lock()
    44  	defer lock.Unlock()
    45  	ctx, span := trace.StartSpan(ctx, "validator.ProposeBlock")
    46  	defer span.End()
    47  	fmtKey := fmt.Sprintf("%#x", pubKey[:])
    48  
    49  	span.AddAttributes(trace.StringAttribute("validator", fmt.Sprintf("%#x", pubKey)))
    50  	log := log.WithField("pubKey", fmt.Sprintf("%#x", bytesutil.Trunc(pubKey[:])))
    51  
    52  	// Sign randao reveal, it's used to request block from beacon node
    53  	epoch := types.Epoch(slot / params.BeaconConfig().SlotsPerEpoch)
    54  	randaoReveal, err := v.signRandaoReveal(ctx, pubKey, epoch)
    55  	if err != nil {
    56  		log.WithError(err).Error("Failed to sign randao reveal")
    57  		if v.emitAccountMetrics {
    58  			ValidatorProposeFailVec.WithLabelValues(fmtKey).Inc()
    59  		}
    60  		return
    61  	}
    62  
    63  	g, err := v.getGraffiti(ctx, pubKey)
    64  	if err != nil {
    65  		// Graffiti is not a critical enough to fail block production and cause
    66  		// validator to miss block reward. When failed, validator should continue
    67  		// to produce the block.
    68  		log.WithError(err).Warn("Could not get graffiti")
    69  	}
    70  
    71  	// Request block from beacon node
    72  	b, err := v.validatorClient.GetBlock(ctx, &ethpb.BlockRequest{
    73  		Slot:         slot,
    74  		RandaoReveal: randaoReveal,
    75  		Graffiti:     g,
    76  	})
    77  	if err != nil {
    78  		log.WithField("blockSlot", slot).WithError(err).Error("Failed to request block from beacon node")
    79  		if v.emitAccountMetrics {
    80  			ValidatorProposeFailVec.WithLabelValues(fmtKey).Inc()
    81  		}
    82  		return
    83  	}
    84  
    85  	// Sign returned block from beacon node
    86  	sig, domain, err := v.signBlock(ctx, pubKey, epoch, b)
    87  	if err != nil {
    88  		log.WithError(err).Error("Failed to sign block")
    89  		if v.emitAccountMetrics {
    90  			ValidatorProposeFailVec.WithLabelValues(fmtKey).Inc()
    91  		}
    92  		return
    93  	}
    94  	blk := &ethpb.SignedBeaconBlock{
    95  		Block:     b,
    96  		Signature: sig,
    97  	}
    98  
    99  	signingRoot, err := helpers.ComputeSigningRoot(b, domain.SignatureDomain)
   100  	if err != nil {
   101  		if v.emitAccountMetrics {
   102  			ValidatorProposeFailVec.WithLabelValues(fmtKey).Inc()
   103  		}
   104  		log.WithError(err).Error("Failed to compute signing root for block")
   105  		return
   106  	}
   107  
   108  	if err := v.preBlockSignValidations(ctx, pubKey, b, signingRoot); err != nil {
   109  		log.WithFields(
   110  			blockLogFields(pubKey, b, nil),
   111  		).WithError(err).Error("Failed block slashing protection check")
   112  		return
   113  	}
   114  
   115  	if err := v.postBlockSignUpdate(ctx, pubKey, blk, signingRoot); err != nil {
   116  		log.WithFields(
   117  			blockLogFields(pubKey, b, sig),
   118  		).WithError(err).Error("Failed block slashing protection check")
   119  		return
   120  	}
   121  
   122  	// Propose and broadcast block via beacon node
   123  	blkResp, err := v.validatorClient.ProposeBlock(ctx, blk)
   124  	if err != nil {
   125  		log.WithError(err).Error("Failed to propose block")
   126  		if v.emitAccountMetrics {
   127  			ValidatorProposeFailVec.WithLabelValues(fmtKey).Inc()
   128  		}
   129  		return
   130  	}
   131  
   132  	span.AddAttributes(
   133  		trace.StringAttribute("blockRoot", fmt.Sprintf("%#x", blkResp.BlockRoot)),
   134  		trace.Int64Attribute("numDeposits", int64(len(b.Body.Deposits))),
   135  		trace.Int64Attribute("numAttestations", int64(len(b.Body.Attestations))),
   136  	)
   137  
   138  	blkRoot := fmt.Sprintf("%#x", bytesutil.Trunc(blkResp.BlockRoot))
   139  	log.WithFields(logrus.Fields{
   140  		"slot":            b.Slot,
   141  		"blockRoot":       blkRoot,
   142  		"numAttestations": len(b.Body.Attestations),
   143  		"numDeposits":     len(b.Body.Deposits),
   144  		"graffiti":        string(b.Body.Graffiti),
   145  	}).Info("Submitted new block")
   146  
   147  	if v.emitAccountMetrics {
   148  		ValidatorProposeSuccessVec.WithLabelValues(fmtKey).Inc()
   149  	}
   150  }
   151  
   152  // ProposeExit performs a voluntary exit on a validator.
   153  // The exit is signed by the validator before being sent to the beacon node for broadcasting.
   154  func ProposeExit(
   155  	ctx context.Context,
   156  	validatorClient ethpb.BeaconNodeValidatorClient,
   157  	nodeClient ethpb.NodeClient,
   158  	signer signingFunc,
   159  	pubKey []byte,
   160  ) error {
   161  	ctx, span := trace.StartSpan(ctx, "validator.ProposeExit")
   162  	defer span.End()
   163  
   164  	indexResponse, err := validatorClient.ValidatorIndex(ctx, &ethpb.ValidatorIndexRequest{PublicKey: pubKey})
   165  	if err != nil {
   166  		return errors.Wrap(err, "gRPC call to get validator index failed")
   167  	}
   168  	genesisResponse, err := nodeClient.GetGenesis(ctx, &emptypb.Empty{})
   169  	if err != nil {
   170  		return errors.Wrap(err, "gRPC call to get genesis time failed")
   171  	}
   172  	totalSecondsPassed := timeutils.Now().Unix() - genesisResponse.GenesisTime.Seconds
   173  	currentEpoch := types.Epoch(uint64(totalSecondsPassed) / uint64(params.BeaconConfig().SlotsPerEpoch.Mul(params.BeaconConfig().SecondsPerSlot)))
   174  
   175  	exit := &ethpb.VoluntaryExit{Epoch: currentEpoch, ValidatorIndex: indexResponse.Index}
   176  	sig, err := signVoluntaryExit(ctx, validatorClient, signer, pubKey, exit)
   177  	if err != nil {
   178  		return errors.Wrap(err, "failed to sign voluntary exit")
   179  	}
   180  
   181  	signedExit := &ethpb.SignedVoluntaryExit{Exit: exit, Signature: sig}
   182  	exitResp, err := validatorClient.ProposeExit(ctx, signedExit)
   183  	if err != nil {
   184  		return errors.Wrap(err, "failed to propose voluntary exit")
   185  	}
   186  
   187  	span.AddAttributes(
   188  		trace.StringAttribute("exitRoot", fmt.Sprintf("%#x", exitResp.ExitRoot)),
   189  	)
   190  
   191  	return nil
   192  }
   193  
   194  // Sign randao reveal with randao domain and private key.
   195  func (v *validator) signRandaoReveal(ctx context.Context, pubKey [48]byte, epoch types.Epoch) ([]byte, error) {
   196  	domain, err := v.domainData(ctx, epoch, params.BeaconConfig().DomainRandao[:])
   197  	if err != nil {
   198  		return nil, errors.Wrap(err, domainDataErr)
   199  	}
   200  	if domain == nil {
   201  		return nil, errors.New(domainDataErr)
   202  	}
   203  
   204  	var randaoReveal bls.Signature
   205  	sszUint := types.SSZUint64(epoch)
   206  	root, err := helpers.ComputeSigningRoot(&sszUint, domain.SignatureDomain)
   207  	if err != nil {
   208  		return nil, err
   209  	}
   210  	randaoReveal, err = v.keyManager.Sign(ctx, &validatorpb.SignRequest{
   211  		PublicKey:       pubKey[:],
   212  		SigningRoot:     root[:],
   213  		SignatureDomain: domain.SignatureDomain,
   214  		Object:          &validatorpb.SignRequest_Epoch{Epoch: epoch},
   215  	})
   216  	if err != nil {
   217  		return nil, err
   218  	}
   219  	return randaoReveal.Marshal(), nil
   220  }
   221  
   222  // Sign block with proposer domain and private key.
   223  func (v *validator) signBlock(ctx context.Context, pubKey [48]byte, epoch types.Epoch, b *ethpb.BeaconBlock) ([]byte, *ethpb.DomainResponse, error) {
   224  	domain, err := v.domainData(ctx, epoch, params.BeaconConfig().DomainBeaconProposer[:])
   225  	if err != nil {
   226  		return nil, nil, errors.Wrap(err, domainDataErr)
   227  	}
   228  	if domain == nil {
   229  		return nil, nil, errors.New(domainDataErr)
   230  	}
   231  
   232  	var sig bls.Signature
   233  	blockRoot, err := helpers.ComputeSigningRoot(b, domain.SignatureDomain)
   234  	if err != nil {
   235  		return nil, nil, errors.Wrap(err, signingRootErr)
   236  	}
   237  	sig, err = v.keyManager.Sign(ctx, &validatorpb.SignRequest{
   238  		PublicKey:       pubKey[:],
   239  		SigningRoot:     blockRoot[:],
   240  		SignatureDomain: domain.SignatureDomain,
   241  		Object:          &validatorpb.SignRequest_Block{Block: b},
   242  	})
   243  	if err != nil {
   244  		return nil, nil, errors.Wrap(err, "could not sign block proposal")
   245  	}
   246  	return sig.Marshal(), domain, nil
   247  }
   248  
   249  // Sign voluntary exit with proposer domain and private key.
   250  func signVoluntaryExit(
   251  	ctx context.Context,
   252  	validatorClient ethpb.BeaconNodeValidatorClient,
   253  	signer signingFunc,
   254  	pubKey []byte,
   255  	exit *ethpb.VoluntaryExit,
   256  ) ([]byte, error) {
   257  	req := &ethpb.DomainRequest{
   258  		Epoch:  exit.Epoch,
   259  		Domain: params.BeaconConfig().DomainVoluntaryExit[:],
   260  	}
   261  
   262  	domain, err := validatorClient.DomainData(ctx, req)
   263  	if err != nil {
   264  		return nil, errors.Wrap(err, domainDataErr)
   265  	}
   266  	if domain == nil {
   267  		return nil, errors.New(domainDataErr)
   268  	}
   269  
   270  	exitRoot, err := helpers.ComputeSigningRoot(exit, domain.SignatureDomain)
   271  	if err != nil {
   272  		return nil, errors.Wrap(err, signingRootErr)
   273  	}
   274  
   275  	sig, err := signer(ctx, &validatorpb.SignRequest{
   276  		PublicKey:       pubKey,
   277  		SigningRoot:     exitRoot[:],
   278  		SignatureDomain: domain.SignatureDomain,
   279  		Object:          &validatorpb.SignRequest_Exit{Exit: exit},
   280  	})
   281  	if err != nil {
   282  		return nil, errors.Wrap(err, signExitErr)
   283  	}
   284  	return sig.Marshal(), nil
   285  }
   286  
   287  // Gets the graffiti from cli or file for the validator public key.
   288  func (v *validator) getGraffiti(ctx context.Context, pubKey [48]byte) ([]byte, error) {
   289  	// When specified, default graffiti from the command line takes the first priority.
   290  	if len(v.graffiti) != 0 {
   291  		return v.graffiti, nil
   292  	}
   293  
   294  	if v.graffitiStruct == nil {
   295  		return nil, errors.New("graffitiStruct can't be nil")
   296  	}
   297  
   298  	// When specified, individual validator specified graffiti takes the second priority.
   299  	idx, err := v.validatorClient.ValidatorIndex(ctx, &ethpb.ValidatorIndexRequest{PublicKey: pubKey[:]})
   300  	if err != nil {
   301  		return []byte{}, err
   302  	}
   303  	g, ok := v.graffitiStruct.Specific[idx.Index]
   304  	if ok {
   305  		return []byte(g), nil
   306  	}
   307  
   308  	// When specified, a graffiti from the ordered list in the file take third priority.
   309  	if v.graffitiOrderedIndex < uint64(len(v.graffitiStruct.Ordered)) {
   310  		graffiti := v.graffitiStruct.Ordered[v.graffitiOrderedIndex]
   311  		v.graffitiOrderedIndex = v.graffitiOrderedIndex + 1
   312  		err := v.db.SaveGraffitiOrderedIndex(ctx, v.graffitiOrderedIndex)
   313  		if err != nil {
   314  			return nil, errors.Wrap(err, "failed to update graffiti ordered index")
   315  		}
   316  		return []byte(graffiti), nil
   317  	}
   318  
   319  	// When specified, a graffiti from the random list in the file take fourth priority.
   320  	if len(v.graffitiStruct.Random) != 0 {
   321  		r := rand.NewGenerator()
   322  		r.Seed(time.Now().Unix())
   323  		i := r.Uint64() % uint64(len(v.graffitiStruct.Random))
   324  		return []byte(v.graffitiStruct.Random[i]), nil
   325  	}
   326  
   327  	// Finally, default graffiti if specified in the file will be used.
   328  	if v.graffitiStruct.Default != "" {
   329  		return []byte(v.graffitiStruct.Default), nil
   330  	}
   331  
   332  	return []byte{}, nil
   333  }