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, ðpb.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 := ðpb.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, ðpb.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 := ðpb.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 := ðpb.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 := ðpb.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, ðpb.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 }