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 1d03ea97357..f346cd307cc 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
@@ -193,11 +193,7 @@ public void setIndexedAttestation(final IndexedAttestation indexedAttestation) {
public void saveCommitteeShufflingSeedAndCommitteesSize(final BeaconState state) {
saveCommitteeShufflingSeed(state);
- // The committees size is only required when the committee_bits field is present in the
- // Attestation
- if (attestation.isSingleAttestation() || attestation.requiresCommitteeBits()) {
- saveCommitteesSize(state);
- }
+ saveCommitteesSize(state);
}
private void saveCommitteeShufflingSeed(final BeaconState state) {
@@ -212,10 +208,16 @@ private void saveCommitteeShufflingSeed(final BeaconState state) {
this.committeeShufflingSeed = Optional.of(committeeShufflingSeed);
}
- private void saveCommitteesSize(final BeaconState state) {
+ public void saveCommitteesSize(final BeaconState state) {
if (committeesSize.isPresent()) {
return;
}
+
+ if (!(attestation.isSingleAttestation() || attestation.requiresCommitteeBits())) {
+ // it isn't a PECTRA attestation, do nothing
+ return;
+ }
+
final Int2IntMap committeesSize =
spec.getBeaconCommitteesSize(state, attestation.getData().getSlot());
this.committeesSize = Optional.of(committeesSize);
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 0471f5fde83..ce22614d843 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
@@ -15,15 +15,31 @@
import java.util.List;
import java.util.Optional;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
import org.apache.tuweni.bytes.Bytes32;
import tech.pegasys.teku.ethereum.events.SlotEventsChannel;
+import tech.pegasys.teku.infrastructure.async.SafeFuture;
import tech.pegasys.teku.infrastructure.ssz.SszList;
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.spec.datastructures.state.beaconstate.BeaconState;
+import tech.pegasys.teku.spec.logic.common.helpers.MiscHelpers;
+import tech.pegasys.teku.storage.client.RecentChainData;
+
+public abstract class AggregatingAttestationPool implements SlotEventsChannel {
+ private static final Logger LOG = LogManager.getLogger();
+ protected final Spec spec;
+ protected final RecentChainData recentChainData;
+
+ AggregatingAttestationPool(final Spec spec, final RecentChainData recentChainData) {
+ this.spec = spec;
+ this.recentChainData = recentChainData;
+ }
-public interface AggregatingAttestationPool extends SlotEventsChannel {
/**
* Default maximum number of attestations to store in the pool.
*
@@ -33,25 +49,91 @@ public interface AggregatingAttestationPool extends SlotEventsChannel {
*
Strictly to cache all attestations for a full 2 epochs is significantly larger than this
* cache.
*/
- int DEFAULT_MAXIMUM_ATTESTATION_COUNT = 187_500;
+ public static final int DEFAULT_MAXIMUM_ATTESTATION_COUNT = 187_500;
/** The valid attestation retention period is 64 slots in deneb */
- long ATTESTATION_RETENTION_SLOTS = 64;
+ public static final long ATTESTATION_RETENTION_SLOTS = 64;
- int getSize();
+ public abstract int getSize();
- void add(ValidatableAttestation attestation);
+ public abstract void add(ValidatableAttestation attestation);
- SszList getAttestationsForBlock(
+ public abstract SszList getAttestationsForBlock(
BeaconState stateAtBlockSlot, AttestationForkChecker forkChecker);
- Optional createAggregateFor(
+ public abstract Optional createAggregateFor(
Bytes32 attestationHashTreeRoot, Optional committeeIndex);
- List getAttestations(
+ public abstract List getAttestations(
Optional maybeSlot, Optional maybeCommitteeIndex);
- void onAttestationsIncludedInBlock(UInt64 slot, Iterable attestations);
+ public abstract void onAttestationsIncludedInBlock(
+ UInt64 slot, Iterable attestations);
+
+ public abstract void onReorg(UInt64 commonAncestorSlot);
+
+ /**
+ * Ensures that the committees size is set in the attestation. This is needed for the
+ *
+ * @return false if it was not possible to set the committees size but was required, true
+ * otherwise.
+ */
+ protected boolean ensureCommitteesSizeInAttestation(final ValidatableAttestation attestation) {
+ if (attestation.getCommitteesSize().isPresent()
+ || !attestation.getAttestation().requiresCommitteeBits()) {
+ return true;
+ }
+
+ final Optional maybeState =
+ retrieveStateForAttestation(attestation.getAttestation().getData());
+ if (maybeState.isEmpty()) {
+ return false;
+ }
+
+ attestation.saveCommitteesSize(maybeState.get());
+
+ return true;
+ }
+
+ private Optional retrieveStateForAttestation(final AttestationData attestationData) {
+ // we can use the first state of the epoch to get committees for an attestation
+ final MiscHelpers miscHelpers = spec.atSlot(attestationData.getSlot()).miscHelpers();
+ final Optional maybeEpoch = recentChainData.getCurrentEpoch();
+ // the only reason this can happen is we don't have a store yet.
+ if (maybeEpoch.isEmpty()) {
+ return Optional.empty();
+ }
+ final UInt64 currentEpoch = maybeEpoch.get();
+ final UInt64 attestationEpoch = miscHelpers.computeEpochAtSlot(attestationData.getSlot());
+
+ LOG.debug("currentEpoch {}, attestationEpoch {}", currentEpoch, attestationEpoch);
+ if (attestationEpoch.equals(currentEpoch)
+ || attestationEpoch.equals(currentEpoch.minusMinZero(1))) {
+
+ try {
+ return recentChainData.getBestState().map(SafeFuture::getImmediately);
+ } catch (final IllegalStateException e) {
+ LOG.debug("Couldn't retrieve state for attestation at slot {}", attestationData.getSlot());
+ return Optional.empty();
+ }
+ }
- void onReorg(UInt64 commonAncestorSlot);
+ // attestation is not from the current or previous epoch
+ // this is really an edge case because the current or previous epoch is at least 31 slots
+ // and the attestation is only valid for 64 slots, so it may be epoch-2 but not beyond.
+ final UInt64 attestationEpochStartSlot = miscHelpers.computeStartSlotAtEpoch(attestationEpoch);
+ LOG.debug("State at slot {} needed", attestationEpochStartSlot);
+ try {
+ // Assuming retrieveStateInEffectAtSlot and getBeaconCommitteesSize are thread-safe
+ return recentChainData
+ .retrieveStateInEffectAtSlot(attestationEpochStartSlot)
+ .getImmediately();
+ } catch (final IllegalStateException e) {
+ LOG.debug(
+ "Couldn't retrieve state in effect at slot {} for attestation at slot {}",
+ attestationEpochStartSlot,
+ attestationData.getSlot());
+ return Optional.empty();
+ }
+ }
}
diff --git a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/attestation/AggregatingAttestationPoolV1.java b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/attestation/AggregatingAttestationPoolV1.java
index d7caca9ed2f..31c0ee02526 100644
--- a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/attestation/AggregatingAttestationPoolV1.java
+++ b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/attestation/AggregatingAttestationPoolV1.java
@@ -44,7 +44,6 @@
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;
import tech.pegasys.teku.storage.client.RecentChainData;
@@ -54,7 +53,7 @@
* cases the returned attestations are aggregated to maximise the number of validators that can be
* included.
*/
-public class AggregatingAttestationPoolV1 implements AggregatingAttestationPool {
+public class AggregatingAttestationPoolV1 extends AggregatingAttestationPool {
private static final Logger LOG = LogManager.getLogger();
static final Comparator ATTESTATION_INCLUSION_COMPARATOR =
@@ -66,8 +65,6 @@ public class AggregatingAttestationPoolV1 implements AggregatingAttestationPool
new HashMap<>();
private final NavigableMap> dataHashBySlot = new TreeMap<>();
- private final Spec spec;
- private final RecentChainData recentChainData;
private final SettableGauge sizeGauge;
private final int maximumAttestationCount;
@@ -78,8 +75,7 @@ public AggregatingAttestationPoolV1(
final RecentChainData recentChainData,
final MetricsSystem metricsSystem,
final int maximumAttestationCount) {
- this.spec = spec;
- this.recentChainData = recentChainData;
+ super(spec, recentChainData);
this.sizeGauge =
SettableGauge.create(
metricsSystem,
@@ -91,9 +87,16 @@ public AggregatingAttestationPoolV1(
@Override
public synchronized void add(final ValidatableAttestation attestation) {
- final Optional committeesSize =
- attestation.getCommitteesSize().or(() -> getCommitteesSize(attestation.getAttestation()));
- getOrCreateAttestationGroup(attestation.getAttestation(), committeesSize)
+ if (!ensureCommitteesSizeInAttestation(attestation)) {
+ LOG.debug(
+ "Attestation at slot {}, block root {} and target root {} has no committee size. Will NOT add this attestation to the pool.",
+ attestation.getData().getSlot(),
+ attestation.getData().getBeaconBlockRoot(),
+ attestation.getData().getTarget().getRoot());
+ return;
+ }
+
+ getOrCreateAttestationGroup(attestation.getData(), attestation.getCommitteesSize())
.ifPresent(
attestationGroup -> {
final boolean added =
@@ -114,30 +117,12 @@ public synchronized void add(final ValidatableAttestation attestation) {
}
}
- private Optional getCommitteesSize(final Attestation attestation) {
- if (attestation.requiresCommitteeBits()) {
- return getCommitteesSizeUsingTheState(attestation.getData());
- }
- return Optional.empty();
- }
-
/**
* @param committeesSize Required for aggregating attestations as per EIP-7549
*/
private Optional getOrCreateAttestationGroup(
- final Attestation attestation, final Optional committeesSize) {
- final AttestationData attestationData = attestation.getData();
- // if an attestation has committee bits, committees size should have been computed. If this is
- // not the case, we should ignore this attestation and not add it to the pool
- if (attestation.requiresCommitteeBits() && committeesSize.isEmpty()) {
- LOG.debug(
- "Committees size couldn't be retrieved for attestation at slot {}, block root {} and target root {}. Will NOT add this attestation to the pool.",
- attestationData.getSlot(),
- attestationData.getBeaconBlockRoot(),
- attestationData.getTarget().getRoot());
- return Optional.empty();
- }
+ final AttestationData attestationData, final Optional committeesSize) {
dataHashBySlot
.computeIfAbsent(attestationData.getSlot(), slot -> new HashSet<>())
.add(attestationData.hashTreeRoot());
@@ -148,58 +133,6 @@ private Optional getOrCreateAttestationGroup(
return Optional.of(attestationGroup);
}
- private Optional getCommitteesSizeUsingTheState(
- final AttestationData attestationData) {
- // we can use the first state of the epoch to get committees for an attestation
- final MiscHelpers miscHelpers = spec.atSlot(attestationData.getSlot()).miscHelpers();
- final Optional maybeEpoch = recentChainData.getCurrentEpoch();
- // the only reason this can happen is we don't have a store yet.
- if (maybeEpoch.isEmpty()) {
- return Optional.empty();
- }
- final UInt64 currentEpoch = maybeEpoch.get();
- final UInt64 attestationEpoch = miscHelpers.computeEpochAtSlot(attestationData.getSlot());
-
- LOG.debug("currentEpoch {}, attestationEpoch {}", currentEpoch, attestationEpoch);
- if (attestationEpoch.equals(currentEpoch)
- || attestationEpoch.equals(currentEpoch.minusMinZero(1))) {
-
- return recentChainData
- .getBestState()
- .flatMap(
- state -> {
- try {
- return Optional.of(
- spec.getBeaconCommitteesSize(
- state.getImmediately(), attestationData.getSlot()));
- } catch (IllegalStateException e) {
- LOG.debug(
- "Couldn't retrieve state for committee calculation of slot {}",
- attestationData.getSlot());
- return Optional.empty();
- }
- });
- }
-
- // attestation is not from the current or previous epoch
- // this is really an edge case because the current or previous epoch is at least 31 slots
- // and the attestation is only valid for 64 slots, so it may be epoch-2 but not beyond.
- final UInt64 attestationEpochStartSlot = miscHelpers.computeStartSlotAtEpoch(attestationEpoch);
- LOG.debug("State at slot {} needed", attestationEpochStartSlot);
- try {
- return recentChainData
- .retrieveStateInEffectAtSlot(attestationEpochStartSlot)
- .getImmediately()
- .map(state -> spec.getBeaconCommitteesSize(state, attestationData.getSlot()));
- } catch (final IllegalStateException e) {
- LOG.debug(
- "Couldn't retrieve state in effect at slot {} for committee calculation of slot {}",
- attestationEpochStartSlot,
- attestationData.getSlot());
- return Optional.empty();
- }
- }
-
@Override
public synchronized void onSlot(final UInt64 slot) {
if (slot.compareTo(ATTESTATION_RETENTION_SLOTS) <= 0) {
@@ -236,7 +169,17 @@ public synchronized void onAttestationsIncludedInBlock(
}
private void onAttestationIncludedInBlock(final UInt64 slot, final Attestation attestation) {
- getOrCreateAttestationGroup(attestation, getCommitteesSize(attestation))
+ final ValidatableAttestation validatableAttestation =
+ ValidatableAttestation.from(spec, attestation);
+ if (!ensureCommitteesSizeInAttestation(validatableAttestation)) {
+ LOG.debug(
+ "Attestation at slot {}, block root {} and target root {} has no committee size. Unable to call onAttestationIncludedInBlock.",
+ attestation.getData().getSlot(),
+ attestation.getData().getBeaconBlockRoot(),
+ attestation.getData().getTarget().getRoot());
+ return;
+ }
+ getOrCreateAttestationGroup(attestation.getData(), validatableAttestation.getCommitteesSize())
.ifPresent(
attestationGroup -> {
final int numRemoved =
diff --git a/ethereum/statetransition/src/test/java/tech/pegasys/teku/statetransition/attestation/AggregatingAttestationPoolTest.java b/ethereum/statetransition/src/test/java/tech/pegasys/teku/statetransition/attestation/AggregatingAttestationPoolTest.java
index 33dde53b9ca..a6d827ae58d 100644
--- a/ethereum/statetransition/src/test/java/tech/pegasys/teku/statetransition/attestation/AggregatingAttestationPoolTest.java
+++ b/ethereum/statetransition/src/test/java/tech/pegasys/teku/statetransition/attestation/AggregatingAttestationPoolTest.java
@@ -17,7 +17,11 @@
import static org.assertj.core.api.Assumptions.assumeThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.argThat;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static tech.pegasys.teku.infrastructure.unsigned.UInt64.ONE;
import static tech.pegasys.teku.infrastructure.unsigned.UInt64.ZERO;
@@ -74,6 +78,7 @@ abstract class AggregatingAttestationPoolTest {
private final AttestationForkChecker forkChecker = mock(AttestationForkChecker.class);
+ private BeaconState state;
private Int2IntMap committeeSizes;
abstract AggregatingAttestationPool instantiatePool(
@@ -98,15 +103,19 @@ public void setUp(final SpecContext specContext) {
dataStructureUtil.randomUInt64(
spec.getGenesisSpec().getConfig().getMaxCommitteesPerSlot()));
- final BeaconState state = dataStructureUtil.randomBeaconState();
+ state = dataStructureUtil.randomBeaconState();
final UpdatableStore mockStore = mock(UpdatableStore.class);
+ when(mockRecentChainData.getCurrentEpoch()).thenReturn(Optional.of(ZERO));
when(mockRecentChainData.getStore()).thenReturn(mockStore);
when(mockRecentChainData.getBestState())
.thenReturn(Optional.of(SafeFuture.completedFuture(state)));
+ when(mockRecentChainData.retrieveStateInEffectAtSlot(any()))
+ .thenReturn(SafeFuture.completedFuture(Optional.of(state)));
when(mockSpec.getBeaconCommitteesSize(any(), any())).thenReturn(committeeSizes);
}
when(forkChecker.areAttestationsFromCorrectFork(any())).thenReturn(true);
+
when(mockSpec.getPreviousEpochAttestationCapacity(any())).thenReturn(Integer.MAX_VALUE);
// Fwd some calls to the real spec
when(mockSpec.computeEpochAtSlot(any()))
@@ -118,6 +127,69 @@ public void setUp(final SpecContext specContext) {
when(mockSpec.getGenesisSchemaDefinitions()).thenReturn(spec.getGenesisSchemaDefinitions());
}
+ @TestTemplate
+ public void add_shouldRetrieveCommitteeSizesFromStateWhenMissing() {
+ final AttestationData attestationData = dataStructureUtil.randomAttestationData(ZERO);
+
+ final Attestation attestation = createAttestation(attestationData, spec, 1);
+
+ final ValidatableAttestation validatableAttestation =
+ ValidatableAttestation.from(mockSpec, attestation);
+
+ assertThat(validatableAttestation.getCommitteesSize()).isEmpty();
+
+ aggregatingPool.add(validatableAttestation);
+
+ final int expectedCalls = specMilestone.isGreaterThanOrEqualTo(ELECTRA) ? 1 : 0;
+
+ verify(mockSpec, times(expectedCalls))
+ .getBeaconCommitteesSize(eq(state), eq(attestationData.getSlot()));
+
+ assertThat(aggregatingPool.getSize()).isEqualTo(1);
+ }
+
+ @TestTemplate
+ public void add_shouldNotRetrieveCommitteeSizesWhenNotNeeded() {
+ final AttestationData attestationData = dataStructureUtil.randomAttestationData(ZERO);
+
+ final Attestation attestation = createAttestation(attestationData, spec, 1);
+
+ final ValidatableAttestation validatableAttestation =
+ ValidatableAttestation.from(mockSpec, attestation, committeeSizes);
+
+ assertThat(validatableAttestation.getCommitteesSize()).isNotEmpty();
+
+ aggregatingPool.add(validatableAttestation);
+
+ verify(mockSpec, never()).getBeaconCommitteesSize(eq(state), eq(attestationData.getSlot()));
+
+ assertThat(aggregatingPool.getSize()).isEqualTo(1);
+ }
+
+ @TestTemplate
+ public void add_shouldNotAddIfFailsRetrievingCommitteesSize() {
+ when(mockRecentChainData.getBestState()).thenReturn(Optional.empty());
+ when(mockRecentChainData.retrieveStateInEffectAtSlot(any()))
+ .thenReturn(SafeFuture.completedFuture(Optional.empty()));
+
+ final AttestationData attestationData = dataStructureUtil.randomAttestationData(ZERO);
+
+ final Attestation attestation = createAttestation(attestationData, spec, 1);
+
+ final ValidatableAttestation validatableAttestation =
+ ValidatableAttestation.from(mockSpec, attestation);
+
+ assertThat(validatableAttestation.getCommitteesSize()).isEmpty();
+
+ aggregatingPool.add(validatableAttestation);
+
+ if (specMilestone.isGreaterThanOrEqualTo(ELECTRA)) {
+ assertThat(aggregatingPool.getSize()).isZero();
+ } else {
+ assertThat(aggregatingPool.getSize()).isEqualTo(1);
+ }
+ }
+
@TestTemplate
public void createAggregateFor_shouldReturnEmptyWhenNoAttestationsMatchGivenData() {
final Optional result =
@@ -179,7 +251,7 @@ void getAttestationsForBlock_shouldNotThrowExceptionWhenShufflingSeedIsUnknown()
// Receive the attestation from a block, prior to receiving it via gossip
aggregatingPool.onAttestationsIncludedInBlock(ONE, List.of(attestation));
// Attestation isn't added because it's already redundant
- aggregatingPool.add(ValidatableAttestation.fromValidator(spec, attestation));
+ aggregatingPool.add(ValidatableAttestation.fromValidator(mockSpec, attestation));
assertThat(aggregatingPool.getSize()).isZero();
// But we now have a MatchingDataAttestationGroup with unknown shuffling seed present
@@ -398,7 +470,6 @@ public void getSize_shouldDecreaseWhenAttestationsRemoved() {
addAttestationFromValidators(attestationData, 1, 2, 3, 4);
final Attestation attestationToRemove = addAttestationFromValidators(attestationData, 2, 5);
- when(mockRecentChainData.getCurrentEpoch()).thenReturn(Optional.of(ZERO));
aggregatingPool.onAttestationsIncludedInBlock(ZERO, List.of(attestationToRemove));
assertThat(aggregatingPool.getSize()).isEqualTo(1);
}
@@ -408,7 +479,7 @@ public void getSize_shouldNotIncrementWhenAttestationAlreadyExists() {
final AttestationData attestationData = dataStructureUtil.randomAttestationData();
final Attestation attestation = addAttestationFromValidators(attestationData, 1, 2, 3, 4);
- aggregatingPool.add(ValidatableAttestation.from(spec, attestation));
+ aggregatingPool.add(ValidatableAttestation.from(mockSpec, attestation));
assertThat(aggregatingPool.getSize()).isEqualTo(1);
}
@@ -421,7 +492,6 @@ public void getSize_shouldDecrementForAllRemovedAttestations() {
final Attestation attestationToRemove =
addAttestationFromValidators(attestationData, 1, 2, 3, 4, 5);
- when(mockRecentChainData.getCurrentEpoch()).thenReturn(Optional.of(ZERO));
aggregatingPool.onAttestationsIncludedInBlock(ZERO, List.of(attestationToRemove));
assertThat(aggregatingPool.getSize()).isEqualTo(0);
}
@@ -450,7 +520,6 @@ public void getSize_shouldDecrementForAllRemovedAttestationsWhileKeepingOthers()
addAttestationFromValidators(attestationData, 1, 2, 3, 4, 5);
assertThat(aggregatingPool.getSize()).isEqualTo(5);
- when(mockRecentChainData.getCurrentEpoch()).thenReturn(Optional.of(ONE));
aggregatingPool.onAttestationsIncludedInBlock(ZERO, List.of(attestationToRemove));
assertThat(aggregatingPool.getSize()).isEqualTo(2);
}
@@ -620,7 +689,6 @@ public void getAttestations_shouldReturnAttestationsForGivenSlotOnly() {
@TestTemplate
void onAttestationsIncludedInBlock_shouldNotAddAttestationsAlreadySeenInABlock() {
final AttestationData attestationData = dataStructureUtil.randomAttestationData(ZERO);
- when(mockRecentChainData.getCurrentEpoch()).thenReturn(Optional.of(ZERO));
// Included in block before we see any attestations with this data
aggregatingPool.onAttestationsIncludedInBlock(
ONE, List.of(createAttestation(attestationData, 1, 2, 3, 4)));
@@ -633,7 +701,6 @@ void onAttestationsIncludedInBlock_shouldNotAddAttestationsAlreadySeenInABlock()
@TestTemplate
void onAttestationsIncludedInBlock_shouldRemoveAttestationsWhenSeenInABlock() {
final AttestationData attestationData = dataStructureUtil.randomAttestationData(ZERO);
- when(mockRecentChainData.getCurrentEpoch()).thenReturn(Optional.of(ZERO));
addAttestationFromValidators(attestationData, 2, 3);
aggregatingPool.onAttestationsIncludedInBlock(
@@ -642,6 +709,40 @@ void onAttestationsIncludedInBlock_shouldRemoveAttestationsWhenSeenInABlock() {
assertThat(aggregatingPool.getSize()).isZero();
}
+ @TestTemplate
+ public void onAttestationsIncludedInBlock_shouldRetrieveCommitteeSizesFromStateWhenMissing() {
+ final AttestationData attestationData = dataStructureUtil.randomAttestationData(ZERO);
+
+ final Attestation attestation = createAttestation(attestationData, spec, 1);
+
+ aggregatingPool.onAttestationsIncludedInBlock(ONE, List.of(attestation));
+
+ final int expectedCalls = specMilestone.isGreaterThanOrEqualTo(ELECTRA) ? 1 : 0;
+
+ verify(mockSpec, times(expectedCalls))
+ .getBeaconCommitteesSize(eq(state), eq(attestationData.getSlot()));
+ }
+
+ @TestTemplate
+ public void onAttestationsIncludedInBlock_shouldNotAddIfFailsRetrievingCommitteesSize() {
+ final AttestationData attestationData = dataStructureUtil.randomAttestationData(ZERO);
+ addAttestationFromValidators(attestationData, 2, 3);
+
+ when(mockRecentChainData.getBestState()).thenReturn(Optional.empty());
+ when(mockRecentChainData.retrieveStateInEffectAtSlot(any()))
+ .thenReturn(SafeFuture.completedFuture(Optional.empty()));
+
+ aggregatingPool.onAttestationsIncludedInBlock(
+ ONE, List.of(createAttestation(attestationData, 1, 2, 3, 4)));
+
+ if (specMilestone.isGreaterThanOrEqualTo(ELECTRA)) {
+ // we can't process onAttestationsIncludedInBlock wihthout committees size
+ assertThat(aggregatingPool.getSize()).isEqualTo(1);
+ } else {
+ assertThat(aggregatingPool.getSize()).isZero();
+ }
+ }
+
@TestTemplate
void onReorg_shouldBeAbleToReadAttestations() {
final AttestationData attestationData = dataStructureUtil.randomAttestationData(ZERO);