From 8ee3e182c47e7c2d800da746389a821937621897 Mon Sep 17 00:00:00 2001 From: Kaloyan Tanev Date: Wed, 22 Jan 2025 18:19:47 +0200 Subject: [PATCH 1/3] Add electra versioned aggregations --- app/eth2wrap/eth2wrap_gen.go | 16 +- app/eth2wrap/mocks/client.go | 14 +- app/eth2wrap/success.go | 4 +- core/bcast/bcast.go | 11 +- core/bcast/bcast_test.go | 8 +- core/dutydb/memory.go | 26 +- core/dutydb/memory_test.go | 16 +- core/eth2signeddata.go | 13 +- core/eth2signeddata_test.go | 10 +- core/fetcher/fetcher.go | 10 +- core/fetcher/fetcher_test.go | 38 ++- core/interfaces.go | 9 +- core/parsigex/parsigex_test.go | 19 +- core/proto.go | 2 +- core/proto_test.go | 18 +- core/serialise_test.go | 4 +- core/signeddata.go | 350 ++++++++++++++++++++-- core/signeddata_test.go | 17 +- core/ssz.go | 190 ++++++++++++ core/ssz_test.go | 8 +- core/tracker/inclusion.go | 6 +- core/tracker/inclusion_internal_test.go | 7 +- core/unsigneddata.go | 132 ++++++-- core/unsigneddata_test.go | 2 +- core/validatorapi/router.go | 4 +- core/validatorapi/router_internal_test.go | 25 +- core/validatorapi/validatorapi.go | 21 +- core/validatorapi/validatorapi_test.go | 31 +- eth2util/signing/signing.go | 15 +- go.mod | 2 +- go.sum | 4 +- testutil/beaconmock/beaconmock.go | 8 +- testutil/beaconmock/beaconmock_fuzz.go | 4 +- testutil/beaconmock/options.go | 13 +- testutil/random.go | 24 +- testutil/validatormock/attest.go | 29 +- testutil/validatormock/propose_test.go | 8 +- 37 files changed, 877 insertions(+), 241 deletions(-) diff --git a/app/eth2wrap/eth2wrap_gen.go b/app/eth2wrap/eth2wrap_gen.go index 739f49fc9..d872aec6b 100644 --- a/app/eth2wrap/eth2wrap_gen.go +++ b/app/eth2wrap/eth2wrap_gen.go @@ -172,12 +172,12 @@ func (m multi) SignedBeaconBlock(ctx context.Context, opts *api.SignedBeaconBloc } // AggregateAttestation fetches the aggregate attestation for the given options. -func (m multi) AggregateAttestation(ctx context.Context, opts *api.AggregateAttestationOpts) (*api.Response[*phase0.Attestation], error) { +func (m multi) AggregateAttestation(ctx context.Context, opts *api.AggregateAttestationOpts) (*api.Response[*spec.VersionedAttestation], error) { const label = "aggregate_attestation" defer latency(ctx, label, false)() res0, err := provide(ctx, m.clients, m.fallback, - func(ctx context.Context, args provideArgs) (*api.Response[*phase0.Attestation], error) { + func(ctx context.Context, args provideArgs) (*api.Response[*spec.VersionedAttestation], error) { res0, err := args.client.AggregateAttestation(ctx, opts) if err != nil { // use a fallback BN if any @@ -206,13 +206,13 @@ func (m multi) AggregateAttestation(ctx context.Context, opts *api.AggregateAtte } // SubmitAggregateAttestations submits aggregate attestations. -func (m multi) SubmitAggregateAttestations(ctx context.Context, aggregateAndProofs []*phase0.SignedAggregateAndProof) error { +func (m multi) SubmitAggregateAttestations(ctx context.Context, opts *api.SubmitAggregateAttestationsOpts) error { const label = "submit_aggregate_attestations" defer latency(ctx, label, false)() err := submit(ctx, m.clients, m.fallback, func(ctx context.Context, args provideArgs) error { - err := args.client.SubmitAggregateAttestations(ctx, aggregateAndProofs) + err := args.client.SubmitAggregateAttestations(ctx, opts) if err != nil { // use a fallback BN if any fe, fallbackErr := args.fallback.pick() @@ -223,7 +223,7 @@ func (m multi) SubmitAggregateAttestations(ctx context.Context, aggregateAndProo defer args.fallback.place() - return fe.SubmitAggregateAttestations(ctx, aggregateAndProofs) + return fe.SubmitAggregateAttestations(ctx, opts) } return err @@ -1231,7 +1231,7 @@ func (l *lazy) SignedBeaconBlock(ctx context.Context, opts *api.SignedBeaconBloc } // AggregateAttestation fetches the aggregate attestation for the given options. -func (l *lazy) AggregateAttestation(ctx context.Context, opts *api.AggregateAttestationOpts) (res0 *api.Response[*phase0.Attestation], err error) { +func (l *lazy) AggregateAttestation(ctx context.Context, opts *api.AggregateAttestationOpts) (res0 *api.Response[*spec.VersionedAttestation], err error) { cl, err := l.getOrCreateClient(ctx) if err != nil { return res0, err @@ -1241,13 +1241,13 @@ func (l *lazy) AggregateAttestation(ctx context.Context, opts *api.AggregateAtte } // SubmitAggregateAttestations submits aggregate attestations. -func (l *lazy) SubmitAggregateAttestations(ctx context.Context, aggregateAndProofs []*phase0.SignedAggregateAndProof) (err error) { +func (l *lazy) SubmitAggregateAttestations(ctx context.Context, opts *api.SubmitAggregateAttestationsOpts) (err error) { cl, err := l.getOrCreateClient(ctx) if err != nil { return err } - return cl.SubmitAggregateAttestations(ctx, aggregateAndProofs) + return cl.SubmitAggregateAttestations(ctx, opts) } // AttestationData fetches the attestation data for the given options. diff --git a/app/eth2wrap/mocks/client.go b/app/eth2wrap/mocks/client.go index 2317908fe..f716ca26d 100644 --- a/app/eth2wrap/mocks/client.go +++ b/app/eth2wrap/mocks/client.go @@ -79,23 +79,23 @@ func (_m *Client) Address() string { } // AggregateAttestation provides a mock function with given fields: ctx, opts -func (_m *Client) AggregateAttestation(ctx context.Context, opts *api.AggregateAttestationOpts) (*api.Response[*phase0.Attestation], error) { +func (_m *Client) AggregateAttestation(ctx context.Context, opts *api.AggregateAttestationOpts) (*api.Response[*spec.VersionedAttestation], error) { ret := _m.Called(ctx, opts) if len(ret) == 0 { panic("no return value specified for AggregateAttestation") } - var r0 *api.Response[*phase0.Attestation] + var r0 *api.Response[*spec.VersionedAttestation] var r1 error - if rf, ok := ret.Get(0).(func(context.Context, *api.AggregateAttestationOpts) (*api.Response[*phase0.Attestation], error)); ok { + if rf, ok := ret.Get(0).(func(context.Context, *api.AggregateAttestationOpts) (*api.Response[*spec.VersionedAttestation], error)); ok { return rf(ctx, opts) } - if rf, ok := ret.Get(0).(func(context.Context, *api.AggregateAttestationOpts) *api.Response[*phase0.Attestation]); ok { + if rf, ok := ret.Get(0).(func(context.Context, *api.AggregateAttestationOpts) *api.Response[*spec.VersionedAttestation]); ok { r0 = rf(ctx, opts) } else { if ret.Get(0) != nil { - r0 = ret.Get(0).(*api.Response[*phase0.Attestation]) + r0 = ret.Get(0).(*api.Response[*spec.VersionedAttestation]) } } @@ -916,7 +916,7 @@ func (_m *Client) Spec(ctx context.Context, opts *api.SpecOpts) (*api.Response[m } // SubmitAggregateAttestations provides a mock function with given fields: ctx, aggregateAndProofs -func (_m *Client) SubmitAggregateAttestations(ctx context.Context, aggregateAndProofs []*phase0.SignedAggregateAndProof) error { +func (_m *Client) SubmitAggregateAttestations(ctx context.Context, aggregateAndProofs *api.SubmitAggregateAttestationsOpts) error { ret := _m.Called(ctx, aggregateAndProofs) if len(ret) == 0 { @@ -924,7 +924,7 @@ func (_m *Client) SubmitAggregateAttestations(ctx context.Context, aggregateAndP } var r0 error - if rf, ok := ret.Get(0).(func(context.Context, []*phase0.SignedAggregateAndProof) error); ok { + if rf, ok := ret.Get(0).(func(context.Context, *api.SubmitAggregateAttestationsOpts) error); ok { r0 = rf(ctx, aggregateAndProofs) } else { r0 = ret.Error(0) diff --git a/app/eth2wrap/success.go b/app/eth2wrap/success.go index 2144af89d..c9d649fb1 100644 --- a/app/eth2wrap/success.go +++ b/app/eth2wrap/success.go @@ -5,7 +5,7 @@ package eth2wrap import ( eth2api "github.com/attestantio/go-eth2-client/api" eth2v1 "github.com/attestantio/go-eth2-client/api/v1" - eth2p0 "github.com/attestantio/go-eth2-client/spec/phase0" + eth2spec "github.com/attestantio/go-eth2-client/spec" ) // isSyncStateOk returns true if the sync state is not syncing. @@ -14,6 +14,6 @@ func isSyncStateOk(resp *eth2api.Response[*eth2v1.SyncState]) bool { } // isAggregateAttestationOk returns true if the aggregate attestation is not nil (which can happen if the subscription wasn't successful). -func isAggregateAttestationOk(resp *eth2api.Response[*eth2p0.Attestation]) bool { +func isAggregateAttestationOk(resp *eth2api.Response[*eth2spec.VersionedAttestation]) bool { return resp.Data != nil } diff --git a/core/bcast/bcast.go b/core/bcast/bcast.go index c24862c2b..9d208c221 100644 --- a/core/bcast/bcast.go +++ b/core/bcast/bcast.go @@ -12,7 +12,6 @@ import ( eth2api "github.com/attestantio/go-eth2-client/api" eth2spec "github.com/attestantio/go-eth2-client/spec" "github.com/attestantio/go-eth2-client/spec/altair" - eth2p0 "github.com/attestantio/go-eth2-client/spec/phase0" "github.com/obolnetwork/charon/app/errors" "github.com/obolnetwork/charon/app/eth2wrap" @@ -240,18 +239,18 @@ func setToSyncMessages(set core.SignedDataSet) ([]*altair.SyncCommitteeMessage, } // setToAggAndProof converts a set of signed data into a list of aggregate and proofs. -func setToAggAndProof(set core.SignedDataSet) ([]*eth2p0.SignedAggregateAndProof, error) { - var resp []*eth2p0.SignedAggregateAndProof +func setToAggAndProof(set core.SignedDataSet) (*eth2api.SubmitAggregateAttestationsOpts, error) { + var resp []*eth2spec.VersionedSignedAggregateAndProof for _, aggAndProof := range set { - aggAndProof, ok := aggAndProof.(core.SignedAggregateAndProof) + aggAndProof, ok := aggAndProof.(core.VersionedSignedAggregateAndProof) if !ok { return nil, errors.New("invalid aggregate and proof") } - resp = append(resp, &aggAndProof.SignedAggregateAndProof) + resp = append(resp, &aggAndProof.VersionedSignedAggregateAndProof) } - return resp, nil + return ð2api.SubmitAggregateAttestationsOpts{SignedAggregateAndProofs: resp}, nil } // setToRegistrations converts a set of signed data into a list of registrations. diff --git a/core/bcast/bcast_test.go b/core/bcast/bcast_test.go index d16bb231b..6d0e84240 100644 --- a/core/bcast/bcast_test.go +++ b/core/bcast/bcast_test.go @@ -238,10 +238,12 @@ func aggregateAttestationData(t *testing.T, mock *beaconmock.Mock) test { asserted := make(chan struct{}) aggAndProof := testutil.RandomSignedAggregateAndProof() - aggData := core.SignedAggregateAndProof{SignedAggregateAndProof: *aggAndProof} + aggData := core.VersionedSignedAggregateAndProof{ + VersionedSignedAggregateAndProof: *aggAndProof, + } - mock.SubmitAggregateAttestationsFunc = func(ctx context.Context, aggregateAndProofs []*eth2p0.SignedAggregateAndProof) error { - require.Equal(t, aggAndProof, aggregateAndProofs[0]) + mock.SubmitAggregateAttestationsFunc = func(ctx context.Context, aggregateAndProofs *eth2api.SubmitAggregateAttestationsOpts) error { + require.Equal(t, aggAndProof, aggregateAndProofs.SignedAggregateAndProofs[0]) close(asserted) return nil diff --git a/core/dutydb/memory.go b/core/dutydb/memory.go index cf74dbe9f..0dea7e99b 100644 --- a/core/dutydb/memory.go +++ b/core/dutydb/memory.go @@ -7,6 +7,7 @@ import ( "sync" eth2api "github.com/attestantio/go-eth2-client/api" + eth2spec "github.com/attestantio/go-eth2-client/spec" "github.com/attestantio/go-eth2-client/spec/altair" eth2p0 "github.com/attestantio/go-eth2-client/spec/phase0" @@ -22,7 +23,7 @@ func NewMemDB(deadliner core.Deadliner) *MemDB { attPubKeys: make(map[pkKey]core.PubKey), attKeysBySlot: make(map[uint64][]pkKey), proDuties: make(map[uint64]*eth2api.VersionedProposal), - aggDuties: make(map[aggKey]core.AggregatedAttestation), + aggDuties: make(map[aggKey]core.VersionedAggregatedAttestation), aggKeysBySlot: make(map[uint64][]aggKey), contribDuties: make(map[contribKey]*altair.SyncCommitteeContribution), contribKeysBySlot: make(map[uint64][]contribKey), @@ -47,7 +48,7 @@ type MemDB struct { proQueries []proQuery // DutyAggregator - aggDuties map[aggKey]core.AggregatedAttestation + aggDuties map[aggKey]core.VersionedAggregatedAttestation aggKeysBySlot map[uint64][]aggKey aggQueries []aggQuery @@ -195,10 +196,10 @@ func (db *MemDB) AwaitAttestation(ctx context.Context, slot uint64, commIdx uint // AwaitAggAttestation blocks and returns the aggregated attestation for the slot // and attestation when available. func (db *MemDB) AwaitAggAttestation(ctx context.Context, slot uint64, attestationRoot eth2p0.Root, -) (*eth2p0.Attestation, error) { +) (*eth2spec.VersionedAttestation, error) { cancel := make(chan struct{}) defer close(cancel) - response := make(chan core.AggregatedAttestation, 1) // Instance of one so resolving never blocks + response := make(chan core.VersionedAggregatedAttestation, 1) // Instance of one so resolving never blocks db.mu.Lock() db.aggQueries = append(db.aggQueries, aggQuery{ @@ -223,12 +224,12 @@ func (db *MemDB) AwaitAggAttestation(ctx context.Context, slot uint64, attestati if err != nil { return nil, err } - aggAtt, ok := clone.(core.AggregatedAttestation) + aggAtt, ok := clone.(core.VersionedAggregatedAttestation) if !ok { return nil, errors.New("invalid aggregated attestation") } - return &aggAtt.Attestation, nil + return &aggAtt.VersionedAttestation, nil } } @@ -332,17 +333,22 @@ func (db *MemDB) storeAggAttestationUnsafe(unsignedData core.UnsignedData) error return err } - aggAtt, ok := cloned.(core.AggregatedAttestation) + aggAtt, ok := cloned.(core.VersionedAggregatedAttestation) if !ok { return errors.New("invalid unsigned aggregated attestation") } - aggRoot, err := aggAtt.Attestation.Data.HashTreeRoot() + aggAttData, err := aggAtt.VersionedAttestation.Data() + if err != nil { + return err + } + aggAttData.HashTreeRoot() + aggRoot, err := aggAttData.HashTreeRoot() if err != nil { return errors.Wrap(err, "hash aggregated attestation root") } - slot := uint64(aggAtt.Attestation.Data.Slot) + slot := uint64(aggAttData.Slot) // Store key and value for PubKeyByAttestation key := aggKey{ @@ -606,7 +612,7 @@ type proQuery struct { // aggQuery is a waiting aggQuery with a response channel. type aggQuery struct { Key aggKey - Response chan<- core.AggregatedAttestation + Response chan<- core.VersionedAggregatedAttestation Cancel <-chan struct{} } diff --git a/core/dutydb/memory_test.go b/core/dutydb/memory_test.go index 8009e3e46..cbd96f802 100644 --- a/core/dutydb/memory_test.go +++ b/core/dutydb/memory_test.go @@ -220,11 +220,15 @@ func TestMemDBAggregator(t *testing.T) { const queries = 3 for range queries { - agg := testutil.RandomAttestation() + att := testutil.RandomDenebVersionedAttestation() + aggAttest, err := core.NewVersionedAggregatedAttestation(att) + require.NoError(t, err) set := core.UnsignedDataSet{ - testutil.RandomCorePubKey(t): core.NewAggregatedAttestation(agg), + testutil.RandomCorePubKey(t): aggAttest, } - slot := uint64(agg.Data.Slot) + attData, err := att.Data() + require.NoError(t, err) + slot := uint64(attData.Slot) errCh := make(chan error, 1) go func() { @@ -232,13 +236,13 @@ func TestMemDBAggregator(t *testing.T) { errCh <- err }() - root, err := agg.Data.HashTreeRoot() + root, err := attData.HashTreeRoot() require.NoError(t, err) err = <-errCh require.NoError(t, err) resp, err := db.AwaitAggAttestation(ctx, slot, root) require.NoError(t, err) - require.Equal(t, agg, resp) + require.Equal(t, att, resp) } } @@ -333,7 +337,7 @@ func TestMemDBSyncContribution(t *testing.T) { ) err := db.Store(ctx, duty, core.UnsignedDataSet{ - testutil.RandomCorePubKey(t): core.NewAggregatedAttestation(testutil.RandomAttestation()), + testutil.RandomCorePubKey(t): testutil.RandomDenebCoreVersionedAggregateAttestation(), }) require.Error(t, err) require.ErrorContains(t, err, "invalid unsigned sync committee contribution") diff --git a/core/eth2signeddata.go b/core/eth2signeddata.go index c40c86347..969eee9a8 100644 --- a/core/eth2signeddata.go +++ b/core/eth2signeddata.go @@ -21,7 +21,7 @@ var ( _ Eth2SignedData = VersionedSignedValidatorRegistration{} _ Eth2SignedData = SignedRandao{} _ Eth2SignedData = BeaconCommitteeSelection{} - _ Eth2SignedData = SignedAggregateAndProof{} + _ Eth2SignedData = VersionedSignedAggregateAndProof{} _ Eth2SignedData = SignedSyncMessage{} _ Eth2SignedData = SignedSyncContributionAndProof{} _ Eth2SignedData = SyncCommitteeSelection{} @@ -115,12 +115,17 @@ func (s BeaconCommitteeSelection) Epoch(ctx context.Context, eth2Cl eth2wrap.Cli // Implement Eth2SignedData for SignedAggregateAndProof. -func (SignedAggregateAndProof) DomainName() signing.DomainName { +func (VersionedSignedAggregateAndProof) DomainName() signing.DomainName { return signing.DomainAggregateAndProof } -func (s SignedAggregateAndProof) Epoch(ctx context.Context, eth2Cl eth2wrap.Client) (eth2p0.Epoch, error) { - return eth2util.EpochFromSlot(ctx, eth2Cl, s.Message.Aggregate.Data.Slot) +func (s VersionedSignedAggregateAndProof) Epoch(ctx context.Context, eth2Cl eth2wrap.Client) (eth2p0.Epoch, error) { + slot, err := s.Slot() + if err != nil { + return 0, err + } + + return eth2util.EpochFromSlot(ctx, eth2Cl, slot) } // Implement Eth2SignedData for SignedSyncMessage. diff --git a/core/eth2signeddata_test.go b/core/eth2signeddata_test.go index ec1561e96..3521f1e7b 100644 --- a/core/eth2signeddata_test.go +++ b/core/eth2signeddata_test.go @@ -6,6 +6,7 @@ import ( "context" "testing" + eth2spec "github.com/attestantio/go-eth2-client/spec" eth2p0 "github.com/attestantio/go-eth2-client/spec/phase0" "github.com/stretchr/testify/require" @@ -48,9 +49,12 @@ func TestVerifyEth2SignedData(t *testing.T) { }, { name: "verify attestation aggregate and proof", - data: core.SignedAggregateAndProof{ - SignedAggregateAndProof: eth2p0.SignedAggregateAndProof{ - Message: testutil.RandomAggregateAndProof(), + data: core.VersionedSignedAggregateAndProof{ + VersionedSignedAggregateAndProof: eth2spec.VersionedSignedAggregateAndProof{ + Version: eth2spec.DataVersionDeneb, + Deneb: ð2p0.SignedAggregateAndProof{ + Message: testutil.RandomAggregateAndProof(), + }, }, }, }, diff --git a/core/fetcher/fetcher.go b/core/fetcher/fetcher.go index cf16225de..f6ab9bbb6 100644 --- a/core/fetcher/fetcher.go +++ b/core/fetcher/fetcher.go @@ -157,7 +157,7 @@ func (f *Fetcher) fetchAttesterData(ctx context.Context, slot uint64, defSet cor // fetchAggregatorData fetches the attestation aggregation data. func (f *Fetcher) fetchAggregatorData(ctx context.Context, slot uint64, defSet core.DutyDefinitionSet) (core.UnsignedDataSet, error) { // We may have multiple aggregators in the same committee, use the same aggregated attestation in that case. - aggAttByCommIdx := make(map[eth2p0.CommitteeIndex]*eth2p0.Attestation) + aggAttByCommIdx := make(map[eth2p0.CommitteeIndex]*eth2spec.VersionedAttestation) resp := make(core.UnsignedDataSet) for pubkey, dutyDef := range defSet { @@ -188,8 +188,8 @@ func (f *Fetcher) fetchAggregatorData(ctx context.Context, slot uint64, defSet c aggAtt, ok := aggAttByCommIdx[attDef.CommitteeIndex] if ok { - resp[pubkey] = core.AggregatedAttestation{ - Attestation: *aggAtt, + resp[pubkey] = core.VersionedAggregatedAttestation{ + VersionedAttestation: *aggAtt, } // Skips querying aggregate attestation for aggregators of same committee. @@ -226,8 +226,8 @@ func (f *Fetcher) fetchAggregatorData(ctx context.Context, slot uint64, defSet c aggAttByCommIdx[attDef.CommitteeIndex] = aggAtt - resp[pubkey] = core.AggregatedAttestation{ - Attestation: *aggAtt, + resp[pubkey] = core.VersionedAggregatedAttestation{ + VersionedAttestation: *aggAtt, } } diff --git a/core/fetcher/fetcher_test.go b/core/fetcher/fetcher_test.go index c60c8d910..7dede3e3d 100644 --- a/core/fetcher/fetcher_test.go +++ b/core/fetcher/fetcher_test.go @@ -105,24 +105,28 @@ func TestFetchAggregator(t *testing.T) { vIdxB: testutil.RandomCorePubKey(t), } - attA := testutil.RandomAttestation() - attB := testutil.RandomAttestation() - attByCommIdx := map[uint64]*eth2p0.Attestation{ - uint64(attA.Data.Index): attA, - uint64(attB.Data.Index): attB, + attA := testutil.RandomDenebVersionedAttestation() + attB := testutil.RandomDenebVersionedAttestation() + attAData, err := attA.Data() + require.NoError(t, err) + attBData, err := attB.Data() + require.NoError(t, err) + attByCommIdx := map[uint64]*eth2spec.VersionedAttestation{ + uint64(attAData.Index): attA, + uint64(attBData.Index): attB, } newDefSet := func(commLength uint64, sameCommitteeIndex bool) core.DutyDefinitionSet { dutyA := testutil.RandomAttestationDuty(t) dutyA.CommitteeLength = commLength - dutyA.CommitteeIndex = attA.Data.Index + dutyA.CommitteeIndex = attAData.Index dutyB := testutil.RandomAttestationDuty(t) dutyB.CommitteeLength = commLength - dutyB.CommitteeIndex = attB.Data.Index + dutyB.CommitteeIndex = attBData.Index if sameCommitteeIndex { - dutyB.CommitteeIndex = attA.Data.Index - attB.Data.Index = attA.Data.Index + dutyB.CommitteeIndex = attAData.Index + attBData.Index = attAData.Index } return map[core.PubKey]core.DutyDefinition{ @@ -140,13 +144,15 @@ func TestFetchAggregator(t *testing.T) { require.NoError(t, err) var aggAttCallCount int - bmock.AggregateAttestationFunc = func(ctx context.Context, slot eth2p0.Slot, root eth2p0.Root) (*eth2p0.Attestation, error) { + bmock.AggregateAttestationFunc = func(ctx context.Context, slot eth2p0.Slot, root eth2p0.Root) (*eth2spec.VersionedAttestation, error) { aggAttCallCount-- if nilAggregate { return nil, nil //nolint:nilnil // This reproduces what go-eth2-client does } for _, att := range attByCommIdx { - dataRoot, err := att.Data.HashTreeRoot() + attData, err := att.Data() + require.NoError(t, err) + dataRoot, err := attData.HashTreeRoot() require.NoError(t, err) if dataRoot == root { return att, nil @@ -164,7 +170,7 @@ func TestFetchAggregator(t *testing.T) { }) fetch.RegisterAwaitAttData(func(ctx context.Context, slot uint64, commIdx uint64) (*eth2p0.AttestationData, error) { - return attByCommIdx[commIdx].Data, nil + return attByCommIdx[commIdx].Data() }) done := errors.New("done") @@ -173,12 +179,14 @@ func TestFetchAggregator(t *testing.T) { require.Len(t, resDataSet, 2) for _, aggAtt := range resDataSet { - aggregated, ok := aggAtt.(core.AggregatedAttestation) + aggregated, ok := aggAtt.(core.VersionedAggregatedAttestation) require.True(t, ok) - att, ok := attByCommIdx[uint64(aggregated.Attestation.Data.Index)] + aggregatedData, err := aggregated.VersionedAttestation.Data() + require.NoError(t, err) + att, ok := attByCommIdx[uint64(aggregatedData.Index)] require.True(t, ok) - require.Equal(t, aggregated.Attestation, *att) + require.Equal(t, aggregated.VersionedAttestation, *att) } return done diff --git a/core/interfaces.go b/core/interfaces.go index 73a74a687..73af118a5 100644 --- a/core/interfaces.go +++ b/core/interfaces.go @@ -6,6 +6,7 @@ import ( "context" eth2api "github.com/attestantio/go-eth2-client/api" + eth2spec "github.com/attestantio/go-eth2-client/spec" "github.com/attestantio/go-eth2-client/spec/altair" eth2p0 "github.com/attestantio/go-eth2-client/spec/phase0" "github.com/libp2p/go-libp2p/core/protocol" @@ -61,7 +62,7 @@ type DutyDB interface { // AwaitAggAttestation blocks and returns the aggregated attestation for the slot // and attestation when available. - AwaitAggAttestation(ctx context.Context, slot uint64, attestationRoot eth2p0.Root) (*eth2p0.Attestation, error) + AwaitAggAttestation(ctx context.Context, slot uint64, attestationRoot eth2p0.Root) (*eth2spec.VersionedAttestation, error) // AwaitSyncContribution blocks and returns the sync committee contribution data for the slot and // the subcommittee and the beacon block root when available. @@ -132,7 +133,7 @@ type ValidatorAPI interface { RegisterGetDutyDefinition(func(context.Context, Duty) (DutyDefinitionSet, error)) // RegisterAwaitAggAttestation registers a function to query aggregated attestation. - RegisterAwaitAggAttestation(fn func(ctx context.Context, slot uint64, attestationDataRoot eth2p0.Root) (*eth2p0.Attestation, error)) + RegisterAwaitAggAttestation(fn func(ctx context.Context, slot uint64, attestationDataRoot eth2p0.Root) (*eth2spec.VersionedAttestation, error)) // RegisterAwaitAggSigDB registers a function to query aggregated signed data from aggSigDB. RegisterAwaitAggSigDB(func(context.Context, Duty, PubKey) (SignedData, error)) @@ -252,14 +253,14 @@ type wireFuncs struct { DutyDBAwaitProposal func(ctx context.Context, slot uint64) (*eth2api.VersionedProposal, error) DutyDBAwaitAttestation func(ctx context.Context, slot, commIdx uint64) (*eth2p0.AttestationData, error) DutyDBPubKeyByAttestation func(ctx context.Context, slot, commIdx, valCommIdx uint64) (PubKey, error) - DutyDBAwaitAggAttestation func(ctx context.Context, slot uint64, attestationRoot eth2p0.Root) (*eth2p0.Attestation, error) + DutyDBAwaitAggAttestation func(ctx context.Context, slot uint64, attestationRoot eth2p0.Root) (*eth2spec.VersionedAttestation, error) DutyDBAwaitSyncContribution func(ctx context.Context, slot, subcommIdx uint64, beaconBlockRoot eth2p0.Root) (*altair.SyncCommitteeContribution, error) VAPIRegisterAwaitAttestation func(func(ctx context.Context, slot, commIdx uint64) (*eth2p0.AttestationData, error)) VAPIRegisterAwaitSyncContribution func(func(ctx context.Context, slot, subcommIdx uint64, beaconBlockRoot eth2p0.Root) (*altair.SyncCommitteeContribution, error)) VAPIRegisterAwaitProposal func(func(ctx context.Context, slot uint64) (*eth2api.VersionedProposal, error)) VAPIRegisterGetDutyDefinition func(func(context.Context, Duty) (DutyDefinitionSet, error)) VAPIRegisterPubKeyByAttestation func(func(ctx context.Context, slot, commIdx, valCommIdx uint64) (PubKey, error)) - VAPIRegisterAwaitAggAttestation func(func(ctx context.Context, slot uint64, attestationRoot eth2p0.Root) (*eth2p0.Attestation, error)) + VAPIRegisterAwaitAggAttestation func(func(ctx context.Context, slot uint64, attestationRoot eth2p0.Root) (*eth2spec.VersionedAttestation, error)) VAPIRegisterAwaitAggSigDB func(func(context.Context, Duty, PubKey) (SignedData, error)) VAPISubscribe func(func(context.Context, Duty, ParSignedDataSet) error) ParSigDBStoreInternal func(context.Context, Duty, ParSignedDataSet) error diff --git a/core/parsigex/parsigex_test.go b/core/parsigex/parsigex_test.go index 5fd297e08..b2cc02a11 100644 --- a/core/parsigex/parsigex_test.go +++ b/core/parsigex/parsigex_test.go @@ -256,19 +256,22 @@ func TestParSigExVerifier(t *testing.T) { }) t.Run("Verify aggregate and proof", func(t *testing.T) { - agg := ð2p0.SignedAggregateAndProof{ - Message: ð2p0.AggregateAndProof{ - AggregatorIndex: 0, - Aggregate: testutil.RandomAttestation(), - SelectionProof: testutil.RandomEth2Signature(), + agg := ð2spec.VersionedSignedAggregateAndProof{ + Version: eth2spec.DataVersionDeneb, + Deneb: ð2p0.SignedAggregateAndProof{ + Message: ð2p0.AggregateAndProof{ + AggregatorIndex: 0, + Aggregate: testutil.RandomAttestation(), + SelectionProof: testutil.RandomEth2Signature(), + }, }, } - agg.Message.Aggregate.Data.Slot = slot - sigRoot, err := agg.Message.HashTreeRoot() + agg.Deneb.Message.Aggregate.Data.Slot = slot + sigRoot, err := agg.Deneb.Message.HashTreeRoot() require.NoError(t, err) sigData, err := signing.GetDataRoot(ctx, bmock, signing.DomainAggregateAndProof, epoch, sigRoot) require.NoError(t, err) - agg.Signature = sign(sigData[:]) + agg.Deneb.Signature = sign(sigData[:]) data := core.NewPartialSignedAggregateAndProof(agg, shareIdx) require.NoError(t, verifyFunc(ctx, core.NewAggregatorDuty(slot), pubkey, data)) diff --git a/core/proto.go b/core/proto.go index 3c19eda6b..c1831aeba 100644 --- a/core/proto.go +++ b/core/proto.go @@ -106,7 +106,7 @@ func ParSignedDataFromProto(typ DutyType, data *pbv1.ParSignedData) (_ ParSigned } signedData = s case DutyAggregator: - var s SignedAggregateAndProof + var s VersionedSignedAggregateAndProof if err := unmarshal(data.GetData(), &s); err != nil { return ParSignedData{}, errors.Wrap(err, "unmarshal signed aggregate and proof") } diff --git a/core/proto_test.go b/core/proto_test.go index 363e9149c..2eaecf79c 100644 --- a/core/proto_test.go +++ b/core/proto_test.go @@ -7,6 +7,7 @@ import ( "math/rand" "testing" + eth2spec "github.com/attestantio/go-eth2-client/spec" eth2p0 "github.com/attestantio/go-eth2-client/spec/phase0" "github.com/stretchr/testify/require" "google.golang.org/protobuf/proto" @@ -61,10 +62,15 @@ func TestParSignedDataSetProto(t *testing.T) { }, { Type: core.DutyAggregator, - Data: core.SignedAggregateAndProof{SignedAggregateAndProof: eth2p0.SignedAggregateAndProof{ - Message: testutil.RandomAggregateAndProof(), - Signature: testutil.RandomEth2Signature(), - }}, + Data: core.VersionedSignedAggregateAndProof{ + VersionedSignedAggregateAndProof: eth2spec.VersionedSignedAggregateAndProof{ + Version: eth2spec.DataVersionDeneb, + Deneb: ð2p0.SignedAggregateAndProof{ + Message: testutil.RandomAggregateAndProof(), + Signature: testutil.RandomEth2Signature(), + }, + }, + }, }, { Type: core.DutySyncMessage, @@ -122,7 +128,7 @@ func TestUnsignedDataToProto(t *testing.T) { }, { Type: core.DutyAggregator, - Data: core.NewAggregatedAttestation(testutil.RandomAttestation()), + Data: testutil.RandomDenebCoreVersionedAggregateAttestation(), }, { Type: core.DutySyncContribution, @@ -186,7 +192,7 @@ func TestParSignedData(t *testing.T) { func TestParSignedDataFromProtoErrors(t *testing.T) { parSig1 := core.ParSignedData{ - SignedData: core.SignedAggregateAndProof{*testutil.RandomSignedAggregateAndProof()}, + SignedData: core.VersionedSignedAggregateAndProof{*testutil.RandomSignedAggregateAndProof()}, ShareIdx: rand.Intn(100), } diff --git a/core/serialise_test.go b/core/serialise_test.go index 8e50894de..82169260e 100644 --- a/core/serialise_test.go +++ b/core/serialise_test.go @@ -27,13 +27,13 @@ var coreTypeFuncs = []func() any{ func() any { return new(core.SignedVoluntaryExit) }, func() any { return new(core.SignedRandao) }, func() any { return new(core.BeaconCommitteeSelection) }, - func() any { return new(core.SignedAggregateAndProof) }, + func() any { return new(core.VersionedSignedAggregateAndProof) }, func() any { return new(core.SignedSyncMessage) }, func() any { return new(core.SyncContributionAndProof) }, func() any { return new(core.SignedSyncContributionAndProof) }, func() any { return new(core.SyncCommitteeSelection) }, func() any { return new(core.AttestationData) }, - func() any { return new(core.AggregatedAttestation) }, + func() any { return new(core.VersionedAggregatedAttestation) }, func() any { return new(core.VersionedProposal) }, func() any { return new(core.SyncContribution) }, } diff --git a/core/signeddata.go b/core/signeddata.go index 6d9cf4662..d57bd9e3d 100644 --- a/core/signeddata.go +++ b/core/signeddata.go @@ -38,7 +38,7 @@ var ( _ SignedData = VersionedSignedValidatorRegistration{} _ SignedData = SignedRandao{} _ SignedData = BeaconCommitteeSelection{} - _ SignedData = SignedAggregateAndProof{} + _ SignedData = VersionedSignedAggregateAndProof{} _ SignedData = SignedSyncMessage{} _ SignedData = SyncContributionAndProof{} _ SignedData = SignedSyncContributionAndProof{} @@ -47,13 +47,13 @@ var ( // Some types support SSZ marshalling and unmarshalling. _ ssz.Marshaler = VersionedSignedProposal{} _ ssz.Marshaler = VersionedAttestation{} - _ ssz.Marshaler = SignedAggregateAndProof{} + _ ssz.Marshaler = VersionedSignedAggregateAndProof{} _ ssz.Marshaler = SignedSyncMessage{} _ ssz.Marshaler = SyncContributionAndProof{} _ ssz.Marshaler = SignedSyncContributionAndProof{} _ ssz.Unmarshaler = new(VersionedSignedProposal) _ ssz.Unmarshaler = new(VersionedAttestation) - _ ssz.Unmarshaler = new(SignedAggregateAndProof) + _ ssz.Unmarshaler = new(VersionedSignedAggregateAndProof) _ ssz.Unmarshaler = new(SignedSyncMessage) _ ssz.Unmarshaler = new(SyncContributionAndProof) _ ssz.Unmarshaler = new(SignedSyncContributionAndProof) @@ -538,6 +538,12 @@ type versionedRawAttestationJSON struct { Attestation json.RawMessage `json:"attestation"` } +// versionedRawAggregateAndProofJSON is a custom VersionedAttestation serialiser. +type versionedRawAggregateAndProofJSON struct { + Version eth2util.DataVersion `json:"version"` + AggregateAndProof json.RawMessage `json:"aggregate_and_proof"` +} + // NewVersionedAttestation is a convenience function that returns a new wrapped attestation. func NewVersionedAttestation(att *eth2spec.VersionedAttestation) (VersionedAttestation, error) { switch att.Version { @@ -1168,78 +1174,356 @@ func (s *SyncCommitteeSelection) UnmarshalJSON(input []byte) error { } // NewSignedAggregateAndProof is a convenience function which returns a new signed SignedAggregateAndProof. -func NewSignedAggregateAndProof(data *eth2p0.SignedAggregateAndProof) SignedAggregateAndProof { - return SignedAggregateAndProof{SignedAggregateAndProof: *data} +func NewSignedAggregateAndProof(data *eth2spec.VersionedSignedAggregateAndProof) VersionedSignedAggregateAndProof { + return VersionedSignedAggregateAndProof{VersionedSignedAggregateAndProof: *data} } // NewPartialSignedAggregateAndProof is a convenience function which returns a new partially signed SignedAggregateAndProof. -func NewPartialSignedAggregateAndProof(data *eth2p0.SignedAggregateAndProof, shareIdx int) ParSignedData { +func NewPartialSignedAggregateAndProof(data *eth2spec.VersionedSignedAggregateAndProof, shareIdx int) ParSignedData { return ParSignedData{ SignedData: NewSignedAggregateAndProof(data), ShareIdx: shareIdx, } } -// SignedAggregateAndProof wraps eth2p0.SignedAggregateAndProof and implements SignedData. -type SignedAggregateAndProof struct { - eth2p0.SignedAggregateAndProof +// VersionedSignedAggregateAndProof wraps eth2spec.VersionedSignedAggregateAndProof and implements SignedData. +type VersionedSignedAggregateAndProof struct { + eth2spec.VersionedSignedAggregateAndProof } -func (s SignedAggregateAndProof) MessageRoot() ([32]byte, error) { - return s.Message.HashTreeRoot() +func (s VersionedSignedAggregateAndProof) MessageRoot() ([32]byte, error) { + switch s.Version { + case eth2spec.DataVersionPhase0: + if s.Phase0 == nil { + return [32]byte{}, errors.New("unmarshal phase0") + } + + return s.Phase0.Message.HashTreeRoot() + case eth2spec.DataVersionAltair: + if s.Altair == nil { + return [32]byte{}, errors.New("unmarshal altair") + } + + return s.Altair.Message.HashTreeRoot() + case eth2spec.DataVersionBellatrix: + if s.Bellatrix == nil { + return [32]byte{}, errors.New("unmarshal bellatrix") + } + + return s.Bellatrix.Message.HashTreeRoot() + case eth2spec.DataVersionCapella: + if s.Capella == nil { + return [32]byte{}, errors.New("unmarshal capella") + } + + return s.Capella.Message.HashTreeRoot() + case eth2spec.DataVersionDeneb: + if s.Deneb == nil { + return [32]byte{}, errors.New("unmarshal deneb") + } + + return s.Deneb.Message.HashTreeRoot() + case eth2spec.DataVersionElectra: + if s.Electra == nil { + return [32]byte{}, errors.New("unmarshal electra") + } + + return s.Electra.Message.HashTreeRoot() + default: + return [32]byte{}, errors.New("unknown version") + } } -func (s SignedAggregateAndProof) Signature() Signature { - return SigFromETH2(s.SignedAggregateAndProof.Signature) +func (s VersionedSignedAggregateAndProof) Signature() Signature { + switch s.Version { + case eth2spec.DataVersionPhase0: + if s.Phase0 == nil { + return Signature{} + } + + return SigFromETH2(s.Phase0.Signature) + case eth2spec.DataVersionAltair: + if s.Altair == nil { + return Signature{} + } + + return SigFromETH2(s.Altair.Signature) + case eth2spec.DataVersionBellatrix: + if s.Bellatrix == nil { + return Signature{} + } + + return SigFromETH2(s.Bellatrix.Signature) + case eth2spec.DataVersionCapella: + if s.Capella == nil { + return Signature{} + } + + return SigFromETH2(s.Capella.Signature) + case eth2spec.DataVersionDeneb: + if s.Deneb == nil { + return Signature{} + } + + return SigFromETH2(s.Deneb.Signature) + case eth2spec.DataVersionElectra: + if s.Electra == nil { + return Signature{} + } + + return SigFromETH2(s.Electra.Signature) + default: + return Signature{} + } } -func (s SignedAggregateAndProof) SetSignature(sig Signature) (SignedData, error) { +func (s VersionedSignedAggregateAndProof) SetSignature(sig Signature) (SignedData, error) { resp, err := s.clone() if err != nil { return nil, err } - resp.SignedAggregateAndProof.Signature = sig.ToETH2() + switch s.Version { + case eth2spec.DataVersionPhase0: + if s.Phase0 == nil { + return nil, errors.New("unmarshal phase0") + } + + resp.Phase0.Signature = sig.ToETH2() + case eth2spec.DataVersionAltair: + if s.Altair == nil { + return nil, errors.New("unmarshal altair") + } + + resp.Altair.Signature = sig.ToETH2() + case eth2spec.DataVersionBellatrix: + if s.Bellatrix == nil { + return nil, errors.New("unmarshal bellatrix") + } + + resp.Bellatrix.Signature = sig.ToETH2() + case eth2spec.DataVersionCapella: + if s.Capella == nil { + return nil, errors.New("unmarshal capella") + } + + resp.Capella.Signature = sig.ToETH2() + case eth2spec.DataVersionDeneb: + if s.Deneb == nil { + return nil, errors.New("unmarshal deneb") + } + + resp.Deneb.Signature = sig.ToETH2() + case eth2spec.DataVersionElectra: + if s.Electra == nil { + return nil, errors.New("unmarshal electra") + } + + resp.Electra.Signature = sig.ToETH2() + default: + return nil, errors.New("unknown version") + } return resp, nil } -func (s SignedAggregateAndProof) Clone() (SignedData, error) { +func (s VersionedSignedAggregateAndProof) Clone() (SignedData, error) { return s.clone() } -func (s SignedAggregateAndProof) clone() (SignedAggregateAndProof, error) { - var resp SignedAggregateAndProof +func (s VersionedSignedAggregateAndProof) clone() (VersionedSignedAggregateAndProof, error) { + var resp VersionedSignedAggregateAndProof err := cloneJSONMarshaler(s, &resp) if err != nil { - return SignedAggregateAndProof{}, errors.Wrap(err, "clone signed aggregate and proof") + return VersionedSignedAggregateAndProof{}, errors.Wrap(err, "clone signed aggregate and proof") } return resp, nil } -func (s SignedAggregateAndProof) MarshalJSON() ([]byte, error) { - return s.SignedAggregateAndProof.MarshalJSON() -} +func (a VersionedSignedAggregateAndProof) MarshalJSON() ([]byte, error) { + if a.IsEmpty() { + return nil, errors.New("empty versioned signedAggregateAndProof object") + } -func (s *SignedAggregateAndProof) UnmarshalJSON(input []byte) error { - return s.SignedAggregateAndProof.UnmarshalJSON(input) -} + var marshaller json.Marshaler + switch a.Version { + case eth2spec.DataVersionPhase0: + marshaller = a.Phase0 + case eth2spec.DataVersionAltair: + marshaller = a.Altair + case eth2spec.DataVersionBellatrix: + marshaller = a.Bellatrix + case eth2spec.DataVersionCapella: + marshaller = a.Capella + case eth2spec.DataVersionDeneb: + marshaller = a.Deneb + case eth2spec.DataVersionElectra: + marshaller = a.Electra + default: + return nil, errors.New("unknown signedAggregateAndProof version", z.Str("version", a.Version.String())) + } + + signedAggregateAndProof, err := marshaller.MarshalJSON() + if err != nil { + return nil, errors.Wrap(err, "marshal signedAggregateAndProof") + } + + version, err := eth2util.DataVersionFromETH2(a.Version) + if err != nil { + return nil, errors.Wrap(err, "convert version") + } + + resp, err := json.Marshal(versionedRawAggregateAndProofJSON{ + Version: version, + AggregateAndProof: signedAggregateAndProof, + }) + if err != nil { + return nil, errors.Wrap(err, "marshal wrapper") + } -func (s SignedAggregateAndProof) MarshalSSZ() ([]byte, error) { - return s.SignedAggregateAndProof.MarshalSSZ() + return resp, nil } -func (s SignedAggregateAndProof) MarshalSSZTo(dst []byte) ([]byte, error) { - return s.SignedAggregateAndProof.MarshalSSZTo(dst) +func (s *VersionedSignedAggregateAndProof) UnmarshalJSON(input []byte) error { + var raw versionedRawAggregateAndProofJSON + if err := json.Unmarshal(input, &raw); err != nil { + return errors.Wrap(err, "unmarshal aggregateAndProof") + } + + resp := eth2spec.VersionedSignedAggregateAndProof{Version: raw.Version.ToETH2()} + switch resp.Version { + case eth2spec.DataVersionPhase0: + aggregateAndProof := new(eth2p0.SignedAggregateAndProof) + if err := json.Unmarshal(raw.AggregateAndProof, &aggregateAndProof); err != nil { + return errors.Wrap(err, "unmarshal phase0") + } + resp.Phase0 = aggregateAndProof + case eth2spec.DataVersionAltair: + aggregateAndProof := new(eth2p0.SignedAggregateAndProof) + if err := json.Unmarshal(raw.AggregateAndProof, &aggregateAndProof); err != nil { + return errors.Wrap(err, "unmarshal altair") + } + resp.Altair = aggregateAndProof + case eth2spec.DataVersionBellatrix: + aggregateAndProof := new(eth2p0.SignedAggregateAndProof) + if err := json.Unmarshal(raw.AggregateAndProof, &aggregateAndProof); err != nil { + return errors.Wrap(err, "unmarshal bellatrix") + } + resp.Bellatrix = aggregateAndProof + case eth2spec.DataVersionCapella: + aggregateAndProof := new(eth2p0.SignedAggregateAndProof) + if err := json.Unmarshal(raw.AggregateAndProof, &aggregateAndProof); err != nil { + return errors.Wrap(err, "unmarshal capella") + } + resp.Capella = aggregateAndProof + case eth2spec.DataVersionDeneb: + aggregateAndProof := new(eth2p0.SignedAggregateAndProof) + if err := json.Unmarshal(raw.AggregateAndProof, &aggregateAndProof); err != nil { + return errors.Wrap(err, "unmarshal deneb") + } + resp.Deneb = aggregateAndProof + case eth2spec.DataVersionElectra: + aggregateAndProof := new(eth2e.SignedAggregateAndProof) + if err := json.Unmarshal(raw.AggregateAndProof, &aggregateAndProof); err != nil { + return errors.Wrap(err, "unmarshal electra") + } + resp.Electra = aggregateAndProof + default: + return errors.New("unknown version") + } + + s.VersionedSignedAggregateAndProof = resp + + return nil } -func (s SignedAggregateAndProof) SizeSSZ() int { - return s.SignedAggregateAndProof.SizeSSZ() +// TODO: remove after go-eth2-client make util functions for Aggregate or Data fields +func (s *VersionedSignedAggregateAndProof) Data() *eth2p0.AttestationData { + switch s.Version { + case eth2spec.DataVersionPhase0: + if s.Phase0 == nil { + return nil + } + + return s.Phase0.Message.Aggregate.Data + case eth2spec.DataVersionAltair: + if s.Phase0 == nil { + return nil + } + + return s.Altair.Message.Aggregate.Data + case eth2spec.DataVersionBellatrix: + if s.Phase0 == nil { + return nil + } + + return s.Bellatrix.Message.Aggregate.Data + case eth2spec.DataVersionCapella: + if s.Phase0 == nil { + return nil + } + + return s.Capella.Message.Aggregate.Data + case eth2spec.DataVersionDeneb: + if s.Phase0 == nil { + return nil + } + + return s.Deneb.Message.Aggregate.Data + case eth2spec.DataVersionElectra: + if s.Phase0 == nil { + return nil + } + + return s.Electra.Message.Aggregate.Data + default: + return nil + } } -func (s *SignedAggregateAndProof) UnmarshalSSZ(b []byte) error { - return s.SignedAggregateAndProof.UnmarshalSSZ(b) +// TODO: remove after go-eth2-client make util functions for Aggregate or Data fields +func (s *VersionedSignedAggregateAndProof) AggregationBits() bitfield.Bitlist { + switch s.Version { + case eth2spec.DataVersionPhase0: + if s.Phase0 == nil { + return nil + } + + return s.Phase0.Message.Aggregate.AggregationBits + case eth2spec.DataVersionAltair: + if s.Phase0 == nil { + return nil + } + + return s.Altair.Message.Aggregate.AggregationBits + case eth2spec.DataVersionBellatrix: + if s.Phase0 == nil { + return nil + } + + return s.Bellatrix.Message.Aggregate.AggregationBits + case eth2spec.DataVersionCapella: + if s.Phase0 == nil { + return nil + } + + return s.Capella.Message.Aggregate.AggregationBits + case eth2spec.DataVersionDeneb: + if s.Phase0 == nil { + return nil + } + + return s.Deneb.Message.Aggregate.AggregationBits + case eth2spec.DataVersionElectra: + if s.Phase0 == nil { + return nil + } + + return s.Electra.Message.Aggregate.AggregationBits + default: + return nil + } } // SyncCommitteeMessage: https://github.com/ethereum/consensus-specs/blob/dev/specs/altair/validator.md#synccommitteemessage. diff --git a/core/signeddata_test.go b/core/signeddata_test.go index 9d8bed9b1..780249e51 100644 --- a/core/signeddata_test.go +++ b/core/signeddata_test.go @@ -48,14 +48,17 @@ 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(), + data: core.VersionedSignedAggregateAndProof{ + VersionedSignedAggregateAndProof: eth2spec.VersionedSignedAggregateAndProof{ + Version: eth2spec.DataVersionDeneb, + Deneb: ð2p0.SignedAggregateAndProof{ + Message: ð2p0.AggregateAndProof{ + AggregatorIndex: 0, + Aggregate: testutil.RandomAttestation(), + SelectionProof: testutil.RandomEth2Signature(), + }, + Signature: testutil.RandomEth2Signature(), }, - Signature: testutil.RandomEth2Signature(), }, }, }, diff --git a/core/ssz.go b/core/ssz.go index 99b7a95a2..c343bfed5 100644 --- a/core/ssz.go +++ b/core/ssz.go @@ -372,6 +372,196 @@ func (a *VersionedAttestation) sszValFromVersion(version eth2util.DataVersion) ( } } +// ================== VersionedSignedAggregateAndProof =================== + +// MarshalSSZ ssz marshals the VersionedSignedAggregateAndProof object. +func (a VersionedSignedAggregateAndProof) MarshalSSZ() ([]byte, error) { + resp, err := ssz.MarshalSSZ(a) + if err != nil { + return nil, errors.Wrap(err, "marshal VersionedSignedAggregateAndProof") + } + + return resp, nil +} + +// MarshalSSZTo ssz marshals the VersionedSignedAggregateAndProof object to a target array. +func (a VersionedSignedAggregateAndProof) MarshalSSZTo(dst []byte) ([]byte, error) { + version, err := eth2util.DataVersionFromETH2(a.Version) + if err != nil { + return nil, errors.Wrap(err, "invalid version") + } + + return marshalSSZVersionedTo(dst, version, a.sszValFromVersion) +} + +// UnmarshalSSZ ssz unmarshalls the VersionedSignedAggregateAndProof object. +func (a *VersionedSignedAggregateAndProof) UnmarshalSSZ(b []byte) error { + version, err := unmarshalSSZVersioned(b, a.sszValFromVersion) + if err != nil { + return errors.Wrap(err, "unmarshal VersionedSignedAggregateAndProof") + } + + a.Version = version.ToETH2() + + return nil +} + +// SizeSSZ returns the ssz encoded size in bytes for the VersionedSignedAggregateAndProof object. +func (a VersionedSignedAggregateAndProof) SizeSSZ() int { + version, err := eth2util.DataVersionFromETH2(a.Version) + if err != nil { + // SSZMarshaller interface doesn't return an error, so we can't either. + return 0 + } + + val, err := a.sszValFromVersion(version) + if err != nil { + // SSZMarshaller interface doesn't return an error, so we can't either. + return 0 + } + + return sizeSSZVersioned(val) +} + +// sszValFromVersion returns the internal value of the VersionedSignedAggregateAndProof object for a given version. +func (a *VersionedSignedAggregateAndProof) sszValFromVersion(version eth2util.DataVersion) (sszType, error) { + switch version { + case eth2util.DataVersionPhase0: + if a.Phase0 == nil { + a.Phase0 = new(eth2p0.SignedAggregateAndProof) + } + + return a.Phase0, nil + case eth2util.DataVersionAltair: + if a.Altair == nil { + a.Altair = new(eth2p0.SignedAggregateAndProof) + } + + return a.Altair, nil + case eth2util.DataVersionBellatrix: + if a.Bellatrix == nil { + a.Bellatrix = new(eth2p0.SignedAggregateAndProof) + } + + return a.Bellatrix, nil + case eth2util.DataVersionCapella: + if a.Capella == nil { + a.Capella = new(eth2p0.SignedAggregateAndProof) + } + + return a.Capella, nil + case eth2util.DataVersionDeneb: + if a.Deneb == nil { + a.Deneb = new(eth2p0.SignedAggregateAndProof) + } + + return a.Deneb, nil + case eth2util.DataVersionElectra: + if a.Electra == nil { + a.Electra = new(electra.SignedAggregateAndProof) + } + + return a.Electra, nil + default: + return nil, errors.New("invalid version") + } +} + +// ================== VersionedAggregatedAttestation =================== + +// MarshalSSZ ssz marshals the VersionedAggregatedAttestation object. +func (a VersionedAggregatedAttestation) MarshalSSZ() ([]byte, error) { + resp, err := ssz.MarshalSSZ(a) + if err != nil { + return nil, errors.Wrap(err, "marshal VersionedAggregatedAttestation") + } + + return resp, nil +} + +// MarshalSSZTo ssz marshals the VersionedAggregatedAttestation object to a target array. +func (a VersionedAggregatedAttestation) MarshalSSZTo(dst []byte) ([]byte, error) { + version, err := eth2util.DataVersionFromETH2(a.Version) + if err != nil { + return nil, errors.Wrap(err, "invalid version") + } + + return marshalSSZVersionedTo(dst, version, a.sszValFromVersion) +} + +// UnmarshalSSZ ssz unmarshalls the VersionedAggregatedAttestation object. +func (a *VersionedAggregatedAttestation) UnmarshalSSZ(b []byte) error { + version, err := unmarshalSSZVersioned(b, a.sszValFromVersion) + if err != nil { + return errors.Wrap(err, "unmarshal VersionedAggregatedAttestation") + } + + a.Version = version.ToETH2() + + return nil +} + +// SizeSSZ returns the ssz encoded size in bytes for the VersionedAggregatedAttestation object. +func (a VersionedAggregatedAttestation) SizeSSZ() int { + version, err := eth2util.DataVersionFromETH2(a.Version) + if err != nil { + // SSZMarshaller interface doesn't return an error, so we can't either. + return 0 + } + + val, err := a.sszValFromVersion(version) + if err != nil { + // SSZMarshaller interface doesn't return an error, so we can't either. + return 0 + } + + return sizeSSZVersioned(val) +} + +// sszValFromVersion returns the internal value of the VersionedAggregatedAttestation object for a given version. +func (a *VersionedAggregatedAttestation) sszValFromVersion(version eth2util.DataVersion) (sszType, error) { + switch version { + case eth2util.DataVersionPhase0: + if a.Phase0 == nil { + a.Phase0 = new(eth2p0.Attestation) + } + + return a.Phase0, nil + case eth2util.DataVersionAltair: + if a.Altair == nil { + a.Altair = new(eth2p0.Attestation) + } + + return a.Altair, nil + case eth2util.DataVersionBellatrix: + if a.Bellatrix == nil { + a.Bellatrix = new(eth2p0.Attestation) + } + + return a.Bellatrix, nil + case eth2util.DataVersionCapella: + if a.Capella == nil { + a.Capella = new(eth2p0.Attestation) + } + + return a.Capella, nil + case eth2util.DataVersionDeneb: + if a.Deneb == nil { + a.Deneb = new(eth2p0.Attestation) + } + + return a.Deneb, nil + case eth2util.DataVersionElectra: + if a.Electra == nil { + a.Electra = new(electra.Attestation) + } + + return a.Electra, nil + default: + return nil, errors.New("invalid version") + } +} + const ( // versionedBlindedOffset is the offset of a versioned blinded ssz encoded object. versionedBlindedOffset = 8 + 1 + 4 // version (uint64) + blinded (uint8) + offset (uint32) diff --git a/core/ssz_test.go b/core/ssz_test.go index 0d830e848..69e7ac2d7 100644 --- a/core/ssz_test.go +++ b/core/ssz_test.go @@ -63,11 +63,11 @@ func TestSSZ(t *testing.T) { }{ {zero: func() any { return new(core.VersionedSignedProposal) }}, {zero: func() any { return new(core.VersionedAttestation) }}, - {zero: func() any { return new(core.SignedAggregateAndProof) }}, + {zero: func() any { return new(core.VersionedSignedAggregateAndProof) }}, {zero: func() any { return new(core.SignedSyncMessage) }}, {zero: func() any { return new(core.SyncContributionAndProof) }}, {zero: func() any { return new(core.SignedSyncContributionAndProof) }}, - {zero: func() any { return new(core.AggregatedAttestation) }}, + {zero: func() any { return new(core.VersionedAggregatedAttestation) }}, {zero: func() any { return new(core.VersionedProposal) }}, {zero: func() any { return new(core.SyncContribution) }}, } @@ -108,7 +108,7 @@ func TestMarshalUnsignedProto(t *testing.T) { }, { dutyType: core.DutyAggregator, - unsignedPtr: func() any { return new(core.AggregatedAttestation) }, + unsignedPtr: func() any { return new(core.VersionedAggregatedAttestation) }, }, { dutyType: core.DutyProposer, @@ -182,7 +182,7 @@ func TestMarshalParSignedProto(t *testing.T) { }, { dutyType: core.DutyAggregator, - signedPtr: func() any { return new(core.SignedAggregateAndProof) }, + signedPtr: func() any { return new(core.VersionedSignedAggregateAndProof) }, }, { dutyType: core.DutyProposer, diff --git a/core/tracker/inclusion.go b/core/tracker/inclusion.go index 9e86460ee..336f89970 100644 --- a/core/tracker/inclusion.go +++ b/core/tracker/inclusion.go @@ -99,11 +99,11 @@ func (i *inclusionCore) Submitted(duty core.Duty, pubkey core.PubKey, data core. return errors.Wrap(err, "hash attestation") } } else if duty.Type == core.DutyAggregator { - agg, ok := data.(core.SignedAggregateAndProof) + agg, ok := data.(core.VersionedSignedAggregateAndProof) if !ok { return errors.New("invalid aggregate and proof") } - attRoot, err = agg.Message.Aggregate.Data.HashTreeRoot() + attRoot, err = agg.Data().HashTreeRoot() if err != nil { return errors.Wrap(err, "hash aggregate") } @@ -259,7 +259,7 @@ func checkAggregationInclusion(sub submission, block block) (bool, error) { if err != nil { return false, errors.Wrap(err, "get attestation aggregation bits") } - subBits := sub.Data.(core.SignedAggregateAndProof).Message.Aggregate.AggregationBits + subBits := sub.Data.(*core.VersionedSignedAggregateAndProof).AggregationBits() ok, err = attAggregationBits.Contains(subBits) if err != nil { return false, errors.Wrap(err, "check aggregation bits", diff --git a/core/tracker/inclusion_internal_test.go b/core/tracker/inclusion_internal_test.go index 1afa3a148..2d0bfa046 100644 --- a/core/tracker/inclusion_internal_test.go +++ b/core/tracker/inclusion_internal_test.go @@ -95,7 +95,9 @@ func TestInclusion(t *testing.T) { att1Duty := core.NewAttesterDuty(uint64(att1Data.Slot)) agg2 := testutil.RandomSignedAggregateAndProof() - agg2Duty := core.NewAggregatorDuty(uint64(agg2.Message.Aggregate.Data.Slot)) + slot, err := agg2.Slot() + require.NoError(t, err) + agg2Duty := core.NewAggregatorDuty(uint64(slot)) att3 := testutil.RandomDenebVersionedAttestation() att3Data, err := att3.Data() @@ -134,17 +136,18 @@ func TestInclusion(t *testing.T) { // Create a mock block with the 1st and 2nd attestations. att1Root, err := att1.Deneb.Data.HashTreeRoot() require.NoError(t, err) + //TODO: fix after go-eth2-client make util functions for Aggregate or Data fields att2Root, err := agg2.Message.Aggregate.Data.HashTreeRoot() require.NoError(t, err) // Add some random aggregation bits to the attestation addRandomBits(att1.Deneb.AggregationBits) + //TODO: fix after go-eth2-client make util functions for Aggregate or Data fields addRandomBits(agg2.Message.Aggregate.AggregationBits) block := block{ Slot: block4Duty.Slot, AttestationsByDataRoot: map[eth2p0.Root]*eth2spec.VersionedAttestation{ att1Root: att1, - // TODO(kalo): go-eth2-client should (?) have versioned attestations for AggregateAndProof att2Root: {Version: eth2spec.DataVersionDeneb, Deneb: agg2.Message.Aggregate}, }, } diff --git a/core/unsigneddata.go b/core/unsigneddata.go index 04e1bb348..4e09dff30 100644 --- a/core/unsigneddata.go +++ b/core/unsigneddata.go @@ -24,17 +24,17 @@ import ( var ( _ UnsignedData = AttestationData{} - _ UnsignedData = AggregatedAttestation{} + _ UnsignedData = VersionedAggregatedAttestation{} _ UnsignedData = VersionedProposal{} _ UnsignedData = SyncContribution{} // Some types also support SSZ marshalling and unmarshalling. _ ssz.Marshaler = AttestationData{} - _ ssz.Marshaler = AggregatedAttestation{} + _ ssz.Marshaler = VersionedAggregatedAttestation{} _ ssz.Marshaler = VersionedProposal{} _ ssz.Marshaler = SyncContribution{} _ ssz.Unmarshaler = new(AttestationData) - _ ssz.Unmarshaler = new(AggregatedAttestation) + _ ssz.Unmarshaler = new(VersionedAggregatedAttestation) _ ssz.Unmarshaler = new(VersionedProposal) _ ssz.Unmarshaler = new(SyncContribution) ) @@ -86,18 +86,47 @@ type attestationDataJSON struct { Duty *eth2v1.AttesterDuty `json:"attestation_duty"` } -// NewAggregatedAttestation returns a new aggregated attestation. -func NewAggregatedAttestation(att *eth2p0.Attestation) AggregatedAttestation { - return AggregatedAttestation{Attestation: *att} +// NewVersionedAggregatedAttestation returns a new aggregated attestation. +func NewVersionedAggregatedAttestation(att *eth2spec.VersionedAttestation) (VersionedAggregatedAttestation, error) { + switch att.Version { + case eth2spec.DataVersionPhase0: + if att.Phase0 == nil { + return VersionedAggregatedAttestation{}, errors.New("no phase0 attestation") + } + case eth2spec.DataVersionAltair: + if att.Altair == nil { + return VersionedAggregatedAttestation{}, errors.New("no altair attestation") + } + case eth2spec.DataVersionBellatrix: + if att.Bellatrix == nil { + return VersionedAggregatedAttestation{}, errors.New("no bellatrix attestation") + } + case eth2spec.DataVersionCapella: + if att.Capella == nil { + return VersionedAggregatedAttestation{}, errors.New("no capella attestation") + } + case eth2spec.DataVersionDeneb: + if att.Deneb == nil { + return VersionedAggregatedAttestation{}, errors.New("no deneb attestation") + } + case eth2spec.DataVersionElectra: + if att.Electra == nil { + return VersionedAggregatedAttestation{}, errors.New("no electra attestation") + } + default: + return VersionedAggregatedAttestation{}, errors.New("unknown version") + } + + return VersionedAggregatedAttestation{VersionedAttestation: *att}, nil } -// AggregatedAttestation wraps un unsigned aggregated attestation and implements the UnsignedData interface. -type AggregatedAttestation struct { - eth2p0.Attestation +// VersionedAggregatedAttestation wraps un unsigned aggregated attestation and implements the UnsignedData interface. +type VersionedAggregatedAttestation struct { + eth2spec.VersionedAttestation } -func (a AggregatedAttestation) Clone() (UnsignedData, error) { - var resp AggregatedAttestation +func (a VersionedAggregatedAttestation) Clone() (UnsignedData, error) { + var resp VersionedAggregatedAttestation err := cloneJSONMarshaler(a, &resp) if err != nil { return nil, errors.Wrap(err, "clone aggregated attestation") @@ -106,35 +135,76 @@ func (a AggregatedAttestation) Clone() (UnsignedData, error) { return resp, nil } -func (a AggregatedAttestation) MarshalJSON() ([]byte, error) { - return a.Attestation.MarshalJSON() -} +func (a VersionedAggregatedAttestation) MarshalJSON() ([]byte, error) { + var marshaller json.Marshaler + switch a.Version { + // No aggregatedAttestation nil checks since `NewVersionedProposal` assumed. + case eth2spec.DataVersionPhase0: + marshaller = a.Phase0 + case eth2spec.DataVersionAltair: + marshaller = a.Altair + case eth2spec.DataVersionBellatrix: + marshaller = a.Bellatrix + case eth2spec.DataVersionCapella: + marshaller = a.Capella + case eth2spec.DataVersionDeneb: + marshaller = a.Deneb + case eth2spec.DataVersionElectra: + marshaller = a.Electra + default: + return nil, errors.New("unknown version") + } -func (a *AggregatedAttestation) UnmarshalJSON(input []byte) error { - var att eth2p0.Attestation - if err := json.Unmarshal(input, &att); err != nil { - return errors.Wrap(err, "unmarshal aggregated attestation") + aggregatedAttestation, err := marshaller.MarshalJSON() + if err != nil { + return nil, errors.Wrap(err, "marshal aggregatedAttestation") } - *a = AggregatedAttestation{Attestation: att} + version, err := eth2util.DataVersionFromETH2(a.Version) + if err != nil { + return nil, errors.Wrap(err, "convert version") + } - return nil -} + resp, err := json.Marshal(versionedRawAggregateAndProofJSON{ + Version: version, + AggregateAndProof: aggregatedAttestation, + }) + if err != nil { + return nil, errors.Wrap(err, "marshal wrapper") + } -func (a AggregatedAttestation) MarshalSSZ() ([]byte, error) { - return a.Attestation.MarshalSSZ() + return resp, nil } -func (a AggregatedAttestation) MarshalSSZTo(dst []byte) ([]byte, error) { - return a.Attestation.MarshalSSZTo(dst) +func (a VersionedAggregatedAttestation) HashTreeRoot() ([32]byte, error) { + switch a.Version { + // No aggregatedAttestation nil checks since `NewVersionedProposal` assumed. + case eth2spec.DataVersionPhase0: + return a.Phase0.HashTreeRoot() + case eth2spec.DataVersionAltair: + return a.Altair.HashTreeRoot() + case eth2spec.DataVersionBellatrix: + return a.Bellatrix.HashTreeRoot() + case eth2spec.DataVersionCapella: + return a.Capella.HashTreeRoot() + case eth2spec.DataVersionDeneb: + return a.Deneb.HashTreeRoot() + case eth2spec.DataVersionElectra: + return a.Electra.HashTreeRoot() + default: + return [32]byte{}, errors.New("unknown version") + } } -func (a AggregatedAttestation) SizeSSZ() int { - return a.Attestation.SizeSSZ() -} +func (a *VersionedAggregatedAttestation) UnmarshalJSON(input []byte) error { + var att eth2spec.VersionedAttestation + if err := json.Unmarshal(input, &att); err != nil { + return errors.Wrap(err, "unmarshal aggregated attestation") + } + + *a = VersionedAggregatedAttestation{VersionedAttestation: att} -func (a *AggregatedAttestation) UnmarshalSSZ(b []byte) error { - return a.Attestation.UnmarshalSSZ(b) + return nil } // NewVersionedProposal validates and returns a new wrapped VersionedProposal. @@ -412,7 +482,7 @@ func unmarshalUnsignedData(typ DutyType, data []byte) (UnsignedData, error) { return resp, nil case DutyAggregator: - var resp AggregatedAttestation + var resp VersionedAggregatedAttestation if err := unmarshal(data, &resp); err != nil { return nil, errors.Wrap(err, "unmarshal aggregated attestation") } diff --git a/core/unsigneddata_test.go b/core/unsigneddata_test.go index 116da1f9c..764cc3dda 100644 --- a/core/unsigneddata_test.go +++ b/core/unsigneddata_test.go @@ -41,7 +41,7 @@ func TestUnsignedDataClone(t *testing.T) { }, { name: "aggregated attestation", - data: core.NewAggregatedAttestation(testutil.RandomAttestation()), + data: testutil.RandomDenebCoreVersionedAggregateAttestation(), }, { name: "sync contribution", diff --git a/core/validatorapi/router.go b/core/validatorapi/router.go index cdb26cf76..bc768e304 100644 --- a/core/validatorapi/router.go +++ b/core/validatorapi/router.go @@ -1008,7 +1008,7 @@ func aggregateAttestation(p eth2client.AggregateAttestationProvider) handlerFunc data := eth2Resp.Data return struct { - Data *eth2p0.Attestation `json:"data"` + Data *eth2spec.VersionedAttestation `json:"data"` }{ Data: data, }, nil, nil @@ -1017,7 +1017,7 @@ func aggregateAttestation(p eth2client.AggregateAttestationProvider) handlerFunc func submitAggregateAttestations(s eth2client.AggregateAttestationsSubmitter) handlerFunc { return func(ctx context.Context, _ map[string]string, _ url.Values, typ contentType, body []byte) (any, http.Header, error) { - var aggs []*eth2p0.SignedAggregateAndProof + var aggs *eth2api.SubmitAggregateAttestationsOpts err := unmarshal(typ, body, &aggs) if err != nil { return nil, nil, errors.Wrap(err, "unmarshal signed aggregate and proofs") diff --git a/core/validatorapi/router_internal_test.go b/core/validatorapi/router_internal_test.go index 93ba3e4dc..095fbd268 100644 --- a/core/validatorapi/router_internal_test.go +++ b/core/validatorapi/router_internal_test.go @@ -1425,18 +1425,21 @@ func TestSubmitAggregateAttestations(t *testing.T) { const vIdx = 1 - agg := ð2p0.SignedAggregateAndProof{ - Message: ð2p0.AggregateAndProof{ - AggregatorIndex: vIdx, - Aggregate: testutil.RandomAttestation(), - SelectionProof: testutil.RandomEth2Signature(), + agg := ð2spec.VersionedSignedAggregateAndProof{ + Version: eth2spec.DataVersionDeneb, + Deneb: ð2p0.SignedAggregateAndProof{ + Message: ð2p0.AggregateAndProof{ + AggregatorIndex: vIdx, + Aggregate: testutil.RandomAttestation(), + SelectionProof: testutil.RandomEth2Signature(), + }, + Signature: testutil.RandomEth2Signature(), }, - Signature: testutil.RandomEth2Signature(), } handler := testHandler{ - SubmitAggregateAttestationsFunc: func(_ context.Context, aggregateAndProofs []*eth2p0.SignedAggregateAndProof) error { - require.Equal(t, agg, aggregateAndProofs[0]) + SubmitAggregateAttestationsFunc: func(_ context.Context, aggregateAndProofs *eth2api.SubmitAggregateAttestationsOpts) error { + require.Equal(t, agg, aggregateAndProofs.SignedAggregateAndProofs[0]) return nil }, @@ -1459,7 +1462,7 @@ func TestSubmitAggregateAttestations(t *testing.T) { require.NoError(t, err) eth2Cl := eth2wrap.AdaptEth2HTTP(eth2Svc.(*eth2http.Service), time.Second) - err = eth2Cl.SubmitAggregateAttestations(ctx, []*eth2p0.SignedAggregateAndProof{agg}) + err = eth2Cl.SubmitAggregateAttestations(ctx, ð2api.SubmitAggregateAttestationsOpts{SignedAggregateAndProofs: []*eth2spec.VersionedSignedAggregateAndProof{agg}}) require.NoError(t, err) } @@ -1795,7 +1798,7 @@ type testHandler struct { SubmitVoluntaryExitFunc func(ctx context.Context, exit *eth2p0.SignedVoluntaryExit) error SubmitValidatorRegistrationsFunc func(ctx context.Context, registrations []*eth2api.VersionedSignedValidatorRegistration) error AggregateBeaconCommitteeSelectionsFunc func(ctx context.Context, selections []*eth2exp.BeaconCommitteeSelection) ([]*eth2exp.BeaconCommitteeSelection, error) - SubmitAggregateAttestationsFunc func(ctx context.Context, aggregateAndProofs []*eth2p0.SignedAggregateAndProof) error + SubmitAggregateAttestationsFunc func(ctx context.Context, aggregateAndProofs *eth2api.SubmitAggregateAttestationsOpts) error SubmitSyncCommitteeMessagesFunc func(ctx context.Context, messages []*altair.SyncCommitteeMessage) error SyncCommitteeDutiesFunc func(ctx context.Context, opts *eth2api.SyncCommitteeDutiesOpts) (*eth2api.Response[[]*eth2v1.SyncCommitteeDuty], error) SyncCommitteeContributionFunc func(ctx context.Context, opts *eth2api.SyncCommitteeContributionOpts) (*eth2api.Response[*altair.SyncCommitteeContribution], error) @@ -1857,7 +1860,7 @@ func (h testHandler) AggregateBeaconCommitteeSelections(ctx context.Context, sel return h.AggregateBeaconCommitteeSelectionsFunc(ctx, selections) } -func (h testHandler) SubmitAggregateAttestations(ctx context.Context, aggregateAndProofs []*eth2p0.SignedAggregateAndProof) error { +func (h testHandler) SubmitAggregateAttestations(ctx context.Context, aggregateAndProofs *eth2api.SubmitAggregateAttestationsOpts) error { return h.SubmitAggregateAttestationsFunc(ctx, aggregateAndProofs) } diff --git a/core/validatorapi/validatorapi.go b/core/validatorapi/validatorapi.go index d30c4f394..c8c0dc841 100644 --- a/core/validatorapi/validatorapi.go +++ b/core/validatorapi/validatorapi.go @@ -196,7 +196,7 @@ type Component struct { awaitAttFunc func(ctx context.Context, slot, commIdx uint64) (*eth2p0.AttestationData, error) awaitProposalFunc func(ctx context.Context, slot uint64) (*eth2api.VersionedProposal, error) awaitSyncContributionFunc func(ctx context.Context, slot, subcommIdx uint64, beaconBlockRoot eth2p0.Root) (*altair.SyncCommitteeContribution, error) - awaitAggAttFunc func(ctx context.Context, slot uint64, attestationRoot eth2p0.Root) (*eth2p0.Attestation, error) + awaitAggAttFunc func(ctx context.Context, slot uint64, attestationRoot eth2p0.Root) (*eth2spec.VersionedAttestation, error) awaitAggSigDBFunc func(context.Context, core.Duty, core.PubKey) (core.SignedData, error) dutyDefFunc func(ctx context.Context, duty core.Duty) (core.DutyDefinitionSet, error) subs []func(context.Context, core.Duty, core.ParSignedDataSet) error @@ -234,7 +234,7 @@ func (c *Component) RegisterGetDutyDefinition(fn func(ctx context.Context, duty // RegisterAwaitAggAttestation registers a function to query an aggregated attestation. // It supports a single function, since it is an input of the component. -func (c *Component) RegisterAwaitAggAttestation(fn func(ctx context.Context, slot uint64, attestationRoot eth2p0.Root) (*eth2p0.Attestation, error)) { +func (c *Component) RegisterAwaitAggAttestation(fn func(ctx context.Context, slot uint64, attestationRoot eth2p0.Root) (*eth2spec.VersionedAttestation, error)) { c.awaitAggAttFunc = fn } @@ -786,7 +786,7 @@ func (c Component) AggregateBeaconCommitteeSelections(ctx context.Context, selec // AggregateAttestation returns the aggregate attestation for the given attestation root. // It does a blocking query to DutyAggregator unsigned data from dutyDB. -func (c Component) AggregateAttestation(ctx context.Context, opts *eth2api.AggregateAttestationOpts) (*eth2api.Response[*eth2p0.Attestation], error) { +func (c Component) AggregateAttestation(ctx context.Context, opts *eth2api.AggregateAttestationOpts) (*eth2api.Response[*eth2spec.VersionedAttestation], error) { aggAtt, err := c.awaitAggAttFunc(ctx, uint64(opts.Slot), opts.AttestationDataRoot) if err != nil { return nil, err @@ -798,16 +798,21 @@ func (c Component) AggregateAttestation(ctx context.Context, opts *eth2api.Aggre // SubmitAggregateAttestations receives partially signed aggregateAndProofs. // - It verifies partial signature on AggregateAndProof. // - It then calls all the subscribers for further steps on partially signed aggregate and proof. -func (c Component) SubmitAggregateAttestations(ctx context.Context, aggregateAndProofs []*eth2p0.SignedAggregateAndProof) error { +func (c Component) SubmitAggregateAttestations(ctx context.Context, aggregateAndProofs *eth2api.SubmitAggregateAttestationsOpts) error { + aggsAndProofs := aggregateAndProofs.SignedAggregateAndProofs vals, err := c.eth2Cl.ActiveValidators(ctx) if err != nil { return err } psigsBySlot := make(map[eth2p0.Slot]core.ParSignedDataSet) - for _, agg := range aggregateAndProofs { - slot := agg.Message.Aggregate.Data.Slot - eth2Pubkey, ok := vals[agg.Message.AggregatorIndex] + for _, agg := range aggsAndProofs { + slot, err := agg.Slot() + if err != nil { + return err + } + //TODO: fix after go-eth2-client make util function for AggregatorIndex field + eth2Pubkey, ok := vals[agg.AggregatorIndex()] if !ok { return errors.New("validator not found") } @@ -819,7 +824,7 @@ func (c Component) SubmitAggregateAttestations(ctx context.Context, aggregateAnd // Verify inner selection proof (outcome of DutyPrepareAggregator). if !c.insecureTest { - err = signing.VerifyAggregateAndProofSelection(ctx, c.eth2Cl, tbls.PublicKey(eth2Pubkey), agg.Message) + err = signing.VerifyAggregateAndProofSelection(ctx, c.eth2Cl, tbls.PublicKey(eth2Pubkey), agg) if err != nil { return err } diff --git a/core/validatorapi/validatorapi_test.go b/core/validatorapi/validatorapi_test.go index b9352cb22..1ae8df9d3 100644 --- a/core/validatorapi/validatorapi_test.go +++ b/core/validatorapi/validatorapi_test.go @@ -1666,16 +1666,20 @@ func TestComponent_SubmitAggregateAttestations(t *testing.T) { const vIdx = 1 - agg := ð2p0.SignedAggregateAndProof{ - Message: ð2p0.AggregateAndProof{ - AggregatorIndex: vIdx, - Aggregate: testutil.RandomAttestation(), - SelectionProof: testutil.RandomEth2Signature(), + agg := ð2spec.VersionedSignedAggregateAndProof{ + Version: eth2spec.DataVersionDeneb, + Deneb: ð2p0.SignedAggregateAndProof{ + Message: ð2p0.AggregateAndProof{ + AggregatorIndex: vIdx, + Aggregate: testutil.RandomAttestation(), + SelectionProof: testutil.RandomEth2Signature(), + }, + Signature: testutil.RandomEth2Signature(), }, - Signature: testutil.RandomEth2Signature(), } - slot := agg.Message.Aggregate.Data.Slot + slot, err := agg.Slot() + require.NoError(t, err) pubkey := beaconmock.ValidatorSetA[vIdx].Validator.PublicKey bmock, err := beaconmock.New(beaconmock.WithValidatorSet(beaconmock.ValidatorSetA)) @@ -1697,7 +1701,7 @@ func TestComponent_SubmitAggregateAttestations(t *testing.T) { return nil }) - require.NoError(t, vapi.SubmitAggregateAttestations(ctx, []*eth2p0.SignedAggregateAndProof{agg})) + require.NoError(t, vapi.SubmitAggregateAttestations(ctx, ð2api.SubmitAggregateAttestationsOpts{SignedAggregateAndProofs: []*eth2spec.VersionedSignedAggregateAndProof{agg}})) } func TestComponent_SubmitAggregateAttestationVerify(t *testing.T) { @@ -1731,9 +1735,12 @@ func TestComponent_SubmitAggregateAttestationVerify(t *testing.T) { } aggProof.Aggregate.Data.Slot = slot aggProof.SelectionProof = signBeaconSelection(t, bmock, secret, slot) - signedAggProof := ð2p0.SignedAggregateAndProof{ - Message: aggProof, - Signature: signAggregationAndProof(t, bmock, secret, aggProof), + signedAggProof := ð2spec.VersionedSignedAggregateAndProof{ + Version: eth2spec.DataVersionDeneb, + Deneb: ð2p0.SignedAggregateAndProof{ + Message: aggProof, + Signature: signAggregationAndProof(t, bmock, secret, aggProof), + }, } // Construct the validator api component @@ -1751,7 +1758,7 @@ func TestComponent_SubmitAggregateAttestationVerify(t *testing.T) { return nil }) - err = vapi.SubmitAggregateAttestations(ctx, []*eth2p0.SignedAggregateAndProof{signedAggProof}) + err = vapi.SubmitAggregateAttestations(ctx, ð2api.SubmitAggregateAttestationsOpts{SignedAggregateAndProofs: []*eth2spec.VersionedSignedAggregateAndProof{signedAggProof}}) require.NoError(t, err) <-done } diff --git a/eth2util/signing/signing.go b/eth2util/signing/signing.go index 6df16c330..42146f5d5 100644 --- a/eth2util/signing/signing.go +++ b/eth2util/signing/signing.go @@ -6,6 +6,7 @@ import ( "context" eth2api "github.com/attestantio/go-eth2-client/api" + eth2spec "github.com/attestantio/go-eth2-client/spec" eth2p0 "github.com/attestantio/go-eth2-client/spec/phase0" "github.com/obolnetwork/charon/app/errors" @@ -78,18 +79,24 @@ func GetDataRoot(ctx context.Context, eth2Cl eth2wrap.Client, name DomainName, e // VerifyAggregateAndProofSelection verifies the eth2p0.AggregateAndProof with the provided pubkey. // Refer get_slot_signature from https://github.com/ethereum/consensus-specs/blob/dev/specs/phase0/validator.md#aggregation-selection. -func VerifyAggregateAndProofSelection(ctx context.Context, eth2Cl eth2wrap.Client, pubkey tbls.PublicKey, agg *eth2p0.AggregateAndProof) error { - epoch, err := eth2util.EpochFromSlot(ctx, eth2Cl, agg.Aggregate.Data.Slot) +func VerifyAggregateAndProofSelection(ctx context.Context, eth2Cl eth2wrap.Client, pubkey tbls.PublicKey, agg *eth2spec.VersionedSignedAggregateAndProof) error { + slot, err := agg.Slot() if err != nil { return err } - sigRoot, err := eth2util.SlotHashRoot(agg.Aggregate.Data.Slot) + epoch, err := eth2util.EpochFromSlot(ctx, eth2Cl, slot) + if err != nil { + return err + } + + sigRoot, err := eth2util.SlotHashRoot(slot) if err != nil { return errors.Wrap(err, "cannot get hash root of slot") } - return Verify(ctx, eth2Cl, DomainSelectionProof, epoch, sigRoot, agg.SelectionProof, pubkey) + //TODO: fix after go-eth2-client make util function for SelectionProof field + return Verify(ctx, eth2Cl, DomainSelectionProof, epoch, sigRoot, agg.SelectionProof(), pubkey) } // Verify returns an error if the signature doesn't match the eth2 domain signed root. diff --git a/go.mod b/go.mod index 4b537c9eb..8a18f2404 100644 --- a/go.mod +++ b/go.mod @@ -266,4 +266,4 @@ replace github.com/coinbase/kryptology => github.com/ObolNetwork/kryptology v0.0 // replace github.com/attestantio/go-eth2-client => github.com/ObolNetwork/go-eth2-client v0.21.11-0.20240822135044-f0a5b21e02c6 // Replace go-eth2-client version with the electra branch -replace github.com/attestantio/go-eth2-client => github.com/attestantio/go-eth2-client v0.23.1-0.20250114123312-5a900c7c87e7 +replace github.com/attestantio/go-eth2-client => github.com/attestantio/go-eth2-client v0.23.1-0.20250116194652-2157b429c0f7 diff --git a/go.sum b/go.sum index c7adb72de..b6063c8bd 100644 --- a/go.sum +++ b/go.sum @@ -47,8 +47,8 @@ github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYW github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw= github.com/attestantio/go-builder-client v0.5.3 h1:4YFT0u823JvF4Uesj7SEG8f0De1uxLrVioOgKZYPl3I= github.com/attestantio/go-builder-client v0.5.3/go.mod h1:RaWc8K2SjyU19sL5UinOQ8n4vTZBrEQzsNuMWpNmwK4= -github.com/attestantio/go-eth2-client v0.23.1-0.20250114123312-5a900c7c87e7 h1:Vx2r2n9t4w9PAxzOSHTj0g9fLbS0FjV49uYj15fNabc= -github.com/attestantio/go-eth2-client v0.23.1-0.20250114123312-5a900c7c87e7/go.mod h1:vy5jU/uDZ2+RcVzq5BfnG+bQ3/6uu9DGwCrGsPtjJ1A= +github.com/attestantio/go-eth2-client v0.23.1-0.20250116194652-2157b429c0f7 h1:k+x71pLHEEPNVHS+hMlC+G5hjMT0ynAZZygOSkPayrI= +github.com/attestantio/go-eth2-client v0.23.1-0.20250116194652-2157b429c0f7/go.mod h1:vy5jU/uDZ2+RcVzq5BfnG+bQ3/6uu9DGwCrGsPtjJ1A= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/benbjohnson/clock v1.3.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/benbjohnson/clock v1.3.5 h1:VvXlSJBzZpA/zum6Sj74hxwYI2DIxRWuNIoXAzHZz5o= diff --git a/testutil/beaconmock/beaconmock.go b/testutil/beaconmock/beaconmock.go index 22f6b7fba..eebf2c45d 100644 --- a/testutil/beaconmock/beaconmock.go +++ b/testutil/beaconmock/beaconmock.go @@ -145,8 +145,8 @@ type Mock struct { AggregateBeaconCommitteeSelectionsFunc func(context.Context, []*eth2exp.BeaconCommitteeSelection) ([]*eth2exp.BeaconCommitteeSelection, error) AggregateSyncCommitteeSelectionsFunc func(context.Context, []*eth2exp.SyncCommitteeSelection) ([]*eth2exp.SyncCommitteeSelection, 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 + AggregateAttestationFunc func(ctx context.Context, slot eth2p0.Slot, attestationDataRoot eth2p0.Root) (*eth2spec.VersionedAttestation, error) + SubmitAggregateAttestationsFunc func(ctx context.Context, aggregateAndProofs *eth2api.SubmitAggregateAttestationsOpts) error SyncCommitteeDutiesFunc func(ctx context.Context, epoch eth2p0.Epoch, validatorIndices []eth2p0.ValidatorIndex) ([]*eth2v1.SyncCommitteeDuty, error) SubmitSyncCommitteeMessagesFunc func(ctx context.Context, messages []*altair.SyncCommitteeMessage) error SubmitSyncCommitteeContributionsFunc func(ctx context.Context, contributionAndProofs []*altair.SignedContributionAndProof) error @@ -157,7 +157,7 @@ type Mock struct { ProposerConfigFunc func(context.Context) (*eth2exp.ProposerConfigResponse, error) } -func (m Mock) AggregateAttestation(ctx context.Context, opts *eth2api.AggregateAttestationOpts) (*eth2api.Response[*eth2p0.Attestation], error) { +func (m Mock) AggregateAttestation(ctx context.Context, opts *eth2api.AggregateAttestationOpts) (*eth2api.Response[*eth2spec.VersionedAttestation], error) { aggAtt, err := m.AggregateAttestationFunc(ctx, opts.Slot, opts.AttestationDataRoot) if err != nil { return nil, err @@ -323,7 +323,7 @@ func (m Mock) SubmitBeaconCommitteeSubscriptions(ctx context.Context, subscripti return m.SubmitBeaconCommitteeSubscriptionsFunc(ctx, subscriptions) } -func (m Mock) SubmitAggregateAttestations(ctx context.Context, aggregateAndProofs []*eth2p0.SignedAggregateAndProof) error { +func (m Mock) SubmitAggregateAttestations(ctx context.Context, aggregateAndProofs *eth2api.SubmitAggregateAttestationsOpts) error { return m.SubmitAggregateAttestationsFunc(ctx, aggregateAndProofs) } diff --git a/testutil/beaconmock/beaconmock_fuzz.go b/testutil/beaconmock/beaconmock_fuzz.go index 24fd7a747..90343d398 100644 --- a/testutil/beaconmock/beaconmock_fuzz.go +++ b/testutil/beaconmock/beaconmock_fuzz.go @@ -116,8 +116,8 @@ func WithBeaconMockFuzzer() Option { return block, nil } - mock.AggregateAttestationFunc = func(context.Context, eth2p0.Slot, eth2p0.Root) (*eth2p0.Attestation, error) { - var att *eth2p0.Attestation + mock.AggregateAttestationFunc = func(context.Context, eth2p0.Slot, eth2p0.Root) (*eth2spec.VersionedAttestation, error) { + var att *eth2spec.VersionedAttestation fuzz.New().Fuzz(&att) return att, nil diff --git a/testutil/beaconmock/options.go b/testutil/beaconmock/options.go index e68792385..dc1ba7f8a 100644 --- a/testutil/beaconmock/options.go +++ b/testutil/beaconmock/options.go @@ -548,15 +548,18 @@ func defaultMock(httpMock HTTPMock, httpServer *http.Server, clock clockwork.Clo AttestationDataFunc: func(ctx context.Context, slot eth2p0.Slot, index eth2p0.CommitteeIndex) (*eth2p0.AttestationData, error) { return attStore.NewAttestationData(ctx, slot, index) }, - AggregateAttestationFunc: func(_ context.Context, _ eth2p0.Slot, root eth2p0.Root) (*eth2p0.Attestation, error) { + AggregateAttestationFunc: func(_ context.Context, _ eth2p0.Slot, root eth2p0.Root) (*eth2spec.VersionedAttestation, error) { attData, err := attStore.AttestationDataByRoot(root) if err != nil { return nil, err } - return ð2p0.Attestation{ - AggregationBits: bitfield.NewBitlist(0), - Data: attData, + return ð2spec.VersionedAttestation{ + Version: eth2spec.DataVersionDeneb, + Deneb: ð2p0.Attestation{ + AggregationBits: bitfield.NewBitlist(0), + Data: attData, + }, }, nil }, CachedValidatorsFunc: func(context.Context) (eth2wrap.ActiveValidators, eth2wrap.CompleteValidators, error) { @@ -597,7 +600,7 @@ func defaultMock(httpMock HTTPMock, httpServer *http.Server, clock clockwork.Clo AggregateBeaconCommitteeSelectionsFunc: func(_ context.Context, selections []*eth2exp.BeaconCommitteeSelection) ([]*eth2exp.BeaconCommitteeSelection, error) { return selections, nil }, - SubmitAggregateAttestationsFunc: func(context.Context, []*eth2p0.SignedAggregateAndProof) error { + SubmitAggregateAttestationsFunc: func(context.Context, *eth2api.SubmitAggregateAttestationsOpts) error { return nil }, SlotsPerEpochFunc: func(ctx context.Context) (uint64, error) { diff --git a/testutil/random.go b/testutil/random.go index 4d5ccfcee..aa415637d 100644 --- a/testutil/random.go +++ b/testutil/random.go @@ -149,6 +149,18 @@ func RandomAggregateAttestation() *eth2p0.Attestation { } } +func RandomDenebCoreVersionedAggregateAttestation() core.VersionedAggregatedAttestation { + return core.VersionedAggregatedAttestation{ + VersionedAttestation: eth2spec.VersionedAttestation{ + Version: eth2spec.DataVersionDeneb, + Deneb: ð2p0.Attestation{ + AggregationBits: RandomBitList(64), + Data: RandomAttestationData(), + Signature: RandomEth2Signature(), + }, + }} +} + func RandomAttestationData() *eth2p0.AttestationData { return RandomAttestationDataSeed(NewSeedRand()) } @@ -661,11 +673,13 @@ func RandomCoreSyncCommitteeSelection() core.SyncCommitteeSelection { return core.NewSyncCommitteeSelection(RandomSyncCommitteeSelection()) } -func RandomSignedAggregateAndProof() *eth2p0.SignedAggregateAndProof { - return ð2p0.SignedAggregateAndProof{ - Message: RandomAggregateAndProof(), - Signature: RandomEth2Signature(), - } +func RandomSignedAggregateAndProof() *eth2spec.VersionedSignedAggregateAndProof { + return ð2spec.VersionedSignedAggregateAndProof{ + Version: eth2spec.DataVersionDeneb, + Deneb: ð2p0.SignedAggregateAndProof{ + Message: RandomAggregateAndProof(), + Signature: RandomEth2Signature(), + }} } func RandomAggregateAndProof() *eth2p0.AggregateAndProof { diff --git a/testutil/validatormock/attest.go b/testutil/validatormock/attest.go index ea1611174..b2be79809 100644 --- a/testutil/validatormock/attest.go +++ b/testutil/validatormock/attest.go @@ -8,6 +8,7 @@ import ( eth2api "github.com/attestantio/go-eth2-client/api" eth2v1 "github.com/attestantio/go-eth2-client/api/v1" + "github.com/attestantio/go-eth2-client/spec" eth2spec "github.com/attestantio/go-eth2-client/spec" eth2p0 "github.com/attestantio/go-eth2-client/spec/phase0" "github.com/prysmaticlabs/go-bitfield" @@ -373,8 +374,8 @@ func aggregate(ctx context.Context, eth2Cl eth2wrap.Client, signFunc SignFunc, s } var ( - aggs []*eth2p0.SignedAggregateAndProof - attsByComm = make(map[eth2p0.CommitteeIndex]*eth2p0.Attestation) + aggs []*spec.VersionedSignedAggregateAndProof + attsByComm = make(map[eth2p0.CommitteeIndex]*eth2spec.VersionedAttestation) ) for _, selection := range selections { commIdx, ok := committees[selection.ValidatorIndex] @@ -392,10 +393,13 @@ func aggregate(ctx context.Context, eth2Cl eth2wrap.Client, signFunc SignFunc, s attsByComm[commIdx] = att } - proof := eth2p0.AggregateAndProof{ - AggregatorIndex: selection.ValidatorIndex, - Aggregate: att, - SelectionProof: selection.SelectionProof, + proof := spec.VersionedAggregateAndProof{ + Version: eth2spec.DataVersionDeneb, + Deneb: ð2p0.AggregateAndProof{ + AggregatorIndex: selection.ValidatorIndex, + Aggregate: att.Deneb, + SelectionProof: selection.SelectionProof, + }, } proofRoot, err := proof.HashTreeRoot() @@ -418,13 +422,16 @@ func aggregate(ctx context.Context, eth2Cl eth2wrap.Client, signFunc SignFunc, s return false, err } - aggs = append(aggs, ð2p0.SignedAggregateAndProof{ - Message: &proof, - Signature: proofSig, + aggs = append(aggs, &spec.VersionedSignedAggregateAndProof{ + Version: eth2spec.DataVersionDeneb, + Deneb: ð2p0.SignedAggregateAndProof{ + Message: proof.Deneb, + Signature: proofSig, + }, }) } - if err := eth2Cl.SubmitAggregateAttestations(ctx, aggs); err != nil { + if err := eth2Cl.SubmitAggregateAttestations(ctx, ð2api.SubmitAggregateAttestationsOpts{SignedAggregateAndProofs: aggs}); err != nil { return false, err } @@ -434,7 +441,7 @@ func aggregate(ctx context.Context, eth2Cl eth2wrap.Client, signFunc SignFunc, s // getAggregateAttestation returns an aggregated attestation for the provided committee. func getAggregateAttestation(ctx context.Context, eth2Cl eth2wrap.Client, datas attDatas, commIdx eth2p0.CommitteeIndex, -) (*eth2p0.Attestation, error) { +) (*eth2spec.VersionedAttestation, error) { for _, data := range datas { if data.Index != commIdx { continue diff --git a/testutil/validatormock/propose_test.go b/testutil/validatormock/propose_test.go index bdbf7b40c..b38e013ff 100644 --- a/testutil/validatormock/propose_test.go +++ b/testutil/validatormock/propose_test.go @@ -11,6 +11,7 @@ import ( "testing" "time" + "github.com/attestantio/go-eth2-client/api" eth2api "github.com/attestantio/go-eth2-client/api" eth2spec "github.com/attestantio/go-eth2-client/spec" eth2p0 "github.com/attestantio/go-eth2-client/spec/phase0" @@ -58,12 +59,12 @@ func TestAttest(t *testing.T) { // Callback to collect attestations var atts []*eth2spec.VersionedAttestation - var aggs []*eth2p0.SignedAggregateAndProof + var aggs *api.SubmitAggregateAttestationsOpts beaconMock.SubmitAttestationsFunc = func(_ context.Context, attestations *eth2api.SubmitAttestationsOpts) error { atts = attestations.Attestations return nil } - beaconMock.SubmitAggregateAttestationsFunc = func(_ context.Context, aggAndProofs []*eth2p0.SignedAggregateAndProof) error { + beaconMock.SubmitAggregateAttestationsFunc = func(_ context.Context, aggAndProofs *api.SubmitAggregateAttestationsOpts) error { aggs = aggAndProofs return nil } @@ -107,7 +108,8 @@ func TestAttest(t *testing.T) { }) sort.Slice(aggs, func(i, j int) bool { - return aggs[i].Message.Aggregate.Data.Index < aggs[j].Message.Aggregate.Data.Index + //TODO: fix after go-eth2-client make util function for Data field + return aggs.SignedAggregateAndProofs[i].Message.Aggregate.Data.Index < aggs.SignedAggregateAndProofs[j].Message.Aggregate.Data.Index }) t.Run("attestations", func(t *testing.T) { From 7073b0dd09b0c9aa097c58d026956553d7f3efe0 Mon Sep 17 00:00:00 2001 From: Kaloyan Tanev Date: Wed, 22 Jan 2025 18:25:48 +0200 Subject: [PATCH 2/3] Bump go-eth2-client version and fix TODOs --- core/tracker/inclusion_internal_test.go | 4 ++-- go.mod | 2 +- go.sum | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/core/tracker/inclusion_internal_test.go b/core/tracker/inclusion_internal_test.go index 2d0bfa046..50beb9098 100644 --- a/core/tracker/inclusion_internal_test.go +++ b/core/tracker/inclusion_internal_test.go @@ -136,12 +136,12 @@ func TestInclusion(t *testing.T) { // Create a mock block with the 1st and 2nd attestations. att1Root, err := att1.Deneb.Data.HashTreeRoot() require.NoError(t, err) - //TODO: fix after go-eth2-client make util functions for Aggregate or Data fields + //TODO: fix after go-eth2-client make util functions for Data field att2Root, err := agg2.Message.Aggregate.Data.HashTreeRoot() require.NoError(t, err) // Add some random aggregation bits to the attestation addRandomBits(att1.Deneb.AggregationBits) - //TODO: fix after go-eth2-client make util functions for Aggregate or Data fields + //TODO: fix after go-eth2-client make util functions for AggregationBits field addRandomBits(agg2.Message.Aggregate.AggregationBits) block := block{ diff --git a/go.mod b/go.mod index 8a18f2404..49e2ddd47 100644 --- a/go.mod +++ b/go.mod @@ -266,4 +266,4 @@ replace github.com/coinbase/kryptology => github.com/ObolNetwork/kryptology v0.0 // replace github.com/attestantio/go-eth2-client => github.com/ObolNetwork/go-eth2-client v0.21.11-0.20240822135044-f0a5b21e02c6 // Replace go-eth2-client version with the electra branch -replace github.com/attestantio/go-eth2-client => github.com/attestantio/go-eth2-client v0.23.1-0.20250116194652-2157b429c0f7 +replace github.com/attestantio/go-eth2-client => github.com/attestantio/go-eth2-client v0.23.1-0.20250122084024-be3c6bce48f3 diff --git a/go.sum b/go.sum index b6063c8bd..9db3fdf3c 100644 --- a/go.sum +++ b/go.sum @@ -47,8 +47,8 @@ github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYW github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw= github.com/attestantio/go-builder-client v0.5.3 h1:4YFT0u823JvF4Uesj7SEG8f0De1uxLrVioOgKZYPl3I= github.com/attestantio/go-builder-client v0.5.3/go.mod h1:RaWc8K2SjyU19sL5UinOQ8n4vTZBrEQzsNuMWpNmwK4= -github.com/attestantio/go-eth2-client v0.23.1-0.20250116194652-2157b429c0f7 h1:k+x71pLHEEPNVHS+hMlC+G5hjMT0ynAZZygOSkPayrI= -github.com/attestantio/go-eth2-client v0.23.1-0.20250116194652-2157b429c0f7/go.mod h1:vy5jU/uDZ2+RcVzq5BfnG+bQ3/6uu9DGwCrGsPtjJ1A= +github.com/attestantio/go-eth2-client v0.23.1-0.20250122084024-be3c6bce48f3 h1:wU1n1cUf5oPwr4Y9db+g+ZLSUP9VWyy8Zas9VnCLtyQ= +github.com/attestantio/go-eth2-client v0.23.1-0.20250122084024-be3c6bce48f3/go.mod h1:vy5jU/uDZ2+RcVzq5BfnG+bQ3/6uu9DGwCrGsPtjJ1A= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/benbjohnson/clock v1.3.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/benbjohnson/clock v1.3.5 h1:VvXlSJBzZpA/zum6Sj74hxwYI2DIxRWuNIoXAzHZz5o= From fe560a31d9b003dbe26d56e48e918e39b7214647 Mon Sep 17 00:00:00 2001 From: Kaloyan Tanev Date: Thu, 23 Jan 2025 12:05:12 +0200 Subject: [PATCH 3/3] Write own switch cases, instead of waiting on go-eth2-client util funcs --- core/bcast/bcast_test.go | 2 +- core/proto_test.go | 4 +-- core/signeddata.go | 20 +++++------ core/tracker/inclusion_internal_test.go | 10 +++--- core/validatorapi/validatorapi.go | 46 +++++++++++++++++++++++-- eth2util/signing/signing.go | 46 +++++++++++++++++++++++-- testutil/random.go | 2 +- testutil/validatormock/propose_test.go | 5 ++- 8 files changed, 108 insertions(+), 27 deletions(-) diff --git a/core/bcast/bcast_test.go b/core/bcast/bcast_test.go index 6d0e84240..565485b19 100644 --- a/core/bcast/bcast_test.go +++ b/core/bcast/bcast_test.go @@ -237,7 +237,7 @@ func aggregateAttestationData(t *testing.T, mock *beaconmock.Mock) test { t.Helper() asserted := make(chan struct{}) - aggAndProof := testutil.RandomSignedAggregateAndProof() + aggAndProof := testutil.RandomDenebVersionedSignedAggregateAndProof() aggData := core.VersionedSignedAggregateAndProof{ VersionedSignedAggregateAndProof: *aggAndProof, } diff --git a/core/proto_test.go b/core/proto_test.go index 2eaecf79c..004685709 100644 --- a/core/proto_test.go +++ b/core/proto_test.go @@ -192,7 +192,7 @@ func TestParSignedData(t *testing.T) { func TestParSignedDataFromProtoErrors(t *testing.T) { parSig1 := core.ParSignedData{ - SignedData: core.VersionedSignedAggregateAndProof{*testutil.RandomSignedAggregateAndProof()}, + SignedData: core.VersionedSignedAggregateAndProof{*testutil.RandomDenebVersionedSignedAggregateAndProof()}, ShareIdx: rand.Intn(100), } @@ -246,7 +246,7 @@ func randomSignedData(t *testing.T) map[core.DutyType]core.SignedData { core.DutyRandao: core.SignedRandao{SignedEpoch: eth2util.SignedEpoch{Epoch: testutil.RandomEpoch(), Signature: testutil.RandomEth2Signature()}}, core.DutyProposer: testutil.RandomBellatrixCoreVersionedSignedProposal(), core.DutyPrepareAggregator: testutil.RandomCoreBeaconCommitteeSelection(), - core.DutyAggregator: core.NewSignedAggregateAndProof(testutil.RandomSignedAggregateAndProof()), + core.DutyAggregator: core.NewSignedAggregateAndProof(testutil.RandomDenebVersionedSignedAggregateAndProof()), core.DutyPrepareSyncContribution: core.NewSyncCommitteeSelection(testutil.RandomSyncCommitteeSelection()), core.DutySyncContribution: core.NewSignedSyncContributionAndProof(testutil.RandomSignedSyncContributionAndProof()), } diff --git a/core/signeddata.go b/core/signeddata.go index d57bd9e3d..9f65b7631 100644 --- a/core/signeddata.go +++ b/core/signeddata.go @@ -1448,31 +1448,31 @@ func (s *VersionedSignedAggregateAndProof) Data() *eth2p0.AttestationData { return s.Phase0.Message.Aggregate.Data case eth2spec.DataVersionAltair: - if s.Phase0 == nil { + if s.Altair == nil { return nil } return s.Altair.Message.Aggregate.Data case eth2spec.DataVersionBellatrix: - if s.Phase0 == nil { + if s.Bellatrix == nil { return nil } return s.Bellatrix.Message.Aggregate.Data case eth2spec.DataVersionCapella: - if s.Phase0 == nil { + if s.Capella == nil { return nil } return s.Capella.Message.Aggregate.Data case eth2spec.DataVersionDeneb: - if s.Phase0 == nil { + if s.Deneb == nil { return nil } return s.Deneb.Message.Aggregate.Data case eth2spec.DataVersionElectra: - if s.Phase0 == nil { + if s.Electra == nil { return nil } @@ -1492,31 +1492,31 @@ func (s *VersionedSignedAggregateAndProof) AggregationBits() bitfield.Bitlist { return s.Phase0.Message.Aggregate.AggregationBits case eth2spec.DataVersionAltair: - if s.Phase0 == nil { + if s.Altair == nil { return nil } return s.Altair.Message.Aggregate.AggregationBits case eth2spec.DataVersionBellatrix: - if s.Phase0 == nil { + if s.Bellatrix == nil { return nil } return s.Bellatrix.Message.Aggregate.AggregationBits case eth2spec.DataVersionCapella: - if s.Phase0 == nil { + if s.Capella == nil { return nil } return s.Capella.Message.Aggregate.AggregationBits case eth2spec.DataVersionDeneb: - if s.Phase0 == nil { + if s.Deneb == nil { return nil } return s.Deneb.Message.Aggregate.AggregationBits case eth2spec.DataVersionElectra: - if s.Phase0 == nil { + if s.Electra == nil { return nil } diff --git a/core/tracker/inclusion_internal_test.go b/core/tracker/inclusion_internal_test.go index 50beb9098..429975922 100644 --- a/core/tracker/inclusion_internal_test.go +++ b/core/tracker/inclusion_internal_test.go @@ -94,7 +94,7 @@ func TestInclusion(t *testing.T) { require.NoError(t, err) att1Duty := core.NewAttesterDuty(uint64(att1Data.Slot)) - agg2 := testutil.RandomSignedAggregateAndProof() + agg2 := testutil.RandomDenebVersionedSignedAggregateAndProof() slot, err := agg2.Slot() require.NoError(t, err) agg2Duty := core.NewAggregatorDuty(uint64(slot)) @@ -136,19 +136,17 @@ func TestInclusion(t *testing.T) { // Create a mock block with the 1st and 2nd attestations. att1Root, err := att1.Deneb.Data.HashTreeRoot() require.NoError(t, err) - //TODO: fix after go-eth2-client make util functions for Data field - att2Root, err := agg2.Message.Aggregate.Data.HashTreeRoot() + att2Root, err := agg2.Deneb.Message.Aggregate.Data.HashTreeRoot() require.NoError(t, err) // Add some random aggregation bits to the attestation addRandomBits(att1.Deneb.AggregationBits) - //TODO: fix after go-eth2-client make util functions for AggregationBits field - addRandomBits(agg2.Message.Aggregate.AggregationBits) + addRandomBits(agg2.Deneb.Message.Aggregate.AggregationBits) block := block{ Slot: block4Duty.Slot, AttestationsByDataRoot: map[eth2p0.Root]*eth2spec.VersionedAttestation{ att1Root: att1, - att2Root: {Version: eth2spec.DataVersionDeneb, Deneb: agg2.Message.Aggregate}, + att2Root: {Version: eth2spec.DataVersionDeneb, Deneb: agg2.Deneb.Message.Aggregate}, }, } diff --git a/core/validatorapi/validatorapi.go b/core/validatorapi/validatorapi.go index c8c0dc841..ca32a2bca 100644 --- a/core/validatorapi/validatorapi.go +++ b/core/validatorapi/validatorapi.go @@ -811,8 +811,50 @@ func (c Component) SubmitAggregateAttestations(ctx context.Context, aggregateAnd if err != nil { return err } - //TODO: fix after go-eth2-client make util function for AggregatorIndex field - eth2Pubkey, ok := vals[agg.AggregatorIndex()] + // TODO: remove switch case after go-eth2-client make util function for AggregatorIndex field + var aggregatorIndex eth2p0.ValidatorIndex + switch agg.Version { + case eth2spec.DataVersionPhase0: + if agg.Phase0 == nil { + return errors.New("no phase0 aggregateAndProof") + } + + aggregatorIndex = agg.Phase0.Message.AggregatorIndex + case eth2spec.DataVersionAltair: + if agg.Altair == nil { + return errors.New("no altair aggregateAndProof") + } + + aggregatorIndex = agg.Altair.Message.AggregatorIndex + case eth2spec.DataVersionBellatrix: + if agg.Bellatrix == nil { + return errors.New("no bellatrix aggregateAndProof") + } + + aggregatorIndex = agg.Bellatrix.Message.AggregatorIndex + case eth2spec.DataVersionCapella: + if agg.Capella == nil { + return errors.New("no capella aggregateAndProof") + } + + aggregatorIndex = agg.Capella.Message.AggregatorIndex + case eth2spec.DataVersionDeneb: + if agg.Deneb == nil { + return errors.New("no deneb aggregateAndProof") + } + + aggregatorIndex = agg.Deneb.Message.AggregatorIndex + case eth2spec.DataVersionElectra: + if agg.Electra == nil { + return errors.New("no electra aggregateAndProof") + } + + aggregatorIndex = agg.Electra.Message.AggregatorIndex + default: + return errors.New("unknown version") + } + + eth2Pubkey, ok := vals[aggregatorIndex] if !ok { return errors.New("validator not found") } diff --git a/eth2util/signing/signing.go b/eth2util/signing/signing.go index 42146f5d5..8c943f1ff 100644 --- a/eth2util/signing/signing.go +++ b/eth2util/signing/signing.go @@ -95,8 +95,50 @@ func VerifyAggregateAndProofSelection(ctx context.Context, eth2Cl eth2wrap.Clien return errors.Wrap(err, "cannot get hash root of slot") } - //TODO: fix after go-eth2-client make util function for SelectionProof field - return Verify(ctx, eth2Cl, DomainSelectionProof, epoch, sigRoot, agg.SelectionProof(), pubkey) + // TODO: remove switch case after go-eth2-client make util function for SelectionProof field + var selectionProof eth2p0.BLSSignature + switch agg.Version { + case eth2spec.DataVersionPhase0: + if agg.Phase0 == nil { + return errors.New("no phase0 aggregateAndProof") + } + + selectionProof = agg.Phase0.Message.SelectionProof + case eth2spec.DataVersionAltair: + if agg.Altair == nil { + return errors.New("no altair aggregateAndProof") + } + + selectionProof = agg.Altair.Message.SelectionProof + case eth2spec.DataVersionBellatrix: + if agg.Bellatrix == nil { + return errors.New("no bellatrix aggregateAndProof") + } + + selectionProof = agg.Bellatrix.Message.SelectionProof + case eth2spec.DataVersionCapella: + if agg.Capella == nil { + return errors.New("no capella aggregateAndProof") + } + + selectionProof = agg.Capella.Message.SelectionProof + case eth2spec.DataVersionDeneb: + if agg.Deneb == nil { + return errors.New("no deneb aggregateAndProof") + } + + selectionProof = agg.Deneb.Message.SelectionProof + case eth2spec.DataVersionElectra: + if agg.Electra == nil { + return errors.New("no electra aggregateAndProof") + } + + selectionProof = agg.Electra.Message.SelectionProof + default: + return errors.New("unknown version") + } + + return Verify(ctx, eth2Cl, DomainSelectionProof, epoch, sigRoot, selectionProof, pubkey) } // Verify returns an error if the signature doesn't match the eth2 domain signed root. diff --git a/testutil/random.go b/testutil/random.go index aa415637d..bfd982b8c 100644 --- a/testutil/random.go +++ b/testutil/random.go @@ -673,7 +673,7 @@ func RandomCoreSyncCommitteeSelection() core.SyncCommitteeSelection { return core.NewSyncCommitteeSelection(RandomSyncCommitteeSelection()) } -func RandomSignedAggregateAndProof() *eth2spec.VersionedSignedAggregateAndProof { +func RandomDenebVersionedSignedAggregateAndProof() *eth2spec.VersionedSignedAggregateAndProof { return ð2spec.VersionedSignedAggregateAndProof{ Version: eth2spec.DataVersionDeneb, Deneb: ð2p0.SignedAggregateAndProof{ diff --git a/testutil/validatormock/propose_test.go b/testutil/validatormock/propose_test.go index b38e013ff..08884e2b7 100644 --- a/testutil/validatormock/propose_test.go +++ b/testutil/validatormock/propose_test.go @@ -107,9 +107,8 @@ func TestAttest(t *testing.T) { return attsiData.Index < attsjData.Index }) - sort.Slice(aggs, func(i, j int) bool { - //TODO: fix after go-eth2-client make util function for Data field - return aggs.SignedAggregateAndProofs[i].Message.Aggregate.Data.Index < aggs.SignedAggregateAndProofs[j].Message.Aggregate.Data.Index + sort.Slice(aggs.SignedAggregateAndProofs, func(i, j int) bool { + return aggs.SignedAggregateAndProofs[i].Deneb.Message.Aggregate.Data.Index < aggs.SignedAggregateAndProofs[j].Deneb.Message.Aggregate.Data.Index }) t.Run("attestations", func(t *testing.T) {