Skip to content

feat(net): support domain names in peer configuration#75

Open
317787106 wants to merge 9 commits intodevelopfrom
feature/support_domain
Open

feat(net): support domain names in peer configuration#75
317787106 wants to merge 9 commits intodevelopfrom
feature/support_domain

Conversation

@317787106
Copy link
Copy Markdown
Owner

@317787106 317787106 commented Apr 24, 2026

Summary

Allows node.seed.ip.list, node.active, node.passive, node.fastForward, and node.backup.members configuration entries to specify domain names in addition to IP literals. Domains are resolved to IPs at startup; backup member IPs are refreshed automatically every 60 seconds when DNS returns a different address.
Closes tronprotocol#6634

Problem

All network peer configuration fields previously required numeric IP addresses. Operators running nodes behind dynamic DNS or cloud load-balancers had to manually update config files whenever IPs changed. Backup members in particular need continuous accuracy because they participate in keep-alive health checks.

Changes

New: InetUtil (framework/src/main/java/org/tron/core/config/args/InetUtil.java)

Central DNS resolution utility used by both startup parsing and runtime refresh:

  • resolveInetSocketAddressList(List<String>) — converts a list of ipOrDomain:port strings to InetSocketAddress objects. IP literals are used as-is; domain entries are resolved via LookUpTxt.lookUpIp (IPv4 first, IPv6 fallback). When there are multiple domain entries, resolution is parallelised using a bounded thread pool (up to 10 threads). Unresolvable entries are silently skipped.
  • resolveInetAddress(String) — resolves a bare host (no port) to InetAddress; IP literals take the fast path without a DNS lookup.

BackupManager (framework)

  • At init(), each configured backup member is resolved via InetUtil.resolveInetAddress. The resolved IP is stored in members; entries with a domain host are additionally tracked in a domainIpCache.
  • A new dnsExecutorService scheduler runs refreshMemberIps() every 60 seconds when domainIpCache is non-empty. If DNS returns a new IP, the old IP is removed from members and the new IP is added; localIp is never added.
  • dnsExecutorService is shut down alongside executorService in stop().

Args (framework)

  • Extracted a shared getInetSockerAddress(config, path) helper that calls InetUtil.resolveInetSocketAddressList, used by both getInetSocketAddress (seed/active/passive nodes) and getInetAddress. Unresolvable domain entries in seed lists are silently dropped; unresolvable backup member entries throw TronError(PARAMETER_INIT) at startup via the new checkBackupMembers() validator.

Dependency

  • Switched libp2p from io.github.tronprotocol:libp2p:2.2.7 to io.github.tronprotocol:libp2p:2.2.8, which exposes the LookUpTxt.lookUpIp API used for DNS resolution.

Behaviour Summary

Config field Unresolvable domain Domain IP change
Seed / active / passive / fastForward Entry skipped silently Re-read at next startup
node.backup.members TronError thrown at startup Auto-refreshed every 60 s

Test

  • Unit tests covering InetUtil resolution paths and BackupManager DNS refresh logic.

Summary by CodeRabbit

  • New Features

    • Added DNS resolution caching with periodic refresh for backup member addresses to handle dynamic network configurations.
  • Improvements

    • Enhanced network configuration validation with centralized address resolution and improved error handling.
    • Improved inet address parsing to support both IP addresses and domain names throughout the application.
    • Updated libp2p dependency to latest version.
  • Chores

    • Added dependency verification checksums for enhanced security.

Summary by cubic

Add domain name support to peer config fields (node.seed.ip.list, node.active, node.passive, node.fastForward, node.backup.members). Domains resolve at startup, and backup member IPs auto-refresh every 60s.

  • New Features

    • Added InetUtil for DNS resolution (IPv4-first, IPv6 fallback) with parallel lookups for multiple domains; IP literals bypass DNS.
    • Seeds/active/passive/fastForward: resolve at startup; unresolvable entries are skipped.
    • Backup members: validated at startup (throws TronError(PARAMETER_INIT) if unresolved); track domain→IP and refresh every 60s, updating members on change and never adding the local IP.
    • Args: shared resolver for InetSocketAddress/InetAddress used across peer config parsing.
  • Dependencies

    • Switched to com.github.317787106:libp2p@v0.10.0 to use LookUpTxt.lookUpIp; updated verification-metadata.xml.

Written for commit 634f111. Summary will update on new commits.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 24, 2026

📝 Walkthrough

Walkthrough

This pull request implements DNS domain name resolution for backup members and seed nodes. A new utility class handles hostname-to-IP conversion with IPv4/IPv6 fallback and parallel resolution. BackupManager now caches resolved IPs and periodically refreshes them via a scheduled executor. Configuration parsing is centralized and validates all backup member entries at startup.

Changes

