github.com/prysmaticlabs/prysm@v1.4.4/beacon-chain/powchain/block_cache.go (about) 1 package powchain 2 3 import ( 4 "errors" 5 "math/big" 6 "sync" 7 8 "github.com/ethereum/go-ethereum/common" 9 gethTypes "github.com/ethereum/go-ethereum/core/types" 10 "github.com/prometheus/client_golang/prometheus" 11 "github.com/prometheus/client_golang/prometheus/promauto" 12 "github.com/prysmaticlabs/prysm/beacon-chain/powchain/types" 13 "github.com/prysmaticlabs/prysm/shared/params" 14 "k8s.io/client-go/tools/cache" 15 ) 16 17 var ( 18 // ErrNotAHeaderInfo will be returned when a cache object is not a pointer to 19 // a headerInfo struct. 20 ErrNotAHeaderInfo = errors.New("object is not a header info") 21 22 // maxCacheSize is 2x of the follow distance for additional cache padding. 23 // Requests should be only accessing blocks within recent blocks within the 24 // Eth1FollowDistance. 25 maxCacheSize = 2 * params.BeaconConfig().Eth1FollowDistance 26 27 // Metrics 28 headerCacheMiss = promauto.NewCounter(prometheus.CounterOpts{ 29 Name: "powchain_header_cache_miss", 30 Help: "The number of header requests that aren't present in the cache.", 31 }) 32 headerCacheHit = promauto.NewCounter(prometheus.CounterOpts{ 33 Name: "powchain_header_cache_hit", 34 Help: "The number of header requests that are present in the cache.", 35 }) 36 headerCacheSize = promauto.NewGauge(prometheus.GaugeOpts{ 37 Name: "powchain_header_cache_size", 38 Help: "The number of headers in the header cache", 39 }) 40 ) 41 42 // hashKeyFn takes the hex string representation as the key for a headerInfo. 43 func hashKeyFn(obj interface{}) (string, error) { 44 hInfo, ok := obj.(*types.HeaderInfo) 45 if !ok { 46 return "", ErrNotAHeaderInfo 47 } 48 49 return hInfo.Hash.Hex(), nil 50 } 51 52 // heightKeyFn takes the string representation of the block header number as the key 53 // for a headerInfo. 54 func heightKeyFn(obj interface{}) (string, error) { 55 hInfo, ok := obj.(*types.HeaderInfo) 56 if !ok { 57 return "", ErrNotAHeaderInfo 58 } 59 60 return hInfo.Number.String(), nil 61 } 62 63 // headerCache struct with two queues for looking up by hash or by block height. 64 type headerCache struct { 65 hashCache *cache.FIFO 66 heightCache *cache.FIFO 67 lock sync.RWMutex 68 } 69 70 // newHeaderCache creates a new block cache for storing/accessing headerInfo from 71 // memory. 72 func newHeaderCache() *headerCache { 73 return &headerCache{ 74 hashCache: cache.NewFIFO(hashKeyFn), 75 heightCache: cache.NewFIFO(heightKeyFn), 76 } 77 } 78 79 // HeaderInfoByHash fetches headerInfo by its header hash. Returns true with a 80 // reference to the header info, if exists. Otherwise returns false, nil. 81 func (c *headerCache) HeaderInfoByHash(hash common.Hash) (bool, *types.HeaderInfo, error) { 82 c.lock.RLock() 83 defer c.lock.RUnlock() 84 85 obj, exists, err := c.hashCache.GetByKey(hash.Hex()) 86 if err != nil { 87 return false, nil, err 88 } 89 90 if exists { 91 headerCacheHit.Inc() 92 } else { 93 headerCacheMiss.Inc() 94 return false, nil, nil 95 } 96 97 hInfo, ok := obj.(*types.HeaderInfo) 98 if !ok { 99 return false, nil, ErrNotAHeaderInfo 100 } 101 102 return true, hInfo.Copy(), nil 103 } 104 105 // HeaderInfoByHeight fetches headerInfo by its header number. Returns true with a 106 // reference to the header info, if exists. Otherwise returns false, nil. 107 func (c *headerCache) HeaderInfoByHeight(height *big.Int) (bool, *types.HeaderInfo, error) { 108 c.lock.RLock() 109 defer c.lock.RUnlock() 110 111 obj, exists, err := c.heightCache.GetByKey(height.String()) 112 if err != nil { 113 return false, nil, err 114 } 115 116 if exists { 117 headerCacheHit.Inc() 118 } else { 119 headerCacheMiss.Inc() 120 return false, nil, nil 121 } 122 123 hInfo, ok := obj.(*types.HeaderInfo) 124 if !ok { 125 return false, nil, ErrNotAHeaderInfo 126 } 127 128 return exists, hInfo.Copy(), nil 129 } 130 131 // AddHeader adds a headerInfo object to the cache. This method also trims the 132 // least recently added header info if the cache size has reached the max cache 133 // size limit. This method should be called in sequential header number order if 134 // the desired behavior is that the blocks with the highest header number should 135 // be present in the cache. 136 func (c *headerCache) AddHeader(hdr *gethTypes.Header) error { 137 c.lock.Lock() 138 defer c.lock.Unlock() 139 140 hInfo, err := types.HeaderToHeaderInfo(hdr) 141 if err != nil { 142 return err 143 } 144 145 if err := c.hashCache.AddIfNotPresent(hInfo); err != nil { 146 return err 147 } 148 if err := c.heightCache.AddIfNotPresent(hInfo); err != nil { 149 return err 150 } 151 152 trim(c.hashCache, maxCacheSize) 153 trim(c.heightCache, maxCacheSize) 154 155 headerCacheSize.Set(float64(len(c.hashCache.ListKeys()))) 156 157 return nil 158 } 159 160 // trim the FIFO queue to the maxSize. 161 func trim(queue *cache.FIFO, maxSize uint64) { 162 for s := uint64(len(queue.ListKeys())); s > maxSize; s-- { 163 // #nosec G104 popProcessNoopFunc never returns an error 164 if _, err := queue.Pop(popProcessNoopFunc); err != nil { // This never returns an error, but we'll handle anyway for sanity. 165 panic(err) 166 } 167 } 168 } 169 170 // popProcessNoopFunc is a no-op function that never returns an error. 171 func popProcessNoopFunc(_ interface{}) error { 172 return nil 173 }