Skip to content
Open
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 @@ -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();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -362,17 +363,28 @@ public String ethGetBlockTransactionCountByNumber(String blockNumOrTag)
@Override
public BlockResult ethGetBlockByHash(String blockHash, Boolean fullTransactionObjects)
throws JsonRpcInvalidParamsException {
final Block b = getBlockByJsonHash(blockHash);
byte[] hash = hashToByteArray(blockHash);
requireFullTransactionObjects(fullTransactionObjects);
final Block b = wallet.getBlockById(ByteString.copyFrom(hash));
return getBlockResult(b, fullTransactionObjects);
}

@Override
public BlockResult ethGetBlockByNumber(String blockNumOrTag, Boolean fullTransactionObjects)
throws JsonRpcInvalidParamsException {
final Block b = getBlockByNumOrTag(blockNumOrTag);
Long blockNum = parseBlockSelector(blockNumOrTag);
requireFullTransactionObjects(fullTransactionObjects);
final Block b = getBlockBySelector(blockNum);
return (b == null ? null : getBlockResult(b, fullTransactionObjects));
}

private static void requireFullTransactionObjects(Boolean fullTransactionObjects)
throws JsonRpcInvalidParamsException {
if (fullTransactionObjects == null) {
throw new JsonRpcInvalidParamsException(INVALID_PARAMS);
}
}

/**
* Reject any block selector that is not "latest".
* Accepts "latest" silently; throws for other tags, numeric blocks, or invalid input.
Expand All @@ -395,15 +407,29 @@ private Block getBlockByJsonHash(String blockHash) throws JsonRpcInvalidParamsEx
}

private Block getBlockByNumOrTag(String blockNumOrTag) throws JsonRpcInvalidParamsException {
return getBlockBySelector(parseBlockSelector(blockNumOrTag));
}

/**
* Validates a block number or tag without reading a block. Returns null for "latest".
*/
private Long parseBlockSelector(String blockNumOrTag) throws JsonRpcInvalidParamsException {
if (JsonRpcApiUtil.isBlockTag(blockNumOrTag)) {
if (LATEST_STR.equalsIgnoreCase(blockNumOrTag)) {
// Return the head block directly from blockStore, bypassing blockIndexStore
// which may not yet be written when latestBlockHeaderNumber is already updated.
return wallet.getNowBlock();
return null;
}
return wallet.getBlockByNum(JsonRpcApiUtil.parseBlockTag(blockNumOrTag, wallet));
return JsonRpcApiUtil.parseBlockTag(blockNumOrTag, wallet);
}
return parseBlockNumber(blockNumOrTag);
}

private Block getBlockBySelector(Long blockNum) {
if (blockNum == null) {
// Return the head block directly from blockStore, bypassing blockIndexStore
// which may not yet be written when latestBlockHeaderNumber is already updated.
return wallet.getNowBlock();
}
return wallet.getBlockByNum(parseBlockNumber(blockNumOrTag));
return wallet.getBlockByNum(blockNum);
}

private BlockResult getBlockResult(Block block, boolean fullTx) {
Expand Down Expand Up @@ -673,6 +699,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);
Expand Down Expand Up @@ -1001,6 +1031,9 @@ private List<TransactionReceipt> 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) {
Expand Down Expand Up @@ -1333,6 +1366,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());
Expand Down Expand Up @@ -1441,6 +1478,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())) {
Expand Down Expand Up @@ -1488,10 +1529,14 @@ public String newBlockFilter() throws JsonRpcMethodNotFoundException,
}

@Override
public boolean uninstallFilter(String filterId) throws ItemNotFoundException,
public boolean uninstallFilter(String filterId) throws JsonRpcInvalidParamsException,
JsonRpcMethodNotFoundException {
disableInPBFT("eth_uninstallFilter");

if (filterId == null) {
throw new JsonRpcInvalidParamsException(INVALID_PARAMS);
}

Map<String, BlockFilterAndResult> blockFilter2Result;
Map<String, LogFilterAndResult> eventFilter2Result;
if (getSource() == RequestSource.FULLNODE) {
Expand All @@ -1503,22 +1548,21 @@ 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
public Object[] getFilterChanges(String filterId) throws ItemNotFoundException,
JsonRpcMethodNotFoundException {
JsonRpcInvalidParamsException, JsonRpcMethodNotFoundException {
disableInPBFT("eth_getFilterChanges");

if (filterId == null) {
throw new JsonRpcInvalidParamsException(INVALID_PARAMS);
}

Map<String, BlockFilterAndResult> blockFilter2Result;
Map<String, LogFilterAndResult> eventFilter2Result;
if (getSource() == RequestSource.FULLNODE) {
Expand All @@ -1540,6 +1584,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);
Expand All @@ -1554,6 +1602,10 @@ public LogFilterElement[] getFilterLogs(String filterId) throws
JsonRpcMethodNotFoundException, JsonRpcTooManyResultException {
disableInPBFT("eth_getFilterLogs");

if (filterId == null) {
throw new JsonRpcInvalidParamsException(INVALID_PARAMS);
}

Map<String, LogFilterAndResult> eventFilter2Result;
if (getSource() == RequestSource.FULLNODE) {
eventFilter2Result = eventFilter2ResultFull;
Expand Down
Original file line number Diff line number Diff line change
@@ -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;

Expand Down Expand Up @@ -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<byte[][]> topics = new ArrayList<>();
Expand All @@ -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()));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -23,6 +25,13 @@
import org.tron.protos.Protocol.Transaction.Contract.ContractType;
import org.tron.protos.contract.SmartContractOuterClass.SmartContract;

/**
* Arguments for {@code buildTransaction}.
*
* <p>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
Expand Down Expand Up @@ -65,28 +74,34 @@ 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
private String name = "";

@Getter
@Setter
@JsonSetter(nulls = Nulls.SKIP)
private Integer permissionId = 0;
@Getter
@Setter
@JsonSetter(nulls = Nulls.SKIP)
private String extraData = "";

@Getter
Expand Down Expand Up @@ -205,4 +220,4 @@ private static boolean calldataEquals(String a, String b) {
return Arrays.equals(ByteArray.fromHexString(a), ByteArray.fromHexString(b));
}

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -20,13 +22,21 @@
import org.tron.protos.Protocol.Transaction.Contract.ContractType;
import org.tron.protos.contract.SmartContractOuterClass.SmartContract;

/**
* Arguments for EVM call-style JSON-RPC methods.
*
* <p>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
public class CallArguments {

@Getter
@Setter
@JsonSetter(nulls = Nulls.SKIP)
private String from = "0x0000000000000000000000000000000000000000";
@Getter
@Setter
Expand Down Expand Up @@ -108,4 +118,4 @@ public ContractType getContractType(Wallet wallet) throws JsonRpcInvalidRequestE
public long parseValue() throws JsonRpcInvalidParamsException {
return parseQuantityValue(value);
}
}
}
Original file line number Diff line number Diff line change
@@ -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());
}
}
Loading
Loading