Cohort / File(s) Summary
Dependency Update
common/build.gradle, gradle/verification-metadata.xml
Updated libp2p from io.github.tronprotocol:2.2.7 to com.github.317787106:v0.10.0 with SHA-256 verification checksums added.
DNS Resolution Infrastructure
framework/src/main/java/org/tron/core/config/args/InetUtil.java
New utility class providing resolveInetSocketAddressList and resolveInetAddress methods. Resolves ipOrDomain:port entries using parallel DNS lookups (IPv4 first, then IPv6 fallback) via bounded thread pool.
Configuration Parsing Refactor
framework/src/main/java/org/tron/core/config/args/Args.java
Refactored inet address parsing with new getInetSockerAddress helper centralizing resolution and validation. Added checkBackupMembers validator to resolve all backup member entries at config load time, failing fast on unresolvable hostnames.
Backup Manager Enhancement
framework/src/main/java/org/tron/common/backup/BackupManager.java
Introduced domainIpCache for domain-to-IP mapping and a scheduled executor for periodic re-resolution of backup member domains. Removed manual getter/setter methods in favor of Lombok @Getter/@Setter. Made several fields final.

Sequence Diagram(s)

sequenceDiagram
    participant Startup as Node Startup
    participant Args as Args.checkBackupMembers()
    participant InetUtil as InetUtil
    participant DNS as DNS Resolver
    participant BackupMgr as BackupManager
    participant Scheduler as ScheduledExecutor

    Startup->>Args: Load node.backup.members config
    activate Args
    Args->>Args: Iterate each member entry
    Args->>InetUtil: resolveInetAddress(member)
    activate InetUtil
    InetUtil->>DNS: Resolve IPv4 (or IPv6 fallback)
    DNS-->>InetUtil: Return InetAddress or null
    InetUtil-->>Args: InetAddress or null
    deactivate InetUtil
    alt Resolution failed
        Args->>Args: Throw TronError(PARAMETER_INIT)
    else Resolution succeeded
        Args->>Args: Add to validated members list
    end
    Args-->>Startup: Validated members with resolved IPs
    deactivate Args

    Startup->>BackupMgr: Initialize with resolved members
    activate BackupMgr
    BackupMgr->>BackupMgr: Populate domainIpCache
    BackupMgr->>Scheduler: Schedule periodic refresh task
    deactivate BackupMgr

    Scheduler->>Scheduler: Fixed interval (e.g., 10 min)
    loop Periodic Re-resolution
        Scheduler->>InetUtil: resolveInetAddress(domain)
        InetUtil->>DNS: Resolve IPv4/IPv6
        DNS-->>InetUtil: New InetAddress or null
        alt IP changed
            InetUtil->>BackupMgr: Update domainIpCache & members
            BackupMgr->>BackupMgr: Atomically swap IP in member set
        else IP unchanged
            InetUtil->>InetUtil: No-op
        else Resolution failed
            BackupMgr->>BackupMgr: Retain last known IP, log warning
        end
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 Hop, hop—domains bloom where IPs once stood!
A cache refreshes, round and round so good.
DNS whispers secrets, parallel and fleet,
IPv4, then IPv6—no heartbeat skipped a beat!
From startup's dawn to midnight's call,
We resolve them all, we resolve them all.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 23.53% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat(net): support domain names in peer configuration' accurately captures the main feature: adding domain name support to network peer configuration.
Linked Issues check ✅ Passed The PR implements all core objectives from issue #6634: domain resolution in backup members and seed lists with synchronous startup blocking for backup members, periodic refresh in BackupManager, asynchronous non-blocking resolution for seeds, and the new InetUtil resolver.
Out of Scope Changes check ✅ Passed All changes align with the stated PR objectives. The libp2p dependency update (2.2.7 to v0.10.0) supports the DNS resolution feature; BackupManager, Args, and InetUtil changes directly implement the requirements from issue #6634.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/support_domain

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🧹 Nitpick comments (6)
gradle/verification-metadata.xml (1)

250-257: Remove the stale verification entry for io.github.tronprotocol:libp2p:2.2.7.

The old coordinate is commented out in common/build.gradle (line 25) and replaced with com.github.317787106:libp2p:v0.10.0 (line 24). The verification block at lines 857–867 in verification-metadata.xml for the 2.2.7 entry is no longer reachable and should be removed. Stale entries can obscure real version drift.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@gradle/verification-metadata.xml` around lines 250 - 257, Remove the stale
verification entry for the old coordinate io.github.tronprotocol:libp2p:2.2.7
from gradle/verification-metadata.xml; since common/build.gradle now uses
com.github.317787106:libp2p:v0.10.0, locate the verification block that
references the io.github.tronprotocol:libp2p:2.2.7 component and delete that
entire component/artifact SHA256 entries so only the current
com.github.317787106:libp2p:v0.10.0 verification remains.
framework/src/main/java/org/tron/core/config/args/Args.java (3)

1675-1683: Sequential DNS on startup path; also duplicates work done later in BackupManager.init().

Two concerns:

  1. Each member is resolved serially with IPv4-then-IPv6 fallback. If any entry is slow/timing out, startup is blocked for N × (ipv4_timeout + ipv6_timeout). For a small backup cluster the wall-clock cost is usually fine, but a single misconfigured entry can stall the node for tens of seconds before the TronError is thrown. Consider using InetUtil.resolveInetSocketAddressList (or a similar parallel helper) and asserting every entry resolved.
  2. BackupManager.init() re-resolves the same list immediately afterwards. Caching the results once on PARAMETER (e.g. a List<InetAddress> sibling to backupMembers) would let both paths share the lookups and avoid a second round of DNS on startup.

Non-blocking for correctness, but worth considering for operational UX.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@framework/src/main/java/org/tron/core/config/args/Args.java` around lines
1675 - 1683, The checkBackupMembers method does serial DNS lookups via
resolveInetAddress causing startup blocking and duplicates work in
BackupManager.init; replace the sequential loop by using a parallel resolver
such as InetUtil.resolveInetSocketAddressList to resolve PARAMETER.backupMembers
in parallel, assert every input resolved, and store the resolved InetAddress
results on PARAMETER (e.g. add a sibling List<InetAddress> like
resolvedBackupMembers) so BackupManager.init can reuse the cached addresses
instead of re-resolving; update checkBackupMembers to populate and validate that
cached list rather than calling resolveInetAddress per entry.

