Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

validator REST API: block v2 and Electra support #14623

Open
wants to merge 5 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ The format is based on Keep a Changelog, and this project adheres to Semantic Ve
- Add ability to rollback node's internal state during processing.
- Change how unsafe protobuf state is created to prevent unnecessary copies.
- Added benchmarks for process slots for Capella, Deneb, Electra
- Validator REST mode Electra support

### Changed

Expand Down Expand Up @@ -48,6 +49,7 @@ The format is based on Keep a Changelog, and this project adheres to Semantic Ve
- Capella blocks are execution.
- Fixed panic when http request to subscribe to event stream fails.
- Return early for blob reconstructor during capella fork
- Updated block endpoint from V1 to V2

### Deprecated

Expand Down
2 changes: 2 additions & 0 deletions validator/client/beacon-api/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -97,8 +97,10 @@ go_test(
"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_capella_test.go",
"propose_beacon_block_deneb_test.go",
"propose_beacon_block_electra_test.go",
"propose_beacon_block_phase0_test.go",
"propose_beacon_block_test.go",
"propose_exit_test.go",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ func TestBeaconApiValidatorClient_ProposeBeaconBlockValid(t *testing.T) {
jsonRestHandler := mock.NewMockJsonRestHandler(ctrl)
jsonRestHandler.EXPECT().Post(
gomock.Any(),
"/eth/v1/beacon/blocks",
"/eth/v2/beacon/blocks",
map[string]string{"Eth-Consensus-Version": "phase0"},
gomock.Any(),
nil,
Expand Down Expand Up @@ -175,7 +175,7 @@ func TestBeaconApiValidatorClient_ProposeBeaconBlockError(t *testing.T) {
jsonRestHandler := mock.NewMockJsonRestHandler(ctrl)
jsonRestHandler.EXPECT().Post(
gomock.Any(),
"/eth/v1/beacon/blocks",
"/eth/v2/beacon/blocks",
map[string]string{"Eth-Consensus-Version": "phase0"},
gomock.Any(),
nil,
Expand Down
34 changes: 31 additions & 3 deletions validator/client/beacon-api/get_beacon_block.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,14 @@ func (c *beaconApiValidatorClient) beaconBlock(ctx context.Context, slot primiti
blinded = produceBlockV3ResponseJson.ExecutionPayloadBlinded
decoder = json.NewDecoder(bytes.NewReader(produceBlockV3ResponseJson.Data))
}
return processBlockResponse(ver, blinded, decoder)
}

func processBlockResponse(ver string, isBlinded bool, decoder *json.Decoder) (*ethpb.GenericBeaconBlock, error) {
var response *ethpb.GenericBeaconBlock
if decoder == nil {
return nil, errors.New("no produce block json decoder found")
}
switch ver {
case version.String(version.Phase0):
jsonPhase0Block := structs.BeaconBlock{}
Expand All @@ -93,7 +99,7 @@ func (c *beaconApiValidatorClient) beaconBlock(ctx context.Context, slot primiti
}
response = genericBlock
case version.String(version.Bellatrix):
if blinded {
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")
Expand All @@ -115,7 +121,7 @@ func (c *beaconApiValidatorClient) beaconBlock(ctx context.Context, slot primiti
response = genericBlock
}
case version.String(version.Capella):
if blinded {
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")
Expand All @@ -137,7 +143,7 @@ func (c *beaconApiValidatorClient) beaconBlock(ctx context.Context, slot primiti
response = genericBlock
}
case version.String(version.Deneb):
if blinded {
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")
Expand All @@ -158,6 +164,28 @@ func (c *beaconApiValidatorClient) beaconBlock(ctx context.Context, slot primiti
}
response = genericBlock
}
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
}
default:
return nil, errors.Errorf("unsupported consensus version `%s`", ver)
}
Expand Down
90 changes: 90 additions & 0 deletions validator/client/beacon-api/get_beacon_block_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -500,6 +500,96 @@ func TestGetBeaconBlock_BlindedDenebValid(t *testing.T) {
assert.DeepEqual(t, expectedBeaconBlock, beaconBlock)
}

func TestGetBeaconBlock_ElectraValid(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()

proto := testhelpers.GenerateProtoElectraBeaconBlockContents()
block := testhelpers.GenerateJsonElectraBeaconBlockContents()
bytes, err := json.Marshal(block)
require.NoError(t, err)

const slot = primitives.Slot(1)
randaoReveal := []byte{2}
graffiti := []byte{3}

ctx := context.Background()

jsonRestHandler := mock.NewMockJsonRestHandler(ctrl)
jsonRestHandler.EXPECT().Get(
gomock.Any(),
fmt.Sprintf("/eth/v3/validator/blocks/%d?graffiti=%s&randao_reveal=%s", slot, hexutil.Encode(graffiti), hexutil.Encode(randaoReveal)),
&structs.ProduceBlockV3Response{},
).SetArg(
2,
structs.ProduceBlockV3Response{
Version: "electra",
ExecutionPayloadBlinded: false,
Data: bytes,
},
).Return(
nil,
).Times(1)

validatorClient := &beaconApiValidatorClient{jsonRestHandler: jsonRestHandler}
beaconBlock, err := validatorClient.beaconBlock(ctx, slot, randaoReveal, graffiti)
require.NoError(t, err)

expectedBeaconBlock := &ethpb.GenericBeaconBlock{
Block: &ethpb.GenericBeaconBlock_Electra{
Electra: proto,
},
IsBlinded: false,
}

assert.DeepEqual(t, expectedBeaconBlock, beaconBlock)
}

func TestGetBeaconBlock_BlindedElectraValid(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()

proto := testhelpers.GenerateProtoBlindedElectraBeaconBlock()
block := testhelpers.GenerateJsonBlindedElectraBeaconBlock()
bytes, err := json.Marshal(block)
require.NoError(t, err)

const slot = primitives.Slot(1)
randaoReveal := []byte{2}
graffiti := []byte{3}

ctx := context.Background()

jsonRestHandler := mock.NewMockJsonRestHandler(ctrl)
jsonRestHandler.EXPECT().Get(
gomock.Any(),
fmt.Sprintf("/eth/v3/validator/blocks/%d?graffiti=%s&randao_reveal=%s", slot, hexutil.Encode(graffiti), hexutil.Encode(randaoReveal)),
&structs.ProduceBlockV3Response{},
).SetArg(
2,
structs.ProduceBlockV3Response{
Version: "electra",
ExecutionPayloadBlinded: true,
Data: bytes,
},
).Return(
nil,
).Times(1)

validatorClient := &beaconApiValidatorClient{jsonRestHandler: jsonRestHandler}
beaconBlock, err := validatorClient.beaconBlock(ctx, slot, randaoReveal, graffiti)
require.NoError(t, err)

expectedBeaconBlock := &ethpb.GenericBeaconBlock{
Block: &ethpb.GenericBeaconBlock_BlindedElectra{
BlindedElectra: proto,
},
IsBlinded: true,
}

assert.DeepEqual(t, expectedBeaconBlock, beaconBlock)
}

func TestGetBeaconBlock_FallbackToBlindedBlock(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
Expand Down
33 changes: 31 additions & 2 deletions validator/client/beacon-api/propose_beacon_block.go
Original file line number Diff line number Diff line change
Expand Up @@ -120,16 +120,45 @@ func (c *beaconApiValidatorClient) proposeBeaconBlock(ctx context.Context, in *e
if err != nil {
return nil, errors.Wrap(err, "failed to marshal blinded deneb beacon block contents")
}
case *ethpb.GenericSignedBeaconBlock_Electra:
consensusVersion = "electra"
beaconBlockRoot, err = blockType.Electra.Block.HashTreeRoot()
if err != nil {
return nil, errors.Wrap(err, "failed to compute block root for electra beacon block")
}
signedBlock, err := structs.SignedBeaconBlockContentsElectraFromConsensus(blockType.Electra)
if err != nil {
return nil, errors.Wrap(err, "failed to convert electra beacon block contents")
}
marshalledSignedBeaconBlockJson, err = json.Marshal(signedBlock)
if err != nil {
return nil, errors.Wrap(err, "failed to marshal electra beacon block contents")
}
case *ethpb.GenericSignedBeaconBlock_BlindedElectra:
blinded = true
consensusVersion = "electra"
beaconBlockRoot, err = blockType.BlindedElectra.HashTreeRoot()
if err != nil {
return nil, errors.Wrap(err, "failed to compute block root for blinded electra beacon block")
}
signedBlock, err := structs.SignedBlindedBeaconBlockElectraFromConsensus(blockType.BlindedElectra)
if err != nil {
return nil, errors.Wrap(err, "failed to convert blinded electra beacon block contents")
}
marshalledSignedBeaconBlockJson, err = json.Marshal(signedBlock)
if err != nil {
return nil, errors.Wrap(err, "failed to marshal blinded electra beacon block contents")
}
default:
return nil, errors.Errorf("unsupported block type %T", in.Block)
}

var endpoint string

if blinded {
endpoint = "/eth/v1/beacon/blinded_blocks"
endpoint = "/eth/v2/beacon/blinded_blocks"
} else {
endpoint = "/eth/v1/beacon/blocks"
endpoint = "/eth/v2/beacon/blocks"
}

headers := map[string]string{"Eth-Consensus-Version": consensusVersion}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ func TestProposeBeaconBlock_Altair(t *testing.T) {
headers := map[string]string{"Eth-Consensus-Version": "altair"}
jsonRestHandler.EXPECT().Post(
gomock.Any(),
"/eth/v1/beacon/blocks",
"/eth/v2/beacon/blocks",
headers,
bytes.NewBuffer(marshalledBlock),
nil,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ func TestProposeBeaconBlock_Bellatrix(t *testing.T) {
headers := map[string]string{"Eth-Consensus-Version": "bellatrix"}
jsonRestHandler.EXPECT().Post(
gomock.Any(),
"/eth/v1/beacon/blocks",
"/eth/v2/beacon/blocks",
headers,
bytes.NewBuffer(marshalledBlock),
nil,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ func TestProposeBeaconBlock_BlindedBellatrix(t *testing.T) {
headers := map[string]string{"Eth-Consensus-Version": "bellatrix"}
jsonRestHandler.EXPECT().Post(
gomock.Any(),
"/eth/v1/beacon/blinded_blocks",
"/eth/v2/beacon/blinded_blocks",
headers,
bytes.NewBuffer(marshalledBlock),
nil,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ func TestProposeBeaconBlock_BlindedCapella(t *testing.T) {
headers := map[string]string{"Eth-Consensus-Version": "capella"}
jsonRestHandler.EXPECT().Post(
gomock.Any(),
"/eth/v1/beacon/blinded_blocks",
"/eth/v2/beacon/blinded_blocks",
headers,
bytes.NewBuffer(marshalledBlock),
nil,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ import (
)

func TestProposeBeaconBlock_BlindedDeneb(t *testing.T) {
t.Skip("TODO: Fix this in the beacon-API PR")
ctrl := gomock.NewController(t)
defer ctrl.Finish()
jsonRestHandler := mock.NewMockJsonRestHandler(ctrl)
Expand All @@ -32,7 +31,7 @@ func TestProposeBeaconBlock_BlindedDeneb(t *testing.T) {
headers := map[string]string{"Eth-Consensus-Version": "deneb"}
jsonRestHandler.EXPECT().Post(
gomock.Any(),
"/eth/v1/beacon/blinded_blocks",
"/eth/v2/beacon/blinded_blocks",
headers,
bytes.NewBuffer(denebBytes),
nil,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package beacon_api

import (
"bytes"
"context"
"encoding/json"
"testing"

"github.com/prysmaticlabs/prysm/v5/api/server/structs"
rpctesting "github.com/prysmaticlabs/prysm/v5/beacon-chain/rpc/eth/shared/testing"
"github.com/prysmaticlabs/prysm/v5/testing/assert"
"github.com/prysmaticlabs/prysm/v5/testing/require"
"github.com/prysmaticlabs/prysm/v5/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(context.Background(), 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)
}
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ func TestProposeBeaconBlock_Capella(t *testing.T) {
headers := map[string]string{"Eth-Consensus-Version": "capella"}
jsonRestHandler.EXPECT().Post(
gomock.Any(),
"/eth/v1/beacon/blocks",
"/eth/v2/beacon/blocks",
headers,
bytes.NewBuffer(marshalledBlock),
nil,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,6 @@ import (
)

func TestProposeBeaconBlock_Deneb(t *testing.T) {
t.Skip("TODO: Fix this in the beacon-API PR")

ctrl := gomock.NewController(t)
defer ctrl.Finish()
jsonRestHandler := mock.NewMockJsonRestHandler(ctrl)
Expand All @@ -33,7 +31,7 @@ func TestProposeBeaconBlock_Deneb(t *testing.T) {
headers := map[string]string{"Eth-Consensus-Version": "deneb"}
jsonRestHandler.EXPECT().Post(
gomock.Any(),
"/eth/v1/beacon/blocks",
"/eth/v2/beacon/blocks",
headers,
bytes.NewBuffer(denebBytes),
nil,
Expand Down
Loading
Loading