From 2f60f09fc238b89abcb803128ef8704ffc01f329 Mon Sep 17 00:00:00 2001 From: Dhruv Bodani Date: Tue, 13 Sep 2022 12:53:58 +0530 Subject: [PATCH] core: add interfaces and signed data for attestation aggregation (#1121) Adds required interfaces to eth2wrap and signed data for SignedAggregateAndProof. category: feature ticket: #1094 --- app/eth2wrap/eth2wrap_gen.go | 41 +++++++++++++++++++++++ app/eth2wrap/genwrap/genwrap.go | 2 ++ core/signeddata.go | 55 +++++++++++++++++++++++++++++++ core/signeddata_test.go | 15 +++++++++ testutil/beaconmock/beaconmock.go | 10 ++++++ 5 files changed, 123 insertions(+) diff --git a/app/eth2wrap/eth2wrap_gen.go b/app/eth2wrap/eth2wrap_gen.go index 00173ac44..34ff64057 100644 --- a/app/eth2wrap/eth2wrap_gen.go +++ b/app/eth2wrap/eth2wrap_gen.go @@ -35,6 +35,8 @@ type Client interface { eth2client.Service eth2exp.BeaconCommitteeSubscriptionsSubmitterV2 + eth2client.AggregateAttestationProvider + eth2client.AggregateAttestationsSubmitter eth2client.AttestationDataProvider eth2client.AttestationsSubmitter eth2client.AttesterDutiesProvider @@ -182,6 +184,45 @@ func (m multi) BeaconCommitteesAtEpoch(ctx context.Context, stateID string, epoc return res0, err } +// AggregateAttestation fetches the aggregate attestation given an attestation. +func (m multi) AggregateAttestation(ctx context.Context, slot phase0.Slot, attestationDataRoot phase0.Root) (*phase0.Attestation, error) { + const label = "aggregate_attestation" + defer latency(label)() + + res0, err := provide(ctx, m.clients, + func(ctx context.Context, cl Client) (*phase0.Attestation, error) { + return cl.AggregateAttestation(ctx, slot, attestationDataRoot) + }, + nil, + ) + + if err != nil { + incError(label) + err = errors.Wrap(err, "eth2wrap") + } + + return res0, err +} + +// SubmitAggregateAttestations submits aggregate attestations. +func (m multi) SubmitAggregateAttestations(ctx context.Context, aggregateAndProofs []*phase0.SignedAggregateAndProof) error { + const label = "submit_aggregate_attestations" + defer latency(label)() + + err := submit(ctx, m.clients, + func(ctx context.Context, cl Client) error { + return cl.SubmitAggregateAttestations(ctx, aggregateAndProofs) + }, + ) + + if err != nil { + incError(label) + err = errors.Wrap(err, "eth2wrap") + } + + return err +} + // AttestationData fetches the attestation data for the given slot and committee index. func (m multi) AttestationData(ctx context.Context, slot phase0.Slot, committeeIndex phase0.CommitteeIndex) (*phase0.AttestationData, error) { const label = "attestation_data" diff --git a/app/eth2wrap/genwrap/genwrap.go b/app/eth2wrap/genwrap/genwrap.go index 944a77f6a..a00f39916 100644 --- a/app/eth2wrap/genwrap/genwrap.go +++ b/app/eth2wrap/genwrap/genwrap.go @@ -89,6 +89,8 @@ type Client interface { // interfaces defines all the interfaces to implement and whether to measure latency for each. interfaces = map[string]bool{ + "AggregateAttestationProvider": true, + "AggregateAttestationsSubmitter": true, "AttestationDataProvider": true, "AttestationsSubmitter": true, "AttesterDutiesProvider": true, diff --git a/core/signeddata.go b/core/signeddata.go index 460831f7d..980a4c3a9 100644 --- a/core/signeddata.go +++ b/core/signeddata.go @@ -747,6 +747,61 @@ func (s *SignedBeaconCommitteeSubscription) UnmarshalJSON(input []byte) error { return s.BeaconCommitteeSubscription.UnmarshalJSON(input) } +// NewSignedAggregateAndProof is a convenience function which returns a new signed SignedAggregateAndProof. +func NewSignedAggregateAndProof(data *eth2p0.SignedAggregateAndProof) SignedAggregateAndProof { + return SignedAggregateAndProof{SignedAggregateAndProof: *data} +} + +// NewPartialSignedAggregateAndProof is a convenience function which returns a new partially signed SignedAggregateAndProof. +func NewPartialSignedAggregateAndProof(data *eth2p0.SignedAggregateAndProof, shareIdx int) ParSignedData { + return ParSignedData{ + SignedData: NewSignedAggregateAndProof(data), + ShareIdx: shareIdx, + } +} + +// SignedAggregateAndProof wraps eth2p0.SignedAggregateAndProof and implements SignedData. +type SignedAggregateAndProof struct { + eth2p0.SignedAggregateAndProof +} + +func (s SignedAggregateAndProof) Signature() Signature { + return SigFromETH2(s.SignedAggregateAndProof.Signature) +} + +func (s SignedAggregateAndProof) SetSignature(sig Signature) (SignedData, error) { + resp, err := s.clone() + if err != nil { + return nil, err + } + + resp.SignedAggregateAndProof.Signature = sig.ToETH2() + + return resp, nil +} + +func (s SignedAggregateAndProof) Clone() (SignedData, error) { + return s.clone() +} + +func (s SignedAggregateAndProof) clone() (SignedAggregateAndProof, error) { + var resp SignedAggregateAndProof + err := cloneJSONMarshaler(s, &resp) + if err != nil { + return SignedAggregateAndProof{}, errors.Wrap(err, "clone signed aggregate and proof") + } + + return resp, nil +} + +func (s SignedAggregateAndProof) MarshalJSON() ([]byte, error) { + return s.SignedAggregateAndProof.MarshalJSON() +} + +func (s *SignedAggregateAndProof) UnmarshalJSON(input []byte) error { + return s.SignedAggregateAndProof.UnmarshalJSON(input) +} + // cloneJSONMarshaler clones the marshaler by serialising to-from json // since eth2 types contains pointers. The result is stored // in the value pointed to by v. diff --git a/core/signeddata_test.go b/core/signeddata_test.go index 05f4c0707..e9dd27eaf 100644 --- a/core/signeddata_test.go +++ b/core/signeddata_test.go @@ -22,6 +22,7 @@ import ( eth2v1 "github.com/attestantio/go-eth2-client/api/v1" "github.com/attestantio/go-eth2-client/spec" "github.com/attestantio/go-eth2-client/spec/bellatrix" + eth2p0 "github.com/attestantio/go-eth2-client/spec/phase0" "github.com/stretchr/testify/require" "github.com/obolnetwork/charon/core" @@ -72,6 +73,19 @@ func TestSignedDataSetSignature(t *testing.T) { }, }, }, + { + name: "signed aggregate and proof", + data: core.SignedAggregateAndProof{ + SignedAggregateAndProof: eth2p0.SignedAggregateAndProof{ + Message: ð2p0.AggregateAndProof{ + AggregatorIndex: 0, + Aggregate: testutil.RandomAttestation(), + SelectionProof: testutil.RandomEth2Signature(), + }, + Signature: testutil.RandomEth2Signature(), + }, + }, + }, } for _, test := range tests { @@ -79,6 +93,7 @@ func TestSignedDataSetSignature(t *testing.T) { clone, err := test.data.SetSignature(testutil.RandomCoreSignature()) require.NoError(t, err) require.NotEqual(t, clone.Signature(), test.data.Signature()) + require.NotEmpty(t, clone.Signature()) }) } } diff --git a/testutil/beaconmock/beaconmock.go b/testutil/beaconmock/beaconmock.go index 62f104834..2d522ba50 100644 --- a/testutil/beaconmock/beaconmock.go +++ b/testutil/beaconmock/beaconmock.go @@ -141,6 +141,8 @@ type Mock struct { SlotsPerEpochFunc func(context.Context) (uint64, error) SubmitBeaconCommitteeSubscriptionsV2Func func(context.Context, []*eth2exp.BeaconCommitteeSubscription) ([]*eth2exp.BeaconCommitteeSubscriptionResponse, error) SubmitBeaconCommitteeSubscriptionsFunc func(ctx context.Context, subscriptions []*eth2v1.BeaconCommitteeSubscription) error + AggregateAttestationFunc func(ctx context.Context, slot eth2p0.Slot, attestationDataRoot eth2p0.Root) (*eth2p0.Attestation, error) + SubmitAggregateAttestationsFunc func(ctx context.Context, aggregateAndProofs []*eth2p0.SignedAggregateAndProof) error } func (m Mock) SubmitAttestations(ctx context.Context, attestations []*eth2p0.Attestation) error { @@ -219,6 +221,14 @@ func (m Mock) SubmitBeaconCommitteeSubscriptions(ctx context.Context, subscripti return m.SubmitBeaconCommitteeSubscriptionsFunc(ctx, subscriptions) } +func (m Mock) AggregateAttestation(ctx context.Context, slot eth2p0.Slot, attestationDataRoot eth2p0.Root) (*eth2p0.Attestation, error) { + return m.AggregateAttestationFunc(ctx, slot, attestationDataRoot) +} + +func (m Mock) SubmitAggregateAttestations(ctx context.Context, aggregateAndProofs []*eth2p0.SignedAggregateAndProof) error { + return m.SubmitAggregateAttestationsFunc(ctx, aggregateAndProofs) +} + func (m Mock) SlotsPerEpoch(ctx context.Context) (uint64, error) { return m.SlotsPerEpochFunc(ctx) }