Files
prysm/beacon-chain/light-client/store_test.go
Preston Van Loon 27c009e7ff Tests: Add require.Eventually and fix a few test flakes (#16217)
**What type of PR is this?**

Other

**What does this PR do? Why is it needed?**

This is a better way to wait for a test condition to hit, rather than
time.Sleep.

**Which issues(s) does this PR fix?**


**Other notes for review**

**Acknowledgements**

- [x] I have read
[CONTRIBUTING.md](https://github.com/prysmaticlabs/prysm/blob/develop/CONTRIBUTING.md).
- [x] I have included a uniquely named [changelog fragment
file](https://github.com/prysmaticlabs/prysm/blob/develop/CONTRIBUTING.md#maintaining-changelogmd).
- [x] I have added a description with sufficient context for reviewers
to understand this PR.
- [x] I have tested that my changes work as expected and I added a
testing plan to the PR description (if applicable).
2026-01-06 18:20:27 +00:00

971 lines
45 KiB
Go

package light_client
import (
"context"
"testing"
"time"
"github.com/OffchainLabs/prysm/v7/async/event"
"github.com/OffchainLabs/prysm/v7/beacon-chain/db"
testDB "github.com/OffchainLabs/prysm/v7/beacon-chain/db/testing"
p2pTesting "github.com/OffchainLabs/prysm/v7/beacon-chain/p2p/testing"
"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"
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"
"github.com/OffchainLabs/prysm/v7/time/slots"
)
func TestLightClientStore(t *testing.T) {
params.SetupTestConfigCleanup(t)
cfg := params.BeaconConfig()
cfg.AltairForkEpoch = 1
cfg.BellatrixForkEpoch = 2
cfg.CapellaForkEpoch = 3
cfg.DenebForkEpoch = 4
cfg.ElectraForkEpoch = 5
params.OverrideBeaconConfig(cfg)
// Initialize the light client store
lcStore := NewLightClientStore(&p2pTesting.FakeP2P{}, new(event.Feed), testDB.SetupDB(t))
// Create test light client updates for Capella and Deneb
lCapella := util.NewTestLightClient(t, version.Capella)
opUpdateCapella, err := NewLightClientOptimisticUpdateFromBeaconState(lCapella.Ctx, lCapella.State, lCapella.Block, lCapella.AttestedState, lCapella.AttestedBlock)
require.NoError(t, err)
require.NotNil(t, opUpdateCapella, "OptimisticUpdateCapella is nil")
finUpdateCapella, err := NewLightClientFinalityUpdateFromBeaconState(lCapella.Ctx, lCapella.State, lCapella.Block, lCapella.AttestedState, lCapella.AttestedBlock, lCapella.FinalizedBlock)
require.NoError(t, err)
require.NotNil(t, finUpdateCapella, "FinalityUpdateCapella is nil")
lDeneb := util.NewTestLightClient(t, version.Deneb)
opUpdateDeneb, err := NewLightClientOptimisticUpdateFromBeaconState(lDeneb.Ctx, lDeneb.State, lDeneb.Block, lDeneb.AttestedState, lDeneb.AttestedBlock)
require.NoError(t, err)
require.NotNil(t, opUpdateDeneb, "OptimisticUpdateDeneb is nil")
finUpdateDeneb, err := NewLightClientFinalityUpdateFromBeaconState(lDeneb.Ctx, lDeneb.State, lDeneb.Block, lDeneb.AttestedState, lDeneb.AttestedBlock, lDeneb.FinalizedBlock)
require.NoError(t, err)
require.NotNil(t, finUpdateDeneb, "FinalityUpdateDeneb is nil")
// Initially the store should have nil values for both updates
require.IsNil(t, lcStore.LastFinalityUpdate(), "lastFinalityUpdate should be nil")
require.IsNil(t, lcStore.LastOptimisticUpdate(), "lastOptimisticUpdate should be nil")
// Set and get finality with Capella update. Optimistic update should be nil
lcStore.SetLastFinalityUpdate(finUpdateCapella, false)
require.Equal(t, finUpdateCapella, lcStore.LastFinalityUpdate(), "lastFinalityUpdate is wrong")
require.IsNil(t, lcStore.LastOptimisticUpdate(), "lastOptimisticUpdate should be nil")
// Set and get optimistic with Capella update. Finality update should be Capella
lcStore.SetLastOptimisticUpdate(opUpdateCapella, false)
require.Equal(t, opUpdateCapella, lcStore.LastOptimisticUpdate(), "lastOptimisticUpdate is wrong")
require.Equal(t, finUpdateCapella, lcStore.LastFinalityUpdate(), "lastFinalityUpdate is wrong")
// Set and get finality and optimistic with Deneb update
lcStore.SetLastFinalityUpdate(finUpdateDeneb, false)
lcStore.SetLastOptimisticUpdate(opUpdateDeneb, false)
require.Equal(t, finUpdateDeneb, lcStore.LastFinalityUpdate(), "lastFinalityUpdate is wrong")
require.Equal(t, opUpdateDeneb, lcStore.LastOptimisticUpdate(), "lastOptimisticUpdate is wrong")
}
func TestLightClientStore_SetLastFinalityUpdate(t *testing.T) {
p2p := p2pTesting.NewTestP2P(t)
lcStore := NewLightClientStore(p2p, new(event.Feed), testDB.SetupDB(t))
// update 0 with basic data and no supermajority following an empty lastFinalityUpdate - should save and broadcast
l0 := util.NewTestLightClient(t, version.Altair)
update0, err := NewLightClientFinalityUpdateFromBeaconState(l0.Ctx, l0.State, l0.Block, l0.AttestedState, l0.AttestedBlock, l0.FinalizedBlock)
require.NoError(t, err, "Failed to create light client finality update")
require.Equal(t, true, IsBetterFinalityUpdate(update0, lcStore.LastFinalityUpdate()), "update0 should be better than nil")
// update0 should be valid for broadcast - meaning it should be broadcasted
require.Equal(t, true, IsFinalityUpdateValidForBroadcast(update0, lcStore.LastFinalityUpdate()), "update0 should be valid for broadcast")
lcStore.SetLastFinalityUpdate(update0, true)
require.Equal(t, update0, lcStore.LastFinalityUpdate(), "lastFinalityUpdate should match the set value")
require.Eventually(t, func() bool {
return p2p.BroadcastCalled.Load()
}, time.Second, 10*time.Millisecond, "Broadcast should have been called after setting a new last finality update when previous is nil")
p2p.BroadcastCalled.Store(false) // Reset for next test
// update 1 with same finality slot, increased attested slot, and no supermajority - should save but not broadcast
l1 := util.NewTestLightClient(t, version.Altair, util.WithIncreasedAttestedSlot(1))
update1, err := NewLightClientFinalityUpdateFromBeaconState(l1.Ctx, l1.State, l1.Block, l1.AttestedState, l1.AttestedBlock, l1.FinalizedBlock)
require.NoError(t, err, "Failed to create light client finality update")
require.Equal(t, true, IsBetterFinalityUpdate(update1, update0), "update1 should be better than update0")
// update1 should not be valid for broadcast - meaning it should not be broadcasted
require.Equal(t, false, IsFinalityUpdateValidForBroadcast(update1, lcStore.LastFinalityUpdate()), "update1 should not be valid for broadcast")
lcStore.SetLastFinalityUpdate(update1, true)
require.Equal(t, update1, lcStore.LastFinalityUpdate(), "lastFinalityUpdate should match the set value")
time.Sleep(50 * time.Millisecond) // Wait briefly to verify broadcast is not called
require.Equal(t, false, p2p.BroadcastCalled.Load(), "Broadcast should not have been called after setting a new last finality update without supermajority")
p2p.BroadcastCalled.Store(false) // Reset for next test
// update 2 with same finality slot, increased attested slot, and supermajority - should save and broadcast
l2 := util.NewTestLightClient(t, version.Altair, util.WithIncreasedAttestedSlot(2), util.WithSupermajority(0))
update2, err := NewLightClientFinalityUpdateFromBeaconState(l2.Ctx, l2.State, l2.Block, l2.AttestedState, l2.AttestedBlock, l2.FinalizedBlock)
require.NoError(t, err, "Failed to create light client finality update")
require.Equal(t, true, IsBetterFinalityUpdate(update2, update1), "update2 should be better than update1")
// update2 should be valid for broadcast - meaning it should be broadcasted
require.Equal(t, true, IsFinalityUpdateValidForBroadcast(update2, lcStore.LastFinalityUpdate()), "update2 should be valid for broadcast")
lcStore.SetLastFinalityUpdate(update2, true)
require.Equal(t, update2, lcStore.LastFinalityUpdate(), "lastFinalityUpdate should match the set value")
require.Eventually(t, func() bool {
return p2p.BroadcastCalled.Load()
}, time.Second, 10*time.Millisecond, "Broadcast should have been called after setting a new last finality update with supermajority")
p2p.BroadcastCalled.Store(false) // Reset for next test
// update 3 with same finality slot, increased attested slot, and supermajority - should save but not broadcast
l3 := util.NewTestLightClient(t, version.Altair, util.WithIncreasedAttestedSlot(3), util.WithSupermajority(0))
update3, err := NewLightClientFinalityUpdateFromBeaconState(l3.Ctx, l3.State, l3.Block, l3.AttestedState, l3.AttestedBlock, l3.FinalizedBlock)
require.NoError(t, err, "Failed to create light client finality update")
require.Equal(t, true, IsBetterFinalityUpdate(update3, update2), "update3 should be better than update2")
// update3 should not be valid for broadcast - meaning it should not be broadcasted
require.Equal(t, false, IsFinalityUpdateValidForBroadcast(update3, lcStore.LastFinalityUpdate()), "update3 should not be valid for broadcast")
lcStore.SetLastFinalityUpdate(update3, true)
require.Equal(t, update3, lcStore.LastFinalityUpdate(), "lastFinalityUpdate should match the set value")
time.Sleep(50 * time.Millisecond) // Wait briefly to verify broadcast is not called
require.Equal(t, false, p2p.BroadcastCalled.Load(), "Broadcast should not have been when previous was already broadcast")
// update 4 with increased finality slot, increased attested slot, and supermajority - should save and broadcast
l4 := util.NewTestLightClient(t, version.Altair, util.WithIncreasedFinalizedSlot(1), util.WithIncreasedAttestedSlot(1), util.WithSupermajority(0))
update4, err := NewLightClientFinalityUpdateFromBeaconState(l4.Ctx, l4.State, l4.Block, l4.AttestedState, l4.AttestedBlock, l4.FinalizedBlock)
require.NoError(t, err, "Failed to create light client finality update")
require.Equal(t, true, IsBetterFinalityUpdate(update4, update3), "update4 should be better than update3")
// update4 should be valid for broadcast - meaning it should be broadcasted
require.Equal(t, true, IsFinalityUpdateValidForBroadcast(update4, lcStore.LastFinalityUpdate()), "update4 should be valid for broadcast")
lcStore.SetLastFinalityUpdate(update4, true)
require.Equal(t, update4, lcStore.LastFinalityUpdate(), "lastFinalityUpdate should match the set value")
require.Eventually(t, func() bool {
return p2p.BroadcastCalled.Load()
}, time.Second, 10*time.Millisecond, "Broadcast should have been called after a new finality update with increased finality slot")
p2p.BroadcastCalled.Store(false) // Reset for next test
// update 5 with the same new finality slot, increased attested slot, and supermajority - should save but not broadcast
l5 := util.NewTestLightClient(t, version.Altair, util.WithIncreasedFinalizedSlot(1), util.WithIncreasedAttestedSlot(2), util.WithSupermajority(0))
update5, err := NewLightClientFinalityUpdateFromBeaconState(l5.Ctx, l5.State, l5.Block, l5.AttestedState, l5.AttestedBlock, l5.FinalizedBlock)
require.NoError(t, err, "Failed to create light client finality update")
require.Equal(t, true, IsBetterFinalityUpdate(update5, update4), "update5 should be better than update4")
// update5 should not be valid for broadcast - meaning it should not be broadcasted
require.Equal(t, false, IsFinalityUpdateValidForBroadcast(update5, lcStore.LastFinalityUpdate()), "update5 should not be valid for broadcast")
lcStore.SetLastFinalityUpdate(update5, true)
require.Equal(t, update5, lcStore.LastFinalityUpdate(), "lastFinalityUpdate should match the set value")
time.Sleep(50 * time.Millisecond) // Wait briefly to verify broadcast is not called
require.Equal(t, false, p2p.BroadcastCalled.Load(), "Broadcast should not have been called when previous was already broadcast with supermajority")
// update 6 with the same new finality slot, increased attested slot, and no supermajority - should save but not broadcast
l6 := util.NewTestLightClient(t, version.Altair, util.WithIncreasedFinalizedSlot(1), util.WithIncreasedAttestedSlot(3))
update6, err := NewLightClientFinalityUpdateFromBeaconState(l6.Ctx, l6.State, l6.Block, l6.AttestedState, l6.AttestedBlock, l6.FinalizedBlock)
require.NoError(t, err, "Failed to create light client finality update")
require.Equal(t, true, IsBetterFinalityUpdate(update6, update5), "update6 should be better than update5")
// update6 should not be valid for broadcast - meaning it should not be broadcasted
require.Equal(t, false, IsFinalityUpdateValidForBroadcast(update6, lcStore.LastFinalityUpdate()), "update6 should not be valid for broadcast")
lcStore.SetLastFinalityUpdate(update6, true)
require.Equal(t, update6, lcStore.LastFinalityUpdate(), "lastFinalityUpdate should match the set value")
time.Sleep(50 * time.Millisecond) // Wait briefly to verify broadcast is not called
require.Equal(t, false, p2p.BroadcastCalled.Load(), "Broadcast should not have been called when previous was already broadcast with supermajority")
}
func TestLightClientStore_SaveLCData(t *testing.T) {
t.Run("no parent in cache or db - new is head", func(t *testing.T) {
db := testDB.SetupDB(t)
s := NewLightClientStore(&p2pTesting.FakeP2P{}, new(event.Feed), db)
require.NotNil(t, s)
l := util.NewTestLightClient(t, version.Altair)
blkRoot, err := l.Block.Block().HashTreeRoot()
require.NoError(t, err)
require.NoError(t, s.SaveLCData(l.Ctx, l.State, l.Block, l.AttestedState, l.AttestedBlock, l.FinalizedBlock, blkRoot), "Failed to save light client data")
update, err := NewLightClientUpdateFromBeaconState(l.Ctx, l.State, l.Block, l.AttestedState, l.AttestedBlock, l.FinalizedBlock)
require.NoError(t, err)
finalityUpdate, err := NewLightClientFinalityUpdateFromBeaconState(l.Ctx, l.State, l.Block, l.AttestedState, l.AttestedBlock, l.FinalizedBlock)
require.NoError(t, err)
optimisticUpdate, err := NewLightClientOptimisticUpdateFromBeaconState(l.Ctx, l.State, l.Block, l.AttestedState, l.AttestedBlock)
require.NoError(t, err)
attstedBlkRoot, err := l.AttestedBlock.Block().HashTreeRoot()
require.NoError(t, err)
require.DeepEqual(t, finalityUpdate, s.lastFinalityUpdate, "Expected to find the last finality update in the store")
require.DeepEqual(t, optimisticUpdate, s.lastOptimisticUpdate, "Expected to find the last optimistic update in the store")
require.DeepEqual(t, update, s.cache.items[attstedBlkRoot].bestUpdate, "Expected to find the update in the non-finality cache")
require.DeepEqual(t, finalityUpdate, s.cache.items[attstedBlkRoot].bestFinalityUpdate, "Expected to find the finality update in the non-finality cache")
})
t.Run("no parent in cache or db - new not head", func(t *testing.T) {
db := testDB.SetupDB(t)
s := NewLightClientStore(&p2pTesting.FakeP2P{}, new(event.Feed), db)
require.NotNil(t, s)
l := util.NewTestLightClient(t, version.Altair)
blkRoot, err := l.FinalizedBlock.Block().HashTreeRoot()
require.NoError(t, err)
require.NoError(t, s.SaveLCData(l.Ctx, l.State, l.Block, l.AttestedState, l.AttestedBlock, l.FinalizedBlock, blkRoot), "Failed to save light client data")
update, err := NewLightClientUpdateFromBeaconState(l.Ctx, l.State, l.Block, l.AttestedState, l.AttestedBlock, l.FinalizedBlock)
require.NoError(t, err)
finalityUpdate, err := NewLightClientFinalityUpdateFromBeaconState(l.Ctx, l.State, l.Block, l.AttestedState, l.AttestedBlock, l.FinalizedBlock)
require.NoError(t, err)
optimisticUpdate, err := NewLightClientOptimisticUpdateFromBeaconState(l.Ctx, l.State, l.Block, l.AttestedState, l.AttestedBlock)
require.NoError(t, err)
attstedBlkRoot, err := l.AttestedBlock.Block().HashTreeRoot()
require.NoError(t, err)
require.IsNil(t, s.lastFinalityUpdate, "Expected to not find the last finality update in the store since the block is not head")
require.DeepEqual(t, optimisticUpdate, s.lastOptimisticUpdate, "Expected to find the last optimistic update in the store")
require.DeepEqual(t, update, s.cache.items[attstedBlkRoot].bestUpdate, "Expected to find the update in the non-finality cache")
require.DeepEqual(t, finalityUpdate, s.cache.items[attstedBlkRoot].bestFinalityUpdate, "Expected to find the finality update in the non-finality cache")
})
t.Run("parent in db", func(t *testing.T) {
db := testDB.SetupDB(t)
s := NewLightClientStore(&p2pTesting.FakeP2P{}, new(event.Feed), db)
require.NotNil(t, s)
l := util.NewTestLightClient(t, version.Altair)
// save an update for this period in db
period := slots.SyncCommitteePeriod(slots.ToEpoch(l.AttestedBlock.Block().Slot()))
update, err := NewLightClientUpdateFromBeaconState(l.Ctx, l.State, l.Block, l.AttestedState, l.AttestedBlock, l.FinalizedBlock)
require.NoError(t, err)
require.NoError(t, db.SaveLightClientUpdate(l.Ctx, period, update), "Failed to save light client update in db")
l2 := util.NewTestLightClient(t, version.Altair, util.WithIncreasedAttestedSlot(1), util.WithSupermajority(0)) // updates from this setup should be all better
blkRoot, err := l2.Block.Block().HashTreeRoot()
require.NoError(t, err)
require.NoError(t, s.SaveLCData(l2.Ctx, l2.State, l2.Block, l2.AttestedState, l2.AttestedBlock, l2.FinalizedBlock, blkRoot), "Failed to save light client data")
update, err = NewLightClientUpdateFromBeaconState(l2.Ctx, l2.State, l2.Block, l2.AttestedState, l2.AttestedBlock, l2.FinalizedBlock)
require.NoError(t, err)
finalityUpdate, err := NewLightClientFinalityUpdateFromBeaconState(l2.Ctx, l2.State, l2.Block, l2.AttestedState, l2.AttestedBlock, l2.FinalizedBlock)
require.NoError(t, err)
optimisticUpdate, err := NewLightClientOptimisticUpdateFromBeaconState(l2.Ctx, l2.State, l2.Block, l2.AttestedState, l2.AttestedBlock)
require.NoError(t, err)
attstedBlkRoot, err := l2.AttestedBlock.Block().HashTreeRoot()
require.NoError(t, err)
require.DeepEqual(t, finalityUpdate, s.lastFinalityUpdate, "Expected to find the last finality update in the store")
require.DeepEqual(t, optimisticUpdate, s.lastOptimisticUpdate, "Expected to find the last optimistic update in the store")
require.DeepEqual(t, update, s.cache.items[attstedBlkRoot].bestUpdate, "Expected to find the update in the non-finality cache")
require.DeepEqual(t, finalityUpdate, s.cache.items[attstedBlkRoot].bestFinalityUpdate, "Expected to find the finality update in the non-finality cache")
})
t.Run("parent in cache", func(t *testing.T) {
db := testDB.SetupDB(t)
s := NewLightClientStore(&p2pTesting.FakeP2P{}, new(event.Feed), db)
require.NotNil(t, s)
l := util.NewTestLightClient(t, version.Altair)
l2 := util.NewTestLightClient(t, version.Altair, util.WithIncreasedAttestedSlot(1), util.WithSupermajority(0)) // updates from this setup should be all better
// save the cache item for this period in cache
period := slots.SyncCommitteePeriod(slots.ToEpoch(l.AttestedBlock.Block().Slot()))
update, err := NewLightClientUpdateFromBeaconState(l.Ctx, l.State, l.Block, l.AttestedState, l.AttestedBlock, l.FinalizedBlock)
require.NoError(t, err)
finalityUpdate, err := NewLightClientFinalityUpdateFromBeaconState(l.Ctx, l.State, l.Block, l.AttestedState, l.AttestedBlock, l.FinalizedBlock)
require.NoError(t, err)
item := &cacheItem{
period: period,
bestUpdate: update,
bestFinalityUpdate: finalityUpdate,
}
attestedBlockRoot := l2.AttestedBlock.Block().ParentRoot() // we want this item to be the parent of the new block
s.cache.items[attestedBlockRoot] = item
blkRoot, err := l2.Block.Block().HashTreeRoot()
require.NoError(t, err)
require.NoError(t, s.SaveLCData(l2.Ctx, l2.State, l2.Block, l2.AttestedState, l2.AttestedBlock, l2.FinalizedBlock, blkRoot), "Failed to save light client data")
update, err = NewLightClientUpdateFromBeaconState(l2.Ctx, l2.State, l2.Block, l2.AttestedState, l2.AttestedBlock, l2.FinalizedBlock)
require.NoError(t, err)
finalityUpdate, err = NewLightClientFinalityUpdateFromBeaconState(l2.Ctx, l2.State, l2.Block, l2.AttestedState, l2.AttestedBlock, l2.FinalizedBlock)
require.NoError(t, err)
optimisticUpdate, err := NewLightClientOptimisticUpdateFromBeaconState(l2.Ctx, l2.State, l2.Block, l2.AttestedState, l2.AttestedBlock)
require.NoError(t, err)
attstedBlkRoot, err := l2.AttestedBlock.Block().HashTreeRoot()
require.NoError(t, err)
require.DeepEqual(t, finalityUpdate, s.lastFinalityUpdate, "Expected to find the last finality update in the store")
require.DeepEqual(t, optimisticUpdate, s.lastOptimisticUpdate, "Expected to find the last optimistic update in the store")
require.DeepEqual(t, update, s.cache.items[attstedBlkRoot].bestUpdate, "Expected to find the update in the non-finality cache")
require.DeepEqual(t, finalityUpdate, s.cache.items[attstedBlkRoot].bestFinalityUpdate, "Expected to find the finality update in the non-finality cache")
})
t.Run("parent in the previous period", func(t *testing.T) {
db := testDB.SetupDB(t)
s := NewLightClientStore(&p2pTesting.FakeP2P{}, new(event.Feed), db)
require.NotNil(t, s)
l := util.NewTestLightClient(t, version.Altair)
l2 := util.NewTestLightClient(t, version.Bellatrix, util.WithIncreasedAttestedSlot(1), util.WithSupermajority(0)) // updates from this setup should be all better
// save the cache item for this period1 in cache
period1 := slots.SyncCommitteePeriod(slots.ToEpoch(l.AttestedBlock.Block().Slot()))
update, err := NewLightClientUpdateFromBeaconState(l.Ctx, l.State, l.Block, l.AttestedState, l.AttestedBlock, l.FinalizedBlock)
require.NoError(t, err)
finalityUpdate, err := NewLightClientFinalityUpdateFromBeaconState(l.Ctx, l.State, l.Block, l.AttestedState, l.AttestedBlock, l.FinalizedBlock)
require.NoError(t, err)
item := &cacheItem{
period: period1,
bestUpdate: update,
bestFinalityUpdate: finalityUpdate,
}
attestedBlockRoot := l2.AttestedBlock.Block().ParentRoot() // we want this item to be the parent of the new block
s.cache.items[attestedBlockRoot] = item
blkRoot, err := l2.Block.Block().HashTreeRoot()
require.NoError(t, err)
require.NoError(t, s.SaveLCData(l2.Ctx, l2.State, l2.Block, l2.AttestedState, l2.AttestedBlock, l2.FinalizedBlock, blkRoot), "Failed to save light client data")
update, err = NewLightClientUpdateFromBeaconState(l2.Ctx, l2.State, l2.Block, l2.AttestedState, l2.AttestedBlock, l2.FinalizedBlock)
require.NoError(t, err)
finalityUpdate, err = NewLightClientFinalityUpdateFromBeaconState(l2.Ctx, l2.State, l2.Block, l2.AttestedState, l2.AttestedBlock, l2.FinalizedBlock)
require.NoError(t, err)
optimisticUpdate, err := NewLightClientOptimisticUpdateFromBeaconState(l2.Ctx, l2.State, l2.Block, l2.AttestedState, l2.AttestedBlock)
require.NoError(t, err)
attstedBlkRoot, err := l2.AttestedBlock.Block().HashTreeRoot()
require.NoError(t, err)
require.DeepEqual(t, finalityUpdate, s.lastFinalityUpdate, "Expected to find the last finality update in the store")
require.DeepEqual(t, optimisticUpdate, s.lastOptimisticUpdate, "Expected to find the last optimistic update in the store")
require.DeepEqual(t, update, s.cache.items[attstedBlkRoot].bestUpdate, "Expected to find the update in the non-finality cache")
require.DeepEqual(t, finalityUpdate, s.cache.items[attstedBlkRoot].bestFinalityUpdate, "Expected to find the finality update in the non-finality cache")
})
}
func TestLightClientStore_MigrateToCold(t *testing.T) {
// This tests the scenario where chain advances but the cache is empty.
// It should see that there is nothing in the cache to migrate and just update the tail to the new finalized root.
t.Run("empty cache", func(t *testing.T) {
beaconDB := testDB.SetupDB(t)
ctx := context.Background()
finalizedBlockRoot, _ := saveInitialFinalizedCheckpointData(t, ctx, beaconDB)
s := NewLightClientStore(&p2pTesting.FakeP2P{}, new(event.Feed), beaconDB)
require.NotNil(t, s)
for i := range 3 {
newBlock := util.NewBeaconBlock()
newBlock.Block.Slot = primitives.Slot(32 + uint64(i))
newBlock.Block.ParentRoot = finalizedBlockRoot[:]
signedNewBlock, err := blocks.NewSignedBeaconBlock(newBlock)
require.NoError(t, err)
blockRoot, err := signedNewBlock.Block().HashTreeRoot()
require.NoError(t, err)
require.NoError(t, beaconDB.SaveBlock(ctx, signedNewBlock))
finalizedBlockRoot = blockRoot
}
err := s.MigrateToCold(ctx, finalizedBlockRoot)
require.NoError(t, err)
require.Equal(t, 0, len(s.cache.items))
})
// This tests the scenario where chain advances but the CANONICAL cache is empty.
// It should see that there is nothing in the canonical cache to migrate and just update the tail to the new finalized root AND delete anything non-canonical.
t.Run("non canonical cache", func(t *testing.T) {
beaconDB := testDB.SetupDB(t)
ctx := context.Background()
finalizedBlockRoot, _ := saveInitialFinalizedCheckpointData(t, ctx, beaconDB)
s := NewLightClientStore(&p2pTesting.FakeP2P{}, new(event.Feed), beaconDB)
require.NotNil(t, s)
for i := range 3 {
newBlock := util.NewBeaconBlock()
newBlock.Block.Slot = primitives.Slot(32 + uint64(i))
newBlock.Block.ParentRoot = finalizedBlockRoot[:]
signedNewBlock, err := blocks.NewSignedBeaconBlock(newBlock)
require.NoError(t, err)
blockRoot, err := signedNewBlock.Block().HashTreeRoot()
require.NoError(t, err)
require.NoError(t, beaconDB.SaveBlock(ctx, signedNewBlock))
finalizedBlockRoot = blockRoot
}
// Add a non-canonical item to the cache
cacheItem := &cacheItem{
period: 0,
slot: 33,
}
nonCanonicalBlockRoot := [32]byte{1, 2, 3, 4}
s.cache.items[nonCanonicalBlockRoot] = cacheItem
require.Equal(t, 1, len(s.cache.items))
err := s.MigrateToCold(ctx, finalizedBlockRoot)
require.NoError(t, err)
require.Equal(t, 0, len(s.cache.items), "Expected the non-canonical item in the cache to be deleted")
})
// db has update - cache has both canonical and non-canonical items.
// should update the update in db and delete cache.
t.Run("mixed cache - finality immediately after cache", func(t *testing.T) {
beaconDB := testDB.SetupDB(t)
ctx := context.Background()
finalizedBlockRoot, _ := saveInitialFinalizedCheckpointData(t, ctx, beaconDB)
require.NoError(t, beaconDB.SaveHeadBlockRoot(ctx, finalizedBlockRoot))
s := NewLightClientStore(&p2pTesting.FakeP2P{}, new(event.Feed), beaconDB)
require.NotNil(t, s)
// Save an update for this period in db
l := util.NewTestLightClient(t, version.Altair)
update, err := NewLightClientUpdateFromBeaconState(l.Ctx, l.State, l.Block, l.AttestedState, l.AttestedBlock, l.FinalizedBlock)
require.NoError(t, err)
period := slots.SyncCommitteePeriod(slots.ToEpoch(l.AttestedBlock.Block().Slot()))
require.NoError(t, beaconDB.SaveLightClientUpdate(ctx, period, update))
lastBlockRoot := finalizedBlockRoot
lastAttestedRoot := finalizedBlockRoot
lastUpdate := update
for i := 1; i < 4; i++ {
l = util.NewTestLightClient(t, version.Altair, util.WithIncreasedAttestedSlot(uint64(i)), util.WithSupermajority(uint64(i)), util.WithAttestedParentRoot(lastAttestedRoot))
require.NoError(t, beaconDB.SaveBlock(ctx, l.Block))
require.NoError(t, s.SaveLCData(ctx, l.State, l.Block, l.AttestedState, l.AttestedBlock, l.FinalizedBlock, [32]byte{1}))
lastBlockRoot, err = l.Block.Block().HashTreeRoot()
require.NoError(t, err)
lastAttestedRoot, err = l.AttestedBlock.Block().HashTreeRoot()
require.NoError(t, err)
update, err = NewLightClientUpdateFromBeaconState(l.Ctx, l.State, l.Block, l.AttestedState, l.AttestedBlock, l.FinalizedBlock)
require.NoError(t, err)
lastUpdate = update
}
require.Equal(t, 3, len(s.cache.items))
// Add a non-canonical item to the cache
cacheItem := &cacheItem{
period: 0,
slot: 33,
}
nonCanonicalBlockRoot := [32]byte{1, 2, 3, 4}
s.cache.items[nonCanonicalBlockRoot] = cacheItem
require.Equal(t, 4, len(s.cache.items))
err = s.MigrateToCold(ctx, lastBlockRoot)
require.NoError(t, err)
require.Equal(t, 0, len(s.cache.items), "Expected the non-canonical item in the cache to be deleted")
u, err := beaconDB.LightClientUpdate(ctx, period)
require.NoError(t, err)
require.NotNil(t, u)
require.DeepEqual(t, lastUpdate, u)
})
// db has update - cache has both canonical and non-canonical items. finalized height is in the middle.
// should update the update in db and delete items in cache before finalized slot.
t.Run("mixed cache - finality middle of cache", func(t *testing.T) {
beaconDB := testDB.SetupDB(t)
ctx := context.Background()
finalizedBlockRoot, _ := saveInitialFinalizedCheckpointData(t, ctx, beaconDB)
require.NoError(t, beaconDB.SaveHeadBlockRoot(ctx, finalizedBlockRoot))
s := NewLightClientStore(&p2pTesting.FakeP2P{}, new(event.Feed), beaconDB)
require.NotNil(t, s)
// Save an update for this period in db
l := util.NewTestLightClient(t, version.Altair)
update, err := NewLightClientUpdateFromBeaconState(l.Ctx, l.State, l.Block, l.AttestedState, l.AttestedBlock, l.FinalizedBlock)
require.NoError(t, err)
period := slots.SyncCommitteePeriod(slots.ToEpoch(l.AttestedBlock.Block().Slot()))
require.NoError(t, beaconDB.SaveLightClientUpdate(ctx, period, update))
lastBlockRoot := finalizedBlockRoot
lastUpdate := update
lastAttestedRoot := [32]byte{}
for i := 1; i < 4; i++ {
l = util.NewTestLightClient(t, version.Altair, util.WithIncreasedAttestedSlot(uint64(i)), util.WithSupermajority(uint64(i)), util.WithAttestedParentRoot(lastAttestedRoot))
require.NoError(t, beaconDB.SaveBlock(ctx, l.Block))
require.NoError(t, s.SaveLCData(ctx, l.State, l.Block, l.AttestedState, l.AttestedBlock, l.FinalizedBlock, [32]byte{1}))
root, err := l.Block.Block().HashTreeRoot()
require.NoError(t, err)
lastBlockRoot = root
update, err = NewLightClientUpdateFromBeaconState(l.Ctx, l.State, l.Block, l.AttestedState, l.AttestedBlock, l.FinalizedBlock)
require.NoError(t, err)
lastUpdate = update
lastAttestedRoot, err = l.AttestedBlock.Block().HashTreeRoot()
require.NoError(t, err)
}
require.Equal(t, 3, len(s.cache.items))
// Add a non-canonical item to the cache
cacheItem := &cacheItem{
period: 0,
slot: 33,
}
nonCanonicalBlockRoot := [32]byte{1, 2, 3, 4}
s.cache.items[nonCanonicalBlockRoot] = cacheItem
require.Equal(t, 4, len(s.cache.items))
for i := 4; i < 7; i++ {
l = util.NewTestLightClient(t, version.Altair, util.WithIncreasedAttestedSlot(uint64(i)), util.WithSupermajority(0), util.WithAttestedParentRoot(lastAttestedRoot))
require.NoError(t, beaconDB.SaveBlock(ctx, l.Block))
require.NoError(t, s.SaveLCData(ctx, l.State, l.Block, l.AttestedState, l.AttestedBlock, l.FinalizedBlock, [32]byte{1}))
lastAttestedRoot, err = l.AttestedBlock.Block().HashTreeRoot()
require.NoError(t, err)
}
require.Equal(t, 7, len(s.cache.items))
err = s.MigrateToCold(ctx, lastBlockRoot)
require.NoError(t, err)
require.Equal(t, 3, len(s.cache.items), "Expected the non-canonical item in the cache to be deleted")
u, err := beaconDB.LightClientUpdate(ctx, period)
require.NoError(t, err)
require.NotNil(t, u)
require.DeepEqual(t, lastUpdate, u)
})
// we have multiple periods in the cache before finalization happens. we expect all of them to be saved in db.
t.Run("finality after multiple periods in cache", func(t *testing.T) {
beaconDB := testDB.SetupDB(t)
ctx := context.Background()
cfg := params.BeaconConfig().Copy()
cfg.EpochsPerSyncCommitteePeriod = 1
params.OverrideBeaconConfig(cfg)
finalizedBlockRoot, _ := saveInitialFinalizedCheckpointData(t, ctx, beaconDB)
require.NoError(t, beaconDB.SaveHeadBlockRoot(ctx, finalizedBlockRoot))
s := NewLightClientStore(&p2pTesting.FakeP2P{}, new(event.Feed), beaconDB)
require.NotNil(t, s)
// Save an update for this period1 in db
l := util.NewTestLightClient(t, version.Altair)
update, err := NewLightClientUpdateFromBeaconState(l.Ctx, l.State, l.Block, l.AttestedState, l.AttestedBlock, l.FinalizedBlock)
require.NoError(t, err)
period1 := slots.SyncCommitteePeriod(slots.ToEpoch(l.AttestedBlock.Block().Slot()))
require.NoError(t, beaconDB.SaveLightClientUpdate(ctx, period1, update))
lastBlockRoot := finalizedBlockRoot
lastUpdatePeriod1 := update
lastAttestedRoot := [32]byte{}
for i := 1; i < 4; i++ {
l = util.NewTestLightClient(t, version.Altair, util.WithIncreasedAttestedSlot(uint64(i)), util.WithSupermajority(uint64(i)), util.WithAttestedParentRoot(lastAttestedRoot))
require.NoError(t, beaconDB.SaveBlock(ctx, l.Block))
require.NoError(t, s.SaveLCData(ctx, l.State, l.Block, l.AttestedState, l.AttestedBlock, l.FinalizedBlock, [32]byte{1}))
root, err := l.Block.Block().HashTreeRoot()
require.NoError(t, err)
lastBlockRoot = root
lastUpdatePeriod1, err = NewLightClientUpdateFromBeaconState(l.Ctx, l.State, l.Block, l.AttestedState, l.AttestedBlock, l.FinalizedBlock)
require.NoError(t, err)
lastAttestedRoot, err = l.AttestedBlock.Block().HashTreeRoot()
require.NoError(t, err)
}
period2 := period1
var lastUpdatePeriod2 interfaces.LightClientUpdate
for i := 1; i < 4; i++ {
l = util.NewTestLightClient(t, version.Altair, util.WithIncreasedAttestedSlot(uint64(i)+33), util.WithSupermajority(uint64(i)), util.WithAttestedParentRoot(lastAttestedRoot))
require.NoError(t, beaconDB.SaveBlock(ctx, l.Block))
require.NoError(t, s.SaveLCData(ctx, l.State, l.Block, l.AttestedState, l.AttestedBlock, l.FinalizedBlock, [32]byte{1}))
root, err := l.Block.Block().HashTreeRoot()
require.NoError(t, err)
lastBlockRoot = root
lastUpdatePeriod2, err = NewLightClientUpdateFromBeaconState(l.Ctx, l.State, l.Block, l.AttestedState, l.AttestedBlock, l.FinalizedBlock)
require.NoError(t, err)
lastAttestedRoot, err = l.AttestedBlock.Block().HashTreeRoot()
require.NoError(t, err)
period2 = slots.SyncCommitteePeriod(slots.ToEpoch(l.AttestedBlock.Block().Slot()))
}
require.Equal(t, 6, len(s.cache.items))
// Add a non-canonical item to the cache
cacheItem := &cacheItem{
period: 0,
slot: 33,
}
nonCanonicalBlockRoot := [32]byte{1, 2, 3, 4}
s.cache.items[nonCanonicalBlockRoot] = cacheItem
require.Equal(t, 7, len(s.cache.items))
for i := 4; i < 7; i++ {
l = util.NewTestLightClient(t, version.Altair, util.WithIncreasedAttestedSlot(uint64(i)+33), util.WithSupermajority(0), util.WithAttestedParentRoot(lastAttestedRoot))
require.NoError(t, beaconDB.SaveBlock(ctx, l.Block))
require.NoError(t, s.SaveLCData(ctx, l.State, l.Block, l.AttestedState, l.AttestedBlock, l.FinalizedBlock, [32]byte{1}))
lastAttestedRoot, err = l.AttestedBlock.Block().HashTreeRoot()
require.NoError(t, err)
}
require.Equal(t, 10, len(s.cache.items))
err = s.MigrateToCold(ctx, lastBlockRoot)
require.NoError(t, err)
require.Equal(t, 3, len(s.cache.items), "Expected the non-canonical item in the cache to be deleted")
u, err := beaconDB.LightClientUpdate(ctx, period2)
require.NoError(t, err)
require.NotNil(t, u)
require.DeepEqual(t, lastUpdatePeriod2, u)
u, err = beaconDB.LightClientUpdate(ctx, period1)
require.NoError(t, err)
require.NotNil(t, u)
require.DeepEqual(t, lastUpdatePeriod1, u)
})
}
func saveInitialFinalizedCheckpointData(t *testing.T, ctx context.Context, beaconDB db.Database) ([32]byte, interfaces.SignedBeaconBlock) {
genesis := util.NewBeaconBlock()
genesisRoot, err := genesis.Block.HashTreeRoot()
require.NoError(t, err)
require.NoError(t, beaconDB.SaveGenesisBlockRoot(ctx, genesisRoot))
util.SaveBlock(t, ctx, beaconDB, genesis)
genesisState, err := util.NewBeaconState()
require.NoError(t, err)
require.NoError(t, beaconDB.SaveState(ctx, genesisState, genesisRoot))
finalizedState, err := util.NewBeaconState()
require.NoError(t, err)
finalizedBlock := util.NewBeaconBlock()
finalizedBlock.Block.Slot = 32
finalizedBlock.Block.ParentRoot = genesisRoot[:]
signedFinalizedBlock, err := blocks.NewSignedBeaconBlock(finalizedBlock)
require.NoError(t, err)
finalizedBlockHeader, err := signedFinalizedBlock.Header()
require.NoError(t, err)
require.NoError(t, finalizedState.SetLatestBlockHeader(finalizedBlockHeader.Header))
finalizedStateRoot, err := finalizedState.HashTreeRoot(ctx)
require.NoError(t, err)
finalizedBlock.Block.StateRoot = finalizedStateRoot[:]
signedFinalizedBlock, err = blocks.NewSignedBeaconBlock(finalizedBlock)
require.NoError(t, err)
finalizedBlockRoot, err := signedFinalizedBlock.Block().HashTreeRoot()
require.NoError(t, err)
cp := ethpb.Checkpoint{
Epoch: 1,
Root: finalizedBlockRoot[:],
}
require.NoError(t, beaconDB.SaveBlock(ctx, signedFinalizedBlock))
require.NoError(t, beaconDB.SaveState(ctx, finalizedState, finalizedBlockRoot))
require.NoError(t, beaconDB.SaveFinalizedCheckpoint(ctx, &cp))
return finalizedBlockRoot, signedFinalizedBlock
}
func TestLightClientStore_LightClientUpdatesByRange(t *testing.T) {
t.Run("no updates", func(t *testing.T) {
d := testDB.SetupDB(t)
ctx := context.Background()
_, finalizedBlock := saveInitialFinalizedCheckpointData(t, ctx, d)
s := NewLightClientStore(&p2pTesting.FakeP2P{}, new(event.Feed), d)
require.NotNil(t, s)
updates, err := s.LightClientUpdates(ctx, 2, 5, finalizedBlock)
require.NoError(t, err)
require.Equal(t, 0, len(updates))
})
t.Run("single update from db", func(t *testing.T) {
d := testDB.SetupDB(t)
ctx := context.Background()
_, finalizedBlock := saveInitialFinalizedCheckpointData(t, ctx, d)
s := NewLightClientStore(&p2pTesting.FakeP2P{}, new(event.Feed), d)
require.NotNil(t, s)
l := util.NewTestLightClient(t, version.Altair)
update, err := NewLightClientUpdateFromBeaconState(l.Ctx, l.State, l.Block, l.AttestedState, l.AttestedBlock, l.FinalizedBlock)
require.NoError(t, err)
require.NoError(t, d.SaveLightClientUpdate(ctx, 3, update))
updates, err := s.LightClientUpdates(ctx, 3, 3, finalizedBlock)
require.NoError(t, err)
require.Equal(t, 1, len(updates))
require.DeepEqual(t, update, updates[0], "Expected to find the update in the store")
})
t.Run("multiple updates from db", func(t *testing.T) {
d := testDB.SetupDB(t)
ctx := context.Background()
_, finalizedBlock := saveInitialFinalizedCheckpointData(t, ctx, d)
s := NewLightClientStore(&p2pTesting.FakeP2P{}, new(event.Feed), d)
require.NotNil(t, s)
l := util.NewTestLightClient(t, version.Altair)
update, err := NewLightClientUpdateFromBeaconState(l.Ctx, l.State, l.Block, l.AttestedState, l.AttestedBlock, l.FinalizedBlock)
require.NoError(t, err)
require.NoError(t, d.SaveLightClientUpdate(ctx, 3, update))
require.NoError(t, d.SaveLightClientUpdate(ctx, 4, update))
require.NoError(t, d.SaveLightClientUpdate(ctx, 5, update))
updates, err := s.LightClientUpdates(ctx, 3, 5, finalizedBlock)
require.NoError(t, err)
require.Equal(t, 3, len(updates))
require.DeepEqual(t, update, updates[0], "Expected to find the update in the store")
require.DeepEqual(t, update, updates[1], "Expected to find the update in the store")
require.DeepEqual(t, update, updates[2], "Expected to find the update in the store")
})
t.Run("single update from cache", func(t *testing.T) {
d := testDB.SetupDB(t)
ctx := context.Background()
_, _ = saveInitialFinalizedCheckpointData(t, ctx, d)
s := NewLightClientStore(&p2pTesting.FakeP2P{}, new(event.Feed), d)
require.NotNil(t, s)
l := util.NewTestLightClient(t, version.Altair)
update, err := NewLightClientUpdateFromBeaconState(l.Ctx, l.State, l.Block, l.AttestedState, l.AttestedBlock, l.FinalizedBlock)
require.NoError(t, err)
cacheItem := &cacheItem{
period: 3,
bestUpdate: update,
}
s.cache.items[[32]byte{3}] = cacheItem
_, headBlock := saveStateAndBlockWithParentRoot(t, ctx, d, [32]byte{3})
updates, err := s.LightClientUpdates(ctx, 3, 3, headBlock)
require.NoError(t, err)
require.Equal(t, 1, len(updates))
require.DeepEqual(t, update, updates[0], "Expected to find the update in the store")
})
t.Run("multiple updates from cache", func(t *testing.T) {
d := testDB.SetupDB(t)
ctx := context.Background()
_, _ = saveInitialFinalizedCheckpointData(t, ctx, d)
s := NewLightClientStore(&p2pTesting.FakeP2P{}, new(event.Feed), d)
require.NotNil(t, s)
l := util.NewTestLightClient(t, version.Altair)
update, err := NewLightClientUpdateFromBeaconState(l.Ctx, l.State, l.Block, l.AttestedState, l.AttestedBlock, l.FinalizedBlock)
require.NoError(t, err)
cacheItemP3 := &cacheItem{
period: 3,
bestUpdate: update,
}
s.cache.items[[32]byte{3}] = cacheItemP3
cacheItemP4 := &cacheItem{
period: 4,
bestUpdate: update,
parent: cacheItemP3,
}
s.cache.items[[32]byte{4}] = cacheItemP4
cacheItemP5 := &cacheItem{
period: 5,
bestUpdate: update,
parent: cacheItemP4,
}
s.cache.items[[32]byte{5}] = cacheItemP5
_, headBlock := saveStateAndBlockWithParentRoot(t, ctx, d, [32]byte{5})
updates, err := s.LightClientUpdates(ctx, 3, 5, headBlock)
require.NoError(t, err)
require.Equal(t, 3, len(updates))
require.DeepEqual(t, update, updates[0], "Expected to find the update in the store")
require.DeepEqual(t, update, updates[1], "Expected to find the update in the store")
require.DeepEqual(t, update, updates[2], "Expected to find the update in the store")
})
t.Run("multiple updates from both db and cache - no overlap", func(t *testing.T) {
d := testDB.SetupDB(t)
ctx := context.Background()
_, _ = saveInitialFinalizedCheckpointData(t, ctx, d)
s := NewLightClientStore(&p2pTesting.FakeP2P{}, new(event.Feed), d)
require.NotNil(t, s)
l := util.NewTestLightClient(t, version.Altair)
update, err := NewLightClientUpdateFromBeaconState(l.Ctx, l.State, l.Block, l.AttestedState, l.AttestedBlock, l.FinalizedBlock)
require.NoError(t, err)
require.NoError(t, d.SaveLightClientUpdate(ctx, 1, update))
require.NoError(t, d.SaveLightClientUpdate(ctx, 2, update))
cacheItemP3 := &cacheItem{
period: 3,
bestUpdate: update,
}
s.cache.items[[32]byte{3}] = cacheItemP3
cacheItemP4 := &cacheItem{
period: 4,
bestUpdate: update,
parent: cacheItemP3,
}
s.cache.items[[32]byte{4}] = cacheItemP4
cacheItemP5 := &cacheItem{
period: 5,
bestUpdate: update,
parent: cacheItemP4,
}
s.cache.items[[32]byte{5}] = cacheItemP5
_, headBlock := saveStateAndBlockWithParentRoot(t, ctx, d, [32]byte{5})
updates, err := s.LightClientUpdates(ctx, 1, 5, headBlock)
require.NoError(t, err)
require.Equal(t, 5, len(updates))
for i := range 5 {
require.DeepEqual(t, update, updates[i], "Expected to find the update in the store")
}
})
t.Run("multiple updates from both db and cache - overlap", func(t *testing.T) {
d := testDB.SetupDB(t)
ctx := context.Background()
_, _ = saveInitialFinalizedCheckpointData(t, ctx, d)
s := NewLightClientStore(&p2pTesting.FakeP2P{}, new(event.Feed), d)
require.NotNil(t, s)
l1 := util.NewTestLightClient(t, version.Altair)
update1, err := NewLightClientUpdateFromBeaconState(l1.Ctx, l1.State, l1.Block, l1.AttestedState, l1.AttestedBlock, l1.FinalizedBlock)
require.NoError(t, err)
l2 := util.NewTestLightClient(t, version.Altair, util.WithIncreasedAttestedSlot(1))
update2, err := NewLightClientUpdateFromBeaconState(l2.Ctx, l2.State, l2.Block, l2.AttestedState, l2.AttestedBlock, l2.FinalizedBlock)
require.NoError(t, err)
require.DeepNotEqual(t, update1, update2)
require.NoError(t, d.SaveLightClientUpdate(ctx, 1, update1))
require.NoError(t, d.SaveLightClientUpdate(ctx, 2, update1))
require.NoError(t, d.SaveLightClientUpdate(ctx, 3, update1))
require.NoError(t, d.SaveLightClientUpdate(ctx, 4, update1))
cacheItemP3 := &cacheItem{
period: 3,
bestUpdate: update2,
}
s.cache.items[[32]byte{3}] = cacheItemP3
cacheItemP4 := &cacheItem{
period: 4,
bestUpdate: update2,
parent: cacheItemP3,
}
s.cache.items[[32]byte{4}] = cacheItemP4
cacheItemP5 := &cacheItem{
period: 5,
bestUpdate: update2,
parent: cacheItemP4,
}
s.cache.items[[32]byte{5}] = cacheItemP5
_, headBlock := saveStateAndBlockWithParentRoot(t, ctx, d, [32]byte{5})
updates, err := s.LightClientUpdates(ctx, 1, 5, headBlock)
require.NoError(t, err)
require.Equal(t, 5, len(updates))
// first two updates should be update1
for i := range 2 {
require.DeepEqual(t, update1, updates[i], "Expected to find the update in the store")
}
// next three updates should be update2 - as cache overrides db
for i := 2; i < 5; i++ {
require.DeepEqual(t, update2, updates[i], "Expected to find the update in the store")
}
})
t.Run("first continuous range", func(t *testing.T) {
d := testDB.SetupDB(t)
ctx := context.Background()
_, _ = saveInitialFinalizedCheckpointData(t, ctx, d)
s := NewLightClientStore(&p2pTesting.FakeP2P{}, new(event.Feed), d)
require.NotNil(t, s)
l1 := util.NewTestLightClient(t, version.Altair)
update, err := NewLightClientUpdateFromBeaconState(l1.Ctx, l1.State, l1.Block, l1.AttestedState, l1.AttestedBlock, l1.FinalizedBlock)
require.NoError(t, err)
require.NoError(t, d.SaveLightClientUpdate(ctx, 1, update))
require.NoError(t, d.SaveLightClientUpdate(ctx, 2, update))
cacheItemP4 := &cacheItem{
period: 4,
bestUpdate: update,
}
s.cache.items[[32]byte{4}] = cacheItemP4
cacheItemP5 := &cacheItem{
period: 5,
bestUpdate: update,
parent: cacheItemP4,
}
s.cache.items[[32]byte{5}] = cacheItemP5
_, headBlock := saveStateAndBlockWithParentRoot(t, ctx, d, [32]byte{5})
updates, err := s.LightClientUpdates(ctx, 1, 5, headBlock)
require.NoError(t, err)
require.Equal(t, 2, len(updates))
require.DeepEqual(t, update, updates[0], "Expected to find the update in the store")
require.DeepEqual(t, update, updates[1], "Expected to find the update in the store")
})
}
func saveStateAndBlockWithParentRoot(t *testing.T, ctx context.Context, d db.Database, parentRoot [32]byte) ([32]byte, interfaces.SignedBeaconBlock) {
blk := util.NewBeaconBlock()
blk.Block.ParentRoot = parentRoot[:]
blkRoot, err := blk.Block.HashTreeRoot()
require.NoError(t, err)
util.SaveBlock(t, ctx, d, blk)
st, err := util.NewBeaconState()
require.NoError(t, err)
require.NoError(t, d.SaveState(ctx, st, blkRoot))
signedFinalizedBlock, err := blocks.NewSignedBeaconBlock(blk)
require.NoError(t, err)
return blkRoot, signedFinalizedBlock
}