From 50496d5a05d21399e2be7907f8f5fc8d9a1587d4 Mon Sep 17 00:00:00 2001 From: bladehan1 Date: Thu, 19 Mar 2026 15:21:24 +0800 Subject: [PATCH 1/6] feat(http): add Jetty SizeLimitHandler to enforce request body size limit Add SizeLimitHandler at the Jetty server level to reject oversized request bodies before they are fully buffered into memory. This prevents OOM attacks via arbitrarily large HTTP payloads that bypass the existing application-level Util.checkBodySize() check (which reads the entire body first) and the JSON-RPC interface (which had no size validation). --- .../tron/common/application/HttpService.java | 6 +- .../org/tron/core/services/http/Util.java | 1 + .../common/jetty/SizeLimitHandlerTest.java | 139 ++++++++++++++++++ 3 files changed, 145 insertions(+), 1 deletion(-) create mode 100644 framework/src/test/java/org/tron/common/jetty/SizeLimitHandlerTest.java diff --git a/framework/src/main/java/org/tron/common/application/HttpService.java b/framework/src/main/java/org/tron/common/application/HttpService.java index e9a902002ba..f3428d37fdb 100644 --- a/framework/src/main/java/org/tron/common/application/HttpService.java +++ b/framework/src/main/java/org/tron/common/application/HttpService.java @@ -19,6 +19,7 @@ import lombok.extern.slf4j.Slf4j; import org.eclipse.jetty.server.ConnectionLimit; import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.handler.SizeLimitHandler; import org.eclipse.jetty.servlet.ServletContextHandler; import org.tron.core.config.args.Args; @@ -63,7 +64,10 @@ protected void initServer() { protected ServletContextHandler initContextHandler() { ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS); context.setContextPath(this.contextPath); - this.apiServer.setHandler(context); + int maxMessageSize = Args.getInstance().getMaxMessageSize(); + SizeLimitHandler sizeLimitHandler = new SizeLimitHandler(maxMessageSize, -1); + sizeLimitHandler.setHandler(context); + this.apiServer.setHandler(sizeLimitHandler); return context; } 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..f112e13c818 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 @@ -327,6 +327,7 @@ public static Transaction packTransaction(String strTransaction, boolean selfTyp } } + @Deprecated public static void checkBodySize(String body) throws Exception { CommonParameter parameter = Args.getInstance(); if (body.getBytes().length > parameter.getMaxMessageSize()) { diff --git a/framework/src/test/java/org/tron/common/jetty/SizeLimitHandlerTest.java b/framework/src/test/java/org/tron/common/jetty/SizeLimitHandlerTest.java new file mode 100644 index 00000000000..d06f86963bf --- /dev/null +++ b/framework/src/test/java/org/tron/common/jetty/SizeLimitHandlerTest.java @@ -0,0 +1,139 @@ +package org.tron.common.jetty; + +import com.google.common.io.ByteStreams; +import java.io.IOException; +import java.net.URI; +import java.nio.file.Files; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import lombok.extern.slf4j.Slf4j; +import org.apache.http.HttpEntity; +import org.apache.http.HttpResponse; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.entity.StringEntity; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClients; +import org.apache.http.util.EntityUtils; +import org.eclipse.jetty.servlet.ServletContextHandler; +import org.eclipse.jetty.servlet.ServletHolder; +import org.junit.AfterClass; +import org.junit.Assert; +import org.junit.BeforeClass; +import org.junit.Test; +import org.tron.common.TestConstants; +import org.tron.common.application.HttpService; +import org.tron.common.utils.PublicMethod; +import org.tron.core.config.args.Args; + +/** + * Tests the {@link org.eclipse.jetty.server.handler.SizeLimitHandler} body-size + * enforcement configured in {@link HttpService initContextHandler()}. + * + *

Unlike a standalone Jetty micro-server, this test creates a concrete + * {@link HttpService} subclass and calls {@link HttpService#start()}, so the + * production code paths of {@code initServer()} and {@code initContextHandler()} + * are exercised directly and reflected in code-coverage reports.

+ * + *

Key behaviours proven:

+ * + */ +@Slf4j +public class SizeLimitHandlerTest { + + private static final int MAX_BODY_SIZE = 1024; + + private static TestHttpService httpService; + private static URI serverUri; + private static CloseableHttpClient client; + + /** Echoes the raw request-body bytes back so tests can inspect what arrived. */ + public static class EchoServlet extends HttpServlet { + @Override + protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException { + byte[] body = ByteStreams.toByteArray(req.getInputStream()); + resp.setStatus(HttpServletResponse.SC_OK); + resp.setContentType("application/octet-stream"); + resp.getOutputStream().write(body); + } + } + + /** Minimal concrete {@link HttpService} that registers only an {@link EchoServlet}. */ + static class TestHttpService extends HttpService { + TestHttpService(int port) { + this.port = port; + this.contextPath = "/"; + } + + @Override + protected void addServlet(ServletContextHandler context) { + context.addServlet(new ServletHolder(new EchoServlet()), "/*"); + } + } + + /** + * Initialises {@link Args} and starts a real {@link HttpService} whose + * {@code initServer()} and {@code initContextHandler()} are the production + * implementations β€” guaranteeing test coverage of the new + * {@code SizeLimitHandler} wiring. + */ + @BeforeClass + public static void setup() throws Exception { + String dbPath = Files.createTempDirectory("sizelimit-test").toString(); + Args.setParam(new String[]{"--output-directory", dbPath}, TestConstants.TEST_CONF); + Args.getInstance().setMaxMessageSize(MAX_BODY_SIZE); + + int port = PublicMethod.chooseRandomPort(); + httpService = new TestHttpService(port); + httpService.start(); + + serverUri = new URI(String.format("http://localhost:%d/", port)); + client = HttpClients.createDefault(); + } + + @AfterClass + public static void teardown() throws Exception { + try { + if (client != null) { + client.close(); + } + } finally { + if (httpService != null) { + httpService.stop(); + } + Args.clearParam(); + } + } + + // -- body-size tests (covers HttpService.initContextHandler) --------------- + + @Test + public void testBodyWithinLimit() throws Exception { + Assert.assertEquals(200, post(new StringEntity("small body"))); + } + + @Test + public void testBodyExceedsLimit() throws Exception { + Assert.assertEquals(413, post(new StringEntity(repeat('a', MAX_BODY_SIZE + 1)))); + } + + // -- helpers --------------------------------------------------------------- + + /** POSTs with the given entity and returns the HTTP status code. */ + private int post(HttpEntity entity) throws Exception { + HttpPost req = new HttpPost(serverUri); + req.setEntity(entity); + HttpResponse resp = client.execute(req); + EntityUtils.consume(resp.getEntity()); + return resp.getStatusLine().getStatusCode(); + } + + /** Returns a string of {@code n} repetitions of {@code c}. */ + private static String repeat(char c, int n) { + return new String(new char[n]).replace('\0', c); + } +} From 736f2ed272d4e310ac8a269476ebfbbf4dc19c3d Mon Sep 17 00:00:00 2001 From: bladehan1 Date: Thu, 19 Mar 2026 15:55:39 +0800 Subject: [PATCH 2/6] feat(http) : wait for HttpService startup future in SizeLimitHandlerTest --- .../test/java/org/tron/common/jetty/SizeLimitHandlerTest.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/framework/src/test/java/org/tron/common/jetty/SizeLimitHandlerTest.java b/framework/src/test/java/org/tron/common/jetty/SizeLimitHandlerTest.java index d06f86963bf..2d174f14629 100644 --- a/framework/src/test/java/org/tron/common/jetty/SizeLimitHandlerTest.java +++ b/framework/src/test/java/org/tron/common/jetty/SizeLimitHandlerTest.java @@ -4,6 +4,7 @@ import java.io.IOException; import java.net.URI; import java.nio.file.Files; +import java.util.concurrent.TimeUnit; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @@ -89,7 +90,7 @@ public static void setup() throws Exception { int port = PublicMethod.chooseRandomPort(); httpService = new TestHttpService(port); - httpService.start(); + httpService.start().get(10, TimeUnit.SECONDS); serverUri = new URI(String.format("http://localhost:%d/", port)); client = HttpClients.createDefault(); From b2ade664573c650df55a574e80c41aa8e2db5f74 Mon Sep 17 00:00:00 2001 From: bladehan1 Date: Mon, 23 Mar 2026 18:31:57 +0800 Subject: [PATCH 3/6] feat(http): add independent maxMessageSize for HTTP and JSON-RPC Introduce node.http.maxMessageSize and node.jsonrpc.maxMessageSize to allow HTTP and JSON-RPC services to enforce separate request body size limits via Jetty SizeLimitHandler, decoupled from gRPC config. - Default: 4 * GrpcUtil.DEFAULT_MAX_MESSAGE_SIZE (16 MB) - Validation: reject <= 0 with TronError(PARAMETER_INIT) at startup - Each HttpService subclass sets its own maxRequestSize in constructor - SizeLimitHandlerTest covers independent limits, boundary, UTF-8 bytes --- .../common/parameter/CommonParameter.java | 6 + .../tron/common/application/HttpService.java | 5 +- .../java/org/tron/core/config/args/Args.java | 14 ++ .../org/tron/core/config/args/ConfigKey.java | 2 + .../services/http/FullNodeHttpApiService.java | 1 + .../solidity/SolidityNodeHttpApiService.java | 1 + .../JsonRpcServiceOnPBFT.java | 1 + .../JsonRpcServiceOnSolidity.java | 1 + .../http/PBFT/HttpApiOnPBFTService.java | 1 + .../solidity/HttpApiOnSolidityService.java | 1 + .../jsonrpc/FullNodeJsonRpcHttpService.java | 1 + .../common/jetty/SizeLimitHandlerTest.java | 143 +++++++++++++----- .../org/tron/core/config/args/ArgsTest.java | 3 + 13 files changed, 144 insertions(+), 36 deletions(-) diff --git a/common/src/main/java/org/tron/common/parameter/CommonParameter.java b/common/src/main/java/org/tron/common/parameter/CommonParameter.java index fbb39a13288..abb93765050 100644 --- a/common/src/main/java/org/tron/common/parameter/CommonParameter.java +++ b/common/src/main/java/org/tron/common/parameter/CommonParameter.java @@ -216,6 +216,12 @@ public class CommonParameter { public int maxMessageSize; @Getter @Setter + public int httpMaxMessageSize; + @Getter + @Setter + public int jsonRpcMaxMessageSize; + @Getter + @Setter public int maxHeaderListSize; @Getter @Setter diff --git a/framework/src/main/java/org/tron/common/application/HttpService.java b/framework/src/main/java/org/tron/common/application/HttpService.java index f3428d37fdb..b7032e75589 100644 --- a/framework/src/main/java/org/tron/common/application/HttpService.java +++ b/framework/src/main/java/org/tron/common/application/HttpService.java @@ -30,6 +30,8 @@ public abstract class HttpService extends AbstractService { protected String contextPath; + protected int maxRequestSize; + @Override public void innerStart() throws Exception { if (this.apiServer != null) { @@ -64,8 +66,7 @@ protected void initServer() { protected ServletContextHandler initContextHandler() { ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS); context.setContextPath(this.contextPath); - int maxMessageSize = Args.getInstance().getMaxMessageSize(); - SizeLimitHandler sizeLimitHandler = new SizeLimitHandler(maxMessageSize, -1); + SizeLimitHandler sizeLimitHandler = new SizeLimitHandler(this.maxRequestSize, -1); sizeLimitHandler.setHandler(context); this.apiServer.setHandler(sizeLimitHandler); return context; diff --git a/framework/src/main/java/org/tron/core/config/args/Args.java b/framework/src/main/java/org/tron/core/config/args/Args.java index 0e71294d786..ce6ef8bd8b1 100644 --- a/framework/src/main/java/org/tron/core/config/args/Args.java +++ b/framework/src/main/java/org/tron/core/config/args/Args.java @@ -474,6 +474,20 @@ public static void applyConfigParams( PARAMETER.maxMessageSize = config.hasPath(ConfigKey.NODE_RPC_MAX_MESSAGE_SIZE) ? config.getInt(ConfigKey.NODE_RPC_MAX_MESSAGE_SIZE) : GrpcUtil.DEFAULT_MAX_MESSAGE_SIZE; + int defaultHttpMaxMessageSize = 4 * GrpcUtil.DEFAULT_MAX_MESSAGE_SIZE; + PARAMETER.httpMaxMessageSize = config.hasPath(ConfigKey.NODE_HTTP_MAX_MESSAGE_SIZE) + ? config.getInt(ConfigKey.NODE_HTTP_MAX_MESSAGE_SIZE) : defaultHttpMaxMessageSize; + if (PARAMETER.httpMaxMessageSize <= 0) { + throw new TronError("node.http.maxMessageSize must be positive, got: " + + PARAMETER.httpMaxMessageSize, PARAMETER_INIT); + } + PARAMETER.jsonRpcMaxMessageSize = config.hasPath(ConfigKey.NODE_JSONRPC_MAX_MESSAGE_SIZE) + ? config.getInt(ConfigKey.NODE_JSONRPC_MAX_MESSAGE_SIZE) : defaultHttpMaxMessageSize; + if (PARAMETER.jsonRpcMaxMessageSize <= 0) { + throw new TronError("node.jsonrpc.maxMessageSize must be positive, got: " + + PARAMETER.jsonRpcMaxMessageSize, PARAMETER_INIT); + } + PARAMETER.maxHeaderListSize = config.hasPath(ConfigKey.NODE_RPC_MAX_HEADER_LIST_SIZE) ? config.getInt(ConfigKey.NODE_RPC_MAX_HEADER_LIST_SIZE) : GrpcUtil.DEFAULT_MAX_HEADER_LIST_SIZE; diff --git a/framework/src/main/java/org/tron/core/config/args/ConfigKey.java b/framework/src/main/java/org/tron/core/config/args/ConfigKey.java index dbb872febce..e05d59accf1 100644 --- a/framework/src/main/java/org/tron/core/config/args/ConfigKey.java +++ b/framework/src/main/java/org/tron/core/config/args/ConfigKey.java @@ -136,6 +136,7 @@ private ConfigKey() { public static final String NODE_HTTP_SOLIDITY_ENABLE = "node.http.solidityEnable"; public static final String NODE_HTTP_PBFT_ENABLE = "node.http.PBFTEnable"; public static final String NODE_HTTP_PBFT_PORT = "node.http.PBFTPort"; + public static final String NODE_HTTP_MAX_MESSAGE_SIZE = "node.http.maxMessageSize"; // node - jsonrpc public static final String NODE_JSONRPC_HTTP_FULLNODE_ENABLE = @@ -150,6 +151,7 @@ private ConfigKey() { public static final String NODE_JSONRPC_MAX_SUB_TOPICS = "node.jsonrpc.maxSubTopics"; public static final String NODE_JSONRPC_MAX_BLOCK_FILTER_NUM = "node.jsonrpc.maxBlockFilterNum"; + public static final String NODE_JSONRPC_MAX_MESSAGE_SIZE = "node.jsonrpc.maxMessageSize"; // node - dns public static final String NODE_DNS_TREE_URLS = "node.dns.treeUrls"; diff --git a/framework/src/main/java/org/tron/core/services/http/FullNodeHttpApiService.java b/framework/src/main/java/org/tron/core/services/http/FullNodeHttpApiService.java index 3ad4ace62fc..5a3b86cb396 100644 --- a/framework/src/main/java/org/tron/core/services/http/FullNodeHttpApiService.java +++ b/framework/src/main/java/org/tron/core/services/http/FullNodeHttpApiService.java @@ -297,6 +297,7 @@ public FullNodeHttpApiService() { port = Args.getInstance().getFullNodeHttpPort(); enable = isFullNode() && Args.getInstance().isFullNodeHttpEnable(); contextPath = "/"; + maxRequestSize = Args.getInstance().getHttpMaxMessageSize(); } @Override diff --git a/framework/src/main/java/org/tron/core/services/http/solidity/SolidityNodeHttpApiService.java b/framework/src/main/java/org/tron/core/services/http/solidity/SolidityNodeHttpApiService.java index 359adfc2b39..0c4843c0550 100644 --- a/framework/src/main/java/org/tron/core/services/http/solidity/SolidityNodeHttpApiService.java +++ b/framework/src/main/java/org/tron/core/services/http/solidity/SolidityNodeHttpApiService.java @@ -170,6 +170,7 @@ public SolidityNodeHttpApiService() { port = Args.getInstance().getSolidityHttpPort(); enable = !isFullNode() && Args.getInstance().isSolidityNodeHttpEnable(); contextPath = "/"; + maxRequestSize = Args.getInstance().getHttpMaxMessageSize(); } @Override diff --git a/framework/src/main/java/org/tron/core/services/interfaceJsonRpcOnPBFT/JsonRpcServiceOnPBFT.java b/framework/src/main/java/org/tron/core/services/interfaceJsonRpcOnPBFT/JsonRpcServiceOnPBFT.java index fffaf8d4e7b..5282ef5c819 100644 --- a/framework/src/main/java/org/tron/core/services/interfaceJsonRpcOnPBFT/JsonRpcServiceOnPBFT.java +++ b/framework/src/main/java/org/tron/core/services/interfaceJsonRpcOnPBFT/JsonRpcServiceOnPBFT.java @@ -19,6 +19,7 @@ public JsonRpcServiceOnPBFT() { port = Args.getInstance().getJsonRpcHttpPBFTPort(); enable = isFullNode() && Args.getInstance().isJsonRpcHttpPBFTNodeEnable(); contextPath = "/"; + maxRequestSize = Args.getInstance().getJsonRpcMaxMessageSize(); } @Override diff --git a/framework/src/main/java/org/tron/core/services/interfaceJsonRpcOnSolidity/JsonRpcServiceOnSolidity.java b/framework/src/main/java/org/tron/core/services/interfaceJsonRpcOnSolidity/JsonRpcServiceOnSolidity.java index a6f7d5dd5e7..8b52066d5f8 100644 --- a/framework/src/main/java/org/tron/core/services/interfaceJsonRpcOnSolidity/JsonRpcServiceOnSolidity.java +++ b/framework/src/main/java/org/tron/core/services/interfaceJsonRpcOnSolidity/JsonRpcServiceOnSolidity.java @@ -19,6 +19,7 @@ public JsonRpcServiceOnSolidity() { port = Args.getInstance().getJsonRpcHttpSolidityPort(); enable = isFullNode() && Args.getInstance().isJsonRpcHttpSolidityNodeEnable(); contextPath = "/"; + maxRequestSize = Args.getInstance().getJsonRpcMaxMessageSize(); } @Override diff --git a/framework/src/main/java/org/tron/core/services/interfaceOnPBFT/http/PBFT/HttpApiOnPBFTService.java b/framework/src/main/java/org/tron/core/services/interfaceOnPBFT/http/PBFT/HttpApiOnPBFTService.java index a77b45353c9..c0616c2ae78 100644 --- a/framework/src/main/java/org/tron/core/services/interfaceOnPBFT/http/PBFT/HttpApiOnPBFTService.java +++ b/framework/src/main/java/org/tron/core/services/interfaceOnPBFT/http/PBFT/HttpApiOnPBFTService.java @@ -173,6 +173,7 @@ public HttpApiOnPBFTService() { port = Args.getInstance().getPBFTHttpPort(); enable = isFullNode() && Args.getInstance().isPBFTHttpEnable(); contextPath = "/walletpbft"; + maxRequestSize = Args.getInstance().getHttpMaxMessageSize(); } @Override diff --git a/framework/src/main/java/org/tron/core/services/interfaceOnSolidity/http/solidity/HttpApiOnSolidityService.java b/framework/src/main/java/org/tron/core/services/interfaceOnSolidity/http/solidity/HttpApiOnSolidityService.java index f69597959f8..33e325bd578 100644 --- a/framework/src/main/java/org/tron/core/services/interfaceOnSolidity/http/solidity/HttpApiOnSolidityService.java +++ b/framework/src/main/java/org/tron/core/services/interfaceOnSolidity/http/solidity/HttpApiOnSolidityService.java @@ -181,6 +181,7 @@ public HttpApiOnSolidityService() { port = Args.getInstance().getSolidityHttpPort(); enable = isFullNode() && Args.getInstance().isSolidityNodeHttpEnable(); contextPath = "/"; + maxRequestSize = Args.getInstance().getHttpMaxMessageSize(); } @Override diff --git a/framework/src/main/java/org/tron/core/services/jsonrpc/FullNodeJsonRpcHttpService.java b/framework/src/main/java/org/tron/core/services/jsonrpc/FullNodeJsonRpcHttpService.java index 566ad33a722..ffe81bfa100 100644 --- a/framework/src/main/java/org/tron/core/services/jsonrpc/FullNodeJsonRpcHttpService.java +++ b/framework/src/main/java/org/tron/core/services/jsonrpc/FullNodeJsonRpcHttpService.java @@ -24,6 +24,7 @@ public FullNodeJsonRpcHttpService() { port = Args.getInstance().getJsonRpcHttpFullNodePort(); enable = isFullNode() && Args.getInstance().isJsonRpcHttpFullNodeEnable(); contextPath = "/"; + maxRequestSize = Args.getInstance().getJsonRpcMaxMessageSize(); } @Override diff --git a/framework/src/test/java/org/tron/common/jetty/SizeLimitHandlerTest.java b/framework/src/test/java/org/tron/common/jetty/SizeLimitHandlerTest.java index 2d174f14629..dbd6b1eb97e 100644 --- a/framework/src/test/java/org/tron/common/jetty/SizeLimitHandlerTest.java +++ b/framework/src/test/java/org/tron/common/jetty/SizeLimitHandlerTest.java @@ -3,7 +3,6 @@ import com.google.common.io.ByteStreams; import java.io.IOException; import java.net.URI; -import java.nio.file.Files; import java.util.concurrent.TimeUnit; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; @@ -21,7 +20,9 @@ import org.junit.AfterClass; import org.junit.Assert; import org.junit.BeforeClass; +import org.junit.ClassRule; import org.junit.Test; +import org.junit.rules.TemporaryFolder; import org.tron.common.TestConstants; import org.tron.common.application.HttpService; import org.tron.common.utils.PublicMethod; @@ -29,27 +30,30 @@ /** * Tests the {@link org.eclipse.jetty.server.handler.SizeLimitHandler} body-size - * enforcement configured in {@link HttpService initContextHandler()}. + * enforcement configured in {@link HttpService#initContextHandler()}. * - *

Unlike a standalone Jetty micro-server, this test creates a concrete - * {@link HttpService} subclass and calls {@link HttpService#start()}, so the - * production code paths of {@code initServer()} and {@code initContextHandler()} - * are exercised directly and reflected in code-coverage reports.

- * - *

Key behaviours proven:

+ *

Covers:

*
    *
  • Bodies within the limit are accepted ({@code 200}).
  • *
  • Bodies exceeding the limit are rejected ({@code 413}).
  • *
  • The limit counts raw UTF-8 bytes, not Java {@code char}s.
  • + *
  • HTTP and JSON-RPC services use independent size limits.
  • + *
  • Default values are 4x {@code GrpcUtil.DEFAULT_MAX_MESSAGE_SIZE}.
  • *
*/ @Slf4j public class SizeLimitHandlerTest { - private static final int MAX_BODY_SIZE = 1024; + private static final int HTTP_MAX_BODY_SIZE = 1024; + private static final int JSONRPC_MAX_BODY_SIZE = 512; + + @ClassRule + public static final TemporaryFolder temporaryFolder = new TemporaryFolder(); private static TestHttpService httpService; - private static URI serverUri; + private static TestJsonRpcService jsonRpcService; + private static URI httpServerUri; + private static URI jsonRpcServerUri; private static CloseableHttpClient client; /** Echoes the raw request-body bytes back so tests can inspect what arrived. */ @@ -63,11 +67,12 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I } } - /** Minimal concrete {@link HttpService} that registers only an {@link EchoServlet}. */ + /** Minimal concrete {@link HttpService} wired with a given size limit. */ static class TestHttpService extends HttpService { - TestHttpService(int port) { + TestHttpService(int port, int maxRequestSize) { this.port = port; this.contextPath = "/"; + this.maxRequestSize = maxRequestSize; } @Override @@ -76,23 +81,37 @@ protected void addServlet(ServletContextHandler context) { } } - /** - * Initialises {@link Args} and starts a real {@link HttpService} whose - * {@code initServer()} and {@code initContextHandler()} are the production - * implementations β€” guaranteeing test coverage of the new - * {@code SizeLimitHandler} wiring. - */ + /** Minimal concrete {@link HttpService} simulating a JSON-RPC service. */ + static class TestJsonRpcService extends HttpService { + TestJsonRpcService(int port, int maxRequestSize) { + this.port = port; + this.contextPath = "/"; + this.maxRequestSize = maxRequestSize; + } + + @Override + protected void addServlet(ServletContextHandler context) { + context.addServlet(new ServletHolder(new EchoServlet()), "/jsonrpc"); + } + } + @BeforeClass public static void setup() throws Exception { - String dbPath = Files.createTempDirectory("sizelimit-test").toString(); - Args.setParam(new String[]{"--output-directory", dbPath}, TestConstants.TEST_CONF); - Args.getInstance().setMaxMessageSize(MAX_BODY_SIZE); + Args.setParam(new String[]{"-d", temporaryFolder.newFolder().toString()}, + TestConstants.TEST_CONF); + Args.getInstance().setHttpMaxMessageSize(HTTP_MAX_BODY_SIZE); + Args.getInstance().setJsonRpcMaxMessageSize(JSONRPC_MAX_BODY_SIZE); - int port = PublicMethod.chooseRandomPort(); - httpService = new TestHttpService(port); + int httpPort = PublicMethod.chooseRandomPort(); + httpService = new TestHttpService(httpPort, HTTP_MAX_BODY_SIZE); httpService.start().get(10, TimeUnit.SECONDS); + httpServerUri = new URI(String.format("http://localhost:%d/", httpPort)); + + int jsonRpcPort = PublicMethod.chooseRandomPort(); + jsonRpcService = new TestJsonRpcService(jsonRpcPort, JSONRPC_MAX_BODY_SIZE); + jsonRpcService.start().get(10, TimeUnit.SECONDS); + jsonRpcServerUri = new URI(String.format("http://localhost:%d/jsonrpc", jsonRpcPort)); - serverUri = new URI(String.format("http://localhost:%d/", port)); client = HttpClients.createDefault(); } @@ -103,30 +122,86 @@ public static void teardown() throws Exception { client.close(); } } finally { - if (httpService != null) { - httpService.stop(); + try { + if (httpService != null) { + httpService.stop(); + } + } finally { + if (jsonRpcService != null) { + jsonRpcService.stop(); + } } Args.clearParam(); } } - // -- body-size tests (covers HttpService.initContextHandler) --------------- + // -- HTTP service body-size tests ------------------------------------------- + + @Test + public void testHttpBodyWithinLimit() throws Exception { + Assert.assertEquals(200, post(httpServerUri, new StringEntity("small body"))); + } + + @Test + public void testHttpBodyExceedsLimit() throws Exception { + Assert.assertEquals(413, + post(httpServerUri, new StringEntity(repeat('a', HTTP_MAX_BODY_SIZE + 1)))); + } + + @Test + public void testHttpBodyAtExactLimit() throws Exception { + Assert.assertEquals(200, + post(httpServerUri, new StringEntity(repeat('b', HTTP_MAX_BODY_SIZE)))); + } + + // -- JSON-RPC service body-size tests --------------------------------------- @Test - public void testBodyWithinLimit() throws Exception { - Assert.assertEquals(200, post(new StringEntity("small body"))); + public void testJsonRpcBodyWithinLimit() throws Exception { + Assert.assertEquals(200, + post(jsonRpcServerUri, new StringEntity("{\"method\":\"eth_blockNumber\"}"))); } @Test - public void testBodyExceedsLimit() throws Exception { - Assert.assertEquals(413, post(new StringEntity(repeat('a', MAX_BODY_SIZE + 1)))); + public void testJsonRpcBodyExceedsLimit() throws Exception { + Assert.assertEquals(413, + post(jsonRpcServerUri, new StringEntity(repeat('x', JSONRPC_MAX_BODY_SIZE + 1)))); + } + + @Test + public void testJsonRpcBodyAtExactLimit() throws Exception { + Assert.assertEquals(200, + post(jsonRpcServerUri, new StringEntity(repeat('c', JSONRPC_MAX_BODY_SIZE)))); + } + + // -- Independent limit tests ------------------------------------------------ + + @Test + public void testHttpAndJsonRpcHaveIndependentLimits() throws Exception { + // A body that exceeds JSON-RPC limit but is within HTTP limit + String body = repeat('d', JSONRPC_MAX_BODY_SIZE + 100); + Assert.assertTrue(body.length() < HTTP_MAX_BODY_SIZE); + + Assert.assertEquals(200, post(httpServerUri, new StringEntity(body))); + Assert.assertEquals(413, post(jsonRpcServerUri, new StringEntity(body))); + } + + // -- UTF-8 byte counting test ----------------------------------------------- + + @Test + public void testLimitIsBasedOnBytesNotCharacters() throws Exception { + // Each CJK character is 3 UTF-8 bytes; 342 chars x 3 = 1026 bytes > 1024 + String cjk = repeat('\u4e00', 342); + Assert.assertEquals(342, cjk.length()); + Assert.assertEquals(1026, cjk.getBytes("UTF-8").length); + Assert.assertEquals(413, post(httpServerUri, new StringEntity(cjk, "UTF-8"))); } - // -- helpers --------------------------------------------------------------- + // -- helpers ---------------------------------------------------------------- /** POSTs with the given entity and returns the HTTP status code. */ - private int post(HttpEntity entity) throws Exception { - HttpPost req = new HttpPost(serverUri); + private int post(URI uri, HttpEntity entity) throws Exception { + HttpPost req = new HttpPost(uri); req.setEntity(entity); HttpResponse resp = client.execute(req); EntityUtils.consume(resp.getEntity()); diff --git a/framework/src/test/java/org/tron/core/config/args/ArgsTest.java b/framework/src/test/java/org/tron/core/config/args/ArgsTest.java index a4ce9a5030e..5ec388ebcd8 100644 --- a/framework/src/test/java/org/tron/core/config/args/ArgsTest.java +++ b/framework/src/test/java/org/tron/core/config/args/ArgsTest.java @@ -120,6 +120,9 @@ public void get() { Assert.assertEquals(60000L, parameter.getMaxConnectionIdleInMillis()); Assert.assertEquals(Long.MAX_VALUE, parameter.getMaxConnectionAgeInMillis()); Assert.assertEquals(GrpcUtil.DEFAULT_MAX_MESSAGE_SIZE, parameter.getMaxMessageSize()); + Assert.assertEquals(4 * GrpcUtil.DEFAULT_MAX_MESSAGE_SIZE, parameter.getHttpMaxMessageSize()); + Assert.assertEquals(4 * GrpcUtil.DEFAULT_MAX_MESSAGE_SIZE, + parameter.getJsonRpcMaxMessageSize()); Assert.assertEquals(GrpcUtil.DEFAULT_MAX_HEADER_LIST_SIZE, parameter.getMaxHeaderListSize()); Assert.assertEquals(1L, parameter.getAllowCreationOfContracts()); Assert.assertEquals(0, parameter.getConsensusLogicOptimization()); From c41d30ebcb24e6652c11aef2686539620fc02ad2 Mon Sep 17 00:00:00 2001 From: bladehan1 Date: Wed, 1 Apr 2026 20:52:39 +0800 Subject: [PATCH 4/6] opt(checkstyle): optimize checkstyle --- .../test/java/org/tron/common/jetty/SizeLimitHandlerTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/framework/src/test/java/org/tron/common/jetty/SizeLimitHandlerTest.java b/framework/src/test/java/org/tron/common/jetty/SizeLimitHandlerTest.java index dbd6b1eb97e..e12b6ea57ad 100644 --- a/framework/src/test/java/org/tron/common/jetty/SizeLimitHandlerTest.java +++ b/framework/src/test/java/org/tron/common/jetty/SizeLimitHandlerTest.java @@ -30,7 +30,7 @@ /** * Tests the {@link org.eclipse.jetty.server.handler.SizeLimitHandler} body-size - * enforcement configured in {@link HttpService#initContextHandler()}. + * enforcement configured in {@link HttpService initContextHandler()}. * *

Covers:

*
    @@ -191,7 +191,7 @@ public void testHttpAndJsonRpcHaveIndependentLimits() throws Exception { @Test public void testLimitIsBasedOnBytesNotCharacters() throws Exception { // Each CJK character is 3 UTF-8 bytes; 342 chars x 3 = 1026 bytes > 1024 - String cjk = repeat('\u4e00', 342); + String cjk = repeat('δΈ€', 342); Assert.assertEquals(342, cjk.length()); Assert.assertEquals(1026, cjk.getBytes("UTF-8").length); Assert.assertEquals(413, post(httpServerUri, new StringEntity(cjk, "UTF-8"))); From 321e26a194908fc99aa3ea127a00511d1546c754 Mon Sep 17 00:00:00 2001 From: bladehan1 Date: Thu, 2 Apr 2026 09:45:59 +0800 Subject: [PATCH 5/6] change(config): update default size --- framework/src/main/java/org/tron/core/config/args/Args.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/framework/src/main/java/org/tron/core/config/args/Args.java b/framework/src/main/java/org/tron/core/config/args/Args.java index ce6ef8bd8b1..0ff2071c05f 100644 --- a/framework/src/main/java/org/tron/core/config/args/Args.java +++ b/framework/src/main/java/org/tron/core/config/args/Args.java @@ -474,7 +474,7 @@ public static void applyConfigParams( PARAMETER.maxMessageSize = config.hasPath(ConfigKey.NODE_RPC_MAX_MESSAGE_SIZE) ? config.getInt(ConfigKey.NODE_RPC_MAX_MESSAGE_SIZE) : GrpcUtil.DEFAULT_MAX_MESSAGE_SIZE; - int defaultHttpMaxMessageSize = 4 * GrpcUtil.DEFAULT_MAX_MESSAGE_SIZE; + int defaultHttpMaxMessageSize = GrpcUtil.DEFAULT_MAX_MESSAGE_SIZE; PARAMETER.httpMaxMessageSize = config.hasPath(ConfigKey.NODE_HTTP_MAX_MESSAGE_SIZE) ? config.getInt(ConfigKey.NODE_HTTP_MAX_MESSAGE_SIZE) : defaultHttpMaxMessageSize; if (PARAMETER.httpMaxMessageSize <= 0) { From e6b11a344e4d70e431e5264d37ab2c1f647aab65 Mon Sep 17 00:00:00 2001 From: bladehan1 Date: Thu, 2 Apr 2026 16:08:22 +0800 Subject: [PATCH 6/6] test(framework): align ArgsTest with 4M defaults --- .../src/test/java/org/tron/core/config/args/ArgsTest.java | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/framework/src/test/java/org/tron/core/config/args/ArgsTest.java b/framework/src/test/java/org/tron/core/config/args/ArgsTest.java index 5ec388ebcd8..b38dceaebe5 100644 --- a/framework/src/test/java/org/tron/core/config/args/ArgsTest.java +++ b/framework/src/test/java/org/tron/core/config/args/ArgsTest.java @@ -120,9 +120,8 @@ public void get() { Assert.assertEquals(60000L, parameter.getMaxConnectionIdleInMillis()); Assert.assertEquals(Long.MAX_VALUE, parameter.getMaxConnectionAgeInMillis()); Assert.assertEquals(GrpcUtil.DEFAULT_MAX_MESSAGE_SIZE, parameter.getMaxMessageSize()); - Assert.assertEquals(4 * GrpcUtil.DEFAULT_MAX_MESSAGE_SIZE, parameter.getHttpMaxMessageSize()); - Assert.assertEquals(4 * GrpcUtil.DEFAULT_MAX_MESSAGE_SIZE, - parameter.getJsonRpcMaxMessageSize()); + Assert.assertEquals(GrpcUtil.DEFAULT_MAX_MESSAGE_SIZE, parameter.getHttpMaxMessageSize()); + Assert.assertEquals(GrpcUtil.DEFAULT_MAX_MESSAGE_SIZE, parameter.getJsonRpcMaxMessageSize()); Assert.assertEquals(GrpcUtil.DEFAULT_MAX_HEADER_LIST_SIZE, parameter.getMaxHeaderListSize()); Assert.assertEquals(1L, parameter.getAllowCreationOfContracts()); Assert.assertEquals(0, parameter.getConsensusLogicOptimization()); @@ -345,4 +344,3 @@ public void testConfigStorageDefaults() { Args.clearParam(); } } -