Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
746abf9
refactor(framework): remove unused Wallet.getMerkleTreeOfBlock and Wa…
bladehan1 Apr 8, 2026
0fe5d6f
fix(api): replace Class.forName with whitelist in RateLimiterServlet
bladehan1 Apr 8, 2026
0980488
perf(api): optimize block JSON serialization in printBlockList and pr…
bladehan1 Mar 6, 2026
5080779
fix(api): handle JSONException in checkGetParam and reduce Solidity l…
bladehan1 Apr 8, 2026
508cf88
feat(api): add length guard for hexToBigInteger to prevent CPU DoS
bladehan1 Apr 9, 2026
29be3db
fix(api): relax hexToBigInteger length limit, improve tests and code …
bladehan1 Apr 16, 2026
7e966c9
fix(api): address PR review feedback on style and defensive defaults
bladehan1 Apr 16, 2026
88b7f26
test(api): add printBlockToJSON with-transactions and decimal BigInte…
bladehan1 Apr 16, 2026
5b67bf6
fix(api): address PR review feedback – log levels, limiter fallback, …
bladehan1 Apr 17, 2026
ac05ea8
test(api): clean up test style and add missing coverage
bladehan1 Apr 17, 2026
4c6df2d
refactor(api): move block-number length guard to JsonRpcApiUtil
bladehan1 Apr 21, 2026
3d41716
fix(api): restore fail-fast for unknown rate limiter adapter
bladehan1 Apr 21, 2026
f200e1f
test(api): verify visible flag in printBlockToJSON
bladehan1 Apr 21, 2026
f4f7412
test(api): revert goalless ByteArrayTest changes out of PR scope
bladehan1 Apr 21, 2026
db91a72
test(api): harden printBlockToJSON tests against drift
bladehan1 Apr 21, 2026
a437574
fix(api): validate block number range in parseBlockNumber
bladehan1 Apr 22, 2026
0fdf579
style(api): use Arrays.asList for typed adapter whitelist
bladehan1 Apr 22, 2026
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
50 changes: 10 additions & 40 deletions framework/src/main/java/org/tron/core/Wallet.java
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import static org.tron.core.config.Parameter.DatabaseConstants.MARKET_COUNT_LIMIT_MAX;
import static org.tron.core.config.Parameter.DatabaseConstants.PROPOSAL_COUNT_LIMIT_MAX;
import static org.tron.core.config.Parameter.DatabaseConstants.WITNESS_COUNT_LIMIT_MAX;
import static org.tron.core.services.jsonrpc.JsonRpcApiUtil.parseBlockNumber;
import static org.tron.core.services.jsonrpc.JsonRpcApiUtil.parseEnergyFee;
import static org.tron.core.services.jsonrpc.TronJsonRpcImpl.EARLIEST_STR;
import static org.tron.core.services.jsonrpc.TronJsonRpcImpl.FINALIZED_STR;
Expand Down Expand Up @@ -247,7 +248,6 @@
import org.tron.protos.contract.BalanceContract.BlockBalanceTrace;
import org.tron.protos.contract.BalanceContract.TransferContract;
import org.tron.protos.contract.Common;
import org.tron.protos.contract.ShieldContract.IncrementalMerkleTree;
import org.tron.protos.contract.ShieldContract.IncrementalMerkleVoucherInfo;
import org.tron.protos.contract.ShieldContract.OutputPoint;
import org.tron.protos.contract.ShieldContract.OutputPointInfo;
Expand Down Expand Up @@ -575,41 +575,41 @@ public GrpcAPI.Return broadcastTransaction(Transaction signedTransaction) {
return builder.setResult(true).setCode(response_code.SUCCESS).build();
}
} catch (ValidateSignatureException e) {
logger.warn(BROADCAST_TRANS_FAILED, txID, e.getMessage());
logger.info(BROADCAST_TRANS_FAILED, txID, e.getMessage());
return builder.setResult(false).setCode(response_code.SIGERROR)
.setMessage(ByteString.copyFromUtf8("Validate signature error: " + e.getMessage()))
.build();
} catch (ContractValidateException e) {
logger.warn(BROADCAST_TRANS_FAILED, txID, e.getMessage());
logger.info(BROADCAST_TRANS_FAILED, txID, e.getMessage());
return builder.setResult(false).setCode(response_code.CONTRACT_VALIDATE_ERROR)
.setMessage(ByteString.copyFromUtf8(CONTRACT_VALIDATE_ERROR + e.getMessage()))
.build();
} catch (ContractExeException e) {
logger.warn(BROADCAST_TRANS_FAILED, txID, e.getMessage());
logger.info(BROADCAST_TRANS_FAILED, txID, e.getMessage());
return builder.setResult(false).setCode(response_code.CONTRACT_EXE_ERROR)
.setMessage(ByteString.copyFromUtf8("Contract execute error : " + e.getMessage()))
.build();
} catch (AccountResourceInsufficientException e) {
logger.warn(BROADCAST_TRANS_FAILED, txID, e.getMessage());
logger.info(BROADCAST_TRANS_FAILED, txID, e.getMessage());
return builder.setResult(false).setCode(response_code.BANDWITH_ERROR)
.setMessage(ByteString.copyFromUtf8("Account resource insufficient error."))
.build();
} catch (DupTransactionException e) {
logger.warn(BROADCAST_TRANS_FAILED, txID, e.getMessage());
logger.info(BROADCAST_TRANS_FAILED, txID, e.getMessage());
return builder.setResult(false).setCode(response_code.DUP_TRANSACTION_ERROR)
.setMessage(ByteString.copyFromUtf8("Dup transaction."))
.build();
} catch (TaposException e) {
logger.warn(BROADCAST_TRANS_FAILED, txID, e.getMessage());
logger.info(BROADCAST_TRANS_FAILED, txID, e.getMessage());
return builder.setResult(false).setCode(response_code.TAPOS_ERROR)
.setMessage(ByteString.copyFromUtf8("Tapos check error."))
.build();
} catch (TooBigTransactionException e) {
logger.warn(BROADCAST_TRANS_FAILED, txID, e.getMessage());
logger.info(BROADCAST_TRANS_FAILED, txID, e.getMessage());
return builder.setResult(false).setCode(response_code.TOO_BIG_TRANSACTION_ERROR)
.setMessage(ByteString.copyFromUtf8(e.getMessage())).build();
} catch (TransactionExpirationException e) {
logger.warn(BROADCAST_TRANS_FAILED, txID, e.getMessage());
logger.info(BROADCAST_TRANS_FAILED, txID, e.getMessage());
return builder.setResult(false).setCode(response_code.TRANSACTION_EXPIRATION_ERROR)
.setMessage(ByteString.copyFromUtf8("Transaction expired"))
.build();
Expand Down Expand Up @@ -743,13 +743,7 @@ public Block getByJsonBlockId(String id) throws JsonRpcInvalidParamsException {
} else if (PENDING_STR.equalsIgnoreCase(id)) {
throw new JsonRpcInvalidParamsException(TAG_PENDING_SUPPORT_ERROR);
} else {
long blockNumber;
try {
blockNumber = ByteArray.hexToBigInteger(id).longValue();
} catch (Exception e) {
throw new JsonRpcInvalidParamsException("invalid block number");
}

long blockNumber = parseBlockNumber(id);
return getBlockByNum(blockNumber);
}
}
Expand Down Expand Up @@ -2207,23 +2201,6 @@ public IncrementalMerkleVoucherInfo getMerkleTreeVoucherInfo(OutputPointInfo req
return result.build();
}

