/****************************************************************************** * 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; import nxt.crypto.Crypto; import nxt.env.DirProvider; import nxt.env.RuntimeEnvironment; import nxt.env.RuntimeMode; import nxt.http.API; import nxt.peer.Peers; import nxt.user.Users; import nxt.util.Convert; import nxt.util.Logger; import nxt.util.ThreadPool; import nxt.util.Time; import org.json.simple.JSONObject; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.PrintStream; import java.lang.management.ManagementFactory; import java.net.URI; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.security.AccessControlException; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Properties; public final class Nxt { public static final String VERSION = "1.7.4"; public static final String APPLICATION = "NRS"; private static volatile Time time = new Time.EpochTime(); public static final String NXT_DEFAULT_PROPERTIES = "nxt-default.properties"; public static final String NXT_PROPERTIES = "nxt.properties"; public static final String CONFIG_DIR = "conf"; private static final RuntimeMode runtimeMode; private static final DirProvider dirProvider; private static final Properties defaultProperties = new Properties(); static { redirectSystemStreams("out"); redirectSystemStreams("err"); System.out.println("Initializing Nxt server version " + Nxt.VERSION); printCommandLineArguments(); runtimeMode = RuntimeEnvironment.getRuntimeMode(); dirProvider = RuntimeEnvironment.getDirProvider(); loadProperties(defaultProperties, NXT_DEFAULT_PROPERTIES, true); if (!VERSION.equals(Nxt.defaultProperties.getProperty("nxt.version"))) { throw new RuntimeException("Using an nxt-default.properties file from a version other than " + VERSION + " is not supported!!!"); } } private static void redirectSystemStreams(String streamName) { String isStandardRedirect = System.getProperty("nxt.redirect.system." + streamName); Path path = null; if (isStandardRedirect != null) { try { path = Files.createTempFile("nxt.system." + streamName + ".", ".log"); } catch (IOException e) { e.printStackTrace(); return; } } else { String explicitFileName = System.getProperty("nxt.system." + streamName); if (explicitFileName != null) { path = Paths.get(explicitFileName); } } if (path != null) { try { PrintStream stream = new PrintStream(Files.newOutputStream(path)); if (streamName.equals("out")) { System.setOut(new PrintStream(stream)); } else { System.setErr(new PrintStream(stream)); } } catch (IOException e) { e.printStackTrace(); } } } private static final Properties properties = new Properties(defaultProperties); static { loadProperties(properties, NXT_PROPERTIES, false); } public static Properties loadProperties(Properties properties, String propertiesFile, boolean isDefault) { try { // Load properties from location specified as command line parameter String configFile = System.getProperty(propertiesFile); if (configFile != null) { System.out.printf("Loading %s from %s\n", propertiesFile, configFile); try (InputStream fis = new FileInputStream(configFile)) { properties.load(fis); return properties; } catch (IOException e) { throw new IllegalArgumentException(String.format("Error loading %s from %s", propertiesFile, configFile)); } } else { try (InputStream is = ClassLoader.getSystemResourceAsStream(propertiesFile)) { // When running nxt.exe from a Windows installation we always have nxt.properties in the classpath but this is not the nxt properties file // Therefore we first load it from the classpath and then look for the real nxt.properties in the user folder. if (is != null) { System.out.printf("Loading %s from classpath\n", propertiesFile); properties.load(is); if (isDefault) { return properties; } } // load non-default properties files from the user folder if (!dirProvider.isLoadPropertyFileFromUserDir()) { return properties; } if (!Files.isReadable(Paths.get(dirProvider.getUserHomeDir()))) { System.out.printf("Creating dir %s\n", dirProvider.getUserHomeDir()); Files.createDirectory(Paths.get(dirProvider.getUserHomeDir())); } Path confDir = Paths.get(dirProvider.getUserHomeDir(), CONFIG_DIR); if (!Files.isReadable(confDir)) { System.out.printf("Creating dir %s\n", confDir); Files.createDirectory(confDir); } Path propPath = Paths.get(confDir.toString(), propertiesFile); if (Files.isReadable(propPath)) { System.out.printf("Loading %s from dir %s\n", propertiesFile, confDir); properties.load(Files.newInputStream(propPath)); } else { System.out.printf("Creating property file %s\n", propPath); Files.createFile(propPath); Files.write(propPath, Convert.toBytes("# use this file for workstation specific " + propertiesFile)); } return properties; } catch (IOException e) { throw new IllegalArgumentException("Error loading " + propertiesFile, e); } } } catch(IllegalArgumentException e) { e.printStackTrace(); // make sure we log this exception throw e; } } private static void printCommandLineArguments() { try { List<String> inputArguments = ManagementFactory.getRuntimeMXBean().getInputArguments(); if (inputArguments != null && inputArguments.size() > 0) { System.out.println("Command line arguments"); } else { return; } inputArguments.forEach(System.out::println); } catch (AccessControlException e) { System.out.println("Cannot read input arguments " + e.getMessage()); } } public static int getIntProperty(String name) { return getIntProperty(name, 0); } public static int getIntProperty(String name, int defaultValue) { try { int result = Integer.parseInt(properties.getProperty(name)); Logger.logMessage(name + " = \"" + result + "\""); return result; } catch (NumberFormatException e) { Logger.logMessage(name + " not defined or not numeric, using default value " + defaultValue); return defaultValue; } } public static String getStringProperty(String name) { return getStringProperty(name, null, false); } public static String getStringProperty(String name, String defaultValue) { return getStringProperty(name, defaultValue, false); } public static String getStringProperty(String name, String defaultValue, boolean doNotLog) { String value = properties.getProperty(name); if (value != null && ! "".equals(value)) { Logger.logMessage(name + " = \"" + (doNotLog ? "{not logged}" : value) + "\""); return value; } else { Logger.logMessage(name + " not defined"); return defaultValue; } } public static List<String> getStringListProperty(String name) { String value = getStringProperty(name); if (value == null || value.length() == 0) { return Collections.emptyList(); } List<String> result = new ArrayList<>(); for (String s : value.split(";")) { s = s.trim(); if (s.length() > 0) { result.add(s); } } return result; } public static Boolean getBooleanProperty(String name) { String value = properties.getProperty(name); if (Boolean.TRUE.toString().equals(value)) { Logger.logMessage(name + " = \"true\""); return true; } else if (Boolean.FALSE.toString().equals(value)) { Logger.logMessage(name + " = \"false\""); return false; } Logger.logMessage(name + " not defined, assuming false"); return false; } public static Blockchain getBlockchain() { return BlockchainImpl.getInstance(); } public static BlockchainProcessor getBlockchainProcessor() { return BlockchainProcessorImpl.getInstance(); } public static TransactionProcessor getTransactionProcessor() { return TransactionProcessorImpl.getInstance(); } public static Transaction.Builder newTransactionBuilder(byte[] senderPublicKey, long amountNQT, long feeNQT, short deadline, Attachment attachment) { return new TransactionImpl.BuilderImpl((byte)1, senderPublicKey, amountNQT, feeNQT, deadline, (Attachment.AbstractAttachment)attachment); } public static Transaction.Builder newTransactionBuilder(byte[] transactionBytes) throws NxtException.NotValidException { return TransactionImpl.newTransactionBuilder(transactionBytes); } public static Transaction.Builder newTransactionBuilder(JSONObject transactionJSON) throws NxtException.NotValidException { return TransactionImpl.newTransactionBuilder(transactionJSON); } public static Transaction.Builder newTransactionBuilder(byte[] transactionBytes, JSONObject prunableAttachments) throws NxtException.NotValidException { return TransactionImpl.newTransactionBuilder(transactionBytes, prunableAttachments); } public static int getEpochTime() { return time.getTime(); } static void setTime(Time time) { Nxt.time = time; } public static void main(String[] args) { try { Runtime.getRuntime().addShutdownHook(new Thread(Nxt::shutdown)); init(); } catch (Throwable t) { System.out.println("Fatal error: " + t.toString()); t.printStackTrace(); } } public static void init(Properties customProperties) { properties.putAll(customProperties); init(); } public static void init() { Init.init(); } public static void shutdown() { Logger.logShutdownMessage("Shutting down..."); API.shutdown(); Users.shutdown(); ThreadPool.shutdown(); Peers.shutdown(); Db.shutdown(); Logger.logShutdownMessage("Nxt server " + VERSION + " stopped."); Logger.shutdown(); runtimeMode.shutdown(); } private static class Init { private static volatile boolean initialized = false; static { try { long startTime = System.currentTimeMillis(); Logger.init(); setSystemProperties(); logSystemProperties(); runtimeMode.init(); Thread secureRandomInitThread = initSecureRandom(); setServerStatus("NXT Server - Loading database", null); Db.init(); setServerStatus("NXT Server - Loading resources", null); TransactionProcessorImpl.getInstance(); BlockchainProcessorImpl.getInstance(); Account.init(); AccountRestrictions.init(); AccountLedger.init(); Alias.init(); Asset.init(); DigitalGoodsStore.init(); Hub.init(); Order.init(); Poll.init(); PhasingPoll.init(); Trade.init(); AssetTransfer.init(); AssetDelete.init(); Vote.init(); PhasingVote.init(); Currency.init(); CurrencyBuyOffer.init(); CurrencySellOffer.init(); CurrencyFounder.init(); CurrencyMint.init(); CurrencyTransfer.init(); Exchange.init(); ExchangeRequest.init(); Shuffling.init(); ShufflingParticipant.init(); PrunableMessage.init(); TaggedData.init(); Peers.init(); Generator.init(); API.init(); Users.init(); DebugTrace.init(); int timeMultiplier = (Constants.isTestnet && Constants.isOffline) ? Math.max(Nxt.getIntProperty("nxt.timeMultiplier"), 1) : 1; ThreadPool.start(timeMultiplier); if (timeMultiplier > 1) { setTime(new Time.FasterTime(Math.max(getEpochTime(), Nxt.getBlockchain().getLastBlock().getTimestamp()), timeMultiplier)); Logger.logMessage("TIME WILL FLOW " + timeMultiplier + " TIMES FASTER!"); } try { secureRandomInitThread.join(10000); } catch (InterruptedException ignore) {} testSecureRandom(); long currentTime = System.currentTimeMillis(); Logger.logMessage("Initialization took " + (currentTime - startTime) / 1000 + " seconds"); Logger.logMessage("Nxt server " + VERSION + " started successfully."); Logger.logMessage("Copyright © 2013-2016 The Nxt Core Developers."); Logger.logMessage("Distributed under GPLv2, with ABSOLUTELY NO WARRANTY."); if (API.getBrowserUri() != null) { Logger.logMessage("Client UI is at " + API.getBrowserUri()); } setServerStatus("NXT Server - Online", API.getBrowserUri()); if (Constants.isTestnet) { Logger.logMessage("RUNNING ON TESTNET - DO NOT USE REAL ACCOUNTS!"); } } catch (Exception e) { Logger.logErrorMessage(e.getMessage(), e); System.exit(1); } } private static void init() { if (initialized) { throw new RuntimeException("Nxt.init has already been called"); } initialized = true; } private Init() {} // never } private static void setSystemProperties() { // Override system settings that the user has define in nxt.properties file. String[] systemProperties = new String[] { "socksProxyHost", "socksProxyPort", }; for (String propertyName : systemProperties) { String propertyValue; if ((propertyValue = getStringProperty(propertyName)) != null) { System.setProperty(propertyName, propertyValue); } } } private static void logSystemProperties() { String[] loggedProperties = new String[] { "java.version", "java.vm.version", "java.vm.name", "java.vendor", "java.vm.vendor", "java.home", "java.library.path", "java.class.path", "os.arch", "sun.arch.data.model", "os.name", "file.encoding", RuntimeMode.RUNTIME_MODE_ARG }; for (String property : loggedProperties) { Logger.logDebugMessage(String.format("%s = %s", property, System.getProperty(property))); } Logger.logDebugMessage(String.format("availableProcessors = %s", Runtime.getRuntime().availableProcessors())); Logger.logDebugMessage(String.format("maxMemory = %s", Runtime.getRuntime().maxMemory())); Logger.logDebugMessage(String.format("processId = %s", getProcessId())); } private static Thread initSecureRandom() { Thread secureRandomInitThread = new Thread(() -> { Crypto.getSecureRandom().nextBytes(new byte[1024]); }); secureRandomInitThread.setDaemon(true); secureRandomInitThread.start(); return secureRandomInitThread; } private static void testSecureRandom() { Thread thread = new Thread(() -> { Crypto.getSecureRandom().nextBytes(new byte[1024]); }); thread.setDaemon(true); thread.start(); try { thread.join(2000); if (thread.isAlive()) { throw new RuntimeException("SecureRandom implementation too slow!!! " + "Install haveged if on linux, or set nxt.useStrongSecureRandom=false."); } } catch (InterruptedException ignore) {} } public static String getProcessId() { String runtimeName = ManagementFactory.getRuntimeMXBean().getName(); if (runtimeName == null) { return ""; } String[] tokens = runtimeName.split("@"); if (tokens.length == 2) { return tokens[0]; } return ""; } public static String getDbDir(String dbDir) { return dirProvider.getDbDir(dbDir); } public static void updateLogFileHandler(Properties loggingProperties) { dirProvider.updateLogFileHandler(loggingProperties); } public static String getUserHomeDir() { return dirProvider.getUserHomeDir(); } public static void setServerStatus(String status, URI wallet) { runtimeMode.setServerStatus(status, wallet, dirProvider.getLogFileDir()); } private Nxt() {} // never }