diff --git a/framework/src/main/java/org/tron/core/services/jsonrpc/JsonRpcApiUtil.java b/framework/src/main/java/org/tron/core/services/jsonrpc/JsonRpcApiUtil.java index f4bba9fbf37..6bc6ac26c4d 100644 --- a/framework/src/main/java/org/tron/core/services/jsonrpc/JsonRpcApiUtil.java +++ b/framework/src/main/java/org/tron/core/services/jsonrpc/JsonRpcApiUtil.java @@ -63,6 +63,7 @@ public class JsonRpcApiUtil { public static final String TAG_SAFE_SUPPORT_ERROR = "TAG safe not supported"; public static final String BLOCK_NUM_ERROR = "invalid block number"; public static final String TX_INDEX_ERROR = "invalid index value"; + public static final String INVALID_FILTER_REQUEST = "invalid filter request"; private static final SecureRandom random = new SecureRandom(); diff --git a/framework/src/main/java/org/tron/core/services/jsonrpc/TronJsonRpc.java b/framework/src/main/java/org/tron/core/services/jsonrpc/TronJsonRpc.java index 50da763b8b9..476bf76f724 100644 --- a/framework/src/main/java/org/tron/core/services/jsonrpc/TronJsonRpc.java +++ b/framework/src/main/java/org/tron/core/services/jsonrpc/TronJsonRpc.java @@ -307,10 +307,9 @@ String newFilter(FilterRequest fr) throws JsonRpcInvalidParamsException, @JsonRpcErrors({ @JsonRpcError(exception = JsonRpcMethodNotFoundException.class, code = -32601, data = "{}"), @JsonRpcError(exception = JsonRpcInvalidParamsException.class, code = -32602, data = "{}"), - @JsonRpcError(exception = ItemNotFoundException.class, code = -32000, data = "{}"), }) boolean uninstallFilter(String filterId) throws JsonRpcInvalidParamsException, - JsonRpcMethodNotFoundException, ItemNotFoundException; + JsonRpcMethodNotFoundException; @JsonRpcMethod("eth_getFilterChanges") @JsonRpcErrors({ diff --git a/framework/src/main/java/org/tron/core/services/jsonrpc/TronJsonRpcImpl.java b/framework/src/main/java/org/tron/core/services/jsonrpc/TronJsonRpcImpl.java index 6be47886117..94d46170a6d 100644 --- a/framework/src/main/java/org/tron/core/services/jsonrpc/TronJsonRpcImpl.java +++ b/framework/src/main/java/org/tron/core/services/jsonrpc/TronJsonRpcImpl.java @@ -122,6 +122,7 @@ public enum RequestSource { } private static final String FILTER_NOT_FOUND = "filter not found"; + private static final String INVALID_PARAMS = "invalid params"; public static final int EXPIRE_SECONDS = 5 * 60; private final int maxBlockFilterNum = Args.getInstance().getJsonRpcMaxBlockFilterNum(); private final int maxLogFilterNum = Args.getInstance().getJsonRpcMaxLogFilterNum(); @@ -363,14 +364,14 @@ public String ethGetBlockTransactionCountByNumber(String blockNumOrTag) public BlockResult ethGetBlockByHash(String blockHash, Boolean fullTransactionObjects) throws JsonRpcInvalidParamsException { final Block b = getBlockByJsonHash(blockHash); - return getBlockResult(b, fullTransactionObjects); + return getBlockResult(b, Boolean.TRUE.equals(fullTransactionObjects)); } @Override public BlockResult ethGetBlockByNumber(String blockNumOrTag, Boolean fullTransactionObjects) throws JsonRpcInvalidParamsException { final Block b = getBlockByNumOrTag(blockNumOrTag); - return (b == null ? null : getBlockResult(b, fullTransactionObjects)); + return (b == null ? null : getBlockResult(b, Boolean.TRUE.equals(fullTransactionObjects))); } /** @@ -673,6 +674,10 @@ public String gasPrice() { @Override public String estimateGas(CallArguments args) throws JsonRpcInvalidRequestException, JsonRpcInvalidParamsException, JsonRpcInternalException { + if (args == null) { + throw new JsonRpcInvalidParamsException(INVALID_PARAMS); + } + byte[] ownerAddress = addressCompatibleToByteArray(args.getFrom()); ContractType contractType = args.getContractType(wallet); @@ -1001,6 +1006,9 @@ private List getTransactionReceiptsFromBlock(BlockCapsule bl public String getCall(CallArguments transactionCall, Object blockParamObj) throws JsonRpcInvalidParamsException, JsonRpcInvalidRequestException, JsonRpcInternalException { + if (transactionCall == null) { + throw new JsonRpcInvalidParamsException(INVALID_PARAMS); + } String blockNumOrTag; if (blockParamObj instanceof HashMap) { @@ -1333,6 +1341,10 @@ public TransactionJson buildTransaction(BuildArguments args) throw new JsonRpcMethodNotFoundException(msg); } + if (args == null) { + throw new JsonRpcInvalidParamsException(INVALID_PARAMS); + } + byte[] fromAddressData; try { fromAddressData = addressCompatibleToByteArray(args.getFrom()); @@ -1441,6 +1453,10 @@ public String newFilter(FilterRequest fr) throws JsonRpcInvalidParamsException, JsonRpcMethodNotFoundException, JsonRpcExceedLimitException { disableInPBFT("eth_newFilter"); + if (fr == null) { + throw new JsonRpcInvalidParamsException(JsonRpcApiUtil.INVALID_FILTER_REQUEST); + } + // not supports finalized as block parameter if (FINALIZED_STR.equalsIgnoreCase(fr.getFromBlock()) || FINALIZED_STR.equalsIgnoreCase(fr.getToBlock())) { @@ -1488,10 +1504,13 @@ public String newBlockFilter() throws JsonRpcMethodNotFoundException, } @Override - public boolean uninstallFilter(String filterId) throws ItemNotFoundException, - JsonRpcMethodNotFoundException { + public boolean uninstallFilter(String filterId) throws JsonRpcMethodNotFoundException { disableInPBFT("eth_uninstallFilter"); + if (filterId == null) { + return false; + } + Map blockFilter2Result; Map eventFilter2Result; if (getSource() == RequestSource.FULLNODE) { @@ -1503,15 +1522,10 @@ public boolean uninstallFilter(String filterId) throws ItemNotFoundException, } filterId = ByteArray.fromHex(filterId); - if (eventFilter2Result.containsKey(filterId)) { - eventFilter2Result.remove(filterId); - } else if (blockFilter2Result.containsKey(filterId)) { - blockFilter2Result.remove(filterId); - } else { - throw new ItemNotFoundException(FILTER_NOT_FOUND); + if (eventFilter2Result.remove(filterId) != null) { + return true; } - - return true; + return blockFilter2Result.remove(filterId) != null; } @Override @@ -1519,6 +1533,10 @@ public Object[] getFilterChanges(String filterId) throws ItemNotFoundException, JsonRpcMethodNotFoundException { disableInPBFT("eth_getFilterChanges"); + if (filterId == null) { + throw new ItemNotFoundException(FILTER_NOT_FOUND); + } + Map blockFilter2Result; Map eventFilter2Result; if (getSource() == RequestSource.FULLNODE) { @@ -1540,6 +1558,10 @@ public LogFilterElement[] getLogs(FilterRequest fr) throws JsonRpcInvalidParamsE JsonRpcMethodNotFoundException, JsonRpcTooManyResultException { disableInPBFT("eth_getLogs"); + if (fr == null) { + throw new JsonRpcInvalidParamsException(JsonRpcApiUtil.INVALID_FILTER_REQUEST); + } + long currentMaxBlockNum = wallet.getNowBlock().getBlockHeader().getRawData().getNumber(); //convert FilterRequest to LogFilterWrapper LogFilterWrapper logFilterWrapper = new LogFilterWrapper(fr, currentMaxBlockNum, wallet, true); @@ -1554,6 +1576,10 @@ public LogFilterElement[] getFilterLogs(String filterId) throws JsonRpcMethodNotFoundException, JsonRpcTooManyResultException { disableInPBFT("eth_getFilterLogs"); + if (filterId == null) { + throw new ItemNotFoundException(FILTER_NOT_FOUND); + } + Map eventFilter2Result; if (getSource() == RequestSource.FULLNODE) { eventFilter2Result = eventFilter2ResultFull; diff --git a/framework/src/main/java/org/tron/core/services/jsonrpc/filters/LogFilter.java b/framework/src/main/java/org/tron/core/services/jsonrpc/filters/LogFilter.java index 03232f3549d..40230ab8090 100644 --- a/framework/src/main/java/org/tron/core/services/jsonrpc/filters/LogFilter.java +++ b/framework/src/main/java/org/tron/core/services/jsonrpc/filters/LogFilter.java @@ -1,5 +1,6 @@ package org.tron.core.services.jsonrpc.filters; +import static org.tron.core.services.jsonrpc.JsonRpcApiUtil.INVALID_FILTER_REQUEST; import static org.tron.core.services.jsonrpc.JsonRpcApiUtil.addressToByteArray; import static org.tron.core.services.jsonrpc.JsonRpcApiUtil.topicToByteArray; @@ -28,7 +29,8 @@ public class LogFilter { private byte[][] contractAddresses = new byte[0][]; // example: [[func1, func2], null, [A, B], [C]] // first topic must be func1 or func2,second can be any,third must be A or B,forth must be C - // [A, null] is not allowed. + // A null positional topic is a wildcard, so [A, null] is valid. + // A null entry in an OR-list is invalid, so [[A, null]] is not allowed. @Getter @Setter private List topics = new ArrayList<>(); @@ -46,6 +48,10 @@ public LogFilter() { * construct one LogFilter from part parameters of FilterRequest */ public LogFilter(FilterRequest fr) throws JsonRpcInvalidParamsException { + if (fr == null) { + throw new JsonRpcInvalidParamsException(INVALID_FILTER_REQUEST); + } + if (fr.getAddress() instanceof String) { withContractAddress(addressToByteArray((String) fr.getAddress())); diff --git a/framework/src/main/java/org/tron/core/services/jsonrpc/types/BuildArguments.java b/framework/src/main/java/org/tron/core/services/jsonrpc/types/BuildArguments.java index ef4e958ae44..84853a1fac0 100644 --- a/framework/src/main/java/org/tron/core/services/jsonrpc/types/BuildArguments.java +++ b/framework/src/main/java/org/tron/core/services/jsonrpc/types/BuildArguments.java @@ -6,6 +6,8 @@ import static org.tron.core.services.jsonrpc.JsonRpcApiUtil.parseQuantityValue; import static org.tron.core.services.jsonrpc.JsonRpcApiUtil.requireValidHex; +import com.fasterxml.jackson.annotation.JsonSetter; +import com.fasterxml.jackson.annotation.Nulls; import com.google.protobuf.ByteString; import java.util.Arrays; import lombok.AllArgsConstructor; @@ -23,6 +25,13 @@ import org.tron.protos.Protocol.Transaction.Contract.ContractType; import org.tron.protos.contract.SmartContractOuterClass.SmartContract; +/** + * Arguments for {@code buildTransaction}. + * + *

Fields annotated with {@link JsonSetter} retain their defaults when an explicit JSON + * {@code null} is deserialized. This protection applies to Jackson input only; direct setter calls + * may still assign {@code null} and callers must preserve the DTO invariants. + */ @NoArgsConstructor @AllArgsConstructor @ToString @@ -65,18 +74,22 @@ public class BuildArguments { @Getter @Setter + @JsonSetter(nulls = Nulls.SKIP) private Long tokenId = 0L; @Getter @Setter + @JsonSetter(nulls = Nulls.SKIP) private Long tokenValue = 0L; @Getter @Setter private String abi = ""; @Getter @Setter + @JsonSetter(nulls = Nulls.SKIP) private Long consumeUserResourcePercent = 0L; @Getter @Setter + @JsonSetter(nulls = Nulls.SKIP) private Long originEnergyLimit = 0L; @Getter @Setter @@ -84,9 +97,11 @@ public class BuildArguments { @Getter @Setter + @JsonSetter(nulls = Nulls.SKIP) private Integer permissionId = 0; @Getter @Setter + @JsonSetter(nulls = Nulls.SKIP) private String extraData = ""; @Getter @@ -205,4 +220,4 @@ private static boolean calldataEquals(String a, String b) { return Arrays.equals(ByteArray.fromHexString(a), ByteArray.fromHexString(b)); } -} \ No newline at end of file +} diff --git a/framework/src/main/java/org/tron/core/services/jsonrpc/types/CallArguments.java b/framework/src/main/java/org/tron/core/services/jsonrpc/types/CallArguments.java index 1715636a2a4..8219b108c70 100644 --- a/framework/src/main/java/org/tron/core/services/jsonrpc/types/CallArguments.java +++ b/framework/src/main/java/org/tron/core/services/jsonrpc/types/CallArguments.java @@ -5,6 +5,8 @@ import static org.tron.core.services.jsonrpc.JsonRpcApiUtil.parseQuantityValue; import static org.tron.core.services.jsonrpc.JsonRpcApiUtil.requireValidHex; +import com.fasterxml.jackson.annotation.JsonSetter; +import com.fasterxml.jackson.annotation.Nulls; import com.google.protobuf.ByteString; import lombok.AllArgsConstructor; import lombok.Getter; @@ -20,6 +22,13 @@ import org.tron.protos.Protocol.Transaction.Contract.ContractType; import org.tron.protos.contract.SmartContractOuterClass.SmartContract; +/** + * Arguments for EVM call-style JSON-RPC methods. + * + *

Fields annotated with {@link JsonSetter} retain their defaults when an explicit JSON + * {@code null} is deserialized. This protection applies to Jackson input only; direct setter calls + * may still assign {@code null} and callers must preserve the DTO invariants. + */ @NoArgsConstructor @AllArgsConstructor @ToString @@ -27,6 +36,7 @@ public class CallArguments { @Getter @Setter + @JsonSetter(nulls = Nulls.SKIP) private String from = "0x0000000000000000000000000000000000000000"; @Getter @Setter @@ -108,4 +118,4 @@ public ContractType getContractType(Wallet wallet) throws JsonRpcInvalidRequestE public long parseValue() throws JsonRpcInvalidParamsException { return parseQuantityValue(value); } -} \ No newline at end of file +} diff --git a/framework/src/test/java/org/tron/core/jsonrpc/JsonRpcArgumentNullBindingTest.java b/framework/src/test/java/org/tron/core/jsonrpc/JsonRpcArgumentNullBindingTest.java new file mode 100644 index 00000000000..f035f0a3949 --- /dev/null +++ b/framework/src/test/java/org/tron/core/jsonrpc/JsonRpcArgumentNullBindingTest.java @@ -0,0 +1,54 @@ +package org.tron.core.jsonrpc; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; +import org.junit.Assert; +import org.junit.Test; +import org.tron.core.services.jsonrpc.types.BuildArguments; +import org.tron.core.services.jsonrpc.types.CallArguments; + +public class JsonRpcArgumentNullBindingTest { + + private static final ObjectMapper MAPPER = new ObjectMapper(); + + @Test + public void testBuildArgumentsExplicitNullMatchesOmittedDefaults() throws Exception { + BuildArguments omitted = MAPPER.readValue("{}", BuildArguments.class); + String[] optionalFields = { + "tokenId", + "tokenValue", + "consumeUserResourcePercent", + "originEnergyLimit", + "permissionId", + "extraData" + }; + + for (String field : optionalFields) { + ObjectNode input = MAPPER.createObjectNode(); + input.putNull(field); + BuildArguments explicitNull = MAPPER.treeToValue(input, BuildArguments.class); + + assertBuildDefaultsEqual(omitted, explicitNull); + } + } + + @Test + public void testCallArgumentsNullFromMatchesOmittedDefault() throws Exception { + CallArguments omitted = MAPPER.readValue("{}", CallArguments.class); + CallArguments explicitNull = MAPPER.readValue("{\"from\":null}", CallArguments.class); + + Assert.assertEquals(omitted.getFrom(), explicitNull.getFrom()); + Assert.assertEquals( + "0x0000000000000000000000000000000000000000", explicitNull.getFrom()); + } + + private static void assertBuildDefaultsEqual(BuildArguments expected, BuildArguments actual) { + Assert.assertEquals(expected.getTokenId(), actual.getTokenId()); + Assert.assertEquals(expected.getTokenValue(), actual.getTokenValue()); + Assert.assertEquals(expected.getConsumeUserResourcePercent(), + actual.getConsumeUserResourcePercent()); + Assert.assertEquals(expected.getOriginEnergyLimit(), actual.getOriginEnergyLimit()); + Assert.assertEquals(expected.getPermissionId(), actual.getPermissionId()); + Assert.assertEquals(expected.getExtraData(), actual.getExtraData()); + } +} diff --git a/framework/src/test/java/org/tron/core/jsonrpc/JsonRpcCallAndEstimateGasTest.java b/framework/src/test/java/org/tron/core/jsonrpc/JsonRpcCallAndEstimateGasTest.java index 2ab455fa580..b64e3009a79 100644 --- a/framework/src/test/java/org/tron/core/jsonrpc/JsonRpcCallAndEstimateGasTest.java +++ b/framework/src/test/java/org/tron/core/jsonrpc/JsonRpcCallAndEstimateGasTest.java @@ -18,6 +18,7 @@ import org.tron.core.capsule.TransactionCapsule; import org.tron.core.db.Manager; import org.tron.core.exception.jsonrpc.JsonRpcInternalException; +import org.tron.core.exception.jsonrpc.JsonRpcInvalidParamsException; import org.tron.core.services.NodeInfoService; import org.tron.core.services.jsonrpc.TronJsonRpcImpl; import org.tron.core.services.jsonrpc.types.CallArguments; @@ -51,6 +52,34 @@ public void tearDown() throws Exception { CommonParameter.getInstance().setEstimateEnergy(originalEstimateEnergy); } + @Test + public void testNullCallArgumentsRejectedAsInvalidParams() throws Exception { + mockRpc = new TronJsonRpcImpl(mock(NodeInfoService.class), mock(Wallet.class)); + + JsonRpcInvalidParamsException callError = assertThrows(JsonRpcInvalidParamsException.class, + () -> mockRpc.getCall(null, "latest")); + Assert.assertEquals("invalid params", callError.getMessage()); + + JsonRpcInvalidParamsException estimateError = assertThrows(JsonRpcInvalidParamsException.class, + () -> mockRpc.estimateGas(null)); + Assert.assertEquals("invalid params", estimateError.getMessage()); + } + + @Test + public void testEstimateGasKeepsInvalidQuantityAndDataAsInvalidParams() throws Exception { + mockRpc = new TronJsonRpcImpl(mock(NodeInfoService.class), mock(Wallet.class)); + + CallArguments invalidQuantity = newCallArgs(); + invalidQuantity.setValue("0xzz"); + assertThrows(JsonRpcInvalidParamsException.class, + () -> mockRpc.estimateGas(invalidQuantity)); + + CallArguments invalidData = newCallArgs(); + invalidData.setData("0xzz"); + assertThrows(JsonRpcInvalidParamsException.class, + () -> mockRpc.estimateGas(invalidData)); + } + @Test public void testGetCallAppendsRevertReason() throws Exception { byte[] revertData = ByteArray.fromHexString(ERROR_REVERT_HEX); diff --git a/framework/src/test/java/org/tron/core/jsonrpc/JsonRpcTest.java b/framework/src/test/java/org/tron/core/jsonrpc/JsonRpcTest.java index 49f875f3823..74a1ab18f29 100644 --- a/framework/src/test/java/org/tron/core/jsonrpc/JsonRpcTest.java +++ b/framework/src/test/java/org/tron/core/jsonrpc/JsonRpcTest.java @@ -229,14 +229,16 @@ public void testLogFilter() { Assert.assertTrue(e.getMessage().contains("invalid topic")); } - // not empty topic and null cannot be in same level - try { - new LogFilter(new FilterRequest(null, null, null, new String[][] { - {"0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", null}, - }, null)); - } catch (JsonRpcInvalidParamsException e) { - Assert.assertTrue(e.getMessage().contains("invalid topic")); - } + // null is invalid inside an OR-list. Use ArrayList to match Jackson's nested-array binding. + ArrayList invalidOrTopics = new ArrayList<>(); + invalidOrTopics.add( + "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"); + invalidOrTopics.add(null); + JsonRpcInvalidParamsException invalidOrTopic = Assert.assertThrows( + JsonRpcInvalidParamsException.class, + () -> new LogFilter(new FilterRequest( + null, null, null, new Object[] {invalidOrTopics}, null))); + Assert.assertEquals("invalid topic(s): null", invalidOrTopic.getMessage()); // non-string element in address array -> -32602, not a leaked ClassCastException JsonRpcInvalidParamsException badAddrElement = Assert.assertThrows( @@ -287,6 +289,14 @@ public void testLogFilter() { } } + @Test + public void testNullLogFilterRequestRejectedAsInvalidParams() { + JsonRpcInvalidParamsException error = Assert.assertThrows( + JsonRpcInvalidParamsException.class, () -> new LogFilter(null)); + + Assert.assertEquals("invalid filter request", error.getMessage()); + } + @Test public void testLogFilterAddressSizeLimit() { // Two valid 20-byte addresses (40 hex chars with 0x prefix) diff --git a/framework/src/test/java/org/tron/core/jsonrpc/JsonrpcServiceTest.java b/framework/src/test/java/org/tron/core/jsonrpc/JsonrpcServiceTest.java index e8d14ace060..cb81b84c3ca 100644 --- a/framework/src/test/java/org/tron/core/jsonrpc/JsonrpcServiceTest.java +++ b/framework/src/test/java/org/tron/core/jsonrpc/JsonrpcServiceTest.java @@ -7,15 +7,26 @@ import static org.tron.core.services.jsonrpc.JsonRpcApiUtil.parseBlockTag; import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonNull; import com.google.gson.JsonObject; import com.google.protobuf.ByteString; +import com.googlecode.jsonrpc4j.JsonRpcServer; +import com.googlecode.jsonrpc4j.ProxyUtil; import io.prometheus.client.CollectorRegistry; import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; +import java.util.Map; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; import javax.annotation.Resource; import lombok.extern.slf4j.Slf4j; import org.apache.http.client.methods.CloseableHttpResponse; @@ -35,6 +46,7 @@ import org.tron.common.application.HttpService; import org.tron.common.parameter.CommonParameter; import org.tron.common.prometheus.Metrics; +import org.tron.common.runtime.RuntimeImpl; import org.tron.common.utils.ByteArray; import org.tron.common.utils.PublicMethod; import org.tron.common.utils.Sha256Hash; @@ -46,25 +58,39 @@ import org.tron.core.capsule.TransactionRetCapsule; import org.tron.core.capsule.utils.BlockUtil; import org.tron.core.config.args.Args; +import org.tron.core.db.TransactionTrace; +import org.tron.core.db2.ISession; +import org.tron.core.exception.ContractValidateException; +import org.tron.core.exception.ItemNotFoundException; import org.tron.core.exception.jsonrpc.JsonRpcInternalException; import org.tron.core.exception.jsonrpc.JsonRpcInvalidParamsException; import org.tron.core.services.NodeInfoService; +import org.tron.core.services.http.Util; import org.tron.core.services.interfaceJsonRpcOnPBFT.JsonRpcServiceOnPBFT; import org.tron.core.services.interfaceJsonRpcOnSolidity.JsonRpcServiceOnSolidity; import org.tron.core.services.jsonrpc.FullNodeJsonRpcHttpService; +import org.tron.core.services.jsonrpc.JsonRpcErrorResolver; +import org.tron.core.services.jsonrpc.TronJsonRpc; import org.tron.core.services.jsonrpc.TronJsonRpc.FilterRequest; import org.tron.core.services.jsonrpc.TronJsonRpc.LogFilterElement; import org.tron.core.services.jsonrpc.TronJsonRpcImpl; +import org.tron.core.services.jsonrpc.filters.BlockFilterAndResult; import org.tron.core.services.jsonrpc.filters.LogFilterWrapper; import org.tron.core.services.jsonrpc.types.BlockResult; import org.tron.core.services.jsonrpc.types.BuildArguments; +import org.tron.core.services.jsonrpc.types.CallArguments; import org.tron.core.services.jsonrpc.types.TransactionReceipt; import org.tron.core.services.jsonrpc.types.TransactionResult; +import org.tron.core.store.StoreFactory; +import org.tron.core.vm.config.ConfigLoader; +import org.tron.core.vm.config.VMConfig; import org.tron.json.JSON; import org.tron.json.JSONArray; import org.tron.json.JSONObject; import org.tron.protos.Protocol; +import org.tron.protos.Protocol.Transaction; import org.tron.protos.Protocol.Transaction.Contract.ContractType; +import org.tron.protos.Protocol.Transaction.Result.contractResult; import org.tron.protos.contract.BalanceContract.TransferContract; @@ -241,6 +267,11 @@ public void testWeb3Sha3() { @Test public void testGetBlockTransactionCountByHash() { + JsonRpcInvalidParamsException nullError = Assert.assertThrows( + JsonRpcInvalidParamsException.class, + () -> tronJsonRpc.ethGetBlockTransactionCountByHash(null)); + Assert.assertEquals("invalid hash value", nullError.getMessage()); + try { tronJsonRpc.ethGetBlockTransactionCountByHash("0x111111"); } catch (Exception e) { @@ -268,6 +299,11 @@ public void testGetBlockTransactionCountByHash() { @Test public void testGetBlockTransactionCountByNumber() { + JsonRpcInvalidParamsException nullError = Assert.assertThrows( + JsonRpcInvalidParamsException.class, + () -> tronJsonRpc.ethGetBlockTransactionCountByNumber(null)); + Assert.assertEquals("invalid block number", nullError.getMessage()); + String result = ""; try { result = tronJsonRpc.ethGetBlockTransactionCountByNumber("0x0"); @@ -322,6 +358,19 @@ public void testGetBlockByHash() { Assert.assertEquals(ByteArray.toJsonHex(blockCapsule1.getNum()), blockResult.getNumber()); Assert.assertEquals(blockCapsule1.getTransactions().size(), blockResult.getTransactions().length); + + try { + blockResult = tronJsonRpc.ethGetBlockByHash( + Hex.toHexString(blockCapsule1.getBlockId().getBytes()), null); + } catch (Exception e) { + Assert.fail(e.getMessage()); + } + Assert.assertTrue(blockResult.getTransactions()[0] instanceof String); + + JsonRpcInvalidParamsException nullHashError = Assert.assertThrows( + JsonRpcInvalidParamsException.class, + () -> tronJsonRpc.ethGetBlockByHash(null, true)); + Assert.assertEquals("invalid hash value", nullHashError.getMessage()); } @Test @@ -336,6 +385,18 @@ public void testGetBlockByNumber() { Assert.fail(); } Assert.assertEquals(ByteArray.toJsonHex(blockCapsule1.getNum()), blockResult.getNumber()); + + try { + blockResult = tronJsonRpc.ethGetBlockByNumber("latest", null); + } catch (Exception e) { + Assert.fail(e.getMessage()); + } + Assert.assertTrue(blockResult.getTransactions()[0] instanceof String); + + JsonRpcInvalidParamsException nullNumberError = Assert.assertThrows( + JsonRpcInvalidParamsException.class, + () -> tronJsonRpc.ethGetBlockByNumber(null, true)); + Assert.assertEquals("invalid block number", nullNumberError.getMessage()); Assert.assertEquals(blockCapsule1.getTransactions().size(), blockResult.getTransactions().length); Assert.assertEquals("0x0000000000000000", blockResult.getNonce()); @@ -392,6 +453,10 @@ public void testGetBlockByNumber() { @Test public void testGetTransactionByHash() { + JsonRpcInvalidParamsException nullError = Assert.assertThrows( + JsonRpcInvalidParamsException.class, () -> tronJsonRpc.getTransactionByHash(null)); + Assert.assertEquals("invalid hash value", nullError.getMessage()); + TransactionResult transactionResult = null; try { transactionResult = tronJsonRpc.getTransactionByHash( @@ -649,21 +714,346 @@ public void testGetABIOfSmartContract() { @Test public void testGetCall() { + CallArguments validArgs = newValidCallArguments(); Exception e1 = Assert.assertThrows(Exception.class, - () -> tronJsonRpc.getCall(null, "earliest")); + () -> tronJsonRpc.getCall(validArgs, "earliest")); Assert.assertEquals(TAG_NOT_SUPPORT_ERROR, e1.getMessage()); Exception e2 = Assert.assertThrows(Exception.class, - () -> tronJsonRpc.getCall(null, "pending")); + () -> tronJsonRpc.getCall(validArgs, "pending")); Assert.assertEquals(TAG_NOT_SUPPORT_ERROR, e2.getMessage()); Exception e3 = Assert.assertThrows(Exception.class, - () -> tronJsonRpc.getCall(null, "finalized")); + () -> tronJsonRpc.getCall(validArgs, "finalized")); Assert.assertEquals(TAG_NOT_SUPPORT_ERROR, e3.getMessage()); Exception e4 = Assert.assertThrows(Exception.class, - () -> tronJsonRpc.getCall(null, "safe")); + () -> tronJsonRpc.getCall(validArgs, "safe")); Assert.assertEquals(TAG_NOT_SUPPORT_ERROR, e4.getMessage()); + + JsonRpcInvalidParamsException nullArgsError = Assert.assertThrows( + JsonRpcInvalidParamsException.class, () -> tronJsonRpc.getCall(null, "latest")); + Assert.assertEquals("invalid params", nullArgsError.getMessage()); + + JsonRpcInvalidParamsException nullArgsWithUnsupportedTagError = Assert.assertThrows( + JsonRpcInvalidParamsException.class, () -> tronJsonRpc.getCall(null, "earliest")); + Assert.assertEquals("invalid params", nullArgsWithUnsupportedTagError.getMessage()); + + JsonRpcInvalidParamsException doubleNullError = Assert.assertThrows( + JsonRpcInvalidParamsException.class, () -> tronJsonRpc.getCall(null, null)); + Assert.assertEquals("invalid params", doubleNullError.getMessage()); + } + + @Test + public void testTopLevelNullParametersAtServiceLayer() throws Exception { + JsonRpcInvalidParamsException estimateError = Assert.assertThrows( + JsonRpcInvalidParamsException.class, () -> tronJsonRpc.estimateGas(null)); + Assert.assertEquals("invalid params", estimateError.getMessage()); + + JsonRpcInvalidParamsException buildError = Assert.assertThrows( + JsonRpcInvalidParamsException.class, () -> tronJsonRpc.buildTransaction(null)); + Assert.assertEquals("invalid params", buildError.getMessage()); + + JsonRpcInvalidParamsException logsError = Assert.assertThrows( + JsonRpcInvalidParamsException.class, () -> tronJsonRpc.getLogs(null)); + Assert.assertEquals("invalid filter request", logsError.getMessage()); + + JsonRpcInvalidParamsException newFilterError = Assert.assertThrows( + JsonRpcInvalidParamsException.class, () -> tronJsonRpc.newFilter(null)); + Assert.assertEquals("invalid filter request", newFilterError.getMessage()); + + Assert.assertFalse(tronJsonRpc.uninstallFilter(null)); + + ItemNotFoundException changesError = Assert.assertThrows(ItemNotFoundException.class, + () -> tronJsonRpc.getFilterChanges(null)); + Assert.assertEquals("filter not found", changesError.getMessage()); + + ItemNotFoundException filterLogsError = Assert.assertThrows(ItemNotFoundException.class, + () -> tronJsonRpc.getFilterLogs(null)); + Assert.assertEquals("filter not found", filterLogsError.getMessage()); + + Assert.assertFalse(tronJsonRpc.uninstallFilter("0xdeadbeef")); + + JsonRpcInvalidParamsException receiptError = Assert.assertThrows( + JsonRpcInvalidParamsException.class, () -> tronJsonRpc.getTransactionReceipt(null)); + Assert.assertEquals("invalid hash value", receiptError.getMessage()); + } + + @Test + public void testNullParameterWireContract() throws Exception { + JsonRpcServer server = newJsonRpcServer(); + + assertMappedError(handleJsonRpc(server, "eth_getLogs", jsonParams((Object) null)), + -32602, "invalid filter request"); + assertMappedError(handleJsonRpc(server, "eth_newFilter", jsonParams((Object) null)), + -32602, "invalid filter request"); + assertMappedError(handleJsonRpc(server, "eth_estimateGas", jsonParams((Object) null)), + -32602, "invalid params"); + assertMappedError(handleJsonRpc(server, "buildTransaction", jsonParams((Object) null)), + -32602, "invalid params"); + + assertFrameworkError(handleJsonRpc(server, "eth_call", jsonParams((Object) null)), + -32602, "method parameters invalid"); + assertMappedError(handleJsonRpc(server, "eth_call", jsonParams(null, null)), + -32602, "invalid params"); + assertMappedError(handleJsonRpc(server, "eth_call", jsonParams(null, "latest")), + -32602, "invalid params"); + + JSONObject uninstallNull = handleJsonRpc( + server, "eth_uninstallFilter", jsonParams((Object) null)); + assertBooleanResult(uninstallNull, false); + + assertBooleanResult(handleJsonRpc(server, "eth_uninstallFilter", + jsonParams("0xdeadbeef")), false); + assertMappedError(handleJsonRpc(server, "eth_getFilterChanges", jsonParams((Object) null)), + -32000, "filter not found"); + assertMappedError(handleJsonRpc(server, "eth_getFilterLogs", jsonParams((Object) null)), + -32000, "filter not found"); + + String blockHash = ByteArray.toJsonHex(blockCapsule1.getBlockId().getBytes()); + JSONObject blockByHash = handleJsonRpc( + server, "eth_getBlockByHash", jsonParams(blockHash, null)); + assertHashOnlyTransactions(blockByHash); + JSONObject blockByNumber = handleJsonRpc( + server, "eth_getBlockByNumber", jsonParams("latest", null)); + assertHashOnlyTransactions(blockByNumber); + + assertMappedError(handleJsonRpc(server, "eth_getBlockByHash", jsonParams(null, true)), + -32602, "invalid hash value"); + assertMappedError(handleJsonRpc(server, "eth_getBlockByNumber", jsonParams(null, true)), + -32602, "invalid block number"); + + String address = "0xabd4b9367799eaa3197fecb144eb71de1e049abc"; + assertMappedError(handleJsonRpc(server, "eth_getStorageAt", + jsonParams(address, null, "latest")), -32602, "invalid storage key value"); + assertMappedError(handleJsonRpc(server, "eth_getBlockTransactionCountByNumber", + jsonParams((Object) null)), -32602, "invalid block number"); + + assertMappedError(handleJsonRpc(server, "eth_getBlockReceipts", jsonParams((Object) null)), + -32602, "invalid block number"); + assertMappedError(handleJsonRpc(server, "eth_getTransactionByHash", jsonParams((Object) null)), + -32602, "invalid hash value"); + assertMappedError(handleJsonRpc(server, "eth_getTransactionReceipt", + jsonParams((Object) null)), -32602, "invalid hash value"); + assertMappedError(handleJsonRpc(server, "eth_getBlockTransactionCountByHash", + jsonParams((Object) null)), -32602, "invalid hash value"); + } + + @Test + public void testUninstallFilterLookupMisses() throws Exception { + JsonRpcServer server = newJsonRpcServer(); + for (String filterId : new String[] {null, "0xdeadbeef", "", "0x", "not-hex", "0xz"}) { + Assert.assertFalse("filter ID: " + filterId, tronJsonRpc.uninstallFilter(filterId)); + assertBooleanResult(handleJsonRpc(server, "eth_uninstallFilter", jsonParams(filterId)), + false); + } + } + + @Test + public void testUninstallInstalledFilters() throws Exception { + String eventId = tronJsonRpc.newFilter(new FilterRequest()); + String blockId = tronJsonRpc.newBlockFilter(); + String eventKey = ByteArray.fromHex(eventId); + String blockKey = ByteArray.fromHex(blockId); + + Assert.assertTrue(tronJsonRpc.getEventFilter2ResultFull().containsKey(eventKey)); + Assert.assertTrue(tronJsonRpc.getBlockFilter2ResultFull().containsKey(blockKey)); + Assert.assertTrue(tronJsonRpc.uninstallFilter(eventId)); + Assert.assertFalse(tronJsonRpc.getEventFilter2ResultFull().containsKey(eventKey)); + Assert.assertTrue(tronJsonRpc.getBlockFilter2ResultFull().containsKey(blockKey)); + Assert.assertFalse(tronJsonRpc.uninstallFilter(eventId)); + + Assert.assertTrue(tronJsonRpc.uninstallFilter(blockId)); + Assert.assertFalse(tronJsonRpc.getBlockFilter2ResultFull().containsKey(blockKey)); + Assert.assertFalse(tronJsonRpc.uninstallFilter(blockId)); + } + + @Test + public void testUninstallInstalledFiltersOverWire() throws Exception { + JsonRpcServer server = newJsonRpcServer(); + for (String method : new String[] {"eth_newFilter", "eth_newBlockFilter"}) { + boolean eventFilter = "eth_newFilter".equals(method); + JSONObject created = handleJsonRpc(server, method, + eventFilter ? jsonParams(new JsonObject()) : jsonParams()); + Assert.assertFalse(created.toJSONString(), created.containsKey("error")); + Assert.assertTrue(created.toJSONString(), created.get("result") instanceof String); + String filterId = created.getString("result"); + String key = ByteArray.fromHex(filterId); + Map filters = eventFilter + ? tronJsonRpc.getEventFilter2ResultFull() : tronJsonRpc.getBlockFilter2ResultFull(); + Assert.assertTrue(filters.containsKey(key)); + + assertBooleanResult(handleJsonRpc(server, "eth_uninstallFilter", jsonParams(filterId)), true); + Assert.assertFalse(filters.containsKey(key)); + assertBooleanResult(handleJsonRpc(server, "eth_uninstallFilter", jsonParams(filterId)), + false); + } + } + + @Test + public void testFilterLookupsStillRejectUnknownIds() throws Exception { + String filterId = "0xdeadbeef"; + ItemNotFoundException changesError = Assert.assertThrows(ItemNotFoundException.class, + () -> tronJsonRpc.getFilterChanges(filterId)); + Assert.assertEquals("filter not found", changesError.getMessage()); + ItemNotFoundException logsError = Assert.assertThrows(ItemNotFoundException.class, + () -> tronJsonRpc.getFilterLogs(filterId)); + Assert.assertEquals("filter not found", logsError.getMessage()); + + JsonRpcServer server = newJsonRpcServer(); + assertMappedError(handleJsonRpc(server, "eth_getFilterChanges", jsonParams(filterId)), + -32000, "filter not found"); + assertMappedError(handleJsonRpc(server, "eth_getFilterLogs", jsonParams(filterId)), + -32000, "filter not found"); + } + + @Test + public void testUninstallFilterPreservesIdNormalization() throws Exception { + for (String filterId : new String[] {"0xa", "a", "0x0a", "0a"}) { + tronJsonRpc.getBlockFilter2ResultFull().put("0a", new BlockFilterAndResult()); + Assert.assertTrue("filter ID: " + filterId, tronJsonRpc.uninstallFilter(filterId)); + Assert.assertFalse(tronJsonRpc.getBlockFilter2ResultFull().containsKey("0a")); + } + } + + @Test(timeout = 15000) + public void testConcurrentUninstallRemovesFilterOnce() throws Exception { + String filterId = tronJsonRpc.newBlockFilter(); + CountDownLatch ready = new CountDownLatch(2); + CountDownLatch start = new CountDownLatch(1); + ExecutorService executor = Executors.newFixedThreadPool(2); + // Keep cleanup failures suppressed when the test itself has already failed. + try (AutoCloseable cleanup = () -> { + start.countDown(); + executor.shutdownNow(); + try { + Assert.assertTrue("workers must terminate", executor.awaitTermination(5, TimeUnit.SECONDS)); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw e; + } + }) { + List> results = new ArrayList<>(); + for (int i = 0; i < 2; i++) { + results.add(executor.submit(() -> { + ready.countDown(); + if (!start.await(5, TimeUnit.SECONDS)) { + throw new IllegalStateException("timed out waiting to uninstall filter"); + } + return tronJsonRpc.uninstallFilter(filterId); + })); + } + Assert.assertTrue("workers must be ready", ready.await(5, TimeUnit.SECONDS)); + start.countDown(); + Assert.assertNotEquals(results.get(0).get(5, TimeUnit.SECONDS), + results.get(1).get(5, TimeUnit.SECONDS)); + Assert.assertFalse(tronJsonRpc.getBlockFilter2ResultFull() + .containsKey(ByteArray.fromHex(filterId))); + } + } + + @Test + public void testFilterFieldNullSemanticsOverWire() throws Exception { + JsonRpcServer server = newJsonRpcServer(); + String topic = + "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"; + + JsonObject nullAddress = new JsonObject(); + nullAddress.add("address", JsonNull.INSTANCE); + assertSuccessfulLogsResult(handleJsonRpc(server, "eth_getLogs", jsonParams(nullAddress))); + + JsonObject nullTopics = new JsonObject(); + nullTopics.add("topics", JsonNull.INSTANCE); + assertSuccessfulLogsResult(handleJsonRpc(server, "eth_getLogs", jsonParams(nullTopics))); + + JsonArray positionalTopics = new JsonArray(); + positionalTopics.add(topic); + positionalTopics.add(JsonNull.INSTANCE); + JsonObject positionalWildcard = new JsonObject(); + positionalWildcard.add("topics", positionalTopics); + assertSuccessfulLogsResult(handleJsonRpc( + server, "eth_getLogs", jsonParams(positionalWildcard))); + + JsonArray invalidOrList = new JsonArray(); + invalidOrList.add(topic); + invalidOrList.add(JsonNull.INSTANCE); + JsonArray nestedTopics = new JsonArray(); + nestedTopics.add(invalidOrList); + JsonObject invalidOrWildcard = new JsonObject(); + invalidOrWildcard.add("topics", nestedTopics); + assertMappedError(handleJsonRpc(server, "eth_getLogs", jsonParams(invalidOrWildcard)), + -32602, "invalid topic(s): null"); + } + + @Test + public void testOptionalDtoNullFieldsOverWire() throws Exception { + JsonRpcServer server = newJsonRpcServer(); + + JSONObject omittedTransfer = handleJsonRpc( + server, "buildTransaction", jsonParams(newTransferBuildArguments())); + assertSuccessfulResponse(omittedTransfer); + String[] optionalBuildFields = { + "tokenId", + "tokenValue", + "consumeUserResourcePercent", + "originEnergyLimit", + "permissionId", + "extraData" + }; + for (String field : optionalBuildFields) { + JsonObject explicitNullArgs = newTransferBuildArguments(); + explicitNullArgs.add(field, JsonNull.INSTANCE); + JSONObject explicitNull = handleJsonRpc( + server, "buildTransaction", jsonParams(explicitNullArgs)); + assertEquivalentBuiltTransaction(omittedTransfer, explicitNull); + } + + JsonObject omittedConsume = newCreateBuildArguments(); + omittedConsume.addProperty("originEnergyLimit", 10_000_000L); + JsonObject nullConsume = newCreateBuildArguments(); + nullConsume.add("consumeUserResourcePercent", JsonNull.INSTANCE); + nullConsume.addProperty("originEnergyLimit", 10_000_000L); + JSONObject omittedConsumeResponse = handleJsonRpc( + server, "buildTransaction", jsonParams(omittedConsume)); + JSONObject nullConsumeResponse = handleJsonRpc( + server, "buildTransaction", jsonParams(nullConsume)); + assertEquivalentCreatedContract(omittedConsumeResponse, nullConsumeResponse); + Assert.assertEquals(0L, getNewContract(nullConsumeResponse) + .getLongValue("consume_user_resource_percent")); + Assert.assertEquals(10_000_000L, getNewContract(nullConsumeResponse) + .getLongValue("origin_energy_limit")); + assertSuccessfulVmExecution(omittedConsumeResponse); + assertSuccessfulVmExecution(nullConsumeResponse); + + JsonObject omittedOrigin = newCreateBuildArguments(); + omittedOrigin.addProperty("consumeUserResourcePercent", 10L); + JsonObject nullOrigin = newCreateBuildArguments(); + nullOrigin.addProperty("consumeUserResourcePercent", 10L); + nullOrigin.add("originEnergyLimit", JsonNull.INSTANCE); + JSONObject omittedOriginResponse = handleJsonRpc( + server, "buildTransaction", jsonParams(omittedOrigin)); + JSONObject nullOriginResponse = handleJsonRpc( + server, "buildTransaction", jsonParams(nullOrigin)); + assertEquivalentCreatedContract(omittedOriginResponse, nullOriginResponse); + Assert.assertEquals(0L, + getNewContract(nullOriginResponse).getLongValue("origin_energy_limit")); + ContractValidateException omittedOriginError = Assert.assertThrows( + ContractValidateException.class, + () -> executeBuiltTransaction(omittedOriginResponse)); + ContractValidateException nullOriginError = Assert.assertThrows( + ContractValidateException.class, + () -> executeBuiltTransaction(nullOriginResponse)); + Assert.assertEquals("The originEnergyLimit must be > 0", omittedOriginError.getMessage()); + Assert.assertEquals(omittedOriginError.getMessage(), nullOriginError.getMessage()); + + JsonObject omittedFrom = newEstimateGasArguments(); + JsonObject nullFrom = newEstimateGasArguments(); + nullFrom.add("from", JsonNull.INSTANCE); + JSONObject omittedFromResponse = handleJsonRpc( + server, "eth_estimateGas", jsonParams(omittedFrom)); + JSONObject nullFromResponse = handleJsonRpc( + server, "eth_estimateGas", jsonParams(nullFrom)); + Assert.assertEquals(omittedFromResponse.toJSONString(), nullFromResponse.toJSONString()); } @Test @@ -773,35 +1163,36 @@ public void testGetTransactionByBlockNumberAndIndex() { */ @Test public void testGetCallWithBlockObject() { + CallArguments validArgs = newValidCallArguments(); // neither HashMap nor String -> invalid json request Exception nonMapEx = Assert.assertThrows(Exception.class, - () -> tronJsonRpc.getCall(null, new Object())); + () -> tronJsonRpc.getCall(validArgs, new Object())); Assert.assertEquals("invalid json request", nonMapEx.getMessage()); // HashMap without blockNumber/blockHash keys -> invalid json request Exception emptyMapEx = Assert.assertThrows(Exception.class, - () -> tronJsonRpc.getCall(null, new HashMap())); + () -> tronJsonRpc.getCall(validArgs, new HashMap())); Assert.assertEquals("invalid json request", emptyMapEx.getMessage()); // blockNumber with malformed hex -> invalid block number HashMap badHexParams = new HashMap<>(); badHexParams.put("blockNumber", "xxx"); Exception badHexEx = Assert.assertThrows(Exception.class, - () -> tronJsonRpc.getCall(null, badHexParams)); + () -> tronJsonRpc.getCall(validArgs, badHexParams)); Assert.assertEquals("invalid block number", badHexEx.getMessage()); // blockNumber overflows long -> invalid block number (longValueExact) HashMap overflowParams = new HashMap<>(); overflowParams.put("blockNumber", "0x10000000000000000"); Exception overflowEx = Assert.assertThrows(Exception.class, - () -> tronJsonRpc.getCall(null, overflowParams)); + () -> tronJsonRpc.getCall(validArgs, overflowParams)); Assert.assertEquals("invalid block number", overflowEx.getMessage()); // blockNumber points to a non-existent block -> header not found HashMap missingNumParams = new HashMap<>(); missingNumParams.put("blockNumber", "0x1"); Exception missingNumEx = Assert.assertThrows(Exception.class, - () -> tronJsonRpc.getCall(null, missingNumParams)); + () -> tronJsonRpc.getCall(validArgs, missingNumParams)); Assert.assertEquals("header not found", missingNumEx.getMessage()); // blockHash of an unknown block -> header for hash not found @@ -809,7 +1200,7 @@ public void testGetCallWithBlockObject() { missingHashParams.put("blockHash", "0x1111111111111111111111111111111111111111111111111111111111111111"); Exception missingHashEx = Assert.assertThrows(Exception.class, - () -> tronJsonRpc.getCall(null, missingHashParams)); + () -> tronJsonRpc.getCall(validArgs, missingHashParams)); Assert.assertEquals("header for hash not found", missingHashEx.getMessage()); } @@ -1632,4 +2023,193 @@ public void testJsonRpcSizeLimitIntegration() { fullNodeJsonRpcHttpService.stop(); } } + + private static JsonRpcServer newJsonRpcServer() { + ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); + Object compositeService = ProxyUtil.createCompositeServiceProxy( + classLoader, + new Object[] {tronJsonRpc}, + new Class[] {TronJsonRpc.class}, + true); + JsonRpcServer server = new JsonRpcServer(compositeService); + server.setErrorResolver(JsonRpcErrorResolver.INSTANCE); + server.setShouldLogInvocationErrors(false); + return server; + } + + private static JSONObject handleJsonRpc(JsonRpcServer server, String method, JsonArray params) + throws Exception { + JsonObject request = new JsonObject(); + request.addProperty("jsonrpc", "2.0"); + request.addProperty("method", method); + request.add("params", params); + request.addProperty("id", 1); + + ByteArrayOutputStream output = new ByteArrayOutputStream(); + server.handleRequest( + new ByteArrayInputStream(request.toString().getBytes("UTF-8")), output); + String responseBody = output.toString("UTF-8"); + + Assert.assertFalse(responseBody, responseBody.contains("java.")); + Assert.assertFalse(responseBody, responseBody.contains("NullPointerException")); + JSONObject response = JSON.parseObject(responseBody); + Assert.assertEquals("2.0", response.getString("jsonrpc")); + Assert.assertEquals(1, response.getIntValue("id")); + return response; + } + + private static JsonArray jsonParams(Object... values) { + JsonArray params = new JsonArray(); + for (Object value : values) { + if (value == null) { + params.add(JsonNull.INSTANCE); + } else if (value instanceof JsonElement) { + params.add((JsonElement) value); + } else if (value instanceof String) { + params.add((String) value); + } else if (value instanceof Boolean) { + params.add((Boolean) value); + } else if (value instanceof Number) { + params.add((Number) value); + } else { + throw new IllegalArgumentException("unsupported JSON parameter type: " + value.getClass()); + } + } + return params; + } + + private static void assertMappedError(JSONObject response, int code, String message) { + JSONObject error = response.getJSONObject("error"); + Assert.assertNotNull(response.toJSONString(), error); + Assert.assertEquals(code, error.getIntValue("code")); + Assert.assertEquals(message, error.getString("message")); + Assert.assertTrue(error.toJSONString(), error.containsKey("data")); + Assert.assertEquals("{}", error.get("data")); + } + + private static void assertFrameworkError(JSONObject response, int code, String message) { + JSONObject error = response.getJSONObject("error"); + Assert.assertNotNull(response.toJSONString(), error); + Assert.assertEquals(code, error.getIntValue("code")); + Assert.assertEquals(message, error.getString("message")); + Assert.assertFalse(error.toJSONString(), error.containsKey("data")); + } + + private static void assertSuccessfulLogsResult(JSONObject response) { + Assert.assertFalse(response.toJSONString(), response.containsKey("error")); + Assert.assertNotNull(response.toJSONString(), response.getJSONArray("result")); + } + + private static void assertBooleanResult(JSONObject response, boolean expected) { + Assert.assertEquals(response.toJSONString(), Boolean.valueOf(expected), response.get("result")); + Assert.assertFalse(response.toJSONString(), response.containsKey("error")); + } + + private static void assertHashOnlyTransactions(JSONObject response) { + JSONObject result = response.getJSONObject("result"); + Assert.assertNotNull(response.toJSONString(), result); + JSONArray transactions = result.getJSONArray("transactions"); + Assert.assertTrue(transactions.size() > 0); + Assert.assertTrue(transactions.get(0) instanceof String); + } + + private static JsonObject newTransferBuildArguments() { + JsonObject args = new JsonObject(); + args.addProperty("from", "0xabd4b9367799eaa3197fecb144eb71de1e049abc"); + args.addProperty("to", "0x548794500882809695a8a687866e76d4271a1abc"); + args.addProperty("value", "0x1f4"); + return args; + } + + private static JsonObject newCreateBuildArguments() { + JsonObject args = new JsonObject(); + args.addProperty("from", "0xabd4b9367799eaa3197fecb144eb71de1e049abc"); + args.addProperty("data", "60006000f3"); + args.addProperty("gas", "0xf4240"); + args.addProperty("abi", "[]"); + return args; + } + + private static JsonObject newEstimateGasArguments() { + JsonObject args = new JsonObject(); + args.addProperty("to", "0x548794500882809695a8a687866e76d4271a1abc"); + args.addProperty("value", "0x1"); + return args; + } + + private static void assertSuccessfulResponse(JSONObject response) { + Assert.assertFalse(response.toJSONString(), response.containsKey("error")); + Assert.assertNotNull(response.toJSONString(), response.getJSONObject("result")); + } + + private void assertSuccessfulVmExecution(JSONObject response) throws Exception { + TransactionTrace trace = executeBuiltTransaction(response); + Assert.assertEquals(contractResult.SUCCESS, trace.getRuntimeResult().getResultCode()); + Assert.assertFalse(trace.getRuntimeResult().isRevert()); + Assert.assertNull(trace.getRuntimeError()); + } + + private TransactionTrace executeBuiltTransaction(JSONObject response) throws Exception { + assertSuccessfulResponse(response); + JSONObject transactionJson = response.getJSONObject("result").getJSONObject("transaction"); + Transaction transaction = Util.packTransaction(transactionJson.toJSONString(), false); + Assert.assertNotNull(transactionJson.toJSONString(), transaction); + + boolean loaderWasDisabled = ConfigLoader.disable; + boolean energyLimitForkWasEnabled = VMConfig.getEnergyLimitHardFork(); + try { + ConfigLoader.disable = true; + VMConfig.initVmHardFork(true); + try (ISession ignored = dbManager.getRevokingStore().buildSession()) { + dbManager.getDynamicPropertiesStore().saveMaxCpuTimeOfOneTx(5_000L); + TransactionTrace trace = new TransactionTrace( + new TransactionCapsule(transaction), StoreFactory.getInstance(), new RuntimeImpl()); + trace.init(null); + trace.exec(); + return trace; + } + } finally { + ConfigLoader.disable = loaderWasDisabled; + VMConfig.initVmHardFork(energyLimitForkWasEnabled); + } + } + + private static void assertEquivalentBuiltTransaction(JSONObject expected, JSONObject actual) { + assertSuccessfulResponse(expected); + assertSuccessfulResponse(actual); + JSONObject expectedRawData = expected.getJSONObject("result") + .getJSONObject("transaction").getJSONObject("raw_data"); + JSONObject actualRawData = actual.getJSONObject("result") + .getJSONObject("transaction").getJSONObject("raw_data"); + Assert.assertEquals(expectedRawData.getJSONArray("contract").toJSONString(), + actualRawData.getJSONArray("contract").toJSONString()); + Assert.assertEquals(expectedRawData.get("data"), actualRawData.get("data")); + } + + private static void assertEquivalentCreatedContract(JSONObject expected, JSONObject actual) { + assertSuccessfulResponse(expected); + assertSuccessfulResponse(actual); + Assert.assertEquals(getNewContract(expected).toJSONString(), + getNewContract(actual).toJSONString()); + } + + private static JSONObject getNewContract(JSONObject response) { + return response.getJSONObject("result") + .getJSONObject("transaction") + .getJSONObject("raw_data") + .getJSONArray("contract") + .getJSONObject(0) + .getJSONObject("parameter") + .getJSONObject("value") + .getJSONObject("new_contract"); + } + + private static CallArguments newValidCallArguments() { + CallArguments args = new CallArguments(); + args.setFrom("0x0000000000000000000000000000000000000000"); + args.setTo("0x0000000000000000000000000000000000000001"); + args.setValue("0x0"); + args.setData("0x"); + return args; + } } diff --git a/framework/src/test/java/org/tron/core/jsonrpc/WalletCursorTest.java b/framework/src/test/java/org/tron/core/jsonrpc/WalletCursorTest.java index 24ca71a74bc..c7bd75614c3 100644 --- a/framework/src/test/java/org/tron/core/jsonrpc/WalletCursorTest.java +++ b/framework/src/test/java/org/tron/core/jsonrpc/WalletCursorTest.java @@ -16,11 +16,15 @@ import org.tron.core.capsule.AccountCapsule; import org.tron.core.config.args.Args; import org.tron.core.db2.core.Chainbase.Cursor; +import org.tron.core.exception.ItemNotFoundException; import org.tron.core.exception.jsonrpc.JsonRpcExceedLimitException; +import org.tron.core.exception.jsonrpc.JsonRpcInvalidParamsException; +import org.tron.core.exception.jsonrpc.JsonRpcMethodNotFoundException; import org.tron.core.services.NodeInfoService; import org.tron.core.services.jsonrpc.TronJsonRpc.FilterRequest; import org.tron.core.services.jsonrpc.TronJsonRpcImpl; import org.tron.core.services.jsonrpc.TronJsonRpcImpl.RequestSource; +import org.tron.core.services.jsonrpc.filters.BlockFilterAndResult; import org.tron.core.services.jsonrpc.filters.LogFilterAndResult; import org.tron.core.services.jsonrpc.types.BuildArguments; import org.tron.protos.Protocol; @@ -58,6 +62,114 @@ public void init() { init = true; } + @Test + public void testNullParameterChecksRespectRequestSource() throws Exception { + TronJsonRpcImpl tronJsonRpc = new TronJsonRpcImpl(nodeInfoService, wallet); + tronJsonRpc.setManager(dbManager); + + try { + dbManager.setCursor(Cursor.SOLIDITY); + Assert.assertThrows(JsonRpcInvalidParamsException.class, + () -> tronJsonRpc.newFilter(null)); + Assert.assertThrows(JsonRpcInvalidParamsException.class, + () -> tronJsonRpc.getLogs(null)); + Assert.assertFalse(tronJsonRpc.uninstallFilter(null)); + Assert.assertThrows(ItemNotFoundException.class, + () -> tronJsonRpc.getFilterChanges(null)); + Assert.assertThrows(ItemNotFoundException.class, + () -> tronJsonRpc.getFilterLogs(null)); + Assert.assertThrows(JsonRpcMethodNotFoundException.class, + () -> tronJsonRpc.buildTransaction(null)); + + dbManager.resetCursor(); + dbManager.setCursor(Cursor.PBFT); + Assert.assertThrows(JsonRpcMethodNotFoundException.class, + () -> tronJsonRpc.newFilter(null)); + Assert.assertThrows(JsonRpcMethodNotFoundException.class, + () -> tronJsonRpc.getLogs(null)); + Assert.assertThrows(JsonRpcMethodNotFoundException.class, + () -> tronJsonRpc.uninstallFilter(null)); + Assert.assertThrows(JsonRpcMethodNotFoundException.class, + () -> tronJsonRpc.getFilterChanges(null)); + Assert.assertThrows(JsonRpcMethodNotFoundException.class, + () -> tronJsonRpc.getFilterLogs(null)); + Assert.assertThrows(JsonRpcMethodNotFoundException.class, + () -> tronJsonRpc.buildTransaction(null)); + } finally { + dbManager.resetCursor(); + tronJsonRpc.close(); + } + } + + @Test + public void testUninstallFilterKeepsSourcesIsolated() throws Exception { + try (TronJsonRpcImpl tronJsonRpc = new TronJsonRpcImpl(nodeInfoService, wallet)) { + for (boolean eventFilter : new boolean[] {true, false}) { + if (eventFilter) { + tronJsonRpc.getEventFilter2ResultFull() + .put("10", new LogFilterAndResult(new FilterRequest(), 0L, wallet)); + tronJsonRpc.getEventFilter2ResultSolidity() + .put("20", new LogFilterAndResult(new FilterRequest(), 0L, wallet)); + } else { + tronJsonRpc.getBlockFilter2ResultFull().put("10", new BlockFilterAndResult()); + tronJsonRpc.getBlockFilter2ResultSolidity().put("20", new BlockFilterAndResult()); + } + Map fullFilters = eventFilter + ? tronJsonRpc.getEventFilter2ResultFull() : tronJsonRpc.getBlockFilter2ResultFull(); + Map solidityFilters = eventFilter + ? tronJsonRpc.getEventFilter2ResultSolidity() + : tronJsonRpc.getBlockFilter2ResultSolidity(); + Object fullFilter = fullFilters.get("10"); + Object solidityFilter = solidityFilters.get("20"); + + dbManager.resetCursor(); + dbManager.setCursor(Cursor.HEAD); + Assert.assertFalse(tronJsonRpc.uninstallFilter("0x20")); + Assert.assertSame(solidityFilter, solidityFilters.get("20")); + + dbManager.resetCursor(); + dbManager.setCursor(Cursor.SOLIDITY); + Assert.assertFalse(tronJsonRpc.uninstallFilter("0x10")); + Assert.assertSame(fullFilter, fullFilters.get("10")); + Assert.assertTrue(tronJsonRpc.uninstallFilter("0x20")); + Assert.assertFalse(solidityFilters.containsKey("20")); + Assert.assertFalse(tronJsonRpc.uninstallFilter("0x20")); + + dbManager.resetCursor(); + dbManager.setCursor(Cursor.HEAD); + Assert.assertTrue(tronJsonRpc.uninstallFilter("0x10")); + Assert.assertFalse(fullFilters.containsKey("10")); + Assert.assertFalse(tronJsonRpc.uninstallFilter("0x10")); + } + } finally { + dbManager.resetCursor(); + } + } + + @Test + public void testUninstallFilterInPbftRejectsAllIdsBeforeRemoval() throws Exception { + try (TronJsonRpcImpl tronJsonRpc = new TronJsonRpcImpl(nodeInfoService, wallet)) { + BlockFilterAndResult fullFilter = new BlockFilterAndResult(); + BlockFilterAndResult solidityFilter = new BlockFilterAndResult(); + tronJsonRpc.getBlockFilter2ResultFull().put("10", fullFilter); + tronJsonRpc.getBlockFilter2ResultSolidity().put("20", solidityFilter); + dbManager.setCursor(Cursor.PBFT); + + String[] filterIds = {null, "0x10", "0x20", "0xdeadbeef", "", "not-hex"}; + for (String filterId : filterIds) { + JsonRpcMethodNotFoundException error = Assert.assertThrows( + JsonRpcMethodNotFoundException.class, () -> tronJsonRpc.uninstallFilter(filterId)); + Assert.assertEquals( + "the method eth_uninstallFilter does not exist/is not available in PBFT", + error.getMessage()); + } + Assert.assertSame(fullFilter, tronJsonRpc.getBlockFilter2ResultFull().get("10")); + Assert.assertSame(solidityFilter, tronJsonRpc.getBlockFilter2ResultSolidity().get("20")); + } finally { + dbManager.resetCursor(); + } + } + @Test public void testSource() { TronJsonRpcImpl tronJsonRpc = new TronJsonRpcImpl(nodeInfoService, wallet); @@ -189,4 +301,4 @@ public void testNewFilter_exceedsCapThrowsException() throws Exception { } } -} \ No newline at end of file +}