mirror of
https://gitlab.com/pulsechaincom/prysm-pulse.git
synced 2024-12-25 21:07:18 +00:00
39c33b82ad
* quick lazy balance cache proof of concept * WIP refactoring to use lazy cache * updating tests to use functional opts * updating the rest of the tests, all passing * use mock stategen where possible reduces the number of test cases that require db setup * rename test opt method for clear link * Update beacon-chain/blockchain/process_block.go Co-authored-by: terence tsao <terence@prysmaticlabs.com> * test assumption that zerohash is in db * remove unused MockDB (mocking stategen instead) * fix cache bug, switch to sync.Mutex * improve test coverage for the state cache * uncomment failing genesis test for discussion * gofmt * remove unused Service struct member * cleanup unused func input * combining type declaration in signature * don't export the state cache constructor * work around blockchain deps w/ new file service_test brings in a ton of dependencies that make bazel rules for blockchain complex, so just sticking these mocks in their own file simplifies things. * gofmt * remove intentionally failing test this test established that the zero root can't be used to look up the state, resulting in a change in another PR to update stategen to use the GenesisState db method instead when the zero root is detected. * fixed error introduced by develop refresh * fix import ordering * appease deepsource * remove unused function * godoc comments on new requires/assert * defensive constructor per terence's PR comment * more differentiated balance cache metric names Co-authored-by: kasey <kasey@users.noreply.github.com> Co-authored-by: terence tsao <terence@prysmaticlabs.com> Co-authored-by: Raul Jordan <raul@prysmaticlabs.com> Co-authored-by: prylabs-bulldozer[bot] <58059840+prylabs-bulldozer[bot]@users.noreply.github.com>
226 lines
6.2 KiB
Go
226 lines
6.2 KiB
Go
package blockchain
|
|
|
|
import (
|
|
"context"
|
|
"encoding/binary"
|
|
"errors"
|
|
"testing"
|
|
|
|
types "github.com/prysmaticlabs/eth2-types"
|
|
"github.com/prysmaticlabs/prysm/beacon-chain/state"
|
|
v2 "github.com/prysmaticlabs/prysm/beacon-chain/state/v2"
|
|
"github.com/prysmaticlabs/prysm/encoding/bytesutil"
|
|
ethpb "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1"
|
|
"github.com/prysmaticlabs/prysm/testing/require"
|
|
"github.com/prysmaticlabs/prysm/time/slots"
|
|
)
|
|
|
|
type mockStateByRoot struct {
|
|
state state.BeaconState
|
|
err error
|
|
}
|
|
|
|
func (m *mockStateByRoot) StateByRoot(context.Context, [32]byte) (state.BeaconState, error) {
|
|
return m.state, m.err
|
|
}
|
|
|
|
type testStateOpt func(*ethpb.BeaconStateAltair)
|
|
|
|
func testStateWithValidators(v []*ethpb.Validator) testStateOpt {
|
|
return func(a *ethpb.BeaconStateAltair) {
|
|
a.Validators = v
|
|
}
|
|
}
|
|
|
|
func testStateWithSlot(slot types.Slot) testStateOpt {
|
|
return func(a *ethpb.BeaconStateAltair) {
|
|
a.Slot = slot
|
|
}
|
|
}
|
|
|
|
func testStateFixture(opts ...testStateOpt) state.BeaconState {
|
|
a := ðpb.BeaconStateAltair{}
|
|
for _, o := range opts {
|
|
o(a)
|
|
}
|
|
s, _ := v2.InitializeFromProtoUnsafe(a)
|
|
return s
|
|
}
|
|
|
|
func generateTestValidators(count int, opts ...func(*ethpb.Validator)) []*ethpb.Validator {
|
|
vs := make([]*ethpb.Validator, count)
|
|
var i uint32 = 0
|
|
for ; i < uint32(count); i++ {
|
|
pk := make([]byte, 48)
|
|
binary.LittleEndian.PutUint32(pk, i)
|
|
v := ðpb.Validator{PublicKey: pk}
|
|
for _, o := range opts {
|
|
o(v)
|
|
}
|
|
vs[i] = v
|
|
}
|
|
return vs
|
|
}
|
|
|
|
func oddValidatorsExpired(currentSlot types.Slot) func(*ethpb.Validator) {
|
|
return func(v *ethpb.Validator) {
|
|
pki := binary.LittleEndian.Uint64(v.PublicKey)
|
|
if pki%2 == 0 {
|
|
v.ExitEpoch = types.Epoch(int(slots.ToEpoch(currentSlot)) + 1)
|
|
} else {
|
|
v.ExitEpoch = types.Epoch(int(slots.ToEpoch(currentSlot)) - 1)
|
|
}
|
|
}
|
|
}
|
|
|
|
func oddValidatorsQueued(currentSlot types.Slot) func(*ethpb.Validator) {
|
|
return func(v *ethpb.Validator) {
|
|
v.ExitEpoch = types.Epoch(int(slots.ToEpoch(currentSlot)) + 1)
|
|
pki := binary.LittleEndian.Uint64(v.PublicKey)
|
|
if pki%2 == 0 {
|
|
v.ActivationEpoch = types.Epoch(int(slots.ToEpoch(currentSlot)) - 1)
|
|
} else {
|
|
v.ActivationEpoch = types.Epoch(int(slots.ToEpoch(currentSlot)) + 1)
|
|
}
|
|
}
|
|
}
|
|
|
|
func allValidatorsValid(currentSlot types.Slot) func(*ethpb.Validator) {
|
|
return func(v *ethpb.Validator) {
|
|
v.ActivationEpoch = types.Epoch(int(slots.ToEpoch(currentSlot)) - 1)
|
|
v.ExitEpoch = types.Epoch(int(slots.ToEpoch(currentSlot)) + 1)
|
|
}
|
|
}
|
|
|
|
func balanceIsKeyTimes2(v *ethpb.Validator) {
|
|
pki := binary.LittleEndian.Uint64(v.PublicKey)
|
|
v.EffectiveBalance = uint64(pki) * 2
|
|
}
|
|
|
|
func testHalfExpiredValidators() ([]*ethpb.Validator, []uint64) {
|
|
balances := []uint64{0, 0, 4, 0, 8, 0, 12, 0, 16, 0}
|
|
return generateTestValidators(10,
|
|
oddValidatorsExpired(types.Slot(99)),
|
|
balanceIsKeyTimes2), balances
|
|
}
|
|
|
|
func testHalfQueuedValidators() ([]*ethpb.Validator, []uint64) {
|
|
balances := []uint64{0, 0, 4, 0, 8, 0, 12, 0, 16, 0}
|
|
return generateTestValidators(10,
|
|
oddValidatorsQueued(types.Slot(99)),
|
|
balanceIsKeyTimes2), balances
|
|
}
|
|
|
|
func testAllValidValidators() ([]*ethpb.Validator, []uint64) {
|
|
balances := []uint64{0, 2, 4, 6, 8, 10, 12, 14, 16, 18}
|
|
return generateTestValidators(10,
|
|
allValidatorsValid(types.Slot(99)),
|
|
balanceIsKeyTimes2), balances
|
|
}
|
|
|
|
func TestStateBalanceCache(t *testing.T) {
|
|
type sbcTestCase struct {
|
|
err error
|
|
root [32]byte
|
|
sbc *stateBalanceCache
|
|
balances []uint64
|
|
name string
|
|
}
|
|
sentinelCacheMiss := errors.New("Cache missed, as expected!")
|
|
sentinelBalances := []uint64{1, 2, 3, 4, 5}
|
|
halfExpiredValidators, halfExpiredBalances := testHalfExpiredValidators()
|
|
halfQueuedValidators, halfQueuedBalances := testHalfQueuedValidators()
|
|
allValidValidators, allValidBalances := testAllValidValidators()
|
|
cases := []sbcTestCase{
|
|
{
|
|
root: bytesutil.ToBytes32([]byte{'A'}),
|
|
balances: sentinelBalances,
|
|
sbc: &stateBalanceCache{
|
|
stateGen: &mockStateByRooter{
|
|
err: sentinelCacheMiss,
|
|
},
|
|
root: bytesutil.ToBytes32([]byte{'A'}),
|
|
balances: sentinelBalances,
|
|
},
|
|
name: "cache hit",
|
|
},
|
|
// this works by using a staterooter that returns a known error
|
|
// so really we're testing the miss by making sure stategen got called
|
|
// this also tells us stategen errors are propagated
|
|
{
|
|
sbc: &stateBalanceCache{
|
|
stateGen: &mockStateByRooter{
|
|
//state: generateTestValidators(1, testWithBadEpoch),
|
|
err: sentinelCacheMiss,
|
|
},
|
|
root: bytesutil.ToBytes32([]byte{'B'}),
|
|
},
|
|
err: sentinelCacheMiss,
|
|
root: bytesutil.ToBytes32([]byte{'A'}),
|
|
name: "cache miss",
|
|
},
|
|
{
|
|
sbc: &stateBalanceCache{
|
|
stateGen: &mockStateByRooter{},
|
|
root: bytesutil.ToBytes32([]byte{'B'}),
|
|
},
|
|
err: errNilStateFromStategen,
|
|
root: bytesutil.ToBytes32([]byte{'A'}),
|
|
name: "error for nil state upon cache miss",
|
|
},
|
|
{
|
|
sbc: &stateBalanceCache{
|
|
stateGen: &mockStateByRooter{
|
|
state: testStateFixture(
|
|
testStateWithSlot(99),
|
|
testStateWithValidators(halfExpiredValidators)),
|
|
},
|
|
},
|
|
balances: halfExpiredBalances,
|
|
root: bytesutil.ToBytes32([]byte{'A'}),
|
|
name: "test filtering by exit epoch",
|
|
},
|
|
{
|
|
sbc: &stateBalanceCache{
|
|
stateGen: &mockStateByRooter{
|
|
state: testStateFixture(
|
|
testStateWithSlot(99),
|
|
testStateWithValidators(halfQueuedValidators)),
|
|
},
|
|
},
|
|
balances: halfQueuedBalances,
|
|
root: bytesutil.ToBytes32([]byte{'A'}),
|
|
name: "test filtering by activation epoch",
|
|
},
|
|
{
|
|
sbc: &stateBalanceCache{
|
|
stateGen: &mockStateByRooter{
|
|
state: testStateFixture(
|
|
testStateWithSlot(99),
|
|
testStateWithValidators(allValidValidators)),
|
|
},
|
|
},
|
|
balances: allValidBalances,
|
|
root: bytesutil.ToBytes32([]byte{'A'}),
|
|
name: "happy path",
|
|
},
|
|
}
|
|
ctx := context.Background()
|
|
for _, c := range cases {
|
|
t.Run(c.name, func(t *testing.T) {
|
|
cache := c.sbc
|
|
cacheRootStart := cache.root
|
|
b, err := cache.get(ctx, c.root)
|
|
require.ErrorIs(t, err, c.err)
|
|
require.DeepEqual(t, c.balances, b)
|
|
if c.err != nil {
|
|
// if there was an error somewhere, the root should not have changed (unless it already matched)
|
|
require.Equal(t, cacheRootStart, cache.root)
|
|
} else {
|
|
// when successful, the cache should always end with a root matching the request
|
|
require.Equal(t, c.root, cache.root)
|
|
}
|
|
})
|
|
}
|
|
}
|