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

core/validatorapi: add SubmitBeaconBlock to validatorapi #409

Merged
merged 4 commits into from
Apr 11, 2022
Merged
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
8 changes: 4 additions & 4 deletions core/dutydb/memory_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ func TestMemDBProposer(t *testing.T) {
for i := 0; i < queries; i++ {
blocks[i] = &spec.VersionedBeaconBlock{
Version: spec.DataVersionPhase0,
Phase0: testutil.RandomBeaconBlock(),
Phase0: testutil.RandomPhase0BeaconBlock(),
}
blocks[i].Phase0.Slot = eth2p0.Slot(slots[i])
blocks[i].Phase0.ProposerIndex = eth2p0.ValidatorIndex(i)
Expand Down Expand Up @@ -180,12 +180,12 @@ func TestMemDBClashingBlocks(t *testing.T) {
const slot = 123
block1 := &spec.VersionedBeaconBlock{
Version: spec.DataVersionPhase0,
Phase0: testutil.RandomBeaconBlock(),
Phase0: testutil.RandomPhase0BeaconBlock(),
}
block1.Phase0.Slot = eth2p0.Slot(slot)
block2 := &spec.VersionedBeaconBlock{
Version: spec.DataVersionPhase0,
Phase0: testutil.RandomBeaconBlock(),
Phase0: testutil.RandomPhase0BeaconBlock(),
}
block2.Phase0.Slot = eth2p0.Slot(slot)
pubkey := testutil.RandomCorePubKey(t)
Expand Down Expand Up @@ -218,7 +218,7 @@ func TestMemDBClashProposer(t *testing.T) {

block := &spec.VersionedBeaconBlock{
Version: spec.DataVersionPhase0,
Phase0: testutil.RandomBeaconBlock(),
Phase0: testutil.RandomPhase0BeaconBlock(),
}
block.Phase0.Slot = eth2p0.Slot(slot)
pubkeyA := testutil.RandomCorePubKey(t)
Expand Down
48 changes: 47 additions & 1 deletion core/encode.go
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ func EncodeProposerUnsignedData(proData *spec.VersionedBeaconBlock) (UnsignedDat
return b, nil
}

// DecodeProposerUnsignedData returns the proposer data as an encoded UnsignedData.
// DecodeProposerUnsignedData returns the proposer data from the encoded UnsignedData.
func DecodeProposerUnsignedData(unsignedData UnsignedData) (*spec.VersionedBeaconBlock, error) {
proData := new(spec.VersionedBeaconBlock)
err := json.Unmarshal(unsignedData, proData)
Expand All @@ -184,3 +184,49 @@ func DecodeProposerUnsignedData(unsignedData UnsignedData) (*spec.VersionedBeaco

return proData, nil
}

// EncodeBlockParSignedData returns the partially signed block data as an encoded ParSignedData.
func EncodeBlockParSignedData(block *spec.VersionedSignedBeaconBlock, shareIdx int) (ParSignedData, error) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dB2510 wouldn't it make sense if EncodeBlockParSignedData would be an accesor of spec.VersionedSignedBeaconBlock and DecodeBlockParSignedData a constructor of ParSignedData ?

Copy link
Contributor

@corverroos corverroos Apr 11, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We prefer to keep data and logic separate. Pure functions and pure data.

As per golang guidelines: Functions over methods.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, spec.VersionedSignedBeaconBlock isn't our type. So we cannot add methods to it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah right, spec.VersionedSignedBeaconBlock is go-eth2-client's type that we are using.

data, err := json.Marshal(block)
if err != nil {
return ParSignedData{}, errors.Wrap(err, "marshal block")
}

var sig Signature
switch block.Version {
case spec.DataVersionPhase0:
if block.Phase0 == nil {
return ParSignedData{}, errors.New("no phase0 block")
}
sig = SigFromETH2(block.Phase0.Signature)
case spec.DataVersionAltair:
if block.Altair == nil {
return ParSignedData{}, errors.New("no altair block")
}
sig = SigFromETH2(block.Altair.Signature)
case spec.DataVersionBellatrix:
if block.Bellatrix == nil {
return ParSignedData{}, errors.New("no bellatrix block")
}
sig = SigFromETH2(block.Bellatrix.Signature)
default:
return ParSignedData{}, errors.New("invalid block")
}

return ParSignedData{
Data: data,
Signature: sig,
ShareIdx: shareIdx,
}, nil
}

// DecodeBlockParSignedData returns the partially signed block data from the encoded ParSignedData.
func DecodeBlockParSignedData(data ParSignedData) (*spec.VersionedSignedBeaconBlock, error) {
block := new(spec.VersionedSignedBeaconBlock)
err := json.Unmarshal(data.Data, block)
if err != nil {
return nil, errors.Wrap(err, "unmarshal block")
}

return block, nil
}
25 changes: 24 additions & 1 deletion core/encode_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
"testing"

"github.com/attestantio/go-eth2-client/spec"
eth2p0 "github.com/attestantio/go-eth2-client/spec/phase0"
"github.com/stretchr/testify/require"

"github.com/obolnetwork/charon/core"
Expand Down Expand Up @@ -133,7 +134,7 @@ func TestEncodeProposerFetchArg(t *testing.T) {
func TestEncodeProposerUnsignedData(t *testing.T) {
proData1 := &spec.VersionedBeaconBlock{
Version: spec.DataVersionPhase0,
Phase0: testutil.RandomBeaconBlock(),
Phase0: testutil.RandomPhase0BeaconBlock(),
}

data1, err := core.EncodeProposerUnsignedData(proData1)
Expand All @@ -148,3 +149,25 @@ func TestEncodeProposerUnsignedData(t *testing.T) {
require.Equal(t, proData1, proData2)
require.Equal(t, data1, data2)
}

func TestEncodeBlockParSignedData(t *testing.T) {
block1 := &spec.VersionedSignedBeaconBlock{
Version: spec.DataVersionPhase0,
Phase0: &eth2p0.SignedBeaconBlock{
Message: testutil.RandomPhase0BeaconBlock(),
Signature: testutil.RandomEth2Signature(),
},
}

data1, err := core.EncodeBlockParSignedData(block1, 0)
require.NoError(t, err)

block2, err := core.DecodeBlockParSignedData(data1)
require.NoError(t, err)

data2, err := core.EncodeBlockParSignedData(block2, 0)
require.NoError(t, err)

require.Equal(t, block1, block2)
require.Equal(t, data1, data2)
}
12 changes: 12 additions & 0 deletions core/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,18 @@ func NewRandaoDuty(slot int64) Duty {
}
}

// NewProposerDuty returns a new proposer duty. It is a convenience function that is
// slightly more readable and concise than the struct literal equivalent:
// core.Duty{Slot: slot, Type: core.DutyProposer}
// vs
// core.NewProposerDuty(slot)
func NewProposerDuty(slot int64) Duty {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dB2510 slot is an uint64 or eth2p0.Slot

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added this to align with the other similar types: NewAttesterDuty and NewRandaoDuty in the same file.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we can address this later as part of this issue: #383

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, it can also be done as big refactor when we do that issue

return Duty{
Slot: slot,
Type: DutyProposer,
}
}

const (
pkLen = 98 // "0x" + hex.Encode([48]byte) = 2+2*48
sigLen = 96
Expand Down
50 changes: 50 additions & 0 deletions core/validatorapi/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ import (
eth2client "github.com/attestantio/go-eth2-client"
eth2v1 "github.com/attestantio/go-eth2-client/api/v1"
"github.com/attestantio/go-eth2-client/spec"
"github.com/attestantio/go-eth2-client/spec/altair"
"github.com/attestantio/go-eth2-client/spec/bellatrix"
eth2p0 "github.com/attestantio/go-eth2-client/spec/phase0"
"github.com/gorilla/mux"
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
Expand All @@ -54,6 +56,7 @@ type Handler interface {
eth2client.AttestationsSubmitter
eth2client.AttesterDutiesProvider
eth2client.BeaconBlockProposalProvider
eth2client.BeaconBlockSubmitter
eth2client.ProposerDutiesProvider
eth2client.ValidatorsProvider
// Above sorted alphabetically.
Expand Down Expand Up @@ -104,6 +107,11 @@ func NewRouter(h Handler, beaconNodeAddr string) (*mux.Router, error) {
Path: "/eth/v2/validator/blocks/{slot}",
Handler: proposeBlock(h),
},
{
Name: "submit_block",
Path: "/eth/v1/beacon/blocks",
Handler: submitBlock(h),
},
// TODO(corver): Add more endpoints
}

Expand Down Expand Up @@ -386,6 +394,48 @@ func proposeBlock(p eth2client.BeaconBlockProposalProvider) handlerFunc {
}
}

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)
err := bellatrixBlock.UnmarshalJSON(body)
if err == nil {
block := &spec.VersionedSignedBeaconBlock{
Version: spec.DataVersionBellatrix,
Bellatrix: bellatrixBlock,
}
err = p.SubmitBeaconBlock(ctx, block)

return nil, err
}

altairBlock := new(altair.SignedBeaconBlock)
err = altairBlock.UnmarshalJSON(body)
if err == nil {
block := &spec.VersionedSignedBeaconBlock{
Version: spec.DataVersionAltair,
Altair: altairBlock,
}
err = p.SubmitBeaconBlock(ctx, block)

return nil, err
}

phase0Block := new(eth2p0.SignedBeaconBlock)
err = phase0Block.UnmarshalJSON(body)
if err == nil {
block := &spec.VersionedSignedBeaconBlock{
Version: spec.DataVersionPhase0,
Phase0: phase0Block,
}
err = p.SubmitBeaconBlock(ctx, block)

return nil, err
}

return nil, errors.New("invalid block")
}
}

// proxyHandler returns a reverse proxy handler.
func proxyHandler(target string) (http.HandlerFunc, error) {
targetURL, err := url.Parse(target)
Expand Down
76 changes: 76 additions & 0 deletions core/validatorapi/router_internal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ import (
eth2http "github.com/attestantio/go-eth2-client/http"
eth2mock "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"
eth2p0 "github.com/attestantio/go-eth2-client/spec/phase0"
"github.com/stretchr/testify/require"

Expand Down Expand Up @@ -361,6 +363,75 @@ func TestRouter(t *testing.T) {

testRouter(t, handler, callback)
})

t.Run("submit_block_phase0", func(t *testing.T) {
block1 := &spec.VersionedSignedBeaconBlock{
Version: spec.DataVersionPhase0,
Phase0: &eth2p0.SignedBeaconBlock{
Message: testutil.RandomPhase0BeaconBlock(),
Signature: testutil.RandomEth2Signature(),
},
}
handler := testHandler{
SubmitBeaconBlockFunc: func(ctx context.Context, block *spec.VersionedSignedBeaconBlock) error {
require.Equal(t, block, block1)
return nil
},
}

callback := func(ctx context.Context, cl *eth2http.Service) {
err := cl.SubmitBeaconBlock(ctx, block1)
require.NoError(t, err)
}

testRouter(t, handler, callback)
})

t.Run("submit_block_altair", func(t *testing.T) {
block1 := &spec.VersionedSignedBeaconBlock{
Version: spec.DataVersionAltair,
Altair: &altair.SignedBeaconBlock{
Message: testutil.RandomAltairBeaconBlock(t),
Signature: testutil.RandomEth2Signature(),
},
}
handler := testHandler{
SubmitBeaconBlockFunc: func(ctx context.Context, block *spec.VersionedSignedBeaconBlock) error {
require.Equal(t, block, block1)
return nil
},
}

callback := func(ctx context.Context, cl *eth2http.Service) {
err := cl.SubmitBeaconBlock(ctx, block1)
require.NoError(t, err)
}

testRouter(t, handler, callback)
})

t.Run("submit_block_bellatrix", func(t *testing.T) {
block1 := &spec.VersionedSignedBeaconBlock{
Version: spec.DataVersionBellatrix,
Bellatrix: &bellatrix.SignedBeaconBlock{
Message: testutil.RandomBellatrixBeaconBlock(t),
Signature: testutil.RandomEth2Signature(),
},
}
handler := testHandler{
SubmitBeaconBlockFunc: func(ctx context.Context, block *spec.VersionedSignedBeaconBlock) error {
require.Equal(t, block, block1)
return nil
},
}

callback := func(ctx context.Context, cl *eth2http.Service) {
err := cl.SubmitBeaconBlock(ctx, block1)
require.NoError(t, err)
}

testRouter(t, handler, callback)
})
}

// testRouter is a helper function to test router endpoints with an eth2http client. The outer test
Expand Down Expand Up @@ -409,6 +480,7 @@ type testHandler struct {
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)
Expand All @@ -426,6 +498,10 @@ func (h testHandler) BeaconBlockProposal(ctx context.Context, slot eth2p0.Slot,
return h.BeaconBlockProposalFunc(ctx, slot, randaoReveal, graffiti)
}

func (h testHandler) SubmitBeaconBlock(ctx context.Context, block *spec.VersionedSignedBeaconBlock) error {
return h.SubmitBeaconBlockFunc(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)
}
Expand Down
Loading