Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Initial cut of late block reorg #7789

Closed
wants to merge 21 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
38de6dc
Initial cut of late block reorg
rolfyone Dec 6, 2023
23498e0
added a dev option to override validator_is_proposer
rolfyone Dec 6, 2023
2db1396
Merge branch 'master' into 6595-fcu
rolfyone Dec 6, 2023
ed9d735
Merge remote-tracking branch 'upstream/master' into 6595-fcu
rolfyone Dec 8, 2023
64dbecb
Added a labelled counter and some key points to count.
rolfyone Dec 8, 2023
23e6b55
Update description and label of metric.
rolfyone Dec 8, 2023
2469853
Merge branch 'master' into 6595-fcu
rolfyone Dec 8, 2023
bd33b57
Merge remote-tracking branch 'upstream/master' into 6595-fcu
rolfyone Dec 8, 2023
ec558a5
build block from parent when appropriate
rolfyone Dec 9, 2023
0e40800
Merge remote-tracking branch 'upstream/master' into 6595-fcu
rolfyone Dec 11, 2023
8c84d36
Merge remote-tracking branch 'upstream/master' into 6595-fcu
rolfyone Dec 11, 2023
38cd0d3
split out computation of thresholds
rolfyone Dec 13, 2023
e3f8048
Merge remote-tracking branch 'upstream/master' into 6595-fcu
rolfyone Dec 13, 2023
235d40e
renamed newSlot, rework CombinedChainDataclient a little to be more c…
rolfyone Dec 14, 2023
2d0868b
recompute payload attributes if overriding the head block
rolfyone Dec 19, 2023
dad3e25
Merge remote-tracking branch 'upstream/master' into 6595-fcu
rolfyone Jan 25, 2024
d3eadcb
merge cleanup
rolfyone Jan 25, 2024
e666d74
Merge remote-tracking branch 'upstream/master' into 6595-fcu
rolfyone Jan 31, 2024
3206a85
Merge remote-tracking branch 'upstream/master' into 6595-fcu
rolfyone Feb 1, 2024
d955eb8
Merge branch 'master' into 6595-fcu
rolfyone Feb 6, 2024
6c6a0a8
Merge remote-tracking branch 'upstream/master' into 6595-fcu
rolfyone Feb 19, 2024
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 @@ -49,12 +49,16 @@ public class Eth2NetworkConfiguration {
private static final int DEFAULT_STARTUP_TARGET_PEER_COUNT = 5;
private static final int DEFAULT_STARTUP_TIMEOUT_SECONDS = 30;

public static final boolean DEFAULT_FORK_CHOICE_UPDATE_HEAD_ON_BLOCK_IMPORT_ENABLED = false;

public static final boolean DEFAULT_FORK_CHOICE_LATE_BLOCK_REORG_ENABLED = false;

public static final boolean DEFAULT_FORK_CHOICE_UPDATED_ALWAYS_SEND_PAYLOAD_ATTRIBUTES = false;

public static final boolean DEFAULT_ALLOW_SYNC_OUTSIDE_WEAK_SUBJECTIVITY_PERIOD = false;

public static final boolean DEFAULT_FORK_CHOICE_VALIDATOR_IS_PROPOSER_ALWAYS_ENABLED = false;

public static final int DEFAULT_ASYNC_P2P_MAX_THREADS = 10;

public static final int DEFAULT_ASYNC_P2P_MAX_QUEUE = DEFAULT_MAX_QUEUE_SIZE;
Expand Down Expand Up @@ -99,6 +103,8 @@ public class Eth2NetworkConfiguration {
private final boolean forkChoiceLateBlockReorgEnabled;
private final boolean forkChoiceUpdatedAlwaysSendPayloadAttributes;

private final boolean forkChoiceValidatorIsProposerAlwaysEnabled;

private Eth2NetworkConfiguration(
final Spec spec,
final String constants,
Expand All @@ -123,6 +129,7 @@ private Eth2NetworkConfiguration(
final int asyncBeaconChainMaxThreads,
final int asyncBeaconChainMaxQueue,
final boolean forkChoiceLateBlockReorgEnabled,
final boolean forkChoiceValidatorIsProposerAlwaysEnabled,
final boolean forkChoiceUpdatedAlwaysSendPayloadAttributes) {
this.spec = spec;
this.constants = constants;
Expand Down Expand Up @@ -150,6 +157,7 @@ private Eth2NetworkConfiguration(
this.asyncBeaconChainMaxThreads = asyncBeaconChainMaxThreads;
this.asyncBeaconChainMaxQueue = asyncBeaconChainMaxQueue;
this.forkChoiceLateBlockReorgEnabled = forkChoiceLateBlockReorgEnabled;
this.forkChoiceValidatorIsProposerAlwaysEnabled = forkChoiceValidatorIsProposerAlwaysEnabled;
this.forkChoiceUpdatedAlwaysSendPayloadAttributes =
forkChoiceUpdatedAlwaysSendPayloadAttributes;
}
Expand Down Expand Up @@ -261,6 +269,10 @@ public boolean isForkChoiceUpdatedAlwaysSendPayloadAttributes() {
return forkChoiceUpdatedAlwaysSendPayloadAttributes;
}

public boolean isForkChoiceValidatorIsProposerAlwaysEnabled() {
return forkChoiceValidatorIsProposerAlwaysEnabled;
}

@Override
public String toString() {
return constants;
Expand Down Expand Up @@ -298,6 +310,8 @@ public static class Builder {
private boolean forkChoiceLateBlockReorgEnabled = DEFAULT_FORK_CHOICE_LATE_BLOCK_REORG_ENABLED;
private boolean forkChoiceUpdatedAlwaysSendPayloadAttributes =
DEFAULT_FORK_CHOICE_UPDATED_ALWAYS_SEND_PAYLOAD_ATTRIBUTES;
private boolean forkChoiceValidatorIsProposerAlwaysEnabled =
DEFAULT_FORK_CHOICE_VALIDATOR_IS_PROPOSER_ALWAYS_ENABLED;

public void spec(Spec spec) {
this.spec = spec;
Expand Down Expand Up @@ -380,6 +394,7 @@ public Eth2NetworkConfiguration build() {
asyncBeaconChainMaxThreads,
asyncBeaconChainMaxQueue,
forkChoiceLateBlockReorgEnabled,
forkChoiceValidatorIsProposerAlwaysEnabled,
forkChoiceUpdatedAlwaysSendPayloadAttributes);
}

Expand Down Expand Up @@ -855,5 +870,11 @@ public Builder forkChoiceUpdatedAlwaysSendPayloadAttributes(
forkChoiceUpdatedAlwaysSendPayloadAttributes;
return this;
}

public Builder forkChoiceValidatorIsProposerAlwaysEnabled(
boolean forkChoiceValidatorIsProposerAlwaysEnabled) {
this.forkChoiceValidatorIsProposerAlwaysEnabled = forkChoiceValidatorIsProposerAlwaysEnabled;
return this;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -758,10 +758,9 @@ private void reportInvalidBlock(final SignedBeaconBlock block, final BlockImport

private void notifyForkChoiceUpdatedAndOptimisticSyncingChanged(
final Optional<UInt64> proposingSlot) {
final ForkChoiceState forkChoiceState = forkChoiceStateProvider.getForkChoiceStateSync();
final ForkChoiceState forkChoiceState = forkChoiceStateProvider.getForkChoiceStateSync(false);

forkChoiceNotifier.onForkChoiceUpdated(forkChoiceState, proposingSlot);
getProposerHeadSelectedCounter.labels("fork_choice").inc();
notifyForkChoiceUpdated(forkChoiceState, proposingSlot);

if (optimisticSyncing
.map(oldValue -> !oldValue.equals(forkChoiceState.isHeadOptimistic()))
Expand All @@ -772,6 +771,58 @@ private void notifyForkChoiceUpdatedAndOptimisticSyncingChanged(
}
}

@SuppressWarnings("AlreadyChecked")
private void notifyForkChoiceUpdated(
final ForkChoiceState forkChoiceState, final Optional<UInt64> proposingSlot) {
if (!forkChoiceLateBlockReorgEnabled) {
forkChoiceNotifier.onForkChoiceUpdated(forkChoiceState, proposingSlot);
return;
}
// late block reorg logic
final boolean shouldOverrideForkChoice =
recentChainData.shouldOverrideForkChoiceUpdate(forkChoiceState.getHeadBlockRoot());
final Optional<Bytes32> maybeProposerHead =
proposingSlot.map(
slot ->
recentChainData.getProposerHead(
forkChoiceState.getHeadBlockRoot(), proposingSlot.get()));

if (!shouldOverrideForkChoice) {
LOG.debug(
"Fork Choice Notify shouldOverrideForkChoice {}, getProposerHead {}, chose fork choice head {}:({})",
() -> shouldOverrideForkChoice,
() -> maybeProposerHead,
forkChoiceState::getHeadBlockRoot,
forkChoiceState::getHeadBlockSlot);
forkChoiceNotifier.onForkChoiceUpdated(forkChoiceState, proposingSlot);
maybeProposerHead.ifPresent(
proposerHead -> getProposerHeadSelectedCounter.labels("fork_choice").inc());
return;
} else if (maybeProposerHead.isPresent()
&& maybeProposerHead.get().equals(forkChoiceState.getHeadBlockRoot())) {
LOG.debug(
"Fork Choice Notify shouldOverrideForkChoice {}, getProposerHead {}, chose head {}:({})",
() -> shouldOverrideForkChoice,
() -> maybeProposerHead,
forkChoiceState::getHeadBlockRoot,
forkChoiceState::getHeadBlockSlot);
getProposerHeadSelectedCounter.labels("head").inc();
forkChoiceNotifier.onForkChoiceUpdated(forkChoiceState, proposingSlot);
return;
}
final ForkChoiceState parentForkChoiceState =
forkChoiceStateProvider.getForkChoiceStateSync(true);
LOG.debug(
"Fork Choice Notify shouldOverrideForkChoice {}, fork choice head {}:({}), chose parent {}:({})",
() -> shouldOverrideForkChoice,
forkChoiceState::getHeadBlockRoot,
forkChoiceState::getHeadBlockSlot,
parentForkChoiceState::getHeadBlockRoot,
parentForkChoiceState::getHeadBlockSlot);
getProposerHeadSelectedCounter.labels("parent").inc();
forkChoiceNotifier.onForkChoiceUpdated(parentForkChoiceState, proposingSlot);
}

private void applyVotesFromBlock(
final ForkChoiceStrategy forkChoiceStrategy,
final UInt64 currentEpoch,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import tech.pegasys.teku.spec.Spec;
import tech.pegasys.teku.spec.config.SpecConfig;
import tech.pegasys.teku.spec.datastructures.execution.ExecutionPayloadContext;
import tech.pegasys.teku.spec.datastructures.state.beaconstate.BeaconState;
import tech.pegasys.teku.spec.executionlayer.ExecutionLayerChannel;
import tech.pegasys.teku.spec.executionlayer.ForkChoiceState;
import tech.pegasys.teku.statetransition.forkchoice.ForkChoiceUpdatedResultSubscriber.ForkChoiceUpdatedResultNotification;
Expand Down Expand Up @@ -171,7 +172,7 @@ private SafeFuture<Optional<ExecutionPayloadContext>> internalGetPayloadId(
.getForkChoiceStateAsync()
.thenCombine(
proposersDataManager.calculatePayloadBuildingAttributes(
blockSlot, inSync, localForkChoiceUpdateData, true),
blockSlot, inSync, localForkChoiceUpdateData, true, Optional.empty()),
(forkChoiceState, payloadBuildingAttributes) -> {
forkChoiceUpdateData =
localForkChoiceUpdateData
Expand Down Expand Up @@ -207,7 +208,7 @@ private void internalUpdatePreparableProposers() {
final UInt64 currentSlot = recentChainData.getCurrentSlot().orElse(SpecConfig.GENESIS_SLOT);

// Update payload attributes in case we now need to propose the next block
updatePayloadAttributes(currentSlot.plus(1));
updatePayloadAttributes(currentSlot.plus(1), Optional.empty());
}

private void internalForkChoiceUpdated(
Expand All @@ -223,7 +224,8 @@ private void internalForkChoiceUpdated(
final Optional<UInt64> attributesSlot =
proposingSlot.or(() -> recentChainData.getCurrentSlot().map(UInt64::increment));

attributesSlot.ifPresent(this::updatePayloadAttributes);
attributesSlot.ifPresent(
slot -> updatePayloadAttributesWithForkChoiceState(slot, Optional.of(forkChoiceState)));

sendForkChoiceUpdated();
}
Expand All @@ -234,7 +236,7 @@ private void internalAttestationsDue(final UInt64 slot) {
LOG.debug("internalAttestationsDue slot {}", slot);

// Assume `slot` is empty and check if we need to prepare to propose in the next slot
updatePayloadAttributes(slot.plus(1));
updatePayloadAttributes(slot.plus(1), Optional.empty());
}

private void sendForkChoiceUpdated() {
Expand All @@ -251,14 +253,38 @@ private void sendForkChoiceUpdated() {
forkChoiceUpdatedResultFuture)));
}

private void updatePayloadAttributes(final UInt64 blockSlot) {
private void updatePayloadAttributesWithForkChoiceState(
final UInt64 blockSlot, final Optional<ForkChoiceState> maybeForkChoiceState) {
final SafeFuture<Optional<BeaconState>> maybeFutureState;
final Optional<Bytes32> maybeRoot = recentChainData.getBestBlockRoot();
if (maybeForkChoiceState.isPresent()
&& maybeRoot.isPresent()
&& !maybeRoot.get().equals(maybeForkChoiceState.get().getHeadBlockRoot())) {
// if we know the root we're building on isn't the head root, supply the state
maybeFutureState =
recentChainData.retrieveBlockState(maybeForkChoiceState.get().getHeadBlockRoot());
} else {
maybeFutureState = SafeFuture.completedFuture(Optional.empty());
}
maybeFutureState
.thenAccept(maybeState -> updatePayloadAttributes(blockSlot, maybeState))
.finish(
error ->
LOG.debug(
"Failed to retrieve state to calculate payload attributes for slot {}",
blockSlot,
error));
}

private void updatePayloadAttributes(
final UInt64 blockSlot, final Optional<BeaconState> maybeState) {
LOG.debug("updatePayloadAttributes blockSlot {}", blockSlot);

forkChoiceUpdateData
.withPayloadBuildingAttributesAsync(
() ->
proposersDataManager.calculatePayloadBuildingAttributes(
blockSlot, inSync, forkChoiceUpdateData, false),
blockSlot, inSync, forkChoiceUpdateData, false, maybeState),
eventThread)
.thenAccept(
newForkChoiceUpdateData -> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,21 +28,22 @@ public ForkChoiceStateProvider(EventThread forkChoiceExecutor, RecentChainData r
}

public SafeFuture<ForkChoiceState> getForkChoiceStateAsync() {
return forkChoiceExecutor.execute(this::internalGetForkChoiceState);
return forkChoiceExecutor.execute(() -> internalGetForkChoiceState(false));
}

public ForkChoiceState getForkChoiceStateSync() {
public ForkChoiceState getForkChoiceStateSync(final boolean isForkChoiceOverrideHead) {
forkChoiceExecutor.checkOnEventThread();
return internalGetForkChoiceState();
return internalGetForkChoiceState(isForkChoiceOverrideHead);
}

private ForkChoiceState internalGetForkChoiceState() {
private ForkChoiceState internalGetForkChoiceState(final boolean isForkChoiceOverrideHead) {
return recentChainData
.getUpdatableForkChoiceStrategy()
.orElseThrow()
.getForkChoiceState(
recentChainData.getCurrentEpoch().orElseThrow(),
recentChainData.getJustifiedCheckpoint().orElseThrow(),
recentChainData.getFinalizedCheckpoint().orElseThrow());
recentChainData.getFinalizedCheckpoint().orElseThrow(),
isForkChoiceOverrideHead);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@
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.spec.logic.common.statetransition.exceptions.EpochProcessingException;
import tech.pegasys.teku.spec.logic.common.statetransition.exceptions.SlotProcessingException;
import tech.pegasys.teku.storage.client.ChainHead;
import tech.pegasys.teku.storage.client.RecentChainData;

Expand Down Expand Up @@ -189,7 +191,8 @@ public SafeFuture<Optional<PayloadBuildingAttributes>> calculatePayloadBuildingA
final UInt64 blockSlot,
final boolean inSync,
final ForkChoiceUpdateData forkChoiceUpdateData,
final boolean mandatory) {
final boolean mandatory,
final Optional<BeaconState> maybeState) {
eventThread.checkOnEventThread();
if (!inSync) {
// We don't produce blocks while syncing so don't bother preparing the payload
Expand All @@ -207,11 +210,21 @@ public SafeFuture<Optional<PayloadBuildingAttributes>> calculatePayloadBuildingA
final UInt64 epoch = spec.computeEpochAtSlot(blockSlot);
final ForkChoiceState forkChoiceState = forkChoiceUpdateData.getForkChoiceState();
final Bytes32 currentHeadBlockRoot = forkChoiceState.getHeadBlockRoot();
if (maybeState.isPresent()) {
try {
final BeaconState preState = spec.processSlots(maybeState.get(), blockSlot);
return SafeFuture.completedFuture(
calculatePayloadBuildingAttributes(
currentHeadBlockRoot, blockSlot, epoch, Optional.of(preState), mandatory));
} catch (SlotProcessingException | EpochProcessingException e) {
LOG.error("Failed to process slots to get state current at blockSlot {}", blockSlot, e);
}
}
return getStateInEpoch(epoch)
.thenApplyAsync(
maybeState ->
maybeStateInEpoch ->
calculatePayloadBuildingAttributes(
currentHeadBlockRoot, blockSlot, epoch, maybeState, mandatory),
currentHeadBlockRoot, blockSlot, epoch, maybeStateInEpoch, mandatory),
eventThread);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -331,7 +331,7 @@ void onForkChoiceUpdated_shouldNotSendNotificationOfOutOfOrderPayloadBuildingAtt
return deferredResponseA;
})
.when(proposersDataManager)
.calculatePayloadBuildingAttributes(any(), anyBoolean(), any(), anyBoolean());
.calculatePayloadBuildingAttributes(any(), anyBoolean(), any(), anyBoolean(), any());

notifyForkChoiceUpdated(forkChoiceState); // calculate attributes for slot 2

Expand All @@ -341,7 +341,7 @@ void onForkChoiceUpdated_shouldNotSendNotificationOfOutOfOrderPayloadBuildingAtt
// forward to real method call
doAnswer(InvocationOnMock::callRealMethod)
.when(proposersDataManager)
.calculatePayloadBuildingAttributes(any(), anyBoolean(), any(), anyBoolean());
.calculatePayloadBuildingAttributes(any(), anyBoolean(), any(), anyBoolean(), any());

storageSystem
.chainUpdater()
Expand Down Expand Up @@ -441,7 +441,7 @@ void onForkChoiceUpdated_shouldNotSendNotificationWithOldPayloadBuildingAttribut
return deferredResponseA;
})
.when(proposersDataManager)
.calculatePayloadBuildingAttributes(any(), anyBoolean(), any(), anyBoolean());
.calculatePayloadBuildingAttributes(any(), anyBoolean(), any(), anyBoolean(), any());

notifyForkChoiceUpdated(forkChoiceStateSlot3); // calculate attributes for slot 2

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -184,11 +184,13 @@ public List<ProtoNodeData> getChainHeads(final boolean includeNonViableHeads) {
public ForkChoiceState getForkChoiceState(
final UInt64 currentEpoch,
final Checkpoint justifiedCheckpoint,
final Checkpoint finalizedCheckpoint) {
final Checkpoint finalizedCheckpoint,
final boolean isForkChoiceOverrideHead) {
protoArrayLock.readLock().lock();
try {
final ProtoNode headNode =
protoArray.findOptimisticHead(currentEpoch, justifiedCheckpoint, finalizedCheckpoint);
final Optional<ProtoNode> maybeParentNode = protoArray.getProtoNode(headNode.getParentRoot());
final UInt64 headExecutionBlockNumber = headNode.getExecutionBlockNumber();
final Bytes32 headExecutionBlockHash = headNode.getExecutionBlockHash();
final Bytes32 justifiedExecutionHash =
Expand All @@ -201,6 +203,17 @@ public ForkChoiceState getForkChoiceState(
.getProtoNode(finalizedCheckpoint.getRoot())
.map(ProtoNode::getExecutionBlockHash)
.orElse(Bytes32.ZERO);
if (isForkChoiceOverrideHead && maybeParentNode.isPresent()) {
final ProtoNode parentNode = maybeParentNode.get();
return new ForkChoiceState(
parentNode.getBlockRoot(),
parentNode.getBlockSlot(),
parentNode.getExecutionBlockNumber(),
parentNode.getExecutionBlockHash(),
justifiedExecutionHash,
finalizedExecutionHash,
headNode.isOptimistic() || !protoArray.nodeIsViableForHead(headNode));
}
return new ForkChoiceState(
headNode.getBlockRoot(),
headNode.getBlockSlot(),
Expand Down Expand Up @@ -289,10 +302,10 @@ public Optional<UInt64> executionBlockNumber(final Bytes32 blockRoot) {
}

@Override
public Optional<Bytes32> executionBlockHash(final Bytes32 blockRoot) {
public Optional<Bytes32> executionBlockHash(final Bytes32 beaconBlockRoot) {
protoArrayLock.readLock().lock();
try {
return getProtoNode(blockRoot).map(ProtoNode::getExecutionBlockHash);
return getProtoNode(beaconBlockRoot).map(ProtoNode::getExecutionBlockHash);
} finally {
protoArrayLock.readLock().unlock();
}
Expand Down
Loading
Loading