From ffbb722777161f586e1aa6c75cdadda3ea9c2080 Mon Sep 17 00:00:00 2001 From: terence tsao Date: Wed, 25 Aug 2021 09:39:12 -0700 Subject: [PATCH] Add active balance cache (#9456) * Add active balance cache * Better comment * Fuzz * , * go fmt * Fix err * Raul's feedback * A few renames to effective --- beacon-chain/cache/BUILD.bazel | 3 + beacon-chain/cache/active_balance.go | 108 ++++++++++++++++++++++ beacon-chain/cache/active_balance_test.go | 75 +++++++++++++++ 3 files changed, 186 insertions(+) create mode 100644 beacon-chain/cache/active_balance.go create mode 100644 beacon-chain/cache/active_balance_test.go diff --git a/beacon-chain/cache/BUILD.bazel b/beacon-chain/cache/BUILD.bazel index 4e18cf91ea..15c6e05f79 100644 --- a/beacon-chain/cache/BUILD.bazel +++ b/beacon-chain/cache/BUILD.bazel @@ -23,6 +23,7 @@ go_library( "sync_committee_disabled.go", ], "//conditions:default": [ + "active_balance.go", "committee.go", "proposer_indices.go", "sync_committee.go", @@ -46,6 +47,7 @@ go_library( "//shared/sliceutil:go_default_library", "@com_github_hashicorp_golang_lru//:go_default_library", "@com_github_patrickmn_go_cache//:go_default_library", + "@com_github_pkg_errors//:go_default_library", "@com_github_prometheus_client_golang//prometheus:go_default_library", "@com_github_prometheus_client_golang//prometheus/promauto:go_default_library", "@com_github_prysmaticlabs_eth2_types//:go_default_library", @@ -58,6 +60,7 @@ go_test( name = "go_default_test", size = "small", srcs = [ + "active_balance_test.go", "attestation_data_test.go", "cache_test.go", "checkpoint_state_test.go", diff --git a/beacon-chain/cache/active_balance.go b/beacon-chain/cache/active_balance.go new file mode 100644 index 0000000000..7d68e27cdf --- /dev/null +++ b/beacon-chain/cache/active_balance.go @@ -0,0 +1,108 @@ +// +build !libfuzzer + +package cache + +import ( + "encoding/binary" + "sync" + + lru "github.com/hashicorp/golang-lru" + "github.com/pkg/errors" + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/promauto" + ethTypes "github.com/prysmaticlabs/eth2-types" + "github.com/prysmaticlabs/prysm/beacon-chain/state" + "github.com/prysmaticlabs/prysm/shared/params" +) + +var ( + // maxBalanceCacheSize defines the max number of active balances can cache. + maxBalanceCacheSize = uint64(4) + + // BalanceCacheMiss tracks the number of balance requests that aren't present in the cache. + balanceCacheMiss = promauto.NewCounter(prometheus.CounterOpts{ + Name: "total_effective_balance_cache_miss", + Help: "The number of get requests that aren't present in the cache.", + }) + // BalanceCacheHit tracks the number of balance requests that are in the cache. + balanceCacheHit = promauto.NewCounter(prometheus.CounterOpts{ + Name: "total_effective_balance_cache_hit", + Help: "The number of get requests that are present in the cache.", + }) +) + +// BalanceCache is a struct with 1 LRU cache for looking up balance by epoch. +type BalanceCache struct { + cache *lru.Cache + lock sync.RWMutex +} + +// NewEffectiveBalanceCache creates a new effective balance cache for storing/accessing total balance by epoch. +func NewEffectiveBalanceCache() *BalanceCache { + c, err := lru.New(int(maxBalanceCacheSize)) + // An error is only returned if the size of the cache is <= 0. + if err != nil { + panic(err) + } + return &BalanceCache{ + cache: c, + } +} + +// AddTotalEffectiveBalance adds a new total effective balance entry for current balance for state `st` into the cache. +func (c *BalanceCache) AddTotalEffectiveBalance(st state.BeaconState, balance uint64) error { + key, err := balanceCacheKey(st) + if err != nil { + return err + } + + c.lock.Lock() + defer c.lock.Unlock() + + _ = c.cache.Add(key, balance) + return nil +} + +// Get returns the current epoch's effective balance for state `st` in cache. +func (c *BalanceCache) Get(st state.BeaconState) (uint64, error) { + key, err := balanceCacheKey(st) + if err != nil { + return 0, err + } + + c.lock.RLock() + defer c.lock.RUnlock() + + value, exists := c.cache.Get(key) + if !exists { + balanceCacheMiss.Inc() + return 0, ErrNotFound + } + balanceCacheHit.Inc() + return value.(uint64), nil +} + +// Given input state `st`, balance key is constructed as: +// (block_root in `st` at epoch_start_slot - 1) + current_epoch +func balanceCacheKey(st state.BeaconState) (string, error) { + slotsPerEpoch := params.BeaconConfig().SlotsPerEpoch + currentEpoch := st.Slot().DivSlot(slotsPerEpoch) + epochStartSlot, err := slotsPerEpoch.SafeMul(uint64(currentEpoch)) + if err != nil { + // impossible condition due to early division + return "", errors.Errorf("start slot calculation overflows: %v", err) + } + prevSlot := ethTypes.Slot(0) + if epochStartSlot > 1 { + prevSlot = epochStartSlot - 1 + } + r, err := st.BlockRootAtIndex(uint64(prevSlot % params.BeaconConfig().SlotsPerHistoricalRoot)) + if err != nil { + // impossible condition because index is always constrained within state + return "", err + } + + b := make([]byte, 8) + binary.LittleEndian.PutUint64(b, uint64(currentEpoch)) + return string(append(r, b...)), nil +} diff --git a/beacon-chain/cache/active_balance_test.go b/beacon-chain/cache/active_balance_test.go new file mode 100644 index 0000000000..d14bbc401a --- /dev/null +++ b/beacon-chain/cache/active_balance_test.go @@ -0,0 +1,75 @@ +package cache + +import ( + "encoding/binary" + "math" + "testing" + + types "github.com/prysmaticlabs/eth2-types" + state "github.com/prysmaticlabs/prysm/beacon-chain/state/v1" + ethpb "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1" + "github.com/prysmaticlabs/prysm/shared/params" + "github.com/prysmaticlabs/prysm/shared/testutil/require" +) + +func TestBalanceCache_AddGetBalance(t *testing.T) { + blockRoots := make([][]byte, params.BeaconConfig().SlotsPerHistoricalRoot) + for i := 0; i < len(blockRoots); i++ { + b := make([]byte, 8) + binary.LittleEndian.PutUint64(b, uint64(i)) + blockRoots[i] = b + } + raw := ðpb.BeaconState{ + BlockRoots: blockRoots, + } + st, err := state.InitializeFromProto(raw) + require.NoError(t, err) + + cache := NewEffectiveBalanceCache() + _, err = cache.Get(st) + require.ErrorContains(t, ErrNotFound.Error(), err) + + b := uint64(100) + require.NoError(t, cache.AddTotalEffectiveBalance(st, b)) + cachedB, err := cache.Get(st) + require.NoError(t, err) + require.Equal(t, b, cachedB) + + require.NoError(t, st.SetSlot(1000)) + _, err = cache.Get(st) + require.ErrorContains(t, ErrNotFound.Error(), err) + + b = uint64(200) + require.NoError(t, cache.AddTotalEffectiveBalance(st, b)) + cachedB, err = cache.Get(st) + require.NoError(t, err) + require.Equal(t, b, cachedB) + + require.NoError(t, st.SetSlot(1000+params.BeaconConfig().SlotsPerHistoricalRoot)) + _, err = cache.Get(st) + require.ErrorContains(t, ErrNotFound.Error(), err) + + b = uint64(300) + require.NoError(t, cache.AddTotalEffectiveBalance(st, b)) + cachedB, err = cache.Get(st) + require.NoError(t, err) + require.Equal(t, b, cachedB) +} + +func TestBalanceCache_BalanceKey(t *testing.T) { + blockRoots := make([][]byte, params.BeaconConfig().SlotsPerHistoricalRoot) + for i := 0; i < len(blockRoots); i++ { + b := make([]byte, 8) + binary.LittleEndian.PutUint64(b, uint64(i)) + blockRoots[i] = b + } + raw := ðpb.BeaconState{ + BlockRoots: blockRoots, + } + st, err := state.InitializeFromProto(raw) + require.NoError(t, err) + require.NoError(t, st.SetSlot(types.Slot(math.MaxUint64))) + + _, err = balanceCacheKey(st) + require.NoError(t, err) +}