mirror of
https://github.com/OffchainLabs/prysm.git
synced 2026-01-09 13:28:01 -05:00
Compare commits
24 Commits
c6c9414d8b
...
db-hdiff
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ff6bdd2304 | ||
|
|
00908ce586 | ||
|
|
f95cd9edb6 | ||
|
|
1ae2c53329 | ||
|
|
ebe53ed849 | ||
|
|
daf5139917 | ||
|
|
4077329804 | ||
|
|
0e1ddfb403 | ||
|
|
58c85bfab7 | ||
|
|
f4e6bc73c7 | ||
|
|
06dff01bc8 | ||
|
|
e94628d302 | ||
|
|
f1596b6c47 | ||
|
|
49ff1ae771 | ||
|
|
a13a5604ac | ||
|
|
7071b6998d | ||
|
|
4cbf791332 | ||
|
|
6c859c33d6 | ||
|
|
c29a8708fb | ||
|
|
0f3527971a | ||
|
|
d404e47dff | ||
|
|
2b164df38d | ||
|
|
81020ca939 | ||
|
|
95e4ff4dc2 |
@@ -41,6 +41,8 @@ type ReadOnlyDatabase interface {
|
||||
StateSummary(ctx context.Context, blockRoot [32]byte) (*ethpb.StateSummary, error)
|
||||
HasStateSummary(ctx context.Context, blockRoot [32]byte) bool
|
||||
HighestSlotStatesBelow(ctx context.Context, slot primitives.Slot) ([]state.ReadOnlyBeaconState, error)
|
||||
// StateDiff related methods.
|
||||
StateDiff(ctx context.Context, slot primitives.Slot) (state.BeaconState, error)
|
||||
// Checkpoint operations.
|
||||
JustifiedCheckpoint(ctx context.Context) (*ethpb.Checkpoint, error)
|
||||
FinalizedCheckpoint(ctx context.Context) (*ethpb.Checkpoint, error)
|
||||
@@ -83,6 +85,8 @@ type NoHeadAccessDatabase interface {
|
||||
DeleteStates(ctx context.Context, blockRoots [][32]byte) error
|
||||
SaveStateSummary(ctx context.Context, summary *ethpb.StateSummary) error
|
||||
SaveStateSummaries(ctx context.Context, summaries []*ethpb.StateSummary) error
|
||||
// Statediff related methods.
|
||||
SaveStateDiff(ctx context.Context, state state.ReadOnlyBeaconState) error
|
||||
// Checkpoint operations.
|
||||
SaveJustifiedCheckpoint(ctx context.Context, checkpoint *ethpb.Checkpoint) error
|
||||
SaveFinalizedCheckpoint(ctx context.Context, checkpoint *ethpb.Checkpoint) error
|
||||
|
||||
@@ -25,6 +25,7 @@ go_library(
|
||||
"migration_state_validators.go",
|
||||
"schema.go",
|
||||
"state.go",
|
||||
"state_diff.go",
|
||||
"state_summary.go",
|
||||
"state_summary_cache.go",
|
||||
"utils.go",
|
||||
@@ -44,6 +45,7 @@ go_library(
|
||||
"//config/fieldparams:go_default_library",
|
||||
"//config/params:go_default_library",
|
||||
"//consensus-types/blocks:go_default_library",
|
||||
"//consensus-types/hdiff:go_default_library",
|
||||
"//consensus-types/interfaces:go_default_library",
|
||||
"//consensus-types/light-client:go_default_library",
|
||||
"//consensus-types/primitives:go_default_library",
|
||||
@@ -94,6 +96,7 @@ go_test(
|
||||
"migration_archived_index_test.go",
|
||||
"migration_block_slot_index_test.go",
|
||||
"migration_state_validators_test.go",
|
||||
"state_diff_test.go",
|
||||
"state_summary_test.go",
|
||||
"state_test.go",
|
||||
"utils_test.go",
|
||||
@@ -116,6 +119,7 @@ go_test(
|
||||
"//consensus-types/light-client:go_default_library",
|
||||
"//consensus-types/primitives:go_default_library",
|
||||
"//encoding/bytesutil:go_default_library",
|
||||
"//math:go_default_library",
|
||||
"//proto/dbval:go_default_library",
|
||||
"//proto/engine/v1:go_default_library",
|
||||
"//proto/prysm/v1alpha1:go_default_library",
|
||||
|
||||
@@ -112,6 +112,7 @@ var Buckets = [][]byte{
|
||||
lightClientUpdatesBucket,
|
||||
lightClientBootstrapBucket,
|
||||
lightClientSyncCommitteeBucket,
|
||||
stateDiffBucket,
|
||||
// Indices buckets.
|
||||
blockSlotIndicesBucket,
|
||||
stateSlotIndicesBucket,
|
||||
|
||||
@@ -16,6 +16,7 @@ var (
|
||||
stateValidatorsBucket = []byte("state-validators")
|
||||
feeRecipientBucket = []byte("fee-recipient")
|
||||
registrationBucket = []byte("registration")
|
||||
stateDiffBucket = []byte("state-diff")
|
||||
|
||||
// Light Client Updates Bucket
|
||||
lightClientUpdatesBucket = []byte("light-client-updates")
|
||||
|
||||
272
beacon-chain/db/kv/state_diff.go
Normal file
272
beacon-chain/db/kv/state_diff.go
Normal file
@@ -0,0 +1,272 @@
|
||||
package kv
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/OffchainLabs/prysm/v6/beacon-chain/state"
|
||||
"github.com/OffchainLabs/prysm/v6/config/params"
|
||||
"github.com/OffchainLabs/prysm/v6/consensus-types/hdiff"
|
||||
"github.com/OffchainLabs/prysm/v6/consensus-types/primitives"
|
||||
"github.com/OffchainLabs/prysm/v6/math"
|
||||
"github.com/OffchainLabs/prysm/v6/monitoring/tracing/trace"
|
||||
"github.com/pkg/errors"
|
||||
bolt "go.etcd.io/bbolt"
|
||||
)
|
||||
|
||||
/*
|
||||
We use a level-based approach to save state diffs. The levels are 0-6, where each level corresponds to an exponent of 2 (exponents[lvl]).
|
||||
The data at level 0 is saved every 2**exponent[0] slots and always contains a full state snapshot that is used as a base for the delta saved at other levels.
|
||||
*/
|
||||
|
||||
var (
|
||||
anchorCache = make(map[int]state.ReadOnlyBeaconState, len(params.StateHierarchyExponents())) // cache full states at the last node at each level
|
||||
)
|
||||
|
||||
// SaveStateDiff takes a state and decides between saving a full state snapshot or a diff.
|
||||
func (s *Store) SaveStateDiff(ctx context.Context, st state.ReadOnlyBeaconState) error {
|
||||
ctx, span := trace.StartSpan(ctx, "BeaconDB.SaveStateDiff")
|
||||
defer span.End()
|
||||
|
||||
slot := st.Slot()
|
||||
offset, err := s.loadOrInitOffset(slot)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if uint64(slot) < offset {
|
||||
return ErrSlotBeforeOffset
|
||||
}
|
||||
|
||||
// Find the level to save the state.
|
||||
lvl := computeLevel(offset, slot)
|
||||
if lvl == -1 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Save full state if level is 0.
|
||||
if lvl == 0 {
|
||||
return s.saveFullSnapshot(lvl, st)
|
||||
}
|
||||
|
||||
// Get anchor state to compute the diff from.
|
||||
anchorState, err := s.getAnchorState(offset, lvl, slot)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = s.saveHdiff(lvl, anchorState, st)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// StateDiff retrieves the full state for a given slot.
|
||||
func (s *Store) StateDiff(ctx context.Context, slot primitives.Slot) (state.BeaconState, error) {
|
||||
offset, err := s.getOffset()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if uint64(slot) < offset {
|
||||
return nil, ErrSlotBeforeOffset
|
||||
}
|
||||
|
||||
snapshot, diffChain, err := s.getBaseAndDiffChain(offset, slot)
|
||||
|
||||
// TODO: apply the diff chain to the snapshot and return the final state.
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// SaveHdiff computes the diff between the anchor state and the current state and saves it to the database.
|
||||
func (s *Store) saveHdiff(lvl int, anchor, st state.ReadOnlyBeaconState) error {
|
||||
slot := uint64(st.Slot())
|
||||
key := makeKey(lvl, slot)
|
||||
|
||||
// TODO: compute actual diff
|
||||
diff := hdiff.HdiffSerialized{}
|
||||
|
||||
err := s.db.Update(func(tx *bolt.Tx) error {
|
||||
bucket := tx.Bucket(stateDiffBucket)
|
||||
if bucket == nil {
|
||||
return bolt.ErrBucketNotFound
|
||||
}
|
||||
// TODO: save the diff bytes per field with suffix
|
||||
buf := make([]byte, len(key)+len("_s"))
|
||||
copy(buf, key)
|
||||
copy(buf[len(key):], "_s")
|
||||
if err := bucket.Put(buf, diff); err != nil {
|
||||
return err
|
||||
}
|
||||
copy(buf[len(key):], "_v")
|
||||
if err := bucket.Put(buf, diff); err != nil {
|
||||
return err
|
||||
}
|
||||
copy(buf[len(key):], "_b")
|
||||
if err := bucket.Put(buf, diff); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Save the full state to the cache (if not the last level).
|
||||
if lvl != len(params.StateHierarchyExponents())-1 {
|
||||
anchorCache[lvl] = st
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// SaveFullSnapshot saves the full level 0 state snapshot to the database.
|
||||
func (s *Store) saveFullSnapshot(lvl int, st state.ReadOnlyBeaconState) error {
|
||||
slot := uint64(st.Slot())
|
||||
key := makeKey(lvl, slot)
|
||||
stateBytes, err := st.MarshalSSZ()
|
||||
// add version key to value
|
||||
enc, err := addKey(st.Version(), stateBytes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = s.db.Update(func(tx *bolt.Tx) error {
|
||||
bucket := tx.Bucket(stateDiffBucket)
|
||||
if bucket == nil {
|
||||
return bolt.ErrBucketNotFound
|
||||
}
|
||||
|
||||
if err := bucket.Put(key, enc); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Save the full state to the cache.
|
||||
anchorCache[lvl] = st
|
||||
return nil
|
||||
}
|
||||
|
||||
func addKey(v int, bytes []byte) ([]byte, error) {
|
||||
key, err := keyForSnapshot(v)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
enc := make([]byte, len(key)+len(bytes))
|
||||
copy(enc, key)
|
||||
copy(enc[len(key):], bytes)
|
||||
return enc, nil
|
||||
}
|
||||
|
||||
func (s *Store) getBaseAndDiffChain(offset uint64, slot primitives.Slot) (state.BeaconState, []*hdiff.HdiffSerialized, error) {
|
||||
rel := uint64(slot) - offset
|
||||
lvl := computeLevel(offset, slot)
|
||||
if lvl == -1 {
|
||||
return nil, nil, errors.New("slot not in tree")
|
||||
}
|
||||
|
||||
exponents := params.StateHierarchyExponents()
|
||||
|
||||
baseSpan := math.PowerOf2(exponents[0])
|
||||
baseAnchorSlot := (rel / baseSpan * baseSpan) + offset
|
||||
|
||||
var diffChainIndices []uint64
|
||||
for i := 1; i < lvl; i++ {
|
||||
span := math.PowerOf2(exponents[i])
|
||||
diffSlot := rel / span * span
|
||||
if diffSlot == baseAnchorSlot {
|
||||
continue
|
||||
}
|
||||
diffChainIndices = appendUnique(diffChainIndices, diffSlot+offset)
|
||||
}
|
||||
|
||||
baseSnapshot, err := s.getFullSnapshot(lvl, baseAnchorSlot)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
diffChain := make([]*hdiff.HdiffSerialized, len(diffChainIndices))
|
||||
for _, diffSlot := range diffChainIndices {
|
||||
diff, err := s.getDiff(computeLevel(offset, primitives.Slot(diffSlot)), diffSlot)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
diffChain = append(diffChain, diff)
|
||||
}
|
||||
|
||||
return baseSnapshot, diffChain, nil
|
||||
}
|
||||
|
||||
func (s *Store) getDiff(lvl int, slot uint64) (*hdiff.HdiffSerialized, error) {
|
||||
key := makeKey(lvl, slot)
|
||||
var stateDiff []byte
|
||||
var validatorDiff []byte
|
||||
var balancesDiff []byte
|
||||
|
||||
err := s.db.View(func(tx *bolt.Tx) error {
|
||||
bucket := tx.Bucket(stateDiffBucket)
|
||||
if bucket == nil {
|
||||
return bolt.ErrBucketNotFound
|
||||
}
|
||||
buf := make([]byte, len(key)+len("_s"))
|
||||
copy(buf, key)
|
||||
copy(buf[len(key):], "_s")
|
||||
stateDiff = bucket.Get(buf)
|
||||
if stateDiff == nil {
|
||||
return errors.New("state diff not found")
|
||||
}
|
||||
copy(buf[len(key):], "_v")
|
||||
validatorDiff = bucket.Get(buf)
|
||||
if validatorDiff == nil {
|
||||
return errors.New("validator diff not found")
|
||||
}
|
||||
copy(buf[len(key):], "_b")
|
||||
balancesDiff = bucket.Get(buf)
|
||||
if balancesDiff == nil {
|
||||
return errors.New("balances diff not found")
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &hdiff.HdiffSerialized{}, nil
|
||||
}
|
||||
|
||||
func (s *Store) getFullSnapshot(lvl int, slot uint64) (state.BeaconState, error) {
|
||||
key := makeKey(lvl, slot)
|
||||
var enc []byte
|
||||
|
||||
err := s.db.View(func(tx *bolt.Tx) error {
|
||||
bucket := tx.Bucket(stateDiffBucket)
|
||||
if bucket == nil {
|
||||
return bolt.ErrBucketNotFound
|
||||
}
|
||||
enc = bucket.Get(key)
|
||||
if enc == nil {
|
||||
return errors.New("state not found")
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return s.decodeStateSnapshot(enc)
|
||||
}
|
||||
|
||||
func appendUnique(s []uint64, v uint64) []uint64 {
|
||||
for _, x := range s {
|
||||
if x == v {
|
||||
return s
|
||||
}
|
||||
}
|
||||
return append(s, v)
|
||||
}
|
||||
167
beacon-chain/db/kv/state_diff_helpers.go
Normal file
167
beacon-chain/db/kv/state_diff_helpers.go
Normal file
@@ -0,0 +1,167 @@
|
||||
package kv
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/OffchainLabs/prysm/v6/beacon-chain/state"
|
||||
state_native "github.com/OffchainLabs/prysm/v6/beacon-chain/state/state-native"
|
||||
"github.com/OffchainLabs/prysm/v6/config/params"
|
||||
"github.com/OffchainLabs/prysm/v6/consensus-types/primitives"
|
||||
"github.com/OffchainLabs/prysm/v6/math"
|
||||
ethpb "github.com/OffchainLabs/prysm/v6/proto/prysm/v1alpha1"
|
||||
"github.com/OffchainLabs/prysm/v6/runtime/version"
|
||||
"go.etcd.io/bbolt"
|
||||
)
|
||||
|
||||
var (
|
||||
offsetKey = []byte("offset")
|
||||
ErrSlotBeforeOffset = errors.New("slot is before root offset")
|
||||
)
|
||||
|
||||
func makeKey(level int, slot uint64) []byte {
|
||||
buf := make([]byte, 1+8)
|
||||
buf[0] = byte(level)
|
||||
binary.BigEndian.PutUint64(buf[1:], slot)
|
||||
return buf
|
||||
}
|
||||
|
||||
func (s *Store) getAnchorState(offset uint64, lvl int, slot primitives.Slot) (anchor state.ReadOnlyBeaconState, err error) {
|
||||
if lvl == 0 {
|
||||
return nil, errors.New("no anchor for level 0")
|
||||
}
|
||||
|
||||
relSlot := uint64(slot) - offset
|
||||
prevExp := params.StateHierarchyExponents()[lvl-1]
|
||||
span := math.PowerOf2(prevExp)
|
||||
anchorSlot := primitives.Slot((relSlot / span * span) + offset)
|
||||
|
||||
anchorLvl := computeLevel(offset, anchorSlot)
|
||||
if anchorLvl == -1 {
|
||||
return nil, errors.New("could not compute anchor level")
|
||||
}
|
||||
|
||||
// Check if we have the anchor in cache.
|
||||
anchor, ok := anchorCache[anchorLvl]
|
||||
if ok {
|
||||
return anchor, nil
|
||||
}
|
||||
|
||||
// If not, load it from the database.
|
||||
anchor, err = s.StateDiff(context.Background(), anchorSlot)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Save it in the cache.
|
||||
anchorCache[anchorLvl] = anchor
|
||||
return anchor, nil
|
||||
}
|
||||
|
||||
// ComputeLevel computes the level in the diff tree. Returns -1 in case slot should not be in tree.
|
||||
func computeLevel(offset uint64, slot primitives.Slot) int {
|
||||
rel := uint64(slot) - offset
|
||||
for i, exp := range params.StateHierarchyExponents() {
|
||||
span := math.PowerOf2(exp)
|
||||
if rel%span == 0 {
|
||||
return i
|
||||
}
|
||||
}
|
||||
// If rel isn’t on any of the boundaries, we should ignore saving it.
|
||||
return -1
|
||||
}
|
||||
|
||||
func (s *Store) loadOrInitOffset(slot primitives.Slot) (offset uint64, err error) {
|
||||
return offset, s.db.Update(func(tx *bbolt.Tx) error {
|
||||
bucket := tx.Bucket(stateDiffBucket)
|
||||
if bucket == nil {
|
||||
return bbolt.ErrBucketNotFound
|
||||
}
|
||||
|
||||
offsetBytes := bucket.Get(offsetKey)
|
||||
if offsetBytes != nil {
|
||||
offset = binary.BigEndian.Uint64(offsetBytes)
|
||||
return nil
|
||||
}
|
||||
|
||||
offset = uint64(slot)
|
||||
offsetBytes = make([]byte, 8)
|
||||
binary.BigEndian.PutUint64(offsetBytes, offset)
|
||||
if err := bucket.Put(offsetKey, offsetBytes); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (s *Store) getOffset() (offset uint64, err error) {
|
||||
return offset, 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 {
|
||||
offset = binary.BigEndian.Uint64(offsetBytes)
|
||||
return nil
|
||||
}
|
||||
return bbolt.ErrIncompatibleValue
|
||||
})
|
||||
}
|
||||
|
||||
func keyForSnapshot(v int) ([]byte, error) {
|
||||
switch v {
|
||||
case version.Electra:
|
||||
return ElectraKey, nil
|
||||
case version.Deneb:
|
||||
return denebKey, nil
|
||||
case version.Capella:
|
||||
return capellaKey, nil
|
||||
case version.Bellatrix:
|
||||
return bellatrixKey, nil
|
||||
case version.Altair:
|
||||
return altairKey, nil
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported version %s", version.String(v))
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Store) decodeStateSnapshot(enc []byte) (state.BeaconState, error) {
|
||||
switch {
|
||||
case HasElectraKey(enc):
|
||||
var electraState ethpb.BeaconStateElectra
|
||||
if err := electraState.UnmarshalSSZ(enc[len(ElectraKey):]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return state_native.InitializeFromProtoElectra(&electraState)
|
||||
case hasDenebKey(enc):
|
||||
var denebState ethpb.BeaconStateDeneb
|
||||
if err := denebState.UnmarshalSSZ(enc[len(denebKey):]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return state_native.InitializeFromProtoDeneb(&denebState)
|
||||
case hasCapellaKey(enc):
|
||||
var capellaState ethpb.BeaconStateCapella
|
||||
if err := capellaState.UnmarshalSSZ(enc[len(capellaKey):]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return state_native.InitializeFromProtoCapella(&capellaState)
|
||||
case hasBellatrixKey(enc):
|
||||
var bellatrixState ethpb.BeaconStateBellatrix
|
||||
if err := bellatrixState.UnmarshalSSZ(enc[len(bellatrixKey):]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return state_native.InitializeFromProtoBellatrix(&bellatrixState)
|
||||
case hasAltairKey(enc):
|
||||
var altairState ethpb.BeaconStateAltair
|
||||
if err := altairState.UnmarshalSSZ(enc[len(altairKey):]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return state_native.InitializeFromProtoAltair(&altairState)
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported encoding %x", enc)
|
||||
}
|
||||
}
|
||||
163
beacon-chain/db/kv/state_diff_test.go
Normal file
163
beacon-chain/db/kv/state_diff_test.go
Normal file
@@ -0,0 +1,163 @@
|
||||
package kv
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/OffchainLabs/prysm/v6/consensus-types/primitives"
|
||||
"github.com/OffchainLabs/prysm/v6/math"
|
||||
"github.com/OffchainLabs/prysm/v6/testing/require"
|
||||
"github.com/OffchainLabs/prysm/v6/testing/util"
|
||||
"go.etcd.io/bbolt"
|
||||
)
|
||||
|
||||
func TestStateDiff_LoadOrInitOffset(t *testing.T) {
|
||||
db := setupDB(t)
|
||||
|
||||
offset, err := loadOrInitOffset(db, 10)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, uint64(10), offset)
|
||||
|
||||
offset, err = loadOrInitOffset(db, 20)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, uint64(10), offset)
|
||||
|
||||
offset, err = loadOrInitOffset(db, 5)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, uint64(10), offset)
|
||||
}
|
||||
|
||||
func TestStateDiff_ComputeLevel(t *testing.T) {
|
||||
db := setupDB(t)
|
||||
|
||||
offset, err := loadOrInitOffset(db, 0)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, uint64(0), offset)
|
||||
|
||||
// 2 ** 21
|
||||
lvl, shouldSave := computeLevel(math.PowerOf2(21))
|
||||
require.Equal(t, 0, lvl)
|
||||
require.Equal(t, true, shouldSave)
|
||||
|
||||
// 2 ** 21 * 3
|
||||
lvl, shouldSave = computeLevel(math.PowerOf2(21) * 3)
|
||||
require.Equal(t, 0, lvl)
|
||||
require.Equal(t, true, shouldSave)
|
||||
|
||||
// 2 ** 18
|
||||
lvl, shouldSave = computeLevel(math.PowerOf2(18))
|
||||
require.Equal(t, 1, lvl)
|
||||
require.Equal(t, true, shouldSave)
|
||||
|
||||
// 2 ** 18 * 3
|
||||
lvl, shouldSave = computeLevel(math.PowerOf2(18) * 3)
|
||||
require.Equal(t, 1, lvl)
|
||||
require.Equal(t, true, shouldSave)
|
||||
|
||||
// 2 ** 16
|
||||
lvl, shouldSave = computeLevel(math.PowerOf2(16))
|
||||
require.Equal(t, 2, lvl)
|
||||
require.Equal(t, true, shouldSave)
|
||||
|
||||
// 2 ** 16 * 3
|
||||
lvl, shouldSave = computeLevel(math.PowerOf2(16) * 3)
|
||||
require.Equal(t, 2, lvl)
|
||||
require.Equal(t, true, shouldSave)
|
||||
|
||||
// 2 ** 13
|
||||
lvl, shouldSave = computeLevel(math.PowerOf2(13))
|
||||
require.Equal(t, 3, lvl)
|
||||
require.Equal(t, true, shouldSave)
|
||||
|
||||
// 2 ** 13 * 3
|
||||
lvl, shouldSave = computeLevel(math.PowerOf2(13) * 3)
|
||||
require.Equal(t, 3, lvl)
|
||||
require.Equal(t, true, shouldSave)
|
||||
|
||||
// 2 ** 11
|
||||
lvl, shouldSave = computeLevel(math.PowerOf2(11))
|
||||
require.Equal(t, 4, lvl)
|
||||
require.Equal(t, true, shouldSave)
|
||||
|
||||
// 2 ** 11 * 3
|
||||
lvl, shouldSave = computeLevel(math.PowerOf2(11) * 3)
|
||||
require.Equal(t, 4, lvl)
|
||||
require.Equal(t, true, shouldSave)
|
||||
|
||||
// 2 ** 9
|
||||
lvl, shouldSave = computeLevel(math.PowerOf2(9))
|
||||
require.Equal(t, 5, lvl)
|
||||
require.Equal(t, true, shouldSave)
|
||||
|
||||
// 2 ** 9 * 3
|
||||
lvl, shouldSave = computeLevel(math.PowerOf2(9) * 3)
|
||||
require.Equal(t, 5, lvl)
|
||||
require.Equal(t, true, shouldSave)
|
||||
|
||||
// 2 ** 5
|
||||
lvl, shouldSave = computeLevel(math.PowerOf2(5))
|
||||
require.Equal(t, 6, lvl)
|
||||
require.Equal(t, true, shouldSave)
|
||||
|
||||
// 2 ** 5 * 3
|
||||
lvl, shouldSave = computeLevel(math.PowerOf2(5) * 3)
|
||||
require.Equal(t, 6, lvl)
|
||||
require.Equal(t, true, shouldSave)
|
||||
|
||||
// 2 ** 7
|
||||
lvl, shouldSave = computeLevel(math.PowerOf2(7))
|
||||
require.Equal(t, 6, lvl)
|
||||
require.Equal(t, true, shouldSave)
|
||||
|
||||
// 2 ** 5 + 1
|
||||
lvl, shouldSave = computeLevel(math.PowerOf2(5) + 1)
|
||||
require.Equal(t, false, shouldSave)
|
||||
|
||||
// 2 ** 5 + 16
|
||||
lvl, shouldSave = computeLevel(math.PowerOf2(5) + 16)
|
||||
require.Equal(t, false, shouldSave)
|
||||
|
||||
// 2 ** 5 + 32
|
||||
lvl, shouldSave = computeLevel(math.PowerOf2(5) + 32)
|
||||
require.Equal(t, true, shouldSave)
|
||||
require.Equal(t, 6, lvl)
|
||||
|
||||
}
|
||||
|
||||
func TestStateDiff_SaveFullSnapshot(t *testing.T) {
|
||||
db := setupDB(t)
|
||||
|
||||
// Set offset to zero
|
||||
offset, err := loadOrInitOffset(db, 0)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, uint64(0), offset)
|
||||
|
||||
// Create state with slot 2**21 * 3
|
||||
st, err := util.NewBeaconStateElectra()
|
||||
require.NoError(t, err)
|
||||
slot := primitives.Slot(math.PowerOf2(21))
|
||||
err = st.SetSlot(slot)
|
||||
require.NoError(t, err)
|
||||
stssz, err := st.MarshalSSZ()
|
||||
require.NoError(t, err)
|
||||
|
||||
err = db.SaveStateDiff(nil, st)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = db.db.View(func(tx *bbolt.Tx) error {
|
||||
bucket := tx.Bucket(stateDiffBucket)
|
||||
if bucket == nil {
|
||||
return bbolt.ErrBucketNotFound
|
||||
}
|
||||
for i := 6; i >= 0; i-- {
|
||||
s := bucket.Get(makeKey(i, uint64(slot)))
|
||||
require.NotNil(t, s, "key not found")
|
||||
if i > 0 {
|
||||
require.DeepEqual(t, EmptyNodeMarker, s, "node not marked as empty")
|
||||
} else {
|
||||
require.DeepSSZEqual(t, stssz, s, "retrieved state does not match saved state")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
require.NoError(t, err)
|
||||
}
|
||||
3
changelog/potuz_hdiff_diff_type.md
Normal file
3
changelog/potuz_hdiff_diff_type.md
Normal file
@@ -0,0 +1,3 @@
|
||||
### Added
|
||||
|
||||
- Add native state diff type and marshalling functions
|
||||
9
config/params/state_diff_config.go
Normal file
9
config/params/state_diff_config.go
Normal file
@@ -0,0 +1,9 @@
|
||||
package params
|
||||
|
||||
var (
|
||||
stateHierarchyExponents = []uint64{21, 18, 16, 13, 11, 9, 5}
|
||||
)
|
||||
|
||||
func StateHierarchyExponents() []uint64 {
|
||||
return stateHierarchyExponents
|
||||
}
|
||||
@@ -40,6 +40,12 @@ func NewWrappedExecutionData(v proto.Message) (interfaces.ExecutionData, error)
|
||||
case *enginev1.ExecutionBundleElectra:
|
||||
// note: no payload changes in electra so using deneb
|
||||
return WrappedExecutionPayloadDeneb(pbStruct.Payload)
|
||||
case *enginev1.ExecutionPayloadHeader:
|
||||
return WrappedExecutionPayloadHeader(pbStruct)
|
||||
case *enginev1.ExecutionPayloadHeaderCapella:
|
||||
return WrappedExecutionPayloadHeaderCapella(pbStruct)
|
||||
case *enginev1.ExecutionPayloadHeaderDeneb:
|
||||
return WrappedExecutionPayloadHeaderDeneb(pbStruct)
|
||||
default:
|
||||
return nil, ErrUnsupportedVersion
|
||||
}
|
||||
|
||||
33
consensus-types/hdiff/BUILD.bazel
Normal file
33
consensus-types/hdiff/BUILD.bazel
Normal file
@@ -0,0 +1,33 @@
|
||||
load("@prysm//tools/go:def.bzl", "go_library", "go_test")
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = ["state_diff.go"],
|
||||
importpath = "github.com/OffchainLabs/prysm/v6/consensus-types/hdiff",
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
"//beacon-chain/state:go_default_library",
|
||||
"//config/fieldparams:go_default_library",
|
||||
"//consensus-types/blocks:go_default_library",
|
||||
"//consensus-types/helpers:go_default_library",
|
||||
"//consensus-types/interfaces:go_default_library",
|
||||
"//consensus-types/primitives:go_default_library",
|
||||
"//proto/engine/v1:go_default_library",
|
||||
"//proto/prysm/v1alpha1:go_default_library",
|
||||
"//runtime/version:go_default_library",
|
||||
"@com_github_pkg_errors//:go_default_library",
|
||||
"@com_github_prysmaticlabs_fastssz//:go_default_library",
|
||||
"@com_github_sirupsen_logrus//:go_default_library",
|
||||
"@org_golang_google_protobuf//proto:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = ["state_diff_test.go"],
|
||||
embed = [":go_default_library"],
|
||||
deps = [
|
||||
"//testing/require:go_default_library",
|
||||
"//testing/util:go_default_library",
|
||||
],
|
||||
)
|
||||
1458
consensus-types/hdiff/state_diff.go
Normal file
1458
consensus-types/hdiff/state_diff.go
Normal file
File diff suppressed because it is too large
Load Diff
60
consensus-types/hdiff/state_diff_test.go
Normal file
60
consensus-types/hdiff/state_diff_test.go
Normal file
@@ -0,0 +1,60 @@
|
||||
package hdiff
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/OffchainLabs/prysm/v6/testing/require"
|
||||
"github.com/OffchainLabs/prysm/v6/testing/util"
|
||||
)
|
||||
|
||||
func Test_diffToState(t *testing.T) {
|
||||
source, _ := util.DeterministicGenesisStateElectra(t, 256)
|
||||
target := source.Copy()
|
||||
require.NoError(t, target.SetSlot(source.Slot()+1))
|
||||
hdiff, err := diffToState(source, target)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, hdiff.slot, target.Slot())
|
||||
require.Equal(t, hdiff.targetVersion, target.Version())
|
||||
}
|
||||
|
||||
func Test_kmpIndex(t *testing.T) {
|
||||
intSlice := make([]*int, 10)
|
||||
for i := 0; i < len(intSlice); i++ {
|
||||
intSlice[i] = new(int)
|
||||
*intSlice[i] = i
|
||||
}
|
||||
integerEquals := func(a, b *int) bool {
|
||||
if a == nil && b == nil {
|
||||
return true
|
||||
}
|
||||
if a == nil || b == nil {
|
||||
return false
|
||||
}
|
||||
return *a == *b
|
||||
}
|
||||
t.Run("integer entries match", func(t *testing.T) {
|
||||
source := []*int{intSlice[0], intSlice[1], intSlice[2], intSlice[3], intSlice[4]}
|
||||
target := []*int{intSlice[2], intSlice[3], intSlice[4], intSlice[5], intSlice[6], intSlice[7], nil}
|
||||
target = append(target, source...)
|
||||
require.Equal(t, 2, kmpIndex(len(source), target, integerEquals))
|
||||
})
|
||||
t.Run("integer entries skipped", func(t *testing.T) {
|
||||
source := []*int{intSlice[0], intSlice[1], intSlice[2], intSlice[3], intSlice[4]}
|
||||
target := []*int{intSlice[2], intSlice[3], intSlice[4], intSlice[0], intSlice[5], nil}
|
||||
target = append(target, source...)
|
||||
require.Equal(t, 2, kmpIndex(len(source), target, integerEquals))
|
||||
})
|
||||
t.Run("integer entries repetitions", func(t *testing.T) {
|
||||
source := []*int{intSlice[0], intSlice[1], intSlice[0], intSlice[0], intSlice[0]}
|
||||
target := []*int{intSlice[0], intSlice[0], intSlice[1], intSlice[2], intSlice[5], nil}
|
||||
target = append(target, source...)
|
||||
require.Equal(t, 3, kmpIndex(len(source), target, integerEquals))
|
||||
})
|
||||
t.Run("integer entries no match", func(t *testing.T) {
|
||||
source := []*int{intSlice[0], intSlice[1], intSlice[2], intSlice[3]}
|
||||
target := []*int{intSlice[4], intSlice[5], intSlice[6], nil}
|
||||
target = append(target, source...)
|
||||
require.Equal(t, len(source), kmpIndex(len(source), target, integerEquals))
|
||||
})
|
||||
|
||||
}
|
||||
9
consensus-types/helpers/BUILD.bazel
Normal file
9
consensus-types/helpers/BUILD.bazel
Normal file
@@ -0,0 +1,9 @@
|
||||
load("@prysm//tools/go:def.bzl", "go_library")
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = ["comparisons.go"],
|
||||
importpath = "github.com/OffchainLabs/prysm/v6/consensus-types/helpers",
|
||||
visibility = ["//visibility:public"],
|
||||
deps = ["//proto/prysm/v1alpha1:go_default_library"],
|
||||
)
|
||||
109
consensus-types/helpers/comparisons.go
Normal file
109
consensus-types/helpers/comparisons.go
Normal file
@@ -0,0 +1,109 @@
|
||||
package helpers
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
|
||||
ethpb "github.com/OffchainLabs/prysm/v6/proto/prysm/v1alpha1"
|
||||
)
|
||||
|
||||
func ForksEqual(s, t *ethpb.Fork) bool {
|
||||
if s == nil && t == nil {
|
||||
return true
|
||||
}
|
||||
if s == nil || t == nil {
|
||||
return false
|
||||
}
|
||||
if s.Epoch != t.Epoch {
|
||||
return false
|
||||
}
|
||||
if !bytes.Equal(s.PreviousVersion, t.PreviousVersion) {
|
||||
return false
|
||||
}
|
||||
return bytes.Equal(s.CurrentVersion, t.CurrentVersion)
|
||||
}
|
||||
|
||||
func BlockHeadersEqual(s, t *ethpb.BeaconBlockHeader) bool {
|
||||
if s == nil && t == nil {
|
||||
return true
|
||||
}
|
||||
if s == nil || t == nil {
|
||||
return false
|
||||
}
|
||||
if s.Slot != t.Slot {
|
||||
return false
|
||||
}
|
||||
if s.ProposerIndex != t.ProposerIndex {
|
||||
return false
|
||||
}
|
||||
if !bytes.Equal(s.ParentRoot, t.ParentRoot) {
|
||||
return false
|
||||
}
|
||||
if !bytes.Equal(s.StateRoot, t.StateRoot) {
|
||||
return false
|
||||
}
|
||||
return bytes.Equal(s.BodyRoot, t.BodyRoot)
|
||||
}
|
||||
|
||||
func Eth1DataEqual(s, t *ethpb.Eth1Data) bool {
|
||||
if s == nil && t == nil {
|
||||
return true
|
||||
}
|
||||
if s == nil || t == nil {
|
||||
return false
|
||||
}
|
||||
if !bytes.Equal(s.DepositRoot, t.DepositRoot) {
|
||||
return false
|
||||
}
|
||||
if s.DepositCount != t.DepositCount {
|
||||
return false
|
||||
}
|
||||
return bytes.Equal(s.BlockHash, t.BlockHash)
|
||||
}
|
||||
|
||||
func PendingDepositsEqual(s, t *ethpb.PendingDeposit) bool {
|
||||
if s == nil && t == nil {
|
||||
return true
|
||||
}
|
||||
if s == nil || t == nil {
|
||||
return false
|
||||
}
|
||||
if !bytes.Equal(s.PublicKey, t.PublicKey) {
|
||||
return false
|
||||
}
|
||||
if !bytes.Equal(s.WithdrawalCredentials, t.WithdrawalCredentials) {
|
||||
return false
|
||||
}
|
||||
if s.Amount != t.Amount {
|
||||
return false
|
||||
}
|
||||
if !bytes.Equal(s.Signature, t.Signature) {
|
||||
return false
|
||||
}
|
||||
return s.Slot == t.Slot
|
||||
}
|
||||
|
||||
func PendingPartialWithdrawalsEqual(s, t *ethpb.PendingPartialWithdrawal) bool {
|
||||
if s == nil && t == nil {
|
||||
return true
|
||||
}
|
||||
if s == nil || t == nil {
|
||||
return false
|
||||
}
|
||||
if s.Index != t.Index {
|
||||
return false
|
||||
}
|
||||
if s.Amount != t.Amount {
|
||||
return false
|
||||
}
|
||||
return s.WithdrawableEpoch == t.WithdrawableEpoch
|
||||
}
|
||||
|
||||
func PendingConsolidationsEqual(s, t *ethpb.PendingConsolidation) bool {
|
||||
if s == nil && t == nil {
|
||||
return true
|
||||
}
|
||||
if s == nil || t == nil {
|
||||
return false
|
||||
}
|
||||
return s.SourceIndex == t.SourceIndex && s.TargetIndex == t.TargetIndex
|
||||
}
|
||||
Reference in New Issue
Block a user