Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
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
93 changes: 93 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,93 @@
package org.tron.core.config;

import com.typesafe.config.Config;
import com.typesafe.config.ConfigFactory;
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;

/**
* 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));
}

private static Map<String, Object> toMap(Object bean) {
Map<String, Object> map = new LinkedHashMap<>();
try {
BeanInfo info = Introspector.getBeanInfo(bean.getClass());
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.
String key = pd.getName();
Object value = getter.invoke(bean);
map.put(key, toValue(value));
}
} catch (Exception ignored) {
// Best-effort: any unresolvable field is simply omitted.
// ConfigBeanFactory will throw with a clear path if a required key is missing.
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
return map;
}

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

Choose a reason for hiding this comment

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

P1: parseFile(confFile) drops the default reference.conf fallback, so file-based config loading can lose required defaults and diverge from classpath loading behavior.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At common/src/main/java/org/tron/core/config/Configuration.java, line 51:

<comment>`parseFile(confFile)` drops the default `reference.conf` fallback, so file-based config loading can lose required defaults and diverge from classpath loading behavior.</comment>

<file context>
@@ -48,10 +48,11 @@ public static com.typesafe.config.Config getByFileName(
     if (confFile.exists()) {
-      config = ConfigFactory.parseFile(confFile)
-          .withFallback(ConfigFactory.defaultReference());
+      config = ConfigFactory.parseFile(confFile);
     } else if (Thread.currentThread().getContextClassLoader().getResourceAsStream(fileName)
         != null) {
</file context>
Suggested change
config = ConfigFactory.parseFile(confFile);
config = ConfigFactory.parseFile(confFile)
.withFallback(ConfigFactory.defaultReference());

} else if (Thread.currentThread().getContextClassLoader().getResourceAsStream(fileName)
!= null) {
// 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
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

import com.typesafe.config.Config;
import com.typesafe.config.ConfigBeanFactory;
import org.tron.core.config.BeanDefaults;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
Expand All @@ -25,8 +26,6 @@ public class BlockConfig {
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,7 +37,10 @@ public static BlockConfig fromConfig(Config config) {
+ "config.conf, please set the value in block.proposalExpireTime.", PARAMETER_INIT);
}

Config blockSection = config.getConfig("block");
Config defaults = BeanDefaults.toConfig(new BlockConfig());
Config blockSection = config.hasPath("block")
? config.getConfig("block").withFallback(defaults)
: defaults;
BlockConfig blockConfig = ConfigBeanFactory.create(blockSection, BlockConfig.class);
blockConfig.postProcess();
return blockConfig;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import com.typesafe.config.Config;
import com.typesafe.config.ConfigBeanFactory;
import org.tron.core.config.BeanDefaults;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
Expand Down Expand Up @@ -87,8 +88,6 @@ 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.
*
Expand All @@ -100,8 +99,10 @@ public class CommitteeConfig {
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 section = config.hasPath("committee")
? config.getConfig("committee").withFallback(defaults)
: 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;
Expand Down
13 changes: 9 additions & 4 deletions common/src/main/java/org/tron/core/config/args/EventConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import com.typesafe.config.Config;
import com.typesafe.config.ConfigBeanFactory;
import com.typesafe.config.ConfigFactory;
import org.tron.core.config.BeanDefaults;
import java.util.ArrayList;
import java.util.List;
import lombok.Getter;
Expand Down Expand Up @@ -68,23 +69,27 @@ public static class FilterConfig {
private List<String> contractTopic = new ArrayList<>();
}

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

/**
* Create EventConfig from the "event.subscribe" section of the application config.
*
* <p>Note: HOCON key "native" is a Java reserved word, so the bean field is named
* "nativeQueue" but config key is "native". We handle this manually after binding.
*/
public static EventConfig fromConfig(Config config) {
Config section = config.getConfig("event.subscribe");
// BeanDefaults covers enable/version/startSyncBlockNum/path/server/dbconfig/
// contractParse/filter. nativeQueue and topics are excluded (no public setter).
Config defaults = BeanDefaults.toConfig(new EventConfig());
Config section = config.hasPath("event.subscribe")
? config.getConfig("event.subscribe")
: ConfigFactory.empty();

// "native" is a Java reserved word, "topics" has optional fields per item —
// strip both before binding, read manually
String nativeKey = "native";
String topicsKey = "topics";
Config bindable = section.withoutPath(nativeKey).withoutPath(topicsKey)
.withoutPath("topicDefaults");
.withoutPath("topicDefaults")
.withFallback(defaults);
EventConfig ec = ConfigBeanFactory.create(bindable, EventConfig.class);

// manually bind "native" sub-section
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import com.typesafe.config.Config;
import com.typesafe.config.ConfigBeanFactory;
import org.tron.core.config.BeanDefaults;
import java.util.ArrayList;
import java.util.List;
import lombok.Getter;
Expand Down Expand Up @@ -41,10 +42,11 @@ public static class WitnessConfig {
private long voteCount = 0;
}

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

public static GenesisConfig fromConfig(Config config) {
Config section = config.getConfig("genesis.block");
Config defaults = BeanDefaults.toConfig(new GenesisConfig());
Config section = config.hasPath("genesis.block")
? config.getConfig("genesis.block").withFallback(defaults)
: defaults;
return ConfigBeanFactory.create(section, GenesisConfig.class);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import com.typesafe.config.Config;
import com.typesafe.config.ConfigBeanFactory;
import org.tron.core.config.BeanDefaults;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
Expand Down Expand Up @@ -35,13 +36,11 @@ public static class InfluxDbConfig {
private int metricsReportInterval = 10;
}
Comment on lines +29 to +36

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Wire InfluxDbConfig into MetricsConfig so it can actually bind.

InfluxDbConfig is declared, but MetricsConfig has no corresponding field/getter. As a result, Line 39 (BeanDefaults.toConfig(new MetricsConfig())) and Line 44 (ConfigBeanFactory.create(...)) will ignore these defaults and user values.

Suggested fix
 public class MetricsConfig {

   private PrometheusConfig prometheus = new PrometheusConfig();
+  private InfluxDbConfig influxDb = new InfluxDbConfig();

   `@Getter`
   `@Setter`
   public static class PrometheusConfig {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@common/src/main/java/org/tron/core/config/args/MetricsConfig.java` around
lines 29 - 36, MetricsConfig currently doesn’t expose the nested InfluxDbConfig
so defaults and bindings are ignored; add a field of type InfluxDbConfig with
getter/setter (and initialize it to new InfluxDbConfig()) inside the
MetricsConfig class so BeanDefaults.toConfig(new MetricsConfig()) and
ConfigBeanFactory.create(...) can pick up defaults and user values; refer to the
nested class name InfluxDbConfig and the outer class MetricsConfig when adding
the private InfluxDbConfig influxDb = new InfluxDbConfig() plus corresponding
getInfluxDb()/setInfluxDb() methods.


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

/**
* Create MetricsConfig from the "node.metrics" section of the application config.
*/
public static MetricsConfig fromConfig(Config config) {
Config section = config.getConfig("node.metrics");
Config defaults = BeanDefaults.toConfig(new MetricsConfig());
Config section = config.hasPath("node.metrics")
? config.getConfig("node.metrics").withFallback(defaults)
: defaults;
return ConfigBeanFactory.create(section, MetricsConfig.class);
}
}
15 changes: 8 additions & 7 deletions common/src/main/java/org/tron/core/config/args/NodeConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import com.typesafe.config.Config;
import com.typesafe.config.ConfigBeanFactory;
import com.typesafe.config.ConfigFactory;
import org.tron.core.config.BeanDefaults;
import java.util.ArrayList;
import java.util.List;
import lombok.Getter;
Expand Down Expand Up @@ -89,8 +90,8 @@ public class NodeConfig {
private double activeConnectFactor = 0.1;
private double connectFactor = 0.6;
// Legacy alias `maxActiveNodesWithSameIp` has no bean field: we only peek at it via
// section.hasPath() below. Keeping it field-less means reference.conf doesn't have to
// ship a default that would otherwise mask the modern `maxConnectionsWithSameIp` key.
// section.hasPath() below. Keeping it field-less means BeanDefaults does not emit a
// default that would mask the modern `maxConnectionsWithSameIp` key.

// ---- Sub-beans matching config's dot-notation nested structure ----
private ListenConfig listen = new ListenConfig();
Expand Down Expand Up @@ -339,8 +340,6 @@ public static class DnsConfig {
private String awsHostZoneId = "";
}

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

// ===========================================================================
// Factory method
// ===========================================================================
Expand All @@ -359,8 +358,10 @@ public static class DnsConfig {
* since ConfigBeanFactory expects typed bean lists, not string lists.
*/
public static NodeConfig fromConfig(Config config) {
Config section = config.getConfig("node");

Config defaults = BeanDefaults.toConfig(new NodeConfig());
Config section = config.hasPath("node")
? config.getConfig("node").withFallback(defaults)
: defaults;
// Auto-bind all fields and sub-beans. ConfigBeanFactory fails fast with a
// descriptive path on any `= null` value — external configs that use the
// HOCON null keyword should fix their config rather than rely on silent coercion.
Expand All @@ -386,7 +387,7 @@ public static NodeConfig fromConfig(Config config) {
}

// Legacy key fallback: node.fullNodeAllowShieldedTransaction -> allowShieldedTransactionApi.
// reference.conf does not ship the legacy key, so hasPath here reliably means the user
// BeanDefaults does not emit this legacy key, so hasPath here reliably means the user
// set it in their config. When present, it overrides the modern key.
if (section.hasPath("fullNodeAllowShieldedTransaction")) {
nc.allowShieldedTransactionApi = section.getBoolean("fullNodeAllowShieldedTransaction");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import com.typesafe.config.Config;
import com.typesafe.config.ConfigBeanFactory;
import org.tron.core.config.BeanDefaults;
import java.util.ArrayList;
import java.util.List;
import lombok.Getter;
Expand Down Expand Up @@ -66,10 +67,11 @@ public static class RpcRateLimitItem {
private String paramString = "";
}

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

public static RateLimiterConfig fromConfig(Config config) {
Config section = config.getConfig("rate.limiter");
Config defaults = BeanDefaults.toConfig(new RateLimiterConfig());
Config section = config.hasPath("rate.limiter")
? config.getConfig("rate.limiter").withFallback(defaults)
: defaults;
return ConfigBeanFactory.create(section, RateLimiterConfig.class);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import com.typesafe.config.Config;
import com.typesafe.config.ConfigBeanFactory;
import org.tron.core.config.BeanDefaults;
import com.typesafe.config.ConfigObject;
import java.util.ArrayList;
import java.util.List;
Expand Down Expand Up @@ -205,11 +206,14 @@ public static class PropertyConfig {
private int maxOpenFiles = 100;
}

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

public static StorageConfig fromConfig(Config config) {
Config section = config.getConfig("storage");

Config defaults = BeanDefaults.toConfig(new StorageConfig());
// User's storage section takes priority; defaults fill in any omitted scalar keys.
// readDbOption() uses hasPath() on the merged section, so user-set optional keys
// (default, defaultM, defaultL) are still detected correctly.
Config section = config.hasPath("storage")
? config.getConfig("storage").withFallback(defaults)
: defaults;
StorageConfig sc = ConfigBeanFactory.create(section, StorageConfig.class);
sc.rawStorageConfig = section;

Expand Down
11 changes: 5 additions & 6 deletions common/src/main/java/org/tron/core/config/args/VmConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import com.typesafe.config.Config;
import com.typesafe.config.ConfigBeanFactory;
import org.tron.core.config.BeanDefaults;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
Expand All @@ -28,13 +29,11 @@ public class VmConfig {
private boolean saveFeaturedInternalTx = false;
private boolean saveCancelAllUnfreezeV2Details = false;

/**
* Create VmConfig from the "vm" section of the application config.
* Defaults come from reference.conf (loaded globally via Configuration.java),
* so no per-bean DEFAULTS needed.
*/
public static VmConfig fromConfig(Config config) {
Config vmSection = config.getConfig("vm");
Config defaults = BeanDefaults.toConfig(new VmConfig());
Config vmSection = config.hasPath("vm")
? config.getConfig("vm").withFallback(defaults)
: defaults;
VmConfig vmConfig = ConfigBeanFactory.create(vmSection, VmConfig.class);
vmConfig.postProcess();
return vmConfig;
Expand Down
Loading
Loading