Skip to content
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
b32fa58
restrict batch size and response size of jsonrpc
317787106 Mar 18, 2026
02a588f
add node.jsonrpc.maxAddressSize and node.jsonrpc.maxRequestTimeout
317787106 Apr 1, 2026
eff49b9
update comment
317787106 Apr 1, 2026
19217b8
add jsonRpcMaxBatchSize default as 10
317787106 Apr 16, 2026
0351c22
remove timeout restrict
317787106 Apr 19, 2026
acd51cb
remove input restrict
317787106 Apr 20, 2026
fa87c7e
optimize ContentType when writeJsonRpcError
317787106 Apr 20, 2026
01da427
add error code -32700
317787106 Apr 23, 2026
7b2585d
add getReader for CachedBodyRequestWrapper; set jsonRpcMaxBatchSize d…
317787106 Apr 23, 2026
a0f51f8
add getWriter() for BufferedResponseWrapper
317787106 Apr 23, 2026
38edfda
use overflow to replace exception
317787106 Apr 26, 2026
e70ea6b
reuse the PrintWriter
317787106 Apr 26, 2026
bf9a5a1
fix default maxResponseSize
317787106 Apr 28, 2026
9eb44ce
optimize JsonRpcServlet
317787106 May 6, 2026
d65f064
don't invoke getInputStream and getReader in HttpServletRequestWrappe…
317787106 May 6, 2026
801e7ae
add testcase of JsonRpcServlet and CachedBodyRequestWrapper
317787106 May 7, 2026
2bb35a8
format code of testcase
317787106 May 7, 2026
d09d720
Merge branch 'develop' into hotfix/restrict_jsonrpc_size
317787106 May 8, 2026
4bdc025
reject empty batch; use StandardCharsets.UTF_8
317787106 May 8, 2026
fd7fdf9
reorganize the package import
317787106 May 8, 2026
91a2f12
Merge branch 'develop' into hotfix/restrict_jsonrpc_size
317787106 May 8, 2026
7bdddbb
Merge branch 'develop' into hotfix/restrict_jsonrpc_size
317787106 May 8, 2026
2fdb6b8
add some document for CachedBodyRequestWrapper
317787106 May 8, 2026
c329b47
format the code
317787106 May 8, 2026
998f52f
don't continue rest jsonrpc request of batch if overflow
317787106 May 8, 2026
e6eca1c
Merge branch 'develop' into hotfix/restrict_jsonrpc_size
317787106 May 9, 2026
498c9da
fix the bug of only one erro is return is the n-th request is overflow
317787106 May 9, 2026
90eabc6
merge develop
317787106 May 9, 2026
3908770
use ContentType as application/json-rpc
317787106 May 9, 2026
f28beb3
remove reduant maxBlockFilterNum; optimize commitToResponse
317787106 May 9, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -459,6 +459,15 @@ public class CommonParameter {
@Getter
@Setter
public int jsonRpcMaxBlockFilterNum = 50000;
@Getter
@Setter
public int jsonRpcMaxBatchSize = 100;
Comment thread
317787106 marked this conversation as resolved.
@Getter
@Setter
public int jsonRpcMaxResponseSize = 25 * 1024 * 1024;
@Getter
@Setter
public int jsonRpcMaxAddressSize = 1000;

@Getter
@Setter
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,9 @@ public void setHttpPBFTPort(int v) {
private int maxBlockRange = 5000;
private int maxSubTopics = 1000;
private int maxBlockFilterNum = 50000;
private int maxBatchSize = 100;
private int maxResponseSize = 25 * 1024 * 1024;
Comment thread
317787106 marked this conversation as resolved.
private int maxAddressSize = 1000;
}

@Getter
Expand Down
9 changes: 9 additions & 0 deletions common/src/main/resources/reference.conf
Original file line number Diff line number Diff line change
Expand Up @@ -400,6 +400,15 @@ node {

# Maximum number for blockFilter
maxBlockFilterNum = 50000

# Maximum number of requests in a JSON-RPC batch, >0 otherwise no limit
maxBatchSize = 100

# Maximum response body size in bytes for JSON-RPC (default 25MB), >0 otherwise no limit
maxResponseSize = 26214400

# Maximum number of addresses in a single JSON-RPC request, >0 otherwise no limit
maxAddressSize = 1000
}

# Disabled API list (works for http, rpc and pbft, not jsonrpc). Case insensitive.
Expand Down
1 change: 1 addition & 0 deletions framework/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ dependencies {
}

testImplementation group: 'org.springframework', name: 'spring-test', version: "${springVersion}"
testImplementation group: 'javax.portlet', name: 'portlet-api', version: '3.0.1'
implementation group: 'org.zeromq', name: 'jeromq', version: '0.5.3'
api project(":chainbase")
api project(":protocol")
Expand Down
3 changes: 3 additions & 0 deletions framework/src/main/java/org/tron/core/config/args/Args.java
Original file line number Diff line number Diff line change
Expand Up @@ -585,6 +585,9 @@ private static void applyNodeConfig(NodeConfig nc) {
PARAMETER.jsonRpcMaxBlockRange = jsonrpc.getMaxBlockRange();
PARAMETER.jsonRpcMaxSubTopics = jsonrpc.getMaxSubTopics();
PARAMETER.jsonRpcMaxBlockFilterNum = jsonrpc.getMaxBlockFilterNum();
PARAMETER.jsonRpcMaxBatchSize = jsonrpc.getMaxBatchSize();
PARAMETER.jsonRpcMaxResponseSize = jsonrpc.getMaxResponseSize();
PARAMETER.jsonRpcMaxAddressSize = jsonrpc.getMaxAddressSize();

// ---- P2P sub-bean ----
PARAMETER.nodeP2pVersion = nc.getP2p().getVersion();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
package org.tron.core.services.filter;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletOutputStream;
import javax.servlet.WriteListener;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;
import lombok.Getter;

/**
* Buffers the response body without writing to the underlying response,
* so the caller can replay it after the handler returns.
*
* <p>If {@code maxBytes > 0} and the response would exceed that limit, the
* {@link #isOverflow()} flag is set instead of throwing. The caller should check this flag after
* the handler returns and write its own error response when true.
*
* <p>Header-mutating methods ({@code setStatus}, {@code setContentType}) are buffered here and
* only forwarded to the real response via {@link #commitToResponse()}.
*/
public class BufferedResponseWrapper extends HttpServletResponseWrapper {

private final HttpServletResponse actual;
private final ByteArrayOutputStream buffer = new ByteArrayOutputStream();
private final int maxBytes;
private int status = HttpServletResponse.SC_OK;
private String contentType;
private boolean committed = false;
@Getter
private volatile boolean overflow = false;

private final ServletOutputStream outputStream = new ServletOutputStream() {
@Override
public void write(int b) {
if (overflow) {
return;
}
if (maxBytes > 0 && buffer.size() >= maxBytes) {
markOverflow();
return;
}
buffer.write(b);
}

@Override
public void write(byte[] b, int off, int len) {
if (overflow) {
return;
}
if (maxBytes > 0 && buffer.size() + len > maxBytes) {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[MUST] buffer.size() + len is unsafe int arithmetic and can overflow before the limit check. Please use checked or long arithmetic, e.g. long nextSize = (long) buffer.size() + len, and mark overflow when it exceeds maxBytes.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

There is little possibility to int overflow, so stays.

markOverflow();
return;
}
buffer.write(b, off, len);
}

@Override
public boolean isReady() {
return true;
}

@Override
public void setWriteListener(WriteListener writeListener) {
}
};

private final PrintWriter writer = new PrintWriter(outputStream, true);
Comment thread
317787106 marked this conversation as resolved.
Outdated

/**
* @param response the wrapped response
* @param maxBytes max allowed response bytes; {@code 0} means no limit
*/
public BufferedResponseWrapper(HttpServletResponse response, int maxBytes) {
super(response);
this.actual = response;
this.maxBytes = maxBytes;
}

private void markOverflow() {
overflow = true;
buffer.reset();
}

/**
* Early-detection path: if the framework reports the full content length before writing any
* bytes, we can flag overflow without buffering anything.
*/
@Override
public void setContentLength(int len) {
if (maxBytes > 0 && len > maxBytes) {
markOverflow();
}
}

@Override
public void setContentLengthLong(long len) {
if (maxBytes > 0 && len > maxBytes) {
markOverflow();
}
}

@Override
public int getStatus() {
return this.status;
}

@Override
public void setStatus(int sc) {
Comment thread
317787106 marked this conversation as resolved.
this.status = sc;
}

@Override
public void setHeader(String name, String value) {
if ("content-length".equalsIgnoreCase(name)) {
try {
setContentLengthLong(Long.parseLong(value));
} catch (NumberFormatException ignored) {
// malformed value, skip overflow check
}
} else {
super.setHeader(name, value);
}
}

@Override
public void addHeader(String name, String value) {
if ("content-length".equalsIgnoreCase(name)) {
try {
setContentLengthLong(Long.parseLong(value));
} catch (NumberFormatException ignored) {
// malformed value, skip overflow check
}
} else {
super.addHeader(name, value);
}
}

@Override
public void setContentType(String type) {
this.contentType = type;
}

@Override
public ServletOutputStream getOutputStream() {
return outputStream;
}

@Override
public PrintWriter getWriter() {
return writer;
}

public void commitToResponse() throws IOException {
Comment thread
317787106 marked this conversation as resolved.
if (committed) {
throw new IllegalStateException("commitToResponse() already called");
}
committed = true;
if (contentType != null) {
actual.setContentType(contentType);
}
actual.setStatus(status);
actual.setContentLength(buffer.size());
Comment thread
waynercheung marked this conversation as resolved.
buffer.writeTo(actual.getOutputStream());
actual.getOutputStream().flush();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package org.tron.core.services.filter;

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.InputStreamReader;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;

/**
* Wraps a request and replays a pre-read body from a byte array.
Comment thread
317787106 marked this conversation as resolved.
Outdated
*/
public class CachedBodyRequestWrapper extends HttpServletRequestWrapper {

private enum BodyAccessor { NONE, STREAM, READER }

private final byte[] body;
private BodyAccessor accessor = BodyAccessor.NONE;

public CachedBodyRequestWrapper(HttpServletRequest request, byte[] body) {
super(request);
this.body = body;
}

@Override
public ServletInputStream getInputStream() {
Comment thread
317787106 marked this conversation as resolved.
if (accessor == BodyAccessor.READER) {
throw new IllegalStateException("getReader() has already been called on this request");
}
accessor = BodyAccessor.STREAM;
final ByteArrayInputStream bais = new ByteArrayInputStream(body);
return new ServletInputStream() {
@Override
public int read() {
return bais.read();
}

@Override
public int read(byte[] b, int off, int len) {
return bais.read(b, off, len);
}

@Override
public boolean isFinished() {
return bais.available() == 0;
}

@Override
public boolean isReady() {
return true;
}

@Override
public void setReadListener(ReadListener readListener) {
Comment thread
317787106 marked this conversation as resolved.
}
};
}

@Override
public BufferedReader getReader() {
if (accessor == BodyAccessor.STREAM) {
throw new IllegalStateException("getInputStream() has already been called on this request");
}
accessor = BodyAccessor.READER;
String encoding = getCharacterEncoding();
Charset charset = encoding != null ? Charset.forName(encoding) : StandardCharsets.UTF_8;
Comment thread
317787106 marked this conversation as resolved.
Outdated
return new BufferedReader(new InputStreamReader(new ByteArrayInputStream(body), charset));
}
}
Loading
Loading