Add SSZ support for light client updates by range API (#15082)

* create ssz payload

* remove unused function

* remove access to state

* gazelle fix

* fix ssz size for electra finality update

* fix fork digest version problem

* fix chunk size

* fix fork version

* fix fork version

* add tests

* add changelog entry

* add PR link in changelog entry

* fix lint error

* Update beacon-chain/rpc/eth/light-client/handlers.go

Co-authored-by: Preston Van Loon <pvanloon@offchainlabs.com>

* check for context error

* send response in chunks

* remove content disposition header

---------

Co-authored-by: Preston Van Loon <pvanloon@offchainlabs.com>
This commit is contained in:
Bastin
2025-03-26 16:06:03 +01:00
committed by GitHub
parent 1295c987e8
commit 38a6a7a4ea
8 changed files with 524 additions and 104 deletions

View File

@@ -13,18 +13,22 @@ go_library(
"//api/server/structs:go_default_library",
"//beacon-chain/blockchain:go_default_library",
"//beacon-chain/core/light-client:go_default_library",
"//beacon-chain/core/signing:go_default_library",
"//beacon-chain/db:go_default_library",
"//beacon-chain/rpc/eth/shared:go_default_library",
"//beacon-chain/rpc/lookup:go_default_library",
"//config/features:go_default_library",
"//config/params:go_default_library",
"//consensus-types/interfaces:go_default_library",
"//encoding/bytesutil:go_default_library",
"//monitoring/tracing/trace:go_default_library",
"//network/forks:go_default_library",
"//network/httputil:go_default_library",
"//runtime/version:go_default_library",
"//time/slots:go_default_library",
"@com_github_ethereum_go_ethereum//common/hexutil:go_default_library",
"@com_github_pkg_errors//:go_default_library",
"@com_github_wealdtech_go_bytesutil//:go_default_library",
"@com_github_prysmaticlabs_fastssz//:go_default_library",
],
)
@@ -56,5 +60,6 @@ go_test(
"//testing/require:go_default_library",
"//testing/util:go_default_library",
"@com_github_ethereum_go_ethereum//common/hexutil:go_default_library",
"@com_github_prysmaticlabs_fastssz//:go_default_library",
],
)

View File

