Skip to content
Merged
Show file tree
Hide file tree
Changes from 13 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',{

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Is this release-v2.2.8-SNAPSHOT a correct usage?
Why change from io.github.tronprotocol to com.github.tronprotocol

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

It's a gitpack temporary dependency now. After the libp2p v2.2.8 releases, it will be updated as

api group: 'io.github.tronprotocol', name: 'libp2p', version: '2.2.8'

//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
90 changes: 71 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,45 @@
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 ScheduledExecutorService dnsExecutorService;

@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 +82,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;
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[SHOULD] Currently, the master and backup servers mainly use external IP addresses, so we can also add external IP address filtering logic.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Maybe, we can run master and backup in the same node. So, there maybe no need to ignore external IP.

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 +125,17 @@ public void init() {
logger.error("Exception in send keep alive", t);
}
}, 1000, keepAliveInterval, TimeUnit.MILLISECONDS);

if (!domainIpCache.isEmpty()) {
dnsExecutorService = ExecutorServiceManager.newSingleThreadScheduledExecutor(dnsEsName);
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,9 @@ public void handleEvent(UdpEvent udpEvent) {

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

@Override
Expand All @@ -162,4 +190,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();
Comment thread
317787106 marked this conversation as resolved.
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
171 changes: 171 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,171 @@
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.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.function.BiFunction;
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;
// Per-lookup wall-clock budget. After this, the entry is treated as unresolvable.
private static final long DNS_LOOKUP_TIMEOUT_SECONDS = 10;

// Overridable in tests so worker threads (parallel path) can use a non-network lookup.
// Reset to LookUpTxt::lookUpIp after each test that overrides it.
public static volatile BiFunction<String, Boolean, InetAddress> dnsLookup =
LookUpTxt::lookUpIp;

/**
* 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(
Comment thread
Sunny6889 marked this conversation as resolved.
List<String> ipOrDomainWithPortList) {
List<InetSocketAddress> result = new ArrayList<>();
if (ipOrDomainWithPortList.isEmpty()) {
return result;
}

// Single pass: parse every entry once; collect domain entries for DNS resolution.
LinkedHashMap<String, InetSocketAddress> parsedMap = new LinkedHashMap<>();
List<String> domainEntries = new ArrayList<>();
for (String item : ipOrDomainWithPortList) {
InetSocketAddress parsed = NetUtil.parseInetSocketAddress(item);
parsedMap.put(item, parsed);
if (!isIpLiteral(parsed.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) {
Comment thread
317787106 marked this conversation as resolved.
Outdated
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(DNS_LOOKUP_TIMEOUT_SECONDS, TimeUnit.SECONDS));
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
logger.warn("DNS lookup interrupted for: {}", entry);
break;
} catch (ExecutionException e) {
logger.warn("Failed to resolve address, skip: {}", entry);
} catch (TimeoutException e) {
logger.warn("DNS lookup timed out after {}s, skip: {}", DNS_LOOKUP_TIMEOUT_SECONDS,
entry);
}
}
ExecutorServiceManager.shutdownAndAwaitTermination(dnsPool, DNS_POOL_NAME);
} else if (domainEntries.size() == 1) {
String entry = domainEntries.get(0);
resolvedDomains.put(entry, resolveInetSocketAddress(entry));
Comment thread
317787106 marked this conversation as resolved.
Outdated
}

// Build the result list preserving the original config order.
for (Map.Entry<String, InetSocketAddress> entry : parsedMap.entrySet()) {
String item = entry.getKey();
InetSocketAddress parsed = entry.getValue();
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) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Could you rename resolveInetSocketAddress to resolveInetAddressWithPort, as it will be more clear?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Class InetSocketAddress already contains InetAddress and Port.

InetSocketAddress parsed = NetUtil.parseInetSocketAddress(ipOrDomainWithPort);
String host = parsed.getHostString();
int port = parsed.getPort();
InetAddress address = dnsLookup.apply(host, true);
if (address == null) {
address = dnsLookup.apply(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 = dnsLookup.apply(ipOrDomain, true);
if (address == null) {
address = dnsLookup.apply(ipOrDomain, false);
}
return address;
}

private static boolean isIpLiteral(String host) {
return NetUtil.validIpV4(host) || NetUtil.validIpV6(host);
}
}
Loading
Loading