Skip to content

Commit

Permalink
Add custom json marshalling for versioned structs (#493)
Browse files Browse the repository at this point in the history
  • Loading branch information
avalonche committed Jan 19, 2024
1 parent d4bb90a commit 0d01945
Show file tree
Hide file tree
Showing 23 changed files with 468 additions and 330 deletions.
3 changes: 1 addition & 2 deletions beaconclient/mock_beacon_instance.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import (
"sync"
"time"

"github.com/attestantio/go-eth2-client/spec"
"github.com/flashbots/mev-boost-relay/common"
)

Expand Down Expand Up @@ -107,7 +106,7 @@ func (c *MockBeaconInstance) addDelay() {
}
}

func (c *MockBeaconInstance) PublishBlock(block *spec.VersionedSignedBeaconBlock, broadcaseMode BroadcastMode) (code int, err error) {
func (c *MockBeaconInstance) PublishBlock(block *common.VersionedSignedBlockRequest, broadcaseMode BroadcastMode) (code int, err error) {
return 0, nil
}

Expand Down
4 changes: 2 additions & 2 deletions beaconclient/mock_multi_beacon_client.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package beaconclient

import (
"github.com/attestantio/go-eth2-client/spec"
"github.com/attestantio/go-eth2-client/spec/capella"
"github.com/flashbots/mev-boost-relay/common"
)

type MockMultiBeaconClient struct{}
Expand All @@ -28,7 +28,7 @@ func (*MockMultiBeaconClient) GetProposerDuties(epoch uint64) (*ProposerDutiesRe
return nil, nil
}

func (*MockMultiBeaconClient) PublishBlock(block *spec.VersionedSignedBeaconBlock) (code int, err error) {
func (*MockMultiBeaconClient) PublishBlock(block *common.VersionedSignedBlockRequest) (code int, err error) {
return 0, nil
}

Expand Down
14 changes: 9 additions & 5 deletions beaconclient/multi_beacon_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import (
"strings"
"sync"

"github.com/attestantio/go-eth2-client/spec"
"github.com/flashbots/mev-boost-relay/common"
"github.com/sirupsen/logrus"
uberatomic "go.uber.org/atomic"
)
Expand All @@ -29,7 +29,11 @@ const (
)

func (b BroadcastMode) String() string {
return [...]string{"gossip", "consensus", "consensus_and_equivocation"}[b]
broadcastModeStrings := [...]string{"gossip", "consensus", "consensus_and_equivocation"}
if int(b) >= len(broadcastModeStrings) {
return "invalid broadcast mode value"
}
return broadcastModeStrings[b]
}

// IMultiBeaconClient is the interface for the MultiBeaconClient, which can manage several beacon client instances under the hood
Expand All @@ -42,7 +46,7 @@ type IMultiBeaconClient interface {
// GetStateValidators returns all active and pending validators from the beacon node
GetStateValidators(stateID string) (*GetStateValidatorsResponse, error)
GetProposerDuties(epoch uint64) (*ProposerDutiesResponse, error)
PublishBlock(block *spec.VersionedSignedBeaconBlock) (code int, err error)
PublishBlock(block *common.VersionedSignedBlockRequest) (code int, err error)
GetGenesis() (*GetGenesisResponse, error)
GetSpec() (spec *GetSpecResponse, err error)
GetForkSchedule() (spec *GetForkScheduleResponse, err error)
Expand All @@ -60,7 +64,7 @@ type IBeaconInstance interface {
GetStateValidators(stateID string) (*GetStateValidatorsResponse, error)
GetProposerDuties(epoch uint64) (*ProposerDutiesResponse, error)
GetURI() string
PublishBlock(block *spec.VersionedSignedBeaconBlock, broadcastMode BroadcastMode) (code int, err error)
PublishBlock(block *common.VersionedSignedBlockRequest, broadcastMode BroadcastMode) (code int, err error)
GetGenesis() (*GetGenesisResponse, error)
GetSpec() (spec *GetSpecResponse, err error)
GetForkSchedule() (spec *GetForkScheduleResponse, err error)
Expand Down Expand Up @@ -255,7 +259,7 @@ type publishResp struct {
}

// PublishBlock publishes the signed beacon block via https://ethereum.github.io/beacon-APIs/#/ValidatorRequiredApi/publishBlock
func (c *MultiBeaconClient) PublishBlock(block *spec.VersionedSignedBeaconBlock) (code int, err error) {
func (c *MultiBeaconClient) PublishBlock(block *common.VersionedSignedBlockRequest) (code int, err error) {
slot, err := block.Slot()
if err != nil {
c.log.WithError(err).Warn("failed to publish block as block slot is missing")
Expand Down
7 changes: 3 additions & 4 deletions beaconclient/prod_beacon_instance.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import (
"os"
"time"

"github.com/attestantio/go-eth2-client/spec"
"github.com/attestantio/go-eth2-client/spec/capella"
"github.com/flashbots/mev-boost-relay/common"
"github.com/r3labs/sse/v2"
Expand Down Expand Up @@ -269,16 +268,16 @@ func (c *ProdBeaconInstance) GetURI() string {
return c.beaconURI
}

func (c *ProdBeaconInstance) PublishBlock(block *common.SignedBeaconBlock, broadcastMode BroadcastMode) (code int, err error) {
func (c *ProdBeaconInstance) PublishBlock(block *common.VersionedSignedBlockRequest, broadcastMode BroadcastMode) (code int, err error) {
var uri string
if c.ffUseV2PublishBlockEndpoint {
uri = fmt.Sprintf("%s/eth/v2/beacon/blocks?broadcast_validation=%s", c.beaconURI, broadcastMode.String())
} else {
uri = fmt.Sprintf("%s/eth/v1/beacon/blocks", c.beaconURI)
}
headers := http.Header{}
headers.Add("Eth-Consensus-Version", common.ForkVersionStringCapella) // optional in v1, required in v2
return fetchBeacon(http.MethodPost, uri, block.Capella, nil, nil, headers)
headers.Add("Eth-Consensus-Version", block.Version.String()) // optional in v1, required in v2
return fetchBeacon(http.MethodPost, uri, block, nil, nil, headers)
}

type GetGenesisResponse struct {
Expand Down
29 changes: 22 additions & 7 deletions common/ssz_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package common

import (
"bytes"
"encoding/json"
"os"
"testing"
Expand All @@ -12,21 +13,35 @@ import (
)

func TestSSZBuilderSubmission(t *testing.T) {
byteValue := LoadGzippedBytes(t, "../testdata/submitBlockPayloadCapella_Goerli.json.gz")
// json matches marshalled SSZ
jsonBytes := LoadGzippedBytes(t, "../testdata/submitBlockPayloadCapella_Goerli.json.gz")

depositData := new(capella.SubmitBlockRequest)
err := json.Unmarshal(byteValue, &depositData)
submitBlockData := new(VersionedSubmitBlockRequest)
err := json.Unmarshal(jsonBytes, &submitBlockData)
require.NoError(t, err)

ssz, err := depositData.MarshalSSZ()
require.NotNil(t, submitBlockData.Capella)
marshalledSszBytes, err := submitBlockData.Capella.MarshalSSZ()
require.NoError(t, err)

sszExpectedBytes := LoadGzippedBytes(t, "../testdata/submitBlockPayloadCapella_Goerli.ssz.gz")
require.Equal(t, sszExpectedBytes, ssz)
sszBytes := LoadGzippedBytes(t, "../testdata/submitBlockPayloadCapella_Goerli.ssz.gz")
require.Equal(t, sszBytes, marshalledSszBytes)

htr, err := depositData.HashTreeRoot()
htr, err := submitBlockData.Capella.HashTreeRoot()
require.NoError(t, err)
require.Equal(t, "0x014c218ba41c2ed5388e7f0ed055e109b83692c772de5c2800140a95a4b66d13", hexutil.Encode(htr[:]))

// marshalled json matches ssz
submitBlockSSZ := new(VersionedSubmitBlockRequest)
err = submitBlockSSZ.UnmarshalSSZ(sszBytes)
require.NoError(t, err)
marshalledJSONBytes, err := json.Marshal(submitBlockSSZ)
require.NoError(t, err)
// trim white space from expected json
buffer := new(bytes.Buffer)
err = json.Compact(buffer, jsonBytes)
require.NoError(t, err)
require.Equal(t, buffer.Bytes(), bytes.ToLower(marshalledJSONBytes))
}

func TestSSZGetHeaderResponse(t *testing.T) {
Expand Down
24 changes: 13 additions & 11 deletions common/test_utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,19 +74,21 @@ var ValidPayloadRegisterValidator = apiv1.SignedValidatorRegistration{
"0xaf12df007a0c78abb5575067e5f8b089cfcc6227e4a91db7dd8cf517fe86fb944ead859f0781277d9b78c672e4a18c5d06368b603374673cf2007966cece9540f3a1b3f6f9e1bf421d779c4e8010368e6aac134649c7a009210780d401a778a5"),
}

func TestBuilderSubmitBlockRequest(sk *bls.SecretKey, bid *BidTraceV2) spec.VersionedSubmitBlockRequest {
func TestBuilderSubmitBlockRequest(sk *bls.SecretKey, bid *BidTraceV2) VersionedSubmitBlockRequest {
signature, err := ssz.SignMessage(bid, ssz.DomainBuilder, sk)
check(err, " SignMessage: ", bid, sk)
return spec.VersionedSubmitBlockRequest{ //nolint:exhaustruct
Version: consensusspec.DataVersionCapella,
Capella: &capella.SubmitBlockRequest{
Message: &bid.BidTrace,
Signature: [96]byte(signature),
ExecutionPayload: &consensuscapella.ExecutionPayload{ //nolint:exhaustruct
Transactions: []bellatrix.Transaction{[]byte{0x03}},
Timestamp: bid.Slot * 12, // 12 seconds per slot.
PrevRandao: _HexToHash("0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2"),
Withdrawals: []*consensuscapella.Withdrawal{},
return VersionedSubmitBlockRequest{
VersionedSubmitBlockRequest: spec.VersionedSubmitBlockRequest{ //nolint:exhaustruct
Version: consensusspec.DataVersionCapella,
Capella: &capella.SubmitBlockRequest{
Message: &bid.BidTrace,
Signature: [96]byte(signature),
ExecutionPayload: &consensuscapella.ExecutionPayload{ //nolint:exhaustruct
Transactions: []bellatrix.Transaction{[]byte{0x03}},
Timestamp: bid.Slot * 12, // 12 seconds per slot.
PrevRandao: _HexToHash("0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2"),
Withdrawals: []*consensuscapella.Withdrawal{},
},
},
},
}
Expand Down
151 changes: 136 additions & 15 deletions common/types_spec.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,11 @@ import (

"github.com/attestantio/go-builder-client/api"
"github.com/attestantio/go-builder-client/api/capella"
"github.com/attestantio/go-builder-client/api/deneb"
"github.com/attestantio/go-builder-client/spec"
consensusapi "github.com/attestantio/go-eth2-client/api"
apiv1capella "github.com/attestantio/go-eth2-client/api/v1/capella"
apiv1deneb "github.com/attestantio/go-eth2-client/api/v1/deneb"
consensusspec "github.com/attestantio/go-eth2-client/spec"
consensuscapella "github.com/attestantio/go-eth2-client/spec/capella"
"github.com/attestantio/go-eth2-client/spec/phase0"
Expand Down Expand Up @@ -36,7 +39,7 @@ var NilResponse = struct{}{}

var ZeroU256 = boostTypes.IntToU256(0)

func BuildGetHeaderResponse(payload *spec.VersionedSubmitBlockRequest, sk *bls.SecretKey, pubkey *phase0.BLSPubKey, domain phase0.Domain) (*spec.VersionedSignedBuilderBid, error) {
func BuildGetHeaderResponse(payload *VersionedSubmitBlockRequest, sk *bls.SecretKey, pubkey *phase0.BLSPubKey, domain phase0.Domain) (*spec.VersionedSignedBuilderBid, error) {
if payload == nil {
return nil, ErrMissingRequest
}
Expand All @@ -59,7 +62,7 @@ func BuildGetHeaderResponse(payload *spec.VersionedSubmitBlockRequest, sk *bls.S
return nil, ErrEmptyPayload
}

func BuildGetPayloadResponse(payload *spec.VersionedSubmitBlockRequest) (*api.VersionedExecutionPayload, error) {
func BuildGetPayloadResponse(payload *VersionedSubmitBlockRequest) (*api.VersionedExecutionPayload, error) {
if payload.Capella != nil {
return &api.VersionedExecutionPayload{
Version: consensusspec.DataVersionCapella,
Expand All @@ -70,7 +73,7 @@ func BuildGetPayloadResponse(payload *spec.VersionedSubmitBlockRequest) (*api.Ve
return nil, ErrEmptyPayload
}

func BuilderSubmitBlockRequestToSignedBuilderBid(req *spec.VersionedSubmitBlockRequest, sk *bls.SecretKey, pubkey *phase0.BLSPubKey, domain phase0.Domain) (*spec.VersionedSignedBuilderBid, error) {
func BuilderSubmitBlockRequestToSignedBuilderBid(req *VersionedSubmitBlockRequest, sk *bls.SecretKey, pubkey *phase0.BLSPubKey, domain phase0.Domain) (*spec.VersionedSignedBuilderBid, error) {
value, err := req.Value()
if err != nil {
return nil, err
Expand Down Expand Up @@ -167,8 +170,8 @@ func CapellaPayloadToPayloadHeader(p *consensuscapella.ExecutionPayload) (*conse
}, nil
}

func SignedBlindedBeaconBlockToBeaconBlock(signedBlindedBeaconBlock *consensusapi.VersionedSignedBlindedBeaconBlock, executionPayload *api.VersionedExecutionPayload) *consensusspec.VersionedSignedBeaconBlock {
var signedBeaconBlock consensusspec.VersionedSignedBeaconBlock
func SignedBlindedBeaconBlockToBeaconBlock(signedBlindedBeaconBlock *VersionedSignedBlindedBlockRequest, executionPayload *api.VersionedExecutionPayload) *VersionedSignedBlockRequest {
var signedBeaconBlock VersionedSignedBlockRequest
capellaBlindedBlock := signedBlindedBeaconBlock.Capella
if capellaBlindedBlock != nil {
signedBeaconBlock.Capella = &consensuscapella.SignedBeaconBlock{
Expand Down Expand Up @@ -198,20 +201,12 @@ func SignedBlindedBeaconBlockToBeaconBlock(signedBlindedBeaconBlock *consensusap
}

type BuilderBlockValidationRequest struct {
spec.VersionedSubmitBlockRequest
VersionedSubmitBlockRequest
RegisteredGasLimit uint64 `json:"registered_gas_limit,string"`
}

func (r *BuilderBlockValidationRequest) MarshalJSON() ([]byte, error) {
var blockRequest []byte
var err error

switch r.VersionedSubmitBlockRequest.Version { //nolint:exhaustive
case consensusspec.DataVersionCapella:
blockRequest, err = r.VersionedSubmitBlockRequest.Capella.MarshalJSON()
default:
return nil, errors.Wrap(ErrInvalidVersion, fmt.Sprintf("%d is not supported", r.VersionedSubmitBlockRequest.Version))
}
blockRequest, err := json.Marshal(r.VersionedSubmitBlockRequest)
if err != nil {
return nil, err
}
Expand All @@ -226,3 +221,129 @@ func (r *BuilderBlockValidationRequest) MarshalJSON() ([]byte, error) {
gasLimit[0] = ','
return append(blockRequest[:len(blockRequest)-1], gasLimit...), nil
}

type VersionedSubmitBlockRequest struct {
spec.VersionedSubmitBlockRequest
}

func (r *VersionedSubmitBlockRequest) UnmarshalSSZ(input []byte) error {
var err error

deneb := new(deneb.SubmitBlockRequest)
if err = deneb.UnmarshalSSZ(input); err == nil {
r.Version = consensusspec.DataVersionDeneb
r.Deneb = deneb
return nil
}

capella := new(capella.SubmitBlockRequest)
if err = capella.UnmarshalSSZ(input); err == nil {
r.Version = consensusspec.DataVersionCapella
r.Capella = capella
return nil
}
return errors.Wrap(err, "failed to unmarshal SubmitBlockRequest SSZ")
}

func (r *VersionedSubmitBlockRequest) MarshalJSON() ([]byte, error) {
switch r.Version {
case consensusspec.DataVersionCapella:
return json.Marshal(r.Capella)
case consensusspec.DataVersionDeneb:
return json.Marshal(r.Deneb)
case consensusspec.DataVersionUnknown, consensusspec.DataVersionPhase0, consensusspec.DataVersionAltair, consensusspec.DataVersionBellatrix:
fallthrough
default:
return nil, errors.Wrap(ErrInvalidVersion, fmt.Sprintf("%d is not supported", r.Version))
}
}

func (r *VersionedSubmitBlockRequest) UnmarshalJSON(input []byte) error {
var err error

deneb := new(deneb.SubmitBlockRequest)
if err = json.Unmarshal(input, deneb); err == nil {
r.Version = consensusspec.DataVersionDeneb
r.Deneb = deneb
return nil
}
capella := new(capella.SubmitBlockRequest)
if err = json.Unmarshal(input, capella); err == nil {
r.Version = consensusspec.DataVersionCapella
r.Capella = capella
return nil
}
return errors.Wrap(err, "failed to unmarshal SubmitBlockRequest")
}

type VersionedSignedBlockRequest struct {
consensusapi.VersionedBlockRequest
}

func (r *VersionedSignedBlockRequest) MarshalJSON() ([]byte, error) {
switch r.Version {
case consensusspec.DataVersionCapella:
return json.Marshal(r.Capella)
case consensusspec.DataVersionDeneb:
return json.Marshal(r.Deneb)
case consensusspec.DataVersionUnknown, consensusspec.DataVersionPhase0, consensusspec.DataVersionAltair, consensusspec.DataVersionBellatrix:
fallthrough
default:
return nil, errors.Wrap(ErrInvalidVersion, fmt.Sprintf("%d is not supported", r.Version))
}
}

func (r *VersionedSignedBlockRequest) UnmarshalJSON(input []byte) error {
var err error

deneb := new(apiv1deneb.SignedBlockContents)
if err = json.Unmarshal(input, deneb); err == nil {
r.Version = consensusspec.DataVersionDeneb
r.Deneb = deneb
return nil
}

capella := new(consensuscapella.SignedBeaconBlock)
if err = json.Unmarshal(input, capella); err == nil {
r.Version = consensusspec.DataVersionCapella
r.Capella = capella
return nil
}
return errors.Wrap(err, "failed to unmarshal SignedBeaconBlockRequest")
}

type VersionedSignedBlindedBlockRequest struct {
consensusapi.VersionedBlindedBlockRequest
}

func (r *VersionedSignedBlindedBlockRequest) MarshalJSON() ([]byte, error) {
switch r.Version {
case consensusspec.DataVersionCapella:
return json.Marshal(r.Capella)
case consensusspec.DataVersionDeneb:
return json.Marshal(r.Deneb)
case consensusspec.DataVersionUnknown, consensusspec.DataVersionPhase0, consensusspec.DataVersionAltair, consensusspec.DataVersionBellatrix:
fallthrough
default:
return nil, errors.Wrap(ErrInvalidVersion, fmt.Sprintf("%d is not supported", r.Version))
}
}

func (r *VersionedSignedBlindedBlockRequest) UnmarshalJSON(input []byte) error {
var err error

deneb := new(apiv1deneb.SignedBlindedBlockContents)
if err = json.Unmarshal(input, deneb); err == nil {
r.Version = consensusspec.DataVersionDeneb
r.Deneb = deneb
return nil
}

capella := new(apiv1capella.SignedBlindedBeaconBlock)
if err = json.Unmarshal(input, capella); err == nil {
r.Version = consensusspec.DataVersionCapella
r.Capella = capella
return nil
}
return errors.Wrap(err, "failed to unmarshal SignedBlindedBeaconBlock")
}
Loading

0 comments on commit 0d01945

Please sign in to comment.