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 }