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 7c8c16ed422..4bd06c1108b 100644 --- a/common/src/main/java/org/tron/common/parameter/CommonParameter.java +++ b/common/src/main/java/org/tron/common/parameter/CommonParameter.java @@ -316,9 +316,6 @@ public class CommonParameter { public List backupMembers; @Getter @Setter - public long receiveTcpMinDataLength; // clearParam: 2048 - @Getter - @Setter public boolean isOpenFullTcpDisconnect; @Getter @Setter 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 82619726b7e..527bea948d8 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 @@ -17,6 +17,7 @@ // ConfigBeanFactory auto-binds all fields including sub-beans, dot-notation keys, // PBFT fields, and list fields. Only legacy key fallbacks and PascalCase shutdown // keys are read manually. +// Always construct via {@link #fromConfig} — direct construction skips postProcess() clamping. @Slf4j @Getter @Setter @@ -77,7 +78,6 @@ public String getDiscoveryExternalIp() { private ValidContractProtoConfig validContractProto = new ValidContractProtoConfig(); private int shieldedTransInPendingMaxCounts = 10; private long blockCacheTimeout = 60; - private long receiveTcpMinDataLength = 2048; private int maxTransactionPendingSize = 2000; private long pendingTransactionTimeout = 60000; private int maxTrxCacheSize = 50_000; @@ -217,10 +217,10 @@ public static class RpcConfig { private int pBFTPort = 50071; private int thread = 0; - private int maxConcurrentCallsPerConnection = 2147483647; + private int maxConcurrentCallsPerConnection = 0; private int flowControlWindow = 1048576; - private long maxConnectionIdleInMillis = Long.MAX_VALUE; - private long maxConnectionAgeInMillis = Long.MAX_VALUE; + private long maxConnectionIdleInMillis = 0; + private long maxConnectionAgeInMillis = 0; private int maxMessageSize = 4194304; private int maxHeaderListSize = 8192; private int maxRstStream = 0; @@ -279,8 +279,8 @@ public static class DnsConfig { private String dnsPrivate = ""; private List knownUrls = new ArrayList<>(); private List staticNodes = new ArrayList<>(); - private int maxMergeSize = 0; - private double changeThreshold = 0.0; + private int maxMergeSize = 5; + private double changeThreshold = 0.1; private String serverType = ""; private String accessKeyId = ""; private String accessKeySecret = ""; @@ -296,8 +296,7 @@ public static class DnsConfig { * since ConfigBeanFactory expects typed bean lists, not string lists. */ public static NodeConfig fromConfig(Config config) { - Config section = normalizeNonStandardKeys( - normalizeMaxMessageSizes(config).getConfig("node")); + Config section = normalizeNonStandardKeys(config.getConfig("node")); // Auto-bind all fields and sub-beans. ConfigBeanFactory fails fast with a // descriptive path on any `= null` value @@ -306,20 +305,28 @@ public static NodeConfig fromConfig(Config config) { // --- Legacy key fallbacks (backward compatibility) --- // node.maxActiveNodes (old) -> maxConnections (new) if (section.hasPath("maxActiveNodes")) { + logger.warn("Configuring [node.maxActiveNodes] is deprecated and will be removed in a future " + + "release. Please use [node.maxConnections] instead."); nc.maxConnections = section.getInt("maxActiveNodes"); if (section.hasPath("connectFactor")) { + logger.warn("Configuring [node.connectFactor] is deprecated and will be removed in a future " + + "release."); nc.minConnections = (int) (nc.maxConnections * section.getDouble("connectFactor")); } if (section.hasPath("activeConnectFactor")) { + logger.warn("Configuring [node.activeConnectFactor] is deprecated and will be removed in a " + + "future release."); nc.minActiveConnections = (int) (nc.maxConnections * section.getDouble("activeConnectFactor")); } } if (section.hasPath("maxActiveNodesWithSameIp")) { + logger.warn("Configuring [node.maxActiveNodesWithSameIp] is deprecated and will be removed " + + "in a future release. Please use [node.maxConnectionsWithSameIp] instead."); nc.maxConnectionsWithSameIp = section.getInt("maxActiveNodesWithSameIp"); } - // Legacy key fallback: node.fullNodeAllowShieldedTransaction -> allowShieldedTransactionApi. + // Legacy key fallback: node.allowShieldedTransactionApi wins fullNodeAllowShieldedTransaction if (section.hasPath("allowShieldedTransactionApi")) { nc.allowShieldedTransactionApi = section.getBoolean("allowShieldedTransactionApi"); @@ -353,6 +360,16 @@ private void postProcess() { rpc.thread = (Runtime.getRuntime().availableProcessors() + 1) / 2; } + if (rpc.maxConcurrentCallsPerConnection == 0) { + rpc.maxConcurrentCallsPerConnection = Integer.MAX_VALUE; + } + if (rpc.maxConnectionIdleInMillis == 0) { + rpc.maxConnectionIdleInMillis = Long.MAX_VALUE; + } + if (rpc.maxConnectionAgeInMillis == 0) { + rpc.maxConnectionAgeInMillis = Long.MAX_VALUE; + } + // validateSignThreadNum: 0 = auto-detect if (validateSignThreadNum == 0) { validateSignThreadNum = Runtime.getRuntime().availableProcessors(); @@ -376,6 +393,14 @@ private void postProcess() { syncFetchBatchNum = 100; } + // fetchBlock.timeout : clamp to [100, 1000] + if (fetchBlock.timeout > 1000) { + fetchBlock.timeout = 1000; + } + if (fetchBlock.timeout < 100) { + fetchBlock.timeout = 100; + } + // maxPendingBlockSize: clamp to [50, 2000] if (maxPendingBlockSize > 2000) { maxPendingBlockSize = 2000; @@ -427,6 +452,20 @@ private void postProcess() { if (maxTrxCacheSize < 2000) { maxTrxCacheSize = 2000; } + + // maxMessageSize: reject negative values + if (rpc.maxMessageSize < 0) { + throw new TronError("node.rpc.maxMessageSize must be non-negative, got: " + + rpc.maxMessageSize, PARAMETER_INIT); + } + if (http.maxMessageSize < 0) { + throw new TronError("node.http.maxMessageSize must be non-negative, got: " + + http.maxMessageSize, PARAMETER_INIT); + } + if (jsonrpc.maxMessageSize < 0) { + throw new TronError("node.jsonrpc.maxMessageSize must be non-negative, got: " + + jsonrpc.maxMessageSize, PARAMETER_INIT); + } } // =========================================================================== @@ -467,36 +506,4 @@ private static Config normalizeNonStandardKeys(Config section) { return section; } - /** - * Pre-normalize size paths so ConfigBeanFactory's primitive int/long binding succeeds - * for human-readable values like "4m" / "128MB". For each maxMessageSize key, parse - * via getMemorySize, validate non-negative and <= Integer.MAX_VALUE, and write the - * numeric byte value back into the Config tree. Validation errors propagate before - * bean creation so the failure points at the user-facing config path. - */ - private static Config normalizeMaxMessageSizes(Config config) { - String[] paths = { - "node.rpc.maxMessageSize", - "node.http.maxMessageSize", - "node.jsonrpc.maxMessageSize" - }; - Config result = config; - for (String path : paths) { - if (config.hasPath(path)) { - long bytes = parseMaxMessageSize(config, path); - result = result.withValue(path, ConfigValueFactory.fromAnyRef(bytes)); - } - } - return result; - } - - private static long parseMaxMessageSize(Config config, String key) { - long value = config.getMemorySize(key).toBytes(); - if (value < 0 || value > Integer.MAX_VALUE) { - throw new TronError(key + " must be non-negative and <= " - + Integer.MAX_VALUE + ", got: " + value, PARAMETER_INIT); - } - return value; - } - } diff --git a/common/src/main/resources/reference.conf b/common/src/main/resources/reference.conf index 0864f4d5126..b016ef7ba6d 100644 --- a/common/src/main/resources/reference.conf +++ b/common/src/main/resources/reference.conf @@ -72,19 +72,27 @@ storage { # } # setting can improve leveldb performance .... end, deprecated for arm - # Example per-database overrides: + # Per-database storage configuration overrides. Otherwise databases use global defaults and store + # data in "output-directory" or the directory specified by the "-d" / "--output-directory" option. + # Attention: name is a required field that must be set! + # The name and path properties take effect for both LevelDB and RocksDB storage engines, + # while additional properties (createIfMissing, paranoidChecks, compressionType, etc.) + # only take effect when using LevelDB. + # Example: + # properties = [ # { # name = "account", # path = "storage_directory_test", - # createIfMissing = true, + # createIfMissing = true, // deprecated for arm start # paranoidChecks = true, # verifyChecksums = true, # compressionType = 1, // compressed with snappy # blockSize = 4096, // 4 KB = 4 * 1024 B # writeBufferSize = 10485760, // 10 MB = 10 * 1024 * 1024 B # cacheSize = 10485760, // 10 MB = 10 * 1024 * 1024 B - # maxOpenFiles = 100 + # maxOpenFiles = 100 // deprecated for arm end # } + # ] properties = [] needToUpdateAsset = true @@ -138,12 +146,11 @@ node.discovery = { # } node.backup { - port = 10001 - priority = 0 - keepAliveInterval = 3000 + port = 10001 # UDP listen port; each member should have the same configuration + priority = 0 # Node priority; each member should use a different priority + keepAliveInterval = 3000 # Keep-alive interval (ms); each member should have the same configuration members = [ - # "ip", - # "ip" + # "ip", # Peer IP list, better to add at most one IP; must not contain this node's own IP ] } @@ -188,7 +195,12 @@ node { maxHttpConnectNumber = 50 minParticipationRate = 0 - # Whether to enable shielded transaction API + # WARNING: Some shielded transaction APIs require sending private keys as parameters. + # Calling these APIs on untrusted or remote nodes may leak your private keys. + # It is recommended to invoke them locally for development and testing. + # To opt in, set: allowShieldedTransactionApi = true + # Migration: the legacy key node.fullNodeAllowShieldedTransaction is still supported + # but deprecated; please migrate to node.allowShieldedTransactionApi. # allowShieldedTransactionApi = false # Whether to print config log at startup @@ -207,8 +219,7 @@ node { isOpenFullTcpDisconnect = false inactiveThreshold = 600 // seconds maxFastForwardNum = 4 - activeConnectFactor = 0.1 - connectFactor = 0.6 + # Legacy alias `maxActiveNodesWithSameIp` is still accepted from user config # (see NodeConfig alias-fallback) but is intentionally NOT defaulted here — # shipping it in reference.conf would always mask the modern `maxConnectionsWithSameIp`. @@ -243,8 +254,10 @@ node { PBFTEnable = true PBFTPort = 8092 - # Maximum HTTP request body size, default 4MB. Independent from rpc.maxMessageSize. - maxMessageSize = 4M + # Maximum HTTP request body size in bytes (default 4194304, ~4MB). + # Must be a non-negative integer. Setting to 0 rejects all non-empty request bodies. + # Independent from rpc.maxMessageSize. + maxMessageSize = 4194304 maxNestingDepth = 100 maxTokenCount = 100000 } @@ -261,19 +274,20 @@ node { thread = 0 # Maximum concurrent calls per incoming connection - # No limit on concurrent calls per connection - maxConcurrentCallsPerConnection = 2147483647 + # 0 means No limit on concurrent calls per connection + maxConcurrentCallsPerConnection = 0 # HTTP/2 flow control window (bytes), default 1MB flowControlWindow = 1048576 - # Connection idle timeout (ms). No limit by default. - maxConnectionIdleInMillis = 9223372036854775807 + # Connection idle timeout (ms). Connections idle longer than this are gracefully terminated. 0 = no limit + maxConnectionIdleInMillis = 0 - # Connection max age (ms). No limit by default. - maxConnectionAgeInMillis = 9223372036854775807 + # Connection max age (ms). 0 = no limit + maxConnectionAgeInMillis = 0 - # Maximum message size (bytes), default 4MB + # Maximum gRPC message size in bytes (default 4194304, ~4MB). + # Must be a non-negative integer. Setting to 0 rejects all non-empty messages. maxMessageSize = 4194304 # Maximum header list size (bytes), default 8192 @@ -326,7 +340,6 @@ node { blockCacheTimeout = 60 # TCP and transaction limits - receiveTcpMinDataLength = 2048 maxTransactionPendingSize = 2000 pendingTransactionTimeout = 60000 # total cached trx across handler queues + pending + rePush @@ -343,28 +356,50 @@ node { validContractProto.threads = 0 dns { + # DNS URLs to discover peers, format: tree://{pubkey}@{domain}. Default: empty. treeUrls = [ # "tree://AKMQMNAJJBL73LXWPXDI4I5ZWWIZ4AWO34DWQ636QOBBXNFXH3LQS@main.trondisco.net", ] + + # Enable or disable DNS publish. Default: false. publish = false + # DNS domain to publish nodes, required if publish is true. dnsDomain = "" + # DNS private key used to publish, required if publish is true, hex string of length 64. dnsPrivate = "" + # Known DNS URLs to publish if publish is true, format: tree://{pubkey}@{domain}. Default: empty. knownUrls = [] + # Static nodes to publish on DNS, "ip:port". staticNodes = [] - maxMergeSize = 0 - changeThreshold = 0.0 + # Merge several nodes into a leaf of tree, should be 1~5. + maxMergeSize = 5 + # Only update DNS data when node change percent exceeds this threshold. + changeThreshold = 0.1 + # DNS server to publish, required if publish is true. Supported values: "aws", "aliyun". serverType = "" + # Access key ID of AWS or Aliyun API, required if publish is true. accessKeyId = "" + # Access key secret of AWS or Aliyun API, required if publish is true. accessKeySecret = "" + # Endpoint of Aliyun DNS server, required if serverType is "aliyun". aliyunDnsEndpoint = "" + # Region of AWS API (e.g. "us-east-1"), required if serverType is "aws". awsRegion = "" + # Host zone ID of AWS domain, required if serverType is "aws". awsHostZoneId = "" } # Open history query APIs on lite FullNode (may return null for some queries) openHistoryQueryWhenLiteFN = false + # Deprecated: these fields were used by the old connection-factor algorithm. + # They are still accepted from user config for backward compatibility but have no effect. + activeConnectFactor = 0.1 + connectFactor = 0.6 + jsonrpc { + # Note: Before release_4.8.1, if you turn on jsonrpc and run it for a while and then turn it off, + # you will not be able to get the data from eth_getLogs for that period of time. Default: false httpFullNodeEnable = false httpFullNodePort = 8545 httpSolidityEnable = false @@ -386,8 +421,8 @@ node { maxResponseSize = 26214400 # Allowed maximum number for newFilter, <=0 means no limit maxLogFilterNum = 20000 - # Maximum JSON-RPC request body size, default 4MB. Independent from rpc.maxMessageSize. - maxMessageSize = 4M + # Maximum JSON-RPC request body size in bytes (default 4194304, ~4MB). Independent from rpc.maxMessageSize. + maxMessageSize = 4194304 } # Disabled API list (works for http, rpc and pbft, not jsonrpc). Case insensitive. @@ -699,7 +734,16 @@ vm = { # Max retry time for executing transaction in estimating energy estimateEnergyMaxRetry = 3 - # Max TVM execution time (ms) for constant calls. 0 means no effect + # Max TVM execution time (ms) for constant calls — applies to + # triggerconstantcontract, triggersmartcontract dispatched to view/pure + # functions, estimateenergy, eth_call, eth_estimateGas, and any other RPC + # routed through the constant-call path. When set, must be a positive + # integer that fits VM deadline conversion and is used verbatim as the + # per-call deadline (no clamp against the network's maxCpuTimeOfOneTx). + # Omit the property entirely to keep the default behaviour of sharing the + # block-processing deadline. Migration note: if previously running --debug + # to extend constant calls, switch to this option (--debug also extends + # block-processing, which is unsafe; see issue #6266). Default: 0 (no effect). constantCallTimeoutMs = 0 } @@ -758,34 +802,38 @@ event.subscribe = { enable = false native = { - useNativeQueue = false - bindport = 5555 - sendqueuelength = 1000 + useNativeQueue = false // if true, use native message queue, else use event plugin. + bindport = 5555 // bind port + sendqueuelength = 1000 // max length of send queue } version = 0 + # Specify the starting block number to sync historical events. Only applicable when version = 1. + # After performing a full event sync, set this value to 0 or a negative number. startSyncBlockNum = 0 - path = "" - server = "" + path = "" // absolute path of plugin + server = "" // target server address to receive event triggers, "ip:port" + # dbname|username|password. To auto-create indexes on missing collections, append |2: + # dbname|username|password|2 (if collection exists, indexes must be created manually). dbconfig = "" contractParse = true topics = [ { - triggerName = "block" + triggerName = "block" // block trigger, the value can't be modified enable = false - topic = "block" - solidified = false + topic = "block" // plugin topic, the value could be modified + solidified = false // if set true, just need solidified block. Default: false }, { triggerName = "transaction" enable = false topic = "transaction" solidified = false - ethCompatible = false + ethCompatible = false // if set true, add transactionIndex, cumulativeEnergyUsed, preCumulativeLogCount, logList, energyUnitPrice. Default: false }, { - triggerName = "contractevent" + triggerName = "contractevent" // contractevent represents contractlog data decoded by the ABI. enable = false topic = "contractevent" }, @@ -793,11 +841,11 @@ event.subscribe = { triggerName = "contractlog" enable = false topic = "contractlog" - redundancy = false + redundancy = false // if set true, contractevent will also be regarded as contractlog }, { - triggerName = "solidity" - enable = true + triggerName = "solidity" // solidity block trigger (just block number and timestamp), the value can't be modified + enable = true // Default: true topic = "solidity" }, { @@ -809,18 +857,18 @@ event.subscribe = { triggerName = "soliditylog" enable = false topic = "soliditylog" - redundancy = false + redundancy = false // if set true, solidityevent will also be regarded as soliditylog } ] filter = { - fromblock = "" - toblock = "" + fromblock = "" // "", "earliest", or a specific block number as the beginning of the queried range + toblock = "" // "", "latest", or a specific block number as end of the queried range contractAddress = [ - "" + "" // contract address to subscribe; "" means any contract address ] contractTopic = [ - "" + "" // contract topic to subscribe; "" means any contract topic ] } } diff --git a/docs/configuration-conventions.md b/docs/configuration-conventions.md new file mode 100644 index 00000000000..6d456c1ba05 --- /dev/null +++ b/docs/configuration-conventions.md @@ -0,0 +1,208 @@ +# HOCON Configuration Conventions for Developers + +This document covers the rules and patterns that developers must follow when adding or modifying configuration parameters in java-tron. Violations cause silent misreads, startup failures, or hard-to-diagnose defaults being applied instead of user-supplied values. + +## Configuration Parameter vs. Constant: Which One to Use? + +Before writing any code, decide whether the value belongs in a config file or in source code as a constant. Getting this wrong creates dead configuration surface (parameters that exist but are never tuned) or inflexibility (values that should be adjustable but aren't). + +### Use a configuration parameter when + +- **Different deployments legitimately need different values.** Port numbers, peer lists, storage paths, block-production timeouts, and rate limits vary by environment (mainnet / testnet / private chain) or by hardware capacity. +- **Operators may need to tune the value without rebuilding.** Examples: thread pool sizes, connection limits, QPS caps. +- **The value is an on/off feature flag with production-safe semantics.** The flag must be safe to flip while the rest of the system is unchanged (e.g. `node.rpc.reflectionService`, `vm.estimateEnergy`). +- **The default differs across deployment scenarios.** If the mainnet default and the private-chain default are different, it belongs in config so each can override. + +### Use a constant when + +- **No operator would ever need to change it.** Protocol-level numbers (address prefix bytes, transaction size ceilings, energy unit ratios) are part of the chain specification — changing them causes a fork. +- **The value is a technical limit determined by the implementation, not the deployment.** Jackson `StreamReadConstraints` (`MAX_NESTING_DEPTH`, `MAX_TOKEN_COUNT`) guard against malformed input; no legitimate request comes close to the limit and no operator tunes it. +- **The "configurability" is an illusion.** If the value is captured in a `static final` field at class-load time (before config is applied), a config key is misleading — it appears tunable but changes are silently ignored. Convert to a constant and document why. +- **The value is derived from other constants or from the Java runtime.** Use `Runtime.getRuntime().availableProcessors()` or arithmetic on existing constants; don't push the formula into a config file. +- **No code path reads the value after assignment.** A parameter that exists in `reference.conf` and propagates through `NodeConfig → Args → CommonParameter` but is never consumed by business logic is dead weight. Delete it entirely (see `receiveTcpMinDataLength` as a past example). + +### The warning signs of a misplaced parameter + +| Symptom | Likely problem | +|---------|---------------| +| Parameter exists in `reference.conf` but `grep` finds no call site beyond the binding chain | Dead parameter — delete it | +| Value is read from a `static final` field initialized before `Args.setParam()` | Config change is silently ignored — convert to constant | +| Operator sets the value and nothing changes | Same as above, or value is clamped away in `postProcess()` | +| Parameter controls something that would cause a network fork if mismatched across nodes | Must be a constant, not configurable | +| Parameter has been at its default value in every known deployment for over a year | Candidate for removal or promotion to constant | + +## How Config Keys Bind to Java Fields + +java-tron uses [Typesafe Config](https://github.com/lightbend/config)'s `ConfigBeanFactory` to map a HOCON section to a Java bean automatically. The mapping algorithm is: + +1. For each field `fooBar` in the bean, `ConfigBeanFactory` looks for a HOCON key named `fooBar`. +2. The bean class must expose a public setter (`setFooBar`) — in practice this is provided by Lombok `@Setter`. +3. If the key is absent from the config, the field keeps its Java default value (the one assigned in the field declaration). +4. If the key is present but the type does not match, binding fails with a `ConfigException` at startup. + +The binding entry point for each top-level section looks like: + +```java +// "node" section → NodeConfig bean +Config section = config.getConfig("node"); +NodeConfig nc = ConfigBeanFactory.create(section, NodeConfig.class); +``` + +## Key Naming: Use camelCase + +**All keys in `reference.conf` and `config.conf` must use standard camelCase.** + +`ConfigBeanFactory` derives the expected key name from the Java setter via the JavaBean Introspector: `setFooBar` → property name `fooBar` → expected HOCON key `fooBar`. If the key in the config file uses a different casing, the binding silently skips it and the field keeps its Java default. + +```hocon +# Correct +node { + maxConnections = 30 + syncFetchBatchNum = 2000 +} + +# Wrong — ConfigBeanFactory cannot find these +node { + MaxConnections = 30 # PascalCase → ignored + sync_fetch_batch_num = 2000 # snake_case → ignored + max-connections = 30 # kebab-case → ignored +} +``` + +### The PBFT Exception + +Two legacy keys under `committee` (`allowPBFT`, `pBFTExpireNum`) and the HTTP/RPC fields (`PBFTEnable`, `PBFTPort`) were introduced with non-standard casing before this rule was established. They are retained as-is in the config files for backward compatibility. **Do not model new keys after them.** + +For `allowPBFT` and `pBFTExpireNum`, `CommitteeConfig.normalizeNonStandardKeys()` renames them to proper camelCase (`allowPbft`, `pbftExpireNum`) before handing the section to `ConfigBeanFactory`. If you ever need to accept a non-standard key from users while binding to a standard field, follow this same pattern. + +### The `is` Prefix Exception + +A HOCON key named `isOpenFullTcpDisconnect` produces the setter `setIsOpenFullTcpDisconnect`, but the JavaBean Introspector derives the property name as `openFullTcpDisconnect` (stripping `is`), so `ConfigBeanFactory` looks for key `openFullTcpDisconnect`. `NodeConfig.normalizeNonStandardKeys()` renames the key at read time for backward compatibility. **Do not add new keys with an `is` prefix.** + +## Nesting Depth + +The CI gate enforces a hard ceiling of **5 levels** (the historical maximum in `reference.conf`). New parameters must stay within **3 levels** from the top-level section. The gap between 3 and 5 is reserved for legacy paths that already exist — it is not a license to add new deep keys. + +``` +level 1: node { ... } +level 2: node { rpc { ... } } +level 3: node { rpc { flowControl { ... } } } ← limit for new keys +level 4+: node { rpc { flowControl { window { ... } } } } ← legacy only; do not add new keys here +level 6+: rejected by CI gate unconditionally +``` + +Each level of nesting requires a corresponding inner static bean class. If you find yourself going beyond 3 levels deep, consider flattening by moving the leaf keys up one level or using a longer camelCase key at level 2. + +## Adding a New Parameter: Checklist + +When adding a configuration parameter, all four steps are required in the same commit. + +### Step 1 — Add the key to `reference.conf` with its default value + +`reference.conf` (in `common/src/main/resources/`) must contain every key the code reads. This is the single source of truth for defaults. Add a brief inline comment explaining the key's purpose and valid range. + +```hocon +node { + # Maximum number of transaction verifier threads. 0 = auto (availableProcessors). + myNewOption = 0 +} +``` + +### Step 2 — Add the field to the corresponding bean class + +Add a field whose name **exactly matches** the HOCON key, with the same default value as `reference.conf`. If the field is in a sub-bean, ensure the sub-bean is mapped correctly. + +```java +// NodeConfig.java +private int myNewOption = 0; // 0 = auto +``` + +Lombok `@Getter` and `@Setter` on the class provide the accessor methods that `ConfigBeanFactory` needs. Do not write them by hand. + +### Step 3 — Add clamping / validation in `postProcess()` if needed + +Every bean's `postProcess()` (called from `fromConfig()` after binding) is where out-of-range values are clamped and cross-field invariants are enforced. Do not add defensive checks scattered through the rest of the codebase. + +```java +// in NodeConfig.postProcess() +if (myNewOption == 0) { + myNewOption = Runtime.getRuntime().availableProcessors(); +} +if (myNewOption > 64) { + myNewOption = 64; +} +``` + +### Step 4 — Add the key to `config.conf` only if the default is intentionally different + +`config.conf` (in `framework/src/main/resources/`) is the sample user config shipped with the distribution. Only add your new key there if the value users should start with differs from the `reference.conf` default, or if the key needs a visible comment for users. + +## Field Types and HOCON Value Types + +| Java field type | HOCON value | Notes | +|-------------------|-------------|-------| +| `boolean` | `true` / `false` | | +| `int` / `long` | numeric | Must be a plain integer; human-readable sizes (`4m`, `128MB`) are not supported | +| `double` | numeric | | +| `String` | `"value"` | Null HOCON values must be normalized to `""` before binding (see `normalizeNonStandardKeys`) | +| `List` | `["a", "b"]` | Must be read manually; `ConfigBeanFactory` does not handle `List` | +| Inner bean | `{ key = val }` | The Java field type must be the inner static class | + +### List Fields + +`ConfigBeanFactory` handles `List` but not `List`. Read string-list fields manually after `ConfigBeanFactory.create()`: + +```java +NodeConfig nc = ConfigBeanFactory.create(section, NodeConfig.class); +nc.active = section.getStringList("active"); +``` + +## Backward Compatibility and Legacy Keys + +When renaming a key, keep reading the old key as a fallback for at least one major release: + +```java +// fromConfig() — after ConfigBeanFactory binding +if (section.hasPath("oldKeyName")) { + nc.newFieldName = section.getInt("oldKeyName"); + logger.warn("Config key [section.oldKeyName] is deprecated; use [section.newKeyName]."); +} +``` + +Never remove the old key from this fallback read without a deprecation period and a release note. + +## Optional Keys (Not in `reference.conf`) + +Most keys should be in `reference.conf`. Use optional keys (absent from `reference.conf`, only read if present) sparingly — only for parameters where the presence/absence itself carries meaning. Read them with `hasPath()` guards and annotate the Java field with `@Setter(lombok.AccessLevel.NONE)` to prevent `ConfigBeanFactory` from requiring the key: + +```java +@Setter(lombok.AccessLevel.NONE) +private String shutdownBlockTime = ""; // "" = not set + +// in fromConfig(), after ConfigBeanFactory.create(): +nc.shutdownBlockTime = section.hasPath("shutdown.BlockTime") + ? section.getString("shutdown.BlockTime") : ""; +``` + +## Key Naming Conventions Summary + +| Rule | Good | Bad | +|------|------|-----| +| Standard camelCase | `maxConnections` | `MaxConnections`, `max_connections`, `max-connections` | +| No `is` prefix | `openFullTcpDisconnect` | `isOpenFullTcpDisconnect` | +| No all-caps acronym prefix | `pbftExpireNum`, `pBFTPort`* | `PBFTExpireNum` | +| New keys: nesting ≤ 3 levels | `node.rpc.maxMessageSize` | `node.rpc.limits.size.max` | +| Java field name matches HOCON key exactly | field `maxConnections` ↔ key `maxConnections` | field `maxConns` ↔ key `maxConnections` | + +\* `PBFTEnable` / `PBFTPort` are legacy exceptions; do not model new keys after them. + +## Where to Find Existing Patterns + +| Pattern | Reference location | +|---------|-------------------| +| Standard flat scalar binding | `VmConfig.java`, `BlockConfig.java` | +| Sub-bean nesting | `NodeConfig.HttpConfig`, `NodeConfig.RpcConfig` | +| Legacy key fallback | `NodeConfig.fromConfig()` (`maxActiveNodes`, `maxActiveNodesWithSameIp`) | +| Non-standard key normalization | `CommitteeConfig.normalizeNonStandardKeys()`, `NodeConfig.normalizeNonStandardKeys()` | +| Optional PascalCase keys | `NodeConfig.fromConfig()` (`shutdown.BlockTime/Height/Count`) | +| `postProcess()` clamping | `NodeConfig.postProcess()`, `CommitteeConfig.postProcess()` | diff --git a/docs/configuration.md b/docs/configuration.md new file mode 100644 index 00000000000..42dc1ea3777 --- /dev/null +++ b/docs/configuration.md @@ -0,0 +1,257 @@ +# Node Configuration Guide + +This guide explains the two-layer configuration system used by java-tron and walks through the most common customizations a node operator needs. + +## How the Two Config Files Work Together + +java-tron uses [Typesafe Config](https://github.com/lightbend/config) and applies two layers of configuration at startup: + +| File | Location | Purpose | +|------|----------|---------| +| `reference.conf` | Bundled inside the jar (`common` module) | Declares every parameter with its default value | +| `config.conf` | Bundled template, optionally edited & passed via -c | Overrides only the values that differ from defaults | + +**Loading priority:** values in `config.conf` always win. Any parameter that your `config.conf` omits is automatically filled in from `reference.conf`. You never need to copy the entire `reference.conf` into your own file — only include the parameters you actually want to change. + +``` +startup resolution order (highest wins): + 1. config.conf (your file, passed with -c) + 2. reference.conf (bundled in jar, fallback for everything) +``` + +`reference.conf` is the authoritative source of truth for every parameter name and its default. When in doubt, consult that file to see what a parameter does and what value the node will use if you leave it out. + +## Starting a Node with a Config File + +```bash +# Using the distribution script +java-tron-1.0.0/bin/FullNode -c /path/to/config.conf + +# Using the jar directly +java -jar FullNode.jar -c /path/to/config.conf + +# SR (Super Representative) mode +java-tron-1.0.0/bin/FullNode -c /path/to/config.conf -w +``` + +If `-c` is omitted, the node loads the `config.conf` bundled inside the jar (the same file shipped with the distribution) merged with `reference.conf` as fallback. The bundled file already enables discovery/persist for mainnet operation. For production, copy it out, edit, and pass the edited copy via `-c` to make your configuration visible to operators. + +## Minimal config.conf + +A `config.conf` only needs to contain what you want to change. The following is sufficient for a mainnet full node: + +```hocon +node.discovery = { + enable = true + persist = true +} + +node { + listen.port = 18888 + minParticipationRate = 15 + p2p.version = 11111 # mainnet +} + +seed.node.ip.list = [ + "3.225.171.164:18888", + "52.8.46.215:18888", + # ... (see reference.conf for the full seed list) +] +``` + +## Common Configuration Sections + +### Network and P2P (`node`, `node.discovery`, `seed.node`) + +```hocon +node.discovery = { + enable = true # join the peer-discovery network + persist = true # save discovered peers across restarts +} + +node { + listen.port = 18888 # TCP port for peer connections + maxConnections = 30 # maximum peer connections + minConnections = 8 # minimum peer connections to maintain + minParticipationRate = 15 # minimum % of active witnesses before producing blocks + + p2p { + version = 11111 # Mainnet:11111 Nile:201910292 Shasta:1 + } +} + +seed.node.ip.list = [ + "3.225.171.164:18888", + # add more entries as needed +] +``` + +### HTTP and gRPC APIs (`node.http`, `node.rpc`) + +```hocon +node { + http { + fullNodeEnable = true + fullNodePort = 8090 + solidityEnable = true + solidityPort = 8091 + } + + rpc { + enable = true + port = 50051 + solidityEnable = true + solidityPort = 50061 + # Maximum concurrent calls per connection. 0 = no limit. + maxConcurrentCallsPerConnection = 0 + # Idle connection timeout (ms). 0 = no limit. + maxConnectionIdleInMillis = 0 + # Minimum active connections required before broadcasting transactions. + minEffectiveConnection = 1 + } +} +``` + +To disable an API endpoint that you do not want to expose publicly, set its `Enable` flag to `false` or add endpoints to `node.disabledApi`: + +```hocon +node.disabledApi = [ + "getaccount", + "getnowblock2" +] +``` + +### Storage Engine (`storage`) + +```hocon +storage { + db.engine = "LEVELDB" # "LEVELDB" or "ROCKSDB"; ARM64 requires "ROCKSDB" + db.sync = false # set true for maximum durability (slower writes) + db.directory = "database" +} +``` + +To override the storage path for individual databases: + +```hocon +storage.properties = [ + { + name = "account", + path = "/data/tron/account-db" + } +] +``` + +### Block Production (Super Representatives) + +```hocon +# Plain private key (use localwitnesskeystore for production) +localwitness = [ + "your-private-key-hex" +] + +# Recommended: keystore file +# localwitnesskeystore = [ +# "/path/to/localwitnesskeystore.json" +# ] + +# Required when the witness account has delegated block-signing to a separate key +# localWitnessAccountAddress = "T..." +``` + +### JSON-RPC (Ethereum-compatible, `node.jsonrpc`) + +```hocon +node.jsonrpc { + httpFullNodeEnable = true + httpFullNodePort = 8545 + maxBlockRange = 5000 # max block range for eth_getLogs + maxResponseSize = 26214400 # 25 MB +} +``` + +### Event Subscription (`event.subscribe`) + +```hocon +event.subscribe = { + enable = true + native { + useNativeQueue = true + bindport = 5555 + sendqueuelength = 1000 + } + topics = [ + { triggerName = "block", enable = true, topic = "block" }, + { triggerName = "transaction", enable = true, topic = "transaction" }, + { triggerName = "solidity", enable = true, topic = "solidity" } + ] +} +``` + +### Rate Limiting (`rate.limiter`) + +```hocon +rate.limiter = { + # Available strategies: + # GlobalPreemptibleAdapter — semaphore-based, paramString = "permit=N" + # QpsRateLimiterAdapter — node-wide QPS cap, paramString = "qps=N" + # IPQPSRateLimiterAdapter — per-IP QPS cap, paramString = "qps=N" + + http = [ + { + component = "GetAccountServlet", + strategy = "IPQPSRateLimiterAdapter", + paramString = "qps=10" + } + ] + + global.qps = 50000 + global.ip.qps = 10000 +} +``` + +### Dynamic Config Reload (`node.dynamicConfig`) + +When enabled, the node re-reads `config.conf` periodically without restarting: + +```hocon +node.dynamicConfig = { + enable = true + checkInterval = 600 # seconds between checks +} +``` + +Not all parameters support hot-reload. Parameters that affect node identity, genesis block, or database layout require a full restart. + +## Parameters You Should Not Change + +| Parameter | Reason | +|-----------|--------| +| `crypto.engine` | Changing the key-derivation algorithm will fork the node | +| `genesis.block.*` | Must be identical on every node in the network | +| `committee.*` | Controlled by on-chain governance proposals; manual overrides are for private chains only | +| `node.p2p.version` | Must match the network (11111 for mainnet) | +| `enery.limit.block.num` | Intentional typo preserved for backward compatibility; do not rename | + +## Applying a Config Change + +1. Edit your `config.conf` — only add or change the keys you need. +2. If `node.dynamicConfig.enable = true`, wait up to `checkInterval` seconds; the node picks up the change automatically. +3. Otherwise, restart the node: `kill ` then relaunch with the same `-c` flag. +4. Check startup logs for a `[config]` line confirming the file was loaded and watch for any `ERROR` lines about unknown or invalid keys. + +## Viewing Effective Configuration + +At startup, the node unconditionally logs a summary of key parameters under `Net config`, `Backup config`, `Code version`, `DB config`, and `shutDown config` headers (see `Args.logConfig()` for the exact fields). For parameters not in this summary, you must inspect runtime behavior or consult `reference.conf` directly — the full merged configuration is never dumped. + +Note: `node.openPrintLog` is a separate flag that controls runtime verbosity of P2P/inventory/pending-tx logs, not startup config logging. + +## Full Reference + +Every parameter with its default value and an inline comment is documented in: + +``` +common/src/main/resources/reference.conf +``` + +When you need the authoritative default for a parameter or want to understand what a key does, consult that file directly. 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 8d8e2500c9f..9f1c6afe35c 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 @@ -583,14 +583,7 @@ private static void applyNodeConfig(NodeConfig nc) { // ---- Flat scalar fields ---- PARAMETER.nodeEffectiveCheckEnable = nc.isEffectiveCheckEnable(); - // fetchBlock.timeout — range check [100, 1000], default 500 - int fetchTimeout = nc.getFetchBlockTimeout(); - if (fetchTimeout > 1000) { - fetchTimeout = 1000; - } else if (fetchTimeout < 100) { - fetchTimeout = 100; - } - PARAMETER.fetchBlockTimeout = fetchTimeout; + PARAMETER.fetchBlockTimeout = nc.getFetchBlockTimeout(); PARAMETER.maxConnections = nc.getMaxConnections(); PARAMETER.minConnections = nc.getMinConnections(); @@ -614,7 +607,6 @@ private static void applyNodeConfig(NodeConfig nc) { PARAMETER.validateSignThreadNum = nc.getValidateSignThreadNum(); PARAMETER.walletExtensionApi = nc.isWalletExtensionApi(); - PARAMETER.receiveTcpMinDataLength = nc.getReceiveTcpMinDataLength(); PARAMETER.isOpenFullTcpDisconnect = nc.isOpenFullTcpDisconnect(); PARAMETER.nodeDetectEnable = nc.isNodeDetectEnable(); @@ -630,7 +622,7 @@ private static void applyNodeConfig(NodeConfig nc) { PARAMETER.shieldedTransInPendingMaxCounts = nc.getShieldedTransInPendingMaxCounts(); PARAMETER.agreeNodeCount = nc.getAgreeNodeCount(); - PARAMETER.setOpenHistoryQueryWhenLiteFN(nc.isOpenHistoryQueryWhenLiteFN()); + PARAMETER.openHistoryQueryWhenLiteFN = nc.isOpenHistoryQueryWhenLiteFN(); PARAMETER.nodeMetricsEnable = nc.isMetricsEnable(); PARAMETER.openPrintLog = nc.isOpenPrintLog(); PARAMETER.openTransactionSort = nc.isOpenTransactionSort(); diff --git a/framework/src/main/resources/config.conf b/framework/src/main/resources/config.conf index 0686890f030..13a31f52c02 100644 --- a/framework/src/main/resources/config.conf +++ b/framework/src/main/resources/config.conf @@ -4,7 +4,6 @@ net { } storage { - # Directory for storing persistent data db.engine = "LEVELDB", // deprecated for arm, because arm only support "ROCKSDB". db.sync = false, db.directory = "database", @@ -12,57 +11,17 @@ storage { # Whether to write transaction result in transactionRetStore transHistory.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 - # } - # setting can improve leveldb performance .... end, deprecated for arm - - # You can customize the configuration for each database. Otherwise, the database settings will use - # their defaults, and data will be stored in the "output-directory" or in the directory specified - # by the "-d" or "--output-directory" option. Attention: name is a required field that must be set! - # In this configuration, the name and path properties take effect for both LevelDB and RocksDB storage engines, - # while the additional properties (such as createIfMissing, paranoidChecks, compressionType, etc.) only take effect when using LevelDB. + # Per-database storage path overrides (name is required; see reference.conf for full property list). properties = [ # { # name = "account", # path = "storage_directory_test", - # createIfMissing = true, // deprecated for arm start - # paranoidChecks = true, - # verifyChecksums = true, - # compressionType = 1, // compressed with snappy - # blockSize = 4096, // 4 KB = 4 * 1024 B - # writeBufferSize = 10485760, // 10 MB = 10 * 1024 * 1024 B - # cacheSize = 10485760, // 10 MB = 10 * 1024 * 1024 B - # maxOpenFiles = 100 // deprecated for arm end - # }, - # { - # name = "account-index", - # path = "storage_directory_test", - # createIfMissing = true, - # paranoidChecks = true, - # verifyChecksums = true, - # compressionType = 1, // compressed with snappy - # blockSize = 4096, // 4 KB = 4 * 1024 B - # writeBufferSize = 10485760, // 10 MB = 10 * 1024 * 1024 B - # cacheSize = 10485760, // 10 MB = 10 * 1024 * 1024 B - # maxOpenFiles = 100 # }, ] needToUpdateAsset = true - # dbsettings is needed when using rocksdb as the storage implement (db.engine="ROCKSDB"). - # we'd strongly recommend that do not modify it unless you know every item's meaning clearly. + # RocksDB settings (only used when db.engine = "ROCKSDB"). See reference.conf for details. dbSettings = { levelNumber = 7 # compactThreads = 32 @@ -77,24 +36,8 @@ storage { balance.history.lookup = false - # checkpoint.version = 2 - # 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 - - # if true, transaction cache initialization will be faster. Default: false + # 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 - - # data root setting, for check data, currently, only reward-vi is used. - # merkleRoot = { - # reward-vi = 9debcb9924055500aaae98cdee10501c5c39d4daa75800a996f4bdda73dbccd8 // main-net, Sha256Hash, hexString - # } - } node.discovery = { @@ -102,24 +45,17 @@ node.discovery = { persist = true } -# custom stop condition -#node.shutdown = { -# BlockTime = "54 59 08 * * ?" # if block header time in persistent db matched. -# BlockHeight = 33350800 # if block header height in persistent db matched. -# BlockCount = 12 # block sync count after node start. -#} +# Custom stop condition +# node.shutdown = { +# BlockTime = "54 59 08 * * ?" # if block header time in persistent db matched. +# BlockHeight = 33350800 # if block header height in persistent db matched. +# BlockCount = 12 # block sync count after node start. +# } node.backup { - # udp listen port, each member should have the same configuration port = 10001 - - # my priority, each member should use different priority priority = 8 - - # time interval to send keepAlive message, each member should have the same configuration keepAliveInterval = 3000 - - # peer's ip list, can't contain mine members = [ # "ip", # "ip" @@ -132,17 +68,13 @@ crypto { } node.metrics = { - # prometheus metrics prometheus { enable = false port = 9527 } - } node { - # trust node for solidity node - # trustNode = "ip:port" trustNode = "127.0.0.1:50051" # expose extension api to public or not @@ -151,50 +83,14 @@ node { listen.port = 18888 fetchBlock.timeout = 200 - # syncFetchBatchNum = 2000 - - # Maximum number of blocks allowed in-flight (requested but not yet processed). - # Throttles block download to reduce memory pressure during sync. - # Range: [50, 2000], default: 500 - # maxPendingBlockSize = 500 - - # Maximum total number of cached transactions (handler queues + pending + rePush). - # When exceeded, the node stops accepting TRX INV messages from peers. - # maxTrxCacheSize = 50000 - - # Number of validate sign thread, default availableProcessors - # validateSignThreadNum = 16 maxConnections = 30 - minConnections = 8 - minActiveConnections = 3 - maxConnectionsWithSameIp = 2 - maxHttpConnectNumber = 50 - minParticipationRate = 15 - # WARNING: Some shielded transaction APIs require sending private keys as parameters. - # Calling these APIs on untrusted or remote nodes may leak your private keys. - # It is recommended to invoke them locally for development and testing. - # To opt in, set: allowShieldedTransactionApi = true - # Migration: the legacy key node.fullNodeAllowShieldedTransaction is still supported - # but deprecated; please migrate to node.allowShieldedTransactionApi. - # allowShieldedTransactionApi = false - - # openPrintLog = true - - # If set to true, SR packs transactions into a block in descending order of fee; otherwise, it packs - # them based on their receive timestamp. Default: false - # openTransactionSort = false - - # The threshold for the number of broadcast transactions received from each peer every second, - # transactions exceeding this threshold will be discarded - # maxTps = 1000 - isOpenFullTcpDisconnect = false inactiveThreshold = 600 //seconds @@ -204,14 +100,12 @@ node { active = [ # Active establish connection in any case - # Sample entries: # "ip:port", # "ip:port" ] passive = [ # Passive accept connection in any case - # Sample entries: # "ip:port", # "ip:port" ] @@ -228,12 +122,6 @@ node { solidityPort = 8091 PBFTEnable = true PBFTPort = 8092 - - # The maximum request body size for HTTP API, default 4M (4194304 bytes). - # Supports human-readable sizes: 4m, 4MB, 4194304. - # Must be non-negative and <= 2147483647 (Integer.MAX_VALUE, ~2 GiB). - # Setting to 0 rejects all non-empty request bodies (not "unlimited"). - # maxMessageSize = 4m } rpc { @@ -244,223 +132,51 @@ node { PBFTEnable = true PBFTPort = 50071 - # Number of gRPC thread, default availableProcessors / 2 - # thread = 16 - - # The maximum number of concurrent calls permitted for each incoming connection - # maxConcurrentCallsPerConnection = - - # The HTTP/2 flow control window, default 1MB - # flowControlWindow = - - # Connection being idle for longer than which will be gracefully terminated maxConnectionIdleInMillis = 60000 - - # Connection lasting longer than which will be gracefully terminated - # maxConnectionAgeInMillis = - - # The maximum message size allowed to be received on the server, default 4M (4194304 bytes). - # Supports human-readable sizes: 4m, 4MB, 4194304. - # Must be non-negative and <= 2147483647 (Integer.MAX_VALUE, ~2 GiB). - # Setting to 0 rejects all non-empty request bodies (not "unlimited"). - # maxMessageSize = 4m - - # The maximum size of header list allowed to be received, default 8192 - # maxHeaderListSize = - - # The number of RST_STREAM frames allowed to be sent per connection per period for grpc, by default there is no limit. - # maxRstStream = - - # The number of seconds per period for grpc - # secondsPerWindow = - - # Transactions can only be broadcast if the number of effective connections is reached. minEffectiveConnection = 1 - # The switch of the reflection service, effective for all gRPC services, used for grpcurl tool. Default: false + # The switch of the reflection service for grpcurl tool. Default: false reflectionService = false } - # number of solidity thread in the FullNode. - # If accessing solidity rpc and http interface timeout, could increase the number of threads, - # The default value is the number of cpu cores of the machine. - # solidity.threads = 8 - - # Limits the maximum percentage (default 75%) of producing block interval - # to provide sufficient time to perform other operations e.g. broadcast block - # blockProducedTimeOut = 75 - - # Limits the maximum number (default 700) of transaction from network layer - # netMaxTrxPerSecond = 700 - - # Whether to enable the node detection function. Default: false - # nodeDetectEnable = false - - # use your ipv6 address for node discovery and tcp connection. Default: false - # enableIpv6 = false - - # if your node's highest block num is below than all your pees', try to acquire new connection. Default: false - # effectiveCheckEnable = false - - # Dynamic loading configuration function, disabled by default dynamicConfig = { # enable = false - # checkInterval = 600 // Check interval of Configuration file's change, default is 600 seconds + # checkInterval = 600 } - # Whether to continue broadcast transactions after at least maxUnsolidifiedBlocks are not solidified. Default: false - # unsolidifiedBlockCheck = false - # maxUnsolidifiedBlocks = 54 - dns { - # dns urls to get nodes, url format tree://{pubkey}@{domain}, default empty treeUrls = [ #"tree://AKMQMNAJJBL73LXWPXDI4I5ZWWIZ4AWO34DWQ636QOBBXNFXH3LQS@main.trondisco.net", ] - - # enable or disable dns publish. Default: false - # publish = false - - # dns domain to publish nodes, required if publish is true - # dnsDomain = "nodes1.example.org" - - # dns private key used to publish, required if publish is true, hex string of length 64 - # dnsPrivate = "b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291" - - # known dns urls to publish if publish is true, url format tree://{pubkey}@{domain}, default empty - # knownUrls = [ - #"tree://APFGGTFOBVE2ZNAB3CSMNNX6RRK3ODIRLP2AA5U4YFAA6MSYZUYTQ@nodes2.example.org", - # ] - - # staticNodes = [ - # static nodes to published on dns - # Sample entries: - # "ip:port", - # "ip:port" - # ] - - # merge several nodes into a leaf of tree, should be 1~5 - # maxMergeSize = 5 - - # only nodes change percent is bigger then the threshold, we update data on dns - # changeThreshold = 0.1 - - # dns server to publish, required if publish is true, only aws or aliyun is support - # serverType = "aws" - - # access key id of aws or aliyun api, required if publish is true, string - # accessKeyId = "your-key-id" - - # access key secret of aws or aliyun api, required if publish is true, string - # accessKeySecret = "your-key-secret" - - # if publish is true and serverType is aliyun, it's endpoint of aws dns server, string - # aliyunDnsEndpoint = "alidns.aliyuncs.com" - - # if publish is true and serverType is aws, it's region of aws api, such as "eu-south-1", string - # awsRegion = "us-east-1" - - # if publish is true and server-type is aws, it's host zone id of aws's domain, string - # awsHostZoneId = "your-host-zone-id" } - # open the history query APIs(http&GRPC) when node is a lite FullNode, - # like {getBlockByNum, getBlockByID, getTransactionByID...}. Default: false. - # note: above APIs may return null even if blocks and transactions actually are on the blockchain - # when opening on a lite FullNode. only open it if the consequences being clearly known - # openHistoryQueryWhenLiteFN = false - jsonrpc { - # The maximum request body size for JSON-RPC API, default 4M (4194304 bytes). - # Supports human-readable sizes: 4m, 4MB, 4194304. - # Must be non-negative and <= 2147483647 (Integer.MAX_VALUE, ~2 GiB). - # Setting to 0 rejects all non-empty request bodies (not "unlimited"). - # maxMessageSize = 4m - - # Note: Before release_4.8.1, if you turn on jsonrpc and run it for a while and then turn it off, - # you will not be able to get the data from eth_getLogs for that period of time. Default: false - # httpFullNodeEnable = false - # httpFullNodePort = 8545 - # httpSolidityEnable = false - # httpSolidityPort = 8555 - # httpPBFTEnable = false - # httpPBFTPort = 8565 - - # The maximum blocks range to retrieve logs for eth_getLogs, default: 5000, <=0 means no limit + httpFullNodeEnable = false + httpFullNodePort = 8545 + maxBlockRange = 5000 - # Allowed max address count in filter request, default: 1000, <=0 means no limit maxAddressSize = 1000 - # The maximum number of allowed topics within a topic criteria, default: 1000, <=0 means no limit maxSubTopics = 1000 - # Allowed maximum number for blockFilter, default: 50000, <=0 means no limit maxBlockFilterNum = 50000 - # Allowed batch size, default: 100, <=0 means no limit maxBatchSize = 100 - # Allowed max response byte size, default: 26214400 (25 MB), <=0 means no limit maxResponseSize = 26214400 - # Allowed maximum number for newFilter, <=0 means no limit maxLogFilterNum = 20000 - # Maximum JSON-RPC request body size, default 4MB. Independent from rpc.maxMessageSize. - maxMessageSize = 4M + maxMessageSize = 4194304 } - # Disabled api list, it will work for http, rpc and pbft, both FullNode and SolidityNode, - # but not jsonrpc. The setting is case insensitive, GetNowBlock2 is equal to getnowblock2 disabledApi = [ # "getaccount", # "getnowblock2" ] - } ## rate limiter config rate.limiter = { - # Every api could only set a specific rate limit strategy. Three strategy are supported: - # GlobalPreemptibleAdapter: The number of preemptible resource or maximum concurrent requests globally. - # QpsRateLimiterAdapter: qps is the average request count in one second supported by the server, it could be a Double or a Integer. - # IPQPSRateLimiterAdapter: similar to the QpsRateLimiterAdapter, qps could be a Double or a Integer. - # If not set, QpsRateLimiterAdapter with qps=1000 is the default strategy. - # - # Sample entries: - # + # See reference.conf for available strategies (GlobalPreemptibleAdapter, QpsRateLimiterAdapter, IPQPSRateLimiterAdapter). http = [ - # { - # component = "GetNowBlockServlet", - # strategy = "GlobalPreemptibleAdapter", - # paramString = "permit=1" - # }, - - # { - # component = "GetAccountServlet", - # strategy = "IPQPSRateLimiterAdapter", - # paramString = "qps=1" - # }, - - # { - # component = "ListWitnessesServlet", - # strategy = "QpsRateLimiterAdapter", - # paramString = "qps=1" - # } ], rpc = [ - # { - # component = "protocol.Wallet/GetBlockByLatestNum2", - # strategy = "GlobalPreemptibleAdapter", - # paramString = "permit=1" - # }, - - # { - # component = "protocol.Wallet/GetAccount", - # strategy = "IPQPSRateLimiterAdapter", - # paramString = "qps=1" - # }, - - # { - # component = "protocol.Wallet/ListWitnesses", - # strategy = "QpsRateLimiterAdapter", - # paramString = "qps=1" - # }, ] p2p = { @@ -473,8 +189,7 @@ rate.limiter = { global.qps = 50000 # IP-based global qps, default 10000 global.ip.qps = 10000 - # If true, API rate limiters reject immediately on overload (non-blocking). - # If false (default), callers wait for a permit (blocking, the legacy behaviour). + # If true, API rate limiters reject immediately on overload (non-blocking). Default: false apiNonBlocking = false } @@ -483,11 +198,6 @@ rate.limiter = { seed.node = { # List of the seed nodes # Seed nodes are stable full nodes - # example: - # ip.list = [ - # "ip:port", - # "ip:port" - # ] ip.list = [ "3.225.171.164:18888", "52.8.46.215:18888", @@ -690,10 +400,10 @@ genesis.block = { parentHash = "0xe58f33f9baf9305dc6f82b9f1934ea8f0ade2defb951258d50167028c780351f" } -# Optional. The default is empty. It is used when the witness account has set the witnessPermission. -# When it is not empty, the localWitnessAccountAddress represents the address of the witness account, -# and the localwitness is configured with the private key of the witnessPermissionAddress in the witness account. -# When it is empty,the localwitness is configured with the private key of the witness account. +# Optional. Used when the witness account has set witnessPermission. +# localWitnessAccountAddress is the witness account address; +# localwitness is configured with the private key of the witnessPermissionAddress. +# When empty, localwitness is the private key of the witness account itself. # localWitnessAccountAddress = localwitness = [ @@ -707,100 +417,24 @@ block = { needSyncCheck = true maintenanceTimeInterval = 21600000 // 6 hours: 21600000(ms) proposalExpireTime = 259200000 // default value: 3 days: 259200000(ms), Note: this value is controlled by committee proposal - # checkFrozenTime = 1 // for test only } # Transaction reference block, default is "solid", configure to "head" may cause TaPos error trx.reference.block = "solid" // "head" or "solid" -# This property sets the number of milliseconds after the creation of the transaction that is expired, default value is 60000. -# trx.expiration.timeInMilliseconds = 60000 - vm = { supportConstant = false maxEnergyLimitForConstant = 100000000 minTimeRatio = 0.0 maxTimeRatio = 5.0 saveInternalTx = false - # lruCacheSize = 500 - # vmTrace = false - - # Indicates whether the node stores featured internal transactions, such as freeze, vote and so on. Default: false. - # saveFeaturedInternalTx = false - - # Indicates whether the node stores the details of the internal transactions generated by the CANCELALLUNFREEZEV2 opcode, - # such as bandwidth/energy/tronpower cancel amount. Default: false. - # saveCancelAllUnfreezeV2Details = false - - # In rare cases, transactions that will be within the specified maximum execution time (default 10(ms)) are re-executed and packaged - # longRunningTime = 10 - - # Indicates whether the node support estimate energy API. Default: false. - # estimateEnergy = false - - # Indicates the max retry time for executing transaction in estimating energy. Default 3. - # estimateEnergyMaxRetry = 3 - - # Max TVM execution time (ms) for constant calls — applies to - # triggerconstantcontract, triggersmartcontract dispatched to view/pure - # functions, estimateenergy, eth_call, eth_estimateGas, and any other RPC - # routed through the constant-call path. When set, must be a positive - # integer that fits VM deadline conversion and is used verbatim as the - # per-call deadline (no clamp against the network's maxCpuTimeOfOneTx). - # Omit the property entirely to keep the default behaviour of sharing the - # block-processing deadline. Migration note: if previously running --debug - # to extend constant calls, switch to this option (--debug also extends - # block-processing, which is unsafe; see issue #6266). Default: 0. - # constantCallTimeoutMs = 100 } # These parameters are designed for private chain testing only and cannot be freely switched on or off in production systems. +# In production, they are controlled by on-chain committee proposals. committee = { allowCreationOfContracts = 0 //mainnet:0 (reset by committee),test:1 allowAdaptiveEnergy = 0 //mainnet:0 (reset by committee),test:1 - # allowCreationOfContracts = 0 - # allowMultiSign = 0 - # allowAdaptiveEnergy = 0 - # allowDelegateResource = 0 - # allowSameTokenName = 0 - # allowTvmTransferTrc10 = 0 - # allowTvmConstantinople = 0 - # allowTvmSolidity059 = 0 - # forbidTransferToContract = 0 - # allowShieldedTRC20Transaction = 0 - # allowTvmIstanbul = 0 - # allowMarketTransaction = 0 - # allowProtoFilterNum = 0 - # allowAccountStateRoot = 0 - # changedDelegation = 0 - # allowPBFT = 0 - # pBFTExpireNum = 0 - # allowTransactionFeePool = 0 - # allowBlackHoleOptimization = 0 - # allowNewResourceModel = 0 - # allowReceiptsMerkleRoot = 0 - # allowTvmFreeze = 0 - # allowTvmVote = 0 - # unfreezeDelayDays = 0 - # allowTvmLondon = 0 - # allowTvmCompatibleEvm = 0 - # allowNewRewardAlgorithm = 0 - # allowAccountAssetOptimization = 0 - # allowAssetOptimization = 0 - # allowNewReward = 0 - # memoFee = 0 - # allowDelegateOptimization = 0 - # allowDynamicEnergy = 0 - # dynamicEnergyThreshold = 0 - # dynamicEnergyMaxFactor = 0 - # allowTvmShangHai = 0 - # allowOldRewardOpt = 0 - # allowEnergyAdjustment = 0 - # allowStrictMath = 0 - # allowTvmCancun = 0 - # allowTvmBlob = 0 - # consensusLogicOptimization = 0 - # allowOptimizedReturnValueOfChainId = 0 } event.subscribe = { @@ -811,17 +445,13 @@ event.subscribe = { sendqueuelength = 1000 //max length of send queue } version = 0 - # Specify the starting block number to sync historical events. This is only applicable when version = 1. + # Specify the starting block number to sync historical events. Only applicable when version = 1. # After performing a full event sync, set this value to 0 or a negative number. # startSyncBlockNum = 1 path = "" // absolute path of plugin - server = "" // target server address to receive event triggers - # dbname|username|password, if you want to create indexes for collections when the collections - # are not exist, you can add version and set it to 2, as dbname|username|password|version - # if you use version 2 and one collection not exists, it will create index automaticaly; - # if you use version 2 and one collection exists, it will not create index, you must create index manually; - dbconfig = "" + server = "" // target server address to receive event triggers, "ip:port" + dbconfig = "" // dbname|username|password (append |2 to auto-create indexes on missing collections) contractParse = true topics = [ { diff --git a/framework/src/test/java/org/tron/common/ParameterTest.java b/framework/src/test/java/org/tron/common/ParameterTest.java index 91bb580a3b4..0b66c96462c 100644 --- a/framework/src/test/java/org/tron/common/ParameterTest.java +++ b/framework/src/test/java/org/tron/common/ParameterTest.java @@ -176,8 +176,6 @@ public void testCommonParameter() { assertEquals(2, parameter.getEstimateEnergyMaxRetry()); parameter.setKeepAliveInterval(1000); assertEquals(1000, parameter.getKeepAliveInterval()); - parameter.setReceiveTcpMinDataLength(10); - assertEquals(10, parameter.getReceiveTcpMinDataLength()); parameter.setOpenFullTcpDisconnect(false); assertFalse(parameter.isOpenFullTcpDisconnect()); parameter.setNodeDetectEnable(false); 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 3ae5677fbda..a62b1418d56 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 @@ -17,7 +17,6 @@ import com.google.common.collect.Lists; import com.typesafe.config.Config; -import com.typesafe.config.ConfigException; import com.typesafe.config.ConfigFactory; import io.grpc.internal.GrpcUtil; import io.grpc.netty.NettyServerBuilder; @@ -381,12 +380,9 @@ public void testConfigStorageDefaults() { } // =========================================================================== - // Boundary tests for clamps applied in Args.java bridge code (not in - // bean postProcess()). + // Boundary tests for node.fetchBlock.timeout clamping. // - // fetchBlockTimeout is read from NodeConfig but clamped in Args.applyNodeConfig - // to range [100, 1000]. Pin this clamp here so any future refactor that moves - // it (e.g. into NodeConfig.postProcess()) preserves the behavior. + // The clamp to [100, 1000] is applied in NodeConfig.postProcess(). // =========================================================================== @Test @@ -515,79 +511,14 @@ public void testAllowShieldedTransactionApiDefault() { } @Test - public void testMaxMessageSizeHumanReadable() { + public void testMaxMessageSizePureNumber() { Map configMap = new HashMap<>(); configMap.put("storage.db.directory", "database"); - // --- KB tier: binary (k/K/Ki/KiB = 1024) vs SI (kB = 1000) --- - configMap.put("node.rpc.maxMessageSize", "512k"); - configMap.put("node.http.maxMessageSize", "512K"); - configMap.put("node.jsonrpc.maxMessageSize", "512kB"); - Config config = ConfigFactory.defaultOverrides() - .withFallback(ConfigFactory.parseMap(configMap)) - .withFallback(ConfigFactory.defaultReference()); - Args.applyConfigParams(config); - Assert.assertEquals(512 * 1024, Args.getInstance().getMaxMessageSize()); - Assert.assertEquals(512 * 1024, Args.getInstance().getHttpMaxMessageSize()); - Assert.assertEquals(512 * 1000, Args.getInstance().getJsonRpcMaxMessageSize()); - Args.clearParam(); - - configMap.put("node.rpc.maxMessageSize", "256Ki"); - configMap.put("node.http.maxMessageSize", "256KiB"); - configMap.put("node.jsonrpc.maxMessageSize", "256kB"); - config = ConfigFactory.defaultOverrides() - .withFallback(ConfigFactory.parseMap(configMap)) - .withFallback(ConfigFactory.defaultReference()); - Args.applyConfigParams(config); - Assert.assertEquals(256 * 1024, Args.getInstance().getMaxMessageSize()); - Assert.assertEquals(256 * 1024, Args.getInstance().getHttpMaxMessageSize()); - Assert.assertEquals(256 * 1000, Args.getInstance().getJsonRpcMaxMessageSize()); - Args.clearParam(); - - // --- MB tier: binary (m/M/Mi/MiB = 1024*1024) vs SI (MB = 1000*1000) --- - configMap.put("node.rpc.maxMessageSize", "4m"); - configMap.put("node.http.maxMessageSize", "8M"); - configMap.put("node.jsonrpc.maxMessageSize", "2MB"); - config = ConfigFactory.defaultOverrides() - .withFallback(ConfigFactory.parseMap(configMap)) - .withFallback(ConfigFactory.defaultReference()); - Args.applyConfigParams(config); - Assert.assertEquals(4 * 1024 * 1024, Args.getInstance().getMaxMessageSize()); - Assert.assertEquals(8 * 1024 * 1024, Args.getInstance().getHttpMaxMessageSize()); - Assert.assertEquals(2 * 1000 * 1000, Args.getInstance().getJsonRpcMaxMessageSize()); - Args.clearParam(); - - configMap.put("node.rpc.maxMessageSize", "4Mi"); - configMap.put("node.http.maxMessageSize", "4MiB"); - configMap.put("node.jsonrpc.maxMessageSize", "4MB"); - config = ConfigFactory.defaultOverrides() - .withFallback(ConfigFactory.parseMap(configMap)) - .withFallback(ConfigFactory.defaultReference()); - Args.applyConfigParams(config); - Assert.assertEquals(4 * 1024 * 1024, Args.getInstance().getMaxMessageSize()); - Assert.assertEquals(4 * 1024 * 1024, Args.getInstance().getHttpMaxMessageSize()); - Assert.assertEquals(4 * 1000 * 1000, Args.getInstance().getJsonRpcMaxMessageSize()); - Args.clearParam(); - - // --- GB tier: binary (g/G/Gi/GiB) vs SI (GB) --- - // All three paths are int-bounded; values up to Integer.MAX_VALUE are accepted. - configMap.put("node.rpc.maxMessageSize", "4m"); - configMap.put("node.http.maxMessageSize", "1g"); - configMap.put("node.jsonrpc.maxMessageSize", "1GB"); - config = ConfigFactory.defaultOverrides() - .withFallback(ConfigFactory.parseMap(configMap)) - .withFallback(ConfigFactory.defaultReference()); - Args.applyConfigParams(config); - Assert.assertEquals(4 * 1024 * 1024, Args.getInstance().getMaxMessageSize()); - Assert.assertEquals(1024L * 1024 * 1024, Args.getInstance().getHttpMaxMessageSize()); - Assert.assertEquals(1000L * 1000 * 1000, Args.getInstance().getJsonRpcMaxMessageSize()); - Args.clearParam(); - - // --- raw integer (backward compatible): treated as bytes --- configMap.put("node.rpc.maxMessageSize", "4194304"); configMap.put("node.http.maxMessageSize", "4194304"); configMap.put("node.jsonrpc.maxMessageSize", "4194304"); - config = ConfigFactory.defaultOverrides() + Config config = ConfigFactory.defaultOverrides() .withFallback(ConfigFactory.parseMap(configMap)) .withFallback(ConfigFactory.defaultReference()); Args.applyConfigParams(config); @@ -596,7 +527,6 @@ public void testMaxMessageSizeHumanReadable() { Assert.assertEquals(4 * 1024 * 1024, Args.getInstance().getJsonRpcMaxMessageSize()); Args.clearParam(); - // --- zero is allowed --- configMap.put("node.rpc.maxMessageSize", "0"); configMap.put("node.http.maxMessageSize", "0"); configMap.put("node.jsonrpc.maxMessageSize", "0"); @@ -611,82 +541,43 @@ public void testMaxMessageSizeHumanReadable() { } @Test - public void testRpcMaxMessageSizeExceedsIntMax() { - Map configMap = new HashMap<>(); - configMap.put("storage.db.directory", "database"); - configMap.put("node.rpc.maxMessageSize", "3g"); - Config config = ConfigFactory.defaultOverrides() - .withFallback(ConfigFactory.parseMap(configMap)) - .withFallback(ConfigFactory.defaultReference()); - TronError e = Assert.assertThrows(TronError.class, - () -> Args.applyConfigParams(config)); - Assert.assertTrue(e.getMessage().contains("node.rpc.maxMessageSize must be non-negative")); - } - - @Test - public void testHttpMaxMessageSizeExceedsIntMax() { - Map configMap = new HashMap<>(); - configMap.put("storage.db.directory", "database"); - configMap.put("node.http.maxMessageSize", "2Gi"); - Config config = ConfigFactory.defaultOverrides() - .withFallback(ConfigFactory.parseMap(configMap)) - .withFallback(ConfigFactory.defaultReference()); - TronError e = Assert.assertThrows(TronError.class, - () -> Args.applyConfigParams(config)); - Assert.assertTrue(e.getMessage().contains("node.http.maxMessageSize must be non-negative")); - } - - @Test - public void testJsonRpcMaxMessageSizeExceedsIntMax() { - Map configMap = new HashMap<>(); - configMap.put("storage.db.directory", "database"); - configMap.put("node.jsonrpc.maxMessageSize", "2Gi"); - Config config = ConfigFactory.defaultOverrides() - .withFallback(ConfigFactory.parseMap(configMap)) - .withFallback(ConfigFactory.defaultReference()); - TronError e = Assert.assertThrows(TronError.class, - () -> Args.applyConfigParams(config)); - Assert.assertTrue( - e.getMessage().contains("node.jsonrpc.maxMessageSize must be non-negative")); - } - - @Test - public void testMaxMessageSizeNegativeValue() { - Map configMap = new HashMap<>(); - configMap.put("storage.db.directory", "database"); - configMap.put("node.rpc.maxMessageSize", "-4m"); - Config config = ConfigFactory.defaultOverrides() - .withFallback(ConfigFactory.parseMap(configMap)) - .withFallback(ConfigFactory.defaultReference()); - IllegalArgumentException e = Assert.assertThrows(IllegalArgumentException.class, - () -> Args.applyConfigParams(config)); - Assert.assertTrue(e.getMessage().contains("negative")); - } - - @Test - public void testMaxMessageSizeInvalidUnit() { - Map configMap = new HashMap<>(); - configMap.put("storage.db.directory", "database"); - configMap.put("node.rpc.maxMessageSize", "4x"); - Config config = ConfigFactory.defaultOverrides() - .withFallback(ConfigFactory.parseMap(configMap)) - .withFallback(ConfigFactory.defaultReference()); - ConfigException.BadValue e = Assert.assertThrows(ConfigException.BadValue.class, - () -> Args.applyConfigParams(config)); - Assert.assertTrue(e.getMessage().contains("Could not parse size-in-bytes unit")); + public void testMaxMessageSizeNegativeValueRejected() { + // Negative maxMessageSize must be rejected at startup which threw TronError(PARAMETER_INIT) + // for negative values). + for (String key : new String[]{ + "node.rpc.maxMessageSize", "node.http.maxMessageSize", "node.jsonrpc.maxMessageSize"}) { + Map configMap = new HashMap<>(); + configMap.put("storage.db.directory", "database"); + configMap.put(key, "-1"); + Config config = ConfigFactory.defaultOverrides() + .withFallback(ConfigFactory.parseMap(configMap)) + .withFallback(ConfigFactory.defaultReference()); + try { + Args.applyConfigParams(config); + Assert.fail("Expected TronError for negative " + key); + } catch (TronError e) { + Assert.assertEquals(TronError.ErrCode.PARAMETER_INIT, e.getErrCode()); + } + Args.clearParam(); + } } @Test - public void testMaxMessageSizeNonNumeric() { - Map configMap = new HashMap<>(); + public void testRpcMaxMessageSizeExceedsIntMax() { + // HOCON's Config.getInt() throws when a numeric value exceeds int range. + // This documents the failure mode for node.rpc.maxMessageSize (int field). + Map configMap = new HashMap<>(); configMap.put("storage.db.directory", "database"); - configMap.put("node.http.maxMessageSize", "abc"); + configMap.put("node.rpc.maxMessageSize", (long) Integer.MAX_VALUE + 1); Config config = ConfigFactory.defaultOverrides() .withFallback(ConfigFactory.parseMap(configMap)) .withFallback(ConfigFactory.defaultReference()); - ConfigException.BadValue e = Assert.assertThrows(ConfigException.BadValue.class, - () -> Args.applyConfigParams(config)); - Assert.assertTrue(e.getMessage().contains("No number in size-in-bytes value")); + try { + Args.applyConfigParams(config); + Assert.fail("Expected RuntimeException for maxMessageSize > Integer.MAX_VALUE"); + } catch (RuntimeException e) { + // ConfigBeanFactory/HOCON throws when binding a long out of int range + } } // ===== checkBackupMembers() tests =====