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 @@ -26,7 +26,9 @@
import org.hyperledger.besu.ethereum.api.query.BlockchainQueries;
import org.hyperledger.besu.ethereum.core.BlockHeader;
import org.hyperledger.besu.ethereum.core.Synchronizer;
import org.hyperledger.besu.plugin.data.SyncStatus;

import java.util.Optional;
import java.util.function.Supplier;

import com.google.common.base.Suppliers;
Expand Down Expand Up @@ -96,10 +98,11 @@ protected Object latestResult(final JsonRpcRequestContext request) {
.get()
.getWorldStateArchive()
.isWorldStateAvailable(stateRoot, block)) {
if (this.synchronizer.getSyncStatus().isEmpty()) { // we are already in sync
final Optional<SyncStatus> maybeSyncStatus = this.synchronizer.getSyncStatus();
if (maybeSyncStatus.isEmpty()) { // we are already in sync
return resultByBlockNumber(request, headBlockNumber);
} else { // out of sync, return highest pulled block
long headishBlock = this.synchronizer.getSyncStatus().get().getCurrentBlock();
long headishBlock = maybeSyncStatus.get().getCurrentBlock();
return resultByBlockNumber(request, headishBlock);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
package org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatCode;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.hyperledger.besu.ethereum.core.InMemoryKeyValueStorageProvider.createInMemoryBlockchain;
import static org.mockito.Mockito.spy;
Expand All @@ -35,6 +36,7 @@
import org.hyperledger.besu.ethereum.core.Block;
import org.hyperledger.besu.ethereum.core.BlockDataGenerator;
import org.hyperledger.besu.ethereum.core.BlockHeader;
import org.hyperledger.besu.ethereum.core.DefaultSyncStatus;
import org.hyperledger.besu.ethereum.core.MiningConfiguration;
import org.hyperledger.besu.ethereum.core.Synchronizer;
import org.hyperledger.besu.ethereum.core.TransactionReceipt;
Expand All @@ -43,6 +45,8 @@
import org.hyperledger.besu.plugin.services.rpc.RpcResponseType;

import java.util.List;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicInteger;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
Expand Down Expand Up @@ -196,6 +200,32 @@ private void assertSuccessPos(final String tag, final long height) {
assertSuccess(tag, height);
}

@Test
public void latestDoesNotThrowWhenSyncStatusClearedBetweenChecks() {
// Simulates the race where sync completes between the isEmpty() check and the .get() call.
// Before the fix, getSyncStatus() was called twice — once for the isEmpty() guard and once to
// read getCurrentBlock(). If clearSyncTarget() fired on the sync thread between those two
// calls, the second call returned Optional.empty() and .get() threw NoSuchElementException,
// crashing the RPC handler for eth_getBlockByNumber("latest").
final long currentBlock = BLOCKCHAIN_LENGTH - 2;
final AtomicInteger callCount = new AtomicInteger(0);
when(synchronizer.getSyncStatus())
.thenAnswer(
invocation -> {
// First call: looks like sync is still in progress.
// Second call (if it were made): sync has just completed, no status.
if (callCount.incrementAndGet() == 1) {
return Optional.of(
new DefaultSyncStatus(
0, currentBlock, currentBlock + 10, Optional.empty(), Optional.empty()));
}
return Optional.empty();
});

assertThatCode(() -> method.response(requestWithParams("latest", "false")))
.doesNotThrowAnyException();
}

private JsonRpcRequestContext requestWithParams(final Object... params) {
return new JsonRpcRequestContext(new JsonRpcRequest(JSON_RPC_VERSION, ETH_METHOD, params));
}
Expand Down
Loading