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