diff --git a/common/pom.xml b/common/pom.xml index 96abceb58e3..4d637ab0707 100644 --- a/common/pom.xml +++ b/common/pom.xml @@ -5,7 +5,7 @@ parent io.bitsquare - 0.4.9.2 + 0.4.9.3 4.0.0 diff --git a/common/src/main/java/io/bitsquare/app/Version.java b/common/src/main/java/io/bitsquare/app/Version.java index d9e31bd348e..ce3d4d70096 100644 --- a/common/src/main/java/io/bitsquare/app/Version.java +++ b/common/src/main/java/io/bitsquare/app/Version.java @@ -24,7 +24,7 @@ public class Version { private static final Logger log = LoggerFactory.getLogger(Version.class); // The application versions - public static final String VERSION = "0.4.9.2"; + public static final String VERSION = "0.4.9.3"; // The version nr. for the objects sent over the network. A change will break the serialization of old objects. // If objects are used for both network and database the network version is applied. diff --git a/common/src/main/java/io/bitsquare/common/crypto/Hash.java b/common/src/main/java/io/bitsquare/common/crypto/Hash.java index 8a780d153e9..0a08679ccc0 100644 --- a/common/src/main/java/io/bitsquare/common/crypto/Hash.java +++ b/common/src/main/java/io/bitsquare/common/crypto/Hash.java @@ -37,15 +37,14 @@ public class Hash { * @return Hash of data */ public static byte[] getHash(byte[] data) { - MessageDigest digest; try { - digest = MessageDigest.getInstance("SHA-256", "BC"); + MessageDigest digest = MessageDigest.getInstance("SHA-256", "BC"); + digest.update(data, 0, data.length); + return digest.digest(); } catch (NoSuchAlgorithmException | NoSuchProviderException e) { log.error("Could not create MessageDigest for hash. " + e.getMessage()); throw new RuntimeException(e); } - digest.update(data, 0, data.length); - return digest.digest(); } /** diff --git a/common/src/main/java/io/bitsquare/common/crypto/KeyStorage.java b/common/src/main/java/io/bitsquare/common/crypto/KeyStorage.java index ee25b622b26..75304d5ba9d 100644 --- a/common/src/main/java/io/bitsquare/common/crypto/KeyStorage.java +++ b/common/src/main/java/io/bitsquare/common/crypto/KeyStorage.java @@ -88,7 +88,7 @@ private boolean fileExists(KeyEntry keyEntry) { } public KeyPair loadKeyPair(KeyEntry keyEntry) { - FileUtil.rollingBackup(storageDir, keyEntry.getFileName() + ".key"); + FileUtil.rollingBackup(storageDir, keyEntry.getFileName() + ".key", 20); // long now = System.currentTimeMillis(); try { KeyFactory keyFactory = KeyFactory.getInstance(keyEntry.getAlgorithm(), "BC"); diff --git a/common/src/main/java/io/bitsquare/storage/FileManager.java b/common/src/main/java/io/bitsquare/storage/FileManager.java index 6dbcce6f712..2e7b96da6ce 100644 --- a/common/src/main/java/io/bitsquare/storage/FileManager.java +++ b/common/src/main/java/io/bitsquare/storage/FileManager.java @@ -152,8 +152,8 @@ public synchronized void removeAndBackupFile(String fileName) throws IOException renameTempFileToFile(storageFile, corruptedFile); } - public synchronized void backupFile(String fileName) throws IOException { - FileUtil.rollingBackup(dir, fileName); + public synchronized void backupFile(String fileName, int numMaxBackupFiles) throws IOException { + FileUtil.rollingBackup(dir, fileName, numMaxBackupFiles); } /////////////////////////////////////////////////////////////////////////////////////////// @@ -171,30 +171,36 @@ private synchronized void saveToFile(T serializable, File dir, File storageFile) File tempFile = null; FileOutputStream fileOutputStream = null; ObjectOutputStream objectOutputStream = null; + PrintWriter printWriter = null; try { if (!dir.exists()) if (!dir.mkdir()) log.warn("make dir failed"); tempFile = File.createTempFile("temp", null, dir); - - // Don't use auto closeable resources in try() as we would need too many try/catch clauses (for tempFile) - // and we need to close it - // manually before replacing file with temp file - fileOutputStream = new FileOutputStream(tempFile); - objectOutputStream = new ObjectOutputStream(fileOutputStream); - - objectOutputStream.writeObject(serializable); - // Attempt to force the bits to hit the disk. In reality the OS or hard disk itself may still decide - // to not write through to physical media for at least a few seconds, but this is the best we can do. - fileOutputStream.flush(); - fileOutputStream.getFD().sync(); - - // Close resources before replacing file with temp file because otherwise it causes problems on windows - // when rename temp file - fileOutputStream.close(); - objectOutputStream.close(); - + tempFile.deleteOnExit(); + if (serializable instanceof PlainTextWrapper) { + // When we dump json files we don't want to safe it as java serialized string objects, so we use PrintWriter instead. + printWriter = new PrintWriter(tempFile); + printWriter.println(((PlainTextWrapper) serializable).plainText); + } else { + // Don't use auto closeable resources in try() as we would need too many try/catch clauses (for tempFile) + // and we need to close it + // manually before replacing file with temp file + fileOutputStream = new FileOutputStream(tempFile); + objectOutputStream = new ObjectOutputStream(fileOutputStream); + + objectOutputStream.writeObject(serializable); + // Attempt to force the bits to hit the disk. In reality the OS or hard disk itself may still decide + // to not write through to physical media for at least a few seconds, but this is the best we can do. + fileOutputStream.flush(); + fileOutputStream.getFD().sync(); + + // Close resources before replacing file with temp file because otherwise it causes problems on windows + // when rename temp file + fileOutputStream.close(); + objectOutputStream.close(); + } renameTempFileToFile(tempFile, storageFile); } catch (Throwable t) { log.error("storageFile " + storageFile.toString()); @@ -212,6 +218,8 @@ private synchronized void saveToFile(T serializable, File dir, File storageFile) objectOutputStream.close(); if (fileOutputStream != null) fileOutputStream.close(); + if (printWriter != null) + printWriter.close(); } catch (IOException e) { // We swallow that e.printStackTrace(); diff --git a/common/src/main/java/io/bitsquare/storage/FileUtil.java b/common/src/main/java/io/bitsquare/storage/FileUtil.java index 84040b3449b..822e18598fc 100644 --- a/common/src/main/java/io/bitsquare/storage/FileUtil.java +++ b/common/src/main/java/io/bitsquare/storage/FileUtil.java @@ -4,9 +4,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.io.File; -import java.io.FileNotFoundException; -import java.io.IOException; +import java.io.*; import java.nio.file.Paths; import java.util.Arrays; import java.util.Date; @@ -14,10 +12,8 @@ public class FileUtil { private static final Logger log = LoggerFactory.getLogger(FileUtil.class); - /** Number of copies to keep in backup directory. */ - private static final int KEPT_BACKUPS = 10; - public static void rollingBackup(File dir, String fileName) { + public static void rollingBackup(File dir, String fileName, int numMaxBackupFiles) { if (dir.exists()) { File backupDir = new File(Paths.get(dir.getAbsolutePath(), "backup").toString()); if (!backupDir.exists()) @@ -39,7 +35,7 @@ public static void rollingBackup(File dir, String fileName) { try { Files.copy(origFile, backupFile); - pruneBackup(backupFileDir); + pruneBackup(backupFileDir, numMaxBackupFiles); } catch (IOException e) { log.error("Backup key failed: " + e.getMessage()); e.printStackTrace(); @@ -48,22 +44,22 @@ public static void rollingBackup(File dir, String fileName) { } } - private static void pruneBackup(File backupDir) { + private static void pruneBackup(File backupDir, int numMaxBackupFiles) { if (backupDir.isDirectory()) { File[] files = backupDir.listFiles(); if (files != null) { List filesList = Arrays.asList(files); - if (filesList.size() > KEPT_BACKUPS) { + if (filesList.size() > numMaxBackupFiles) { filesList.sort((o1, o2) -> o1.getName().compareTo(o2.getName())); File file = filesList.get(0); if (file.isFile()) { if (!file.delete()) log.error("Failed to delete file: " + file); else - pruneBackup(backupDir); + pruneBackup(backupDir, numMaxBackupFiles); } else { - pruneBackup(new File(Paths.get(backupDir.getAbsolutePath(), file.getName()).toString())); + pruneBackup(new File(Paths.get(backupDir.getAbsolutePath(), file.getName()).toString()), numMaxBackupFiles); } } } @@ -80,4 +76,20 @@ public static void deleteDirectory(File file) throws IOException { if (file.exists() && !file.delete()) throw new FileNotFoundException("Failed to delete file: " + file); } + + public static void resourceToFile(String resourcePath, File destinationFile) throws ResourceNotFoundException, IOException { + InputStream inputStream = ClassLoader.getSystemClassLoader().getResourceAsStream(resourcePath); + if (inputStream == null) + throw new ResourceNotFoundException(resourcePath); + + try (FileOutputStream fileOutputStream = new FileOutputStream(destinationFile)) { + byte[] buffer = new byte[1024]; + int bytesRead; + while ((bytesRead = inputStream.read(buffer)) != -1) { + fileOutputStream.write(buffer, 0, bytesRead); + } + } catch (IOException e) { + throw e; + } + } } diff --git a/common/src/main/java/io/bitsquare/storage/PlainTextWrapper.java b/common/src/main/java/io/bitsquare/storage/PlainTextWrapper.java new file mode 100644 index 00000000000..c5414ffa1d7 --- /dev/null +++ b/common/src/main/java/io/bitsquare/storage/PlainTextWrapper.java @@ -0,0 +1,18 @@ +package io.bitsquare.storage; + +import java.io.Serializable; + +/** + * Used to wrap a plaintext string to distinguish at file storage and safe it as plain text instead of a serialized java object. + */ +public class PlainTextWrapper implements Serializable { + // That object is not saved to disc it is only of type Serializable to support the persistent framework. + // SerialVersionUID has no relevance here. + private static final long serialVersionUID = 0; + + public final String plainText; + + public PlainTextWrapper(String plainText) { + this.plainText = plainText; + } +} diff --git a/common/src/main/java/io/bitsquare/storage/ResourceNotFoundException.java b/common/src/main/java/io/bitsquare/storage/ResourceNotFoundException.java new file mode 100644 index 00000000000..6b77fef71da --- /dev/null +++ b/common/src/main/java/io/bitsquare/storage/ResourceNotFoundException.java @@ -0,0 +1,7 @@ +package io.bitsquare.storage; + +public class ResourceNotFoundException extends Exception { + public ResourceNotFoundException(String path) { + super("Resource not found: path = " + path); + } +} diff --git a/common/src/main/java/io/bitsquare/storage/Storage.java b/common/src/main/java/io/bitsquare/storage/Storage.java index 4621144e79d..93909b20ca3 100644 --- a/common/src/main/java/io/bitsquare/storage/Storage.java +++ b/common/src/main/java/io/bitsquare/storage/Storage.java @@ -65,6 +65,7 @@ public interface DataBaseCorruptionHandler { private File storageFile; private T serializable; private String fileName; + private int numMaxBackupFiles = 10; /////////////////////////////////////////////////////////////////////////////////////////// @@ -76,6 +77,13 @@ public Storage(@Named(DIR_KEY) File dir) { this.dir = dir; } + @Nullable + public void initWithFileName(String fileName) { + this.fileName = fileName; + storageFile = new File(dir, fileName); + fileManager = new FileManager<>(dir, storageFile, 300); + } + @Nullable public T initAndGetPersistedWithFileName(String fileName) { this.fileName = fileName; @@ -108,6 +116,10 @@ public void queueUpForSave(long delayInMilli) { queueUpForSave(serializable, delayInMilli); } + public void setNumMaxBackupFiles(int numMaxBackupFiles) { + this.numMaxBackupFiles = numMaxBackupFiles; + } + // Save delayed and on a background thread private void queueUpForSave(T serializable) { if (serializable != null) { @@ -152,7 +164,7 @@ private T getPersisted() { // If we did not get any exception we can be sure the data are consistent so we make a backup now = System.currentTimeMillis(); - fileManager.backupFile(fileName); + fileManager.backupFile(fileName, numMaxBackupFiles); log.trace("Backup {} completed in {}msec", storageFile, System.currentTimeMillis() - now); return persistedObject; diff --git a/core/pom.xml b/core/pom.xml index fa34ab2e2b1..a8d1e511201 100755 --- a/core/pom.xml +++ b/core/pom.xml @@ -6,7 +6,7 @@ parent io.bitsquare - 0.4.9.2 + 0.4.9.3 core diff --git a/core/src/main/java/io/bitsquare/app/BitsquareEnvironment.java b/core/src/main/java/io/bitsquare/app/BitsquareEnvironment.java index bbf180a62e9..2ecea5b5078 100644 --- a/core/src/main/java/io/bitsquare/app/BitsquareEnvironment.java +++ b/core/src/main/java/io/bitsquare/app/BitsquareEnvironment.java @@ -79,7 +79,7 @@ public static void setDefaultAppName(String defaultAppName) { private final String logLevel; private BitcoinNetwork bitcoinNetwork; private final String btcSeedNodes, seedNodes, ignoreDevMsg, useTorForBtc, useTorForHttp, - myAddress, banList, dumpStatistics, socks5ProxyBtcAddress, socks5ProxyHttpAddress; + myAddress, banList, dumpStatistics, maxMemory, socks5ProxyBtcAddress, socks5ProxyHttpAddress; public BitsquareEnvironment(OptionSet options) { this(new JOptCommandLinePropertySource(BITSQUARE_COMMANDLINE_PROPERTY_SOURCE_NAME, checkNotNull( @@ -141,7 +141,10 @@ protected BitsquareEnvironment(PropertySource commandLineProperties) { dumpStatistics = commandLineProperties.containsProperty(CoreOptionKeys.DUMP_STATISTICS) ? (String) commandLineProperties.getProperty(CoreOptionKeys.DUMP_STATISTICS) : ""; - + maxMemory = commandLineProperties.containsProperty(CoreOptionKeys.MAX_MEMORY) ? + (String) commandLineProperties.getProperty(CoreOptionKeys.MAX_MEMORY) : + ""; + seedNodes = commandLineProperties.containsProperty(NetworkOptionKeys.SEED_NODES_KEY) ? (String) commandLineProperties.getProperty(NetworkOptionKeys.SEED_NODES_KEY) : ""; @@ -239,6 +242,7 @@ private PropertySource defaultProperties() { setProperty(CoreOptionKeys.IGNORE_DEV_MSG_KEY, ignoreDevMsg); setProperty(CoreOptionKeys.DUMP_STATISTICS, dumpStatistics); setProperty(CoreOptionKeys.APP_NAME_KEY, appName); + setProperty(CoreOptionKeys.MAX_MEMORY, maxMemory); setProperty(CoreOptionKeys.USER_DATA_DIR_KEY, userDataDir); setProperty(BtcOptionKeys.BTC_SEED_NODES, btcSeedNodes); diff --git a/core/src/main/java/io/bitsquare/app/BitsquareExecutable.java b/core/src/main/java/io/bitsquare/app/BitsquareExecutable.java index fd52ad4c66b..694a16a4514 100644 --- a/core/src/main/java/io/bitsquare/app/BitsquareExecutable.java +++ b/core/src/main/java/io/bitsquare/app/BitsquareExecutable.java @@ -100,6 +100,8 @@ protected void customizeOptionParsing(OptionParser parser) { .withRequiredArg(); parser.accepts(CoreOptionKeys.APP_NAME_KEY, description("Application name", DEFAULT_APP_NAME)) .withRequiredArg(); + parser.accepts(CoreOptionKeys.MAX_MEMORY, description("Max. permitted memory (used only at headless versions)", 600)) + .withRequiredArg(); parser.accepts(CoreOptionKeys.APP_DATA_DIR_KEY, description("Application data directory", DEFAULT_APP_DATA_DIR)) .withRequiredArg(); parser.accepts(CoreOptionKeys.IGNORE_DEV_MSG_KEY, description("If set to true all signed messages from Bitsquare developers are ignored " + diff --git a/core/src/main/java/io/bitsquare/app/CoreOptionKeys.java b/core/src/main/java/io/bitsquare/app/CoreOptionKeys.java index 4e24b8c65b6..6df92e3bff0 100644 --- a/core/src/main/java/io/bitsquare/app/CoreOptionKeys.java +++ b/core/src/main/java/io/bitsquare/app/CoreOptionKeys.java @@ -6,4 +6,5 @@ public class CoreOptionKeys { public static final String USER_DATA_DIR_KEY = "userDataDir"; public static final String APP_NAME_KEY = "appName"; public static final String APP_DATA_DIR_KEY = "appDataDir"; + public static final String MAX_MEMORY = "maxMemory"; } diff --git a/core/src/main/java/io/bitsquare/arbitration/DisputeResult.java b/core/src/main/java/io/bitsquare/arbitration/DisputeResult.java index 84fb8d5c6a9..a7a059023b2 100644 --- a/core/src/main/java/io/bitsquare/arbitration/DisputeResult.java +++ b/core/src/main/java/io/bitsquare/arbitration/DisputeResult.java @@ -57,7 +57,8 @@ public enum Reason { public final String tradeId; public final int traderId; private DisputeFeePolicy disputeFeePolicy; - + private Winner winner; + private Reason reason; private boolean tamperProofEvidence; private boolean idVerification; private boolean screenCast; @@ -70,8 +71,6 @@ public enum Reason { private String arbitratorAddressAsString; private byte[] arbitratorPubKey; private long closeDate; - private Winner winner; - private Reason reason; transient private BooleanProperty tamperProofEvidenceProperty = new SimpleBooleanProperty(); transient private BooleanProperty idVerificationProperty = new SimpleBooleanProperty(); @@ -251,6 +250,22 @@ public boolean equals(Object o) { if (tradeId != null ? !tradeId.equals(that.tradeId) : that.tradeId != null) return false; if (disputeFeePolicy != that.disputeFeePolicy) return false; if (reason != that.reason) return false; + + if (disputeFeePolicy != null && that.disputeFeePolicy != null && disputeFeePolicy.ordinal() != that.disputeFeePolicy.ordinal()) + return false; + else if ((disputeFeePolicy == null && that.disputeFeePolicy != null) || (disputeFeePolicy != null && that.disputeFeePolicy == null)) + return false; + + if (reason != null && that.reason != null && reason.ordinal() != that.reason.ordinal()) + return false; + else if ((reason == null && that.reason != null) || (reason != null && that.reason == null)) + return false; + + if (winner != null && that.winner != null && winner.ordinal() != that.winner.ordinal()) + return false; + else if ((winner == null && that.winner != null) || (winner != null && that.winner == null)) + return false; + if (summaryNotes != null ? !summaryNotes.equals(that.summaryNotes) : that.summaryNotes != null) return false; if (disputeCommunicationMessage != null ? !disputeCommunicationMessage.equals(that.disputeCommunicationMessage) : that.disputeCommunicationMessage != null) return false; @@ -258,7 +273,7 @@ public boolean equals(Object o) { if (arbitratorAddressAsString != null ? !arbitratorAddressAsString.equals(that.arbitratorAddressAsString) : that.arbitratorAddressAsString != null) return false; if (!Arrays.equals(arbitratorPubKey, that.arbitratorPubKey)) return false; - return winner == that.winner; + return true; } @@ -266,8 +281,9 @@ public boolean equals(Object o) { public int hashCode() { int result = tradeId != null ? tradeId.hashCode() : 0; result = 31 * result + traderId; - result = 31 * result + (disputeFeePolicy != null ? disputeFeePolicy.hashCode() : 0); - result = 31 * result + (reason != null ? reason.hashCode() : 0); + result = 31 * result + (disputeFeePolicy != null ? disputeFeePolicy.ordinal() : 0); + result = 31 * result + (reason != null ? reason.ordinal() : 0); + result = 31 * result + (winner != null ? winner.ordinal() : 0); result = 31 * result + (tamperProofEvidence ? 1 : 0); result = 31 * result + (idVerification ? 1 : 0); result = 31 * result + (screenCast ? 1 : 0); @@ -280,7 +296,6 @@ public int hashCode() { result = 31 * result + (arbitratorAddressAsString != null ? arbitratorAddressAsString.hashCode() : 0); result = 31 * result + (arbitratorPubKey != null ? Arrays.hashCode(arbitratorPubKey) : 0); result = 31 * result + (int) (closeDate ^ (closeDate >>> 32)); - result = 31 * result + (winner != null ? winner.hashCode() : 0); return result; } } diff --git a/core/src/main/java/io/bitsquare/btc/WalletService.java b/core/src/main/java/io/bitsquare/btc/WalletService.java index aa3b0d1c105..b72b9f958a9 100644 --- a/core/src/main/java/io/bitsquare/btc/WalletService.java +++ b/core/src/main/java/io/bitsquare/btc/WalletService.java @@ -410,7 +410,7 @@ public void restoreSeedWords(DeterministicSeed seed, ResultHandler resultHandler } public void backupWallet() { - FileUtil.rollingBackup(walletDir, "Bitsquare.wallet"); + FileUtil.rollingBackup(walletDir, "Bitsquare.wallet", 20); } public void clearBackup() { diff --git a/core/src/main/java/io/bitsquare/locale/BankUtil.java b/core/src/main/java/io/bitsquare/locale/BankUtil.java index fcecabc5e2f..bbeef006d59 100644 --- a/core/src/main/java/io/bitsquare/locale/BankUtil.java +++ b/core/src/main/java/io/bitsquare/locale/BankUtil.java @@ -52,7 +52,7 @@ public static String getBankNameLabel(String countryCode) { case "BR": return "Bank name:"; default: - return "Bank name (optional):"; + return isBankNameRequired(countryCode) ? "Bank name:" : "Bank name (optional):"; } } @@ -83,7 +83,7 @@ public static String getBankIdLabel(String countryCode) { case "HK": return "Bank code:"; default: - return "Bank ID (e.g. BIC or SWIFT) (optional):"; + return isBankIdRequired(countryCode) ? "Bank ID (e.g. BIC or SWIFT):" : "Bank ID (e.g. BIC or SWIFT) (optional):"; } } @@ -120,7 +120,7 @@ public static String getBranchIdLabel(String countryCode) { case "CA": return "Transit Number:"; default: - return "Branch nr. (optional):"; + return isBranchIdRequired(countryCode) ? "Branch nr.:" : "Branch nr. (optional):"; } } diff --git a/core/src/main/java/io/bitsquare/locale/CurrencyTuple.java b/core/src/main/java/io/bitsquare/locale/CurrencyTuple.java index a124acd7b3c..350f98b0b09 100644 --- a/core/src/main/java/io/bitsquare/locale/CurrencyTuple.java +++ b/core/src/main/java/io/bitsquare/locale/CurrencyTuple.java @@ -24,9 +24,17 @@ public class CurrencyTuple implements Serializable { public final String code; public final String name; + public final int precision; // precision 4 is 1/10000 -> 0.0001 is smallest unit public CurrencyTuple(String code, String name) { + // We use Fiat class and there precision is 4 + // In future we might add custom precision per currency + this(code, name, 4); + } + + public CurrencyTuple(String code, String name, int precision) { this.code = code; this.name = name; + this.precision = precision; } } diff --git a/core/src/main/java/io/bitsquare/locale/CurrencyUtil.java b/core/src/main/java/io/bitsquare/locale/CurrencyUtil.java index 4bbcb101d04..d488bc10144 100644 --- a/core/src/main/java/io/bitsquare/locale/CurrencyUtil.java +++ b/core/src/main/java/io/bitsquare/locale/CurrencyUtil.java @@ -77,7 +77,9 @@ public static List createAllSortedCryptoCurrenciesList() { result.add(new CryptoCurrency("ETH", "Ether")); result.add(new CryptoCurrency("ETC", "EtherClassic")); result.add(new CryptoCurrency("STEEM", "STEEM")); + result.add(new CryptoCurrency("STEEMUSD", "Steem Dollars", true)); result.add(new CryptoCurrency("FLO", "FlorinCoin")); + result.add(new CryptoCurrency("MT", "Mycelium Token", true)); result.add(new CryptoCurrency("XEM", "NEM")); result.add(new CryptoCurrency("LTC", "Litecoin")); result.add(new CryptoCurrency("DASH", "Dash")); @@ -85,6 +87,17 @@ public static List createAllSortedCryptoCurrenciesList() { result.add(new CryptoCurrency("NBT", "NuBits")); result.add(new CryptoCurrency("NSR", "NuShares")); result.add(new CryptoCurrency("SDC", "ShadowCash")); + result.add(new CryptoCurrency("BTS", "BitShares")); + result.add(new CryptoCurrency("BITUSD", "BitUSD", true)); + result.add(new CryptoCurrency("BITEUR", "BitEUR", true)); + result.add(new CryptoCurrency("BITCNY", "BitCNY", true)); + result.add(new CryptoCurrency("BITCHF", "BitCHF", true)); + result.add(new CryptoCurrency("BITGBP", "BitGBP", true)); + result.add(new CryptoCurrency("BITNZD", "BitNZD", true)); + result.add(new CryptoCurrency("BITAUD", "BitAUD", true)); + result.add(new CryptoCurrency("BITSGD", "BitSGD", true)); + result.add(new CryptoCurrency("BITHKD", "BitHKD", true)); + result.add(new CryptoCurrency("BITSEK", "BitSEK", true)); result.add(new CryptoCurrency("PPC", "Peercoin")); result.add(new CryptoCurrency("XPM", "Primecoin")); result.add(new CryptoCurrency("SJCX", "StorjcoinX")); @@ -93,7 +106,6 @@ public static List createAllSortedCryptoCurrenciesList() { result.add(new CryptoCurrency("BLK", "Blackcoin")); result.add(new CryptoCurrency("FCT", "Factom")); result.add(new CryptoCurrency("NXT", "Nxt")); - result.add(new CryptoCurrency("BTS", "BitShares")); result.add(new CryptoCurrency("XCP", "Counterparty")); result.add(new CryptoCurrency("XRP", "Ripple")); result.add(new CryptoCurrency("FAIR", "FairCoin")); @@ -131,7 +143,8 @@ public static List createAllSortedCryptoCurrenciesList() { result.add(new CryptoCurrency("JPYT", "JPY Tether")); result.add(new CryptoCurrency("WDC", "Worldcoin")); result.add(new CryptoCurrency("DAO", "DAO", true)); - result.add(new CryptoCurrency("ETHC", "EtherClassic (deprecated ticker symbol)")); + result.add(new CryptoCurrency("CMT", "Comet")); + result.add(new CryptoCurrency("SYNQ", "BitSYNQ")); return result; } @@ -142,11 +155,14 @@ public static List getMainCryptoCurrencies() { result.add(new CryptoCurrency("ETH", "Ether")); result.add(new CryptoCurrency("ETC", "EtherClassic")); result.add(new CryptoCurrency("STEEM", "STEEM")); + result.add(new CryptoCurrency("MT", "Mycelium Token", true)); result.add(new CryptoCurrency("FLO", "FlorinCoin")); result.add(new CryptoCurrency("LTC", "Litecoin")); result.add(new CryptoCurrency("DASH", "Dash")); result.add(new CryptoCurrency("NMC", "Namecoin")); result.add(new CryptoCurrency("NBT", "NuBits")); + result.add(new CryptoCurrency("BITUSD", "BitUSD", true)); + result.add(new CryptoCurrency("STEEMUSD", "Steem Dollars", true)); result.add(new CryptoCurrency("DOGE", "Dogecoin")); result.add(new CryptoCurrency("NXT", "Nxt")); result.add(new CryptoCurrency("BTS", "BitShares")); @@ -237,11 +253,15 @@ public static String getNameByCode(String currencyCode) { try { return Currency.getInstance(currencyCode).getDisplayName(Preferences.getDefaultLocale()); } catch (Throwable t) { - log.warn("No currency name available " + t.getMessage()); - return "N/A (" + currencyCode + ")"; + log.debug("No currency name available " + t.getMessage()); + return currencyCode; } } + public static String getNameAndCode(String currencyCode) { + return getNameByCode(currencyCode) + " (" + currencyCode + ")"; + } + public static TradeCurrency getDefaultTradeCurrency() { return Preferences.getDefaultTradeCurrency(); } diff --git a/core/src/main/java/io/bitsquare/payment/CryptoCurrencyAccountContractData.java b/core/src/main/java/io/bitsquare/payment/CryptoCurrencyAccountContractData.java index de5189da32a..a45cefccf84 100644 --- a/core/src/main/java/io/bitsquare/payment/CryptoCurrencyAccountContractData.java +++ b/core/src/main/java/io/bitsquare/payment/CryptoCurrencyAccountContractData.java @@ -42,7 +42,7 @@ public String getAddress() { @Override public String getPaymentDetails() { - return "Receivers cryptocurrency address: " + address; + return "Receivers altcoin address: " + address; } @Override diff --git a/core/src/main/java/io/bitsquare/payment/PaymentMethod.java b/core/src/main/java/io/bitsquare/payment/PaymentMethod.java index c22aea7591c..a72e8fd78e2 100644 --- a/core/src/main/java/io/bitsquare/payment/PaymentMethod.java +++ b/core/src/main/java/io/bitsquare/payment/PaymentMethod.java @@ -62,13 +62,13 @@ public final class PaymentMethod implements Persistable, Comparable { public static final List ALL_VALUES = new ArrayList<>(Arrays.asList( OK_PAY = new PaymentMethod(OK_PAY_ID, 0, DAY, Coin.parseCoin("1.5")), // tx instant so min. wait time - SEPA = new PaymentMethod(SEPA_ID, 0, 8 * DAY, Coin.parseCoin("0.75")), // sepa takes 1-3 business days. We use 8 days to include safety for holidays - NATIONAL_BANK = new PaymentMethod(NATIONAL_BANK_ID, 0, 4 * DAY, Coin.parseCoin("0.75")), - SAME_BANK = new PaymentMethod(SAME_BANK_ID, 0, 2 * DAY, Coin.parseCoin("0.75")), - SPECIFIC_BANKS = new PaymentMethod(SPECIFIC_BANKS_ID, 0, 4 * DAY, Coin.parseCoin("0.75")), + SEPA = new PaymentMethod(SEPA_ID, 0, 8 * DAY, Coin.parseCoin("1")), // sepa takes 1-3 business days. We use 8 days to include safety for holidays + NATIONAL_BANK = new PaymentMethod(NATIONAL_BANK_ID, 0, 4 * DAY, Coin.parseCoin("1")), + SAME_BANK = new PaymentMethod(SAME_BANK_ID, 0, 2 * DAY, Coin.parseCoin("1")), + SPECIFIC_BANKS = new PaymentMethod(SPECIFIC_BANKS_ID, 0, 4 * DAY, Coin.parseCoin("1")), PERFECT_MONEY = new PaymentMethod(PERFECT_MONEY_ID, 0, DAY, Coin.parseCoin("1")), - SWISH = new PaymentMethod(SWISH_ID, 0, DAY, Coin.parseCoin("1")), - ALI_PAY = new PaymentMethod(ALI_PAY_ID, 0, DAY, Coin.parseCoin("1")), + SWISH = new PaymentMethod(SWISH_ID, 0, DAY, Coin.parseCoin("1.5")), + ALI_PAY = new PaymentMethod(ALI_PAY_ID, 0, DAY, Coin.parseCoin("1.5")), BLOCK_CHAINS = new PaymentMethod(BLOCK_CHAINS_ID, 0, DAY, Coin.parseCoin("2")) )); diff --git a/core/src/main/java/io/bitsquare/trade/TradeManager.java b/core/src/main/java/io/bitsquare/trade/TradeManager.java index e1102eebe72..e78ffd01807 100644 --- a/core/src/main/java/io/bitsquare/trade/TradeManager.java +++ b/core/src/main/java/io/bitsquare/trade/TradeManager.java @@ -66,7 +66,10 @@ import javax.inject.Inject; import javax.inject.Named; import java.io.File; -import java.util.*; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.Optional; import java.util.concurrent.TimeUnit; import java.util.stream.Stream; @@ -222,7 +225,7 @@ private void initPendingTrades() { // We start later to have better connectivity to the network UserThread.runAfter(() -> publishTradeStatistics(tradesForStatistics), - 30, TimeUnit.SECONDS); + 90, TimeUnit.SECONDS); pendingTradesInitialized.set(true); } @@ -238,10 +241,12 @@ private void publishTradeStatistics(List trades) { keyRing.getPubKeyRing()); tradeStatisticsManager.add(tradeStatistics); - // Only trades from last 30 days - //TODO we want to get old trades published so we dont do the 30 days check for the first few weeks of the new version - if (new Date().before(new Date(2016 - 1900, Calendar.AUGUST, 30)) || (new Date().getTime() - trade.getDate().getTime()) < TimeUnit.DAYS.toMillis(30)) { - long delay = 3000; + // We only republish trades from last 10 days + // TODO check if needed at all. Don't want to remove it atm to not risk anything. + // But we could check which tradeStatistics we received from the seed nodes and + // only re-publish in case tradeStatistics are missing. + if ((new Date().getTime() - trade.getDate().getTime()) < TimeUnit.DAYS.toMillis(10)) { + long delay = 5000; final long minDelay = (i + 1) * delay; final long maxDelay = (i + 2) * delay; UserThread.runAfterRandomDelay(() -> { diff --git a/core/src/main/java/io/bitsquare/trade/closed/ClosedTradableManager.java b/core/src/main/java/io/bitsquare/trade/closed/ClosedTradableManager.java index ee153f236ca..9fc08472bc3 100644 --- a/core/src/main/java/io/bitsquare/trade/closed/ClosedTradableManager.java +++ b/core/src/main/java/io/bitsquare/trade/closed/ClosedTradableManager.java @@ -40,7 +40,10 @@ public class ClosedTradableManager { @Inject public ClosedTradableManager(KeyRing keyRing, PriceFeedService priceFeedService, @Named(Storage.DIR_KEY) File storageDir) { this.keyRing = keyRing; - this.closedTrades = new TradableList<>(new Storage<>(storageDir), "ClosedTrades"); + final Storage> tradableListStorage = new Storage<>(storageDir); + // The ClosedTrades object can become a few MB so we don't keep so many backups + tradableListStorage.setNumMaxBackupFiles(3); + this.closedTrades = new TradableList<>(tradableListStorage, "ClosedTrades"); closedTrades.forEach(e -> e.getOffer().setPriceFeedService(priceFeedService)); } diff --git a/core/src/main/java/io/bitsquare/trade/offer/FlatOffer.java b/core/src/main/java/io/bitsquare/trade/offer/FlatOffer.java new file mode 100644 index 00000000000..dcd47ef983d --- /dev/null +++ b/core/src/main/java/io/bitsquare/trade/offer/FlatOffer.java @@ -0,0 +1,46 @@ +package io.bitsquare.trade.offer; + +import io.bitsquare.payment.PaymentMethod; +import org.bitcoinj.core.Coin; +import org.bitcoinj.utils.Fiat; + +import java.util.Date; + +public class FlatOffer { + public final Offer.Direction direction; + public final String currencyCode; + public final long minAmount; + public final long amount; + public final long price; + public final long date; + public final boolean useMarketBasedPrice; + public final double marketPriceMargin; + public final String paymentMethod; + public final String id; + public final String offerFeeTxID; + + public FlatOffer(Offer.Direction direction, + String currencyCode, + Coin minAmount, + Coin amount, + Fiat price, + Date date, + String id, + boolean useMarketBasedPrice, + double marketPriceMargin, + PaymentMethod paymentMethod, + String offerFeeTxID) { + + this.direction = direction; + this.currencyCode = currencyCode; + this.minAmount = minAmount.value; + this.amount = amount.value; + this.price = price.value; + this.date = date.getTime(); + this.id = id; + this.useMarketBasedPrice = useMarketBasedPrice; + this.marketPriceMargin = marketPriceMargin; + this.paymentMethod = paymentMethod.getId(); + this.offerFeeTxID = offerFeeTxID; + } +} diff --git a/core/src/main/java/io/bitsquare/trade/offer/Offer.java b/core/src/main/java/io/bitsquare/trade/offer/Offer.java index 0a7cafd2552..b992ca01e0c 100644 --- a/core/src/main/java/io/bitsquare/trade/offer/Offer.java +++ b/core/src/main/java/io/bitsquare/trade/offer/Offer.java @@ -54,6 +54,7 @@ import static com.google.common.base.Preconditions.checkNotNull; public final class Offer implements StoragePayload, RequiresOwnerIsOnlinePayload { + /////////////////////////////////////////////////////////////////////////////////////////// // Static /////////////////////////////////////////////////////////////////////////////////////////// @@ -493,39 +494,44 @@ public ReadOnlyStringProperty errorMessageProperty() { public boolean equals(Object o) { if (this == o) return true; if (!(o instanceof Offer)) return false; - Offer offer = (Offer) o; - if (date != offer.date) return false; - if (fiatPrice != offer.fiatPrice) return false; - if (Double.compare(offer.marketPriceMargin, marketPriceMargin) != 0) return false; - if (useMarketBasedPrice != offer.useMarketBasedPrice) return false; - if (amount != offer.amount) return false; - if (minAmount != offer.minAmount) return false; - if (id != null ? !id.equals(offer.id) : offer.id != null) return false; - if (direction != offer.direction) return false; - if (currencyCode != null ? !currencyCode.equals(offer.currencyCode) : offer.currencyCode != null) return false; - if (offererNodeAddress != null ? !offererNodeAddress.equals(offer.offererNodeAddress) : offer.offererNodeAddress != null) + Offer that = (Offer) o; + if (date != that.date) return false; + if (fiatPrice != that.fiatPrice) return false; + if (Double.compare(that.marketPriceMargin, marketPriceMargin) != 0) return false; + if (useMarketBasedPrice != that.useMarketBasedPrice) return false; + if (amount != that.amount) return false; + if (minAmount != that.minAmount) return false; + if (id != null ? !id.equals(that.id) : that.id != null) return false; + + if (direction != null && that.direction != null && direction.ordinal() != that.direction.ordinal()) return false; - if (pubKeyRing != null ? !pubKeyRing.equals(offer.pubKeyRing) : offer.pubKeyRing != null) return false; - if (paymentMethodName != null ? !paymentMethodName.equals(offer.paymentMethodName) : offer.paymentMethodName != null) + else if ((direction == null && that.direction != null) || (direction != null && that.direction == null)) return false; - if (countryCode != null ? !countryCode.equals(offer.countryCode) : offer.countryCode != null) + + if (currencyCode != null ? !currencyCode.equals(that.currencyCode) : that.currencyCode != null) return false; + if (offererNodeAddress != null ? !offererNodeAddress.equals(that.offererNodeAddress) : that.offererNodeAddress != null) + return false; + if (pubKeyRing != null ? !pubKeyRing.equals(that.pubKeyRing) : that.pubKeyRing != null) return false; + if (paymentMethodName != null ? !paymentMethodName.equals(that.paymentMethodName) : that.paymentMethodName != null) + return false; + if (countryCode != null ? !countryCode.equals(that.countryCode) : that.countryCode != null) return false; - if (offererPaymentAccountId != null ? !offererPaymentAccountId.equals(offer.offererPaymentAccountId) : offer.offererPaymentAccountId != null) + if (offererPaymentAccountId != null ? !offererPaymentAccountId.equals(that.offererPaymentAccountId) : that.offererPaymentAccountId != null) return false; - if (acceptedCountryCodes != null ? !acceptedCountryCodes.equals(offer.acceptedCountryCodes) : offer.acceptedCountryCodes != null) + if (acceptedCountryCodes != null ? !acceptedCountryCodes.equals(that.acceptedCountryCodes) : that.acceptedCountryCodes != null) return false; - if (bankId != null ? !bankId.equals(offer.bankId) : offer.bankId != null) return false; - if (acceptedBankIds != null ? !acceptedBankIds.equals(offer.acceptedBankIds) : offer.acceptedBankIds != null) + if (bankId != null ? !bankId.equals(that.bankId) : that.bankId != null) return false; + if (acceptedBankIds != null ? !acceptedBankIds.equals(that.acceptedBankIds) : that.acceptedBankIds != null) return false; - if (arbitratorNodeAddresses != null ? !arbitratorNodeAddresses.equals(offer.arbitratorNodeAddresses) : offer.arbitratorNodeAddresses != null) + if (arbitratorNodeAddresses != null ? !arbitratorNodeAddresses.equals(that.arbitratorNodeAddresses) : that.arbitratorNodeAddresses != null) return false; - return !(offerFeePaymentTxID != null ? !offerFeePaymentTxID.equals(offer.offerFeePaymentTxID) : offer.offerFeePaymentTxID != null); + return !(offerFeePaymentTxID != null ? !offerFeePaymentTxID.equals(that.offerFeePaymentTxID) : that.offerFeePaymentTxID != null); } @Override public int hashCode() { int result = id != null ? id.hashCode() : 0; - result = 31 * result + (direction != null ? direction.hashCode() : 0); + result = 31 * result + (direction != null ? direction.ordinal() : 0); result = 31 * result + (currencyCode != null ? currencyCode.hashCode() : 0); result = 31 * result + (int) (date ^ (date >>> 32)); result = 31 * result + (int) (fiatPrice ^ (fiatPrice >>> 32)); @@ -576,6 +582,7 @@ public String toString() { "\n\terrorMessageProperty=" + errorMessageProperty + "\n\tTAC_OFFERER=" + TAC_OFFERER + "\n\tTAC_TAKER=" + TAC_TAKER + + "\n\thashCode=" + hashCode() + '}'; } } diff --git a/core/src/main/java/io/bitsquare/trade/offer/OfferBookService.java b/core/src/main/java/io/bitsquare/trade/offer/OfferBookService.java index 50a54afe948..b495cb53325 100644 --- a/core/src/main/java/io/bitsquare/trade/offer/OfferBookService.java +++ b/core/src/main/java/io/bitsquare/trade/offer/OfferBookService.java @@ -17,12 +17,19 @@ package io.bitsquare.trade.offer; +import com.google.inject.name.Named; +import io.bitsquare.app.CoreOptionKeys; import io.bitsquare.btc.pricefeed.PriceFeedService; +import io.bitsquare.common.UserThread; import io.bitsquare.common.handlers.ErrorMessageHandler; import io.bitsquare.common.handlers.ResultHandler; +import io.bitsquare.common.util.Utilities; +import io.bitsquare.p2p.BootstrapListener; import io.bitsquare.p2p.P2PService; import io.bitsquare.p2p.storage.HashMapChangedListener; import io.bitsquare.p2p.storage.storageentry.ProtectedStorageEntry; +import io.bitsquare.storage.PlainTextWrapper; +import io.bitsquare.storage.Storage; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -47,6 +54,7 @@ public interface OfferBookChangedListener { private final P2PService p2PService; private PriceFeedService priceFeedService; + private final Storage offersJsonStorage; private final List offerBookChangedListeners = new LinkedList<>(); @@ -55,9 +63,13 @@ public interface OfferBookChangedListener { /////////////////////////////////////////////////////////////////////////////////////////// @Inject - public OfferBookService(P2PService p2PService, PriceFeedService priceFeedService) { + public OfferBookService(P2PService p2PService, + PriceFeedService priceFeedService, + Storage offersJsonStorage, + @Named(CoreOptionKeys.DUMP_STATISTICS) boolean dumpStatistics) { this.p2PService = p2PService; this.priceFeedService = priceFeedService; + this.offersJsonStorage = offersJsonStorage; p2PService.addHashSetChangedListener(new HashMapChangedListener() { @Override @@ -79,10 +91,28 @@ public void onRemoved(ProtectedStorageEntry data) { }); } }); - } - public void addOfferBookChangedListener(OfferBookChangedListener offerBookChangedListener) { - offerBookChangedListeners.add(offerBookChangedListener); + if (dumpStatistics) { + this.offersJsonStorage.initWithFileName("offers_statistics.json"); + + p2PService.addP2PServiceListener(new BootstrapListener() { + @Override + public void onBootstrapComplete() { + addOfferBookChangedListener(new OfferBookChangedListener() { + @Override + public void onAdded(Offer offer) { + doDumpStatistics(); + } + + @Override + public void onRemoved(Offer offer) { + doDumpStatistics(); + } + }); + UserThread.runAfter(OfferBookService.this::doDumpStatistics, 1); + } + }); + } } @@ -140,4 +170,42 @@ public void removeOfferAtShutDown(Offer offer) { public boolean isBootstrapped() { return p2PService.isBootstrapped(); } + + public void addOfferBookChangedListener(OfferBookChangedListener offerBookChangedListener) { + offerBookChangedListeners.add(offerBookChangedListener); + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // Private + /////////////////////////////////////////////////////////////////////////////////////////// + + private void doDumpStatistics() { + // We filter the case that it is a MarketBasedPrice but the price is not available + // That should only be possible if the price feed provider is not available + final List flatOffers = getOffers().stream() + .filter(offer -> !offer.getUseMarketBasedPrice() || priceFeedService.getMarketPrice(offer.getCurrencyCode()) != null) + .map(offer -> { + try { + return new FlatOffer(offer.getDirection(), + offer.getCurrencyCode(), + offer.getMinAmount(), + offer.getAmount(), + offer.getPrice(), + offer.getDate(), + offer.getId(), + offer.getUseMarketBasedPrice(), + offer.getMarketPriceMargin(), + offer.getPaymentMethod(), + offer.getOfferFeePaymentTxID() + ); + } catch (Throwable t) { + // In case a offer was corrupted with null values we ignore it + return null; + } + }) + .filter(e -> e != null) + .collect(Collectors.toList()); + offersJsonStorage.queueUpForSave(new PlainTextWrapper(Utilities.objectToJson(flatOffers)), 5000); + } } diff --git a/core/src/main/java/io/bitsquare/trade/statistics/TradeStatistics.java b/core/src/main/java/io/bitsquare/trade/statistics/TradeStatistics.java index ca3481c5901..e563dab863f 100644 --- a/core/src/main/java/io/bitsquare/trade/statistics/TradeStatistics.java +++ b/core/src/main/java/io/bitsquare/trade/statistics/TradeStatistics.java @@ -5,7 +5,8 @@ import io.bitsquare.common.crypto.PubKeyRing; import io.bitsquare.common.util.JsonExclude; import io.bitsquare.p2p.storage.payload.CapabilityRequiringPayload; -import io.bitsquare.p2p.storage.payload.StoragePayload; +import io.bitsquare.p2p.storage.payload.LazyProcessedStoragePayload; +import io.bitsquare.p2p.storage.payload.PersistedStoragePayload; import io.bitsquare.trade.offer.Offer; import org.bitcoinj.core.Coin; import org.bitcoinj.utils.ExchangeRate; @@ -19,7 +20,7 @@ import java.util.concurrent.TimeUnit; @Immutable -public final class TradeStatistics implements StoragePayload, CapabilityRequiringPayload { +public final class TradeStatistics implements LazyProcessedStoragePayload, CapabilityRequiringPayload, PersistedStoragePayload { @JsonExclude private static final long serialVersionUID = Version.P2P_NETWORK_VERSION; @JsonExclude @@ -110,12 +111,16 @@ public boolean equals(Object o) { if (offerAmount != that.offerAmount) return false; if (offerMinAmount != that.offerMinAmount) return false; if (currency != null ? !currency.equals(that.currency) : that.currency != null) return false; - if (direction != that.direction) return false; + + if (direction != null && that.direction != null && direction.ordinal() != that.direction.ordinal()) + return false; + else if ((direction == null && that.direction != null) || (direction != null && that.direction == null)) + return false; + if (paymentMethod != null ? !paymentMethod.equals(that.paymentMethod) : that.paymentMethod != null) return false; if (offerId != null ? !offerId.equals(that.offerId) : that.offerId != null) return false; return !(depositTxId != null ? !depositTxId.equals(that.depositTxId) : that.depositTxId != null); - } @Override @@ -123,7 +128,7 @@ public int hashCode() { int result; long temp; result = currency != null ? currency.hashCode() : 0; - result = 31 * result + (direction != null ? direction.hashCode() : 0); + result = 31 * result + (direction != null ? direction.ordinal() : 0); result = 31 * result + (int) (tradePrice ^ (tradePrice >>> 32)); result = 31 * result + (int) (tradeAmount ^ (tradeAmount >>> 32)); result = 31 * result + (paymentMethod != null ? paymentMethod.hashCode() : 0); @@ -155,6 +160,7 @@ public String toString() { ", offerId='" + offerId + '\'' + ", depositTxId='" + depositTxId + '\'' + ", pubKeyRing=" + pubKeyRing + + ", hashCode=" + hashCode() + '}'; } } diff --git a/core/src/main/java/io/bitsquare/trade/statistics/TradeStatisticsManager.java b/core/src/main/java/io/bitsquare/trade/statistics/TradeStatisticsManager.java index a9ddfb707a8..21ec2730570 100644 --- a/core/src/main/java/io/bitsquare/trade/statistics/TradeStatisticsManager.java +++ b/core/src/main/java/io/bitsquare/trade/statistics/TradeStatisticsManager.java @@ -10,6 +10,7 @@ import io.bitsquare.p2p.storage.HashMapChangedListener; import io.bitsquare.p2p.storage.payload.StoragePayload; import io.bitsquare.p2p.storage.storageentry.ProtectedStorageEntry; +import io.bitsquare.storage.PlainTextWrapper; import io.bitsquare.storage.Storage; import javafx.collections.FXCollections; import javafx.collections.ObservableSet; @@ -25,18 +26,18 @@ public class TradeStatisticsManager { private static final Logger log = LoggerFactory.getLogger(TradeStatisticsManager.class); private final Storage> statisticsStorage; - private Storage fiatCurrencyListJsonStorage; - private Storage cryptoCurrencyListJsonStorage; - private Storage statisticsJsonStorage; + private Storage fiatCurrencyListJsonStorage; + private Storage cryptoCurrencyListJsonStorage; + private Storage statisticsJsonStorage; private boolean dumpStatistics; private ObservableSet observableTradeStatisticsSet = FXCollections.observableSet(); private HashSet tradeStatisticsSet = new HashSet<>(); @Inject public TradeStatisticsManager(Storage> statisticsStorage, - Storage fiatCurrencyListJsonStorage, - Storage cryptoCurrencyListJsonStorage, - Storage statisticsJsonStorage, + Storage fiatCurrencyListJsonStorage, + Storage cryptoCurrencyListJsonStorage, + Storage statisticsJsonStorage, P2PService p2PService, @Named(CoreOptionKeys.DUMP_STATISTICS) boolean dumpStatistics) { this.statisticsStorage = statisticsStorage; @@ -45,20 +46,24 @@ public TradeStatisticsManager(Storage> statisticsStorag this.statisticsJsonStorage = statisticsJsonStorage; this.dumpStatistics = dumpStatistics; + init(p2PService); + } + + private void init(P2PService p2PService) { if (dumpStatistics) { - this.statisticsJsonStorage.initAndGetPersistedWithFileName("trade_statistics.json"); + this.statisticsJsonStorage.initWithFileName("trade_statistics.json"); - this.fiatCurrencyListJsonStorage.initAndGetPersistedWithFileName("fiat_currency_list.json"); + this.fiatCurrencyListJsonStorage.initWithFileName("fiat_currency_list.json"); ArrayList fiatCurrencyList = new ArrayList<>(CurrencyUtil.getAllSortedFiatCurrencies().stream() .map(e -> new CurrencyTuple(e.getCode(), e.getName())) .collect(Collectors.toList())); - fiatCurrencyListJsonStorage.queueUpForSave(Utilities.objectToJson(fiatCurrencyList), 2000); + fiatCurrencyListJsonStorage.queueUpForSave(new PlainTextWrapper(Utilities.objectToJson(fiatCurrencyList)), 2000); - this.cryptoCurrencyListJsonStorage.initAndGetPersistedWithFileName("crypto_currency_list.json"); + this.cryptoCurrencyListJsonStorage.initWithFileName("crypto_currency_list.json"); ArrayList cryptoCurrencyList = new ArrayList<>(CurrencyUtil.getAllSortedCryptoCurrencies().stream() .map(e -> new CurrencyTuple(e.getCode(), e.getName())) .collect(Collectors.toList())); - cryptoCurrencyListJsonStorage.queueUpForSave(Utilities.objectToJson(cryptoCurrencyList), 2000); + cryptoCurrencyListJsonStorage.queueUpForSave(new PlainTextWrapper(Utilities.objectToJson(cryptoCurrencyList)), 2000); } HashSet persisted = statisticsStorage.initAndGetPersistedWithFileName("TradeStatistics"); @@ -78,6 +83,13 @@ public void onRemoved(ProtectedStorageEntry data) { // We don't remove items } }); + + // At startup the P2PDataStorage inits earlier, otherwise we ge the listener called. + p2PService.getP2PDataStorage().getMap().values().forEach(e -> { + final StoragePayload storagePayload = e.getStoragePayload(); + if (storagePayload instanceof TradeStatistics) + add((TradeStatistics) storagePayload); + }); } public void add(TradeStatistics tradeStatistics) { @@ -90,7 +102,7 @@ public void add(TradeStatistics tradeStatistics) { dump(); } else { - log.error("We have already an item with the same offer ID. That might happen if both the offerer and the taker published the tradeStatistics"); + log.debug("We have already an item with the same offer ID. That might happen if both the offerer and the taker published the tradeStatistics"); } } } @@ -111,8 +123,7 @@ private void dump() { list.sort((o1, o2) -> (o1.tradeDate < o2.tradeDate ? 1 : (o1.tradeDate == o2.tradeDate ? 0 : -1))); TradeStatistics[] array = new TradeStatistics[tradeStatisticsSet.size()]; list.toArray(array); - statisticsJsonStorage.queueUpForSave(Utilities.objectToJson(array), 5000); + statisticsJsonStorage.queueUpForSave(new PlainTextWrapper(Utilities.objectToJson(array)), 5000); } } - } diff --git a/doc/build.md b/doc/build.md index 661ec87ebf8..4a79f134d2a 100644 --- a/doc/build.md +++ b/doc/build.md @@ -5,6 +5,8 @@ This guide will walk you through the process of building Bitsquare from source. > _**NOTE:** For most users, building from source is not necessary. See the [releases page](https://github.com/bitsquare/bitsquare/releases), where you'll find installers for Windows, Linux and Mac OS X._ +There is an install script (2 parts) for setup (JDK, git, maven, Bitcoinj, Bitsquare) on Linux in that directory (install_on_unix.sh, install_on_unix_fin.sh). + System requirements ------------- @@ -27,7 +29,7 @@ To install the Oracle JDK use: $ sudo add-apt-repository ppa:webupd8team/java $ sudo apt-get update - $ sudo apt-get -y oracle-java8-installer install + $ sudo apt-get -y install oracle-java8-installer Check if $JAVA_HOME is set @@ -71,11 +73,11 @@ You need to get the Bitsquare dependencies first as we need to copy the BountyCa ### 4. Copy the jdkfix jar file -Copy the jdkfix-0.4.9.2.jar from the Bitsquare jdkfix/target directory to $JAVA_HOME/jre/lib/ext/. -jdkfix-0.4.9.2.jar includes a bugfix of the SortedList class which will be released with the next JDK version. +Copy the jdkfix-0.4.9.3.jar from the Bitsquare jdkfix/target directory to $JAVA_HOME/jre/lib/ext/. +jdkfix-0.4.9.3.jar includes a bugfix of the SortedList class which will be released with the next JDK version. We need to load that class before the default java class. This step will be removed once the bugfix is in the official JDK. - $ sudo cp bitsquare/jdkfix/target/jdkfix-0.4.9.2.jar $JAVA_HOME/jre/lib/ext/jdkfix-0.4.9.2.jar + $ sudo cp bitsquare/jdkfix/target/jdkfix-0.4.9.3.jar $JAVA_HOME/jre/lib/ext/jdkfix-0.4.9.3.jar ### 5. Copy the BountyCastle provider jar file @@ -105,6 +107,10 @@ You will get an error when building Bitsquare package if you don't have these. $ unzip jce_policy-8.zip $ sudo cp UnlimitedJCEPolicyJDK8/US_export_policy.jar $JAVA_HOME/jre/lib/security/US_export_policy.jar $ sudo cp UnlimitedJCEPolicyJDK8/local_policy.jar $JAVA_HOME/jre/lib/security/local_policy.jar + $ sudo chmod 777 /usr/lib/jvm/java-8-oracle/jre/lib/security/US_export_policy.jar + $ sudo chmod 777 /usr/lib/jvm/java-8-oracle/jre/lib/security/local_policy.jar + $ sudo rm -r UnlimitedJCEPolicyJDK8 + $ sudo rm jce_policy-8.zip Build Bitsquare ----------------- diff --git a/doc/install_on_unix.sh b/doc/install_on_unix.sh new file mode 100755 index 00000000000..8000d8b92cb --- /dev/null +++ b/doc/install_on_unix.sh @@ -0,0 +1,45 @@ +#!/bin/sh + +sudo -i + +JAVA_HOME=/usr/lib/jvm/java-8-oracle + +echo "Install Oracle Java 8, git, maven, unzip" +apt-get update +add-apt-repository ppa:webupd8team/java +apt-get update +apt-get -y install oracle-java8-installer git maven unzip + + +echo "Enable unlimited Strength for cryptographic keys" +wget --no-check-certificate --no-cookies --header "Cookie: oraclelicense=accept-securebackup-cookie" http://download.oracle.com/otn-pub/java/jce/8/jce_policy-8.zip +unzip jce_policy-8.zip +cp UnlimitedJCEPolicyJDK8/US_export_policy.jar $JAVA_HOME/jre/lib/security/US_export_policy.jar +cp UnlimitedJCEPolicyJDK8/local_policy.jar $JAVA_HOME/jre/lib/security/local_policy.jar + +chmod 777 $JAVA_HOME/jre/lib/security/US_export_policy.jar +chmod 777 $JAVA_HOME/jre/lib/security/local_policy.jar + +rm -r UnlimitedJCEPolicyJDK8 +rm jce_policy-8.zip + +echo "Install bitcoinj" +cd ~ +git clone -b FixBloomFilters https://github.com/bitsquare/bitcoinj.git +cd bitcoinj +mvn clean install -DskipTests -Dmaven.javadoc.skip=true + +echo "Install and resolve dependencies for bitsquare" +cd ~ +git clone https://github.com/bitsquare/bitsquare.git +cd bitsquare +mvn clean package -DskipTests -Dmaven.javadoc.skip=true + +echo "Copy the jdkfix jar file" +cp bitsquare/jdkfix/target/jdkfix-0.4.9.3.jar $JAVA_HOME/jre/lib/ext/jdkfix-0.4.9.3.jar + +echo "Add BountyCastle.jar" +cd ~ +cp /root/.m2/repository/org/bouncycastle/bcprov-jdk15on/1.53/bcprov-jdk15on-1.53.jar $JAVA_HOME/jre/lib/ext/bcprov-jdk15on-1.53.jar + + diff --git a/doc/install_on_unix_fin.sh b/doc/install_on_unix_fin.sh new file mode 100755 index 00000000000..2903364a65f --- /dev/null +++ b/doc/install_on_unix_fin.sh @@ -0,0 +1,23 @@ +#!/bin/sh + +sudo -i + +JAVA_HOME=/usr/lib/jvm/java-8-oracle + +# Before running the second part edit $JAVA_HOME/jre/lib/security/java.security file +# add line: security.provider.10=org.bouncycastle.jce.provider.BouncyCastleProvider + +# and add JAVA_HOME to .bashrc +# export JAVA_HOME=/usr/lib/jvm/java-8-oracle + +echo "Install bitsquare" +cd ~/bitsquare +mvn clean package -DskipTests -Dmaven.javadoc.skip=true +cd .. +mkdir .local +mkdir .local/share + +echo "Start bitsquare" +java -jar ~/bitsquare/gui/target/shaded.jar + + diff --git a/gui/pom.xml b/gui/pom.xml index 0fe6f1ab14e..2debf3f952c 100644 --- a/gui/pom.xml +++ b/gui/pom.xml @@ -22,7 +22,7 @@ parent io.bitsquare - 0.4.9.2 + 0.4.9.3 4.0.0 diff --git a/gui/src/main/java/io/bitsquare/app/BitsquareApp.java b/gui/src/main/java/io/bitsquare/app/BitsquareApp.java index 1f4531bccf0..0f585be533c 100644 --- a/gui/src/main/java/io/bitsquare/app/BitsquareApp.java +++ b/gui/src/main/java/io/bitsquare/app/BitsquareApp.java @@ -27,6 +27,7 @@ import io.bitsquare.common.CommonOptionKeys; import io.bitsquare.common.UserThread; import io.bitsquare.common.handlers.ResultHandler; +import io.bitsquare.common.util.Profiler; import io.bitsquare.common.util.Utilities; import io.bitsquare.filter.FilterManager; import io.bitsquare.gui.SystemTray; @@ -83,6 +84,8 @@ public class BitsquareApp extends Application { private static final Logger log = (ch.qos.logback.classic.Logger) LoggerFactory.getLogger(BitsquareApp.class); + private static final long LOG_MEMORY_PERIOD_MIN = 10; + private static Environment env; private BitsquareAppModule bitsquareAppModule; @@ -237,6 +240,8 @@ else if (Utilities.isWindows()) .show(); } + UserThread.runPeriodically(() -> Profiler.printSystemLoad(log), LOG_MEMORY_PERIOD_MIN, TimeUnit.MINUTES); + } catch (Throwable throwable) { showErrorPopup(throwable, false); } diff --git a/gui/src/main/java/io/bitsquare/gui/Navigation.java b/gui/src/main/java/io/bitsquare/gui/Navigation.java index a5bac398d97..28ed5c6a0af 100644 --- a/gui/src/main/java/io/bitsquare/gui/Navigation.java +++ b/gui/src/main/java/io/bitsquare/gui/Navigation.java @@ -59,6 +59,7 @@ public interface Listener { @Inject public Navigation(Storage storage) { this.storage = storage; + storage.setNumMaxBackupFiles(3); Navigation persisted = storage.initAndGetPersisted(this); if (persisted != null) { diff --git a/gui/src/main/java/io/bitsquare/gui/components/paymentmethods/AliPayForm.java b/gui/src/main/java/io/bitsquare/gui/components/paymentmethods/AliPayForm.java index f64e56cdc47..ce1e98dafec 100644 --- a/gui/src/main/java/io/bitsquare/gui/components/paymentmethods/AliPayForm.java +++ b/gui/src/main/java/io/bitsquare/gui/components/paymentmethods/AliPayForm.java @@ -73,9 +73,9 @@ public void addFormForAddAccount() { protected void autoFillNameTextField() { if (useCustomAccountNameCheckBox != null && !useCustomAccountNameCheckBox.isSelected()) { String accountNr = accountNrInputTextField.getText(); - accountNr = StringUtils.abbreviate(accountNr, 5); + accountNr = StringUtils.abbreviate(accountNr, 9); String method = BSResources.get(paymentAccount.getPaymentMethod().getId()); - accountNameTextField.setText(method.concat(", ").concat(accountNr)); + accountNameTextField.setText(method.concat(": ").concat(accountNr)); } } diff --git a/gui/src/main/java/io/bitsquare/gui/components/paymentmethods/BankForm.java b/gui/src/main/java/io/bitsquare/gui/components/paymentmethods/BankForm.java index 6f6dd5a0dd3..dce3aaa6a43 100644 --- a/gui/src/main/java/io/bitsquare/gui/components/paymentmethods/BankForm.java +++ b/gui/src/main/java/io/bitsquare/gui/components/paymentmethods/BankForm.java @@ -470,27 +470,27 @@ protected void autoFillNameTextField() { countryCode = ""; if (BankUtil.isBankIdRequired(countryCode)) { bankId = bankIdInputTextField.getText(); - if (bankId.length() > 6) + if (bankId.length() > 9) bankId = StringUtils.abbreviate(bankId, 9); } else if (BankUtil.isBranchIdRequired(countryCode)) { bankId = branchIdInputTextField.getText(); - if (bankId.length() > 6) + if (bankId.length() > 9) bankId = StringUtils.abbreviate(bankId, 9); } else if (BankUtil.isBankNameRequired(countryCode)) { bankId = bankNameInputTextField.getText(); - if (bankId.length() > 6) + if (bankId.length() > 9) bankId = StringUtils.abbreviate(bankId, 9); } String accountNr = accountNrInputTextField.getText(); - if (accountNr.length() > 6) + if (accountNr.length() > 9) accountNr = StringUtils.abbreviate(accountNr, 9); String method = BSResources.get(paymentAccount.getPaymentMethod().getId()); - if (bankId != null) - accountNameTextField.setText(method.concat(", ").concat(bankId).concat(", ").concat(accountNr)); + if (bankId != null && !bankId.isEmpty()) + accountNameTextField.setText(method.concat(": ").concat(bankId).concat(", ").concat(accountNr)); else - accountNameTextField.setText(method.concat(", ").concat(accountNr)); + accountNameTextField.setText(method.concat(": ").concat(accountNr)); } } diff --git a/gui/src/main/java/io/bitsquare/gui/components/paymentmethods/CryptoCurrencyForm.java b/gui/src/main/java/io/bitsquare/gui/components/paymentmethods/CryptoCurrencyForm.java index 476e3b80732..1fc5a3a8014 100644 --- a/gui/src/main/java/io/bitsquare/gui/components/paymentmethods/CryptoCurrencyForm.java +++ b/gui/src/main/java/io/bitsquare/gui/components/paymentmethods/CryptoCurrencyForm.java @@ -72,7 +72,7 @@ public void addFormForAddAccount() { addTradeCurrencyComboBox(); currencyComboBox.setPrefWidth(250); - Tuple2 tuple2 = addLabelInputTextField(gridPane, ++gridRow, "Cryptocurrency address:"); + Tuple2 tuple2 = addLabelInputTextField(gridPane, ++gridRow, "Altcoin address:"); addressLabel = tuple2.first; addressInputTextField = tuple2.second; addressInputTextField.setValidator(altCoinAddressValidator); @@ -100,7 +100,7 @@ protected void autoFillNameTextField() { String address = addressInputTextField.getText(); address = StringUtils.abbreviate(address, 9); String currency = paymentAccount.getSingleTradeCurrency() != null ? paymentAccount.getSingleTradeCurrency().getCode() : "?"; - accountNameTextField.setText(method.concat(", ").concat(currency).concat(", ").concat(address)); + accountNameTextField.setText(currency.concat(": ").concat(address)); } } @@ -109,11 +109,11 @@ public void addFormForDisplayAccount() { gridRowFrom = gridRow; addLabelTextField(gridPane, gridRow, "Account name:", cryptoCurrencyAccount.getAccountName(), Layout.FIRST_ROW_AND_GROUP_DISTANCE); addLabelTextField(gridPane, ++gridRow, "Payment method:", BSResources.get(cryptoCurrencyAccount.getPaymentMethod().getId())); - Tuple2 tuple2 = addLabelTextField(gridPane, ++gridRow, "Cryptocurrency address:", cryptoCurrencyAccount.getAddress()); + Tuple2 tuple2 = addLabelTextField(gridPane, ++gridRow, "Altcoin address:", cryptoCurrencyAccount.getAddress()); addressLabel = tuple2.first; TextField field = tuple2.second; field.setMouseTransparent(false); - addLabelTextField(gridPane, ++gridRow, "Cryptocurrency:", cryptoCurrencyAccount.getSingleTradeCurrency().getNameAndCode()); + addLabelTextField(gridPane, ++gridRow, "Altcoin:", cryptoCurrencyAccount.getSingleTradeCurrency().getNameAndCode()); addAllowedPeriod(); } @@ -126,8 +126,8 @@ public void updateAllInputsValid() { @Override protected void addTradeCurrencyComboBox() { - currencyComboBox = addLabelSearchComboBox(gridPane, ++gridRow, "Cryptocurrency:", Layout.FIRST_ROW_AND_GROUP_DISTANCE).second; - currencyComboBox.setPromptText("Select or search cryptocurrency"); + currencyComboBox = addLabelSearchComboBox(gridPane, ++gridRow, "Altcoin:", Layout.FIRST_ROW_AND_GROUP_DISTANCE).second; + currencyComboBox.setPromptText("Select or search altcoin"); currencyComboBox.setItems(FXCollections.observableArrayList(CurrencyUtil.getAllSortedCryptoCurrencies())); currencyComboBox.setVisibleRowCount(Math.min(currencyComboBox.getItems().size(), 20)); currencyComboBox.setConverter(new StringConverter() { diff --git a/gui/src/main/java/io/bitsquare/gui/components/paymentmethods/OKPayForm.java b/gui/src/main/java/io/bitsquare/gui/components/paymentmethods/OKPayForm.java index b58d815bf23..156b2a88b2a 100644 --- a/gui/src/main/java/io/bitsquare/gui/components/paymentmethods/OKPayForm.java +++ b/gui/src/main/java/io/bitsquare/gui/components/paymentmethods/OKPayForm.java @@ -117,9 +117,9 @@ private void addCurrenciesGrid(boolean isEditable) { protected void autoFillNameTextField() { if (useCustomAccountNameCheckBox != null && !useCustomAccountNameCheckBox.isSelected()) { String accountNr = accountNrInputTextField.getText(); - accountNr = StringUtils.abbreviate(accountNr, 5); + accountNr = StringUtils.abbreviate(accountNr, 9); String method = BSResources.get(paymentAccount.getPaymentMethod().getId()); - accountNameTextField.setText(method.concat(", ").concat(accountNr)); + accountNameTextField.setText(method.concat(": ").concat(accountNr)); } } diff --git a/gui/src/main/java/io/bitsquare/gui/components/paymentmethods/PerfectMoneyForm.java b/gui/src/main/java/io/bitsquare/gui/components/paymentmethods/PerfectMoneyForm.java index 99a5d73ba2a..ea96896fbd1 100644 --- a/gui/src/main/java/io/bitsquare/gui/components/paymentmethods/PerfectMoneyForm.java +++ b/gui/src/main/java/io/bitsquare/gui/components/paymentmethods/PerfectMoneyForm.java @@ -75,9 +75,9 @@ public void addFormForAddAccount() { protected void autoFillNameTextField() { if (useCustomAccountNameCheckBox != null && !useCustomAccountNameCheckBox.isSelected()) { String accountNr = accountNrInputTextField.getText(); - accountNr = StringUtils.abbreviate(accountNr, 5); + accountNr = StringUtils.abbreviate(accountNr, 9); String method = BSResources.get(paymentAccount.getPaymentMethod().getId()); - accountNameTextField.setText(method.concat(", ").concat(accountNr)); + accountNameTextField.setText(method.concat(": ").concat(accountNr)); } } diff --git a/gui/src/main/java/io/bitsquare/gui/components/paymentmethods/SepaForm.java b/gui/src/main/java/io/bitsquare/gui/components/paymentmethods/SepaForm.java index 81817122c03..9a286545e6d 100644 --- a/gui/src/main/java/io/bitsquare/gui/components/paymentmethods/SepaForm.java +++ b/gui/src/main/java/io/bitsquare/gui/components/paymentmethods/SepaForm.java @@ -280,12 +280,12 @@ private void updateCountriesSelection(boolean isEditable, List checkBo protected void autoFillNameTextField() { if (useCustomAccountNameCheckBox != null && !useCustomAccountNameCheckBox.isSelected()) { String iban = ibanInputTextField.getText(); - if (iban.length() > 5) - iban = StringUtils.abbreviate(iban, 5); + if (iban.length() > 9) + iban = StringUtils.abbreviate(iban, 9); String method = BSResources.get(paymentAccount.getPaymentMethod().getId()); String country = ((CountryBasedPaymentAccount) paymentAccount).getCountry() != null ? ((CountryBasedPaymentAccount) paymentAccount).getCountry().code : "?"; String currency = paymentAccount.getSingleTradeCurrency() != null ? paymentAccount.getSingleTradeCurrency().getCode() : "?"; - accountNameTextField.setText(method.concat(", ").concat(currency).concat(", ").concat(country).concat(", ").concat(iban)); + accountNameTextField.setText(method.concat(" (").concat(currency).concat("/").concat(country).concat("): ").concat(iban)); } } diff --git a/gui/src/main/java/io/bitsquare/gui/components/paymentmethods/SwishForm.java b/gui/src/main/java/io/bitsquare/gui/components/paymentmethods/SwishForm.java index f868287763e..e41b6cba43d 100644 --- a/gui/src/main/java/io/bitsquare/gui/components/paymentmethods/SwishForm.java +++ b/gui/src/main/java/io/bitsquare/gui/components/paymentmethods/SwishForm.java @@ -82,9 +82,9 @@ public void addFormForAddAccount() { protected void autoFillNameTextField() { if (useCustomAccountNameCheckBox != null && !useCustomAccountNameCheckBox.isSelected()) { String mobileNr = mobileNrInputTextField.getText(); - mobileNr = StringUtils.abbreviate(mobileNr, 5); + mobileNr = StringUtils.abbreviate(mobileNr, 9); String method = BSResources.get(paymentAccount.getPaymentMethod().getId()); - accountNameTextField.setText(method.concat(", ").concat(mobileNr)); + accountNameTextField.setText(method.concat(": ").concat(mobileNr)); } } diff --git a/gui/src/main/java/io/bitsquare/gui/main/MainViewModel.java b/gui/src/main/java/io/bitsquare/gui/main/MainViewModel.java index 779cf4a85e8..8a169c8ebb2 100644 --- a/gui/src/main/java/io/bitsquare/gui/main/MainViewModel.java +++ b/gui/src/main/java/io/bitsquare/gui/main/MainViewModel.java @@ -63,6 +63,7 @@ import io.bitsquare.p2p.peers.keepalive.messages.Ping; import io.bitsquare.payment.CryptoCurrencyAccount; import io.bitsquare.payment.OKPayAccount; +import io.bitsquare.payment.PaymentAccount; import io.bitsquare.trade.Trade; import io.bitsquare.trade.TradeManager; import io.bitsquare.trade.offer.OpenOffer; @@ -74,6 +75,7 @@ import javafx.collections.FXCollections; import javafx.collections.ListChangeListener; import javafx.collections.ObservableList; +import javafx.collections.SetChangeListener; import org.bitcoinj.core.Address; import org.bitcoinj.core.Coin; import org.bitcoinj.core.Transaction; @@ -350,7 +352,7 @@ public void onTorNodeReady() { bootstrapState.set("Tor node created"); p2PNetworkIconId.set("image-connection-tor"); - if (preferences.getUseTorForBitcoinJ()) + if (preferences.getUseTorForBitcoinJ()) initWalletService(); } @@ -430,10 +432,10 @@ private void initBitcoinWallet() { // We only init wallet service here if not using Tor for bitcoinj. // When using Tor, wallet init must be deferred until Tor is ready. - if (!preferences.getUseTorForBitcoinJ()) + if (!preferences.getUseTorForBitcoinJ()) initWalletService(); } - + private void initWalletService() { Log.traceCall(); ObjectProperty walletServiceException = new SimpleObjectProperty<>(); @@ -459,10 +461,16 @@ private void initWalletService() { if (exception instanceof TimeoutException) { walletServiceErrorMsg.set("Connecting to the bitcoin network failed because of a timeout."); } else if (exception.getCause() instanceof BlockStoreException) { - new Popup().warning("Bitsquare is already running. You cannot run 2 instances of Bitsquare.") - .closeButtonText("Shut down") - .onClose(BitsquareApp.shutDownHandler::run) - .show(); + log.error(exception.getMessage()); + // Ugly, but no other way to cover that specific case + if (exception.getMessage().equals("Store file is already locked by another process")) + new Popup().warning("Bitsquare is already running. You cannot run 2 instances of Bitsquare.") + .closeButtonText("Shut down") + .onClose(BitsquareApp.shutDownHandler::run) + .show(); + else + new Popup().error("Cannot open wallet because of an exception:\n" + exception.getMessage()) + .show(); } else if (exception.getMessage() != null) { walletServiceErrorMsg.set("Connection to the bitcoin network failed because of an error:" + exception.getMessage()); } else { @@ -566,6 +574,7 @@ public void onBalanceChanged(Coin balance, Transaction tx) { @Override public void run() { try { + Thread.currentThread().setName("checkCryptoThread"); log.trace("Run crypto test"); // just use any simple dummy msg io.bitsquare.p2p.peers.keepalive.messages.Ping payload = new Ping(1, 1); @@ -605,6 +614,20 @@ public void run() { .onClose(() -> GUIUtil.openWebPage("https://github.com/bitsquare/bitsquare/issues")) .show(); } + + String remindPasswordAndBackupKey = "remindPasswordAndBackup"; + user.getPaymentAccountsAsObservable().addListener((SetChangeListener) change -> { + if (preferences.showAgain(remindPasswordAndBackupKey) && change.wasAdded()) { + new Popup<>().headLine("Important security recommendation") + .information("We would like to remind you to consider using password protection for your wallet if you have not already enabled that.\n\n" + + "It is also highly recommended to write down the wallet seed words. Those seed words are like a master password for recovering your Bitcoin wallet.\n" + + "At the \"Wallet Seed\" section you find more information.\n\n" + + "Additionally you can backup the complete application data folder at the \"Backup\" section.\n" + + "Please note, that this backup is not encrypted!") + .dontShowAgainId(remindPasswordAndBackupKey, preferences) + .show(); + } + }); } diff --git a/gui/src/main/java/io/bitsquare/gui/main/account/AccountView.java b/gui/src/main/java/io/bitsquare/gui/main/account/AccountView.java index 3c346516584..e040e7f208b 100644 --- a/gui/src/main/java/io/bitsquare/gui/main/account/AccountView.java +++ b/gui/src/main/java/io/bitsquare/gui/main/account/AccountView.java @@ -122,9 +122,11 @@ else if (arbitratorRegistrationTab != null) String key = "accountPrivacyInfo"; if (!DevFlags.DEV_MODE) new Popup().backgroundInfo("In the account screen you can setup your payment accounts for national currencies " + - "as well as for crypto currencies.\n\n" + - "Please note that this data is stored locally on your computer only. Bitsquare does not operate servers " + - "and has no access to users data.\n\n" + + "as well as for altcoins.\n\n" + + "For Bitcoin you don't need to set up an account.\n" + + "You can manage your Bitsquare wallet at the \"Funds\" section.\n\n" + + "Please note, that any account data is only stored locally on your computer. Bitsquare does not operate servers " + + "and has no access to users data!\n\n" + "When you are trading you will exchange in the trade process with your trading partner the " + "required account data for that trade (e.g. bank account data or altcoin address).") .dontShowAgainId(key, preferences) diff --git a/gui/src/main/java/io/bitsquare/gui/main/account/content/altcoinaccounts/AltCoinAccountsView.java b/gui/src/main/java/io/bitsquare/gui/main/account/content/altcoinaccounts/AltCoinAccountsView.java index e9a9d87cccf..8ded96da1e8 100644 --- a/gui/src/main/java/io/bitsquare/gui/main/account/content/altcoinaccounts/AltCoinAccountsView.java +++ b/gui/src/main/java/io/bitsquare/gui/main/account/content/altcoinaccounts/AltCoinAccountsView.java @@ -218,7 +218,7 @@ private void onDeleteAccount(PaymentAccount paymentAccount) { private void buildForm() { addTitledGroupBg(root, gridRow, 1, "Manage accounts"); - Tuple2 tuple = addLabelListView(root, gridRow, "Your cryptocurrency accounts:", Layout.FIRST_ROW_DISTANCE); + Tuple2 tuple = addLabelListView(root, gridRow, "Your altcoin accounts:", Layout.FIRST_ROW_DISTANCE); GridPane.setValignment(tuple.first, VPos.TOP); paymentAccountsListView = tuple.second; paymentAccountsListView.setPrefHeight(2 * Layout.LIST_ROW_HEIGHT + 14); diff --git a/gui/src/main/java/io/bitsquare/gui/main/account/content/seedwords/SeedWordsView.java b/gui/src/main/java/io/bitsquare/gui/main/account/content/seedwords/SeedWordsView.java index ee3941488d4..7f34ee5a35e 100644 --- a/gui/src/main/java/io/bitsquare/gui/main/account/content/seedwords/SeedWordsView.java +++ b/gui/src/main/java/io/bitsquare/gui/main/account/content/seedwords/SeedWordsView.java @@ -103,8 +103,9 @@ protected void initialize() { addTitledGroupBg(root, ++gridRow, 1, "Information", Layout.GROUP_DISTANCE); addMultilineLabel(root, gridRow, "Please write down you wallet seed words.\n" + - "You can recover your wallet with those seed words.\n" + - "Please note that the wallet date is the date of the wallet into which you want to restore!", + "You can recover your wallet with those seed words.\n\n" + + "If you are restoring your wallet you need to use the wallet date of the wallet into which you want to restore.\n" + + "So in case you restore to a new wallet it is the today's date.", Layout.FIRST_ROW_AND_GROUP_DISTANCE); diff --git a/gui/src/main/java/io/bitsquare/gui/main/account/settings/AccountSettingsView.java b/gui/src/main/java/io/bitsquare/gui/main/account/settings/AccountSettingsView.java index e387fce199d..82eb6e3c633 100644 --- a/gui/src/main/java/io/bitsquare/gui/main/account/settings/AccountSettingsView.java +++ b/gui/src/main/java/io/bitsquare/gui/main/account/settings/AccountSettingsView.java @@ -77,7 +77,7 @@ public void initialize() { ToggleGroup toggleGroup = new ToggleGroup(); paymentAccount = new MenuItem(navigation, toggleGroup, "National currency accounts", FiatAccountsView.class, AwesomeIcon.MONEY); - altCoinsAccountView = new MenuItem(navigation, toggleGroup, "Cryptocurrency accounts", AltCoinAccountsView.class, AwesomeIcon.LINK); + altCoinsAccountView = new MenuItem(navigation, toggleGroup, "Altcoin accounts", AltCoinAccountsView.class, AwesomeIcon.LINK); arbitratorSelection = new MenuItem(navigation, toggleGroup, "Arbitrator selection", ArbitratorSelectionView.class, AwesomeIcon.USER_MD); password = new MenuItem(navigation, toggleGroup, "Wallet password", PasswordView.class, AwesomeIcon.UNLOCK_ALT); seedWords = new MenuItem(navigation, toggleGroup, "Wallet seed", SeedWordsView.class, AwesomeIcon.KEY); diff --git a/gui/src/main/java/io/bitsquare/gui/main/market/trades/TradesChartsViewModel.java b/gui/src/main/java/io/bitsquare/gui/main/market/trades/TradesChartsViewModel.java index a496deb04ca..9a04c1a715c 100644 --- a/gui/src/main/java/io/bitsquare/gui/main/market/trades/TradesChartsViewModel.java +++ b/gui/src/main/java/io/bitsquare/gui/main/market/trades/TradesChartsViewModel.java @@ -81,7 +81,7 @@ public enum TickUnit { ObservableList> priceItems = FXCollections.observableArrayList(); ObservableList> volumeItems = FXCollections.observableArrayList(); - TickUnit tickUnit = TickUnit.MONTH; + TickUnit tickUnit = TickUnit.DAY; int maxTicks = 30; private int selectedTabIndex; diff --git a/gui/src/main/java/io/bitsquare/gui/main/offer/createoffer/CreateOfferView.java b/gui/src/main/java/io/bitsquare/gui/main/offer/createoffer/CreateOfferView.java index b7169ab5bbf..90b826801b4 100644 --- a/gui/src/main/java/io/bitsquare/gui/main/offer/createoffer/CreateOfferView.java +++ b/gui/src/main/java/io/bitsquare/gui/main/offer/createoffer/CreateOfferView.java @@ -763,6 +763,7 @@ private void addPaymentGroup() { paymentAccountsComboBox = addLabelComboBox(gridPane, gridRow, "Payment account:", Layout.FIRST_ROW_DISTANCE).second; paymentAccountsComboBox.setPromptText("Select payment account"); + paymentAccountsComboBox.setMinWidth(300); editOfferElements.add(paymentAccountsComboBox); // we display either currencyComboBox (multi currency account) or currencyTextField (single) diff --git a/gui/src/main/java/io/bitsquare/gui/main/offer/offerbook/OfferBookView.java b/gui/src/main/java/io/bitsquare/gui/main/offer/offerbook/OfferBookView.java index 80daf3581db..38cadaac850 100644 --- a/gui/src/main/java/io/bitsquare/gui/main/offer/offerbook/OfferBookView.java +++ b/gui/src/main/java/io/bitsquare/gui/main/offer/offerbook/OfferBookView.java @@ -300,7 +300,7 @@ public void onTabSelected(boolean isSelected) { private void onCreateOffer() { if (!model.hasPaymentAccount()) { openPopupForMissingAccountSetup("You have not setup a payment account", - "You need to setup a national currency or cryptocurrency account before you can create an offer.\n" + + "You need to setup a national currency or altcoin account before you can create an offer.\n" + "Do you want to setup an account?", FiatAccountsView.class, "\"Account\""); } else if (!model.hasPaymentAccountForCurrency()) { openPopupForMissingAccountSetup("No matching payment account", diff --git a/gui/src/main/java/io/bitsquare/gui/main/offer/offerbook/OfferBookViewModel.java b/gui/src/main/java/io/bitsquare/gui/main/offer/offerbook/OfferBookViewModel.java index de07cc8b111..7fc84e8ebc6 100644 --- a/gui/src/main/java/io/bitsquare/gui/main/offer/offerbook/OfferBookViewModel.java +++ b/gui/src/main/java/io/bitsquare/gui/main/offer/offerbook/OfferBookViewModel.java @@ -313,26 +313,35 @@ String getPaymentMethodToolTip(OfferBookListItem item) { String result = ""; if (item != null) { Offer offer = item.getOffer(); - String method = BSResources.get(offer.getPaymentMethod().getId()); + result = "Payment method: " + BSResources.get(offer.getPaymentMethod().getId()); + result += "\nCurrency: " + CurrencyUtil.getNameAndCode(offer.getCurrencyCode()); + String methodCountryCode = offer.getCountryCode(); + if (methodCountryCode != null) { + String bankId = offer.getBankId(); + if (bankId != null && !bankId.equals("null")) { + if (BankUtil.isBankIdRequired(methodCountryCode)) + result += "\nOfferers bank ID: " + bankId; + else if (BankUtil.isBankNameRequired(methodCountryCode)) + result += "\nOfferers bank name: " + bankId; + } + } if (methodCountryCode != null) - result = method + "\n\nOfferers seat of bank country:\n" + CountryUtil.getNameByCode(methodCountryCode); - else - result = method; + result += "\nOfferers seat of bank country: " + CountryUtil.getNameByCode(methodCountryCode); List acceptedCountryCodes = offer.getAcceptedCountryCodes(); List acceptedBanks = offer.getAcceptedBankIds(); if (acceptedCountryCodes != null && !acceptedCountryCodes.isEmpty()) { if (CountryUtil.containsAllSepaEuroCountries(acceptedCountryCodes)) - result += "\n\nAccepted takers seat of bank countries:\nAll Euro countries"; + result += "\nAccepted seat of bank countries (taker): All Euro countries"; else - result += "\n\nAccepted taker seat of bank countries:\n" + CountryUtil.getNamesByCodesString(acceptedCountryCodes); + result += "\nAccepted seat of bank countries (taker):\n" + CountryUtil.getNamesByCodesString(acceptedCountryCodes); } else if (acceptedBanks != null && !acceptedBanks.isEmpty()) { if (offer.getPaymentMethod().equals(PaymentMethod.SAME_BANK)) - result += "\n\nBank name: " + acceptedBanks.get(0); + result += "\nBank name: " + acceptedBanks.get(0); else if (offer.getPaymentMethod().equals(PaymentMethod.SPECIFIC_BANKS)) - result += "\n\nAccepted banks: " + Joiner.on(", ").join(acceptedBanks); + result += "\nAccepted banks: " + Joiner.on(", ").join(acceptedBanks); } } return result; diff --git a/gui/src/main/java/io/bitsquare/gui/main/overlays/windows/OfferDetailsWindow.java b/gui/src/main/java/io/bitsquare/gui/main/overlays/windows/OfferDetailsWindow.java index 05e64c3ea37..8c07e49b392 100644 --- a/gui/src/main/java/io/bitsquare/gui/main/overlays/windows/OfferDetailsWindow.java +++ b/gui/src/main/java/io/bitsquare/gui/main/overlays/windows/OfferDetailsWindow.java @@ -238,8 +238,13 @@ else if (BankUtil.isBankNameRequired(offer.getCountryCode())) if (CountryUtil.containsAllSepaEuroCountries(acceptedCountryCodes)) { countries = "All Euro countries"; } else { - countries = CountryUtil.getCodesString(acceptedCountryCodes); - tooltip = new Tooltip(CountryUtil.getNamesByCodesString(acceptedCountryCodes)); + if (acceptedCountryCodes.size() == 1) { + countries = CountryUtil.getNameAndCode(acceptedCountryCodes.get(0)); + tooltip = new Tooltip(countries); + } else { + countries = CountryUtil.getCodesString(acceptedCountryCodes); + tooltip = new Tooltip(CountryUtil.getNamesByCodesString(acceptedCountryCodes)); + } } TextField acceptedCountries = addLabelTextField(gridPane, ++rowIndex, "Accepted taker countries:", countries).second; if (tooltip != null) { diff --git a/gui/src/main/java/io/bitsquare/gui/main/overlays/windows/TacWindow.java b/gui/src/main/java/io/bitsquare/gui/main/overlays/windows/TacWindow.java index 6fa3047dab5..e18c114e459 100644 --- a/gui/src/main/java/io/bitsquare/gui/main/overlays/windows/TacWindow.java +++ b/gui/src/main/java/io/bitsquare/gui/main/overlays/windows/TacWindow.java @@ -18,7 +18,7 @@ public class TacWindow extends Overlay { public TacWindow(Preferences preferences) { this.preferences = preferences; type = Type.Attention; - width = 800; + width = 900; } public void showIfNeeded() { diff --git a/gui/src/main/java/io/bitsquare/gui/main/portfolio/pendingtrades/steps/seller/SellerStep3View.java b/gui/src/main/java/io/bitsquare/gui/main/portfolio/pendingtrades/steps/seller/SellerStep3View.java index 62bf7763d26..ac5160c5fc5 100644 --- a/gui/src/main/java/io/bitsquare/gui/main/portfolio/pendingtrades/steps/seller/SellerStep3View.java +++ b/gui/src/main/java/io/bitsquare/gui/main/portfolio/pendingtrades/steps/seller/SellerStep3View.java @@ -193,7 +193,7 @@ protected void addContent() { protected String getInfoText() { if (model.isBlockChainMethod()) { return "The bitcoin buyer has started the " + model.dataModel.getCurrencyCode() + " payment.\n" + - "Check for blockchain confirmations at your cryptocurrency wallet or block explorer and " + + "Check for blockchain confirmations at your altcoin wallet or block explorer and " + "confirm the payment when you have sufficient blockchain confirmations."; } else { return "The bitcoin buyer has started the " + model.dataModel.getCurrencyCode() + " payment.\n" + diff --git a/gui/src/main/java/io/bitsquare/gui/main/settings/network/NetworkSettingsView.fxml b/gui/src/main/java/io/bitsquare/gui/main/settings/network/NetworkSettingsView.fxml index d039c39762f..11d73dec364 100644 --- a/gui/src/main/java/io/bitsquare/gui/main/settings/network/NetworkSettingsView.fxml +++ b/gui/src/main/java/io/bitsquare/gui/main/settings/network/NetworkSettingsView.fxml @@ -74,12 +74,12 @@ - + - + @@ -89,22 +89,22 @@ --> - + - + - + - + diff --git a/gui/src/main/java/io/bitsquare/gui/main/settings/preferences/PreferencesView.java b/gui/src/main/java/io/bitsquare/gui/main/settings/preferences/PreferencesView.java index 2dc6cb4142e..89ce11d1ffb 100644 --- a/gui/src/main/java/io/bitsquare/gui/main/settings/preferences/PreferencesView.java +++ b/gui/src/main/java/io/bitsquare/gui/main/settings/preferences/PreferencesView.java @@ -206,7 +206,7 @@ public void updateItem(final FiatCurrency item, boolean empty) { } }); - Tuple2 cryptoCurrenciesTuple = addLabelListView(root, gridRow, "Display crypto currencies:"); + Tuple2 cryptoCurrenciesTuple = addLabelListView(root, gridRow, "Display altcoins:"); GridPane.setValignment(cryptoCurrenciesTuple.first, VPos.TOP); GridPane.setMargin(cryptoCurrenciesTuple.first, new Insets(0, 0, 0, 20)); cryptoCurrenciesListView = cryptoCurrenciesTuple.second; @@ -214,7 +214,7 @@ public void updateItem(final FiatCurrency item, boolean empty) { GridPane.setColumnIndex(cryptoCurrenciesListView, 3); cryptoCurrenciesListView.setMinHeight(2 * Layout.LIST_ROW_HEIGHT + 2); cryptoCurrenciesListView.setMaxHeight(6 * Layout.LIST_ROW_HEIGHT + 2); - placeholder = new Label("There are no crypto currencies selected"); + placeholder = new Label("There are no altcoins selected"); placeholder.setWrapText(true); cryptoCurrenciesListView.setPlaceholder(placeholder); cryptoCurrenciesListView.setCellFactory(new Callback, ListCell>() { @@ -273,7 +273,7 @@ public FiatCurrency fromString(String s) { Tuple2 labelComboBoxTuple2 = addLabelComboBox(root, gridRow); cryptoCurrenciesComboBox = labelComboBoxTuple2.second; GridPane.setColumnIndex(cryptoCurrenciesComboBox, 3); - cryptoCurrenciesComboBox.setPromptText("Add cryptocurrency"); + cryptoCurrenciesComboBox.setPromptText("Add altcoin"); cryptoCurrenciesComboBox.setConverter(new StringConverter() { @Override public String toString(CryptoCurrency tradeCurrency) { diff --git a/gui/src/main/resources/i18n/displayStrings.properties b/gui/src/main/resources/i18n/displayStrings.properties index e5d48766a39..1d07ed27a91 100644 --- a/gui/src/main/resources/i18n/displayStrings.properties +++ b/gui/src/main/resources/i18n/displayStrings.properties @@ -158,7 +158,7 @@ FED_WIRE=Fed Wire SWISH= Swish TRANSFER_WISE=TransferWise US_POSTAL_MONEY_ORDER=US Postal money order -BLOCK_CHAINS=Cryptocurrencies +BLOCK_CHAINS=Altcoins OK_PAY_SHORT=OKPay PERFECT_MONEY_SHORT=Perfect Money @@ -171,4 +171,4 @@ FED_WIRE_SHORT=Fed Wire SWISH_SHORT= Swish TRANSFER_WISE_SHORT=TransferWise US_POSTAL_MONEY_ORDER_SHORT=Money order -BLOCK_CHAINS_SHORT=Block chain +BLOCK_CHAINS_SHORT=Altcoins diff --git a/headless/pom.xml b/headless/pom.xml index b9a6c446c33..d7d74918782 100644 --- a/headless/pom.xml +++ b/headless/pom.xml @@ -5,7 +5,7 @@ parent io.bitsquare - 0.4.9.2 + 0.4.9.3 4.0.0 diff --git a/jdkfix/pom.xml b/jdkfix/pom.xml index 6049733bab5..c52b8ba3c0c 100644 --- a/jdkfix/pom.xml +++ b/jdkfix/pom.xml @@ -22,7 +22,7 @@ parent io.bitsquare - 0.4.9.2 + 0.4.9.3 4.0.0 diff --git a/jsocks/pom.xml b/jsocks/pom.xml index 0c7778f150f..e6a87eb85d7 100644 --- a/jsocks/pom.xml +++ b/jsocks/pom.xml @@ -5,7 +5,7 @@ parent io.bitsquare - 0.4.9.2 + 0.4.9.3 4.0.0 diff --git a/jtorctl/pom.xml b/jtorctl/pom.xml index 9cb46129278..059cc0de9c0 100644 --- a/jtorctl/pom.xml +++ b/jtorctl/pom.xml @@ -5,7 +5,7 @@ parent io.bitsquare - 0.4.9.2 + 0.4.9.3 4.0.0 diff --git a/jtorproxy/pom.xml b/jtorproxy/pom.xml index 1e842882d78..0fcac1f453b 100644 --- a/jtorproxy/pom.xml +++ b/jtorproxy/pom.xml @@ -5,7 +5,7 @@ parent io.bitsquare - 0.4.9.2 + 0.4.9.3 4.0.0 diff --git a/monitor/pom.xml b/monitor/pom.xml index 89889054a16..9250f71c8df 100644 --- a/monitor/pom.xml +++ b/monitor/pom.xml @@ -5,7 +5,7 @@ parent io.bitsquare - 0.4.9.2 + 0.4.9.3 4.0.0 diff --git a/network/pom.xml b/network/pom.xml index e2305bbfdb8..3c9f7ab0b7c 100644 --- a/network/pom.xml +++ b/network/pom.xml @@ -5,7 +5,7 @@ parent io.bitsquare - 0.4.9.2 + 0.4.9.3 4.0.0 diff --git a/network/src/main/java/io/bitsquare/p2p/P2PService.java b/network/src/main/java/io/bitsquare/p2p/P2PService.java index 13c1561cb6a..69f104a01d1 100644 --- a/network/src/main/java/io/bitsquare/p2p/P2PService.java +++ b/network/src/main/java/io/bitsquare/p2p/P2PService.java @@ -174,7 +174,7 @@ private void init(boolean useLocalhost, String myAddress, String banList) { if (!useLocalhost) - FileUtil.rollingBackup(new File(Paths.get(torDir.getAbsolutePath(), "hiddenservice").toString()), "private_key"); + FileUtil.rollingBackup(new File(Paths.get(torDir.getAbsolutePath(), "hiddenservice").toString()), "private_key", 20); if (banList != null && !banList.isEmpty()) BanList.setList(Arrays.asList(banList.replace(" ", "").split(",")).stream().map(NodeAddress::new).collect(Collectors.toList())); diff --git a/network/src/main/java/io/bitsquare/p2p/network/Connection.java b/network/src/main/java/io/bitsquare/p2p/network/Connection.java index 137ad436c16..3fe9d4240e8 100644 --- a/network/src/main/java/io/bitsquare/p2p/network/Connection.java +++ b/network/src/main/java/io/bitsquare/p2p/network/Connection.java @@ -711,16 +711,16 @@ public void run() { Thread.currentThread().setName("InputHandler-" + sharedModel.connection.getPeersNodeAddressOptional().get().getFullAddress()); threadNameSet = true; } + try { + if (objectInputStream.available() < 0) { + log.warn("Shutdown because objectInputStream.available() < 0. objectInputStream.available()=" + objectInputStream.available()); + sharedModel.shutDown(CloseConnectionReason.TERMINATED); + return; + } - if (objectInputStream.available() < 0) { - log.warn("Shutdown because objectInputStream.available() < 0. objectInputStream.available()=" + objectInputStream.available()); - sharedModel.shutDown(CloseConnectionReason.TERMINATED); - return; - } + Connection connection = sharedModel.connection; + log.trace("InputHandler waiting for incoming messages.\n\tConnection=" + connection); - Connection connection = sharedModel.connection; - log.trace("InputHandler waiting for incoming messages.\n\tConnection=" + connection); - try { Object rawInputObject = objectInputStream.readObject(); // Throttle inbound messages @@ -757,12 +757,7 @@ public void run() { Utilities.toTruncatedString(rawInputObject), size); } else { - log.error("\n\n<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n" + - "Invalid data arrived at inputHandler of connection {}.\n" + - "Size={}" - + "\n<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n", - connection, - size); + log.error("Invalid data arrived at inputHandler of connection {} Size={}", connection, size); try { // Don't call toString on rawInputObject log.error("rawInputObject.className=" + rawInputObject.getClass().getName()); @@ -783,11 +778,12 @@ public void run() { // First we check the size boolean exceeds; - if (rawInputObject instanceof GetDataResponse) + if (rawInputObject instanceof GetDataResponse) { exceeds = size > MAX_MSG_SIZE_GET_DATA; - else + log.info("size={}; object={}", size, Utilities.toTruncatedString(rawInputObject.toString(), 100)); + } else { exceeds = size > MAX_MSG_SIZE; - + } if (exceeds) log.warn("size > MAX_MSG_SIZE. size={}; object={}", size, message); diff --git a/network/src/main/java/io/bitsquare/p2p/peers/PeerManager.java b/network/src/main/java/io/bitsquare/p2p/peers/PeerManager.java index 394499b5b42..d3c74573883 100644 --- a/network/src/main/java/io/bitsquare/p2p/peers/PeerManager.java +++ b/network/src/main/java/io/bitsquare/p2p/peers/PeerManager.java @@ -162,7 +162,9 @@ public void onConnection(Connection connection) { final boolean seedNode = isSeedNode(connection); final Optional addressOptional = connection.getPeersNodeAddressOptional(); - log.info("onConnection: peer = {}{}", (addressOptional.isPresent() ? addressOptional.get().hostName : "unknown address"), seedNode ? " (SeedNode)" : ""); + log.debug("onConnection: peer = {}{}", + (addressOptional.isPresent() ? addressOptional.get().hostName : "not known yet (connection id=" + connection.getUid() + ")"), + seedNode ? " (SeedNode)" : ""); if (seedNode) connection.setPeerType(Connection.PeerType.SEED_NODE); @@ -184,8 +186,11 @@ public void onDisconnect(CloseConnectionReason closeConnectionReason, Connection " / closeConnectionReason: " + closeConnectionReason); final Optional addressOptional = connection.getPeersNodeAddressOptional(); - log.info("onDisconnect: peer = {}{}", (addressOptional.isPresent() ? addressOptional.get().hostName : "not known yet"), isSeedNode(connection) ? " (SeedNode)" : ""); - + log.debug("onDisconnect: peer = {}{} / closeConnectionReason: {}", + (addressOptional.isPresent() ? addressOptional.get().hostName : "not known yet (connection id=" + connection.getUid() + ")"), + isSeedNode(connection) ? " (SeedNode)" : "", + closeConnectionReason); + handleConnectionFault(connection); lostAllConnections = networkNode.getAllConnections().isEmpty(); diff --git a/network/src/main/java/io/bitsquare/p2p/peers/getdata/GetDataRequestHandler.java b/network/src/main/java/io/bitsquare/p2p/peers/getdata/GetDataRequestHandler.java index 70d70a5eb1f..5411b8d11f9 100644 --- a/network/src/main/java/io/bitsquare/p2p/peers/getdata/GetDataRequestHandler.java +++ b/network/src/main/java/io/bitsquare/p2p/peers/getdata/GetDataRequestHandler.java @@ -22,7 +22,9 @@ import java.util.HashSet; import java.util.List; +import java.util.Set; import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; public class GetDataRequestHandler { private static final Logger log = LoggerFactory.getLogger(GetDataRequestHandler.class); @@ -71,7 +73,15 @@ public void handle(GetDataRequest getDataRequest, final Connection connection) { Log.traceCall(getDataRequest + "\n\tconnection=" + connection); final HashSet filteredDataSet = new HashSet<>(); - for (ProtectedStorageEntry protectedStorageEntry : dataStorage.getMap().values()) { + final Set lookupSet = new HashSet<>(); + + Set excludedItems = getDataRequest.getExcludedKeys() != null ? + getDataRequest.getExcludedKeys().stream() + .map(P2PDataStorage.ByteArray::new) + .collect(Collectors.toSet()) + : new HashSet<>(); + + for (ProtectedStorageEntry protectedStorageEntry : dataStorage.getFilteredValues(excludedItems)) { final StoragePayload storagePayload = protectedStorageEntry.getStoragePayload(); boolean doAdd = false; if (storagePayload instanceof CapabilityRequiringPayload) { @@ -99,8 +109,15 @@ public void handle(GetDataRequest getDataRequest, final Connection connection) { } else { doAdd = true; } - if (doAdd) - filteredDataSet.add(protectedStorageEntry); + if (doAdd) { + // We have TradeStatistic data of both traders but we only send 1 item, + // so we use lookupSet as for a fast lookup. Using filteredDataSet would require a loop as it stores + // protectedStorageEntry not storagePayload. protectedStorageEntry is different for both traders but storagePayload not, + // as we ignore the pubKey and data there in the hashCode method. + boolean notContained = lookupSet.add(storagePayload.hashCode()); + if (notContained) + filteredDataSet.add(protectedStorageEntry); + } } GetDataResponse getDataResponse = new GetDataResponse(filteredDataSet, getDataRequest.getNonce()); diff --git a/network/src/main/java/io/bitsquare/p2p/peers/getdata/RequestDataHandler.java b/network/src/main/java/io/bitsquare/p2p/peers/getdata/RequestDataHandler.java index 55648bb2438..ff04ff35348 100644 --- a/network/src/main/java/io/bitsquare/p2p/peers/getdata/RequestDataHandler.java +++ b/network/src/main/java/io/bitsquare/p2p/peers/getdata/RequestDataHandler.java @@ -18,13 +18,18 @@ import io.bitsquare.p2p.peers.getdata.messages.GetUpdatedDataRequest; import io.bitsquare.p2p.peers.getdata.messages.PreliminaryGetDataRequest; import io.bitsquare.p2p.storage.P2PDataStorage; +import io.bitsquare.p2p.storage.payload.LazyProcessedStoragePayload; +import io.bitsquare.p2p.storage.payload.PersistedStoragePayload; import io.bitsquare.p2p.storage.payload.StoragePayload; +import io.bitsquare.p2p.storage.storageentry.ProtectedStorageEntry; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.*; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; import static com.google.common.base.Preconditions.checkArgument; @@ -84,10 +89,20 @@ public void requestData(NodeAddress nodeAddress) { Log.traceCall("nodeAddress=" + nodeAddress); if (!stopped) { GetDataRequest getDataRequest; + + // We collect the keys of the PersistedStoragePayload items so we exclude them in our request. + // PersistedStoragePayload items don't get removed, so we don't have an issue with the case that + // an object gets removed in between PreliminaryGetDataRequest and the GetUpdatedDataRequest and we would + // miss that event if we do not load the full set or use some delta handling. + Set excludedKeys = dataStorage.getMap().entrySet().stream() + .filter(e -> e.getValue().getStoragePayload() instanceof PersistedStoragePayload) + .map(e -> e.getKey().bytes) + .collect(Collectors.toSet()); + if (networkNode.getNodeAddress() == null) - getDataRequest = new PreliminaryGetDataRequest(nonce); + getDataRequest = new PreliminaryGetDataRequest(nonce, excludedKeys); else - getDataRequest = new GetUpdatedDataRequest(networkNode.getNodeAddress(), nonce); + getDataRequest = new GetUpdatedDataRequest(networkNode.getNodeAddress(), nonce, excludedKeys); if (timeoutTimer == null) { timeoutTimer = UserThread.runAfter(() -> { // setup before sending to avoid race conditions @@ -151,7 +166,8 @@ public void onMessage(Message message, Connection connection) { if (!stopped) { GetDataResponse getDataResponse = (GetDataResponse) message; Map> payloadByClassName = new HashMap<>(); - getDataResponse.dataSet.stream().forEach(e -> { + final HashSet dataSet = getDataResponse.dataSet; + dataSet.stream().forEach(e -> { final StoragePayload storagePayload = e.getStoragePayload(); String className = storagePayload.getClass().getSimpleName(); if (!payloadByClassName.containsKey(className)) @@ -159,7 +175,7 @@ public void onMessage(Message message, Connection connection) { payloadByClassName.get(className).add(storagePayload); }); - StringBuilder sb = new StringBuilder("Received data size: ").append(getDataResponse.dataSet.size()).append(", data items: "); + StringBuilder sb = new StringBuilder("Received data size: ").append(dataSet.size()).append(", data items: "); payloadByClassName.entrySet().stream().forEach(e -> sb.append(e.getValue().size()).append(" items of ").append(e.getKey()).append("; ")); log.info(sb.toString()); @@ -170,10 +186,43 @@ public void onMessage(Message message, Connection connection) { "at that moment"); final NodeAddress sender = connection.getPeersNodeAddressOptional().get(); - getDataResponse.dataSet.stream().forEach(protectedStorageEntry -> { - // We dont broadcast here as we are only connected to the seed node and would be pointless - dataStorage.add(protectedStorageEntry, sender, null, false, false); + + List processDelayedItems = new ArrayList<>(); + dataSet.stream().forEach(e -> { + if (e.getStoragePayload() instanceof LazyProcessedStoragePayload) + processDelayedItems.add(e); + else { + // We dont broadcast here (last param) as we are only connected to the seed node and would be pointless + dataStorage.add(e, sender, null, false, false); + } }); + + // We process the LazyProcessedStoragePayload items (TradeStatistics) in batches with a delay in between. + // We want avoid that the UI get stuck when processing many entries. + // The dataStorage.add call is a bit expensive as sig checks is done there. + + // Using a background thread might be an alternative but it would require much more effort and + // it would also decrease user experience if the app gets under heavy load (like at startup with wallet sync). + // Beside that we mitigated the problem already as we will not get the whole TradeStatistics as we + // pass the excludeKeys and we pack the latest data dump + // into the resources, so a new user do not need to request all data. + + // In future we will probably limit by date or load on demand from user intent to not get too much data. + + // We split the list into sub lists with max 50 items and delay each batch with 200 ms. + int size = processDelayedItems.size(); + int chunkSize = 50; + int chunks = 1 + size / chunkSize; + int startIndex = 0; + for (int i = 0; i < chunks && startIndex < size; i++, startIndex += chunkSize) { + long delay = (i + 1) * 200; + int endIndex = Math.min(size, startIndex + chunkSize); + List subList = processDelayedItems.subList(startIndex, endIndex); + UserThread.runAfter(() -> { + subList.stream().forEach(protectedStorageEntry -> dataStorage.add(protectedStorageEntry, sender, null, false, false)); + }, delay, TimeUnit.MILLISECONDS); + } + cleanup(); listener.onComplete(); } else { diff --git a/network/src/main/java/io/bitsquare/p2p/peers/getdata/messages/GetDataRequest.java b/network/src/main/java/io/bitsquare/p2p/peers/getdata/messages/GetDataRequest.java index 92c0a773364..0b64ccb49fd 100644 --- a/network/src/main/java/io/bitsquare/p2p/peers/getdata/messages/GetDataRequest.java +++ b/network/src/main/java/io/bitsquare/p2p/peers/getdata/messages/GetDataRequest.java @@ -2,6 +2,10 @@ import io.bitsquare.p2p.Message; +import java.util.Set; + public interface GetDataRequest extends Message { int getNonce(); + + Set getExcludedKeys(); } diff --git a/network/src/main/java/io/bitsquare/p2p/peers/getdata/messages/GetUpdatedDataRequest.java b/network/src/main/java/io/bitsquare/p2p/peers/getdata/messages/GetUpdatedDataRequest.java index 23783bed8f9..1956d71864c 100644 --- a/network/src/main/java/io/bitsquare/p2p/peers/getdata/messages/GetUpdatedDataRequest.java +++ b/network/src/main/java/io/bitsquare/p2p/peers/getdata/messages/GetUpdatedDataRequest.java @@ -4,6 +4,8 @@ import io.bitsquare.p2p.NodeAddress; import io.bitsquare.p2p.network.messages.SendersNodeAddressMessage; +import java.util.Set; + import static com.google.common.base.Preconditions.checkNotNull; public final class GetUpdatedDataRequest implements SendersNodeAddressMessage, GetDataRequest { @@ -13,11 +15,13 @@ public final class GetUpdatedDataRequest implements SendersNodeAddressMessage, G private final int messageVersion = Version.getP2PMessageVersion(); private final NodeAddress senderNodeAddress; private final int nonce; + private final Set excludedKeys; - public GetUpdatedDataRequest(NodeAddress senderNodeAddress, int nonce) { + public GetUpdatedDataRequest(NodeAddress senderNodeAddress, int nonce, Set excludedKeys) { checkNotNull(senderNodeAddress, "senderNodeAddress must not be null at GetUpdatedDataRequest"); this.senderNodeAddress = senderNodeAddress; this.nonce = nonce; + this.excludedKeys = excludedKeys; } @Override @@ -30,6 +34,11 @@ public NodeAddress getSenderNodeAddress() { return senderNodeAddress; } + @Override + public Set getExcludedKeys() { + return excludedKeys; + } + @Override public int getMessageVersion() { return messageVersion; diff --git a/network/src/main/java/io/bitsquare/p2p/peers/getdata/messages/PreliminaryGetDataRequest.java b/network/src/main/java/io/bitsquare/p2p/peers/getdata/messages/PreliminaryGetDataRequest.java index 6a460132316..99e9c77a5c3 100644 --- a/network/src/main/java/io/bitsquare/p2p/peers/getdata/messages/PreliminaryGetDataRequest.java +++ b/network/src/main/java/io/bitsquare/p2p/peers/getdata/messages/PreliminaryGetDataRequest.java @@ -7,6 +7,7 @@ import javax.annotation.Nullable; import java.util.ArrayList; +import java.util.Set; public final class PreliminaryGetDataRequest implements AnonymousMessage, GetDataRequest, SupportedCapabilitiesMessage { // That object is sent over the wire, so we need to take care of version compatibility. @@ -14,11 +15,13 @@ public final class PreliminaryGetDataRequest implements AnonymousMessage, GetDat private final int messageVersion = Version.getP2PMessageVersion(); private final int nonce; + private final Set excludedKeys; @Nullable private ArrayList supportedCapabilities = Capabilities.getCapabilities(); - public PreliminaryGetDataRequest(int nonce) { + public PreliminaryGetDataRequest(int nonce, Set excludedKeys) { this.nonce = nonce; + this.excludedKeys = excludedKeys; } @Override @@ -32,6 +35,11 @@ public int getNonce() { return nonce; } + @Override + public Set getExcludedKeys() { + return excludedKeys; + } + @Override public int getMessageVersion() { return messageVersion; diff --git a/network/src/main/java/io/bitsquare/p2p/seed/SeedNodesRepository.java b/network/src/main/java/io/bitsquare/p2p/seed/SeedNodesRepository.java index e5eaea40df5..934cfacf9ff 100644 --- a/network/src/main/java/io/bitsquare/p2p/seed/SeedNodesRepository.java +++ b/network/src/main/java/io/bitsquare/p2p/seed/SeedNodesRepository.java @@ -39,7 +39,7 @@ public class SeedNodesRepository { DevFlags.STRESS_TEST_MODE ? new NodeAddress("hlitt7z4bec4kdh4.onion:8000") : new NodeAddress("uadzuib66jupaept.onion:8000"), DevFlags.STRESS_TEST_MODE ? new NodeAddress("hlitt7z4bec4kdh4.onion:8000") : new NodeAddress("wgthuiqn3aoiovbm.onion:8000"), - // ...188 + // ...14 DevFlags.STRESS_TEST_MODE ? new NodeAddress("hlitt7z4bec4kdh4.onion:8000") : new NodeAddress("hbma455xxbqhcuqh.onion:8000"), DevFlags.STRESS_TEST_MODE ? new NodeAddress("hlitt7z4bec4kdh4.onion:8000") : new NodeAddress("2zxtnprnx5wqr7a3.onion:8000"), diff --git a/network/src/main/java/io/bitsquare/p2p/storage/P2PDataStorage.java b/network/src/main/java/io/bitsquare/p2p/storage/P2PDataStorage.java index 05d98515827..3d2a00f18bb 100644 --- a/network/src/main/java/io/bitsquare/p2p/storage/P2PDataStorage.java +++ b/network/src/main/java/io/bitsquare/p2p/storage/P2PDataStorage.java @@ -18,12 +18,11 @@ import io.bitsquare.p2p.peers.BroadcastHandler; import io.bitsquare.p2p.peers.Broadcaster; import io.bitsquare.p2p.storage.messages.*; -import io.bitsquare.p2p.storage.payload.ExpirablePayload; -import io.bitsquare.p2p.storage.payload.MailboxStoragePayload; -import io.bitsquare.p2p.storage.payload.RequiresOwnerIsOnlinePayload; -import io.bitsquare.p2p.storage.payload.StoragePayload; +import io.bitsquare.p2p.storage.payload.*; import io.bitsquare.p2p.storage.storageentry.ProtectedMailboxStorageEntry; import io.bitsquare.p2p.storage.storageentry.ProtectedStorageEntry; +import io.bitsquare.storage.FileUtil; +import io.bitsquare.storage.ResourceNotFoundException; import io.bitsquare.storage.Storage; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; @@ -32,7 +31,9 @@ import javax.annotation.Nullable; import java.io.File; +import java.io.IOException; import java.io.Serializable; +import java.nio.file.Paths; import java.security.KeyPair; import java.security.PublicKey; import java.util.*; @@ -58,7 +59,9 @@ public class P2PDataStorage implements MessageListener, ConnectionListener { private final CopyOnWriteArraySet hashMapChangedListeners = new CopyOnWriteArraySet<>(); private Timer removeExpiredEntriesTimer; private HashMap sequenceNumberMap = new HashMap<>(); - private final Storage> storage; + private final Storage> sequenceNumberMapStorage; + private HashMap persistedMap = new HashMap<>(); + private final Storage> persistedEntryMapStorage; /////////////////////////////////////////////////////////////////////////////////////////// @@ -71,11 +74,45 @@ public P2PDataStorage(Broadcaster broadcaster, NetworkNode networkNode, File sto networkNode.addMessageListener(this); networkNode.addConnectionListener(this); - storage = new Storage<>(storageDir); + sequenceNumberMapStorage = new Storage<>(storageDir); + persistedEntryMapStorage = new Storage<>(storageDir); - HashMap persisted = storage.>initAndGetPersistedWithFileName("SequenceNumberMap"); - if (persisted != null) - sequenceNumberMap = getPurgedSequenceNumberMap(persisted); + init(storageDir); + } + + private void init(File storageDir) { + sequenceNumberMapStorage.setNumMaxBackupFiles(5); + HashMap persistedSequenceNumberMap = sequenceNumberMapStorage.>initAndGetPersistedWithFileName("SequenceNumberMap"); + if (persistedSequenceNumberMap != null) + sequenceNumberMap = getPurgedSequenceNumberMap(persistedSequenceNumberMap); + + final String storageFileName = "PersistedP2PStorageData"; + + File dbDir = new File(storageDir.getAbsolutePath()); + if (!dbDir.exists() && !dbDir.mkdir()) + log.warn("make dir failed.\ndbDir=" + dbDir.getAbsolutePath()); + + final File destinationFile = new File(Paths.get(storageDir.getAbsolutePath(), storageFileName).toString()); + if (!destinationFile.exists()) { + try { + FileUtil.resourceToFile(storageFileName, destinationFile); + } catch (ResourceNotFoundException | IOException e) { + e.printStackTrace(); + log.error("Could not copy the " + storageFileName + " resource file to the db directory.\n" + e.getMessage()); + } + } else { + log.debug(storageFileName + " file exists already."); + } + + HashMap persisted = persistedEntryMapStorage.>initAndGetPersistedWithFileName(storageFileName); + if (persisted != null) { + persistedMap = persisted; + map.putAll(persistedMap); + + // In case another object is already listening... + map.values().stream() + .forEach(protectedStorageEntry -> hashMapChangedListeners.stream().forEach(e -> e.onAdded(protectedStorageEntry))); + } } public void shutDown() { @@ -210,7 +247,8 @@ public boolean add(ProtectedStorageEntry protectedStorageEntry, @Nullable NodeAd @Nullable BroadcastHandler.Listener listener, boolean isDataOwner, boolean allowBroadcast) { Log.traceCall("with allowBroadcast=" + allowBroadcast); - ByteArray hashOfPayload = getHashAsByteArray(protectedStorageEntry.getStoragePayload()); + final StoragePayload storagePayload = protectedStorageEntry.getStoragePayload(); + ByteArray hashOfPayload = getHashAsByteArray(storagePayload); boolean sequenceNrValid = isSequenceNrValid(protectedStorageEntry.sequenceNumber, hashOfPayload); boolean result = checkPublicKeys(protectedStorageEntry, true) && checkSignature(protectedStorageEntry) @@ -226,8 +264,15 @@ && checkSignature(protectedStorageEntry) if (!containsKey || hasSequenceNrIncreased) { // At startup we don't have the item so we store it. At updates of the seq nr we store as well. map.put(hashOfPayload, protectedStorageEntry); + + // If we get a PersistedStoragePayload we save to disc + if (storagePayload instanceof PersistedStoragePayload) { + persistedMap.put(hashOfPayload, protectedStorageEntry); + persistedEntryMapStorage.queueUpForSave(new HashMap<>(persistedMap), 5000); + } + hashMapChangedListeners.stream().forEach(e -> e.onAdded(protectedStorageEntry)); - printData("after add"); + // printData("after add"); } else { log.trace("We got that version of the data already, so we don't store it."); } @@ -235,7 +280,7 @@ && checkSignature(protectedStorageEntry) if (hasSequenceNrIncreased) { sequenceNumberMap.put(hashOfPayload, new MapValue(protectedStorageEntry.sequenceNumber, System.currentTimeMillis())); // We set the delay higher as we might receive a batch of items - storage.queueUpForSave(new HashMap<>(sequenceNumberMap), 2000); + sequenceNumberMapStorage.queueUpForSave(new HashMap<>(sequenceNumberMap), 2000); if (allowBroadcast) broadcast(new AddDataMessage(protectedStorageEntry), sender, listener, isDataOwner); @@ -279,7 +324,7 @@ public boolean refreshTTL(RefreshTTLMessage refreshTTLMessage, @Nullable NodeAdd storedData.updateSignature(signature); printData("after refreshTTL"); sequenceNumberMap.put(hashOfPayload, new MapValue(sequenceNumber, System.currentTimeMillis())); - storage.queueUpForSave(new HashMap<>(sequenceNumberMap), 1000); + sequenceNumberMapStorage.queueUpForSave(new HashMap<>(sequenceNumberMap), 1000); broadcast(refreshTTLMessage, sender, null, isDataOwner); } @@ -308,7 +353,7 @@ && checkSignature(protectedStorageEntry) doRemoveProtectedExpirableData(protectedStorageEntry, hashOfPayload); printData("after remove"); sequenceNumberMap.put(hashOfPayload, new MapValue(protectedStorageEntry.sequenceNumber, System.currentTimeMillis())); - storage.queueUpForSave(new HashMap<>(sequenceNumberMap), 300); + sequenceNumberMapStorage.queueUpForSave(new HashMap<>(sequenceNumberMap), 300); broadcast(new RemoveDataMessage(protectedStorageEntry), sender, null, isDataOwner); } else { @@ -335,7 +380,7 @@ && checkSignature(protectedMailboxStorageEntry) doRemoveProtectedExpirableData(protectedMailboxStorageEntry, hashOfData); printData("after removeMailboxData"); sequenceNumberMap.put(hashOfData, new MapValue(protectedMailboxStorageEntry.sequenceNumber, System.currentTimeMillis())); - storage.queueUpForSave(new HashMap<>(sequenceNumberMap), 300); + sequenceNumberMapStorage.queueUpForSave(new HashMap<>(sequenceNumberMap), 300); broadcast(new RemoveMailboxDataMessage(protectedMailboxStorageEntry), sender, null, isDataOwner); } else { @@ -401,6 +446,13 @@ public void removeHashMapChangedListener(HashMapChangedListener hashMapChangedLi hashMapChangedListeners.remove(hashMapChangedListener); } + public Set getFilteredValues(Set excludedKeys) { + return map.entrySet() + .stream().filter(e -> !excludedKeys.contains(e.getKey())) + .map(Map.Entry::getValue) + .collect(Collectors.toSet()); + } + /////////////////////////////////////////////////////////////////////////////////////////// // Private @@ -465,7 +517,7 @@ private boolean checkSignature(PublicKey ownerPubKey, byte[] hashOfDataAndSeqNr, try { boolean result = Sig.verify(ownerPubKey, hashOfDataAndSeqNr, signature); if (!result) - log.error("Signature verification failed at checkSignature. " + + log.warn("Signature verification failed at checkSignature. " + "That should not happen. ownerPubKey=" + ownerPubKey + ", hashOfDataAndSeqNr=" + Arrays.toString(hashOfDataAndSeqNr) + ", signature=" + Arrays.toString(signature)); @@ -510,7 +562,7 @@ private boolean checkPublicKeys(ProtectedStorageEntry protectedStorageEntry, boo res2 = protectedStorageEntry.getStoragePayload().getOwnerPubKey().toString(); } - log.error("PublicKey of payload data and ProtectedData are not matching. protectedStorageEntry=" + res1 + + log.warn("PublicKey of payload data and ProtectedData are not matching. protectedStorageEntry=" + res1 + "protectedStorageEntry.getStoragePayload().getOwnerPubKey()=" + res2); } return result; @@ -520,7 +572,7 @@ private boolean checkIfStoredDataPubKeyMatchesNewDataPubKey(PublicKey ownerPubKe ProtectedStorageEntry storedData = map.get(hashOfData); boolean result = storedData.ownerPubKey != null && storedData.ownerPubKey.equals(ownerPubKey); if (!result) - log.error("New data entry does not match our stored data. storedData.ownerPubKey=" + + log.warn("New data entry does not match our stored data. storedData.ownerPubKey=" + (storedData.ownerPubKey != null ? storedData.ownerPubKey.toString() : "null") + ", ownerPubKey=" + ownerPubKey); @@ -535,7 +587,7 @@ private boolean checkIfStoredMailboxDataMatchesNewMailboxData(PublicKey receiver boolean result = entry.receiversPubKey.equals(receiversPubKey) && getHashAsByteArray(entry.getStoragePayload()).equals(hashOfData); if (!result) - log.error("New data entry does not match our stored data. entry.receiversPubKey=" + entry.receiversPubKey + log.warn("New data entry does not match our stored data. entry.receiversPubKey=" + entry.receiversPubKey + ", receiversPubKey=" + receiversPubKey); return result; diff --git a/network/src/main/java/io/bitsquare/p2p/storage/payload/LazyProcessedStoragePayload.java b/network/src/main/java/io/bitsquare/p2p/storage/payload/LazyProcessedStoragePayload.java new file mode 100644 index 00000000000..c112d169285 --- /dev/null +++ b/network/src/main/java/io/bitsquare/p2p/storage/payload/LazyProcessedStoragePayload.java @@ -0,0 +1,8 @@ +package io.bitsquare.p2p.storage.payload; + +/** + * Marker interface for payload which gets delayed processed at startup so we don't hit performance too much. + * Used for TradeStatistics. + */ +public interface LazyProcessedStoragePayload extends StoragePayload { +} diff --git a/network/src/main/java/io/bitsquare/p2p/storage/payload/PersistedStoragePayload.java b/network/src/main/java/io/bitsquare/p2p/storage/payload/PersistedStoragePayload.java new file mode 100644 index 00000000000..3e726c70a95 --- /dev/null +++ b/network/src/main/java/io/bitsquare/p2p/storage/payload/PersistedStoragePayload.java @@ -0,0 +1,8 @@ +package io.bitsquare.p2p.storage.payload; + +/** + * Marker interface for payload which gets persisted. + * Used for TradeStatistics. + */ +public interface PersistedStoragePayload extends StoragePayload { +} diff --git a/network/src/main/resources/PersistedP2PStorageData b/network/src/main/resources/PersistedP2PStorageData new file mode 100644 index 00000000000..38dadc02e23 Binary files /dev/null and b/network/src/main/resources/PersistedP2PStorageData differ diff --git a/package/linux/32bitBuild.sh b/package/linux/32bitBuild.sh index bd709b67d29..2e16eea703c 100644 --- a/package/linux/32bitBuild.sh +++ b/package/linux/32bitBuild.sh @@ -6,7 +6,7 @@ mkdir -p gui/deploy set -e # Edit version -version=0.4.9.2 +version=0.4.9.3 jarFile="/media/sf_vm_shared_ubuntu14_32bit/Bitsquare-$version.jar" jdkfixFile="/media/sf_vm_shared_ubuntu14_32bit/jdkfix-$version.jar" @@ -32,12 +32,10 @@ $JAVA_HOME/bin/javapackager \ -appclass io.bitsquare.app.BitsquareAppMain \ -outfile Bitsquare -rm gui/deploy/Bitsquare.html -rm gui/deploy/Bitsquare.jnlp -rm gui/deploy/LICENSE -mv "gui/deploy/bundles/bitsquare-$version.deb" "gui/deploy/Bitsquare-32bit-$version.deb" -rmdir gui/deploy/bundles -cp "gui/deploy/Bitsquare-32bit-$version.deb" "/media/sf_vm_shared_ubuntu14_32bit/Bitsquare-32bit-$version.deb" -cp "gui/deploy/Bitsquare-32bit-$version.deb" "/home/bitsquare/Desktop/Bitsquare-32bit-$version.deb" +sudo alien -r -c -k gui/deploy/bundles/bitsquare-$version.deb + +mv "gui/deploy/bundles/bitsquare-$version.deb" "/media/sf_vm_shared_ubuntu14_32bit/Bitsquare-32bit-$version.deb" +mv "bitsquare-$version-1.i386.rpm" "/media/sf_vm_shared_ubuntu14_32bit/Bitsquare-32bit-$version.rpm" +rm -r gui/deploy/ cd package/linux \ No newline at end of file diff --git a/package/linux/64bitBuild.sh b/package/linux/64bitBuild.sh index 993283dec79..4a376b87b0e 100644 --- a/package/linux/64bitBuild.sh +++ b/package/linux/64bitBuild.sh @@ -6,7 +6,7 @@ mkdir -p gui/deploy set -e # Edit version -version=0.4.9.2 +version=0.4.9.3 jarFile="/media/sf_vm_shared_ubuntu/Bitsquare-$version.jar" jdkfixFile="/media/sf_vm_shared_ubuntu/jdkfix-$version.jar" @@ -32,12 +32,10 @@ $JAVA_HOME/bin/javapackager \ -appclass io.bitsquare.app.BitsquareAppMain \ -outfile Bitsquare -rm gui/deploy/Bitsquare.html -rm gui/deploy/Bitsquare.jnlp -rm gui/deploy/LICENSE -mv "gui/deploy/bundles/bitsquare-$version.deb" "gui/deploy/Bitsquare-$version.deb" -rmdir gui/deploy/bundles -cp "gui/deploy/Bitsquare-$version.deb" "/media/sf_vm_shared_ubuntu/Bitsquare-64bit-$version.deb" -cp "gui/deploy/Bitsquare-$version.deb" "/home/mk/Desktop/Bitsquare-64bit-$version.deb" +#sudo alien -r -c -k gui/deploy/bundles/bitsquare-$version.deb + +mv "gui/deploy/bundles/bitsquare-$version.deb" "/media/sf_vm_shared_ubuntu/Bitsquare-64bit-$version.deb" +#mv "bitsquare-$version-1.x86_64.rpm" "/media/sf_vm_shared_ubuntu/Bitsquare-64bit-$version.rpm" +rm -r gui/deploy/ cd package/linux \ No newline at end of file diff --git a/package/mac/create_app.sh b/package/mac/create_app.sh index 61131206408..d51be6ab045 100755 --- a/package/mac/create_app.sh +++ b/package/mac/create_app.sh @@ -5,7 +5,7 @@ mkdir -p gui/deploy set -e -version="0.4.9.2" +version="0.4.9.3" mvn clean package -DskipTests -Dmaven.javadoc.skip=true diff --git a/package/mac/finalize.sh b/package/mac/finalize.sh index d86a69c7f32..1b9bb226955 100644 --- a/package/mac/finalize.sh +++ b/package/mac/finalize.sh @@ -1,6 +1,6 @@ #!/bin/bash -version="0.4.9.2" +version="0.4.9.3" target_dir="/Users/mk/Documents/__bitsquare/_releases/$version" src_dir="/Users/mk/Documents/_intellij/bitsquare" @@ -17,6 +17,13 @@ cp "/Users/mk/vm_shared_ubuntu14_32bit/$deb32" "$target_dir/" deb64="Bitsquare-64bit-$version.deb" cp "/Users/mk/vm_shared_ubuntu/$deb64" "$target_dir/" +rpm32="Bitsquare-32bit-$version.rpm" +cp "/Users/mk/vm_shared_ubuntu14_32bit/$rpm32" "$target_dir/" + +rpm64="Bitsquare-64bit-$version.rpm" +cp "/Users/mk/vm_shared_ubuntu/$rpm64" "$target_dir/" + + exe="Bitsquare-$version.exe" win32="Bitsquare-32bit-$version.exe" cp "/Users/mk/vm_shared_windows_32bit/bundles/$exe" "$target_dir/$win32" @@ -26,7 +33,7 @@ cp "/Users/mk/vm_shared_windows/bundles/$exe" "/Users/mk/vm_shared_win10/$win64" cd "$target_dir" -shasum -a 256 "$mac" "$deb64" "$deb32" "$win64" "$win32" > sha256_hashes.txt +shasum -a 256 "$mac" "$deb64" "$deb32" "$rpm64" "$rpm32" "$win64" "$win32" > sha256_hashes.txt gpg --local-user manfred@bitsquare.io --output signed_sha256_hashes.txt --clearsign sha256_hashes.txt diff --git a/package/windows/32bitBuild.bat b/package/windows/32bitBuild.bat index ce2d898c32d..5402c053e56 100644 --- a/package/windows/32bitBuild.bat +++ b/package/windows/32bitBuild.bat @@ -6,7 +6,7 @@ :: 64 bit build :: Needs Inno Setup 5 or later (http://www.jrsoftware.org/isdl.php) -SET version=0.4.9.2 +SET version=0.4.9.3 SET jdk=C:\Program Files\Java\jdk1.8.0_92 SET outdir=\\VBOXSVR\vm_shared_windows_32bit diff --git a/package/windows/64bitBuild.bat b/package/windows/64bitBuild.bat index 0312e9a328e..6a32b345f09 100644 --- a/package/windows/64bitBuild.bat +++ b/package/windows/64bitBuild.bat @@ -8,7 +8,7 @@ :: Did not get -BjvmOptions=-Xbootclasspath working on windows, but if the jdkfix jar is copied into the jdk/jre dir it will override the default classes -SET version=0.4.9.2 +SET version=0.4.9.3 SET jdk=C:\Program Files\Java\jdk1.8.0_92 SET outdir=\\VBOXSVR\vm_shared_windows diff --git a/package/windows/Bitsquare.iss b/package/windows/Bitsquare.iss index 2a229d3b286..e825906a3f7 100755 --- a/package/windows/Bitsquare.iss +++ b/package/windows/Bitsquare.iss @@ -3,7 +3,7 @@ [Setup] AppId={{bitsquare}} AppName=Bitsquare -AppVersion=0.4.9.2 +AppVersion=0.4.9.3 AppVerName=Bitsquare AppPublisher=Bitsquare AppComments=Bitsquare diff --git a/pom.xml b/pom.xml old mode 100755 new mode 100644 index ec29562ce6a..a8e3c725452 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ io.bitsquare parent pom - 0.4.9.2 + 0.4.9.3 Bitsquare - The decentralized bitcoin exchange https://bitsquare.io @@ -48,6 +48,7 @@ headless seednode monitor + statistics jdkfix diff --git a/seednode/pom.xml b/seednode/pom.xml index 0c20d443011..c2815a6ff8f 100644 --- a/seednode/pom.xml +++ b/seednode/pom.xml @@ -5,7 +5,7 @@ parent io.bitsquare - 0.4.9.2 + 0.4.9.3 4.0.0 diff --git a/seednode/src/main/java/io/bitsquare/seednode/SeedNode.java b/seednode/src/main/java/io/bitsquare/seednode/SeedNode.java index c594edece47..4ea1783af13 100644 --- a/seednode/src/main/java/io/bitsquare/seednode/SeedNode.java +++ b/seednode/src/main/java/io/bitsquare/seednode/SeedNode.java @@ -13,8 +13,8 @@ import io.bitsquare.common.UserThread; import io.bitsquare.common.handlers.ResultHandler; import io.bitsquare.common.util.Utilities; +import io.bitsquare.p2p.BootstrapListener; import io.bitsquare.p2p.P2PService; -import io.bitsquare.p2p.P2PServiceListener; import io.bitsquare.trade.offer.OpenOfferManager; import io.bitsquare.trade.statistics.TradeStatisticsManager; import org.apache.commons.lang3.exception.ExceptionUtils; @@ -74,49 +74,9 @@ public SeedNode() { injector = Guice.createInjector(seedNodeModule); Version.setBtcNetworkId(injector.getInstance(BitsquareEnvironment.class).getBitcoinNetwork().ordinal()); p2pService = injector.getInstance(P2PService.class); - p2pService.start(false, new P2PServiceListener() { - @Override - public void onRequestingDataCompleted() { - } - - @Override - public void onNoSeedNodeAvailable() { - - } - - @Override - public void onNoPeersAvailable() { - - } - + p2pService.start(false, new BootstrapListener() { @Override public void onBootstrapComplete() { - - } - - @Override - public void onTorNodeReady() { - - } - - @Override - public void onHiddenServicePublished() { - - } - - @Override - public void onSetupFailed(Throwable throwable) { - - } - - @Override - public void onUseDefaultBridges() { - - } - - @Override - public void onRequestCustomBridges(Runnable resultHandler) { - } }); diff --git a/seednode/src/main/java/io/bitsquare/seednode/SeedNodeMain.java b/seednode/src/main/java/io/bitsquare/seednode/SeedNodeMain.java index 75ad977e8db..19502528b88 100644 --- a/seednode/src/main/java/io/bitsquare/seednode/SeedNodeMain.java +++ b/seednode/src/main/java/io/bitsquare/seednode/SeedNodeMain.java @@ -39,10 +39,11 @@ public class SeedNodeMain extends BitsquareExecutable { private static final Logger log = LoggerFactory.getLogger(SeedNodeMain.class); - private static final long MAX_MEMORY_MB = 600; + private static long MAX_MEMORY_MB_DEFAULT = 500; private static final long CHECK_MEMORY_PERIOD_SEC = 10 * 60; private SeedNode seedNode; private volatile boolean stopped; + private static long maxMemory = MAX_MEMORY_MB_DEFAULT; public static void main(String[] args) throws Exception { final ThreadFactory threadFactory = new ThreadFactoryBuilder() @@ -89,11 +90,20 @@ protected void doExecute(OptionSet options) { SeedNode.setEnvironment(environment); UserThread.execute(() -> seedNode = new SeedNode()); + String maxMemoryOption = environment.getProperty(CoreOptionKeys.MAX_MEMORY); + if (maxMemoryOption != null && !maxMemoryOption.isEmpty()) { + try { + maxMemory = Integer.parseInt(maxMemoryOption); + } catch (Throwable t) { + log.error(t.getMessage()); + } + } + UserThread.runPeriodically(() -> { Profiler.printSystemLoad(log); long usedMemoryInMB = Profiler.getUsedMemoryInMB(); if (!stopped) { - if (usedMemoryInMB > (MAX_MEMORY_MB - 100)) { + if (usedMemoryInMB > (maxMemory - 100)) { log.warn("\n\n%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n" + "We are over our memory warn limit and call the GC. usedMemoryInMB: {}" + "\n%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n\n", @@ -103,7 +113,7 @@ protected void doExecute(OptionSet options) { Profiler.printSystemLoad(log); } - if (usedMemoryInMB > MAX_MEMORY_MB) { + if (usedMemoryInMB > maxMemory) { stopped = true; seedNode.gracefulShutDown(() -> { try { diff --git a/statistics/pom.xml b/statistics/pom.xml new file mode 100644 index 00000000000..ec210d47e98 --- /dev/null +++ b/statistics/pom.xml @@ -0,0 +1,85 @@ + + + + parent + io.bitsquare + 0.4.9.3 + + 4.0.0 + + statistics + + + + + + false + ${basedir}/src/main/java + + **/*.fxml + **/*.css + + + + false + ${basedir}/src/main/resources + + **/*.* + + + + + + + org.apache.maven.plugins + maven-shade-plugin + 2.3 + + + false + + + io.bitsquare.statistics.StatisticsMain + + + + + + *:* + + META-INF/*.SF + META-INF/*.DSA + META-INF/*.RSA + + + + + + + package + + shade + + + true + bundled + Statistics + + + + + + + + + + + io.bitsquare + core + ${project.parent.version} + + + \ No newline at end of file diff --git a/statistics/src/main/java/io/bitsquare/statistics/Statistics.java b/statistics/src/main/java/io/bitsquare/statistics/Statistics.java new file mode 100644 index 00000000000..2a4b79d04ff --- /dev/null +++ b/statistics/src/main/java/io/bitsquare/statistics/Statistics.java @@ -0,0 +1,133 @@ +package io.bitsquare.statistics; + +import ch.qos.logback.classic.Level; +import com.google.inject.Guice; +import com.google.inject.Injector; +import io.bitsquare.app.BitsquareEnvironment; +import io.bitsquare.app.CoreOptionKeys; +import io.bitsquare.app.Log; +import io.bitsquare.app.Version; +import io.bitsquare.arbitration.ArbitratorManager; +import io.bitsquare.btc.WalletService; +import io.bitsquare.btc.pricefeed.PriceFeedService; +import io.bitsquare.common.CommonOptionKeys; +import io.bitsquare.common.UserThread; +import io.bitsquare.common.handlers.ResultHandler; +import io.bitsquare.common.util.Utilities; +import io.bitsquare.locale.CurrencyUtil; +import io.bitsquare.p2p.BootstrapListener; +import io.bitsquare.p2p.P2PService; +import io.bitsquare.trade.offer.OfferBookService; +import io.bitsquare.trade.offer.OpenOfferManager; +import io.bitsquare.trade.statistics.TradeStatisticsManager; +import org.apache.commons.lang3.exception.ExceptionUtils; +import org.bitcoinj.store.BlockStoreException; +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.core.env.Environment; + +import java.nio.file.Paths; +import java.security.Security; + +public class Statistics { + private static final Logger log = LoggerFactory.getLogger(Statistics.class); + private static Environment env; + private final Injector injector; + private final StatisticsModule statisticsModule; + private final TradeStatisticsManager tradeStatisticsManager; + private final OfferBookService offerBookService; + private final PriceFeedService priceFeedService; + + private P2PService p2pService; + + public static void setEnvironment(Environment env) { + Statistics.env = env; + } + + public Statistics() { + String logPath = Paths.get(env.getProperty(CoreOptionKeys.APP_DATA_DIR_KEY), "bitsquare").toString(); + Log.setup(logPath); + log.info("Log files under: " + logPath); + Version.printVersion(); + Utilities.printSysInfo(); + Log.setLevel(Level.toLevel(env.getRequiredProperty(CommonOptionKeys.LOG_LEVEL_KEY))); + + // setup UncaughtExceptionHandler + Thread.UncaughtExceptionHandler handler = (thread, throwable) -> { + // Might come from another thread + if (throwable.getCause() != null && throwable.getCause().getCause() != null && + throwable.getCause().getCause() instanceof BlockStoreException) { + log.error(throwable.getMessage()); + } else { + log.error("Uncaught Exception from thread " + Thread.currentThread().getName()); + log.error("throwableMessage= " + throwable.getMessage()); + log.error("throwableClass= " + throwable.getClass()); + log.error("Stack trace:\n" + ExceptionUtils.getStackTrace(throwable)); + throwable.printStackTrace(); + } + }; + Thread.setDefaultUncaughtExceptionHandler(handler); + Thread.currentThread().setUncaughtExceptionHandler(handler); + + if (Utilities.isRestrictedCryptography()) + Utilities.removeCryptographyRestrictions(); + Security.addProvider(new BouncyCastleProvider()); + + + statisticsModule = new StatisticsModule(env); + injector = Guice.createInjector(statisticsModule); + Version.setBtcNetworkId(injector.getInstance(BitsquareEnvironment.class).getBitcoinNetwork().ordinal()); + p2pService = injector.getInstance(P2PService.class); + p2pService.start(false, new BootstrapListener() { + @Override + public void onBootstrapComplete() { + } + }); + + // We want to persist trade statistics so we need to instantiate the tradeStatisticsManager + tradeStatisticsManager = injector.getInstance(TradeStatisticsManager.class); + offerBookService = injector.getInstance(OfferBookService.class); + priceFeedService = injector.getInstance(PriceFeedService.class); + + // We need the price feed for market based offers + priceFeedService.setCurrencyCode(CurrencyUtil.getDefaultTradeCurrency().getCode()); + priceFeedService.setType(PriceFeedService.Type.LAST); + priceFeedService.init(price -> log.debug("price " + price), + (errorMessage, throwable) -> log.warn(throwable.getMessage())); + } + + public void shutDown() { + gracefulShutDown(() -> { + log.debug("Shutdown complete"); + System.exit(0); + }); + } + + public void gracefulShutDown(ResultHandler resultHandler) { + log.debug("gracefulShutDown"); + try { + if (injector != null) { + injector.getInstance(ArbitratorManager.class).shutDown(); + injector.getInstance(OpenOfferManager.class).shutDown(() -> { + injector.getInstance(P2PService.class).shutDown(() -> { + injector.getInstance(WalletService.class).shutDownDone.addListener((ov, o, n) -> { + statisticsModule.close(injector); + log.debug("Graceful shutdown completed"); + resultHandler.handleResult(); + }); + injector.getInstance(WalletService.class).shutDown(); + }); + }); + // we wait max 5 sec. + UserThread.runAfter(resultHandler::handleResult, 5); + } else { + UserThread.runAfter(resultHandler::handleResult, 1); + } + } catch (Throwable t) { + log.debug("App shutdown failed with exception"); + t.printStackTrace(); + System.exit(1); + } + } +} diff --git a/statistics/src/main/java/io/bitsquare/statistics/StatisticsMain.java b/statistics/src/main/java/io/bitsquare/statistics/StatisticsMain.java new file mode 100644 index 00000000000..45219fd1828 --- /dev/null +++ b/statistics/src/main/java/io/bitsquare/statistics/StatisticsMain.java @@ -0,0 +1,142 @@ +/* + * This file is part of Bitsquare. + * + * Bitsquare is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bitsquare is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bitsquare. If not, see . + */ + +package io.bitsquare.statistics; + +import com.google.common.util.concurrent.ThreadFactoryBuilder; +import io.bitsquare.app.BitsquareEnvironment; +import io.bitsquare.app.BitsquareExecutable; +import io.bitsquare.app.CoreOptionKeys; +import io.bitsquare.common.UserThread; +import io.bitsquare.common.util.Profiler; +import io.bitsquare.common.util.RestartUtil; +import joptsimple.OptionException; +import joptsimple.OptionParser; +import joptsimple.OptionSet; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.util.concurrent.Executors; +import java.util.concurrent.ThreadFactory; + +import static io.bitsquare.app.BitsquareEnvironment.DEFAULT_APP_NAME; +import static io.bitsquare.app.BitsquareEnvironment.DEFAULT_USER_DATA_DIR; + +public class StatisticsMain extends BitsquareExecutable { + private static final Logger log = LoggerFactory.getLogger(StatisticsMain.class); + private static long MAX_MEMORY_MB_DEFAULT = 400; + private static final long CHECK_MEMORY_PERIOD_SEC = 10 * 60; + private Statistics statistics; + private volatile boolean stopped; + private static long maxMemory = MAX_MEMORY_MB_DEFAULT; + + public static void main(String[] args) throws Exception { + final ThreadFactory threadFactory = new ThreadFactoryBuilder() + .setNameFormat("Statistics") + .setDaemon(true) + .build(); + UserThread.setExecutor(Executors.newSingleThreadExecutor(threadFactory)); + + // We don't want to do the full argument parsing here as that might easily change in update versions + // So we only handle the absolute minimum which is APP_NAME, APP_DATA_DIR_KEY and USER_DATA_DIR + BitsquareEnvironment.setDefaultAppName("Bitsquare_statistics"); + OptionParser parser = new OptionParser(); + parser.allowsUnrecognizedOptions(); + parser.accepts(CoreOptionKeys.USER_DATA_DIR_KEY, description("User data directory", DEFAULT_USER_DATA_DIR)) + .withRequiredArg(); + parser.accepts(CoreOptionKeys.APP_NAME_KEY, description("Application name", DEFAULT_APP_NAME)) + .withRequiredArg(); + + OptionSet options; + try { + options = parser.parse(args); + } catch (OptionException ex) { + System.out.println("error: " + ex.getMessage()); + System.out.println(); + parser.printHelpOn(System.out); + System.exit(EXIT_FAILURE); + return; + } + BitsquareEnvironment bitsquareEnvironment = new BitsquareEnvironment(options); + + // need to call that before BitsquareAppMain().execute(args) + BitsquareExecutable.initAppDir(bitsquareEnvironment.getProperty(CoreOptionKeys.APP_DATA_DIR_KEY)); + + // For some reason the JavaFX launch process results in us losing the thread context class loader: reset it. + // In order to work around a bug in JavaFX 8u25 and below, you must include the following code as the first line of your realMain method: + Thread.currentThread().setContextClassLoader(StatisticsMain.class.getClassLoader()); + + new StatisticsMain().execute(args); + } + + @Override + protected void doExecute(OptionSet options) { + final BitsquareEnvironment environment = new BitsquareEnvironment(options); + Statistics.setEnvironment(environment); + UserThread.execute(() -> statistics = new Statistics()); + + String maxMemoryOption = environment.getProperty(CoreOptionKeys.MAX_MEMORY); + if (maxMemoryOption != null && !maxMemoryOption.isEmpty()) { + try { + maxMemory = Integer.parseInt(maxMemoryOption); + } catch (Throwable t) { + log.error(t.getMessage()); + } + } + + UserThread.runPeriodically(() -> { + Profiler.printSystemLoad(log); + long usedMemoryInMB = Profiler.getUsedMemoryInMB(); + if (!stopped) { + if (usedMemoryInMB > (maxMemory - 100)) { + log.warn("\n\n%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n" + + "We are over our memory warn limit and call the GC. usedMemoryInMB: {}" + + "\n%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n\n", + usedMemoryInMB); + System.gc(); + usedMemoryInMB = Profiler.getUsedMemoryInMB(); + Profiler.printSystemLoad(log); + } + + if (usedMemoryInMB > maxMemory) { + stopped = true; + statistics.gracefulShutDown(() -> { + try { + final String[] tokens = environment.getAppDataDir().split("_"); + String logPath = "error_" + (tokens.length > 1 ? tokens[tokens.length - 2] : "") + ".log"; + RestartUtil.restartApplication(logPath); + } catch (IOException e) { + log.error(e.toString()); + e.printStackTrace(); + } finally { + log.warn("Shutdown complete"); + System.exit(0); + } + }); + } + } + }, CHECK_MEMORY_PERIOD_SEC); + + while (true) { + try { + Thread.sleep(Long.MAX_VALUE); + } catch (InterruptedException ignore) { + } + } + } +} diff --git a/statistics/src/main/java/io/bitsquare/statistics/StatisticsModule.java b/statistics/src/main/java/io/bitsquare/statistics/StatisticsModule.java new file mode 100644 index 00000000000..fad4dbfecdc --- /dev/null +++ b/statistics/src/main/java/io/bitsquare/statistics/StatisticsModule.java @@ -0,0 +1,110 @@ +/* + * This file is part of Bitsquare. + * + * Bitsquare is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bitsquare is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bitsquare. If not, see . + */ + +package io.bitsquare.statistics; + +import com.google.inject.Singleton; +import io.bitsquare.alert.AlertModule; +import io.bitsquare.app.AppModule; +import io.bitsquare.app.BitsquareEnvironment; +import io.bitsquare.arbitration.ArbitratorModule; +import io.bitsquare.btc.BitcoinModule; +import io.bitsquare.common.Clock; +import io.bitsquare.common.crypto.KeyRing; +import io.bitsquare.common.crypto.KeyStorage; +import io.bitsquare.crypto.EncryptionServiceModule; +import io.bitsquare.filter.FilterModule; +import io.bitsquare.p2p.P2PModule; +import io.bitsquare.storage.Storage; +import io.bitsquare.trade.TradeModule; +import io.bitsquare.trade.offer.OfferModule; +import io.bitsquare.user.Preferences; +import io.bitsquare.user.User; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.core.env.Environment; + +import java.io.File; + +import static com.google.inject.name.Names.named; + +class StatisticsModule extends AppModule { + private static final Logger log = LoggerFactory.getLogger(StatisticsModule.class); + + public StatisticsModule(Environment env) { + super(env); + } + + @Override + protected void configure() { + bind(KeyStorage.class).in(Singleton.class); + bind(KeyRing.class).in(Singleton.class); + bind(User.class).in(Singleton.class); + bind(Preferences.class).in(Singleton.class); + bind(Clock.class).in(Singleton.class); + + File storageDir = new File(env.getRequiredProperty(Storage.DIR_KEY)); + bind(File.class).annotatedWith(named(Storage.DIR_KEY)).toInstance(storageDir); + + File keyStorageDir = new File(env.getRequiredProperty(KeyStorage.DIR_KEY)); + bind(File.class).annotatedWith(named(KeyStorage.DIR_KEY)).toInstance(keyStorageDir); + + bind(BitsquareEnvironment.class).toInstance((BitsquareEnvironment) env); + + // ordering is used for shut down sequence + install(tradeModule()); + install(encryptionServiceModule()); + install(arbitratorModule()); + install(offerModule()); + install(torModule()); + install(bitcoinModule()); + install(alertModule()); + install(filterModule()); + } + + private TradeModule tradeModule() { + return new TradeModule(env); + } + + private EncryptionServiceModule encryptionServiceModule() { + return new EncryptionServiceModule(env); + } + + private ArbitratorModule arbitratorModule() { + return new ArbitratorModule(env); + } + + private AlertModule alertModule() { + return new AlertModule(env); + } + + private FilterModule filterModule() { + return new FilterModule(env); + } + + private OfferModule offerModule() { + return new OfferModule(env); + } + + private P2PModule torModule() { + return new P2PModule(env); + } + + private BitcoinModule bitcoinModule() { + return new BitcoinModule(env); + } +} diff --git a/statistics/src/main/resources/logback.xml b/statistics/src/main/resources/logback.xml new file mode 100644 index 00000000000..b535a848292 --- /dev/null +++ b/statistics/src/main/resources/logback.xml @@ -0,0 +1,51 @@ + + + + + + + %highlight(%d{MMM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{15}: %msg %xEx%n) + + + + + + + + + + + + + + + + + +