github.com/prysmaticlabs/prysm@v1.4.4/beacon-chain/cache/committee.go (about)

     1  // +build !libfuzzer
     2  
     3  package cache
     4  
     5  import (
     6  	"errors"
     7  	"sync"
     8  
     9  	lru "github.com/hashicorp/golang-lru"
    10  	"github.com/prometheus/client_golang/prometheus"
    11  	"github.com/prometheus/client_golang/prometheus/promauto"
    12  	types "github.com/prysmaticlabs/eth2-types"
    13  	"github.com/prysmaticlabs/prysm/shared/params"
    14  	"github.com/prysmaticlabs/prysm/shared/sliceutil"
    15  )
    16  
    17  var (
    18  	// maxCommitteesCacheSize defines the max number of shuffled committees on per randao basis can cache.
    19  	// Due to reorgs and long finality, it's good to keep the old cache around for quickly switch over.
    20  	maxCommitteesCacheSize = uint64(32)
    21  
    22  	// CommitteeCacheMiss tracks the number of committee requests that aren't present in the cache.
    23  	CommitteeCacheMiss = promauto.NewCounter(prometheus.CounterOpts{
    24  		Name: "committee_cache_miss",
    25  		Help: "The number of committee requests that aren't present in the cache.",
    26  	})
    27  	// CommitteeCacheHit tracks the number of committee requests that are in the cache.
    28  	CommitteeCacheHit = promauto.NewCounter(prometheus.CounterOpts{
    29  		Name: "committee_cache_hit",
    30  		Help: "The number of committee requests that are present in the cache.",
    31  	})
    32  )
    33  
    34  // CommitteeCache is a struct with 1 queue for looking up shuffled indices list by seed.
    35  type CommitteeCache struct {
    36  	CommitteeCache *lru.Cache
    37  	lock           sync.RWMutex
    38  }
    39  
    40  // committeeKeyFn takes the seed as the key to retrieve shuffled indices of a committee in a given epoch.
    41  func committeeKeyFn(obj interface{}) (string, error) {
    42  	info, ok := obj.(*Committees)
    43  	if !ok {
    44  		return "", ErrNotCommittee
    45  	}
    46  	return key(info.Seed), nil
    47  }
    48  
    49  // NewCommitteesCache creates a new committee cache for storing/accessing shuffled indices of a committee.
    50  func NewCommitteesCache() *CommitteeCache {
    51  	cCache, err := lru.New(int(maxCommitteesCacheSize))
    52  	// An error is only returned if the size of the cache is
    53  	// <= 0.
    54  	if err != nil {
    55  		panic(err)
    56  	}
    57  	return &CommitteeCache{
    58  		CommitteeCache: cCache,
    59  	}
    60  }
    61  
    62  // Committee fetches the shuffled indices by slot and committee index. Every list of indices
    63  // represent one committee. Returns true if the list exists with slot and committee index. Otherwise returns false, nil.
    64  func (c *CommitteeCache) Committee(slot types.Slot, seed [32]byte, index types.CommitteeIndex) ([]types.ValidatorIndex, error) {
    65  	c.lock.RLock()
    66  	defer c.lock.RUnlock()
    67  
    68  	obj, exists := c.CommitteeCache.Get(key(seed))
    69  	if exists {
    70  		CommitteeCacheHit.Inc()
    71  	} else {
    72  		CommitteeCacheMiss.Inc()
    73  		return nil, nil
    74  	}
    75  
    76  	item, ok := obj.(*Committees)
    77  	if !ok {
    78  		return nil, ErrNotCommittee
    79  	}
    80  
    81  	committeeCountPerSlot := uint64(1)
    82  	if item.CommitteeCount/uint64(params.BeaconConfig().SlotsPerEpoch) > 1 {
    83  		committeeCountPerSlot = item.CommitteeCount / uint64(params.BeaconConfig().SlotsPerEpoch)
    84  	}
    85  
    86  	indexOffSet := uint64(index) + uint64(slot.ModSlot(params.BeaconConfig().SlotsPerEpoch).Mul(committeeCountPerSlot))
    87  	start, end := startEndIndices(item, indexOffSet)
    88  
    89  	if end > uint64(len(item.ShuffledIndices)) || end < start {
    90  		return nil, errors.New("requested index out of bound")
    91  	}
    92  
    93  	return item.ShuffledIndices[start:end], nil
    94  }
    95  
    96  // AddCommitteeShuffledList adds Committee shuffled list object to the cache. T
    97  // his method also trims the least recently list if the cache size has ready the max cache size limit.
    98  func (c *CommitteeCache) AddCommitteeShuffledList(committees *Committees) error {
    99  	c.lock.Lock()
   100  	defer c.lock.Unlock()
   101  	key, err := committeeKeyFn(committees)
   102  	if err != nil {
   103  		return err
   104  	}
   105  	_ = c.CommitteeCache.Add(key, committees)
   106  	return nil
   107  }
   108  
   109  // ActiveIndices returns the active indices of a given seed stored in cache.
   110  func (c *CommitteeCache) ActiveIndices(seed [32]byte) ([]types.ValidatorIndex, error) {
   111  	c.lock.RLock()
   112  	defer c.lock.RUnlock()
   113  	obj, exists := c.CommitteeCache.Get(key(seed))
   114  
   115  	if exists {
   116  		CommitteeCacheHit.Inc()
   117  	} else {
   118  		CommitteeCacheMiss.Inc()
   119  		return nil, nil
   120  	}
   121  
   122  	item, ok := obj.(*Committees)
   123  	if !ok {
   124  		return nil, ErrNotCommittee
   125  	}
   126  
   127  	return item.SortedIndices, nil
   128  }
   129  
   130  // ActiveIndicesCount returns the active indices count of a given seed stored in cache.
   131  func (c *CommitteeCache) ActiveIndicesCount(seed [32]byte) (int, error) {
   132  	c.lock.RLock()
   133  	defer c.lock.RUnlock()
   134  	obj, exists := c.CommitteeCache.Get(key(seed))
   135  	if exists {
   136  		CommitteeCacheHit.Inc()
   137  	} else {
   138  		CommitteeCacheMiss.Inc()
   139  		return 0, nil
   140  	}
   141  
   142  	item, ok := obj.(*Committees)
   143  	if !ok {
   144  		return 0, ErrNotCommittee
   145  	}
   146  
   147  	return len(item.SortedIndices), nil
   148  }
   149  
   150  // HasEntry returns true if the committee cache has a value.
   151  func (c *CommitteeCache) HasEntry(seed string) bool {
   152  	_, ok := c.CommitteeCache.Get(seed)
   153  	return ok
   154  }
   155  
   156  func startEndIndices(c *Committees, index uint64) (uint64, uint64) {
   157  	validatorCount := uint64(len(c.ShuffledIndices))
   158  	start := sliceutil.SplitOffset(validatorCount, c.CommitteeCount, index)
   159  	end := sliceutil.SplitOffset(validatorCount, c.CommitteeCount, index+1)
   160  	return start, end
   161  }
   162  
   163  // Using seed as source for key to handle reorgs in the same epoch.
   164  // The seed is derived from state's array of randao mixes and epoch value
   165  // hashed together. This avoids collisions on different validator set. Spec definition:
   166  // https://github.com/ethereum/eth2.0-specs/blob/v0.9.3/specs/core/0_beacon-chain.md#get_seed
   167  func key(seed [32]byte) string {
   168  	return string(seed[:])
   169  }