Skip to content

Commit

Permalink
Read LC Bootstraps from DB (#14718)
Browse files Browse the repository at this point in the history
* fix bootstrap handler

* add tests for get bootstrap handler

* changelog

* make CreateDefaultLightClientBootstrap private

* remove fulu test

---------

Co-authored-by: Radosław Kapka <[email protected]>
  • Loading branch information
Inspector-Butters and rkapka authored Jan 10, 2025
1 parent e99df5e commit 39cf2c8
Show file tree
Hide file tree
Showing 4 changed files with 79 additions and 99 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
1 change: 1 addition & 0 deletions beacon-chain/blockchain/process_block.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
24 changes: 8 additions & 16 deletions beacon-chain/rpc/eth/light-client/handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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) {
return
}

// Get the state
state, err := s.Stater.StateBySlot(ctx, blk.Block().Slot())
bootstrap, err := s.BeaconDB.LightClientBootstrap(ctx, blockRoot[:])
if err != nil {
httputil.HandleError(w, "could not get state: "+err.Error(), http.StatusInternalServerError)
httputil.HandleError(w, "Could not get light client bootstrap: "+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)
if bootstrap == nil {
httputil.HandleError(w, "Light client bootstrap not found", http.StatusNotFound)
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{
Expand Down
152 changes: 69 additions & 83 deletions beacon-chain/rpc/eth/light-client/handlers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -90,23 +91,49 @@ 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()

slot := primitives.Slot(params.BeaconConfig().BellatrixForkEpoch * 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}
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[:]))
Expand Down Expand Up @@ -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[:]))
Expand Down Expand Up @@ -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[:]))
Expand Down Expand Up @@ -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}
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()
bootstrap, err := lightclient.NewLightClientBootstrapFromBeaconState(l.Ctx, slot, l.State, l.Block)
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
db := dbtesting.SetupDB(t)

slot := primitives.Slot(params.BeaconConfig().FuluForkEpoch * primitives.Epoch(params.BeaconConfig().SlotsPerEpoch)).Add(1)
blockRoot, err := l.Block.Block().HashTreeRoot()
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(blockRoot[:]))
Expand Down

0 comments on commit 39cf2c8

Please sign in to comment.