From 4435f75233bfa12bf2f6865a992a8f586138ceea Mon Sep 17 00:00:00 2001 From: Wetitpig Date: Tue, 10 Dec 2024 19:28:42 +0800 Subject: [PATCH] Retrieve all transaction receipts for a block in one request (#6646) Signed-off-by: Sally MacFarlane --- CHANGELOG.md | 1 + .../pojoadapter/NormalBlockAdapter.java | 18 +++++- .../pojoadapter/PendingStateAdapter.java | 2 +- .../pojoadapter/TransactionAdapter.java | 20 +++++++ .../internal/methods/EthGetBlockReceipts.java | 34 +++-------- .../methods/EthGetMinerDataByBlockHash.java | 22 ++++--- .../ethereum/api/query/BlockchainQueries.java | 57 +++++++++++++++++++ .../api/query/BlockchainQueriesTest.java | 35 ++++++++++++ 8 files changed, 148 insertions(+), 41 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 462bb8bd17b..b7c2504a4e9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ - Fast Sync ### Additions and Improvements +- Retrieve all transaction receipts for a block in one request [#6646](https://github.com/hyperledger/besu/pull/6646) ### Bug fixes diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/graphql/internal/pojoadapter/NormalBlockAdapter.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/graphql/internal/pojoadapter/NormalBlockAdapter.java index fa75028a383..f1a953481f3 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/graphql/internal/pojoadapter/NormalBlockAdapter.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/graphql/internal/pojoadapter/NormalBlockAdapter.java @@ -15,11 +15,14 @@ package org.hyperledger.besu.ethereum.api.graphql.internal.pojoadapter; import org.hyperledger.besu.datatypes.Hash; +import org.hyperledger.besu.ethereum.api.graphql.GraphQLContextType; import org.hyperledger.besu.ethereum.api.query.BlockWithMetadata; import org.hyperledger.besu.ethereum.api.query.BlockchainQueries; +import org.hyperledger.besu.ethereum.api.query.TransactionReceiptWithMetadata; import org.hyperledger.besu.ethereum.api.query.TransactionWithMetadata; import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.ethereum.core.Difficulty; +import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; import java.util.ArrayList; import java.util.List; @@ -122,13 +125,22 @@ public Optional getOmmerAt(final DataFetchingEnvironment envi * *

Each TransactionAdapter object is created by adapting a TransactionWithMetadata object. * + * @param environment the data fetching environment. * @return a list of TransactionAdapter objects for the transactions in the block. */ - public List getTransactions() { + public List getTransactions(final DataFetchingEnvironment environment) { + final BlockchainQueries query = getBlockchainQueries(environment); + final Hash hash = blockWithMetaData.getHeader().getHash(); + final ProtocolSchedule protocolSchedule = + environment.getGraphQlContext().get(GraphQLContextType.PROTOCOL_SCHEDULE); + final List trans = blockWithMetaData.getTransactions(); + final List transReceipts = + query.transactionReceiptsByBlockHash(hash, protocolSchedule).get(); + final List results = new ArrayList<>(); - for (final TransactionWithMetadata tran : trans) { - results.add(new TransactionAdapter(tran)); + for (int i = 0; i < trans.size(); i++) { + results.add(new TransactionAdapter(trans.get(i), transReceipts.get(i))); } return results; } diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/graphql/internal/pojoadapter/PendingStateAdapter.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/graphql/internal/pojoadapter/PendingStateAdapter.java index 9ec80fbedd0..73e0d4cb570 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/graphql/internal/pojoadapter/PendingStateAdapter.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/graphql/internal/pojoadapter/PendingStateAdapter.java @@ -75,7 +75,7 @@ public List getTransactions() { return transactionPool.getPendingTransactions().stream() .map(PendingTransaction::getTransaction) .map(TransactionWithMetadata::new) - .map(TransactionAdapter::new) + .map(tx -> new TransactionAdapter(tx, null)) .toList(); } diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/graphql/internal/pojoadapter/TransactionAdapter.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/graphql/internal/pojoadapter/TransactionAdapter.java index f8e60c5527c..4f26273112e 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/graphql/internal/pojoadapter/TransactionAdapter.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/graphql/internal/pojoadapter/TransactionAdapter.java @@ -35,6 +35,7 @@ import java.util.List; import java.util.Optional; import javax.annotation.Nonnull; +import javax.annotation.Nullable; import graphql.schema.DataFetchingEnvironment; import org.apache.tuweni.bytes.Bytes; @@ -65,6 +66,25 @@ public TransactionAdapter(final @Nonnull TransactionWithMetadata transactionWith this.transactionWithMetadata = transactionWithMetadata; } + /** + * Constructs a new TransactionAdapter object with receipt. + * + * @param transactionWithMetadata the TransactionWithMetadata object to adapt. + * @param transactionReceiptWithMetadata the TransactionReceiptWithMetadata object to adapt. + */ + public TransactionAdapter( + final @Nonnull TransactionWithMetadata transactionWithMetadata, + final @Nullable TransactionReceiptWithMetadata transactionReceiptWithMetadata) { + this.transactionWithMetadata = transactionWithMetadata; + this.transactionReceiptWithMetadata = Optional.ofNullable(transactionReceiptWithMetadata); + } + + /** + * Reurns the receipt of the transaction. + * + * @param environment the data fetching environment. + * @return the receipt of the transaction. + */ private Optional getReceipt( final DataFetchingEnvironment environment) { if (transactionReceiptWithMetadata == null) { diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetBlockReceipts.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetBlockReceipts.java index 819ac5e1eb4..aaa2fa7e082 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetBlockReceipts.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetBlockReceipts.java @@ -27,13 +27,11 @@ import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.TransactionReceiptStatusResult; import org.hyperledger.besu.ethereum.api.query.BlockchainQueries; import org.hyperledger.besu.ethereum.api.query.TransactionReceiptWithMetadata; -import org.hyperledger.besu.ethereum.api.query.TransactionWithMetadata; import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; import org.hyperledger.besu.ethereum.mainnet.TransactionReceiptType; import java.util.ArrayList; import java.util.List; -import java.util.Optional; import java.util.stream.Collectors; import com.google.common.base.Suppliers; @@ -70,35 +68,21 @@ protected Object resultByBlockHash(final JsonRpcRequestContext request, final Ha } /* - * For a given transaction, get its receipt and if it exists, wrap in a transaction receipt of the correct type + * For a given block, get receipts of transactions in the block and if they exist, wrap in transaction receipts of the correct type */ - private Optional txReceipt(final TransactionWithMetadata tx) { - Optional receipt = - blockchainQueries - .get() - .transactionReceiptByTransactionHash(tx.getTransaction().getHash(), protocolSchedule); - if (receipt.isPresent()) { - if (receipt.get().getReceipt().getTransactionReceiptType() == TransactionReceiptType.ROOT) { - return Optional.of(new TransactionReceiptRootResult(receipt.get())); - } else { - return Optional.of(new TransactionReceiptStatusResult(receipt.get())); - } - } - return Optional.empty(); - } - private BlockReceiptsResult getBlockReceiptsResult(final Hash blockHash) { final List receiptList = blockchainQueries .get() - .blockByHash(blockHash) + .transactionReceiptsByBlockHash(blockHash, protocolSchedule) + .orElse(new ArrayList()) + .stream() .map( - block -> - block.getTransactions().stream() - .map(this::txReceipt) - .flatMap(Optional::stream) - .collect(Collectors.toList())) - .orElse(new ArrayList<>()); + receipt -> + receipt.getReceipt().getTransactionReceiptType() == TransactionReceiptType.ROOT + ? new TransactionReceiptRootResult(receipt) + : new TransactionReceiptStatusResult(receipt)) + .collect(Collectors.toList()); return new BlockReceiptsResult(receiptList); } diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetMinerDataByBlockHash.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetMinerDataByBlockHash.java index dbde135f34d..8fc3e6d732a 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetMinerDataByBlockHash.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetMinerDataByBlockHash.java @@ -29,6 +29,7 @@ import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.MinerDataResult.UncleRewardResult; import org.hyperledger.besu.ethereum.api.query.BlockWithMetadata; import org.hyperledger.besu.ethereum.api.query.BlockchainQueries; +import org.hyperledger.besu.ethereum.api.query.TransactionReceiptWithMetadata; import org.hyperledger.besu.ethereum.api.query.TransactionWithMetadata; import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; @@ -91,19 +92,16 @@ public static MinerDataResult createMinerDataResult( final ProtocolSpec protocolSpec = protocolSchedule.getByBlockHeader(blockHeader); final Wei staticBlockReward = protocolSpec.getBlockReward(); final Wei transactionFee = - block.getTransactions().stream() + blockchainQueries + .transactionReceiptsByBlockHash(blockHeader.getHash(), protocolSchedule) + .orElse(new ArrayList()) + .stream() .map( - t -> - blockchainQueries - .transactionReceiptByTransactionHash( - t.getTransaction().getHash(), protocolSchedule) - .map( - receipt -> - receipt - .getTransaction() - .getEffectiveGasPrice(receipt.getBaseFee()) - .multiply(receipt.getGasUsed())) - .orElse(Wei.ZERO)) + receipt -> + receipt + .getTransaction() + .getEffectivePriorityFeePerGas(receipt.getBaseFee()) + .multiply(receipt.getGasUsed())) .reduce(Wei.ZERO, BaseUInt256Value::add); final Wei uncleInclusionReward = staticBlockReward.multiply(block.getOmmers().size()).divide(32); diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/query/BlockchainQueries.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/query/BlockchainQueries.java index 7e5a7c6fe04..a673e1df7b1 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/query/BlockchainQueries.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/query/BlockchainQueries.java @@ -631,6 +631,63 @@ public Optional transactionLocationByHash(final Hash transa return blockchain.getTransactionLocation(transactionHash); } + /** + * Returns the transaction receipts associated with the given block hash. + * + * @param blockHash The hash of the block that corresponds to the receipts to retrieve. + * @return The transaction receipts associated with the referenced block. + */ + public Optional> transactionReceiptsByBlockHash( + final Hash blockHash, final ProtocolSchedule protocolSchedule) { + final Optional block = blockchain.getBlockByHash(blockHash); + if (block.isEmpty()) { + return Optional.empty(); + } + final BlockHeader header = block.get().getHeader(); + final List transactions = block.get().getBody().getTransactions(); + + final List transactionReceipts = + blockchain.getTxReceipts(blockHash).orElseThrow(); + + long cumulativeGasUsedUntilTx = 0; + int logIndexOffset = 0; + + List receiptsResult = + new ArrayList(transactions.size()); + + for (int transactionIndex = 0; transactionIndex < transactions.size(); transactionIndex++) { + final Transaction transaction = transactions.get(transactionIndex); + final TransactionReceipt transactionReceipt = transactionReceipts.get(transactionIndex); + final Hash transactionHash = transaction.getHash(); + + long gasUsed = transactionReceipt.getCumulativeGasUsed() - cumulativeGasUsedUntilTx; + + Optional maybeBlobGasUsed = + getBlobGasUsed(transaction, protocolSchedule.getByBlockHeader(header)); + + Optional maybeBlobGasPrice = + getBlobGasPrice(transaction, header, protocolSchedule.getByBlockHeader(header)); + + receiptsResult.add( + TransactionReceiptWithMetadata.create( + transactionReceipt, + transaction, + transactionHash, + transactionIndex, + gasUsed, + header.getBaseFee(), + blockHash, + header.getNumber(), + maybeBlobGasUsed, + maybeBlobGasPrice, + logIndexOffset)); + + cumulativeGasUsedUntilTx = transactionReceipt.getCumulativeGasUsed(); + logIndexOffset += transactionReceipt.getLogsList().size(); + } + return Optional.of(receiptsResult); + } + /** * Returns the transaction receipt associated with the given transaction hash. * diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/query/BlockchainQueriesTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/query/BlockchainQueriesTest.java index a0213a41a80..e9235f38d4b 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/query/BlockchainQueriesTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/query/BlockchainQueriesTest.java @@ -330,6 +330,41 @@ public void getOmmerCountForLatestBlock() { assertThat(result).contains(targetBlock.getBody().getOmmers().size()); } + @Test + public void getTransactionReceiptsByHash() { + final BlockchainWithData data = setupBlockchain(3); + final BlockchainQueries queries = data.blockchainQueries; + + final Block targetBlock = data.blockData.get(1).block; + + final Optional> receipts = + queries.transactionReceiptsByBlockHash( + targetBlock.getHash(), Mockito.mock(ProtocolSchedule.class)); + assertThat(receipts).isNotEmpty(); + + receipts + .get() + .forEach( + receipt -> { + final long gasUsed = receipt.getGasUsed(); + + assertThat(gasUsed) + .isEqualTo( + targetBlock.getHeader().getGasUsed() + / targetBlock.getBody().getTransactions().size()); + }); + } + + @Test + public void getTransactionReceiptsByInvalidHash() { + final BlockchainWithData data = setupBlockchain(3); + final BlockchainQueries queries = data.blockchainQueries; + + final Optional> result = + queries.transactionReceiptsByBlockHash(gen.hash(), Mockito.mock(ProtocolSchedule.class)); + assertThat(result).isEmpty(); + } + @Test public void logsShouldBeFlaggedAsRemovedWhenBlockIsNotInCanonicalChain() { // create initial blockchain