Skip to content

Commit

Permalink
Addresses PR comments
Browse files Browse the repository at this point in the history
Signed-off-by: Konstantina Blazhukova <[email protected]>
  • Loading branch information
konstantinabl committed Jun 12, 2024
1 parent 989f0ff commit 943724c
Show file tree
Hide file tree
Showing 7 changed files with 91 additions and 77 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import com.hedera.mirror.web3.evm.contracts.execution.traceability.OpcodeTracerOptions;
import com.hedera.mirror.web3.evm.store.CachingStateFrame;
import com.hedera.mirror.web3.evm.store.StackedStateFrames;
import java.util.ArrayList;
import java.util.EmptyStackException;
import java.util.List;
import java.util.Optional;
Expand Down Expand Up @@ -53,7 +54,7 @@ public class ContractCallContext {
private OpcodeTracerOptions opcodeTracerOptions;

@Setter
private List<Opcode> opcodes = List.of();
private List<Opcode> opcodes = new ArrayList<Opcode>();

@Setter
private List<ContractAction> contractActions = List.of();
Expand Down Expand Up @@ -91,6 +92,10 @@ public void updateStackFromUpstream() {
setStack(stack.getUpstream().orElseThrow(EmptyStackException::new));
}

public void addOpcodes(Opcode opcode) {
opcodes.add(opcode);
}

/**
* Chop the stack back to its base. This keeps the most-upstream-layer which connects to the database, and the
* `ROCachingStateFrame` on top of it. Therefore, everything already read from the database is still present,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,10 @@

import java.util.List;
import java.util.Map;
import lombok.Builder;
import org.apache.tuweni.bytes.Bytes;

@Builder
public record Opcode(int pc,
String op,
long gas,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@
import com.hedera.mirror.common.domain.contract.ContractAction;
import com.hedera.mirror.web3.common.ContractCallContext;
import com.hedera.node.app.service.evm.contracts.execution.traceability.HederaEvmOperationTracer;
import com.hedera.services.store.contracts.precompile.SyntheticTxnFactory;
import jakarta.inject.Named;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
Expand All @@ -37,99 +37,84 @@
import org.hyperledger.besu.evm.frame.MessageFrame;
import org.hyperledger.besu.evm.operation.Operation;
import org.springframework.util.CollectionUtils;
import javax.swing.plaf.synth.SynthTextAreaUI;

@Named
@CustomLog
@Getter
public class OpcodeTracer implements HederaEvmOperationTracer {

private OpcodeTracerOptions options;
private List<Opcode> opcodes;
private List<ContractAction> contractActions;
private ContractCallContext context;

@Override
public void init(MessageFrame initialFrame) {
opcodes = new ArrayList<>();
ContractCallContext ctx = initialFrame.getContextVariable(ContractCallContext.CONTEXT_NAME);
this.context = ctx;
options = ctx.getOpcodeTracerOptions();
contractActions = ctx.getContractActions();
if (CollectionUtils.isEmpty(contractActions)) {
log.warn("No contract actions found in context!");
}
}

@Override
public void tracePostExecution(final MessageFrame frame, final Operation.OperationResult operationResult) {
final List<Bytes> memory = captureMemory(frame);
final List<Bytes> stack = captureStack(frame);
final Map<Bytes, Bytes> storage = captureStorage(frame);
opcodes.add(new Opcode(
frame.getPC(),
frame.getCurrentOperation().getName(),
frame.getRemainingGas(),
operationResult.getGasCost(),
frame.getDepth(),
stack,
memory,
storage,
frame.getRevertReason().map(Bytes::toString).orElse(null)));
ContractCallContext context = getContext();
Opcode opcode = Opcode.builder()
.pc(frame.getPC())
.op(frame.getCurrentOperation().getName())
.gas(frame.getRemainingGas())
.gasCost(operationResult.getGasCost())
.depth(frame.getDepth())
.stack(stack)
.memory(memory)
.storage(storage)
.reason(frame.getRevertReason().map(Bytes::toString).orElse(null))
.build();

context.addOpcodes(opcode);
}

@Override
public void tracePrecompileCall(final MessageFrame frame, final long gasRequirement, final Bytes output) {
final Optional<Bytes> revertReason =
frame.getRevertReason().isPresent() ? frame.getRevertReason() : getRevertReason(contractActions);

revertReason.ifPresent(bytes -> log.trace("Revert reason: {}", bytes.toHexString()));

opcodes.add(new Opcode(
frame.getPC(),
frame.getCurrentOperation() != null
ContractCallContext context = getContext();
Optional<Bytes> revertReason = isCallToHederaTokenService(frame) ? getRevertReasonFromContractActions(frame, context.getContractActions()) : frame.getRevertReason();
Opcode opcode = Opcode.builder()
.pc(frame.getPC())
.op(frame.getCurrentOperation() != null
? frame.getCurrentOperation().getName()
: StringUtils.EMPTY,
frame.getRemainingGas(),
output != null ? gasRequirement : 0L,
frame.getDepth(),
Collections.emptyList(),
Collections.emptyList(),
Collections.emptyMap(),
revertReason.map(Bytes::toString).orElse(null)));
}

@Override
public void finalizeOperation(final MessageFrame frame) {
context.setOpcodes(opcodes);
: StringUtils.EMPTY)
.gas(frame.getRemainingGas())
.gasCost(output != null ? gasRequirement : 0L)
.depth(frame.getDepth())
.stack(Collections.emptyList())
.memory(Collections.emptyList())
.storage(Collections.emptyMap())
.reason(revertReason.map(Bytes::toString).orElse(null))
.build();

context.addOpcodes(opcode);
}

private List<Bytes> captureMemory(final MessageFrame frame) {
if (!options.isMemory()) {
if (!getOptions().isMemory()) {
return Collections.emptyList();
}

final Bytes[] memoryContents = new Bytes[frame.memoryWordSize()];
for (int i = 0; i < memoryContents.length; i++) {
memoryContents[i] = frame.readMemory(i * 32L, 32);
int size = frame.memoryWordSize();
var memory = new ArrayList<Bytes>(size);
for (int i = 0; i < size; i++) {
memory.add(frame.readMemory(i * 32L, 32));
}
return Arrays.asList(memoryContents);

return memory;
}

private List<Bytes> captureStack(final MessageFrame frame) {
if (!options.isStack()) {
if (!getOptions().isStack()) {
return Collections.emptyList();
}

final Bytes[] stackContents = new Bytes[frame.stackSize()];
for (int i = 0; i < stackContents.length; i++) {
// Record stack contents in reverse
stackContents[i] = frame.getStackItem(stackContents.length - i - 1);
int size = frame.stackSize();
var stack = new ArrayList<Bytes>(size);
for (int i = 0; i < size; ++i) {
stack.add(frame.getStackItem(size - 1 - i));
}
return Arrays.asList(stackContents);

return stack;
}

private Map<Bytes, Bytes> captureStorage(final MessageFrame frame) {
if (!options.isStorage()) {
if (!getOptions().isStorage()) {
return Collections.emptyMap();
}

Expand All @@ -144,18 +129,36 @@ private Map<Bytes, Bytes> captureStorage(final MessageFrame frame) {

return new TreeMap<>(account.getUpdatedStorage());
} catch (final ModificationNotAllowedException e) {
log.warn(e.getMessage(), e);
log.warn("Failed to retrieve storage contents", e);
return Collections.emptyMap();
}
}

private static Optional<Bytes> getRevertReason(List<ContractAction> contractActions) {
private static Optional<Bytes> getRevertReasonFromContractActions(MessageFrame frame, List<ContractAction> contractActions) {
if (CollectionUtils.isEmpty(contractActions)) {
return Optional.empty();
}
List<MessageFrame> messageFrameList = new ArrayList<>(frame.getMessageFrameStack());
int index = messageFrameList.indexOf(frame);

//Need to rework further, index does not match with contractActions
return contractActions.stream()
.filter(ContractAction::hasRevertReason)
.filter(action -> action.hasRevertReason() && action.getIndex() == index)
.map(action -> Bytes.of(action.getResultData()))
.findFirst();
}

private ContractCallContext getContext() {
return ContractCallContext.get();
}

private OpcodeTracerOptions getOptions() {
ContractCallContext context = getContext();
return context.getOpcodeTracerOptions();
}

private boolean isCallToHederaTokenService(MessageFrame frame) {
Address recipientAddress = frame.getRecipientAddress();
return recipientAddress.equals(Address.fromHexString(SyntheticTxnFactory.HTS_PRECOMPILED_CONTRACT_ADDRESS));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,11 @@

import com.hedera.mirror.common.domain.contract.ContractAction;
import java.util.List;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.CrudRepository;

public interface ContractActionRepository extends CrudRepository<ContractAction, Long> {

@Query(value = "select * from contract_action where consensus_timestamp = ? order by index asc", nativeQuery = true)
List<ContractAction> findAllByConsensusTimestamp(long consensusTimestamp);
}
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,7 @@ private ContractDebugParameters buildCallServiceParameters(
.gas(getGasLimit(ethTransaction, contractResult))
.value(getValue(ethTransaction, contractResult).longValue())
.callData(getCallData(ethTransaction, contractResult))
.consensusTimestamp(consensusTimestamp)
.block(blockType)
.build();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2019-2024 Hedera Hashgraph, LLC
* Copyright (C) 2024 Hedera Hashgraph, LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -27,14 +27,14 @@
@Value
@Builder
public class ContractExecutionParameters implements CallServiceParameters {
BlockType block;
Bytes callData;
CallType callType;
long gas;
boolean isEstimate;
boolean isStatic;
Address receiver;
HederaEvmAccount sender;
TracerType tracerType = TracerType.OPERATION;
long value;
private final BlockType block;
private final Bytes callData;
private final CallType callType;
private final long gas;
private final boolean isEstimate;
private final boolean isStatic;
private final Address receiver;
private final HederaEvmAccount sender;
private final TracerType tracerType = TracerType.OPERATION;
private final long value;
}
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@ public HederaEvmTransactionProcessingResult execute(
final var initialFrame = buildInitialFrame(commonInitialFrame, receiver, payload, value);
final var messageFrameStack = initialFrame.getMessageFrameStack();
HederaEvmOperationTracer tracer = this.getTracer(tracerType);

tracer.init(initialFrame);

final var evmVersion = ((MirrorNodeEvmProperties) dynamicProperties).getSemanticEvmVersion();
Expand Down

0 comments on commit 943724c

Please sign in to comment.