diff --git a/ethereum/api/build.gradle b/ethereum/api/build.gradle index d6c230ae018..ed395df8c5d 100644 --- a/ethereum/api/build.gradle +++ b/ethereum/api/build.gradle @@ -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" 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 index d505568bd4e..240cdaeb655 100644 --- 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 @@ -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; @@ -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> cache; + private static final int MAXIMUM_CACHE_SIZE = 100_000; + + record RewardCacheKey(Hash blockHash, List 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 @@ -96,6 +102,7 @@ public JsonRpcResponse response(final JsonRpcRequestContext request) { final List blockHeaders = LongStream.range(oldestBlock, lastBlock) + .parallel() .mapToObj(blockchain::getBlockHeader) .flatMap(Optional::stream) .collect(toUnmodifiableList()); @@ -143,14 +150,31 @@ public JsonRpcResponse response(final JsonRpcRequestContext request) { final Optional>> 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 = + blockchain.getBlockByHash(blockHeader.getBlockHash()); + return block.map( + b -> { + List rewards = + computeRewards( + rewardPercentiles.stream() + .sorted() + .collect(toUnmodifiableList()), + b); + cache.put(key, rewards); + return rewards; + }); + }); + }) + .flatMap(Optional::stream) .collect(toUnmodifiableList())); return new JsonRpcSuccessResponse( @@ -188,13 +212,21 @@ private List computeRewards(final List rewardPercentiles, final Blo : transactionReceipt.getCumulativeGasUsed() - transactionsGasUsed.get(transactionsGasUsed.size() - 1)); } - final List> transactionsAndGasUsedAscendingEffectiveGasFee = + + record TransactionInfo(Transaction transaction, Long gasUsed, Wei effectivePriorityFeePerGas) {} + + final List 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 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. @@ -203,18 +235,21 @@ private List computeRewards(final List rewardPercentiles, final Blo final ArrayList rewards = new ArrayList<>(); int rewardPercentileIndex = 0; long gasUsed = 0; - for (final Map.Entry 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; } }