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

fcU: Ignore newHead if it is an ancestor of chain head #4055

Merged
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
- Add TTD and DNS to Sepolia config [#4024](https://github.com/hyperledger/besu/pull/4024)
- Add terminal block hash and number to Ropsten genesis file [#4026](https://github.com/hyperledger/besu/pull/4026)
- Return `type` with value `0x0` when serializing legacy transactions [#4027](https://github.com/hyperledger/besu/pull/4027)
- Ignore `ForkchoiceUpdate` if `newHead` is an ancestor of the chain head [#4055](https://github.com/hyperledger/besu/pull/4055)

### Bug Fixes
- Fixed a snapsync issue that can sometimes block the healing step [#3920](https://github.com/hyperledger/besu/pull/3920)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,12 @@ public ForkchoiceResult updateForkChoice(
MutableBlockchain blockchain = protocolContext.getBlockchain();
final Optional<BlockHeader> newFinalized = blockchain.getBlockHeader(finalizedBlockHash);

if (newHead.getNumber() < blockchain.getChainHeadBlockNumber()
&& isDescendantOf(newHead, blockchain.getChainHeadHeader())) {
LOG.info("Ignoring update to old head");
return ForkchoiceResult.withIgnoreUpdateToOldHead(newHead);
}

final Optional<Hash> latestValid = getLatestValidAncestor(newHead);

Optional<BlockHeader> parentOfNewHead = blockchain.getBlockHeader(newHead.getParentHash());
Expand Down Expand Up @@ -434,7 +440,7 @@ boolean ancestorIsValidTerminalProofOfWork(final BlockHeader blockheader) {

boolean resp =
parent.filter(header -> isTerminalProofOfWorkBlock(header, protocolContext)).isPresent();
LOG.warn(
LOG.debug(
Copy link
Contributor Author

@daniellehrner daniellehrner Jul 5, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've changed this one to debug, because it does not look like anything that should cause a warning in the log

"checking ancestor {} is valid terminal PoW for {}\n {}",
parent.map(BlockHeader::toLogString).orElse("empty"),
blockheader.toLogString(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
*/
package org.hyperledger.besu.consensus.merge.blockcreation;

import static org.hyperledger.besu.consensus.merge.blockcreation.MergeMiningCoordinator.ForkchoiceResult.Status.IGNORE_UPDATE_TO_OLD_HEAD;
import static org.hyperledger.besu.consensus.merge.blockcreation.MergeMiningCoordinator.ForkchoiceResult.Status.VALID;

import org.hyperledger.besu.datatypes.Address;
Expand Down Expand Up @@ -69,7 +70,8 @@ class ForkchoiceResult {
public enum Status {
VALID,
INVALID,
INVALID_PAYLOAD_ATTRIBUTES
INVALID_PAYLOAD_ATTRIBUTES,
IGNORE_UPDATE_TO_OLD_HEAD
}

private final Status status;
Expand Down Expand Up @@ -101,6 +103,15 @@ public static ForkchoiceResult withFailure(
latestValid);
}

public static ForkchoiceResult withIgnoreUpdateToOldHead(final BlockHeader oldHead) {
return new ForkchoiceResult(
IGNORE_UPDATE_TO_OLD_HEAD,
Optional.empty(),
Optional.empty(),
Optional.empty(),
Optional.of(oldHead.getHash()));
}

public static ForkchoiceResult withResult(
final Optional<BlockHeader> newFinalized, final Optional<BlockHeader> newHead) {
return new ForkchoiceResult(VALID, Optional.empty(), newFinalized, newHead, Optional.empty());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -467,6 +467,37 @@ public void invalidPayloadShouldReturnErrorAndUpdateForkchoiceState() {
verify(mergeContext).setSafeBlock(lastFinalizedHeader);
}

@Test
public void forkchoiceUpdateShouldIgnoreAncestorOfChainHead() {
BlockHeader terminalHeader = terminalPowBlock();
sendNewPayloadAndForkchoiceUpdate(
new Block(terminalHeader, BlockBody.empty()), Optional.empty(), Hash.ZERO);

BlockHeader parentHeader = nextBlockHeader(terminalHeader);
Block parent = new Block(parentHeader, BlockBody.empty());
sendNewPayloadAndForkchoiceUpdate(parent, Optional.empty(), terminalHeader.getHash());

BlockHeader childHeader = nextBlockHeader(parentHeader);
Block child = new Block(childHeader, BlockBody.empty());
sendNewPayloadAndForkchoiceUpdate(child, Optional.empty(), parent.getHash());

ForkchoiceResult res =
coordinator.updateForkChoice(
parentHeader,
Hash.ZERO,
terminalHeader.getHash(),
Optional.of(
new PayloadAttributes(parentHeader.getTimestamp() + 1, Hash.ZERO, Address.ZERO)));

assertThat(res.getStatus()).isEqualTo(ForkchoiceResult.Status.IGNORE_UPDATE_TO_OLD_HEAD);
assertThat(res.getNewHead().isEmpty()).isTrue();
assertThat(res.getLatestValid().isPresent()).isTrue();
assertThat(res.getLatestValid().get()).isEqualTo(parentHeader.getHash());
assertThat(res.getErrorMessage().isEmpty()).isTrue();

verify(blockchain, never()).rewindToBlock(any());
}

private void sendNewPayloadAndForkchoiceUpdate(
final Block block, final Optional<BlockHeader> finalizedHeader, final Hash safeHash) {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ public JsonRpcResponse syncResponse(final JsonRpcRequestContext requestContext)
payloadAttributes.getSuggestedFeeRecipient())));

if (!result.isValid()) {
return handleForkchoiceError(requestId, result);
return handleNonValidForkchoiceUpdate(requestId, result);
}

// begin preparing a block if we have a non-empty payload attributes param
Expand Down Expand Up @@ -172,7 +172,7 @@ public JsonRpcResponse syncResponse(final JsonRpcRequestContext requestContext)
Optional.empty()));
}

private JsonRpcResponse handleForkchoiceError(
private JsonRpcResponse handleNonValidForkchoiceUpdate(
final Object requestId, final ForkchoiceResult result) {
JsonRpcResponse response;

Expand All @@ -184,14 +184,18 @@ private JsonRpcResponse handleForkchoiceError(
new JsonRpcSuccessResponse(
requestId,
new EngineUpdateForkchoiceResult(
INVALID,
latestValid.isPresent() ? latestValid.get() : null,
null,
result.getErrorMessage()));
INVALID, latestValid.orElse(null), null, result.getErrorMessage()));
break;
case INVALID_PAYLOAD_ATTRIBUTES:
response = new JsonRpcErrorResponse(requestId, JsonRpcError.INVALID_PAYLOAD_ATTRIBUTES);
break;
case IGNORE_UPDATE_TO_OLD_HEAD:
response =
new JsonRpcSuccessResponse(
requestId,
new EngineUpdateForkchoiceResult(
VALID, latestValid.orElse(null), null, result.getErrorMessage()));
break;
default:
throw new AssertionError(
"ForkchoiceResult.Status "
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import static org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.ExecutionEngineJsonRpcMethod.EngineStatus.VALID;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

Expand Down Expand Up @@ -339,6 +340,41 @@ public void shouldReturnInvalidForkchoiceStateIfSafeBlockIsNotAnAncestorOfNewHea
assertInvalidForkchoiceState(resp);
}

@Test
public void shouldIgnoreUpdateToOldHeadAndNotPreparePayload() {
BlockHeader mockHeader = new BlockHeaderTestFixture().baseFeePerGas(Wei.ONE).buildHeader();

when(blockchain.getBlockHeader(any())).thenReturn(Optional.of(mockHeader));
when(mergeCoordinator.latestValidAncestorDescendsFromTerminal(mockHeader)).thenReturn(true);

var ignoreOldHeadUpdateRes = ForkchoiceResult.withIgnoreUpdateToOldHead(mockHeader);
when(mergeCoordinator.updateForkChoice(any(), any(), any(), any()))
.thenReturn(ignoreOldHeadUpdateRes);

var payloadParams =
new EnginePayloadAttributesParameter(
String.valueOf(System.currentTimeMillis()),
Bytes32.fromHexStringLenient("0xDEADBEEF").toHexString(),
Address.ECREC.toString());

var resp =
(JsonRpcSuccessResponse)
resp(
new EngineForkchoiceUpdatedParameter(
mockHeader.getBlockHash(), Hash.ZERO, Hash.ZERO),
Optional.of(payloadParams));

var forkchoiceRes = (EngineUpdateForkchoiceResult) resp.getResult();

verify(mergeCoordinator, never()).preparePayload(any(), any(), any(), any());

assertThat(forkchoiceRes.getPayloadStatus().getStatus()).isEqualTo(EngineStatus.VALID);
assertThat(forkchoiceRes.getPayloadStatus().getError()).isNull();
assertThat(forkchoiceRes.getPayloadStatus().getLatestValidHashAsString())
.isEqualTo(mockHeader.getHash().toHexString());
assertThat(forkchoiceRes.getPayloadId()).isNull();
}

private EngineUpdateForkchoiceResult assertSuccessWithPayloadForForkchoiceResult(
final Optional<EnginePayloadAttributesParameter> payloadParam,
final ForkchoiceResult forkchoiceResult,
Expand Down