Skip to content
Open
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
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ src/test/java/org/tron/consensus2
src/main/java/META-INF/
src/main/resources/META-INF/
/bin/
bin/

# Eclipse IDE specific files and folders
/.project
Expand All @@ -57,3 +58,4 @@ Wallet

/framework/propPath
.cache
.claude
Original file line number Diff line number Diff line change
Expand Up @@ -90,12 +90,12 @@ public void merge(ProgramTrace programTrace) {
this.ops.addAll(programTrace.ops);
}

public String asJsonString(boolean formatted) {
return serializeFieldsOnly(this, formatted);
public String asJsonString() {
return serializeFieldsOnly(this);
}

@Override
public String toString() {
return asJsonString(true);
return asJsonString();
}
}
67 changes: 11 additions & 56 deletions actuator/src/main/java/org/tron/core/vm/trace/Serializers.java
Original file line number Diff line number Diff line change
@@ -1,73 +1,28 @@
package org.tron.core.vm.trace;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.introspect.VisibilityChecker;
import java.io.IOException;
import com.fasterxml.jackson.databind.json.JsonMapper;
import lombok.extern.slf4j.Slf4j;
import org.bouncycastle.util.encoders.Hex;
import org.tron.common.runtime.vm.DataWord;
import org.tron.core.vm.Op;

