From 837cd4eb8f8215fd7c3589809cc10b8926e62af7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Kapka?= Date: Mon, 26 Jul 2021 21:22:58 +0200 Subject: [PATCH] Implement `ProduceAttestationData` in the validator API (#9271) * functionality * test * build file Co-authored-by: Raul Jordan --- .../rpc/apimiddleware/endpoint_factory.go | 8 ++ beacon-chain/rpc/apimiddleware/structs.go | 5 ++ beacon-chain/rpc/eth/v1/validator/BUILD.bazel | 3 + .../rpc/eth/v1/validator/validator.go | 16 +++- .../rpc/eth/v1/validator/validator_test.go | 82 ++++++++++++++++++- 5 files changed, 112 insertions(+), 2 deletions(-) diff --git a/beacon-chain/rpc/apimiddleware/endpoint_factory.go b/beacon-chain/rpc/apimiddleware/endpoint_factory.go index f5e52309c..8c668147a 100644 --- a/beacon-chain/rpc/apimiddleware/endpoint_factory.go +++ b/beacon-chain/rpc/apimiddleware/endpoint_factory.go @@ -50,6 +50,7 @@ func (f *BeaconEndpointFactory) Paths() []string { "/eth/v1/validator/duties/attester/{epoch}", "/eth/v1/validator/duties/proposer/{epoch}", "/eth/v1/validator/blocks/{slot}", + "/eth/v1/validator/attestation_data", "/eth/v1/validator/aggregate_attestation", } } @@ -258,6 +259,13 @@ func (f *BeaconEndpointFactory) Create(path string) (*gateway.Endpoint, error) { GetResponse: &produceBlockResponseJson{}, RequestURLLiterals: []string{"slot"}, RequestQueryParams: []gateway.QueryParam{{Name: "randao_reveal", Hex: true}, {Name: "graffiti", Hex: true}}, + Err: &gateway.DefaultErrorJson{}, + } + case "/eth/v1/validator/attestation_data": + endpoint = gateway.Endpoint{ + GetResponse: &produceAttestationDataResponseJson{}, + RequestQueryParams: []gateway.QueryParam{{Name: "slot"}, {Name: "committee_index"}}, + Err: &gateway.DefaultErrorJson{}, } case "/eth/v1/validator/aggregate_attestation": endpoint = gateway.Endpoint{ diff --git a/beacon-chain/rpc/apimiddleware/structs.go b/beacon-chain/rpc/apimiddleware/structs.go index 72b0097d7..35ce456e1 100644 --- a/beacon-chain/rpc/apimiddleware/structs.go +++ b/beacon-chain/rpc/apimiddleware/structs.go @@ -196,6 +196,11 @@ type produceBlockResponseJson struct { Data *beaconBlockJson `json:"data"` } +// produceAttestationDataResponseJson is used in /validator/attestation_data API endpoint. +type produceAttestationDataResponseJson struct { + Data *attestationDataJson `json:"data"` +} + // aggregateAttestationResponseJson is used in /validator/aggregate_attestation API endpoint. type aggregateAttestationResponseJson struct { Data *attestationJson `json:"data"` diff --git a/beacon-chain/rpc/eth/v1/validator/BUILD.bazel b/beacon-chain/rpc/eth/v1/validator/BUILD.bazel index 4a4a43993..4807040ac 100644 --- a/beacon-chain/rpc/eth/v1/validator/BUILD.bazel +++ b/beacon-chain/rpc/eth/v1/validator/BUILD.bazel @@ -35,6 +35,7 @@ go_test( embed = [":go_default_library"], deps = [ "//beacon-chain/blockchain/testing:go_default_library", + "//beacon-chain/cache:go_default_library", "//beacon-chain/core/blocks:go_default_library", "//beacon-chain/core/helpers:go_default_library", "//beacon-chain/core/state:go_default_library", @@ -42,6 +43,7 @@ go_test( "//beacon-chain/operations/attestations:go_default_library", "//beacon-chain/operations/slashings:go_default_library", "//beacon-chain/operations/voluntaryexits:go_default_library", + "//beacon-chain/p2p/testing:go_default_library", "//beacon-chain/powchain/testing:go_default_library", "//beacon-chain/rpc/prysm/v1alpha1/validator:go_default_library", "//beacon-chain/state/stategen:go_default_library", @@ -57,5 +59,6 @@ go_test( "//shared/testutil/require:go_default_library", "@com_github_prysmaticlabs_eth2_types//:go_default_library", "@com_github_prysmaticlabs_go_bitfield//:go_default_library", + "@org_golang_google_protobuf//proto:go_default_library", ], ) diff --git a/beacon-chain/rpc/eth/v1/validator/validator.go b/beacon-chain/rpc/eth/v1/validator/validator.go index 2eeed8e57..18dd94c2a 100644 --- a/beacon-chain/rpc/eth/v1/validator/validator.go +++ b/beacon-chain/rpc/eth/v1/validator/validator.go @@ -184,7 +184,21 @@ func (vs *Server) ProduceBlock(ctx context.Context, req *v1.ProduceBlockRequest) // ProduceAttestationData requests that the beacon node produces attestation data for // the requested committee index and slot based on the nodes current head. func (vs *Server) ProduceAttestationData(ctx context.Context, req *v1.ProduceAttestationDataRequest) (*v1.ProduceAttestationDataResponse, error) { - return nil, errors.New("Unimplemented") + ctx, span := trace.StartSpan(ctx, "validatorv1.ProduceAttestationData") + defer span.End() + + v1alpha1req := &v1alpha1.AttestationDataRequest{ + Slot: req.Slot, + CommitteeIndex: req.CommitteeIndex, + } + v1alpha1resp, err := vs.V1Alpha1Server.GetAttestationData(ctx, v1alpha1req) + if err != nil { + // We simply return err because it's already of a gRPC error type. + return nil, err + } + attData := migration.V1Alpha1AttDataToV1(v1alpha1resp) + + return &v1.ProduceAttestationDataResponse{Data: attData}, nil } // GetAggregateAttestation aggregates all attestations matching the given attestation data root and slot, returning the aggregated result. diff --git a/beacon-chain/rpc/eth/v1/validator/validator_test.go b/beacon-chain/rpc/eth/v1/validator/validator_test.go index 3ccbdda53..2f540d31c 100644 --- a/beacon-chain/rpc/eth/v1/validator/validator_test.go +++ b/beacon-chain/rpc/eth/v1/validator/validator_test.go @@ -4,10 +4,12 @@ import ( "context" "fmt" "testing" + "time" types "github.com/prysmaticlabs/eth2-types" "github.com/prysmaticlabs/go-bitfield" mockChain "github.com/prysmaticlabs/prysm/beacon-chain/blockchain/testing" + "github.com/prysmaticlabs/prysm/beacon-chain/cache" b "github.com/prysmaticlabs/prysm/beacon-chain/core/blocks" "github.com/prysmaticlabs/prysm/beacon-chain/core/helpers" "github.com/prysmaticlabs/prysm/beacon-chain/core/state" @@ -15,6 +17,7 @@ import ( "github.com/prysmaticlabs/prysm/beacon-chain/operations/attestations" "github.com/prysmaticlabs/prysm/beacon-chain/operations/slashings" "github.com/prysmaticlabs/prysm/beacon-chain/operations/voluntaryexits" + mockp2p "github.com/prysmaticlabs/prysm/beacon-chain/p2p/testing" mockPOW "github.com/prysmaticlabs/prysm/beacon-chain/powchain/testing" v1alpha1validator "github.com/prysmaticlabs/prysm/beacon-chain/rpc/prysm/v1alpha1/validator" "github.com/prysmaticlabs/prysm/beacon-chain/state/stategen" @@ -28,6 +31,7 @@ import ( "github.com/prysmaticlabs/prysm/shared/testutil" "github.com/prysmaticlabs/prysm/shared/testutil/assert" "github.com/prysmaticlabs/prysm/shared/testutil/require" + "google.golang.org/protobuf/proto" ) func TestGetAttesterDuties(t *testing.T) { @@ -314,7 +318,7 @@ func TestGetProposerDuties_SyncNotReady(t *testing.T) { assert.ErrorContains(t, "Syncing to latest head, not ready to respond", err) } -func TestGetBlock(t *testing.T) { +func TestProduceBlock(t *testing.T) { db := dbutil.SetupDB(t) ctx := context.Background() @@ -405,6 +409,82 @@ func TestGetBlock(t *testing.T) { assert.DeepEqual(t, expectedAttSlashings, resp.Data.Body.AttesterSlashings) } +func TestProduceAttestationData(t *testing.T) { + block := testutil.NewBeaconBlock() + block.Block.Slot = 3*params.BeaconConfig().SlotsPerEpoch + 1 + targetBlock := testutil.NewBeaconBlock() + targetBlock.Block.Slot = 1 * params.BeaconConfig().SlotsPerEpoch + justifiedBlock := testutil.NewBeaconBlock() + justifiedBlock.Block.Slot = 2 * params.BeaconConfig().SlotsPerEpoch + blockRoot, err := block.Block.HashTreeRoot() + require.NoError(t, err, "Could not hash beacon block") + justifiedRoot, err := justifiedBlock.Block.HashTreeRoot() + require.NoError(t, err, "Could not get signing root for justified block") + targetRoot, err := targetBlock.Block.HashTreeRoot() + require.NoError(t, err, "Could not get signing root for target block") + slot := 3*params.BeaconConfig().SlotsPerEpoch + 1 + beaconState, err := testutil.NewBeaconState() + require.NoError(t, err) + require.NoError(t, beaconState.SetSlot(slot)) + err = beaconState.SetCurrentJustifiedCheckpoint(ðpb.Checkpoint{ + Epoch: 2, + Root: justifiedRoot[:], + }) + require.NoError(t, err) + + blockRoots := beaconState.BlockRoots() + blockRoots[1] = blockRoot[:] + blockRoots[1*params.BeaconConfig().SlotsPerEpoch] = targetRoot[:] + blockRoots[2*params.BeaconConfig().SlotsPerEpoch] = justifiedRoot[:] + require.NoError(t, beaconState.SetBlockRoots(blockRoots)) + chainService := &mockChain.ChainService{ + Genesis: time.Now(), + } + offset := int64(slot.Mul(params.BeaconConfig().SecondsPerSlot)) + v1Alpha1Server := &v1alpha1validator.Server{ + P2P: &mockp2p.MockBroadcaster{}, + SyncChecker: &mockSync.Sync{IsSyncing: false}, + AttestationCache: cache.NewAttestationCache(), + HeadFetcher: &mockChain.ChainService{ + State: beaconState, Root: blockRoot[:], + }, + FinalizationFetcher: &mockChain.ChainService{ + CurrentJustifiedCheckPoint: beaconState.CurrentJustifiedCheckpoint(), + }, + TimeFetcher: &mockChain.ChainService{ + Genesis: time.Now().Add(time.Duration(-1*offset) * time.Second), + }, + StateNotifier: chainService.StateNotifier(), + } + v1Server := &Server{ + V1Alpha1Server: v1Alpha1Server, + } + + req := &v1.ProduceAttestationDataRequest{ + CommitteeIndex: 0, + Slot: 3*params.BeaconConfig().SlotsPerEpoch + 1, + } + res, err := v1Server.ProduceAttestationData(context.Background(), req) + require.NoError(t, err, "Could not get attestation info at slot") + + expectedInfo := &v1.AttestationData{ + Slot: 3*params.BeaconConfig().SlotsPerEpoch + 1, + BeaconBlockRoot: blockRoot[:], + Source: &v1.Checkpoint{ + Epoch: 2, + Root: justifiedRoot[:], + }, + Target: &v1.Checkpoint{ + Epoch: 3, + Root: blockRoot[:], + }, + } + + if !proto.Equal(res.Data, expectedInfo) { + t.Errorf("Expected attestation info to match, received %v, wanted %v", res, expectedInfo) + } +} + func TestGetAggregateAttestation(t *testing.T) { ctx := context.Background() root1 := bytesutil.PadTo([]byte("root1"), 32)