github.com/prysmaticlabs/prysm@v1.4.4/slasher/db/kv/spanner_new.go (about)

     1  package kv
     2  
     3  import (
     4  	"context"
     5  
     6  	"github.com/pkg/errors"
     7  	"github.com/prometheus/client_golang/prometheus"
     8  	"github.com/prometheus/client_golang/prometheus/promauto"
     9  	types "github.com/prysmaticlabs/eth2-types"
    10  	"github.com/prysmaticlabs/prysm/shared/bytesutil"
    11  	"github.com/prysmaticlabs/prysm/shared/params"
    12  	slashertypes "github.com/prysmaticlabs/prysm/slasher/detection/attestations/types"
    13  	bolt "go.etcd.io/bbolt"
    14  	"go.opencensus.io/trace"
    15  )
    16  
    17  // Tracks the highest and lowest observed epochs from the validator span maps
    18  // used for attester slashing detection. This value is purely used
    19  // as a cache key and only needs to be maintained in memory.
    20  var highestObservedEpoch types.Epoch
    21  var lowestObservedEpoch = params.BeaconConfig().FarFutureEpoch
    22  
    23  var (
    24  	slasherLowestObservedEpoch = promauto.NewGauge(prometheus.GaugeOpts{
    25  		Name: "slasher_lowest_observed_epoch",
    26  		Help: "The lowest epoch number seen by slasher",
    27  	})
    28  	slasherHighestObservedEpoch = promauto.NewGauge(prometheus.GaugeOpts{
    29  		Name: "slasher_highest_observed_epoch",
    30  		Help: "The highest epoch number seen by slasher",
    31  	})
    32  	epochSpansCacheEvictions = promauto.NewCounter(prometheus.CounterOpts{
    33  		Name: "epoch_spans_cache_evictions_total",
    34  		Help: "The number of cache evictions seen by slasher",
    35  	})
    36  )
    37  
    38  // This function defines a function which triggers upon a span map being
    39  // evicted from the cache. It allows us to persist the span map by the epoch value
    40  // to the database itself in the validatorsMinMaxSpanBucket.
    41  func persistFlatSpanMapsOnEviction(db *Store) func(key interface{}, value interface{}) {
    42  	// We use a closure here so we can access the database itself
    43  	// on the eviction of a span map from the cache. The function has the signature
    44  	// required by the ristretto cache OnEvict method.
    45  	// See https://godoc.org/github.com/dgraph-io/ristretto#Config.
    46  	return func(key interface{}, value interface{}) {
    47  		log.Tracef("Evicting flat span map for epoch: %d", key)
    48  		err := db.update(func(tx *bolt.Tx) error {
    49  			epoch, keyOK := key.(types.Epoch)
    50  			epochStore, valueOK := value.(*slashertypes.EpochStore)
    51  			if !keyOK || !valueOK {
    52  				return errors.New("could not cast key and value into needed types")
    53  			}
    54  
    55  			bucket := tx.Bucket(validatorsMinMaxSpanBucketNew)
    56  			if err := bucket.Put(bytesutil.Bytes8(uint64(epoch)), epochStore.Bytes()); err != nil {
    57  				return err
    58  			}
    59  			epochSpansCacheEvictions.Inc()
    60  			return nil
    61  		})
    62  		if err != nil {
    63  			log.Errorf("Failed to save span map to db on cache eviction: %v", err)
    64  		}
    65  	}
    66  }
    67  
    68  // EpochSpans accepts epoch and returns the corresponding spans byte array
    69  // for slashing detection.
    70  // Returns span byte array, and error in case of db error.
    71  // returns empty byte array if no entry for this epoch exists in db.
    72  func (s *Store) EpochSpans(_ context.Context, epoch types.Epoch, fromCache bool) (*slashertypes.EpochStore, error) {
    73  	// Get from the cache if it exists or is requested, if not, go to DB.
    74  	if fromCache && s.flatSpanCache.Has(epoch) || s.flatSpanCache.Has(epoch) {
    75  		spans, _ := s.flatSpanCache.Get(epoch)
    76  		return spans, nil
    77  	}
    78  
    79  	var copiedSpans []byte
    80  	err := s.view(func(tx *bolt.Tx) error {
    81  		b := tx.Bucket(validatorsMinMaxSpanBucketNew)
    82  		if b == nil {
    83  			return nil
    84  		}
    85  		spans := b.Get(bytesutil.Bytes8(uint64(epoch)))
    86  		copiedSpans = make([]byte, len(spans))
    87  		copy(copiedSpans, spans)
    88  		return nil
    89  	})
    90  	if err != nil {
    91  		return &slashertypes.EpochStore{}, err
    92  	}
    93  	if copiedSpans == nil {
    94  		copiedSpans = []byte{}
    95  	}
    96  	return slashertypes.NewEpochStore(copiedSpans)
    97  }
    98  
    99  // SaveEpochSpans accepts a epoch and span byte array and writes it to disk.
   100  func (s *Store) SaveEpochSpans(ctx context.Context, epoch types.Epoch, es *slashertypes.EpochStore, toCache bool) error {
   101  	if len(es.Bytes())%int(slashertypes.SpannerEncodedLength) != 0 {
   102  		return slashertypes.ErrWrongSize
   103  	}
   104  	// Also prune indexed attestations older then weak subjectivity period.
   105  	if err := s.setObservedEpochs(ctx, epoch); err != nil {
   106  		return err
   107  	}
   108  	// Saving to the cache if it exists so cache and DB never conflict.
   109  	if toCache || s.flatSpanCache.Has(epoch) {
   110  		s.flatSpanCache.Set(epoch, es)
   111  	}
   112  	if toCache {
   113  		return nil
   114  	}
   115  
   116  	return s.update(func(tx *bolt.Tx) error {
   117  		b, err := tx.CreateBucketIfNotExists(validatorsMinMaxSpanBucketNew)
   118  		if err != nil {
   119  			return err
   120  		}
   121  		return b.Put(bytesutil.Bytes8(uint64(epoch)), es.Bytes())
   122  	})
   123  }
   124  
   125  // CacheLength returns the number of cached items.
   126  func (s *Store) CacheLength(ctx context.Context) int {
   127  	ctx, span := trace.StartSpan(ctx, "slasherDB.CacheLength")
   128  	defer span.End()
   129  	length := s.flatSpanCache.Length()
   130  	log.Debugf("Span cache length %d", length)
   131  	return length
   132  }
   133  
   134  // EnableSpanCache used to enable or disable span map cache in tests.
   135  func (s *Store) EnableSpanCache(enable bool) {
   136  	s.spanCacheEnabled = enable
   137  }
   138  
   139  func (s *Store) setObservedEpochs(ctx context.Context, epoch types.Epoch) error {
   140  	var err error
   141  	if epoch > highestObservedEpoch {
   142  		slasherHighestObservedEpoch.Set(float64(epoch))
   143  		highestObservedEpoch = epoch
   144  		// Prune block header history every PruneSlasherStoragePeriod epoch.
   145  		if highestObservedEpoch%params.BeaconConfig().PruneSlasherStoragePeriod == 0 {
   146  			if err = s.PruneAttHistory(ctx, epoch, params.BeaconConfig().WeakSubjectivityPeriod); err != nil {
   147  				return errors.Wrap(err, "failed to prune indexed attestations store")
   148  			}
   149  		}
   150  	}
   151  	if epoch < lowestObservedEpoch {
   152  		slasherLowestObservedEpoch.Set(float64(epoch))
   153  		lowestObservedEpoch = epoch
   154  	}
   155  	return err
   156  }