Skip to content

feat(api): support int64_as_string parameter for GET requests #6699

Merged
lvs0075 merged 2 commits intotronprotocol:developfrom
waynercheung:feat/support_param_int64
May 8, 2026
Merged

feat(api): support int64_as_string parameter for GET requests #6699
lvs0075 merged 2 commits intotronprotocol:developfrom
waynercheung:feat/support_param_int64

Conversation

@waynercheung
Copy link
Copy Markdown
Collaborator

@waynercheung waynercheung commented Apr 23, 2026

What does this PR do?

Adds 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 (e.g. "123456789012345" instead of 123456789012345), preventing precision loss in clients whose native number type cannot safely represent integers above 2^53 - 1 (e.g. JavaScript).

# Without flag (unchanged from current develop)
$ curl http://node/wallet/getnowblock | jq .block_header.raw_data.timestamp
1700000000000
 
# With flag
$ curl 'http://node/wallet/getnowblock?int64_as_string=true' | jq .block_header.raw_data.timestamp
"1700000000000"

Scope: GET only, by design.

POST is intentionally unsupported. Reading the body in a centralized location (RateLimiterServlet.service or a Servlet Filter) would consume request.getReader(), which TRON's existing 25 servlets already consume themselves for business logic. Centralizing POST would require introducing a CachingHttpServletRequestWrapper to make the body re-readable -- significant infrastructure (~150 lines), runtime overhead (every POST body byte-copied to memory), and edge cases (multipart, encoding, large bodies). The simpler and lower-risk approach is to limit this flag to GET.

Most TRON query endpoints support both GET and POST. JS clients that need precision can use the GET form. POST-only write endpoints (Transfer / Trigger / Proposal*) return Transaction proto whose int64 fields would break round-trip JsonFormat.merge if quoted -- those clients should not enable this flag in the first place.

Backward compatibility: when the parameter is absent or false, every response is byte-identical to current develop -- same JSON shape, same error messages, same log output, same exception paths.


Why are these changes required?

JavaScript Number is IEEE 754 double-precision with 53 bits of mantissa. Any int64 / uint64 value larger than 2^53 - 1 loses precision when consumed as a JS number -- for example, an account balance of 9007199254740993 becomes 9007199254740992. Many TRON HTTP fields routinely exceed this threshold (balance, frozen.amount, energy/bandwidth limits, transaction fee_limit, block timestamp in microseconds, etc.).

