github.com/prysmaticlabs/prysm@v1.4.4/beacon-chain/state/stategen/epoch_boundary_state_cache.go (about)

     1  package stategen
     2  
     3  import (
     4  	"errors"
     5  	"strconv"
     6  	"sync"
     7  
     8  	types "github.com/prysmaticlabs/eth2-types"
     9  	iface "github.com/prysmaticlabs/prysm/beacon-chain/state/interface"
    10  	"k8s.io/client-go/tools/cache"
    11  )
    12  
    13  var (
    14  	// maxCacheSize is 8. That means 8 epochs and roughly an hour
    15  	// of no finality can be endured.
    16  	maxCacheSize        = uint64(8)
    17  	errNotSlotRootInfo  = errors.New("not slot root info type")
    18  	errNotRootStateInfo = errors.New("not root state info type")
    19  )
    20  
    21  // slotRootInfo specifies the slot root info in the epoch boundary state cache.
    22  type slotRootInfo struct {
    23  	slot types.Slot
    24  	root [32]byte
    25  }
    26  
    27  // slotKeyFn takes the string representation of the slot to be used as key
    28  // to retrieve root.
    29  func slotKeyFn(obj interface{}) (string, error) {
    30  	s, ok := obj.(*slotRootInfo)
    31  	if !ok {
    32  		return "", errNotSlotRootInfo
    33  	}
    34  	return slotToString(s.slot), nil
    35  }
    36  
    37  // rootStateInfo specifies the root state info in the epoch boundary state cache.
    38  type rootStateInfo struct {
    39  	root  [32]byte
    40  	state iface.BeaconState
    41  }
    42  
    43  // rootKeyFn takes the string representation of the block root to be used as key
    44  // to retrieve epoch boundary state.
    45  func rootKeyFn(obj interface{}) (string, error) {
    46  	s, ok := obj.(*rootStateInfo)
    47  	if !ok {
    48  		return "", errNotRootStateInfo
    49  	}
    50  	return string(s.root[:]), nil
    51  }
    52  
    53  // epochBoundaryState struct with two queues by looking up beacon state by slot or root.
    54  type epochBoundaryState struct {
    55  	rootStateCache *cache.FIFO
    56  	slotRootCache  *cache.FIFO
    57  	lock           sync.RWMutex
    58  }
    59  
    60  // newBoundaryStateCache creates a new block newBoundaryStateCache for storing and accessing epoch boundary states from
    61  // memory.
    62  func newBoundaryStateCache() *epochBoundaryState {
    63  	return &epochBoundaryState{
    64  		rootStateCache: cache.NewFIFO(rootKeyFn),
    65  		slotRootCache:  cache.NewFIFO(slotKeyFn),
    66  	}
    67  }
    68  
    69  // get epoch boundary state by its block root. Returns copied state in state info object if exists. Otherwise returns nil.
    70  func (e *epochBoundaryState) getByRoot(r [32]byte) (*rootStateInfo, bool, error) {
    71  	e.lock.RLock()
    72  	defer e.lock.RUnlock()
    73  
    74  	obj, exists, err := e.rootStateCache.GetByKey(string(r[:]))
    75  	if err != nil {
    76  		return nil, false, err
    77  	}
    78  	if !exists {
    79  		return nil, false, nil
    80  	}
    81  	s, ok := obj.(*rootStateInfo)
    82  	if !ok {
    83  		return nil, false, errNotRootStateInfo
    84  	}
    85  
    86  	return &rootStateInfo{
    87  		root:  r,
    88  		state: s.state.Copy(),
    89  	}, true, nil
    90  }
    91  
    92  // get epoch boundary state by its slot. Returns copied state in state info object if exists. Otherwise returns nil.
    93  func (e *epochBoundaryState) getBySlot(s types.Slot) (*rootStateInfo, bool, error) {
    94  	e.lock.RLock()
    95  	defer e.lock.RUnlock()
    96  
    97  	obj, exists, err := e.slotRootCache.GetByKey(slotToString(s))
    98  	if err != nil {
    99  		return nil, false, err
   100  	}
   101  	if !exists {
   102  		return nil, false, nil
   103  	}
   104  	info, ok := obj.(*slotRootInfo)
   105  	if !ok {
   106  		return nil, false, errNotSlotRootInfo
   107  	}
   108  
   109  	return e.getByRoot(info.root)
   110  }
   111  
   112  // put adds a state to the epoch boundary state cache. This method also trims the
   113  // least recently added state info if the cache size has reached the max cache
   114  // size limit.
   115  func (e *epochBoundaryState) put(r [32]byte, s iface.BeaconState) error {
   116  	e.lock.Lock()
   117  	defer e.lock.Unlock()
   118  
   119  	if err := e.slotRootCache.AddIfNotPresent(&slotRootInfo{
   120  		slot: s.Slot(),
   121  		root: r,
   122  	}); err != nil {
   123  		return err
   124  	}
   125  	if err := e.rootStateCache.AddIfNotPresent(&rootStateInfo{
   126  		root:  r,
   127  		state: s.Copy(),
   128  	}); err != nil {
   129  		return err
   130  	}
   131  
   132  	trim(e.rootStateCache, maxCacheSize)
   133  	trim(e.slotRootCache, maxCacheSize)
   134  
   135  	return nil
   136  }
   137  
   138  // trim the FIFO queue to the maxSize.
   139  func trim(queue *cache.FIFO, maxSize uint64) {
   140  	for s := uint64(len(queue.ListKeys())); s > maxSize; s-- {
   141  		if _, err := queue.Pop(popProcessNoopFunc); err != nil { // This never returns an error, but we'll handle anyway for sanity.
   142  			panic(err)
   143  		}
   144  	}
   145  }
   146  
   147  // popProcessNoopFunc is a no-op function that never returns an error.
   148  func popProcessNoopFunc(_ interface{}) error {
   149  	return nil
   150  }
   151  
   152  // Converts input uint64 to string. To be used as key for slot to get root.
   153  func slotToString(s types.Slot) string {
   154  	return strconv.FormatUint(uint64(s), 10)
   155  }