From 41de50a11c51fbe3e49f2991c24063ddbbf11b81 Mon Sep 17 00:00:00 2001 From: Angel Soto Date: Mon, 4 Nov 2024 18:36:41 +0100 Subject: [PATCH 1/8] Adding support to tracer parameter into debug_tracer module --- .../src/main/java/co/rsk/RskContext.java | 16 +- .../main/java/co/rsk/rpc/Web3DebugModule.java | 13 +- .../co/rsk/rpc/modules/debug/DebugModule.java | 10 +- .../rpc/modules/debug/DebugModuleImpl.java | 143 +++++++----------- .../rsk/rpc/modules/debug/DisableOption.java | 9 ++ .../rsk/rpc/modules/debug/TraceOptions.java | 97 ++++++++++-- .../rpc/modules/debug/trace/CallTracer.java | 48 ++++++ .../rpc/modules/debug/trace/DebugTracer.java | 31 ++++ .../rpc/modules/debug/trace/RskTracer.java | 123 +++++++++++++++ .../modules/debug/trace/TraceProvider.java | 41 +++++ .../rpc/modules/debug/trace/TracerConfig.java | 25 +++ .../rpc/modules/debug/trace/TracerType.java | 43 ++++++ .../rpc/parameters/DebugTracerParam.java | 79 ++++++++++ rskj-core/src/main/resources/reference.conf | 2 +- .../co/rsk/mine/TransactionModuleTest.java | 8 +- .../modules/debug/DebugModuleImplTest.java | 78 +++++----- .../org/ethereum/rpc/Web3ImplLogsTest.java | 8 +- .../org/ethereum/rpc/Web3ImplScoringTest.java | 8 +- .../ethereum/rpc/Web3ImplSnapshotTest.java | 8 +- .../java/org/ethereum/rpc/Web3ImplTest.java | 19 ++- 20 files changed, 646 insertions(+), 163 deletions(-) create mode 100644 rskj-core/src/main/java/co/rsk/rpc/modules/debug/trace/CallTracer.java create mode 100644 rskj-core/src/main/java/co/rsk/rpc/modules/debug/trace/DebugTracer.java create mode 100644 rskj-core/src/main/java/co/rsk/rpc/modules/debug/trace/RskTracer.java create mode 100644 rskj-core/src/main/java/co/rsk/rpc/modules/debug/trace/TraceProvider.java create mode 100644 rskj-core/src/main/java/co/rsk/rpc/modules/debug/trace/TracerConfig.java create mode 100644 rskj-core/src/main/java/co/rsk/rpc/modules/debug/trace/TracerType.java create mode 100644 rskj-core/src/main/java/org/ethereum/rpc/parameters/DebugTracerParam.java diff --git a/rskj-core/src/main/java/co/rsk/RskContext.java b/rskj-core/src/main/java/co/rsk/RskContext.java index b3f8a0ee4da..079cd5e3e26 100644 --- a/rskj-core/src/main/java/co/rsk/RskContext.java +++ b/rskj-core/src/main/java/co/rsk/RskContext.java @@ -63,6 +63,9 @@ import co.rsk.rpc.*; import co.rsk.rpc.modules.debug.DebugModule; import co.rsk.rpc.modules.debug.DebugModuleImpl; +import co.rsk.rpc.modules.debug.trace.CallTracer; +import co.rsk.rpc.modules.debug.trace.RskTracer; +import co.rsk.rpc.modules.debug.trace.TraceProvider; import co.rsk.rpc.modules.eth.*; import co.rsk.rpc.modules.eth.subscribe.BlockHeaderNotificationEmitter; import co.rsk.rpc.modules.eth.subscribe.LogsNotificationEmitter; @@ -811,16 +814,13 @@ public synchronized ConfigCapabilities getConfigCapabilities() { public synchronized DebugModule getDebugModule() { checkIfNotClosed(); + RskTracer rskTracer = new RskTracer(getBlockStore(), getReceiptStore(), + getBlockExecutor(), getWeb3InformationRetriever()); + CallTracer callTracer = new CallTracer(); + TraceProvider traceProvider = new TraceProvider(Arrays.asList(callTracer, rskTracer)); if (debugModule == null) { - debugModule = new DebugModuleImpl( - getBlockStore(), - getReceiptStore(), - getNodeMessageHandler(), - getBlockExecutor(), - getTxQuotaChecker(), - getWeb3InformationRetriever() - ); + debugModule = new DebugModuleImpl(traceProvider,getNodeMessageHandler(),getTxQuotaChecker()); } return debugModule; diff --git a/rskj-core/src/main/java/co/rsk/rpc/Web3DebugModule.java b/rskj-core/src/main/java/co/rsk/rpc/Web3DebugModule.java index c59feddf07f..2e49fd3eb56 100644 --- a/rskj-core/src/main/java/co/rsk/rpc/Web3DebugModule.java +++ b/rskj-core/src/main/java/co/rsk/rpc/Web3DebugModule.java @@ -21,6 +21,7 @@ import co.rsk.net.handler.quota.TxQuota; import co.rsk.rpc.modules.debug.DebugModule; import com.fasterxml.jackson.databind.JsonNode; +import org.ethereum.rpc.parameters.DebugTracerParam; import java.util.Map; @@ -32,19 +33,19 @@ default String debug_wireProtocolQueueSize() { } default JsonNode debug_traceTransaction(String transactionHash) throws Exception { - return debug_traceTransaction(transactionHash, null); + return getDebugModule().traceTransaction(transactionHash); } - default JsonNode debug_traceTransaction(String transactionHash, Map traceOptions) throws Exception { - return getDebugModule().traceTransaction(transactionHash, traceOptions); + default JsonNode debug_traceTransaction(String transactionHash, DebugTracerParam traceParams) throws Exception { + return getDebugModule().traceTransaction(transactionHash, traceParams.getTraceOptions(), traceParams.getTracerType()); } default JsonNode debug_traceBlockByHash(String blockHash) throws Exception { - return debug_traceBlockByHash(blockHash, null); + return getDebugModule().traceBlockByHash(blockHash); } - default JsonNode debug_traceBlockByHash(String blockHash, Map traceOptions) throws Exception { - return getDebugModule().traceBlockByHash(blockHash, traceOptions); + default JsonNode debug_traceBlockByHash(String blockHash, DebugTracerParam debugTracerParam) throws Exception { + return getDebugModule().traceBlockByHash(blockHash, debugTracerParam.getTraceOptions(), debugTracerParam.getTracerType()); } default JsonNode debug_traceBlockByNumber(String bnOrId) throws Exception { diff --git a/rskj-core/src/main/java/co/rsk/rpc/modules/debug/DebugModule.java b/rskj-core/src/main/java/co/rsk/rpc/modules/debug/DebugModule.java index 4b7c3e2ce30..6d499084423 100644 --- a/rskj-core/src/main/java/co/rsk/rpc/modules/debug/DebugModule.java +++ b/rskj-core/src/main/java/co/rsk/rpc/modules/debug/DebugModule.java @@ -19,6 +19,7 @@ package co.rsk.rpc.modules.debug; import co.rsk.net.handler.quota.TxQuota; +import co.rsk.rpc.modules.debug.trace.TracerType; import com.fasterxml.jackson.databind.JsonNode; import java.util.Map; @@ -27,11 +28,16 @@ public interface DebugModule { String wireProtocolQueueSize(); - JsonNode traceTransaction(String transactionHash, Map traceOptions) throws Exception; + JsonNode traceTransaction(String transactionHash) throws Exception; - JsonNode traceBlockByHash(String blockHash, Map traceOptions) throws Exception; + JsonNode traceTransaction(String transactionHash, TraceOptions traceOptions, TracerType tracerType) throws Exception; + + JsonNode traceBlockByHash(String blockHash, TraceOptions traceOptions, TracerType tracerType) throws Exception; + JsonNode traceBlockByHash(String blockHash) throws Exception; JsonNode traceBlockByNumber(String bnOrId, Map traceOptions) throws Exception; + JsonNode traceBlockByNumber(String bnOrId, Map traceOptions, TracerType tracerType) throws Exception; + TxQuota accountTransactionQuota(String address); } diff --git a/rskj-core/src/main/java/co/rsk/rpc/modules/debug/DebugModuleImpl.java b/rskj-core/src/main/java/co/rsk/rpc/modules/debug/DebugModuleImpl.java index 62be8e6e55c..2d1d9007940 100644 --- a/rskj-core/src/main/java/co/rsk/rpc/modules/debug/DebugModuleImpl.java +++ b/rskj-core/src/main/java/co/rsk/rpc/modules/debug/DebugModuleImpl.java @@ -19,55 +19,36 @@ package co.rsk.rpc.modules.debug; import co.rsk.core.RskAddress; -import co.rsk.core.bc.BlockExecutor; -import co.rsk.crypto.Keccak256; import co.rsk.net.MessageHandler; import co.rsk.net.handler.quota.TxQuota; import co.rsk.net.handler.quota.TxQuotaChecker; -import co.rsk.rpc.Web3InformationRetriever; +import co.rsk.rpc.modules.debug.trace.DebugTracer; +import co.rsk.rpc.modules.debug.trace.TraceProvider; +import co.rsk.rpc.modules.debug.trace.TracerType; import co.rsk.util.HexUtils; import co.rsk.util.StringUtils; import com.fasterxml.jackson.databind.JsonNode; -import org.ethereum.core.Block; -import org.ethereum.core.Transaction; -import org.ethereum.db.BlockStore; -import org.ethereum.db.ReceiptStore; -import org.ethereum.db.TransactionInfo; -import org.ethereum.vm.trace.ProgramTraceProcessor; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.List; import java.util.Map; -import java.util.stream.Collectors; +import java.util.Optional; public class DebugModuleImpl implements DebugModule { + //this could be configurable + public static final TracerType DEFAULT_TRACER_TYPE = TracerType.RSK_TRACER; private static final Logger logger = LoggerFactory.getLogger("web3"); - - private final BlockStore blockStore; - private final ReceiptStore receiptStore; - + private final TraceProvider traceProvider; private final MessageHandler messageHandler; - private final BlockExecutor blockExecutor; - private final TxQuotaChecker txQuotaChecker; - private final Web3InformationRetriever web3InformationRetriever; - - public DebugModuleImpl( - BlockStore blockStore, - ReceiptStore receiptStore, - MessageHandler messageHandler, - BlockExecutor blockExecutor, - TxQuotaChecker txQuotaChecker, - Web3InformationRetriever web3InformationRetriever) { - this.blockStore = blockStore; - this.receiptStore = receiptStore; + + public DebugModuleImpl(TraceProvider traceProvider, MessageHandler messageHandler, TxQuotaChecker txQuotaChecker) { + this.traceProvider = traceProvider; this.messageHandler = messageHandler; - this.blockExecutor = blockExecutor; this.txQuotaChecker = txQuotaChecker; - this.web3InformationRetriever = web3InformationRetriever; } + @Override public String wireProtocolQueueSize() { long n = messageHandler.getMessageQueueSize(); @@ -75,72 +56,60 @@ public String wireProtocolQueueSize() { } @Override - public JsonNode traceTransaction(String transactionHash, Map traceOptions) { - logger.trace("debug_traceTransaction for txHash: {}", StringUtils.trim(transactionHash)); - - TraceOptions options = toTraceOptions(traceOptions); + public TxQuota accountTransactionQuota(String address) { + logger.trace("debug_accountTransactionQuota({})", StringUtils.trim(address)); + RskAddress rskAddress = new RskAddress(address); + return txQuotaChecker.getTxQuota(rskAddress); + } - byte[] hash = HexUtils.stringHexToByteArray(transactionHash); - TransactionInfo txInfo = receiptStore.getInMainChain(hash, blockStore).orElse(null); + @Override + public JsonNode traceTransaction(String transactionHash) { + return traceTransaction(transactionHash, new TraceOptions(), null); + } - if (txInfo == null) { - logger.trace("No transaction info for txHash: {}", StringUtils.trim(transactionHash)); - return null; + @Override + public JsonNode traceTransaction(String transactionHash, TraceOptions traceOptions, TracerType tracerType) { + if (tracerType == null) { + tracerType = DEFAULT_TRACER_TYPE; } - - Block block = blockStore.getBlockByHash(txInfo.getBlockHash()); - Block parent = blockStore.getBlockByHash(block.getParentHash().getBytes()); - Transaction tx = block.getTransactionsList().get(txInfo.getIndex()); - txInfo.setTransaction(tx); - - ProgramTraceProcessor programTraceProcessor = new ProgramTraceProcessor(options); - blockExecutor.traceBlock(programTraceProcessor, 0, block, parent.getHeader(), false, false); - - return programTraceProcessor.getProgramTraceAsJsonNode(tx.getHash()); + if (traceOptions == null) { + traceOptions = new TraceOptions(); + } + DebugTracer tracer = traceProvider.getTracer(tracerType); + logger.trace("debug_traceTransaction for txHash: {}", StringUtils.trim(transactionHash)); + return tracer.traceTransaction(transactionHash, traceOptions); } @Override - public JsonNode traceBlockByHash(String blockHash, Map traceOptions) { + public JsonNode traceBlockByHash(String blockHash, TraceOptions traceOptions, TracerType tracerType) { + if (tracerType == null) { + tracerType = DEFAULT_TRACER_TYPE; + } + if (traceOptions == null) { + traceOptions = new TraceOptions(); + } logger.trace("debug_traceBlockByHash for blockHash: {}", StringUtils.trim(blockHash)); + DebugTracer tracer = traceProvider.getTracer(tracerType); + return tracer.traceBlockByHash(blockHash, traceOptions); + } - TraceOptions options = toTraceOptions(traceOptions); + @Override + public JsonNode traceBlockByHash(String blockHash) throws Exception { + return traceBlockByHash(blockHash, new TraceOptions(), null); + } - byte[] bHash = HexUtils.stringHexToByteArray(blockHash); - Block block = blockStore.getBlockByHash(bHash); - if (block == null) { - logger.trace("No block is found for blockHash: {}", StringUtils.trim(blockHash)); - return null; - } - return traceBlock(block, options); + @Override + public JsonNode traceBlockByNumber(String bnOrId, Map traceOptions) throws Exception { + return traceBlockByNumber(bnOrId, traceOptions, DEFAULT_TRACER_TYPE); } @Override - public JsonNode traceBlockByNumber(String bnOrId, Map traceOptions) { + public JsonNode traceBlockByNumber(String bnOrId, Map traceOptions, TracerType tracerType) throws Exception { logger.trace("debug_traceBlockByNumber for bnOrId: {}", StringUtils.trim(bnOrId)); - + DebugTracer tracer = traceProvider.getTracer(tracerType); TraceOptions options = toTraceOptions(traceOptions); - - Block block = web3InformationRetriever.getBlock(bnOrId).orElse(null); - if (block == null) { - logger.trace("No block is found for bnOrId: {}", StringUtils.trim(bnOrId)); - return null; - } - - return traceBlock(block, options); - } - - private JsonNode traceBlock(Block block, TraceOptions options) { - Block parent = blockStore.getBlockByHash(block.getParentHash().getBytes()); - - ProgramTraceProcessor programTraceProcessor = new ProgramTraceProcessor(options); - blockExecutor.traceBlock(programTraceProcessor, 0, block, parent.getHeader(), false, false); - - List txHashes = block.getTransactionsList().stream() - .map(Transaction::getHash) - .collect(Collectors.toList()); - - return programTraceProcessor.getProgramTracesAsJsonNode(txHashes); + return tracer.traceBlockByNumber(bnOrId, options); } private TraceOptions toTraceOptions(Map traceOptions) { @@ -154,10 +123,12 @@ private TraceOptions toTraceOptions(Map traceOptions) { return options; } - @Override - public TxQuota accountTransactionQuota(String address) { - logger.trace("debug_accountTransactionQuota({})", StringUtils.trim(address)); - RskAddress rskAddress = new RskAddress(address); - return this.txQuotaChecker.getTxQuota(rskAddress); + private TracerType getTracerType(Map options) { + if (options.containsKey("tracer")) { + Optional.ofNullable(TracerType.getTracerType(options.get("tracer"))) + .orElseThrow(() -> new IllegalArgumentException("Invalid tracer type: " + options.get("tracer"))); + } + return DEFAULT_TRACER_TYPE; } + } diff --git a/rskj-core/src/main/java/co/rsk/rpc/modules/debug/DisableOption.java b/rskj-core/src/main/java/co/rsk/rpc/modules/debug/DisableOption.java index 6fac1c24916..94987d8bc5d 100644 --- a/rskj-core/src/main/java/co/rsk/rpc/modules/debug/DisableOption.java +++ b/rskj-core/src/main/java/co/rsk/rpc/modules/debug/DisableOption.java @@ -31,4 +31,13 @@ public enum DisableOption { this.option = option; this.value = value; } + + public static DisableOption getDisableOption(String option) { + for (DisableOption disableOption : DisableOption.values()) { + if (disableOption.option.equalsIgnoreCase(option)) { + return disableOption; + } + } + return null; + } } diff --git a/rskj-core/src/main/java/co/rsk/rpc/modules/debug/TraceOptions.java b/rskj-core/src/main/java/co/rsk/rpc/modules/debug/TraceOptions.java index b5d3fa3cc40..819dd6da0fb 100644 --- a/rskj-core/src/main/java/co/rsk/rpc/modules/debug/TraceOptions.java +++ b/rskj-core/src/main/java/co/rsk/rpc/modules/debug/TraceOptions.java @@ -19,42 +19,76 @@ package co.rsk.rpc.modules.debug; +import co.rsk.rpc.modules.debug.trace.TracerType; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.deser.std.StdDeserializer; + +import java.io.IOException; +import java.io.Serial; import java.util.*; -import java.util.stream.Collectors; +@JsonDeserialize(using = TraceOptions.Deserializer.class) public class TraceOptions { - private final List supportedOptions; private final Set disabledFields; private final Set unsupportedOptions; + private boolean onlyTopCall; + private boolean diffMode; + private TracerType tracerType; public TraceOptions() { - supportedOptions = Arrays.stream(DisableOption.values()).map(option -> option.option) - .collect(Collectors.toList()); - this.disabledFields = new HashSet<>(); this.unsupportedOptions = new HashSet<>(); } public TraceOptions(Map traceOptions) { this(); + if (traceOptions == null) { + return; + } + traceOptions.forEach(this::addOption); + } + + public final void addOption(String key, String value) { + switch (key) { + case "onlyTopCall" -> onlyTopCall = Boolean.parseBoolean(value); + case "diffMode" -> diffMode = Boolean.parseBoolean(value); + default -> addDisableOption(key, value); + } + } - if (traceOptions == null || traceOptions.isEmpty()) return; - // Disabled Fields Parsing - for (DisableOption disableOption : DisableOption.values()) { - if (Boolean.parseBoolean(traceOptions.get(disableOption.option))) { - this.disabledFields.add(disableOption.value); + + private void addDisableOption(String key, String value) { + DisableOption disableOption = DisableOption.getDisableOption(key); + if (disableOption != null) { + if (Boolean.parseBoolean(value)) { + disabledFields.add(disableOption.value); } + } else { + unsupportedOptions.add(key); } + } + - // Unsupported Options + public void setOnlyTopCall(boolean onlyTopCall) { + this.onlyTopCall = onlyTopCall; + } + + public void setDiffMode(boolean diffMode) { + this.diffMode = diffMode; + } - traceOptions.keySet() - .stream() - .filter(key -> supportedOptions.stream().noneMatch(option -> option.equals(key))) - .forEach(unsupportedOptions::add); + public boolean isOnlyTopCall() { + return onlyTopCall; + } + + public boolean isDiffMode() { + return diffMode; } public Set getDisabledFields() { @@ -65,4 +99,37 @@ public Set getUnsupportedOptions() { return Collections.unmodifiableSet(unsupportedOptions); } + public TracerType getTracerType() { + return tracerType; + } + + public static class Deserializer extends StdDeserializer { + @Serial + private static final long serialVersionUID = 4222943114560623356L; + + public Deserializer() { + this(null); + } + + public Deserializer(Class vc) { + super(vc); + } + + @Override + public TraceOptions deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException { + JsonNode node = jp.getCodec().readTree(jp); + TraceOptions traceOptions = new TraceOptions(); + Iterator> fields = node.fields(); + while (fields.hasNext()) { + Map.Entry entry = fields.next(); + if ("tracerConfig".equalsIgnoreCase(entry.getKey())) { + JsonNode tracerConfigNode = entry.getValue(); + tracerConfigNode.fields().forEachRemaining(tracerConfigEntry + -> traceOptions.addOption(tracerConfigEntry.getKey(), tracerConfigEntry.getValue().asText())); + } + traceOptions.addOption(entry.getKey(), entry.getValue().asText()); + } + return traceOptions; + } + } } diff --git a/rskj-core/src/main/java/co/rsk/rpc/modules/debug/trace/CallTracer.java b/rskj-core/src/main/java/co/rsk/rpc/modules/debug/trace/CallTracer.java new file mode 100644 index 00000000000..9b60881a474 --- /dev/null +++ b/rskj-core/src/main/java/co/rsk/rpc/modules/debug/trace/CallTracer.java @@ -0,0 +1,48 @@ +/* + * This file is part of RskJ + * Copyright (C) 2024 RSK Labs Ltd. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ +package co.rsk.rpc.modules.debug.trace; + +import co.rsk.rpc.modules.debug.TraceOptions; +import com.fasterxml.jackson.databind.JsonNode; + +public class CallTracer implements DebugTracer { + + public static final String UNSUPPORTED_OPERATION = "Operation not supported by this tracer."; + + @Override + public JsonNode traceTransaction(String transactionHash, TraceOptions traceOptions) { + throw new UnsupportedOperationException(UNSUPPORTED_OPERATION); + } + + @Override + public JsonNode traceBlockByHash(String blockHash, TraceOptions traceOptions) { + throw new UnsupportedOperationException(UNSUPPORTED_OPERATION); + + } + + @Override + public JsonNode traceBlockByNumber(String bnOrId, TraceOptions traceOptions) { + throw new UnsupportedOperationException(UNSUPPORTED_OPERATION); + + } + + @Override + public TracerType getTracerType() { + return TracerType.CALL_TRACER; + } +} diff --git a/rskj-core/src/main/java/co/rsk/rpc/modules/debug/trace/DebugTracer.java b/rskj-core/src/main/java/co/rsk/rpc/modules/debug/trace/DebugTracer.java new file mode 100644 index 00000000000..bf938f7b0f9 --- /dev/null +++ b/rskj-core/src/main/java/co/rsk/rpc/modules/debug/trace/DebugTracer.java @@ -0,0 +1,31 @@ +/* + * This file is part of RskJ + * Copyright (C) 2024 RSK Labs Ltd. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ +package co.rsk.rpc.modules.debug.trace; + +import co.rsk.rpc.modules.debug.TraceOptions; +import com.fasterxml.jackson.databind.JsonNode; + +public interface DebugTracer { + JsonNode traceTransaction(String transactionHash, TraceOptions traceOptions); + + JsonNode traceBlockByHash(String blockHash, TraceOptions traceOptions); + + JsonNode traceBlockByNumber(String bnOrId, TraceOptions traceOptions); + + TracerType getTracerType(); +} diff --git a/rskj-core/src/main/java/co/rsk/rpc/modules/debug/trace/RskTracer.java b/rskj-core/src/main/java/co/rsk/rpc/modules/debug/trace/RskTracer.java new file mode 100644 index 00000000000..0dd923e9bad --- /dev/null +++ b/rskj-core/src/main/java/co/rsk/rpc/modules/debug/trace/RskTracer.java @@ -0,0 +1,123 @@ +/* + * This file is part of RskJ + * Copyright (C) 2024 RSK Labs Ltd. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ +package co.rsk.rpc.modules.debug.trace; + +import co.rsk.core.bc.BlockExecutor; +import co.rsk.crypto.Keccak256; +import co.rsk.rpc.Web3InformationRetriever; +import co.rsk.rpc.modules.debug.TraceOptions; +import co.rsk.util.HexUtils; +import co.rsk.util.StringUtils; +import com.fasterxml.jackson.databind.JsonNode; +import org.ethereum.core.Block; +import org.ethereum.core.Transaction; +import org.ethereum.db.BlockStore; +import org.ethereum.db.ReceiptStore; +import org.ethereum.db.TransactionInfo; +import org.ethereum.vm.trace.ProgramTraceProcessor; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.List; +import java.util.stream.Collectors; + +public class RskTracer implements DebugTracer { + + private static final Logger logger = LoggerFactory.getLogger("web3"); + private final ReceiptStore receiptStore; + private final BlockStore blockStore; + private final BlockExecutor blockExecutor; + private final Web3InformationRetriever web3InformationRetriever; + + public RskTracer( + BlockStore blockStore, + ReceiptStore receiptStore, + BlockExecutor blockExecutor, + Web3InformationRetriever web3InformationRetriever) { + this.blockStore = blockStore; + this.receiptStore = receiptStore; + this.blockExecutor = blockExecutor; + this.web3InformationRetriever = web3InformationRetriever; + } + + @Override + public JsonNode traceTransaction(String transactionHash, TraceOptions traceOptions) { + logger.trace("debug_traceTransaction for txHash: {}", StringUtils.trim(transactionHash)); + + + byte[] hash = HexUtils.stringHexToByteArray(transactionHash); + TransactionInfo txInfo = receiptStore.getInMainChain(hash, blockStore).orElse(null); + + if (txInfo == null) { + logger.trace("No transaction info for txHash: {}", StringUtils.trim(transactionHash)); + return null; + } + + Block block = blockStore.getBlockByHash(txInfo.getBlockHash()); + Block parent = blockStore.getBlockByHash(block.getParentHash().getBytes()); + Transaction tx = block.getTransactionsList().get(txInfo.getIndex()); + txInfo.setTransaction(tx); + + ProgramTraceProcessor programTraceProcessor = new ProgramTraceProcessor(traceOptions); + blockExecutor.traceBlock(programTraceProcessor, 0, block, parent.getHeader(), false, false); + + return programTraceProcessor.getProgramTraceAsJsonNode(tx.getHash()); + } + + @Override + public JsonNode traceBlockByHash(String blockHash, TraceOptions traceOptions) { + + byte[] bHash = HexUtils.stringHexToByteArray(blockHash); + Block block = blockStore.getBlockByHash(bHash); + if (block == null) { + logger.trace("No block is found for blockHash: {}", StringUtils.trim(blockHash)); + return null; + } + + return traceBlock(block, traceOptions); + } + + @Override + public JsonNode traceBlockByNumber(String bnOrId, TraceOptions traceOptions){ + Block block = web3InformationRetriever.getBlock(bnOrId).orElse(null); + if (block == null) { + logger.trace("No block is found for bnOrId: {}", StringUtils.trim(bnOrId)); + return null; + } + + return traceBlock(block, traceOptions); + } + + @Override + public TracerType getTracerType() { + return TracerType.RSK_TRACER; + } + + private JsonNode traceBlock(Block block, TraceOptions options) { + Block parent = blockStore.getBlockByHash(block.getParentHash().getBytes()); + + ProgramTraceProcessor programTraceProcessor = new ProgramTraceProcessor(options); + blockExecutor.traceBlock(programTraceProcessor, 0, block, parent.getHeader(), false, false); + + List txHashes = block.getTransactionsList().stream() + .map(Transaction::getHash) + .collect(Collectors.toList()); + + return programTraceProcessor.getProgramTracesAsJsonNode(txHashes); + } +} diff --git a/rskj-core/src/main/java/co/rsk/rpc/modules/debug/trace/TraceProvider.java b/rskj-core/src/main/java/co/rsk/rpc/modules/debug/trace/TraceProvider.java new file mode 100644 index 00000000000..bd180c5726d --- /dev/null +++ b/rskj-core/src/main/java/co/rsk/rpc/modules/debug/trace/TraceProvider.java @@ -0,0 +1,41 @@ +/* + * This file is part of RskJ + * Copyright (C) 2024 RSK Labs Ltd. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ +package co.rsk.rpc.modules.debug.trace; + +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.function.Function; +import java.util.stream.Collectors; + +public class TraceProvider { + + + private final Map tracers; + + public TraceProvider(List tracers) { + this.tracers = tracers.stream().collect(Collectors.toMap(DebugTracer::getTracerType, Function.identity())); + } + + + public DebugTracer getTracer(TracerType tracerType) { + return Optional.ofNullable(tracers.get(tracerType)) + .orElseThrow(() -> new IllegalArgumentException("Requested Tracer is not available." + tracerType.getTracerName())); + } + +} diff --git a/rskj-core/src/main/java/co/rsk/rpc/modules/debug/trace/TracerConfig.java b/rskj-core/src/main/java/co/rsk/rpc/modules/debug/trace/TracerConfig.java new file mode 100644 index 00000000000..61b56138792 --- /dev/null +++ b/rskj-core/src/main/java/co/rsk/rpc/modules/debug/trace/TracerConfig.java @@ -0,0 +1,25 @@ +package co.rsk.rpc.modules.debug.trace; + +public class TracerConfig { + + public TracerConfig(boolean onlyTopCall, boolean diffMode) { + this.onlyTopCall = onlyTopCall; + this.diffMode = diffMode; + } + public TracerConfig() { + this(false, false); + } + private boolean onlyTopCall; + private boolean diffMode; + + public boolean isOnlyTopCall() { + return onlyTopCall; + } + + public boolean isDiffMode() { + return diffMode; + } + + + +} diff --git a/rskj-core/src/main/java/co/rsk/rpc/modules/debug/trace/TracerType.java b/rskj-core/src/main/java/co/rsk/rpc/modules/debug/trace/TracerType.java new file mode 100644 index 00000000000..862ea970239 --- /dev/null +++ b/rskj-core/src/main/java/co/rsk/rpc/modules/debug/trace/TracerType.java @@ -0,0 +1,43 @@ +/* + * This file is part of RskJ + * Copyright (C) 2024 RSK Labs Ltd. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ +package co.rsk.rpc.modules.debug.trace; + +public enum TracerType { + CALL_TRACER("callTracer"), RSK_TRACER("rskTracer"); + + private final String tracerName; + TracerType(String tracerName) { + this.tracerName = tracerName; + } + + public static TracerType getTracerType(String tracerName) { + for (TracerType tracerType : TracerType.values()) { + if (tracerType.getTracerName().equalsIgnoreCase(tracerName)) { + return tracerType; + } + } + return null; + } + public String getTracerName() { + return tracerName; + } + + public TracerType getDefault() { + return RSK_TRACER; + } +} diff --git a/rskj-core/src/main/java/org/ethereum/rpc/parameters/DebugTracerParam.java b/rskj-core/src/main/java/org/ethereum/rpc/parameters/DebugTracerParam.java new file mode 100644 index 00000000000..716d551beab --- /dev/null +++ b/rskj-core/src/main/java/org/ethereum/rpc/parameters/DebugTracerParam.java @@ -0,0 +1,79 @@ +package org.ethereum.rpc.parameters; + +import co.rsk.rpc.modules.debug.TraceOptions; +import co.rsk.rpc.modules.debug.trace.TracerType; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.deser.std.StdDeserializer; + +import java.io.IOException; +import java.io.Serial; +import java.util.Iterator; +import java.util.Map; +import java.util.Optional; + +@JsonDeserialize(using = DebugTracerParam.Deserializer.class) + +public class DebugTracerParam { + private final TracerType tracerType; + private final TraceOptions traceOptions; + + public DebugTracerParam(TracerType tracerType, TraceOptions traceOptions) { + this.tracerType = tracerType; + this.traceOptions = traceOptions; + } + + public TracerType getTracerType() { + return tracerType; + } + + public TraceOptions getTraceOptions() { + return traceOptions; + } + + public static class Deserializer extends StdDeserializer { + @Serial + private static final long serialVersionUID = 4222943114560623356L; + + public Deserializer() { + this(null); + } + + public Deserializer(Class vc) { + super(vc); + } + + @Override + public DebugTracerParam deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException { + JsonNode node; + try { + node = jp.getCodec().readTree(jp); + } catch (Exception e) { + throw new IllegalArgumentException("Can not deserialize parameters", e); + } + TracerType tracerType = null; + TraceOptions traceOptions = new TraceOptions(); + Iterator> fields = node.fields(); + while (fields.hasNext()) { + Map.Entry entry = fields.next(); + if ("tracerConfig".equalsIgnoreCase(entry.getKey())) { + JsonNode tracerConfigNode = entry.getValue(); + tracerConfigNode.fields().forEachRemaining(tracerConfigEntry + -> traceOptions.addOption(tracerConfigEntry.getKey(), tracerConfigEntry.getValue().asText())); + }else if("tracer".equalsIgnoreCase(entry.getKey())) { + tracerType = getTracerType(entry.getValue().asText()); + }else { + traceOptions.addOption(entry.getKey(), entry.getValue().asText()); + } + } + return new DebugTracerParam(tracerType, traceOptions); + } + + private TracerType getTracerType(String tracerType) { + return Optional.ofNullable(TracerType.getTracerType(tracerType)).orElseThrow(() + -> new IllegalArgumentException("Invalid tracer type: " + tracerType)); + } + } +} diff --git a/rskj-core/src/main/resources/reference.conf b/rskj-core/src/main/resources/reference.conf index a7d71a2b4db..1b1495a56ae 100644 --- a/rskj-core/src/main/resources/reference.conf +++ b/rskj-core/src/main/resources/reference.conf @@ -469,7 +469,7 @@ rpc { }, debug { version: "1.0", - enabled: "false" + enabled: "true" }, trace { version: "1.0", diff --git a/rskj-core/src/test/java/co/rsk/mine/TransactionModuleTest.java b/rskj-core/src/test/java/co/rsk/mine/TransactionModuleTest.java index f158eb2bcd2..5f5d1b1400c 100644 --- a/rskj-core/src/test/java/co/rsk/mine/TransactionModuleTest.java +++ b/rskj-core/src/test/java/co/rsk/mine/TransactionModuleTest.java @@ -35,6 +35,9 @@ import co.rsk.rpc.Web3RskImpl; import co.rsk.rpc.modules.debug.DebugModule; import co.rsk.rpc.modules.debug.DebugModuleImpl; +import co.rsk.rpc.modules.debug.trace.DebugTracer; +import co.rsk.rpc.modules.debug.trace.RskTracer; +import co.rsk.rpc.modules.debug.trace.TraceProvider; import co.rsk.rpc.modules.eth.*; import co.rsk.rpc.modules.personal.PersonalModuleWalletEnabled; import co.rsk.rpc.modules.txpool.TxPoolModule; @@ -82,6 +85,7 @@ import java.math.BigInteger; import java.time.Clock; +import java.util.List; import static org.mockito.Mockito.mock; @@ -650,7 +654,9 @@ repositoryLocator, new EthModuleWalletEnabled(wallet, transactionPool, signature config.getCallGasCap() ); TxPoolModule txPoolModule = new TxPoolModuleImpl(transactionPool, new ReceivedTxSignatureCache()); - DebugModule debugModule = new DebugModuleImpl(null, null, Web3Mocks.getMockMessageHandler(), null, null, null); + DebugTracer debugTracer = new RskTracer(null, null, null, null); + TraceProvider traceProvider = new TraceProvider(List.of(debugTracer)); + DebugModule debugModule = new DebugModuleImpl(traceProvider, Web3Mocks.getMockMessageHandler(), null); ChannelManager channelManager = new SimpleChannelManager(); return new Web3RskImpl( diff --git a/rskj-core/src/test/java/co/rsk/rpc/modules/debug/DebugModuleImplTest.java b/rskj-core/src/test/java/co/rsk/rpc/modules/debug/DebugModuleImplTest.java index bdcb437179c..67d2f069f8d 100644 --- a/rskj-core/src/test/java/co/rsk/rpc/modules/debug/DebugModuleImplTest.java +++ b/rskj-core/src/test/java/co/rsk/rpc/modules/debug/DebugModuleImplTest.java @@ -19,10 +19,14 @@ package co.rsk.rpc.modules.debug; import co.rsk.core.RskAddress; +import co.rsk.core.bc.BlockExecutor; import co.rsk.net.MessageHandler; import co.rsk.net.handler.quota.TxQuota; import co.rsk.net.handler.quota.TxQuotaChecker; import co.rsk.rpc.Web3InformationRetriever; +import co.rsk.rpc.modules.debug.trace.DebugTracer; +import co.rsk.rpc.modules.debug.trace.RskTracer; +import co.rsk.rpc.modules.debug.trace.TraceProvider; import co.rsk.test.World; import co.rsk.test.dsl.DslParser; import co.rsk.test.dsl.WorldDslProcessor; @@ -44,10 +48,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; -import java.util.Optional; +import java.util.*; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -69,8 +70,7 @@ void setup() { messageHandlerMock = Web3Mocks.getMockMessageHandler(); txQuotaCheckerMock = mock(TxQuotaChecker.class); web3InformationRetrieverMock = mock(Web3InformationRetriever.class); - - mockedDebugModule = new DebugModuleImpl(blockStoreMock, receiptStoreMock, messageHandlerMock, Web3Mocks.getMockBlockExecutor(), txQuotaCheckerMock, web3InformationRetrieverMock); + mockedDebugModule = getDebugModule(blockStoreMock, Web3Mocks.getMockBlockExecutor(), receiptStoreMock, messageHandlerMock, txQuotaCheckerMock, web3InformationRetrieverMock); } @Test @@ -101,7 +101,7 @@ void debug_traceTransaction_retrieveUnknownTransactionAsNull() { when(receiptStoreMock.getInMainChain(hash, blockStoreMock)).thenReturn(Optional.empty()); - JsonNode result = mockedDebugModule.traceTransaction("0x00", null); + JsonNode result = mockedDebugModule.traceTransaction("0x00"); Assertions.assertNull(result); } @@ -117,9 +117,9 @@ void debug_traceTransaction_retrieveSimpleContractCreationTrace() throws Excepti Transaction transaction = world.getTransactionByName("tx01"); - DebugModuleImpl debugModule = new DebugModuleImpl(world.getBlockStore(), receiptStore, messageHandlerMock, world.getBlockExecutor(), null, null); + DebugModuleImpl debugModule = getDebugModule(world.getBlockStore(), world.getBlockExecutor(), receiptStore, messageHandlerMock, null, null); - JsonNode result = debugModule.traceTransaction(transaction.getHash().toJsonString(), null); + JsonNode result = debugModule.traceTransaction(transaction.getHash().toJsonString()); Assertions.assertNotNull(result); Assertions.assertTrue(result.isObject()); @@ -143,9 +143,9 @@ void debug_traceTransaction_retrieveEmptyContractCreationTrace() throws Exceptio Transaction transaction = world.getTransactionByName("tx01"); - DebugModuleImpl debugModule = new DebugModuleImpl(world.getBlockStore(), receiptStore, messageHandlerMock, world.getBlockExecutor(), null, null); + DebugModuleImpl debugModule = getDebugModule(world.getBlockStore(), world.getBlockExecutor(), receiptStore, messageHandlerMock, null, null); - JsonNode result = debugModule.traceTransaction(transaction.getHash().toJsonString(), null); + JsonNode result = debugModule.traceTransaction(transaction.getHash().toJsonString()); Assertions.assertNotNull(result); Assertions.assertTrue(result.isObject()); @@ -168,9 +168,9 @@ void debug_traceTransaction_retrieveSimpleContractInvocationTrace() throws Excep Transaction transaction = world.getTransactionByName("tx02"); - DebugModuleImpl debugModule = new DebugModuleImpl(world.getBlockStore(), receiptStore, messageHandlerMock, world.getBlockExecutor(), null, null); + DebugModuleImpl debugModule = getDebugModule(world.getBlockStore(), world.getBlockExecutor(), receiptStore, messageHandlerMock, null, null); - JsonNode result = debugModule.traceTransaction(transaction.getHash().toJsonString(), null); + JsonNode result = debugModule.traceTransaction(transaction.getHash().toJsonString()); Assertions.assertNotNull(result); Assertions.assertTrue(result.isObject()); @@ -194,9 +194,9 @@ void debug_traceTransaction_retrieveSimpleAccountTransfer() throws Exception { Transaction transaction = world.getTransactionByName("tx01"); - DebugModuleImpl debugModule = new DebugModuleImpl(world.getBlockStore(), receiptStore, messageHandlerMock, world.getBlockExecutor(), null, null); + DebugModuleImpl debugModule = getDebugModule(world.getBlockStore(), world.getBlockExecutor(), receiptStore, messageHandlerMock, null, null); - JsonNode result = debugModule.traceTransaction(transaction.getHash().toJsonString(), null); + JsonNode result = debugModule.traceTransaction(transaction.getHash().toJsonString()); Assertions.assertNotNull(result); Assertions.assertTrue(result.isObject()); @@ -219,28 +219,29 @@ void debug_traceTransaction_retrieveSimpleAccountTransferWithTraceOptions() thro Transaction transaction = world.getTransactionByName("tx01"); - DebugModuleImpl debugModule = new DebugModuleImpl(world.getBlockStore(), receiptStore, messageHandlerMock, world.getBlockExecutor(), null, null); + DebugModuleImpl debugModule = getDebugModule(world.getBlockStore(), world.getBlockExecutor(), receiptStore, messageHandlerMock, null, null); - JsonNode resultWithNoOptions = debugModule.traceTransaction(transaction.getHash().toJsonString(), null); - JsonNode resultWithEmptyOptions = debugModule.traceTransaction(transaction.getHash().toJsonString(), Collections.emptyMap()); + JsonNode resultWithNoOptions = debugModule.traceTransaction(transaction.getHash().toJsonString()); + TraceOptions traceOptions = new TraceOptions(Collections.emptyMap()); + JsonNode resultWithEmptyOptions = debugModule.traceTransaction(transaction.getHash().toJsonString(), traceOptions, null); Assertions.assertEquals(resultWithNoOptions, resultWithEmptyOptions); - Map traceOptions = new HashMap<>(); - traceOptions.put("disableStorage", "true"); - - JsonNode resultWithNonEmptyOptions = debugModule.traceTransaction(transaction.getHash().toJsonString(), traceOptions); + Map traceOptionMap = new HashMap<>(); + traceOptionMap.put("disableStorage", "true"); + TraceOptions traceOptions2 = new TraceOptions(traceOptionMap); + JsonNode resultWithNonEmptyOptions = debugModule.traceTransaction(transaction.getHash().toJsonString(), traceOptions2, null); Assertions.assertEquals(resultWithNoOptions, resultWithNonEmptyOptions); } @Test - void debug_traceBlockByHash_retrieveUnknownBlockAsNull() throws Exception { + void debug_traceBlockByHash_retrieveUnknownBlockAsNull() { byte[] hash = HexUtils.stringHexToByteArray("0x00"); when(blockStoreMock.getBlockByHash(hash)).thenReturn(null); - JsonNode result = mockedDebugModule.traceBlockByHash("0x00", null); + JsonNode result = mockedDebugModule.traceBlockByHash("0x00", null, null); Assertions.assertNull(result); } @@ -256,9 +257,10 @@ void debug_traceBlockByHash_retrieveSimpleContractsCreationTrace() throws Except Block block = world.getBlockByName("b01"); - DebugModuleImpl debugModule = new DebugModuleImpl(world.getBlockStore(), receiptStore, messageHandlerMock, world.getBlockExecutor(), null, null); + DebugModuleImpl debugModule = getDebugModule(world.getBlockStore(), world.getBlockExecutor(), receiptStore, messageHandlerMock, null, null); + - JsonNode result = debugModule.traceBlockByHash(block.getHash().toJsonString(), null); + JsonNode result = debugModule.traceBlockByHash(block.getHash().toJsonString(), null, null); Assertions.assertNotNull(result); Assertions.assertTrue(result.isArray()); @@ -297,7 +299,7 @@ void debug_traceBlockByNumber_retrieveSimpleContractsCreationTrace() throws Exce String blockNumber = HexUtils.toQuantityJsonHex(block.getNumber()); when(web3InformationRetrieverMock.getBlock(blockNumber)).thenReturn(Optional.of(block)); - DebugModuleImpl debugModule = new DebugModuleImpl(world.getBlockStore(), receiptStore, messageHandlerMock, world.getBlockExecutor(), null, web3InformationRetrieverMock); + DebugModuleImpl debugModule = getDebugModule(world.getBlockStore(), world.getBlockExecutor(), receiptStore, messageHandlerMock, null, web3InformationRetrieverMock); JsonNode result = debugModule.traceBlockByNumber(blockNumber, null); @@ -327,15 +329,15 @@ void debug_traceTransaction_retrieveSimpleContractInvocationTrace_traceOptions_d Transaction transaction = world.getTransactionByName("tx02"); - DebugModuleImpl debugModule = new DebugModuleImpl(world.getBlockStore(), receiptStore, messageHandlerMock, world.getBlockExecutor(), null, null); + DebugModuleImpl debugModule = getDebugModule(world.getBlockStore(), world.getBlockExecutor(), receiptStore, messageHandlerMock, null, null); - Map traceOptions = new HashMap<>(); - traceOptions.put("disableStack", "true"); - traceOptions.put("disableMemory", "true"); - traceOptions.put("disableStorage", "true"); - - JsonNode witnessResult = debugModule.traceTransaction(transaction.getHash().toJsonString(), null); - JsonNode result = debugModule.traceTransaction(transaction.getHash().toJsonString(), traceOptions); + Map traceOptionsMap = new HashMap<>(); + traceOptionsMap.put("disableStack", "true"); + traceOptionsMap.put("disableMemory", "true"); + traceOptionsMap.put("disableStorage", "true"); + TraceOptions traceOptions = new TraceOptions(traceOptionsMap); + JsonNode witnessResult = debugModule.traceTransaction(transaction.getHash().toJsonString()); + JsonNode result = debugModule.traceTransaction(transaction.getHash().toJsonString(), traceOptions, null); // Sanity Check @@ -406,4 +408,10 @@ void debug_accountTransactionQuota_whenNonExistingAddress_returnsNull() { Assertions.assertNull(txQuotaRetrieved); } + + private DebugModuleImpl getDebugModule(BlockStore bockStore, BlockExecutor blockExecutor, ReceiptStore receiptStore, MessageHandler messageHandler, TxQuotaChecker txQuotaChecker, Web3InformationRetriever web3InformationRetriever) { + DebugTracer tracer = new RskTracer(bockStore, receiptStore, blockExecutor, web3InformationRetriever); + TraceProvider traceProvider = new TraceProvider(List.of(tracer)); + return new DebugModuleImpl(traceProvider, messageHandler, txQuotaChecker); + } } diff --git a/rskj-core/src/test/java/org/ethereum/rpc/Web3ImplLogsTest.java b/rskj-core/src/test/java/org/ethereum/rpc/Web3ImplLogsTest.java index 426e0488cf1..05934b848d4 100644 --- a/rskj-core/src/test/java/org/ethereum/rpc/Web3ImplLogsTest.java +++ b/rskj-core/src/test/java/org/ethereum/rpc/Web3ImplLogsTest.java @@ -32,6 +32,9 @@ import co.rsk.rpc.Web3RskImpl; import co.rsk.rpc.modules.debug.DebugModule; import co.rsk.rpc.modules.debug.DebugModuleImpl; +import co.rsk.rpc.modules.debug.trace.DebugTracer; +import co.rsk.rpc.modules.debug.trace.RskTracer; +import co.rsk.rpc.modules.debug.trace.TraceProvider; import co.rsk.rpc.modules.eth.EthModule; import co.rsk.rpc.modules.eth.EthModuleWalletEnabled; import co.rsk.rpc.modules.personal.PersonalModule; @@ -1079,8 +1082,9 @@ null, new EthModuleWalletEnabled(wallet, transactionPool, signatureCache), null, config.getCallGasCap() ); TxPoolModule txPoolModule = new TxPoolModuleImpl(transactionPool, signatureCache); - DebugModule debugModule = new DebugModuleImpl(null, null, Web3Mocks.getMockMessageHandler(), null, null, null); - blocksBloomStore = new BlocksBloomStore(2, 0, new HashMapDB()); + DebugTracer debugTracer = new RskTracer(null, null, null, null); + TraceProvider traceProvider = new TraceProvider(List.of(debugTracer)); + DebugModule debugModule = new DebugModuleImpl(traceProvider, Web3Mocks.getMockMessageHandler(), null); blocksBloomStore = new BlocksBloomStore(2, 0, new HashMapDB()); return new Web3RskImpl( eth, blockChain, diff --git a/rskj-core/src/test/java/org/ethereum/rpc/Web3ImplScoringTest.java b/rskj-core/src/test/java/org/ethereum/rpc/Web3ImplScoringTest.java index e6122a4e356..596462c072d 100644 --- a/rskj-core/src/test/java/org/ethereum/rpc/Web3ImplScoringTest.java +++ b/rskj-core/src/test/java/org/ethereum/rpc/Web3ImplScoringTest.java @@ -27,6 +27,9 @@ import co.rsk.rpc.Web3RskImpl; import co.rsk.rpc.modules.debug.DebugModule; import co.rsk.rpc.modules.debug.DebugModuleImpl; +import co.rsk.rpc.modules.debug.trace.DebugTracer; +import co.rsk.rpc.modules.debug.trace.RskTracer; +import co.rsk.rpc.modules.debug.trace.TraceProvider; import co.rsk.rpc.modules.eth.EthModule; import co.rsk.rpc.modules.eth.EthModuleWalletEnabled; import co.rsk.rpc.modules.personal.PersonalModule; @@ -51,6 +54,7 @@ import java.net.InetAddress; import java.net.UnknownHostException; import java.util.Collections; +import java.util.List; /** * Created by ajlopez on 12/07/2017. @@ -400,7 +404,9 @@ null, new EthModuleWalletEnabled(wallet, world.getTransactionPool(), world.getBl config.getCallGasCap() ); TxPoolModule tpm = new TxPoolModuleImpl(Web3Mocks.getMockTransactionPool(), new ReceivedTxSignatureCache()); - DebugModule dm = new DebugModuleImpl(null, null, Web3Mocks.getMockMessageHandler(), null, null, null); + DebugTracer debugTracer = new RskTracer(null, null, null, null); + TraceProvider traceProvider = new TraceProvider(List.of(debugTracer)); + DebugModule dm = new DebugModuleImpl(traceProvider, Web3Mocks.getMockMessageHandler(), null); return new Web3RskImpl( rsk, world.getBlockChain(), diff --git a/rskj-core/src/test/java/org/ethereum/rpc/Web3ImplSnapshotTest.java b/rskj-core/src/test/java/org/ethereum/rpc/Web3ImplSnapshotTest.java index 49a4cabb19c..8140c5d2358 100644 --- a/rskj-core/src/test/java/org/ethereum/rpc/Web3ImplSnapshotTest.java +++ b/rskj-core/src/test/java/org/ethereum/rpc/Web3ImplSnapshotTest.java @@ -29,6 +29,9 @@ import co.rsk.mine.gas.provider.FixedMinGasPriceProvider; import co.rsk.rpc.modules.debug.DebugModule; import co.rsk.rpc.modules.debug.DebugModuleImpl; +import co.rsk.rpc.modules.debug.trace.DebugTracer; +import co.rsk.rpc.modules.debug.trace.RskTracer; +import co.rsk.rpc.modules.debug.trace.TraceProvider; import co.rsk.rpc.modules.evm.EvmModule; import co.rsk.rpc.modules.evm.EvmModuleImpl; import co.rsk.rpc.modules.personal.PersonalModule; @@ -174,8 +177,9 @@ private Web3Impl createWeb3(SimpleEthereum ethereum) { ); PersonalModule pm = new PersonalModuleWalletDisabled(); TxPoolModule tpm = new TxPoolModuleImpl(Web3Mocks.getMockTransactionPool(), new ReceivedTxSignatureCache()); - DebugModule dm = new DebugModuleImpl(null, null, Web3Mocks.getMockMessageHandler(), null, null, null); - + DebugTracer debugTracer = new RskTracer(null, null, null, null); + TraceProvider traceProvider = new TraceProvider(List.of(debugTracer)); + DebugModule dm = new DebugModuleImpl(traceProvider, Web3Mocks.getMockMessageHandler(), null); ethereum.blockchain = blockchain; return new Web3Impl( diff --git a/rskj-core/src/test/java/org/ethereum/rpc/Web3ImplTest.java b/rskj-core/src/test/java/org/ethereum/rpc/Web3ImplTest.java index 9c498a3a35a..decc6c37fec 100644 --- a/rskj-core/src/test/java/org/ethereum/rpc/Web3ImplTest.java +++ b/rskj-core/src/test/java/org/ethereum/rpc/Web3ImplTest.java @@ -42,6 +42,9 @@ import co.rsk.rpc.Web3RskImpl; import co.rsk.rpc.modules.debug.DebugModule; import co.rsk.rpc.modules.debug.DebugModuleImpl; +import co.rsk.rpc.modules.debug.trace.DebugTracer; +import co.rsk.rpc.modules.debug.trace.RskTracer; +import co.rsk.rpc.modules.debug.trace.TraceProvider; import co.rsk.rpc.modules.eth.EthModule; import co.rsk.rpc.modules.eth.EthModuleTransactionBase; import co.rsk.rpc.modules.eth.EthModuleWalletEnabled; @@ -749,7 +752,9 @@ void eth_mining() { MinerClient minerClient = new SimpleMinerClient(); PersonalModule personalModule = new PersonalModuleWalletDisabled(); TxPoolModule txPoolModule = new TxPoolModuleImpl(Web3Mocks.getMockTransactionPool(), signatureCache); - DebugModule debugModule = new DebugModuleImpl(null, null, Web3Mocks.getMockMessageHandler(), null, null, null); + DebugTracer debugTracer = new RskTracer(null, null, null, null); + TraceProvider traceProvider = new TraceProvider(List.of(debugTracer)); + DebugModule debugModule = new DebugModuleImpl(traceProvider, Web3Mocks.getMockMessageHandler(), null); Web3 web3 = new Web3Impl( ethMock, blockchain, @@ -2773,7 +2778,9 @@ null, new EthModuleWalletEnabled(wallet, transactionPool, signatureCache), null, config.getCallGasCap() ); TxPoolModule txPoolModule = new TxPoolModuleImpl(Web3Mocks.getMockTransactionPool(), signatureCache); - DebugModule debugModule = new DebugModuleImpl(null, null, Web3Mocks.getMockMessageHandler(), null, null, null); + DebugTracer debugTracer = new RskTracer(null, null, null, null); + TraceProvider traceProvider = new TraceProvider(List.of(debugTracer)); + DebugModule debugModule = new DebugModuleImpl(traceProvider, Web3Mocks.getMockMessageHandler(), null); MinerClient minerClient = new SimpleMinerClient(); ChannelManager channelManager = new SimpleChannelManager(); return new Web3RskImpl( @@ -2891,7 +2898,9 @@ private Web3Impl createWeb3( config.getCallGasCap() ); TxPoolModule txPoolModule = new TxPoolModuleImpl(transactionPool, signatureCache); - DebugModule debugModule = new DebugModuleImpl(null, null, Web3Mocks.getMockMessageHandler(), null, null, null); + DebugTracer debugTracer = new RskTracer(null, null, null, null); + TraceProvider traceProvider = new TraceProvider(List.of(debugTracer)); + DebugModule debugModule = new DebugModuleImpl(traceProvider, Web3Mocks.getMockMessageHandler(), null); RskModule rskModule = new RskModuleImpl(blockchain, blockStore, receiptStore, retriever, flusher, nodeStopper); MinerClient minerClient = new SimpleMinerClient(); ChannelManager channelManager = new SimpleChannelManager(); @@ -2954,7 +2963,9 @@ private Web3Impl createWeb3CallNoReturn( config.getGasEstimationCap(), config.getCallGasCap()); TxPoolModule txPoolModule = new TxPoolModuleImpl(transactionPool, signatureCache); - DebugModule debugModule = new DebugModuleImpl(null, null, Web3Mocks.getMockMessageHandler(), null, null, null); + DebugTracer debugTracer = new RskTracer(null, null, null, null); + TraceProvider traceProvider = new TraceProvider(List.of(debugTracer)); + DebugModule debugModule = new DebugModuleImpl(traceProvider, Web3Mocks.getMockMessageHandler(), null); RskModule rskModule = new RskModuleImpl(blockchain, blockStore, receiptStore, retriever, mock(Flusher.class)); MinerClient minerClient = new SimpleMinerClient(); ChannelManager channelManager = new SimpleChannelManager(); From 61a2b5dc77f0d5c6e46002741eb1ff22355effa9 Mon Sep 17 00:00:00 2001 From: Angel Soto Date: Thu, 21 Nov 2024 21:07:34 +0100 Subject: [PATCH 2/8] Adding callTracer tracer --- .../src/main/java/co/rsk/RskContext.java | 15 +- .../main/java/co/rsk/rpc/Web3DebugModule.java | 9 +- .../co/rsk/rpc/modules/debug/DebugModule.java | 6 +- .../rpc/modules/debug/DebugModuleImpl.java | 73 +++--- .../rpc/modules/debug/trace/CallTracer.java | 48 ---- .../rpc/modules/debug/trace/DebugTracer.java | 2 +- .../rpc/modules/debug/trace/TracerConfig.java | 25 -- .../trace/call/CallTraceTransformer.java | 167 ++++++++++++++ .../modules/debug/trace/call/CallTracer.java | 176 +++++++++++++++ .../debug/trace/call/TransactionTrace.java | 41 ++++ .../debug/trace/call/TxTraceResult.java | 213 ++++++++++++++++++ .../rpc/parameters/DebugTracerParam.java | 10 +- .../java/org/ethereum/vm/program/Program.java | 3 +- rskj-core/src/main/resources/reference.conf | 2 +- .../modules/debug/DebugModuleImplTest.java | 6 +- 15 files changed, 656 insertions(+), 140 deletions(-) delete mode 100644 rskj-core/src/main/java/co/rsk/rpc/modules/debug/trace/CallTracer.java delete mode 100644 rskj-core/src/main/java/co/rsk/rpc/modules/debug/trace/TracerConfig.java create mode 100644 rskj-core/src/main/java/co/rsk/rpc/modules/debug/trace/call/CallTraceTransformer.java create mode 100644 rskj-core/src/main/java/co/rsk/rpc/modules/debug/trace/call/CallTracer.java create mode 100644 rskj-core/src/main/java/co/rsk/rpc/modules/debug/trace/call/TransactionTrace.java create mode 100644 rskj-core/src/main/java/co/rsk/rpc/modules/debug/trace/call/TxTraceResult.java diff --git a/rskj-core/src/main/java/co/rsk/RskContext.java b/rskj-core/src/main/java/co/rsk/RskContext.java index 079cd5e3e26..70d16a00351 100644 --- a/rskj-core/src/main/java/co/rsk/RskContext.java +++ b/rskj-core/src/main/java/co/rsk/RskContext.java @@ -63,9 +63,9 @@ import co.rsk.rpc.*; import co.rsk.rpc.modules.debug.DebugModule; import co.rsk.rpc.modules.debug.DebugModuleImpl; -import co.rsk.rpc.modules.debug.trace.CallTracer; import co.rsk.rpc.modules.debug.trace.RskTracer; import co.rsk.rpc.modules.debug.trace.TraceProvider; +import co.rsk.rpc.modules.debug.trace.call.CallTracer; import co.rsk.rpc.modules.eth.*; import co.rsk.rpc.modules.eth.subscribe.BlockHeaderNotificationEmitter; import co.rsk.rpc.modules.eth.subscribe.LogsNotificationEmitter; @@ -107,6 +107,7 @@ import org.ethereum.crypto.ECKey; import org.ethereum.crypto.signature.Secp256k1; import org.ethereum.datasource.*; +import org.ethereum.db.BlockStore; import org.ethereum.db.IndexedBlockStore; import org.ethereum.db.ReceiptStore; import org.ethereum.db.ReceiptStoreImplV2; @@ -814,13 +815,17 @@ public synchronized ConfigCapabilities getConfigCapabilities() { public synchronized DebugModule getDebugModule() { checkIfNotClosed(); - RskTracer rskTracer = new RskTracer(getBlockStore(), getReceiptStore(), - getBlockExecutor(), getWeb3InformationRetriever()); - CallTracer callTracer = new CallTracer(); + Web3InformationRetriever web3i = getWeb3InformationRetriever(); + BlockStore bs = getBlockStore(); + BlockExecutor be = getBlockExecutor(); + RskTracer rskTracer = new RskTracer(bs, getReceiptStore(), + be, web3i); + + CallTracer callTracer = new CallTracer(bs, be, web3i, getReceiptStore(), getBlockchain()); TraceProvider traceProvider = new TraceProvider(Arrays.asList(callTracer, rskTracer)); if (debugModule == null) { - debugModule = new DebugModuleImpl(traceProvider,getNodeMessageHandler(),getTxQuotaChecker()); + debugModule = new DebugModuleImpl(traceProvider, getNodeMessageHandler(), getTxQuotaChecker()); } return debugModule; diff --git a/rskj-core/src/main/java/co/rsk/rpc/Web3DebugModule.java b/rskj-core/src/main/java/co/rsk/rpc/Web3DebugModule.java index 2e49fd3eb56..98074fc4ac8 100644 --- a/rskj-core/src/main/java/co/rsk/rpc/Web3DebugModule.java +++ b/rskj-core/src/main/java/co/rsk/rpc/Web3DebugModule.java @@ -23,8 +23,6 @@ import com.fasterxml.jackson.databind.JsonNode; import org.ethereum.rpc.parameters.DebugTracerParam; -import java.util.Map; - @java.lang.SuppressWarnings("squid:S100") public interface Web3DebugModule { @@ -49,11 +47,12 @@ default JsonNode debug_traceBlockByHash(String blockHash, DebugTracerParam debug } default JsonNode debug_traceBlockByNumber(String bnOrId) throws Exception { - return debug_traceBlockByNumber(bnOrId, null); + + return debug_traceBlockByNumber(bnOrId, new DebugTracerParam()); } - default JsonNode debug_traceBlockByNumber(String bnOrId, Map traceOptions) throws Exception { - return getDebugModule().traceBlockByNumber(bnOrId, traceOptions); + default JsonNode debug_traceBlockByNumber(String bnOrId, DebugTracerParam debugTracerParam) throws Exception { + return getDebugModule().traceBlockByNumber(bnOrId, debugTracerParam.getTraceOptions(), debugTracerParam.getTracerType()); } default TxQuota debug_accountTransactionQuota(String address) { diff --git a/rskj-core/src/main/java/co/rsk/rpc/modules/debug/DebugModule.java b/rskj-core/src/main/java/co/rsk/rpc/modules/debug/DebugModule.java index 6d499084423..ead881a9b3e 100644 --- a/rskj-core/src/main/java/co/rsk/rpc/modules/debug/DebugModule.java +++ b/rskj-core/src/main/java/co/rsk/rpc/modules/debug/DebugModule.java @@ -22,8 +22,6 @@ import co.rsk.rpc.modules.debug.trace.TracerType; import com.fasterxml.jackson.databind.JsonNode; -import java.util.Map; - public interface DebugModule { String wireProtocolQueueSize(); @@ -35,9 +33,7 @@ public interface DebugModule { JsonNode traceBlockByHash(String blockHash, TraceOptions traceOptions, TracerType tracerType) throws Exception; JsonNode traceBlockByHash(String blockHash) throws Exception; - JsonNode traceBlockByNumber(String bnOrId, Map traceOptions) throws Exception; - - JsonNode traceBlockByNumber(String bnOrId, Map traceOptions, TracerType tracerType) throws Exception; + JsonNode traceBlockByNumber(String bnOrId, TraceOptions traceOptions, TracerType tracerType) throws Exception; TxQuota accountTransactionQuota(String address); } diff --git a/rskj-core/src/main/java/co/rsk/rpc/modules/debug/DebugModuleImpl.java b/rskj-core/src/main/java/co/rsk/rpc/modules/debug/DebugModuleImpl.java index 2d1d9007940..ff99323790e 100644 --- a/rskj-core/src/main/java/co/rsk/rpc/modules/debug/DebugModuleImpl.java +++ b/rskj-core/src/main/java/co/rsk/rpc/modules/debug/DebugModuleImpl.java @@ -31,9 +31,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.Map; -import java.util.Optional; - public class DebugModuleImpl implements DebugModule { //this could be configurable public static final TracerType DEFAULT_TRACER_TYPE = TracerType.RSK_TRACER; @@ -57,39 +54,43 @@ public String wireProtocolQueueSize() { @Override public TxQuota accountTransactionQuota(String address) { - logger.trace("debug_accountTransactionQuota({})", StringUtils.trim(address)); + if (logger.isTraceEnabled()) { + logger.trace("debug_accountTransactionQuota({})", StringUtils.trim(address)); + } RskAddress rskAddress = new RskAddress(address); return txQuotaChecker.getTxQuota(rskAddress); } @Override - public JsonNode traceTransaction(String transactionHash) { + public JsonNode traceTransaction(String transactionHash) throws Exception { return traceTransaction(transactionHash, new TraceOptions(), null); } @Override - public JsonNode traceTransaction(String transactionHash, TraceOptions traceOptions, TracerType tracerType) { - if (tracerType == null) { - tracerType = DEFAULT_TRACER_TYPE; - } + public JsonNode traceTransaction(String transactionHash, TraceOptions traceOptions, TracerType tracerType) throws Exception { + TracerType type = getTracerTypeOrDefault(tracerType); + if (traceOptions == null) { traceOptions = new TraceOptions(); } - DebugTracer tracer = traceProvider.getTracer(tracerType); - logger.trace("debug_traceTransaction for txHash: {}", StringUtils.trim(transactionHash)); + DebugTracer tracer = traceProvider.getTracer(type); + if (logger.isTraceEnabled()) { + logger.trace("debug_traceTransaction for txHash: {}", StringUtils.trim(transactionHash)); + } return tracer.traceTransaction(transactionHash, traceOptions); } @Override public JsonNode traceBlockByHash(String blockHash, TraceOptions traceOptions, TracerType tracerType) { - if (tracerType == null) { - tracerType = DEFAULT_TRACER_TYPE; - } + TracerType type = getTracerTypeOrDefault(tracerType); + if (traceOptions == null) { traceOptions = new TraceOptions(); } - logger.trace("debug_traceBlockByHash for blockHash: {}", StringUtils.trim(blockHash)); - DebugTracer tracer = traceProvider.getTracer(tracerType); + if (logger.isTraceEnabled()) { + logger.trace("debug_traceBlockByHash for blockHash: {}", StringUtils.trim(blockHash)); + } + DebugTracer tracer = traceProvider.getTracer(type); return tracer.traceBlockByHash(blockHash, traceOptions); } @@ -100,35 +101,23 @@ public JsonNode traceBlockByHash(String blockHash) throws Exception { @Override - public JsonNode traceBlockByNumber(String bnOrId, Map traceOptions) throws Exception { - return traceBlockByNumber(bnOrId, traceOptions, DEFAULT_TRACER_TYPE); - } - - @Override - public JsonNode traceBlockByNumber(String bnOrId, Map traceOptions, TracerType tracerType) throws Exception { - logger.trace("debug_traceBlockByNumber for bnOrId: {}", StringUtils.trim(bnOrId)); - DebugTracer tracer = traceProvider.getTracer(tracerType); - TraceOptions options = toTraceOptions(traceOptions); - return tracer.traceBlockByNumber(bnOrId, options); - } - - private TraceOptions toTraceOptions(Map traceOptions) { - TraceOptions options = new TraceOptions(traceOptions); - - if (!options.getUnsupportedOptions().isEmpty()) { - // TODO: implement the logic that takes into account the remaining trace options. - logger.warn("Received {} unsupported trace options", options.getUnsupportedOptions().size()); + public JsonNode traceBlockByNumber(String bnOrId, TraceOptions traceOptions, TracerType tracerType) throws Exception { + TracerType type = getTracerTypeOrDefault(tracerType); + if (traceOptions == null) { + traceOptions = new TraceOptions(); } - - return options; + if (logger.isTraceEnabled()) { + logger.trace("debug_traceBlockByNumber for bnOrId: {}", StringUtils.trim(bnOrId)); + } + DebugTracer tracer = traceProvider.getTracer(type); + return tracer.traceBlockByNumber(bnOrId, traceOptions); } - private TracerType getTracerType(Map options) { - if (options.containsKey("tracer")) { - Optional.ofNullable(TracerType.getTracerType(options.get("tracer"))) - .orElseThrow(() -> new IllegalArgumentException("Invalid tracer type: " + options.get("tracer"))); + private TracerType getTracerTypeOrDefault(TracerType tracerType) { + //TODO review about this default tracer logic + if (tracerType == null) { + return DEFAULT_TRACER_TYPE; } - return DEFAULT_TRACER_TYPE; + return tracerType; } - } diff --git a/rskj-core/src/main/java/co/rsk/rpc/modules/debug/trace/CallTracer.java b/rskj-core/src/main/java/co/rsk/rpc/modules/debug/trace/CallTracer.java deleted file mode 100644 index 9b60881a474..00000000000 --- a/rskj-core/src/main/java/co/rsk/rpc/modules/debug/trace/CallTracer.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * This file is part of RskJ - * Copyright (C) 2024 RSK Labs Ltd. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program. If not, see . - */ -package co.rsk.rpc.modules.debug.trace; - -import co.rsk.rpc.modules.debug.TraceOptions; -import com.fasterxml.jackson.databind.JsonNode; - -public class CallTracer implements DebugTracer { - - public static final String UNSUPPORTED_OPERATION = "Operation not supported by this tracer."; - - @Override - public JsonNode traceTransaction(String transactionHash, TraceOptions traceOptions) { - throw new UnsupportedOperationException(UNSUPPORTED_OPERATION); - } - - @Override - public JsonNode traceBlockByHash(String blockHash, TraceOptions traceOptions) { - throw new UnsupportedOperationException(UNSUPPORTED_OPERATION); - - } - - @Override - public JsonNode traceBlockByNumber(String bnOrId, TraceOptions traceOptions) { - throw new UnsupportedOperationException(UNSUPPORTED_OPERATION); - - } - - @Override - public TracerType getTracerType() { - return TracerType.CALL_TRACER; - } -} diff --git a/rskj-core/src/main/java/co/rsk/rpc/modules/debug/trace/DebugTracer.java b/rskj-core/src/main/java/co/rsk/rpc/modules/debug/trace/DebugTracer.java index bf938f7b0f9..02ea22aaff0 100644 --- a/rskj-core/src/main/java/co/rsk/rpc/modules/debug/trace/DebugTracer.java +++ b/rskj-core/src/main/java/co/rsk/rpc/modules/debug/trace/DebugTracer.java @@ -21,7 +21,7 @@ import com.fasterxml.jackson.databind.JsonNode; public interface DebugTracer { - JsonNode traceTransaction(String transactionHash, TraceOptions traceOptions); + JsonNode traceTransaction(String transactionHash, TraceOptions traceOptions) throws Exception; JsonNode traceBlockByHash(String blockHash, TraceOptions traceOptions); diff --git a/rskj-core/src/main/java/co/rsk/rpc/modules/debug/trace/TracerConfig.java b/rskj-core/src/main/java/co/rsk/rpc/modules/debug/trace/TracerConfig.java deleted file mode 100644 index 61b56138792..00000000000 --- a/rskj-core/src/main/java/co/rsk/rpc/modules/debug/trace/TracerConfig.java +++ /dev/null @@ -1,25 +0,0 @@ -package co.rsk.rpc.modules.debug.trace; - -public class TracerConfig { - - public TracerConfig(boolean onlyTopCall, boolean diffMode) { - this.onlyTopCall = onlyTopCall; - this.diffMode = diffMode; - } - public TracerConfig() { - this(false, false); - } - private boolean onlyTopCall; - private boolean diffMode; - - public boolean isOnlyTopCall() { - return onlyTopCall; - } - - public boolean isDiffMode() { - return diffMode; - } - - - -} diff --git a/rskj-core/src/main/java/co/rsk/rpc/modules/debug/trace/call/CallTraceTransformer.java b/rskj-core/src/main/java/co/rsk/rpc/modules/debug/trace/call/CallTraceTransformer.java new file mode 100644 index 00000000000..6e303906079 --- /dev/null +++ b/rskj-core/src/main/java/co/rsk/rpc/modules/debug/trace/call/CallTraceTransformer.java @@ -0,0 +1,167 @@ +/* + * This file is part of RskJ + * Copyright (C) 2024 RSK Labs Ltd. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ +package co.rsk.rpc.modules.debug.trace.call; + +import co.rsk.core.RskAddress; +import co.rsk.rpc.modules.eth.EthModule; +import co.rsk.rpc.modules.trace.CallType; +import co.rsk.rpc.modules.trace.CreationData; +import co.rsk.rpc.modules.trace.ProgramSubtrace; +import co.rsk.rpc.modules.trace.TraceType; +import co.rsk.util.HexUtils; +import org.apache.commons.lang3.tuple.Pair; +import org.bouncycastle.util.encoders.Hex; +import org.ethereum.db.TransactionInfo; +import org.ethereum.vm.DataWord; +import org.ethereum.vm.program.ProgramResult; +import org.ethereum.vm.program.invoke.InvokeData; +import org.ethereum.vm.trace.SummarizedProgramTrace; + +import java.math.BigInteger; + +public class CallTraceTransformer { + + private CallTraceTransformer() { + } + + public static TransactionTrace toTrace(SummarizedProgramTrace trace, TransactionInfo txInfo, DataWord codeAddress) { + boolean isContractCreation = txInfo.getReceipt().getTransaction().isContractCreation(); + CallType callType = isContractCreation ? CallType.NONE : CallType.CALL; + + ProgramResult programResult = ProgramResult.empty(); + programResult.spendGas(new BigInteger(1, txInfo.getReceipt().getGasUsed()).longValue()); + + if (trace.getReverted()) { + programResult.setRevert(); + } + InvokeData invoke = trace.getInvokeData(); + + CreationData creationData = null; + + TraceType traceType = TraceType.CALL; + + if (isContractCreation) { + String outputText = trace.getResult(); + byte[] createdCode = outputText == null ? new byte[0] : Hex.decode(outputText); + RskAddress createdAddress = txInfo.getReceipt().getTransaction().getContractAddress(); + byte[] input = txInfo.getReceipt().getTransaction().getData(); + creationData = new CreationData(input, createdCode, createdAddress); + traceType = TraceType.CREATE; + } + + TxTraceResult traceOutput = toTrace(traceType, callType, invoke, codeAddress, programResult, creationData); + for (ProgramSubtrace subtrace : trace.getSubtraces()) { + traceOutput.addCall(toTrace(subtrace)); + } + return new TransactionTrace(txInfo.getReceipt().getTransaction().getHash().toHexString(), traceOutput); + } + + private static TxTraceResult toTrace(ProgramSubtrace programSubtrace) { + InvokeData invokeData = programSubtrace.getInvokeData(); + TxTraceResult subTrace = toTrace(programSubtrace.getTraceType(), programSubtrace.getCallType(), invokeData, programSubtrace.getCodeAddress(), programSubtrace.getProgramResult(), programSubtrace.getCreationData()); + for (ProgramSubtrace call : programSubtrace.getSubtraces()) { + subTrace.addCall(toTrace(call)); + } + return subTrace; + } + + private static TxTraceResult toTrace(TraceType traceType, CallType callType, InvokeData invoke, DataWord codeAddress, ProgramResult programResult, CreationData creationData) { + String type = traceType == TraceType.CREATE ? "CREATE" : callType.name(); + String from; + if (callType == CallType.DELEGATECALL) { + from = new RskAddress(invoke.getOwnerAddress().getLast20Bytes()).toJsonString(); + } else { + from = new RskAddress(invoke.getCallerAddress().getLast20Bytes()).toJsonString(); + } + String to = null; + String gas = null; + + DataWord callValue = invoke.getCallValue(); + //TODO input data is missing in TransferInvoke + String input = null; + String value = null; + String output = null; + + + if (traceType == TraceType.CREATE) { + if (creationData != null) { + input = HexUtils.toJsonHex(creationData.getCreationInput()); + output = creationData.getCreatedAddress().toJsonString(); + } + value = HexUtils.toQuantityJsonHex(callValue.getData()); + gas = HexUtils.toQuantityJsonHex(invoke.getGas()); + } + + if (traceType == TraceType.CALL) { + input = HexUtils.toJsonHex(invoke.getDataCopy(DataWord.ZERO, invoke.getDataSize())); + value = HexUtils.toQuantityJsonHex(callValue.getData()); + + if (callType == CallType.DELEGATECALL) { + // The code address should not be null in a DELEGATECALL case + // but handling the case here + if (codeAddress != null) { + to = new RskAddress(codeAddress.getLast20Bytes()).toJsonString(); + } + } else { + to = new RskAddress(invoke.getOwnerAddress().getLast20Bytes()).toJsonString(); + } + + gas = HexUtils.toQuantityJsonHex(invoke.getGas()); + + } + + String gasUsed = null; + + String revertReason = null; + + + String error = null; + + if (programResult != null) { + gasUsed = HexUtils.toQuantityJsonHex(programResult.getGasUsed()); + + if (programResult.isRevert()) { + Pair programRevert = EthModule.decodeProgramRevert(programResult); + revertReason = programRevert.getLeft(); + output = HexUtils.toJsonHex(programRevert.getRight()); + + error = "execution reverted"; + } else if (traceType != TraceType.CREATE) { + output = HexUtils.toJsonHex(programResult.getHReturn()); + } + if (programResult.getException() != null) { + error = programResult.getException().toString(); + } + } + + return TxTraceResult.builder() + .type(type) + .from(from) + .to(to) + .value(value) + .gas(gas) + .input(input) + .gasUsed(gasUsed) + .output(output) + .revertReason(revertReason) + .error(error) + .build(); + + } + +} diff --git a/rskj-core/src/main/java/co/rsk/rpc/modules/debug/trace/call/CallTracer.java b/rskj-core/src/main/java/co/rsk/rpc/modules/debug/trace/call/CallTracer.java new file mode 100644 index 00000000000..c09a542053e --- /dev/null +++ b/rskj-core/src/main/java/co/rsk/rpc/modules/debug/trace/call/CallTracer.java @@ -0,0 +1,176 @@ +/* + * This file is part of RskJ + * Copyright (C) 2024 RSK Labs Ltd. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ +package co.rsk.rpc.modules.debug.trace.call; + +import co.rsk.config.VmConfig; +import co.rsk.core.bc.BlockExecutor; +import co.rsk.rpc.Web3InformationRetriever; +import co.rsk.rpc.modules.debug.TraceOptions; +import co.rsk.rpc.modules.debug.trace.DebugTracer; +import co.rsk.rpc.modules.debug.trace.TracerType; +import co.rsk.util.HexUtils; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.commons.lang3.StringUtils; +import org.ethereum.core.Block; +import org.ethereum.core.Blockchain; +import org.ethereum.core.Transaction; +import org.ethereum.core.TransactionReceipt; +import org.ethereum.db.BlockStore; +import org.ethereum.db.ReceiptStore; +import org.ethereum.db.TransactionInfo; +import org.ethereum.vm.trace.ProgramTraceProcessor; +import org.ethereum.vm.trace.Serializers; +import org.ethereum.vm.trace.SummarizedProgramTrace; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class CallTracer implements DebugTracer { + + public static final String UNSUPPORTED_OPERATION = "Operation not supported by this tracer."; + private static final Logger logger = LoggerFactory.getLogger("callTracer"); + private static final ObjectMapper OBJECT_MAPPER = Serializers.createMapper(true); + + private final Web3InformationRetriever web3InformationRetriever; + private final BlockStore blockStore; + private final BlockExecutor blockExecutor; + private final ReceiptStore receiptStore; + private final Blockchain blockchain; + + public CallTracer(BlockStore blockStore, BlockExecutor blockExecutor, Web3InformationRetriever web3InformationRetriever, ReceiptStore receiptStore, Blockchain blockchain) { + this.blockStore = blockStore; + this.blockExecutor = blockExecutor; + this.web3InformationRetriever = web3InformationRetriever; + this.receiptStore = receiptStore; + this.blockchain = blockchain; + } + + @Override + public JsonNode traceTransaction(String transactionHash, TraceOptions traceOptions) throws Exception { + logger.trace("trace_transaction({})", transactionHash); + if (traceOptions != null) { + logger.warn("Trace Options not supported yet"); + } + + byte[] hash = HexUtils.stringHexToByteArray(transactionHash); + TransactionInfo txInfo = this.receiptStore.getInMainChain(hash, this.blockStore).orElse(null); + + if (txInfo == null) { + logger.trace("No transaction info for {}", transactionHash); + return null; + } + + Block block = this.blockchain.getBlockByHash(txInfo.getBlockHash()); + Block parent = this.blockchain.getBlockByHash(block.getParentHash().getBytes()); + Transaction tx = block.getTransactionsList().get(txInfo.getIndex()); + txInfo.setTransaction(tx); + + ProgramTraceProcessor programTraceProcessor = new ProgramTraceProcessor(); + this.blockExecutor.traceBlock(programTraceProcessor, VmConfig.LIGHT_TRACE, block, parent.getHeader(), false, false); + + SummarizedProgramTrace programTrace = (SummarizedProgramTrace) programTraceProcessor.getProgramTrace(tx.getHash()); + + if (programTrace == null) { + //TODO define and return proper exception + logger.error("No program trace could be obtained for transaction: {}", transactionHash); + return null; + } + + TransactionTrace trace = CallTraceTransformer.toTrace(programTrace, txInfo, null); + return OBJECT_MAPPER.valueToTree(trace.getResult()); + } + + @Override + public JsonNode traceBlockByHash(String blockHash, TraceOptions traceOptions) { + byte[] bHash = HexUtils.stringHexToByteArray(blockHash); + Block block = blockStore.getBlockByHash(bHash); + if (block == null) { + if (logger.isTraceEnabled()) { + logger.trace("No block is found for blockHash: {}", StringUtils.trim(blockHash)); + } + return null; + } + return traceBlock(block, traceOptions); + } + + @Override + public JsonNode traceBlockByNumber(String bnOrId, TraceOptions traceOptions) { + Block block = web3InformationRetriever.getBlock(bnOrId).orElse(null); + if (block == null) { + if (logger.isTraceEnabled()) { + logger.trace("No block is found for bnOrId: {}", StringUtils.trim(bnOrId)); + } + return null; + } + + return traceBlock(block, traceOptions); + } + + @Override + public TracerType getTracerType() { + return TracerType.CALL_TRACER; + } + + private JsonNode traceBlock(Block block, TraceOptions traceOptions) { + if (traceOptions != null) { + logger.warn("Trace Options not supported yet"); + } + List result = buildBlockTraces(block); + return OBJECT_MAPPER.valueToTree(result); + } + + private List buildBlockTraces(Block block) { + List blockTraces = new ArrayList<>(); + + if (block != null && block.getNumber() != 0) { + List txList = block.getTransactionsList(); + + ProgramTraceProcessor programTraceProcessor = new ProgramTraceProcessor(); + Block parent = this.blockchain.getBlockByHash(block.getParentHash().getBytes()); + this.blockExecutor.traceBlock(programTraceProcessor, VmConfig.LIGHT_TRACE, block, parent.getHeader(), false, false); + + + for (Transaction tx : txList) { + TransactionInfo txInfo = receiptStore.getInMainChain(tx.getHash().getBytes(), this.blockStore).orElse(null); + if (txInfo == null) { // for a pending block we have no receipt, so empty one is being provided + txInfo = new TransactionInfo(new TransactionReceipt(), block.getHash().getBytes(), block.getTransactionsList().indexOf(tx)); + } + txInfo.setTransaction(tx); + + SummarizedProgramTrace programTrace = (SummarizedProgramTrace) programTraceProcessor.getProgramTrace(tx.getHash()); + + if (programTrace == null) { + blockTraces.clear(); + return Collections.emptyList(); + } + + TransactionTrace trace = CallTraceTransformer.toTrace(programTrace, txInfo, null); + + blockTraces.add(trace); + } + } + + return blockTraces; + } + + +} diff --git a/rskj-core/src/main/java/co/rsk/rpc/modules/debug/trace/call/TransactionTrace.java b/rskj-core/src/main/java/co/rsk/rpc/modules/debug/trace/call/TransactionTrace.java new file mode 100644 index 00000000000..86cd3e5c271 --- /dev/null +++ b/rskj-core/src/main/java/co/rsk/rpc/modules/debug/trace/call/TransactionTrace.java @@ -0,0 +1,41 @@ +/* + * This file is part of RskJ + * Copyright (C) 2024 RSK Labs Ltd. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ +package co.rsk.rpc.modules.debug.trace.call; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public class TransactionTrace { + + private String txHash; + private TxTraceResult result; + + public TransactionTrace(String txHash, TxTraceResult result) { + this.txHash = txHash; + this.result = result; + } + + @JsonProperty("txHash") + public String getTxHash() { + return txHash; + } + + @JsonProperty("result") + public TxTraceResult getResult() { + return result; + } +} diff --git a/rskj-core/src/main/java/co/rsk/rpc/modules/debug/trace/call/TxTraceResult.java b/rskj-core/src/main/java/co/rsk/rpc/modules/debug/trace/call/TxTraceResult.java new file mode 100644 index 00000000000..1b313ed3249 --- /dev/null +++ b/rskj-core/src/main/java/co/rsk/rpc/modules/debug/trace/call/TxTraceResult.java @@ -0,0 +1,213 @@ +/* + * This file is part of RskJ + * Copyright (C) 2024 RSK Labs Ltd. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ +package co.rsk.rpc.modules.debug.trace.call; + +import com.fasterxml.jackson.annotation.JsonGetter; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonInclude; + +import java.util.ArrayList; +import java.util.List; + +@JsonInclude(JsonInclude.Include.NON_NULL) +public class TxTraceResult { + + private final String type; + private final String from; + private final String to; + private final String value; + private final String gas; + private final String gasUsed; + private final String input; + private final String output; + private final String error; + private final String revertReason; + private final List calls; + private final List logs; + + public TxTraceResult(String type, String from, String to, String value, String gas, String gasUsed, String input, String output, String error, String revertReason, List calls, List logs) { + this.type = type; + this.from = from; + this.to = to; + this.value = value; + this.gas = gas; + this.gasUsed = gasUsed; + this.input = input; + this.output = output; + this.error = error; + this.revertReason = revertReason; + this.calls = calls == null ? new ArrayList<>() : calls; + this.logs = logs == null ? new ArrayList<>() : logs; + } + + @JsonGetter("type") + public String getType() { + return type; + } + + @JsonGetter("from") + public String getFrom() { + return from; + } + + @JsonGetter("to") + public String getTo() { + return to; + } + + @JsonGetter("value") + public String getValue() { + return value; + } + + @JsonGetter("gas") + public String getGas() { + return gas; + } + + @JsonGetter("gasUsed") + public String getGasUsed() { + return gasUsed; + } + + @JsonGetter("input") + public String getInput() { + return input; + } + + @JsonGetter("output") + public String getOutput() { + return output; + } + + @JsonGetter("error") + public String getError() { + return error; + } + + @JsonGetter("revertReason") + public String getRevertReason() { + return revertReason; + } + + @JsonGetter("calls") + public List getCalls() { + return calls.isEmpty() ? null : calls; + } + + //TODO + @JsonGetter("logs") + public List getLogs() { + return logs.isEmpty() ? null : logs; + } + + @JsonIgnore + public static Builder builder() { + return new Builder(); + } + + public void addCall(TxTraceResult call) { + calls.add(call); + } + + public void addAllCall(List calls) { + this.calls.addAll(calls); + } + + + //Builder class + public static class Builder { + private String type; + private String from; + private String to; + private String value; + private String gas; + private String gasUsed; + private String input; + private String output; + private String error; + private String revertReason; + private List calls; + private List logs; + + public Builder type(String type) { + this.type = type; + return this; + } + + public Builder from(String from) { + this.from = from; + return this; + } + + public Builder to(String to) { + this.to = to; + return this; + } + + public Builder value(String value) { + this.value = value; + return this; + } + + public Builder gas(String gas) { + this.gas = gas; + return this; + } + + public Builder gasUsed(String gasUsed) { + this.gasUsed = gasUsed; + return this; + } + + public Builder input(String input) { + this.input = input; + return this; + } + + public Builder output(String output) { + this.output = output; + return this; + } + + public Builder error(String error) { + this.error = error; + return this; + } + + public Builder revertReason(String revertReason) { + this.revertReason = revertReason; + return this; + } + + public Builder calls(List calls) { + this.calls = calls; + return this; + } + + public Builder logs(List logs) { + this.logs = logs; + return this; + } + + public TxTraceResult build() { + return new TxTraceResult(type, from, to, value, gas, gasUsed, input, output, error, revertReason, calls, logs); + } + } + +} diff --git a/rskj-core/src/main/java/org/ethereum/rpc/parameters/DebugTracerParam.java b/rskj-core/src/main/java/org/ethereum/rpc/parameters/DebugTracerParam.java index 716d551beab..f94564fac72 100644 --- a/rskj-core/src/main/java/org/ethereum/rpc/parameters/DebugTracerParam.java +++ b/rskj-core/src/main/java/org/ethereum/rpc/parameters/DebugTracerParam.java @@ -15,11 +15,14 @@ import java.util.Optional; @JsonDeserialize(using = DebugTracerParam.Deserializer.class) - public class DebugTracerParam { private final TracerType tracerType; private final TraceOptions traceOptions; + public DebugTracerParam() { + this(null, null); + } + public DebugTracerParam(TracerType tracerType, TraceOptions traceOptions) { this.tracerType = tracerType; this.traceOptions = traceOptions; @@ -62,15 +65,16 @@ public DebugTracerParam deserialize(JsonParser jp, DeserializationContext ctxt) JsonNode tracerConfigNode = entry.getValue(); tracerConfigNode.fields().forEachRemaining(tracerConfigEntry -> traceOptions.addOption(tracerConfigEntry.getKey(), tracerConfigEntry.getValue().asText())); - }else if("tracer".equalsIgnoreCase(entry.getKey())) { + } else if ("tracer".equalsIgnoreCase(entry.getKey())) { tracerType = getTracerType(entry.getValue().asText()); - }else { + } else { traceOptions.addOption(entry.getKey(), entry.getValue().asText()); } } return new DebugTracerParam(tracerType, traceOptions); } + //TODO check how this exeption is handled private TracerType getTracerType(String tracerType) { return Optional.ofNullable(TracerType.getTracerType(tracerType)).orElseThrow(() -> new IllegalArgumentException("Invalid tracer type: " + tracerType)); diff --git a/rskj-core/src/main/java/org/ethereum/vm/program/Program.java b/rskj-core/src/main/java/org/ethereum/vm/program/Program.java index e326c26a5cf..4573528fc8c 100644 --- a/rskj-core/src/main/java/org/ethereum/vm/program/Program.java +++ b/rskj-core/src/main/java/org/ethereum/vm/program/Program.java @@ -633,7 +633,7 @@ private ProgramResult getProgramResult(RskAddress senderAddress, byte[] nonce, D InternalTransaction internalTx = addInternalTx(nonce, getGasLimit(), senderAddress, RskAddress.nullAddress(), endowment, programCode, "create"); ProgramInvoke programInvoke = programInvokeFactory.createProgramInvoke( this, DataWord.valueOf(contractAddress.getBytes()), getOwnerAddress(), value, gasLimit, - newBalance, null, track, this.invoke.getBlockStore(), false, byTestingSuite()); + newBalance, programCode, track, this.invoke.getBlockStore(), false, byTestingSuite()); returnDataBuffer = null; // reset return buffer right before the call @@ -812,7 +812,6 @@ public void callToAddress(MessageCall msg) { DataWord callerAddress = DataWord.valueOf(senderAddress.getBytes()); DataWord ownerAddress = DataWord.valueOf(contextAddress.getBytes()); DataWord transferValue = DataWord.valueOf(endowment.getBytes()); - TransferInvoke invoke = new TransferInvoke(callerAddress, ownerAddress, msg.getGas().longValue(), transferValue); ProgramResult result = new ProgramResult(); diff --git a/rskj-core/src/main/resources/reference.conf b/rskj-core/src/main/resources/reference.conf index 1b1495a56ae..a7d71a2b4db 100644 --- a/rskj-core/src/main/resources/reference.conf +++ b/rskj-core/src/main/resources/reference.conf @@ -469,7 +469,7 @@ rpc { }, debug { version: "1.0", - enabled: "true" + enabled: "false" }, trace { version: "1.0", diff --git a/rskj-core/src/test/java/co/rsk/rpc/modules/debug/DebugModuleImplTest.java b/rskj-core/src/test/java/co/rsk/rpc/modules/debug/DebugModuleImplTest.java index 67d2f069f8d..1d36dc28d48 100644 --- a/rskj-core/src/test/java/co/rsk/rpc/modules/debug/DebugModuleImplTest.java +++ b/rskj-core/src/test/java/co/rsk/rpc/modules/debug/DebugModuleImplTest.java @@ -96,7 +96,7 @@ void debug_wireProtocolQueueSize_value() { } @Test - void debug_traceTransaction_retrieveUnknownTransactionAsNull() { + void debug_traceTransaction_retrieveUnknownTransactionAsNull() throws Exception { byte[] hash = HexUtils.stringHexToByteArray("0x00"); when(receiptStoreMock.getInMainChain(hash, blockStoreMock)).thenReturn(Optional.empty()); @@ -281,7 +281,7 @@ void debug_traceBlockByHash_retrieveSimpleContractsCreationTrace() throws Except void debug_traceBlockByNumber_retrieveUnknownBlockAsNull() throws Exception { when(web3InformationRetrieverMock.getBlock("0x1")).thenReturn(Optional.empty()); - JsonNode result = mockedDebugModule.traceBlockByNumber("0x1", null); + JsonNode result = mockedDebugModule.traceBlockByNumber("0x1", null, null); Assertions.assertNull(result); } @@ -301,7 +301,7 @@ void debug_traceBlockByNumber_retrieveSimpleContractsCreationTrace() throws Exce DebugModuleImpl debugModule = getDebugModule(world.getBlockStore(), world.getBlockExecutor(), receiptStore, messageHandlerMock, null, web3InformationRetrieverMock); - JsonNode result = debugModule.traceBlockByNumber(blockNumber, null); + JsonNode result = debugModule.traceBlockByNumber(blockNumber, null, null); Assertions.assertNotNull(result); Assertions.assertTrue(result.isArray()); From f4af3ca35cdc700fa52a64016666b79eb00d6147 Mon Sep 17 00:00:00 2001 From: Angel Soto Date: Thu, 21 Nov 2024 21:46:50 +0100 Subject: [PATCH 3/8] Adding onlyTopCall callTracer config option --- .../debug/trace/call/CallTraceTransformer.java | 9 ++++++--- .../rpc/modules/debug/trace/call/CallTracer.java | 16 +++++++--------- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/rskj-core/src/main/java/co/rsk/rpc/modules/debug/trace/call/CallTraceTransformer.java b/rskj-core/src/main/java/co/rsk/rpc/modules/debug/trace/call/CallTraceTransformer.java index 6e303906079..26ce8ff95e2 100644 --- a/rskj-core/src/main/java/co/rsk/rpc/modules/debug/trace/call/CallTraceTransformer.java +++ b/rskj-core/src/main/java/co/rsk/rpc/modules/debug/trace/call/CallTraceTransformer.java @@ -39,7 +39,7 @@ public class CallTraceTransformer { private CallTraceTransformer() { } - public static TransactionTrace toTrace(SummarizedProgramTrace trace, TransactionInfo txInfo, DataWord codeAddress) { + public static TransactionTrace toTrace(SummarizedProgramTrace trace, TransactionInfo txInfo, DataWord codeAddress, boolean onlyTopCall) { boolean isContractCreation = txInfo.getReceipt().getTransaction().isContractCreation(); CallType callType = isContractCreation ? CallType.NONE : CallType.CALL; @@ -65,9 +65,12 @@ public static TransactionTrace toTrace(SummarizedProgramTrace trace, Transaction } TxTraceResult traceOutput = toTrace(traceType, callType, invoke, codeAddress, programResult, creationData); - for (ProgramSubtrace subtrace : trace.getSubtraces()) { - traceOutput.addCall(toTrace(subtrace)); + if(!onlyTopCall) { + for (ProgramSubtrace subtrace : trace.getSubtraces()) { + traceOutput.addCall(toTrace(subtrace)); + } } + return new TransactionTrace(txInfo.getReceipt().getTransaction().getHash().toHexString(), traceOutput); } diff --git a/rskj-core/src/main/java/co/rsk/rpc/modules/debug/trace/call/CallTracer.java b/rskj-core/src/main/java/co/rsk/rpc/modules/debug/trace/call/CallTracer.java index c09a542053e..e5efe278529 100644 --- a/rskj-core/src/main/java/co/rsk/rpc/modules/debug/trace/call/CallTracer.java +++ b/rskj-core/src/main/java/co/rsk/rpc/modules/debug/trace/call/CallTracer.java @@ -40,6 +40,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import javax.annotation.Nonnull; import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -65,11 +66,8 @@ public CallTracer(BlockStore blockStore, BlockExecutor blockExecutor, Web3Inform } @Override - public JsonNode traceTransaction(String transactionHash, TraceOptions traceOptions) throws Exception { + public JsonNode traceTransaction(@Nonnull String transactionHash, @Nonnull TraceOptions traceOptions) throws Exception { logger.trace("trace_transaction({})", transactionHash); - if (traceOptions != null) { - logger.warn("Trace Options not supported yet"); - } byte[] hash = HexUtils.stringHexToByteArray(transactionHash); TransactionInfo txInfo = this.receiptStore.getInMainChain(hash, this.blockStore).orElse(null); @@ -95,7 +93,7 @@ public JsonNode traceTransaction(String transactionHash, TraceOptions traceOptio return null; } - TransactionTrace trace = CallTraceTransformer.toTrace(programTrace, txInfo, null); + TransactionTrace trace = CallTraceTransformer.toTrace(programTrace, txInfo, null, traceOptions.isOnlyTopCall()); return OBJECT_MAPPER.valueToTree(trace.getResult()); } @@ -113,7 +111,7 @@ public JsonNode traceBlockByHash(String blockHash, TraceOptions traceOptions) { } @Override - public JsonNode traceBlockByNumber(String bnOrId, TraceOptions traceOptions) { + public JsonNode traceBlockByNumber(@Nonnull String bnOrId, @Nonnull TraceOptions traceOptions) { Block block = web3InformationRetriever.getBlock(bnOrId).orElse(null); if (block == null) { if (logger.isTraceEnabled()) { @@ -134,11 +132,11 @@ private JsonNode traceBlock(Block block, TraceOptions traceOptions) { if (traceOptions != null) { logger.warn("Trace Options not supported yet"); } - List result = buildBlockTraces(block); + List result = buildBlockTraces(block, traceOptions.isOnlyTopCall()); return OBJECT_MAPPER.valueToTree(result); } - private List buildBlockTraces(Block block) { + private List buildBlockTraces(Block block, boolean onlyTopCall) { List blockTraces = new ArrayList<>(); if (block != null && block.getNumber() != 0) { @@ -163,7 +161,7 @@ private List buildBlockTraces(Block block) { return Collections.emptyList(); } - TransactionTrace trace = CallTraceTransformer.toTrace(programTrace, txInfo, null); + TransactionTrace trace = CallTraceTransformer.toTrace(programTrace, txInfo, null, onlyTopCall); blockTraces.add(trace); } From 0bdec480f578134e86ca742d4c0d7bd78d1ce6da Mon Sep 17 00:00:00 2001 From: Angel Soto Date: Tue, 26 Nov 2024 19:14:27 +0100 Subject: [PATCH 4/8] Adding withLogs parameter --- .../rsk/rpc/modules/debug/TraceOptions.java | 26 +------ .../trace/call/CallTraceTransformer.java | 74 +++++++++++++------ .../modules/debug/trace/call/CallTracer.java | 15 ++-- .../debug/trace/call/LogInfoResult.java | 44 +++++++++++ .../debug/trace/call/TxTraceResult.java | 16 ++-- 5 files changed, 111 insertions(+), 64 deletions(-) create mode 100644 rskj-core/src/main/java/co/rsk/rpc/modules/debug/trace/call/LogInfoResult.java diff --git a/rskj-core/src/main/java/co/rsk/rpc/modules/debug/TraceOptions.java b/rskj-core/src/main/java/co/rsk/rpc/modules/debug/TraceOptions.java index 819dd6da0fb..b6814b1c051 100644 --- a/rskj-core/src/main/java/co/rsk/rpc/modules/debug/TraceOptions.java +++ b/rskj-core/src/main/java/co/rsk/rpc/modules/debug/TraceOptions.java @@ -19,7 +19,6 @@ package co.rsk.rpc.modules.debug; -import co.rsk.rpc.modules.debug.trace.TracerType; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.JsonNode; @@ -36,8 +35,7 @@ public class TraceOptions { private final Set disabledFields; private final Set unsupportedOptions; private boolean onlyTopCall; - private boolean diffMode; - private TracerType tracerType; + private boolean withLog; public TraceOptions() { this.disabledFields = new HashSet<>(); @@ -55,14 +53,11 @@ public TraceOptions(Map traceOptions) { public final void addOption(String key, String value) { switch (key) { case "onlyTopCall" -> onlyTopCall = Boolean.parseBoolean(value); - case "diffMode" -> diffMode = Boolean.parseBoolean(value); + case "withLog" -> withLog = Boolean.parseBoolean(value); default -> addDisableOption(key, value); } } - - - private void addDisableOption(String key, String value) { DisableOption disableOption = DisableOption.getDisableOption(key); if (disableOption != null) { @@ -74,21 +69,12 @@ private void addDisableOption(String key, String value) { } } - - public void setOnlyTopCall(boolean onlyTopCall) { - this.onlyTopCall = onlyTopCall; - } - - public void setDiffMode(boolean diffMode) { - this.diffMode = diffMode; - } - public boolean isOnlyTopCall() { return onlyTopCall; } - public boolean isDiffMode() { - return diffMode; + public boolean isWithLog() { + return withLog; } public Set getDisabledFields() { @@ -99,10 +85,6 @@ public Set getUnsupportedOptions() { return Collections.unmodifiableSet(unsupportedOptions); } - public TracerType getTracerType() { - return tracerType; - } - public static class Deserializer extends StdDeserializer { @Serial private static final long serialVersionUID = 4222943114560623356L; diff --git a/rskj-core/src/main/java/co/rsk/rpc/modules/debug/trace/call/CallTraceTransformer.java b/rskj-core/src/main/java/co/rsk/rpc/modules/debug/trace/call/CallTraceTransformer.java index 26ce8ff95e2..04825839bfc 100644 --- a/rskj-core/src/main/java/co/rsk/rpc/modules/debug/trace/call/CallTraceTransformer.java +++ b/rskj-core/src/main/java/co/rsk/rpc/modules/debug/trace/call/CallTraceTransformer.java @@ -28,18 +28,21 @@ import org.bouncycastle.util.encoders.Hex; import org.ethereum.db.TransactionInfo; import org.ethereum.vm.DataWord; +import org.ethereum.vm.LogInfo; import org.ethereum.vm.program.ProgramResult; import org.ethereum.vm.program.invoke.InvokeData; import org.ethereum.vm.trace.SummarizedProgramTrace; import java.math.BigInteger; +import java.util.ArrayList; +import java.util.List; public class CallTraceTransformer { private CallTraceTransformer() { } - public static TransactionTrace toTrace(SummarizedProgramTrace trace, TransactionInfo txInfo, DataWord codeAddress, boolean onlyTopCall) { + public static TransactionTrace toTrace(SummarizedProgramTrace trace, TransactionInfo txInfo, DataWord codeAddress, boolean onlyTopCall, boolean withLog) { boolean isContractCreation = txInfo.getReceipt().getTransaction().isContractCreation(); CallType callType = isContractCreation ? CallType.NONE : CallType.CALL; @@ -49,6 +52,11 @@ public static TransactionTrace toTrace(SummarizedProgramTrace trace, Transaction if (trace.getReverted()) { programResult.setRevert(); } + + if (withLog) { + programResult.addLogInfos(txInfo.getReceipt().getLogInfoList()); + } + InvokeData invoke = trace.getInvokeData(); CreationData creationData = null; @@ -64,41 +72,48 @@ public static TransactionTrace toTrace(SummarizedProgramTrace trace, Transaction traceType = TraceType.CREATE; } - TxTraceResult traceOutput = toTrace(traceType, callType, invoke, codeAddress, programResult, creationData); - if(!onlyTopCall) { + TxTraceResult traceOutput = toTrace(traceType, callType, invoke, codeAddress, programResult, creationData, withLog); + if (!onlyTopCall) { for (ProgramSubtrace subtrace : trace.getSubtraces()) { - traceOutput.addCall(toTrace(subtrace)); + traceOutput.addCall(toTrace(subtrace, withLog)); } } return new TransactionTrace(txInfo.getReceipt().getTransaction().getHash().toHexString(), traceOutput); } - private static TxTraceResult toTrace(ProgramSubtrace programSubtrace) { + private static TxTraceResult toTrace(ProgramSubtrace programSubtrace, boolean withLog) { InvokeData invokeData = programSubtrace.getInvokeData(); - TxTraceResult subTrace = toTrace(programSubtrace.getTraceType(), programSubtrace.getCallType(), invokeData, programSubtrace.getCodeAddress(), programSubtrace.getProgramResult(), programSubtrace.getCreationData()); + TxTraceResult subTrace = toTrace(programSubtrace.getTraceType(), programSubtrace.getCallType(), invokeData, programSubtrace.getCodeAddress(), programSubtrace.getProgramResult(), programSubtrace.getCreationData(), withLog); for (ProgramSubtrace call : programSubtrace.getSubtraces()) { - subTrace.addCall(toTrace(call)); + subTrace.addCall(toTrace(call, withLog)); } return subTrace; } - private static TxTraceResult toTrace(TraceType traceType, CallType callType, InvokeData invoke, DataWord codeAddress, ProgramResult programResult, CreationData creationData) { + private static TxTraceResult toTrace(TraceType traceType, CallType callType, InvokeData invoke, DataWord codeAddress, ProgramResult programResult, CreationData creationData, boolean withLog) { String type = traceType == TraceType.CREATE ? "CREATE" : callType.name(); String from; + String to = null; + String gas = null; + //TODO input data is missing in TransferInvoke + String input = null; + String value = null; + String output = null; + String gasUsed = null; + String revertReason = null; + String error = null; + + if (callType == CallType.DELEGATECALL) { from = new RskAddress(invoke.getOwnerAddress().getLast20Bytes()).toJsonString(); } else { from = new RskAddress(invoke.getCallerAddress().getLast20Bytes()).toJsonString(); } - String to = null; - String gas = null; + + List logInfoResultList = null; DataWord callValue = invoke.getCallValue(); - //TODO input data is missing in TransferInvoke - String input = null; - String value = null; - String output = null; if (traceType == TraceType.CREATE) { @@ -125,16 +140,10 @@ private static TxTraceResult toTrace(TraceType traceType, CallType callType, Inv } gas = HexUtils.toQuantityJsonHex(invoke.getGas()); - } - String gasUsed = null; - - String revertReason = null; - String error = null; - if (programResult != null) { gasUsed = HexUtils.toQuantityJsonHex(programResult.getGasUsed()); @@ -142,7 +151,6 @@ private static TxTraceResult toTrace(TraceType traceType, CallType callType, Inv Pair programRevert = EthModule.decodeProgramRevert(programResult); revertReason = programRevert.getLeft(); output = HexUtils.toJsonHex(programRevert.getRight()); - error = "execution reverted"; } else if (traceType != TraceType.CREATE) { output = HexUtils.toJsonHex(programResult.getHReturn()); @@ -152,6 +160,18 @@ private static TxTraceResult toTrace(TraceType traceType, CallType callType, Inv } } + if(withLog) { + logInfoResultList = new ArrayList<>(); + List logInfoList = programResult.getLogInfoList(); + if(logInfoList != null) { + for (int i = 0; i < programResult.getLogInfoList().size(); i++) { + LogInfo logInfo = programResult.getLogInfoList().get(i); + LogInfoResult logInfoResult = fromLogInfo(logInfo, i); + logInfoResultList.add(logInfoResult); + } + } + } + return TxTraceResult.builder() .type(type) .from(from) @@ -163,8 +183,20 @@ private static TxTraceResult toTrace(TraceType traceType, CallType callType, Inv .output(output) .revertReason(revertReason) .error(error) + .logs(logInfoResultList) .build(); } + private static LogInfoResult fromLogInfo(LogInfo logInfo, int index) { + String address = HexUtils.toJsonHex(logInfo.getAddress()); + List topics = logInfo.getTopics().stream().map(DataWord::getData).map(HexUtils::toJsonHex).toList(); + String data = HexUtils.toJsonHex(logInfo.getData()); + return LogInfoResult.builder() + .index(index) + .address(address) + .topics(topics) + .data(data) + .build(); + } } diff --git a/rskj-core/src/main/java/co/rsk/rpc/modules/debug/trace/call/CallTracer.java b/rskj-core/src/main/java/co/rsk/rpc/modules/debug/trace/call/CallTracer.java index e5efe278529..53b33cf5a1f 100644 --- a/rskj-core/src/main/java/co/rsk/rpc/modules/debug/trace/call/CallTracer.java +++ b/rskj-core/src/main/java/co/rsk/rpc/modules/debug/trace/call/CallTracer.java @@ -46,8 +46,6 @@ import java.util.List; public class CallTracer implements DebugTracer { - - public static final String UNSUPPORTED_OPERATION = "Operation not supported by this tracer."; private static final Logger logger = LoggerFactory.getLogger("callTracer"); private static final ObjectMapper OBJECT_MAPPER = Serializers.createMapper(true); @@ -93,7 +91,7 @@ public JsonNode traceTransaction(@Nonnull String transactionHash, @Nonnull Trace return null; } - TransactionTrace trace = CallTraceTransformer.toTrace(programTrace, txInfo, null, traceOptions.isOnlyTopCall()); + TransactionTrace trace = CallTraceTransformer.toTrace(programTrace, txInfo, null, traceOptions.isOnlyTopCall(), traceOptions.isWithLog()); return OBJECT_MAPPER.valueToTree(trace.getResult()); } @@ -128,15 +126,12 @@ public TracerType getTracerType() { return TracerType.CALL_TRACER; } - private JsonNode traceBlock(Block block, TraceOptions traceOptions) { - if (traceOptions != null) { - logger.warn("Trace Options not supported yet"); - } - List result = buildBlockTraces(block, traceOptions.isOnlyTopCall()); + private JsonNode traceBlock(Block block, @Nonnull TraceOptions traceOptions) { + List result = buildBlockTraces(block, traceOptions.isOnlyTopCall(), traceOptions.isWithLog()); return OBJECT_MAPPER.valueToTree(result); } - private List buildBlockTraces(Block block, boolean onlyTopCall) { + private List buildBlockTraces(Block block, boolean onlyTopCall, boolean withLog) { List blockTraces = new ArrayList<>(); if (block != null && block.getNumber() != 0) { @@ -161,7 +156,7 @@ private List buildBlockTraces(Block block, boolean onlyTopCall return Collections.emptyList(); } - TransactionTrace trace = CallTraceTransformer.toTrace(programTrace, txInfo, null, onlyTopCall); + TransactionTrace trace = CallTraceTransformer.toTrace(programTrace, txInfo, null, onlyTopCall, withLog); blockTraces.add(trace); } diff --git a/rskj-core/src/main/java/co/rsk/rpc/modules/debug/trace/call/LogInfoResult.java b/rskj-core/src/main/java/co/rsk/rpc/modules/debug/trace/call/LogInfoResult.java new file mode 100644 index 00000000000..25aa6f8f019 --- /dev/null +++ b/rskj-core/src/main/java/co/rsk/rpc/modules/debug/trace/call/LogInfoResult.java @@ -0,0 +1,44 @@ +package co.rsk.rpc.modules.debug.trace.call; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.util.List; + +public record LogInfoResult(@JsonProperty int index, @JsonProperty String address, @JsonProperty List topics, + @JsonProperty String data) { + + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + private int index; + private String address; + private List topics; + private String data; + + public Builder index(int index) { + this.index = index; + return this; + } + + public Builder address(String address) { + this.address = address; + return this; + } + + public Builder topics(List topics) { + this.topics = topics; + return this; + } + + public Builder data(String data) { + this.data = data; + return this; + } + + public LogInfoResult build() { + return new LogInfoResult(index, address, topics, data); + } + } +} diff --git a/rskj-core/src/main/java/co/rsk/rpc/modules/debug/trace/call/TxTraceResult.java b/rskj-core/src/main/java/co/rsk/rpc/modules/debug/trace/call/TxTraceResult.java index 1b313ed3249..95eea860732 100644 --- a/rskj-core/src/main/java/co/rsk/rpc/modules/debug/trace/call/TxTraceResult.java +++ b/rskj-core/src/main/java/co/rsk/rpc/modules/debug/trace/call/TxTraceResult.java @@ -38,9 +38,9 @@ public class TxTraceResult { private final String error; private final String revertReason; private final List calls; - private final List logs; + private final List logs; - public TxTraceResult(String type, String from, String to, String value, String gas, String gasUsed, String input, String output, String error, String revertReason, List calls, List logs) { + public TxTraceResult(String type, String from, String to, String value, String gas, String gasUsed, String input, String output, String error, String revertReason, List calls, List logs) { this.type = type; this.from = from; this.to = to; @@ -110,9 +110,8 @@ public List getCalls() { return calls.isEmpty() ? null : calls; } - //TODO @JsonGetter("logs") - public List getLogs() { + public List getLogs() { return logs.isEmpty() ? null : logs; } @@ -125,11 +124,6 @@ public void addCall(TxTraceResult call) { calls.add(call); } - public void addAllCall(List calls) { - this.calls.addAll(calls); - } - - //Builder class public static class Builder { private String type; @@ -143,7 +137,7 @@ public static class Builder { private String error; private String revertReason; private List calls; - private List logs; + private List logs; public Builder type(String type) { this.type = type; @@ -200,7 +194,7 @@ public Builder calls(List calls) { return this; } - public Builder logs(List logs) { + public Builder logs(List logs) { this.logs = logs; return this; } From 360718e993c3e9b36e8ac0633d3354d0403d9ed0 Mon Sep 17 00:00:00 2001 From: Angel Soto Date: Thu, 28 Nov 2024 17:56:53 +0100 Subject: [PATCH 5/8] Adding input data to transferInvoke --- .../ethereum/core/TransactionExecutor.java | 2 +- .../vm/program/invoke/TransferInvoke.java | 55 ++++++++++++++++++- 2 files changed, 53 insertions(+), 4 deletions(-) diff --git a/rskj-core/src/main/java/org/ethereum/core/TransactionExecutor.java b/rskj-core/src/main/java/org/ethereum/core/TransactionExecutor.java index 79f89cfff83..b3f53e028ab 100644 --- a/rskj-core/src/main/java/org/ethereum/core/TransactionExecutor.java +++ b/rskj-core/src/main/java/org/ethereum/core/TransactionExecutor.java @@ -676,7 +676,7 @@ public void extractTrace(ProgramTraceProcessor programTraceProcessor) { programTraceProcessor.processProgramTrace(trace, tx.getHash()); } else { - TransferInvoke invoke = new TransferInvoke(DataWord.valueOf(tx.getSender(signatureCache).getBytes()), DataWord.valueOf(tx.getReceiveAddress().getBytes()), 0L, DataWord.valueOf(tx.getValue().getBytes())); + TransferInvoke invoke = new TransferInvoke(DataWord.valueOf(tx.getSender(signatureCache).getBytes()), DataWord.valueOf(tx.getReceiveAddress().getBytes()), 0L, DataWord.valueOf(tx.getValue().getBytes()), tx.getData()); SummarizedProgramTrace trace = new SummarizedProgramTrace(invoke); diff --git a/rskj-core/src/main/java/org/ethereum/vm/program/invoke/TransferInvoke.java b/rskj-core/src/main/java/org/ethereum/vm/program/invoke/TransferInvoke.java index 7d23a50a97f..fa5b77f47ba 100644 --- a/rskj-core/src/main/java/org/ethereum/vm/program/invoke/TransferInvoke.java +++ b/rskj-core/src/main/java/org/ethereum/vm/program/invoke/TransferInvoke.java @@ -22,17 +22,27 @@ import org.ethereum.util.ByteUtil; import org.ethereum.vm.DataWord; +import java.math.BigInteger; + public class TransferInvoke implements InvokeData { + private static BigInteger maxMsgData = BigInteger.valueOf(Integer.MAX_VALUE); + private final DataWord ownerAddress; private final DataWord callerAddress; private final long gas; private final DataWord callValue; + private final byte[] msgData; public TransferInvoke(DataWord callerAddress, DataWord ownerAddress, long gas, DataWord callValue) { + this(callerAddress, ownerAddress, gas, callValue, ByteUtil.EMPTY_BYTE_ARRAY); + } + + public TransferInvoke(DataWord callerAddress, DataWord ownerAddress, long gas, DataWord callValue, byte[] msgData) { this.callerAddress = callerAddress; this.ownerAddress = ownerAddress; this.gas = gas; this.callValue = callValue; + this.msgData = msgData; } @Override @@ -57,16 +67,55 @@ public DataWord getCallValue() { @Override public DataWord getDataSize() { - return DataWord.ZERO; + if (msgData == null || msgData.length == 0) { + return DataWord.ZERO; + } + int size = msgData.length; + return DataWord.valueOf(size); } @Override public DataWord getDataValue(DataWord indexData) { - return DataWord.ZERO; + if (msgData == null || msgData.length == 0) { + return DataWord.ZERO; + } + BigInteger tempIndex = indexData.value(); + int index = tempIndex.intValue(); // possible overflow is caught below + int size = 32; // maximum datavalue size + + if (index >= msgData.length + || tempIndex.compareTo(maxMsgData) == 1) { + return DataWord.ZERO; + } + if (index + size > msgData.length) { + size = msgData.length - index; + } + + byte[] data = new byte[32]; + System.arraycopy(msgData, index, data, 0, size); + return DataWord.valueOf(data); } @Override public byte[] getDataCopy(DataWord offsetData, DataWord lengthData) { - return ByteUtil.EMPTY_BYTE_ARRAY; + int offset = offsetData.intValueSafe(); + int length = lengthData.intValueSafe(); + + byte[] data = new byte[length]; + + if (msgData == null) { + return data; + } + + if (offset > msgData.length) { + return data; + } + if (offset + length > msgData.length) { + length = msgData.length - offset; + } + + System.arraycopy(msgData, offset, data, 0, length); + + return data; } } From 13e139d23153bef4e6d4f2311640b83797a64e45 Mon Sep 17 00:00:00 2001 From: Angel Soto Date: Tue, 3 Dec 2024 18:32:22 +0100 Subject: [PATCH 6/8] Updating output format and adding some tests --- .../trace/call/CallTraceTransformer.java | 55 +++-- .../modules/debug/trace/call/CallTracer.java | 7 +- .../debug/trace/call/TxTraceResult.java | 16 ++ .../vm/program/invoke/TransferInvoke.java | 2 +- .../rpc/modules/debug/TraceOptionsTest.java | 121 +++++++++++ .../debug/trace/call/CallTracerTest.java | 59 ++++++ .../rpc/parameters/DebugTracerParamTest.java | 191 ++++++++++++++++++ .../src/test/resources/dsl/simple_storage.txt | 21 ++ 8 files changed, 449 insertions(+), 23 deletions(-) create mode 100644 rskj-core/src/test/java/co/rsk/rpc/modules/debug/trace/call/CallTracerTest.java create mode 100644 rskj-core/src/test/java/org/ethereum/rpc/parameters/DebugTracerParamTest.java create mode 100644 rskj-core/src/test/resources/dsl/simple_storage.txt diff --git a/rskj-core/src/main/java/co/rsk/rpc/modules/debug/trace/call/CallTraceTransformer.java b/rskj-core/src/main/java/co/rsk/rpc/modules/debug/trace/call/CallTraceTransformer.java index 04825839bfc..67fa1a6232c 100644 --- a/rskj-core/src/main/java/co/rsk/rpc/modules/debug/trace/call/CallTraceTransformer.java +++ b/rskj-core/src/main/java/co/rsk/rpc/modules/debug/trace/call/CallTraceTransformer.java @@ -35,6 +35,7 @@ import java.math.BigInteger; import java.util.ArrayList; +import java.util.Collections; import java.util.List; public class CallTraceTransformer { @@ -96,7 +97,6 @@ private static TxTraceResult toTrace(TraceType traceType, CallType callType, Inv String from; String to = null; String gas = null; - //TODO input data is missing in TransferInvoke String input = null; String value = null; String output = null; @@ -105,11 +105,7 @@ private static TxTraceResult toTrace(TraceType traceType, CallType callType, Inv String error = null; - if (callType == CallType.DELEGATECALL) { - from = new RskAddress(invoke.getOwnerAddress().getLast20Bytes()).toJsonString(); - } else { - from = new RskAddress(invoke.getCallerAddress().getLast20Bytes()).toJsonString(); - } + from = getFrom(callType, invoke); List logInfoResultList = null; @@ -118,7 +114,7 @@ private static TxTraceResult toTrace(TraceType traceType, CallType callType, Inv if (traceType == TraceType.CREATE) { if (creationData != null) { - input = HexUtils.toJsonHex(creationData.getCreationInput()); + input = HexUtils.toUnformattedJsonHex(creationData.getCreationInput()); output = creationData.getCreatedAddress().toJsonString(); } value = HexUtils.toQuantityJsonHex(callValue.getData()); @@ -126,7 +122,7 @@ private static TxTraceResult toTrace(TraceType traceType, CallType callType, Inv } if (traceType == TraceType.CALL) { - input = HexUtils.toJsonHex(invoke.getDataCopy(DataWord.ZERO, invoke.getDataSize())); + input = HexUtils.toUnformattedJsonHex(invoke.getDataCopy(DataWord.ZERO, invoke.getDataSize())); value = HexUtils.toQuantityJsonHex(callValue.getData()); if (callType == CallType.DELEGATECALL) { @@ -143,35 +139,28 @@ private static TxTraceResult toTrace(TraceType traceType, CallType callType, Inv } - if (programResult != null) { gasUsed = HexUtils.toQuantityJsonHex(programResult.getGasUsed()); if (programResult.isRevert()) { Pair programRevert = EthModule.decodeProgramRevert(programResult); revertReason = programRevert.getLeft(); - output = HexUtils.toJsonHex(programRevert.getRight()); + output = HexUtils.toQuantityJsonHex(programRevert.getRight()); error = "execution reverted"; } else if (traceType != TraceType.CREATE) { - output = HexUtils.toJsonHex(programResult.getHReturn()); + output = HexUtils.toQuantityJsonHex(programResult.getHReturn()); } + if (programResult.getException() != null) { error = programResult.getException().toString(); } } - if(withLog) { - logInfoResultList = new ArrayList<>(); - List logInfoList = programResult.getLogInfoList(); - if(logInfoList != null) { - for (int i = 0; i < programResult.getLogInfoList().size(); i++) { - LogInfo logInfo = programResult.getLogInfoList().get(i); - LogInfoResult logInfoResult = fromLogInfo(logInfo, i); - logInfoResultList.add(logInfoResult); - } - } + if (withLog) { + logInfoResultList = getLogs(programResult); } + return TxTraceResult.builder() .type(type) .from(from) @@ -188,6 +177,30 @@ private static TxTraceResult toTrace(TraceType traceType, CallType callType, Inv } + private static String getFrom(CallType callType, InvokeData invoke) { + if (callType == CallType.DELEGATECALL) { + return new RskAddress(invoke.getOwnerAddress().getLast20Bytes()).toJsonString(); + } else { + return new RskAddress(invoke.getCallerAddress().getLast20Bytes()).toJsonString(); + } + } + + private static List getLogs(ProgramResult programResult) { + if (programResult == null) { + return Collections.emptyList(); + } + List logInfoResultList = new ArrayList<>(); + List logInfoList = programResult.getLogInfoList(); + if (logInfoList != null) { + for (int i = 0; i < programResult.getLogInfoList().size(); i++) { + LogInfo logInfo = programResult.getLogInfoList().get(i); + LogInfoResult logInfoResult = fromLogInfo(logInfo, i); + logInfoResultList.add(logInfoResult); + } + } + return logInfoResultList; + } + private static LogInfoResult fromLogInfo(LogInfo logInfo, int index) { String address = HexUtils.toJsonHex(logInfo.getAddress()); List topics = logInfo.getTopics().stream().map(DataWord::getData).map(HexUtils::toJsonHex).toList(); diff --git a/rskj-core/src/main/java/co/rsk/rpc/modules/debug/trace/call/CallTracer.java b/rskj-core/src/main/java/co/rsk/rpc/modules/debug/trace/call/CallTracer.java index 53b33cf5a1f..b769e2c31df 100644 --- a/rskj-core/src/main/java/co/rsk/rpc/modules/debug/trace/call/CallTracer.java +++ b/rskj-core/src/main/java/co/rsk/rpc/modules/debug/trace/call/CallTracer.java @@ -68,11 +68,16 @@ public JsonNode traceTransaction(@Nonnull String transactionHash, @Nonnull Trace logger.trace("trace_transaction({})", transactionHash); byte[] hash = HexUtils.stringHexToByteArray(transactionHash); + if(hash == null) { + logger.error("Invalid transaction hash: {}", transactionHash); + throw new IllegalArgumentException("Invalid transaction hash: " + transactionHash); + } + TransactionInfo txInfo = this.receiptStore.getInMainChain(hash, this.blockStore).orElse(null); if (txInfo == null) { logger.trace("No transaction info for {}", transactionHash); - return null; + throw new IllegalArgumentException("No transaction info for " + transactionHash); } Block block = this.blockchain.getBlockByHash(txInfo.getBlockHash()); diff --git a/rskj-core/src/main/java/co/rsk/rpc/modules/debug/trace/call/TxTraceResult.java b/rskj-core/src/main/java/co/rsk/rpc/modules/debug/trace/call/TxTraceResult.java index 95eea860732..11a221484a2 100644 --- a/rskj-core/src/main/java/co/rsk/rpc/modules/debug/trace/call/TxTraceResult.java +++ b/rskj-core/src/main/java/co/rsk/rpc/modules/debug/trace/call/TxTraceResult.java @@ -40,6 +40,22 @@ public class TxTraceResult { private final List calls; private final List logs; + //Used by deserializer + public TxTraceResult(){ + this.type = null; + this.from = null; + this.to = null; + this.value = null; + this.gas = null; + this.gasUsed = null; + this.input = null; + this.output = null; + this.error = null; + this.revertReason = null; + this.calls = new ArrayList<>(); + this.logs = new ArrayList<>(); + } + public TxTraceResult(String type, String from, String to, String value, String gas, String gasUsed, String input, String output, String error, String revertReason, List calls, List logs) { this.type = type; this.from = from; diff --git a/rskj-core/src/main/java/org/ethereum/vm/program/invoke/TransferInvoke.java b/rskj-core/src/main/java/org/ethereum/vm/program/invoke/TransferInvoke.java index fa5b77f47ba..cb373bf3a01 100644 --- a/rskj-core/src/main/java/org/ethereum/vm/program/invoke/TransferInvoke.java +++ b/rskj-core/src/main/java/org/ethereum/vm/program/invoke/TransferInvoke.java @@ -84,7 +84,7 @@ public DataWord getDataValue(DataWord indexData) { int size = 32; // maximum datavalue size if (index >= msgData.length - || tempIndex.compareTo(maxMsgData) == 1) { + || tempIndex.compareTo(maxMsgData) > 0) { return DataWord.ZERO; } if (index + size > msgData.length) { diff --git a/rskj-core/src/test/java/co/rsk/rpc/modules/debug/TraceOptionsTest.java b/rskj-core/src/test/java/co/rsk/rpc/modules/debug/TraceOptionsTest.java index b369a5a0917..d5e03e955d6 100644 --- a/rskj-core/src/test/java/co/rsk/rpc/modules/debug/TraceOptionsTest.java +++ b/rskj-core/src/test/java/co/rsk/rpc/modules/debug/TraceOptionsTest.java @@ -19,9 +19,11 @@ package co.rsk.rpc.modules.debug; +import com.fasterxml.jackson.databind.ObjectMapper; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; +import java.io.IOException; import java.util.Collections; import java.util.HashMap; import java.util.Map; @@ -140,4 +142,123 @@ void testTraceOptions_mixOfSupportedAndUnsupportedOptionsGiven_disabledFieldsAnd Assertions.assertTrue(options.getUnsupportedOptions().contains("unsupportedOption.2")); } + private final ObjectMapper objectMapper = new ObjectMapper(); + + @Test + void testDeserialize_withValidJson_shouldSetAllFields() throws IOException { + // Given + String json = """ + { + "onlyTopCall": "true", + "withLog": "false", + "disableMemory": "true", + "disableStack": "false", + "disableStorage": "true" + } + """; + + // When + TraceOptions options = objectMapper.readValue(json, TraceOptions.class); + + // Then + Assertions.assertTrue(options.isOnlyTopCall()); + Assertions.assertFalse(options.isWithLog()); + Assertions.assertTrue(options.getDisabledFields().contains("memory")); + Assertions.assertFalse(options.getDisabledFields().contains("stack")); + Assertions.assertTrue(options.getDisabledFields().contains("storage")); + } + + @Test + void testDeserialize_withUnsupportedOptions_shouldAddToUnsupported() throws IOException { + // Given + String json = """ + { + "onlyTopCall": "true", + "withLog": "true", + "unsupportedOption1": "true", + "unsupportedOption2": "false" + } + """; + + // When + TraceOptions options = objectMapper.readValue(json, TraceOptions.class); + + // Then + Assertions.assertTrue(options.isOnlyTopCall()); + Assertions.assertTrue(options.isWithLog()); + Assertions.assertTrue(options.getUnsupportedOptions().contains("unsupportedOption1")); + Assertions.assertTrue(options.getUnsupportedOptions().contains("unsupportedOption2")); + } + + @Test + void testDeserialize_withTracerConfig_shouldHandleNestedOptions() throws IOException { + // Given + String json = """ + { + "tracerConfig": { + "disableMemory": "true", + "disableStack": "true" + }, + "withLog": "false" + } + """; + + // When + TraceOptions options = objectMapper.readValue(json, TraceOptions.class); + + // Then + Assertions.assertFalse(options.isWithLog()); + Assertions.assertTrue(options.getDisabledFields().contains("memory")); + Assertions.assertTrue(options.getDisabledFields().contains("stack")); + } + + @Test + void testDeserialize_withEmptyJson_shouldReturnDefaultValues() throws IOException { + // Given + String json = "{}"; + + // When + TraceOptions options = objectMapper.readValue(json, TraceOptions.class); + + // Then + Assertions.assertFalse(options.isOnlyTopCall()); + Assertions.assertFalse(options.isWithLog()); + Assertions.assertTrue(options.getDisabledFields().isEmpty()); + Assertions.assertTrue(options.getUnsupportedOptions().isEmpty()); + } + + @Test + void testDeserialize_withInvalidJson_shouldThrowException() { + // Given + String invalidJson = "{ invalid json }"; + + // Then + Assertions.assertThrows(IOException.class, () -> { + // When + objectMapper.readValue(invalidJson, TraceOptions.class); + }); + } + + @Test + void testDeserialize_withConflictingOptions_shouldResolveCorrectly() throws IOException { + // Given + String json = """ + { + "onlyTopCall": "true", + "withLog": "true", + "disableMemory": "true", + "disableMemory": "false" + } + """; + + // When + TraceOptions options = objectMapper.readValue(json, TraceOptions.class); + + // Then + Assertions.assertTrue(options.isOnlyTopCall()); + Assertions.assertTrue(options.isWithLog()); + // Last occurrence of conflicting key takes precedence + Assertions.assertFalse(options.getDisabledFields().contains("memory")); + } + } diff --git a/rskj-core/src/test/java/co/rsk/rpc/modules/debug/trace/call/CallTracerTest.java b/rskj-core/src/test/java/co/rsk/rpc/modules/debug/trace/call/CallTracerTest.java new file mode 100644 index 00000000000..68ba2842050 --- /dev/null +++ b/rskj-core/src/test/java/co/rsk/rpc/modules/debug/trace/call/CallTracerTest.java @@ -0,0 +1,59 @@ +package co.rsk.rpc.modules.debug.trace.call; + +import co.rsk.rpc.ExecutionBlockRetriever; +import co.rsk.rpc.Web3InformationRetriever; +import co.rsk.rpc.modules.debug.TraceOptions; +import co.rsk.test.World; +import co.rsk.test.dsl.DslParser; +import co.rsk.test.dsl.WorldDslProcessor; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.ethereum.core.Account; +import org.ethereum.core.TransactionReceipt; +import org.ethereum.datasource.HashMapDB; +import org.ethereum.db.ReceiptStore; +import org.ethereum.db.ReceiptStoreImpl; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +class CallTracerTest { + + private final ObjectMapper objectMapper = new ObjectMapper(); + + + @Test + void retrieveSimpleStorageContractCreationTrace() throws Exception { + DslParser parser = DslParser.fromResource("dsl/simple_storage.txt"); + ReceiptStore receiptStore = new ReceiptStoreImpl(new HashMapDB()); + World world = new World(receiptStore); + ExecutionBlockRetriever executionBlockRetriever = Mockito.mock(ExecutionBlockRetriever.class); + Web3InformationRetriever web3InformationRetriever = new Web3InformationRetriever(world.getTransactionPool(), world.getBlockChain(), world.getRepositoryLocator(), executionBlockRetriever); + + + WorldDslProcessor processor = new WorldDslProcessor(world); + processor.processCommands(parser); + + //Transaction transaction = world.getTransactionByName("tx01"); + TransactionReceipt contractTransactionReceipt = world.getTransactionReceiptByName("tx01"); + + CallTracer callTracer = new CallTracer(world.getBlockStore(), world.getBlockExecutor(), web3InformationRetriever, receiptStore, world.getBlockChain()); + + JsonNode result = callTracer.traceTransaction(contractTransactionReceipt.getTransaction().getHash().toJsonString(), new TraceOptions()); + + assertNotNull(result); + + TxTraceResult traceResult = objectMapper.treeToValue(result, TxTraceResult.class); + + Account account = world.getAccountByName("acc1"); + + assertNotNull(traceResult); + assertEquals("CREATE", traceResult.getType()); + assertEquals("0x608060405234801561000f575f80fd5b506101438061001d5f395ff3fe608060405234801561000f575f80fd5b5060043610610034575f3560e01c80636057361d146100385780636d4ce63c14610054575b5f80fd5b610052600480360381019061004d91906100ba565b610072565b005b61005c61007b565b60405161006991906100f4565b60405180910390f35b805f8190555050565b5f8054905090565b5f80fd5b5f819050919050565b61009981610087565b81146100a3575f80fd5b50565b5f813590506100b481610090565b92915050565b5f602082840312156100cf576100ce610083565b5b5f6100dc848285016100a6565b91505092915050565b6100ee81610087565b82525050565b5f6020820190506101075f8301846100e5565b9291505056fea2646970667358221220271ba6597ab51821beed677d25c76e319892db25c1f66b3dc76e547fdc1fd0e164736f6c63430008140033", traceResult.getInput()); + assertEquals(account.getAddress().toJsonString(), traceResult.getFrom()); + + } + +} \ No newline at end of file diff --git a/rskj-core/src/test/java/org/ethereum/rpc/parameters/DebugTracerParamTest.java b/rskj-core/src/test/java/org/ethereum/rpc/parameters/DebugTracerParamTest.java new file mode 100644 index 00000000000..a7ddf97be99 --- /dev/null +++ b/rskj-core/src/test/java/org/ethereum/rpc/parameters/DebugTracerParamTest.java @@ -0,0 +1,191 @@ +package org.ethereum.rpc.parameters; + +import co.rsk.rpc.modules.debug.TraceOptions; +import co.rsk.rpc.modules.debug.trace.TracerType; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.util.Map; + +class DebugTracerParamTest { + + private final ObjectMapper objectMapper = new ObjectMapper(); + + @Test + void testConstructor_withNullValues_shouldInitializeFieldsToNull() { + // Given + DebugTracerParam param = new DebugTracerParam(null, null); + + // Then + Assertions.assertNull(param.getTracerType()); + Assertions.assertNull(param.getTraceOptions()); + } + + @Test + void testConstructor_withoutValues_shouldInitializeFieldsToNull() { + // Given + DebugTracerParam param = new DebugTracerParam(); + + // Then + Assertions.assertNull(param.getTracerType()); + Assertions.assertNull(param.getTraceOptions()); + } + + @Test + void testConstructor_withNonNullValues_shouldInitializeFieldsCorrectly() { + // Given + TraceOptions traceOptions = new TraceOptions(Map.of("disableMemory", "true")); + TracerType tracerType = TracerType.CALL_TRACER; + + // When + DebugTracerParam param = new DebugTracerParam(tracerType, traceOptions); + + // Then + Assertions.assertEquals(tracerType, param.getTracerType()); + Assertions.assertEquals(traceOptions, param.getTraceOptions()); + } + + @Test + void oldStyleTracerCall_shouldReturnCorrectInstance() throws IOException { + // Given + String json = """ + { + "disableMemory": "true", + "disableStorage": "false" + } + """; + + // When + DebugTracerParam param = objectMapper.readValue(json, DebugTracerParam.class); + + // Then + Assertions.assertNotNull(param); + Assertions.assertNull(param.getTracerType()); + Assertions.assertNotNull(param.getTraceOptions()); + Assertions.assertTrue(param.getTraceOptions().getDisabledFields().contains("memory")); + Assertions.assertFalse(param.getTraceOptions().getDisabledFields().contains("storage")); + } + + @Test + void callTracerWithConfig_shouldReturnCorrectInstance() throws IOException { + // Given + String json = """ + { + "tracer": "callTracer", + "tracerConfig": { + "onlyTopCall": true, + "withLog": true + } + } + """; + + // When + DebugTracerParam param = objectMapper.readValue(json, DebugTracerParam.class); + + // Then + Assertions.assertNotNull(param); + Assertions.assertEquals(TracerType.CALL_TRACER, param.getTracerType()); + Assertions.assertNotNull(param.getTraceOptions()); + Assertions.assertTrue(param.getTraceOptions().isWithLog()); + Assertions.assertTrue(param.getTraceOptions().isOnlyTopCall()); + } + + + @Test + void testDeserialize_withUnknownTracerType_shouldThrowException() { + // Given + String json = """ + { + "tracer": "unknownTracer" + } + """; + + // Then + Assertions.assertThrows(IllegalArgumentException.class, () -> { + // When + objectMapper.readValue(json, DebugTracerParam.class); + }); + } + + @Test + void testDeserialize_withInvalidTraceOptions_shouldStillDeserialize() throws IOException { + // Given + String json = """ + { + "tracer": "callTracer", + "unsupportedOption": "true" + } + """; + + // When + DebugTracerParam param = objectMapper.readValue(json, DebugTracerParam.class); + + // Then + Assertions.assertNotNull(param); + Assertions.assertEquals(TracerType.CALL_TRACER, param.getTracerType()); + Assertions.assertNotNull(param.getTraceOptions()); + Assertions.assertTrue(param.getTraceOptions().getUnsupportedOptions().contains("unsupportedOption")); + } + + @Test + void testDeserialize_withEmptyJson_shouldReturnDefaultValues() throws IOException { + // Given + String json = "{}"; + + // When + DebugTracerParam param = objectMapper.readValue(json, DebugTracerParam.class); + + // Then + Assertions.assertNotNull(param); + Assertions.assertNull(param.getTracerType()); + Assertions.assertNotNull(param.getTraceOptions()); + Assertions.assertTrue(param.getTraceOptions().getDisabledFields().isEmpty()); + Assertions.assertTrue(param.getTraceOptions().getUnsupportedOptions().isEmpty()); + } + + @Test + void testDeserialize_withInvalidJson_shouldThrowException() { + // Given + String invalidJson = "{ invalid json }"; + + // Then + Assertions.assertThrows(IllegalArgumentException.class, () -> { + // When + objectMapper.readValue(invalidJson, DebugTracerParam.class); + }); + } + + @Test + void testDeserializer_getTracerType_shouldReturnCorrectTracerType() throws IOException { + // Given + String json = """ + { + "tracer": "callTracer" + } + """; + + // When + DebugTracerParam param = objectMapper.readValue(json, DebugTracerParam.class); + + // Then + Assertions.assertEquals(TracerType.CALL_TRACER, param.getTracerType()); + } + + @Test + void testDeserializer_getTracerType_withNull_shouldThrowException() { + // Given + String json = """ + { + "tracer": null + } + """; + + // Then + Assertions.assertThrows(IllegalArgumentException.class, () -> { + // When + objectMapper.readValue(json, DebugTracerParam.class); + }); + } +} \ No newline at end of file diff --git a/rskj-core/src/test/resources/dsl/simple_storage.txt b/rskj-core/src/test/resources/dsl/simple_storage.txt new file mode 100644 index 00000000000..e4d467d5598 --- /dev/null +++ b/rskj-core/src/test/resources/dsl/simple_storage.txt @@ -0,0 +1,21 @@ +account_new acc1 10000000 + +transaction_build tx01 + sender acc1 + receiverAddress 00 + value 0 + data 608060405234801561000f575f80fd5b506101438061001d5f395ff3fe608060405234801561000f575f80fd5b5060043610610034575f3560e01c80636057361d146100385780636d4ce63c14610054575b5f80fd5b610052600480360381019061004d91906100ba565b610072565b005b61005c61007b565b60405161006991906100f4565b60405180910390f35b805f8190555050565b5f8054905090565b5f80fd5b5f819050919050565b61009981610087565b81146100a3575f80fd5b50565b5f813590506100b481610090565b92915050565b5f602082840312156100cf576100ce610083565b5b5f6100dc848285016100a6565b91505092915050565b6100ee81610087565b82525050565b5f6020820190506101075f8301846100e5565b9291505056fea2646970667358221220271ba6597ab51821beed677d25c76e319892db25c1f66b3dc76e547fdc1fd0e164736f6c63430008140033 + gas 1200000 + build + +block_build b01 + parent g00 + gasLimit 7500000 + transactions tx01 + build + +block_connect b01 + +# Assert best block +assert_best b01 + From 7a008e4d68725b9c7b3c203bdfd53121156883c3 Mon Sep 17 00:00:00 2001 From: Angel Soto Date: Wed, 4 Dec 2024 10:34:58 +0100 Subject: [PATCH 7/8] Adding missing copyright comments --- .../modules/debug/trace/call/LogInfoResult.java | 17 +++++++++++++++++ .../rpc/parameters/DebugTracerParam.java | 17 +++++++++++++++++ .../debug/trace/call/CallTracerTest.java | 17 +++++++++++++++++ .../rpc/parameters/DebugTracerParamTest.java | 17 +++++++++++++++++ 4 files changed, 68 insertions(+) diff --git a/rskj-core/src/main/java/co/rsk/rpc/modules/debug/trace/call/LogInfoResult.java b/rskj-core/src/main/java/co/rsk/rpc/modules/debug/trace/call/LogInfoResult.java index 25aa6f8f019..6d2b0975793 100644 --- a/rskj-core/src/main/java/co/rsk/rpc/modules/debug/trace/call/LogInfoResult.java +++ b/rskj-core/src/main/java/co/rsk/rpc/modules/debug/trace/call/LogInfoResult.java @@ -1,3 +1,20 @@ +/* + * This file is part of RskJ + * Copyright (C) 2024 RSK Labs Ltd. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ package co.rsk.rpc.modules.debug.trace.call; import com.fasterxml.jackson.annotation.JsonProperty; diff --git a/rskj-core/src/main/java/org/ethereum/rpc/parameters/DebugTracerParam.java b/rskj-core/src/main/java/org/ethereum/rpc/parameters/DebugTracerParam.java index f94564fac72..752a90817bb 100644 --- a/rskj-core/src/main/java/org/ethereum/rpc/parameters/DebugTracerParam.java +++ b/rskj-core/src/main/java/org/ethereum/rpc/parameters/DebugTracerParam.java @@ -1,3 +1,20 @@ +/* + * This file is part of RskJ + * Copyright (C) 2024 RSK Labs Ltd. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ package org.ethereum.rpc.parameters; import co.rsk.rpc.modules.debug.TraceOptions; diff --git a/rskj-core/src/test/java/co/rsk/rpc/modules/debug/trace/call/CallTracerTest.java b/rskj-core/src/test/java/co/rsk/rpc/modules/debug/trace/call/CallTracerTest.java index 68ba2842050..246995bc8b4 100644 --- a/rskj-core/src/test/java/co/rsk/rpc/modules/debug/trace/call/CallTracerTest.java +++ b/rskj-core/src/test/java/co/rsk/rpc/modules/debug/trace/call/CallTracerTest.java @@ -1,3 +1,20 @@ +/* + * This file is part of RskJ + * Copyright (C) 2024 RSK Labs Ltd. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ package co.rsk.rpc.modules.debug.trace.call; import co.rsk.rpc.ExecutionBlockRetriever; diff --git a/rskj-core/src/test/java/org/ethereum/rpc/parameters/DebugTracerParamTest.java b/rskj-core/src/test/java/org/ethereum/rpc/parameters/DebugTracerParamTest.java index a7ddf97be99..49cfba830f7 100644 --- a/rskj-core/src/test/java/org/ethereum/rpc/parameters/DebugTracerParamTest.java +++ b/rskj-core/src/test/java/org/ethereum/rpc/parameters/DebugTracerParamTest.java @@ -1,3 +1,20 @@ +/* + * This file is part of RskJ + * Copyright (C) 2024 RSK Labs Ltd. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ package org.ethereum.rpc.parameters; import co.rsk.rpc.modules.debug.TraceOptions; From 6ff2c50b0b82cb0ab9b99a66cace6ba4e0b7f277 Mon Sep 17 00:00:00 2001 From: Angel Soto Date: Wed, 4 Dec 2024 14:01:00 +0100 Subject: [PATCH 8/8] Rollback unneeded change --- rskj-core/src/main/java/org/ethereum/vm/program/Program.java | 2 +- .../co/rsk/rpc/modules/debug/trace/call/CallTracerTest.java | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/rskj-core/src/main/java/org/ethereum/vm/program/Program.java b/rskj-core/src/main/java/org/ethereum/vm/program/Program.java index 4573528fc8c..4ee46950c71 100644 --- a/rskj-core/src/main/java/org/ethereum/vm/program/Program.java +++ b/rskj-core/src/main/java/org/ethereum/vm/program/Program.java @@ -633,7 +633,7 @@ private ProgramResult getProgramResult(RskAddress senderAddress, byte[] nonce, D InternalTransaction internalTx = addInternalTx(nonce, getGasLimit(), senderAddress, RskAddress.nullAddress(), endowment, programCode, "create"); ProgramInvoke programInvoke = programInvokeFactory.createProgramInvoke( this, DataWord.valueOf(contractAddress.getBytes()), getOwnerAddress(), value, gasLimit, - newBalance, programCode, track, this.invoke.getBlockStore(), false, byTestingSuite()); + newBalance, null, track, this.invoke.getBlockStore(), false, byTestingSuite()); returnDataBuffer = null; // reset return buffer right before the call diff --git a/rskj-core/src/test/java/co/rsk/rpc/modules/debug/trace/call/CallTracerTest.java b/rskj-core/src/test/java/co/rsk/rpc/modules/debug/trace/call/CallTracerTest.java index 246995bc8b4..2b7b93c4344 100644 --- a/rskj-core/src/test/java/co/rsk/rpc/modules/debug/trace/call/CallTracerTest.java +++ b/rskj-core/src/test/java/co/rsk/rpc/modules/debug/trace/call/CallTracerTest.java @@ -53,7 +53,6 @@ void retrieveSimpleStorageContractCreationTrace() throws Exception { WorldDslProcessor processor = new WorldDslProcessor(world); processor.processCommands(parser); - //Transaction transaction = world.getTransactionByName("tx01"); TransactionReceipt contractTransactionReceipt = world.getTransactionReceiptByName("tx01"); CallTracer callTracer = new CallTracer(world.getBlockStore(), world.getBlockExecutor(), web3InformationRetriever, receiptStore, world.getBlockChain());