diff --git a/CHANGELOG.md b/CHANGELOG.md index c4463e28efb..f8323da19dc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,7 @@ The format is based on Keep a Changelog, and this project adheres to Semantic Ve - Fix `engine_exchangeCapabilities` implementation. - Updated the default `scrape-interval` in `Client-stats` to 2 minutes to accommodate Beaconcha.in API rate limits. - Switch to compounding when consolidating with source==target. +- Changed `GetLightClientUpdatesByRange` API to read from the DB instead of computing. ### Deprecated diff --git a/api/server/structs/BUILD.bazel b/api/server/structs/BUILD.bazel index c194d502ee0..d6532e7bcde 100644 --- a/api/server/structs/BUILD.bazel +++ b/api/server/structs/BUILD.bazel @@ -39,6 +39,7 @@ go_library( "//proto/eth/v2:go_default_library", "//proto/migration:go_default_library", "//proto/prysm/v1alpha1:go_default_library", + "//runtime/version:go_default_library", "@com_github_ethereum_go_ethereum//common:go_default_library", "@com_github_ethereum_go_ethereum//common/hexutil:go_default_library", "@com_github_pkg_errors//:go_default_library", diff --git a/api/server/structs/conversions_lightclient.go b/api/server/structs/conversions_lightclient.go index 50e6281ef9b..662266f0117 100644 --- a/api/server/structs/conversions_lightclient.go +++ b/api/server/structs/conversions_lightclient.go @@ -10,8 +10,21 @@ import ( v1 "github.com/prysmaticlabs/prysm/v5/proto/eth/v1" v2 "github.com/prysmaticlabs/prysm/v5/proto/eth/v2" "github.com/prysmaticlabs/prysm/v5/proto/migration" + "github.com/prysmaticlabs/prysm/v5/runtime/version" ) +func LightClientUpdateResponseFromConsensus(update *v2.LightClientUpdateWithVersion) (*LightClientUpdateResponse, error) { + data, err := LightClientUpdateFromConsensus(update.Data) + if err != nil { + return nil, err + } + + return &LightClientUpdateResponse{ + Version: version.String(int(update.Version)), + Data: data, + }, nil +} + func LightClientUpdateFromConsensus(update *v2.LightClientUpdate) (*LightClientUpdate, error) { attestedHeader, err := lightClientHeaderContainerToJSON(update.AttestedHeader) if err != nil { diff --git a/beacon-chain/rpc/eth/light-client/BUILD.bazel b/beacon-chain/rpc/eth/light-client/BUILD.bazel index 85b46a07ecc..498cce61e0b 100644 --- a/beacon-chain/rpc/eth/light-client/BUILD.bazel +++ b/beacon-chain/rpc/eth/light-client/BUILD.bazel @@ -21,7 +21,6 @@ go_library( "//config/fieldparams:go_default_library", "//config/params:go_default_library", "//consensus-types/interfaces:go_default_library", - "//consensus-types/primitives:go_default_library", "//monitoring/tracing/trace:go_default_library", "//network/httputil:go_default_library", "//proto/eth/v2:go_default_library", @@ -46,6 +45,7 @@ go_test( "//beacon-chain/blockchain/testing:go_default_library", "//beacon-chain/core/helpers:go_default_library", "//beacon-chain/core/light-client:go_default_library", + "//beacon-chain/db/kv:go_default_library", "//beacon-chain/rpc/testutil:go_default_library", "//beacon-chain/state:go_default_library", "//config/fieldparams:go_default_library", @@ -53,9 +53,11 @@ go_test( "//consensus-types/blocks:go_default_library", "//consensus-types/interfaces:go_default_library", "//consensus-types/primitives:go_default_library", + "//proto/engine/v1:go_default_library", "//proto/eth/v1:go_default_library", "//proto/eth/v2:go_default_library", "//proto/prysm/v1alpha1:go_default_library", + "//runtime/version:go_default_library", "//testing/assert:go_default_library", "//testing/require:go_default_library", "//testing/util:go_default_library", diff --git a/beacon-chain/rpc/eth/light-client/handlers.go b/beacon-chain/rpc/eth/light-client/handlers.go index 245d703eaaf..2df55fbad62 100644 --- a/beacon-chain/rpc/eth/light-client/handlers.go +++ b/beacon-chain/rpc/eth/light-client/handlers.go @@ -11,10 +11,8 @@ import ( "github.com/prysmaticlabs/prysm/v5/api" "github.com/prysmaticlabs/prysm/v5/api/server/structs" "github.com/prysmaticlabs/prysm/v5/beacon-chain/rpc/eth/shared" - "github.com/prysmaticlabs/prysm/v5/beacon-chain/state" "github.com/prysmaticlabs/prysm/v5/config/params" "github.com/prysmaticlabs/prysm/v5/consensus-types/interfaces" - types "github.com/prysmaticlabs/prysm/v5/consensus-types/primitives" "github.com/prysmaticlabs/prysm/v5/monitoring/tracing/trace" "github.com/prysmaticlabs/prysm/v5/network/httputil" "github.com/prysmaticlabs/prysm/v5/runtime/version" @@ -117,108 +115,29 @@ func (s *Server) GetLightClientUpdatesByRange(w http.ResponseWriter, req *http.R endPeriod = maxSlot / slotsPerPeriod } - // Populate updates - var updates []*structs.LightClientUpdateResponse - for period := startPeriod; period <= endPeriod; period++ { - // Get the last known state of the period, - // 1. We wish the block has a parent in the same period if possible - // 2. We wish the block has a state in the same period - lastSlotInPeriod := period*slotsPerPeriod + slotsPerPeriod - 1 - if lastSlotInPeriod > maxSlot { - lastSlotInPeriod = maxSlot - } - firstSlotInPeriod := period * slotsPerPeriod - - // Let's not use the first slot in the period, otherwise the attested header will be in previous period - firstSlotInPeriod++ - - var state state.BeaconState - var block interfaces.ReadOnlySignedBeaconBlock - for slot := lastSlotInPeriod; slot >= firstSlotInPeriod; slot-- { - state, err = s.Stater.StateBySlot(ctx, types.Slot(slot)) - if err != nil { - continue - } - - // Get the block - latestBlockHeader := state.LatestBlockHeader() - latestStateRoot, err := state.HashTreeRoot(ctx) - if err != nil { - continue - } - latestBlockHeader.StateRoot = latestStateRoot[:] - blockRoot, err := latestBlockHeader.HashTreeRoot() - if err != nil { - continue - } - - block, err = s.Blocker.Block(ctx, blockRoot[:]) - if err != nil || block == nil { - continue - } - - syncAggregate, err := block.Block().Body().SyncAggregate() - if err != nil || syncAggregate == nil { - continue - } - - if syncAggregate.SyncCommitteeBits.Count()*3 < config.SyncCommitteeSize*2 { - // Not enough votes - continue - } - - break - } + // get updates + updatesMap, err := s.BeaconDB.LightClientUpdates(ctx, startPeriod, endPeriod) + if err != nil { + httputil.HandleError(w, "Could not get light client updates from DB: "+err.Error(), http.StatusInternalServerError) + return + } - if block == nil { - // No valid block found for the period - continue - } + updates := make([]*structs.LightClientUpdateResponse, 0, len(updatesMap)) - // Get attested state - attestedRoot := block.Block().ParentRoot() - attestedBlock, err := s.Blocker.Block(ctx, attestedRoot[:]) - if err != nil || attestedBlock == nil { - continue + for i := startPeriod; i <= endPeriod; i++ { + update, ok := updatesMap[i] + if !ok { + // Only return the first contiguous range of updates + break } - attestedSlot := attestedBlock.Block().Slot() - attestedState, err := s.Stater.StateBySlot(ctx, attestedSlot) + updateResponse, err := structs.LightClientUpdateResponseFromConsensus(update) if err != nil { - continue - } - - // Get finalized block - var finalizedBlock interfaces.ReadOnlySignedBeaconBlock - finalizedCheckPoint := attestedState.FinalizedCheckpoint() - if finalizedCheckPoint != nil { - finalizedRoot := bytesutil.ToBytes32(finalizedCheckPoint.Root) - finalizedBlock, err = s.Blocker.Block(ctx, finalizedRoot[:]) - if err != nil { - finalizedBlock = nil - } + httputil.HandleError(w, "Could not convert light client update: "+err.Error(), http.StatusInternalServerError) + return } - update, err := newLightClientUpdateFromBeaconState( - ctx, - state, - block, - attestedState, - attestedBlock, - finalizedBlock, - ) - - if err == nil { - updates = append(updates, &structs.LightClientUpdateResponse{ - Version: version.String(attestedState.Version()), - Data: update, - }) - } - } - - if len(updates) == 0 { - httputil.HandleError(w, "no updates found", http.StatusNotFound) - return + updates = append(updates, updateResponse) } httputil.WriteJson(w, updates) diff --git a/beacon-chain/rpc/eth/light-client/handlers_test.go b/beacon-chain/rpc/eth/light-client/handlers_test.go index 342ea679ee3..7483948e1ba 100644 --- a/beacon-chain/rpc/eth/light-client/handlers_test.go +++ b/beacon-chain/rpc/eth/light-client/handlers_test.go @@ -14,13 +14,18 @@ import ( "github.com/prysmaticlabs/prysm/v5/api/server/structs" mock "github.com/prysmaticlabs/prysm/v5/beacon-chain/blockchain/testing" "github.com/prysmaticlabs/prysm/v5/beacon-chain/core/helpers" + "github.com/prysmaticlabs/prysm/v5/beacon-chain/db/kv" "github.com/prysmaticlabs/prysm/v5/beacon-chain/rpc/testutil" "github.com/prysmaticlabs/prysm/v5/beacon-chain/state" "github.com/prysmaticlabs/prysm/v5/config/params" "github.com/prysmaticlabs/prysm/v5/consensus-types/blocks" "github.com/prysmaticlabs/prysm/v5/consensus-types/interfaces" "github.com/prysmaticlabs/prysm/v5/consensus-types/primitives" + enginev1 "github.com/prysmaticlabs/prysm/v5/proto/engine/v1" + ethpbv1 "github.com/prysmaticlabs/prysm/v5/proto/eth/v1" + ethpbv2 "github.com/prysmaticlabs/prysm/v5/proto/eth/v2" ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1" + "github.com/prysmaticlabs/prysm/v5/runtime/version" "github.com/prysmaticlabs/prysm/v5/testing/require" "github.com/prysmaticlabs/prysm/v5/testing/util" ) @@ -225,90 +230,64 @@ func TestLightClientHandler_GetLightClientBootstrap_Electra(t *testing.T) { require.NotNil(t, resp.Data.CurrentSyncCommitteeBranch) } +// GetLightClientByRange tests + func TestLightClientHandler_GetLightClientUpdatesByRangeAltair(t *testing.T) { helpers.ClearCache() ctx := context.Background() config := params.BeaconConfig() slot := primitives.Slot(config.AltairForkEpoch * primitives.Epoch(config.SlotsPerEpoch)).Add(1) - attestedState, err := util.NewBeaconStateAltair() - require.NoError(t, err) - err = attestedState.SetSlot(slot.Sub(1)) - require.NoError(t, err) - - parent := util.NewBeaconBlockAltair() - parent.Block.Slot = slot.Sub(1) - - signedParent, err := blocks.NewSignedBeaconBlock(parent) - require.NoError(t, err) - - parentHeader, err := signedParent.Header() - require.NoError(t, err) - attestedHeader := parentHeader.Header - - err = attestedState.SetLatestBlockHeader(attestedHeader) - require.NoError(t, err) - attestedStateRoot, err := attestedState.HashTreeRoot(ctx) - require.NoError(t, err) - - // get a new signed block so the root is updated with the new state root - parent.Block.StateRoot = attestedStateRoot[:] - signedParent, err = blocks.NewSignedBeaconBlock(parent) - require.NoError(t, err) - st, err := util.NewBeaconStateAltair() require.NoError(t, err) err = st.SetSlot(slot) require.NoError(t, err) - parentRoot, err := signedParent.Block().HashTreeRoot() - require.NoError(t, err) + db := setupDB(t) - block := util.NewBeaconBlockAltair() - block.Block.Slot = slot - block.Block.ParentRoot = parentRoot[:] + updatePeriod := uint64(slot.Div(uint64(config.EpochsPerSyncCommitteePeriod)).Div(uint64(config.SlotsPerEpoch))) - for i := uint64(0); i < config.SyncCommitteeSize; i++ { - block.Block.Body.SyncAggregate.SyncCommitteeBits.SetBitAt(i, true) + update := ðpbv2.LightClientUpdate{ + AttestedHeader: ðpbv2.LightClientHeaderContainer{ + Header: ðpbv2.LightClientHeaderContainer_HeaderAltair{ + HeaderAltair: ðpbv2.LightClientHeader{ + Beacon: ðpbv1.BeaconBlockHeader{ + Slot: slot.Sub(1), + ProposerIndex: 1, + ParentRoot: []byte{1, 1, 1}, + StateRoot: []byte{1, 1, 1}, + BodyRoot: []byte{1, 1, 1}, + }, + }, + }, + }, + NextSyncCommittee: ðpbv2.SyncCommittee{ + Pubkeys: nil, + AggregatePubkey: nil, + }, + NextSyncCommitteeBranch: nil, + FinalizedHeader: nil, + FinalityBranch: nil, + SyncAggregate: ðpbv1.SyncAggregate{ + SyncCommitteeBits: []byte{1, 1, 1}, + SyncCommitteeSignature: []byte{1, 1, 1}, + }, + SignatureSlot: 7, } - signedBlock, err := blocks.NewSignedBeaconBlock(block) - require.NoError(t, err) - - h, err := signedBlock.Header() - require.NoError(t, err) - - err = st.SetLatestBlockHeader(h.Header) - require.NoError(t, err) - stateRoot, err := st.HashTreeRoot(ctx) - require.NoError(t, err) - - // get a new signed block so the root is updated with the new state root - block.Block.StateRoot = stateRoot[:] - signedBlock, err = blocks.NewSignedBeaconBlock(block) - require.NoError(t, err) - - root, err := block.Block.HashTreeRoot() + err = db.SaveLightClientUpdate(ctx, updatePeriod, ðpbv2.LightClientUpdateWithVersion{ + Version: version.Altair, + Data: update, + }) require.NoError(t, err) - mockBlocker := &testutil.MockBlocker{ - RootBlockMap: map[[32]byte]interfaces.ReadOnlySignedBeaconBlock{ - parentRoot: signedParent, - root: signedBlock, - }, - SlotBlockMap: map[primitives.Slot]interfaces.ReadOnlySignedBeaconBlock{ - slot.Sub(1): signedParent, - slot: signedBlock, - }, - } + mockBlocker := &testutil.MockBlocker{} mockChainService := &mock.ChainService{Optimistic: true, Slot: &slot, State: st} s := &Server{ - Stater: &testutil.MockStater{StatesBySlot: map[primitives.Slot]state.BeaconState{ - slot.Sub(1): attestedState, - slot: st, - }}, + Stater: &testutil.MockStater{}, Blocker: mockBlocker, HeadFetcher: mockChainService, + BeaconDB: db, } startPeriod := slot.Div(uint64(config.EpochsPerSyncCommitteePeriod)).Div(uint64(config.SlotsPerEpoch)) url := fmt.Sprintf("http://foo.com/?count=1&start_period=%d", startPeriod) @@ -322,13 +301,11 @@ func TestLightClientHandler_GetLightClientUpdatesByRangeAltair(t *testing.T) { var resp structs.LightClientUpdatesByRangeResponse err = json.Unmarshal(writer.Body.Bytes(), &resp.Updates) require.NoError(t, err) - var respHeader structs.LightClientHeader - err = json.Unmarshal(resp.Updates[0].Data.AttestedHeader, &respHeader) - require.NoError(t, err) require.Equal(t, 1, len(resp.Updates)) require.Equal(t, "altair", resp.Updates[0].Version) - require.Equal(t, hexutil.Encode(attestedHeader.BodyRoot), respHeader.Beacon.BodyRoot) - require.NotNil(t, resp) + updateJson, err := structs.LightClientUpdateFromConsensus(update) + require.NoError(t, err) + require.DeepEqual(t, updateJson, resp.Updates[0].Data) } func TestLightClientHandler_GetLightClientUpdatesByRangeCapella(t *testing.T) { @@ -337,84 +314,76 @@ func TestLightClientHandler_GetLightClientUpdatesByRangeCapella(t *testing.T) { config := params.BeaconConfig() slot := primitives.Slot(config.CapellaForkEpoch * primitives.Epoch(config.SlotsPerEpoch)).Add(1) - attestedState, err := util.NewBeaconStateCapella() - require.NoError(t, err) - err = attestedState.SetSlot(slot.Sub(1)) - require.NoError(t, err) - - parent := util.NewBeaconBlockCapella() - parent.Block.Slot = slot.Sub(1) - - signedParent, err := blocks.NewSignedBeaconBlock(parent) - require.NoError(t, err) - - parentHeader, err := signedParent.Header() - require.NoError(t, err) - attestedHeader := parentHeader.Header - - err = attestedState.SetLatestBlockHeader(attestedHeader) - require.NoError(t, err) - attestedStateRoot, err := attestedState.HashTreeRoot(ctx) - require.NoError(t, err) - - // get a new signed block so the root is updated with the new state root - parent.Block.StateRoot = attestedStateRoot[:] - signedParent, err = blocks.NewSignedBeaconBlock(parent) - require.NoError(t, err) - st, err := util.NewBeaconStateCapella() require.NoError(t, err) err = st.SetSlot(slot) require.NoError(t, err) - parentRoot, err := signedParent.Block().HashTreeRoot() - require.NoError(t, err) - - block := util.NewBeaconBlockCapella() - block.Block.Slot = slot - block.Block.ParentRoot = parentRoot[:] - - for i := uint64(0); i < config.SyncCommitteeSize; i++ { - block.Block.Body.SyncAggregate.SyncCommitteeBits.SetBitAt(i, true) + db := setupDB(t) + + updatePeriod := uint64(slot.Div(uint64(config.EpochsPerSyncCommitteePeriod)).Div(uint64(config.SlotsPerEpoch))) + + update := ðpbv2.LightClientUpdate{ + AttestedHeader: ðpbv2.LightClientHeaderContainer{ + Header: ðpbv2.LightClientHeaderContainer_HeaderCapella{ + HeaderCapella: ðpbv2.LightClientHeaderCapella{ + Beacon: ðpbv1.BeaconBlockHeader{ + Slot: slot.Sub(1), + ProposerIndex: 1, + ParentRoot: []byte{1, 1, 1}, + StateRoot: []byte{1, 1, 1}, + BodyRoot: []byte{1, 1, 1}, + }, + Execution: &enginev1.ExecutionPayloadHeaderCapella{ + FeeRecipient: []byte{1, 2, 3}, + }, + ExecutionBranch: [][]byte{{1, 2, 3}, {4, 5, 6}}, + }, + }, + }, + NextSyncCommittee: ðpbv2.SyncCommittee{ + Pubkeys: nil, + AggregatePubkey: nil, + }, + NextSyncCommitteeBranch: nil, + FinalizedHeader: ðpbv2.LightClientHeaderContainer{ + Header: ðpbv2.LightClientHeaderContainer_HeaderCapella{ + HeaderCapella: ðpbv2.LightClientHeaderCapella{ + Beacon: ðpbv1.BeaconBlockHeader{ + Slot: 12, + ProposerIndex: 1, + ParentRoot: []byte{1, 1, 1}, + StateRoot: []byte{1, 1, 1}, + BodyRoot: []byte{1, 1, 1}, + }, + Execution: &enginev1.ExecutionPayloadHeaderCapella{ + FeeRecipient: []byte{1, 2, 3}, + }, + ExecutionBranch: [][]byte{{1, 2, 3}, {4, 5, 6}}, + }, + }, + }, + FinalityBranch: nil, + SyncAggregate: ðpbv1.SyncAggregate{ + SyncCommitteeBits: []byte{1, 1, 1}, + SyncCommitteeSignature: []byte{1, 1, 1}, + }, + SignatureSlot: 7, } - signedBlock, err := blocks.NewSignedBeaconBlock(block) - require.NoError(t, err) - - h, err := signedBlock.Header() - require.NoError(t, err) - - err = st.SetLatestBlockHeader(h.Header) - require.NoError(t, err) - stateRoot, err := st.HashTreeRoot(ctx) - require.NoError(t, err) - - // get a new signed block so the root is updated with the new state root - block.Block.StateRoot = stateRoot[:] - signedBlock, err = blocks.NewSignedBeaconBlock(block) - require.NoError(t, err) - - root, err := block.Block.HashTreeRoot() + err = db.SaveLightClientUpdate(ctx, updatePeriod, ðpbv2.LightClientUpdateWithVersion{ + Version: version.Capella, + Data: update, + }) require.NoError(t, err) - mockBlocker := &testutil.MockBlocker{ - RootBlockMap: map[[32]byte]interfaces.ReadOnlySignedBeaconBlock{ - parentRoot: signedParent, - root: signedBlock, - }, - SlotBlockMap: map[primitives.Slot]interfaces.ReadOnlySignedBeaconBlock{ - slot.Sub(1): signedParent, - slot: signedBlock, - }, - } + mockBlocker := &testutil.MockBlocker{} mockChainService := &mock.ChainService{Optimistic: true, Slot: &slot, State: st} s := &Server{ - Stater: &testutil.MockStater{StatesBySlot: map[primitives.Slot]state.BeaconState{ - slot.Sub(1): attestedState, - slot: st, - }}, + Stater: &testutil.MockStater{}, Blocker: mockBlocker, HeadFetcher: mockChainService, + BeaconDB: db, } startPeriod := slot.Div(uint64(config.EpochsPerSyncCommitteePeriod)).Div(uint64(config.SlotsPerEpoch)) url := fmt.Sprintf("http://foo.com/?count=1&start_period=%d", startPeriod) @@ -428,13 +397,11 @@ func TestLightClientHandler_GetLightClientUpdatesByRangeCapella(t *testing.T) { var resp structs.LightClientUpdatesByRangeResponse err = json.Unmarshal(writer.Body.Bytes(), &resp.Updates) require.NoError(t, err) - var respHeader structs.LightClientHeaderCapella - err = json.Unmarshal(resp.Updates[0].Data.AttestedHeader, &respHeader) - require.NoError(t, err) require.Equal(t, 1, len(resp.Updates)) require.Equal(t, "capella", resp.Updates[0].Version) - require.Equal(t, hexutil.Encode(attestedHeader.BodyRoot), respHeader.Beacon.BodyRoot) - require.NotNil(t, resp) + updateJson, err := structs.LightClientUpdateFromConsensus(update) + require.NoError(t, err) + require.DeepEqual(t, updateJson, resp.Updates[0].Data) } func TestLightClientHandler_GetLightClientUpdatesByRangeDeneb(t *testing.T) { @@ -443,84 +410,76 @@ func TestLightClientHandler_GetLightClientUpdatesByRangeDeneb(t *testing.T) { config := params.BeaconConfig() slot := primitives.Slot(config.DenebForkEpoch * primitives.Epoch(config.SlotsPerEpoch)).Add(1) - attestedState, err := util.NewBeaconStateDeneb() - require.NoError(t, err) - err = attestedState.SetSlot(slot.Sub(1)) - require.NoError(t, err) - - parent := util.NewBeaconBlockDeneb() - parent.Block.Slot = slot.Sub(1) - - signedParent, err := blocks.NewSignedBeaconBlock(parent) - require.NoError(t, err) - - parentHeader, err := signedParent.Header() - require.NoError(t, err) - attestedHeader := parentHeader.Header - - err = attestedState.SetLatestBlockHeader(attestedHeader) - require.NoError(t, err) - attestedStateRoot, err := attestedState.HashTreeRoot(ctx) - require.NoError(t, err) - - // get a new signed block so the root is updated with the new state root - parent.Block.StateRoot = attestedStateRoot[:] - signedParent, err = blocks.NewSignedBeaconBlock(parent) - require.NoError(t, err) - st, err := util.NewBeaconStateDeneb() require.NoError(t, err) err = st.SetSlot(slot) require.NoError(t, err) - parentRoot, err := signedParent.Block().HashTreeRoot() - require.NoError(t, err) - - block := util.NewBeaconBlockDeneb() - block.Block.Slot = slot - block.Block.ParentRoot = parentRoot[:] - - for i := uint64(0); i < config.SyncCommitteeSize; i++ { - block.Block.Body.SyncAggregate.SyncCommitteeBits.SetBitAt(i, true) + db := setupDB(t) + + updatePeriod := uint64(slot.Div(uint64(config.EpochsPerSyncCommitteePeriod)).Div(uint64(config.SlotsPerEpoch))) + + update := ðpbv2.LightClientUpdate{ + AttestedHeader: ðpbv2.LightClientHeaderContainer{ + Header: ðpbv2.LightClientHeaderContainer_HeaderCapella{ + HeaderCapella: ðpbv2.LightClientHeaderCapella{ + Beacon: ðpbv1.BeaconBlockHeader{ + Slot: slot.Sub(1), + ProposerIndex: 1, + ParentRoot: []byte{1, 1, 1}, + StateRoot: []byte{1, 1, 1}, + BodyRoot: []byte{1, 1, 1}, + }, + Execution: &enginev1.ExecutionPayloadHeaderCapella{ + FeeRecipient: []byte{1, 2, 3}, + }, + ExecutionBranch: [][]byte{{1, 2, 3}, {4, 5, 6}}, + }, + }, + }, + NextSyncCommittee: ðpbv2.SyncCommittee{ + Pubkeys: nil, + AggregatePubkey: nil, + }, + NextSyncCommitteeBranch: nil, + FinalizedHeader: ðpbv2.LightClientHeaderContainer{ + Header: ðpbv2.LightClientHeaderContainer_HeaderDeneb{ + HeaderDeneb: ðpbv2.LightClientHeaderDeneb{ + Beacon: ðpbv1.BeaconBlockHeader{ + Slot: 12, + ProposerIndex: 1, + ParentRoot: []byte{1, 1, 1}, + StateRoot: []byte{1, 1, 1}, + BodyRoot: []byte{1, 1, 1}, + }, + Execution: &enginev1.ExecutionPayloadHeaderDeneb{ + FeeRecipient: []byte{1, 2, 3}, + }, + ExecutionBranch: [][]byte{{1, 2, 3}, {4, 5, 6}}, + }, + }, + }, + FinalityBranch: nil, + SyncAggregate: ðpbv1.SyncAggregate{ + SyncCommitteeBits: []byte{1, 1, 1}, + SyncCommitteeSignature: []byte{1, 1, 1}, + }, + SignatureSlot: 7, } - signedBlock, err := blocks.NewSignedBeaconBlock(block) - require.NoError(t, err) - - h, err := signedBlock.Header() - require.NoError(t, err) - - err = st.SetLatestBlockHeader(h.Header) - require.NoError(t, err) - stateRoot, err := st.HashTreeRoot(ctx) - require.NoError(t, err) - - // get a new signed block so the root is updated with the new state root - block.Block.StateRoot = stateRoot[:] - signedBlock, err = blocks.NewSignedBeaconBlock(block) - require.NoError(t, err) - - root, err := block.Block.HashTreeRoot() + err = db.SaveLightClientUpdate(ctx, updatePeriod, ðpbv2.LightClientUpdateWithVersion{ + Version: version.Deneb, + Data: update, + }) require.NoError(t, err) - mockBlocker := &testutil.MockBlocker{ - RootBlockMap: map[[32]byte]interfaces.ReadOnlySignedBeaconBlock{ - parentRoot: signedParent, - root: signedBlock, - }, - SlotBlockMap: map[primitives.Slot]interfaces.ReadOnlySignedBeaconBlock{ - slot.Sub(1): signedParent, - slot: signedBlock, - }, - } + mockBlocker := &testutil.MockBlocker{} mockChainService := &mock.ChainService{Optimistic: true, Slot: &slot, State: st} s := &Server{ - Stater: &testutil.MockStater{StatesBySlot: map[primitives.Slot]state.BeaconState{ - slot.Sub(1): attestedState, - slot: st, - }}, + Stater: &testutil.MockStater{}, Blocker: mockBlocker, HeadFetcher: mockChainService, + BeaconDB: db, } startPeriod := slot.Div(uint64(config.EpochsPerSyncCommitteePeriod)).Div(uint64(config.SlotsPerEpoch)) url := fmt.Sprintf("http://foo.com/?count=1&start_period=%d", startPeriod) @@ -534,210 +493,193 @@ func TestLightClientHandler_GetLightClientUpdatesByRangeDeneb(t *testing.T) { var resp structs.LightClientUpdatesByRangeResponse err = json.Unmarshal(writer.Body.Bytes(), &resp.Updates) require.NoError(t, err) - var respHeader structs.LightClientHeaderDeneb - err = json.Unmarshal(resp.Updates[0].Data.AttestedHeader, &respHeader) - require.NoError(t, err) require.Equal(t, 1, len(resp.Updates)) require.Equal(t, "deneb", resp.Updates[0].Version) - require.Equal(t, hexutil.Encode(attestedHeader.BodyRoot), respHeader.Beacon.BodyRoot) - require.NotNil(t, resp) + updateJson, err := structs.LightClientUpdateFromConsensus(update) + require.NoError(t, err) + require.DeepEqual(t, updateJson, resp.Updates[0].Data) } -func TestLightClientHandler_GetLightClientUpdatesByRange_TooBigInputCountAltair(t *testing.T) { +func TestLightClientHandler_GetLightClientUpdatesByRangeMultipleAltair(t *testing.T) { helpers.ClearCache() ctx := context.Background() config := params.BeaconConfig() slot := primitives.Slot(config.AltairForkEpoch * primitives.Epoch(config.SlotsPerEpoch)).Add(1) - attestedState, err := util.NewBeaconStateAltair() - require.NoError(t, err) - err = attestedState.SetSlot(slot.Sub(1)) + st, err := util.NewBeaconStateAltair() require.NoError(t, err) + headSlot := slot.Add(820000) + err = st.SetSlot(headSlot) + require.NoError(t, err) + + db := setupDB(t) + + updates := make([]*ethpbv2.LightClientUpdate, 100) + + updatePeriod := slot.Div(uint64(config.EpochsPerSyncCommitteePeriod)).Div(uint64(config.SlotsPerEpoch)) + + for i := 0; i < 100; i++ { + newSlot := slot.Add(uint64(i)) + + update := ðpbv2.LightClientUpdate{ + AttestedHeader: ðpbv2.LightClientHeaderContainer{ + Header: ðpbv2.LightClientHeaderContainer_HeaderAltair{ + HeaderAltair: ðpbv2.LightClientHeader{ + Beacon: ðpbv1.BeaconBlockHeader{ + Slot: newSlot, + ProposerIndex: 1, + ParentRoot: []byte{1, 1, 1}, + StateRoot: []byte{1, 1, 1}, + BodyRoot: []byte{1, 1, 1}, + }, + }, + }, + }, + NextSyncCommittee: ðpbv2.SyncCommittee{ + Pubkeys: nil, + AggregatePubkey: nil, + }, + NextSyncCommitteeBranch: nil, + FinalizedHeader: nil, + FinalityBranch: nil, + SyncAggregate: ðpbv1.SyncAggregate{ + SyncCommitteeBits: []byte{1, 1, 1}, + SyncCommitteeSignature: []byte{1, 1, 1}, + }, + SignatureSlot: 7, + } + + updates[i] = update + + err = db.SaveLightClientUpdate(ctx, uint64(updatePeriod), ðpbv2.LightClientUpdateWithVersion{ + Version: version.Altair, + Data: update, + }) + require.NoError(t, err) + + updatePeriod++ + } - parent := util.NewBeaconBlockAltair() - parent.Block.Slot = slot.Sub(1) - - signedParent, err := blocks.NewSignedBeaconBlock(parent) - require.NoError(t, err) + mockBlocker := &testutil.MockBlocker{} + mockChainService := &mock.ChainService{Optimistic: true, Slot: &headSlot, State: st} + s := &Server{ + Stater: &testutil.MockStater{}, + Blocker: mockBlocker, + HeadFetcher: mockChainService, + BeaconDB: db, + } + startPeriod := slot.Sub(1).Div(uint64(config.EpochsPerSyncCommitteePeriod)).Div(uint64(config.SlotsPerEpoch)) + url := fmt.Sprintf("http://foo.com/?count=100&start_period=%d", startPeriod) + request := httptest.NewRequest("GET", url, nil) + writer := httptest.NewRecorder() + writer.Body = &bytes.Buffer{} - parentHeader, err := signedParent.Header() - require.NoError(t, err) - attestedHeader := parentHeader.Header + s.GetLightClientUpdatesByRange(writer, request) - err = attestedState.SetLatestBlockHeader(attestedHeader) - require.NoError(t, err) - attestedStateRoot, err := attestedState.HashTreeRoot(ctx) + require.Equal(t, http.StatusOK, writer.Code) + var resp structs.LightClientUpdatesByRangeResponse + err = json.Unmarshal(writer.Body.Bytes(), &resp.Updates) require.NoError(t, err) + require.Equal(t, 100, len(resp.Updates)) + for i, update := range updates { + require.Equal(t, "altair", resp.Updates[i].Version) + updateJson, err := structs.LightClientUpdateFromConsensus(update) + require.NoError(t, err) + require.DeepEqual(t, updateJson, resp.Updates[i].Data) + } +} - // get a new signed block so the root is updated with the new state root - parent.Block.StateRoot = attestedStateRoot[:] - signedParent, err = blocks.NewSignedBeaconBlock(parent) - require.NoError(t, err) +func TestLightClientHandler_GetLightClientUpdatesByRangeMultipleCapella(t *testing.T) { + helpers.ClearCache() + ctx := context.Background() + config := params.BeaconConfig() + slot := primitives.Slot(config.CapellaForkEpoch * primitives.Epoch(config.SlotsPerEpoch)).Add(1) st, err := util.NewBeaconStateAltair() require.NoError(t, err) - err = st.SetSlot(slot) - require.NoError(t, err) - - parentRoot, err := signedParent.Block().HashTreeRoot() - require.NoError(t, err) - - block := util.NewBeaconBlockAltair() - block.Block.Slot = slot - block.Block.ParentRoot = parentRoot[:] - - for i := uint64(0); i < config.SyncCommitteeSize; i++ { - block.Block.Body.SyncAggregate.SyncCommitteeBits.SetBitAt(i, true) - } - - signedBlock, err := blocks.NewSignedBeaconBlock(block) - require.NoError(t, err) - - h, err := signedBlock.Header() - require.NoError(t, err) - - err = st.SetLatestBlockHeader(h.Header) - require.NoError(t, err) - stateRoot, err := st.HashTreeRoot(ctx) - require.NoError(t, err) - - // get a new signed block so the root is updated with the new state root - block.Block.StateRoot = stateRoot[:] - signedBlock, err = blocks.NewSignedBeaconBlock(block) - require.NoError(t, err) - - root, err := block.Block.HashTreeRoot() - require.NoError(t, err) - - mockBlocker := &testutil.MockBlocker{ - RootBlockMap: map[[32]byte]interfaces.ReadOnlySignedBeaconBlock{ - parentRoot: signedParent, - root: signedBlock, - }, - SlotBlockMap: map[primitives.Slot]interfaces.ReadOnlySignedBeaconBlock{ - slot.Sub(1): signedParent, - slot: signedBlock, - }, - } - mockChainService := &mock.ChainService{Optimistic: true, Slot: &slot, State: st} - s := &Server{ - Stater: &testutil.MockStater{StatesBySlot: map[primitives.Slot]state.BeaconState{ - slot.Sub(1): attestedState, - slot: st, - }}, - Blocker: mockBlocker, - HeadFetcher: mockChainService, - } - startPeriod := slot.Div(uint64(config.EpochsPerSyncCommitteePeriod)).Div(uint64(config.SlotsPerEpoch)) - count := 129 // config.MaxRequestLightClientUpdates is 128 - url := fmt.Sprintf("http://foo.com/?count=%d&start_period=%d", count, startPeriod) - request := httptest.NewRequest("GET", url, nil) - writer := httptest.NewRecorder() - writer.Body = &bytes.Buffer{} - - s.GetLightClientUpdatesByRange(writer, request) - - require.Equal(t, http.StatusOK, writer.Code) - var resp structs.LightClientUpdatesByRangeResponse - err = json.Unmarshal(writer.Body.Bytes(), &resp.Updates) - require.NoError(t, err) - var respHeader structs.LightClientHeader - err = json.Unmarshal(resp.Updates[0].Data.AttestedHeader, &respHeader) - require.NoError(t, err) - require.Equal(t, 1, len(resp.Updates)) // Even with big count input, the response is still the max available period, which is 1 in test case. - require.Equal(t, "altair", resp.Updates[0].Version) - require.Equal(t, hexutil.Encode(attestedHeader.BodyRoot), respHeader.Beacon.BodyRoot) - require.NotNil(t, resp) -} - -func TestLightClientHandler_GetLightClientUpdatesByRange_TooBigInputCountCapella(t *testing.T) { - helpers.ClearCache() - ctx := context.Background() - config := params.BeaconConfig() - slot := primitives.Slot(config.CapellaForkEpoch * primitives.Epoch(config.SlotsPerEpoch)).Add(1) - - attestedState, err := util.NewBeaconStateCapella() - require.NoError(t, err) - err = attestedState.SetSlot(slot.Sub(1)) - require.NoError(t, err) - - parent := util.NewBeaconBlockCapella() - parent.Block.Slot = slot.Sub(1) - - signedParent, err := blocks.NewSignedBeaconBlock(parent) - require.NoError(t, err) - - parentHeader, err := signedParent.Header() - require.NoError(t, err) - attestedHeader := parentHeader.Header - - err = attestedState.SetLatestBlockHeader(attestedHeader) - require.NoError(t, err) - attestedStateRoot, err := attestedState.HashTreeRoot(ctx) - require.NoError(t, err) - - // get a new signed block so the root is updated with the new state root - parent.Block.StateRoot = attestedStateRoot[:] - signedParent, err = blocks.NewSignedBeaconBlock(parent) - require.NoError(t, err) - - st, err := util.NewBeaconStateCapella() - require.NoError(t, err) - err = st.SetSlot(slot) - require.NoError(t, err) - - parentRoot, err := signedParent.Block().HashTreeRoot() - require.NoError(t, err) - - block := util.NewBeaconBlockCapella() - block.Block.Slot = slot - block.Block.ParentRoot = parentRoot[:] - - for i := uint64(0); i < config.SyncCommitteeSize; i++ { - block.Block.Body.SyncAggregate.SyncCommitteeBits.SetBitAt(i, true) + headSlot := slot.Add(820000) + err = st.SetSlot(headSlot) + require.NoError(t, err) + + db := setupDB(t) + + updates := make([]*ethpbv2.LightClientUpdate, 100) + + updatePeriod := slot.Div(uint64(config.EpochsPerSyncCommitteePeriod)).Div(uint64(config.SlotsPerEpoch)) + + for i := 0; i < 100; i++ { + + newSlot := slot.Add(uint64(i)) + + update := ðpbv2.LightClientUpdate{ + AttestedHeader: ðpbv2.LightClientHeaderContainer{ + Header: ðpbv2.LightClientHeaderContainer_HeaderCapella{ + HeaderCapella: ðpbv2.LightClientHeaderCapella{ + Beacon: ðpbv1.BeaconBlockHeader{ + Slot: newSlot, + ProposerIndex: 1, + ParentRoot: []byte{1, 1, 1}, + StateRoot: []byte{1, 1, 1}, + BodyRoot: []byte{1, 1, 1}, + }, + Execution: &enginev1.ExecutionPayloadHeaderCapella{ + FeeRecipient: []byte{1, 2, 3}, + }, + ExecutionBranch: [][]byte{{1, 2, 3}, {4, 5, 6}}, + }, + }, + }, + NextSyncCommittee: ðpbv2.SyncCommittee{ + Pubkeys: nil, + AggregatePubkey: nil, + }, + NextSyncCommitteeBranch: nil, + FinalizedHeader: ðpbv2.LightClientHeaderContainer{ + Header: ðpbv2.LightClientHeaderContainer_HeaderCapella{ + HeaderCapella: ðpbv2.LightClientHeaderCapella{ + Beacon: ðpbv1.BeaconBlockHeader{ + Slot: 12, + ProposerIndex: 1, + ParentRoot: []byte{1, 1, 1}, + StateRoot: []byte{1, 1, 1}, + BodyRoot: []byte{1, 1, 1}, + }, + Execution: &enginev1.ExecutionPayloadHeaderCapella{ + FeeRecipient: []byte{1, 2, 3}, + }, + ExecutionBranch: [][]byte{{1, 2, 3}, {4, 5, 6}}, + }, + }, + }, + FinalityBranch: nil, + SyncAggregate: ðpbv1.SyncAggregate{ + SyncCommitteeBits: []byte{1, 1, 1}, + SyncCommitteeSignature: []byte{1, 1, 1}, + }, + SignatureSlot: 7, + } + + updates[i] = update + + err = db.SaveLightClientUpdate(ctx, uint64(updatePeriod), ðpbv2.LightClientUpdateWithVersion{ + Version: version.Capella, + Data: update, + }) + require.NoError(t, err) + + updatePeriod++ } - signedBlock, err := blocks.NewSignedBeaconBlock(block) - require.NoError(t, err) - - h, err := signedBlock.Header() - require.NoError(t, err) - - err = st.SetLatestBlockHeader(h.Header) - require.NoError(t, err) - stateRoot, err := st.HashTreeRoot(ctx) - require.NoError(t, err) - - // get a new signed block so the root is updated with the new state root - block.Block.StateRoot = stateRoot[:] - signedBlock, err = blocks.NewSignedBeaconBlock(block) - require.NoError(t, err) - - root, err := block.Block.HashTreeRoot() - require.NoError(t, err) - - mockBlocker := &testutil.MockBlocker{ - RootBlockMap: map[[32]byte]interfaces.ReadOnlySignedBeaconBlock{ - parentRoot: signedParent, - root: signedBlock, - }, - SlotBlockMap: map[primitives.Slot]interfaces.ReadOnlySignedBeaconBlock{ - slot.Sub(1): signedParent, - slot: signedBlock, - }, - } - mockChainService := &mock.ChainService{Optimistic: true, Slot: &slot, State: st} + mockBlocker := &testutil.MockBlocker{} + mockChainService := &mock.ChainService{Optimistic: true, Slot: &headSlot, State: st} s := &Server{ - Stater: &testutil.MockStater{StatesBySlot: map[primitives.Slot]state.BeaconState{ - slot.Sub(1): attestedState, - slot: st, - }}, + Stater: &testutil.MockStater{}, Blocker: mockBlocker, HeadFetcher: mockChainService, + BeaconDB: db, } - startPeriod := slot.Div(uint64(config.EpochsPerSyncCommitteePeriod)).Div(uint64(config.SlotsPerEpoch)) - count := 129 // config.MaxRequestLightClientUpdates is 128 - url := fmt.Sprintf("http://foo.com/?count=%d&start_period=%d", count, startPeriod) + startPeriod := slot.Sub(1).Div(uint64(config.EpochsPerSyncCommitteePeriod)).Div(uint64(config.SlotsPerEpoch)) + url := fmt.Sprintf("http://foo.com/?count=100&start_period=%d", startPeriod) request := httptest.NewRequest("GET", url, nil) writer := httptest.NewRecorder() writer.Body = &bytes.Buffer{} @@ -748,211 +690,106 @@ func TestLightClientHandler_GetLightClientUpdatesByRange_TooBigInputCountCapella var resp structs.LightClientUpdatesByRangeResponse err = json.Unmarshal(writer.Body.Bytes(), &resp.Updates) require.NoError(t, err) - var respHeader structs.LightClientHeaderCapella - err = json.Unmarshal(resp.Updates[0].Data.AttestedHeader, &respHeader) - require.NoError(t, err) - require.Equal(t, 1, len(resp.Updates)) // Even with big count input, the response is still the max available period, which is 1 in test case. - require.Equal(t, "capella", resp.Updates[0].Version) - require.Equal(t, hexutil.Encode(attestedHeader.BodyRoot), respHeader.Beacon.BodyRoot) - require.NotNil(t, resp) -} - -func TestLightClientHandler_GetLightClientUpdatesByRange_TooBigInputCountDeneb(t *testing.T) { - helpers.ClearCache() - ctx := context.Background() - config := params.BeaconConfig() - slot := primitives.Slot(config.DenebForkEpoch * primitives.Epoch(config.SlotsPerEpoch)).Add(1) - - attestedState, err := util.NewBeaconStateDeneb() - require.NoError(t, err) - err = attestedState.SetSlot(slot.Sub(1)) - require.NoError(t, err) - - parent := util.NewBeaconBlockDeneb() - parent.Block.Slot = slot.Sub(1) - - signedParent, err := blocks.NewSignedBeaconBlock(parent) - require.NoError(t, err) - - parentHeader, err := signedParent.Header() - require.NoError(t, err) - attestedHeader := parentHeader.Header - - err = attestedState.SetLatestBlockHeader(attestedHeader) - require.NoError(t, err) - attestedStateRoot, err := attestedState.HashTreeRoot(ctx) - require.NoError(t, err) - - // get a new signed block so the root is updated with the new state root - parent.Block.StateRoot = attestedStateRoot[:] - signedParent, err = blocks.NewSignedBeaconBlock(parent) - require.NoError(t, err) - - st, err := util.NewBeaconStateDeneb() - require.NoError(t, err) - err = st.SetSlot(slot) - require.NoError(t, err) - - parentRoot, err := signedParent.Block().HashTreeRoot() - require.NoError(t, err) - - block := util.NewBeaconBlockDeneb() - block.Block.Slot = slot - block.Block.ParentRoot = parentRoot[:] - - for i := uint64(0); i < config.SyncCommitteeSize; i++ { - block.Block.Body.SyncAggregate.SyncCommitteeBits.SetBitAt(i, true) - } - - signedBlock, err := blocks.NewSignedBeaconBlock(block) - require.NoError(t, err) - - h, err := signedBlock.Header() - require.NoError(t, err) - - err = st.SetLatestBlockHeader(h.Header) - require.NoError(t, err) - stateRoot, err := st.HashTreeRoot(ctx) - require.NoError(t, err) - - // get a new signed block so the root is updated with the new state root - block.Block.StateRoot = stateRoot[:] - signedBlock, err = blocks.NewSignedBeaconBlock(block) - require.NoError(t, err) - - root, err := block.Block.HashTreeRoot() - require.NoError(t, err) - - mockBlocker := &testutil.MockBlocker{ - RootBlockMap: map[[32]byte]interfaces.ReadOnlySignedBeaconBlock{ - parentRoot: signedParent, - root: signedBlock, - }, - SlotBlockMap: map[primitives.Slot]interfaces.ReadOnlySignedBeaconBlock{ - slot.Sub(1): signedParent, - slot: signedBlock, - }, - } - mockChainService := &mock.ChainService{Optimistic: true, Slot: &slot, State: st} - s := &Server{ - Stater: &testutil.MockStater{StatesBySlot: map[primitives.Slot]state.BeaconState{ - slot.Sub(1): attestedState, - slot: st, - }}, - Blocker: mockBlocker, - HeadFetcher: mockChainService, + require.Equal(t, 100, len(resp.Updates)) + for i, update := range updates { + require.Equal(t, "capella", resp.Updates[i].Version) + updateJson, err := structs.LightClientUpdateFromConsensus(update) + require.NoError(t, err) + require.DeepEqual(t, updateJson, resp.Updates[i].Data) } - startPeriod := slot.Div(uint64(config.EpochsPerSyncCommitteePeriod)).Div(uint64(config.SlotsPerEpoch)) - count := 129 // config.MaxRequestLightClientUpdates is 128 - url := fmt.Sprintf("http://foo.com/?count=%d&start_period=%d", count, startPeriod) - request := httptest.NewRequest("GET", url, nil) - writer := httptest.NewRecorder() - writer.Body = &bytes.Buffer{} - - s.GetLightClientUpdatesByRange(writer, request) - - require.Equal(t, http.StatusOK, writer.Code) - var resp structs.LightClientUpdatesByRangeResponse - err = json.Unmarshal(writer.Body.Bytes(), &resp.Updates) - require.NoError(t, err) - var respHeader structs.LightClientHeaderDeneb - err = json.Unmarshal(resp.Updates[0].Data.AttestedHeader, &respHeader) - require.NoError(t, err) - require.Equal(t, 1, len(resp.Updates)) // Even with big count input, the response is still the max available period, which is 1 in test case. - require.Equal(t, "deneb", resp.Updates[0].Version) - require.Equal(t, hexutil.Encode(attestedHeader.BodyRoot), respHeader.Beacon.BodyRoot) - require.NotNil(t, resp) } -// TODO - check for not having any blocks from the min period, and startPeriod being too early -func TestLightClientHandler_GetLightClientUpdatesByRange_TooEarlyPeriodAltair(t *testing.T) { - helpers.ClearCache() - ctx := context.Background() - config := params.BeaconConfig() - slot := primitives.Slot(config.AltairForkEpoch * primitives.Epoch(config.SlotsPerEpoch)).Add(1) - - attestedState, err := util.NewBeaconStateAltair() - require.NoError(t, err) - err = attestedState.SetSlot(slot.Sub(1)) - require.NoError(t, err) - - parent := util.NewBeaconBlockAltair() - parent.Block.Slot = slot.Sub(1) - - signedParent, err := blocks.NewSignedBeaconBlock(parent) - require.NoError(t, err) - - parentHeader, err := signedParent.Header() - require.NoError(t, err) - attestedHeader := parentHeader.Header - - err = attestedState.SetLatestBlockHeader(attestedHeader) - require.NoError(t, err) - attestedStateRoot, err := attestedState.HashTreeRoot(ctx) - require.NoError(t, err) - - // get a new signed block so the root is updated with the new state root - parent.Block.StateRoot = attestedStateRoot[:] - signedParent, err = blocks.NewSignedBeaconBlock(parent) - require.NoError(t, err) - - st, err := util.NewBeaconStateAltair() - require.NoError(t, err) - err = st.SetSlot(slot) - require.NoError(t, err) - - parentRoot, err := signedParent.Block().HashTreeRoot() - require.NoError(t, err) - - block := util.NewBeaconBlockAltair() - block.Block.Slot = slot - block.Block.ParentRoot = parentRoot[:] - - for i := uint64(0); i < config.SyncCommitteeSize; i++ { - block.Block.Body.SyncAggregate.SyncCommitteeBits.SetBitAt(i, true) - } - - signedBlock, err := blocks.NewSignedBeaconBlock(block) - require.NoError(t, err) - - h, err := signedBlock.Header() - require.NoError(t, err) - - err = st.SetLatestBlockHeader(h.Header) - require.NoError(t, err) - stateRoot, err := st.HashTreeRoot(ctx) - require.NoError(t, err) - - // get a new signed block so the root is updated with the new state root - block.Block.StateRoot = stateRoot[:] - signedBlock, err = blocks.NewSignedBeaconBlock(block) - require.NoError(t, err) - - root, err := block.Block.HashTreeRoot() - require.NoError(t, err) +func TestLightClientHandler_GetLightClientUpdatesByRangeMultipleDeneb(t *testing.T) { + helpers.ClearCache() + ctx := context.Background() + config := params.BeaconConfig() + slot := primitives.Slot(config.DenebForkEpoch * primitives.Epoch(config.SlotsPerEpoch)).Add(1) - mockBlocker := &testutil.MockBlocker{ - RootBlockMap: map[[32]byte]interfaces.ReadOnlySignedBeaconBlock{ - parentRoot: signedParent, - root: signedBlock, - }, - SlotBlockMap: map[primitives.Slot]interfaces.ReadOnlySignedBeaconBlock{ - slot.Sub(1): signedParent, - slot: signedBlock, - }, + st, err := util.NewBeaconStateAltair() + require.NoError(t, err) + headSlot := slot.Add(820000) + err = st.SetSlot(headSlot) + require.NoError(t, err) + + db := setupDB(t) + + updates := make([]*ethpbv2.LightClientUpdate, 100) + + updatePeriod := slot.Div(uint64(config.EpochsPerSyncCommitteePeriod)).Div(uint64(config.SlotsPerEpoch)) + + for i := 0; i < 100; i++ { + + newSlot := slot.Add(uint64(i)) + + update := ðpbv2.LightClientUpdate{ + AttestedHeader: ðpbv2.LightClientHeaderContainer{ + Header: ðpbv2.LightClientHeaderContainer_HeaderDeneb{ + HeaderDeneb: ðpbv2.LightClientHeaderDeneb{ + Beacon: ðpbv1.BeaconBlockHeader{ + Slot: newSlot, + ProposerIndex: 1, + ParentRoot: []byte{1, 1, 1}, + StateRoot: []byte{1, 1, 1}, + BodyRoot: []byte{1, 1, 1}, + }, + Execution: &enginev1.ExecutionPayloadHeaderDeneb{ + FeeRecipient: []byte{1, 2, 3}, + }, + ExecutionBranch: [][]byte{{1, 2, 3}, {4, 5, 6}}, + }, + }, + }, + NextSyncCommittee: ðpbv2.SyncCommittee{ + Pubkeys: nil, + AggregatePubkey: nil, + }, + NextSyncCommitteeBranch: nil, + FinalizedHeader: ðpbv2.LightClientHeaderContainer{ + Header: ðpbv2.LightClientHeaderContainer_HeaderDeneb{ + HeaderDeneb: ðpbv2.LightClientHeaderDeneb{ + Beacon: ðpbv1.BeaconBlockHeader{ + Slot: 12, + ProposerIndex: 1, + ParentRoot: []byte{1, 1, 1}, + StateRoot: []byte{1, 1, 1}, + BodyRoot: []byte{1, 1, 1}, + }, + Execution: &enginev1.ExecutionPayloadHeaderDeneb{ + FeeRecipient: []byte{1, 2, 3}, + }, + ExecutionBranch: [][]byte{{1, 2, 3}, {4, 5, 6}}, + }, + }, + }, + FinalityBranch: nil, + SyncAggregate: ðpbv1.SyncAggregate{ + SyncCommitteeBits: []byte{1, 1, 1}, + SyncCommitteeSignature: []byte{1, 1, 1}, + }, + SignatureSlot: 7, + } + + updates[i] = update + + err = db.SaveLightClientUpdate(ctx, uint64(updatePeriod), ðpbv2.LightClientUpdateWithVersion{ + Version: version.Deneb, + Data: update, + }) + require.NoError(t, err) + + updatePeriod++ } - mockChainService := &mock.ChainService{Optimistic: true, Slot: &slot, State: st} + + mockBlocker := &testutil.MockBlocker{} + mockChainService := &mock.ChainService{Optimistic: true, Slot: &headSlot, State: st} s := &Server{ - Stater: &testutil.MockStater{StatesBySlot: map[primitives.Slot]state.BeaconState{ - slot.Sub(1): attestedState, - slot: st, - }}, + Stater: &testutil.MockStater{}, Blocker: mockBlocker, HeadFetcher: mockChainService, + BeaconDB: db, } - startPeriod := 1 // very early period before Altair fork - count := 1 - url := fmt.Sprintf("http://foo.com/?count=%d&start_period=%d", count, startPeriod) + startPeriod := slot.Sub(1).Div(uint64(config.EpochsPerSyncCommitteePeriod)).Div(uint64(config.SlotsPerEpoch)) + url := fmt.Sprintf("http://foo.com/?count=100&start_period=%d", startPeriod) request := httptest.NewRequest("GET", url, nil) writer := httptest.NewRecorder() writer.Body = &bytes.Buffer{} @@ -963,104 +800,435 @@ func TestLightClientHandler_GetLightClientUpdatesByRange_TooEarlyPeriodAltair(t var resp structs.LightClientUpdatesByRangeResponse err = json.Unmarshal(writer.Body.Bytes(), &resp.Updates) require.NoError(t, err) - var respHeader structs.LightClientHeader - err = json.Unmarshal(resp.Updates[0].Data.AttestedHeader, &respHeader) - require.NoError(t, err) - require.Equal(t, 1, len(resp.Updates)) - require.Equal(t, "altair", resp.Updates[0].Version) - require.Equal(t, hexutil.Encode(attestedHeader.BodyRoot), respHeader.Beacon.BodyRoot) - require.NotNil(t, resp) + require.Equal(t, 100, len(resp.Updates)) + for i, update := range updates { + require.Equal(t, "deneb", resp.Updates[i].Version) + updateJson, err := structs.LightClientUpdateFromConsensus(update) + require.NoError(t, err) + require.DeepEqual(t, updateJson, resp.Updates[i].Data) + } } -// TODO - same as above -func TestLightClientHandler_GetLightClientUpdatesByRange_TooBigCountAltair(t *testing.T) { +func TestLightClientHandler_GetLightClientUpdatesByRangeMultipleForksAltairCapella(t *testing.T) { helpers.ClearCache() ctx := context.Background() config := params.BeaconConfig() - slot := primitives.Slot(config.AltairForkEpoch * primitives.Epoch(config.SlotsPerEpoch)).Add(1) + slot := primitives.Slot(config.CapellaForkEpoch * primitives.Epoch(config.SlotsPerEpoch)).Add(1) - attestedState, err := util.NewBeaconStateAltair() - require.NoError(t, err) - err = attestedState.SetSlot(slot.Sub(1)) + st, err := util.NewBeaconStateAltair() require.NoError(t, err) + headSlot := slot.Add(820000) + err = st.SetSlot(headSlot) + require.NoError(t, err) + + db := setupDB(t) + + updates := make([]*ethpbv2.LightClientUpdate, 100) + + updatePeriod := slot.Div(uint64(config.EpochsPerSyncCommitteePeriod)).Div(uint64(config.SlotsPerEpoch)) + updatePeriod-- + for i := 1; i < 51; i++ { + + newSlot := slot.Sub(uint64(i)) + + update := ðpbv2.LightClientUpdate{ + AttestedHeader: ðpbv2.LightClientHeaderContainer{ + Header: ðpbv2.LightClientHeaderContainer_HeaderAltair{ + HeaderAltair: ðpbv2.LightClientHeader{ + Beacon: ðpbv1.BeaconBlockHeader{ + Slot: newSlot, + ProposerIndex: 1, + ParentRoot: []byte{1, 1, 1}, + StateRoot: []byte{1, 1, 1}, + BodyRoot: []byte{1, 1, 1}, + }, + }, + }, + }, + NextSyncCommittee: ðpbv2.SyncCommittee{ + Pubkeys: nil, + AggregatePubkey: nil, + }, + NextSyncCommitteeBranch: nil, + FinalizedHeader: ðpbv2.LightClientHeaderContainer{ + Header: ðpbv2.LightClientHeaderContainer_HeaderAltair{ + HeaderAltair: ðpbv2.LightClientHeader{ + Beacon: ðpbv1.BeaconBlockHeader{ + Slot: 12, + ProposerIndex: 1, + ParentRoot: []byte{1, 1, 1}, + StateRoot: []byte{1, 1, 1}, + BodyRoot: []byte{1, 1, 1}, + }, + }, + }, + }, + FinalityBranch: nil, + SyncAggregate: ðpbv1.SyncAggregate{ + SyncCommitteeBits: []byte{1, 1, 1}, + SyncCommitteeSignature: []byte{1, 1, 1}, + }, + SignatureSlot: newSlot, + } + + updates[50-i] = update + + err = db.SaveLightClientUpdate(ctx, uint64(updatePeriod), ðpbv2.LightClientUpdateWithVersion{ + Version: version.Altair, + Data: update, + }) + require.NoError(t, err) + + updatePeriod-- + } - parent := util.NewBeaconBlockAltair() - parent.Block.Slot = slot.Sub(1) + updatePeriod = slot.Div(uint64(config.EpochsPerSyncCommitteePeriod)).Div(uint64(config.SlotsPerEpoch)) + + for i := 50; i < 100; i++ { + + newSlot := slot.Add(uint64(i)) + + update := ðpbv2.LightClientUpdate{ + AttestedHeader: ðpbv2.LightClientHeaderContainer{ + Header: ðpbv2.LightClientHeaderContainer_HeaderCapella{ + HeaderCapella: ðpbv2.LightClientHeaderCapella{ + Beacon: ðpbv1.BeaconBlockHeader{ + Slot: newSlot, + ProposerIndex: 1, + ParentRoot: []byte{1, 1, 1}, + StateRoot: []byte{1, 1, 1}, + BodyRoot: []byte{1, 1, 1}, + }, + Execution: &enginev1.ExecutionPayloadHeaderCapella{ + FeeRecipient: []byte{1, 2, 3}, + }, + ExecutionBranch: [][]byte{{1, 2, 3}, {4, 5, 6}}, + }, + }, + }, + NextSyncCommittee: ðpbv2.SyncCommittee{ + Pubkeys: nil, + AggregatePubkey: nil, + }, + NextSyncCommitteeBranch: nil, + FinalizedHeader: ðpbv2.LightClientHeaderContainer{ + Header: ðpbv2.LightClientHeaderContainer_HeaderCapella{ + HeaderCapella: ðpbv2.LightClientHeaderCapella{ + Beacon: ðpbv1.BeaconBlockHeader{ + Slot: 12, + ProposerIndex: 1, + ParentRoot: []byte{1, 1, 1}, + StateRoot: []byte{1, 1, 1}, + BodyRoot: []byte{1, 1, 1}, + }, + Execution: &enginev1.ExecutionPayloadHeaderCapella{ + FeeRecipient: []byte{1, 2, 3}, + }, + ExecutionBranch: [][]byte{{1, 2, 3}, {4, 5, 6}}, + }, + }, + }, + FinalityBranch: nil, + SyncAggregate: ðpbv1.SyncAggregate{ + SyncCommitteeBits: []byte{1, 1, 1}, + SyncCommitteeSignature: []byte{1, 1, 1}, + }, + SignatureSlot: newSlot, + } + + updates[i] = update + + err = db.SaveLightClientUpdate(ctx, uint64(updatePeriod), ðpbv2.LightClientUpdateWithVersion{ + Version: version.Capella, + Data: update, + }) + require.NoError(t, err) + + updatePeriod++ + } - signedParent, err := blocks.NewSignedBeaconBlock(parent) - require.NoError(t, err) + mockBlocker := &testutil.MockBlocker{} + mockChainService := &mock.ChainService{Optimistic: true, Slot: &headSlot, State: st} + s := &Server{ + Stater: &testutil.MockStater{}, + Blocker: mockBlocker, + HeadFetcher: mockChainService, + BeaconDB: db, + } + startPeriod := slot.Sub(1).Div(uint64(config.EpochsPerSyncCommitteePeriod)).Div(uint64(config.SlotsPerEpoch)) + url := fmt.Sprintf("http://foo.com/?count=100&start_period=%d", startPeriod.Sub(50)) + request := httptest.NewRequest("GET", url, nil) + writer := httptest.NewRecorder() + writer.Body = &bytes.Buffer{} - parentHeader, err := signedParent.Header() - require.NoError(t, err) - attestedHeader := parentHeader.Header + s.GetLightClientUpdatesByRange(writer, request) - err = attestedState.SetLatestBlockHeader(attestedHeader) - require.NoError(t, err) - attestedStateRoot, err := attestedState.HashTreeRoot(ctx) + require.Equal(t, http.StatusOK, writer.Code) + var resp structs.LightClientUpdatesByRangeResponse + err = json.Unmarshal(writer.Body.Bytes(), &resp.Updates) require.NoError(t, err) + require.Equal(t, 100, len(resp.Updates)) + for i, update := range updates { + if i < 50 { + require.Equal(t, "altair", resp.Updates[i].Version) + } else { + require.Equal(t, "capella", resp.Updates[i].Version) + } + updateJson, err := structs.LightClientUpdateFromConsensus(update) + require.NoError(t, err) + require.DeepEqual(t, updateJson, resp.Updates[i].Data) + } +} - // get a new signed block so the root is updated with the new state root - parent.Block.StateRoot = attestedStateRoot[:] - signedParent, err = blocks.NewSignedBeaconBlock(parent) - require.NoError(t, err) +func TestLightClientHandler_GetLightClientUpdatesByRangeMultipleForksCapellaDeneb(t *testing.T) { + helpers.ClearCache() + ctx := context.Background() + config := params.BeaconConfig() + slot := primitives.Slot(config.DenebForkEpoch * primitives.Epoch(config.SlotsPerEpoch)).Add(1) st, err := util.NewBeaconStateAltair() require.NoError(t, err) - err = st.SetSlot(slot) - require.NoError(t, err) - - parentRoot, err := signedParent.Block().HashTreeRoot() - require.NoError(t, err) - - block := util.NewBeaconBlockAltair() - block.Block.Slot = slot - block.Block.ParentRoot = parentRoot[:] + headSlot := slot.Add(820000) + err = st.SetSlot(headSlot) + require.NoError(t, err) + + db := setupDB(t) + + updates := make([]*ethpbv2.LightClientUpdate, 100) + + updatePeriod := slot.Div(uint64(config.EpochsPerSyncCommitteePeriod)).Div(uint64(config.SlotsPerEpoch)) + updatePeriod-- + for i := 1; i < 51; i++ { + + newSlot := slot.Sub(uint64(i)) + + update := ðpbv2.LightClientUpdate{ + AttestedHeader: ðpbv2.LightClientHeaderContainer{ + Header: ðpbv2.LightClientHeaderContainer_HeaderCapella{ + HeaderCapella: ðpbv2.LightClientHeaderCapella{ + Beacon: ðpbv1.BeaconBlockHeader{ + Slot: newSlot, + ProposerIndex: 1, + ParentRoot: []byte{1, 1, 1}, + StateRoot: []byte{1, 1, 1}, + BodyRoot: []byte{1, 1, 1}, + }, + Execution: &enginev1.ExecutionPayloadHeaderCapella{ + FeeRecipient: []byte{1, 2, 3}, + }, + ExecutionBranch: [][]byte{{1, 2, 3}, {4, 5, 6}}, + }, + }, + }, + NextSyncCommittee: ðpbv2.SyncCommittee{ + Pubkeys: nil, + AggregatePubkey: nil, + }, + NextSyncCommitteeBranch: nil, + FinalizedHeader: ðpbv2.LightClientHeaderContainer{ + Header: ðpbv2.LightClientHeaderContainer_HeaderCapella{ + HeaderCapella: ðpbv2.LightClientHeaderCapella{ + Beacon: ðpbv1.BeaconBlockHeader{ + Slot: 12, + ProposerIndex: 1, + ParentRoot: []byte{1, 1, 1}, + StateRoot: []byte{1, 1, 1}, + BodyRoot: []byte{1, 1, 1}, + }, + Execution: &enginev1.ExecutionPayloadHeaderCapella{ + FeeRecipient: []byte{1, 2, 3}, + }, + ExecutionBranch: [][]byte{{1, 2, 3}, {4, 5, 6}}, + }, + }, + }, + FinalityBranch: nil, + SyncAggregate: ðpbv1.SyncAggregate{ + SyncCommitteeBits: []byte{1, 1, 1}, + SyncCommitteeSignature: []byte{1, 1, 1}, + }, + SignatureSlot: newSlot, + } + + updates[50-i] = update + + err = db.SaveLightClientUpdate(ctx, uint64(updatePeriod), ðpbv2.LightClientUpdateWithVersion{ + Version: version.Capella, + Data: update, + }) + require.NoError(t, err) + + updatePeriod-- + } - for i := uint64(0); i < config.SyncCommitteeSize; i++ { - block.Block.Body.SyncAggregate.SyncCommitteeBits.SetBitAt(i, true) + updatePeriod = slot.Div(uint64(config.EpochsPerSyncCommitteePeriod)).Div(uint64(config.SlotsPerEpoch)) + + for i := 50; i < 100; i++ { + + newSlot := slot.Add(uint64(i)) + + update := ðpbv2.LightClientUpdate{ + AttestedHeader: ðpbv2.LightClientHeaderContainer{ + Header: ðpbv2.LightClientHeaderContainer_HeaderDeneb{ + HeaderDeneb: ðpbv2.LightClientHeaderDeneb{ + Beacon: ðpbv1.BeaconBlockHeader{ + Slot: newSlot, + ProposerIndex: 1, + ParentRoot: []byte{1, 1, 1}, + StateRoot: []byte{1, 1, 1}, + BodyRoot: []byte{1, 1, 1}, + }, + Execution: &enginev1.ExecutionPayloadHeaderDeneb{ + FeeRecipient: []byte{1, 2, 3}, + }, + ExecutionBranch: [][]byte{{1, 2, 3}, {4, 5, 6}}, + }, + }, + }, + NextSyncCommittee: ðpbv2.SyncCommittee{ + Pubkeys: nil, + AggregatePubkey: nil, + }, + NextSyncCommitteeBranch: nil, + FinalizedHeader: ðpbv2.LightClientHeaderContainer{ + Header: ðpbv2.LightClientHeaderContainer_HeaderDeneb{ + HeaderDeneb: ðpbv2.LightClientHeaderDeneb{ + Beacon: ðpbv1.BeaconBlockHeader{ + Slot: 12, + ProposerIndex: 1, + ParentRoot: []byte{1, 1, 1}, + StateRoot: []byte{1, 1, 1}, + BodyRoot: []byte{1, 1, 1}, + }, + Execution: &enginev1.ExecutionPayloadHeaderDeneb{ + FeeRecipient: []byte{1, 2, 3}, + }, + ExecutionBranch: [][]byte{{1, 2, 3}, {4, 5, 6}}, + }, + }, + }, + FinalityBranch: nil, + SyncAggregate: ðpbv1.SyncAggregate{ + SyncCommitteeBits: []byte{1, 1, 1}, + SyncCommitteeSignature: []byte{1, 1, 1}, + }, + SignatureSlot: newSlot, + } + + updates[i] = update + + err = db.SaveLightClientUpdate(ctx, uint64(updatePeriod), ðpbv2.LightClientUpdateWithVersion{ + Version: version.Deneb, + Data: update, + }) + require.NoError(t, err) + + updatePeriod++ } - signedBlock, err := blocks.NewSignedBeaconBlock(block) - require.NoError(t, err) + mockBlocker := &testutil.MockBlocker{} + mockChainService := &mock.ChainService{Optimistic: true, Slot: &headSlot, State: st} + s := &Server{ + Stater: &testutil.MockStater{}, + Blocker: mockBlocker, + HeadFetcher: mockChainService, + BeaconDB: db, + } + startPeriod := slot.Sub(1).Div(uint64(config.EpochsPerSyncCommitteePeriod)).Div(uint64(config.SlotsPerEpoch)) + url := fmt.Sprintf("http://foo.com/?count=100&start_period=%d", startPeriod.Sub(50)) + request := httptest.NewRequest("GET", url, nil) + writer := httptest.NewRecorder() + writer.Body = &bytes.Buffer{} - h, err := signedBlock.Header() - require.NoError(t, err) + s.GetLightClientUpdatesByRange(writer, request) - err = st.SetLatestBlockHeader(h.Header) - require.NoError(t, err) - stateRoot, err := st.HashTreeRoot(ctx) + require.Equal(t, http.StatusOK, writer.Code) + var resp structs.LightClientUpdatesByRangeResponse + err = json.Unmarshal(writer.Body.Bytes(), &resp.Updates) require.NoError(t, err) + require.Equal(t, 100, len(resp.Updates)) + for i, update := range updates { + if i < 50 { + require.Equal(t, "capella", resp.Updates[i].Version) + } else { + require.Equal(t, "deneb", resp.Updates[i].Version) + } + updateJson, err := structs.LightClientUpdateFromConsensus(update) + require.NoError(t, err) + require.DeepEqual(t, updateJson, resp.Updates[i].Data) + } +} - // get a new signed block so the root is updated with the new state root - block.Block.StateRoot = stateRoot[:] - signedBlock, err = blocks.NewSignedBeaconBlock(block) - require.NoError(t, err) +func TestLightClientHandler_GetLightClientUpdatesByRangeCountBiggerThanLimit(t *testing.T) { + helpers.ClearCache() + ctx := context.Background() + config := params.BeaconConfig() + slot := primitives.Slot(config.AltairForkEpoch * primitives.Epoch(config.SlotsPerEpoch)).Add(1) - root, err := block.Block.HashTreeRoot() + st, err := util.NewBeaconStateAltair() require.NoError(t, err) - - mockBlocker := &testutil.MockBlocker{ - RootBlockMap: map[[32]byte]interfaces.ReadOnlySignedBeaconBlock{ - parentRoot: signedParent, - root: signedBlock, - }, - SlotBlockMap: map[primitives.Slot]interfaces.ReadOnlySignedBeaconBlock{ - slot.Sub(1): signedParent, - slot: signedBlock, - }, + headSlot := slot.Add(5000000) + err = st.SetSlot(headSlot) + require.NoError(t, err) + + db := setupDB(t) + + updates := make([]*ethpbv2.LightClientUpdate, 150) + + updatePeriod := slot.Div(uint64(config.EpochsPerSyncCommitteePeriod)).Div(uint64(config.SlotsPerEpoch)) + + for i := 0; i < 150; i++ { + newSlot := slot.Add(uint64(i)) + + update := ðpbv2.LightClientUpdate{ + AttestedHeader: ðpbv2.LightClientHeaderContainer{ + Header: ðpbv2.LightClientHeaderContainer_HeaderAltair{ + HeaderAltair: ðpbv2.LightClientHeader{ + Beacon: ðpbv1.BeaconBlockHeader{ + Slot: newSlot, + ProposerIndex: 1, + ParentRoot: []byte{1, 1, 1}, + StateRoot: []byte{1, 1, 1}, + BodyRoot: []byte{1, 1, 1}, + }, + }, + }, + }, + NextSyncCommittee: ðpbv2.SyncCommittee{ + Pubkeys: nil, + AggregatePubkey: nil, + }, + NextSyncCommitteeBranch: nil, + FinalizedHeader: nil, + FinalityBranch: nil, + SyncAggregate: ðpbv1.SyncAggregate{ + SyncCommitteeBits: []byte{1, 1, 1}, + SyncCommitteeSignature: []byte{1, 1, 1}, + }, + SignatureSlot: 7, + } + + updates[i] = update + + err = db.SaveLightClientUpdate(ctx, uint64(updatePeriod), ðpbv2.LightClientUpdateWithVersion{ + Version: version.Altair, + Data: update, + }) + require.NoError(t, err) + + updatePeriod++ } - mockChainService := &mock.ChainService{Optimistic: true, Slot: &slot, State: st} + + mockBlocker := &testutil.MockBlocker{} + mockChainService := &mock.ChainService{Optimistic: true, Slot: &headSlot, State: st} s := &Server{ - Stater: &testutil.MockStater{StatesBySlot: map[primitives.Slot]state.BeaconState{ - slot.Sub(1): attestedState, - slot: st, - }}, + Stater: &testutil.MockStater{}, Blocker: mockBlocker, HeadFetcher: mockChainService, + BeaconDB: db, } - startPeriod := 1 // very early period before Altair fork - count := 10 // This is big count as we only have one period in test case. - url := fmt.Sprintf("http://foo.com/?count=%d&start_period=%d", count, startPeriod) + startPeriod := slot.Sub(1).Div(uint64(config.EpochsPerSyncCommitteePeriod)).Div(uint64(config.SlotsPerEpoch)) + url := fmt.Sprintf("http://foo.com/?count=150&start_period=%d", startPeriod) request := httptest.NewRequest("GET", url, nil) writer := httptest.NewRecorder() writer.Body = &bytes.Buffer{} @@ -1071,112 +1239,397 @@ func TestLightClientHandler_GetLightClientUpdatesByRange_TooBigCountAltair(t *te var resp structs.LightClientUpdatesByRangeResponse err = json.Unmarshal(writer.Body.Bytes(), &resp.Updates) require.NoError(t, err) - var respHeader structs.LightClientHeader - err = json.Unmarshal(resp.Updates[0].Data.AttestedHeader, &respHeader) - require.NoError(t, err) - require.Equal(t, 1, len(resp.Updates)) - require.Equal(t, "altair", resp.Updates[0].Version) - require.Equal(t, hexutil.Encode(attestedHeader.BodyRoot), respHeader.Beacon.BodyRoot) - require.NotNil(t, resp) + require.Equal(t, 128, len(resp.Updates)) + for i, update := range updates { + if i < 128 { + require.Equal(t, "altair", resp.Updates[i].Version) + updateJson, err := structs.LightClientUpdateFromConsensus(update) + require.NoError(t, err) + require.DeepEqual(t, updateJson, resp.Updates[i].Data) + } + } } -func TestLightClientHandler_GetLightClientUpdatesByRange_BeforeAltair(t *testing.T) { +func TestLightClientHandler_GetLightClientUpdatesByRangeCountBiggerThanMax(t *testing.T) { helpers.ClearCache() ctx := context.Background() config := params.BeaconConfig() - slot := primitives.Slot(config.AltairForkEpoch * primitives.Epoch(config.SlotsPerEpoch)).Sub(1) + slot := primitives.Slot(config.AltairForkEpoch * primitives.Epoch(config.SlotsPerEpoch)).Add(1) - attestedState, err := util.NewBeaconStateCapella() - require.NoError(t, err) - err = attestedState.SetSlot(slot.Sub(1)) + st, err := util.NewBeaconStateAltair() require.NoError(t, err) + headSlot := slot.Add(900000) + err = st.SetSlot(headSlot) + require.NoError(t, err) + + db := setupDB(t) + + updates := make([]*ethpbv2.LightClientUpdate, 150) + + updatePeriod := slot.Div(uint64(config.EpochsPerSyncCommitteePeriod)).Div(uint64(config.SlotsPerEpoch)) + + for i := 0; i < 150; i++ { + newSlot := slot.Add(uint64(i)) + + update := ðpbv2.LightClientUpdate{ + AttestedHeader: ðpbv2.LightClientHeaderContainer{ + Header: ðpbv2.LightClientHeaderContainer_HeaderAltair{ + HeaderAltair: ðpbv2.LightClientHeader{ + Beacon: ðpbv1.BeaconBlockHeader{ + Slot: newSlot, + ProposerIndex: 1, + ParentRoot: []byte{1, 1, 1}, + StateRoot: []byte{1, 1, 1}, + BodyRoot: []byte{1, 1, 1}, + }, + }, + }, + }, + NextSyncCommittee: ðpbv2.SyncCommittee{ + Pubkeys: nil, + AggregatePubkey: nil, + }, + NextSyncCommitteeBranch: nil, + FinalizedHeader: nil, + FinalityBranch: nil, + SyncAggregate: ðpbv1.SyncAggregate{ + SyncCommitteeBits: []byte{1, 1, 1}, + SyncCommitteeSignature: []byte{1, 1, 1}, + }, + SignatureSlot: 7, + } + + updates[i] = update + + err = db.SaveLightClientUpdate(ctx, uint64(updatePeriod), ðpbv2.LightClientUpdateWithVersion{ + Version: version.Altair, + Data: update, + }) + require.NoError(t, err) + + updatePeriod++ + } - parent := util.NewBeaconBlockCapella() - parent.Block.Slot = slot.Sub(1) + mockBlocker := &testutil.MockBlocker{} + mockChainService := &mock.ChainService{Optimistic: true, Slot: &headSlot, State: st} + s := &Server{ + Stater: &testutil.MockStater{}, + Blocker: mockBlocker, + HeadFetcher: mockChainService, + BeaconDB: db, + } + startPeriod := slot.Sub(1).Div(uint64(config.EpochsPerSyncCommitteePeriod)).Div(uint64(config.SlotsPerEpoch)) + url := fmt.Sprintf("http://foo.com/?count=150&start_period=%d", startPeriod) + request := httptest.NewRequest("GET", url, nil) + writer := httptest.NewRecorder() + writer.Body = &bytes.Buffer{} - signedParent, err := blocks.NewSignedBeaconBlock(parent) - require.NoError(t, err) + s.GetLightClientUpdatesByRange(writer, request) - parentHeader, err := signedParent.Header() - require.NoError(t, err) - attestedHeader := parentHeader.Header + maxSlot := slot.Add(900000).Div(uint64(config.SlotsPerEpoch)).Div(uint64(config.EpochsPerSyncCommitteePeriod)) + updatePeriod = slot.Div(uint64(config.EpochsPerSyncCommitteePeriod)).Div(uint64(config.SlotsPerEpoch)) + diffSlots := maxSlot - updatePeriod + 1 - err = attestedState.SetLatestBlockHeader(attestedHeader) - require.NoError(t, err) - attestedStateRoot, err := attestedState.HashTreeRoot(ctx) + require.Equal(t, http.StatusOK, writer.Code) + var resp structs.LightClientUpdatesByRangeResponse + err = json.Unmarshal(writer.Body.Bytes(), &resp.Updates) require.NoError(t, err) + require.Equal(t, int(diffSlots), len(resp.Updates)) + for i, update := range updates { + if i < int(diffSlots) { + require.Equal(t, "altair", resp.Updates[i].Version) + updateJson, err := structs.LightClientUpdateFromConsensus(update) + require.NoError(t, err) + require.DeepEqual(t, updateJson, resp.Updates[i].Data) + } + } +} - // get a new signed block so the root is updated with the new state root - parent.Block.StateRoot = attestedStateRoot[:] - signedParent, err = blocks.NewSignedBeaconBlock(parent) - require.NoError(t, err) +func TestLightClientHandler_GetLightClientUpdatesByRangeStartPeriodBeforeAltair(t *testing.T) { + helpers.ClearCache() + ctx := context.Background() + config := params.BeaconConfig() + slot := primitives.Slot(config.AltairForkEpoch * primitives.Epoch(config.SlotsPerEpoch)).Add(1) - st, err := util.NewBeaconStateCapella() - require.NoError(t, err) - err = st.SetSlot(slot) + st, err := util.NewBeaconStateAltair() require.NoError(t, err) + headSlot := slot.Add(820000) + err = st.SetSlot(headSlot) + require.NoError(t, err) + + db := setupDB(t) + + updates := make([]*ethpbv2.LightClientUpdate, 100) + + updatePeriod := slot.Div(uint64(config.EpochsPerSyncCommitteePeriod)).Div(uint64(config.SlotsPerEpoch)) + + for i := 0; i < 100; i++ { + newSlot := slot.Add(uint64(i)) + + update := ðpbv2.LightClientUpdate{ + AttestedHeader: ðpbv2.LightClientHeaderContainer{ + Header: ðpbv2.LightClientHeaderContainer_HeaderAltair{ + HeaderAltair: ðpbv2.LightClientHeader{ + Beacon: ðpbv1.BeaconBlockHeader{ + Slot: newSlot, + ProposerIndex: 1, + ParentRoot: []byte{1, 1, 1}, + StateRoot: []byte{1, 1, 1}, + BodyRoot: []byte{1, 1, 1}, + }, + }, + }, + }, + NextSyncCommittee: ðpbv2.SyncCommittee{ + Pubkeys: nil, + AggregatePubkey: nil, + }, + NextSyncCommitteeBranch: nil, + FinalizedHeader: nil, + FinalityBranch: nil, + SyncAggregate: ðpbv1.SyncAggregate{ + SyncCommitteeBits: []byte{1, 1, 1}, + SyncCommitteeSignature: []byte{1, 1, 1}, + }, + SignatureSlot: 7, + } + + updates[i] = update + + err = db.SaveLightClientUpdate(ctx, uint64(updatePeriod), ðpbv2.LightClientUpdateWithVersion{ + Version: version.Altair, + Data: update, + }) + require.NoError(t, err) + + updatePeriod++ + } - parentRoot, err := signedParent.Block().HashTreeRoot() - require.NoError(t, err) + mockBlocker := &testutil.MockBlocker{} + mockChainService := &mock.ChainService{Optimistic: true, Slot: &headSlot, State: st} + s := &Server{ + Stater: &testutil.MockStater{}, + Blocker: mockBlocker, + HeadFetcher: mockChainService, + BeaconDB: db, + } + startPeriod := slot.Sub(1).Div(uint64(config.EpochsPerSyncCommitteePeriod)).Div(uint64(config.SlotsPerEpoch)) + url := fmt.Sprintf("http://foo.com/?count=100&start_period=%d", startPeriod-20) + request := httptest.NewRequest("GET", url, nil) + writer := httptest.NewRecorder() + writer.Body = &bytes.Buffer{} - block := util.NewBeaconBlockCapella() - block.Block.Slot = slot - block.Block.ParentRoot = parentRoot[:] + s.GetLightClientUpdatesByRange(writer, request) - for i := uint64(0); i < config.SyncCommitteeSize; i++ { - block.Block.Body.SyncAggregate.SyncCommitteeBits.SetBitAt(i, true) + require.Equal(t, http.StatusOK, writer.Code) + var resp structs.LightClientUpdatesByRangeResponse + err = json.Unmarshal(writer.Body.Bytes(), &resp.Updates) + require.NoError(t, err) + require.Equal(t, 100, len(resp.Updates)) + for i, update := range updates { + require.Equal(t, "altair", resp.Updates[i].Version) + updateJson, err := structs.LightClientUpdateFromConsensus(update) + require.NoError(t, err) + require.DeepEqual(t, updateJson, resp.Updates[i].Data) } +} - signedBlock, err := blocks.NewSignedBeaconBlock(block) - require.NoError(t, err) +func TestLightClientHandler_GetLightClientUpdatesByRangeMissingUpdates(t *testing.T) { + helpers.ClearCache() + ctx := context.Background() + config := params.BeaconConfig() + slot := primitives.Slot(config.AltairForkEpoch * primitives.Epoch(config.SlotsPerEpoch)).Add(1) - h, err := signedBlock.Header() + st, err := util.NewBeaconStateAltair() require.NoError(t, err) + headSlot := slot.Add(820000) + err = st.SetSlot(headSlot) + require.NoError(t, err) + + db := setupDB(t) + + updates := make([]*ethpbv2.LightClientUpdate, 100) + + updatePeriod := slot.Div(uint64(config.EpochsPerSyncCommitteePeriod)).Div(uint64(config.SlotsPerEpoch)) + + validUpdatesLen := 70 + + for i := 0; i < 100; i++ { + if i == validUpdatesLen { // skip this update + updatePeriod++ + continue + } + newSlot := slot.Add(uint64(i)) + + update := ðpbv2.LightClientUpdate{ + AttestedHeader: ðpbv2.LightClientHeaderContainer{ + Header: ðpbv2.LightClientHeaderContainer_HeaderAltair{ + HeaderAltair: ðpbv2.LightClientHeader{ + Beacon: ðpbv1.BeaconBlockHeader{ + Slot: newSlot, + ProposerIndex: 1, + ParentRoot: []byte{1, 1, 1}, + StateRoot: []byte{1, 1, 1}, + BodyRoot: []byte{1, 1, 1}, + }, + }, + }, + }, + NextSyncCommittee: ðpbv2.SyncCommittee{ + Pubkeys: nil, + AggregatePubkey: nil, + }, + NextSyncCommitteeBranch: nil, + FinalizedHeader: nil, + FinalityBranch: nil, + SyncAggregate: ðpbv1.SyncAggregate{ + SyncCommitteeBits: []byte{1, 1, 1}, + SyncCommitteeSignature: []byte{1, 1, 1}, + }, + SignatureSlot: 7, + } + + updates[i] = update + + err = db.SaveLightClientUpdate(ctx, uint64(updatePeriod), ðpbv2.LightClientUpdateWithVersion{ + Version: version.Altair, + Data: update, + }) + require.NoError(t, err) + + updatePeriod++ + } - err = st.SetLatestBlockHeader(h.Header) - require.NoError(t, err) - stateRoot, err := st.HashTreeRoot(ctx) - require.NoError(t, err) + mockBlocker := &testutil.MockBlocker{} + mockChainService := &mock.ChainService{Optimistic: true, Slot: &headSlot, State: st} + s := &Server{ + Stater: &testutil.MockStater{}, + Blocker: mockBlocker, + HeadFetcher: mockChainService, + BeaconDB: db, + } + startPeriod := slot.Sub(1).Div(uint64(config.EpochsPerSyncCommitteePeriod)).Div(uint64(config.SlotsPerEpoch)) + url := fmt.Sprintf("http://foo.com/?count=100&start_period=%d", startPeriod) + request := httptest.NewRequest("GET", url, nil) + writer := httptest.NewRecorder() + writer.Body = &bytes.Buffer{} - // get a new signed block so the root is updated with the new state root - block.Block.StateRoot = stateRoot[:] - signedBlock, err = blocks.NewSignedBeaconBlock(block) - require.NoError(t, err) + s.GetLightClientUpdatesByRange(writer, request) - root, err := block.Block.HashTreeRoot() + require.Equal(t, http.StatusOK, writer.Code) + var resp structs.LightClientUpdatesByRangeResponse + err = json.Unmarshal(writer.Body.Bytes(), &resp.Updates) require.NoError(t, err) + require.Equal(t, validUpdatesLen, len(resp.Updates)) + for i, update := range updates { + if i < validUpdatesLen { + require.Equal(t, "altair", resp.Updates[i].Version) + updateJson, err := structs.LightClientUpdateFromConsensus(update) + require.NoError(t, err) + require.DeepEqual(t, updateJson, resp.Updates[i].Data) + } + } +} - mockBlocker := &testutil.MockBlocker{ - RootBlockMap: map[[32]byte]interfaces.ReadOnlySignedBeaconBlock{ - parentRoot: signedParent, - root: signedBlock, - }, - SlotBlockMap: map[primitives.Slot]interfaces.ReadOnlySignedBeaconBlock{ - slot.Sub(1): signedParent, - slot: signedBlock, - }, +func TestLightClientHandler_GetLightClientUpdatesByRangeMissingUpdates2(t *testing.T) { + helpers.ClearCache() + ctx := context.Background() + config := params.BeaconConfig() + slot := primitives.Slot(config.AltairForkEpoch * primitives.Epoch(config.SlotsPerEpoch)).Add(1) + + st, err := util.NewBeaconStateAltair() + require.NoError(t, err) + headSlot := slot.Add(820000) + err = st.SetSlot(headSlot) + require.NoError(t, err) + + db := setupDB(t) + + updates := make([]*ethpbv2.LightClientUpdate, 100) + + updatePeriod := slot.Div(uint64(config.EpochsPerSyncCommitteePeriod)).Div(uint64(config.SlotsPerEpoch)) + + validUpdatesLen := 0 + + for i := 0; i < 100; i++ { + if i == validUpdatesLen { // skip this update + updatePeriod++ + continue + } + newSlot := slot.Add(uint64(i)) + + update := ðpbv2.LightClientUpdate{ + AttestedHeader: ðpbv2.LightClientHeaderContainer{ + Header: ðpbv2.LightClientHeaderContainer_HeaderAltair{ + HeaderAltair: ðpbv2.LightClientHeader{ + Beacon: ðpbv1.BeaconBlockHeader{ + Slot: newSlot, + ProposerIndex: 1, + ParentRoot: []byte{1, 1, 1}, + StateRoot: []byte{1, 1, 1}, + BodyRoot: []byte{1, 1, 1}, + }, + }, + }, + }, + NextSyncCommittee: ðpbv2.SyncCommittee{ + Pubkeys: nil, + AggregatePubkey: nil, + }, + NextSyncCommitteeBranch: nil, + FinalizedHeader: nil, + FinalityBranch: nil, + SyncAggregate: ðpbv1.SyncAggregate{ + SyncCommitteeBits: []byte{1, 1, 1}, + SyncCommitteeSignature: []byte{1, 1, 1}, + }, + SignatureSlot: 7, + } + + updates[i] = update + + err = db.SaveLightClientUpdate(ctx, uint64(updatePeriod), ðpbv2.LightClientUpdateWithVersion{ + Version: version.Altair, + Data: update, + }) + require.NoError(t, err) + + updatePeriod++ } - mockChainService := &mock.ChainService{Optimistic: true, Slot: &slot, State: st} + + mockBlocker := &testutil.MockBlocker{} + mockChainService := &mock.ChainService{Optimistic: true, Slot: &headSlot, State: st} s := &Server{ - Stater: &testutil.MockStater{StatesBySlot: map[primitives.Slot]state.BeaconState{ - slot.Sub(1): attestedState, - slot: st, - }}, + Stater: &testutil.MockStater{}, Blocker: mockBlocker, HeadFetcher: mockChainService, + BeaconDB: db, } - startPeriod := slot.Div(uint64(config.EpochsPerSyncCommitteePeriod)).Div(uint64(config.SlotsPerEpoch)) - count := 1 - url := fmt.Sprintf("http://foo.com/?count=%d&start_period=%d", count, startPeriod) + startPeriod := slot.Sub(1).Div(uint64(config.EpochsPerSyncCommitteePeriod)).Div(uint64(config.SlotsPerEpoch)) + url := fmt.Sprintf("http://foo.com/?count=100&start_period=%d", startPeriod) request := httptest.NewRequest("GET", url, nil) writer := httptest.NewRecorder() writer.Body = &bytes.Buffer{} s.GetLightClientUpdatesByRange(writer, request) - require.Equal(t, http.StatusNotFound, writer.Code) + require.Equal(t, http.StatusOK, writer.Code) + var resp structs.LightClientUpdatesByRangeResponse + err = json.Unmarshal(writer.Body.Bytes(), &resp.Updates) + require.NoError(t, err) + require.Equal(t, validUpdatesLen, len(resp.Updates)) + for i, update := range updates { + if i < validUpdatesLen { + require.Equal(t, "altair", resp.Updates[i].Version) + updateJson, err := structs.LightClientUpdateFromConsensus(update) + require.NoError(t, err) + require.DeepEqual(t, updateJson, resp.Updates[i].Data) + } + } } +// TestLightClientHandler_GetLightClientFinalityUpdate tests + func TestLightClientHandler_GetLightClientFinalityUpdateAltair(t *testing.T) { helpers.ClearCache() ctx := context.Background() @@ -2047,3 +2500,13 @@ func TestLightClientHandler_GetLightClientEventBlock_NeedFetchParent(t *testing. require.Equal(t, true, syncAggregate.SyncCommitteeBits.Count() >= minSignaturesRequired) require.Equal(t, slot-1, eventBlock.Block().Slot()) } + +// setupDB instantiates and returns a Store instance. +func setupDB(t testing.TB) *kv.Store { + db, err := kv.NewKVStore(context.Background(), t.TempDir()) + require.NoError(t, err, "Failed to instantiate DB") + t.Cleanup(func() { + require.NoError(t, db.Close(), "Failed to close database") + }) + return db +} diff --git a/beacon-chain/rpc/eth/light-client/helpers.go b/beacon-chain/rpc/eth/light-client/helpers.go index ce989a7d567..da19a591fd2 100644 --- a/beacon-chain/rpc/eth/light-client/helpers.go +++ b/beacon-chain/rpc/eth/light-client/helpers.go @@ -245,22 +245,6 @@ func createLightClientBootstrapDeneb(ctx context.Context, state state.BeaconStat return result, nil } -func newLightClientUpdateFromBeaconState( - ctx context.Context, - state state.BeaconState, - block interfaces.ReadOnlySignedBeaconBlock, - attestedState state.BeaconState, - attestedBlock interfaces.ReadOnlySignedBeaconBlock, - finalizedBlock interfaces.ReadOnlySignedBeaconBlock, -) (*structs.LightClientUpdate, error) { - result, err := lightclient.NewLightClientUpdateFromBeaconState(ctx, state, block, attestedState, attestedBlock, finalizedBlock) - if err != nil { - return nil, err - } - - return structs.LightClientUpdateFromConsensus(result) -} - func newLightClientFinalityUpdateFromBeaconState( ctx context.Context, state state.BeaconState,