Skip to content
/ besu Public
forked from hyperledger/besu

Commit

Permalink
Add blobs to eth_feeHistory (hyperledger#6679)
Browse files Browse the repository at this point in the history
Signed-off-by: Gabriel-Trintinalia <gabriel.trintinalia@consensys.net>
Signed-off-by: amsmota <antonio.mota@citi.com>
  • Loading branch information
Gabriel-Trintinalia authored and amsmota committed Apr 16, 2024
1 parent 096d1f4 commit 911b8a7
Show file tree
Hide file tree
Showing 6 changed files with 164 additions and 30 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
- RocksDB database metadata refactoring [#6555](https://github.com/hyperledger/besu/pull/6555)
- Make layered txpool aware of minGasPrice and minPriorityFeePerGas dynamic options [#6611](https://github.com/hyperledger/besu/pull/6611)
- Update commons-compress to 1.26.0 [#6648](https://github.com/hyperledger/besu/pull/6648)
- Add blobs to `eth_feeHistory` [#6679](https://github.com/hyperledger/besu/pull/6679)

### Bug fixes

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
package org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods;

import static java.util.stream.Collectors.toUnmodifiableList;
import static org.hyperledger.besu.ethereum.mainnet.feemarket.ExcessBlobGasCalculator.calculateExcessBlobGasForParent;

import org.hyperledger.besu.datatypes.Hash;
import org.hyperledger.besu.datatypes.Wei;
Expand All @@ -36,14 +37,17 @@
import org.hyperledger.besu.ethereum.core.Transaction;
import org.hyperledger.besu.ethereum.core.TransactionReceipt;
import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule;
import org.hyperledger.besu.ethereum.mainnet.ProtocolSpec;
import org.hyperledger.besu.ethereum.mainnet.feemarket.BaseFeeMarket;
import org.hyperledger.besu.ethereum.mainnet.feemarket.FeeMarket;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.LongStream;
import java.util.stream.Stream;

Expand Down Expand Up @@ -104,15 +108,23 @@ public JsonRpcResponse response(final JsonRpcRequestContext request) {

final List<BlockHeader> blockHeaderRange = getBlockHeaders(firstBlock, lastBlock);
final List<Wei> requestedBaseFees = getBaseFees(blockHeaderRange);
final List<Wei> requestedBlobBaseFees = getBlobBaseFees(blockHeaderRange);
final Wei nextBaseFee =
getNextBaseFee(highestBlockNumber, chainHeadHeader, requestedBaseFees, blockHeaderRange);
final List<Double> gasUsedRatios = getGasUsedRatios(blockHeaderRange);
final List<Double> blobGasUsedRatios = getBlobGasUsedRatios(blockHeaderRange);
final Optional<List<List<Wei>>> maybeRewards =
maybeRewardPercentiles.map(rewards -> getRewards(rewards, blockHeaderRange));
return new JsonRpcSuccessResponse(
requestId,
createFeeHistoryResult(
firstBlock, requestedBaseFees, nextBaseFee, gasUsedRatios, maybeRewards));
firstBlock,
requestedBaseFees,
requestedBlobBaseFees,
nextBaseFee,
gasUsedRatios,
blobGasUsedRatios,
maybeRewards));
}

private Wei getNextBaseFee(
Expand Down Expand Up @@ -326,31 +338,88 @@ private List<BlockHeader> getBlockHeaders(final long oldestBlock, final long las
}

private List<Wei> getBaseFees(final List<BlockHeader> blockHeaders) {
// we return the base fees for the blocks requested and 1 more because we can always compute it
return blockHeaders.stream()
.map(blockHeader -> blockHeader.getBaseFee().orElse(Wei.ZERO))
.toList();
}

private List<Wei> getBlobBaseFees(final List<BlockHeader> blockHeaders) {
if (blockHeaders.isEmpty()) {
return Collections.emptyList();
}
// Calculate the BlobFee for the requested range
List<Wei> baseFeesPerBlobGas =
blockHeaders.stream().map(this::getBlobGasFee).collect(Collectors.toList());

// Calculate the next blob base fee and add it to the list
Wei nextBlobBaseFee = getNextBlobFee(blockHeaders.get(blockHeaders.size() - 1));
baseFeesPerBlobGas.add(nextBlobBaseFee);

return baseFeesPerBlobGas;
}

private Wei getBlobGasFee(final BlockHeader header) {
return blockchain
.getBlockHeader(header.getParentHash())
.map(parent -> getBlobGasFee(protocolSchedule.getByBlockHeader(header), parent))
.orElse(Wei.ZERO);
}

private Wei getBlobGasFee(final ProtocolSpec spec, final BlockHeader parent) {
return spec.getFeeMarket().blobGasPricePerGas(calculateExcessBlobGasForParent(spec, parent));
}

private Wei getNextBlobFee(final BlockHeader header) {
// Attempt to retrieve the next header based on the current header's number.
long nextBlockNumber = header.getNumber() + 1;
return blockchain
.getBlockHeader(nextBlockNumber)
.map(nextHeader -> getBlobGasFee(protocolSchedule.getByBlockHeader(nextHeader), header))
// If the next header is not present, calculate the fee using the current time.
.orElseGet(
() ->
getBlobGasFee(
protocolSchedule.getForNextBlockHeader(header, System.currentTimeMillis()),
header));
}

private List<Double> getGasUsedRatios(final List<BlockHeader> blockHeaders) {
return blockHeaders.stream()
.map(blockHeader -> blockHeader.getGasUsed() / (double) blockHeader.getGasLimit())
.toList();
}

private List<Double> getBlobGasUsedRatios(final List<BlockHeader> blockHeaders) {
return blockHeaders.stream().map(this::calculateBlobGasUsedRatio).toList();
}

private double calculateBlobGasUsedRatio(final BlockHeader blockHeader) {
ProtocolSpec spec = protocolSchedule.getByBlockHeader(blockHeader);
long blobGasUsed = blockHeader.getBlobGasUsed().orElse(0L);
double currentBlobGasLimit = spec.getGasLimitCalculator().currentBlobGasLimit();
if (currentBlobGasLimit == 0) {
return 0;
}
return blobGasUsed / currentBlobGasLimit;
}

private FeeHistory.FeeHistoryResult createFeeHistoryResult(
final long oldestBlock,
final List<Wei> explicitlyRequestedBaseFees,
final List<Wei> requestedBlobBaseFees,
final Wei nextBaseFee,
final List<Double> gasUsedRatios,
final List<Double> blobGasUsedRatio,
final Optional<List<List<Wei>>> maybeRewards) {
return FeeHistory.FeeHistoryResult.from(
ImmutableFeeHistory.builder()
.oldestBlock(oldestBlock)
.baseFeePerGas(
Stream.concat(explicitlyRequestedBaseFees.stream(), Stream.of(nextBaseFee))
.collect(toUnmodifiableList()))
.baseFeePerBlobGas(requestedBlobBaseFees)
.gasUsedRatio(gasUsedRatios)
.blobGasUsedRatio(blobGasUsedRatio)
.reward(maybeRewards)
.build());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,12 @@ public interface FeeHistory {

List<Wei> getBaseFeePerGas();

List<Wei> getBaseFeePerBlobGas();

List<Double> getGasUsedRatio();

List<Double> getBlobGasUsedRatio();

Optional<List<List<Wei>>> getReward();

@Value.Immutable
Expand All @@ -47,9 +51,15 @@ interface FeeHistoryResult {
@JsonProperty("baseFeePerGas")
List<String> getBaseFeePerGas();

@JsonProperty("baseFeePerBlobGas")
List<String> getBaseFeePerBlobGas();

@JsonProperty("gasUsedRatio")
List<Double> getGasUsedRatio();

@JsonProperty("blobGasUsedRatio")
List<Double> getBlobGasUsedRatio();

@Nullable
@JsonProperty("reward")
List<List<String>> getReward();
Expand All @@ -60,7 +70,9 @@ static FeeHistoryResult from(final FeeHistory feeHistory) {
feeHistory.getBaseFeePerGas().stream()
.map(Quantity::create)
.collect(toUnmodifiableList()),
feeHistory.getBaseFeePerBlobGas().stream().map(Quantity::create).toList(),
feeHistory.getGasUsedRatio(),
feeHistory.getBlobGasUsedRatio(),
feeHistory
.getReward()
.map(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import org.hyperledger.besu.consensus.merge.blockcreation.MergeCoordinator;
import org.hyperledger.besu.datatypes.Hash;
import org.hyperledger.besu.datatypes.Wei;
import org.hyperledger.besu.ethereum.GasLimitCalculator;
import org.hyperledger.besu.ethereum.api.ApiConfiguration;
import org.hyperledger.besu.ethereum.api.ImmutableApiConfiguration;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequest;
Expand All @@ -47,9 +48,12 @@
import org.hyperledger.besu.ethereum.core.BlockHeader;
import org.hyperledger.besu.ethereum.core.Transaction;
import org.hyperledger.besu.ethereum.core.TransactionReceipt;
import org.hyperledger.besu.ethereum.mainnet.CancunTargetingGasLimitCalculator;
import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule;
import org.hyperledger.besu.ethereum.mainnet.ProtocolSpec;
import org.hyperledger.besu.ethereum.mainnet.feemarket.FeeMarket;
import org.hyperledger.besu.evm.gascalculator.CancunGasCalculator;
import org.hyperledger.besu.evm.gascalculator.LondonGasCalculator;

import java.util.ArrayList;
import java.util.Arrays;
Expand Down Expand Up @@ -80,6 +84,8 @@ public void setUp() {
miningCoordinator = mock(MergeCoordinator.class);
when(miningCoordinator.getMinPriorityFeePerGas()).thenReturn(Wei.ONE);

mockFork();

method =
new EthFeeHistory(
protocolSchedule,
Expand All @@ -90,9 +96,6 @@ public void setUp() {

@Test
public void params() {
final ProtocolSpec londonSpec = mock(ProtocolSpec.class);
when(londonSpec.getFeeMarket()).thenReturn(FeeMarket.london(5));
when(protocolSchedule.getForNextBlockHeader(any(), anyLong())).thenReturn(londonSpec);
// should fail because no required params given
assertThatThrownBy(this::feeHistoryRequest).isInstanceOf(InvalidJsonRpcParameters.class);
// should fail because newestBlock not given
Expand All @@ -110,12 +113,7 @@ public void params() {

@Test
public void allFieldsPresentForLatestBlock() {
final ProtocolSpec londonSpec = mock(ProtocolSpec.class);
when(londonSpec.getFeeMarket()).thenReturn(FeeMarket.london(5));
when(protocolSchedule.getForNextBlockHeader(
eq(blockchain.getChainHeadHeader()),
eq(blockchain.getChainHeadHeader().getTimestamp())))
.thenReturn(londonSpec);

final Object latest =
((JsonRpcSuccessResponse) feeHistoryRequest("0x1", "latest", new double[] {100.0}))
.getResult();
Expand All @@ -126,6 +124,8 @@ public void allFieldsPresentForLatestBlock() {
.oldestBlock(10)
.baseFeePerGas(List.of(Wei.of(25496L), Wei.of(28683L)))
.gasUsedRatio(List.of(0.9999999992132459))
.baseFeePerBlobGas(List.of(Wei.of(0), Wei.of(0)))
.blobGasUsedRatio(List.of(0.0))
.reward(List.of(List.of(Wei.of(1524763764L))))
.build()));
}
Expand Down Expand Up @@ -250,29 +250,19 @@ public void blockCountBounds() {

@Test
public void doesntGoPastChainHeadWithHighBlockCount() {
final ProtocolSpec londonSpec = mock(ProtocolSpec.class);
when(londonSpec.getFeeMarket()).thenReturn(FeeMarket.london(5));
when(protocolSchedule.getForNextBlockHeader(
eq(blockchain.getChainHeadHeader()),
eq(blockchain.getChainHeadHeader().getTimestamp())))
.thenReturn(londonSpec);
final FeeHistory.FeeHistoryResult result =
(ImmutableFeeHistoryResult)
((JsonRpcSuccessResponse) feeHistoryRequest("0x14", "latest")).getResult();
assertThat(Long.decode(result.getOldestBlock())).isEqualTo(0);
assertThat(result.getBaseFeePerGas()).hasSize(12);
assertThat(result.getGasUsedRatio()).hasSize(11);
assertThat(result.getBaseFeePerBlobGas()).hasSize(12);
assertThat(result.getBlobGasUsedRatio()).hasSize(11);
assertThat(result.getReward()).isNull();
}

@Test
public void feeValuesAreInTheBlockCountAndHighestBlock() {
final ProtocolSpec londonSpec = mock(ProtocolSpec.class);
when(londonSpec.getFeeMarket()).thenReturn(FeeMarket.london(5));
when(protocolSchedule.getForNextBlockHeader(
eq(blockchain.getChainHeadHeader()),
eq(blockchain.getChainHeadHeader().getTimestamp())))
.thenReturn(londonSpec);
double[] percentile = new double[] {100.0};

final Object ninth =
Expand All @@ -286,12 +276,6 @@ public void feeValuesAreInTheBlockCountAndHighestBlock() {

@Test
public void feeValuesDontGoPastHighestBlock() {
final ProtocolSpec londonSpec = mock(ProtocolSpec.class);
when(londonSpec.getFeeMarket()).thenReturn(FeeMarket.london(5));
when(protocolSchedule.getForNextBlockHeader(
eq(blockchain.getChainHeadHeader()),
eq(blockchain.getChainHeadHeader().getTimestamp())))
.thenReturn(londonSpec);
double[] percentile = new double[] {100.0};

final Object second =
Expand Down Expand Up @@ -345,6 +329,70 @@ private void assertFeeMetadataSize(final Object feeObject, final int blockCount)
assertThat(((ImmutableFeeHistoryResult) feeObject).getReward().size()).isEqualTo(blockCount);
assertThat(((ImmutableFeeHistoryResult) feeObject).getGasUsedRatio().size())
.isEqualTo(blockCount);
assertThat(((ImmutableFeeHistoryResult) feeObject).getBaseFeePerBlobGas().size())
.isEqualTo(blockCount + 1);
assertThat(((ImmutableFeeHistoryResult) feeObject).getBlobGasUsedRatio().size())
.isEqualTo(blockCount);
}

@Test
public void shouldCalculateBlobFeeCorrectly_preBlob() {
assertBlobBaseFee(List.of(Wei.ZERO, Wei.ZERO));
}

@Test
public void shouldCalculateBlobFeeCorrectly_postBlob() {
mockPostBlobFork();
assertBlobBaseFee(List.of(Wei.ONE, Wei.ONE));
}

@Test
public void shouldCalculateBlobFeeCorrectly_transitionFork() {
mockTransitionBlobFork();
assertBlobBaseFee(List.of(Wei.ZERO, Wei.ONE));
}

private void mockFork() {
final ProtocolSpec londonSpec = mock(ProtocolSpec.class);
when(londonSpec.getGasCalculator()).thenReturn(new LondonGasCalculator());
when(londonSpec.getFeeMarket()).thenReturn(FeeMarket.london(5));
when(londonSpec.getGasLimitCalculator()).thenReturn(mock(GasLimitCalculator.class));

when(protocolSchedule.getByBlockHeader(any())).thenReturn(londonSpec);
when(protocolSchedule.getForNextBlockHeader(any(), anyLong())).thenReturn(londonSpec);
}

private void mockPostBlobFork() {
final ProtocolSpec cancunSpec = mock(ProtocolSpec.class);
when(cancunSpec.getGasCalculator()).thenReturn(new CancunGasCalculator());
when(cancunSpec.getFeeMarket()).thenReturn(FeeMarket.cancun(5, Optional.empty()));
when(cancunSpec.getGasLimitCalculator())
.thenReturn(mock(CancunTargetingGasLimitCalculator.class));
when(protocolSchedule.getByBlockHeader(any())).thenReturn(cancunSpec);
when(protocolSchedule.getForNextBlockHeader(any(), anyLong())).thenReturn(cancunSpec);
}

private void mockTransitionBlobFork() {
final ProtocolSpec cancunSpec = mock(ProtocolSpec.class);
when(cancunSpec.getGasCalculator()).thenReturn(new CancunGasCalculator());
when(cancunSpec.getFeeMarket()).thenReturn(FeeMarket.cancun(5, Optional.empty()));
when(cancunSpec.getGasLimitCalculator())
.thenReturn(mock(CancunTargetingGasLimitCalculator.class));
when(protocolSchedule.getForNextBlockHeader(any(), anyLong())).thenReturn(cancunSpec);
}

private void assertBlobBaseFee(final List<Wei> baseFeePerBlobGas) {
final Object latest = ((JsonRpcSuccessResponse) feeHistoryRequest("0x1", "latest")).getResult();
assertThat(latest)
.isEqualTo(
FeeHistory.FeeHistoryResult.from(
ImmutableFeeHistory.builder()
.oldestBlock(10)
.baseFeePerGas(List.of(Wei.of(25496L), Wei.of(28683L)))
.gasUsedRatio(List.of(0.9999999992132459))
.baseFeePerBlobGas(baseFeePerBlobGas)
.blobGasUsedRatio(List.of(0.0))
.build()));
}

private JsonRpcResponse feeHistoryRequest(final Object... params) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,12 @@
"0x3437004a",
"0x2dbc88c1"
],
"baseFeePerBlobGas" : [ "0x0", "0x1", "0x1" ],
"gasUsedRatio": [
0.004079142040086682,
0.003713085594819442
],
"blobGasUsedRatio" : [ 0.0, 0.3333333333333333 ],
"reward": [
[
"0x3b9aca00",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,12 @@
"0x3437004a",
"0x2dbc88c1"
],
"baseFeePerBlobGas" : [ "0x0", "0x1", "0x1" ],
"gasUsedRatio": [
0.004079142040086682,
0.003713085594819442
]
],
"blobGasUsedRatio" : [ 0.0, 0.3333333333333333 ]
}
},
"statusCode": 200
Expand Down

0 comments on commit 911b8a7

Please sign in to comment.