Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add debug_standardTraceBlockToFile JSON RPC method #1392

Merged
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

### Additions and Improvements
* The new version of the [web3js-eea library (v0.10)](https://github.com/PegaSysEng/web3js-eea) supports the onchain privacy group management changes made in Besu v1.5.3.
* Added `debug_standardTraceBlockToFile` JSON-RPC API. This API accepts a block hash and will replay the block. It returns a list of files containing the result of the trace (one file per transaction). [\#1392](https://github.com/hyperledger/besu/pull/1392)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be moved to the 1.6.0-RC1 section once merged.


### Bug Fixes
* Added `debug_getBadBlocks` JSON-RPC API to analyze and detect consensus flaws. Even if a block is rejected it will be returned by this method [\#1378](https://github.com/hyperledger/besu/pull/1378)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ public enum RpcMethod {
DEBUG_TRACE_BLOCK("debug_traceBlock"),
DEBUG_TRACE_BLOCK_BY_HASH("debug_traceBlockByHash"),
DEBUG_TRACE_BLOCK_BY_NUMBER("debug_traceBlockByNumber"),
DEBUG_STANDARD_TRACE_BLOCK_TO_FILE("debug_standardTraceBlockToFile"),
DEBUG_TRACE_TRANSACTION("debug_traceTransaction"),
DEBUG_BATCH_RAW_TRANSACTION("debug_batchSendRawTransaction"),
DEBUG_GET_BAD_BLOCKS("debug_getBadBlocks"),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
/*
* Copyright ConsenSys AG.
*
* 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.ethereum.api.jsonrpc.internal.methods;

import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.TransactionTraceParams;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.processor.TransactionTracer;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcError;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcErrorResponse;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse;
import org.hyperledger.besu.ethereum.api.query.BlockchainQueries;
import org.hyperledger.besu.ethereum.core.Block;
import org.hyperledger.besu.ethereum.core.Hash;

import java.util.List;
import java.util.Optional;
import java.util.function.Supplier;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.base.Suppliers;

public class DebugStandardTraceBlockToFile implements JsonRpcMethod {

private final Supplier<TransactionTracer> transactionTracerSupplier;
private final Supplier<BlockchainQueries> blockchainQueries;

public DebugStandardTraceBlockToFile(
final Supplier<TransactionTracer> transactionTracerSupplier,
final BlockchainQueries blockchainQueries) {
this.transactionTracerSupplier = transactionTracerSupplier;
this.blockchainQueries = Suppliers.ofInstance(blockchainQueries);
}

@Override
public String getName() {
return RpcMethod.DEBUG_STANDARD_TRACE_BLOCK_TO_FILE.getMethodName();
}

@Override
public JsonRpcResponse response(final JsonRpcRequestContext requestContext) {
final Hash blockHash = requestContext.getRequiredParameter(0, Hash.class);
final Optional<TransactionTraceParams> transactionTraceParams =
requestContext.getOptionalParameter(1, TransactionTraceParams.class);

return blockchainQueries
.get()
.getBlockchain()
.getBlockByHash(blockHash)
.map(
block ->
(JsonRpcResponse)
new JsonRpcSuccessResponse(
requestContext.getRequest().getId(),
traceBlock(block, transactionTraceParams)))
.orElse(
new JsonRpcErrorResponse(
requestContext.getRequest().getId(), JsonRpcError.BLOCK_NOT_FOUND));
}

private List<String> traceBlock(
final Block block, final Optional<TransactionTraceParams> transactionTraceParams) {
return transactionTracerSupplier
.get()
.traceTransactionToFile(
block.getHash(), block.getBody().getTransactions(), transactionTraceParams);
}

protected Object emptyResult() {
final ObjectMapper mapper = new ObjectMapper();
return mapper.createArrayNode();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,30 +14,53 @@
*/
package org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters;

import org.hyperledger.besu.ethereum.core.Hash;
import org.hyperledger.besu.ethereum.debug.TraceOptions;

import com.fasterxml.jackson.annotation.JsonCreator;
import java.util.Optional;
import javax.annotation.Nullable;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import org.immutables.value.Value;

@Value.Immutable
@JsonSerialize(as = ImmutableTransactionTraceParams.class)
@JsonDeserialize(as = ImmutableTransactionTraceParams.class)
@JsonIgnoreProperties(ignoreUnknown = true)
public class TransactionTraceParams {

private final boolean disableStorage;
private final boolean disableMemory;
private final boolean disableStack;

@JsonCreator()
public TransactionTraceParams(
@JsonProperty("disableStorage") final boolean disableStorage,
@JsonProperty("disableMemory") final boolean disableMemory,
@JsonProperty("disableStack") final boolean disableStack) {
this.disableStorage = disableStorage;
this.disableMemory = disableMemory;
this.disableStack = disableStack;
public interface TransactionTraceParams {

@JsonProperty("txHash")
@Nullable
String transactionHash();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use standard getter/setters

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done


@JsonProperty(value = "disableStorage")
@Value.Default
default boolean disableStorage() {
return false;
}

@JsonProperty(value = "disableMemory")
@Value.Default
default boolean disableMemory() {
return false;
}

@JsonProperty(value = "disableStack")
@Value.Default
default boolean disableStack() {
return false;
}

default TraceOptions traceOptions() {
return new TraceOptions(!disableStorage(), !disableMemory(), !disableStack());
}

public TraceOptions traceOptions() {
return new TraceOptions(!disableStorage, !disableMemory, !disableStack);
default Optional<Hash> getTransactionHash() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will need to be renamed, or dropped.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

return transactionHash() == null
? Optional.empty()
: Optional.of(Hash.fromHexString(transactionHash()));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
*/
package org.hyperledger.besu.ethereum.api.jsonrpc.internal.processor;

import org.hyperledger.besu.ethereum.api.jsonrpc.internal.processor.BlockReplay.TransactionAction;
import org.hyperledger.besu.ethereum.core.AbstractWorldUpdater;
import org.hyperledger.besu.ethereum.core.AbstractWorldUpdater.StackedUpdater;
import org.hyperledger.besu.ethereum.core.Block;
Expand Down Expand Up @@ -47,7 +46,7 @@ public Optional<BlockTrace> trace(final Block block, final DebugOperationTracer
return blockReplay.block(block, prepareReplayAction(tracer));
}

private TransactionAction<TransactionTrace> prepareReplayAction(
private BlockReplay.TransactionAction<TransactionTrace> prepareReplayAction(
final DebugOperationTracer tracer) {
return (transaction, header, blockchain, mutableWorldState, transactionProcessor) -> {
// if we have no prior updater, it must be the first TX, so use the block's initial state
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,18 +19,32 @@
import org.hyperledger.besu.ethereum.mainnet.TransactionProcessor.Result;

import java.util.List;
import java.util.Optional;

public class TransactionTrace {

private final Transaction transaction;
private final Result result;
private final List<TraceFrame> traceFrames;
private final Optional<Long> time;

public TransactionTrace(
final Transaction transaction, final Result result, final List<TraceFrame> traceFrames) {
this.transaction = transaction;
this.result = result;
this.traceFrames = traceFrames;
this.time = Optional.empty();
}

public TransactionTrace(
final Transaction transaction,
final Result result,
final List<TraceFrame> traceFrames,
final Long time) {
this.transaction = transaction;
this.result = result;
this.traceFrames = traceFrames;
this.time = Optional.of(time);
}

public Transaction getTransaction() {
Expand All @@ -52,4 +66,8 @@ public Result getResult() {
public List<TraceFrame> getTraceFrames() {
return traceFrames;
}

public Optional<Long> getTime() {
return time;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,26 @@
*/
package org.hyperledger.besu.ethereum.api.jsonrpc.internal.processor;

import static java.util.function.Predicate.isEqual;

import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.TransactionTraceParams;
import org.hyperledger.besu.ethereum.core.Hash;
import org.hyperledger.besu.ethereum.core.Transaction;
import org.hyperledger.besu.ethereum.mainnet.TransactionProcessor.Result;
import org.hyperledger.besu.ethereum.mainnet.TransactionValidationParams;
import org.hyperledger.besu.ethereum.vm.BlockHashLookup;
import org.hyperledger.besu.ethereum.vm.DebugOperationTracer;
import org.hyperledger.besu.ethereum.vm.EVMToolTracer;

import java.io.File;
import java.io.FileOutputStream;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.TimeUnit;

import com.google.common.base.Stopwatch;

/** Used to produce debug traces of transactions */
public class TransactionTracer {
Expand All @@ -36,6 +50,7 @@ public Optional<TransactionTrace> traceTransaction(
blockHash,
transactionHash,
(transaction, header, blockchain, mutableWorldState, transactionProcessor) -> {
final Stopwatch timer = Stopwatch.createStarted();
final Result result =
transactionProcessor.processTransaction(
blockchain,
Expand All @@ -46,7 +61,71 @@ public Optional<TransactionTrace> traceTransaction(
tracer,
new BlockHashLookup(header, blockchain),
false);
return new TransactionTrace(transaction, result, tracer.getTraceFrames());
timer.stop();
return new TransactionTrace(
transaction, result, tracer.getTraceFrames(), timer.elapsed(TimeUnit.NANOSECONDS));
});
}

public Optional<TransactionTrace> traceTransaction(
final Hash blockHash, final Hash transactionHash, final EVMToolTracer tracer) {
return blockReplay.beforeTransactionInBlock(
blockHash,
transactionHash,
(transaction, header, blockchain, mutableWorldState, transactionProcessor) -> {
final Stopwatch timer = Stopwatch.createStarted();
final Result result =
transactionProcessor.processTransaction(
blockchain,
mutableWorldState.updater(),
header,
transaction,
header.getCoinbase(),
tracer,
new BlockHashLookup(header, blockchain),
false,
new TransactionValidationParams.Builder().allowFutureNonce(true).build());

timer.stop();
return new TransactionTrace(
transaction, result, new ArrayList<>(), timer.elapsed(TimeUnit.NANOSECONDS));
});
}

public List<String> traceTransactionToFile(
final Hash blockHash,
final List<Transaction> transactions,
final Optional<TransactionTraceParams> transactionTraceParams) {
final List<String> traces = new ArrayList<>();
try {
final Optional<Hash> selectedHash =
transactionTraceParams.flatMap(TransactionTraceParams::getTransactionHash);
final boolean showMemory =
transactionTraceParams.map(TransactionTraceParams::disableMemory).orElse(true);
for (int i = 0; i < transactions.size(); i++) {
final Transaction transaction = transactions.get(i);
if (selectedHash.isEmpty()
|| selectedHash.filter(isEqual(transaction.getHash())).isPresent()) {
final File tmpFile =
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we want these going to tmp? perhaps our data root under traces?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't have a strong opinion on it. I did this to be consistent with the other clients. But I just changed to put it in the data/traces now

File.createTempFile(
String.format(
"block_%.10s-%d-%.10s-",
blockHash.toHexString(), i, transaction.getHash().toHexString()),
"");
try (PrintStream out = new PrintStream(new FileOutputStream(tmpFile))) {
traceTransaction(blockHash, transaction.getHash(), new EVMToolTracer(out, showMemory))
.ifPresent(
trace ->
out.println(
EVMToolTracer.summaryTrace(
transaction, trace.getTime().orElseThrow(), trace.getResult())));
traces.add(tmpFile.getPath());
}
}
}
} catch (Exception e) {
throw new RuntimeException(String.format("Unable to trace transaction : %s", e.getMessage()));
}
return traces;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ public enum JsonRpcError {
WORLD_STATE_UNAVAILABLE(-32000, "World state unavailable"),

// Debug failures
BLOCK_NOT_FOUND(-32000, "Block not found"),
PARENT_BLOCK_NOT_FOUND(-32000, "Parent block not found"),

// Permissioning/Account allowlist errors
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.DebugBatchSendRawTransaction;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.DebugGetBadBlocks;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.DebugMetrics;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.DebugStandardTraceBlockToFile;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.DebugStorageRangeAt;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.DebugTraceBlock;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.DebugTraceBlockByHash;
Expand Down Expand Up @@ -83,6 +84,8 @@ protected Map<String, JsonRpcMethod> create() {
new DebugTraceBlockByNumber(() -> new BlockTracer(blockReplay), blockchainQueries),
new DebugTraceBlockByHash(() -> new BlockTracer(blockReplay)),
new DebugBatchSendRawTransaction(transactionPool),
new DebugGetBadBlocks(blockchainQueries, protocolSchedule, blockResult));
new DebugGetBadBlocks(blockchainQueries, protocolSchedule, blockResult),
new DebugStandardTraceBlockToFile(
() -> new TransactionTracer(blockReplay), blockchainQueries));
}
}
Loading