@Slf4j(topic = "VM")
public final class Serializers {

public static String serializeFieldsOnly(Object value, boolean pretty) {
try {
ObjectMapper mapper = createMapper(pretty);
mapper.setVisibilityChecker(fieldsOnlyVisibilityChecker(mapper));
private static final ObjectMapper mapper = JsonMapper.builder()
.enable(SerializationFeature.INDENT_OUTPUT)
.visibility(PropertyAccessor.FIELD, Visibility.ANY)
.visibility(PropertyAccessor.GETTER, Visibility.NONE)
.visibility(PropertyAccessor.IS_GETTER, Visibility.NONE)
.build();

public static String serializeFieldsOnly(Object value) {
try {
return mapper.writeValueAsString(value);
} catch (Exception e) {
logger.error("JSON serialization error: ", e);
return "{}";
}
}

private static VisibilityChecker<?> fieldsOnlyVisibilityChecker(ObjectMapper mapper) {
return mapper.getSerializationConfig().getDefaultVisibilityChecker()
.withFieldVisibility(JsonAutoDetect.Visibility.ANY)
.withGetterVisibility(JsonAutoDetect.Visibility.NONE)
.withIsGetterVisibility(JsonAutoDetect.Visibility.NONE);
}

public static ObjectMapper createMapper(boolean pretty) {
ObjectMapper mapper = new ObjectMapper();
if (pretty) {
mapper.enable(SerializationFeature.INDENT_OUTPUT);
}
return mapper;
}

public static class DataWordSerializer extends JsonSerializer<DataWord> {

@Override
public void serialize(DataWord energy, JsonGenerator jgen, SerializerProvider provider)
throws IOException, JsonProcessingException {
jgen.writeString(energy.value().toString());
}
}

public static class ByteArraySerializer extends JsonSerializer<byte[]> {

@Override
public void serialize(byte[] memory, JsonGenerator jgen, SerializerProvider provider)
throws IOException, JsonProcessingException {
jgen.writeString(Hex.toHexString(memory));
}
}

public static class OpCodeSerializer extends JsonSerializer<Byte> {

@Override
public void serialize(Byte op, JsonGenerator jgen, SerializerProvider provider)
throws IOException, JsonProcessingException {
jgen.writeString(Op.getNameOf(op));
}
}
}
2 changes: 1 addition & 1 deletion common/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ sourceCompatibility = 1.8


dependencies {
api group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.18.3' // https://github.com/FasterXML/jackson-databind/issues/3627
api group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.18.6' // https://github.com/FasterXML/jackson-databind/issues/3627
api "com.cedarsoftware:java-util:3.2.0"
api group: 'org.apache.httpcomponents', name: 'httpasyncclient', version: '4.1.1'
api group: 'commons-codec', name: 'commons-codec', version: '1.11'
Expand Down
5 changes: 3 additions & 2 deletions common/src/main/java/org/tron/common/utils/JsonUtil.java
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
package org.tron.common.utils;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.json.JsonMapper;
import org.springframework.util.StringUtils;

public class JsonUtil {

private static final ObjectMapper om = JsonMapper.builder().build();

public static final <T> T json2Obj(String jsonString, Class<T> clazz) {
if (!StringUtils.isEmpty(jsonString) && clazz != null) {
try {
ObjectMapper om = new ObjectMapper();
return om.readValue(jsonString, clazz);
} catch (Exception var3) {
throw new RuntimeException(var3);
Expand All @@ -22,7 +24,6 @@ public static final String obj2Json(Object obj) {
if (obj == null) {
return null;
} else {
ObjectMapper om = new ObjectMapper();
try {
return om.writeValueAsString(obj);
} catch (Exception var3) {
Expand Down
156 changes: 156 additions & 0 deletions common/src/main/java/org/tron/json/JSON.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
package org.tron.json;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.MapperFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.json.JsonMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;

/**
* Drop-in replacement for {@code com.alibaba.fastjson.JSON}.
* Swap the import line; no other source changes required for basic usages.
*
* <p>All static methods delegate to a shared, thread-safe Jackson {@link ObjectMapper} that is
* configured to match the lenient parsing behavior historically provided by Fastjson:
* <ul>
* <li>Unquoted field names and single-quoted strings are accepted.</li>
* <li>Unknown properties are ignored (Fastjson default).</li>
* <li>Floating-point numbers are mapped to {@link java.math.BigDecimal} for precision.</li>
* <li>Case-insensitive property matching is enabled.</li>
* </ul>
*/
public final class JSON {

/**
* Shared, fully-configured Jackson mapper. Exposed for callers that hold a mapper reference.
*/
public static final ObjectMapper MAPPER = JsonMapper.builder()
.configure(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, true)
.configure(JsonParser.Feature.ALLOW_SINGLE_QUOTES, true)
.configure(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS, true)
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false)
.serializationInclusion(JsonInclude.Include.NON_NULL)
.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
.configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES, true)
.build();

private JSON() {
}

// -------------------------------------------------------------------------
// parseObject
// -------------------------------------------------------------------------

/**
* Parses a JSON object string and returns a {@link JSONObject}.
* Mirrors {@code JSON.parseObject(String)} and {@code JSONObject.parseObject(String)}.
*/
public static JSONObject parseObject(String text) {
if (text == null || text.isEmpty()) {
return new JSONObject();
}
try {
return new JSONObject((ObjectNode) MAPPER.readTree(text));
} catch (Exception e) {
throw new JSONException("Failed to parse JSON object: " + e.getMessage(), e);
}
}

/**
* Parses a JSON string and deserializes it into the given Java type.
* Mirrors {@code JSON.parseObject(String, Class)}.
*/
public static <T> T parseObject(String text, Class<T> clazz) throws JsonProcessingException {
if (text == null || text.isEmpty()) {
return null;
}
return MAPPER.readValue(text, clazz);
}

// -------------------------------------------------------------------------
// parse (validate / generic parse — callers typically ignore the return value)
// -------------------------------------------------------------------------

/**
* Parses any valid JSON value. Returns {@code null} on failure (mirrors Fastjson behaviour).
* Mirrors {@code JSON.parse(String)}.
* @return a Jackson {@link JsonNode} representing the parsed JSON,
* or {@code null} if parsing fails.
*/
public static JsonNode parse(String text) {
if (text == null || text.isEmpty()) {
return null;
}
try {
return MAPPER.readTree(text);
} catch (Exception e) {
return null;
Comment on lines +92 to +93
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Throw parse errors instead of swallowing invalid JSON

JSON.parse now catches all parsing failures and returns null, but Util.isValidJson still treats success as “no exception” (framework/src/main/java/org/tron/core/services/http/Util.java). This makes malformed payloads (including application/x-www-form-urlencoded bodies like a=1&b=2) look like valid JSON, so Util.getJsonString skips URL-decoding and downstream request parsing fails. Previously, Fastjson surfaced parse errors via exceptions, which this validation path depends on.

Useful? React with 👍 / 👎.

Comment on lines +81 to +93
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Suggestion: The parse(String) method swallows all parsing exceptions and returns null on failure, which breaks existing callers like Util.isValidJson that rely on an exception being thrown to detect invalid JSON; this will cause invalid JSON strings to be treated as valid and skip the intended URL-encoded fallback path. [logic error]

Severity Level: Critical 🚨
- ❌ Form-encoded POST /wallet/marketcancelorder requests fail to parse.
- ❌ Other wallet HTTP POSTs with form bodies break parsing.
- ⚠️ UtilMockTest.testGetJsonString unit test fails with bug.
Suggested change
* Parses any valid JSON value. Returns {@code null} on failure (mirrors Fastjson behaviour).
* Mirrors {@code JSON.parse(String)}.
*/
public static Object parse(String text) {
if (text == null || text.isEmpty()) {
return null;
}
try {
return MAPPER.readTree(text);
} catch (Exception e) {
return null;
* Parses any valid JSON value. Throws {@link JSONException} on invalid input
* so callers can detect malformed JSON (e.g. via try/catch), mirroring
* Fastjson's observable behaviour in this codebase.
* Mirrors {@code JSON.parse(String)}.
*/
public static Object parse(String text) {
if (text == null || text.isEmpty()) {
return null;
}
try {
return MAPPER.readTree(text);
} catch (Exception e) {
throw new JSONException("Failed to parse JSON: " + e.getMessage(), e);
Steps of Reproduction ✅
1. Start a full node with HTTP API enabled so `FullNodeHttpApiService` is active (see
`framework/src/main/java/org/tron/core/services/http/FullNodeHttpApiService.java:37-41`).

2. Send an HTTP POST request to `/wallet/marketcancelorder` with header `Content-Type:
application/x-www-form-urlencoded` (endpoint wiring at
`FullNodeHttpApiService.java:58-60`) and a typical form body, e.g.
`owner_address=owner_address&contract_address=contract_address`.

3. The request is handled by `MarketCancelOrderServlet.doPost(...)`
(`framework/src/main/java/org/tron/core/services/http/MarketCancelOrderServlet.java:6-26`),
which calls `PostParams.getPostParams(request)` (`PostParams.java:24-32`). Because the
content type is `APPLICATION_FORM_URLENCODED`, `PostParams.getPostParams` calls
`Util.getJsonString(input)` (`PostParams.java:24-29`).

4. Inside `Util.getJsonString`
(`framework/src/main/java/org/tron/core/services/http/Util.java:614-633`),
`isValidJson(str)` (`Util.java:636-643`) calls `JSON.parse(str)`
(`common/src/main/java/org/tron/json/JSON.java:97-106`). With the current implementation,
`MAPPER.readTree(str)` throws for the URL-encoded string, `JSON.parse` catches the
exception and returns `null` instead of rethrowing, so no exception propagates.
`isValidJson` returns `true`, `getJsonString` returns the original URL-encoded string
instead of converting it to JSON, and `MarketCancelOrderServlet.doPost` then passes this
non-JSON string to `JsonFormat.merge(contract, build, visible)`
(`MarketCancelOrderServlet.java:8-12`), which fails to parse and results in an error
response instead of the previously working behaviour where URL-encoded bodies are
converted to JSON and processed successfully.
Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** common/src/main/java/org/tron/json/JSON.java
**Line:** 94:104
**Comment:**
	*Logic Error: The `parse(String)` method swallows all parsing exceptions and returns null on failure, which breaks existing callers like `Util.isValidJson` that rely on an exception being thrown to detect invalid JSON; this will cause invalid JSON strings to be treated as valid and skip the intended URL-encoded fallback path.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
👍 | 👎

}
}

/**
* Parses a JSON array string and returns a {@link JSONArray}.
* Mirrors {@code JSON.parseArray(String)} and {@code JSONArray.parseArray(String)}.
*/
public static JSONArray parseArray(String text) {
return JSONArray.parseArray(text);
}

// -------------------------------------------------------------------------
// toJSONString
// -------------------------------------------------------------------------

/**
* Serializes an object to a compact JSON string.
* Mirrors {@code JSON.toJSONString(Object)}.
*/
public static String toJSONString(Object obj) {
if (obj == null) {
return "null";
}
// Unwrap our own wrapper types so the inner Jackson node is serialized
if (obj instanceof JSONObject) {
return ((JSONObject) obj).unwrap().toString();
}
if (obj instanceof JSONArray) {
return ((JSONArray) obj).unwrap().toString();
}
try {
return MAPPER.writeValueAsString(obj);
} catch (Exception e) {
throw new JSONException("Failed to serialise object: " + e.getMessage(), e);
}
}

/**
* Serializes an object to a JSON string, optionally pretty-printed.
* Mirrors {@code JSON.toJSONString(Object, boolean)}.
*/
public static String toJSONString(Object obj, boolean prettyFormat) {
if (!prettyFormat) {
return toJSONString(obj);
}
if (obj == null) {
return "null";
}
try {
if (obj instanceof JSONObject) {
return MAPPER.writerWithDefaultPrettyPrinter()
.writeValueAsString(((JSONObject) obj).unwrap());
}
if (obj instanceof JSONArray) {
return MAPPER.writerWithDefaultPrettyPrinter()
.writeValueAsString(((JSONArray) obj).unwrap());
}
return MAPPER.writerWithDefaultPrettyPrinter().writeValueAsString(obj);
} catch (Exception e) {
throw new JSONException("Failed to serialise object: " + e.getMessage(), e);
}
}
}
Loading
Loading