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

Next Next commit
init trace_transaction api
Signed-off-by: Karim TAAM <[email protected]>
  • Loading branch information
matkt committed Mar 3, 2020
commit 61373109211d74e51f0feab2acb150867048ac60
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,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,102 @@
/*
* 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.Optional;
import java.util.concurrent.atomic.AtomicInteger;

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 BlockTracer blockTracer;

private final BlockchainQueries blockchainQueries;

public TraceTransaction(
final BlockTracer blockTracerSupplier, final BlockchainQueries blockchainQueries) {
this.blockTracer = 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 =
blockTracer.trace(block, new DebugOperationTracer(TraceOptions.DEFAULT))
.map(BlockTrace::getTransactionTraces).orElse(Collections.emptyList()).stream()
.filter(trxTrace -> trxTrace.getTransaction().getHash().equals(transactionHash))
.findFirst()
.orElseThrow();
return generateTracesFromTransactionTrace(transactionTrace, block);
}

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

final ArrayNode resultArrayNode = mapper.createArrayNode();

FlatTraceGenerator.generateFromTransactionTrace(
transactionTrace, Optional.of(block), new AtomicInteger())
.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 @@ -29,6 +29,10 @@
public class FlatTrace implements Trace {
private final Action action;
private final Result result;
private final Optional<Long> blockNumber;
private final Optional<String> blockHash;
private final Optional<Integer> transactionPosition;
private final Optional<String> transactionHash;
private final Optional<String> error;
private final int subtraces;
private final List<Integer> traceAddress;
Expand All @@ -40,13 +44,21 @@ private FlatTrace(
final int subtraces,
final List<Integer> traceAddress,
final String type,
final Optional<Long> blockNumber,
final Optional<String> blockHash,
final Optional<Integer> transactionPosition,
final Optional<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 +68,20 @@ private FlatTrace(
final int subtraces,
final List<Integer> traceAddress,
final String type,
final Optional<Long> blockNumber,
final Optional<String> blockHash,
final Optional<Integer> transactionPosition,
final Optional<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 +95,26 @@ public Action getAction() {
return action;
}

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

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

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

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

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

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

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

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

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

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

public Builder error(final Optional<String> error) {
this.error = error;
return this;
Expand All @@ -201,7 +265,17 @@ 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() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.tracing.Trace;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.tracing.TracingUtils;
import org.hyperledger.besu.ethereum.core.Address;
import org.hyperledger.besu.ethereum.core.Block;
import org.hyperledger.besu.ethereum.core.Gas;
import org.hyperledger.besu.ethereum.core.Transaction;
import org.hyperledger.besu.ethereum.core.Wei;
Expand Down Expand Up @@ -55,7 +56,9 @@ public class FlatTraceGenerator {
* @return a stream of generated traces {@link Trace}
*/
public static Stream<Trace> generateFromTransactionTrace(
final TransactionTrace transactionTrace, final AtomicInteger traceCounter) {
final TransactionTrace transactionTrace,
final Optional<Block> blockOptional,
final AtomicInteger traceCounter) {
final FlatTrace.Builder firstFlatTraceBuilder = FlatTrace.freshBuilder(transactionTrace);
final Transaction tx = transactionTrace.getTransaction();

Expand Down Expand Up @@ -86,6 +89,14 @@ public static Stream<Trace> generateFromTransactionTrace(
.address(smartContractAddress.orElse(null));
}

blockOptional.ifPresent(
block ->
firstFlatTraceBuilder
.blockHash(Optional.of(block.getHash().toHexString()))
.blockNumber(Optional.of(block.getHeader().getNumber()))
.transactionHash(
Optional.of(transactionTrace.getTransaction().getHash().toHexString())));

final List<FlatTrace.Builder> flatTraces = new ArrayList<>();

// stack of previous contexts
Expand Down Expand Up @@ -147,6 +158,17 @@ public static Stream<Trace> generateFromTransactionTrace(

return flatTraces.stream().map(FlatTrace.Builder::build);
}
/**
* Generates a stream of {@link Trace} from the passed {@link TransactionTrace} data.
*
* @param transactionTrace the {@link TransactionTrace} to use
* @param traceCounter the current trace counter value
* @return a stream of generated traces {@link Trace}
*/
public static Stream<Trace> generateFromTransactionTrace(
final TransactionTrace transactionTrace, final AtomicInteger traceCounter) {
return generateFromTransactionTrace(transactionTrace, Optional.empty(), traceCounter);
}

private static FlatTrace.Context handleCall(
final TransactionTrace transactionTrace,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import org.hyperledger.besu.ethereum.api.jsonrpc.RpcApis;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.JsonRpcMethod;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.TraceReplayBlockTransactions;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.TraceTransaction;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.processor.BlockReplay;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.processor.BlockTracer;
import org.hyperledger.besu.ethereum.api.query.BlockchainQueries;
Expand Down Expand Up @@ -50,6 +51,7 @@ protected Map<String, JsonRpcMethod> create() {
blockchainQueries.getBlockchain(),
blockchainQueries.getWorldStateArchive());
return mapOf(
new TraceReplayBlockTransactions(() -> new BlockTracer(blockReplay), blockchainQueries));
new TraceReplayBlockTransactions(() -> new BlockTracer(blockReplay), blockchainQueries),
new TraceTransaction(new BlockTracer(blockReplay), blockchainQueries));
}
}