2019-04-11 19:34:23 +00:00
|
|
|
package cache
|
|
|
|
|
|
|
|
import (
|
|
|
|
"errors"
|
|
|
|
"strconv"
|
|
|
|
"sync"
|
|
|
|
|
|
|
|
"github.com/prometheus/client_golang/prometheus"
|
|
|
|
"github.com/prometheus/client_golang/prometheus/promauto"
|
|
|
|
"github.com/prysmaticlabs/prysm/shared/params"
|
|
|
|
"k8s.io/client-go/tools/cache"
|
|
|
|
)
|
|
|
|
|
|
|
|
var (
|
|
|
|
// ErrNotACommitteeInfo will be returned when a cache object is not a pointer to
|
|
|
|
// a committeeInfo struct.
|
|
|
|
ErrNotACommitteeInfo = errors.New("object is not an committee info")
|
|
|
|
|
|
|
|
// maxCacheSize is 4x of the epoch length for additional cache padding.
|
|
|
|
// Requests should be only accessing committees within defined epoch length.
|
2019-04-12 15:31:01 +00:00
|
|
|
maxCacheSize = int(4 * params.BeaconConfig().SlotsPerEpoch)
|
2019-04-11 19:34:23 +00:00
|
|
|
|
|
|
|
// Metrics
|
|
|
|
committeeCacheMiss = promauto.NewCounter(prometheus.CounterOpts{
|
|
|
|
Name: "committee_cache_miss",
|
|
|
|
Help: "The number of committee requests that aren't present in the cache.",
|
|
|
|
})
|
|
|
|
committeeCacheHit = promauto.NewCounter(prometheus.CounterOpts{
|
|
|
|
Name: "committee_cache_hit",
|
|
|
|
Help: "The number of committee requests that are present in the cache.",
|
|
|
|
})
|
|
|
|
committeeCacheSize = promauto.NewGauge(prometheus.GaugeOpts{
|
|
|
|
Name: "committee_cache_size",
|
|
|
|
Help: "The number of committees in the committee cache",
|
|
|
|
})
|
|
|
|
)
|
|
|
|
|
|
|
|
// CommitteeInfo defines the validator committee of slot and shard combinations.
|
|
|
|
type CommitteeInfo struct {
|
|
|
|
Committee []uint64
|
|
|
|
Shard uint64
|
|
|
|
}
|
|
|
|
|
2019-04-12 15:31:01 +00:00
|
|
|
// CommitteesInSlot specifies how many CommitteeInfos are in a given slot.
|
2019-04-11 19:34:23 +00:00
|
|
|
type CommitteesInSlot struct {
|
|
|
|
Slot uint64
|
|
|
|
Committees []*CommitteeInfo
|
|
|
|
}
|
|
|
|
|
2019-04-12 15:31:01 +00:00
|
|
|
// CommitteesCache structs with 1 queue for looking up committees by slot.
|
2019-04-11 19:34:23 +00:00
|
|
|
type CommitteesCache struct {
|
|
|
|
committeesCache *cache.FIFO
|
|
|
|
lock sync.RWMutex
|
|
|
|
}
|
|
|
|
|
|
|
|
// slotKeyFn takes the string representation of the slot number as the key
|
2019-04-12 15:31:01 +00:00
|
|
|
// for the committees of a given slot (CommitteesInSlot).
|
2019-04-11 19:34:23 +00:00
|
|
|
func slotKeyFn(obj interface{}) (string, error) {
|
|
|
|
cInfo, ok := obj.(*CommitteesInSlot)
|
|
|
|
if !ok {
|
|
|
|
return "", ErrNotACommitteeInfo
|
|
|
|
}
|
|
|
|
|
|
|
|
return strconv.Itoa(int(cInfo.Slot)), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// NewCommitteesCache creates a new committee cache for storing/accessing blockInfo from
|
|
|
|
// memory.
|
|
|
|
func NewCommitteesCache() *CommitteesCache {
|
|
|
|
return &CommitteesCache{
|
|
|
|
committeesCache: cache.NewFIFO(slotKeyFn),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-04-12 15:31:01 +00:00
|
|
|
// CommitteesInfoBySlot fetches CommitteesInSlot by slot. Returns true with a
|
2019-04-11 19:34:23 +00:00
|
|
|
// reference to the committees info, if exists. Otherwise returns false, nil.
|
|
|
|
func (c *CommitteesCache) CommitteesInfoBySlot(slot uint64) (*CommitteesInSlot, error) {
|
|
|
|
c.lock.RLock()
|
|
|
|
defer c.lock.RUnlock()
|
|
|
|
|
|
|
|
obj, exists, err := c.committeesCache.GetByKey(strconv.Itoa(int(slot)))
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if exists {
|
|
|
|
committeeCacheHit.Inc()
|
|
|
|
} else {
|
|
|
|
committeeCacheMiss.Inc()
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
cInfo, ok := obj.(*CommitteesInSlot)
|
|
|
|
if !ok {
|
|
|
|
return nil, ErrNotACommitteeInfo
|
|
|
|
}
|
|
|
|
|
|
|
|
return cInfo, nil
|
|
|
|
}
|
|
|
|
|
2019-04-12 15:31:01 +00:00
|
|
|
// AddCommittees adds CommitteesInSlot object to the cache. This method also trims the least
|
2019-04-11 19:34:23 +00:00
|
|
|
// recently added committeeInfo object if the cache size has ready the max cache size limit.
|
|
|
|
func (c *CommitteesCache) AddCommittees(committees *CommitteesInSlot) error {
|
|
|
|
c.lock.Lock()
|
|
|
|
defer c.lock.Unlock()
|
|
|
|
|
|
|
|
if err := c.committeesCache.AddIfNotPresent(committees); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
trim(c.committeesCache, maxCacheSize)
|
|
|
|
committeeCacheSize.Set(float64(len(c.committeesCache.ListKeys())))
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// trim the FIFO queue to the maxSize.
|
|
|
|
func trim(queue *cache.FIFO, maxSize int) {
|
|
|
|
for s := len(queue.ListKeys()); s > maxSize; s-- {
|
|
|
|
// #nosec G104 popProcessNoopFunc never returns an error
|
|
|
|
_, _ = queue.Pop(popProcessNoopFunc)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// popProcessNoopFunc is a no-op function that never returns an error.
|
|
|
|
func popProcessNoopFunc(obj interface{}) error {
|
|
|
|
return nil
|
|
|
|
}
|