diff --git a/CHANGELOG.md b/CHANGELOG.md index 20e3429410c..09f95eb17ae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## 21.7.0-RC2 +### Additions and Improvements +- eth_feeHistory API for wallet providers [\#2432](https://github.com/hyperledger/besu/pull/2432) ### Bug Fixes - Ibft2 could create invalid RoundChange messages in some circumstances containing duplicate prepares [\#2449](https://github.com/hyperledger/besu/pull/2449) diff --git a/besu/src/test/java/org/hyperledger/besu/PrivacyReorgTest.java b/besu/src/test/java/org/hyperledger/besu/PrivacyReorgTest.java index f8e557d7761..21137ac992c 100644 --- a/besu/src/test/java/org/hyperledger/besu/PrivacyReorgTest.java +++ b/besu/src/test/java/org/hyperledger/besu/PrivacyReorgTest.java @@ -105,9 +105,9 @@ public class PrivacyReorgTest { Bytes.fromBase64String("A1aVtMxLCUHmBVHXoZzzBgPbW/wj5axDpW9X8l91SGo="); private static final String FIRST_BLOCK_WITH_NO_TRANSACTIONS_STATE_ROOT = - "0xe368938a01d983e331eb0e4ea61224726d06075c1ad525569b369f664067ff26"; + "0xb0784ff11dffceac824188583f20f3b8bb4ca275e033b3b1c0e280915743be7f"; private static final String FIRST_BLOCK_WITH_SINGLE_TRANSACTION_STATE_ROOT = - "0x9c88988f9602184efc538cf1c2f482a6b8757ff918d234602884dc8e3b983edd"; + "0xe33629724501c0bc271a2b6858da64d3e92048d7e0cd019c5646770330694ff4"; private static final String BLOCK_WITH_SINGLE_TRANSACTION_RECEIPTS_ROOT = "0xc8267b3f9ed36df3ff8adb51a6d030716f23eeb50270e7fce8d9822ffa7f0461"; private static final String STATE_ROOT_AFTER_TRANSACTION_APPENDED_TO_EMPTY_STATE = @@ -221,7 +221,7 @@ public void privacyGroupHeadIsTracked() { .contains(expected); final String secondBlockStateRoot = - "0xd86a520e49caf215e7e4028262924db50540a5b26e415ab7c944e46a0c01d704"; + "0x57a19f52a9ff4405428b3e605e136662d5c1b6be6f84f98f3b8c42ddac5139c2"; final Block secondBlock = gen.block(getBlockOptionsNoTransaction(firstBlock, secondBlockStateRoot)); @@ -278,7 +278,7 @@ public void reorgToShorterChain() { gen.block(getBlockOptionsNoTransaction(blockchain.getGenesisBlock(), firstBlockStateRoot)); final String secondBlockStateRoot = - "0x7e887f91d2a6205f4a643701aba022c2db0bac5ab235102ab7477edd7a8a4317"; + "0xb3d70bce4428fb9b4549240b2130c4eea12c4ea36ae13108ed21289366a8d65f"; final Block secondBlock = gen.block( getBlockOptionsWithTransaction( @@ -305,7 +305,7 @@ public void reorgToShorterChain() { .plus(blockchain.getBlockByNumber(2).get().getHeader().getDifficulty()); final String forkBlockStateRoot = - "0x486b886bde6472e8d706f8eb4fb6378ebbdceb4848a5a8d69a726575b22e41b6"; + "0x5c0adcdde38d38b4365c238c4ba05bf9ebfdf506f749b884b67003f375e43e4b"; final Block forkBlock = gen.block( getBlockOptionsNoTransactionWithDifficulty( @@ -355,7 +355,7 @@ public void reorgToLongerChain() { privateStateRootResolver, blockchain, STATE_ROOT_AFTER_TRANSACTION_APPENDED_TO_EMPTY_STATE); final String secondForkBlockStateRoot = - "0x2c37a360a700c614b10c980138f64be9ad66fc4a14cd5145199cd0d8ec43d51d"; + "0x76b8747d05fabcc87b2cfba519da0b1359b29fe7553bfd4837746edf31fb95fc"; final Block secondForkBlock = gen.block( getBlockOptionsNoTransactionWithDifficulty( @@ -373,7 +373,7 @@ public void reorgToLongerChain() { // Add another private transaction final String thirdForkBlockStateRoot = - "0x8fe42678733e6099e7b10b9b1d4684b8f2ce3d6479cb122ea12932ef304a1793"; + "0xcae9fa05107c1501c1962239f729d2f34186414abbaeb0dd1a3e0a6c899f79a3"; final Block thirdForkBlock = gen.block( getBlockOptionsWithTransactionAndDifficulty( @@ -385,7 +385,8 @@ public void reorgToLongerChain() { appendBlock(besuController, blockchain, protocolContext, thirdForkBlock); // Check that the private state did change after reorg - assertPrivateStateRoot(privateStateRootResolver, blockchain, EMPTY_ROOT_HASH); + assertPrivateStateRoot( + privateStateRootResolver, blockchain, STATE_ROOT_AFTER_TRANSACTION_APPENDED_TO_EMPTY_STATE); } @SuppressWarnings("unchecked") @@ -532,6 +533,7 @@ private BlockDataGenerator.BlockOptions getBlockOptionsWithTransactionAndDifficu private BlockDataGenerator.BlockOptions getBlockOptions( final BlockDataGenerator.BlockOptions blockOptions, final Block parentBlock) { return blockOptions + .setBaseFee(Optional.empty()) .setBlockNumber(parentBlock.getHeader().getNumber() + 1) .setParentHash(parentBlock.getHash()) .hasOmmers(false) diff --git a/besu/src/test/java/org/hyperledger/besu/services/BesuEventsImplTest.java b/besu/src/test/java/org/hyperledger/besu/services/BesuEventsImplTest.java index 36867e26d47..b6f3a71d899 100644 --- a/besu/src/test/java/org/hyperledger/besu/services/BesuEventsImplTest.java +++ b/besu/src/test/java/org/hyperledger/besu/services/BesuEventsImplTest.java @@ -36,6 +36,7 @@ import org.hyperledger.besu.ethereum.core.TransactionReceipt; import org.hyperledger.besu.ethereum.core.TransactionTestFixture; import org.hyperledger.besu.ethereum.core.Wei; +import org.hyperledger.besu.ethereum.core.fees.EIP1559; import org.hyperledger.besu.ethereum.eth.manager.EthContext; import org.hyperledger.besu.ethereum.eth.manager.EthMessages; import org.hyperledger.besu.ethereum.eth.manager.EthPeer; @@ -150,7 +151,7 @@ public void setUp() { syncState, Wei.ZERO, txPoolConfig, - Optional.empty()); + Optional.of(new EIP1559(0))); serviceImpl = new BesuEventsImpl(blockchain, blockBroadcaster, transactionPool, syncState); } diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/RpcMethod.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/RpcMethod.java index 752aa676d58..4c32ce91d25 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/RpcMethod.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/RpcMethod.java @@ -71,6 +71,7 @@ public enum RpcMethod { ETH_CHAIN_ID("eth_chainId"), ETH_COINBASE("eth_coinbase"), ETH_ESTIMATE_GAS("eth_estimateGas"), + ETH_FEE_HISTORY("eth_feeHistory"), ETH_GAS_PRICE("eth_gasPrice"), ETH_GET_BALANCE("eth_getBalance"), ETH_GET_BLOCK_BY_HASH("eth_getBlockByHash"), diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthFeeHistory.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthFeeHistory.java new file mode 100644 index 00000000000..65b879163fb --- /dev/null +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthFeeHistory.java @@ -0,0 +1,188 @@ +/* + * 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.internal.methods; + +import static java.util.stream.Collectors.toUnmodifiableList; + +import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.BlockParameter; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcError; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcErrorResponse; +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.ImmutableFeeHistoryResult; +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.Transaction; +import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; +import java.util.List; +import java.util.Optional; +import java.util.stream.LongStream; +import java.util.stream.Stream; + +public class EthFeeHistory implements JsonRpcMethod { + private final ProtocolSchedule protocolSchedule; + private final BlockchainQueries blockchainQueries; + private final Blockchain blockchain; + + public EthFeeHistory( + final ProtocolSchedule protocolSchedule, final BlockchainQueries blockchainQueries) { + this.protocolSchedule = protocolSchedule; + this.blockchainQueries = blockchainQueries; + this.blockchain = blockchainQueries.getBlockchain(); + } + + @Override + public String getName() { + return RpcMethod.ETH_FEE_HISTORY.getMethodName(); + } + + @Override + public JsonRpcResponse response(final JsonRpcRequestContext request) { + final Object requestId = request.getRequest().getId(); + + final long blockCount = request.getRequiredParameter(0, Long.class); + if (blockCount < 1 || blockCount > 1024) { + return new JsonRpcErrorResponse(requestId, JsonRpcError.INVALID_PARAMS); + } + final BlockParameter highestBlock = request.getRequiredParameter(1, BlockParameter.class); + final Optional> maybeRewardPercentiles = + request.getOptionalParameter(2, Double[].class).map(Arrays::asList); + + final long chainHeadBlockNumber = blockchain.getChainHeadBlockNumber(); + final long resolvedHighestBlockNumber = + highestBlock + .getNumber() + .orElse( + chainHeadBlockNumber /* both latest and pending use the head block until we have pending block support */); + + if (resolvedHighestBlockNumber > chainHeadBlockNumber) { + return new JsonRpcErrorResponse(requestId, JsonRpcError.INVALID_PARAMS); + } + + final long oldestBlock = Math.max(0, resolvedHighestBlockNumber - (blockCount - 1)); + + final List blockHeaders = + LongStream.range(oldestBlock, oldestBlock + blockCount) + .mapToObj(blockchain::getBlockHeader) + .flatMap(Optional::stream) + .collect(toUnmodifiableList()); + + // we return the base fees for the blocks requested and 1 more because we can always compute it + final List explicitlyRequestedBaseFees = + blockHeaders.stream() + .map(blockHeader -> blockHeader.getBaseFee().orElse(0L)) + .collect(toUnmodifiableList()); + final long nextBlockNumber = resolvedHighestBlockNumber + 1; + final Long nextBaseFee = + blockchain + .getBlockHeader(nextBlockNumber) + .map(blockHeader -> blockHeader.getBaseFee().orElse(0L)) + .orElseGet( + () -> + protocolSchedule + .getByBlockNumber(nextBlockNumber) + .getEip1559() + .map( + eip1559 -> { + final BlockHeader lastBlockHeader = + blockHeaders.get(blockHeaders.size() - 1); + return eip1559.computeBaseFee( + nextBlockNumber, + explicitlyRequestedBaseFees.get( + explicitlyRequestedBaseFees.size() - 1), + lastBlockHeader.getGasUsed(), + eip1559.targetGasUsed(lastBlockHeader)); + }) + .orElse(0L)); + + final List gasUsedRatios = + blockHeaders.stream() + .map(blockHeader -> blockHeader.getGasUsed() / (double) blockHeader.getGasLimit()) + .collect(toUnmodifiableList()); + + final Optional>> maybeRewards = + maybeRewardPercentiles.map( + rewardPercentiles -> + LongStream.range(oldestBlock, oldestBlock + blockCount) + .mapToObj(blockchain::getBlockByNumber) + .flatMap(Optional::stream) + .map( + block -> + computeRewards( + rewardPercentiles.stream().sorted().collect(toUnmodifiableList()), + block)) + .collect(toUnmodifiableList())); + + final ImmutableFeeHistoryResult.Builder feeHistoryResultBuilder = + ImmutableFeeHistoryResult.builder() + .oldestBlock(oldestBlock) + .baseFeePerGas( + Stream.concat(explicitlyRequestedBaseFees.stream(), Stream.of(nextBaseFee)) + .collect(toUnmodifiableList())) + .gasUsedRatio(gasUsedRatios); + maybeRewards.ifPresent(feeHistoryResultBuilder::reward); + return new JsonRpcSuccessResponse(requestId, feeHistoryResultBuilder.build()); + } + + private List computeRewards( + final List rewardPercentiles, final org.hyperledger.besu.ethereum.core.Block block) { + final List transactions = block.getBody().getTransactions(); + if (transactions.isEmpty()) { + // all 0's for empty block + return LongStream.generate(() -> 0) + .limit(rewardPercentiles.size()) + .boxed() + .collect(toUnmodifiableList()); + } + + final Optional baseFee = block.getHeader().getBaseFee(); + final List transactionsAscendingEffectiveGasFee = + transactions.stream() + .sorted( + Comparator.comparing( + transaction -> transaction.getEffectivePriorityFeePerGas(baseFee))) + .collect(toUnmodifiableList()); + + // We need to weight the percentile of rewards by the gas used in the transaction. + // That's why we're keeping track of the cumulative gas used and checking to see which + // percentile markers we've passed + final ArrayList rewards = new ArrayList<>(); + int rewardPercentileIndex = 0; + long gasUsed = 0; + for (final Transaction transaction : transactionsAscendingEffectiveGasFee) { + + gasUsed += + blockchainQueries + .transactionReceiptByTransactionHash(transaction.getHash()) + .get() + .getGasUsed(); + + while (rewardPercentileIndex < rewardPercentiles.size() + && 100.0 * gasUsed / block.getHeader().getGasUsed() + >= rewardPercentiles.get(rewardPercentileIndex)) { + rewards.add(transaction.getEffectivePriorityFeePerGas(baseFee)); + rewardPercentileIndex++; + } + } + return rewards; + } +} diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/FeeHistoryResult.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/FeeHistoryResult.java new file mode 100644 index 00000000000..7311c6e6c3a --- /dev/null +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/FeeHistoryResult.java @@ -0,0 +1,40 @@ +/* + * 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.internal.results; + +import java.util.List; +import javax.annotation.Nullable; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import org.immutables.value.Value; + +@Value.Immutable +@JsonInclude(JsonInclude.Include.NON_NULL) +public interface FeeHistoryResult { + + @JsonProperty("oldestBlock") + long getOldestBlock(); + + @JsonProperty("baseFeePerGas") + List getBaseFeePerGas(); + + @JsonProperty("gasUsedRatio") + List getGasUsedRatio(); + + @Nullable + @JsonProperty("reward") + List> getReward(); +} 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 bf2b691dded..08a9b2dfed6 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 @@ -23,6 +23,7 @@ import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.EthChainId; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.EthCoinbase; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.EthEstimateGas; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.EthFeeHistory; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.EthGasPrice; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.EthGetBalance; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.EthGetBlockByHash; @@ -128,6 +129,7 @@ protected Map create() { blockchainQueries.getWorldStateArchive(), protocolSchedule, privacyParameters)), + new EthFeeHistory(protocolSchedule, blockchainQueries), new EthGetCode(blockchainQueries, Optional.of(privacyParameters)), new EthGetLogs(blockchainQueries), new EthGetProof(blockchainQueries), diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonRpcHttpServiceTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonRpcHttpServiceTest.java index 57a065c13c7..4ca312e45bb 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonRpcHttpServiceTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonRpcHttpServiceTest.java @@ -1699,8 +1699,13 @@ private void assertTransactionResultMatchesTransaction( assertThat(result.getValue("to")).isNull(); } assertThat(Wei.fromHexString(result.getString("value"))).isEqualTo(transaction.getValue()); - assertThat(Wei.fromHexString(result.getString("gasPrice"))) - .isEqualTo(transaction.getGasPrice().get()); + assertThat(Optional.ofNullable(result.getString("gasPrice")).map(Wei::fromHexString)) + .isEqualTo(transaction.getGasPrice()); + assertThat(Optional.ofNullable(result.getString("maxFeePerGas")).map(Wei::fromHexString)) + .isEqualTo(transaction.getMaxFeePerGas()); + assertThat( + Optional.ofNullable(result.getString("maxPriorityFeePerGas")).map(Wei::fromHexString)) + .isEqualTo(transaction.getMaxPriorityFeePerGas()); assertThat(Long.decode(result.getString("gas"))).isEqualTo(transaction.getGasLimit()); assertThat(Bytes.fromHexString(result.getString("input"))).isEqualTo(transaction.getPayload()); } diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthFeeHistoryTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthFeeHistoryTest.java new file mode 100644 index 00000000000..9c166a39e0b --- /dev/null +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthFeeHistoryTest.java @@ -0,0 +1,182 @@ +/* + * 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.internal.methods; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.hyperledger.besu.ethereum.core.InMemoryKeyValueStorageProvider.createInMemoryBlockchain; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import org.hyperledger.besu.config.experimental.ExperimentalEIPs; +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.exception.InvalidJsonRpcParameters; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcError; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcErrorResponse; +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.FeeHistoryResult; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.ImmutableFeeHistoryResult; +import org.hyperledger.besu.ethereum.api.query.BlockchainQueries; +import org.hyperledger.besu.ethereum.chain.MutableBlockchain; +import org.hyperledger.besu.ethereum.core.Block; +import org.hyperledger.besu.ethereum.core.BlockDataGenerator; +import org.hyperledger.besu.ethereum.core.fees.EIP1559; +import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; +import org.hyperledger.besu.ethereum.mainnet.ProtocolSpec; +import org.hyperledger.besu.ethereum.storage.keyvalue.WorldStateKeyValueStorage; +import org.hyperledger.besu.ethereum.storage.keyvalue.WorldStatePreimageKeyValueStorage; +import org.hyperledger.besu.ethereum.worldstate.DefaultWorldStateArchive; +import org.hyperledger.besu.services.kvstore.InMemoryKeyValueStorage; + +import java.util.List; +import java.util.Optional; + +import org.junit.Before; +import org.junit.Test; + +public class EthFeeHistoryTest { + final BlockDataGenerator gen = new BlockDataGenerator(); + private MutableBlockchain blockchain; + private BlockchainQueries blockchainQueries; + private EthFeeHistory method; + private ProtocolSchedule protocolSchedule; + + @Before + public void setUp() { + protocolSchedule = mock(ProtocolSchedule.class); + final Block genesisBlock = gen.genesisBlock(); + blockchain = createInMemoryBlockchain(genesisBlock); + gen.blockSequence(genesisBlock, 10) + .forEach(block -> blockchain.appendBlock(block, gen.receipts(block))); + blockchainQueries = + new BlockchainQueries( + blockchain, + new DefaultWorldStateArchive( + new WorldStateKeyValueStorage(new InMemoryKeyValueStorage()), + new WorldStatePreimageKeyValueStorage(new InMemoryKeyValueStorage()))); + method = new EthFeeHistory(protocolSchedule, blockchainQueries); + } + + @Test + public void params() { + final ProtocolSpec londonSpec = mock(ProtocolSpec.class); + when(londonSpec.getEip1559()).thenReturn(Optional.of(new EIP1559(5))); + when(protocolSchedule.getByBlockNumber(anyLong())).thenReturn(londonSpec); + // should fail because no required params given + assertThatThrownBy(this::feeHistoryRequest).isInstanceOf(InvalidJsonRpcParameters.class); + // should fail because newestBlock not given + assertThatThrownBy(() -> feeHistoryRequest(1)).isInstanceOf(InvalidJsonRpcParameters.class); + // should fail because blockCount not given + assertThatThrownBy(() -> feeHistoryRequest("latest")) + .isInstanceOf(InvalidJsonRpcParameters.class); + // should pass because both required params given + feeHistoryRequest(1, "latest"); + // should pass because both required params and optional param given + feeHistoryRequest(1, "latest", new double[] {1, 20.4}); + } + + @Test + public void allFieldsPresentForLatestBlock() { + final ProtocolSpec londonSpec = mock(ProtocolSpec.class); + when(londonSpec.getEip1559()).thenReturn(Optional.of(new EIP1559(5))); + when(protocolSchedule.getByBlockNumber(eq(11L))).thenReturn(londonSpec); + assertThat( + ((JsonRpcSuccessResponse) feeHistoryRequest(1, "latest", new double[] {100.0})) + .getResult()) + .isEqualTo( + ImmutableFeeHistoryResult.builder() + .oldestBlock(10) + .baseFeePerGas(List.of(25496L, 28683L)) + .gasUsedRatio(List.of(0.9999999992132459)) + .reward(List.of(List.of(1524763764L))) + .build()); + } + + @Test + public void cantGetBlockHigherThanChainHead() { + final ProtocolSpec londonSpec = mock(ProtocolSpec.class); + when(londonSpec.getEip1559()).thenReturn(Optional.of(new EIP1559(5))); + when(protocolSchedule.getByBlockNumber(anyLong())).thenReturn(londonSpec); + assertThat(((JsonRpcErrorResponse) feeHistoryRequest(2, "11", new double[] {100.0})).getError()) + .isEqualTo(JsonRpcError.INVALID_PARAMS); + } + + @Test + public void blockCountBounds() { + final ProtocolSpec londonSpec = mock(ProtocolSpec.class); + when(londonSpec.getEip1559()).thenReturn(Optional.of(new EIP1559(5))); + when(protocolSchedule.getByBlockNumber(anyLong())).thenReturn(londonSpec); + assertThat( + ((JsonRpcErrorResponse) feeHistoryRequest(0, "latest", new double[] {100.0})) + .getError()) + .isEqualTo(JsonRpcError.INVALID_PARAMS); + assertThat( + ((JsonRpcErrorResponse) feeHistoryRequest(1025, "latest", new double[] {100.0})) + .getError()) + .isEqualTo(JsonRpcError.INVALID_PARAMS); + } + + @Test + public void doesntGoPastChainHeadWithHighBlockCount() { + final ProtocolSpec londonSpec = mock(ProtocolSpec.class); + when(londonSpec.getEip1559()).thenReturn(Optional.of(new EIP1559(5))); + when(protocolSchedule.getByBlockNumber(anyLong())).thenReturn(londonSpec); + final FeeHistoryResult result = + (ImmutableFeeHistoryResult) + ((JsonRpcSuccessResponse) feeHistoryRequest(20, "latest")).getResult(); + assertThat(result.getOldestBlock()).isEqualTo(0); + assertThat(result.getBaseFeePerGas()).hasSize(12); + assertThat(result.getGasUsedRatio()).hasSize(11); + assertThat(result.getReward()).isNull(); + } + + @Test + public void correctlyHandlesForkBlock() { + final ProtocolSpec londonSpec = mock(ProtocolSpec.class); + when(londonSpec.getEip1559()).thenReturn(Optional.of(new EIP1559(11))); + when(protocolSchedule.getByBlockNumber(anyLong())).thenReturn(londonSpec); + final FeeHistoryResult result = + (FeeHistoryResult) ((JsonRpcSuccessResponse) feeHistoryRequest(1, "latest")).getResult(); + assertThat(result.getBaseFeePerGas().get(1)) + .isEqualTo(ExperimentalEIPs.EIP1559_BASEFEE_DEFAULT_VALUE); + } + + @Test + public void allZeroPercentilesForZeroBlock() { + final ProtocolSpec londonSpec = mock(ProtocolSpec.class); + when(londonSpec.getEip1559()).thenReturn(Optional.of(new EIP1559(5))); + when(protocolSchedule.getByBlockNumber(anyLong())).thenReturn(londonSpec); + final BlockDataGenerator.BlockOptions blockOptions = BlockDataGenerator.BlockOptions.create(); + blockOptions.hasTransactions(false); + blockOptions.setParentHash(blockchain.getChainHeadHash()); + blockOptions.setBlockNumber(11); + final Block emptyBlock = gen.block(blockOptions); + blockchain.appendBlock(emptyBlock, gen.receipts(emptyBlock)); + final FeeHistoryResult result = + (FeeHistoryResult) + ((JsonRpcSuccessResponse) feeHistoryRequest(1, "latest", new double[] {100.0})) + .getResult(); + assertThat(result.getReward()).isEqualTo(List.of(List.of(0L))); + } + + private JsonRpcResponse feeHistoryRequest(final Object... params) { + return method.response( + new JsonRpcRequestContext(new JsonRpcRequest("2.0", "eth_feeHistory", params))); + } +} diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/TransactionCompleteResultTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/TransactionCompleteResultTest.java index ae8534041dc..412bbefd943 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/TransactionCompleteResultTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/TransactionCompleteResultTest.java @@ -24,10 +24,12 @@ import org.hyperledger.besu.ethereum.core.Wei; import org.hyperledger.besu.plugin.data.TransactionType; +import java.util.List; import java.util.Optional; -import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.common.collect.ImmutableList; import org.junit.Test; public class TransactionCompleteResultTest { @@ -62,7 +64,7 @@ public void eip1559TransactionFields() { } @Test - public void accessListTransactionFields() throws JsonProcessingException { + public void accessListTransactionFields() { final BlockDataGenerator gen = new BlockDataGenerator(); final Transaction transaction = gen.transaction(TransactionType.ACCESS_LIST); final TransactionCompleteResult transactionCompleteResult = @@ -78,18 +80,17 @@ public void accessListTransactionFields() throws JsonProcessingException { assertThat(transactionCompleteResult.getMaxFeePerGas()).isNull(); assertThat(transactionCompleteResult.getMaxPriorityFeePerGas()).isNull(); final ObjectMapper objectMapper = new ObjectMapper(); - final String jsonString = - objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(transactionCompleteResult); - - assertThat(jsonString) - .startsWith( - "{\n" - + " \"accessList\" : [ {\n" - + " \"address\" : \"0x47902028e61cfdc243d9d16008aabc9fb77cc723\",\n" - + " \"storageKeys\" : [ ]\n" - + " }, {\n" - + " \"address\" : \"0xa56017e14f1ce8b1698341734a6823ce02043e01\",\n" - + " \"storageKeys\" : [ \"0x6b544901214a2ddab82fec85c0b9fe0549c475be5b887bb4b8995b24fb5c6846\", \"0xf88b527b4f9d4c1391f1678b23ba4f9c9cd7bc93eb5776f4f036753448642946\" ]\n" - + " } ],"); + final JsonNode transactionCompleteResultJson = + objectMapper.valueToTree(transactionCompleteResult); + final List accessListJson = + ImmutableList.copyOf(transactionCompleteResultJson.get("accessList").elements()); + assertThat(accessListJson).hasSizeGreaterThan(0); + accessListJson.forEach( + accessListEntryJson -> { + assertThat(accessListEntryJson.get("address").asText()).matches("^0x\\X{40}$"); + ImmutableList.copyOf(accessListEntryJson.get("storageKeys").elements()) + .forEach( + storageKeyJson -> assertThat(storageKeyJson.asText()).matches("^0x\\X{64}$")); + }); } } diff --git a/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_feeHistory_fieldNamesWithReward.json b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_feeHistory_fieldNamesWithReward.json new file mode 100644 index 00000000000..5fb9038d758 --- /dev/null +++ b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_feeHistory_fieldNamesWithReward.json @@ -0,0 +1,45 @@ +{ + "request": { + "id": 28, + "jsonrpc": "2.0", + "method": "eth_feeHistory", + "params": [ + 2, + "latest", + [ + 0.0, + 100.0, + 4.0 + ] + ] + }, + "response": { + "jsonrpc": "2.0", + "id": 28, + "result": { + "oldestBlock": 31, + "baseFeePerGas": [ + 0, + 0, + 0 + ], + "gasUsedRatio": [ + 0.00773588677333021, + 0.007545537421791245 + ], + "reward": [ + [ + 1, + 1, + 1 + ], + [ + 1, + 1, + 1 + ] + ] + } + }, + "statusCode": 200 +} diff --git a/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_feeHistory_fieldNamesWithoutReward.json b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_feeHistory_fieldNamesWithoutReward.json new file mode 100644 index 00000000000..04c9e1dd19e --- /dev/null +++ b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_feeHistory_fieldNamesWithoutReward.json @@ -0,0 +1,28 @@ +{ + "request": { + "id": 28, + "jsonrpc": "2.0", + "method": "eth_feeHistory", + "params": [ + 2, + "latest" + ] + }, + "response": { + "jsonrpc": "2.0", + "id": 28, + "result": { + "oldestBlock": 31, + "baseFeePerGas": [ + 0, + 0, + 0 + ], + "gasUsedRatio": [ + 0.00773588677333021, + 0.007545537421791245 + ] + } + }, + "statusCode": 200 +} diff --git a/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/AbstractBlockCreator.java b/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/AbstractBlockCreator.java index fd33cf6d848..d1e126747cc 100644 --- a/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/AbstractBlockCreator.java +++ b/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/AbstractBlockCreator.java @@ -263,15 +263,13 @@ private ProcessableBlockHeader createPendingBlockHeader(final long timestamp) { final EIP1559 eip1559 = protocolSpec.getEip1559().orElseThrow(); if (eip1559.isForkBlock(newBlockNumber)) { gasLimit = gasLimit * eip1559.getFeeMarket().getSlackCoefficient(); - baseFee = eip1559.getFeeMarket().getInitialBasefee(); - } else { - baseFee = - eip1559.computeBaseFee( - newBlockNumber, - parentHeader.getBaseFee().orElseThrow(), - parentHeader.getGasUsed(), - eip1559.targetGasUsed(parentHeader)); } + baseFee = + eip1559.computeBaseFee( + newBlockNumber, + parentHeader.getBaseFee().orElse(0L), + parentHeader.getGasUsed(), + eip1559.targetGasUsed(parentHeader)); } return BlockHeaderBuilder.create() .parentHash(parentHeader.getHash()) diff --git a/ethereum/blockcreation/src/test/java/org/hyperledger/besu/ethereum/blockcreation/BlockTransactionSelectorTest.java b/ethereum/blockcreation/src/test/java/org/hyperledger/besu/ethereum/blockcreation/BlockTransactionSelectorTest.java index c49d90059c9..e8953906aa8 100644 --- a/ethereum/blockcreation/src/test/java/org/hyperledger/besu/ethereum/blockcreation/BlockTransactionSelectorTest.java +++ b/ethereum/blockcreation/src/test/java/org/hyperledger/besu/ethereum/blockcreation/BlockTransactionSelectorTest.java @@ -318,12 +318,25 @@ public void useSingleGasSpaceForAllTransactions() { final ProcessableBlockHeader blockHeader = createBlockWithGasLimit(300); final Address miningBeneficiary = AddressHelpers.ofValue(1); + final PendingTransactions pendingTransactions1559 = + new PendingTransactions( + TransactionPoolConfiguration.DEFAULT_TX_RETENTION_HOURS, + 5, + 5, + TestClock.fixed(), + metricsSystem, + () -> { + final BlockHeader mockBlockHeader = mock(BlockHeader.class); + when(mockBlockHeader.getBaseFee()).thenReturn(Optional.of(1L)); + return mockBlockHeader; + }, + TransactionPoolConfiguration.DEFAULT_PRICE_BUMP); final BlockTransactionSelector selector = new BlockTransactionSelector( transactionProcessor, blockchain, worldState, - pendingTransactions, + pendingTransactions1559, blockHeader, this::createReceipt, Wei.of(6), @@ -366,8 +379,8 @@ public void useSingleGasSpaceForAllTransactions() { TransactionProcessingResult.successful( new ArrayList<>(), 0, 0, Bytes.EMPTY, ValidationResult.valid())); - pendingTransactions.addRemoteTransaction(fillingLegacyTx); - pendingTransactions.addRemoteTransaction(extraEIP1559Tx); + pendingTransactions1559.addRemoteTransaction(fillingLegacyTx); + pendingTransactions1559.addRemoteTransaction(extraEIP1559Tx); final BlockTransactionSelector.TransactionSelectionResults results = selector.buildTransactionListForBlock(); diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/Transaction.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/Transaction.java index 56ac5ba3b2c..e99a9cec753 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/Transaction.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/Transaction.java @@ -351,11 +351,26 @@ public boolean hasCostParams() { return Arrays.asList(getGasPrice(), getMaxFeePerGas(), getMaxPriorityFeePerGas()).stream() .flatMap(Optional::stream) .map(Quantity::getAsBigInteger) - .filter(q -> q.longValue() > 0L) - .findAny() - .isPresent(); + .anyMatch(q -> q.longValue() > 0L); } + public long getEffectivePriorityFeePerGas(final Optional maybeBaseFee) { + return maybeBaseFee + .map( + baseFee -> { + if (getType().supports1559FeeMarket()) { + return Math.min( + getMaxPriorityFeePerGas().get().getAsBigInteger().longValue(), + getMaxFeePerGas().get().getAsBigInteger().longValue() - baseFee); + } else { + return getGasPrice().get().getValue().longValue() - baseFee; + } + }) + .map( + maybeNegativeEffectivePriorityFeePerGas -> + Math.max(0, maybeNegativeEffectivePriorityFeePerGas)) + .orElseGet(() -> getGasPrice().get().getValue().longValue()); + } /** * Returns the transaction gas limit. * diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/fees/EIP1559.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/fees/EIP1559.java index d8b57ece119..20888cc1b01 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/fees/EIP1559.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/fees/EIP1559.java @@ -39,6 +39,10 @@ public long computeBaseFee( final long parentBaseFee, final long parentBlockGasUsed, final long targetGasUsed) { + if (isForkBlock(blockNumber)) { + return getFeeMarket().getInitialBasefee(); + } + long gasDelta, feeDelta, baseFee; if (parentBlockGasUsed == targetGasUsed) { return parentBaseFee; diff --git a/ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/BlockDataGenerator.java b/ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/BlockDataGenerator.java index 675eb44defe..8320004adc2 100644 --- a/ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/BlockDataGenerator.java +++ b/ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/BlockDataGenerator.java @@ -274,28 +274,30 @@ public BlockHeader header(final long number, final BlockBody body, final BlockOp final int gasLimit = random.nextInt() & Integer.MAX_VALUE; final int gasUsed = Math.max(0, gasLimit - 1); final long blockNonce = random.nextLong(); - - return BlockHeaderBuilder.create() - .parentHash(options.getParentHash(hash())) - .ommersHash(BodyValidation.ommersHash(body.getOmmers())) - .coinbase(options.getCoinbase(address())) - .stateRoot(options.getStateRoot(hash())) - .transactionsRoot(BodyValidation.transactionsRoot(body.getTransactions())) - .receiptsRoot(options.getReceiptsRoot(hash())) - .logsBloom(options.getLogsBloom(logsBloom())) - .difficulty(options.getDifficulty(Difficulty.of(uint256(4)))) - .number(number) - .gasLimit(gasLimit) - .gasUsed(options.getGasUsed(gasUsed)) - .timestamp( - options - .getTimestamp() - .orElse(Instant.now().truncatedTo(ChronoUnit.SECONDS).getEpochSecond())) - .extraData(options.getExtraData(bytes32())) - .mixHash(hash()) - .nonce(blockNonce) - .blockHeaderFunctions(options.getBlockHeaderFunctions(new MainnetBlockHeaderFunctions())) - .buildBlockHeader(); + final BlockHeaderBuilder blockHeaderBuilder = + BlockHeaderBuilder.create() + .parentHash(options.getParentHash(hash())) + .ommersHash(BodyValidation.ommersHash(body.getOmmers())) + .coinbase(options.getCoinbase(address())) + .stateRoot(options.getStateRoot(hash())) + .transactionsRoot(BodyValidation.transactionsRoot(body.getTransactions())) + .receiptsRoot(options.getReceiptsRoot(hash())) + .logsBloom(options.getLogsBloom(logsBloom())) + .difficulty(options.getDifficulty(Difficulty.of(uint256(4)))) + .number(number) + .gasLimit(gasLimit) + .gasUsed(options.getGasUsed(gasUsed)) + .timestamp( + options + .getTimestamp() + .orElse(Instant.now().truncatedTo(ChronoUnit.SECONDS).getEpochSecond())) + .extraData(options.getExtraData(bytes32())) + .mixHash(hash()) + .nonce(blockNonce) + .blockHeaderFunctions( + options.getBlockHeaderFunctions(new MainnetBlockHeaderFunctions())); + options.getBaseFee(Optional.of(uint256(2).toLong())).ifPresent(blockHeaderBuilder::baseFee); + return blockHeaderBuilder.buildBlockHeader(); } public BlockBody body() { @@ -372,7 +374,7 @@ private Transaction accessListTransaction(final Bytes payload, final Address to) return Transaction.builder() .type(TransactionType.ACCESS_LIST) .nonce(positiveLong()) - .gasPrice(Wei.wrap(bytes32())) + .gasPrice(Wei.wrap(bytesValue(4))) .gasLimit(positiveLong()) .to(to) .value(Wei.wrap(bytes32())) @@ -399,8 +401,8 @@ private Transaction eip1559Transaction(final Bytes payload, final Address to) { return Transaction.builder() .type(TransactionType.EIP1559) .nonce(positiveLong()) - .maxPriorityFeePerGas(Wei.wrap(bytes32())) - .maxFeePerGas(Wei.wrap(bytes32())) + .maxPriorityFeePerGas(Wei.wrap(bytesValue(4))) + .maxFeePerGas(Wei.wrap(bytesValue(4))) .gasLimit(positiveLong()) .to(to) .value(Wei.of(positiveLong())) @@ -413,7 +415,7 @@ private Transaction frontierTransaction(final Bytes payload, final Address to) { return Transaction.builder() .type(TransactionType.FRONTIER) .nonce(positiveLong()) - .gasPrice(Wei.wrap(bytes32())) + .gasPrice(Wei.wrap(bytesValue(4))) .gasLimit(positiveLong()) .to(to) .value(Wei.wrap(bytes32())) @@ -618,6 +620,7 @@ public static class BlockOptions { private boolean hasTransactions = true; private TransactionType[] transactionTypes = TransactionType.values(); private Optional
coinbase = Optional.empty(); + private Optional> maybeBaseFee = Optional.empty(); public static BlockOptions create() { return new BlockOptions(); @@ -770,5 +773,14 @@ public BlockOptions setCoinbase(final Address coinbase) { public Address getCoinbase(final Address defaultValue) { return coinbase.orElse(defaultValue); } + + public Optional getBaseFee(final Optional defaultValue) { + return maybeBaseFee.orElse(defaultValue); + } + + public BlockOptions setBaseFee(final Optional baseFee) { + this.maybeBaseFee = Optional.of(baseFee); + return this; + } } } diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/core/fees/EIP1559Test.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/core/fees/EIP1559Test.java index 02eeed6c6c1..25f2e5a37cb 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/core/fees/EIP1559Test.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/core/fees/EIP1559Test.java @@ -29,7 +29,7 @@ public class EIP1559Test { public void assertThatBaseFeeDecreasesWhenBelowTargetGasUsed() { assertThat( eip1559.computeBaseFee( - FORK_BLOCK, + FORK_BLOCK + 1, feeMarket.getInitialBasefee(), TARGET_GAS_USED - 1000000L, TARGET_GAS_USED)) @@ -41,7 +41,7 @@ public void assertThatBaseFeeDecreasesWhenBelowTargetGasUsed() { public void assertThatBaseFeeIncreasesWhenAboveTargetGasUsed() { assertThat( eip1559.computeBaseFee( - FORK_BLOCK, + FORK_BLOCK + 1, feeMarket.getInitialBasefee(), TARGET_GAS_USED + 1000000L, TARGET_GAS_USED)) diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/PendingTransactions.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/PendingTransactions.java index 6b142c1ec3b..bb77356414f 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/PendingTransactions.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/PendingTransactions.java @@ -28,7 +28,6 @@ import org.hyperledger.besu.ethereum.core.Wei; import org.hyperledger.besu.ethereum.transaction.TransactionInvalidReason; import org.hyperledger.besu.metrics.BesuMetricCategory; -import org.hyperledger.besu.plugin.data.TransactionType; import org.hyperledger.besu.plugin.services.MetricsSystem; import org.hyperledger.besu.plugin.services.metrics.Counter; import org.hyperledger.besu.plugin.services.metrics.LabelledMetric; @@ -108,7 +107,7 @@ public class PendingTransactions { .orElse(transactionInfo.getGasPrice().toLong())) .thenComparing(TransactionInfo::getSequence) .reversed()); - private Optional baseFee = Optional.empty(); + private Optional baseFee; private final Map transactionsBySender = new ConcurrentHashMap<>(); @@ -326,11 +325,15 @@ public TransactionInfo next() { // there are both static and dynamic txs remaining so we need to compare them by their // effective priority fees final long dynamicRangeEffectivePriorityFee = - effectivePriorityFeePerGas( - currentDynamicRangeTransaction.get().getTransaction(), baseFee); + currentDynamicRangeTransaction + .get() + .getTransaction() + .getEffectivePriorityFeePerGas(baseFee); final long staticRangeEffectivePriorityFee = - effectivePriorityFeePerGas( - currentStaticRangeTransaction.get().getTransaction(), baseFee); + currentStaticRangeTransaction + .get() + .getTransaction() + .getEffectivePriorityFeePerGas(baseFee); final TransactionInfo best; if (dynamicRangeEffectivePriorityFee > staticRangeEffectivePriorityFee) { best = currentDynamicRangeTransaction.get(); @@ -394,7 +397,7 @@ private TransactionAddedStatus addTransaction(final TransactionInfo transactionI .build() .min( Comparator.comparing( - txInfo -> effectivePriorityFeePerGas(txInfo.getTransaction(), baseFee))) + txInfo -> txInfo.getTransaction().getEffectivePriorityFeePerGas(baseFee))) // safe because we just added a tx to the pool so we're guaranteed to have one .get(); doRemoveTransaction(toRemove.getTransaction(), false); @@ -412,28 +415,13 @@ private boolean isInStaticRange(final Transaction transaction, final Optional - effectivePriorityFeePerGas(transaction, baseFee) + transaction.getEffectivePriorityFeePerGas(baseFee) >= maxPriorityFeePerGas.getValue().longValue()) .orElse( // non-eip-1559 txs can't be in static range false); } - private long effectivePriorityFeePerGas( - final Transaction transaction, final Optional curBaseFee) { - final long maybeNegativePriorityFeePerGas; - if (transaction.getType().equals(TransactionType.EIP1559)) { - maybeNegativePriorityFeePerGas = - Math.min( - transaction.getMaxPriorityFeePerGas().get().getValue().longValue(), - transaction.getMaxFeePerGas().get().getValue().longValue() - curBaseFee.orElse(0L)); - } else { - maybeNegativePriorityFeePerGas = - transaction.getGasPrice().get().getValue().longValue() - curBaseFee.orElse(0L); - } - return maybeNegativePriorityFeePerGas; - } - public void updateBaseFee(final Long newBaseFee) { LOG.trace("Updating base fee from {} to {}", this.baseFee, newBaseFee); if (this.baseFee.orElse(0L).equals(newBaseFee)) {