Skip to content

Commit

Permalink
TraceService: return results for transactions in block
Browse files Browse the repository at this point in the history
Signed-off-by: Daniel Lehrner <[email protected]>
  • Loading branch information
daniellehrner committed Oct 25, 2023
1 parent 03a8335 commit 596fba5
Show file tree
Hide file tree
Showing 7 changed files with 263 additions and 18 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,16 @@
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;
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;

Expand Down Expand Up @@ -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> 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);
}

/**
Expand All @@ -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<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();
}

/**
Expand Down Expand Up @@ -136,15 +169,20 @@ public void trace(
});
}

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(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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<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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)));
}
}

Expand All @@ -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();
}
}
2 changes: 1 addition & 1 deletion plugin-api/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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 = 'pSutbB9biIQPQX14VvzzVGqfeT/SivfMh4rqDhPuPOQ='
}
check.dependsOn('checkAPIChanges')

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
/*
* 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;

public class BlockTraceResult {
final List<TransactionTraceResult> transactionTraceResults;

public BlockTraceResult(final List<TransactionTraceResult> transactionTraceResults) {
this.transactionTraceResults = transactionTraceResults;
}

public static BlockTraceResult empty() {
return new BlockTraceResult(new ArrayList<>());
}

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();
}

public static Builder builder() {
return new Builder();
}

public static class Builder {
List<TransactionTraceResult> transactionTraceResults = new ArrayList<>();

public Builder addTransactionTraceResult(final TransactionTraceResult transactionTraceResult) {
transactionTraceResults.add(transactionTraceResult);

return this;
}

public BlockTraceResult build() {
return new BlockTraceResult(transactionTraceResults);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
/*
* 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;

public class TransactionTraceResult {
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;
}

public static TransactionTraceResult success(final Hash txHash) {
return new TransactionTraceResult(txHash, Status.SUCCESS, null);
}

public static TransactionTraceResult error(final Hash txHash, final String errorMessage) {
return new TransactionTraceResult(txHash, Status.ERROR, errorMessage);
}

public Hash getTxHash() {
return txHash;
}

public Status getStatus() {
return status;
}

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
+ '\''
+ '}';
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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
Expand Down

0 comments on commit 596fba5

Please sign in to comment.