Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding Tracing Options #1644

Merged
merged 2 commits into from
Mar 18, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -68,9 +68,13 @@ public String wireProtocolQueueSize() {
public JsonNode traceTransaction(String transactionHash, Map<String, String> traceOptions) {
logger.trace("debug_traceTransaction({}, {})", transactionHash, traceOptions);

if (traceOptions != null && !traceOptions.isEmpty()) {
// TODO: implement the logic that takes into account trace options.
logger.warn("Received {} trace options. For now trace options are being ignored", 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());
}

byte[] hash = stringHexToByteArray(transactionHash);
Expand All @@ -86,7 +90,7 @@ public JsonNode traceTransaction(String transactionHash, Map<String, String> tra
Transaction tx = block.getTransactionsList().get(txInfo.getIndex());
txInfo.setTransaction(tx);

ProgramTraceProcessor programTraceProcessor = new ProgramTraceProcessor();
ProgramTraceProcessor programTraceProcessor = new ProgramTraceProcessor(options);
blockExecutor.traceBlock(programTraceProcessor, 0, block, parent.getHeader(), false, false);

return programTraceProcessor.getProgramTraceAsJsonNode(tx.getHash());
Expand All @@ -96,9 +100,13 @@ public JsonNode traceTransaction(String transactionHash, Map<String, String> tra
public JsonNode traceBlock(String blockHash, Map<String, String> traceOptions) {
logger.trace("debug_traceBlockByHash({}, {})", blockHash, traceOptions);

if (traceOptions != null && !traceOptions.isEmpty()) {
// TODO: implement the logic that takes into account trace options.
logger.warn("Received {} trace options. For now trace options are being ignored", 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());
}

byte[] bHash = stringHexToByteArray(blockHash);
Expand All @@ -110,7 +118,7 @@ public JsonNode traceBlock(String blockHash, Map<String, String> traceOptions) {

Block parent = blockStore.getBlockByHash(block.getParentHash().getBytes());

ProgramTraceProcessor programTraceProcessor = new ProgramTraceProcessor();
ProgramTraceProcessor programTraceProcessor = new ProgramTraceProcessor(options);
blockExecutor.traceBlock(programTraceProcessor, 0, block, parent.getHeader(), false, false);

List<Keccak256> txHashes = block.getTransactionsList().stream()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
* This file is part of RskJ
* Copyright (C) 2017 RSK Labs Ltd.
* (derived from ethereumJ library, Copyright (c) 2016 <ether.camp>)
*
* 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 <http://www.gnu.org/licenses/>.
*/

package co.rsk.rpc.modules.debug;

public enum DisableOption {
DISABLE_MEMORY("disableMemory", "memory"),
DISABLE_STACK("disableStack", "stack"),
DISABLE_STORAGE("disableStorage", "storage");

public final String option;
public final String value;

DisableOption(String option, String value) {
this.option = option;
this.value = value;
}
}
68 changes: 68 additions & 0 deletions rskj-core/src/main/java/co/rsk/rpc/modules/debug/TraceOptions.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/*
* This file is part of RskJ
* Copyright (C) 2017 RSK Labs Ltd.
* (derived from ethereumJ library, Copyright (c) 2016 <ether.camp>)
*
* 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 <http://www.gnu.org/licenses/>.
*/

package co.rsk.rpc.modules.debug;

import java.util.*;
import java.util.stream.Collectors;

public class TraceOptions {

private final List<String> supportedOptions;
private final Set<String> disabledFields;
private final Set<String> unsupportedOptions;

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<String, String> traceOptions) {
this();

if (traceOptions == null || traceOptions.isEmpty()) return;

// Disabled Fields Parsing

for (DisableOption disableOption : DisableOption.values()) {
if (Boolean.parseBoolean(traceOptions.get(disableOption.option))) {
this.disabledFields.add(disableOption.value);
}
}

// Unsupported Options

traceOptions.keySet()
.stream()
.filter(key -> supportedOptions.stream().noneMatch(option -> option.equals(key)))
.forEach(unsupportedOptions::add);
}

public Set<String> getDisabledFields() {
return Collections.unmodifiableSet(disabledFields);
}

public Set<String> getUnsupportedOptions() {
return Collections.unmodifiableSet(unsupportedOptions);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ class Secp256k1ServiceBC implements Secp256k1Service {
@Override
public ECKey recoverFromSignature(int recId, ECDSASignature sig, byte[] messageHash, boolean compressed) {
check(recId >= 0, "recId must be positive");
check(recId <= 3, "recId must be less than or equal to 3");
check(sig.getR().signum() >= 0, "r must be positive");
check(sig.getS().signum() >= 0, "s must be positive");
check(messageHash != null, "messageHash must not be null");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ public class Secp256k1ServiceNative extends Secp256k1ServiceBC {
@Override
public ECKey recoverFromSignature(int recId, ECDSASignature sig, byte[] messageHash, boolean compressed) {
check(recId >= 0, "recId must be positive");
check(recId <= 3, "recId must be less than or equal to 3");
check(sig.getR().signum() >= 0, "r must be positive");
check(sig.getS().signum() >= 0, "s must be positive");
check(messageHash != null, "messageHash must not be null");
Expand Down
2 changes: 2 additions & 0 deletions rskj-core/src/main/java/org/ethereum/vm/trace/Op.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

package org.ethereum.vm.trace;

import com.fasterxml.jackson.annotation.JsonFilter;
import org.ethereum.util.ByteUtil;
import org.ethereum.vm.OpCode;
import org.ethereum.vm.program.Memory;
Expand All @@ -29,6 +30,7 @@
import java.util.List;
import java.util.Map;

@JsonFilter("opFilter")
public class Op {

private OpCode op;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,25 +20,33 @@
package org.ethereum.vm.trace;

import co.rsk.crypto.Keccak256;
import co.rsk.rpc.modules.debug.TraceOptions;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ser.impl.SimpleBeanPropertyFilter;
import com.fasterxml.jackson.databind.ser.impl.SimpleFilterProvider;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.*;
import java.util.stream.Collectors;

/**
* Provides tracing and exporting to JSON
*/
public class ProgramTraceProcessor {

private static final ObjectMapper OBJECT_MAPPER = makeObjectMapper();

private final Map<Keccak256, ProgramTrace> traces = new HashMap<>();

private final TraceOptions traceOptions;

public ProgramTraceProcessor() {
traceOptions = new TraceOptions();
}

public ProgramTraceProcessor(TraceOptions options) {
traceOptions = options;
}

public void processProgramTrace(ProgramTrace programTrace, Keccak256 txHash) {
this.traces.put(txHash, programTrace);
}
Expand All @@ -53,7 +61,11 @@ public JsonNode getProgramTracesAsJsonNode(List<Keccak256> txHashes) {
.filter(Objects::nonNull)
.collect(Collectors.toList());

return OBJECT_MAPPER.valueToTree(txTraces);
SimpleFilterProvider filterProvider = new SimpleFilterProvider();
filterProvider.addFilter("opFilter",
SimpleBeanPropertyFilter.serializeAllExcept(traceOptions.getDisabledFields()));

return makeObjectMapper().setFilterProvider(filterProvider).valueToTree(txTraces);
}

public JsonNode getProgramTraceAsJsonNode(Keccak256 txHash) {
Expand All @@ -63,7 +75,11 @@ public JsonNode getProgramTraceAsJsonNode(Keccak256 txHash) {
return null;
}

return OBJECT_MAPPER.valueToTree(trace);
SimpleFilterProvider filterProvider = new SimpleFilterProvider();
filterProvider.addFilter("opFilter",
SimpleBeanPropertyFilter.serializeAllExcept(traceOptions.getDisabledFields()));

return makeObjectMapper().setFilterProvider(filterProvider).valueToTree(trace);
}

private static ObjectMapper makeObjectMapper() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

package co.rsk.rpc.modules.debug;

import co.rsk.net.MessageHandler;
Expand All @@ -36,8 +37,9 @@
import org.junit.Before;
import org.junit.Test;

import java.io.IOException;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;

import static org.ethereum.rpc.TypeConverter.stringHexToByteArray;
Expand All @@ -52,7 +54,7 @@ public class DebugModuleImplTest {
private DebugModuleImpl debugModule;

@Before
public void setup(){
public void setup() {
blockStore = Web3Mocks.getMockBlockStore();
receiptStore = Web3Mocks.getMockReceiptStore();
messageHandler = Web3Mocks.getMockMessageHandler();
Expand All @@ -61,7 +63,7 @@ public void setup(){
}

@Test
public void debug_wireProtocolQueueSize_basic() throws IOException {
public void debug_wireProtocolQueueSize_basic() {
String result = debugModule.wireProtocolQueueSize();
try {
TypeConverter.JSonHexToLong(result);
Expand All @@ -71,7 +73,7 @@ public void debug_wireProtocolQueueSize_basic() throws IOException {
}

@Test
public void debug_wireProtocolQueueSize_value() throws IOException {
public void debug_wireProtocolQueueSize_value() {
when(messageHandler.getMessageQueueSize()).thenReturn(5L);
String result = debugModule.wireProtocolQueueSize();
try {
Expand All @@ -83,7 +85,7 @@ public void debug_wireProtocolQueueSize_value() throws IOException {
}

@Test
public void debug_traceTransaction_retrieveUnknownTransactionAsNull() throws Exception {
public void debug_traceTransaction_retrieveUnknownTransactionAsNull() {
byte[] hash = stringHexToByteArray("0x00");
when(receiptStore.getInMainChain(hash, blockStore)).thenReturn(Optional.empty());

Expand Down Expand Up @@ -212,13 +214,16 @@ public void debug_traceTransaction_retrieveSimpleAccountTransferWithTraceOptions

Assert.assertEquals(resultWithNoOptions, resultWithEmptyOptions);

JsonNode resultWithNonEmptyOptions = debugModule.traceTransaction(transaction.getHash().toJsonString(), Collections.singletonMap("disableStorage", "true"));
Map<String, String> traceOptions = new HashMap<>();
traceOptions.put("disableStorage", "true");

JsonNode resultWithNonEmptyOptions = debugModule.traceTransaction(transaction.getHash().toJsonString(), traceOptions);

Assert.assertEquals(resultWithNoOptions, resultWithNonEmptyOptions);
}

@Test
public void debug_traceBlock_retrieveUnknownBlockAsNull() throws Exception {
public void debug_traceBlock_retrieveUnknownBlockAsNull() {
byte[] hash = stringHexToByteArray("0x00");
when(blockStore.getBlockByHash(hash)).thenReturn(null);

Expand Down Expand Up @@ -256,4 +261,58 @@ public void debug_traceBlock_retrieveSimpleContractsCreationTrace() throws Excep
Assert.assertTrue(structLogs.size() > 0);
});
}

@Test
public void debug_traceTransaction_retrieveSimpleContractInvocationTrace_traceOptions_disableAllFields_OK() throws Exception {
DslParser parser = DslParser.fromResource("dsl/contracts02.txt");
ReceiptStore receiptStore = new ReceiptStoreImpl(new HashMapDB());
World world = new World(receiptStore);

WorldDslProcessor processor = new WorldDslProcessor(world);
processor.processCommands(parser);

Transaction transaction = world.getTransactionByName("tx02");

DebugModuleImpl debugModule = new DebugModuleImpl(world.getBlockStore(), receiptStore, messageHandler, world.getBlockExecutor());

Map<String, String> 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);

// Sanity Check

Assert.assertNotNull(witnessResult);
Assert.assertTrue(witnessResult.isObject());

ObjectNode oWitnessResult = (ObjectNode) witnessResult;
Assert.assertTrue(oWitnessResult.get("error").textValue().isEmpty());
Assert.assertTrue(oWitnessResult.get("result").textValue().isEmpty());
JsonNode witnessStructLogs = oWitnessResult.get("structLogs");
Assert.assertTrue(witnessStructLogs.isArray());
Assert.assertTrue(witnessStructLogs.size() > 0);

Assert.assertNotNull(result);
Assert.assertTrue(result.isObject());

ObjectNode oResult = (ObjectNode) result;
Assert.assertTrue(oResult.get("error").textValue().isEmpty());
Assert.assertTrue(oResult.get("result").textValue().isEmpty());
JsonNode structLogs = oResult.get("structLogs");
Assert.assertTrue(structLogs.isArray());
Assert.assertTrue(structLogs.size() > 0);

// Check Filters

Assert.assertNotEquals(witnessResult, result);
Assert.assertFalse(witnessStructLogs.findValues("stack").isEmpty());
Assert.assertFalse(witnessStructLogs.findValues("memory").isEmpty());
Assert.assertFalse(witnessStructLogs.findValues("storage").isEmpty());
Assert.assertTrue(structLogs.findValues("stack").isEmpty());
Assert.assertTrue(structLogs.findValues("memory").isEmpty());
Assert.assertTrue(structLogs.findValues("storage").isEmpty());
}
}
Loading