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.tronprotocol', name: 'libp2p', version: 'release-v2.2.8-SNAPSHOT',{
//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);
}
}
}
}
16 changes: 13 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 @@ -275,6 +275,7 @@ private static void applyNodeBackupConfig(NodeConfig nc) {
PARAMETER.backupPort = b.getPort();
PARAMETER.keepAliveInterval = b.getKeepAliveInterval();
PARAMETER.backupMembers = b.getMembers();
checkBackupMembers();
}

/**
Expand Down Expand Up @@ -1009,8 +1010,7 @@ public static void clearParam() {
public static List<InetSocketAddress> filterInetSocketAddress(
List<String> addressList, boolean filter) {
List<InetSocketAddress> ret = new ArrayList<>();
for (String configString : addressList) {
InetSocketAddress inetSocketAddress = NetUtil.parseInetSocketAddress(configString);
for (InetSocketAddress inetSocketAddress : InetUtil.resolveInetSocketAddressList(addressList)) {
if (filter) {
String ip = inetSocketAddress.getAddress().getHostAddress();
int port = inetSocketAddress.getPort();
Expand Down Expand Up @@ -1158,6 +1158,16 @@ private static void externalIp(NodeConfig nodeConfig) {
// initRocksDbSettings, initRocksDbBackupProperty, initBackupProperty
// removed — logic moved to applyStorageConfig() and applyNodeBackupConfig()

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() {
CommonParameter parameter = CommonParameter.getInstance();
logger.info("\n");
Expand Down
151 changes: 151 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,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.
*
* <p>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.
*
* <p>Supported formats:
* <ul>
* <li>{@code 192.168.100.0:18888}
* <li>{@code [fe80::48ff:fe00:1122]:18888}
* <li>{@code example.com:18888}
* <li>{@code hostname:18888}
* </ul>
*
* @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<InetSocketAddress> resolveInetSocketAddressList(
List<String> ipOrDomainWithPortList) {
List<InetSocketAddress> result = new ArrayList<>();
if (ipOrDomainWithPortList.isEmpty()) {
return result;
}

// Collect entries whose host part is a domain name (not an IP literal).
List<String> 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<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));
}

// 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.
*
* <p>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}.
*
* <p>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);
}
}
Loading
Loading