Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion common/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ dependencies {
api 'org.aspectj:aspectjrt:1.9.8'
api 'org.aspectj:aspectjweaver:1.9.8'
api 'org.aspectj:aspectjtools:1.9.8'
api group: 'io.github.tronprotocol', name: 'libp2p', version: '2.2.7',{
api group: 'com.github.317787106', name: 'libp2p', version: 'v0.0.9',{
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai bot Apr 16, 2026

Choose a reason for hiding this comment

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

P0: Supply-chain risk: This replaces the official io.github.tronprotocol libp2p artifact with a personal GitHub fork (com.github.317787106) resolved via JitPack. For a blockchain node, pulling a core networking dependency from an individual's fork introduces serious supply-chain risk — the fork is outside the project's normal review/release process and could contain unaudited changes. If this is intentional for local development, it should not be merged to a shared branch; if the upstream library needs patches, they should be contributed back and released under the official group ID.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At common/build.gradle, line 24:

<comment>**Supply-chain risk**: This replaces the official `io.github.tronprotocol` libp2p artifact with a personal GitHub fork (`com.github.317787106`) resolved via JitPack. For a blockchain node, pulling a core networking dependency from an individual's fork introduces serious supply-chain risk — the fork is outside the project's normal review/release process and could contain unaudited changes. If this is intentional for local development, it should not be merged to a shared branch; if the upstream library needs patches, they should be contributed back and released under the official group ID.</comment>

<file context>
@@ -21,7 +21,8 @@ dependencies {
     api 'org.aspectj:aspectjweaver:1.9.8'
     api 'org.aspectj:aspectjtools:1.9.8'
-    api group: 'io.github.tronprotocol', name: 'libp2p', version: '2.2.7',{
+    api group: 'com.github.317787106', name: 'libp2p', version: 'v0.0.9',{
+    //api group: 'io.github.tronprotocol', name: 'libp2p', version: '2.2.7',{
         exclude group: 'io.grpc', module: 'grpc-context'
</file context>
Fix with Cubic

//api group: 'io.github.tronprotocol', name: 'libp2p', version: '2.2.7',{
exclude group: 'io.grpc', module: 'grpc-context'
exclude group: 'io.grpc', module: 'grpc-core'
exclude group: 'io.grpc', module: 'grpc-netty'
Expand Down
88 changes: 69 additions & 19 deletions framework/src/main/java/org/tron/common/backup/BackupManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,18 @@
import static org.tron.common.backup.BackupManager.BackupStatusEnum.MASTER;
import static org.tron.common.backup.BackupManager.BackupStatusEnum.SLAVER;
import static org.tron.common.backup.message.UdpMessageTypeEnum.BACKUP_KEEP_ALIVE;
import static org.tron.core.config.args.InetUtil.resolveInetAddress;

import io.netty.util.internal.ConcurrentSet;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.tron.common.backup.message.KeepAliveMessage;
Expand All @@ -20,46 +25,46 @@
import org.tron.common.backup.socket.UdpEvent;
import org.tron.common.es.ExecutorServiceManager;
import org.tron.common.parameter.CommonParameter;
import org.tron.p2p.utils.NetUtil;

@Slf4j(topic = "backup")
@Component
public class BackupManager implements EventHandler {

private CommonParameter parameter = CommonParameter.getInstance();
private final CommonParameter parameter = CommonParameter.getInstance();

private int priority = parameter.getBackupPriority();
private final int priority = parameter.getBackupPriority();

private int port = parameter.getBackupPort();
private final int port = parameter.getBackupPort();

private int keepAliveInterval = parameter.getKeepAliveInterval();
private final int keepAliveInterval = parameter.getKeepAliveInterval();

private int keepAliveTimeout = keepAliveInterval * 6;
private final int keepAliveTimeout = keepAliveInterval * 6;

private String localIp = "";

private Set<String> members = new ConcurrentSet<>();
private final Set<String> members = new ConcurrentSet<>();

private final String esName = "backup-manager";
private final Map<String, String> domainIpCache = new ConcurrentHashMap<>();

private ScheduledExecutorService executorService =
private final String esName = "backup-manager";
private final ScheduledExecutorService executorService =
ExecutorServiceManager.newSingleThreadScheduledExecutor(esName);

private final String dnsEsName = "backup-dns-refresh";
private final ScheduledExecutorService dnsExecutorService =
ExecutorServiceManager.newSingleThreadScheduledExecutor(dnsEsName);

@Setter
private MessageHandler messageHandler;

@Getter
private BackupStatusEnum status = MASTER;

private volatile long lastKeepAliveTime;

private volatile boolean isInit = false;

public void setMessageHandler(MessageHandler messageHandler) {
this.messageHandler = messageHandler;
}

public BackupStatusEnum getStatus() {
return status;
}

public void setStatus(BackupStatusEnum status) {
logger.info("Change backup status to {}", status);
this.status = status;
Expand All @@ -78,10 +83,20 @@ public void init() {
logger.warn("Failed to get local ip");
}

for (String member : parameter.getBackupMembers()) {
if (!localIp.equals(member)) {
members.add(member);
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);
}

logger.info("Backup localIp:{}, members: size= {}, {}", localIp, members.size(), members);
Expand Down Expand Up @@ -111,6 +126,16 @@ public void init() {
logger.error("Exception in send keep alive", t);
}
}, 1000, keepAliveInterval, TimeUnit.MILLISECONDS);

if (!domainIpCache.isEmpty()) {
dnsExecutorService.scheduleWithFixedDelay(() -> {
try {
refreshMemberIps();
} catch (Throwable t) {
logger.error("Exception in backup DNS refresh", t);
}
}, 60_000L, 60_000L, TimeUnit.MILLISECONDS);
}
}

@Override
Expand Down Expand Up @@ -149,6 +174,7 @@ public void handleEvent(UdpEvent udpEvent) {

public void stop() {
ExecutorServiceManager.shutdownAndAwaitTermination(executorService, esName);
ExecutorServiceManager.shutdownAndAwaitTermination(dnsExecutorService, dnsEsName);
}

@Override
Expand All @@ -162,4 +188,28 @@ public enum BackupStatusEnum {
MASTER
}

/**
* Re-resolves all tracked domain entries. If an IP has changed, the old IP is
* removed from {@link #members} and the new IP is added.
*/
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);
}
Comment on lines +195 to +212
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Don't remove a shared IP blindly during DNS refresh.

Line 207 drops oldIp from members as soon as one domain changes. That breaks when two domains, or a domain plus a literal backup member, currently resolve to the same IP: refreshing one entry removes the other still-valid peer from the set.

members needs either reference counting per IP or a full recompute from all current sources after each refresh, rather than members.remove(oldIp) on a single domain change.

🤖 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 - 212, The refreshMemberIps method currently calls
members.remove(oldIp) when a domain's IP changes, which can drop a shared IP
still referenced by other domains; modify refreshMemberIps to avoid removing an
IP blindly by either (A) maintaining a reference count map keyed by IP
(increment when any domain or literal member maps to an IP, decrement and only
remove from members when count reaches zero) or (B) after resolving all domains
in domainIpCache, recompute the members set from scratch using the updated
domainIpCache plus the static/literal backup members and excluding localIp;
update domainIpCache and members atomically so refreshMemberIps no longer calls
members.remove(oldIp) for a single-domain change.

}
}
}
17 changes: 14 additions & 3 deletions framework/src/main/java/org/tron/core/config/args/Args.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import static org.tron.core.Constant.MIN_PROPOSAL_EXPIRE_TIME;
import static org.tron.core.config.Parameter.ChainConstant.BLOCK_PRODUCE_TIMEOUT_PERCENT;
import static org.tron.core.config.Parameter.ChainConstant.MAX_ACTIVE_WITNESS_NUM;
import static org.tron.core.config.args.InetUtil.resolveInetAddress;
import static org.tron.core.exception.TronError.ErrCode.PARAMETER_INIT;

import com.beust.jcommander.JCommander;
Expand All @@ -37,7 +38,6 @@
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.BlockingQueue;
Expand Down Expand Up @@ -74,6 +74,7 @@
import org.tron.core.exception.TronError;
import org.tron.core.store.AccountStore;
import org.tron.p2p.P2pConfig;
import org.tron.p2p.dns.lookup.LookUpTxt;
import org.tron.p2p.dns.update.DnsType;
import org.tron.p2p.dns.update.PublishConfig;
import org.tron.p2p.utils.NetUtil;
Expand Down Expand Up @@ -1308,8 +1309,7 @@ public static List<InetSocketAddress> getInetSocketAddress(
return ret;
}
List<String> list = config.getStringList(path);
for (String configString : list) {
InetSocketAddress inetSocketAddress = NetUtil.parseInetSocketAddress(configString);
for (InetSocketAddress inetSocketAddress : InetUtil.getInetSocketAddressList(list)) {
if (filter) {
String ip = inetSocketAddress.getAddress().getHostAddress();
int port = inetSocketAddress.getPort();
Expand Down Expand Up @@ -1660,6 +1660,17 @@ private static void initBackupProperty(Config config) {

PARAMETER.backupMembers = config.hasPath(ConfigKey.NODE_BACKUP_MEMBERS)
? config.getStringList(ConfigKey.NODE_BACKUP_MEMBERS) : new ArrayList<>();
checkBackupMembers();
}

private static void checkBackupMembers() {
for (String member : PARAMETER.backupMembers) {
InetAddress inetAddress = resolveInetAddress(member);
if (inetAddress == null) {
throw new TronError("Failed to resolve backup member: " + member,
TronError.ErrCode.PARAMETER_INIT);
}
}
Comment on lines +1663 to +1673
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Don't make node startup fail on transient DNS for backup peers.

checkBackupMembers() now resolves every backup member during config load and throws on any miss. That is much stricter than framework/src/main/java/org/tron/common/backup/BackupManager.java:86-90, which already skips unresolved members, and BackupManager.java:130-138,195-214, which retries DNS later. A temporary DNS issue now prevents the node from booting at all.

Possible fix
   private static void checkBackupMembers() {
     for (String member : PARAMETER.backupMembers) {
       InetAddress inetAddress = resolveInetAddress(member);
       if (inetAddress == null) {
-        throw new TronError("Failed to resolve backup member: " + member,
-            TronError.ErrCode.PARAMETER_INIT);
+        logger.warn("Failed to resolve backup member during startup, will retry later: {}",
+            member);
       }
     }
   }
📝 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
checkBackupMembers();
}
private static void checkBackupMembers() {
for (String member : PARAMETER.backupMembers) {
InetAddress inetAddress = resolveInetAddress(member);
if (inetAddress == null) {
throw new TronError("Failed to resolve backup member: " + member,
TronError.ErrCode.PARAMETER_INIT);
}
}
checkBackupMembers();
}
private static void checkBackupMembers() {
for (String member : PARAMETER.backupMembers) {
InetAddress inetAddress = resolveInetAddress(member);
if (inetAddress == null) {
logger.warn("Failed to resolve backup member during startup, will retry later: {}",
member);
}
}
🤖 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
1663 - 1673, The current checkBackupMembers() eagerly resolves every entry in
PARAMETER.backupMembers using resolveInetAddress and throws a TronError on any
unresolved host, causing startup to fail on transient DNS issues; change it to
mirror BackupManager behavior by skipping unresolved entries instead of
throwing: call resolveInetAddress(member), if it returns null log a warning (or
info) mentioning the member and continue, otherwise keep the resolved address
(or no-op), and remove the throw of TronError so startup proceeds and
BackupManager can retry DNS later.

}

public static void logConfig() {
Expand Down
115 changes: 115 additions & 0 deletions framework/src/main/java/org/tron/core/config/args/InetUtil.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
package org.tron.core.config.args;

import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import lombok.extern.slf4j.Slf4j;
import org.tron.common.es.ExecutorServiceManager;
import org.tron.p2p.dns.lookup.LookUpTxt;
import org.tron.p2p.utils.NetUtil;

@Slf4j(topic = "app")
public class InetUtil {

public static List<InetSocketAddress> getInetSocketAddressList(List<String> items) {
List<InetSocketAddress> ret = new ArrayList<>();
if (items.isEmpty()) {
return ret;
}
// Collect entries whose host part is a domain name (not an IP literal).
List<String> domainEntries = new ArrayList<>();
for (String item : items) {
String host = NetUtil.parseInetSocketAddress(item).getHostString();
if (!NetUtil.validIpV4(host) && !NetUtil.validIpV6(host)) {
Comment on lines +21 to +30
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Guard parseInetSocketAddress() failures instead of aborting the whole list.

Line 29 and Line 65 call NetUtil.parseInetSocketAddress(...) without handling IllegalArgumentException. framework/src/test/java/org/tron/core/net/NodeTest.java:28-51 shows that parser throws on malformed values, so one bad host:port now fails the entire config load instead of skipping just that entry like the rest of this method does for unresolved DNS results.

Possible fix
+  private static InetSocketAddress tryParseInetSocketAddress(String configString) {
+    try {
+      return NetUtil.parseInetSocketAddress(configString);
+    } catch (IllegalArgumentException e) {
+      logger.warn("Invalid address format, skip: {}", configString);
+      return null;
+    }
+  }
+
   public static List<InetSocketAddress> getInetSocketAddressList(List<String> items) {
     List<InetSocketAddress> ret = new ArrayList<>();
     if (items.isEmpty()) {
       return ret;
     }
@@
     List<String> domainEntries = new ArrayList<>();
     for (String item : items) {
-      String host = NetUtil.parseInetSocketAddress(item).getHostString();
+      InetSocketAddress parsed = tryParseInetSocketAddress(item);
+      if (parsed == null) {
+        continue;
+      }
+      String host = parsed.getHostString();
       if (!NetUtil.validIpV4(host) && !NetUtil.validIpV6(host)) {
         domainEntries.add(item);
       }
     }
@@
     for (String configString : items) {
       InetSocketAddress inetSocketAddress;
-      InetSocketAddress parsed = NetUtil.parseInetSocketAddress(configString);
+      InetSocketAddress parsed = tryParseInetSocketAddress(configString);
+      if (parsed == null) {
+        continue;
+      }
       if (NetUtil.validIpV4(parsed.getHostString()) || NetUtil.validIpV6(parsed.getHostString())) {
         inetSocketAddress = parsed;
       } else {
         inetSocketAddress = domainResolved.get(configString);
       }

Also applies to: 63-66

🤖 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
21 - 30, The code calls NetUtil.parseInetSocketAddress(...) inside
getInetSocketAddressList without guarding for IllegalArgumentException so a
single malformed "host:port" aborts the whole list; wrap each call to
NetUtil.parseInetSocketAddress (both where host is read into host variable and
the later call around line 65) in a try/catch for IllegalArgumentException, skip
the offending entry (do not add to ret or domainEntries), and emit a warning log
or comment; ensure you continue processing remaining items so
getInetSocketAddressList returns only the valid InetSocketAddress entries.

domainEntries.add(item);
}
}

// Resolve domain names: spin up a thread pool only when there are multiple domains
Map<String, InetSocketAddress> domainResolved = new HashMap<>();
if (domainEntries.size() > 1) {
String poolName = "args-dns-lookup";
ExecutorService dnsPool = ExecutorServiceManager
.newFixedThreadPool(poolName, domainEntries.size(), 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 {
domainResolved.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, poolName);
} else if (domainEntries.size() == 1) {
String entry = domainEntries.get(0);
domainResolved.put(entry, resolveInetSocketAddress(entry));
}

// Build the result list preserving the original config order.
for (String configString : items) {
InetSocketAddress inetSocketAddress;
InetSocketAddress parsed = NetUtil.parseInetSocketAddress(configString);
if (NetUtil.validIpV4(parsed.getHostString()) || NetUtil.validIpV6(parsed.getHostString())) {
inetSocketAddress = parsed;
} else {
inetSocketAddress = domainResolved.get(configString);
}
if (inetSocketAddress == null) {
continue;
}
ret.add(inetSocketAddress);
}
return ret;
}

/**
* Resolves a {@code domain:port} address string to an {@link InetSocketAddress} via DNS.
*/
private static InetSocketAddress resolveInetSocketAddress(String configString) {
InetSocketAddress parsed = NetUtil.parseInetSocketAddress(configString);
String host = parsed.getHostString();
int port = parsed.getPort();
InetAddress address = LookUpTxt.lookUpIp(host, true);
if (address == null) {
address = LookUpTxt.lookUpIp(host, false);
}
if (address == null) {
return null;
}
logger.info("Resolve {} to {}", host, address.getHostAddress());
return new InetSocketAddress(address, port);
}

/**
* Resolves a hostname or IP string to a numeric IP address string.
*/
public static InetAddress resolveInetAddress(String ipOrDomain) {
// Fast path: already a numeric address — no lookup needed.
if (NetUtil.validIpV4(ipOrDomain) || NetUtil.validIpV6(ipOrDomain)) {
try {
return InetAddress.getByName(ipOrDomain);
} catch (UnknownHostException e) {
return null;
}
}
InetAddress address = LookUpTxt.lookUpIp(ipOrDomain, true);
if (address == null) {
address = LookUpTxt.lookUpIp(ipOrDomain, false);
}
return address;
}
}
8 changes: 8 additions & 0 deletions gradle/verification-metadata.xml
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,14 @@
<sha256 value="e58f48ac14e4dbd48595d69e20bad35019ec1281514ef7ef155e18072ada617f" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="com.github.317787106" name="libp2p" version="v0.0.9">
<artifact name="libp2p-v0.0.9.jar">
<sha256 value="2a955c3e133590e8ccc5411e7fbda358088f5829b13bd9052c39824b2258aac6" origin="Generated by Gradle"/>
</artifact>
<artifact name="libp2p-v0.0.9.module">
<sha256 value="0434f4772b9e5df2a7bdd607091aa985a096c60eb889e75193fe1162e4e1ecd8" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="com.github.briandilley.jsonrpc4j" name="jsonrpc4j" version="1.6">
<artifact name="jsonrpc4j-1.6.jar">
<sha256 value="700620939640e1ba55a39713656a1c7df554e7a4e679c6b349c37734ebd04182" origin="Generated by Gradle"/>
Expand Down
Loading