From bb3856f3b3d42139e436ae5e99e1381898f45ccb Mon Sep 17 00:00:00 2001 From: Enrico Del Fante Date: Wed, 25 Sep 2024 17:45:03 +0200 Subject: [PATCH] SingleAttestation --- .../attestation/ValidatableAttestation.java | 40 ++++++++++ .../operations/Attestation.java | 17 +++- .../operations/AttestationSchema.java | 5 ++ .../operations/SingleAttestation.java | 79 +++++++++++++++++++ .../operations/SingleAttestationSchema.java | 70 ++++++++++++++++ .../versions/electra/AttestationElectra.java | 14 ---- .../versions/phase0/AttestationPhase0.java | 14 ---- .../electra/util/AttestationUtilElectra.java | 66 ++++++++++++++++ .../schemas/SchemaDefinitionsElectra.java | 10 +++ .../MatchingDataAttestationGroup.java | 10 +-- .../validation/AttestationValidator.java | 25 +++--- .../AttestationSubnetSubscriptions.java | 10 ++- 12 files changed, 315 insertions(+), 45 deletions(-) create mode 100644 ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/operations/SingleAttestation.java create mode 100644 ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/operations/SingleAttestationSchema.java diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/attestation/ValidatableAttestation.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/attestation/ValidatableAttestation.java index 7e50d6acefb..909b06c2e7f 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/attestation/ValidatableAttestation.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/attestation/ValidatableAttestation.java @@ -24,6 +24,7 @@ import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Supplier; import org.apache.tuweni.bytes.Bytes32; +import tech.pegasys.teku.infrastructure.ssz.collections.SszBitlist; import tech.pegasys.teku.infrastructure.unsigned.UInt64; import tech.pegasys.teku.spec.Spec; import tech.pegasys.teku.spec.constants.Domain; @@ -31,6 +32,8 @@ import tech.pegasys.teku.spec.datastructures.operations.AttestationData; import tech.pegasys.teku.spec.datastructures.operations.IndexedAttestation; import tech.pegasys.teku.spec.datastructures.operations.SignedAggregateAndProof; +import tech.pegasys.teku.spec.datastructures.operations.versions.electra.AttestationElectra; +import tech.pegasys.teku.spec.datastructures.operations.versions.electra.AttestationElectraSchema; import tech.pegasys.teku.spec.datastructures.state.beaconstate.BeaconState; public class ValidatableAttestation { @@ -49,6 +52,33 @@ public class ValidatableAttestation { private volatile Optional committeeShufflingSeed = Optional.empty(); private volatile Optional committeesSize = Optional.empty(); + private volatile Optional singleAttestationAggregationBits = Optional.empty(); + + public ValidatableAttestation convertFromSingleAttestationIfRequired() { + if (!attestation.isSingleAttestation()) { + return this; + } + + final AttestationElectraSchema attestationElectraSchema = + spec.atSlot(attestation.getData().getSlot()) + .getSchemaDefinitions() + .getAttestationSchema() + .toVersionElectra() + .orElseThrow(); + + final AttestationElectra convertedAttestation = + attestationElectraSchema.create( + singleAttestationAggregationBits.orElseThrow(), + attestation.getData(), + attestation.getAggregateSignature(), + attestationElectraSchema + .getCommitteeBitsSchema() + .orElseThrow() + .ofBits(attestation.getFirstCommitteeIndex().intValue())); + + return from(spec, convertedAttestation); + } + public static ValidatableAttestation from(final Spec spec, final Attestation attestation) { return new ValidatableAttestation( spec, attestation, Optional.empty(), OptionalInt.empty(), false); @@ -204,6 +234,15 @@ private void saveCommitteesSize(final BeaconState state) { this.committeesSize = Optional.of(committeesSize); } + public Optional getSingleAttestationAggregationBits() { + return singleAttestationAggregationBits; + } + + public void setSingleAttestationAggregationBits( + final SszBitlist singleAttestationAggregationBits) { + this.singleAttestationAggregationBits = Optional.of(singleAttestationAggregationBits); + } + public boolean isGossiped() { return gossiped.get(); } @@ -273,6 +312,7 @@ public String toString() { .add("committeeShufflingSeed", committeeShufflingSeed) .add("committeesSize", committeesSize) .add("receivedSubnetId", receivedSubnetId) + .add("singleAttestationAggregationBits", singleAttestationAggregationBits) .toString(); } } diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/operations/Attestation.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/operations/Attestation.java index 57f0ff3f234..03be364170b 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/operations/Attestation.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/operations/Attestation.java @@ -13,6 +13,7 @@ package tech.pegasys.teku.spec.datastructures.operations; +import com.google.common.collect.Sets; import java.util.Collection; import java.util.List; import java.util.Optional; @@ -34,9 +35,13 @@ public interface Attestation extends SszContainer { @Override AttestationSchema getSchema(); - UInt64 getEarliestSlotForForkChoiceProcessing(final Spec spec); + default UInt64 getEarliestSlotForForkChoiceProcessing(final Spec spec) { + return getData().getEarliestSlotForForkChoice(spec); + } - Collection getDependentBlockRoots(); + default Collection getDependentBlockRoots() { + return Sets.newHashSet(getData().getTarget().getRoot(), getData().getBeaconBlockRoot()); + } AttestationData getData(); @@ -65,4 +70,12 @@ default List getCommitteeIndicesRequired() { } boolean requiresCommitteeBits(); + + default boolean isSingleAttestation() { + return false; + } + + default Optional getValidatorIndex() { + return Optional.empty(); + } } diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/operations/AttestationSchema.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/operations/AttestationSchema.java index 3b0b0f880ec..7c7569d5598 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/operations/AttestationSchema.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/operations/AttestationSchema.java @@ -41,6 +41,11 @@ default SszBitlist createEmptyAggregationBits() { return bitsSchema.ofBits(Math.toIntExact(bitsSchema.getMaxLength())); } + default SszBitlist createAggregationBitsOf(final int... indices) { + final SszBitlistSchema bitsSchema = getAggregationBitsSchema(); + return bitsSchema.ofBits(Math.toIntExact(bitsSchema.getMaxLength()), indices); + } + default Optional createEmptyCommitteeBits() { return getCommitteeBitsSchema().map(SszBitvectorSchema::ofBits); } diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/operations/SingleAttestation.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/operations/SingleAttestation.java new file mode 100644 index 00000000000..4510bd31d99 --- /dev/null +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/operations/SingleAttestation.java @@ -0,0 +1,79 @@ +/* + * Copyright Consensys Software Inc., 2024 + * + * 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 tech.pegasys.teku.spec.datastructures.operations; + +import tech.pegasys.teku.bls.BLSSignature; +import tech.pegasys.teku.infrastructure.ssz.collections.SszBitlist; +import tech.pegasys.teku.infrastructure.ssz.containers.Container4; +import tech.pegasys.teku.infrastructure.ssz.primitive.SszUInt64; +import tech.pegasys.teku.infrastructure.ssz.tree.TreeNode; +import tech.pegasys.teku.infrastructure.unsigned.UInt64; +import tech.pegasys.teku.spec.datastructures.type.SszSignature; + +public class SingleAttestation + extends Container4 + implements Attestation { + public SingleAttestation(final SingleAttestationSchema type, final TreeNode backingNode) { + super(type, backingNode); + } + + public SingleAttestation( + final SingleAttestationSchema schema, + final UInt64 committeeIndex, + final UInt64 validatorIndex, + final AttestationData data, + final BLSSignature signature) { + super( + schema, + SszUInt64.of(committeeIndex), + SszUInt64.of(validatorIndex), + data, + new SszSignature(signature)); + } + + @Override + public SingleAttestationSchema getSchema() { + return (SingleAttestationSchema) super.getSchema(); + } + + @Override + public AttestationData getData() { + return getField2(); + } + + @Override + public SszBitlist getAggregationBits() { + throw new UnsupportedOperationException("Not supported in SingleAttestation"); + } + + @Override + public UInt64 getFirstCommitteeIndex() { + return getField0().get(); + } + + @Override + public BLSSignature getAggregateSignature() { + return getField3().getSignature(); + } + + @Override + public boolean requiresCommitteeBits() { + return false; + } + + @Override + public boolean isSingleAttestation() { + return true; + } +} diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/operations/SingleAttestationSchema.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/operations/SingleAttestationSchema.java new file mode 100644 index 00000000000..c458757df55 --- /dev/null +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/operations/SingleAttestationSchema.java @@ -0,0 +1,70 @@ +/* + * Copyright Consensys Software Inc., 2024 + * + * 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 tech.pegasys.teku.spec.datastructures.operations; + +import java.util.Optional; +import java.util.function.Supplier; +import tech.pegasys.teku.bls.BLSSignature; +import tech.pegasys.teku.infrastructure.ssz.collections.SszBitlist; +import tech.pegasys.teku.infrastructure.ssz.collections.SszBitvector; +import tech.pegasys.teku.infrastructure.ssz.containers.ContainerSchema4; +import tech.pegasys.teku.infrastructure.ssz.primitive.SszUInt64; +import tech.pegasys.teku.infrastructure.ssz.schema.SszPrimitiveSchemas; +import tech.pegasys.teku.infrastructure.ssz.schema.collections.SszBitlistSchema; +import tech.pegasys.teku.infrastructure.ssz.schema.collections.SszBitvectorSchema; +import tech.pegasys.teku.infrastructure.ssz.tree.TreeNode; +import tech.pegasys.teku.spec.datastructures.type.SszSignature; +import tech.pegasys.teku.spec.datastructures.type.SszSignatureSchema; + +public class SingleAttestationSchema + extends ContainerSchema4 + implements AttestationSchema { + public SingleAttestationSchema() { + super( + "SingleAttestation", + namedSchema("committee_index", SszPrimitiveSchemas.UINT64_SCHEMA), + namedSchema("attester_index", SszPrimitiveSchemas.UINT64_SCHEMA), + namedSchema("data", AttestationData.SSZ_SCHEMA), + namedSchema("signature", SszSignatureSchema.INSTANCE)); + } + + @Override + public SingleAttestation createFromBackingNode(final TreeNode node) { + return new SingleAttestation(this, node); + } + + @Override + public Attestation create( + final SszBitlist aggregationBits, + final AttestationData data, + final BLSSignature signature, + final Supplier committeeBits) { + throw new UnsupportedOperationException("Not supported in SingleAttestation"); + } + + @Override + public SszBitlistSchema getAggregationBitsSchema() { + throw new UnsupportedOperationException("Not supported in SingleAttestation"); + } + + @Override + public Optional> getCommitteeBitsSchema() { + return Optional.empty(); + } + + @Override + public boolean requiresCommitteeBits() { + return false; + } +} diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/operations/versions/electra/AttestationElectra.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/operations/versions/electra/AttestationElectra.java index 1842c46e897..cebb76f5cb3 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/operations/versions/electra/AttestationElectra.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/operations/versions/electra/AttestationElectra.java @@ -13,18 +13,14 @@ package tech.pegasys.teku.spec.datastructures.operations.versions.electra; -import com.google.common.collect.Sets; -import java.util.Collection; import java.util.List; import java.util.Optional; -import org.apache.tuweni.bytes.Bytes32; import tech.pegasys.teku.bls.BLSSignature; import tech.pegasys.teku.infrastructure.ssz.collections.SszBitlist; import tech.pegasys.teku.infrastructure.ssz.collections.SszBitvector; import tech.pegasys.teku.infrastructure.ssz.containers.Container4; import tech.pegasys.teku.infrastructure.ssz.tree.TreeNode; import tech.pegasys.teku.infrastructure.unsigned.UInt64; -import tech.pegasys.teku.spec.Spec; import tech.pegasys.teku.spec.datastructures.operations.Attestation; import tech.pegasys.teku.spec.datastructures.operations.AttestationData; import tech.pegasys.teku.spec.datastructures.type.SszSignature; @@ -51,16 +47,6 @@ public AttestationElectraSchema getSchema() { return (AttestationElectraSchema) super.getSchema(); } - @Override - public UInt64 getEarliestSlotForForkChoiceProcessing(final Spec spec) { - return getData().getEarliestSlotForForkChoice(spec); - } - - @Override - public Collection getDependentBlockRoots() { - return Sets.newHashSet(getData().getTarget().getRoot(), getData().getBeaconBlockRoot()); - } - @Override public SszBitlist getAggregationBits() { return getField0(); diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/operations/versions/phase0/AttestationPhase0.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/operations/versions/phase0/AttestationPhase0.java index dd343f37361..e7d464a43aa 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/operations/versions/phase0/AttestationPhase0.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/operations/versions/phase0/AttestationPhase0.java @@ -13,15 +13,11 @@ package tech.pegasys.teku.spec.datastructures.operations.versions.phase0; -import com.google.common.collect.Sets; -import java.util.Collection; -import org.apache.tuweni.bytes.Bytes32; import tech.pegasys.teku.bls.BLSSignature; import tech.pegasys.teku.infrastructure.ssz.collections.SszBitlist; import tech.pegasys.teku.infrastructure.ssz.containers.Container3; import tech.pegasys.teku.infrastructure.ssz.tree.TreeNode; import tech.pegasys.teku.infrastructure.unsigned.UInt64; -import tech.pegasys.teku.spec.Spec; import tech.pegasys.teku.spec.datastructures.operations.Attestation; import tech.pegasys.teku.spec.datastructures.operations.AttestationData; import tech.pegasys.teku.spec.datastructures.type.SszSignature; @@ -47,16 +43,6 @@ public AttestationPhase0Schema getSchema() { return (AttestationPhase0Schema) super.getSchema(); } - @Override - public UInt64 getEarliestSlotForForkChoiceProcessing(final Spec spec) { - return getData().getEarliestSlotForForkChoice(spec); - } - - @Override - public Collection getDependentBlockRoots() { - return Sets.newHashSet(getData().getTarget().getRoot(), getData().getBeaconBlockRoot()); - } - @Override public SszBitlist getAggregationBits() { return getField0(); diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/electra/util/AttestationUtilElectra.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/electra/util/AttestationUtilElectra.java index 943536e7bf6..7404783ed84 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/electra/util/AttestationUtilElectra.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/electra/util/AttestationUtilElectra.java @@ -13,19 +13,28 @@ package tech.pegasys.teku.spec.logic.versions.electra.util; +import static com.google.common.base.Preconditions.checkArgument; + import it.unimi.dsi.fastutil.ints.IntArrayList; import it.unimi.dsi.fastutil.ints.IntList; import java.util.List; import java.util.stream.IntStream; +import tech.pegasys.teku.infrastructure.async.SafeFuture; import tech.pegasys.teku.infrastructure.ssz.collections.SszBitlist; import tech.pegasys.teku.infrastructure.unsigned.UInt64; import tech.pegasys.teku.spec.config.SpecConfig; +import tech.pegasys.teku.spec.datastructures.attestation.ValidatableAttestation; import tech.pegasys.teku.spec.datastructures.blocks.BeaconBlockSummary; import tech.pegasys.teku.spec.datastructures.operations.Attestation; import tech.pegasys.teku.spec.datastructures.operations.AttestationData; +import tech.pegasys.teku.spec.datastructures.operations.IndexedAttestation; +import tech.pegasys.teku.spec.datastructures.operations.IndexedAttestationSchema; +import tech.pegasys.teku.spec.datastructures.state.Fork; import tech.pegasys.teku.spec.datastructures.state.beaconstate.BeaconState; +import tech.pegasys.teku.spec.datastructures.util.AttestationProcessingResult; import tech.pegasys.teku.spec.logic.common.helpers.BeaconStateAccessors; import tech.pegasys.teku.spec.logic.common.helpers.MiscHelpers; +import tech.pegasys.teku.spec.logic.common.util.AsyncBLSSignatureVerifier; import tech.pegasys.teku.spec.logic.versions.deneb.util.AttestationUtilDeneb; import tech.pegasys.teku.spec.schemas.SchemaDefinitions; @@ -93,4 +102,61 @@ public AttestationData getGenericAttestationData( final UInt64 committeeIndex) { return super.getGenericAttestationData(slot, state, block, UInt64.ZERO); } + + @Override + public IndexedAttestation getIndexedAttestation( + final BeaconState state, final Attestation attestation) { + if (attestation.isSingleAttestation()) { + final IndexedAttestationSchema indexedAttestationSchema = + schemaDefinitions.getIndexedAttestationSchema(); + + return indexedAttestationSchema.create( + indexedAttestationSchema + .getAttestingIndicesSchema() + .of(attestation.getValidatorIndex().orElseThrow()), + attestation.getData(), + attestation.getAggregateSignature()); + } + return super.getIndexedAttestation(state, attestation); + } + + @Override + public SafeFuture isValidIndexedAttestationAsync( + final Fork fork, + final BeaconState state, + final ValidatableAttestation attestation, + final AsyncBLSSignatureVerifier blsSignatureVerifier) { + + return super.isValidIndexedAttestationAsync(fork, state, attestation, blsSignatureVerifier) + .thenPeek( + result -> { + if (result.isSuccessful() + && attestation.getAttestation().isSingleAttestation() + && attestation.getSingleAttestationAggregationBits().isEmpty()) { + attestation.setSingleAttestationAggregationBits( + getSingleAttestationAggregationBits(state, attestation.getAttestation())); + } + }); + } + + private SszBitlist getSingleAttestationAggregationBits( + final BeaconState state, final Attestation attestation) { + checkArgument(attestation.isSingleAttestation(), "Expecting single attestation"); + + final IntList committee = + beaconStateAccessors.getBeaconCommittee( + state, attestation.getData().getSlot(), attestation.getFirstCommitteeIndex()); + + int validatorIndex = attestation.getValidatorIndex().orElseThrow().intValue(); + + int validatorCommitteeBit = committee.indexOf(validatorIndex); + + checkArgument( + validatorCommitteeBit >= 0, + "Validator index %s is not part of the committee %s", + validatorIndex, + attestation.getFirstCommitteeIndex()); + + return attestation.getSchema().createAggregationBitsOf(validatorCommitteeBit); + } } diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/schemas/SchemaDefinitionsElectra.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/schemas/SchemaDefinitionsElectra.java index 4d6ecfd09eb..561c2323c3c 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/schemas/SchemaDefinitionsElectra.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/schemas/SchemaDefinitionsElectra.java @@ -57,6 +57,8 @@ import tech.pegasys.teku.spec.datastructures.operations.IndexedAttestation; import tech.pegasys.teku.spec.datastructures.operations.IndexedAttestationSchema; import tech.pegasys.teku.spec.datastructures.operations.SignedAggregateAndProof.SignedAggregateAndProofSchema; +import tech.pegasys.teku.spec.datastructures.operations.SingleAttestation; +import tech.pegasys.teku.spec.datastructures.operations.SingleAttestationSchema; import tech.pegasys.teku.spec.datastructures.operations.versions.electra.AttestationElectraSchema; import tech.pegasys.teku.spec.datastructures.operations.versions.electra.AttesterSlashingElectraSchema; import tech.pegasys.teku.spec.datastructures.operations.versions.electra.IndexedAttestationElectraSchema; @@ -107,6 +109,8 @@ public class SchemaDefinitionsElectra extends SchemaDefinitionsDeneb { pendingPartialWithdrawalSchema; private final PendingConsolidation.PendingConsolidationSchema pendingConsolidationSchema; + private final SingleAttestationSchema singleAttestationSchema; + public SchemaDefinitionsElectra(final SpecConfigElectra specConfig) { super(specConfig); @@ -118,6 +122,8 @@ public SchemaDefinitionsElectra(final SpecConfigElectra specConfig) { new AttesterSlashingElectraSchema(indexedAttestationSchema) .castTypeToAttesterSlashingSchema(); + this.singleAttestationSchema = new SingleAttestationSchema(); + this.attestationSchema = new AttestationElectraSchema( maxValidatorsPerAttestation, specConfig.getMaxCommitteesPerSlot()) @@ -351,6 +357,10 @@ public PendingBalanceDeposit.PendingBalanceDepositSchema getPendingBalanceDeposi return beaconStateSchema.getPendingPartialWithdrawalsSchema(); } + public AttestationSchema getSingleAttestationSchema() { + return singleAttestationSchema; + } + public PendingPartialWithdrawal.PendingPartialWithdrawalSchema getPendingPartialWithdrawalSchema() { return pendingPartialWithdrawalSchema; diff --git a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/attestation/MatchingDataAttestationGroup.java b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/attestation/MatchingDataAttestationGroup.java index 27303e656bd..ed64af01d5c 100644 --- a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/attestation/MatchingDataAttestationGroup.java +++ b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/attestation/MatchingDataAttestationGroup.java @@ -103,18 +103,18 @@ public AttestationData getAttestationData() { * @return True if the attestation was added, false otherwise */ public boolean add(final ValidatableAttestation attestation) { - if (includedValidators.isSuperSetOf(attestation.getAttestation())) { + final ValidatableAttestation converted = attestation.convertFromSingleAttestationIfRequired(); + if (includedValidators.isSuperSetOf(converted.getAttestation())) { // All attestation bits have already been included on chain return false; } if (committeeShufflingSeed.isEmpty()) { - committeeShufflingSeed = attestation.getCommitteeShufflingSeed(); + committeeShufflingSeed = converted.getCommitteeShufflingSeed(); } return attestationsByValidatorCount .computeIfAbsent( - attestation.getAttestation().getAggregationBits().getBitCount(), - count -> new HashSet<>()) - .add(attestation); + converted.getAttestation().getAggregationBits().getBitCount(), count -> new HashSet<>()) + .add(converted); } /** diff --git a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/validation/AttestationValidator.java b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/validation/AttestationValidator.java index f652f597585..041d1f9cb07 100644 --- a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/validation/AttestationValidator.java +++ b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/validation/AttestationValidator.java @@ -72,6 +72,11 @@ public SafeFuture validate( } private InternalValidationResult singleAttestationChecks(final Attestation attestation) { + // if it is a SingleAttestation type we are guaranteed to be a valid single attestation + if (attestation.isSingleAttestation()) { + return InternalValidationResult.ACCEPT; + } + // The attestation is unaggregated -- that is, it has exactly one participating validator // (len([bit for bit in attestation.aggregation_bits if bit == 0b1]) == 1). final int bitCount = attestation.getAggregationBits().getBitCount(); @@ -167,15 +172,17 @@ SafeFuture singleOrAggregateAttestationChecks attestation.getFirstCommitteeIndex(), receivedOnSubnetId.getAsInt())); } - // [REJECT] The number of aggregation bits matches the committee size - final IntList committee = - spec.getBeaconCommittee( - state, data.getSlot(), attestation.getFirstCommitteeIndex()); - if (committee.size() != attestation.getAggregationBits().size()) { - return completedFuture( - InternalValidationResultWithState.reject( - "Aggregation bit size %s is greater than committee size %s", - attestation.getAggregationBits().size(), committee.size())); + if (!attestation.isSingleAttestation()) { + // [REJECT] The number of aggregation bits matches the committee size + final IntList committee = + spec.getBeaconCommittee( + state, data.getSlot(), attestation.getFirstCommitteeIndex()); + if (committee.size() != attestation.getAggregationBits().size()) { + return completedFuture( + InternalValidationResultWithState.reject( + "Aggregation bit size %s is greater than committee size %s", + attestation.getAggregationBits().size(), committee.size())); + } } return spec.isValidIndexedAttestation( diff --git a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/subnets/AttestationSubnetSubscriptions.java b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/subnets/AttestationSubnetSubscriptions.java index de1231d38c0..356a0e3036f 100644 --- a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/subnets/AttestationSubnetSubscriptions.java +++ b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/subnets/AttestationSubnetSubscriptions.java @@ -30,6 +30,8 @@ import tech.pegasys.teku.spec.datastructures.operations.Attestation; import tech.pegasys.teku.spec.datastructures.operations.AttestationSchema; import tech.pegasys.teku.spec.datastructures.state.ForkInfo; +import tech.pegasys.teku.spec.schemas.SchemaDefinitions; +import tech.pegasys.teku.spec.schemas.SchemaDefinitionsElectra; import tech.pegasys.teku.statetransition.util.DebugDataDumper; import tech.pegasys.teku.storage.client.RecentChainData; @@ -58,8 +60,14 @@ public AttestationSubnetSubscriptions( this.recentChainData = recentChainData; this.processor = processor; this.forkInfo = forkInfo; + final SchemaDefinitions schemaDefinitions = + spec.atEpoch(forkInfo.getFork().getEpoch()).getSchemaDefinitions(); attestationSchema = - spec.atEpoch(forkInfo.getFork().getEpoch()).getSchemaDefinitions().getAttestationSchema(); + schemaDefinitions + .toVersionElectra() + .>map( + SchemaDefinitionsElectra::getSingleAttestationSchema) + .orElse(schemaDefinitions.getAttestationSchema()); this.debugDataDumper = debugDataDumper; }