Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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 @@ -494,8 +494,16 @@ public class CommonParameter {
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
public int jsonRpcMaxLogFilterNum = 20000;

@Getter
@Setter
public int maxTransactionPendingSize;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,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;
private int maxLogFilterNum = 20000;
private long maxMessageSize = 4194304;
}
Expand Down
18 changes: 10 additions & 8 deletions common/src/main/resources/reference.conf
Original file line number Diff line number Diff line change
Expand Up @@ -380,18 +380,20 @@ node {
httpPBFTEnable = false
httpPBFTPort = 8565

# Maximum blocks range for eth_getLogs, >0 otherwise no limit
# The maximum blocks range to retrieve logs for eth_getLogs, default: 5000, <=0 means no limit
maxBlockRange = 5000

# Maximum topics within a topic criteria, >0 otherwise no limit
# Allowed max address count in filter request, default: 1000, <=0 means no limit
maxAddressSize = 1000
# The maximum number of allowed topics within a topic criteria, default: 1000, <=0 means no limit
maxSubTopics = 1000

# Maximum number for blockFilter. >0 otherwise no limit
# Allowed maximum number for blockFilter, default: 50000, <=0 means no limit
maxBlockFilterNum = 50000

# Maximum number of concurrent eth_newFilter registrations, >0 otherwise no limit
# Allowed batch size, default: 100, <=0 means no limit
maxBatchSize = 100
# Allowed max response byte size, default: 26214400 (25 MB), <=0 means no limit
maxResponseSize = 26214400
# Allowed maximum number for newFilter, <=0 means no limit
maxLogFilterNum = 20000

# Maximum JSON-RPC request body size, default 4MB. Independent from rpc.maxMessageSize.
maxMessageSize = 4M
}
Expand Down
1 change: 1 addition & 0 deletions framework/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,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 @@ -559,6 +559,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();
PARAMETER.jsonRpcMaxLogFilterNum = jsonrpc.getMaxLogFilterNum();
PARAMETER.jsonRpcMaxMessageSize = jsonrpc.getMaxMessageSize();

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
package org.tron.core.services.filter;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.nio.charset.StandardCharsets;
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(new OutputStreamWriter(outputStream, StandardCharsets.UTF_8), true);

/**
* @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;
// Flush the PrintWriter's OutputStreamWriter encoder into our ByteArrayOutputStream.
// PrintWriter(autoFlush=true) only auto-flushes on println/printf/format, not print/write,
// so bytes can remain buffered in the encoder until an explicit flush.
writer.flush();
if (overflow) {
return;
}
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,97 @@
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.IllegalCharsetNameException;
import java.nio.charset.StandardCharsets;
import java.nio.charset.UnsupportedCharsetException;
import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;

/**
* Wraps a request to replay a pre-read body from a byte array,
* allowing the body to be read more than once.
*
* <p><b>Scope:</b> designed for synchronous, raw-body POST endpoints
* (e.g. JSON-RPC). It is NOT compatible with:
* <ul>
* <li>{@code application/x-www-form-urlencoded} — cached body cannot back
* {@code getParameter*}.</li>
* <li>multipart — {@code getPart()/getParts()} read from the original
* (already-consumed) stream.</li>
* <li>async non-blocking I/O — see {@code setReadListener}.</li>
* <li>request dispatch / forward chains.</li>
* </ul>
*
* <p>Multiple calls to {@code getInputStream()} (or {@code getReader()})
* are allowed and each returns a fresh stream over the same cached body —
* a deliberate extension of the standard servlet contract.
*/
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.
throw new UnsupportedOperationException(
"async I/O is not supported on cached body");
}
};
}

@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;
try {
charset = encoding != null ? Charset.forName(encoding) : StandardCharsets.UTF_8;
} catch (IllegalCharsetNameException | UnsupportedCharsetException ex) {
charset = StandardCharsets.UTF_8;
}
return new BufferedReader(new InputStreamReader(new ByteArrayInputStream(body), charset));
}
}
Loading
Loading