Skip to content
Merged
11 changes: 10 additions & 1 deletion chainbase/src/main/java/org/tron/common/utils/Commons.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ public class Commons {

public static final int ASSET_ISSUE_COUNT_LIMIT_MAX = 1000;

public static final int BASE58_ADDRESS_LENGTH = 34;

public static byte[] decode58Check(String input) {
byte[] decodeCheck = Base58.decode(input);
if (decodeCheck.length <= 4) {
Expand All @@ -41,9 +43,16 @@ public static byte[] decode58Check(String input) {
return null;
}

/**
* Decode a Base58Check address string to its 21-byte form.
*/
public static byte[] decodeFromBase58Check(String addressBase58) {
if (StringUtils.isEmpty(addressBase58)) {
logger.warn("Warning: Address is empty !!");
logger.debug("address is empty !!");
return null;
}
if (addressBase58.length() != BASE58_ADDRESS_LENGTH) {
logger.debug("invalid Base58 address length");
return null;
}
byte[] address = decode58Check(addressBase58);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1313,7 +1313,18 @@ static ByteString unescapeBytesSelfType(String input, final String fliedName)
throws InvalidEscapeSequence {
//Address base58 -> ByteString
if (HttpSelfFormatFieldName.isAddressFormat(fliedName)) {
return ByteString.copyFrom(Commons.decodeFromBase58Check(input));
byte[] addressBytes = null;
try {
addressBytes = Commons.decodeFromBase58Check(input);
} catch (IllegalArgumentException e) {
// Base58.decode throws on illegal chars -> leave addressBytes null (treated as invalid)
}
if (addressBytes == null) {
// empty / wrong-length / bad-checksum / illegal chars -> all invalid addresses; throw a
// clear error instead of letting ByteString.copyFrom(null) throw a bare NPE.
throw new InvalidEscapeSequence("invalid address for field: " + fliedName);
}
return ByteString.copyFrom(addressBytes);
}

//Normal String -> ByteString
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,13 @@
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Pattern;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.bouncycastle.util.encoders.Hex;
import org.tron.api.GrpcAPI.AssetIssueList;
import org.tron.common.crypto.Hash;
import org.tron.common.math.StrictMathWrapper;
import org.tron.common.parameter.CommonParameter;
import org.tron.common.runtime.vm.DataWord;
import org.tron.common.utils.ByteArray;
Expand Down Expand Up @@ -60,6 +62,7 @@ public class JsonRpcApiUtil {
public static final String TAG_PENDING_SUPPORT_ERROR = "TAG pending not supported";
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";

private static final SecureRandom random = new SecureRandom();

Expand Down Expand Up @@ -395,46 +398,90 @@ public static long getUnfreezeAssetAmount(byte[] addressBytes, Wallet wallet) {
*/
public static byte[] addressCompatibleToByteArray(String hexAddress)
throws JsonRpcInvalidParamsException {
// ADDRESS_SIZE (42) is the hex length of a 21-byte address; +2 leaves room for the optional
// "0x" prefix, so a 0x-prefixed 21-byte address (0x41..., 44 chars) is still accepted.
if (hexAddress == null || hexAddress.length() > DecodeUtil.ADDRESS_SIZE + 2) {
throw new JsonRpcInvalidParamsException("invalid address");
}
byte[] addressByte;
try {
addressByte = ByteArray.fromHexString(hexAddress);
if (addressByte.length != DecodeUtil.ADDRESS_SIZE / 2
&& addressByte.length != DecodeUtil.ADDRESS_SIZE / 2 - 1) {
throw new JsonRpcInvalidParamsException("invalid address hash value");
throw new JsonRpcInvalidParamsException("invalid address");
}

if (addressByte.length == DecodeUtil.ADDRESS_SIZE / 2 - 1) {
addressByte = ByteUtil.merge(new byte[] {DecodeUtil.addressPreFixByte}, addressByte);
} else if (addressByte[0] != ByteArray.fromHexString(DecodeUtil.addressPreFixString)[0]) {
// addressByte.length == DecodeUtil.ADDRESS_SIZE / 2
throw new JsonRpcInvalidParamsException("invalid address hash value");
throw new JsonRpcInvalidParamsException("invalid address");
}
} catch (Exception e) {
throw new JsonRpcInvalidParamsException(e.getMessage());
}
return addressByte;
}

/** Matches a 32-byte hash hex string: optional 0x prefix + 64 hex chars (also caps length). */
public static final String HASH_REGEX = "^(0x)?[0-9a-fA-F]{64}$";

/**
* convert 40 hex string of address to byte array, padding 0 ahead if length is odd.
* Convert a hash hex string (optional 0x prefix) to a byte array, validating
* format and length via {@link #HASH_REGEX} first.
*/
public static byte[] addressToByteArray(String hexAddress) throws JsonRpcInvalidParamsException {
byte[] addressByte = ByteArray.fromHexString(hexAddress);
if (addressByte.length != DecodeUtil.ADDRESS_SIZE / 2 - 1) {
throw new JsonRpcInvalidParamsException("invalid address: " + hexAddress);
public static byte[] hashToByteArray(String hash) throws JsonRpcInvalidParamsException {
if (hash == null || !Pattern.matches(HASH_REGEX, hash)) {
throw new JsonRpcInvalidParamsException("invalid hash value");
}
return new DataWord(addressByte).getLast20Bytes();
byte[] bHash;
try {
bHash = ByteArray.fromHexString(hash);
} catch (Exception e) {
throw new JsonRpcInvalidParamsException(e.getMessage());
}
return bHash;
}

/**
* check if topic is hex string of size 64, padding 0 ahead if length is odd.
* Matches a 32-byte topic hex string: optional 0x prefix + 63 or 64 hex chars.
*/
public static final String TOPIC_REGEX = "^(0x)?[0-9a-fA-F]{63,64}$";

/**
* Convert a topic hex string (optional 0x prefix, leading zero may be omitted) to a 32-byte
* array, validating format and length via {@link #TOPIC_REGEX} first.
*/
public static byte[] topicToByteArray(String hexTopic) throws JsonRpcInvalidParamsException {
byte[] topicByte = ByteArray.fromHexString(hexTopic);
if (topicByte.length != 32) {
if (hexTopic == null || !Pattern.matches(TOPIC_REGEX, hexTopic)) {
throw new JsonRpcInvalidParamsException("invalid topic: " + hexTopic);
}
try {
return ByteArray.fromHexString(hexTopic);
} catch (Exception e) {
throw new JsonRpcInvalidParamsException("invalid topic: " + hexTopic);
}
return topicByte;
}

/**
* convert 40 hex string of address to byte array, padding 0 ahead if length is odd.
*/
public static byte[] addressToByteArray(String hexAddress) throws JsonRpcInvalidParamsException {
if (hexAddress == null) {
throw new JsonRpcInvalidParamsException("address is null");
} else if (hexAddress.length() > DecodeUtil.ADDRESS_SIZE) {
throw new JsonRpcInvalidParamsException("invalid address: " + hexAddress);
}
byte[] addressByte;
try {
addressByte = ByteArray.fromHexString(hexAddress);
} catch (Exception e) {
throw new JsonRpcInvalidParamsException("invalid address: " + hexAddress);
}
if (addressByte.length != DecodeUtil.ADDRESS_SIZE / 2 - 1) {
throw new JsonRpcInvalidParamsException("invalid address: " + hexAddress);
}
return new DataWord(addressByte).getLast20Bytes();
}

public static boolean paramStringIsNull(String string) {
Expand Down Expand Up @@ -499,7 +546,10 @@ public static long parseQuantityValue(String value) throws JsonRpcInvalidParamsE
throw new JsonRpcInvalidParamsException("invalid param value: invalid hex number");
}
}

// QUANTITY is unsigned; reject a signed ("0x-..") value instead of returning a negative.
if (callValue < 0) {
throw new JsonRpcInvalidParamsException("invalid param value: negative");
}
return callValue;
}

Expand Down Expand Up @@ -603,10 +653,11 @@ public static long parseBlockTag(String tag, Wallet wallet)
/**
* 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.
* whose cost grows quadratically with length. A block number fits a signed long,
* so the longest valid input is 19 chars (decimal Long.MAX_VALUE) or 18 (0x + 16
* hex); 20 leaves a small margin.
*/
private static final int MAX_BLOCK_NUM_HEX_LEN = 100;
private static final int MAX_BLOCK_NUM_HEX_LEN = 20;

/**
* Parse a JSON-RPC block number (hex "0x..." or decimal) into a long,
Expand Down Expand Up @@ -636,16 +687,50 @@ public static long parseBlockNumber(String blockNum)
}

/**
* Parse a block tag or hex number. Uses strict jsonHexToLong (requires 0x prefix) for hex.
* Callers needing flexible hex parsing (0x -> hex, bare number -> decimal) should use
* isBlockTag/parseBlockTag and handle hex separately with hexToBigInteger.
* Parse a block tag, or a 0x-prefixed hex block number.
*/
public static long parseBlockNumber(String blockNumOrTag, Wallet wallet)
throws JsonRpcInvalidParamsException {
if (isBlockTag(blockNumOrTag)) {
return parseBlockTag(blockNumOrTag, wallet);
}
return ByteArray.jsonHexToLong(blockNumOrTag);
if (blockNumOrTag == null || !blockNumOrTag.startsWith("0x")) {
throw new JsonRpcInvalidParamsException("Incorrect hex syntax");
}
return parseBlockNumber(blockNumOrTag);
}

/**
* Max hex digits of a 32-bit int (0x7FFFFFFF). A transaction index fits a signed int, so the
* longest valid input is "0x" + 8 hex digits; the +2 in the guard covers the prefix.
*/
private static final int MAX_TX_INDEX_HEX_LEN = 8;

/**
* Parse a 0x-prefixed hex transaction index at the JSON-RPC boundary.
*/
public static int parseTxIndex(String index) throws JsonRpcInvalidParamsException {
if (index == null || index.length() > MAX_TX_INDEX_HEX_LEN + 2) {
throw new JsonRpcInvalidParamsException(TX_INDEX_ERROR);
}
try {
return ByteArray.jsonHexToInt(index);
} catch (Exception e) {
throw new JsonRpcInvalidParamsException(TX_INDEX_ERROR);
}
}

/**
* Compute feeLimit = gas * energyFee with overflow protection. A gas value large enough to
* overflow a signed 64-bit feeLimit is rejected as invalid-params instead of silently wrapping
* to a bogus (possibly negative) value.
*/
public static long calcFeeLimit(long gas, long energyFee) throws JsonRpcInvalidParamsException {
try {
return StrictMathWrapper.multiplyExact(gas, energyFee);
} catch (ArithmeticException e) {
throw new JsonRpcInvalidParamsException("invalid gas: fee limit overflow");
}
}

public static String generateFilterId() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,18 @@
import static org.tron.core.Wallet.CONTRACT_VALIDATE_ERROR;
import static org.tron.core.services.http.Util.setTransactionExtraData;
import static org.tron.core.services.http.Util.setTransactionPermissionId;
import static org.tron.core.services.jsonrpc.JsonRpcApiUtil.BLOCK_NUM_ERROR;
import static org.tron.core.services.jsonrpc.JsonRpcApiUtil.FINALIZED_STR;
import static org.tron.core.services.jsonrpc.JsonRpcApiUtil.HASH_REGEX;
import static org.tron.core.services.jsonrpc.JsonRpcApiUtil.LATEST_STR;
import static org.tron.core.services.jsonrpc.JsonRpcApiUtil.addressCompatibleToByteArray;
import static org.tron.core.services.jsonrpc.JsonRpcApiUtil.calcFeeLimit;
import static org.tron.core.services.jsonrpc.JsonRpcApiUtil.generateFilterId;
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.hashToByteArray;
import static org.tron.core.services.jsonrpc.JsonRpcApiUtil.parseBlockNumber;
import static org.tron.core.services.jsonrpc.JsonRpcApiUtil.parseTxIndex;
import static org.tron.core.services.jsonrpc.JsonRpcApiUtil.triggerCallContract;

import com.google.common.annotations.VisibleForTesting;
Expand Down Expand Up @@ -157,7 +160,11 @@ public enum RequestSource {
private final Map<String, BlockFilterAndResult> blockFilter2ResultSolidity =
new ConcurrentHashMap<>();

public static final String HASH_REGEX = "(0x)?[a-zA-Z0-9]{64}$";
// Storage key is a 32-byte word: 64 hex chars + optional "0x" prefix = 66 max.
// Reject oversized input before fromHexString / new DataWord, which would otherwise
// throw a RuntimeException whose message embeds the whole hex (amplifying log output)
// and surface as a -32603 Internal error instead of -32602 invalid params.
public static final int MAX_STORAGE_KEY_HEX_LEN = 66;

public static final String INVALID_BLOCK_RANGE = "invalid block range params";

Expand Down Expand Up @@ -366,20 +373,6 @@ public BlockResult ethGetBlockByNumber(String blockNumOrTag, Boolean fullTransac
return (b == null ? null : getBlockResult(b, fullTransactionObjects));
}

private byte[] hashToByteArray(String hash) throws JsonRpcInvalidParamsException {
if (!Pattern.matches(HASH_REGEX, hash)) {
throw new JsonRpcInvalidParamsException("invalid hash value");
}

byte[] bHash;
try {
bHash = ByteArray.fromHexString(hash);
} catch (Exception e) {
throw new JsonRpcInvalidParamsException(e.getMessage());
}
return bHash;
}

/**
* Reject any block selector that is not "latest".
* Accepts "latest" silently; throws for other tags, numeric blocks, or invalid input.
Expand Down Expand Up @@ -612,8 +605,19 @@ public String getStorageAt(String address, String storageIdx, String blockNumOrT
throws JsonRpcInvalidParamsException {
requireLatestBlockTag(blockNumOrTag);

if (storageIdx == null || storageIdx.length() > MAX_STORAGE_KEY_HEX_LEN) {
throw new JsonRpcInvalidParamsException("invalid storage key value");
}

byte[] addressByte = addressCompatibleToByteArray(address);

DataWord index;
try {
index = new DataWord(ByteArray.fromHexString(storageIdx));
} catch (Exception e) {
throw new JsonRpcInvalidParamsException("invalid storage key value");
}

// get contract from contractStore
BytesMessage.Builder build = BytesMessage.newBuilder();
BytesMessage bytesMessage = build.setValue(ByteString.copyFrom(addressByte)).build();
Expand All @@ -627,7 +631,7 @@ public String getStorageAt(String address, String storageIdx, String blockNumOrT
storage.setContractVersion(smartContract.getVersion());
storage.generateAddrHash(smartContract.getTrxHash().toByteArray());

DataWord value = storage.getValue(new DataWord(ByteArray.fromHexString(storageIdx)));
DataWord value = storage.getValue(index);
return ByteArray.toJsonHex(value == null ? new byte[32] : value.getData());
}

Expand Down Expand Up @@ -812,14 +816,9 @@ private TransactionResult formatTransactionResult(TransactionInfo transactioninf

private TransactionResult getTransactionByBlockAndIndex(Block block, String index)
throws JsonRpcInvalidParamsException {
int txIndex;
try {
txIndex = ByteArray.jsonHexToInt(index);
} catch (Exception e) {
throw new JsonRpcInvalidParamsException("invalid index value");
}
int txIndex = parseTxIndex(index);

if (txIndex >= block.getTransactionsCount()) {
if (txIndex < 0 || txIndex >= block.getTransactionsCount()) {
return null;
}

Expand Down Expand Up @@ -934,9 +933,10 @@ public List<TransactionReceipt> getBlockReceipts(String blockNumOrHashOrTag)

Block block = null;

if (Pattern.matches(HASH_REGEX, blockNumOrHashOrTag)) {
if (blockNumOrHashOrTag != null && Pattern.matches(HASH_REGEX, blockNumOrHashOrTag)) {
block = getBlockByJsonHash(blockNumOrHashOrTag);
} else {
// null falls through to getBlockByNumOrTag -> parseBlockNumber -> -32602 (not an NPE)
block = getBlockByNumOrTag(blockNumOrHashOrTag);
}

Expand Down Expand Up @@ -1141,7 +1141,11 @@ private TransactionJson buildCreateSmartContractTransaction(byte[] ownerAddress,
ABI.Builder abiBuilder = ABI.newBuilder();
if (StringUtils.isNotEmpty(args.getAbi())) {
String abiStr = "{" + "\"entrys\":" + args.getAbi() + "}";
JsonFormat.merge(abiStr, abiBuilder, args.isVisible());
try {
JsonFormat.merge(abiStr, abiBuilder, args.isVisible());
} catch (StackOverflowError e) {
throw new JsonRpcInvalidParamsException("invalid abi");
}
}

SmartContract.Builder smartBuilder = SmartContract.newBuilder();
Expand All @@ -1167,7 +1171,7 @@ private TransactionJson buildCreateSmartContractTransaction(byte[] ownerAddress,
.createTransactionCapsule(build.build(), ContractType.CreateSmartContract).getInstance();
Transaction.Builder txBuilder = tx.toBuilder();
Transaction.raw.Builder rawBuilder = tx.getRawData().toBuilder();
rawBuilder.setFeeLimit(args.parseGas() * wallet.getEnergyFee());
rawBuilder.setFeeLimit(calcFeeLimit(args.parseGas(), wallet.getEnergyFee()));

txBuilder.setRawData(rawBuilder);
tx = setTransactionPermissionId(args.getPermissionId(), txBuilder.build());
Expand Down Expand Up @@ -1216,7 +1220,7 @@ private TransactionJson buildTriggerSmartContractTransaction(byte[] ownerAddress

Transaction.Builder txBuilder = tx.toBuilder();
Transaction.raw.Builder rawBuilder = tx.getRawData().toBuilder();
rawBuilder.setFeeLimit(args.parseGas() * wallet.getEnergyFee());
rawBuilder.setFeeLimit(calcFeeLimit(args.parseGas(), wallet.getEnergyFee()));
txBuilder.setRawData(rawBuilder);

Transaction trx = wallet
Expand Down
Loading
Loading