The standard mitigation across protobuf JSON ecosystems (proto3 JSON spec, Google's protobuf-java JsonFormat) is to emit 64-bit integers as quoted strings. This PR brings TRON's HTTP API in line with that convention while keeping the new behavior fully opt-in.

Closes #6568.


This PR has been tested by:

  • Unit Tests -- 19 tests across 2 files, all passing locally:

    • JsonFormatInt64AsStringTest (13 tests): 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.
    • RateLimiterServletInt64Test (6 tests): end-to-end integration pinning four contracts:
      1. GET with ?int64_as_string=true produces quoted int64 fields.
      2. GET without flag produces unquoted int64 fields (regression baseline).
      3. POST never honors the flag (pins the GET-only design contract).
      4. service()'s finally block clears the ThreadLocal (defends against silent leak across reused Tomcat threads).
      5. Hand-built JSON servlets (GetBurnTrx / GetPendingSize / GetTransactionCountByBlockNum / GetReward) honor the flag through their isInt64AsString() ? quoted : unquoted ternary -- mutation-tested against ternary inversion.
      6. Hand-built JSON servlets default to unquoted output (regression baseline).
  • Manual Testing -- recommended pre-merge: hit a few endpoints with and without the flag, diff the JSON shape, confirm no regression on the default path.


Follow up

  1. Documentation update -- client-facing docs should note: (1) the flag is honored only on GET requests; (2) when set, responses containing Transaction / Block / TransactionExtention / TransactionSignWeight / TransactionApprovedList proto structures cannot be re-parsed by JsonFormat.merge (the parser does not accept quoted int64 input). Clients that need precision and only consume the response should enable the flag; clients that need round-trip should leave it off.
  2. POST support (optional, future) -- if clients later request POST support, it can be added in an independent follow-up PR by introducing a Servlet Filter + CachingHttpServletRequestWrapper. The trade-offs of that approach (body memory caching, multipart edge cases) deserve their own scoped review.
  3. JSON-RPC -- out of scope for this PR. If JS clients hit similar precision issues via the JSON-RPC layer, it would be addressed separately.
  4. Two endpoints bypass JsonFormat -- /wallet/getnodeinfo and /monitor/getstatsinfo serialize their POJO responses (NodeInfo / MetricsInfo) directly via fastjson, so int64_as_string has no effect there. The long fields they expose (memory sizes, network counters, sync numbers, sampling intervals) are well within JS safe integer range in any realistic deployment, so this is a contract-consistency note rather than a precision concern. Can be addressed in a follow-up by passing SerializerFeature.BrowserCompatible to JSON.toJSONString conditionally.

Extra details

Files changed (9 total).

Core machinery:

  • JsonFormat.java: INT64_AS_STRING ThreadLocal + setInt64AsString / clearInt64AsString / isInt64AsString helpers; split INT64 and UINT64 branches in printFieldValue to emit quoted strings only when the flag is set. When the flag is unset, the code path is byte-identical to develop.
  • Util.java: 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. The finally clear has a defensive comment because removing it silently breaks request isolation.
    Hand-built JSON responses (4 files) -- these emit JSON literals manually instead of going through JsonFormat.printToString, so they read JsonFormat.isInt64AsString() to choose between quoted and unquoted format:
  • GetBurnTrxServlet: burnTrxAmount field
  • GetPendingSizeServlet: pendingSize field
  • GetTransactionCountByBlockNumServlet: count field
  • GetRewardServlet: reward field
    Test coverage:
  • JsonFormatInt64AsStringTest (mechanism level)
  • RateLimiterServletInt64Test (end-to-end integration, including critical defense against ThreadLocal leakage)
    ~50 other read-only query servlets automatically pick up the flag through the centralized RateLimiterServlet.service GET path with zero changes at the call site.

Sample contract for clients

int64_as_string parameter (issue #6568):
 
  Format:
    GET /wallet/<endpoint>?int64_as_string=true
 
  Behavior:
    When set, all int64/uint64 fields in the response are serialized as
    quoted JSON strings. This avoids precision loss in clients whose native
    number type cannot safely represent integers above 2^53 - 1 (e.g.,
    JavaScript).
 
  Limitation:
    Only honored on GET requests. POST requests always emit unquoted int64
    fields. If you need precision-safe responses, use GET; the same data is
    available on the GET form of every dual-method endpoint.
 
  Round-trip note:
    Responses containing Transaction / Block / TransactionExtention /
    TransactionSignWeight / TransactionApprovedList proto structures
    cannot be re-parsed by JsonFormat.merge when this flag is enabled.
    If your client needs round-trip (e.g., signing, broadcasting), do not
    enable this flag.

Comment thread framework/src/main/java/org/tron/core/services/http/Util.java Fixed
@waynercheung waynercheung reopened this Apr 23, 2026
@waynercheung waynercheung changed the title feat(http): add int64_as_string parameter to query endpoints feat(http): add int64_as_string parameter to query endpoints Apr 23, 2026
@halibobo1205 halibobo1205 added this to the GreatVoyage-v4.8.2 milestone Apr 23, 2026
@halibobo1205 halibobo1205 added the topic:api rpc/http related issue label Apr 23, 2026
@waynercheung waynercheung force-pushed the feat/support_param_int64 branch 6 times, most recently from b2ce599 to 0587166 Compare May 1, 2026 07:22
@waynercheung waynercheung changed the title feat(http): add int64_as_string parameter to query endpoints feat(api): support int64_as_string parameter for GET requests May 1, 2026
…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
@waynercheung waynercheung requested a review from lxcmyf May 6, 2026 10:12
Copy link
Copy Markdown
Collaborator

@halibobo1205 halibobo1205 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@317787106
Copy link
Copy Markdown
Collaborator

317787106 commented May 7, 2026

@waynercheung It’s a bit unfortunate that POST request parameters are not supported, as the functionality feels somewhat incomplete. Do you have plans to support this in the future?

This PR — java-tron PR #6728 — introduced CachedBodyRequestWrapper and added request validation.

Comment thread framework/src/main/java/org/tron/core/services/http/Util.java
Comment thread framework/src/main/java/org/tron/core/services/http/Util.java Outdated
@waynercheung
Copy link
Copy Markdown
Collaborator Author

@waynercheung It’s a bit unfortunate that POST request parameters are not supported, as the functionality feels somewhat incomplete. Do you have plans to support this in the future?

This PR — java-tron PR #6728 — introduced CachedBodyRequestWrapper and added request validation.

@317787106 Thanks for raising this. A few reasons this PR stays GET-only:

  1. int64_as_string is a pure read-side format flag, the kind of parameter that semantically belongs on GET. REST conventions place query/format parameters on retrieval verbs, not on POST -- POST is conceptually for state-changing or non-idempotent operations. Extending a new format flag to POST would tie it into a legacy pattern (POST-for-read) that we'd rather not amplify going forward. Existing endpoints that historically accept POST for reads continue to work as before; we're simply not extending new format flags to that pattern.

  2. POST body retrieval in TRON's HTTP layer is fragmented: 25+ servlets read the POST body in three different ways (PostParams.getPostParams, inline request.getReader(), Util.getRequestValue). There is no single chokepoint where int64_as_string can be extracted from POST body without either patching every servlet individually or introducing global request-body-caching infrastructure. The wrapper from feat(jsonrpc): add resource restrict for jsonrpc #6728 helps on the second point but does not address the first.

  3. feat(jsonrpc): add resource restrict for jsonrpc #6728 is still open and registers CachedBodyRequestWrapper only inside JsonRpcServlet.service(), not at the HTTP servlet layer. There's also an unresolved spec-compliance comment from @bladehan1 about getInputStream() / getReader() mutual exclusivity. So the infrastructure is not directly reusable for the HTTP layer yet.

If POST support becomes a clear need, I'd rather do it properly as a follow-up once #6728 lands and the spec issue is resolved -- happy to file a tracking issue at that point so it doesn't get lost.

Three small adjustments per review on PR tronprotocol#6699:

- GetTransactionCountByBlockNumServlet: add trailing newline at end of
  file to satisfy checkstyle.
- Util.getInt64AsString: align control flow with the existing
  Util.getVisible (single-return via local boolean, Boolean.valueOf
  instead of Boolean.parseBoolean). Functionally identical -- both
  return true only when the parameter value is "true" (case-insensitive).
- Util.INT64_AS_STRING -> Util.INT64_AS_STRING_PARAM: rename the public
  parameter-name constant to avoid potential confusion with the unrelated
  private ThreadLocal field of the same simple name in JsonFormat.
  The user-facing query parameter remains "int64_as_string" -- only the
  Java identifier changes.
@waynercheung waynercheung requested a review from 317787106 May 7, 2026 07:39
@lvs0075 lvs0075 merged commit 709e1c3 into tronprotocol:develop May 8, 2026
12 checks passed
@github-project-automation github-project-automation Bot moved this to Done in java-tron May 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

topic:api rpc/http related issue

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

Regarding the potential numeric overflow issue when querying asset values

7 participants