mirror of
https://github.com/OffchainLabs/prysm.git
synced 2026-01-08 21:08:10 -05:00
adding ssz for get block endpoint (#15390)
* adding get ssz * adding some tests * gaz * adding ssz to e2e * wip ssz * adding in additional check on header type * remove unused * renaming json rest handler, and adding in usage of use ssz debug flag * fixing unit tests * fixing tests * gaz * radek feedback * Update config/features/config.go Co-authored-by: Radosław Kapka <rkapka@wp.pl> * Update config/features/flags.go Co-authored-by: Radosław Kapka <rkapka@wp.pl> * Update config/features/flags.go Co-authored-by: Radosław Kapka <rkapka@wp.pl> * Update validator/client/beacon-api/get_beacon_block.go Co-authored-by: Radosław Kapka <rkapka@wp.pl> * Update validator/client/beacon-api/get_beacon_block.go Co-authored-by: Radosław Kapka <rkapka@wp.pl> * Update validator/client/beacon-api/get_beacon_block.go Co-authored-by: Radosław Kapka <rkapka@wp.pl> * addressing feedback * missing import * another missing import * fixing tests * gaz * removing unused * gaz * more radek feedback * fixing context * adding in check for non accepted conent type * reverting to not create more edgecases --------- Co-authored-by: Radosław Kapka <rkapka@wp.pl>
This commit is contained in:
@@ -87,7 +87,7 @@ func (acm *CLIManager) prepareBeaconClients(ctx context.Context) (*iface.Validat
|
||||
acm.beaconApiTimeout,
|
||||
)
|
||||
|
||||
restHandler := beaconApi.NewBeaconApiJsonRestHandler(
|
||||
restHandler := beaconApi.NewBeaconApiRestHandler(
|
||||
http.Client{Timeout: acm.beaconApiTimeout},
|
||||
acm.beaconApiEndpoint,
|
||||
)
|
||||
|
||||
@@ -18,7 +18,6 @@ go_library(
|
||||
"genesis.go",
|
||||
"get_beacon_block.go",
|
||||
"index.go",
|
||||
"json_rest_handler.go",
|
||||
"log.go",
|
||||
"metrics.go",
|
||||
"prepare_beacon_proposer.go",
|
||||
@@ -27,6 +26,7 @@ go_library(
|
||||
"propose_exit.go",
|
||||
"prysm_beacon_chain_client.go",
|
||||
"registration.go",
|
||||
"rest_handler_client.go",
|
||||
"state_validators.go",
|
||||
"status.go",
|
||||
"stream_blocks.go",
|
||||
@@ -47,6 +47,7 @@ go_library(
|
||||
"//api/server/structs:go_default_library",
|
||||
"//beacon-chain/core/helpers:go_default_library",
|
||||
"//beacon-chain/core/signing:go_default_library",
|
||||
"//config/features:go_default_library",
|
||||
"//config/fieldparams:go_default_library",
|
||||
"//config/params:go_default_library",
|
||||
"//consensus-types/primitives:go_default_library",
|
||||
@@ -91,7 +92,6 @@ go_test(
|
||||
"genesis_test.go",
|
||||
"get_beacon_block_test.go",
|
||||
"index_test.go",
|
||||
"json_rest_handler_test.go",
|
||||
"prepare_beacon_proposer_test.go",
|
||||
"propose_attestation_test.go",
|
||||
"propose_beacon_block_altair_test.go",
|
||||
@@ -110,6 +110,7 @@ go_test(
|
||||
"propose_exit_test.go",
|
||||
"prysm_beacon_chain_client_test.go",
|
||||
"registration_test.go",
|
||||
"rest_handler_client_test.go",
|
||||
"state_validators_test.go",
|
||||
"status_test.go",
|
||||
"stream_blocks_test.go",
|
||||
@@ -128,6 +129,7 @@ go_test(
|
||||
"//api/server/structs:go_default_library",
|
||||
"//beacon-chain/core/helpers:go_default_library",
|
||||
"//beacon-chain/rpc/eth/shared/testing:go_default_library",
|
||||
"//config/features:go_default_library",
|
||||
"//config/params:go_default_library",
|
||||
"//consensus-types/primitives:go_default_library",
|
||||
"//consensus-types/validator:go_default_library",
|
||||
@@ -145,6 +147,8 @@ go_test(
|
||||
"@com_github_ethereum_go_ethereum//common/hexutil:go_default_library",
|
||||
"@com_github_golang_protobuf//ptypes/empty",
|
||||
"@com_github_pkg_errors//:go_default_library",
|
||||
"@com_github_sirupsen_logrus//:go_default_library",
|
||||
"@com_github_sirupsen_logrus//hooks/test:go_default_library",
|
||||
"@org_golang_google_protobuf//types/known/emptypb:go_default_library",
|
||||
"@org_golang_google_protobuf//types/known/timestamppb:go_default_library",
|
||||
"@org_uber_go_mock//gomock:go_default_library",
|
||||
|
||||
@@ -17,7 +17,7 @@ import (
|
||||
|
||||
type beaconApiChainClient struct {
|
||||
fallbackClient iface.ChainClient
|
||||
jsonRestHandler JsonRestHandler
|
||||
jsonRestHandler RestHandler
|
||||
stateValidatorsProvider StateValidatorsProvider
|
||||
}
|
||||
|
||||
@@ -333,7 +333,7 @@ func (c beaconApiChainClient) ValidatorParticipation(ctx context.Context, in *et
|
||||
return nil, errors.New("beaconApiChainClient.ValidatorParticipation is not implemented. To use a fallback client, pass a fallback client as the last argument of NewBeaconApiChainClientWithFallback.")
|
||||
}
|
||||
|
||||
func NewBeaconApiChainClientWithFallback(jsonRestHandler JsonRestHandler, fallbackClient iface.ChainClient) iface.ChainClient {
|
||||
func NewBeaconApiChainClientWithFallback(jsonRestHandler RestHandler, fallbackClient iface.ChainClient) iface.ChainClient {
|
||||
return &beaconApiChainClient{
|
||||
jsonRestHandler: jsonRestHandler,
|
||||
fallbackClient: fallbackClient,
|
||||
|
||||
@@ -20,7 +20,7 @@ var (
|
||||
|
||||
type beaconApiNodeClient struct {
|
||||
fallbackClient iface.NodeClient
|
||||
jsonRestHandler JsonRestHandler
|
||||
jsonRestHandler RestHandler
|
||||
genesisProvider GenesisProvider
|
||||
healthTracker health.Tracker
|
||||
}
|
||||
@@ -111,7 +111,7 @@ func (c *beaconApiNodeClient) HealthTracker() health.Tracker {
|
||||
return c.healthTracker
|
||||
}
|
||||
|
||||
func NewNodeClientWithFallback(jsonRestHandler JsonRestHandler, fallbackClient iface.NodeClient) iface.NodeClient {
|
||||
func NewNodeClientWithFallback(jsonRestHandler RestHandler, fallbackClient iface.NodeClient) iface.NodeClient {
|
||||
b := &beaconApiNodeClient{
|
||||
jsonRestHandler: jsonRestHandler,
|
||||
fallbackClient: fallbackClient,
|
||||
|
||||
@@ -22,13 +22,13 @@ type beaconApiValidatorClient struct {
|
||||
genesisProvider GenesisProvider
|
||||
dutiesProvider dutiesProvider
|
||||
stateValidatorsProvider StateValidatorsProvider
|
||||
jsonRestHandler JsonRestHandler
|
||||
jsonRestHandler RestHandler
|
||||
beaconBlockConverter BeaconBlockConverter
|
||||
prysmChainClient iface.PrysmChainClient
|
||||
isEventStreamRunning bool
|
||||
}
|
||||
|
||||
func NewBeaconApiValidatorClient(jsonRestHandler JsonRestHandler, opts ...ValidatorClientOpt) iface.ValidatorClient {
|
||||
func NewBeaconApiValidatorClient(jsonRestHandler RestHandler, opts ...ValidatorClientOpt) iface.ValidatorClient {
|
||||
c := &beaconApiValidatorClient{
|
||||
genesisProvider: &beaconApiGenesisProvider{jsonRestHandler: jsonRestHandler},
|
||||
dutiesProvider: beaconApiDutiesProvider{jsonRestHandler: jsonRestHandler},
|
||||
|
||||
@@ -27,7 +27,7 @@ type dutiesProvider interface {
|
||||
}
|
||||
|
||||
type beaconApiDutiesProvider struct {
|
||||
jsonRestHandler JsonRestHandler
|
||||
jsonRestHandler RestHandler
|
||||
}
|
||||
|
||||
type attesterDuty struct {
|
||||
|
||||
@@ -20,7 +20,7 @@ type GenesisProvider interface {
|
||||
}
|
||||
|
||||
type beaconApiGenesisProvider struct {
|
||||
jsonRestHandler JsonRestHandler
|
||||
jsonRestHandler RestHandler
|
||||
genesis *structs.Genesis
|
||||
once sync.Once
|
||||
}
|
||||
|
||||
@@ -6,7 +6,10 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
neturl "net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/OffchainLabs/prysm/v6/api"
|
||||
"github.com/OffchainLabs/prysm/v6/api/apiutil"
|
||||
"github.com/OffchainLabs/prysm/v6/api/server/structs"
|
||||
"github.com/OffchainLabs/prysm/v6/consensus-types/primitives"
|
||||
@@ -22,160 +25,224 @@ func (c *beaconApiValidatorClient) beaconBlock(ctx context.Context, slot primiti
|
||||
if len(graffiti) > 0 {
|
||||
queryParams.Add("graffiti", hexutil.Encode(graffiti))
|
||||
}
|
||||
|
||||
queryUrl := apiutil.BuildURL(fmt.Sprintf("/eth/v3/validator/blocks/%d", slot), queryParams)
|
||||
produceBlockV3ResponseJson := structs.ProduceBlockV3Response{}
|
||||
err := c.jsonRestHandler.Get(ctx, queryUrl, &produceBlockV3ResponseJson)
|
||||
data, header, err := c.jsonRestHandler.GetSSZ(ctx, queryUrl)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return processBlockResponse(
|
||||
produceBlockV3ResponseJson.Version,
|
||||
produceBlockV3ResponseJson.ExecutionPayloadBlinded,
|
||||
json.NewDecoder(bytes.NewReader(produceBlockV3ResponseJson.Data)),
|
||||
)
|
||||
if strings.Contains(header.Get("Content-Type"), api.OctetStreamMediaType) {
|
||||
ver, err := version.FromString(header.Get(api.VersionHeader))
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, fmt.Sprintf("unsupported header version %s", header.Get(api.VersionHeader)))
|
||||
}
|
||||
isBlindedRaw := header.Get(api.ExecutionPayloadBlindedHeader)
|
||||
isBlinded, err := strconv.ParseBool(isBlindedRaw)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return processBlockSSZResponse(ver, data, isBlinded)
|
||||
} else {
|
||||
decoder := json.NewDecoder(bytes.NewBuffer(data))
|
||||
produceBlockV3ResponseJson := structs.ProduceBlockV3Response{}
|
||||
if err = decoder.Decode(&produceBlockV3ResponseJson); err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to decode response body into json for %s", queryUrl)
|
||||
}
|
||||
return processBlockJSONResponse(
|
||||
produceBlockV3ResponseJson.Version,
|
||||
produceBlockV3ResponseJson.ExecutionPayloadBlinded,
|
||||
json.NewDecoder(bytes.NewReader(produceBlockV3ResponseJson.Data)),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// nolint: gocognit
|
||||
func processBlockResponse(ver string, isBlinded bool, decoder *json.Decoder) (*ethpb.GenericBeaconBlock, error) {
|
||||
var response *ethpb.GenericBeaconBlock
|
||||
func processBlockSSZResponse(ver int, data []byte, isBlinded bool) (*ethpb.GenericBeaconBlock, error) {
|
||||
if ver >= version.Fulu {
|
||||
return processBlockSSZResponseFulu(data, isBlinded)
|
||||
}
|
||||
if ver >= version.Electra {
|
||||
return processBlockSSZResponseElectra(data, isBlinded)
|
||||
}
|
||||
if ver >= version.Deneb {
|
||||
return processBlockSSZResponseDeneb(data, isBlinded)
|
||||
}
|
||||
if ver >= version.Capella {
|
||||
return processBlockSSZResponseCapella(data, isBlinded)
|
||||
}
|
||||
if ver >= version.Bellatrix {
|
||||
return processBlockSSZResponseBellatrix(data, isBlinded)
|
||||
}
|
||||
if ver >= version.Altair {
|
||||
block := ðpb.BeaconBlockAltair{}
|
||||
if err := block.UnmarshalSSZ(data); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ðpb.GenericBeaconBlock{Block: ðpb.GenericBeaconBlock_Altair{Altair: block}}, nil
|
||||
}
|
||||
if ver >= version.Phase0 {
|
||||
block := ðpb.BeaconBlock{}
|
||||
if err := block.UnmarshalSSZ(data); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ðpb.GenericBeaconBlock{Block: ðpb.GenericBeaconBlock_Phase0{Phase0: block}}, nil
|
||||
}
|
||||
return nil, fmt.Errorf("unsupported block version %s", version.String(ver))
|
||||
}
|
||||
|
||||
func processBlockSSZResponseFulu(data []byte, isBlinded bool) (*ethpb.GenericBeaconBlock, error) {
|
||||
if isBlinded {
|
||||
blindedBlock := ðpb.BlindedBeaconBlockFulu{}
|
||||
if err := blindedBlock.UnmarshalSSZ(data); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ðpb.GenericBeaconBlock{Block: ðpb.GenericBeaconBlock_BlindedFulu{BlindedFulu: blindedBlock}, IsBlinded: true}, nil
|
||||
}
|
||||
block := ðpb.BeaconBlockContentsFulu{}
|
||||
if err := block.UnmarshalSSZ(data); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ðpb.GenericBeaconBlock{Block: ðpb.GenericBeaconBlock_Fulu{Fulu: block}}, nil
|
||||
}
|
||||
|
||||
func processBlockSSZResponseElectra(data []byte, isBlinded bool) (*ethpb.GenericBeaconBlock, error) {
|
||||
if isBlinded {
|
||||
blindedBlock := ðpb.BlindedBeaconBlockElectra{}
|
||||
if err := blindedBlock.UnmarshalSSZ(data); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ðpb.GenericBeaconBlock{Block: ðpb.GenericBeaconBlock_BlindedElectra{BlindedElectra: blindedBlock}, IsBlinded: true}, nil
|
||||
}
|
||||
block := ðpb.BeaconBlockContentsElectra{}
|
||||
if err := block.UnmarshalSSZ(data); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ðpb.GenericBeaconBlock{Block: ðpb.GenericBeaconBlock_Electra{Electra: block}}, nil
|
||||
}
|
||||
|
||||
func processBlockSSZResponseDeneb(data []byte, isBlinded bool) (*ethpb.GenericBeaconBlock, error) {
|
||||
if isBlinded {
|
||||
blindedBlock := ðpb.BlindedBeaconBlockDeneb{}
|
||||
if err := blindedBlock.UnmarshalSSZ(data); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ðpb.GenericBeaconBlock{Block: ðpb.GenericBeaconBlock_BlindedDeneb{BlindedDeneb: blindedBlock}, IsBlinded: true}, nil
|
||||
}
|
||||
block := ðpb.BeaconBlockContentsDeneb{}
|
||||
if err := block.UnmarshalSSZ(data); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ðpb.GenericBeaconBlock{Block: ðpb.GenericBeaconBlock_Deneb{Deneb: block}}, nil
|
||||
}
|
||||
|
||||
func processBlockSSZResponseCapella(data []byte, isBlinded bool) (*ethpb.GenericBeaconBlock, error) {
|
||||
if isBlinded {
|
||||
blindedBlock := ðpb.BlindedBeaconBlockCapella{}
|
||||
if err := blindedBlock.UnmarshalSSZ(data); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ðpb.GenericBeaconBlock{Block: ðpb.GenericBeaconBlock_BlindedCapella{BlindedCapella: blindedBlock}, IsBlinded: true}, nil
|
||||
}
|
||||
block := ðpb.BeaconBlockCapella{}
|
||||
if err := block.UnmarshalSSZ(data); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ðpb.GenericBeaconBlock{Block: ðpb.GenericBeaconBlock_Capella{Capella: block}}, nil
|
||||
}
|
||||
|
||||
func processBlockSSZResponseBellatrix(data []byte, isBlinded bool) (*ethpb.GenericBeaconBlock, error) {
|
||||
if isBlinded {
|
||||
blindedBlock := ðpb.BlindedBeaconBlockBellatrix{}
|
||||
if err := blindedBlock.UnmarshalSSZ(data); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ðpb.GenericBeaconBlock{Block: ðpb.GenericBeaconBlock_BlindedBellatrix{BlindedBellatrix: blindedBlock}, IsBlinded: true}, nil
|
||||
}
|
||||
block := ðpb.BeaconBlockBellatrix{}
|
||||
if err := block.UnmarshalSSZ(data); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ðpb.GenericBeaconBlock{Block: ðpb.GenericBeaconBlock_Bellatrix{Bellatrix: block}}, nil
|
||||
}
|
||||
|
||||
func convertBlockToGeneric(decoder *json.Decoder, dest ethpb.GenericConverter, version string, isBlinded bool) (*ethpb.GenericBeaconBlock, error) {
|
||||
typeName := version
|
||||
if isBlinded {
|
||||
typeName = "blinded " + typeName
|
||||
}
|
||||
|
||||
if err := decoder.Decode(dest); err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to decode %s block response json", typeName)
|
||||
}
|
||||
|
||||
genericBlock, err := dest.ToGeneric()
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to convert %s block", typeName)
|
||||
}
|
||||
return genericBlock, nil
|
||||
}
|
||||
|
||||
func processBlockJSONResponse(ver string, isBlinded bool, decoder *json.Decoder) (*ethpb.GenericBeaconBlock, error) {
|
||||
if decoder == nil {
|
||||
return nil, errors.New("no produce block json decoder found")
|
||||
}
|
||||
|
||||
switch ver {
|
||||
case version.String(version.Phase0):
|
||||
jsonPhase0Block := structs.BeaconBlock{}
|
||||
if err := decoder.Decode(&jsonPhase0Block); err != nil {
|
||||
return nil, errors.Wrap(err, "failed to decode phase0 block response json")
|
||||
}
|
||||
genericBlock, err := jsonPhase0Block.ToGeneric()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to get phase0 block")
|
||||
}
|
||||
response = genericBlock
|
||||
return convertBlockToGeneric(decoder, &structs.BeaconBlock{}, version.String(version.Phase0), false)
|
||||
|
||||
case version.String(version.Altair):
|
||||
jsonAltairBlock := structs.BeaconBlockAltair{}
|
||||
if err := decoder.Decode(&jsonAltairBlock); err != nil {
|
||||
return nil, errors.Wrap(err, "failed to decode altair block response json")
|
||||
}
|
||||
genericBlock, err := jsonAltairBlock.ToGeneric()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to get altair block")
|
||||
}
|
||||
response = genericBlock
|
||||
return convertBlockToGeneric(decoder, &structs.BeaconBlockAltair{}, "altair", false)
|
||||
|
||||
case version.String(version.Bellatrix):
|
||||
if isBlinded {
|
||||
jsonBellatrixBlock := structs.BlindedBeaconBlockBellatrix{}
|
||||
if err := decoder.Decode(&jsonBellatrixBlock); err != nil {
|
||||
return nil, errors.Wrap(err, "failed to decode blinded bellatrix block response json")
|
||||
}
|
||||
genericBlock, err := jsonBellatrixBlock.ToGeneric()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to get blinded bellatrix block")
|
||||
}
|
||||
response = genericBlock
|
||||
} else {
|
||||
jsonBellatrixBlock := structs.BeaconBlockBellatrix{}
|
||||
if err := decoder.Decode(&jsonBellatrixBlock); err != nil {
|
||||
return nil, errors.Wrap(err, "failed to decode bellatrix block response json")
|
||||
}
|
||||
genericBlock, err := jsonBellatrixBlock.ToGeneric()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to get bellatrix block")
|
||||
}
|
||||
response = genericBlock
|
||||
}
|
||||
return processBellatrixBlock(decoder, isBlinded)
|
||||
|
||||
case version.String(version.Capella):
|
||||
if isBlinded {
|
||||
jsonCapellaBlock := structs.BlindedBeaconBlockCapella{}
|
||||
if err := decoder.Decode(&jsonCapellaBlock); err != nil {
|
||||
return nil, errors.Wrap(err, "failed to decode blinded capella block response json")
|
||||
}
|
||||
genericBlock, err := jsonCapellaBlock.ToGeneric()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to get blinded capella block")
|
||||
}
|
||||
response = genericBlock
|
||||
} else {
|
||||
jsonCapellaBlock := structs.BeaconBlockCapella{}
|
||||
if err := decoder.Decode(&jsonCapellaBlock); err != nil {
|
||||
return nil, errors.Wrap(err, "failed to decode capella block response json")
|
||||
}
|
||||
genericBlock, err := jsonCapellaBlock.ToGeneric()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to get capella block")
|
||||
}
|
||||
response = genericBlock
|
||||
}
|
||||
return processCapellaBlock(decoder, isBlinded)
|
||||
|
||||
case version.String(version.Deneb):
|
||||
if isBlinded {
|
||||
jsonDenebBlock := structs.BlindedBeaconBlockDeneb{}
|
||||
if err := decoder.Decode(&jsonDenebBlock); err != nil {
|
||||
return nil, errors.Wrap(err, "failed to decode blinded deneb block response json")
|
||||
}
|
||||
genericBlock, err := jsonDenebBlock.ToGeneric()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to get blinded deneb block")
|
||||
}
|
||||
response = genericBlock
|
||||
} else {
|
||||
jsonDenebBlockContents := structs.BeaconBlockContentsDeneb{}
|
||||
if err := decoder.Decode(&jsonDenebBlockContents); err != nil {
|
||||
return nil, errors.Wrap(err, "failed to decode deneb block response json")
|
||||
}
|
||||
genericBlock, err := jsonDenebBlockContents.ToGeneric()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to get deneb block")
|
||||
}
|
||||
response = genericBlock
|
||||
}
|
||||
return processDenebBlock(decoder, isBlinded)
|
||||
|
||||
case version.String(version.Electra):
|
||||
if isBlinded {
|
||||
jsonElectraBlock := structs.BlindedBeaconBlockElectra{}
|
||||
if err := decoder.Decode(&jsonElectraBlock); err != nil {
|
||||
return nil, errors.Wrap(err, "failed to decode blinded electra block response json")
|
||||
}
|
||||
genericBlock, err := jsonElectraBlock.ToGeneric()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to get blinded electra block")
|
||||
}
|
||||
response = genericBlock
|
||||
} else {
|
||||
jsonElectraBlockContents := structs.BeaconBlockContentsElectra{}
|
||||
if err := decoder.Decode(&jsonElectraBlockContents); err != nil {
|
||||
return nil, errors.Wrap(err, "failed to decode electra block response json")
|
||||
}
|
||||
genericBlock, err := jsonElectraBlockContents.ToGeneric()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to get electra block")
|
||||
}
|
||||
response = genericBlock
|
||||
}
|
||||
return processElectraBlock(decoder, isBlinded)
|
||||
|
||||
case version.String(version.Fulu):
|
||||
if isBlinded {
|
||||
jsonFuluBlock := structs.BlindedBeaconBlockFulu{}
|
||||
if err := decoder.Decode(&jsonFuluBlock); err != nil {
|
||||
return nil, errors.Wrap(err, "failed to decode blinded fulu block response json")
|
||||
}
|
||||
genericBlock, err := jsonFuluBlock.ToGeneric()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to get blinded fulu block")
|
||||
}
|
||||
response = genericBlock
|
||||
} else {
|
||||
jsonFuluBlockContents := structs.BeaconBlockContentsFulu{}
|
||||
if err := decoder.Decode(&jsonFuluBlockContents); err != nil {
|
||||
return nil, errors.Wrap(err, "failed to decode fulu block response json")
|
||||
}
|
||||
genericBlock, err := jsonFuluBlockContents.ToGeneric()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to get fulu block")
|
||||
}
|
||||
response = genericBlock
|
||||
}
|
||||
return processFuluBlock(decoder, isBlinded)
|
||||
|
||||
default:
|
||||
return nil, errors.Errorf("unsupported consensus version `%s`", ver)
|
||||
}
|
||||
return response, nil
|
||||
}
|
||||
|
||||
func processBellatrixBlock(decoder *json.Decoder, isBlinded bool) (*ethpb.GenericBeaconBlock, error) {
|
||||
if isBlinded {
|
||||
return convertBlockToGeneric(decoder, &structs.BlindedBeaconBlockBellatrix{}, "bellatrix", true)
|
||||
}
|
||||
return convertBlockToGeneric(decoder, &structs.BeaconBlockBellatrix{}, "bellatrix", false)
|
||||
}
|
||||
|
||||
func processCapellaBlock(decoder *json.Decoder, isBlinded bool) (*ethpb.GenericBeaconBlock, error) {
|
||||
if isBlinded {
|
||||
return convertBlockToGeneric(decoder, &structs.BlindedBeaconBlockCapella{}, "capella", true)
|
||||
}
|
||||
return convertBlockToGeneric(decoder, &structs.BeaconBlockCapella{}, "capella", false)
|
||||
}
|
||||
|
||||
func processDenebBlock(decoder *json.Decoder, isBlinded bool) (*ethpb.GenericBeaconBlock, error) {
|
||||
if isBlinded {
|
||||
return convertBlockToGeneric(decoder, &structs.BlindedBeaconBlockDeneb{}, "deneb", true)
|
||||
}
|
||||
return convertBlockToGeneric(decoder, &structs.BeaconBlockContentsDeneb{}, "deneb", false)
|
||||
}
|
||||
|
||||
func processElectraBlock(decoder *json.Decoder, isBlinded bool) (*ethpb.GenericBeaconBlock, error) {
|
||||
if isBlinded {
|
||||
return convertBlockToGeneric(decoder, &structs.BlindedBeaconBlockElectra{}, "electra", true)
|
||||
}
|
||||
return convertBlockToGeneric(decoder, &structs.BeaconBlockContentsElectra{}, "electra", false)
|
||||
}
|
||||
|
||||
func processFuluBlock(decoder *json.Decoder, isBlinded bool) (*ethpb.GenericBeaconBlock, error) {
|
||||
if isBlinded {
|
||||
return convertBlockToGeneric(decoder, &structs.BlindedBeaconBlockFulu{}, "fulu", true)
|
||||
}
|
||||
return convertBlockToGeneric(decoder, &structs.BeaconBlockContentsFulu{}, "fulu", false)
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -56,6 +56,23 @@ func (mr *MockJsonRestHandlerMockRecorder) Get(ctx, endpoint, resp any) *gomock.
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockJsonRestHandler)(nil).Get), ctx, endpoint, resp)
|
||||
}
|
||||
|
||||
|
||||
// GetSSZ mocks base method.
|
||||
func (m *MockJsonRestHandler) GetSSZ(ctx context.Context, endpoint string) ([]byte, http.Header, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetSSZ", ctx, endpoint)
|
||||
ret0, _ := ret[0].([]byte)
|
||||
ret1, _ := ret[1].(http.Header)
|
||||
ret2, _ := ret[2].(error)
|
||||
return ret0, ret1, ret2
|
||||
}
|
||||
|
||||
// GetSSZ indicates an expected call of GetSSZ.
|
||||
func (mr *MockJsonRestHandlerMockRecorder) GetSSZ(ctx, endpoint any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSSZ", reflect.TypeOf((*MockJsonRestHandler)(nil).GetSSZ), ctx, endpoint)
|
||||
}
|
||||
|
||||
// Host mocks base method.
|
||||
func (m *MockJsonRestHandler) Host() string {
|
||||
m.ctrl.T.Helper()
|
||||
|
||||
@@ -18,7 +18,7 @@ import (
|
||||
)
|
||||
|
||||
// NewPrysmChainClient returns implementation of iface.PrysmChainClient.
|
||||
func NewPrysmChainClient(jsonRestHandler JsonRestHandler, nodeClient iface.NodeClient) iface.PrysmChainClient {
|
||||
func NewPrysmChainClient(jsonRestHandler RestHandler, nodeClient iface.NodeClient) iface.PrysmChainClient {
|
||||
return prysmChainClient{
|
||||
jsonRestHandler: jsonRestHandler,
|
||||
nodeClient: nodeClient,
|
||||
@@ -26,7 +26,7 @@ func NewPrysmChainClient(jsonRestHandler JsonRestHandler, nodeClient iface.NodeC
|
||||
}
|
||||
|
||||
type prysmChainClient struct {
|
||||
jsonRestHandler JsonRestHandler
|
||||
jsonRestHandler RestHandler
|
||||
nodeClient iface.NodeClient
|
||||
}
|
||||
|
||||
|
||||
@@ -4,49 +4,53 @@ import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/OffchainLabs/prysm/v6/api"
|
||||
"github.com/OffchainLabs/prysm/v6/config/features"
|
||||
"github.com/OffchainLabs/prysm/v6/network/httputil"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type JsonRestHandler interface {
|
||||
type RestHandler interface {
|
||||
Get(ctx context.Context, endpoint string, resp interface{}) error
|
||||
GetSSZ(ctx context.Context, endpoint string) ([]byte, http.Header, error)
|
||||
Post(ctx context.Context, endpoint string, headers map[string]string, data *bytes.Buffer, resp interface{}) error
|
||||
HttpClient() *http.Client
|
||||
Host() string
|
||||
SetHost(host string)
|
||||
}
|
||||
|
||||
type BeaconApiJsonRestHandler struct {
|
||||
type BeaconApiRestHandler struct {
|
||||
client http.Client
|
||||
host string
|
||||
}
|
||||
|
||||
// NewBeaconApiJsonRestHandler returns a JsonRestHandler
|
||||
func NewBeaconApiJsonRestHandler(client http.Client, host string) JsonRestHandler {
|
||||
return &BeaconApiJsonRestHandler{
|
||||
// NewBeaconApiRestHandler returns a RestHandler
|
||||
func NewBeaconApiRestHandler(client http.Client, host string) RestHandler {
|
||||
return &BeaconApiRestHandler{
|
||||
client: client,
|
||||
host: host,
|
||||
}
|
||||
}
|
||||
|
||||
// HttpClient returns the underlying HTTP client of the handler
|
||||
func (c *BeaconApiJsonRestHandler) HttpClient() *http.Client {
|
||||
func (c *BeaconApiRestHandler) HttpClient() *http.Client {
|
||||
return &c.client
|
||||
}
|
||||
|
||||
// Host returns the underlying HTTP host
|
||||
func (c *BeaconApiJsonRestHandler) Host() string {
|
||||
func (c *BeaconApiRestHandler) Host() string {
|
||||
return c.host
|
||||
}
|
||||
|
||||
// Get sends a GET request and decodes the response body as a JSON object into the passed in object.
|
||||
// If an HTTP error is returned, the body is decoded as a DefaultJsonError JSON object and returned as the first return value.
|
||||
func (c *BeaconApiJsonRestHandler) Get(ctx context.Context, endpoint string, resp interface{}) error {
|
||||
func (c *BeaconApiRestHandler) Get(ctx context.Context, endpoint string, resp interface{}) error {
|
||||
url := c.host + endpoint
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
|
||||
if err != nil {
|
||||
@@ -66,9 +70,61 @@ func (c *BeaconApiJsonRestHandler) Get(ctx context.Context, endpoint string, res
|
||||
return decodeResp(httpResp, resp)
|
||||
}
|
||||
|
||||
func (c *BeaconApiRestHandler) GetSSZ(ctx context.Context, endpoint string) ([]byte, http.Header, error) {
|
||||
url := c.host + endpoint
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
|
||||
if err != nil {
|
||||
return nil, nil, errors.Wrapf(err, "failed to create request for endpoint %s", url)
|
||||
}
|
||||
primaryAcceptType := fmt.Sprintf("%s;q=%s", api.OctetStreamMediaType, "0.95")
|
||||
secondaryAcceptType := fmt.Sprintf("%s;q=%s", api.JsonMediaType, "0.9")
|
||||
acceptHeaderString := fmt.Sprintf("%s,%s", primaryAcceptType, secondaryAcceptType)
|
||||
if features.Get().SSZOnly {
|
||||
acceptHeaderString = api.OctetStreamMediaType
|
||||
}
|
||||
req.Header.Set("Accept", acceptHeaderString)
|
||||
httpResp, err := c.client.Do(req)
|
||||
if err != nil {
|
||||
return nil, nil, errors.Wrapf(err, "failed to perform request for endpoint %s", url)
|
||||
}
|
||||
defer func() {
|
||||
if err := httpResp.Body.Close(); err != nil {
|
||||
return
|
||||
}
|
||||
}()
|
||||
contentType := httpResp.Header.Get("Content-Type")
|
||||
body, err := io.ReadAll(httpResp.Body)
|
||||
if err != nil {
|
||||
return nil, nil, errors.Wrapf(err, "failed to read response body for %s", httpResp.Request.URL)
|
||||
}
|
||||
if !strings.Contains(primaryAcceptType, contentType) {
|
||||
log.WithFields(logrus.Fields{
|
||||
"primaryAcceptType": primaryAcceptType,
|
||||
"secondaryAcceptType": secondaryAcceptType,
|
||||
"receivedAcceptType": contentType,
|
||||
}).Debug("Server responded with non primary accept type")
|
||||
}
|
||||
|
||||
// non-2XX codes are a failure
|
||||
if !strings.HasPrefix(httpResp.Status, "2") {
|
||||
decoder := json.NewDecoder(bytes.NewBuffer(body))
|
||||
errorJson := &httputil.DefaultJsonError{}
|
||||
if err = decoder.Decode(errorJson); err != nil {
|
||||
return nil, nil, errors.Wrapf(err, "failed to decode response body into error json for %s", httpResp.Request.URL)
|
||||
}
|
||||
return nil, nil, errorJson
|
||||
}
|
||||
|
||||
if features.Get().SSZOnly && contentType != api.OctetStreamMediaType {
|
||||
return nil, nil, errors.Errorf("server responded with non primary accept type %s", contentType)
|
||||
}
|
||||
|
||||
return body, httpResp.Header, nil
|
||||
}
|
||||
|
||||
// Post sends a POST request and decodes the response body as a JSON object into the passed in object.
|
||||
// If an HTTP error is returned, the body is decoded as a DefaultJsonError JSON object and returned as the first return value.
|
||||
func (c *BeaconApiJsonRestHandler) Post(
|
||||
func (c *BeaconApiRestHandler) Post(
|
||||
ctx context.Context,
|
||||
apiEndpoint string,
|
||||
headers map[string]string,
|
||||
@@ -136,6 +192,6 @@ func decodeResp(httpResp *http.Response, resp interface{}) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *BeaconApiJsonRestHandler) SetHost(host string) {
|
||||
func (c *BeaconApiRestHandler) SetHost(host string) {
|
||||
c.host = host
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package beacon_api
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
@@ -15,6 +16,8 @@ import (
|
||||
"github.com/OffchainLabs/prysm/v6/testing/assert"
|
||||
"github.com/OffchainLabs/prysm/v6/testing/require"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/sirupsen/logrus/hooks/test"
|
||||
)
|
||||
|
||||
func TestGet(t *testing.T) {
|
||||
@@ -39,7 +42,7 @@ func TestGet(t *testing.T) {
|
||||
server := httptest.NewServer(mux)
|
||||
defer server.Close()
|
||||
|
||||
jsonRestHandler := BeaconApiJsonRestHandler{
|
||||
jsonRestHandler := BeaconApiRestHandler{
|
||||
client: http.Client{Timeout: time.Second * 5},
|
||||
host: server.URL,
|
||||
}
|
||||
@@ -48,6 +51,98 @@ func TestGet(t *testing.T) {
|
||||
assert.DeepEqual(t, genesisJson, resp)
|
||||
}
|
||||
|
||||
func TestGetSSZ(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
const endpoint = "/example/rest/api/ssz"
|
||||
genesisJson := &structs.GetGenesisResponse{
|
||||
Data: &structs.Genesis{
|
||||
GenesisTime: "123",
|
||||
GenesisValidatorsRoot: "0x456",
|
||||
GenesisForkVersion: "0x789",
|
||||
},
|
||||
}
|
||||
|
||||
t.Run("Successful SSZ response", func(t *testing.T) {
|
||||
expectedBody := []byte{10, 20, 30, 40}
|
||||
|
||||
mux := http.NewServeMux()
|
||||
mux.HandleFunc(endpoint, func(w http.ResponseWriter, r *http.Request) {
|
||||
assert.StringContains(t, api.OctetStreamMediaType, r.Header.Get("Accept"))
|
||||
w.Header().Set("Content-Type", api.OctetStreamMediaType)
|
||||
_, err := w.Write(expectedBody)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
server := httptest.NewServer(mux)
|
||||
defer server.Close()
|
||||
|
||||
jsonRestHandler := BeaconApiRestHandler{
|
||||
client: http.Client{Timeout: time.Second * 5},
|
||||
host: server.URL,
|
||||
}
|
||||
|
||||
body, header, err := jsonRestHandler.GetSSZ(ctx, endpoint)
|
||||
require.NoError(t, err)
|
||||
assert.DeepEqual(t, expectedBody, body)
|
||||
require.StringContains(t, api.OctetStreamMediaType, header.Get("Content-Type"))
|
||||
})
|
||||
|
||||
t.Run("Json Content-Type response", func(t *testing.T) {
|
||||
logrus.SetLevel(logrus.DebugLevel)
|
||||
defer logrus.SetLevel(logrus.InfoLevel) // reset it afterwards
|
||||
logHook := test.NewGlobal()
|
||||
mux := http.NewServeMux()
|
||||
mux.HandleFunc(endpoint, func(w http.ResponseWriter, r *http.Request) {
|
||||
assert.StringContains(t, api.OctetStreamMediaType, r.Header.Get("Accept"))
|
||||
w.Header().Set("Content-Type", api.JsonMediaType)
|
||||
|
||||
marshalledJson, err := json.Marshal(genesisJson)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = w.Write(marshalledJson)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
server := httptest.NewServer(mux)
|
||||
defer server.Close()
|
||||
|
||||
jsonRestHandler := BeaconApiRestHandler{
|
||||
client: http.Client{Timeout: time.Second * 5},
|
||||
host: server.URL,
|
||||
}
|
||||
|
||||
body, header, err := jsonRestHandler.GetSSZ(ctx, endpoint)
|
||||
require.NoError(t, err)
|
||||
assert.LogsContain(t, logHook, "Server responded with non primary accept type")
|
||||
require.Equal(t, api.JsonMediaType, header.Get("Content-Type"))
|
||||
resp := &structs.GetGenesisResponse{}
|
||||
require.NoError(t, json.Unmarshal(body, resp))
|
||||
require.Equal(t, "123", resp.Data.GenesisTime)
|
||||
})
|
||||
|
||||
t.Run("Wrong Content-Type response, doesn't error out and instead handled downstream", func(t *testing.T) {
|
||||
logrus.SetLevel(logrus.DebugLevel)
|
||||
defer logrus.SetLevel(logrus.InfoLevel) // reset it afterwards
|
||||
logHook := test.NewGlobal()
|
||||
mux := http.NewServeMux()
|
||||
mux.HandleFunc(endpoint, func(w http.ResponseWriter, r *http.Request) {
|
||||
assert.StringContains(t, api.OctetStreamMediaType, r.Header.Get("Accept"))
|
||||
w.Header().Set("Content-Type", "text/plain") // Invalid content type
|
||||
_, err := w.Write([]byte("some text"))
|
||||
require.NoError(t, err)
|
||||
})
|
||||
server := httptest.NewServer(mux)
|
||||
defer server.Close()
|
||||
|
||||
jsonRestHandler := BeaconApiRestHandler{
|
||||
client: http.Client{Timeout: time.Second * 5},
|
||||
host: server.URL,
|
||||
}
|
||||
|
||||
_, _, err := jsonRestHandler.GetSSZ(ctx, endpoint)
|
||||
require.NoError(t, err)
|
||||
assert.LogsContain(t, logHook, "Server responded with non primary accept type")
|
||||
})
|
||||
}
|
||||
|
||||
func TestPost(t *testing.T) {
|
||||
ctx := t.Context()
|
||||
const endpoint = "/example/rest/api/endpoint"
|
||||
@@ -85,7 +180,7 @@ func TestPost(t *testing.T) {
|
||||
server := httptest.NewServer(mux)
|
||||
defer server.Close()
|
||||
|
||||
jsonRestHandler := BeaconApiJsonRestHandler{
|
||||
jsonRestHandler := BeaconApiRestHandler{
|
||||
client: http.Client{Timeout: time.Second * 5},
|
||||
host: server.URL,
|
||||
}
|
||||
@@ -21,7 +21,7 @@ type StateValidatorsProvider interface {
|
||||
}
|
||||
|
||||
type beaconApiStateValidatorsProvider struct {
|
||||
jsonRestHandler JsonRestHandler
|
||||
jsonRestHandler RestHandler
|
||||
}
|
||||
|
||||
func (c beaconApiStateValidatorsProvider) StateValidators(
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
validatorHelpers "github.com/OffchainLabs/prysm/v6/validator/helpers"
|
||||
)
|
||||
|
||||
func NewChainClient(validatorConn validatorHelpers.NodeConnection, jsonRestHandler beaconApi.JsonRestHandler) iface.ChainClient {
|
||||
func NewChainClient(validatorConn validatorHelpers.NodeConnection, jsonRestHandler beaconApi.RestHandler) iface.ChainClient {
|
||||
grpcClient := grpcApi.NewGrpcChainClient(validatorConn.GetGrpcClientConn())
|
||||
if features.Get().EnableBeaconRESTApi {
|
||||
return beaconApi.NewBeaconApiChainClientWithFallback(jsonRestHandler, grpcClient)
|
||||
@@ -18,7 +18,7 @@ func NewChainClient(validatorConn validatorHelpers.NodeConnection, jsonRestHandl
|
||||
}
|
||||
}
|
||||
|
||||
func NewPrysmChainClient(validatorConn validatorHelpers.NodeConnection, jsonRestHandler beaconApi.JsonRestHandler) iface.PrysmChainClient {
|
||||
func NewPrysmChainClient(validatorConn validatorHelpers.NodeConnection, jsonRestHandler beaconApi.RestHandler) iface.PrysmChainClient {
|
||||
if features.Get().EnableBeaconRESTApi {
|
||||
return beaconApi.NewPrysmChainClient(jsonRestHandler, nodeClientFactory.NewNodeClient(validatorConn, jsonRestHandler))
|
||||
} else {
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
validatorHelpers "github.com/OffchainLabs/prysm/v6/validator/helpers"
|
||||
)
|
||||
|
||||
func NewNodeClient(validatorConn validatorHelpers.NodeConnection, jsonRestHandler beaconApi.JsonRestHandler) iface.NodeClient {
|
||||
func NewNodeClient(validatorConn validatorHelpers.NodeConnection, jsonRestHandler beaconApi.RestHandler) iface.NodeClient {
|
||||
grpcClient := grpcApi.NewNodeClient(validatorConn.GetGrpcClientConn())
|
||||
if features.Get().EnableBeaconRESTApi {
|
||||
return beaconApi.NewNodeClientWithFallback(jsonRestHandler, grpcClient)
|
||||
|
||||
@@ -179,7 +179,7 @@ func (v *ValidatorService) Start() {
|
||||
return
|
||||
}
|
||||
|
||||
restHandler := beaconApi.NewBeaconApiJsonRestHandler(
|
||||
restHandler := beaconApi.NewBeaconApiRestHandler(
|
||||
http.Client{Timeout: v.conn.GetBeaconApiTimeout(), Transport: otelhttp.NewTransport(http.DefaultTransport)},
|
||||
hosts[0],
|
||||
)
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
|
||||
func NewValidatorClient(
|
||||
validatorConn validatorHelpers.NodeConnection,
|
||||
jsonRestHandler beaconApi.JsonRestHandler,
|
||||
jsonRestHandler beaconApi.RestHandler,
|
||||
opt ...beaconApi.ValidatorClientOpt,
|
||||
) iface.ValidatorClient {
|
||||
if features.Get().EnableBeaconRESTApi {
|
||||
|
||||
@@ -55,7 +55,7 @@ func (s *Server) registerBeaconClient() error {
|
||||
s.beaconApiTimeout,
|
||||
)
|
||||
|
||||
restHandler := beaconApi.NewBeaconApiJsonRestHandler(
|
||||
restHandler := beaconApi.NewBeaconApiRestHandler(
|
||||
http.Client{Timeout: s.beaconApiTimeout, Transport: otelhttp.NewTransport(http.DefaultTransport)},
|
||||
s.beaconApiEndpoint,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user