public IncrementalMerkleTree getMerkleTreeOfBlock(long blockNum) throws ZksnarkException {
checkAllowShieldedTransactionApi();
if (blockNum < 0) {
return null;
}

try {
if (chainBaseManager.getMerkleTreeIndexStore().has(ByteArray.fromLong(blockNum))) {
return IncrementalMerkleTree
.parseFrom(chainBaseManager.getMerkleTreeIndexStore().get(blockNum));
}
} catch (Exception ex) {
logger.error("GetMerkleTreeOfBlock failed, blockNum:{}", blockNum, ex);
}

return null;
}

public long getShieldedTransactionFee() {
return chainBaseManager.getDynamicPropertiesStore().getShieldedTransactionFee();
Expand Down Expand Up @@ -2958,13 +2935,6 @@ public MarketOrderList getMarketOrderListByPair(byte[] sellTokenId, byte[] buyTo
return builder.build();
}

public Transaction deployContract(TransactionCapsule trxCap) {

// do nothing, so can add some useful function later
// trxCap contract para cacheUnpackValue has value

return trxCap.getInstance();
}

public Transaction triggerContract(TriggerSmartContract
triggerSmartContract,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -418,7 +418,7 @@ public void getAssetIssueByName(BytesMessage request,
responseObserver.onNext(wallet.getAssetIssueByName(assetName));
} catch (NonUniqueObjectException e) {
responseObserver.onNext(null);
logger.error("Solidity NonUniqueObjectException: {}", e.getMessage());
logger.debug("Solidity NonUniqueObjectException: {}", e.getMessage());
Comment thread
bladehan1 marked this conversation as resolved.
}
} else {
responseObserver.onNext(null);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,11 @@
import com.google.common.base.Strings;
import io.prometheus.client.Histogram;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.annotation.PostConstruct;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
Expand Down Expand Up @@ -31,56 +35,66 @@
@Slf4j
public abstract class RateLimiterServlet extends HttpServlet {
private static final String KEY_PREFIX_HTTP = "http_";
private static final String ADAPTER_PREFIX = "org.tron.core.services.ratelimiter.adapter.";

static final Map<String, Class<? extends IRateLimiter>> ALLOWED_ADAPTERS;
static final String DEFAULT_ADAPTER_NAME = DefaultBaseQqsAdapter.class.getSimpleName();

static {
List<Class<? extends IRateLimiter>> adapters = Arrays.asList(
GlobalPreemptibleAdapter.class,
QpsRateLimiterAdapter.class,
IPQPSRateLimiterAdapter.class,
DefaultBaseQqsAdapter.class);
Map<String, Class<? extends IRateLimiter>> m = new HashMap<>();
for (Class<? extends IRateLimiter> c : adapters) {
m.put(c.getSimpleName(), c);
}
ALLOWED_ADAPTERS = Collections.unmodifiableMap(m);
}

@Autowired
private RateLimiterContainer container;

@PostConstruct
private void addRateContainer() {
RateLimiterInitialization.HttpRateLimiterItem item = Args.getInstance()
.getRateLimiterInitialization().getHttpMap().get(getClass().getSimpleName());
boolean success = false;
final String name = getClass().getSimpleName();
if (item != null) {
String cName = "";
String params = "";
Object obj;
try {
cName = item.getStrategy();
params = item.getParams();
// add the specific rate limiter strategy of servlet.
Class<?> c = Class.forName(ADAPTER_PREFIX + cName);
Constructor constructor;
if (c == GlobalPreemptibleAdapter.class || c == QpsRateLimiterAdapter.class
|| c == IPQPSRateLimiterAdapter.class) {
constructor = c.getConstructor(String.class);
obj = constructor.newInstance(params);
container.add(KEY_PREFIX_HTTP, name, (IRateLimiter) obj);
} else {
constructor = c.getConstructor();
obj = constructor.newInstance(QpsStrategy.DEFAULT_QPS_PARAM);
container.add(KEY_PREFIX_HTTP, name, (IRateLimiter) obj);
}
success = true;
} catch (Exception e) {
this.throwTronError(cName, params, name, e);
}
RateLimiterInitialization.HttpRateLimiterItem item = Args.getInstance()
.getRateLimiterInitialization().getHttpMap().get(name);

String cName;
String params;
if (item == null) {
cName = DEFAULT_ADAPTER_NAME;
params = QpsStrategy.DEFAULT_QPS_PARAM;
} else {
cName = item.getStrategy();
params = item.getParams();
}
if (!success) {
// if the specific rate limiter strategy of servlet is not defined or fail to add,
// then add a default Strategy.
try {
IRateLimiter rateLimiter = new DefaultBaseQqsAdapter(QpsStrategy.DEFAULT_QPS_PARAM);
container.add(KEY_PREFIX_HTTP, name, rateLimiter);
} catch (Exception e) {
this.throwTronError("DefaultBaseQqsAdapter", QpsStrategy.DEFAULT_QPS_PARAM, name, e);
}

try {
container.add(KEY_PREFIX_HTTP, name, buildAdapter(cName, params, name));
} catch (Exception e) {
throw throwTronError(cName, params, name, e);
}
}

static IRateLimiter buildAdapter(String cName, String params, String name) {
Class<? extends IRateLimiter> c = ALLOWED_ADAPTERS.get(cName);
if (c == null) {
throw throwTronError(cName, params, name,
new IllegalArgumentException("unknown rate limiter adapter; allowed="
+ ALLOWED_ADAPTERS.keySet()));
}
try {
return c.getConstructor(String.class).newInstance(params);
} catch (Exception e) {
throw throwTronError(cName, params, name, e);
}
}

private void throwTronError(String strategy, String params, String servlet, Exception e) {
throw new TronError("failure to add the rate limiter strategy. servlet = " + servlet
private static TronError throwTronError(String strategy, String params, String servlet,
Exception e) {
return new TronError("failure to add the rate limiter strategy. servlet = " + servlet
+ ", strategy name = " + strategy + ", params = \"" + params + "\".",
e, TronError.ErrCode.RATE_LIMITER_INIT);
}
Expand Down
6 changes: 4 additions & 2 deletions framework/src/main/java/org/tron/core/services/http/Util.java
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ public static String printErrorMsg(Exception e) {

public static String printBlockList(BlockList list, boolean selfType) {
List<Block> blocks = list.getBlockList();
JSONObject jsonObject = JSONObject.parseObject(JsonFormat.printToString(list, selfType));
JSONObject jsonObject = new JSONObject();
JSONArray jsonArray = new JSONArray();
blocks.stream().forEach(block -> jsonArray.add(printBlockToJSON(block, selfType)));
jsonObject.put("block", jsonArray);
Expand All @@ -110,8 +110,10 @@ public static String printBlock(Block block, boolean selfType) {
public static JSONObject printBlockToJSON(Block block, boolean selfType) {
BlockCapsule blockCapsule = new BlockCapsule(block);
String blockID = ByteArray.toHexString(blockCapsule.getBlockId().getBytes());
JSONObject jsonObject = JSONObject.parseObject(JsonFormat.printToString(block, selfType));
JSONObject jsonObject = new JSONObject();
jsonObject.put("blockID", blockID);
jsonObject.put("block_header",
JSONObject.parseObject(JsonFormat.printToString(block.getBlockHeader(), selfType)));
if (!blockCapsule.getTransactions().isEmpty()) {
jsonObject.put("transactions",
printTransactionListToJSON(blockCapsule.getTransactions(), selfType));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import com.google.common.primitives.Longs;
import com.google.protobuf.Any;
import com.google.protobuf.ByteString;
import java.math.BigInteger;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.List;
Expand Down Expand Up @@ -515,6 +516,43 @@ public static long parseEnergyFee(long timestamp, String energyPriceHistory) {
return -1;
}

/**
* Max allowed length for a JSON-RPC block number hex/decimal input.
* API-level DoS guard: rejects pathological inputs before BigInteger parsing,
* whose cost grows quadratically with length. Covers hex (0x + 64 chars for
* uint256) and decimal (78 chars for uint256) representations with headroom.
*/
private static final int MAX_BLOCK_NUM_HEX_LEN = 100;

private static final String BLOCK_NUM_ERROR = "invalid block number";

/**
* Parse a JSON-RPC block number (hex "0x..." or decimal) into a long,
* enforcing the {@link #MAX_BLOCK_NUM_HEX_LEN} length limit, rejecting
* negative values, and rejecting values that overflow a signed 64-bit
* block number.
*/
public static long parseBlockNumber(String blockNumOrTag)
throws JsonRpcInvalidParamsException {
if (blockNumOrTag == null || blockNumOrTag.length() > MAX_BLOCK_NUM_HEX_LEN) {
throw new JsonRpcInvalidParamsException(BLOCK_NUM_ERROR);
}
BigInteger value;
try {
value = ByteArray.hexToBigInteger(blockNumOrTag);
} catch (Exception e) {
throw new JsonRpcInvalidParamsException(BLOCK_NUM_ERROR);
}
if (value.signum() < 0) {
throw new JsonRpcInvalidParamsException(BLOCK_NUM_ERROR);
}
try {
return value.longValueExact();
} catch (ArithmeticException e) {
throw new JsonRpcInvalidParamsException(BLOCK_NUM_ERROR);
}
}

public static long getByJsonBlockId(String blockNumOrTag, Wallet wallet)
throws JsonRpcInvalidParamsException {
if (PENDING_STR.equalsIgnoreCase(blockNumOrTag)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import static org.tron.core.services.jsonrpc.JsonRpcApiUtil.getEnergyUsageTotal;
import static org.tron.core.services.jsonrpc.JsonRpcApiUtil.getTransactionIndex;
import static org.tron.core.services.jsonrpc.JsonRpcApiUtil.getTxID;
import static org.tron.core.services.jsonrpc.JsonRpcApiUtil.parseBlockNumber;
import static org.tron.core.services.jsonrpc.JsonRpcApiUtil.triggerCallContract;

import com.alibaba.fastjson.JSON;
Expand Down Expand Up @@ -409,12 +410,7 @@ public String getTrxBalance(String address, String blockNumOrTag)
}
return ByteArray.toJsonHex(balance);
} else {
try {
ByteArray.hexToBigInteger(blockNumOrTag);
} catch (Exception e) {
throw new JsonRpcInvalidParamsException(BLOCK_NUM_ERROR);
}

parseBlockNumber(blockNumOrTag);
throw new JsonRpcInvalidParamsException(QUANTITY_NOT_SUPPORT_ERROR);
}
}
Expand Down Expand Up @@ -558,12 +554,7 @@ public String getStorageAt(String address, String storageIdx, String blockNumOrT
DataWord value = storage.getValue(new DataWord(ByteArray.fromHexString(storageIdx)));
return ByteArray.toJsonHex(value == null ? new byte[32] : value.getData());
} else {
try {
ByteArray.hexToBigInteger(blockNumOrTag);
} catch (Exception e) {
throw new JsonRpcInvalidParamsException(BLOCK_NUM_ERROR);
}

parseBlockNumber(blockNumOrTag);
throw new JsonRpcInvalidParamsException(QUANTITY_NOT_SUPPORT_ERROR);
}
}
Expand All @@ -589,12 +580,7 @@ public String getABIOfSmartContract(String contractAddress, String blockNumOrTag
}

} else {
try {
ByteArray.hexToBigInteger(blockNumOrTag);
} catch (Exception e) {
throw new JsonRpcInvalidParamsException(BLOCK_NUM_ERROR);
}

parseBlockNumber(blockNumOrTag);
throw new JsonRpcInvalidParamsException(QUANTITY_NOT_SUPPORT_ERROR);
}
}
Expand Down Expand Up @@ -971,12 +957,7 @@ public String getCall(CallArguments transactionCall, Object blockParamObj)
throw new JsonRpcInvalidParamsException(JSON_ERROR);
}

long blockNumber;
try {
blockNumber = ByteArray.hexToBigInteger(blockNumOrTag).longValue();
} catch (Exception e) {
throw new JsonRpcInvalidParamsException(BLOCK_NUM_ERROR);
}
long blockNumber = parseBlockNumber(blockNumOrTag);

if (wallet.getBlockByNum(blockNumber) == null) {
throw new JsonRpcInternalException(NO_BLOCK_HEADER);
Expand Down Expand Up @@ -1014,12 +995,7 @@ public String getCall(CallArguments transactionCall, Object blockParamObj)
return call(addressData, contractAddressData, transactionCall.parseValue(),
ByteArray.fromHexString(transactionCall.getData()));
} else {
try {
ByteArray.hexToBigInteger(blockNumOrTag);
} catch (Exception e) {
throw new JsonRpcInvalidParamsException(BLOCK_NUM_ERROR);
}

parseBlockNumber(blockNumOrTag);
throw new JsonRpcInvalidParamsException(QUANTITY_NOT_SUPPORT_ERROR);
}
}
Expand Down
Loading
Loading