/******************************************************************************
* 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.user;
import nxt.Account;
import nxt.Block;
import nxt.BlockchainProcessor;
import nxt.Constants;
import nxt.Generator;
import nxt.Nxt;
import nxt.Transaction;
import nxt.TransactionProcessor;
import nxt.peer.Peer;
import nxt.peer.Peers;
import nxt.util.Convert;
import nxt.util.Logger;
import nxt.util.ThreadPool;
import org.eclipse.jetty.server.HttpConfiguration;
import org.eclipse.jetty.server.HttpConnectionFactory;
import org.eclipse.jetty.server.SecureRequestCustomizer;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.server.SslConnectionFactory;
import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.server.handler.DefaultHandler;
import org.eclipse.jetty.server.handler.HandlerList;
import org.eclipse.jetty.server.handler.ResourceHandler;
import org.eclipse.jetty.servlet.FilterHolder;
import org.eclipse.jetty.servlet.FilterMapping;
import org.eclipse.jetty.servlet.ServletHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.eclipse.jetty.servlets.CrossOriginFilter;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import org.json.simple.JSONStreamAware;
import java.math.BigInteger;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicInteger;
public final class Users {
private static final int TESTNET_UI_PORT=2875;
private static final ConcurrentMap<String, User> users = new ConcurrentHashMap<>();
private static final Collection<User> allUsers = Collections.unmodifiableCollection(users.values());
private static final AtomicInteger peerCounter = new AtomicInteger();
private static final ConcurrentMap<String, Integer> peerIndexMap = new ConcurrentHashMap<>();
private static final ConcurrentMap<Integer, String> peerAddressMap = new ConcurrentHashMap<>();
private static final AtomicInteger blockCounter = new AtomicInteger();
private static final ConcurrentMap<Long, Integer> blockIndexMap = new ConcurrentHashMap<>();
private static final AtomicInteger transactionCounter = new AtomicInteger();
private static final ConcurrentMap<Long, Integer> transactionIndexMap = new ConcurrentHashMap<>();
static final Set<String> allowedUserHosts;
private static final Server userServer;
static {
List<String> allowedUserHostsList = Nxt.getStringListProperty("nxt.allowedUserHosts");
if (! allowedUserHostsList.contains("*")) {
allowedUserHosts = Collections.unmodifiableSet(new HashSet<>(allowedUserHostsList));
} else {
allowedUserHosts = null;
}
boolean enableUIServer = Nxt.getBooleanProperty("nxt.enableUIServer");
if (enableUIServer) {
final int port = Constants.isTestnet ? TESTNET_UI_PORT : Nxt.getIntProperty("nxt.uiServerPort");
final String host = Nxt.getStringProperty("nxt.uiServerHost");
userServer = new Server();
ServerConnector connector;
boolean enableSSL = Nxt.getBooleanProperty("nxt.uiSSL");
if (enableSSL) {
Logger.logMessage("Using SSL (https) for the user interface server");
HttpConfiguration https_config = new HttpConfiguration();
https_config.setSecureScheme("https");
https_config.setSecurePort(port);
https_config.addCustomizer(new SecureRequestCustomizer());
SslContextFactory sslContextFactory = new SslContextFactory();
sslContextFactory.setKeyStorePath(Nxt.getStringProperty("nxt.keyStorePath"));
sslContextFactory.setKeyStorePassword(Nxt.getStringProperty("nxt.keyStorePassword", null, true));
sslContextFactory.setExcludeCipherSuites("SSL_RSA_WITH_DES_CBC_SHA", "SSL_DHE_RSA_WITH_DES_CBC_SHA",
"SSL_DHE_DSS_WITH_DES_CBC_SHA", "SSL_RSA_EXPORT_WITH_RC4_40_MD5", "SSL_RSA_EXPORT_WITH_DES40_CBC_SHA",
"SSL_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA", "SSL_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA");
sslContextFactory.setExcludeProtocols("SSLv3");
connector = new ServerConnector(userServer, new SslConnectionFactory(sslContextFactory, "http/1.1"),
new HttpConnectionFactory(https_config));
} else {
connector = new ServerConnector(userServer);
}
connector.setPort(port);
connector.setHost(host);
connector.setIdleTimeout(Nxt.getIntProperty("nxt.uiServerIdleTimeout"));
connector.setReuseAddress(true);
userServer.addConnector(connector);
HandlerList userHandlers = new HandlerList();
ResourceHandler userFileHandler = new ResourceHandler();
userFileHandler.setDirectoriesListed(false);
userFileHandler.setWelcomeFiles(new String[]{"index.html"});
userFileHandler.setResourceBase(Nxt.getStringProperty("nxt.uiResourceBase"));
userHandlers.addHandler(userFileHandler);
String javadocResourceBase = Nxt.getStringProperty("nxt.javadocResourceBase");
if (javadocResourceBase != null) {
ContextHandler contextHandler = new ContextHandler("/doc");
ResourceHandler docFileHandler = new ResourceHandler();
docFileHandler.setDirectoriesListed(false);
docFileHandler.setWelcomeFiles(new String[]{"index.html"});
docFileHandler.setResourceBase(javadocResourceBase);
contextHandler.setHandler(docFileHandler);
userHandlers.addHandler(contextHandler);
}
ServletHandler userHandler = new ServletHandler();
ServletHolder userHolder = userHandler.addServletWithMapping(UserServlet.class, "/nxt");
userHolder.setAsyncSupported(true);
if (Nxt.getBooleanProperty("nxt.uiServerCORS")) {
FilterHolder filterHolder = userHandler.addFilterWithMapping(CrossOriginFilter.class, "/*", FilterMapping.DEFAULT);
filterHolder.setInitParameter("allowedHeaders", "*");
filterHolder.setAsyncSupported(true);
}
userHandlers.addHandler(userHandler);
userHandlers.addHandler(new DefaultHandler());
userServer.setHandler(userHandlers);
userServer.setStopAtShutdown(true);
ThreadPool.runBeforeStart(() -> {
try {
userServer.start();
Logger.logMessage("Started user interface server at " + host + ":" + port);
} catch (Exception e) {
Logger.logErrorMessage("Failed to start user interface server", e);
throw new RuntimeException(e.toString(), e);
}
}, true);
} else {
userServer = null;
Logger.logMessage("User interface server not enabled");
}
}
static {
if (userServer != null) {
Account.addListener(account -> {
JSONObject response = new JSONObject();
response.put("response", "setBalance");
response.put("balanceNQT", account.getUnconfirmedBalanceNQT());
byte[] accountPublicKey = account.getPublicKey();
Users.users.values().forEach(user -> {
if (user.getSecretPhrase() != null && Arrays.equals(user.getPublicKey(), accountPublicKey)) {
user.send(response);
}
});
}, Account.Event.UNCONFIRMED_BALANCE);
Peers.addListener(peer -> {
JSONObject response = new JSONObject();
JSONArray removedActivePeers = new JSONArray();
JSONObject removedActivePeer = new JSONObject();
removedActivePeer.put("index", Users.getIndex(peer));
removedActivePeers.add(removedActivePeer);
response.put("removedActivePeers", removedActivePeers);
JSONArray removedKnownPeers = new JSONArray();
JSONObject removedKnownPeer = new JSONObject();
removedKnownPeer.put("index", Users.getIndex(peer));
removedKnownPeers.add(removedKnownPeer);
response.put("removedKnownPeers", removedKnownPeers);
JSONArray addedBlacklistedPeers = new JSONArray();
JSONObject addedBlacklistedPeer = new JSONObject();
addedBlacklistedPeer.put("index", Users.getIndex(peer));
addedBlacklistedPeer.put("address", peer.getHost());
addedBlacklistedPeer.put("announcedAddress", Convert.truncate(peer.getAnnouncedAddress(), "-", 25, true));
addedBlacklistedPeer.put("software", peer.getSoftware());
addedBlacklistedPeers.add(addedBlacklistedPeer);
response.put("addedBlacklistedPeers", addedBlacklistedPeers);
Users.sendNewDataToAll(response);
}, Peers.Event.BLACKLIST);
Peers.addListener(peer -> {
JSONObject response = new JSONObject();
JSONArray removedActivePeers = new JSONArray();
JSONObject removedActivePeer = new JSONObject();
removedActivePeer.put("index", Users.getIndex(peer));
removedActivePeers.add(removedActivePeer);
response.put("removedActivePeers", removedActivePeers);
JSONArray addedKnownPeers = new JSONArray();
JSONObject addedKnownPeer = new JSONObject();
addedKnownPeer.put("index", Users.getIndex(peer));
addedKnownPeer.put("address", peer.getHost());
addedKnownPeer.put("announcedAddress", Convert.truncate(peer.getAnnouncedAddress(), "-", 25, true));
addedKnownPeer.put("software", peer.getSoftware());
addedKnownPeers.add(addedKnownPeer);
response.put("addedKnownPeers", addedKnownPeers);
Users.sendNewDataToAll(response);
}, Peers.Event.DEACTIVATE);
Peers.addListener(peer -> {
JSONObject response = new JSONObject();
JSONArray removedBlacklistedPeers = new JSONArray();
JSONObject removedBlacklistedPeer = new JSONObject();
removedBlacklistedPeer.put("index", Users.getIndex(peer));
removedBlacklistedPeers.add(removedBlacklistedPeer);
response.put("removedBlacklistedPeers", removedBlacklistedPeers);
JSONArray addedKnownPeers = new JSONArray();
JSONObject addedKnownPeer = new JSONObject();
addedKnownPeer.put("index", Users.getIndex(peer));
addedKnownPeer.put("address", peer.getHost());
addedKnownPeer.put("announcedAddress", Convert.truncate(peer.getAnnouncedAddress(), "-", 25, true));
addedKnownPeer.put("software", peer.getSoftware());
addedKnownPeers.add(addedKnownPeer);
response.put("addedKnownPeers", addedKnownPeers);
Users.sendNewDataToAll(response);
}, Peers.Event.UNBLACKLIST);
Peers.addListener(peer -> {
JSONObject response = new JSONObject();
JSONArray removedKnownPeers = new JSONArray();
JSONObject removedKnownPeer = new JSONObject();
removedKnownPeer.put("index", Users.getIndex(peer));
removedKnownPeers.add(removedKnownPeer);
response.put("removedKnownPeers", removedKnownPeers);
Users.sendNewDataToAll(response);
}, Peers.Event.REMOVE);
Peers.addListener(peer -> {
JSONObject response = new JSONObject();
JSONArray changedActivePeers = new JSONArray();
JSONObject changedActivePeer = new JSONObject();
changedActivePeer.put("index", Users.getIndex(peer));
changedActivePeer.put("downloaded", peer.getDownloadedVolume());
changedActivePeers.add(changedActivePeer);
response.put("changedActivePeers", changedActivePeers);
Users.sendNewDataToAll(response);
}, Peers.Event.DOWNLOADED_VOLUME);
Peers.addListener(peer -> {
JSONObject response = new JSONObject();
JSONArray changedActivePeers = new JSONArray();
JSONObject changedActivePeer = new JSONObject();
changedActivePeer.put("index", Users.getIndex(peer));
changedActivePeer.put("uploaded", peer.getUploadedVolume());
changedActivePeers.add(changedActivePeer);
response.put("changedActivePeers", changedActivePeers);
Users.sendNewDataToAll(response);
}, Peers.Event.UPLOADED_VOLUME);
Peers.addListener(peer -> {
JSONObject response = new JSONObject();
JSONArray changedActivePeers = new JSONArray();
JSONObject changedActivePeer = new JSONObject();
changedActivePeer.put("index", Users.getIndex(peer));
changedActivePeer.put("weight", peer.getWeight());
changedActivePeers.add(changedActivePeer);
response.put("changedActivePeers", changedActivePeers);
Users.sendNewDataToAll(response);
}, Peers.Event.WEIGHT);
Peers.addListener(peer -> {
JSONObject response = new JSONObject();
JSONArray removedKnownPeers = new JSONArray();
JSONObject removedKnownPeer = new JSONObject();
removedKnownPeer.put("index", Users.getIndex(peer));
removedKnownPeers.add(removedKnownPeer);
response.put("removedKnownPeers", removedKnownPeers);
JSONArray addedActivePeers = new JSONArray();
JSONObject addedActivePeer = new JSONObject();
addedActivePeer.put("index", Users.getIndex(peer));
if (peer.getState() != Peer.State.CONNECTED) {
addedActivePeer.put("disconnected", true);
}
addedActivePeer.put("address", peer.getHost());
addedActivePeer.put("announcedAddress", Convert.truncate(peer.getAnnouncedAddress(), "-", 25, true));
addedActivePeer.put("weight", peer.getWeight());
addedActivePeer.put("downloaded", peer.getDownloadedVolume());
addedActivePeer.put("uploaded", peer.getUploadedVolume());
addedActivePeer.put("software", peer.getSoftware());
addedActivePeers.add(addedActivePeer);
response.put("addedActivePeers", addedActivePeers);
Users.sendNewDataToAll(response);
}, Peers.Event.ADDED_ACTIVE_PEER);
Peers.addListener(peer -> {
JSONObject response = new JSONObject();
JSONArray changedActivePeers = new JSONArray();
JSONObject changedActivePeer = new JSONObject();
changedActivePeer.put("index", Users.getIndex(peer));
changedActivePeer.put(peer.getState() == Peer.State.CONNECTED ? "connected" : "disconnected", true);
changedActivePeer.put("announcedAddress", Convert.truncate(peer.getAnnouncedAddress(), "-", 25, true));
changedActivePeers.add(changedActivePeer);
response.put("changedActivePeers", changedActivePeers);
Users.sendNewDataToAll(response);
}, Peers.Event.CHANGED_ACTIVE_PEER);
Peers.addListener(peer -> {
JSONObject response = new JSONObject();
JSONArray addedKnownPeers = new JSONArray();
JSONObject addedKnownPeer = new JSONObject();
addedKnownPeer.put("index", Users.getIndex(peer));
addedKnownPeer.put("address", peer.getHost());
addedKnownPeer.put("announcedAddress", Convert.truncate(peer.getAnnouncedAddress(), "-", 25, true));
addedKnownPeer.put("software", peer.getSoftware());
addedKnownPeers.add(addedKnownPeer);
response.put("addedKnownPeers", addedKnownPeers);
Users.sendNewDataToAll(response);
}, Peers.Event.NEW_PEER);
Nxt.getTransactionProcessor().addListener(transactions -> {
JSONObject response = new JSONObject();
JSONArray removedUnconfirmedTransactions = new JSONArray();
for (Transaction transaction : transactions) {
JSONObject removedUnconfirmedTransaction = new JSONObject();
removedUnconfirmedTransaction.put("index", Users.getIndex(transaction));
removedUnconfirmedTransactions.add(removedUnconfirmedTransaction);
}
response.put("removedUnconfirmedTransactions", removedUnconfirmedTransactions);
Users.sendNewDataToAll(response);
}, TransactionProcessor.Event.REMOVED_UNCONFIRMED_TRANSACTIONS);
Nxt.getTransactionProcessor().addListener(transactions -> {
JSONObject response = new JSONObject();
JSONArray addedUnconfirmedTransactions = new JSONArray();
for (Transaction transaction : transactions) {
JSONObject addedUnconfirmedTransaction = new JSONObject();
addedUnconfirmedTransaction.put("index", Users.getIndex(transaction));
addedUnconfirmedTransaction.put("timestamp", transaction.getTimestamp());
addedUnconfirmedTransaction.put("deadline", transaction.getDeadline());
addedUnconfirmedTransaction.put("recipient", Long.toUnsignedString(transaction.getRecipientId()));
addedUnconfirmedTransaction.put("amountNQT", transaction.getAmountNQT());
addedUnconfirmedTransaction.put("feeNQT", transaction.getFeeNQT());
addedUnconfirmedTransaction.put("sender", Long.toUnsignedString(transaction.getSenderId()));
addedUnconfirmedTransaction.put("id", transaction.getStringId());
addedUnconfirmedTransactions.add(addedUnconfirmedTransaction);
}
response.put("addedUnconfirmedTransactions", addedUnconfirmedTransactions);
Users.sendNewDataToAll(response);
}, TransactionProcessor.Event.ADDED_UNCONFIRMED_TRANSACTIONS);
Nxt.getTransactionProcessor().addListener(transactions -> {
JSONObject response = new JSONObject();
JSONArray addedConfirmedTransactions = new JSONArray();
for (Transaction transaction : transactions) {
JSONObject addedConfirmedTransaction = new JSONObject();
addedConfirmedTransaction.put("index", Users.getIndex(transaction));
addedConfirmedTransaction.put("blockTimestamp", transaction.getBlockTimestamp());
addedConfirmedTransaction.put("transactionTimestamp", transaction.getTimestamp());
addedConfirmedTransaction.put("sender", Long.toUnsignedString(transaction.getSenderId()));
addedConfirmedTransaction.put("recipient", Long.toUnsignedString(transaction.getRecipientId()));
addedConfirmedTransaction.put("amountNQT", transaction.getAmountNQT());
addedConfirmedTransaction.put("feeNQT", transaction.getFeeNQT());
addedConfirmedTransaction.put("id", transaction.getStringId());
addedConfirmedTransactions.add(addedConfirmedTransaction);
}
response.put("addedConfirmedTransactions", addedConfirmedTransactions);
Users.sendNewDataToAll(response);
}, TransactionProcessor.Event.ADDED_CONFIRMED_TRANSACTIONS);
Nxt.getBlockchainProcessor().addListener(block -> {
JSONObject response = new JSONObject();
JSONArray addedOrphanedBlocks = new JSONArray();
JSONObject addedOrphanedBlock = new JSONObject();
addedOrphanedBlock.put("index", Users.getIndex(block));
addedOrphanedBlock.put("timestamp", block.getTimestamp());
addedOrphanedBlock.put("numberOfTransactions", block.getTransactions().size());
addedOrphanedBlock.put("totalAmountNQT", block.getTotalAmountNQT());
addedOrphanedBlock.put("totalFeeNQT", block.getTotalFeeNQT());
addedOrphanedBlock.put("payloadLength", block.getPayloadLength());
addedOrphanedBlock.put("generator", Long.toUnsignedString(block.getGeneratorId()));
addedOrphanedBlock.put("height", block.getHeight());
addedOrphanedBlock.put("version", block.getVersion());
addedOrphanedBlock.put("block", block.getStringId());
addedOrphanedBlock.put("baseTarget", BigInteger.valueOf(block.getBaseTarget()).multiply(BigInteger.valueOf(100000)).divide(BigInteger.valueOf(Constants.INITIAL_BASE_TARGET)));
addedOrphanedBlocks.add(addedOrphanedBlock);
response.put("addedOrphanedBlocks", addedOrphanedBlocks);
Users.sendNewDataToAll(response);
}, BlockchainProcessor.Event.BLOCK_POPPED);
Nxt.getBlockchainProcessor().addListener(block -> {
JSONObject response = new JSONObject();
JSONArray addedRecentBlocks = new JSONArray();
JSONObject addedRecentBlock = new JSONObject();
addedRecentBlock.put("index", Users.getIndex(block));
addedRecentBlock.put("timestamp", block.getTimestamp());
addedRecentBlock.put("numberOfTransactions", block.getTransactions().size());
addedRecentBlock.put("totalAmountNQT", block.getTotalAmountNQT());
addedRecentBlock.put("totalFeeNQT", block.getTotalFeeNQT());
addedRecentBlock.put("payloadLength", block.getPayloadLength());
addedRecentBlock.put("generator", Long.toUnsignedString(block.getGeneratorId()));
addedRecentBlock.put("height", block.getHeight());
addedRecentBlock.put("version", block.getVersion());
addedRecentBlock.put("block", block.getStringId());
addedRecentBlock.put("baseTarget", BigInteger.valueOf(block.getBaseTarget()).multiply(BigInteger.valueOf(100000)).divide(BigInteger.valueOf(Constants.INITIAL_BASE_TARGET)));
addedRecentBlocks.add(addedRecentBlock);
response.put("addedRecentBlocks", addedRecentBlocks);
Users.sendNewDataToAll(response);
}, BlockchainProcessor.Event.BLOCK_PUSHED);
Generator.addListener(generator -> {
JSONObject response = new JSONObject();
response.put("response", "setBlockGenerationDeadline");
response.put("deadline", generator.getDeadline());
users.values().forEach(user -> {
if (Arrays.equals(generator.getPublicKey(), user.getPublicKey())) {
user.send(response);
}
});
}, Generator.Event.GENERATION_DEADLINE);
}
}
static Collection<User> getAllUsers() {
return allUsers;
}
static User getUser(String userId) {
User user = users.get(userId);
if (user == null) {
user = new User(userId);
User oldUser = users.putIfAbsent(userId, user);
if (oldUser != null) {
user = oldUser;
user.setInactive(false);
}
} else {
user.setInactive(false);
}
return user;
}
static User remove(User user) {
return users.remove(user.getUserId());
}
private static void sendNewDataToAll(JSONObject response) {
response.put("response", "processNewData");
sendToAll(response);
}
private static void sendToAll(JSONStreamAware response) {
for (User user : users.values()) {
user.send(response);
}
}
static int getIndex(Peer peer) {
Integer index = peerIndexMap.get(peer.getHost());
if (index == null) {
index = peerCounter.incrementAndGet();
peerIndexMap.put(peer.getHost(), index);
peerAddressMap.put(index, peer.getHost());
}
return index;
}
static Peer getPeer(int index) {
String peerAddress = peerAddressMap.get(index);
if (peerAddress == null) {
return null;
}
return Peers.getPeer(peerAddress);
}
static int getIndex(Block block) {
Integer index = blockIndexMap.get(block.getId());
if (index == null) {
index = blockCounter.incrementAndGet();
blockIndexMap.put(block.getId(), index);
}
return index;
}
static int getIndex(Transaction transaction) {
Integer index = transactionIndexMap.get(transaction.getId());
if (index == null) {
index = transactionCounter.incrementAndGet();
transactionIndexMap.put(transaction.getId(), index);
}
return index;
}
public static void init() {}
public static void shutdown() {
if (userServer != null) {
try {
userServer.stop();
} catch (Exception e) {
Logger.logShutdownMessage("Failed to stop user interface server", e);
}
}
}
private Users() {} // never
}