@@ -8,17 +8,21 @@ import (
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/pkg/errors"
ssz "github.com/prysmaticlabs/fastssz"
"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/core/signing"
"github.com/prysmaticlabs/prysm/v5/beacon-chain/rpc/eth/shared"
"github.com/prysmaticlabs/prysm/v5/config/features"
"github.com/prysmaticlabs/prysm/v5/config/params"
"github.com/prysmaticlabs/prysm/v5/consensus-types/interfaces"
"github.com/prysmaticlabs/prysm/v5/encoding/bytesutil"
"github.com/prysmaticlabs/prysm/v5/monitoring/tracing/trace"
"github.com/prysmaticlabs/prysm/v5/network/forks"
"github.com/prysmaticlabs/prysm/v5/network/httputil"
"github.com/prysmaticlabs/prysm/v5/runtime/version"
"github.com/wealdtech/go-bytesutil"
"github.com/prysmaticlabs/prysm/v5/time/slots"
)
// GetLightClientBootstrap - implements https://github.com/ethereum/beacon-APIs/blob/263f4ed6c263c967f13279c7a9f5629b51c5fc55/apis/beacon/light_client/bootstrap.yaml
@@ -111,27 +115,79 @@ func (s *Server) GetLightClientUpdatesByRange(w http.ResponseWriter, req *http.R
return
}
updates := make([]*structs.LightClientUpdateResponse, 0, len(updatesMap))
if httputil.RespondWithSsz(req) {
w.Header().Set("Content-Type", "application/octet-stream")
for i := startPeriod; i <= endPeriod; i++ {
update, ok := updatesMap[i]
if !ok {
// Only return the first contiguous range of updates
break
for i := startPeriod; i <= endPeriod; i++ {
if ctx.Err() != nil {
httputil.HandleError(w, "Context error: "+ctx.Err().Error(), http.StatusInternalServerError)
}
update, ok := updatesMap[i]
if !ok {
// Only return the first contiguous range of updates
break
}
updateSlot := update.AttestedHeader().Beacon().Slot
updateEpoch := slots.ToEpoch(updateSlot)
updateFork, err := forks.Fork(updateEpoch)
if err != nil {
httputil.HandleError(w, "Could not get fork Version: "+err.Error(), http.StatusInternalServerError)
return
}
forkDigest, err := signing.ComputeForkDigest(updateFork.CurrentVersion, params.BeaconConfig().GenesisValidatorsRoot[:])
if err != nil {
httputil.HandleError(w, "Could not compute fork digest: "+err.Error(), http.StatusInternalServerError)
return
}
updateSSZ, err := update.MarshalSSZ()
if err != nil {
httputil.HandleError(w, "Could not marshal update to SSZ: "+err.Error(), http.StatusInternalServerError)
return
}
var chunkLength []byte
chunkLength = ssz.MarshalUint64(chunkLength, uint64(len(updateSSZ)+4))
if _, err := w.Write(chunkLength); err != nil {
httputil.HandleError(w, "Could not write chunk length: "+err.Error(), http.StatusInternalServerError)
}
if _, err := w.Write(forkDigest[:]); err != nil {
httputil.HandleError(w, "Could not write fork digest: "+err.Error(), http.StatusInternalServerError)
}
if _, err := w.Write(updateSSZ); err != nil {
httputil.HandleError(w, "Could not write update SSZ: "+err.Error(), http.StatusInternalServerError)
}
}
} else {
updates := make([]*structs.LightClientUpdateResponse, 0, len(updatesMap))
for i := startPeriod; i <= endPeriod; i++ {
if ctx.Err() != nil {
httputil.HandleError(w, "Context error: "+ctx.Err().Error(), http.StatusInternalServerError)
}
update, ok := updatesMap[i]
if !ok {
// Only return the first contiguous range of updates
break
}
updateJson, err := structs.LightClientUpdateFromConsensus(update)
if err != nil {
httputil.HandleError(w, "Could not convert light client update: "+err.Error(), http.StatusInternalServerError)
return
}
updateResponse := &structs.LightClientUpdateResponse{
Version: version.String(update.Version()),
Data: updateJson,
}
updates = append(updates, updateResponse)
}
updateJson, err := structs.LightClientUpdateFromConsensus(update)
if err != nil {
httputil.HandleError(w, "Could not convert light client update: "+err.Error(), http.StatusInternalServerError)
return
}
updateResponse := &structs.LightClientUpdateResponse{
Version: version.String(update.Version()),
Data: updateJson,
}
updates = append(updates, updateResponse)
httputil.WriteJson(w, updates)
}
httputil.WriteJson(w, updates)
}
// GetLightClientFinalityUpdate - implements https://github.com/ethereum/beacon-APIs/blob/263f4ed6c263c967f13279c7a9f5629b51c5fc55/apis/beacon/light_client/finality_update.yaml

View File

@@ -12,6 +12,7 @@ import (
"testing"
"github.com/ethereum/go-ethereum/common/hexutil"
ssz "github.com/prysmaticlabs/fastssz"
"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"
@@ -511,6 +512,44 @@ func TestLightClientHandler_GetLightClientByRange(t *testing.T) {
require.DeepEqual(t, updateJson, resp.Updates[0].Data)
})
t.Run("altair ssz", func(t *testing.T) {
slot := primitives.Slot(config.AltairForkEpoch * primitives.Epoch(config.SlotsPerEpoch)).Add(1)
st, err := util.NewBeaconStateAltair()
require.NoError(t, err)
err = st.SetSlot(slot)
require.NoError(t, err)
db := dbtesting.SetupDB(t)
updatePeriod := uint64(slot.Div(uint64(config.EpochsPerSyncCommitteePeriod)).Div(uint64(config.SlotsPerEpoch)))
update, err := createUpdate(t, version.Altair)
require.NoError(t, err)
err = db.SaveLightClientUpdate(ctx, updatePeriod, update)
require.NoError(t, err)
mockChainService := &mock.ChainService{State: st}
s := &Server{
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)
request := httptest.NewRequest("GET", url, nil)
request.Header.Add("Accept", "application/octet-stream")
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
s.GetLightClientUpdatesByRange(writer, request)
require.Equal(t, http.StatusOK, writer.Code)
var resp pb.LightClientUpdateAltair
err = resp.UnmarshalSSZ(writer.Body.Bytes()[12:]) // skip the length and fork digest prefixes
require.NoError(t, err)
require.DeepEqual(t, resp.AttestedHeader, update.AttestedHeader().Proto())
})
t.Run("capella", func(t *testing.T) {
slot := primitives.Slot(config.CapellaForkEpoch * primitives.Epoch(config.SlotsPerEpoch)).Add(1)
@@ -553,6 +592,45 @@ func TestLightClientHandler_GetLightClientByRange(t *testing.T) {
require.DeepEqual(t, updateJson, resp.Updates[0].Data)
})
t.Run("capella ssz", func(t *testing.T) {
slot := primitives.Slot(config.CapellaForkEpoch * primitives.Epoch(config.SlotsPerEpoch)).Add(1)
st, err := util.NewBeaconStateCapella()
require.NoError(t, err)
err = st.SetSlot(slot)
require.NoError(t, err)
db := dbtesting.SetupDB(t)
updatePeriod := uint64(slot.Div(uint64(config.EpochsPerSyncCommitteePeriod)).Div(uint64(config.SlotsPerEpoch)))
update, err := createUpdate(t, version.Capella)
require.NoError(t, err)
err = db.SaveLightClientUpdate(ctx, updatePeriod, update)
require.NoError(t, err)
mockChainService := &mock.ChainService{State: st}
s := &Server{
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)
request := httptest.NewRequest("GET", url, nil)
request.Header.Add("Accept", "application/octet-stream")
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
s.GetLightClientUpdatesByRange(writer, request)
require.Equal(t, http.StatusOK, writer.Code)
var resp pb.LightClientUpdateCapella
err = resp.UnmarshalSSZ(writer.Body.Bytes()[12:]) // skip the length and fork digest prefixes
require.NoError(t, err)
require.DeepEqual(t, resp.AttestedHeader, update.AttestedHeader().Proto())
})
t.Run("deneb", func(t *testing.T) {
slot := primitives.Slot(config.DenebForkEpoch * primitives.Epoch(config.SlotsPerEpoch)).Add(1)
@@ -594,6 +672,44 @@ func TestLightClientHandler_GetLightClientByRange(t *testing.T) {
require.DeepEqual(t, updateJson, resp.Updates[0].Data)
})
t.Run("deneb ssz", func(t *testing.T) {
slot := primitives.Slot(config.DenebForkEpoch * primitives.Epoch(config.SlotsPerEpoch)).Add(1)
st, err := util.NewBeaconStateDeneb()
require.NoError(t, err)
err = st.SetSlot(slot)
require.NoError(t, err)
db := dbtesting.SetupDB(t)
updatePeriod := uint64(slot.Div(uint64(config.EpochsPerSyncCommitteePeriod)).Div(uint64(config.SlotsPerEpoch)))
update, err := createUpdate(t, version.Deneb)
require.NoError(t, err)
err = db.SaveLightClientUpdate(ctx, updatePeriod, update)
require.NoError(t, err)
mockChainService := &mock.ChainService{State: st}
s := &Server{
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)
request := httptest.NewRequest("GET", url, nil)
request.Header.Add("Accept", "application/octet-stream")
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
s.GetLightClientUpdatesByRange(writer, request)
require.Equal(t, http.StatusOK, writer.Code)
var resp pb.LightClientUpdateDeneb
err = resp.UnmarshalSSZ(writer.Body.Bytes()[12:]) // skip the length and fork digest prefixes
require.NoError(t, err)
require.DeepEqual(t, resp.AttestedHeader, update.AttestedHeader().Proto())
})
t.Run("altair Multiple", func(t *testing.T) {
slot := primitives.Slot(config.AltairForkEpoch * primitives.Epoch(config.SlotsPerEpoch)).Add(1)
@@ -646,6 +762,60 @@ func TestLightClientHandler_GetLightClientByRange(t *testing.T) {
}
})
t.Run("altair Multiple ssz", func(t *testing.T) {
slot := primitives.Slot(config.AltairForkEpoch * primitives.Epoch(config.SlotsPerEpoch)).Add(1)
st, err := util.NewBeaconStateAltair()
require.NoError(t, err)
headSlot := slot.Add(2 * uint64(config.SlotsPerEpoch) * uint64(config.EpochsPerSyncCommitteePeriod)) // 2 periods
err = st.SetSlot(headSlot)
require.NoError(t, err)
db := dbtesting.SetupDB(t)
updatePeriod := slot.Div(uint64(config.EpochsPerSyncCommitteePeriod)).Div(uint64(config.SlotsPerEpoch))
updates := make([]interfaces.LightClientUpdate, 0)
for i := 1; i <= 2; i++ {
update, err := createUpdate(t, version.Altair)
require.NoError(t, err)
updates = append(updates, update)
}
for _, update := range updates {
err := db.SaveLightClientUpdate(ctx, uint64(updatePeriod), update)
require.NoError(t, err)
updatePeriod++
}
mockChainService := &mock.ChainService{State: st}
s := &Server{
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)
request.Header.Add("Accept", "application/octet-stream")
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
s.GetLightClientUpdatesByRange(writer, request)
require.Equal(t, http.StatusOK, writer.Code)
offset := 0
for i := 0; offset < writer.Body.Len(); i++ {
updateLen := int(ssz.UnmarshallUint64(writer.Body.Bytes()[offset:offset+8]) - 4)
offset += 12
var resp pb.LightClientUpdateAltair
err = resp.UnmarshalSSZ(writer.Body.Bytes()[offset : offset+updateLen])
require.NoError(t, err)
require.DeepEqual(t, resp.AttestedHeader, updates[i].AttestedHeader().Proto())
offset += updateLen
}
})
t.Run("capella Multiple", func(t *testing.T) {
slot := primitives.Slot(config.CapellaForkEpoch * primitives.Epoch(config.SlotsPerEpoch)).Add(1)
@@ -698,6 +868,60 @@ func TestLightClientHandler_GetLightClientByRange(t *testing.T) {
}
})
t.Run("capella Multiple ssz", func(t *testing.T) {
slot := primitives.Slot(config.CapellaForkEpoch * primitives.Epoch(config.SlotsPerEpoch)).Add(1)
st, err := util.NewBeaconStateAltair()
require.NoError(t, err)
headSlot := slot.Add(2 * uint64(config.SlotsPerEpoch) * uint64(config.EpochsPerSyncCommitteePeriod)) // 2 periods
err = st.SetSlot(headSlot)
require.NoError(t, err)
db := dbtesting.SetupDB(t)
updatePeriod := slot.Div(uint64(config.EpochsPerSyncCommitteePeriod)).Div(uint64(config.SlotsPerEpoch))
updates := make([]interfaces.LightClientUpdate, 0)
for i := 0; i < 2; i++ {
update, err := createUpdate(t, version.Capella)
require.NoError(t, err)
updates = append(updates, update)
}
for _, update := range updates {
err := db.SaveLightClientUpdate(ctx, uint64(updatePeriod), update)
require.NoError(t, err)
updatePeriod++
}
mockChainService := &mock.ChainService{State: st}
s := &Server{
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)
request.Header.Add("Accept", "application/octet-stream")
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
s.GetLightClientUpdatesByRange(writer, request)
require.Equal(t, http.StatusOK, writer.Code)
offset := 0
for i := 0; offset < writer.Body.Len(); i++ {
updateLen := int(ssz.UnmarshallUint64(writer.Body.Bytes()[offset:offset+8]) - 4)
offset += 12
var resp pb.LightClientUpdateCapella
err = resp.UnmarshalSSZ(writer.Body.Bytes()[offset : offset+updateLen])
require.NoError(t, err)
require.DeepEqual(t, resp.AttestedHeader, updates[i].AttestedHeader().Proto())
offset += updateLen
}
})
t.Run("deneb Multiple", func(t *testing.T) {
slot := primitives.Slot(config.DenebForkEpoch * primitives.Epoch(config.SlotsPerEpoch)).Add(1)
@@ -749,6 +973,59 @@ func TestLightClientHandler_GetLightClientByRange(t *testing.T) {
}
})
t.Run("deneb Multiple", func(t *testing.T) {
slot := primitives.Slot(config.DenebForkEpoch * primitives.Epoch(config.SlotsPerEpoch)).Add(1)
st, err := util.NewBeaconStateAltair()
require.NoError(t, err)
headSlot := slot.Add(2 * uint64(config.SlotsPerEpoch) * uint64(config.EpochsPerSyncCommitteePeriod))
err = st.SetSlot(headSlot)
require.NoError(t, err)
db := dbtesting.SetupDB(t)
updatePeriod := slot.Div(uint64(config.EpochsPerSyncCommitteePeriod)).Div(uint64(config.SlotsPerEpoch))
updates := make([]interfaces.LightClientUpdate, 0)
for i := 0; i < 2; i++ {
update, err := createUpdate(t, version.Deneb)
require.NoError(t, err)
updates = append(updates, update)
}
for _, update := range updates {
err := db.SaveLightClientUpdate(ctx, uint64(updatePeriod), update)
require.NoError(t, err)
updatePeriod++
}
mockChainService := &mock.ChainService{State: st}
s := &Server{
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)
request.Header.Add("Accept", "application/octet-stream")
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
s.GetLightClientUpdatesByRange(writer, request)
require.Equal(t, http.StatusOK, writer.Code)
offset := 0
for i := 0; offset < writer.Body.Len(); i++ {
updateLen := int(ssz.UnmarshallUint64(writer.Body.Bytes()[offset:offset+8]) - 4)
offset += 12
var resp pb.LightClientUpdateDeneb
err = resp.UnmarshalSSZ(writer.Body.Bytes()[offset : offset+updateLen])
require.NoError(t, err)
require.DeepEqual(t, resp.AttestedHeader, updates[i].AttestedHeader().Proto())
offset += updateLen
}
})
t.Run("multiple forks - altair, capella", func(t *testing.T) {
slotCapella := primitives.Slot(config.CapellaForkEpoch * primitives.Epoch(config.SlotsPerEpoch)).Add(1)
slotAltair := primitives.Slot(config.AltairForkEpoch * primitives.Epoch(config.SlotsPerEpoch)).Add(1)
@@ -809,6 +1086,68 @@ func TestLightClientHandler_GetLightClientByRange(t *testing.T) {
}
})
t.Run("multiple forks - altair, capella - ssz", func(t *testing.T) {
slotCapella := primitives.Slot(config.CapellaForkEpoch * primitives.Epoch(config.SlotsPerEpoch)).Add(1)
slotAltair := primitives.Slot(config.AltairForkEpoch * primitives.Epoch(config.SlotsPerEpoch)).Add(1)
st, err := util.NewBeaconStateAltair()
require.NoError(t, err)
headSlot := slotCapella.Add(1)
err = st.SetSlot(headSlot)
require.NoError(t, err)
db := dbtesting.SetupDB(t)
updates := make([]interfaces.LightClientUpdate, 2)
updatePeriod := slotAltair.Div(uint64(config.EpochsPerSyncCommitteePeriod)).Div(uint64(config.SlotsPerEpoch))
updates[0], err = createUpdate(t, version.Altair)
require.NoError(t, err)
err = db.SaveLightClientUpdate(ctx, uint64(updatePeriod), updates[0])
require.NoError(t, err)
updatePeriod = slotCapella.Div(uint64(config.EpochsPerSyncCommitteePeriod)).Div(uint64(config.SlotsPerEpoch))
updates[1], err = createUpdate(t, version.Capella)
require.NoError(t, err)
err = db.SaveLightClientUpdate(ctx, uint64(updatePeriod), updates[1])
require.NoError(t, err)
mockChainService := &mock.ChainService{State: st}
s := &Server{
HeadFetcher: mockChainService,
BeaconDB: db,
}
startPeriod := 0
url := fmt.Sprintf("http://foo.com/?count=100&start_period=%d", startPeriod)
request := httptest.NewRequest("GET", url, nil)
request.Header.Add("Accept", "application/octet-stream")
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
s.GetLightClientUpdatesByRange(writer, request)
require.Equal(t, http.StatusOK, writer.Code)
offset := 0
updateLen := int(ssz.UnmarshallUint64(writer.Body.Bytes()[offset:offset+8]) - 4)
offset += 12
var resp pb.LightClientUpdateAltair
err = resp.UnmarshalSSZ(writer.Body.Bytes()[offset : offset+updateLen])
require.NoError(t, err)
require.DeepEqual(t, resp.AttestedHeader, updates[0].AttestedHeader().Proto())
offset += updateLen
updateLen = int(ssz.UnmarshallUint64(writer.Body.Bytes()[offset:offset+8]) - 4)
offset += 12
var resp1 pb.LightClientUpdateCapella
err = resp1.UnmarshalSSZ(writer.Body.Bytes()[offset : offset+updateLen])
require.NoError(t, err)
require.DeepEqual(t, resp1.AttestedHeader, updates[1].AttestedHeader().Proto())
})
t.Run("multiple forks - capella, deneb", func(t *testing.T) {
slotDeneb := primitives.Slot(config.DenebForkEpoch * primitives.Epoch(config.SlotsPerEpoch)).Add(1)
slotCapella := primitives.Slot(config.CapellaForkEpoch * primitives.Epoch(config.SlotsPerEpoch)).Add(1)
@@ -869,6 +1208,68 @@ func TestLightClientHandler_GetLightClientByRange(t *testing.T) {
}
})
t.Run("multiple forks - capella, deneb - ssz", func(t *testing.T) {
slotDeneb := primitives.Slot(config.DenebForkEpoch * primitives.Epoch(config.SlotsPerEpoch)).Add(1)
slotCapella := primitives.Slot(config.CapellaForkEpoch * primitives.Epoch(config.SlotsPerEpoch)).Add(1)
st, err := util.NewBeaconStateAltair()
require.NoError(t, err)
headSlot := slotDeneb.Add(1)
err = st.SetSlot(headSlot)
require.NoError(t, err)
db := dbtesting.SetupDB(t)
updates := make([]interfaces.LightClientUpdate, 2)
updatePeriod := slotCapella.Div(uint64(config.EpochsPerSyncCommitteePeriod)).Div(uint64(config.SlotsPerEpoch))
updates[0], err = createUpdate(t, version.Capella)
require.NoError(t, err)
err = db.SaveLightClientUpdate(ctx, uint64(updatePeriod), updates[0])
require.NoError(t, err)
updatePeriod = slotDeneb.Div(uint64(config.EpochsPerSyncCommitteePeriod)).Div(uint64(config.SlotsPerEpoch))
updates[1], err = createUpdate(t, version.Deneb)
require.NoError(t, err)
err = db.SaveLightClientUpdate(ctx, uint64(updatePeriod), updates[1])
require.NoError(t, err)
mockChainService := &mock.ChainService{State: st}
s := &Server{
HeadFetcher: mockChainService,
BeaconDB: db,
}
startPeriod := 1
url := fmt.Sprintf("http://foo.com/?count=100&start_period=%d", startPeriod)
request := httptest.NewRequest("GET", url, nil)
request.Header.Add("Accept", "application/octet-stream")
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
s.GetLightClientUpdatesByRange(writer, request)
require.Equal(t, http.StatusOK, writer.Code)
offset := 0
updateLen := int(ssz.UnmarshallUint64(writer.Body.Bytes()[offset:offset+8]) - 4)
offset += 12
var resp pb.LightClientUpdateCapella
err = resp.UnmarshalSSZ(writer.Body.Bytes()[offset : offset+updateLen])
require.NoError(t, err)
require.DeepEqual(t, resp.AttestedHeader, updates[0].AttestedHeader().Proto())
offset += updateLen
updateLen = int(ssz.UnmarshallUint64(writer.Body.Bytes()[offset:offset+8]) - 4)
offset += 12
var resp1 pb.LightClientUpdateDeneb
err = resp1.UnmarshalSSZ(writer.Body.Bytes()[offset : offset+updateLen])
require.NoError(t, err)
require.DeepEqual(t, resp1.AttestedHeader, updates[1].AttestedHeader().Proto())
})
t.Run("count bigger than limit", func(t *testing.T) {
config.MaxRequestLightClientUpdates = 2
params.OverrideBeaconConfig(config)

View File

@@ -0,0 +1,3 @@
### Added
- Add SSZ support to light client updates by range API. [[PR]](https://github.com/prysmaticlabs/prysm/pull/15082)

View File

@@ -5279,7 +5279,7 @@ func (l *LightClientFinalityUpdateElectra) MarshalSSZ() ([]byte, error) {
// MarshalSSZTo ssz marshals the LightClientFinalityUpdateElectra object to a target array
func (l *LightClientFinalityUpdateElectra) MarshalSSZTo(buf []byte) (dst []byte, err error) {
dst = buf
offset := int(180)
offset := int(400)
// Offset (0) 'AttestedHeader'
dst = ssz.WriteOffset(dst, offset)
@@ -5295,11 +5295,17 @@ func (l *LightClientFinalityUpdateElectra) MarshalSSZTo(buf []byte) (dst []byte,
}
offset += l.FinalizedHeader.SizeSSZ()
// Offset (2) 'FinalityBranch'
dst = ssz.WriteOffset(dst, offset)
for ii := 0; ii < len(l.FinalityBranch); ii++ {
offset += 4
offset += len(l.FinalityBranch[ii])
// Field (2) 'FinalityBranch'
if size := len(l.FinalityBranch); size != 7 {
err = ssz.ErrVectorLengthFn("--.FinalityBranch", size, 7)
return
}
for ii := 0; ii < 7; ii++ {
if size := len(l.FinalityBranch[ii]); size != 32 {
err = ssz.ErrBytesLengthFn("--.FinalityBranch[ii]", size, 32)
return
}
dst = append(dst, l.FinalityBranch[ii]...)
}
// Field (3) 'SyncAggregate'
@@ -5323,26 +5329,6 @@ func (l *LightClientFinalityUpdateElectra) MarshalSSZTo(buf []byte) (dst []byte,
return
}
// Field (2) 'FinalityBranch'
if size := len(l.FinalityBranch); size > 7 {
err = ssz.ErrListTooBigFn("--.FinalityBranch", size, 7)
return
}
{
offset = 4 * len(l.FinalityBranch)
for ii := 0; ii < len(l.FinalityBranch); ii++ {
dst = ssz.WriteOffset(dst, offset)
offset += len(l.FinalityBranch[ii])
}
}
for ii := 0; ii < len(l.FinalityBranch); ii++ {
if size := len(l.FinalityBranch[ii]); size > 32 {
err = ssz.ErrBytesLengthFn("--.FinalityBranch[ii]", size, 32)
return
}
dst = append(dst, l.FinalityBranch[ii]...)
}
return
}
@@ -5350,19 +5336,19 @@ func (l *LightClientFinalityUpdateElectra) MarshalSSZTo(buf []byte) (dst []byte,
func (l *LightClientFinalityUpdateElectra) UnmarshalSSZ(buf []byte) error {
var err error
size := uint64(len(buf))
if size < 180 {
if size < 400 {
return ssz.ErrSize
}
tail := buf
var o0, o1, o2 uint64
var o0, o1 uint64
// Offset (0) 'AttestedHeader'
if o0 = ssz.ReadOffset(buf[0:4]); o0 > size {
return ssz.ErrOffset
}
if o0 != 180 {
if o0 != 400 {
return ssz.ErrInvalidVariableOffset
}
@@ -5371,21 +5357,25 @@ func (l *LightClientFinalityUpdateElectra) UnmarshalSSZ(buf []byte) error {
return ssz.ErrOffset
}
// Offset (2) 'FinalityBranch'
if o2 = ssz.ReadOffset(buf[8:12]); o2 > size || o1 > o2 {
return ssz.ErrOffset
// Field (2) 'FinalityBranch'
l.FinalityBranch = make([][]byte, 7)
for ii := 0; ii < 7; ii++ {
if cap(l.FinalityBranch[ii]) == 0 {
l.FinalityBranch[ii] = make([]byte, 0, len(buf[8:232][ii*32:(ii+1)*32]))
}
l.FinalityBranch[ii] = append(l.FinalityBranch[ii], buf[8:232][ii*32:(ii+1)*32]...)
}
// Field (3) 'SyncAggregate'
if l.SyncAggregate == nil {
l.SyncAggregate = new(SyncAggregate)
}
if err = l.SyncAggregate.UnmarshalSSZ(buf[12:172]); err != nil {
if err = l.SyncAggregate.UnmarshalSSZ(buf[232:392]); err != nil {
return err
}
// Field (4) 'SignatureSlot'
l.SignatureSlot = github_com_prysmaticlabs_prysm_v5_consensus_types_primitives.Slot(ssz.UnmarshallUint64(buf[172:180]))
l.SignatureSlot = github_com_prysmaticlabs_prysm_v5_consensus_types_primitives.Slot(ssz.UnmarshallUint64(buf[392:400]))
// Field (0) 'AttestedHeader'
{
@@ -5400,7 +5390,7 @@ func (l *LightClientFinalityUpdateElectra) UnmarshalSSZ(buf []byte) error {
// Field (1) 'FinalizedHeader'
{
buf = tail[o1:o2]
buf = tail[o1:]
if l.FinalizedHeader == nil {
l.FinalizedHeader = new(LightClientHeaderDeneb)
}
@@ -5408,35 +5398,12 @@ func (l *LightClientFinalityUpdateElectra) UnmarshalSSZ(buf []byte) error {
return err
}
}
// Field (2) 'FinalityBranch'
{
buf = tail[o2:]
num, err := ssz.DecodeDynamicLength(buf, 7)
if err != nil {
return err
}
l.FinalityBranch = make([][]byte, num)
err = ssz.UnmarshalDynamic(buf, num, func(indx int, buf []byte) (err error) {
if len(buf) > 32 {
return ssz.ErrBytesLength
}
if cap(l.FinalityBranch[indx]) == 0 {
l.FinalityBranch[indx] = make([]byte, 0, len(buf))
}
l.FinalityBranch[indx] = append(l.FinalityBranch[indx], buf...)
return nil
})
if err != nil {
return err
}
}
return err
}
// SizeSSZ returns the ssz encoded size in bytes for the LightClientFinalityUpdateElectra object
func (l *LightClientFinalityUpdateElectra) SizeSSZ() (size int) {
size = 180
size = 400
// Field (0) 'AttestedHeader'
if l.AttestedHeader == nil {
@@ -5450,12 +5417,6 @@ func (l *LightClientFinalityUpdateElectra) SizeSSZ() (size int) {
}
size += l.FinalizedHeader.SizeSSZ()
// Field (2) 'FinalityBranch'
for ii := 0; ii < len(l.FinalityBranch); ii++ {
size += 4
size += len(l.FinalityBranch[ii])
}
return
}
@@ -5480,25 +5441,19 @@ func (l *LightClientFinalityUpdateElectra) HashTreeRootWith(hh *ssz.Hasher) (err
// Field (2) 'FinalityBranch'
{
subIndx := hh.Index()
num := uint64(len(l.FinalityBranch))
if num > 7 {
err = ssz.ErrIncorrectListSize
if size := len(l.FinalityBranch); size != 7 {
err = ssz.ErrVectorLengthFn("--.FinalityBranch", size, 7)
return
}
for _, elem := range l.FinalityBranch {
{
elemIndx := hh.Index()
byteLen := uint64(len(elem))
if byteLen > 32 {
err = ssz.ErrIncorrectListSize
return
}
hh.AppendBytes32(elem)
hh.MerkleizeWithMixin(elemIndx, byteLen, (32+31)/32)
subIndx := hh.Index()
for _, i := range l.FinalityBranch {
if len(i) != 32 {
err = ssz.ErrBytesLength
return
}
hh.Append(i)
}
hh.MerkleizeWithMixin(subIndx, num, 7)
hh.Merkleize(subIndx)
}
// Field (3) 'SyncAggregate'

View File

@@ -1262,7 +1262,7 @@ type LightClientFinalityUpdateElectra struct {
AttestedHeader *LightClientHeaderDeneb `protobuf:"bytes,1,opt,name=attested_header,json=attestedHeader,proto3" json:"attested_header,omitempty"`
FinalizedHeader *LightClientHeaderDeneb `protobuf:"bytes,2,opt,name=finalized_header,json=finalizedHeader,proto3" json:"finalized_header,omitempty"`
FinalityBranch [][]byte `protobuf:"bytes,3,rep,name=finality_branch,json=finalityBranch,proto3" json:"finality_branch,omitempty" ssz-max:"7,32"`
FinalityBranch [][]byte `protobuf:"bytes,3,rep,name=finality_branch,json=finalityBranch,proto3" json:"finality_branch,omitempty" ssz-size:"7,32"`
SyncAggregate *SyncAggregate `protobuf:"bytes,4,opt,name=sync_aggregate,json=syncAggregate,proto3" json:"sync_aggregate,omitempty"`
SignatureSlot github_com_prysmaticlabs_prysm_v5_consensus_types_primitives.Slot `protobuf:"varint,5,opt,name=signature_slot,json=signatureSlot,proto3" json:"signature_slot,omitempty" cast-type:"github.com/prysmaticlabs/prysm/v5/consensus-types/primitives.Slot"`
}
@@ -1762,7 +1762,7 @@ var file_proto_prysm_v1alpha1_light_client_proto_rawDesc = []byte{
0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x44, 0x65, 0x6e, 0x65, 0x62, 0x52, 0x0f, 0x66, 0x69, 0x6e,
0x61, 0x6c, 0x69, 0x7a, 0x65, 0x64, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x12, 0x31, 0x0a, 0x0f,
0x66, 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x74, 0x79, 0x5f, 0x62, 0x72, 0x61, 0x6e, 0x63, 0x68, 0x18,
0x03, 0x20, 0x03, 0x28, 0x0c, 0x42, 0x08, 0x92, 0xb5, 0x18, 0x04, 0x37, 0x2c, 0x33, 0x32, 0x52,
0x03, 0x20, 0x03, 0x28, 0x0c, 0x42, 0x08, 0x8a, 0xb5, 0x18, 0x04, 0x37, 0x2c, 0x33, 0x32, 0x52,
0x0e, 0x66, 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x74, 0x79, 0x42, 0x72, 0x61, 0x6e, 0x63, 0x68, 0x12,
0x4b, 0x0a, 0x0e, 0x73, 0x79, 0x6e, 0x63, 0x5f, 0x61, 0x67, 0x67, 0x72, 0x65, 0x67, 0x61, 0x74,
0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65,

View File

@@ -204,7 +204,7 @@ message LightClientUpdateElectra {
message LightClientFinalityUpdateElectra {
LightClientHeaderDeneb attested_header = 1;
LightClientHeaderDeneb finalized_header = 2;
repeated bytes finality_branch = 3 [ (ethereum.eth.ext.ssz_max) = "7,32" ];
repeated bytes finality_branch = 3 [ (ethereum.eth.ext.ssz_size) = "7,32" ];
SyncAggregate sync_aggregate = 4;
uint64 signature_slot = 5 [
(ethereum.eth.ext.cast_type) =

View File

@@ -1,7 +1,7 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.33.0
// protoc v3.21.7
// protoc-gen-go v1.31.0
// protoc v4.25.1
// source: proto/testing/test.proto
package testing