Files
prysm/beacon-chain/db/kv/lightclient_test.go

889 lines
33 KiB
Go

package kv
import (
"fmt"
"math/rand"
"testing"
"github.com/OffchainLabs/prysm/v7/beacon-chain/state"
fieldparams "github.com/OffchainLabs/prysm/v7/config/fieldparams"
"github.com/OffchainLabs/prysm/v7/config/params"
"github.com/OffchainLabs/prysm/v7/consensus-types/interfaces"
light_client "github.com/OffchainLabs/prysm/v7/consensus-types/light-client"
"github.com/OffchainLabs/prysm/v7/consensus-types/primitives"
enginev1 "github.com/OffchainLabs/prysm/v7/proto/engine/v1"
pb "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"
bolt "go.etcd.io/bbolt"
"google.golang.org/protobuf/proto"
)
func createUpdate(t *testing.T, v int) (interfaces.LightClientUpdate, error) {
config := params.BeaconConfig()
var slot primitives.Slot
var header interfaces.LightClientHeader
var st state.BeaconState
var err error
sampleRoot := make([]byte, 32)
for i := range 32 {
sampleRoot[i] = byte(i)
}
sampleExecutionBranch := make([][]byte, fieldparams.ExecutionBranchDepth)
for i := range 4 {
sampleExecutionBranch[i] = make([]byte, 32)
for j := range 32 {
sampleExecutionBranch[i][j] = byte(i + j)
}
}
switch v {
case version.Altair:
slot = primitives.Slot(config.AltairForkEpoch * primitives.Epoch(config.SlotsPerEpoch)).Add(1)
header, err = light_client.NewWrappedHeader(&pb.LightClientHeaderAltair{
Beacon: &pb.BeaconBlockHeader{
Slot: slot,
ProposerIndex: primitives.ValidatorIndex(rand.Int()),
ParentRoot: sampleRoot,
StateRoot: sampleRoot,
BodyRoot: sampleRoot,
},
})
require.NoError(t, err)
st, err = util.NewBeaconState()
require.NoError(t, err)
case version.Bellatrix:
slot = primitives.Slot(config.BellatrixForkEpoch * primitives.Epoch(config.SlotsPerEpoch)).Add(1)
header, err = light_client.NewWrappedHeader(&pb.LightClientHeaderAltair{
Beacon: &pb.BeaconBlockHeader{
Slot: slot,
ProposerIndex: primitives.ValidatorIndex(rand.Int()),
ParentRoot: sampleRoot,
StateRoot: sampleRoot,
BodyRoot: sampleRoot,
},
})
require.NoError(t, err)
st, err = util.NewBeaconState()
require.NoError(t, err)
case version.Capella:
slot = primitives.Slot(config.CapellaForkEpoch * primitives.Epoch(config.SlotsPerEpoch)).Add(1)
header, err = light_client.NewWrappedHeader(&pb.LightClientHeaderCapella{
Beacon: &pb.BeaconBlockHeader{
Slot: slot,
ProposerIndex: primitives.ValidatorIndex(rand.Int()),
ParentRoot: sampleRoot,
StateRoot: sampleRoot,
BodyRoot: sampleRoot,
},
Execution: &enginev1.ExecutionPayloadHeaderCapella{
ParentHash: make([]byte, fieldparams.RootLength),
FeeRecipient: make([]byte, fieldparams.FeeRecipientLength),
StateRoot: make([]byte, fieldparams.RootLength),
ReceiptsRoot: make([]byte, fieldparams.RootLength),
LogsBloom: make([]byte, fieldparams.LogsBloomLength),
PrevRandao: make([]byte, fieldparams.RootLength),
ExtraData: make([]byte, 0),
BaseFeePerGas: make([]byte, fieldparams.RootLength),
BlockHash: make([]byte, fieldparams.RootLength),
TransactionsRoot: make([]byte, fieldparams.RootLength),
WithdrawalsRoot: make([]byte, fieldparams.RootLength),
},
ExecutionBranch: sampleExecutionBranch,
})
require.NoError(t, err)
st, err = util.NewBeaconStateCapella()
require.NoError(t, err)
case version.Deneb:
slot = primitives.Slot(config.DenebForkEpoch * primitives.Epoch(config.SlotsPerEpoch)).Add(1)
header, err = light_client.NewWrappedHeader(&pb.LightClientHeaderDeneb{
Beacon: &pb.BeaconBlockHeader{
Slot: slot,
ProposerIndex: primitives.ValidatorIndex(rand.Int()),
ParentRoot: sampleRoot,
StateRoot: sampleRoot,
BodyRoot: sampleRoot,
},
Execution: &enginev1.ExecutionPayloadHeaderDeneb{
ParentHash: make([]byte, fieldparams.RootLength),
FeeRecipient: make([]byte, fieldparams.FeeRecipientLength),
StateRoot: make([]byte, fieldparams.RootLength),
ReceiptsRoot: make([]byte, fieldparams.RootLength),
LogsBloom: make([]byte, fieldparams.LogsBloomLength),
PrevRandao: make([]byte, fieldparams.RootLength),
ExtraData: make([]byte, 0),
BaseFeePerGas: make([]byte, fieldparams.RootLength),
BlockHash: make([]byte, fieldparams.RootLength),
TransactionsRoot: make([]byte, fieldparams.RootLength),
WithdrawalsRoot: make([]byte, fieldparams.RootLength),
},
ExecutionBranch: sampleExecutionBranch,
})
require.NoError(t, err)
st, err = util.NewBeaconStateDeneb()
require.NoError(t, err)
case version.Electra:
slot = primitives.Slot(config.ElectraForkEpoch * primitives.Epoch(config.SlotsPerEpoch)).Add(1)
header, err = light_client.NewWrappedHeader(&pb.LightClientHeaderDeneb{
Beacon: &pb.BeaconBlockHeader{
Slot: slot,
ProposerIndex: primitives.ValidatorIndex(rand.Int()),
ParentRoot: sampleRoot,
StateRoot: sampleRoot,
BodyRoot: sampleRoot,
},
Execution: &enginev1.ExecutionPayloadHeaderDeneb{
ParentHash: make([]byte, fieldparams.RootLength),
FeeRecipient: make([]byte, fieldparams.FeeRecipientLength),
StateRoot: make([]byte, fieldparams.RootLength),
ReceiptsRoot: make([]byte, fieldparams.RootLength),
LogsBloom: make([]byte, fieldparams.LogsBloomLength),
PrevRandao: make([]byte, fieldparams.RootLength),
ExtraData: make([]byte, 0),
BaseFeePerGas: make([]byte, fieldparams.RootLength),
BlockHash: make([]byte, fieldparams.RootLength),
TransactionsRoot: make([]byte, fieldparams.RootLength),
WithdrawalsRoot: make([]byte, fieldparams.RootLength),
},
ExecutionBranch: sampleExecutionBranch,
})
require.NoError(t, err)
st, err = util.NewBeaconStateElectra()
require.NoError(t, err)
case version.Fulu:
slot = primitives.Slot(config.FuluForkEpoch * primitives.Epoch(config.SlotsPerEpoch)).Add(1)
header, err = light_client.NewWrappedHeader(&pb.LightClientHeaderDeneb{
Beacon: &pb.BeaconBlockHeader{
Slot: slot,
ProposerIndex: primitives.ValidatorIndex(rand.Int()),
ParentRoot: sampleRoot,
StateRoot: sampleRoot,
BodyRoot: sampleRoot,
},
Execution: &enginev1.ExecutionPayloadHeaderDeneb{
ParentHash: make([]byte, fieldparams.RootLength),
FeeRecipient: make([]byte, fieldparams.FeeRecipientLength),
StateRoot: make([]byte, fieldparams.RootLength),
ReceiptsRoot: make([]byte, fieldparams.RootLength),
LogsBloom: make([]byte, fieldparams.LogsBloomLength),
PrevRandao: make([]byte, fieldparams.RootLength),
ExtraData: make([]byte, 0),
BaseFeePerGas: make([]byte, fieldparams.RootLength),
BlockHash: make([]byte, fieldparams.RootLength),
TransactionsRoot: make([]byte, fieldparams.RootLength),
WithdrawalsRoot: make([]byte, fieldparams.RootLength),
},
ExecutionBranch: sampleExecutionBranch,
})
require.NoError(t, err)
st, err = util.NewBeaconStateFulu()
require.NoError(t, err)
default:
return nil, fmt.Errorf("unsupported version %s", version.String(v))
}
update, err := createDefaultLightClientUpdate(slot, st)
require.NoError(t, err)
update.SetSignatureSlot(slot - 1)
syncCommitteeBits := make([]byte, 64)
syncCommitteeSignature := make([]byte, 96)
update.SetSyncAggregate(&pb.SyncAggregate{
SyncCommitteeBits: syncCommitteeBits,
SyncCommitteeSignature: syncCommitteeSignature,
})
require.NoError(t, update.SetAttestedHeader(header))
require.NoError(t, update.SetFinalizedHeader(header))
return update, nil
}
func TestStore_LightClientUpdate_CanSaveRetrieve(t *testing.T) {
params.SetupTestConfigCleanup(t)
cfg := params.BeaconConfig()
cfg.AltairForkEpoch = 0
cfg.BellatrixForkEpoch = 1
cfg.CapellaForkEpoch = 2
cfg.DenebForkEpoch = 3
cfg.ElectraForkEpoch = 4
cfg.FuluForkEpoch = 5
params.OverrideBeaconConfig(cfg)
db := setupDB(t)
ctx := t.Context()
for _, testVersion := range version.All()[1:] {
if testVersion == version.Gloas {
// TODO(16027): Unskip light client tests for Gloas
continue
}
t.Run(version.String(testVersion), func(t *testing.T) {
update, err := createUpdate(t, testVersion)
require.NoError(t, err)
period := uint64(1)
err = db.SaveLightClientUpdate(ctx, period, update)
require.NoError(t, err)
retrievedUpdate, err := db.LightClientUpdate(ctx, period)
require.NoError(t, err)
require.DeepEqual(t, update, retrievedUpdate, "retrieved update does not match saved update")
})
}
}
func TestStore_LightClientUpdates_canRetrieveRange(t *testing.T) {
db := setupDB(t)
ctx := t.Context()
updates := make([]interfaces.LightClientUpdate, 0, 3)
for i := 1; i <= 3; i++ {
update, err := createUpdate(t, version.Altair)
require.NoError(t, err)
updates = append(updates, update)
}
for i, update := range updates {
err := db.SaveLightClientUpdate(ctx, uint64(i+1), update)
require.NoError(t, err)
}
// Retrieve the updates
retrievedUpdatesMap, err := db.LightClientUpdates(ctx, 1, 3)
require.NoError(t, err)
require.Equal(t, len(updates), len(retrievedUpdatesMap), "retrieved updates do not match saved updates")
for i, update := range updates {
require.DeepEqual(t, update, retrievedUpdatesMap[uint64(i+1)], "retrieved update does not match saved update")
}
}
func TestStore_LightClientUpdate_EndPeriodSmallerThanStartPeriod(t *testing.T) {
db := setupDB(t)
ctx := t.Context()
updates := make([]interfaces.LightClientUpdate, 0, 3)
for i := 1; i <= 3; i++ {
update, err := createUpdate(t, version.Altair)
require.NoError(t, err)
updates = append(updates, update)
}
for i, update := range updates {
err := db.SaveLightClientUpdate(ctx, uint64(i+1), update)
require.NoError(t, err)
}
// Retrieve the updates
retrievedUpdates, err := db.LightClientUpdates(ctx, 3, 1)
require.NotNil(t, err)
require.Equal(t, err.Error(), "start period 3 is greater than end period 1")
require.IsNil(t, retrievedUpdates)
}
func TestStore_LightClientUpdate_EndPeriodEqualToStartPeriod(t *testing.T) {
db := setupDB(t)
ctx := t.Context()
updates := make([]interfaces.LightClientUpdate, 0, 3)
for i := 1; i <= 3; i++ {
update, err := createUpdate(t, version.Altair)
require.NoError(t, err)
updates = append(updates, update)
}
for i, update := range updates {
err := db.SaveLightClientUpdate(ctx, uint64(i+1), update)
require.NoError(t, err)
}
// Retrieve the updates
retrievedUpdates, err := db.LightClientUpdates(ctx, 2, 2)
require.NoError(t, err)
require.Equal(t, 1, len(retrievedUpdates))
require.DeepEqual(t, updates[1], retrievedUpdates[2], "retrieved update does not match saved update")
}
func TestStore_LightClientUpdate_StartPeriodBeforeFirstUpdate(t *testing.T) {
db := setupDB(t)
ctx := t.Context()
updates := make([]interfaces.LightClientUpdate, 0, 3)
for i := 1; i <= 3; i++ {
update, err := createUpdate(t, version.Altair)
require.NoError(t, err)
updates = append(updates, update)
}
for i, update := range updates {
err := db.SaveLightClientUpdate(ctx, uint64(i+1), update)
require.NoError(t, err)
}
// Retrieve the updates
retrievedUpdates, err := db.LightClientUpdates(ctx, 0, 4)
require.NoError(t, err)
require.Equal(t, 3, len(retrievedUpdates))
for i, update := range updates {
require.DeepEqual(t, update, retrievedUpdates[uint64(i+1)], "retrieved update does not match saved update")
}
}
func TestStore_LightClientUpdate_EndPeriodAfterLastUpdate(t *testing.T) {
db := setupDB(t)
ctx := t.Context()
updates := make([]interfaces.LightClientUpdate, 0, 3)
for i := 1; i <= 3; i++ {
update, err := createUpdate(t, version.Altair)
require.NoError(t, err)
updates = append(updates, update)
}
for i, update := range updates {
err := db.SaveLightClientUpdate(ctx, uint64(i+1), update)
require.NoError(t, err)
}
// Retrieve the updates
retrievedUpdates, err := db.LightClientUpdates(ctx, 1, 6)
require.NoError(t, err)
require.Equal(t, 3, len(retrievedUpdates))
for i, update := range updates {
require.DeepEqual(t, update, retrievedUpdates[uint64(i+1)], "retrieved update does not match saved update")
}
}
func TestStore_LightClientUpdate_PartialUpdates(t *testing.T) {
db := setupDB(t)
ctx := t.Context()
updates := make([]interfaces.LightClientUpdate, 0, 3)
for i := 1; i <= 3; i++ {
update, err := createUpdate(t, version.Altair)
require.NoError(t, err)
updates = append(updates, update)
}
for i, update := range updates {
err := db.SaveLightClientUpdate(ctx, uint64(i+1), update)
require.NoError(t, err)
}
// Retrieve the updates
retrievedUpdates, err := db.LightClientUpdates(ctx, 1, 2)
require.NoError(t, err)
require.Equal(t, 2, len(retrievedUpdates))
for i, update := range updates[:2] {
require.DeepEqual(t, update, retrievedUpdates[uint64(i+1)], "retrieved update does not match saved update")
}
}
func TestStore_LightClientUpdate_MissingPeriods_SimpleData(t *testing.T) {
db := setupDB(t)
ctx := t.Context()
updates := make([]interfaces.LightClientUpdate, 0, 4)
for i := 1; i <= 4; i++ {
update, err := createUpdate(t, version.Altair)
require.NoError(t, err)
updates = append(updates, update)
}
for i, update := range updates {
if i == 1 || i == 2 {
continue
}
err := db.SaveLightClientUpdate(ctx, uint64(i+1), update)
require.NoError(t, err)
}
// Retrieve the updates
retrievedUpdates, err := db.LightClientUpdates(ctx, 1, 4)
require.NoError(t, err)
require.Equal(t, 2, len(retrievedUpdates))
require.DeepEqual(t, updates[0], retrievedUpdates[uint64(1)], "retrieved update does not match saved update")
require.DeepEqual(t, updates[3], retrievedUpdates[uint64(4)], "retrieved update does not match saved update")
// Retrieve the updates from the middle
retrievedUpdates, err = db.LightClientUpdates(ctx, 2, 4)
require.NoError(t, err)
require.Equal(t, 1, len(retrievedUpdates))
require.DeepEqual(t, updates[3], retrievedUpdates[4], "retrieved update does not match saved update")
// Retrieve the updates from after the missing period
retrievedUpdates, err = db.LightClientUpdates(ctx, 4, 4)
require.NoError(t, err)
require.Equal(t, 1, len(retrievedUpdates))
require.DeepEqual(t, updates[3], retrievedUpdates[4], "retrieved update does not match saved update")
//retrieve the updates from before the missing period to after the missing period
retrievedUpdates, err = db.LightClientUpdates(ctx, 0, 6)
require.NoError(t, err)
require.Equal(t, 2, len(retrievedUpdates))
require.DeepEqual(t, updates[0], retrievedUpdates[uint64(1)], "retrieved update does not match saved update")
require.DeepEqual(t, updates[3], retrievedUpdates[uint64(4)], "retrieved update does not match saved update")
}
func TestStore_LightClientUpdate_EmptyDB(t *testing.T) {
db := setupDB(t)
ctx := t.Context()
// Retrieve the updates
retrievedUpdates, err := db.LightClientUpdates(ctx, 1, 3)
require.IsNil(t, err)
require.Equal(t, 0, len(retrievedUpdates))
}
func TestStore_LightClientUpdate_RetrieveMissingPeriodDistributed(t *testing.T) {
db := setupDB(t)
ctx := t.Context()
updates := make([]interfaces.LightClientUpdate, 0, 5)
for i := 1; i <= 5; i++ {
update, err := createUpdate(t, version.Altair)
require.NoError(t, err)
updates = append(updates, update)
}
for i, update := range updates {
if i == 1 || i == 3 {
continue
}
err := db.SaveLightClientUpdate(ctx, uint64(i+1), update)
require.NoError(t, err)
}
// Retrieve the updates
retrievedUpdates, err := db.LightClientUpdates(ctx, 0, 7)
require.NoError(t, err)
require.Equal(t, 3, len(retrievedUpdates))
require.DeepEqual(t, updates[0], retrievedUpdates[uint64(1)], "retrieved update does not match saved update")
require.DeepEqual(t, updates[2], retrievedUpdates[uint64(3)], "retrieved update does not match saved update")
require.DeepEqual(t, updates[4], retrievedUpdates[uint64(5)], "retrieved update does not match saved update")
}
func createDefaultLightClientUpdate(currentSlot primitives.Slot, attestedState state.BeaconState) (interfaces.LightClientUpdate, error) {
currentEpoch := slots.ToEpoch(currentSlot)
syncCommitteeSize := params.BeaconConfig().SyncCommitteeSize
pubKeys := make([][]byte, syncCommitteeSize)
for i := range syncCommitteeSize {
pubKeys[i] = make([]byte, fieldparams.BLSPubkeyLength)
}
nextSyncCommittee := &pb.SyncCommittee{
Pubkeys: pubKeys,
AggregatePubkey: make([]byte, fieldparams.BLSPubkeyLength),
}
var nextSyncCommitteeBranch [][]byte
if attestedState.Version() >= version.Electra {
nextSyncCommitteeBranch = make([][]byte, fieldparams.SyncCommitteeBranchDepthElectra)
} else {
nextSyncCommitteeBranch = make([][]byte, fieldparams.SyncCommitteeBranchDepth)
}
for i := 0; i < len(nextSyncCommitteeBranch); i++ {
nextSyncCommitteeBranch[i] = make([]byte, fieldparams.RootLength)
}
executionBranch := make([][]byte, fieldparams.ExecutionBranchDepth)
for i := range fieldparams.ExecutionBranchDepth {
executionBranch[i] = make([]byte, 32)
}
var finalityBranch [][]byte
if attestedState.Version() >= version.Electra {
finalityBranch = make([][]byte, fieldparams.FinalityBranchDepthElectra)
} else {
finalityBranch = make([][]byte, fieldparams.FinalityBranchDepth)
}
for i := 0; i < len(finalityBranch); i++ {
finalityBranch[i] = make([]byte, 32)
}
var m proto.Message
if currentEpoch < params.BeaconConfig().CapellaForkEpoch {
m = &pb.LightClientUpdateAltair{
AttestedHeader: &pb.LightClientHeaderAltair{},
NextSyncCommittee: nextSyncCommittee,
NextSyncCommitteeBranch: nextSyncCommitteeBranch,
FinalityBranch: finalityBranch,
}
} else if currentEpoch < params.BeaconConfig().DenebForkEpoch {
m = &pb.LightClientUpdateCapella{
AttestedHeader: &pb.LightClientHeaderCapella{
Beacon: &pb.BeaconBlockHeader{},
Execution: &enginev1.ExecutionPayloadHeaderCapella{},
ExecutionBranch: executionBranch,
},
NextSyncCommittee: nextSyncCommittee,
NextSyncCommitteeBranch: nextSyncCommitteeBranch,
FinalityBranch: finalityBranch,
}
} else if currentEpoch < params.BeaconConfig().ElectraForkEpoch {
m = &pb.LightClientUpdateDeneb{
AttestedHeader: &pb.LightClientHeaderDeneb{
Beacon: &pb.BeaconBlockHeader{},
Execution: &enginev1.ExecutionPayloadHeaderDeneb{},
ExecutionBranch: executionBranch,
},
NextSyncCommittee: nextSyncCommittee,
NextSyncCommitteeBranch: nextSyncCommitteeBranch,
FinalityBranch: finalityBranch,
}
} else {
if attestedState.Version() >= version.Electra {
m = &pb.LightClientUpdateElectra{
AttestedHeader: &pb.LightClientHeaderDeneb{
Beacon: &pb.BeaconBlockHeader{},
Execution: &enginev1.ExecutionPayloadHeaderDeneb{},
ExecutionBranch: executionBranch,
},
NextSyncCommittee: nextSyncCommittee,
NextSyncCommitteeBranch: nextSyncCommitteeBranch,
FinalityBranch: finalityBranch,
}
} else {
m = &pb.LightClientUpdateDeneb{
AttestedHeader: &pb.LightClientHeaderDeneb{
Beacon: &pb.BeaconBlockHeader{},
Execution: &enginev1.ExecutionPayloadHeaderDeneb{},
ExecutionBranch: executionBranch,
},
NextSyncCommittee: nextSyncCommittee,
NextSyncCommitteeBranch: nextSyncCommitteeBranch,
FinalityBranch: finalityBranch,
}
}
}
return light_client.NewWrappedUpdate(m)
}
func TestStore_LightClientBootstrap_CanSaveRetrieve(t *testing.T) {
params.SetupTestConfigCleanup(t)
cfg := params.BeaconConfig()
cfg.AltairForkEpoch = 0
cfg.BellatrixForkEpoch = 1
cfg.CapellaForkEpoch = 2
cfg.DenebForkEpoch = 3
cfg.ElectraForkEpoch = 4
cfg.EpochsPerSyncCommitteePeriod = 1
params.OverrideBeaconConfig(cfg)
db := setupDB(t)
ctx := t.Context()
t.Run("Nil", func(t *testing.T) {
retrievedBootstrap, err := db.LightClientBootstrap(ctx, []byte("NilBlockRoot"))
require.NoError(t, err)
require.IsNil(t, retrievedBootstrap)
})
for _, testVersion := range version.All()[1:] {
if testVersion == version.Gloas {
// TODO(16027): Unskip light client tests for Gloas
continue
}
t.Run(version.String(testVersion), func(t *testing.T) {
bootstrap, err := createDefaultLightClientBootstrap(primitives.Slot(uint64(params.BeaconConfig().VersionToForkEpochMap()[testVersion]) * uint64(params.BeaconConfig().SlotsPerEpoch)))
require.NoError(t, err)
err = bootstrap.SetCurrentSyncCommittee(createRandomSyncCommittee())
require.NoError(t, err)
blockRoot := []byte("blockRootAltair" + version.String(testVersion))
err = db.SaveLightClientBootstrap(ctx, blockRoot, bootstrap)
require.NoError(t, err)
retrievedBootstrap, err := db.LightClientBootstrap(ctx, blockRoot)
require.NoError(t, err)
require.DeepEqual(t, bootstrap.Header(), retrievedBootstrap.Header(), "retrieved bootstrap header does not match saved bootstrap header")
require.DeepEqual(t, bootstrap.CurrentSyncCommittee(), retrievedBootstrap.CurrentSyncCommittee(), "retrieved bootstrap sync committee does not match saved bootstrap sync committee")
if testVersion >= version.Electra {
savedBranch, err := bootstrap.CurrentSyncCommitteeBranchElectra()
require.NoError(t, err)
retrievedBranch, err := retrievedBootstrap.CurrentSyncCommitteeBranchElectra()
require.NoError(t, err)
require.DeepEqual(t, savedBranch, retrievedBranch, "retrieved bootstrap sync committee branch does not match saved bootstrap sync committee branch")
} else {
savedBranch, err := bootstrap.CurrentSyncCommitteeBranch()
require.NoError(t, err)
retrievedBranch, err := retrievedBootstrap.CurrentSyncCommitteeBranch()
require.NoError(t, err)
require.DeepEqual(t, savedBranch, retrievedBranch, "retrieved bootstrap sync committee branch does not match saved bootstrap sync committee branch")
}
})
}
}
func TestStore_LightClientBootstrap_MultipleBootstrapsWithSameSyncCommittee(t *testing.T) {
params.SetupTestConfigCleanup(t)
cfg := params.BeaconConfig()
cfg.AltairForkEpoch = 0
cfg.CapellaForkEpoch = 1
cfg.DenebForkEpoch = 2
cfg.ElectraForkEpoch = 3
cfg.EpochsPerSyncCommitteePeriod = 1
params.OverrideBeaconConfig(cfg)
db := setupDB(t)
ctx := t.Context()
bootstrap1, err := createDefaultLightClientBootstrap(primitives.Slot(uint64(params.BeaconConfig().AltairForkEpoch) * uint64(params.BeaconConfig().SlotsPerEpoch)))
require.NoError(t, err)
bootstrap2, err := createDefaultLightClientBootstrap(primitives.Slot(uint64(params.BeaconConfig().AltairForkEpoch) * uint64(params.BeaconConfig().SlotsPerEpoch)))
require.NoError(t, err)
randomSyncCommittee := createRandomSyncCommittee()
err = bootstrap1.SetCurrentSyncCommittee(randomSyncCommittee)
require.NoError(t, err)
err = bootstrap2.SetCurrentSyncCommittee(randomSyncCommittee)
require.NoError(t, err)
err = db.SaveLightClientBootstrap(ctx, []byte("blockRootAltair1"), bootstrap1)
require.NoError(t, err)
err = db.SaveLightClientBootstrap(ctx, []byte("blockRootAltair2"), bootstrap2)
require.NoError(t, err)
retrievedBootstrap1, err := db.LightClientBootstrap(ctx, []byte("blockRootAltair1"))
require.NoError(t, err)
retrievedBootstrap2, err := db.LightClientBootstrap(ctx, []byte("blockRootAltair2"))
require.NoError(t, err)
require.DeepEqual(t, bootstrap1.Header(), retrievedBootstrap1.Header(), "retrieved bootstrap1 header does not match saved bootstrap1 header")
require.DeepEqual(t, randomSyncCommittee, retrievedBootstrap1.CurrentSyncCommittee(), "retrieved bootstrap1 sync committee does not match saved bootstrap1 sync committee")
savedBranch, err := bootstrap1.CurrentSyncCommitteeBranch()
require.NoError(t, err)
retrievedBranch, err := retrievedBootstrap1.CurrentSyncCommitteeBranch()
require.NoError(t, err)
require.DeepEqual(t, savedBranch, retrievedBranch, "retrieved bootstrap1 sync committee branch does not match saved bootstrap1 sync committee branch")
require.DeepEqual(t, bootstrap2.Header(), retrievedBootstrap2.Header(), "retrieved bootstrap1 header does not match saved bootstrap1 header")
require.DeepEqual(t, randomSyncCommittee, retrievedBootstrap2.CurrentSyncCommittee(), "retrieved bootstrap1 sync committee does not match saved bootstrap1 sync committee")
savedBranch2, err := bootstrap2.CurrentSyncCommitteeBranch()
require.NoError(t, err)
retrievedBranch2, err := retrievedBootstrap2.CurrentSyncCommitteeBranch()
require.NoError(t, err)
require.DeepEqual(t, savedBranch2, retrievedBranch2, "retrieved bootstrap1 sync committee branch does not match saved bootstrap1 sync committee branch")
// Ensure that the sync committee is only stored once
err = db.db.View(func(tx *bolt.Tx) error {
bucket := tx.Bucket(lightClientSyncCommitteeBucket)
require.NotNil(t, bucket)
count := bucket.Stats().KeyN
require.Equal(t, 1, count)
return nil
})
require.NoError(t, err)
}
func TestStore_LightClientBootstrap_MultipleBootstrapsWithDifferentSyncCommittees(t *testing.T) {
params.SetupTestConfigCleanup(t)
cfg := params.BeaconConfig()
cfg.AltairForkEpoch = 0
cfg.CapellaForkEpoch = 1
cfg.DenebForkEpoch = 2
cfg.ElectraForkEpoch = 3
cfg.EpochsPerSyncCommitteePeriod = 1
params.OverrideBeaconConfig(cfg)
db := setupDB(t)
ctx := t.Context()
bootstrap1, err := createDefaultLightClientBootstrap(primitives.Slot(uint64(params.BeaconConfig().AltairForkEpoch) * uint64(params.BeaconConfig().SlotsPerEpoch)))
require.NoError(t, err)
bootstrap2, err := createDefaultLightClientBootstrap(primitives.Slot(uint64(params.BeaconConfig().AltairForkEpoch) * uint64(params.BeaconConfig().SlotsPerEpoch)))
require.NoError(t, err)
err = bootstrap1.SetCurrentSyncCommittee(createRandomSyncCommittee())
require.NoError(t, err)
err = bootstrap2.SetCurrentSyncCommittee(createRandomSyncCommittee())
require.NoError(t, err)
err = db.SaveLightClientBootstrap(ctx, []byte("blockRootAltair1"), bootstrap1)
require.NoError(t, err)
err = db.SaveLightClientBootstrap(ctx, []byte("blockRootAltair2"), bootstrap2)
require.NoError(t, err)
retrievedBootstrap1, err := db.LightClientBootstrap(ctx, []byte("blockRootAltair1"))
require.NoError(t, err)
retrievedBootstrap2, err := db.LightClientBootstrap(ctx, []byte("blockRootAltair2"))
require.NoError(t, err)
require.DeepEqual(t, bootstrap1.Header(), retrievedBootstrap1.Header(), "retrieved bootstrap1 header does not match saved bootstrap1 header")
require.DeepEqual(t, bootstrap1.CurrentSyncCommittee(), retrievedBootstrap1.CurrentSyncCommittee(), "retrieved bootstrap1 sync committee does not match saved bootstrap1 sync committee")
savedBranch, err := bootstrap1.CurrentSyncCommitteeBranch()
require.NoError(t, err)
retrievedBranch, err := retrievedBootstrap1.CurrentSyncCommitteeBranch()
require.NoError(t, err)
require.DeepEqual(t, savedBranch, retrievedBranch, "retrieved bootstrap1 sync committee branch does not match saved bootstrap1 sync committee branch")
require.DeepEqual(t, bootstrap2.Header(), retrievedBootstrap2.Header(), "retrieved bootstrap1 header does not match saved bootstrap1 header")
require.DeepEqual(t, bootstrap2.CurrentSyncCommittee(), retrievedBootstrap2.CurrentSyncCommittee(), "retrieved bootstrap1 sync committee does not match saved bootstrap1 sync committee")
savedBranch2, err := bootstrap2.CurrentSyncCommitteeBranch()
require.NoError(t, err)
retrievedBranch2, err := retrievedBootstrap2.CurrentSyncCommitteeBranch()
require.NoError(t, err)
require.DeepEqual(t, savedBranch2, retrievedBranch2, "retrieved bootstrap1 sync committee branch does not match saved bootstrap1 sync committee branch")
// Ensure that the sync committee is stored twice
err = db.db.View(func(tx *bolt.Tx) error {
bucket := tx.Bucket(lightClientSyncCommitteeBucket)
require.NotNil(t, bucket)
count := bucket.Stats().KeyN
require.Equal(t, 2, count)
return nil
})
require.NoError(t, err)
}
func createDefaultLightClientBootstrap(currentSlot primitives.Slot) (interfaces.LightClientBootstrap, error) {
currentEpoch := slots.ToEpoch(currentSlot)
syncCommitteeSize := params.BeaconConfig().SyncCommitteeSize
pubKeys := make([][]byte, syncCommitteeSize)
for i := range syncCommitteeSize {
pubKeys[i] = make([]byte, fieldparams.BLSPubkeyLength)
}
currentSyncCommittee := &pb.SyncCommittee{
Pubkeys: pubKeys,
AggregatePubkey: make([]byte, fieldparams.BLSPubkeyLength),
}
var currentSyncCommitteeBranch [][]byte
if currentEpoch >= params.BeaconConfig().ElectraForkEpoch {
currentSyncCommitteeBranch = make([][]byte, fieldparams.SyncCommitteeBranchDepthElectra)
} else {
currentSyncCommitteeBranch = make([][]byte, fieldparams.SyncCommitteeBranchDepth)
}
for i := 0; i < len(currentSyncCommitteeBranch); i++ {
currentSyncCommitteeBranch[i] = make([]byte, fieldparams.RootLength)
}
executionBranch := make([][]byte, fieldparams.ExecutionBranchDepth)
for i := range fieldparams.ExecutionBranchDepth {
executionBranch[i] = make([]byte, 32)
}
// TODO: can this be based on the current epoch?
var m proto.Message
if currentEpoch < params.BeaconConfig().CapellaForkEpoch {
m = &pb.LightClientBootstrapAltair{
Header: &pb.LightClientHeaderAltair{
Beacon: &pb.BeaconBlockHeader{
Slot: currentSlot,
ParentRoot: make([]byte, 32),
StateRoot: make([]byte, 32),
BodyRoot: make([]byte, 32),
},
},
CurrentSyncCommittee: currentSyncCommittee,
CurrentSyncCommitteeBranch: currentSyncCommitteeBranch,
}
} else if currentEpoch < params.BeaconConfig().DenebForkEpoch {
m = &pb.LightClientBootstrapCapella{
Header: &pb.LightClientHeaderCapella{
Beacon: &pb.BeaconBlockHeader{
Slot: currentSlot,
ParentRoot: make([]byte, 32),
StateRoot: make([]byte, 32),
BodyRoot: make([]byte, 32),
},
Execution: &enginev1.ExecutionPayloadHeaderCapella{
ParentHash: make([]byte, fieldparams.RootLength),
FeeRecipient: make([]byte, fieldparams.FeeRecipientLength),
StateRoot: make([]byte, fieldparams.RootLength),
ReceiptsRoot: make([]byte, fieldparams.RootLength),
LogsBloom: make([]byte, fieldparams.LogsBloomLength),
PrevRandao: make([]byte, fieldparams.RootLength),
ExtraData: make([]byte, 0),
BaseFeePerGas: make([]byte, fieldparams.RootLength),
BlockHash: make([]byte, fieldparams.RootLength),
TransactionsRoot: make([]byte, fieldparams.RootLength),
WithdrawalsRoot: make([]byte, fieldparams.RootLength),
},
ExecutionBranch: executionBranch,
},
CurrentSyncCommittee: currentSyncCommittee,
CurrentSyncCommitteeBranch: currentSyncCommitteeBranch,
}
} else if currentEpoch < params.BeaconConfig().ElectraForkEpoch {
m = &pb.LightClientBootstrapDeneb{
Header: &pb.LightClientHeaderDeneb{
Beacon: &pb.BeaconBlockHeader{
Slot: currentSlot,
ParentRoot: make([]byte, 32),
StateRoot: make([]byte, 32),
BodyRoot: make([]byte, 32),
},
Execution: &enginev1.ExecutionPayloadHeaderDeneb{
ParentHash: make([]byte, fieldparams.RootLength),
FeeRecipient: make([]byte, fieldparams.FeeRecipientLength),
StateRoot: make([]byte, fieldparams.RootLength),
ReceiptsRoot: make([]byte, fieldparams.RootLength),
LogsBloom: make([]byte, fieldparams.LogsBloomLength),
PrevRandao: make([]byte, fieldparams.RootLength),
ExtraData: make([]byte, 0),
BaseFeePerGas: make([]byte, fieldparams.RootLength),
BlockHash: make([]byte, fieldparams.RootLength),
TransactionsRoot: make([]byte, fieldparams.RootLength),
WithdrawalsRoot: make([]byte, fieldparams.RootLength),
GasLimit: 0,
GasUsed: 0,
},
ExecutionBranch: executionBranch,
},
CurrentSyncCommittee: currentSyncCommittee,
CurrentSyncCommitteeBranch: currentSyncCommitteeBranch,
}
} else {
m = &pb.LightClientBootstrapElectra{
Header: &pb.LightClientHeaderDeneb{
Beacon: &pb.BeaconBlockHeader{
Slot: currentSlot,
ParentRoot: make([]byte, 32),
StateRoot: make([]byte, 32),
BodyRoot: make([]byte, 32),
},
Execution: &enginev1.ExecutionPayloadHeaderDeneb{
ParentHash: make([]byte, fieldparams.RootLength),
FeeRecipient: make([]byte, fieldparams.FeeRecipientLength),
StateRoot: make([]byte, fieldparams.RootLength),
ReceiptsRoot: make([]byte, fieldparams.RootLength),
LogsBloom: make([]byte, fieldparams.LogsBloomLength),
PrevRandao: make([]byte, fieldparams.RootLength),
ExtraData: make([]byte, 0),
BaseFeePerGas: make([]byte, fieldparams.RootLength),
BlockHash: make([]byte, fieldparams.RootLength),
TransactionsRoot: make([]byte, fieldparams.RootLength),
WithdrawalsRoot: make([]byte, fieldparams.RootLength),
GasLimit: 0,
GasUsed: 0,
},
ExecutionBranch: executionBranch,
},
CurrentSyncCommittee: currentSyncCommittee,
CurrentSyncCommitteeBranch: currentSyncCommitteeBranch,
}
}
return light_client.NewWrappedBootstrap(m)
}
func createRandomSyncCommittee() *pb.SyncCommittee {
// random number between 2 and 128
base := rand.Int()%127 + 2
syncCom := make([][]byte, params.BeaconConfig().SyncCommitteeSize)
for i := 0; uint64(i) < params.BeaconConfig().SyncCommitteeSize; i++ {
if i%base == 0 {
syncCom[i] = make([]byte, fieldparams.BLSPubkeyLength)
syncCom[i][0] = 1
continue
}
syncCom[i] = make([]byte, fieldparams.BLSPubkeyLength)
}
return &pb.SyncCommittee{
Pubkeys: syncCom,
AggregatePubkey: make([]byte, fieldparams.BLSPubkeyLength),
}
}