Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import com.fasterxml.jackson.annotation.JsonProperty;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
Expand Down Expand Up @@ -247,7 +248,8 @@ public void onBlockImported(

// acceptedPreferences tracks proposer preferences that have been accepted by the validator,
// so the bid validator can look them up to check bid compatibility.
final Map<UInt64, ProposerPreferences> acceptedPreferences = new ConcurrentHashMap<>();
final Map<UInt64, Map<Bytes32, ProposerPreferences>> acceptedPreferences =
new ConcurrentHashMap<>();
final ProposerPreferencesManager proposerPreferencesManager =
new ProposerPreferencesManager() {
@Override
Expand All @@ -263,8 +265,17 @@ public SafeFuture<InternalValidationResult> addRemote(
}

@Override
public Optional<ProposerPreferences> getProposerPreferences(final UInt64 slot) {
return Optional.ofNullable(acceptedPreferences.get(slot));
public Optional<ProposerPreferences> getProposerPreferences(
final UInt64 slot, final Bytes32 dependentRoot) {
return Optional.ofNullable(acceptedPreferences.get(slot))
.map(preferencesByDependentRoot -> preferencesByDependentRoot.get(dependentRoot));
}

@Override
public Collection<ProposerPreferences> getProposerPreferencesForSlot(final UInt64 slot) {
return Optional.ofNullable(acceptedPreferences.get(slot))
.map(preferencesByDependentRoot -> List.copyOf(preferencesByDependentRoot.values()))
.orElse(List.of());
}

@Override
Expand Down Expand Up @@ -295,8 +306,10 @@ public void subscribeOperationAdded(
proposerPreferencesSchema::sszDeserialize);
result = safeJoin(preferencesValidator.validate(signedPreferences));
if (result.isAccept()) {
acceptedPreferences.put(
signedPreferences.getMessage().getProposalSlot(), signedPreferences.getMessage());
final ProposerPreferences preferences = signedPreferences.getMessage();
acceptedPreferences
.computeIfAbsent(preferences.getProposalSlot(), __ -> new ConcurrentHashMap<>())
.put(preferences.getDependentRoot(), preferences);
}
} else if (messageName.startsWith("execution_payload_envelope_")) {
final SignedExecutionPayloadEnvelope signedEnvelope =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,14 @@

import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentNavigableMap;
import java.util.concurrent.ConcurrentSkipListMap;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.tuweni.bytes.Bytes32;
import tech.pegasys.teku.ethereum.events.SlotEventsChannel;
import tech.pegasys.teku.infrastructure.async.SafeFuture;
import tech.pegasys.teku.infrastructure.subscribers.Subscribers;
Expand All @@ -40,8 +43,8 @@ public class DefaultProposerPreferencesManager

private final ProposerPreferencesGossipValidator proposerPreferencesGossipValidator;
private final PendingPool<PendingProposerPreferences> pendingProposerPreferences;
private final ConcurrentNavigableMap<UInt64, ProposerPreferences> acceptedProposerPreferences =
new ConcurrentSkipListMap<>();
private final ConcurrentNavigableMap<UInt64, Map<Bytes32, ProposerPreferences>>
acceptedProposerPreferences = new ConcurrentSkipListMap<>();
private final Subscribers<OperationAddedSubscriber<SignedProposerPreferences>> subscribers =
Subscribers.create(true);

Expand All @@ -65,8 +68,17 @@ public SafeFuture<InternalValidationResult> addRemote(
}

@Override
public Optional<ProposerPreferences> getProposerPreferences(final UInt64 slot) {
return Optional.ofNullable(acceptedProposerPreferences.get(slot));
public Optional<ProposerPreferences> getProposerPreferences(
final UInt64 slot, final Bytes32 dependentRoot) {
return Optional.ofNullable(acceptedProposerPreferences.get(slot))
.map(preferencesByDependentRoot -> preferencesByDependentRoot.get(dependentRoot));
}

@Override
public Collection<ProposerPreferences> getProposerPreferencesForSlot(final UInt64 slot) {
return Optional.ofNullable(acceptedProposerPreferences.get(slot))
.map(preferencesByDependentRoot -> List.copyOf(preferencesByDependentRoot.values()))
.orElse(List.of());
}

@Override
Expand Down Expand Up @@ -117,9 +129,10 @@ private void processValidationResult(
switch (result.code()) {
case ACCEPT -> {
removePendingPreferences(signedProposerPreferences);
acceptedProposerPreferences.put(
signedProposerPreferences.getMessage().getProposalSlot(),
signedProposerPreferences.getMessage());
final ProposerPreferences proposerPreferences = signedProposerPreferences.getMessage();
acceptedProposerPreferences
.computeIfAbsent(proposerPreferences.getProposalSlot(), __ -> new ConcurrentHashMap<>())
.put(proposerPreferences.getDependentRoot(), proposerPreferences);
subscribers.forEach(
subscriber ->
subscriber.onOperationAdded(signedProposerPreferences, result, fromNetwork));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,10 @@

package tech.pegasys.teku.statetransition.execution;

import java.util.Collection;
import java.util.List;
import java.util.Optional;
import org.apache.tuweni.bytes.Bytes32;
import tech.pegasys.teku.infrastructure.async.SafeFuture;
import tech.pegasys.teku.infrastructure.unsigned.UInt64;
import tech.pegasys.teku.spec.datastructures.epbs.versions.gloas.ProposerPreferences;
Expand All @@ -38,10 +41,16 @@ public SafeFuture<InternalValidationResult> addRemote(
}

@Override
public Optional<ProposerPreferences> getProposerPreferences(final UInt64 slot) {
public Optional<ProposerPreferences> getProposerPreferences(
final UInt64 slot, final Bytes32 dependentRoot) {
return Optional.empty();
}

@Override
public Collection<ProposerPreferences> getProposerPreferencesForSlot(final UInt64 slot) {
return List.of();
}

@Override
public void subscribeOperationAdded(
final OperationAddedSubscriber<SignedProposerPreferences> subscriber) {}
Expand All @@ -53,7 +62,9 @@ SafeFuture<InternalValidationResult> addLocal(
SafeFuture<InternalValidationResult> addRemote(
SignedProposerPreferences signedProposerPreferences);

Optional<ProposerPreferences> getProposerPreferences(UInt64 slot);
Optional<ProposerPreferences> getProposerPreferences(UInt64 slot, Bytes32 dependentRoot);

Collection<ProposerPreferences> getProposerPreferencesForSlot(UInt64 slot);

void subscribeOperationAdded(OperationAddedSubscriber<SignedProposerPreferences> subscriber);
}
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@
import tech.pegasys.teku.statetransition.forkchoice.fastconfirmation.ForkChoiceFastConfirmation;
import tech.pegasys.teku.statetransition.payloadattestation.ValidatablePayloadAttestationMessage;
import tech.pegasys.teku.statetransition.util.DebugDataDumper;
import tech.pegasys.teku.statetransition.util.ShufflingDependentRootUtil;
import tech.pegasys.teku.statetransition.validation.AttestationStateSelector;
import tech.pegasys.teku.statetransition.validation.BlockBroadcastValidator;
import tech.pegasys.teku.statetransition.validation.InternalValidationResult;
Expand Down Expand Up @@ -1014,11 +1015,7 @@ private Optional<Bytes32> getShufflingDependentRoot(
}

private Optional<UInt64> getShufflingDependentSlot(final UInt64 epoch) {
final int minSeedLookahead = spec.getSpecConfig(epoch).getMinSeedLookahead();
if (epoch.isLessThanOrEqualTo(UInt64.valueOf(minSeedLookahead))) {
return Optional.empty();
}
return Optional.of(spec.computeStartSlotAtEpoch(epoch.minus(minSeedLookahead)).minus(1));
return ShufflingDependentRootUtil.getShufflingDependentSlotForEpoch(spec, epoch);
}

private Optional<List<BlobSidecar>> extractBlobSidecarsFromValidationResults(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,12 +44,14 @@
import tech.pegasys.teku.spec.datastructures.execution.versions.capella.Withdrawal;
import tech.pegasys.teku.spec.datastructures.forkchoice.ForkChoiceNode;
import tech.pegasys.teku.spec.datastructures.forkchoice.ForkChoicePayloadStatus;
import tech.pegasys.teku.spec.datastructures.forkchoice.ReadOnlyForkChoiceStrategy;
import tech.pegasys.teku.spec.datastructures.state.beaconstate.BeaconState;
import tech.pegasys.teku.spec.datastructures.validator.BeaconPreparableProposer;
import tech.pegasys.teku.spec.executionlayer.ExecutionLayerChannel;
import tech.pegasys.teku.spec.executionlayer.ForkChoiceState;
import tech.pegasys.teku.spec.executionlayer.PayloadBuildingAttributes;
import tech.pegasys.teku.statetransition.execution.ProposerPreferencesManager;
import tech.pegasys.teku.statetransition.util.ShufflingDependentRootUtil;
import tech.pegasys.teku.storage.client.ChainHead;
import tech.pegasys.teku.storage.client.RecentChainData;
import tech.pegasys.teku.storage.client.ValidatorIsConnectedProvider;
Expand Down Expand Up @@ -307,10 +309,12 @@ private SafeFuture<Optional<PayloadBuildingAttributes>> calculatePayloadBuilding
final Optional<SignedValidatorRegistration> validatorRegistration =
Optional.ofNullable(validatorRegistrationInfoByValidatorIndex.get(proposerIndex))
.map(RegisteredValidatorInfo::getSignedValidatorRegistration);
final Optional<Bytes32> dependentRoot =
getShufflingDependentRoot(currentHeadBlock.blockRoot(), blockSlot);

final Eth1Address feeRecipient = getFeeRecipient(proposerInfo, blockSlot);
final UInt64 targetGasLimit =
getTargetGasLimit(blockSlot, proposerIndex, validatorRegistration);
getTargetGasLimit(blockSlot, proposerIndex, dependentRoot, validatorRegistration);

return getPayloadAttributeWithdrawals(currentHeadBlock, state)
.thenApplyAsync(
Expand Down Expand Up @@ -374,10 +378,11 @@ private SafeFuture<Optional<BeaconState>> getStateForPayloadBuildingAttributes(
UInt64 getTargetGasLimit(
final UInt64 blockSlot,
final UInt64 proposerIndex,
final Optional<Bytes32> dependentRoot,
final Optional<SignedValidatorRegistration> validatorRegistration) {
// post-Gloas, we use signed proposer preferences
return proposerPreferencesManager
.getProposerPreferences(blockSlot)
return dependentRoot
.flatMap(root -> proposerPreferencesManager.getProposerPreferences(blockSlot, root))
.filter(
proposerPreferences -> proposerPreferences.getValidatorIndex().equals(proposerIndex))
.map(ProposerPreferences::getTargetGasLimit)
Expand All @@ -388,6 +393,17 @@ UInt64 getTargetGasLimit(
.orElse(UInt64.ZERO);
}

private Optional<Bytes32> getShufflingDependentRoot(
final Bytes32 blockRoot, final UInt64 proposalSlot) {
final Optional<ReadOnlyForkChoiceStrategy> maybeForkChoiceStrategy =
recentChainData.getForkChoiceStrategy();
if (maybeForkChoiceStrategy == null || maybeForkChoiceStrategy.isEmpty()) {
return Optional.empty();
}
return ShufflingDependentRootUtil.getShufflingDependentRoot(
spec, maybeForkChoiceStrategy.get(), blockRoot, proposalSlot);
}

// this function MUST return a fee recipient.
private Eth1Address getFeeRecipient(
final PreparedProposerInfo preparedProposerInfo, final UInt64 blockSlot) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/*
* 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.statetransition.util;

import java.util.Optional;
import org.apache.tuweni.bytes.Bytes32;
import tech.pegasys.teku.infrastructure.unsigned.UInt64;
import tech.pegasys.teku.spec.Spec;
import tech.pegasys.teku.spec.datastructures.forkchoice.ReadOnlyForkChoiceStrategy;

public final class ShufflingDependentRootUtil {

private static final UInt64 ONE = UInt64.ONE;

private ShufflingDependentRootUtil() {}

public static Optional<Bytes32> getShufflingDependentRoot(
final Spec spec,
final ReadOnlyForkChoiceStrategy forkChoiceStrategy,
final Bytes32 blockRoot,
final UInt64 proposalSlot) {
final UInt64 proposalEpoch = spec.computeEpochAtSlot(proposalSlot);
final UInt64 minSeedLookahead =
UInt64.valueOf(spec.getSpecConfig(proposalEpoch).getMinSeedLookahead());
final UInt64 dependentSlot =
proposalEpoch.isLessThanOrEqualTo(minSeedLookahead)
? UInt64.ZERO
: spec.computeStartSlotAtEpoch(proposalEpoch.minus(minSeedLookahead)).minus(ONE);
return forkChoiceStrategy.getAncestor(blockRoot, dependentSlot);
}

public static Optional<UInt64> getShufflingDependentSlotForEpoch(
final Spec spec, final UInt64 proposalEpoch) {
final UInt64 minSeedLookahead =
UInt64.valueOf(spec.getSpecConfig(proposalEpoch).getMinSeedLookahead());
if (proposalEpoch.isLessThanOrEqualTo(minSeedLookahead)) {
return Optional.empty();
}
return Optional.of(
spec.computeStartSlotAtEpoch(proposalEpoch.minus(minSeedLookahead)).minus(ONE));
}
}
Loading
Loading