1333-1339: Error message reads awkwardly when wrapping a runtime exception.

String.format("config %s contains %s", path, e.getMessage()) produces messages like config node.active contains Invalid host/port ..., which is hard to parse. Prefer something like "Failed to parse %s: %s" and include the cause:

🛠️ Suggested fix
     try {
       socketAddressList = InetUtil.resolveInetSocketAddressList(list);
     } catch (RuntimeException e) {
-      throw new TronError(String.format("config %s contains %s", path, e.getMessage()),
-          TronError.ErrCode.PARAMETER_INIT);
+      throw new TronError(
+          String.format("Failed to parse %s: %s", path, e.getMessage()),
+          e, TronError.ErrCode.PARAMETER_INIT);
     }

(Assumes a TronError(String, Throwable, ErrCode) constructor exists; if not, at least propagate e.getMessage() in the same sentence structure.)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@framework/src/main/java/org/tron/core/config/args/Args.java` around lines
1333 - 1339, The catch block that wraps
InetUtil.resolveInetSocketAddressList(list) currently throws a TronError with an
awkward message via String.format("config %s contains %s", path,
e.getMessage()); change this to a clearer message like "Failed to parse %s: %s"
and pass the original exception as the cause when creating the TronError (i.e.,
use the TronError constructor that accepts a Throwable) so the thrown error
includes both the clearer text and the original exception; update the throw in
Args (the catch around InetUtil.resolveInetSocketAddressList) to use the new
message format and include e as the cause, or at minimum reorder message
formatting to "%s: %s" and include e.getMessage().

1326-1340: Typo in helper name: getInetSockerAddressgetInetSocketAddress.

The private helper is misspelled (Socker). Since the public overload is getInetSocketAddress, the two names differ by only a character, which is both confusing and easy to miss at a call-site. Given it's private, renaming is risk-free.

🛠️ Suggested fix
-  private static List<InetSocketAddress> getInetSockerAddress(
+  private static List<InetSocketAddress> resolveInetSocketAddresses(
       final com.typesafe.config.Config config, String path) {

(And update the two internal callers on lines 1308 and 1345.)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@framework/src/main/java/org/tron/core/config/args/Args.java` around lines
1326 - 1340, Private helper getInetSockerAddress is misspelled; rename it to
getInetSocketAddress to match the public overload and avoid confusion. Change
the method name getInetSockerAddress to getInetSocketAddress, update all
internal callers that reference the old name to the new name, ensure there is no
duplicate signature/conflict with the existing public getInetSocketAddress
overload, and run a build to confirm all references compile.
framework/src/main/java/org/tron/common/backup/BackupManager.java (1)

54-56: Minor: allocate the DNS-refresh executor lazily.

dnsExecutorService is created unconditionally at field-init time, but it's only scheduled when domainIpCache is non-empty (line 130). For deployments that stick to IP literals (the vast majority today), this spins up — and later shuts down — an unused scheduled-executor thread. Consider deferring creation to inside the if (!domainIpCache.isEmpty()) block.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@framework/src/main/java/org/tron/common/backup/BackupManager.java` around
lines 54 - 56, The dnsExecutorService is created eagerly at field initialization
(dnsExecutorService =
ExecutorServiceManager.newSingleThreadScheduledExecutor(dnsEsName)) but only
used when domainIpCache is non-empty; change to lazy allocation by replacing the
field with a nullable/optional reference (e.g., private ScheduledExecutorService
dnsExecutorService = null) and instantiate it inside the existing if
(!domainIpCache.isEmpty()) block using
ExecutorServiceManager.newSingleThreadScheduledExecutor(dnsEsName); update any
shutdown/cleanup logic to check for null before calling shutdown on
dnsExecutorService and keep dnsEsName and domainIpCache references unchanged so
the allocator and lifecycle remain consistent.
framework/src/main/java/org/tron/core/config/args/InetUtil.java (1)

51-97: Parse each entry once and reuse.

