Skip to content

Commit

Permalink
Optimize Eth_feeHistory RPC method (hyperledger#6011)
Browse files Browse the repository at this point in the history
* Add a cache to EthFeeHistory and improve rewards algorithm
* cache the rewards by hash instead of block number
* Add final on some fields

Signed-off-by: Ameziane H <[email protected]>
  • Loading branch information
ahamlat authored Oct 13, 2023
1 parent 5fce4fd commit 8507bb7
Show file tree
Hide file tree
Showing 2 changed files with 54 additions and 18 deletions.
1 change: 1 addition & 0 deletions ethereum/api/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ dependencies {
implementation 'io.tmio:tuweni-bytes'
implementation 'io.tmio:tuweni-units'
implementation 'org.web3j:abi'
implementation 'com.github.ben-manes.caffeine:caffeine'

annotationProcessor "org.immutables:value"
implementation "org.immutables:value-annotations"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

import static java.util.stream.Collectors.toUnmodifiableList;

import org.hyperledger.besu.datatypes.Hash;
import org.hyperledger.besu.datatypes.Wei;
import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext;
Expand All @@ -36,25 +37,30 @@
import org.hyperledger.besu.ethereum.mainnet.feemarket.BaseFeeMarket;
import org.hyperledger.besu.ethereum.mainnet.feemarket.FeeMarket;

import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.LongStream;
import java.util.stream.Stream;

import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.google.common.collect.Streams;

public class EthFeeHistory implements JsonRpcMethod {
private final ProtocolSchedule protocolSchedule;
private final Blockchain blockchain;
private final Cache<RewardCacheKey, List<Wei>> cache;
private static final int MAXIMUM_CACHE_SIZE = 100_000;

record RewardCacheKey(Hash blockHash, List<Double> rewardPercentiles) {}

public EthFeeHistory(final ProtocolSchedule protocolSchedule, final Blockchain blockchain) {
this.protocolSchedule = protocolSchedule;
this.blockchain = blockchain;
this.cache = Caffeine.newBuilder().maximumSize(MAXIMUM_CACHE_SIZE).build();
}

@Override
Expand Down Expand Up @@ -96,6 +102,7 @@ public JsonRpcResponse response(final JsonRpcRequestContext request) {

final List<BlockHeader> blockHeaders =
LongStream.range(oldestBlock, lastBlock)
.parallel()
.mapToObj(blockchain::getBlockHeader)
.flatMap(Optional::stream)
.collect(toUnmodifiableList());
Expand Down Expand Up @@ -143,14 +150,31 @@ public JsonRpcResponse response(final JsonRpcRequestContext request) {
final Optional<List<List<Wei>>> maybeRewards =
maybeRewardPercentiles.map(
rewardPercentiles ->
LongStream.range(oldestBlock, lastBlock)
.mapToObj(blockchain::getBlockByNumber)
.flatMap(Optional::stream)
blockHeaders.stream()
.parallel()
.map(
block ->
computeRewards(
rewardPercentiles.stream().sorted().collect(toUnmodifiableList()),
block))
blockHeader -> {
final RewardCacheKey key =
new RewardCacheKey(blockHeader.getBlockHash(), rewardPercentiles);
return Optional.ofNullable(cache.getIfPresent(key))
.or(
() -> {
Optional<Block> block =
blockchain.getBlockByHash(blockHeader.getBlockHash());
return block.map(
b -> {
List<Wei> rewards =
computeRewards(
rewardPercentiles.stream()
.sorted()
.collect(toUnmodifiableList()),
b);
cache.put(key, rewards);
return rewards;
});
});
})
.flatMap(Optional::stream)
.collect(toUnmodifiableList()));

return new JsonRpcSuccessResponse(
Expand Down Expand Up @@ -188,13 +212,21 @@ private List<Wei> computeRewards(final List<Double> rewardPercentiles, final Blo
: transactionReceipt.getCumulativeGasUsed()
- transactionsGasUsed.get(transactionsGasUsed.size() - 1));
}
final List<Map.Entry<Transaction, Long>> transactionsAndGasUsedAscendingEffectiveGasFee =

record TransactionInfo(Transaction transaction, Long gasUsed, Wei effectivePriorityFeePerGas) {}

final List<TransactionInfo> transactionsInfo =
Streams.zip(
transactions.stream(), transactionsGasUsed.stream(), AbstractMap.SimpleEntry::new)
.sorted(
Comparator.comparing(
transactionAndGasUsed ->
transactionAndGasUsed.getKey().getEffectivePriorityFeePerGas(baseFee)))
transactions.stream(),
transactionsGasUsed.stream(),
(transaction, gasUsed) ->
new TransactionInfo(
transaction, gasUsed, transaction.getEffectivePriorityFeePerGas(baseFee)))
.collect(toUnmodifiableList());

final List<TransactionInfo> transactionsAndGasUsedAscendingEffectiveGasFee =
transactionsInfo.stream()
.sorted(Comparator.comparing(TransactionInfo::effectivePriorityFeePerGas))
.collect(toUnmodifiableList());

// We need to weight the percentile of rewards by the gas used in the transaction.
Expand All @@ -203,18 +235,21 @@ private List<Wei> computeRewards(final List<Double> rewardPercentiles, final Blo
final ArrayList<Wei> rewards = new ArrayList<>();
int rewardPercentileIndex = 0;
long gasUsed = 0;
for (final Map.Entry<Transaction, Long> transactionAndGasUsed :
for (final TransactionInfo transactionAndGasUsed :
transactionsAndGasUsedAscendingEffectiveGasFee) {

gasUsed += transactionAndGasUsed.getValue();
gasUsed += transactionAndGasUsed.gasUsed();

while (rewardPercentileIndex < rewardPercentiles.size()
&& 100.0 * gasUsed / block.getHeader().getGasUsed()
>= rewardPercentiles.get(rewardPercentileIndex)) {
rewards.add(transactionAndGasUsed.getKey().getEffectivePriorityFeePerGas(baseFee));
rewards.add(transactionAndGasUsed.effectivePriorityFeePerGas);
rewardPercentileIndex++;
}
}
// Put the computed rewards in the cache
cache.put(new RewardCacheKey(block.getHeader().getBlockHash(), rewardPercentiles), rewards);

return rewards;
}
}

0 comments on commit 8507bb7

Please sign in to comment.