From 09abdde106fa826619be5600fd7ee99cc5ecd916 Mon Sep 17 00:00:00 2001 From: StefanBratanov Date: Tue, 19 May 2026 16:42:21 +0100 Subject: [PATCH 01/16] Optimize onboarding builders at the fork --- .../forktransition/GloasStateUpgrade.java | 84 ++++++++++--------- .../gloas/helpers/MiscHelpersGloas.java | 2 +- 2 files changed, 46 insertions(+), 40 deletions(-) diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/gloas/forktransition/GloasStateUpgrade.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/gloas/forktransition/GloasStateUpgrade.java index 35ddecaea4c..d6b07d11ddc 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/gloas/forktransition/GloasStateUpgrade.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/gloas/forktransition/GloasStateUpgrade.java @@ -13,7 +13,9 @@ package tech.pegasys.teku.spec.logic.versions.gloas.forktransition; +import java.util.ArrayList; import java.util.Collections; +import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.stream.Collectors; @@ -23,7 +25,6 @@ import org.apache.tuweni.bytes.Bytes32; import tech.pegasys.teku.bls.BLSPublicKey; import tech.pegasys.teku.infrastructure.bytes.Bytes20; -import tech.pegasys.teku.infrastructure.ssz.SszList; import tech.pegasys.teku.infrastructure.ssz.collections.SszBitvector; import tech.pegasys.teku.infrastructure.unsigned.UInt64; import tech.pegasys.teku.spec.config.SpecConfigGloas; @@ -168,50 +169,55 @@ public BeaconStateGloas upgrade(final BeaconState preState) { private void onboardBuildersFromPendingDeposits(final MutableBeaconStateGloas state) { final long startTimeNanos = System.nanoTime(); LOG.debug( - "Starting processing builder deposits from {} pending deposits at timestampNanos={}", + "Starting onboarding builders at fork from {} pending deposits at timestampNanos={}", state.getPendingDeposits().size(), startTimeNanos); final Set validatorPubkeys = state.getValidators().stream().map(Validator::getPublicKey).collect(Collectors.toSet()); - final SszList pendingDeposits = - state.getPendingDeposits().stream() - .filter( - deposit -> { - if (validatorPubkeys.contains(deposit.getPublicKey())) { - return true; - } - final Set builderPubkeys = - state.getBuilders().stream() - .map(Builder::getPublicKey) - .collect(Collectors.toSet()); - if (builderPubkeys.contains(deposit.getPublicKey()) - || predicates.isBuilderWithdrawalCredential( - deposit.getWithdrawalCredentials())) { - beaconStateMutators.applyDepositForBuilder( - state, - deposit.getPublicKey(), - deposit.getWithdrawalCredentials(), - deposit.getAmount(), - deposit.getSignature(), - deposit.getSlot(), - false); - return false; - } - if (miscHelpers.isValidDepositSignature( - deposit.getPublicKey(), - deposit.getWithdrawalCredentials(), - deposit.getAmount(), - deposit.getSignature())) { - validatorPubkeys.add(deposit.getPublicKey()); - return true; - } - return false; - }) - .collect(schemaDefinitions.getPendingDepositsSchema().collector()); - state.setPendingDeposits(pendingDeposits); + final List pendingDeposits = new ArrayList<>(); + // Avoids re-scanning pending deposits and re-verifying signatures for repeated pubkeys + final Set verifiedPendingValidatorPubkeys = new HashSet<>(); + + for (final PendingDeposit deposit : state.getPendingDeposits()) { + final BLSPublicKey pubkey = deposit.getPublicKey(); + // Deposits for existing validators stay in the pending queue + if (validatorPubkeys.contains(pubkey)) { + pendingDeposits.add(deposit); + continue; + } + final Set builderPubkeys = + state.getBuilders().stream().map(Builder::getPublicKey).collect(Collectors.toSet()); + if (!builderPubkeys.contains(pubkey)) { + // Deposits without builder credentials stay in the pending queue + if (!predicates.isBuilderWithdrawalCredential(deposit.getWithdrawalCredentials())) { + pendingDeposits.add(deposit); + continue; + } + // If there is a valid pending deposit for a new validator with this pubkey, keep this + // deposit in the pending queue to be applied to that validator later. + final boolean isPendingValidator = + verifiedPendingValidatorPubkeys.contains(pubkey) + || (miscHelpers.isPendingValidator(pendingDeposits, pubkey) + && verifiedPendingValidatorPubkeys.add(pubkey)); + if (isPendingValidator) { + pendingDeposits.add(deposit); + continue; + } + } + beaconStateMutators.applyDepositForBuilder( + state, + deposit.getPublicKey(), + deposit.getWithdrawalCredentials(), + deposit.getAmount(), + deposit.getSignature(), + deposit.getSlot(), + false); + } + state.setPendingDeposits( + schemaDefinitions.getPendingDepositsSchema().createFromElements(pendingDeposits)); final long finishTimeNanos = System.nanoTime(); LOG.debug( - "Finished processing builder deposits at timestampNanos={}. Pending deposits remaining: {}, builders: {}, elapsedNanos={}", + "Finished onboarding builders at fork at timestampNanos={}. Pending deposits remaining: {}, builders: {}, elapsedNanos={}", finishTimeNanos, pendingDeposits.size(), state.getBuilders().size(), diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/gloas/helpers/MiscHelpersGloas.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/gloas/helpers/MiscHelpersGloas.java index f43b86ed702..f5a161757ce 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/gloas/helpers/MiscHelpersGloas.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/gloas/helpers/MiscHelpersGloas.java @@ -242,7 +242,7 @@ public boolean isActiveBuilder(final BeaconState state, final UInt64 builderInde // Check if a pending deposit with a valid signature is in the queue for the given pubkey. public boolean isPendingValidator( - final SszList pendingDeposits, final BLSPublicKey pubkey) { + final List pendingDeposits, final BLSPublicKey pubkey) { for (final PendingDeposit pendingDeposit : pendingDeposits) { if (!pendingDeposit.getPublicKey().equals(pubkey)) { continue; From 2bc97441949f9a97611e4d748f924b16de41e304 Mon Sep 17 00:00:00 2001 From: StefanBratanov Date: Thu, 21 May 2026 14:02:42 +0100 Subject: [PATCH 02/16] cleaner --- .../gloas/forktransition/GloasStateUpgrade.java | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/gloas/forktransition/GloasStateUpgrade.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/gloas/forktransition/GloasStateUpgrade.java index d6b07d11ddc..4e25e30944f 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/gloas/forktransition/GloasStateUpgrade.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/gloas/forktransition/GloasStateUpgrade.java @@ -195,10 +195,14 @@ private void onboardBuildersFromPendingDeposits(final MutableBeaconStateGloas st } // If there is a valid pending deposit for a new validator with this pubkey, keep this // deposit in the pending queue to be applied to that validator later. - final boolean isPendingValidator = - verifiedPendingValidatorPubkeys.contains(pubkey) - || (miscHelpers.isPendingValidator(pendingDeposits, pubkey) - && verifiedPendingValidatorPubkeys.add(pubkey)); + boolean isPendingValidator; + if (verifiedPendingValidatorPubkeys.contains(pubkey)) { + isPendingValidator = true; + } else { + isPendingValidator = + miscHelpers.isPendingValidator(pendingDeposits, pubkey) + && verifiedPendingValidatorPubkeys.add(pubkey); + } if (isPendingValidator) { pendingDeposits.add(deposit); continue; From 1f154f65c5ee4b80f600c91c95d76231cb04795d Mon Sep 17 00:00:00 2001 From: StefanBratanov Date: Tue, 26 May 2026 10:37:58 +0100 Subject: [PATCH 03/16] fix merge conflicts --- .../gloas/execution/ExecutionRequestsProcessorGloas.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/gloas/execution/ExecutionRequestsProcessorGloas.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/gloas/execution/ExecutionRequestsProcessorGloas.java index ba75a5d5887..31cd61be0ab 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/gloas/execution/ExecutionRequestsProcessorGloas.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/gloas/execution/ExecutionRequestsProcessorGloas.java @@ -158,7 +158,8 @@ public void processDepositRequests( final boolean isValidator = validatorsUtil.getValidatorIndex(state, pubkey).isPresent(); final boolean isPendingValidator = verifiedPendingValidatorPubkeys.contains(pubkey) - || (miscHelpersGloas.isPendingValidator(stateElectra.getPendingDeposits(), pubkey) + || (miscHelpersGloas.isPendingValidator( + stateElectra.getPendingDeposits().asList(), pubkey) && verifiedPendingValidatorPubkeys.add(pubkey)); final boolean isNewBuilderDeposit = !isBuilder From 6fa3dd9a823916b2bbb9fe934847282d4df8a072 Mon Sep 17 00:00:00 2001 From: StefanBratanov Date: Wed, 27 May 2026 15:13:58 +0100 Subject: [PATCH 04/16] some refactor --- .../logic/versions/gloas/SpecLogicGloas.java | 2 +- .../gloas/block/BlockProcessorGloas.java | 61 +++++++++++++++- .../ExecutionRequestsProcessorGloas.java | 72 ------------------- .../forktransition/GloasStateUpgrade.java | 13 ++-- .../gloas/util/ForkChoiceUtilGloas.java | 22 +++--- .../logic/versions/heze/SpecLogicHeze.java | 2 +- 6 files changed, 77 insertions(+), 95 deletions(-) diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/gloas/SpecLogicGloas.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/gloas/SpecLogicGloas.java index 65016dff999..bb2a56cdd88 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/gloas/SpecLogicGloas.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/gloas/SpecLogicGloas.java @@ -231,7 +231,7 @@ public static SpecLogicGloas create( attestationUtil, miscHelpers, withdrawalsHelpers, - executionRequestsProcessor); + blockProcessor); final BlockProposalUtil blockProposalUtil = new BlockProposalUtilFulu(schemaDefinitions, blockProcessor, config.getFuluForkEpoch()); diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/gloas/block/BlockProcessorGloas.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/gloas/block/BlockProcessorGloas.java index 7f02e62d8b9..dee36a3b833 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/gloas/block/BlockProcessorGloas.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/gloas/block/BlockProcessorGloas.java @@ -18,6 +18,8 @@ import java.util.Optional; import java.util.OptionalInt; import java.util.function.Supplier; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import tech.pegasys.teku.bls.BLSSignatureVerifier; import tech.pegasys.teku.infrastructure.ssz.SszList; import tech.pegasys.teku.infrastructure.unsigned.UInt64; @@ -63,6 +65,8 @@ public class BlockProcessorGloas extends BlockProcessorFulu { + private static final Logger LOG = LogManager.getLogger(); + private final PredicatesGloas predicatesGloas; private final SchemaDefinitionsGloas schemaDefinitionsGloas; private final MiscHelpersGloas miscHelpersGloas; @@ -153,9 +157,62 @@ public void processParentExecutionPayload( throw new BlockProcessingException( "The execution requests root in the latest committed bid does not match the parent execution requests in the block"); } + applyParentExecutionPayload(stateGloas, requests, validatorExitContextSupplier); + } + + // apply_parent_execution_payload + public void applyParentExecutionPayload( + final MutableBeaconStateGloas state, + final ExecutionRequests requests, + final Supplier validatorExitContextSupplier) { + final ExecutionPayloadBid parentBid = state.getLatestExecutionPayloadBid(); + final UInt64 parentSlot = parentBid.getSlot(); + final UInt64 parentEpoch = miscHelpers.computeEpochAtSlot(parentSlot); + + // Process execution requests from parent's payload. The execution requests are processed at + // state.slot (child's slot), not the parent's slot. + final long startTimeMillis = System.currentTimeMillis(); + LOG.debug("Starting processing {} deposit requests", requests.getDeposits().size()); + executionRequestsProcessorGloas.processDepositRequests(state, requests.getDeposits()); + LOG.debug( + "Finished processing {} deposit requests. Pending deposits: {}, builders: {}. Took {} ms", + requests.getDeposits().size(), + state.getPendingDeposits().size(), + state.getBuilders().size(), + System.currentTimeMillis() - startTimeMillis); + executionRequestsProcessorGloas.processWithdrawalRequests( + state, requests.getWithdrawals(), validatorExitContextSupplier); + executionRequestsProcessorGloas.processConsolidationRequests( + state, requests.getConsolidations()); + + // Settle the builder payment + if (parentEpoch.equals(beaconStateAccessorsGloas.getCurrentEpoch(state))) { + final UInt64 paymentIndex = + parentSlot.mod(specConfig.getSlotsPerEpoch()).plus(specConfig.getSlotsPerEpoch()); + beaconStateMutatorsGloas.settleBuilderPayment(state, paymentIndex); + } else if (parentEpoch.equals(beaconStateAccessorsGloas.getPreviousEpoch(state))) { + final UInt64 paymentIndex = parentSlot.mod(specConfig.getSlotsPerEpoch()); + beaconStateMutatorsGloas.settleBuilderPayment(state, paymentIndex); + } else if (parentBid.getValue().isGreaterThan(UInt64.ZERO)) { + // Parent is older than the previous epoch, its payment entry has been + // evicted from builder_pending_payments. Append the withdrawal directly. + state + .getBuilderPendingWithdrawals() + .append( + schemaDefinitionsGloas + .getBuilderPendingWithdrawalSchema() + .create( + parentBid.getFeeRecipient(), + parentBid.getValue(), + parentBid.getBuilderIndex())); + } - executionRequestsProcessorGloas.applyParentExecutionPayload( - stateGloas, requests, validatorExitContextSupplier); + // Update parent payload availability and latest block hash + state.setExecutionPayloadAvailability( + state + .getExecutionPayloadAvailability() + .withBit(parentSlot.mod(specConfig.getSlotsPerHistoricalRoot()).intValue())); + state.setLatestBlockHash(parentBid.getBlockHash()); } // process_withdrawals with only state as a parameter diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/gloas/execution/ExecutionRequestsProcessorGloas.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/gloas/execution/ExecutionRequestsProcessorGloas.java index 31cd61be0ab..fe7187dea4f 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/gloas/execution/ExecutionRequestsProcessorGloas.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/gloas/execution/ExecutionRequestsProcessorGloas.java @@ -17,9 +17,6 @@ import java.util.HashSet; import java.util.List; import java.util.Set; -import java.util.function.Supplier; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; import org.apache.tuweni.bytes.Bytes; import tech.pegasys.teku.bls.BLSPublicKey; import tech.pegasys.teku.bls.BLSSignature; @@ -27,18 +24,13 @@ import tech.pegasys.teku.infrastructure.ssz.SszMutableList; import tech.pegasys.teku.infrastructure.ssz.primitive.SszBytes32; import tech.pegasys.teku.infrastructure.ssz.primitive.SszUInt64; -import tech.pegasys.teku.infrastructure.unsigned.UInt64; import tech.pegasys.teku.spec.config.SpecConfigGloas; -import tech.pegasys.teku.spec.datastructures.epbs.versions.gloas.ExecutionPayloadBid; import tech.pegasys.teku.spec.datastructures.execution.versions.electra.DepositRequest; -import tech.pegasys.teku.spec.datastructures.execution.versions.electra.ExecutionRequests; import tech.pegasys.teku.spec.datastructures.state.beaconstate.MutableBeaconState; import tech.pegasys.teku.spec.datastructures.state.beaconstate.versions.electra.MutableBeaconStateElectra; -import tech.pegasys.teku.spec.datastructures.state.beaconstate.versions.gloas.MutableBeaconStateGloas; import tech.pegasys.teku.spec.datastructures.state.versions.electra.PendingDeposit; import tech.pegasys.teku.spec.datastructures.type.SszPublicKey; import tech.pegasys.teku.spec.datastructures.type.SszSignature; -import tech.pegasys.teku.spec.logic.common.helpers.BeaconStateMutators.ValidatorExitContext; import tech.pegasys.teku.spec.logic.common.util.ValidatorsUtil; import tech.pegasys.teku.spec.logic.versions.electra.execution.ExecutionRequestsProcessorElectra; import tech.pegasys.teku.spec.logic.versions.gloas.helpers.BeaconStateAccessorsGloas; @@ -48,10 +40,7 @@ import tech.pegasys.teku.spec.schemas.SchemaDefinitionsGloas; public class ExecutionRequestsProcessorGloas extends ExecutionRequestsProcessorElectra { - private static final Logger LOG = LogManager.getLogger(); - private final SchemaDefinitionsGloas schemaDefinitionsGloas; - private final SpecConfigGloas specConfigGloas; private final MiscHelpersGloas miscHelpersGloas; private final PredicatesGloas predicatesGloas; private final BeaconStateMutatorsGloas beaconStateMutatorsGloas; @@ -73,73 +62,12 @@ public ExecutionRequestsProcessorGloas( validatorsUtil, beaconStateMutators, beaconStateAccessors); - this.schemaDefinitionsGloas = schemaDefinitions; - this.specConfigGloas = specConfig; this.miscHelpersGloas = miscHelpers; this.predicatesGloas = predicates; this.beaconStateMutatorsGloas = beaconStateMutators; this.beaconStateAccessorsGloas = beaconStateAccessors; } - // apply_parent_execution_payload - public void applyParentExecutionPayload( - final MutableBeaconStateGloas state, - final ExecutionRequests requests, - final Supplier validatorExitContextSupplier) { - final ExecutionPayloadBid parentBid = state.getLatestExecutionPayloadBid(); - final UInt64 parentSlot = parentBid.getSlot(); - final UInt64 parentEpoch = miscHelpers.computeEpochAtSlot(parentSlot); - - // Process execution requests from parent's payload. The execution requests are processed at - // state.slot (child's slot), not the parent's slot. - final long startTimeNanos = System.nanoTime(); - LOG.debug( - "Starting processing builder deposits from {} execution request deposits at timestampNanos={}", - requests.getDeposits().size(), - startTimeNanos); - processDepositRequests(state, requests.getDeposits()); - final long finishTimeNanos = System.nanoTime(); - LOG.debug( - "Finished processing builder deposits at timestampNanos={}. Pending deposits: {}, builders: {}, elapsedNanos={}", - finishTimeNanos, - state.getPendingDeposits().size(), - state.getBuilders().size(), - finishTimeNanos - startTimeNanos); - processWithdrawalRequests(state, requests.getWithdrawals(), validatorExitContextSupplier); - processConsolidationRequests(state, requests.getConsolidations()); - - // Settle the builder payment - if (parentEpoch.equals(beaconStateAccessorsGloas.getCurrentEpoch(state))) { - final UInt64 paymentIndex = - parentSlot - .mod(specConfigGloas.getSlotsPerEpoch()) - .plus(specConfigGloas.getSlotsPerEpoch()); - beaconStateMutatorsGloas.settleBuilderPayment(state, paymentIndex); - } else if (parentEpoch.equals(beaconStateAccessorsGloas.getPreviousEpoch(state))) { - final UInt64 paymentIndex = parentSlot.mod(specConfigGloas.getSlotsPerEpoch()); - beaconStateMutatorsGloas.settleBuilderPayment(state, paymentIndex); - } else if (parentBid.getValue().isGreaterThan(UInt64.ZERO)) { - // Parent is older than the previous epoch, its payment entry has been - // evicted from builder_pending_payments. Append the withdrawal directly. - state - .getBuilderPendingWithdrawals() - .append( - schemaDefinitionsGloas - .getBuilderPendingWithdrawalSchema() - .create( - parentBid.getFeeRecipient(), - parentBid.getValue(), - parentBid.getBuilderIndex())); - } - - // Update parent payload availability and latest block hash - state.setExecutionPayloadAvailability( - state - .getExecutionPayloadAvailability() - .withBit(parentSlot.mod(specConfigGloas.getSlotsPerHistoricalRoot()).intValue())); - state.setLatestBlockHash(parentBid.getBlockHash()); - } - @Override public void processDepositRequests( final MutableBeaconState state, final List depositRequests) { diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/gloas/forktransition/GloasStateUpgrade.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/gloas/forktransition/GloasStateUpgrade.java index 4e25e30944f..d68ef0af16e 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/gloas/forktransition/GloasStateUpgrade.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/gloas/forktransition/GloasStateUpgrade.java @@ -167,11 +167,10 @@ public BeaconStateGloas upgrade(final BeaconState preState) { /** Applies any pending deposit for builders, effectively onboarding builders at the fork. */ private void onboardBuildersFromPendingDeposits(final MutableBeaconStateGloas state) { - final long startTimeNanos = System.nanoTime(); + final long startTimeMillis = System.currentTimeMillis(); LOG.debug( - "Starting onboarding builders at fork from {} pending deposits at timestampNanos={}", - state.getPendingDeposits().size(), - startTimeNanos); + "Starting onboarding builders at fork from {} pending deposits", + state.getPendingDeposits().size()); final Set validatorPubkeys = state.getValidators().stream().map(Validator::getPublicKey).collect(Collectors.toSet()); final List pendingDeposits = new ArrayList<>(); @@ -219,12 +218,10 @@ private void onboardBuildersFromPendingDeposits(final MutableBeaconStateGloas st } state.setPendingDeposits( schemaDefinitions.getPendingDepositsSchema().createFromElements(pendingDeposits)); - final long finishTimeNanos = System.nanoTime(); LOG.debug( - "Finished onboarding builders at fork at timestampNanos={}. Pending deposits remaining: {}, builders: {}, elapsedNanos={}", - finishTimeNanos, + "Finished onboarding builders at fork. Pending deposits remaining: {}, builders: {}. Took {} ms", pendingDeposits.size(), state.getBuilders().size(), - finishTimeNanos - startTimeNanos); + System.currentTimeMillis() - startTimeMillis); } } diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/gloas/util/ForkChoiceUtilGloas.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/gloas/util/ForkChoiceUtilGloas.java index e44f58b7d6c..ef0dc37d6e1 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/gloas/util/ForkChoiceUtilGloas.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/gloas/util/ForkChoiceUtilGloas.java @@ -45,7 +45,7 @@ import tech.pegasys.teku.spec.logic.common.statetransition.availability.AvailabilityCheckerFactory; import tech.pegasys.teku.spec.logic.common.util.ForkChoiceUtil; import tech.pegasys.teku.spec.logic.versions.fulu.util.ForkChoiceUtilFulu; -import tech.pegasys.teku.spec.logic.versions.gloas.execution.ExecutionRequestsProcessorGloas; +import tech.pegasys.teku.spec.logic.versions.gloas.block.BlockProcessorGloas; import tech.pegasys.teku.spec.logic.versions.gloas.helpers.BeaconStateAccessorsGloas; import tech.pegasys.teku.spec.logic.versions.gloas.helpers.BeaconStateMutatorsGloas; import tech.pegasys.teku.spec.logic.versions.gloas.helpers.MiscHelpersGloas; @@ -55,7 +55,7 @@ public class ForkChoiceUtilGloas extends ForkChoiceUtilFulu { private final BeaconStateMutatorsGloas beaconStateMutatorsGloas; private final WithdrawalsHelpersGloas withdrawalsHelpers; - private final ExecutionRequestsProcessorGloas executionRequestsProcessor; + private final BlockProcessorGloas blockProcessor; public ForkChoiceUtilGloas( final SpecConfigGloas specConfig, @@ -65,11 +65,11 @@ public ForkChoiceUtilGloas( final AttestationUtilGloas attestationUtil, final MiscHelpersGloas miscHelpers, final WithdrawalsHelpersGloas withdrawalsHelpers, - final ExecutionRequestsProcessorGloas executionRequestsProcessor) { + final BlockProcessorGloas blockProcessor) { super(specConfig, beaconStateAccessors, epochProcessor, attestationUtil, miscHelpers); this.beaconStateMutatorsGloas = beaconStateMutators; this.withdrawalsHelpers = withdrawalsHelpers; - this.executionRequestsProcessor = executionRequestsProcessor; + this.blockProcessor = blockProcessor; } @Override @@ -94,14 +94,14 @@ public SszList getPayloadAttributeWithdrawals( final BeaconState state, final ExecutionRequests parentExecutionRequests) { final BeaconState effectiveState = state.updated( - stateMutable -> { - final MutableBeaconStateGloas stateGloas = - MutableBeaconStateGloas.required(stateMutable); - executionRequestsProcessor.applyParentExecutionPayload( - stateGloas, + mutableState -> { + final MutableBeaconStateGloas mutableStateGloas = + MutableBeaconStateGloas.required(mutableState); + blockProcessor.applyParentExecutionPayload( + mutableStateGloas, parentExecutionRequests, - beaconStateMutatorsGloas.createValidatorExitContextSupplier(stateGloas)); - withdrawalsHelpers.processWithdrawals(stateGloas); + beaconStateMutatorsGloas.createValidatorExitContextSupplier(mutableStateGloas)); + withdrawalsHelpers.processWithdrawals(mutableStateGloas); }); return BeaconStateGloas.required(effectiveState).getPayloadExpectedWithdrawals(); } diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/heze/SpecLogicHeze.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/heze/SpecLogicHeze.java index 791b3667ec5..e1ce2035e36 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/heze/SpecLogicHeze.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/heze/SpecLogicHeze.java @@ -229,7 +229,7 @@ public static SpecLogicHeze create( attestationUtil, miscHelpers, withdrawalsHelpers, - executionRequestsProcessor); + blockProcessor); final BlockProposalUtil blockProposalUtil = new BlockProposalUtilFulu(schemaDefinitions, blockProcessor, config.getFuluForkEpoch()); From 4acffc683d5d25945dc6c2994f7973aff14e18ec Mon Sep 17 00:00:00 2001 From: StefanBratanov Date: Wed, 27 May 2026 15:17:27 +0100 Subject: [PATCH 05/16] super nit --- .../spec/logic/versions/gloas/block/BlockProcessorGloas.java | 2 +- .../logic/versions/gloas/forktransition/GloasStateUpgrade.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/gloas/block/BlockProcessorGloas.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/gloas/block/BlockProcessorGloas.java index dee36a3b833..a940e6a32d0 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/gloas/block/BlockProcessorGloas.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/gloas/block/BlockProcessorGloas.java @@ -175,7 +175,7 @@ public void applyParentExecutionPayload( LOG.debug("Starting processing {} deposit requests", requests.getDeposits().size()); executionRequestsProcessorGloas.processDepositRequests(state, requests.getDeposits()); LOG.debug( - "Finished processing {} deposit requests. Pending deposits: {}, builders: {}. Took {} ms", + "Finished processing {} deposit requests. Pending deposits: {}, builders: {}. Took {} ms.", requests.getDeposits().size(), state.getPendingDeposits().size(), state.getBuilders().size(), diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/gloas/forktransition/GloasStateUpgrade.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/gloas/forktransition/GloasStateUpgrade.java index d68ef0af16e..0d6ca03abd0 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/gloas/forktransition/GloasStateUpgrade.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/gloas/forktransition/GloasStateUpgrade.java @@ -219,7 +219,7 @@ private void onboardBuildersFromPendingDeposits(final MutableBeaconStateGloas st state.setPendingDeposits( schemaDefinitions.getPendingDepositsSchema().createFromElements(pendingDeposits)); LOG.debug( - "Finished onboarding builders at fork. Pending deposits remaining: {}, builders: {}. Took {} ms", + "Finished onboarding builders at fork. Pending deposits remaining: {}, builders: {}. Took {} ms.", pendingDeposits.size(), state.getBuilders().size(), System.currentTimeMillis() - startTimeMillis); From a69eeb4c4457e862b08a7a61836047a3403dc58b Mon Sep 17 00:00:00 2001 From: StefanBratanov Date: Wed, 27 May 2026 23:40:48 +0100 Subject: [PATCH 06/16] setting gas limit during fork upgrade --- .../logic/versions/gloas/forktransition/GloasStateUpgrade.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/gloas/forktransition/GloasStateUpgrade.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/gloas/forktransition/GloasStateUpgrade.java index 0d6ca03abd0..7c202874b35 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/gloas/forktransition/GloasStateUpgrade.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/gloas/forktransition/GloasStateUpgrade.java @@ -144,7 +144,7 @@ public BeaconStateGloas upgrade(final BeaconState preState) { latestBlockHash, Bytes32.ZERO, Bytes20.ZERO, - UInt64.ZERO, + preStateFulu.getLatestExecutionPayloadHeaderRequired().getGasLimit(), UInt64.ZERO, UInt64.ZERO, UInt64.ZERO, From a3b1e20d689b7ac969e8b2797decaa6b766dfccf Mon Sep 17 00:00:00 2001 From: StefanBratanov Date: Thu, 28 May 2026 10:41:29 +0100 Subject: [PATCH 07/16] more optimizations --- .../beaconstate/common/BuilderIndexCache.java | 102 ++++++++++++++++ .../beaconstate/common/TransitionCaches.java | 19 ++- .../logic/versions/gloas/SpecLogicGloas.java | 3 +- .../ExecutionRequestsProcessorGloas.java | 15 ++- .../forktransition/GloasStateUpgrade.java | 17 ++- .../helpers/BeaconStateAccessorsGloas.java | 20 +--- .../common/BuilderIndexCacheTest.java | 113 ++++++++++++++++++ 7 files changed, 241 insertions(+), 48 deletions(-) create mode 100644 ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/beaconstate/common/BuilderIndexCache.java create mode 100644 ethereum/spec/src/test/java/tech/pegasys/teku/spec/datastructures/state/beaconstate/common/BuilderIndexCacheTest.java diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/beaconstate/common/BuilderIndexCache.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/beaconstate/common/BuilderIndexCache.java new file mode 100644 index 00000000000..dc8ff27c4de --- /dev/null +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/beaconstate/common/BuilderIndexCache.java @@ -0,0 +1,102 @@ +/* + * Copyright Consensys Software Inc., 2026 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package tech.pegasys.teku.spec.datastructures.state.beaconstate.common; + +import com.google.common.annotations.VisibleForTesting; +import java.util.Optional; +import java.util.concurrent.atomic.AtomicInteger; +import tech.pegasys.teku.bls.BLSPublicKey; +import tech.pegasys.teku.infrastructure.collections.cache.Cache; +import tech.pegasys.teku.infrastructure.collections.cache.LRUCache; +import tech.pegasys.teku.infrastructure.collections.cache.NoOpCache; +import tech.pegasys.teku.infrastructure.ssz.SszList; +import tech.pegasys.teku.spec.datastructures.state.beaconstate.BeaconState; +import tech.pegasys.teku.spec.datastructures.state.beaconstate.versions.gloas.BeaconStateGloas; +import tech.pegasys.teku.spec.datastructures.state.versions.gloas.Builder; + +public class BuilderIndexCache { + private final Cache builderIndices; + private final AtomicInteger lastCachedIndex; + + private static final int INDEX_NONE = -1; + public static final BuilderIndexCache NO_OP_INSTANCE = + new BuilderIndexCache(NoOpCache.getNoOpCache(), INDEX_NONE); + + @VisibleForTesting + BuilderIndexCache(final Cache builderIndices, final int lastCachedIndex) { + this.builderIndices = builderIndices; + this.lastCachedIndex = new AtomicInteger(lastCachedIndex); + } + + public BuilderIndexCache() { + this.builderIndices = LRUCache.create(Integer.MAX_VALUE - 1); + this.lastCachedIndex = new AtomicInteger(INDEX_NONE); + } + + public Optional getBuilderIndex(final BeaconState state, final BLSPublicKey publicKey) { + final SszList builders = BeaconStateGloas.required(state).getBuilders(); + final Optional builderIndex = builderIndices.getCached(publicKey); + if (builderIndex.isPresent()) { + return builderIndex.filter(index -> index < builders.size()); + } + + return findIndexFromState(builders, publicKey); + } + + public void invalidateWithNewValue(final BLSPublicKey pubKey, final int updatedIndex) { + builderIndices.invalidateWithNewValue(pubKey, updatedIndex); + } + + public void invalidate(final BLSPublicKey pubKey) { + builderIndices.invalidate(pubKey); + } + + @VisibleForTesting + int getLastCachedIndex() { + return lastCachedIndex.get(); + } + + @VisibleForTesting + int getCacheSize() { + return builderIndices.size(); + } + + private void updateLastIndex(final int i) { + lastCachedIndex.updateAndGet(curr -> Math.max(curr, i)); + } + + @VisibleForTesting + Cache getBuilderIndices() { + return builderIndices; + } + + private Optional findIndexFromState( + final SszList builders, final BLSPublicKey publicKey) { + final int initialCacheSize = getCacheSize(); + for (int i = Math.max(lastCachedIndex.get() + 1, 0); i < builders.size(); i++) { + final BLSPublicKey pubKey = builders.get(i).getPublicKey(); + builderIndices.invalidateWithNewValue(pubKey, i); + if (pubKey.equals(publicKey)) { + if (initialCacheSize < getCacheSize()) { + updateLastIndex(i); + } + return Optional.of(i); + } + } + if (initialCacheSize < getCacheSize()) { + updateLastIndex(getCacheSize() - 1); + } + return Optional.empty(); + } +} diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/beaconstate/common/TransitionCaches.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/beaconstate/common/TransitionCaches.java index f755632230c..426a6e09a1b 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/beaconstate/common/TransitionCaches.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/beaconstate/common/TransitionCaches.java @@ -58,7 +58,7 @@ public class TransitionCaches { NoOpCache.getNoOpCache(), ProgressiveTotalBalancesUpdates.NOOP, NoOpCache.getNoOpCache(), - NoOpCache.getNoOpCache()) { + BuilderIndexCache.NO_OP_INSTANCE) { @Override public TransitionCaches copy() { @@ -88,7 +88,7 @@ public static TransitionCaches getNoOp() { private final Cache> effectiveBalances; private final Cache baseRewardPerIncrement; private final Cache buildersPubKeys; - private final Cache builderIndexCache; + private final BuilderIndexCache builderIndexCache; private final Cache> syncCommitteeCache; @@ -110,7 +110,7 @@ private TransitionCaches() { baseRewardPerIncrement = LRUCache.create(MAX_BASE_REWARD_PER_INCREMENT_CACHE); progressiveTotalBalances = ProgressiveTotalBalancesUpdates.NOOP; buildersPubKeys = LRUCache.create(Integer.MAX_VALUE - 1); - builderIndexCache = LRUCache.create(Integer.MAX_VALUE - 1); + builderIndexCache = new BuilderIndexCache(); } private TransitionCaches( @@ -128,7 +128,7 @@ private TransitionCaches( final Cache baseRewardPerIncrement, final ProgressiveTotalBalancesUpdates progressiveTotalBalances, final Cache buildersPubKeys, - final Cache builderIndexCache) { + final BuilderIndexCache builderIndexCache) { this.activeValidators = activeValidators; this.beaconProposerIndex = beaconProposerIndex; this.beaconCommittee = beaconCommittee; @@ -239,13 +239,8 @@ public Cache getBuildersPubKeys() { return buildersPubKeys; } - /** - * (builder pub key) -> (builder index) cache - * - *

More complicated cache such as the {@link ValidatorIndexCache} is not required since the - * builders in the state are expected to be a tiny number initially - */ - public Cache getBuilderIndexCache() { + /** (builder pub key) -> (builder index) cache */ + public BuilderIndexCache getBuilderIndexCache() { return builderIndexCache; } @@ -269,6 +264,6 @@ public TransitionCaches copy() { baseRewardPerIncrement.copy(), progressiveTotalBalances.copy(), buildersPubKeys.copy(), - builderIndexCache.copy()); + builderIndexCache); } } diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/gloas/SpecLogicGloas.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/gloas/SpecLogicGloas.java index bb2a56cdd88..46861e79a42 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/gloas/SpecLogicGloas.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/gloas/SpecLogicGloas.java @@ -248,7 +248,8 @@ public static SpecLogicGloas create( beaconStateAccessors, predicates, beaconStateMutators, - miscHelpers); + miscHelpers, + validatorsUtil); // Data column sidecar util final DataColumnSidecarUtil dataColumnSidecarUtil = new DataColumnSidecarUtilGloas(miscHelpers); diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/gloas/execution/ExecutionRequestsProcessorGloas.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/gloas/execution/ExecutionRequestsProcessorGloas.java index fe7187dea4f..6e02091fbfa 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/gloas/execution/ExecutionRequestsProcessorGloas.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/gloas/execution/ExecutionRequestsProcessorGloas.java @@ -83,18 +83,17 @@ public void processDepositRequests( // this pubkey, apply the deposit to their balance final boolean isBuilder = beaconStateAccessorsGloas.getBuilderIndex(state, pubkey).isPresent(); - final boolean isValidator = validatorsUtil.getValidatorIndex(state, pubkey).isPresent(); - final boolean isPendingValidator = - verifiedPendingValidatorPubkeys.contains(pubkey) - || (miscHelpersGloas.isPendingValidator( - stateElectra.getPendingDeposits().asList(), pubkey) - && verifiedPendingValidatorPubkeys.add(pubkey)); final boolean isNewBuilderDeposit = !isBuilder && predicatesGloas.isBuilderWithdrawalCredential( depositRequest.getWithdrawalCredentials()) - && !isValidator - && !isPendingValidator; + // not is_validator + && validatorsUtil.getValidatorIndex(state, pubkey).isEmpty() + // not is_pending_validator + && !(verifiedPendingValidatorPubkeys.contains(pubkey) + || (miscHelpersGloas.isPendingValidator( + stateElectra.getPendingDeposits().asList(), pubkey) + && verifiedPendingValidatorPubkeys.add(pubkey))); if (isNewBuilderDeposit) { // new builder deposits will be processed at the end so we can batch the signature diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/gloas/forktransition/GloasStateUpgrade.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/gloas/forktransition/GloasStateUpgrade.java index 7c202874b35..e791889d25a 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/gloas/forktransition/GloasStateUpgrade.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/gloas/forktransition/GloasStateUpgrade.java @@ -18,7 +18,6 @@ import java.util.HashSet; import java.util.List; import java.util.Set; -import java.util.stream.Collectors; import java.util.stream.IntStream; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -29,7 +28,6 @@ import tech.pegasys.teku.infrastructure.unsigned.UInt64; import tech.pegasys.teku.spec.config.SpecConfigGloas; import tech.pegasys.teku.spec.datastructures.state.Fork; -import tech.pegasys.teku.spec.datastructures.state.Validator; import tech.pegasys.teku.spec.datastructures.state.beaconstate.BeaconState; import tech.pegasys.teku.spec.datastructures.state.beaconstate.common.BeaconStateFields; import tech.pegasys.teku.spec.datastructures.state.beaconstate.versions.fulu.BeaconStateFulu; @@ -37,9 +35,9 @@ import tech.pegasys.teku.spec.datastructures.state.beaconstate.versions.gloas.BeaconStateSchemaGloas; import tech.pegasys.teku.spec.datastructures.state.beaconstate.versions.gloas.MutableBeaconStateGloas; import tech.pegasys.teku.spec.datastructures.state.versions.electra.PendingDeposit; -import tech.pegasys.teku.spec.datastructures.state.versions.gloas.Builder; import tech.pegasys.teku.spec.datastructures.state.versions.gloas.BuilderPendingPayment; import tech.pegasys.teku.spec.logic.common.forktransition.StateUpgrade; +import tech.pegasys.teku.spec.logic.common.util.ValidatorsUtil; import tech.pegasys.teku.spec.logic.versions.gloas.helpers.BeaconStateAccessorsGloas; import tech.pegasys.teku.spec.logic.versions.gloas.helpers.BeaconStateMutatorsGloas; import tech.pegasys.teku.spec.logic.versions.gloas.helpers.MiscHelpersGloas; @@ -56,6 +54,7 @@ public class GloasStateUpgrade implements StateUpgrade { private final PredicatesGloas predicates; private final BeaconStateMutatorsGloas beaconStateMutators; private final MiscHelpersGloas miscHelpers; + private final ValidatorsUtil validatorsUtil; public GloasStateUpgrade( final SpecConfigGloas specConfig, @@ -63,13 +62,15 @@ public GloasStateUpgrade( final BeaconStateAccessorsGloas beaconStateAccessors, final PredicatesGloas predicates, final BeaconStateMutatorsGloas beaconStateMutators, - final MiscHelpersGloas miscHelpers) { + final MiscHelpersGloas miscHelpers, + final ValidatorsUtil validatorsUtil) { this.specConfig = specConfig; this.schemaDefinitions = schemaDefinitions; this.beaconStateAccessors = beaconStateAccessors; this.predicates = predicates; this.beaconStateMutators = beaconStateMutators; this.miscHelpers = miscHelpers; + this.validatorsUtil = validatorsUtil; } @Override @@ -171,8 +172,6 @@ private void onboardBuildersFromPendingDeposits(final MutableBeaconStateGloas st LOG.debug( "Starting onboarding builders at fork from {} pending deposits", state.getPendingDeposits().size()); - final Set validatorPubkeys = - state.getValidators().stream().map(Validator::getPublicKey).collect(Collectors.toSet()); final List pendingDeposits = new ArrayList<>(); // Avoids re-scanning pending deposits and re-verifying signatures for repeated pubkeys final Set verifiedPendingValidatorPubkeys = new HashSet<>(); @@ -180,13 +179,11 @@ private void onboardBuildersFromPendingDeposits(final MutableBeaconStateGloas st for (final PendingDeposit deposit : state.getPendingDeposits()) { final BLSPublicKey pubkey = deposit.getPublicKey(); // Deposits for existing validators stay in the pending queue - if (validatorPubkeys.contains(pubkey)) { + if (validatorsUtil.getValidatorIndex(state, pubkey).isPresent()) { pendingDeposits.add(deposit); continue; } - final Set builderPubkeys = - state.getBuilders().stream().map(Builder::getPublicKey).collect(Collectors.toSet()); - if (!builderPubkeys.contains(pubkey)) { + if (beaconStateAccessors.getBuilderIndex(state, pubkey).isEmpty()) { // Deposits without builder credentials stay in the pending queue if (!predicates.isBuilderWithdrawalCredential(deposit.getWithdrawalCredentials())) { pendingDeposits.add(deposit); diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/gloas/helpers/BeaconStateAccessorsGloas.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/gloas/helpers/BeaconStateAccessorsGloas.java index 2cceef69ed9..a9ce01ccbeb 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/gloas/helpers/BeaconStateAccessorsGloas.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/gloas/helpers/BeaconStateAccessorsGloas.java @@ -26,7 +26,6 @@ import org.apache.tuweni.bytes.Bytes; import org.apache.tuweni.bytes.Bytes32; import tech.pegasys.teku.bls.BLSPublicKey; -import tech.pegasys.teku.infrastructure.collections.cache.Cache; import tech.pegasys.teku.infrastructure.crypto.Hash; import tech.pegasys.teku.infrastructure.ssz.SszList; import tech.pegasys.teku.infrastructure.ssz.SszVector; @@ -384,21 +383,8 @@ public Optional getBuilderPubKey( @Override public Optional getBuilderIndex(final BeaconState state, final BLSPublicKey publicKey) { - final SszList builders = BeaconStateGloas.required(state).getBuilders(); - final Cache builderIndexCache = - BeaconStateCache.getTransitionCaches(state).getBuilderIndexCache(); - return builderIndexCache - .getCached(publicKey) - .or( - () -> { - for (int i = 0; i < builders.size(); i++) { - final BLSPublicKey builderPubKey = builders.get(i).getPublicKey(); - if (builderPubKey.equals(publicKey)) { - builderIndexCache.invalidateWithNewValue(builderPubKey, i); - return Optional.of(i); - } - } - return Optional.empty(); - }); + return BeaconStateCache.getTransitionCaches(state) + .getBuilderIndexCache() + .getBuilderIndex(state, publicKey); } } diff --git a/ethereum/spec/src/test/java/tech/pegasys/teku/spec/datastructures/state/beaconstate/common/BuilderIndexCacheTest.java b/ethereum/spec/src/test/java/tech/pegasys/teku/spec/datastructures/state/beaconstate/common/BuilderIndexCacheTest.java new file mode 100644 index 00000000000..cf3f801b84a --- /dev/null +++ b/ethereum/spec/src/test/java/tech/pegasys/teku/spec/datastructures/state/beaconstate/common/BuilderIndexCacheTest.java @@ -0,0 +1,113 @@ +/* + * Copyright Consensys Software Inc., 2026 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package tech.pegasys.teku.spec.datastructures.state.beaconstate.common; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.Optional; +import org.junit.jupiter.api.Test; +import tech.pegasys.teku.bls.BLSPublicKey; +import tech.pegasys.teku.infrastructure.collections.cache.Cache; +import tech.pegasys.teku.spec.TestSpecFactory; +import tech.pegasys.teku.spec.datastructures.state.beaconstate.versions.gloas.BeaconStateGloas; +import tech.pegasys.teku.spec.util.DataStructureUtil; + +public class BuilderIndexCacheTest { + final DataStructureUtil dataStructureUtil = + new DataStructureUtil(TestSpecFactory.createMinimalGloas()); + final BeaconStateGloas state = BeaconStateGloas.required(dataStructureUtil.randomBeaconState()); + final BLSPublicKey missingPublicKey = dataStructureUtil.randomPublicKey(); + + @SuppressWarnings("unchecked") + final Cache cache = mock(Cache.class); + + @Test + public void shouldNotScanStateIfAlreadyHaveBuilders() { + final BuilderIndexCache builderIndexCache = + new BuilderIndexCache(cache, state.getBuilders().size()); + + when(cache.getCached(missingPublicKey)).thenReturn(Optional.empty()); + final Optional index = builderIndexCache.getBuilderIndex(state, missingPublicKey); + + verify(cache).getCached(missingPublicKey); + verify(cache, never()).invalidateWithNewValue(any(), any()); + assertThat(index).isEmpty(); + } + + @Test + public void shouldScanNewBuildersInSuppliedState() { + final int initialLastCachedIndex = state.getBuilders().size() - 3; + final BuilderIndexCache builderIndexCache = + new BuilderIndexCache(cache, initialLastCachedIndex); + + when(cache.getCached(missingPublicKey)).thenReturn(Optional.empty()); + final Optional index = builderIndexCache.getBuilderIndex(state, missingPublicKey); + verify(cache).getCached(missingPublicKey); + verify(cache, times(state.getBuilders().size() - initialLastCachedIndex - 1)) + .invalidateWithNewValue(any(), any()); + assertThat(index).isEmpty(); + } + + @Test + public void shouldGetAllBuilderKeysCachedIfMissingKeyPassed() { + final BuilderIndexCache builderIndexCache = new BuilderIndexCache(); + final Optional index = builderIndexCache.getBuilderIndex(state, missingPublicKey); + assertThat(index).isEmpty(); + assertThat(builderIndexCache.getBuilderIndices().size()).isEqualTo(state.getBuilders().size()); + } + + @Test + public void shouldPopulateCacheItemsFromState() { + final BuilderIndexCache builderIndexCache = new BuilderIndexCache(); + final int targetIndex = state.getBuilders().size() - 1; + final BLSPublicKey foundKey = state.getBuilders().get(targetIndex).getPublicKey(); + + final Optional index = builderIndexCache.getBuilderIndex(state, foundKey); + assertThat(index).contains(targetIndex); + assertThat(builderIndexCache.getLastCachedIndex()).isEqualTo(targetIndex); + assertThat(builderIndexCache.getBuilderIndices().size()).isEqualTo(targetIndex + 1); + } + + @Test + public void shouldFilterItemsBeyondStateIndex() { + final BuilderIndexCache builderIndexCache = new BuilderIndexCache(); + builderIndexCache.invalidateWithNewValue(missingPublicKey, 100); + final Optional index = builderIndexCache.getBuilderIndex(state, missingPublicKey); + + assertThat(index).isEmpty(); + // state didn't get scanned, because we had the index but it was out of bounds + assertThat(builderIndexCache.getLastCachedIndex()).isEqualTo(-1); + assertThat(builderIndexCache.getBuilderIndices().size()).isEqualTo(1); + } + + @Test + public void shouldInvalidateMapping() { + final BuilderIndexCache builderIndexCache = new BuilderIndexCache(); + final BLSPublicKey existingKey = state.getBuilders().get(0).getPublicKey(); + + // populate the cache + assertThat(builderIndexCache.getBuilderIndex(state, existingKey)).contains(0); + assertThat(builderIndexCache.getBuilderIndices().getCached(existingKey)).contains(0); + + // invalidate the mapping (simulates builder reassignment) + builderIndexCache.invalidate(existingKey); + assertThat(builderIndexCache.getBuilderIndices().getCached(existingKey)).isEmpty(); + } +} From 31e120090184dfc6f5f035314c3be082cc9d48b2 Mon Sep 17 00:00:00 2001 From: StefanBratanov Date: Thu, 28 May 2026 11:56:45 +0100 Subject: [PATCH 08/16] change comment to be as per spec --- .../versions/gloas/forktransition/GloasStateUpgrade.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/gloas/forktransition/GloasStateUpgrade.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/gloas/forktransition/GloasStateUpgrade.java index e791889d25a..579833bcebd 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/gloas/forktransition/GloasStateUpgrade.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/gloas/forktransition/GloasStateUpgrade.java @@ -183,14 +183,15 @@ private void onboardBuildersFromPendingDeposits(final MutableBeaconStateGloas st pendingDeposits.add(deposit); continue; } + // Deposits for non-builders stay in the pending queue. If there is a valid pending deposit + // for a new validator with this pubkey, keep this deposit in the pending queue to be + // applied to that validator later. if (beaconStateAccessors.getBuilderIndex(state, pubkey).isEmpty()) { // Deposits without builder credentials stay in the pending queue if (!predicates.isBuilderWithdrawalCredential(deposit.getWithdrawalCredentials())) { pendingDeposits.add(deposit); continue; } - // If there is a valid pending deposit for a new validator with this pubkey, keep this - // deposit in the pending queue to be applied to that validator later. boolean isPendingValidator; if (verifiedPendingValidatorPubkeys.contains(pubkey)) { isPendingValidator = true; From 078b4a846d9d05a9b2768092a81001bf834118e7 Mon Sep 17 00:00:00 2001 From: Stefan Bratanov Date: Thu, 28 May 2026 13:06:41 +0100 Subject: [PATCH 09/16] Change constant --- .../tech/pegasys/teku/spec/config/configs/mainnet.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ethereum/spec/src/main/resources/tech/pegasys/teku/spec/config/configs/mainnet.yaml b/ethereum/spec/src/main/resources/tech/pegasys/teku/spec/config/configs/mainnet.yaml index edf4edc2010..3ef39f216e1 100644 --- a/ethereum/spec/src/main/resources/tech/pegasys/teku/spec/config/configs/mainnet.yaml +++ b/ethereum/spec/src/main/resources/tech/pegasys/teku/spec/config/configs/mainnet.yaml @@ -88,8 +88,8 @@ SYNC_MESSAGE_DUE_BPS: 3333 CONTRIBUTION_DUE_BPS: 6667 # Gloas -# 2**6 (= 64) epochs -MIN_BUILDER_WITHDRAWABILITY_DELAY: 64 +# 2**13 (= 8, 4 192) epochs +MIN_BUILDER_WITHDRAWABILITY_DELAY: 8192 # 2500 basis points, 25% of SLOT_DURATION_MS ATTESTATION_DUE_BPS_GLOAS: 2500 # 5000 basis points, 50% of SLOT_DURATION_MS @@ -224,4 +224,4 @@ BLOB_SCHEDULE: - EPOCH: 412672 # December 9, 2025, 02:21:11pm UTC MAX_BLOBS_PER_BLOCK: 15 - EPOCH: 419072 # January 7, 2026, 01:01:11am UTC - MAX_BLOBS_PER_BLOCK: 21 \ No newline at end of file + MAX_BLOBS_PER_BLOCK: 21 From a6ea676946f013bb190196b7c375e2bc87ecb811 Mon Sep 17 00:00:00 2001 From: Stefan Bratanov Date: Thu, 28 May 2026 13:14:33 +0100 Subject: [PATCH 10/16] Fix test --- .../teku/beaconrestapi/handlers/v1/config/mainnetConfig.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/data/beaconrestapi/src/test/resources/tech/pegasys/teku/beaconrestapi/handlers/v1/config/mainnetConfig.json b/data/beaconrestapi/src/test/resources/tech/pegasys/teku/beaconrestapi/handlers/v1/config/mainnetConfig.json index 60d5d14a15a..d7b7424216a 100644 --- a/data/beaconrestapi/src/test/resources/tech/pegasys/teku/beaconrestapi/handlers/v1/config/mainnetConfig.json +++ b/data/beaconrestapi/src/test/resources/tech/pegasys/teku/beaconrestapi/handlers/v1/config/mainnetConfig.json @@ -128,7 +128,7 @@ "MAXIMUM_GOSSIP_CLOCK_DISPARITY" : "500", "TARGET_COMMITTEE_SIZE" : "128", "TERMINAL_BLOCK_HASH" : "0x0000000000000000000000000000000000000000000000000000000000000000", - "MIN_BUILDER_WITHDRAWABILITY_DELAY" : "64", + "MIN_BUILDER_WITHDRAWABILITY_DELAY" : "8192", "CONSOLIDATION_CHURN_LIMIT_QUOTIENT" : "65536", "DOMAIN_DEPOSIT" : "0x03000000", "DOMAIN_CONTRIBUTION_AND_PROOF" : "0x09000000", @@ -195,4 +195,4 @@ "PROPOSER_REORG_CUTOFF_BPS" : "1667", "BLS_WITHDRAWAL_PREFIX" : "0x00", "MIN_ACTIVATION_BALANCE" : "32000000000" -} \ No newline at end of file +} From 5474696fe60a60eef76ed589753c0e4d847d2801 Mon Sep 17 00:00:00 2001 From: Stefan Bratanov Date: Thu, 28 May 2026 13:21:16 +0100 Subject: [PATCH 11/16] Fix typo --- .../tech/pegasys/teku/spec/config/configs/mainnet.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ethereum/spec/src/main/resources/tech/pegasys/teku/spec/config/configs/mainnet.yaml b/ethereum/spec/src/main/resources/tech/pegasys/teku/spec/config/configs/mainnet.yaml index 3ef39f216e1..f0bfecd42cb 100644 --- a/ethereum/spec/src/main/resources/tech/pegasys/teku/spec/config/configs/mainnet.yaml +++ b/ethereum/spec/src/main/resources/tech/pegasys/teku/spec/config/configs/mainnet.yaml @@ -88,7 +88,7 @@ SYNC_MESSAGE_DUE_BPS: 3333 CONTRIBUTION_DUE_BPS: 6667 # Gloas -# 2**13 (= 8, 4 192) epochs +# 2**13 (= 8,192) epochs MIN_BUILDER_WITHDRAWABILITY_DELAY: 8192 # 2500 basis points, 25% of SLOT_DURATION_MS ATTESTATION_DUE_BPS_GLOAS: 2500 From 50c82903edfa2fdeb3db3cb6e44d66543bce0fc4 Mon Sep 17 00:00:00 2001 From: StefanBratanov Date: Fri, 29 May 2026 09:41:30 +0100 Subject: [PATCH 12/16] add copy() to BuilderIndexCache --- .../state/beaconstate/common/BuilderIndexCache.java | 4 ++++ .../state/beaconstate/common/TransitionCaches.java | 4 +++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/beaconstate/common/BuilderIndexCache.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/beaconstate/common/BuilderIndexCache.java index dc8ff27c4de..f7d8951893b 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/beaconstate/common/BuilderIndexCache.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/beaconstate/common/BuilderIndexCache.java @@ -62,6 +62,10 @@ public void invalidate(final BLSPublicKey pubKey) { builderIndices.invalidate(pubKey); } + public BuilderIndexCache copy() { + return new BuilderIndexCache(builderIndices.copy(), lastCachedIndex.get()); + } + @VisibleForTesting int getLastCachedIndex() { return lastCachedIndex.get(); diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/beaconstate/common/TransitionCaches.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/beaconstate/common/TransitionCaches.java index 426a6e09a1b..a4965e5ee78 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/beaconstate/common/TransitionCaches.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/beaconstate/common/TransitionCaches.java @@ -264,6 +264,8 @@ public TransitionCaches copy() { baseRewardPerIncrement.copy(), progressiveTotalBalances.copy(), buildersPubKeys.copy(), - builderIndexCache); + // Unlike validators, builder indices can be reassigned, so the cache must be copied to + // prevent invalidations in one branch from corrupting lookups in another. + builderIndexCache.copy()); } } From ec23f68116280d79d13efa77446cd8b2181e6421 Mon Sep 17 00:00:00 2001 From: StefanBratanov Date: Fri, 29 May 2026 09:48:32 +0100 Subject: [PATCH 13/16] change comment --- .../state/beaconstate/common/TransitionCaches.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/beaconstate/common/TransitionCaches.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/beaconstate/common/TransitionCaches.java index a4965e5ee78..a2af2039e6f 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/beaconstate/common/TransitionCaches.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/beaconstate/common/TransitionCaches.java @@ -263,9 +263,9 @@ public TransitionCaches copy() { syncCommitteeCache.copy(), baseRewardPerIncrement.copy(), progressiveTotalBalances.copy(), + // Unlike validators, builder indices can be reassigned, so the builder caches must be + // copied to prevent invalidations in one branch from corrupting lookups in another. buildersPubKeys.copy(), - // Unlike validators, builder indices can be reassigned, so the cache must be copied to - // prevent invalidations in one branch from corrupting lookups in another. builderIndexCache.copy()); } } From 302d931531d7e5cca7763f6a1546dd0a8c7f90a1 Mon Sep 17 00:00:00 2001 From: StefanBratanov Date: Fri, 29 May 2026 09:55:09 +0100 Subject: [PATCH 14/16] add some small comment --- .../state/beaconstate/common/BuilderIndexCache.java | 2 ++ .../state/beaconstate/common/ValidatorIndexCache.java | 7 +++++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/beaconstate/common/BuilderIndexCache.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/beaconstate/common/BuilderIndexCache.java index f7d8951893b..d7f938af6e7 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/beaconstate/common/BuilderIndexCache.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/beaconstate/common/BuilderIndexCache.java @@ -48,6 +48,8 @@ public Optional getBuilderIndex(final BeaconState state, final BLSPubli final SszList builders = BeaconStateGloas.required(state).getBuilders(); final Optional builderIndex = builderIndices.getCached(publicKey); if (builderIndex.isPresent()) { + // The cache is shared across states, so a cached index may be stale for the state being + // queried return builderIndex.filter(index -> index < builders.size()); } diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/beaconstate/common/ValidatorIndexCache.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/beaconstate/common/ValidatorIndexCache.java index 41505941871..09793bac616 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/beaconstate/common/ValidatorIndexCache.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/beaconstate/common/ValidatorIndexCache.java @@ -46,12 +46,15 @@ public ValidatorIndexCache() { public Optional getValidatorIndex( final BeaconState state, final BLSPublicKey publicKey) { + final SszList validators = state.getValidators(); final Optional validatorIndex = validatorIndices.getCached(publicKey); if (validatorIndex.isPresent()) { - return validatorIndex.filter(index -> index < state.getValidators().size()); + // The cache is shared across states, so a cached index may be stale for the state being + // queried + return validatorIndex.filter(index -> index < validators.size()); } - return findIndexFromState(state.getValidators(), publicKey); + return findIndexFromState(validators, publicKey); } public void invalidateWithNewValue(final BLSPublicKey pubKey, final int updatedIndex) { From 6da87a48f88b294ae91c93c283590de6ff17a407 Mon Sep 17 00:00:00 2001 From: StefanBratanov Date: Fri, 29 May 2026 10:09:42 +0100 Subject: [PATCH 15/16] cleaner --- .../beaconstate/common/BuilderIndexCache.java | 14 ++++++-------- .../beaconstate/common/ValidatorIndexCache.java | 14 ++++++-------- 2 files changed, 12 insertions(+), 16 deletions(-) diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/beaconstate/common/BuilderIndexCache.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/beaconstate/common/BuilderIndexCache.java index d7f938af6e7..352b477f077 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/beaconstate/common/BuilderIndexCache.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/beaconstate/common/BuilderIndexCache.java @@ -46,14 +46,12 @@ public BuilderIndexCache() { public Optional getBuilderIndex(final BeaconState state, final BLSPublicKey publicKey) { final SszList builders = BeaconStateGloas.required(state).getBuilders(); - final Optional builderIndex = builderIndices.getCached(publicKey); - if (builderIndex.isPresent()) { - // The cache is shared across states, so a cached index may be stale for the state being - // queried - return builderIndex.filter(index -> index < builders.size()); - } - - return findIndexFromState(builders, publicKey); + return builderIndices + .getCached(publicKey) + // The cache is shared across states, so a cached index may be stale for the state being + // queried + .filter(index -> index < builders.size()) + .or(() -> findIndexFromState(builders, publicKey)); } public void invalidateWithNewValue(final BLSPublicKey pubKey, final int updatedIndex) { diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/beaconstate/common/ValidatorIndexCache.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/beaconstate/common/ValidatorIndexCache.java index 09793bac616..c874df636f2 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/beaconstate/common/ValidatorIndexCache.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/beaconstate/common/ValidatorIndexCache.java @@ -47,14 +47,12 @@ public ValidatorIndexCache() { public Optional getValidatorIndex( final BeaconState state, final BLSPublicKey publicKey) { final SszList validators = state.getValidators(); - final Optional validatorIndex = validatorIndices.getCached(publicKey); - if (validatorIndex.isPresent()) { - // The cache is shared across states, so a cached index may be stale for the state being - // queried - return validatorIndex.filter(index -> index < validators.size()); - } - - return findIndexFromState(validators, publicKey); + return validatorIndices + .getCached(publicKey) + // The cache is shared across states, so a cached index may be stale for the state being + // queried + .filter(index -> index < validators.size()) + .or(() -> findIndexFromState(validators, publicKey)); } public void invalidateWithNewValue(final BLSPublicKey pubKey, final int updatedIndex) { From da95d83104dbbdbac3102926022c8583d1ecb312 Mon Sep 17 00:00:00 2001 From: StefanBratanov Date: Fri, 29 May 2026 10:32:35 +0100 Subject: [PATCH 16/16] fix tests --- .../state/beaconstate/common/BuilderIndexCache.java | 13 +++++++------ .../beaconstate/common/ValidatorIndexCache.java | 13 +++++++------ 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/beaconstate/common/BuilderIndexCache.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/beaconstate/common/BuilderIndexCache.java index 352b477f077..c5d2d376eaf 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/beaconstate/common/BuilderIndexCache.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/beaconstate/common/BuilderIndexCache.java @@ -46,12 +46,13 @@ public BuilderIndexCache() { public Optional getBuilderIndex(final BeaconState state, final BLSPublicKey publicKey) { final SszList builders = BeaconStateGloas.required(state).getBuilders(); - return builderIndices - .getCached(publicKey) - // The cache is shared across states, so a cached index may be stale for the state being - // queried - .filter(index -> index < builders.size()) - .or(() -> findIndexFromState(builders, publicKey)); + final Optional builderIndex = builderIndices.getCached(publicKey); + if (builderIndex.isPresent()) { + // The cache is shared across states, so a cached index may be stale for the state being + // queried + return builderIndex.filter(index -> index < builders.size()); + } + return findIndexFromState(builders, publicKey); } public void invalidateWithNewValue(final BLSPublicKey pubKey, final int updatedIndex) { diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/beaconstate/common/ValidatorIndexCache.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/beaconstate/common/ValidatorIndexCache.java index c874df636f2..5a063496618 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/beaconstate/common/ValidatorIndexCache.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/beaconstate/common/ValidatorIndexCache.java @@ -47,12 +47,13 @@ public ValidatorIndexCache() { public Optional getValidatorIndex( final BeaconState state, final BLSPublicKey publicKey) { final SszList validators = state.getValidators(); - return validatorIndices - .getCached(publicKey) - // The cache is shared across states, so a cached index may be stale for the state being - // queried - .filter(index -> index < validators.size()) - .or(() -> findIndexFromState(validators, publicKey)); + final Optional validatorIndex = validatorIndices.getCached(publicKey); + if (validatorIndex.isPresent()) { + // The cache is shared across states, so a cached index may be stale for the state being + // queried + return validatorIndex.filter(index -> index < validators.size()); + } + return findIndexFromState(validators, publicKey); } public void invalidateWithNewValue(final BLSPublicKey pubKey, final int updatedIndex) {