diff --git a/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/core/Gas.java b/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/core/Gas.java index 94ef1ca977..003375b049 100644 --- a/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/core/Gas.java +++ b/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/core/Gas.java @@ -149,4 +149,8 @@ public boolean equals(final Object obj) { public String toString() { return Long.toString(value); } + + public String toHexString() { + return String.format("0x%s", Long.toHexString(value)); + } } diff --git a/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/JsonRpcMethodsFactory.java b/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/JsonRpcMethodsFactory.java index 9e61c3f154..60103deed5 100644 --- a/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/JsonRpcMethodsFactory.java +++ b/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/JsonRpcMethodsFactory.java @@ -79,6 +79,7 @@ import tech.pegasys.pantheon.ethereum.jsonrpc.internal.methods.NetServices; import tech.pegasys.pantheon.ethereum.jsonrpc.internal.methods.NetVersion; import tech.pegasys.pantheon.ethereum.jsonrpc.internal.methods.RpcModules; +import tech.pegasys.pantheon.ethereum.jsonrpc.internal.methods.TraceReplayBlockTransactions; import tech.pegasys.pantheon.ethereum.jsonrpc.internal.methods.TxPoolPantheonStatistics; import tech.pegasys.pantheon.ethereum.jsonrpc.internal.methods.TxPoolPantheonTransactions; import tech.pegasys.pantheon.ethereum.jsonrpc.internal.methods.Web3ClientVersion; @@ -333,6 +334,19 @@ blockchainQueries, new TransactionTracer(blockReplay), parameter), new AdminChangeLogLevel(parameter)); } + if (rpcApis.contains(RpcApis.TRACE)) { + addMethods( + enabledMethods, + new TraceReplayBlockTransactions( + parameter, + new BlockTracer( + new BlockReplay( + protocolSchedule, + blockchainQueries.getBlockchain(), + blockchainQueries.getWorldStateArchive())), + blockchainQueries)); + } + boolean eea = rpcApis.contains(RpcApis.EEA), priv = rpcApis.contains(RpcApis.PRIV); if (eea || priv) { final PrivateMarkerTransactionFactory markerTransactionFactory = diff --git a/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/RpcApis.java b/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/RpcApis.java index 3d17901110..dc9ee078aa 100644 --- a/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/RpcApis.java +++ b/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/RpcApis.java @@ -28,6 +28,7 @@ public class RpcApis { public static final RpcApi EEA = new RpcApi("EEA"); public static final RpcApi PRIV = new RpcApi("PRIV"); public static final RpcApi TX_POOL = new RpcApi("TXPOOL"); + public static final RpcApi TRACE = new RpcApi("TRACE"); public static final List DEFAULT_JSON_RPC_APIS = Arrays.asList(ETH, NET, WEB3); @@ -52,6 +53,8 @@ public static Optional valueOf(final String name) { return Optional.of(PRIV); } else if (name.equals(TX_POOL.getCliValue())) { return Optional.of(TX_POOL); + } else if (name.equals(TRACE.getCliValue())) { + return Optional.of(TRACE); } else { return Optional.empty(); } diff --git a/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/methods/TraceReplayBlockTransactions.java b/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/methods/TraceReplayBlockTransactions.java index 1dcd29207f..2c1e4a6731 100644 --- a/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/methods/TraceReplayBlockTransactions.java +++ b/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/methods/TraceReplayBlockTransactions.java @@ -12,8 +12,11 @@ */ package tech.pegasys.pantheon.ethereum.jsonrpc.internal.methods; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.ObjectNode; import tech.pegasys.pantheon.ethereum.core.Block; import tech.pegasys.pantheon.ethereum.core.BlockHeader; +import tech.pegasys.pantheon.ethereum.core.Gas; import tech.pegasys.pantheon.ethereum.debug.TraceOptions; import tech.pegasys.pantheon.ethereum.jsonrpc.RpcMethod; import tech.pegasys.pantheon.ethereum.jsonrpc.internal.JsonRpcRequest; @@ -27,10 +30,12 @@ import tech.pegasys.pantheon.ethereum.vm.DebugOperationTracer; import java.util.List; +import java.util.Set; import java.util.stream.Collectors; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; +import tech.pegasys.pantheon.util.bytes.BytesValue; public class TraceReplayBlockTransactions extends AbstractBlockParameterMethod { @@ -82,23 +87,59 @@ private Object traceBlock(final Block block, final TraceTypeParameter traceTypeP .orElse(null); } - private List formatTraces( + private JsonNode formatTraces( final List traces, final TraceTypeParameter traceTypeParameter) { - return traces.stream() - .map((trace) -> formatTrace(trace, traceTypeParameter)) - .collect(Collectors.toList()); + final Set traceTypes = traceTypeParameter.getTraceTypes(); + final ObjectMapper mapper = new ObjectMapper(); + final ArrayNode resultArrayNode = mapper.createArrayNode(); + final ObjectNode resultNode = mapper.createObjectNode(); + if (traceTypes.contains(TraceTypeParameter.TraceType.TRACE)) { + final ArrayNode tracesNode = resultNode.putArray("trace"); + traces.forEach((trace) -> formatWithTraceOption(trace, mapper, tracesNode)); + } + + resultArrayNode.add(resultNode); + return resultArrayNode; } @SuppressWarnings("unused") - private JsonNode formatTrace( - final TransactionTrace trace, final TraceTypeParameter traceTypeParameter) { - // TODO: generate result - ObjectMapper mapper = new ObjectMapper(); - return mapper.createObjectNode(); + private void formatWithTraceOption( + final TransactionTrace trace, final ObjectMapper mapper, final ArrayNode tracesNode) { + final ObjectNode traceNode = mapper.createObjectNode(); + generateActionNode(traceNode, trace); + generateResultNode(traceNode, trace); + traceNode.put("type", "call"); + tracesNode.add(traceNode); + } + + private void generateResultNode(final ObjectNode traceNode, final TransactionTrace trace) { + final ObjectNode traceResultNode = traceNode.putObject("result"); + traceResultNode.put("output", "0x"); + traceResultNode.put("gasUsed", Gas.of(trace.getGas()).toHexString()); + } + + private void generateActionNode(final ObjectNode traceNode, final TransactionTrace trace) { + final ObjectNode actionResultNode = traceNode.putObject("action"); + actionResultNode.put("callType", "call"); + actionResultNode.put("from", trace.getTransaction().getSender().toString()); + actionResultNode.put( + "gas", trace.getTransaction().getUpfrontGasCost().toStrictShortHexString()); + actionResultNode.put( + "input", + trace + .getTransaction() + .getData() + .orElse(trace.getTransaction().getInit().orElse(BytesValue.EMPTY)) + .toString()); + trace + .getTransaction() + .getTo() + .ifPresent(address -> actionResultNode.put("to", address.toString())); + actionResultNode.put("value", trace.getTransaction().getValue().toStrictShortHexString()); } private Object emptyResult() { - ObjectMapper mapper = new ObjectMapper(); + final ObjectMapper mapper = new ObjectMapper(); return mapper.createArrayNode(); } } diff --git a/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/parameters/TraceTypeParameter.java b/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/parameters/TraceTypeParameter.java index 059cc0677f..8b13ba9dc5 100644 --- a/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/parameters/TraceTypeParameter.java +++ b/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/parameters/TraceTypeParameter.java @@ -25,7 +25,7 @@ public class TraceTypeParameter { - enum TraceType { + public enum TraceType { TRACE, VM_TRACE, STATE_DIFF;