Include current chain head block when computing eth_maxPriorityFeePerGas (#7485)

Signed-off-by: Fabio Di Fabio <fabio.difabio@consensys.net>
pull/7532/head
Fabio Di Fabio 3 months ago committed by GitHub
parent ac5e9af5e0
commit e1dd400aa5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 1
      CHANGELOG.md
  2. 4
      ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/graphql/GraphQLDataFetchers.java
  3. 11
      ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthMaxPriorityFeePerGas.java
  4. 2
      ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/EthJsonRpcMethods.java
  5. 39
      ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/query/BlockchainQueries.java
  6. 16
      ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/graphql/GraphQLHttpServiceCorsTest.java
  7. 16
      ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/graphql/GraphQLHttpServiceHostWhitelistTest.java
  8. 2
      ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGasPriceTest.java
  9. 190
      ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthMaxPriorityFeePerGasTest.java

@ -7,6 +7,7 @@
### Breaking Changes
### Additions and Improvements
- Include current chain head block when computing `eth_maxPriorityFeePerGas` [#7485](https://github.com/hyperledger/besu/pull/7485)
### Bug fixes

@ -203,7 +203,7 @@ public class GraphQLDataFetchers {
*
* <p>The DataFetcher is a functional interface. It has a single method that takes a
* DataFetchingEnvironment object as input and returns the maximum priority fee per gas as a Wei
* object. If the maximum priority fee per gas is not available, it returns Wei.ZERO.
* object.
*
* @return a DataFetcher that fetches the maximum priority fee per gas of the Ethereum node
*/
@ -211,7 +211,7 @@ public class GraphQLDataFetchers {
return dataFetchingEnvironment -> {
final BlockchainQueries blockchainQuery =
dataFetchingEnvironment.getGraphQlContext().get(GraphQLContextType.BLOCKCHAIN_QUERIES);
return blockchainQuery.gasPriorityFee().orElse(Wei.ZERO);
return blockchainQuery.gasPriorityFee();
};
}

@ -21,19 +21,13 @@ import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcRespon
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 java.util.Optional;
public class EthMaxPriorityFeePerGas implements JsonRpcMethod {
private final BlockchainQueries blockchainQueries;
private final MiningCoordinator miningCoordinator;
public EthMaxPriorityFeePerGas(
final BlockchainQueries blockchainQueries, final MiningCoordinator miningCoordinator) {
public EthMaxPriorityFeePerGas(final BlockchainQueries blockchainQueries) {
this.blockchainQueries = blockchainQueries;
this.miningCoordinator = miningCoordinator;
}
@Override
@ -48,7 +42,6 @@ public class EthMaxPriorityFeePerGas implements JsonRpcMethod {
}
private Wei fetchAndLimitPriorityFeePerGas() {
final Optional<Wei> gasPrice = blockchainQueries.gasPriorityFee();
return gasPrice.orElseGet(miningCoordinator::getMinPriorityFeePerGas);
return blockchainQueries.gasPriorityFee();
}
}

@ -185,6 +185,6 @@ public class EthJsonRpcMethods extends ApiGroupJsonRpcMethods {
new EthGetMinerDataByBlockHash(blockchainQueries, protocolSchedule),
new EthGetMinerDataByBlockNumber(blockchainQueries, protocolSchedule),
new EthBlobBaseFee(blockchainQueries.getBlockchain(), protocolSchedule),
new EthMaxPriorityFeePerGas(blockchainQueries, miningCoordinator));
new EthMaxPriorityFeePerGas(blockchainQueries));
}
}

@ -46,7 +46,6 @@ import org.hyperledger.besu.evm.log.LogsBloomFilter;
import java.io.EOFException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.math.BigInteger;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
@ -1006,7 +1005,7 @@ public class BlockchainQueries {
.sorted()
.toArray(Wei[]::new);
return (gasCollection == null || gasCollection.length == 0)
return gasCollection.length == 0
? gasPriceLowerBound(chainHeadHeader, nextBlockFeeMarket)
: UInt256s.max(
gasPriceLowerBound(chainHeadHeader, nextBlockFeeMarket),
@ -1045,31 +1044,39 @@ public class BlockchainQueries {
return minGasPrice;
}
public Optional<Wei> gasPriorityFee() {
final long blockHeight = headBlockNumber();
final BigInteger[] gasCollection =
LongStream.range(Math.max(0, blockHeight - apiConfig.getGasPriceBlocks()), blockHeight)
public Wei gasPriorityFee() {
final Block chainHeadBlock = blockchain.getChainHeadBlock();
final long blockHeight = chainHeadBlock.getHeader().getNumber();
final Wei[] gasCollection =
Stream.concat(
LongStream.range(
Math.max(0, blockHeight - apiConfig.getGasPriceBlocks() + 1), blockHeight)
.mapToObj(
l ->
blockchain
.getBlockByNumber(l)
.orElseThrow(
() ->
new IllegalStateException(
"Could not retrieve block #" + l))),
Stream.of(chainHeadBlock))
.map(Block::getBody)
.map(BlockBody::getTransactions)
.orElseThrow(
() -> new IllegalStateException("Could not retrieve block #" + l)))
.flatMap(Collection::stream)
.filter(t -> t.getMaxPriorityFeePerGas().isPresent())
.map(t -> t.getMaxPriorityFeePerGas().get().toBigInteger())
.sorted(BigInteger::compareTo)
.toArray(BigInteger[]::new);
return (gasCollection.length == 0)
? Optional.empty()
: Optional.of(
Wei.of(
.map(t -> t.getMaxPriorityFeePerGas().get())
.sorted()
.toArray(Wei[]::new);
return gasCollection.length == 0
? miningParameters.getMinPriorityFeePerGas()
: UInt256s.max(
miningParameters.getMinPriorityFeePerGas(),
gasCollection[
Math.min(
gasCollection.length - 1,
(int) ((gasCollection.length) * apiConfig.getGasPriceFraction()))]));
(int) ((gasCollection.length) * apiConfig.getGasPriceFraction()))]);
}
/**

@ -14,6 +14,10 @@
*/
package org.hyperledger.besu.ethereum.api.graphql;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import org.hyperledger.besu.datatypes.Wei;
import org.hyperledger.besu.ethereum.api.query.BlockchainQueries;
import org.hyperledger.besu.ethereum.blockcreation.PoWMiningCoordinator;
import org.hyperledger.besu.ethereum.core.Synchronizer;
@ -38,7 +42,6 @@ import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import org.mockito.Mockito;
public class GraphQLHttpServiceCorsTest {
@TempDir private Path folder;
@ -208,10 +211,11 @@ public class GraphQLHttpServiceCorsTest {
config.setCorsAllowedDomains(Lists.newArrayList(corsAllowedDomains));
}
final BlockchainQueries blockchainQueries = Mockito.mock(BlockchainQueries.class);
final Synchronizer synchronizer = Mockito.mock(Synchronizer.class);
final BlockchainQueries blockchainQueries = mock(BlockchainQueries.class);
when(blockchainQueries.gasPriorityFee()).thenReturn(Wei.ONE);
final Synchronizer synchronizer = mock(Synchronizer.class);
final PoWMiningCoordinator miningCoordinatorMock = Mockito.mock(PoWMiningCoordinator.class);
final PoWMiningCoordinator miningCoordinatorMock = mock(PoWMiningCoordinator.class);
// mock graphql context
final Map<GraphQLContextType, Object> graphQLContextMap =
@ -219,7 +223,7 @@ public class GraphQLHttpServiceCorsTest {
GraphQLContextType.BLOCKCHAIN_QUERIES,
blockchainQueries,
GraphQLContextType.TRANSACTION_POOL,
Mockito.mock(TransactionPool.class),
mock(TransactionPool.class),
GraphQLContextType.MINING_COORDINATOR,
miningCoordinatorMock,
GraphQLContextType.SYNCHRONIZER,
@ -233,7 +237,7 @@ public class GraphQLHttpServiceCorsTest {
final GraphQLHttpService graphQLHttpService =
new GraphQLHttpService(
vertx, folder, config, graphQL, graphQLContextMap, Mockito.mock(EthScheduler.class));
vertx, folder, config, graphQL, graphQLContextMap, mock(EthScheduler.class));
graphQLHttpService.start().join();
return graphQLHttpService;

@ -14,6 +14,10 @@
*/
package org.hyperledger.besu.ethereum.api.graphql;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import org.hyperledger.besu.datatypes.Wei;
import org.hyperledger.besu.ethereum.api.query.BlockchainQueries;
import org.hyperledger.besu.ethereum.blockcreation.PoWMiningCoordinator;
import org.hyperledger.besu.ethereum.core.Synchronizer;
@ -42,7 +46,6 @@ import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import org.mockito.Mockito;
public class GraphQLHttpServiceHostWhitelistTest {
@ -69,17 +72,18 @@ public class GraphQLHttpServiceHostWhitelistTest {
}
private GraphQLHttpService createGraphQLHttpService() throws Exception {
final BlockchainQueries blockchainQueries = Mockito.mock(BlockchainQueries.class);
final Synchronizer synchronizer = Mockito.mock(Synchronizer.class);
final BlockchainQueries blockchainQueries = mock(BlockchainQueries.class);
when(blockchainQueries.gasPriorityFee()).thenReturn(Wei.ONE);
final Synchronizer synchronizer = mock(Synchronizer.class);
final PoWMiningCoordinator miningCoordinatorMock = Mockito.mock(PoWMiningCoordinator.class);
final PoWMiningCoordinator miningCoordinatorMock = mock(PoWMiningCoordinator.class);
final Map<GraphQLContextType, Object> graphQLContextMap =
Map.of(
GraphQLContextType.BLOCKCHAIN_QUERIES,
blockchainQueries,
GraphQLContextType.TRANSACTION_POOL,
Mockito.mock(TransactionPool.class),
mock(TransactionPool.class),
GraphQLContextType.MINING_COORDINATOR,
miningCoordinatorMock,
GraphQLContextType.SYNCHRONIZER,
@ -92,7 +96,7 @@ public class GraphQLHttpServiceHostWhitelistTest {
final GraphQL graphQL = GraphQLProvider.buildGraphQL(dataFetchers);
return new GraphQLHttpService(
vertx, folder, graphQLConfig, graphQL, graphQLContextMap, Mockito.mock(EthScheduler.class));
vertx, folder, graphQLConfig, graphQL, graphQLContextMap, mock(EthScheduler.class));
}
private static GraphQLConfiguration createGraphQLConfig() {

@ -328,7 +328,7 @@ public class EthGasPriceTest {
}
when(blockchain.getChainHeadBlock()).thenReturn(blocksByNumber.get(chainHeadBlockNumber));
if (chainHeadBlockNumber > 1) {
if (chainHeadBlockNumber > 0) {
when(blockchain.getBlockByNumber(anyLong()))
.thenAnswer(
invocation -> Optional.of(blocksByNumber.get(invocation.getArgument(0, Long.class))));

@ -15,29 +15,43 @@
package org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.verify;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.Mockito.lenient;
import static org.mockito.Mockito.when;
import org.hyperledger.besu.datatypes.Address;
import org.hyperledger.besu.datatypes.Hash;
import org.hyperledger.besu.datatypes.TransactionType;
import org.hyperledger.besu.datatypes.Wei;
import org.hyperledger.besu.ethereum.api.ImmutableApiConfiguration;
import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod;
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.MiningCoordinator;
import org.hyperledger.besu.ethereum.chain.Blockchain;
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.MiningParameters;
import org.hyperledger.besu.ethereum.core.Transaction;
import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule;
import org.hyperledger.besu.ethereum.mainnet.feemarket.CancunFeeMarket;
import org.hyperledger.besu.evm.log.LogsBloomFilter;
import java.math.BigInteger;
import java.util.HashMap;
import java.util.List;
import java.util.Optional;
import java.util.stream.Stream;
import java.util.stream.IntStream;
import org.apache.tuweni.bytes.Bytes;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.mockito.Mock;
import org.mockito.internal.verification.VerificationModeFactory;
import org.mockito.junit.jupiter.MockitoExtension;
@ExtendWith(MockitoExtension.class)
@ -45,13 +59,20 @@ public class EthMaxPriorityFeePerGasTest {
private static final String JSON_RPC_VERSION = "2.0";
private static final String ETH_METHOD =
RpcMethod.ETH_GET_MAX_PRIORITY_FEE_PER_GAS.getMethodName();
private EthMaxPriorityFeePerGas method;
private static final Wei DEFAULT_MIN_PRIORITY_FEE_PER_GAS = Wei.ZERO;
private static final long DEFAULT_BLOCK_GAS_LIMIT = 100_000;
private static final long DEFAULT_BLOCK_GAS_USED = 21_000;
private static final Wei DEFAULT_BASE_FEE = Wei.of(100_000);
@Mock private BlockchainQueries blockchainQueries;
@Mock private MiningCoordinator miningCoordinator;
private EthMaxPriorityFeePerGas method;
@Mock private ProtocolSchedule protocolSchedule;
@Mock private Blockchain blockchain;
private MiningParameters miningParameters;
@BeforeEach
public void setUp() {
miningParameters =
MiningParameters.newDefault().setMinPriorityFeePerGas(DEFAULT_MIN_PRIORITY_FEE_PER_GAS);
method = createEthMaxPriorityFeePerGasMethod();
}
@ -63,71 +84,156 @@ public class EthMaxPriorityFeePerGasTest {
@Test
public void whenNoTransactionsExistReturnMinPriorityFeePerGasPrice() {
final JsonRpcRequestContext request = requestWithParams();
final String expectedWei = Wei.ONE.toShortHexString();
final Wei expectedWei = Wei.ONE;
miningParameters.setMinPriorityFeePerGas(expectedWei);
final JsonRpcResponse expectedResponse =
new JsonRpcSuccessResponse(request.getRequest().getId(), expectedWei);
when(miningCoordinator.getMinPriorityFeePerGas()).thenReturn(Wei.ONE);
new JsonRpcSuccessResponse(request.getRequest().getId(), expectedWei.toShortHexString());
mockBlockchainQueriesMaxPriorityFeePerGasPrice(Optional.empty());
mockBlockchain(10, 0);
final JsonRpcResponse actualResponse = method.response(request);
assertThat(actualResponse).usingRecursiveComparison().isEqualTo(expectedResponse);
verify(miningCoordinator, VerificationModeFactory.times(1)).getMinPriorityFeePerGas();
}
@ParameterizedTest
@MethodSource("minPriorityFeePerGasValues")
public void whenNoTransactionsExistReturnMinPriorityFeePerGasPriceExist(
final Wei minPriorityFeePerGasValue) {
@Test
public void whenTransactionsExistReturnMedianMaxPriorityFeePerGasPrice() {
final JsonRpcRequestContext request = requestWithParams();
final String expectedWei = minPriorityFeePerGasValue.toShortHexString();
final Wei expectedWei = Wei.of(51_000); // max priority fee per gas prices are 1000-100000 wei.
final JsonRpcResponse expectedResponse =
new JsonRpcSuccessResponse(request.getRequest().getId(), expectedWei);
when(miningCoordinator.getMinPriorityFeePerGas()).thenReturn(minPriorityFeePerGasValue);
new JsonRpcSuccessResponse(request.getRequest().getId(), expectedWei.toShortHexString());
mockBlockchain(100, 1);
mockBlockchainQueriesMaxPriorityFeePerGasPrice(Optional.empty());
final JsonRpcResponse actualResponse = method.response(request);
assertThat(actualResponse).usingRecursiveComparison().isEqualTo(expectedResponse);
verify(miningCoordinator, VerificationModeFactory.times(1)).getMinPriorityFeePerGas();
}
@Test
public void whenNoTransactionsExistReturnNullMinPriorityFeePerGasPriceExist() {
public void returnMinPriorityFeePerGasWhenMedianValueIsLower() {
final JsonRpcRequestContext request = requestWithParams();
final Wei expectedWei = Wei.of(100_000);
miningParameters.setMinPriorityFeePerGas(expectedWei);
mockBlockchain(100, 1);
// median value is 51000 wei, that is lower than the value this node is willing to accept,
// so the configured min priority fee per gas is returned.
final JsonRpcResponse expectedResponse =
new JsonRpcSuccessResponse(request.getRequest().getId(), null);
when(miningCoordinator.getMinPriorityFeePerGas()).thenReturn(null);
new JsonRpcSuccessResponse(request.getRequest().getId(), expectedWei.toShortHexString());
mockBlockchainQueriesMaxPriorityFeePerGasPrice(Optional.empty());
final JsonRpcResponse actualResponse = method.response(request);
assertThat(actualResponse).usingRecursiveComparison().isEqualTo(expectedResponse);
verify(miningCoordinator, VerificationModeFactory.times(1)).getMinPriorityFeePerGas();
}
@Test
public void whenTransactionsExistReturnMaxPriorityFeePerGasPrice() {
public void atGenesisReturnMinPriorityFeePerGas() {
final JsonRpcRequestContext request = requestWithParams();
final String expectedWei = Wei.of(2000000000).toShortHexString();
final Wei expectedWei = Wei.ONE;
miningParameters.setMinPriorityFeePerGas(expectedWei);
final JsonRpcResponse expectedResponse =
new JsonRpcSuccessResponse(request.getRequest().getId(), expectedWei);
mockBlockchainQueriesMaxPriorityFeePerGasPrice(Optional.of(Wei.of(2000000000)));
new JsonRpcSuccessResponse(request.getRequest().getId(), expectedWei.toShortHexString());
mockBlockchain(0, 0);
final JsonRpcResponse actualResponse = method.response(request);
assertThat(actualResponse).usingRecursiveComparison().isEqualTo(expectedResponse);
verify(miningCoordinator, VerificationModeFactory.times(0)).getMinPriorityFeePerGas();
}
private static Stream<Arguments> minPriorityFeePerGasValues() {
return Stream.of(Arguments.of(Wei.ONE), Arguments.of(Wei.ZERO));
private JsonRpcRequestContext requestWithParams(final Object... params) {
return new JsonRpcRequestContext(new JsonRpcRequest(JSON_RPC_VERSION, ETH_METHOD, params));
}
private void mockBlockchainQueriesMaxPriorityFeePerGasPrice(final Optional<Wei> result) {
when(blockchainQueries.gasPriorityFee()).thenReturn(result);
private void mockBlockchain(final long chainHeadBlockNumber, final int txsNum) {
final var genesisBaseFee = DEFAULT_BASE_FEE;
final var blocksByNumber = new HashMap<Long, Block>();
final var genesisBlock = createFakeBlock(0, 0, genesisBaseFee);
blocksByNumber.put(0L, genesisBlock);
final var baseFeeMarket = new CancunFeeMarket(0, Optional.empty());
var baseFee = genesisBaseFee;
for (long i = 1; i <= chainHeadBlockNumber; i++) {
final var parentHeader = blocksByNumber.get(i - 1).getHeader();
baseFee =
baseFeeMarket.computeBaseFee(
i,
parentHeader.getBaseFee().get(),
parentHeader.getGasUsed(),
parentHeader.getGasLimit());
blocksByNumber.put(i, createFakeBlock(i, txsNum, baseFee));
}
private JsonRpcRequestContext requestWithParams(final Object... params) {
return new JsonRpcRequestContext(new JsonRpcRequest(JSON_RPC_VERSION, ETH_METHOD, params));
when(blockchain.getChainHeadBlock()).thenReturn(blocksByNumber.get(chainHeadBlockNumber));
if (chainHeadBlockNumber > 0) {
when(blockchain.getBlockByNumber(anyLong()))
.thenAnswer(
invocation -> Optional.of(blocksByNumber.get(invocation.getArgument(0, Long.class))));
}
lenient()
.when(blockchain.getChainHeadHeader())
.thenReturn(blocksByNumber.get(chainHeadBlockNumber).getHeader());
}
private Block createFakeBlock(final long height, final int txsNum, final Wei baseFee) {
return createFakeBlock(
height, txsNum, baseFee, DEFAULT_BLOCK_GAS_LIMIT, DEFAULT_BLOCK_GAS_USED * txsNum);
}
private Block createFakeBlock(
final long height,
final int txsNum,
final Wei baseFee,
final long gasLimit,
final long gasUsed) {
return 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,
gasLimit,
gasUsed,
0,
Bytes.EMPTY,
baseFee,
Hash.EMPTY,
0,
null,
null,
null,
null,
null,
null),
new BlockBody(
IntStream.range(0, txsNum)
.mapToObj(
i ->
new Transaction.Builder()
.chainId(BigInteger.ONE)
.type(TransactionType.EIP1559)
.nonce(i)
.maxFeePerGas(Wei.of(height * 10_000L))
.maxPriorityFeePerGas(Wei.of(height * 1_000L))
.gasLimit(gasUsed)
.value(Wei.ZERO)
.build())
.toList(),
List.of()));
}
private EthMaxPriorityFeePerGas createEthMaxPriorityFeePerGasMethod() {
return new EthMaxPriorityFeePerGas(blockchainQueries, miningCoordinator);
return new EthMaxPriorityFeePerGas(
new BlockchainQueries(
protocolSchedule,
blockchain,
null,
Optional.empty(),
Optional.empty(),
ImmutableApiConfiguration.builder().build(),
miningParameters));
}
}

Loading…
Cancel
Save