mirror of
https://github.com/OffchainLabs/prysm.git
synced 2026-01-06 22:23:56 -05:00
* Ran gopls modernize to fix everything go run golang.org/x/tools/gopls/internal/analysis/modernize/cmd/modernize@latest -fix -test ./... * Override rules_go provided dependency for golang.org/x/tools to v0.38.0. To update this, checked out rules_go, then ran `bazel run //go/tools/releaser -- upgrade-dep -mirror=false org_golang_x_tools` and copied the patches. * Fix buildtag violations and ignore buildtag violations in external * Introduce modernize analyzer package. * Add modernize "any" analyzer. * Fix violations of any analyzer * Add modernize "appendclipped" analyzer. * Fix violations of appendclipped * Add modernize "bloop" analyzer. * Add modernize "fmtappendf" analyzer. * Add modernize "forvar" analyzer. * Add modernize "mapsloop" analyzer. * Add modernize "minmax" analyzer. * Fix violations of minmax analyzer * Add modernize "omitzero" analyzer. * Add modernize "rangeint" analyzer. * Fix violations of rangeint. * Add modernize "reflecttypefor" analyzer. * Fix violations of reflecttypefor analyzer. * Add modernize "slicescontains" analyzer. * Add modernize "slicessort" analyzer. * Add modernize "slicesdelete" analyzer. This is disabled by default for now. See https://go.dev/issue/73686. * Add modernize "stringscutprefix" analyzer. * Add modernize "stringsbuilder" analyzer. * Fix violations of stringsbuilder analyzer. * Add modernize "stringsseq" analyzer. * Add modernize "testingcontext" analyzer. * Add modernize "waitgroup" analyzer. * Changelog fragment * gofmt * gazelle * Add modernize "newexpr" analyzer. * Disable newexpr until go1.26 * Add more details in WORKSPACE on how to update the override * @nalepae feedback on min() * gofmt * Fix violations of forvar
1332 lines
42 KiB
Go
1332 lines
42 KiB
Go
package kv
|
|
|
|
import (
|
|
"crypto/rand"
|
|
"encoding/binary"
|
|
mathRand "math/rand"
|
|
"strconv"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/OffchainLabs/prysm/v7/beacon-chain/state"
|
|
"github.com/OffchainLabs/prysm/v7/config/features"
|
|
fieldparams "github.com/OffchainLabs/prysm/v7/config/fieldparams"
|
|
"github.com/OffchainLabs/prysm/v7/config/params"
|
|
"github.com/OffchainLabs/prysm/v7/consensus-types/blocks"
|
|
"github.com/OffchainLabs/prysm/v7/consensus-types/interfaces"
|
|
"github.com/OffchainLabs/prysm/v7/consensus-types/primitives"
|
|
"github.com/OffchainLabs/prysm/v7/encoding/bytesutil"
|
|
"github.com/OffchainLabs/prysm/v7/genesis"
|
|
enginev1 "github.com/OffchainLabs/prysm/v7/proto/engine/v1"
|
|
ethpb "github.com/OffchainLabs/prysm/v7/proto/prysm/v1alpha1"
|
|
"github.com/OffchainLabs/prysm/v7/testing/assert"
|
|
"github.com/OffchainLabs/prysm/v7/testing/require"
|
|
"github.com/OffchainLabs/prysm/v7/testing/util"
|
|
bolt "go.etcd.io/bbolt"
|
|
)
|
|
|
|
func TestStateNil(t *testing.T) {
|
|
db := setupDB(t)
|
|
_, err := db.StateOrError(t.Context(), [32]byte{})
|
|
require.ErrorIs(t, err, ErrNotFoundState)
|
|
}
|
|
|
|
func TestState_CanSaveRetrieve(t *testing.T) {
|
|
type testCase struct {
|
|
name string
|
|
s func() state.BeaconState
|
|
rootSeed byte
|
|
}
|
|
|
|
cases := []testCase{
|
|
{
|
|
name: "phase0",
|
|
s: func() state.BeaconState {
|
|
st, err := util.NewBeaconState()
|
|
require.NoError(t, err)
|
|
require.NoError(t, st.SetSlot(100))
|
|
return st
|
|
},
|
|
rootSeed: '0',
|
|
},
|
|
{
|
|
name: "altair",
|
|
s: func() state.BeaconState {
|
|
st, err := util.NewBeaconStateAltair()
|
|
require.NoError(t, err)
|
|
require.NoError(t, st.SetSlot(100))
|
|
return st
|
|
},
|
|
rootSeed: 'A',
|
|
},
|
|
{
|
|
name: "bellatrix",
|
|
s: func() state.BeaconState {
|
|
st, err := util.NewBeaconStateBellatrix()
|
|
require.NoError(t, err)
|
|
require.NoError(t, st.SetSlot(100))
|
|
p, err := blocks.WrappedExecutionPayloadHeader(&enginev1.ExecutionPayloadHeader{
|
|
ParentHash: make([]byte, 32),
|
|
FeeRecipient: make([]byte, 20),
|
|
StateRoot: make([]byte, 32),
|
|
ReceiptsRoot: make([]byte, 32),
|
|
LogsBloom: make([]byte, 256),
|
|
PrevRandao: make([]byte, 32),
|
|
ExtraData: []byte("foo"),
|
|
BaseFeePerGas: make([]byte, 32),
|
|
BlockHash: make([]byte, 32),
|
|
TransactionsRoot: make([]byte, 32),
|
|
})
|
|
require.NoError(t, err)
|
|
require.NoError(t, st.SetLatestExecutionPayloadHeader(p))
|
|
return st
|
|
},
|
|
rootSeed: 'B',
|
|
},
|
|
{
|
|
name: "capella",
|
|
s: func() state.BeaconState {
|
|
st, err := util.NewBeaconStateCapella()
|
|
require.NoError(t, err)
|
|
require.NoError(t, st.SetSlot(100))
|
|
p, err := blocks.WrappedExecutionPayloadHeaderCapella(&enginev1.ExecutionPayloadHeaderCapella{
|
|
ParentHash: make([]byte, 32),
|
|
FeeRecipient: make([]byte, 20),
|
|
StateRoot: make([]byte, 32),
|
|
ReceiptsRoot: make([]byte, 32),
|
|
LogsBloom: make([]byte, 256),
|
|
PrevRandao: make([]byte, 32),
|
|
ExtraData: []byte("foo"),
|
|
BaseFeePerGas: make([]byte, 32),
|
|
BlockHash: make([]byte, 32),
|
|
TransactionsRoot: make([]byte, 32),
|
|
WithdrawalsRoot: make([]byte, 32),
|
|
})
|
|
require.NoError(t, err)
|
|
require.NoError(t, st.SetLatestExecutionPayloadHeader(p))
|
|
return st
|
|
},
|
|
rootSeed: 'C',
|
|
},
|
|
{
|
|
name: "deneb",
|
|
s: func() state.BeaconState {
|
|
st, err := util.NewBeaconStateDeneb()
|
|
require.NoError(t, err)
|
|
require.NoError(t, st.SetSlot(100))
|
|
p, err := blocks.WrappedExecutionPayloadHeaderDeneb(&enginev1.ExecutionPayloadHeaderDeneb{
|
|
ParentHash: make([]byte, 32),
|
|
FeeRecipient: make([]byte, 20),
|
|
StateRoot: make([]byte, 32),
|
|
ReceiptsRoot: make([]byte, 32),
|
|
LogsBloom: make([]byte, 256),
|
|
PrevRandao: make([]byte, 32),
|
|
ExtraData: []byte("foo"),
|
|
BaseFeePerGas: make([]byte, 32),
|
|
BlockHash: make([]byte, 32),
|
|
TransactionsRoot: make([]byte, 32),
|
|
WithdrawalsRoot: make([]byte, 32),
|
|
})
|
|
require.NoError(t, err)
|
|
require.NoError(t, st.SetLatestExecutionPayloadHeader(p))
|
|
return st
|
|
},
|
|
rootSeed: 'D',
|
|
},
|
|
{
|
|
name: "electra",
|
|
s: func() state.BeaconState {
|
|
st, err := util.NewBeaconStateElectra()
|
|
require.NoError(t, err)
|
|
require.NoError(t, st.SetSlot(100))
|
|
p, err := blocks.WrappedExecutionPayloadHeaderDeneb(&enginev1.ExecutionPayloadHeaderDeneb{
|
|
ParentHash: make([]byte, 32),
|
|
FeeRecipient: make([]byte, 20),
|
|
StateRoot: make([]byte, 32),
|
|
ReceiptsRoot: make([]byte, 32),
|
|
LogsBloom: make([]byte, 256),
|
|
PrevRandao: make([]byte, 32),
|
|
ExtraData: []byte("foo"),
|
|
BaseFeePerGas: make([]byte, 32),
|
|
BlockHash: make([]byte, 32),
|
|
TransactionsRoot: make([]byte, 32),
|
|
WithdrawalsRoot: make([]byte, 32),
|
|
})
|
|
require.NoError(t, err)
|
|
require.NoError(t, st.SetLatestExecutionPayloadHeader(p))
|
|
return st
|
|
},
|
|
rootSeed: 'E',
|
|
},
|
|
}
|
|
|
|
db := setupDB(t)
|
|
|
|
for _, enableFlag := range []bool{true, false} {
|
|
reset := features.InitWithReset(&features.Flags{EnableHistoricalSpaceRepresentation: enableFlag})
|
|
|
|
for _, tc := range cases {
|
|
t.Run(tc.name+" - EnableHistoricalSpaceRepresentation is "+strconv.FormatBool(enableFlag), func(t *testing.T) {
|
|
rootNonce := byte('0')
|
|
if enableFlag {
|
|
rootNonce = '1'
|
|
}
|
|
root := bytesutil.ToBytes32([]byte{tc.rootSeed, rootNonce})
|
|
require.Equal(t, false, db.HasState(t.Context(), root))
|
|
st := tc.s()
|
|
|
|
require.NoError(t, db.SaveState(t.Context(), st, root))
|
|
assert.Equal(t, true, db.HasState(t.Context(), root))
|
|
|
|
savedSt, err := db.State(t.Context(), root)
|
|
require.NoError(t, err)
|
|
|
|
assert.DeepSSZEqual(t, st.ToProtoUnsafe(), savedSt.ToProtoUnsafe())
|
|
})
|
|
}
|
|
|
|
reset()
|
|
}
|
|
}
|
|
|
|
func TestState_CanSaveRetrieveValidatorEntries(t *testing.T) {
|
|
db := setupDB(t)
|
|
|
|
// enable historical state representation flag to test this
|
|
resetCfg := features.InitWithReset(&features.Flags{
|
|
EnableHistoricalSpaceRepresentation: true,
|
|
})
|
|
defer resetCfg()
|
|
|
|
r := [32]byte{'A'}
|
|
|
|
require.Equal(t, false, db.HasState(t.Context(), r))
|
|
|
|
stateValidators := validators(10)
|
|
st, err := util.NewBeaconState()
|
|
require.NoError(t, err)
|
|
require.NoError(t, st.SetSlot(100))
|
|
require.NoError(t, st.SetValidators(stateValidators))
|
|
|
|
ctx := t.Context()
|
|
require.NoError(t, db.SaveState(ctx, st, r))
|
|
assert.Equal(t, true, db.HasState(t.Context(), r))
|
|
|
|
savedS, err := db.State(t.Context(), r)
|
|
require.NoError(t, err)
|
|
|
|
require.DeepSSZEqual(t, st.ToProtoUnsafe(), savedS.ToProtoUnsafe(), "saved state with validators and retrieved state are not matching")
|
|
|
|
// check if the index of the second state is still present.
|
|
err = db.db.Update(func(tx *bolt.Tx) error {
|
|
idxBkt := tx.Bucket(blockRootValidatorHashesBucket)
|
|
data := idxBkt.Get(r[:])
|
|
require.NotEqual(t, 0, len(data))
|
|
return nil
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
// check if all the validator entries are still intact in the validator entry bucket.
|
|
err = db.db.Update(func(tx *bolt.Tx) error {
|
|
valBkt := tx.Bucket(stateValidatorsBucket)
|
|
// if any of the original validator entry is not present, then fail the test.
|
|
for _, val := range stateValidators {
|
|
hash, hashErr := val.HashTreeRoot()
|
|
assert.NoError(t, hashErr)
|
|
data := valBkt.Get(hash[:])
|
|
require.NotNil(t, data)
|
|
require.NotEqual(t, 0, len(data))
|
|
}
|
|
return nil
|
|
})
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
func TestStateAltair_CanSaveRetrieveValidatorEntries(t *testing.T) {
|
|
db := setupDB(t)
|
|
|
|
// enable historical state representation flag to test this
|
|
resetCfg := features.InitWithReset(&features.Flags{
|
|
EnableHistoricalSpaceRepresentation: true,
|
|
})
|
|
defer resetCfg()
|
|
|
|
r := [32]byte{'A'}
|
|
|
|
require.Equal(t, false, db.HasState(t.Context(), r))
|
|
|
|
stateValidators := validators(10)
|
|
st, _ := util.DeterministicGenesisStateAltair(t, 20)
|
|
require.NoError(t, st.SetSlot(100))
|
|
require.NoError(t, st.SetValidators(stateValidators))
|
|
|
|
ctx := t.Context()
|
|
require.NoError(t, db.SaveState(ctx, st, r))
|
|
assert.Equal(t, true, db.HasState(t.Context(), r))
|
|
|
|
savedS, err := db.State(t.Context(), r)
|
|
require.NoError(t, err)
|
|
|
|
require.DeepSSZEqual(t, st.ToProtoUnsafe(), savedS.ToProtoUnsafe(), "saved state with validators and retrieved state are not matching")
|
|
|
|
// check if the index of the second state is still present.
|
|
err = db.db.Update(func(tx *bolt.Tx) error {
|
|
idxBkt := tx.Bucket(blockRootValidatorHashesBucket)
|
|
data := idxBkt.Get(r[:])
|
|
require.NotEqual(t, 0, len(data))
|
|
return nil
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
// check if all the validator entries are still intact in the validator entry bucket.
|
|
err = db.db.Update(func(tx *bolt.Tx) error {
|
|
valBkt := tx.Bucket(stateValidatorsBucket)
|
|
// if any of the original validator entry is not present, then fail the test.
|
|
for _, val := range stateValidators {
|
|
hash, hashErr := val.HashTreeRoot()
|
|
assert.NoError(t, hashErr)
|
|
data := valBkt.Get(hash[:])
|
|
require.NotNil(t, data)
|
|
require.NotEqual(t, 0, len(data))
|
|
}
|
|
return nil
|
|
})
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
func TestState_CanSaveRetrieveValidatorEntriesFromCache(t *testing.T) {
|
|
db := setupDB(t)
|
|
|
|
// enable historical state representation flag to test this
|
|
resetCfg := features.InitWithReset(&features.Flags{
|
|
EnableHistoricalSpaceRepresentation: true,
|
|
})
|
|
defer resetCfg()
|
|
|
|
r := [32]byte{'A'}
|
|
|
|
require.Equal(t, false, db.HasState(t.Context(), r))
|
|
|
|
stateValidators := validators(10)
|
|
st, err := util.NewBeaconState()
|
|
require.NoError(t, err)
|
|
require.NoError(t, st.SetSlot(100))
|
|
require.NoError(t, st.SetValidators(stateValidators))
|
|
|
|
ctx := t.Context()
|
|
require.NoError(t, db.SaveState(ctx, st, r))
|
|
assert.Equal(t, true, db.HasState(t.Context(), r))
|
|
|
|
// check if the state is in cache
|
|
for i := range stateValidators {
|
|
hash, hashErr := stateValidators[i].HashTreeRoot()
|
|
assert.NoError(t, hashErr)
|
|
|
|
data, ok := db.validatorEntryCache.Get(hash[:])
|
|
assert.Equal(t, true, ok)
|
|
require.NotNil(t, data)
|
|
|
|
require.DeepSSZEqual(t, stateValidators[i], data, "validator entry is not matching")
|
|
}
|
|
|
|
// check if all the validator entries are still intact in the validator entry bucket.
|
|
err = db.db.Update(func(tx *bolt.Tx) error {
|
|
valBkt := tx.Bucket(stateValidatorsBucket)
|
|
// if any of the original validator entry is not present, then fail the test.
|
|
for _, val := range stateValidators {
|
|
hash, hashErr := val.HashTreeRoot()
|
|
assert.NoError(t, hashErr)
|
|
data := valBkt.Get(hash[:])
|
|
require.NotNil(t, data)
|
|
require.NotEqual(t, 0, len(data))
|
|
}
|
|
return nil
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
}
|
|
|
|
func TestState_CanSaveRetrieveValidatorEntriesWithoutCache(t *testing.T) {
|
|
db := setupDB(t)
|
|
|
|
// enable historical state representation flag to test this
|
|
resetCfg := features.InitWithReset(&features.Flags{
|
|
EnableHistoricalSpaceRepresentation: true,
|
|
})
|
|
defer resetCfg()
|
|
|
|
r := [32]byte{'A'}
|
|
|
|
require.Equal(t, false, db.HasState(t.Context(), r))
|
|
|
|
stateValidators := validators(10)
|
|
st, err := util.NewBeaconState()
|
|
require.NoError(t, err)
|
|
require.NoError(t, st.SetSlot(100))
|
|
require.NoError(t, st.SetValidators(stateValidators))
|
|
|
|
ctx := t.Context()
|
|
require.NoError(t, db.SaveState(ctx, st, r))
|
|
assert.Equal(t, true, db.HasState(t.Context(), r))
|
|
db.validatorEntryCache.Clear()
|
|
|
|
savedS, err := db.State(t.Context(), r)
|
|
require.NoError(t, err)
|
|
|
|
require.DeepSSZEqual(t, st.ToProtoUnsafe(), savedS.ToProtoUnsafe(), "saved state with validators and retrieved state are not matching")
|
|
|
|
// check if the index of the second state is still present.
|
|
err = db.db.Update(func(tx *bolt.Tx) error {
|
|
idxBkt := tx.Bucket(blockRootValidatorHashesBucket)
|
|
data := idxBkt.Get(r[:])
|
|
require.NotEqual(t, 0, len(data))
|
|
return nil
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
// check if all the validator entries are still intact in the validator entry bucket.
|
|
err = db.db.Update(func(tx *bolt.Tx) error {
|
|
valBkt := tx.Bucket(stateValidatorsBucket)
|
|
// if any of the original validator entry is not present, then fail the test.
|
|
for _, val := range stateValidators {
|
|
hash, hashErr := val.HashTreeRoot()
|
|
assert.NoError(t, hashErr)
|
|
data := valBkt.Get(hash[:])
|
|
require.NotNil(t, data)
|
|
require.NotEqual(t, 0, len(data))
|
|
}
|
|
return nil
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
}
|
|
|
|
func TestState_DeleteState(t *testing.T) {
|
|
db := setupDB(t)
|
|
|
|
// enable historical state representation flag to test this
|
|
resetCfg := features.InitWithReset(&features.Flags{
|
|
EnableHistoricalSpaceRepresentation: true,
|
|
})
|
|
defer resetCfg()
|
|
|
|
r1 := [32]byte{'A'}
|
|
r2 := [32]byte{'B'}
|
|
|
|
require.Equal(t, false, db.HasState(t.Context(), r1))
|
|
require.Equal(t, false, db.HasState(t.Context(), r2))
|
|
|
|
// create two states with the same set of validators.
|
|
stateValidators := validators(10)
|
|
st1, err := util.NewBeaconState()
|
|
require.NoError(t, err)
|
|
require.NoError(t, st1.SetSlot(100))
|
|
require.NoError(t, st1.SetValidators(stateValidators))
|
|
|
|
st2, err := util.NewBeaconState()
|
|
require.NoError(t, err)
|
|
require.NoError(t, st2.SetSlot(101))
|
|
require.NoError(t, st2.SetValidators(stateValidators))
|
|
|
|
// save both the states.
|
|
ctx := t.Context()
|
|
require.NoError(t, db.SaveState(ctx, st1, r1))
|
|
require.NoError(t, db.SaveState(ctx, st2, r2))
|
|
|
|
// delete the first state.
|
|
var deleteBlockRoots [][32]byte
|
|
deleteBlockRoots = append(deleteBlockRoots, r1)
|
|
require.NoError(t, db.DeleteStates(ctx, deleteBlockRoots))
|
|
|
|
// check if the validator entries of this state is removed from cache.
|
|
for _, val := range stateValidators {
|
|
hash, hashErr := val.HashTreeRoot()
|
|
assert.NoError(t, hashErr)
|
|
v, found := db.validatorEntryCache.Get(hash[:])
|
|
require.Equal(t, false, found)
|
|
require.IsNil(t, v)
|
|
}
|
|
|
|
// check if the index of the first state is deleted.
|
|
err = db.db.Update(func(tx *bolt.Tx) error {
|
|
idxBkt := tx.Bucket(blockRootValidatorHashesBucket)
|
|
data := idxBkt.Get(r1[:])
|
|
require.Equal(t, 0, len(data))
|
|
return nil
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
// check if the index of the second state is still present.
|
|
err = db.db.Update(func(tx *bolt.Tx) error {
|
|
idxBkt := tx.Bucket(blockRootValidatorHashesBucket)
|
|
data := idxBkt.Get(r2[:])
|
|
require.NotEqual(t, 0, len(data))
|
|
return nil
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
// check if all the validator entries are still intact in the validator entry bucket.
|
|
err = db.db.Update(func(tx *bolt.Tx) error {
|
|
valBkt := tx.Bucket(stateValidatorsBucket)
|
|
// if any of the original validator entry is not present, then fail the test.
|
|
for _, val := range stateValidators {
|
|
hash, hashErr := val.HashTreeRoot()
|
|
assert.NoError(t, hashErr)
|
|
data := valBkt.Get(hash[:])
|
|
require.NotNil(t, data)
|
|
require.NotEqual(t, 0, len(data))
|
|
}
|
|
return nil
|
|
})
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
func TestGenesisState_CanSaveRetrieve(t *testing.T) {
|
|
db := setupDB(t)
|
|
|
|
headRoot := [32]byte{'B'}
|
|
|
|
st, err := util.NewBeaconState()
|
|
require.NoError(t, err)
|
|
require.NoError(t, st.SetSlot(1))
|
|
require.NoError(t, db.SaveGenesisBlockRoot(t.Context(), headRoot))
|
|
genesis.StoreStateDuringTest(t, st)
|
|
|
|
savedGenesisS, err := db.GenesisState(t.Context())
|
|
require.NoError(t, err)
|
|
assert.DeepSSZEqual(t, st.ToProtoUnsafe(), savedGenesisS.ToProtoUnsafe(), "Did not retrieve saved state")
|
|
require.NoError(t, db.SaveGenesisBlockRoot(t.Context(), [32]byte{'C'}))
|
|
}
|
|
|
|
func TestStore_StatesBatchDelete(t *testing.T) {
|
|
db := setupDB(t)
|
|
ctx := t.Context()
|
|
numBlocks := 100
|
|
totalBlocks := make([]interfaces.ReadOnlySignedBeaconBlock, numBlocks)
|
|
blockRoots := make([][32]byte, 0)
|
|
evenBlockRoots := make([][32]byte, 0)
|
|
for i := range totalBlocks {
|
|
b := util.NewBeaconBlock()
|
|
b.Block.Slot = primitives.Slot(i)
|
|
var err error
|
|
totalBlocks[i], err = blocks.NewSignedBeaconBlock(b)
|
|
require.NoError(t, err)
|
|
r, err := totalBlocks[i].Block().HashTreeRoot()
|
|
require.NoError(t, err)
|
|
st, err := util.NewBeaconState()
|
|
require.NoError(t, err)
|
|
require.NoError(t, st.SetSlot(primitives.Slot(i)))
|
|
require.NoError(t, db.SaveState(t.Context(), st, r))
|
|
blockRoots = append(blockRoots, r)
|
|
if i%2 == 0 {
|
|
evenBlockRoots = append(evenBlockRoots, r)
|
|
}
|
|
}
|
|
require.NoError(t, db.SaveBlocks(ctx, totalBlocks))
|
|
// We delete all even indexed states.
|
|
require.NoError(t, db.DeleteStates(ctx, evenBlockRoots))
|
|
// When we retrieve the data, only the odd indexed state should remain.
|
|
for _, r := range blockRoots {
|
|
s, err := db.State(t.Context(), r)
|
|
require.NoError(t, err)
|
|
if s == nil {
|
|
continue
|
|
}
|
|
assert.Equal(t, primitives.Slot(1), s.Slot()%2, "State with slot %d should have been deleted", s.Slot())
|
|
}
|
|
}
|
|
|
|
func TestStore_DeleteGenesisState(t *testing.T) {
|
|
db := setupDB(t)
|
|
ctx := t.Context()
|
|
|
|
genesisBlockRoot := [32]byte{'A'}
|
|
require.NoError(t, db.SaveGenesisBlockRoot(ctx, genesisBlockRoot))
|
|
st, err := util.NewBeaconState()
|
|
require.NoError(t, err)
|
|
require.NoError(t, st.SetSlot(100))
|
|
require.NoError(t, db.SaveState(ctx, st, genesisBlockRoot))
|
|
wantedErr := "cannot delete finalized block or state"
|
|
assert.ErrorContains(t, wantedErr, db.DeleteState(ctx, genesisBlockRoot))
|
|
}
|
|
|
|
func TestStore_DeleteFinalizedState(t *testing.T) {
|
|
db := setupDB(t)
|
|
ctx := t.Context()
|
|
|
|
genesis := bytesutil.ToBytes32([]byte{'G', 'E', 'N', 'E', 'S', 'I', 'S'})
|
|
require.NoError(t, db.SaveGenesisBlockRoot(ctx, genesis))
|
|
|
|
blk := util.NewBeaconBlock()
|
|
blk.Block.ParentRoot = genesis[:]
|
|
blk.Block.Slot = 100
|
|
|
|
wsb, err := blocks.NewSignedBeaconBlock(blk)
|
|
require.NoError(t, err)
|
|
require.NoError(t, db.SaveBlock(ctx, wsb))
|
|
|
|
finalizedBlockRoot, err := blk.Block.HashTreeRoot()
|
|
require.NoError(t, err)
|
|
|
|
finalizedState, err := util.NewBeaconState()
|
|
require.NoError(t, err)
|
|
require.NoError(t, finalizedState.SetSlot(100))
|
|
require.NoError(t, db.SaveState(ctx, finalizedState, finalizedBlockRoot))
|
|
finalizedCheckpoint := ðpb.Checkpoint{Root: finalizedBlockRoot[:]}
|
|
require.NoError(t, db.SaveFinalizedCheckpoint(ctx, finalizedCheckpoint))
|
|
wantedErr := "cannot delete finalized block or state"
|
|
assert.ErrorContains(t, wantedErr, db.DeleteState(ctx, finalizedBlockRoot))
|
|
}
|
|
|
|
func TestStore_DeleteHeadState(t *testing.T) {
|
|
db := setupDB(t)
|
|
ctx := t.Context()
|
|
|
|
genesis := bytesutil.ToBytes32([]byte{'G', 'E', 'N', 'E', 'S', 'I', 'S'})
|
|
require.NoError(t, db.SaveGenesisBlockRoot(ctx, genesis))
|
|
|
|
blk := util.NewBeaconBlock()
|
|
blk.Block.ParentRoot = genesis[:]
|
|
blk.Block.Slot = 100
|
|
wsb, err := blocks.NewSignedBeaconBlock(blk)
|
|
require.NoError(t, err)
|
|
require.NoError(t, db.SaveBlock(ctx, wsb))
|
|
|
|
headBlockRoot, err := blk.Block.HashTreeRoot()
|
|
require.NoError(t, err)
|
|
st, err := util.NewBeaconState()
|
|
require.NoError(t, err)
|
|
require.NoError(t, st.SetSlot(100))
|
|
require.NoError(t, db.SaveState(ctx, st, headBlockRoot))
|
|
require.NoError(t, db.SaveHeadBlockRoot(ctx, headBlockRoot))
|
|
require.NoError(t, db.DeleteState(ctx, headBlockRoot)) // Ok to delete head state if it's optimistic.
|
|
}
|
|
|
|
func TestStore_SaveDeleteState_CanGetHighestBelow(t *testing.T) {
|
|
db := setupDB(t)
|
|
|
|
b := util.NewBeaconBlock()
|
|
b.Block.Slot = 1
|
|
r, err := b.Block.HashTreeRoot()
|
|
require.NoError(t, err)
|
|
wsb, err := blocks.NewSignedBeaconBlock(b)
|
|
require.NoError(t, err)
|
|
require.NoError(t, db.SaveBlock(t.Context(), wsb))
|
|
st, err := util.NewBeaconState()
|
|
require.NoError(t, err)
|
|
require.NoError(t, st.SetSlot(1))
|
|
s0 := st.ToProtoUnsafe()
|
|
require.NoError(t, db.SaveState(t.Context(), st, r))
|
|
|
|
b.Block.Slot = 100
|
|
r1, err := b.Block.HashTreeRoot()
|
|
require.NoError(t, err)
|
|
wsb, err = blocks.NewSignedBeaconBlock(b)
|
|
require.NoError(t, err)
|
|
require.NoError(t, db.SaveBlock(t.Context(), wsb))
|
|
st, err = util.NewBeaconState()
|
|
require.NoError(t, err)
|
|
require.NoError(t, st.SetSlot(100))
|
|
s1 := st.ToProtoUnsafe()
|
|
require.NoError(t, db.SaveState(t.Context(), st, r1))
|
|
|
|
b.Block.Slot = 1000
|
|
r2, err := b.Block.HashTreeRoot()
|
|
require.NoError(t, err)
|
|
wsb, err = blocks.NewSignedBeaconBlock(b)
|
|
require.NoError(t, err)
|
|
require.NoError(t, db.SaveBlock(t.Context(), wsb))
|
|
st, err = util.NewBeaconState()
|
|
require.NoError(t, err)
|
|
require.NoError(t, st.SetSlot(1000))
|
|
s2 := st.ToProtoUnsafe()
|
|
|
|
require.NoError(t, db.SaveState(t.Context(), st, r2))
|
|
|
|
highest, err := db.HighestSlotStatesBelow(t.Context(), 2)
|
|
require.NoError(t, err)
|
|
assert.DeepSSZEqual(t, highest[0].ToProtoUnsafe(), s0)
|
|
|
|
highest, err = db.HighestSlotStatesBelow(t.Context(), 101)
|
|
require.NoError(t, err)
|
|
assert.DeepSSZEqual(t, highest[0].ToProtoUnsafe(), s1)
|
|
|
|
highest, err = db.HighestSlotStatesBelow(t.Context(), 1001)
|
|
require.NoError(t, err)
|
|
assert.DeepSSZEqual(t, highest[0].ToProtoUnsafe(), s2)
|
|
}
|
|
|
|
func TestStore_GenesisState_CanGetHighestBelow(t *testing.T) {
|
|
db := setupDB(t)
|
|
|
|
genesisState, err := util.NewBeaconState()
|
|
require.NoError(t, err)
|
|
genesisRoot := [32]byte{'a'}
|
|
require.NoError(t, db.SaveGenesisBlockRoot(t.Context(), genesisRoot))
|
|
genesis.StoreStateDuringTest(t, genesisState)
|
|
|
|
b := util.NewBeaconBlock()
|
|
b.Block.Slot = 1
|
|
r, err := b.Block.HashTreeRoot()
|
|
require.NoError(t, err)
|
|
wsb, err := blocks.NewSignedBeaconBlock(b)
|
|
require.NoError(t, err)
|
|
require.NoError(t, db.SaveBlock(t.Context(), wsb))
|
|
|
|
st, err := util.NewBeaconState()
|
|
require.NoError(t, err)
|
|
require.NoError(t, st.SetSlot(1))
|
|
require.NoError(t, db.SaveState(t.Context(), st, r))
|
|
|
|
highest, err := db.HighestSlotStatesBelow(t.Context(), 2)
|
|
require.NoError(t, err)
|
|
assert.DeepSSZEqual(t, highest[0].ToProtoUnsafe(), st.ToProtoUnsafe())
|
|
|
|
highest, err = db.HighestSlotStatesBelow(t.Context(), 1)
|
|
require.NoError(t, err)
|
|
assert.DeepSSZEqual(t, highest[0].ToProtoUnsafe(), genesisState.ToProtoUnsafe())
|
|
highest, err = db.HighestSlotStatesBelow(t.Context(), 0)
|
|
require.NoError(t, err)
|
|
assert.DeepSSZEqual(t, highest[0].ToProtoUnsafe(), genesisState.ToProtoUnsafe())
|
|
}
|
|
|
|
func TestStore_CleanUpDirtyStates_AboveThreshold(t *testing.T) {
|
|
db := setupDB(t)
|
|
|
|
genesisState, err := util.NewBeaconState()
|
|
require.NoError(t, err)
|
|
genesisRoot := [32]byte{'a'}
|
|
require.NoError(t, db.SaveGenesisBlockRoot(t.Context(), genesisRoot))
|
|
require.NoError(t, db.SaveState(t.Context(), genesisState, genesisRoot))
|
|
require.NoError(t, db.SaveOriginCheckpointBlockRoot(t.Context(), [32]byte{'a'}))
|
|
|
|
bRoots := make([][32]byte, 0)
|
|
slotsPerArchivedPoint := primitives.Slot(128)
|
|
prevRoot := genesisRoot
|
|
for i := primitives.Slot(1); i <= slotsPerArchivedPoint; i++ {
|
|
b := util.NewBeaconBlock()
|
|
b.Block.Slot = i
|
|
b.Block.ParentRoot = prevRoot[:]
|
|
r, err := b.Block.HashTreeRoot()
|
|
require.NoError(t, err)
|
|
wsb, err := blocks.NewSignedBeaconBlock(b)
|
|
require.NoError(t, err)
|
|
require.NoError(t, db.SaveBlock(t.Context(), wsb))
|
|
bRoots = append(bRoots, r)
|
|
prevRoot = r
|
|
|
|
st, err := util.NewBeaconState()
|
|
require.NoError(t, err)
|
|
require.NoError(t, st.SetSlot(i))
|
|
require.NoError(t, db.SaveState(t.Context(), st, r))
|
|
}
|
|
|
|
require.NoError(t, db.SaveFinalizedCheckpoint(t.Context(), ðpb.Checkpoint{
|
|
Root: bRoots[len(bRoots)-1][:],
|
|
Epoch: primitives.Epoch(slotsPerArchivedPoint / params.BeaconConfig().SlotsPerEpoch),
|
|
}))
|
|
require.NoError(t, db.CleanUpDirtyStates(t.Context(), slotsPerArchivedPoint))
|
|
|
|
for i, root := range bRoots {
|
|
if primitives.Slot(i) >= slotsPerArchivedPoint.SubSlot(slotsPerArchivedPoint.Div(3)) {
|
|
require.Equal(t, true, db.HasState(t.Context(), root))
|
|
} else {
|
|
require.Equal(t, false, db.HasState(t.Context(), root))
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestStore_CleanUpDirtyStates_Finalized(t *testing.T) {
|
|
db := setupDB(t)
|
|
|
|
genesisState, err := util.NewBeaconState()
|
|
require.NoError(t, err)
|
|
genesisRoot := [32]byte{'a'}
|
|
require.NoError(t, db.SaveGenesisBlockRoot(t.Context(), genesisRoot))
|
|
require.NoError(t, db.SaveState(t.Context(), genesisState, genesisRoot))
|
|
require.NoError(t, db.SaveOriginCheckpointBlockRoot(t.Context(), [32]byte{'a'}))
|
|
|
|
for i := primitives.Slot(1); i <= params.BeaconConfig().SlotsPerEpoch; i++ {
|
|
b := util.NewBeaconBlock()
|
|
b.Block.Slot = i
|
|
r, err := b.Block.HashTreeRoot()
|
|
require.NoError(t, err)
|
|
wsb, err := blocks.NewSignedBeaconBlock(b)
|
|
require.NoError(t, err)
|
|
require.NoError(t, db.SaveBlock(t.Context(), wsb))
|
|
|
|
st, err := util.NewBeaconState()
|
|
require.NoError(t, err)
|
|
require.NoError(t, st.SetSlot(i))
|
|
require.NoError(t, db.SaveState(t.Context(), st, r))
|
|
}
|
|
|
|
require.NoError(t, db.SaveFinalizedCheckpoint(t.Context(), ðpb.Checkpoint{Root: genesisRoot[:]}))
|
|
require.NoError(t, db.CleanUpDirtyStates(t.Context(), params.BeaconConfig().SlotsPerEpoch))
|
|
require.Equal(t, true, db.HasState(t.Context(), genesisRoot))
|
|
}
|
|
|
|
func TestStore_CleanUpDirtyStates_OriginRoot(t *testing.T) {
|
|
db := setupDB(t)
|
|
|
|
genesisState, err := util.NewBeaconState()
|
|
require.NoError(t, err)
|
|
r := [32]byte{'a'}
|
|
require.NoError(t, db.SaveGenesisBlockRoot(t.Context(), r))
|
|
require.NoError(t, db.SaveState(t.Context(), genesisState, r))
|
|
|
|
for i := primitives.Slot(1); i <= params.BeaconConfig().SlotsPerEpoch; i++ {
|
|
b := util.NewBeaconBlock()
|
|
b.Block.Slot = i
|
|
r, err := b.Block.HashTreeRoot()
|
|
require.NoError(t, err)
|
|
wsb, err := blocks.NewSignedBeaconBlock(b)
|
|
require.NoError(t, err)
|
|
require.NoError(t, db.SaveBlock(t.Context(), wsb))
|
|
|
|
st, err := util.NewBeaconState()
|
|
require.NoError(t, err)
|
|
require.NoError(t, st.SetSlot(i))
|
|
require.NoError(t, db.SaveState(t.Context(), st, r))
|
|
}
|
|
|
|
require.NoError(t, db.SaveOriginCheckpointBlockRoot(t.Context(), r))
|
|
require.NoError(t, db.CleanUpDirtyStates(t.Context(), params.BeaconConfig().SlotsPerEpoch))
|
|
require.Equal(t, true, db.HasState(t.Context(), r))
|
|
}
|
|
|
|
func TestStore_CleanUpDirtyStates_DontDeleteNonFinalized(t *testing.T) {
|
|
db := setupDB(t)
|
|
|
|
genesisState, err := util.NewBeaconState()
|
|
require.NoError(t, err)
|
|
genesisRoot := [32]byte{'a'}
|
|
require.NoError(t, db.SaveGenesisBlockRoot(t.Context(), genesisRoot))
|
|
require.NoError(t, db.SaveState(t.Context(), genesisState, genesisRoot))
|
|
require.NoError(t, db.SaveOriginCheckpointBlockRoot(t.Context(), [32]byte{'a'}))
|
|
|
|
var unfinalizedRoots [][32]byte
|
|
for i := primitives.Slot(1); i <= params.BeaconConfig().SlotsPerEpoch; i++ {
|
|
b := util.NewBeaconBlock()
|
|
b.Block.Slot = i
|
|
r, err := b.Block.HashTreeRoot()
|
|
require.NoError(t, err)
|
|
wsb, err := blocks.NewSignedBeaconBlock(b)
|
|
require.NoError(t, err)
|
|
require.NoError(t, db.SaveBlock(t.Context(), wsb))
|
|
unfinalizedRoots = append(unfinalizedRoots, r)
|
|
|
|
st, err := util.NewBeaconState()
|
|
require.NoError(t, err)
|
|
require.NoError(t, st.SetSlot(i))
|
|
require.NoError(t, db.SaveState(t.Context(), st, r))
|
|
}
|
|
|
|
require.NoError(t, db.SaveFinalizedCheckpoint(t.Context(), ðpb.Checkpoint{Root: genesisRoot[:]}))
|
|
require.NoError(t, db.CleanUpDirtyStates(t.Context(), params.BeaconConfig().SlotsPerEpoch))
|
|
|
|
for _, rt := range unfinalizedRoots {
|
|
require.Equal(t, true, db.HasState(t.Context(), rt))
|
|
}
|
|
}
|
|
|
|
func TestAltairState_CanSaveRetrieve(t *testing.T) {
|
|
db := setupDB(t)
|
|
|
|
r := [32]byte{'A'}
|
|
|
|
require.Equal(t, false, db.HasState(t.Context(), r))
|
|
|
|
st, _ := util.DeterministicGenesisStateAltair(t, 1)
|
|
require.NoError(t, st.SetSlot(100))
|
|
|
|
require.NoError(t, db.SaveState(t.Context(), st, r))
|
|
require.Equal(t, true, db.HasState(t.Context(), r))
|
|
|
|
savedS, err := db.State(t.Context(), r)
|
|
require.NoError(t, err)
|
|
|
|
require.DeepSSZEqual(t, st.ToProtoUnsafe(), savedS.ToProtoUnsafe())
|
|
|
|
savedS, err = db.State(t.Context(), [32]byte{'B'})
|
|
require.NoError(t, err)
|
|
require.Equal(t, state.ReadOnlyBeaconState(nil), savedS, "Unsaved state should've been nil")
|
|
}
|
|
|
|
func TestAltairState_CanDelete(t *testing.T) {
|
|
db := setupDB(t)
|
|
|
|
r := [32]byte{'A'}
|
|
|
|
require.Equal(t, false, db.HasState(t.Context(), r))
|
|
|
|
st, _ := util.DeterministicGenesisStateAltair(t, 1)
|
|
require.NoError(t, st.SetSlot(100))
|
|
|
|
require.NoError(t, db.SaveState(t.Context(), st, r))
|
|
require.Equal(t, true, db.HasState(t.Context(), r))
|
|
|
|
require.NoError(t, db.DeleteState(t.Context(), r))
|
|
savedS, err := db.State(t.Context(), r)
|
|
require.NoError(t, err)
|
|
require.Equal(t, state.ReadOnlyBeaconState(nil), savedS, "Unsaved state should've been nil")
|
|
}
|
|
|
|
func validators(limit int) []*ethpb.Validator {
|
|
var vals []*ethpb.Validator
|
|
for i := range limit {
|
|
pubKey := make([]byte, params.BeaconConfig().BLSPubkeyLength)
|
|
binary.LittleEndian.PutUint64(pubKey, mathRand.Uint64())
|
|
val := ðpb.Validator{
|
|
PublicKey: pubKey,
|
|
WithdrawalCredentials: bytesutil.ToBytes(mathRand.Uint64(), 32),
|
|
EffectiveBalance: mathRand.Uint64(),
|
|
Slashed: i%2 != 0,
|
|
ActivationEligibilityEpoch: primitives.Epoch(mathRand.Uint64()),
|
|
ActivationEpoch: primitives.Epoch(mathRand.Uint64()),
|
|
ExitEpoch: primitives.Epoch(mathRand.Uint64()),
|
|
WithdrawableEpoch: primitives.Epoch(mathRand.Uint64()),
|
|
}
|
|
vals = append(vals, val)
|
|
}
|
|
return vals
|
|
}
|
|
|
|
func checkStateSaveTime(b *testing.B, saveCount int) {
|
|
|
|
db := setupDB(b)
|
|
initialSetOfValidators := validators(100000)
|
|
|
|
// construct some states and save to randomize benchmark.
|
|
for range saveCount {
|
|
key := make([]byte, 32)
|
|
_, err := rand.Read(key)
|
|
require.NoError(b, err)
|
|
st, err := util.NewBeaconState()
|
|
require.NoError(b, err)
|
|
|
|
// Add some more new validator to the base validator.
|
|
validatosToAddInTest := validators(10000)
|
|
allValidators := append(initialSetOfValidators, validatosToAddInTest...)
|
|
|
|
// shuffle validators.
|
|
mathRand.New(mathRand.NewSource(time.Now().UnixNano()))
|
|
mathRand.Shuffle(len(allValidators), func(i, j int) { allValidators[i], allValidators[j] = allValidators[j], allValidators[i] })
|
|
|
|
require.NoError(b, st.SetValidators(allValidators))
|
|
require.NoError(b, db.SaveState(b.Context(), st, bytesutil.ToBytes32(key)))
|
|
}
|
|
|
|
// create a state to save in benchmark
|
|
r := [32]byte{'A'}
|
|
st, err := util.NewBeaconState()
|
|
require.NoError(b, err)
|
|
require.NoError(b, st.SetValidators(initialSetOfValidators))
|
|
|
|
b.ReportAllocs()
|
|
|
|
for b.Loop() {
|
|
require.NoError(b, db.SaveState(b.Context(), st, r))
|
|
}
|
|
}
|
|
|
|
func checkStateReadTime(b *testing.B, saveCount int) {
|
|
|
|
db := setupDB(b)
|
|
initialSetOfValidators := validators(100000)
|
|
|
|
// Save a state to read in benchmark
|
|
r := [32]byte{'A'}
|
|
st, err := util.NewBeaconState()
|
|
require.NoError(b, err)
|
|
require.NoError(b, st.SetValidators(initialSetOfValidators))
|
|
require.NoError(b, db.SaveState(b.Context(), st, r))
|
|
|
|
// construct some states and save to randomize benchmark.
|
|
for range saveCount {
|
|
key := make([]byte, 32)
|
|
_, err := rand.Read(key)
|
|
require.NoError(b, err)
|
|
st, err = util.NewBeaconState()
|
|
require.NoError(b, err)
|
|
|
|
// Add some more new validator to the base validator.
|
|
validatosToAddInTest := validators(10000)
|
|
allValidators := append(initialSetOfValidators, validatosToAddInTest...)
|
|
|
|
// shuffle validators.
|
|
mathRand.New(mathRand.NewSource(time.Now().UnixNano()))
|
|
mathRand.Shuffle(len(allValidators), func(i, j int) { allValidators[i], allValidators[j] = allValidators[j], allValidators[i] })
|
|
|
|
require.NoError(b, st.SetValidators(allValidators))
|
|
require.NoError(b, db.SaveState(b.Context(), st, bytesutil.ToBytes32(key)))
|
|
}
|
|
|
|
b.ReportAllocs()
|
|
|
|
for b.Loop() {
|
|
_, err := db.State(b.Context(), r)
|
|
require.NoError(b, err)
|
|
}
|
|
}
|
|
|
|
func TestStateBellatrix_CanSaveRetrieveValidatorEntries(t *testing.T) {
|
|
db := setupDB(t)
|
|
|
|
// enable historical state representation flag to test this
|
|
resetCfg := features.InitWithReset(&features.Flags{
|
|
EnableHistoricalSpaceRepresentation: true,
|
|
})
|
|
defer resetCfg()
|
|
|
|
r := [32]byte{'A'}
|
|
|
|
require.Equal(t, false, db.HasState(t.Context(), r))
|
|
|
|
stateValidators := validators(10)
|
|
st, _ := util.DeterministicGenesisStateBellatrix(t, 20)
|
|
require.NoError(t, st.SetSlot(100))
|
|
require.NoError(t, st.SetValidators(stateValidators))
|
|
|
|
ctx := t.Context()
|
|
require.NoError(t, db.SaveState(ctx, st, r))
|
|
assert.Equal(t, true, db.HasState(t.Context(), r))
|
|
|
|
savedS, err := db.State(t.Context(), r)
|
|
require.NoError(t, err)
|
|
|
|
require.DeepSSZEqual(t, st.ToProtoUnsafe(), savedS.ToProtoUnsafe(), "saved state with validators and retrieved state are not matching")
|
|
|
|
// check if the index of the second state is still present.
|
|
err = db.db.Update(func(tx *bolt.Tx) error {
|
|
idxBkt := tx.Bucket(blockRootValidatorHashesBucket)
|
|
data := idxBkt.Get(r[:])
|
|
require.NotEqual(t, 0, len(data))
|
|
return nil
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
// check if all the validator entries are still intact in the validator entry bucket.
|
|
err = db.db.Update(func(tx *bolt.Tx) error {
|
|
valBkt := tx.Bucket(stateValidatorsBucket)
|
|
// if any of the original validator entry is not present, then fail the test.
|
|
for _, val := range stateValidators {
|
|
hash, hashErr := val.HashTreeRoot()
|
|
assert.NoError(t, hashErr)
|
|
data := valBkt.Get(hash[:])
|
|
require.NotNil(t, data)
|
|
require.NotEqual(t, 0, len(data))
|
|
}
|
|
return nil
|
|
})
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
func TestBellatrixState_CanSaveRetrieve(t *testing.T) {
|
|
db := setupDB(t)
|
|
|
|
r := [32]byte{'A'}
|
|
|
|
require.Equal(t, false, db.HasState(t.Context(), r))
|
|
|
|
st, _ := util.DeterministicGenesisStateBellatrix(t, 1)
|
|
require.NoError(t, st.SetSlot(100))
|
|
|
|
require.NoError(t, db.SaveState(t.Context(), st, r))
|
|
require.Equal(t, true, db.HasState(t.Context(), r))
|
|
|
|
savedS, err := db.State(t.Context(), r)
|
|
require.NoError(t, err)
|
|
|
|
require.DeepSSZEqual(t, st.ToProtoUnsafe(), savedS.ToProtoUnsafe())
|
|
|
|
savedS, err = db.State(t.Context(), [32]byte{'B'})
|
|
require.NoError(t, err)
|
|
require.Equal(t, state.ReadOnlyBeaconState(nil), savedS, "Unsaved state should've been nil")
|
|
}
|
|
|
|
func TestBellatrixState_CanDelete(t *testing.T) {
|
|
db := setupDB(t)
|
|
|
|
r := [32]byte{'A'}
|
|
|
|
require.Equal(t, false, db.HasState(t.Context(), r))
|
|
|
|
st, _ := util.DeterministicGenesisStateBellatrix(t, 1)
|
|
require.NoError(t, st.SetSlot(100))
|
|
|
|
require.NoError(t, db.SaveState(t.Context(), st, r))
|
|
require.Equal(t, true, db.HasState(t.Context(), r))
|
|
|
|
require.NoError(t, db.DeleteState(t.Context(), r))
|
|
savedS, err := db.State(t.Context(), r)
|
|
require.NoError(t, err)
|
|
require.Equal(t, state.ReadOnlyBeaconState(nil), savedS, "Unsaved state should've been nil")
|
|
}
|
|
|
|
func TestBellatrixState_CanDeleteWithBlock(t *testing.T) {
|
|
db := setupDB(t)
|
|
|
|
b := util.NewBeaconBlockBellatrix()
|
|
b.Block.Slot = 100
|
|
r, err := b.Block.HashTreeRoot()
|
|
require.NoError(t, err)
|
|
wsb, err := blocks.NewSignedBeaconBlock(b)
|
|
require.NoError(t, err)
|
|
require.NoError(t, db.SaveBlock(t.Context(), wsb))
|
|
|
|
require.Equal(t, false, db.HasState(t.Context(), r))
|
|
|
|
st, _ := util.DeterministicGenesisStateBellatrix(t, 1)
|
|
require.NoError(t, st.SetSlot(100))
|
|
|
|
require.NoError(t, db.SaveState(t.Context(), st, r))
|
|
require.Equal(t, true, db.HasState(t.Context(), r))
|
|
|
|
require.NoError(t, db.DeleteState(t.Context(), r))
|
|
savedS, err := db.State(t.Context(), r)
|
|
require.NoError(t, err)
|
|
require.Equal(t, state.ReadOnlyBeaconState(nil), savedS, "Unsaved state should've been nil")
|
|
}
|
|
|
|
func TestDenebState_CanSaveRetrieve(t *testing.T) {
|
|
db := setupDB(t)
|
|
|
|
r := [32]byte{'A'}
|
|
|
|
require.Equal(t, false, db.HasState(t.Context(), r))
|
|
|
|
st, _ := util.DeterministicGenesisStateDeneb(t, 1)
|
|
require.NoError(t, st.SetSlot(100))
|
|
|
|
require.NoError(t, db.SaveState(t.Context(), st, r))
|
|
require.Equal(t, true, db.HasState(t.Context(), r))
|
|
|
|
savedS, err := db.State(t.Context(), r)
|
|
require.NoError(t, err)
|
|
|
|
require.DeepSSZEqual(t, st.ToProtoUnsafe(), savedS.ToProtoUnsafe())
|
|
|
|
savedS, err = db.State(t.Context(), [32]byte{'B'})
|
|
require.NoError(t, err)
|
|
require.Equal(t, state.ReadOnlyBeaconState(nil), savedS, "Unsaved state should've been nil")
|
|
}
|
|
|
|
func TestDenebState_CanDelete(t *testing.T) {
|
|
db := setupDB(t)
|
|
|
|
r := [32]byte{'A'}
|
|
|
|
require.Equal(t, false, db.HasState(t.Context(), r))
|
|
|
|
st, _ := util.DeterministicGenesisStateDeneb(t, 1)
|
|
require.NoError(t, st.SetSlot(100))
|
|
|
|
require.NoError(t, db.SaveState(t.Context(), st, r))
|
|
require.Equal(t, true, db.HasState(t.Context(), r))
|
|
|
|
require.NoError(t, db.DeleteState(t.Context(), r))
|
|
savedS, err := db.State(t.Context(), r)
|
|
require.NoError(t, err)
|
|
require.Equal(t, state.ReadOnlyBeaconState(nil), savedS, "Unsaved state should've been nil")
|
|
}
|
|
|
|
func TestStateDeneb_CanSaveRetrieveValidatorEntries(t *testing.T) {
|
|
db := setupDB(t)
|
|
|
|
// enable historical state representation flag to test this
|
|
resetCfg := features.InitWithReset(&features.Flags{
|
|
EnableHistoricalSpaceRepresentation: true,
|
|
})
|
|
defer resetCfg()
|
|
|
|
r := [32]byte{'A'}
|
|
|
|
require.Equal(t, false, db.HasState(t.Context(), r))
|
|
|
|
stateValidators := validators(10)
|
|
st, _ := util.DeterministicGenesisStateDeneb(t, 20)
|
|
require.NoError(t, st.SetSlot(100))
|
|
require.NoError(t, st.SetValidators(stateValidators))
|
|
|
|
ctx := t.Context()
|
|
require.NoError(t, db.SaveState(ctx, st, r))
|
|
assert.Equal(t, true, db.HasState(t.Context(), r))
|
|
|
|
savedS, err := db.State(t.Context(), r)
|
|
require.NoError(t, err)
|
|
|
|
require.DeepSSZEqual(t, st.Validators(), savedS.Validators(), "saved state with validators and retrieved state are not matching")
|
|
|
|
// check if the index of the second state is still present.
|
|
err = db.db.Update(func(tx *bolt.Tx) error {
|
|
idxBkt := tx.Bucket(blockRootValidatorHashesBucket)
|
|
data := idxBkt.Get(r[:])
|
|
require.NotEqual(t, 0, len(data))
|
|
return nil
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
// check if all the validator entries are still intact in the validator entry bucket.
|
|
err = db.db.Update(func(tx *bolt.Tx) error {
|
|
valBkt := tx.Bucket(stateValidatorsBucket)
|
|
// if any of the original validator entry is not present, then fail the test.
|
|
for _, val := range stateValidators {
|
|
hash, hashErr := val.HashTreeRoot()
|
|
assert.NoError(t, hashErr)
|
|
data := valBkt.Get(hash[:])
|
|
require.NotNil(t, data)
|
|
require.NotEqual(t, 0, len(data))
|
|
}
|
|
return nil
|
|
})
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
func TestElectraState_CanSaveRetrieve(t *testing.T) {
|
|
db := setupDB(t)
|
|
|
|
r := [32]byte{'A'}
|
|
|
|
require.Equal(t, false, db.HasState(t.Context(), r))
|
|
|
|
st, _ := util.DeterministicGenesisStateElectra(t, 1)
|
|
require.NoError(t, st.SetSlot(100))
|
|
|
|
require.NoError(t, db.SaveState(t.Context(), st, r))
|
|
require.Equal(t, true, db.HasState(t.Context(), r))
|
|
|
|
savedS, err := db.State(t.Context(), r)
|
|
require.NoError(t, err)
|
|
|
|
assert.DeepSSZEqual(t, st.ToProtoUnsafe(), savedS.ToProtoUnsafe())
|
|
|
|
savedS, err = db.State(t.Context(), [32]byte{'B'})
|
|
require.NoError(t, err)
|
|
require.Equal(t, state.ReadOnlyBeaconState(nil), savedS, "Unsaved state should've been nil")
|
|
}
|
|
|
|
func TestElectraState_CanDelete(t *testing.T) {
|
|
db := setupDB(t)
|
|
|
|
r := [32]byte{'A'}
|
|
|
|
require.Equal(t, false, db.HasState(t.Context(), r))
|
|
|
|
st, _ := util.DeterministicGenesisStateElectra(t, 1)
|
|
require.NoError(t, st.SetSlot(100))
|
|
|
|
require.NoError(t, db.SaveState(t.Context(), st, r))
|
|
require.Equal(t, true, db.HasState(t.Context(), r))
|
|
|
|
require.NoError(t, db.DeleteState(t.Context(), r))
|
|
savedS, err := db.State(t.Context(), r)
|
|
require.NoError(t, err)
|
|
require.Equal(t, state.ReadOnlyBeaconState(nil), savedS, "Unsaved state should've been nil")
|
|
}
|
|
|
|
func TestStateElectra_CanSaveRetrieveValidatorEntries(t *testing.T) {
|
|
db := setupDB(t)
|
|
|
|
// enable historical state representation flag to test this
|
|
resetCfg := features.InitWithReset(&features.Flags{
|
|
EnableHistoricalSpaceRepresentation: true,
|
|
})
|
|
defer resetCfg()
|
|
|
|
r := [32]byte{'A'}
|
|
|
|
require.Equal(t, false, db.HasState(t.Context(), r))
|
|
|
|
stateValidators := validators(10)
|
|
st, _ := util.DeterministicGenesisStateElectra(t, 20)
|
|
require.NoError(t, st.SetSlot(100))
|
|
require.NoError(t, st.SetValidators(stateValidators))
|
|
|
|
ctx := t.Context()
|
|
require.NoError(t, db.SaveState(ctx, st, r))
|
|
assert.Equal(t, true, db.HasState(t.Context(), r))
|
|
|
|
savedS, err := db.State(t.Context(), r)
|
|
require.NoError(t, err)
|
|
|
|
require.DeepSSZEqual(t, st.Validators(), savedS.Validators(), "saved state with validators and retrieved state are not matching")
|
|
|
|
// check if the index of the second state is still present.
|
|
err = db.db.Update(func(tx *bolt.Tx) error {
|
|
idxBkt := tx.Bucket(blockRootValidatorHashesBucket)
|
|
data := idxBkt.Get(r[:])
|
|
require.NotEqual(t, 0, len(data))
|
|
return nil
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
// check if all the validator entries are still intact in the validator entry bucket.
|
|
err = db.db.Update(func(tx *bolt.Tx) error {
|
|
valBkt := tx.Bucket(stateValidatorsBucket)
|
|
// if any of the original validator entry is not present, then fail the test.
|
|
for _, val := range stateValidators {
|
|
hash, hashErr := val.HashTreeRoot()
|
|
assert.NoError(t, hashErr)
|
|
data := valBkt.Get(hash[:])
|
|
require.NotNil(t, data)
|
|
require.NotEqual(t, 0, len(data))
|
|
}
|
|
return nil
|
|
})
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
func BenchmarkState_CheckStateSaveTime_1(b *testing.B) { checkStateSaveTime(b, 1) }
|
|
func BenchmarkState_CheckStateSaveTime_10(b *testing.B) { checkStateSaveTime(b, 10) }
|
|
|
|
func BenchmarkState_CheckStateReadTime_1(b *testing.B) { checkStateReadTime(b, 1) }
|
|
func BenchmarkState_CheckStateReadTime_10(b *testing.B) { checkStateReadTime(b, 10) }
|
|
|
|
func TestStore_CleanUpDirtyStates_NoOriginRoot(t *testing.T) {
|
|
// This test verifies that CleanUpDirtyStates does not fail when the origin block root is not set,
|
|
// which can happen when starting from genesis or in certain fork scenarios like Fulu.
|
|
db := setupDB(t)
|
|
genesisState, err := util.NewBeaconState()
|
|
require.NoError(t, err)
|
|
genesisRoot := [fieldparams.RootLength]byte{'a'}
|
|
require.NoError(t, db.SaveGenesisBlockRoot(t.Context(), genesisRoot))
|
|
require.NoError(t, db.SaveState(t.Context(), genesisState, genesisRoot))
|
|
// Note: We intentionally do NOT call SaveOriginCheckpointBlockRoot here
|
|
// to simulate the scenario where origin block root is not set
|
|
slotsPerArchivedPoint := primitives.Slot(128)
|
|
bRoots := make([][fieldparams.RootLength]byte, 0)
|
|
prevRoot := genesisRoot
|
|
for i := primitives.Slot(1); i <= slotsPerArchivedPoint; i++ { // skip slot 0
|
|
b := util.NewBeaconBlock()
|
|
b.Block.Slot = i
|
|
b.Block.ParentRoot = prevRoot[:]
|
|
r, err := b.Block.HashTreeRoot()
|
|
require.NoError(t, err)
|
|
wsb, err := blocks.NewSignedBeaconBlock(b)
|
|
require.NoError(t, err)
|
|
require.NoError(t, db.SaveBlock(t.Context(), wsb))
|
|
bRoots = append(bRoots, r)
|
|
prevRoot = r
|
|
st, err := util.NewBeaconState()
|
|
require.NoError(t, err)
|
|
require.NoError(t, st.SetSlot(i))
|
|
require.NoError(t, db.SaveState(t.Context(), st, r))
|
|
}
|
|
require.NoError(t, db.SaveFinalizedCheckpoint(t.Context(), ðpb.Checkpoint{
|
|
Root: bRoots[len(bRoots)-1][:],
|
|
Epoch: primitives.Epoch(slotsPerArchivedPoint / params.BeaconConfig().SlotsPerEpoch),
|
|
}))
|
|
// This should not fail even though origin block root is not set
|
|
err = db.CleanUpDirtyStates(t.Context(), slotsPerArchivedPoint)
|
|
require.NoError(t, err)
|
|
// Verify that cleanup still works correctly
|
|
for i, root := range bRoots {
|
|
if primitives.Slot(i) >= slotsPerArchivedPoint.SubSlot(slotsPerArchivedPoint.Div(3)) {
|
|
require.Equal(t, true, db.HasState(t.Context(), root))
|
|
} else {
|
|
require.Equal(t, false, db.HasState(t.Context(), root))
|
|
}
|
|
}
|
|
}
|