Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,21 @@
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static tech.pegasys.teku.infrastructure.async.SafeFutureAssert.safeJoin;
import static tech.pegasys.teku.infrastructure.time.TimeUtilities.secondsToMillis;
import static tech.pegasys.teku.reference.BlsSetting.IGNORED;
import static tech.pegasys.teku.reference.TestDataUtils.loadYaml;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.google.common.collect.ImmutableMap;
import java.io.IOException;
import java.nio.ByteOrder;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
Expand All @@ -42,6 +48,7 @@
import tech.pegasys.teku.infrastructure.unsigned.UInt64;
import tech.pegasys.teku.kzg.KZG;
import tech.pegasys.teku.kzg.KZGProof;
import tech.pegasys.teku.reference.BlsSetting;
import tech.pegasys.teku.reference.KzgRetriever;
import tech.pegasys.teku.reference.TestDataUtils;
import tech.pegasys.teku.reference.TestExecutor;
Expand All @@ -53,12 +60,15 @@
import tech.pegasys.teku.spec.datastructures.blocks.SignedBlockAndState;
import tech.pegasys.teku.spec.datastructures.blocks.SlotAndBlockRoot;
import tech.pegasys.teku.spec.datastructures.execution.PowBlock;
import tech.pegasys.teku.spec.datastructures.forkchoice.ProtoNodeData;
import tech.pegasys.teku.spec.datastructures.forkchoice.ReadOnlyForkChoiceStrategy;
import tech.pegasys.teku.spec.datastructures.forkchoice.VoteUpdater;
import tech.pegasys.teku.spec.datastructures.operations.Attestation;
import tech.pegasys.teku.spec.datastructures.operations.AttesterSlashing;
import tech.pegasys.teku.spec.datastructures.state.AnchorPoint;
import tech.pegasys.teku.spec.datastructures.state.Checkpoint;
import tech.pegasys.teku.spec.datastructures.state.beaconstate.BeaconState;
import tech.pegasys.teku.spec.datastructures.util.AttestationProcessingResult;
import tech.pegasys.teku.spec.executionlayer.ExecutionLayerChannel;
import tech.pegasys.teku.spec.executionlayer.ExecutionLayerChannelStub;
import tech.pegasys.teku.spec.executionlayer.ExecutionPayloadStatus;
Expand Down Expand Up @@ -92,6 +102,13 @@ public class ForkChoiceTestExecutor implements TestExecutor {
.put("sync/optimistic", new ForkChoiceTestExecutor())
.put("fork_choice/should_override_forkchoice_update", new ForkChoiceTestExecutor())
.put("fork_choice/get_proposer_head", new ForkChoiceTestExecutor("basic_is_parent_root"))
// Fork choice generated test types
.put("fork_choice_generated/block_weight_test", new ForkChoiceTestExecutor())
.put("fork_choice_generated/block_tree_test", new ForkChoiceTestExecutor())
.put("fork_choice_generated/attester_slashing_test", new ForkChoiceTestExecutor())
.put("fork_choice_generated/invalid_message_test", new ForkChoiceTestExecutor())
.put("fork_choice_generated/block_cover_test", new ForkChoiceTestExecutor())
.put("fork_choice_generated/shuffling_test", new ForkChoiceTestExecutor())
.build();

private final List<?> testsToSkip;
Expand All @@ -107,9 +124,10 @@ public void runTest(final TestDefinition testDefinition) throws Throwable {
"Test " + testDefinition.getDisplayName() + " has been ignored");
}

