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

Trace API fixes #377

Merged
merged 9 commits into from
Feb 10, 2020
Merged
Show file tree
Hide file tree
Changes from 8 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 @@ -41,6 +41,10 @@ public long getGas() {
return transaction.getGasLimit() - result.getGasRemaining();
}

public long getGasLimit() {
return transaction.getGasLimit();
}

public Result getResult() {
return result;
}
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.internal.results.Quantity;
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.api.jsonrpc.internal.results.tracing.flat.FlatTrace.Context;
import org.hyperledger.besu.ethereum.core.Address;
import org.hyperledger.besu.ethereum.core.Gas;
import org.hyperledger.besu.ethereum.core.Transaction;
Expand Down Expand Up @@ -100,7 +101,9 @@ public static Stream<Trace> generateFromTransactionTrace(
int traceFrameIndex = 0;
final List<TraceFrame> traceFrames = transactionTrace.getTraceFrames();
for (final TraceFrame traceFrame : traceFrames) {
cumulativeGasCost += traceFrame.getGasCost().orElse(Gas.ZERO).toLong();
cumulativeGasCost +=
traceFrame.getGasCost().orElse(Gas.ZERO).toLong()
+ traceFrame.getPrecompiledGasCost().orElse(Gas.ZERO).toLong();
final String opcodeString = traceFrame.getOpcode();
if ("CALL".equals(opcodeString)
|| "CALLCODE".equals(opcodeString)
Expand Down Expand Up @@ -129,15 +132,14 @@ public static Stream<Trace> generateFromTransactionTrace(
} else if ("CREATE".equals(opcodeString) || "CREATE2".equals(opcodeString)) {
currentContext =
handleCreateOperation(
transactionTrace,
smartContractAddress,
flatTraces,
tracesContexts,
cumulativeGasCost,
traceFrameIndex,
traceFrames);
} else if ("REVERT".equals(opcodeString)) {
currentContext.getBuilder().error(Optional.of("Reverted"));
currentContext = handleRevert(tracesContexts, currentContext);
} else if (!traceFrame.getExceptionalHaltReasons().isEmpty()) {
currentContext
.getBuilder()
Expand All @@ -148,6 +150,7 @@ public static Stream<Trace> generateFromTransactionTrace(
}
traceFrameIndex++;
}

return flatTraces.stream().map(FlatTrace.Builder::build);
}

Expand Down Expand Up @@ -207,6 +210,7 @@ private static FlatTrace.Context handleReturn(
final FlatTrace.Builder traceFrameBuilder = currentContext.getBuilder();
final Result.Builder resultBuilder = traceFrameBuilder.getResultBuilder();
final Action.Builder actionBuilder = traceFrameBuilder.getActionBuilder();
actionBuilder.value(Quantity.create(traceFrame.getValue()));
final Bytes outputData = traceFrame.getOutputData();
if (resultBuilder.getCode() == null) {
resultBuilder.output(outputData.toHexString());
Expand Down Expand Up @@ -267,7 +271,6 @@ private static FlatTrace.Context handleSelfDestruct(
}

private static FlatTrace.Context handleCreateOperation(
final TransactionTrace transactionTrace,
final Optional<String> smartContractAddress,
final List<FlatTrace.Builder> flatTraces,
final Deque<FlatTrace.Context> tracesContexts,
Expand All @@ -287,7 +290,7 @@ private static FlatTrace.Context handleCreateOperation(
Action.builder()
.from(smartContractAddress.orElse(callingAddress))
.gas(nextTraceFrame.getGasRemaining().toHexString())
.value(Quantity.create(transactionTrace.getTransaction().getValue()));
.value(Quantity.create(nextTraceFrame.getValue()));

final FlatTrace.Context currentContext =
new FlatTrace.Context(subTraceBuilder.actionBuilder(subTraceActionBuilder));
Expand All @@ -298,6 +301,17 @@ private static FlatTrace.Context handleCreateOperation(
return currentContext;
}

private static Context handleRevert(
final Deque<Context> tracesContexts, final FlatTrace.Context currentContext) {
currentContext.getBuilder().error(Optional.of("Reverted"));
tracesContexts.removeLast();
final FlatTrace.Context nextContext = tracesContexts.peekLast();
if (nextContext != null) {
nextContext.getBuilder().incSubTraces();
}
return nextContext;
}

private static String calculateCallingAddress(final FlatTrace.Context lastContext) {
if (lastContext.getBuilder().getActionBuilder().getCallType() == null) {
return ZERO_ADDRESS_STRING;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,18 @@
*/
package org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.tracing.vm;

import static com.fasterxml.jackson.annotation.JsonInclude.Include.NON_NULL;

import java.util.Objects;

import com.fasterxml.jackson.annotation.JsonGetter;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;

@JsonPropertyOrder({"cost", "ex", "pc", "sub"})
@JsonPropertyOrder({"cost", "operation", "ex", "pc", "sub"})
public class VmOperation {
private long cost;
private String operation;
// Information concerning the execution of the operation.
private VmOperationExecutionReport vmOperationExecutionReport;
private long pc;
Expand Down Expand Up @@ -62,6 +66,15 @@ public void setSub(final VmTrace sub) {
this.sub = sub;
}

@JsonInclude(NON_NULL)
public String getOperation() {
return operation;
}

public void setOperation(final String operation) {
this.operation = operation;
}

@Override
public int hashCode() {
return Objects.hash(cost, vmOperationExecutionReport, pc, sub);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,10 @@

import java.util.ArrayDeque;
import java.util.Deque;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.stream.IntStream;
import java.util.stream.Stream;

import com.google.common.collect.MapDifference;
import com.google.common.collect.Maps;
import org.apache.tuweni.bytes.Bytes;
import org.apache.tuweni.units.bigints.UInt256;

Expand All @@ -44,6 +40,7 @@ public class VmTraceGenerator {
private final TransactionTrace transactionTrace;
private final VmTrace rootVmTrace = new VmTrace();
private final Deque<VmTrace> parentTraces = new ArrayDeque<>();
int lastDepth = 0;

public VmTraceGenerator(final TransactionTrace transactionTrace) {
this.transactionTrace = transactionTrace;
Expand Down Expand Up @@ -82,18 +79,18 @@ private Trace generateTrace() {
* @param frame the current trace frame
*/
private void addFrame(final TraceFrame frame) {
if (mustIgnore(frame)) {
return;
handleDepthDecreased(frame);
if (!mustIgnore(frame)) {
initStep(frame);
final VmOperation op = buildVmOperation();
final VmOperationExecutionReport report = generateExecutionReport();
generateTracingMemory(report);
generateTracingPush(report);
generateTracingStorage(report);
handleDepthIncreased(op, report);
completeStep(op, report);
lastDepth = frame.getDepth();
}
initStep(frame);
final VmOperation op = buildVmOperation();
final VmOperationExecutionReport report = generateExecutionReport();
generateTracingMemory(report);
generateTracingPush(report);
generateTracingStorage(report);
handleDepthIncreased(op, report);
handleDepthDecreased();
completeStep(op, report);
}

private boolean mustIgnore(final TraceFrame frame) {
Expand All @@ -112,53 +109,60 @@ private boolean mustIgnore(final TraceFrame frame) {
private void completeStep(final VmOperation op, final VmOperationExecutionReport report) {
// add the operation representation to the list of traces
op.setVmOperationExecutionReport(report);
currentTrace.add(op);
if (currentTrace != null) {
currentTrace.add(op);
}
currentIndex++;
}

private void handleDepthIncreased(final VmOperation op, final VmOperationExecutionReport report) {
// check if next frame depth has increased i.e the current operation is a call
if (currentTraceFrame.depthHasIncreased()
|| "STATICCALL".equals(currentOperation)
|| "CALL".equals(currentOperation)) {
findLastFrameInCall(currentTraceFrame, currentIndex)
.ifPresent(
lastFrameInCall -> {
report.setUsed(lastFrameInCall.getGasRemaining().toLong());
lastFrameInCall
.getStack()
.filter(stack -> stack.length > 0)
.map(stack -> stack[stack.length - 1])
.map(last -> Quantity.create(UInt256.fromHexString(last.toHexString())))
.ifPresent(report::singlePush);
switch (currentTraceFrame.getOpcode()) {
case "DELEGATECALL":
case "CREATE":
case "CREATE2":
break;
default:
switch (currentOperation) {
case "STATICCALL":
case "DELEGATECALL":
case "CALLCODE":
case "CALL":
case "CREATE":
case "CREATE2":
findLastFrameInCall(currentTraceFrame, currentIndex)
.ifPresent(
lastFrameInCall -> {
report.setUsed(lastFrameInCall.getGasRemaining().toLong());
lastFrameInCall
.getStack()
.filter(stack -> stack.length > 0)
.map(stack -> stack[stack.length - 1])
.map(last -> Quantity.create(UInt256.fromHexString(last.toHexString())))
.ifPresent(report::singlePush);
if (!currentOperation.startsWith("CREATE")) {
lastFrameInCall
.getMaybeUpdatedMemory()
.map(
mem ->
new Mem(mem.getValue().toHexString(), mem.getOffset().intValue()))
.ifPresent(report::setMem);
}
});
if (currentTraceFrame.depthHasIncreased()) {
op.setCost(currentTraceFrame.getGasRemainingPostExecution().toLong() + op.getCost());
final VmTrace newSubTrace = new VmTrace();
parentTraces.addLast(newSubTrace);
op.setSub(newSubTrace);
} else {
op.setSub(new VmTrace());
}
}
});
if (currentTraceFrame.getMaybeCode().map(Code::getSize).orElse(0) > 0) {
op.setCost(currentTraceFrame.getGasRemainingPostExecution().toLong() + op.getCost());
final VmTrace newSubTrace = new VmTrace();
parentTraces.addLast(newSubTrace);
op.setSub(newSubTrace);
} else {
if (currentTraceFrame.getPrecompiledGasCost().isPresent()) {
op.setCost(op.getCost() + currentTraceFrame.getPrecompiledGasCost().get().toLong());
}
op.setSub(new VmTrace());
}
break;
default:
break;
}
}

private void handleDepthDecreased() {
private void handleDepthDecreased(final TraceFrame frame) {
// check if next frame depth has decreased i.e the current operation closes the parent trace
if (currentTraceFrame.depthHasDecreased()) {
if (currentTraceFrame != null && frame.getDepth() < lastDepth) {
currentTrace = parentTraces.removeLast();
}
}
Expand All @@ -168,6 +172,7 @@ private VmOperation buildVmOperation() {
// set gas cost and program counter
op.setCost(currentTraceFrame.getGasCost().orElse(Gas.ZERO).toLong());
op.setPc(currentTraceFrame.getPc());
// op.setOperation(currentOperation);
return op;
}

Expand Down Expand Up @@ -220,12 +225,14 @@ private void generateTracingPush(final VmOperationExecutionReport report) {

private void generateTracingStorage(final VmOperationExecutionReport report) {
// set storage if updated
updatedStorage(currentTraceFrame.getStoragePreExecution(), currentTraceFrame.getStorage())
.map(
storageEntry ->
new Store(
storageEntry.key.toShortHexString(), storageEntry.value.toShortHexString()))
.ifPresent(report::setStore);
currentTraceFrame
.getMaybeUpdatedStorage()
.ifPresent(
entry ->
report.setStore(
new Store(
entry.getOffset().toShortHexString(),
entry.getValue().toShortHexString())));
}

/**
Expand All @@ -236,39 +243,12 @@ private void generateTracingStorage(final VmOperationExecutionReport report) {
private void initStep(final TraceFrame frame) {
this.currentTraceFrame = frame;
this.currentOperation = frame.getOpcode();
currentTrace = parentTraces.getLast();
currentTrace = parentTraces.peekLast();
// set smart contract code
currentTrace.setCode(
currentTraceFrame.getMaybeCode().orElse(new Code()).getBytes().toHexString());
}

/**
* Find updated storage from 2 storage captures.
*
* @param firstCapture The first storage capture.
* @param secondCapture The second storage capture.
* @return an {@link Optional} wrapping the diff.
*/
private Optional<StorageEntry> updatedStorage(
final Optional<Map<UInt256, UInt256>> firstCapture,
final Optional<Map<UInt256, UInt256>> secondCapture) {
final Map<UInt256, UInt256> first = firstCapture.orElse(new HashMap<>());
final Map<UInt256, UInt256> second = secondCapture.orElse(new HashMap<>());
final MapDifference<UInt256, UInt256> diff = Maps.difference(first, second);
final Map<UInt256, MapDifference.ValueDifference<UInt256>> entriesDiffering =
diff.entriesDiffering();
if (entriesDiffering.size() > 0) {
final UInt256 firstDiffKey = entriesDiffering.keySet().iterator().next();
final MapDifference.ValueDifference<UInt256> firstDiff = entriesDiffering.get(firstDiffKey);
return Optional.of(new StorageEntry(firstDiffKey, firstDiff.rightValue()));
if (currentTrace != null && "0x".equals(currentTrace.getCode())) {
currentTrace.setCode(
currentTraceFrame.getMaybeCode().orElse(new Code()).getBytes().toHexString());
}
final Map<UInt256, UInt256> onlyOnRight = diff.entriesOnlyOnRight();
if (onlyOnRight.size() > 0) {
final UInt256 firstOnlyOnRightKey = onlyOnRight.keySet().iterator().next();
return Optional.of(
new StorageEntry(firstOnlyOnRightKey, onlyOnRight.get(firstOnlyOnRightKey)));
}
return Optional.empty();
}

/**
Expand All @@ -290,14 +270,4 @@ private Optional<TraceFrame> findLastFrameInCall(
}
return Optional.empty();
}

static class StorageEntry {
private final UInt256 key;
private final UInt256 value;

StorageEntry(final UInt256 key, final UInt256 value) {
this.key = key;
this.value = value;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse;
import org.hyperledger.besu.ethereum.core.Gas;
import org.hyperledger.besu.ethereum.core.Hash;
import org.hyperledger.besu.ethereum.core.Wei;
import org.hyperledger.besu.ethereum.debug.TraceFrame;
import org.hyperledger.besu.ethereum.mainnet.TransactionProcessor;
import org.hyperledger.besu.ethereum.vm.ExceptionalHaltReason;
Expand Down Expand Up @@ -66,9 +67,11 @@ public void shouldReturnCorrectResponse() {
"NONE",
Gas.of(45),
Optional.of(Gas.of(56)),
Gas.ZERO,
2,
EnumSet.noneOf(ExceptionalHaltReason.class),
null,
Wei.ZERO,
Bytes.EMPTY,
Bytes.EMPTY,
Optional.empty(),
Expand All @@ -80,9 +83,8 @@ public void shouldReturnCorrectResponse() {
Optional.empty(),
0,
Optional.empty(),
Optional.empty(),
Optional.empty(),
false,
Optional.empty(),
Optional.empty());

final TransactionProcessor.Result transaction1Result = mock(TransactionProcessor.Result.class);
Expand Down
Loading