Skip to content

Commit

Permalink
Enable validator client to submit payload attestation message (#14064)
Browse files Browse the repository at this point in the history
  • Loading branch information
potuz committed Jul 10, 2024
1 parent fdd0b57 commit 29e2460
Show file tree
Hide file tree
Showing 18 changed files with 1,661 additions and 1,237 deletions.
1 change: 1 addition & 0 deletions beacon-chain/rpc/prysm/v1alpha1/validator/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ go_library(
"proposer_exits.go",
"proposer_slashings.go",
"proposer_sync_aggregate.go",
"ptc_attester.go",
"server.go",
"status.go",
"sync_committee.go",
Expand Down
17 changes: 17 additions & 0 deletions beacon-chain/rpc/prysm/v1alpha1/validator/ptc_attester.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package validator

import (
"context"

"github.com/golang/protobuf/ptypes/empty"
"github.com/pkg/errors"
ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1"
)

func (vs *Server) GetPayloadAttestationData(ctx context.Context, req *ethpb.GetPayloadAttestationDataRequest) (*ethpb.PayloadAttestationData, error) {
return nil, errors.New("not implemented")
}

func (vs *Server) SubmitPayloadAttestation(ctx context.Context, in *ethpb.PayloadAttestationMessage) (*empty.Empty, error) {
return nil, errors.New("not implemented")
}
2,639 changes: 1,402 additions & 1,237 deletions proto/prysm/v1alpha1/validator.pb.go

Large diffs are not rendered by default.

8 changes: 8 additions & 0 deletions proto/prysm/v1alpha1/validator.proto
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import "proto/eth/ext/options.proto";
import "proto/prysm/v1alpha1/beacon_block.proto";
import "proto/prysm/v1alpha1/sync_committee.proto";
import "proto/prysm/v1alpha1/attestation.proto";
import "proto/prysm/v1alpha1/payload_attestation.proto";

option csharp_namespace = "Ethereum.Eth.V1";
option go_package = "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1;eth";
Expand Down Expand Up @@ -338,6 +339,13 @@ service BeaconNodeValidator {
get: "/eth/v1alpha1/validator/blocks/aggregated_sig_and_aggregation_bits"
};
}

rpc GetPayloadAttestationData(GetPayloadAttestationDataRequest) returns (PayloadAttestationData) {}
rpc SubmitPayloadAttestation(PayloadAttestationMessage) returns (google.protobuf.Empty) {}
}

message GetPayloadAttestationDataRequest {
uint64 slot = 1 [(ethereum.eth.ext.cast_type) = "github.com/prysmaticlabs/prysm/v5/consensus-types/primitives.Slot"];
}

// SyncMessageBlockRootResponse for beacon chain validator to retrieve and
Expand Down
40 changes: 40 additions & 0 deletions testing/mock/beacon_validator_client_mock.go

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

30 changes: 30 additions & 0 deletions testing/mock/beacon_validator_server_mock.go

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

30 changes: 30 additions & 0 deletions testing/validator-mock/validator_client_mock.go

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

4 changes: 4 additions & 0 deletions validator/accounts/testing/mock.go
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,10 @@ func (_ *Validator) SubmitSignedContributionAndProof(_ context.Context, _ primit
panic("implement me")
}

func (_ *Validator) SubmitPayloadAttestationMessage(_ context.Context, _ primitives.Slot, _ [48]byte) {
panic("implement me")
}

func (_ *Validator) LogSubmittedAtts(_ primitives.Slot) {
panic("implement me")
}
Expand Down
8 changes: 8 additions & 0 deletions validator/client/beacon-api/beacon_api_validator_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,14 @@ func (c *beaconApiValidatorClient) AggregatedSyncSelections(ctx context.Context,
})
}

func (c *beaconApiValidatorClient) GetPayloadAttestationData(ctx context.Context, in *ethpb.GetPayloadAttestationDataRequest) (*ethpb.PayloadAttestationData, error) {
return nil, errors.New("not implemented")
}

func (c *beaconApiValidatorClient) SubmitPayloadAttestation(ctx context.Context, in *ethpb.PayloadAttestationMessage) (*empty.Empty, error) {
return nil, errors.New("not implemented")
}

