From 2b53916c1e04bf38c4d47b98c47bf93b13dfbba9 Mon Sep 17 00:00:00 2001 From: waynercheung Date: Tue, 21 Apr 2026 19:19:22 +0800 Subject: [PATCH 1/9] feat(http): add int64_as_string core machinery for issue #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. --- .../tron/core/services/http/JsonFormat.java | 57 +++- .../tron/core/services/http/PostParams.java | 17 +- .../org/tron/core/services/http/Util.java | 28 ++ .../http/JsonFormatInt64AsStringTest.java | 320 ++++++++++++++++++ 4 files changed, 416 insertions(+), 6 deletions(-) create mode 100644 framework/src/test/java/org/tron/core/services/http/JsonFormatInt64AsStringTest.java diff --git a/framework/src/main/java/org/tron/core/services/http/JsonFormat.java b/framework/src/main/java/org/tron/core/services/http/JsonFormat.java index 96dedb1e20c..8d5fa405093 100644 --- a/framework/src/main/java/org/tron/core/services/http/JsonFormat.java +++ b/framework/src/main/java/org/tron/core/services/http/JsonFormat.java @@ -90,6 +90,40 @@ public class JsonFormat { BalanceContract.TransactionBalanceTrace.class ); + /** + * Thread-local flag controlling whether int64/uint64 fields are serialized as JSON strings. + * Scope is strictly controlled via {@link #pushInt64AsString(boolean)} with try-with-resources. + * Do not read or write this ThreadLocal directly from outside this class. + */ + private static final ThreadLocal INT64_AS_STRING = + ThreadLocal.withInitial(() -> false); + + /** + * Enter a scope where int64/uint64 protobuf fields 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). Must be used with try-with-resources: + *
{@code
+   * try (AutoCloseable ignored = JsonFormat.pushInt64AsString(enabled)) {
+   *   response.getWriter().println(JsonFormat.printToString(message, visible));
+   * }
+   * }
+ * Preserves and restores the previous value to support rare nested usage. + * + * @param enabled whether to serialize int64/uint64 as quoted strings within the scope + * @return an AutoCloseable that restores the previous state when closed + */ + public static AutoCloseable pushInt64AsString(boolean enabled) { + boolean previous = INT64_AS_STRING.get(); + INT64_AS_STRING.set(enabled); + return () -> { + if (previous) { + INT64_AS_STRING.set(true); + } else { + INT64_AS_STRING.remove(); + } + }; + } + /** * Outputs a textual representation of the Protocol Message supplied into the parameter output. * (This representation is the new version of the classic "ProtocolPrinter" output from the @@ -340,11 +374,8 @@ private static void printFieldValue(FieldDescriptor field, Object value, throws IOException { switch (field.getType()) { case INT32: - case INT64: case SINT32: - case SINT64: case SFIXED32: - case SFIXED64: case FLOAT: case DOUBLE: case BOOL: @@ -352,6 +383,18 @@ private static void printFieldValue(FieldDescriptor field, Object value, generator.print(value.toString()); break; + case INT64: + case SINT64: + case SFIXED64: + if (INT64_AS_STRING.get()) { + generator.print("\""); + generator.print(value.toString()); + generator.print("\""); + } else { + generator.print(value.toString()); + } + break; + case UINT32: case FIXED32: generator.print(unsignedToString((Integer) value)); @@ -359,7 +402,13 @@ private static void printFieldValue(FieldDescriptor field, Object value, case UINT64: case FIXED64: - generator.print(unsignedToString((Long) value)); + if (INT64_AS_STRING.get()) { + generator.print("\""); + generator.print(unsignedToString((Long) value)); + generator.print("\""); + } else { + generator.print(unsignedToString((Long) value)); + } break; case STRING: diff --git a/framework/src/main/java/org/tron/core/services/http/PostParams.java b/framework/src/main/java/org/tron/core/services/http/PostParams.java index 7dcb0be6ae3..78be0c2bd34 100644 --- a/framework/src/main/java/org/tron/core/services/http/PostParams.java +++ b/framework/src/main/java/org/tron/core/services/http/PostParams.java @@ -15,10 +15,22 @@ public class PostParams { private String params; @Getter private boolean visible; + @Getter + private boolean int64AsString; - public PostParams(String params, boolean visible) { + public PostParams(String params, boolean visible, boolean int64AsString) { this.params = params; this.visible = visible; + this.int64AsString = int64AsString; + } + + /** + * Legacy 2-arg constructor retained for backward compatibility with callers that + * manually construct PostParams without specifying int64_as_string. Defaults + * int64AsString to false, which preserves the pre-existing behavior. + */ + public PostParams(String params, boolean visible) { + this(params, visible, false); } public static PostParams getPostParams(HttpServletRequest request) throws Exception { @@ -28,6 +40,7 @@ public static PostParams getPostParams(HttpServletRequest request) throws Except input = getJsonString(input); } boolean visible = Util.getVisiblePost(input); - return new PostParams(input, visible); + boolean int64AsString = Util.getInt64AsStringPost(input); + return new PostParams(input, visible, int64AsString); } } diff --git a/framework/src/main/java/org/tron/core/services/http/Util.java b/framework/src/main/java/org/tron/core/services/http/Util.java index 2b6b929d8a0..2c0474b704c 100644 --- a/framework/src/main/java/org/tron/core/services/http/Util.java +++ b/framework/src/main/java/org/tron/core/services/http/Util.java @@ -66,6 +66,7 @@ public class Util { public static final String PERMISSION_ID = "Permission_id"; public static final String VISIBLE = "visible"; + public static final String INT64_AS_STRING = "int64_as_string"; public static final String TRANSACTION = "transaction"; public static final String TRANSACTION_EXTENSION = "transactionExtension"; public static final String VALUE = "value"; @@ -346,6 +347,18 @@ public static boolean existVisible(final HttpServletRequest request) { return Objects.nonNull(request.getParameter(VISIBLE)); } + /** + * Read int64_as_string from URL query parameter. Mirrors + * {@link #getVisible(HttpServletRequest)}. Intended for doGet on whitelisted query + * servlets and for any doPost that reads visible from the URL query. + */ + public static boolean getInt64AsString(final HttpServletRequest request) { + if (StringUtil.isNotBlank(request.getParameter(INT64_AS_STRING))) { + return Boolean.parseBoolean(request.getParameter(INT64_AS_STRING)); + } + return false; + } + public static boolean getVisiblePost(final String input) { boolean visible = false; if (StringUtil.isNotBlank(input)) { @@ -358,6 +371,21 @@ public static boolean getVisiblePost(final String input) { return visible; } + /** + * Read int64_as_string from the POST body JSON string. Mirrors + * {@link #getVisiblePost(String)}. Used by {@link PostParams#getPostParams} and by + * servlets that manually read the request body (e.g. GetPaginatedProposalListServlet). + */ + public static boolean getInt64AsStringPost(final String input) { + if (StringUtil.isNotBlank(input)) { + JSONObject jsonObject = JSON.parseObject(input); + if (jsonObject.containsKey(INT64_AS_STRING)) { + return Boolean.parseBoolean(jsonObject.getString(INT64_AS_STRING)); + } + } + return false; + } + public static String getContractType(final String input) { String contractType = null; JSONObject jsonObject = JSON.parseObject(input); diff --git a/framework/src/test/java/org/tron/core/services/http/JsonFormatInt64AsStringTest.java b/framework/src/test/java/org/tron/core/services/http/JsonFormatInt64AsStringTest.java new file mode 100644 index 00000000000..4b1817342bc --- /dev/null +++ b/framework/src/test/java/org/tron/core/services/http/JsonFormatInt64AsStringTest.java @@ -0,0 +1,320 @@ +package org.tron.core.services.http; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import com.google.protobuf.ByteString; +import com.google.protobuf.UInt64Value; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import org.junit.After; +import org.junit.Test; +import org.tron.protos.Protocol; + +/** + * Tests for {@link JsonFormat#pushInt64AsString(boolean)}. + * + *

Tron protos do not define uint64/fixed64 fields directly — all 64-bit values use int64. + * The uint64 branch is exercised using {@link com.google.protobuf.UInt64Value}, a protobuf + * well-known wrapper with a single {@code uint64 value} field. + */ +public class JsonFormatInt64AsStringTest { + + /** Defensive cleanup in case a test leaves the ThreadLocal dirty. */ + @After + public void clearState() throws Exception { + try (AutoCloseable ignored = JsonFormat.pushInt64AsString(false)) { + // no-op — just ensures ThreadLocal.remove() runs on this thread + } + } + + // ---------- default behavior ---------- + + @Test + public void defaultBehaviorUnchangedWithoutPush() { + Protocol.Account account = Protocol.Account.newBuilder() + .setBalance(123456789012345L) + .build(); + String out = JsonFormat.printToString(account, true); + assertTrue("expected unquoted balance, got: " + out, + out.contains("\"balance\":123456789012345") + || out.contains("\"balance\": 123456789012345")); + assertFalse("balance should not be quoted by default, got: " + out, + out.contains("\"balance\":\"123456789012345\"") + || out.contains("\"balance\": \"123456789012345\"")); + } + + // ---------- int64 / uint64 field quoting ---------- + + @Test + public void int64FieldQuotedInScope() throws Exception { + Protocol.Account account = Protocol.Account.newBuilder() + .setBalance(123456789012345L) + .build(); + try (AutoCloseable ignored = JsonFormat.pushInt64AsString(true)) { + String out = JsonFormat.printToString(account, true); + assertTrue("expected quoted balance, got: " + out, + out.contains("\"123456789012345\"")); + } + } + + @Test + public void uint64FieldQuotedInScope() throws Exception { + UInt64Value v = UInt64Value.of(9007199254740993L); // 2^53 + 1 + try (AutoCloseable ignored = JsonFormat.pushInt64AsString(true)) { + String out = JsonFormat.printToString(v, true); + assertTrue("expected quoted uint64 value, got: " + out, + out.contains("\"9007199254740993\"")); + } + } + + @Test + public void uint64DefaultUnquoted() { + UInt64Value v = UInt64Value.of(9007199254740993L); + String out = JsonFormat.printToString(v, true); + assertTrue("expected unquoted uint64 value, got: " + out, + out.contains("9007199254740993")); + assertFalse("uint64 should not be quoted by default, got: " + out, + out.contains("\"9007199254740993\"")); + } + + // ---------- non-int64 fields should not be affected ---------- + + @Test + public void stringBytesEnumNotAffected() throws Exception { + // Note: proto3 does not serialize default-valued fields, so enum/bytes fields are + // set to non-default values to verify they appear in the output. + Protocol.Account account = Protocol.Account.newBuilder() + .setAccountName(ByteString.copyFromUtf8("alice")) + .setType(Protocol.AccountType.AssetIssue) // non-default enum value + .setBalance(1L) + .build(); + try (AutoCloseable ignored = JsonFormat.pushInt64AsString(true)) { + String out = JsonFormat.printToString(account, true); + // balance int64 should be quoted + assertTrue("balance should be quoted, got: " + out, out.contains("\"1\"")); + // enum type serialized by name (not a number), not affected by int64_as_string + assertTrue("enum type should appear as name, got: " + out, + out.contains("AssetIssue")); + // bytes account_name should still serialize normally + assertTrue("account_name should appear, got: " + out, out.contains("account_name")); + } + } + + // ---------- nested / repeated / map ---------- + + @Test + public void nestedInt64FieldsQuoted() throws Exception { + Protocol.Block block = Protocol.Block.newBuilder() + .setBlockHeader(Protocol.BlockHeader.newBuilder() + .setRawData(Protocol.BlockHeader.raw.newBuilder() + .setNumber(9007199254740993L) // 2^53 + 1 + .setTimestamp(1700000000000L) + .build()) + .build()) + .build(); + try (AutoCloseable ignored = JsonFormat.pushInt64AsString(true)) { + String out = JsonFormat.printToString(block, true); + assertTrue("nested number should be quoted, got: " + out, + out.contains("\"9007199254740993\"")); + assertTrue("nested timestamp should be quoted, got: " + out, + out.contains("\"1700000000000\"")); + } + } + + @Test + public void mapStringInt64ValuesQuoted() throws Exception { + Protocol.Account account = Protocol.Account.newBuilder() + .putAsset("USDT", 123456789012345L) + .build(); + try (AutoCloseable ignored = JsonFormat.pushInt64AsString(true)) { + String out = JsonFormat.printToString(account, true); + assertTrue("map value should be quoted, got: " + out, + out.contains("\"123456789012345\"")); + } + } + + // ---------- boundary values ---------- + + @Test + public void boundaryValuesAllQuoted() throws Exception { + // Note: proto3 does not serialize a field whose value equals its type default (0 for int64), + // so 0L is covered separately via defaultBehaviorUnchangedWithoutPush / uint64DefaultUnquoted + // (both use non-default values) and does not need an explicit quoted-output test. + long[] values = { + (1L << 53) - 1, // max safe JS integer + 1L << 53, // boundary + (1L << 53) + 1, // first unsafe + Long.MAX_VALUE, + Long.MIN_VALUE, + -1L + }; + for (long v : values) { + Protocol.Account account = Protocol.Account.newBuilder().setBalance(v).build(); + try (AutoCloseable ignored = JsonFormat.pushInt64AsString(true)) { + String out = JsonFormat.printToString(account, true); + assertTrue("value=" + v + " expected quoted, got: " + out, + out.contains("\"" + v + "\"")); + } + } + } + + // ---------- lifecycle ---------- + + @Test + public void stateClearedAfterScope() throws Exception { + Protocol.Account account = Protocol.Account.newBuilder().setBalance(1L).build(); + try (AutoCloseable ignored = JsonFormat.pushInt64AsString(true)) { + // inside scope + } + String out = JsonFormat.printToString(account, true); + assertFalse("state leaked after scope, got: " + out, out.contains("\"1\"")); + } + + @Test + public void stateClearedAfterException() { + Protocol.Account account = Protocol.Account.newBuilder().setBalance(1L).build(); + try { + try (AutoCloseable ignored = JsonFormat.pushInt64AsString(true)) { + throw new RuntimeException("boom"); + } + } catch (Exception e) { + // expected + } + String out = JsonFormat.printToString(account, true); + assertFalse("state leaked after exception, got: " + out, out.contains("\"1\"")); + } + + @Test + public void nestedScopesRestorePreviousValue() throws Exception { + Protocol.Account account = Protocol.Account.newBuilder().setBalance(1L).build(); + try (AutoCloseable outer = JsonFormat.pushInt64AsString(true)) { + try (AutoCloseable inner = JsonFormat.pushInt64AsString(false)) { + String innerOut = JsonFormat.printToString(account, true); + assertFalse("inner should be unquoted, got: " + innerOut, + innerOut.contains("\"1\"")); + } + String outerOut = JsonFormat.printToString(account, true); + assertTrue("outer should be quoted again, got: " + outerOut, + outerOut.contains("\"1\"")); + } + String out = JsonFormat.printToString(account, true); + assertFalse("state leaked after outer scope, got: " + out, + out.contains("\"1\"")); + } + + // ---------- concurrency ---------- + + @Test + public void threadIsolation() throws Exception { + final Protocol.Account account = Protocol.Account.newBuilder().setBalance(1L).build(); + final CountDownLatch barrier = new CountDownLatch(2); + ExecutorService ex = Executors.newFixedThreadPool(2); + try { + Future trueThread = ex.submit(() -> { + try (AutoCloseable ignored = JsonFormat.pushInt64AsString(true)) { + barrier.countDown(); + barrier.await(); + return JsonFormat.printToString(account, true); + } + }); + Future falseThread = ex.submit(() -> { + barrier.countDown(); + barrier.await(); + return JsonFormat.printToString(account, true); + }); + String withScope = trueThread.get(5, TimeUnit.SECONDS); + String noScope = falseThread.get(5, TimeUnit.SECONDS); + assertTrue("trueThread should see quoted: " + withScope, + withScope.contains("\"1\"")); + assertFalse("falseThread should see unquoted: " + noScope, + noScope.contains("\"1\"")); + } finally { + ex.shutdownNow(); + } + } + + @Test + public void noPollutionOnThreadReuse() throws Exception { + final Protocol.Account account = Protocol.Account.newBuilder().setBalance(1L).build(); + ExecutorService single = Executors.newSingleThreadExecutor(); + try { + Future firstRun = single.submit(() -> { + try (AutoCloseable ignored = JsonFormat.pushInt64AsString(true)) { + return JsonFormat.printToString(account, true); + } + }); + assertTrue(firstRun.get(5, TimeUnit.SECONDS).contains("\"1\"")); + + // Reuse the same thread; without a new scope, state must be cleared. + Future secondRun = single.submit(() -> JsonFormat.printToString(account, true)); + String second = secondRun.get(5, TimeUnit.SECONDS); + assertFalse("thread reuse leaked quoted state: " + second, + second.contains("\"1\"")); + } finally { + single.shutdownNow(); + } + } + + // ---------- helper close() idempotency (defensive) ---------- + + @Test + public void helperCanBeClosedExplicitly() throws Exception { + Protocol.Account account = Protocol.Account.newBuilder().setBalance(1L).build(); + AutoCloseable scope = JsonFormat.pushInt64AsString(true); + try { + String inside = JsonFormat.printToString(account, true); + assertTrue(inside, inside.contains("\"1\"")); + } finally { + scope.close(); + } + String outside = JsonFormat.printToString(account, true); + assertFalse("explicit close should clear state: " + outside, + outside.contains("\"1\"")); + } + + // ---------- adapter: 2-arg PostParams constructor ---------- + + @Test + public void legacyPostParamsConstructorDefaultsFalse() { + PostParams params = new PostParams("{}", true); + assertEquals(true, params.isVisible()); + assertEquals(false, params.isInt64AsString()); + } + + @Test + public void threeArgPostParamsConstructorHonorsFlag() { + PostParams params = new PostParams("{}", false, true); + assertEquals(false, params.isVisible()); + assertEquals(true, params.isInt64AsString()); + } + + // ---------- Util.getInt64AsStringPost (body parse, mirrors getVisiblePost) ---------- + + @Test + public void getInt64AsStringPostDefaultsFalseWhenAbsent() { + assertEquals(false, Util.getInt64AsStringPost("{\"address\":\"T123\"}")); + } + + @Test + public void getInt64AsStringPostTrueParsedFromBody() { + assertEquals(true, + Util.getInt64AsStringPost("{\"address\":\"T123\",\"int64_as_string\":true}")); + } + + @Test + public void getInt64AsStringPostFalseParsedFromBody() { + assertEquals(false, + Util.getInt64AsStringPost("{\"address\":\"T123\",\"int64_as_string\":false}")); + } + + @Test + public void getInt64AsStringPostHandlesBlankInput() { + assertEquals(false, Util.getInt64AsStringPost("")); + assertEquals(false, Util.getInt64AsStringPost(null)); + } +} From 3f4ec0694a17c4d0fc06536b90c041a1103afc84 Mon Sep 17 00:00:00 2001 From: waynercheung Date: Tue, 21 Apr 2026 19:52:44 +0800 Subject: [PATCH 2/9] feat(http): wire int64_as_string into 43 whitelisted query servlets for #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. --- .../http/GetAccountBalanceServlet.java | 4 +++- .../services/http/GetAccountByIdServlet.java | 9 ++++++-- .../services/http/GetAccountNetServlet.java | 9 ++++++-- .../http/GetAccountResourceServlet.java | 11 +++++++--- .../core/services/http/GetAccountServlet.java | 9 ++++++-- .../http/GetAssetIssueByAccountServlet.java | 9 ++++++-- .../http/GetAssetIssueByIdServlet.java | 9 ++++++-- .../http/GetAssetIssueByNameServlet.java | 11 +++++++--- .../http/GetAssetIssueListByNameServlet.java | 9 ++++++-- .../http/GetAssetIssueListServlet.java | 5 ++++- .../GetAvailableUnfreezeCountServlet.java | 17 +++++++++------ .../services/http/GetBlockByIdServlet.java | 9 ++++++-- .../http/GetBlockByLatestNumServlet.java | 9 ++++++-- .../http/GetBlockByLimitNextServlet.java | 10 ++++++--- .../services/http/GetBlockByNumServlet.java | 8 +++++-- .../core/services/http/GetBlockServlet.java | 7 +++++-- .../core/services/http/GetBurnTrxServlet.java | 5 ++++- .../http/GetCanDelegatedMaxSizeServlet.java | 17 +++++++++------ .../GetCanWithdrawUnfreezeAmountServlet.java | 21 ++++++++++++------- .../http/GetChainParametersServlet.java | 6 +++++- ...tDelegatedResourceAccountIndexServlet.java | 9 ++++++-- ...elegatedResourceAccountIndexV2Servlet.java | 9 ++++++-- .../http/GetDelegatedResourceServlet.java | 11 +++++++--- .../http/GetDelegatedResourceV2Servlet.java | 11 +++++++--- .../services/http/GetNowBlockServlet.java | 5 ++++- .../GetPaginatedAssetIssueListServlet.java | 9 ++++++-- .../http/GetPaginatedExchangeListServlet.java | 9 ++++++-- .../GetPaginatedNowWitnessListServlet.java | 9 ++++++-- .../http/GetPaginatedProposalListServlet.java | 10 +++++++-- .../services/http/GetPendingSizeServlet.java | 5 ++++- .../core/services/http/GetRewardServlet.java | 5 ++++- .../http/GetTransactionByIdServlet.java | 9 ++++++-- .../GetTransactionCountByBlockNumServlet.java | 12 +++++++---- .../GetTransactionFromPendingServlet.java | 11 +++++++--- .../GetTransactionInfoByBlockNumServlet.java | 9 ++++++-- .../http/GetTransactionInfoByIdServlet.java | 9 ++++++-- .../GetTransactionReceiptByIdServlet.java | 15 ++++++++----- .../services/http/ListExchangesServlet.java | 5 ++++- .../services/http/ListProposalsServlet.java | 5 ++++- .../services/http/ListWitnessesServlet.java | 5 ++++- .../http/TotalTransactionServlet.java | 5 ++++- .../GetTransactionByIdSolidityServlet.java | 9 ++++++-- ...GetTransactionInfoByIdSolidityServlet.java | 9 ++++++-- 43 files changed, 289 insertions(+), 100 deletions(-) diff --git a/framework/src/main/java/org/tron/core/services/http/GetAccountBalanceServlet.java b/framework/src/main/java/org/tron/core/services/http/GetAccountBalanceServlet.java index 159c3899666..e09f9db36d4 100644 --- a/framework/src/main/java/org/tron/core/services/http/GetAccountBalanceServlet.java +++ b/framework/src/main/java/org/tron/core/services/http/GetAccountBalanceServlet.java @@ -24,7 +24,9 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response) BalanceContract.AccountBalanceRequest.Builder builder = BalanceContract.AccountBalanceRequest.newBuilder(); JsonFormat.merge(params.getParams(), builder, params.isVisible()); - fillResponse(params.isVisible(), builder.build(), response); + try (AutoCloseable ignored = JsonFormat.pushInt64AsString(params.isInt64AsString())) { + fillResponse(params.isVisible(), builder.build(), response); + } } catch (Exception e) { Util.processError(e, response); } diff --git a/framework/src/main/java/org/tron/core/services/http/GetAccountByIdServlet.java b/framework/src/main/java/org/tron/core/services/http/GetAccountByIdServlet.java index 7387b801168..cdd0266827b 100644 --- a/framework/src/main/java/org/tron/core/services/http/GetAccountByIdServlet.java +++ b/framework/src/main/java/org/tron/core/services/http/GetAccountByIdServlet.java @@ -21,12 +21,15 @@ public class GetAccountByIdServlet extends RateLimiterServlet { protected void doGet(HttpServletRequest request, HttpServletResponse response) { try { boolean visible = Util.getVisible(request); + boolean int64AsString = Util.getInt64AsString(request); String accountId = request.getParameter("account_id"); Account.Builder build = Account.newBuilder(); JSONObject jsonObject = new JSONObject(); jsonObject.put("account_id", accountId); JsonFormat.merge(jsonObject.toJSONString(), build, visible); - fillResponse(build.build(), visible, response); + try (AutoCloseable ignored = JsonFormat.pushInt64AsString(int64AsString)) { + fillResponse(build.build(), visible, response); + } } catch (Exception e) { Util.processError(e, response); } @@ -37,7 +40,9 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response) PostParams params = PostParams.getPostParams(request); Account.Builder build = Account.newBuilder(); JsonFormat.merge(params.getParams(), build, params.isVisible()); - fillResponse(build.build(), params.isVisible(), response); + try (AutoCloseable ignored = JsonFormat.pushInt64AsString(params.isInt64AsString())) { + fillResponse(build.build(), params.isVisible(), response); + } } catch (Exception e) { Util.processError(e, response); } diff --git a/framework/src/main/java/org/tron/core/services/http/GetAccountNetServlet.java b/framework/src/main/java/org/tron/core/services/http/GetAccountNetServlet.java index 121a23c61ab..0d4b42fa012 100644 --- a/framework/src/main/java/org/tron/core/services/http/GetAccountNetServlet.java +++ b/framework/src/main/java/org/tron/core/services/http/GetAccountNetServlet.java @@ -22,11 +22,14 @@ public class GetAccountNetServlet extends RateLimiterServlet { protected void doGet(HttpServletRequest request, HttpServletResponse response) { try { boolean visible = Util.getVisible(request); + boolean int64AsString = Util.getInt64AsString(request); String address = request.getParameter("address"); if (visible) { address = Util.getHexAddress(address); } - fillResponse(visible, ByteString.copyFrom(ByteArray.fromHexString(address)), response); + try (AutoCloseable ignored = JsonFormat.pushInt64AsString(int64AsString)) { + fillResponse(visible, ByteString.copyFrom(ByteArray.fromHexString(address)), response); + } } catch (Exception e) { Util.processError(e, response); } @@ -37,7 +40,9 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response) PostParams params = PostParams.getPostParams(request); Account.Builder build = Account.newBuilder(); JsonFormat.merge(params.getParams(), build, params.isVisible()); - fillResponse(params.isVisible(), build.getAddress(), response); + try (AutoCloseable ignored = JsonFormat.pushInt64AsString(params.isInt64AsString())) { + fillResponse(params.isVisible(), build.getAddress(), response); + } } catch (Exception e) { Util.processError(e, response); } diff --git a/framework/src/main/java/org/tron/core/services/http/GetAccountResourceServlet.java b/framework/src/main/java/org/tron/core/services/http/GetAccountResourceServlet.java index 98224334e1a..36f0e465a25 100644 --- a/framework/src/main/java/org/tron/core/services/http/GetAccountResourceServlet.java +++ b/framework/src/main/java/org/tron/core/services/http/GetAccountResourceServlet.java @@ -21,11 +21,14 @@ public class GetAccountResourceServlet extends RateLimiterServlet { protected void doGet(HttpServletRequest request, HttpServletResponse response) { try { boolean visible = Util.getVisible(request); + boolean int64AsString = Util.getInt64AsString(request); String address = request.getParameter("address"); if (visible) { address = Util.getHexAddress(address); } - fillResponse(visible, ByteString.copyFrom(ByteArray.fromHexString(address)), response); + try (AutoCloseable ignored = JsonFormat.pushInt64AsString(int64AsString)) { + fillResponse(visible, ByteString.copyFrom(ByteArray.fromHexString(address)), response); + } } catch (Exception e) { Util.processError(e, response); } @@ -39,8 +42,10 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response) if (params.isVisible()) { address = Util.getHexAddress(address); } - fillResponse(params.isVisible(), ByteString.copyFrom(ByteArray.fromHexString(address)), - response); + try (AutoCloseable ignored = JsonFormat.pushInt64AsString(params.isInt64AsString())) { + fillResponse(params.isVisible(), ByteString.copyFrom(ByteArray.fromHexString(address)), + response); + } } catch (Exception e) { Util.processError(e, response); } diff --git a/framework/src/main/java/org/tron/core/services/http/GetAccountServlet.java b/framework/src/main/java/org/tron/core/services/http/GetAccountServlet.java index 7de95dab541..e6fcf52e7d5 100644 --- a/framework/src/main/java/org/tron/core/services/http/GetAccountServlet.java +++ b/framework/src/main/java/org/tron/core/services/http/GetAccountServlet.java @@ -20,12 +20,15 @@ public class GetAccountServlet extends RateLimiterServlet { protected void doGet(HttpServletRequest request, HttpServletResponse response) { try { boolean visible = Util.getVisible(request); + boolean int64AsString = Util.getInt64AsString(request); String address = request.getParameter("address"); Account.Builder build = Account.newBuilder(); JSONObject jsonObject = new JSONObject(); jsonObject.put("address", address); JsonFormat.merge(jsonObject.toJSONString(), build, visible); - fillResponse(visible, build.build(), response); + try (AutoCloseable ignored = JsonFormat.pushInt64AsString(int64AsString)) { + fillResponse(visible, build.build(), response); + } } catch (Exception e) { Util.processError(e, response); } @@ -36,7 +39,9 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response) PostParams params = PostParams.getPostParams(request); Account.Builder build = Account.newBuilder(); JsonFormat.merge(params.getParams(), build, params.isVisible()); - fillResponse(params.isVisible(), build.build(), response); + try (AutoCloseable ignored = JsonFormat.pushInt64AsString(params.isInt64AsString())) { + fillResponse(params.isVisible(), build.build(), response); + } } catch (Exception e) { Util.processError(e, response); } diff --git a/framework/src/main/java/org/tron/core/services/http/GetAssetIssueByAccountServlet.java b/framework/src/main/java/org/tron/core/services/http/GetAssetIssueByAccountServlet.java index e9e85af2e81..d6e252a4a82 100644 --- a/framework/src/main/java/org/tron/core/services/http/GetAssetIssueByAccountServlet.java +++ b/framework/src/main/java/org/tron/core/services/http/GetAssetIssueByAccountServlet.java @@ -22,11 +22,14 @@ public class GetAssetIssueByAccountServlet extends RateLimiterServlet { protected void doGet(HttpServletRequest request, HttpServletResponse response) { try { boolean visible = Util.getVisible(request); + boolean int64AsString = Util.getInt64AsString(request); String address = request.getParameter("address"); if (visible) { address = Util.getHexAddress(address); } - fillResponse(visible, ByteString.copyFrom(ByteArray.fromHexString(address)), response); + try (AutoCloseable ignored = JsonFormat.pushInt64AsString(int64AsString)) { + fillResponse(visible, ByteString.copyFrom(ByteArray.fromHexString(address)), response); + } } catch (Exception e) { Util.processError(e, response); } @@ -37,7 +40,9 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response) PostParams params = PostParams.getPostParams(request); Account.Builder build = Account.newBuilder(); JsonFormat.merge(params.getParams(), build, params.isVisible()); - fillResponse(params.isVisible(), build.getAddress(), response); + try (AutoCloseable ignored = JsonFormat.pushInt64AsString(params.isInt64AsString())) { + fillResponse(params.isVisible(), build.getAddress(), response); + } } catch (Exception e) { Util.processError(e, response); } diff --git a/framework/src/main/java/org/tron/core/services/http/GetAssetIssueByIdServlet.java b/framework/src/main/java/org/tron/core/services/http/GetAssetIssueByIdServlet.java index 08f8227deee..1cc929d476d 100644 --- a/framework/src/main/java/org/tron/core/services/http/GetAssetIssueByIdServlet.java +++ b/framework/src/main/java/org/tron/core/services/http/GetAssetIssueByIdServlet.java @@ -20,10 +20,13 @@ public class GetAssetIssueByIdServlet extends RateLimiterServlet { protected void doGet(HttpServletRequest request, HttpServletResponse response) { try { boolean visible = Util.getVisible(request); + boolean int64AsString = Util.getInt64AsString(request); String input = request.getParameter("value"); AssetIssueContract reply = wallet.getAssetIssueById(input); if (reply != null) { - response.getWriter().println(JsonFormat.printToString(reply, visible)); + try (AutoCloseable ignored = JsonFormat.pushInt64AsString(int64AsString)) { + response.getWriter().println(JsonFormat.printToString(reply, visible)); + } } else { response.getWriter().println("{}"); } @@ -39,7 +42,9 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response) String id = jsonObject.getString("value"); AssetIssueContract reply = wallet.getAssetIssueById(id); if (reply != null) { - response.getWriter().println(JsonFormat.printToString(reply, params.isVisible())); + try (AutoCloseable ignored = JsonFormat.pushInt64AsString(params.isInt64AsString())) { + response.getWriter().println(JsonFormat.printToString(reply, params.isVisible())); + } } else { response.getWriter().println("{}"); } diff --git a/framework/src/main/java/org/tron/core/services/http/GetAssetIssueByNameServlet.java b/framework/src/main/java/org/tron/core/services/http/GetAssetIssueByNameServlet.java index a1cc0525514..f0c2d243623 100644 --- a/framework/src/main/java/org/tron/core/services/http/GetAssetIssueByNameServlet.java +++ b/framework/src/main/java/org/tron/core/services/http/GetAssetIssueByNameServlet.java @@ -23,11 +23,14 @@ public class GetAssetIssueByNameServlet extends RateLimiterServlet { protected void doGet(HttpServletRequest request, HttpServletResponse response) { try { boolean visible = Util.getVisible(request); + boolean int64AsString = Util.getInt64AsString(request); String input = request.getParameter("value"); if (visible) { input = Util.getHexString(input); } - fillResponse(visible, ByteString.copyFrom(ByteArray.fromHexString(input)), response); + try (AutoCloseable ignored = JsonFormat.pushInt64AsString(int64AsString)) { + fillResponse(visible, ByteString.copyFrom(ByteArray.fromHexString(input)), response); + } } catch (Exception e) { Util.processError(e, response); } @@ -41,8 +44,10 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response) if (params.isVisible()) { value = Util.getHexString(value); } - fillResponse(params.isVisible(), ByteString.copyFrom( - ByteArray.fromHexString(value)), response); + try (AutoCloseable ignored = JsonFormat.pushInt64AsString(params.isInt64AsString())) { + fillResponse(params.isVisible(), ByteString.copyFrom( + ByteArray.fromHexString(value)), response); + } } catch (Exception e) { Util.processError(e, response); } diff --git a/framework/src/main/java/org/tron/core/services/http/GetAssetIssueListByNameServlet.java b/framework/src/main/java/org/tron/core/services/http/GetAssetIssueListByNameServlet.java index d9b7426011d..927cd00af4a 100644 --- a/framework/src/main/java/org/tron/core/services/http/GetAssetIssueListByNameServlet.java +++ b/framework/src/main/java/org/tron/core/services/http/GetAssetIssueListByNameServlet.java @@ -24,8 +24,11 @@ public class GetAssetIssueListByNameServlet extends RateLimiterServlet { protected void doGet(HttpServletRequest request, HttpServletResponse response) { try { boolean visible = Util.getVisible(request); + boolean int64AsString = Util.getInt64AsString(request); String value = request.getParameter("value"); - fillResponse(visible, value, response); + try (AutoCloseable ignored = JsonFormat.pushInt64AsString(int64AsString)) { + fillResponse(visible, value, response); + } } catch (Exception e) { Util.processError(e, response); } @@ -36,7 +39,9 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response) PostParams params = PostParams.getPostParams(request); JSONObject jsonObject = JSON.parseObject(params.getParams()); String value = jsonObject.getString("value"); - fillResponse(params.isVisible(), value, response); + try (AutoCloseable ignored = JsonFormat.pushInt64AsString(params.isInt64AsString())) { + fillResponse(params.isVisible(), value, response); + } } catch (Exception e) { Util.processError(e, response); } diff --git a/framework/src/main/java/org/tron/core/services/http/GetAssetIssueListServlet.java b/framework/src/main/java/org/tron/core/services/http/GetAssetIssueListServlet.java index 3968554a671..458144b9ef3 100644 --- a/framework/src/main/java/org/tron/core/services/http/GetAssetIssueListServlet.java +++ b/framework/src/main/java/org/tron/core/services/http/GetAssetIssueListServlet.java @@ -19,9 +19,12 @@ public class GetAssetIssueListServlet extends RateLimiterServlet { protected void doGet(HttpServletRequest request, HttpServletResponse response) { try { boolean visible = Util.getVisible(request); + boolean int64AsString = Util.getInt64AsString(request); AssetIssueList reply = wallet.getAssetIssueList(); if (reply != null) { - response.getWriter().println(JsonFormat.printToString(reply, visible)); + try (AutoCloseable ignored = JsonFormat.pushInt64AsString(int64AsString)) { + response.getWriter().println(JsonFormat.printToString(reply, visible)); + } } else { response.getWriter().println("{}"); } diff --git a/framework/src/main/java/org/tron/core/services/http/GetAvailableUnfreezeCountServlet.java b/framework/src/main/java/org/tron/core/services/http/GetAvailableUnfreezeCountServlet.java index 51f78fc4390..a9c62829858 100644 --- a/framework/src/main/java/org/tron/core/services/http/GetAvailableUnfreezeCountServlet.java +++ b/framework/src/main/java/org/tron/core/services/http/GetAvailableUnfreezeCountServlet.java @@ -22,6 +22,7 @@ public class GetAvailableUnfreezeCountServlet extends RateLimiterServlet { protected void doGet(HttpServletRequest request, HttpServletResponse response) { try { boolean visible = Util.getVisible(request); + boolean int64AsString = Util.getInt64AsString(request); String ownerAddress = request.getParameter("ownerAddress"); if (ownerAddress == null) { ownerAddress = request.getParameter("owner_address"); @@ -29,9 +30,11 @@ protected void doGet(HttpServletRequest request, HttpServletResponse response) { if (visible) { ownerAddress = Util.getHexAddress(ownerAddress); } - fillResponse(visible, - ByteString.copyFrom(ByteArray.fromHexString(ownerAddress)), - response); + try (AutoCloseable ignored = JsonFormat.pushInt64AsString(int64AsString)) { + fillResponse(visible, + ByteString.copyFrom(ByteArray.fromHexString(ownerAddress)), + response); + } } catch (Exception e) { Util.processError(e, response); } @@ -44,9 +47,11 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response) GrpcAPI.GetAvailableUnfreezeCountRequestMessage.Builder build = GrpcAPI.GetAvailableUnfreezeCountRequestMessage.newBuilder(); JsonFormat.merge(params.getParams(), build, params.isVisible()); - fillResponse(params.isVisible(), - build.getOwnerAddress(), - response); + try (AutoCloseable ignored = JsonFormat.pushInt64AsString(params.isInt64AsString())) { + fillResponse(params.isVisible(), + build.getOwnerAddress(), + response); + } } catch (Exception e) { Util.processError(e, response); } diff --git a/framework/src/main/java/org/tron/core/services/http/GetBlockByIdServlet.java b/framework/src/main/java/org/tron/core/services/http/GetBlockByIdServlet.java index f7e6ac0ff7d..77b7c789a2b 100644 --- a/framework/src/main/java/org/tron/core/services/http/GetBlockByIdServlet.java +++ b/framework/src/main/java/org/tron/core/services/http/GetBlockByIdServlet.java @@ -23,8 +23,11 @@ public class GetBlockByIdServlet extends RateLimiterServlet { protected void doGet(HttpServletRequest request, HttpServletResponse response) { try { boolean visible = Util.getVisible(request); + boolean int64AsString = Util.getInt64AsString(request); String input = request.getParameter("value"); - fillResponse(visible, ByteString.copyFrom(ByteArray.fromHexString(input)), response); + try (AutoCloseable ignored = JsonFormat.pushInt64AsString(int64AsString)) { + fillResponse(visible, ByteString.copyFrom(ByteArray.fromHexString(input)), response); + } } catch (Exception e) { Util.processError(e, response); } @@ -35,7 +38,9 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response) PostParams params = PostParams.getPostParams(request); BytesMessage.Builder build = BytesMessage.newBuilder(); JsonFormat.merge(params.getParams(), build, params.isVisible()); - fillResponse(params.isVisible(), build.getValue(), response); + try (AutoCloseable ignored = JsonFormat.pushInt64AsString(params.isInt64AsString())) { + fillResponse(params.isVisible(), build.getValue(), response); + } } catch (Exception e) { Util.processError(e, response); } diff --git a/framework/src/main/java/org/tron/core/services/http/GetBlockByLatestNumServlet.java b/framework/src/main/java/org/tron/core/services/http/GetBlockByLatestNumServlet.java index 1f2eabdd8dc..bd215ca4be2 100644 --- a/framework/src/main/java/org/tron/core/services/http/GetBlockByLatestNumServlet.java +++ b/framework/src/main/java/org/tron/core/services/http/GetBlockByLatestNumServlet.java @@ -21,7 +21,10 @@ public class GetBlockByLatestNumServlet extends RateLimiterServlet { protected void doGet(HttpServletRequest request, HttpServletResponse response) { try { - fillResponse(Util.getVisible(request), Long.parseLong(request.getParameter("num")), response); + try (AutoCloseable ignored = JsonFormat.pushInt64AsString(Util.getInt64AsString(request))) { + fillResponse(Util.getVisible(request), Long.parseLong(request.getParameter("num")), + response); + } } catch (Exception e) { Util.processError(e, response); } @@ -32,7 +35,9 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response) PostParams params = PostParams.getPostParams(request); NumberMessage.Builder build = NumberMessage.newBuilder(); JsonFormat.merge(params.getParams(), build, params.isVisible()); - fillResponse(params.isVisible(), build.getNum(), response); + try (AutoCloseable ignored = JsonFormat.pushInt64AsString(params.isInt64AsString())) { + fillResponse(params.isVisible(), build.getNum(), response); + } } catch (Exception e) { Util.processError(e, response); } diff --git a/framework/src/main/java/org/tron/core/services/http/GetBlockByLimitNextServlet.java b/framework/src/main/java/org/tron/core/services/http/GetBlockByLimitNextServlet.java index 3e6700a1fae..d76515a96a6 100644 --- a/framework/src/main/java/org/tron/core/services/http/GetBlockByLimitNextServlet.java +++ b/framework/src/main/java/org/tron/core/services/http/GetBlockByLimitNextServlet.java @@ -21,8 +21,10 @@ public class GetBlockByLimitNextServlet extends RateLimiterServlet { protected void doGet(HttpServletRequest request, HttpServletResponse response) { try { - fillResponse(Util.getVisible(request), Long.parseLong(request.getParameter("startNum")), - Long.parseLong(request.getParameter("endNum")), response); + try (AutoCloseable ignored = JsonFormat.pushInt64AsString(Util.getInt64AsString(request))) { + fillResponse(Util.getVisible(request), Long.parseLong(request.getParameter("startNum")), + Long.parseLong(request.getParameter("endNum")), response); + } } catch (Exception e) { Util.processError(e, response); } @@ -33,7 +35,9 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response) PostParams params = PostParams.getPostParams(request); BlockLimit.Builder build = BlockLimit.newBuilder(); JsonFormat.merge(params.getParams(), build, params.isVisible()); - fillResponse(params.isVisible(), build.getStartNum(), build.getEndNum(), response); + try (AutoCloseable ignored = JsonFormat.pushInt64AsString(params.isInt64AsString())) { + fillResponse(params.isVisible(), build.getStartNum(), build.getEndNum(), response); + } } catch (Exception e) { Util.processError(e, response); } diff --git a/framework/src/main/java/org/tron/core/services/http/GetBlockByNumServlet.java b/framework/src/main/java/org/tron/core/services/http/GetBlockByNumServlet.java index 800b421ace0..8f986d0a61c 100644 --- a/framework/src/main/java/org/tron/core/services/http/GetBlockByNumServlet.java +++ b/framework/src/main/java/org/tron/core/services/http/GetBlockByNumServlet.java @@ -26,7 +26,9 @@ protected void doGet(HttpServletRequest request, HttpServletResponse response) { if (numStr != null) { num = Long.parseLong(numStr); } - fillResponse(Util.getVisible(request), num, response); + try (AutoCloseable ignored = JsonFormat.pushInt64AsString(Util.getInt64AsString(request))) { + fillResponse(Util.getVisible(request), num, response); + } } catch (Exception e) { Util.processError(e, response); } @@ -38,7 +40,9 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response) PostParams params = PostParams.getPostParams(request); NumberMessage.Builder build = NumberMessage.newBuilder(); JsonFormat.merge(params.getParams(), build, params.isVisible()); - fillResponse(params.isVisible(), build.getNum(), response); + try (AutoCloseable ignored = JsonFormat.pushInt64AsString(params.isInt64AsString())) { + fillResponse(params.isVisible(), build.getNum(), response); + } } catch (Exception e) { Util.processError(e, response); } diff --git a/framework/src/main/java/org/tron/core/services/http/GetBlockServlet.java b/framework/src/main/java/org/tron/core/services/http/GetBlockServlet.java index 0e0104e8014..e352968f3c2 100644 --- a/framework/src/main/java/org/tron/core/services/http/GetBlockServlet.java +++ b/framework/src/main/java/org/tron/core/services/http/GetBlockServlet.java @@ -34,7 +34,9 @@ private void handle(HttpServletRequest request, HttpServletResponse response) { try { PostParams params = parseParams(request); BlockReq message = buildRequest(params.getParams(), params.isVisible()); - fillResponse(params.isVisible(), message, response); + try (AutoCloseable ignored = JsonFormat.pushInt64AsString(params.isInt64AsString())) { + fillResponse(params.isVisible(), message, response); + } } catch (Exception e) { Util.processError(e, response); } @@ -50,7 +52,8 @@ private PostParams parseParams(HttpServletRequest request) throws Exception { } params.put("detail", Boolean.parseBoolean(request.getParameter("detail"))); return new PostParams(JSON.toJSONString(params), - Boolean.parseBoolean(request.getParameter(Util.VISIBLE))); + Boolean.parseBoolean(request.getParameter(Util.VISIBLE)), + Util.getInt64AsString(request)); } if (HttpMethod.POST.equals(m)) { return PostParams.getPostParams(request); diff --git a/framework/src/main/java/org/tron/core/services/http/GetBurnTrxServlet.java b/framework/src/main/java/org/tron/core/services/http/GetBurnTrxServlet.java index e574affff6b..4e5604661c6 100644 --- a/framework/src/main/java/org/tron/core/services/http/GetBurnTrxServlet.java +++ b/framework/src/main/java/org/tron/core/services/http/GetBurnTrxServlet.java @@ -19,7 +19,10 @@ public class GetBurnTrxServlet extends RateLimiterServlet { protected void doGet(HttpServletRequest request, HttpServletResponse response) { try { long value = manager.getDynamicPropertiesStore().getBurnTrxAmount(); - response.getWriter().println("{\"burnTrxAmount\": " + value + "}"); + String out = Util.getInt64AsString(request) + ? "{\"burnTrxAmount\": \"" + value + "\"}" + : "{\"burnTrxAmount\": " + value + "}"; + response.getWriter().println(out); } catch (Exception e) { logger.error("", e); try { diff --git a/framework/src/main/java/org/tron/core/services/http/GetCanDelegatedMaxSizeServlet.java b/framework/src/main/java/org/tron/core/services/http/GetCanDelegatedMaxSizeServlet.java index 924306a6a3f..85cd6ac0708 100644 --- a/framework/src/main/java/org/tron/core/services/http/GetCanDelegatedMaxSizeServlet.java +++ b/framework/src/main/java/org/tron/core/services/http/GetCanDelegatedMaxSizeServlet.java @@ -22,6 +22,7 @@ public class GetCanDelegatedMaxSizeServlet extends RateLimiterServlet { protected void doGet(HttpServletRequest request, HttpServletResponse response) { try { boolean visible = Util.getVisible(request); + boolean int64AsString = Util.getInt64AsString(request); int type = 0; String typeStr = request.getParameter("type"); if (typeStr != null) { @@ -31,9 +32,11 @@ protected void doGet(HttpServletRequest request, HttpServletResponse response) { if (visible) { ownerAddress = Util.getHexAddress(ownerAddress); } - fillResponse(visible, - ByteString.copyFrom(ByteArray.fromHexString(ownerAddress)), - type, response); + try (AutoCloseable ignored = JsonFormat.pushInt64AsString(int64AsString)) { + fillResponse(visible, + ByteString.copyFrom(ByteArray.fromHexString(ownerAddress)), + type, response); + } } catch (Exception e) { Util.processError(e, response); } @@ -46,9 +49,11 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response) GrpcAPI.CanDelegatedMaxSizeRequestMessage.Builder build = GrpcAPI.CanDelegatedMaxSizeRequestMessage.newBuilder(); JsonFormat.merge(params.getParams(), build, params.isVisible()); - fillResponse(params.isVisible(), - build.getOwnerAddress(), - build.getType(), response); + try (AutoCloseable ignored = JsonFormat.pushInt64AsString(params.isInt64AsString())) { + fillResponse(params.isVisible(), + build.getOwnerAddress(), + build.getType(), response); + } } catch (Exception e) { Util.processError(e, response); } diff --git a/framework/src/main/java/org/tron/core/services/http/GetCanWithdrawUnfreezeAmountServlet.java b/framework/src/main/java/org/tron/core/services/http/GetCanWithdrawUnfreezeAmountServlet.java index 435cca9e5fb..9065b4a210a 100644 --- a/framework/src/main/java/org/tron/core/services/http/GetCanWithdrawUnfreezeAmountServlet.java +++ b/framework/src/main/java/org/tron/core/services/http/GetCanWithdrawUnfreezeAmountServlet.java @@ -22,6 +22,7 @@ public class GetCanWithdrawUnfreezeAmountServlet extends RateLimiterServlet { protected void doGet(HttpServletRequest request, HttpServletResponse response) { try { boolean visible = Util.getVisible(request); + boolean int64AsString = Util.getInt64AsString(request); String ownerAddress = request.getParameter("owner_address"); long timestamp = 0; String timestampStr = request.getParameter("timestamp"); @@ -31,10 +32,12 @@ protected void doGet(HttpServletRequest request, HttpServletResponse response) { if (visible) { ownerAddress = Util.getHexAddress(ownerAddress); } - fillResponse(visible, - ByteString.copyFrom(ByteArray.fromHexString(ownerAddress)), - timestamp, - response); + try (AutoCloseable ignored = JsonFormat.pushInt64AsString(int64AsString)) { + fillResponse(visible, + ByteString.copyFrom(ByteArray.fromHexString(ownerAddress)), + timestamp, + response); + } } catch (Exception e) { Util.processError(e, response); } @@ -47,10 +50,12 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response) GrpcAPI.CanWithdrawUnfreezeAmountRequestMessage.Builder build = GrpcAPI.CanWithdrawUnfreezeAmountRequestMessage.newBuilder(); JsonFormat.merge(params.getParams(), build, params.isVisible()); - fillResponse(params.isVisible(), - build.getOwnerAddress(), - build.getTimestamp(), - response); + try (AutoCloseable ignored = JsonFormat.pushInt64AsString(params.isInt64AsString())) { + fillResponse(params.isVisible(), + build.getOwnerAddress(), + build.getTimestamp(), + response); + } } catch (Exception e) { Util.processError(e, response); } diff --git a/framework/src/main/java/org/tron/core/services/http/GetChainParametersServlet.java b/framework/src/main/java/org/tron/core/services/http/GetChainParametersServlet.java index 18c96f4d64e..dbed50aed71 100644 --- a/framework/src/main/java/org/tron/core/services/http/GetChainParametersServlet.java +++ b/framework/src/main/java/org/tron/core/services/http/GetChainParametersServlet.java @@ -18,7 +18,11 @@ public class GetChainParametersServlet extends RateLimiterServlet { protected void doPost(HttpServletRequest request, HttpServletResponse response) { try { boolean visible = Util.getVisible(request); - response.getWriter().println(JsonFormat.printToString(wallet.getChainParameters(), visible)); + boolean int64AsString = Util.getInt64AsString(request); + try (AutoCloseable ignored = JsonFormat.pushInt64AsString(int64AsString)) { + response.getWriter() + .println(JsonFormat.printToString(wallet.getChainParameters(), visible)); + } } catch (Exception e) { Util.processError(e, response); } diff --git a/framework/src/main/java/org/tron/core/services/http/GetDelegatedResourceAccountIndexServlet.java b/framework/src/main/java/org/tron/core/services/http/GetDelegatedResourceAccountIndexServlet.java index 035e20cb873..66eb11228b5 100644 --- a/framework/src/main/java/org/tron/core/services/http/GetDelegatedResourceAccountIndexServlet.java +++ b/framework/src/main/java/org/tron/core/services/http/GetDelegatedResourceAccountIndexServlet.java @@ -24,11 +24,14 @@ public class GetDelegatedResourceAccountIndexServlet extends RateLimiterServlet protected void doGet(HttpServletRequest request, HttpServletResponse response) { try { boolean visible = Util.getVisible(request); + boolean int64AsString = Util.getInt64AsString(request); String address = request.getParameter("value"); if (visible) { address = Util.getHexAddress(address); } - fillResponse(ByteString.copyFrom(ByteArray.fromHexString(address)), visible, response); + try (AutoCloseable ignored = JsonFormat.pushInt64AsString(int64AsString)) { + fillResponse(ByteString.copyFrom(ByteArray.fromHexString(address)), visible, response); + } } catch (Exception e) { Util.processError(e, response); } @@ -49,7 +52,9 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response) BytesMessage.Builder build = BytesMessage.newBuilder(); JsonFormat.merge(input, build, visible); - fillResponse(build.getValue(), visible, response); + try (AutoCloseable ignored = JsonFormat.pushInt64AsString(params.isInt64AsString())) { + fillResponse(build.getValue(), visible, response); + } } catch (Exception e) { Util.processError(e, response); } diff --git a/framework/src/main/java/org/tron/core/services/http/GetDelegatedResourceAccountIndexV2Servlet.java b/framework/src/main/java/org/tron/core/services/http/GetDelegatedResourceAccountIndexV2Servlet.java index 3d5bff80941..250b863f273 100644 --- a/framework/src/main/java/org/tron/core/services/http/GetDelegatedResourceAccountIndexV2Servlet.java +++ b/framework/src/main/java/org/tron/core/services/http/GetDelegatedResourceAccountIndexV2Servlet.java @@ -26,11 +26,14 @@ public class GetDelegatedResourceAccountIndexV2Servlet extends RateLimiterServle protected void doGet(HttpServletRequest request, HttpServletResponse response) { try { boolean visible = Util.getVisible(request); + boolean int64AsString = Util.getInt64AsString(request); String address = request.getParameter(VALUE_FIELD_NAME); if (visible) { address = Util.getHexAddress(address); } - fillResponse(ByteString.copyFrom(ByteArray.fromHexString(address)), visible, response); + try (AutoCloseable ignored = JsonFormat.pushInt64AsString(int64AsString)) { + fillResponse(ByteString.copyFrom(ByteArray.fromHexString(address)), visible, response); + } } catch (Exception e) { Util.processError(e, response); } @@ -51,7 +54,9 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response) BytesMessage.Builder build = BytesMessage.newBuilder(); JsonFormat.merge(input, build, visible); - fillResponse(build.getValue(), visible, response); + try (AutoCloseable ignored = JsonFormat.pushInt64AsString(params.isInt64AsString())) { + fillResponse(build.getValue(), visible, response); + } } catch (Exception e) { Util.processError(e, response); } diff --git a/framework/src/main/java/org/tron/core/services/http/GetDelegatedResourceServlet.java b/framework/src/main/java/org/tron/core/services/http/GetDelegatedResourceServlet.java index 5787aba3c93..e73d33254f5 100644 --- a/framework/src/main/java/org/tron/core/services/http/GetDelegatedResourceServlet.java +++ b/framework/src/main/java/org/tron/core/services/http/GetDelegatedResourceServlet.java @@ -24,14 +24,17 @@ public class GetDelegatedResourceServlet extends RateLimiterServlet { protected void doGet(HttpServletRequest request, HttpServletResponse response) { try { boolean visible = Util.getVisible(request); + boolean int64AsString = Util.getInt64AsString(request); String fromAddress = request.getParameter("fromAddress"); String toAddress = request.getParameter("toAddress"); if (visible) { fromAddress = Util.getHexAddress(fromAddress); toAddress = Util.getHexAddress(toAddress); } - fillResponse(visible, ByteString.copyFrom(ByteArray.fromHexString(fromAddress)), - ByteString.copyFrom(ByteArray.fromHexString(toAddress)), response); + try (AutoCloseable ignored = JsonFormat.pushInt64AsString(int64AsString)) { + fillResponse(visible, ByteString.copyFrom(ByteArray.fromHexString(fromAddress)), + ByteString.copyFrom(ByteArray.fromHexString(toAddress)), response); + } } catch (Exception e) { Util.processError(e, response); } @@ -42,7 +45,9 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response) PostParams params = PostParams.getPostParams(request); DelegatedResourceMessage.Builder build = DelegatedResourceMessage.newBuilder(); JsonFormat.merge(params.getParams(), build, params.isVisible()); - fillResponse(params.isVisible(), build.getFromAddress(), build.getToAddress(), response); + try (AutoCloseable ignored = JsonFormat.pushInt64AsString(params.isInt64AsString())) { + fillResponse(params.isVisible(), build.getFromAddress(), build.getToAddress(), response); + } } catch (Exception e) { Util.processError(e, response); } diff --git a/framework/src/main/java/org/tron/core/services/http/GetDelegatedResourceV2Servlet.java b/framework/src/main/java/org/tron/core/services/http/GetDelegatedResourceV2Servlet.java index 632bb4f8033..51d85536949 100644 --- a/framework/src/main/java/org/tron/core/services/http/GetDelegatedResourceV2Servlet.java +++ b/framework/src/main/java/org/tron/core/services/http/GetDelegatedResourceV2Servlet.java @@ -25,14 +25,17 @@ public class GetDelegatedResourceV2Servlet extends RateLimiterServlet { protected void doGet(HttpServletRequest request, HttpServletResponse response) { try { boolean visible = Util.getVisible(request); + boolean int64AsString = Util.getInt64AsString(request); String fromAddress = request.getParameter("fromAddress"); String toAddress = request.getParameter("toAddress"); if (visible) { fromAddress = Util.getHexAddress(fromAddress); toAddress = Util.getHexAddress(toAddress); } - fillResponse(visible, ByteString.copyFrom(ByteArray.fromHexString(fromAddress)), - ByteString.copyFrom(ByteArray.fromHexString(toAddress)), response); + try (AutoCloseable ignored = JsonFormat.pushInt64AsString(int64AsString)) { + fillResponse(visible, ByteString.copyFrom(ByteArray.fromHexString(fromAddress)), + ByteString.copyFrom(ByteArray.fromHexString(toAddress)), response); + } } catch (Exception e) { Util.processError(e, response); } @@ -43,7 +46,9 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response) PostParams params = PostParams.getPostParams(request); DelegatedResourceMessage.Builder build = DelegatedResourceMessage.newBuilder(); JsonFormat.merge(params.getParams(), build, params.isVisible()); - fillResponse(params.isVisible(), build.getFromAddress(), build.getToAddress(), response); + try (AutoCloseable ignored = JsonFormat.pushInt64AsString(params.isInt64AsString())) { + fillResponse(params.isVisible(), build.getFromAddress(), build.getToAddress(), response); + } } catch (Exception e) { Util.processError(e, response); } diff --git a/framework/src/main/java/org/tron/core/services/http/GetNowBlockServlet.java b/framework/src/main/java/org/tron/core/services/http/GetNowBlockServlet.java index 56e01d557f5..2e2a0c63622 100644 --- a/framework/src/main/java/org/tron/core/services/http/GetNowBlockServlet.java +++ b/framework/src/main/java/org/tron/core/services/http/GetNowBlockServlet.java @@ -19,9 +19,12 @@ public class GetNowBlockServlet extends RateLimiterServlet { protected void doGet(HttpServletRequest request, HttpServletResponse response) { try { boolean visible = Util.getVisible(request); + boolean int64AsString = Util.getInt64AsString(request); Block reply = wallet.getNowBlock(); if (reply != null) { - response.getWriter().println(Util.printBlock(reply, visible)); + try (AutoCloseable ignored = JsonFormat.pushInt64AsString(int64AsString)) { + response.getWriter().println(Util.printBlock(reply, visible)); + } } else { response.getWriter().println("{}"); } diff --git a/framework/src/main/java/org/tron/core/services/http/GetPaginatedAssetIssueListServlet.java b/framework/src/main/java/org/tron/core/services/http/GetPaginatedAssetIssueListServlet.java index 0246979e279..b5f6599715d 100644 --- a/framework/src/main/java/org/tron/core/services/http/GetPaginatedAssetIssueListServlet.java +++ b/framework/src/main/java/org/tron/core/services/http/GetPaginatedAssetIssueListServlet.java @@ -20,9 +20,12 @@ public class GetPaginatedAssetIssueListServlet extends RateLimiterServlet { protected void doGet(HttpServletRequest request, HttpServletResponse response) { try { boolean visible = Util.getVisible(request); + boolean int64AsString = Util.getInt64AsString(request); long offset = Long.parseLong(request.getParameter("offset")); long limit = Long.parseLong(request.getParameter("limit")); - fillResponse(offset, limit, visible, response); + try (AutoCloseable ignored = JsonFormat.pushInt64AsString(int64AsString)) { + fillResponse(offset, limit, visible, response); + } } catch (Exception e) { Util.processError(e, response); } @@ -35,7 +38,9 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response) boolean visible = params.isVisible(); PaginatedMessage.Builder build = PaginatedMessage.newBuilder(); JsonFormat.merge(input, build, visible); - fillResponse(build.getOffset(), build.getLimit(), visible, response); + try (AutoCloseable ignored = JsonFormat.pushInt64AsString(params.isInt64AsString())) { + fillResponse(build.getOffset(), build.getLimit(), visible, response); + } } catch (Exception e) { Util.processError(e, response); } diff --git a/framework/src/main/java/org/tron/core/services/http/GetPaginatedExchangeListServlet.java b/framework/src/main/java/org/tron/core/services/http/GetPaginatedExchangeListServlet.java index 28018497952..3bbb798bead 100644 --- a/framework/src/main/java/org/tron/core/services/http/GetPaginatedExchangeListServlet.java +++ b/framework/src/main/java/org/tron/core/services/http/GetPaginatedExchangeListServlet.java @@ -20,9 +20,12 @@ public class GetPaginatedExchangeListServlet extends RateLimiterServlet { protected void doGet(HttpServletRequest request, HttpServletResponse response) { try { boolean visible = Util.getVisible(request); + boolean int64AsString = Util.getInt64AsString(request); long offset = Long.parseLong(request.getParameter("offset")); long limit = Long.parseLong(request.getParameter("limit")); - fillResponse(offset, limit, visible, response); + try (AutoCloseable ignored = JsonFormat.pushInt64AsString(int64AsString)) { + fillResponse(offset, limit, visible, response); + } } catch (Exception e) { Util.processError(e, response); } @@ -33,7 +36,9 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response) PostParams params = PostParams.getPostParams(request); PaginatedMessage.Builder build = PaginatedMessage.newBuilder(); JsonFormat.merge(params.getParams(), build, params.isVisible()); - fillResponse(build.getOffset(), build.getLimit(), params.isVisible(), response); + try (AutoCloseable ignored = JsonFormat.pushInt64AsString(params.isInt64AsString())) { + fillResponse(build.getOffset(), build.getLimit(), params.isVisible(), response); + } } catch (Exception e) { Util.processError(e, response); } diff --git a/framework/src/main/java/org/tron/core/services/http/GetPaginatedNowWitnessListServlet.java b/framework/src/main/java/org/tron/core/services/http/GetPaginatedNowWitnessListServlet.java index e53ab6610ec..e9dfc45f99f 100644 --- a/framework/src/main/java/org/tron/core/services/http/GetPaginatedNowWitnessListServlet.java +++ b/framework/src/main/java/org/tron/core/services/http/GetPaginatedNowWitnessListServlet.java @@ -21,9 +21,12 @@ public class GetPaginatedNowWitnessListServlet extends RateLimiterServlet { protected void doGet(HttpServletRequest request, HttpServletResponse response) { try { boolean visible = Util.getVisible(request); + boolean int64AsString = Util.getInt64AsString(request); long offset = Long.parseLong(request.getParameter("offset")); long limit = Long.parseLong(request.getParameter("limit")); - fillResponse(offset, limit, visible, response); + try (AutoCloseable ignored = JsonFormat.pushInt64AsString(int64AsString)) { + fillResponse(offset, limit, visible, response); + } } catch (Exception e) { Util.processError(e, response); } @@ -34,7 +37,9 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response) PostParams params = PostParams.getPostParams(request); GrpcAPI.PaginatedMessage.Builder build = GrpcAPI.PaginatedMessage.newBuilder(); JsonFormat.merge(params.getParams(), build, params.isVisible()); - fillResponse(build.getOffset(), build.getLimit(), params.isVisible(), response); + try (AutoCloseable ignored = JsonFormat.pushInt64AsString(params.isInt64AsString())) { + fillResponse(build.getOffset(), build.getLimit(), params.isVisible(), response); + } } catch (Exception e) { Util.processError(e, response); } diff --git a/framework/src/main/java/org/tron/core/services/http/GetPaginatedProposalListServlet.java b/framework/src/main/java/org/tron/core/services/http/GetPaginatedProposalListServlet.java index 11b2c57ff48..f3f80e632e0 100644 --- a/framework/src/main/java/org/tron/core/services/http/GetPaginatedProposalListServlet.java +++ b/framework/src/main/java/org/tron/core/services/http/GetPaginatedProposalListServlet.java @@ -21,9 +21,12 @@ public class GetPaginatedProposalListServlet extends RateLimiterServlet { protected void doGet(HttpServletRequest request, HttpServletResponse response) { try { boolean visible = Util.getVisible(request); + boolean int64AsString = Util.getInt64AsString(request); long offset = Long.parseLong(request.getParameter("offset")); long limit = Long.parseLong(request.getParameter("limit")); - fillResponse(offset, limit, visible, response); + try (AutoCloseable ignored = JsonFormat.pushInt64AsString(int64AsString)) { + fillResponse(offset, limit, visible, response); + } } catch (Exception e) { Util.processError(e, response); } @@ -35,9 +38,12 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response) .collect(Collectors.joining(System.lineSeparator())); Util.checkBodySize(input); boolean visible = Util.getVisiblePost(input); + boolean int64AsString = Util.getInt64AsStringPost(input); PaginatedMessage.Builder build = PaginatedMessage.newBuilder(); JsonFormat.merge(input, build, visible); - fillResponse(build.getOffset(), build.getLimit(), visible, response); + try (AutoCloseable ignored = JsonFormat.pushInt64AsString(int64AsString)) { + fillResponse(build.getOffset(), build.getLimit(), visible, response); + } } catch (Exception e) { Util.processError(e, response); } diff --git a/framework/src/main/java/org/tron/core/services/http/GetPendingSizeServlet.java b/framework/src/main/java/org/tron/core/services/http/GetPendingSizeServlet.java index 7e1a5f71841..2ec75ae969b 100644 --- a/framework/src/main/java/org/tron/core/services/http/GetPendingSizeServlet.java +++ b/framework/src/main/java/org/tron/core/services/http/GetPendingSizeServlet.java @@ -19,7 +19,10 @@ public class GetPendingSizeServlet extends RateLimiterServlet { protected void doGet(HttpServletRequest request, HttpServletResponse response) { try { long value = manager.getPendingSize(); - response.getWriter().println("{\"pendingSize\": " + value + "}"); + String out = Util.getInt64AsString(request) + ? "{\"pendingSize\": \"" + value + "\"}" + : "{\"pendingSize\": " + value + "}"; + response.getWriter().println(out); } catch (Exception e) { logger.error("", e); try { diff --git a/framework/src/main/java/org/tron/core/services/http/GetRewardServlet.java b/framework/src/main/java/org/tron/core/services/http/GetRewardServlet.java index c4d97f46c57..cfa4eb1d4b8 100644 --- a/framework/src/main/java/org/tron/core/services/http/GetRewardServlet.java +++ b/framework/src/main/java/org/tron/core/services/http/GetRewardServlet.java @@ -24,7 +24,10 @@ protected void doGet(HttpServletRequest request, HttpServletResponse response) { if (address != null) { value = manager.getMortgageService().queryReward(address); } - response.getWriter().println("{\"reward\": " + value + "}"); + String out = Util.getInt64AsString(request) + ? "{\"reward\": \"" + value + "\"}" + : "{\"reward\": " + value + "}"; + response.getWriter().println(out); } catch (DecoderException | IllegalArgumentException e) { try { response.getWriter() diff --git a/framework/src/main/java/org/tron/core/services/http/GetTransactionByIdServlet.java b/framework/src/main/java/org/tron/core/services/http/GetTransactionByIdServlet.java index 49343da9070..29b299c5190 100644 --- a/framework/src/main/java/org/tron/core/services/http/GetTransactionByIdServlet.java +++ b/framework/src/main/java/org/tron/core/services/http/GetTransactionByIdServlet.java @@ -24,8 +24,11 @@ public class GetTransactionByIdServlet extends RateLimiterServlet { protected void doGet(HttpServletRequest request, HttpServletResponse response) { try { boolean visible = Util.getVisible(request); + boolean int64AsString = Util.getInt64AsString(request); String input = request.getParameter("value"); - fillResponse(ByteString.copyFrom(ByteArray.fromHexString(input)), visible, response); + try (AutoCloseable ignored = JsonFormat.pushInt64AsString(int64AsString)) { + fillResponse(ByteString.copyFrom(ByteArray.fromHexString(input)), visible, response); + } } catch (Exception e) { Util.processError(e, response); } @@ -36,7 +39,9 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response) PostParams params = PostParams.getPostParams(request); BytesMessage.Builder build = BytesMessage.newBuilder(); JsonFormat.merge(params.getParams(), build, params.isVisible()); - fillResponse(build.getValue(), params.isVisible(), response); + try (AutoCloseable ignored = JsonFormat.pushInt64AsString(params.isInt64AsString())) { + fillResponse(build.getValue(), params.isVisible(), response); + } } catch (Exception e) { Util.processError(e, response); } diff --git a/framework/src/main/java/org/tron/core/services/http/GetTransactionCountByBlockNumServlet.java b/framework/src/main/java/org/tron/core/services/http/GetTransactionCountByBlockNumServlet.java index e096df507d7..00a1f7c2d2e 100644 --- a/framework/src/main/java/org/tron/core/services/http/GetTransactionCountByBlockNumServlet.java +++ b/framework/src/main/java/org/tron/core/services/http/GetTransactionCountByBlockNumServlet.java @@ -21,7 +21,7 @@ public class GetTransactionCountByBlockNumServlet extends RateLimiterServlet { protected void doGet(HttpServletRequest request, HttpServletResponse response) { try { long num = Long.parseLong(request.getParameter("num")); - fillResponse(num, response); + fillResponse(num, Util.getInt64AsString(request), response); } catch (Exception e) { Util.processError(e, response); } @@ -32,14 +32,18 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response) PostParams params = PostParams.getPostParams(request); NumberMessage.Builder build = NumberMessage.newBuilder(); JsonFormat.merge(params.getParams(), build, params.isVisible()); - fillResponse(build.getNum(), response); + fillResponse(build.getNum(), params.isInt64AsString(), response); } catch (Exception e) { Util.processError(e, response); } } - private void fillResponse(long num, HttpServletResponse response) throws IOException { + private void fillResponse(long num, boolean int64AsString, HttpServletResponse response) + throws IOException { long count = wallet.getTransactionCountByBlockNum(num); - response.getWriter().println("{\"count\": " + count + "}"); + String out = int64AsString + ? "{\"count\": \"" + count + "\"}" + : "{\"count\": " + count + "}"; + response.getWriter().println(out); } } \ No newline at end of file diff --git a/framework/src/main/java/org/tron/core/services/http/GetTransactionFromPendingServlet.java b/framework/src/main/java/org/tron/core/services/http/GetTransactionFromPendingServlet.java index 276eff62af8..791643d11df 100644 --- a/framework/src/main/java/org/tron/core/services/http/GetTransactionFromPendingServlet.java +++ b/framework/src/main/java/org/tron/core/services/http/GetTransactionFromPendingServlet.java @@ -21,10 +21,13 @@ public class GetTransactionFromPendingServlet extends RateLimiterServlet { protected void doGet(HttpServletRequest request, HttpServletResponse response) { try { boolean visible = Util.getVisible(request); + boolean int64AsString = Util.getInt64AsString(request); String input = request.getParameter("value"); TransactionCapsule reply = manager.getTxFromPending(input); if (reply != null) { - response.getWriter().println(Util.printTransaction(reply.getInstance(), visible)); + try (AutoCloseable ignored = JsonFormat.pushInt64AsString(int64AsString)) { + response.getWriter().println(Util.printTransaction(reply.getInstance(), visible)); + } } else { response.getWriter().println("{}"); } @@ -41,8 +44,10 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response) TransactionCapsule reply = manager .getTxFromPending(ByteArray.toHexString(build.getValue().toByteArray())); if (reply != null) { - response.getWriter() - .println(Util.printTransaction(reply.getInstance(), params.isVisible())); + try (AutoCloseable ignored = JsonFormat.pushInt64AsString(params.isInt64AsString())) { + response.getWriter() + .println(Util.printTransaction(reply.getInstance(), params.isVisible())); + } } else { response.getWriter().println("{}"); } diff --git a/framework/src/main/java/org/tron/core/services/http/GetTransactionInfoByBlockNumServlet.java b/framework/src/main/java/org/tron/core/services/http/GetTransactionInfoByBlockNumServlet.java index 587dd2d6613..a91d1c08935 100644 --- a/framework/src/main/java/org/tron/core/services/http/GetTransactionInfoByBlockNumServlet.java +++ b/framework/src/main/java/org/tron/core/services/http/GetTransactionInfoByBlockNumServlet.java @@ -43,11 +43,14 @@ private String printTransactionInfoList(TransactionInfoList list, boolean selfTy protected void doGet(HttpServletRequest request, HttpServletResponse response) { try { boolean visible = Util.getVisible(request); + boolean int64AsString = Util.getInt64AsString(request); long num = Long.parseLong(request.getParameter("num")); if (num > 0L) { TransactionInfoList reply = wallet.getTransactionInfoByBlockNum(num); - response.getWriter().println(printTransactionInfoList(reply, visible)); + try (AutoCloseable ignored = JsonFormat.pushInt64AsString(int64AsString)) { + response.getWriter().println(printTransactionInfoList(reply, visible)); + } } else { response.getWriter().println("{}"); } @@ -70,7 +73,9 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response) long num = build.getNum(); if (num > 0L) { TransactionInfoList reply = wallet.getTransactionInfoByBlockNum(num); - response.getWriter().println(printTransactionInfoList(reply, params.isVisible())); + try (AutoCloseable ignored = JsonFormat.pushInt64AsString(params.isInt64AsString())) { + response.getWriter().println(printTransactionInfoList(reply, params.isVisible())); + } } else { response.getWriter().println("{}"); } diff --git a/framework/src/main/java/org/tron/core/services/http/GetTransactionInfoByIdServlet.java b/framework/src/main/java/org/tron/core/services/http/GetTransactionInfoByIdServlet.java index 580cb4a5b66..fb30e89eb37 100644 --- a/framework/src/main/java/org/tron/core/services/http/GetTransactionInfoByIdServlet.java +++ b/framework/src/main/java/org/tron/core/services/http/GetTransactionInfoByIdServlet.java @@ -34,11 +34,14 @@ private static String convertLogAddressToTronAddress(TransactionInfo transaction protected void doGet(HttpServletRequest request, HttpServletResponse response) { try { boolean visible = Util.getVisible(request); + boolean int64AsString = Util.getInt64AsString(request); String input = request.getParameter("value"); TransactionInfo reply = wallet .getTransactionInfoById(ByteString.copyFrom(ByteArray.fromHexString(input))); if (reply != null) { - response.getWriter().println(convertLogAddressToTronAddress(reply, visible)); + try (AutoCloseable ignored = JsonFormat.pushInt64AsString(int64AsString)) { + response.getWriter().println(convertLogAddressToTronAddress(reply, visible)); + } } else { response.getWriter().println("{}"); } @@ -54,7 +57,9 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response) JsonFormat.merge(params.getParams(), build, params.isVisible()); TransactionInfo reply = wallet.getTransactionInfoById(build.getValue()); if (reply != null) { - response.getWriter().println(convertLogAddressToTronAddress(reply, params.isVisible())); + try (AutoCloseable ignored = JsonFormat.pushInt64AsString(params.isInt64AsString())) { + response.getWriter().println(convertLogAddressToTronAddress(reply, params.isVisible())); + } } else { response.getWriter().println("{}"); } diff --git a/framework/src/main/java/org/tron/core/services/http/GetTransactionReceiptByIdServlet.java b/framework/src/main/java/org/tron/core/services/http/GetTransactionReceiptByIdServlet.java index bdd46d85a88..801ff177589 100644 --- a/framework/src/main/java/org/tron/core/services/http/GetTransactionReceiptByIdServlet.java +++ b/framework/src/main/java/org/tron/core/services/http/GetTransactionReceiptByIdServlet.java @@ -22,13 +22,16 @@ public class GetTransactionReceiptByIdServlet extends RateLimiterServlet { protected void doGet(HttpServletRequest request, HttpServletResponse response) { try { boolean visible = Util.getVisible(request); + boolean int64AsString = Util.getInt64AsString(request); String input = request.getParameter("value"); TransactionInfo result = wallet .getTransactionInfoById(ByteString.copyFrom(ByteArray.fromHexString(input))); if (result != null) { - response.getWriter().println( - Util.printTransactionFee(JsonFormat.printToString(result, visible))); + try (AutoCloseable ignored = JsonFormat.pushInt64AsString(int64AsString)) { + response.getWriter().println( + Util.printTransactionFee(JsonFormat.printToString(result, visible))); + } } else { response.getWriter().println("{}"); } @@ -45,9 +48,11 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response) TransactionInfo result = wallet.getTransactionInfoById(build.getValue()); if (result != null) { - response.getWriter().println( - Util.printTransactionFee(JsonFormat - .printToString(result, params.isVisible()))); + try (AutoCloseable ignored = JsonFormat.pushInt64AsString(params.isInt64AsString())) { + response.getWriter().println( + Util.printTransactionFee(JsonFormat + .printToString(result, params.isVisible()))); + } } else { response.getWriter().println("{}"); } diff --git a/framework/src/main/java/org/tron/core/services/http/ListExchangesServlet.java b/framework/src/main/java/org/tron/core/services/http/ListExchangesServlet.java index ff349ecefab..5cc6f5c0811 100644 --- a/framework/src/main/java/org/tron/core/services/http/ListExchangesServlet.java +++ b/framework/src/main/java/org/tron/core/services/http/ListExchangesServlet.java @@ -18,7 +18,10 @@ public class ListExchangesServlet extends RateLimiterServlet { protected void doPost(HttpServletRequest request, HttpServletResponse response) { try { boolean visible = Util.getVisible(request); - response.getWriter().println(JsonFormat.printToString(wallet.getExchangeList(), visible)); + boolean int64AsString = Util.getInt64AsString(request); + try (AutoCloseable ignored = JsonFormat.pushInt64AsString(int64AsString)) { + response.getWriter().println(JsonFormat.printToString(wallet.getExchangeList(), visible)); + } } catch (Exception e) { Util.processError(e, response); } diff --git a/framework/src/main/java/org/tron/core/services/http/ListProposalsServlet.java b/framework/src/main/java/org/tron/core/services/http/ListProposalsServlet.java index a3b26d4afc6..38f94ee075a 100644 --- a/framework/src/main/java/org/tron/core/services/http/ListProposalsServlet.java +++ b/framework/src/main/java/org/tron/core/services/http/ListProposalsServlet.java @@ -19,9 +19,12 @@ public class ListProposalsServlet extends RateLimiterServlet { protected void doGet(HttpServletRequest request, HttpServletResponse response) { try { boolean visible = Util.getVisible(request); + boolean int64AsString = Util.getInt64AsString(request); ProposalList reply = wallet.getProposalList(); if (reply != null) { - response.getWriter().println(JsonFormat.printToString(reply, visible)); + try (AutoCloseable ignored = JsonFormat.pushInt64AsString(int64AsString)) { + response.getWriter().println(JsonFormat.printToString(reply, visible)); + } } else { response.getWriter().println("{}"); } diff --git a/framework/src/main/java/org/tron/core/services/http/ListWitnessesServlet.java b/framework/src/main/java/org/tron/core/services/http/ListWitnessesServlet.java index 5274d07992b..dfbdbe622e3 100644 --- a/framework/src/main/java/org/tron/core/services/http/ListWitnessesServlet.java +++ b/framework/src/main/java/org/tron/core/services/http/ListWitnessesServlet.java @@ -19,9 +19,12 @@ public class ListWitnessesServlet extends RateLimiterServlet { protected void doGet(HttpServletRequest request, HttpServletResponse response) { try { boolean visible = Util.getVisible(request); + boolean int64AsString = Util.getInt64AsString(request); WitnessList reply = wallet.getWitnessList(); if (reply != null) { - response.getWriter().println(JsonFormat.printToString(reply, visible)); + try (AutoCloseable ignored = JsonFormat.pushInt64AsString(int64AsString)) { + response.getWriter().println(JsonFormat.printToString(reply, visible)); + } } else { response.getWriter().println("{}"); } diff --git a/framework/src/main/java/org/tron/core/services/http/TotalTransactionServlet.java b/framework/src/main/java/org/tron/core/services/http/TotalTransactionServlet.java index 6af40232e8f..9adf8a5c953 100644 --- a/framework/src/main/java/org/tron/core/services/http/TotalTransactionServlet.java +++ b/framework/src/main/java/org/tron/core/services/http/TotalTransactionServlet.java @@ -19,9 +19,12 @@ public class TotalTransactionServlet extends RateLimiterServlet { protected void doGet(HttpServletRequest request, HttpServletResponse response) { try { boolean visible = Util.getVisible(request); + boolean int64AsString = Util.getInt64AsString(request); NumberMessage reply = wallet.totalTransaction(); if (reply != null) { - response.getWriter().println(JsonFormat.printToString(reply, visible)); + try (AutoCloseable ignored = JsonFormat.pushInt64AsString(int64AsString)) { + response.getWriter().println(JsonFormat.printToString(reply, visible)); + } } else { response.getWriter().println("{}"); } diff --git a/framework/src/main/java/org/tron/core/services/http/solidity/GetTransactionByIdSolidityServlet.java b/framework/src/main/java/org/tron/core/services/http/solidity/GetTransactionByIdSolidityServlet.java index f98c7450afc..01fddb559dd 100644 --- a/framework/src/main/java/org/tron/core/services/http/solidity/GetTransactionByIdSolidityServlet.java +++ b/framework/src/main/java/org/tron/core/services/http/solidity/GetTransactionByIdSolidityServlet.java @@ -27,8 +27,11 @@ public class GetTransactionByIdSolidityServlet extends RateLimiterServlet { protected void doGet(HttpServletRequest request, HttpServletResponse response) { try { boolean visible = Util.getVisible(request); + boolean int64AsString = Util.getInt64AsString(request); String input = request.getParameter("value"); - fillResponse(ByteString.copyFrom(ByteArray.fromHexString(input)), visible, response); + try (AutoCloseable ignored = JsonFormat.pushInt64AsString(int64AsString)) { + fillResponse(ByteString.copyFrom(ByteArray.fromHexString(input)), visible, response); + } } catch (Exception e) { logger.debug("Exception: {}", e.getMessage()); try { @@ -44,7 +47,9 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response) PostParams params = PostParams.getPostParams(request); BytesMessage.Builder build = BytesMessage.newBuilder(); JsonFormat.merge(params.getParams(), build, params.isVisible()); - fillResponse(build.build().getValue(), params.isVisible(), response); + try (AutoCloseable ignored = JsonFormat.pushInt64AsString(params.isInt64AsString())) { + fillResponse(build.build().getValue(), params.isVisible(), response); + } } catch (Exception e) { logger.debug("Exception: {}", e.getMessage()); try { diff --git a/framework/src/main/java/org/tron/core/services/http/solidity/GetTransactionInfoByIdSolidityServlet.java b/framework/src/main/java/org/tron/core/services/http/solidity/GetTransactionInfoByIdSolidityServlet.java index 0408215f09d..5faf9736b0d 100644 --- a/framework/src/main/java/org/tron/core/services/http/solidity/GetTransactionInfoByIdSolidityServlet.java +++ b/framework/src/main/java/org/tron/core/services/http/solidity/GetTransactionInfoByIdSolidityServlet.java @@ -28,13 +28,16 @@ public class GetTransactionInfoByIdSolidityServlet extends RateLimiterServlet { protected void doGet(HttpServletRequest request, HttpServletResponse response) { try { boolean visible = Util.getVisible(request); + boolean int64AsString = Util.getInt64AsString(request); String input = request.getParameter("value"); TransactionInfo transInfo = wallet .getTransactionInfoById(ByteString.copyFrom(ByteArray.fromHexString(input))); if (transInfo == null) { response.getWriter().println("{}"); } else { - response.getWriter().println(JsonFormat.printToString(transInfo, visible)); + try (AutoCloseable ignored = JsonFormat.pushInt64AsString(int64AsString)) { + response.getWriter().println(JsonFormat.printToString(transInfo, visible)); + } } } catch (Exception e) { logger.debug("Exception: {}", e.getMessage()); @@ -57,7 +60,9 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response) if (transInfo == null) { response.getWriter().println("{}"); } else { - response.getWriter().println(JsonFormat.printToString(transInfo, params.isVisible())); + try (AutoCloseable ignored = JsonFormat.pushInt64AsString(params.isInt64AsString())) { + response.getWriter().println(JsonFormat.printToString(transInfo, params.isVisible())); + } } } catch (Exception e) { logger.debug("Exception: {}", e.getMessage()); From 00bc4f819a620d1fd075188929085721612db2d0 Mon Sep 17 00:00:00 2001 From: waynercheung Date: Tue, 21 Apr 2026 20:14:33 +0800 Subject: [PATCH 3/9] test(http): add int64_as_string integration coverage for representative 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 --- .../services/http/GetNowBlockServletTest.java | 45 +++++++++++++++ .../services/http/GetRewardServletTest.java | 55 +++++++++++++++++++ 2 files changed, 100 insertions(+) diff --git a/framework/src/test/java/org/tron/core/services/http/GetNowBlockServletTest.java b/framework/src/test/java/org/tron/core/services/http/GetNowBlockServletTest.java index bf5ab766fb1..b962564a8f1 100644 --- a/framework/src/test/java/org/tron/core/services/http/GetNowBlockServletTest.java +++ b/framework/src/test/java/org/tron/core/services/http/GetNowBlockServletTest.java @@ -118,4 +118,49 @@ public void testGetNowBlockEmptyParam() { fail(e.getMessage()); } } + + @Test + public void testGetNowBlockInt64AsStringQueryTrue() { + // Template S-b (doPost delegates to doGet that reads query): Block.block_header. + // raw_data.timestamp and .number are int64; with int64_as_string=true they should be + // serialized as quoted strings. + MockHttpServletRequest request = createRequest("application/x-www-form-urlencoded"); + request.addParameter("int64_as_string", "true"); + MockHttpServletResponse response = new MockHttpServletResponse(); + getNowBlockServlet.doPost(request, response); + try { + String contentAsString = response.getContentAsString(); + // At genesis the header's number field defaults to 0 and proto3 omits defaults, + // so we assert on the witness_address/parentHash presence and on the absence of an + // unquoted timestamp when the field is present. + assertTrue("expected a block response: " + contentAsString, + contentAsString.contains("block_header") || contentAsString.contains("blockID")); + // If timestamp appears (>0), it must be quoted. + if (contentAsString.contains("\"timestamp\"")) { + assertTrue("timestamp should be quoted when int64_as_string=true, got: " + + contentAsString, + contentAsString.matches("(?s).*\"timestamp\"\\s*:\\s*\"\\d+\".*")); + } + } catch (UnsupportedEncodingException e) { + fail(e.getMessage()); + } + } + + @Test + public void testGetNowBlockInt64AsStringDefaultUnquoted() { + // Regression: without int64_as_string, response must be identical to prior behavior. + MockHttpServletRequest request = createRequest("application/x-www-form-urlencoded"); + MockHttpServletResponse response = new MockHttpServletResponse(); + getNowBlockServlet.doPost(request, response); + try { + String contentAsString = response.getContentAsString(); + if (contentAsString.contains("\"timestamp\"")) { + // Default: unquoted integer. + assertTrue("timestamp should be unquoted by default, got: " + contentAsString, + contentAsString.matches("(?s).*\"timestamp\"\\s*:\\s*\\d+.*")); + } + } catch (UnsupportedEncodingException e) { + fail(e.getMessage()); + } + } } diff --git a/framework/src/test/java/org/tron/core/services/http/GetRewardServletTest.java b/framework/src/test/java/org/tron/core/services/http/GetRewardServletTest.java index 3de72eb3d45..9d84c0a949f 100644 --- a/framework/src/test/java/org/tron/core/services/http/GetRewardServletTest.java +++ b/framework/src/test/java/org/tron/core/services/http/GetRewardServletTest.java @@ -136,4 +136,59 @@ public void getByBlankParamTest() { } } + @Test + public void getRewardWithInt64AsStringTrue() { + // Hand-rolled JSON servlet: int64_as_string=true should stringify the reward field. + MockHttpServletRequest request = createRequest("application/x-www-form-urlencoded"); + MockHttpServletResponse response = new MockHttpServletResponse(); + request.addParameter("address", "TNboetpFgv9SqMoHvaVt626NLXETnbdW1K"); + request.addParameter("int64_as_string", "true"); + getRewardServlet.doGet(request, response); + try { + String contentAsString = response.getContentAsString(); + JSONObject result = JSONObject.parseObject(contentAsString); + Object reward = result.get("reward"); + Assert.assertTrue("reward should be a String when int64_as_string=true, got: " + + contentAsString, reward instanceof String); + } catch (UnsupportedEncodingException e) { + Assert.fail(e.getMessage()); + } + } + + @Test + public void getRewardWithInt64AsStringFalseKeepsNumber() { + MockHttpServletRequest request = createRequest("application/x-www-form-urlencoded"); + MockHttpServletResponse response = new MockHttpServletResponse(); + request.addParameter("address", "TNboetpFgv9SqMoHvaVt626NLXETnbdW1K"); + request.addParameter("int64_as_string", "false"); + getRewardServlet.doGet(request, response); + try { + String contentAsString = response.getContentAsString(); + JSONObject result = JSONObject.parseObject(contentAsString); + Object reward = result.get("reward"); + Assert.assertFalse("reward should remain a number when int64_as_string=false, got: " + + contentAsString, reward instanceof String); + } catch (UnsupportedEncodingException e) { + Assert.fail(e.getMessage()); + } + } + + @Test + public void getRewardDefaultStillReturnsNumber() { + // Regression: absence of int64_as_string must preserve legacy behavior (raw number). + MockHttpServletRequest request = createRequest("application/x-www-form-urlencoded"); + MockHttpServletResponse response = new MockHttpServletResponse(); + request.addParameter("address", "TNboetpFgv9SqMoHvaVt626NLXETnbdW1K"); + getRewardServlet.doGet(request, response); + try { + String contentAsString = response.getContentAsString(); + JSONObject result = JSONObject.parseObject(contentAsString); + Object reward = result.get("reward"); + Assert.assertFalse("default behavior must not quote reward, got: " + contentAsString, + reward instanceof String); + } catch (UnsupportedEncodingException e) { + Assert.fail(e.getMessage()); + } + } + } From 5e718c9943410ab9262be701ca6fdf2b126033a2 Mon Sep 17 00:00:00 2001 From: waynercheung Date: Wed, 22 Apr 2026 11:20:42 +0800 Subject: [PATCH 4/9] refactor(http): read visible and int64_as_string via locals in GetBlockServlet.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. --- .../java/org/tron/core/services/http/GetBlockServlet.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/framework/src/main/java/org/tron/core/services/http/GetBlockServlet.java b/framework/src/main/java/org/tron/core/services/http/GetBlockServlet.java index e352968f3c2..99ada558855 100644 --- a/framework/src/main/java/org/tron/core/services/http/GetBlockServlet.java +++ b/framework/src/main/java/org/tron/core/services/http/GetBlockServlet.java @@ -51,9 +51,9 @@ private PostParams parseParams(HttpServletRequest request) throws Exception { params.put("id_or_num", idOrNum); } params.put("detail", Boolean.parseBoolean(request.getParameter("detail"))); - return new PostParams(JSON.toJSONString(params), - Boolean.parseBoolean(request.getParameter(Util.VISIBLE)), - Util.getInt64AsString(request)); + boolean visible = Util.getVisible(request); + boolean int64AsString = Util.getInt64AsString(request); + return new PostParams(JSON.toJSONString(params), visible, int64AsString); } if (HttpMethod.POST.equals(m)) { return PostParams.getPostParams(request); From 43061c64276444ae2f3e3d70444ee6ec6f5a9873 Mon Sep 17 00:00:00 2001 From: waynercheung Date: Wed, 22 Apr 2026 16:48:08 +0800 Subject: [PATCH 5/9] test(http): add int64_as_string coverage for GetBlockServlet and GetPaginatedProposalListServlet 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. --- .../services/http/GetBlockServletTest.java | 149 ++++++++++++++++++ .../GetPaginatedProposalListServletTest.java | 147 +++++++++++++++++ 2 files changed, 296 insertions(+) create mode 100644 framework/src/test/java/org/tron/core/services/http/GetBlockServletTest.java create mode 100644 framework/src/test/java/org/tron/core/services/http/GetPaginatedProposalListServletTest.java diff --git a/framework/src/test/java/org/tron/core/services/http/GetBlockServletTest.java b/framework/src/test/java/org/tron/core/services/http/GetBlockServletTest.java new file mode 100644 index 00000000000..af19da08408 --- /dev/null +++ b/framework/src/test/java/org/tron/core/services/http/GetBlockServletTest.java @@ -0,0 +1,149 @@ +package org.tron.core.services.http; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import com.alibaba.fastjson.JSONObject; +import java.io.UnsupportedEncodingException; +import javax.annotation.Resource; +import org.junit.Test; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; +import org.tron.common.BaseTest; +import org.tron.common.TestConstants; +import org.tron.core.config.args.Args; + +/** + * Integration tests for GetBlockServlet. This servlet is unique in that it delegates + * both doGet and doPost to a private handle() method whose parseParams() assembles + * a PostParams value from either URL query (GET) or JSON body (POST). These tests + * exercise both sides of the parseParams fork and assert that int64_as_string is + * threaded through on each path. + */ +public class GetBlockServletTest extends BaseTest { + + @Resource + private GetBlockServlet getBlockServlet; + + static { + Args.setParam( + new String[]{ + "--output-directory", dbPath(), + }, TestConstants.TEST_CONF + ); + } + + private MockHttpServletRequest createGetRequest() { + MockHttpServletRequest request = new MockHttpServletRequest(); + request.setMethod("GET"); + return request; + } + + private MockHttpServletRequest createPostJsonRequest(String jsonBody) { + MockHttpServletRequest request = new MockHttpServletRequest(); + request.setMethod("POST"); + request.setContentType("application/json"); + request.setCharacterEncoding("UTF-8"); + request.setContent(jsonBody.getBytes()); + return request; + } + + @Test + public void testGetDefaultKeepsInt64AsNumber() { + // Default GET: parseParams reads visible/int64_as_string from URL query, both absent. + // Response int64 fields (timestamp, number) must remain unquoted. + MockHttpServletRequest request = createGetRequest(); + MockHttpServletResponse response = new MockHttpServletResponse(); + getBlockServlet.doGet(request, response); + try { + String body = response.getContentAsString(); + assertNotNull(JSONObject.parseObject(body)); + if (body.contains("\"timestamp\"")) { + assertTrue("timestamp should be unquoted by default, got: " + body, + body.matches("(?s).*\"timestamp\"\\s*:\\s*\\d+.*")); + } + } catch (UnsupportedEncodingException e) { + fail(e.getMessage()); + } + } + + @Test + public void testGetInt64AsStringFromQuery() { + // GET branch reads int64_as_string from URL query via Util.getInt64AsString(request), + // mirroring how the same branch reads visible. + MockHttpServletRequest request = createGetRequest(); + request.addParameter("int64_as_string", "true"); + MockHttpServletResponse response = new MockHttpServletResponse(); + getBlockServlet.doGet(request, response); + try { + String body = response.getContentAsString(); + assertNotNull(JSONObject.parseObject(body)); + if (body.contains("\"timestamp\"")) { + assertTrue("timestamp should be quoted when int64_as_string=true, got: " + body, + body.matches("(?s).*\"timestamp\"\\s*:\\s*\"\\d+\".*")); + } + } catch (UnsupportedEncodingException e) { + fail(e.getMessage()); + } + } + + @Test + public void testPostInt64AsStringFromBody() { + // POST branch delegates to PostParams.getPostParams which reads int64_as_string + // from the JSON body (identical semantics to the visible flag). + MockHttpServletRequest request = createPostJsonRequest( + "{\"int64_as_string\": true}"); + MockHttpServletResponse response = new MockHttpServletResponse(); + getBlockServlet.doPost(request, response); + try { + String body = response.getContentAsString(); + assertNotNull(JSONObject.parseObject(body)); + if (body.contains("\"timestamp\"")) { + assertTrue("timestamp should be quoted when body carries int64_as_string=true, got: " + + body, body.matches("(?s).*\"timestamp\"\\s*:\\s*\"\\d+\".*")); + } + } catch (UnsupportedEncodingException e) { + fail(e.getMessage()); + } + } + + @Test + public void testPostInt64AsStringFalseKeepsNumber() { + // Regression: explicitly false behaves like the default. + MockHttpServletRequest request = createPostJsonRequest( + "{\"int64_as_string\": false}"); + MockHttpServletResponse response = new MockHttpServletResponse(); + getBlockServlet.doPost(request, response); + try { + String body = response.getContentAsString(); + assertNotNull(JSONObject.parseObject(body)); + if (body.contains("\"timestamp\"")) { + assertTrue("timestamp should be unquoted when int64_as_string=false, got: " + body, + body.matches("(?s).*\"timestamp\"\\s*:\\s*\\d+.*")); + } + } catch (UnsupportedEncodingException e) { + fail(e.getMessage()); + } + } + + @Test + public void testPostIgnoresUrlQuery() { + // Mirror visible semantics: on POST, URL query parameters are NOT read; + // only the JSON body controls the flag. + MockHttpServletRequest request = createPostJsonRequest("{}"); + request.addParameter("int64_as_string", "true"); // must be ignored + MockHttpServletResponse response = new MockHttpServletResponse(); + getBlockServlet.doPost(request, response); + try { + String body = response.getContentAsString(); + if (body.contains("\"timestamp\"")) { + assertFalse("POST URL query must not leak into body-parsed flag, got: " + body, + body.matches("(?s).*\"timestamp\"\\s*:\\s*\"\\d+\".*")); + } + } catch (UnsupportedEncodingException e) { + fail(e.getMessage()); + } + } +} diff --git a/framework/src/test/java/org/tron/core/services/http/GetPaginatedProposalListServletTest.java b/framework/src/test/java/org/tron/core/services/http/GetPaginatedProposalListServletTest.java new file mode 100644 index 00000000000..bf700835a7b --- /dev/null +++ b/framework/src/test/java/org/tron/core/services/http/GetPaginatedProposalListServletTest.java @@ -0,0 +1,147 @@ +package org.tron.core.services.http; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONObject; +import java.io.UnsupportedEncodingException; +import javax.annotation.Resource; +import org.junit.Test; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; +import org.tron.common.BaseTest; +import org.tron.common.TestConstants; +import org.tron.core.config.args.Args; + +/** + * Integration tests for GetPaginatedProposalListServlet. This servlet is unique + * in the whitelist because its doPost manually reads the request body instead of + * using PostParams.getPostParams, so it takes the direct + * Util.getInt64AsStringPost(input) path rather than PostParams.isInt64AsString(). + * Proposal-list data may be empty on the test DB, so item-level assertions are + * defensive: they validate the quoting shape only if proposal_id is present. + */ +public class GetPaginatedProposalListServletTest extends BaseTest { + + @Resource + private GetPaginatedProposalListServlet servlet; + + static { + Args.setParam( + new String[]{ + "--output-directory", dbPath(), + }, TestConstants.TEST_CONF + ); + } + + private MockHttpServletRequest createGetRequest() { + MockHttpServletRequest request = new MockHttpServletRequest(); + request.setMethod("GET"); + request.addParameter("offset", "0"); + request.addParameter("limit", "10"); + return request; + } + + private MockHttpServletRequest createPostJsonRequest(String body) { + MockHttpServletRequest request = new MockHttpServletRequest(); + request.setMethod("POST"); + request.setContentType("application/json"); + request.setCharacterEncoding("UTF-8"); + request.setContent(body.getBytes()); + return request; + } + + @Test + public void testDoGetDefaultProducesValidJson() { + MockHttpServletRequest request = createGetRequest(); + MockHttpServletResponse response = new MockHttpServletResponse(); + servlet.doGet(request, response); + try { + String content = response.getContentAsString(); + assertNotNull("expected valid JSON response", + JSON.parseObject(content)); + } catch (UnsupportedEncodingException e) { + fail(e.getMessage()); + } + } + + @Test + public void testDoGetInt64AsStringQueryTrue() { + MockHttpServletRequest request = createGetRequest(); + request.addParameter("int64_as_string", "true"); + MockHttpServletResponse response = new MockHttpServletResponse(); + servlet.doGet(request, response); + try { + String content = response.getContentAsString(); + JSONObject result = JSON.parseObject(content); + assertNotNull(result); + if (content.contains("\"proposal_id\"")) { + assertTrue("proposal_id should be quoted when int64_as_string=true, got: " + content, + content.matches("(?s).*\"proposal_id\"\\s*:\\s*\"\\d+\".*")); + } + } catch (UnsupportedEncodingException e) { + fail(e.getMessage()); + } + } + + @Test + public void testDoPostReadsInt64AsStringFromBody() { + // Exercises the Util.getInt64AsStringPost(input) branch introduced because this + // servlet bypasses PostParams.getPostParams. + String bodyJson = "{\"offset\": 0, \"limit\": 10, \"int64_as_string\": true}"; + MockHttpServletRequest request = createPostJsonRequest(bodyJson); + MockHttpServletResponse response = new MockHttpServletResponse(); + servlet.doPost(request, response); + try { + String content = response.getContentAsString(); + assertNotNull(JSON.parseObject(content)); + if (content.contains("\"proposal_id\"")) { + assertTrue("proposal_id should be quoted when body carries int64_as_string=true, got: " + + content, content.matches("(?s).*\"proposal_id\"\\s*:\\s*\"\\d+\".*")); + } + } catch (UnsupportedEncodingException e) { + fail(e.getMessage()); + } + } + + @Test + public void testDoPostDefaultKeepsNumbers() { + // Regression: absence of int64_as_string preserves pre-existing behavior. + String bodyJson = "{\"offset\": 0, \"limit\": 10}"; + MockHttpServletRequest request = createPostJsonRequest(bodyJson); + MockHttpServletResponse response = new MockHttpServletResponse(); + servlet.doPost(request, response); + try { + String content = response.getContentAsString(); + assertNotNull(JSON.parseObject(content)); + if (content.contains("\"proposal_id\"")) { + assertFalse("proposal_id should be unquoted by default, got: " + content, + content.matches("(?s).*\"proposal_id\"\\s*:\\s*\"\\d+\".*")); + } + } catch (UnsupportedEncodingException e) { + fail(e.getMessage()); + } + } + + @Test + public void testDoPostIgnoresUrlQuery() { + // URL query must NOT be read when body is present (mirror visible semantics). + String bodyJson = "{\"offset\": 0, \"limit\": 10}"; + MockHttpServletRequest request = createPostJsonRequest(bodyJson); + request.addParameter("int64_as_string", "true"); // must be ignored + MockHttpServletResponse response = new MockHttpServletResponse(); + servlet.doPost(request, response); + try { + String content = response.getContentAsString(); + if (content.contains("\"proposal_id\"")) { + assertFalse("POST URL query must not leak into body-parsed flag, got: " + content, + content.matches("(?s).*\"proposal_id\"\\s*:\\s*\"\\d+\".*")); + } + } catch (UnsupportedEncodingException e) { + fail(e.getMessage()); + } + } +} From 05bf8f55f1b82779ccd71df8f9b87f2d3b5da511 Mon Sep 17 00:00:00 2001 From: waynercheung Date: Wed, 22 Apr 2026 23:56:13 +0800 Subject: [PATCH 6/9] fix(http): parse int64_as_string from POST JSON body in GetRewardServlet --- .../services/http/GetBrokerageServlet.java | 8 +-- .../core/services/http/GetRewardServlet.java | 65 +++++++++++++------ .../org/tron/core/services/http/Util.java | 39 ++++++++--- .../services/http/GetRewardServletTest.java | 63 ++++++++++++++++++ 4 files changed, 137 insertions(+), 38 deletions(-) diff --git a/framework/src/main/java/org/tron/core/services/http/GetBrokerageServlet.java b/framework/src/main/java/org/tron/core/services/http/GetBrokerageServlet.java index 1fbd94fe690..57954f9f12f 100644 --- a/framework/src/main/java/org/tron/core/services/http/GetBrokerageServlet.java +++ b/framework/src/main/java/org/tron/core/services/http/GetBrokerageServlet.java @@ -1,6 +1,5 @@ package org.tron.core.services.http; -import java.io.IOException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import lombok.extern.slf4j.Slf4j; @@ -27,12 +26,7 @@ protected void doGet(HttpServletRequest request, HttpServletResponse response) { } response.getWriter().println("{\"brokerage\": " + value + "}"); } catch (DecoderException | IllegalArgumentException e) { - try { - response.getWriter() - .println("{\"Error\": " + "\"INVALID address, " + e.getMessage() + "\"}"); - } catch (IOException ioe) { - logger.debug("IOException: {}", ioe.getMessage()); - } + Util.processAddressError(e, response); } catch (Exception e) { Util.processError(e, response); } diff --git a/framework/src/main/java/org/tron/core/services/http/GetRewardServlet.java b/framework/src/main/java/org/tron/core/services/http/GetRewardServlet.java index cfa4eb1d4b8..dbc8d2ec2de 100644 --- a/framework/src/main/java/org/tron/core/services/http/GetRewardServlet.java +++ b/framework/src/main/java/org/tron/core/services/http/GetRewardServlet.java @@ -1,9 +1,12 @@ package org.tron.core.services.http; +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONObject; import java.io.IOException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; import org.bouncycastle.util.encoders.DecoderException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @@ -19,33 +22,53 @@ public class GetRewardServlet extends RateLimiterServlet { protected void doGet(HttpServletRequest request, HttpServletResponse response) { try { - long value = 0; byte[] address = Util.getAddress(request); - if (address != null) { - value = manager.getMortgageService().queryReward(address); - } - String out = Util.getInt64AsString(request) - ? "{\"reward\": \"" + value + "\"}" - : "{\"reward\": " + value + "}"; - response.getWriter().println(out); + boolean int64AsString = Util.getInt64AsString(request); + renderReward(address, int64AsString, response); } catch (DecoderException | IllegalArgumentException e) { - try { - response.getWriter() - .println("{\"Error\": " + "\"INVALID address, " + e.getMessage() + "\"}"); - } catch (IOException ioe) { - logger.debug("IOException: {}", ioe.getMessage()); - } + Util.processAddressError(e, response); } catch (Exception e) { - logger.error("", e); - try { - response.getWriter().println(Util.printErrorMsg(e)); - } catch (IOException ioe) { - logger.debug("IOException: {}", ioe.getMessage()); - } + printGenericError(e, response); } } protected void doPost(HttpServletRequest request, HttpServletResponse response) { - doGet(request, response); + String contentType = request.getContentType(); + if (contentType == null || !contentType.contains("application/json")) { + doGet(request, response); + return; + } + try { + String body = Util.getRequestValue(request); + JSONObject json = StringUtils.isNotBlank(body) ? JSON.parseObject(body) : null; + String addressStr = (json != null) ? json.getString("address") : null; + boolean int64AsString = (json != null) + && Boolean.parseBoolean(json.getString(Util.INT64_AS_STRING)); + renderReward(Util.decodeAddress(addressStr), int64AsString, response); + } catch (DecoderException | IllegalArgumentException e) { + Util.processAddressError(e, response); + } catch (Exception e) { + printGenericError(e, response); + } + } + + private void renderReward(byte[] address, boolean int64AsString, + HttpServletResponse response) throws IOException { + long value = (address != null) + ? manager.getMortgageService().queryReward(address) + : 0L; + String out = int64AsString + ? "{\"reward\": \"" + value + "\"}" + : "{\"reward\": " + value + "}"; + response.getWriter().println(out); + } + + private void printGenericError(Exception e, HttpServletResponse response) { + logger.error("", e); + try { + response.getWriter().println(Util.printErrorMsg(e)); + } catch (IOException ioe) { + logger.debug("IOException: {}", ioe.getMessage()); + } } } diff --git a/framework/src/main/java/org/tron/core/services/http/Util.java b/framework/src/main/java/org/tron/core/services/http/Util.java index 2c0474b704c..9aa282f3f5d 100644 --- a/framework/src/main/java/org/tron/core/services/http/Util.java +++ b/framework/src/main/java/org/tron/core/services/http/Util.java @@ -511,6 +511,20 @@ public static void processError(Exception e, HttpServletResponse response) { } } + /** + * Write an "INVALID address" error JSON to the response. Used by servlets that decode an + * address from the request (e.g. GetRewardServlet, GetBrokerageServlet) to keep the error + * payload shape consistent and avoid duplicating the catch block. + */ + public static void processAddressError(Exception e, HttpServletResponse response) { + try { + response.getWriter() + .println("{\"Error\": " + "\"INVALID address, " + e.getMessage() + "\"}"); + } catch (IOException ioe) { + logger.debug("IOException: {}", ioe.getMessage()); + } + } + public static String convertOutput(Account account) { if (account.getAssetIssuedID().isEmpty()) { return JsonFormat.printToString(account, false); @@ -537,17 +551,22 @@ public static void printAccount(Account reply, HttpServletResponse response, Boo } public static byte[] getAddress(HttpServletRequest request) throws Exception { - byte[] address = null; - String addressParam = "address"; - String addressStr = checkGetParam(request, addressParam); - if (StringUtils.isNotBlank(addressStr)) { - if (StringUtils.startsWith(addressStr, Constant.ADD_PRE_FIX_STRING_MAINNET)) { - address = Hex.decode(addressStr); - } else { - address = decodeFromBase58Check(addressStr); - } + return decodeAddress(checkGetParam(request, "address")); + } + + /** + * Decode an address string (hex with mainnet prefix, or base58check) to raw bytes. + * Returns null for blank input. Shared by {@link #getAddress(HttpServletRequest)} and by + * servlets that parse the address from a manually-read JSON body (e.g. GetRewardServlet). + */ + public static byte[] decodeAddress(String addressStr) { + if (StringUtils.isBlank(addressStr)) { + return null; + } + if (StringUtils.startsWith(addressStr, Constant.ADD_PRE_FIX_STRING_MAINNET)) { + return Hex.decode(addressStr); } - return address; + return decodeFromBase58Check(addressStr); } private static String checkGetParam(HttpServletRequest request, String key) throws Exception { diff --git a/framework/src/test/java/org/tron/core/services/http/GetRewardServletTest.java b/framework/src/test/java/org/tron/core/services/http/GetRewardServletTest.java index 9d84c0a949f..b5c18bb7252 100644 --- a/framework/src/test/java/org/tron/core/services/http/GetRewardServletTest.java +++ b/framework/src/test/java/org/tron/core/services/http/GetRewardServletTest.java @@ -191,4 +191,67 @@ public void getRewardDefaultStillReturnsNumber() { } } + @Test + public void postJsonBodyInt64AsStringReadFromBody() { + String jsonParam = "{\"address\": \"TNboetpFgv9SqMoHvaVt626NLXETnbdW1K\"," + + " \"int64_as_string\": true}"; + MockHttpServletRequest request = createRequest("application/json"); + MockHttpServletResponse response = new MockHttpServletResponse(); + request.setContent(jsonParam.getBytes()); + getRewardServlet.doPost(request, response); + try { + String contentAsString = response.getContentAsString(); + JSONObject result = JSONObject.parseObject(contentAsString); + Object reward = result.get("reward"); + Assert.assertNotNull("response should contain reward, got: " + contentAsString, reward); + Assert.assertTrue( + "reward should be a String when body carries int64_as_string=true, got: " + + contentAsString, reward instanceof String); + } catch (UnsupportedEncodingException e) { + Assert.fail(e.getMessage()); + } + } + + @Test + public void postJsonBodyInt64AsStringFalseKeepsNumber() { + String jsonParam = "{\"address\": \"TNboetpFgv9SqMoHvaVt626NLXETnbdW1K\"," + + " \"int64_as_string\": false}"; + MockHttpServletRequest request = createRequest("application/json"); + MockHttpServletResponse response = new MockHttpServletResponse(); + request.setContent(jsonParam.getBytes()); + getRewardServlet.doPost(request, response); + try { + String contentAsString = response.getContentAsString(); + JSONObject result = JSONObject.parseObject(contentAsString); + Object reward = result.get("reward"); + Assert.assertNotNull("response should contain reward, got: " + contentAsString, reward); + Assert.assertFalse( + "reward should remain a number when body carries int64_as_string=false, got: " + + contentAsString, reward instanceof String); + } catch (UnsupportedEncodingException e) { + Assert.fail(e.getMessage()); + } + } + + @Test + public void postJsonBodyIgnoresUrlQueryFlag() { + String jsonParam = "{\"address\": \"TNboetpFgv9SqMoHvaVt626NLXETnbdW1K\"}"; + MockHttpServletRequest request = createRequest("application/json"); + request.addParameter("int64_as_string", "true"); // URL query on a JSON POST must be ignored + MockHttpServletResponse response = new MockHttpServletResponse(); + request.setContent(jsonParam.getBytes()); + getRewardServlet.doPost(request, response); + try { + String contentAsString = response.getContentAsString(); + JSONObject result = JSONObject.parseObject(contentAsString); + Object reward = result.get("reward"); + Assert.assertNotNull("response should contain reward, got: " + contentAsString, reward); + Assert.assertFalse( + "URL query flag must not leak into JSON body path, got: " + contentAsString, + reward instanceof String); + } catch (UnsupportedEncodingException e) { + Assert.fail(e.getMessage()); + } + } + } From 77ea7313408691d496be26048b713ff9f398c894 Mon Sep 17 00:00:00 2001 From: waynercheung Date: Thu, 23 Apr 2026 00:27:14 +0800 Subject: [PATCH 7/9] feat(http): wire int64_as_string into 10 additional query servlets A later review of FullNodeHttpApiService surfaced these query servlets as also returning int64 fields: getproposalbyid, getexchangebyid, getcontract, getcontractinfo, getmarketorderbyaccount, getmarketorderbyid, getmarketorderlistbypair, getmarketpricebypair, getblockbalance, getnextmaintenancetime. --- .../services/http/GetBlockBalanceServlet.java | 4 +++- .../services/http/GetContractInfoServlet.java | 17 +++++++++++------ .../core/services/http/GetContractServlet.java | 17 +++++++++++------ .../services/http/GetExchangeByIdServlet.java | 9 +++++++-- .../http/GetMarketOrderByAccountServlet.java | 9 +++++++-- .../http/GetMarketOrderByIdServlet.java | 10 ++++++++-- .../http/GetMarketOrderListByPairServlet.java | 14 ++++++++++---- .../http/GetMarketPriceByPairServlet.java | 14 ++++++++++---- .../http/GetNextMaintenanceTimeServlet.java | 5 ++++- .../services/http/GetProposalByIdServlet.java | 9 +++++++-- 10 files changed, 78 insertions(+), 30 deletions(-) diff --git a/framework/src/main/java/org/tron/core/services/http/GetBlockBalanceServlet.java b/framework/src/main/java/org/tron/core/services/http/GetBlockBalanceServlet.java index 0fc3256899c..2df93905338 100644 --- a/framework/src/main/java/org/tron/core/services/http/GetBlockBalanceServlet.java +++ b/framework/src/main/java/org/tron/core/services/http/GetBlockBalanceServlet.java @@ -24,7 +24,9 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response) BlockBalanceTrace.BlockIdentifier.Builder builder = BlockBalanceTrace.BlockIdentifier .newBuilder(); JsonFormat.merge(params.getParams(), builder, params.isVisible()); - fillResponse(params.isVisible(), builder.build(), response); + try (AutoCloseable ignored = JsonFormat.pushInt64AsString(params.isInt64AsString())) { + fillResponse(params.isVisible(), builder.build(), response); + } } catch (Exception e) { Util.processError(e, response); } diff --git a/framework/src/main/java/org/tron/core/services/http/GetContractInfoServlet.java b/framework/src/main/java/org/tron/core/services/http/GetContractInfoServlet.java index 86ed7f6d9b6..33aa1064265 100644 --- a/framework/src/main/java/org/tron/core/services/http/GetContractInfoServlet.java +++ b/framework/src/main/java/org/tron/core/services/http/GetContractInfoServlet.java @@ -25,6 +25,7 @@ public class GetContractInfoServlet extends RateLimiterServlet { protected void doGet(HttpServletRequest request, HttpServletResponse response) { try { boolean visible = Util.getVisible(request); + boolean int64AsString = Util.getInt64AsString(request); String input = request.getParameter(VALUE); if (visible) { input = Util.getHexAddress(input); @@ -39,9 +40,11 @@ protected void doGet(HttpServletRequest request, HttpServletResponse response) { if (smartContract == null) { response.getWriter().println("{}"); } else { - JSONObject jsonSmartContract = JSONObject - .parseObject(JsonFormat.printToString(smartContract, visible)); - response.getWriter().println(jsonSmartContract.toJSONString()); + try (AutoCloseable ignored = JsonFormat.pushInt64AsString(int64AsString)) { + JSONObject jsonSmartContract = JSONObject + .parseObject(JsonFormat.printToString(smartContract, visible)); + response.getWriter().println(jsonSmartContract.toJSONString()); + } } } catch (Exception e) { Util.processError(e, response); @@ -67,9 +70,11 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response) if (smartContract == null) { response.getWriter().println("{}"); } else { - JSONObject jsonSmartContract = JSONObject - .parseObject(JsonFormat.printToString(smartContract, visible)); - response.getWriter().println(jsonSmartContract.toJSONString()); + try (AutoCloseable ignored = JsonFormat.pushInt64AsString(params.isInt64AsString())) { + JSONObject jsonSmartContract = JSONObject + .parseObject(JsonFormat.printToString(smartContract, visible)); + response.getWriter().println(jsonSmartContract.toJSONString()); + } } } catch (Exception e) { Util.processError(e, response); diff --git a/framework/src/main/java/org/tron/core/services/http/GetContractServlet.java b/framework/src/main/java/org/tron/core/services/http/GetContractServlet.java index b9efd6c1520..7c0a51303da 100644 --- a/framework/src/main/java/org/tron/core/services/http/GetContractServlet.java +++ b/framework/src/main/java/org/tron/core/services/http/GetContractServlet.java @@ -23,6 +23,7 @@ public class GetContractServlet extends RateLimiterServlet { protected void doGet(HttpServletRequest request, HttpServletResponse response) { try { boolean visible = Util.getVisible(request); + boolean int64AsString = Util.getInt64AsString(request); String input = request.getParameter(S_VALUE); if (visible) { input = Util.getHexAddress(input); @@ -37,9 +38,11 @@ protected void doGet(HttpServletRequest request, HttpServletResponse response) { if (smartContract == null) { response.getWriter().println("{}"); } else { - JSONObject jsonSmartContract = JSONObject - .parseObject(JsonFormat.printToString(smartContract, visible)); - response.getWriter().println(jsonSmartContract.toJSONString()); + try (AutoCloseable ignored = JsonFormat.pushInt64AsString(int64AsString)) { + JSONObject jsonSmartContract = JSONObject + .parseObject(JsonFormat.printToString(smartContract, visible)); + response.getWriter().println(jsonSmartContract.toJSONString()); + } } } catch (Exception e) { Util.processError(e, response); @@ -65,9 +68,11 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response) if (smartContract == null) { response.getWriter().println("{}"); } else { - JSONObject jsonSmartContract = JSONObject - .parseObject(JsonFormat.printToString(smartContract, visible)); - response.getWriter().println(jsonSmartContract.toJSONString()); + try (AutoCloseable ignored = JsonFormat.pushInt64AsString(params.isInt64AsString())) { + JSONObject jsonSmartContract = JSONObject + .parseObject(JsonFormat.printToString(smartContract, visible)); + response.getWriter().println(jsonSmartContract.toJSONString()); + } } } catch (Exception e) { Util.processError(e, response); diff --git a/framework/src/main/java/org/tron/core/services/http/GetExchangeByIdServlet.java b/framework/src/main/java/org/tron/core/services/http/GetExchangeByIdServlet.java index 7a84c0ea8a4..2794a036ba0 100644 --- a/framework/src/main/java/org/tron/core/services/http/GetExchangeByIdServlet.java +++ b/framework/src/main/java/org/tron/core/services/http/GetExchangeByIdServlet.java @@ -25,7 +25,9 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response) PostParams params = PostParams.getPostParams(request); JSONObject jsonObject = JSONObject.parseObject(params.getParams()); long id = Util.getJsonLongValue(jsonObject, "id", true); - fillResponse(params.isVisible(), id, response); + try (AutoCloseable ignored = JsonFormat.pushInt64AsString(params.isInt64AsString())) { + fillResponse(params.isVisible(), id, response); + } } catch (Exception e) { Util.processError(e, response); } @@ -34,8 +36,11 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response) protected void doGet(HttpServletRequest request, HttpServletResponse response) { try { boolean visible = Util.getVisible(request); + boolean int64AsString = Util.getInt64AsString(request); String input = request.getParameter("id"); - fillResponse(visible, Long.parseLong(input), response); + try (AutoCloseable ignored = JsonFormat.pushInt64AsString(int64AsString)) { + fillResponse(visible, Long.parseLong(input), response); + } } catch (Exception e) { Util.processError(e, response); } diff --git a/framework/src/main/java/org/tron/core/services/http/GetMarketOrderByAccountServlet.java b/framework/src/main/java/org/tron/core/services/http/GetMarketOrderByAccountServlet.java index 1c3190b62ea..110825c38da 100644 --- a/framework/src/main/java/org/tron/core/services/http/GetMarketOrderByAccountServlet.java +++ b/framework/src/main/java/org/tron/core/services/http/GetMarketOrderByAccountServlet.java @@ -40,9 +40,12 @@ protected void getResult(String address, boolean visible, HttpServletResponse re protected void doGet(HttpServletRequest request, HttpServletResponse response) { try { boolean visible = Util.getVisible(request); + boolean int64AsString = Util.getInt64AsString(request); String address = request.getParameter("value"); - getResult(address, visible, response); + try (AutoCloseable ignored = JsonFormat.pushInt64AsString(int64AsString)) { + getResult(address, visible, response); + } } catch (Exception e) { Util.processError(e, response); } @@ -55,7 +58,9 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response) JSONObject jsonObject = JSON.parseObject(params.getParams()); String value = jsonObject.getString("value"); - getResult(value, params.isVisible(), response); + try (AutoCloseable ignored = JsonFormat.pushInt64AsString(params.isInt64AsString())) { + getResult(value, params.isVisible(), response); + } } catch (Exception e) { Util.processError(e, response); } diff --git a/framework/src/main/java/org/tron/core/services/http/GetMarketOrderByIdServlet.java b/framework/src/main/java/org/tron/core/services/http/GetMarketOrderByIdServlet.java index bdb8a4b6d3c..aa374822eae 100644 --- a/framework/src/main/java/org/tron/core/services/http/GetMarketOrderByIdServlet.java +++ b/framework/src/main/java/org/tron/core/services/http/GetMarketOrderByIdServlet.java @@ -23,11 +23,14 @@ public class GetMarketOrderByIdServlet extends RateLimiterServlet { protected void doGet(HttpServletRequest request, HttpServletResponse response) { try { boolean visible = Util.getVisible(request); + boolean int64AsString = Util.getInt64AsString(request); String orderId = request.getParameter("value"); MarketOrder reply = wallet .getMarketOrderById(ByteString.copyFrom(ByteArray.fromHexString(orderId))); if (reply != null) { - response.getWriter().println(JsonFormat.printToString(reply, visible)); + try (AutoCloseable ignored = JsonFormat.pushInt64AsString(int64AsString)) { + response.getWriter().println(JsonFormat.printToString(reply, visible)); + } } else { response.getWriter().println("{}"); } @@ -43,12 +46,15 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response) .collect(Collectors.joining(System.lineSeparator())); Util.checkBodySize(input); boolean visible = Util.getVisiblePost(input); + boolean int64AsString = Util.getInt64AsStringPost(input); BytesMessage.Builder build = BytesMessage.newBuilder(); JsonFormat.merge(input, build, visible); MarketOrder reply = wallet.getMarketOrderById(build.getValue()); if (reply != null) { - response.getWriter().println(JsonFormat.printToString(reply, visible)); + try (AutoCloseable ignored = JsonFormat.pushInt64AsString(int64AsString)) { + response.getWriter().println(JsonFormat.printToString(reply, visible)); + } } else { response.getWriter().println("{}"); } diff --git a/framework/src/main/java/org/tron/core/services/http/GetMarketOrderListByPairServlet.java b/framework/src/main/java/org/tron/core/services/http/GetMarketOrderListByPairServlet.java index 963ea880033..3a39860dbc0 100644 --- a/framework/src/main/java/org/tron/core/services/http/GetMarketOrderListByPairServlet.java +++ b/framework/src/main/java/org/tron/core/services/http/GetMarketOrderListByPairServlet.java @@ -22,6 +22,7 @@ public class GetMarketOrderListByPairServlet extends RateLimiterServlet { protected void doGet(HttpServletRequest request, HttpServletResponse response) { try { boolean visible = Util.getVisible(request); + boolean int64AsString = Util.getInt64AsString(request); String sellTokenId = request.getParameter("sell_token_id"); String buyTokenId = request.getParameter("buy_token_id"); @@ -31,8 +32,10 @@ protected void doGet(HttpServletRequest request, HttpServletResponse response) { buyTokenId = Util.getHexString(buyTokenId); } - fillResponse(visible, ByteArray.fromHexString(sellTokenId), - ByteArray.fromHexString(buyTokenId), response); + try (AutoCloseable ignored = JsonFormat.pushInt64AsString(int64AsString)) { + fillResponse(visible, ByteArray.fromHexString(sellTokenId), + ByteArray.fromHexString(buyTokenId), response); + } } catch (Exception e) { Util.processError(e, response); } @@ -44,12 +47,15 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response) .collect(Collectors.joining(System.lineSeparator())); Util.checkBodySize(input); boolean visible = Util.getVisiblePost(input); + boolean int64AsString = Util.getInt64AsStringPost(input); MarketOrderPair.Builder build = MarketOrderPair.newBuilder(); JsonFormat.merge(input, build, visible); - fillResponse(visible, build.getSellTokenId().toByteArray(), - build.getBuyTokenId().toByteArray(), response); + try (AutoCloseable ignored = JsonFormat.pushInt64AsString(int64AsString)) { + fillResponse(visible, build.getSellTokenId().toByteArray(), + build.getBuyTokenId().toByteArray(), response); + } } catch (Exception e) { Util.processError(e, response); } diff --git a/framework/src/main/java/org/tron/core/services/http/GetMarketPriceByPairServlet.java b/framework/src/main/java/org/tron/core/services/http/GetMarketPriceByPairServlet.java index b6d31e10936..4b9658de9fb 100644 --- a/framework/src/main/java/org/tron/core/services/http/GetMarketPriceByPairServlet.java +++ b/framework/src/main/java/org/tron/core/services/http/GetMarketPriceByPairServlet.java @@ -22,6 +22,7 @@ public class GetMarketPriceByPairServlet extends RateLimiterServlet { protected void doGet(HttpServletRequest request, HttpServletResponse response) { try { boolean visible = Util.getVisible(request); + boolean int64AsString = Util.getInt64AsString(request); String sellTokenId = request.getParameter("sell_token_id"); String buyTokenId = request.getParameter("buy_token_id"); @@ -31,8 +32,10 @@ protected void doGet(HttpServletRequest request, HttpServletResponse response) { buyTokenId = Util.getHexString(buyTokenId); } - fillResponse(visible, ByteArray.fromHexString(sellTokenId), - ByteArray.fromHexString(buyTokenId), response); + try (AutoCloseable ignored = JsonFormat.pushInt64AsString(int64AsString)) { + fillResponse(visible, ByteArray.fromHexString(sellTokenId), + ByteArray.fromHexString(buyTokenId), response); + } } catch (Exception e) { Util.processError(e, response); } @@ -44,12 +47,15 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response) .collect(Collectors.joining(System.lineSeparator())); Util.checkBodySize(input); boolean visible = Util.getVisiblePost(input); + boolean int64AsString = Util.getInt64AsStringPost(input); MarketOrderPair.Builder build = MarketOrderPair.newBuilder(); JsonFormat.merge(input, build, visible); - fillResponse(visible, build.getSellTokenId().toByteArray(), - build.getBuyTokenId().toByteArray(), response); + try (AutoCloseable ignored = JsonFormat.pushInt64AsString(int64AsString)) { + fillResponse(visible, build.getSellTokenId().toByteArray(), + build.getBuyTokenId().toByteArray(), response); + } } catch (Exception e) { Util.processError(e, response); } diff --git a/framework/src/main/java/org/tron/core/services/http/GetNextMaintenanceTimeServlet.java b/framework/src/main/java/org/tron/core/services/http/GetNextMaintenanceTimeServlet.java index c93244f5782..07f57143d2a 100644 --- a/framework/src/main/java/org/tron/core/services/http/GetNextMaintenanceTimeServlet.java +++ b/framework/src/main/java/org/tron/core/services/http/GetNextMaintenanceTimeServlet.java @@ -19,9 +19,12 @@ public class GetNextMaintenanceTimeServlet extends RateLimiterServlet { protected void doGet(HttpServletRequest request, HttpServletResponse response) { try { boolean visible = Util.getVisible(request); + boolean int64AsString = Util.getInt64AsString(request); NumberMessage reply = wallet.getNextMaintenanceTime(); if (reply != null) { - response.getWriter().println(JsonFormat.printToString(reply, visible)); + try (AutoCloseable ignored = JsonFormat.pushInt64AsString(int64AsString)) { + response.getWriter().println(JsonFormat.printToString(reply, visible)); + } } else { response.getWriter().println("{}"); } diff --git a/framework/src/main/java/org/tron/core/services/http/GetProposalByIdServlet.java b/framework/src/main/java/org/tron/core/services/http/GetProposalByIdServlet.java index 9d7805d4f98..9a013c82f2a 100644 --- a/framework/src/main/java/org/tron/core/services/http/GetProposalByIdServlet.java +++ b/framework/src/main/java/org/tron/core/services/http/GetProposalByIdServlet.java @@ -25,9 +25,12 @@ public class GetProposalByIdServlet extends RateLimiterServlet { protected void doGet(HttpServletRequest request, HttpServletResponse response) { try { boolean visible = Util.getVisible(request); + boolean int64AsString = Util.getInt64AsString(request); String input = request.getParameter("id"); long id = Long.parseLong(input); - fillResponse(ByteString.copyFrom(ByteArray.fromLong(id)), visible, response); + try (AutoCloseable ignored = JsonFormat.pushInt64AsString(int64AsString)) { + fillResponse(ByteString.copyFrom(ByteArray.fromLong(id)), visible, response); + } } catch (Exception e) { Util.processError(e, response); } @@ -38,7 +41,9 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response) PostParams params = PostParams.getPostParams(request); JSONObject jsonObject = JSONObject.parseObject(params.getParams()); long id = Util.getJsonLongValue(jsonObject, "id", true); - fillResponse(ByteString.copyFrom(ByteArray.fromLong(id)), params.isVisible(), response); + try (AutoCloseable ignored = JsonFormat.pushInt64AsString(params.isInt64AsString())) { + fillResponse(ByteString.copyFrom(ByteArray.fromLong(id)), params.isVisible(), response); + } } catch (Exception e) { Util.processError(e, response); } From 7ba22ba6013abeb68f34b6d6c3eb5ac373b982a4 Mon Sep 17 00:00:00 2001 From: waynercheung Date: Thu, 23 Apr 2026 11:24:52 +0800 Subject: [PATCH 8/9] test(http): cover Util helpers and drop decorative dividers Cover Util.decodeAddress (blank-input and mainnet-hex branches) and Util.processAddressError in UtilMockTest; both were previously exercised only indirectly. --- .../http/JsonFormatInt64AsStringTest.java | 20 ------------ .../tron/core/services/http/UtilMockTest.java | 32 +++++++++++++++++++ 2 files changed, 32 insertions(+), 20 deletions(-) diff --git a/framework/src/test/java/org/tron/core/services/http/JsonFormatInt64AsStringTest.java b/framework/src/test/java/org/tron/core/services/http/JsonFormatInt64AsStringTest.java index 4b1817342bc..bbf665dfc2e 100644 --- a/framework/src/test/java/org/tron/core/services/http/JsonFormatInt64AsStringTest.java +++ b/framework/src/test/java/org/tron/core/services/http/JsonFormatInt64AsStringTest.java @@ -32,8 +32,6 @@ public void clearState() throws Exception { } } - // ---------- default behavior ---------- - @Test public void defaultBehaviorUnchangedWithoutPush() { Protocol.Account account = Protocol.Account.newBuilder() @@ -48,8 +46,6 @@ public void defaultBehaviorUnchangedWithoutPush() { || out.contains("\"balance\": \"123456789012345\"")); } - // ---------- int64 / uint64 field quoting ---------- - @Test public void int64FieldQuotedInScope() throws Exception { Protocol.Account account = Protocol.Account.newBuilder() @@ -82,8 +78,6 @@ public void uint64DefaultUnquoted() { out.contains("\"9007199254740993\"")); } - // ---------- non-int64 fields should not be affected ---------- - @Test public void stringBytesEnumNotAffected() throws Exception { // Note: proto3 does not serialize default-valued fields, so enum/bytes fields are @@ -105,8 +99,6 @@ public void stringBytesEnumNotAffected() throws Exception { } } - // ---------- nested / repeated / map ---------- - @Test public void nestedInt64FieldsQuoted() throws Exception { Protocol.Block block = Protocol.Block.newBuilder() @@ -138,8 +130,6 @@ public void mapStringInt64ValuesQuoted() throws Exception { } } - // ---------- boundary values ---------- - @Test public void boundaryValuesAllQuoted() throws Exception { // Note: proto3 does not serialize a field whose value equals its type default (0 for int64), @@ -163,8 +153,6 @@ public void boundaryValuesAllQuoted() throws Exception { } } - // ---------- lifecycle ---------- - @Test public void stateClearedAfterScope() throws Exception { Protocol.Account account = Protocol.Account.newBuilder().setBalance(1L).build(); @@ -207,8 +195,6 @@ public void nestedScopesRestorePreviousValue() throws Exception { out.contains("\"1\"")); } - // ---------- concurrency ---------- - @Test public void threadIsolation() throws Exception { final Protocol.Account account = Protocol.Account.newBuilder().setBalance(1L).build(); @@ -260,8 +246,6 @@ public void noPollutionOnThreadReuse() throws Exception { } } - // ---------- helper close() idempotency (defensive) ---------- - @Test public void helperCanBeClosedExplicitly() throws Exception { Protocol.Account account = Protocol.Account.newBuilder().setBalance(1L).build(); @@ -277,8 +261,6 @@ public void helperCanBeClosedExplicitly() throws Exception { outside.contains("\"1\"")); } - // ---------- adapter: 2-arg PostParams constructor ---------- - @Test public void legacyPostParamsConstructorDefaultsFalse() { PostParams params = new PostParams("{}", true); @@ -293,8 +275,6 @@ public void threeArgPostParamsConstructorHonorsFlag() { assertEquals(true, params.isInt64AsString()); } - // ---------- Util.getInt64AsStringPost (body parse, mirrors getVisiblePost) ---------- - @Test public void getInt64AsStringPostDefaultsFalseWhenAbsent() { assertEquals(false, Util.getInt64AsStringPost("{\"address\":\"T123\"}")); diff --git a/framework/src/test/java/org/tron/core/services/http/UtilMockTest.java b/framework/src/test/java/org/tron/core/services/http/UtilMockTest.java index 221c5a7a165..351bb89a408 100644 --- a/framework/src/test/java/org/tron/core/services/http/UtilMockTest.java +++ b/framework/src/test/java/org/tron/core/services/http/UtilMockTest.java @@ -12,6 +12,7 @@ import org.junit.Assert; import org.junit.Test; import org.mockito.Mockito; +import org.springframework.mock.web.MockHttpServletResponse; import org.tron.api.GrpcAPI; import org.tron.common.utils.Sha256Hash; import org.tron.core.capsule.BlockCapsule; @@ -240,4 +241,35 @@ public void testGetJsonString() { Assert.assertEquals(expect, ret2); } + @Test + public void testDecodeAddressBlankInputReturnsNull() { + Assert.assertNull(Util.decodeAddress(null)); + Assert.assertNull(Util.decodeAddress("")); + Assert.assertNull(Util.decodeAddress(" ")); + } + + @Test + public void testDecodeAddressMainnetHexPrefix() { + // Mainnet prefix "41" + 40 hex chars => 21-byte address, first byte 0x41. + String hex = "41e552f6487585c2b58bc2c9bb4492bc1f17132cd0"; + byte[] result = Util.decodeAddress(hex); + Assert.assertNotNull(result); + Assert.assertEquals(21, result.length); + Assert.assertEquals((byte) 0x41, result[0]); + } + + @Test + public void testProcessAddressErrorWritesExpectedJson() throws Exception { + MockHttpServletResponse response = new MockHttpServletResponse(); + Util.processAddressError(new IllegalArgumentException("bad hex"), response); + String content = response.getContentAsString().trim(); + JSONObject json = JSONObject.parseObject(content); + String error = json.getString("Error"); + Assert.assertNotNull("response must contain \"Error\" field: " + content, error); + Assert.assertTrue("error message should start with INVALID address, got: " + error, + error.startsWith("INVALID address")); + Assert.assertTrue("error message should include the exception detail, got: " + error, + error.contains("bad hex")); + } + } \ No newline at end of file From 96dfa86851130dfb51fcdc300527da031a276e3d Mon Sep 17 00:00:00 2001 From: waynercheung Date: Thu, 23 Apr 2026 11:33:52 +0800 Subject: [PATCH 9/9] test(http): pin the URL-query-only contract of ListExchangesServlet 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. --- .../http/ListExchangesServletTest.java | 111 ++++++++++++++++++ 1 file changed, 111 insertions(+) create mode 100644 framework/src/test/java/org/tron/core/services/http/ListExchangesServletTest.java diff --git a/framework/src/test/java/org/tron/core/services/http/ListExchangesServletTest.java b/framework/src/test/java/org/tron/core/services/http/ListExchangesServletTest.java new file mode 100644 index 00000000000..88c06a13ffd --- /dev/null +++ b/framework/src/test/java/org/tron/core/services/http/ListExchangesServletTest.java @@ -0,0 +1,111 @@ +package org.tron.core.services.http; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONObject; +import java.io.UnsupportedEncodingException; +import javax.annotation.Resource; +import org.junit.Test; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; +import org.tron.common.BaseTest; +import org.tron.common.TestConstants; +import org.tron.core.config.args.Args; + +/** + * Integration tests for ListExchangesServlet. This servlet is the canonical example of the + * "doGet-delegates-to-doPost, doPost reads URL query (not body)" shape: its doPost calls + * Util.getVisible(request) and Util.getInt64AsString(request) instead of PostParams. The + * tests below pin that contract so a future refactor that "normalizes" the POST path to + * read from the body (as most other servlets do) will fail visibly rather than silently + * breaking clients that pass the flag via URL query. + * + *

ExchangeList may be empty on the test DB, so item-level assertions are defensive: + * they verify int64 quoting shape only when exchange_id is present in the response. + */ +public class ListExchangesServletTest extends BaseTest { + + @Resource(name = "listExchangesServlet") + private ListExchangesServlet servlet; + + static { + Args.setParam( + new String[]{ + "--output-directory", dbPath(), + }, TestConstants.TEST_CONF + ); + } + + private MockHttpServletRequest createPostJsonRequest(String body) { + MockHttpServletRequest request = new MockHttpServletRequest(); + request.setMethod("POST"); + request.setContentType("application/json"); + request.setCharacterEncoding("UTF-8"); + if (body != null) { + request.setContent(body.getBytes()); + } + return request; + } + + @Test + public void testPostWithQueryInt64AsStringTrue() { + MockHttpServletRequest request = createPostJsonRequest(null); + request.addParameter("int64_as_string", "true"); + MockHttpServletResponse response = new MockHttpServletResponse(); + servlet.doPost(request, response); + try { + String content = response.getContentAsString(); + assertNotNull("expected valid JSON, got: " + content, JSON.parseObject(content)); + if (content.contains("\"exchange_id\"")) { + assertTrue("exchange_id should be quoted when URL query carries int64_as_string=true, " + + "got: " + content, + content.matches("(?s).*\"exchange_id\"\\s*:\\s*\"\\d+\".*")); + } + } catch (UnsupportedEncodingException e) { + fail(e.getMessage()); + } + } + + @Test + public void testPostBodyFlagIsIgnored() { + // Contract: ListExchangesServlet.doPost reads only URL query, mirroring how the same + // servlet reads `visible`. A body-only flag must NOT take effect. Breaking this + // behavior would be a silent regression for clients that pass the flag via URL. + String body = "{\"int64_as_string\": true}"; + MockHttpServletRequest request = createPostJsonRequest(body); + MockHttpServletResponse response = new MockHttpServletResponse(); + servlet.doPost(request, response); + try { + String content = response.getContentAsString(); + assertNotNull("expected valid JSON, got: " + content, JSON.parseObject(content)); + if (content.contains("\"exchange_id\"")) { + assertFalse("body-only int64_as_string must be ignored on this servlet, got: " + content, + content.matches("(?s).*\"exchange_id\"\\s*:\\s*\"\\d+\".*")); + } + } catch (UnsupportedEncodingException e) { + fail(e.getMessage()); + } + } + + @Test + public void testPostDefaultKeepsNumbers() { + MockHttpServletRequest request = createPostJsonRequest(null); + MockHttpServletResponse response = new MockHttpServletResponse(); + servlet.doPost(request, response); + try { + String content = response.getContentAsString(); + JSONObject result = JSON.parseObject(content); + assertNotNull("expected valid JSON, got: " + content, result); + if (content.contains("\"exchange_id\"")) { + assertFalse("default behavior must not quote exchange_id, got: " + content, + content.matches("(?s).*\"exchange_id\"\\s*:\\s*\"\\d+\".*")); + } + } catch (UnsupportedEncodingException e) { + fail(e.getMessage()); + } + } +}