feat(http): add int64_as_string parameter to query endpoints#6699
Open
waynercheung wants to merge 9 commits intotronprotocol:developfrom
Open
feat(http): add int64_as_string parameter to query endpoints#6699waynercheung wants to merge 9 commits intotronprotocol:developfrom
waynercheung wants to merge 9 commits intotronprotocol:developfrom
Conversation
…#6568 Introduce JsonFormat.pushInt64AsString(boolean) returning an AutoCloseable, using a strictly scoped ThreadLocal to serialize int64/uint64 protobuf fields as quoted JSON strings within try-with-resources blocks. This avoids precision loss in clients whose native number type cannot safely represent integers above 2^53 - 1 (e.g. JavaScript). - JsonFormat: add INT64_AS_STRING ThreadLocal + pushInt64AsString helper; split printFieldValue INT64/SINT64/SFIXED64 and UINT64/FIXED64 branches so they emit quoted strings only when the helper is active. - Util: add INT64_AS_STRING constant + getInt64AsString (URL query, mirrors getVisible) + getInt64AsStringPost (JSON body, mirrors getVisiblePost). - PostParams: add int64AsString field; 3-arg primary constructor; keep legacy 2-arg constructor as adapter defaulting to false; getPostParams parses the new field from the body. - Test: JsonFormatInt64AsStringTest covers default behavior, int64/uint64 quoting, non-int64 fields unaffected, nested/map/boundary values (2^53 +/- 1, Long.MAX/MIN, -1), scope lifecycle (clean after normal close, after exception, after explicit close, across nested scopes), thread isolation, thread-reuse anti-pollution, PostParams body parsing, and the adapter constructor defaults.
tronprotocol#6568 Thread the new JsonFormat.pushInt64AsString helper through every query servlet whose response contains int64/uint64 fields, preserving existing `visible` semantics on each route. Parameter reading mirrors visible: GET reads URL query via Util.getInt64AsString; POST that uses PostParams reads the JSON body via PostParams.isInt64AsString; the single POST path that manually reads the request body (GetPaginatedProposalListServlet) calls Util.getInt64AsStringPost directly.
…ve servlets Extend GetRewardServletTest and GetNowBlockServletTest with int64_as_string matrix cases: - default (no param) preserves pre-existing number output - int64_as_string=true stringifies the int64 field(s) in the response - int64_as_string=false behaves identically to the default
…ckServlet.parseParams The GET branch of GetBlockServlet.parseParams previously constructed a 3-arg PostParams inline with Boolean.parseBoolean(request.getParameter(Util.VISIBLE)) for visible and Util.getInt64AsString(request) for int64_as_string. Extract both into named local booleans so the call site is symmetric and readable without needing a comment, and switch the visible read to Util.getVisible(request) to align with how every other servlet in this package reads the flag. Behavior is unchanged: Util.getVisible is the standard Boolean.parseBoolean + null-safety wrapper already defined in Util.java.
…aginatedProposalListServlet Two whitelisted servlets have non-standard integration shapes that deserve their own end-to-end tests beyond what JsonFormatInt64AsStringTest covers: - GetBlockServlet delegates both doGet and doPost to a private handle() method whose parseParams() assembles a PostParams from either URL query (GET) or JSON body (POST). GetBlockServletTest exercises the GET query path, the POST body path (int64_as_string=true / =false), and verifies that URL query is ignored on POST (mirroring visible semantics). - GetPaginatedProposalListServlet.doPost manually reads the request body and bypasses PostParams.getPostParams, so it takes the direct Util.getInt64AsStringPost(input) branch instead of params.isInt64AsString(). GetPaginatedProposalListServletTest exercises that branch plus the regular doGet path.
A later review of FullNodeHttpApiService surfaced these query servlets as also returning int64 fields: getproposalbyid, getexchangebyid, getcontract, getcontractinfo, getmarketorderbyaccount, getmarketorderbyid, getmarketorderlistbypair, getmarketpricebypair, getblockbalance, getnextmaintenancetime.
Cover Util.decodeAddress (blank-input and mainnet-hex branches) and Util.processAddressError in UtilMockTest; both were previously exercised only indirectly.
ListExchangesServlet.doPost reads visible and int64_as_string from the URL query, not from the JSON body -- a counter-intuitive shape shared only by GetChainParametersServlet. Without a test, a future refactor that "normalizes" this POST path to read from the body would silently break clients that pass the flag via the URL.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What does this PR do?
Closes #6568. Adds an optional
int64_as_stringrequest parameter on whitelisted query servlets. When set totrue,int64/uint64protobuf fields in the response are serialized as quoted JSON strings, avoiding precision loss in clients whose native number type cannot safely represent integers above 2^53 - 1 (most notably JavaScript).Design:
JsonFormat.pushInt64AsString(boolean)returns anAutoCloseablecontrolling a scope-boundThreadLocal. Each whitelisted servlet wraps the final response write intry (AutoCloseable ignored = JsonFormat.pushInt64AsString(flag)) { ... }.GetRewardServlet.doPostparses bothaddressandint64_as_stringfrom the same JSON body in a single read.JsonFormat, and endpoints whose response has no int64 fields are left untouched.JsonFormat.mergeis deliberately not modified — input contract stays strict (see "Known limitation" below)./walletsolidity/*routes inherit the feature via Spring bean reuse inSolidityNodeHttpApiService(two solidity-specific servlets —GetTransactionByIdSolidityServletandGetTransactionInfoByIdSolidityServlet— needed explicit edits)./walletpbft/*routes inherit via Java subclassing: the PBFT query servlets covered here extend their corresponding wallet servlet and only wrap it inwalletOnPBFT.futureGet(...), so the superclass'sint64_as_stringhandling applies without further changes.Why are these changes required?
Full rationale and scope discussion in #6568. TL;DR: JS clients lose precision parsing
int64values likebalance,total_supply, and asset/market balances once the value exceeds 2^53 - 1. This PR gives clients an opt-in way to receive those fields as strings.Supported
/wallet/*endpoints (this list enumerates/wallet/*endpoints only; equivalent/walletsolidity/*and/walletpbft/*routes are supported where those routes already exist and reuse the same servlet logic per the mechanisms described above):Explicitly not supported (code unchanged; the flag is silently ignored):
getnodeinfoJSON.toJSONString(nodeInfo)on a POJO, bypassingJsonFormat; thepushInt64AsStringhelper does not apply.NodeInfohas long fields that do fall within this issue's domain; supporting them needs a separate fastjson-based mechanism. Deferred to follow-up as a deliberate scope cut, not a claim of "no precision risk".getsignweight,getapprovedlistTransactionExtentionthat clients round-trip back for signing.getBrokerageint, notlong.getbandwidthprices,getenergyprices,getmemofeePricesResponseMessage.prices) has no int64 fields.gettransactionlistfrompendingTransactionIdListhas no int64 fields (repeated string txIdonly).getmarketpairlistMarketOrderPairhas onlybytestoken ids.listnodesNodeList/Node/Addresscontain onlystring ipandint32 port.create*/trigger*/broadcast*/deploy*/transfer*/freeze*/unfreeze*/vote*/proposal*/exchange*(create/inject/transaction/withdraw) / write-path market endpoints (e.g.marketsellasset,marketcancelorder) /participate*/withdraw*JsonFormat.mergeon the client side for signing and re-broadcasting.This PR has been tested by:
JsonFormatInt64AsStringTest(20 cases): default behavior, int64/uint64 quoting, non-int64 fields unaffected, nested / map / boundary values (2^53 ± 1,Long.MAX/MIN,-1), scope lifecycle (clean close, exception, explicit close, nested scopes), thread isolation, thread-reuse anti-pollution, PostParams body parsing.UtilMockTest(+3 cases):Util.decodeAddressblank-input and mainnet-hex branches,Util.processAddressErrorerror-JSON shape.GetRewardServletTest— hand-rolled JSON servlet + POST JSON body fix.GetNowBlockServletTest— JsonFormat path withdoPost → doGetdelegation.GetBlockServletTest(new) — shared privatehandle()with customparseParams.GetPaginatedProposalListServletTest(new) —doPostthat manually reads the body viaUtil.getInt64AsStringPost.ListExchangesServletTest(new) — URL-query-only POST contract.testPostBodyFlagIsIgnoredpins the counter-intuitive "body-only flag must not take effect" semantic against future refactors.org.tron.core.services.http.*continue to pass../gradlew :framework:checkstyleMain :framework:checkstyleTestpasses.Follow up
developers.tron.networkAPI reference needs anint64_as_stringparameter entry for each supported endpoint plus a top-level section documenting GET vs POST read paths and the round-trip limitation.GetNodeInfoServlet: addint64_as_stringsupport via a fastjson localSerializeConfigbindingLong/longtoToStringSerializer, since this endpoint bypassesJsonFormat. Deferred as a deliberate scope cut.Extra details
Transaction/BlockresponsesJsonFormat.merge()only accepts unquoted 64-bit integer tokens. As a result, responses from block and transaction endpoints whose payload embeds aTransactionorBlockprotobuf (thegetnowblock/getblock*family andgettransactionbyid/gettransactionfrompending, plus their/walletsolidity/*and/walletpbft/*counterparts where exposed) cannot be locally parsed back into a protobuf message viaJsonFormat.mergewhenint64_as_string=trueis set. This is a deliberate design choice, not a bug — keepingmergestrict preserves backward-compatible input semantics and avoids widening the signing/broadcast input contract. Clients needing round-trip simply don't set the flag.Thread safety & wire compatibility
Preserve-restore
ThreadLocalwith try-with-resources guarantees cleanup on normal return, exception, and nested scopes; explicitly validated against Jetty'sQueuedThreadPoolreuse bynoPollutionOnThreadReuseandthreadIsolationunit tests.Zero protobuf schema changes. Default behavior (flag absent) is preserved: existing clients see no change in response encoding.
GetRewardServlet.doPostwas restructured soapplication/jsonPOST parses bothaddressandint64_as_stringfrom a single body read — fixes a body-internal inconsistency surfaced during review (address from body, flag from URL query). Form-encoded POSTs still delegate todoGet.