66import com .typesafe .config .ConfigBeanFactory ;
77import com .typesafe .config .ConfigFactory ;
88import java .util .ArrayList ;
9- import java .util .Collections ;
109import java .util .List ;
1110import lombok .Getter ;
1211import lombok .Setter ;
1312import lombok .extern .slf4j .Slf4j ;
1413
15- /**
16- * Node configuration bean for the "node" section of config.conf.
17- *
18- * <p>This section is complex: it mixes flat scalars, dot-notation nested keys
19- * (e.g. "listen.port"), sub-objects (http, rpc, jsonrpc, p2p, dynamicConfig, dns),
20- * and list fields (active, passive, fastForward, disabledApi).
21- *
22- * <p>Strategy:
23- * <ul>
24- * <li>ConfigBeanFactory handles simple scalar fields and clean sub-objects</li>
25- * <li>Dot-notation fields (listen.port, connection.timeout, fetchBlock.timeout,
26- * solidity.threads) are read manually — HOCON parses them as nested objects,
27- * not flat keys, so ConfigBeanFactory cannot bind them to flat fields</li>
28- * <li>PBFT-named fields in sub-beans have the same JavaBean naming issue as
29- * CommitteeConfig — handled manually after binding</li>
30- * <li>List fields are read manually since ConfigBeanFactory expects bean lists</li>
31- * </ul>
32- */
14+ // Node configuration bean for the "node" section of config.conf.
15+ // ConfigBeanFactory auto-binds all fields including sub-beans, dot-notation keys,
16+ // PBFT fields, and list fields. Only legacy key fallbacks and PascalCase shutdown
17+ // keys are read manually.
3318@ Slf4j
3419@ Getter
3520@ Setter
@@ -50,26 +35,15 @@ public class NodeConfig {
5035 private boolean openPrintLog = true ;
5136 private boolean openTransactionSort = false ;
5237 private int maxTps = 1000 ;
53- // "isOpenFullTcpDisconnect" in config.conf: JavaBean convention converts
54- // setOpenFullTcpDisconnect -> key "openFullTcpDisconnect", but config uses
55- // "isOpenFullTcpDisconnect". Excluded from auto-binding, read manually.
38+ // Config key "isOpenFullTcpDisconnect" cannot auto-bind — read manually in fromConfig()
5639 @ Getter (lombok .AccessLevel .NONE )
5740 @ Setter (lombok .AccessLevel .NONE )
5841 private boolean isOpenFullTcpDisconnect = false ;
5942
6043 public boolean isOpenFullTcpDisconnect () { return isOpenFullTcpDisconnect ; }
6144
62- // node.discovery.* is a separate top-level section (not inside node {}).
63- // Excluded from auto-binding, read manually in fromConfig().
64- @ Getter (lombok .AccessLevel .NONE )
65- @ Setter (lombok .AccessLevel .NONE )
66- private boolean discoveryEnable = false ;
67- @ Getter (lombok .AccessLevel .NONE )
68- @ Setter (lombok .AccessLevel .NONE )
69- private boolean discoveryPersist = false ;
70- @ Getter (lombok .AccessLevel .NONE )
71- @ Setter (lombok .AccessLevel .NONE )
72- private String discoveryExternalIp = "" ;
45+ // node.discovery.* — HOCON merges into node { discovery { ... } }, auto-bound
46+ private DiscoveryConfig discovery = new DiscoveryConfig ();
7347
7448 // node.shutdown.* uses PascalCase keys (BlockTime, BlockHeight, BlockCount)
7549 // that don't match JavaBean naming. Excluded, read manually.
@@ -83,9 +57,11 @@ public class NodeConfig {
8357 @ Setter (lombok .AccessLevel .NONE )
8458 private long shutdownBlockCount = -1 ;
8559
86- public boolean isDiscoveryEnable () { return discoveryEnable ; }
87- public boolean isDiscoveryPersist () { return discoveryPersist ; }
88- public String getDiscoveryExternalIp () { return discoveryExternalIp ; }
60+ public boolean isDiscoveryEnable () { return discovery .isEnable (); }
61+ public boolean isDiscoveryPersist () { return discovery .isPersist (); }
62+ public String getDiscoveryExternalIp () {
63+ return discovery .getExternal ().getIp ();
64+ }
8965 public String getShutdownBlockTime () { return shutdownBlockTime ; }
9066 public long getShutdownBlockHeight () { return shutdownBlockHeight ; }
9167 public long getShutdownBlockCount () { return shutdownBlockCount ; }
@@ -151,7 +127,21 @@ public class NodeConfig {
151127 // ===========================================================================
152128
153129 // ---- Sub-beans for dot-notation config keys ----
154- // These match config.conf's nested structure (e.g., listen.port -> listen { port })
130+ // HOCON merges dot-notation into nested objects, ConfigBeanFactory auto-binds
131+
132+ @ Getter
133+ @ Setter
134+ public static class DiscoveryConfig {
135+ private boolean enable = true ;
136+ private boolean persist = true ;
137+ private ExternalConfig external = new ExternalConfig ();
138+
139+ @ Getter
140+ @ Setter
141+ public static class ExternalConfig {
142+ private String ip = "" ;
143+ }
144+ }
155145
156146 @ Getter
157147 @ Setter
@@ -372,18 +362,14 @@ public static class DnsConfig {
372362 public static NodeConfig fromConfig (Config config ) {
373363 Config section = config .getConfig ("node" );
374364
375- // --- Phase 1: Auto-bind flat scalars and sub-objects ---
376- // ConfigBeanFactory will bind all simple fields and nested sub-beans.
377- // It will skip dot-notation fields (they are nested objects, not scalar keys)
378- // and may mis-bind PBFT fields due to JavaBean naming.
365+ // Auto-bind all fields and sub-beans
379366 NodeConfig nc = ConfigBeanFactory .create (section , NodeConfig .class );
380367
381- // --- Phase 2: Dot-notation and naming-mismatch fields (manually read) ---
382- // listen, connection, fetchBlock, solidity, channel, validContractProto
383- // are now sub-beans — auto-bound by ConfigBeanFactory
368+ // isOpenFullTcpDisconnect: boolean "is" prefix breaks JavaBean pairing
384369 nc .isOpenFullTcpDisconnect = getBool (section , "isOpenFullTcpDisconnect" , false );
385370
386- // Legacy key fallback: node.maxActiveNodes (old) -> maxConnections (new)
371+ // --- Legacy key fallbacks (backward compatibility) ---
372+ // node.maxActiveNodes (old) -> maxConnections (new)
387373 if (section .hasPath ("maxActiveNodes" )) {
388374 nc .maxConnections = section .getInt ("maxActiveNodes" );
389375 if (section .hasPath ("connectFactor" )) {
@@ -404,50 +390,14 @@ public static NodeConfig fromConfig(Config config) {
404390 } else if (section .hasPath ("fullNodeAllowShieldedTransaction" )) {
405391 nc .allowShieldedTransactionApi = section .getBoolean ("fullNodeAllowShieldedTransaction" );
406392 }
407- nc .discoveryExternalIp = config .hasPath ("node.discovery.external.ip" )
408- ? config .getString ("node.discovery.external.ip" ).trim () : "" ;
409-
410- // node.discovery.* — dot-notation creates nested HOCON objects
411- Config discoverySection = config .hasPath ("node.discovery" )
412- ? config .getConfig ("node.discovery" ) : ConfigFactory .empty ();
413- nc .discoveryEnable = getBool (discoverySection , "enable" , false );
414- nc .discoveryPersist = getBool (discoverySection , "persist" , false );
415-
416- // node.shutdown.* — dot-notation
393+ // node.shutdown.* — PascalCase keys (BlockTime, BlockHeight), cannot auto-bind
417394 nc .shutdownBlockTime = config .hasPath ("node.shutdown.BlockTime" )
418395 ? config .getString ("node.shutdown.BlockTime" ) : "" ;
419396 nc .shutdownBlockHeight = config .hasPath ("node.shutdown.BlockHeight" )
420397 ? config .getLong ("node.shutdown.BlockHeight" ) : -1 ;
421398 nc .shutdownBlockCount = config .hasPath ("node.shutdown.BlockCount" )
422399 ? config .getLong ("node.shutdown.BlockCount" ) : -1 ;
423400
424- // node.channel.read.timeout — triple-dot-notation
425- // channel.read.timeout is now a sub-bean — auto-bound
426-
427- // --- Phase 3: PBFT fields in sub-beans (manually patch) ---
428- // http
429- Config httpSection = section .hasPath ("http" )
430- ? section .getConfig ("http" ) : ConfigFactory .empty ();
431- nc .http .pBFTEnable = getBool (httpSection , "PBFTEnable" , true );
432- nc .http .pBFTPort = getInt (httpSection , "PBFTPort" , 8092 );
433-
434- // rpc
435- Config rpcSection = section .hasPath ("rpc" )
436- ? section .getConfig ("rpc" ) : ConfigFactory .empty ();
437- nc .rpc .pBFTEnable = getBool (rpcSection , "PBFTEnable" , true );
438- nc .rpc .pBFTPort = getInt (rpcSection , "PBFTPort" , 50071 );
439-
440- // jsonrpc
441- Config jsonrpcSection = section .hasPath ("jsonrpc" )
442- ? section .getConfig ("jsonrpc" ) : ConfigFactory .empty ();
443- nc .jsonrpc .httpPBFTEnable = getBool (jsonrpcSection , "httpPBFTEnable" , false );
444- nc .jsonrpc .httpPBFTPort = getInt (jsonrpcSection , "httpPBFTPort" , 8565 );
445-
446- // --- Phase 4: List fields (manually read) ---
447- nc .active = getStringList (section , "active" );
448- nc .passive = getStringList (section , "passive" );
449- nc .fastForward = getStringList (section , "fastForward" );
450- nc .disabledApi = getStringList (section , "disabledApi" );
451401
452402 nc .postProcess ();
453403 return nc ;
@@ -526,10 +476,4 @@ private static String getString(Config config, String path, String defaultValue)
526476 return config .hasPath (path ) ? config .getString (path ) : defaultValue ;
527477 }
528478
529- private static List <String > getStringList (Config config , String path ) {
530- if (config .hasPath (path )) {
531- return config .getStringList (path );
532- }
533- return Collections .emptyList ();
534- }
535479}
0 commit comments