diff --git a/framework/src/main/java/org/tron/core/Wallet.java b/framework/src/main/java/org/tron/core/Wallet.java index 39e8f06c281..efbceda1b16 100755 --- a/framework/src/main/java/org/tron/core/Wallet.java +++ b/framework/src/main/java/org/tron/core/Wallet.java @@ -32,11 +32,6 @@ 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.parseEnergyFee; -import static org.tron.core.services.jsonrpc.TronJsonRpcImpl.EARLIEST_STR; -import static org.tron.core.services.jsonrpc.TronJsonRpcImpl.FINALIZED_STR; -import static org.tron.core.services.jsonrpc.TronJsonRpcImpl.LATEST_STR; -import static org.tron.core.services.jsonrpc.TronJsonRpcImpl.PENDING_STR; -import static org.tron.core.services.jsonrpc.TronJsonRpcImpl.TAG_PENDING_SUPPORT_ERROR; import static org.tron.core.vm.utils.FreezeV2Util.getV2EnergyUsage; import static org.tron.core.vm.utils.FreezeV2Util.getV2NetUsage; import static org.tron.protos.contract.Common.ResourceCode; @@ -193,7 +188,6 @@ import org.tron.core.exception.VMIllegalException; import org.tron.core.exception.ValidateSignatureException; import org.tron.core.exception.ZksnarkException; -import org.tron.core.exception.jsonrpc.JsonRpcInvalidParamsException; import org.tron.core.net.TronNetDelegate; import org.tron.core.net.TronNetService; import org.tron.core.net.message.adv.TransactionMessage; @@ -711,6 +705,10 @@ public long getSolidBlockNum() { return chainBaseManager.getDynamicPropertiesStore().getLatestSolidifiedBlockNum(); } + public long getHeadBlockNum() { + return chainBaseManager.getHeadBlockNum(); + } + public BlockCapsule getBlockCapsuleByNum(long blockNum) { try { return chainBaseManager.getBlockByNum(blockNum); @@ -733,37 +731,6 @@ public long getTransactionCountByBlockNum(long blockNum) { return count; } - public Block getByJsonBlockId(String id) throws JsonRpcInvalidParamsException { - if (EARLIEST_STR.equalsIgnoreCase(id)) { - return getBlockByNum(0); - } else if (LATEST_STR.equalsIgnoreCase(id)) { - return getNowBlock(); - } else if (FINALIZED_STR.equalsIgnoreCase(id)) { - return getSolidBlock(); - } 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"); - } - - return getBlockByNum(blockNumber); - } - } - - public List getTransactionsByJsonBlockId(String id) - throws JsonRpcInvalidParamsException { - if (PENDING_STR.equalsIgnoreCase(id)) { - throw new JsonRpcInvalidParamsException(TAG_PENDING_SUPPORT_ERROR); - } else { - Block block = getByJsonBlockId(id); - return block != null ? block.getTransactionsList() : null; - } - } - public WitnessList getWitnessList() { WitnessList.Builder builder = WitnessList.newBuilder(); List witnessCapsuleList = chainBaseManager.getWitnessStore().getAllWitnesses(); diff --git a/framework/src/main/java/org/tron/core/services/jsonrpc/JsonRpcApiUtil.java b/framework/src/main/java/org/tron/core/services/jsonrpc/JsonRpcApiUtil.java index 4a60f14b534..08c1068e3a2 100644 --- a/framework/src/main/java/org/tron/core/services/jsonrpc/JsonRpcApiUtil.java +++ b/framework/src/main/java/org/tron/core/services/jsonrpc/JsonRpcApiUtil.java @@ -1,11 +1,5 @@ package org.tron.core.services.jsonrpc; -import static org.tron.core.services.jsonrpc.TronJsonRpcImpl.EARLIEST_STR; -import static org.tron.core.services.jsonrpc.TronJsonRpcImpl.FINALIZED_STR; -import static org.tron.core.services.jsonrpc.TronJsonRpcImpl.LATEST_STR; -import static org.tron.core.services.jsonrpc.TronJsonRpcImpl.PENDING_STR; -import static org.tron.core.services.jsonrpc.TronJsonRpcImpl.TAG_PENDING_SUPPORT_ERROR; - import com.google.common.base.Throwables; import com.google.common.primitives.Longs; import com.google.protobuf.Any; @@ -57,6 +51,15 @@ @Slf4j(topic = "API") public class JsonRpcApiUtil { + public static final String EARLIEST_STR = "earliest"; + public static final String PENDING_STR = "pending"; + public static final String LATEST_STR = "latest"; + public static final String FINALIZED_STR = "finalized"; + public static final String SAFE_STR = "safe"; + 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 byte[] convertToTronAddress(byte[] address) { byte[] newAddress = new byte[21]; byte[] temp = new byte[] {Wallet.getAddressPreFixByte()}; @@ -515,20 +518,52 @@ public static long parseEnergyFee(long timestamp, String energyPriceHistory) { return -1; } - public static long getByJsonBlockId(String blockNumOrTag, Wallet wallet) + public static boolean isBlockTag(String tag) { + return LATEST_STR.equalsIgnoreCase(tag) + || EARLIEST_STR.equalsIgnoreCase(tag) + || FINALIZED_STR.equalsIgnoreCase(tag) + || PENDING_STR.equalsIgnoreCase(tag) + || SAFE_STR.equalsIgnoreCase(tag); + } + + /** + * Parse a block tag (latest, earliest, finalized) to block number. + * + *

Note: for "latest", the returned block number may not yet be available in + * blockStore or blockIndexStore due to write ordering. Callers that need the + * actual block must handle the not-found case.

+ */ + public static long parseBlockTag(String tag, Wallet wallet) throws JsonRpcInvalidParamsException { - if (PENDING_STR.equalsIgnoreCase(blockNumOrTag)) { - throw new JsonRpcInvalidParamsException(TAG_PENDING_SUPPORT_ERROR); + if (LATEST_STR.equalsIgnoreCase(tag)) { + return wallet.getHeadBlockNum(); } - if (StringUtils.isEmpty(blockNumOrTag) || LATEST_STR.equalsIgnoreCase(blockNumOrTag)) { - return -1; - } else if (EARLIEST_STR.equalsIgnoreCase(blockNumOrTag)) { + if (EARLIEST_STR.equalsIgnoreCase(tag)) { return 0; - } else if (FINALIZED_STR.equalsIgnoreCase(blockNumOrTag)) { + } + if (FINALIZED_STR.equalsIgnoreCase(tag)) { return wallet.getSolidBlockNum(); - } else { - return ByteArray.jsonHexToLong(blockNumOrTag); } + if (PENDING_STR.equalsIgnoreCase(tag)) { + throw new JsonRpcInvalidParamsException(TAG_PENDING_SUPPORT_ERROR); + } + if (SAFE_STR.equalsIgnoreCase(tag)) { + throw new JsonRpcInvalidParamsException(TAG_SAFE_SUPPORT_ERROR); + } + throw new JsonRpcInvalidParamsException(BLOCK_NUM_ERROR); + } + + /** + * 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. + */ + public static long parseBlockNumber(String blockNumOrTag, Wallet wallet) + throws JsonRpcInvalidParamsException { + if (isBlockTag(blockNumOrTag)) { + return parseBlockTag(blockNumOrTag, wallet); + } + return ByteArray.jsonHexToLong(blockNumOrTag); } public static String generateFilterId() { diff --git a/framework/src/main/java/org/tron/core/services/jsonrpc/TronJsonRpc.java b/framework/src/main/java/org/tron/core/services/jsonrpc/TronJsonRpc.java index 115df6ef9da..8e7c8615da4 100644 --- a/framework/src/main/java/org/tron/core/services/jsonrpc/TronJsonRpc.java +++ b/framework/src/main/java/org/tron/core/services/jsonrpc/TronJsonRpc.java @@ -461,10 +461,12 @@ class LogFilterElement { private final String[] topics; @Getter private final boolean removed; + @Getter + private final String blockTimestamp; public LogFilterElement(String blockHash, Long blockNum, String txId, Integer txIndex, String contractAddress, List topicList, String logData, int logIdx, - boolean removed) { + boolean removed, long blockTimestampMs) { logIndex = ByteArray.toJsonHex(logIdx); this.blockNumber = blockNum == null ? null : ByteArray.toJsonHex(blockNum); this.blockHash = blockHash == null ? null : ByteArray.toJsonHex(blockHash); @@ -477,6 +479,7 @@ public LogFilterElement(String blockHash, Long blockNum, String txId, Integer tx topics[i] = ByteArray.toJsonHex(topicList.get(i).getData()); } this.removed = removed; + this.blockTimestamp = ByteArray.toJsonHex(blockTimestampMs / 1000); } @Override @@ -500,12 +503,16 @@ public boolean equals(Object o) { if (!Objects.equals(logIndex, item.logIndex)) { return false; } - return removed == item.removed; + if (removed != item.removed) { + return false; + } + return Objects.equals(blockTimestamp, item.blockTimestamp); } @Override public int hashCode() { - return Objects.hash(blockHash, transactionHash, transactionIndex, logIndex, removed); + return Objects.hash(blockHash, transactionHash, transactionIndex, + logIndex, removed, blockTimestamp); } } diff --git a/framework/src/main/java/org/tron/core/services/jsonrpc/TronJsonRpcImpl.java b/framework/src/main/java/org/tron/core/services/jsonrpc/TronJsonRpcImpl.java index de939bdfff4..72fc579aa56 100644 --- a/framework/src/main/java/org/tron/core/services/jsonrpc/TronJsonRpcImpl.java +++ b/framework/src/main/java/org/tron/core/services/jsonrpc/TronJsonRpcImpl.java @@ -3,6 +3,9 @@ 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.LATEST_STR; import static org.tron.core.services.jsonrpc.JsonRpcApiUtil.addressCompatibleToByteArray; import static org.tron.core.services.jsonrpc.JsonRpcApiUtil.generateFilterId; import static org.tron.core.services.jsonrpc.JsonRpcApiUtil.getEnergyUsageTotal; @@ -152,17 +155,11 @@ public enum RequestSource { public static final String HASH_REGEX = "(0x)?[a-zA-Z0-9]{64}$"; - public static final String EARLIEST_STR = "earliest"; - public static final String PENDING_STR = "pending"; - public static final String LATEST_STR = "latest"; - public static final String FINALIZED_STR = "finalized"; - public static final String TAG_PENDING_SUPPORT_ERROR = "TAG pending not supported"; public static final String INVALID_BLOCK_RANGE = "invalid block range params"; private static final String JSON_ERROR = "invalid json request"; - private static final String BLOCK_NUM_ERROR = "invalid block number"; private static final String TAG_NOT_SUPPORT_ERROR = - "TAG [earliest | pending | finalized] not supported"; + "TAG [earliest | pending | finalized | safe] not supported"; private static final String QUANTITY_NOT_SUPPORT_ERROR = "QUANTITY not supported, just support TAG as latest"; private static final String NO_BLOCK_HEADER = "header not found"; @@ -308,12 +305,12 @@ public String ethGetBlockTransactionCountByHash(String blockHash) @Override public String ethGetBlockTransactionCountByNumber(String blockNumOrTag) throws JsonRpcInvalidParamsException { - List list = wallet.getTransactionsByJsonBlockId(blockNumOrTag); - if (list == null) { + Block block = getBlockByNumOrTag(blockNumOrTag); + if (block == null) { return null; } - long n = list.size(); + long n = block.getTransactionsCount(); return ByteArray.toJsonHex(n); } @@ -327,7 +324,7 @@ public BlockResult ethGetBlockByHash(String blockHash, Boolean fullTransactionOb @Override public BlockResult ethGetBlockByNumber(String blockNumOrTag, Boolean fullTransactionObjects) throws JsonRpcInvalidParamsException { - final Block b = wallet.getByJsonBlockId(blockNumOrTag); + final Block b = getBlockByNumOrTag(blockNumOrTag); return (b == null ? null : getBlockResult(b, fullTransactionObjects)); } @@ -345,11 +342,49 @@ private byte[] hashToByteArray(String hash) throws JsonRpcInvalidParamsException return bHash; } + /** + * Reject any block selector that is not "latest". + * Accepts "latest" silently; throws for other tags, numeric blocks, or invalid input. + */ + private void requireLatestBlockTag(String blockNumOrTag) + throws JsonRpcInvalidParamsException { + if (LATEST_STR.equalsIgnoreCase(blockNumOrTag)) { + return; + } + if (JsonRpcApiUtil.isBlockTag(blockNumOrTag)) { + throw new JsonRpcInvalidParamsException(TAG_NOT_SUPPORT_ERROR); + } + try { + ByteArray.hexToBigInteger(blockNumOrTag); + } catch (Exception e) { + throw new JsonRpcInvalidParamsException(BLOCK_NUM_ERROR); + } + throw new JsonRpcInvalidParamsException(QUANTITY_NOT_SUPPORT_ERROR); + } + private Block getBlockByJsonHash(String blockHash) throws JsonRpcInvalidParamsException { byte[] bHash = hashToByteArray(blockHash); return wallet.getBlockById(ByteString.copyFrom(bHash)); } + private Block getBlockByNumOrTag(String blockNumOrTag) throws JsonRpcInvalidParamsException { + long blockNum; + if (JsonRpcApiUtil.isBlockTag(blockNumOrTag)) { + if (LATEST_STR.equalsIgnoreCase(blockNumOrTag)) { + // Return the head block directly from blockStore, bypassing blockIndexStore + // which may not yet be written when latestBlockHeaderNumber is already updated. + return wallet.getNowBlock(); + } + return wallet.getBlockByNum(JsonRpcApiUtil.parseBlockTag(blockNumOrTag, wallet)); + } + try { + blockNum = ByteArray.hexToBigInteger(blockNumOrTag).longValueExact(); + } catch (Exception e) { + throw new JsonRpcInvalidParamsException(BLOCK_NUM_ERROR); + } + return wallet.getBlockByNum(blockNum); + } + private BlockResult getBlockResult(Block block, boolean fullTx) { if (block == null) { return null; @@ -393,30 +428,18 @@ public String getLatestBlockNum() { @Override public String getTrxBalance(String address, String blockNumOrTag) throws JsonRpcInvalidParamsException { - if (EARLIEST_STR.equalsIgnoreCase(blockNumOrTag) - || PENDING_STR.equalsIgnoreCase(blockNumOrTag) - || FINALIZED_STR.equalsIgnoreCase(blockNumOrTag)) { - throw new JsonRpcInvalidParamsException(TAG_NOT_SUPPORT_ERROR); - } else if (LATEST_STR.equalsIgnoreCase(blockNumOrTag)) { - byte[] addressData = addressCompatibleToByteArray(address); + requireLatestBlockTag(blockNumOrTag); - Account account = Account.newBuilder().setAddress(ByteString.copyFrom(addressData)).build(); - Account reply = wallet.getAccount(account); - long balance = 0; + byte[] addressData = addressCompatibleToByteArray(address); - if (reply != null) { - balance = reply.getBalance(); - } - return ByteArray.toJsonHex(balance); - } else { - try { - ByteArray.hexToBigInteger(blockNumOrTag); - } catch (Exception e) { - throw new JsonRpcInvalidParamsException(BLOCK_NUM_ERROR); - } + Account account = Account.newBuilder().setAddress(ByteString.copyFrom(addressData)).build(); + Account reply = wallet.getAccount(account); + long balance = 0; - throw new JsonRpcInvalidParamsException(QUANTITY_NOT_SUPPORT_ERROR); + if (reply != null) { + balance = reply.getBalance(); } + return ByteArray.toJsonHex(balance); } private void callTriggerConstantContract(byte[] ownerAddressByte, byte[] contractAddressByte, @@ -535,67 +558,42 @@ private String call(byte[] ownerAddressByte, byte[] contractAddressByte, long va @Override public String getStorageAt(String address, String storageIdx, String blockNumOrTag) throws JsonRpcInvalidParamsException { - if (EARLIEST_STR.equalsIgnoreCase(blockNumOrTag) - || PENDING_STR.equalsIgnoreCase(blockNumOrTag) - || FINALIZED_STR.equalsIgnoreCase(blockNumOrTag)) { - throw new JsonRpcInvalidParamsException(TAG_NOT_SUPPORT_ERROR); - } else if (LATEST_STR.equalsIgnoreCase(blockNumOrTag)) { - byte[] addressByte = addressCompatibleToByteArray(address); - - // get contract from contractStore - BytesMessage.Builder build = BytesMessage.newBuilder(); - BytesMessage bytesMessage = build.setValue(ByteString.copyFrom(addressByte)).build(); - SmartContract smartContract = wallet.getContract(bytesMessage); - if (smartContract == null) { - return ByteArray.toJsonHex(new byte[32]); - } + requireLatestBlockTag(blockNumOrTag); - StorageRowStore store = manager.getStorageRowStore(); - Storage storage = new Storage(addressByte, store); - storage.setContractVersion(smartContract.getVersion()); - storage.generateAddrHash(smartContract.getTrxHash().toByteArray()); + byte[] addressByte = addressCompatibleToByteArray(address); - 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); - } - - throw new JsonRpcInvalidParamsException(QUANTITY_NOT_SUPPORT_ERROR); + // get contract from contractStore + BytesMessage.Builder build = BytesMessage.newBuilder(); + BytesMessage bytesMessage = build.setValue(ByteString.copyFrom(addressByte)).build(); + SmartContract smartContract = wallet.getContract(bytesMessage); + if (smartContract == null) { + return ByteArray.toJsonHex(new byte[32]); } + + StorageRowStore store = manager.getStorageRowStore(); + Storage storage = new Storage(addressByte, store); + storage.setContractVersion(smartContract.getVersion()); + storage.generateAddrHash(smartContract.getTrxHash().toByteArray()); + + DataWord value = storage.getValue(new DataWord(ByteArray.fromHexString(storageIdx))); + return ByteArray.toJsonHex(value == null ? new byte[32] : value.getData()); } @Override public String getABIOfSmartContract(String contractAddress, String blockNumOrTag) throws JsonRpcInvalidParamsException { - if (EARLIEST_STR.equalsIgnoreCase(blockNumOrTag) - || PENDING_STR.equalsIgnoreCase(blockNumOrTag) - || FINALIZED_STR.equalsIgnoreCase(blockNumOrTag)) { - throw new JsonRpcInvalidParamsException(TAG_NOT_SUPPORT_ERROR); - } else if (LATEST_STR.equalsIgnoreCase(blockNumOrTag)) { - byte[] addressData = addressCompatibleToByteArray(contractAddress); + requireLatestBlockTag(blockNumOrTag); - BytesMessage.Builder build = BytesMessage.newBuilder(); - BytesMessage bytesMessage = build.setValue(ByteString.copyFrom(addressData)).build(); - SmartContractDataWrapper contractDataWrapper = wallet.getContractInfo(bytesMessage); + byte[] addressData = addressCompatibleToByteArray(contractAddress); - if (contractDataWrapper != null) { - return ByteArray.toJsonHex(contractDataWrapper.getRuntimecode().toByteArray()); - } else { - return "0x"; - } + BytesMessage.Builder build = BytesMessage.newBuilder(); + BytesMessage bytesMessage = build.setValue(ByteString.copyFrom(addressData)).build(); + SmartContractDataWrapper contractDataWrapper = wallet.getContractInfo(bytesMessage); + if (contractDataWrapper != null) { + return ByteArray.toJsonHex(contractDataWrapper.getRuntimecode().toByteArray()); } else { - try { - ByteArray.hexToBigInteger(blockNumOrTag); - } catch (Exception e) { - throw new JsonRpcInvalidParamsException(BLOCK_NUM_ERROR); - } - - throw new JsonRpcInvalidParamsException(QUANTITY_NOT_SUPPORT_ERROR); + return "0x"; } } @@ -803,7 +801,7 @@ public TransactionResult getTransactionByBlockHashAndIndex(String blockHash, Str @Override public TransactionResult getTransactionByBlockNumberAndIndex(String blockNumOrTag, String index) throws JsonRpcInvalidParamsException { - Block block = wallet.getByJsonBlockId(blockNumOrTag); + Block block = getBlockByNumOrTag(blockNumOrTag); if (block == null) { return null; } @@ -894,7 +892,7 @@ public List getBlockReceipts(String blockNumOrHashOrTag) if (Pattern.matches(HASH_REGEX, blockNumOrHashOrTag)) { block = getBlockByJsonHash(blockNumOrHashOrTag); } else { - block = wallet.getByJsonBlockId(blockNumOrHashOrTag); + block = getBlockByNumOrTag(blockNumOrHashOrTag); } // block receipts not available: block is genesis, not produced yet, or pruned in light node @@ -973,7 +971,7 @@ public String getCall(CallArguments transactionCall, Object blockParamObj) long blockNumber; try { - blockNumber = ByteArray.hexToBigInteger(blockNumOrTag).longValue(); + blockNumber = ByteArray.hexToBigInteger(blockNumOrTag).longValueExact(); } catch (Exception e) { throw new JsonRpcInvalidParamsException(BLOCK_NUM_ERROR); } @@ -1003,25 +1001,13 @@ public String getCall(CallArguments transactionCall, Object blockParamObj) throw new JsonRpcInvalidRequestException(JSON_ERROR); } - if (EARLIEST_STR.equalsIgnoreCase(blockNumOrTag) - || PENDING_STR.equalsIgnoreCase(blockNumOrTag) - || FINALIZED_STR.equalsIgnoreCase(blockNumOrTag)) { - throw new JsonRpcInvalidParamsException(TAG_NOT_SUPPORT_ERROR); - } else if (LATEST_STR.equalsIgnoreCase(blockNumOrTag)) { - byte[] addressData = addressCompatibleToByteArray(transactionCall.getFrom()); - byte[] contractAddressData = addressCompatibleToByteArray(transactionCall.getTo()); + requireLatestBlockTag(blockNumOrTag); - return call(addressData, contractAddressData, transactionCall.parseValue(), - ByteArray.fromHexString(transactionCall.getData())); - } else { - try { - ByteArray.hexToBigInteger(blockNumOrTag); - } catch (Exception e) { - throw new JsonRpcInvalidParamsException(BLOCK_NUM_ERROR); - } + byte[] addressData = addressCompatibleToByteArray(transactionCall.getFrom()); + byte[] contractAddressData = addressCompatibleToByteArray(transactionCall.getTo()); - throw new JsonRpcInvalidParamsException(QUANTITY_NOT_SUPPORT_ERROR); - } + return call(addressData, contractAddressData, transactionCall.parseValue(), + ByteArray.fromHexString(transactionCall.getData())); } @Override diff --git a/framework/src/main/java/org/tron/core/services/jsonrpc/filters/LogFilterWrapper.java b/framework/src/main/java/org/tron/core/services/jsonrpc/filters/LogFilterWrapper.java index 97a012b7f9a..0331ab3694a 100644 --- a/framework/src/main/java/org/tron/core/services/jsonrpc/filters/LogFilterWrapper.java +++ b/framework/src/main/java/org/tron/core/services/jsonrpc/filters/LogFilterWrapper.java @@ -1,6 +1,7 @@ package org.tron.core.services.jsonrpc.filters; import static org.tron.common.math.StrictMathWrapper.min; +import static org.tron.core.services.jsonrpc.JsonRpcApiUtil.LATEST_STR; import com.google.protobuf.ByteString; import lombok.Getter; @@ -50,39 +51,50 @@ public LogFilterWrapper(FilterRequest fr, long currentMaxBlockNum, Wallet wallet toBlockSrc = fromBlockSrc; } else { - // if fromBlock is empty but toBlock is not empty, - // then if toBlock < maxBlockNum, set fromBlock = toBlock - // then if toBlock >= maxBlockNum, set fromBlock = maxBlockNum - if (StringUtils.isEmpty(fr.getFromBlock()) && StringUtils.isNotEmpty(fr.getToBlock())) { - toBlockSrc = JsonRpcApiUtil.getByJsonBlockId(fr.getToBlock(), wallet); - if (toBlockSrc == -1) { - toBlockSrc = Long.MAX_VALUE; - } - fromBlockSrc = min(toBlockSrc, currentMaxBlockNum); + // Normalize the request into one of four strategies based on parameter emptiness. + // Long.MAX_VALUE is an internal sentinel meaning "open upper bound"; it is never + // treated as a real block number by later query stages. + // Note: "latest" tag handling differs by strategy: + // - Strategy 2: toBlock="latest" -> Long.MAX_VALUE (track future blocks) + // - Strategy 3: fromBlock="latest" -> currentMaxBlockNum snapshot (bounded start) + // - Strategy 4: fromBlock="latest" -> currentMaxBlockNum; toBlock="latest" -> Long.MAX_VALUE - } else if (StringUtils.isNotEmpty(fr.getFromBlock()) - && StringUtils.isEmpty(fr.getToBlock())) { - fromBlockSrc = JsonRpcApiUtil.getByJsonBlockId(fr.getFromBlock(), wallet); - if (fromBlockSrc == -1) { - fromBlockSrc = currentMaxBlockNum; - } - toBlockSrc = Long.MAX_VALUE; + boolean fromEmpty = StringUtils.isEmpty(fr.getFromBlock()); + boolean toEmpty = StringUtils.isEmpty(fr.getToBlock()); - } else if (StringUtils.isEmpty(fr.getFromBlock()) && StringUtils.isEmpty(fr.getToBlock())) { + if (fromEmpty && toEmpty) { + // Strategy 1: Both parameters omitted. Start at the current head and track new blocks. fromBlockSrc = currentMaxBlockNum; toBlockSrc = Long.MAX_VALUE; - } else { - fromBlockSrc = JsonRpcApiUtil.getByJsonBlockId(fr.getFromBlock(), wallet); - toBlockSrc = JsonRpcApiUtil.getByJsonBlockId(fr.getToBlock(), wallet); - if (fromBlockSrc == -1 && toBlockSrc == -1) { - fromBlockSrc = currentMaxBlockNum; - toBlockSrc = Long.MAX_VALUE; - } else if (fromBlockSrc == -1 && toBlockSrc >= 0) { - fromBlockSrc = currentMaxBlockNum; - } else if (fromBlockSrc >= 0 && toBlockSrc == -1) { + } else if (fromEmpty) { + // Strategy 2: Only toBlock specified. + // If toBlock is "latest": track future blocks (fromBlock = currentMaxBlockNum, + // toBlock = MAX_VALUE). If concrete: bounded query with fromBlock = min(toBlock, + // currentMaxBlockNum). + if (LATEST_STR.equalsIgnoreCase(fr.getToBlock())) { toBlockSrc = Long.MAX_VALUE; + } else { + toBlockSrc = JsonRpcApiUtil.parseBlockNumber(fr.getToBlock(), wallet); } + fromBlockSrc = min(toBlockSrc, currentMaxBlockNum); + + } else if (toEmpty) { + // Strategy 3: Only fromBlock specified. Start at fromBlock and track new blocks. + // If fromBlock is "latest", use the snapshot (currentMaxBlockNum) as the starting point. + fromBlockSrc = LATEST_STR.equalsIgnoreCase(fr.getFromBlock()) ? currentMaxBlockNum + : JsonRpcApiUtil.parseBlockNumber(fr.getFromBlock(), wallet); + toBlockSrc = Long.MAX_VALUE; + + } else { + // Strategy 4: Both parameters specified. + // If fromBlock is "latest": use the snapshot (currentMaxBlockNum) as a fixed start point. + // If toBlock is "latest": use Long.MAX_VALUE to track future blocks. + // Otherwise: parse both as concrete block numbers + fromBlockSrc = LATEST_STR.equalsIgnoreCase(fr.getFromBlock()) ? currentMaxBlockNum + : JsonRpcApiUtil.parseBlockNumber(fr.getFromBlock(), wallet); + toBlockSrc = LATEST_STR.equalsIgnoreCase(fr.getToBlock()) ? Long.MAX_VALUE + : JsonRpcApiUtil.parseBlockNumber(fr.getToBlock(), wallet); if (fromBlockSrc > toBlockSrc) { throw new JsonRpcInvalidParamsException("please verify: fromBlock <= toBlock"); } diff --git a/framework/src/main/java/org/tron/core/services/jsonrpc/filters/LogMatch.java b/framework/src/main/java/org/tron/core/services/jsonrpc/filters/LogMatch.java index cf958d1e2cb..67d229b2948 100644 --- a/framework/src/main/java/org/tron/core/services/jsonrpc/filters/LogMatch.java +++ b/framework/src/main/java/org/tron/core/services/jsonrpc/filters/LogMatch.java @@ -66,7 +66,8 @@ public static List matchBlock(LogFilter logFilter, long blockN topicList, ByteArray.toHexString(log.getData().toByteArray()), logIndexInBlock, - removed + removed, + transactionInfo.getBlockTimeStamp() ); matchedLog.add(logFilterElement); } diff --git a/framework/src/main/java/org/tron/core/services/jsonrpc/types/TransactionReceipt.java b/framework/src/main/java/org/tron/core/services/jsonrpc/types/TransactionReceipt.java index fd57ec0d9ad..6c22c1560e4 100644 --- a/framework/src/main/java/org/tron/core/services/jsonrpc/types/TransactionReceipt.java +++ b/framework/src/main/java/org/tron/core/services/jsonrpc/types/TransactionReceipt.java @@ -35,6 +35,7 @@ public static class TransactionLog { private String data; private String[] topics; private boolean removed = false; + private String blockTimestamp; public TransactionLog() {} } @@ -108,6 +109,7 @@ public TransactionReceipt( // Set logs List logList = new ArrayList<>(); + String blockTimestamp = ByteArray.toJsonHex(blockCapsule.getTimeStamp() / 1000); for (int logIndex = 0; logIndex < txInfo.getLogCount(); logIndex++) { TransactionInfo.Log log = txInfo.getLogList().get(logIndex); TransactionLog transactionLog = new TransactionLog(); @@ -116,6 +118,7 @@ public TransactionReceipt( transactionLog.setTransactionIndex(this.transactionIndex); transactionLog.setBlockHash(this.blockHash); transactionLog.setBlockNumber(this.blockNumber); + transactionLog.setBlockTimestamp(blockTimestamp); byte[] addressByte = convertToTronAddress(log.getAddress().toByteArray()); transactionLog.setAddress(ByteArray.toJsonHexAddress(addressByte)); diff --git a/framework/src/test/java/org/tron/core/jsonrpc/JsonrpcServiceTest.java b/framework/src/test/java/org/tron/core/jsonrpc/JsonrpcServiceTest.java index e1a698216c4..7de494f1783 100644 --- a/framework/src/test/java/org/tron/core/jsonrpc/JsonrpcServiceTest.java +++ b/framework/src/test/java/org/tron/core/jsonrpc/JsonrpcServiceTest.java @@ -1,7 +1,10 @@ package org.tron.core.jsonrpc; -import static org.tron.core.services.jsonrpc.JsonRpcApiUtil.getByJsonBlockId; -import static org.tron.core.services.jsonrpc.TronJsonRpcImpl.TAG_PENDING_SUPPORT_ERROR; +import static org.tron.core.services.jsonrpc.JsonRpcApiUtil.TAG_PENDING_SUPPORT_ERROR; +import static org.tron.core.services.jsonrpc.JsonRpcApiUtil.TAG_SAFE_SUPPORT_ERROR; +import static org.tron.core.services.jsonrpc.JsonRpcApiUtil.isBlockTag; +import static org.tron.core.services.jsonrpc.JsonRpcApiUtil.parseBlockNumber; +import static org.tron.core.services.jsonrpc.JsonRpcApiUtil.parseBlockTag; import com.alibaba.fastjson.JSON; import com.google.gson.JsonArray; @@ -10,6 +13,7 @@ import io.prometheus.client.CollectorRegistry; import java.util.ArrayList; import java.util.Collections; +import java.util.HashMap; import java.util.List; import javax.annotation.Resource; import lombok.extern.slf4j.Slf4j; @@ -45,6 +49,7 @@ import org.tron.core.services.interfaceJsonRpcOnSolidity.JsonRpcServiceOnSolidity; import org.tron.core.services.jsonrpc.FullNodeJsonRpcHttpService; import org.tron.core.services.jsonrpc.TronJsonRpc.FilterRequest; +import org.tron.core.services.jsonrpc.TronJsonRpc.LogFilterElement; import org.tron.core.services.jsonrpc.TronJsonRpcImpl; import org.tron.core.services.jsonrpc.filters.LogFilterWrapper; import org.tron.core.services.jsonrpc.types.BlockResult; @@ -62,6 +67,8 @@ public class JsonrpcServiceTest extends BaseTest { private static final String OWNER_ADDRESS_ACCOUNT_NAME = "first"; private static final long LATEST_BLOCK_NUM = 10_000L; private static final long LATEST_SOLIDIFIED_BLOCK_NUM = 4L; + private static final String TAG_NOT_SUPPORT_ERROR = + "TAG [earliest | pending | finalized | safe] not supported"; private static TronJsonRpcImpl tronJsonRpc; @Resource @@ -109,11 +116,11 @@ public void init() { blockCapsule0 = BlockUtil.newGenesisBlockCapsule(); blockCapsule1 = new BlockCapsule(LATEST_BLOCK_NUM, Sha256Hash.wrap(ByteString.copyFrom( ByteArray.fromHexString( - "0304f784e4e7bae517bcab94c3e0c9214fb4ac7ff9d7d5a937d1f40031f87b81"))), 1, + "0304f784e4e7bae517bcab94c3e0c9214fb4ac7ff9d7d5a937d1f40031f87b81"))), 1000000, ByteString.copyFromUtf8("testAddress")); blockCapsule2 = new BlockCapsule(LATEST_SOLIDIFIED_BLOCK_NUM, Sha256Hash.wrap( ByteString.copyFrom(ByteArray.fromHexString( - "9938a342238077182498b464ac029222ae169360e540d1fd6aee7c2ae9575a06"))), 1, + "9938a342238077182498b464ac029222ae169360e540d1fd6aee7c2ae9575a06"))), 2000000, ByteString.copyFromUtf8("testAddress")); TransferContract transferContract1 = TransferContract.newBuilder().setAmount(1L) @@ -135,13 +142,15 @@ public void init() { transactionCapsule1 = new TransactionCapsule(transferContract1, ContractType.TransferContract); transactionCapsule1.setBlockNum(blockCapsule1.getNum()); + transactionCapsule1.setTimestamp(blockCapsule1.getTimeStamp()); TransactionCapsule transactionCapsule2 = new TransactionCapsule(transferContract2, ContractType.TransferContract); transactionCapsule2.setBlockNum(blockCapsule1.getNum()); + transactionCapsule2.setTimestamp(blockCapsule1.getTimeStamp()); TransactionCapsule transactionCapsule3 = new TransactionCapsule(transferContract3, ContractType.TransferContract); transactionCapsule3.setBlockNum(blockCapsule2.getNum()); - + transactionCapsule3.setTimestamp(blockCapsule2.getTimeStamp()); blockCapsule1.addTransaction(transactionCapsule1); blockCapsule1.addTransaction(transactionCapsule2); blockCapsule2.addTransaction(transactionCapsule3); @@ -181,6 +190,7 @@ public void init() { TransactionInfoCapsule transactionInfoCapsule = new TransactionInfoCapsule(); transactionInfoCapsule.setId(tx.getTransactionId().getBytes()); transactionInfoCapsule.setBlockNumber(blockCapsule1.getNum()); + transactionInfoCapsule.setBlockTimeStamp(blockCapsule1.getTimeStamp()); transactionInfoCapsule.addAllLog(logs); transactionRetCapsule1.addTransactionInfo(transactionInfoCapsule.getInstance()); }); @@ -192,6 +202,7 @@ public void init() { TransactionInfoCapsule transactionInfoCapsule = new TransactionInfoCapsule(); transactionInfoCapsule.setId(tx.getTransactionId().getBytes()); transactionInfoCapsule.setBlockNumber(blockCapsule2.getNum()); + transactionInfoCapsule.setBlockTimeStamp(blockCapsule2.getTimeStamp()); transactionRetCapsule2.addTransactionInfo(transactionInfoCapsule.getInstance()); }); dbManager.getTransactionRetStore() @@ -255,17 +266,13 @@ public void testGetBlockTransactionCountByNumber() { Assert.assertNull(result); } - try { - result = tronJsonRpc.ethGetBlockTransactionCountByNumber("pending"); - } catch (Exception e) { - Assert.assertEquals("TAG pending not supported", e.getMessage()); - } + Exception pendingEx = Assert.assertThrows(Exception.class, + () -> tronJsonRpc.ethGetBlockTransactionCountByNumber("pending")); + Assert.assertEquals(TAG_PENDING_SUPPORT_ERROR, pendingEx.getMessage()); - try { - result = tronJsonRpc.ethGetBlockTransactionCountByNumber("qqqqq"); - } catch (Exception e) { - Assert.assertEquals("invalid block number", e.getMessage()); - } + Exception malformedEx = Assert.assertThrows(Exception.class, + () -> tronJsonRpc.ethGetBlockTransactionCountByNumber("qqqqq")); + Assert.assertEquals("invalid block number", malformedEx.getMessage()); try { result = tronJsonRpc.ethGetBlockTransactionCountByNumber("latest"); @@ -282,6 +289,15 @@ public void testGetBlockTransactionCountByNumber() { } Assert.assertEquals(ByteArray.toJsonHex(blockCapsule1.getTransactions().size()), result); + // safe tag is not supported (new tag added in this refactor) + Exception safeEx = Assert.assertThrows(Exception.class, + () -> tronJsonRpc.ethGetBlockTransactionCountByNumber("safe")); + Assert.assertEquals(TAG_SAFE_SUPPORT_ERROR, safeEx.getMessage()); + + // hex that overflows long -> longValueExact rejects (previously silently truncated) + Exception overflowEx = Assert.assertThrows(Exception.class, + () -> tronJsonRpc.ethGetBlockTransactionCountByNumber("0x10000000000000000")); + Assert.assertEquals("invalid block number", overflowEx.getMessage()); } @Test @@ -353,6 +369,16 @@ public void testGetBlockByNumber() { Exception e2 = Assert.assertThrows(Exception.class, () -> tronJsonRpc.ethGetBlockByNumber("0x", false)); Assert.assertEquals("invalid block number", e2.getMessage()); + + // safe + Exception e3 = Assert.assertThrows(Exception.class, + () -> tronJsonRpc.ethGetBlockByNumber("safe", false)); + Assert.assertEquals(TAG_SAFE_SUPPORT_ERROR, e3.getMessage()); + + // hex overflows long -> longValueExact rejects + Exception e4 = Assert.assertThrows(Exception.class, + () -> tronJsonRpc.ethGetBlockByNumber("0x10000000000000000", false)); + Assert.assertEquals("invalid block number", e4.getMessage()); } @Test @@ -423,47 +449,67 @@ public void testServicesInit() { } @Test - public void testGetByJsonBlockId() { - long blkNum = 0; - + public void testBlockTagParsing() { + // isBlockTag + Assert.assertTrue(isBlockTag("pending")); + Assert.assertTrue(isBlockTag("latest")); + Assert.assertTrue(isBlockTag("earliest")); + Assert.assertTrue(isBlockTag("finalized")); + Assert.assertTrue(isBlockTag("safe")); + Assert.assertFalse(isBlockTag(null)); + Assert.assertFalse(isBlockTag("0xa")); + Assert.assertFalse(isBlockTag("")); + + // parseBlockTag: pending throws Exception pendingEx = Assert.assertThrows(Exception.class, - () -> getByJsonBlockId("pending", wallet)); - Assert.assertEquals("TAG pending not supported", pendingEx.getMessage()); + () -> parseBlockTag("pending", wallet)); + Assert.assertEquals(TAG_PENDING_SUPPORT_ERROR, pendingEx.getMessage()); + // parseBlockTag: safe throws + Exception safeEx = Assert.assertThrows(Exception.class, + () -> parseBlockTag("safe", wallet)); + Assert.assertEquals(TAG_SAFE_SUPPORT_ERROR, safeEx.getMessage()); + + // parseBlockTag: latest -> headBlockNum try { - blkNum = getByJsonBlockId(null, wallet); + long blkNum = parseBlockTag("latest", wallet); + Assert.assertEquals(LATEST_BLOCK_NUM, blkNum); } catch (Exception e) { Assert.fail(); } - Assert.assertEquals(-1, blkNum); + // parseBlockTag: earliest -> 0 try { - blkNum = getByJsonBlockId("latest", wallet); + long blkNum = parseBlockTag("earliest", wallet); + Assert.assertEquals(0L, blkNum); } catch (Exception e) { Assert.fail(); } - Assert.assertEquals(-1, blkNum); + // parseBlockTag: finalized -> solidBlockNum try { - blkNum = getByJsonBlockId("finalized", wallet); + long blkNum = parseBlockTag("finalized", wallet); + Assert.assertEquals(LATEST_SOLIDIFIED_BLOCK_NUM, blkNum); } catch (Exception e) { Assert.fail(); } - Assert.assertEquals(LATEST_SOLIDIFIED_BLOCK_NUM, blkNum); + // parseBlockNumber: hex -> number try { - blkNum = getByJsonBlockId("0xa", wallet); + long blkNum = parseBlockNumber("0xa", wallet); + Assert.assertEquals(10L, blkNum); } catch (Exception e) { Assert.fail(); } - Assert.assertEquals(10L, blkNum); + // parseBlockNumber: bad hex -> throws Exception abcEx = Assert.assertThrows(Exception.class, - () -> getByJsonBlockId("abc", wallet)); + () -> parseBlockNumber("abc", wallet)); Assert.assertEquals("Incorrect hex syntax", abcEx.getMessage()); + // parseBlockNumber: malformed hex -> throws Exception hexEx = Assert.assertThrows(Exception.class, - () -> getByJsonBlockId("0xxabc", wallet)); + () -> parseBlockNumber("0xxabc", wallet)); // https://bugs.openjdk.org/browse/JDK-8176425, from JDK 12, the exception message is changed Assert.assertTrue(hexEx.getMessage().startsWith("For input string: \"xabc\"")); } @@ -474,18 +520,19 @@ public void testGetTrxBalance() { Exception e1 = Assert.assertThrows(Exception.class, () -> tronJsonRpc.getTrxBalance("", "earliest")); - Assert.assertEquals("TAG [earliest | pending | finalized] not supported", - e1.getMessage()); + Assert.assertEquals(TAG_NOT_SUPPORT_ERROR, e1.getMessage()); Exception e2 = Assert.assertThrows(Exception.class, () -> tronJsonRpc.getTrxBalance("", "pending")); - Assert.assertEquals("TAG [earliest | pending | finalized] not supported", - e2.getMessage()); + Assert.assertEquals(TAG_NOT_SUPPORT_ERROR, e2.getMessage()); Exception e3 = Assert.assertThrows(Exception.class, () -> tronJsonRpc.getTrxBalance("", "finalized")); - Assert.assertEquals("TAG [earliest | pending | finalized] not supported", - e3.getMessage()); + Assert.assertEquals(TAG_NOT_SUPPORT_ERROR, e3.getMessage()); + + Exception e4 = Assert.assertThrows(Exception.class, + () -> tronJsonRpc.getTrxBalance("", "safe")); + Assert.assertEquals(TAG_NOT_SUPPORT_ERROR, e4.getMessage()); try { balance = tronJsonRpc.getTrxBalance("0xabd4b9367799eaa3197fecb144eb71de1e049abc", @@ -500,54 +547,219 @@ public void testGetTrxBalance() { public void testGetStorageAt() { Exception e1 = Assert.assertThrows(Exception.class, () -> tronJsonRpc.getStorageAt("", "", "earliest")); - Assert.assertEquals("TAG [earliest | pending | finalized] not supported", - e1.getMessage()); + Assert.assertEquals(TAG_NOT_SUPPORT_ERROR, e1.getMessage()); Exception e2 = Assert.assertThrows(Exception.class, () -> tronJsonRpc.getStorageAt("", "", "pending")); - Assert.assertEquals("TAG [earliest | pending | finalized] not supported", - e2.getMessage()); + Assert.assertEquals(TAG_NOT_SUPPORT_ERROR, e2.getMessage()); Exception e3 = Assert.assertThrows(Exception.class, () -> tronJsonRpc.getStorageAt("", "", "finalized")); - Assert.assertEquals("TAG [earliest | pending | finalized] not supported", - e3.getMessage()); + Assert.assertEquals(TAG_NOT_SUPPORT_ERROR, e3.getMessage()); + + Exception e4 = Assert.assertThrows(Exception.class, + () -> tronJsonRpc.getStorageAt("", "", "safe")); + Assert.assertEquals(TAG_NOT_SUPPORT_ERROR, e4.getMessage()); + + // hex block number -> QUANTITY_NOT_SUPPORT_ERROR + Exception e5 = Assert.assertThrows(Exception.class, + () -> tronJsonRpc.getStorageAt("", "", "0x1")); + Assert.assertEquals( + "QUANTITY not supported, just support TAG as latest", e5.getMessage()); + + // malformed hex -> BLOCK_NUM_ERROR + Exception e6 = Assert.assertThrows(Exception.class, + () -> tronJsonRpc.getStorageAt("", "", "abc")); + Assert.assertEquals("invalid block number", e6.getMessage()); + + // latest happy path: address is an account, not a contract, so returns 32 zero bytes + try { + String value = tronJsonRpc.getStorageAt( + "0xabd4b9367799eaa3197fecb144eb71de1e049abc", "0x0", "latest"); + Assert.assertEquals(ByteArray.toJsonHex(new byte[32]), value); + } catch (Exception e) { + Assert.fail(); + } } @Test public void testGetABIOfSmartContract() { Exception e1 = Assert.assertThrows(Exception.class, () -> tronJsonRpc.getABIOfSmartContract("", "earliest")); - Assert.assertEquals("TAG [earliest | pending | finalized] not supported", - e1.getMessage()); + Assert.assertEquals(TAG_NOT_SUPPORT_ERROR, e1.getMessage()); Exception e2 = Assert.assertThrows(Exception.class, () -> tronJsonRpc.getABIOfSmartContract("", "pending")); - Assert.assertEquals("TAG [earliest | pending | finalized] not supported", - e2.getMessage()); + Assert.assertEquals(TAG_NOT_SUPPORT_ERROR, e2.getMessage()); Exception e3 = Assert.assertThrows(Exception.class, () -> tronJsonRpc.getABIOfSmartContract("", "finalized")); - Assert.assertEquals("TAG [earliest | pending | finalized] not supported", - e3.getMessage()); + Assert.assertEquals(TAG_NOT_SUPPORT_ERROR, e3.getMessage()); + + Exception e4 = Assert.assertThrows(Exception.class, + () -> tronJsonRpc.getABIOfSmartContract("", "safe")); + Assert.assertEquals(TAG_NOT_SUPPORT_ERROR, e4.getMessage()); + + // hex block number -> QUANTITY_NOT_SUPPORT_ERROR + Exception e5 = Assert.assertThrows(Exception.class, + () -> tronJsonRpc.getABIOfSmartContract("", "0x1")); + Assert.assertEquals( + "QUANTITY not supported, just support TAG as latest", e5.getMessage()); + + // malformed hex -> BLOCK_NUM_ERROR + Exception e6 = Assert.assertThrows(Exception.class, + () -> tronJsonRpc.getABIOfSmartContract("", "abc")); + Assert.assertEquals("invalid block number", e6.getMessage()); + + // latest happy path: address is an account, not a contract, so returns "0x" + try { + String code = tronJsonRpc.getABIOfSmartContract( + "0xabd4b9367799eaa3197fecb144eb71de1e049abc", "latest"); + Assert.assertEquals("0x", code); + } catch (Exception e) { + Assert.fail(); + } } @Test public void testGetCall() { Exception e1 = Assert.assertThrows(Exception.class, () -> tronJsonRpc.getCall(null, "earliest")); - Assert.assertEquals("TAG [earliest | pending | finalized] not supported", - e1.getMessage()); + Assert.assertEquals(TAG_NOT_SUPPORT_ERROR, e1.getMessage()); Exception e2 = Assert.assertThrows(Exception.class, () -> tronJsonRpc.getCall(null, "pending")); - Assert.assertEquals("TAG [earliest | pending | finalized] not supported", - e2.getMessage()); + Assert.assertEquals(TAG_NOT_SUPPORT_ERROR, e2.getMessage()); Exception e3 = Assert.assertThrows(Exception.class, () -> tronJsonRpc.getCall(null, "finalized")); - Assert.assertEquals("TAG [earliest | pending | finalized] not supported", - e3.getMessage()); + Assert.assertEquals(TAG_NOT_SUPPORT_ERROR, e3.getMessage()); + + Exception e4 = Assert.assertThrows(Exception.class, + () -> tronJsonRpc.getCall(null, "safe")); + Assert.assertEquals(TAG_NOT_SUPPORT_ERROR, e4.getMessage()); + } + + @Test + public void testGetTransactionByBlockNumberAndIndex() { + // valid hex block number: blockCapsule1 has 2 txs; index 0 is transactionCapsule1. + // Assert the returned tx actually resolves to transactionCapsule1's hash, + // block number, and index rather than just non-null. + try { + TransactionResult result = tronJsonRpc.getTransactionByBlockNumberAndIndex( + ByteArray.toJsonHex(blockCapsule1.getNum()), "0x0"); + Assert.assertNotNull(result); + Assert.assertEquals( + ByteArray.toJsonHex(transactionCapsule1.getTransactionId().getBytes()), + result.getHash()); + Assert.assertEquals(ByteArray.toJsonHex(blockCapsule1.getNum()), result.getBlockNumber()); + Assert.assertEquals(ByteArray.toJsonHex(0L), result.getTransactionIndex()); + } catch (Exception e) { + Assert.fail(); + } + + // index out of range in an existing block returns null + try { + TransactionResult result = tronJsonRpc.getTransactionByBlockNumberAndIndex( + ByteArray.toJsonHex(blockCapsule1.getNum()), "0x5"); + Assert.assertNull(result); + } catch (Exception e) { + Assert.fail(); + } + + // latest -> blockCapsule1 (head) + try { + TransactionResult result = tronJsonRpc.getTransactionByBlockNumberAndIndex("latest", "0x0"); + Assert.assertNotNull(result); + Assert.assertEquals(ByteArray.toJsonHex(blockCapsule1.getNum()), result.getBlockNumber()); + } catch (Exception e) { + Assert.fail(); + } + + // finalized -> blockCapsule2 (solid), has 1 tx + try { + TransactionResult result = + tronJsonRpc.getTransactionByBlockNumberAndIndex("finalized", "0x0"); + Assert.assertNotNull(result); + } catch (Exception e) { + Assert.fail(); + } + + // non-existent block number returns null (not an error) + try { + TransactionResult result = tronJsonRpc.getTransactionByBlockNumberAndIndex("0x1", "0x0"); + Assert.assertNull(result); + } catch (Exception e) { + Assert.fail(); + } + + // pending tag rejected + Exception pendingEx = Assert.assertThrows(Exception.class, + () -> tronJsonRpc.getTransactionByBlockNumberAndIndex("pending", "0x0")); + Assert.assertEquals(TAG_PENDING_SUPPORT_ERROR, pendingEx.getMessage()); + + // safe tag rejected (new tag) + Exception safeEx = Assert.assertThrows(Exception.class, + () -> tronJsonRpc.getTransactionByBlockNumberAndIndex("safe", "0x0")); + Assert.assertEquals(TAG_SAFE_SUPPORT_ERROR, safeEx.getMessage()); + + // malformed hex rejected + Exception qqqEx = Assert.assertThrows(Exception.class, + () -> tronJsonRpc.getTransactionByBlockNumberAndIndex("qqq", "0x0")); + Assert.assertEquals("invalid block number", qqqEx.getMessage()); + + // hex overflows long -> longValueExact rejects + Exception overflowEx = Assert.assertThrows(Exception.class, + () -> tronJsonRpc.getTransactionByBlockNumberAndIndex("0x10000000000000000", "0x0")); + Assert.assertEquals("invalid block number", overflowEx.getMessage()); + } + + /** + * Tests the object-form second argument of eth_call: + * {"blockNumber": "0x..."} or {"blockHash": "0x..."}. + * Only the block-selector parsing is exercised here; the call() + * execution path is covered by other tests. + */ + @Test + public void testGetCallWithBlockObject() { + // neither HashMap nor String -> invalid json request + Exception nonMapEx = Assert.assertThrows(Exception.class, + () -> tronJsonRpc.getCall(null, new Object())); + Assert.assertEquals("invalid json request", nonMapEx.getMessage()); + + // HashMap without blockNumber/blockHash keys -> invalid json request + Exception emptyMapEx = Assert.assertThrows(Exception.class, + () -> tronJsonRpc.getCall(null, new HashMap())); + Assert.assertEquals("invalid json request", emptyMapEx.getMessage()); + + // blockNumber with malformed hex -> invalid block number + HashMap badHexParams = new HashMap<>(); + badHexParams.put("blockNumber", "xxx"); + Exception badHexEx = Assert.assertThrows(Exception.class, + () -> tronJsonRpc.getCall(null, badHexParams)); + Assert.assertEquals("invalid block number", badHexEx.getMessage()); + + // blockNumber overflows long -> invalid block number (longValueExact) + HashMap overflowParams = new HashMap<>(); + overflowParams.put("blockNumber", "0x10000000000000000"); + Exception overflowEx = Assert.assertThrows(Exception.class, + () -> tronJsonRpc.getCall(null, overflowParams)); + Assert.assertEquals("invalid block number", overflowEx.getMessage()); + + // blockNumber points to a non-existent block -> header not found + HashMap missingNumParams = new HashMap<>(); + missingNumParams.put("blockNumber", "0x1"); + Exception missingNumEx = Assert.assertThrows(Exception.class, + () -> tronJsonRpc.getCall(null, missingNumParams)); + Assert.assertEquals("header not found", missingNumEx.getMessage()); + + // blockHash of an unknown block -> header for hash not found + HashMap missingHashParams = new HashMap<>(); + missingHashParams.put("blockHash", + "0x1111111111111111111111111111111111111111111111111111111111111111"); + Exception missingHashEx = Assert.assertThrows(Exception.class, + () -> tronJsonRpc.getCall(null, missingHashParams)); + Assert.assertEquals("header for hash not found", missingHashEx.getMessage()); } /** @@ -904,6 +1116,30 @@ public void testMethodBlockRange() { } } + @Test + public void testGetLogs() { + try { + LogFilterElement[] logs = tronJsonRpc.getLogs( + new FilterRequest("0x2710", "0x2710", null, null, null)); + Assert.assertTrue(logs.length > 0); + LogFilterElement log = logs[0]; + Assert.assertEquals(ByteArray.toJsonHex(blockCapsule1.getNum()), log.getBlockNumber()); + Assert.assertEquals(ByteArray.toJsonHex(blockCapsule1.getBlockId().toString()), + log.getBlockHash()); + Assert.assertEquals("0x0", log.getLogIndex()); + Assert.assertFalse(log.isRemoved()); + Assert.assertEquals(1, log.getTopics().length); + Assert.assertEquals( + "0x0000000000000000000000000000000000000000000000000000746f70696331", + log.getTopics()[0]); + Assert.assertEquals(ByteArray.toJsonHex("data1".getBytes()), log.getData()); + Assert.assertEquals(ByteArray.toJsonHex(blockCapsule1.getTimeStamp() / 1000), + log.getBlockTimestamp()); + } catch (Exception e) { + Assert.fail(); + } + } + @Test public void testNewFilterFinalizedBlock() { @@ -934,6 +1170,91 @@ public void testNewFilterFinalizedBlock() { Assert.assertEquals("invalid block range params", e5.getMessage()); } + /** + * Tag handling at the RPC boundary for eth_newFilter / eth_getLogs / eth_getFilterLogs. + * - safe/pending are rejected inside LogFilterWrapper (parseBlockNumber -> parseBlockTag) + * - finalized is intercepted by newFilter's upfront guard, but allowed by getLogs + * - getFilterLogs round-trips a filter created with concrete block numbers + */ + @Test + public void testLogFilterTagHandling() { + // eth_newFilter: safe in fromBlock -> TAG_SAFE_SUPPORT_ERROR + Exception newFilterSafeFromEx = Assert.assertThrows(Exception.class, + () -> tronJsonRpc.newFilter(new FilterRequest("safe", null, null, null, null))); + Assert.assertEquals(TAG_SAFE_SUPPORT_ERROR, newFilterSafeFromEx.getMessage()); + + // eth_newFilter: safe in toBlock + Exception newFilterSafeToEx = Assert.assertThrows(Exception.class, + () -> tronJsonRpc.newFilter(new FilterRequest("0x1", "safe", null, null, null))); + Assert.assertEquals(TAG_SAFE_SUPPORT_ERROR, newFilterSafeToEx.getMessage()); + + // eth_newFilter: pending in fromBlock + Exception newFilterPendingFromEx = Assert.assertThrows(Exception.class, + () -> tronJsonRpc.newFilter(new FilterRequest("pending", null, null, null, null))); + Assert.assertEquals(TAG_PENDING_SUPPORT_ERROR, newFilterPendingFromEx.getMessage()); + + // eth_newFilter: pending in toBlock + Exception newFilterPendingToEx = Assert.assertThrows(Exception.class, + () -> tronJsonRpc.newFilter(new FilterRequest("0x1", "pending", null, null, null))); + Assert.assertEquals(TAG_PENDING_SUPPORT_ERROR, newFilterPendingToEx.getMessage()); + + // eth_getLogs: safe in fromBlock + Exception getLogsSafeFromEx = Assert.assertThrows(Exception.class, + () -> tronJsonRpc.getLogs(new FilterRequest("safe", null, null, null, null))); + Assert.assertEquals(TAG_SAFE_SUPPORT_ERROR, getLogsSafeFromEx.getMessage()); + + // eth_getLogs: safe in toBlock + Exception getLogsSafeToEx = Assert.assertThrows(Exception.class, + () -> tronJsonRpc.getLogs(new FilterRequest(null, "safe", null, null, null))); + Assert.assertEquals(TAG_SAFE_SUPPORT_ERROR, getLogsSafeToEx.getMessage()); + + // eth_getLogs: pending in fromBlock + Exception getLogsPendingFromEx = Assert.assertThrows(Exception.class, + () -> tronJsonRpc.getLogs(new FilterRequest("pending", null, null, null, null))); + Assert.assertEquals(TAG_PENDING_SUPPORT_ERROR, getLogsPendingFromEx.getMessage()); + + // eth_getLogs: pending in toBlock + Exception getLogsPendingToEx = Assert.assertThrows(Exception.class, + () -> tronJsonRpc.getLogs(new FilterRequest(null, "pending", null, null, null))); + Assert.assertEquals(TAG_PENDING_SUPPORT_ERROR, getLogsPendingToEx.getMessage()); + + // eth_getLogs: finalized is accepted (resolves to solidBlockNum via parseBlockTag). + // With fromBlock empty, Strategy 2 resolves the range to [solid, solid]. blockCapsule2 + // (solid=4) has no logs in test fixtures, so result must be empty. + try { + LogFilterElement[] result = + tronJsonRpc.getLogs(new FilterRequest(null, "finalized", null, null, null)); + Assert.assertNotNull(result); + Assert.assertEquals(0, result.length); + } catch (Exception e) { + Assert.fail(e.getMessage()); + } + + // End-to-end happy path for eth_getLogs and eth_getFilterLogs. + // Query range [head, head] = [blockCapsule1, blockCapsule1]. No address/topic filter, + // so LogBlockQuery marks all blocks in the range as candidates. LogMatch then iterates + // blockCapsule1's 2 txs * 2 logs each = 4 LogFilterElements. + String headHex = ByteArray.toJsonHex(blockCapsule1.getNum()); + int expectedLogs = blockCapsule1.getTransactions().size() * 2; + + try { + LogFilterElement[] directResult = + tronJsonRpc.getLogs(new FilterRequest(headHex, headHex, null, null, null)); + Assert.assertEquals(expectedLogs, directResult.length); + } catch (Exception e) { + Assert.fail(e.getMessage()); + } + + try { + String filterIdHex = tronJsonRpc.newFilter( + new FilterRequest(headHex, headHex, null, null, null)); + LogFilterElement[] filterResult = tronJsonRpc.getFilterLogs(filterIdHex); + Assert.assertEquals(expectedLogs, filterResult.length); + } catch (Exception e) { + Assert.fail(e.getMessage()); + } + } + @Test public void testGetBlockReceipts() { @@ -946,6 +1267,10 @@ public void testGetBlockReceipts() { Assert.assertEquals( JSON.toJSONString(transactionReceipt), JSON.toJSONString(transactionReceipt1)); + + Assert.assertTrue(transactionReceipt1.getLogs().length > 0); + Assert.assertEquals(ByteArray.toJsonHex(blockCapsule1.getTimeStamp() / 1000), + transactionReceipt1.getLogs()[0].getBlockTimestamp()); } } catch (JsonRpcInvalidParamsException | JsonRpcInternalException e) { throw new RuntimeException(e); @@ -1001,6 +1326,13 @@ public void testGetBlockReceipts() { throw new RuntimeException(e); } + Exception safeReceiptsEx = Assert.assertThrows(Exception.class, + () -> tronJsonRpc.getBlockReceipts("safe")); + Assert.assertEquals(TAG_SAFE_SUPPORT_ERROR, safeReceiptsEx.getMessage()); + + Exception overflowReceiptsEx = Assert.assertThrows(Exception.class, + () -> tronJsonRpc.getBlockReceipts("0x10000000000000000")); + Assert.assertEquals("invalid block number", overflowReceiptsEx.getMessage()); } @Test diff --git a/framework/src/test/java/org/tron/core/jsonrpc/LogMatchExactlyTest.java b/framework/src/test/java/org/tron/core/jsonrpc/LogMatchExactlyTest.java index 0f9f125b74e..2151801fc59 100644 --- a/framework/src/test/java/org/tron/core/jsonrpc/LogMatchExactlyTest.java +++ b/framework/src/test/java/org/tron/core/jsonrpc/LogMatchExactlyTest.java @@ -37,6 +37,7 @@ private TransactionInfo createTransactionInfo(byte[] address, byte[][] topicArra LogInfo logInfo = new LogInfo(address, topics, data); logList.add(LogInfo.buildLog(logInfo)); builder.addAllLog(logList); + builder.setBlockTimeStamp(1000000L); return builder.build(); } @@ -230,6 +231,8 @@ public void testMatchBlock() { LogFilterElement logFilterElement1 = elementList.get(0); LogFilterElement logFilterElement2 = elementList2.get(0); + Assert.assertEquals("0x3e8", logFilterElement1.getBlockTimestamp()); + Assert.assertEquals("0x3e8", logFilterElement2.getBlockTimestamp()); Assert.assertEquals(logFilterElement1.hashCode(), logFilterElement2.hashCode()); Assert.assertEquals(logFilterElement1, logFilterElement2); diff --git a/framework/src/test/java/org/tron/core/services/jsonrpc/LogFilterWrapperStrategyTest.java b/framework/src/test/java/org/tron/core/services/jsonrpc/LogFilterWrapperStrategyTest.java new file mode 100644 index 00000000000..4150275c5e2 --- /dev/null +++ b/framework/src/test/java/org/tron/core/services/jsonrpc/LogFilterWrapperStrategyTest.java @@ -0,0 +1,162 @@ +package org.tron.core.services.jsonrpc; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.tron.core.Wallet; +import org.tron.core.exception.jsonrpc.JsonRpcInvalidParamsException; +import org.tron.core.services.jsonrpc.TronJsonRpc.FilterRequest; +import org.tron.core.services.jsonrpc.filters.LogFilterWrapper; + +/** + * Verify LogFilterWrapper strategies match develop branch behavior. + * + * Four filter strategies based on parameter emptiness (develop branch semantics): + * - Strategy 1: Both fromBlock and toBlock are empty -> (currentMaxBlockNum, Long.MAX_VALUE) + * - Strategy 2: fromBlock empty, toBlock non-empty -> based on toBlock value + * - Strategy 3: fromBlock non-empty, toBlock empty -> (fromBlock, Long.MAX_VALUE) + * - Strategy 4: Both non-empty -> parse both, handle "latest" using snapshot + */ +public class LogFilterWrapperStrategyTest { + + private Wallet mockWallet; + private static final long CURRENT_MAX_BLOCK = 81628775L; + + @Before + public void setUp() { + mockWallet = mock(Wallet.class); + when(mockWallet.getHeadBlockNum()).thenReturn(CURRENT_MAX_BLOCK); + when(mockWallet.getSolidBlockNum()).thenReturn(CURRENT_MAX_BLOCK - 100); + } + + private LogFilterWrapper createFilter(String fromBlock, String toBlock) throws Exception { + FilterRequest request = new FilterRequest(fromBlock, toBlock, null, null, null); + return new LogFilterWrapper(request, CURRENT_MAX_BLOCK, mockWallet, false); + } + + @Test + public void testStrategy1_BothNull() throws Exception { + LogFilterWrapper filter = createFilter(null, null); + assertEquals("fromBlock should be currentMaxBlockNum", CURRENT_MAX_BLOCK, + filter.getFromBlock()); + assertEquals("toBlock should be Long.MAX_VALUE", Long.MAX_VALUE, + filter.getToBlock()); + } + + @Test + public void testStrategy1_BothEmptyString() throws Exception { + LogFilterWrapper filter = createFilter("", ""); + assertEquals(CURRENT_MAX_BLOCK, filter.getFromBlock()); + assertEquals(Long.MAX_VALUE, filter.getToBlock()); + } + + @Test + public void testStrategy2_FromEmptyToHex() throws Exception { + // toBlock = 0x100 = 256 + // fromBlock = min(256, CURRENT_MAX_BLOCK) = 256 + LogFilterWrapper filter = createFilter(null, "0x100"); + assertEquals(256L, filter.getFromBlock()); + assertEquals(256L, filter.getToBlock()); + } + + @Test + public void testStrategy2_FromEmptyToLatest() throws Exception { + // toBlock = "latest" is treated as Long.MAX_VALUE in Strategy 2 + // fromBlock = min(Long.MAX_VALUE, CURRENT_MAX_BLOCK) = CURRENT_MAX_BLOCK + LogFilterWrapper filter = createFilter(null, "latest"); + assertEquals(CURRENT_MAX_BLOCK, filter.getFromBlock()); + assertEquals(Long.MAX_VALUE, filter.getToBlock()); + } + + @Test + public void testStrategy2_FromEmptyStringToHex() throws Exception { + LogFilterWrapper filter = createFilter("", "0x200"); + assertEquals(512L, filter.getFromBlock()); + assertEquals(512L, filter.getToBlock()); + } + + @Test + public void testStrategy3_FromHexToEmpty() throws Exception { + // fromBlock = 0x1 = 1 + // toBlock = Long.MAX_VALUE (tracking future blocks) + LogFilterWrapper filter = createFilter("0x1", null); + assertEquals(1L, filter.getFromBlock()); + assertEquals(Long.MAX_VALUE, filter.getToBlock()); + } + + @Test + public void testStrategy3_FromLatestToEmpty() throws Exception { + // fromBlock = "latest" (using snapshot) = currentMaxBlockNum + // toBlock = Long.MAX_VALUE + LogFilterWrapper filter = createFilter("latest", null); + assertEquals(CURRENT_MAX_BLOCK, filter.getFromBlock()); + assertEquals(Long.MAX_VALUE, filter.getToBlock()); + } + + @Test + public void testStrategy3_FromHexToEmptyString() throws Exception { + LogFilterWrapper filter = createFilter("0x5", ""); + assertEquals(5L, filter.getFromBlock()); + assertEquals(Long.MAX_VALUE, filter.getToBlock()); + } + + @Test + public void testStrategy4_BothHex() throws Exception { + // fromBlock = 1, toBlock = 256 + LogFilterWrapper filter = createFilter("0x1", "0x100"); + assertEquals(1L, filter.getFromBlock()); + assertEquals(256L, filter.getToBlock()); + } + + @Test + public void testStrategy4_BothLatest() throws Exception { + // Both "latest" are non-empty, so Strategy 4. + // fromBlock "latest" -> currentMaxBlockNum (snapshot). toBlock "latest" -> Long.MAX_VALUE. + LogFilterWrapper filter = createFilter("latest", "latest"); + assertEquals(CURRENT_MAX_BLOCK, filter.getFromBlock()); + assertEquals(Long.MAX_VALUE, filter.getToBlock()); + } + + @Test + public void testStrategy4_FromHexToLatest() throws Exception { + // fromBlock = 0x1 (concrete). toBlock = "latest" resolves to Long.MAX_VALUE. + LogFilterWrapper filter = createFilter("0x1", "latest"); + assertEquals(1L, filter.getFromBlock()); + assertEquals(Long.MAX_VALUE, filter.getToBlock()); + } + + @Test + public void testStrategy4_FromLatestToHexAboveLatest() throws Exception { + // This test requires a toBlock value larger than currentMaxBlockNum + // Using 0x5000000 (83886080) which is > 81628775 + LogFilterWrapper filter = createFilter("latest", "0x5000000"); + assertEquals(CURRENT_MAX_BLOCK, filter.getFromBlock()); + assertEquals(83886080L, filter.getToBlock()); + } + + @Test + public void testStrategy4_InvertedRangeThrows() throws Exception { + // fromBlock (0x100 = 256) > toBlock (0x1 = 1) should throw + try { + createFilter("0x100", "0x1"); + Assert.fail("Expected exception"); + } catch (JsonRpcInvalidParamsException e) { + assertEquals("please verify: fromBlock <= toBlock", e.getMessage()); + } + } + + @Test + public void testStrategy4_LatestGreaterThanSmallBlock_Throws() throws Exception { + // fromBlock = "latest" (currentMaxBlockNum = 81628775) > toBlock (0x100 = 256) should throw + try { + createFilter("latest", "0x100"); + Assert.fail("Expected exception"); + } catch (JsonRpcInvalidParamsException e) { + assertEquals("please verify: fromBlock <= toBlock", e.getMessage()); + } + } +} diff --git a/framework/src/test/java/org/tron/core/services/jsonrpc/TransactionReceiptTest.java b/framework/src/test/java/org/tron/core/services/jsonrpc/TransactionReceiptTest.java index a53a32daf45..e9cb8b7e274 100644 --- a/framework/src/test/java/org/tron/core/services/jsonrpc/TransactionReceiptTest.java +++ b/framework/src/test/java/org/tron/core/services/jsonrpc/TransactionReceiptTest.java @@ -32,6 +32,7 @@ public void testTransactionReceipt() throws JsonRpcInternalException { Protocol.TransactionInfo transactionInfo = Protocol.TransactionInfo.newBuilder() .setId(ByteString.copyFrom("1".getBytes())) .setContractAddress(ByteString.copyFrom("address1".getBytes())) + .setBlockTimeStamp(1000000L) .setReceipt(Protocol.ResourceReceipt.newBuilder() .setEnergyUsageTotal(100L) .setResult(Protocol.Transaction.Result.contractResult.DEFAULT) @@ -53,8 +54,11 @@ public void testTransactionReceipt() throws JsonRpcInternalException { Protocol.Block block = Protocol.Block.newBuilder().setBlockHeader( Protocol.BlockHeader.newBuilder().setRawData( - Protocol.BlockHeader.raw.newBuilder().setNumber(1))).addTransactions( - transaction).build(); + Protocol.BlockHeader.raw.newBuilder() + .setNumber(1) + .setTimestamp(1000000L))) + .addTransactions(transaction) + .build(); BlockCapsule blockCapsule = new BlockCapsule(block); long energyFee = wallet.getEnergyFee(blockCapsule.getTimeStamp()); @@ -65,35 +69,35 @@ public void testTransactionReceipt() throws JsonRpcInternalException { new TransactionReceipt(blockCapsule, transactionInfo, context, energyFee); Assert.assertNotNull(transactionReceipt); - String blockHash = "0x0000000000000001464f071c8a336fd22eb5145dff1b245bda013ec89add8497"; + String blockHash = "0x0000000000000001ba51f50f562758a449ff4a98df4febef89e122c1bb7e1a0c"; // assert basic fields - Assert.assertEquals(transactionReceipt.getBlockHash(), blockHash); - Assert.assertEquals(transactionReceipt.getBlockNumber(), "0x1"); - Assert.assertEquals(transactionReceipt.getTransactionHash(), "0x31"); - Assert.assertEquals(transactionReceipt.getTransactionIndex(), "0x0"); - Assert.assertEquals(transactionReceipt.getCumulativeGasUsed(), ByteArray.toJsonHex(102)); - Assert.assertEquals(transactionReceipt.getGasUsed(), ByteArray.toJsonHex(100)); - Assert.assertEquals(transactionReceipt.getEffectiveGasPrice(), ByteArray.toJsonHex(energyFee)); - Assert.assertEquals(transactionReceipt.getStatus(), "0x1"); + Assert.assertEquals(blockHash, transactionReceipt.getBlockHash()); + Assert.assertEquals("0x1", transactionReceipt.getBlockNumber()); + Assert.assertEquals("0x31", transactionReceipt.getTransactionHash()); + Assert.assertEquals("0x0", transactionReceipt.getTransactionIndex()); + Assert.assertEquals(ByteArray.toJsonHex(102), transactionReceipt.getCumulativeGasUsed()); + Assert.assertEquals(ByteArray.toJsonHex(100), transactionReceipt.getGasUsed()); + Assert.assertEquals(ByteArray.toJsonHex(energyFee), transactionReceipt.getEffectiveGasPrice()); + Assert.assertEquals("0x1", transactionReceipt.getStatus()); // assert contract fields - Assert.assertEquals(transactionReceipt.getFrom(), ByteArray.toJsonHexAddress(new byte[0])); - Assert.assertEquals(transactionReceipt.getTo(), ByteArray.toJsonHexAddress(new byte[0])); + Assert.assertEquals(ByteArray.toJsonHexAddress(new byte[0]), transactionReceipt.getFrom()); + Assert.assertEquals(ByteArray.toJsonHexAddress(new byte[0]), transactionReceipt.getTo()); Assert.assertNull(transactionReceipt.getContractAddress()); // assert logs fields - Assert.assertEquals(transactionReceipt.getLogs().length, 1); - Assert.assertEquals(transactionReceipt.getLogs()[0].getLogIndex(), "0x3"); - Assert.assertEquals( - transactionReceipt.getLogs()[0].getBlockHash(), blockHash); - Assert.assertEquals(transactionReceipt.getLogs()[0].getBlockNumber(), "0x1"); - Assert.assertEquals(transactionReceipt.getLogs()[0].getTransactionHash(), "0x31"); - Assert.assertEquals(transactionReceipt.getLogs()[0].getTransactionIndex(), "0x0"); + Assert.assertEquals(1, transactionReceipt.getLogs().length); + Assert.assertEquals("0x3", transactionReceipt.getLogs()[0].getLogIndex()); + Assert.assertEquals(blockHash, transactionReceipt.getLogs()[0].getBlockHash()); + Assert.assertEquals("0x1", transactionReceipt.getLogs()[0].getBlockNumber()); + Assert.assertEquals("0x31", transactionReceipt.getLogs()[0].getTransactionHash()); + Assert.assertEquals("0x0", transactionReceipt.getLogs()[0].getTransactionIndex()); + Assert.assertEquals("0x3e8", transactionReceipt.getLogs()[0].getBlockTimestamp()); // assert default fields Assert.assertNull(transactionReceipt.getRoot()); - Assert.assertEquals(transactionReceipt.getType(), "0x0"); - Assert.assertEquals(transactionReceipt.getLogsBloom(), ByteArray.toJsonHex(new byte[256])); + Assert.assertEquals("0x0", transactionReceipt.getType()); + Assert.assertEquals(ByteArray.toJsonHex(new byte[256]), transactionReceipt.getLogsBloom()); } }