mirror of
https://github.com/OffchainLabs/prysm.git
synced 2026-02-11 13:35:06 -05:00
Compare commits
11 Commits
gloas/fork
...
hdiff-rest
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
373c9ef7c6 | ||
|
|
f9cf80feaf | ||
|
|
2ec87e23fb | ||
|
|
03156649e8 | ||
|
|
982c3f8fb7 | ||
|
|
fc51fb28d6 | ||
|
|
cee0b57601 | ||
|
|
d518377588 | ||
|
|
bb7b4ba0ad | ||
|
|
25b3ee468f | ||
|
|
7df09b12a5 |
@@ -26,6 +26,15 @@ var ErrNotFoundMetadataSeqNum = errors.Wrap(ErrNotFound, "metadata sequence numb
|
||||
// but the database was created without state-diff support.
|
||||
var ErrStateDiffIncompatible = errors.New("state-diff feature enabled but database was created without state-diff support")
|
||||
|
||||
// ErrStateDiffCorrupted is returned when state-diff metadata or data is missing or invalid.
|
||||
var ErrStateDiffCorrupted = errors.New("state-diff database corrupted")
|
||||
|
||||
// ErrStateDiffExponentMismatch is returned when configured exponents differ from stored metadata.
|
||||
var ErrStateDiffExponentMismatch = errors.New("state-diff exponents mismatch")
|
||||
|
||||
// ErrStateDiffMissingSnapshot is returned when the offset snapshot is missing.
|
||||
var ErrStateDiffMissingSnapshot = errors.New("state-diff offset snapshot missing")
|
||||
|
||||
var errEmptyBlockSlice = errors.New("[]blocks.ROBlock is empty")
|
||||
var errIncorrectBlockParent = errors.New("unexpected missing or forked blocks in a []ROBlock")
|
||||
var errFinalizedChildNotFound = errors.New("unable to find finalized root descending from backfill batch")
|
||||
|
||||
@@ -7,9 +7,11 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"slices"
|
||||
"time"
|
||||
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/db/iface"
|
||||
"github.com/OffchainLabs/prysm/v7/cmd/beacon-chain/flags"
|
||||
"github.com/OffchainLabs/prysm/v7/config/features"
|
||||
"github.com/OffchainLabs/prysm/v7/config/params"
|
||||
"github.com/OffchainLabs/prysm/v7/consensus-types/blocks"
|
||||
@@ -21,6 +23,7 @@ import (
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/prometheus/client_golang/prometheus/promauto"
|
||||
prombolt "github.com/prysmaticlabs/prombbolt"
|
||||
logrus "github.com/sirupsen/logrus"
|
||||
bolt "go.etcd.io/bbolt"
|
||||
)
|
||||
|
||||
@@ -223,8 +226,42 @@ func (kv *Store) startStateDiff(ctx context.Context) error {
|
||||
}
|
||||
|
||||
if hasOffset {
|
||||
// Existing state-diff database - restarts not yet supported.
|
||||
return errors.New("restarting with existing state-diff database not yet supported")
|
||||
storedExponents, err := kv.loadStateDiffExponents()
|
||||
if err != nil {
|
||||
return fmt.Errorf("%w: state-diff metadata missing or invalid; re-sync required: %v", ErrStateDiffCorrupted, err)
|
||||
}
|
||||
currentExponents := flags.Get().StateDiffExponents
|
||||
if !slices.Equal(storedExponents, currentExponents) {
|
||||
return errors.Wrapf(
|
||||
ErrStateDiffExponentMismatch,
|
||||
"state-diff exponents changed; database incompatible. "+
|
||||
"Database was initialized with: %v. "+
|
||||
"Current configuration: %v. "+
|
||||
"Options: use original exponents (--state-diff-exponents=%s) or delete database and re-sync from genesis/checkpoint.",
|
||||
storedExponents,
|
||||
currentExponents,
|
||||
formatStateDiffExponents(storedExponents),
|
||||
)
|
||||
}
|
||||
offset, err := kv.loadOffset()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cache, err := populateStateDiffCacheFromDB(kv, offset)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
kv.stateDiffCache = cache
|
||||
if flags.Get().StateDiffValidateOnStartup {
|
||||
if err := validateStateDiffCache(ctx, kv, cache); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
log.WithFields(logrus.Fields{
|
||||
"offset": offset,
|
||||
"exponents": storedExponents,
|
||||
}).Info("State-diff cache initialized from existing database")
|
||||
return nil
|
||||
}
|
||||
|
||||
// Check if this is a new database (no head block).
|
||||
|
||||
@@ -3,12 +3,15 @@ package kv
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/OffchainLabs/prysm/v7/cmd/beacon-chain/flags"
|
||||
"github.com/OffchainLabs/prysm/v7/config/features"
|
||||
"github.com/OffchainLabs/prysm/v7/consensus-types/blocks"
|
||||
ethpb "github.com/OffchainLabs/prysm/v7/proto/prysm/v1alpha1"
|
||||
"github.com/OffchainLabs/prysm/v7/runtime/version"
|
||||
"github.com/OffchainLabs/prysm/v7/testing/require"
|
||||
"github.com/OffchainLabs/prysm/v7/testing/util"
|
||||
bolt "go.etcd.io/bbolt"
|
||||
@@ -27,6 +30,108 @@ func setupDB(t testing.TB) *Store {
|
||||
return db
|
||||
}
|
||||
|
||||
func TestStartStateDiff_ExponentMismatch(t *testing.T) {
|
||||
resetCfg := features.InitWithReset(&features.Flags{EnableStateDiff: true})
|
||||
defer resetCfg()
|
||||
setDefaultStateDiffExponents()
|
||||
|
||||
store := setupDB(t)
|
||||
require.NoError(t, store.db.Update(func(tx *bolt.Tx) error {
|
||||
bucket := tx.Bucket(stateDiffBucket)
|
||||
if bucket == nil {
|
||||
return bolt.ErrBucketNotFound
|
||||
}
|
||||
offsetBytes := make([]byte, 8)
|
||||
binary.LittleEndian.PutUint64(offsetBytes, 0)
|
||||
if err := bucket.Put(offsetKey, offsetBytes); err != nil {
|
||||
return err
|
||||
}
|
||||
encoded, err := encodeStateDiffExponents([]int{20, 10})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return bucket.Put(exponentsKey, encoded)
|
||||
}))
|
||||
|
||||
ctx := t.Context()
|
||||
err := store.startStateDiff(ctx)
|
||||
require.ErrorContains(t, "state-diff exponents changed", err)
|
||||
}
|
||||
|
||||
func TestStartStateDiff_MissingOffsetSnapshot(t *testing.T) {
|
||||
resetCfg := features.InitWithReset(&features.Flags{EnableStateDiff: true})
|
||||
defer resetCfg()
|
||||
setDefaultStateDiffExponents()
|
||||
|
||||
store := setupDB(t)
|
||||
require.NoError(t, store.db.Update(func(tx *bolt.Tx) error {
|
||||
bucket := tx.Bucket(stateDiffBucket)
|
||||
if bucket == nil {
|
||||
return bolt.ErrBucketNotFound
|
||||
}
|
||||
offsetBytes := make([]byte, 8)
|
||||
binary.LittleEndian.PutUint64(offsetBytes, 0)
|
||||
if err := bucket.Put(offsetKey, offsetBytes); err != nil {
|
||||
return err
|
||||
}
|
||||
encoded, err := encodeStateDiffExponents(flags.Get().StateDiffExponents)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return bucket.Put(exponentsKey, encoded)
|
||||
}))
|
||||
|
||||
ctx := t.Context()
|
||||
err := store.startStateDiff(ctx)
|
||||
require.ErrorContains(t, "missing offset snapshot", err)
|
||||
}
|
||||
|
||||
func TestStartStateDiff_ValidateOnStartup(t *testing.T) {
|
||||
resetCfg := features.InitWithReset(&features.Flags{EnableStateDiff: true})
|
||||
defer resetCfg()
|
||||
setDefaultStateDiffExponents()
|
||||
|
||||
globalFlags := flags.GlobalFlags{
|
||||
StateDiffExponents: flags.Get().StateDiffExponents,
|
||||
StateDiffValidateOnStartup: true,
|
||||
}
|
||||
flags.Init(&globalFlags)
|
||||
|
||||
store := setupDB(t)
|
||||
require.NoError(t, store.db.Update(func(tx *bolt.Tx) error {
|
||||
bucket := tx.Bucket(stateDiffBucket)
|
||||
if bucket == nil {
|
||||
return bolt.ErrBucketNotFound
|
||||
}
|
||||
st, _ := createState(t, 0, version.Phase0)
|
||||
stateBytes, err := st.MarshalSSZ()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
enc, err := addKey(st.Version(), stateBytes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
offsetBytes := make([]byte, 8)
|
||||
binary.LittleEndian.PutUint64(offsetBytes, 0)
|
||||
if err := bucket.Put(offsetKey, offsetBytes); err != nil {
|
||||
return err
|
||||
}
|
||||
encoded, err := encodeStateDiffExponents(flags.Get().StateDiffExponents)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := bucket.Put(exponentsKey, encoded); err != nil {
|
||||
return err
|
||||
}
|
||||
key := makeKeyForStateDiffTree(0, 0)
|
||||
return bucket.Put(key, enc)
|
||||
}))
|
||||
|
||||
err := store.startStateDiff(t.Context())
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func Test_setupBlockStorageType(t *testing.T) {
|
||||
ctx := t.Context()
|
||||
t.Run("fresh database with feature enabled to store full blocks should store full blocks", func(t *testing.T) {
|
||||
|
||||
@@ -1048,10 +1048,15 @@ func (s *Store) isStateValidatorMigrationOver() (bool, error) {
|
||||
}
|
||||
|
||||
func (s *Store) getStateUsingStateDiff(ctx context.Context, blockRoot [32]byte) (state.BeaconState, error) {
|
||||
slot, err := s.SlotByBlockRoot(ctx, blockRoot)
|
||||
stateSummary, err := s.StateSummary(ctx, blockRoot)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if stateSummary == nil {
|
||||
return nil, ErrNotFoundState
|
||||
}
|
||||
|
||||
slot := stateSummary.Slot
|
||||
|
||||
if uint64(slot) < s.getOffset() {
|
||||
return nil, ErrSlotBeforeOffset
|
||||
@@ -1065,14 +1070,33 @@ func (s *Store) getStateUsingStateDiff(ctx context.Context, blockRoot [32]byte)
|
||||
return nil, errors.New("state not found")
|
||||
}
|
||||
|
||||
blk, err := s.Block(ctx, blockRoot)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if blk != nil && !blk.IsNil() {
|
||||
stateRoot, err := st.HashTreeRoot(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if stateRoot != blk.Block().StateRoot() {
|
||||
return nil, errors.Wrap(ErrNotFoundState, "state root mismatch for block")
|
||||
}
|
||||
}
|
||||
|
||||
return st, nil
|
||||
}
|
||||
|
||||
func (s *Store) hasStateUsingStateDiff(ctx context.Context, blockRoot [32]byte) (bool, error) {
|
||||
slot, err := s.SlotByBlockRoot(ctx, blockRoot)
|
||||
stateSummary, err := s.StateSummary(ctx, blockRoot)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if stateSummary == nil {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
slot := stateSummary.Slot
|
||||
|
||||
if uint64(slot) < s.getOffset() {
|
||||
return false, ErrSlotBeforeOffset
|
||||
|
||||
@@ -133,6 +133,9 @@ func (s *Store) saveHdiff(lvl int, anchor, st state.ReadOnlyBeaconState) error {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err := s.stateDiffCache.setLevelHasData(lvl); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -172,6 +175,9 @@ func (s *Store) saveFullSnapshot(st state.ReadOnlyBeaconState) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.stateDiffCache.setLevelHasData(0); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,19 +1,131 @@
|
||||
package kv
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"sync"
|
||||
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/state"
|
||||
"github.com/OffchainLabs/prysm/v7/cmd/beacon-chain/flags"
|
||||
"github.com/OffchainLabs/prysm/v7/consensus-types/primitives"
|
||||
pkgerrors "github.com/pkg/errors"
|
||||
"go.etcd.io/bbolt"
|
||||
)
|
||||
|
||||
type stateDiffCache struct {
|
||||
sync.RWMutex
|
||||
anchors []state.ReadOnlyBeaconState
|
||||
offset uint64
|
||||
anchors []state.ReadOnlyBeaconState
|
||||
levelsWithData []bool
|
||||
offset uint64
|
||||
}
|
||||
|
||||
func populateStateDiffCacheFromDB(s *Store, offset uint64) (*stateDiffCache, error) {
|
||||
cache := &stateDiffCache{
|
||||
anchors: make([]state.ReadOnlyBeaconState, len(flags.Get().StateDiffExponents)-1),
|
||||
levelsWithData: make([]bool, len(flags.Get().StateDiffExponents)),
|
||||
offset: offset,
|
||||
}
|
||||
|
||||
if err := s.db.View(func(tx *bbolt.Tx) error {
|
||||
bucket := tx.Bucket(stateDiffBucket)
|
||||
if bucket == nil {
|
||||
return bbolt.ErrBucketNotFound
|
||||
}
|
||||
for level := range cache.levelsWithData {
|
||||
if level == 0 {
|
||||
if bucket.Get(makeKeyForStateDiffTree(0, offset)) != nil {
|
||||
cache.levelsWithData[level] = true
|
||||
}
|
||||
continue
|
||||
}
|
||||
cursor := bucket.Cursor()
|
||||
prefix := []byte{byte(level)}
|
||||
key, _ := cursor.Seek(prefix)
|
||||
if key != nil && key[0] == byte(level) {
|
||||
slot, ok := slotFromStateDiffKey(key)
|
||||
if !ok {
|
||||
return ErrStateDiffCorrupted
|
||||
}
|
||||
if slot < offset {
|
||||
return ErrStateDiffCorrupted
|
||||
}
|
||||
if level == 0 && slot != offset {
|
||||
return ErrStateDiffCorrupted
|
||||
}
|
||||
if computeLevel(offset, primitives.Slot(slot)) != level {
|
||||
return ErrStateDiffCorrupted
|
||||
}
|
||||
cache.levelsWithData[level] = true
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
anchor0, err := s.getFullSnapshot(offset)
|
||||
if err != nil {
|
||||
return nil, pkgerrors.Wrapf(ErrStateDiffMissingSnapshot, "state diff cache: missing offset snapshot at %d", offset)
|
||||
}
|
||||
cache.anchors[0] = anchor0
|
||||
cache.levelsWithData[0] = true
|
||||
|
||||
return cache, nil
|
||||
}
|
||||
|
||||
func validateStateDiffCache(ctx context.Context, s *Store, cache *stateDiffCache) error {
|
||||
for level, hasData := range cache.levelsWithData {
|
||||
if !hasData || level == 0 {
|
||||
continue
|
||||
}
|
||||
maxSlot, err := latestSlotForLevel(s, level)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := s.stateByDiff(ctx, primitives.Slot(maxSlot)); err != nil {
|
||||
return pkgerrors.Wrapf(ErrStateDiffCorrupted, "state diff validation failed for level %d slot %d: %v", level, maxSlot, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func latestSlotForLevel(s *Store, level int) (uint64, error) {
|
||||
var maxSlot uint64
|
||||
found := false
|
||||
err := s.db.View(func(tx *bbolt.Tx) error {
|
||||
bucket := tx.Bucket(stateDiffBucket)
|
||||
if bucket == nil {
|
||||
return bbolt.ErrBucketNotFound
|
||||
}
|
||||
cursor := bucket.Cursor()
|
||||
prefix := []byte{byte(level)}
|
||||
for key, _ := cursor.Seek(prefix); key != nil && key[0] == byte(level); key, _ = cursor.Next() {
|
||||
slot, ok := slotFromStateDiffKey(key)
|
||||
if !ok {
|
||||
return ErrStateDiffCorrupted
|
||||
}
|
||||
if !found || slot > maxSlot {
|
||||
maxSlot = slot
|
||||
found = true
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if !found {
|
||||
return 0, ErrStateDiffCorrupted
|
||||
}
|
||||
return maxSlot, nil
|
||||
}
|
||||
|
||||
func slotFromStateDiffKey(key []byte) (uint64, bool) {
|
||||
if len(key) < 9 {
|
||||
return 0, false
|
||||
}
|
||||
return binary.LittleEndian.Uint64(key[1:9]), true
|
||||
}
|
||||
|
||||
func newStateDiffCache(s *Store) (*stateDiffCache, error) {
|
||||
@@ -37,8 +149,9 @@ func newStateDiffCache(s *Store) (*stateDiffCache, error) {
|
||||
}
|
||||
|
||||
return &stateDiffCache{
|
||||
anchors: make([]state.ReadOnlyBeaconState, len(flags.Get().StateDiffExponents)-1), // -1 because last level doesn't need to be cached
|
||||
offset: offset,
|
||||
anchors: make([]state.ReadOnlyBeaconState, len(flags.Get().StateDiffExponents)-1), // -1 because last level doesn't need to be cached
|
||||
levelsWithData: make([]bool, len(flags.Get().StateDiffExponents)),
|
||||
offset: offset,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -58,6 +171,25 @@ func (c *stateDiffCache) setAnchor(level int, anchor state.ReadOnlyBeaconState)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *stateDiffCache) levelHasData(level int) bool {
|
||||
c.RLock()
|
||||
defer c.RUnlock()
|
||||
if level < 0 || level >= len(c.levelsWithData) {
|
||||
return false
|
||||
}
|
||||
return c.levelsWithData[level]
|
||||
}
|
||||
|
||||
func (c *stateDiffCache) setLevelHasData(level int) error {
|
||||
c.Lock()
|
||||
defer c.Unlock()
|
||||
if level < 0 || level >= len(c.levelsWithData) {
|
||||
return errors.New("state diff cache: level data index out of range")
|
||||
}
|
||||
c.levelsWithData[level] = true
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *stateDiffCache) getOffset() uint64 {
|
||||
c.RLock()
|
||||
defer c.RUnlock()
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/state"
|
||||
statenative "github.com/OffchainLabs/prysm/v7/beacon-chain/state/state-native"
|
||||
@@ -21,9 +22,78 @@ import (
|
||||
|
||||
var (
|
||||
offsetKey = []byte("offset")
|
||||
exponentsKey = []byte("exponents")
|
||||
ErrSlotBeforeOffset = errors.New("slot is before state-diff root offset")
|
||||
)
|
||||
|
||||
func encodeStateDiffExponents(exponents []int) ([]byte, error) {
|
||||
if len(exponents) == 0 {
|
||||
return nil, errors.New("state diff exponents cannot be empty")
|
||||
}
|
||||
if len(exponents) > 255 {
|
||||
return nil, fmt.Errorf("state diff exponents length %d exceeds max 255", len(exponents))
|
||||
}
|
||||
encoded := make([]byte, len(exponents)+1)
|
||||
encoded[0] = byte(len(exponents))
|
||||
for i, exp := range exponents {
|
||||
if exp < 2 || exp > flags.MaxStateDiffExponent {
|
||||
return nil, fmt.Errorf("state diff exponent %d out of range for encoding", exp)
|
||||
}
|
||||
encoded[i+1] = byte(exp)
|
||||
}
|
||||
return encoded, nil
|
||||
}
|
||||
|
||||
func decodeStateDiffExponents(encoded []byte) ([]int, error) {
|
||||
if len(encoded) == 0 {
|
||||
return nil, errors.New("state diff exponents missing length prefix")
|
||||
}
|
||||
count := int(encoded[0])
|
||||
if count == 0 {
|
||||
return nil, errors.New("state diff exponents length cannot be zero")
|
||||
}
|
||||
if len(encoded) != count+1 {
|
||||
return nil, fmt.Errorf("state diff exponents length mismatch: expected %d got %d", count, len(encoded)-1)
|
||||
}
|
||||
exponents := make([]int, count)
|
||||
for i := range count {
|
||||
exponents[i] = int(encoded[i+1])
|
||||
}
|
||||
return exponents, nil
|
||||
}
|
||||
|
||||
func formatStateDiffExponents(exponents []int) string {
|
||||
if len(exponents) == 0 {
|
||||
return ""
|
||||
}
|
||||
parts := make([]string, len(exponents))
|
||||
for i, exp := range exponents {
|
||||
parts[i] = fmt.Sprintf("%d", exp)
|
||||
}
|
||||
return strings.Join(parts, ",")
|
||||
}
|
||||
|
||||
func (s *Store) loadStateDiffExponents() ([]int, error) {
|
||||
var encoded []byte
|
||||
err := s.db.View(func(tx *bbolt.Tx) error {
|
||||
bucket := tx.Bucket(stateDiffBucket)
|
||||
if bucket == nil {
|
||||
return bbolt.ErrBucketNotFound
|
||||
}
|
||||
value := bucket.Get(exponentsKey)
|
||||
if value == nil {
|
||||
return errors.New("state diff exponents not found")
|
||||
}
|
||||
encoded = make([]byte, len(value))
|
||||
copy(encoded, value)
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return decodeStateDiffExponents(encoded)
|
||||
}
|
||||
|
||||
func makeKeyForStateDiffTree(level int, slot uint64) []byte {
|
||||
buf := make([]byte, 16)
|
||||
buf[0] = byte(level)
|
||||
@@ -124,6 +194,29 @@ func (s *Store) getOffset() uint64 {
|
||||
return s.stateDiffCache.getOffset()
|
||||
}
|
||||
|
||||
func (s *Store) loadOffset() (uint64, error) {
|
||||
var offset uint64
|
||||
err := s.db.View(func(tx *bbolt.Tx) error {
|
||||
bucket := tx.Bucket(stateDiffBucket)
|
||||
if bucket == nil {
|
||||
return bbolt.ErrBucketNotFound
|
||||
}
|
||||
offsetBytes := bucket.Get(offsetKey)
|
||||
if offsetBytes == nil {
|
||||
return errors.New("state diff offset not found")
|
||||
}
|
||||
if len(offsetBytes) != 8 {
|
||||
return fmt.Errorf("state diff offset has invalid length %d", len(offsetBytes))
|
||||
}
|
||||
offset = binary.LittleEndian.Uint64(offsetBytes)
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return offset, nil
|
||||
}
|
||||
|
||||
// hasStateDiffOffset checks if the state-diff offset has been set in the database.
|
||||
// This is used to detect if an existing database has state-diff enabled.
|
||||
func (s *Store) hasStateDiffOffset() (bool, error) {
|
||||
@@ -153,8 +246,13 @@ func (s *Store) initializeStateDiff(slot primitives.Slot, initialState state.Rea
|
||||
return nil
|
||||
}
|
||||
}
|
||||
// Write offset directly to the database (without using cache which doesn't exist yet).
|
||||
err := s.db.Update(func(tx *bbolt.Tx) error {
|
||||
exponentsBytes, err := encodeStateDiffExponents(flags.Get().StateDiffExponents)
|
||||
if err != nil {
|
||||
return pkgerrors.Wrap(err, "failed to encode state diff exponents")
|
||||
}
|
||||
|
||||
// Write metadata directly to the database (without using cache which doesn't exist yet).
|
||||
err = s.db.Update(func(tx *bbolt.Tx) error {
|
||||
bucket := tx.Bucket(stateDiffBucket)
|
||||
if bucket == nil {
|
||||
return bbolt.ErrBucketNotFound
|
||||
@@ -162,7 +260,10 @@ func (s *Store) initializeStateDiff(slot primitives.Slot, initialState state.Rea
|
||||
|
||||
offsetBytes := make([]byte, 8)
|
||||
binary.LittleEndian.PutUint64(offsetBytes, uint64(slot))
|
||||
return bucket.Put(offsetKey, offsetBytes)
|
||||
if err := bucket.Put(offsetKey, offsetBytes); err != nil {
|
||||
return err
|
||||
}
|
||||
return bucket.Put(exponentsKey, exponentsBytes)
|
||||
})
|
||||
if err != nil {
|
||||
return pkgerrors.Wrap(err, "failed to set offset")
|
||||
@@ -286,15 +387,20 @@ func (s *Store) getBaseAndDiffChain(offset uint64, slot primitives.Slot) (state.
|
||||
}
|
||||
|
||||
var diffChainItems []diffItem
|
||||
lastSeenAnchorSlot := baseAnchorSlot
|
||||
lastSeenAnchorRelSlot := baseAnchorSlot - offset
|
||||
for i, exp := range exponents[1 : lvl+1] {
|
||||
span := math.PowerOf2(uint64(exp))
|
||||
diffSlot := rel / span * span
|
||||
if diffSlot == lastSeenAnchorSlot {
|
||||
if diffSlot == lastSeenAnchorRelSlot {
|
||||
continue
|
||||
}
|
||||
diffChainItems = append(diffChainItems, diffItem{level: i + 1, slot: diffSlot + offset})
|
||||
lastSeenAnchorSlot = diffSlot
|
||||
level := i + 1
|
||||
if s.stateDiffCache != nil && !s.stateDiffCache.levelHasData(level) {
|
||||
lastSeenAnchorRelSlot = diffSlot
|
||||
continue
|
||||
}
|
||||
diffChainItems = append(diffChainItems, diffItem{level: level, slot: diffSlot + offset})
|
||||
lastSeenAnchorRelSlot = diffSlot
|
||||
}
|
||||
|
||||
baseSnapshot, err := s.getFullSnapshot(baseAnchorSlot)
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/state"
|
||||
"github.com/OffchainLabs/prysm/v7/cmd/beacon-chain/flags"
|
||||
"github.com/OffchainLabs/prysm/v7/config/features"
|
||||
"github.com/OffchainLabs/prysm/v7/config/params"
|
||||
"github.com/OffchainLabs/prysm/v7/consensus-types/primitives"
|
||||
"github.com/OffchainLabs/prysm/v7/math"
|
||||
@@ -34,6 +35,81 @@ func TestStateDiff_LoadOrInitOffset(t *testing.T) {
|
||||
require.Equal(t, uint64(10), offset)
|
||||
}
|
||||
|
||||
func TestStateDiff_LoadOffset(t *testing.T) {
|
||||
setDefaultStateDiffExponents()
|
||||
|
||||
db := setupDB(t)
|
||||
_, err := db.loadOffset()
|
||||
require.ErrorContains(t, "offset not found", err)
|
||||
|
||||
err = setOffsetInDB(db, 10)
|
||||
require.NoError(t, err)
|
||||
offset, err := db.loadOffset()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, uint64(10), offset)
|
||||
}
|
||||
|
||||
func TestStateDiff_EncodeDecodeExponents(t *testing.T) {
|
||||
t.Run("roundtrip", func(t *testing.T) {
|
||||
exponents := []int{21, 18, 16, 13}
|
||||
encoded, err := encodeStateDiffExponents(exponents)
|
||||
require.NoError(t, err)
|
||||
decoded, err := decodeStateDiffExponents(encoded)
|
||||
require.NoError(t, err)
|
||||
require.DeepEqual(t, exponents, decoded)
|
||||
})
|
||||
|
||||
t.Run("encode-empty", func(t *testing.T) {
|
||||
_, err := encodeStateDiffExponents(nil)
|
||||
require.ErrorContains(t, "cannot be empty", err)
|
||||
})
|
||||
|
||||
t.Run("encode-negative", func(t *testing.T) {
|
||||
_, err := encodeStateDiffExponents([]int{21, -1})
|
||||
require.ErrorContains(t, "out of range", err)
|
||||
})
|
||||
|
||||
t.Run("encode-too-large", func(t *testing.T) {
|
||||
_, err := encodeStateDiffExponents([]int{flags.MaxStateDiffExponent + 1})
|
||||
require.ErrorContains(t, "out of range", err)
|
||||
})
|
||||
|
||||
t.Run("decode-empty", func(t *testing.T) {
|
||||
_, err := decodeStateDiffExponents(nil)
|
||||
require.ErrorContains(t, "missing length prefix", err)
|
||||
})
|
||||
|
||||
t.Run("decode-zero-length", func(t *testing.T) {
|
||||
_, err := decodeStateDiffExponents([]byte{0})
|
||||
require.ErrorContains(t, "length cannot be zero", err)
|
||||
})
|
||||
|
||||
t.Run("decode-length-mismatch", func(t *testing.T) {
|
||||
_, err := decodeStateDiffExponents([]byte{2, 10})
|
||||
require.ErrorContains(t, "length mismatch", err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestStateDiff_InitializeStoresExponents(t *testing.T) {
|
||||
setDefaultStateDiffExponents()
|
||||
resetCfg := features.InitWithReset(&features.Flags{EnableStateDiff: true})
|
||||
defer resetCfg()
|
||||
|
||||
db := setupDB(t)
|
||||
st, _ := createState(t, 0, version.Phase0)
|
||||
require.NoError(t, db.initializeStateDiff(0, st))
|
||||
|
||||
stored, err := db.loadStateDiffExponents()
|
||||
require.NoError(t, err)
|
||||
require.DeepEqual(t, flags.Get().StateDiffExponents, stored)
|
||||
}
|
||||
|
||||
func TestStateDiff_LoadExponentsMissing(t *testing.T) {
|
||||
db := setupDB(t)
|
||||
_, err := db.loadStateDiffExponents()
|
||||
require.ErrorContains(t, "exponents not found", err)
|
||||
}
|
||||
|
||||
func TestStateDiff_ComputeLevel(t *testing.T) {
|
||||
db := setupDB(t)
|
||||
setDefaultStateDiffExponents()
|
||||
@@ -154,6 +230,124 @@ func TestStateDiff_SaveFullSnapshot(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestStateDiff_StateByDiff_NonZeroOffsetSkipsRedundantLevelDiff(t *testing.T) {
|
||||
setStateDiffExponents([]int{6, 5, 4})
|
||||
|
||||
db := setupDB(t)
|
||||
|
||||
offset := uint64(1000)
|
||||
require.NoError(t, setOffsetInDB(db, offset))
|
||||
|
||||
stOffset, _ := createState(t, primitives.Slot(offset), version.Phase0)
|
||||
require.NoError(t, db.saveStateByDiff(context.Background(), stOffset))
|
||||
|
||||
st32, _ := createState(t, primitives.Slot(offset+32), version.Phase0)
|
||||
require.NoError(t, db.saveStateByDiff(context.Background(), st32))
|
||||
|
||||
st64, _ := createState(t, primitives.Slot(offset+64), version.Phase0)
|
||||
require.NoError(t, db.saveStateByDiff(context.Background(), st64))
|
||||
|
||||
st80, _ := createState(t, primitives.Slot(offset+80), version.Phase0)
|
||||
require.NoError(t, db.saveStateByDiff(context.Background(), st80))
|
||||
|
||||
readSt, err := db.stateByDiff(context.Background(), primitives.Slot(offset+80))
|
||||
require.NoError(t, err)
|
||||
|
||||
stWantSSZ, err := st80.MarshalSSZ()
|
||||
require.NoError(t, err)
|
||||
stGotSSZ, err := readSt.MarshalSSZ()
|
||||
require.NoError(t, err)
|
||||
require.DeepSSZEqual(t, stWantSSZ, stGotSSZ)
|
||||
}
|
||||
|
||||
func TestStateDiff_PopulateStateDiffCacheFromDB(t *testing.T) {
|
||||
setDefaultStateDiffExponents()
|
||||
|
||||
db := setupDB(t)
|
||||
_, err := populateStateDiffCacheFromDB(db, 0)
|
||||
require.ErrorContains(t, "missing offset snapshot", err)
|
||||
|
||||
st, _ := createState(t, 0, version.Phase0)
|
||||
require.NoError(t, setOffsetInDB(db, 0))
|
||||
require.NoError(t, db.saveStateByDiff(context.Background(), st))
|
||||
|
||||
err = db.db.Update(func(tx *bbolt.Tx) error {
|
||||
bucket := tx.Bucket(stateDiffBucket)
|
||||
if bucket == nil {
|
||||
return bbolt.ErrBucketNotFound
|
||||
}
|
||||
key := makeKeyForStateDiffTree(2, math.PowerOf2(16))
|
||||
return bucket.Put(append(key, stateSuffix...), []byte{1})
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
cache, err := populateStateDiffCacheFromDB(db, 0)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, cache)
|
||||
require.Equal(t, uint64(0), cache.getOffset())
|
||||
require.NotNil(t, cache.getAnchor(0))
|
||||
require.Equal(t, true, cache.levelHasData(0))
|
||||
require.Equal(t, false, cache.levelHasData(1))
|
||||
require.Equal(t, true, cache.levelHasData(2))
|
||||
}
|
||||
|
||||
func TestStateDiff_PopulateStateDiffCacheFromDB_InvalidLevelKey(t *testing.T) {
|
||||
setDefaultStateDiffExponents()
|
||||
|
||||
db := setupDB(t)
|
||||
st, _ := createState(t, 0, version.Phase0)
|
||||
require.NoError(t, setOffsetInDB(db, 0))
|
||||
require.NoError(t, db.saveStateByDiff(context.Background(), st))
|
||||
|
||||
require.NoError(t, db.db.Update(func(tx *bbolt.Tx) error {
|
||||
bucket := tx.Bucket(stateDiffBucket)
|
||||
if bucket == nil {
|
||||
return bbolt.ErrBucketNotFound
|
||||
}
|
||||
key := makeKeyForStateDiffTree(2, 1)
|
||||
return bucket.Put(append(key, stateSuffix...), []byte{1})
|
||||
}))
|
||||
|
||||
_, err := populateStateDiffCacheFromDB(db, 0)
|
||||
require.ErrorIs(t, ErrStateDiffCorrupted, err)
|
||||
}
|
||||
|
||||
func TestStateDiff_GetBaseAndDiffChainSkipsEmptyLevels(t *testing.T) {
|
||||
setDefaultStateDiffExponents()
|
||||
|
||||
db := setupDB(t)
|
||||
require.NoError(t, setOffsetInDB(db, 0))
|
||||
st, _ := createState(t, 0, version.Phase0)
|
||||
require.NoError(t, db.saveFullSnapshot(st))
|
||||
|
||||
cache, err := populateStateDiffCacheFromDB(db, 0)
|
||||
require.NoError(t, err)
|
||||
cache.levelsWithData[0] = true
|
||||
cache.levelsWithData[1] = false
|
||||
cache.levelsWithData[2] = true
|
||||
db.stateDiffCache = cache
|
||||
|
||||
slot := primitives.Slot(math.PowerOf2(18) + math.PowerOf2(16))
|
||||
key := makeKeyForStateDiffTree(2, uint64(slot))
|
||||
require.NoError(t, db.db.Update(func(tx *bbolt.Tx) error {
|
||||
bucket := tx.Bucket(stateDiffBucket)
|
||||
if bucket == nil {
|
||||
return bbolt.ErrBucketNotFound
|
||||
}
|
||||
if err := bucket.Put(append(key, stateSuffix...), []byte{1}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := bucket.Put(append(key, validatorSuffix...), []byte{2}); err != nil {
|
||||
return err
|
||||
}
|
||||
return bucket.Put(append(key, balancesSuffix...), []byte{3})
|
||||
}))
|
||||
|
||||
_, diffChain, err := db.getBaseAndDiffChain(0, slot)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 1, len(diffChain))
|
||||
}
|
||||
|
||||
func TestStateDiff_SaveAndReadFullSnapshot(t *testing.T) {
|
||||
setDefaultStateDiffExponents()
|
||||
|
||||
@@ -659,8 +853,10 @@ func setOffsetInDB(s *Store, offset uint64) error {
|
||||
}
|
||||
|
||||
func setDefaultStateDiffExponents() {
|
||||
globalFlags := flags.GlobalFlags{
|
||||
StateDiffExponents: []int{21, 18, 16, 13, 11, 9, 5},
|
||||
}
|
||||
setStateDiffExponents([]int{21, 18, 16, 13, 11, 9, 5})
|
||||
}
|
||||
|
||||
func setStateDiffExponents(exponents []int) {
|
||||
globalFlags := flags.GlobalFlags{StateDiffExponents: exponents}
|
||||
flags.Init(&globalFlags)
|
||||
}
|
||||
|
||||
@@ -26,7 +26,6 @@ import (
|
||||
"github.com/OffchainLabs/prysm/v7/testing/assert"
|
||||
"github.com/OffchainLabs/prysm/v7/testing/require"
|
||||
"github.com/OffchainLabs/prysm/v7/testing/util"
|
||||
logTest "github.com/sirupsen/logrus/hooks/test"
|
||||
bolt "go.etcd.io/bbolt"
|
||||
)
|
||||
|
||||
@@ -1349,7 +1348,7 @@ func TestStore_CanSaveRetrieveStateUsingStateDiff(t *testing.T) {
|
||||
|
||||
readSt, err := db.State(context.Background(), [32]byte{'A'})
|
||||
require.IsNil(t, readSt)
|
||||
require.ErrorContains(t, "neither state summary nor block found", err)
|
||||
require.ErrorIs(t, err, ErrNotFoundState)
|
||||
})
|
||||
|
||||
t.Run("Slot not in tree", func(t *testing.T) {
|
||||
@@ -1477,14 +1476,8 @@ func TestStore_CanSaveRetrieveStateUsingStateDiff(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
|
||||
readSt, err := db.State(context.Background(), r)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, readSt)
|
||||
|
||||
stSSZ, err := st.MarshalSSZ()
|
||||
require.NoError(t, err)
|
||||
readStSSZ, err := readSt.MarshalSSZ()
|
||||
require.NoError(t, err)
|
||||
require.DeepSSZEqual(t, stSSZ, readStSSZ)
|
||||
require.ErrorIs(t, err, ErrNotFoundState)
|
||||
require.IsNil(t, readSt)
|
||||
})
|
||||
}
|
||||
})
|
||||
@@ -1578,14 +1571,8 @@ func TestStore_CanSaveRetrieveStateUsingStateDiff(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
|
||||
readSt, err := db.State(context.Background(), r)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, readSt)
|
||||
|
||||
stSSZ, err := st.MarshalSSZ()
|
||||
require.NoError(t, err)
|
||||
readStSSZ, err := readSt.MarshalSSZ()
|
||||
require.NoError(t, err)
|
||||
require.DeepSSZEqual(t, stSSZ, readStSSZ)
|
||||
require.ErrorIs(t, err, ErrNotFoundState)
|
||||
require.IsNil(t, readSt)
|
||||
})
|
||||
}
|
||||
})
|
||||
@@ -1594,7 +1581,6 @@ func TestStore_CanSaveRetrieveStateUsingStateDiff(t *testing.T) {
|
||||
|
||||
func TestStore_HasStateUsingStateDiff(t *testing.T) {
|
||||
t.Run("No state summary or block", func(t *testing.T) {
|
||||
hook := logTest.NewGlobal()
|
||||
db := setupDB(t)
|
||||
featCfg := &features.Flags{}
|
||||
featCfg.EnableStateDiff = true
|
||||
@@ -1607,7 +1593,6 @@ func TestStore_HasStateUsingStateDiff(t *testing.T) {
|
||||
|
||||
hasSt := db.HasState(t.Context(), [32]byte{'A'})
|
||||
require.Equal(t, false, hasSt)
|
||||
require.LogsContain(t, hook, "neither state summary nor block found")
|
||||
})
|
||||
|
||||
t.Run("slot in tree or not", func(t *testing.T) {
|
||||
|
||||
@@ -550,6 +550,12 @@ func openDB(ctx context.Context, dbPath string, clearer *dbClearer) (*kv.Store,
|
||||
cfg := features.Get()
|
||||
cfg.EnableStateDiff = false
|
||||
features.Init(cfg)
|
||||
} else if errors.Is(err, kv.ErrStateDiffExponentMismatch) {
|
||||
log.WithError(err).Error("State-diff configuration mismatch; restart aborted. Use the stored exponents or re-sync the database.")
|
||||
return nil, err
|
||||
} else if errors.Is(err, kv.ErrStateDiffMissingSnapshot) || errors.Is(err, kv.ErrStateDiffCorrupted) {
|
||||
log.WithError(err).Error("State-diff database corrupted; restart aborted. Delete database and re-sync from genesis/checkpoint.")
|
||||
return nil, err
|
||||
} else if err != nil {
|
||||
return nil, errors.Wrapf(err, "could not create database at %s", dbPath)
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/core/helpers"
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/core/time"
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/db"
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/state"
|
||||
"github.com/OffchainLabs/prysm/v7/config/params"
|
||||
"github.com/OffchainLabs/prysm/v7/consensus-types/blocks"
|
||||
@@ -122,8 +123,13 @@ func (s *State) StateByRootInitialSync(ctx context.Context, blockRoot [32]byte)
|
||||
}
|
||||
|
||||
if s.beaconDB.HasState(ctx, blockRoot) {
|
||||
s, err := s.beaconDB.State(ctx, blockRoot)
|
||||
return s, errors.Wrap(err, "failed to retrieve init-sync state from db")
|
||||
st, err := s.beaconDB.State(ctx, blockRoot)
|
||||
if err == nil {
|
||||
return st, nil
|
||||
}
|
||||
if !stderrors.Is(err, db.ErrNotFoundState) {
|
||||
return nil, errors.Wrap(err, "failed to retrieve init-sync state from db")
|
||||
}
|
||||
}
|
||||
|
||||
startState, err := s.latestAncestor(ctx, blockRoot)
|
||||
@@ -213,7 +219,13 @@ func (s *State) loadStateByRoot(ctx context.Context, blockRoot [32]byte) (state.
|
||||
|
||||
// Short circuit if the state is already in the DB.
|
||||
if s.beaconDB.HasState(ctx, blockRoot) {
|
||||
return s.beaconDB.State(ctx, blockRoot)
|
||||
st, err := s.beaconDB.State(ctx, blockRoot)
|
||||
if err == nil {
|
||||
return st, nil
|
||||
}
|
||||
if !stderrors.Is(err, db.ErrNotFoundState) {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
summary, err := s.stateSummary(ctx, blockRoot)
|
||||
|
||||
@@ -185,6 +185,71 @@ type testSetupSlots struct {
|
||||
lastblock primitives.Slot
|
||||
}
|
||||
|
||||
type notFoundOnRootDB struct {
|
||||
db.NoHeadAccessDatabase
|
||||
target [32]byte
|
||||
}
|
||||
|
||||
func (d *notFoundOnRootDB) HasState(ctx context.Context, blockRoot [32]byte) bool {
|
||||
if blockRoot == d.target {
|
||||
return true
|
||||
}
|
||||
return d.NoHeadAccessDatabase.HasState(ctx, blockRoot)
|
||||
}
|
||||
|
||||
func (d *notFoundOnRootDB) State(ctx context.Context, blockRoot [32]byte) (state.BeaconState, error) {
|
||||
if blockRoot == d.target {
|
||||
return nil, db.ErrNotFoundState
|
||||
}
|
||||
return d.NoHeadAccessDatabase.State(ctx, blockRoot)
|
||||
}
|
||||
|
||||
func TestStateByRoot_FallsBackToReplayOnNotFoundStateFromDirectRead(t *testing.T) {
|
||||
ctx := t.Context()
|
||||
beaconDB := testDB.SetupDB(t)
|
||||
|
||||
st9, _ := util.DeterministicGenesisState(t, 32)
|
||||
st9, err := ReplayProcessSlots(ctx, st9, 9)
|
||||
require.NoError(t, err)
|
||||
|
||||
hdr := st9.LatestBlockHeader()
|
||||
hdrRoot, err := hdr.HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
|
||||
st10 := st9.Copy()
|
||||
blk10 := util.NewBeaconBlock()
|
||||
blk10.Block.Slot = 10
|
||||
blk10.Block.ParentRoot = hdrRoot[:]
|
||||
idx10, err := helpers.BeaconProposerIndexAtSlot(ctx, st10, blk10.Block.Slot)
|
||||
require.NoError(t, err)
|
||||
blk10.Block.ProposerIndex = idx10
|
||||
ib10, err := blt.NewSignedBeaconBlock(blk10)
|
||||
require.NoError(t, err)
|
||||
|
||||
st10, err = executeStateTransitionStateGen(ctx, st10, ib10)
|
||||
require.NoError(t, err)
|
||||
st10Root, err := st10.HashTreeRoot(ctx)
|
||||
require.NoError(t, err)
|
||||
blk10.Block.StateRoot = st10Root[:]
|
||||
|
||||
util.SaveBlock(t, ctx, beaconDB, blk10)
|
||||
require.NoError(t, beaconDB.SaveState(ctx, st9, hdrRoot))
|
||||
|
||||
ib10, err = blt.NewSignedBeaconBlock(blk10)
|
||||
require.NoError(t, err)
|
||||
rob10, err := blt.NewROBlock(ib10)
|
||||
require.NoError(t, err)
|
||||
|
||||
service := New(¬FoundOnRootDB{NoHeadAccessDatabase: beaconDB, target: rob10.Root()}, doublylinkedtree.New())
|
||||
|
||||
got, err := service.StateByRoot(ctx, rob10.Root())
|
||||
require.NoError(t, err)
|
||||
|
||||
gotRoot, err := got.HashTreeRoot(ctx)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, st10Root, gotRoot)
|
||||
}
|
||||
|
||||
func TestLoadStateByRoot(t *testing.T) {
|
||||
ctx := t.Context()
|
||||
persistEpochBoundary := func(r testChain, slot primitives.Slot) {
|
||||
|
||||
@@ -356,6 +356,11 @@ var (
|
||||
Usage: "A comma-separated list of exponents (of 2) in decreasing order, defining the state diff hierarchy levels. The last exponent must be greater than or equal to 5.",
|
||||
Value: cli.NewIntSlice(21, 18, 16, 13, 11, 9, 5),
|
||||
}
|
||||
// StateDiffValidateOnStartup validates state diff data on startup.
|
||||
StateDiffValidateOnStartup = &cli.BoolFlag{
|
||||
Name: "disable-hdiff-validate-on-startup",
|
||||
Usage: "Disables state-diff validation on startup (enabled by default).",
|
||||
}
|
||||
// DisableEphemeralLogFile disables the 24 hour debug log file.
|
||||
DisableEphemeralLogFile = &cli.BoolFlag{
|
||||
Name: "disable-ephemeral-log-file",
|
||||
|
||||
@@ -9,24 +9,25 @@ import (
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
const maxStateDiffExponents = 30
|
||||
const MaxStateDiffExponent = 30
|
||||
|
||||
// GlobalFlags specifies all the global flags for the
|
||||
// beacon node.
|
||||
type GlobalFlags struct {
|
||||
SubscribeToAllSubnets bool
|
||||
StateDiffValidateOnStartup bool
|
||||
Supernode bool
|
||||
SemiSupernode bool
|
||||
DisableGetBlobsV2 bool
|
||||
MinimumSyncPeers int
|
||||
MinimumPeersPerSubnet int
|
||||
MaxConcurrentDials int
|
||||
BlockBatchLimit int
|
||||
BlockBatchLimitBurstFactor int
|
||||
BlobBatchLimit int
|
||||
SemiSupernode bool
|
||||
SubscribeToAllSubnets bool
|
||||
BlobBatchLimitBurstFactor int
|
||||
DataColumnBatchLimit int
|
||||
BlockBatchLimit int
|
||||
MaxConcurrentDials int
|
||||
MinimumPeersPerSubnet int
|
||||
MinimumSyncPeers int
|
||||
DataColumnBatchLimitBurstFactor int
|
||||
BlockBatchLimitBurstFactor int
|
||||
BlobBatchLimit int
|
||||
StateDiffExponents []int
|
||||
}
|
||||
|
||||
@@ -80,6 +81,7 @@ func ConfigureGlobalFlags(ctx *cli.Context) error {
|
||||
|
||||
// State-diff-exponents
|
||||
cfg.StateDiffExponents = ctx.IntSlice(StateDiffExponents.Name)
|
||||
cfg.StateDiffValidateOnStartup = !ctx.Bool(StateDiffValidateOnStartup.Name)
|
||||
if features.Get().EnableStateDiff {
|
||||
if err := validateStateDiffExponents(cfg.StateDiffExponents); err != nil {
|
||||
return err
|
||||
@@ -88,6 +90,9 @@ func ConfigureGlobalFlags(ctx *cli.Context) error {
|
||||
if ctx.IsSet(StateDiffExponents.Name) {
|
||||
log.Warn("--state-diff-exponents is set but --enable-state-diff is not; the value will be ignored.")
|
||||
}
|
||||
if ctx.IsSet(StateDiffValidateOnStartup.Name) {
|
||||
log.Warn("--disable-hdiff-validate-on-startup is set but --enable-state-diff is not; the value will be ignored.")
|
||||
}
|
||||
}
|
||||
|
||||
cfg.BlockBatchLimit = ctx.Int(BlockBatchLimit.Name)
|
||||
@@ -132,7 +137,7 @@ func validateStateDiffExponents(exponents []int) error {
|
||||
if exponents[length-1] < 5 {
|
||||
return errors.New("the last state diff exponent must be at least 5")
|
||||
}
|
||||
prev := maxStateDiffExponents + 1
|
||||
prev := MaxStateDiffExponent + 1
|
||||
for _, exp := range exponents {
|
||||
if exp >= prev {
|
||||
return errors.New("state diff exponents must be in strictly decreasing order, and each exponent must be <= 30")
|
||||
|
||||
@@ -161,6 +161,7 @@ var appFlags = []cli.Flag{
|
||||
dasFlags.BlobRetentionEpochFlag,
|
||||
flags.BatchVerifierLimit,
|
||||
flags.StateDiffExponents,
|
||||
flags.StateDiffValidateOnStartup,
|
||||
flags.DisableEphemeralLogFile,
|
||||
}
|
||||
|
||||
|
||||
@@ -75,6 +75,7 @@ var appHelpFlagGroups = []flagGroup{
|
||||
flags.RPCPort,
|
||||
flags.BatchVerifierLimit,
|
||||
flags.StateDiffExponents,
|
||||
flags.StateDiffValidateOnStartup,
|
||||
},
|
||||
},
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user