diff --git a/core/validatorapi/eth2types.go b/core/validatorapi/eth2types.go index c60b92ac2..993ea9b93 100644 --- a/core/validatorapi/eth2types.go +++ b/core/validatorapi/eth2types.go @@ -98,6 +98,11 @@ type proposeBlockResponseBellatrix struct { Data *bellatrix.BeaconBlock `json:"data"` } +type proposeBlindedBlockResponseBellatrix struct { + Version string `json:"version"` + Data *eth2v1.BlindedBeaconBlock `json:"data"` +} + type validatorsResponse struct { Data []v1Validator `json:"data"` } diff --git a/core/validatorapi/router.go b/core/validatorapi/router.go index edf7a6a37..1df2906fa 100644 --- a/core/validatorapi/router.go +++ b/core/validatorapi/router.go @@ -34,6 +34,7 @@ import ( "time" eth2client "github.com/attestantio/go-eth2-client" + eth2api "github.com/attestantio/go-eth2-client/api" eth2v1 "github.com/attestantio/go-eth2-client/api/v1" "github.com/attestantio/go-eth2-client/spec" "github.com/attestantio/go-eth2-client/spec/altair" @@ -57,6 +58,8 @@ type Handler interface { eth2client.AttesterDutiesProvider eth2client.BeaconBlockProposalProvider eth2client.BeaconBlockSubmitter + eth2client.BlindedBeaconBlockProposalProvider + eth2client.BlindedBeaconBlockSubmitter eth2client.ProposerDutiesProvider eth2client.ValidatorsProvider eth2client.VoluntaryExitSubmitter @@ -113,6 +116,16 @@ func NewRouter(h Handler, eth2Cl eth2client.Service) (*mux.Router, error) { Path: "/eth/v1/beacon/blocks", Handler: submitBlock(h), }, + { + Name: "propose_blinded_block", + Path: "/eth/v1/validator/blinded_blocks/{slot}", + Handler: proposeBlindedBlock(h), + }, + { + Name: "submit_blinded_block", + Path: "/eth/v1/beacon/blinded_blocks", + Handler: submitBlindedBlock(h), + }, { Name: "submit_voluntary_exit", Path: "/eth/v1/beacon/pool/voluntary_exits", @@ -391,6 +404,45 @@ func proposeBlock(p eth2client.BeaconBlockProposalProvider) handlerFunc { } } +// proposeBlindedBlock receives the randao from the validator and returns the unsigned BlindedBeaconBlock. +func proposeBlindedBlock(p eth2client.BlindedBeaconBlockProposalProvider) handlerFunc { + return func(ctx context.Context, params map[string]string, query url.Values, body []byte) (interface{}, error) { + slot, err := uintParam(params, "slot") + if err != nil { + return nil, err + } + + var randao eth2p0.BLSSignature + b, err := hexQuery(query, "randao_reveal") + if err != nil { + return nil, err + } + if len(b) != len(randao) { + return nil, errors.New("input randao_reveal has wrong length") + } + copy(randao[:], b) + + block, err := p.BlindedBeaconBlockProposal(ctx, eth2p0.Slot(slot), randao, nil) + if err != nil { + return nil, err + } + + switch block.Version { + case spec.DataVersionBellatrix: + if block.Bellatrix == nil { + return 0, errors.New("no bellatrix block") + } + + return proposeBlindedBlockResponseBellatrix{ + Version: "BELLATRIX", + Data: block.Bellatrix, + }, nil + default: + return 0, errors.New("invalid block") + } + } +} + func submitBlock(p eth2client.BeaconBlockSubmitter) handlerFunc { return func(ctx context.Context, params map[string]string, query url.Values, body []byte) (interface{}, error) { bellatrixBlock := new(bellatrix.SignedBeaconBlock) @@ -430,6 +482,23 @@ func submitBlock(p eth2client.BeaconBlockSubmitter) handlerFunc { } } +func submitBlindedBlock(p eth2client.BlindedBeaconBlockSubmitter) handlerFunc { + return func(ctx context.Context, params map[string]string, query url.Values, body []byte) (interface{}, error) { + bellatrixBlock := new(eth2v1.SignedBlindedBeaconBlock) + err := bellatrixBlock.UnmarshalJSON(body) + if err == nil { + block := ð2api.VersionedSignedBlindedBeaconBlock{ + Version: spec.DataVersionBellatrix, + Bellatrix: bellatrixBlock, + } + + return nil, p.SubmitBlindedBeaconBlock(ctx, block) + } + + return nil, errors.New("invalid block") + } +} + // submitExit returns a handler function for the exit submitter endpoint. func submitExit(p eth2client.VoluntaryExitSubmitter) handlerFunc { return func(ctx context.Context, _ map[string]string, _ url.Values, body []byte) (interface{}, error) { diff --git a/core/validatorapi/router_internal_test.go b/core/validatorapi/router_internal_test.go index ae7b8c7d8..e5cd1f2aa 100644 --- a/core/validatorapi/router_internal_test.go +++ b/core/validatorapi/router_internal_test.go @@ -27,6 +27,7 @@ import ( "strings" "testing" + eth2api "github.com/attestantio/go-eth2-client/api" eth2v1 "github.com/attestantio/go-eth2-client/api/v1" eth2http "github.com/attestantio/go-eth2-client/http" eth2mock "github.com/attestantio/go-eth2-client/mock" @@ -389,6 +390,26 @@ func TestRouter(t *testing.T) { testRouter(t, handler, callback) }) + t.Run("submit_randao_blinded_block", func(t *testing.T) { + handler := testHandler{ + BlindedBeaconBlockProposalFunc: func(ctx context.Context, slot eth2p0.Slot, randaoReveal eth2p0.BLSSignature, graffiti []byte) (*eth2api.VersionedBlindedBeaconBlock, error) { + return nil, errors.New("not implemented") + }, + } + + callback := func(ctx context.Context, cl *eth2http.Service) { + slot := eth2p0.Slot(1) + randaoReveal := testutil.RandomEth2Signature() + graffiti := testutil.RandomBytes32() + + res, err := cl.BlindedBeaconBlockProposal(ctx, slot, randaoReveal, graffiti) + require.Error(t, err) + require.Nil(t, res) + } + + testRouter(t, handler, callback) + }) + t.Run("submit_block_phase0", func(t *testing.T) { block1 := &spec.VersionedSignedBeaconBlock{ Version: spec.DataVersionPhase0, @@ -458,6 +479,29 @@ func TestRouter(t *testing.T) { testRouter(t, handler, callback) }) + t.Run("submit_blinded_block_bellatrix", func(t *testing.T) { + block1 := ð2api.VersionedSignedBlindedBeaconBlock{ + Version: spec.DataVersionBellatrix, + Bellatrix: ð2v1.SignedBlindedBeaconBlock{ + Message: testutil.RandomBellatrixBlindedBeaconBlock(t), + Signature: testutil.RandomEth2Signature(), + }, + } + handler := testHandler{ + SubmitBlindedBeaconBlockFunc: func(ctx context.Context, block *eth2api.VersionedSignedBlindedBeaconBlock) error { + require.Equal(t, block, block1) + return nil + }, + } + + callback := func(ctx context.Context, cl *eth2http.Service) { + err := cl.SubmitBlindedBeaconBlock(ctx, block1) + require.NoError(t, err) + } + + testRouter(t, handler, callback) + }) + t.Run("submit_voluntary_exit", func(t *testing.T) { exit1 := testutil.RandomExit() @@ -519,15 +563,17 @@ func testRawRouter(t *testing.T, handler testHandler, callback func(context.Cont // mocked beacon-node endpoints required by the eth2http client during startup. type testHandler struct { Handler - ProxyHandler http.HandlerFunc - AttestationDataFunc func(ctx context.Context, slot eth2p0.Slot, commIdx eth2p0.CommitteeIndex) (*eth2p0.AttestationData, error) - AttesterDutiesFunc func(ctx context.Context, epoch eth2p0.Epoch, il []eth2p0.ValidatorIndex) ([]*eth2v1.AttesterDuty, error) - BeaconBlockProposalFunc func(ctx context.Context, slot eth2p0.Slot, randaoReveal eth2p0.BLSSignature, graffiti []byte) (*spec.VersionedBeaconBlock, error) - SubmitBeaconBlockFunc func(ctx context.Context, block *spec.VersionedSignedBeaconBlock) error - ProposerDutiesFunc func(ctx context.Context, epoch eth2p0.Epoch, il []eth2p0.ValidatorIndex) ([]*eth2v1.ProposerDuty, error) - ValidatorsFunc func(ctx context.Context, stateID string, indices []eth2p0.ValidatorIndex) (map[eth2p0.ValidatorIndex]*eth2v1.Validator, error) - ValidatorsByPubKeyFunc func(ctx context.Context, stateID string, pubkeys []eth2p0.BLSPubKey) (map[eth2p0.ValidatorIndex]*eth2v1.Validator, error) - SubmitVoluntaryExitFunc func(ctx context.Context, exit *eth2p0.SignedVoluntaryExit) error + ProxyHandler http.HandlerFunc + AttestationDataFunc func(ctx context.Context, slot eth2p0.Slot, commIdx eth2p0.CommitteeIndex) (*eth2p0.AttestationData, error) + AttesterDutiesFunc func(ctx context.Context, epoch eth2p0.Epoch, il []eth2p0.ValidatorIndex) ([]*eth2v1.AttesterDuty, error) + BeaconBlockProposalFunc func(ctx context.Context, slot eth2p0.Slot, randaoReveal eth2p0.BLSSignature, graffiti []byte) (*spec.VersionedBeaconBlock, error) + SubmitBeaconBlockFunc func(ctx context.Context, block *spec.VersionedSignedBeaconBlock) error + BlindedBeaconBlockProposalFunc func(ctx context.Context, slot eth2p0.Slot, randaoReveal eth2p0.BLSSignature, graffiti []byte) (*eth2api.VersionedBlindedBeaconBlock, error) + SubmitBlindedBeaconBlockFunc func(ctx context.Context, block *eth2api.VersionedSignedBlindedBeaconBlock) error + ProposerDutiesFunc func(ctx context.Context, epoch eth2p0.Epoch, il []eth2p0.ValidatorIndex) ([]*eth2v1.ProposerDuty, error) + ValidatorsFunc func(ctx context.Context, stateID string, indices []eth2p0.ValidatorIndex) (map[eth2p0.ValidatorIndex]*eth2v1.Validator, error) + ValidatorsByPubKeyFunc func(ctx context.Context, stateID string, pubkeys []eth2p0.BLSPubKey) (map[eth2p0.ValidatorIndex]*eth2v1.Validator, error) + SubmitVoluntaryExitFunc func(ctx context.Context, exit *eth2p0.SignedVoluntaryExit) error } func (h testHandler) AttestationData(ctx context.Context, slot eth2p0.Slot, commIdx eth2p0.CommitteeIndex) (*eth2p0.AttestationData, error) { @@ -546,6 +592,14 @@ func (h testHandler) SubmitBeaconBlock(ctx context.Context, block *spec.Versione return h.SubmitBeaconBlockFunc(ctx, block) } +func (h testHandler) BlindedBeaconBlockProposal(ctx context.Context, slot eth2p0.Slot, randaoReveal eth2p0.BLSSignature, graffiti []byte) (*eth2api.VersionedBlindedBeaconBlock, error) { + return h.BlindedBeaconBlockProposalFunc(ctx, slot, randaoReveal, graffiti) +} + +func (h testHandler) SubmitBlindedBeaconBlock(ctx context.Context, block *eth2api.VersionedSignedBlindedBeaconBlock) error { + return h.SubmitBlindedBeaconBlockFunc(ctx, block) +} + func (h testHandler) Validators(ctx context.Context, stateID string, indices []eth2p0.ValidatorIndex) (map[eth2p0.ValidatorIndex]*eth2v1.Validator, error) { return h.ValidatorsFunc(ctx, stateID, indices) } diff --git a/core/validatorapi/validatorapi.go b/core/validatorapi/validatorapi.go index e85ddec6b..d4b22499c 100644 --- a/core/validatorapi/validatorapi.go +++ b/core/validatorapi/validatorapi.go @@ -43,6 +43,7 @@ type eth2Provider interface { eth2client.BeaconBlockProposalProvider eth2client.BeaconBlockSubmitter eth2client.BlindedBeaconBlockSubmitter + eth2client.BlindedBeaconBlockProposalProvider eth2client.DomainProvider eth2client.ProposerDutiesProvider eth2client.SlotsPerEpochProvider diff --git a/core/validatorapi/validatorapi_test.go b/core/validatorapi/validatorapi_test.go index ebbbfb3ff..0a6a41f5c 100644 --- a/core/validatorapi/validatorapi_test.go +++ b/core/validatorapi/validatorapi_test.go @@ -24,7 +24,6 @@ import ( eth2api "github.com/attestantio/go-eth2-client/api" eth2v1 "github.com/attestantio/go-eth2-client/api/v1" - "github.com/attestantio/go-eth2-client/mock" "github.com/attestantio/go-eth2-client/spec" "github.com/attestantio/go-eth2-client/spec/altair" "github.com/attestantio/go-eth2-client/spec/bellatrix" @@ -45,7 +44,7 @@ import ( func TestComponent_ValidSubmitAttestations(t *testing.T) { ctx := context.Background() - eth2Svc, err := mock.New(ctx) + eth2Svc, err := beaconmock.New() require.NoError(t, err) const ( @@ -123,7 +122,7 @@ func TestComponent_ValidSubmitAttestations(t *testing.T) { func TestComponent_InvalidSubmitAttestations(t *testing.T) { ctx := context.Background() - eth2Svc, err := mock.New(ctx) + eth2Svc, err := beaconmock.New() require.NoError(t, err) const ( @@ -323,7 +322,7 @@ func padTo(b []byte, size int) []byte { func TestComponent_BeaconBlockProposal(t *testing.T) { ctx := context.Background() - eth2Svc, err := mock.New(ctx) + eth2Svc, err := beaconmock.New() require.NoError(t, err) const ( @@ -618,7 +617,7 @@ func TestComponent_SubmitBeaconBlockInvalidBlock(t *testing.T) { func TestComponent_BlindedBeaconBlockProposal(t *testing.T) { ctx := context.Background() - eth2Svc, err := mock.New(ctx) + eth2Svc, err := beaconmock.New() require.NoError(t, err) const ( diff --git a/testutil/validatormock/validatormock.go b/testutil/validatormock/validatormock.go index e51b99c31..24c6e9afb 100644 --- a/testutil/validatormock/validatormock.go +++ b/testutil/validatormock/validatormock.go @@ -50,9 +50,10 @@ type Eth2Provider interface { eth2client.AttestationDataProvider eth2client.AttestationsSubmitter eth2client.AttesterDutiesProvider - eth2client.BlindedBeaconBlockSubmitter eth2client.BeaconBlockProposalProvider eth2client.BeaconBlockSubmitter + eth2client.BlindedBeaconBlockProposalProvider + eth2client.BlindedBeaconBlockSubmitter eth2client.DomainProvider eth2client.ProposerDutiesProvider eth2client.SlotsPerEpochProvider