diff --git a/common/src/main/java/org/tron/common/parameter/CommonParameter.java b/common/src/main/java/org/tron/common/parameter/CommonParameter.java index f2831b4168f..3f57d64981a 100644 --- a/common/src/main/java/org/tron/common/parameter/CommonParameter.java +++ b/common/src/main/java/org/tron/common/parameter/CommonParameter.java @@ -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; @@ -445,8 +444,6 @@ public class CommonParameter { @Getter public Storage storage; @Getter - public Overlay overlay; - @Getter public SeedNode seedNode; @Getter public EventPluginConfig eventPluginConfig; diff --git a/common/src/main/java/org/tron/core/config/BeanDefaults.java b/common/src/main/java/org/tron/core/config/BeanDefaults.java new file mode 100644 index 00000000000..1b9d8836759 --- /dev/null +++ b/common/src/main/java/org/tron/core/config/BeanDefaults.java @@ -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; + +/** + * Generates a Typesafe {@link Config} from a bean instance's current field values. + * + *

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}. + * + *

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)}. + * + *

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

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) { + 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 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 toMap(Object bean) { + Map 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) { + // 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 ""; + } + if (value instanceof Boolean || value instanceof Number || value instanceof String) { + return value; + } + if (value instanceof List) { + List 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); + } +} diff --git a/common/src/main/java/org/tron/core/config/Configuration.java b/common/src/main/java/org/tron/core/config/Configuration.java index 80735290b8c..9870f56a194 100644 --- a/common/src/main/java/org/tron/core/config/Configuration.java +++ b/common/src/main/java/org/tron/core/config/Configuration.java @@ -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); } 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( diff --git a/common/src/main/java/org/tron/core/config/args/BlockConfig.java b/common/src/main/java/org/tron/core/config/args/BlockConfig.java index 4746f390e0c..a0e187f1b5e 100644 --- a/common/src/main/java/org/tron/core/config/args/BlockConfig.java +++ b/common/src/main/java/org/tron/core/config/args/BlockConfig.java @@ -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; /** @@ -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). @@ -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; } diff --git a/common/src/main/java/org/tron/core/config/args/CommitteeConfig.java b/common/src/main/java/org/tron/core/config/args/CommitteeConfig.java index 5cd9de842a0..5c698e82c82 100644 --- a/common/src/main/java/org/tron/core/config/args/CommitteeConfig.java +++ b/common/src/main/java/org/tron/core/config/args/CommitteeConfig.java @@ -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; import lombok.Getter; import lombok.Setter; import lombok.extern.slf4j.Slf4j; @@ -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; @@ -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; } diff --git a/common/src/main/java/org/tron/core/config/args/EventConfig.java b/common/src/main/java/org/tron/core/config/args/EventConfig.java index ac1731de2dc..0dd8750e148 100644 --- a/common/src/main/java/org/tron/core/config/args/EventConfig.java +++ b/common/src/main/java/org/tron/core/config/args/EventConfig.java @@ -8,6 +8,7 @@ import lombok.Getter; import lombok.Setter; import lombok.extern.slf4j.Slf4j; +import org.tron.core.config.BeanDefaults; /** * Event subscribe configuration bean. @@ -25,11 +26,9 @@ public class EventConfig { private String server = ""; private String dbconfig = ""; private boolean contractParse = true; - @Getter(lombok.AccessLevel.NONE) - @Setter(lombok.AccessLevel.NONE) + // Config key is "native" (Java reserved word); remapped to this field in fromConfig(). private NativeConfig nativeQueue = new NativeConfig(); - public NativeConfig getNativeQueue() { return nativeQueue; } // Topics list has optional fields (ethCompatible, redundancy, solidified) that // not all items have. ConfigBeanFactory requires all bean fields to exist in config. // Excluded from auto-binding, read manually in fromConfig(). @@ -37,12 +36,16 @@ public class EventConfig { @Setter(lombok.AccessLevel.NONE) private List topics = new ArrayList<>(); - public List getTopics() { return topics; } + public List getTopics() { + return topics; + } + private FilterConfig filter = new FilterConfig(); @Getter @Setter public static class NativeConfig { + private boolean useNativeQueue = true; private int bindport = 5555; private int sendqueuelength = 1000; @@ -51,6 +54,7 @@ public static class NativeConfig { @Getter @Setter public static class TopicConfig { + private String triggerName = ""; private boolean enable = false; private String topic = ""; @@ -62,14 +66,13 @@ public static class TopicConfig { @Getter @Setter public static class FilterConfig { + private String fromblock = ""; private String toblock = ""; private List contractAddress = new ArrayList<>(); private List contractTopic = new ArrayList<>(); } - // Defaults come from reference.conf (loaded globally via Configuration.java) - /** * Create EventConfig from the "event.subscribe" section of the application config. * @@ -77,55 +80,29 @@ public static class FilterConfig { * "nativeQueue" but config key is "native". We handle this manually after binding. */ public static EventConfig fromConfig(Config config) { - Config section = config.getConfig("event.subscribe"); - - // "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"); - EventConfig ec = ConfigBeanFactory.create(bindable, EventConfig.class); + Config defaults = BeanDefaults.toConfig(new EventConfig()); + Config userSection = config.hasPath("event.subscribe") + ? BeanDefaults.stripNullLeaves(config.getConfig("event.subscribe")) + : ConfigFactory.empty(); - // manually bind "native" sub-section - Config nativeSection = section.hasPath(nativeKey) - ? section.getConfig(nativeKey) : ConfigFactory.empty(); - ec.nativeQueue = new NativeConfig(); - if (nativeSection.hasPath("useNativeQueue")) { - ec.nativeQueue.useNativeQueue = nativeSection.getBoolean("useNativeQueue"); - } - if (nativeSection.hasPath("bindport")) { - ec.nativeQueue.bindport = nativeSection.getInt("bindport"); - } - if (nativeSection.hasPath("sendqueuelength")) { - ec.nativeQueue.sendqueuelength = nativeSection.getInt("sendqueuelength"); - } + // "native" is a Java reserved word — remap to the field name so ConfigBeanFactory + // auto-binds it as NativeConfig nativeQueue. topics has optional fields per item + // so it is excluded from auto-binding and populated manually below. + Config bindable = BeanDefaults.remapKey(userSection, "native", "nativeQueue") + .withoutPath("topics") + .withoutPath("topicDefaults") + .withFallback(defaults); + EventConfig ec = ConfigBeanFactory.create(bindable, EventConfig.class); - // manually bind topics — each item may have optional fields - if (section.hasPath(topicsKey)) { + // topics: apply per-item BeanDefaults so optional fields (solidified, ethCompatible, + // redundancy) don't require every item to declare them explicitly. + if (userSection.hasPath("topics")) { + Config topicDefaults = BeanDefaults.toConfig(new TopicConfig()); ec.topics = new ArrayList<>(); - for (com.typesafe.config.ConfigObject obj : section.getObjectList(topicsKey)) { - Config tc = obj.toConfig(); - TopicConfig topic = new TopicConfig(); - if (tc.hasPath("triggerName")) { - topic.triggerName = tc.getString("triggerName"); - } - if (tc.hasPath("enable")) { - topic.enable = tc.getBoolean("enable"); - } - if (tc.hasPath("topic")) { - topic.topic = tc.getString("topic"); - } - if (tc.hasPath("solidified")) { - topic.solidified = tc.getBoolean("solidified"); - } - if (tc.hasPath("ethCompatible")) { - topic.ethCompatible = tc.getBoolean("ethCompatible"); - } - if (tc.hasPath("redundancy")) { - topic.redundancy = tc.getBoolean("redundancy"); - } - ec.topics.add(topic); + for (com.typesafe.config.ConfigObject obj : userSection.getObjectList("topics")) { + ec.topics.add(ConfigBeanFactory.create( + BeanDefaults.stripNullLeaves(obj.toConfig()).withFallback(topicDefaults), + TopicConfig.class)); } } diff --git a/common/src/main/java/org/tron/core/config/args/GenesisConfig.java b/common/src/main/java/org/tron/core/config/args/GenesisConfig.java index a17e06d5c0f..6d87ede74f7 100644 --- a/common/src/main/java/org/tron/core/config/args/GenesisConfig.java +++ b/common/src/main/java/org/tron/core/config/args/GenesisConfig.java @@ -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; import java.util.ArrayList; import java.util.List; import lombok.Getter; @@ -41,10 +43,12 @@ 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 userSection = config.hasPath("genesis.block") + ? BeanDefaults.stripNullLeaves(config.getConfig("genesis.block")) + : ConfigFactory.empty(); + Config section = userSection.withFallback(defaults); return ConfigBeanFactory.create(section, GenesisConfig.class); } } diff --git a/common/src/main/java/org/tron/core/config/args/MetricsConfig.java b/common/src/main/java/org/tron/core/config/args/MetricsConfig.java index 5547dfa6d3a..d5034428009 100644 --- a/common/src/main/java/org/tron/core/config/args/MetricsConfig.java +++ b/common/src/main/java/org/tron/core/config/args/MetricsConfig.java @@ -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; import lombok.Getter; import lombok.Setter; import lombok.extern.slf4j.Slf4j; @@ -24,13 +26,21 @@ public static class PrometheusConfig { private int port = 9527; } - // Defaults come from reference.conf (loaded globally via Configuration.java) + @Getter + @Setter + public static class InfluxDbConfig { + private String ip = ""; + private int port = 8086; + private String database = "metrics"; + private int metricsReportInterval = 10; + } - /** - * 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 userSection = config.hasPath("node.metrics") + ? BeanDefaults.stripNullLeaves(config.getConfig("node.metrics")) + : ConfigFactory.empty(); + Config section = userSection.withFallback(defaults); return ConfigBeanFactory.create(section, MetricsConfig.class); } } diff --git a/common/src/main/java/org/tron/core/config/args/NodeConfig.java b/common/src/main/java/org/tron/core/config/args/NodeConfig.java index aac3e930931..2b931bb5557 100644 --- a/common/src/main/java/org/tron/core/config/args/NodeConfig.java +++ b/common/src/main/java/org/tron/core/config/args/NodeConfig.java @@ -12,6 +12,7 @@ import lombok.Getter; import lombok.Setter; import lombok.extern.slf4j.Slf4j; +import org.tron.core.config.BeanDefaults; import org.tron.core.exception.TronError; // Node configuration bean for the "node" section of config.conf. @@ -45,7 +46,9 @@ public class NodeConfig { @Setter(lombok.AccessLevel.NONE) private boolean isOpenFullTcpDisconnect = false; - public boolean isOpenFullTcpDisconnect() { return isOpenFullTcpDisconnect; } + public boolean isOpenFullTcpDisconnect() { + return isOpenFullTcpDisconnect; + } // node.discovery.* — HOCON merges into node { discovery { ... } }, auto-bound private DiscoveryConfig discovery = new DiscoveryConfig(); @@ -62,12 +65,30 @@ public class NodeConfig { @Setter(lombok.AccessLevel.NONE) private long shutdownBlockCount = -1; - public boolean isDiscoveryEnable() { return discovery.isEnable(); } - public boolean isDiscoveryPersist() { return discovery.isPersist(); } - public String getDiscoveryExternalIp() { return discovery.getExternal().getIp(); } - public String getShutdownBlockTime() { return shutdownBlockTime; } - public long getShutdownBlockHeight() { return shutdownBlockHeight; } - public long getShutdownBlockCount() { return shutdownBlockCount; } + public boolean isDiscoveryEnable() { + return discovery.isEnable(); + } + + public boolean isDiscoveryPersist() { + return discovery.isPersist(); + } + + public String getDiscoveryExternalIp() { + return discovery.getExternal().getIp(); + } + + public String getShutdownBlockTime() { + return shutdownBlockTime; + } + + public long getShutdownBlockHeight() { + return shutdownBlockHeight; + } + + public long getShutdownBlockCount() { + return shutdownBlockCount; + } + private int inactiveThreshold = 600; private boolean metricsEnable = false; private int blockProducedTimeOut = 50; @@ -95,8 +116,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(); @@ -105,12 +126,29 @@ public class NodeConfig { private SolidityConfig solidity = new SolidityConfig(); // Convenience getters for backward compatibility with applyNodeConfig - public int getListenPort() { return listen.getPort(); } - public int getConnectionTimeout() { return connection.getTimeout(); } - public int getFetchBlockTimeout() { return fetchBlock.getTimeout(); } - public int getSolidityThreads() { return solidity.getThreads(); } - public int getChannelReadTimeout() { return channel.getRead().getTimeout(); } - public int getValidContractProtoThreads() { return validContractProto.getThreads(); } + public int getListenPort() { + return listen.getPort(); + } + + public int getConnectionTimeout() { + return connection.getTimeout(); + } + + public int getFetchBlockTimeout() { + return fetchBlock.getTimeout(); + } + + public int getSolidityThreads() { + return solidity.getThreads(); + } + + public int getChannelReadTimeout() { + return channel.getRead().getTimeout(); + } + + public int getValidContractProtoThreads() { + return validContractProto.getThreads(); + } // ---- List fields (manually read) ---- private List active = new ArrayList<>(); @@ -206,69 +244,30 @@ public static class HttpConfig { private long maxMessageSize = 4194304; private int maxNestingDepth = 100; private int maxTokenCount = 100_000; - // PBFT fields — handled manually (same naming issue as CommitteeConfig) - // Default must match CommonParameter.pBFTHttpEnable = true - @Getter(lombok.AccessLevel.NONE) - @Setter(lombok.AccessLevel.NONE) + // pBFTEnable/pBFTPort: fromConfig() remaps "pBFTEnable"→"PBFTEnable" so + // ConfigBeanFactory finds these under the JavaBean-derived key name. private boolean pBFTEnable = true; - @Getter(lombok.AccessLevel.NONE) - @Setter(lombok.AccessLevel.NONE) private int pBFTPort = 8092; - - public boolean isPBFTEnable() { - return pBFTEnable; - } - - public void setPBFTEnable(boolean v) { - this.pBFTEnable = v; - } - - public int getPBFTPort() { - return pBFTPort; - } - - public void setPBFTPort(int v) { - this.pBFTPort = v; - } } @Getter @Setter public static class RpcConfig { + private boolean enable = true; private int port = 50051; private boolean solidityEnable = true; private int solidityPort = 50061; - // PBFT fields — handled manually - @Getter(lombok.AccessLevel.NONE) - @Setter(lombok.AccessLevel.NONE) + // pBFTEnable/pBFTPort: remapped in NodeConfig.fromConfig() (same reason as HttpConfig). private boolean pBFTEnable = true; - @Getter(lombok.AccessLevel.NONE) - @Setter(lombok.AccessLevel.NONE) private int pBFTPort = 50071; - public boolean isPBFTEnable() { - return pBFTEnable; - } - - public void setPBFTEnable(boolean v) { - this.pBFTEnable = v; - } - - public int getPBFTPort() { - return pBFTPort; - } - - public void setPBFTPort(int v) { - this.pBFTPort = v; - } - private int thread = 0; - private int maxConcurrentCallsPerConnection = 2147483647; - private int flowControlWindow = 1048576; + private int maxConcurrentCallsPerConnection = Integer.MAX_VALUE; + private int flowControlWindow = 1024 * 1024; private long maxConnectionIdleInMillis = Long.MAX_VALUE; private long maxConnectionAgeInMillis = Long.MAX_VALUE; - private int maxMessageSize = 4194304; + private int maxMessageSize = 4 * 1024 * 1024; private int maxHeaderListSize = 8192; private int maxRstStream = 0; private int secondsPerWindow = 0; @@ -280,34 +279,16 @@ public void setPBFTPort(int v) { @Getter @Setter public static class JsonRpcConfig { + private boolean httpFullNodeEnable = false; private int httpFullNodePort = 8545; private boolean httpSolidityEnable = false; private int httpSolidityPort = 8555; - // PBFT fields — handled manually - @Getter(lombok.AccessLevel.NONE) - @Setter(lombok.AccessLevel.NONE) + // httpPBFTEnable/httpPBFTPort: setHttpPBFTEnable → property "httpPBFTEnable" — matches + // config key directly, no remapping needed. private boolean httpPBFTEnable = false; - @Getter(lombok.AccessLevel.NONE) - @Setter(lombok.AccessLevel.NONE) private int httpPBFTPort = 8565; - public boolean isHttpPBFTEnable() { - return httpPBFTEnable; - } - - public void setHttpPBFTEnable(boolean v) { - this.httpPBFTEnable = v; - } - - public int getHttpPBFTPort() { - return httpPBFTPort; - } - - public void setHttpPBFTPort(int v) { - this.httpPBFTPort = v; - } - private int maxBlockRange = 5000; private int maxSubTopics = 1000; private int maxBlockFilterNum = 50000; @@ -353,8 +334,6 @@ public static class DnsConfig { private String awsHostZoneId = ""; } - // Defaults come from reference.conf (loaded globally via Configuration.java) - // =========================================================================== // Factory method // =========================================================================== @@ -366,22 +345,28 @@ public static class DnsConfig { * solidity.threads) become nested HOCON objects and cannot be auto-bound to flat * Java fields. They are read manually after ConfigBeanFactory binding. * - *

PBFT-named fields in http, rpc, and jsonrpc sub-beans have the same JavaBean - * naming issue as CommitteeConfig and are patched manually. + *

pBFT-prefixed fields in http and rpc sub-beans are remapped before binding + * (pBFTEnable → PBFTEnable) because consecutive uppercase letters prevent + * Introspector from decapitalizing the JavaBean property name. + * jsonrpc.httpPBFT* binds directly (httpPBFTEnable → property "httpPBFTEnable" ✓). * - *

List fields (active, passive, fastForward, disabledApi) are read manually - * since ConfigBeanFactory expects typed bean lists, not string lists. + *

List fields (active, passive, fastForward, disabledApi) auto-bind via + * ConfigBeanFactory's List<String> support. */ public static NodeConfig fromConfig(Config config) { - // Normalize human-readable size values (e.g. "4m") to numeric bytes so - // ConfigBeanFactory's primitive int/long binding succeeds; same step - // enforces non-negative and <= Integer.MAX_VALUE before bean creation - // so failures point at the user-facing config path. - Config section = normalizeMaxMessageSizes(config).getConfig("node"); - - // 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. + Config defaults = BeanDefaults.toConfig(new NodeConfig()); + Config userSection = config.hasPath("node") + ? BeanDefaults.stripNullLeaves(config.getConfig("node")) + : ConfigFactory.empty(); + // pBFTEnable/pBFTPort: config uses lowercase-p prefix; JavaBean derives "PBFTEnable" + // (consecutive uppercase prevents Introspector from decapitalizing). Remap so + // ConfigBeanFactory binds the user value instead of silently using the default. + userSection = BeanDefaults.remapKey(userSection, "http.pBFTEnable", "http.PBFTEnable"); + userSection = BeanDefaults.remapKey(userSection, "http.pBFTPort", "http.PBFTPort"); + userSection = BeanDefaults.remapKey(userSection, "rpc.pBFTEnable", "rpc.PBFTEnable"); + userSection = BeanDefaults.remapKey(userSection, "rpc.pBFTPort", "rpc.PBFTPort"); + Config section = userSection.withFallback(defaults); + NodeConfig nc = ConfigBeanFactory.create(section, NodeConfig.class); // isOpenFullTcpDisconnect: boolean "is" prefix breaks JavaBean pairing @@ -403,10 +388,11 @@ public static NodeConfig fromConfig(Config config) { nc.maxConnectionsWithSameIp = section.getInt("maxActiveNodesWithSameIp"); } - // Legacy key fallback: node.fullNodeAllowShieldedTransaction -> allowShieldedTransactionApi. - // reference.conf does not ship the legacy key, so hasPath here reliably means the user + // Legacy key fallback: node.allowShieldedTransactionApi -> fullNodeAllowShieldedTransaction. + // 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")) { + if (!userSection.hasPath("allowShieldedTransactionApi") && + userSection.hasPath("fullNodeAllowShieldedTransaction")) { nc.allowShieldedTransactionApi = section.getBoolean("fullNodeAllowShieldedTransaction"); logger.warn("Configuring [node.fullNodeAllowShieldedTransaction] will be deprecated. " + "Please use [node.allowShieldedTransactionApi] instead."); @@ -419,7 +405,6 @@ public static NodeConfig fromConfig(Config config) { nc.shutdownBlockCount = config.hasPath("node.shutdown.BlockCount") ? config.getLong("node.shutdown.BlockCount") : -1; - nc.postProcess(); return nc; } diff --git a/common/src/main/java/org/tron/core/config/args/Overlay.java b/common/src/main/java/org/tron/core/config/args/Overlay.java deleted file mode 100644 index bdaa40724c7..00000000000 --- a/common/src/main/java/org/tron/core/config/args/Overlay.java +++ /dev/null @@ -1,22 +0,0 @@ -package org.tron.core.config.args; - -import lombok.Getter; -import org.apache.commons.lang3.Range; - -public class Overlay { - - @Getter - private int port; - - /** - * Monitor port number. - */ - public void setPort(final int port) { - Range range = Range.between(0, 65535); - if (!range.contains(port)) { - throw new IllegalArgumentException("Port(" + port + ") must in [0, 65535]"); - } - - this.port = port; - } -} diff --git a/common/src/main/java/org/tron/core/config/args/RateLimiterConfig.java b/common/src/main/java/org/tron/core/config/args/RateLimiterConfig.java index eed5ef1898b..33f42569154 100644 --- a/common/src/main/java/org/tron/core/config/args/RateLimiterConfig.java +++ b/common/src/main/java/org/tron/core/config/args/RateLimiterConfig.java @@ -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; import java.util.ArrayList; import java.util.List; import lombok.Getter; @@ -66,10 +68,12 @@ 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 userSection = config.hasPath("rate.limiter") + ? BeanDefaults.stripNullLeaves(config.getConfig("rate.limiter")) + : ConfigFactory.empty(); + Config section = userSection.withFallback(defaults); return ConfigBeanFactory.create(section, RateLimiterConfig.class); } } diff --git a/common/src/main/java/org/tron/core/config/args/StorageConfig.java b/common/src/main/java/org/tron/core/config/args/StorageConfig.java index 3d7046ebae2..6981db23c72 100644 --- a/common/src/main/java/org/tron/core/config/args/StorageConfig.java +++ b/common/src/main/java/org/tron/core/config/args/StorageConfig.java @@ -2,7 +2,9 @@ import com.typesafe.config.Config; import com.typesafe.config.ConfigBeanFactory; +import com.typesafe.config.ConfigFactory; import com.typesafe.config.ConfigObject; +import org.tron.core.config.BeanDefaults; import java.util.ArrayList; import java.util.List; import lombok.Getter; @@ -31,38 +33,20 @@ public class StorageConfig { private TxCacheConfig txCache = new TxCacheConfig(); private List properties = new ArrayList<>(); - // merkleRoot is a nested object (e.g. { reward-vi = "hash..." }) not a string. - // Excluded from auto-binding, handled by Storage class directly. - @Getter(lombok.AccessLevel.NONE) - @Setter(lombok.AccessLevel.NONE) - private Object merkleRoot; - // Raw storage config sub-tree, kept for setCacheStrategies/setDbRoots which // have dynamic keys that ConfigBeanFactory cannot bind. - @Getter(lombok.AccessLevel.NONE) @Setter(lombok.AccessLevel.NONE) private Config rawStorageConfig; - public Config getRawStorageConfig() { - return rawStorageConfig; - } - // LevelDB per-database option overrides (default, defaultM, defaultL). // Excluded from auto-binding: optional partial overrides that ConfigBeanFactory cannot handle. - @Getter(lombok.AccessLevel.NONE) @Setter(lombok.AccessLevel.NONE) private DbOptionOverride defaultDbOption; - @Getter(lombok.AccessLevel.NONE) @Setter(lombok.AccessLevel.NONE) private DbOptionOverride defaultMDbOption; - @Getter(lombok.AccessLevel.NONE) @Setter(lombok.AccessLevel.NONE) private DbOptionOverride defaultLDbOption; - public DbOptionOverride getDefaultDbOption() { return defaultDbOption; } - public DbOptionOverride getDefaultMDbOption() { return defaultMDbOption; } - public DbOptionOverride getDefaultLDbOption() { return defaultLDbOption; } - @Getter @Setter public static class DbConfig { @@ -194,11 +178,16 @@ 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()); + Config userSection = config.hasPath("storage") + ? BeanDefaults.stripNullLeaves(config.getConfig("storage")) + : ConfigFactory.empty(); + // 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 because they are + // absent from BeanDefaults and only present when the user explicitly set them. + Config section = userSection.withFallback(defaults); StorageConfig sc = ConfigBeanFactory.create(section, StorageConfig.class); sc.rawStorageConfig = section; diff --git a/common/src/main/java/org/tron/core/config/args/VmConfig.java b/common/src/main/java/org/tron/core/config/args/VmConfig.java index 00ba85aa6cc..39b5449d78e 100644 --- a/common/src/main/java/org/tron/core/config/args/VmConfig.java +++ b/common/src/main/java/org/tron/core/config/args/VmConfig.java @@ -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; import lombok.AccessLevel; import lombok.Getter; import lombok.Setter; @@ -38,15 +40,14 @@ public class VmConfig { @Setter(AccessLevel.NONE) private long constantCallTimeoutMs = 0L; - /** - * 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"); - VmConfig vmConfig = ConfigBeanFactory.create(vmSection, VmConfig.class); - vmConfig.postProcess(vmSection); + Config defaults = BeanDefaults.toConfig(new VmConfig()); + Config userSection = config.hasPath("vm") + ? BeanDefaults.stripNullLeaves(config.getConfig("vm")) + : ConfigFactory.empty(); + Config section = userSection.withFallback(defaults); + VmConfig vmConfig = ConfigBeanFactory.create(section, VmConfig.class); + vmConfig.postProcess(userSection); return vmConfig; } diff --git a/common/src/test/java/org/tron/core/config/BeanDefaultsTest.java b/common/src/test/java/org/tron/core/config/BeanDefaultsTest.java new file mode 100644 index 00000000000..fff457e3e00 --- /dev/null +++ b/common/src/test/java/org/tron/core/config/BeanDefaultsTest.java @@ -0,0 +1,235 @@ +package org.tron.core.config; + +import com.typesafe.config.Config; +import com.typesafe.config.ConfigBeanFactory; +import com.typesafe.config.ConfigFactory; +import org.junit.Assert; +import org.junit.Test; +import org.tron.core.config.args.CommitteeConfig; +import org.tron.core.config.args.MetricsConfig; +import org.tron.core.config.args.NodeConfig; +import org.tron.core.config.args.RateLimiterConfig; +import org.tron.core.config.args.StorageConfig; +import org.tron.core.config.args.VmConfig; + +/** + * Verifies that BeanDefaults.toConfig() produces a Config that: + * 1. Contains the correct default values from Java field initializers. + * 2. Satisfies ConfigBeanFactory.create() without ConfigException.Missing. + * 3. Is properly overridden when a user value is supplied via withFallback(). + */ +public class BeanDefaultsTest { + + // ── VmConfig ───────────────────────────────────────────────────────────── + + @Test + public void vmConfig_defaultValues() { + Config cfg = BeanDefaults.toConfig(new VmConfig()); + + Assert.assertFalse(cfg.getBoolean("supportConstant")); + Assert.assertEquals(100_000_000L, cfg.getLong("maxEnergyLimitForConstant")); + Assert.assertEquals(500, cfg.getInt("lruCacheSize")); + Assert.assertEquals(0.0, cfg.getDouble("minTimeRatio"), 0.0); + Assert.assertEquals(5.0, cfg.getDouble("maxTimeRatio"), 0.0); + Assert.assertEquals(10, cfg.getInt("longRunningTime")); + Assert.assertFalse(cfg.getBoolean("estimateEnergy")); + Assert.assertEquals(3, cfg.getInt("estimateEnergyMaxRetry")); + Assert.assertFalse(cfg.getBoolean("vmTrace")); + Assert.assertFalse(cfg.getBoolean("saveInternalTx")); + Assert.assertFalse(cfg.getBoolean("saveFeaturedInternalTx")); + Assert.assertFalse(cfg.getBoolean("saveCancelAllUnfreezeV2Details")); + } + + @Test + public void vmConfig_roundTrip_withConfigBeanFactory() { + Config defaults = BeanDefaults.toConfig(new VmConfig()); + // ConfigBeanFactory must not throw ConfigException.Missing + VmConfig vm = ConfigBeanFactory.create(defaults, VmConfig.class); + Assert.assertFalse(vm.isSupportConstant()); + Assert.assertEquals(500, vm.getLruCacheSize()); + } + + @Test + public void vmConfig_userValueOverridesDefault() { + Config user = ConfigFactory.parseString("lruCacheSize = 999"); + Config merged = user.withFallback(BeanDefaults.toConfig(new VmConfig())); + VmConfig vm = ConfigBeanFactory.create(merged, VmConfig.class); + Assert.assertEquals(999, vm.getLruCacheSize()); + // other fields keep defaults + Assert.assertEquals(10, vm.getLongRunningTime()); + } + + // ── NodeConfig nested bean ──────────────────────────────────────────────── + + @Test + public void nodeConfig_defaultScalars() { + Config cfg = BeanDefaults.toConfig(new NodeConfig()); + + Assert.assertEquals(30, cfg.getInt("maxConnections")); + Assert.assertEquals(8, cfg.getInt("minConnections")); + Assert.assertEquals(1000, cfg.getInt("maxTps")); + Assert.assertTrue(cfg.getBoolean("openPrintLog")); + Assert.assertFalse(cfg.getBoolean("walletExtensionApi")); + } + + @Test + public void nodeConfig_nestedBeans_present() { + Config cfg = BeanDefaults.toConfig(new NodeConfig()); + + // listen.port should exist as a nested object + Assert.assertTrue(cfg.hasPath("listen")); + Assert.assertEquals(18888, cfg.getInt("listen.port")); + + // discovery.enable + Assert.assertTrue(cfg.hasPath("discovery")); + Assert.assertFalse(cfg.getBoolean("discovery.enable")); + + // http.fullNodeEnable + Assert.assertTrue(cfg.hasPath("http")); + Assert.assertTrue(cfg.getBoolean("http.fullNodeEnable")); + Assert.assertEquals(8090, cfg.getInt("http.fullNodePort")); + + // rpc.enable + Assert.assertTrue(cfg.hasPath("rpc")); + Assert.assertTrue(cfg.getBoolean("rpc.enable")); + Assert.assertEquals(50051, cfg.getInt("rpc.port")); + } + + @Test + public void nodeConfig_listFields_empty() { + Config cfg = BeanDefaults.toConfig(new NodeConfig()); + Assert.assertTrue(cfg.getList("active").isEmpty()); + Assert.assertTrue(cfg.getList("passive").isEmpty()); + Assert.assertTrue(cfg.getList("fastForward").isEmpty()); + Assert.assertTrue(cfg.getList("disabledApi").isEmpty()); + } + + @Test + public void nodeConfig_pBFTFields_usePropertyNameAsIs() { + Config cfg = BeanDefaults.toConfig(new NodeConfig()); + // setPBFTEnable → Introspector property name "PBFTEnable" (capital P, two consecutive + // uppercase letters → JavaBean spec forbids decapitalization). + // ConfigBeanFactory looks up configProps.get("PBFTEnable"), so the map key must match. + Assert.assertTrue(cfg.hasPath("http.PBFTEnable")); + Assert.assertTrue(cfg.getBoolean("http.PBFTEnable")); + Assert.assertEquals(8092, cfg.getInt("http.PBFTPort")); + + Assert.assertTrue(cfg.hasPath("rpc.PBFTEnable")); + Assert.assertEquals(50071, cfg.getInt("rpc.PBFTPort")); + } + + @Test + public void nodeConfig_roundTrip_withConfigBeanFactory() { + Config defaults = BeanDefaults.toConfig(new NodeConfig()); + // Must not throw — all keys present + NodeConfig nc = ConfigBeanFactory.create(defaults, NodeConfig.class); + Assert.assertEquals(30, nc.getMaxConnections()); + Assert.assertEquals(18888, nc.getListenPort()); + Assert.assertTrue(nc.getRpc().isEnable()); + } + + // ── StorageConfig nested bean ───────────────────────────────────────────── + + @Test + public void storageConfig_defaultValues() { + Config cfg = BeanDefaults.toConfig(new StorageConfig()); + + Assert.assertEquals("LEVELDB", cfg.getString("db.engine")); + Assert.assertFalse(cfg.getBoolean("db.sync")); + Assert.assertEquals("database", cfg.getString("db.directory")); + Assert.assertEquals(7, cfg.getInt("dbSettings.levelNumber")); + Assert.assertEquals(1, cfg.getInt("checkpoint.version")); + Assert.assertTrue(cfg.getBoolean("checkpoint.sync")); + Assert.assertEquals(1, cfg.getInt("snapshot.maxFlushCount")); + Assert.assertTrue(cfg.getList("properties").isEmpty()); + } + + @Test + public void storageConfig_roundTrip_withConfigBeanFactory() { + Config defaults = BeanDefaults.toConfig(new StorageConfig()); + StorageConfig sc = ConfigBeanFactory.create(defaults, StorageConfig.class); + Assert.assertEquals("LEVELDB", sc.getDb().getEngine()); + Assert.assertEquals(7, sc.getDbSettings().getLevelNumber()); + } + + // ── MetricsConfig nested sub-beans ─────────────────────────────────────── + + @Test + public void metricsConfig_defaultValues() { + Config cfg = BeanDefaults.toConfig(new MetricsConfig()); + + Assert.assertFalse(cfg.getBoolean("storageEnable")); + Assert.assertFalse(cfg.getBoolean("prometheus.enable")); + Assert.assertEquals(9527, cfg.getInt("prometheus.port")); + Assert.assertEquals("", cfg.getString("influxdb.ip")); + Assert.assertEquals(8086, cfg.getInt("influxdb.port")); + Assert.assertEquals("metrics", cfg.getString("influxdb.database")); + Assert.assertEquals(10, cfg.getInt("influxdb.metricsReportInterval")); + } + + @Test + public void metricsConfig_roundTrip() { + Config defaults = BeanDefaults.toConfig(new MetricsConfig()); + MetricsConfig mc = ConfigBeanFactory.create(defaults, MetricsConfig.class); + Assert.assertEquals(9527, mc.getPrometheus().getPort()); + } + + // ── RateLimiterConfig ──────────────────────────────────────────────────── + + @Test + public void rateLimiterConfig_defaultValues() { + Config cfg = BeanDefaults.toConfig(new RateLimiterConfig()); + + Assert.assertEquals(50000, cfg.getInt("global.qps")); + Assert.assertEquals(10000, cfg.getInt("global.ip.qps")); + Assert.assertEquals(1000, cfg.getInt("global.api.qps")); + Assert.assertTrue(cfg.getList("http").isEmpty()); + Assert.assertTrue(cfg.getList("rpc").isEmpty()); + } + + @Test + public void rateLimiterConfig_roundTrip() { + Config defaults = BeanDefaults.toConfig(new RateLimiterConfig()); + RateLimiterConfig rl = ConfigBeanFactory.create(defaults, RateLimiterConfig.class); + Assert.assertEquals(50000, rl.getGlobal().getQps()); + Assert.assertTrue(rl.getHttp().isEmpty()); + } + + // ── CommitteeConfig ─────────────────────────────────────────────────────── + + @Test + public void committeeConfig_allZeroDefaults() { + Config cfg = BeanDefaults.toConfig(new CommitteeConfig()); + + Assert.assertEquals(0L, cfg.getLong("allowCreationOfContracts")); + Assert.assertEquals(0L, cfg.getLong("allowMultiSign")); + Assert.assertEquals(0L, cfg.getLong("allowTvmCancun")); + } + + @Test + public void committeeConfig_roundTrip() { + Config defaults = BeanDefaults.toConfig(new CommitteeConfig()); + CommitteeConfig cc = ConfigBeanFactory.create(defaults, CommitteeConfig.class); + Assert.assertEquals(0L, cc.getAllowCreationOfContracts()); + } + + @Test + public void stripNullLeaves_removesNullPaths() { + Config cfg = ConfigFactory.parseString("a = null\nb = 1\nc.d = null\nc.e = 2"); + Config stripped = BeanDefaults.stripNullLeaves(cfg); + Assert.assertFalse(stripped.hasPath("a")); + Assert.assertTrue(stripped.hasPath("b")); + Assert.assertFalse(stripped.hasPath("c.d")); + Assert.assertTrue(stripped.hasPath("c.e")); + } + + @Test + public void nodeConfig_fromConfig_toleratesNullExternalIp() { + // Legacy configs used "node.discovery.external.ip = null" — must not throw. + Config cfg = ConfigFactory.parseString( + "node { discovery { external { ip = null } } }"); + NodeConfig nc = NodeConfig.fromConfig(cfg); + Assert.assertNotNull(nc); + Assert.assertEquals("", nc.getDiscoveryExternalIp()); + } +} diff --git a/common/src/test/java/org/tron/core/config/args/EventConfigTest.java b/common/src/test/java/org/tron/core/config/args/EventConfigTest.java index 361d9f48581..1aaca42f6fc 100644 --- a/common/src/test/java/org/tron/core/config/args/EventConfigTest.java +++ b/common/src/test/java/org/tron/core/config/args/EventConfigTest.java @@ -22,11 +22,11 @@ private static Config withRef() { public void testDefaults() { Config empty = withRef(); EventConfig ec = EventConfig.fromConfig(empty); - // reference.conf has event.subscribe with enable=false, topics with 7 entries + // BeanDefaults provides scalar defaults; topics list is empty by default (user must configure) assertFalse(ec.isEnable()); assertEquals(0, ec.getVersion()); assertEquals("", ec.getPath()); - assertFalse(ec.getTopics().isEmpty()); // reference.conf has default topic entries + assertTrue(ec.getTopics().isEmpty()); } @Test diff --git a/common/src/test/java/org/tron/core/config/args/GenesisConfigTest.java b/common/src/test/java/org/tron/core/config/args/GenesisConfigTest.java index 5e653a79b7f..4f3e8829ade 100644 --- a/common/src/test/java/org/tron/core/config/args/GenesisConfigTest.java +++ b/common/src/test/java/org/tron/core/config/args/GenesisConfigTest.java @@ -22,10 +22,10 @@ private static Config withRef() { public void testDefaults() { Config empty = withRef(); GenesisConfig gc = GenesisConfig.fromConfig(empty); - // reference.conf has genesis.block with timestamp, parentHash, assets, witnesses - assertEquals("0", gc.getTimestamp()); - assertFalse(gc.getAssets().isEmpty()); // reference.conf has seed accounts - assertFalse(gc.getWitnesses().isEmpty()); // reference.conf has seed witnesses + // BeanDefaults: timestamp/parentHash default to "", assets/witnesses to empty lists + assertEquals("", gc.getTimestamp()); + assertTrue(gc.getAssets().isEmpty()); + assertTrue(gc.getWitnesses().isEmpty()); } @Test diff --git a/common/src/test/java/org/tron/core/config/args/NodeConfigTest.java b/common/src/test/java/org/tron/core/config/args/NodeConfigTest.java index a52c51c1ba4..5dbc161c4f8 100644 --- a/common/src/test/java/org/tron/core/config/args/NodeConfigTest.java +++ b/common/src/test/java/org/tron/core/config/args/NodeConfigTest.java @@ -105,10 +105,10 @@ public void testRpcDefaultsFromReference() { NodeConfig.RpcConfig rpc = nc.getRpc(); // reference.conf provides actual final defaults, no sentinel conversion needed - assertEquals(2147483647, rpc.getMaxConcurrentCallsPerConnection()); + assertEquals(Integer.MAX_VALUE, rpc.getMaxConcurrentCallsPerConnection()); assertEquals(1048576, rpc.getFlowControlWindow()); - assertEquals(9223372036854775807L, rpc.getMaxConnectionIdleInMillis()); - assertEquals(9223372036854775807L, rpc.getMaxConnectionAgeInMillis()); + assertEquals(Long.MAX_VALUE, rpc.getMaxConnectionIdleInMillis()); + assertEquals(Long.MAX_VALUE, rpc.getMaxConnectionAgeInMillis()); assertEquals(4194304, rpc.getMaxMessageSize()); assertEquals(8192, rpc.getMaxHeaderListSize()); assertEquals(1, rpc.getMinEffectiveConnection()); @@ -307,13 +307,13 @@ public void testShieldedApiLegacyKeyRespected() { } @Test - public void testShieldedApiLegacyKeyTakesPriorityOverModern() { - // Consistent with maxActiveNodesWithSameIp: legacy key presence wins over modern. + public void testShieldedApiModernKeyTakesPriorityOverLegacy() { + // When both keys are present, allowShieldedTransactionApi (modern) wins. NodeConfig nc = NodeConfig.fromConfig( withRef("node {\n" + " allowShieldedTransactionApi = false\n" + " fullNodeAllowShieldedTransaction = true\n" + "}")); - assertTrue(nc.isAllowShieldedTransactionApi()); + assertFalse(nc.isAllowShieldedTransactionApi()); } } diff --git a/framework/src/main/java/org/tron/core/config/args/Args.java b/framework/src/main/java/org/tron/core/config/args/Args.java index de8b7dba1ad..68cb9740f16 100644 --- a/framework/src/main/java/org/tron/core/config/args/Args.java +++ b/framework/src/main/java/org/tron/core/config/args/Args.java @@ -61,7 +61,6 @@ import org.tron.p2p.P2pConfig; import org.tron.p2p.dns.update.DnsType; import org.tron.p2p.dns.update.PublishConfig; -import org.tron.p2p.utils.NetUtil; import org.tron.program.Version; @Slf4j(topic = "app") diff --git a/framework/src/main/resources/config.conf b/framework/src/main/resources/config.conf index b180ecd6d10..1021637a437 100644 --- a/framework/src/main/resources/config.conf +++ b/framework/src/main/resources/config.conf @@ -11,20 +11,22 @@ storage { # Whether to write transaction result in transactionRetStore transHistory.switch = "on", + index.directory = "index", + index.switch = "on", # setting can improve leveldb performance .... start, deprecated for arm # node: if this will increase process fds,you may be check your ulimit if 'too many open files' error occurs # see https://github.com/tronprotocol/tips/blob/master/tip-343.md for detail # if you find block sync has lower performance, you can try this settings - # default = { - # maxOpenFiles = 100 - # } - # defaultM = { - # maxOpenFiles = 500 - # } - # defaultL = { - # maxOpenFiles = 1000 - # } + default = { + # maxOpenFiles = 100 + } + defaultM = { + # maxOpenFiles = 500 + } + defaultL = { + # maxOpenFiles = 1000 + } # setting can improve leveldb performance .... end, deprecated for arm # You can customize the configuration for each database. Otherwise, the database settings will use @@ -77,18 +79,18 @@ storage { balance.history.lookup = false - # checkpoint.version = 2 - # checkpoint.sync = true + checkpoint.version = 1 + checkpoint.sync = true - # the estimated number of block transactions (default 1000, min 100, max 10000). - # so the total number of cached transactions is 65536 * txCache.estimatedTransactions - # txCache.estimatedTransactions = 1000 + # the estimated number of block transactions (min 100, max 10000). + # total cached transactions = 65536 * txCache.estimatedTransactions + txCache.estimatedTransactions = 1000 # if true, transaction cache initialization will be faster. Default: false txCache.initOptimization = true # The number of blocks flushed to db in each batch during node syncing. Default: 1 - # snapshot.maxFlushCount = 1 + snapshot.maxFlushCount = 1 # data root setting, for check data, currently, only reward-vi is used. # merkleRoot = { @@ -98,8 +100,9 @@ storage { } node.discovery = { - enable = true - persist = true + enable = true # default: false + persist = true # default: false + # external.ip = "" # default: "" (auto-detect) } # custom stop condition @@ -143,16 +146,16 @@ node.metrics = { node { # trust node for solidity node # trustNode = "ip:port" - trustNode = "127.0.0.1:50051" + trustNode = "" # expose extension api to public or not - walletExtensionApi = true + walletExtensionApi = false listen.port = 18888 connection.timeout = 2 - fetchBlock.timeout = 200 + fetchBlock.timeout = 500 # syncFetchBatchNum = 2000 # Maximum number of blocks allowed in-flight (requested but not yet processed). @@ -200,6 +203,40 @@ node { isOpenFullTcpDisconnect = false inactiveThreshold = 600 //seconds + # Maximum number of fast-forward peers. Default: 4 + maxFastForwardNum = 4 + + # Netty work thread counts; 0 = auto (availableProcessors). Default: tcp=0, udp=1 + tcpNettyWorkThreadNum = 0 + udpNettyWorkThreadNum = 1 + + # Maximum number of shielded transactions in the pending pool. Default: 10 + shieldedTransInPendingMaxCounts = 10 + + # Block cache timeout in seconds. Default: 60 + blockCacheTimeout = 60 + + # Minimum data length (bytes) to read from TCP. Default: 2048 + receiveTcpMinDataLength = 2048 + + # Maximum pending transaction pool size. Default: 2000 + maxTransactionPendingSize = 2000 + + # Timeout for pending transactions in milliseconds. Default: 60000 + pendingTransactionTimeout = 60000 + + # Required agreement count for block consensus; 0 = auto (2/3 of witness count + 1). Default: 0 + agreeNodeCount = 0 + + # Enable node-level metrics collection (prerequisite for prometheus/influxdb reporting). Default: false + metricsEnable = false + + # Channel read timeout in seconds; 0 = no timeout. Default: 0 + channel.read.timeout = 0 + + # Threads for contract protobuf validation; 0 = auto (availableProcessors). Default: 0 + validContractProto.threads = 0 + p2p { version = 11111 # Mainnet:11111; Nile:201910292; Shasta:1 } @@ -281,6 +318,9 @@ node { # The switch of the reflection service, effective for all gRPC services, used for grpcurl tool. Default: false reflectionService = false + + # Cache transactions in the RPC layer. Default: false + trxCacheEnable = false } # number of solidity thread in the FullNode. @@ -466,15 +506,17 @@ rate.limiter = { ] p2p = { - # syncBlockChain = 3.0 - # fetchInvData = 3.0 - # disconnect = 1.0 + syncBlockChain = 3.0 + fetchInvData = 3.0 + disconnect = 1.0 } # global qps, default 50000 global.qps = 50000 # IP-based global qps, default 10000 global.ip.qps = 10000 + # API-based global qps, default 1000 + global.api.qps = 1000 } @@ -877,3 +919,7 @@ event.subscribe = { ] } } + +# Block number from which the energy limit calculation applies. +# Note: the config key contains a legacy typo ("enery") preserved for backward compatibility. +# enery.limit.block.num = 4727890 diff --git a/framework/src/test/java/org/tron/common/ParameterTest.java b/framework/src/test/java/org/tron/common/ParameterTest.java index 1e7bbc1545c..9575dfc197a 100644 --- a/framework/src/test/java/org/tron/common/ParameterTest.java +++ b/framework/src/test/java/org/tron/common/ParameterTest.java @@ -224,7 +224,6 @@ public void testCommonParameter() { assertEquals(1000, parameter.getRateLimiterGlobalQps()); parameter.setRateLimiterGlobalIpQps(100); assertEquals(100, parameter.getRateLimiterGlobalIpQps()); - assertNull(parameter.getOverlay()); assertNull(parameter.getEventPluginConfig()); assertNull(parameter.getEventFilter()); parameter.setCryptoEngine(ECKey_ENGINE); diff --git a/framework/src/test/java/org/tron/core/config/args/ArgsTest.java b/framework/src/test/java/org/tron/core/config/args/ArgsTest.java index bb3d1c4b210..9bec7f46eff 100644 --- a/framework/src/test/java/org/tron/core/config/args/ArgsTest.java +++ b/framework/src/test/java/org/tron/core/config/args/ArgsTest.java @@ -161,7 +161,7 @@ public void testInitService() { storage.put("storage.db.directory", "database"); Config config = ConfigFactory.defaultOverrides() .withFallback(ConfigFactory.parseMap(storage)) - .withFallback(ConfigFactory.defaultReference()); + .withFallback(ConfigFactory.empty()); // test default value Args.applyConfigParams(config); Assert.assertTrue(Args.getInstance().isRpcEnable()); @@ -190,7 +190,7 @@ public void testInitService() { storage.put("node.jsonrpc.maxSubTopics", "20"); config = ConfigFactory.defaultOverrides() .withFallback(ConfigFactory.parseMap(storage)) - .withFallback(ConfigFactory.defaultReference()); + .withFallback(ConfigFactory.empty()); // test value Args.applyConfigParams(config); Assert.assertTrue(Args.getInstance().isRpcEnable()); @@ -219,7 +219,7 @@ public void testInitService() { storage.put("node.jsonrpc.maxSubTopics", "1000"); config = ConfigFactory.defaultOverrides() .withFallback(ConfigFactory.parseMap(storage)) - .withFallback(ConfigFactory.defaultReference()); + .withFallback(ConfigFactory.empty()); // test value Args.applyConfigParams(config); Assert.assertFalse(Args.getInstance().isRpcEnable()); @@ -248,7 +248,7 @@ public void testInitService() { storage.put("node.jsonrpc.maxSubTopics", "40"); config = ConfigFactory.defaultOverrides() .withFallback(ConfigFactory.parseMap(storage)) - .withFallback(ConfigFactory.defaultReference()); + .withFallback(ConfigFactory.empty()); // test value Args.applyConfigParams(config); Assert.assertFalse(Args.getInstance().isRpcEnable()); @@ -268,7 +268,7 @@ public void testInitService() { storage.put("node.jsonrpc.maxSubTopics", "0"); config = ConfigFactory.defaultOverrides() .withFallback(ConfigFactory.parseMap(storage)) - .withFallback(ConfigFactory.defaultReference()); + .withFallback(ConfigFactory.empty()); // check value Args.applyConfigParams(config); Assert.assertEquals(0, Args.getInstance().getJsonRpcMaxBlockRange()); @@ -279,7 +279,7 @@ public void testInitService() { storage.put("node.jsonrpc.maxSubTopics", "-4"); config = ConfigFactory.defaultOverrides() .withFallback(ConfigFactory.parseMap(storage)) - .withFallback(ConfigFactory.defaultReference()); + .withFallback(ConfigFactory.empty()); // check value Args.applyConfigParams(config); Assert.assertEquals(-2, Args.getInstance().getJsonRpcMaxBlockRange()); @@ -381,7 +381,7 @@ public void testFetchBlockTimeoutClampedBelowMin() { override.put("storage.db.directory", "database"); override.put("node.fetchBlock.timeout", "50"); Config config = ConfigFactory.parseMap(override) - .withFallback(ConfigFactory.defaultReference()); + .withFallback(ConfigFactory.empty()); Args.applyConfigParams(config); Assert.assertEquals(100, Args.getInstance().getFetchBlockTimeout()); Args.clearParam(); @@ -393,7 +393,7 @@ public void testFetchBlockTimeoutClampedAboveMax() { override.put("storage.db.directory", "database"); override.put("node.fetchBlock.timeout", "2000"); Config config = ConfigFactory.parseMap(override) - .withFallback(ConfigFactory.defaultReference()); + .withFallback(ConfigFactory.empty()); Args.applyConfigParams(config); Assert.assertEquals(1000, Args.getInstance().getFetchBlockTimeout()); Args.clearParam(); @@ -434,7 +434,7 @@ public void testFetchBlockTimeoutInRangeUnchanged() { override.put("storage.db.directory", "database"); override.put("node.fetchBlock.timeout", "500"); Config config = ConfigFactory.parseMap(override) - .withFallback(ConfigFactory.defaultReference()); + .withFallback(ConfigFactory.empty()); Args.applyConfigParams(config); Assert.assertEquals(500, Args.getInstance().getFetchBlockTimeout()); Args.clearParam(); diff --git a/framework/src/test/java/org/tron/core/config/args/OverlayTest.java b/framework/src/test/java/org/tron/core/config/args/OverlayTest.java deleted file mode 100644 index 1b7045c5b21..00000000000 --- a/framework/src/test/java/org/tron/core/config/args/OverlayTest.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * java-tron is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * java-tron is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package org.tron.core.config.args; - -import org.junit.Assert; -import org.junit.Before; -import org.junit.Test; - -public class OverlayTest { - - private Overlay overlay = new Overlay(); - - @Before - public void setOverlay() { - overlay.setPort(8080); - } - - @Test(expected = IllegalArgumentException.class) - public void whenSetOutOfBoundsPort() { - overlay.setPort(-1); - } - - @Test - public void getOverlay() { - Assert.assertEquals(8080, overlay.getPort()); - } -}