Skip to content

Commit

Permalink
Retrieve all transaction receipts for a block in one request (#6646)
Browse files Browse the repository at this point in the history
    Signed-off-by: Sally MacFarlane <[email protected]>
  • Loading branch information
Wetitpig authored Dec 10, 2024
1 parent 8148f0d commit 4435f75
Show file tree
Hide file tree
Showing 8 changed files with 148 additions and 41 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -122,13 +125,22 @@ public Optional<UncleBlockAdapter> getOmmerAt(final DataFetchingEnvironment envi
*
* <p>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<TransactionAdapter> getTransactions() {
public List<TransactionAdapter> 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<TransactionWithMetadata> trans = blockWithMetaData.getTransactions();
final List<TransactionReceiptWithMetadata> transReceipts =
query.transactionReceiptsByBlockHash(hash, protocolSchedule).get();

final List<TransactionAdapter> 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;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ public List<TransactionAdapter> getTransactions() {
return transactionPool.getPendingTransactions().stream()
.map(PendingTransaction::getTransaction)
.map(TransactionWithMetadata::new)
.map(TransactionAdapter::new)
.map(tx -> new TransactionAdapter(tx, null))
.toList();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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<TransactionReceiptWithMetadata> getReceipt(
final DataFetchingEnvironment environment) {
if (transactionReceiptWithMetadata == null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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<TransactionReceiptResult> txReceipt(final TransactionWithMetadata tx) {
Optional<TransactionReceiptWithMetadata> 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<TransactionReceiptResult> receiptList =
blockchainQueries
.get()
.blockByHash(blockHash)
.transactionReceiptsByBlockHash(blockHash, protocolSchedule)
.orElse(new ArrayList<TransactionReceiptWithMetadata>())
.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);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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<TransactionReceiptWithMetadata>())
.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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -631,6 +631,63 @@ public Optional<TransactionLocation> 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<List<TransactionReceiptWithMetadata>> transactionReceiptsByBlockHash(
final Hash blockHash, final ProtocolSchedule protocolSchedule) {
final Optional<Block> block = blockchain.getBlockByHash(blockHash);
if (block.isEmpty()) {
return Optional.empty();
}
final BlockHeader header = block.get().getHeader();
final List<Transaction> transactions = block.get().getBody().getTransactions();

final List<TransactionReceipt> transactionReceipts =
blockchain.getTxReceipts(blockHash).orElseThrow();

long cumulativeGasUsedUntilTx = 0;
int logIndexOffset = 0;

List<TransactionReceiptWithMetadata> receiptsResult =
new ArrayList<TransactionReceiptWithMetadata>(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<Long> maybeBlobGasUsed =
getBlobGasUsed(transaction, protocolSchedule.getByBlockHeader(header));

Optional<Wei> 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.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<List<TransactionReceiptWithMetadata>> 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<List<TransactionReceiptWithMetadata>> result =
queries.transactionReceiptsByBlockHash(gen.hash(), Mockito.mock(ProtocolSchedule.class));
assertThat(result).isEmpty();
}

@Test
public void logsShouldBeFlaggedAsRemovedWhenBlockIsNotInCanonicalChain() {
// create initial blockchain
Expand Down

0 comments on commit 4435f75

Please sign in to comment.