diff --git a/CHANGELOG.md b/CHANGELOG.md index e6ecb2b6..7052b822 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ gloas: - do not update execution chain head state from gloas blocks, as an execution payload bid carries no execution block number - report "builder" as a beacon block proposal source method - preserve the beacon node's gloas payload vote in both the attestation signing root and the submitted attestation, rejecting an invalid vote before signing + - convert gloas aggregate attestations and signed aggregate-and-proof containers for aggregation and submission, rejecting a missing gloas arm before signing - update go-eth2-client to a gloas pseudo-version - satisfy the attgo struct field order and comment capitalisation rules across services and strategies diff --git a/services/attestationaggregator/standard/service.go b/services/attestationaggregator/standard/service.go index beeef0c7..623c0e25 100644 --- a/services/attestationaggregator/standard/service.go +++ b/services/attestationaggregator/standard/service.go @@ -24,6 +24,7 @@ import ( "github.com/attestantio/go-eth2-client/api" "github.com/attestantio/go-eth2-client/spec" "github.com/attestantio/go-eth2-client/spec/electra" + "github.com/attestantio/go-eth2-client/spec/gloas" "github.com/attestantio/go-eth2-client/spec/phase0" "github.com/attestantio/vouch/services/accountmanager" "github.com/attestantio/vouch/services/attestationaggregator" @@ -211,204 +212,172 @@ func (s *Service) Aggregate(ctx context.Context, duty *attestationaggregator.Dut func createVersionedAggregateAndProof(duty *attestationaggregator.Duty, aggregateAttestation *spec.VersionedAttestation) (*spec.VersionedAggregateAndProof, error) { switch aggregateAttestation.Version { case spec.DataVersionPhase0: - if aggregateAttestation.Phase0 == nil { - return nil, errors.New("no phase0 attestation") - } - aggregateAndProof := &phase0.AggregateAndProof{ - AggregatorIndex: duty.ValidatorIndex, - Aggregate: aggregateAttestation.Phase0, - SelectionProof: duty.SlotSignature, - } - versionedAggregateAndProof := &spec.VersionedAggregateAndProof{ - Version: aggregateAttestation.Version, - Phase0: aggregateAndProof, - } - return versionedAggregateAndProof, nil + return createVersionedPhase0AggregateAndProof(duty, aggregateAttestation.Version, aggregateAttestation.Phase0) case spec.DataVersionAltair: - if aggregateAttestation.Altair == nil { - return nil, errors.New("no altair attestation") - } - aggregateAndProof := &phase0.AggregateAndProof{ - AggregatorIndex: duty.ValidatorIndex, - Aggregate: aggregateAttestation.Altair, - SelectionProof: duty.SlotSignature, - } - versionedAggregateAndProof := &spec.VersionedAggregateAndProof{ - Version: aggregateAttestation.Version, - Altair: aggregateAndProof, - } - return versionedAggregateAndProof, nil + return createVersionedPhase0AggregateAndProof(duty, aggregateAttestation.Version, aggregateAttestation.Altair) case spec.DataVersionBellatrix: - if aggregateAttestation.Bellatrix == nil { - return nil, errors.New("no bellatrix attestation") - } - aggregateAndProof := &phase0.AggregateAndProof{ - AggregatorIndex: duty.ValidatorIndex, - Aggregate: aggregateAttestation.Bellatrix, - SelectionProof: duty.SlotSignature, - } - versionedAggregateAndProof := &spec.VersionedAggregateAndProof{ - Version: aggregateAttestation.Version, - Bellatrix: aggregateAndProof, - } - return versionedAggregateAndProof, nil + return createVersionedPhase0AggregateAndProof(duty, aggregateAttestation.Version, aggregateAttestation.Bellatrix) case spec.DataVersionCapella: - if aggregateAttestation.Capella == nil { - return nil, errors.New("no capella attestation") - } - aggregateAndProof := &phase0.AggregateAndProof{ - AggregatorIndex: duty.ValidatorIndex, - Aggregate: aggregateAttestation.Capella, - SelectionProof: duty.SlotSignature, - } - versionedAggregateAndProof := &spec.VersionedAggregateAndProof{ - Version: aggregateAttestation.Version, - Capella: aggregateAndProof, - } - return versionedAggregateAndProof, nil + return createVersionedPhase0AggregateAndProof(duty, aggregateAttestation.Version, aggregateAttestation.Capella) case spec.DataVersionDeneb: - if aggregateAttestation.Deneb == nil { - return nil, errors.New("no deneb attestation") - } - aggregateAndProof := &phase0.AggregateAndProof{ - AggregatorIndex: duty.ValidatorIndex, - Aggregate: aggregateAttestation.Deneb, - SelectionProof: duty.SlotSignature, - } - versionedAggregateAndProof := &spec.VersionedAggregateAndProof{ - Version: aggregateAttestation.Version, - Deneb: aggregateAndProof, - } - return versionedAggregateAndProof, nil + return createVersionedPhase0AggregateAndProof(duty, aggregateAttestation.Version, aggregateAttestation.Deneb) case spec.DataVersionElectra: - if aggregateAttestation.Electra == nil { - return nil, errors.New("no electra attestation") - } - aggregateAndProof := &electra.AggregateAndProof{ - AggregatorIndex: duty.ValidatorIndex, - Aggregate: aggregateAttestation.Electra, - SelectionProof: duty.SlotSignature, - } - versionedAggregateAndProof := &spec.VersionedAggregateAndProof{ - Version: aggregateAttestation.Version, - Electra: aggregateAndProof, - } - return versionedAggregateAndProof, nil + return createVersionedElectraAggregateAndProof(duty, aggregateAttestation.Version, aggregateAttestation.Electra) case spec.DataVersionFulu: - if aggregateAttestation.Fulu == nil { - return nil, errors.New("no fulu attestation") - } - aggregateAndProof := &electra.AggregateAndProof{ - AggregatorIndex: duty.ValidatorIndex, - Aggregate: aggregateAttestation.Fulu, - SelectionProof: duty.SlotSignature, + return createVersionedElectraAggregateAndProof(duty, aggregateAttestation.Version, aggregateAttestation.Fulu) + case spec.DataVersionGloas: + if aggregateAttestation.Gloas == nil { + return nil, errors.New("no gloas attestation") } - versionedAggregateAndProof := &spec.VersionedAggregateAndProof{ + return &spec.VersionedAggregateAndProof{ Version: aggregateAttestation.Version, - Fulu: aggregateAndProof, - } - return versionedAggregateAndProof, nil + Gloas: &gloas.AggregateAndProof{ + AggregatorIndex: duty.ValidatorIndex, + Aggregate: aggregateAttestation.Gloas, + SelectionProof: duty.SlotSignature, + }, + }, nil default: - return &spec.VersionedAggregateAndProof{}, errors.New("unknown version") + return nil, errors.New("unknown version") } } func createVersionedSignedAggregateAndProof(aggregateAndProof *spec.VersionedAggregateAndProof, sig phase0.BLSSignature) (*spec.VersionedSignedAggregateAndProof, error) { switch aggregateAndProof.Version { case spec.DataVersionPhase0: - if aggregateAndProof.Phase0 == nil { - return nil, errors.New("no phase0 aggregate and proof") - } - signedAggregateAndProof := &phase0.SignedAggregateAndProof{ - Message: aggregateAndProof.Phase0, - Signature: sig, - } - signedVersionedAggregateAndProof := &spec.VersionedSignedAggregateAndProof{ - Version: aggregateAndProof.Version, - Phase0: signedAggregateAndProof, - } - return signedVersionedAggregateAndProof, nil + return createVersionedPhase0SignedAggregateAndProof(aggregateAndProof.Version, aggregateAndProof.Phase0, sig) case spec.DataVersionAltair: - if aggregateAndProof.Altair == nil { - return nil, errors.New("no altair aggregate and proof") - } - signedAggregateAndProof := &phase0.SignedAggregateAndProof{ - Message: aggregateAndProof.Altair, - Signature: sig, - } - signedVersionedAggregateAndProof := &spec.VersionedSignedAggregateAndProof{ - Version: aggregateAndProof.Version, - Altair: signedAggregateAndProof, - } - return signedVersionedAggregateAndProof, nil + return createVersionedPhase0SignedAggregateAndProof(aggregateAndProof.Version, aggregateAndProof.Altair, sig) case spec.DataVersionBellatrix: - if aggregateAndProof.Bellatrix == nil { - return nil, errors.New("no bellatrix aggregate and proof") - } - signedAggregateAndProof := &phase0.SignedAggregateAndProof{ - Message: aggregateAndProof.Bellatrix, - Signature: sig, - } - signedVersionedAggregateAndProof := &spec.VersionedSignedAggregateAndProof{ - Version: aggregateAndProof.Version, - Bellatrix: signedAggregateAndProof, - } - return signedVersionedAggregateAndProof, nil + return createVersionedPhase0SignedAggregateAndProof(aggregateAndProof.Version, aggregateAndProof.Bellatrix, sig) case spec.DataVersionCapella: - if aggregateAndProof.Capella == nil { - return nil, errors.New("no capella aggregate and proof") - } - signedAggregateAndProof := &phase0.SignedAggregateAndProof{ - Message: aggregateAndProof.Capella, - Signature: sig, - } - signedVersionedAggregateAndProof := &spec.VersionedSignedAggregateAndProof{ - Version: aggregateAndProof.Version, - Capella: signedAggregateAndProof, - } - return signedVersionedAggregateAndProof, nil + return createVersionedPhase0SignedAggregateAndProof(aggregateAndProof.Version, aggregateAndProof.Capella, sig) case spec.DataVersionDeneb: - if aggregateAndProof.Deneb == nil { - return nil, errors.New("no deneb aggregate and proof") - } - signedAggregateAndProof := &phase0.SignedAggregateAndProof{ - Message: aggregateAndProof.Deneb, - Signature: sig, - } - signedVersionedAggregateAndProof := &spec.VersionedSignedAggregateAndProof{ - Version: aggregateAndProof.Version, - Deneb: signedAggregateAndProof, - } - return signedVersionedAggregateAndProof, nil + return createVersionedPhase0SignedAggregateAndProof(aggregateAndProof.Version, aggregateAndProof.Deneb, sig) case spec.DataVersionElectra: - if aggregateAndProof.Electra == nil { - return nil, errors.New("no electra aggregate and proof") - } - signedAggregateAndProof := &electra.SignedAggregateAndProof{ - Message: aggregateAndProof.Electra, - Signature: sig, - } - signedVersionedAggregateAndProof := &spec.VersionedSignedAggregateAndProof{ - Version: aggregateAndProof.Version, - Electra: signedAggregateAndProof, - } - return signedVersionedAggregateAndProof, nil + return createVersionedElectraSignedAggregateAndProof(aggregateAndProof.Version, aggregateAndProof.Electra, sig) case spec.DataVersionFulu: - if aggregateAndProof.Fulu == nil { - return nil, errors.New("no fulu aggregate and proof") - } - signedAggregateAndProof := &electra.SignedAggregateAndProof{ - Message: aggregateAndProof.Fulu, - Signature: sig, + return createVersionedElectraSignedAggregateAndProof(aggregateAndProof.Version, aggregateAndProof.Fulu, sig) + case spec.DataVersionGloas: + if aggregateAndProof.Gloas == nil { + return nil, errors.New("no gloas aggregate and proof") } - signedVersionedAggregateAndProof := &spec.VersionedSignedAggregateAndProof{ + return &spec.VersionedSignedAggregateAndProof{ Version: aggregateAndProof.Version, - Fulu: signedAggregateAndProof, - } - return signedVersionedAggregateAndProof, nil + Gloas: &gloas.SignedAggregateAndProof{ + Message: aggregateAndProof.Gloas, + Signature: sig, + }, + }, nil + default: + return nil, errors.New("unknown version") + } +} + +func createVersionedPhase0AggregateAndProof(duty *attestationaggregator.Duty, + version spec.DataVersion, + aggregateAttestation *phase0.Attestation, +) (*spec.VersionedAggregateAndProof, error) { + if aggregateAttestation == nil { + return nil, errors.New("no " + version.String() + " attestation") + } + aggregateAndProof := &phase0.AggregateAndProof{ + AggregatorIndex: duty.ValidatorIndex, + Aggregate: aggregateAttestation, + SelectionProof: duty.SlotSignature, + } + result := &spec.VersionedAggregateAndProof{Version: version} + switch version { + case spec.DataVersionPhase0: + result.Phase0 = aggregateAndProof + case spec.DataVersionAltair: + result.Altair = aggregateAndProof + case spec.DataVersionBellatrix: + result.Bellatrix = aggregateAndProof + case spec.DataVersionCapella: + result.Capella = aggregateAndProof + case spec.DataVersionDeneb: + result.Deneb = aggregateAndProof + default: + return nil, errors.New("unknown version") + } + return result, nil +} + +func createVersionedElectraAggregateAndProof(duty *attestationaggregator.Duty, + version spec.DataVersion, + aggregateAttestation *electra.Attestation, +) (*spec.VersionedAggregateAndProof, error) { + if aggregateAttestation == nil { + return nil, errors.New("no " + version.String() + " attestation") + } + aggregateAndProof := &electra.AggregateAndProof{ + AggregatorIndex: duty.ValidatorIndex, + Aggregate: aggregateAttestation, + SelectionProof: duty.SlotSignature, + } + result := &spec.VersionedAggregateAndProof{Version: version} + switch version { + case spec.DataVersionElectra: + result.Electra = aggregateAndProof + case spec.DataVersionFulu: + result.Fulu = aggregateAndProof + default: + return nil, errors.New("unknown version") + } + return result, nil +} + +func createVersionedPhase0SignedAggregateAndProof(version spec.DataVersion, + aggregateAndProof *phase0.AggregateAndProof, + sig phase0.BLSSignature, +) (*spec.VersionedSignedAggregateAndProof, error) { + if aggregateAndProof == nil { + return nil, errors.New("no " + version.String() + " aggregate and proof") + } + signedAggregateAndProof := &phase0.SignedAggregateAndProof{ + Message: aggregateAndProof, + Signature: sig, + } + result := &spec.VersionedSignedAggregateAndProof{Version: version} + switch version { + case spec.DataVersionPhase0: + result.Phase0 = signedAggregateAndProof + case spec.DataVersionAltair: + result.Altair = signedAggregateAndProof + case spec.DataVersionBellatrix: + result.Bellatrix = signedAggregateAndProof + case spec.DataVersionCapella: + result.Capella = signedAggregateAndProof + case spec.DataVersionDeneb: + result.Deneb = signedAggregateAndProof + default: + return nil, errors.New("unknown version") + } + return result, nil +} + +func createVersionedElectraSignedAggregateAndProof(version spec.DataVersion, + aggregateAndProof *electra.AggregateAndProof, + sig phase0.BLSSignature, +) (*spec.VersionedSignedAggregateAndProof, error) { + if aggregateAndProof == nil { + return nil, errors.New("no " + version.String() + " aggregate and proof") + } + signedAggregateAndProof := &electra.SignedAggregateAndProof{ + Message: aggregateAndProof, + Signature: sig, + } + result := &spec.VersionedSignedAggregateAndProof{Version: version} + switch version { + case spec.DataVersionElectra: + result.Electra = signedAggregateAndProof + case spec.DataVersionFulu: + result.Fulu = signedAggregateAndProof default: - return &spec.VersionedSignedAggregateAndProof{}, errors.New("unknown version") + return nil, errors.New("unknown version") } + return result, nil } // AggregatorsAndSignatures reports signatures and whether validators are attestation aggregators for a given slot. diff --git a/services/attestationaggregator/standard/service_internal_test.go b/services/attestationaggregator/standard/service_internal_test.go new file mode 100644 index 00000000..d851ce8e --- /dev/null +++ b/services/attestationaggregator/standard/service_internal_test.go @@ -0,0 +1,299 @@ +// Copyright © 2026 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 standard + +import ( + "testing" + + "github.com/attestantio/go-eth2-client/spec" + "github.com/attestantio/go-eth2-client/spec/electra" + "github.com/attestantio/go-eth2-client/spec/gloas" + "github.com/attestantio/go-eth2-client/spec/phase0" + "github.com/attestantio/vouch/services/attestationaggregator" + "github.com/stretchr/testify/require" +) + +// testAggregateSlot is the slot carried by the attestations the tests supply. Asserting on it +// proves the conversion carried the supplied attestation through rather than leaving it nil. +const testAggregateSlot = phase0.Slot(99) + +// aggregateOf returns the aggregate attestation held by the arm the version names, so a +// conversion that populated the wrong arm - or left the aggregate nil - is visible to the caller. +func aggregateOf(t *testing.T, aggregateAndProof *spec.VersionedAggregateAndProof) any { + t.Helper() + + switch aggregateAndProof.Version { + case spec.DataVersionPhase0: + return aggregateAndProof.Phase0.Aggregate + case spec.DataVersionAltair: + return aggregateAndProof.Altair.Aggregate + case spec.DataVersionBellatrix: + return aggregateAndProof.Bellatrix.Aggregate + case spec.DataVersionCapella: + return aggregateAndProof.Capella.Aggregate + case spec.DataVersionDeneb: + return aggregateAndProof.Deneb.Aggregate + case spec.DataVersionElectra: + return aggregateAndProof.Electra.Aggregate + case spec.DataVersionFulu: + return aggregateAndProof.Fulu.Aggregate + case spec.DataVersionGloas: + return aggregateAndProof.Gloas.Aggregate + default: + require.FailNow(t, "no aggregate for version", aggregateAndProof.Version.String()) + + return nil + } +} + +func TestCreateVersionedAggregateAndProof(t *testing.T) { + duty := &attestationaggregator.Duty{ + ValidatorIndex: phase0.ValidatorIndex(42), + SlotSignature: phase0.BLSSignature{1}, + } + phase0Attestation := &phase0.Attestation{Data: &phase0.AttestationData{Slot: testAggregateSlot}} + electraAttestation := &electra.Attestation{Data: &phase0.AttestationData{Slot: testAggregateSlot}} + gloasAttestation := &gloas.Attestation{Data: &phase0.AttestationData{Slot: testAggregateSlot}} + + tests := []struct { + name string + versionedAttestation *spec.VersionedAttestation + expectedAggregate any + }{ + { + name: "Phase0", + versionedAttestation: &spec.VersionedAttestation{ + Version: spec.DataVersionPhase0, + Phase0: phase0Attestation, + }, + expectedAggregate: phase0Attestation, + }, + { + name: "Altair", + versionedAttestation: &spec.VersionedAttestation{ + Version: spec.DataVersionAltair, + Altair: phase0Attestation, + }, + expectedAggregate: phase0Attestation, + }, + { + name: "Bellatrix", + versionedAttestation: &spec.VersionedAttestation{ + Version: spec.DataVersionBellatrix, + Bellatrix: phase0Attestation, + }, + expectedAggregate: phase0Attestation, + }, + { + name: "Capella", + versionedAttestation: &spec.VersionedAttestation{ + Version: spec.DataVersionCapella, + Capella: phase0Attestation, + }, + expectedAggregate: phase0Attestation, + }, + { + name: "Deneb", + versionedAttestation: &spec.VersionedAttestation{ + Version: spec.DataVersionDeneb, + Deneb: phase0Attestation, + }, + expectedAggregate: phase0Attestation, + }, + { + name: "Electra", + versionedAttestation: &spec.VersionedAttestation{ + Version: spec.DataVersionElectra, + Electra: electraAttestation, + }, + expectedAggregate: electraAttestation, + }, + { + name: "Fulu", + versionedAttestation: &spec.VersionedAttestation{ + Version: spec.DataVersionFulu, + Fulu: electraAttestation, + }, + expectedAggregate: electraAttestation, + }, + { + name: "Gloas", + versionedAttestation: &spec.VersionedAttestation{ + Version: spec.DataVersionGloas, + Gloas: gloasAttestation, + }, + expectedAggregate: gloasAttestation, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + result, err := createVersionedAggregateAndProof(duty, test.versionedAttestation) + require.NoError(t, err) + require.Equal(t, test.versionedAttestation.Version, result.Version) + aggregatorIndex, err := result.AggregatorIndex() + require.NoError(t, err) + require.Equal(t, duty.ValidatorIndex, aggregatorIndex) + selectionProof, err := result.SelectionProof() + require.NoError(t, err) + require.Equal(t, duty.SlotSignature, selectionProof) + require.Same(t, test.expectedAggregate, aggregateOf(t, result)) + }) + } +} + +func TestCreateVersionedSignedAggregateAndProof(t *testing.T) { + sig := phase0.BLSSignature{2} + phase0AggregateAndProof := &phase0.AggregateAndProof{ + Aggregate: &phase0.Attestation{Data: &phase0.AttestationData{Slot: testAggregateSlot}}, + } + electraAggregateAndProof := &electra.AggregateAndProof{ + Aggregate: &electra.Attestation{Data: &phase0.AttestationData{Slot: testAggregateSlot}}, + } + gloasAggregateAndProof := &gloas.AggregateAndProof{ + Aggregate: &gloas.Attestation{Data: &phase0.AttestationData{Slot: testAggregateSlot}}, + } + + tests := []struct { + name string + versionedAggregateAndProof *spec.VersionedAggregateAndProof + }{ + { + name: "Phase0", + versionedAggregateAndProof: &spec.VersionedAggregateAndProof{ + Version: spec.DataVersionPhase0, + Phase0: phase0AggregateAndProof, + }, + }, + { + name: "Altair", + versionedAggregateAndProof: &spec.VersionedAggregateAndProof{ + Version: spec.DataVersionAltair, + Altair: phase0AggregateAndProof, + }, + }, + { + name: "Bellatrix", + versionedAggregateAndProof: &spec.VersionedAggregateAndProof{ + Version: spec.DataVersionBellatrix, + Bellatrix: phase0AggregateAndProof, + }, + }, + { + name: "Capella", + versionedAggregateAndProof: &spec.VersionedAggregateAndProof{ + Version: spec.DataVersionCapella, + Capella: phase0AggregateAndProof, + }, + }, + { + name: "Deneb", + versionedAggregateAndProof: &spec.VersionedAggregateAndProof{ + Version: spec.DataVersionDeneb, + Deneb: phase0AggregateAndProof, + }, + }, + { + name: "Electra", + versionedAggregateAndProof: &spec.VersionedAggregateAndProof{ + Version: spec.DataVersionElectra, + Electra: electraAggregateAndProof, + }, + }, + { + name: "Fulu", + versionedAggregateAndProof: &spec.VersionedAggregateAndProof{ + Version: spec.DataVersionFulu, + Fulu: electraAggregateAndProof, + }, + }, + { + name: "Gloas", + versionedAggregateAndProof: &spec.VersionedAggregateAndProof{ + Version: spec.DataVersionGloas, + Gloas: gloasAggregateAndProof, + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + result, err := createVersionedSignedAggregateAndProof(test.versionedAggregateAndProof, sig) + require.NoError(t, err) + require.Equal(t, test.versionedAggregateAndProof.Version, result.Version) + signature, err := result.Signature() + require.NoError(t, err) + require.Equal(t, sig, signature) + // Slot() reads through Message.Aggregate.Data without a nil guard, and the multinode + // submitter calls it on every submission, so this proves the message was carried + // through rather than left nil. + slot, err := result.Slot() + require.NoError(t, err) + require.Equal(t, testAggregateSlot, slot) + }) + } +} + +func TestCreateVersionedAggregateAndProofRejectsMissingArms(t *testing.T) { + duty := &attestationaggregator.Duty{} + tests := []struct { + name string + version spec.DataVersion + err string + }{ + {name: "Phase0", version: spec.DataVersionPhase0, err: "no phase0 attestation"}, + {name: "Altair", version: spec.DataVersionAltair, err: "no altair attestation"}, + {name: "Bellatrix", version: spec.DataVersionBellatrix, err: "no bellatrix attestation"}, + {name: "Capella", version: spec.DataVersionCapella, err: "no capella attestation"}, + {name: "Deneb", version: spec.DataVersionDeneb, err: "no deneb attestation"}, + {name: "Electra", version: spec.DataVersionElectra, err: "no electra attestation"}, + {name: "Fulu", version: spec.DataVersionFulu, err: "no fulu attestation"}, + {name: "Gloas", version: spec.DataVersionGloas, err: "no gloas attestation"}, + {name: "Unknown", version: spec.DataVersionUnknown, err: "unknown version"}, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + result, err := createVersionedAggregateAndProof(duty, &spec.VersionedAttestation{Version: test.version}) + require.Nil(t, result) + require.EqualError(t, err, test.err) + }) + } +} + +func TestCreateVersionedSignedAggregateAndProofRejectsMissingArms(t *testing.T) { + tests := []struct { + name string + version spec.DataVersion + err string + }{ + {name: "Phase0", version: spec.DataVersionPhase0, err: "no phase0 aggregate and proof"}, + {name: "Altair", version: spec.DataVersionAltair, err: "no altair aggregate and proof"}, + {name: "Bellatrix", version: spec.DataVersionBellatrix, err: "no bellatrix aggregate and proof"}, + {name: "Capella", version: spec.DataVersionCapella, err: "no capella aggregate and proof"}, + {name: "Deneb", version: spec.DataVersionDeneb, err: "no deneb aggregate and proof"}, + {name: "Electra", version: spec.DataVersionElectra, err: "no electra aggregate and proof"}, + {name: "Fulu", version: spec.DataVersionFulu, err: "no fulu aggregate and proof"}, + {name: "Gloas", version: spec.DataVersionGloas, err: "no gloas aggregate and proof"}, + {name: "Unknown", version: spec.DataVersionUnknown, err: "unknown version"}, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + result, err := createVersionedSignedAggregateAndProof(&spec.VersionedAggregateAndProof{Version: test.version}, phase0.BLSSignature{}) + require.Nil(t, result) + require.EqualError(t, err, test.err) + }) + } +}