diff --git a/common/build.gradle b/common/build.gradle index 98fc3257190..afc8652e6c5 100644 --- a/common/build.gradle +++ b/common/build.gradle @@ -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.10.0',{ + //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' diff --git a/framework/src/main/java/org/tron/common/backup/BackupManager.java b/framework/src/main/java/org/tron/common/backup/BackupManager.java index a8812a62bb4..c70d6df4d63 100644 --- a/framework/src/main/java/org/tron/common/backup/BackupManager.java +++ b/framework/src/main/java/org/tron/common/backup/BackupManager.java @@ -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; @@ -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 members = new ConcurrentSet<>(); + private final Set members = new ConcurrentSet<>(); - private final String esName = "backup-manager"; + private final Map 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; @@ -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); @@ -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 @@ -149,6 +174,7 @@ public void handleEvent(UdpEvent udpEvent) { public void stop() { ExecutorServiceManager.shutdownAndAwaitTermination(executorService, esName); + ExecutorServiceManager.shutdownAndAwaitTermination(dnsExecutorService, dnsEsName); } @Override @@ -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 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); + } + } + } } 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 83d7fd2c63d..43a3bfd141a 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 @@ -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; @@ -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; @@ -1301,15 +1301,12 @@ private static RateLimiterInitialization getRateLimiterFromConfig( return initialization; } + public static List getInetSocketAddress( final com.typesafe.config.Config config, String path, boolean filter) { List ret = new ArrayList<>(); - if (!config.hasPath(path)) { - return ret; - } - List list = config.getStringList(path); - for (String configString : list) { - InetSocketAddress inetSocketAddress = NetUtil.parseInetSocketAddress(configString); + List socketAddressList = getInetSockerAddress(config, path); + for (InetSocketAddress inetSocketAddress : socketAddressList) { if (filter) { String ip = inetSocketAddress.getAddress().getHostAddress(); int port = inetSocketAddress.getPort(); @@ -1326,15 +1323,27 @@ public static List getInetSocketAddress( return ret; } - public static List getInetAddress( + private static List getInetSockerAddress( final com.typesafe.config.Config config, String path) { - List ret = new ArrayList<>(); + List socketAddressList = new ArrayList<>(); if (!config.hasPath(path)) { - return ret; + return socketAddressList; } List list = config.getStringList(path); - for (String configString : list) { - InetSocketAddress inetSocketAddress = NetUtil.parseInetSocketAddress(configString); + try { + socketAddressList = InetUtil.resolveInetSocketAddressList(list); + } catch (RuntimeException e) { + throw new TronError(String.format("config %s contains %s", path, e.getMessage()), + TronError.ErrCode.PARAMETER_INIT); + } + return socketAddressList; + } + + public static List getInetAddress( + final com.typesafe.config.Config config, String path) { + List ret = new ArrayList<>(); + List socketAddressList = getInetSockerAddress(config, path); + for (InetSocketAddress inetSocketAddress : socketAddressList) { ret.add(inetSocketAddress.getAddress()); } return ret; @@ -1660,6 +1669,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); + } + } } public static void logConfig() { diff --git a/framework/src/main/java/org/tron/core/config/args/InetUtil.java b/framework/src/main/java/org/tron/core/config/args/InetUtil.java new file mode 100644 index 00000000000..3d5e44f9588 --- /dev/null +++ b/framework/src/main/java/org/tron/core/config/args/InetUtil.java @@ -0,0 +1,151 @@ +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 { + + private static final String DNS_POOL_NAME = "args-dns-lookup"; + private static final int DNS_POOL_MAX_SIZE = 10; + + /** + * Converts a list of {@code ipOrDomain:port} config strings into resolved {@link + * InetSocketAddress} objects, preserving the original order. + * + *

IP literals (IPv4 and IPv6) are used as-is. Domain names are resolved via DNS: when there + * are multiple domains, they are resolved in parallel using a dedicated thread pool; a single + * domain is resolved inline. Entries that fail DNS resolution are silently dropped. + * + *

Supported formats: + *

    + *
  • {@code 192.168.100.0:18888} + *
  • {@code [fe80::48ff:fe00:1122]:18888} + *
  • {@code example.com:18888} + *
  • {@code hostname:18888} + *
+ * + * @param ipOrDomainWithPortList list of address strings in {@code ipOrDomain:port} format, + * may mix IP literals and domain names + * @return resolved addresses in the same order as the input, omitting unresolvable entries + */ + public static List resolveInetSocketAddressList( + List ipOrDomainWithPortList) { + List result = new ArrayList<>(); + if (ipOrDomainWithPortList.isEmpty()) { + return result; + } + + // Collect entries whose host part is a domain name (not an IP literal). + List domainEntries = new ArrayList<>(); + for (String item : ipOrDomainWithPortList) { + if (!isIpLiteral(NetUtil.parseInetSocketAddress(item).getHostString())) { + domainEntries.add(item); + } + } + + // Resolve domain names: spin up a thread pool only when there are multiple domains. + Map 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> 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)); + } + + // Build the result list preserving the original config order. + for (String item : ipOrDomainWithPortList) { + InetSocketAddress parsed = NetUtil.parseInetSocketAddress(item); + InetSocketAddress resolved = isIpLiteral(parsed.getHostString()) + ? parsed + : resolvedDomains.get(item); + if (resolved != null) { + result.add(resolved); + } + } + return result; + } + + /** + * Resolves a {@code ipOrDomain:port} config string to an {@link InetSocketAddress} via DNS. + * + *

The host is looked up first over IPv4, then over IPv6 as a fallback. Returns {@code null} + * if DNS resolution fails for both address families. + * + * @param ipOrDomainWithPort address string in {@code ipOrDomain:port} format + * @return resolved {@link InetSocketAddress}, or {@code null} if the host cannot be resolved + */ + private static InetSocketAddress resolveInetSocketAddress(String ipOrDomainWithPort) { + InetSocketAddress parsed = NetUtil.parseInetSocketAddress(ipOrDomainWithPort); + 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 {@code ipOrDomain} to an {@link InetAddress}. + * + *

IP literals are converted directly without a DNS lookup. Domain names are first resolved + * over IPv4, then retried over IPv6 if the first attempt fails. + * + * @param ipOrDomain IPv4/IPv6 literal or a domain name to resolve + * @return the resolved {@link InetAddress}, or {@code null} if resolution fails + */ + public static InetAddress resolveInetAddress(String ipOrDomain) { + // Fast path: already a numeric address — no lookup needed. + if (isIpLiteral(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; + } + + private static boolean isIpLiteral(String host) { + return NetUtil.validIpV4(host) || NetUtil.validIpV6(host); + } +} diff --git a/gradle/verification-metadata.xml b/gradle/verification-metadata.xml index 4d0bf1013d6..81f5997c9ec 100644 --- a/gradle/verification-metadata.xml +++ b/gradle/verification-metadata.xml @@ -247,6 +247,14 @@ + + + + + + + +