mirror of
https://github.com/OffchainLabs/prysm.git
synced 2026-01-09 15:37:56 -05:00
Add unit tests for migrateToColdHdiff
Add tests for the state diff migration path in MigrateToCold: - TestMigrateToColdHdiff_CanUpdateFinalizedInfo: verifies finalized info updates - TestMigrateToColdHdiff_SkipsSlotsNotInDiffTree: verifies non-diff-tree slots are skipped - TestMigrateToColdHdiff_NoOpWhenFinalizedSlotNotAdvanced: verifies no-op behavior The test helper InitStateDiffCacheForTesting is in a separate file with //go:build testing tag, ensuring it's excluded from production builds but available for tests via gotags = ["testing"] in Bazel. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -30,6 +30,7 @@ go_library(
|
||||
"state_diff.go",
|
||||
"state_diff_cache.go",
|
||||
"state_diff_helpers.go",
|
||||
"testing_helpers.go",
|
||||
"state_summary.go",
|
||||
"state_summary_cache.go",
|
||||
"utils.go",
|
||||
|
||||
37
beacon-chain/db/kv/testing_helpers.go
Normal file
37
beacon-chain/db/kv/testing_helpers.go
Normal file
@@ -0,0 +1,37 @@
|
||||
//go:build testing
|
||||
|
||||
package kv
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
|
||||
"go.etcd.io/bbolt"
|
||||
)
|
||||
|
||||
// InitStateDiffCacheForTesting initializes the state diff cache with the given offset.
|
||||
// This is intended for testing purposes when setting up state diff after database creation.
|
||||
// This file is only compiled when the "testing" build tag is set.
|
||||
func (s *Store) InitStateDiffCacheForTesting(offset uint64) error {
|
||||
// First, set the offset in the database.
|
||||
err := s.db.Update(func(tx *bbolt.Tx) error {
|
||||
bucket := tx.Bucket(stateDiffBucket)
|
||||
if bucket == nil {
|
||||
return bbolt.ErrBucketNotFound
|
||||
}
|
||||
|
||||
offsetBytes := make([]byte, 8)
|
||||
binary.LittleEndian.PutUint64(offsetBytes, offset)
|
||||
return bucket.Put([]byte("offset"), offsetBytes)
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Then create the state diff cache.
|
||||
sdCache, err := newStateDiffCache(s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.stateDiffCache = sdCache
|
||||
return nil
|
||||
}
|
||||
@@ -64,16 +64,20 @@ go_test(
|
||||
"setter_test.go",
|
||||
],
|
||||
embed = [":go_default_library"],
|
||||
gotags = ["testing"],
|
||||
deps = [
|
||||
"//beacon-chain/core/blocks:go_default_library",
|
||||
"//beacon-chain/core/helpers:go_default_library",
|
||||
"//beacon-chain/core/transition:go_default_library",
|
||||
"//beacon-chain/db:go_default_library",
|
||||
"//beacon-chain/db/kv:go_default_library",
|
||||
"//beacon-chain/db/testing:go_default_library",
|
||||
"//beacon-chain/forkchoice/doubly-linked-tree:go_default_library",
|
||||
"//beacon-chain/state:go_default_library",
|
||||
"//beacon-chain/state/state-native:go_default_library",
|
||||
"//beacon-chain/state/testing:go_default_library",
|
||||
"//cmd/beacon-chain/flags:go_default_library",
|
||||
"//config/features:go_default_library",
|
||||
"//config/params:go_default_library",
|
||||
"//consensus-types/blocks:go_default_library",
|
||||
"//consensus-types/blocks/testing:go_default_library",
|
||||
|
||||
@@ -4,8 +4,11 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/core/blocks"
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/db/kv"
|
||||
testDB "github.com/OffchainLabs/prysm/v7/beacon-chain/db/testing"
|
||||
doublylinkedtree "github.com/OffchainLabs/prysm/v7/beacon-chain/forkchoice/doubly-linked-tree"
|
||||
"github.com/OffchainLabs/prysm/v7/cmd/beacon-chain/flags"
|
||||
"github.com/OffchainLabs/prysm/v7/config/features"
|
||||
consensusblocks "github.com/OffchainLabs/prysm/v7/consensus-types/blocks"
|
||||
"github.com/OffchainLabs/prysm/v7/consensus-types/primitives"
|
||||
ethpb "github.com/OffchainLabs/prysm/v7/proto/prysm/v1alpha1"
|
||||
@@ -224,3 +227,170 @@ func TestMigrateToCold_ParallelCalls(t *testing.T) {
|
||||
assert.DeepEqual(t, [][32]byte{r7}, service.saveHotStateDB.blockRootsOfSavedStates, "Did not remove all saved hot state roots")
|
||||
require.LogsContain(t, hook, "Saved state in DB")
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Tests for migrateToColdHdiff (state diff migration)
|
||||
// =========================================================================
|
||||
|
||||
// setStateDiffExponents sets state diff exponents for testing.
|
||||
// Uses exponents [6, 5] which means:
|
||||
// - Level 0: Every 2^6 = 64 slots (full snapshot)
|
||||
// - Level 1: Every 2^5 = 32 slots (diff)
|
||||
func setStateDiffExponents() {
|
||||
globalFlags := flags.GlobalFlags{
|
||||
StateDiffExponents: []int{6, 5},
|
||||
}
|
||||
flags.Init(&globalFlags)
|
||||
}
|
||||
|
||||
// TestMigrateToColdHdiff_CanUpdateFinalizedInfo verifies that the migration
|
||||
// correctly updates finalized info when migrating to slots not in the diff tree.
|
||||
func TestMigrateToColdHdiff_CanUpdateFinalizedInfo(t *testing.T) {
|
||||
ctx := t.Context()
|
||||
// Set exponents and create DB first (without EnableStateDiff flag).
|
||||
setStateDiffExponents()
|
||||
beaconDB := testDB.SetupDB(t)
|
||||
// Initialize the state diff cache via the method on *kv.Store (not in interface).
|
||||
require.NoError(t, beaconDB.(*kv.Store).InitStateDiffCacheForTesting(0))
|
||||
// Now enable the feature flag.
|
||||
resetCfg := features.InitWithReset(&features.Flags{EnableStateDiff: true})
|
||||
defer resetCfg()
|
||||
service := New(beaconDB, doublylinkedtree.New())
|
||||
|
||||
beaconState, _ := util.DeterministicGenesisState(t, 32)
|
||||
genesisStateRoot, err := beaconState.HashTreeRoot(ctx)
|
||||
require.NoError(t, err)
|
||||
genesis := blocks.NewGenesisBlock(genesisStateRoot[:])
|
||||
util.SaveBlock(t, ctx, beaconDB, genesis)
|
||||
gRoot, err := genesis.Block.HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, beaconDB.SaveGenesisBlockRoot(ctx, gRoot))
|
||||
|
||||
// Put genesis state in epoch boundary cache so migrateToColdHdiff doesn't need to retrieve from DB.
|
||||
require.NoError(t, service.epochBoundaryStateCache.put(gRoot, beaconState))
|
||||
|
||||
// Set initial finalized info at genesis.
|
||||
service.finalizedInfo = &finalizedInfo{
|
||||
slot: 0,
|
||||
root: gRoot,
|
||||
state: beaconState,
|
||||
}
|
||||
|
||||
// Create finalized block at slot 10 (not in diff tree, so no intermediate states saved).
|
||||
finalizedState := beaconState.Copy()
|
||||
require.NoError(t, finalizedState.SetSlot(10))
|
||||
b := util.NewBeaconBlock()
|
||||
b.Block.Slot = 10
|
||||
fRoot, err := b.Block.HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
util.SaveBlock(t, ctx, beaconDB, b)
|
||||
require.NoError(t, service.epochBoundaryStateCache.put(fRoot, finalizedState))
|
||||
|
||||
require.NoError(t, service.MigrateToCold(ctx, fRoot))
|
||||
|
||||
// Verify finalized info is updated.
|
||||
assert.Equal(t, primitives.Slot(10), service.finalizedInfo.slot)
|
||||
assert.DeepEqual(t, fRoot, service.finalizedInfo.root)
|
||||
expectedHTR, err := finalizedState.HashTreeRoot(ctx)
|
||||
require.NoError(t, err)
|
||||
actualHTR, err := service.finalizedInfo.state.HashTreeRoot(ctx)
|
||||
require.NoError(t, err)
|
||||
assert.DeepEqual(t, expectedHTR, actualHTR)
|
||||
}
|
||||
|
||||
// TestMigrateToColdHdiff_SkipsSlotsNotInDiffTree verifies that the migration
|
||||
// skips slots that are not in the diff tree.
|
||||
func TestMigrateToColdHdiff_SkipsSlotsNotInDiffTree(t *testing.T) {
|
||||
hook := logTest.NewGlobal()
|
||||
ctx := t.Context()
|
||||
// Set exponents and create DB first (without EnableStateDiff flag).
|
||||
setStateDiffExponents()
|
||||
beaconDB := testDB.SetupDB(t)
|
||||
// Initialize the state diff cache via the method on *kv.Store (not in interface).
|
||||
require.NoError(t, beaconDB.(*kv.Store).InitStateDiffCacheForTesting(0))
|
||||
// Now enable the feature flag.
|
||||
resetCfg := features.InitWithReset(&features.Flags{EnableStateDiff: true})
|
||||
defer resetCfg()
|
||||
service := New(beaconDB, doublylinkedtree.New())
|
||||
|
||||
beaconState, pks := util.DeterministicGenesisState(t, 32)
|
||||
genesisStateRoot, err := beaconState.HashTreeRoot(ctx)
|
||||
require.NoError(t, err)
|
||||
genesis := blocks.NewGenesisBlock(genesisStateRoot[:])
|
||||
util.SaveBlock(t, ctx, beaconDB, genesis)
|
||||
gRoot, err := genesis.Block.HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, beaconDB.SaveGenesisBlockRoot(ctx, gRoot))
|
||||
|
||||
// Start from slot 1 to avoid slot 0 which is in the diff tree.
|
||||
service.finalizedInfo = &finalizedInfo{
|
||||
slot: 1,
|
||||
root: gRoot,
|
||||
state: beaconState,
|
||||
}
|
||||
|
||||
// Reset the log hook to ignore setup logs.
|
||||
hook.Reset()
|
||||
|
||||
// Create a block at slot 20 (NOT in diff tree with exponents [6,5]).
|
||||
b20, err := util.GenerateFullBlock(beaconState, pks, util.DefaultBlockGenConfig(), 20)
|
||||
require.NoError(t, err)
|
||||
r20, err := b20.Block.HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
util.SaveBlock(t, ctx, beaconDB, b20)
|
||||
require.NoError(t, beaconDB.SaveStateSummary(ctx, ðpb.StateSummary{Slot: 20, Root: r20[:]}))
|
||||
|
||||
// Put finalized state in cache.
|
||||
finalizedState := beaconState.Copy()
|
||||
require.NoError(t, finalizedState.SetSlot(20))
|
||||
require.NoError(t, service.epochBoundaryStateCache.put(r20, finalizedState))
|
||||
|
||||
require.NoError(t, service.MigrateToCold(ctx, r20))
|
||||
|
||||
// Verify NO states were saved during migration (slots 1-19 are not in diff tree).
|
||||
assert.LogsDoNotContain(t, hook, "Saved state in DB")
|
||||
}
|
||||
|
||||
// TestMigrateToColdHdiff_NoOpWhenFinalizedSlotNotAdvanced verifies that
|
||||
// migration is a no-op when the finalized slot has not advanced.
|
||||
func TestMigrateToColdHdiff_NoOpWhenFinalizedSlotNotAdvanced(t *testing.T) {
|
||||
ctx := t.Context()
|
||||
// Set exponents and create DB first (without EnableStateDiff flag).
|
||||
setStateDiffExponents()
|
||||
beaconDB := testDB.SetupDB(t)
|
||||
// Initialize the state diff cache via the method on *kv.Store (not in interface).
|
||||
require.NoError(t, beaconDB.(*kv.Store).InitStateDiffCacheForTesting(0))
|
||||
// Now enable the feature flag.
|
||||
resetCfg := features.InitWithReset(&features.Flags{EnableStateDiff: true})
|
||||
defer resetCfg()
|
||||
service := New(beaconDB, doublylinkedtree.New())
|
||||
|
||||
beaconState, _ := util.DeterministicGenesisState(t, 32)
|
||||
genesisStateRoot, err := beaconState.HashTreeRoot(ctx)
|
||||
require.NoError(t, err)
|
||||
genesis := blocks.NewGenesisBlock(genesisStateRoot[:])
|
||||
util.SaveBlock(t, ctx, beaconDB, genesis)
|
||||
gRoot, err := genesis.Block.HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, beaconDB.SaveGenesisBlockRoot(ctx, gRoot))
|
||||
|
||||
// Set finalized info already at slot 50.
|
||||
finalizedState := beaconState.Copy()
|
||||
require.NoError(t, finalizedState.SetSlot(50))
|
||||
service.finalizedInfo = &finalizedInfo{
|
||||
slot: 50,
|
||||
root: gRoot,
|
||||
state: finalizedState,
|
||||
}
|
||||
|
||||
// Create block at same slot 50.
|
||||
b := util.NewBeaconBlock()
|
||||
b.Block.Slot = 50
|
||||
fRoot, err := b.Block.HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
util.SaveBlock(t, ctx, beaconDB, b)
|
||||
require.NoError(t, service.epochBoundaryStateCache.put(fRoot, finalizedState))
|
||||
|
||||
// Migration should be a no-op (finalized slot not advancing).
|
||||
require.NoError(t, service.MigrateToCold(ctx, fRoot))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user