NetUtil.parseInetSocketAddress(item) is invoked on line 54 (filter), line 88 (rebuild), and a third time inside resolveInetSocketAddress for each submitted domain. Besides the redundant work, any malformed entry will also throw from two different code paths, making error messages inconsistent. Parsing once and threading InetSocketAddress objects through the pipeline is both cleaner and more robust.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@framework/src/main/java/org/tron/core/config/args/InetUtil.java` around lines
51 - 97, The code repeatedly calls NetUtil.parseInetSocketAddress(item) in the
filter loop, when submitting DNS tasks, and again when rebuilding the result;
parse each input string once up-front into an InetSocketAddress (e.g., produce a
List<Pair<String,InetSocketAddress>> or Map<String,InetSocketAddress> from
ipOrDomainWithPortList), use the parsed address to decide whether it is an IP
literal (isIpLiteral(parsed.getHostString())) and only submit unresolved domain
InetSocketAddress objects to resolveInetSocketAddress, store resolved results in
resolvedDomains keyed by the original input string, and then build the final
result from those parsed/resolved addresses so parsing is not duplicated and
malformed-entry parsing errors occur in a single place.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@common/build.gradle`:
- Around line 24-25: The build currently depends on a personal JitPack fork
(com.github.317787106:libp2p:v0.10.0) instead of the official
io.github.tronprotocol libp2p artifact; revert common/build.gradle to use the
official dependency (replace the com.github... coordinate with
io.github.tronprotocol:libp2p at the appropriate released version) or, if the
forked API (LookUpTxt.lookUpIp) is truly required and not yet upstream, move the
fork dependency behind a clearly named non-default Gradle profile (e.g.,
"jitpack-fork") and document that exception, and remove the commented-out
io.github.tronprotocol line so no dead config remains; also create a ticket to
coordinate with the libp2p maintainers to upstream LookUpTxt.lookUpIp (or get an
official 2.2.8+ release) so the fork can be removed.

In `@framework/src/main/java/org/tron/common/backup/BackupManager.java`:
- Around line 86-100: The loop over parameter.getBackupMembers() skips caching
domain names that resolve to localIp, so later DNS changes are never detected by
refreshMemberIps(); change the logic in the loop (around resolveInetAddress,
localIp, domainIpCache, members and NetUtil.validIpV4/validIpV6) to always
populate domainIpCache.put(ipOrDomain, ip) for domain names (i.e., when
!NetUtil.validIpV4(ipOrDomain) && !NetUtil.validIpV6(ipOrDomain)) before the
localIp check/continue, and only then skip adding the ip to members when
ip.equals(localIp).
- Around line 195-214: refreshMemberIps currently unconditionally removes oldIp
from members which can drop an IP still referenced by another domain; change the
removal logic to only remove oldIp when no other domain in domainIpCache maps to
that same IP and the oldIp is not present as an explicit IP-literal member or
the localIp. Specifically, inside refreshMemberIps (using domainIpCache,
members, localIp), before calling members.remove(oldIp) check: (1) iterate
domainIpCache.values() to see if any other domain (key != current domain) still
resolves to oldIp, and (2) ensure oldIp is not equal to localIp and not present
in members as an explicit IP-literal that should be preserved; only call
members.remove(oldIp) if both checks indicate it is no longer referenced. Also
ensure you still add newIp (unless it equals localIp) and update
domainIpCache.put(domain, newIp) as before.

