Skip to content

Add debug tracers needed for modularized workflow #11477

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

Open
wants to merge 12 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all 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

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
import org.hiero.mirror.common.domain.contract.ContractAction;
import org.hiero.mirror.common.domain.transaction.RecordFile;
import org.hiero.mirror.web3.evm.contracts.execution.traceability.Opcode;
import org.hiero.mirror.web3.evm.contracts.execution.traceability.OpcodeTracer;
import org.hiero.mirror.web3.evm.contracts.execution.traceability.OpcodeTracerOptions;
import org.hiero.mirror.web3.evm.store.CachingStateFrame;
import org.hiero.mirror.web3.evm.store.StackedStateFrames;
Expand All @@ -42,14 +41,6 @@ public class ContractCallContext {
@Setter
private List<ContractAction> contractActions = List.of();

/**
* This is used to determine the contract action index of the current frame. It starts from {@code -1} because when
* the tracer receives the initial frame, it will increment this immediately inside
* {@link OpcodeTracer#traceContextEnter}.
*/
@Setter
private int contractActionIndexOfCurrentFrame = -1;

@Setter
private OpcodeTracerOptions opcodeTracerOptions;

Expand Down Expand Up @@ -83,6 +74,9 @@ public class ContractCallContext {
@Setter
private boolean isBalanceCall;

@Setter
private long gasRequirement;

private ContractCallContext() {}

public static ContractCallContext get() {
Expand Down Expand Up @@ -147,10 +141,6 @@ public boolean useHistorical() {
return recordFile != null; // Remove recordFile comparison after mono code deletion
}

public void incrementContractActionsCounter() {
this.contractActionIndexOfCurrentFrame++;
}

/**
* Returns the set timestamp or the consensus end timestamp from the set record file only if we are in a historical
* context. If not - an empty optional is returned.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

import com.github.benmanes.caffeine.cache.Caffeine;
import com.hedera.hapi.node.base.SemanticVersion;
import com.hedera.node.app.service.contract.impl.exec.ActionSidecarContentTracer;
import com.hedera.node.app.service.contract.impl.exec.operations.HederaCustomCallOperation;
import com.hedera.node.app.service.evm.contracts.execution.traceability.HederaEvmOperationTracer;
import com.hedera.node.app.service.evm.contracts.operations.CreateOperationExternalizer;
Expand Down Expand Up @@ -41,7 +42,9 @@
import org.hiero.mirror.web3.evm.contracts.execution.MirrorEvmMessageCallProcessor;
import org.hiero.mirror.web3.evm.contracts.execution.MirrorEvmMessageCallProcessorV30;
import org.hiero.mirror.web3.evm.contracts.execution.MirrorEvmMessageCallProcessorV50;
import org.hiero.mirror.web3.evm.contracts.execution.traceability.MirrorOperationActionTracer;
import org.hiero.mirror.web3.evm.contracts.execution.traceability.MirrorOperationTracer;
import org.hiero.mirror.web3.evm.contracts.execution.traceability.OpcodeActionTracer;
import org.hiero.mirror.web3.evm.contracts.execution.traceability.OpcodeTracer;
import org.hiero.mirror.web3.evm.contracts.execution.traceability.TracerType;
import org.hiero.mirror.web3.evm.contracts.operations.HederaBlockHashOperation;
Expand All @@ -63,6 +66,7 @@
import org.hyperledger.besu.evm.processor.ContractCreationProcessor;
import org.hyperledger.besu.evm.processor.MessageCallProcessor;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.caffeine.CaffeineCacheManager;
Expand Down Expand Up @@ -248,14 +252,29 @@
}

@Bean
Map<TracerType, Provider<HederaEvmOperationTracer>> tracerProvider(
@ConditionalOnProperty(
name = "hiero.mirror.web3.evm.modularizedServices",
havingValue = "false",
matchIfMissing = true)
Map<TracerType, Provider<HederaEvmOperationTracer>> monoTracerProvider(
final MirrorOperationTracer mirrorOperationTracer, final OpcodeTracer opcodeTracer) {
Map<TracerType, Provider<HederaEvmOperationTracer>> tracerMap = new EnumMap<>(TracerType.class);
tracerMap.put(TracerType.OPCODE, () -> opcodeTracer);
tracerMap.put(TracerType.OPERATION, () -> mirrorOperationTracer);
return tracerMap;
}

@Bean
@ConditionalOnProperty(name = "hiero.mirror.web3.evm.modularizedServices", havingValue = "true")
Map<TracerType, Provider<ActionSidecarContentTracer>> tracerProvider(
final MirrorOperationActionTracer mirrorOperationActionTracer,
final OpcodeActionTracer opcodeActionTracer) {
Map<TracerType, Provider<ActionSidecarContentTracer>> tracerMap = new EnumMap<>(TracerType.class);

Check warning on line 272 in web3/src/main/java/org/hiero/mirror/web3/evm/config/EvmConfiguration.java

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

web3/src/main/java/org/hiero/mirror/web3/evm/config/EvmConfiguration.java#L272

If you run in Java5 or newer and have concurrent access, you should use the ConcurrentHashMap implementation
tracerMap.put(TracerType.OPCODE, () -> opcodeActionTracer);
tracerMap.put(TracerType.OPERATION, () -> mirrorOperationActionTracer);
return tracerMap;
}

@Bean
Map<SemanticVersion, Provider<ContractCreationProcessor>> contractCreationProcessorProvider(
final ContractCreationProcessor contractCreationProcessor30,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
// SPDX-License-Identifier: Apache-2.0

package org.hiero.mirror.web3.evm.contracts.execution.traceability;

import com.hedera.hapi.streams.ContractActionType;
import com.hedera.hapi.streams.ContractActions;
import com.hedera.node.app.service.contract.impl.exec.ActionSidecarContentTracer;
import com.hedera.services.utils.EntityIdUtils;
import edu.umd.cs.findbugs.annotations.NonNull;
import jakarta.inject.Named;
import java.util.Optional;
import lombok.CustomLog;
import org.apache.commons.lang3.StringUtils;
import org.hiero.mirror.common.domain.entity.Entity;
import org.hiero.mirror.web3.evm.properties.TraceProperties;
import org.hiero.mirror.web3.state.CommonEntityAccessor;
import org.hyperledger.besu.evm.frame.MessageFrame;
import org.hyperledger.besu.evm.operation.Operation;

@Named
@CustomLog
public class MirrorOperationActionTracer implements ActionSidecarContentTracer {

private final TraceProperties traceProperties;
private final CommonEntityAccessor commonEntityAccessor;

public MirrorOperationActionTracer(
@NonNull final TraceProperties traceProperties, @NonNull final CommonEntityAccessor commonEntityAccessor) {
this.traceProperties = traceProperties;
this.commonEntityAccessor = commonEntityAccessor;
}

@Override
public void tracePostExecution(

Check notice on line 34 in web3/src/main/java/org/hiero/mirror/web3/evm/contracts/execution/traceability/MirrorOperationActionTracer.java

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

web3/src/main/java/org/hiero/mirror/web3/evm/contracts/execution/traceability/MirrorOperationActionTracer.java#L34

Method MirrorOperationActionTracer::tracePostExecution has 34 lines of code (limit is 30)

Check notice on line 34 in web3/src/main/java/org/hiero/mirror/web3/evm/contracts/execution/traceability/MirrorOperationActionTracer.java

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

web3/src/main/java/org/hiero/mirror/web3/evm/contracts/execution/traceability/MirrorOperationActionTracer.java#L34

Method MirrorOperationActionTracer::tracePostExecution has a cyclomatic complexity of 6 (limit is 5)
@NonNull final MessageFrame frame, @NonNull final Operation.OperationResult operationResult) {
if (!traceProperties.isEnabled()) {
return;
}

if (traceProperties.stateFilterCheck(frame.getState())) {
return;
}

final var recipientAddress = frame.getRecipientAddress();
final var recipientNum = recipientAddress != null
? commonEntityAccessor.get(
com.hedera.pbj.runtime.io.buffer.Bytes.wrap(recipientAddress.toArray()), Optional.empty())
: Optional.empty();

if (recipientNum.isPresent()
&& traceProperties.contractFilterCheck(
EntityIdUtils.asHexedEvmAddress(((Entity) recipientNum.get()).getId()))) {
return;
}

log.info(
"type={} operation={}, callDepth={}, contract={}, sender={}, recipient={}, remainingGas={}, revertReason={}, input={}, output={}, return={}",
frame.getType(),
frame.getCurrentOperation() != null
? frame.getCurrentOperation().getName()
: StringUtils.EMPTY,
frame.getDepth(),
frame.getContractAddress().toShortHexString(),
frame.getSenderAddress().toShortHexString(),
frame.getRecipientAddress().toShortHexString(),
frame.getRemainingGas(),
frame.getRevertReason()
.orElse(org.apache.tuweni.bytes.Bytes.EMPTY)
.toHexString(),
frame.getInputData().toShortHexString(),
frame.getOutputData().toShortHexString(),
frame.getReturnData().toShortHexString());
}

@Override
public void traceOriginAction(@NonNull MessageFrame frame) {
// NO-OP
}

@Override
public void sanitizeTracedActions(@NonNull MessageFrame frame) {
// NO-OP
}

@Override
public void tracePrecompileResult(@NonNull MessageFrame frame, @NonNull ContractActionType type) {
// NO-OP
}

@Override
public ContractActions contractActions() {
return null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import jakarta.inject.Named;
import lombok.CustomLog;
import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.apache.tuweni.bytes.Bytes;
import org.hiero.mirror.web3.evm.account.MirrorEvmContractAliases;
import org.hiero.mirror.web3.evm.properties.TraceProperties;
Expand Down Expand Up @@ -39,7 +40,9 @@ public void tracePostExecution(final MessageFrame currentFrame, final Operation.
log.info(
"type={} operation={}, callDepth={}, contract={}, sender={}, recipient={}, remainingGas={}, revertReason={}, input={}, output={}, return={}",
currentFrame.getType(),
currentFrame.getCurrentOperation().getName(),
currentFrame.getCurrentOperation() != null
? currentFrame.getCurrentOperation().getName()
: StringUtils.EMPTY,
currentFrame.getDepth(),
currentFrame.getContractAddress().toShortHexString(),
currentFrame.getSenderAddress().toShortHexString(),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
// SPDX-License-Identifier: Apache-2.0

package org.hiero.mirror.web3.evm.contracts.execution.traceability;

import static org.hiero.mirror.web3.evm.contracts.execution.traceability.TracerUtils.captureMemory;
import static org.hiero.mirror.web3.evm.contracts.execution.traceability.TracerUtils.captureStack;
import static org.hiero.mirror.web3.evm.contracts.execution.traceability.TracerUtils.getRevertReasonFromContractActions;
import static org.hiero.mirror.web3.evm.contracts.execution.traceability.TracerUtils.isCallToHederaPrecompile;

import com.hedera.hapi.streams.ContractActionType;
import com.hedera.hapi.streams.ContractActions;
import com.hedera.node.app.service.contract.impl.exec.ActionSidecarContentTracer;
import com.hedera.node.app.service.contract.impl.state.ProxyWorldUpdater;
import edu.umd.cs.findbugs.annotations.NonNull;
import edu.umd.cs.findbugs.annotations.Nullable;
import jakarta.inject.Named;
import java.util.Collections;
import java.util.Map;
import java.util.TreeMap;
import java.util.stream.Collectors;
import lombok.CustomLog;
import org.apache.commons.lang3.StringUtils;
import org.apache.tuweni.bytes.Bytes;
import org.hiero.mirror.web3.common.ContractCallContext;
import org.hiero.mirror.web3.evm.config.PrecompiledContractProvider;
import org.hyperledger.besu.datatypes.Address;
import org.hyperledger.besu.evm.ModificationNotAllowedException;
import org.hyperledger.besu.evm.frame.MessageFrame;
import org.hyperledger.besu.evm.operation.Operation.OperationResult;
import org.hyperledger.besu.evm.precompile.PrecompiledContract;

@Named
@CustomLog
public class OpcodeActionTracer implements ActionSidecarContentTracer {

private final Map<Address, PrecompiledContract> hederaPrecompiles;

public OpcodeActionTracer(@NonNull final PrecompiledContractProvider precompiledContractProvider) {
this.hederaPrecompiles = precompiledContractProvider.getHederaPrecompiles().entrySet().stream()
.collect(Collectors.toMap(e -> Address.fromHexString(e.getKey()), Map.Entry::getValue));
}

@Override
public void tracePostExecution(@NonNull final MessageFrame frame, @NonNull final OperationResult operationResult) {
final var context = ContractCallContext.get();

final var options = context.getOpcodeTracerOptions();
final var memory = captureMemory(frame, options);
final var stack = captureStack(frame, options);
final var storage = captureStorage(frame, options);
final var 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(
@NonNull final MessageFrame frame, final long gasRequirement, @Nullable final Bytes output) {
final var context = ContractCallContext.get();
final var revertReason = isCallToHederaPrecompile(frame, hederaPrecompiles)
? getRevertReasonFromContractActions(context)
: frame.getRevertReason();

final var opcode = Opcode.builder()
.pc(frame.getPC())
.op(
frame.getCurrentOperation() != null
? frame.getCurrentOperation().getName()
: StringUtils.EMPTY)
.gas(frame.getRemainingGas())
.gasCost(output != null && !output.isEmpty() ? gasRequirement : 0L)
.depth(frame.getDepth())
.stack(Collections.emptyList())
.memory(Collections.emptyList())
.storage(Collections.emptyMap())
.reason(revertReason.map(Bytes::toHexString).orElse(null))
.build();
context.addOpcodes(opcode);
}

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

try {
final var updates = ((ProxyWorldUpdater) frame.getWorldUpdater()).pendingStorageUpdates();
return updates.stream()
.flatMap(storageAccesses ->
storageAccesses.accesses().stream()) // Properly flatten the nested structure
.collect(Collectors.toMap(
e -> Bytes.wrap(e.key().toArray()),
e -> Bytes.wrap(e.value().toArray()),
(v1, v2) -> v1, // in case of duplicates, keep the first value
TreeMap::new));

} catch (final ModificationNotAllowedException e) {
log.warn("Failed to retrieve storage contents", e);
return Collections.emptyMap();
}
}

@Override
public void traceOriginAction(@NonNull MessageFrame frame) {
// NO-OP
}

@Override
public void sanitizeTracedActions(@NonNull MessageFrame frame) {
// NO-OP
}

@Override
public void tracePrecompileResult(@NonNull MessageFrame frame, @NonNull ContractActionType type) {
// NO-OP
}

@Override
public ContractActions contractActions() {
return null;
}
}
Loading
Loading