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 }