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 sync committee duties to validatorapi #1278

Merged
merged 3 commits into from
Oct 13, 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
15 changes: 11 additions & 4 deletions core/validatorapi/eth2types.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,12 @@ type errorResponse struct {
// TODO(corver): Maybe add stacktraces field for debugging.
}

// attesterDutiesRequest defines the request to the getAttesterDuties and getProposerDuties endpoint.
// See https://ethereum.github.io/beacon-APIs/#/ValidatorRequiredApi/getAttesterDuties.
type attesterDutiesRequest []eth2p0.ValidatorIndex
// valIndexesJSON defines the request to the getAttesterDuties and getSyncCommitteeDuties endpoint.
// See https://ethereum.github.io/beacon-APIs/#/ValidatorRequiredApi/getAttesterDuties and
// https://ethereum.github.io/beacon-APIs/#/ValidatorRequiredApi/getSyncCommitteeDuties.
type valIndexesJSON []eth2p0.ValidatorIndex

func (r *attesterDutiesRequest) UnmarshalJSON(bytes []byte) error {
func (r *valIndexesJSON) UnmarshalJSON(bytes []byte) error {
// First try normal json number array
var ints []uint64
if err := json.Unmarshal(bytes, &ints); err == nil {
Expand Down Expand Up @@ -135,3 +136,9 @@ func (v v1Validator) MarshalJSON() ([]byte, error) {

return bytes.ToLower(b), nil // ValidatorState must be lower case.
}

// syncCommitteeDutiesResponse defines the response to the getSyncCommitteeDuties endpoint.
// See https://ethereum.github.io/beacon-APIs/#/ValidatorRequiredApi/getSyncCommitteeDuties.
type syncCommitteeDutiesResponse struct {
Data []*eth2v1.SyncCommitteeDuty `json:"data"`
}
38 changes: 29 additions & 9 deletions core/validatorapi/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ type Handler interface {
eth2client.BlindedBeaconBlockProposalProvider
eth2client.BlindedBeaconBlockSubmitter
eth2client.ProposerDutiesProvider
eth2client.SyncCommitteeDutiesProvider
eth2client.SyncCommitteeMessagesSubmitter
eth2client.ValidatorsProvider
eth2client.ValidatorRegistrationsSubmitter
Expand Down Expand Up @@ -94,6 +95,11 @@ func NewRouter(h Handler, eth2Cl eth2wrap.Client) (*mux.Router, error) {
Path: "/eth/v1/validator/duties/proposer/{epoch}",
Handler: proposerDuties(h),
},
{
Name: "sync_committee_duties",
Path: "/eth/v1/validator/duties/sync/{epoch}",
Handler: syncCommitteeDuties(h),
},
{
Name: "attestation_data",
Path: "/eth/v1/validator/attestation_data",
Expand Down Expand Up @@ -344,10 +350,6 @@ func proposerDuties(p eth2client.ProposerDutiesProvider) handlerFunc {
return nil, err
}

if len(data) == 0 {
data = []*eth2v1.ProposerDuty{}
}

return proposerDutiesResponse{
DependentRoot: stubRoot(epoch), // TODO(corver): Fill this properly
Data: data,
Expand All @@ -363,7 +365,7 @@ func attesterDuties(p eth2client.AttesterDutiesProvider) handlerFunc {
return nil, err
}

var req attesterDutiesRequest
var req valIndexesJSON
if err := unmarshal(body, &req); err != nil {
return nil, err
}
Expand All @@ -373,17 +375,35 @@ func attesterDuties(p eth2client.AttesterDutiesProvider) handlerFunc {
return nil, err
}

if len(data) == 0 {
data = []*eth2v1.AttesterDuty{}
}

return attesterDutiesResponse{
DependentRoot: stubRoot(epoch), // TODO(corver): Fill this properly
Data: data,
}, nil
}
}

// syncCommitteeDuties returns a handler function for the sync committee duty endpoint.
func syncCommitteeDuties(p eth2client.SyncCommitteeDutiesProvider) handlerFunc {
return func(ctx context.Context, params map[string]string, query url.Values, body []byte) (interface{}, error) {
epoch, err := uintParam(params, "epoch")
if err != nil {
return nil, err
}

var req valIndexesJSON
if err := unmarshal(body, &req); err != nil {
return nil, err
}

data, err := p.SyncCommitteeDuties(ctx, eth2p0.Epoch(epoch), req)
if err != nil {
return nil, err
}

return syncCommitteeDutiesResponse{Data: data}, nil
}
}

// proposeBlock receives the randao from the validator and returns the unsigned BeaconBlock.
func proposeBlock(p eth2client.BeaconBlockProposalProvider) handlerFunc {
return func(ctx context.Context, params map[string]string, query url.Values, body []byte) (interface{}, error) {
Expand Down
37 changes: 37 additions & 0 deletions core/validatorapi/router_internal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,38 @@ func TestRouter(t *testing.T) {
testRouter(t, handler, callback)
})

t.Run("synccommduty", func(t *testing.T) {
handler := testHandler{
SyncCommitteeDutiesFunc: func(ctx context.Context, epoch eth2p0.Epoch, vIdxs []eth2p0.ValidatorIndex) ([]*eth2v1.SyncCommitteeDuty, error) {
// Returns ordered total number of duties for the epoch
var res []*eth2v1.SyncCommitteeDuty
for _, vIdx := range vIdxs {
res = append(res, &eth2v1.SyncCommitteeDuty{
ValidatorIndex: vIdx,
ValidatorSyncCommitteeIndices: []eth2p0.CommitteeIndex{eth2p0.CommitteeIndex(vIdx)},
})
}

return res, nil
},
}

callback := func(ctx context.Context, cl *eth2http.Service) {
const epoch = 4
const validator = 1
res, err := cl.SyncCommitteeDuties(ctx, eth2p0.Epoch(epoch), []eth2p0.ValidatorIndex{
eth2p0.ValidatorIndex(validator), // Only request 1 of total 2 validators
})
require.NoError(t, err)

require.Len(t, res, 1)
require.Equal(t, res[0].ValidatorSyncCommitteeIndices, []eth2p0.CommitteeIndex{eth2p0.CommitteeIndex(validator)})
require.Equal(t, int(res[0].ValidatorIndex), validator)
}

testRouter(t, handler, callback)
})

t.Run("get_validator_index", func(t *testing.T) {
handler := testHandler{
ValidatorsFunc: func(_ context.Context, stateID string, indices []eth2p0.ValidatorIndex) (map[eth2p0.ValidatorIndex]*eth2v1.Validator, error) {
Expand Down Expand Up @@ -740,6 +772,7 @@ type testHandler struct {
SubmitBeaconCommitteeSubscriptionsV2Func func(ctx context.Context, subscriptions []*eth2exp.BeaconCommitteeSubscription) ([]*eth2exp.BeaconCommitteeSubscriptionResponse, error)
SubmitAggregateAttestationsFunc func(ctx context.Context, aggregateAndProofs []*eth2p0.SignedAggregateAndProof) error
SubmitSyncCommitteeMessagesFunc func(ctx context.Context, messages []*altair.SyncCommitteeMessage) error
SyncCommitteeDutiesFunc func(ctx context.Context, epoch eth2p0.Epoch, validatorIndices []eth2p0.ValidatorIndex) ([]*eth2v1.SyncCommitteeDuty, error)
}

func (h testHandler) AttestationData(ctx context.Context, slot eth2p0.Slot, commIdx eth2p0.CommitteeIndex) (*eth2p0.AttestationData, error) {
Expand Down Expand Up @@ -798,6 +831,10 @@ func (h testHandler) SubmitSyncCommitteeMessages(ctx context.Context, messages [
return h.SubmitSyncCommitteeMessagesFunc(ctx, messages)
}

func (h testHandler) SyncCommitteeDuties(ctx context.Context, epoch eth2p0.Epoch, validatorIndices []eth2p0.ValidatorIndex) ([]*eth2v1.SyncCommitteeDuty, error) {
return h.SyncCommitteeDutiesFunc(ctx, epoch, validatorIndices)
}

// newBeaconHandler returns a mock beacon node handler. It registers a few mock handlers required by the
// eth2http service on startup, all other requests are routed to ProxyHandler if not nil.
func (h testHandler) newBeaconHandler(t *testing.T) http.Handler {
Expand Down
18 changes: 18 additions & 0 deletions core/validatorapi/validatorapi.go
Original file line number Diff line number Diff line change
Expand Up @@ -822,6 +822,24 @@ func (c Component) AttesterDuties(ctx context.Context, epoch eth2p0.Epoch, valid
return duties, nil
}

func (c Component) SyncCommitteeDuties(ctx context.Context, epoch eth2p0.Epoch, validatorIndices []eth2p0.ValidatorIndex) ([]*eth2v1.SyncCommitteeDuty, error) {
duties, err := c.eth2Cl.SyncCommitteeDuties(ctx, epoch, validatorIndices)
if err != nil {
return nil, err
}

// Replace root public keys with public shares.
for i := 0; i < len(duties); i++ {
Copy link
Contributor

Choose a reason for hiding this comment

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

no test for this?

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

pubshare, ok := c.getPubShareFunc(duties[i].PubKey)
if !ok {
return nil, errors.New("pubshare not found")
}
duties[i].PubKey = pubshare
}

return duties, nil
}

func (c Component) Validators(ctx context.Context, stateID string, validatorIndices []eth2p0.ValidatorIndex) (map[eth2p0.ValidatorIndex]*eth2v1.Validator, error) {
vals, err := c.eth2Cl.Validators(ctx, stateID, validatorIndices)
if err != nil {
Expand Down
92 changes: 69 additions & 23 deletions core/validatorapi/validatorapi_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ package validatorapi_test

import (
"context"
"crypto/rand"
"fmt"
mrand "math/rand"
"sync"
Expand Down Expand Up @@ -992,43 +991,90 @@ func TestComponent_SubmitVoluntaryExitInvalidSignature(t *testing.T) {
require.ErrorContains(t, err, "invalid signature")
}

func TestComponent_ProposerDuties(t *testing.T) {
func TestComponent_Duties(t *testing.T) {
ctx := context.Background()

// Configure validator
const vIdx = 1
const (
vIdx = 123
epch = 456
)

tss, _, err := tbls.GenerateTSS(3, 4, rand.Reader)
require.NoError(t, err)
// Create pubkey and pubshare
eth2Pubkey := testutil.RandomEth2PubKey(t)
eth2Share := testutil.RandomEth2PubKey(t)

// Create keys (just use normal keys, not split tbls)
pubkey := tss.PublicKey()
pubshare := tss.PublicShare(vIdx)

eth2Share, err := tblsconv.KeyToETH2(pubshare)
pubshare, err := tblsconv.KeyFromETH2(eth2Share)
require.NoError(t, err)

validator := beaconmock.ValidatorSetA[vIdx]
validator.Validator.PublicKey, err = tblsconv.KeyToETH2(pubkey)
pubkey, err := tblsconv.KeyFromETH2(eth2Pubkey)
require.NoError(t, err)

pubShareByKey := map[*bls_sig.PublicKey]*bls_sig.PublicKey{pubkey: pubshare} // Maps self to self since not tbls

// Configure beacon mock
bmock, err := beaconmock.New(
beaconmock.WithValidatorSet(beaconmock.ValidatorSet{vIdx: validator}),
beaconmock.WithDeterministicProposerDuties(0), // All duties in first slot of epoch.
)
bmock, err := beaconmock.New()
require.NoError(t, err)

// Construct the validator api component
vapi, err := validatorapi.NewComponent(bmock, pubShareByKey, 0, "")
require.NoError(t, err)
t.Run("proposer_duties", func(t *testing.T) {
bmock.ProposerDutiesFunc = func(ctx context.Context, epoch eth2p0.Epoch, indices []eth2p0.ValidatorIndex) ([]*eth2v1.ProposerDuty, error) {
require.Equal(t, epoch, eth2p0.Epoch(epch))
require.Equal(t, []eth2p0.ValidatorIndex{eth2p0.ValidatorIndex(vIdx)}, indices)

duties, err := vapi.ProposerDuties(ctx, eth2p0.Epoch(0), []eth2p0.ValidatorIndex{eth2p0.ValidatorIndex(vIdx)})
require.NoError(t, err)
require.Len(t, duties, 1)
require.Equal(t, duties[0].PubKey, eth2Share)
return []*eth2v1.ProposerDuty{{
PubKey: eth2Pubkey,
ValidatorIndex: vIdx,
}}, nil
}

// Construct the validator api component
vapi, err := validatorapi.NewComponent(bmock, pubShareByKey, 0, "")
require.NoError(t, err)
duties, err := vapi.ProposerDuties(ctx, eth2p0.Epoch(epch), []eth2p0.ValidatorIndex{eth2p0.ValidatorIndex(vIdx)})
require.NoError(t, err)
require.Len(t, duties, 1)
require.Equal(t, duties[0].PubKey, eth2Share)
})

t.Run("attester_duties", func(t *testing.T) {
bmock.AttesterDutiesFunc = func(_ context.Context, epoch eth2p0.Epoch, indices []eth2p0.ValidatorIndex) ([]*eth2v1.AttesterDuty, error) {
require.Equal(t, epoch, eth2p0.Epoch(epch))
require.Equal(t, []eth2p0.ValidatorIndex{eth2p0.ValidatorIndex(vIdx)}, indices)

return []*eth2v1.AttesterDuty{{
PubKey: eth2Pubkey,
ValidatorIndex: vIdx,
}}, nil
}

// Construct the validator api component
vapi, err := validatorapi.NewComponent(bmock, pubShareByKey, 0, "")
require.NoError(t, err)
duties, err := vapi.AttesterDuties(ctx, eth2p0.Epoch(epch), []eth2p0.ValidatorIndex{eth2p0.ValidatorIndex(vIdx)})
require.NoError(t, err)
require.Len(t, duties, 1)
require.Equal(t, duties[0].PubKey, eth2Share)
})

t.Run("sync_committee_duties", func(t *testing.T) {
bmock.SyncCommitteeDutiesFunc = func(ctx context.Context, epoch eth2p0.Epoch, indices []eth2p0.ValidatorIndex) ([]*eth2v1.SyncCommitteeDuty, error) {
require.Equal(t, epoch, eth2p0.Epoch(epch))
require.Equal(t, []eth2p0.ValidatorIndex{eth2p0.ValidatorIndex(vIdx)}, indices)

return []*eth2v1.SyncCommitteeDuty{{
PubKey: eth2Pubkey,
ValidatorIndex: vIdx,
}}, nil
}

// Construct the validator api component
vapi, err := validatorapi.NewComponent(bmock, pubShareByKey, 0, "")
require.NoError(t, err)
duties, err := vapi.SyncCommitteeDuties(ctx, eth2p0.Epoch(epch), []eth2p0.ValidatorIndex{eth2p0.ValidatorIndex(vIdx)})
require.NoError(t, err)
require.Len(t, duties, 1)
require.Equal(t, duties[0].PubKey, eth2Share)
})
}

func TestComponent_SubmitValidatorRegistration(t *testing.T) {
Expand Down
3 changes: 3 additions & 0 deletions testutil/beaconmock/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -533,6 +533,9 @@ func defaultMock(httpMock HTTPMock, httpServer *http.Server, clock clockwork.Clo
SubmitProposalPreparationsFunc: func(_ context.Context, _ []*eth2v1.ProposalPreparation) error {
return nil
},
SyncCommitteeDutiesFunc: func(ctx context.Context, epoch eth2p0.Epoch, validatorIndices []eth2p0.ValidatorIndex) ([]*eth2v1.SyncCommitteeDuty, error) {
return []*eth2v1.SyncCommitteeDuty{}, nil
},
}
}

Expand Down