diff --git a/eth-reference-tests/src/referenceTest/java/tech/pegasys/teku/reference/phase0/forkchoice/ForkChoiceTestExecutor.java b/eth-reference-tests/src/referenceTest/java/tech/pegasys/teku/reference/phase0/forkchoice/ForkChoiceTestExecutor.java
index 9bc378181fe..8ea030d77f7 100644
--- a/eth-reference-tests/src/referenceTest/java/tech/pegasys/teku/reference/phase0/forkchoice/ForkChoiceTestExecutor.java
+++ b/eth-reference-tests/src/referenceTest/java/tech/pegasys/teku/reference/phase0/forkchoice/ForkChoiceTestExecutor.java
@@ -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;
@@ -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;
@@ -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;
@@ -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;
@@ -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);
@@ -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")) {
@@ -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(
@@ -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);
         }
@@ -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);
+    }
+  }
 }
diff --git a/eth-tests/src/main/java/tech/pegasys/teku/ethtests/finder/TestDefinition.java b/eth-tests/src/main/java/tech/pegasys/teku/ethtests/finder/TestDefinition.java
index 951bbe64e55..e96db79ba61 100644
--- a/eth-tests/src/main/java/tech/pegasys/teku/ethtests/finder/TestDefinition.java
+++ b/eth-tests/src/main/java/tech/pegasys/teku/ethtests/finder/TestDefinition.java
@@ -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;
@@ -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() {
diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/DelegatingSpecConfig.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/DelegatingSpecConfig.java
index b5da8abbada..8e6fe490a50 100644
--- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/DelegatingSpecConfig.java
+++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/DelegatingSpecConfig.java
@@ -301,6 +301,11 @@ public int getReorgParentWeightThreshold() {
     return specConfig.getReorgParentWeightThreshold();
   }
 
+  @Override
+  public boolean isBlsDisabled() {
+    return specConfig.isBlsDisabled();
+  }
+
   @Override
   public long getDepositChainId() {
     return specConfig.getDepositChainId();
diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/SpecConfig.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/SpecConfig.java
index 54802ae2332..1e047010ec6 100644
--- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/SpecConfig.java
+++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/SpecConfig.java
@@ -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();
diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/SpecConfigPhase0.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/SpecConfigPhase0.java
index e44266cd681..1a6ce2c08bb 100644
--- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/SpecConfigPhase0.java
+++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/SpecConfigPhase0.java
@@ -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,
@@ -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;
@@ -262,6 +265,7 @@ public SpecConfigPhase0(
     this.reorgHeadWeightThreshold = reorgHeadWeightThreshold;
     this.reorgParentWeightThreshold = reorgParentWeightThreshold;
     this.maxPerEpochActivationExitChurnLimit = maxPerEpochActivationExitChurnLimit;
+    this.blsDisabled = blsDisabled;
   }
 
   @Override
@@ -539,6 +543,11 @@ public int getReorgParentWeightThreshold() {
     return reorgParentWeightThreshold;
   }
 
+  @Override
+  public boolean isBlsDisabled() {
+    return blsDisabled;
+  }
+
   @Override
   public int getProposerScoreBoost() {
     return proposerScoreBoost;
diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/builder/SpecConfigBuilder.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/builder/SpecConfigBuilder.java
index d79d7723be4..808d2995663 100644
--- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/builder/SpecConfigBuilder.java
+++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/builder/SpecConfigBuilder.java
@@ -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) -> {
@@ -216,7 +219,8 @@ public SpecConfig build() {
             reorgMaxEpochsSinceFinalization,
             reorgHeadWeightThreshold,
             reorgParentWeightThreshold,
-            maxPerEpochActivationExitChurnLimit);
+            maxPerEpochActivationExitChurnLimit,
+            blsDisabled);
 
     return builderChain.build(config);
   }
@@ -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;
+  }
 }
diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/forkchoice/ReadOnlyForkChoiceStrategy.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/forkchoice/ReadOnlyForkChoiceStrategy.java
index 3082af1e5fa..b8e2af2eb7b 100644
--- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/forkchoice/ReadOnlyForkChoiceStrategy.java
+++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/forkchoice/ReadOnlyForkChoiceStrategy.java
@@ -41,6 +41,8 @@ default List<ProtoNodeData> getChainHeads() {
 
   List<ProtoNodeData> getChainHeads(boolean includeNonViableHeads);
 
+  List<ProtoNodeData> getViableChainHeads();
+
   Optional<Bytes32> getOptimisticallySyncedTransitionBlockRoot(Bytes32 head);
 
   List<ProtoNodeData> getBlockData();
diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/common/block/AbstractBlockProcessor.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/common/block/AbstractBlockProcessor.java
index 24d20bb7aee..3ce9e3e0661 100644
--- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/common/block/AbstractBlockProcessor.java
+++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/common/block/AbstractBlockProcessor.java
@@ -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(
@@ -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
@@ -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(
         () -> {
@@ -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(
@@ -533,7 +536,8 @@ public void processAttesterSlashings(
         () -> {
           final Supplier<ValidatorExitContext> validatorExitContextSupplier =
               beaconStateMutators.createValidatorExitContextSupplier(state);
-          processAttesterSlashings(state, attesterSlashings, validatorExitContextSupplier);
+          processAttesterSlashings(
+              state, attesterSlashings, validatorExitContextSupplier, BLSSignatureVerifier.SIMPLE);
         });
   }
 
@@ -541,7 +545,8 @@ public void processAttesterSlashings(
   public void processAttesterSlashings(
       final MutableBeaconState state,
       final SszList<AttesterSlashing> attesterSlashings,
-      final Supplier<ValidatorExitContext> validatorExitContextSupplier)
+      final Supplier<ValidatorExitContext> validatorExitContextSupplier,
+      final BLSSignatureVerifier signatureVerifier)
       throws BlockProcessingException {
     safelyProcess(
         () -> {
@@ -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(),
diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/common/block/BlockProcessor.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/common/block/BlockProcessor.java
index 5bb086645bb..e807bb9df18 100644
--- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/common/block/BlockProcessor.java
+++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/common/block/BlockProcessor.java
@@ -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(
diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/common/operations/validation/OperationValidator.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/common/operations/validation/OperationValidator.java
index f28102b5ec8..2a7b6f8dda7 100644
--- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/common/operations/validation/OperationValidator.java
+++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/common/operations/validation/OperationValidator.java
@@ -14,6 +14,7 @@
 package tech.pegasys.teku.spec.logic.common.operations.validation;
 
 import java.util.Optional;
+import tech.pegasys.teku.bls.BLSSignatureVerifier;
 import tech.pegasys.teku.spec.datastructures.operations.AttestationData;
 import tech.pegasys.teku.spec.datastructures.operations.AttesterSlashing;
 import tech.pegasys.teku.spec.datastructures.operations.BlsToExecutionChange;
@@ -34,7 +35,8 @@ Optional<OperationInvalidReason> validateAttesterSlashing(
       Fork fork,
       BeaconState state,
       AttesterSlashing attesterSlashing,
-      AttesterSlashingValidator.SlashedIndicesCaptor slashedIndicesCaptor);
+      AttesterSlashingValidator.SlashedIndicesCaptor slashedIndicesCaptor,
+      BLSSignatureVerifier signatureVerifier);
 
   Optional<OperationInvalidReason> validateProposerSlashing(
       Fork fork, BeaconState state, ProposerSlashing proposerSlashing);
diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/common/util/ForkChoiceUtil.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/common/util/ForkChoiceUtil.java
index 859b5fe6e81..dace1732225 100644
--- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/common/util/ForkChoiceUtil.java
+++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/common/util/ForkChoiceUtil.java
@@ -22,6 +22,7 @@
 import java.util.TreeMap;
 import javax.annotation.CheckReturnValue;
 import org.apache.tuweni.bytes.Bytes32;
+import tech.pegasys.teku.bls.BLSSignatureVerifier;
 import tech.pegasys.teku.infrastructure.unsigned.UInt64;
 import tech.pegasys.teku.spec.Spec;
 import tech.pegasys.teku.spec.config.SpecConfig;
@@ -300,8 +301,12 @@ public AttestationProcessingResult validate(
               if (maybeState.isEmpty()) {
                 return AttestationProcessingResult.UNKNOWN_BLOCK;
               } else {
+                final BLSSignatureVerifier signatureVerifier =
+                    specConfig.isBlsDisabled()
+                        ? BLSSignatureVerifier.NO_OP
+                        : BLSSignatureVerifier.SIMPLE;
                 return attestationUtil.isValidIndexedAttestation(
-                    fork, maybeState.get(), validatableAttestation);
+                    fork, maybeState.get(), validatableAttestation, signatureVerifier);
               }
             })
         .ifSuccessful(() -> checkIfAttestationShouldBeSavedForFuture(store, attestation));
diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/altair/helpers/BeaconStateAccessorsAltair.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/altair/helpers/BeaconStateAccessorsAltair.java
index d8826b49ecc..26f52e0cb85 100644
--- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/altair/helpers/BeaconStateAccessorsAltair.java
+++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/altair/helpers/BeaconStateAccessorsAltair.java
@@ -37,6 +37,7 @@
 import tech.pegasys.teku.spec.datastructures.state.beaconstate.BeaconState;
 import tech.pegasys.teku.spec.datastructures.state.beaconstate.BeaconStateCache;
 import tech.pegasys.teku.spec.datastructures.state.beaconstate.MutableBeaconState;
+import tech.pegasys.teku.spec.datastructures.state.beaconstate.versions.altair.BeaconStateAltair;
 import tech.pegasys.teku.spec.datastructures.type.SszPublicKey;
 import tech.pegasys.teku.spec.logic.common.helpers.BeaconStateAccessors;
 import tech.pegasys.teku.spec.logic.common.helpers.Predicates;
@@ -159,7 +160,17 @@ public SyncCommittee getNextSyncCommittee(final BeaconState state) {
             .intStream()
             .mapToObj(index -> getValidatorPubKey(state, UInt64.valueOf(index)).orElseThrow())
             .toList();
-    final BLSPublicKey aggregatePubkey = BLSPublicKey.aggregate(pubkeys);
+    final BLSPublicKey aggregatePubkey;
+    // Copy the previous aggregatePubkey if BLS is disabled
+    if (altairConfig.isBlsDisabled()) {
+      aggregatePubkey =
+          BeaconStateAltair.required(state)
+              .getNextSyncCommittee()
+              .getAggregatePubkey()
+              .getBLSPublicKey();
+    } else {
+      aggregatePubkey = BLSPublicKey.aggregate(pubkeys);
+    }
 
     return state
         .getBeaconStateSchema()
diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/bellatrix/block/BlockProcessorBellatrix.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/bellatrix/block/BlockProcessorBellatrix.java
index d5b0c3f1a29..d031c1cb4ac 100644
--- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/bellatrix/block/BlockProcessorBellatrix.java
+++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/bellatrix/block/BlockProcessorBellatrix.java
@@ -101,7 +101,8 @@ public void processBlock(
     }
     processRandaoNoValidation(state, block.getBody());
     processEth1Data(state, block.getBody());
-    processOperationsNoValidation(state, block.getBody(), indexedAttestationCache);
+    processOperationsNoValidation(
+        state, block.getBody(), indexedAttestationCache, signatureVerifier);
     processSyncAggregate(
         state, blockBody.getOptionalSyncAggregate().orElseThrow(), signatureVerifier);
   }
diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/capella/block/BlockProcessorCapella.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/capella/block/BlockProcessorCapella.java
index 7a5d88f95de..ab63e6cdf30 100644
--- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/capella/block/BlockProcessorCapella.java
+++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/capella/block/BlockProcessorCapella.java
@@ -130,9 +130,10 @@ protected BlockValidationResult validateBlockPreProcessing(
   protected void processOperationsNoValidation(
       final MutableBeaconState state,
       final BeaconBlockBody body,
-      final IndexedAttestationCache indexedAttestationCache)
+      final IndexedAttestationCache indexedAttestationCache,
+      final BLSSignatureVerifier signatureVerifier)
       throws BlockProcessingException {
-    super.processOperationsNoValidation(state, body, indexedAttestationCache);
+    super.processOperationsNoValidation(state, body, indexedAttestationCache, signatureVerifier);
 
     safelyProcess(
         () ->
diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/electra/block/BlockProcessorElectra.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/electra/block/BlockProcessorElectra.java
index 63677a1bc05..18ea514a3e9 100644
--- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/electra/block/BlockProcessorElectra.java
+++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/electra/block/BlockProcessorElectra.java
@@ -26,6 +26,7 @@
 import org.apache.tuweni.bytes.Bytes32;
 import tech.pegasys.teku.bls.BLSPublicKey;
 import tech.pegasys.teku.bls.BLSSignature;
+import tech.pegasys.teku.bls.BLSSignatureVerifier;
 import tech.pegasys.teku.ethereum.execution.types.Eth1Address;
 import tech.pegasys.teku.infrastructure.bytes.Bytes20;
 import tech.pegasys.teku.infrastructure.ssz.SszList;
@@ -117,9 +118,10 @@ public BlockProcessorElectra(
   protected void processOperationsNoValidation(
       final MutableBeaconState state,
       final BeaconBlockBody body,
-      final IndexedAttestationCache indexedAttestationCache)
+      final IndexedAttestationCache indexedAttestationCache,
+      final BLSSignatureVerifier signatureVerifier)
       throws BlockProcessingException {
-    super.processOperationsNoValidation(state, body, indexedAttestationCache);
+    super.processOperationsNoValidation(state, body, indexedAttestationCache, signatureVerifier);
 
     safelyProcess(
         () -> {
diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/phase0/operations/validation/AttesterSlashingValidator.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/phase0/operations/validation/AttesterSlashingValidator.java
index 82e54986149..65b17e5bc24 100644
--- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/phase0/operations/validation/AttesterSlashingValidator.java
+++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/phase0/operations/validation/AttesterSlashingValidator.java
@@ -18,6 +18,7 @@
 
 import java.util.Optional;
 import java.util.Set;
+import tech.pegasys.teku.bls.BLSSignatureVerifier;
 import tech.pegasys.teku.infrastructure.unsigned.UInt64;
 import tech.pegasys.teku.spec.datastructures.operations.AttesterSlashing;
 import tech.pegasys.teku.spec.datastructures.operations.IndexedAttestation;
@@ -48,14 +49,16 @@ public class AttesterSlashingValidator
   @Override
   public Optional<OperationInvalidReason> validate(
       final Fork fork, final BeaconState state, final AttesterSlashing attesterSlashing) {
-    return validate(fork, state, attesterSlashing, SlashedIndicesCaptor.NOOP);
+    return validate(
+        fork, state, attesterSlashing, SlashedIndicesCaptor.NOOP, BLSSignatureVerifier.SIMPLE);
   }
 
   public Optional<OperationInvalidReason> validate(
       final Fork fork,
       final BeaconState state,
       final AttesterSlashing attesterSlashing,
-      final SlashedIndicesCaptor slashedIndicesCaptor) {
+      final SlashedIndicesCaptor slashedIndicesCaptor,
+      final BLSSignatureVerifier signatureVerifier) {
     IndexedAttestation attestation1 = attesterSlashing.getAttestation1();
     IndexedAttestation attestation2 = attesterSlashing.getAttestation2();
     return firstOf(
@@ -66,11 +69,15 @@ public Optional<OperationInvalidReason> validate(
                 AttesterSlashingInvalidReason.ATTESTATIONS_NOT_SLASHABLE),
         () ->
             check(
-                attestationUtil.isValidIndexedAttestation(fork, state, attestation1).isSuccessful(),
+                attestationUtil
+                    .isValidIndexedAttestation(fork, state, attestation1, signatureVerifier)
+                    .isSuccessful(),
                 AttesterSlashingInvalidReason.ATTESTATION_1_INVALID),
         () ->
             check(
-                attestationUtil.isValidIndexedAttestation(fork, state, attestation2).isSuccessful(),
+                attestationUtil
+                    .isValidIndexedAttestation(fork, state, attestation2, signatureVerifier)
+                    .isSuccessful(),
                 AttesterSlashingInvalidReason.ATTESTATION_2_INVALID),
         () -> {
           boolean slashedAny = false;
diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/phase0/operations/validation/OperationValidatorPhase0.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/phase0/operations/validation/OperationValidatorPhase0.java
index 082515c1aad..43dee845585 100644
--- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/phase0/operations/validation/OperationValidatorPhase0.java
+++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/phase0/operations/validation/OperationValidatorPhase0.java
@@ -14,6 +14,7 @@
 package tech.pegasys.teku.spec.logic.versions.phase0.operations.validation;
 
 import java.util.Optional;
+import tech.pegasys.teku.bls.BLSSignatureVerifier;
 import tech.pegasys.teku.spec.datastructures.operations.AttestationData;
 import tech.pegasys.teku.spec.datastructures.operations.AttesterSlashing;
 import tech.pegasys.teku.spec.datastructures.operations.BlsToExecutionChange;
@@ -59,7 +60,8 @@ public Optional<OperationInvalidReason> validateAttestationData(
   @Override
   public Optional<OperationInvalidReason> validateAttesterSlashing(
       final Fork fork, final BeaconState state, final AttesterSlashing attesterSlashing) {
-    return attesterSlashingValidator.validate(fork, state, attesterSlashing);
+    return attesterSlashingValidator.validate(
+        fork, state, attesterSlashing, SlashedIndicesCaptor.NOOP, BLSSignatureVerifier.SIMPLE);
   }
 
   @Override
@@ -67,8 +69,10 @@ public Optional<OperationInvalidReason> validateAttesterSlashing(
       final Fork fork,
       final BeaconState state,
       final AttesterSlashing attesterSlashing,
-      final SlashedIndicesCaptor slashedIndicesCaptor) {
-    return attesterSlashingValidator.validate(fork, state, attesterSlashing, slashedIndicesCaptor);
+      final SlashedIndicesCaptor slashedIndicesCaptor,
+      final BLSSignatureVerifier signatureVerifier) {
+    return attesterSlashingValidator.validate(
+        fork, state, attesterSlashing, slashedIndicesCaptor, signatureVerifier);
   }
 
   @Override
diff --git a/ethereum/spec/src/testFixtures/java/tech/pegasys/teku/spec/datastructures/forkchoice/TestStoreImpl.java b/ethereum/spec/src/testFixtures/java/tech/pegasys/teku/spec/datastructures/forkchoice/TestStoreImpl.java
index 38f733281ab..90ee46ee748 100644
--- a/ethereum/spec/src/testFixtures/java/tech/pegasys/teku/spec/datastructures/forkchoice/TestStoreImpl.java
+++ b/ethereum/spec/src/testFixtures/java/tech/pegasys/teku/spec/datastructures/forkchoice/TestStoreImpl.java
@@ -438,6 +438,11 @@ public List<ProtoNodeData> getChainHeads(final boolean includeNonViableChainHead
       return new ArrayList<>(headsByRoot.values());
     }
 
+    @Override
+    public List<ProtoNodeData> getViableChainHeads() {
+      return getChainHeads(false);
+    }
+
     @Override
     public Optional<Bytes32> getOptimisticallySyncedTransitionBlockRoot(final Bytes32 head) {
       return Optional.empty();
diff --git a/ethereum/spec/src/testFixtures/java/tech/pegasys/teku/spec/util/RandomChainBuilderForkChoiceStrategy.java b/ethereum/spec/src/testFixtures/java/tech/pegasys/teku/spec/util/RandomChainBuilderForkChoiceStrategy.java
index d66006f2afe..3aaa7b08b55 100644
--- a/ethereum/spec/src/testFixtures/java/tech/pegasys/teku/spec/util/RandomChainBuilderForkChoiceStrategy.java
+++ b/ethereum/spec/src/testFixtures/java/tech/pegasys/teku/spec/util/RandomChainBuilderForkChoiceStrategy.java
@@ -97,6 +97,11 @@ public List<ProtoNodeData> getChainHeads(final boolean includeNonViableHeads) {
         .orElse(Collections.emptyList());
   }
 
+  @Override
+  public List<ProtoNodeData> getViableChainHeads() {
+    return getChainHeads(false);
+  }
+
   private static ProtoNodeData asProtoNodeData(final SignedBlockAndState blockAndState) {
     return new ProtoNodeData(
         blockAndState.getSlot(),
diff --git a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/forkchoice/ForkChoice.java b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/forkchoice/ForkChoice.java
index 601d05a0f36..9786aa25d5d 100644
--- a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/forkchoice/ForkChoice.java
+++ b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/forkchoice/ForkChoice.java
@@ -303,13 +303,24 @@ public void subscribeToOptimisticHeadChangesAndUpdate(final OptimisticHeadSubscr
 
   public void onTick(
       final UInt64 currentTimeMillis, final Optional<TickProcessingPerformance> performanceRecord) {
+    onTick(currentTimeMillis, performanceRecord, false);
+  }
+
+  public void onTick(
+      final UInt64 currentTimeMillis,
+      final Optional<TickProcessingPerformance> performanceRecord,
+      final boolean discardDeferredAttestations) {
     final UpdatableStore store = recentChainData.getStore();
     final UInt64 slotAtStartOfTick = spec.getCurrentSlot(store);
     tickProcessor.onTick(currentTimeMillis).join();
     performanceRecord.ifPresent(TickProcessingPerformance::tickProcessorComplete);
     final UInt64 currentSlot = spec.getCurrentSlot(store);
     if (currentSlot.isGreaterThan(slotAtStartOfTick)) {
-      applyDeferredAttestations(currentSlot).ifExceptionGetsHereRaiseABug();
+      if (discardDeferredAttestations) {
+        discardDeferredAttestations(currentSlot);
+      } else {
+        applyDeferredAttestations(currentSlot).ifExceptionGetsHereRaiseABug();
+      }
     }
     performanceRecord.ifPresent(TickProcessingPerformance::deferredAttestationsApplied);
   }
@@ -834,7 +845,9 @@ private void storeEquivocatingIndices(
         .forEach(
             validatorIndex -> {
               final VoteTracker voteTracker = transaction.getVote(validatorIndex);
-              transaction.putVote(validatorIndex, voteTracker.createNextEquivocating());
+              if (!voteTracker.isEquivocating()) {
+                transaction.putVote(validatorIndex, voteTracker.createNextEquivocating());
+              }
             });
   }
 
@@ -877,6 +890,10 @@ private SafeFuture<Void> applyDeferredAttestations(final UInt64 slot) {
     return applyDeferredAttestations(deferredVoteUpdates);
   }
 
+  private void discardDeferredAttestations(final UInt64 slot) {
+    deferredAttestations.prune(slot);
+  }
+
   private ForkChoiceStrategy getForkChoiceStrategy() {
     forkChoiceExecutor.checkOnEventThread();
     return recentChainData
diff --git a/storage/src/main/java/tech/pegasys/teku/storage/protoarray/ForkChoiceStrategy.java b/storage/src/main/java/tech/pegasys/teku/storage/protoarray/ForkChoiceStrategy.java
index 019f774db91..6a447bc6c59 100644
--- a/storage/src/main/java/tech/pegasys/teku/storage/protoarray/ForkChoiceStrategy.java
+++ b/storage/src/main/java/tech/pegasys/teku/storage/protoarray/ForkChoiceStrategy.java
@@ -182,6 +182,23 @@ public List<ProtoNodeData> getChainHeads(final boolean includeNonViableHeads) {
     }
   }
 
+  @Override
+  public List<ProtoNodeData> getViableChainHeads() {
+    protoArrayLock.readLock().lock();
+    try {
+      return protoArray.getNodes().stream()
+          .filter(
+              protoNode ->
+                  protoNode.getBestChildIndex().isEmpty()
+                      && protoArray.nodeIsViableForHead(protoNode)
+                      && protoArray.isJustifiedRootOrDescendant(protoNode))
+          .map(ProtoNode::getBlockData)
+          .toList();
+    } finally {
+      protoArrayLock.readLock().unlock();
+    }
+  }
+
   public ForkChoiceState getForkChoiceState(
       final UInt64 currentEpoch,
       final Checkpoint justifiedCheckpoint,
diff --git a/storage/src/main/java/tech/pegasys/teku/storage/protoarray/ProtoArray.java b/storage/src/main/java/tech/pegasys/teku/storage/protoarray/ProtoArray.java
index 7e8b8e4dd1c..6d668561c2b 100644
--- a/storage/src/main/java/tech/pegasys/teku/storage/protoarray/ProtoArray.java
+++ b/storage/src/main/java/tech/pegasys/teku/storage/protoarray/ProtoArray.java
@@ -633,6 +633,14 @@ public boolean nodeIsViableForHead(final ProtoNode node) {
         || isFinalizedRootOrDescendant(node);
   }
 
+  boolean isJustifiedRootOrDescendant(final ProtoNode node) {
+    return getProtoNode(justifiedCheckpoint.getRoot())
+        .map(
+            justifiedNode ->
+                hasAncestorAtSlot(node, justifiedNode.getBlockSlot(), justifiedNode.getBlockRoot()))
+        .orElse(false);
+  }
+
   private boolean isFinalizedRootOrDescendant(final ProtoNode node) {
     final UInt64 finalizedEpoch = finalizedCheckpoint.getEpoch();
     final Bytes32 finalizedRoot = finalizedCheckpoint.getRoot();