diff --git a/CHANGELOG.md b/CHANGELOG.md index 5a2079ba147..b31523aab0a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ ### Deprecations ### Additions and Improvements +- TraceService: return results for transactions in block [#6086](https://github.com/hyperledger/besu/pull/6086) ### Bug Fixes diff --git a/besu/src/main/java/org/hyperledger/besu/services/TraceServiceImpl.java b/besu/src/main/java/org/hyperledger/besu/services/TraceServiceImpl.java index 485880ac50d..98c233197f3 100644 --- a/besu/src/main/java/org/hyperledger/besu/services/TraceServiceImpl.java +++ b/besu/src/main/java/org/hyperledger/besu/services/TraceServiceImpl.java @@ -26,6 +26,7 @@ import org.hyperledger.besu.ethereum.chain.Blockchain; import org.hyperledger.besu.ethereum.core.Block; import org.hyperledger.besu.ethereum.core.BlockHeader; +import org.hyperledger.besu.ethereum.core.Transaction; import org.hyperledger.besu.ethereum.mainnet.MainnetTransactionProcessor; import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; import org.hyperledger.besu.ethereum.mainnet.ProtocolSpec; @@ -33,6 +34,8 @@ import org.hyperledger.besu.ethereum.vm.CachingBlockHashLookup; import org.hyperledger.besu.evm.worldstate.WorldUpdater; import org.hyperledger.besu.plugin.Unstable; +import org.hyperledger.besu.plugin.data.BlockTraceResult; +import org.hyperledger.besu.plugin.data.TransactionTraceResult; import org.hyperledger.besu.plugin.services.TraceService; import org.hyperledger.besu.plugin.services.tracer.BlockAwareOperationTracer; @@ -72,10 +75,9 @@ public TraceServiceImpl( * @param tracer an instance of OperationTracer */ @Override - public void traceBlock(final long blockNumber, final BlockAwareOperationTracer tracer) { - checkArgument(tracer != null); - final Optional block = blockchainQueries.getBlockchain().getBlockByNumber(blockNumber); - block.ifPresent(value -> trace(value, tracer)); + public BlockTraceResult traceBlock( + final long blockNumber, final BlockAwareOperationTracer tracer) { + return traceBlock(blockchainQueries.getBlockchain().getBlockByNumber(blockNumber), tracer); } /** @@ -85,10 +87,41 @@ public void traceBlock(final long blockNumber, final BlockAwareOperationTracer t * @param tracer an instance of OperationTracer */ @Override - public void traceBlock(final Hash hash, final BlockAwareOperationTracer tracer) { + public BlockTraceResult traceBlock(final Hash hash, final BlockAwareOperationTracer tracer) { + return traceBlock(blockchainQueries.getBlockchain().getBlockByHash(hash), tracer); + } + + private BlockTraceResult traceBlock( + final Optional maybeBlock, final BlockAwareOperationTracer tracer) { checkArgument(tracer != null); - final Optional block = blockchainQueries.getBlockchain().getBlockByHash(hash); - block.ifPresent(value -> trace(value, tracer)); + if (maybeBlock.isEmpty()) { + return BlockTraceResult.empty(); + } + + final Optional> results = trace(maybeBlock.get(), tracer); + + if (results.isEmpty()) { + return BlockTraceResult.empty(); + } + + final BlockTraceResult.Builder builder = BlockTraceResult.builder(); + + final List transactionProcessingResults = results.get(); + final List transactions = maybeBlock.get().getBody().getTransactions(); + for (int i = 0; i < transactionProcessingResults.size(); i++) { + final TransactionProcessingResult transactionProcessingResult = + transactionProcessingResults.get(i); + final TransactionTraceResult transactionTraceResult = + transactionProcessingResult.isInvalid() + ? TransactionTraceResult.error( + transactions.get(i).getHash(), + transactionProcessingResult.getValidationResult().getErrorMessage()) + : TransactionTraceResult.success(transactions.get(i).getHash()); + + builder.addTransactionTraceResult(transactionTraceResult); + } + + return builder.build(); } /** @@ -136,15 +169,20 @@ public void trace( }); } - private void trace(final Block block, final BlockAwareOperationTracer tracer) { + private Optional> trace( + final Block block, final BlockAwareOperationTracer tracer) { LOG.debug("Tracing block {}", block.toLogString()); final Blockchain blockchain = blockchainQueries.getBlockchain(); - Tracer.processTracing( - blockchainQueries, - block.getHash(), - traceableState -> - Optional.of(trace(blockchain, block, new ChainUpdater(traceableState), tracer))); + + final Optional> results = + Tracer.processTracing( + blockchainQueries, + block.getHash(), + traceableState -> + Optional.of(trace(blockchain, block, new ChainUpdater(traceableState), tracer))); tracer.traceEndBlock(block.getHeader(), block.getBody()); + + return results; } private List trace( diff --git a/besu/src/test/java/org/hyperledger/besu/services/TraceServiceImplTest.java b/besu/src/test/java/org/hyperledger/besu/services/TraceServiceImplTest.java index cb88a9b9730..30d51cfa737 100644 --- a/besu/src/test/java/org/hyperledger/besu/services/TraceServiceImplTest.java +++ b/besu/src/test/java/org/hyperledger/besu/services/TraceServiceImplTest.java @@ -26,6 +26,8 @@ import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive; import org.hyperledger.besu.evm.log.Log; import org.hyperledger.besu.evm.worldstate.WorldView; +import org.hyperledger.besu.plugin.data.BlockTraceResult; +import org.hyperledger.besu.plugin.data.TransactionTraceResult; import org.hyperledger.besu.plugin.services.TraceService; import org.hyperledger.besu.plugin.services.tracer.BlockAwareOperationTracer; @@ -115,7 +117,18 @@ void shouldReturnTheCorrectWorldViewForTxStartEnd() { final TxStartEndTracer txStartEndTracer = new TxStartEndTracer(); // block contains 1 transaction - traceService.traceBlock(31, txStartEndTracer); + final BlockTraceResult blockTraceResult = traceService.traceBlock(31, txStartEndTracer); + + assertThat(blockTraceResult).isNotNull(); + + final List transactionTraceResults = + blockTraceResult.transactionTraceResults(); + assertThat(transactionTraceResults.size()).isEqualTo(1); + + assertThat(transactionTraceResults.get(0).getTxHash()).isNotNull(); + assertThat(transactionTraceResults.get(0).getStatus()) + .isEqualTo(TransactionTraceResult.Status.SUCCESS); + assertThat(transactionTraceResults.get(0).errorMessage()).isEmpty(); assertThat(txStartEndTracer.txStartWorldView).isNotNull(); assertThat(txStartEndTracer.txEndWorldView).isNotNull(); diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetTransactionProcessor.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetTransactionProcessor.java index d8b938a2fa1..35909c0c62e 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetTransactionProcessor.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetTransactionProcessor.java @@ -500,7 +500,8 @@ public TransactionProcessingResult processTransaction( LOG.error("Critical Exception Processing Transaction", re); return TransactionProcessingResult.invalid( ValidationResult.invalid( - TransactionInvalidReason.INTERNAL_ERROR, "Internal Error in Besu - " + re)); + TransactionInvalidReason.INTERNAL_ERROR, + "Internal Error in Besu - " + re + "\n" + printableStackTraceFromThrowable(re))); } } @@ -525,4 +526,14 @@ protected long refunded( final long refundAllowance = Math.min(maxRefundAllowance, gasRefund); return gasRemaining + refundAllowance; } + + private String printableStackTraceFromThrowable(final RuntimeException re) { + final StringBuilder builder = new StringBuilder(); + + for (final StackTraceElement stackTraceElement : re.getStackTrace()) { + builder.append("\tat ").append(stackTraceElement.toString()).append("\n"); + } + + return builder.toString(); + } } diff --git a/plugin-api/build.gradle b/plugin-api/build.gradle index f79852f2011..1160012008c 100644 --- a/plugin-api/build.gradle +++ b/plugin-api/build.gradle @@ -69,7 +69,7 @@ Calculated : ${currentHash} tasks.register('checkAPIChanges', FileStateChecker) { description = "Checks that the API for the Plugin-API project does not change without deliberate thought" files = sourceSets.main.allJava.files - knownHash = 'j6NRklFHlG35Pq/t6t/oJBrT8DbYOyruGq3cJNh4ENw=' + knownHash = 'MtslBKSKFkbHlLJZZ0j4Nv6CMKizULVXztr1tmDa9qA=' } check.dependsOn('checkAPIChanges') diff --git a/plugin-api/src/main/java/org/hyperledger/besu/plugin/data/BlockTraceResult.java b/plugin-api/src/main/java/org/hyperledger/besu/plugin/data/BlockTraceResult.java new file mode 100644 index 00000000000..1ee4779cc0c --- /dev/null +++ b/plugin-api/src/main/java/org/hyperledger/besu/plugin/data/BlockTraceResult.java @@ -0,0 +1,124 @@ +/* + * Copyright Hyperledger Besu Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.plugin.data; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Objects; + +/** + * Represents the result of tracing a block, containing information about the transaction traces. + */ +public class BlockTraceResult { + final List transactionTraceResults; + + /** + * Constructs a BlockTraceResult with the given list of transaction trace results. + * + * @param transactionTraceResults The list of transaction trace results to be associated with this + * block. + */ + public BlockTraceResult(final List transactionTraceResults) { + this.transactionTraceResults = transactionTraceResults; + } + + /** + * Creates an empty BlockTraceResult with no transaction trace results. + * + * @return An empty BlockTraceResult. + */ + public static BlockTraceResult empty() { + return new BlockTraceResult(new ArrayList<>()); + } + + /** + * Get the list of transaction trace results for this block. + * + * @return The list of transaction trace results. + */ + public List transactionTraceResults() { + return transactionTraceResults; + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + final BlockTraceResult that = (BlockTraceResult) o; + return transactionTraceResults.equals(that.transactionTraceResults()); + } + + @Override + public int hashCode() { + return Objects.hash(transactionTraceResults); + } + + @Override + public String toString() { + final StringBuilder builder = new StringBuilder(); + builder.append("BlockTraceResult{transactionTraceResults=["); + + final Iterator iterator = transactionTraceResults.iterator(); + while (iterator.hasNext()) { + builder.append(iterator.next().toString()); + + if (iterator.hasNext()) { + builder.append(","); + } + } + builder.append("]}"); + return builder.toString(); + } + + /** + * Creates a new builder to construct a BlockTraceResult. + * + * @return A new BlockTraceResult.Builder instance. + */ + public static Builder builder() { + return new Builder(); + } + + /** A builder class for constructing a BlockTraceResult. */ + public static class Builder { + List transactionTraceResults = new ArrayList<>(); + + /** + * Adds a transaction trace result to the builder. + * + * @param transactionTraceResult The transaction trace result to add to the builder. + * @return This builder instance, for method chaining. + */ + public Builder addTransactionTraceResult(final TransactionTraceResult transactionTraceResult) { + transactionTraceResults.add(transactionTraceResult); + + return this; + } + + /** + * Constructs a BlockTraceResult using the transaction trace results added to the builder. + * + * @return A BlockTraceResult containing the added transaction trace results. + */ + public BlockTraceResult build() { + return new BlockTraceResult(transactionTraceResults); + } + } +} diff --git a/plugin-api/src/main/java/org/hyperledger/besu/plugin/data/TransactionTraceResult.java b/plugin-api/src/main/java/org/hyperledger/besu/plugin/data/TransactionTraceResult.java new file mode 100644 index 00000000000..8b6619de285 --- /dev/null +++ b/plugin-api/src/main/java/org/hyperledger/besu/plugin/data/TransactionTraceResult.java @@ -0,0 +1,128 @@ +/* + * Copyright Hyperledger Besu Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.plugin.data; + +import org.hyperledger.besu.datatypes.Hash; + +import java.util.Objects; +import java.util.Optional; + +/** + * Represents the result of tracing a transaction, including its status and optional error message. + */ +public class TransactionTraceResult { + /** Enumeration representing the status of the transaction trace. */ + public enum Status { + /** + * The transaction was traced successfully. This might include transactions that have been + * reverted. + */ + SUCCESS, + /** There was an internal error while generating the trace. */ + ERROR + } + + private final Hash txHash; + private final Status status; + private final String errorMessage; + + private TransactionTraceResult( + final Hash txHash, final Status status, final String errorMessage) { + this.txHash = txHash; + this.status = status; + this.errorMessage = errorMessage; + } + + /** + * Creates a TransactionTraceResult with a successful status and the given transaction hash. + * + * @param txHash The hash of the traced transaction. + * @return A successful TransactionTraceResult. + */ + public static TransactionTraceResult success(final Hash txHash) { + return new TransactionTraceResult(txHash, Status.SUCCESS, null); + } + + /** + * Creates a TransactionTraceResult with an error status, the given transaction hash, and an error + * message. + * + * @param txHash The hash of the traced transaction. + * @param errorMessage An error message describing the issue encountered during tracing. + * @return An error TransactionTraceResult. + */ + public static TransactionTraceResult error(final Hash txHash, final String errorMessage) { + return new TransactionTraceResult(txHash, Status.ERROR, errorMessage); + } + + /** + * Get the hash of the traced transaction. + * + * @return The hash of the transaction. + */ + public Hash getTxHash() { + return txHash; + } + + /** + * Get the status of the transaction trace. + * + * @return The status of the transaction trace. + */ + public Status getStatus() { + return status; + } + + /** + * Get an optional error message associated with the transaction trace. + * + * @return An optional error message, which may be empty if no error occurred. + */ + public Optional errorMessage() { + return Optional.ofNullable(errorMessage); + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + final TransactionTraceResult that = (TransactionTraceResult) o; + return Objects.equals(txHash, that.txHash) + && status == that.status + && Objects.equals(errorMessage, that.errorMessage); + } + + @Override + public int hashCode() { + return Objects.hash(txHash, status, errorMessage); + } + + @Override + public String toString() { + return "TransactionTraceResult{" + + "txHash=" + + txHash + + ", status=" + + status + + ", errorMessage='" + + errorMessage + + '\'' + + '}'; + } +} diff --git a/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/TraceService.java b/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/TraceService.java index 0b4809544ab..b084d3712c7 100644 --- a/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/TraceService.java +++ b/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/TraceService.java @@ -17,6 +17,7 @@ import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.evm.worldstate.WorldUpdater; import org.hyperledger.besu.plugin.Unstable; +import org.hyperledger.besu.plugin.data.BlockTraceResult; import org.hyperledger.besu.plugin.services.tracer.BlockAwareOperationTracer; import java.util.function.Consumer; @@ -29,16 +30,18 @@ public interface TraceService extends BesuService { * * @param blockNumber the block number * @param tracer the tracer (OperationTracer) + * @return BlockTraceResult the result of the trace */ - void traceBlock(long blockNumber, BlockAwareOperationTracer tracer); + BlockTraceResult traceBlock(long blockNumber, BlockAwareOperationTracer tracer); /** * Traces a block by hash * * @param hash the block hash * @param tracer the tracer (OperationTracer) + * @return BlockTraceResult the result of the trace */ - void traceBlock(Hash hash, BlockAwareOperationTracer tracer); + BlockTraceResult traceBlock(Hash hash, BlockAwareOperationTracer tracer); /** * Traces range of blocks