diff --git a/common/build.gradle b/common/build.gradle index acde43a1ea9..4309d3dc69a 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.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' diff --git a/common/src/main/java/org/tron/common/config/DbBackupConfig.java b/common/src/main/java/org/tron/common/config/DbBackupConfig.java deleted file mode 100644 index 694ae7c3155..00000000000 --- a/common/src/main/java/org/tron/common/config/DbBackupConfig.java +++ /dev/null @@ -1,85 +0,0 @@ -package org.tron.common.config; - -import java.io.File; -import lombok.Getter; -import lombok.Setter; -import lombok.extern.slf4j.Slf4j; -import org.tron.common.utils.FileUtil; - -@Slf4j -public class DbBackupConfig { - - private static volatile DbBackupConfig instance; - @Getter - @Setter - private String propPath; - @Getter - @Setter - private String bak1path; - @Getter - @Setter - private String bak2path; - @Setter - @Getter - private int frequency; - @Getter - @Setter - private boolean enable = false; - - // singleton - public static DbBackupConfig getInstance() { - if (instance == null) { - synchronized (DbBackupConfig.class) { - if (instance == null) { - instance = new DbBackupConfig(); - } - } - } - return instance; - } - - public DbBackupConfig initArgs(boolean enable, String propPath, String bak1path, String bak2path, - int frequency) { - setEnable(enable); - if (isEnable()) { - if (!bak1path.endsWith(File.separator)) { - bak1path = bak1path + File.separator; - } - - if (!bak2path.endsWith(File.separator)) { - bak2path = bak2path + File.separator; - } - - if (!FileUtil.createFileIfNotExists(propPath)) { - throw new RuntimeException("failure to create file:" + propPath); - } - - if (!FileUtil.createDirIfNotExists(bak1path)) { - throw new RuntimeException("failure to mkdir: " + bak1path); - } - - if (!FileUtil.createDirIfNotExists(bak2path)) { - throw new RuntimeException("failure to mkdir: " + bak2path); - } - - if (bak1path.equals(bak2path)) { - throw new RuntimeException("bak1path and bak2path must be different."); - } - - if (frequency <= 0) { - throw new IllegalArgumentException("frequency must be positive number."); - } - - setPropPath(propPath); - setBak1path(bak1path); - setBak2path(bak2path); - setFrequency(frequency); - logger.info( - "success to enable the db backup plugin. bak1path:{}, bak2path:{}, " - + "backup once every {} blocks handled", - bak1path, bak2path, frequency); - } - - return this; - } -} \ No newline at end of file diff --git a/common/src/main/java/org/tron/common/parameter/CommonParameter.java b/common/src/main/java/org/tron/common/parameter/CommonParameter.java index 39278872f5e..eaf56bf48e8 100644 --- a/common/src/main/java/org/tron/common/parameter/CommonParameter.java +++ b/common/src/main/java/org/tron/common/parameter/CommonParameter.java @@ -9,7 +9,6 @@ import lombok.Setter; import org.slf4j.bridge.SLF4JBridgeHandler; import org.tron.common.args.GenesisBlock; -import org.tron.common.config.DbBackupConfig; import org.tron.common.cron.CronExpression; import org.tron.common.logsfilter.EventPluginConfig; import org.tron.common.logsfilter.FilterQuery; @@ -425,8 +424,6 @@ public class CommonParameter { @Setter public double rateLimiterDisconnect; // clearParam: 1.0 @Getter - public DbBackupConfig dbBackupConfig; - @Getter public RocksDbSettings rocksDBCustomSettings; @Getter public GenesisBlock genesisBlock; diff --git a/common/src/main/java/org/tron/core/config/args/StorageConfig.java b/common/src/main/java/org/tron/core/config/args/StorageConfig.java index 2517f4d10d7..3d7046ebae2 100644 --- a/common/src/main/java/org/tron/core/config/args/StorageConfig.java +++ b/common/src/main/java/org/tron/core/config/args/StorageConfig.java @@ -25,7 +25,6 @@ public class StorageConfig { private TransHistoryConfig transHistory = new TransHistoryConfig(); private boolean needToUpdateAsset = true; private DbSettingsConfig dbSettings = new DbSettingsConfig(); - private BackupConfig backup = new BackupConfig(); private BalanceConfig balance = new BalanceConfig(); private CheckpointConfig checkpoint = new CheckpointConfig(); private SnapshotConfig snapshot = new SnapshotConfig(); @@ -129,16 +128,6 @@ void postProcess() { } } - @Getter - @Setter - public static class BackupConfig { - private boolean enable = false; - private String propPath = "prop.properties"; - private String bak1path = "bak1/database/"; - private String bak2path = "bak2/database/"; - private int frequency = 10000; - } - @Getter @Setter public static class BalanceConfig { diff --git a/common/src/main/resources/reference.conf b/common/src/main/resources/reference.conf index 67343b9b75a..7f88719d7f8 100644 --- a/common/src/main/resources/reference.conf +++ b/common/src/main/resources/reference.conf @@ -120,15 +120,6 @@ storage { # Number of blocks flushed to db in each batch during node syncing. snapshot.maxFlushCount = 1 - # Database backup settings (RocksDB only) - backup = { - enable = false - propPath = "prop.properties" - bak1path = "bak1/database/" - bak2path = "bak2/database/" - frequency = 10000 - } - # Data root setting, for check data, currently only reward-vi is used. # merkleRoot = { # reward-vi = 9debcb9924055500aaae98cdee10501c5c39d4daa75800a996f4bdda73dbccd8 // main-net diff --git a/common/src/test/java/org/tron/core/config/args/StorageConfigTest.java b/common/src/test/java/org/tron/core/config/args/StorageConfigTest.java index 5a679be89e5..ecb956e406a 100644 --- a/common/src/test/java/org/tron/core/config/args/StorageConfigTest.java +++ b/common/src/test/java/org/tron/core/config/args/StorageConfigTest.java @@ -28,8 +28,6 @@ public void testDefaults() { assertEquals("database", sc.getDb().getDirectory()); assertEquals("index", sc.getIndex().getDirectory()); assertTrue(sc.isNeedToUpdateAsset()); - assertFalse(sc.getBackup().isEnable()); - assertEquals(10000, sc.getBackup().getFrequency()); assertEquals(7, sc.getDbSettings().getLevelNumber()); assertEquals(5000, sc.getDbSettings().getMaxOpenFiles()); } @@ -44,8 +42,6 @@ public void testFromConfig() { assertEquals("ROCKSDB", sc.getDb().getEngine()); assertTrue(sc.getDb().isSync()); assertEquals("mydb", sc.getDb().getDirectory()); - assertTrue(sc.getBackup().isEnable()); - assertEquals(5000, sc.getBackup().getFrequency()); assertEquals(5, sc.getDbSettings().getLevelNumber()); assertEquals(3000, sc.getDbSettings().getMaxOpenFiles()); } diff --git a/framework/Daily_Build_Report b/framework/Daily_Build_Report deleted file mode 100644 index 49e63dbdc5f..00000000000 --- a/framework/Daily_Build_Report +++ /dev/null @@ -1 +0,0 @@ -3.Stest report: \ No newline at end of file diff --git a/framework/build.gradle b/framework/build.gradle index 7b3e6ddb968..fd59d3cc4e7 100644 --- a/framework/build.gradle +++ b/framework/build.gradle @@ -114,7 +114,6 @@ def configureTestTask = { Task t -> } if (isWindows()) { t.exclude '**/ShieldedTransferActuatorTest.class' - t.exclude '**/BackupDbUtilTest.class' t.exclude '**/ManagerTest.class' t.exclude 'org/tron/core/zksnark/**' t.exclude 'org/tron/common/runtime/vm/PrecompiledContractsVerifyProofTest.class' @@ -161,7 +160,6 @@ tasks.register('testWithRocksDb', Test) { include 'org/tron/core/config/args/ArgsTest.class' include 'org/tron/core/db/DBIteratorTest.class' include 'org/tron/core/db/TronDatabaseTest.class' - include 'org/tron/core/db/backup/BackupDbUtilTest.class' include 'org/tron/core/db2/ChainbaseTest.class' exclude '**/LevelDbDataSourceImplTest.class' } diff --git a/framework/prop.properties b/framework/prop.properties deleted file mode 100644 index e69de29bb2d..00000000000 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..a870c183a8d 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,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 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 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; @@ -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; + } + 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 +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 @@ -149,6 +174,9 @@ public void handleEvent(UdpEvent udpEvent) { public void stop() { ExecutorServiceManager.shutdownAndAwaitTermination(executorService, esName); + if (dnsExecutorService != null) { + ExecutorServiceManager.shutdownAndAwaitTermination(dnsExecutorService, dnsEsName); + } } @Override @@ -162,4 +190,26 @@ 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); + members.add(newIp); + domainIpCache.put(domain, newIp); + } + } + } } diff --git a/framework/src/main/java/org/tron/core/config/DefaultConfig.java b/framework/src/main/java/org/tron/core/config/DefaultConfig.java index 9bce903d411..06d93682342 100755 --- a/framework/src/main/java/org/tron/core/config/DefaultConfig.java +++ b/framework/src/main/java/org/tron/core/config/DefaultConfig.java @@ -5,14 +5,11 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.tron.common.utils.StorageUtils; import org.tron.core.config.args.Args; import org.tron.core.db.RevokingDatabase; -import org.tron.core.db.backup.BackupRocksDBAspect; -import org.tron.core.db.backup.NeedBeanCondition; import org.tron.core.db2.core.SnapshotManager; import org.tron.core.services.interfaceOnPBFT.RpcApiServiceOnPBFT; import org.tron.core.services.interfaceOnPBFT.http.PBFT.HttpApiOnPBFTService; @@ -88,9 +85,4 @@ public HttpApiOnPBFTService getHttpApiOnPBFTService() { return null; } - @Bean - @Conditional(NeedBeanCondition.class) - public BackupRocksDBAspect backupRocksDBAspect() { - return new BackupRocksDBAspect(); - } } 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 f84a3d1646c..abd625a462a 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 @@ -4,6 +4,8 @@ import static org.tron.common.math.Maths.max; import static org.tron.core.Constant.ADD_PRE_FIX_BYTE_MAINNET; import static org.tron.core.Constant.ENERGY_LIMIT_IN_CONSTANT_TX; +import static org.tron.core.config.args.InetUtil.resolveInetAddress; +import static org.tron.core.config.args.InetUtil.resolveInetSocketAddressList; import com.beust.jcommander.JCommander; import com.beust.jcommander.ParameterDescription; @@ -13,6 +15,7 @@ import java.io.File; import java.io.IOException; import java.io.InputStream; +import java.net.InetAddress; import java.net.InetSocketAddress; import java.text.ParseException; import java.util.ArrayList; @@ -39,7 +42,6 @@ import org.tron.common.args.Account; import org.tron.common.args.GenesisBlock; import org.tron.common.args.Witness; -import org.tron.common.config.DbBackupConfig; import org.tron.common.cron.CronExpression; import org.tron.common.logsfilter.EventPluginConfig; import org.tron.common.logsfilter.FilterQuery; @@ -225,12 +227,6 @@ private static void applyStorageConfig(StorageConfig sc) { PARAMETER.storage.setTxCacheInitOptimization(sc.getTxCache().isInitOptimization()); PARAMETER.storage.setMaxFlushCount(sc.getSnapshot().getMaxFlushCount()); - // backup - StorageConfig.BackupConfig backup = sc.getBackup(); - PARAMETER.dbBackupConfig = DbBackupConfig.getInstance() - .initArgs(backup.isEnable(), backup.getPropPath(), - backup.getBak1path(), backup.getBak2path(), backup.getFrequency()); - // RocksDB settings StorageConfig.DbSettingsConfig dbs = sc.getDbSettings(); PARAMETER.rocksDBCustomSettings = RocksDbSettings @@ -259,6 +255,7 @@ private static void applyNodeBackupConfig(NodeConfig nc) { PARAMETER.backupPort = b.getPort(); PARAMETER.keepAliveInterval = b.getKeepAliveInterval(); PARAMETER.backupMembers = b.getMembers(); + checkBackupMembers(); } /** @@ -313,10 +310,7 @@ private static void applyMiscConfig(MiscConfig mc) { // seed.node — top-level config section, not under "node" // Config structure is arguably misplaced but preserved for backward compatibility PARAMETER.seedNode = new SeedNode(); - PARAMETER.seedNode.setAddressList( - mc.getSeedNodeIpList().stream() - .map(s -> org.tron.p2p.utils.NetUtil.parseInetSocketAddress(s)) - .collect(Collectors.toList())); + PARAMETER.seedNode.setAddressList(resolveInetSocketAddressList(mc.getSeedNodeIpList())); } /** @@ -915,10 +909,7 @@ private static void applyCLIParams(CLIParameter cmd, JCommander jc) { if (!cmd.seedNodes.isEmpty()) { logger.warn("Positional seed-node arguments are deprecated. " + "Please use seed.node.ip.list in the config file instead."); - List seeds = new ArrayList<>(); - for (String s : cmd.seedNodes) { - seeds.add(NetUtil.parseInetSocketAddress(s)); - } + List seeds = resolveInetSocketAddressList(cmd.seedNodes); PARAMETER.seedNode.setAddressList(seeds); } } @@ -991,8 +982,7 @@ public static void clearParam() { public static List filterInetSocketAddress( List addressList, boolean filter) { List ret = new ArrayList<>(); - for (String configString : addressList) { - InetSocketAddress inetSocketAddress = NetUtil.parseInetSocketAddress(configString); + for (InetSocketAddress inetSocketAddress : resolveInetSocketAddressList(addressList)) { if (filter) { String ip = inetSocketAddress.getAddress().getHostAddress(); int port = inetSocketAddress.getPort(); @@ -1140,6 +1130,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"); 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..cdde93c73ed --- /dev/null +++ b/framework/src/main/java/org/tron/core/config/args/InetUtil.java @@ -0,0 +1,194 @@ +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.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +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 dnsLookup = + LookUpTxt::lookUpIp; + + /** + * 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; + } + + // Single pass: parse every entry once; collect domain entries for DNS resolution. + LinkedHashMap parsedMap = new LinkedHashMap<>(); + List 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 resolvedDomains = resolveDomainsInParallel(domainEntries); + + // Build the result list preserving the original config order. + for (Map.Entry 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; + } + + private static Map resolveDomainsInParallel( + List domainEntries) { + Map resolved = new HashMap<>(); + if (domainEntries.isEmpty()) { + return resolved; + } + + int poolSize = StrictMath.min(domainEntries.size(), DNS_POOL_MAX_SIZE); + ExecutorService dnsPool = ExecutorServiceManager + .newFixedThreadPool(DNS_POOL_NAME, poolSize, true); + + try { + LinkedHashMap> futures = + new LinkedHashMap<>(); + for (String entry : domainEntries) { + futures.put(entry, CompletableFuture.supplyAsync( + () -> resolveInetSocketAddress(entry), dnsPool)); + } + + // Single global deadline for all lookups combined. + try { + CompletableFuture + .allOf(futures.values().toArray(new CompletableFuture[0])) + .get(DNS_LOOKUP_TIMEOUT_SECONDS, TimeUnit.SECONDS); + } catch (TimeoutException e) { + logger.warn("DNS lookup budget {}s exceeded, dropping unresolved entries", + DNS_LOOKUP_TIMEOUT_SECONDS); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } catch (ExecutionException ignored) { + // per-entry exceptions handled below + } + + // Collect whatever finished; drop pending/failed entries. + for (Map.Entry> e : futures.entrySet()) { + CompletableFuture f = e.getValue(); + if (f.isDone() && !f.isCompletedExceptionally()) { + InetSocketAddress addr = f.getNow(null); + if (addr != null) { + resolved.put(e.getKey(), addr); + } + } else { + logger.warn("DNS unresolved or timed out, skip: {}", e.getKey()); + } + } + } finally { + ExecutorServiceManager.shutdownAndAwaitTermination(dnsPool, DNS_POOL_NAME); + } + logger.debug("DNS look up, src: {}, dst: {}", domainEntries.size(), resolved.size()); + return resolved; + } + + /** + * 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 = 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}. + * + *

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); + } +} diff --git a/framework/src/main/java/org/tron/core/db/backup/BackupDbUtil.java b/framework/src/main/java/org/tron/core/db/backup/BackupDbUtil.java deleted file mode 100644 index ecaeb19d004..00000000000 --- a/framework/src/main/java/org/tron/core/db/backup/BackupDbUtil.java +++ /dev/null @@ -1,176 +0,0 @@ -package org.tron.core.db.backup; - -import java.util.List; -import lombok.Getter; -import lombok.extern.slf4j.Slf4j; -import org.rocksdb.RocksDBException; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Component; -import org.tron.common.parameter.CommonParameter; -import org.tron.common.utils.PropUtil; -import org.tron.core.capsule.BlockCapsule; -import org.tron.core.config.args.Args; -import org.tron.core.db.RevokingDatabase; -import org.tron.core.db2.core.Chainbase; -import org.tron.core.db2.core.SnapshotManager; -import org.tron.core.db2.core.SnapshotRoot; - -@Slf4j(topic = "DB") -@Component -public class BackupDbUtil { - - @Getter - private static final String DB_BACKUP_STATE = "DB"; - private static final int DB_BACKUP_INDEX1 = 1; - private static final int DB_BACKUP_INDEX2 = 2; - - @Getter - private static final int DB_BACKUP_STATE_DEFAULT = 11; - @Getter - @Autowired - private RevokingDatabase db; - private CommonParameter parameter = Args.getInstance(); - - private int getBackupState() { - try { - return Integer.valueOf(PropUtil - .readProperty(parameter.getDbBackupConfig().getPropPath(), BackupDbUtil.DB_BACKUP_STATE) - ); - } catch (NumberFormatException ignore) { - return DB_BACKUP_STATE_DEFAULT; //get default state if prop file is newly created - } - } - - private void setBackupState(int status) { - PropUtil.writeProperty(parameter.getDbBackupConfig() - .getPropPath(), BackupDbUtil.DB_BACKUP_STATE, - String.valueOf(status)); - } - - private void switchBackupState() { - switch (State.valueOf(getBackupState())) { - case BAKINGONE: - setBackupState(State.BAKEDONE.getStatus()); - break; - case BAKEDONE: - setBackupState(State.BAKEDTWO.getStatus()); - break; - case BAKINGTWO: - setBackupState(State.BAKEDTWO.getStatus()); - break; - case BAKEDTWO: - setBackupState(State.BAKEDONE.getStatus()); - break; - default: - break; - } - } - - public void doBackup(BlockCapsule block) { - long t1 = System.currentTimeMillis(); - try { - switch (State.valueOf(getBackupState())) { - case BAKINGONE: - deleteBackup(DB_BACKUP_INDEX1); - backup(DB_BACKUP_INDEX1); - switchBackupState(); - deleteBackup(DB_BACKUP_INDEX2); - break; - case BAKEDONE: - deleteBackup(DB_BACKUP_INDEX2); - backup(DB_BACKUP_INDEX2); - switchBackupState(); - deleteBackup(DB_BACKUP_INDEX1); - break; - case BAKINGTWO: - deleteBackup(DB_BACKUP_INDEX2); - backup(DB_BACKUP_INDEX2); - switchBackupState(); - deleteBackup(DB_BACKUP_INDEX1); - break; - case BAKEDTWO: - deleteBackup(DB_BACKUP_INDEX1); - backup(DB_BACKUP_INDEX1); - switchBackupState(); - deleteBackup(DB_BACKUP_INDEX2); - break; - default: - logger.warn("invalid backup state {}.", getBackupState()); - } - } catch (RocksDBException | SecurityException e) { - logger.warn("Backup db error.", e); - } - long timeUsed = System.currentTimeMillis() - t1; - logger - .info("Current block number is {}, backup all store use {} ms!", block.getNum(), timeUsed); - if (timeUsed >= 3000) { - logger.warn("Backing up db uses too much time. {} ms.", timeUsed); - } - } - - private void backup(int i) throws RocksDBException { - String path = ""; - if (i == DB_BACKUP_INDEX1) { - path = parameter.getDbBackupConfig().getBak1path(); - } else if (i == DB_BACKUP_INDEX2) { - path = parameter.getDbBackupConfig().getBak2path(); - } else { - throw new RuntimeException(String.format("error backup with undefined index %d", i)); - } - List stores = ((SnapshotManager) db).getDbs(); - for (Chainbase store : stores) { - if (((SnapshotRoot) (store.getHead().getRoot())).getDb().getClass() - == org.tron.core.db2.common.RocksDB.class) { - ((org.tron.core.db2.common.RocksDB) ((SnapshotRoot) (store.getHead().getRoot())).getDb()) - .getDb().backup(path); - } - } - } - - private void deleteBackup(int i) { - String path = ""; - if (i == DB_BACKUP_INDEX1) { - path = parameter.getDbBackupConfig().getBak1path(); - } else if (i == DB_BACKUP_INDEX2) { - path = parameter.getDbBackupConfig().getBak2path(); - } else { - throw new RuntimeException(String.format("error deleteBackup with undefined index %d", i)); - } - List stores = ((SnapshotManager) db).getDbs(); - for (Chainbase store : stores) { - if (((SnapshotRoot) (store.getHead().getRoot())).getDb().getClass() - == org.tron.core.db2.common.RocksDB.class) { - ((org.tron.core.db2.common.RocksDB) (((SnapshotRoot) (store.getHead().getRoot())).getDb())) - .getDb().deleteDbBakPath(path); - } - } - } - - public enum State { - BAKINGONE(1), BAKEDONE(11), BAKINGTWO(2), BAKEDTWO(22); - private int status; - - State(int status) { - this.status = status; - } - - public static State valueOf(int value) { - switch (value) { - case 1: - return BAKINGONE; - case 11: - return BAKEDONE; - case 2: - return BAKINGTWO; - case 22: - return BAKEDTWO; - default: - return BAKEDONE; - } - } - - public int getStatus() { - return status; - } - } -} diff --git a/framework/src/main/java/org/tron/core/db/backup/BackupRocksDBAspect.java b/framework/src/main/java/org/tron/core/db/backup/BackupRocksDBAspect.java deleted file mode 100644 index 25ef66fb8d0..00000000000 --- a/framework/src/main/java/org/tron/core/db/backup/BackupRocksDBAspect.java +++ /dev/null @@ -1,51 +0,0 @@ -package org.tron.core.db.backup; - -import lombok.extern.slf4j.Slf4j; -import org.aspectj.lang.annotation.AfterThrowing; -import org.aspectj.lang.annotation.Aspect; -import org.aspectj.lang.annotation.Before; -import org.aspectj.lang.annotation.Pointcut; -import org.springframework.beans.factory.annotation.Autowired; -import org.tron.common.backup.BackupManager; -import org.tron.common.backup.BackupManager.BackupStatusEnum; -import org.tron.core.capsule.BlockCapsule; -import org.tron.core.config.args.Args; - -@Slf4j -@Aspect -public class BackupRocksDBAspect { - - @Autowired - private BackupDbUtil util; - - @Autowired - private BackupManager backupManager; - - - @Pointcut("execution(** org.tron.core.db.Manager.pushBlock(..)) && args(block)") - public void pointPushBlock(BlockCapsule block) { - - } - - @Before("pointPushBlock(block)") - public void backupDb(BlockCapsule block) { - //SR-Master Node do not backup db; - if (Args.getInstance().isWitness() && backupManager.getStatus() != BackupStatusEnum.SLAVER) { - return; - } - - //backup db when reach frequency. - if (block.getNum() % Args.getInstance().getDbBackupConfig().getFrequency() == 0) { - try { - util.doBackup(block); - } catch (Exception e) { - logger.error("backup db failed:", e); - } - } - } - - @AfterThrowing("pointPushBlock(block)") - public void logErrorPushBlock(BlockCapsule block) { - logger.info("AfterThrowing pushBlock"); - } -} \ No newline at end of file diff --git a/framework/src/main/java/org/tron/core/db/backup/NeedBeanCondition.java b/framework/src/main/java/org/tron/core/db/backup/NeedBeanCondition.java deleted file mode 100644 index 02ed63907d4..00000000000 --- a/framework/src/main/java/org/tron/core/db/backup/NeedBeanCondition.java +++ /dev/null @@ -1,20 +0,0 @@ -package org.tron.core.db.backup; - -import org.springframework.context.annotation.Condition; -import org.springframework.context.annotation.ConditionContext; -import org.springframework.core.type.AnnotatedTypeMetadata; -import org.tron.core.config.args.Args; - -public class NeedBeanCondition implements Condition { - - @Override - public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { - if (Args.getInstance() == null || Args.getInstance().getStorage() == null - || Args.getInstance().getStorage().getDbEngine() == null - || Args.getInstance().getDbBackupConfig() == null) { - return false; - } - return "ROCKSDB".equalsIgnoreCase(Args.getInstance().getStorage().getDbEngine()) - && Args.getInstance().getDbBackupConfig().isEnable() && !Args.getInstance().isWitness(); - } -} \ No newline at end of file diff --git a/framework/src/main/java/org/tron/core/net/messagehandler/BlockMsgHandler.java b/framework/src/main/java/org/tron/core/net/messagehandler/BlockMsgHandler.java index dc886517476..3b9e86d4791 100644 --- a/framework/src/main/java/org/tron/core/net/messagehandler/BlockMsgHandler.java +++ b/framework/src/main/java/org/tron/core/net/messagehandler/BlockMsgHandler.java @@ -150,6 +150,7 @@ private void processBlock(PeerConnection peer, BlockCapsule block) throws P2pExc try { tronNetDelegate.processBlock(block, false); + peer.setBlockRcvTime(System.currentTimeMillis()); witnessProductBlockService.validWitnessProductTwoBlock(block); Item item = new Item(blockId, InventoryType.BLOCK); diff --git a/framework/src/main/java/org/tron/core/net/messagehandler/InventoryMsgHandler.java b/framework/src/main/java/org/tron/core/net/messagehandler/InventoryMsgHandler.java index c92d53584a3..59232a8d258 100644 --- a/framework/src/main/java/org/tron/core/net/messagehandler/InventoryMsgHandler.java +++ b/framework/src/main/java/org/tron/core/net/messagehandler/InventoryMsgHandler.java @@ -6,6 +6,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.tron.common.utils.Sha256Hash; +import org.tron.core.capsule.BlockCapsule.BlockId; import org.tron.core.config.args.Args; import org.tron.core.exception.P2pException; import org.tron.core.exception.P2pException.TypeEnum; @@ -44,7 +45,10 @@ public void processMessage(PeerConnection peer, TronMessage msg) throws P2pExcep peer.getAdvInvReceive().put(item, System.currentTimeMillis()); advService.addInv(item); if (type.equals(InventoryType.BLOCK) && peer.getAdvInvSpread().getIfPresent(item) == null) { - peer.setLastInteractiveTime(System.currentTimeMillis()); + long headNum = tronNetDelegate.getHeadBlockId().getNum(); + if (new BlockId(id).getNum() > headNum) { + peer.setLastInteractiveTime(System.currentTimeMillis()); + } } } } diff --git a/framework/src/main/java/org/tron/core/net/peer/PeerConnection.java b/framework/src/main/java/org/tron/core/net/peer/PeerConnection.java index 253502bc3a1..8d7818d1608 100644 --- a/framework/src/main/java/org/tron/core/net/peer/PeerConnection.java +++ b/framework/src/main/java/org/tron/core/net/peer/PeerConnection.java @@ -88,6 +88,10 @@ public class PeerConnection { @Setter private volatile long lastInteractiveTime; + @Setter + @Getter + private volatile long blockRcvTime; + @Getter @Setter private volatile TronState tronState = TronState.INIT; diff --git a/framework/src/main/java/org/tron/core/net/service/effective/ResilienceService.java b/framework/src/main/java/org/tron/core/net/service/effective/ResilienceService.java index b99b5b52bad..8abb8404cf3 100644 --- a/framework/src/main/java/org/tron/core/net/service/effective/ResilienceService.java +++ b/framework/src/main/java/org/tron/core/net/service/effective/ResilienceService.java @@ -3,8 +3,10 @@ import static org.tron.common.math.Maths.ceil; import static org.tron.common.math.Maths.max; +import java.util.ArrayList; import java.util.Comparator; import java.util.HashMap; +import java.util.IdentityHashMap; import java.util.List; import java.util.Map; import java.util.Optional; @@ -44,7 +46,7 @@ public class ResilienceService { @Autowired private ChainBaseManager chainBaseManager; - + public void init() { if (Args.getInstance().isOpenFullTcpDisconnect) { executor.scheduleWithFixedDelay(() -> { @@ -86,6 +88,7 @@ private void disconnectRandom() { .collect(Collectors.toList()); if (peers.size() >= minBroadcastPeerSize) { + peers = getRandomDisconnectionPeers(peers); long now = System.currentTimeMillis(); Map weights = new HashMap<>(); peers.forEach(peer -> { @@ -121,6 +124,14 @@ private void disconnectRandom() { } + private List getRandomDisconnectionPeers(List peers) { + Map snapshot = new IdentityHashMap<>(peers.size()); + peers.forEach(p -> snapshot.put(p, p.getBlockRcvTime())); + List sorted = new ArrayList<>(peers); + sorted.sort(Comparator.comparingLong(snapshot::get)); + return sorted.subList(0, sorted.size() / 2); + } + private void disconnectLan() { if (!isLanNode()) { return; diff --git a/framework/src/main/java/org/tron/core/net/service/sync/SyncService.java b/framework/src/main/java/org/tron/core/net/service/sync/SyncService.java index 32230612743..0ffe69db097 100644 --- a/framework/src/main/java/org/tron/core/net/service/sync/SyncService.java +++ b/framework/src/main/java/org/tron/core/net/service/sync/SyncService.java @@ -337,6 +337,7 @@ private void processSyncBlock(BlockCapsule block, PeerConnection peerConnection) try { tronNetDelegate.validSignature(block); tronNetDelegate.processBlock(block, true); + peerConnection.setBlockRcvTime(System.currentTimeMillis()); pbftDataSyncHandler.processPBFTCommitData(block); } catch (P2pException p2pException) { logger.error("Process sync block {} failed, type: {}", diff --git a/framework/src/main/java/org/tron/program/Version.java b/framework/src/main/java/org/tron/program/Version.java index de3f91f0a5c..3ce7ce20312 100644 --- a/framework/src/main/java/org/tron/program/Version.java +++ b/framework/src/main/java/org/tron/program/Version.java @@ -4,7 +4,7 @@ public class Version { public static final String VERSION_NAME = "GreatVoyage-v4.8.0.1-1-g44a4bc8263"; public static final String VERSION_CODE = "18636"; - private static final String VERSION = "4.8.1"; + private static final String VERSION = "4.8.2"; public static String getVersion() { return VERSION; diff --git a/framework/src/test/java/org/tron/common/TestConstants.java b/framework/src/test/java/org/tron/common/TestConstants.java index 8e1057f2f67..88f28688936 100644 --- a/framework/src/test/java/org/tron/common/TestConstants.java +++ b/framework/src/test/java/org/tron/common/TestConstants.java @@ -25,7 +25,6 @@ public class TestConstants { public static final String TEST_CONF = "config-test.conf"; public static final String NET_CONF = "config.conf"; public static final String MAINNET_CONF = "config-test-mainnet.conf"; - public static final String DBBACKUP_CONF = "config-test-dbbackup.conf"; public static final String LOCAL_CONF = "config-localtest.conf"; public static final String STORAGE_CONF = "config-test-storagetest.conf"; public static final String INDEX_CONF = "config-test-index.conf"; diff --git a/framework/src/test/java/org/tron/common/backup/BackupManagerTest.java b/framework/src/test/java/org/tron/common/backup/BackupManagerTest.java index 71d1bae447b..5ff02fc8cb5 100644 --- a/framework/src/test/java/org/tron/common/backup/BackupManagerTest.java +++ b/framework/src/test/java/org/tron/common/backup/BackupManagerTest.java @@ -1,11 +1,17 @@ package org.tron.common.backup; import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.net.InetAddress; import java.net.InetSocketAddress; import java.util.ArrayList; +import java.util.Collections; import java.util.List; +import java.util.Map; +import java.util.Set; import java.util.concurrent.ExecutorService; import java.util.concurrent.ScheduledExecutorService; +import java.util.function.BiFunction; import org.junit.After; import org.junit.Assert; import org.junit.Before; @@ -20,6 +26,7 @@ import org.tron.common.parameter.CommonParameter; import org.tron.common.utils.PublicMethod; import org.tron.core.config.args.Args; +import org.tron.core.config.args.InetUtil; public class BackupManagerTest { @@ -27,6 +34,7 @@ public class BackupManagerTest { public TemporaryFolder temporaryFolder = new TemporaryFolder(); private BackupManager manager; private BackupServer backupServer; + private BiFunction savedLookup; @Before public void setUp() throws Exception { @@ -35,10 +43,12 @@ public void setUp() throws Exception { CommonParameter.getInstance().setBackupPort(PublicMethod.chooseRandomPort()); manager = new BackupManager(); backupServer = new BackupServer(manager); + savedLookup = InetUtil.dnsLookup; } @After public void tearDown() { + InetUtil.dnsLookup = savedLookup; Args.clearParam(); } @@ -140,4 +150,108 @@ public void testSendKeepAliveMessage() throws Exception { Assert.assertEquals(BackupManager.BackupStatusEnum.INIT, manager.getStatus()); } + + // ===== domain-handling tests for init() ===== + + @Test(timeout = 5000) + public void testInitResolvesDomainsToMembers() throws Exception { + CommonParameter.getInstance().setBackupMembers( + Collections.singletonList("node.example.com")); + InetAddress resolved = InetAddress.getByName("1.2.3.4"); + InetUtil.dnsLookup = (host, ipv4) -> + ("node.example.com".equals(host) && ipv4) ? resolved : null; + manager.init(); + Set members = getField(manager, "members"); + Map cache = getField(manager, "domainIpCache"); + Assert.assertTrue(members.contains("1.2.3.4")); + Assert.assertEquals("1.2.3.4", cache.get("node.example.com")); + manager.stop(); + } + + @Test(timeout = 5000) + public void testInitSkipsUnresolvableDomain() throws Exception { + CommonParameter.getInstance().setBackupMembers( + Collections.singletonList("bad.invalid.domain")); + InetUtil.dnsLookup = (host, ipv4) -> null; + manager.init(); + Set members = getField(manager, "members"); + Map cache = getField(manager, "domainIpCache"); + Assert.assertTrue("unresolvable domain should be silently dropped", members.isEmpty()); + Assert.assertTrue(cache.isEmpty()); + manager.stop(); + } + + @Test(timeout = 5000) + public void testInitSkipsDomainResolvingToLocalIp() throws Exception { + String localIp = InetAddress.getLocalHost().getHostAddress(); + CommonParameter.getInstance().setBackupMembers( + Collections.singletonList("self.local.host")); + InetAddress selfAddr = InetAddress.getByName(localIp); + InetUtil.dnsLookup = (host, ipv4) -> + ("self.local.host".equals(host) && ipv4) ? selfAddr : null; + manager.init(); + Set members = getField(manager, "members"); + Assert.assertFalse("domain resolving to local IP should not be in members", + members.contains(localIp)); + manager.stop(); + } + + // ===== refreshMemberIps() tests ===== + + @Test(timeout = 5000) + public void testRefreshMemberIpsIpChanged() throws Exception { + Set members = getField(manager, "members"); + Map cache = getField(manager, "domainIpCache"); + members.add("1.1.1.1"); + cache.put("peer.tron.network", "1.1.1.1"); + + InetAddress newAddr = InetAddress.getByName("2.2.2.2"); + InetUtil.dnsLookup = (host, ipv4) -> + ("peer.tron.network".equals(host) && ipv4) ? newAddr : null; + invokeRefreshMemberIps(manager); + Assert.assertFalse(members.contains("1.1.1.1")); + Assert.assertTrue(members.contains("2.2.2.2")); + Assert.assertEquals("2.2.2.2", cache.get("peer.tron.network")); + } + + @Test(timeout = 5000) + public void testRefreshMemberIpsIpUnchanged() throws Exception { + Set members = getField(manager, "members"); + Map cache = getField(manager, "domainIpCache"); + members.add("1.1.1.1"); + cache.put("peer.tron.network", "1.1.1.1"); + + InetAddress sameAddr = InetAddress.getByName("1.1.1.1"); + InetUtil.dnsLookup = (host, ipv4) -> + ("peer.tron.network".equals(host) && ipv4) ? sameAddr : null; + invokeRefreshMemberIps(manager); + Assert.assertTrue(members.contains("1.1.1.1")); + Assert.assertEquals("1.1.1.1", cache.get("peer.tron.network")); + } + + @Test(timeout = 5000) + public void testRefreshMemberIpsDnsFailure() throws Exception { + Set members = getField(manager, "members"); + Map cache = getField(manager, "domainIpCache"); + members.add("1.1.1.1"); + cache.put("peer.tron.network", "1.1.1.1"); + + InetUtil.dnsLookup = (host, ipv4) -> null; + invokeRefreshMemberIps(manager); + Assert.assertTrue("old IP should be kept on DNS failure", members.contains("1.1.1.1")); + Assert.assertEquals("1.1.1.1", cache.get("peer.tron.network")); + } + + @SuppressWarnings("unchecked") + private T getField(Object obj, String name) throws Exception { + Field f = obj.getClass().getDeclaredField(name); + f.setAccessible(true); + return (T) f.get(obj); + } + + private void invokeRefreshMemberIps(BackupManager mgr) throws Exception { + Method m = mgr.getClass().getDeclaredMethod("refreshMemberIps"); + m.setAccessible(true); + m.invoke(mgr); + } } diff --git a/framework/src/test/java/org/tron/common/storage/rocksdb/RocksDbDataSourceImplTest.java b/framework/src/test/java/org/tron/common/storage/rocksdb/RocksDbDataSourceImplTest.java index f3ca15c7cbe..b0f13eb9154 100644 --- a/framework/src/test/java/org/tron/common/storage/rocksdb/RocksDbDataSourceImplTest.java +++ b/framework/src/test/java/org/tron/common/storage/rocksdb/RocksDbDataSourceImplTest.java @@ -2,7 +2,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertThrows; -import static org.tron.common.TestConstants.DBBACKUP_CONF; +import static org.tron.common.TestConstants.TEST_CONF; import static org.tron.common.TestConstants.assumeLevelDbAvailable; import java.io.File; @@ -49,7 +49,8 @@ public static void destroy() { @BeforeClass public static void initDb() throws IOException { Args.setParam(new String[]{"--output-directory", - temporaryFolder.newFolder().toString()}, DBBACKUP_CONF); + temporaryFolder.newFolder().toString()}, TEST_CONF); + CommonParameter.getInstance().storage.setDbEngine("ROCKSDB"); } @Test diff --git a/framework/src/test/java/org/tron/core/config/args/ArgsTest.java b/framework/src/test/java/org/tron/core/config/args/ArgsTest.java index a67414bd388..bb3d1c4b210 100644 --- a/framework/src/test/java/org/tron/core/config/args/ArgsTest.java +++ b/framework/src/test/java/org/tron/core/config/args/ArgsTest.java @@ -23,11 +23,11 @@ import io.grpc.netty.NettyServerBuilder; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; +import java.net.InetAddress; import java.util.Arrays; import java.util.HashMap; import java.util.Map; import lombok.extern.slf4j.Slf4j; -import org.junit.After; import org.junit.Assert; import org.junit.Rule; import org.junit.Test; @@ -45,17 +45,9 @@ public class ArgsTest { private final String privateKey = PublicMethod.getRandomPrivateKey(); - private String address; - private LocalWitnesses localWitnesses; - @Rule public ExpectedException thrown = ExpectedException.none(); - @After - public void destroy() { - Args.clearParam(); - } - @Test public void get() { Args.setParam(new String[] {"-c", TestConstants.TEST_CONF, "--keystore-factory"}, @@ -65,11 +57,11 @@ public void get() { Args.logConfig(); - localWitnesses = new LocalWitnesses(); + LocalWitnesses localWitnesses = new LocalWitnesses(); localWitnesses.setPrivateKeys(Arrays.asList(privateKey)); localWitnesses.initWitnessAccountAddress(null, true); Args.setLocalWitnesses(localWitnesses); - address = ByteArray.toHexString(Args.getLocalWitnesses() + String address = ByteArray.toHexString(Args.getLocalWitnesses() .getWitnessAccountAddress()); Assert.assertEquals("41", DecodeUtil.addressPreFixString); Assert.assertEquals(TestConstants.TEST_CONF, Args.getConfigFilePath()); @@ -495,7 +487,7 @@ public void testEventConfigEnabledWithInvalidFromBlockLeavesFilterNull() { Assert.assertNull(Args.getInstance().getEventFilter()); Args.clearParam(); } - + @Test public void testAllowShieldedTransactionApiDefault() { Args.setParam(new String[]{}, TestConstants.TEST_CONF); @@ -679,4 +671,46 @@ public void testMaxMessageSizeNonNumeric() { () -> Args.applyConfigParams(config)); Assert.assertTrue(e.getMessage().contains("No number in size-in-bytes value")); } + + // ===== checkBackupMembers() tests ===== + + @Test + public void testCheckBackupMembersWithIpPasses() throws Exception { + Args.setParam(new String[]{}, TestConstants.TEST_CONF); + CommonParameter.getInstance().setBackupMembers(Arrays.asList("1.2.3.4", "10.0.0.1")); + Method method = Args.class.getDeclaredMethod("checkBackupMembers"); + method.setAccessible(true); + method.invoke(null); + } + + @Test(timeout = 5000) + public void testCheckBackupMembersUnresolvableDomainThrows() throws Exception { + Args.setParam(new String[]{}, TestConstants.TEST_CONF); + CommonParameter.getInstance().setBackupMembers( + Arrays.asList("bad.invalid.domain")); + Method method = Args.class.getDeclaredMethod("checkBackupMembers"); + method.setAccessible(true); + InetUtil.dnsLookup = (host, ipv4) -> null; + try { + method.invoke(null); + Assert.fail("Expected InvocationTargetException wrapping TronError"); + } catch (InvocationTargetException ex) { + Assert.assertTrue(ex.getCause() instanceof TronError); + Assert.assertEquals(TronError.ErrCode.PARAMETER_INIT, + ((TronError) ex.getCause()).getErrCode()); + } + } + + @Test(timeout = 5000) + public void testCheckBackupMembersResolvableDomainPasses() throws Exception { + Args.setParam(new String[]{}, TestConstants.TEST_CONF); + CommonParameter.getInstance().setBackupMembers( + Arrays.asList("peer.tron.network")); + Method method = Args.class.getDeclaredMethod("checkBackupMembers"); + method.setAccessible(true); + InetAddress mockAddr = InetAddress.getByName("5.5.5.5"); + InetUtil.dnsLookup = (host, ipv4) -> + ("peer.tron.network".equals(host) && ipv4) ? mockAddr : null; + method.invoke(null); + } } diff --git a/framework/src/test/java/org/tron/core/config/args/InetUtilTest.java b/framework/src/test/java/org/tron/core/config/args/InetUtilTest.java new file mode 100644 index 00000000000..4611947211c --- /dev/null +++ b/framework/src/test/java/org/tron/core/config/args/InetUtilTest.java @@ -0,0 +1,306 @@ +package org.tron.core.config.args; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.function.BiFunction; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +public class InetUtilTest { + + private BiFunction savedLookup; + + @Before + public void saveLookup() { + savedLookup = InetUtil.dnsLookup; + } + + @After + public void restoreLookup() { + InetUtil.dnsLookup = savedLookup; + } + + // ===== resolveInetSocketAddressList ===== + + @Test + public void testResolveListEmpty() { + List result = + InetUtil.resolveInetSocketAddressList(Collections.emptyList()); + assertTrue(result.isEmpty()); + } + + @Test + public void testResolveListIpv4Literals() { + List input = Arrays.asList("192.168.1.1:18888", "10.0.0.2:8080"); + List result = InetUtil.resolveInetSocketAddressList(input); + assertEquals(2, result.size()); + assertEquals("192.168.1.1", result.get(0).getAddress().getHostAddress()); + assertEquals(18888, result.get(0).getPort()); + assertEquals("10.0.0.2", result.get(1).getAddress().getHostAddress()); + assertEquals(8080, result.get(1).getPort()); + } + + @Test + public void testResolveListIpv4LiteralOrderPreserved() { + List input = Arrays.asList("10.0.0.3:1", "10.0.0.1:2", "10.0.0.2:3"); + List result = InetUtil.resolveInetSocketAddressList(input); + assertEquals(3, result.size()); + assertEquals("10.0.0.3", result.get(0).getAddress().getHostAddress()); + assertEquals("10.0.0.1", result.get(1).getAddress().getHostAddress()); + assertEquals("10.0.0.2", result.get(2).getAddress().getHostAddress()); + } + + @Test + public void testResolveListIpv6Loopback() { + // Bracketed IPv6 loopback — treated as IP literal, no DNS lookup. + List result = InetUtil.resolveInetSocketAddressList( + Collections.singletonList("[::1]:18888")); + assertEquals(1, result.size()); + assertTrue(result.get(0).getAddress().getHostAddress().contains(":")); + assertEquals(18888, result.get(0).getPort()); + } + + @Test + public void testResolveListIpv6FullAddress() { + // Full IPv6 address in bracketed format. + List result = InetUtil.resolveInetSocketAddressList( + Collections.singletonList("[2001:db8::1]:18888")); + assertEquals(1, result.size()); + assertTrue(result.get(0).getAddress().getHostAddress().contains(":")); + assertEquals(18888, result.get(0).getPort()); + } + + @Test + public void testResolveListMixedIpv4AndIpv6Literals() { + // Mix of IPv4 and IPv6 literals — both treated as IP literals, order preserved. + List input = Arrays.asList("192.168.0.1:18888", "[2001:db8::2]:18889"); + List result = InetUtil.resolveInetSocketAddressList(input); + assertEquals(2, result.size()); + assertEquals("192.168.0.1", result.get(0).getAddress().getHostAddress()); + assertEquals(18888, result.get(0).getPort()); + assertTrue(result.get(1).getAddress().getHostAddress().contains(":")); + assertEquals(18889, result.get(1).getPort()); + } + + @Test(timeout = 5000) + public void testResolveListSingleDomainResolved() throws Exception { + InetAddress mockAddr = InetAddress.getByName("1.2.3.4"); + InetUtil.dnsLookup = (host, ipv4) -> + ("node.example.com".equals(host) && ipv4) ? mockAddr : null; + List result = InetUtil.resolveInetSocketAddressList( + Collections.singletonList("node.example.com:18888")); + assertEquals(1, result.size()); + assertEquals("1.2.3.4", result.get(0).getAddress().getHostAddress()); + assertEquals(18888, result.get(0).getPort()); + } + + @Test(timeout = 5000) + public void testResolveListSingleDomainUnresolvable() { + InetUtil.dnsLookup = (host, ipv4) -> null; + List result = InetUtil.resolveInetSocketAddressList( + Collections.singletonList("bad.invalid:18888")); + assertTrue("unresolvable domain should be silently dropped", result.isEmpty()); + } + + @Test(timeout = 5000) + public void testResolveListDomainFirstOrderPreservedBeforeIp() throws Exception { + // Domain in position 0, IP literal in position 1 — verifies the final ordering loop + // places the resolved domain before the IP literal. + InetAddress domainAddr = InetAddress.getByName("3.3.3.3"); + InetUtil.dnsLookup = (host, ipv4) -> + ("first.node".equals(host) && ipv4) ? domainAddr : null; + List result = InetUtil.resolveInetSocketAddressList( + Arrays.asList("first.node:18888", "10.0.0.2:8080")); + assertEquals(2, result.size()); + assertEquals("3.3.3.3", result.get(0).getAddress().getHostAddress()); + assertEquals(18888, result.get(0).getPort()); + assertEquals("10.0.0.2", result.get(1).getAddress().getHostAddress()); + assertEquals(8080, result.get(1).getPort()); + } + + @Test(timeout = 5000) + public void testResolveListUnresolvableDomainFirstIpLiteralKept() { + // Unresolvable domain in position 0 is dropped; trailing IP literal is kept. + InetUtil.dnsLookup = (host, ipv4) -> null; + List result = InetUtil.resolveInetSocketAddressList( + Arrays.asList("bad.invalid:18888", "1.1.1.1:8080")); + assertEquals(1, result.size()); + assertEquals("1.1.1.1", result.get(0).getAddress().getHostAddress()); + assertEquals(8080, result.get(0).getPort()); + } + + @Test(timeout = 5000) + public void testResolveListMixedIpAndDomain() throws Exception { + InetAddress domainAddr = InetAddress.getByName("5.5.5.5"); + InetUtil.dnsLookup = (host, ipv4) -> + ("my.node".equals(host) && ipv4) ? domainAddr : null; + List result = InetUtil.resolveInetSocketAddressList( + Arrays.asList("192.168.0.1:18888", "my.node:8080", "10.0.0.1:9090")); + assertEquals(3, result.size()); + assertEquals("192.168.0.1", result.get(0).getAddress().getHostAddress()); + assertEquals("5.5.5.5", result.get(1).getAddress().getHostAddress()); + assertEquals("10.0.0.1", result.get(2).getAddress().getHostAddress()); + } + + // ===== resolveInetSocketAddressList — parallel path (domainEntries.size() > 1) ===== + + /** Two domain entries, both resolvable: parallel pool is used, original order preserved. */ + @Test(timeout = 5000) + public void testResolveListTwoDomainsParallelBothResolved() throws Exception { + InetAddress addr1 = InetAddress.getByName("1.1.1.1"); + InetAddress addr2 = InetAddress.getByName("2.2.2.2"); + InetUtil.dnsLookup = (host, ipv4) -> { + if (!ipv4) { + return null; + } + if ("node-a.example.com".equals(host)) { + return addr1; + } + if ("node-b.example.com".equals(host)) { + return addr2; + } + return null; + }; + List result = InetUtil.resolveInetSocketAddressList( + Arrays.asList("node-a.example.com:18888", "node-b.example.com:18889")); + assertEquals(2, result.size()); + assertEquals("1.1.1.1", result.get(0).getAddress().getHostAddress()); + assertEquals(18888, result.get(0).getPort()); + assertEquals("2.2.2.2", result.get(1).getAddress().getHostAddress()); + assertEquals(18889, result.get(1).getPort()); + } + + /** Two domain entries, one fails: the failing entry is dropped, the other is kept. */ + @Test(timeout = 5000) + public void testResolveListTwoDomainsParallelOneFails() throws Exception { + InetAddress goodAddr = InetAddress.getByName("3.3.3.3"); + InetUtil.dnsLookup = (host, ipv4) -> + ("good.node".equals(host) && ipv4) ? goodAddr : null; + List result = InetUtil.resolveInetSocketAddressList( + Arrays.asList("good.node:18888", "bad.invalid:18889")); + assertEquals(1, result.size()); + assertEquals("3.3.3.3", result.get(0).getAddress().getHostAddress()); + assertEquals(18888, result.get(0).getPort()); + } + + /** + * Two domain entries interleaved with IP literals: parallel pool resolves the domains + * while IP literals pass through, and original config order is preserved in the result. + */ + @Test(timeout = 5000) + public void testResolveListTwoDomainsParallelOrderWithIpsPreserved() throws Exception { + InetAddress addr1 = InetAddress.getByName("4.4.4.4"); + InetAddress addr2 = InetAddress.getByName("5.5.5.5"); + InetUtil.dnsLookup = (host, ipv4) -> { + if (!ipv4) { + return null; + } + if ("alpha.node".equals(host)) { + return addr1; + } + if ("beta.node".equals(host)) { + return addr2; + } + return null; + }; + List result = InetUtil.resolveInetSocketAddressList( + Arrays.asList("10.0.0.1:8001", "alpha.node:8002", "10.0.0.2:8003", "beta.node:8004")); + assertEquals(4, result.size()); + assertEquals("10.0.0.1", result.get(0).getAddress().getHostAddress()); + assertEquals("4.4.4.4", result.get(1).getAddress().getHostAddress()); + assertEquals("10.0.0.2", result.get(2).getAddress().getHostAddress()); + assertEquals("5.5.5.5", result.get(3).getAddress().getHostAddress()); + } + + /** + * One domain times out (lookup hangs beyond DNS_LOOKUP_TIMEOUT_SECONDS), the other resolves: + * the timed-out entry is dropped, the successful entry is kept, and the test itself completes + * well within the per-lookup budget because {@code dnsLookup} returns immediately. + */ + @Test(timeout = 5000) + public void testResolveListTwoDomainsParallelOneTimesOut() throws Exception { + InetAddress goodAddr = InetAddress.getByName("6.6.6.6"); + InetUtil.dnsLookup = (host, ipv4) -> { + if ("slow.node".equals(host)) { + // Simulate a hang that would exceed the 10-second per-lookup timeout. + // In this test the lookup returns immediately with null so the test itself is fast; + // the TimeoutException path is exercised when future.get() times out in production. + // Here we verify the structural handling: a null result drops the entry. + return null; + } + return ("fast.node".equals(host) && ipv4) ? goodAddr : null; + }; + List result = InetUtil.resolveInetSocketAddressList( + Arrays.asList("slow.node:18888", "fast.node:18889")); + assertEquals("timed-out/unresolvable domain should be dropped", 1, result.size()); + assertEquals("6.6.6.6", result.get(0).getAddress().getHostAddress()); + assertEquals(18889, result.get(0).getPort()); + } + + // ===== resolveInetAddress ===== + + @Test + public void testResolveInetAddressIpv4Literal() { + InetAddress result = InetUtil.resolveInetAddress("127.0.0.1"); + assertNotNull(result); + assertEquals("127.0.0.1", result.getHostAddress()); + } + + @Test + public void testResolveInetAddressIpv6Loopback() { + // ::1 is an IPv6 literal — resolved without DNS. + InetAddress result = InetUtil.resolveInetAddress("::1"); + assertNotNull(result); + assertTrue(result.getHostAddress().contains(":")); + } + + @Test + public void testResolveInetAddressIpv6FullLiteral() { + // Full-form IPv6 address — treated as IP literal, no DNS lookup. + InetAddress result = InetUtil.resolveInetAddress("2001:db8::1"); + assertNotNull(result); + assertTrue(result.getHostAddress().contains(":")); + } + + @Test + public void testResolveInetAddressIpv6CompressedLiteral() { + // Compressed IPv6 with multiple groups — still a literal, no DNS. + InetAddress result = InetUtil.resolveInetAddress("fe80::1"); + assertNotNull(result); + assertTrue(result.getHostAddress().contains(":")); + } + + @Test(timeout = 5000) + public void testResolveInetAddressDomainResolved() throws Exception { + InetAddress mockAddr = InetAddress.getByName("3.3.3.3"); + InetUtil.dnsLookup = (host, ipv4) -> + ("peer.tron.network".equals(host) && ipv4) ? mockAddr : null; + InetAddress result = InetUtil.resolveInetAddress("peer.tron.network"); + assertNotNull(result); + assertEquals("3.3.3.3", result.getHostAddress()); + } + + @Test(timeout = 5000) + public void testResolveInetAddressDomainIpv4FallsBackToIpv6() throws Exception { + InetAddress ipv6Addr = InetAddress.getByName("::1"); + InetUtil.dnsLookup = (host, ipv4) -> ipv4 ? null : ipv6Addr; + InetAddress result = InetUtil.resolveInetAddress("ipv6only.host"); + assertNotNull(result); + } + + @Test(timeout = 5000) + public void testResolveInetAddressUnresolvableReturnsNull() { + InetUtil.dnsLookup = (host, ipv4) -> null; + InetAddress result = InetUtil.resolveInetAddress("bad.invalid"); + assertNull(result); + } +} diff --git a/framework/src/test/java/org/tron/core/db/backup/BackupDbUtilTest.java b/framework/src/test/java/org/tron/core/db/backup/BackupDbUtilTest.java deleted file mode 100644 index 0153faeab71..00000000000 --- a/framework/src/test/java/org/tron/core/db/backup/BackupDbUtilTest.java +++ /dev/null @@ -1,92 +0,0 @@ -package org.tron.core.db.backup; - -import java.io.File; -import javax.annotation.Resource; -import lombok.extern.slf4j.Slf4j; -import org.junit.Assert; -import org.junit.Before; -import org.junit.Test; -import org.rocksdb.RocksDB; -import org.tron.common.BaseTest; -import org.tron.common.parameter.CommonParameter; -import org.tron.common.utils.FileUtil; -import org.tron.common.utils.PropUtil; -import org.tron.consensus.dpos.DposSlot; -import org.tron.core.config.args.Args; -import org.tron.core.consensus.ConsensusService; -import org.tron.core.db.ManagerForTest; - -@Slf4j -public class BackupDbUtilTest extends BaseTest { - - static { - RocksDB.loadLibrary(); - } - - @Resource - public ConsensusService consensusService; - @Resource - public DposSlot dposSlot; - public ManagerForTest mngForTest; - - String propPath; - String bak1Path; - String bak2Path; - int frequency; - - private static final String dbPath; - - static { - dbPath = dbPath(); - Args.setParam( - new String[]{ - "--output-directory", dbPath, - "--storage-db-directory", "database", - "--storage-index-directory", "index" - }, - "config-test-dbbackup.conf" - ); - } - - @Before - public void before() { - consensusService.start(); - mngForTest = new ManagerForTest(dbManager, dposSlot); - //prepare prop.properties - propPath = dbPath + File.separator + "test_prop.properties"; - bak1Path = dbPath + File.separator + "bak1/database"; - bak2Path = dbPath + File.separator + "bak2/database"; - frequency = 50; - CommonParameter parameter = Args.getInstance(); - parameter.getDbBackupConfig() - .initArgs(true, propPath, bak1Path, bak2Path, frequency); - FileUtil.createFileIfNotExists(propPath); - } - - @Test - public void testDoBackup() { - PropUtil.writeProperty(propPath, BackupDbUtil.getDB_BACKUP_STATE(), "11"); - mngForTest.pushNTestBlock(50); - - Assert.assertEquals(50, dbManager.getDynamicPropertiesStore().getLatestBlockHeaderNumber()); - Assert.assertEquals("22", PropUtil.readProperty(propPath, BackupDbUtil.getDB_BACKUP_STATE())); - - mngForTest.pushNTestBlock(50); - Assert.assertEquals(100, dbManager.getDynamicPropertiesStore().getLatestBlockHeaderNumber()); - Assert.assertEquals("11", PropUtil.readProperty(propPath, BackupDbUtil.getDB_BACKUP_STATE())); - - mngForTest.pushNTestBlock(50); - Assert.assertEquals(150, dbManager.getDynamicPropertiesStore().getLatestBlockHeaderNumber()); - Assert.assertEquals("22", PropUtil.readProperty(propPath, BackupDbUtil.getDB_BACKUP_STATE())); - - PropUtil.writeProperty(propPath, BackupDbUtil.getDB_BACKUP_STATE(), "1"); - mngForTest.pushNTestBlock(50); - Assert.assertEquals(200, dbManager.getDynamicPropertiesStore().getLatestBlockHeaderNumber()); - Assert.assertEquals("11", PropUtil.readProperty(propPath, BackupDbUtil.getDB_BACKUP_STATE())); - - PropUtil.writeProperty(propPath, BackupDbUtil.getDB_BACKUP_STATE(), "2"); - mngForTest.pushNTestBlock(50); - Assert.assertEquals(250, dbManager.getDynamicPropertiesStore().getLatestBlockHeaderNumber()); - Assert.assertEquals("22", PropUtil.readProperty(propPath, BackupDbUtil.getDB_BACKUP_STATE())); - } -} diff --git a/framework/src/test/java/org/tron/core/net/services/ResilienceServiceTest.java b/framework/src/test/java/org/tron/core/net/services/ResilienceServiceTest.java index 792fb82c2c6..c8c4d974d8e 100644 --- a/framework/src/test/java/org/tron/core/net/services/ResilienceServiceTest.java +++ b/framework/src/test/java/org/tron/core/net/services/ResilienceServiceTest.java @@ -7,6 +7,7 @@ import java.io.IOException; import java.net.InetSocketAddress; import java.util.HashSet; +import java.util.List; import java.util.Set; import javax.annotation.Resource; import org.junit.After; @@ -97,6 +98,57 @@ public void testDisconnectRandom() { Assert.assertEquals(maxConnection - 1, PeerManager.getPeers().size()); } + @Test + public void testDisconnectRandomPreservesRecentBlockRcvTimePeer() { + int maxConnection = 30; + Assert.assertEquals(0, PeerManager.getPeers().size()); + + ApplicationContext ctx = (ApplicationContext) ReflectUtils.getFieldObject(p2pEventHandler, + "ctx"); + + // Create maxConnection + 1 peers (triggers disconnectRandom) + for (int i = 0; i < maxConnection + 1; i++) { + InetSocketAddress inetSocketAddress = new InetSocketAddress("202.0.0." + i, 10001); + Channel c1 = spy(Channel.class); + ReflectUtils.setFieldValue(c1, "inetSocketAddress", inetSocketAddress); + ReflectUtils.setFieldValue(c1, "inetAddress", inetSocketAddress.getAddress()); + ReflectUtils.setFieldValue(c1, "ctx", spy(ChannelHandlerContext.class)); + Mockito.doNothing().when(c1).send((byte[]) any()); + PeerManager.add(ctx, c1); + } + + // Set first minBroadcastPeerSize peers as broadcast-state + List peers = PeerManager.getPeers(); + for (PeerConnection peer : peers.subList(0, ResilienceService.minBroadcastPeerSize)) { + peer.setNeedSyncFromPeer(false); + peer.setNeedSyncFromUs(false); + peer.setLastInteractiveTime(System.currentTimeMillis() - 1000); + } + for (PeerConnection peer : peers.subList(ResilienceService.minBroadcastPeerSize, + maxConnection + 1)) { + peer.setNeedSyncFromPeer(false); + peer.setNeedSyncFromUs(true); + } + + // Give the LAST broadcast peer a very recent blockRcvTime — it must NOT be disconnected + PeerConnection bestPeer = peers.stream() + .filter(p -> !p.isNeedSyncFromUs() && !p.isNeedSyncFromPeer()) + .reduce((a, b) -> b) // last broadcast peer + .orElseThrow(() -> new AssertionError("no broadcast peer")); + bestPeer.setBlockRcvTime(System.currentTimeMillis()); + + InetSocketAddress bestPeerAddress = bestPeer.getChannel().getInetSocketAddress(); + + // With minBroadcastPeerSize=3 broadcast peers, getRandomDisconnectionPeers returns + // the 1 peer with oldest blockRcvTime (0). bestPeer has most recent time → exempt. + ReflectUtils.invokeMethod(service, "disconnectRandom"); + + boolean bestPeerStillConnected = PeerManager.getPeers().stream() + .anyMatch(p -> p.getChannel().getInetSocketAddress().equals(bestPeerAddress)); + Assert.assertTrue("Peer with most recent blockRcvTime should not be disconnected", + bestPeerStillConnected); + } + @Test public void testDisconnectLan() { int minConnection = 8; diff --git a/framework/src/test/java/org/tron/program/SupplementTest.java b/framework/src/test/java/org/tron/program/SupplementTest.java index 483922cf8c5..f95f3222108 100644 --- a/framework/src/test/java/org/tron/program/SupplementTest.java +++ b/framework/src/test/java/org/tron/program/SupplementTest.java @@ -16,7 +16,6 @@ import org.junit.rules.ExpectedException; import org.tron.common.BaseTest; import org.tron.common.TestConstants; -import org.tron.common.config.DbBackupConfig; import org.tron.common.entity.PeerInfo; import org.tron.common.utils.CompactEncoder; import org.tron.common.utils.JsonUtil; @@ -49,9 +48,6 @@ public void testGet() throws Exception { StorageRowCapsule storageRowCapsule = storageRowStore.get(new byte[]{}); assertNotNull(storageRowCapsule); - DbBackupConfig dbBackupConfig = new DbBackupConfig(); - String p = dbPath + File.separator; - dbBackupConfig.initArgs(true, p + "propPath", p + "bak1path/", p + "bak2path/", 1); Value value = new Value(new byte[]{1}); value.asBytes(); diff --git a/framework/src/test/resources/config-test-dbbackup.conf b/framework/src/test/resources/config-test-dbbackup.conf deleted file mode 100644 index b660965f3e9..00000000000 --- a/framework/src/test/resources/config-test-dbbackup.conf +++ /dev/null @@ -1,401 +0,0 @@ -net { - # type is deprecated and has no effect. - # type = mainnet -} - -storage { - # Directory for storing persistent data - db.engine = "ROCKSDB", - db.directory = "database", - index.directory = "index", - - # You can custom these 14 databases' configs: - - # account, account-index, asset-issue, block, block-index, - # block_KDB, peers, properties, recent-block, trans, - # utxo, votes, witness, witness_schedule. - - # Otherwise, db configs will remain defualt and data will be stored in - # the path of "output-directory" or which is set by "-d" ("--output-directory"). - - # Attention: name is a required field that must be set !!! - properties = [ - // { - // name = "account", - // path = "storage_directory_test", - // createIfMissing = true, - // paranoidChecks = true, - // verifyChecksums = true, - // compressionType = 1, // compressed with snappy - // blockSize = 4096, // 4 KB = 4 * 1024 B - // writeBufferSize = 10485760, // 10 MB = 10 * 1024 * 1024 B - // cacheSize = 10485760, // 10 MB = 10 * 1024 * 1024 B - // maxOpenFiles = 100 - // }, - // { - // name = "account-index", - // path = "storage_directory_test", - // createIfMissing = true, - // paranoidChecks = true, - // verifyChecksums = true, - // compressionType = 1, // compressed with snappy - // blockSize = 4096, // 4 KB = 4 * 1024 B - // writeBufferSize = 10485760, // 10 MB = 10 * 1024 * 1024 B - // cacheSize = 10485760, // 10 MB = 10 * 1024 * 1024 B - // maxOpenFiles = 100 - // }, - ] - - needToUpdateAsset = true - - backup = { - enable = true - properties = "test_prop.properties" - bak1path = "bak1/database" - bak2path = "bak2/database" - frequency = 10000 // backup db every ? blocks processed. - } -} - -node.discovery = { - enable = true - persist = true - external.ip = "" -} - -node.backup { - port = 10001 - - # my priority, each member should use different priority - priority = 8 - - # peer's ip list, can't contain mine - members = [ - # "ip", - # "ip" - ] -} - -node { - # trust node for solidity node - # trustNode = "ip:port" - trustNode = "127.0.0.1:50051" - - # expose extension api to public or not - walletExtensionApi = true - - listen.port = 18888 - - connection.timeout = 2 - - tcpNettyWorkThreadNum = 0 - - udpNettyWorkThreadNum = 1 - - # Number of validate sign thread, default availableProcessors / 2 - # validateSignThreadNum = 16 - - maxConnectionsWithSameIp = 2 - - minParticipationRate = 15 - - # check the peer data transfer ,disconnect factor - isOpenFullTcpDisconnect = true - - p2p { - version = 11111 # 11111: mainnet; 20180622: testnet - } - - active = [ - # Active establish connection in any case - # Sample entries: - # "ip:port", - # "ip:port" - ] - - passive = [ - # Passive accept connection in any case - # Sample entries: - # "ip:port", - # "ip:port" - ] - - http { - fullNodePort = 8090 - solidityPort = 8091 - } - - rpc { - port = 50051 - #solidityPort = 50061 - # Number of gRPC thread, default availableProcessors / 2 - # thread = 16 - - # The maximum number of concurrent calls permitted for each incoming connection - # maxConcurrentCallsPerConnection = - - # The HTTP/2 flow control window, default 1MB - # flowControlWindow = - - # Connection being idle for longer than which will be gracefully terminated - maxConnectionIdleInMillis = 60000 - - # Connection lasting longer than which will be gracefully terminated - # maxConnectionAgeInMillis = - - # The maximum message size allowed to be received on the server, default 4MB - # maxMessageSize = - - # The maximum size of header list allowed to be received, default 8192 - # maxHeaderListSize = - } - - # Limits the maximum percentage (default 75%) of producing block interval - # to provide sufficient time to perform other operations e.g. broadcast block - # blockProducedTimeOut = 75 - - # Limits the maximum number (default 700) of transaction from network layer - # netMaxTrxPerSecond = 700 -} - - -seed.node = { - # List of the seed nodes - # Seed nodes are stable full nodes - # example: - # ip.list = [ - # "ip:port", - # "ip:port" - # ] - ip.list = [ - "54.236.37.243:18888", - "52.53.189.99:18888", - "18.196.99.16:18888", - "34.253.187.192:18888", - "52.56.56.149:18888", - "35.180.51.163:18888", - "54.252.224.209:18888", - "18.228.15.36:18888", - "52.15.93.92:18888", - "34.220.77.106:18888", - "13.127.47.162:18888", - "13.124.62.58:18888", - "13.229.128.108:18888", - "35.182.37.246:18888", - "34.200.228.125:18888", - "18.220.232.201:18888", - "13.57.30.186:18888", - "35.165.103.105:18888", - "18.184.238.21:18888", - "34.250.140.143:18888", - "35.176.192.130:18888", - "52.47.197.188:18888", - "52.62.210.100:18888", - "13.231.4.243:18888", - "18.231.76.29:18888", - "35.154.90.144:18888", - "13.125.210.234:18888", - "13.250.40.82:18888", - "35.183.101.48:18888" - ] -} - -genesis.block = { - # Reserve balance - assets = [ - { - accountName = "Zion" - accountType = "AssetIssue" - address = "TLLM21wteSPs4hKjbxgmH1L6poyMjeTbHm" - balance = "99000000000000000" - }, - { - accountName = "Sun" - accountType = "AssetIssue" - address = "TXmVpin5vq5gdZsciyyjdZgKRUju4st1wM" - balance = "0" - }, - { - accountName = "Blackhole" - accountType = "AssetIssue" - address = "TLsV52sRDL79HXGGm9yzwKibb6BeruhUzy" - balance = "-9223372036854775808" - } - ] - - witnesses = [ - { - address: THKJYuUmMKKARNf7s2VT51g5uPY6KEqnat, - url = "http://GR1.com", - voteCount = 100000026 - }, - { - address: TVDmPWGYxgi5DNeW8hXrzrhY8Y6zgxPNg4, - url = "http://GR2.com", - voteCount = 100000025 - }, - { - address: TWKZN1JJPFydd5rMgMCV5aZTSiwmoksSZv, - url = "http://GR3.com", - voteCount = 100000024 - }, - { - address: TDarXEG2rAD57oa7JTK785Yb2Et32UzY32, - url = "http://GR4.com", - voteCount = 100000023 - }, - { - address: TAmFfS4Tmm8yKeoqZN8x51ASwdQBdnVizt, - url = "http://GR5.com", - voteCount = 100000022 - }, - { - address: TK6V5Pw2UWQWpySnZyCDZaAvu1y48oRgXN, - url = "http://GR6.com", - voteCount = 100000021 - }, - { - address: TGqFJPFiEqdZx52ZR4QcKHz4Zr3QXA24VL, - url = "http://GR7.com", - voteCount = 100000020 - }, - { - address: TC1ZCj9Ne3j5v3TLx5ZCDLD55MU9g3XqQW, - url = "http://GR8.com", - voteCount = 100000019 - }, - { - address: TWm3id3mrQ42guf7c4oVpYExyTYnEGy3JL, - url = "http://GR9.com", - voteCount = 100000018 - }, - { - address: TCvwc3FV3ssq2rD82rMmjhT4PVXYTsFcKV, - url = "http://GR10.com", - voteCount = 100000017 - }, - { - address: TFuC2Qge4GxA2U9abKxk1pw3YZvGM5XRir, - url = "http://GR11.com", - voteCount = 100000016 - }, - { - address: TNGoca1VHC6Y5Jd2B1VFpFEhizVk92Rz85, - url = "http://GR12.com", - voteCount = 100000015 - }, - { - address: TLCjmH6SqGK8twZ9XrBDWpBbfyvEXihhNS, - url = "http://GR13.com", - voteCount = 100000014 - }, - { - address: TEEzguTtCihbRPfjf1CvW8Euxz1kKuvtR9, - url = "http://GR14.com", - voteCount = 100000013 - }, - { - address: TZHvwiw9cehbMxrtTbmAexm9oPo4eFFvLS, - url = "http://GR15.com", - voteCount = 100000012 - }, - { - address: TGK6iAKgBmHeQyp5hn3imB71EDnFPkXiPR, - url = "http://GR16.com", - voteCount = 100000011 - }, - { - address: TLaqfGrxZ3dykAFps7M2B4gETTX1yixPgN, - url = "http://GR17.com", - voteCount = 100000010 - }, - { - address: TX3ZceVew6yLC5hWTXnjrUFtiFfUDGKGty, - url = "http://GR18.com", - voteCount = 100000009 - }, - { - address: TYednHaV9zXpnPchSywVpnseQxY9Pxw4do, - url = "http://GR19.com", - voteCount = 100000008 - }, - { - address: TCf5cqLffPccEY7hcsabiFnMfdipfyryvr, - url = "http://GR20.com", - voteCount = 100000007 - }, - { - address: TAa14iLEKPAetX49mzaxZmH6saRxcX7dT5, - url = "http://GR21.com", - voteCount = 100000006 - }, - { - address: TBYsHxDmFaRmfCF3jZNmgeJE8sDnTNKHbz, - url = "http://GR22.com", - voteCount = 100000005 - }, - { - address: TEVAq8dmSQyTYK7uP1ZnZpa6MBVR83GsV6, - url = "http://GR23.com", - voteCount = 100000004 - }, - { - address: TRKJzrZxN34YyB8aBqqPDt7g4fv6sieemz, - url = "http://GR24.com", - voteCount = 100000003 - }, - { - address: TRMP6SKeFUt5NtMLzJv8kdpYuHRnEGjGfe, - url = "http://GR25.com", - voteCount = 100000002 - }, - { - address: TDbNE1VajxjpgM5p7FyGNDASt3UVoFbiD3, - url = "http://GR26.com", - voteCount = 100000001 - }, - { - address: TLTDZBcPoJ8tZ6TTEeEqEvwYFk2wgotSfD, - url = "http://GR27.com", - voteCount = 100000000 - } - ] - - timestamp = "0" #2017-8-26 12:00:00 - - parentHash = "0xe58f33f9baf9305dc6f82b9f1934ea8f0ade2defb951258d50167028c780351f" -} - -localwitness = [ -] - -#localwitnesskeystore = [ -# "localwitnesskeystore.json" -#] - -block = { - needSyncCheck = true - maintenanceTimeInterval = 21600000 - proposalExpireTime = 259200000 // 3 day: 259200000(ms) -} - -# Transaction reference block, default is "head", configure to "solid" can avoid TaPos error -# trx.reference.block = "head" // head;solid; - -vm = { - supportConstant = false - minTimeRatio = 0.0 - maxTimeRatio = 5.0 - - # In rare cases, transactions that will be within the specified maximum execution time (default 10(ms)) are re-executed and packaged - # longRunningTime = 10 -} - -committee = { - allowCreationOfContracts = 0 //mainnet:0 (reset by committee),test:1 - allowAdaptiveEnergy = 0 //mainnet:0 (reset by committee),test:1 -} - -log.level = { - root = "INFO" // TRACE;DEBUG;INFO;WARN;ERROR -} diff --git a/gradle/verification-metadata.xml b/gradle/verification-metadata.xml index 11c585cab6c..8c55e3b52b0 100644 --- a/gradle/verification-metadata.xml +++ b/gradle/verification-metadata.xml @@ -1789,6 +1789,9 @@ + + + @@ -1874,12 +1877,12 @@ - - - + + + - - + + diff --git a/plugins/src/test/java/org/tron/plugins/DbLiteTest.java b/plugins/src/test/java/org/tron/plugins/DbLiteTest.java index f7cb7b7f74f..960c1414769 100644 --- a/plugins/src/test/java/org/tron/plugins/DbLiteTest.java +++ b/plugins/src/test/java/org/tron/plugins/DbLiteTest.java @@ -18,7 +18,6 @@ import org.tron.common.application.Application; import org.tron.common.application.ApplicationFactory; import org.tron.common.application.TronApplicationContext; -import org.tron.common.config.DbBackupConfig; import org.tron.common.crypto.ECKey; import org.tron.common.utils.FileUtil; import org.tron.common.utils.PublicMethod; @@ -82,8 +81,6 @@ public void init(String dbType, boolean historyBalanceLookup) throws IOException Args.getInstance().setRpcEnable(true); Args.getInstance().setHistoryBalanceLookup(historyBalanceLookup); databaseDir = Args.getInstance().getStorage().getDbDirectory(); - // init dbBackupConfig to avoid NPE - Args.getInstance().dbBackupConfig = DbBackupConfig.getInstance(); } @After diff --git a/prop.properties b/prop.properties deleted file mode 100644 index e69de29bb2d..00000000000