Skip to content

Commit 426eb5f

Browse files
committed
fix(jsonrpc): accept "input" as alias for "data" in call args
Closes #6517. JSON-RPC requests using the execution-apis field name `input` were rejected with UnrecognizedPropertyException, blocking spec-compliant clients -- notably go-ethereum's ethclient since ethereum/go-ethereum#28078, which only emits `input`. CallArguments and BuildArguments now declare both fields. A new resolveData() prefers `input` over `data`, mirroring geth's TransactionArgs.data(). Five call sites in TronJsonRpcImpl use the resolver instead of getData(). The verb-prefix name (not getXxx) is the defence keeping JavaBean introspection in Jackson and FastJSON from picking the method up as a `resolveData` wire property; two serialisation tests pin this as a regression guard. Hex validation is asymmetric: - `input` (new field) follows the execution-apis BYTES schema -- requires `0x` prefix and even length; "" is accepted as empty bytes per geth's hexutil.Bytes.UnmarshalText. - `data` retains lenient parsing via ByteArray.fromHexString for backward compatibility with existing callers (e.g. BuildTransactionTest.testCreateSmartContract uses bare-hex bytecode). On the build path, conflicting input/data byte values throw JsonRpcInvalidParamsException with go-ethereum's setDefaults wording verbatim. The query path stays lenient (input wins silently). Comparison is byte-level so case differences are not flagged. Tested by 53 unit tests including Jackson/FastJSON serialisation safety guards; BuildTransactionTest.testCreateSmartContract still passes; checkstyle clean.
1 parent 09127ab commit 426eb5f

6 files changed

Lines changed: 606 additions & 29 deletions

File tree

framework/src/main/java/org/tron/core/services/jsonrpc/JsonRpcApiUtil.java

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -439,6 +439,39 @@ public static boolean paramQuantityIsNull(String quantity) {
439439
return StringUtils.isEmpty(quantity) || quantity.equals("0x0");
440440
}
441441

442+
/**
443+
* Throws if {@code value} is not parseable hex.
444+
*
445+
* <p>For {@code "input"}, applies the execution-apis BYTES schema:
446+
* {@code 0x} prefix and even length required; {@code ""} is accepted
447+
* as empty bytes per geth's {@code hexutil.Bytes.UnmarshalText}.
448+
*
449+
* <p>Other fields (notably {@code "data"}) keep
450+
* {@link ByteArray#fromHexString}'s lenient parsing for backward
451+
* compatibility.
452+
*/
453+
public static void requireValidHex(String fieldName, String value)
454+
throws JsonRpcInvalidParamsException {
455+
if (value == null) {
456+
return;
457+
}
458+
if ("input".equals(fieldName)) {
459+
if (value.isEmpty()) {
460+
return;
461+
}
462+
if (!value.startsWith("0x") || value.length() % 2 != 0) {
463+
throw new JsonRpcInvalidParamsException(
464+
"invalid hex string for \"" + fieldName + "\"");
465+
}
466+
}
467+
try {
468+
ByteArray.fromHexString(value);
469+
} catch (Exception e) {
470+
throw new JsonRpcInvalidParamsException(
471+
"invalid hex string for \"" + fieldName + "\"");
472+
}
473+
}
474+
442475
public static long parseQuantityValue(String value) throws JsonRpcInvalidParamsException {
443476
long callValue = 0L;
444477

framework/src/main/java/org/tron/core/services/jsonrpc/TronJsonRpcImpl.java

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -647,15 +647,15 @@ public String estimateGas(CallArguments args) throws JsonRpcInvalidRequestExcept
647647
estimateEnergy(ownerAddress,
648648
contractAddress,
649649
args.parseValue(),
650-
ByteArray.fromHexString(args.getData()),
650+
ByteArray.fromHexString(args.resolveData()),
651651
trxExtBuilder,
652652
retBuilder,
653653
estimateBuilder);
654654
} else {
655655
callTriggerConstantContract(ownerAddress,
656656
contractAddress,
657657
args.parseValue(),
658-
ByteArray.fromHexString(args.getData()),
658+
ByteArray.fromHexString(args.resolveData()),
659659
trxExtBuilder,
660660
retBuilder);
661661
}
@@ -1012,7 +1012,7 @@ public String getCall(CallArguments transactionCall, Object blockParamObj)
10121012
byte[] contractAddressData = addressCompatibleToByteArray(transactionCall.getTo());
10131013

