Add REST implementation for validator's ProposeAttestation (#11800)

* Add REST implementation for validator's ProposeAttestation

* handle nil attestation

* update propose attestation with context

* fix lint

* add remaining nil testcases

* Update validator/client/beacon-api/propose_attestation_test.go

Co-authored-by: Radosław Kapka <radek@prysmaticlabs.com>

* fix BUILD.bazel

Co-authored-by: Radosław Kapka <radek@prysmaticlabs.com>
Co-authored-by: james-prysm <90280386+james-prysm@users.noreply.github.com>
This commit is contained in:
Dhruv Bodani 2023-01-10 21:51:29 +05:30 committed by GitHub
parent bbe003720c
commit 116f3ac265
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 216 additions and 6 deletions

View File

@ -15,6 +15,7 @@ go_library(
"index.go",
"json_rest_handler.go",
"prepare_beacon_proposer.go",
"propose_attestation.go",
"propose_beacon_block.go",
"propose_exit.go",
"registration.go",
@ -64,6 +65,7 @@ go_test(
"index_test.go",
"json_rest_handler_test.go",
"prepare_beacon_proposer_test.go",
"propose_attestation_test.go",
"propose_beacon_block_altair_test.go",
"propose_beacon_block_bellatrix_test.go",
"propose_beacon_block_blinded_bellatrix_test.go",

View File

@ -117,12 +117,7 @@ func (c *beaconApiValidatorClient) PrepareBeaconProposer(ctx context.Context, in
}
func (c *beaconApiValidatorClient) ProposeAttestation(ctx context.Context, in *ethpb.Attestation) (*ethpb.AttestResponse, error) {
if c.fallbackClient != nil {
return c.fallbackClient.ProposeAttestation(ctx, in)
}
// TODO: Implement me
panic("beaconApiValidatorClient.ProposeAttestation is not implemented. To use a fallback client, create this validator with NewBeaconApiValidatorClientWithFallback instead.")
return c.proposeAttestation(ctx, in)
}
func (c *beaconApiValidatorClient) ProposeBeaconBlock(ctx context.Context, in *ethpb.GenericSignedBeaconBlock) (*ethpb.ProposeResponse, error) {

View File

@ -0,0 +1,57 @@
package beacon_api
import (
"bytes"
"context"
"encoding/json"
"github.com/pkg/errors"
ethpb "github.com/prysmaticlabs/prysm/v3/proto/prysm/v1alpha1"
)
func (c beaconApiValidatorClient) proposeAttestation(ctx context.Context, attestation *ethpb.Attestation) (*ethpb.AttestResponse, error) {
if err := checkNilAttestation(attestation); err != nil {
return nil, err
}
marshalledAttestation, err := json.Marshal(jsonifyAttestations([]*ethpb.Attestation{attestation}))
if err != nil {
return nil, err
}
if _, err := c.jsonRestHandler.PostRestJson(ctx, "/eth/v1/beacon/pool/attestations", nil, bytes.NewBuffer(marshalledAttestation), nil); err != nil {
return nil, errors.Wrap(err, "failed to send POST data to REST endpoint")
}
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
}
// 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 {
return errors.New("attestation data is nil")
}
if attestation.Data.Source == nil || attestation.Data.Target == nil {
return errors.New("source/target in attestation data is nil")
}
if len(attestation.AggregationBits) == 0 {
return errors.New("attestation aggregation bits is empty")
}
if len(attestation.Signature) == 0 {
return errors.New("attestation signature is empty")
}
return nil
}

View File

@ -0,0 +1,156 @@
package beacon_api
import (
"bytes"
"context"
"encoding/json"
"errors"
"testing"
"github.com/golang/mock/gomock"
ethpb "github.com/prysmaticlabs/prysm/v3/proto/prysm/v1alpha1"
"github.com/prysmaticlabs/prysm/v3/testing/assert"
"github.com/prysmaticlabs/prysm/v3/testing/require"
"github.com/prysmaticlabs/prysm/v3/validator/client/beacon-api/mock"
test_helpers "github.com/prysmaticlabs/prysm/v3/validator/client/beacon-api/test-helpers"
)
func TestProposeAttestation(t *testing.T) {
attestation := &ethpb.Attestation{
AggregationBits: test_helpers.FillByteSlice(4, 74),
Data: &ethpb.AttestationData{
Slot: 75,
CommitteeIndex: 76,
BeaconBlockRoot: test_helpers.FillByteSlice(32, 38),
Source: &ethpb.Checkpoint{
Epoch: 78,
Root: test_helpers.FillByteSlice(32, 79),
},
Target: &ethpb.Checkpoint{
Epoch: 80,
Root: test_helpers.FillByteSlice(32, 81),
},
},
Signature: test_helpers.FillByteSlice(96, 82),
}
tests := []struct {
name string
attestation *ethpb.Attestation
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.Attestation{
AggregationBits: test_helpers.FillByteSlice(4, 74),
Signature: test_helpers.FillByteSlice(96, 82),
},
expectedErrorMessage: "attestation data is nil",
},
{
name: "nil source checkpoint",
attestation: &ethpb.Attestation{
AggregationBits: test_helpers.FillByteSlice(4, 74),
Data: &ethpb.AttestationData{
Target: &ethpb.Checkpoint{},
},
Signature: test_helpers.FillByteSlice(96, 82),
},
expectedErrorMessage: "source/target in attestation data is nil",
},
{
name: "nil target checkpoint",
attestation: &ethpb.Attestation{
AggregationBits: test_helpers.FillByteSlice(4, 74),
Data: &ethpb.AttestationData{
Source: &ethpb.Checkpoint{},
},
Signature: test_helpers.FillByteSlice(96, 82),
},
expectedErrorMessage: "source/target in attestation data is nil",
},
{
name: "nil aggregation bits",
attestation: &ethpb.Attestation{
Data: &ethpb.AttestationData{
Source: &ethpb.Checkpoint{},
Target: &ethpb.Checkpoint{},
},
Signature: test_helpers.FillByteSlice(96, 82),
},
expectedErrorMessage: "attestation aggregation bits is empty",
},
{
name: "nil signature",
attestation: &ethpb.Attestation{
AggregationBits: test_helpers.FillByteSlice(4, 74),
Data: &ethpb.AttestationData{
Source: &ethpb.Checkpoint{},
Target: &ethpb.Checkpoint{},
},
},
expectedErrorMessage: "attestation signature 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 checkNilAttestation(test.attestation) == nil {
b, err := json.Marshal(jsonifyAttestations([]*ethpb.Attestation{test.attestation}))
require.NoError(t, err)
marshalledAttestations = b
}
ctx := context.Background()
jsonRestHandler.EXPECT().PostRestJson(
ctx,
"/eth/v1/beacon/pool/attestations",
nil,
bytes.NewBuffer(marshalledAttestations),
nil,
).Return(
nil,
test.endpointError,
).Times(test.endpointCall)
validatorClient := &beaconApiValidatorClient{jsonRestHandler: jsonRestHandler}
proposeResponse, err := validatorClient.proposeAttestation(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)
})
}
}