Skip to content
This repository has been archived by the owner on Sep 26, 2019. It is now read-only.

[PAN-2499] debug trace transaction #1258

Merged
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ public class Transaction {
// Note that this hash does not include the transaction signature so it does not
// fully identify the transaction (use the result of the {@code hash()} for that).
// It is only used to compute said signature and recover the sender from it.
protected volatile Bytes32 hashNoSignature;
private volatile Bytes32 hashNoSignature;

// Caches the transaction sender.
protected volatile Address sender;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ public class TraceFrame {
private final Optional<Bytes32[]> stack;
private final Optional<Bytes32[]> memory;
private final Optional<Map<UInt256, UInt256>> storage;
private final String reason;

public TraceFrame(
final int pc,
Expand All @@ -44,7 +45,8 @@ public TraceFrame(
final EnumSet<ExceptionalHaltReason> exceptionalHaltReasons,
final Optional<Bytes32[]> stack,
final Optional<Bytes32[]> memory,
final Optional<Map<UInt256, UInt256>> storage) {
final Optional<Map<UInt256, UInt256>> storage,
final String reason) {
this.pc = pc;
this.opcode = opcode;
this.gasRemaining = gasRemaining;
Expand All @@ -54,6 +56,30 @@ public TraceFrame(
this.stack = stack;
this.memory = memory;
this.storage = storage;
this.reason = reason;
}

public TraceFrame(
final int pc,
final String opcode,
final Gas gasRemaining,
final Optional<Gas> gasCost,
final int depth,
final EnumSet<ExceptionalHaltReason> exceptionalHaltReasons,
final Optional<Bytes32[]> stack,
final Optional<Bytes32[]> memory,
final Optional<Map<UInt256, UInt256>> storage) {
this(
pc,
opcode,
gasRemaining,
gasCost,
depth,
exceptionalHaltReasons,
stack,
memory,
storage,
null);
}

public int getPc() {
Expand Down Expand Up @@ -92,6 +118,10 @@ public Optional<Map<UInt256, UInt256>> getStorage() {
return storage;
}

public String getReason() {
return reason;
}

@Override
public String toString() {
return MoreObjects.toStringHelper(this)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,8 @@ public void traceExecution(
exceptionalHaltReasons,
stack,
memory,
storage));
storage,
frame.getReason().orElse(null)));
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,7 @@
import tech.pegasys.pantheon.util.uint.UInt256;
import tech.pegasys.pantheon.util.uint.UInt256Value;

import java.util.Deque;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.Set;
import java.util.*;
import java.util.function.Consumer;

/**
Expand Down Expand Up @@ -215,6 +212,7 @@ public enum Type {
private final Deque<MessageFrame> messageFrameStack;
private final Address miningBeneficiary;
private final Boolean isPersistingState;
private Optional<String> reason;

// Miscellaneous fields.
private final EnumSet<ExceptionalHaltReason> exceptionalHaltReasons =
Expand Down Expand Up @@ -247,7 +245,8 @@ private MessageFrame(
final Consumer<MessageFrame> completer,
final Address miningBeneficiary,
final BlockHashLookup blockHashLookup,
final Boolean isPersistingState) {
final Boolean isPersistingState,
final Optional<String> reason) {
this.type = type;
this.blockchain = blockchain;
this.messageFrameStack = messageFrameStack;
Expand Down Expand Up @@ -278,6 +277,7 @@ private MessageFrame(
this.completer = completer;
this.miningBeneficiary = miningBeneficiary;
this.isPersistingState = isPersistingState;
this.reason = reason;
}

/**
Expand Down Expand Up @@ -495,6 +495,19 @@ public UInt256 memoryWordSize() {
return memory.getActiveWords();
}

/**
* Returns the reason as string
*
* @return the reason string
*/
public Optional<String> getReason() {
return reason;
}

public void setReason(final String reason) {
this.reason = Optional.ofNullable(reason);
}

/**
* Read bytes in memory.
*
Expand Down Expand Up @@ -845,6 +858,7 @@ public static class Builder {
private Address miningBeneficiary;
private BlockHashLookup blockHashLookup;
private Boolean isPersistingState = false;
private Optional<String> reason = Optional.empty();

public Builder type(final Type type) {
this.type = type;
Expand Down Expand Up @@ -951,6 +965,11 @@ public Builder isPersistingState(final Boolean isPersistingState) {
return this;
}

public Builder reason(final String reason) {
this.reason = Optional.ofNullable(reason);
return this;
}

private void validate() {
checkState(type != null, "Missing message frame type");
checkState(blockchain != null, "Missing message frame blockchain");
Expand Down Expand Up @@ -998,7 +1017,8 @@ public MessageFrame build() {
completer,
miningBeneficiary,
blockHashLookup,
isPersistingState);
isPersistingState,
reason);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,13 @@
import tech.pegasys.pantheon.ethereum.vm.AbstractOperation;
import tech.pegasys.pantheon.ethereum.vm.GasCalculator;
import tech.pegasys.pantheon.ethereum.vm.MessageFrame;
import tech.pegasys.pantheon.util.bytes.BytesValue;
import tech.pegasys.pantheon.util.uint.UInt256;

import java.nio.charset.Charset;

public class RevertOperation extends AbstractOperation {
private static final Charset CHARSET = Charset.forName("UTF-8");

public RevertOperation(final GasCalculator gasCalculator) {
super(0xFD, "REVERT", 2, 0, false, 1, gasCalculator);
Expand All @@ -36,8 +40,10 @@ public Gas cost(final MessageFrame frame) {
public void execute(final MessageFrame frame) {
final UInt256 from = frame.popStackItem().asUInt256();
final UInt256 length = frame.popStackItem().asUInt256();

frame.setOutputData(frame.readMemory(from, length));
BytesValue reason = frame.readMemory(from, length);
frame.setOutputData(reason);
String reasonMessage = new String(reason.extractArray(), CHARSET);
frame.setReason(reasonMessage);
frame.setState(MessageFrame.State.REVERT);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/*
* Copyright 2018 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.
*/
package tech.pegasys.pantheon.ethereum.vm.operations;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

import tech.pegasys.pantheon.ethereum.mainnet.ConstantinopleGasCalculator;
import tech.pegasys.pantheon.ethereum.vm.MessageFrame;
import tech.pegasys.pantheon.util.bytes.Bytes32;
import tech.pegasys.pantheon.util.bytes.BytesValue;
import tech.pegasys.pantheon.util.uint.UInt256;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
import org.mockito.ArgumentCaptor;
import org.mockito.Mockito;

@RunWith(Parameterized.class)
public class RevertOperationTest {

private final String code;

private final MessageFrame messageFrame = mock(MessageFrame.class);
private final RevertOperation operation = new RevertOperation(new ConstantinopleGasCalculator());

@Parameters(name = "sender: {0}, salt: {1}, code: {2}")
public static Object[][] params() {
return new Object[][] {
{
"0x6c726576657274656420646174616000557f726576657274206d657373616765000000000000000000000000000000000000600052600e6000fd",
}
};
}

public RevertOperationTest(final String code) {
this.code = code;
}

@Before
public void setUp() {
when(messageFrame.popStackItem())
.thenReturn(Bytes32.fromHexString("0x00"))
.thenReturn(Bytes32.fromHexString("0x0e"));
when(messageFrame.readMemory(UInt256.ZERO, UInt256.of(0x0e)))
.thenReturn(BytesValue.fromHexString("726576657274206d657373616765"));
}

@Test
public void shouldReturnReason() {
assertTrue(code.contains("726576657274206d657373616765"));
ArgumentCaptor<String> arg = ArgumentCaptor.forClass(String.class);
operation.execute(messageFrame);
Mockito.verify(messageFrame).setReason(arg.capture());
assertEquals("revert message", arg.getValue());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
import tech.pegasys.pantheon.ethereum.jsonrpc.internal.processor.TransactionTracer;
import tech.pegasys.pantheon.ethereum.jsonrpc.internal.queries.BlockchainQueries;
import tech.pegasys.pantheon.ethereum.jsonrpc.internal.queries.TransactionWithMetadata;
import tech.pegasys.pantheon.ethereum.jsonrpc.internal.response.JsonRpcError;
import tech.pegasys.pantheon.ethereum.jsonrpc.internal.response.JsonRpcErrorResponse;
import tech.pegasys.pantheon.ethereum.jsonrpc.internal.response.JsonRpcResponse;
import tech.pegasys.pantheon.ethereum.jsonrpc.internal.response.JsonRpcSuccessResponse;
import tech.pegasys.pantheon.ethereum.jsonrpc.internal.results.DebugTraceTransactionResult;
Expand Down Expand Up @@ -54,19 +56,31 @@ public JsonRpcResponse response(final JsonRpcRequest request) {
parameters.optional(request.getParams(), 1, TransactionTraceParams.class);
final Optional<TransactionWithMetadata> transactionWithMetadata =
blockchain.transactionByHash(hash);
final Hash blockHash = transactionWithMetadata.get().getBlockHash();
if (transactionWithMetadata.isPresent()) {
DebugTraceTransactionResult debugTraceTransactionResult =
debugTraceTransactionResult(hash, transactionWithMetadata.get(), transactionTraceParams);

return new JsonRpcSuccessResponse(request.getId(), debugTraceTransactionResult);
} else {
return new JsonRpcErrorResponse(request.getId(), JsonRpcError.TX_NOT_FOUND);
}
}

private DebugTraceTransactionResult debugTraceTransactionResult(
final Hash hash,
final TransactionWithMetadata transactionWithMetadata,
final Optional<TransactionTraceParams> transactionTraceParams) {
final Hash blockHash = transactionWithMetadata.getBlockHash();
final TraceOptions traceOptions =
transactionTraceParams
.map(TransactionTraceParams::traceOptions)
.orElse(TraceOptions.DEFAULT);

final DebugOperationTracer execTracer = new DebugOperationTracer(traceOptions);

final DebugTraceTransactionResult result =
transactionTracer
.traceTransaction(blockHash, hash, execTracer)
.map(DebugTraceTransactionResult::new)
.orElse(null);
return new JsonRpcSuccessResponse(request.getId(), result);
return transactionTracer
.traceTransaction(blockHash, hash, execTracer)
.map(DebugTraceTransactionResult::new)
.orElse(null);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ public enum JsonRpcError {
INCORRECT_NONCE(-32006, "Incorrect nonce"),
TX_SENDER_NOT_AUTHORIZED(-32007, "Sender account not authorized to send transactions"),
CHAIN_HEAD_WORLD_STATE_NOT_AVAILABLE(-32008, "Initial sync is still in progress"),

TX_NOT_FOUND(-32009, "Transaction not found"),
// Miner failures
COINBASE_NOT_SET(-32010, "Coinbase not set. Unable to start mining without a coinbase"),
NO_HASHES_PER_SECOND(-32011, "No hashes being generated by the current node"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ public class StructLog {
private final int pc;
private final String[] stack;
private final Object storage;
private final String reason;

public StructLog(final TraceFrame traceFrame) {
depth = traceFrame.getDepth() + 1;
Expand All @@ -54,6 +55,7 @@ public StructLog(final TraceFrame traceFrame) {
.map(a -> Arrays.stream(a).map(Bytes32s::unprefixedHexString).toArray(String[]::new))
.orElse(null);
storage = traceFrame.getStorage().map(StructLog::formatStorage).orElse(null);
reason = traceFrame.getReason();
}

private static Map<String, String> formatStorage(final Map<UInt256, UInt256> storage) {
Expand Down Expand Up @@ -104,6 +106,11 @@ public Object storage() {
return storage;
}

@JsonGetter("reason")
public String reason() {
return reason;
}

@Override
public boolean equals(final Object o) {
if (this == o) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,8 @@ public void shouldTraceTheTransactionUsingTheTransactionTracer() {
EnumSet.noneOf(ExceptionalHaltReason.class),
Optional.empty(),
Optional.empty(),
Optional.empty());
Optional.empty(),
"revert message");
final List<TraceFrame> traceFrames = Collections.singletonList(traceFrame);
final TransactionTrace transactionTrace =
new TransactionTrace(transaction, result, traceFrames);
Expand Down
Loading