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

validator REST: attestation v2 #14633

Merged
merged 29 commits into from
Nov 20, 2024
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
22b610f
wip
james-prysm Nov 7, 2024
f9d3c42
fixing tests
james-prysm Nov 7, 2024
4eb8ad0
adding unit tests
james-prysm Nov 7, 2024
e5c2157
fixing tests
james-prysm Nov 8, 2024
22a9d0a
adding back v1 usage
james-prysm Nov 8, 2024
1ff1822
changelog
james-prysm Nov 8, 2024
7caa823
Merge branch 'develop' into validator-rest-electra-attestation
james-prysm Nov 8, 2024
84eb391
rolling back test and adding placeholder
james-prysm Nov 8, 2024
6b5d44a
adding electra tests
james-prysm Nov 8, 2024
f1c677a
Merge branch 'develop' into validator-rest-electra-attestation
james-prysm Nov 8, 2024
94d18c6
Merge branch 'develop' into validator-rest-electra-attestation
james-prysm Nov 12, 2024
b6d96d5
adding attestation nil check based on review
james-prysm Nov 12, 2024
83b3d09
reduce code duplication
james-prysm Nov 13, 2024
f489d4e
Merge branch 'develop' into validator-rest-electra-attestation
james-prysm Nov 13, 2024
299048f
linting
james-prysm Nov 13, 2024
6636965
fixing tests
james-prysm Nov 13, 2024
0059090
based on sammy review
james-prysm Nov 14, 2024
e397dd3
radek feedback
james-prysm Nov 18, 2024
2fdce65
Merge branch 'develop' into validator-rest-electra-attestation
james-prysm Nov 18, 2024
4003c86
Merge branch 'develop' into validator-rest-electra-attestation
james-prysm Nov 19, 2024
736d0d2
adding fall back for pre electra and updated tests
james-prysm Nov 19, 2024
48cb111
fixing api calls and associated tests
james-prysm Nov 19, 2024
5dfbc2f
Merge branch 'develop' into validator-rest-electra-attestation
james-prysm Nov 19, 2024
2855c2e
gaz
james-prysm Nov 19, 2024
42d9e22
Update validator/client/beacon-api/propose_attestation.go
james-prysm Nov 20, 2024
8254f2f
review feedback
james-prysm Nov 20, 2024
bf4577d
add missing fallback
james-prysm Nov 20, 2024
6fc4954
Merge branch 'develop' into validator-rest-electra-attestation
james-prysm Nov 20, 2024
a0cc0c3
fixing tests
james-prysm Nov 20, 2024
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
21 changes: 18 additions & 3 deletions validator/client/beacon-api/beacon_api_validator_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,12 @@ func (c *beaconApiValidatorClient) ProposeAttestation(ctx context.Context, in *e
}

func (c *beaconApiValidatorClient) ProposeAttestationElectra(ctx context.Context, in *ethpb.AttestationElectra) (*ethpb.AttestResponse, error) {
return nil, errors.New("ProposeAttestationElectra is not implemented")
ctx, span := trace.StartSpan(ctx, "beacon-api.ProposeAttestationElectra")
defer span.End()

return wrapInMetrics[*ethpb.AttestResponse]("ProposeAttestationElectra", func() (*ethpb.AttestResponse, error) {
return c.proposeAttestationElectra(ctx, in)
})
}

