Skip to content

Commit 4ac360d

Browse files
authored
Reimplement proposer boost changes (#10368)
1 parent 9b4b99b commit 4ac360d

9 files changed

Lines changed: 92 additions & 47 deletions

File tree

eth-tests/src/main/java/tech/pegasys/teku/ethtests/finder/ReferenceTestFinder.java

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -71,18 +71,11 @@ private static Stream<TestDefinition> findTestTypes(final Path specDirectory) th
7171
new PyspecTestFinder(
7272
List.of(),
7373
List.of(
74-
// TODO: https://github.com/Consensys/teku/issues/10320 ignoring tests
75-
// which fail because of the proposer boost changes added in
76-
// https://github.com/ethereum/consensus-specs/pull/4807
77-
"minimal - fork_choice/get_head - voting_source_beyond_two_epoch",
78-
"minimal - fork_choice/on_block - justified_update_always_if_better",
79-
"minimal - fork_choice/on_block - justified_update_not_realized_finality",
8074
// TODO-GLOAS: Limit what tests we run for Gloas while it is
8175
// under development. This is temporary and should be removed once we
8276
// are up-to-date with Gloas specs (see
8377
// https://github.com/Consensys/teku-internal/issues/221)
84-
"gloas - minimal - fork_choice/",
85-
"gloas - mainnet - fork_choice/")),
78+
"gloas - minimal - fork_choice/", "gloas - mainnet - fork_choice/")),
8679
new MerkleProofTestFinder())
8780
.flatMap(unchecked(finder -> finder.findTests(fork, spec, testsPath)));
8881
});

ethereum/spec/src/main/java/tech/pegasys/teku/spec/Spec.java

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
package tech.pegasys.teku.spec;
1515

16+
import static com.google.common.base.Preconditions.checkArgument;
1617
import static com.google.common.base.Preconditions.checkState;
1718
import static tech.pegasys.teku.infrastructure.time.TimeUtilities.millisToSeconds;
1819
import static tech.pegasys.teku.spec.SpecMilestone.DENEB;
@@ -632,6 +633,38 @@ public int getBeaconProposerIndex(final BeaconState state, final UInt64 slot) {
632633
return atState(state).beaconStateAccessors().getBeaconProposerIndex(state, slot);
633634
}
634635

636+
/**
637+
* Returns the proposer index for the given slot, advancing the state via slot processing if
638+
* necessary. On FULU+, the ProposerLookahead allows resolving the proposer without slot
639+
* processing for slots in the current or next epoch. On earlier forks, slot processing is avoided
640+
* only when the slot is in the same epoch as the state.
641+
*
642+
* @param state a beacon state at or before the requested slot
643+
* @param slot the slot to get the proposer index for (must be >= state.slot)
644+
*/
645+
public int getProposerIndexAtSlot(final BeaconState state, final UInt64 slot)
646+
throws SlotProcessingException, EpochProcessingException {
647+
checkArgument(
648+
state.getSlot().isLessThanOrEqualTo(slot),
649+
"State slot %s must not be ahead of requested slot %s",
650+
state.getSlot(),
651+
slot);
652+
653+
final BeaconState actualState;
654+
655+
if (canCalculateProposerIndexAtSlot(state, slot)) {
656+
actualState = state;
657+
} else {
658+
actualState = processSlots(state, slot);
659+
}
660+
661+
return getBeaconProposerIndex(actualState, slot);
662+
}
663+
664+
public boolean canCalculateProposerIndexAtSlot(final BeaconState state, final UInt64 slot) {
665+
return atState(state).beaconStateAccessors().canCalculateProposerIndexAtSlot(state, slot);
666+
}
667+
635668
public UInt64 getCommitteeCountPerSlot(final BeaconState state, final UInt64 epoch) {
636669
return atState(state).beaconStateAccessors().getCommitteeCountPerSlot(state, epoch);
637670
}

ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/common/helpers/BeaconStateAccessors.java

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -229,15 +229,18 @@ public int getBeaconProposerIndex(final BeaconState state, final UInt64 requeste
229229

230230
protected void validateStateCanCalculateProposerIndexAtSlot(
231231
final BeaconState state, final UInt64 requestedSlot) {
232-
final UInt64 epoch = miscHelpers.computeEpochAtSlot(requestedSlot);
233-
final UInt64 stateEpoch = getCurrentEpoch(state);
234232
checkArgument(
235-
epoch.equals(stateEpoch),
236-
"get_beacon_proposer_index is only used for requesting a slot in the current epoch. Requested slot %s (in epoch %s), state slot %s (in epoch %s)",
233+
canCalculateProposerIndexAtSlot(state, requestedSlot),
234+
"get_beacon_proposer_index is only used for requesting a slot in the current epoch (or next epoch in FULU). Requested slot %s, state slot %s",
237235
requestedSlot,
238-
epoch,
239-
state.getSlot(),
240-
stateEpoch);
236+
state.getSlot());
237+
}
238+
239+
public boolean canCalculateProposerIndexAtSlot(
240+
final BeaconState state, final UInt64 requestedSlot) {
241+
final UInt64 epoch = miscHelpers.computeEpochAtSlot(requestedSlot);
242+
final UInt64 stateEpoch = getCurrentEpoch(state);
243+
return epoch.equals(stateEpoch);
241244
}
242245

