Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[EIP-7549] Add committees size map to ValidatableAttestation #8301

Merged
merged 2 commits into from
May 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions ethereum/spec/src/main/java/tech/pegasys/teku/spec/Spec.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@

import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.base.Preconditions;
import it.unimi.dsi.fastutil.ints.Int2IntMap;
import it.unimi.dsi.fastutil.ints.IntList;
import java.io.File;
import java.io.IOException;
Expand Down Expand Up @@ -850,6 +851,10 @@ public IntList getBeaconCommittee(
return atState(state).beaconStateAccessors().getBeaconCommittee(state, slot, index);
}

public Int2IntMap getBeaconCommitteesSize(final BeaconState state, final UInt64 slot) {
return atState(state).beaconStateAccessors().getBeaconCommitteesSize(state, slot);
}

public Optional<BLSPublicKey> getValidatorPubKey(
final BeaconState state, final UInt64 validatorIndex) {
return atState(state).beaconStateAccessors().getValidatorPubKey(state, validatorIndex);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import com.google.common.base.MoreObjects;
import com.google.common.base.Objects;
import com.google.common.base.Suppliers;
import it.unimi.dsi.fastutil.ints.Int2IntMap;
import java.util.Collection;
import java.util.Optional;
import java.util.OptionalInt;
Expand Down Expand Up @@ -45,6 +46,7 @@ public class ValidatableAttestation {

private volatile Optional<IndexedAttestation> indexedAttestation = Optional.empty();
private volatile Optional<Bytes32> committeeShufflingSeed = Optional.empty();
private volatile Optional<Int2IntMap> committeesSize = Optional.empty();

public static ValidatableAttestation from(final Spec spec, final Attestation attestation) {
return new ValidatableAttestation(
Expand Down Expand Up @@ -136,6 +138,10 @@ public Optional<Bytes32> getCommitteeShufflingSeed() {
return committeeShufflingSeed;
}

public Optional<Int2IntMap> getCommitteesSize() {
return committeesSize;
}

public OptionalInt getReceivedSubnetId() {
return receivedSubnetId;
}
Expand All @@ -144,11 +150,19 @@ public void setIndexedAttestation(final IndexedAttestation indexedAttestation) {
this.indexedAttestation = Optional.of(indexedAttestation);
}

public void saveCommitteeShufflingSeed(final BeaconState state) {
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.getCommitteeBits().isPresent()) {
saveCommitteesSize(state);
}
}

private void saveCommitteeShufflingSeed(final BeaconState state) {
if (committeeShufflingSeed.isPresent()) {
return;
}

final Bytes32 committeeShufflingSeed =
spec.getSeed(
state,
Expand All @@ -157,6 +171,15 @@ public void saveCommitteeShufflingSeed(final BeaconState state) {
this.committeeShufflingSeed = Optional.of(committeeShufflingSeed);
}

private void saveCommitteesSize(final BeaconState state) {
if (committeesSize.isPresent()) {
return;
}
final Int2IntMap committeesSize =
spec.getBeaconCommitteesSize(state, attestation.getData().getSlot());
this.committeesSize = Optional.of(committeesSize);
}

public boolean isGossiped() {
return gossiped.get();
}
Expand Down Expand Up @@ -224,6 +247,7 @@ public String toString() {
.add("isValidIndexedAttestation", isValidIndexedAttestation)
.add("indexedAttestation", indexedAttestation)
.add("committeeShufflingSeed", committeeShufflingSeed)
.add("committeesSize", committeesSize)
.add("receivedSubnetId", receivedSubnetId)
.toString();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

package tech.pegasys.teku.spec.datastructures.state.beaconstate.common;

import it.unimi.dsi.fastutil.ints.Int2IntMap;
import it.unimi.dsi.fastutil.ints.IntList;
import java.util.List;
import java.util.Map;
Expand All @@ -34,6 +35,7 @@ public class TransitionCaches {
private static final int MAX_ACTIVE_VALIDATORS_CACHE = 8;
private static final int MAX_BEACON_PROPOSER_INDEX_CACHE = 1;
private static final int MAX_BEACON_COMMITTEE_CACHE = 64 * 64;
private static final int MAX_BEACON_COMMITTEES_SIZE_CACHE = 64;
private static final int MAX_TOTAL_ACTIVE_BALANCE_CACHE = 2;
private static final int MAX_COMMITTEE_SHUFFLE_CACHE = 3;
private static final int MAX_EFFECTIVE_BALANCE_CACHE = 1;
Expand All @@ -48,6 +50,7 @@ public class TransitionCaches {
NoOpCache.getNoOpCache(),
NoOpCache.getNoOpCache(),
NoOpCache.getNoOpCache(),
NoOpCache.getNoOpCache(),
ValidatorIndexCache.NO_OP_INSTANCE,
NoOpCache.getNoOpCache(),
NoOpCache.getNoOpCache(),
Expand All @@ -74,6 +77,7 @@ public static TransitionCaches getNoOp() {
private final Cache<UInt64, IntList> activeValidators;
private final Cache<UInt64, Integer> beaconProposerIndex;
private final Cache<TekuPair<UInt64, UInt64>, IntList> beaconCommittee;
private final Cache<UInt64, Int2IntMap> beaconCommitteesSize;
private final Cache<UInt64, UInt64> attestersTotalBalance;
private final Cache<UInt64, UInt64> totalActiveBalance;
private final Cache<UInt64, BLSPublicKey> validatorsPubKeys;
Expand All @@ -91,6 +95,7 @@ private TransitionCaches() {
activeValidators = LRUCache.create(MAX_ACTIVE_VALIDATORS_CACHE);
beaconProposerIndex = LRUCache.create(MAX_BEACON_PROPOSER_INDEX_CACHE);
beaconCommittee = LRUCache.create(MAX_BEACON_COMMITTEE_CACHE);
beaconCommitteesSize = LRUCache.create(MAX_BEACON_COMMITTEES_SIZE_CACHE);
attestersTotalBalance = LRUCache.create(MAX_BEACON_COMMITTEE_CACHE);
totalActiveBalance = LRUCache.create(MAX_TOTAL_ACTIVE_BALANCE_CACHE);
validatorsPubKeys = LRUCache.create(Integer.MAX_VALUE - 1);
Expand All @@ -106,6 +111,7 @@ private TransitionCaches(
final Cache<UInt64, IntList> activeValidators,
final Cache<UInt64, Integer> beaconProposerIndex,
final Cache<TekuPair<UInt64, UInt64>, IntList> beaconCommittee,
final Cache<UInt64, Int2IntMap> beaconCommitteesSize,
final Cache<UInt64, UInt64> attestersTotalBalance,
final Cache<UInt64, UInt64> totalActiveBalance,
final Cache<UInt64, BLSPublicKey> validatorsPubKeys,
Expand All @@ -118,6 +124,7 @@ private TransitionCaches(
this.activeValidators = activeValidators;
this.beaconProposerIndex = beaconProposerIndex;
this.beaconCommittee = beaconCommittee;
this.beaconCommitteesSize = beaconCommitteesSize;
this.attestersTotalBalance = attestersTotalBalance;
this.totalActiveBalance = totalActiveBalance;
this.validatorsPubKeys = validatorsPubKeys;
Expand Down Expand Up @@ -161,6 +168,11 @@ public Cache<TekuPair<UInt64, UInt64>, IntList> getBeaconCommittee() {
return beaconCommittee;
}

/** (epoch) -> Map(committeeIndex, size of a committee) */
public Cache<UInt64, Int2IntMap> getBeaconCommitteesSize() {
return beaconCommitteesSize;
}

/** (slot) -> (total effective balance of attesters in slot) */
public Cache<UInt64, UInt64> getAttestersTotalBalance() {
return attestersTotalBalance;
Expand Down Expand Up @@ -221,6 +233,7 @@ public TransitionCaches copy() {
activeValidators.copy(),
beaconProposerIndex.copy(),
beaconCommittee.copy(),
beaconCommitteesSize.copy(),
attestersTotalBalance.copy(),
totalActiveBalance.copy(),
validatorsPubKeys,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
import static tech.pegasys.teku.spec.config.SpecConfig.GENESIS_EPOCH;
import static tech.pegasys.teku.spec.logic.common.helpers.MathHelpers.uint64ToBytes;

import it.unimi.dsi.fastutil.ints.Int2IntMap;
import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
import it.unimi.dsi.fastutil.ints.IntList;
import java.util.Collection;
import java.util.Optional;
Expand Down Expand Up @@ -295,7 +297,7 @@ public IntList getBeaconCommittee(
.getBeaconCommittee()
.get(
TekuPair.of(slot, index),
p -> {
__ -> {
UInt64 epoch = miscHelpers.computeEpochAtSlot(slot);
UInt64 committeesPerSlot = getCommitteeCountPerSlot(state, epoch);
int committeeIndex =
Expand All @@ -313,6 +315,25 @@ public IntList getBeaconCommittee(
});
}

public Int2IntMap getBeaconCommitteesSize(final BeaconState state, final UInt64 slot) {
final UInt64 epoch = miscHelpers.computeEpochAtSlot(slot);
return BeaconStateCache.getTransitionCaches(state)
.getBeaconCommitteesSize()
.get(
epoch,
__ -> {
final UInt64 committees = getCommitteeCountPerSlot(state, epoch);
final Int2IntMap committeesSize = new Int2IntOpenHashMap(committees.intValue());
UInt64.range(UInt64.ZERO, committees)
.forEach(
index -> {
final IntList committee = getBeaconCommittee(state, slot, index);
committeesSize.put(index.intValue(), committee.size());
});
return committeesSize;
});
}

public void validateStateForCommitteeQuery(final BeaconState state, final UInt64 slot) {
final UInt64 oldestQueryableSlot =
miscHelpers.getEarliestQueryableSlotForBeaconCommitteeAtTargetSlot(slot);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@ public SafeFuture<AttestationProcessingResult> isValidIndexedAttestationAsync(
.thenApply(
result -> {
if (result.isSuccessful()) {
attestation.saveCommitteeShufflingSeed(state);
attestation.saveCommitteeShufflingSeedAndCommitteesSize(state);
attestation.setValidIndexedAttestation();
}
return result;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import static tech.pegasys.teku.spec.config.SpecConfig.GENESIS_EPOCH;
import static tech.pegasys.teku.spec.config.SpecConfig.GENESIS_SLOT;

import it.unimi.dsi.fastutil.ints.Int2IntMap;
import it.unimi.dsi.fastutil.ints.IntList;
import org.junit.jupiter.api.Test;
import tech.pegasys.teku.infrastructure.unsigned.UInt64;
Expand Down Expand Up @@ -130,6 +131,32 @@ public void getBeaconCommittee_stateIsNewerThanSlot() {
assertDoesNotThrow(() -> beaconStateAccessors.getBeaconCommittee(state, oldSlot, ONE));
}

@Test
public void getBeaconCommitteesSize_stateIsTooOld() {
final UInt64 epoch = ONE;
final UInt64 epochSlot = spec.computeStartSlotAtEpoch(epoch);
final BeaconState state = dataStructureUtil.randomBeaconState(epochSlot);

final UInt64 outOfRangeSlot = spec.computeStartSlotAtEpoch(epoch.plus(2));
assertThatThrownBy(() -> beaconStateAccessors.getBeaconCommitteesSize(state, outOfRangeSlot))
.isInstanceOf(IllegalArgumentException.class)
.hasMessageContaining(
"Committee information must be derived from a state no older than the previous epoch");
}

@Test
public void getBeaconCommitteesSize_hasTheSizeOfTheCommitteeCount() {
final BeaconState state = dataStructureUtil.randomBeaconState();
final UInt64 epoch = spec.computeEpochAtSlot(state.getSlot());

final Int2IntMap committeesSize =
beaconStateAccessors.getBeaconCommitteesSize(state, state.getSlot());

final UInt64 committeeCount = beaconStateAccessors.getCommitteeCountPerSlot(state, epoch);

assertThat(committeesSize).hasSize(committeeCount.intValue());
}

@Test
void calculateCommitteeFraction_full() {
final BeaconState state = dataStructureUtil.randomBeaconState(1024);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ void createsAndValidatesIndexedAttestation(final SpecContext specContext) {
assertThat(validatableAttestation.isValidIndexedAttestation()).isTrue();
assertThat(validatableAttestation.getIndexedAttestation()).isPresent();
assertThat(validatableAttestation.getCommitteeShufflingSeed()).isPresent();
assertThat(validatableAttestation.getCommitteesSize()).isEmpty();

verify(asyncBLSSignatureVerifier).verify(anyList(), any(Bytes.class), any(BLSSignature.class));
}
Expand All @@ -147,6 +148,7 @@ void createsButDoesNotValidateIndexedAttestationBecauseItHasAlreadyBeenValidated
assertThat(validatableAttestation.isValidIndexedAttestation()).isTrue();
assertThat(validatableAttestation.getIndexedAttestation()).isPresent();
assertThat(validatableAttestation.getCommitteeShufflingSeed()).isPresent();
assertThat(validatableAttestation.getCommitteesSize()).isEmpty();

verifyNoInteractions(miscHelpers, asyncBLSSignatureVerifier);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,12 +93,9 @@ private void primeEpochStateCaches(final BeaconState state) {
final UInt64 lookaheadEpoch =
stateEpoch.plus(spec.getSpecConfig(stateEpoch).getMinSeedLookahead());
final UInt64 lookAheadEpochStartSlot = spec.computeStartSlotAtEpoch(lookaheadEpoch);
final UInt64 committeeCount = spec.getCommitteeCountPerSlot(state, lookaheadEpoch);
UInt64.range(lookAheadEpochStartSlot, spec.computeStartSlotAtEpoch(lookaheadEpoch.plus(1)))
.forEach(
slot ->
UInt64.range(UInt64.ZERO, committeeCount)
.forEach(index -> spec.getBeaconCommittee(state, slot, index)));
// Note: calculating the committeesSize also calculates the committees
.forEach(slot -> spec.getBeaconCommitteesSize(state, slot));
}

private void primeJustifiedState(final Checkpoint justifiedCheckpoint) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,7 @@ SafeFuture<InternalValidationResultWithState> singleOrAggregateAttestationChecks

// Save committee shuffling seed since the state is available and
// attestation is valid
validatableAttestation.saveCommitteeShufflingSeed(state);
validatableAttestation.saveCommitteeShufflingSeedAndCommitteesSize(state);
return InternalValidationResultWithState.accept(state);
});
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -142,21 +142,15 @@ void shouldComputeCommitteesForMaxLookAheadEpoch() {
final BeaconState state = getStateForEpoch(epoch);
final UInt64 lookaheadEpoch = epoch.plus(1);
forEachSlotInEpoch(
lookaheadEpoch,
slot ->
UInt64.range(UInt64.ZERO, realSpec.getCommitteeCountPerSlot(state, lookaheadEpoch))
.forEach(
committeeIndex ->
verify(mockSpec).getBeaconCommittee(state, slot, committeeIndex)));
lookaheadEpoch, slot -> verify(mockSpec).getBeaconCommitteesSize(state, slot));

final UInt64 firstSlotAfterLookAheadPeriod =
realSpec.computeStartSlotAtEpoch(lookaheadEpoch.plus(1));
// Should not precalculate beyond the end of the look ahead period
verify(mockSpec, never())
.getBeaconCommittee(
.getBeaconCommitteesSize(
any(),
argThat(argument -> argument.isGreaterThanOrEqualTo(firstSlotAfterLookAheadPeriod)),
any());
argThat(argument -> argument.isGreaterThanOrEqualTo(firstSlotAfterLookAheadPeriod)));
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -502,7 +502,8 @@ private Attestation addAttestationFromValidators(
final AttestationData data, final int... validators) {
final Attestation attestation = createAttestation(data, validators);
ValidatableAttestation validatableAttestation = ValidatableAttestation.from(spec, attestation);
validatableAttestation.saveCommitteeShufflingSeed(dataStructureUtil.randomBeaconState(100, 15));
validatableAttestation.saveCommitteeShufflingSeedAndCommitteesSize(
dataStructureUtil.randomBeaconState(100, 15));
aggregatingPool.add(validatableAttestation);
return attestation;
}
Expand Down