Read LC Bootstraps from DB (#14718)

* fix bootstrap handler

* add tests for get bootstrap handler

* changelog

* make CreateDefaultLightClientBootstrap private

* remove fulu test

---------

Co-authored-by: Radosław Kapka <rkapka@wp.pl>
This commit is contained in:
Bastin
2025-01-10 17:51:55 +01:00
committed by GitHub
parent e99df5e489
commit 39cf2c8f06
4 changed files with 86 additions and 106 deletions

View File

@@ -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. - Update our `go-libp2p-pubsub` dependency.
- Re-organize the content of files to ease the creation of a new fork boilerplate. - Re-organize the content of files to ease the creation of a new fork boilerplate.
- Fixed Metadata errors for peers connected via QUIC. - 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 ### Deprecated

View File

@@ -69,6 +69,7 @@ func (s *Service) postBlockProcess(cfg *postBlockProcessConfig) error {
if features.Get().EnableLightClient && slots.ToEpoch(s.CurrentSlot()) >= params.BeaconConfig().AltairForkEpoch { if features.Get().EnableLightClient && slots.ToEpoch(s.CurrentSlot()) >= params.BeaconConfig().AltairForkEpoch {
defer s.processLightClientUpdates(cfg) defer s.processLightClientUpdates(cfg)
defer s.saveLightClientUpdate(cfg) defer s.saveLightClientUpdate(cfg)
defer s.saveLightClientBootstrap(cfg)
} }
defer s.sendStateFeedOnBlock(cfg) defer s.sendStateFeedOnBlock(cfg)
defer reportProcessingTime(startTime) defer reportProcessingTime(startTime)

View File

@@ -10,7 +10,6 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/prysmaticlabs/prysm/v5/api" "github.com/prysmaticlabs/prysm/v5/api"
"github.com/prysmaticlabs/prysm/v5/api/server/structs" "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/beacon-chain/rpc/eth/shared"
"github.com/prysmaticlabs/prysm/v5/config/features" "github.com/prysmaticlabs/prysm/v5/config/features"
"github.com/prysmaticlabs/prysm/v5/config/params" "github.com/prysmaticlabs/prysm/v5/config/params"
@@ -35,41 +34,34 @@ func (s *Server) GetLightClientBootstrap(w http.ResponseWriter, req *http.Reques
// Get the block // Get the block
blockRootParam, err := hexutil.Decode(req.PathValue("block_root")) blockRootParam, err := hexutil.Decode(req.PathValue("block_root"))
if err != nil { if err != nil {
httputil.HandleError(w, "invalid block root: "+err.Error(), http.StatusBadRequest) httputil.HandleError(w, "Invalid block root: "+err.Error(), http.StatusBadRequest)
return return
} }
blockRoot := bytesutil.ToBytes32(blockRootParam) blockRoot := bytesutil.ToBytes32(blockRootParam)
blk, err := s.Blocker.Block(ctx, blockRoot[:]) bootstrap, err := s.BeaconDB.LightClientBootstrap(ctx, blockRoot[:])
if !shared.WriteBlockFetchError(w, blk, err) { 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 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())) w.Header().Set(api.VersionHeader, version.String(bootstrap.Version()))
if httputil.RespondWithSsz(req) { if httputil.RespondWithSsz(req) {
ssz, err := bootstrap.MarshalSSZ() ssz, err := bootstrap.MarshalSSZ()
if err != nil { 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 return
} }
httputil.WriteSsz(w, ssz, "light_client_bootstrap.ssz") httputil.WriteSsz(w, ssz, "light_client_bootstrap.ssz")
} else { } else {
data, err := structs.LightClientBootstrapFromConsensus(bootstrap) data, err := structs.LightClientBootstrapFromConsensus(bootstrap)
if err != nil { 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 return
} }
response := &structs.LightClientBootstrapResponse{ response := &structs.LightClientBootstrapResponse{

View File

@@ -53,27 +53,28 @@ func TestLightClientHandler_GetLightClientBootstrap(t *testing.T) {
l := util.NewTestLightClient(t).SetupTestAltair() l := util.NewTestLightClient(t).SetupTestAltair()
slot := primitives.Slot(params.BeaconConfig().AltairForkEpoch * primitives.Epoch(params.BeaconConfig().SlotsPerEpoch)).Add(1) 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) require.NoError(t, err)
mockBlocker := &testutil.MockBlocker{BlockToReturn: l.Block}
mockChainService := &mock.ChainService{Optimistic: true, Slot: &slot}
mockChainInfoFetcher := &mock.ChainService{Slot: &slot}
s := &Server{ s := &Server{
Stater: &testutil.MockStater{StatesBySlot: map[primitives.Slot]state.BeaconState{ BeaconDB: db,
slot: l.State,
}},
Blocker: mockBlocker,
HeadFetcher: mockChainService,
ChainInfoFetcher: mockChainInfoFetcher,
} }
request := httptest.NewRequest("GET", "http://foo.com/", nil) 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 := httptest.NewRecorder()
writer.Body = &bytes.Buffer{} writer.Body = &bytes.Buffer{}
s.GetLightClientBootstrap(writer, request) s.GetLightClientBootstrap(writer, request)
require.Equal(t, http.StatusOK, writer.Code) require.Equal(t, http.StatusOK, writer.Code)
var resp structs.LightClientBootstrapResponse var resp structs.LightClientBootstrapResponse
err = json.Unmarshal(writer.Body.Bytes(), &resp) err = json.Unmarshal(writer.Body.Bytes(), &resp)
require.NoError(t, err) 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.CurrentSyncCommittee)
require.NotNil(t, resp.Data.CurrentSyncCommitteeBranch) 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) { t.Run("bellatrix", func(t *testing.T) {
l := util.NewTestLightClient(t).SetupTestBellatrix() l := util.NewTestLightClient(t).SetupTestBellatrix()
@@ -97,16 +124,16 @@ func TestLightClientHandler_GetLightClientBootstrap(t *testing.T) {
blockRoot, err := l.Block.Block().HashTreeRoot() blockRoot, err := l.Block.Block().HashTreeRoot()
require.NoError(t, err) require.NoError(t, err)
mockBlocker := &testutil.MockBlocker{BlockToReturn: l.Block} bootstrap, err := lightclient.NewLightClientBootstrapFromBeaconState(l.Ctx, slot, l.State, l.Block)
mockChainService := &mock.ChainService{Optimistic: true, Slot: &slot} require.NoError(t, err)
mockChainInfoFetcher := &mock.ChainService{Slot: &slot}
db := dbtesting.SetupDB(t)
err = db.SaveLightClientBootstrap(l.Ctx, blockRoot[:], bootstrap)
require.NoError(t, err)
s := &Server{ s := &Server{
Stater: &testutil.MockStater{StatesBySlot: map[primitives.Slot]state.BeaconState{ BeaconDB: db,
slot: l.State,
}},
Blocker: mockBlocker,
HeadFetcher: mockChainService,
ChainInfoFetcher: mockChainInfoFetcher,
} }
request := httptest.NewRequest("GET", "http://foo.com/", nil) request := httptest.NewRequest("GET", "http://foo.com/", nil)
request.SetPathValue("block_root", hexutil.Encode(blockRoot[:])) request.SetPathValue("block_root", hexutil.Encode(blockRoot[:]))
@@ -138,16 +165,16 @@ func TestLightClientHandler_GetLightClientBootstrap(t *testing.T) {
blockRoot, err := l.Block.Block().HashTreeRoot() blockRoot, err := l.Block.Block().HashTreeRoot()
require.NoError(t, err) require.NoError(t, err)
mockBlocker := &testutil.MockBlocker{BlockToReturn: l.Block} bootstrap, err := lightclient.NewLightClientBootstrapFromBeaconState(l.Ctx, slot, l.State, l.Block)
mockChainService := &mock.ChainService{Optimistic: true, Slot: &slot} require.NoError(t, err)
mockChainInfoFetcher := &mock.ChainService{Slot: &slot}
db := dbtesting.SetupDB(t)
err = db.SaveLightClientBootstrap(l.Ctx, blockRoot[:], bootstrap)
require.NoError(t, err)
s := &Server{ s := &Server{
Stater: &testutil.MockStater{StatesBySlot: map[primitives.Slot]state.BeaconState{ BeaconDB: db,
slot: l.State,
}},
Blocker: mockBlocker,
HeadFetcher: mockChainService,
ChainInfoFetcher: mockChainInfoFetcher,
} }
request := httptest.NewRequest("GET", "http://foo.com/", nil) request := httptest.NewRequest("GET", "http://foo.com/", nil)
request.SetPathValue("block_root", hexutil.Encode(blockRoot[:])) request.SetPathValue("block_root", hexutil.Encode(blockRoot[:]))
@@ -179,16 +206,16 @@ func TestLightClientHandler_GetLightClientBootstrap(t *testing.T) {
blockRoot, err := l.Block.Block().HashTreeRoot() blockRoot, err := l.Block.Block().HashTreeRoot()
require.NoError(t, err) require.NoError(t, err)
mockBlocker := &testutil.MockBlocker{BlockToReturn: l.Block} bootstrap, err := lightclient.NewLightClientBootstrapFromBeaconState(l.Ctx, slot, l.State, l.Block)
mockChainService := &mock.ChainService{Optimistic: true, Slot: &slot} require.NoError(t, err)
mockChainInfoFetcher := &mock.ChainService{Slot: &slot}
db := dbtesting.SetupDB(t)
err = db.SaveLightClientBootstrap(l.Ctx, blockRoot[:], bootstrap)
require.NoError(t, err)
s := &Server{ s := &Server{
Stater: &testutil.MockStater{StatesBySlot: map[primitives.Slot]state.BeaconState{ BeaconDB: db,
slot: l.State,
}},
Blocker: mockBlocker,
HeadFetcher: mockChainService,
ChainInfoFetcher: mockChainInfoFetcher,
} }
request := httptest.NewRequest("GET", "http://foo.com/", nil) request := httptest.NewRequest("GET", "http://foo.com/", nil)
request.SetPathValue("block_root", hexutil.Encode(blockRoot[:])) request.SetPathValue("block_root", hexutil.Encode(blockRoot[:]))
@@ -220,57 +247,16 @@ func TestLightClientHandler_GetLightClientBootstrap(t *testing.T) {
blockRoot, err := l.Block.Block().HashTreeRoot() blockRoot, err := l.Block.Block().HashTreeRoot()
require.NoError(t, err) require.NoError(t, err)
mockBlocker := &testutil.MockBlocker{BlockToReturn: l.Block} bootstrap, err := lightclient.NewLightClientBootstrapFromBeaconState(l.Ctx, slot, l.State, l.Block)
mockChainService := &mock.ChainService{Optimistic: true, Slot: &slot} require.NoError(t, err)
mockChainInfoFetcher := &mock.ChainService{Slot: &slot}
db := dbtesting.SetupDB(t)
err = db.SaveLightClientBootstrap(l.Ctx, blockRoot[:], bootstrap)
require.NoError(t, err)
s := &Server{ s := &Server{
Stater: &testutil.MockStater{StatesBySlot: map[primitives.Slot]state.BeaconState{ BeaconDB: db,
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,
} }
request := httptest.NewRequest("GET", "http://foo.com/", nil) request := httptest.NewRequest("GET", "http://foo.com/", nil)
request.SetPathValue("block_root", hexutil.Encode(blockRoot[:])) request.SetPathValue("block_root", hexutil.Encode(blockRoot[:]))