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

[PIE-2284] Add trace transaction api #441

3 changes: 2 additions & 1 deletion ethereum/api/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -134,4 +134,5 @@ task generateTestBlockchain() {
}
}
}
test.dependsOn(generateTestBlockchain)
// TODO must be reactivated when we can generate the block.bin file during build
//test.dependsOn(generateTestBlockchain)
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ public enum RpcMethod {
PERM_REMOVE_NODES_FROM_WHITELIST("perm_removeNodesFromWhitelist"),
RPC_MODULES("rpc_modules"),
TRACE_REPLAY_BLOCK_TRANSACTIONS("trace_replayBlockTransactions"),
TRACE_TRANSACTION("trace_transaction"),
TX_POOL_BESU_STATISTICS("txpool_besuStatistics"),
TX_POOL_BESU_TRANSACTIONS("txpool_besuTransactions"),
WEB3_CLIENT_VERSION("web3_clientVersion"),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
/*
* 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
*
* https://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.processor.BlockTrace;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.processor.BlockTracer;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.processor.TransactionTrace;
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.jsonrpc.internal.results.tracing.flat.FlatTraceGenerator;
import org.hyperledger.besu.ethereum.api.query.BlockchainQueries;
import org.hyperledger.besu.ethereum.api.query.TransactionWithMetadata;
import org.hyperledger.besu.ethereum.core.Block;
import org.hyperledger.besu.ethereum.core.Hash;
import org.hyperledger.besu.ethereum.debug.TraceOptions;
import org.hyperledger.besu.ethereum.vm.DebugOperationTracer;

import java.util.Collections;
import java.util.function.Supplier;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;

public class TraceTransaction implements JsonRpcMethod {
private final Supplier<BlockTracer> blockTracerSupplier;

private final BlockchainQueries blockchainQueries;

public TraceTransaction(
final Supplier<BlockTracer> blockTracerSupplier, final BlockchainQueries blockchainQueries) {
this.blockTracerSupplier = blockTracerSupplier;
this.blockchainQueries = blockchainQueries;
}

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

@Override
public JsonRpcResponse response(final JsonRpcRequestContext requestContext) {
final Hash transactionHash = requestContext.getRequiredParameter(0, Hash.class);
return new JsonRpcSuccessResponse(
requestContext.getRequest().getId(), resultByTransactionHash(transactionHash));
}

private Object resultByTransactionHash(final Hash transactionHash) {
return blockchainQueries
.transactionByHash(transactionHash)
.flatMap(TransactionWithMetadata::getBlockNumber)
.flatMap(blockNumber -> blockchainQueries.getBlockchain().getBlockByNumber(blockNumber))
.map((block) -> traceBlock(block, transactionHash))
.orElse(emptyResult());
}

private Object traceBlock(final Block block, final Hash transactionHash) {
if (block == null || block.getBody().getTransactions().isEmpty()) {
return emptyResult();
}
final TransactionTrace transactionTrace =
blockTracerSupplier.get().trace(block, new DebugOperationTracer(TraceOptions.DEFAULT))
.map(BlockTrace::getTransactionTraces).orElse(Collections.emptyList()).stream()
.filter(trxTrace -> trxTrace.getTransaction().getHash().equals(transactionHash))
.findFirst()
.orElseThrow();
return generateTracesFromTransactionTraceAndBlock(transactionTrace, block);
}

private JsonNode generateTracesFromTransactionTraceAndBlock(
final TransactionTrace transactionTrace, final Block block) {
final ObjectMapper mapper = new ObjectMapper();

final ArrayNode resultArrayNode = mapper.createArrayNode();

FlatTraceGenerator.generateFromTransactionTraceAndBlock(transactionTrace, block)
.forEachOrdered(resultArrayNode::addPOJO);

return resultArrayNode;
}

private Object emptyResult() {
final ObjectMapper mapper = new ObjectMapper();
return mapper.createArrayNode();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
@JsonInclude(NON_NULL)
public class Action {

private final String creationMethod;
private final String callType;
private final String from;
private final String gas;
Expand All @@ -36,6 +37,7 @@ public class Action {
private final String refundAddress;

private Action(
final String creationMethod,
final String callType,
final String from,
final String gas,
Expand All @@ -46,6 +48,7 @@ private Action(
final String address,
final String balance,
final String refundAddress) {
this.creationMethod = creationMethod;
this.callType = callType;
this.from = from;
this.gas = gas;
Expand All @@ -62,6 +65,10 @@ public static Builder builder() {
return new Builder();
}

public String getCreationMethod() {
return creationMethod;
}

public String getCallType() {
return callType;
}
Expand Down Expand Up @@ -103,6 +110,7 @@ public String getRefundAddress() {
}

public static final class Builder {
private String creationMethod;
private String callType;
private String from;
private String gas;
Expand All @@ -118,6 +126,7 @@ private Builder() {}

public static Builder of(final Action action) {
final Builder builder = new Builder();
builder.creationMethod = action.creationMethod;
builder.callType = action.callType;
builder.from = action.from;
builder.gas = action.gas;
Expand All @@ -138,6 +147,11 @@ public static Builder from(final TransactionTrace trace) {
.value(Quantity.create(trace.getTransaction().getValue()));
}

public Builder creationMethod(final String creationMethod) {
this.creationMethod = creationMethod;
return this;
}

public Builder callType(final String callType) {
this.callType = callType;
return this;
Expand Down Expand Up @@ -206,7 +220,17 @@ public String getGas() {

public Action build() {
return new Action(
callType, from, gas, input, to, init, value, address, balance, refundAddress);
creationMethod,
callType,
from,
gas,
input,
to,
init,
value,
address,
balance,
refundAddress);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,27 @@
import java.util.concurrent.atomic.AtomicReference;

import com.fasterxml.jackson.annotation.JsonInclude;

import com.fasterxml.jackson.annotation.JsonPropertyOrder;

@JsonPropertyOrder({
"action",
"blockHash",
"blockNumber",
"result",
"error",
"subtraces",
"traceAddress",
"transactionHash",
"transactionPosition",
"type"
})
public class FlatTrace implements Trace {
private final Action action;
private final Result result;
private final Long blockNumber;
private final String blockHash;
private final Integer transactionPosition;
private final String transactionHash;
private final Optional<String> error;
private final int subtraces;
private final List<Integer> traceAddress;
Expand All @@ -40,13 +57,21 @@ private FlatTrace(
final int subtraces,
final List<Integer> traceAddress,
final String type,
final Long blockNumber,
final String blockHash,
final Integer transactionPosition,
final String transactionHash,
final Optional<String> error) {
this(
actionBuilder != null ? actionBuilder.build() : null,
resultBuilder != null ? resultBuilder.build() : null,
subtraces,
traceAddress,
type,
blockNumber,
blockHash,
transactionPosition,
transactionHash,
error);
}

Expand All @@ -56,12 +81,20 @@ private FlatTrace(
final int subtraces,
final List<Integer> traceAddress,
final String type,
final Long blockNumber,
final String blockHash,
final Integer transactionPosition,
final String transactionHash,
final Optional<String> error) {
this.action = action;
this.result = result;
this.subtraces = subtraces;
this.traceAddress = traceAddress;
this.type = type;
this.blockNumber = blockNumber;
this.blockHash = blockHash;
this.transactionPosition = transactionPosition;
this.transactionHash = transactionHash;
this.error = error;
}

Expand All @@ -75,6 +108,26 @@ public Action getAction() {
return action;
}

@JsonInclude(NON_NULL)
public Long getBlockNumber() {
return blockNumber;
}

@JsonInclude(NON_NULL)
public String getBlockHash() {
return blockHash;
}

@JsonInclude(NON_NULL)
public String getTransactionHash() {
return transactionHash;
}

@JsonInclude(NON_NULL)
public Integer getTransactionPosition() {
return transactionPosition;
}

@JsonInclude(NON_NULL)
public String getError() {
return error.orElse(null);
Expand Down Expand Up @@ -159,6 +212,10 @@ public static final class Builder {
private int subtraces;
private List<Integer> traceAddress = new ArrayList<>();
private String type = "call";
private Long blockNumber;
private String blockHash;
private String transactionHash;
private Integer transactionPosition;
private Optional<String> error = Optional.empty();

private Builder() {}
Expand Down Expand Up @@ -191,6 +248,26 @@ public String getType() {
return type;
}

public Builder blockNumber(final Long blockNumber) {
this.blockNumber = blockNumber;
return this;
}

public Builder blockHash(final String blockHash) {
this.blockHash = blockHash;
return this;
}

public Builder transactionHash(final String transactionHash) {
this.transactionHash = transactionHash;
return this;
}

public Builder transactionPosition(final Integer transactionPosition) {
this.transactionPosition = transactionPosition;
return this;
}

public Builder error(final Optional<String> error) {
this.error = error;
return this;
Expand All @@ -201,14 +278,24 @@ void incSubTraces() {
}

public FlatTrace build() {
return new FlatTrace(actionBuilder, resultBuilder, subtraces, traceAddress, type, error);
return new FlatTrace(
actionBuilder,
resultBuilder,
subtraces,
traceAddress,
type,
blockNumber,
blockHash,
transactionPosition,
transactionHash,
error);
}

Result.Builder getResultBuilder() {
public Result.Builder getResultBuilder() {
return resultBuilder;
}

Action.Builder getActionBuilder() {
public Action.Builder getActionBuilder() {
return actionBuilder;
}
}
Expand Down
Loading