Skip to content

Commit

Permalink
Updating output format and adding some tests
Browse files Browse the repository at this point in the history
  • Loading branch information
asoto-iov committed Dec 3, 2024
1 parent 07249bf commit 91a0ba9
Show file tree
Hide file tree
Showing 8 changed files with 449 additions and 23 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@

import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class CallTraceTransformer {
Expand Down Expand Up @@ -96,7 +97,6 @@ private static TxTraceResult toTrace(TraceType traceType, CallType callType, Inv
String from;
String to = null;
String gas = null;
//TODO input data is missing in TransferInvoke
String input = null;
String value = null;
String output = null;
Expand All @@ -105,11 +105,7 @@ private static TxTraceResult toTrace(TraceType traceType, CallType callType, Inv
String error = null;


if (callType == CallType.DELEGATECALL) {
from = new RskAddress(invoke.getOwnerAddress().getLast20Bytes()).toJsonString();
} else {
from = new RskAddress(invoke.getCallerAddress().getLast20Bytes()).toJsonString();
}
from = getFrom(callType, invoke);

List<LogInfoResult> logInfoResultList = null;

Expand All @@ -118,15 +114,15 @@ private static TxTraceResult toTrace(TraceType traceType, CallType callType, Inv

if (traceType == TraceType.CREATE) {
if (creationData != null) {
input = HexUtils.toJsonHex(creationData.getCreationInput());
input = HexUtils.toUnformattedJsonHex(creationData.getCreationInput());
output = creationData.getCreatedAddress().toJsonString();
}
value = HexUtils.toQuantityJsonHex(callValue.getData());
gas = HexUtils.toQuantityJsonHex(invoke.getGas());
}

if (traceType == TraceType.CALL) {
input = HexUtils.toJsonHex(invoke.getDataCopy(DataWord.ZERO, invoke.getDataSize()));
input = HexUtils.toUnformattedJsonHex(invoke.getDataCopy(DataWord.ZERO, invoke.getDataSize()));
value = HexUtils.toQuantityJsonHex(callValue.getData());

if (callType == CallType.DELEGATECALL) {
Expand All @@ -143,35 +139,28 @@ private static TxTraceResult toTrace(TraceType traceType, CallType callType, Inv
}



if (programResult != null) {
gasUsed = HexUtils.toQuantityJsonHex(programResult.getGasUsed());

if (programResult.isRevert()) {
Pair<String, byte[]> programRevert = EthModule.decodeProgramRevert(programResult);
revertReason = programRevert.getLeft();
output = HexUtils.toJsonHex(programRevert.getRight());
output = HexUtils.toQuantityJsonHex(programRevert.getRight());
error = "execution reverted";
} else if (traceType != TraceType.CREATE) {
output = HexUtils.toJsonHex(programResult.getHReturn());
output = HexUtils.toQuantityJsonHex(programResult.getHReturn());
}

if (programResult.getException() != null) {
error = programResult.getException().toString();
}
}

if(withLog) {
logInfoResultList = new ArrayList<>();
List<LogInfo> logInfoList = programResult.getLogInfoList();
if(logInfoList != null) {
for (int i = 0; i < programResult.getLogInfoList().size(); i++) {
LogInfo logInfo = programResult.getLogInfoList().get(i);
LogInfoResult logInfoResult = fromLogInfo(logInfo, i);
logInfoResultList.add(logInfoResult);
}
}
if (withLog) {
logInfoResultList = getLogs(programResult);
}


return TxTraceResult.builder()
.type(type)
.from(from)
Expand All @@ -188,6 +177,30 @@ private static TxTraceResult toTrace(TraceType traceType, CallType callType, Inv

}

private static String getFrom(CallType callType, InvokeData invoke) {
if (callType == CallType.DELEGATECALL) {
return new RskAddress(invoke.getOwnerAddress().getLast20Bytes()).toJsonString();
} else {
return new RskAddress(invoke.getCallerAddress().getLast20Bytes()).toJsonString();
}
}

private static List<LogInfoResult> getLogs(ProgramResult programResult) {
if (programResult == null) {
return Collections.emptyList();
}
List<LogInfoResult> logInfoResultList = new ArrayList<>();
List<LogInfo> 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<String> topics = logInfo.getTopics().stream().map(DataWord::getData).map(HexUtils::toJsonHex).toList();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,11 +68,16 @@ public JsonNode traceTransaction(@Nonnull String transactionHash, @Nonnull Trace
logger.trace("trace_transaction({})", transactionHash);

byte[] hash = HexUtils.stringHexToByteArray(transactionHash);
if(hash == null) {
logger.error("Invalid transaction hash: {}", transactionHash);
throw new IllegalArgumentException("Invalid transaction hash: " + transactionHash);
}

TransactionInfo txInfo = this.receiptStore.getInMainChain(hash, this.blockStore).orElse(null);

if (txInfo == null) {
logger.trace("No transaction info for {}", transactionHash);
return null;
throw new IllegalArgumentException("No transaction info for " + transactionHash);
}

Block block = this.blockchain.getBlockByHash(txInfo.getBlockHash());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,22 @@ public class TxTraceResult {
private final List<TxTraceResult> calls;
private final List<LogInfoResult> 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<TxTraceResult> calls, List<LogInfoResult> logs) {
this.type = type;
this.from = from;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ public DataWord getDataValue(DataWord indexData) {
int size = 32; // maximum datavalue size

if (index >= msgData.length
|| tempIndex.compareTo(maxMsgData) == 1) {
|| tempIndex.compareTo(maxMsgData) > 0) {
return DataWord.ZERO;
}
if (index + size > msgData.length) {
Expand Down
121 changes: 121 additions & 0 deletions rskj-core/src/test/java/co/rsk/rpc/modules/debug/TraceOptionsTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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"));
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package co.rsk.rpc.modules.debug.trace.call;

import co.rsk.rpc.ExecutionBlockRetriever;
import co.rsk.rpc.Web3InformationRetriever;
import co.rsk.rpc.modules.debug.TraceOptions;
import co.rsk.test.World;
import co.rsk.test.dsl.DslParser;
import co.rsk.test.dsl.WorldDslProcessor;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.ethereum.core.Account;
import org.ethereum.core.TransactionReceipt;
import org.ethereum.datasource.HashMapDB;
import org.ethereum.db.ReceiptStore;
import org.ethereum.db.ReceiptStoreImpl;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;

class CallTracerTest {

private final ObjectMapper objectMapper = new ObjectMapper();


@Test
void retrieveSimpleStorageContractCreationTrace() throws Exception {
DslParser parser = DslParser.fromResource("dsl/simple_storage.txt");
ReceiptStore receiptStore = new ReceiptStoreImpl(new HashMapDB());
World world = new World(receiptStore);
ExecutionBlockRetriever executionBlockRetriever = Mockito.mock(ExecutionBlockRetriever.class);
Web3InformationRetriever web3InformationRetriever = new Web3InformationRetriever(world.getTransactionPool(), world.getBlockChain(), world.getRepositoryLocator(), executionBlockRetriever);


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

//Transaction transaction = world.getTransactionByName("tx01");
TransactionReceipt contractTransactionReceipt = world.getTransactionReceiptByName("tx01");

CallTracer callTracer = new CallTracer(world.getBlockStore(), world.getBlockExecutor(), web3InformationRetriever, receiptStore, world.getBlockChain());

JsonNode result = callTracer.traceTransaction(contractTransactionReceipt.getTransaction().getHash().toJsonString(), new TraceOptions());

assertNotNull(result);

TxTraceResult traceResult = objectMapper.treeToValue(result, TxTraceResult.class);

Account account = world.getAccountByName("acc1");

assertNotNull(traceResult);
assertEquals("CREATE", traceResult.getType());
assertEquals("0x608060405234801561000f575f80fd5b506101438061001d5f395ff3fe608060405234801561000f575f80fd5b5060043610610034575f3560e01c80636057361d146100385780636d4ce63c14610054575b5f80fd5b610052600480360381019061004d91906100ba565b610072565b005b61005c61007b565b60405161006991906100f4565b60405180910390f35b805f8190555050565b5f8054905090565b5f80fd5b5f819050919050565b61009981610087565b81146100a3575f80fd5b50565b5f813590506100b481610090565b92915050565b5f602082840312156100cf576100ce610083565b5b5f6100dc848285016100a6565b91505092915050565b6100ee81610087565b82525050565b5f6020820190506101075f8301846100e5565b9291505056fea2646970667358221220271ba6597ab51821beed677d25c76e319892db25c1f66b3dc76e547fdc1fd0e164736f6c63430008140033", traceResult.getInput());
assertEquals(account.getAddress().toJsonString(), traceResult.getFrom());

}

}
Loading

0 comments on commit 91a0ba9

Please sign in to comment.