func wrapInMetrics[Resp any](action string, f func() (Resp, error)) (Resp, error) {
now := time.Now()
resp, err := f()
Expand Down
8 changes: 8 additions & 0 deletions validator/client/grpc-api/grpc_validator_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,14 @@ func (*grpcValidatorClient) AggregatedSyncSelections(context.Context, []iface.Sy
return nil, iface.ErrNotSupported
}

func (c *grpcValidatorClient) GetPayloadAttestationData(ctx context.Context, in *ethpb.GetPayloadAttestationDataRequest) (*ethpb.PayloadAttestationData, error) {
return c.beaconNodeValidatorClient.GetPayloadAttestationData(ctx, in)
}

func (c *grpcValidatorClient) SubmitPayloadAttestation(ctx context.Context, in *ethpb.PayloadAttestationMessage) (*empty.Empty, error) {
return c.beaconNodeValidatorClient.SubmitPayloadAttestation(ctx, in)
}

func NewGrpcValidatorClient(cc grpc.ClientConnInterface) iface.ValidatorClient {
return &grpcValidatorClient{ethpb.NewBeaconNodeValidatorClient(cc), false}
}
Expand Down
3 changes: 3 additions & 0 deletions validator/client/iface/validator.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ const (
RoleSyncCommittee
// RoleSyncCommitteeAggregator means the validator should aggregate sync committee messages and submit a sync committee contribution.
RoleSyncCommitteeAggregator
// RolePayloadTimelinessCommittee means the validator should submit a payload attestation message.
RolePayloadTimelinessCommittee
)

// Validator interface defines the primary methods of a validator client.
Expand All @@ -50,6 +52,7 @@ type Validator interface {
SubmitAggregateAndProof(ctx context.Context, slot primitives.Slot, pubKey [fieldparams.BLSPubkeyLength]byte)
SubmitSyncCommitteeMessage(ctx context.Context, slot primitives.Slot, pubKey [fieldparams.BLSPubkeyLength]byte)
SubmitSignedContributionAndProof(ctx context.Context, slot primitives.Slot, pubKey [fieldparams.BLSPubkeyLength]byte)
SubmitPayloadAttestationMessage(ctx context.Context, slot primitives.Slot, pubKey [fieldparams.BLSPubkeyLength]byte)
LogSubmittedAtts(slot primitives.Slot)
LogSubmittedSyncCommitteeMessages()
UpdateDomainDataCaches(ctx context.Context, slot primitives.Slot)
Expand Down
3 changes: 3 additions & 0 deletions validator/client/iface/validator_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,9 @@ type ValidatorClient interface {
FeeRecipientByPubKey(ctx context.Context, in *ethpb.FeeRecipientByPubKeyRequest) (*ethpb.FeeRecipientByPubKeyResponse, error)
AttestationData(ctx context.Context, in *ethpb.AttestationDataRequest) (*ethpb.AttestationData, error)
ProposeAttestation(ctx context.Context, in *ethpb.Attestation) (*ethpb.AttestResponse, error)
GetPayloadAttestationData(ctx context.Context, in *ethpb.GetPayloadAttestationDataRequest) (*ethpb.PayloadAttestationData, error)
SubmitPayloadAttestation(ctx context.Context, in *ethpb.PayloadAttestationMessage) (*empty.Empty, error)

SubmitAggregateSelectionProof(ctx context.Context, in *ethpb.AggregateSelectionRequest, index primitives.ValidatorIndex, committeeLength uint64) (*ethpb.AggregateSelectionResponse, error)
SubmitSignedAggregateSelectionProof(ctx context.Context, in *ethpb.SignedAggregateSubmitRequest) (*ethpb.SignedAggregateSubmitResponse, error)
ProposeExit(ctx context.Context, in *ethpb.SignedVoluntaryExit) (*ethpb.ProposeExitResponse, error)
Expand Down
32 changes: 32 additions & 0 deletions validator/client/payload_attestation.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,43 @@ import (
"github.com/prysmaticlabs/prysm/v5/beacon-chain/core/signing"
fieldparams "github.com/prysmaticlabs/prysm/v5/config/fieldparams"
"github.com/prysmaticlabs/prysm/v5/config/params"
"github.com/prysmaticlabs/prysm/v5/consensus-types/primitives"
ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1"
validatorpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1/validator-client"
"github.com/prysmaticlabs/prysm/v5/time/slots"
)

// SubmitPayloadAttestationMessage submits a payload attestation message to the beacon node.
func (v *validator) SubmitPayloadAttestationMessage(ctx context.Context, slot primitives.Slot, pubKey [fieldparams.BLSPubkeyLength]byte) {
data, err := v.validatorClient.GetPayloadAttestationData(ctx, &ethpb.GetPayloadAttestationDataRequest{Slot: slot})
if err != nil {
log.WithError(err).Error("could not get payload attestation data")
return
}

signature, err := v.signPayloadAttestation(ctx, data, pubKey)
if err != nil {
log.WithError(err).Error("could not sign payload attestation")
return
}

index, found := v.pubkeyToValidatorIndex[pubKey]
if !found {
log.WithField("pubkey", pubKey).Error("could not find validator index for pubkey")
return
}

message := &ethpb.PayloadAttestationMessage{
ValidatorIndex: index,
Data: data,
Signature: signature,
}

if _, err := v.validatorClient.SubmitPayloadAttestation(ctx, message); err != nil {
log.WithError(err).Error("could not submit payload attestation")
}
}

func (v *validator) signPayloadAttestation(ctx context.Context, p *ethpb.PayloadAttestationData, pubKey [fieldparams.BLSPubkeyLength]byte) ([]byte, error) {
// Get domain data
epoch := slots.ToEpoch(p.Slot)
Expand Down
62 changes: 62 additions & 0 deletions validator/client/payload_attestation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,79 @@ import (
"context"
"testing"

"github.com/golang/protobuf/ptypes/empty"
"github.com/prysmaticlabs/prysm/v5/beacon-chain/core/signing"
fieldparams "github.com/prysmaticlabs/prysm/v5/config/fieldparams"
"github.com/prysmaticlabs/prysm/v5/config/params"
"github.com/prysmaticlabs/prysm/v5/consensus-types/primitives"
"github.com/prysmaticlabs/prysm/v5/crypto/bls"
"github.com/prysmaticlabs/prysm/v5/encoding/bytesutil"
ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1"
"github.com/prysmaticlabs/prysm/v5/testing/require"
"github.com/prysmaticlabs/prysm/v5/testing/util/random"
"github.com/prysmaticlabs/prysm/v5/time/slots"
"go.uber.org/mock/gomock"
)

func TestValidator_SubmitPayloadAttestationMessage(t *testing.T) {
// Setup the test environment.
validator, m, validatorKey, finish := setup(t, true)
defer finish()
var pubKey [fieldparams.BLSPubkeyLength]byte
copy(pubKey[:], validatorKey.PublicKey().Marshal())

// Map to associate public keys with validator indices.
validator.pubkeyToValidatorIndex = make(map[[fieldparams.BLSPubkeyLength]byte]primitives.ValidatorIndex)
validatorIndex := primitives.ValidatorIndex(1)
validator.pubkeyToValidatorIndex[pubKey] = validatorIndex

// Generate random payload attestation data for the test.
d := random.PayloadAttestationData(t)
slot := primitives.Slot(1000)
epoch := slots.ToEpoch(slot)
d.Slot = slot

// Expectation for the mock validator client to return the generated payload attestation data.
m.validatorClient.EXPECT().GetPayloadAttestationData(
gomock.Any(), // Context
gomock.AssignableToTypeOf(&ethpb.GetPayloadAttestationDataRequest{Slot: slot}),
).Return(d, nil)

// Expectation for the mock validator client to return the domain data for the given epoch and domain.
m.validatorClient.EXPECT().DomainData(
gomock.Any(), // Context
&ethpb.DomainRequest{
Epoch: epoch,
Domain: params.BeaconConfig().DomainPTCAttester[:],
},
).Return(&ethpb.DomainResponse{SignatureDomain: make([]byte, 32)}, nil /*err*/)

// Duplicate domain data request for computing the correct signature for matching expectations.
m.validatorClient.EXPECT().DomainData(
gomock.Any(), // Context
&ethpb.DomainRequest{
Epoch: epoch,
Domain: params.BeaconConfig().DomainPTCAttester[:],
},
).Return(&ethpb.DomainResponse{SignatureDomain: make([]byte, 32)}, nil /*err*/)

// Sign the payload attestation data using the validator's private key.
sig, err := validator.signPayloadAttestation(context.Background(), d, pubKey)
require.NoError(t, err)

// Expectation for the mock validator client to submit the payload attestation with the signed data.
m.validatorClient.EXPECT().SubmitPayloadAttestation(
gomock.Any(), // Context
gomock.Eq(&ethpb.PayloadAttestationMessage{
ValidatorIndex: validatorIndex,
Data: d,
Signature: sig,
}),
).Return(&empty.Empty{}, nil)

validator.SubmitPayloadAttestationMessage(context.Background(), slot, pubKey)
}

func Test_validator_signPayloadAttestation(t *testing.T) {
v, m, vk, finish := setup(t, false)
defer finish()
Expand Down
Loading

0 comments on commit 29e2460

Please sign in to comment.