/******************************************************************************
* Copyright © 2013-2016 The Nxt Core Developers. *
* *
* See the AUTHORS.txt, DEVELOPER-AGREEMENT.txt and LICENSE.txt files at *
* the top-level directory of this distribution for the individual copyright *
* holder information and the developer policies on copyright and licensing. *
* *
* Unless otherwise agreed in a custom licensing agreement, no part of the *
* Nxt software, including this file, may be copied, modified, propagated, *
* or distributed except according to the terms contained in the LICENSE.txt *
* file. *
* *
* Removal or modification of this copyright notice is prohibited. *
* *
******************************************************************************/
package nxt.peer;
import nxt.Account;
import nxt.Block;
import nxt.Constants;
import nxt.Db;
import nxt.Nxt;
import nxt.Transaction;
import nxt.http.API;
import nxt.util.Convert;
import nxt.util.Filter;
import nxt.util.JSON;
import nxt.util.Listener;
import nxt.util.Listeners;
import nxt.util.Logger;
import nxt.util.QueuedThreadPool;
import nxt.util.ThreadPool;
import nxt.util.UPnP;
import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.server.handler.gzip.GzipHandler;
import org.eclipse.jetty.servlet.FilterHolder;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.eclipse.jetty.servlets.DoSFilter;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import org.json.simple.JSONStreamAware;
import javax.servlet.DispatcherType;
import java.net.InetAddress;
import java.net.InterfaceAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.PriorityQueue;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
public final class Peers {
public enum Event {
BLACKLIST, UNBLACKLIST, DEACTIVATE, REMOVE,
DOWNLOADED_VOLUME, UPLOADED_VOLUME, WEIGHT,
ADDED_ACTIVE_PEER, CHANGED_ACTIVE_PEER,
NEW_PEER, ADD_INBOUND, REMOVE_INBOUND, CHANGED_SERVICES
}
static final int LOGGING_MASK_EXCEPTIONS = 1;
static final int LOGGING_MASK_NON200_RESPONSES = 2;
static final int LOGGING_MASK_200_RESPONSES = 4;
static volatile int communicationLoggingMask;
private static final List<String> wellKnownPeers;
static final Set<String> knownBlacklistedPeers;
static final int connectTimeout;
static final int readTimeout;
static final int blacklistingPeriod;
static final boolean getMorePeers;
static final int MAX_REQUEST_SIZE = 1024 * 1024;
static final int MAX_RESPONSE_SIZE = 1024 * 1024;
static final int MAX_MESSAGE_SIZE = 10 * 1024 * 1024;
public static final int MIN_COMPRESS_SIZE = 256;
static final boolean useWebSockets;
static final int webSocketIdleTimeout;
static final boolean useProxy = System.getProperty("socksProxyHost") != null || System.getProperty("http.proxyHost") != null;
static final boolean isGzipEnabled;
private static final int DEFAULT_PEER_PORT = 7874;
private static final int TESTNET_PEER_PORT = 6874;
private static final String myPlatform;
private static final String myAddress;
private static final int myPeerServerPort;
private static final String myHallmark;
private static final boolean shareMyAddress;
private static final boolean enablePeerUPnP;
private static final int maxNumberOfInboundConnections;
private static final int maxNumberOfOutboundConnections;
public static final int maxNumberOfConnectedPublicPeers;
private static final int maxNumberOfKnownPeers;
private static final int minNumberOfKnownPeers;
private static final boolean enableHallmarkProtection;
private static final int pushThreshold;
private static final int pullThreshold;
private static final int sendToPeersLimit;
private static final boolean usePeersDb;
private static final boolean savePeers;
static final boolean ignorePeerAnnouncedAddress;
static final boolean cjdnsOnly;
static final int MAX_VERSION_LENGTH = 10;
static final int MAX_APPLICATION_LENGTH = 20;
static final int MAX_PLATFORM_LENGTH = 30;
static final int MAX_ANNOUNCED_ADDRESS_LENGTH = 100;
static final boolean hideErrorDetails = Nxt.getBooleanProperty("nxt.hideErrorDetails");
static final JSONStreamAware myPeerInfoRequest;
static final JSONStreamAware myPeerInfoResponse;
private static final List<Peer.Service> myServices;
private static final Listeners<Peer,Event> listeners = new Listeners<>();
private static final ConcurrentMap<String, PeerImpl> peers = new ConcurrentHashMap<>();
private static final ConcurrentMap<String, String> selfAnnouncedAddresses = new ConcurrentHashMap<>();
static final Collection<PeerImpl> allPeers = Collections.unmodifiableCollection(peers.values());
static final ExecutorService peersService = new QueuedThreadPool(2, 15);
private static final ExecutorService sendingService = Executors.newFixedThreadPool(10);
static {
myPlatform = Nxt.getStringProperty("nxt.myPlatform");
if (myPlatform.length() > MAX_PLATFORM_LENGTH) {
throw new RuntimeException("nxt.myPlatform length exceeds " + MAX_PLATFORM_LENGTH);
}
myAddress = Convert.emptyToNull(Nxt.getStringProperty("nxt.myAddress", "").trim());
if (myAddress != null && myAddress.endsWith(":" + TESTNET_PEER_PORT) && !Constants.isTestnet) {
throw new RuntimeException("Port " + TESTNET_PEER_PORT + " should only be used for testnet!!!");
}
String myHost = null;
int myPort = -1;
if (myAddress != null) {
try {
URI uri = new URI("http://" + myAddress);
myHost = uri.getHost();
myPort = (uri.getPort() == -1 ? Peers.getDefaultPeerPort() : uri.getPort());
InetAddress[] myAddrs = InetAddress.getAllByName(myHost);
boolean addrValid = false;
Enumeration<NetworkInterface> intfs = NetworkInterface.getNetworkInterfaces();
chkAddr: while (intfs.hasMoreElements()) {
NetworkInterface intf = intfs.nextElement();
List<InterfaceAddress> intfAddrs = intf.getInterfaceAddresses();
for (InterfaceAddress intfAddr: intfAddrs) {
InetAddress extAddr = intfAddr.getAddress();
for (InetAddress myAddr : myAddrs) {
if (extAddr.equals(myAddr)) {
addrValid = true;
break chkAddr;
}
}
}
}
if (!addrValid) {
InetAddress extAddr = UPnP.getExternalAddress();
if (extAddr != null) {
for (InetAddress myAddr : myAddrs) {
if (extAddr.equals(myAddr)) {
addrValid = true;
break;
}
}
}
}
if (!addrValid) {
Logger.logWarningMessage("Your announced address does not match your external address");
}
} catch (SocketException e) {
Logger.logErrorMessage("Unable to enumerate the network interfaces :" + e.toString());
} catch (URISyntaxException | UnknownHostException e) {
Logger.logWarningMessage("Your announced address is not valid: " + e.toString());
}
}
myPeerServerPort = Nxt.getIntProperty("nxt.peerServerPort");
if (myPeerServerPort == TESTNET_PEER_PORT && !Constants.isTestnet) {
throw new RuntimeException("Port " + TESTNET_PEER_PORT + " should only be used for testnet!!!");
}
shareMyAddress = Nxt.getBooleanProperty("nxt.shareMyAddress") && ! Constants.isOffline;
enablePeerUPnP = Nxt.getBooleanProperty("nxt.enablePeerUPnP");
myHallmark = Nxt.getStringProperty("nxt.myHallmark");
if (Peers.myHallmark != null && Peers.myHallmark.length() > 0) {
try {
Hallmark hallmark = Hallmark.parseHallmark(Peers.myHallmark);
if (!hallmark.isValid()) {
throw new RuntimeException("Hallmark is not valid");
}
if (myAddress != null) {
if (!hallmark.getHost().equals(myHost)) {
throw new RuntimeException("Invalid hallmark host");
}
if (myPort != hallmark.getPort()) {
throw new RuntimeException("Invalid hallmark port");
}
}
} catch (RuntimeException e) {
Logger.logErrorMessage("Your hallmark is invalid: " + Peers.myHallmark + " for your address: " + myAddress);
throw new RuntimeException(e.toString(), e);
}
}
List<Peer.Service> servicesList = new ArrayList<>();
JSONObject json = new JSONObject();
if (myAddress != null) {
try {
URI uri = new URI("http://" + myAddress);
String host = uri.getHost();
int port = uri.getPort();
String announcedAddress;
if (!Constants.isTestnet) {
if (port >= 0)
announcedAddress = myAddress;
else
announcedAddress = host + (myPeerServerPort != DEFAULT_PEER_PORT ? ":" + myPeerServerPort : "");
} else {
announcedAddress = host;
}
if (announcedAddress == null || announcedAddress.length() > MAX_ANNOUNCED_ADDRESS_LENGTH) {
throw new RuntimeException("Invalid announced address length: " + announcedAddress);
}
json.put("announcedAddress", announcedAddress);
} catch (URISyntaxException e) {
Logger.logMessage("Your announce address is invalid: " + myAddress);
throw new RuntimeException(e.toString(), e);
}
}
if (Peers.myHallmark != null && Peers.myHallmark.length() > 0) {
json.put("hallmark", Peers.myHallmark);
servicesList.add(Peer.Service.HALLMARK);
}
json.put("application", Nxt.APPLICATION);
json.put("version", Nxt.VERSION);
json.put("platform", Peers.myPlatform);
json.put("shareAddress", Peers.shareMyAddress);
if (!Constants.ENABLE_PRUNING && Constants.INCLUDE_EXPIRED_PRUNABLE) {
servicesList.add(Peer.Service.PRUNABLE);
}
if (API.openAPIPort > 0) {
json.put("apiPort", API.openAPIPort);
servicesList.add(Peer.Service.API);
}
if (API.openAPISSLPort > 0) {
json.put("apiSSLPort", API.openAPISSLPort);
servicesList.add(Peer.Service.API_SSL);
}
long services = 0;
for (Peer.Service service : servicesList) {
services |= service.getCode();
}
json.put("services", Long.toUnsignedString(services));
myServices = Collections.unmodifiableList(servicesList);
Logger.logDebugMessage("My peer info:\n" + json.toJSONString());
myPeerInfoResponse = JSON.prepare(json);
json.put("requestType", "getInfo");
myPeerInfoRequest = JSON.prepareRequest(json);
final List<String> defaultPeers = Constants.isTestnet ? Nxt.getStringListProperty("nxt.defaultTestnetPeers")
: Nxt.getStringListProperty("nxt.defaultPeers");
wellKnownPeers = Collections.unmodifiableList(Constants.isTestnet ? Nxt.getStringListProperty("nxt.testnetPeers")
: Nxt.getStringListProperty("nxt.wellKnownPeers"));
List<String> knownBlacklistedPeersList = Nxt.getStringListProperty("nxt.knownBlacklistedPeers");
if (knownBlacklistedPeersList.isEmpty()) {
knownBlacklistedPeers = Collections.emptySet();
} else {
knownBlacklistedPeers = Collections.unmodifiableSet(new HashSet<>(knownBlacklistedPeersList));
}
maxNumberOfInboundConnections = Nxt.getIntProperty("nxt.maxNumberOfInboundConnections");
maxNumberOfOutboundConnections = Nxt.getIntProperty("nxt.maxNumberOfOutboundConnections");
maxNumberOfConnectedPublicPeers = Math.min(Nxt.getIntProperty("nxt.maxNumberOfConnectedPublicPeers"),
maxNumberOfOutboundConnections);
maxNumberOfKnownPeers = Nxt.getIntProperty("nxt.maxNumberOfKnownPeers");
minNumberOfKnownPeers = Nxt.getIntProperty("nxt.minNumberOfKnownPeers");
connectTimeout = Nxt.getIntProperty("nxt.connectTimeout");
readTimeout = Nxt.getIntProperty("nxt.readTimeout");
enableHallmarkProtection = Nxt.getBooleanProperty("nxt.enableHallmarkProtection");
pushThreshold = Nxt.getIntProperty("nxt.pushThreshold");
pullThreshold = Nxt.getIntProperty("nxt.pullThreshold");
useWebSockets = Nxt.getBooleanProperty("nxt.useWebSockets");
webSocketIdleTimeout = Nxt.getIntProperty("nxt.webSocketIdleTimeout");
isGzipEnabled = Nxt.getBooleanProperty("nxt.enablePeerServerGZIPFilter");
blacklistingPeriod = Nxt.getIntProperty("nxt.blacklistingPeriod") / 1000;
communicationLoggingMask = Nxt.getIntProperty("nxt.communicationLoggingMask");
sendToPeersLimit = Nxt.getIntProperty("nxt.sendToPeersLimit");
usePeersDb = Nxt.getBooleanProperty("nxt.usePeersDb") && ! Constants.isOffline;
savePeers = usePeersDb && Nxt.getBooleanProperty("nxt.savePeers");
getMorePeers = Nxt.getBooleanProperty("nxt.getMorePeers");
cjdnsOnly = Nxt.getBooleanProperty("nxt.cjdnsOnly");
ignorePeerAnnouncedAddress = Nxt.getBooleanProperty("nxt.ignorePeerAnnouncedAddress");
if (useWebSockets && useProxy) {
Logger.logMessage("Using a proxy, will not create outbound websockets.");
}
final List<Future<String>> unresolvedPeers = Collections.synchronizedList(new ArrayList<>());
if (!Constants.isOffline) {
ThreadPool.runBeforeStart(new Runnable() {
private final Set<PeerDb.Entry> entries = new HashSet<>();
@Override
public void run() {
final int now = Nxt.getEpochTime();
wellKnownPeers.forEach(address -> entries.add(new PeerDb.Entry(address, 0, now)));
if (usePeersDb) {
Logger.logDebugMessage("Loading known peers from the database...");
defaultPeers.forEach(address -> entries.add(new PeerDb.Entry(address, 0, now)));
if (savePeers) {
List<PeerDb.Entry> dbPeers = PeerDb.loadPeers();
dbPeers.forEach(entry -> {
if (!entries.add(entry)) {
// Database entries override entries from nxt.properties
entries.remove(entry);
entries.add(entry);
}
});
}
}
entries.forEach(entry -> {
Future<String> unresolvedAddress = peersService.submit(() -> {
PeerImpl peer = Peers.findOrCreatePeer(entry.getAddress(), true);
if (peer != null) {
peer.setLastUpdated(entry.getLastUpdated());
peer.setServices(entry.getServices());
Peers.addPeer(peer);
return null;
}
return entry.getAddress();
});
unresolvedPeers.add(unresolvedAddress);
});
}
}, false);
}
ThreadPool.runAfterStart(() -> {
for (Future<String> unresolvedPeer : unresolvedPeers) {
try {
String badAddress = unresolvedPeer.get(5, TimeUnit.SECONDS);
if (badAddress != null) {
Logger.logDebugMessage("Failed to resolve peer address: " + badAddress);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} catch (ExecutionException e) {
Logger.logDebugMessage("Failed to add peer", e);
} catch (TimeoutException ignore) {
}
}
Logger.logDebugMessage("Known peers: " + peers.size());
});
}
private static class Init {
private final static Server peerServer;
static {
if (Peers.shareMyAddress) {
peerServer = new Server();
ServerConnector connector = new ServerConnector(peerServer);
final int port = Constants.isTestnet ? TESTNET_PEER_PORT : Peers.myPeerServerPort;
connector.setPort(port);
final String host = Nxt.getStringProperty("nxt.peerServerHost");
connector.setHost(host);
connector.setIdleTimeout(Nxt.getIntProperty("nxt.peerServerIdleTimeout"));
connector.setReuseAddress(true);
peerServer.addConnector(connector);
ServletContextHandler ctxHandler = new ServletContextHandler();
ctxHandler.setContextPath("/");
ServletHolder peerServletHolder = new ServletHolder(new PeerServlet());
ctxHandler.addServlet(peerServletHolder, "/*");
if (Nxt.getBooleanProperty("nxt.enablePeerServerDoSFilter")) {
FilterHolder dosFilterHolder = ctxHandler.addFilter(DoSFilter.class, "/*",
EnumSet.of(DispatcherType.REQUEST));
dosFilterHolder.setInitParameter("maxRequestsPerSec", Nxt.getStringProperty("nxt.peerServerDoSFilter.maxRequestsPerSec"));
dosFilterHolder.setInitParameter("delayMs", Nxt.getStringProperty("nxt.peerServerDoSFilter.delayMs"));
dosFilterHolder.setInitParameter("maxRequestMs", Nxt.getStringProperty("nxt.peerServerDoSFilter.maxRequestMs"));
dosFilterHolder.setInitParameter("trackSessions", "false");
dosFilterHolder.setAsyncSupported(true);
}
if (isGzipEnabled) {
GzipHandler gzipHandler = new GzipHandler();
gzipHandler.setIncludedMethods("GET", "POST");
gzipHandler.setIncludedPaths("/*");
gzipHandler.setMinGzipSize(MIN_COMPRESS_SIZE);
ctxHandler.setGzipHandler(gzipHandler);
}
peerServer.setHandler(ctxHandler);
peerServer.setStopAtShutdown(true);
ThreadPool.runBeforeStart(() -> {
try {
if (enablePeerUPnP) {
Connector[] peerConnectors = peerServer.getConnectors();
for (Connector peerConnector : peerConnectors) {
if (peerConnector instanceof ServerConnector)
UPnP.addPort(((ServerConnector)peerConnector).getPort());
}
}
peerServer.start();
Logger.logMessage("Started peer networking server at " + host + ":" + port);
} catch (Exception e) {
Logger.logErrorMessage("Failed to start peer networking server", e);
throw new RuntimeException(e.toString(), e);
}
}, true);
} else {
peerServer = null;
Logger.logMessage("shareMyAddress is disabled, will not start peer networking server");
}
}
private static void init() {}
private Init() {}
}
private static final Runnable peerUnBlacklistingThread = () -> {
try {
try {
int curTime = Nxt.getEpochTime();
for (PeerImpl peer : peers.values()) {
peer.updateBlacklistedStatus(curTime);
}
} catch (Exception e) {
Logger.logDebugMessage("Error un-blacklisting peer", e);
}
} catch (Throwable t) {
Logger.logErrorMessage("CRITICAL ERROR. PLEASE REPORT TO THE DEVELOPERS", t);
System.exit(1);
}
};
private static final Runnable peerConnectingThread = new Runnable() {
@Override
public void run() {
try {
try {
final int now = Nxt.getEpochTime();
if (!hasEnoughConnectedPublicPeers(Peers.maxNumberOfConnectedPublicPeers)) {
List<Future<?>> futures = new ArrayList<>();
List<Peer> hallmarkedPeers = getPeers(peer -> !peer.isBlacklisted()
&& peer.getAnnouncedAddress() != null
&& peer.getState() != Peer.State.CONNECTED
&& now - peer.getLastConnectAttempt() > 600
&& peer.providesService(Peer.Service.HALLMARK));
List<Peer> nonhallmarkedPeers = getPeers(peer -> !peer.isBlacklisted()
&& peer.getAnnouncedAddress() != null
&& peer.getState() != Peer.State.CONNECTED
&& now - peer.getLastConnectAttempt() > 600
&& !peer.providesService(Peer.Service.HALLMARK));
if (!hallmarkedPeers.isEmpty() || !nonhallmarkedPeers.isEmpty()) {
Set<PeerImpl> connectSet = new HashSet<>();
for (int i = 0; i < 10; i++) {
List<Peer> peerList;
if (hallmarkedPeers.isEmpty()) {
peerList = nonhallmarkedPeers;
} else if (nonhallmarkedPeers.isEmpty()) {
peerList = hallmarkedPeers;
} else {
peerList = (ThreadLocalRandom.current().nextInt(2) == 0 ? hallmarkedPeers : nonhallmarkedPeers);
}
connectSet.add((PeerImpl)peerList.get(ThreadLocalRandom.current().nextInt(peerList.size())));
}
connectSet.forEach(peer -> futures.add(peersService.submit(() -> {
peer.connect();
if (peer.getState() == Peer.State.CONNECTED &&
enableHallmarkProtection && peer.getWeight() == 0 &&
hasTooManyOutboundConnections()) {
Logger.logDebugMessage("Too many outbound connections, deactivating peer " + peer.getHost());
peer.deactivate();
}
return null;
})));
for (Future<?> future : futures) {
future.get();
}
}
}
peers.values().forEach(peer -> {
if (peer.getState() == Peer.State.CONNECTED
&& now - peer.getLastUpdated() > 3600
&& now - peer.getLastConnectAttempt() > 600) {
peersService.submit(peer::connect);
}
if (peer.getLastInboundRequest() != 0 &&
now - peer.getLastInboundRequest() > Peers.webSocketIdleTimeout / 1000) {
peer.setLastInboundRequest(0);
notifyListeners(peer, Event.REMOVE_INBOUND);
}
});
if (hasTooManyKnownPeers() && hasEnoughConnectedPublicPeers(Peers.maxNumberOfConnectedPublicPeers)) {
int initialSize = peers.size();
for (PeerImpl peer : peers.values()) {
if (now - peer.getLastUpdated() > 24 * 3600) {
peer.remove();
}
if (hasTooFewKnownPeers()) {
break;
}
}
if (hasTooManyKnownPeers()) {
PriorityQueue<PeerImpl> sortedPeers = new PriorityQueue<>(peers.values());
int skipped = 0;
while (skipped < Peers.minNumberOfKnownPeers) {
if (sortedPeers.poll() == null) {
break;
}
skipped += 1;
}
while (!sortedPeers.isEmpty()) {
sortedPeers.poll().remove();
}
}
Logger.logDebugMessage("Reduced peer pool size from " + initialSize + " to " + peers.size());
}
for (String wellKnownPeer : wellKnownPeers) {
PeerImpl peer = findOrCreatePeer(wellKnownPeer, true);
if (peer != null && now - peer.getLastUpdated() > 3600 && now - peer.getLastConnectAttempt() > 600) {
peersService.submit(() -> {
addPeer(peer);
connectPeer(peer);
});
}
}
} catch (Exception e) {
Logger.logDebugMessage("Error connecting to peer", e);
}
} catch (Throwable t) {
Logger.logErrorMessage("CRITICAL ERROR. PLEASE REPORT TO THE DEVELOPERS", t);
System.exit(1);
}
}
};
private static final Runnable getMorePeersThread = new Runnable() {
private final JSONStreamAware getPeersRequest;
{
JSONObject request = new JSONObject();
request.put("requestType", "getPeers");
getPeersRequest = JSON.prepareRequest(request);
}
private volatile boolean updatedPeer;
@Override
public void run() {
try {
try {
if (hasTooManyKnownPeers()) {
return;
}
Peer peer = getAnyPeer(Peer.State.CONNECTED, true);
if (peer == null) {
return;
}
JSONObject response = peer.send(getPeersRequest, 10 * 1024 * 1024);
if (response == null) {
return;
}
JSONArray peers = (JSONArray)response.get("peers");
Set<String> addedAddresses = new HashSet<>();
if (peers != null) {
JSONArray services = (JSONArray)response.get("services");
boolean setServices = (services != null && services.size() == peers.size());
int now = Nxt.getEpochTime();
for (int i=0; i<peers.size(); i++) {
String announcedAddress = (String)peers.get(i);
PeerImpl newPeer = findOrCreatePeer(announcedAddress, true);
if (newPeer != null) {
if (now - newPeer.getLastUpdated() > 24 * 3600) {
newPeer.setLastUpdated(now);
updatedPeer = true;
}
if (Peers.addPeer(newPeer) && setServices) {
newPeer.setServices(Long.parseUnsignedLong((String)services.get(i)));
}
addedAddresses.add(announcedAddress);
if (hasTooManyKnownPeers()) {
break;
}
}
}
if (savePeers && updatedPeer) {
updateSavedPeers();
updatedPeer = false;
}
}
JSONArray myPeers = new JSONArray();
JSONArray myServices = new JSONArray();
Peers.getAllPeers().forEach(myPeer -> {
if (!myPeer.isBlacklisted() && myPeer.getAnnouncedAddress() != null
&& myPeer.getState() == Peer.State.CONNECTED && myPeer.shareAddress()
&& !addedAddresses.contains(myPeer.getAnnouncedAddress())
&& !myPeer.getAnnouncedAddress().equals(peer.getAnnouncedAddress())) {
myPeers.add(myPeer.getAnnouncedAddress());
myServices.add(Long.toUnsignedString(((PeerImpl) myPeer).getServices()));
}
});
if (myPeers.size() > 0) {
JSONObject request = new JSONObject();
request.put("requestType", "addPeers");
request.put("peers", myPeers);
request.put("services", myServices); // Separate array for backwards compatibility
peer.send(JSON.prepareRequest(request), 0);
}
} catch (Exception e) {
Logger.logDebugMessage("Error requesting peers from a peer", e);
}
} catch (Throwable t) {
Logger.logErrorMessage("CRITICAL ERROR. PLEASE REPORT TO THE DEVELOPERS", t);
System.exit(1);
}
}
private void updateSavedPeers() {
int now = Nxt.getEpochTime();
//
// Load the current database entries and map announced address to database entry
//
List<PeerDb.Entry> oldPeers = PeerDb.loadPeers();
Map<String, PeerDb.Entry> oldMap = new HashMap<>(oldPeers.size());
oldPeers.forEach(entry -> oldMap.put(entry.getAddress(), entry));
//
// Create the current peer map (note that there can be duplicate peer entries with
// the same announced address)
//
Map<String, PeerDb.Entry> currentPeers = new HashMap<>();
Peers.peers.values().forEach(peer -> {
if (peer.getAnnouncedAddress() != null && !peer.isBlacklisted() && now - peer.getLastUpdated() < 7*24*3600) {
currentPeers.put(peer.getAnnouncedAddress(),
new PeerDb.Entry(peer.getAnnouncedAddress(), peer.getServices(), peer.getLastUpdated()));
}
});
//
// Build toDelete and toUpdate lists
//
List<PeerDb.Entry> toDelete = new ArrayList<>(oldPeers.size());
oldPeers.forEach(entry -> {
if (currentPeers.get(entry.getAddress()) == null)
toDelete.add(entry);
});
List<PeerDb.Entry> toUpdate = new ArrayList<>(currentPeers.size());
currentPeers.values().forEach(entry -> {
PeerDb.Entry oldEntry = oldMap.get(entry.getAddress());
if (oldEntry == null || entry.getLastUpdated() - oldEntry.getLastUpdated() > 24*3600)
toUpdate.add(entry);
});
//
// Nothing to do if all of the lists are empty
//
if (toDelete.isEmpty() && toUpdate.isEmpty())
return;
//
// Update the peer database
//
try {
Db.db.beginTransaction();
PeerDb.deletePeers(toDelete);
PeerDb.updatePeers(toUpdate);
Db.db.commitTransaction();
} catch (Exception e) {
Db.db.rollbackTransaction();
throw e;
} finally {
Db.db.endTransaction();
}
}
};
static {
Peers.addListener(peer -> peersService.submit(() -> {
if (peer.getAnnouncedAddress() != null && !peer.isBlacklisted()) {
try {
Db.db.beginTransaction();
PeerDb.updatePeer((PeerImpl)peer);
Db.db.commitTransaction();
} catch (RuntimeException e) {
Logger.logErrorMessage("Unable to update peer database", e);
Db.db.rollbackTransaction();
} finally {
Db.db.endTransaction();
}
}
}), Peers.Event.CHANGED_SERVICES);
}
static {
Account.addListener(account -> peers.values().forEach(peer -> {
if (peer.getHallmark() != null && peer.getHallmark().getAccountId() == account.getId()) {
Peers.listeners.notify(peer, Event.WEIGHT);
}
}), Account.Event.BALANCE);
}
static {
if (! Constants.isOffline) {
ThreadPool.scheduleThread("PeerConnecting", Peers.peerConnectingThread, 20);
ThreadPool.scheduleThread("PeerUnBlacklisting", Peers.peerUnBlacklistingThread, 60);
if (Peers.getMorePeers) {
ThreadPool.scheduleThread("GetMorePeers", Peers.getMorePeersThread, 20);
}
}
}
public static void init() {
Init.init();
}
public static void shutdown() {
if (Init.peerServer != null) {
try {
Init.peerServer.stop();
if (enablePeerUPnP) {
Connector[] peerConnectors = Init.peerServer.getConnectors();
for (Connector peerConnector : peerConnectors) {
if (peerConnector instanceof ServerConnector)
UPnP.deletePort(((ServerConnector)peerConnector).getPort());
}
}
} catch (Exception e) {
Logger.logShutdownMessage("Failed to stop peer server", e);
}
}
ThreadPool.shutdownExecutor("sendingService", sendingService, 2);
ThreadPool.shutdownExecutor("peersService", peersService, 5);
}
public static boolean addListener(Listener<Peer> listener, Event eventType) {
return Peers.listeners.addListener(listener, eventType);
}
public static boolean removeListener(Listener<Peer> listener, Event eventType) {
return Peers.listeners.removeListener(listener, eventType);
}
static void notifyListeners(Peer peer, Event eventType) {
Peers.listeners.notify(peer, eventType);
}
public static int getDefaultPeerPort() {
return Constants.isTestnet ? TESTNET_PEER_PORT : DEFAULT_PEER_PORT;
}
public static Collection<? extends Peer> getAllPeers() {
return allPeers;
}
public static List<Peer> getActivePeers() {
return getPeers(peer -> peer.getState() != Peer.State.NON_CONNECTED);
}
public static List<Peer> getPeers(final Peer.State state) {
return getPeers(peer -> peer.getState() == state);
}
public static List<Peer> getPeers(Filter<Peer> filter) {
return getPeers(filter, Integer.MAX_VALUE);
}
public static List<Peer> getPeers(Filter<Peer> filter, int limit) {
List<Peer> result = new ArrayList<>();
for (Peer peer : peers.values()) {
if (filter.ok(peer)) {
result.add(peer);
if (result.size() >= limit) {
break;
}
}
}
return result;
}
public static Peer getPeer(String host) {
return peers.get(host);
}
public static List<Peer> getInboundPeers() {
return getPeers(Peer::isInbound);
}
public static boolean hasTooManyInboundPeers() {
return getPeers(Peer::isInbound, maxNumberOfInboundConnections).size() >= maxNumberOfInboundConnections;
}
public static boolean hasTooManyOutboundConnections() {
return getPeers(peer -> !peer.isBlacklisted() && peer.getState() == Peer.State.CONNECTED && peer.getAnnouncedAddress() != null,
maxNumberOfOutboundConnections).size() >= maxNumberOfOutboundConnections;
}
public static PeerImpl findOrCreatePeer(String announcedAddress, boolean create) {
if (announcedAddress == null) {
return null;
}
announcedAddress = announcedAddress.trim().toLowerCase();
PeerImpl peer;
if ((peer = peers.get(announcedAddress)) != null) {
return peer;
}
String host = selfAnnouncedAddresses.get(announcedAddress);
if (host != null && (peer = peers.get(host)) != null) {
return peer;
}
try {
URI uri = new URI("http://" + announcedAddress);
host = uri.getHost();
if (host == null) {
return null;
}
if ((peer = peers.get(host)) != null) {
return peer;
}
String host2 = selfAnnouncedAddresses.get(host);
if (host2 != null && (peer = peers.get(host2)) != null) {
return peer;
}
InetAddress inetAddress = InetAddress.getByName(host);
return findOrCreatePeer(inetAddress, addressWithPort(announcedAddress), create);
} catch (URISyntaxException | UnknownHostException e) {
//Logger.logDebugMessage("Invalid peer address: " + announcedAddress + ", " + e.toString());
return null;
}
}
static PeerImpl findOrCreatePeer(String host) {
try {
InetAddress inetAddress = InetAddress.getByName(host);
return findOrCreatePeer(inetAddress, null, true);
} catch (UnknownHostException e) {
return null;
}
}
static PeerImpl findOrCreatePeer(final InetAddress inetAddress, final String announcedAddress, final boolean create) {
if (inetAddress.isAnyLocalAddress() || inetAddress.isLoopbackAddress() || inetAddress.isLinkLocalAddress()) {
return null;
}
String host = inetAddress.getHostAddress();
if (Peers.cjdnsOnly && !host.substring(0,2).equals("fc")) {
return null;
}
//re-add the [] to ipv6 addresses lost in getHostAddress() above
if (host.split(":").length > 2) {
host = "[" + host + "]";
}
PeerImpl peer;
if ((peer = peers.get(host)) != null) {
return peer;
}
if (!create) {
return null;
}
if (Peers.myAddress != null && Peers.myAddress.equalsIgnoreCase(announcedAddress)) {
return null;
}
if (announcedAddress != null && announcedAddress.length() > MAX_ANNOUNCED_ADDRESS_LENGTH) {
return null;
}
peer = new PeerImpl(host, announcedAddress);
if (Constants.isTestnet && peer.getPort() != TESTNET_PEER_PORT) {
Logger.logDebugMessage("Peer " + host + " on testnet is not using port " + TESTNET_PEER_PORT + ", ignoring");
return null;
}
if (!Constants.isTestnet && peer.getPort() == TESTNET_PEER_PORT) {
Logger.logDebugMessage("Peer " + host + " is using testnet port " + peer.getPort() + ", ignoring");
return null;
}
return peer;
}
static void setAnnouncedAddress(PeerImpl peer, String newAnnouncedAddress) {
Peer oldPeer = peers.get(peer.getHost());
if (oldPeer != null) {
String oldAnnouncedAddress = oldPeer.getAnnouncedAddress();
if (oldAnnouncedAddress != null && !oldAnnouncedAddress.equals(newAnnouncedAddress)) {
Logger.logDebugMessage("Removing old announced address " + oldAnnouncedAddress + " for peer " + oldPeer.getHost());
selfAnnouncedAddresses.remove(oldAnnouncedAddress);
}
}
if (newAnnouncedAddress != null) {
String oldHost = selfAnnouncedAddresses.put(newAnnouncedAddress, peer.getHost());
if (oldHost != null && !peer.getHost().equals(oldHost)) {
Logger.logDebugMessage("Announced address " + newAnnouncedAddress + " now maps to peer " + peer.getHost()
+ ", removing old peer " + oldHost);
oldPeer = peers.remove(oldHost);
if (oldPeer != null) {
Peers.notifyListeners(oldPeer, Event.REMOVE);
}
}
}
peer.setAnnouncedAddress(newAnnouncedAddress);
}
public static boolean addPeer(Peer peer, String newAnnouncedAddress) {
setAnnouncedAddress((PeerImpl)peer, newAnnouncedAddress.toLowerCase());
return addPeer(peer);
}
public static boolean addPeer(Peer peer) {
if (peers.put(peer.getHost(), (PeerImpl) peer) == null) {
listeners.notify(peer, Event.NEW_PEER);
return true;
}
return false;
}
public static PeerImpl removePeer(Peer peer) {
if (peer.getAnnouncedAddress() != null) {
selfAnnouncedAddresses.remove(peer.getAnnouncedAddress());
}
return peers.remove(peer.getHost());
}
public static void connectPeer(Peer peer) {
peer.unBlacklist();
((PeerImpl)peer).connect();
}
public static void sendToSomePeers(Block block) {
JSONObject request = block.getJSONObject();
request.put("requestType", "processBlock");
sendToSomePeers(request);
}
private static final int sendTransactionsBatchSize = 10;
public static void sendToSomePeers(List<? extends Transaction> transactions) {
int nextBatchStart = 0;
while (nextBatchStart < transactions.size()) {
JSONObject request = new JSONObject();
JSONArray transactionsData = new JSONArray();
for (int i = nextBatchStart; i < nextBatchStart + sendTransactionsBatchSize && i < transactions.size(); i++) {
transactionsData.add(transactions.get(i).getJSONObject());
}
request.put("requestType", "processTransactions");
request.put("transactions", transactionsData);
sendToSomePeers(request);
nextBatchStart += sendTransactionsBatchSize;
}
}
private static void sendToSomePeers(final JSONObject request) {
sendingService.submit(() -> {
final JSONStreamAware jsonRequest = JSON.prepareRequest(request);
int successful = 0;
List<Future<JSONObject>> expectedResponses = new ArrayList<>();
for (final Peer peer : peers.values()) {
if (Peers.enableHallmarkProtection && peer.getWeight() < Peers.pushThreshold) {
continue;
}
if (!peer.isBlacklisted() && peer.getState() == Peer.State.CONNECTED && peer.getAnnouncedAddress() != null) {
Future<JSONObject> futureResponse = peersService.submit(() -> peer.send(jsonRequest));
expectedResponses.add(futureResponse);
}
if (expectedResponses.size() >= Peers.sendToPeersLimit - successful) {
for (Future<JSONObject> future : expectedResponses) {
try {
JSONObject response = future.get();
if (response != null && response.get("error") == null) {
successful += 1;
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} catch (ExecutionException e) {
Logger.logDebugMessage("Error in sendToSomePeers", e);
}
}
expectedResponses.clear();
}
if (successful >= Peers.sendToPeersLimit) {
return;
}
}
});
}
public static Peer getAnyPeer(final Peer.State state, final boolean applyPullThreshold) {
return getWeightedPeer(getPublicPeers(state, applyPullThreshold));
}
public static List<Peer> getPublicPeers(final Peer.State state, final boolean applyPullThreshold) {
return getPeers(peer -> !peer.isBlacklisted() && peer.getState() == state && peer.getAnnouncedAddress() != null
&& (!applyPullThreshold || !Peers.enableHallmarkProtection || peer.getWeight() >= Peers.pullThreshold));
}
public static Peer getWeightedPeer(List<Peer> selectedPeers) {
if (selectedPeers.isEmpty()) {
return null;
}
if (! Peers.enableHallmarkProtection || ThreadLocalRandom.current().nextInt(3) == 0) {
return selectedPeers.get(ThreadLocalRandom.current().nextInt(selectedPeers.size()));
}
long totalWeight = 0;
for (Peer peer : selectedPeers) {
long weight = peer.getWeight();
if (weight == 0) {
weight = 1;
}
totalWeight += weight;
}
long hit = ThreadLocalRandom.current().nextLong(totalWeight);
for (Peer peer : selectedPeers) {
long weight = peer.getWeight();
if (weight == 0) {
weight = 1;
}
if ((hit -= weight) < 0) {
return peer;
}
}
return null;
}
static String addressWithPort(String address) {
if (address == null) {
return null;
}
try {
URI uri = new URI("http://" + address);
String host = uri.getHost();
int port = uri.getPort();
return port > 0 && port != Peers.getDefaultPeerPort() ? host + ":" + port : host;
} catch (URISyntaxException e) {
return null;
}
}
public static boolean hasTooFewKnownPeers() {
return peers.size() < Peers.minNumberOfKnownPeers;
}
public static boolean hasTooManyKnownPeers() {
return peers.size() > Peers.maxNumberOfKnownPeers;
}
private static boolean hasEnoughConnectedPublicPeers(int limit) {
return getPeers(peer -> !peer.isBlacklisted() && peer.getState() == Peer.State.CONNECTED && peer.getAnnouncedAddress() != null
&& (! Peers.enableHallmarkProtection || peer.getWeight() > 0), limit).size() >= limit;
}
/**
* Set the communication logging mask
*
* @param events Communication event list or null to reset communications logging
* @return TRUE if the communication logging mask was updated
*/
public static boolean setCommunicationLoggingMask(String[] events) {
boolean updated = true;
int mask = 0;
if (events != null) {
for (String event : events) {
switch (event) {
case "EXCEPTION":
mask |= LOGGING_MASK_EXCEPTIONS;
break;
case "HTTP-ERROR":
mask |= LOGGING_MASK_NON200_RESPONSES;
break;
case "HTTP-OK":
mask |= LOGGING_MASK_200_RESPONSES;
break;
default:
updated = false;
}
if (!updated)
break;
}
}
if (updated)
communicationLoggingMask = mask;
return updated;
}
/**
* Return local peer services
*
* @return List of local peer services
*/
public static List<Peer.Service> getServices() {
return myServices;
}
private Peers() {} // never
}