Skip to content

Commit d71e1e4

Browse files
authored
feat(api): optimize and harden HTTP/JSON-RPC API layer (#6693)
1 parent bd3b5ef commit d71e1e4

9 files changed

Lines changed: 425 additions & 95 deletions

File tree

framework/src/main/java/org/tron/core/Wallet.java

Lines changed: 8 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -241,7 +241,6 @@
241241
import org.tron.protos.contract.BalanceContract.BlockBalanceTrace;
242242
import org.tron.protos.contract.BalanceContract.TransferContract;
243243
import org.tron.protos.contract.Common;
244-
import org.tron.protos.contract.ShieldContract.IncrementalMerkleTree;
245244
import org.tron.protos.contract.ShieldContract.IncrementalMerkleVoucherInfo;
246245
import org.tron.protos.contract.ShieldContract.OutputPoint;
247246
import org.tron.protos.contract.ShieldContract.OutputPointInfo;
@@ -569,41 +568,41 @@ public GrpcAPI.Return broadcastTransaction(Transaction signedTransaction) {
569568
return builder.setResult(true).setCode(response_code.SUCCESS).build();
570569
}
571570
} catch (ValidateSignatureException e) {
572-
logger.warn(BROADCAST_TRANS_FAILED, txID, e.getMessage());
571+
logger.info(BROADCAST_TRANS_FAILED, txID, e.getMessage());
573572
return builder.setResult(false).setCode(response_code.SIGERROR)
574573
.setMessage(ByteString.copyFromUtf8("Validate signature error: " + e.getMessage()))
575574
.build();
576575
} catch (ContractValidateException e) {
577-
logger.warn(BROADCAST_TRANS_FAILED, txID, e.getMessage());
576+
logger.info(BROADCAST_TRANS_FAILED, txID, e.getMessage());
578577
return builder.setResult(false).setCode(response_code.CONTRACT_VALIDATE_ERROR)
579578
.setMessage(ByteString.copyFromUtf8(CONTRACT_VALIDATE_ERROR + e.getMessage()))
580579
.build();
581580
} catch (ContractExeException e) {
582-
logger.warn(BROADCAST_TRANS_FAILED, txID, e.getMessage());
581+
logger.info(BROADCAST_TRANS_FAILED, txID, e.getMessage());
583582
return builder.setResult(false).setCode(response_code.CONTRACT_EXE_ERROR)
584583
.setMessage(ByteString.copyFromUtf8("Contract execute error : " + e.getMessage()))
585584
.build();
586585
} catch (AccountResourceInsufficientException e) {
587-
logger.warn(BROADCAST_TRANS_FAILED, txID, e.getMessage());
586+
logger.info(BROADCAST_TRANS_FAILED, txID, e.getMessage());
588587
return builder.setResult(false).setCode(response_code.BANDWITH_ERROR)
589588
.setMessage(ByteString.copyFromUtf8("Account resource insufficient error."))
590589
.build();
591590
} catch (DupTransactionException e) {
592-
logger.warn(BROADCAST_TRANS_FAILED, txID, e.getMessage());
591+
logger.info(BROADCAST_TRANS_FAILED, txID, e.getMessage());
593592
return builder.setResult(false).setCode(response_code.DUP_TRANSACTION_ERROR)
594593
.setMessage(ByteString.copyFromUtf8("Dup transaction."))
595594
.build();
596595
} catch (TaposException e) {
597-
logger.warn(BROADCAST_TRANS_FAILED, txID, e.getMessage());
596+
logger.info(BROADCAST_TRANS_FAILED, txID, e.getMessage());
598597
return builder.setResult(false).setCode(response_code.TAPOS_ERROR)
599598
.setMessage(ByteString.copyFromUtf8("Tapos check error."))
600599
.build();
601600
} catch (TooBigTransactionException e) {
602-
logger.warn(BROADCAST_TRANS_FAILED, txID, e.getMessage());
601+
logger.info(BROADCAST_TRANS_FAILED, txID, e.getMessage());
603602
return builder.setResult(false).setCode(response_code.TOO_BIG_TRANSACTION_ERROR)
604603
.setMessage(ByteString.copyFromUtf8(e.getMessage())).build();
605604
} catch (TransactionExpirationException e) {
606-
logger.warn(BROADCAST_TRANS_FAILED, txID, e.getMessage());
605+
logger.info(BROADCAST_TRANS_FAILED, txID, e.getMessage());
607606
return builder.setResult(false).setCode(response_code.TRANSACTION_EXPIRATION_ERROR)
608607
.setMessage(ByteString.copyFromUtf8("Transaction expired"))
609608
.build();
@@ -2179,23 +2178,6 @@ public IncrementalMerkleVoucherInfo getMerkleTreeVoucherInfo(OutputPointInfo req
21792178
return result.build();
21802179
}
21812180

2182-
public IncrementalMerkleTree getMerkleTreeOfBlock(long blockNum) throws ZksnarkException {
2183-
checkAllowShieldedTransactionApi();
2184-
if (blockNum < 0) {
2185-
return null;
2186-
}
2187-
2188-
try {
2189-
if (chainBaseManager.getMerkleTreeIndexStore().has(ByteArray.fromLong(blockNum))) {
2190-
return IncrementalMerkleTree
2191-
.parseFrom(chainBaseManager.getMerkleTreeIndexStore().get(blockNum));
2192-
}
2193-
} catch (Exception ex) {
2194-
logger.error("GetMerkleTreeOfBlock failed, blockNum:{}", blockNum, ex);
2195-
}
2196-
2197-
return null;
2198-
}
21992181

22002182
public long getShieldedTransactionFee() {
22012183
return chainBaseManager.getDynamicPropertiesStore().getShieldedTransactionFee();
@@ -2930,13 +2912,6 @@ public MarketOrderList getMarketOrderListByPair(byte[] sellTokenId, byte[] buyTo
29302912
return builder.build();
29312913
}
29322914

2933-
public Transaction deployContract(TransactionCapsule trxCap) {
2934-
2935-
// do nothing, so can add some useful function later
2936-
// trxCap contract para cacheUnpackValue has value
2937-
2938-
return trxCap.getInstance();
2939-
}
29402915

29412916
public Transaction triggerContract(TriggerSmartContract
29422917
triggerSmartContract,

framework/src/main/java/org/tron/core/services/RpcApiService.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -418,7 +418,7 @@ public void getAssetIssueByName(BytesMessage request,
418418
responseObserver.onNext(wallet.getAssetIssueByName(assetName));
419419
} catch (NonUniqueObjectException e) {
420420
responseObserver.onNext(null);
421-
logger.error("Solidity NonUniqueObjectException: {}", e.getMessage());
421+
logger.debug("Solidity NonUniqueObjectException: {}", e.getMessage());
422422
}
423423
} else {
424424
responseObserver.onNext(null);

framework/src/main/java/org/tron/core/services/http/RateLimiterServlet.java

Lines changed: 54 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,11 @@
33
import com.google.common.base.Strings;
44
import io.prometheus.client.Histogram;
55
import java.io.IOException;
6-
import java.lang.reflect.Constructor;
6+
import java.util.Arrays;
7+
import java.util.Collections;
8+
import java.util.HashMap;
9+
import java.util.List;
10+
import java.util.Map;
711
import javax.annotation.PostConstruct;
812
import javax.servlet.ServletException;
913
import javax.servlet.http.HttpServlet;
@@ -31,56 +35,66 @@
3135
@Slf4j
3236
public abstract class RateLimiterServlet extends HttpServlet {
3337
private static final String KEY_PREFIX_HTTP = "http_";
34-
private static final String ADAPTER_PREFIX = "org.tron.core.services.ratelimiter.adapter.";
38+
39+
static final Map<String, Class<? extends IRateLimiter>> ALLOWED_ADAPTERS;
40+
static final String DEFAULT_ADAPTER_NAME = DefaultBaseQqsAdapter.class.getSimpleName();
41+
42+
static {
43+
List<Class<? extends IRateLimiter>> adapters = Arrays.asList(
44+
GlobalPreemptibleAdapter.class,
45+
QpsRateLimiterAdapter.class,
46+
IPQPSRateLimiterAdapter.class,
47+
DefaultBaseQqsAdapter.class);
48+
Map<String, Class<? extends IRateLimiter>> m = new HashMap<>();
49+
for (Class<? extends IRateLimiter> c : adapters) {
50+
m.put(c.getSimpleName(), c);
51+
}
52+
ALLOWED_ADAPTERS = Collections.unmodifiableMap(m);
53+
}
3554

3655
@Autowired
3756
private RateLimiterContainer container;
3857

3958
@PostConstruct
4059
private void addRateContainer() {
41-
RateLimiterInitialization.HttpRateLimiterItem item = Args.getInstance()
42-
.getRateLimiterInitialization().getHttpMap().get(getClass().getSimpleName());
43-
boolean success = false;
4460
final String name = getClass().getSimpleName();
45-
if (item != null) {
46-
String cName = "";
47-
String params = "";
48-
Object obj;
49-
try {
50-
cName = item.getStrategy();
51-
params = item.getParams();
52-
// add the specific rate limiter strategy of servlet.
53-
Class<?> c = Class.forName(ADAPTER_PREFIX + cName);
54-
Constructor constructor;
55-
if (c == GlobalPreemptibleAdapter.class || c == QpsRateLimiterAdapter.class
56-
|| c == IPQPSRateLimiterAdapter.class) {
57-
constructor = c.getConstructor(String.class);
58-
obj = constructor.newInstance(params);
59-
container.add(KEY_PREFIX_HTTP, name, (IRateLimiter) obj);
60-
} else {
61-
constructor = c.getConstructor();
62-
obj = constructor.newInstance(QpsStrategy.DEFAULT_QPS_PARAM);
63-
container.add(KEY_PREFIX_HTTP, name, (IRateLimiter) obj);
64-
}
65-
success = true;
66-
} catch (Exception e) {
67-
this.throwTronError(cName, params, name, e);
68-
}
61+
RateLimiterInitialization.HttpRateLimiterItem item = Args.getInstance()
62+
.getRateLimiterInitialization().getHttpMap().get(name);
63+
64+
String cName;
65+
String params;
66+
if (item == null) {
67+
cName = DEFAULT_ADAPTER_NAME;
68+
params = QpsStrategy.DEFAULT_QPS_PARAM;
69+
} else {
70+
cName = item.getStrategy();
71+
params = item.getParams();
6972
}
70-
if (!success) {
71-
// if the specific rate limiter strategy of servlet is not defined or fail to add,
72-
// then add a default Strategy.
73-
try {
74-
IRateLimiter rateLimiter = new DefaultBaseQqsAdapter(QpsStrategy.DEFAULT_QPS_PARAM);
75-
container.add(KEY_PREFIX_HTTP, name, rateLimiter);
76-
} catch (Exception e) {
77-
this.throwTronError("DefaultBaseQqsAdapter", QpsStrategy.DEFAULT_QPS_PARAM, name, e);
78-
}
73+
74+
try {
75+
container.add(KEY_PREFIX_HTTP, name, buildAdapter(cName, params, name));
76+
} catch (Exception e) {
77+
throw rateLimiterInitError(cName, params, name, e);
78+
}
79+
}
80+
81+
static IRateLimiter buildAdapter(String cName, String params, String name) {
82+
Class<? extends IRateLimiter> c = ALLOWED_ADAPTERS.get(cName);
83+
if (c == null) {
84+
throw rateLimiterInitError(cName, params, name,
85+
new IllegalArgumentException("unknown rate limiter adapter; allowed="
86+
+ ALLOWED_ADAPTERS.keySet()));
87+
}
88+
try {
89+
return c.getConstructor(String.class).newInstance(params);
90+
} catch (Exception e) {
91+
throw rateLimiterInitError(cName, params, name, e);
7992
}
8093
}
8194

82-
private void throwTronError(String strategy, String params, String servlet, Exception e) {
83-
throw new TronError("failure to add the rate limiter strategy. servlet = " + servlet
95+
private static TronError rateLimiterInitError(String strategy, String params, String servlet,
96+
Exception e) {
97+
return new TronError("failure to add the rate limiter strategy. servlet = " + servlet
8498
+ ", strategy name = " + strategy + ", params = \"" + params + "\".",
8599
e, TronError.ErrCode.RATE_LIMITER_INIT);
86100
}

framework/src/main/java/org/tron/core/services/http/Util.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ public static String printErrorMsg(Exception e) {
9595

9696
public static String printBlockList(BlockList list, boolean selfType) {
9797
List<Block> blocks = list.getBlockList();
98-
JSONObject jsonObject = JSONObject.parseObject(JsonFormat.printToString(list, selfType));
98+
JSONObject jsonObject = new JSONObject();
9999
JSONArray jsonArray = new JSONArray();
100100
blocks.stream().forEach(block -> jsonArray.add(printBlockToJSON(block, selfType)));
101101
jsonObject.put("block", jsonArray);
@@ -110,8 +110,10 @@ public static String printBlock(Block block, boolean selfType) {
110110
public static JSONObject printBlockToJSON(Block block, boolean selfType) {
111111
BlockCapsule blockCapsule = new BlockCapsule(block);
112112
String blockID = ByteArray.toHexString(blockCapsule.getBlockId().getBytes());
113-
JSONObject jsonObject = JSONObject.parseObject(JsonFormat.printToString(block, selfType));
113+
JSONObject jsonObject = new JSONObject();
114114
jsonObject.put("blockID", blockID);
115+
jsonObject.put("block_header",
116+
JSONObject.parseObject(JsonFormat.printToString(block.getBlockHeader(), selfType)));
115117
if (!blockCapsule.getTransactions().isEmpty()) {
116118
jsonObject.put("transactions",
117119
printTransactionListToJSON(blockCapsule.getTransactions(), selfType));

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

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import com.google.common.primitives.Longs;
55
import com.google.protobuf.Any;
66
import com.google.protobuf.ByteString;
7+
import java.math.BigInteger;
78
import java.security.SecureRandom;
89
import java.util.ArrayList;
910
import java.util.List;
@@ -597,6 +598,41 @@ public static long parseBlockTag(String tag, Wallet wallet)
597598
throw new JsonRpcInvalidParamsException(BLOCK_NUM_ERROR);
598599
}
599600

601+
/**
602+
* Max allowed length for a JSON-RPC block number hex/decimal input.
603+
* API-level DoS guard: rejects pathological inputs before BigInteger parsing,
604+
* whose cost grows quadratically with length. Covers hex (0x + 64 chars for
605+
* uint256) and decimal (78 chars for uint256) representations with headroom.
606+
*/
607+
private static final int MAX_BLOCK_NUM_HEX_LEN = 100;
608+
609+
/**
610+
* Parse a JSON-RPC block number (hex "0x..." or decimal) into a long,
611+
* enforcing the {@link #MAX_BLOCK_NUM_HEX_LEN} length limit, rejecting
612+
* negative values, and rejecting values that overflow a signed 64-bit
613+
* block number.
614+
*/
615+
public static long parseBlockNumber(String blockNum)
616+
throws JsonRpcInvalidParamsException {
617+
if (blockNum == null || blockNum.length() > MAX_BLOCK_NUM_HEX_LEN) {
618+
throw new JsonRpcInvalidParamsException(BLOCK_NUM_ERROR);
619+
}
620+
BigInteger value;
621+
try {
622+
value = ByteArray.hexToBigInteger(blockNum);
623+
} catch (Exception e) {
624+
throw new JsonRpcInvalidParamsException(BLOCK_NUM_ERROR);
625+
}
626+
if (value.signum() < 0) {
627+
throw new JsonRpcInvalidParamsException(BLOCK_NUM_ERROR);
628+
}
629+
try {
630+
return value.longValueExact();
631+
} catch (ArithmeticException e) {
632+
throw new JsonRpcInvalidParamsException(BLOCK_NUM_ERROR);
633+
}
634+
}
635+
600636
/**
601637
* Parse a block tag or hex number. Uses strict jsonHexToLong (requires 0x prefix) for hex.
602638
* Callers needing flexible hex parsing (0x -> hex, bare number -> decimal) should use

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

Lines changed: 4 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import static org.tron.core.services.jsonrpc.JsonRpcApiUtil.getEnergyUsageTotal;
1212
import static org.tron.core.services.jsonrpc.JsonRpcApiUtil.getTransactionIndex;
1313
import static org.tron.core.services.jsonrpc.JsonRpcApiUtil.getTxID;
14+
import static org.tron.core.services.jsonrpc.JsonRpcApiUtil.parseBlockNumber;
1415
import static org.tron.core.services.jsonrpc.JsonRpcApiUtil.triggerCallContract;
1516

1617
import com.alibaba.fastjson.JSON;
@@ -354,11 +355,7 @@ private void requireLatestBlockTag(String blockNumOrTag)
354355
if (JsonRpcApiUtil.isBlockTag(blockNumOrTag)) {
355356
throw new JsonRpcInvalidParamsException(TAG_NOT_SUPPORT_ERROR);
356357
}
357-
try {
358-
ByteArray.hexToBigInteger(blockNumOrTag);
359-
} catch (Exception e) {
360-
throw new JsonRpcInvalidParamsException(BLOCK_NUM_ERROR);
361-
}
358+
parseBlockNumber(blockNumOrTag);
362359
throw new JsonRpcInvalidParamsException(QUANTITY_NOT_SUPPORT_ERROR);
363360
}
364361

@@ -368,7 +365,6 @@ private Block getBlockByJsonHash(String blockHash) throws JsonRpcInvalidParamsEx
368365
}
369366

370367
private Block getBlockByNumOrTag(String blockNumOrTag) throws JsonRpcInvalidParamsException {
371-
long blockNum;
372368
if (JsonRpcApiUtil.isBlockTag(blockNumOrTag)) {
373369
if (LATEST_STR.equalsIgnoreCase(blockNumOrTag)) {
374370
// Return the head block directly from blockStore, bypassing blockIndexStore
@@ -377,12 +373,7 @@ private Block getBlockByNumOrTag(String blockNumOrTag) throws JsonRpcInvalidPara
377373
}
378374
return wallet.getBlockByNum(JsonRpcApiUtil.parseBlockTag(blockNumOrTag, wallet));
379375
}
380-
try {
381-
blockNum = ByteArray.hexToBigInteger(blockNumOrTag).longValueExact();
382-
} catch (Exception e) {
383-
throw new JsonRpcInvalidParamsException(BLOCK_NUM_ERROR);
384-
}
385-
return wallet.getBlockByNum(blockNum);
376+
return wallet.getBlockByNum(parseBlockNumber(blockNumOrTag));
386377
}
387378

388379
private BlockResult getBlockResult(Block block, boolean fullTx) {
@@ -969,12 +960,7 @@ public String getCall(CallArguments transactionCall, Object blockParamObj)
969960
throw new JsonRpcInvalidParamsException(JSON_ERROR);
970961
}
971962

972-
long blockNumber;
973-
try {
974-
blockNumber = ByteArray.hexToBigInteger(blockNumOrTag).longValueExact();
975-
} catch (Exception e) {
976-
throw new JsonRpcInvalidParamsException(BLOCK_NUM_ERROR);
977-
}
963+
long blockNumber = parseBlockNumber(blockNumOrTag);
978964

979965
if (wallet.getBlockByNum(blockNumber) == null) {
980966
throw new JsonRpcInternalException(NO_BLOCK_HEADER);

0 commit comments

Comments
 (0)