diff --git a/src/main/java/org/qortal/network/Network.java b/src/main/java/org/qortal/network/Network.java index 0ef7d5a0..dd7cb01f 100644 --- a/src/main/java/org/qortal/network/Network.java +++ b/src/main/java/org/qortal/network/Network.java @@ -80,6 +80,8 @@ public class Network { public static final int MAX_SIGNATURES_PER_REPLY = 500; public static final int MAX_BLOCK_SUMMARIES_PER_REPLY = 500; + private static final long DISCONNECTION_CHECK_INTERVAL = 10 * 1000L; // milliseconds + // Generate our node keys / ID private final Ed25519PrivateKeyParameters edPrivateKeyParams = new Ed25519PrivateKeyParameters(new SecureRandom()); private final Ed25519PublicKeyParameters edPublicKeyParams = edPrivateKeyParams.generatePublicKey(); @@ -89,6 +91,8 @@ public class Network { private final int minOutboundPeers; private final int maxPeers; + private long nextDisconnectionCheck = 0L; + private final List allKnownPeers = new ArrayList<>(); private final List connectedPeers = new ArrayList<>(); private final List selfPeers = new ArrayList<>(); @@ -576,6 +580,8 @@ public class Network { // Don't consider already connected peers (resolved address match) // XXX This might be too slow if we end up waiting a long time for hostnames to resolve via DNS peers.removeIf(isResolvedAsConnectedPeer); + + this.checkLongestConnection(now); } // Any left? @@ -633,6 +639,24 @@ public class Network { return null; } + private void checkLongestConnection(Long now) { + if (now == null || now < nextDisconnectionCheck) { + return; + } + + // Find peers that have reached their maximum connection age, and disconnect them + List peersToDisconnect = this.connectedPeers.stream().filter(peer -> peer.hasReachedMaxConnectionAge()).collect(Collectors.toList()); + if (peersToDisconnect != null && peersToDisconnect.size() > 0) { + for (Peer peer : peersToDisconnect) { + LOGGER.debug("Forcing disconnect of peer {} because connection age ({} ms) has reached the maximum ({} ms)", peer, peer.getConnectionAge(), peer.getMaxConnectionAge()); + peer.disconnect("Connection age too old"); + } + } + + // Check again after a minimum fixed interval + nextDisconnectionCheck = now + DISCONNECTION_CHECK_INTERVAL; + } + // Peer callbacks protected void wakeupChannelSelector() { diff --git a/src/main/java/org/qortal/network/Peer.java b/src/main/java/org/qortal/network/Peer.java index c84d1118..66ef3ae0 100644 --- a/src/main/java/org/qortal/network/Peer.java +++ b/src/main/java/org/qortal/network/Peer.java @@ -79,6 +79,7 @@ public class Peer { private Handshake handshakeStatus = Handshake.STARTED; private volatile boolean handshakeMessagePending = false; private long handshakeComplete = -1L; + private long maxConnectionAge = 0L; /** * Timestamp of when socket was accepted, or connected. @@ -192,10 +193,24 @@ public class Peer { this.handshakeStatus = handshakeStatus; if (handshakeStatus.equals(Handshake.COMPLETED)) { this.handshakeComplete = System.currentTimeMillis(); + this.generateRandomMaxConnectionAge(); } } } + private void generateRandomMaxConnectionAge() { + // Retrieve the min and max connection time from the settings, and calculate the range + final int minPeerConnectionTime = Settings.getInstance().getMinPeerConnectionTime(); + final int maxPeerConnectionTime = Settings.getInstance().getMaxPeerConnectionTime(); + final int peerConnectionTimeRange = maxPeerConnectionTime - minPeerConnectionTime; + + // Generate a random number between the min and the max + Random random = new Random(); + this.maxConnectionAge = (random.nextInt(peerConnectionTimeRange) + minPeerConnectionTime) * 1000L; + LOGGER.debug(String.format("Generated max connection age for peer %s. Min: %ds, max: %ds, range: %ds, random max: %dms", this, minPeerConnectionTime, maxPeerConnectionTime, peerConnectionTimeRange, this.maxConnectionAge)); + + } + protected void resetHandshakeMessagePending() { this.handshakeMessagePending = false; } @@ -777,4 +792,12 @@ public class Peer { } return handshakeComplete; } + + public long getMaxConnectionAge() { + return maxConnectionAge; + } + + public boolean hasReachedMaxConnectionAge() { + return this.getConnectionAge() > this.getMaxConnectionAge(); + } } diff --git a/src/main/java/org/qortal/settings/Settings.java b/src/main/java/org/qortal/settings/Settings.java index 164223c9..7f67195e 100644 --- a/src/main/java/org/qortal/settings/Settings.java +++ b/src/main/java/org/qortal/settings/Settings.java @@ -132,6 +132,11 @@ public class Settings { * If false, sync will be blocked both ways, and they will not appear in the peers list */ private boolean allowConnectionsWithOlderPeerVersions = true; + /** Minimum time (in seconds) that we should attempt to remain connected to a peer for */ + private int minPeerConnectionTime = 2 * 60; + /** Maximum time (in seconds) that we should attempt to remain connected to a peer for */ + private int maxPeerConnectionTime = 60 * 60; + // Which blockchains this node is running private String blockchainConfig = null; // use default from resources private BitcoinNet bitcoinNet = BitcoinNet.MAIN; @@ -423,6 +428,10 @@ public class Settings { public boolean getAllowConnectionsWithOlderPeerVersions() { return this.allowConnectionsWithOlderPeerVersions; } + public int getMinPeerConnectionTime() { return this.minPeerConnectionTime; } + + public int getMaxPeerConnectionTime() { return this.maxPeerConnectionTime; } + public String getBlockchainConfig() { return this.blockchainConfig; }