Skip to content
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
7 changes: 7 additions & 0 deletions core/validatorapi/eth2types.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,13 @@ type proposerDutiesResponse struct {
ExecutionOptimistic bool `json:"execution_optimistic"`
}

// ptcDutiesResponse defines the response to the ptcDuties endpoint.
type ptcDutiesResponse struct {
DependentRoot root `json:"dependent_root"`
Data []*eth2v1.PTCDuty `json:"data"`
ExecutionOptimistic bool `json:"execution_optimistic"`
}

type proposeBlockV3Response struct {
ExecutionPayloadBlinded bool `json:"execution_payload_blinded"`
ExecutionPayloadValue string `json:"execution_payload_value"`
Expand Down
30 changes: 30 additions & 0 deletions core/validatorapi/mocks/handler.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

54 changes: 54 additions & 0 deletions core/validatorapi/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ type Handler interface {
eth2client.PayloadAttestationDataProvider
eth2client.PayloadAttestationMessagesSubmitter
eth2client.ProposerDutiesProvider
eth2client.PTCDutiesProvider
eth2client.SyncCommitteeContributionProvider
eth2client.SyncCommitteeContributionsSubmitter
eth2client.SyncCommitteeDutiesProvider
Expand Down Expand Up @@ -123,6 +124,13 @@ func NewRouter(h Handler, builderEnabled bool) (*mux.Router, error) {
Methods: []string{http.MethodGet},
Encodings: []contentType{contentTypeJSON},
},
{
Name: "ptc_duties",
Path: "/eth/v1/validator/duties/ptc/{epoch}",
Handler: ptcDuties(h),
Methods: []string{http.MethodPost},
Encodings: []contentType{contentTypeJSON},
},
{
Name: "proposer_duties_v2",
Path: "/eth/v2/validator/duties/proposer/{epoch}",
Expand Down Expand Up @@ -797,6 +805,52 @@ func attesterDuties(p eth2client.AttesterDutiesProvider) handlerFunc {
}
}

// ptcDuties returns a handler function for the payload timeliness committee duty endpoint.
func ptcDuties(p eth2client.PTCDutiesProvider) handlerFunc {
return func(ctx context.Context, params map[string]string, _ http.Header, _ url.Values, typ contentType, body []byte) (any, http.Header, error) {
epoch, err := uintParam(params, "epoch")
if err != nil {
return nil, nil, err
}

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

opts := &eth2api.PTCDutiesOpts{
Epoch: eth2p0.Epoch(epoch),
Indices: req,
}

eth2Resp, err := p.PTCDuties(ctx, opts)
if err != nil {
return nil, nil, err
}

data := eth2Resp.Data
if len(data) == 0 { // Return empty json array instead of null
data = []*eth2v1.PTCDuty{}
}

executionOptimistic, err := getExecutionOptimisticFromMetadata(eth2Resp.Metadata)
if err != nil {
return nil, nil, errors.Wrap(err, "failed to decode PTCDuties response metadata")
}

dependentRoot, err := getDependentRootFromMetadata(eth2Resp.Metadata)
if err != nil {
return nil, nil, errors.Wrap(err, "failed to decode PTCDuties response metadata")
}

return ptcDutiesResponse{
ExecutionOptimistic: executionOptimistic,
DependentRoot: dependentRoot,
Data: data,
}, nil, 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, _ http.Header, _ url.Values, typ contentType, body []byte) (any, http.Header, error) {
Expand Down
55 changes: 55 additions & 0 deletions core/validatorapi/router_internal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -936,6 +936,56 @@ func TestRouter(t *testing.T) {
testRouter(t, handler, callback)
})

t.Run("ptcduty", func(t *testing.T) {
handler := testHandler{
PTCDutiesFunc: func(_ context.Context, opts *eth2api.PTCDutiesOpts) (*eth2api.Response[[]*eth2v1.PTCDuty], error) {
var res []*eth2v1.PTCDuty
for _, index := range opts.Indices {
res = append(res, &eth2v1.PTCDuty{
ValidatorIndex: index, // Echo index
Slot: eth2p0.Slot(slotsPerEpoch * opts.Epoch), // Echo first slot of epoch
PubKey: testutil.RandomEth2PubKey(t),
})
}

return wrapResponseWithMetadata(res, metadata), nil
},
}

callback := func(ctx context.Context, cl *eth2http.Service) {
const (
slotEpoch = 1
index0 = 2
index1 = 3
)

opts := &eth2api.PTCDutiesOpts{
Epoch: eth2p0.Epoch(slotEpoch),
Indices: []eth2p0.ValidatorIndex{
eth2p0.ValidatorIndex(index0),
eth2p0.ValidatorIndex(index1),
},
}
resp, err := cl.PTCDuties(ctx, opts)
require.NoError(t, err)

res := resp.Data

require.Len(t, res, 2)
require.Equal(t, int(res[0].Slot), slotEpoch*slotsPerEpoch)
require.Equal(t, int(res[0].ValidatorIndex), index0)
require.Equal(t, int(res[1].Slot), slotEpoch*slotsPerEpoch)
require.Equal(t, int(res[1].ValidatorIndex), index1)

metadata := resp.Metadata
require.Len(t, metadata, 2)
require.Equal(t, true, metadata["execution_optimistic"])
require.Equal(t, dependentRoot, metadata["dependent_root"].(eth2p0.Root))
}

testRouter(t, handler, callback)
})

t.Run("proposerduty", func(t *testing.T) {
const total = 2

Expand Down Expand Up @@ -2289,6 +2339,7 @@ type testHandler struct {
SyncCommitteeDutiesFunc func(ctx context.Context, opts *eth2api.SyncCommitteeDutiesOpts) (*eth2api.Response[[]*eth2v1.SyncCommitteeDuty], error)
SyncCommitteeContributionFunc func(ctx context.Context, opts *eth2api.SyncCommitteeContributionOpts) (*eth2api.Response[*altair.SyncCommitteeContribution], error)
PayloadAttestationDataFunc func(ctx context.Context, opts *eth2api.PayloadAttestationDataOpts) (*eth2api.Response[*eth2spec.VersionedPayloadAttestationData], error)
PTCDutiesFunc func(ctx context.Context, opts *eth2api.PTCDutiesOpts) (*eth2api.Response[[]*eth2v1.PTCDuty], error)
SubmitPayloadAttMsgsFunc func(ctx context.Context, opts *eth2api.SubmitPayloadAttestationMessagesOpts) error
ProxyFunc func(ctx context.Context, req *http.Request) (*http.Response, error)
AddressFunc func() string
Expand All @@ -2307,6 +2358,10 @@ func (h testHandler) SubmitPayloadAttestationMessages(ctx context.Context, opts
return h.SubmitPayloadAttMsgsFunc(ctx, opts)
}

func (h testHandler) PTCDuties(ctx context.Context, opts *eth2api.PTCDutiesOpts) (*eth2api.Response[[]*eth2v1.PTCDuty], error) {
return h.PTCDutiesFunc(ctx, opts)
}

func (h testHandler) AttesterDuties(ctx context.Context, opts *eth2api.AttesterDutiesOpts) (*eth2api.Response[[]*eth2v1.AttesterDuty], error) {
return h.AttesterDutiesFunc(ctx, opts)
}
Expand Down
33 changes: 33 additions & 0 deletions core/validatorapi/validatorapi.go
Original file line number Diff line number Diff line change
Expand Up @@ -1313,6 +1313,39 @@ func (c Component) AttesterDuties(ctx context.Context, opts *eth2api.AttesterDut
return wrapResponseWithMetadata(duties, metadata), nil
}

// PTCDuties obtains payload timeliness committee duties with the root public keys replaced by public shares.
func (c Component) PTCDuties(ctx context.Context, opts *eth2api.PTCDutiesOpts) (*eth2api.Response[[]*eth2v1.PTCDuty], error) {
var span trace.Span

ctx, span = tracer.Start(ctx, "core/validatorapi.PTCDuties")

span.SetAttributes(attribute.Int64("epoch", int64(opts.Epoch)))
defer span.End()

eth2Resp, err := c.eth2Cl.PTCDuties(ctx, opts)
if err != nil {
return nil, err
}

duties := eth2Resp.Data

// Replace root public keys with public shares.
for _, d := range duties {
if d == nil {
return nil, errors.New("ptc duty cannot be nil")
}

pubshare, ok := c.getPubShareFunc(d.PubKey)
if !ok {
return nil, errors.New("pubshare not found", z.Str("pubkey", d.PubKey.String()))
}

d.PubKey = pubshare
}

return wrapResponseWithMetadata(duties, eth2Resp.Metadata), nil
}

// SyncCommitteeDuties obtains sync committee duties. If validatorIndices is nil it will return all duties for the given epoch.
func (c Component) SyncCommitteeDuties(ctx context.Context, opts *eth2api.SyncCommitteeDutiesOpts) (*eth2api.Response[[]*eth2v1.SyncCommitteeDuty], error) {
var duties []*eth2v1.SyncCommitteeDuty
Expand Down
27 changes: 27 additions & 0 deletions core/validatorapi/validatorapi_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1851,6 +1851,33 @@ func TestComponent_Duties(t *testing.T) {
require.Equal(t, duties[0].PubKey, eth2Share)
})

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

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

// Construct the validator api component
vapi, err := validatorapi.NewComponent(bmock, allPubSharesByKey, shareIdx, nil, false, 30000000)
require.NoError(t, err)

opts := &eth2api.PTCDutiesOpts{
Epoch: eth2p0.Epoch(epch),
Indices: []eth2p0.ValidatorIndex{eth2p0.ValidatorIndex(vIdx)},
}
resp, err := vapi.PTCDuties(ctx, opts)
require.NoError(t, err)

duties := resp.Data
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))
Expand Down
Loading