mirror of
https://github.com/OffchainLabs/prysm.git
synced 2026-01-08 23:18:15 -05:00
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:
@@ -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",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -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,9 +115,59 @@ func (s *Server) GetLightClientUpdatesByRange(w http.ResponseWriter, req *http.R
|
||||
return
|
||||
}
|
||||
|
||||
if httputil.RespondWithSsz(req) {
|
||||
w.Header().Set("Content-Type", "application/octet-stream")
|
||||
|
||||
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
|
||||
@@ -131,8 +185,10 @@ func (s *Server) GetLightClientUpdatesByRange(w http.ResponseWriter, req *http.R
|
||||
}
|
||||
updates = append(updates, updateResponse)
|
||||
}
|
||||
|
||||
httputil.WriteJson(w, updates)
|
||||
}
|
||||
}
|
||||
|
||||
// GetLightClientFinalityUpdate - implements https://github.com/ethereum/beacon-APIs/blob/263f4ed6c263c967f13279c7a9f5629b51c5fc55/apis/beacon/light_client/finality_update.yaml
|
||||
func (s *Server) GetLightClientFinalityUpdate(w http.ResponseWriter, req *http.Request) {
|
||||
|
||||
@@ -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)
|
||||
|
||||
3
changelog/bastin_lightclient-updates-ssz.md
Normal file
3
changelog/bastin_lightclient-updates-ssz.md
Normal file
@@ -0,0 +1,3 @@
|
||||
### Added
|
||||
|
||||
- Add SSZ support to light client updates by range API. [[PR]](https://github.com/prysmaticlabs/prysm/pull/15082)
|
||||
@@ -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'
|
||||
{
|
||||
if size := len(l.FinalityBranch); size != 7 {
|
||||
err = ssz.ErrVectorLengthFn("--.FinalityBranch", size, 7)
|
||||
return
|
||||
}
|
||||
subIndx := hh.Index()
|
||||
num := uint64(len(l.FinalityBranch))
|
||||
if num > 7 {
|
||||
err = ssz.ErrIncorrectListSize
|
||||
for _, i := range l.FinalityBranch {
|
||||
if len(i) != 32 {
|
||||
err = ssz.ErrBytesLength
|
||||
return
|
||||
}
|
||||
for _, elem := range l.FinalityBranch {
|
||||
{
|
||||
elemIndx := hh.Index()
|
||||
byteLen := uint64(len(elem))
|
||||
if byteLen > 32 {
|
||||
err = ssz.ErrIncorrectListSize
|
||||
return
|
||||
hh.Append(i)
|
||||
}
|
||||
hh.AppendBytes32(elem)
|
||||
hh.MerkleizeWithMixin(elemIndx, byteLen, (32+31)/32)
|
||||
}
|
||||
}
|
||||
hh.MerkleizeWithMixin(subIndx, num, 7)
|
||||
hh.Merkleize(subIndx)
|
||||
}
|
||||
|
||||
// Field (3) 'SyncAggregate'
|
||||
|
||||
4
proto/prysm/v1alpha1/light_client.pb.go
generated
4
proto/prysm/v1alpha1/light_client.pb.go
generated
@@ -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,
|
||||
|
||||
@@ -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) =
|
||||
|
||||
4
proto/testing/test.pb.go
generated
4
proto/testing/test.pb.go
generated
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user