Skip to content

Commit

Permalink
Update aggregate attestation fetching and submitting for Electra
Browse files Browse the repository at this point in the history
  • Loading branch information
Bez625 committed Jan 16, 2025
1 parent 5a900c7 commit 5129348
Show file tree
Hide file tree
Showing 13 changed files with 388 additions and 44 deletions.
24 changes: 24 additions & 0 deletions api/submitaggregateattestationsopts.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// Copyright © 2025 Attestant Limited.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package api

import "github.com/attestantio/go-eth2-client/spec"

// SubmitAggregateAttestationsOpts are the options for submitting attestations.
type SubmitAggregateAttestationsOpts struct {
Common CommonOpts

// SignedAggregateAndProofs are the aggregate and proofs to submit.
SignedAggregateAndProofs []*spec.VersionedSignedAggregateAndProof
}
24 changes: 16 additions & 8 deletions http/aggregateattestation.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,14 @@ import (

client "github.com/attestantio/go-eth2-client"
"github.com/attestantio/go-eth2-client/api"
"github.com/attestantio/go-eth2-client/spec/phase0"
"github.com/attestantio/go-eth2-client/spec"
)

// AggregateAttestation fetches the aggregate attestation for the given options.
func (s *Service) AggregateAttestation(ctx context.Context,
opts *api.AggregateAttestationOpts,
) (
*api.Response[*phase0.Attestation],
*api.Response[*spec.VersionedAttestation],
error,
) {
if err := s.assertIsSynced(ctx); err != nil {
Expand All @@ -41,29 +41,37 @@ func (s *Service) AggregateAttestation(ctx context.Context,
return nil, errors.Join(errors.New("no attestation data root specified"), client.ErrInvalidOptions)
}

endpoint := "/eth/v1/validator/aggregate_attestation"
endpoint := "/eth/v2/validator/aggregate_attestation"
query := fmt.Sprintf("slot=%d&attestation_data_root=%#x", opts.Slot, opts.AttestationDataRoot)
httpResponse, err := s.get(ctx, endpoint, query, &opts.Common, false)
if err != nil {
return nil, err
}

data, metadata, err := decodeJSONResponse(bytes.NewReader(httpResponse.body), phase0.Attestation{})
data, metadata, err := decodeJSONResponse(bytes.NewReader(httpResponse.body), spec.VersionedAttestation{})
if err != nil {
return nil, err
}

// Confirm the attestation is for the requested slot.
if data.Data.Slot != opts.Slot {
attestationData, err := data.Data()
if err != nil {
return nil,
errors.Join(
errors.New("failed to extract attestation data from response"),
err,
)
}
if attestationData.Slot != opts.Slot {
return nil,
errors.Join(
fmt.Errorf("aggregate attestation for slot %d; expected %d", data.Data.Slot, opts.Slot),
fmt.Errorf("aggregate attestation for slot %d; expected %d", attestationData.Slot, opts.Slot),
client.ErrInconsistentResult,
)
}

// Confirm the attestation data is correct.
dataRoot, err := data.Data.HashTreeRoot()
dataRoot, err := attestationData.HashTreeRoot()
if err != nil {
return nil, errors.Join(errors.New("failed to obtain hash tree root of aggregate attestation data"), err)
}
Expand All @@ -74,7 +82,7 @@ func (s *Service) AggregateAttestation(ctx context.Context,
)
}

return &api.Response[*phase0.Attestation]{
return &api.Response[*spec.VersionedAttestation]{
Metadata: metadata,
Data: &data,
}, nil
Expand Down
69 changes: 61 additions & 8 deletions http/submitaggregateattestations.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,35 +18,88 @@ import (
"context"
"encoding/json"
"errors"
"strings"

client "github.com/attestantio/go-eth2-client"
"github.com/attestantio/go-eth2-client/api"
"github.com/attestantio/go-eth2-client/spec/phase0"
"github.com/attestantio/go-eth2-client/spec"
)

// SubmitAggregateAttestations submits aggregate attestations.
func (s *Service) SubmitAggregateAttestations(ctx context.Context, aggregateAndProofs []*phase0.SignedAggregateAndProof) error {
func (s *Service) SubmitAggregateAttestations(ctx context.Context, opts *api.SubmitAggregateAttestationsOpts) error {
if err := s.assertIsSynced(ctx); err != nil {
return err
}
if opts == nil {
return client.ErrNoOptions
}
if len(opts.SignedAggregateAndProofs) == 0 {
return errors.Join(errors.New("no aggregate and proofs supplied"), client.ErrInvalidOptions)
}
aggregateAndProofs := opts.SignedAggregateAndProofs
unversionedAggregates, err := createUnversionedAggregates(aggregateAndProofs)
if err != nil {
return err
}

specJSON, err := json.Marshal(aggregateAndProofs)
specJSON, err := json.Marshal(unversionedAggregates)
if err != nil {
return errors.Join(errors.New("failed to marshal JSON"), err)
}

endpoint := "/eth/v1/validator/aggregate_and_proofs"
endpoint := "/eth/v2/validator/aggregate_and_proofs"
query := ""

if _, err := s.post(ctx,
headers := make(map[string]string)
headers["Eth-Consensus-Version"] = strings.ToLower(aggregateAndProofs[0].Version.String())
if _, err = s.post(ctx,
endpoint,
query,
&api.CommonOpts{},
&opts.Common,
bytes.NewReader(specJSON),
ContentTypeJSON,
map[string]string{},
headers,
); err != nil {
return errors.Join(errors.New("failed to submit aggregate and proofs"), err)
return errors.Join(errors.New("failed to submit versioned aggregate and proofs"), err)
}

return nil
}

func createUnversionedAggregates(aggregateAndProofs []*spec.VersionedSignedAggregateAndProof) ([]any, error) {
var version spec.DataVersion
var unversionedAggregates []any

for i := range aggregateAndProofs {
if aggregateAndProofs[i] == nil {
return nil, errors.Join(errors.New("nil aggregate and proof version supplied"), client.ErrInvalidOptions)
}

// Ensure consistent versioning.
if version == spec.DataVersionUnknown {
version = aggregateAndProofs[i].Version
} else if version != aggregateAndProofs[i].Version {
return nil, errors.Join(errors.New("aggregate and proofs must all be of the same version"), client.ErrInvalidOptions)
}

// Append to unversionedAggregates.
switch aggregateAndProofs[i].Version {
case spec.DataVersionPhase0:
unversionedAggregates = append(unversionedAggregates, aggregateAndProofs[i].Phase0)
case spec.DataVersionAltair:
unversionedAggregates = append(unversionedAggregates, aggregateAndProofs[i].Altair)
case spec.DataVersionBellatrix:
unversionedAggregates = append(unversionedAggregates, aggregateAndProofs[i].Bellatrix)
case spec.DataVersionCapella:
unversionedAggregates = append(unversionedAggregates, aggregateAndProofs[i].Capella)
case spec.DataVersionDeneb:
unversionedAggregates = append(unversionedAggregates, aggregateAndProofs[i].Deneb)
case spec.DataVersionElectra:
unversionedAggregates = append(unversionedAggregates, aggregateAndProofs[i].Electra)
default:
return nil, errors.Join(errors.New("unknown aggregate and proof version"), client.ErrInvalidOptions)
}
}

return unversionedAggregates, nil
}
18 changes: 11 additions & 7 deletions mock/aggregateattestation.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,21 +17,25 @@ import (
"context"

"github.com/attestantio/go-eth2-client/api"
"github.com/attestantio/go-eth2-client/spec"
"github.com/attestantio/go-eth2-client/spec/phase0"
)

// AggregateAttestation fetches the aggregate attestation given an attestation.
// AggregateAttestation fetches the aggregate attestation for the given options.
func (*Service) AggregateAttestation(_ context.Context,
_ *api.AggregateAttestationOpts,
) (
*api.Response[*phase0.Attestation],
*api.Response[*spec.VersionedAttestation],
error,
) {
return &api.Response[*phase0.Attestation]{
Data: &phase0.Attestation{
Data: &phase0.AttestationData{
Source: &phase0.Checkpoint{},
Target: &phase0.Checkpoint{},
return &api.Response[*spec.VersionedAttestation]{
Data: &spec.VersionedAttestation{
Version: spec.DataVersionPhase0,
Phase0: &phase0.Attestation{
Data: &phase0.AttestationData{
Source: &phase0.Checkpoint{},
Target: &phase0.Checkpoint{},
},
},
},
Metadata: make(map[string]any),
Expand Down
4 changes: 2 additions & 2 deletions mock/submitaggregateattestations.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,10 @@ package mock
import (
"context"

spec "github.com/attestantio/go-eth2-client/spec/phase0"
"github.com/attestantio/go-eth2-client/api"
)

// SubmitAggregateAttestations submits aggregate attestations.
func (*Service) SubmitAggregateAttestations(_ context.Context, _ []*spec.SignedAggregateAndProof) error {
func (*Service) SubmitAggregateAttestations(_ context.Context, _ *api.SubmitAggregateAttestationsOpts) error {
return nil
}
8 changes: 4 additions & 4 deletions multi/aggregateattestation.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,14 @@ import (

consensusclient "github.com/attestantio/go-eth2-client"
"github.com/attestantio/go-eth2-client/api"
"github.com/attestantio/go-eth2-client/spec/phase0"
"github.com/attestantio/go-eth2-client/spec"
)

// AggregateAttestation fetches the aggregate attestation given an attestation.
// AggregateAttestation fetches the aggregate attestation for the given options.
func (s *Service) AggregateAttestation(ctx context.Context,
opts *api.AggregateAttestationOpts,
) (
*api.Response[*phase0.Attestation],
*api.Response[*spec.VersionedAttestation],
error,
) {
res, err := s.doCall(ctx, func(ctx context.Context, client consensusclient.Service) (any, error) {
Expand All @@ -40,7 +40,7 @@ func (s *Service) AggregateAttestation(ctx context.Context,
return nil, err
}

response, isResponse := res.(*api.Response[*phase0.Attestation])
response, isResponse := res.(*api.Response[*spec.VersionedAttestation])
if !isResponse {
return nil, ErrIncorrectType
}
Expand Down
6 changes: 3 additions & 3 deletions multi/submitaggregateattestations.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,15 @@ import (
"context"

consensusclient "github.com/attestantio/go-eth2-client"
"github.com/attestantio/go-eth2-client/spec/phase0"
"github.com/attestantio/go-eth2-client/api"
)

// SubmitAggregateAttestations submits aggregate attestations.
func (s *Service) SubmitAggregateAttestations(ctx context.Context,
aggregateAndProofs []*phase0.SignedAggregateAndProof,
opts *api.SubmitAggregateAttestationsOpts,
) error {
_, err := s.doCall(ctx, func(ctx context.Context, client consensusclient.Service) (any, error) {
err := client.(consensusclient.AggregateAttestationsSubmitter).SubmitAggregateAttestations(ctx, aggregateAndProofs)
err := client.(consensusclient.AggregateAttestationsSubmitter).SubmitAggregateAttestations(ctx, opts)
if err != nil {
return nil, err
}
Expand Down
9 changes: 7 additions & 2 deletions multi/submitaggregateattestations_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,13 @@ package multi_test

import (
"context"
"github.com/attestantio/go-eth2-client/api"
"github.com/attestantio/go-eth2-client/spec"
"testing"

consensusclient "github.com/attestantio/go-eth2-client"
"github.com/attestantio/go-eth2-client/mock"
"github.com/attestantio/go-eth2-client/multi"
"github.com/attestantio/go-eth2-client/spec/phase0"
"github.com/attestantio/go-eth2-client/testclients"
"github.com/rs/zerolog"
"github.com/stretchr/testify/require"
Expand Down Expand Up @@ -51,7 +52,11 @@ func TestSubmitAggregateAttestations(t *testing.T) {
require.NoError(t, err)

for i := 0; i < 128; i++ {
err := multiClient.(consensusclient.AggregateAttestationsSubmitter).SubmitAggregateAttestations(ctx, []*phase0.SignedAggregateAndProof{})
opts := &api.SubmitAggregateAttestationsOpts{
Common: api.CommonOpts{},
SignedAggregateAndProofs: []*spec.VersionedSignedAggregateAndProof{},
}
err := multiClient.(consensusclient.AggregateAttestationsSubmitter).SubmitAggregateAttestations(ctx, opts)
require.NoError(t, err)
}
// At this point we expect mock 3 to be in active (unless probability hates us).
Expand Down
4 changes: 2 additions & 2 deletions service.go
Original file line number Diff line number Diff line change
Expand Up @@ -161,15 +161,15 @@ type AggregateAttestationProvider interface {
AggregateAttestation(ctx context.Context,
opts *api.AggregateAttestationOpts,
) (
*api.Response[*phase0.Attestation],
*api.Response[*spec.VersionedAttestation],
error,
)
}

// AggregateAttestationsSubmitter is the interface for submitting aggregate attestations.
type AggregateAttestationsSubmitter interface {
// SubmitAggregateAttestations submits aggregate attestations.
SubmitAggregateAttestations(ctx context.Context, aggregateAndProofs []*phase0.SignedAggregateAndProof) error
SubmitAggregateAttestations(ctx context.Context, opts *api.SubmitAggregateAttestationsOpts) error
}

// AttestationDataProvider is the interface for providing attestation data.
Expand Down
Loading

0 comments on commit 5129348

Please sign in to comment.