|
|
|
@ -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)); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
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)); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|