mirror of
https://github.com/OffchainLabs/prysm.git
synced 2026-01-08 21:08:10 -05:00
**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).
971 lines
45 KiB
Go
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
|
|
}
|