In `@framework/src/main/java/org/tron/core/config/args/InetUtil.java`:
- Around line 60-84: The single-domain branch in InetUtil currently calls
resolveInetSocketAddress(entry) directly and can throw/propagate runtime
exceptions; change it to mirror the multi-domain path by wrapping the
single-domain resolution in a try/catch, call resolveInetSocketAddress(entry)
inside the try, only put into resolvedDomains on success, and catch and log
failures (e.g., catch RuntimeException/Exception and logger.warn("Failed to
resolve address, skip: {}", entry)); this keeps behavior consistent with the
parallel path (resolveInetSocketAddress, resolvedDomains) and prevents startup
failures observed in Args.getInetSockerAddress.

---

Nitpick comments:
In `@framework/src/main/java/org/tron/common/backup/BackupManager.java`:
- Around line 54-56: The dnsExecutorService is created eagerly at field
initialization (dnsExecutorService =
ExecutorServiceManager.newSingleThreadScheduledExecutor(dnsEsName)) but only
used when domainIpCache is non-empty; change to lazy allocation by replacing the
field with a nullable/optional reference (e.g., private ScheduledExecutorService
dnsExecutorService = null) and instantiate it inside the existing if
(!domainIpCache.isEmpty()) block using
ExecutorServiceManager.newSingleThreadScheduledExecutor(dnsEsName); update any
shutdown/cleanup logic to check for null before calling shutdown on
dnsExecutorService and keep dnsEsName and domainIpCache references unchanged so
the allocator and lifecycle remain consistent.

In `@framework/src/main/java/org/tron/core/config/args/Args.java`:
- Around line 1675-1683: The checkBackupMembers method does serial DNS lookups
via resolveInetAddress causing startup blocking and duplicates work in
BackupManager.init; replace the sequential loop by using a parallel resolver
such as InetUtil.resolveInetSocketAddressList to resolve PARAMETER.backupMembers
in parallel, assert every input resolved, and store the resolved InetAddress
results on PARAMETER (e.g. add a sibling List<InetAddress> like
resolvedBackupMembers) so BackupManager.init can reuse the cached addresses
instead of re-resolving; update checkBackupMembers to populate and validate that
cached list rather than calling resolveInetAddress per entry.
- Around line 1333-1339: The catch block that wraps
InetUtil.resolveInetSocketAddressList(list) currently throws a TronError with an
awkward message via String.format("config %s contains %s", path,
e.getMessage()); change this to a clearer message like "Failed to parse %s: %s"
and pass the original exception as the cause when creating the TronError (i.e.,
use the TronError constructor that accepts a Throwable) so the thrown error
includes both the clearer text and the original exception; update the throw in
Args (the catch around InetUtil.resolveInetSocketAddressList) to use the new
message format and include e as the cause, or at minimum reorder message
formatting to "%s: %s" and include e.getMessage().
- Around line 1326-1340: Private helper getInetSockerAddress is misspelled;
rename it to getInetSocketAddress to match the public overload and avoid
confusion. Change the method name getInetSockerAddress to getInetSocketAddress,
update all internal callers that reference the old name to the new name, ensure
there is no duplicate signature/conflict with the existing public
getInetSocketAddress overload, and run a build to confirm all references
compile.

In `@framework/src/main/java/org/tron/core/config/args/InetUtil.java`:
- Around line 51-97: The code repeatedly calls
NetUtil.parseInetSocketAddress(item) in the filter loop, when submitting DNS
tasks, and again when rebuilding the result; parse each input string once
up-front into an InetSocketAddress (e.g., produce a
List<Pair<String,InetSocketAddress>> or Map<String,InetSocketAddress> from
ipOrDomainWithPortList), use the parsed address to decide whether it is an IP
literal (isIpLiteral(parsed.getHostString())) and only submit unresolved domain
InetSocketAddress objects to resolveInetSocketAddress, store resolved results in
resolvedDomains keyed by the original input string, and then build the final
result from those parsed/resolved addresses so parsing is not duplicated and
malformed-entry parsing errors occur in a single place.

In `@gradle/verification-metadata.xml`:
- Around line 250-257: Remove the stale verification entry for the old
coordinate io.github.tronprotocol:libp2p:2.2.7 from
gradle/verification-metadata.xml; since common/build.gradle now uses
com.github.317787106:libp2p:v0.10.0, locate the verification block that
references the io.github.tronprotocol:libp2p:2.2.7 component and delete that
entire component/artifact SHA256 entries so only the current
com.github.317787106:libp2p:v0.10.0 verification remains.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: c8388a78-066b-4989-9f2c-c77171921972

📥 Commits

Reviewing files that changed from the base of the PR and between f0a8f0f and 634f111.

📒 Files selected for processing (5)
  • common/build.gradle
  • framework/src/main/java/org/tron/common/backup/BackupManager.java
  • framework/src/main/java/org/tron/core/config/args/Args.java
  • framework/src/main/java/org/tron/core/config/args/InetUtil.java
  • gradle/verification-metadata.xml

Comment thread common/build.gradle
Comment on lines +24 to +25
api group: 'com.github.317787106', name: 'libp2p', version: 'v0.10.0',{
//api group: 'io.github.tronprotocol', name: 'libp2p', version: '2.2.7',{
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🌐 Web query:

Does the io.github.tronprotocol:libp2p Maven artifact have a 2.2.8 release that exposes LookUpTxt.lookUpIp?

💡 Result:

No, the io.github.tronprotocol:libp2p Maven artifact does not have a 2.2.8 release. The latest available version is 2.2.7, released on November 12, 2025. No sources mention a 2.2.8 release on Maven Central, Sonatype Central, mvnrepository.com, or the project's GitHub releases page. No evidence was found for a class or method named LookUpTxt.lookUpIp in the libp2p codebase or documentation across all versions.

Citations:


🏁 Script executed:

#!/bin/bash
# Check what the new fork coordinate actually resolves to on JitPack and whether
# LookUpTxt.lookUpIp is referenced anywhere in this repo's sources.
rg -nP -C2 '\bLookUpTxt\b|lookUpIp\s*\('
# Show every Gradle file referencing libp2p to confirm no other module pins the old coord.
rg -nP --type=gradle -C1 'libp2p'

Repository: 317787106/java-tron

Length of output: 2290


Do not ship a personal JitPack fork of libp2p as the production dependency.

This replaces the official io.github.tronprotocol:libp2p:2.2.7 with com.github.317787106:libp2p:v0.10.0. The new group coordinate com.github.317787106 is a JitPack path that resolves to a personal GitHub account (user id 317787106), not the tronprotocol organization. Several critical problems:

  1. PR summary is incorrect. The PR description states the bump is libp2p 2.2.7 -> 2.2.8 to acquire LookUpTxt.lookUpIp, but this is misleading: io.github.tronprotocol:libp2p has no 2.2.8 release (latest is 2.2.7), and LookUpTxt.lookUpIp does not exist in the official libp2p codebase. The method exists only in the fork (com.github.317787106). The actual change is 2.2.7 -> v0.10.0 from a personal account — this is a material difference from what the commit message promises.

  2. The fork is not optional, making supply chain risk acute. Because LookUpTxt.lookUpIp is used in this codebase (see framework/src/main/java/org/tron/core/config/args/InetUtil.java lines 112, 114, 141, 143) and exists nowhere else, the fork dependency is not a temporary workaround—it is a production requirement for a mainnet node's core P2P configuration. This means the trust anchor is permanently anchored to an individual contributor's GitHub account rather than the Tron Protocol organization.

  3. Commented-out coordinate. Line 25 retains the old io.github.tronprotocol dependency as a comment. Dead config should not be merged. Its presence is a strong signal that this change was a development workaround that was never properly resolved upstream.

Recommended: Coordinate with the libp2p maintainers to release LookUpTxt.lookUpIp under the official io.github.tronprotocol org (e.g., as 2.2.8 or later), then revert to the official dependency. If that is not feasible in the release timeline, gate the fork behind a clearly labeled non-default profile and document the limitation and timeline for resolution in the PR. Do not merge commented-out code.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@common/build.gradle` around lines 24 - 25, The build currently depends on a
personal JitPack fork (com.github.317787106:libp2p:v0.10.0) instead of the
official io.github.tronprotocol libp2p artifact; revert common/build.gradle to
use the official dependency (replace the com.github... coordinate with
io.github.tronprotocol:libp2p at the appropriate released version) or, if the
forked API (LookUpTxt.lookUpIp) is truly required and not yet upstream, move the
fork dependency behind a clearly named non-default Gradle profile (e.g.,
"jitpack-fork") and document that exception, and remove the commented-out
io.github.tronprotocol line so no dead config remains; also create a ticket to
coordinate with the libp2p maintainers to upstream LookUpTxt.lookUpIp (or get an
official 2.2.8+ release) so the fork can be removed.

Comment on lines +86 to 100
for (String ipOrDomain : parameter.getBackupMembers()) {
InetAddress inetAddress = resolveInetAddress(ipOrDomain);
if (inetAddress == null) {
logger.warn("Failed to resolve backup member domain: {}", ipOrDomain);
continue;
}
String ip = inetAddress.getHostAddress();
if (localIp.equals(ip)) {
continue;
}
if (!NetUtil.validIpV4(ipOrDomain) && !NetUtil.validIpV6(ipOrDomain)) {
domainIpCache.put(ipOrDomain, ip);
}
members.add(ip);
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Domains resolving to localIp at startup are never tracked for DNS refresh.

When a configured backup member is a domain name that currently resolves to localIp, the continue at line 94 fires before domainIpCache.put(...). If that DNS record later points to a non-local IP, the change will never be picked up by refreshMemberIps() — the only way to recover is a process restart.

Unless that's intentional, cache the domain first and only skip adding it to members:

🛠️ Suggested fix
     for (String ipOrDomain : parameter.getBackupMembers()) {
       InetAddress inetAddress = resolveInetAddress(ipOrDomain);
       if (inetAddress == null) {
         logger.warn("Failed to resolve backup member domain: {}", ipOrDomain);
         continue;
       }
       String ip = inetAddress.getHostAddress();
-      if (localIp.equals(ip)) {
-        continue;
-      }
       if (!NetUtil.validIpV4(ipOrDomain) && !NetUtil.validIpV6(ipOrDomain)) {
         domainIpCache.put(ipOrDomain, ip);
       }
+      if (localIp.equals(ip)) {
+        continue;
+      }
       members.add(ip);
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@framework/src/main/java/org/tron/common/backup/BackupManager.java` around
lines 86 - 100, The loop over parameter.getBackupMembers() skips caching domain
names that resolve to localIp, so later DNS changes are never detected by
refreshMemberIps(); change the logic in the loop (around resolveInetAddress,
localIp, domainIpCache, members and NetUtil.validIpV4/validIpV6) to always
populate domainIpCache.put(ipOrDomain, ip) for domain names (i.e., when
!NetUtil.validIpV4(ipOrDomain) && !NetUtil.validIpV6(ipOrDomain)) before the
localIp check/continue, and only then skip adding the ip to members when
ip.equals(localIp).

Comment on lines +195 to +214
private void refreshMemberIps() {
for (Map.Entry<String, String> entry : domainIpCache.entrySet()) {
String domain = entry.getKey();
String oldIp = entry.getValue();
InetAddress inetAddress = resolveInetAddress(domain);
if (inetAddress == null) {
logger.warn("DNS refresh: failed to re-resolve backup member domain {}, keep it", domain);
continue;
}
String newIp = inetAddress.getHostAddress();
if (!newIp.equals(oldIp)) {
logger.info("DNS refresh: backup member {} IP changed {} -> {}", domain, oldIp, newIp);
members.remove(oldIp);
if (!localIp.equals(newIp)) {
members.add(newIp);
}
domainIpCache.put(domain, newIp);
}
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

refreshMemberIps may drop an IP that is still in use by another tracked domain.

members.remove(oldIp) is unconditional. If two backup-member domains currently resolve to the same IP and only one of them rotates to a new address, the shared IP is removed from members even though the second domain still depends on it — keepalive messages will stop flowing to that peer until the other domain is also refreshed (or a restart happens).

Consider keeping oldIp only if no other entry in domainIpCache (or a plain IP-literal member) still maps to it:

🛠️ Sketch
       if (!newIp.equals(oldIp)) {
         logger.info("DNS refresh: backup member {} IP changed {} -> {}", domain, oldIp, newIp);
-        members.remove(oldIp);
+        boolean stillReferenced = domainIpCache.entrySet().stream()
+            .anyMatch(e -> !e.getKey().equals(domain) && oldIp.equals(e.getValue()));
+        if (!stillReferenced) {
+          members.remove(oldIp);
+        }
         if (!localIp.equals(newIp)) {
           members.add(newIp);
         }
         domainIpCache.put(domain, newIp);
       }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@framework/src/main/java/org/tron/common/backup/BackupManager.java` around
lines 195 - 214, refreshMemberIps currently unconditionally removes oldIp from
members which can drop an IP still referenced by another domain; change the
removal logic to only remove oldIp when no other domain in domainIpCache maps to
that same IP and the oldIp is not present as an explicit IP-literal member or
the localIp. Specifically, inside refreshMemberIps (using domainIpCache,
members, localIp), before calling members.remove(oldIp) check: (1) iterate
domainIpCache.values() to see if any other domain (key != current domain) still
resolves to oldIp, and (2) ensure oldIp is not equal to localIp and not present
in members as an explicit IP-literal that should be preserved; only call
members.remove(oldIp) if both checks indicate it is no longer referenced. Also
ensure you still add newIp (unless it equals localIp) and update
domainIpCache.put(domain, newIp) as before.

Comment on lines +60 to +84
Map<String, InetSocketAddress> resolvedDomains = new HashMap<>();
if (domainEntries.size() > 1) {
int poolSize = StrictMath.min(domainEntries.size(), DNS_POOL_MAX_SIZE);
ExecutorService dnsPool = ExecutorServiceManager
.newFixedThreadPool(DNS_POOL_NAME, poolSize, true);
List<Future<InetSocketAddress>> futures = new ArrayList<>(domainEntries.size());
for (String entry : domainEntries) {
futures.add(dnsPool.submit(() -> resolveInetSocketAddress(entry)));
}
for (int i = 0; i < domainEntries.size(); i++) {
String entry = domainEntries.get(i);
try {
resolvedDomains.put(entry, futures.get(i).get());
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
logger.warn("DNS lookup interrupted for: {}", entry);
} catch (ExecutionException e) {
logger.warn("Failed to resolve address, skip: {}", entry);
}
}
ExecutorServiceManager.shutdownAndAwaitTermination(dnsPool, DNS_POOL_NAME);
} else if (domainEntries.size() == 1) {
String entry = domainEntries.get(0);
resolvedDomains.put(entry, resolveInetSocketAddress(entry));
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Single-domain path skips the try/catch that the parallel path has.

When domainEntries.size() == 1, resolveInetSocketAddress(entry) is called inline (line 83). If the libp2p lookup throws a RuntimeException for any reason, it propagates up and turns into a TronError(PARAMETER_INIT) in Args.getInetSockerAddress — i.e. startup fails. The multi-domain path catches ExecutionException, logs a warning, and skips. The behavior for seed/active/passive/fastForward should be "skip on failure" per the PR objectives regardless of whether there's 1 or N domains.

🛠️ Suggested fix
     } else if (domainEntries.size() == 1) {
       String entry = domainEntries.get(0);
-      resolvedDomains.put(entry, resolveInetSocketAddress(entry));
+      try {
+        resolvedDomains.put(entry, resolveInetSocketAddress(entry));
+      } catch (RuntimeException e) {
+        logger.warn("Failed to resolve address, skip: {}", entry);
+      }
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
Map<String, InetSocketAddress> resolvedDomains = new HashMap<>();
if (domainEntries.size() > 1) {
int poolSize = StrictMath.min(domainEntries.size(), DNS_POOL_MAX_SIZE);
ExecutorService dnsPool = ExecutorServiceManager
.newFixedThreadPool(DNS_POOL_NAME, poolSize, true);
List<Future<InetSocketAddress>> futures = new ArrayList<>(domainEntries.size());
for (String entry : domainEntries) {
futures.add(dnsPool.submit(() -> resolveInetSocketAddress(entry)));
}
for (int i = 0; i < domainEntries.size(); i++) {
String entry = domainEntries.get(i);
try {
resolvedDomains.put(entry, futures.get(i).get());
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
logger.warn("DNS lookup interrupted for: {}", entry);
} catch (ExecutionException e) {
logger.warn("Failed to resolve address, skip: {}", entry);
}
}
ExecutorServiceManager.shutdownAndAwaitTermination(dnsPool, DNS_POOL_NAME);
} else if (domainEntries.size() == 1) {
String entry = domainEntries.get(0);
resolvedDomains.put(entry, resolveInetSocketAddress(entry));
}
Map<String, InetSocketAddress> resolvedDomains = new HashMap<>();
if (domainEntries.size() > 1) {
int poolSize = StrictMath.min(domainEntries.size(), DNS_POOL_MAX_SIZE);
ExecutorService dnsPool = ExecutorServiceManager
.newFixedThreadPool(DNS_POOL_NAME, poolSize, true);
List<Future<InetSocketAddress>> futures = new ArrayList<>(domainEntries.size());
for (String entry : domainEntries) {
futures.add(dnsPool.submit(() -> resolveInetSocketAddress(entry)));
}
for (int i = 0; i < domainEntries.size(); i++) {
String entry = domainEntries.get(i);
try {
resolvedDomains.put(entry, futures.get(i).get());
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
logger.warn("DNS lookup interrupted for: {}", entry);
} catch (ExecutionException e) {
logger.warn("Failed to resolve address, skip: {}", entry);
}
}
ExecutorServiceManager.shutdownAndAwaitTermination(dnsPool, DNS_POOL_NAME);
} else if (domainEntries.size() == 1) {
String entry = domainEntries.get(0);
try {
resolvedDomains.put(entry, resolveInetSocketAddress(entry));
} catch (RuntimeException e) {
logger.warn("Failed to resolve address, skip: {}", entry);
}
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@framework/src/main/java/org/tron/core/config/args/InetUtil.java` around lines
60 - 84, The single-domain branch in InetUtil currently calls
resolveInetSocketAddress(entry) directly and can throw/propagate runtime
exceptions; change it to mirror the multi-domain path by wrapping the
single-domain resolution in a try/catch, call resolveInetSocketAddress(entry)
inside the try, only put into resolvedDomains on success, and catch and log
failures (e.g., catch RuntimeException/Exception and logger.warn("Failed to
resolve address, skip: {}", entry)); this keeps behavior consistent with the
parallel path (resolveInetSocketAddress, resolvedDomains) and prevents startup
failures observed in Args.getInetSockerAddress.

Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

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

2 issues found across 5 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="framework/src/main/java/org/tron/common/backup/BackupManager.java">

<violation number="1" location="framework/src/main/java/org/tron/common/backup/BackupManager.java:93">
P1: Domains resolving to `localIp` at startup are skipped before being cached, so later DNS changes for those domains are never picked up.</violation>

<violation number="2" location="framework/src/main/java/org/tron/common/backup/BackupManager.java:207">
P1: Removing `oldIp` unconditionally can drop a still-active backup member when multiple domains share the same IP.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Comment on lines +93 to +99
if (localIp.equals(ip)) {
continue;
}
if (!NetUtil.validIpV4(ipOrDomain) && !NetUtil.validIpV6(ipOrDomain)) {
domainIpCache.put(ipOrDomain, ip);
}
members.add(ip);
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot Apr 24, 2026

Choose a reason for hiding this comment

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

P1: Domains resolving to localIp at startup are skipped before being cached, so later DNS changes for those domains are never picked up.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At framework/src/main/java/org/tron/common/backup/BackupManager.java, line 93:

<comment>Domains resolving to `localIp` at startup are skipped before being cached, so later DNS changes for those domains are never picked up.</comment>

<file context>
@@ -78,10 +83,20 @@ public void init() {
+        continue;
+      }
+      String ip = inetAddress.getHostAddress();
+      if (localIp.equals(ip)) {
+        continue;
+      }
</file context>
Suggested change
if (localIp.equals(ip)) {
continue;
}
if (!NetUtil.validIpV4(ipOrDomain) && !NetUtil.validIpV6(ipOrDomain)) {
domainIpCache.put(ipOrDomain, ip);
}
members.add(ip);
if (!NetUtil.validIpV4(ipOrDomain) && !NetUtil.validIpV6(ipOrDomain)) {
domainIpCache.put(ipOrDomain, ip);
}
if (localIp.equals(ip)) {
continue;
}
members.add(ip);
Fix with Cubic

String newIp = inetAddress.getHostAddress();
if (!newIp.equals(oldIp)) {
logger.info("DNS refresh: backup member {} IP changed {} -> {}", domain, oldIp, newIp);
members.remove(oldIp);
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot Apr 24, 2026

Choose a reason for hiding this comment

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

P1: Removing oldIp unconditionally can drop a still-active backup member when multiple domains share the same IP.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At framework/src/main/java/org/tron/common/backup/BackupManager.java, line 207:

<comment>Removing `oldIp` unconditionally can drop a still-active backup member when multiple domains share the same IP.</comment>

<file context>
@@ -162,4 +188,28 @@ public enum BackupStatusEnum {
+      String newIp = inetAddress.getHostAddress();
+      if (!newIp.equals(oldIp)) {
+        logger.info("DNS refresh: backup member {} IP changed {} -> {}", domain, oldIp, newIp);
+        members.remove(oldIp);
+        if (!localIp.equals(newIp)) {
+          members.add(newIp);
</file context>
Suggested change
members.remove(oldIp);
if (domainIpCache.entrySet().stream().noneMatch(e -> !e.getKey().equals(domain) && oldIp.equals(e.getValue()))) {
members.remove(oldIp);
}
Fix with Cubic

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature] Support domain names in node.backup.members and seed.node.ip.list

1 participant