func (c *beaconApiValidatorClient) ProposeBeaconBlock(ctx context.Context, in *ethpb.GenericSignedBeaconBlock) (*ethpb.ProposeResponse, error) {
Expand Down Expand Up @@ -190,7 +195,12 @@ func (c *beaconApiValidatorClient) SubmitAggregateSelectionProof(ctx context.Con
}

func (c *beaconApiValidatorClient) SubmitAggregateSelectionProofElectra(ctx context.Context, in *ethpb.AggregateSelectionRequest, index primitives.ValidatorIndex, committeeLength uint64) (*ethpb.AggregateSelectionElectraResponse, error) {
return nil, errors.New("SubmitAggregateSelectionProofElectra is not implemented")
ctx, span := trace.StartSpan(ctx, "beacon-api.SubmitAggregateSelectionProofElectra")
defer span.End()

return wrapInMetrics[*ethpb.AggregateSelectionElectraResponse]("SubmitAggregateSelectionProofElectra", func() (*ethpb.AggregateSelectionElectraResponse, error) {
return c.submitAggregateSelectionProofElectra(ctx, in, index, committeeLength)
})
}

func (c *beaconApiValidatorClient) SubmitSignedAggregateSelectionProof(ctx context.Context, in *ethpb.SignedAggregateSubmitRequest) (*ethpb.SignedAggregateSubmitResponse, error) {
Expand All @@ -203,7 +213,12 @@ func (c *beaconApiValidatorClient) SubmitSignedAggregateSelectionProof(ctx conte
}

func (c *beaconApiValidatorClient) SubmitSignedAggregateSelectionProofElectra(ctx context.Context, in *ethpb.SignedAggregateSubmitElectraRequest) (*ethpb.SignedAggregateSubmitResponse, error) {
return nil, errors.New("SubmitSignedAggregateSelectionProofElectra is not implemented")
ctx, span := trace.StartSpan(ctx, "beacon-api.SubmitSignedAggregateSelectionProofElectra")
defer span.End()

return wrapInMetrics[*ethpb.SignedAggregateSubmitResponse]("SubmitSignedAggregateSelectionProofElectra", func() (*ethpb.SignedAggregateSubmitResponse, error) {
return c.submitSignedAggregateSelectionProofElectra(ctx, in)
})
}

func (c *beaconApiValidatorClient) SubmitSignedContributionAndProof(ctx context.Context, in *ethpb.SignedContributionAndProof) (*empty.Empty, error) {
Expand Down
28 changes: 28 additions & 0 deletions validator/client/beacon-api/beacon_block_json_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,14 @@ func jsonifyAttestations(attestations []*ethpb.Attestation) []*structs.Attestati
return jsonAttestations
}

func jsonifyAttestationsElectra(attestations []*ethpb.AttestationElectra) []*structs.AttestationElectra {
jsonAttestations := make([]*structs.AttestationElectra, len(attestations))
for index, attestation := range attestations {
jsonAttestations[index] = jsonifyAttestationElectra(attestation)
}
return jsonAttestations
}

func jsonifyAttesterSlashings(attesterSlashings []*ethpb.AttesterSlashing) []*structs.AttesterSlashing {
jsonAttesterSlashings := make([]*structs.AttesterSlashing, len(attesterSlashings))
for index, attesterSlashing := range attesterSlashings {
Expand Down Expand Up @@ -164,6 +172,15 @@ func jsonifyAttestation(attestation *ethpb.Attestation) *structs.Attestation {
}
}

func jsonifyAttestationElectra(attestation *ethpb.AttestationElectra) *structs.AttestationElectra {
return &structs.AttestationElectra{
AggregationBits: hexutil.Encode(attestation.AggregationBits),
Data: jsonifyAttestationData(attestation.Data),
Signature: hexutil.Encode(attestation.Signature),
CommitteeBits: hexutil.Encode(attestation.CommitteeBits),
}
}

func jsonifySignedAggregateAndProof(signedAggregateAndProof *ethpb.SignedAggregateAttestationAndProof) *structs.SignedAggregateAttestationAndProof {
return &structs.SignedAggregateAttestationAndProof{
Message: &structs.AggregateAttestationAndProof{
Expand All @@ -175,6 +192,17 @@ func jsonifySignedAggregateAndProof(signedAggregateAndProof *ethpb.SignedAggrega
}
}

func jsonifySignedAggregateAndProofElectra(signedAggregateAndProof *ethpb.SignedAggregateAttestationAndProofElectra) *structs.SignedAggregateAttestationAndProofElectra {
return &structs.SignedAggregateAttestationAndProofElectra{
Message: &structs.AggregateAttestationAndProofElectra{
AggregatorIndex: uint64ToString(signedAggregateAndProof.Message.AggregatorIndex),
Aggregate: jsonifyAttestationElectra(signedAggregateAndProof.Message.Aggregate),
SelectionProof: hexutil.Encode(signedAggregateAndProof.Message.SelectionProof),
},
Signature: hexutil.Encode(signedAggregateAndProof.Signature),
}
}

func jsonifyWithdrawals(withdrawals []*enginev1.Withdrawal) []*structs.Withdrawal {
jsonWithdrawals := make([]*structs.Withdrawal, len(withdrawals))
for index, withdrawal := range withdrawals {
Expand Down
33 changes: 33 additions & 0 deletions validator/client/beacon-api/beacon_block_proto_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,39 @@ func convertAttestationToProto(jsonAttestation *structs.Attestation) (*ethpb.Att
}, nil
}

func convertAttestationElectraToProto(jsonAttestation *structs.AttestationElectra) (*ethpb.AttestationElectra, error) {
if jsonAttestation == nil {
return nil, errors.New("json attestation is nil")
}

aggregationBits, err := hexutil.Decode(jsonAttestation.AggregationBits)
if err != nil {
return nil, errors.Wrapf(err, "failed to decode aggregation bits `%s`", jsonAttestation.AggregationBits)
}

attestationData, err := convertAttestationDataToProto(jsonAttestation.Data)
if err != nil {
return nil, errors.Wrap(err, "failed to get attestation data")
}

signature, err := hexutil.Decode(jsonAttestation.Signature)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

should we be checking for lengths?

Copy link
Contributor

Choose a reason for hiding this comment

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

maybe we can add this in a future PR, but I would not do it here

if err != nil {
return nil, errors.Wrapf(err, "failed to decode attestation signature `%s`", jsonAttestation.Signature)
}

committeeBits, err := hexutil.Decode(jsonAttestation.CommitteeBits)
if err != nil {
return nil, errors.Wrapf(err, "failed to decode committee bits `%s`", jsonAttestation.CommitteeBits)
}

return &ethpb.AttestationElectra{
AggregationBits: aggregationBits,
Data: attestationData,
Signature: signature,
CommitteeBits: committeeBits,
}, nil
}

func convertAttestationsToProto(jsonAttestations []*structs.Attestation) ([]*ethpb.Attestation, error) {
var attestations []*ethpb.Attestation
for index, jsonAttestation := range jsonAttestations {
Expand Down
55 changes: 44 additions & 11 deletions validator/client/beacon-api/propose_attestation.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,10 @@ import (
)

func (c *beaconApiValidatorClient) proposeAttestation(ctx context.Context, attestation *ethpb.Attestation) (*ethpb.AttestResponse, error) {
if err := checkNilAttestation(attestation); err != nil {
if attestation == nil {
return nil, errors.New("attestation is nil")
}
if err := checkNilContentsAttestation(attestation); err != nil {
return nil, err
}

Expand All @@ -37,27 +40,57 @@ func (c *beaconApiValidatorClient) proposeAttestation(ctx context.Context, attes
return &ethpb.AttestResponse{AttestationDataRoot: attestationDataRoot[:]}, nil
}

// checkNilAttestation returns error if attestation or any field of attestation is nil.
func checkNilAttestation(attestation *ethpb.Attestation) error {
if attestation == nil {
return errors.New("attestation is nil")
}

if attestation.Data == nil {
// checkNilContentsAttestation returns error if attestation or any field of attestation is nil.
func checkNilContentsAttestation(attestation ethpb.Att) error {
if attestation.GetData() == nil {
james-prysm marked this conversation as resolved.
Show resolved Hide resolved
return errors.New("attestation data is nil")
}

if attestation.Data.Source == nil || attestation.Data.Target == nil {
if attestation.GetData().Source == nil || attestation.GetData().Target == nil {
return errors.New("source/target in attestation data is nil")
}

if len(attestation.AggregationBits) == 0 {
if len(attestation.GetAggregationBits()) == 0 {
return errors.New("attestation aggregation bits is empty")
}

if len(attestation.Signature) == 0 {
if len(attestation.GetSignature()) == 0 {
return errors.New("attestation signature is empty")
}

return nil
}

func (c *beaconApiValidatorClient) proposeAttestationElectra(ctx context.Context, attestation *ethpb.AttestationElectra) (*ethpb.AttestResponse, error) {
if attestation == nil {
return nil, errors.New("attestation is nil")
}
if err := checkNilContentsAttestation(attestation); err != nil {
return nil, err
}
if len(attestation.CommitteeBits) == 0 {
return nil, errors.New("attestation committee bits is empty")
}

marshalledAttestation, err := json.Marshal(jsonifyAttestationsElectra([]*ethpb.AttestationElectra{attestation}))
if err != nil {
return nil, err
}

if err = c.jsonRestHandler.Post(
ctx,
"/eth/v2/beacon/pool/attestations",
nil,
bytes.NewBuffer(marshalledAttestation),
nil,
); err != nil {
return nil, err
}

attestationDataRoot, err := attestation.Data.HashTreeRoot()
if err != nil {
return nil, errors.Wrap(err, "failed to compute attestation data root")
}

return &ethpb.AttestResponse{AttestationDataRoot: attestationDataRoot[:]}, nil
}
168 changes: 167 additions & 1 deletion validator/client/beacon-api/propose_attestation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ func TestProposeAttestation(t *testing.T) {
jsonRestHandler := mock.NewMockJsonRestHandler(ctrl)

var marshalledAttestations []byte
if checkNilAttestation(test.attestation) == nil {
if checkNilContentsAttestation(test.attestation) == nil {
b, err := json.Marshal(jsonifyAttestations([]*ethpb.Attestation{test.attestation}))
require.NoError(t, err)
marshalledAttestations = b
Expand Down Expand Up @@ -153,3 +153,169 @@ func TestProposeAttestation(t *testing.T) {
})
}
}

func TestProposeAttestationElectra(t *testing.T) {
attestation := &ethpb.AttestationElectra{
AggregationBits: testhelpers.FillByteSlice(4, 74),
Data: &ethpb.AttestationData{
Slot: 75,
CommitteeIndex: 76,
BeaconBlockRoot: testhelpers.FillByteSlice(32, 38),
Source: &ethpb.Checkpoint{
Epoch: 78,
Root: testhelpers.FillByteSlice(32, 79),
},
Target: &ethpb.Checkpoint{
Epoch: 80,
Root: testhelpers.FillByteSlice(32, 81),
},
},
Signature: testhelpers.FillByteSlice(96, 82),
CommitteeBits: testhelpers.FillByteSlice(8, 83),
}

tests := []struct {
name string
attestation *ethpb.AttestationElectra
expectedErrorMessage string
endpointError error
endpointCall int
}{
{
name: "valid",
attestation: attestation,
endpointCall: 1,
},
{
name: "nil attestation",
expectedErrorMessage: "attestation is nil",
},
{
name: "nil attestation data",
attestation: &ethpb.AttestationElectra{
AggregationBits: testhelpers.FillByteSlice(4, 74),
Signature: testhelpers.FillByteSlice(96, 82),
CommitteeBits: testhelpers.FillByteSlice(8, 83),
},
expectedErrorMessage: "attestation data is nil",
},
{
name: "nil source checkpoint",
attestation: &ethpb.AttestationElectra{
AggregationBits: testhelpers.FillByteSlice(4, 74),
Data: &ethpb.AttestationData{
Target: &ethpb.Checkpoint{},
},
Signature: testhelpers.FillByteSlice(96, 82),
CommitteeBits: testhelpers.FillByteSlice(8, 83),
},
expectedErrorMessage: "source/target in attestation data is nil",
},
{
name: "nil target checkpoint",
attestation: &ethpb.AttestationElectra{
AggregationBits: testhelpers.FillByteSlice(4, 74),
Data: &ethpb.AttestationData{
Source: &ethpb.Checkpoint{},
},
Signature: testhelpers.FillByteSlice(96, 82),
CommitteeBits: testhelpers.FillByteSlice(8, 83),
},
expectedErrorMessage: "source/target in attestation data is nil",
},
{
name: "nil aggregation bits",
attestation: &ethpb.AttestationElectra{
Data: &ethpb.AttestationData{
Source: &ethpb.Checkpoint{},
Target: &ethpb.Checkpoint{},
},
Signature: testhelpers.FillByteSlice(96, 82),
CommitteeBits: testhelpers.FillByteSlice(8, 83),
},
expectedErrorMessage: "attestation aggregation bits is empty",
},
{
name: "nil signature",
attestation: &ethpb.AttestationElectra{
AggregationBits: testhelpers.FillByteSlice(4, 74),
Data: &ethpb.AttestationData{
Source: &ethpb.Checkpoint{},
Target: &ethpb.Checkpoint{},
},
CommitteeBits: testhelpers.FillByteSlice(8, 83),
},
expectedErrorMessage: "attestation signature is empty",
},
{
name: "nil committee bits",
attestation: &ethpb.AttestationElectra{
AggregationBits: testhelpers.FillByteSlice(4, 74),
Data: &ethpb.AttestationData{
Slot: 75,
CommitteeIndex: 76,
BeaconBlockRoot: testhelpers.FillByteSlice(32, 38),
Source: &ethpb.Checkpoint{
Epoch: 78,
Root: testhelpers.FillByteSlice(32, 79),
},
Target: &ethpb.Checkpoint{
Epoch: 80,
Root: testhelpers.FillByteSlice(32, 81),
},
},
Signature: testhelpers.FillByteSlice(96, 82),
},
expectedErrorMessage: "attestation committee bits is empty",
},
{
name: "bad request",
attestation: attestation,
expectedErrorMessage: "bad request",
endpointError: errors.New("bad request"),
endpointCall: 1,
},
}

for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
ctrl := gomock.NewController(t)
jsonRestHandler := mock.NewMockJsonRestHandler(ctrl)

var marshalledAttestations []byte
if checkNilContentsAttestation(test.attestation) == nil {
b, err := json.Marshal(jsonifyAttestationsElectra([]*ethpb.AttestationElectra{test.attestation}))
require.NoError(t, err)
marshalledAttestations = b
}

ctx := context.Background()

jsonRestHandler.EXPECT().Post(
gomock.Any(),
"/eth/v2/beacon/pool/attestations",
nil,
bytes.NewBuffer(marshalledAttestations),
nil,
).Return(
test.endpointError,
).Times(test.endpointCall)

validatorClient := &beaconApiValidatorClient{jsonRestHandler: jsonRestHandler}
proposeResponse, err := validatorClient.proposeAttestationElectra(ctx, test.attestation)
if test.expectedErrorMessage != "" {
require.ErrorContains(t, test.expectedErrorMessage, err)
return
}

require.NoError(t, err)
require.NotNil(t, proposeResponse)

expectedAttestationDataRoot, err := attestation.Data.HashTreeRoot()
require.NoError(t, err)

// Make sure that the attestation data root is set
assert.DeepEqual(t, expectedAttestationDataRoot[:], proposeResponse.AttestationDataRoot)
})
}
}
Loading
Loading