Tracing multiple levels of calls now works properly (#352)

The iterative look back mechanism flat trace was using was missing
many of the nesting subtlties, so a stack based calculation is used.

A new test case with two levels of calls is also introduced.

Signed-off-by: Danno Ferrin <danno.ferrin@gmail.com>
This commit is contained in:
Danno Ferrin
2020-02-01 00:18:05 -07:00
committed by GitHub
parent ee12a7c284
commit fd3f2d30c1
9 changed files with 566 additions and 201 deletions

View File

@@ -14,6 +14,8 @@
*/
package org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.tracing;
import org.hyperledger.besu.ethereum.core.Wei;
import java.util.Arrays;
import java.util.Optional;
import java.util.stream.Collectors;
@@ -54,4 +56,8 @@ public class TracingUtils {
}
return bytes.size();
}
public static String weiAsHex(final Wei balance) {
return balance.isZero() ? "0x0" : balance.toShortHexString();
}
}

View File

@@ -18,10 +18,10 @@ package org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.tracing.diff;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.processor.TransactionTrace;
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.AbstractWorldUpdater.UpdateTrackingAccount;
import org.hyperledger.besu.ethereum.core.Account;
import org.hyperledger.besu.ethereum.core.Address;
import org.hyperledger.besu.ethereum.core.Wei;
import org.hyperledger.besu.ethereum.core.WorldUpdater;
import org.hyperledger.besu.ethereum.debug.TraceFrame;
@@ -114,8 +114,7 @@ public class StateDiffGenerator {
}
private static String balanceAsHex(final Account account) {
final Wei balance = account.getBalance();
return balance.isZero() ? "0x0" : balance.toShortHexString();
return TracingUtils.weiAsHex(account.getBalance());
}
private static String codeAsHex(final Account account) {

View File

@@ -18,11 +18,11 @@ import static com.fasterxml.jackson.annotation.JsonInclude.Include.NON_NULL;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.processor.TransactionTrace;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.Quantity;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.tracing.TracingUtils;
import org.hyperledger.besu.ethereum.core.Address;
import org.hyperledger.besu.ethereum.core.Gas;
import org.hyperledger.besu.ethereum.core.Transaction;
import org.hyperledger.besu.ethereum.core.Wei;
import org.hyperledger.besu.ethereum.debug.TraceFrame;
import java.util.Optional;
@@ -74,7 +74,6 @@ public class Action {
final Transaction transaction,
final String lastContractAddress,
final Address contractCallAddress,
final TraceFrame traceFrame,
final Gas gasRemaining,
final Optional<Bytes> inputBytes,
final String callType) {
@@ -92,7 +91,7 @@ public class Action {
return builder()
.address(lastContractAddress)
.refundAddress(contractCallAddress.toString())
.balance(balance.toShortHexString());
.balance(TracingUtils.weiAsHex(balance));
}
public String getCallType() {
@@ -176,11 +175,19 @@ public class Action {
return this;
}
public String getCallType() {
return callType;
}
public Builder from(final String from) {
this.from = from;
return this;
}
public String getFrom() {
return from;
}
public Builder gas(final String gas) {
this.gas = gas;
return this;
@@ -196,6 +203,10 @@ public class Action {
return this;
}
public String getTo() {
return to;
}
public Builder init(final String init) {
this.init = init;
return this;
@@ -216,7 +227,7 @@ public class Action {
return this;
}
public Builder refundAddress(final String refundAddress) {
Builder refundAddress(final String refundAddress) {
this.refundAddress = refundAddress;
return this;
}

View File

@@ -115,37 +115,31 @@ public class FlatTrace implements Trace {
public static class Context {
private final Builder builder;
private boolean returned;
private boolean isSubtrace = false;
private long gasUsed = 0;
public Context(final Builder builder) {
this(builder, false);
}
Context(final Builder builder, final boolean returned) {
Context(final Builder builder) {
this.builder = builder;
this.returned = returned;
}
public Builder getBuilder() {
return builder;
}
public boolean isReturned() {
return returned;
void incGasUsed(final long gas) {
setGasUsed(gasUsed + gas);
}
boolean isSubtrace() {
return isSubtrace;
void decGasUsed(final long gas) {
setGasUsed(gasUsed - gas);
}
Context subTrace() {
this.isSubtrace = true;
return this;
public long getGasUsed() {
return gasUsed;
}
void markAsReturned() {
this.returned = true;
public void setGasUsed(final long gasUsed) {
this.gasUsed = gasUsed;
builder.getResultBuilder().gasUsed("0x" + Long.toHexString(gasUsed));
}
}
@@ -170,9 +164,8 @@ public class FlatTrace implements Trace {
return this;
}
public Builder subtraces(final int subtraces) {
this.subtraces = subtraces;
return this;
public int getSubtraces() {
return subtraces;
}
public Builder traceAddress(final List<Integer> traceAddress) {
@@ -190,13 +183,8 @@ public class FlatTrace implements Trace {
return this;
}
Builder incSubTraces() {
return incSubTraces(1);
}
Builder incSubTraces(final int n) {
this.subtraces += n;
return this;
void incSubTraces() {
this.subtraces++;
}
public FlatTrace build() {

View File

@@ -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.flat.FlatTrace.Context;
import org.hyperledger.besu.ethereum.core.Address;
import org.hyperledger.besu.ethereum.core.Gas;
import org.hyperledger.besu.ethereum.core.Transaction;
import org.hyperledger.besu.ethereum.core.Wei;
import org.hyperledger.besu.ethereum.debug.TraceFrame;
import org.hyperledger.besu.ethereum.vm.ExceptionalHaltReason;
@@ -31,11 +32,11 @@ import java.util.List;
import java.util.Locale;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import com.google.common.collect.Lists;
import com.google.common.collect.Streams;
import com.google.common.util.concurrent.Atomics;
import org.apache.tuweni.bytes.Bytes;
import org.apache.tuweni.bytes.Bytes32;
@@ -52,90 +53,77 @@ public class FlatTraceGenerator {
public static Stream<Trace> generateFromTransactionTrace(
final TransactionTrace transactionTrace, final AtomicInteger traceCounter) {
final FlatTrace.Builder firstFlatTraceBuilder = FlatTrace.freshBuilder(transactionTrace);
final String lastContractAddress =
transactionTrace.getTransaction().getTo().orElse(Address.ZERO).toHexString();
final Transaction tx = transactionTrace.getTransaction();
final String lastContractAddress = tx.getTo().orElse(Address.ZERO).toHexString();
final Optional<String> smartContractCode =
transactionTrace.getTransaction().getInit().isPresent()
? Optional.of(transactionTrace.getResult().getOutput().toString())
: Optional.empty();
tx.getInit().map(__ -> transactionTrace.getResult().getOutput().toString());
final Optional<String> smartContractAddress =
smartContractCode.isPresent()
? Optional.of(
Address.contractAddress(
transactionTrace.getTransaction().getSender(),
transactionTrace.getTransaction().getNonce())
.toHexString())
: Optional.empty();
smartContractCode.map(
__ -> Address.contractAddress(tx.getSender(), tx.getNonce()).toHexString());
// set code field in result node
smartContractCode.ifPresent(firstFlatTraceBuilder.getResultBuilder()::code);
// set init field if transaction is a smart contract deployment
transactionTrace
.getTransaction()
.getInit()
.map(Bytes::toHexString)
.ifPresent(firstFlatTraceBuilder.getActionBuilder()::init);
tx.getInit().map(Bytes::toHexString).ifPresent(firstFlatTraceBuilder.getActionBuilder()::init);
// set to, input and callType fields if not a smart contract
transactionTrace
.getTransaction()
.getTo()
.ifPresent(
to ->
firstFlatTraceBuilder
.getActionBuilder()
.to(to.toString())
.callType("call")
.input(
transactionTrace
.getTransaction()
.getData()
.map(Bytes::toHexString)
.orElse(
transactionTrace
.getTransaction()
.getInit()
.map(Bytes::toHexString)
.orElse(Bytes.EMPTY.toHexString()))));
// declare a queue of transactionTrace contexts
if (tx.getTo().isPresent()) {
final Bytes payload = tx.getPayload();
firstFlatTraceBuilder
.getActionBuilder()
.to(tx.getTo().map(Bytes::toHexString).orElse(null))
.callType("call")
.input(payload == null ? "0x" : payload.toHexString());
} else {
firstFlatTraceBuilder
.type("create")
.getResultBuilder()
.address(smartContractAddress.orElse(null));
}
final List<FlatTrace.Builder> flatTraces = new ArrayList<>();
// stack of previous contexts
final Deque<FlatTrace.Context> tracesContexts = new ArrayDeque<>();
// add the first transactionTrace context to the queue of transactionTrace contexts
final Context traceContext = new Context(firstFlatTraceBuilder);
tracesContexts.addLast(traceContext);
FlatTrace.Context currentContext = new FlatTrace.Context(firstFlatTraceBuilder);
tracesContexts.addLast(currentContext);
flatTraces.add(currentContext.getBuilder());
// declare the first transactionTrace context as the previous transactionTrace context
final List<Integer> addressVector = new ArrayList<>();
final AtomicLong cumulativeGasCost = new AtomicLong(0);
long cumulativeGasCost = 0;
int traceFrameIndex = 0;
final List<TraceFrame> traceFrames = transactionTrace.getTraceFrames();
final int lastTraceFrameIndex = traceFrames.size() - 1;
for (final TraceFrame traceFrame : traceFrames) {
cumulativeGasCost.addAndGet(traceFrame.getGasCost().orElse(Gas.ZERO).toLong());
cumulativeGasCost += traceFrame.getGasCost().orElse(Gas.ZERO).toLong();
final String opcodeString = traceFrame.getOpcode();
if ("CALL".equals(opcodeString)
|| "CALLCODE".equals(opcodeString)
|| "DELEGATECALL".equals(opcodeString)
|| "STATICCALL".equals(opcodeString)) {
final Optional<TraceFrame> nextTraceFrame =
(traceFrameIndex < lastTraceFrameIndex)
? Optional.of(traceFrames.get(traceFrameIndex + 1))
: Optional.empty();
handleCall(
transactionTrace,
traceFrame,
nextTraceFrame,
lastContractAddress,
cumulativeGasCost,
addressVector,
tracesContexts,
traceFrameIndex,
opcodeString.toLowerCase(Locale.US));
currentContext =
handleCall(
transactionTrace,
traceFrame,
lastContractAddress,
smartContractAddress,
flatTraces,
cumulativeGasCost,
tracesContexts,
traceFrameIndex,
traceFrames,
opcodeString.toLowerCase(Locale.US));
} else if ("RETURN".equals(opcodeString) || "STOP".equals(opcodeString)) {
handleReturn(transactionTrace, traceFrame, smartContractAddress, tracesContexts);
currentContext.incGasUsed(cumulativeGasCost);
currentContext = handleReturn(transactionTrace, traceFrame, tracesContexts, currentContext);
} else if ("SELFDESTRUCT".equals(opcodeString)) {
handleSelfDestruct(
traceFrame, lastContractAddress, cumulativeGasCost, addressVector, tracesContexts);
currentContext.incGasUsed(cumulativeGasCost);
currentContext = handleSelfDestruct(traceFrame, flatTraces, tracesContexts);
} else if (!traceFrame.getExceptionalHaltReasons().isEmpty()) {
traceContext
currentContext
.getBuilder()
.error(
traceFrame.getExceptionalHaltReasons().stream()
@@ -144,150 +132,110 @@ public class FlatTraceGenerator {
}
traceFrameIndex++;
}
final List<Trace> flatTraces = new ArrayList<>();
tracesContexts.forEach(context -> flatTraces.add(context.getBuilder().build()));
traceCounter.incrementAndGet();
return flatTraces.stream();
return flatTraces.stream().map(FlatTrace.Builder::build);
}
private static void handleCall(
private static FlatTrace.Context handleCall(
final TransactionTrace transactionTrace,
final TraceFrame traceFrame,
final Optional<TraceFrame> nextTraceFrame,
final String lastContractAddress,
final AtomicLong cumulativeGasCost,
final List<Integer> addressVector,
final Optional<String> smartContractAddress,
final List<FlatTrace.Builder> flatTraces,
final long cumulativeGasCost,
final Deque<FlatTrace.Context> tracesContexts,
final int traceFrameIndex,
final String callType) {
final List<TraceFrame> traceFrames,
final String opcodeString) {
final FlatTrace.Context currentContext;
final TraceFrame nextTraceFrame = traceFrames.get(traceFrameIndex + 1);
final Bytes32[] stack = traceFrame.getStack().orElseThrow();
final Address contractCallAddress = toAddress(stack[stack.length - 2]);
if (addressVector.size() <= traceFrame.getDepth()) {
addressVector.add(0);
} else {
addressVector.set(traceFrame.getDepth(), addressVector.get(traceFrame.getDepth()) + 1);
}
final List<Integer> traceAddress = Lists.newCopyOnWriteArrayList(addressVector);
final FlatTrace.Builder subTraceBuilder =
FlatTrace.builder().traceAddress(traceAddress).resultBuilder(Result.builder());
// get the next trace frame to set the gas field (gas remaining) of the sub trace
final TraceFrame traceFrameAfterCall =
transactionTrace.getTraceFrames().get(traceFrameIndex + 1);
FlatTrace.builder()
.traceAddress(calculateTraceAddress(tracesContexts))
.resultBuilder(Result.builder());
final Action.Builder subTraceActionBuilder =
Action.createCallAction(
transactionTrace.getTransaction(),
lastContractAddress,
smartContractAddress.orElse(lastContractAddress),
contractCallAddress,
traceFrame,
traceFrameAfterCall.getGasRemaining(),
nextTraceFrame.map(TraceFrame::getInputData),
callType);
nextTraceFrame.getGasRemaining(),
Optional.ofNullable(nextTraceFrame.getInputData()),
opcodeString.toLowerCase(Locale.US));
final long gasCost = cumulativeGasCost.longValue();
// retrieve the previous transactionTrace context
Optional.ofNullable(tracesContexts.peekLast())
.ifPresent(
previousContext -> {
// increment sub traces counter of previous transactionTrace
previousContext.getBuilder().incSubTraces();
// set gas cost of previous transactionTrace
previousContext
.getBuilder()
.getResultBuilder()
.gasUsed(Gas.of(gasCost).toHexString());
});
tracesContexts.addLast(
new FlatTrace.Context(subTraceBuilder.actionBuilder(subTraceActionBuilder)).subTrace());
cumulativeGasCost.set(0);
currentContext = new FlatTrace.Context(subTraceBuilder.actionBuilder(subTraceActionBuilder));
currentContext.decGasUsed(cumulativeGasCost);
tracesContexts.addLast(currentContext);
flatTraces.add(currentContext.getBuilder());
return currentContext;
}
private static void handleReturn(
private static FlatTrace.Context handleReturn(
final TransactionTrace transactionTrace,
final TraceFrame traceFrame,
final Optional<String> smartContractAddress,
final Deque<FlatTrace.Context> tracesContexts) {
final Deque<FlatTrace.Context> polledContexts = new ArrayDeque<>();
FlatTrace.Context ctx;
// find last non returned transactionTrace
while ((ctx = tracesContexts.pollLast()) != null) {
polledContexts.addFirst(ctx);
// continue until finding a non returned context
if (ctx.isReturned()) {
continue;
}
final FlatTrace.Builder flatTraceBuilder = ctx.getBuilder();
final Gas gasRemainingAtStartOfTrace =
Gas.fromHexString(flatTraceBuilder.getActionBuilder().getGas());
final Gas gasUsed = gasRemainingAtStartOfTrace.minus(traceFrame.getGasRemaining());
final Result.Builder resultBuilder = flatTraceBuilder.getResultBuilder();
final Gas finalGasUsed;
if (ctx.isSubtrace()) {
finalGasUsed = gasUsed;
} else {
finalGasUsed = computeGasUsed(transactionTrace, gasUsed);
}
// set gas used for the trace
resultBuilder.gasUsed(finalGasUsed.toHexString());
// set address and type to create if smart contract deployment
smartContractAddress.ifPresentOrElse(
address -> {
resultBuilder.address(address);
flatTraceBuilder.type("create");
},
// set output otherwise
() -> resultBuilder.output(traceFrame.getOutputData().toHexString()));
ctx.markAsReturned();
break;
final Deque<FlatTrace.Context> tracesContexts,
final FlatTrace.Context currentContext) {
if (tracesContexts.size() == 1) {
currentContext.setGasUsed(computeGasUsed(transactionTrace, currentContext.getGasUsed()));
}
// reinsert polled contexts add the end of the queue
polledContexts.forEach(tracesContexts::addLast);
if (currentContext.getBuilder().getResultBuilder().getCode() == null) {
currentContext
.getBuilder()
.getResultBuilder()
.output(traceFrame.getOutputData().toHexString());
}
tracesContexts.removeLast();
final FlatTrace.Context nextContext = tracesContexts.peekLast();
if (nextContext != null) {
nextContext.getBuilder().incSubTraces();
}
return nextContext;
}
private static void handleSelfDestruct(
private static FlatTrace.Context handleSelfDestruct(
final TraceFrame traceFrame,
final String lastContractAddress,
final AtomicLong cumulativeGasCost,
final List<Integer> addressVector,
final Deque<FlatTrace.Context> tracesContexts) {
final List<FlatTrace.Builder> flatTraces,
final Deque<Context> tracesContexts) {
final Bytes32[] stack = traceFrame.getStack().orElseThrow();
final Address refundAddress = toAddress(stack[0]);
if (addressVector.size() <= traceFrame.getDepth()) {
addressVector.add(0);
}
final FlatTrace.Builder subTraceBuilder =
FlatTrace.builder().type("suicide").traceAddress(addressVector);
FlatTrace.builder()
.type("suicide")
.traceAddress(calculateSelfDescructAddress(tracesContexts));
final AtomicReference<Wei> weiBalance = Atomics.newReference(Wei.ZERO);
traceFrame
.getMaybeRefunds()
.ifPresent(refunds -> weiBalance.set(refunds.getOrDefault(refundAddress, Wei.ZERO)));
final Action.Builder callingAction = tracesContexts.peekLast().getBuilder().getActionBuilder();
final String actionAddress =
callingAction.getCallType().equals("call")
? callingAction.getTo()
: callingAction.getFrom();
final Action.Builder subTraceActionBuilder =
Action.createSelfDestructAction(lastContractAddress, refundAddress, weiBalance.get());
Action.createSelfDestructAction(actionAddress, refundAddress, weiBalance.get());
final long gasCost = cumulativeGasCost.longValue();
// retrieve the previous transactionTrace context
if (!tracesContexts.isEmpty()) {
final FlatTrace.Context previousContext = tracesContexts.peekLast();
// increment sub traces counter of previous transactionTrace
previousContext.getBuilder().incSubTraces();
// set gas cost of previous transactionTrace
previousContext.getBuilder().getResultBuilder().gasUsed(Gas.of(gasCost).toHexString());
flatTraces.add(
new FlatTrace.Context(subTraceBuilder.actionBuilder(subTraceActionBuilder)).getBuilder());
final FlatTrace.Context lastContext = tracesContexts.removeLast();
lastContext.getBuilder().incSubTraces();
final FlatTrace.Context currentContext = tracesContexts.peekLast();
if (currentContext != null) {
currentContext.getBuilder().incSubTraces();
}
tracesContexts.addLast(
new FlatTrace.Context(subTraceBuilder.actionBuilder(subTraceActionBuilder)));
cumulativeGasCost.set(0);
return currentContext;
}
private static Gas computeGasUsed(
final TransactionTrace transactionTrace, final Gas fallbackValue) {
private static long computeGasUsed(
final TransactionTrace transactionTrace, final long fallbackValue) {
final long firstFrameGasRemaining =
transactionTrace.getTraceFrames().get(0).getGasRemaining().toLong();
final long gasRemainingAfterTransactionWasProcessed =
transactionTrace.getResult().getGasRemaining();
if (firstFrameGasRemaining > gasRemainingAfterTransactionWasProcessed) {
return Gas.of(firstFrameGasRemaining - gasRemainingAfterTransactionWasProcessed);
return firstFrameGasRemaining - gasRemainingAfterTransactionWasProcessed;
} else {
return fallbackValue;
}
@@ -297,4 +245,18 @@ public class FlatTraceGenerator {
return Address.wrap(
Bytes.of(Arrays.copyOfRange(value.toArray(), Bytes32.SIZE - Address.SIZE, Bytes32.SIZE)));
}
private static List<Integer> calculateTraceAddress(final Deque<FlatTrace.Context> contexts) {
return contexts.stream()
.map(context -> context.getBuilder().getSubtraces())
.collect(Collectors.toList());
}
private static List<Integer> calculateSelfDescructAddress(
final Deque<FlatTrace.Context> contexts) {
return Streams.concat(
contexts.stream()
.map(context -> context.getBuilder().getSubtraces())) // , Stream.of(0))
.collect(Collectors.toList());
}
}

View File

@@ -80,6 +80,10 @@ public class Result {
return this;
}
public String getCode() {
return code;
}
public Builder address(final String address) {
this.address = address;
return this;

View File

@@ -14,6 +14,7 @@
*/
package org.hyperledger.besu.ethereum.api.jsonrpc;
import static com.fasterxml.jackson.databind.SerializationFeature.INDENT_OUTPUT;
import static com.google.common.base.Preconditions.checkState;
import static org.assertj.core.api.Assertions.assertThat;
@@ -159,7 +160,15 @@ public abstract class AbstractJsonRpcHttpBySpecTest extends AbstractJsonRpcHttpS
final String expectedResult = expectedResponse.get("result").toString();
final String actualResult = responseBody.get("result").toString();
final ObjectMapper mapper = new ObjectMapper();
assertThat(mapper.readTree(actualResult)).isEqualTo(mapper.readTree(expectedResult));
mapper.configure(INDENT_OUTPUT, true);
assertThat(
mapper
.writerWithDefaultPrettyPrinter()
.writeValueAsString(mapper.readTree(actualResult)))
.isEqualTo(
mapper
.writerWithDefaultPrettyPrinter()
.writeValueAsString(mapper.readTree(expectedResult)));
}
// Check error

View File

@@ -256,6 +256,72 @@
"to": "0x00F0000000000000000000000000000000000000"
}
]
},
{
"number": "0x12",
"transactions": [
{
"comment": "Memory Read.",
"secretKey": "0x8f2a55949038a9610f50fb23b5883af3b4ecb3c3bb792cbcefbd1542c692be63",
"gasLimit": "0xFFFFF2",
"gasPrice": "0xEF",
"to": "0x0100000000000000000000000000000000000000"
},
{
"comment": "Revert.",
"secretKey": "0x8f2a55949038a9610f50fb23b5883af3b4ecb3c3bb792cbcefbd1542c692be63",
"gasLimit": "0xFFFFF2",
"gasPrice": "0xEF",
"to": "0x0110000000000000000000000000000000000000"
}
]
},
{
"number": "0x13",
"transactions": [
{
"comment": "Deploy contract that will self-destruct when called.",
"secretKey": "0xc87509a1c067bbde78beb793e6fa76530b6382a4c0241e5e4a9ec0a0f44dc0d3",
"gasLimit": "0xFFFFF2",
"gasPrice": "0xEF",
"data": "0x6004600C60003960046000F3600035FF"
},
{
"comment": "call the prior contract then call log contract 00a0..00.",
"secretKey": "0xc87509a1c067bbde78beb793e6fa76530b6382a4c0241e5e4a9ec0a0f44dc0d3",
"gasLimit": "0xFFFFF2",
"gasPrice": "0xEF",
"data": "0x60006000600060006000738f0483125fcb9aaaefa9209d8e9d7b9c8b9fb90f5AF1600060006000600060007300A00000000000000000000000000000000000005AF1"
},
{
"comment": "Deploy contract that will self-destruct when called.",
"secretKey": "0xc87509a1c067bbde78beb793e6fa76530b6382a4c0241e5e4a9ec0a0f44dc0d3",
"gasLimit": "0xFFFFF2",
"gasPrice": "0xEF",
"data": "0x6004600C60003960046000F3600035FF"
},
{
"comment": "call the prior contract then callcode log contract 00a0..00.",
"secretKey": "0xc87509a1c067bbde78beb793e6fa76530b6382a4c0241e5e4a9ec0a0f44dc0d3",
"gasLimit": "0xFFFFF2",
"gasPrice": "0xEF",
"data": "0x60006000600060006000732c2b9c9a4a25e24b174f26114e8926a9f2128fe45AF2600060006000600060007300A00000000000000000000000000000000000005AF2"
},
{
"comment": "Deploy contract that will self-destruct when called.",
"secretKey": "0xc87509a1c067bbde78beb793e6fa76530b6382a4c0241e5e4a9ec0a0f44dc0d3",
"gasLimit": "0xFFFFF2",
"gasPrice": "0xEF",
"data": "0x6004600C60003960046000F3600035FF"
},
{
"comment": "call the prior contract then delegatecall log contract 00a0..00.",
"secretKey": "0xc87509a1c067bbde78beb793e6fa76530b6382a4c0241e5e4a9ec0a0f44dc0d3",
"gasLimit": "0xFFFFF2",
"gasPrice": "0xEF",
"data": "0x600060006000600073fb88de099e13c3ed21f80a7a1e49f8caecf10df65AF460006000600060007300A00000000000000000000000000000000000005AF4"
}
]
}
]
}

View File

@@ -0,0 +1,320 @@
{
"request": {
"jsonrpc": "2.0",
"method": "trace_replayBlockTransactions",
"params": [
"0x13",
[
"trace"
]
],
"id": 415
},
"response": {
"jsonrpc": "2.0",
"result": [
{
"output": "0x600035ff",
"stateDiff": null,
"trace": [
{
"action": {
"from": "0x627306090abab3a6e1400e9345bc60c78a8bef57",
"gas": "0xff300e",
"init": "0x6004600c60003960046000f3600035ff",
"value": "0x0"
},
"result": {
"address": "0x8f0483125fcb9aaaefa9209d8e9d7b9c8b9fb90f",
"code": "0x600035ff",
"gasUsed": "0x338"
},
"subtraces": 0,
"traceAddress": [],
"type": "create"
}
],
"transactionHash": "0x1309b6d2187aa8b0dfe78fcf0a96d4a3e861bfbc381959d253ede57624a37f9b",
"vmTrace": null
},
{
"output": "0x",
"stateDiff": null,
"trace": [
{
"action": {
"from": "0x627306090abab3a6e1400e9345bc60c78a8bef57",
"gas": "0xff2e26",
"init": "0x60006000600060006000738f0483125fcb9aaaefa9209d8e9d7b9c8b9fb90f5af1600060006000600060007300a00000000000000000000000000000000000005af1",
"value": "0x0"
},
"result": {
"address": "0x9fbda871d559710256a2502a2517b794b482db40",
"code": "0x",
"gasUsed": "0x1c39"
},
"subtraces": 2,
"traceAddress": [],
"type": "create"
},
{
"action": {
"callType": "call",
"from": "0x9fbda871d559710256a2502a2517b794b482db40",
"gas": "0xfb2ea9",
"input": "0x",
"to": "0x8f0483125fcb9aaaefa9209d8e9d7b9c8b9fb90f",
"value": "0x0"
},
"result": {
"gasUsed": "0x138e",
"output": "0x"
},
"subtraces": 1,
"traceAddress": [
0
],
"type": "call"
},
{
"action": {
"address": "0x8f0483125fcb9aaaefa9209d8e9d7b9c8b9fb90f",
"balance": "0x0",
"refundAddress": "0x0000000000000000000000000000000000000000"
},
"result": null,
"subtraces": 0,
"traceAddress": [
0,
0
],
"type": "suicide"
},
{
"action": {
"callType": "call",
"from": "0x9fbda871d559710256a2502a2517b794b482db40",
"gas": "0xfb18a5",
"input": "0x",
"to": "0x00a0000000000000000000000000000000000000",
"value": "0x0"
},
"result": {
"gasUsed": "0x30b",
"output": "0x"
},
"subtraces": 0,
"traceAddress": [
1
],
"type": "call"
}
],
"transactionHash": "0x6b9b967cfbeedeb7f0f4956b8103075ddfcea26c01d6d5dc3f9e2ed2ec9c42c0",
"vmTrace": null
},
{
"output": "0x600035ff",
"stateDiff": null,
"trace": [
{
"action": {
"from": "0x627306090abab3a6e1400e9345bc60c78a8bef57",
"gas": "0xff300e",
"init": "0x6004600c60003960046000f3600035ff",
"value": "0x0"
},
"result": {
"address": "0x2c2b9c9a4a25e24b174f26114e8926a9f2128fe4",
"code": "0x600035ff",
"gasUsed": "0x338"
},
"subtraces": 0,
"traceAddress": [],
"type": "create"
}
],
"transactionHash": "0xf12fd37f0836bd51f21bc15aa6bf5bea4b62fbbd39e1ee06725f91ac13ebc904",
"vmTrace": null
},
{
"output": "0x",
"stateDiff": null,
"trace": [
{
"action": {
"from": "0x627306090abab3a6e1400e9345bc60c78a8bef57",
"gas": "0xff2e26",
"init": "0x60006000600060006000732c2b9c9a4a25e24b174f26114e8926a9f2128fe45af2600060006000600060007300a00000000000000000000000000000000000005af2",
"value": "0x0"
},
"result": {
"address": "0x30753e4a8aad7f8597332e813735def5dd395028",
"code": "0x",
"gasUsed": "0x1c39"
},
"subtraces": 2,
"traceAddress": [],
"type": "create"
},
{
"action": {
"callType": "callcode",
"from": "0x30753e4a8aad7f8597332e813735def5dd395028",
"gas": "0xfb2ea9",
"input": "0x",
"to": "0x2c2b9c9a4a25e24b174f26114e8926a9f2128fe4",
"value": "0x0"
},
"result": {
"gasUsed": "0x138e",
"output": "0x"
},
"subtraces": 1,
"traceAddress": [
0
],
"type": "call"
},
{
"action": {
"address": "0x30753e4a8aad7f8597332e813735def5dd395028",
"balance": "0x0",
"refundAddress": "0x0000000000000000000000000000000000000000"
},
"result": null,
"subtraces": 0,
"traceAddress": [
0,
0
],
"type": "suicide"
},
{
"action": {
"callType": "callcode",
"from": "0x30753e4a8aad7f8597332e813735def5dd395028",
"gas": "0xfb18a5",
"input": "0x",
"to": "0x00a0000000000000000000000000000000000000",
"value": "0x0"
},
"result": {
"gasUsed": "0x30b",
"output": "0x"
},
"subtraces": 0,
"traceAddress": [
1
],
"type": "call"
}
],
"transactionHash": "0x4c253746668dca6ac3f7b9bc18248b558a95b5fc881d140872c2dff984d344a7",
"vmTrace": null
},
{
"output": "0x600035ff",
"stateDiff": null,
"trace": [
{
"action": {
"from": "0x627306090abab3a6e1400e9345bc60c78a8bef57",
"gas": "0xff300e",
"init": "0x6004600c60003960046000f3600035ff",
"value": "0x0"
},
"result": {
"address": "0xfb88de099e13c3ed21f80a7a1e49f8caecf10df6",
"code": "0x600035ff",
"gasUsed": "0x338"
},
"subtraces": 0,
"traceAddress": [],
"type": "create"
}
],
"transactionHash": "0x821ca63d171c5a3c60d32a738803092a52562056db3727a175f659cf49aae283",
"vmTrace": null
},
{
"output": "0x",
"stateDiff": null,
"trace": [
{
"action": {
"from": "0x627306090abab3a6e1400e9345bc60c78a8bef57",
"gas": "0xff2e4e",
"init": "0x600060006000600073fb88de099e13c3ed21f80a7a1e49f8caecf10df65af460006000600060007300a00000000000000000000000000000000000005af4",
"value": "0x0"
},
"result": {
"address": "0xaa588d3737b611bafd7bd713445b314bd453a5c8",
"code": "0x",
"gasUsed": "0x1c33"
},
"subtraces": 2,
"traceAddress": [],
"type": "create"
},
{
"action": {
"callType": "delegatecall",
"from": "0xaa588d3737b611bafd7bd713445b314bd453a5c8",
"gas": "0xfb2ed3",
"input": "0x",
"to": "0xfb88de099e13c3ed21f80a7a1e49f8caecf10df6",
"value": "0x0"
},
"result": {
"gasUsed": "0x138e",
"output": "0x"
},
"subtraces": 1,
"traceAddress": [
0
],
"type": "call"
},
{
"action": {
"address": "0xaa588d3737b611bafd7bd713445b314bd453a5c8",
"balance": "0x0",
"refundAddress": "0x0000000000000000000000000000000000000000"
},
"result": null,
"subtraces": 0,
"traceAddress": [
0,
0
],
"type": "suicide"
},
{
"action": {
"callType": "delegatecall",
"from": "0xaa588d3737b611bafd7bd713445b314bd453a5c8",
"gas": "0xfb18d2",
"input": "0x",
"to": "0x00a0000000000000000000000000000000000000",
"value": "0x0"
},
"result": {
"gasUsed": "0x30b",
"output": "0x"
},
"subtraces": 0,
"traceAddress": [
1
],
"type": "call"
}
],
"transactionHash": "0xb795475e8f1820b683b60eb1a366bdc23e29f9cdf9639f7768c6644d20e3bbd1",
"vmTrace": null
}
],
"id": 415
},
"statusCode": 200
}