diff --git a/CHANGELOG.md b/CHANGELOG.md index 566568a4d5..1329a57bde 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -40,6 +40,7 @@ The format is based on Keep a Changelog, and this project adheres to Semantic Ve - Update our `go-libp2p-pubsub` dependency. - Re-organize the content of files to ease the creation of a new fork boilerplate. - Fixed Metadata errors for peers connected via QUIC. +- Process light client finality updates only for new finalized epochs instead of doing it for every block. ### Deprecated diff --git a/beacon-chain/blockchain/process_block.go b/beacon-chain/blockchain/process_block.go index bd06c40440..646323c0c5 100644 --- a/beacon-chain/blockchain/process_block.go +++ b/beacon-chain/blockchain/process_block.go @@ -69,6 +69,7 @@ func (s *Service) postBlockProcess(cfg *postBlockProcessConfig) error { if features.Get().EnableLightClient && slots.ToEpoch(s.CurrentSlot()) >= params.BeaconConfig().AltairForkEpoch { defer s.processLightClientUpdates(cfg) defer s.saveLightClientUpdate(cfg) + defer s.saveLightClientBootstrap(cfg) } defer s.sendStateFeedOnBlock(cfg) defer reportProcessingTime(startTime) diff --git a/beacon-chain/rpc/eth/light-client/handlers.go b/beacon-chain/rpc/eth/light-client/handlers.go index 5e550c1855..84db74dc06 100644 --- a/beacon-chain/rpc/eth/light-client/handlers.go +++ b/beacon-chain/rpc/eth/light-client/handlers.go @@ -10,7 +10,6 @@ import ( "github.com/pkg/errors" "github.com/prysmaticlabs/prysm/v5/api" "github.com/prysmaticlabs/prysm/v5/api/server/structs" - lightclient "github.com/prysmaticlabs/prysm/v5/beacon-chain/core/light-client" "github.com/prysmaticlabs/prysm/v5/beacon-chain/rpc/eth/shared" "github.com/prysmaticlabs/prysm/v5/config/features" "github.com/prysmaticlabs/prysm/v5/config/params" @@ -35,41 +34,34 @@ func (s *Server) GetLightClientBootstrap(w http.ResponseWriter, req *http.Reques // Get the block blockRootParam, err := hexutil.Decode(req.PathValue("block_root")) if err != nil { - httputil.HandleError(w, "invalid block root: "+err.Error(), http.StatusBadRequest) + httputil.HandleError(w, "Invalid block root: "+err.Error(), http.StatusBadRequest) return } blockRoot := bytesutil.ToBytes32(blockRootParam) - blk, err := s.Blocker.Block(ctx, blockRoot[:]) - if !shared.WriteBlockFetchError(w, blk, err) { + bootstrap, err := s.BeaconDB.LightClientBootstrap(ctx, blockRoot[:]) + if err != nil { + httputil.HandleError(w, "Could not get light client bootstrap: "+err.Error(), http.StatusInternalServerError) + return + } + if bootstrap == nil { + httputil.HandleError(w, "Light client bootstrap not found", http.StatusNotFound) return } - // Get the state - state, err := s.Stater.StateBySlot(ctx, blk.Block().Slot()) - if err != nil { - httputil.HandleError(w, "could not get state: "+err.Error(), http.StatusInternalServerError) - return - } - - bootstrap, err := lightclient.NewLightClientBootstrapFromBeaconState(ctx, s.ChainInfoFetcher.CurrentSlot(), state, blk) - if err != nil { - httputil.HandleError(w, "could not get light client bootstrap: "+err.Error(), http.StatusInternalServerError) - return - } w.Header().Set(api.VersionHeader, version.String(bootstrap.Version())) if httputil.RespondWithSsz(req) { ssz, err := bootstrap.MarshalSSZ() if err != nil { - httputil.HandleError(w, "could not marshal bootstrap to SSZ: "+err.Error(), http.StatusInternalServerError) + httputil.HandleError(w, "Could not marshal bootstrap to SSZ: "+err.Error(), http.StatusInternalServerError) return } httputil.WriteSsz(w, ssz, "light_client_bootstrap.ssz") } else { data, err := structs.LightClientBootstrapFromConsensus(bootstrap) if err != nil { - httputil.HandleError(w, "could not marshal bootstrap to JSON: "+err.Error(), http.StatusInternalServerError) + httputil.HandleError(w, "Could not marshal bootstrap to JSON: "+err.Error(), http.StatusInternalServerError) return } response := &structs.LightClientBootstrapResponse{ diff --git a/beacon-chain/rpc/eth/light-client/handlers_test.go b/beacon-chain/rpc/eth/light-client/handlers_test.go index 8679c25c09..ee15a8d34b 100644 --- a/beacon-chain/rpc/eth/light-client/handlers_test.go +++ b/beacon-chain/rpc/eth/light-client/handlers_test.go @@ -53,27 +53,28 @@ func TestLightClientHandler_GetLightClientBootstrap(t *testing.T) { l := util.NewTestLightClient(t).SetupTestAltair() slot := primitives.Slot(params.BeaconConfig().AltairForkEpoch * primitives.Epoch(params.BeaconConfig().SlotsPerEpoch)).Add(1) - stateRoot, err := l.State.HashTreeRoot(l.Ctx) + blockRoot, err := l.Block.Block().HashTreeRoot() + require.NoError(t, err) + + bootstrap, err := lightclient.NewLightClientBootstrapFromBeaconState(l.Ctx, slot, l.State, l.Block) + require.NoError(t, err) + + db := dbtesting.SetupDB(t) + + err = db.SaveLightClientBootstrap(l.Ctx, blockRoot[:], bootstrap) require.NoError(t, err) - mockBlocker := &testutil.MockBlocker{BlockToReturn: l.Block} - mockChainService := &mock.ChainService{Optimistic: true, Slot: &slot} - mockChainInfoFetcher := &mock.ChainService{Slot: &slot} s := &Server{ - Stater: &testutil.MockStater{StatesBySlot: map[primitives.Slot]state.BeaconState{ - slot: l.State, - }}, - Blocker: mockBlocker, - HeadFetcher: mockChainService, - ChainInfoFetcher: mockChainInfoFetcher, + BeaconDB: db, } request := httptest.NewRequest("GET", "http://foo.com/", nil) - request.SetPathValue("block_root", hexutil.Encode(stateRoot[:])) + request.SetPathValue("block_root", hexutil.Encode(blockRoot[:])) writer := httptest.NewRecorder() writer.Body = &bytes.Buffer{} s.GetLightClientBootstrap(writer, request) require.Equal(t, http.StatusOK, writer.Code) + var resp structs.LightClientBootstrapResponse err = json.Unmarshal(writer.Body.Bytes(), &resp) require.NoError(t, err) @@ -90,6 +91,32 @@ func TestLightClientHandler_GetLightClientBootstrap(t *testing.T) { require.NotNil(t, resp.Data.CurrentSyncCommittee) require.NotNil(t, resp.Data.CurrentSyncCommitteeBranch) }) + t.Run("altair - no bootstrap found", func(t *testing.T) { + l := util.NewTestLightClient(t).SetupTestAltair() + + slot := primitives.Slot(params.BeaconConfig().AltairForkEpoch * primitives.Epoch(params.BeaconConfig().SlotsPerEpoch)).Add(1) + blockRoot, err := l.Block.Block().HashTreeRoot() + require.NoError(t, err) + + bootstrap, err := lightclient.NewLightClientBootstrapFromBeaconState(l.Ctx, slot, l.State, l.Block) + require.NoError(t, err) + + db := dbtesting.SetupDB(t) + + err = db.SaveLightClientBootstrap(l.Ctx, blockRoot[1:], bootstrap) + require.NoError(t, err) + + s := &Server{ + BeaconDB: db, + } + request := httptest.NewRequest("GET", "http://foo.com/", nil) + request.SetPathValue("block_root", hexutil.Encode(blockRoot[:])) + writer := httptest.NewRecorder() + writer.Body = &bytes.Buffer{} + + s.GetLightClientBootstrap(writer, request) + require.Equal(t, http.StatusNotFound, writer.Code) + }) t.Run("bellatrix", func(t *testing.T) { l := util.NewTestLightClient(t).SetupTestBellatrix() @@ -97,16 +124,16 @@ func TestLightClientHandler_GetLightClientBootstrap(t *testing.T) { blockRoot, err := l.Block.Block().HashTreeRoot() require.NoError(t, err) - mockBlocker := &testutil.MockBlocker{BlockToReturn: l.Block} - mockChainService := &mock.ChainService{Optimistic: true, Slot: &slot} - mockChainInfoFetcher := &mock.ChainService{Slot: &slot} + bootstrap, err := lightclient.NewLightClientBootstrapFromBeaconState(l.Ctx, slot, l.State, l.Block) + require.NoError(t, err) + + db := dbtesting.SetupDB(t) + + err = db.SaveLightClientBootstrap(l.Ctx, blockRoot[:], bootstrap) + require.NoError(t, err) + s := &Server{ - Stater: &testutil.MockStater{StatesBySlot: map[primitives.Slot]state.BeaconState{ - slot: l.State, - }}, - Blocker: mockBlocker, - HeadFetcher: mockChainService, - ChainInfoFetcher: mockChainInfoFetcher, + BeaconDB: db, } request := httptest.NewRequest("GET", "http://foo.com/", nil) request.SetPathValue("block_root", hexutil.Encode(blockRoot[:])) @@ -138,16 +165,16 @@ func TestLightClientHandler_GetLightClientBootstrap(t *testing.T) { blockRoot, err := l.Block.Block().HashTreeRoot() require.NoError(t, err) - mockBlocker := &testutil.MockBlocker{BlockToReturn: l.Block} - mockChainService := &mock.ChainService{Optimistic: true, Slot: &slot} - mockChainInfoFetcher := &mock.ChainService{Slot: &slot} + bootstrap, err := lightclient.NewLightClientBootstrapFromBeaconState(l.Ctx, slot, l.State, l.Block) + require.NoError(t, err) + + db := dbtesting.SetupDB(t) + + err = db.SaveLightClientBootstrap(l.Ctx, blockRoot[:], bootstrap) + require.NoError(t, err) + s := &Server{ - Stater: &testutil.MockStater{StatesBySlot: map[primitives.Slot]state.BeaconState{ - slot: l.State, - }}, - Blocker: mockBlocker, - HeadFetcher: mockChainService, - ChainInfoFetcher: mockChainInfoFetcher, + BeaconDB: db, } request := httptest.NewRequest("GET", "http://foo.com/", nil) request.SetPathValue("block_root", hexutil.Encode(blockRoot[:])) @@ -179,16 +206,16 @@ func TestLightClientHandler_GetLightClientBootstrap(t *testing.T) { blockRoot, err := l.Block.Block().HashTreeRoot() require.NoError(t, err) - mockBlocker := &testutil.MockBlocker{BlockToReturn: l.Block} - mockChainService := &mock.ChainService{Optimistic: true, Slot: &slot} - mockChainInfoFetcher := &mock.ChainService{Slot: &slot} + bootstrap, err := lightclient.NewLightClientBootstrapFromBeaconState(l.Ctx, slot, l.State, l.Block) + require.NoError(t, err) + + db := dbtesting.SetupDB(t) + + err = db.SaveLightClientBootstrap(l.Ctx, blockRoot[:], bootstrap) + require.NoError(t, err) + s := &Server{ - Stater: &testutil.MockStater{StatesBySlot: map[primitives.Slot]state.BeaconState{ - slot: l.State, - }}, - Blocker: mockBlocker, - HeadFetcher: mockChainService, - ChainInfoFetcher: mockChainInfoFetcher, + BeaconDB: db, } request := httptest.NewRequest("GET", "http://foo.com/", nil) request.SetPathValue("block_root", hexutil.Encode(blockRoot[:])) @@ -220,57 +247,16 @@ func TestLightClientHandler_GetLightClientBootstrap(t *testing.T) { blockRoot, err := l.Block.Block().HashTreeRoot() require.NoError(t, err) - mockBlocker := &testutil.MockBlocker{BlockToReturn: l.Block} - mockChainService := &mock.ChainService{Optimistic: true, Slot: &slot} - mockChainInfoFetcher := &mock.ChainService{Slot: &slot} + bootstrap, err := lightclient.NewLightClientBootstrapFromBeaconState(l.Ctx, slot, l.State, l.Block) + require.NoError(t, err) + + db := dbtesting.SetupDB(t) + + err = db.SaveLightClientBootstrap(l.Ctx, blockRoot[:], bootstrap) + require.NoError(t, err) + s := &Server{ - Stater: &testutil.MockStater{StatesBySlot: map[primitives.Slot]state.BeaconState{ - slot: l.State, - }}, - Blocker: mockBlocker, - HeadFetcher: mockChainService, - ChainInfoFetcher: mockChainInfoFetcher, - } - request := httptest.NewRequest("GET", "http://foo.com/", nil) - request.SetPathValue("block_root", hexutil.Encode(blockRoot[:])) - writer := httptest.NewRecorder() - writer.Body = &bytes.Buffer{} - - s.GetLightClientBootstrap(writer, request) - require.Equal(t, http.StatusOK, writer.Code) - var resp structs.LightClientBootstrapResponse - err = json.Unmarshal(writer.Body.Bytes(), &resp) - require.NoError(t, err) - var respHeader structs.LightClientHeader - err = json.Unmarshal(resp.Data.Header, &respHeader) - require.NoError(t, err) - require.Equal(t, "electra", resp.Version) - - blockHeader, err := l.Block.Header() - require.NoError(t, err) - require.Equal(t, hexutil.Encode(blockHeader.Header.BodyRoot), respHeader.Beacon.BodyRoot) - require.Equal(t, strconv.FormatUint(uint64(blockHeader.Header.Slot), 10), respHeader.Beacon.Slot) - - require.NotNil(t, resp.Data.CurrentSyncCommittee) - require.NotNil(t, resp.Data.CurrentSyncCommitteeBranch) - }) - t.Run("fulu", func(t *testing.T) { - l := util.NewTestLightClient(t).SetupTestFulu(false) // result is same for true and false - - slot := primitives.Slot(params.BeaconConfig().FuluForkEpoch * primitives.Epoch(params.BeaconConfig().SlotsPerEpoch)).Add(1) - blockRoot, err := l.Block.Block().HashTreeRoot() - require.NoError(t, err) - - mockBlocker := &testutil.MockBlocker{BlockToReturn: l.Block} - mockChainService := &mock.ChainService{Optimistic: true, Slot: &slot} - mockChainInfoFetcher := &mock.ChainService{Slot: &slot} - s := &Server{ - Stater: &testutil.MockStater{StatesBySlot: map[primitives.Slot]state.BeaconState{ - slot: l.State, - }}, - Blocker: mockBlocker, - HeadFetcher: mockChainService, - ChainInfoFetcher: mockChainInfoFetcher, + BeaconDB: db, } request := httptest.NewRequest("GET", "http://foo.com/", nil) request.SetPathValue("block_root", hexutil.Encode(blockRoot[:]))