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 7198856fd2e..1d03ea97357 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 @@ -130,7 +130,7 @@ private ValidatableAttestation( this.attestation = attestation; this.unconvertedAttestation = attestation; this.receivedSubnetId = receivedSubnetId; - this.hashTreeRoot = Suppliers.memoize(attestation::hashTreeRoot); + this.hashTreeRoot = Suppliers.memoize(unconvertedAttestation::hashTreeRoot); this.producedLocally = producedLocally; } @@ -146,7 +146,7 @@ private ValidatableAttestation( this.attestation = attestation; this.unconvertedAttestation = attestation; this.receivedSubnetId = receivedSubnetId; - this.hashTreeRoot = Suppliers.memoize(attestation::hashTreeRoot); + this.hashTreeRoot = Suppliers.memoize(unconvertedAttestation::hashTreeRoot); this.producedLocally = producedLocally; this.committeesSize = Optional.of(committeeSizes); } diff --git a/ethereum/statetransition/src/jmh/java/tech/pegasys/teku/statetransition.validation.signatures/AggregatingAttestationPoolBenchmark.java b/ethereum/statetransition/src/jmh/java/tech/pegasys/teku/statetransition.validation.signatures/AggregatingAttestationPoolBenchmark.java index 5005850c954..c5d59c0a68e 100644 --- a/ethereum/statetransition/src/jmh/java/tech/pegasys/teku/statetransition.validation.signatures/AggregatingAttestationPoolBenchmark.java +++ b/ethereum/statetransition/src/jmh/java/tech/pegasys/teku/statetransition.validation.signatures/AggregatingAttestationPoolBenchmark.java @@ -21,6 +21,7 @@ import java.io.IOException; import java.nio.file.Files; import java.nio.file.Paths; +import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -91,9 +92,9 @@ public class AggregatingAttestationPoolBenchmark { record AttestationDataRootAndCommitteeIndex(Bytes32 attestationDataRoot, UInt64 committeeIndex) {} + private final List attestations = new ArrayList<>(); private BeaconState state; private BeaconState newBlockState; - private List attestations; private AggregatingAttestationPool pool; private RecentChainData recentChainData; private AttestationForkChecker attestationForkChecker; @@ -175,6 +176,7 @@ public void init() throws Exception { attestation -> { attestation.saveCommitteeShufflingSeedAndCommitteesSize(state); pool.add(attestation); + attestations.add(attestation); }); mostFrequentSingleAttestationDataRootAndCI = @@ -205,6 +207,15 @@ public void getAttestationsForBlock(final Blackhole bh) { bh.consume(attestationsForBlock); } + @Benchmark + @BenchmarkMode(Mode.AverageTime) + public void add(final Blackhole bh) { + var emptyPool = + new AggregatingAttestationPool( + SPEC, recentChainData, new NoOpMetricsSystem(), DEFAULT_MAXIMUM_ATTESTATION_COUNT); + attestations.forEach(emptyPool::add); + } + @Benchmark @BenchmarkMode(Mode.Throughput) public void createAggregateFor(final Blackhole bh) { diff --git a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/attestation/AggregateAttestationBuilder.java b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/attestation/AggregateAttestationBuilder.java index a0dfaf264cc..4e1612f5018 100644 --- a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/attestation/AggregateAttestationBuilder.java +++ b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/attestation/AggregateAttestationBuilder.java @@ -15,15 +15,10 @@ import static com.google.common.base.Preconditions.checkState; +import java.util.ArrayList; import java.util.Collection; -import java.util.HashSet; -import java.util.Set; +import java.util.List; import tech.pegasys.teku.bls.BLS; -import tech.pegasys.teku.spec.Spec; -import tech.pegasys.teku.spec.SpecVersion; -import tech.pegasys.teku.spec.datastructures.attestation.ValidatableAttestation; -import tech.pegasys.teku.spec.datastructures.operations.Attestation; -import tech.pegasys.teku.spec.datastructures.operations.AttestationData; import tech.pegasys.teku.statetransition.attestation.utils.AttestationBitsAggregator; /** @@ -31,55 +26,33 @@ * made redundant by the current aggregate. */ class AggregateAttestationBuilder { - private final Spec spec; - private final Set includedAttestations = new HashSet<>(); - private final AttestationData attestationData; + private final List includedAttestations = new ArrayList<>(); private AttestationBitsAggregator currentAggregateBits; - AggregateAttestationBuilder(final Spec spec, final AttestationData attestationData) { - this.spec = spec; - this.attestationData = attestationData; - } - - public boolean isFullyIncluded(final ValidatableAttestation candidate) { - return currentAggregateBits != null - && currentAggregateBits.isSuperSetOf(candidate.getAttestation()); - } - - public boolean aggregate(final ValidatableAttestation attestation) { + public boolean aggregate(final PooledAttestation attestation) { if (currentAggregateBits == null) { includedAttestations.add(attestation); - currentAggregateBits = AttestationBitsAggregator.of(attestation); + currentAggregateBits = attestation.bits().copy(); return true; } - if (currentAggregateBits.aggregateWith(attestation.getAttestation())) { + if (currentAggregateBits.aggregateWith(attestation.bits())) { includedAttestations.add(attestation); return true; } return false; } - public ValidatableAttestation buildAggregate() { + public PooledAttestation buildAggregate() { checkState(currentAggregateBits != null, "Must aggregate at least one attestation"); - final SpecVersion specVersion = spec.atSlot(attestationData.getSlot()); - return ValidatableAttestation.from( - spec, - specVersion - .getSchemaDefinitions() - .getAttestationSchema() - .create( - currentAggregateBits.getAggregationBits(), - attestationData, - BLS.aggregate( - includedAttestations.stream() - .map(ValidatableAttestation::getAttestation) - .map(Attestation::getAggregateSignature) - .toList()), - currentAggregateBits::getCommitteeBits)); + return new PooledAttestation( + currentAggregateBits, + BLS.aggregate( + includedAttestations.stream().map(PooledAttestation::aggregatedSignature).toList()), + false); } - public Collection getIncludedAttestations() { + public Collection getIncludedAttestations() { return includedAttestations; } } diff --git a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/attestation/AggregatingAttestationPool.java b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/attestation/AggregatingAttestationPool.java index 2998fa4eef6..e44b2b71db6 100644 --- a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/attestation/AggregatingAttestationPool.java +++ b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/attestation/AggregatingAttestationPool.java @@ -43,6 +43,7 @@ import tech.pegasys.teku.spec.datastructures.attestation.ValidatableAttestation; import tech.pegasys.teku.spec.datastructures.operations.Attestation; import tech.pegasys.teku.spec.datastructures.operations.AttestationData; +import tech.pegasys.teku.spec.datastructures.operations.AttestationSchema; import tech.pegasys.teku.spec.datastructures.state.beaconstate.BeaconState; import tech.pegasys.teku.spec.logic.common.helpers.MiscHelpers; import tech.pegasys.teku.spec.schemas.SchemaDefinitions; @@ -60,9 +61,9 @@ public class AggregatingAttestationPool implements SlotEventsChannel { /** The valid attestation retention period is 64 slots in deneb */ static final long ATTESTATION_RETENTION_SLOTS = 64; - static final Comparator ATTESTATION_INCLUSION_COMPARATOR = - Comparator.comparingInt( - attestation -> attestation.getAggregationBits().getBitCount()) + static final Comparator ATTESTATION_INCLUSION_COMPARATOR = + Comparator.comparingInt( + attestation -> attestation.pooledAttestation().bits().getBitCount()) .reversed(); /** @@ -109,7 +110,10 @@ public synchronized void add(final ValidatableAttestation attestation) { getOrCreateAttestationGroup(attestation.getAttestation(), committeesSize) .ifPresent( attestationGroup -> { - final boolean added = attestationGroup.add(attestation); + final boolean added = + attestationGroup.add( + PooledAttestation.fromValidatableAttestation(attestation), + attestation.getCommitteeShufflingSeed()); if (added) { updateSize(1); } @@ -270,12 +274,13 @@ public synchronized SszList getAttestationsForBlock( final SchemaDefinitions schemaDefinitions = spec.atSlot(stateAtBlockSlot.getSlot()).getSchemaDefinitions(); - + final AttestationSchema attestationSchema = + schemaDefinitions.getAttestationSchema(); final SszListSchema attestationsSchema = schemaDefinitions.getBeaconBlockBodySchema().getAttestationsSchema(); final boolean blockRequiresAttestationsWithCommitteeBits = - schemaDefinitions.getAttestationSchema().requiresCommitteeBits(); + attestationSchema.requiresCommitteeBits(); final AtomicInteger prevEpochCount = new AtomicInteger(0); @@ -295,17 +300,17 @@ public synchronized SszList getAttestationsForBlock( .limit(attestationsSchema.getMaxLength()) .filter( attestation -> { - if (spec.computeEpochAtSlot(attestation.getData().getSlot()) - .isLessThan(currentEpoch)) { + if (spec.computeEpochAtSlot(attestation.data().getSlot()).isLessThan(currentEpoch)) { final int currentCount = prevEpochCount.getAndIncrement(); return currentCount < previousEpochLimit; } return true; }) + .map(pooledAttestation -> pooledAttestation.toAttestation(attestationSchema)) .collect(attestationsSchema.collector()); } - private Stream streamAggregatesForDataHashesBySlot( + private Stream streamAggregatesForDataHashesBySlot( final Set dataHashSetForSlot, final BeaconState stateAtBlockSlot, final AttestationForkChecker forkChecker, @@ -317,10 +322,10 @@ private Stream streamAggregatesForDataHashesBySlot( .filter(group -> isValid(stateAtBlockSlot, group.getAttestationData())) .filter(forkChecker::areAttestationsFromCorrectFork) .flatMap(MatchingDataAttestationGroup::stream) - .map(ValidatableAttestation::getAttestation) .filter( attestation -> - attestation.requiresCommitteeBits() == blockRequiresAttestationsWithCommitteeBits) + attestation.pooledAttestation().bits().requiresCommitteeBits() + == blockRequiresAttestationsWithCommitteeBits) .sorted(ATTESTATION_INCLUSION_COMPARATOR); } @@ -332,6 +337,8 @@ public synchronized List getAttestations( final UInt64 slot = maybeSlot.orElse(recentChainData.getCurrentSlot().orElse(UInt64.ZERO)); final SchemaDefinitions schemaDefinitions = spec.atSlot(slot).getSchemaDefinitions(); + final AttestationSchema attestationSchema = + schemaDefinitions.getAttestationSchema(); final boolean requiresCommitteeBits = schemaDefinitions.getAttestationSchema().requiresCommitteeBits(); @@ -345,7 +352,7 @@ public synchronized List getAttestations( .flatMap( matchingDataAttestationGroup -> matchingDataAttestationGroup.stream(maybeCommitteeIndex, requiresCommitteeBits)) - .map(ValidatableAttestation::getAttestation) + .map(pooledAttestation -> pooledAttestation.toAttestation(attestationSchema)) .toList(); } @@ -356,9 +363,20 @@ private boolean isValid( public synchronized Optional createAggregateFor( final Bytes32 attestationHashTreeRoot, final Optional committeeIndex) { - return Optional.ofNullable(attestationGroupByDataHash.get(attestationHashTreeRoot)) - .flatMap(attestations -> attestations.stream(committeeIndex).findFirst()) - .map(ValidatableAttestation::getAttestation); + final MatchingDataAttestationGroup group = + attestationGroupByDataHash.get(attestationHashTreeRoot); + if (group == null) { + return Optional.empty(); + } + + final AttestationSchema attestationSchema = + spec.atSlot(group.getAttestationData().getSlot()) + .getSchemaDefinitions() + .getAttestationSchema(); + + return group.stream(committeeIndex) + .findFirst() + .map(pooledAttestation -> pooledAttestation.toAttestation(attestationSchema)); } public synchronized void onReorg(final UInt64 commonAncestorSlot) { 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 b1761e03804..8d7f0395676 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 @@ -27,10 +27,8 @@ import java.util.stream.Stream; import java.util.stream.StreamSupport; import org.apache.tuweni.bytes.Bytes32; -import tech.pegasys.teku.infrastructure.ssz.collections.SszBitvector; import tech.pegasys.teku.infrastructure.unsigned.UInt64; import tech.pegasys.teku.spec.Spec; -import tech.pegasys.teku.spec.datastructures.attestation.ValidatableAttestation; import tech.pegasys.teku.spec.datastructures.operations.Attestation; import tech.pegasys.teku.spec.datastructures.operations.AttestationData; import tech.pegasys.teku.statetransition.attestation.utils.AttestationBitsAggregator; @@ -47,9 +45,9 @@ *

Note that the resulting aggregate will be invalid if attestations with different * AttestationData are added. */ -public class MatchingDataAttestationGroup implements Iterable { +public class MatchingDataAttestationGroup implements Iterable { - private final NavigableMap> attestationsByValidatorCount = + private final NavigableMap> attestationsByValidatorCount = new TreeMap<>(Comparator.reverseOrder()); // Most validators first private final Spec spec; @@ -102,18 +100,17 @@ public AttestationData getAttestationData() { * @param attestation the attestation to add * @return True if the attestation was added, false otherwise */ - public boolean add(final ValidatableAttestation attestation) { - if (includedValidators.isSuperSetOf(attestation.getAttestation())) { + public boolean add( + final PooledAttestation attestation, final Optional committeeShufflingSeed) { + if (includedValidators.isSuperSetOf(attestation.bits())) { // All attestation bits have already been included on chain return false; } - if (committeeShufflingSeed.isEmpty()) { - committeeShufflingSeed = attestation.getCommitteeShufflingSeed(); + if (this.committeeShufflingSeed.isEmpty()) { + this.committeeShufflingSeed = committeeShufflingSeed; } return attestationsByValidatorCount - .computeIfAbsent( - attestation.getAttestation().getAggregationBits().getBitCount(), - count -> new HashSet<>()) + .computeIfAbsent(attestation.bits().getBitCount(), count -> new HashSet<>()) .add(attestation); } @@ -131,31 +128,40 @@ public boolean add(final ValidatableAttestation attestation) { * @return an iterator including attestations for every validator included in this group. */ @Override - public Iterator iterator() { + public Iterator iterator() { return new AggregatingIterator(Optional.empty()); } - public Iterator iterator(final Optional committeeIndex) { + public Iterator iterator(final Optional committeeIndex) { return new AggregatingIterator(committeeIndex); } - public Stream stream() { - return StreamSupport.stream(spliterator(Optional.empty()), false); + public Stream stream() { + return StreamSupport.stream(spliterator(Optional.empty()), false) + .map( + pooledAttestationBitsAndSignature -> + new PooledAttestationWithData(attestationData, pooledAttestationBitsAndSignature)); } - public Stream stream(final Optional committeeIndex) { - return StreamSupport.stream(spliterator(committeeIndex), false); + public Stream stream(final Optional committeeIndex) { + return StreamSupport.stream(spliterator(committeeIndex), false) + .map( + pooledAttestationBitsAndSignature -> + new PooledAttestationWithData(attestationData, pooledAttestationBitsAndSignature)); } - public Stream stream( + public Stream stream( final Optional committeeIndex, final boolean requiresCommitteeBits) { if (noMatchingAttestations(committeeIndex, requiresCommitteeBits)) { return Stream.empty(); } - return StreamSupport.stream(spliterator(committeeIndex), false); + return StreamSupport.stream(spliterator(committeeIndex), false) + .map( + pooledAttestationBitsAndSignature -> + new PooledAttestationWithData(attestationData, pooledAttestationBitsAndSignature)); } - public Spliterator spliterator(final Optional committeeIndex) { + public Spliterator spliterator(final Optional committeeIndex) { return Spliterators.spliteratorUnknownSize(iterator(committeeIndex), 0); } @@ -199,15 +205,15 @@ public int onAttestationIncludedInBlock(final UInt64 slot, final Attestation att } includedValidators.or(attestation); - final Collection> attestationSets = + final Collection> attestationSets = attestationsByValidatorCount.values(); int numRemoved = 0; - for (Iterator> i = attestationSets.iterator(); i.hasNext(); ) { - final Set candidates = i.next(); - for (Iterator iterator = candidates.iterator(); + for (final Iterator> i = attestationSets.iterator(); i.hasNext(); ) { + final Set candidates = i.next(); + for (final Iterator iterator = candidates.iterator(); iterator.hasNext(); ) { - ValidatableAttestation candidate = iterator.next(); - if (includedValidators.isSuperSetOf(candidate.getAttestation())) { + final PooledAttestation candidate = iterator.next(); + if (includedValidators.isSuperSetOf(candidate.bits())) { iterator.remove(); numRemoved++; } @@ -249,12 +255,12 @@ private boolean noMatchingPreElectraAttestations(final Optional committe && !attestationData.getIndex().equals(committeeIndex.get()); } - private class AggregatingIterator implements Iterator { + private class AggregatingIterator implements Iterator { private final Optional maybeCommitteeIndex; private final AttestationBitsAggregator includedValidators; - private Iterator remainingAttestations = getRemainingAttestations(); + private Iterator remainingAttestations = getRemainingAttestations(); private AggregatingIterator(final Optional committeeIndex) { this.maybeCommitteeIndex = committeeIndex; @@ -270,30 +276,27 @@ public boolean hasNext() { } @Override - public ValidatableAttestation next() { - final AggregateAttestationBuilder builder = - new AggregateAttestationBuilder(spec, attestationData); + public PooledAttestation next() { + final AggregateAttestationBuilder builder = new AggregateAttestationBuilder(); remainingAttestations.forEachRemaining( candidate -> { if (builder.aggregate(candidate)) { - includedValidators.or(candidate.getAttestation()); + includedValidators.or(candidate.bits()); } }); return builder.buildAggregate(); } - public Iterator getRemainingAttestations() { + public Iterator getRemainingAttestations() { return attestationsByValidatorCount.values().stream() .flatMap(Set::stream) .filter(this::isAttestationRelevant) - .filter(candidate -> !includedValidators.isSuperSetOf(candidate.getAttestation())) + .filter(candidate -> !includedValidators.isSuperSetOf(candidate.bits())) .iterator(); } - private boolean isAttestationRelevant(final ValidatableAttestation candidate) { - final Optional maybeCommitteeBits = - candidate.getAttestation().getCommitteeBits(); - if (maybeCommitteeBits.isEmpty()) { + private boolean isAttestationRelevant(final PooledAttestation candidate) { + if (!candidate.bits().requiresCommitteeBits()) { // Pre-Electra attestation, we always consider all attestations return true; } @@ -301,18 +304,11 @@ private boolean isAttestationRelevant(final ValidatableAttestation candidate) { if (maybeCommitteeIndex.isEmpty()) { // we are in block proposal scenario (not filtering by committeeIndex) // we will skip single attestations - return !candidate.getUnconvertedAttestation().isSingleAttestation(); + return !candidate.isSingleAttestation(); } // we are in committee aggregation scenario - final SszBitvector committeeBits = maybeCommitteeBits.get(); - if (!committeeBits.isSet(maybeCommitteeIndex.get().intValue())) { - // the committeeIndex must match - return false; - } - - // we want to aggregate attestations for a single committee only - return committeeBits.getBitCount() == 1; + return candidate.bits().isExclusivelyFromCommittee(maybeCommitteeIndex.get().intValue()); } } } diff --git a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/attestation/PooledAttestation.java b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/attestation/PooledAttestation.java new file mode 100644 index 00000000000..a584d7c63f4 --- /dev/null +++ b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/attestation/PooledAttestation.java @@ -0,0 +1,30 @@ +/* + * Copyright Consensys Software Inc., 2025 + * + * 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.statetransition.attestation; + +import tech.pegasys.teku.bls.BLSSignature; +import tech.pegasys.teku.spec.datastructures.attestation.ValidatableAttestation; +import tech.pegasys.teku.statetransition.attestation.utils.AttestationBitsAggregator; + +public record PooledAttestation( + AttestationBitsAggregator bits, BLSSignature aggregatedSignature, boolean isSingleAttestation) { + + public static PooledAttestation fromValidatableAttestation( + final ValidatableAttestation attestation) { + return new PooledAttestation( + AttestationBitsAggregator.of(attestation), + attestation.getAttestation().getAggregateSignature(), + attestation.getUnconvertedAttestation().isSingleAttestation()); + } +} diff --git a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/attestation/PooledAttestationWithData.java b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/attestation/PooledAttestationWithData.java new file mode 100644 index 00000000000..f29b6d7ce70 --- /dev/null +++ b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/attestation/PooledAttestationWithData.java @@ -0,0 +1,29 @@ +/* + * Copyright Consensys Software Inc., 2025 + * + * 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.statetransition.attestation; + +import tech.pegasys.teku.spec.datastructures.operations.Attestation; +import tech.pegasys.teku.spec.datastructures.operations.AttestationData; +import tech.pegasys.teku.spec.datastructures.operations.AttestationSchema; + +public record PooledAttestationWithData(AttestationData data, PooledAttestation pooledAttestation) { + + public Attestation toAttestation(final AttestationSchema attestationSchema) { + return attestationSchema.create( + pooledAttestation.bits().getAggregationBits(), + data, + pooledAttestation.aggregatedSignature(), + pooledAttestation.bits()::getCommitteeBits); + } +} diff --git a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/attestation/utils/AttestationBitsAggregator.java b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/attestation/utils/AttestationBitsAggregator.java index 423c3744de5..9cb18c4a24d 100644 --- a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/attestation/utils/AttestationBitsAggregator.java +++ b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/attestation/utils/AttestationBitsAggregator.java @@ -22,7 +22,6 @@ import tech.pegasys.teku.spec.datastructures.operations.AttestationSchema; public interface AttestationBitsAggregator { - static AttestationBitsAggregator fromEmptyFromAttestationSchema( final AttestationSchema attestationSchema, final Optional committeesSize) { return attestationSchema @@ -64,12 +63,14 @@ static AttestationBitsAggregator of( void or(AttestationBitsAggregator other); - boolean aggregateWith(Attestation other); + boolean aggregateWith(AttestationBitsAggregator other); void or(Attestation other); boolean isSuperSetOf(Attestation other); + boolean isSuperSetOf(AttestationBitsAggregator other); + SszBitlist getAggregationBits(); SszBitvector getCommitteeBits(); @@ -78,6 +79,10 @@ static AttestationBitsAggregator of( boolean requiresCommitteeBits(); + int getBitCount(); + + boolean isExclusivelyFromCommittee(int committeeIndex); + /** Creates an independent copy of this instance */ AttestationBitsAggregator copy(); } diff --git a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/attestation/utils/AttestationBitsAggregatorElectra.java b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/attestation/utils/AttestationBitsAggregatorElectra.java index a4b7c6d2893..55fab2d3d22 100644 --- a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/attestation/utils/AttestationBitsAggregatorElectra.java +++ b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/attestation/utils/AttestationBitsAggregatorElectra.java @@ -14,6 +14,7 @@ package tech.pegasys.teku.statetransition.attestation.utils; import com.google.common.base.MoreObjects; +import com.google.common.base.Supplier; import it.unimi.dsi.fastutil.ints.Int2IntMap; import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; @@ -103,20 +104,15 @@ private static Int2ObjectMap parseAggregationBits( @Override public void or(final AttestationBitsAggregator other) { - if (!(other instanceof AttestationBitsAggregatorElectra otherElectra)) { - throw new IllegalArgumentException( - "AttestationBitsAggregatorElectra.or requires an argument of the same type."); - } + final AttestationBitsAggregatorElectra otherElectra = requiresElectra(other); performMerge(otherElectra.committeeBits, otherElectra.committeeAggregationBitsMap, false); } @Override - public boolean aggregateWith(final Attestation other) { - final BitSet otherCommitteeBits = other.getCommitteeBitsRequired().getAsBitSet(); - final Int2ObjectMap otherParsedAggregationMap = - parseAggregationBits(other.getAggregationBits(), otherCommitteeBits, this.committeesSize); - return performMerge(otherCommitteeBits, otherParsedAggregationMap, true); + public boolean aggregateWith(final AttestationBitsAggregator other) { + final AttestationBitsAggregatorElectra otherElectra = requiresElectra(other); + return performMerge(otherElectra.committeeBits, otherElectra.committeeAggregationBitsMap, true); } @Override @@ -199,21 +195,37 @@ private boolean performMerge( @Override public boolean isSuperSetOf(final Attestation other) { - final BitSet otherInternalCommitteeBits = other.getCommitteeBitsRequired().getAsBitSet(); + final BitSet otherCommitteeBits = other.getCommitteeBitsRequired().getAsBitSet(); + return isSuperSetOf( + otherCommitteeBits, + () -> + parseAggregationBits( + other.getAggregationBits(), otherCommitteeBits, this.committeesSize)); + } + + @Override + public boolean isSuperSetOf(final AttestationBitsAggregator other) { + final AttestationBitsAggregatorElectra otherElectra = requiresElectra(other); + + return isSuperSetOf(otherElectra.committeeBits, () -> otherElectra.committeeAggregationBitsMap); + } + + private boolean isSuperSetOf( + final BitSet otherCommitteeBits, + final Supplier> otherCommitteeAggregationBitsMapSupplier) { final BitSet committeeIntersection = (BitSet) this.committeeBits.clone(); - committeeIntersection.and(otherInternalCommitteeBits); - if (!committeeIntersection.equals(otherInternalCommitteeBits)) { + committeeIntersection.and(otherCommitteeBits); + if (!committeeIntersection.equals(otherCommitteeBits)) { return false; } final Int2ObjectMap otherCommitteeAggregationBitsMap = - parseAggregationBits( - other.getAggregationBits(), otherInternalCommitteeBits, this.committeesSize); + otherCommitteeAggregationBitsMapSupplier.get(); - for (int committeeIndex = otherInternalCommitteeBits.nextSetBit(0); + for (int committeeIndex = otherCommitteeBits.nextSetBit(0); committeeIndex >= 0; - committeeIndex = otherInternalCommitteeBits.nextSetBit(committeeIndex + 1)) { + committeeIndex = otherCommitteeBits.nextSetBit(committeeIndex + 1)) { final BitSet thisAggregationBitsForCommittee = this.committeeAggregationBitsMap.get(committeeIndex); @@ -307,6 +319,17 @@ public AttestationBitsAggregator copy() { (BitSet) committeeBits.clone()); } + @Override + public int getBitCount() { + return committeeAggregationBitsMap.values().stream().mapToInt(BitSet::cardinality).sum(); + } + + @Override + public boolean isExclusivelyFromCommittee(final int committeeIndex) { + return committeeAggregationBitsMap.size() == 1 + && committeeAggregationBitsMap.containsKey(committeeIndex); + } + @Override public String toString() { long totalSetBits = 0; @@ -324,4 +347,32 @@ public String toString() { .add("cached", cachedAggregationBits != null || cachedCommitteeBits != null) .toString(); } + + static AttestationBitsAggregatorElectra requiresElectra( + final AttestationBitsAggregator aggregator) { + if (!(aggregator instanceof AttestationBitsAggregatorElectra aggregatorElectra)) { + throw new IllegalArgumentException( + "AttestationBitsAggregator required to be Electra but was: " + + aggregator.getClass().getSimpleName()); + } + return aggregatorElectra; + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + + if (!(o instanceof AttestationBitsAggregatorElectra that)) { + return false; + } + return this.committeeBits.equals(that.committeeBits) + && Objects.equals(committeeAggregationBitsMap, that.committeeAggregationBitsMap); + } + + @Override + public int hashCode() { + return Objects.hash(committeeBits, committeeAggregationBitsMap); + } } diff --git a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/attestation/utils/AttestationBitsAggregatorPhase0.java b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/attestation/utils/AttestationBitsAggregatorPhase0.java index 87b9ec1d135..2748950b1bf 100644 --- a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/attestation/utils/AttestationBitsAggregatorPhase0.java +++ b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/attestation/utils/AttestationBitsAggregatorPhase0.java @@ -15,6 +15,7 @@ import com.google.common.base.MoreObjects; import it.unimi.dsi.fastutil.ints.Int2IntMap; +import java.util.Objects; import tech.pegasys.teku.infrastructure.ssz.collections.SszBitlist; import tech.pegasys.teku.infrastructure.ssz.collections.SszBitvector; import tech.pegasys.teku.spec.datastructures.operations.Attestation; @@ -34,19 +35,17 @@ static AttestationBitsAggregator fromAttestationSchema( @Override public void or(final AttestationBitsAggregator other) { - aggregationBits = aggregationBits.or(other.getAggregationBits()); + final AttestationBitsAggregatorPhase0 otherPhase0 = requiresPhase0(other); + aggregationBits = aggregationBits.or(otherPhase0.aggregationBits); } @Override - public boolean aggregateWith(final Attestation other) { - return aggregateWith(other.getAggregationBits()); - } - - private boolean aggregateWith(final SszBitlist otherAggregationBits) { - if (aggregationBits.intersects(otherAggregationBits)) { + public boolean aggregateWith(final AttestationBitsAggregator other) { + final AttestationBitsAggregatorPhase0 otherPhase0 = requiresPhase0(other); + if (aggregationBits.intersects(otherPhase0.aggregationBits)) { return false; } - aggregationBits = aggregationBits.or(otherAggregationBits); + aggregationBits = aggregationBits.or(otherPhase0.aggregationBits); return true; } @@ -60,6 +59,12 @@ public boolean isSuperSetOf(final Attestation other) { return aggregationBits.isSuperSetOf(other.getAggregationBits()); } + @Override + public boolean isSuperSetOf(final AttestationBitsAggregator other) { + final AttestationBitsAggregatorPhase0 otherPhase0 = requiresPhase0(other); + return aggregationBits.isSuperSetOf(otherPhase0.aggregationBits); + } + @Override public SszBitlist getAggregationBits() { return aggregationBits; @@ -85,8 +90,45 @@ public AttestationBitsAggregator copy() { return new AttestationBitsAggregatorPhase0(aggregationBits); } + @Override + public int getBitCount() { + return aggregationBits.getBitCount(); + } + + @Override + public boolean isExclusivelyFromCommittee(final int committeeIndex) { + throw new IllegalStateException("Committee bits not available in phase0"); + } + @Override public String toString() { return MoreObjects.toStringHelper(this).add("aggregationBits", aggregationBits).toString(); } + + static AttestationBitsAggregatorPhase0 requiresPhase0( + final AttestationBitsAggregator aggregator) { + if (!(aggregator instanceof AttestationBitsAggregatorPhase0 aggregatorPhase0)) { + throw new IllegalArgumentException( + "AttestationBitsAggregator required to be Phase0 but was: " + + aggregator.getClass().getSimpleName()); + } + return aggregatorPhase0; + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + + if (!(o instanceof AttestationBitsAggregatorPhase0 that)) { + return false; + } + return this.aggregationBits.equals(that.aggregationBits); + } + + @Override + public int hashCode() { + return Objects.hash(aggregationBits); + } } diff --git a/ethereum/statetransition/src/test/java/tech/pegasys/teku/statetransition/attestation/AggregateAttestationBuilderTest.java b/ethereum/statetransition/src/test/java/tech/pegasys/teku/statetransition/attestation/AggregateAttestationBuilderTest.java index 26c5a902e05..68babeb28a2 100644 --- a/ethereum/statetransition/src/test/java/tech/pegasys/teku/statetransition/attestation/AggregateAttestationBuilderTest.java +++ b/ethereum/statetransition/src/test/java/tech/pegasys/teku/statetransition/attestation/AggregateAttestationBuilderTest.java @@ -37,31 +37,30 @@ class AggregateAttestationBuilderTest { spec.getGenesisSchemaDefinitions().getAttestationSchema(); private final AttestationData attestationData = dataStructureUtil.randomAttestationData(); - private final AggregateAttestationBuilder builder = - new AggregateAttestationBuilder(spec, attestationData); + private final AggregateAttestationBuilder builder = new AggregateAttestationBuilder(); @Test public void canAggregate_shouldBeTrueForFirstAttestation() { - assertThat(builder.aggregate(createAttestation(1, 2, 3, 4, 5, 6, 7, 8, 9))).isTrue(); + assertThat(builder.aggregate(createPooledAttestation(1, 2, 3, 4, 5, 6, 7, 8, 9))).isTrue(); } @Test public void canAggregate_shouldBeTrueWhenValidatorsDoNotOverlap() { - builder.aggregate(createAttestation(1, 3, 5)); - assertThat(builder.aggregate(createAttestation(0, 2, 4))).isTrue(); + builder.aggregate(createPooledAttestation(1, 3, 5)); + assertThat(builder.aggregate(createPooledAttestation(0, 2, 4))).isTrue(); } @Test public void canAggregate_shouldBeFalseWhenValidatorsDoOverlap() { - builder.aggregate(createAttestation(1, 3, 5)); - assertThat(builder.aggregate(createAttestation(1, 2, 4))).isFalse(); + builder.aggregate(createPooledAttestation(1, 3, 5)); + assertThat(builder.aggregate(createPooledAttestation(1, 2, 4))).isFalse(); } @Test public void aggregate_shouldTrackIncludedAggregations() { - final ValidatableAttestation attestation1 = createAttestation(1); - final ValidatableAttestation attestation2 = createAttestation(2); - final ValidatableAttestation attestation3 = createAttestation(3); + final PooledAttestation attestation1 = createPooledAttestation(1); + final PooledAttestation attestation2 = createPooledAttestation(2); + final PooledAttestation attestation3 = createPooledAttestation(3); builder.aggregate(attestation1); builder.aggregate(attestation2); builder.aggregate(attestation3); @@ -72,9 +71,9 @@ public void aggregate_shouldTrackIncludedAggregations() { @Test public void aggregate_shouldCombineBitsetsAndSignatures() { - final ValidatableAttestation attestation1 = createAttestation(1); - final ValidatableAttestation attestation2 = createAttestation(2); - final ValidatableAttestation attestation3 = createAttestation(3); + final PooledAttestation attestation1 = createPooledAttestation(1); + final PooledAttestation attestation2 = createPooledAttestation(2); + final PooledAttestation attestation3 = createPooledAttestation(3); builder.aggregate(attestation1); builder.aggregate(attestation2); builder.aggregate(attestation3); @@ -85,16 +84,17 @@ public void aggregate_shouldCombineBitsetsAndSignatures() { final BLSSignature expectedSignature = BLS.aggregate( asList( - attestation1.getAttestation().getAggregateSignature(), - attestation2.getAttestation().getAggregateSignature(), - attestation3.getAttestation().getAggregateSignature())); + attestation1.aggregatedSignature(), + attestation2.aggregatedSignature(), + attestation3.aggregatedSignature())); assertThat(builder.buildAggregate()) .isEqualTo( - ValidatableAttestation.from( - spec, - attestationSchema.create( - expectedAggregationBits, attestationData, expectedSignature))); + PooledAttestation.fromValidatableAttestation( + ValidatableAttestation.from( + spec, + attestationSchema.create( + expectedAggregationBits, attestationData, expectedSignature)))); } @Test @@ -102,12 +102,13 @@ public void buildAggregate_shouldThrowExceptionIfNoAttestationsAggregated() { assertThatThrownBy(builder::buildAggregate).isInstanceOf(IllegalStateException.class); } - private ValidatableAttestation createAttestation(final int... validators) { + private PooledAttestation createPooledAttestation(final int... validators) { final SszBitlist aggregationBits = attestationSchema.getAggregationBitsSchema().ofBits(BITLIST_SIZE, validators); - return ValidatableAttestation.from( - spec, - attestationSchema.create( - aggregationBits, attestationData, dataStructureUtil.randomSignature())); + return PooledAttestation.fromValidatableAttestation( + ValidatableAttestation.from( + spec, + attestationSchema.create( + aggregationBits, attestationData, dataStructureUtil.randomSignature()))); } } diff --git a/ethereum/statetransition/src/test/java/tech/pegasys/teku/statetransition/attestation/MatchingDataAttestationGroupTest.java b/ethereum/statetransition/src/test/java/tech/pegasys/teku/statetransition/attestation/MatchingDataAttestationGroupTest.java index 62a754dfced..e43d7cec1e7 100644 --- a/ethereum/statetransition/src/test/java/tech/pegasys/teku/statetransition/attestation/MatchingDataAttestationGroupTest.java +++ b/ethereum/statetransition/src/test/java/tech/pegasys/teku/statetransition/attestation/MatchingDataAttestationGroupTest.java @@ -42,7 +42,7 @@ class MatchingDataAttestationGroupTest { private Spec spec; private DataStructureUtil dataStructureUtil; - private AttestationSchema attestationSchema; + private AttestationSchema attestationSchema; private AttestationData attestationData; @@ -68,13 +68,13 @@ public void isEmpty_shouldBeEmptyInitially() { @TestTemplate public void isEmpty_shouldNotBeEmptyWhenAnAttestationIsAdded() { - addAttestation(1); + addPooledAttestation(1); assertThat(group.isEmpty()).isFalse(); } @TestTemplate public void isEmpty_shouldBeEmptyAfterAttestationRemoved() { - final Attestation attestation = addAttestation(1).getAttestation(); + final Attestation attestation = toAttestation(addPooledAttestation(1)); int numRemoved = group.onAttestationIncludedInBlock(UInt64.ZERO, attestation); assertThat(group.isEmpty()).isTrue(); @@ -83,7 +83,7 @@ public void isEmpty_shouldBeEmptyAfterAttestationRemoved() { @TestTemplate public void remove_shouldRemoveAttestationEvenWhenInstanceIsDifferent() { - final Attestation attestation = addAttestation(1).getAttestation(); + final Attestation attestation = toAttestation(addPooledAttestation(1)); final Attestation copy = attestationSchema.sszDeserialize(attestation.sszSerialize()); int numRemoved = group.onAttestationIncludedInBlock(UInt64.ZERO, copy); @@ -96,102 +96,117 @@ public void remove_shouldRemoveAttestationEvenWhenInstanceIsDifferent() { public void remove_multipleCallsToRemoveShouldAggregate() { // Create attestations that will be removed - final ValidatableAttestation attestation1 = createAttestation(1); - final ValidatableAttestation attestation2 = createAttestation(2); + final PooledAttestation attestation1 = createPooledAttestation(1); + final PooledAttestation attestation2 = createPooledAttestation(2); // Add some attestations - final ValidatableAttestation attestation3 = addAttestation(3); - addAttestation(1, 2); + final PooledAttestation attestation3 = addPooledAttestation(3); + addPooledAttestation(1, 2); - int numRemoved = group.onAttestationIncludedInBlock(UInt64.ZERO, attestation1.getAttestation()); + int numRemoved = group.onAttestationIncludedInBlock(UInt64.ZERO, toAttestation(attestation1)); assertThat(numRemoved).isEqualTo(0); - numRemoved += group.onAttestationIncludedInBlock(UInt64.ZERO, attestation2.getAttestation()); + numRemoved += group.onAttestationIncludedInBlock(UInt64.ZERO, toAttestation(attestation2)); assertThat(numRemoved).isEqualTo(1); - assertThat(group.stream(Optional.of(UInt64.ZERO))).containsExactly(attestation3); + assertThat(group.stream(Optional.of(UInt64.ZERO))) + .containsExactly(toPooledAttestationWithData(attestation3)); } @TestTemplate public void remove_shouldRemoveAttestationsThatAreAggregatedIntoRemovedAttestation() { - final ValidatableAttestation attestation1 = addAttestation(1); - final ValidatableAttestation attestation2 = addAttestation(2); - final ValidatableAttestation attestation3 = addAttestation(3); + final PooledAttestation attestation1 = addPooledAttestation(1); + final PooledAttestation attestation2 = addPooledAttestation(2); + final PooledAttestation attestation3 = addPooledAttestation(3); int numRemoved = group.onAttestationIncludedInBlock( UInt64.ZERO, - aggregateAttestations(attestation1.getAttestation(), attestation2.getAttestation())); + aggregateAttestations(toAttestation(attestation1), toAttestation(attestation2))); - assertThat(group.stream(Optional.of(UInt64.ZERO))).containsExactly(attestation3); + assertThat(group.stream(Optional.of(UInt64.ZERO))) + .containsExactly(toPooledAttestationWithData(attestation3)); assertThat(numRemoved).isEqualTo(2); // the one attestation is still there, and we've removed 2. } @TestTemplate public void add_shouldIgnoreAttestationWhoseBitsHaveAllBeenRemoved() { // Create attestations that will be removed - final ValidatableAttestation attestation1 = createAttestation(1); - final ValidatableAttestation attestation2 = createAttestation(2); + final PooledAttestation attestation1 = createPooledAttestation(1); + final PooledAttestation attestation2 = createPooledAttestation(2); // Create attestation to be added / ignored - final ValidatableAttestation attestationToIgnore = createAttestation(1, 2); + final PooledAttestation attestationToIgnore = createPooledAttestation(1, 2); - int numRemoved = group.onAttestationIncludedInBlock(UInt64.ZERO, attestation1.getAttestation()); - numRemoved += group.onAttestationIncludedInBlock(UInt64.ZERO, attestation2.getAttestation()); + int numRemoved = group.onAttestationIncludedInBlock(UInt64.ZERO, toAttestation(attestation1)); + numRemoved += group.onAttestationIncludedInBlock(UInt64.ZERO, toAttestation(attestation2)); assertThat(numRemoved).isEqualTo(0); - assertThat(group.add(attestationToIgnore)).isFalse(); + assertThat(group.add(attestationToIgnore, Optional.empty())).isFalse(); assertThat(group.stream()).isEmpty(); } @TestTemplate public void add_shouldAggregateAttestationsFromSameCommittee(final SpecContext specContext) { specContext.assumeElectraActive(); - final ValidatableAttestation attestation1 = addAttestation(Optional.of(0), 1); - final ValidatableAttestation attestation2 = addAttestation(Optional.of(1), 2); - final ValidatableAttestation attestation3 = addAttestation(Optional.of(1), 3); + final PooledAttestation attestation1 = addPooledAttestation(Optional.of(0), 1); + final PooledAttestation attestation2 = addPooledAttestation(Optional.of(1), 2); + final PooledAttestation attestation3 = addPooledAttestation(Optional.of(1), 3); - assertThat(group.stream(Optional.of(UInt64.ZERO))).containsExactly(attestation1); + assertThat(group.stream(Optional.of(UInt64.ZERO))) + .containsExactly(toPooledAttestationWithData(attestation1)); final Attestation expected = - aggregateAttestations(attestation2.getAttestation(), attestation3.getAttestation()); + aggregateAttestations(toAttestation(attestation2), toAttestation(attestation3)); assertThat(group.stream(Optional.of(UInt64.ONE))) - .containsExactly(ValidatableAttestation.from(spec, expected)); + .containsExactly( + toPooledAttestationWithData( + PooledAttestation.fromValidatableAttestation( + ValidatableAttestation.from(spec, expected, committeeSizes)))); } @TestTemplate public void add_shouldIgnoreDuplicateAttestations() { - final ValidatableAttestation attestation = addAttestation(1, 2); - final ValidatableAttestation copy = - ValidatableAttestation.from( - spec, attestationSchema.sszDeserialize(attestation.getAttestation().sszSerialize())); + final PooledAttestation attestation = addPooledAttestation(1, 2); + final PooledAttestation copy = + PooledAttestation.fromValidatableAttestation( + ValidatableAttestation.from( + spec, + attestationSchema.sszDeserialize(toAttestation(attestation).sszSerialize()), + committeeSizes)); - assertThat(group.add(copy)).isFalse(); - assertThat(group.stream()).containsExactly(attestation); + assertThat(group.add(copy, Optional.empty())).isFalse(); + assertThat(group.stream()).containsExactly(toPooledAttestationWithData(attestation)); } @TestTemplate public void iterator_shouldAggregateAttestationsWhereValidatorsDoNotOverlap() { - final ValidatableAttestation attestation1 = addAttestation(1); - final ValidatableAttestation attestation2 = addAttestation(2); + final PooledAttestation attestation1 = addPooledAttestation(1); + final PooledAttestation attestation2 = addPooledAttestation(2); final Attestation expected = - aggregateAttestations(attestation1.getAttestation(), attestation2.getAttestation()); + aggregateAttestations(toAttestation(attestation1), toAttestation(attestation2)); + assertThat(group.stream(Optional.of(UInt64.ZERO))) - .containsExactlyInAnyOrder(ValidatableAttestation.from(spec, expected)); + .containsExactlyInAnyOrder( + toPooledAttestationWithData( + PooledAttestation.fromValidatableAttestation( + ValidatableAttestation.from(spec, expected, committeeSizes)))); } @TestTemplate public void iterator_shouldAggregateAttestationsWithMoreValidatorsFirst() { - final ValidatableAttestation bigAttestation = addAttestation(1, 3, 5, 7); - final ValidatableAttestation mediumAttestation = addAttestation(3, 5, 9); - final ValidatableAttestation littleAttestation = addAttestation(2, 4); + final PooledAttestation bigAttestation = addPooledAttestation(1, 3, 5, 7); + final PooledAttestation mediumAttestation = addPooledAttestation(3, 5, 9); + final PooledAttestation littleAttestation = addPooledAttestation(2, 4); assertThat(group) .containsExactly( - ValidatableAttestation.from( - spec, - aggregateAttestations( - bigAttestation.getAttestation(), littleAttestation.getAttestation())), + PooledAttestation.fromValidatableAttestation( + ValidatableAttestation.from( + spec, + aggregateAttestations( + toAttestation(bigAttestation), toAttestation(littleAttestation)), + committeeSizes)), mediumAttestation); } @@ -199,25 +214,25 @@ public void iterator_shouldAggregateAttestationsWithMoreValidatorsFirst() { public void iterator_electra_shouldAggregateSkipSingleAttestationsInBlockProduction( final SpecContext specContext) { specContext.assumeElectraActive(); - final ValidatableAttestation bigAttestation = addAttestation(1, 3, 5, 7); - final ValidatableAttestation mediumAttestation = addAttestation(3, 5, 9); - addAttestation(2); + final PooledAttestation bigAttestation = addPooledAttestation(1, 3, 5, 7); + final PooledAttestation mediumAttestation = addPooledAttestation(3, 5, 9); + addPooledAttestation(2); assertThat(group).containsExactly(bigAttestation, mediumAttestation); } @TestTemplate public void iterator_shouldNotAggregateAttestationsWhenValidatorsOverlap() { - final ValidatableAttestation attestation1 = addAttestation(1, 2, 5); - final ValidatableAttestation attestation2 = addAttestation(1, 2, 3); + final PooledAttestation attestation1 = addPooledAttestation(1, 2, 5); + final PooledAttestation attestation2 = addPooledAttestation(1, 2, 3); assertThat(group).containsExactlyInAnyOrder(attestation1, attestation2); } @TestTemplate public void iterator_shouldOmitAttestationsThatAreAlreadyIncludedInTheAggregate() { - final ValidatableAttestation aggregate = addAttestation(1, 2, 3); - addAttestation(2); + final PooledAttestation aggregate = addPooledAttestation(1, 2, 3); + addPooledAttestation(2); assertThat(group).containsExactly(aggregate); } @@ -226,27 +241,32 @@ public void iterator_shouldOmitAttestationsThatAreAlreadyIncludedInTheAggregate( void iterator_shouldOmitAttestationsThatOverlapWithFirstAttestationAndAreRedundantWithCombined() { // First aggregate created will have validators 1,2,3,4 which makes the 2,4 attestation // redundant, but iteration will have already passed it before it becomes redundant - final ValidatableAttestation useful1 = addAttestation(1, 2, 3); - addAttestation(2, 4); - final ValidatableAttestation useful2 = addAttestation(4); - - assertThat(group.stream(Optional.of(UInt64.ZERO))) - .containsExactly( - ValidatableAttestation.from( - spec, aggregateAttestations(useful1.getAttestation(), useful2.getAttestation()))); + final PooledAttestation useful1 = addPooledAttestation(1, 2, 3); + addPooledAttestation(2, 4); + final PooledAttestation useful2 = addPooledAttestation(4); + + final PooledAttestationWithData expected = + toPooledAttestationWithData( + PooledAttestation.fromValidatableAttestation( + ValidatableAttestation.from( + spec, + aggregateAttestations(toAttestation(useful1), toAttestation(useful2)), + committeeSizes))); + + assertThat(group.stream(Optional.of(UInt64.ZERO))).containsExactly(expected); } @TestTemplate void onAttestationIncludedInBlock_shouldRemoveAttestationsMadeRedundant() { - final ValidatableAttestation attestation1 = addAttestation(1, 2, 3, 4); - final ValidatableAttestation attestation2 = addAttestation(1, 5, 7); - final ValidatableAttestation attestation3 = addAttestation(1, 6); + final PooledAttestation attestation1 = addPooledAttestation(1, 2, 3, 4); + final PooledAttestation attestation2 = addPooledAttestation(1, 5, 7); + final PooledAttestation attestation3 = addPooledAttestation(1, 6); assertThat(group.size()).isEqualTo(3); assertThat(group).containsExactly(attestation1, attestation2, attestation3); group.onAttestationIncludedInBlock( - UInt64.ZERO, createAttestation(1, 2, 3, 4, 5, 6, 7).getAttestation()); + UInt64.ZERO, toAttestation(createPooledAttestation(1, 2, 3, 4, 5, 6, 7))); assertThat(group.size()).isZero(); assertThat(group).isEmpty(); @@ -254,15 +274,15 @@ void onAttestationIncludedInBlock_shouldRemoveAttestationsMadeRedundant() { @TestTemplate void onAttestationIncludedInBlock_shouldNotRemoveAttestationsWithAdditionalValidators() { - final ValidatableAttestation attestation1 = addAttestation(1, 2, 3, 4); - final ValidatableAttestation attestation2 = addAttestation(1, 5, 7); - final ValidatableAttestation attestation3 = addAttestation(1, 6); + final PooledAttestation attestation1 = addPooledAttestation(1, 2, 3, 4); + final PooledAttestation attestation2 = addPooledAttestation(1, 5, 7); + final PooledAttestation attestation3 = addPooledAttestation(1, 6); assertThat(group.size()).isEqualTo(3); assertThat(group).containsExactly(attestation1, attestation2, attestation3); group.onAttestationIncludedInBlock( - UInt64.ZERO, createAttestation(1, 2, 3, 4, 5, 6).getAttestation()); + UInt64.ZERO, toAttestation(createPooledAttestation(1, 2, 3, 4, 5, 6))); // Validator 7 is still relevant assertThat(group.size()).isEqualTo(1); @@ -272,55 +292,55 @@ void onAttestationIncludedInBlock_shouldNotRemoveAttestationsWithAdditionalValid @TestTemplate void onAttestationIncludedInBlock_shouldNotAddAttestationsAlreadySeenInBlocks() { group.onAttestationIncludedInBlock( - UInt64.valueOf(1), createAttestation(1, 2, 3, 4, 5, 6).getAttestation()); + UInt64.valueOf(1), toAttestation(createPooledAttestation(1, 2, 3, 4, 5, 6))); // Can't add redundant attestation - assertThat(group.add(createAttestation(1))).isFalse(); - assertThat(group.add(createAttestation(1, 2, 3, 4, 5, 6))).isFalse(); - assertThat(group.add(createAttestation(2, 3))).isFalse(); + assertThat(group.add(createPooledAttestation(1), Optional.empty())).isFalse(); + assertThat(group.add(createPooledAttestation(1, 2, 3, 4, 5, 6), Optional.empty())).isFalse(); + assertThat(group.add(createPooledAttestation(2, 3), Optional.empty())).isFalse(); } @TestTemplate void onReorg_shouldAllowReadingAttestationsThatAreNoLongerRedundant() { - final ValidatableAttestation attestation = createAttestation(3, 4); + final PooledAttestation attestation = createPooledAttestation(3, 4); group.onAttestationIncludedInBlock( - UInt64.valueOf(1), createAttestation(1, 2, 3, 4, 5, 6).getAttestation()); + UInt64.valueOf(1), toAttestation(createPooledAttestation(1, 2, 3, 4, 5, 6))); // Can't add redundant attestation - assertThat(group.add(attestation)).isFalse(); + assertThat(group.add(attestation, Optional.empty())).isFalse(); // Reorg removes seen attestation group.onReorg(UInt64.ZERO); // Can now add attestation - assertThat(group.add(attestation)).isTrue(); + assertThat(group.add(attestation, Optional.empty())).isTrue(); assertThat(group.size()).isEqualTo(1); assertThat(group).containsExactly(attestation); } @TestTemplate void onReorg_shouldNotAllowReadingAttestationsThatAreStillRedundant() { - final ValidatableAttestation attestation1 = createAttestation(3, 4); - final ValidatableAttestation attestation2 = createAttestation(1, 2, 3, 4); + final PooledAttestation attestation1 = createPooledAttestation(3, 4); + final PooledAttestation attestation2 = createPooledAttestation(1, 2, 3, 4); group.onAttestationIncludedInBlock( - UInt64.valueOf(1), createAttestation(2, 3, 4).getAttestation()); + UInt64.valueOf(1), toAttestation(createPooledAttestation(2, 3, 4))); group.onAttestationIncludedInBlock( - UInt64.valueOf(3), createAttestation(1, 2, 3, 4).getAttestation()); + UInt64.valueOf(3), toAttestation(createPooledAttestation(1, 2, 3, 4))); // Can't add redundant attestation - assertThat(group.add(attestation1)).isFalse(); - assertThat(group.add(attestation2)).isFalse(); + assertThat(group.add(attestation1, Optional.empty())).isFalse(); + assertThat(group.add(attestation2, Optional.empty())).isFalse(); // Reorg removes only the last seen attestation group.onReorg(UInt64.valueOf(2)); // Still can't add attestation1 because 3 and 4 are included attestation - assertThat(group.add(attestation1)).isFalse(); + assertThat(group.add(attestation1, Optional.empty())).isFalse(); // But can add attestation2 because validator 1 is still relevant - assertThat(group.add(attestation2)).isTrue(); + assertThat(group.add(attestation2, Optional.empty())).isTrue(); assertThat(group.size()).isEqualTo(1); assertThat(group).containsExactly(attestation2); } @@ -328,41 +348,45 @@ void onReorg_shouldNotAllowReadingAttestationsThatAreStillRedundant() { @TestTemplate public void size() { assertThat(group.size()).isEqualTo(0); - final ValidatableAttestation attestation1 = addAttestation(1); + final PooledAttestationWithData attestation1 = + toPooledAttestationWithData(addPooledAttestation(1)); assertThat(group.size()).isEqualTo(1); - final ValidatableAttestation attestation2 = addAttestation(2); + final PooledAttestationWithData attestation2 = + toPooledAttestationWithData(addPooledAttestation(2)); assertThat(group.size()).isEqualTo(2); - addAttestation(3, 4); + addPooledAttestation(3, 4); assertThat(group.size()).isEqualTo(3); - addAttestation(1, 2); + addPooledAttestation(1, 2); assertThat(group.size()).isEqualTo(4); int numRemoved = group.onAttestationIncludedInBlock( UInt64.ZERO, - aggregateAttestations(attestation1.getAttestation(), attestation2.getAttestation())); + aggregateAttestations( + attestation1.toAttestation(attestationSchema), + attestation2.toAttestation(attestationSchema))); assertThat(numRemoved).isEqualTo(3); assertThat(group.size()).isEqualTo(1); } - private ValidatableAttestation addAttestation(final int... validators) { - return addAttestation(Optional.empty(), validators); + private PooledAttestation addPooledAttestation(final int... validators) { + return addPooledAttestation(Optional.empty(), validators); } - private ValidatableAttestation addAttestation( + private PooledAttestation addPooledAttestation( final Optional committeeIndex, final int... validators) { - final ValidatableAttestation attestation = createAttestation(committeeIndex, validators); - final boolean added = group.add(attestation); + final PooledAttestation attestation = createPooledAttestation(committeeIndex, validators); + final boolean added = group.add(attestation, Optional.empty()); assertThat(added).isTrue(); return attestation; } - private ValidatableAttestation createAttestation(final int... validators) { - return createAttestation(Optional.empty(), validators); + private PooledAttestation createPooledAttestation(final int... validators) { + return createPooledAttestation(Optional.empty(), validators); } - private ValidatableAttestation createAttestation( + private PooledAttestation createPooledAttestation( final Optional committeeIndex, final int... validators) { final SszBitlist aggregationBits = attestationSchema.getAggregationBitsSchema().ofBits(10, validators); @@ -408,6 +432,26 @@ private ValidatableAttestation createAttestation( singleAttestation.ifPresent( __ -> validatableAttestation.convertToAggregatedFormatFromSingleAttestation(attestation)); - return validatableAttestation; + return PooledAttestation.fromValidatableAttestation(validatableAttestation); + } + + private Attestation toAttestation(final PooledAttestation pooledAttestation) { + return attestationSchema.create( + pooledAttestation.bits().getAggregationBits(), + attestationData, + pooledAttestation.aggregatedSignature(), + pooledAttestation.bits()::getCommitteeBits); + } + + private PooledAttestationWithData toPooledAttestationWithData( + final PooledAttestation pooledAttestation) { + return toPooledAttestationWithData(toAttestation(pooledAttestation)); + } + + private PooledAttestationWithData toPooledAttestationWithData(final Attestation attestation) { + return new PooledAttestationWithData( + attestationData, + PooledAttestation.fromValidatableAttestation( + ValidatableAttestation.from(spec, attestation, committeeSizes))); } } diff --git a/ethereum/statetransition/src/test/java/tech/pegasys/teku/statetransition/attestation/utils/AttestationBitsAggregatorElectraTest.java b/ethereum/statetransition/src/test/java/tech/pegasys/teku/statetransition/attestation/utils/AttestationBitsAggregatorElectraTest.java index 34009aae4f6..4ead6f4fe01 100644 --- a/ethereum/statetransition/src/test/java/tech/pegasys/teku/statetransition/attestation/utils/AttestationBitsAggregatorElectraTest.java +++ b/ethereum/statetransition/src/test/java/tech/pegasys/teku/statetransition/attestation/utils/AttestationBitsAggregatorElectraTest.java @@ -32,14 +32,16 @@ import tech.pegasys.teku.spec.Spec; import tech.pegasys.teku.spec.TestSpecFactory; import tech.pegasys.teku.spec.datastructures.attestation.ValidatableAttestation; +import tech.pegasys.teku.spec.datastructures.operations.Attestation; import tech.pegasys.teku.spec.datastructures.operations.AttestationData; import tech.pegasys.teku.spec.datastructures.operations.AttestationSchema; import tech.pegasys.teku.spec.util.DataStructureUtil; +import tech.pegasys.teku.statetransition.attestation.PooledAttestation; public class AttestationBitsAggregatorElectraTest { private final Spec spec = TestSpecFactory.createMainnetElectra(); private final DataStructureUtil dataStructureUtil = new DataStructureUtil(spec); - private final AttestationSchema attestationSchema = + private final AttestationSchema attestationSchema = spec.getGenesisSchemaDefinitions().getAttestationSchema(); private final AttestationData attestationData = dataStructureUtil.randomAttestationData(); @@ -64,13 +66,13 @@ void aggregateFromEmpty() { 012 <- committee 1 indices 011 <- bits */ - final ValidatableAttestation initialAttestation = createAttestation(List.of(1), 1, 2); + final AttestationBitsAggregator attestation = createAttestationBits(List.of(1), 1, 2); final AttestationBitsAggregator aggregator = AttestationBitsAggregator.fromEmptyFromAttestationSchema( attestationSchema, Optional.of(committeeSizes)); - assertThat(aggregator.aggregateWith(initialAttestation.getAttestation())).isTrue(); + assertThat(aggregator.aggregateWith(attestation)).isTrue(); assertThat(aggregator.getCommitteeBits().streamAllSetBits()).containsExactly(1); @@ -83,17 +85,15 @@ void cannotAggregateSameCommitteesWithOverlappingAggregates() { 012 <- committee 1 indices 011 <- bits */ - final ValidatableAttestation initialAttestation = createAttestation(List.of(1), 1, 2); + final AttestationBitsAggregator attestation = createAttestationBits(List.of(1), 1, 2); /* 012 <- committee 1 indices 110 <- bits */ - final ValidatableAttestation otherAttestation = createAttestation(List.of(1), 0, 1); + final AttestationBitsAggregator otherAttestation = createAttestationBits(List.of(1), 0, 1); - final AttestationBitsAggregator aggregator = AttestationBitsAggregator.of(initialAttestation); - - assertThat(aggregator.aggregateWith(otherAttestation.getAttestation())).isFalse(); + assertThat(attestation.aggregateWith(otherAttestation)).isFalse(); } @Test @@ -102,25 +102,23 @@ void aggregateOnSameCommittee() { 012 <- committee 1 indices 011 <- bits */ - final ValidatableAttestation initialAttestation = createAttestation(List.of(1), 1, 2); + final AttestationBitsAggregator attestation = createAttestationBits(List.of(1), 1, 2); /* 012 <- committee 1 indices 100 <- bits */ - final ValidatableAttestation otherAttestation = createAttestation(List.of(1), 0); - - final AttestationBitsAggregator aggregator = AttestationBitsAggregator.of(initialAttestation); + final AttestationBitsAggregator otherAttestation = createAttestationBits(List.of(1), 0); - assertThat(aggregator.aggregateWith(otherAttestation.getAttestation())).isTrue(); + assertThat(attestation.aggregateWith(otherAttestation)).isTrue(); /* 012 <- committee 1 indices 111 <- bits */ - assertThat(aggregator.getCommitteeBits().streamAllSetBits()).containsExactly(1); - assertThat(aggregator.getAggregationBits().streamAllSetBits()).containsExactly(0, 1, 2); + assertThat(attestation.getCommitteeBits().streamAllSetBits()).containsExactly(1); + assertThat(attestation.getAggregationBits().streamAllSetBits()).containsExactly(0, 1, 2); } @Test @@ -129,25 +127,23 @@ void aggregateOnMultipleOverlappingCommitteeBits() { 01|234 <- committee 0 and 1 indices 10|100 <- bits */ - final ValidatableAttestation initialAttestation = createAttestation(List.of(0, 1), 0, 2); + final AttestationBitsAggregator attestation = createAttestationBits(List.of(0, 1), 0, 2); /* 01|234 <- committee 0 and 1 indices 01|010 <- bits */ - final ValidatableAttestation otherAttestation = createAttestation(List.of(0, 1), 1, 3); - - final AttestationBitsAggregator aggregator = AttestationBitsAggregator.of(initialAttestation); + final AttestationBitsAggregator otherAttestation = createAttestationBits(List.of(0, 1), 1, 3); - assertThat(aggregator.aggregateWith(otherAttestation.getAttestation())).isTrue(); + assertThat(attestation.aggregateWith(otherAttestation)).isTrue(); /* 01|234 <- committee 0 and 1 indices 11|110 <- bits */ - assertThat(aggregator.getCommitteeBits().streamAllSetBits()).containsExactly(0, 1); - assertThat(aggregator.getAggregationBits().streamAllSetBits()).containsExactly(0, 1, 2, 3); + assertThat(attestation.getCommitteeBits().streamAllSetBits()).containsExactly(0, 1); + assertThat(attestation.getAggregationBits().streamAllSetBits()).containsExactly(0, 1, 2, 3); } @Test @@ -156,25 +152,24 @@ void aggregateOnMultipleOverlappingCommitteeBitsVariation() { 01|234 <- committee 0 and 1 indices 10|100 <- bits */ - final ValidatableAttestation initialAttestation = createAttestation(List.of(0, 1), 0, 2); + final AttestationBitsAggregator attestation = createAttestationBits(List.of(0, 1), 0, 2); /* 01|234|5678 <- committee 0, 1 and 2 indices 01|011|0001 <- bits */ - final ValidatableAttestation otherAttestation = createAttestation(List.of(0, 1, 2), 1, 3, 4, 8); + final AttestationBitsAggregator otherAttestation = + createAttestationBits(List.of(0, 1, 2), 1, 3, 4, 8); - final AttestationBitsAggregator aggregator = AttestationBitsAggregator.of(initialAttestation); - - assertThat(aggregator.aggregateWith(otherAttestation.getAttestation())).isTrue(); + assertThat(attestation.aggregateWith(otherAttestation)).isTrue(); /* 01|234|5678 <- committee 0, 1 and 2 indices 11|111|0001 <- bits */ - assertThat(aggregator.getCommitteeBits().streamAllSetBits()).containsExactly(0, 1, 2); - assertThat(aggregator.getAggregationBits().streamAllSetBits()) + assertThat(attestation.getCommitteeBits().streamAllSetBits()).containsExactly(0, 1, 2); + assertThat(attestation.getAggregationBits().streamAllSetBits()) .containsExactly(0, 1, 2, 3, 4, 8); } @@ -184,22 +179,21 @@ void cannotAggregateOnMultipleOverlappingCommitteeBitsButWithSomeOfAggregationOv 01|234 <- committee 0 and 1 indices 10|100 <- bits */ - final ValidatableAttestation initialAttestation = createAttestation(List.of(0, 1), 0, 2); + final AttestationBitsAggregator attestation = createAttestationBits(List.of(0, 1), 0, 2); /* 01|234|5678 <- committee 0, 1 and 2 indices 01|110|0001 <- bits */ - final ValidatableAttestation otherAttestation = createAttestation(List.of(0, 1, 2), 1, 2, 3, 8); - - final AttestationBitsAggregator aggregator = AttestationBitsAggregator.of(initialAttestation); + final AttestationBitsAggregator otherAttestation = + createAttestationBits(List.of(0, 1, 2), 1, 2, 3, 8); - assertThat(aggregator.aggregateWith(otherAttestation.getAttestation())).isFalse(); + assertThat(attestation.aggregateWith(otherAttestation)).isFalse(); // check remained untouched - assertThat(aggregator.getCommitteeBits().streamAllSetBits()).containsExactly(0, 1); - assertThat(aggregator.getAggregationBits().streamAllSetBits()).containsExactly(0, 2); + assertThat(attestation.getCommitteeBits().streamAllSetBits()).containsExactly(0, 1); + assertThat(attestation.getAggregationBits().streamAllSetBits()).containsExactly(0, 2); } @Test @@ -208,29 +202,28 @@ void aggregateOnMultipleOverlappingCommitteeBitsButWithSomeOfAggregationOverlapp 01|234 <- committee 0 and 1 indices 10|100 <- bits */ - final ValidatableAttestation initialAttestation = createAttestation(List.of(0, 1), 0, 2); + final AttestationBitsAggregator attestation = createAttestationBits(List.of(0, 1), 0, 2); /* 01|234|5678 <- committee 0, 1 and 2 indices 01|110|0001 <- bits */ - final ValidatableAttestation otherAttestation = createAttestation(List.of(0, 1, 2), 1, 2, 3, 8); - - final AttestationBitsAggregator aggregator = AttestationBitsAggregator.of(initialAttestation); + final AttestationBitsAggregator otherAttestation = + createAttestationBits(List.of(0, 1, 2), 1, 2, 3, 8); // cannot aggregate - assertThat(aggregator.aggregateWith(otherAttestation.getAttestation())).isFalse(); + assertThat(attestation.aggregateWith(otherAttestation)).isFalse(); // calculate the or - aggregator.or(otherAttestation.getAttestation()); + attestation.or(otherAttestation); /* 01|234|5678 <- committee 0, 1 and 2 indices 11|110|0001 <- bits */ - assertThat(aggregator.getCommitteeBits().streamAllSetBits()).containsExactly(0, 1, 2); - assertThat(aggregator.getAggregationBits().streamAllSetBits()).containsExactly(0, 1, 2, 3, 8); + assertThat(attestation.getCommitteeBits().streamAllSetBits()).containsExactly(0, 1, 2); + assertThat(attestation.getAggregationBits().streamAllSetBits()).containsExactly(0, 1, 2, 3, 8); } @Test @@ -239,26 +232,24 @@ void aggregateSingleAttestationFillUp() { 01|234 <- committee 0 and 1 indices 10|100 <- bits */ - final ValidatableAttestation initialAttestation = createAttestation(List.of(0, 1), 0, 2); + final AttestationBitsAggregator attestation = createAttestationBits(List.of(0, 1), 0, 2); /* 123 <- committee 1 indices 001 <- bits */ - final ValidatableAttestation otherAttestation = createAttestation(List.of(1), 2); - - final AttestationBitsAggregator aggregator = AttestationBitsAggregator.of(initialAttestation); + final AttestationBitsAggregator otherAttestation = createAttestationBits(List.of(1), 2); // calculate the or - aggregator.or(otherAttestation.getAttestation()); + attestation.or(otherAttestation); /* 01|234 <- committee 0 and 1 indices 10|101 <- bits */ - assertThat(aggregator.getCommitteeBits().streamAllSetBits()).containsExactly(0, 1); - assertThat(aggregator.getAggregationBits().streamAllSetBits()).containsExactly(0, 2, 4); + assertThat(attestation.getCommitteeBits().streamAllSetBits()).containsExactly(0, 1); + assertThat(attestation.getAggregationBits().streamAllSetBits()).containsExactly(0, 2, 4); } @Test @@ -268,29 +259,27 @@ void aggregateSingleAttestationFillUp() { 0123 <- committee 2 indices 0100 <- bits */ - final ValidatableAttestation initialAttestation = createAttestation(List.of(2), 1); + final AttestationBitsAggregator attestation = createAttestationBits(List.of(2), 1); /* 0123 <- committee 2 indices 1101 <- bits */ - final ValidatableAttestation otherAttestation = createAttestation(List.of(2), 0, 1, 3); - - AttestationBitsAggregator aggregator = AttestationBitsAggregator.of(initialAttestation); + final AttestationBitsAggregator otherAttestation = createAttestationBits(List.of(2), 0, 1, 3); // cannot aggregate - assertThat(aggregator.aggregateWith(otherAttestation.getAttestation())).isFalse(); + assertThat(attestation.aggregateWith(otherAttestation)).isFalse(); // calculate the or - aggregator.or(otherAttestation.getAttestation()); + attestation.or(otherAttestation); /* 01|234|5678 <- committee 0, 1 and 2 indices 11|110|0001 <- bits */ - assertThat(aggregator.getCommitteeBits().streamAllSetBits()).containsExactly(2); - assertThat(aggregator.getAggregationBits().streamAllSetBits()).containsExactly(0, 1, 3); + assertThat(attestation.getCommitteeBits().streamAllSetBits()).containsExactly(2); + assertThat(attestation.getAggregationBits().streamAllSetBits()).containsExactly(0, 1, 3); } @Test @@ -299,25 +288,23 @@ void aggregateOnMultipleDisjointedCommitteeBits() { 01|234 <- committee 0 and 1 indices 10|100 <- bits */ - final ValidatableAttestation initialAttestation = createAttestation(List.of(0, 1), 0, 2); + final AttestationBitsAggregator attestation = createAttestationBits(List.of(0, 1), 0, 2); /* 0123 <- committee 1 indices 1101 <- bits */ - final ValidatableAttestation otherAttestation = createAttestation(List.of(2), 0, 1, 3); - - final AttestationBitsAggregator aggregator = AttestationBitsAggregator.of(initialAttestation); + final AttestationBitsAggregator otherAttestation = createAttestationBits(List.of(2), 0, 1, 3); - assertThat(aggregator.aggregateWith(otherAttestation.getAttestation())).isTrue(); + assertThat(attestation.aggregateWith(otherAttestation)).isTrue(); /* 01|234|5678 <- committee 0, 1 and 2 indices 10|100|1101 <- bits */ - assertThat(aggregator.getCommitteeBits().streamAllSetBits()).containsExactly(0, 1, 2); - assertThat(aggregator.getAggregationBits().streamAllSetBits()).containsExactly(0, 2, 5, 6, 8); + assertThat(attestation.getCommitteeBits().streamAllSetBits()).containsExactly(0, 1, 2); + assertThat(attestation.getAggregationBits().streamAllSetBits()).containsExactly(0, 2, 5, 6, 8); } @Test @@ -326,25 +313,23 @@ void aggregateOnMultipleDisjointedCommitteeBitsVariation() { 0123 <- committee 2 indices 0001 <- bits */ - final ValidatableAttestation initialAttestation = createAttestation(List.of(2), 3); + final AttestationBitsAggregator attestation = createAttestationBits(List.of(2), 3); /* 01 <- committee 0 indices 01 <- bits */ - final ValidatableAttestation otherAttestation = createAttestation(List.of(0), 1); + final AttestationBitsAggregator otherAttestation = createAttestationBits(List.of(0), 1); - final AttestationBitsAggregator aggregator = AttestationBitsAggregator.of(initialAttestation); - - assertThat(aggregator.aggregateWith(otherAttestation.getAttestation())).isTrue(); + assertThat(attestation.aggregateWith(otherAttestation)).isTrue(); /* 01|2345 <- committee 0 and 2 indices 01|0001 <- bits */ - assertThat(aggregator.getCommitteeBits().streamAllSetBits()).containsExactly(0, 2); - assertThat(aggregator.getAggregationBits().streamAllSetBits()).containsExactly(1, 5); + assertThat(attestation.getCommitteeBits().streamAllSetBits()).containsExactly(0, 2); + assertThat(attestation.getAggregationBits().streamAllSetBits()).containsExactly(1, 5); } @Test @@ -367,8 +352,8 @@ void bigAggregation() { committeeSizes.put(14, 50); committeeSizes.put(15, 51); - final ValidatableAttestation initialAttestation = - createAttestation( + final AttestationBitsAggregator attestation = + createAttestationBits( "1111111111111111", """ 11111111111111111111111111111111111111111111111111\ @@ -388,14 +373,14 @@ void bigAggregation() { 00100000000000000000000000000000000000000000000000\ 000000000000001000000000000000000000000000000000001\ """); - final ValidatableAttestation att = - createAttestation("0001000000000000", "00000000000000000000000100000000000000000000000000"); + final AttestationBitsAggregator att = + createAttestationBits( + "0001000000000000", "00000000000000000000000100000000000000000000000000"); - final AttestationBitsAggregator aggregator = AttestationBitsAggregator.of(initialAttestation); - assertThat(aggregator.aggregateWith(att.getAttestation())).isTrue(); + assertThat(attestation.aggregateWith(att)).isTrue(); - final ValidatableAttestation result = - createAttestation( + final AttestationBitsAggregator result = + createAttestationBits( "1111111111111111", """ 11111111111111111111111111111111111111111111111111\ @@ -416,10 +401,8 @@ void bigAggregation() { 000000000000001000000000000000000000000000000000001\ """); - assertThat(aggregator.getCommitteeBits()) - .isEqualTo(result.getAttestation().getCommitteeBitsRequired()); - assertThat(aggregator.getAggregationBits()) - .isEqualTo(result.getAttestation().getAggregationBits()); + assertThat(attestation.getCommitteeBits()).isEqualTo(result.getCommitteeBits()); + assertThat(attestation.getAggregationBits()).isEqualTo(result.getAggregationBits()); } @Test @@ -428,9 +411,9 @@ void orIntoEmptyAggregator() { AttestationBitsAggregator.fromEmptyFromAttestationSchema( attestationSchema, Optional.of(committeeSizes)); - final ValidatableAttestation otherAttestation = createAttestation(List.of(1), 2); + final AttestationBitsAggregator otherAttestation = createAttestationBits(List.of(1), 2); - aggregator.or(otherAttestation.getAttestation()); + aggregator.or(otherAttestation); assertThat(aggregator.getCommitteeBits().streamAllSetBits()).containsExactly(1); assertThat(aggregator.getAggregationBits().streamAllSetBits()).containsExactly(2); @@ -438,129 +421,144 @@ void orIntoEmptyAggregator() { @Test void orWithNewCommittee() { - final ValidatableAttestation initialAttestation = createAttestation(List.of(0), 1); - final AttestationBitsAggregator aggregator = AttestationBitsAggregator.of(initialAttestation); + final AttestationBitsAggregator attestation = createAttestationBits(List.of(0), 1); - final ValidatableAttestation otherAttestation = createAttestation(List.of(1), 2); + final AttestationBitsAggregator otherAttestation = createAttestationBits(List.of(1), 2); - aggregator.or(otherAttestation.getAttestation()); + attestation.or(otherAttestation); - assertThat(aggregator.getCommitteeBits().streamAllSetBits()).containsExactlyInAnyOrder(0, 1); - assertThat(aggregator.getAggregationBits().streamAllSetBits()).containsExactlyInAnyOrder(1, 4); + assertThat(attestation.getCommitteeBits().streamAllSetBits()).containsExactlyInAnyOrder(0, 1); + assertThat(attestation.getAggregationBits().streamAllSetBits()).containsExactlyInAnyOrder(1, 4); } @Test void orWithExistingCommitteeAddNewBits() { - final ValidatableAttestation initialAttestation = createAttestation(List.of(1), 0); - final AttestationBitsAggregator aggregator = AttestationBitsAggregator.of(initialAttestation); + final AttestationBitsAggregator attestation = createAttestationBits(List.of(1), 0); - final ValidatableAttestation otherAttestation = createAttestation(List.of(1), 2); + final AttestationBitsAggregator otherAttestation = createAttestationBits(List.of(1), 2); - aggregator.or(otherAttestation.getAttestation()); + attestation.or(otherAttestation); - assertThat(aggregator.getCommitteeBits().streamAllSetBits()).containsExactly(1); - assertThat(aggregator.getAggregationBits().streamAllSetBits()).containsExactlyInAnyOrder(0, 2); + assertThat(attestation.getCommitteeBits().streamAllSetBits()).containsExactly(1); + assertThat(attestation.getAggregationBits().streamAllSetBits()).containsExactlyInAnyOrder(0, 2); } @Test void orWithExistingCommitteeOverlapAndNewBits() { - final ValidatableAttestation initialAttestation = createAttestation(List.of(1), 0, 1); - final AttestationBitsAggregator aggregator = AttestationBitsAggregator.of(initialAttestation); + final AttestationBitsAggregator attestation = createAttestationBits(List.of(1), 0, 1); - final ValidatableAttestation otherAttestation = createAttestation(List.of(1), 1, 2); + final AttestationBitsAggregator otherAttestation = createAttestationBits(List.of(1), 1, 2); - aggregator.or(otherAttestation.getAttestation()); + attestation.or(otherAttestation); - assertThat(aggregator.getCommitteeBits().streamAllSetBits()).containsExactly(1); - assertThat(aggregator.getAggregationBits().streamAllSetBits()) + assertThat(attestation.getCommitteeBits().streamAllSetBits()).containsExactly(1); + assertThat(attestation.getAggregationBits().streamAllSetBits()) .containsExactlyInAnyOrder(0, 1, 2); } @Test void orWithStrictSubsetAttestation() { - final ValidatableAttestation initialAttestation = createAttestation(List.of(1), 0, 2); - final AttestationBitsAggregator aggregator = AttestationBitsAggregator.of(initialAttestation); + final AttestationBitsAggregator attestation = createAttestationBits(List.of(1), 0, 2); - final ValidatableAttestation otherAttestation = createAttestation(List.of(1), 0); + final AttestationBitsAggregator otherAttestation = createAttestationBits(List.of(1), 0); - aggregator.or(otherAttestation.getAttestation()); + attestation.or(otherAttestation); - assertThat(aggregator.getCommitteeBits().streamAllSetBits()).containsExactly(1); - assertThat(aggregator.getAggregationBits().streamAllSetBits()).containsExactlyInAnyOrder(0, 2); + assertThat(attestation.getCommitteeBits().streamAllSetBits()).containsExactly(1); + assertThat(attestation.getAggregationBits().streamAllSetBits()).containsExactlyInAnyOrder(0, 2); } @Test void orWithMultipleCommitteesMixedNewAndExisting() { - final ValidatableAttestation initialAttestation = createAttestation(List.of(0, 1), 0, 3); - final AttestationBitsAggregator aggregator = AttestationBitsAggregator.of(initialAttestation); + final AttestationBitsAggregator attestation = createAttestationBits(List.of(0, 1), 0, 3); - final ValidatableAttestation otherAttestation = createAttestation(List.of(1, 2), 2, 5); + final AttestationBitsAggregator otherAttestation = createAttestationBits(List.of(1, 2), 2, 5); - aggregator.or(otherAttestation.getAttestation()); + attestation.or(otherAttestation); - assertThat(aggregator.getCommitteeBits().streamAllSetBits()).containsExactlyInAnyOrder(0, 1, 2); - assertThat(aggregator.getAggregationBits().streamAllSetBits()) + assertThat(attestation.getCommitteeBits().streamAllSetBits()) + .containsExactlyInAnyOrder(0, 1, 2); + assertThat(attestation.getAggregationBits().streamAllSetBits()) .containsExactlyInAnyOrder(0, 3, 4, 7); } @Test void orAggregatorWithAggregator() { - final ValidatableAttestation att1Data = createAttestation(List.of(0), 0); - final AttestationBitsAggregator aggregator1 = AttestationBitsAggregator.of(att1Data); + final AttestationBitsAggregator att1Data = createAttestationBits(List.of(0), 0); - final ValidatableAttestation att2Data = createAttestation(List.of(0, 1), 1, 2); - final AttestationBitsAggregator aggregator2 = AttestationBitsAggregator.of(att2Data); + final AttestationBitsAggregator att2Data = createAttestationBits(List.of(0, 1), 1, 2); - aggregator1.or(aggregator2); + att1Data.or(att2Data); - assertThat(aggregator1.getCommitteeBits().streamAllSetBits()).containsExactlyInAnyOrder(0, 1); - assertThat(aggregator1.getAggregationBits().streamAllSetBits()) - .containsExactlyInAnyOrder(0, 1, 2); + assertThat(att1Data.getCommitteeBits().streamAllSetBits()).containsExactlyInAnyOrder(0, 1); + assertThat(att1Data.getAggregationBits().streamAllSetBits()).containsExactlyInAnyOrder(0, 1, 2); // aggregator2 should remain unchanged - assertThat(aggregator2.getCommitteeBits().streamAllSetBits()).containsExactlyInAnyOrder(0, 1); - assertThat(aggregator2.getAggregationBits().streamAllSetBits()).containsExactlyInAnyOrder(1, 2); + assertThat(att2Data.getCommitteeBits().streamAllSetBits()).containsExactlyInAnyOrder(0, 1); + assertThat(att2Data.getAggregationBits().streamAllSetBits()).containsExactlyInAnyOrder(1, 2); } @Test - void isSuperSetOf1() { + void isSuperSetOf_nonPooledAttestation() { /* 01|234 <- committee 0 and 1 indices 10|101 <- bits */ - final ValidatableAttestation initialAttestation = createAttestation(List.of(0, 1), 0, 2, 4); + final AttestationBitsAggregator attestation = createAttestationBits(List.of(0, 1), 0, 2, 4); /* 01|234 <- committee 0 and 1 indices 10|100 <- bits */ - final ValidatableAttestation otherAttestation = createAttestation(List.of(0, 1), 0, 2); + final AttestationBitsAggregator otherAttestation = createAttestationBits(List.of(0, 1), 0, 2); - AttestationBitsAggregator aggregator = AttestationBitsAggregator.of(initialAttestation); + final Attestation other = + attestationSchema.create( + otherAttestation.getAggregationBits(), + attestationData, + dataStructureUtil.randomSignature(), + otherAttestation::getCommitteeBits); - assertThat(aggregator.isSuperSetOf(otherAttestation.getAttestation())).isTrue(); + assertThat(attestation.isSuperSetOf(other)).isTrue(); } @Test - void isSuperSetOf2() { + void isSuperSetOf1() { /* 01|234 <- committee 0 and 1 indices 10|101 <- bits */ - final ValidatableAttestation initialAttestation = createAttestation(List.of(0, 1), 0, 2, 4); + final AttestationBitsAggregator attestation = createAttestationBits(List.of(0, 1), 0, 2, 4); + + /* + 01|234 <- committee 0 and 1 indices + 10|100 <- bits + */ + final AttestationBitsAggregator otherAttestation = createAttestationBits(List.of(0, 1), 0, 2); + + assertThat(attestation.isSuperSetOf(otherAttestation)).isTrue(); + } + + @Test + void isSuperSetOf2() { /* 01|234 <- committee 0 and 1 indices 10|101 <- bits */ - final ValidatableAttestation otherAttestation = createAttestation(List.of(0, 1), 0, 2, 4); + final AttestationBitsAggregator attestation = createAttestationBits(List.of(0, 1), 0, 2, 4); - final AttestationBitsAggregator aggregator = AttestationBitsAggregator.of(initialAttestation); + /* + 01|234 <- committee 0 and 1 indices + 10|101 <- bits + */ + final AttestationBitsAggregator otherAttestation = + createAttestationBits(List.of(0, 1), 0, 2, 4); - assertThat(aggregator.isSuperSetOf(otherAttestation.getAttestation())).isTrue(); + assertThat(attestation.isSuperSetOf(otherAttestation)).isTrue(); } @Test @@ -570,17 +568,16 @@ void isSuperSetOf3() { 01|234 <- committee 0 and 1 indices 10|101 <- bits */ - final ValidatableAttestation initialAttestation = createAttestation(List.of(0, 1), 0, 2, 4); + final AttestationBitsAggregator attestation = createAttestationBits(List.of(0, 1), 0, 2, 4); /* 01|234 <- committee 0 and 1 indices 10|111 <- bits */ - final ValidatableAttestation otherAttestation = createAttestation(List.of(0, 1), 0, 2, 3, 4); - - final AttestationBitsAggregator aggregator = AttestationBitsAggregator.of(initialAttestation); + final AttestationBitsAggregator otherAttestation = + createAttestationBits(List.of(0, 1), 0, 2, 3, 4); - assertThat(aggregator.isSuperSetOf(otherAttestation.getAttestation())).isFalse(); + assertThat(attestation.isSuperSetOf(otherAttestation)).isFalse(); } @Test @@ -590,17 +587,15 @@ void isSuperSetOf4() { 01|234 <- committee 0 and 1 indices 10|101 <- bits */ - final ValidatableAttestation initialAttestation = createAttestation(List.of(0, 1), 0, 2, 4); + final AttestationBitsAggregator attestation = createAttestationBits(List.of(0, 1), 0, 2, 4); /* 012 <- committee 1 indices 111 <- bits */ - final ValidatableAttestation otherAttestation = createAttestation(List.of(1), 0, 1, 2); + final AttestationBitsAggregator otherAttestation = createAttestationBits(List.of(1), 0, 1, 2); - AttestationBitsAggregator aggregator = AttestationBitsAggregator.of(initialAttestation); - - assertThat(aggregator.isSuperSetOf(otherAttestation.getAttestation())).isFalse(); + assertThat(attestation.isSuperSetOf(otherAttestation)).isFalse(); } @Test @@ -610,17 +605,15 @@ void isSuperSetOf5() { 01 <- committee 0 10 <- bits */ - final ValidatableAttestation initialAttestation = createAttestation(List.of(0), 0); + final AttestationBitsAggregator attestation = createAttestationBits(List.of(0), 0); /* 012 <- committee 1 indices 100 <- bits */ - final ValidatableAttestation otherAttestation = createAttestation(List.of(1), 0); - - final AttestationBitsAggregator aggregator = AttestationBitsAggregator.of(initialAttestation); + final AttestationBitsAggregator otherAttestation = createAttestationBits(List.of(1), 0); - assertThat(aggregator.isSuperSetOf(otherAttestation.getAttestation())).isFalse(); + assertThat(attestation.isSuperSetOf(otherAttestation)).isFalse(); } @Test @@ -630,70 +623,104 @@ void isSuperSetOf6() { 01|234|5678 <- committee 0, 1 and 2 indices 11|111|1111 <- bits */ - final ValidatableAttestation initialAttestation = - createAttestation(List.of(0, 1, 2), 0, 1, 2, 3, 4, 5, 6, 7, 8); + final AttestationBitsAggregator attestation = + createAttestationBits(List.of(0, 1, 2), 0, 1, 2, 3, 4, 5, 6, 7, 8); /* 012 <- committee 1 indices 100 <- bits */ - final ValidatableAttestation otherAttestation = createAttestation(List.of(1), 0); - - final AttestationBitsAggregator aggregator = AttestationBitsAggregator.of(initialAttestation); + final AttestationBitsAggregator otherAttestation = createAttestationBits(List.of(1), 0); - assertThat(aggregator.isSuperSetOf(otherAttestation.getAttestation())).isTrue(); + assertThat(attestation.isSuperSetOf(otherAttestation)).isTrue(); } @Test void getAggregationBits_shouldBeConsistent_singleCommittee() { - final ValidatableAttestation initialAttestation = createAttestation(List.of(0), 0); - final AttestationBitsAggregator aggregator = AttestationBitsAggregator.of(initialAttestation); + final AttestationBitsAggregator attestation = createAttestationBits(List.of(0), 0); - assertThat(aggregator.getAggregationBits().size()).isEqualTo(committeeSizes.get(0)); + assertThat(attestation.getAggregationBits().size()).isEqualTo(committeeSizes.get(0)); - assertThat(aggregator.getAggregationBits()) - .isEqualTo(initialAttestation.getAttestation().getAggregationBits()); + assertThat(attestation.getAggregationBits()).isEqualTo(attestation.getAggregationBits()); } @Test void getAggregationBits_shouldBeConsistent_multiCommittee() { - final ValidatableAttestation initialAttestation = createAttestation(List.of(0, 1), 0, 3); - final AttestationBitsAggregator aggregator = AttestationBitsAggregator.of(initialAttestation); + final AttestationBitsAggregator attestation = createAttestationBits(List.of(0, 1), 0, 3); - assertThat(aggregator.getAggregationBits().size()) + assertThat(attestation.getAggregationBits().size()) .isEqualTo(committeeSizes.get(0) + committeeSizes.get(1)); - assertThat(aggregator.getAggregationBits()) - .isEqualTo(initialAttestation.getAttestation().getAggregationBits()); + assertThat(attestation.getAggregationBits()).isEqualTo(attestation.getAggregationBits()); } @Test void copy_shouldNotModifyOriginal() { - final ValidatableAttestation initialAttestation = createAttestation(List.of(0), 0); - final AttestationBitsAggregator aggregator = AttestationBitsAggregator.of(initialAttestation); + final AttestationBitsAggregator attestation = createAttestationBits(List.of(0), 0); + final AttestationBitsAggregator sameAttestation = createAttestationBits(List.of(0), 0); - // check aggregator is initialized correctly - assertThat(aggregator.getCommitteeBits()) - .isEqualTo(initialAttestation.getAttestation().getCommitteeBitsRequired()); - assertThat(aggregator.getAggregationBits()) - .isEqualTo(initialAttestation.getAttestation().getAggregationBits()); + final AttestationBitsAggregator copy = attestation.copy(); - final AttestationBitsAggregator copy = aggregator.copy(); + assertThat(copy.getCommitteeBits()).isEqualTo(attestation.getCommitteeBits()); + assertThat(copy.getAggregationBits()).isEqualTo(attestation.getAggregationBits()); + assertThat(copy).isNotSameAs(attestation); - assertThat(copy.getCommitteeBits()).isEqualTo(aggregator.getCommitteeBits()); - assertThat(copy.getAggregationBits()).isEqualTo(aggregator.getAggregationBits()); - assertThat(copy).isNotSameAs(aggregator); - - assertThat(copy.aggregateWith(createAttestation(List.of(1), 1).getAttestation())).isTrue(); + assertThat(copy.aggregateWith(createAttestationBits(List.of(1), 1))).isTrue(); // the original should not be modified - assertThat(aggregator.getCommitteeBits()) - .isEqualTo(initialAttestation.getAttestation().getCommitteeBitsRequired()); - assertThat(aggregator.getAggregationBits()) - .isEqualTo(initialAttestation.getAttestation().getAggregationBits()); + assertThat(attestation.getCommitteeBits()).isEqualTo(sameAttestation.getCommitteeBits()); + assertThat(attestation.getAggregationBits()).isEqualTo(sameAttestation.getAggregationBits()); + } + + @Test + void equals_shouldBeConsistent() { + final AttestationBitsAggregator attestation = createAttestationBits(List.of(0, 1), 0, 3); + final AttestationBitsAggregator aggregator = createAttestationBits(List.of(0, 1), 0, 3); + + assertThat(aggregator).isEqualTo(attestation); + assertThat(aggregator.aggregateWith(createAttestationBits(List.of(0), 1))).isTrue(); + + assertThat(aggregator).isNotEqualTo(attestation); + assertThat(aggregator).isNotEqualTo(null); + assertThat(aggregator).isNotEqualTo(new Object()); + } + + @Test + void isExclusivelyFromCommittee_shouldReturnConsistentResult() { + final AttestationBitsAggregator fromMultipleCommittees = + createAttestationBits(List.of(0, 1), 0, 3); + final AttestationBitsAggregator fromSingleCommittee = createAttestationBits(List.of(0), 0, 1); + final AttestationBitsAggregator singleAttestationFromSingleCommittee = + createAttestationBits(List.of(1), 0); + + assertThat(fromMultipleCommittees.isExclusivelyFromCommittee(0)).isFalse(); + assertThat(fromMultipleCommittees.isExclusivelyFromCommittee(1)).isFalse(); + assertThat(fromMultipleCommittees.isExclusivelyFromCommittee(2)).isFalse(); + + assertThat(fromSingleCommittee.isExclusivelyFromCommittee(0)).isTrue(); + assertThat(fromSingleCommittee.isExclusivelyFromCommittee(1)).isFalse(); + assertThat(fromSingleCommittee.isExclusivelyFromCommittee(2)).isFalse(); + + assertThat(singleAttestationFromSingleCommittee.isExclusivelyFromCommittee(0)).isFalse(); + assertThat(singleAttestationFromSingleCommittee.isExclusivelyFromCommittee(1)).isTrue(); + assertThat(singleAttestationFromSingleCommittee.isExclusivelyFromCommittee(2)).isFalse(); + } + + @Test + void getBitCount_shouldReturnConsistentResult() { + final AttestationBitsAggregator fromMultipleCommittees = + createAttestationBits(List.of(0, 1), 0, 3); + final AttestationBitsAggregator fromSingleCommittee = createAttestationBits(List.of(0), 0, 1); + final AttestationBitsAggregator singleAttestationFromSingleCommittee = + createAttestationBits(List.of(1), 0); + + assertThat(fromMultipleCommittees.getBitCount()).isEqualTo(2); + assertThat(fromSingleCommittee.getBitCount()).isEqualTo(2); + assertThat(singleAttestationFromSingleCommittee.getBitCount()).isEqualTo(1); } - private ValidatableAttestation createAttestation(final String commBits, final String aggBits) { + private AttestationBitsAggregator createAttestationBits( + final String commBits, final String aggBits) { assertThat(commBits).matches(Pattern.compile("^[0-1]+$")); assertThat(aggBits).matches(Pattern.compile("^[0-1]+$")); final List commBitList = @@ -706,10 +733,10 @@ private ValidatableAttestation createAttestation(final String commBits, final St .map(index -> aggBits.charAt(index) == '1' ? index : -1) .filter(index -> index >= 0) .toArray(); - return createAttestation(commBitList, aggBitList); + return createAttestationBits(commBitList, aggBitList); } - private ValidatableAttestation createAttestation( + private AttestationBitsAggregator createAttestationBits( final List committeeIndices, final int... validators) { final SszBitlist aggregationBits = attestationSchema @@ -724,15 +751,13 @@ private ValidatableAttestation createAttestation( () -> attestationSchema.getCommitteeBitsSchema().orElseThrow().ofBits(committeeIndices); final ValidatableAttestation attestation = mock(ValidatableAttestation.class); - when(attestation.getAttestation()) - .thenReturn( - attestationSchema.create( - aggregationBits, - attestationData, - dataStructureUtil.randomSignature(), - committeeBits)); + var realAttestation = + attestationSchema.create( + aggregationBits, attestationData, dataStructureUtil.randomSignature(), committeeBits); + when(attestation.getAttestation()).thenReturn(realAttestation); + when(attestation.getUnconvertedAttestation()).thenReturn(realAttestation); when(attestation.getCommitteesSize()).thenReturn(Optional.of(committeeSizes)); - return attestation; + return PooledAttestation.fromValidatableAttestation(attestation).bits(); } }