243246
public UInt64 getFinalityDelay(final BeaconState state) {

ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/fulu/helpers/BeaconStateAccessorsFulu.java

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -70,17 +70,11 @@ public int getBeaconProposerIndex(final BeaconState state, final UInt64 requeste
7070
}
7171

7272
@Override
73-
protected void validateStateCanCalculateProposerIndexAtSlot(
73+
public boolean canCalculateProposerIndexAtSlot(
7474
final BeaconState state, final UInt64 requestedSlot) {
7575
final UInt64 epoch = miscHelpers.computeEpochAtSlot(requestedSlot);
7676
final UInt64 stateEpoch = getCurrentEpoch(state);
77-
checkArgument(
78-
stateEpoch.equals(epoch) || stateEpoch.increment().equals(epoch),
79-
"get_beacon_proposer_index is only used for requesting a slot in the current or next epoch. Requested slot %s (in epoch %s), state slot %s (in epoch %s)",
80-
requestedSlot,
81-
epoch,
82-
state.getSlot(),
83-
stateEpoch);
77+
return epoch.equals(stateEpoch) || stateEpoch.increment().equals(epoch);
8478
}
8579

8680
public List<Integer> getBeaconProposerIndices(final BeaconState state, final UInt64 epoch) {

ethereum/spec/src/test/java/tech/pegasys/teku/spec/logic/versions/fulu/helpers/BeaconStateAccessorsFuluTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ void getBeaconProposerIndex_shouldThrowIfNotCurrentOrNextEpoch(final int slot) {
136136
stateAccessorsFulu.getBeaconProposerIndex(
137137
blockAndState.getState(), UInt64.valueOf(slot)))
138138
.hasMessageContaining(
139-
"get_beacon_proposer_index is only used for requesting a slot in the current or next epoch");
139+
"get_beacon_proposer_index is only used for requesting a slot in the current epoch (or next epoch in FULU)");
140140
}
141141

142142
private List<Integer> getProposerLookaheadFromState(final BeaconStateFulu beaconStateFulu) {

ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/forkchoice/ForkChoice.java

Lines changed: 39 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515

1616
import static com.google.common.base.Preconditions.checkArgument;
1717
import static tech.pegasys.teku.infrastructure.logging.P2PLogger.P2P_LOG;
18-
import static tech.pegasys.teku.infrastructure.time.TimeUtilities.secondsToMillis;
1918
import static tech.pegasys.teku.statetransition.forkchoice.StateRootCollector.addParentStateRoots;
2019

2120
import com.google.common.annotations.VisibleForTesting;
@@ -66,6 +65,8 @@
6665
import tech.pegasys.teku.spec.executionlayer.PayloadStatus;
6766
import tech.pegasys.teku.spec.logic.common.statetransition.availability.AvailabilityChecker;
6867
import tech.pegasys.teku.spec.logic.common.statetransition.availability.DataAndValidationResult;
68+
import tech.pegasys.teku.spec.logic.common.statetransition.exceptions.EpochProcessingException;
69+
import tech.pegasys.teku.spec.logic.common.statetransition.exceptions.SlotProcessingException;
6970
import tech.pegasys.teku.spec.logic.common.statetransition.exceptions.StateTransitionException;
7071
import tech.pegasys.teku.spec.logic.common.statetransition.results.BlockImportResult;
7172
import tech.pegasys.teku.spec.logic.common.statetransition.results.BlockImportResult.FailureReason;
@@ -662,8 +663,8 @@ private BlockImportResult importBlockAndState(
662663
blobSidecars,
663664
earliestBlobSidecarsSlot);
664665

665-
final boolean shouldApplyProposerBoost = shouldApplyProposerBoost(block, transaction);
666-
if (shouldApplyProposerBoost) {
666+
final boolean shouldUpdateProposerBoostRoot = shouldUpdateProposerBoostRoot(block, transaction);
667+
if (shouldUpdateProposerBoostRoot) {
667668
transaction.setProposerBoostRoot(block.getRoot());
668669
}
669670

@@ -693,7 +694,8 @@ private BlockImportResult importBlockAndState(
693694
} else {
694695
result = BlockImportResult.optimisticallySuccessful(block);
695696
}
696-
updateForkChoiceForImportedBlock(block, shouldApplyProposerBoost, result, forkChoiceStrategy);
697+
updateForkChoiceForImportedBlock(
698+
block, shouldUpdateProposerBoostRoot, result, forkChoiceStrategy);
697699
notifyForkChoiceUpdatedAndOptimisticSyncingChanged(Optional.empty());
698700
return result;
699701
}
@@ -746,20 +748,42 @@ private ExecutionPayloadImportResult importExecutionPayloadAndState(
746748
}
747749

748750
// from consensus-specs/fork-choice:
749-
private boolean shouldApplyProposerBoost(
751+
private boolean shouldUpdateProposerBoostRoot(
750752
final SignedBeaconBlock block, final StoreTransaction transaction) {
751-
// get_current_slot(store) == block.slot
752-
if (!spec.getCurrentSlot(transaction).equals(block.getSlot())) {
753+
// is_first_block
754+
if (transaction.getProposerBoostRoot().isPresent()) {
753755
return false;
754756
}
755-
// is_before_attesting_interval
756-
final UInt64 timeIntoSlotMillis =
757-
getMillisIntoSlot(transaction, spec.getSlotDurationMillis(block.getSlot()));
758-
if (!timeIntoSlotMillis.isLessThan(spec.getAttestationDueMillis(block.getSlot()))) {
757+
// is_timely
758+
if (recentChainData.isBlockLate(block.getRoot())) {
759759
return false;
760760
}
761-
// is_first_block
762-
return transaction.getProposerBoostRoot().isEmpty();
761+
762+
// in the edge cases when the chain head is not present or the head state future is not
763+
// immediately available, the proposer boost root will be set to maintain backwards
764+
// compatibility (see: https://github.com/ethereum/consensus-specs/pull/4807/)
765+
return recentChainData
766+
.getChainHead()
767+
.map(
768+
chainHead -> {
769+
final SafeFuture<BeaconState> headStateFuture = chainHead.getState();
770+
if (!headStateFuture.isCompletedNormally()) {
771+
return true;
772+
}
773+
final BeaconState headState = headStateFuture.join();
774+
final UInt64 currentSlot = spec.getCurrentSlot(transaction);
775+
try {
776+
return block.getProposerIndex().intValue()
777+
== spec.getProposerIndexAtSlot(headState, currentSlot);
778+
} catch (final SlotProcessingException | EpochProcessingException ex) {
779+
throw new RuntimeException(
780+
String.format(
781+
"Can't progress head state from slot %s to slot %s",
782+
headState.getSlot(), currentSlot),
783+
ex);
784+
}
785+
})
786+
.orElse(true);
763787
}
764788

765789
private Optional<List<BlobSidecar>> extractBlobSidecarsFromValidationResults(
@@ -811,13 +835,6 @@ private Optional<UInt64> computeEarliestBlobSidecarsSlot(
811835
earliestAvailabilityWindowSlotBeforeBlock.max(earliestAffectedSlot));
812836
}
813837

814-
private UInt64 getMillisIntoSlot(final StoreTransaction transaction, final int millisPerSlot) {
815-
return transaction
816-
.getTimeInMillis()
817-
.minus(secondsToMillis(transaction.getGenesisTime()))
818-
.mod(millisPerSlot);
819-
}
820-
821838
private void onExecutionPayloadResult(
822839
final Bytes32 blockRoot, final PayloadStatus payloadResult) {
823840
final SafeFuture<PayloadValidationResult> transitionValidatedStatus;
@@ -858,7 +875,7 @@ private void onExecutionPayloadResult(
858875

859876
private void updateForkChoiceForImportedBlock(
860877
final SignedBeaconBlock block,
861-
final boolean shouldApplyProposerBoost,
878+
final boolean shouldUpdateProposerBoostRoot,
862879
final BlockImportResult result,
863880
final ForkChoiceStrategy forkChoiceStrategy) {
864881

@@ -872,7 +889,7 @@ private void updateForkChoiceForImportedBlock(
872889
}
873890
}
874891

875-
if (!result.isBlockOnCanonicalChain() && shouldApplyProposerBoost) {
892+
if (!result.isBlockOnCanonicalChain() && shouldUpdateProposerBoostRoot) {
876893
// This is likely a reorging block that requires a full processHead to update the head.
877894
// Running processHead here will ensure:
878895
//

ethereum/statetransition/src/test/java/tech/pegasys/teku/statetransition/forkchoice/ForkChoiceTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -845,7 +845,7 @@ void onBlock_shouldNotifyOptimisticSyncChangeOnlyWhenImportingOnCanonicalHead()
845845
}
846846

847847
@Test
848-
void onBlock_shouldApplyProposerBoostToFirstBlock() {
848+
void onBlock_shouldUpdateProposerBoostRootToFirstBlock() {
849849
final ChainBuilder forkChain = chainBuilder.fork();
850850

851851
final SignedBlockAndState block = chainBuilder.generateNextBlock();

storage/src/main/java/tech/pegasys/teku/storage/client/LateBlockReorgLogic.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ public LateBlockReorgLogic(final Spec spec, final RecentChainData recentChainDat
5656
this.recentChainData = recentChainData;
5757
}
5858

59+
// record_block_timeliness
5960
public void setBlockTimelinessFromArrivalTime(
6061
final SignedBeaconBlock block, final UInt64 arrivalTimeMillis) {
6162
if (blockTimeliness.get(block.getRoot()) != null) {

storage/src/main/java/tech/pegasys/teku/storage/client/RecentChainData.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -778,6 +778,10 @@ public void setBlockTimelinessIfEmpty(final SignedBeaconBlock block) {
778778
lateBlockReorgLogic.setBlockTimelinessFromArrivalTime(block, store.getTimeInMillis());
779779
}
780780

781+
public boolean isBlockLate(final Bytes32 root) {
782+
return lateBlockReorgLogic.isBlockLate(root);
783+
}
784+
781785
public Optional<UInt64> getCustodyGroupCount() {
782786
return store.getCustodyGroupCount();
783787
}

0 commit comments

Comments
 (0)