Implement eth_getBlockByNumber while downloading state. (#2481)

Introduces a dependency from eth_getBlockByNumber onto the Synchronizer, in order to determine download state, and current most recent block that we have state for. JsonRPC test classes also were refactored to provide a Synchronizer.
Signed-off-by: Justin Florentine <justin.florentine@consensys.net>
pull/2485/head
Justin Florentine 3 years ago committed by GitHub
parent 69223b8e75
commit 55cde86507
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 61
      ethereum/api/src/integration-test/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonRpcTestMethodsFactory.java
  2. 162
      ethereum/api/src/integration-test/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/EthGetBlockByNumberLatestDesyncIntegrationTest.java
  3. 40
      ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetBlockByNumber.java
  4. 2
      ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/EthJsonRpcMethods.java
  5. 22
      ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonRpcHttpServiceTest.java
  6. 68
      ethereum/retesteth/src/main/java/org/hyperledger/besu/ethereum/retesteth/DummySynchronizer.java
  7. 7
      ethereum/retesteth/src/main/java/org/hyperledger/besu/ethereum/retesteth/RetestethService.java

@ -62,30 +62,67 @@ public class JsonRpcTestMethodsFactory {
private static final BigInteger NETWORK_ID = BigInteger.valueOf(123);
private final BlockchainImporter importer;
private final MutableBlockchain blockchain;
private final WorldStateArchive stateArchive;
private final ProtocolContext context;
private final BlockchainQueries blockchainQueries;
private final Synchronizer synchronizer;
public JsonRpcTestMethodsFactory(final BlockchainImporter importer) {
this.importer = importer;
}
public Map<String, JsonRpcMethod> methods() {
final WorldStateArchive stateArchive = createInMemoryWorldStateArchive();
importer.getGenesisState().writeStateTo(stateArchive.getMutable());
final MutableBlockchain blockchain = createInMemoryBlockchain(importer.getGenesisBlock());
final ProtocolContext context = new ProtocolContext(blockchain, stateArchive, null);
this.blockchain = createInMemoryBlockchain(importer.getGenesisBlock());
this.stateArchive = createInMemoryWorldStateArchive();
this.importer.getGenesisState().writeStateTo(stateArchive.getMutable());
this.context = new ProtocolContext(blockchain, stateArchive, null);
final ProtocolSchedule protocolSchedule = importer.getProtocolSchedule();
this.synchronizer = mock(Synchronizer.class);
for (final Block block : importer.getBlocks()) {
final ProtocolSpec protocolSpec =
protocolSchedule.getByBlockNumber(block.getHeader().getNumber());
final BlockImporter blockImporter = protocolSpec.getBlockImporter();
blockImporter.importBlock(context, block, HeaderValidationMode.FULL);
}
this.blockchainQueries = new BlockchainQueries(blockchain, stateArchive);
}
final BlockchainQueries blockchainQueries = new BlockchainQueries(blockchain, stateArchive);
public JsonRpcTestMethodsFactory(
final BlockchainImporter importer,
final MutableBlockchain blockchain,
final WorldStateArchive stateArchive,
final ProtocolContext context) {
this.importer = importer;
this.blockchain = blockchain;
this.stateArchive = stateArchive;
this.context = context;
this.blockchainQueries = new BlockchainQueries(blockchain, stateArchive);
this.synchronizer = mock(Synchronizer.class);
}
final Synchronizer synchronizer = mock(Synchronizer.class);
public JsonRpcTestMethodsFactory(
final BlockchainImporter importer,
final MutableBlockchain blockchain,
final WorldStateArchive stateArchive,
final ProtocolContext context,
final Synchronizer synchronizer) {
this.importer = importer;
this.blockchain = blockchain;
this.stateArchive = stateArchive;
this.context = context;
this.synchronizer = synchronizer;
this.blockchainQueries = new BlockchainQueries(blockchain, stateArchive);
}
public BlockchainQueries getBlockchainQueries() {
return blockchainQueries;
}
public WorldStateArchive getStateArchive() {
return stateArchive;
}
public Map<String, JsonRpcMethod> methods() {
final P2PNetwork peerDiscovery = mock(P2PNetwork.class);
final EthPeers ethPeers = mock(EthPeers.class);
final TransactionPool transactionPool = mock(TransactionPool.class);
@ -126,7 +163,7 @@ public class JsonRpcTestMethodsFactory {
peerDiscovery,
blockchainQueries,
synchronizer,
protocolSchedule,
importer.getProtocolSchedule(),
filterManager,
transactionPool,
miningCoordinator,

@ -0,0 +1,162 @@
/*
* Copyright ConsenSys AG.
*
* 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.
*
* SPDX-License-Identifier: Apache-2.0
*/
package org.hyperledger.besu.ethereum.api.jsonrpc.methods;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import org.hyperledger.besu.ethereum.ProtocolContext;
import org.hyperledger.besu.ethereum.api.jsonrpc.BlockchainImporter;
import org.hyperledger.besu.ethereum.api.jsonrpc.JsonRpcTestMethodsFactory;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequest;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.JsonRpcMethod;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.BlockResult;
import org.hyperledger.besu.ethereum.chain.MutableBlockchain;
import org.hyperledger.besu.ethereum.core.Block;
import org.hyperledger.besu.ethereum.core.BlockImporter;
import org.hyperledger.besu.ethereum.core.Hash;
import org.hyperledger.besu.ethereum.core.InMemoryKeyValueStorageProvider;
import org.hyperledger.besu.ethereum.core.Synchronizer;
import org.hyperledger.besu.ethereum.mainnet.HeaderValidationMode;
import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule;
import org.hyperledger.besu.ethereum.mainnet.ProtocolSpec;
import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive;
import org.hyperledger.besu.plugin.data.SyncStatus;
import org.hyperledger.besu.testutil.BlockTestUtil;
import java.util.Optional;
import com.google.common.base.Charsets;
import com.google.common.io.Resources;
import org.assertj.core.api.Assertions;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.junit.MockitoJUnitRunner;
@RunWith(MockitoJUnitRunner.class)
public class EthGetBlockByNumberLatestDesyncIntegrationTest {
private static JsonRpcTestMethodsFactory methodsFactorySynced;
private static JsonRpcTestMethodsFactory methodsFactoryDesynced;
private static JsonRpcTestMethodsFactory methodsFactoryMidDownload;
private static final long ARBITRARY_SYNC_BLOCK = 4L;
@BeforeClass
public static void setUpOnce() throws Exception {
final String genesisJson =
Resources.toString(BlockTestUtil.getTestGenesisUrl(), Charsets.UTF_8);
BlockchainImporter importer =
new BlockchainImporter(BlockTestUtil.getTestBlockchainUrl(), genesisJson);
MutableBlockchain chain =
InMemoryKeyValueStorageProvider.createInMemoryBlockchain(importer.getGenesisBlock());
WorldStateArchive state = InMemoryKeyValueStorageProvider.createInMemoryWorldStateArchive();
importer.getGenesisState().writeStateTo(state.getMutable());
ProtocolContext context = new ProtocolContext(chain, state, null);
for (final Block block : importer.getBlocks()) {
final ProtocolSchedule protocolSchedule = importer.getProtocolSchedule();
final ProtocolSpec protocolSpec =
protocolSchedule.getByBlockNumber(block.getHeader().getNumber());
final BlockImporter blockImporter = protocolSpec.getBlockImporter();
blockImporter.importBlock(context, block, HeaderValidationMode.FULL);
}
methodsFactorySynced = new JsonRpcTestMethodsFactory(importer, chain, state, context);
WorldStateArchive unsynced = mock(WorldStateArchive.class);
when(unsynced.isWorldStateAvailable(any(Hash.class), any(Hash.class))).thenReturn(false);
methodsFactoryDesynced = new JsonRpcTestMethodsFactory(importer, chain, unsynced, context);
WorldStateArchive midSync = mock(WorldStateArchive.class);
when(midSync.isWorldStateAvailable(any(Hash.class), any(Hash.class))).thenReturn(true);
Synchronizer synchronizer = mock(Synchronizer.class);
SyncStatus status = mock(SyncStatus.class);
when(status.getCurrentBlock())
.thenReturn(ARBITRARY_SYNC_BLOCK); // random choice for current sync state.
when(synchronizer.getSyncStatus()).thenReturn(Optional.of(status));
methodsFactoryMidDownload =
new JsonRpcTestMethodsFactory(importer, chain, midSync, context, synchronizer);
}
@Test
public void shouldReturnHeadIfFullySynced() {
JsonRpcMethod ethGetBlockNumber = methodsFactorySynced.methods().get("eth_getBlockByNumber");
Object[] params = {"latest", false};
JsonRpcRequestContext ctx =
new JsonRpcRequestContext(new JsonRpcRequest("2.0", "eth_getBlockByNumber", params));
Assertions.assertThatNoException()
.isThrownBy(
() -> {
final JsonRpcResponse resp = ethGetBlockNumber.response(ctx);
assertThat(resp).isNotNull();
assertThat(resp).isInstanceOf(JsonRpcSuccessResponse.class);
Object r = ((JsonRpcSuccessResponse) resp).getResult();
assertThat(r).isInstanceOf(BlockResult.class);
BlockResult br = (BlockResult) r;
assertThat(br.getNumber()).isEqualTo("0x20");
// assert on the state existing?
});
}
@Test
public void shouldReturnGenesisIfNotSynced() {
JsonRpcMethod ethGetBlockNumber = methodsFactoryDesynced.methods().get("eth_getBlockByNumber");
Object[] params = {"latest", false};
JsonRpcRequestContext ctx =
new JsonRpcRequestContext(new JsonRpcRequest("2.0", "eth_getBlockByNumber", params));
Assertions.assertThatNoException()
.isThrownBy(
() -> {
final JsonRpcResponse resp = ethGetBlockNumber.response(ctx);
assertThat(resp).isNotNull();
assertThat(resp).isInstanceOf(JsonRpcSuccessResponse.class);
Object r = ((JsonRpcSuccessResponse) resp).getResult();
assertThat(r).isInstanceOf(BlockResult.class);
BlockResult br = (BlockResult) r;
assertThat(br.getNumber()).isEqualTo("0x0");
});
}
@Test
public void shouldReturnCurrentSyncedIfDownloadingWorldState() {
JsonRpcMethod ethGetBlockNumber =
methodsFactoryMidDownload.methods().get("eth_getBlockByNumber");
Object[] params = {"latest", false};
JsonRpcRequestContext ctx =
new JsonRpcRequestContext(new JsonRpcRequest("2.0", "eth_getBlockByNumber", params));
Assertions.assertThatNoException()
.isThrownBy(
() -> {
final JsonRpcResponse resp = ethGetBlockNumber.response(ctx);
assertThat(resp).isNotNull();
assertThat(resp).isInstanceOf(JsonRpcSuccessResponse.class);
Object r = ((JsonRpcSuccessResponse) resp).getResult();
assertThat(r).isInstanceOf(BlockResult.class);
BlockResult br = (BlockResult) r;
assertThat(br.getNumber()).isEqualTo("0x4");
});
}
}

@ -20,27 +20,39 @@ import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.BlockParame
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.BlockResult;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.BlockResultFactory;
import org.hyperledger.besu.ethereum.api.query.BlockchainQueries;
import org.hyperledger.besu.ethereum.chain.Blockchain;
import org.hyperledger.besu.ethereum.core.BlockHeader;
import org.hyperledger.besu.ethereum.core.Hash;
import org.hyperledger.besu.ethereum.core.Synchronizer;
import java.util.function.Supplier;
import com.google.common.base.Suppliers;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public class EthGetBlockByNumber extends AbstractBlockParameterMethod {
private final BlockResultFactory blockResult;
private final boolean includeCoinbase;
private static final Logger LOGGER = LogManager.getLogger();
private final Synchronizer synchronizer;
public EthGetBlockByNumber(
final BlockchainQueries blockchain, final BlockResultFactory blockResult) {
this(Suppliers.ofInstance(blockchain), blockResult, false);
final BlockchainQueries blockchain,
final BlockResultFactory blockResult,
final Synchronizer synchronizer) {
this(Suppliers.ofInstance(blockchain), blockResult, synchronizer, false);
}
public EthGetBlockByNumber(
final Supplier<BlockchainQueries> blockchain,
final BlockResultFactory blockResult,
final Synchronizer synchronizer,
final boolean includeCoinbase) {
super(blockchain);
this.blockResult = blockResult;
this.synchronizer = synchronizer;
this.includeCoinbase = includeCoinbase;
}
@ -64,6 +76,30 @@ public class EthGetBlockByNumber extends AbstractBlockParameterMethod {
return transactionHash(blockNumber);
}
@Override
protected Object latestResult(final JsonRpcRequestContext request) {
final long headBlockNumber = blockchainQueries.get().headBlockNumber();
Blockchain chain = blockchainQueries.get().getBlockchain();
BlockHeader headHeader = chain.getBlockHeader(headBlockNumber).orElse(null);
Hash block = headHeader.getHash();
Hash stateRoot = headHeader.getStateRoot();
if (blockchainQueries.get().getWorldStateArchive().isWorldStateAvailable(stateRoot, block)) {
if (this.synchronizer.getSyncStatus().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();
return resultByBlockNumber(request, headishBlock);
}
}
LOGGER.trace("no world state available for block {} returning genesis", headBlockNumber);
return resultByBlockNumber(
request, blockchainQueries.get().getBlockchain().getGenesisBlock().getHeader().getNumber());
}
private BlockResult transactionComplete(final long blockNumber) {
return getBlockchainQueries()
.blockByNumber(blockNumber)

@ -119,7 +119,7 @@ public class EthJsonRpcMethods extends ApiGroupJsonRpcMethods {
new EthBlockNumber(blockchainQueries),
new EthGetBalance(blockchainQueries),
new EthGetBlockByHash(blockchainQueries, blockResult),
new EthGetBlockByNumber(blockchainQueries, blockResult),
new EthGetBlockByNumber(blockchainQueries, blockResult, synchronizer),
new EthGetBlockTransactionCountByNumber(blockchainQueries),
new EthGetBlockTransactionCountByHash(blockchainQueries),
new EthCall(

@ -36,6 +36,7 @@ import org.hyperledger.besu.ethereum.core.Difficulty;
import org.hyperledger.besu.ethereum.core.Hash;
import org.hyperledger.besu.ethereum.core.Transaction;
import org.hyperledger.besu.ethereum.core.Wei;
import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive;
import org.hyperledger.besu.plugin.data.SyncStatus;
import java.math.BigInteger;
@ -1057,6 +1058,12 @@ public class JsonRpcHttpServiceTest extends JsonRpcHttpServiceTestBase {
blockWithMetadata(block);
when(blockchainQueries.headBlockNumber()).thenReturn(0L);
when(blockchainQueries.blockByNumber(eq(0L))).thenReturn(Optional.of(blockWithMetadata));
when(blockchainQueries.getBlockchain()).thenReturn(blockchain);
when(blockchain.getBlockHeader(blockchainQueries.headBlockNumber()))
.thenReturn(Optional.of(block.getHeader()));
WorldStateArchive state = mock(WorldStateArchive.class);
when(state.isWorldStateAvailable(any(Hash.class), any(Hash.class))).thenReturn(true);
when(blockchainQueries.getWorldStateArchive()).thenReturn(state);
try (final Response resp = client.newCall(buildPostRequest(body)).execute()) {
assertThat(resp.code()).isEqualTo(200);
@ -1079,6 +1086,19 @@ public class JsonRpcHttpServiceTest extends JsonRpcHttpServiceTestBase {
"{\"jsonrpc\":\"2.0\",\"id\":"
+ Json.encode(id)
+ ",\"method\":\"eth_getBlockByNumber\", \"params\": [\"pending\",true]}");
// Setup mocks to return a block
final BlockDataGenerator gen = new BlockDataGenerator();
final Block block = gen.genesisBlock();
final BlockWithMetadata<TransactionWithMetadata, Hash> blockWithMetadata =
blockWithMetadata(block);
when(blockchainQueries.headBlockNumber()).thenReturn(0L);
when(blockchainQueries.blockByNumber(eq(0L))).thenReturn(Optional.of(blockWithMetadata));
when(blockchainQueries.getBlockchain()).thenReturn(blockchain);
when(blockchain.getBlockHeader(blockchainQueries.headBlockNumber()))
.thenReturn(Optional.of(block.getHeader()));
WorldStateArchive state = mock(WorldStateArchive.class);
when(state.isWorldStateAvailable(any(Hash.class), any(Hash.class))).thenReturn(true);
when(blockchainQueries.getWorldStateArchive()).thenReturn(state);
try (final Response resp = client.newCall(buildPostRequest(body)).execute()) {
assertThat(resp.code()).isEqualTo(200);
@ -1088,7 +1108,7 @@ public class JsonRpcHttpServiceTest extends JsonRpcHttpServiceTestBase {
testHelper.assertValidJsonRpcResult(json, id);
// Check result
final JsonObject result = json.getJsonObject("result");
assertThat(result).isNull();
verifyBlockResult(block, blockWithMetadata.getTotalDifficulty(), result, false);
}
}

@ -0,0 +1,68 @@
/*
* Copyright ConsenSys AG.
*
* 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.
*
* SPDX-License-Identifier: Apache-2.0
*/
package org.hyperledger.besu.ethereum.retesteth;
import org.hyperledger.besu.ethereum.core.Synchronizer;
import org.hyperledger.besu.plugin.data.SyncStatus;
import org.hyperledger.besu.plugin.services.BesuEvents;
import java.util.Optional;
/**
* Naive implementation of Synchronizer used by retesteth. Because retesteth is not implemented in
* the test module, it has no access to mockito. This class provides a minimum implementation needed
* to run RPC methods which may require a Synchronizer.
*/
public class DummySynchronizer implements Synchronizer {
@Override
public void start() {}
@Override
public void stop() {}
@Override
public void awaitStop() throws InterruptedException {}
@Override
public Optional<SyncStatus> getSyncStatus() {
return Optional.empty();
}
@Override
public long subscribeSyncStatus(final BesuEvents.SyncStatusListener listener) {
return 0;
}
@Override
public boolean unsubscribeSyncStatus(final long observerId) {
return false;
}
@Override
public long subscribeInSync(final InSyncListener listener) {
return 0;
}
@Override
public long subscribeInSync(final InSyncListener listener, final long syncTolerance) {
return 0;
}
@Override
public boolean unsubscribeInSync(final long listenerId) {
return false;
}
}

@ -30,6 +30,7 @@ import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.EthSendRawTran
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.JsonRpcMethod;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.Web3ClientVersion;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.BlockResultFactory;
import org.hyperledger.besu.ethereum.core.Synchronizer;
import org.hyperledger.besu.ethereum.retesteth.methods.TestGetLogHash;
import org.hyperledger.besu.ethereum.retesteth.methods.TestImportRawBlock;
import org.hyperledger.besu.ethereum.retesteth.methods.TestMineBlocks;
@ -63,13 +64,17 @@ public class RetestethService {
final BlockResultFactory blockResult = new BlockResultFactory();
final NatService natService = new NatService(Optional.empty());
// Synchronizer needed by RPC methods. Didn't wanna mock it, since this isn't the test module.
Synchronizer sync = new DummySynchronizer();
final Map<String, JsonRpcMethod> jsonRpcMethods =
mapOf(
new Web3ClientVersion(clientVersion),
new TestSetChainParams(retestethContext),
new TestImportRawBlock(retestethContext),
new EthBlockNumber(retestethContext::getBlockchainQueries, true),
new EthGetBlockByNumber(retestethContext::getBlockchainQueries, blockResult, true),
new EthGetBlockByNumber(
retestethContext::getBlockchainQueries, blockResult, sync, true),
new DebugAccountRange(retestethContext::getBlockchainQueries),
new EthGetBalance(retestethContext::getBlockchainQueries),
new EthGetBlockByHash(retestethContext::getBlockchainQueries, blockResult, true),

Loading…
Cancel
Save