From e1dd400aa5c2b282a5cee1eb9279efb9b1ee2d7c Mon Sep 17 00:00:00 2001 From: Fabio Di Fabio Date: Mon, 2 Sep 2024 12:12:31 +0200 Subject: [PATCH] Include current chain head block when computing eth_maxPriorityFeePerGas (#7485) Signed-off-by: Fabio Di Fabio --- CHANGELOG.md | 1 + .../api/graphql/GraphQLDataFetchers.java | 4 +- .../methods/EthMaxPriorityFeePerGas.java | 11 +- .../jsonrpc/methods/EthJsonRpcMethods.java | 2 +- .../ethereum/api/query/BlockchainQueries.java | 57 +++--- .../graphql/GraphQLHttpServiceCorsTest.java | 16 +- .../GraphQLHttpServiceHostWhitelistTest.java | 16 +- .../internal/methods/EthGasPriceTest.java | 2 +- .../methods/EthMaxPriorityFeePerGasTest.java | 190 ++++++++++++++---- 9 files changed, 207 insertions(+), 92 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f118aa503b..798e534571 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/graphql/GraphQLDataFetchers.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/graphql/GraphQLDataFetchers.java index 0b97bdb2e9..d4338d58c3 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/graphql/GraphQLDataFetchers.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/graphql/GraphQLDataFetchers.java @@ -203,7 +203,7 @@ public class GraphQLDataFetchers { * *

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(); }; } diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthMaxPriorityFeePerGas.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthMaxPriorityFeePerGas.java index 2828fee7ac..71cb5ea3c6 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthMaxPriorityFeePerGas.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthMaxPriorityFeePerGas.java @@ -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 gasPrice = blockchainQueries.gasPriorityFee(); - return gasPrice.orElseGet(miningCoordinator::getMinPriorityFeePerGas); + return blockchainQueries.gasPriorityFee(); } } diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/EthJsonRpcMethods.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/EthJsonRpcMethods.java index c7575a5435..5baa110646 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/EthJsonRpcMethods.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/EthJsonRpcMethods.java @@ -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)); } } diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/query/BlockchainQueries.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/query/BlockchainQueries.java index 6e0f0e3e35..c03bf124c4 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/query/BlockchainQueries.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/query/BlockchainQueries.java @@ -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 gasPriorityFee() { - final long blockHeight = headBlockNumber(); - final BigInteger[] 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))) + 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) .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( - gasCollection[ - Math.min( - gasCollection.length - 1, - (int) ((gasCollection.length) * apiConfig.getGasPriceFraction()))])); + .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()))]); } /** diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/graphql/GraphQLHttpServiceCorsTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/graphql/GraphQLHttpServiceCorsTest.java index d6a04b4cb8..0e008568dc 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/graphql/GraphQLHttpServiceCorsTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/graphql/GraphQLHttpServiceCorsTest.java @@ -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 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; diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/graphql/GraphQLHttpServiceHostWhitelistTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/graphql/GraphQLHttpServiceHostWhitelistTest.java index ec33bd9268..90833c42a5 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/graphql/GraphQLHttpServiceHostWhitelistTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/graphql/GraphQLHttpServiceHostWhitelistTest.java @@ -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 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() { diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGasPriceTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGasPriceTest.java index 5d6aba7694..e428286444 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGasPriceTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGasPriceTest.java @@ -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)))); diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthMaxPriorityFeePerGasTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthMaxPriorityFeePerGasTest.java index 02d526107a..886c8a1d03 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthMaxPriorityFeePerGasTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthMaxPriorityFeePerGasTest.java @@ -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 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 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(); + + 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)); + } + + 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 JsonRpcRequestContext requestWithParams(final Object... params) { - return new JsonRpcRequestContext(new JsonRpcRequest(JSON_RPC_VERSION, ETH_METHOD, params)); + 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)); } }