Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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 @@ -44,7 +44,7 @@ public byte[] handleRequest(byte[] body) throws IOException {
request = OBJECT_MAPPER.reader()
.with(DeserializationFeature.FAIL_ON_TRAILING_TOKENS)
.with(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS).readTree(body);
} catch (IOException e) {
} catch (IOException | NumberFormatException e) {
return OBJECT_MAPPER.writeValueAsBytes(errorResponse(JsonError.PARSE_ERROR, null));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
public final class JsonRpcRequestValidator {

private static final String INVALID_REQUEST = "Invalid Request";
public static final String INVALID_NUMERIC_ID_MESSAGE =
INVALID_REQUEST + ": numeric id must be an integer literal within the signed 64-bit range";

private JsonRpcRequestValidator() {
}
Expand All @@ -15,6 +17,9 @@ public static String invalidRequestMessage(JsonNode request) {
if (request != null && request.isObject() && hasInvalidVersion(request)) {
return INVALID_REQUEST + ": jsonrpc must be \"2.0\"";
}
if (request != null && request.isObject() && hasInvalidNumericId(request)) {
return INVALID_NUMERIC_ID_MESSAGE;
}
// An explicit null ID is invalid: jsonrpc4j treats it as a notification and drops the result.
// Explicit null params retain the public endpoint's existing dispatch behavior.
if (request == null || !request.isObject()
Expand All @@ -31,6 +36,12 @@ public static boolean isValidResponseId(JsonNode id) {
return id != null && (id.isTextual() || id.isNumber());
}

/** jsonrpc4j parses IDs beyond the long range with asInt() and fractional IDs with asDouble(). */
private static boolean hasInvalidNumericId(JsonNode request) {
JsonNode id = request.get("id");
return id != null && id.isNumber() && !(id.isIntegralNumber() && id.canConvertToLong());
}

private static boolean hasInvalidVersion(JsonNode request) {
JsonNode version = request.get("jsonrpc");
return version == null || !version.isTextual() || !"2.0".equals(version.asText());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
import org.tron.core.services.admin.http.AdminRpcServlet;
import org.tron.core.services.filter.HttpInterceptor;
import org.tron.core.services.jsonrpc.JsonRpcMediaType;
import org.tron.core.services.jsonrpc.JsonRpcRequestValidator;

/** Runs identical protocol assertions through the HTTP and IPC entry points. */
@RunWith(Parameterized.class)
Expand Down Expand Up @@ -100,10 +101,31 @@ public void invalidIdTypesAreRejectedBeforeInvocation() throws Exception {
}

@Test
public void numericIdsOutsideLongRangeRetainTheirValue() throws Exception {
public void nonLongNumericIdsAreRejectedWithTheirValue() throws Exception {
for (String id : new String[] {"9223372036854775808", "-9223372036854775809",
"18446744073709551617", "9223372036854775807.1", "-9223372036854775808.1",
"1e40", "-1e40"}) {
"18446744073709551617", "9223372036854775807.1", "-9223372036854775808.1", "1.5",
"9223372036854775807.0", "0.12345678901234567890123456789", "-0.5", "1e-20", "1e40",
"-1e40"}) {
JsonNode response = assertError(sender.send(REQUEST + ",\"id\":" + id + "}"), -32600, id);
Assert.assertEquals(JsonRpcRequestValidator.INVALID_NUMERIC_ID_MESSAGE,
response.path("error").path("message").asText());
}
Mockito.verifyNoInteractions(adminJsonRpc);
}

@Test
public void exponentOverflowIsAParseError() throws Exception {
for (String literal : new String[] {"1e2147483648", "-1e2147483648", "1e-2147483649"}) {
assertError(sender.send(REQUEST + ",\"id\":" + literal + "}"), -32700, "null");
assertError(sender.send("{\"jsonrpc\":\"2.0\",\"method\":\"admin_addPeer\","
+ "\"params\":[" + literal + "],\"id\":1}"), -32700, "null");
}
Mockito.verifyNoInteractions(adminJsonRpc);
}

@Test
public void longRangeIdsRetainTheirValue() throws Exception {
for (String id : new String[] {"9223372036854775807", "-9223372036854775808"}) {
JsonNode response = parse(sender.send(REQUEST + ",\"id\":" + id + "}"));
assertId(id, response.get("id"));
Assert.assertTrue(response.path("result").path("success").asBoolean());
Expand All @@ -115,8 +137,7 @@ public void numericIdsOutsideLongRangeRetainTheirValue() throws Exception {
@Test
public void acceptedIdsRetainTheirValueAndType() throws Exception {
for (String id : new String[] {"\"null\"", "\"\"", "\"请求-7\"", "7", "0",
"9223372036854775807", "-9223372036854775808", "9007199254740993",
"0.12345678901234567890123456789", "9223372036854775807.0", "-0.5", "1e-20"}) {
"9223372036854775807", "-9223372036854775808", "9007199254740993"}) {
JsonNode response = parse(sender.send(REQUEST + ",\"id\":" + id + "}"));
assertId(id, response.get("id"));
Assert.assertTrue(response.path("result").path("success").asBoolean());
Expand Down Expand Up @@ -230,8 +251,7 @@ public void invalidMethodParamsNotificationHasNoResponse() throws Exception {
public void parameterBindingFailuresRetainIdsAndSilenceNotifications() throws Exception {
for (String params : new String[] {"[{}]", "{\"endpoint\":{}}"}) {
String request = REQUEST.replace(PARAMS, params);
for (String id : new String[] {"9", "\"request\"", "9223372036854775807",
"0.12345678901234567890123456789"}) {
for (String id : new String[] {"9", "\"request\"", "9223372036854775807"}) {
JsonNode error = assertError(sender.send(request + ",\"id\":" + id + "}"), -32602, id)
.get("error");
Assert.assertEquals("Failed to read method parameter at index 0",
Expand All @@ -255,8 +275,7 @@ public void businessErrorNotificationHasNoResponse() throws Exception {
public void errorResponsesRetainAcceptedIds() throws Exception {
Mockito.when(adminJsonRpc.addPeer(ENDPOINT))
.thenThrow(new JsonRpcInvalidParamsException("Invalid admin parameters"));
for (String id : new String[] {"\"null\"", "9223372036854775807",
"0.12345678901234567890123456789"}) {
for (String id : new String[] {"\"null\"", "9223372036854775807"}) {
JsonNode response = assertError(sender.send(REQUEST + ",\"id\":" + id + "}"), -32602, id);
Assert.assertEquals("Invalid admin parameters", response.path("error").path("message")
.asText());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1292,6 +1292,90 @@ public void batchWithNumericAndStringElements_allGetInvalidRequest() throws Exce
}
}

// --- Numeric ids that are not long integers ---

@Test
public void invalidNumericId_isRejectedWithOriginalIdWithoutDispatch() throws Exception {
for (String id : new String[] {"9223372036854775808", "-9223372036854775809",
"18446744073709551617", "1.5", "9223372036854775807.1", "1e19", "-1e3"}) {
MockHttpServletResponse resp = doPost("{\"jsonrpc\":\"2.0\","
+ "\"method\":\"web3_clientVersion\",\"params\":[],\"id\":" + id + "}");
assertEquals(200, resp.getStatus());
assertInvalidRequest(MAPPER.readTree(resp.getContentAsByteArray()),
JsonRpcRequestValidator.INVALID_NUMERIC_ID_MESSAGE, id);
}

verifyNoInteractions(mockRpcServer);
}

@Test
public void longRangeIds_areServedWhileLargerIdIsRejectedInBatch() throws Exception {
TronJsonRpc rpc = useRealRpcServer();

MockHttpServletResponse resp = doPost("["
+ "{\"jsonrpc\":\"2.0\",\"method\":\"eth_blockNumber\",\"id\":9223372036854775807},"
+ "{\"jsonrpc\":\"2.0\",\"method\":\"eth_blockNumber\",\"id\":9223372036854775808},"
+ "{\"jsonrpc\":\"2.0\",\"method\":\"eth_blockNumber\",\"id\":-9223372036854775808}]");
JsonNode body = MAPPER.readTree(resp.getContentAsByteArray());
assertEquals(3, body.size());
assertEquals(Long.MAX_VALUE, body.get(0).get("id").asLong());
assertEquals("0x1", body.get(0).get("result").asText());
assertInvalidRequest(body.get(1), JsonRpcRequestValidator.INVALID_NUMERIC_ID_MESSAGE,
"9223372036854775808");
assertEquals(Long.MIN_VALUE, body.get(2).get("id").asLong());
assertEquals("0x1", body.get(2).get("result").asText());
verify(rpc, times(2)).getLatestBlockNum();
}

@Test
public void rejectedFractionalId_isEchoedAsParsedDouble() throws Exception {
for (String id : new String[] {"0.12345678901234567890123456789", "1e-20", "1e300"}) {
MockHttpServletResponse resp = doPost("{\"jsonrpc\":\"2.0\","
+ "\"method\":\"web3_clientVersion\",\"params\":[],\"id\":" + id + "}");
assertInvalidRequest(MAPPER.readTree(resp.getContentAsByteArray()),
JsonRpcRequestValidator.INVALID_NUMERIC_ID_MESSAGE,
Double.toString(Double.parseDouble(id)));
}

verifyNoInteractions(mockRpcServer);
}

@Test
public void exponentOverflowId_isRejected() throws Exception {
for (String id : new String[] {"1e400", "-1e400", "1e2147483648"}) {
MockHttpServletResponse resp = doPost("{\"jsonrpc\":\"2.0\","
+ "\"method\":\"web3_clientVersion\",\"params\":[],\"id\":" + id + "}");
assertEquals(200, resp.getStatus());
JsonNode body = MAPPER.readTree(resp.getContentAsByteArray());
assertEquals(-32600, body.get("error").get("code").asInt());
assertEquals(JsonRpcRequestValidator.INVALID_NUMERIC_ID_MESSAGE,
body.get("error").get("message").asText());
// Jackson writes the parsed double, which has no JSON number form, as a string.
assertEquals(Double.toString(Double.parseDouble(id)), body.get("id").asText());
}

verifyNoInteractions(mockRpcServer);
}

@Test
public void nonBooleanFloatParam_isRejectedInSingleAndBatch() throws Exception {
TronJsonRpc rpc = useRealRpcServer();

for (String literal : new String[] {"1.0", "0.0", "1e0", "0e0", "1e2147483648"}) {
String request = "{\"jsonrpc\":\"2.0\",\"method\":\"eth_getBlockByNumber\","
+ "\"params\":[\"latest\"," + literal + "],\"id\":1}";
JsonNode single = MAPPER.readTree(doPost(request).getContentAsByteArray());
JsonNode batch = MAPPER.readTree(doPost("[" + request + "]").getContentAsByteArray());
assertEquals(literal, 1, batch.size());
for (JsonNode response : new JsonNode[] {single, batch.get(0)}) {
assertEquals(literal, 1, response.get("id").asInt());
assertEquals(literal, -32602, response.get("error").get("code").asInt());
}
}

verify(rpc, never()).ethGetBlockByNumber(any(), any());
}

// --- StreamReadConstraints: maxNestingDepth and maxTokenCount must be enforced ---

@Test
Expand Down
Loading