github.com/prysmaticlabs/prysm@v1.4.4/validator/db/kv/attester_protection.go (about) 1 package kv 2 3 import ( 4 "context" 5 "fmt" 6 "sync" 7 "time" 8 9 "github.com/pkg/errors" 10 types "github.com/prysmaticlabs/eth2-types" 11 ethpb "github.com/prysmaticlabs/prysm/proto/eth/v1alpha1" 12 "github.com/prysmaticlabs/prysm/shared/bytesutil" 13 "github.com/prysmaticlabs/prysm/shared/slashutil" 14 "github.com/prysmaticlabs/prysm/shared/traceutil" 15 bolt "go.etcd.io/bbolt" 16 "go.opencensus.io/trace" 17 ) 18 19 // SlashingKind used for helpful information upon detection. 20 type SlashingKind int 21 22 // AttestationRecord which can be represented by these simple values 23 // for manipulation by database methods. 24 type AttestationRecord struct { 25 PubKey [48]byte 26 Source types.Epoch 27 Target types.Epoch 28 SigningRoot [32]byte 29 } 30 31 // NewQueuedAttestationRecords constructor allocates the underlying slice and 32 // required attributes for managing pending attestation records. 33 func NewQueuedAttestationRecords() *QueuedAttestationRecords { 34 return &QueuedAttestationRecords{ 35 records: make([]*AttestationRecord, 0, attestationBatchCapacity), 36 } 37 } 38 39 // QueuedAttestationRecords is a thread-safe struct for managing a queue of 40 // attestation records to save to validator database. 41 type QueuedAttestationRecords struct { 42 records []*AttestationRecord 43 lock sync.RWMutex 44 } 45 46 // Append a new attestation record to the queue. 47 func (p *QueuedAttestationRecords) Append(ar *AttestationRecord) { 48 p.lock.Lock() 49 defer p.lock.Unlock() 50 p.records = append(p.records, ar) 51 } 52 53 // Flush all records. This method returns the current pending records and resets 54 // the pending records slice. 55 func (p *QueuedAttestationRecords) Flush() []*AttestationRecord { 56 p.lock.Lock() 57 defer p.lock.Unlock() 58 recs := p.records 59 p.records = make([]*AttestationRecord, 0, attestationBatchCapacity) 60 return recs 61 } 62 63 // Len returns the current length of records. 64 func (p *QueuedAttestationRecords) Len() int { 65 p.lock.RLock() 66 defer p.lock.RUnlock() 67 return len(p.records) 68 } 69 70 // A wrapper over an error received from a background routine 71 // saving batched attestations for slashing protection. 72 // This wrapper allows us to send this response over event feeds, 73 // as our event feed does not allow sending `nil` values to 74 // subscribers. 75 type saveAttestationsResponse struct { 76 err error 77 } 78 79 // Enums representing the types of slashable events for attesters. 80 const ( 81 NotSlashable SlashingKind = iota 82 DoubleVote 83 SurroundingVote 84 SurroundedVote 85 ) 86 87 var ( 88 doubleVoteMessage = "double vote found, existing attestation at target epoch %d with conflicting signing root %#x" 89 surroundingVoteMessage = "attestation with (source %d, target %d) surrounds another with (source %d, target %d)" 90 surroundedVoteMessage = "attestation with (source %d, target %d) is surrounded by another with (source %d, target %d)" 91 ) 92 93 // AttestationHistoryForPubKey retrieves a list of attestation records for data 94 // we have stored in the database for the given validator public key. 95 func (s *Store) AttestationHistoryForPubKey(ctx context.Context, pubKey [48]byte) ([]*AttestationRecord, error) { 96 records := make([]*AttestationRecord, 0) 97 ctx, span := trace.StartSpan(ctx, "Validator.AttestationHistoryForPubKey") 98 defer span.End() 99 err := s.view(func(tx *bolt.Tx) error { 100 bucket := tx.Bucket(pubKeysBucket) 101 pkBucket := bucket.Bucket(pubKey[:]) 102 if pkBucket == nil { 103 return nil 104 } 105 signingRootsBucket := pkBucket.Bucket(attestationSigningRootsBucket) 106 sourceEpochsBucket := pkBucket.Bucket(attestationSourceEpochsBucket) 107 108 return sourceEpochsBucket.ForEach(func(sourceBytes, targetEpochsList []byte) error { 109 targetEpochs := make([]types.Epoch, 0) 110 for i := 0; i < len(targetEpochsList); i += 8 { 111 epoch := bytesutil.BytesToEpochBigEndian(targetEpochsList[i : i+8]) 112 targetEpochs = append(targetEpochs, epoch) 113 } 114 sourceEpoch := bytesutil.BytesToEpochBigEndian(sourceBytes) 115 for _, targetEpoch := range targetEpochs { 116 record := &AttestationRecord{ 117 PubKey: pubKey, 118 Source: sourceEpoch, 119 Target: targetEpoch, 120 } 121 signingRoot := signingRootsBucket.Get(bytesutil.EpochToBytesBigEndian(targetEpoch)) 122 if signingRoot != nil { 123 copy(record.SigningRoot[:], signingRoot) 124 } 125 records = append(records, record) 126 } 127 return nil 128 }) 129 }) 130 return records, err 131 } 132 133 // CheckSlashableAttestation verifies an incoming attestation is 134 // not a double vote for a validator public key nor a surround vote. 135 func (s *Store) CheckSlashableAttestation( 136 ctx context.Context, pubKey [48]byte, signingRoot [32]byte, att *ethpb.IndexedAttestation, 137 ) (SlashingKind, error) { 138 ctx, span := trace.StartSpan(ctx, "Validator.CheckSlashableAttestation") 139 defer span.End() 140 var slashKind SlashingKind 141 err := s.view(func(tx *bolt.Tx) error { 142 if ctx.Err() != nil { 143 return ctx.Err() 144 } 145 bucket := tx.Bucket(pubKeysBucket) 146 pkBucket := bucket.Bucket(pubKey[:]) 147 if pkBucket == nil { 148 return nil 149 } 150 151 // First we check for double votes. 152 signingRootsBucket := pkBucket.Bucket(attestationSigningRootsBucket) 153 if signingRootsBucket != nil { 154 targetEpochBytes := bytesutil.EpochToBytesBigEndian(att.Data.Target.Epoch) 155 existingSigningRoot := signingRootsBucket.Get(targetEpochBytes) 156 if existingSigningRoot != nil { 157 var existing [32]byte 158 copy(existing[:], existingSigningRoot) 159 if slashutil.SigningRootsDiffer(existing, signingRoot) { 160 slashKind = DoubleVote 161 return fmt.Errorf(doubleVoteMessage, att.Data.Target.Epoch, existingSigningRoot) 162 } 163 } 164 } 165 166 sourceEpochsBucket := pkBucket.Bucket(attestationSourceEpochsBucket) 167 targetEpochsBucket := pkBucket.Bucket(attestationTargetEpochsBucket) 168 if sourceEpochsBucket == nil { 169 return nil 170 } 171 172 // Is this attestation surrounding any other? 173 var err error 174 slashKind, err = s.checkSurroundingVote(sourceEpochsBucket, att) 175 if err != nil { 176 return err 177 } 178 if targetEpochsBucket == nil { 179 return nil 180 } 181 182 // Is this attestation surrounded by any other? 183 slashKind, err = s.checkSurroundedVote(targetEpochsBucket, att) 184 if err != nil { 185 return err 186 } 187 return nil 188 }) 189 190 traceutil.AnnotateError(span, err) 191 return slashKind, err 192 } 193 194 // Iterate from the back of the bucket since we are looking for target_epoch > att.target_epoch 195 func (s *Store) checkSurroundedVote( 196 targetEpochsBucket *bolt.Bucket, att *ethpb.IndexedAttestation, 197 ) (SlashingKind, error) { 198 c := targetEpochsBucket.Cursor() 199 for k, v := c.Last(); k != nil; k, v = c.Prev() { 200 existingTargetEpoch := bytesutil.BytesToEpochBigEndian(k) 201 if existingTargetEpoch <= att.Data.Target.Epoch { 202 break 203 } 204 205 // There can be multiple source epochs attested per target epoch. 206 attestedSourceEpochs := make([]types.Epoch, 0, len(v)/8) 207 for i := 0; i < len(v); i += 8 { 208 sourceEpoch := bytesutil.BytesToEpochBigEndian(v[i : i+8]) 209 attestedSourceEpochs = append(attestedSourceEpochs, sourceEpoch) 210 } 211 212 for _, existingSourceEpoch := range attestedSourceEpochs { 213 existingAtt := ðpb.IndexedAttestation{ 214 Data: ðpb.AttestationData{ 215 Source: ðpb.Checkpoint{Epoch: existingSourceEpoch}, 216 Target: ðpb.Checkpoint{Epoch: existingTargetEpoch}, 217 }, 218 } 219 surrounded := slashutil.IsSurround(existingAtt, att) 220 if surrounded { 221 return SurroundedVote, fmt.Errorf( 222 surroundedVoteMessage, 223 att.Data.Source.Epoch, 224 att.Data.Target.Epoch, 225 existingSourceEpoch, 226 existingTargetEpoch, 227 ) 228 } 229 } 230 } 231 return NotSlashable, nil 232 } 233 234 // Iterate from the back of the bucket since we are looking for source_epoch > att.source_epoch 235 func (s *Store) checkSurroundingVote( 236 sourceEpochsBucket *bolt.Bucket, att *ethpb.IndexedAttestation, 237 ) (SlashingKind, error) { 238 c := sourceEpochsBucket.Cursor() 239 for k, v := c.Last(); k != nil; k, v = c.Prev() { 240 existingSourceEpoch := bytesutil.BytesToEpochBigEndian(k) 241 if existingSourceEpoch <= att.Data.Source.Epoch { 242 break 243 } 244 245 // There can be multiple target epochs attested per source epoch. 246 attestedTargetEpochs := make([]types.Epoch, 0, len(v)/8) 247 for i := 0; i < len(v); i += 8 { 248 targetEpoch := bytesutil.BytesToEpochBigEndian(v[i : i+8]) 249 attestedTargetEpochs = append(attestedTargetEpochs, targetEpoch) 250 } 251 252 for _, existingTargetEpoch := range attestedTargetEpochs { 253 existingAtt := ðpb.IndexedAttestation{ 254 Data: ðpb.AttestationData{ 255 Source: ðpb.Checkpoint{Epoch: existingSourceEpoch}, 256 Target: ðpb.Checkpoint{Epoch: existingTargetEpoch}, 257 }, 258 } 259 surrounding := slashutil.IsSurround(att, existingAtt) 260 if surrounding { 261 return SurroundingVote, fmt.Errorf( 262 surroundingVoteMessage, 263 att.Data.Source.Epoch, 264 att.Data.Target.Epoch, 265 existingSourceEpoch, 266 existingTargetEpoch, 267 ) 268 } 269 } 270 } 271 return NotSlashable, nil 272 } 273 274 // SaveAttestationsForPubKey stores a batch of attestations all at once. 275 func (s *Store) SaveAttestationsForPubKey( 276 ctx context.Context, pubKey [48]byte, signingRoots [][32]byte, atts []*ethpb.IndexedAttestation, 277 ) error { 278 ctx, span := trace.StartSpan(ctx, "Validator.SaveAttestationsForPubKey") 279 defer span.End() 280 if len(signingRoots) != len(atts) { 281 return fmt.Errorf( 282 "number of signing roots %d does not match number of attestations %d", 283 len(signingRoots), 284 len(atts), 285 ) 286 } 287 records := make([]*AttestationRecord, len(atts)) 288 for i, a := range atts { 289 records[i] = &AttestationRecord{ 290 PubKey: pubKey, 291 Source: a.Data.Source.Epoch, 292 Target: a.Data.Target.Epoch, 293 SigningRoot: signingRoots[i], 294 } 295 } 296 return s.saveAttestationRecords(ctx, records) 297 } 298 299 // SaveAttestationForPubKey saves an attestation for a validator public 300 // key for local validator slashing protection. 301 func (s *Store) SaveAttestationForPubKey( 302 ctx context.Context, pubKey [48]byte, signingRoot [32]byte, att *ethpb.IndexedAttestation, 303 ) error { 304 ctx, span := trace.StartSpan(ctx, "Validator.SaveAttestationForPubKey") 305 defer span.End() 306 s.batchedAttestationsChan <- &AttestationRecord{ 307 PubKey: pubKey, 308 Source: att.Data.Source.Epoch, 309 Target: att.Data.Target.Epoch, 310 SigningRoot: signingRoot, 311 } 312 // Subscribe to be notified when the attestation record queued 313 // for saving to the DB is indeed saved. If an error occurred 314 // during the process of saving the attestation record, the sender 315 // will give us that error. We use a buffered channel 316 // to prevent blocking the sender from notifying us of the result. 317 responseChan := make(chan saveAttestationsResponse, 1) 318 defer close(responseChan) 319 sub := s.batchAttestationsFlushedFeed.Subscribe(responseChan) 320 defer sub.Unsubscribe() 321 res := <-responseChan 322 return res.err 323 } 324 325 // Meant to run as a background routine, this function checks whether: 326 // (a) we have reached a max capacity of batched attestations in the Store or 327 // (b) attestationBatchWriteInterval has passed 328 // Based on whichever comes first, this function then proceeds 329 // to flush the attestations to the DB all at once in a single boltDB 330 // transaction for efficiency. Then, batched attestations slice is emptied out. 331 func (s *Store) batchAttestationWrites(ctx context.Context) { 332 ticker := time.NewTicker(attestationBatchWriteInterval) 333 defer ticker.Stop() 334 for { 335 select { 336 case v := <-s.batchedAttestationsChan: 337 s.batchedAttestations.Append(v) 338 if numRecords := s.batchedAttestations.Len(); numRecords >= attestationBatchCapacity { 339 log.WithField("numRecords", numRecords).Debug( 340 "Reached max capacity of batched attestation records, flushing to DB", 341 ) 342 if s.batchedAttestationsFlushInProgress.IsNotSet() { 343 s.flushAttestationRecords(ctx, s.batchedAttestations.Flush()) 344 } 345 } 346 case <-ticker.C: 347 if numRecords := s.batchedAttestations.Len(); numRecords > 0 { 348 log.WithField("numRecords", numRecords).Debug( 349 "Batched attestation records write interval reached, flushing to DB", 350 ) 351 if s.batchedAttestationsFlushInProgress.IsNotSet() { 352 s.flushAttestationRecords(ctx, s.batchedAttestations.Flush()) 353 } 354 } 355 case <-ctx.Done(): 356 return 357 } 358 } 359 } 360 361 // Flushes a list of batched attestations to the database 362 // and resets the list of batched attestations for future writes. 363 // This function notifies all subscribers for flushed attestations 364 // of the result of the save operation. 365 func (s *Store) flushAttestationRecords(ctx context.Context, records []*AttestationRecord) { 366 if s.batchedAttestationsFlushInProgress.IsSet() { 367 // This should never happen. This method should not be called when a flush is already in 368 // progress. If you are seeing this log, check the atomic bool before calling this method. 369 log.Error("Attempted to flush attestation records when already in progress") 370 return 371 } 372 s.batchedAttestationsFlushInProgress.Set() 373 defer s.batchedAttestationsFlushInProgress.UnSet() 374 375 start := time.Now() 376 err := s.saveAttestationRecords(ctx, records) 377 // If there was any error, retry the records since the TX would have been reverted. 378 if err == nil { 379 log.WithField("duration", time.Since(start)).Debug("Successfully flushed batched attestations to DB") 380 } else { 381 // This should never happen. 382 log.WithError(err).Error("Failed to batch save attestation records, retrying in queue") 383 for _, ar := range records { 384 s.batchedAttestations.Append(ar) 385 } 386 } 387 // Forward the error, if any, to all subscribers via an event feed. 388 // We use a struct wrapper around the error as the event feed 389 // cannot handle sending a raw `nil` in case there is no error. 390 s.batchAttestationsFlushedFeed.Send(saveAttestationsResponse{ 391 err: err, 392 }) 393 } 394 395 // Saves a list of attestation records to the database in a single boltDB 396 // transaction to minimize write lock contention compared to doing them 397 // all in individual, isolated boltDB transactions. 398 func (s *Store) saveAttestationRecords(ctx context.Context, atts []*AttestationRecord) error { 399 ctx, span := trace.StartSpan(ctx, "Validator.saveAttestationRecords") 400 defer span.End() 401 return s.update(func(tx *bolt.Tx) error { 402 // Initialize buckets for the lowest target and source epochs. 403 lowestSourceBucket, err := tx.CreateBucketIfNotExists(lowestSignedSourceBucket) 404 if err != nil { 405 return err 406 } 407 lowestTargetBucket, err := tx.CreateBucketIfNotExists(lowestSignedTargetBucket) 408 if err != nil { 409 return err 410 } 411 bucket := tx.Bucket(pubKeysBucket) 412 for _, att := range atts { 413 pkBucket, err := bucket.CreateBucketIfNotExists(att.PubKey[:]) 414 if err != nil { 415 return errors.Wrap(err, "could not create public key bucket") 416 } 417 sourceEpochBytes := bytesutil.EpochToBytesBigEndian(att.Source) 418 targetEpochBytes := bytesutil.EpochToBytesBigEndian(att.Target) 419 420 signingRootsBucket, err := pkBucket.CreateBucketIfNotExists(attestationSigningRootsBucket) 421 if err != nil { 422 return errors.Wrap(err, "could not create signing roots bucket") 423 } 424 if err := signingRootsBucket.Put(targetEpochBytes, att.SigningRoot[:]); err != nil { 425 return errors.Wrapf(err, "could not save signing signing root for epoch %d", att.Target) 426 } 427 sourceEpochsBucket, err := pkBucket.CreateBucketIfNotExists(attestationSourceEpochsBucket) 428 if err != nil { 429 return errors.Wrap(err, "could not create source epochs bucket") 430 } 431 432 // There can be multiple attested target epochs per source epoch. 433 // If a previous list exists, we append to that list with the incoming target epoch. 434 // Otherwise, we initialize it using the incoming target epoch. 435 var existingAttestedTargetsBytes []byte 436 if existing := sourceEpochsBucket.Get(sourceEpochBytes); existing != nil { 437 existingAttestedTargetsBytes = append(existing, targetEpochBytes...) 438 } else { 439 existingAttestedTargetsBytes = targetEpochBytes 440 } 441 442 if err := sourceEpochsBucket.Put(sourceEpochBytes, existingAttestedTargetsBytes); err != nil { 443 return errors.Wrapf(err, "could not save source epoch %d for epoch %d", att.Source, att.Target) 444 } 445 446 targetEpochsBucket, err := pkBucket.CreateBucketIfNotExists(attestationTargetEpochsBucket) 447 if err != nil { 448 return errors.Wrap(err, "could not create target epochs bucket") 449 } 450 var existingAttestedSourceBytes []byte 451 if existing := targetEpochsBucket.Get(targetEpochBytes); existing != nil { 452 existingAttestedSourceBytes = append(existing, sourceEpochBytes...) 453 } else { 454 existingAttestedSourceBytes = sourceEpochBytes 455 } 456 457 if err := targetEpochsBucket.Put(targetEpochBytes, existingAttestedSourceBytes); err != nil { 458 return errors.Wrapf(err, "could not save target epoch %d for epoch %d", att.Target, att.Source) 459 } 460 461 // If the incoming source epoch is lower than the lowest signed source epoch, override. 462 lowestSignedSourceBytes := lowestSourceBucket.Get(att.PubKey[:]) 463 var lowestSignedSourceEpoch types.Epoch 464 if len(lowestSignedSourceBytes) >= 8 { 465 lowestSignedSourceEpoch = bytesutil.BytesToEpochBigEndian(lowestSignedSourceBytes) 466 } 467 if len(lowestSignedSourceBytes) == 0 || att.Source < lowestSignedSourceEpoch { 468 if err := lowestSourceBucket.Put( 469 att.PubKey[:], bytesutil.EpochToBytesBigEndian(att.Source), 470 ); err != nil { 471 return err 472 } 473 } 474 475 // If the incoming target epoch is lower than the lowest signed target epoch, override. 476 lowestSignedTargetBytes := lowestTargetBucket.Get(att.PubKey[:]) 477 var lowestSignedTargetEpoch types.Epoch 478 if len(lowestSignedTargetBytes) >= 8 { 479 lowestSignedTargetEpoch = bytesutil.BytesToEpochBigEndian(lowestSignedTargetBytes) 480 } 481 if len(lowestSignedTargetBytes) == 0 || att.Target < lowestSignedTargetEpoch { 482 if err := lowestTargetBucket.Put( 483 att.PubKey[:], bytesutil.EpochToBytesBigEndian(att.Target), 484 ); err != nil { 485 return err 486 } 487 } 488 } 489 return nil 490 }) 491 } 492 493 // AttestedPublicKeys retrieves all public keys that have attested. 494 func (s *Store) AttestedPublicKeys(ctx context.Context) ([][48]byte, error) { 495 ctx, span := trace.StartSpan(ctx, "Validator.AttestedPublicKeys") 496 defer span.End() 497 var err error 498 attestedPublicKeys := make([][48]byte, 0) 499 err = s.view(func(tx *bolt.Tx) error { 500 bucket := tx.Bucket(pubKeysBucket) 501 return bucket.ForEach(func(pubKey []byte, _ []byte) error { 502 var pk [48]byte 503 copy(pk[:], pubKey) 504 attestedPublicKeys = append(attestedPublicKeys, pk) 505 return nil 506 }) 507 }) 508 return attestedPublicKeys, err 509 } 510 511 // SigningRootAtTargetEpoch checks for an existing signing root at a specified 512 // target epoch for a given validator public key. 513 func (s *Store) SigningRootAtTargetEpoch(ctx context.Context, pubKey [48]byte, target types.Epoch) ([32]byte, error) { 514 ctx, span := trace.StartSpan(ctx, "Validator.SigningRootAtTargetEpoch") 515 defer span.End() 516 var signingRoot [32]byte 517 err := s.view(func(tx *bolt.Tx) error { 518 bucket := tx.Bucket(pubKeysBucket) 519 pkBucket := bucket.Bucket(pubKey[:]) 520 if pkBucket == nil { 521 return nil 522 } 523 signingRootsBucket := pkBucket.Bucket(attestationSigningRootsBucket) 524 if signingRootsBucket == nil { 525 return nil 526 } 527 sr := signingRootsBucket.Get(bytesutil.EpochToBytesBigEndian(target)) 528 copy(signingRoot[:], sr) 529 return nil 530 }) 531 return signingRoot, err 532 } 533 534 // LowestSignedSourceEpoch returns the lowest signed source epoch for a validator public key. 535 // If no data exists, returning 0 is a sensible default. 536 func (s *Store) LowestSignedSourceEpoch(ctx context.Context, publicKey [48]byte) (types.Epoch, bool, error) { 537 ctx, span := trace.StartSpan(ctx, "Validator.LowestSignedSourceEpoch") 538 defer span.End() 539 540 var err error 541 var lowestSignedSourceEpoch types.Epoch 542 var exists bool 543 err = s.view(func(tx *bolt.Tx) error { 544 bucket := tx.Bucket(lowestSignedSourceBucket) 545 lowestSignedSourceBytes := bucket.Get(publicKey[:]) 546 // 8 because bytesutil.BytesToEpochBigEndian will return 0 if input is less than 8 bytes. 547 if len(lowestSignedSourceBytes) < 8 { 548 return nil 549 } 550 exists = true 551 lowestSignedSourceEpoch = bytesutil.BytesToEpochBigEndian(lowestSignedSourceBytes) 552 return nil 553 }) 554 return lowestSignedSourceEpoch, exists, err 555 } 556 557 // LowestSignedTargetEpoch returns the lowest signed target epoch for a validator public key. 558 // If no data exists, returning 0 is a sensible default. 559 func (s *Store) LowestSignedTargetEpoch(ctx context.Context, publicKey [48]byte) (types.Epoch, bool, error) { 560 ctx, span := trace.StartSpan(ctx, "Validator.LowestSignedTargetEpoch") 561 defer span.End() 562 563 var err error 564 var lowestSignedTargetEpoch types.Epoch 565 var exists bool 566 err = s.view(func(tx *bolt.Tx) error { 567 bucket := tx.Bucket(lowestSignedTargetBucket) 568 lowestSignedTargetBytes := bucket.Get(publicKey[:]) 569 // 8 because bytesutil.BytesToEpochBigEndian will return 0 if input is less than 8 bytes. 570 if len(lowestSignedTargetBytes) < 8 { 571 return nil 572 } 573 exists = true 574 lowestSignedTargetEpoch = bytesutil.BytesToEpochBigEndian(lowestSignedTargetBytes) 575 return nil 576 }) 577 return lowestSignedTargetEpoch, exists, err 578 }