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 := &ethpb.IndexedAttestation{
   214  				Data: &ethpb.AttestationData{
   215  					Source: &ethpb.Checkpoint{Epoch: existingSourceEpoch},
   216  					Target: &ethpb.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 := &ethpb.IndexedAttestation{
   254  				Data: &ethpb.AttestationData{
   255  					Source: &ethpb.Checkpoint{Epoch: existingSourceEpoch},
   256  					Target: &ethpb.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  }