Skip to content

feat(http): add int64_as_string parameter to query endpoints#1

Open
waynercheung wants to merge 1 commit intodevelopfrom
feat/support_param_int64
Open

feat(http): add int64_as_string parameter to query endpoints#1
waynercheung wants to merge 1 commit intodevelopfrom
feat/support_param_int64

Conversation

@waynercheung
Copy link
Copy Markdown
Owner

@waynercheung waynercheung commented Apr 23, 2026

What does this PR do?

Closes tronprotocol#6568. Adds an optional int64_as_string request parameter on whitelisted query servlets. When set to true, int64/uint64 protobuf 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:

  • Core mechanism: JsonFormat.pushInt64AsString(boolean) returns an AutoCloseable controlling a scope-bound ThreadLocal. Each whitelisted servlet wraps the final response write in try (AutoCloseable ignored = JsonFormat.pushInt64AsString(flag)) { ... }.
  • Parameter reading mirrors each servlet's existing parameter-reading shape where applicable (GET query / POST body). One special case: GetRewardServlet.doPost parses both address and int64_as_string from the same JSON body in a single read.
  • Opt-in whitelist: write paths, endpoints bypassing JsonFormat, and endpoints whose response has no int64 fields are left untouched.
  • JsonFormat.merge is deliberately not modified — input contract stays strict (see "Known limitation" below).
  • /walletsolidity/* routes inherit the feature via Spring bean reuse in SolidityNodeHttpApiService (two solidity-specific servlets — GetTransactionByIdSolidityServlet and GetTransactionInfoByIdSolidityServlet — needed explicit edits).
  • /walletpbft/* routes inherit via Java subclassing: the PBFT query servlets covered here extend their corresponding wallet servlet and only wrap it in walletOnPBFT.futureGet(...), so the superclass's int64_as_string handling applies without further changes.

Why are these changes required?

Full rationale and scope discussion in tronprotocol#6568. TL;DR: JS clients lose precision parsing int64 values like balance, 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):

  • Account / asset / resource queries — getaccount, getaccountresource, getaccountnet, getaccountbyid, getaccountbalance, getassetissuebyid, getassetissuebyname, getassetissuelist, getassetissuelistbyname, getassetissuebyaccount, getpaginatedassetissuelist, getdelegatedresource(v2), getdelegatedresourceaccountindex(v2), getcanwithdrawunfreezeamount, getavailableunfreezecount, getcandelegatedmaxsize
  • Block / transaction queries — getnowblock, getblock, getblockbynum, getblockbyid, getblockbylimitnext, getblockbylatestnum, getblockbalance, gettransactionbyid, gettransactionfrompending, gettransactioninfobyid, gettransactioninfobyblocknum, gettransactionreceiptbyid, gettransactioncountbyblocknum, totaltransaction
  • Governance / chain queries — listwitnesses, listproposals, listexchanges, getpaginatednowwitnesslist, getpaginatedproposallist, getpaginatedexchangelist, getchainparameters, getnextmaintenancetime, getproposalbyid, getexchangebyid
  • Contract / market queries — getcontract, getcontractinfo, getmarketorderbyaccount, getmarketorderbyid, getmarketorderlistbypair, getmarketpricebypair
  • Other int64-bearing queries — getReward, getburntrx, getpendingsize
    Explicitly not supported (code unchanged; the flag is silently ignored):
Endpoint(s) Reason
getnodeinfo Response is serialized via JSON.toJSONString(nodeInfo) on a POJO, bypassing JsonFormat; the pushInt64AsString helper does not apply. NodeInfo has 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, getapprovedlist Response embeds a TransactionExtention that clients round-trip back for signing.
getBrokerage Response field is a 0-100 int, not long.
getbandwidthprices, getenergyprices, getmemofee Response (PricesResponseMessage.prices) has no int64 fields.
gettransactionlistfrompending TransactionIdList has no int64 fields (repeated string txId only).
getmarketpairlist MarketOrderPair has only bytes token ids.
listnodes NodeList/Node/Address contain only string ip and int32 port.
All create* / trigger* / broadcast* / deploy* / transfer* / freeze* / unfreeze* / vote* / proposal* / exchange* (create/inject/transaction/withdraw) / write-path market endpoints (e.g. marketsellasset, marketcancelorder) / participate* / withdraw* Write paths; their responses enter JsonFormat.merge on the client side for signing and re-broadcasting.

This PR has been tested by:

  • Unit Tests:
    • 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.decodeAddress blank-input and mainnet-hex branches, Util.processAddressError error-JSON shape.
  • Integration Tests (one representative per template shape):
    • GetRewardServletTest — hand-rolled JSON servlet + POST JSON body fix.
    • GetNowBlockServletTest — JsonFormat path with doPost → doGet delegation.
    • GetBlockServletTest (new) — shared private handle() with custom parseParams.
    • GetPaginatedProposalListServletTest (new) — doPost that manually reads the body via Util.getInt64AsStringPost.
    • ListExchangesServletTest (new) — URL-query-only POST contract. testPostBodyFlagIsIgnored pins the counter-intuitive "body-only flag must not take effect" semantic against future refactors.
  • All existing tests in org.tron.core.services.http.* continue to pass.
  • ./gradlew :framework:checkstyleMain :framework:checkstyleTest passes.

Follow up

  • developers.tron.network API reference needs an int64_as_string parameter entry for each supported endpoint plus a top-level section documenting GET vs POST read paths and the round-trip limitation.
  • GetNodeInfoServlet: add int64_as_string support via a fastjson local SerializeConfig binding Long/long to ToStringSerializer, since this endpoint bypasses JsonFormat. Deferred as a deliberate scope cut.

Extra details

  • Known limitation — round-trip on Transaction / Block responses

JsonFormat.merge() only accepts unquoted 64-bit integer tokens. As a result, responses from block and transaction endpoints whose payload embeds a Transaction or Block protobuf (the getnowblock / getblock* family and gettransactionbyid / gettransactionfrompending, plus their /walletsolidity/* and /walletpbft/* counterparts where exposed) cannot be locally parsed back into a protobuf message via JsonFormat.merge when int64_as_string=true is set. This is a deliberate design choice, not a bug — keeping merge strict 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 ThreadLocal with try-with-resources guarantees cleanup on normal return, exception, and nested scopes; explicitly validated against Jetty's QueuedThreadPool reuse by noPollutionOnThreadReuse and threadIsolation unit tests.

  • Zero protobuf schema changes. Default behavior (flag absent) is preserved: existing clients see no change in response encoding.

  • GetRewardServlet.doPost was restructured so application/json POST parses both address and int64_as_string from a single body read — fixes a body-internal inconsistency surfaced during review (address from body, flag from URL query). Form-encoded POSTs still delegate to doGet.

Comment thread framework/src/main/java/org/tron/core/services/http/Util.java Fixed
@waynercheung waynercheung force-pushed the feat/support_param_int64 branch 6 times, most recently from b2ce599 to 0587166 Compare May 1, 2026 07:22
…otocol#6568)

Add an opt-in `int64_as_string` query parameter on TRON HTTP GET endpoints.
When set, int64/uint64 protobuf fields in the response are serialized as
quoted JSON strings to avoid precision loss in clients whose native number
type cannot safely represent integers above 2^53 - 1 (e.g. JavaScript).

Scope: GET only. POST is intentionally unsupported because reading the
request body in a centralized location (RateLimiterServlet.service or a
Filter) would consume request.getReader() and break downstream servlets
that read the body themselves. Most TRON query endpoints support both
GET and POST, so clients that need precision can use the GET form. POST-
only write endpoints return Transaction proto whose int64 fields would
break round-trip JsonFormat.merge if quoted, so they should not enable
this flag in the first place.

- JsonFormat: add INT64_AS_STRING ThreadLocal + setInt64AsString /
  clearInt64AsString / isInt64AsString helpers; split printFieldValue
  INT64/SINT64/SFIXED64 and UINT64/FIXED64 branches so they emit quoted
  strings only when the flag is set.
- Util: add INT64_AS_STRING constant + getInt64AsString (URL query,
  mirrors getVisible).
- RateLimiterServlet.service: set ThreadLocal from URL query on GET only;
  clear in finally so reused Tomcat threads do not leak state across
  requests.
- GetBurnTrx / GetPendingSize / GetTransactionCountByBlockNum: emit
  quoted int64 in their hand-built JSON responses when isInt64AsString
  is true.
- JsonFormatInt64AsStringTest: covers default behavior, int64 / uint64
  quoting, non-int64 fields unaffected, nested / map / boundary values
  (2^53 +/- 1, Long.MAX/MIN, -1), state cleanup (normal close, after
  exception, explicit clear), thread isolation, thread-reuse anti-pollution.

Backward compatibility: requests without int64_as_string=true produce
byte-identical responses to develop -- the new code paths are gated
entirely on the new flag.

Closes tronprotocol#6568.
@waynercheung waynercheung force-pushed the feat/support_param_int64 branch from 0587166 to cb77f36 Compare May 1, 2026 09:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Regarding the potential numeric overflow issue when querying asset values

2 participants