default validator client rest mode to ssz for post block (#15645)

* wip

* fixing tests

* updating unit tests for coverage

* gofmt

* changelog

* consolidated propose beacon block tests and improved code coverage

* gaz

* partial review feedback

* continuation fixing feedback on functions

* adding log

* only fall back on 406

* fixing broken tests

* Update validator/client/beacon-api/propose_beacon_block.go

Co-authored-by: Radosław Kapka <rkapka@wp.pl>

* Update validator/client/beacon-api/propose_beacon_block.go

Co-authored-by: Radosław Kapka <rkapka@wp.pl>

* Update validator/client/beacon-api/propose_beacon_block.go

Co-authored-by: Radosław Kapka <rkapka@wp.pl>

* Update validator/client/beacon-api/propose_beacon_block.go

Co-authored-by: Radosław Kapka <rkapka@wp.pl>

* Update validator/client/beacon-api/propose_beacon_block.go

Co-authored-by: Radosław Kapka <rkapka@wp.pl>

* radek review feedback

* more feedback

* radek suggestion

* fixing unit tests

---------

Co-authored-by: Radosław Kapka <rkapka@wp.pl>
This commit is contained in:
james-prysm
2025-09-10 16:08:11 -05:00
committed by GitHub
parent 5e079aa62c
commit 9b0a3e9632
19 changed files with 1297 additions and 1269 deletions

View File

@@ -91,18 +91,6 @@ go_test(
"index_test.go",
"prepare_beacon_proposer_test.go",
"propose_attestation_test.go",
"propose_beacon_block_altair_test.go",
"propose_beacon_block_bellatrix_test.go",
"propose_beacon_block_blinded_bellatrix_test.go",
"propose_beacon_block_blinded_capella_test.go",
"propose_beacon_block_blinded_deneb_test.go",
"propose_beacon_block_blinded_electra_test.go",
"propose_beacon_block_blinded_fulu_test.go",
"propose_beacon_block_capella_test.go",
"propose_beacon_block_deneb_test.go",
"propose_beacon_block_electra_test.go",
"propose_beacon_block_fulu_test.go",
"propose_beacon_block_phase0_test.go",
"propose_beacon_block_test.go",
"propose_exit_test.go",
"prysm_beacon_chain_client_test.go",

View File

@@ -1,14 +1,18 @@
package beacon_api
import (
"encoding/json"
"errors"
"fmt"
"net/http"
"testing"
"github.com/OffchainLabs/prysm/v6/api/server/structs"
rpctesting "github.com/OffchainLabs/prysm/v6/beacon-chain/rpc/eth/shared/testing"
"github.com/OffchainLabs/prysm/v6/config/params"
"github.com/OffchainLabs/prysm/v6/consensus-types/primitives"
"github.com/OffchainLabs/prysm/v6/encoding/bytesutil"
"github.com/OffchainLabs/prysm/v6/network/httputil"
ethpb "github.com/OffchainLabs/prysm/v6/proto/prysm/v1alpha1"
"github.com/OffchainLabs/prysm/v6/testing/assert"
"github.com/OffchainLabs/prysm/v6/testing/require"
@@ -136,15 +140,14 @@ func TestBeaconApiValidatorClient_ProposeBeaconBlockValid(t *testing.T) {
ctx := t.Context()
jsonRestHandler := mock.NewMockJsonRestHandler(ctrl)
jsonRestHandler.EXPECT().Post(
jsonRestHandler.EXPECT().PostSSZ(
gomock.Any(),
"/eth/v2/beacon/blocks",
map[string]string{"Eth-Consensus-Version": "phase0"},
gomock.Any(),
nil,
gomock.Any(),
).Return(
nil,
).Times(2)
nil, nil, nil,
).Times(1)
validatorClient := beaconApiValidatorClient{jsonRestHandler: jsonRestHandler}
expectedResp, expectedErr := validatorClient.proposeBeaconBlock(
@@ -153,34 +156,38 @@ func TestBeaconApiValidatorClient_ProposeBeaconBlockValid(t *testing.T) {
Block: generateSignedPhase0Block(),
},
)
resp, err := validatorClient.ProposeBeaconBlock(
ctx,
&ethpb.GenericSignedBeaconBlock{
Block: generateSignedPhase0Block(),
},
)
assert.DeepEqual(t, expectedErr, err)
assert.DeepEqual(t, expectedResp, resp)
require.NoError(t, expectedErr)
require.NotNil(t, expectedResp)
}
func TestBeaconApiValidatorClient_ProposeBeaconBlockError(t *testing.T) {
func TestBeaconApiValidatorClient_ProposeBeaconBlockError_ThenPass(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
ctx := t.Context()
jsonRestHandler := mock.NewMockJsonRestHandler(ctrl)
jsonRestHandler.EXPECT().PostSSZ(
gomock.Any(),
"/eth/v2/beacon/blocks",
gomock.Any(),
gomock.Any(),
).Return(
nil, nil, &httputil.DefaultJsonError{
Code: http.StatusNotAcceptable,
Message: "SSZ not supported",
},
).Times(1)
jsonRestHandler.EXPECT().Post(
gomock.Any(),
"/eth/v2/beacon/blocks",
map[string]string{"Eth-Consensus-Version": "phase0"},
gomock.Any(),
nil,
gomock.Any(),
gomock.Any(),
).Return(
errors.New("foo error"),
).Times(2)
nil,
).Times(1)
validatorClient := beaconApiValidatorClient{jsonRestHandler: jsonRestHandler}
expectedResp, expectedErr := validatorClient.proposeBeaconBlock(
@@ -189,16 +196,351 @@ func TestBeaconApiValidatorClient_ProposeBeaconBlockError(t *testing.T) {
Block: generateSignedPhase0Block(),
},
)
require.NoError(t, expectedErr)
require.NotNil(t, expectedResp)
}
resp, err := validatorClient.ProposeBeaconBlock(
ctx,
&ethpb.GenericSignedBeaconBlock{
Block: generateSignedPhase0Block(),
func TestBeaconApiValidatorClient_ProposeBeaconBlockAllTypes(t *testing.T) {
tests := []struct {
name string
block *ethpb.GenericSignedBeaconBlock
expectedPath string
wantErr bool
errorMessage string
}{
{
name: "Phase0 block",
block: &ethpb.GenericSignedBeaconBlock{
Block: generateSignedPhase0Block(),
},
expectedPath: "/eth/v2/beacon/blocks",
},
)
{
name: "Altair block",
block: &ethpb.GenericSignedBeaconBlock{
Block: generateSignedAltairBlock(),
},
expectedPath: "/eth/v2/beacon/blocks",
},
{
name: "Bellatrix block",
block: &ethpb.GenericSignedBeaconBlock{
Block: generateSignedBellatrixBlock(),
},
expectedPath: "/eth/v2/beacon/blocks",
},
{
name: "Capella block",
block: &ethpb.GenericSignedBeaconBlock{
Block: generateSignedCapellaBlock(),
},
expectedPath: "/eth/v2/beacon/blocks",
},
{
name: "Blinded Bellatrix block",
block: &ethpb.GenericSignedBeaconBlock{
Block: generateSignedBlindedBellatrixBlock(),
},
expectedPath: "/eth/v2/beacon/blinded_blocks",
},
{
name: "Blinded Capella block",
block: &ethpb.GenericSignedBeaconBlock{
Block: generateSignedBlindedCapellaBlock(),
},
expectedPath: "/eth/v2/beacon/blinded_blocks",
},
{
name: "Deneb block",
block: &ethpb.GenericSignedBeaconBlock{
Block: generateSignedDenebBlock(),
},
expectedPath: "/eth/v2/beacon/blocks",
},
{
name: "Blinded Deneb block",
block: &ethpb.GenericSignedBeaconBlock{
Block: generateSignedBlindedDenebBlock(),
},
expectedPath: "/eth/v2/beacon/blinded_blocks",
},
{
name: "Electra block",
block: &ethpb.GenericSignedBeaconBlock{
Block: generateSignedElectraBlock(),
},
expectedPath: "/eth/v2/beacon/blocks",
},
{
name: "Blinded Electra block",
block: &ethpb.GenericSignedBeaconBlock{
Block: generateSignedBlindedElectraBlock(),
},
expectedPath: "/eth/v2/beacon/blinded_blocks",
},
{
name: "Fulu block",
block: &ethpb.GenericSignedBeaconBlock{
Block: generateSignedFuluBlock(),
},
expectedPath: "/eth/v2/beacon/blocks",
},
{
name: "Blinded Fulu block",
block: &ethpb.GenericSignedBeaconBlock{
Block: generateSignedBlindedFuluBlock(),
},
expectedPath: "/eth/v2/beacon/blinded_blocks",
},
{
name: "Unsupported block type",
block: &ethpb.GenericSignedBeaconBlock{
Block: nil,
},
wantErr: true,
errorMessage: "unsupported block type",
},
}
assert.ErrorContains(t, expectedErr.Error(), err)
assert.DeepEqual(t, expectedResp, resp)
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
ctx := t.Context()
jsonRestHandler := mock.NewMockJsonRestHandler(ctrl)
if !tt.wantErr {
jsonRestHandler.EXPECT().PostSSZ(
gomock.Any(),
tt.expectedPath,
gomock.Any(),
gomock.Any(),
).Return(nil, nil, nil).Times(1)
}
validatorClient := beaconApiValidatorClient{jsonRestHandler: jsonRestHandler}
resp, err := validatorClient.proposeBeaconBlock(ctx, tt.block)
if tt.wantErr {
require.ErrorContains(t, tt.errorMessage, err)
assert.Equal(t, (*ethpb.ProposeResponse)(nil), resp)
} else {
require.NoError(t, err)
require.NotNil(t, resp)
}
})
}
}
func TestBeaconApiValidatorClient_ProposeBeaconBlockHTTPErrors(t *testing.T) {
tests := []struct {
name string
sszError error
expectJSON bool
errorMessage string
}{
{
name: "HTTP 202 Accepted - block broadcast but failed validation",
sszError: &httputil.DefaultJsonError{
Code: http.StatusAccepted,
Message: "block broadcast but failed validation",
},
expectJSON: false, // No fallback for non-406 errors
errorMessage: "failed to submit block ssz",
},
{
name: "Other HTTP error",
sszError: &httputil.DefaultJsonError{
Code: http.StatusBadRequest,
Message: "bad request",
},
expectJSON: false, // No fallback for non-406 errors
errorMessage: "failed to submit block ssz",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
ctx := t.Context()
jsonRestHandler := mock.NewMockJsonRestHandler(ctrl)
jsonRestHandler.EXPECT().PostSSZ(
gomock.Any(),
"/eth/v2/beacon/blocks",
gomock.Any(),
gomock.Any(),
).Return(nil, nil, tt.sszError).Times(1)
if tt.expectJSON {
// When SSZ fails, it falls back to JSON
jsonRestHandler.EXPECT().Post(
gomock.Any(),
"/eth/v2/beacon/blocks",
gomock.Any(),
gomock.Any(),
gomock.Any(),
).Return(tt.sszError).Times(1)
}
validatorClient := beaconApiValidatorClient{jsonRestHandler: jsonRestHandler}
_, err := validatorClient.proposeBeaconBlock(
ctx,
&ethpb.GenericSignedBeaconBlock{
Block: generateSignedPhase0Block(),
},
)
require.ErrorContains(t, tt.errorMessage, err)
})
}
}
func TestBeaconApiValidatorClient_ProposeBeaconBlockJSONFallback(t *testing.T) {
tests := []struct {
name string
block *ethpb.GenericSignedBeaconBlock
expectedPath string
jsonError error
wantErr bool
}{
{
name: "Phase0 block JSON fallback success",
block: &ethpb.GenericSignedBeaconBlock{
Block: generateSignedPhase0Block(),
},
expectedPath: "/eth/v2/beacon/blocks",
},
{
name: "Altair block JSON fallback success",
block: &ethpb.GenericSignedBeaconBlock{
Block: generateSignedAltairBlock(),
},
expectedPath: "/eth/v2/beacon/blocks",
},
{
name: "Bellatrix block JSON fallback success",
block: &ethpb.GenericSignedBeaconBlock{
Block: generateSignedBellatrixBlock(),
},
expectedPath: "/eth/v2/beacon/blocks",
},
{
name: "Capella block JSON fallback success",
block: &ethpb.GenericSignedBeaconBlock{
Block: generateSignedCapellaBlock(),
},
expectedPath: "/eth/v2/beacon/blocks",
},
{
name: "Blinded Bellatrix block JSON fallback success",
block: &ethpb.GenericSignedBeaconBlock{
Block: generateSignedBlindedBellatrixBlock(),
},
expectedPath: "/eth/v2/beacon/blinded_blocks",
},
{
name: "Blinded Capella block JSON fallback success",
block: &ethpb.GenericSignedBeaconBlock{
Block: generateSignedBlindedCapellaBlock(),
},
expectedPath: "/eth/v2/beacon/blinded_blocks",
},
{
name: "Deneb block JSON fallback success",
block: &ethpb.GenericSignedBeaconBlock{
Block: generateSignedDenebBlock(),
},
expectedPath: "/eth/v2/beacon/blocks",
},
{
name: "Blinded Deneb block JSON fallback success",
block: &ethpb.GenericSignedBeaconBlock{
Block: generateSignedBlindedDenebBlock(),
},
expectedPath: "/eth/v2/beacon/blinded_blocks",
},
{
name: "Electra block JSON fallback success",
block: &ethpb.GenericSignedBeaconBlock{
Block: generateSignedElectraBlock(),
},
expectedPath: "/eth/v2/beacon/blocks",
},
{
name: "Blinded Electra block JSON fallback success",
block: &ethpb.GenericSignedBeaconBlock{
Block: generateSignedBlindedElectraBlock(),
},
expectedPath: "/eth/v2/beacon/blinded_blocks",
},
{
name: "Fulu block JSON fallback success",
block: &ethpb.GenericSignedBeaconBlock{
Block: generateSignedFuluBlock(),
},
expectedPath: "/eth/v2/beacon/blocks",
},
{
name: "Blinded Fulu block JSON fallback success",
block: &ethpb.GenericSignedBeaconBlock{
Block: generateSignedBlindedFuluBlock(),
},
expectedPath: "/eth/v2/beacon/blinded_blocks",
},
{
name: "JSON fallback fails",
block: &ethpb.GenericSignedBeaconBlock{
Block: generateSignedPhase0Block(),
},
expectedPath: "/eth/v2/beacon/blocks",
jsonError: errors.New("json post failed"),
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
ctx := t.Context()
jsonRestHandler := mock.NewMockJsonRestHandler(ctrl)
// SSZ call fails with 406 to trigger JSON fallback
jsonRestHandler.EXPECT().PostSSZ(
gomock.Any(),
tt.expectedPath,
gomock.Any(),
gomock.Any(),
).Return(nil, nil, &httputil.DefaultJsonError{
Code: http.StatusNotAcceptable,
Message: "SSZ not supported",
}).Times(1)
// JSON fallback
jsonRestHandler.EXPECT().Post(
gomock.Any(),
tt.expectedPath,
gomock.Any(),
gomock.Any(),
gomock.Any(),
).Return(tt.jsonError).Times(1)
validatorClient := beaconApiValidatorClient{jsonRestHandler: jsonRestHandler}
resp, err := validatorClient.proposeBeaconBlock(ctx, tt.block)
if tt.wantErr {
assert.NotNil(t, err)
assert.Equal(t, (*ethpb.ProposeResponse)(nil), resp)
} else {
require.NoError(t, err)
require.NotNil(t, resp)
}
})
}
}
func TestBeaconApiValidatorClient_Host(t *testing.T) {
@@ -229,3 +571,88 @@ func TestBeaconApiValidatorClient_Host(t *testing.T) {
host = validatorClient.Host()
require.Equal(t, hosts[1], host)
}
// Helper functions for generating test blocks for newer consensus versions
func generateSignedDenebBlock() *ethpb.GenericSignedBeaconBlock_Deneb {
var blockContents structs.SignedBeaconBlockContentsDeneb
if err := json.Unmarshal([]byte(rpctesting.DenebBlockContents), &blockContents); err != nil {
panic(err)
}
genericBlock, err := blockContents.ToGeneric()
if err != nil {
panic(err)
}
return &ethpb.GenericSignedBeaconBlock_Deneb{
Deneb: genericBlock.GetDeneb(),
}
}
func generateSignedBlindedDenebBlock() *ethpb.GenericSignedBeaconBlock_BlindedDeneb {
var blindedBlock structs.SignedBlindedBeaconBlockDeneb
if err := json.Unmarshal([]byte(rpctesting.BlindedDenebBlock), &blindedBlock); err != nil {
panic(err)
}
genericBlock, err := blindedBlock.ToGeneric()
if err != nil {
panic(err)
}
return &ethpb.GenericSignedBeaconBlock_BlindedDeneb{
BlindedDeneb: genericBlock.GetBlindedDeneb(),
}
}
func generateSignedElectraBlock() *ethpb.GenericSignedBeaconBlock_Electra {
var blockContents structs.SignedBeaconBlockContentsElectra
if err := json.Unmarshal([]byte(rpctesting.ElectraBlockContents), &blockContents); err != nil {
panic(err)
}
genericBlock, err := blockContents.ToGeneric()
if err != nil {
panic(err)
}
return &ethpb.GenericSignedBeaconBlock_Electra{
Electra: genericBlock.GetElectra(),
}
}
func generateSignedBlindedElectraBlock() *ethpb.GenericSignedBeaconBlock_BlindedElectra {
var blindedBlock structs.SignedBlindedBeaconBlockElectra
if err := json.Unmarshal([]byte(rpctesting.BlindedElectraBlock), &blindedBlock); err != nil {
panic(err)
}
genericBlock, err := blindedBlock.ToGeneric()
if err != nil {
panic(err)
}
return &ethpb.GenericSignedBeaconBlock_BlindedElectra{
BlindedElectra: genericBlock.GetBlindedElectra(),
}
}
func generateSignedFuluBlock() *ethpb.GenericSignedBeaconBlock_Fulu {
var blockContents structs.SignedBeaconBlockContentsFulu
if err := json.Unmarshal([]byte(rpctesting.FuluBlockContents), &blockContents); err != nil {
panic(err)
}
genericBlock, err := blockContents.ToGeneric()
if err != nil {
panic(err)
}
return &ethpb.GenericSignedBeaconBlock_Fulu{
Fulu: genericBlock.GetFulu(),
}
}
func generateSignedBlindedFuluBlock() *ethpb.GenericSignedBeaconBlock_BlindedFulu {
var blindedBlock structs.SignedBlindedBeaconBlockFulu
if err := json.Unmarshal([]byte(rpctesting.BlindedFuluBlock), &blindedBlock); err != nil {
panic(err)
}
genericBlock, err := blindedBlock.ToGeneric()
if err != nil {
panic(err)
}
return &ethpb.GenericSignedBeaconBlock_BlindedFulu{
BlindedFulu: genericBlock.GetBlindedFulu(),
}
}

View File

@@ -114,6 +114,22 @@ func (mr *MockJsonRestHandlerMockRecorder) Post(ctx, endpoint, headers, data, re
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Post", reflect.TypeOf((*MockJsonRestHandler)(nil).Post), ctx, endpoint, headers, data, resp)
}
// Post mocks base method.
func (m *MockJsonRestHandler) PostSSZ(ctx context.Context, endpoint string, headers map[string]string, data *bytes.Buffer) ([]byte, http.Header, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "PostSSZ", ctx, endpoint, headers, data)
ret0, _ := ret[0].([]byte)
ret1, _ := ret[1].(http.Header)
ret2, _ := ret[2].(error)
return ret0,ret1,ret2
}
// Post indicates an expected call of Post.
func (mr *MockJsonRestHandlerMockRecorder) PostSSZ(ctx, endpoint, headers, data any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PostSSZ", reflect.TypeOf((*MockJsonRestHandler)(nil).PostSSZ), ctx, endpoint, headers, data)
}
// SetHost mocks base method.
func (m *MockJsonRestHandler) SetHost(host string) {
m.ctrl.T.Helper()

View File

@@ -15,8 +15,10 @@ import (
type blockProcessingResult struct {
consensusVersion string
beaconBlockRoot [32]byte
marshalledJSON []byte
marshalledSSZ []byte
blinded bool
// Function to marshal JSON on demand
marshalJSON func() ([]byte, error)
}
func (c *beaconApiValidatorClient) proposeBeaconBlock(ctx context.Context, in *ethpb.GenericSignedBeaconBlock) (*ethpb.ProposeResponse, error) {
@@ -62,17 +64,54 @@ func (c *beaconApiValidatorClient) proposeBeaconBlock(ctx context.Context, in *e
}
headers := map[string]string{"Eth-Consensus-Version": res.consensusVersion}
err = c.jsonRestHandler.Post(ctx, endpoint, headers, bytes.NewBuffer(res.marshalledJSON), nil)
errJson := &httputil.DefaultJsonError{}
if err != nil {
if !errors.As(err, &errJson) {
return nil, err
// Try PostSSZ first with SSZ data
if res.marshalledSSZ != nil {
_, _, err = c.jsonRestHandler.PostSSZ(ctx, endpoint, headers, bytes.NewBuffer(res.marshalledSSZ))
if err != nil {
errJson := &httputil.DefaultJsonError{}
// If PostSSZ fails with 406 (Not Acceptable), fall back to JSON
if !errors.As(err, &errJson) {
return nil, err
}
if errJson.Code == http.StatusNotAcceptable && res.marshalJSON != nil {
log.WithError(err).Warn("Failed to submit block ssz, falling back to JSON")
jsonData, jsonErr := res.marshalJSON()
if jsonErr != nil {
return nil, errors.Wrap(jsonErr, "failed to marshal JSON")
}
// Reset headers for JSON
err = c.jsonRestHandler.Post(ctx, endpoint, headers, bytes.NewBuffer(jsonData), nil)
// If JSON also fails, return that error
if err != nil {
return nil, errors.Wrap(err, "failed to submit block via JSON fallback")
}
} else {
// For non-406 errors or when no JSON fallback is available, return the SSZ error
return nil, errors.Wrap(errJson, "failed to submit block ssz")
}
}
// Error 202 means that the block was successfully broadcast, but validation failed
if errJson.Code == http.StatusAccepted {
return nil, errors.New("block was successfully broadcast but failed validation")
} else if res.marshalJSON == nil {
return nil, errors.New("no marshalling functions available")
} else {
// No SSZ data available, marshal and use JSON
jsonData, jsonErr := res.marshalJSON()
if jsonErr != nil {
return nil, errors.Wrap(jsonErr, "failed to marshal JSON")
}
// Reset headers for JSON
err = c.jsonRestHandler.Post(ctx, endpoint, headers, bytes.NewBuffer(jsonData), nil)
errJson := &httputil.DefaultJsonError{}
if err != nil {
if !errors.As(err, &errJson) {
return nil, err
}
// Error 202 means that the block was successfully broadcast, but validation failed
if errJson.Code == http.StatusAccepted {
return nil, errors.New("block was successfully broadcast but failed validation")
}
return nil, errJson
}
return nil, errJson
}
return &ethpb.ProposeResponse{BlockRoot: res.beaconBlockRoot[:]}, nil
@@ -89,11 +128,19 @@ func handlePhase0Block(block *ethpb.GenericSignedBeaconBlock_Phase0) (*blockProc
}
res.beaconBlockRoot = beaconBlockRoot
signedBlock := structs.SignedBeaconBlockPhase0FromConsensus(block.Phase0)
res.marshalledJSON, err = json.Marshal(signedBlock)
// Marshal SSZ
ssz, err := block.Phase0.MarshalSSZ()
if err != nil {
return nil, errors.Wrap(err, "failed to marshall phase0 beacon block to json")
return nil, errors.Wrap(err, "failed to serialize block for phase0 beacon block")
}
res.marshalledSSZ = ssz
// Set up JSON marshalling function for fallback
res.marshalJSON = func() ([]byte, error) {
signedBlock := structs.SignedBeaconBlockPhase0FromConsensus(block.Phase0)
return json.Marshal(signedBlock)
}
return &res, nil
}
@@ -108,11 +155,19 @@ func handleAltairBlock(block *ethpb.GenericSignedBeaconBlock_Altair) (*blockProc
}
res.beaconBlockRoot = beaconBlockRoot
signedBlock := structs.SignedBeaconBlockAltairFromConsensus(block.Altair)
res.marshalledJSON, err = json.Marshal(signedBlock)
// Marshal SSZ
ssz, err := block.Altair.MarshalSSZ()
if err != nil {
return nil, errors.Wrap(err, "failed to marshall altair beacon block to json")
return nil, errors.Wrap(err, "failed to serialize block for altair beacon block")
}
res.marshalledSSZ = ssz
// Set up JSON marshalling function for fallback
res.marshalJSON = func() ([]byte, error) {
signedBlock := structs.SignedBeaconBlockAltairFromConsensus(block.Altair)
return json.Marshal(signedBlock)
}
return &res, nil
}
@@ -127,13 +182,20 @@ func handleBellatrixBlock(block *ethpb.GenericSignedBeaconBlock_Bellatrix) (*blo
}
res.beaconBlockRoot = beaconBlockRoot
signedBlock, err := structs.SignedBeaconBlockBellatrixFromConsensus(block.Bellatrix)
// Marshal SSZ
ssz, err := block.Bellatrix.MarshalSSZ()
if err != nil {
return nil, errors.Wrap(err, "failed to marshall bellatrix beacon block")
return nil, errors.Wrap(err, "failed to serialize block for bellatrix beacon block")
}
res.marshalledJSON, err = json.Marshal(signedBlock)
if err != nil {
return nil, errors.Wrap(err, "failed to marshall bellatrix beacon block to json")
res.marshalledSSZ = ssz
// Set up JSON marshalling function for fallback
res.marshalJSON = func() ([]byte, error) {
signedBlock, err := structs.SignedBeaconBlockBellatrixFromConsensus(block.Bellatrix)
if err != nil {
return nil, errors.Wrap(err, "failed to convert bellatrix beacon block")
}
return json.Marshal(signedBlock)
}
return &res, nil
@@ -150,13 +212,20 @@ func handleBlindedBellatrixBlock(block *ethpb.GenericSignedBeaconBlock_BlindedBe
}
res.beaconBlockRoot = beaconBlockRoot
signedBlock, err := structs.SignedBlindedBeaconBlockBellatrixFromConsensus(block.BlindedBellatrix)
// Marshal SSZ
ssz, err := block.BlindedBellatrix.MarshalSSZ()
if err != nil {
return nil, errors.Wrap(err, "failed to marshall blinded bellatrix beacon block")
return nil, errors.Wrap(err, "failed to serialize block for bellatrix beacon block")
}
res.marshalledJSON, err = json.Marshal(signedBlock)
if err != nil {
return nil, errors.Wrap(err, "failed to marshall blinded bellatrix beacon block to json")
res.marshalledSSZ = ssz
// Set up JSON marshalling function for fallback
res.marshalJSON = func() ([]byte, error) {
signedBlock, err := structs.SignedBlindedBeaconBlockBellatrixFromConsensus(block.BlindedBellatrix)
if err != nil {
return nil, errors.Wrap(err, "failed to convert blinded bellatrix beacon block")
}
return json.Marshal(signedBlock)
}
return &res, nil
@@ -173,13 +242,20 @@ func handleCapellaBlock(block *ethpb.GenericSignedBeaconBlock_Capella) (*blockPr
}
res.beaconBlockRoot = beaconBlockRoot
signedBlock, err := structs.SignedBeaconBlockCapellaFromConsensus(block.Capella)
// Marshal SSZ
ssz, err := block.Capella.MarshalSSZ()
if err != nil {
return nil, errors.Wrap(err, "failed to marshall capella beacon block")
return nil, errors.Wrap(err, "failed to serialize capella beacon block")
}
res.marshalledJSON, err = json.Marshal(signedBlock)
if err != nil {
return nil, errors.Wrap(err, "failed to marshall capella beacon block to json")
res.marshalledSSZ = ssz
// Set up JSON marshalling function for fallback
res.marshalJSON = func() ([]byte, error) {
signedBlock, err := structs.SignedBeaconBlockCapellaFromConsensus(block.Capella)
if err != nil {
return nil, errors.Wrap(err, "failed to convert capella beacon block")
}
return json.Marshal(signedBlock)
}
return &res, nil
@@ -196,13 +272,20 @@ func handleBlindedCapellaBlock(block *ethpb.GenericSignedBeaconBlock_BlindedCape
}
res.beaconBlockRoot = beaconBlockRoot
signedBlock, err := structs.SignedBlindedBeaconBlockCapellaFromConsensus(block.BlindedCapella)
// Marshal SSZ
ssz, err := block.BlindedCapella.MarshalSSZ()
if err != nil {
return nil, errors.Wrap(err, "failed to marshall blinded capella beacon block")
return nil, errors.Wrap(err, "failed to serialize blinded capella beacon block")
}
res.marshalledJSON, err = json.Marshal(signedBlock)
if err != nil {
return nil, errors.Wrap(err, "failed to marshall blinded capella beacon block to json")
res.marshalledSSZ = ssz
// Set up JSON marshalling function for fallback
res.marshalJSON = func() ([]byte, error) {
signedBlock, err := structs.SignedBlindedBeaconBlockCapellaFromConsensus(block.BlindedCapella)
if err != nil {
return nil, errors.Wrap(err, "failed to convert blinded capella beacon block")
}
return json.Marshal(signedBlock)
}
return &res, nil
@@ -219,13 +302,20 @@ func handleDenebBlockContents(block *ethpb.GenericSignedBeaconBlock_Deneb) (*blo
}
res.beaconBlockRoot = beaconBlockRoot
signedBlock, err := structs.SignedBeaconBlockContentsDenebFromConsensus(block.Deneb)
// Marshal SSZ
ssz, err := block.Deneb.MarshalSSZ()
if err != nil {
return nil, errors.Wrap(err, "failed to marshall deneb beacon block contents")
return nil, errors.Wrap(err, "failed to serialize deneb beacon block")
}
res.marshalledJSON, err = json.Marshal(signedBlock)
if err != nil {
return nil, errors.Wrap(err, "failed to marshall deneb beacon block contents to json")
res.marshalledSSZ = ssz
// Set up JSON marshalling function for fallback
res.marshalJSON = func() ([]byte, error) {
signedBlock, err := structs.SignedBeaconBlockContentsDenebFromConsensus(block.Deneb)
if err != nil {
return nil, errors.Wrap(err, "failed to convert deneb beacon block contents")
}
return json.Marshal(signedBlock)
}
return &res, nil
@@ -242,13 +332,20 @@ func handleBlindedDenebBlock(block *ethpb.GenericSignedBeaconBlock_BlindedDeneb)
}
res.beaconBlockRoot = beaconBlockRoot
signedBlock, err := structs.SignedBlindedBeaconBlockDenebFromConsensus(block.BlindedDeneb)
// Marshal SSZ
ssz, err := block.BlindedDeneb.MarshalSSZ()
if err != nil {
return nil, errors.Wrap(err, "failed to marshall deneb blinded beacon block ")
return nil, errors.Wrap(err, "failed to serialize blinded deneb beacon block")
}
res.marshalledJSON, err = json.Marshal(signedBlock)
if err != nil {
return nil, errors.Wrap(err, "failed to marshall deneb blinded beacon block to json")
res.marshalledSSZ = ssz
// Set up JSON marshalling function for fallback
res.marshalJSON = func() ([]byte, error) {
signedBlock, err := structs.SignedBlindedBeaconBlockDenebFromConsensus(block.BlindedDeneb)
if err != nil {
return nil, errors.Wrap(err, "failed to convert deneb blinded beacon block")
}
return json.Marshal(signedBlock)
}
return &res, nil
@@ -265,13 +362,20 @@ func handleElectraBlockContents(block *ethpb.GenericSignedBeaconBlock_Electra) (
}
res.beaconBlockRoot = beaconBlockRoot
signedBlock, err := structs.SignedBeaconBlockContentsElectraFromConsensus(block.Electra)
// Marshal SSZ
ssz, err := block.Electra.MarshalSSZ()
if err != nil {
return nil, errors.Wrap(err, "failed to marshall electra beacon block contents")
return nil, errors.Wrap(err, "failed to serialize electra beacon block")
}
res.marshalledJSON, err = json.Marshal(signedBlock)
if err != nil {
return nil, errors.Wrap(err, "failed to marshall electra beacon block contents to json")
res.marshalledSSZ = ssz
// Set up JSON marshalling function for fallback
res.marshalJSON = func() ([]byte, error) {
signedBlock, err := structs.SignedBeaconBlockContentsElectraFromConsensus(block.Electra)
if err != nil {
return nil, errors.Wrap(err, "failed to convert electra beacon block contents")
}
return json.Marshal(signedBlock)
}
return &res, nil
@@ -288,13 +392,20 @@ func handleBlindedElectraBlock(block *ethpb.GenericSignedBeaconBlock_BlindedElec
}
res.beaconBlockRoot = beaconBlockRoot
signedBlock, err := structs.SignedBlindedBeaconBlockElectraFromConsensus(block.BlindedElectra)
// Marshal SSZ
ssz, err := block.BlindedElectra.MarshalSSZ()
if err != nil {
return nil, errors.Wrap(err, "failed to marshall electra blinded beacon block")
return nil, errors.Wrap(err, "failed to serialize blinded electra beacon block")
}
res.marshalledJSON, err = json.Marshal(signedBlock)
if err != nil {
return nil, errors.Wrap(err, "failed to marshall electra blinded beacon block to json")
res.marshalledSSZ = ssz
// Set up JSON marshalling function for fallback
res.marshalJSON = func() ([]byte, error) {
signedBlock, err := structs.SignedBlindedBeaconBlockElectraFromConsensus(block.BlindedElectra)
if err != nil {
return nil, errors.Wrap(err, "failed to convert electra blinded beacon block")
}
return json.Marshal(signedBlock)
}
return &res, nil
@@ -311,13 +422,20 @@ func handleFuluBlockContents(block *ethpb.GenericSignedBeaconBlock_Fulu) (*block
}
res.beaconBlockRoot = beaconBlockRoot
signedBlock, err := structs.SignedBeaconBlockContentsFuluFromConsensus(block.Fulu)
// Marshal SSZ
ssz, err := block.Fulu.MarshalSSZ()
if err != nil {
return nil, errors.Wrap(err, "failed to marshall fulu beacon block contents")
return nil, errors.Wrap(err, "failed to serialize fulu beacon block")
}
res.marshalledJSON, err = json.Marshal(signedBlock)
if err != nil {
return nil, errors.Wrap(err, "failed to marshall fulu beacon block contents to json")
res.marshalledSSZ = ssz
// Set up JSON marshalling function for fallback
res.marshalJSON = func() ([]byte, error) {
signedBlock, err := structs.SignedBeaconBlockContentsFuluFromConsensus(block.Fulu)
if err != nil {
return nil, errors.Wrap(err, "failed to convert fulu beacon block contents")
}
return json.Marshal(signedBlock)
}
return &res, nil
@@ -334,13 +452,20 @@ func handleBlindedFuluBlock(block *ethpb.GenericSignedBeaconBlock_BlindedFulu) (
}
res.beaconBlockRoot = beaconBlockRoot
signedBlock, err := structs.SignedBlindedBeaconBlockFuluFromConsensus(block.BlindedFulu)
// Marshal SSZ
ssz, err := block.BlindedFulu.MarshalSSZ()
if err != nil {
return nil, errors.Wrap(err, "failed to marshall fulu blinded beacon block")
return nil, errors.Wrap(err, "failed to serialize blinded fulu beacon block")
}
res.marshalledJSON, err = json.Marshal(signedBlock)
if err != nil {
return nil, errors.Wrap(err, "failed to marshall fulu blinded beacon block to json")
res.marshalledSSZ = ssz
// Set up JSON marshalling function for fallback
res.marshalJSON = func() ([]byte, error) {
signedBlock, err := structs.SignedBlindedBeaconBlockFuluFromConsensus(block.BlindedFulu)
if err != nil {
return nil, errors.Wrap(err, "failed to convert fulu blinded beacon block")
}
return json.Marshal(signedBlock)
}
return &res, nil

View File

@@ -1,63 +0,0 @@
package beacon_api
import (
"bytes"
"encoding/json"
"testing"
"github.com/OffchainLabs/prysm/v6/api/server/structs"
ethpb "github.com/OffchainLabs/prysm/v6/proto/prysm/v1alpha1"
"github.com/OffchainLabs/prysm/v6/testing/assert"
"github.com/OffchainLabs/prysm/v6/testing/require"
"github.com/OffchainLabs/prysm/v6/validator/client/beacon-api/mock"
testhelpers "github.com/OffchainLabs/prysm/v6/validator/client/beacon-api/test-helpers"
"go.uber.org/mock/gomock"
)
func TestProposeBeaconBlock_Altair(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
jsonRestHandler := mock.NewMockJsonRestHandler(ctrl)
altairBlock := generateSignedAltairBlock()
genericSignedBlock := &ethpb.GenericSignedBeaconBlock{}
genericSignedBlock.Block = altairBlock
jsonAltairBlock := structs.SignedBeaconBlockAltairFromConsensus(altairBlock.Altair)
marshalledBlock, err := json.Marshal(jsonAltairBlock)
require.NoError(t, err)
ctx := t.Context()
// Make sure that what we send in the POST body is the marshalled version of the protobuf block
headers := map[string]string{"Eth-Consensus-Version": "altair"}
jsonRestHandler.EXPECT().Post(
gomock.Any(),
"/eth/v2/beacon/blocks",
headers,
bytes.NewBuffer(marshalledBlock),
nil,
)
validatorClient := &beaconApiValidatorClient{jsonRestHandler: jsonRestHandler}
proposeResponse, err := validatorClient.proposeBeaconBlock(ctx, genericSignedBlock)
assert.NoError(t, err)
require.NotNil(t, proposeResponse)
expectedBlockRoot, err := altairBlock.Altair.Block.HashTreeRoot()
require.NoError(t, err)
// Make sure that the block root is set
assert.DeepEqual(t, expectedBlockRoot[:], proposeResponse.BlockRoot)
}
func generateSignedAltairBlock() *ethpb.GenericSignedBeaconBlock_Altair {
return &ethpb.GenericSignedBeaconBlock_Altair{
Altair: &ethpb.SignedBeaconBlockAltair{
Block: testhelpers.GenerateProtoAltairBeaconBlock(),
Signature: testhelpers.FillByteSlice(96, 112),
},
}
}

View File

@@ -1,64 +0,0 @@
package beacon_api
import (
"bytes"
"encoding/json"
"testing"
"github.com/OffchainLabs/prysm/v6/api/server/structs"
ethpb "github.com/OffchainLabs/prysm/v6/proto/prysm/v1alpha1"
"github.com/OffchainLabs/prysm/v6/testing/assert"
"github.com/OffchainLabs/prysm/v6/testing/require"
"github.com/OffchainLabs/prysm/v6/validator/client/beacon-api/mock"
testhelpers "github.com/OffchainLabs/prysm/v6/validator/client/beacon-api/test-helpers"
"go.uber.org/mock/gomock"
)
func TestProposeBeaconBlock_Bellatrix(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
jsonRestHandler := mock.NewMockJsonRestHandler(ctrl)
bellatrixBlock := generateSignedBellatrixBlock()
genericSignedBlock := &ethpb.GenericSignedBeaconBlock{}
genericSignedBlock.Block = bellatrixBlock
jsonBellatrixBlock, err := structs.SignedBeaconBlockBellatrixFromConsensus(bellatrixBlock.Bellatrix)
require.NoError(t, err)
marshalledBlock, err := json.Marshal(jsonBellatrixBlock)
require.NoError(t, err)
ctx := t.Context()
// Make sure that what we send in the POST body is the marshalled version of the protobuf block
headers := map[string]string{"Eth-Consensus-Version": "bellatrix"}
jsonRestHandler.EXPECT().Post(
gomock.Any(),
"/eth/v2/beacon/blocks",
headers,
bytes.NewBuffer(marshalledBlock),
nil,
)
validatorClient := &beaconApiValidatorClient{jsonRestHandler: jsonRestHandler}
proposeResponse, err := validatorClient.proposeBeaconBlock(ctx, genericSignedBlock)
assert.NoError(t, err)
require.NotNil(t, proposeResponse)
expectedBlockRoot, err := bellatrixBlock.Bellatrix.Block.HashTreeRoot()
require.NoError(t, err)
// Make sure that the block root is set
assert.DeepEqual(t, expectedBlockRoot[:], proposeResponse.BlockRoot)
}
func generateSignedBellatrixBlock() *ethpb.GenericSignedBeaconBlock_Bellatrix {
return &ethpb.GenericSignedBeaconBlock_Bellatrix{
Bellatrix: &ethpb.SignedBeaconBlockBellatrix{
Block: testhelpers.GenerateProtoBellatrixBeaconBlock(),
Signature: testhelpers.FillByteSlice(96, 127),
},
}
}

View File

@@ -1,291 +0,0 @@
package beacon_api
import (
"bytes"
"encoding/json"
"testing"
"github.com/OffchainLabs/prysm/v6/api/server/structs"
enginev1 "github.com/OffchainLabs/prysm/v6/proto/engine/v1"
ethpb "github.com/OffchainLabs/prysm/v6/proto/prysm/v1alpha1"
"github.com/OffchainLabs/prysm/v6/testing/assert"
"github.com/OffchainLabs/prysm/v6/testing/require"
"github.com/OffchainLabs/prysm/v6/validator/client/beacon-api/mock"
testhelpers "github.com/OffchainLabs/prysm/v6/validator/client/beacon-api/test-helpers"
"go.uber.org/mock/gomock"
)
func TestProposeBeaconBlock_BlindedBellatrix(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
jsonRestHandler := mock.NewMockJsonRestHandler(ctrl)
blindedBellatrixBlock := generateSignedBlindedBellatrixBlock()
genericSignedBlock := &ethpb.GenericSignedBeaconBlock{}
genericSignedBlock.Block = blindedBellatrixBlock
jsonBlindedBellatrixBlock, err := structs.SignedBlindedBeaconBlockBellatrixFromConsensus(blindedBellatrixBlock.BlindedBellatrix)
require.NoError(t, err)
marshalledBlock, err := json.Marshal(jsonBlindedBellatrixBlock)
require.NoError(t, err)
ctx := t.Context()
// Make sure that what we send in the POST body is the marshalled version of the protobuf block
headers := map[string]string{"Eth-Consensus-Version": "bellatrix"}
jsonRestHandler.EXPECT().Post(
gomock.Any(),
"/eth/v2/beacon/blinded_blocks",
headers,
bytes.NewBuffer(marshalledBlock),
nil,
)
validatorClient := &beaconApiValidatorClient{jsonRestHandler: jsonRestHandler}
proposeResponse, err := validatorClient.proposeBeaconBlock(ctx, genericSignedBlock)
assert.NoError(t, err)
require.NotNil(t, proposeResponse)
expectedBlockRoot, err := blindedBellatrixBlock.BlindedBellatrix.Block.HashTreeRoot()
require.NoError(t, err)
// Make sure that the block root is set
assert.DeepEqual(t, expectedBlockRoot[:], proposeResponse.BlockRoot)
}
func generateSignedBlindedBellatrixBlock() *ethpb.GenericSignedBeaconBlock_BlindedBellatrix {
return &ethpb.GenericSignedBeaconBlock_BlindedBellatrix{
BlindedBellatrix: &ethpb.SignedBlindedBeaconBlockBellatrix{
Block: &ethpb.BlindedBeaconBlockBellatrix{
Slot: 1,
ProposerIndex: 2,
ParentRoot: testhelpers.FillByteSlice(32, 3),
StateRoot: testhelpers.FillByteSlice(32, 4),
Body: &ethpb.BlindedBeaconBlockBodyBellatrix{
RandaoReveal: testhelpers.FillByteSlice(96, 5),
Eth1Data: &ethpb.Eth1Data{
DepositRoot: testhelpers.FillByteSlice(32, 6),
DepositCount: 7,
BlockHash: testhelpers.FillByteSlice(32, 8),
},
Graffiti: testhelpers.FillByteSlice(32, 9),
ProposerSlashings: []*ethpb.ProposerSlashing{
{
Header_1: &ethpb.SignedBeaconBlockHeader{
Header: &ethpb.BeaconBlockHeader{
Slot: 10,
ProposerIndex: 11,
ParentRoot: testhelpers.FillByteSlice(32, 12),
StateRoot: testhelpers.FillByteSlice(32, 13),
BodyRoot: testhelpers.FillByteSlice(32, 14),
},
Signature: testhelpers.FillByteSlice(96, 15),
},
Header_2: &ethpb.SignedBeaconBlockHeader{
Header: &ethpb.BeaconBlockHeader{
Slot: 16,
ProposerIndex: 17,
ParentRoot: testhelpers.FillByteSlice(32, 18),
StateRoot: testhelpers.FillByteSlice(32, 19),
BodyRoot: testhelpers.FillByteSlice(32, 20),
},
Signature: testhelpers.FillByteSlice(96, 21),
},
},
{
Header_1: &ethpb.SignedBeaconBlockHeader{
Header: &ethpb.BeaconBlockHeader{
Slot: 22,
ProposerIndex: 23,
ParentRoot: testhelpers.FillByteSlice(32, 24),
StateRoot: testhelpers.FillByteSlice(32, 25),
BodyRoot: testhelpers.FillByteSlice(32, 26),
},
Signature: testhelpers.FillByteSlice(96, 27),
},
Header_2: &ethpb.SignedBeaconBlockHeader{
Header: &ethpb.BeaconBlockHeader{
Slot: 28,
ProposerIndex: 29,
ParentRoot: testhelpers.FillByteSlice(32, 30),
StateRoot: testhelpers.FillByteSlice(32, 31),
BodyRoot: testhelpers.FillByteSlice(32, 32),
},
Signature: testhelpers.FillByteSlice(96, 33),
},
},
},
AttesterSlashings: []*ethpb.AttesterSlashing{
{
Attestation_1: &ethpb.IndexedAttestation{
AttestingIndices: []uint64{34, 35},
Data: &ethpb.AttestationData{
Slot: 36,
CommitteeIndex: 37,
BeaconBlockRoot: testhelpers.FillByteSlice(32, 38),
Source: &ethpb.Checkpoint{
Epoch: 39,
Root: testhelpers.FillByteSlice(32, 40),
},
Target: &ethpb.Checkpoint{
Epoch: 41,
Root: testhelpers.FillByteSlice(32, 42),
},
},
Signature: testhelpers.FillByteSlice(96, 43),
},
Attestation_2: &ethpb.IndexedAttestation{
AttestingIndices: []uint64{44, 45},
Data: &ethpb.AttestationData{
Slot: 46,
CommitteeIndex: 47,
BeaconBlockRoot: testhelpers.FillByteSlice(32, 38),
Source: &ethpb.Checkpoint{
Epoch: 49,
Root: testhelpers.FillByteSlice(32, 50),
},
Target: &ethpb.Checkpoint{
Epoch: 51,
Root: testhelpers.FillByteSlice(32, 52),
},
},
Signature: testhelpers.FillByteSlice(96, 53),
},
},
{
Attestation_1: &ethpb.IndexedAttestation{
AttestingIndices: []uint64{54, 55},
Data: &ethpb.AttestationData{
Slot: 56,
CommitteeIndex: 57,
BeaconBlockRoot: testhelpers.FillByteSlice(32, 38),
Source: &ethpb.Checkpoint{
Epoch: 59,
Root: testhelpers.FillByteSlice(32, 60),
},
Target: &ethpb.Checkpoint{
Epoch: 61,
Root: testhelpers.FillByteSlice(32, 62),
},
},
Signature: testhelpers.FillByteSlice(96, 63),
},
Attestation_2: &ethpb.IndexedAttestation{
AttestingIndices: []uint64{64, 65},
Data: &ethpb.AttestationData{
Slot: 66,
CommitteeIndex: 67,
BeaconBlockRoot: testhelpers.FillByteSlice(32, 38),
Source: &ethpb.Checkpoint{
Epoch: 69,
Root: testhelpers.FillByteSlice(32, 70),
},
Target: &ethpb.Checkpoint{
Epoch: 71,
Root: testhelpers.FillByteSlice(32, 72),
},
},
Signature: testhelpers.FillByteSlice(96, 73),
},
},
},
Attestations: []*ethpb.Attestation{
{
AggregationBits: testhelpers.FillByteSlice(4, 74),
Data: &ethpb.AttestationData{
Slot: 75,
CommitteeIndex: 76,
BeaconBlockRoot: testhelpers.FillByteSlice(32, 38),
Source: &ethpb.Checkpoint{
Epoch: 78,
Root: testhelpers.FillByteSlice(32, 79),
},
Target: &ethpb.Checkpoint{
Epoch: 80,
Root: testhelpers.FillByteSlice(32, 81),
},
},
Signature: testhelpers.FillByteSlice(96, 82),
},
{
AggregationBits: testhelpers.FillByteSlice(4, 83),
Data: &ethpb.AttestationData{
Slot: 84,
CommitteeIndex: 85,
BeaconBlockRoot: testhelpers.FillByteSlice(32, 38),
Source: &ethpb.Checkpoint{
Epoch: 87,
Root: testhelpers.FillByteSlice(32, 88),
},
Target: &ethpb.Checkpoint{
Epoch: 89,
Root: testhelpers.FillByteSlice(32, 90),
},
},
Signature: testhelpers.FillByteSlice(96, 91),
},
},
Deposits: []*ethpb.Deposit{
{
Proof: testhelpers.FillByteArraySlice(33, testhelpers.FillByteSlice(32, 92)),
Data: &ethpb.Deposit_Data{
PublicKey: testhelpers.FillByteSlice(48, 94),
WithdrawalCredentials: testhelpers.FillByteSlice(32, 95),
Amount: 96,
Signature: testhelpers.FillByteSlice(96, 97),
},
},
{
Proof: testhelpers.FillByteArraySlice(33, testhelpers.FillByteSlice(32, 98)),
Data: &ethpb.Deposit_Data{
PublicKey: testhelpers.FillByteSlice(48, 100),
WithdrawalCredentials: testhelpers.FillByteSlice(32, 101),
Amount: 102,
Signature: testhelpers.FillByteSlice(96, 103),
},
},
},
VoluntaryExits: []*ethpb.SignedVoluntaryExit{
{
Exit: &ethpb.VoluntaryExit{
Epoch: 104,
ValidatorIndex: 105,
},
Signature: testhelpers.FillByteSlice(96, 106),
},
{
Exit: &ethpb.VoluntaryExit{
Epoch: 107,
ValidatorIndex: 108,
},
Signature: testhelpers.FillByteSlice(96, 109),
},
},
SyncAggregate: &ethpb.SyncAggregate{
SyncCommitteeBits: testhelpers.FillByteSlice(64, 110),
SyncCommitteeSignature: testhelpers.FillByteSlice(96, 111),
},
ExecutionPayloadHeader: &enginev1.ExecutionPayloadHeader{
ParentHash: testhelpers.FillByteSlice(32, 112),
FeeRecipient: testhelpers.FillByteSlice(20, 113),
StateRoot: testhelpers.FillByteSlice(32, 114),
ReceiptsRoot: testhelpers.FillByteSlice(32, 115),
LogsBloom: testhelpers.FillByteSlice(256, 116),
PrevRandao: testhelpers.FillByteSlice(32, 117),
BlockNumber: 118,
GasLimit: 119,
GasUsed: 120,
Timestamp: 121,
ExtraData: testhelpers.FillByteSlice(32, 122),
BaseFeePerGas: testhelpers.FillByteSlice(32, 123),
BlockHash: testhelpers.FillByteSlice(32, 124),
TransactionsRoot: testhelpers.FillByteSlice(32, 125),
},
},
},
Signature: testhelpers.FillByteSlice(96, 126),
},
}
}

View File

@@ -1,310 +0,0 @@
package beacon_api
import (
"bytes"
"encoding/json"
"testing"
"github.com/OffchainLabs/prysm/v6/api/server/structs"
enginev1 "github.com/OffchainLabs/prysm/v6/proto/engine/v1"
ethpb "github.com/OffchainLabs/prysm/v6/proto/prysm/v1alpha1"
"github.com/OffchainLabs/prysm/v6/testing/assert"
"github.com/OffchainLabs/prysm/v6/testing/require"
"github.com/OffchainLabs/prysm/v6/validator/client/beacon-api/mock"
testhelpers "github.com/OffchainLabs/prysm/v6/validator/client/beacon-api/test-helpers"
"go.uber.org/mock/gomock"
)
func TestProposeBeaconBlock_BlindedCapella(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
jsonRestHandler := mock.NewMockJsonRestHandler(ctrl)
blindedCapellaBlock := generateSignedBlindedCapellaBlock()
genericSignedBlock := &ethpb.GenericSignedBeaconBlock{}
genericSignedBlock.Block = blindedCapellaBlock
jsonBlindedCapellaBlock, err := structs.SignedBlindedBeaconBlockCapellaFromConsensus(blindedCapellaBlock.BlindedCapella)
require.NoError(t, err)
marshalledBlock, err := json.Marshal(jsonBlindedCapellaBlock)
require.NoError(t, err)
ctx := t.Context()
// Make sure that what we send in the POST body is the marshalled version of the protobuf block
headers := map[string]string{"Eth-Consensus-Version": "capella"}
jsonRestHandler.EXPECT().Post(
gomock.Any(),
"/eth/v2/beacon/blinded_blocks",
headers,
bytes.NewBuffer(marshalledBlock),
nil,
)
validatorClient := &beaconApiValidatorClient{jsonRestHandler: jsonRestHandler}
proposeResponse, err := validatorClient.proposeBeaconBlock(ctx, genericSignedBlock)
assert.NoError(t, err)
require.NotNil(t, proposeResponse)
expectedBlockRoot, err := blindedCapellaBlock.BlindedCapella.Block.HashTreeRoot()
require.NoError(t, err)
// Make sure that the block root is set
assert.DeepEqual(t, expectedBlockRoot[:], proposeResponse.BlockRoot)
}
func generateSignedBlindedCapellaBlock() *ethpb.GenericSignedBeaconBlock_BlindedCapella {
return &ethpb.GenericSignedBeaconBlock_BlindedCapella{
BlindedCapella: &ethpb.SignedBlindedBeaconBlockCapella{
Block: &ethpb.BlindedBeaconBlockCapella{
Slot: 1,
ProposerIndex: 2,
ParentRoot: testhelpers.FillByteSlice(32, 3),
StateRoot: testhelpers.FillByteSlice(32, 4),
Body: &ethpb.BlindedBeaconBlockBodyCapella{
RandaoReveal: testhelpers.FillByteSlice(96, 5),
Eth1Data: &ethpb.Eth1Data{
DepositRoot: testhelpers.FillByteSlice(32, 6),
DepositCount: 7,
BlockHash: testhelpers.FillByteSlice(32, 8),
},
Graffiti: testhelpers.FillByteSlice(32, 9),
ProposerSlashings: []*ethpb.ProposerSlashing{
{
Header_1: &ethpb.SignedBeaconBlockHeader{
Header: &ethpb.BeaconBlockHeader{
Slot: 10,
ProposerIndex: 11,
ParentRoot: testhelpers.FillByteSlice(32, 12),
StateRoot: testhelpers.FillByteSlice(32, 13),
BodyRoot: testhelpers.FillByteSlice(32, 14),
},
Signature: testhelpers.FillByteSlice(96, 15),
},
Header_2: &ethpb.SignedBeaconBlockHeader{
Header: &ethpb.BeaconBlockHeader{
Slot: 16,
ProposerIndex: 17,
ParentRoot: testhelpers.FillByteSlice(32, 18),
StateRoot: testhelpers.FillByteSlice(32, 19),
BodyRoot: testhelpers.FillByteSlice(32, 20),
},
Signature: testhelpers.FillByteSlice(96, 21),
},
},
{
Header_1: &ethpb.SignedBeaconBlockHeader{
Header: &ethpb.BeaconBlockHeader{
Slot: 22,
ProposerIndex: 23,
ParentRoot: testhelpers.FillByteSlice(32, 24),
StateRoot: testhelpers.FillByteSlice(32, 25),
BodyRoot: testhelpers.FillByteSlice(32, 26),
},
Signature: testhelpers.FillByteSlice(96, 27),
},
Header_2: &ethpb.SignedBeaconBlockHeader{
Header: &ethpb.BeaconBlockHeader{
Slot: 28,
ProposerIndex: 29,
ParentRoot: testhelpers.FillByteSlice(32, 30),
StateRoot: testhelpers.FillByteSlice(32, 31),
BodyRoot: testhelpers.FillByteSlice(32, 32),
},
Signature: testhelpers.FillByteSlice(96, 33),
},
},
},
AttesterSlashings: []*ethpb.AttesterSlashing{
{
Attestation_1: &ethpb.IndexedAttestation{
AttestingIndices: []uint64{34, 35},
Data: &ethpb.AttestationData{
Slot: 36,
CommitteeIndex: 37,
BeaconBlockRoot: testhelpers.FillByteSlice(32, 38),
Source: &ethpb.Checkpoint{
Epoch: 39,
Root: testhelpers.FillByteSlice(32, 40),
},
Target: &ethpb.Checkpoint{
Epoch: 41,
Root: testhelpers.FillByteSlice(32, 42),
},
},
Signature: testhelpers.FillByteSlice(96, 43),
},
Attestation_2: &ethpb.IndexedAttestation{
AttestingIndices: []uint64{44, 45},
Data: &ethpb.AttestationData{
Slot: 46,
CommitteeIndex: 47,
BeaconBlockRoot: testhelpers.FillByteSlice(32, 38),
Source: &ethpb.Checkpoint{
Epoch: 49,
Root: testhelpers.FillByteSlice(32, 50),
},
Target: &ethpb.Checkpoint{
Epoch: 51,
Root: testhelpers.FillByteSlice(32, 52),
},
},
Signature: testhelpers.FillByteSlice(96, 53),
},
},
{
Attestation_1: &ethpb.IndexedAttestation{
AttestingIndices: []uint64{54, 55},
Data: &ethpb.AttestationData{
Slot: 56,
CommitteeIndex: 57,
BeaconBlockRoot: testhelpers.FillByteSlice(32, 38),
Source: &ethpb.Checkpoint{
Epoch: 59,
Root: testhelpers.FillByteSlice(32, 60),
},
Target: &ethpb.Checkpoint{
Epoch: 61,
Root: testhelpers.FillByteSlice(32, 62),
},
},
Signature: testhelpers.FillByteSlice(96, 63),
},
Attestation_2: &ethpb.IndexedAttestation{
AttestingIndices: []uint64{64, 65},
Data: &ethpb.AttestationData{
Slot: 66,
CommitteeIndex: 67,
BeaconBlockRoot: testhelpers.FillByteSlice(32, 38),
Source: &ethpb.Checkpoint{
Epoch: 69,
Root: testhelpers.FillByteSlice(32, 70),
},
Target: &ethpb.Checkpoint{
Epoch: 71,
Root: testhelpers.FillByteSlice(32, 72),
},
},
Signature: testhelpers.FillByteSlice(96, 73),
},
},
},
Attestations: []*ethpb.Attestation{
{
AggregationBits: testhelpers.FillByteSlice(4, 74),
Data: &ethpb.AttestationData{
Slot: 75,
CommitteeIndex: 76,
BeaconBlockRoot: testhelpers.FillByteSlice(32, 38),
Source: &ethpb.Checkpoint{
Epoch: 78,
Root: testhelpers.FillByteSlice(32, 79),
},
Target: &ethpb.Checkpoint{
Epoch: 80,
Root: testhelpers.FillByteSlice(32, 81),
},
},
Signature: testhelpers.FillByteSlice(96, 82),
},
{
AggregationBits: testhelpers.FillByteSlice(4, 83),
Data: &ethpb.AttestationData{
Slot: 84,
CommitteeIndex: 85,
BeaconBlockRoot: testhelpers.FillByteSlice(32, 38),
Source: &ethpb.Checkpoint{
Epoch: 87,
Root: testhelpers.FillByteSlice(32, 88),
},
Target: &ethpb.Checkpoint{
Epoch: 89,
Root: testhelpers.FillByteSlice(32, 90),
},
},
Signature: testhelpers.FillByteSlice(96, 91),
},
},
Deposits: []*ethpb.Deposit{
{
Proof: testhelpers.FillByteArraySlice(33, testhelpers.FillByteSlice(32, 92)),
Data: &ethpb.Deposit_Data{
PublicKey: testhelpers.FillByteSlice(48, 94),
WithdrawalCredentials: testhelpers.FillByteSlice(32, 95),
Amount: 96,
Signature: testhelpers.FillByteSlice(96, 97),
},
},
{
Proof: testhelpers.FillByteArraySlice(33, testhelpers.FillByteSlice(32, 98)),
Data: &ethpb.Deposit_Data{
PublicKey: testhelpers.FillByteSlice(48, 100),
WithdrawalCredentials: testhelpers.FillByteSlice(32, 101),
Amount: 102,
Signature: testhelpers.FillByteSlice(96, 103),
},
},
},
VoluntaryExits: []*ethpb.SignedVoluntaryExit{
{
Exit: &ethpb.VoluntaryExit{
Epoch: 104,
ValidatorIndex: 105,
},
Signature: testhelpers.FillByteSlice(96, 106),
},
{
Exit: &ethpb.VoluntaryExit{
Epoch: 107,
ValidatorIndex: 108,
},
Signature: testhelpers.FillByteSlice(96, 109),
},
},
SyncAggregate: &ethpb.SyncAggregate{
SyncCommitteeBits: testhelpers.FillByteSlice(64, 110),
SyncCommitteeSignature: testhelpers.FillByteSlice(96, 111),
},
ExecutionPayloadHeader: &enginev1.ExecutionPayloadHeaderCapella{
ParentHash: testhelpers.FillByteSlice(32, 112),
FeeRecipient: testhelpers.FillByteSlice(20, 113),
StateRoot: testhelpers.FillByteSlice(32, 114),
ReceiptsRoot: testhelpers.FillByteSlice(32, 115),
LogsBloom: testhelpers.FillByteSlice(256, 116),
PrevRandao: testhelpers.FillByteSlice(32, 117),
BlockNumber: 118,
GasLimit: 119,
GasUsed: 120,
Timestamp: 121,
ExtraData: testhelpers.FillByteSlice(32, 122),
BaseFeePerGas: testhelpers.FillByteSlice(32, 123),
BlockHash: testhelpers.FillByteSlice(32, 124),
TransactionsRoot: testhelpers.FillByteSlice(32, 125),
WithdrawalsRoot: testhelpers.FillByteSlice(32, 126),
},
BlsToExecutionChanges: []*ethpb.SignedBLSToExecutionChange{
{
Message: &ethpb.BLSToExecutionChange{
ValidatorIndex: 127,
FromBlsPubkey: testhelpers.FillByteSlice(48, 128),
ToExecutionAddress: testhelpers.FillByteSlice(20, 129),
},
Signature: testhelpers.FillByteSlice(96, 130),
},
{
Message: &ethpb.BLSToExecutionChange{
ValidatorIndex: 131,
FromBlsPubkey: testhelpers.FillByteSlice(48, 132),
ToExecutionAddress: testhelpers.FillByteSlice(20, 133),
},
Signature: testhelpers.FillByteSlice(96, 134),
},
},
},
},
Signature: testhelpers.FillByteSlice(96, 135),
},
}
}

View File

@@ -1,49 +0,0 @@
package beacon_api
import (
"bytes"
"encoding/json"
"testing"
"github.com/OffchainLabs/prysm/v6/api/server/structs"
rpctesting "github.com/OffchainLabs/prysm/v6/beacon-chain/rpc/eth/shared/testing"
"github.com/OffchainLabs/prysm/v6/testing/assert"
"github.com/OffchainLabs/prysm/v6/testing/require"
"github.com/OffchainLabs/prysm/v6/validator/client/beacon-api/mock"
"go.uber.org/mock/gomock"
)
func TestProposeBeaconBlock_BlindedDeneb(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
jsonRestHandler := mock.NewMockJsonRestHandler(ctrl)
var block structs.SignedBlindedBeaconBlockDeneb
err := json.Unmarshal([]byte(rpctesting.BlindedDenebBlock), &block)
require.NoError(t, err)
genericSignedBlock, err := block.ToGeneric()
require.NoError(t, err)
denebBytes, err := json.Marshal(block)
require.NoError(t, err)
// Make sure that what we send in the POST body is the marshalled version of the protobuf block
headers := map[string]string{"Eth-Consensus-Version": "deneb"}
jsonRestHandler.EXPECT().Post(
gomock.Any(),
"/eth/v2/beacon/blinded_blocks",
headers,
bytes.NewBuffer(denebBytes),
nil,
)
validatorClient := &beaconApiValidatorClient{jsonRestHandler: jsonRestHandler}
proposeResponse, err := validatorClient.proposeBeaconBlock(t.Context(), genericSignedBlock)
assert.NoError(t, err)
require.NotNil(t, proposeResponse)
expectedBlockRoot, err := genericSignedBlock.GetBlindedDeneb().HashTreeRoot()
require.NoError(t, err)
// Make sure that the block root is set
assert.DeepEqual(t, expectedBlockRoot[:], proposeResponse.BlockRoot)
}

View File

@@ -1,49 +0,0 @@
package beacon_api
import (
"bytes"
"encoding/json"
"testing"
"github.com/OffchainLabs/prysm/v6/api/server/structs"
rpctesting "github.com/OffchainLabs/prysm/v6/beacon-chain/rpc/eth/shared/testing"
"github.com/OffchainLabs/prysm/v6/testing/assert"
"github.com/OffchainLabs/prysm/v6/testing/require"
"github.com/OffchainLabs/prysm/v6/validator/client/beacon-api/mock"
"go.uber.org/mock/gomock"
)
func TestProposeBeaconBlock_BlindedElectra(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
jsonRestHandler := mock.NewMockJsonRestHandler(ctrl)
var block structs.SignedBlindedBeaconBlockElectra
err := json.Unmarshal([]byte(rpctesting.BlindedElectraBlock), &block)
require.NoError(t, err)
genericSignedBlock, err := block.ToGeneric()
require.NoError(t, err)
electraBytes, err := json.Marshal(block)
require.NoError(t, err)
// Make sure that what we send in the POST body is the marshalled version of the protobuf block
headers := map[string]string{"Eth-Consensus-Version": "electra"}
jsonRestHandler.EXPECT().Post(
gomock.Any(),
"/eth/v2/beacon/blinded_blocks",
headers,
bytes.NewBuffer(electraBytes),
nil,
)
validatorClient := &beaconApiValidatorClient{jsonRestHandler: jsonRestHandler}
proposeResponse, err := validatorClient.proposeBeaconBlock(t.Context(), genericSignedBlock)
assert.NoError(t, err)
require.NotNil(t, proposeResponse)
expectedBlockRoot, err := genericSignedBlock.GetBlindedElectra().HashTreeRoot()
require.NoError(t, err)
// Make sure that the block root is set
assert.DeepEqual(t, expectedBlockRoot[:], proposeResponse.BlockRoot)
}

View File

@@ -1,49 +0,0 @@
package beacon_api
import (
"bytes"
"encoding/json"
"testing"
"github.com/OffchainLabs/prysm/v6/api/server/structs"
rpctesting "github.com/OffchainLabs/prysm/v6/beacon-chain/rpc/eth/shared/testing"
"github.com/OffchainLabs/prysm/v6/testing/assert"
"github.com/OffchainLabs/prysm/v6/testing/require"
"github.com/OffchainLabs/prysm/v6/validator/client/beacon-api/mock"
"go.uber.org/mock/gomock"
)
func TestProposeBeaconBlock_BlindedFulu(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
jsonRestHandler := mock.NewMockJsonRestHandler(ctrl)
var block structs.SignedBlindedBeaconBlockFulu
err := json.Unmarshal([]byte(rpctesting.BlindedFuluBlock), &block)
require.NoError(t, err)
genericSignedBlock, err := block.ToGeneric()
require.NoError(t, err)
fuluBytes, err := json.Marshal(block)
require.NoError(t, err)
// Make sure that what we send in the POST body is the marshalled version of the protobuf block
headers := map[string]string{"Eth-Consensus-Version": "fulu"}
jsonRestHandler.EXPECT().Post(
gomock.Any(),
"/eth/v2/beacon/blinded_blocks",
headers,
bytes.NewBuffer(fuluBytes),
nil,
)
validatorClient := &beaconApiValidatorClient{jsonRestHandler: jsonRestHandler}
proposeResponse, err := validatorClient.proposeBeaconBlock(t.Context(), genericSignedBlock)
assert.NoError(t, err)
require.NotNil(t, proposeResponse)
expectedBlockRoot, err := genericSignedBlock.GetBlindedFulu().HashTreeRoot()
require.NoError(t, err)
// Make sure that the block root is set
assert.DeepEqual(t, expectedBlockRoot[:], proposeResponse.BlockRoot)
}

View File

@@ -1,62 +0,0 @@
package beacon_api
import (
"bytes"
"encoding/json"
"testing"
"github.com/OffchainLabs/prysm/v6/api/server/structs"
ethpb "github.com/OffchainLabs/prysm/v6/proto/prysm/v1alpha1"
"github.com/OffchainLabs/prysm/v6/testing/assert"
"github.com/OffchainLabs/prysm/v6/testing/require"
"github.com/OffchainLabs/prysm/v6/validator/client/beacon-api/mock"
testhelpers "github.com/OffchainLabs/prysm/v6/validator/client/beacon-api/test-helpers"
"go.uber.org/mock/gomock"
)
func TestProposeBeaconBlock_Capella(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
jsonRestHandler := mock.NewMockJsonRestHandler(ctrl)
capellaBlock := generateSignedCapellaBlock()
genericSignedBlock := &ethpb.GenericSignedBeaconBlock{}
genericSignedBlock.Block = capellaBlock
jsonCapellaBlock, err := structs.SignedBeaconBlockCapellaFromConsensus(capellaBlock.Capella)
require.NoError(t, err)
marshalledBlock, err := json.Marshal(jsonCapellaBlock)
require.NoError(t, err)
// Make sure that what we send in the POST body is the marshalled version of the protobuf block
headers := map[string]string{"Eth-Consensus-Version": "capella"}
jsonRestHandler.EXPECT().Post(
gomock.Any(),
"/eth/v2/beacon/blocks",
headers,
bytes.NewBuffer(marshalledBlock),
nil,
)
validatorClient := &beaconApiValidatorClient{jsonRestHandler: jsonRestHandler}
proposeResponse, err := validatorClient.proposeBeaconBlock(t.Context(), genericSignedBlock)
assert.NoError(t, err)
require.NotNil(t, proposeResponse)
expectedBlockRoot, err := capellaBlock.Capella.Block.HashTreeRoot()
require.NoError(t, err)
// Make sure that the block root is set
assert.DeepEqual(t, expectedBlockRoot[:], proposeResponse.BlockRoot)
}
func generateSignedCapellaBlock() *ethpb.GenericSignedBeaconBlock_Capella {
return &ethpb.GenericSignedBeaconBlock_Capella{
Capella: &ethpb.SignedBeaconBlockCapella{
Block: testhelpers.GenerateProtoCapellaBeaconBlock(),
Signature: testhelpers.FillByteSlice(96, 127),
},
}
}

View File

@@ -1,49 +0,0 @@
package beacon_api
import (
"bytes"
"encoding/json"
"testing"
"github.com/OffchainLabs/prysm/v6/api/server/structs"
rpctesting "github.com/OffchainLabs/prysm/v6/beacon-chain/rpc/eth/shared/testing"
"github.com/OffchainLabs/prysm/v6/testing/assert"
"github.com/OffchainLabs/prysm/v6/testing/require"
"github.com/OffchainLabs/prysm/v6/validator/client/beacon-api/mock"
"go.uber.org/mock/gomock"
)
func TestProposeBeaconBlock_Deneb(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
jsonRestHandler := mock.NewMockJsonRestHandler(ctrl)
var blockContents structs.SignedBeaconBlockContentsDeneb
err := json.Unmarshal([]byte(rpctesting.DenebBlockContents), &blockContents)
require.NoError(t, err)
genericSignedBlock, err := blockContents.ToGeneric()
require.NoError(t, err)
denebBytes, err := json.Marshal(blockContents)
require.NoError(t, err)
// Make sure that what we send in the POST body is the marshalled version of the protobuf block
headers := map[string]string{"Eth-Consensus-Version": "deneb"}
jsonRestHandler.EXPECT().Post(
gomock.Any(),
"/eth/v2/beacon/blocks",
headers,
bytes.NewBuffer(denebBytes),
nil,
)
validatorClient := &beaconApiValidatorClient{jsonRestHandler: jsonRestHandler}
proposeResponse, err := validatorClient.proposeBeaconBlock(t.Context(), genericSignedBlock)
assert.NoError(t, err)
require.NotNil(t, proposeResponse)
expectedBlockRoot, err := genericSignedBlock.GetDeneb().Block.HashTreeRoot()
require.NoError(t, err)
// Make sure that the block root is set
assert.DeepEqual(t, expectedBlockRoot[:], proposeResponse.BlockRoot)
}

View File

@@ -1,49 +0,0 @@
package beacon_api
import (
"bytes"
"encoding/json"
"testing"
"github.com/OffchainLabs/prysm/v6/api/server/structs"
rpctesting "github.com/OffchainLabs/prysm/v6/beacon-chain/rpc/eth/shared/testing"
"github.com/OffchainLabs/prysm/v6/testing/assert"
"github.com/OffchainLabs/prysm/v6/testing/require"
"github.com/OffchainLabs/prysm/v6/validator/client/beacon-api/mock"
"go.uber.org/mock/gomock"
)
func TestProposeBeaconBlock_Electra(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
jsonRestHandler := mock.NewMockJsonRestHandler(ctrl)
var blockContents structs.SignedBeaconBlockContentsElectra
err := json.Unmarshal([]byte(rpctesting.ElectraBlockContents), &blockContents)
require.NoError(t, err)
genericSignedBlock, err := blockContents.ToGeneric()
require.NoError(t, err)
electraBytes, err := json.Marshal(blockContents)
require.NoError(t, err)
// Make sure that what we send in the POST body is the marshalled version of the protobuf block
headers := map[string]string{"Eth-Consensus-Version": "electra"}
jsonRestHandler.EXPECT().Post(
gomock.Any(),
"/eth/v2/beacon/blocks",
headers,
bytes.NewBuffer(electraBytes),
nil,
)
validatorClient := &beaconApiValidatorClient{jsonRestHandler: jsonRestHandler}
proposeResponse, err := validatorClient.proposeBeaconBlock(t.Context(), genericSignedBlock)
assert.NoError(t, err)
require.NotNil(t, proposeResponse)
expectedBlockRoot, err := genericSignedBlock.GetElectra().Block.HashTreeRoot()
require.NoError(t, err)
// Make sure that the block root is set
assert.DeepEqual(t, expectedBlockRoot[:], proposeResponse.BlockRoot)
}

View File

@@ -1,49 +0,0 @@
package beacon_api
import (
"bytes"
"encoding/json"
"testing"
"github.com/OffchainLabs/prysm/v6/api/server/structs"
rpctesting "github.com/OffchainLabs/prysm/v6/beacon-chain/rpc/eth/shared/testing"
"github.com/OffchainLabs/prysm/v6/testing/assert"
"github.com/OffchainLabs/prysm/v6/testing/require"
"github.com/OffchainLabs/prysm/v6/validator/client/beacon-api/mock"
"go.uber.org/mock/gomock"
)
func TestProposeBeaconBlock_Fulu(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
jsonRestHandler := mock.NewMockJsonRestHandler(ctrl)
var blockContents structs.SignedBeaconBlockContentsFulu
err := json.Unmarshal([]byte(rpctesting.FuluBlockContents), &blockContents)
require.NoError(t, err)
genericSignedBlock, err := blockContents.ToGeneric()
require.NoError(t, err)
fuluBytes, err := json.Marshal(blockContents)
require.NoError(t, err)
// Make sure that what we send in the POST body is the marshalled version of the protobuf block
headers := map[string]string{"Eth-Consensus-Version": "fulu"}
jsonRestHandler.EXPECT().Post(
gomock.Any(),
"/eth/v2/beacon/blocks",
headers,
bytes.NewBuffer(fuluBytes),
nil,
)
validatorClient := &beaconApiValidatorClient{jsonRestHandler: jsonRestHandler}
proposeResponse, err := validatorClient.proposeBeaconBlock(t.Context(), genericSignedBlock)
assert.NoError(t, err)
require.NotNil(t, proposeResponse)
expectedBlockRoot, err := genericSignedBlock.GetFulu().Block.HashTreeRoot()
require.NoError(t, err)
// Make sure that the block root is set
assert.DeepEqual(t, expectedBlockRoot[:], proposeResponse.BlockRoot)
}

View File

@@ -1,63 +0,0 @@
package beacon_api
import (
"bytes"
"encoding/json"
"testing"
"github.com/OffchainLabs/prysm/v6/api/server/structs"
ethpb "github.com/OffchainLabs/prysm/v6/proto/prysm/v1alpha1"
"github.com/OffchainLabs/prysm/v6/testing/assert"
"github.com/OffchainLabs/prysm/v6/testing/require"
"github.com/OffchainLabs/prysm/v6/validator/client/beacon-api/mock"
testhelpers "github.com/OffchainLabs/prysm/v6/validator/client/beacon-api/test-helpers"
"go.uber.org/mock/gomock"
)
func TestProposeBeaconBlock_Phase0(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
jsonRestHandler := mock.NewMockJsonRestHandler(ctrl)
phase0Block := generateSignedPhase0Block()
genericSignedBlock := &ethpb.GenericSignedBeaconBlock{}
genericSignedBlock.Block = phase0Block
jsonPhase0Block := structs.SignedBeaconBlockPhase0FromConsensus(phase0Block.Phase0)
marshalledBlock, err := json.Marshal(jsonPhase0Block)
require.NoError(t, err)
ctx := t.Context()
// Make sure that what we send in the POST body is the marshalled version of the protobuf block
headers := map[string]string{"Eth-Consensus-Version": "phase0"}
jsonRestHandler.EXPECT().Post(
gomock.Any(),
"/eth/v2/beacon/blocks",
headers,
bytes.NewBuffer(marshalledBlock),
nil,
)
validatorClient := &beaconApiValidatorClient{jsonRestHandler: jsonRestHandler}
proposeResponse, err := validatorClient.proposeBeaconBlock(ctx, genericSignedBlock)
assert.NoError(t, err)
require.NotNil(t, proposeResponse)
expectedBlockRoot, err := phase0Block.Phase0.Block.HashTreeRoot()
require.NoError(t, err)
// Make sure that the block root is set
assert.DeepEqual(t, expectedBlockRoot[:], proposeResponse.BlockRoot)
}
func generateSignedPhase0Block() *ethpb.GenericSignedBeaconBlock_Phase0 {
return &ethpb.GenericSignedBeaconBlock_Phase0{
Phase0: &ethpb.SignedBeaconBlock{
Block: testhelpers.GenerateProtoPhase0BeaconBlock(),
Signature: testhelpers.FillByteSlice(96, 110),
},
}
}

View File

@@ -1,43 +1,42 @@
package beacon_api
import (
"bytes"
"encoding/json"
"errors"
"net/http"
"testing"
"github.com/OffchainLabs/prysm/v6/api/server/structs"
rpctesting "github.com/OffchainLabs/prysm/v6/beacon-chain/rpc/eth/shared/testing"
"github.com/OffchainLabs/prysm/v6/network/httputil"
engine "github.com/OffchainLabs/prysm/v6/proto/engine/v1"
ethpb "github.com/OffchainLabs/prysm/v6/proto/prysm/v1alpha1"
"github.com/OffchainLabs/prysm/v6/testing/assert"
"github.com/OffchainLabs/prysm/v6/testing/require"
"github.com/OffchainLabs/prysm/v6/validator/client/beacon-api/mock"
testhelpers "github.com/OffchainLabs/prysm/v6/validator/client/beacon-api/test-helpers"
"go.uber.org/mock/gomock"
)
func TestProposeBeaconBlock_Error(t *testing.T) {
func TestProposeBeaconBlock_SSZ_Error(t *testing.T) {
testSuites := []struct {
name string
returnedError error
expectedErrorMessage string
}{
{
name: "error 202",
expectedErrorMessage: "block was successfully broadcast but failed validation",
returnedError: &httputil.DefaultJsonError{
Code: http.StatusAccepted,
Message: "202 error",
},
},
{
name: "error 500",
expectedErrorMessage: "HTTP request unsuccessful (500: foo error)",
expectedErrorMessage: "failed to submit block ssz",
returnedError: &httputil.DefaultJsonError{
Code: http.StatusInternalServerError,
Message: "foo error",
Message: "failed to submit block ssz",
},
},
{
name: "other error",
expectedErrorMessage: "foo error",
returnedError: errors.New("foo error"),
expectedErrorMessage: "failed to submit block ssz",
returnedError: errors.New("failed to submit block ssz"),
},
}
@@ -106,17 +105,21 @@ func TestProposeBeaconBlock_Error(t *testing.T) {
ctx := t.Context()
jsonRestHandler := mock.NewMockJsonRestHandler(ctrl)
headers := map[string]string{"Eth-Consensus-Version": testCase.consensusVersion}
jsonRestHandler.EXPECT().Post(
// Expect PostSSZ to be called first with SSZ data
headers := map[string]string{
"Eth-Consensus-Version": testCase.consensusVersion,
}
jsonRestHandler.EXPECT().PostSSZ(
gomock.Any(),
testCase.endpoint,
headers,
gomock.Any(),
nil,
).Return(
testSuite.returnedError,
nil, nil, testSuite.returnedError,
).Times(1)
// No JSON fallback expected for non-406 errors
validatorClient := &beaconApiValidatorClient{jsonRestHandler: jsonRestHandler}
_, err := validatorClient.proposeBeaconBlock(ctx, testCase.block)
assert.ErrorContains(t, testSuite.expectedErrorMessage, err)
@@ -130,3 +133,546 @@ func TestProposeBeaconBlock_UnsupportedBlockType(t *testing.T) {
_, err := validatorClient.proposeBeaconBlock(t.Context(), &ethpb.GenericSignedBeaconBlock{})
assert.ErrorContains(t, "unsupported block type", err)
}
func TestProposeBeaconBlock_SSZSuccess_NoFallback(t *testing.T) {
testCases := []struct {
name string
consensusVersion string
endpoint string
block *ethpb.GenericSignedBeaconBlock
}{
{
name: "phase0",
consensusVersion: "phase0",
endpoint: "/eth/v2/beacon/blocks",
block: &ethpb.GenericSignedBeaconBlock{
Block: generateSignedPhase0Block(),
},
},
{
name: "altair",
consensusVersion: "altair",
endpoint: "/eth/v2/beacon/blocks",
block: &ethpb.GenericSignedBeaconBlock{
Block: generateSignedAltairBlock(),
},
},
}
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
ctx := t.Context()
jsonRestHandler := mock.NewMockJsonRestHandler(ctrl)
// Expect PostSSZ to be called and succeed
headers := map[string]string{
"Eth-Consensus-Version": testCase.consensusVersion,
}
jsonRestHandler.EXPECT().PostSSZ(
gomock.Any(),
testCase.endpoint,
headers,
gomock.Any(),
).Return(
nil, nil, nil,
).Times(1)
// Post should NOT be called when PostSSZ succeeds
jsonRestHandler.EXPECT().Post(
gomock.Any(),
gomock.Any(),
gomock.Any(),
gomock.Any(),
gomock.Any(),
).Times(0)
validatorClient := &beaconApiValidatorClient{jsonRestHandler: jsonRestHandler}
_, err := validatorClient.proposeBeaconBlock(ctx, testCase.block)
assert.NoError(t, err)
})
}
}
func TestProposeBeaconBlock_NewerTypes_SSZMarshal(t *testing.T) {
t.Run("deneb", func(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
jsonRestHandler := mock.NewMockJsonRestHandler(ctrl)
var blockContents structs.SignedBeaconBlockContentsDeneb
err := json.Unmarshal([]byte(rpctesting.DenebBlockContents), &blockContents)
require.NoError(t, err)
genericSignedBlock, err := blockContents.ToGeneric()
require.NoError(t, err)
denebBytes, err := genericSignedBlock.GetDeneb().MarshalSSZ()
require.NoError(t, err)
jsonRestHandler.EXPECT().PostSSZ(
gomock.Any(),
"/eth/v2/beacon/blocks",
gomock.Any(),
bytes.NewBuffer(denebBytes),
)
validatorClient := &beaconApiValidatorClient{jsonRestHandler: jsonRestHandler}
proposeResponse, err := validatorClient.proposeBeaconBlock(t.Context(), genericSignedBlock)
assert.NoError(t, err)
require.NotNil(t, proposeResponse)
expectedBlockRoot, err := genericSignedBlock.GetDeneb().Block.HashTreeRoot()
require.NoError(t, err)
assert.DeepEqual(t, expectedBlockRoot[:], proposeResponse.BlockRoot)
})
t.Run("blinded_deneb", func(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
jsonRestHandler := mock.NewMockJsonRestHandler(ctrl)
var blindedBlock structs.SignedBlindedBeaconBlockDeneb
err := json.Unmarshal([]byte(rpctesting.BlindedDenebBlock), &blindedBlock)
require.NoError(t, err)
genericSignedBlock, err := blindedBlock.ToGeneric()
require.NoError(t, err)
blindedDenebBytes, err := genericSignedBlock.GetBlindedDeneb().MarshalSSZ()
require.NoError(t, err)
jsonRestHandler.EXPECT().PostSSZ(
gomock.Any(),
"/eth/v2/beacon/blinded_blocks",
gomock.Any(),
bytes.NewBuffer(blindedDenebBytes),
)
validatorClient := &beaconApiValidatorClient{jsonRestHandler: jsonRestHandler}
proposeResponse, err := validatorClient.proposeBeaconBlock(t.Context(), genericSignedBlock)
assert.NoError(t, err)
require.NotNil(t, proposeResponse)
expectedBlockRoot, err := genericSignedBlock.GetBlindedDeneb().HashTreeRoot()
require.NoError(t, err)
assert.DeepEqual(t, expectedBlockRoot[:], proposeResponse.BlockRoot)
})
t.Run("electra", func(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
jsonRestHandler := mock.NewMockJsonRestHandler(ctrl)
var blockContents structs.SignedBeaconBlockContentsElectra
err := json.Unmarshal([]byte(rpctesting.ElectraBlockContents), &blockContents)
require.NoError(t, err)
genericSignedBlock, err := blockContents.ToGeneric()
require.NoError(t, err)
electraBytes, err := genericSignedBlock.GetElectra().MarshalSSZ()
require.NoError(t, err)
jsonRestHandler.EXPECT().PostSSZ(
gomock.Any(),
"/eth/v2/beacon/blocks",
gomock.Any(),
bytes.NewBuffer(electraBytes),
)
validatorClient := &beaconApiValidatorClient{jsonRestHandler: jsonRestHandler}
proposeResponse, err := validatorClient.proposeBeaconBlock(t.Context(), genericSignedBlock)
assert.NoError(t, err)
require.NotNil(t, proposeResponse)
expectedBlockRoot, err := genericSignedBlock.GetElectra().Block.HashTreeRoot()
require.NoError(t, err)
assert.DeepEqual(t, expectedBlockRoot[:], proposeResponse.BlockRoot)
})
t.Run("blinded_electra", func(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
jsonRestHandler := mock.NewMockJsonRestHandler(ctrl)
var blindedBlock structs.SignedBlindedBeaconBlockElectra
err := json.Unmarshal([]byte(rpctesting.BlindedElectraBlock), &blindedBlock)
require.NoError(t, err)
genericSignedBlock, err := blindedBlock.ToGeneric()
require.NoError(t, err)
blindedElectraBytes, err := genericSignedBlock.GetBlindedElectra().MarshalSSZ()
require.NoError(t, err)
jsonRestHandler.EXPECT().PostSSZ(
gomock.Any(),
"/eth/v2/beacon/blinded_blocks",
gomock.Any(),
bytes.NewBuffer(blindedElectraBytes),
)
validatorClient := &beaconApiValidatorClient{jsonRestHandler: jsonRestHandler}
proposeResponse, err := validatorClient.proposeBeaconBlock(t.Context(), genericSignedBlock)
assert.NoError(t, err)
require.NotNil(t, proposeResponse)
expectedBlockRoot, err := genericSignedBlock.GetBlindedElectra().HashTreeRoot()
require.NoError(t, err)
assert.DeepEqual(t, expectedBlockRoot[:], proposeResponse.BlockRoot)
})
t.Run("fulu", func(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
jsonRestHandler := mock.NewMockJsonRestHandler(ctrl)
var blockContents structs.SignedBeaconBlockContentsFulu
err := json.Unmarshal([]byte(rpctesting.FuluBlockContents), &blockContents)
require.NoError(t, err)
genericSignedBlock, err := blockContents.ToGeneric()
require.NoError(t, err)
fuluBytes, err := genericSignedBlock.GetFulu().MarshalSSZ()
require.NoError(t, err)
jsonRestHandler.EXPECT().PostSSZ(
gomock.Any(),
"/eth/v2/beacon/blocks",
gomock.Any(),
bytes.NewBuffer(fuluBytes),
)
validatorClient := &beaconApiValidatorClient{jsonRestHandler: jsonRestHandler}
proposeResponse, err := validatorClient.proposeBeaconBlock(t.Context(), genericSignedBlock)
assert.NoError(t, err)
require.NotNil(t, proposeResponse)
expectedBlockRoot, err := genericSignedBlock.GetFulu().Block.HashTreeRoot()
require.NoError(t, err)
assert.DeepEqual(t, expectedBlockRoot[:], proposeResponse.BlockRoot)
})
t.Run("blinded_fulu", func(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
jsonRestHandler := mock.NewMockJsonRestHandler(ctrl)
var blindedBlock structs.SignedBlindedBeaconBlockFulu
err := json.Unmarshal([]byte(rpctesting.BlindedFuluBlock), &blindedBlock)
require.NoError(t, err)
genericSignedBlock, err := blindedBlock.ToGeneric()
require.NoError(t, err)
blindedFuluBytes, err := genericSignedBlock.GetBlindedFulu().MarshalSSZ()
require.NoError(t, err)
jsonRestHandler.EXPECT().PostSSZ(
gomock.Any(),
"/eth/v2/beacon/blinded_blocks",
gomock.Any(),
bytes.NewBuffer(blindedFuluBytes),
)
validatorClient := &beaconApiValidatorClient{jsonRestHandler: jsonRestHandler}
proposeResponse, err := validatorClient.proposeBeaconBlock(t.Context(), genericSignedBlock)
assert.NoError(t, err)
require.NotNil(t, proposeResponse)
expectedBlockRoot, err := genericSignedBlock.GetBlindedFulu().HashTreeRoot()
require.NoError(t, err)
assert.DeepEqual(t, expectedBlockRoot[:], proposeResponse.BlockRoot)
})
}
// Generator functions for test blocks
func generateSignedPhase0Block() *ethpb.GenericSignedBeaconBlock_Phase0 {
return &ethpb.GenericSignedBeaconBlock_Phase0{
Phase0: &ethpb.SignedBeaconBlock{
Block: testhelpers.GenerateProtoPhase0BeaconBlock(),
Signature: testhelpers.FillByteSlice(96, 110),
},
}
}
func generateSignedAltairBlock() *ethpb.GenericSignedBeaconBlock_Altair {
return &ethpb.GenericSignedBeaconBlock_Altair{
Altair: &ethpb.SignedBeaconBlockAltair{
Block: testhelpers.GenerateProtoAltairBeaconBlock(),
Signature: testhelpers.FillByteSlice(96, 112),
},
}
}
func generateSignedBellatrixBlock() *ethpb.GenericSignedBeaconBlock_Bellatrix {
return &ethpb.GenericSignedBeaconBlock_Bellatrix{
Bellatrix: &ethpb.SignedBeaconBlockBellatrix{
Block: testhelpers.GenerateProtoBellatrixBeaconBlock(),
Signature: testhelpers.FillByteSlice(96, 127),
},
}
}
func generateSignedBlindedBellatrixBlock() *ethpb.GenericSignedBeaconBlock_BlindedBellatrix {
return &ethpb.GenericSignedBeaconBlock_BlindedBellatrix{
BlindedBellatrix: &ethpb.SignedBlindedBeaconBlockBellatrix{
Block: &ethpb.BlindedBeaconBlockBellatrix{
Slot: 1,
ProposerIndex: 2,
ParentRoot: testhelpers.FillByteSlice(32, 3),
StateRoot: testhelpers.FillByteSlice(32, 4),
Body: &ethpb.BlindedBeaconBlockBodyBellatrix{
RandaoReveal: testhelpers.FillByteSlice(96, 5),
Eth1Data: &ethpb.Eth1Data{
DepositRoot: testhelpers.FillByteSlice(32, 6),
DepositCount: 7,
BlockHash: testhelpers.FillByteSlice(32, 8),
},
Graffiti: testhelpers.FillByteSlice(32, 9),
ProposerSlashings: []*ethpb.ProposerSlashing{
{
Header_1: &ethpb.SignedBeaconBlockHeader{
Header: &ethpb.BeaconBlockHeader{
Slot: 10,
ProposerIndex: 11,
ParentRoot: testhelpers.FillByteSlice(32, 12),
StateRoot: testhelpers.FillByteSlice(32, 13),
BodyRoot: testhelpers.FillByteSlice(32, 14),
},
Signature: testhelpers.FillByteSlice(96, 15),
},
Header_2: &ethpb.SignedBeaconBlockHeader{
Header: &ethpb.BeaconBlockHeader{
Slot: 16,
ProposerIndex: 17,
ParentRoot: testhelpers.FillByteSlice(32, 18),
StateRoot: testhelpers.FillByteSlice(32, 19),
BodyRoot: testhelpers.FillByteSlice(32, 20),
},
Signature: testhelpers.FillByteSlice(96, 21),
},
},
},
AttesterSlashings: []*ethpb.AttesterSlashing{},
Attestations: []*ethpb.Attestation{},
Deposits: []*ethpb.Deposit{},
VoluntaryExits: []*ethpb.SignedVoluntaryExit{},
SyncAggregate: &ethpb.SyncAggregate{
SyncCommitteeBits: testhelpers.FillByteSlice(64, 100),
SyncCommitteeSignature: testhelpers.FillByteSlice(96, 101),
},
ExecutionPayloadHeader: &engine.ExecutionPayloadHeader{
ParentHash: testhelpers.FillByteSlice(32, 102),
FeeRecipient: testhelpers.FillByteSlice(20, 103),
StateRoot: testhelpers.FillByteSlice(32, 104),
ReceiptsRoot: testhelpers.FillByteSlice(32, 105),
LogsBloom: testhelpers.FillByteSlice(256, 106),
PrevRandao: testhelpers.FillByteSlice(32, 107),
BlockNumber: 108,
GasLimit: 109,
GasUsed: 110,
Timestamp: 111,
ExtraData: testhelpers.FillByteSlice(32, 112),
BaseFeePerGas: testhelpers.FillByteSlice(32, 113),
BlockHash: testhelpers.FillByteSlice(32, 114),
TransactionsRoot: testhelpers.FillByteSlice(32, 115),
},
},
},
Signature: testhelpers.FillByteSlice(96, 116),
},
}
}
func generateSignedCapellaBlock() *ethpb.GenericSignedBeaconBlock_Capella {
return &ethpb.GenericSignedBeaconBlock_Capella{
Capella: &ethpb.SignedBeaconBlockCapella{
Block: testhelpers.GenerateProtoCapellaBeaconBlock(),
Signature: testhelpers.FillByteSlice(96, 127),
},
}
}
func generateSignedBlindedCapellaBlock() *ethpb.GenericSignedBeaconBlock_BlindedCapella {
return &ethpb.GenericSignedBeaconBlock_BlindedCapella{
BlindedCapella: &ethpb.SignedBlindedBeaconBlockCapella{
Block: &ethpb.BlindedBeaconBlockCapella{
Slot: 1,
ProposerIndex: 2,
ParentRoot: testhelpers.FillByteSlice(32, 3),
StateRoot: testhelpers.FillByteSlice(32, 4),
Body: &ethpb.BlindedBeaconBlockBodyCapella{
RandaoReveal: testhelpers.FillByteSlice(96, 5),
Eth1Data: &ethpb.Eth1Data{
DepositRoot: testhelpers.FillByteSlice(32, 6),
DepositCount: 7,
BlockHash: testhelpers.FillByteSlice(32, 8),
},
Graffiti: testhelpers.FillByteSlice(32, 9),
ProposerSlashings: []*ethpb.ProposerSlashing{
{
Header_1: &ethpb.SignedBeaconBlockHeader{
Header: &ethpb.BeaconBlockHeader{
Slot: 10,
ProposerIndex: 11,
ParentRoot: testhelpers.FillByteSlice(32, 12),
StateRoot: testhelpers.FillByteSlice(32, 13),
BodyRoot: testhelpers.FillByteSlice(32, 14),
},
Signature: testhelpers.FillByteSlice(96, 15),
},
Header_2: &ethpb.SignedBeaconBlockHeader{
Header: &ethpb.BeaconBlockHeader{
Slot: 16,
ProposerIndex: 17,
ParentRoot: testhelpers.FillByteSlice(32, 18),
StateRoot: testhelpers.FillByteSlice(32, 19),
BodyRoot: testhelpers.FillByteSlice(32, 20),
},
Signature: testhelpers.FillByteSlice(96, 21),
},
},
},
AttesterSlashings: []*ethpb.AttesterSlashing{},
Attestations: []*ethpb.Attestation{},
Deposits: []*ethpb.Deposit{},
VoluntaryExits: []*ethpb.SignedVoluntaryExit{},
SyncAggregate: &ethpb.SyncAggregate{
SyncCommitteeBits: testhelpers.FillByteSlice(64, 37),
SyncCommitteeSignature: testhelpers.FillByteSlice(96, 38),
},
ExecutionPayloadHeader: &engine.ExecutionPayloadHeaderCapella{
ParentHash: testhelpers.FillByteSlice(32, 39),
FeeRecipient: testhelpers.FillByteSlice(20, 40),
StateRoot: testhelpers.FillByteSlice(32, 41),
ReceiptsRoot: testhelpers.FillByteSlice(32, 42),
LogsBloom: testhelpers.FillByteSlice(256, 43),
PrevRandao: testhelpers.FillByteSlice(32, 44),
BlockNumber: 45,
GasLimit: 46,
GasUsed: 47,
Timestamp: 48,
ExtraData: testhelpers.FillByteSlice(32, 49),
BaseFeePerGas: testhelpers.FillByteSlice(32, 50),
BlockHash: testhelpers.FillByteSlice(32, 51),
TransactionsRoot: testhelpers.FillByteSlice(32, 52),
WithdrawalsRoot: testhelpers.FillByteSlice(32, 53),
},
BlsToExecutionChanges: []*ethpb.SignedBLSToExecutionChange{},
},
},
Signature: testhelpers.FillByteSlice(96, 54),
},
}
}
func TestProposeBeaconBlock_SSZFails_406_FallbackToJSON(t *testing.T) {
testCases := []struct {
name string
consensusVersion string
endpoint string
block *ethpb.GenericSignedBeaconBlock
}{
{
name: "phase0",
consensusVersion: "phase0",
endpoint: "/eth/v2/beacon/blocks",
block: &ethpb.GenericSignedBeaconBlock{
Block: generateSignedPhase0Block(),
},
},
}
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
ctx := t.Context()
jsonRestHandler := mock.NewMockJsonRestHandler(ctrl)
// Expect PostSSZ to be called first and fail
jsonRestHandler.EXPECT().PostSSZ(
gomock.Any(),
testCase.endpoint,
gomock.Any(),
gomock.Any(),
).Return(
nil, nil, &httputil.DefaultJsonError{
Code: http.StatusNotAcceptable,
Message: "SSZ not supported",
},
).Times(1)
jsonRestHandler.EXPECT().Post(
gomock.Any(),
testCase.endpoint,
gomock.Any(),
gomock.Any(),
nil,
).Return(
nil,
).Times(1)
validatorClient := &beaconApiValidatorClient{jsonRestHandler: jsonRestHandler}
_, err := validatorClient.proposeBeaconBlock(ctx, testCase.block)
assert.NoError(t, err)
})
}
}
func TestProposeBeaconBlock_SSZFails_Non406_NoFallback(t *testing.T) {
testCases := []struct {
name string
consensusVersion string
endpoint string
block *ethpb.GenericSignedBeaconBlock
}{
{
name: "phase0",
consensusVersion: "phase0",
endpoint: "/eth/v2/beacon/blocks",
block: &ethpb.GenericSignedBeaconBlock{
Block: generateSignedPhase0Block(),
},
},
}
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
ctx := t.Context()
jsonRestHandler := mock.NewMockJsonRestHandler(ctrl)
// Expect PostSSZ to be called first and fail with non-406 error
sszHeaders := map[string]string{
"Eth-Consensus-Version": testCase.consensusVersion,
}
jsonRestHandler.EXPECT().PostSSZ(
gomock.Any(),
testCase.endpoint,
sszHeaders,
gomock.Any(),
).Return(
nil, nil, &httputil.DefaultJsonError{
Code: http.StatusInternalServerError,
Message: "Internal server error",
},
).Times(1)
// Post should NOT be called for non-406 errors
jsonRestHandler.EXPECT().Post(
gomock.Any(),
gomock.Any(),
gomock.Any(),
gomock.Any(),
gomock.Any(),
).Times(0)
validatorClient := &beaconApiValidatorClient{jsonRestHandler: jsonRestHandler}
_, err := validatorClient.proposeBeaconBlock(ctx, testCase.block)
require.ErrorContains(t, "Internal server error", err)
})
}
}

View File

@@ -25,6 +25,7 @@ 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
PostSSZ(ctx context.Context, endpoint string, headers map[string]string, data *bytes.Buffer) ([]byte, http.Header, error)
HttpClient() *http.Client
Host() string
SetHost(host string)
@@ -179,6 +180,75 @@ func (c *BeaconApiRestHandler) Post(
return decodeResp(httpResp, resp)
}
// PostSSZ sends a POST request and prefers an SSZ (application/octet-stream) response body.
func (c *BeaconApiRestHandler) PostSSZ(
ctx context.Context,
apiEndpoint string,
headers map[string]string,
data *bytes.Buffer,
) ([]byte, http.Header, error) {
if data == nil {
return nil, nil, errors.New("data is nil")
}
url := c.host + apiEndpoint
req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, data)
if err != nil {
return nil, nil, errors.Wrapf(err, "failed to create request for endpoint %s", url)
}
// Accept header: prefer octet-stream (SSZ), fall back to JSON
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)
req.Header.Set("Accept", acceptHeaderString)
// User-supplied headers
for headerKey, headerValue := range headers {
req.Header.Set(headerKey, headerValue)
}
for _, o := range c.reqOverrides {
o(req)
}
req.Header.Set("Content-Type", api.OctetStreamMediaType)
req.Header.Set("User-Agent", version.BuildData())
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
}
}()
accept := req.Header.Get("Accept")
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 !apiutil.PrimaryAcceptMatches(accept, contentType) {
log.WithFields(logrus.Fields{
"Accept": accept,
"Content-Type": 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
}
return body, httpResp.Header, nil
}
func decodeResp(httpResp *http.Response, resp interface{}) error {
body, err := io.ReadAll(httpResp.Body)
if err != nil {