feat(jsonrpc): add resource restrict for jsonrpc#6728
feat(jsonrpc): add resource restrict for jsonrpc#6728317787106 wants to merge 21 commits intotronprotocol:developfrom
Conversation
…r twice; add several methods of HttpServletRequestWrapper
| import javax.servlet.http.HttpServletRequestWrapper; | ||
|
|
||
| /** | ||
| * Wraps a request and replays a pre-read body from a byte array. |
There was a problem hiding this comment.
[NIT] Document the supported scope of CachedBodyRequestWrapper in the class javadoc
The wrapper currently works correctly for what JsonRpcServlet.doPost needs — synchronous JSON-body POST, body read via getInputStream() / getReader(). It is, however, placed in services/filter/ (alongside Filter implementations) and named generically, which invites future reuse. Several HttpServletRequest capabilities are silently incompatible with a cached byte[] body and are easy to overlook:
application/x-www-form-urlencoded— the container consumes the original stream whengetParameter*is called; once the body is cached, those accessors return null/empty.multipart/*—getPart()/getParts()read from the original (already-consumed) stream.- async non-blocking I/O —
setReadListenercannot be honored over aByteArrayInputStream. - request dispatch / forward chains — the cached body does not propagate through dispatchers in a meaningful way.
Making the scope explicit in the class javadoc is the single cheapest hedge against this wrapper being moved to a generic filter or reused on a different content-type later. Suggested wording (feel free to adjust):
/**
* 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.
*/Not blocking. Pure documentation, no behavior change.
| } | ||
|
|
||
| @Override | ||
| public void setReadListener(ReadListener readListener) { |
There was a problem hiding this comment.
[NIT] Make setReadListener fail-fast instead of a silent no-op
A cached byte[] body cannot drive async non-blocking I/O — the registered ReadListener would never receive onDataAvailable() callbacks against a ByteArrayInputStream snapshot. The current empty implementation accepts the listener silently, so a future caller that turns this servlet into an async path would hang with no visible error.
jsonrpc4j today is fully synchronous, so this is latent rather than active. Throwing fail-fast here costs one line and surfaces a future regression immediately at the right place.
@Override
public void setReadListener(ReadListener readListener) {
throw new UnsupportedOperationException(
"async I/O is not supported on cached body");
}The existing isReady() returning true is fine — for an in-memory stream that is the honest answer.
What does this PR do?
Adds configurable resource limits to the JSON-RPC endpoint to prevent memory exhaustion and abuse from oversized requests or responses. Closes #6632
Changes:
Batch size limit (
node.jsonrpc.maxBatchSize, default: 100)-32005(exceed limit).maxBatchSize ≤ 0(no limit).Empty batch rejection
[]is now rejected with error code-32600(Invalid Request) per JSON-RPC 2.0 §6: "the response from the Server MUST be a single Response object" when the input is not an array with at least one value.Response size limit (
node.jsonrpc.maxResponseSize, default: 25 MB)BufferedResponseWrapper: interceptsgetOutputStream()andgetWriter()writes into an in-memory buffer. When a write would exceed the configured limit, it sets anoverflowflag and resets the buffer instead of continuing to accumulate bytes, bounding worst-case memory usage to at mostmaxResponseSize.CachedBodyRequestWrapper: replays the pre-read request body via bothgetInputStream()andgetReader(), so the body can be inspected before being forwarded toJsonRpcServer.isOverflow()and — if set — discards the partial buffer and returns error code-32003(response too large).Address list limit (
node.jsonrpc.maxAddressSize, default: 1000)LogFilter, validates theaddressarray length ineth_getLogs/eth_newFilterrequests.JsonRpcInvalidParamsException.Structured JSON-RPC error responses
writeJsonRpcErrorusesObjectMapperto build error responses safely, avoiding JSON injection from error messages.-32700parse error-32600invalid request-32603internal error-32005exceed limit-32003response too largeWhy are these changes required?
maxResponseSizeand fails fast rather than buffering the entire response before checking.[]closes a spec compliance gap: previously the empty batch fell through toJsonRpcServer, whose behavior for an empty array is undefined by the spec.Configuration
Tests
JsonRpcServletTest[]→-32600, batch size limit, response overflow, internal error, normal pathBufferedResponseWrapperTestgetWriterdelegationCachedBodyRequestWrapperTestgetInputStreamandgetReaderJsonRpcTest.testLogFilterAddressSizeLimit"exceed max addresses:"), limit=0 disabled