From cfcca5e0148dda810733c553c9962589e90347ed Mon Sep 17 00:00:00 2001 From: Ameziane H Date: Tue, 10 Oct 2023 14:53:31 +0200 Subject: [PATCH 1/4] Add a cache to EthFeeHistory and improve rewards algorithm Signed-off-by: Ameziane H --- ethereum/api/build.gradle | 1 + .../internal/methods/EthFeeHistory.java | 68 ++++++++++++++----- 2 files changed, 52 insertions(+), 17 deletions(-) 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..a61068af4cf 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 @@ -36,25 +36,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(Long blockNumber, 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 +101,7 @@ public JsonRpcResponse response(final JsonRpcRequestContext request) { final List blockHeaders = LongStream.range(oldestBlock, lastBlock) + .parallel() .mapToObj(blockchain::getBlockHeader) .flatMap(Optional::stream) .collect(toUnmodifiableList()); @@ -144,13 +150,29 @@ public JsonRpcResponse response(final JsonRpcRequestContext request) { maybeRewardPercentiles.map( rewardPercentiles -> LongStream.range(oldestBlock, lastBlock) - .mapToObj(blockchain::getBlockByNumber) + .parallel() + .mapToObj( + blockNumber -> { + RewardCacheKey key = new RewardCacheKey(blockNumber, rewardPercentiles); + return Optional.ofNullable(cache.getIfPresent(key)) + .or( + () -> { + Optional block = + blockchain.getBlockByNumber(blockNumber); + return block.map( + b -> { + List rewards = + computeRewards( + rewardPercentiles.stream() + .sorted() + .collect(toUnmodifiableList()), + b); + cache.put(key, rewards); + return rewards; + }); + }); + }) .flatMap(Optional::stream) - .map( - block -> - computeRewards( - rewardPercentiles.stream().sorted().collect(toUnmodifiableList()), - block)) .collect(toUnmodifiableList())); return new JsonRpcSuccessResponse( @@ -188,13 +210,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) {} + + 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()); + + 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 +233,22 @@ 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 + RewardCacheKey key = new RewardCacheKey(block.getHeader().getNumber(), rewardPercentiles); + cache.put(key, rewards); + return rewards; } } From 28e7017dd9dde83a602ab0d5bff784b12973ee7b Mon Sep 17 00:00:00 2001 From: Ameziane H Date: Wed, 11 Oct 2023 18:08:54 +0200 Subject: [PATCH 2/4] cache the rewards by hash instead of block number Signed-off-by: Ameziane H --- .../jsonrpc/internal/methods/EthFeeHistory.java | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) 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 a61068af4cf..54e56818150 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; @@ -54,7 +55,7 @@ public class EthFeeHistory implements JsonRpcMethod { private final Cache> cache; private static final int MAXIMUM_CACHE_SIZE = 100_000; - record RewardCacheKey(Long blockNumber, List rewardPercentiles) {} + record RewardCacheKey(Hash blockHash, List rewardPercentiles) {} public EthFeeHistory(final ProtocolSchedule protocolSchedule, final Blockchain blockchain) { this.protocolSchedule = protocolSchedule; @@ -149,16 +150,16 @@ public JsonRpcResponse response(final JsonRpcRequestContext request) { final Optional>> maybeRewards = maybeRewardPercentiles.map( rewardPercentiles -> - LongStream.range(oldestBlock, lastBlock) + blockHeaders.stream() .parallel() - .mapToObj( - blockNumber -> { - RewardCacheKey key = new RewardCacheKey(blockNumber, rewardPercentiles); + .map( + blockHeader -> { + RewardCacheKey key = new RewardCacheKey(blockHeader.getBlockHash(), rewardPercentiles); return Optional.ofNullable(cache.getIfPresent(key)) .or( () -> { Optional block = - blockchain.getBlockByNumber(blockNumber); + blockchain.getBlockByHash(blockHeader.getBlockHash()); return block.map( b -> { List rewards = @@ -246,7 +247,7 @@ record TransactionInfo(Transaction transaction, Long gasUsed, Wei effectivePrior } } // Put the computed rewards in the cache - RewardCacheKey key = new RewardCacheKey(block.getHeader().getNumber(), rewardPercentiles); + RewardCacheKey key = new RewardCacheKey(block.getHeader().getBlockHash(), rewardPercentiles); cache.put(key, rewards); return rewards; From 427422d5a6e0510ba608a4682e0df872c07647a2 Mon Sep 17 00:00:00 2001 From: Ameziane H Date: Thu, 12 Oct 2023 12:28:58 +0200 Subject: [PATCH 3/4] spotless Signed-off-by: Ameziane H --- .../ethereum/api/jsonrpc/internal/methods/EthFeeHistory.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) 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 54e56818150..3e49d25db93 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 @@ -150,11 +150,12 @@ public JsonRpcResponse response(final JsonRpcRequestContext request) { final Optional>> maybeRewards = maybeRewardPercentiles.map( rewardPercentiles -> - blockHeaders.stream() + blockHeaders.stream() .parallel() .map( blockHeader -> { - RewardCacheKey key = new RewardCacheKey(blockHeader.getBlockHash(), rewardPercentiles); + RewardCacheKey key = + new RewardCacheKey(blockHeader.getBlockHash(), rewardPercentiles); return Optional.ofNullable(cache.getIfPresent(key)) .or( () -> { From 6863362be7df552ebd478c28e152d46b21311738 Mon Sep 17 00:00:00 2001 From: Ameziane H Date: Thu, 12 Oct 2023 15:36:30 +0200 Subject: [PATCH 4/4] Add final on some fields Signed-off-by: Ameziane H --- .../api/jsonrpc/internal/methods/EthFeeHistory.java | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) 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 3e49d25db93..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 @@ -154,7 +154,7 @@ public JsonRpcResponse response(final JsonRpcRequestContext request) { .parallel() .map( blockHeader -> { - RewardCacheKey key = + final RewardCacheKey key = new RewardCacheKey(blockHeader.getBlockHash(), rewardPercentiles); return Optional.ofNullable(cache.getIfPresent(key)) .or( @@ -215,7 +215,7 @@ private List computeRewards(final List rewardPercentiles, final Blo record TransactionInfo(Transaction transaction, Long gasUsed, Wei effectivePriorityFeePerGas) {} - List transactionsInfo = + final List transactionsInfo = Streams.zip( transactions.stream(), transactionsGasUsed.stream(), @@ -224,7 +224,7 @@ record TransactionInfo(Transaction transaction, Long gasUsed, Wei effectivePrior transaction, gasUsed, transaction.getEffectivePriorityFeePerGas(baseFee))) .collect(toUnmodifiableList()); - List transactionsAndGasUsedAscendingEffectiveGasFee = + final List transactionsAndGasUsedAscendingEffectiveGasFee = transactionsInfo.stream() .sorted(Comparator.comparing(TransactionInfo::effectivePriorityFeePerGas)) .collect(toUnmodifiableList()); @@ -248,8 +248,7 @@ record TransactionInfo(Transaction transaction, Long gasUsed, Wei effectivePrior } } // Put the computed rewards in the cache - RewardCacheKey key = new RewardCacheKey(block.getHeader().getBlockHash(), rewardPercentiles); - cache.put(key, rewards); + cache.put(new RewardCacheKey(block.getHeader().getBlockHash(), rewardPercentiles), rewards); return rewards; }