// Note: The fork choice spec says there may be settings in a meta.yaml file but currently no
// tests actually have one, so we currently don't bother trying to load it.
final Spec spec = testDefinition.getSpec();
// Load `meta.yaml` and read the BLS setting
final ForkChoiceMetaData metaData = getMetaData(testDefinition);
final boolean blsDisabled = metaData.getBlsSetting() == IGNORED;
final Spec spec = testDefinition.getSpec(blsDisabled);
final BeaconState anchorState =
TestDataUtils.loadStateFromSsz(testDefinition, "anchor_state" + SSZ_SNAPPY_EXTENSION);
final SignedBeaconBlock anchorBlock = loadAnchorBlock(testDefinition);
Expand Down Expand Up @@ -197,7 +215,7 @@ private void runSteps(
applyChecks(recentChainData, forkChoice, step);

} else if (step.containsKey("tick")) {
forkChoice.onTick(secondsToMillis(getUInt64(step, "tick")), Optional.empty());
forkChoice.onTick(secondsToMillis(getUInt64(step, "tick")), Optional.empty(), true);
final UInt64 currentSlot = recentChainData.getCurrentSlot().orElse(UInt64.ZERO);
LOG.info("Current slot: {} Epoch: {}", currentSlot, spec.computeEpochAtSlot(currentSlot));
} else if (step.containsKey("block")) {
Expand Down Expand Up @@ -279,14 +297,20 @@ private void applyAttestation(
final ForkChoice forkChoice,
final Map<String, Object> step) {
final String attestationName = get(step, "attestation");
final boolean valid = !step.containsKey("valid") || (boolean) step.get("valid");
final Attestation attestation =
TestDataUtils.loadSsz(
testDefinition,
attestationName + SSZ_SNAPPY_EXTENSION,
testDefinition.getSpec().getGenesisSchemaDefinitions().getAttestationSchema());
final Spec spec = testDefinition.getSpec();
assertThat(forkChoice.onAttestation(ValidatableAttestation.from(spec, attestation)))
.isCompleted();
final SafeFuture<AttestationProcessingResult> result =
forkChoice.onAttestation(ValidatableAttestation.from(spec, attestation));
assertThat(result).isCompleted();
AttestationProcessingResult processingResult = safeJoin(result);
assertThat(processingResult.isSuccessful())
.withFailMessage(processingResult.getInvalidReason())
.isEqualTo(valid);
}

private void applyAttesterSlashing(
Expand Down Expand Up @@ -483,6 +507,32 @@ private void applyChecks(
assertThat(expectedValidatorIsConnected).isTrue();
}

case "viable_for_head_roots_and_weights" -> {
final List<Map<String, Object>> viableHeadRootsAndWeightsData = get(checks, checkType);
final Map<Bytes32, UInt64> viableHeadRootsAndWeights =
viableHeadRootsAndWeightsData.stream()
.collect(
Collectors.toMap(
entry -> Bytes32.fromHexString((String) entry.get("root")),
entry -> UInt64.valueOf(entry.get("weight").toString())));
List<ProtoNodeData> chainHeads =
recentChainData
.getForkChoiceStrategy()
.map(ReadOnlyForkChoiceStrategy::getViableChainHeads)
.orElse(Collections.emptyList());
Set<Bytes32> actualViableHeadRoots =
chainHeads.stream().map(ProtoNodeData::getRoot).collect(Collectors.toSet());
assertThat(actualViableHeadRoots)
.describedAs("viable head roots")
.isEqualTo(viableHeadRootsAndWeights.keySet());

for (ProtoNodeData chainHead : chainHeads) {
assertThat(chainHead.getWeight())
.describedAs("viable head weight")
.isEqualTo(viableHeadRootsAndWeights.get(chainHead.getRoot()));
}
}

default -> throw new UnsupportedOperationException(
"Unsupported check type: " + checkType);
}
Expand Down Expand Up @@ -534,4 +584,34 @@ private static Optional<Bytes32> getOptionallyBytes32(
final Map<String, Object> yamlData, final String key) {
return ForkChoiceTestExecutor.<String>getOptionally(yamlData, key).map(Bytes32::fromHexString);
}

private static ForkChoiceMetaData getMetaData(final TestDefinition testDefinition)
throws IOException {
final ForkChoiceMetaData metaData;
final Path metaPath = testDefinition.getTestDirectory().resolve("meta.yaml");
if (metaPath.toFile().exists()) {
metaData = loadYaml(testDefinition, "meta.yaml", ForkChoiceMetaData.class);
} else {
metaData = ForkChoiceMetaData.DEFAULT;
}

return metaData;
}

@JsonIgnoreProperties(ignoreUnknown = true)
private static class ForkChoiceMetaData {
static final ForkChoiceMetaData DEFAULT = new ForkChoiceMetaData(0);

private ForkChoiceMetaData(
@JsonProperty(value = "bls_setting", required = false, defaultValue = "0")
final int blsSetting) {
this.blsSetting = blsSetting;
}

private final int blsSetting;

public BlsSetting getBlsSetting() {
return BlsSetting.forCode(blsSetting);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -52,13 +52,17 @@ public String getFork() {
}

public Spec getSpec() {
return getSpec(Boolean.FALSE);
}

public Spec getSpec(final Boolean blsDisabled) {
if (spec == null) {
createSpec();
createSpec(blsDisabled);
}
return spec;
}

private void createSpec() {
private void createSpec(final Boolean blsDisabled) {
final Eth2Network network =
switch (configName) {
case TestSpecConfig.MAINNET -> Eth2Network.MAINNET;
Expand All @@ -75,7 +79,7 @@ private void createSpec() {
case TestFork.ELECTRA -> SpecMilestone.ELECTRA;
default -> throw new IllegalArgumentException("Unknown fork: " + fork);
};
spec = TestSpecFactory.create(milestone, network);
spec = TestSpecFactory.create(milestone, network, builder -> builder.blsDisabled(blsDisabled));
}

public String getTestType() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,11 @@ public int getReorgParentWeightThreshold() {
return specConfig.getReorgParentWeightThreshold();
}

@Override
public boolean isBlsDisabled() {
return specConfig.isBlsDisabled();
}

@Override
public long getDepositChainId() {
return specConfig.getDepositChainId();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,9 @@ default int getMillisPerSlot() {

int getReorgParentWeightThreshold();

// Handle spec tests with BLS disabled
boolean isBlsDisabled();

// Casters
default Optional<SpecConfigAltair> toVersionAltair() {
return Optional.empty();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,8 @@ public class SpecConfigPhase0 implements SpecConfig {

private final UInt64 maxPerEpochActivationExitChurnLimit;

private final boolean blsDisabled;

public SpecConfigPhase0(
final Map<String, Object> rawConfig,
final UInt64 eth1FollowDistance,
Expand Down Expand Up @@ -191,7 +193,8 @@ public SpecConfigPhase0(
final int reorgMaxEpochsSinceFinalization,
final int reorgHeadWeightThreshold,
final int reorgParentWeightThreshold,
final UInt64 maxPerEpochActivationExitChurnLimit) {
final UInt64 maxPerEpochActivationExitChurnLimit,
final boolean blsDisabled) {
this.rawConfig = rawConfig;
this.eth1FollowDistance = eth1FollowDistance;
this.maxCommitteesPerSlot = maxCommitteesPerSlot;
Expand Down Expand Up @@ -262,6 +265,7 @@ public SpecConfigPhase0(
this.reorgHeadWeightThreshold = reorgHeadWeightThreshold;
this.reorgParentWeightThreshold = reorgParentWeightThreshold;
this.maxPerEpochActivationExitChurnLimit = maxPerEpochActivationExitChurnLimit;
this.blsDisabled = blsDisabled;
}

@Override
Expand Down Expand Up @@ -539,6 +543,11 @@ public int getReorgParentWeightThreshold() {
return reorgParentWeightThreshold;
}

@Override
public boolean isBlsDisabled() {
return blsDisabled;
}

@Override
public int getProposerScoreBoost() {
return proposerScoreBoost;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,9 @@ public class SpecConfigBuilder {
.appendBuilder(new DenebBuilder())
.appendBuilder(new ElectraBuilder());

// Allows to handle spec tests with BLS disabled
private Boolean blsDisabled = Boolean.FALSE;

public SpecConfig build() {
builderChain.addOverridableItemsToRawConfig(
(key, value) -> {
Expand Down Expand Up @@ -216,7 +219,8 @@ public SpecConfig build() {
reorgMaxEpochsSinceFinalization,
reorgHeadWeightThreshold,
reorgParentWeightThreshold,
maxPerEpochActivationExitChurnLimit);
maxPerEpochActivationExitChurnLimit,
blsDisabled);

return builderChain.build(config);
}
Expand Down Expand Up @@ -740,4 +744,9 @@ public SpecConfigBuilder electraBuilder(final Consumer<ElectraBuilder> consumer)
builderChain.withBuilder(ElectraBuilder.class, consumer);
return this;
}

public SpecConfigBuilder blsDisabled(final Boolean blsDisabled) {
this.blsDisabled = blsDisabled;
return this;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ default List<ProtoNodeData> getChainHeads() {

List<ProtoNodeData> getChainHeads(boolean includeNonViableHeads);

List<ProtoNodeData> getViableChainHeads();

Optional<Bytes32> getOptimisticallySyncedTransitionBlockRoot(Bytes32 head);

List<ProtoNodeData> getBlockData();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,8 @@ public BeaconState processAndValidateBlock(
signedBlock,
blockSlotState,
indexedAttestationCache,
signatureVerifier,
// Handle spec test run with BLS disabled
specConfig.isBlsDisabled() ? BLSSignatureVerifier.NO_OP : signatureVerifier,
payloadExecutor);
if (!signatureVerifier.batchVerify()) {
throw new StateTransitionException(
Expand Down Expand Up @@ -341,7 +342,8 @@ protected void processBlock(
processBlockHeader(state, block);
processRandaoNoValidation(state, block.getBody());
processEth1Data(state, block.getBody());
processOperationsNoValidation(state, block.getBody(), indexedAttestationCache);
processOperationsNoValidation(
state, block.getBody(), indexedAttestationCache, signatureVerifier);
}

@Override
Expand Down Expand Up @@ -432,7 +434,8 @@ public long getVoteCount(final BeaconState state, final Eth1Data eth1Data) {
protected void processOperationsNoValidation(
final MutableBeaconState state,
final BeaconBlockBody body,
final IndexedAttestationCache indexedAttestationCache)
final IndexedAttestationCache indexedAttestationCache,
final BLSSignatureVerifier signatureVerifier)
throws BlockProcessingException {
safelyProcess(
() -> {
Expand All @@ -444,7 +447,7 @@ protected void processOperationsNoValidation(
processProposerSlashingsNoValidation(
state, body.getProposerSlashings(), validatorExitContextSupplier);
processAttesterSlashings(
state, body.getAttesterSlashings(), validatorExitContextSupplier);
state, body.getAttesterSlashings(), validatorExitContextSupplier, signatureVerifier);
processAttestationsNoVerification(state, body.getAttestations(), indexedAttestationCache);
processDeposits(state, body.getDeposits());
processVoluntaryExitsNoValidation(
Expand Down Expand Up @@ -533,15 +536,17 @@ public void processAttesterSlashings(
() -> {
final Supplier<ValidatorExitContext> validatorExitContextSupplier =
beaconStateMutators.createValidatorExitContextSupplier(state);
processAttesterSlashings(state, attesterSlashings, validatorExitContextSupplier);
processAttesterSlashings(
state, attesterSlashings, validatorExitContextSupplier, BLSSignatureVerifier.SIMPLE);
});
}

@Override
public void processAttesterSlashings(
final MutableBeaconState state,
final SszList<AttesterSlashing> attesterSlashings,
final Supplier<ValidatorExitContext> validatorExitContextSupplier)
final Supplier<ValidatorExitContext> validatorExitContextSupplier,
final BLSSignatureVerifier signatureVerifier)
throws BlockProcessingException {
safelyProcess(
() -> {
Expand All @@ -550,7 +555,11 @@ public void processAttesterSlashings(
List<UInt64> indicesToSlash = new ArrayList<>();
final Optional<OperationInvalidReason> invalidReason =
operationValidator.validateAttesterSlashing(
state.getFork(), state, attesterSlashing, indicesToSlash::add);
state.getFork(),
state,
attesterSlashing,
indicesToSlash::add,
signatureVerifier);

checkArgument(
invalidReason.isEmpty(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,8 @@ void processAttesterSlashings(
void processAttesterSlashings(
MutableBeaconState state,
SszList<AttesterSlashing> attesterSlashings,
Supplier<ValidatorExitContext> validatorExitContextSupplier)
Supplier<ValidatorExitContext> validatorExitContextSupplier,
BLSSignatureVerifier signatureVerifier)
throws BlockProcessingException;

void processAttestations(
Expand Down
Loading