Skip to content

Commit

Permalink
feat(rpc): adds revert data to eth_call
Browse files Browse the repository at this point in the history
  • Loading branch information
jurajpiar committed Apr 5, 2024
1 parent 7f6a8e9 commit 59e2713
Show file tree
Hide file tree
Showing 9 changed files with 196 additions and 98 deletions.
33 changes: 17 additions & 16 deletions rskj-core/src/main/java/co/rsk/rpc/modules/eth/EthModule.java
Original file line number Diff line number Diff line change
Expand Up @@ -137,16 +137,10 @@ public String call(CallArgumentsParam argsParam, BlockIdentifierParam bnOrId) {
} else {
res = callConstant(args, block);
}

if (res.isRevert()) {
Optional<String> revertReason = decodeRevertReason(res);
if (revertReason.isPresent()) {
throw RskJsonRpcRequestException.transactionRevertedExecutionError(revertReason.get());
} else {
throw RskJsonRpcRequestException.transactionRevertedExecutionError();
}
}

throw RskJsonRpcRequestException.transactionRevertedExecutionError(decodeProgramRevert(res));
}
hReturn = HexUtils.toUnformattedJsonHex(res.getHReturn());

return hReturn;
Expand Down Expand Up @@ -300,21 +294,28 @@ public ProgramResult callConstant(CallArguments args, Block executionBlock) {
* The 4 first bytes are the function signature.
*
* @param res
* @return revert reason, empty if didnt match.
* @return revert reason and revert data. reason may be nullable or empty
*/
public static Optional<String> decodeRevertReason(ProgramResult res) {
public static ProgramRevert decodeProgramRevert(ProgramResult res) {
byte[] bytes = res.getHReturn();
if (bytes == null || bytes.length < 4) {
return Optional.empty();
if (bytes.length < 4) {

return new ProgramRevert(null, bytes);
}

final byte[] signature = copyOfRange(res.getHReturn(), 0, 4);
final byte[] signature = copyOfRange(bytes, 0, 4);
if (!Arrays.equals(signature, ERROR_ABI_FUNCTION_SIGNATURE)) {
return Optional.empty();

return new ProgramRevert(null, bytes);
}

final Object[] decode = ERROR_ABI_FUNCTION.decode(bytes);
if (decode == null || decode.length == 0) {

return new ProgramRevert(null, bytes);
}

final Object[] decode = ERROR_ABI_FUNCTION.decode(res.getHReturn());
return decode != null && decode.length > 0 ? Optional.of((String) decode[0]) : Optional.empty();
return new ProgramRevert((String) decode[0], bytes);
}

private ProgramResult callConstantWithState(CallArguments args, Block executionBlock, Trie state) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,10 @@
import static org.ethereum.rpc.exception.RskJsonRpcRequestException.transactionRevertedExecutionError;
import static org.ethereum.rpc.exception.RskJsonRpcRequestException.unknownError;

import java.util.Optional;

import org.ethereum.config.Constants;
import org.ethereum.core.Blockchain;
import org.ethereum.core.TransactionPool;
import org.ethereum.core.TransactionReceipt;
import org.ethereum.db.TransactionInfo;
import org.ethereum.rpc.exception.RskJsonRpcRequestException;
import org.ethereum.rpc.parameters.CallArgumentsParam;
Expand Down Expand Up @@ -115,13 +114,7 @@ private String getReturnMessage(String txHash) {
ProgramResult programResult = this.blockExecutor.getProgramResult(hash);

if (programResult != null && programResult.isRevert()) {
Optional<String> revertReason = EthModule.decodeRevertReason(programResult);

if (revertReason.isPresent()) {
throw RskJsonRpcRequestException.transactionRevertedExecutionError(revertReason.get());
} else {
throw RskJsonRpcRequestException.transactionRevertedExecutionError();
}
throw transactionRevertedExecutionError(EthModule.decodeProgramRevert(programResult));
}

if (!transactionInfo.getReceipt().isSuccessful()) {
Expand Down
41 changes: 41 additions & 0 deletions rskj-core/src/main/java/co/rsk/rpc/modules/eth/ProgramRevert.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
* This file is part of RskJ
* Copyright (C) 2018 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 <http://www.gnu.org/licenses/>.
*/
package co.rsk.rpc.modules.eth;

import org.ethereum.rpc.parameters.HexDataParam;

import javax.annotation.Nullable;

public class ProgramRevert {
private final String reason;
private final byte[] data;


public ProgramRevert(@Nullable String reason, byte[] data) {
this.reason = reason == null ? "" : reason;
this.data = data;
}

public String getReason() {
return reason;
}

public byte[] getData() {
return data;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ public JsonError resolveError(Throwable t, Method method, List<JsonNode> argumen
"invalid argument 0: hex string has length " + arguments.get(0).asText().replace("0x", "").length() + ", want 40 for RSK address",
null);
} else if (t instanceof RskJsonRpcRequestException) {
error = new JsonError(((RskJsonRpcRequestException) t).getCode(), t.getMessage(), null);
error = new JsonError(((RskJsonRpcRequestException) t).getCode(), t.getMessage(), ((RskJsonRpcRequestException) t).getRevertData());
} else if (t instanceof InvalidFormatException) {
error = new JsonError(JsonRpcError.INTERNAL_ERROR, "Internal server error, probably due to invalid parameter type", null);
} else if (t instanceof UnrecognizedPropertyException) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,37 +1,66 @@
package org.ethereum.rpc.exception;

import co.rsk.rpc.modules.eth.ProgramRevert;

import javax.annotation.Nullable;

public class RskJsonRpcRequestException extends RuntimeException {

private final Integer code;

protected RskJsonRpcRequestException(Integer code, String message, Exception e) {
@Nullable
private final byte[] revertData;

protected RskJsonRpcRequestException(Integer code, @Nullable byte[] revertData, String message, Exception e) {
super(message, e);
this.code = code;
this.revertData = revertData;
}

public RskJsonRpcRequestException(Integer code, String message) {
protected RskJsonRpcRequestException(Integer code, String message, Exception e) {
this(code, null, message, e);
}

public RskJsonRpcRequestException(Integer code, @Nullable byte[] revertData, String message) {
super(message);
this.code = code;
this.revertData = revertData;
}

public RskJsonRpcRequestException(Integer code, String message) {
this(code, null, message);
}

public Integer getCode() {
return code;
}

public static RskJsonRpcRequestException transactionRevertedExecutionError() {
return executionError("transaction reverted");
@Nullable
public byte[] getRevertData() {
return revertData;
}

public static RskJsonRpcRequestException transactionRevertedExecutionError(ProgramRevert programRevert) {
byte[] revertData = programRevert.getData();
String revertReason = programRevert.getReason();
if (revertReason.isEmpty()) {

return executionError("transaction reverted, no reason specified", revertData);
}

return executionError("revert" + revertReason, revertData);
}

public static RskJsonRpcRequestException transactionRevertedExecutionError(String revertReason) {
return executionError("revert " + revertReason);
public static RskJsonRpcRequestException transactionRevertedExecutionError() {
return executionError("transaction reverted", null);
}

public static RskJsonRpcRequestException unknownError(String message) {
return new RskJsonRpcRequestException(-32009, message);
}

private static RskJsonRpcRequestException executionError(String message) {
return new RskJsonRpcRequestException(-32015, String.format("VM Exception while processing transaction: %s", message));
private static RskJsonRpcRequestException executionError(String message, byte[] revertData) {
return new RskJsonRpcRequestException(-32015, revertData, String.format("VM Exception while processing transaction: %s", message));
}

public static RskJsonRpcRequestException transactionError(String message) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,13 @@ public class HexDataParam implements Serializable {

private final byte[] rawDataBytes;

public HexDataParam(String rawData){
public HexDataParam(String rawData) {
this(HexUtils.stringHexToByteArray(rawData));
}

public HexDataParam(byte[] rawData){
try {
this.rawDataBytes = HexUtils.stringHexToByteArray(rawData);
this.rawDataBytes = rawData;
} catch (Exception e) {
throw RskJsonRpcRequestException.invalidParamError("Invalid data format: invalid hex value.", e);
}
Expand Down
110 changes: 55 additions & 55 deletions rskj-core/src/main/resources/logback.xml
Original file line number Diff line number Diff line change
Expand Up @@ -56,71 +56,71 @@
</appender>

<!-- Loggers -->
<logger name="execute" level="INFO"/>
<logger name="blockvalidator" level="INFO"/>
<logger name="blockexecutor" level="INFO"/>
<logger name="general" level="INFO"/>
<logger name="gaspricetracker" level="INFO"/>
<logger name="web3" level="INFO"/>
<logger name="repository" level="INFO"/>
<logger name="VM" level="ERROR"/>
<logger name="blockqueue" level="INFO"/>
<logger name="io.netty" level="INFO"/>
<logger name="block" level="INFO"/>
<logger name="minerserver" level="INFO"/>
<logger name="txbuilderex" level="INFO"/>
<logger name="pendingstate" level="INFO"/>
<logger name="hsqldb.db" level="INFO"/>
<logger name="TCK-Test" level="INFO"/>
<logger name="db" level="INFO"/>
<logger name="net" level="INFO"/>
<logger name="start" level="INFO"/>
<logger name="cli" level="INFO"/>
<logger name="txs" level="INFO"/>
<logger name="execute" level="ERROR"/>
<logger name="blockvalidator" level="ERROR"/>
<logger name="blockexecutor" level="ERROR"/>
<logger name="general" level="ERROR"/>
<logger name="gaspricetracker" level="ERROR"/>
<logger name="web3" level="ALL"/>
<logger name="repository" level="ERROR"/>
<logger name="VM" level="ALL"/>
<logger name="blockqueue" level="ERROR"/>
<logger name="io.netty" level="ALL"/>
<logger name="block" level="ERROR"/>
<logger name="minerserver" level="ERROR"/>
<logger name="txbuilderex" level="ERROR"/>
<logger name="pendingstate" level="ERROR"/>
<logger name="hsqldb.db" level="ERROR"/>
<logger name="TCK-Test" level="ERROR"/>
<logger name="db" level="ERROR"/>
<logger name="net" level="ALL"/>
<logger name="start" level="ERROR"/>
<logger name="cli" level="ERROR"/>
<logger name="txs" level="ALL"/>
<logger name="gas" level="ERROR"/>
<logger name="main" level="INFO"/>
<logger name="trie" level="INFO"/>
<logger name="peermonitor" level="INFO"/>
<logger name="bridge" level="INFO"/>
<logger name="rlp" level="INFO"/>
<logger name="messagehandler" level="INFO"/>
<logger name="sync" level="INFO"/>
<logger name="BtcToRskClient" level="INFO"/>
<logger name="ui" level="INFO"/>
<logger name="java.nio" level="INFO"/>
<logger name="org.eclipse.jetty" level="INFO"/>
<logger name="wire" level="INFO"/>
<logger name="BridgeSupport" level="INFO"/>
<logger name="jsonrpc" level="INFO"/>
<logger name="wallet" level="INFO"/>
<logger name="blockchain" level="INFO"/>
<logger name="blockprocessor" level="INFO"/>
<logger name="asyncblockprocessor" level="INFO"/>
<logger name="state" level="INFO"/>
<logger name="org.bitcoinj" level="INFO"/>
<logger name="metrics" level="INFO"/>
<logger name="messageProcess" level="INFO"/>
<logger name="co.rsk.db.migration.OrchidToUnitrieMigrator" level="INFO"/>
<logger name="co.rsk.db.migration.MissingOrchidStorageKeysProvider" level="INFO"/>
<logger name="blooms" level="INFO"/>
<logger name="triestore" level="INFO" />
<logger name="btcBlockStore" level="INFO" />
<logger name="secp256k1" level="INFO" />
<logger name="altbn128" level="INFO" />
<logger name="co.rsk.net.discovery.PeerExplorer" level="INFO" />
<logger name="co.rsk.net.discovery.NodeChallengeManager" level="INFO" />
<logger name="main" level="ALL"/>
<logger name="trie" level="ERROR"/>
<logger name="peermonitor" level="ERROR"/>
<logger name="bridge" level="ERROR"/>
<logger name="rlp" level="ALL"/>
<logger name="messagehandler" level="ERROR"/>
<logger name="sync" level="ERROR"/>
<logger name="BtcToRskClient" level="ERROR"/>
<logger name="ui" level="ERROR"/>
<logger name="java.nio" level="ERROR"/>
<logger name="org.eclipse.jetty" level="ERROR"/>
<logger name="wire" level="ERROR"/>
<logger name="BridgeSupport" level="ERROR"/>
<logger name="jsonrpc" level="ALL"/>
<logger name="wallet" level="ERROR"/>
<logger name="blockchain" level="ERROR"/>
<logger name="blockprocessor" level="ERROR"/>
<logger name="asyncblockprocessor" level="ERROR"/>
<logger name="state" level="ERROR"/>
<logger name="org.bitcoinj" level="ERROR"/>
<logger name="metrics" level="ERROR"/>
<logger name="messageProcess" level="ERROR"/>
<logger name="co.rsk.db.migration.OrchidToUnitrieMigrator" level="ERROR"/>
<logger name="co.rsk.db.migration.MissingOrchidStorageKeysProvider" level="ERROR"/>
<logger name="blooms" level="ERROR"/>
<logger name="triestore" level="ERROR" />
<logger name="btcBlockStore" level="ERROR" />
<logger name="secp256k1" level="ERROR" />
<logger name="altbn128" level="ERROR" />
<logger name="co.rsk.net.discovery.PeerExplorer" level="ERROR" />
<logger name="co.rsk.net.discovery.NodeChallengeManager" level="ERROR" />

<!-- Use INFO or upper levels for production environments. -->
<logger name="com.googlecode.jsonrpc4j" level="INFO" />
<logger name="com.googlecode.jsonrpc4j" level="ALL" />

<!-- Logger for CLI tools -->
<logger name="clitool" level="DEBUG" additivity="false" >
<logger name="clitool" level="ERROR" additivity="false" >
<appender-ref ref="STDOUT_CLI_TOOL"/>
<appender-ref ref="FILE-AUDIT"/>
</logger>

<!-- Root logger -->
<root level="DEBUG">
<root level="ERROR">
<appender-ref ref="stdout"/>
<appender-ref ref="FILE-AUDIT"/>
</root>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -160,15 +160,15 @@ void test_revertedTransaction() {
.thenReturn(blockResult);
when(blockResult.getBlock()).thenReturn(block);

byte[] hreturn = Hex.decode(
byte[] hReturn = Hex.decode(
"08c379a000000000000000000000000000000000000000000000000000000000" +
"0000002000000000000000000000000000000000000000000000000000000000" +
"0000000f6465706f73697420746f6f2062696700000000000000000000000000" +
"00000000");
ProgramResult executorResult = mock(ProgramResult.class);
when(executorResult.isRevert()).thenReturn(true);
when(executorResult.getHReturn())
.thenReturn(hreturn);
.thenReturn(hReturn);

ReversibleTransactionExecutor executor = mock(ReversibleTransactionExecutor.class);
when(executor.executeTransaction(eq(blockResult.getBlock()), any(), any(), any(), any(), any(), any(), any()))
Expand All @@ -189,11 +189,10 @@ void test_revertedTransaction() {
config.getGasEstimationCap(),
config.getCallGasCap());

try {
eth.call(TransactionFactoryHelper.toCallArgumentsParam(args), new BlockIdentifierParam("latest"));
} catch (RskJsonRpcRequestException e) {
assertThat(e.getMessage(), Matchers.containsString("deposit too big"));
}
RskJsonRpcRequestException exception = assertThrows(RskJsonRpcRequestException.class, () -> eth.call(TransactionFactoryHelper.toCallArgumentsParam(args), new BlockIdentifierParam("latest")));
assertThat(exception.getMessage(), Matchers.containsString("deposit too big"));
assertNotNull(exception.getRevertData());
assertEquals(hReturn, exception.getRevertData());
}

@Test
Expand Down
Loading

0 comments on commit 59e2713

Please sign in to comment.