10141014
return call(addressData, contractAddressData, transactionCall.parseValue(),
1015-
ByteArray.fromHexString(transactionCall.getData()));
1015+
ByteArray.fromHexString(transactionCall.resolveData()));
10161016
} else {
10171017
try {
10181018
ByteArray.hexToBigInteger(blockNumOrTag);
@@ -1128,7 +1128,8 @@ private TransactionJson buildCreateSmartContractTransaction(byte[] ownerAddress,
11281128
smartBuilder.setOriginAddress(ByteString.copyFrom(ownerAddress));
11291129

11301130
// bytecode + parameter
1131-
smartBuilder.setBytecode(ByteString.copyFrom(ByteArray.fromHexString(args.getData())));
1131+
smartBuilder.setBytecode(
1132+
ByteString.copyFrom(ByteArray.fromHexString(args.resolveData())));
11321133

11331134
if (StringUtils.isNotEmpty(args.getName())) {
11341135
smartBuilder.setName(args.getName());
@@ -1173,8 +1174,9 @@ private TransactionJson buildTriggerSmartContractTransaction(byte[] ownerAddress
11731174
build.setOwnerAddress(ByteString.copyFrom(ownerAddress))
11741175
.setContractAddress(ByteString.copyFrom(contractAddress));
11751176

1176-
if (StringUtils.isNotEmpty(args.getData())) {
1177-
build.setData(ByteString.copyFrom(ByteArray.fromHexString(args.getData())));
1177+
String callData = args.resolveData();
1178+
if (StringUtils.isNotEmpty(callData)) {
1179+
build.setData(ByteString.copyFrom(ByteArray.fromHexString(callData)));
11781180
} else {
11791181
build.setData(ByteString.copyFrom(new byte[0]));
11801182
}

framework/src/main/java/org/tron/core/services/jsonrpc/types/BuildArguments.java

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,18 @@
44
import static org.tron.core.services.jsonrpc.JsonRpcApiUtil.paramQuantityIsNull;
55
import static org.tron.core.services.jsonrpc.JsonRpcApiUtil.paramStringIsNull;
66
import static org.tron.core.services.jsonrpc.JsonRpcApiUtil.parseQuantityValue;
7+
import static org.tron.core.services.jsonrpc.JsonRpcApiUtil.requireValidHex;
78

89
import com.google.protobuf.ByteString;
10+
import java.util.Arrays;
911
import lombok.AllArgsConstructor;
1012
import lombok.Getter;
1113
import lombok.NoArgsConstructor;
1214
import lombok.Setter;
1315
import lombok.ToString;
1416
import org.apache.commons.lang3.StringUtils;
1517
import org.tron.api.GrpcAPI.BytesMessage;
18+
import org.tron.common.utils.ByteArray;
1619
import org.tron.core.Wallet;
1720
import org.tron.core.exception.jsonrpc.JsonRpcInvalidParamsException;
1821
import org.tron.core.exception.jsonrpc.JsonRpcInvalidRequestException;
@@ -44,6 +47,9 @@ public class BuildArguments {
4447
private String data;
4548
@Getter
4649
@Setter
50+
private String input;
51+
@Getter
52+
@Setter
4753
private String nonce = ""; //not used
4854

4955
@Getter
@@ -83,16 +89,41 @@ public BuildArguments(CallArguments args) {
8389
gasPrice = args.getGasPrice();
8490
value = args.getValue();
8591
data = args.getData();
92+
input = args.getInput();
93+
}
94+
95+
/**
96+
* Returns {@code input} if non-null, else {@code data}. Both go
97+
* through {@link org.tron.core.services.jsonrpc.JsonRpcApiUtil#requireValidHex}
98+
* (strict for {@code input}, lenient for {@code data}); see that
99+
* method for the rules.
100+
*
101+
* <p>Java callers using positional constructors should pass
102+
* {@code null} (not {@code ""}) for unset {@code input}. If both
103+
* fields are supplied and decode to non-equal bytes, throws — strict
104+
* build-path counterpart of {@link CallArguments#resolveData()}.
105+
*
106+
* <p>Verb-prefix name (not {@code getXxx}) keeps Jackson/FastJSON
107+
* introspection from invoking it during serialisation.
108+
*/
109+
public String resolveData() throws JsonRpcInvalidParamsException {
110+
requireValidHex("input", input);
111+
requireValidHex("data", data);
112+
validateCallDataConflict();
113+
return input != null ? input : data;
86114
}
87115

88116
public ContractType getContractType(Wallet wallet) throws JsonRpcInvalidRequestException,
89117
JsonRpcInvalidParamsException {
118+
// Fail fast on bad hex / conflict before the state lookup.
119+
String resolvedData = resolveData();
120+
90121
ContractType contractType;
91122

92123
// to is null
93124
if (paramStringIsNull(to)) {
94125
// data is null
95-
if (paramStringIsNull(data)) {
126+
if (paramStringIsNull(resolvedData)) {
96127
throw new JsonRpcInvalidRequestException("invalid json request");
97128
}
98129

@@ -136,4 +167,24 @@ private boolean availableTransferAsset() {
136167
return tokenId > 0 && tokenValue > 0 && paramQuantityIsNull(value);
137168
}
138169

170+
/**
171+
* Throws when both fields decode to non-equal bytes. Wording matches
172+
* geth's setDefaults so existing tooling can detect the error string.
173+
*/
174+
private void validateCallDataConflict() throws JsonRpcInvalidParamsException {
175+
if (input != null && data != null && !calldataEquals(input, data)) {
176+
throw new JsonRpcInvalidParamsException(
177+
"both \"data\" and \"input\" are set and not equal. "
178+
+ "Please use \"input\" to pass transaction call data");
179+
}
180+
}
181+
182+
/**
183+
* Byte-level equality, so {@code "0xDEAD"} equals {@code "0xdead"}. Both
184+
* args must have passed {@code requireValidHex} first.
185+
*/
186+
private static boolean calldataEquals(String a, String b) {
187+
return Arrays.equals(ByteArray.fromHexString(a), ByteArray.fromHexString(b));
188+
}
189+
139190
}

framework/src/main/java/org/tron/core/services/jsonrpc/types/CallArguments.java

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import static org.tron.core.services.jsonrpc.JsonRpcApiUtil.addressCompatibleToByteArray;
44
import static org.tron.core.services.jsonrpc.JsonRpcApiUtil.paramStringIsNull;
55
import static org.tron.core.services.jsonrpc.JsonRpcApiUtil.parseQuantityValue;
6+
import static org.tron.core.services.jsonrpc.JsonRpcApiUtil.requireValidHex;
67

78
import com.google.protobuf.ByteString;
89
import lombok.AllArgsConstructor;
@@ -43,21 +44,47 @@ public class CallArguments {
4344
private String data;
4445
@Getter
4546
@Setter
47+
private String input;
48+
@Getter
49+
@Setter
4650
private String nonce; // not used
4751

52+
/**
53+
* Returns {@code input} if non-null, else {@code data}. Both go
54+
* through {@link org.tron.core.services.jsonrpc.JsonRpcApiUtil#requireValidHex}
55+
* (strict for {@code input}, lenient for {@code data}); see that
56+
* method for the rules.
57+
*
58+
* <p>Java callers using positional constructors should pass
59+
* {@code null} (not {@code ""}) for unset {@code input}. No conflict
60+
* raised here — see {@link BuildArguments#resolveData()} for the
61+
* strict build-path version.
62+
*
63+
* <p>Verb-prefix name (not {@code getXxx}) keeps Jackson/FastJSON
64+
* introspection from invoking it during serialisation.
65+
*/
66+
public String resolveData() throws JsonRpcInvalidParamsException {
67+
requireValidHex("input", input);
68+
requireValidHex("data", data);
69+
return input != null ? input : data;
70+
}
71+
4872
/**
4973
* just support TransferContract, CreateSmartContract and TriggerSmartContract
5074
* */
5175
public ContractType getContractType(Wallet wallet) throws JsonRpcInvalidRequestException,
5276
JsonRpcInvalidParamsException {
53-
ContractType contractType;
54-
5577
// from or to is null
5678
if (paramStringIsNull(from)) {
5779
throw new JsonRpcInvalidRequestException("invalid json request");
58-
} else if (paramStringIsNull(to)) {
80+
}
81+
// Fail fast on bad hex before the state lookup.
82+
String resolvedData = resolveData();
83+
84+
ContractType contractType;
85+
if (paramStringIsNull(to)) {
5986
// data is null
60-
if (paramStringIsNull(data)) {
87+
if (paramStringIsNull(resolvedData)) {
6188
throw new JsonRpcInvalidRequestException("invalid json request");
6289
}
6390

0 commit comments

Comments
 (0)