diff --git a/rskj-core/src/main/java/co/rsk/RskContext.java b/rskj-core/src/main/java/co/rsk/RskContext.java index b3f8a0ee4da..70d16a00351 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.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; @@ -104,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; @@ -812,15 +816,16 @@ public synchronized ConfigCapabilities getConfigCapabilities() { public synchronized DebugModule getDebugModule() { checkIfNotClosed(); + 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( - 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..98074fc4ac8 100644 --- a/rskj-core/src/main/java/co/rsk/rpc/Web3DebugModule.java +++ b/rskj-core/src/main/java/co/rsk/rpc/Web3DebugModule.java @@ -21,8 +21,7 @@ import co.rsk.net.handler.quota.TxQuota; import co.rsk.rpc.modules.debug.DebugModule; import com.fasterxml.jackson.databind.JsonNode; - -import java.util.Map; +import org.ethereum.rpc.parameters.DebugTracerParam; @java.lang.SuppressWarnings("squid:S100") public interface Web3DebugModule { @@ -32,27 +31,28 @@ 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 { - 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 4b7c3e2ce30..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 @@ -19,19 +19,21 @@ 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; - public interface DebugModule { String wireProtocolQueueSize(); - JsonNode traceTransaction(String transactionHash, Map traceOptions) throws Exception; + JsonNode traceTransaction(String transactionHash) throws Exception; + + JsonNode traceTransaction(String transactionHash, TraceOptions traceOptions, TracerType tracerType) throws Exception; - JsonNode traceBlockByHash(String blockHash, Map traceOptions) 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, 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 62be8e6e55c..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 @@ -19,55 +19,33 @@ 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; - 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,89 +53,71 @@ public String wireProtocolQueueSize() { } @Override - public JsonNode traceTransaction(String transactionHash, Map traceOptions) { - logger.trace("debug_traceTransaction for txHash: {}", StringUtils.trim(transactionHash)); - - TraceOptions options = toTraceOptions(traceOptions); - - 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; + public TxQuota accountTransactionQuota(String address) { + if (logger.isTraceEnabled()) { + logger.trace("debug_accountTransactionQuota({})", StringUtils.trim(address)); } - - 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()); + RskAddress rskAddress = new RskAddress(address); + return txQuotaChecker.getTxQuota(rskAddress); } @Override - public JsonNode traceBlockByHash(String blockHash, Map traceOptions) { - logger.trace("debug_traceBlockByHash for blockHash: {}", StringUtils.trim(blockHash)); + public JsonNode traceTransaction(String transactionHash) throws Exception { + return traceTransaction(transactionHash, new TraceOptions(), null); + } - TraceOptions options = toTraceOptions(traceOptions); + @Override + public JsonNode traceTransaction(String transactionHash, TraceOptions traceOptions, TracerType tracerType) throws Exception { + TracerType type = getTracerTypeOrDefault(tracerType); - 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; + if (traceOptions == null) { + traceOptions = new TraceOptions(); } - - return traceBlock(block, options); + 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 traceBlockByNumber(String bnOrId, Map traceOptions) { - logger.trace("debug_traceBlockByNumber for bnOrId: {}", StringUtils.trim(bnOrId)); + public JsonNode traceBlockByHash(String blockHash, TraceOptions traceOptions, TracerType tracerType) { + TracerType type = getTracerTypeOrDefault(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; + if (traceOptions == null) { + traceOptions = new TraceOptions(); } - - return traceBlock(block, options); + if (logger.isTraceEnabled()) { + logger.trace("debug_traceBlockByHash for blockHash: {}", StringUtils.trim(blockHash)); + } + DebugTracer tracer = traceProvider.getTracer(type); + return tracer.traceBlockByHash(blockHash, traceOptions); } - 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); + @Override + public JsonNode traceBlockByHash(String blockHash) throws Exception { + return traceBlockByHash(blockHash, new TraceOptions(), null); } - 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()); + @Override + 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); } - @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 getTracerTypeOrDefault(TracerType tracerType) { + //TODO review about this default tracer logic + if (tracerType == null) { + return DEFAULT_TRACER_TYPE; + } + return tracerType; } } 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..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,42 +19,62 @@ package co.rsk.rpc.modules.debug; +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 withLog; 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); + } - if (traceOptions == null || traceOptions.isEmpty()) return; - - // Disabled Fields Parsing + public final void addOption(String key, String value) { + switch (key) { + case "onlyTopCall" -> onlyTopCall = Boolean.parseBoolean(value); + case "withLog" -> withLog = Boolean.parseBoolean(value); + default -> addDisableOption(key, value); + } + } - 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 boolean isOnlyTopCall() { + return onlyTopCall; + } - traceOptions.keySet() - .stream() - .filter(key -> supportedOptions.stream().noneMatch(option -> option.equals(key))) - .forEach(unsupportedOptions::add); + public boolean isWithLog() { + return withLog; } public Set getDisabledFields() { @@ -65,4 +85,33 @@ public Set getUnsupportedOptions() { return Collections.unmodifiableSet(unsupportedOptions); } + 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/DebugTracer.java b/rskj-core/src/main/java/co/rsk/rpc/modules/debug/trace/DebugTracer.java new file mode 100644 index 00000000000..02ea22aaff0 --- /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) throws Exception; + + 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/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/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..67fa1a6232c --- /dev/null +++ b/rskj-core/src/main/java/co/rsk/rpc/modules/debug/trace/call/CallTraceTransformer.java @@ -0,0 +1,215 @@ +/* + * 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.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.Collections; +import java.util.List; + +public class CallTraceTransformer { + + private CallTraceTransformer() { + } + + 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; + + ProgramResult programResult = ProgramResult.empty(); + programResult.spendGas(new BigInteger(1, txInfo.getReceipt().getGasUsed()).longValue()); + + if (trace.getReverted()) { + programResult.setRevert(); + } + + if (withLog) { + programResult.addLogInfos(txInfo.getReceipt().getLogInfoList()); + } + + 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, withLog); + if (!onlyTopCall) { + for (ProgramSubtrace subtrace : trace.getSubtraces()) { + traceOutput.addCall(toTrace(subtrace, withLog)); + } + } + + return new TransactionTrace(txInfo.getReceipt().getTransaction().getHash().toHexString(), traceOutput); + } + + 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(), withLog); + for (ProgramSubtrace call : programSubtrace.getSubtraces()) { + subTrace.addCall(toTrace(call, withLog)); + } + return subTrace; + } + + 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; + String input = null; + String value = null; + String output = null; + String gasUsed = null; + String revertReason = null; + String error = null; + + + from = getFrom(callType, invoke); + + List logInfoResultList = null; + + DataWord callValue = invoke.getCallValue(); + + + if (traceType == TraceType.CREATE) { + if (creationData != null) { + input = HexUtils.toUnformattedJsonHex(creationData.getCreationInput()); + output = creationData.getCreatedAddress().toJsonString(); + } + value = HexUtils.toQuantityJsonHex(callValue.getData()); + gas = HexUtils.toQuantityJsonHex(invoke.getGas()); + } + + if (traceType == TraceType.CALL) { + input = HexUtils.toUnformattedJsonHex(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()); + } + + + if (programResult != null) { + gasUsed = HexUtils.toQuantityJsonHex(programResult.getGasUsed()); + + if (programResult.isRevert()) { + Pair programRevert = EthModule.decodeProgramRevert(programResult); + revertReason = programRevert.getLeft(); + output = HexUtils.toQuantityJsonHex(programRevert.getRight()); + error = "execution reverted"; + } else if (traceType != TraceType.CREATE) { + output = HexUtils.toQuantityJsonHex(programResult.getHReturn()); + } + + if (programResult.getException() != null) { + error = programResult.getException().toString(); + } + } + + if (withLog) { + logInfoResultList = getLogs(programResult); + } + + + return TxTraceResult.builder() + .type(type) + .from(from) + .to(to) + .value(value) + .gas(gas) + .input(input) + .gasUsed(gasUsed) + .output(output) + .revertReason(revertReason) + .error(error) + .logs(logInfoResultList) + .build(); + + } + + 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(); + 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 new file mode 100644 index 00000000000..b769e2c31df --- /dev/null +++ b/rskj-core/src/main/java/co/rsk/rpc/modules/debug/trace/call/CallTracer.java @@ -0,0 +1,174 @@ +/* + * 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 javax.annotation.Nonnull; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class CallTracer implements DebugTracer { + 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(@Nonnull String transactionHash, @Nonnull TraceOptions traceOptions) throws Exception { + 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); + throw new IllegalArgumentException("No transaction info for " + transactionHash); + } + + 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, traceOptions.isOnlyTopCall(), traceOptions.isWithLog()); + 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(@Nonnull String bnOrId, @Nonnull 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, @Nonnull TraceOptions traceOptions) { + List result = buildBlockTraces(block, traceOptions.isOnlyTopCall(), traceOptions.isWithLog()); + return OBJECT_MAPPER.valueToTree(result); + } + + private List buildBlockTraces(Block block, boolean onlyTopCall, boolean withLog) { + 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, onlyTopCall, withLog); + + blockTraces.add(trace); + } + } + + return blockTraces; + } + + +} 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..6d2b0975793 --- /dev/null +++ b/rskj-core/src/main/java/co/rsk/rpc/modules/debug/trace/call/LogInfoResult.java @@ -0,0 +1,61 @@ +/* + * 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; + +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/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..11a221484a2 --- /dev/null +++ b/rskj-core/src/main/java/co/rsk/rpc/modules/debug/trace/call/TxTraceResult.java @@ -0,0 +1,223 @@ +/* + * 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; + + //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; + 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; + } + + @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); + } + + //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/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/rpc/parameters/DebugTracerParam.java b/rskj-core/src/main/java/org/ethereum/rpc/parameters/DebugTracerParam.java new file mode 100644 index 00000000000..752a90817bb --- /dev/null +++ b/rskj-core/src/main/java/org/ethereum/rpc/parameters/DebugTracerParam.java @@ -0,0 +1,100 @@ +/* + * 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; +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() { + this(null, null); + } + + 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); + } + + //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..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 @@ -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/java/org/ethereum/vm/program/invoke/TransferInvoke.java b/rskj-core/src/main/java/org/ethereum/vm/program/invoke/TransferInvoke.java index 7d23a50a97f..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 @@ -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) > 0) { + 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; } } 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..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 @@ -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 @@ -96,12 +96,12 @@ 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()); - 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()); @@ -279,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); } @@ -297,9 +299,9 @@ 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); + JsonNode result = debugModule.traceBlockByNumber(blockNumber, null, null); Assertions.assertNotNull(result); Assertions.assertTrue(result.isArray()); @@ -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/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..2b7b93c4344 --- /dev/null +++ b/rskj-core/src/test/java/co/rsk/rpc/modules/debug/trace/call/CallTracerTest.java @@ -0,0 +1,75 @@ +/* + * 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; +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); + + 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/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(); 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..49cfba830f7 --- /dev/null +++ b/rskj-core/src/test/java/org/ethereum/rpc/parameters/DebugTracerParamTest.java @@ -0,0 +1,208 @@ +/* + * 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; +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 +