eth_gasPrice based on median block cost (#1546) (#1563)

Update eth_gasPrice to return the median gas price of transactions in
the last several blocks (or whole chain if the chain is short).  If there
are no TXes return the old value.  Block window and percentile are CLI 
configurable.

Signed-off-by: Danno Ferrin <danno.ferrin@gmail.com>
pull/1581/head
Danno Ferrin 4 years ago committed by GitHub
parent e47032d4c3
commit b9deae46f7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      CHANGELOG.md
  2. 13
      besu/src/main/java/org/hyperledger/besu/RunnerBuilder.java
  3. 34
      besu/src/main/java/org/hyperledger/besu/cli/BesuCommand.java
  4. 1
      besu/src/test/java/org/hyperledger/besu/cli/CommandTestAbstract.java
  5. 5
      besu/src/test/resources/everything_config.toml
  6. 49
      ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/ApiConfiguration.java
  7. 12
      ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/graphql/GraphQLDataFetchers.java
  8. 28
      ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGasPrice.java
  9. 2
      ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/EthJsonRpcMethods.java
  10. 57
      ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/query/BlockchainQueries.java
  11. 8
      ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/graphql/AbstractEthGraphQLHttpServiceTest.java
  12. 141
      ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGasPriceTest.java
  13. 2
      ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/graphql/eth_gasPrice.json

@ -21,6 +21,7 @@
* Fix a bug on `eth_estimateGas` which returned `Internal error` instead of `Execution reverted` in case of reverted transaction. [\#1478](https://github.com/hyperledger/besu/pull/1478)
* Fixed a bug where Local Account Permissioning was being incorrectly enforced on block import/validation. [\#1510](https://github.com/hyperledger/besu/pull/1510)
* Removed duplicate files from zip and tar.gz distributions. [\#1566](https://github.com/hyperledger/besu/pull/1566)
* Add a more rational value to eth_gasPrice, based on a configurable percentile of prior block's transactions (default: median of last 100 blocks). [\#1563](https://github.com/hyperledger/besu/pull/1563)
## Deprecated

@ -24,6 +24,7 @@ import org.hyperledger.besu.cli.config.EthNetworkConfig;
import org.hyperledger.besu.controller.BesuController;
import org.hyperledger.besu.crypto.NodeKey;
import org.hyperledger.besu.ethereum.ProtocolContext;
import org.hyperledger.besu.ethereum.api.ApiConfiguration;
import org.hyperledger.besu.ethereum.api.graphql.GraphQLConfiguration;
import org.hyperledger.besu.ethereum.api.graphql.GraphQLDataFetcherContextImpl;
import org.hyperledger.besu.ethereum.api.graphql.GraphQLDataFetchers;
@ -155,6 +156,7 @@ public class RunnerBuilder {
private JsonRpcConfiguration jsonRpcConfiguration;
private GraphQLConfiguration graphQLConfiguration;
private WebSocketConfiguration webSocketConfiguration;
private ApiConfiguration apiConfiguration;
private Path dataDir;
private Optional<Path> pidPath = Optional.empty();
private MetricsConfiguration metricsConfiguration;
@ -275,6 +277,11 @@ public class RunnerBuilder {
return this;
}
public RunnerBuilder apiConfiguration(final ApiConfiguration apiConfiguration) {
this.apiConfiguration = apiConfiguration;
return this;
}
public RunnerBuilder permissioningConfiguration(
final PermissioningConfiguration permissioningConfiguration) {
this.permissioningConfiguration = Optional.of(permissioningConfiguration);
@ -435,7 +442,8 @@ public class RunnerBuilder {
context.getBlockchain(),
context.getWorldStateArchive(),
Optional.of(dataDir.resolve(CACHE_PATH)),
Optional.of(besuController.getProtocolManager().ethContext().getScheduler()));
Optional.of(besuController.getProtocolManager().ethContext().getScheduler()),
apiConfiguration);
final PrivacyParameters privacyParameters = besuController.getPrivacyParameters();
@ -795,7 +803,8 @@ public class RunnerBuilder {
Optional<PrivacyQueries> privacyQueries = Optional.empty();
if (privacyParameters.isEnabled()) {
final BlockchainQueries blockchainQueries =
new BlockchainQueries(blockchain, worldStateArchive);
new BlockchainQueries(
blockchain, worldStateArchive, Optional.empty(), Optional.empty(), apiConfiguration);
privacyQueries =
Optional.of(
new PrivacyQueries(

@ -82,6 +82,8 @@ import org.hyperledger.besu.crypto.KeyPairUtil;
import org.hyperledger.besu.crypto.NodeKey;
import org.hyperledger.besu.crypto.SECP256K1;
import org.hyperledger.besu.enclave.EnclaveFactory;
import org.hyperledger.besu.ethereum.api.ApiConfiguration;
import org.hyperledger.besu.ethereum.api.ImmutableApiConfiguration;
import org.hyperledger.besu.ethereum.api.graphql.GraphQLConfiguration;
import org.hyperledger.besu.ethereum.api.jsonrpc.JsonRpcConfiguration;
import org.hyperledger.besu.ethereum.api.jsonrpc.RpcApi;
@ -1021,10 +1023,29 @@ public class BesuCommand implements DefaultCommandValues, Runnable {
description = "Path to PID file (optional)")
private final Path pidPath = null;
@CommandLine.Option(
names = {"--api-gas-price-blocks"},
paramLabel = MANDATORY_PATH_FORMAT_HELP,
description = "Number of blocks to consider for eth_gasPrice (default: ${DEFAULT-VALUE})")
private final Long apiGasPriceBlocks = 100L;
@CommandLine.Option(
names = {"--api-gas-price-percentile"},
paramLabel = MANDATORY_PATH_FORMAT_HELP,
description = "Percentile value to measure for eth_gasPrice (default: ${DEFAULT-VALUE})")
private final Double apiGasPricePercentile = 50.0;
@CommandLine.Option(
names = {"--api-gas-price-max"},
paramLabel = MANDATORY_PATH_FORMAT_HELP,
description = "Maximum gas price for eth_gasPrice (default: ${DEFAULT-VALUE})")
private final Long apiGasPriceMax = 500_000_000_000L;
private EthNetworkConfig ethNetworkConfig;
private JsonRpcConfiguration jsonRpcConfiguration;
private GraphQLConfiguration graphQLConfiguration;
private WebSocketConfiguration webSocketConfiguration;
private ApiConfiguration apiConfiguration;
private MetricsConfiguration metricsConfiguration;
private Optional<PermissioningConfiguration> permissioningConfiguration;
private Collection<EnodeURL> staticNodes;
@ -1245,6 +1266,7 @@ public class BesuCommand implements DefaultCommandValues, Runnable {
graphQLConfiguration,
jsonRpcConfiguration,
webSocketConfiguration,
apiConfiguration,
metricsConfiguration,
permissioningConfiguration,
staticNodes,
@ -1428,6 +1450,7 @@ public class BesuCommand implements DefaultCommandValues, Runnable {
jsonRpcConfiguration = jsonRpcConfiguration();
graphQLConfiguration = graphQLConfiguration();
webSocketConfiguration = webSocketConfiguration();
apiConfiguration = apiConfiguration();
// hostsWhitelist is a hidden option. If it is specified, add the list to hostAllowlist
if (!hostsWhitelist.isEmpty()) {
// if allowlist == default values, remove the default values
@ -1714,6 +1737,15 @@ public class BesuCommand implements DefaultCommandValues, Runnable {
return webSocketConfiguration;
}
private ApiConfiguration apiConfiguration() {
return ImmutableApiConfiguration.builder()
.gasPriceBlocks(apiGasPriceBlocks)
.gasPricePercentile(apiGasPricePercentile)
.gasPriceMin(minTransactionGasPrice.toLong())
.gasPriceMax(apiGasPriceMax)
.build();
}
public MetricsConfiguration metricsConfiguration() {
if (isMetricsEnabled && isMetricsPushEnabled) {
throw new ParameterException(
@ -2039,6 +2071,7 @@ public class BesuCommand implements DefaultCommandValues, Runnable {
final GraphQLConfiguration graphQLConfiguration,
final JsonRpcConfiguration jsonRpcConfiguration,
final WebSocketConfiguration webSocketConfiguration,
final ApiConfiguration apiConfiguration,
final MetricsConfiguration metricsConfiguration,
final Optional<PermissioningConfiguration> permissioningConfiguration,
final Collection<EnodeURL> staticNodes,
@ -2071,6 +2104,7 @@ public class BesuCommand implements DefaultCommandValues, Runnable {
.graphQLConfiguration(graphQLConfiguration)
.jsonRpcConfiguration(jsonRpcConfiguration)
.webSocketConfiguration(webSocketConfiguration)
.apiConfiguration(apiConfiguration)
.pidPath(pidPath)
.dataDir(dataDir())
.bannedNodeIds(bannedNodeIds)

@ -223,6 +223,7 @@ public abstract class CommandTestAbstract {
when(mockRunnerBuilder.jsonRpcConfiguration(any())).thenReturn(mockRunnerBuilder);
when(mockRunnerBuilder.graphQLConfiguration(any())).thenReturn(mockRunnerBuilder);
when(mockRunnerBuilder.webSocketConfiguration(any())).thenReturn(mockRunnerBuilder);
when(mockRunnerBuilder.apiConfiguration(any())).thenReturn(mockRunnerBuilder);
when(mockRunnerBuilder.dataDir(any())).thenReturn(mockRunnerBuilder);
when(mockRunnerBuilder.bannedNodeIds(any())).thenReturn(mockRunnerBuilder);
when(mockRunnerBuilder.metricsSystem(any())).thenReturn(mockRunnerBuilder);

@ -90,6 +90,11 @@ rpc-ws-authentication-enabled=false
rpc-ws-authentication-credentials-file="none"
rpc-ws-authentication-jwt-public-key-file="none"
# API
api-gas-price-blocks=100
api-gas-price-percentile=50.0
api-gas-price-max=500000000000
# Prometheus Metrics Endpoint
metrics-enabled=false
metrics-protocol="prometheus"

@ -0,0 +1,49 @@
/*
* 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;
import org.immutables.value.Value;
@Value.Immutable
@Value.Style(allParameters = true)
public abstract class ApiConfiguration {
@Value.Default
public long getGasPriceBlocks() {
return 100;
}
@Value.Default
public double getGasPricePercentile() {
return 50.0d;
}
@Value.Default
public long getGasPriceMin() {
return 1_000_000_000L; // 1 GWei
}
@Value.Default
public long getGasPriceMax() {
return 500_000_000_000L; // 500 GWei
}
@Value.Derived
public double getGasPriceFraction() {
return getGasPricePercentile() / 100.0;
};
}

@ -27,7 +27,6 @@ import org.hyperledger.besu.ethereum.api.query.BlockWithMetadata;
import org.hyperledger.besu.ethereum.api.query.BlockchainQueries;
import org.hyperledger.besu.ethereum.api.query.LogsQuery;
import org.hyperledger.besu.ethereum.api.query.TransactionWithMetadata;
import org.hyperledger.besu.ethereum.blockcreation.MiningCoordinator;
import org.hyperledger.besu.ethereum.core.Account;
import org.hyperledger.besu.ethereum.core.Address;
import org.hyperledger.besu.ethereum.core.Hash;
@ -115,10 +114,13 @@ public class GraphQLDataFetchers {
DataFetcher<Optional<Wei>> getGasPriceDataFetcher() {
return dataFetchingEnvironment -> {
final MiningCoordinator miningCoordinator =
((GraphQLDataFetcherContext) dataFetchingEnvironment.getContext()).getMiningCoordinator();
return Optional.of(miningCoordinator.getMinTransactionGasPrice());
final GraphQLDataFetcherContext context =
(GraphQLDataFetcherContext) dataFetchingEnvironment.getContext();
return (context)
.getBlockchainQueries()
.gasPrice()
.map(Wei::of)
.or(() -> Optional.of(context.getMiningCoordinator().getMinTransactionGasPrice()));
};
}

@ -19,14 +19,24 @@ import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext;
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.Quantity;
import org.hyperledger.besu.ethereum.api.query.BlockchainQueries;
import org.hyperledger.besu.ethereum.blockcreation.MiningCoordinator;
import org.hyperledger.besu.ethereum.core.Wei;
import java.util.function.Supplier;
public class EthGasPrice implements JsonRpcMethod {
private final Supplier<BlockchainQueries> blockchain;
private final MiningCoordinator miningCoordinator;
public EthGasPrice(final MiningCoordinator miningCoordinator) {
public EthGasPrice(
final BlockchainQueries blockchain, final MiningCoordinator miningCoordinator) {
this(() -> blockchain, miningCoordinator);
}
public EthGasPrice(
final Supplier<BlockchainQueries> blockchain, final MiningCoordinator miningCoordinator) {
this.blockchain = blockchain;
this.miningCoordinator = miningCoordinator;
}
@ -37,12 +47,12 @@ public class EthGasPrice implements JsonRpcMethod {
@Override
public JsonRpcResponse response(final JsonRpcRequestContext requestContext) {
final Wei gasPrice;
Object result = null;
gasPrice = miningCoordinator.getMinTransactionGasPrice();
if (gasPrice != null) {
result = Quantity.create(gasPrice);
}
return new JsonRpcSuccessResponse(requestContext.getRequest().getId(), result);
return new JsonRpcSuccessResponse(
requestContext.getRequest().getId(),
blockchain
.get()
.gasPrice()
.map(Quantity::create)
.orElseGet(() -> Quantity.create(miningCoordinator.getMinTransactionGasPrice())));
}
}

@ -153,7 +153,7 @@ public class EthJsonRpcMethods extends ApiGroupJsonRpcMethods {
new EthMining(miningCoordinator),
new EthCoinbase(miningCoordinator),
new EthProtocolVersion(supportedCapabilities),
new EthGasPrice(miningCoordinator),
new EthGasPrice(blockchainQueries, miningCoordinator),
new EthGetWork(miningCoordinator),
new EthSubmitWork(miningCoordinator),
new EthHashrate(miningCoordinator),

@ -17,6 +17,8 @@ package org.hyperledger.besu.ethereum.api.query;
import static com.google.common.base.Preconditions.checkArgument;
import static org.hyperledger.besu.ethereum.api.query.cache.TransactionLogBloomCacher.BLOCKS_PER_BLOOM_CACHE;
import org.hyperledger.besu.ethereum.api.ApiConfiguration;
import org.hyperledger.besu.ethereum.api.ImmutableApiConfiguration;
import org.hyperledger.besu.ethereum.api.handlers.RpcMethodTimeoutException;
import org.hyperledger.besu.ethereum.api.query.cache.TransactionLogBloomCacher;
import org.hyperledger.besu.ethereum.chain.Blockchain;
@ -65,6 +67,7 @@ public class BlockchainQueries {
private final Blockchain blockchain;
private final Optional<Path> cachePath;
private final Optional<TransactionLogBloomCacher> transactionLogBloomCacher;
private final ApiConfiguration apiConfig;
public BlockchainQueries(final Blockchain blockchain, final WorldStateArchive worldStateArchive) {
this(blockchain, worldStateArchive, Optional.empty(), Optional.empty());
@ -82,6 +85,20 @@ public class BlockchainQueries {
final WorldStateArchive worldStateArchive,
final Optional<Path> cachePath,
final Optional<EthScheduler> scheduler) {
this(
blockchain,
worldStateArchive,
cachePath,
scheduler,
ImmutableApiConfiguration.builder().build());
}
public BlockchainQueries(
final Blockchain blockchain,
final WorldStateArchive worldStateArchive,
final Optional<Path> cachePath,
final Optional<EthScheduler> scheduler,
final ApiConfiguration apiConfig) {
this.blockchain = blockchain;
this.worldStateArchive = worldStateArchive;
this.cachePath = cachePath;
@ -90,6 +107,7 @@ public class BlockchainQueries {
? Optional.of(
new TransactionLogBloomCacher(blockchain, cachePath.get(), scheduler.get()))
: Optional.empty();
this.apiConfig = apiConfig;
}
public Blockchain getBlockchain() {
@ -572,7 +590,7 @@ public class BlockchainQueries {
query,
cacheFile,
isQueryAlive);
} catch (Exception e) {
} catch (final Exception e) {
throw new RuntimeException(e);
}
})
@ -586,10 +604,10 @@ public class BlockchainQueries {
currentStep = nextStep;
}
return result;
} catch (RpcMethodTimeoutException e) {
} catch (final RpcMethodTimeoutException e) {
LOG.error("Error retrieving matching logs", e);
throw e;
} catch (Exception e) {
} catch (final Exception e) {
LOG.error("Error retrieving matching logs", e);
throw new RuntimeException(e);
}
@ -692,14 +710,14 @@ public class BlockchainQueries {
transactions.get(i).getHash(),
i,
removed);
} catch (Exception e) {
} catch (final Exception e) {
throw new RuntimeException(e);
}
})
.flatMap(Collection::stream)
.filter(query::matches)
.collect(Collectors.toList());
} catch (Exception e) {
} catch (final Exception e) {
throw new RuntimeException(e);
}
}
@ -715,6 +733,35 @@ public class BlockchainQueries {
return header.map(BlockHeader::getStateRoot).flatMap(worldStateArchive::getMutable);
}
public Optional<Long> gasPrice() {
final long blockHeight = headBlockNumber();
final long[] gasCollection =
LongStream.range(Math.max(0, blockHeight - apiConfig.getGasPriceBlocks()), blockHeight)
.mapToObj(
l ->
blockchain
.getBlockByNumber(l)
.map(Block::getBody)
.map(BlockBody::getTransactions)
.orElseThrow(
() -> new IllegalStateException("Could not retrieve block #" + l)))
.flatMap(Collection::stream)
.mapToLong(t -> t.getGasPrice().toLong())
.sorted()
.toArray();
return (gasCollection == null || gasCollection.length == 0)
? Optional.empty()
: Optional.of(
Math.max(
apiConfig.getGasPriceMin(),
Math.min(
apiConfig.getGasPriceMax(),
gasCollection[
Math.min(
gasCollection.length - 1,
(int) ((gasCollection.length) * apiConfig.getGasPriceFraction()))])));
}
private <T> Optional<T> fromWorldState(
final long blockNumber, final Function<WorldState, T> getter) {
if (outsideBlockchainRange(blockNumber)) {

@ -15,6 +15,7 @@
package org.hyperledger.besu.ethereum.api.graphql;
import org.hyperledger.besu.ethereum.ProtocolContext;
import org.hyperledger.besu.ethereum.api.ImmutableApiConfiguration;
import org.hyperledger.besu.ethereum.api.query.BlockchainQueries;
import org.hyperledger.besu.ethereum.blockcreation.EthHashMiningCoordinator;
import org.hyperledger.besu.ethereum.chain.GenesisState;
@ -153,7 +154,12 @@ public abstract class AbstractEthGraphQLHttpServiceTest {
InMemoryStorageProvider.createInMemoryBlockchain(GENESIS_BLOCK);
context = new ProtocolContext(blockchain, stateArchive, null);
final BlockchainQueries blockchainQueries =
new BlockchainQueries(context.getBlockchain(), context.getWorldStateArchive());
new BlockchainQueries(
context.getBlockchain(),
context.getWorldStateArchive(),
Optional.empty(),
Optional.empty(),
ImmutableApiConfiguration.builder().gasPriceMin(0).build());
final Set<Capability> supportedCapabilities = new HashSet<>();
supportedCapabilities.add(EthProtocol.ETH62);

@ -15,34 +15,60 @@
package org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import org.hyperledger.besu.ethereum.api.ImmutableApiConfiguration;
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.response.JsonRpcResponse;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse;
import org.hyperledger.besu.ethereum.api.query.BlockchainQueries;
import org.hyperledger.besu.ethereum.blockcreation.EthHashMiningCoordinator;
import org.hyperledger.besu.ethereum.chain.Blockchain;
import org.hyperledger.besu.ethereum.core.Address;
import org.hyperledger.besu.ethereum.core.Block;
import org.hyperledger.besu.ethereum.core.BlockBody;
import org.hyperledger.besu.ethereum.core.BlockHeader;
import org.hyperledger.besu.ethereum.core.Difficulty;
import org.hyperledger.besu.ethereum.core.Hash;
import org.hyperledger.besu.ethereum.core.LogsBloomFilter;
import org.hyperledger.besu.ethereum.core.Transaction;
import org.hyperledger.besu.ethereum.core.Wei;
import java.util.List;
import java.util.Optional;
import org.apache.tuweni.bytes.Bytes;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.internal.verification.VerificationModeFactory;
import org.mockito.junit.MockitoJUnitRunner;
@RunWith(MockitoJUnitRunner.class)
public class EthGasPriceTest {
@Mock private EthHashMiningCoordinator miningCoordinator;
@Mock private Blockchain blockchain;
private EthGasPrice method;
private final String JSON_RPC_VERSION = "2.0";
private final String ETH_METHOD = "eth_gasPrice";
@Before
public void setUp() {
method = new EthGasPrice(miningCoordinator);
method =
new EthGasPrice(
new BlockchainQueries(
blockchain,
null,
Optional.empty(),
Optional.empty(),
ImmutableApiConfiguration.builder().gasPriceMin(100).build()),
miningCoordinator);
}
@Test
@ -51,17 +77,128 @@ public class EthGasPriceTest {
}
@Test
public void shouldReturnExpectedValueWhenMiningCoordinatorExists() {
public void shouldReturnMinValueWhenNoTransactionsExist() {
final JsonRpcRequestContext request = requestWithParams();
final String expectedWei = "0x4d2";
final JsonRpcResponse expectedResponse =
new JsonRpcSuccessResponse(request.getRequest().getId(), expectedWei);
when(miningCoordinator.getMinTransactionGasPrice()).thenReturn(Wei.of(1234));
when(blockchain.getChainHeadBlockNumber()).thenReturn(1000L);
when(blockchain.getBlockByNumber(anyLong()))
.thenAnswer(invocation -> createEmptyBlock(invocation.getArgument(0, Long.class)));
final JsonRpcResponse actualResponse = method.response(request);
assertThat(actualResponse).isEqualToComparingFieldByField(expectedResponse);
verify(miningCoordinator).getMinTransactionGasPrice();
verifyNoMoreInteractions(miningCoordinator);
verify(blockchain).getChainHeadBlockNumber();
verify(blockchain, VerificationModeFactory.times(100)).getBlockByNumber(anyLong());
verifyNoMoreInteractions(blockchain);
}
@Test
public void shouldReturnMedianWhenTransactionsExist() {
final JsonRpcRequestContext request = requestWithParams();
final String expectedWei = "0x389fd980"; // 950Wei, gas prices are 900-999 wei.
final JsonRpcResponse expectedResponse =
new JsonRpcSuccessResponse(request.getRequest().getId(), expectedWei);
when(blockchain.getChainHeadBlockNumber()).thenReturn(1000L);
when(blockchain.getBlockByNumber(anyLong()))
.thenAnswer(invocation -> createFakeBlock(invocation.getArgument(0, Long.class)));
final JsonRpcResponse actualResponse = method.response(request);
assertThat(actualResponse).isEqualToComparingFieldByField(expectedResponse);
verifyNoMoreInteractions(miningCoordinator);
verify(blockchain).getChainHeadBlockNumber();
verify(blockchain, VerificationModeFactory.times(100)).getBlockByNumber(anyLong());
verifyNoMoreInteractions(blockchain);
}
@Test
public void shortChainQueriesAllBlocks() {
final JsonRpcRequestContext request = requestWithParams();
final String expectedWei = "0x2625a00";
final JsonRpcResponse expectedResponse =
new JsonRpcSuccessResponse(request.getRequest().getId(), expectedWei);
when(blockchain.getChainHeadBlockNumber()).thenReturn(80L);
when(blockchain.getBlockByNumber(anyLong()))
.thenAnswer(invocation -> createFakeBlock(invocation.getArgument(0, Long.class)));
final JsonRpcResponse actualResponse = method.response(request);
assertThat(actualResponse).isEqualToComparingFieldByField(expectedResponse);
verifyNoMoreInteractions(miningCoordinator);
verify(blockchain).getChainHeadBlockNumber();
verify(blockchain, VerificationModeFactory.times(80)).getBlockByNumber(anyLong());
verifyNoMoreInteractions(blockchain);
}
private Object createFakeBlock(final Long height) {
return Optional.of(
new Block(
new BlockHeader(
Hash.EMPTY,
Hash.EMPTY_TRIE_HASH,
Address.ZERO,
Hash.EMPTY_TRIE_HASH,
Hash.EMPTY_TRIE_HASH,
Hash.EMPTY_TRIE_HASH,
LogsBloomFilter.builder().build(),
Difficulty.ONE,
height,
0,
0,
0,
Bytes.EMPTY,
0L,
Hash.EMPTY,
0,
null),
new BlockBody(
List.of(
new Transaction(
0,
Wei.of(height * 1000000L),
0,
Optional.empty(),
Wei.ZERO,
null,
Bytes.EMPTY,
Address.ZERO,
Optional.empty())),
List.of())));
}
private Object createEmptyBlock(final Long height) {
return Optional.of(
new Block(
new BlockHeader(
Hash.EMPTY,
Hash.EMPTY_TRIE_HASH,
Address.ZERO,
Hash.EMPTY_TRIE_HASH,
Hash.EMPTY_TRIE_HASH,
Hash.EMPTY_TRIE_HASH,
LogsBloomFilter.builder().build(),
Difficulty.ONE,
height,
0,
0,
0,
Bytes.EMPTY,
0L,
Hash.EMPTY,
0,
null),
new BlockBody(List.of(), List.of())));
}
private JsonRpcRequestContext requestWithParams(final Object... params) {

@ -4,7 +4,7 @@
"response": {
"data" : {
"gasPrice" : "0x10"
"gasPrice" : "0x1"
}
},
"statusCode": 200

Loading…
Cancel
Save