Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 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
c8b53b6
optimize config file
317787106 May 5, 2026
afea1c9
format code
317787106 May 5, 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
418cb61
chore: merge develop into hotfix/fix_dup_config
317787106 May 7, 2026
7de92be
add stripNullLeaves to auto binding; simply binding not compatible va…
317787106 May 8, 2026
650e8e1
add userSection; resolve the priority of allowShieldedTransactionApi
317787106 May 8, 2026
7dd0137
optimize BeanDefaults
317787106 May 8, 2026
2e44a07
merge develop
317787106 May 8, 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
627b85d
merge develop
317787106 May 9, 2026
f28beb3
remove reduant maxBlockFilterNum; optimize commitToResponse
317787106 May 9, 2026
86f8d74
fix bug of BeanDefaultsTest
317787106 May 9, 2026
f8850be
merge hotfix/restrict_jsonrpc_size
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 @@ -14,7 +14,6 @@
import org.tron.common.logsfilter.FilterQuery;
import org.tron.common.setting.RocksDbSettings;
import org.tron.core.Constant;
import org.tron.core.config.args.Overlay;
import org.tron.core.config.args.SeedNode;
import org.tron.core.config.args.Storage;
import org.tron.p2p.P2pConfig;
Expand Down Expand Up @@ -445,8 +444,6 @@ public class CommonParameter {
@Getter
public Storage storage;
@Getter
public Overlay overlay;
@Getter
public SeedNode seedNode;
@Getter
public EventPluginConfig eventPluginConfig;
Expand Down Expand Up @@ -494,8 +491,16 @@ public class CommonParameter {
public int jsonRpcMaxBlockFilterNum = 50000;
@Getter
@Setter
public int jsonRpcMaxBatchSize = 100;
@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
145 changes: 145 additions & 0 deletions common/src/main/java/org/tron/core/config/BeanDefaults.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
package org.tron.core.config;

import com.typesafe.config.Config;
import com.typesafe.config.ConfigFactory;
import com.typesafe.config.ConfigObject;
import com.typesafe.config.ConfigValue;
import com.typesafe.config.ConfigValueType;
import java.beans.BeanInfo;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

/**
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.

[NIT] Class Javadoc lacks usage-scope constraint

BeanDefaults exposes three public static entry points (toConfig / stripNullLeaves / remapKey). The Javadoc explains the design intent (ConfigBeanFactory fallback) but does not say "use here only." Because this class lives in the common/ leaf module, business modules (actuator, chainbase, etc.) could plausibly start using toConfig as a generic bean-to-Config serializer, which would couple a leaf utility to runtime concerns it was not designed for.

Suggestion: extend the class-level Javadoc with a one-line constraint such as: "Intended exclusively for ConfigBeanFactory fallback inside org.tron.core.config.args.*. For general bean serialization, use Jackson/Gson directly."

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.

[QUESTION] Provide a default-value parity check vs the deleted reference.conf

In the old model reference.conf was a runtime requirement — without it ConfigBeanFactory would throw Missing — so its values were the actual runtime defaults, and the bean field initializers (private int maxConnections = 30) were effectively dead code (always overwritten via the fallback chain). After this PR the bean field initializers become live for the first time. Any historical drift between a field initializer and the value that used to live in reference.conf is now a silent user-observable behavior change for anyone running with their own config.conf.

Manual spot-checking of the operationally critical keys (seed.node.ip.list, event.subscribe.topics, node.fastForward, genesis.block.*) confirms the shipped framework/src/main/resources/config.conf carries equivalent values, so users on the shipped template are unaffected. But every scalar key needs the same per-key check, and there is no automated guard against future drift.

Question: could the team commit the pre-deletion reference.conf as common/src/test/resources/reference.conf.legacy-fixture and add a parity test (BeanDefaults.toConfig(...) chained over all 9 beans, compared key-by-key against the fixture, with intentional differences explicitly whitelisted)? It would lock the contract permanently and catch future drift in CI.

If a parity test is too heavy for this PR, the minimum ask is to include the per-scalar-key audit result in the PR description as a checklist signed off by both author and reviewer.

* Generates a Typesafe {@link Config} from a bean instance's current field values.
*
* <p>Used by each {@code XxxConfig.fromConfig()} to replace the role that
* {@code reference.conf} played: ensures every key ConfigBeanFactory needs is
* present, so a partial user config works without throwing
* {@code ConfigException.Missing}.
*
* <p>Only public getter+setter pairs (standard JavaBean properties) are included —
* the same set that {@code ConfigBeanFactory.create()} auto-binds. Keys are
* decapitalized exactly as ConfigBeanFactory does:
* {@code Character.toLowerCase(name.charAt(0)) + name.substring(1)}.
*
* <p>Nested bean fields are recursed into nested HOCON objects.
* {@code List} fields are serialized as HOCON lists (empty by default).
* Fields with no public setter (e.g. {@code @Getter(AccessLevel.NONE)} overrides)
* are automatically skipped — these are handled manually in each
* {@code fromConfig()} via {@code hasPath} guards.
*/
public final class BeanDefaults {

private BeanDefaults() {}

/**
* Convert {@code bean}'s public JavaBean properties to a Typesafe Config.
* The resulting Config can be used as a {@code withFallback()} for a user's
* config section to guarantee all keys are present for ConfigBeanFactory.
*/
public static Config toConfig(Object bean) {
return ConfigFactory.parseMap(toMap(bean));
}

/**
* Returns a copy of {@code config} with all null-valued leaf paths removed.
* Call this on a user-supplied config section before {@link Config#withFallback}
* so that HOCON {@code null} entries in legacy configs do not shadow bean defaults.
*
* <p>Uses {@link ConfigObject#entrySet()} (not {@link Config#entrySet()}) because
* the latter silently excludes null values, making them impossible to detect.
*/
public static Config stripNullLeaves(Config config) {
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.

[SHOULD] Consider splitting: stripNullLeaves bug fix and reference.conf removal are independent changes

This PR combines two technically independent changes whose risk profiles differ by an order of magnitude:

  1. stripNullLeaves bug fix (~30 lines) — pure bug fix for the startup crash when a user config contains an explicit null leaf. Self-contained, low risk, does not move the default-value source. Could be cherry-picked into a current release.

  2. Remove reference.conf + introduce BeanDefaults reflection-based fallback (~1900 lines) — pure architectural refactor to eliminate dual-source maintenance. High risk: in the old model reference.conf was a runtime requirement, so bean field initializers were effectively dead code (always overwritten via the fallback chain). Removing reference.conf makes those initializers live for the first time, and any historical drift becomes a silent user-observable behavior change. Needs default-value parity verification, the pBFT* remap idiom, and confirmation that the shipped config.conf carries the operationally critical keys that used to live in reference.conf.

The two changes are loosely coupled: fixing #1 does not require touching reference.conf at all (just call stripNullLeaves on the user-supplied config before merging). Likewise, #2 does not require stripNullLeaves — it's an independent architectural choice.

Merging them together makes both halves harder to review (the small bug fix gets buried in the large refactor's diff), and creates a coupling problem for future rollback decisions: if a user-observable drift surfaces in the architectural part and #2 needs to be reverted, the urgent bug fix in #1 would be reverted with it.

Suggestion: split into PR-A (just stripNullLeaves + call site, keep reference.conf unchanged) and PR-B (BeanDefaults + delete reference.conf + nine fromConfig changes), in that order. Alternatively, if you prefer to keep them together, list the independent logical changes in the PR description with explicit rollback boundaries so a future revert decision can target one without the other.

return stripNullObject(config.root()).toConfig();
}

/**
* Returns a copy of {@code config} where the value at {@code fromKey} is moved to
* {@code toKey}, leaving the original key absent. If {@code fromKey} is absent, the
* config is returned unchanged. Use this in {@code fromConfig()} to bridge config keys
* that violate JavaBean naming (e.g. {@code pBFTExpireNum} → {@code PBFTExpireNum}) so
* that {@code ConfigBeanFactory} finds the value under the key it derives from the setter.
*/
public static Config remapKey(Config config, String fromKey, String toKey) {
if (!config.hasPath(fromKey)) {
return config;
}
return config.withValue(toKey, config.getValue(fromKey)).withoutPath(fromKey);
}

private static ConfigObject stripNullObject(ConfigObject obj) {
ConfigObject result = obj;
for (Map.Entry<String, ConfigValue> entry : obj.entrySet()) {
ConfigValue v = entry.getValue();
if (v.valueType() == ConfigValueType.NULL) {
result = result.withoutKey(entry.getKey());
} else if (v.valueType() == ConfigValueType.OBJECT) {
result = result.withValue(entry.getKey(), stripNullObject((ConfigObject) v));
}
}
return result;
}

private static Map<String, Object> toMap(Object bean) {
Map<String, Object> map = new LinkedHashMap<>();
BeanInfo info;
try {
info = Introspector.getBeanInfo(bean.getClass());
} catch (java.beans.IntrospectionException e) {
// Programming error: bean class does not conform to JavaBean spec.
// Propagate immediately so the misconfigured class is identified at startup,
// rather than returning a silent empty map that produces a confusing
// ConfigException.Missing pointing at the user config.
throw new IllegalStateException("Cannot introspect bean: " + bean.getClass().getName(), e);
}
for (PropertyDescriptor pd : info.getPropertyDescriptors()) {
Method getter = pd.getReadMethod();
Method setter = pd.getWriteMethod();
// Skip read-only properties (no setter) — matches ConfigBeanFactory's contract
if (getter == null || setter == null) {
continue;
}
// Use the property name exactly as Introspector produced it.
// ConfigBeanFactory does configProps.get(beanProp.getName()) — the lookup key
// is the property name verbatim, not decapitalized. For ordinary camelCase
// setters (setMaxConnections → "MaxConnections" → decapitalize → "maxConnections")
// Introspector already returns the lowercase form. For setters that start with
// two consecutive uppercase letters (setPBFTEnable → "PBFTEnable") the JavaBean
// spec forbids decapitalization, so pd.getName() == "PBFTEnable" — matching the
// capital-P key that config.conf uses for those fields.
try {
String key = pd.getName();
Object value = getter.invoke(bean);
map.put(key, toValue(value));
} catch (Exception ignored) {
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.

[SHOULD] Silent swallow of getter exceptions hides root cause

The catch (Exception ignored) here drops any getter failure (typically InvocationTargetException from a buggy bean getter) without any log record. When this happens, the affected property is silently absent from the defaults map and ConfigBeanFactory later throws Missing path xxx pointing at user config — the real cause (a broken bean getter) becomes invisible to operators and to anyone debugging a startup failure.

Keep the best-effort behavior, but add a debug-level log so the root cause is recoverable when needed (e.g. via -Dlogger.org.tron.core.config.BeanDefaults=DEBUG).

Suggestion: instantiate a private static Logger and call logger.debug("Skipping property {}.{} due to introspection failure", bean.getClass().getName(), pd.getName(), e) inside the catch block.

// Best-effort: skip individual unresolvable property so that the rest of
// the defaults are still emitted. getter.invoke() is the only realistic
// throw site (InvocationTargetException / IllegalAccessException).
}
}
return map;
}

private static Object toValue(Object value) {
if (value == null) {
return "";
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.

[NIT] toValue(null) → "" silently masks type mismatches

toValue(null) returns an empty string regardless of the declared field type. Today every nested bean field is eagerly initialized (private DiscoveryConfig discovery = new DiscoveryConfig();), so this never triggers — but a future change to lazy-init style (private DiscoveryConfig discovery;) would silently emit discovery = "" into the defaults map, and ConfigBeanFactory would then fail to bind a String value into a nested bean type with an obscure error.

Suggestion: either restrict the null → "" coercion to declared String types only, or throw IllegalStateException when a nested-bean / numeric / List getter returns null, so the broken bean is identified up-front rather than failing later in ConfigBeanFactory.

}
if (value instanceof Boolean || value instanceof Number || value instanceof String) {
return value;
}
if (value instanceof List) {
List<Object> list = new ArrayList<>();
for (Object item : (List<?>) value) {
list.add(toValue(item));
}
return list;
}
// Assume nested bean — recurse so it becomes a nested HOCON object.
return toMap(value);
}
}
5 changes: 3 additions & 2 deletions common/src/main/java/org/tron/core/config/Configuration.java
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,11 @@ public static com.typesafe.config.Config getByFileName(

private static void resolveConfigFile(String fileName, File confFile) {
if (confFile.exists()) {
config = ConfigFactory.parseFile(confFile)
.withFallback(ConfigFactory.defaultReference());
config = ConfigFactory.parseFile(confFile);
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] Removing defaultReference() here regresses external partial config files. seed.node.ip.list is still read manually in MiscConfig.fromConfig() and no longer has any fallback on the file-load path, so a minimal -c file now yields an empty seed set instead of the legacy bootstrap defaults. Please keep defaultReference() until the remaining top-level/manual-read domains are migrated, or add an equivalent fallback for them before deleting reference.conf.

} else if (Thread.currentThread().getContextClassLoader().getResourceAsStream(fileName)
!= null) {
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.

[NIT] Document that reference.conf is no longer expected on the load() path

ConfigFactory.load(fileName) historically picked up the in-jar reference.conf automatically. After this PR, reference.conf is deleted, but a stale copy left over in a fat-jar by a dirty build cache would still be loaded by this branch — silently masking the new bean-based defaults.

Suggestion: add a comment here noting that reference.conf was removed in this PR and any residual copy in the resource path is a build artifact to be cleaned; consider an optional packaging-time check that asserts no reference.conf ends up in the published jar.

// ConfigFactory.load merges system properties (higher priority than the file),
// which tests rely on to override storage.db.engine via -D flags.
config = ConfigFactory.load(fileName);
} else {
throw new IllegalArgumentException(
Expand Down
14 changes: 9 additions & 5 deletions common/src/main/java/org/tron/core/config/args/BlockConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,11 @@

import com.typesafe.config.Config;
import com.typesafe.config.ConfigBeanFactory;
import com.typesafe.config.ConfigFactory;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.tron.core.config.BeanDefaults;
import org.tron.core.exception.TronError;

/**
Expand All @@ -21,12 +23,10 @@
public class BlockConfig {

private boolean needSyncCheck = false;
private long maintenanceTimeInterval = 21600000L;
private long maintenanceTimeInterval = 6 * 3600 * 1000L; // 6 hours
private long proposalExpireTime = DEFAULT_PROPOSAL_EXPIRE_TIME;
private int checkFrozenTime = 1;

// Defaults come from reference.conf (loaded globally via Configuration.java)

/**
* Create BlockConfig from the "block" section of the application config.
* Also checks that committee.proposalExpireTime is not used (must use block.proposalExpireTime).
Expand All @@ -38,8 +38,12 @@ public static BlockConfig fromConfig(Config config) {
+ "config.conf, please set the value in block.proposalExpireTime.", PARAMETER_INIT);
}

Config blockSection = config.getConfig("block");
BlockConfig blockConfig = ConfigBeanFactory.create(blockSection, BlockConfig.class);
Config defaults = BeanDefaults.toConfig(new BlockConfig());
Config userSection = config.hasPath("block")
? BeanDefaults.stripNullLeaves(config.getConfig("block"))
: ConfigFactory.empty();
Config section = userSection.withFallback(defaults);
BlockConfig blockConfig = ConfigBeanFactory.create(section, BlockConfig.class);
blockConfig.postProcess();
return blockConfig;
}
Expand Down
51 changes: 11 additions & 40 deletions common/src/main/java/org/tron/core/config/args/CommitteeConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import com.typesafe.config.Config;
import com.typesafe.config.ConfigBeanFactory;
import com.typesafe.config.ConfigFactory;
import org.tron.core.config.BeanDefaults;
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.

[NIT] Import order: project imports placed between third-party imports

import org.tron.core.config.BeanDefaults; is inserted between com.typesafe.config.* and lombok.* imports, which breaks the project's Google Java Style import grouping (java/javax → com → org → lombok, with org.tron colocated with other org.*). Several other refactored files have the same issue (StorageConfig, etc.) — checkstyle CI will flag them.

Suggestion: run ./gradlew checkstyleMain locally and let it auto-format imports; the same fix should be applied to the other refactored config files.

import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
Expand Down Expand Up @@ -35,29 +37,8 @@ public class CommitteeConfig {
private long allowProtoFilterNum = 0;
private long allowAccountStateRoot = 0;
private long changedDelegation = 0;
// NON-STANDARD NAMING: "allowPBFT" and "pBFTExpireNum" in config.conf contain
// consecutive uppercase letters ("PBFT"), which violates JavaBean naming convention.
// ConfigBeanFactory derives config keys from setter names using JavaBean rules:
// setPBFTExpireNum -> property "PBFTExpireNum" (capital P, per JavaBean spec)
// but config.conf uses "pBFTExpireNum" (lowercase p) -> mismatch -> binding fails.
//
// These two fields are excluded from auto-binding and handled manually in fromConfig().
// TODO: Rename config keys to standard camelCase (allowPbft, pbftExpireNum) when
// PBFT feature is enabled and a breaking config change is acceptable.
@Getter(lombok.AccessLevel.NONE)
@Setter(lombok.AccessLevel.NONE)
private long allowPBFT = 0;
@Getter(lombok.AccessLevel.NONE)
@Setter(lombok.AccessLevel.NONE)
private long pBFTExpireNum = 20;

// Only getters are exposed. No public setters — ConfigBeanFactory scans public
// setters via reflection and would derive key "PBFTExpireNum" / "AllowPBFT"
// (JavaBean uppercase rule), which does not match config keys "pBFTExpireNum"
// / "allowPBFT" and would throw. Values are assigned to fields directly in
// fromConfig() below.
public long getAllowPBFT() { return allowPBFT; }
public long getPBFTExpireNum() { return pBFTExpireNum; }
private long allowTvmFreeze = 0;
private long allowTvmVote = 0;
private long allowTvmLondon = 0;
Expand Down Expand Up @@ -86,27 +67,17 @@ public class CommitteeConfig {

// proposalExpireTime is NOT a committee field — it's in block.* and handled by BlockConfig

// Defaults come from reference.conf (loaded globally via Configuration.java)

/**
* Create CommitteeConfig from the "committee" section of the application config.
*
* Note: allowPBFT and pBFTExpireNum have non-standard JavaBean naming (consecutive
* uppercase letters) which causes ConfigBeanFactory key mismatch. These two fields
* are excluded from automatic binding and handled manually after.
*/
private static final String PBFT_EXPIRE_NUM_KEY = "pBFTExpireNum";
private static final String ALLOW_PBFT_KEY = "allowPBFT";

public static CommitteeConfig fromConfig(Config config) {
Config section = config.getConfig("committee");

Config defaults = BeanDefaults.toConfig(new CommitteeConfig());
Config userSection = config.hasPath("committee")
? BeanDefaults.stripNullLeaves(config.getConfig("committee"))
: ConfigFactory.empty();
// pBFTExpireNum: config key uses lowercase-p prefix, but setPBFTExpireNum causes
// Introspector to derive "PBFTExpireNum" (consecutive uppercase prevents decapitalization).
// Remap so ConfigBeanFactory finds it under the expected key.
userSection = BeanDefaults.remapKey(userSection, "pBFTExpireNum", "PBFTExpireNum");
Config section = userSection.withFallback(defaults);
CommitteeConfig cc = ConfigBeanFactory.create(section, CommitteeConfig.class);
// Ensure the manually-named fields get the right values from the original keys
cc.allowPBFT = section.hasPath(ALLOW_PBFT_KEY) ? section.getLong(ALLOW_PBFT_KEY) : 0;
cc.pBFTExpireNum = section.hasPath(PBFT_EXPIRE_NUM_KEY)
? section.getLong(PBFT_EXPIRE_NUM_KEY) : 20;

cc.postProcess();
return cc;
}
Expand Down
Loading
Loading