eth_feeHistory (#2466)

Signed-off-by: Ratan Rai Sur <ratan.r.sur@gmail.com>
pull/2475/head
Ratan (Rai) Sur 3 years ago committed by GitHub
parent 2d0732a768
commit b55b076a91
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      CHANGELOG.md
  2. 18
      besu/src/test/java/org/hyperledger/besu/PrivacyReorgTest.java
  3. 3
      besu/src/test/java/org/hyperledger/besu/services/BesuEventsImplTest.java
  4. 1
      ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/RpcMethod.java
  5. 188
      ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthFeeHistory.java
  6. 40
      ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/FeeHistoryResult.java
  7. 2
      ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/EthJsonRpcMethods.java
  8. 9
      ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonRpcHttpServiceTest.java
  9. 182
      ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthFeeHistoryTest.java
  10. 31
      ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/TransactionCompleteResultTest.java
  11. 45
      ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_feeHistory_fieldNamesWithReward.json
  12. 28
      ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_feeHistory_fieldNamesWithoutReward.json
  13. 14
      ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/AbstractBlockCreator.java
  14. 19
      ethereum/blockcreation/src/test/java/org/hyperledger/besu/ethereum/blockcreation/BlockTransactionSelectorTest.java
  15. 21
      ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/Transaction.java
  16. 4
      ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/fees/EIP1559.java
  17. 64
      ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/BlockDataGenerator.java
  18. 4
      ethereum/core/src/test/java/org/hyperledger/besu/ethereum/core/fees/EIP1559Test.java
  19. 34
      ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/PendingTransactions.java

@ -2,6 +2,8 @@
## 21.7.0-RC2
### Additions and Improvements
- eth_feeHistory API for wallet providers [\#2466](https://github.com/hyperledger/besu/pull/2466)
### Bug Fixes
- Ibft2 could create invalid RoundChange messages in some circumstances containing duplicate prepares [\#2449](https://github.com/hyperledger/besu/pull/2449)

@ -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 class PrivacyReorgTest {
.contains(expected);
final String secondBlockStateRoot =
"0xd86a520e49caf215e7e4028262924db50540a5b26e415ab7c944e46a0c01d704";
"0x57a19f52a9ff4405428b3e605e136662d5c1b6be6f84f98f3b8c42ddac5139c2";
final Block secondBlock =
gen.block(getBlockOptionsNoTransaction(firstBlock, secondBlockStateRoot));
@ -278,7 +278,7 @@ public class PrivacyReorgTest {
gen.block(getBlockOptionsNoTransaction(blockchain.getGenesisBlock(), firstBlockStateRoot));
final String secondBlockStateRoot =
"0x7e887f91d2a6205f4a643701aba022c2db0bac5ab235102ab7477edd7a8a4317";
"0xb3d70bce4428fb9b4549240b2130c4eea12c4ea36ae13108ed21289366a8d65f";
final Block secondBlock =
gen.block(
getBlockOptionsWithTransaction(
@ -305,7 +305,7 @@ public class PrivacyReorgTest {
.plus(blockchain.getBlockByNumber(2).get().getHeader().getDifficulty());
final String forkBlockStateRoot =
"0x486b886bde6472e8d706f8eb4fb6378ebbdceb4848a5a8d69a726575b22e41b6";
"0x5c0adcdde38d38b4365c238c4ba05bf9ebfdf506f749b884b67003f375e43e4b";
final Block forkBlock =
gen.block(
getBlockOptionsNoTransactionWithDifficulty(
@ -355,7 +355,7 @@ public class PrivacyReorgTest {
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 class PrivacyReorgTest {
// Add another private transaction
final String thirdForkBlockStateRoot =
"0x8fe42678733e6099e7b10b9b1d4684b8f2ce3d6479cb122ea12932ef304a1793";
"0xcae9fa05107c1501c1962239f729d2f34186414abbaeb0dd1a3e0a6c899f79a3";
final Block thirdForkBlock =
gen.block(
getBlockOptionsWithTransactionAndDifficulty(
@ -385,7 +385,8 @@ public class PrivacyReorgTest {
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 @@ public class PrivacyReorgTest {
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)

@ -36,6 +36,7 @@ import org.hyperledger.besu.ethereum.core.MutableWorldState;
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 class BesuEventsImplTest {
syncState,
Wei.ZERO,
txPoolConfig,
Optional.empty());
Optional.of(new EIP1559(0)));
serviceImpl = new BesuEventsImpl(blockchain, blockBroadcaster, transactionPool, syncState);
}

@ -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"),

@ -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<List<Double>> 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<BlockHeader> 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<Long> 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<Double> gasUsedRatios =
blockHeaders.stream()
.map(blockHeader -> blockHeader.getGasUsed() / (double) blockHeader.getGasLimit())
.collect(toUnmodifiableList());
final Optional<List<List<Long>>> 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<Long> computeRewards(
final List<Double> rewardPercentiles, final org.hyperledger.besu.ethereum.core.Block block) {
final List<Transaction> 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<Long> baseFee = block.getHeader().getBaseFee();
final List<Transaction> 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<Long> 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;
}
}

@ -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<Long> getBaseFeePerGas();
@JsonProperty("gasUsedRatio")
List<Double> getGasUsedRatio();
@Nullable
@JsonProperty("reward")
List<List<Long>> getReward();
}

@ -23,6 +23,7 @@ import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.EthCall;
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 @@ public class EthJsonRpcMethods extends ApiGroupJsonRpcMethods {
blockchainQueries.getWorldStateArchive(),
protocolSchedule,
privacyParameters)),
new EthFeeHistory(protocolSchedule, blockchainQueries),
new EthGetCode(blockchainQueries, Optional.of(privacyParameters)),
new EthGetLogs(blockchainQueries),
new EthGetProof(blockchainQueries),

@ -1699,8 +1699,13 @@ public class JsonRpcHttpServiceTest extends JsonRpcHttpServiceTestBase {
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());
}

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

@ -24,10 +24,12 @@ import org.hyperledger.besu.ethereum.core.TransactionTestFixture;
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 class TransactionCompleteResultTest {
}
@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 class TransactionCompleteResultTest {
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<JsonNode> 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}$"));
});
}
}

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

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

@ -263,15 +263,13 @@ public abstract class AbstractBlockCreator implements AsyncBlockCreator {
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())

@ -318,12 +318,25 @@ public class BlockTransactionSelectorTest {
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 class BlockTransactionSelectorTest {
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();

@ -351,11 +351,26 @@ public class Transaction implements org.hyperledger.besu.plugin.data.Transaction
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<Long> 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.
*

@ -39,6 +39,10 @@ public class EIP1559 {
final long parentBaseFee,
final long parentBlockGasUsed,
final long targetGasUsed) {
if (isForkBlock(blockNumber)) {
return getFeeMarket().getInitialBasefee();
}
long gasDelta, feeDelta, baseFee;
if (parentBlockGasUsed == targetGasUsed) {
return parentBaseFee;

@ -274,28 +274,30 @@ public class BlockDataGenerator {
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 @@ public class BlockDataGenerator {
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 @@ public class BlockDataGenerator {
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 @@ public class BlockDataGenerator {
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 class BlockDataGenerator {
private boolean hasTransactions = true;
private TransactionType[] transactionTypes = TransactionType.values();
private Optional<Address> coinbase = Optional.empty();
private Optional<Optional<Long>> maybeBaseFee = Optional.empty();
public static BlockOptions create() {
return new BlockOptions();
@ -770,5 +773,14 @@ public class BlockDataGenerator {
public Address getCoinbase(final Address defaultValue) {
return coinbase.orElse(defaultValue);
}
public Optional<Long> getBaseFee(final Optional<Long> defaultValue) {
return maybeBaseFee.orElse(defaultValue);
}
public BlockOptions setBaseFee(final Optional<Long> baseFee) {
this.maybeBaseFee = Optional.of(baseFee);
return this;
}
}
}

@ -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 class EIP1559Test {
public void assertThatBaseFeeIncreasesWhenAboveTargetGasUsed() {
assertThat(
eip1559.computeBaseFee(
FORK_BLOCK,
FORK_BLOCK + 1,
feeMarket.getInitialBasefee(),
TARGET_GAS_USED + 1000000L,
TARGET_GAS_USED))

@ -28,7 +28,6 @@ import org.hyperledger.besu.ethereum.core.Transaction;
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<Long> baseFee = Optional.empty();
private Optional<Long> baseFee;
private final Map<Address, TransactionsForSenderInfo> transactionsBySender =
new ConcurrentHashMap<>();
@ -326,11 +325,15 @@ public class PendingTransactions {
// 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 @@ public class PendingTransactions {
.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 @@ public class PendingTransactions {
.getMaxPriorityFeePerGas()
.map(
maxPriorityFeePerGas ->
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<Long> 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)) {

Loading…
Cancel
Save