Skip to content

Commit 0b55d1a

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() does pure precedence resolution -- `input` wins over `data` -- mirroring go-ethereum's TransactionArgs.data(). Five call sites in TronJsonRpcImpl use the resolver instead of getData(). The verb-prefix name (not getXxx) keeps Jackson and FastJSON's JavaBean introspection from picking the method up as a `resolveData` wire property; two serialisation tests pin this as a regression guard. Hex validation is mode-driven via JsonRpcApiUtil.requireValidHex (HexMode.STRICT / HexMode.LENIENT): - `input` (new field) is STRICT: 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). Conflict between `input` and `data` (both set, not equal) is detected on the build path at BuildArguments.getContractType(), mirroring geth's data() / setDefaults split. The error message matches go-ethereum's setDefaults wording verbatim. Comparison is byte-level so case differences are not flagged. The query path (CallArguments.resolveData()) stays lenient -- input wins silently. Tested by 52 unit tests including Jackson/FastJSON serialisation safety guards; BuildTransactionTest.testCreateSmartContract still passes; checkstyle clean.
1 parent 09127ab commit 0b55d1a

6 files changed

Lines changed: 608 additions & 29 deletions

File tree

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

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

442+
/**
443+
* Validation mode for {@link #requireValidHex}.
444+
*/
445+
public enum HexMode {
446+
/**
447+
* Execution-apis BYTES schema: requires {@code 0x} prefix and
448+
* even total length; {@code ""} is accepted as empty bytes per
449+
* geth's {@code hexutil.Bytes.UnmarshalText}.
450+
*/
451+
STRICT,
452+
/**
453+
* {@link ByteArray#fromHexString}'s lenient parsing: accepts bare
454+
* hex and odd-length input. Kept for backward compatibility.
455+
*/
456+
LENIENT
457+
}
458+
459+
/**
460+
* Throws if {@code value} is not parseable hex under the given
461+
* {@code mode}. {@code null} is treated as absent and returns
462+
* silently. {@code fieldName} is used only in error messages.
463+
*/
464+
public static void requireValidHex(String fieldName, String value, HexMode mode)
465+
throws JsonRpcInvalidParamsException {
466+
if (value == null) {
467+
return;
468+
}
469+
if (mode == HexMode.STRICT) {
470+
if (value.isEmpty()) {
471+
return;
472+
}
473+
if (!value.startsWith("0x") || value.length() % 2 != 0) {
474+
throw new JsonRpcInvalidParamsException(
475+
"invalid hex string for \"" + fieldName + "\"");
476+
}
477+
}
478+
try {
479+
ByteArray.fromHexString(value);
480+
} catch (Exception e) {
481+
throw new JsonRpcInvalidParamsException(
482+
"invalid hex string for \"" + fieldName + "\"");
483+
}
484+
}
485+
442486
public static long parseQuantityValue(String value) throws JsonRpcInvalidParamsException {
443487
long callValue = 0L;
444488

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: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,22 @@
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;
22+
import org.tron.core.services.jsonrpc.JsonRpcApiUtil.HexMode;
1923
import org.tron.protos.Protocol.Transaction.Contract.ContractType;
2024
import org.tron.protos.contract.SmartContractOuterClass.SmartContract;
2125

@@ -44,6 +48,9 @@ public class BuildArguments {
4448
private String data;
4549
@Getter
4650
@Setter
51+
private String input;
52+
@Getter
53+
@Setter
4754
private String nonce = ""; //not used
4855

4956
@Getter
@@ -83,16 +90,47 @@ public BuildArguments(CallArguments args) {
8390
gasPrice = args.getGasPrice();
8491
value = args.getValue();
8592
data = args.getData();
93+
input = args.getInput();
94+
}
95+
96+
/**
97+
* Returns {@code input} if non-null, else {@code data}. Pure
98+
* precedence resolution, mirroring go-ethereum's
99+
* {@code TransactionArgs.data()}.
100+
*
101+
* <p>Both fields are first validated by
102+
* {@link org.tron.core.services.jsonrpc.JsonRpcApiUtil#requireValidHex}
103+
* — strict for {@code input}, lenient for {@code data} (see that
104+
* method for the rules). Conflict between {@code input} and
105+
* {@code data} is detected at the build entry point
106+
* {@link #getContractType(Wallet)}, not here, mirroring geth's
107+
* {@code data()} / {@code setDefaults} split.
108+
*
109+
* <p>Java callers using positional constructors should pass
110+
* {@code null} (not {@code ""}) for unset {@code input}.
111+
*
112+
* <p>Verb-prefix name (not {@code getXxx}) keeps Jackson and
113+
* FastJSON's JavaBean introspection from invoking it during
114+
* serialisation; two regression tests per DTO pin this invariant.
115+
*/
116+
public String resolveData() throws JsonRpcInvalidParamsException {
117+
requireValidHex("input", input, HexMode.STRICT);
118+
requireValidHex("data", data, HexMode.LENIENT);
119+
return input != null ? input : data;
86120
}
87121

88122
public ContractType getContractType(Wallet wallet) throws JsonRpcInvalidRequestException,
89123
JsonRpcInvalidParamsException {
124+
// Fail fast on bad hex / conflict before the state lookup.
125+
String resolvedData = resolveData();
126+
validateCallDataConflict();
127+
90128
ContractType contractType;
91129

92130
// to is null
93131
if (paramStringIsNull(to)) {
94132
// data is null
95-
if (paramStringIsNull(data)) {
133+
if (paramStringIsNull(resolvedData)) {
96134
throw new JsonRpcInvalidRequestException("invalid json request");
97135
}
98136

@@ -136,4 +174,24 @@ private boolean availableTransferAsset() {
136174
return tokenId > 0 && tokenValue > 0 && paramQuantityIsNull(value);
137175
}
138176

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

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

Lines changed: 25 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;
@@ -15,6 +16,7 @@
1516
import org.tron.core.Wallet;
1617
import org.tron.core.exception.jsonrpc.JsonRpcInvalidParamsException;
1718
import org.tron.core.exception.jsonrpc.JsonRpcInvalidRequestException;
19+
import org.tron.core.services.jsonrpc.JsonRpcApiUtil.HexMode;
1820
import org.tron.protos.Protocol.Transaction.Contract.ContractType;
1921
import org.tron.protos.contract.SmartContractOuterClass.SmartContract;
2022

@@ -43,21 +45,40 @@ public class CallArguments {
4345
private String data;
4446
@Getter
4547
@Setter
48+
private String input;
49+
@Getter
50+
@Setter
4651
private String nonce; // not used
4752

53+
/**
54+
* Returns {@code input} if non-null, else {@code data}. Pure
55+
* precedence resolution, mirroring go-ethereum's
56+
* {@code TransactionArgs.data()}; see {@link BuildArguments#resolveData()}
57+
* for the rationale on naming, validation split, and serialiser
58+
* interaction.
59+
*/
60+
public String resolveData() throws JsonRpcInvalidParamsException {
61+
requireValidHex("input", input, HexMode.STRICT);
62+
requireValidHex("data", data, HexMode.LENIENT);
63+
return input != null ? input : data;
64+
}
65+
4866
/**
4967
* just support TransferContract, CreateSmartContract and TriggerSmartContract
5068
* */
5169
public ContractType getContractType(Wallet wallet) throws JsonRpcInvalidRequestException,
5270
JsonRpcInvalidParamsException {
53-
ContractType contractType;
54-
5571
// from or to is null
5672
if (paramStringIsNull(from)) {
5773
throw new JsonRpcInvalidRequestException("invalid json request");
58-
} else if (paramStringIsNull(to)) {
74+
}
75+
// Fail fast on bad hex before the state lookup.
76+
String resolvedData = resolveData();
77+
78+
ContractType contractType;
79+
if (paramStringIsNull(to)) {
5980
// data is null
60-
if (paramStringIsNull(data)) {
81+
if (paramStringIsNull(resolvedData)) {
6182
throw new JsonRpcInvalidRequestException("invalid json request");
6283
}
6384

0 commit comments

Comments
 (0)