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 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 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> viableHeadRootsAndWeightsData = get(checks, checkType); + final Map viableHeadRootsAndWeights = + viableHeadRootsAndWeightsData.stream() + .collect( + Collectors.toMap( + entry -> Bytes32.fromHexString((String) entry.get("root")), + entry -> UInt64.valueOf(entry.get("weight").toString()))); + List chainHeads = + recentChainData + .getForkChoiceStrategy() + .map(ReadOnlyForkChoiceStrategy::getViableChainHeads) + .orElse(Collections.emptyList()); + Set 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 getOptionallyBytes32( final Map yamlData, final String key) { return ForkChoiceTestExecutor.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 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 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 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 getChainHeads() { List getChainHeads(boolean includeNonViableHeads); + List getViableChainHeads(); + Optional getOptimisticallySyncedTransitionBlockRoot(Bytes32 head); List 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 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 attesterSlashings, - final Supplier validatorExitContextSupplier) + final Supplier validatorExitContextSupplier, + final BLSSignatureVerifier signatureVerifier) throws BlockProcessingException { safelyProcess( () -> { @@ -550,7 +555,11 @@ public void processAttesterSlashings( List indicesToSlash = new ArrayList<>(); final Optional 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 attesterSlashings, - Supplier validatorExitContextSupplier) + Supplier 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 validateAttesterSlashing( Fork fork, BeaconState state, AttesterSlashing attesterSlashing, - AttesterSlashingValidator.SlashedIndicesCaptor slashedIndicesCaptor); + AttesterSlashingValidator.SlashedIndicesCaptor slashedIndicesCaptor, + BLSSignatureVerifier signatureVerifier); Optional 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 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 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 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 validateAttestationData( @Override public Optional 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 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 getChainHeads(final boolean includeNonViableChainHead return new ArrayList<>(headsByRoot.values()); } + @Override + public List getViableChainHeads() { + return getChainHeads(false); + } + @Override public Optional 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 getChainHeads(final boolean includeNonViableHeads) { .orElse(Collections.emptyList()); } + @Override + public List 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 performanceRecord) { + onTick(currentTimeMillis, performanceRecord, false); + } + + public void onTick( + final UInt64 currentTimeMillis, + final Optional 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 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 getChainHeads(final boolean includeNonViableHeads) { } } + @Override + public List 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();