TraceService: return results for transactions in block (#6087)

* TraceService: return results for transactions in block

Signed-off-by: Daniel Lehrner <daniel.lehrner@consensys.net>
pull/5865/merge
daniellehrner 1 year ago committed by GitHub
parent accac1ccbc
commit a60b31b3af
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      CHANGELOG.md
  2. 64
      besu/src/main/java/org/hyperledger/besu/services/TraceServiceImpl.java
  3. 15
      besu/src/test/java/org/hyperledger/besu/services/TraceServiceImplTest.java
  4. 13
      ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetTransactionProcessor.java
  5. 2
      plugin-api/build.gradle
  6. 124
      plugin-api/src/main/java/org/hyperledger/besu/plugin/data/BlockTraceResult.java
  7. 128
      plugin-api/src/main/java/org/hyperledger/besu/plugin/data/TransactionTraceResult.java
  8. 7
      plugin-api/src/main/java/org/hyperledger/besu/plugin/services/TraceService.java

@ -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

@ -26,6 +26,7 @@ import org.hyperledger.besu.ethereum.api.query.BlockchainQueries;
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.processing.TransactionProcessingResult;
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 class TraceServiceImpl implements TraceService {
* @param tracer an instance of OperationTracer
*/
@Override
public void traceBlock(final long blockNumber, final BlockAwareOperationTracer tracer) {
checkArgument(tracer != null);
final Optional<Block> 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 class TraceServiceImpl implements TraceService {
* @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<Block> maybeBlock, final BlockAwareOperationTracer tracer) {
checkArgument(tracer != null);
final Optional<Block> block = blockchainQueries.getBlockchain().getBlockByHash(hash);
block.ifPresent(value -> trace(value, tracer));
if (maybeBlock.isEmpty()) {
return BlockTraceResult.empty();
}
final Optional<List<TransactionProcessingResult>> results = trace(maybeBlock.get(), tracer);
if (results.isEmpty()) {
return BlockTraceResult.empty();
}
final BlockTraceResult.Builder builder = BlockTraceResult.builder();
final List<TransactionProcessingResult> transactionProcessingResults = results.get();
final List<Transaction> 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 class TraceServiceImpl implements TraceService {
});
}
private void trace(final Block block, final BlockAwareOperationTracer tracer) {
private Optional<List<TransactionProcessingResult>> 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<List<TransactionProcessingResult>> 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<TransactionProcessingResult> trace(

@ -26,6 +26,8 @@ import org.hyperledger.besu.ethereum.worldstate.DataStorageFormat;
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 @@ class TraceServiceImplTest {
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<TransactionTraceResult> 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();

@ -500,7 +500,8 @@ public class MainnetTransactionProcessor {
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 @@ public class MainnetTransactionProcessor {
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();
}
}

@ -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')

@ -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<TransactionTraceResult> 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<TransactionTraceResult> 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<TransactionTraceResult> 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<TransactionTraceResult> 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<TransactionTraceResult> 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);
}
}
}

@ -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<String> 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
+ '\''
+ '}';
}
}

@ -17,6 +17,7 @@ package org.hyperledger.besu.plugin.services;
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

Loading…
Cancel
Save