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

Implement eth_getBlockByNumber while downloading state. #2481

Merged
merged 21 commits into from
Jun 29, 2021
Merged
Show file tree
Hide file tree
Changes from 20 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 @@ -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);
Expand Down Expand Up @@ -126,7 +163,7 @@ public Map<String, JsonRpcMethod> methods() {
peerDiscovery,
blockchainQueries,
synchronizer,
protocolSchedule,
importer.getProtocolSchedule(),
filterManager,
transactionPool,
miningCoordinator,
Expand Down
Original file line number Diff line number Diff line change
@@ -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");
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,27 +20,39 @@
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;
}

Expand All @@ -64,6 +76,30 @@ protected Object resultByBlockNumber(
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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ protected Map<String, JsonRpcMethod> create() {
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(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
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;
Expand Down Expand Up @@ -1057,6 +1058,12 @@ public void getBlockByNumberForLatest() throws Exception {
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);
Expand All @@ -1079,6 +1086,19 @@ public void getBlockByNumberForPending() throws Exception {
"{\"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);
Expand All @@ -1088,7 +1108,7 @@ public void getBlockByNumberForPending() throws Exception {
testHelper.assertValidJsonRpcResult(json, id);
// Check result
final JsonObject result = json.getJsonObject("result");
assertThat(result).isNull();
verifyBlockResult(block, blockWithMetadata.getTotalDifficulty(), result, false);
}
}

Expand Down
Loading