Merge remote-tracking branch 'origin/Development' into Development

This commit is contained in:
Manfred Karrer 2016-07-22 16:32:31 +02:00
commit 25b17e638f
106 changed files with 3045 additions and 1082 deletions

View file

@ -39,7 +39,7 @@ public class Version {
// VERSION = 0.3.5 -> LOCAL_DB_VERSION = 2
// VERSION = 0.4.0 -> LOCAL_DB_VERSION = 3
// VERSION = 0.4.2 -> LOCAL_DB_VERSION = 4
public static final int LOCAL_DB_VERSION = 4;
public static final int LOCAL_DB_VERSION = 4;
// The version nr. of the current protocol. The offer holds that version.
// A taker will check the version of the offers to see if his version is compatible.

View file

@ -1,6 +1,6 @@
package io.bitsquare.common;
public class OptionKeys {
public class CommonOptionKeys {
public static final String LOG_LEVEL_KEY = "logLevel";
public static final String IGNORE_DEV_MSG_KEY = "ignoreDevMsg";
}

View file

@ -68,6 +68,7 @@ public abstract class Task<T extends Model> {
}
protected void failed() {
log.error(errorMessage);
taskHandler.handleErrorMessage(errorMessage);
}

View file

@ -80,7 +80,9 @@ public class Utilities {
ThreadPoolExecutor executor = new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTimeInSec,
TimeUnit.SECONDS, new ArrayBlockingQueue<>(maximumPoolSize), threadFactory);
executor.allowCoreThreadTimeOut(true);
executor.setRejectedExecutionHandler((r, e) -> log.warn("RejectedExecutionHandler called"));
executor.setRejectedExecutionHandler((r, e) -> {
log.debug("RejectedExecutionHandler called");
});
return executor;
}
@ -99,7 +101,9 @@ public class Utilities {
executor.allowCoreThreadTimeOut(true);
executor.setMaximumPoolSize(maximumPoolSize);
executor.setExecuteExistingDelayedTasksAfterShutdownPolicy(false);
executor.setRejectedExecutionHandler((r, e) -> log.warn("RejectedExecutionHandler called"));
executor.setRejectedExecutionHandler((r, e) -> {
log.debug("RejectedExecutionHandler called");
});
return executor;
}
@ -135,7 +139,7 @@ public class Utilities {
? "64" : "32";
} else if (osArch.contains("arm")) {
// armv8 is 64 bit, armv7l is 32 bit
return osArch.contains("64") || osArch.contains("v8") ? "64" : "32";
return osArch.contains("64") || osArch.contains("v8") ? "64" : "32";
} else if (isLinux()) {
return osArch.startsWith("i") ? "32" : "64";
} else {

View file

@ -67,17 +67,9 @@ public class FileManager<T> {
saveNowInternal(serializable);
return null;
};
Runtime.getRuntime().addShutdownHook(new Thread() {
@Override
public void run() {
try {
FileManager.this.shutDown();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
});
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
UserThread.execute(FileManager.this::shutDown);
}, "FileManager.ShutDownHook"));
}

View file

@ -19,7 +19,7 @@ package io.bitsquare.alert;
import com.google.inject.Inject;
import com.google.inject.name.Named;
import io.bitsquare.common.OptionKeys;
import io.bitsquare.common.CommonOptionKeys;
import io.bitsquare.common.crypto.KeyRing;
import io.bitsquare.p2p.P2PService;
import io.bitsquare.p2p.storage.HashMapChangedListener;
@ -56,7 +56,7 @@ public class AlertManager {
///////////////////////////////////////////////////////////////////////////////////////////
@Inject
public AlertManager(P2PService p2PService, KeyRing keyRing, User user, @Named(OptionKeys.IGNORE_DEV_MSG_KEY) boolean ignoreDevMsg) {
public AlertManager(P2PService p2PService, KeyRing keyRing, User user, @Named(CommonOptionKeys.IGNORE_DEV_MSG_KEY) boolean ignoreDevMsg) {
this.p2PService = p2PService;
this.keyRing = keyRing;
this.user = user;

View file

@ -19,7 +19,7 @@ package io.bitsquare.alert;
import com.google.inject.Inject;
import com.google.inject.name.Named;
import io.bitsquare.common.OptionKeys;
import io.bitsquare.common.CommonOptionKeys;
import io.bitsquare.common.crypto.KeyRing;
import io.bitsquare.crypto.DecryptedMsgWithPubKey;
import io.bitsquare.p2p.Message;
@ -58,7 +58,7 @@ public class PrivateNotificationManager {
///////////////////////////////////////////////////////////////////////////////////////////
@Inject
public PrivateNotificationManager(P2PService p2PService, KeyRing keyRing, @Named(OptionKeys.IGNORE_DEV_MSG_KEY) boolean ignoreDevMsg) {
public PrivateNotificationManager(P2PService p2PService, KeyRing keyRing, @Named(CommonOptionKeys.IGNORE_DEV_MSG_KEY) boolean ignoreDevMsg) {
this.p2PService = p2PService;
this.keyRing = keyRing;

View file

@ -20,11 +20,13 @@ package io.bitsquare.app;
import ch.qos.logback.classic.Level;
import io.bitsquare.BitsquareException;
import io.bitsquare.btc.BitcoinNetwork;
import io.bitsquare.btc.BtcOptionKeys;
import io.bitsquare.btc.UserAgent;
import io.bitsquare.btc.WalletService;
import io.bitsquare.common.CommonOptionKeys;
import io.bitsquare.common.crypto.KeyStorage;
import io.bitsquare.common.util.Utilities;
import io.bitsquare.network.OptionKeys;
import io.bitsquare.network.NetworkOptionKeys;
import io.bitsquare.storage.Storage;
import io.bitsquare.util.spring.JOptCommandLinePropertySource;
import joptsimple.OptionSet;
@ -56,7 +58,12 @@ public class BitsquareEnvironment extends StandardEnvironment {
public static final String DEFAULT_USER_DATA_DIR = defaultUserDataDir();
public static final String APP_NAME_KEY = "appName";
public static final String DEFAULT_APP_NAME = "Bitsquare";
public static void setDefaultAppName(String defaultAppName) {
DEFAULT_APP_NAME = defaultAppName;
}
public static String DEFAULT_APP_NAME = "Bitsquare";
public static final String APP_DATA_DIR_KEY = "appDataDir";
public static final String DEFAULT_APP_DATA_DIR = appDataDir(DEFAULT_USER_DATA_DIR, DEFAULT_APP_NAME);
@ -77,7 +84,7 @@ public class BitsquareEnvironment extends StandardEnvironment {
private final String btcNetworkDir;
private final String logLevel;
private BitcoinNetwork bitcoinNetwork;
private final String seedNodes, ignoreDevMsg;
private final String btcSeedNodes, seedNodes, ignoreDevMsg, useTorForBtc, myAddress, banList;
public BitsquareEnvironment(OptionSet options) {
this(new JOptCommandLinePropertySource(BITSQUARE_COMMANDLINE_PROPERTY_SOURCE_NAME, checkNotNull(
@ -126,17 +133,33 @@ public class BitsquareEnvironment extends StandardEnvironment {
(String) commandLineProperties.getProperty(APP_DATA_DIR_KEY) :
appDataDir(userDataDir, appName);
logLevel = commandLineProperties.containsProperty(io.bitsquare.common.OptionKeys.LOG_LEVEL_KEY) ?
(String) commandLineProperties.getProperty(io.bitsquare.common.OptionKeys.LOG_LEVEL_KEY) :
logLevel = commandLineProperties.containsProperty(CommonOptionKeys.LOG_LEVEL_KEY) ?
(String) commandLineProperties.getProperty(CommonOptionKeys.LOG_LEVEL_KEY) :
LOG_LEVEL_DEFAULT;
seedNodes = commandLineProperties.containsProperty(OptionKeys.SEED_NODES_KEY) ?
(String) commandLineProperties.getProperty(OptionKeys.SEED_NODES_KEY) :
seedNodes = commandLineProperties.containsProperty(NetworkOptionKeys.SEED_NODES_KEY) ?
(String) commandLineProperties.getProperty(NetworkOptionKeys.SEED_NODES_KEY) :
"";
ignoreDevMsg = commandLineProperties.containsProperty(io.bitsquare.common.OptionKeys.IGNORE_DEV_MSG_KEY) ?
(String) commandLineProperties.getProperty(io.bitsquare.common.OptionKeys.IGNORE_DEV_MSG_KEY) :
myAddress = commandLineProperties.containsProperty(NetworkOptionKeys.MY_ADDRESS) ?
(String) commandLineProperties.getProperty(NetworkOptionKeys.MY_ADDRESS) :
"";
banList = commandLineProperties.containsProperty(NetworkOptionKeys.BAN_LIST) ?
(String) commandLineProperties.getProperty(NetworkOptionKeys.BAN_LIST) :
"";
ignoreDevMsg = commandLineProperties.containsProperty(CommonOptionKeys.IGNORE_DEV_MSG_KEY) ?
(String) commandLineProperties.getProperty(CommonOptionKeys.IGNORE_DEV_MSG_KEY) :
"";
btcSeedNodes = commandLineProperties.containsProperty(BtcOptionKeys.BTC_SEED_NODES) ?
(String) commandLineProperties.getProperty(BtcOptionKeys.BTC_SEED_NODES) :
"";
useTorForBtc = commandLineProperties.containsProperty(BtcOptionKeys.USE_TOR_FOR_BTC) ?
(String) commandLineProperties.getProperty(BtcOptionKeys.USE_TOR_FOR_BTC) :
"";
MutablePropertySources propertySources = this.getPropertySources();
propertySources.addFirst(commandLineProperties);
@ -193,10 +216,15 @@ public class BitsquareEnvironment extends StandardEnvironment {
{
setProperty(APP_DATA_DIR_KEY, appDataDir);
setProperty(io.bitsquare.common.OptionKeys.LOG_LEVEL_KEY, logLevel);
setProperty(CommonOptionKeys.LOG_LEVEL_KEY, logLevel);
setProperty(OptionKeys.SEED_NODES_KEY, seedNodes);
setProperty(io.bitsquare.common.OptionKeys.IGNORE_DEV_MSG_KEY, ignoreDevMsg);
setProperty(NetworkOptionKeys.SEED_NODES_KEY, seedNodes);
setProperty(NetworkOptionKeys.MY_ADDRESS, myAddress);
setProperty(NetworkOptionKeys.BAN_LIST, banList);
setProperty(CommonOptionKeys.IGNORE_DEV_MSG_KEY, ignoreDevMsg);
setProperty(BtcOptionKeys.BTC_SEED_NODES, btcSeedNodes);
setProperty(BtcOptionKeys.USE_TOR_FOR_BTC, useTorForBtc);
setProperty(APP_NAME_KEY, appName);
setProperty(USER_DATA_DIR_KEY, userDataDir);
@ -208,9 +236,9 @@ public class BitsquareEnvironment extends StandardEnvironment {
setProperty(Storage.DIR_KEY, Paths.get(btcNetworkDir, "db").toString());
setProperty(KeyStorage.DIR_KEY, Paths.get(btcNetworkDir, "keys").toString());
setProperty(OptionKeys.TOR_DIR, Paths.get(btcNetworkDir, "tor").toString());
setProperty(NetworkOptionKeys.TOR_DIR, Paths.get(btcNetworkDir, "tor").toString());
setProperty(OptionKeys.NETWORK_ID, String.valueOf(bitcoinNetwork.ordinal()));
setProperty(NetworkOptionKeys.NETWORK_ID, String.valueOf(bitcoinNetwork.ordinal()));
}
});
}

View file

@ -17,11 +17,25 @@
package io.bitsquare.app;
import io.bitsquare.BitsquareException;
import io.bitsquare.btc.BitcoinNetwork;
import io.bitsquare.btc.BtcOptionKeys;
import io.bitsquare.btc.RegTestHost;
import io.bitsquare.common.CommonOptionKeys;
import io.bitsquare.network.NetworkOptionKeys;
import io.bitsquare.p2p.P2PService;
import io.bitsquare.util.joptsimple.EnumValueConverter;
import joptsimple.OptionException;
import joptsimple.OptionParser;
import joptsimple.OptionSet;
import org.springframework.util.StringUtils;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import static io.bitsquare.app.BitsquareEnvironment.*;
import static java.lang.String.format;
import static java.lang.String.join;
@ -55,7 +69,53 @@ public abstract class BitsquareExecutable {
this.doExecute(options);
}
protected abstract void customizeOptionParsing(OptionParser parser);
protected void customizeOptionParsing(OptionParser parser) {
parser.accepts(USER_DATA_DIR_KEY, description("User data directory", DEFAULT_USER_DATA_DIR))
.withRequiredArg();
parser.accepts(APP_NAME_KEY, description("Application name", DEFAULT_APP_NAME))
.withRequiredArg();
parser.accepts(APP_DATA_DIR_KEY, description("Application data directory", DEFAULT_APP_DATA_DIR))
.withRequiredArg();
parser.accepts(CommonOptionKeys.LOG_LEVEL_KEY, description("Log level [OFF, ALL, ERROR, WARN, INFO, DEBUG, TRACE]", LOG_LEVEL_DEFAULT))
.withRequiredArg();
parser.accepts(NetworkOptionKeys.SEED_NODES_KEY, description("Override hard coded seed nodes as comma separated list: E.g. rxdkppp3vicnbgqt.onion:8002, mfla72c4igh5ta2t.onion:8002", ""))
.withRequiredArg();
parser.accepts(NetworkOptionKeys.MY_ADDRESS, description("My own onion address (used for botstrap nodes to exclude itself)", ""))
.withRequiredArg();
parser.accepts(NetworkOptionKeys.BAN_LIST, description("Nodes to exclude from network connections.", ""))
.withRequiredArg();
parser.accepts(CommonOptionKeys.IGNORE_DEV_MSG_KEY, description("If set to true all signed messages from Bitsquare developers are ignored " +
"(Global alert, Version update alert, Filters for offers, nodes or payment account data)", false))
.withRequiredArg()
.ofType(boolean.class);
parser.accepts(BtcOptionKeys.BTC_SEED_NODES, description("Custom seed nodes used for BitcoinJ.", ""))
.withRequiredArg();
parser.accepts(BtcOptionKeys.USE_TOR_FOR_BTC, description("If set to true BitcoinJ is routed over our native Tor instance.", ""))
.withRequiredArg();
// use a fixed port as arbitrator use that for his ID
parser.accepts(NetworkOptionKeys.PORT_KEY, description("Port to listen on", 9999))
.withRequiredArg()
.ofType(int.class);
parser.accepts(NetworkOptionKeys.USE_LOCALHOST, description("Use localhost network for development", false))
.withRequiredArg()
.ofType(boolean.class);
parser.accepts(NetworkOptionKeys.MAX_CONNECTIONS, description("Max. connections a peer will try to keep", P2PService.MAX_CONNECTIONS_DEFAULT))
.withRequiredArg()
.ofType(int.class);
parser.accepts(BitcoinNetwork.KEY, description("Bitcoin network", BitcoinNetwork.DEFAULT))
.withRequiredArg()
.ofType(BitcoinNetwork.class)
.withValuesConvertedBy(new EnumValueConverter(BitcoinNetwork.class));
parser.accepts(RegTestHost.KEY, description("", RegTestHost.DEFAULT))
.withRequiredArg()
.ofType(RegTestHost.class)
.withValuesConvertedBy(new EnumValueConverter(RegTestHost.class));
}
protected static String description(String descText, Object defaultValue) {
String description = "";
@ -67,4 +127,20 @@ public abstract class BitsquareExecutable {
}
protected abstract void doExecute(OptionSet options);
public static void initAppDir(String appDir) {
Path dir = Paths.get(appDir);
if (Files.exists(dir)) {
if (!Files.isWritable(dir))
throw new BitsquareException("Application data directory '%s' is not writeable", dir);
else
return;
}
try {
Files.createDirectory(dir);
} catch (IOException ex) {
throw new BitsquareException(ex, "Application data directory '%s' could not be created", dir);
}
}
}

View file

@ -27,8 +27,12 @@ import io.bitsquare.btc.TradeWalletService;
import io.bitsquare.btc.WalletService;
import io.bitsquare.btc.exceptions.TransactionVerificationException;
import io.bitsquare.btc.exceptions.WalletException;
import io.bitsquare.common.Timer;
import io.bitsquare.common.UserThread;
import io.bitsquare.common.crypto.KeyRing;
import io.bitsquare.common.crypto.PubKeyRing;
import io.bitsquare.common.handlers.ErrorMessageHandler;
import io.bitsquare.common.handlers.ResultHandler;
import io.bitsquare.crypto.DecryptedMsgWithPubKey;
import io.bitsquare.p2p.BootstrapListener;
import io.bitsquare.p2p.Message;
@ -37,8 +41,10 @@ import io.bitsquare.p2p.P2PService;
import io.bitsquare.p2p.messaging.SendMailboxMessageListener;
import io.bitsquare.storage.Storage;
import io.bitsquare.trade.Contract;
import io.bitsquare.trade.Tradable;
import io.bitsquare.trade.Trade;
import io.bitsquare.trade.TradeManager;
import io.bitsquare.trade.closed.ClosedTradableManager;
import io.bitsquare.trade.offer.OpenOffer;
import io.bitsquare.trade.offer.OpenOfferManager;
import javafx.collections.FXCollections;
@ -51,9 +57,12 @@ import org.slf4j.LoggerFactory;
import javax.inject.Named;
import java.io.File;
import java.util.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class DisputeManager {
private static final Logger log = LoggerFactory.getLogger(DisputeManager.class);
@ -61,6 +70,7 @@ public class DisputeManager {
private final TradeWalletService tradeWalletService;
private final WalletService walletService;
private final TradeManager tradeManager;
private ClosedTradableManager closedTradableManager;
private final OpenOfferManager openOfferManager;
private final P2PService p2PService;
private final KeyRing keyRing;
@ -72,6 +82,7 @@ public class DisputeManager {
private final CopyOnWriteArraySet<DecryptedMsgWithPubKey> decryptedDirectMessageWithPubKeys = new CopyOnWriteArraySet<>();
private final Map<String, Dispute> openDisputes;
private final Map<String, Dispute> closedDisputes;
private Map<String, Timer> delayMsgMap = new HashMap<>();
///////////////////////////////////////////////////////////////////////////////////////////
@ -83,6 +94,7 @@ public class DisputeManager {
TradeWalletService tradeWalletService,
WalletService walletService,
TradeManager tradeManager,
ClosedTradableManager closedTradableManager,
OpenOfferManager openOfferManager,
KeyRing keyRing,
@Named(Storage.DIR_KEY) File storageDir) {
@ -90,6 +102,7 @@ public class DisputeManager {
this.tradeWalletService = tradeWalletService;
this.walletService = walletService;
this.tradeManager = tradeManager;
this.closedTradableManager = closedTradableManager;
this.openOfferManager = openOfferManager;
this.keyRing = keyRing;
@ -192,43 +205,55 @@ public class DisputeManager {
log.warn("Unsupported message at dispatchMessage.\nmessage=" + message);
}
public void sendOpenNewDisputeMessage(Dispute dispute) {
public void sendOpenNewDisputeMessage(Dispute dispute, ResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) {
if (!disputes.contains(dispute)) {
DisputeCommunicationMessage disputeCommunicationMessage = new DisputeCommunicationMessage(dispute.getTradeId(),
keyRing.getPubKeyRing().hashCode(),
true,
"System message: " + (dispute.isSupportTicket() ?
"You opened a request for support."
: "You opened a request for a dispute.\n\n" + disputeInfo),
p2PService.getAddress());
disputeCommunicationMessage.setIsSystemMessage(true);
dispute.addDisputeMessage(disputeCommunicationMessage);
disputes.add(dispute);
disputesObservableList.add(dispute);
final Optional<Dispute> storedDisputeOptional = findDispute(dispute.getTradeId(), dispute.getTraderId());
if (!storedDisputeOptional.isPresent()) {
DisputeCommunicationMessage disputeCommunicationMessage = new DisputeCommunicationMessage(dispute.getTradeId(),
keyRing.getPubKeyRing().hashCode(),
true,
"System message: " + (dispute.isSupportTicket() ?
"You opened a request for support."
: "You opened a request for a dispute.\n\n" + disputeInfo),
p2PService.getAddress());
disputeCommunicationMessage.setIsSystemMessage(true);
dispute.addDisputeMessage(disputeCommunicationMessage);
disputes.add(dispute);
disputesObservableList.add(dispute);
p2PService.sendEncryptedMailboxMessage(dispute.getContract().arbitratorNodeAddress,
dispute.getArbitratorPubKeyRing(),
new OpenNewDisputeMessage(dispute, p2PService.getAddress()),
new SendMailboxMessageListener() {
@Override
public void onArrived() {
disputeCommunicationMessage.setArrived(true);
p2PService.sendEncryptedMailboxMessage(dispute.getContract().arbitratorNodeAddress,
dispute.getArbitratorPubKeyRing(),
new OpenNewDisputeMessage(dispute, p2PService.getAddress()),
new SendMailboxMessageListener() {
@Override
public void onArrived() {
disputeCommunicationMessage.setArrived(true);
resultHandler.handleResult();
}
@Override
public void onStoredInMailbox() {
disputeCommunicationMessage.setStoredInMailbox(true);
resultHandler.handleResult();
}
@Override
public void onFault(String errorMessage) {
log.error("sendEncryptedMessage failed");
errorMessageHandler.handleErrorMessage("Sending dispute message failed: " + errorMessage);
}
}
@Override
public void onStoredInMailbox() {
disputeCommunicationMessage.setStoredInMailbox(true);
}
@Override
public void onFault(String errorMessage) {
log.error("sendEncryptedMessage failed");
}
}
);
);
} else {
final String msg = "We got a dispute already open for that trade and trading peer.\n" +
"TradeId = " + dispute.getTradeId();
log.warn(msg);
errorMessageHandler.handleErrorMessage(msg);
}
} else {
log.warn("We got a dispute msg what we have already stored. TradeId = " + dispute.getTradeId());
final String msg = "We got a dispute msg what we have already stored. TradeId = " + dispute.getTradeId();
log.warn(msg);
errorMessageHandler.handleErrorMessage(msg);
}
}
@ -256,43 +281,49 @@ public class DisputeManager {
disputeFromOpener.getArbitratorPubKeyRing(),
disputeFromOpener.isSupportTicket()
);
DisputeCommunicationMessage disputeCommunicationMessage = new DisputeCommunicationMessage(dispute.getTradeId(),
keyRing.getPubKeyRing().hashCode(),
true,
"System message: " + (dispute.isSupportTicket() ?
"Your trading peer has requested support due technical problems. Please wait for further instructions."
: "Your trading peer has requested a dispute.\n\n" + disputeInfo),
p2PService.getAddress());
disputeCommunicationMessage.setIsSystemMessage(true);
dispute.addDisputeMessage(disputeCommunicationMessage);
disputes.add(dispute);
disputesObservableList.add(dispute);
final Optional<Dispute> storedDisputeOptional = findDispute(dispute.getTradeId(), dispute.getTraderId());
if (!storedDisputeOptional.isPresent()) {
DisputeCommunicationMessage disputeCommunicationMessage = new DisputeCommunicationMessage(dispute.getTradeId(),
keyRing.getPubKeyRing().hashCode(),
true,
"System message: " + (dispute.isSupportTicket() ?
"Your trading peer has requested support due technical problems. Please wait for further instructions."
: "Your trading peer has requested a dispute.\n\n" + disputeInfo),
p2PService.getAddress());
disputeCommunicationMessage.setIsSystemMessage(true);
dispute.addDisputeMessage(disputeCommunicationMessage);
disputes.add(dispute);
disputesObservableList.add(dispute);
// we mirrored dispute already!
Contract contract = dispute.getContract();
PubKeyRing peersPubKeyRing = dispute.isDisputeOpenerIsBuyer() ? contract.getBuyerPubKeyRing() : contract.getSellerPubKeyRing();
NodeAddress peerNodeAddress = dispute.isDisputeOpenerIsBuyer() ? contract.getBuyerNodeAddress() : contract.getSellerNodeAddress();
log.trace("sendPeerOpenedDisputeMessage to peerAddress " + peerNodeAddress);
p2PService.sendEncryptedMailboxMessage(peerNodeAddress,
peersPubKeyRing,
new PeerOpenedDisputeMessage(dispute, p2PService.getAddress()),
new SendMailboxMessageListener() {
@Override
public void onArrived() {
disputeCommunicationMessage.setArrived(true);
}
// we mirrored dispute already!
Contract contract = dispute.getContract();
PubKeyRing peersPubKeyRing = dispute.isDisputeOpenerIsBuyer() ? contract.getBuyerPubKeyRing() : contract.getSellerPubKeyRing();
NodeAddress peerNodeAddress = dispute.isDisputeOpenerIsBuyer() ? contract.getBuyerNodeAddress() : contract.getSellerNodeAddress();
log.trace("sendPeerOpenedDisputeMessage to peerAddress " + peerNodeAddress);
p2PService.sendEncryptedMailboxMessage(peerNodeAddress,
peersPubKeyRing,
new PeerOpenedDisputeMessage(dispute, p2PService.getAddress()),
new SendMailboxMessageListener() {
@Override
public void onArrived() {
disputeCommunicationMessage.setArrived(true);
}
@Override
public void onStoredInMailbox() {
disputeCommunicationMessage.setStoredInMailbox(true);
}
@Override
public void onStoredInMailbox() {
disputeCommunicationMessage.setStoredInMailbox(true);
}
@Override
public void onFault(String errorMessage) {
log.error("sendEncryptedMessage failed");
@Override
public void onFault(String errorMessage) {
log.error("sendEncryptedMessage failed");
}
}
}
);
);
} else {
log.warn("We got a dispute already open for that trade and trading peer.\n" +
"TradeId = " + dispute.getTradeId());
}
}
// traders send msg to the arbitrator or arbitrator to 1 trader (trader to trader is not allowed)
@ -423,10 +454,16 @@ public class DisputeManager {
Dispute dispute = openNewDisputeMessage.dispute;
if (isArbitrator(dispute)) {
if (!disputes.contains(dispute)) {
dispute.setStorage(getDisputeStorage());
disputes.add(dispute);
disputesObservableList.add(dispute);
sendPeerOpenedDisputeMessage(dispute);
final Optional<Dispute> storedDisputeOptional = findDispute(dispute.getTradeId(), dispute.getTraderId());
if (!storedDisputeOptional.isPresent()) {
dispute.setStorage(getDisputeStorage());
disputes.add(dispute);
disputesObservableList.add(dispute);
sendPeerOpenedDisputeMessage(dispute);
} else {
log.warn("We got a dispute already open for that trade and trading peer.\n" +
"TradeId = " + dispute.getTradeId());
}
} else {
log.warn("We got a dispute msg what we have already stored. TradeId = " + dispute.getTradeId());
}
@ -439,14 +476,20 @@ public class DisputeManager {
private void onPeerOpenedDisputeMessage(PeerOpenedDisputeMessage peerOpenedDisputeMessage) {
Dispute dispute = peerOpenedDisputeMessage.dispute;
if (!isArbitrator(dispute)) {
Optional<Trade> tradeOptional = tradeManager.getTradeById(dispute.getTradeId());
if (tradeOptional.isPresent())
tradeOptional.get().setDisputeState(Trade.DisputeState.DISPUTE_STARTED_BY_PEER);
if (!disputes.contains(dispute)) {
dispute.setStorage(getDisputeStorage());
disputes.add(dispute);
disputesObservableList.add(dispute);
final Optional<Dispute> storedDisputeOptional = findDispute(dispute.getTradeId(), dispute.getTraderId());
if (!storedDisputeOptional.isPresent()) {
Optional<Trade> tradeOptional = tradeManager.getTradeById(dispute.getTradeId());
if (tradeOptional.isPresent())
tradeOptional.get().setDisputeState(Trade.DisputeState.DISPUTE_STARTED_BY_PEER);
dispute.setStorage(getDisputeStorage());
disputes.add(dispute);
disputesObservableList.add(dispute);
} else {
log.warn("We got a dispute already open for that trade and trading peer.\n" +
"TradeId = " + dispute.getTradeId());
}
} else {
log.warn("We got a dispute msg what we have already stored. TradeId = " + dispute.getTradeId());
}
@ -457,16 +500,26 @@ public class DisputeManager {
// a trader can receive a msg from the arbitrator or the arbitrator form a trader. Trader to trader is not allowed.
private void onDisputeDirectMessage(DisputeCommunicationMessage disputeCommunicationMessage) {
Log.traceCall("disputeDirectMessage " + disputeCommunicationMessage);
Optional<Dispute> disputeOptional = findDispute(disputeCommunicationMessage.getTradeId(), disputeCommunicationMessage.getTraderId());
Log.traceCall("disputeCommunicationMessage " + disputeCommunicationMessage);
final String tradeId = disputeCommunicationMessage.getTradeId();
Optional<Dispute> disputeOptional = findDispute(tradeId, disputeCommunicationMessage.getTraderId());
final String uid = disputeCommunicationMessage.getUID();
if (disputeOptional.isPresent()) {
cleanupRetryMap(uid);
Dispute dispute = disputeOptional.get();
if (!dispute.getDisputeCommunicationMessagesAsObservableList().contains(disputeCommunicationMessage))
dispute.addDisputeMessage(disputeCommunicationMessage);
else
log.warn("We got a dispute mail msg what we have already stored. TradeId = " + disputeCommunicationMessage.getTradeId());
log.warn("We got a disputeCommunicationMessage what we have already stored. TradeId = " + tradeId);
} else {
log.warn("We got a dispute mail msg but we don't have a matching dispute. TradeId = " + disputeCommunicationMessage.getTradeId());
log.debug("We got a disputeCommunicationMessage but we don't have a matching dispute. TradeId = " + tradeId);
if (!delayMsgMap.containsKey(uid)) {
Timer timer = UserThread.runAfter(() -> onDisputeDirectMessage(disputeCommunicationMessage), 1);
delayMsgMap.put(uid, timer);
} else {
log.warn("We got a disputeCommunicationMessage after we already repeated to apply the message after a delay. That should never happen. TradeId = " + tradeId);
}
}
}
@ -474,8 +527,12 @@ public class DisputeManager {
private void onDisputeResultMessage(DisputeResultMessage disputeResultMessage) {
DisputeResult disputeResult = disputeResultMessage.disputeResult;
if (!isArbitrator(disputeResult)) {
Optional<Dispute> disputeOptional = findDispute(disputeResult.tradeId, disputeResult.traderId);
final String tradeId = disputeResult.tradeId;
Optional<Dispute> disputeOptional = findDispute(tradeId, disputeResult.traderId);
final String uid = disputeResultMessage.getUID();
if (disputeOptional.isPresent()) {
cleanupRetryMap(uid);
Dispute dispute = disputeOptional.get();
DisputeCommunicationMessage disputeCommunicationMessage = disputeResult.getDisputeCommunicationMessage();
@ -500,65 +557,93 @@ public class DisputeManager {
|| (!isBuyer && disputeResult.getWinner() == DisputeResult.Winner.SELLER)
|| (isBuyer && disputeResult.getWinner() == DisputeResult.Winner.STALE_MATE)) {
if (dispute.getDepositTxSerialized() != null) {
try {
log.debug("do payout Transaction ");
Transaction signedDisputedPayoutTx = tradeWalletService.traderSignAndFinalizeDisputedPayoutTx(
dispute.getDepositTxSerialized(),
disputeResult.getArbitratorSignature(),
disputeResult.getBuyerPayoutAmount(),
disputeResult.getSellerPayoutAmount(),
disputeResult.getArbitratorPayoutAmount(),
contract.getBuyerPayoutAddressString(),
contract.getSellerPayoutAddressString(),
disputeResult.getArbitratorAddressAsString(),
walletService.getOrCreateAddressEntry(dispute.getTradeId(), AddressEntry.Context.MULTI_SIG),
contract.getBuyerBtcPubKey(),
contract.getSellerBtcPubKey(),
disputeResult.getArbitratorPubKey()
);
Transaction committedDisputedPayoutTx = tradeWalletService.addTransactionToWallet(signedDisputedPayoutTx);
log.debug("broadcast committedDisputedPayoutTx");
tradeWalletService.broadcastTx(committedDisputedPayoutTx, new FutureCallback<Transaction>() {
@Override
public void onSuccess(Transaction transaction) {
log.debug("BroadcastTx succeeded. Transaction:" + transaction);
final Optional<Trade> tradeOptional = tradeManager.getTradeById(tradeId);
Transaction payoutTx = null;
if (tradeOptional.isPresent()) {
payoutTx = tradeOptional.get().getPayoutTx();
} else {
final Optional<Tradable> tradableOptional = closedTradableManager.getTradableById(tradeId);
if (tradableOptional.isPresent() && tradableOptional.get() instanceof Trade) {
payoutTx = ((Trade) tradableOptional.get()).getPayoutTx();
}
}
// after successful publish we send peer the tx
if (payoutTx == null) {
if (dispute.getDepositTxSerialized() != null) {
try {
log.debug("do payout Transaction ");
dispute.setDisputePayoutTxId(transaction.getHashAsString());
sendPeerPublishedPayoutTxMessage(transaction, dispute, contract);
Transaction signedDisputedPayoutTx = tradeWalletService.traderSignAndFinalizeDisputedPayoutTx(
dispute.getDepositTxSerialized(),
disputeResult.getArbitratorSignature(),
disputeResult.getBuyerPayoutAmount(),
disputeResult.getSellerPayoutAmount(),
disputeResult.getArbitratorPayoutAmount(),
contract.getBuyerPayoutAddressString(),
contract.getSellerPayoutAddressString(),
disputeResult.getArbitratorAddressAsString(),
walletService.getOrCreateAddressEntry(dispute.getTradeId(), AddressEntry.Context.MULTI_SIG),
contract.getBuyerBtcPubKey(),
contract.getSellerBtcPubKey(),
disputeResult.getArbitratorPubKey()
);
Transaction committedDisputedPayoutTx = tradeWalletService.addTransactionToWallet(signedDisputedPayoutTx);
log.debug("broadcast committedDisputedPayoutTx");
tradeWalletService.broadcastTx(committedDisputedPayoutTx, new FutureCallback<Transaction>() {
@Override
public void onSuccess(Transaction transaction) {
log.debug("BroadcastTx succeeded. Transaction:" + transaction);
// set state after payout as we call swapTradeEntryToAvailableEntry
if (tradeManager.getTradeById(dispute.getTradeId()).isPresent())
tradeManager.closeDisputedTrade(dispute.getTradeId());
else {
Optional<OpenOffer> openOfferOptional = openOfferManager.getOpenOfferById(dispute.getTradeId());
if (openOfferOptional.isPresent())
openOfferManager.closeOpenOffer(openOfferOptional.get().getOffer());
// after successful publish we send peer the tx
dispute.setDisputePayoutTxId(transaction.getHashAsString());
sendPeerPublishedPayoutTxMessage(transaction, dispute, contract);
// set state after payout as we call swapTradeEntryToAvailableEntry
if (tradeManager.getTradeById(dispute.getTradeId()).isPresent())
tradeManager.closeDisputedTrade(dispute.getTradeId());
else {
Optional<OpenOffer> openOfferOptional = openOfferManager.getOpenOfferById(dispute.getTradeId());
if (openOfferOptional.isPresent())
openOfferManager.closeOpenOffer(openOfferOptional.get().getOffer());
}
}
}
@Override
public void onFailure(@NotNull Throwable t) {
// TODO error handling
log.error(t.getMessage());
}
});
} catch (AddressFormatException | WalletException | TransactionVerificationException e) {
e.printStackTrace();
log.error("Error at traderSignAndFinalizeDisputedPayoutTx " + e.getMessage());
@Override
public void onFailure(@NotNull Throwable t) {
log.error(t.getMessage());
}
});
} catch (AddressFormatException | WalletException | TransactionVerificationException e) {
e.printStackTrace();
log.error("Error at traderSignAndFinalizeDisputedPayoutTx " + e.getMessage());
}
} else {
log.warn("DepositTx is null. TradeId = " + tradeId);
}
} else {
log.warn("DepositTx is null. TradeId = " + disputeResult.tradeId);
log.warn("We got already a payout tx. That might be the case if the other peer did not get the " +
"payout tx and opened a dispute. TradeId = " + tradeId);
dispute.setDisputePayoutTxId(payoutTx.getHashAsString());
sendPeerPublishedPayoutTxMessage(payoutTx, dispute, contract);
}
}
} else {
log.warn("We got a dispute msg what we have already stored. TradeId = " + disputeResult.tradeId);
log.warn("We got a dispute msg what we have already stored. TradeId = " + tradeId);
}
} else {
log.warn("We got a dispute result msg but we don't have a matching dispute. TradeId = " + disputeResult.tradeId);
log.debug("We got a dispute result msg but we don't have a matching dispute. " +
"That might happen when we get the disputeResultMessage before the dispute was created. " +
"We try again after 1 sec. to apply the disputeResultMessage. TradeId = " + tradeId);
if (!delayMsgMap.containsKey(uid)) {
// We delay2 sec. to be sure the comm. msg gets added first
Timer timer = UserThread.runAfter(() -> onDisputeResultMessage(disputeResultMessage), 2);
delayMsgMap.put(uid, timer);
} else {
log.warn("We got a dispute result msg after we already repeated to apply the message after a delay. " +
"That should never happen. TradeId = " + tradeId);
}
}
} else {
log.error("Arbitrator received disputeResultMessage. That must never happen.");
@ -567,9 +652,26 @@ public class DisputeManager {
// losing trader or in case of 50/50 the seller gets the tx sent from the winner or buyer
private void onDisputedPayoutTxMessage(PeerPublishedPayoutTxMessage peerPublishedPayoutTxMessage) {
Transaction transaction = tradeWalletService.addTransactionToWallet(peerPublishedPayoutTxMessage.transaction);
findOwnDispute(peerPublishedPayoutTxMessage.tradeId).ifPresent(dispute -> dispute.setDisputePayoutTxId(transaction.getHashAsString()));
tradeManager.closeDisputedTrade(peerPublishedPayoutTxMessage.tradeId);
final String uid = peerPublishedPayoutTxMessage.getUID();
final String tradeId = peerPublishedPayoutTxMessage.tradeId;
Optional<Dispute> disputeOptional = findOwnDispute(tradeId);
if (disputeOptional.isPresent()) {
cleanupRetryMap(uid);
Transaction transaction = tradeWalletService.addTransactionToWallet(peerPublishedPayoutTxMessage.transaction);
disputeOptional.get().setDisputePayoutTxId(transaction.getHashAsString());
tradeManager.closeDisputedTrade(tradeId);
} else {
log.debug("We got a peerPublishedPayoutTxMessage but we don't have a matching dispute. TradeId = " + tradeId);
if (!delayMsgMap.containsKey(uid)) {
// We delay 3 sec. to be sure the close msg gets added first
Timer timer = UserThread.runAfter(() -> onDisputedPayoutTxMessage(peerPublishedPayoutTxMessage), 3);
delayMsgMap.put(uid, timer);
} else {
log.warn("We got a peerPublishedPayoutTxMessage after we already repeated to apply the message after a delay. " +
"That should never happen. TradeId = " + tradeId);
}
}
}
@ -607,11 +709,19 @@ public class DisputeManager {
}
public Optional<Dispute> findOwnDispute(String tradeId) {
return disputes.stream().filter(e -> e.getTradeId().equals(tradeId)).findAny();
return getDisputeStream(tradeId).findAny();
}
public List<Dispute> findDisputesByTradeId(String tradeId) {
return disputes.stream().filter(e -> e.getTradeId().equals(tradeId)).collect(Collectors.toList());
private Stream<Dispute> getDisputeStream(String tradeId) {
return disputes.stream().filter(e -> e.getTradeId().equals(tradeId));
}
private void cleanupRetryMap(String uid) {
if (delayMsgMap.containsKey(uid)) {
Timer timer = delayMsgMap.remove(uid);
if (timer != null)
timer.stop();
}
}
}

View file

@ -48,6 +48,9 @@ public class BitcoinModule extends AppModule {
File walletDir = new File(env.getRequiredProperty(WalletService.DIR_KEY));
bind(File.class).annotatedWith(named(WalletService.DIR_KEY)).toInstance(walletDir);
bindConstant().annotatedWith(named(BtcOptionKeys.BTC_SEED_NODES)).to(env.getRequiredProperty(BtcOptionKeys.BTC_SEED_NODES));
bindConstant().annotatedWith(named(BtcOptionKeys.USE_TOR_FOR_BTC)).to(env.getRequiredProperty(BtcOptionKeys.USE_TOR_FOR_BTC));
bind(AddressEntryList.class).in(Singleton.class);
bind(TradeWalletService.class).in(Singleton.class);
bind(WalletService.class).in(Singleton.class);

View file

@ -0,0 +1,6 @@
package io.bitsquare.btc;
public class BtcOptionKeys {
public static final String BTC_SEED_NODES = "btcSeedNodes";
public static final String USE_TOR_FOR_BTC = "useTorForBtc";
}

View file

@ -0,0 +1,94 @@
/**
* Copyright (C) 2010-2014 Leon Blakey <lord.quackstar at gmail.com>
*
* This file is part of PircBotX.
*
* PircBotX is free software: you can redistribute it and/or modify it under the
* terms of the GNU General Public License as published by the Free Software
* Foundation, either version 3 of the License, or (at your option) any later
* version.
*
* PircBotX 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with
* PircBotX. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.btc;
import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Proxy;
import java.net.Socket;
import java.net.UnknownHostException;
import javax.net.SocketFactory;
/**
* A basic SocketFactory for creating sockets that connect through the specified
* proxy.
*
* @author Leon Blakey
*/
public class ProxySocketFactory extends SocketFactory {
protected final Proxy proxy;
/**
* Create all sockets with the specified proxy.
*
* @param proxy An existing proxy
*/
public ProxySocketFactory(Proxy proxy) {
this.proxy = proxy;
}
/**
* A convenience constructor for creating a proxy with the specified host
* and port.
*
* @param proxyType The type of proxy were connecting to
* @param hostname The hostname of the proxy server
* @param port The port of the proxy server
*/
public ProxySocketFactory(Proxy.Type proxyType, String hostname, int port) {
this.proxy = new Proxy(proxyType, new InetSocketAddress(hostname, port));
}
@Override
public Socket createSocket() throws IOException {
Socket socket = new Socket(proxy);
return socket;
}
@Override
public Socket createSocket(String string, int i) throws IOException, UnknownHostException {
Socket socket = new Socket(proxy);
socket.connect(new InetSocketAddress(string, i));
return socket;
}
@Override
public Socket createSocket(String string, int i, InetAddress localAddress, int localPort) throws IOException, UnknownHostException {
Socket socket = new Socket(proxy);
socket.bind(new InetSocketAddress(localAddress, localPort));
socket.connect(new InetSocketAddress(string, i));
return socket;
}
@Override
public Socket createSocket(InetAddress ia, int i) throws IOException {
Socket socket = new Socket(proxy);
socket.connect(new InetSocketAddress(ia, i));
return socket;
}
@Override
public Socket createSocket(InetAddress ia, int i, InetAddress localAddress, int localPort) throws IOException {
Socket socket = new Socket(proxy);
socket.bind(new InetSocketAddress(localAddress, localPort));
socket.connect(new InetSocketAddress(ia, i));
return socket;
}
}

View file

@ -0,0 +1,178 @@
/**
* Copyright 2011 Micheal Swiggs
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.bitsquare.btc;
import com.runjva.sourceforge.jsocks.protocol.Socks5Proxy;
import com.runjva.sourceforge.jsocks.protocol.SocksSocket;
import org.bitcoinj.net.*;
import org.bitcoinj.net.discovery.PeerDiscovery;
import org.bitcoinj.net.discovery.PeerDiscoveryException;
import org.bitcoinj.core.NetworkParameters;
import javax.annotation.Nullable;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.UnknownHostException;
import java.util.concurrent.TimeUnit;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* SeedPeersSocks5Dns resolves peers via Proxy (Socks5) remote DNS.
*/
public class SeedPeersSocks5Dns implements PeerDiscovery {
private Socks5Proxy proxy;
private NetworkParameters params;
private InetSocketAddress[] seedAddrs;
private InetSocketAddress[] seedAddrsIP;
private int pnseedIndex;
private InetSocketAddress[] seedAddrsResolved;
private static final Logger log = LoggerFactory.getLogger(SeedPeersSocks5Dns.class);
/**
* Supports finding peers by hostname over a socks5 proxy.
*
* @param Socks5Proxy proxy the socks5 proxy to connect over.
* @param NetworkParameters param to be used for seed and port information.
*/
public SeedPeersSocks5Dns(Socks5Proxy proxy, NetworkParameters params) {
this.proxy = proxy;
this.params = params;
this.seedAddrs = convertAddrsString( params.getDnsSeeds(), params.getPort() );
if( false ) {
// This is an example of how .onion servers could be used. Unfortunately there is presently no way
// to hand the onion address (or a connected socket) back to bitcoinj without it crashing in PeerAddress.
// note: the onion addresses should be added into bitcoinj NetworkParameters classes, eg for mainnet, testnet
// not here!
this.seedAddrs = new InetSocketAddress[] { InetSocketAddress.createUnresolved( "cajrifqkvalh2ooa.onion", 8333 ),
InetSocketAddress.createUnresolved( "bk7yp6epnmcllq72.onion", 8333 )
};
}
seedAddrsResolved = new InetSocketAddress[seedAddrs.length];
for(int idx = seedAddrs.length; idx < seedAddrsResolved.length; idx ++) {
seedAddrsResolved[idx] = seedAddrsIP[idx-seedAddrs.length];
}
}
/**
* Acts as an iterator, returning the address of each node in the list sequentially.
* Once all the list has been iterated, null will be returned for each subsequent query.
*
* @return InetSocketAddress - The address/port of the next node.
* @throws PeerDiscoveryException
*/
@Nullable
public InetSocketAddress getPeer() throws PeerDiscoveryException {
try {
return nextPeer();
} catch (UnknownHostException e) {
throw new PeerDiscoveryException(e);
}
}
/**
* worker for getPeer()
*/
@Nullable
private InetSocketAddress nextPeer() throws UnknownHostException, PeerDiscoveryException {
if (seedAddrs == null || seedAddrs.length == 0) {
throw new PeerDiscoveryException("No IP address seeds configured; unable to find any peers");
}
if (pnseedIndex >= seedAddrsResolved.length) {
return null;
}
if( seedAddrsResolved[pnseedIndex] == null ) {
seedAddrsResolved[pnseedIndex] = lookup( proxy, seedAddrs[pnseedIndex] );
}
log.error("SeedPeersSocks5Dns::nextPeer: " + seedAddrsResolved[pnseedIndex] );
return seedAddrsResolved[pnseedIndex++];
}
/**
* Returns an array containing all the Bitcoin nodes within the list.
*/
@Override
public InetSocketAddress[] getPeers(long timeoutValue, TimeUnit timeoutUnit) throws PeerDiscoveryException {
try {
return allPeers();
} catch (UnknownHostException e) {
throw new PeerDiscoveryException(e);
}
}
/**
* returns all seed peers, performs hostname lookups if necessary.
*/
private InetSocketAddress[] allPeers() throws UnknownHostException {
for (int i = 0; i < seedAddrsResolved.length; ++i) {
if( seedAddrsResolved[i] == null ) {
seedAddrsResolved[i] = lookup( proxy, seedAddrs[i] );
}
}
return seedAddrsResolved;
}
/**
* Resolves a hostname via remote DNS over socks5 proxy.
*/
public static InetSocketAddress lookup( Socks5Proxy proxy, InetSocketAddress addr ) {
if( !addr.isUnresolved() ) {
return addr;
}
try {
SocksSocket proxySocket = new SocksSocket( proxy, addr.getHostString(), addr.getPort() );
InetAddress addrResolved = proxySocket.getInetAddress();
proxySocket.close();
if( addrResolved != null ) {
log.info("Resolved " + addr.getHostString() + " to " + addrResolved.getHostAddress() );
return new InetSocketAddress(addrResolved, addr.getPort() );
}
else {
// note: .onion nodes fall in here when proxy is Tor. But they have no IP address.
// Unfortunately bitcoinj crashes in PeerAddress if it finds an unresolved address.
log.error("Connected to " + addr.getHostString() + ". But did not resolve to address." );
}
} catch (Exception e) {
log.error("Error resolving " + addr.getHostString() + ". Exception:\n" + e.toString() );
}
return null;
}
/**
* Converts an array of hostnames to array of unresolved InetSocketAddress
*/
private InetSocketAddress[] convertAddrsString(String[] addrs, int port) {
InetSocketAddress[] list = new InetSocketAddress[addrs.length];
for( int i = 0; i < addrs.length; i++) {
list[i] = InetSocketAddress.createUnresolved(addrs[i], port);
}
return list;
}
@Override
public void shutdown() {
}
}

View file

@ -519,15 +519,15 @@ public class TradeWalletService {
/**
* Seller signs payout transaction, buyer has not signed yet.
*
* @param depositTx Deposit transaction
* @param buyerPayoutAmount Payout amount for buyer
* @param sellerPayoutAmount Payout amount for seller
* @param depositTx Deposit transaction
* @param buyerPayoutAmount Payout amount for buyer
* @param sellerPayoutAmount Payout amount for seller
* @param buyerPayoutAddressString Address for buyer
* @param sellerPayoutAddressEntry AddressEntry for seller
* @param lockTime Lock time
* @param buyerPubKey The public key of the buyer.
* @param sellerPubKey The public key of the seller.
* @param arbitratorPubKey The public key of the arbitrator.
* @param lockTime Lock time
* @param buyerPubKey The public key of the buyer.
* @param sellerPubKey The public key of the seller.
* @param arbitratorPubKey The public key of the arbitrator.
* @return DER encoded canonical signature
* @throws AddressFormatException
* @throws TransactionVerificationException
@ -567,7 +567,10 @@ public class TradeWalletService {
// MS output from prev. tx is index 0
Sha256Hash sigHash = preparedPayoutTx.hashForSignature(0, redeemScript, Transaction.SigHash.ALL, false);
DeterministicKey keyPair = multiSigAddressEntry.getKeyPair();
checkNotNull(keyPair);
checkNotNull(keyPair, "multiSigAddressEntry.getKeyPair() must not be null");
if (keyPair.isEncrypted())
checkNotNull(aesKey);
ECKey.ECDSASignature sellerSignature = keyPair.sign(sigHash, aesKey).toCanonicalised();
verifyTransaction(preparedPayoutTx);
@ -580,16 +583,16 @@ public class TradeWalletService {
/**
* Buyer creates and signs payout transaction and adds signature of seller to complete the transaction
*
* @param depositTx Deposit transaction
* @param sellerSignature DER encoded canonical signature of seller
* @param buyerPayoutAmount Payout amount for buyer
* @param sellerPayoutAmount Payout amount for seller
* @param buyerPayoutAddressEntry AddressEntry for buyer
* @param sellerAddressString Address for seller
* @param lockTime Lock time
* @param buyerPubKey The public key of the buyer.
* @param sellerPubKey The public key of the seller.
* @param arbitratorPubKey The public key of the arbitrator.
* @param depositTx Deposit transaction
* @param sellerSignature DER encoded canonical signature of seller
* @param buyerPayoutAmount Payout amount for buyer
* @param sellerPayoutAmount Payout amount for seller
* @param buyerPayoutAddressEntry AddressEntry for buyer
* @param sellerAddressString Address for seller
* @param lockTime Lock time
* @param buyerPubKey The public key of the buyer.
* @param sellerPubKey The public key of the seller.
* @param arbitratorPubKey The public key of the arbitrator.
* @return The payout transaction
* @throws AddressFormatException
* @throws TransactionVerificationException
@ -631,8 +634,12 @@ public class TradeWalletService {
Script redeemScript = getMultiSigRedeemScript(buyerPubKey, sellerPubKey, arbitratorPubKey);
// MS output from prev. tx is index 0
Sha256Hash sigHash = payoutTx.hashForSignature(0, redeemScript, Transaction.SigHash.ALL, false);
checkNotNull(multiSigAddressEntry.getKeyPair(), "multiSigAddressEntry.getKeyPair() must not be null");
ECKey.ECDSASignature buyerSignature = multiSigAddressEntry.getKeyPair().sign(sigHash, aesKey).toCanonicalised();
final DeterministicKey keyPair = multiSigAddressEntry.getKeyPair();
checkNotNull(keyPair, "multiSigAddressEntry.getKeyPair() must not be null");
if (keyPair.isEncrypted())
checkNotNull(aesKey);
ECKey.ECDSASignature buyerSignature = keyPair.sign(sigHash, aesKey).toCanonicalised();
TransactionSignature sellerTxSig = new TransactionSignature(ECKey.ECDSASignature.decodeFromDER(sellerSignature), Transaction.SigHash.ALL, false);
TransactionSignature buyerTxSig = new TransactionSignature(buyerSignature, Transaction.SigHash.ALL, false);
@ -643,7 +650,7 @@ public class TradeWalletService {
input.setScriptSig(inputScript);
printTxWithInputs("payoutTx", payoutTx);
verifyTransaction(payoutTx);
checkWalletConsistency();
checkScriptSig(payoutTx, input, 0);
@ -714,10 +721,12 @@ public class TradeWalletService {
// take care of sorting!
Script redeemScript = getMultiSigRedeemScript(buyerPubKey, sellerPubKey, arbitratorPubKey);
Sha256Hash sigHash = preparedPayoutTx.hashForSignature(0, redeemScript, Transaction.SigHash.ALL, false);
if (arbitratorAddressEntry.getKeyPair() == null)
throw new RuntimeException("Unexpected null value: arbitratorAddressEntry.getKeyPair() must not be null");
final DeterministicKey keyPair = arbitratorAddressEntry.getKeyPair();
checkNotNull(keyPair, "arbitratorAddressEntry.getKeyPair() must not be null");
if (keyPair.isEncrypted())
checkNotNull(aesKey);
ECKey.ECDSASignature arbitratorSignature = arbitratorAddressEntry.getKeyPair().sign(sigHash, aesKey).toCanonicalised();
ECKey.ECDSASignature arbitratorSignature = keyPair.sign(sigHash, aesKey).toCanonicalised();
verifyTransaction(preparedPayoutTx);
@ -729,18 +738,18 @@ public class TradeWalletService {
/**
* A trader who got the signed tx from the arbitrator finalizes the payout tx
*
* @param depositTxSerialized Serialized deposit tx
* @param arbitratorSignature DER encoded canonical signature of arbitrator
* @param buyerPayoutAmount Payout amount of the buyer
* @param sellerPayoutAmount Payout amount of the seller
* @param arbitratorPayoutAmount Payout amount for arbitrator
* @param buyerAddressString The address of the buyer.
* @param sellerAddressString The address of the seller.
* @param arbitratorAddressString The address of the arbitrator.
* @param tradersMultiSigAddressEntry The addressEntry of the trader who calls that method
* @param buyerPubKey The public key of the buyer.
* @param sellerPubKey The public key of the seller.
* @param arbitratorPubKey The public key of the arbitrator.
* @param depositTxSerialized Serialized deposit tx
* @param arbitratorSignature DER encoded canonical signature of arbitrator
* @param buyerPayoutAmount Payout amount of the buyer
* @param sellerPayoutAmount Payout amount of the seller
* @param arbitratorPayoutAmount Payout amount for arbitrator
* @param buyerAddressString The address of the buyer.
* @param sellerAddressString The address of the seller.
* @param arbitratorAddressString The address of the arbitrator.
* @param tradersMultiSigAddressEntry The addressEntry of the trader who calls that method
* @param buyerPubKey The public key of the buyer.
* @param sellerPubKey The public key of the seller.
* @param arbitratorPubKey The public key of the arbitrator.
* @return The completed payout tx
* @throws AddressFormatException
* @throws TransactionVerificationException
@ -791,7 +800,9 @@ public class TradeWalletService {
Script redeemScript = getMultiSigRedeemScript(buyerPubKey, sellerPubKey, arbitratorPubKey);
Sha256Hash sigHash = payoutTx.hashForSignature(0, redeemScript, Transaction.SigHash.ALL, false);
DeterministicKey keyPair = tradersMultiSigAddressEntry.getKeyPair();
checkNotNull(keyPair);
checkNotNull(keyPair, "tradersMultiSigAddressEntry.getKeyPair() must not be null");
if (keyPair.isEncrypted())
checkNotNull(aesKey);
ECKey.ECDSASignature tradersSignature = keyPair.sign(sigHash, aesKey).toCanonicalised();
TransactionSignature tradersTxSig = new TransactionSignature(tradersSignature, Transaction.SigHash.ALL, false);
@ -994,6 +1005,8 @@ public class TradeWalletService {
checkNotNull(wallet);
ECKey sigKey = input.getOutpoint().getConnectedKey(wallet);
checkNotNull(sigKey, "signInput: sigKey must not be null. input.getOutpoint()=" + input.getOutpoint().toString());
if (sigKey.isEncrypted())
checkNotNull(aesKey);
Sha256Hash hash = transaction.hashForSignature(inputIndex, scriptPubKey, Transaction.SigHash.ALL, false);
ECKey.ECDSASignature signature = sigKey.sign(hash, aesKey);
TransactionSignature txSig = new TransactionSignature(signature, Transaction.SigHash.ALL, false);

View file

@ -0,0 +1,74 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.btc;
import com.runjva.sourceforge.jsocks.protocol.Socks5Proxy;
import org.bitcoinj.core.NetworkParameters;
import org.bitcoinj.kits.WalletAppKit;
import org.bitcoinj.net.BlockingClientManager;
import org.bitcoinj.core.PeerGroup;
import java.io.File;
import java.net.Proxy;
import java.net.InetSocketAddress;
import java.util.concurrent.TimeoutException;
public class WalletAppKitBitSquare extends WalletAppKit {
private Socks5Proxy socks5Proxy;
/**
* Creates a new WalletAppKit, with a newly created {@link Context}. Files will be stored in the given directory.
*/
public WalletAppKitBitSquare(NetworkParameters params, Socks5Proxy socks5Proxy, File directory, String filePrefix) {
super(params, directory, filePrefix);
this.socks5Proxy = socks5Proxy;
}
public Socks5Proxy getProxy() {
return socks5Proxy;
}
protected PeerGroup createPeerGroup() throws TimeoutException {
// no proxy case.
if(socks5Proxy == null) {
return super.createPeerGroup();
}
// proxy case.
Proxy proxy = new Proxy ( Proxy.Type.SOCKS,
new InetSocketAddress(socks5Proxy.getInetAddress().getHostName(),
socks5Proxy.getPort() ) );
int CONNECT_TIMEOUT_MSEC = 60 * 1000; // same value used in bitcoinj.
ProxySocketFactory proxySocketFactory = new ProxySocketFactory(proxy);
BlockingClientManager mgr = new BlockingClientManager(proxySocketFactory);
PeerGroup peerGroup = new PeerGroup(params, vChain, mgr);
mgr.setConnectTimeoutMillis(CONNECT_TIMEOUT_MSEC);
peerGroup.setConnectTimeoutMillis(CONNECT_TIMEOUT_MSEC);
// This enables remote DNS lookup of peers over socks5 proxy.
// It is slower, but more private.
// This could be turned into a user option.
this.setDiscovery( new SeedPeersSocks5Dns(socks5Proxy, params) );
return peerGroup;
}
}

View file

@ -22,6 +22,7 @@ import com.google.common.collect.ImmutableList;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.Service;
import com.runjva.sourceforge.jsocks.protocol.Socks5Proxy;
import io.bitsquare.btc.listeners.AddressConfidenceListener;
import io.bitsquare.btc.listeners.BalanceListener;
import io.bitsquare.btc.listeners.TxConfidenceListener;
@ -30,11 +31,14 @@ import io.bitsquare.common.UserThread;
import io.bitsquare.common.handlers.ErrorMessageHandler;
import io.bitsquare.common.handlers.ExceptionHandler;
import io.bitsquare.common.handlers.ResultHandler;
import io.bitsquare.p2p.NodeAddress;
import io.bitsquare.storage.FileUtil;
import io.bitsquare.storage.Storage;
import io.bitsquare.user.Preferences;
import javafx.beans.property.*;
import org.bitcoinj.core.*;
import org.bitcoinj.crypto.DeterministicKey;
import org.bitcoinj.crypto.KeyCrypterScrypt;
import org.bitcoinj.kits.WalletAppKit;
import org.bitcoinj.params.MainNetParams;
import org.bitcoinj.params.RegTestParams;
@ -52,6 +56,7 @@ import javax.inject.Named;
import java.io.File;
import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.UnknownHostException;
import java.nio.file.Paths;
import java.util.*;
@ -83,12 +88,13 @@ public class WalletService {
private final RegTestHost regTestHost;
private final TradeWalletService tradeWalletService;
private final AddressEntryList addressEntryList;
private final String seedNodes;
private final NetworkParameters params;
private final File walletDir;
private final UserAgent userAgent;
private final boolean useTor;
private WalletAppKit walletAppKit;
private WalletAppKitBitSquare walletAppKit;
private Wallet wallet;
private final IntegerProperty numPeers = new SimpleIntegerProperty(0);
private final ObjectProperty<List<Peer>> connectedPeers = new SimpleObjectProperty<>();
@ -103,14 +109,30 @@ public class WalletService {
///////////////////////////////////////////////////////////////////////////////////////////
@Inject
public WalletService(RegTestHost regTestHost, TradeWalletService tradeWalletService, AddressEntryList addressEntryList, UserAgent userAgent,
@Named(DIR_KEY) File appDir, Preferences preferences) {
public WalletService(RegTestHost regTestHost,
TradeWalletService tradeWalletService,
AddressEntryList addressEntryList,
UserAgent userAgent,
@Named(DIR_KEY) File appDir,
Preferences preferences,
@Named(BtcOptionKeys.BTC_SEED_NODES) String seedNodes,
@Named(BtcOptionKeys.USE_TOR_FOR_BTC) String useTorFlagFromOptions) {
this.regTestHost = regTestHost;
this.tradeWalletService = tradeWalletService;
this.addressEntryList = addressEntryList;
this.seedNodes = seedNodes;
this.params = preferences.getBitcoinNetwork().getParameters();
this.walletDir = new File(appDir, "bitcoin");
this.userAgent = userAgent;
// We support a checkbox in the settings to set the use tor flag.
// If we get the options set we override that setting.
if (useTorFlagFromOptions != null && !useTorFlagFromOptions.isEmpty()) {
if (useTorFlagFromOptions.equals("false"))
preferences.setUseTorForBitcoinJ(false);
else if (useTorFlagFromOptions.equals("true"))
preferences.setUseTorForBitcoinJ(true);
}
useTor = preferences.getUseTorForBitcoinJ();
storage = new Storage<>(walletDir);
@ -128,7 +150,7 @@ public class WalletService {
// Public Methods
///////////////////////////////////////////////////////////////////////////////////////////
public void initialize(@Nullable DeterministicSeed seed, ResultHandler resultHandler, ExceptionHandler exceptionHandler) {
public void initialize(@Nullable DeterministicSeed seed, Socks5Proxy proxy, ResultHandler resultHandler, ExceptionHandler exceptionHandler) {
// Tell bitcoinj to execute event handlers on the JavaFX UI thread. This keeps things simple and means
// we cannot forget to switch threads when adding event handlers. Unfortunately, the DownloadListener
// we give to the app kit is currently an exception and runs on a library thread. It'll get fixed in
@ -144,7 +166,7 @@ public class WalletService {
backupWallet();
// If seed is non-null it means we are restoring from backup.
walletAppKit = new WalletAppKit(params, walletDir, "Bitsquare") {
walletAppKit = new WalletAppKitBitSquare(params, proxy, walletDir, "Bitsquare") {
@Override
protected void onSetupCompleted() {
// Don't make the user wait for confirmations for now, as the intention is they're sending it
@ -245,11 +267,52 @@ public class WalletService {
// 1333 / (2800 + 1333) = 0.32 -> 32 % probability that a pub key is in our wallet
walletAppKit.setBloomFilterFalsePositiveRate(0.00005);
log.debug( "seedNodes: " + seedNodes.toString() );
// Pass custom seed nodes if set in options
if (seedNodes != null && !seedNodes.isEmpty()) {
// todo: this parsing should be more robust,
// give validation error if needed.
// also: it seems the peer nodes can be overridden in the case
// of regtest mode below. is that wanted?
String[] nodes = seedNodes.split(",");
List<PeerAddress> peerAddressList = new ArrayList<PeerAddress>();
for(String node : nodes) {
String[] parts = node.split(":");
if( parts.length == 2 ) {
// note: this will cause a DNS request if hostname used.
// note: DNS requests are routed over socks5 proxy, if used.
// fixme: .onion hostnames will fail! see comments in SeedPeersSocks5Dns
InetSocketAddress addr;
if( proxy != null ) {
InetSocketAddress unresolved = InetSocketAddress.createUnresolved(parts[0], Integer.parseInt(parts[1]));
// proxy remote DNS request happens here.
addr = SeedPeersSocks5Dns.lookup( proxy, unresolved );
}
else {
// DNS request happens here. if it fails, addr.isUnresolved() == true.
addr = new InetSocketAddress( parts[0], Integer.parseInt(parts[1]) );
}
// note: isUnresolved check should be removed once we fix PeerAddress
if( addr != null && !addr.isUnresolved() ) {
peerAddressList.add( new PeerAddress( addr.getAddress(), addr.getPort() ));
}
}
}
if(peerAddressList.size() > 0) {
PeerAddress peerAddressListFixed[] = new PeerAddress[peerAddressList.size()];
log.debug( "seedNodes parsed: " + peerAddressListFixed.toString() );
walletAppKit.setPeerNodes(peerAddressList.toArray(peerAddressListFixed));
}
}
// TODO Get bitcoinj running over our tor proxy. BlockingClientManager need to be used to use the socket
// from jtorproxy. To get supported it via nio / netty will be harder
if (useTor && params.getId().equals(NetworkParameters.ID_MAINNET))
walletAppKit.useTor();
// We do not call walletAppKit.useTor() anymore because that would turn
// on orchid Tor, which we do not want. Instead, we create a Tor proxy
// later.
// if (useTor && params.getId().equals(NetworkParameters.ID_MAINNET))
// walletAppKit.useTor();
// Now configure and start the appkit. This will take a second or two - we could show a temporary splash screen
// or progress widget to keep the user engaged whilst we initialise, but we don't.
@ -317,7 +380,7 @@ public class WalletService {
Context.propagate(ctx);
walletAppKit.stopAsync();
walletAppKit.awaitTerminated();
initialize(seed, resultHandler, exceptionHandler);
initialize(seed, walletAppKit.getProxy(), resultHandler, exceptionHandler);
} catch (Throwable t) {
t.printStackTrace();
log.error("Executing task failed. " + t.getMessage());
@ -342,6 +405,36 @@ public class WalletService {
this.aesKey = aesKey;
}
public void decryptWallet(@NotNull KeyParameter key) {
wallet.decrypt(key);
addressEntryList.stream().forEach(e -> {
final DeterministicKey keyPair = e.getKeyPair();
if (keyPair != null && keyPair.isEncrypted())
e.setDeterministicKey(keyPair.decrypt(key));
});
setAesKey(null);
addressEntryList.queueUpForSave();
}
public void encryptWallet(KeyCrypterScrypt keyCrypterScrypt, KeyParameter key) {
if (this.aesKey != null) {
log.warn("encryptWallet called but we have a aesKey already set. " +
"We decryptWallet with the old key before we apply the new key.");
decryptWallet(this.aesKey);
}
wallet.encrypt(keyCrypterScrypt, key);
addressEntryList.stream().forEach(e -> {
final DeterministicKey keyPair = e.getKeyPair();
if (keyPair != null && keyPair.isEncrypted())
e.setDeterministicKey(keyPair.encrypt(keyCrypterScrypt, key));
});
setAesKey(key);
addressEntryList.queueUpForSave();
}
///////////////////////////////////////////////////////////////////////////////////////////
// Listener
@ -736,7 +829,7 @@ public class WalletService {
}
return fee;
}
///////////////////////////////////////////////////////////////////////////////////////////
// Withdrawal Fee calculation

View file

@ -19,7 +19,7 @@ package io.bitsquare.filter;
import com.google.inject.Inject;
import com.google.inject.name.Named;
import io.bitsquare.common.OptionKeys;
import io.bitsquare.common.CommonOptionKeys;
import io.bitsquare.common.crypto.KeyRing;
import io.bitsquare.common.util.Tuple3;
import io.bitsquare.common.util.Utilities;
@ -59,7 +59,7 @@ public class FilterManager {
///////////////////////////////////////////////////////////////////////////////////////////
@Inject
public FilterManager(P2PService p2PService, KeyRing keyRing, User user, @Named(OptionKeys.IGNORE_DEV_MSG_KEY) boolean ignoreDevMsg) {
public FilterManager(P2PService p2PService, KeyRing keyRing, User user, @Named(CommonOptionKeys.IGNORE_DEV_MSG_KEY) boolean ignoreDevMsg) {
this.p2PService = p2PService;
this.keyRing = keyRing;
this.user = user;

View file

@ -19,7 +19,7 @@ package io.bitsquare.filter;
import com.google.inject.Singleton;
import io.bitsquare.app.AppModule;
import io.bitsquare.common.OptionKeys;
import io.bitsquare.common.CommonOptionKeys;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.env.Environment;
@ -36,6 +36,6 @@ public class FilterModule extends AppModule {
@Override
protected final void configure() {
bind(FilterManager.class).in(Singleton.class);
bindConstant().annotatedWith(named(OptionKeys.IGNORE_DEV_MSG_KEY)).to(env.getRequiredProperty(OptionKeys.IGNORE_DEV_MSG_KEY));
bindConstant().annotatedWith(named(CommonOptionKeys.IGNORE_DEV_MSG_KEY)).to(env.getRequiredProperty(CommonOptionKeys.IGNORE_DEV_MSG_KEY));
}
}

View file

@ -75,6 +75,7 @@ public class CurrencyUtil {
result.add(new CryptoCurrency("XMR", "Monero"));
result.add(new CryptoCurrency("SC", "Siacoin"));
result.add(new CryptoCurrency("ETH", "Ether"));
result.add(new CryptoCurrency("ETHC", "EtherClassic"));
result.add(new CryptoCurrency("LTC", "Litecoin"));
result.add(new CryptoCurrency("DASH", "Dash"));
result.add(new CryptoCurrency("NMC", "Namecoin"));
@ -94,6 +95,7 @@ public class CurrencyUtil {
result.add(new CryptoCurrency("XCP", "Counterparty"));
result.add(new CryptoCurrency("XRP", "Ripple"));
result.add(new CryptoCurrency("FAIR", "FairCoin"));
result.add(new CryptoCurrency("FLO", "FlorinCoin"));
result.add(new CryptoCurrency("MKR", "Maker", true));
result.add(new CryptoCurrency("DGD", "DigixDAO Tokens", true));
result.add(new CryptoCurrency("DAO", "DAO", true));
@ -128,14 +130,16 @@ public class CurrencyUtil {
result.add(new CryptoCurrency("USDT", "USD Tether"));
result.add(new CryptoCurrency("EURT", "EUR Tether"));
result.add(new CryptoCurrency("JPYT", "JPY Tether"));
result.add(new CryptoCurrency("WDC", "Worldcoin"));
return result;
}
}
public static List<CryptoCurrency> getMainCryptoCurrencies() {
final List<CryptoCurrency> result = new ArrayList<>();
result.add(new CryptoCurrency("XMR", "Monero"));
result.add(new CryptoCurrency("SC", "Siacoin"));
result.add(new CryptoCurrency("ETH", "Ether"));
result.add(new CryptoCurrency("ETHC", "EtherClassic"));
result.add(new CryptoCurrency("LTC", "Litecoin"));
result.add(new CryptoCurrency("DASH", "Dash"));
result.add(new CryptoCurrency("NMC", "Namecoin"));

View file

@ -60,7 +60,7 @@ public final class SpecificBanksAccountContractData extends BankAccountContractD
@Override
public String getPaymentDetailsForTradePopup() {
return getPaymentDetailsForTradePopup() + "\n" +
"Accepted banks: " + Joiner.on(", ").join(acceptedBanks) + "\n";
return super.getPaymentDetailsForTradePopup() + "\n" +
"Accepted banks: " + Joiner.on(", ").join(acceptedBanks);
}
}

View file

@ -57,15 +57,6 @@ public abstract class BuyerTrade extends Trade {
((BuyerProtocol) tradeProtocol).onFiatPaymentStarted(resultHandler, errorMessageHandler);
}
@Override
public void reSendConfirmation() {
if (state == Trade.State.BUYER_SENT_FIAT_PAYMENT_INITIATED_MSG) {
log.info("reSendConfirmation onFiatPaymentStarted");
onFiatPaymentStarted(() -> log.debug("onFiatPaymentStarted succeeded"),
log::warn);
}
}
@Override
public Coin getPayoutAmount() {
checkNotNull(getTradeAmount(), "Invalid state: getTradeAmount() = null");

View file

@ -56,15 +56,6 @@ public abstract class SellerTrade extends Trade {
((SellerProtocol) tradeProtocol).onFiatPaymentReceived(resultHandler, errorMessageHandler);
}
@Override
public void reSendConfirmation() {
if (state == State.SELLER_SENT_FIAT_PAYMENT_RECEIPT_MSG) {
log.info("reSendConfirmation onFiatPaymentReceived");
onFiatPaymentReceived(() -> log.debug("onFiatPaymentReceived succeeded"),
log::warn);
}
}
@Override
public Coin getPayoutAmount() {
return FeePolicy.getSecurityDeposit();

View file

@ -52,6 +52,8 @@ import javax.annotation.Nullable;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.util.Date;
import java.util.HashSet;
import java.util.Set;
/**
* Holds all data which are relevant to the trade, but not those which are only needed in the trade process as shared data between tasks. Those data are
@ -170,6 +172,7 @@ public abstract class Trade implements Tradable, Model {
transient private StringProperty errorMessageProperty;
transient private ObjectProperty<Coin> tradeAmountProperty;
transient private ObjectProperty<Fiat> tradeVolumeProperty;
transient private Set<DecryptedMsgWithPubKey> mailboxMessageSet = new HashSet<>();
///////////////////////////////////////////////////////////////////////////////////////////
@ -210,6 +213,7 @@ public abstract class Trade implements Tradable, Model {
initStateProperties();
initAmountProperty();
errorMessageProperty = new SimpleStringProperty(errorMessage);
mailboxMessageSet = new HashSet<>();
} catch (Throwable t) {
log.warn("Cannot be deserialized." + t.getMessage());
}
@ -242,16 +246,13 @@ public abstract class Trade implements Tradable, Model {
createProtocol();
log.trace("decryptedMsgWithPubKey = " + decryptedMsgWithPubKey);
if (decryptedMsgWithPubKey != null) {
log.trace("init: decryptedMsgWithPubKey = " + decryptedMsgWithPubKey);
if (decryptedMsgWithPubKey != null && !mailboxMessageSet.contains(decryptedMsgWithPubKey)) {
mailboxMessageSet.add(decryptedMsgWithPubKey);
tradeProtocol.applyMailboxMessage(decryptedMsgWithPubKey, this);
}
reSendConfirmation();
}
public abstract void reSendConfirmation();
protected void initStateProperties() {
stateProperty = new SimpleObjectProperty<>(state);
disputeStateProperty = new SimpleObjectProperty<>(disputeState);
@ -292,8 +293,13 @@ public abstract class Trade implements Tradable, Model {
}
public void setMailboxMessage(DecryptedMsgWithPubKey decryptedMsgWithPubKey) {
log.trace("setMailboxMessage " + decryptedMsgWithPubKey);
log.trace("setMailboxMessage decryptedMsgWithPubKey=" + decryptedMsgWithPubKey);
this.decryptedMsgWithPubKey = decryptedMsgWithPubKey;
if (tradeProtocol != null && decryptedMsgWithPubKey != null && !mailboxMessageSet.contains(decryptedMsgWithPubKey)) {
mailboxMessageSet.add(decryptedMsgWithPubKey);
tradeProtocol.applyMailboxMessage(decryptedMsgWithPubKey, this);
}
}
public DecryptedMsgWithPubKey getMailboxMessage() {
@ -313,7 +319,6 @@ public abstract class Trade implements Tradable, Model {
this.state = state;
stateProperty.set(state);
persist();
persist();
}
public void setDisputeState(DisputeState disputeState) {

View file

@ -26,6 +26,7 @@ import io.bitsquare.btc.TradeWalletService;
import io.bitsquare.btc.WalletService;
import io.bitsquare.btc.pricefeed.PriceFeed;
import io.bitsquare.common.crypto.KeyRing;
import io.bitsquare.common.handlers.ErrorMessageHandler;
import io.bitsquare.common.handlers.FaultHandler;
import io.bitsquare.common.handlers.ResultHandler;
import io.bitsquare.crypto.DecryptedMsgWithPubKey;
@ -158,6 +159,7 @@ public class TradeManager {
}
public void onAllServicesInitialized() {
Log.traceCall();
if (p2PService.isBootstrapped())
initPendingTrades();
else
@ -262,8 +264,9 @@ public class TradeManager {
///////////////////////////////////////////////////////////////////////////////////////////
public void checkOfferAvailability(Offer offer,
ResultHandler resultHandler) {
offer.checkOfferAvailability(getOfferAvailabilityModel(offer), resultHandler);
ResultHandler resultHandler,
ErrorMessageHandler errorMessageHandler) {
offer.checkOfferAvailability(getOfferAvailabilityModel(offer), resultHandler, errorMessageHandler);
}
// When closing take offer view, we are not interested in the onCheckOfferAvailability result anymore, so remove from the map
@ -278,13 +281,15 @@ public class TradeManager {
Offer offer,
String paymentAccountId,
boolean useSavingsWallet,
TradeResultHandler tradeResultHandler) {
TradeResultHandler tradeResultHandler,
ErrorMessageHandler errorMessageHandler) {
final OfferAvailabilityModel model = getOfferAvailabilityModel(offer);
offer.checkOfferAvailability(model,
() -> {
if (offer.getState() == Offer.State.AVAILABLE)
createTrade(amount, tradePrice, fundsNeededForTrade, offer, paymentAccountId, useSavingsWallet, model, tradeResultHandler);
});
},
errorMessage -> errorMessageHandler.handleErrorMessage(errorMessage));
}
private void createTrade(Coin amount,

View file

@ -0,0 +1,7 @@
package io.bitsquare.trade.exceptions;
public class MarketPriceNotAvailableException extends Exception {
public MarketPriceNotAvailableException(String message) {
super(message);
}
}

View file

@ -24,12 +24,14 @@ import io.bitsquare.btc.pricefeed.MarketPrice;
import io.bitsquare.btc.pricefeed.PriceFeed;
import io.bitsquare.common.crypto.KeyRing;
import io.bitsquare.common.crypto.PubKeyRing;
import io.bitsquare.common.handlers.ErrorMessageHandler;
import io.bitsquare.common.handlers.ResultHandler;
import io.bitsquare.common.util.JsonExclude;
import io.bitsquare.p2p.NodeAddress;
import io.bitsquare.p2p.storage.payload.RequiresOwnerIsOnlinePayload;
import io.bitsquare.p2p.storage.payload.StoragePayload;
import io.bitsquare.payment.PaymentMethod;
import io.bitsquare.trade.exceptions.MarketPriceNotAvailableException;
import io.bitsquare.trade.exceptions.TradePriceOutOfToleranceException;
import io.bitsquare.trade.protocol.availability.OfferAvailabilityModel;
import io.bitsquare.trade.protocol.availability.OfferAvailabilityProtocol;
@ -272,7 +274,8 @@ public final class Offer implements StoragePayload, RequiresOwnerIsOnlinePayload
// Availability
///////////////////////////////////////////////////////////////////////////////////////////
public void checkOfferAvailability(OfferAvailabilityModel model, ResultHandler resultHandler) {
public void checkOfferAvailability(OfferAvailabilityModel model, ResultHandler resultHandler,
ErrorMessageHandler errorMessageHandler) {
availabilityProtocol = new OfferAvailabilityProtocol(model,
() -> {
cancelAvailabilityRequest();
@ -282,6 +285,7 @@ public final class Offer implements StoragePayload, RequiresOwnerIsOnlinePayload
if (availabilityProtocol != null)
availabilityProtocol.cancel();
log.error(errorMessage);
errorMessageHandler.handleErrorMessage(errorMessage);
});
availabilityProtocol.sendOfferAvailabilityRequest();
}
@ -382,11 +386,14 @@ public final class Offer implements StoragePayload, RequiresOwnerIsOnlinePayload
}
}
public void checkTradePriceTolerance(long takersTradePrice) throws TradePriceOutOfToleranceException, IllegalArgumentException {
public void checkTradePriceTolerance(long takersTradePrice) throws TradePriceOutOfToleranceException, MarketPriceNotAvailableException, IllegalArgumentException {
checkArgument(takersTradePrice > 0, "takersTradePrice must be positive");
Fiat tradePriceAsFiat = Fiat.valueOf(getCurrencyCode(), takersTradePrice);
Fiat offerPriceAsFiat = getPrice();
checkArgument(offerPriceAsFiat != null, "offerPriceAsFiat must not be null");
if (offerPriceAsFiat == null)
throw new MarketPriceNotAvailableException("Market price required for calculating trade price is not available.");
double factor = (double) takersTradePrice / (double) offerPriceAsFiat.value;
// We allow max. 2 % difference between own offer price calculation and takers calculation.
// Market price might be different at offerers and takers side so we need a bit of tolerance.
@ -545,7 +552,8 @@ public final class Offer implements StoragePayload, RequiresOwnerIsOnlinePayload
"\n\tid='" + id + '\'' +
"\n\tdirection=" + direction +
"\n\tcurrencyCode='" + currencyCode + '\'' +
"\n\tdate=" + date +
"\n\tdate=" + new Date(date) +
"\n\tdateAsTime=" + date +
"\n\tfiatPrice=" + fiatPrice +
"\n\tmarketPriceMargin=" + marketPriceMargin +
"\n\tuseMarketBasedPrice=" + useMarketBasedPrice +

View file

@ -100,13 +100,13 @@ public class OfferBookService {
}
}
public void refreshOffer(Offer offer, ResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) {
public void refreshTTL(Offer offer, ResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) {
boolean result = p2PService.refreshTTL(offer, true);
if (result) {
log.trace("Add offer to network was successful. Offer ID = " + offer.getId());
log.trace("Refresh TTL was successful. Offer ID = " + offer.getId());
resultHandler.handleResult();
} else {
errorMessageHandler.handleErrorMessage("Add offer failed");
errorMessageHandler.handleErrorMessage("Refresh TTL failed.");
}
}
@ -137,4 +137,7 @@ public class OfferBookService {
removeOffer(offer, null, null);
}
public boolean isBootstrapped() {
return p2PService.isBootstrapped();
}
}

View file

@ -40,6 +40,7 @@ import io.bitsquare.p2p.peers.PeerManager;
import io.bitsquare.storage.Storage;
import io.bitsquare.trade.TradableList;
import io.bitsquare.trade.closed.ClosedTradableManager;
import io.bitsquare.trade.exceptions.MarketPriceNotAvailableException;
import io.bitsquare.trade.exceptions.TradePriceOutOfToleranceException;
import io.bitsquare.trade.handlers.TransactionResultHandler;
import io.bitsquare.trade.protocol.availability.AvailabilityResult;
@ -118,8 +119,9 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
openOffers.forEach(e -> e.getOffer().setPriceFeed(priceFeed));
// In case the app did get killed the shutDown from the modules is not called, so we use a shutdown hook
Runtime.getRuntime().addShutdownHook(new Thread(OpenOfferManager.this::shutDown,
"OpenOfferManager.ShutDownHook"));
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
UserThread.execute(OpenOfferManager.this::shutDown);
}, "OpenOfferManager.ShutDownHook"));
}
public void onAllServicesInitialized() {
@ -153,22 +155,26 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
log.info("remove all open offers at shutDown");
// we remove own offers from offerbook when we go offline
// Normally we use a delay for broadcasting to the peers, but at shut down we want to get it fast out
closeAllOpenOffers(completeHandler);
}
public void closeAllOpenOffers(@Nullable Runnable completeHandler) {
openOffers.forEach(openOffer -> offerBookService.removeOfferAtShutDown(openOffer.getOffer()));
if (completeHandler != null)
UserThread.runAfter(completeHandler::run, openOffers.size() * 100 + 200, TimeUnit.MILLISECONDS);
final int size = openOffers.size();
if (offerBookService.isBootstrapped()) {
openOffers.forEach(openOffer -> offerBookService.removeOfferAtShutDown(openOffer.getOffer()));
if (completeHandler != null)
UserThread.runAfter(completeHandler::run, size * 200 + 500, TimeUnit.MILLISECONDS);
} else {
if (completeHandler != null)
completeHandler.run();
}
}
public void removeAllOpenOffers(@Nullable Runnable completeHandler) {
final int size = openOffers.size();
List<OpenOffer> openOffersList = new ArrayList<>(openOffers);
openOffersList.forEach(openOffer -> removeOpenOffer(openOffer, () -> {
}, errorMessage -> {
}));
if (completeHandler != null)
UserThread.runAfter(completeHandler::run, openOffers.size() * 100 + 200, TimeUnit.MILLISECONDS);
UserThread.runAfter(completeHandler::run, size * 200 + 500, TimeUnit.MILLISECONDS);
}
///////////////////////////////////////////////////////////////////////////////////////////
@ -357,7 +363,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
AvailabilityResult availabilityResult;
if (openOfferOptional.isPresent() && openOfferOptional.get().getState() == OpenOffer.State.AVAILABLE) {
final Offer offer = openOfferOptional.get().getOffer();
if (!preferences.getIgnoreTradersList().stream().filter(i -> i.equals(offer.getOffererNodeAddress().hostName)).findAny().isPresent()) {
if (!preferences.getIgnoreTradersList().stream().filter(i -> i.equals(offer.getOffererNodeAddress().getHostNameWithoutPostFix())).findAny().isPresent()) {
availabilityResult = AvailabilityResult.AVAILABLE;
List<NodeAddress> acceptedArbitrators = user.getAcceptedArbitratorAddresses();
if (acceptedArbitrators != null && !acceptedArbitrators.isEmpty()) {
@ -371,6 +377,9 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
} catch (TradePriceOutOfToleranceException e) {
log.warn("Trade price check failed because takers price is outside out tolerance.");
availabilityResult = AvailabilityResult.PRICE_OUT_OF_TOLERANCE;
} catch (MarketPriceNotAvailableException e) {
log.warn(e.getMessage());
availabilityResult = AvailabilityResult.MARKET_PRICE_NOT_AVAILABLE;
} catch (Throwable e) {
log.warn("Trade price check failed. " + e.getMessage());
availabilityResult = AvailabilityResult.UNKNOWN_FAILURE;
@ -494,10 +503,10 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
final ArrayList<OpenOffer> openOffersList = new ArrayList<>(openOffers);
for (int i = 0; i < size; i++) {
// we delay to avoid reaching throttle limits
// roughly 1 offer per second
// roughly 2 offer2 per second
final int n = i;
final long minDelay = i * 500 + 1;
final long maxDelay = minDelay * 2 + 500;
final long minDelay = i * 250 + 1;
final long maxDelay = minDelay * 2;
UserThread.runAfterRandomDelay(() -> {
OpenOffer openOffer = openOffersList.get(n);
// we need to check if in the meantime the offer has been removed
@ -516,9 +525,9 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
}
private void refreshOffer(OpenOffer openOffer) {
offerBookService.refreshOffer(openOffer.getOffer(),
offerBookService.refreshTTL(openOffer.getOffer(),
() -> log.debug("Successful refreshed TTL for offer"),
errorMessage -> log.error("Refresh TTL for offer failed. " + errorMessage));
errorMessage -> log.warn(errorMessage));
}
private void restart() {

View file

@ -4,6 +4,7 @@ public enum AvailabilityResult {
AVAILABLE,
OFFER_TAKEN,
PRICE_OUT_OF_TOLERANCE,
MARKET_PRICE_NOT_AVAILABLE,
NO_ARBITRATORS,
USER_IGNORED,
UNKNOWN_FAILURE

View file

@ -44,8 +44,8 @@ public class ProcessOfferAvailabilityResponse extends Task<OfferAvailabilityMode
if (offerAvailabilityResponse.isAvailable || offerAvailabilityResponse.availabilityResult == AvailabilityResult.AVAILABLE) {
model.offer.setState(Offer.State.AVAILABLE);
} else {
log.warn("Offer rejected because of: " + offerAvailabilityResponse.availabilityResult);
model.offer.setState(Offer.State.NOT_AVAILABLE);
failed("Take offer attempt rejected because of: " + offerAvailabilityResponse.availabilityResult);
}
}

View file

@ -111,8 +111,7 @@ public final class Preferences implements Persistable {
private boolean autoSelectArbitrators = true;
private final Map<String, Boolean> dontShowAgainMap;
private boolean tacAccepted;
// Don't remove as we don't want to break old serialized data
private boolean useTorForBitcoinJ = false;
private boolean useTorForBitcoinJ = true;
private boolean showOwnOffersInOfferBook = true;
private Locale preferredLocale;
private TradeCurrency preferredTradeCurrency;
@ -185,8 +184,7 @@ public final class Preferences implements Persistable {
defaultLocale = preferredLocale;
preferredTradeCurrency = persisted.getPreferredTradeCurrency();
defaultTradeCurrency = preferredTradeCurrency;
// useTorForBitcoinJ = persisted.getUseTorForBitcoinJ();
useTorForBitcoinJ = false;
useTorForBitcoinJ = persisted.getUseTorForBitcoinJ();
useStickyMarketPrice = persisted.getUseStickyMarketPrice();
usePercentageBasedPrice = persisted.getUsePercentageBasedPrice();
showOwnOffersInOfferBook = persisted.getShowOwnOffersInOfferBook();
@ -372,10 +370,10 @@ public final class Preferences implements Persistable {
storage.queueUpForSave();
}
/* public void setUseTorForBitcoinJ(boolean useTorForBitcoinJ) {
public void setUseTorForBitcoinJ(boolean useTorForBitcoinJ) {
this.useTorForBitcoinJ = useTorForBitcoinJ;
storage.queueUpForSave();
}*/
}
public void setShowOwnOffersInOfferBook(boolean showOwnOffersInOfferBook) {
this.showOwnOffersInOfferBook = showOwnOffersInOfferBook;

View file

@ -24,7 +24,7 @@ import com.google.inject.Injector;
import io.bitsquare.alert.AlertManager;
import io.bitsquare.arbitration.ArbitratorManager;
import io.bitsquare.btc.WalletService;
import io.bitsquare.common.OptionKeys;
import io.bitsquare.common.CommonOptionKeys;
import io.bitsquare.common.UserThread;
import io.bitsquare.common.handlers.ResultHandler;
import io.bitsquare.common.util.Utilities;
@ -75,6 +75,7 @@ import java.nio.file.Paths;
import java.security.Security;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import static io.bitsquare.app.BitsquareEnvironment.APP_NAME_KEY;
@ -108,7 +109,7 @@ public class BitsquareApp extends Application {
log.info("Log files under: " + logPath);
Version.printVersion();
Utilities.printSysInfo();
Log.setLevel(Level.toLevel(env.getRequiredProperty(OptionKeys.LOG_LEVEL_KEY)));
Log.setLevel(Level.toLevel(env.getRequiredProperty(CommonOptionKeys.LOG_LEVEL_KEY)));
UserThread.setExecutor(Platform::runLater);
UserThread.setTimerClass(UITimer.class);
@ -345,21 +346,25 @@ public class BitsquareApp extends Application {
@Override
public void stop() {
shutDownRequested = true;
gracefulShutDown(() -> {
log.info("App shutdown complete");
System.exit(0);
});
if (!shutDownRequested) {
new Popup().headLine("Shut down in progress")
.backgroundInfo("Shutting down application can take a few seconds.\n" +
"Please don't interrupt that process.")
.hideCloseButton()
.useAnimation(false)
.show();
UserThread.runAfter(() -> {
gracefulShutDown(() -> {
log.info("App shutdown complete");
System.exit(0);
});
}, 200, TimeUnit.MILLISECONDS);
shutDownRequested = true;
}
}
private void gracefulShutDown(ResultHandler resultHandler) {
log.debug("gracefulShutDown");
new Popup().headLine("Shut down in progress")
.backgroundInfo("Shutting down application can take a few seconds.\n" +
"Please don't interrupt that process.")
.hideCloseButton()
.useAnimation(false)
.show();
try {
if (injector != null) {
injector.getInstance(ArbitratorManager.class).shutDown();
@ -374,8 +379,8 @@ public class BitsquareApp extends Application {
injector.getInstance(WalletService.class).shutDown();
});
});
// we wait max 5 sec.
UserThread.runAfter(resultHandler::handleResult, 5);
// we wait max 20 sec.
UserThread.runAfter(resultHandler::handleResult, 20);
} else {
UserThread.runAfter(resultHandler::handleResult, 1);
}

View file

@ -17,23 +17,12 @@
package io.bitsquare.app;
import io.bitsquare.BitsquareException;
import io.bitsquare.btc.BitcoinNetwork;
import io.bitsquare.btc.RegTestHost;
import io.bitsquare.network.OptionKeys;
import io.bitsquare.p2p.P2PService;
import io.bitsquare.util.joptsimple.EnumValueConverter;
import joptsimple.OptionException;
import joptsimple.OptionParser;
import joptsimple.OptionSet;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import static io.bitsquare.app.BitsquareEnvironment.*;
public class BitsquareAppMain extends BitsquareExecutable {
@ -62,7 +51,7 @@ public class BitsquareAppMain extends BitsquareExecutable {
BitsquareEnvironment bitsquareEnvironment = new BitsquareEnvironment(options);
// need to call that before BitsquareAppMain().execute(args)
initAppDir(bitsquareEnvironment.getProperty(BitsquareEnvironment.APP_DATA_DIR_KEY));
BitsquareExecutable.initAppDir(bitsquareEnvironment.getProperty(BitsquareEnvironment.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:
@ -71,61 +60,6 @@ public class BitsquareAppMain extends BitsquareExecutable {
new BitsquareAppMain().execute(args);
}
private static void initAppDir(String appDir) {
Path dir = Paths.get(appDir);
if (Files.exists(dir)) {
if (!Files.isWritable(dir))
throw new BitsquareException("Application data directory '%s' is not writeable", dir);
else
return;
}
try {
Files.createDirectory(dir);
} catch (IOException ex) {
throw new BitsquareException(ex, "Application data directory '%s' could not be created", dir);
}
}
@Override
protected void customizeOptionParsing(OptionParser parser) {
parser.accepts(USER_DATA_DIR_KEY, description("User data directory", DEFAULT_USER_DATA_DIR))
.withRequiredArg();
parser.accepts(APP_NAME_KEY, description("Application name", DEFAULT_APP_NAME))
.withRequiredArg();
parser.accepts(APP_DATA_DIR_KEY, description("Application data directory", DEFAULT_APP_DATA_DIR))
.withRequiredArg();
parser.accepts(io.bitsquare.common.OptionKeys.LOG_LEVEL_KEY, description("Log level [OFF, ALL, ERROR, WARN, INFO, DEBUG, TRACE]", LOG_LEVEL_DEFAULT))
.withRequiredArg();
parser.accepts(OptionKeys.SEED_NODES_KEY, description("Override hard coded seed nodes as comma separated list: E.g. rxdkppp3vicnbgqt.onion:8002, mfla72c4igh5ta2t.onion:8002", ""))
.withRequiredArg();
parser.accepts(io.bitsquare.common.OptionKeys.IGNORE_DEV_MSG_KEY, description("If set to true all signed messages from Bitsquare developers are ignored " +
"(Global alert, Version update alert, Filters for offers, nodes or payment account data)", false))
.withRequiredArg()
.ofType(boolean.class);
// use a fixed port as arbitrator use that for his ID
parser.accepts(OptionKeys.PORT_KEY, description("Port to listen on", 9999))
.withRequiredArg()
.ofType(int.class);
parser.accepts(OptionKeys.USE_LOCALHOST, description("Use localhost network for development", false))
.withRequiredArg()
.ofType(boolean.class);
parser.accepts(OptionKeys.MAX_CONNECTIONS, description("Max. connections a peer will try to keep", P2PService.MAX_CONNECTIONS_DEFAULT))
.withRequiredArg()
.ofType(int.class);
parser.accepts(BitcoinNetwork.KEY, description("Bitcoin network", BitcoinNetwork.DEFAULT))
.withRequiredArg()
.ofType(BitcoinNetwork.class)
.withValuesConvertedBy(new EnumValueConverter(BitcoinNetwork.class));
parser.accepts(RegTestHost.KEY, description("", RegTestHost.DEFAULT))
.withRequiredArg()
.ofType(RegTestHost.class)
.withValuesConvertedBy(new EnumValueConverter(RegTestHost.class));
}
@Override
protected void doExecute(OptionSet options) {
BitsquareApp.setEnvironment(new BitsquareEnvironment(options));

View file

@ -27,7 +27,7 @@ bg color of non edit textFields: fafafa
-bs-content-bg-grey: #f4f4f4;
-bs-very-light-grey: #f8f8f8;
-fx-accent: #0f87c3;
-fx-accent: #0f86c3;
-bs-blue-soft: derive(-fx-accent, 60%);
-bs-blue-transparent: #0f87c344;

View file

@ -18,6 +18,7 @@
package io.bitsquare.gui.main;
import com.google.inject.Inject;
import com.runjva.sourceforge.jsocks.protocol.Socks5Proxy;
import io.bitsquare.alert.Alert;
import io.bitsquare.alert.AlertManager;
import io.bitsquare.alert.PrivateNotification;
@ -55,6 +56,7 @@ import io.bitsquare.gui.main.overlays.windows.WalletPasswordWindow;
import io.bitsquare.gui.util.BSFormatter;
import io.bitsquare.locale.CurrencyUtil;
import io.bitsquare.locale.TradeCurrency;
import io.bitsquare.p2p.NodeAddress;
import io.bitsquare.p2p.P2PService;
import io.bitsquare.p2p.P2PServiceListener;
import io.bitsquare.p2p.network.CloseConnectionReason;
@ -234,8 +236,8 @@ public class MainViewModel implements ViewModel {
showStartupTimeoutPopup();
}, 4, TimeUnit.MINUTES);
walletInitialized = initBitcoinWallet();
p2pNetWorkReady = initP2PNetwork();
walletInitialized = initBitcoinWallet();
// need to store it to not get garbage collected
allServicesDone = EasyBind.combine(walletInitialized, p2pNetWorkReady, (a, b) -> a && b);
@ -348,6 +350,9 @@ public class MainViewModel implements ViewModel {
public void onTorNodeReady() {
bootstrapState.set("Tor node created");
p2PNetworkIconId.set("image-connection-tor");
if( preferences.getUseTorForBitcoinJ() ) {
initWalletService();
}
}
@Override
@ -422,6 +427,18 @@ public class MainViewModel implements ViewModel {
}
private BooleanProperty initBitcoinWallet() {
final BooleanProperty walletInitialized = new SimpleBooleanProperty();
// 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() ) {
initWalletService();
}
return walletInitialized;
}
private void initWalletService() {
ObjectProperty<Throwable> walletServiceException = new SimpleObjectProperty<>();
btcInfoBinding = EasyBind.combine(walletService.downloadPercentageProperty(), walletService.numPeersProperty(), walletServiceException,
(downloadPercentage, numPeers, exception) -> {
@ -461,9 +478,26 @@ public class MainViewModel implements ViewModel {
btcInfoBinding.subscribe((observable, oldValue, newValue) -> {
btcInfo.set(newValue);
});
Socks5Proxy proxy = null;
if( preferences.getUseTorForBitcoinJ() ) {
// Use p2p service
proxy = p2PService.getNetworkNode().getSocksProxy();
}
/**
* Uncomment this to wire up user specified proxy via program args or config file.
* Could be Tor, i2p, ssh, vpn, etc.
if( preferences.getBitcoinProxyHost() != null &&
preferences.getBitcoinProxyPort() != null ) {
proxy = new Socks5Proxy( preferences.getBitcoinProxyHost(),
preferences.getBitcoinProxyPort() );
}
*/
final BooleanProperty walletInitialized = new SimpleBooleanProperty();
walletService.initialize(null,
proxy,
() -> {
numBtcPeers = walletService.numPeersProperty().get();
@ -473,8 +507,8 @@ public class MainViewModel implements ViewModel {
walletPasswordWindow
.onAesKey(aesKey -> {
tradeWalletService.setAesKey(aesKey);
walletService.setAesKey(aesKey);
tradeWalletService.setAesKey(aesKey);
walletInitialized.set(true);
})
.hideCloseButton()
@ -484,7 +518,6 @@ public class MainViewModel implements ViewModel {
}
},
walletServiceException::set);
return walletInitialized;
}
private void onAllServicesInitialized() {
@ -528,6 +561,8 @@ public class MainViewModel implements ViewModel {
privateNotificationManager.privateNotificationProperty().addListener((observable, oldValue, newValue) -> displayPrivateNotification(newValue));
displayAlertIfPresent(alertManager.alertMessageProperty().get());
p2PService.onAllServicesInitialized();
setupBtcNumPeersWatcher();
setupP2PNumPeersWatcher();
updateBalance();

View file

@ -20,6 +20,7 @@ package io.bitsquare.gui.main.account.content.altcoinaccounts;
import io.bitsquare.common.UserThread;
import io.bitsquare.common.util.Tuple2;
import io.bitsquare.common.util.Tuple3;
import io.bitsquare.common.util.Utilities;
import io.bitsquare.gui.common.view.ActivatableViewAndModel;
import io.bitsquare.gui.common.view.FxmlView;
import io.bitsquare.gui.components.TitledGroupBg;
@ -48,6 +49,8 @@ import javafx.scene.layout.GridPane;
import javafx.util.Callback;
import javax.inject.Inject;
import java.util.Calendar;
import java.util.Date;
import java.util.concurrent.TimeUnit;
import static io.bitsquare.gui.util.FormBuilder.*;
@ -164,6 +167,36 @@ public class AltCoinAccountsView extends ActivatableViewAndModel<GridPane, AltCo
"If you are not sure about that process visit the Monero forum (https://forum.getmonero.org) to find more information.")
.closeButtonText("I understand")
.show();
} else if (code.equals("ETHC")) {
//TODO remove after JULY, 21
if (new Date().before(new Date(2016 - 1900, Calendar.JULY, 21))) {
new Popup().information("You cannot use EtherClassic before the hard fork gets activated.\n" +
"It is planned for July, 20 2016, but please check on their project web page for detailed information.\n\n" +
"The EHT/ETHC fork situation carries considerable risks.\n" +
"Be sure you fully understand the situation and check out the information on the \"Ethereum Classic\" and \"Ethereum\" project web pages.")
.closeButtonText("I understand")
.onAction(() -> Utilities.openWebPage("https://ethereumclassic.github.io/"))
.actionButtonText("Open Ethereum Classic web page")
.show();
} else if (new Date().before(new Date(2016 - 1900, Calendar.AUGUST, 30))) {
//TODO remove after AUGUST, 30
new Popup().information("The EHT/ETHC fork situation carries considerable risks.\n" +
"Be sure you fully understand the situation and check out the information on the \"Ethereum Classic\" and \"Ethereum\" project web pages.")
.closeButtonText("I understand")
.onAction(() -> Utilities.openWebPage("https://ethereumclassic.github.io/"))
.actionButtonText("Open Ethereum Classic web page")
.show();
}
} else if (code.equals("ETH")) {
//TODO remove after AUGUST, 30
if (new Date().before(new Date(2016 - 1900, Calendar.AUGUST, 30))) {
new Popup().information("The EHT/ETHC fork situation carries considerable risks.\n" +
"Be sure you fully understand the situation and check out the information on the \"Ethereum Classic\" and \"Ethereum\" project web pages.")
.closeButtonText("I understand")
.onAction(() -> Utilities.openWebPage("https://www.ethereum.org/"))
.actionButtonText("Open Ethereum web page")
.show();
}
}
if (!model.getPaymentAccounts().stream().filter(e -> {

View file

@ -204,8 +204,8 @@ public class ArbitratorSelectionView extends ActivatableViewAndModel<GridPane, A
TableColumn<ArbitratorListItem, String> dateColumn = new TableColumn("Registration date");
dateColumn.setSortable(false);
dateColumn.setCellValueFactory(param -> new ReadOnlyObjectWrapper(param.getValue().getRegistrationDate()));
dateColumn.setMinWidth(130);
dateColumn.setMaxWidth(130);
dateColumn.setMinWidth(140);
dateColumn.setMaxWidth(140);
TableColumn<ArbitratorListItem, String> nameColumn = new TableColumn("Onion address");
nameColumn.setSortable(false);

View file

@ -113,9 +113,8 @@ public class PasswordView extends ActivatableView<GridPane, Void> {
if (wallet.isEncrypted()) {
if (wallet.checkAESKey(aesKey)) {
wallet.decrypt(aesKey);
walletService.decryptWallet(aesKey);
tradeWalletService.setAesKey(null);
walletService.setAesKey(null);
new Popup()
.feedback("Wallet successfully decrypted and password protection removed.")
.show();
@ -130,10 +129,9 @@ public class PasswordView extends ActivatableView<GridPane, Void> {
.show();
}
} else {
wallet.encrypt(keyCrypterScrypt, aesKey);
// we save the key for the trade wallet as we don't require passwords here
walletService.encryptWallet(keyCrypterScrypt, aesKey);
tradeWalletService.setAesKey(aesKey);
walletService.setAesKey(aesKey);
new Popup()
.feedback("Wallet successfully encrypted and password protection enabled.")
.show();

View file

@ -268,7 +268,7 @@ public class SeedWordsView extends ActivatableView<GridPane, Void> {
DeterministicSeed seed = new DeterministicSeed(Splitter.on(" ").splitToList(restoreSeedWordsTextArea.getText()), null, "", date);
walletService.restoreSeedWords(seed,
() -> UserThread.execute(() -> {
log.debug("Wallet restored with seed words");
log.info("Wallet restored with seed words");
new Popup()
.feedback("Wallet restored successfully with the new seed words.\n\n" +

View file

@ -10,13 +10,17 @@ import javax.annotation.Nullable;
public class MarketStatisticItem {
private static final Logger log = LoggerFactory.getLogger(MarketStatisticItem.class);
public final String currencyCode;
public final int numberOfBuyOffers;
public final int numberOfSellOffers;
public final int numberOfOffers;
@Nullable
public final Fiat spread;
public final Coin totalAmount;
public MarketStatisticItem(String currencyCode, int numberOfOffers, @Nullable Fiat spread, Coin totalAmount) {
public MarketStatisticItem(String currencyCode, int numberOfBuyOffers, int numberOfSellOffers, int numberOfOffers, @Nullable Fiat spread, Coin totalAmount) {
this.currencyCode = currencyCode;
this.numberOfBuyOffers = numberOfBuyOffers;
this.numberOfSellOffers = numberOfSellOffers;
this.numberOfOffers = numberOfOffers;
this.spread = spread;
this.totalAmount = totalAmount;

View file

@ -109,7 +109,7 @@ class MarketsStatisticViewModel extends ActivatableViewModel {
spread = bestSellOfferPrice.subtract(bestBuyOfferPrice);
Coin totalAmount = Coin.valueOf(offers.stream().mapToLong(offer -> offer.getAmount().getValue()).sum());
marketStatisticItems.add(new MarketStatisticItem(currencyCode, offers.size(), spread, totalAmount));
marketStatisticItems.add(new MarketStatisticItem(currencyCode, buyOffers.size(), sellOffers.size(), offers.size(), spread, totalAmount));
}
}
}

View file

@ -44,8 +44,7 @@ public class MarketsStatisticsView extends ActivatableViewAndModel<GridPane, Mar
private TableView<MarketStatisticItem> tableView;
private SortedList<MarketStatisticItem> sortedList;
private ListChangeListener<MarketStatisticItem> itemListChangeListener;
private TableColumn<MarketStatisticItem, MarketStatisticItem> totalAmountColumn;
private TableColumn<MarketStatisticItem, MarketStatisticItem> numberOfOffersColumn;
private TableColumn<MarketStatisticItem, MarketStatisticItem> totalAmountColumn, numberOfOffersColumn, numberOfBuyOffersColumn, numberOfSellOffersColumn;
///////////////////////////////////////////////////////////////////////////////////////////
@ -79,6 +78,10 @@ public class MarketsStatisticsView extends ActivatableViewAndModel<GridPane, Mar
tableView.getColumns().add(currencyColumn);
numberOfOffersColumn = getNumberOfOffersColumn();
tableView.getColumns().add(numberOfOffersColumn);
numberOfBuyOffersColumn = getNumberOfBuyOffersColumn();
tableView.getColumns().add(numberOfBuyOffersColumn);
numberOfSellOffersColumn = getNumberOfSellOffersColumn();
tableView.getColumns().add(numberOfSellOffersColumn);
totalAmountColumn = getTotalAmountColumn();
tableView.getColumns().add(totalAmountColumn);
TableColumn<MarketStatisticItem, MarketStatisticItem> spreadColumn = getSpreadColumn();
@ -87,6 +90,8 @@ public class MarketsStatisticsView extends ActivatableViewAndModel<GridPane, Mar
currencyColumn.setComparator((o1, o2) -> CurrencyUtil.getNameByCode(o1.currencyCode).compareTo(CurrencyUtil.getNameByCode(o2.currencyCode)));
numberOfOffersColumn.setComparator((o1, o2) -> Integer.valueOf(o1.numberOfOffers).compareTo(o2.numberOfOffers));
numberOfBuyOffersColumn.setComparator((o1, o2) -> Integer.valueOf(o1.numberOfBuyOffers).compareTo(o2.numberOfBuyOffers));
numberOfSellOffersColumn.setComparator((o1, o2) -> Integer.valueOf(o1.numberOfSellOffers).compareTo(o2.numberOfSellOffers));
totalAmountColumn.setComparator((o1, o2) -> o1.totalAmount.compareTo(o2.totalAmount));
spreadColumn.setComparator((o1, o2) -> o1.spread != null && o2.spread != null ? formatter.formatFiatWithCode(o1.spread).compareTo(formatter.formatFiatWithCode(o2.spread)) : 0);
@ -112,6 +117,8 @@ public class MarketsStatisticsView extends ActivatableViewAndModel<GridPane, Mar
private void updateHeaders() {
numberOfOffersColumn.setText("Total offers (" + sortedList.stream().mapToInt(item -> item.numberOfOffers).sum() + ")");
numberOfBuyOffersColumn.setText("Bid offers (" + sortedList.stream().mapToInt(item -> item.numberOfBuyOffers).sum() + ")");
numberOfSellOffersColumn.setText("Ask offers (" + sortedList.stream().mapToInt(item -> item.numberOfSellOffers).sum() + ")");
totalAmountColumn.setText("Total amount (" + formatter.formatCoinWithCode(Coin.valueOf(sortedList.stream().mapToLong(item -> item.totalAmount.value).sum())) + ")");
}
@ -176,10 +183,66 @@ public class MarketsStatisticsView extends ActivatableViewAndModel<GridPane, Mar
return column;
}
private TableColumn<MarketStatisticItem, MarketStatisticItem> getNumberOfBuyOffersColumn() {
TableColumn<MarketStatisticItem, MarketStatisticItem> column = new TableColumn<MarketStatisticItem, MarketStatisticItem>("Buy offers") {
{
setMinWidth(100);
}
};
column.setCellValueFactory((item) -> new ReadOnlyObjectWrapper<>(item.getValue()));
column.setCellFactory(
new Callback<TableColumn<MarketStatisticItem, MarketStatisticItem>, TableCell<MarketStatisticItem,
MarketStatisticItem>>() {
@Override
public TableCell<MarketStatisticItem, MarketStatisticItem> call(
TableColumn<MarketStatisticItem, MarketStatisticItem> column) {
return new TableCell<MarketStatisticItem, MarketStatisticItem>() {
@Override
public void updateItem(final MarketStatisticItem item, boolean empty) {
super.updateItem(item, empty);
if (item != null && !empty)
setText(String.valueOf(item.numberOfBuyOffers));
else
setText("");
}
};
}
});
return column;
}
private TableColumn<MarketStatisticItem, MarketStatisticItem> getNumberOfSellOffersColumn() {
TableColumn<MarketStatisticItem, MarketStatisticItem> column = new TableColumn<MarketStatisticItem, MarketStatisticItem>("Sell offers") {
{
setMinWidth(100);
}
};
column.setCellValueFactory((item) -> new ReadOnlyObjectWrapper<>(item.getValue()));
column.setCellFactory(
new Callback<TableColumn<MarketStatisticItem, MarketStatisticItem>, TableCell<MarketStatisticItem,
MarketStatisticItem>>() {
@Override
public TableCell<MarketStatisticItem, MarketStatisticItem> call(
TableColumn<MarketStatisticItem, MarketStatisticItem> column) {
return new TableCell<MarketStatisticItem, MarketStatisticItem>() {
@Override
public void updateItem(final MarketStatisticItem item, boolean empty) {
super.updateItem(item, empty);
if (item != null && !empty)
setText(String.valueOf(item.numberOfSellOffers));
else
setText("");
}
};
}
});
return column;
}
private TableColumn<MarketStatisticItem, MarketStatisticItem> getTotalAmountColumn() {
TableColumn<MarketStatisticItem, MarketStatisticItem> column = new TableColumn<MarketStatisticItem, MarketStatisticItem>("Total amount") {
{
setMinWidth(130);
setMinWidth(150);
}
};
column.setCellValueFactory((item) -> new ReadOnlyObjectWrapper<>(item.getValue()));

View file

@ -22,6 +22,7 @@ import io.bitsquare.btc.pricefeed.MarketPrice;
import io.bitsquare.btc.pricefeed.PriceFeed;
import io.bitsquare.common.Timer;
import io.bitsquare.common.UserThread;
import io.bitsquare.common.util.Utilities;
import io.bitsquare.gui.Navigation;
import io.bitsquare.gui.common.model.ActivatableWithDataModel;
import io.bitsquare.gui.common.model.ViewModel;
@ -49,6 +50,8 @@ import org.bitcoinj.core.Coin;
import org.bitcoinj.utils.Fiat;
import javax.inject.Inject;
import java.util.Calendar;
import java.util.Date;
import static com.google.common.math.LongMath.checkedPow;
import static javafx.beans.binding.Bindings.createStringBinding;
@ -127,6 +130,7 @@ class CreateOfferViewModel extends ActivatableWithDataModel<CreateOfferDataModel
private PriceFeed.Type priceFeedType;
private boolean inputIsMarketBasedPrice;
private ChangeListener<Boolean> useMarketBasedPriceListener;
private ChangeListener<String> currencyCodeListener;
///////////////////////////////////////////////////////////////////////////////////////////
@ -186,6 +190,9 @@ class CreateOfferViewModel extends ActivatableWithDataModel<CreateOfferDataModel
directionLabel = BSResources.get("shared.sellBitcoin");
amountDescription = BSResources.get("createOffer.amountPriceBox.amountDescription", BSResources.get("shared.sell"));
}
//TODO remove after AUGUST, 30
applyCurrencyCode(dataModel.getTradeCurrency().getCode());
}
@Override
@ -336,6 +343,34 @@ class CreateOfferViewModel extends ActivatableWithDataModel<CreateOfferDataModel
/* feeFromFundingTxListener = (ov, oldValue, newValue) -> {
updateButtonDisableState();
};*/
currencyCodeListener = (observable, oldValue, newValue) -> applyCurrencyCode(newValue);
}
//TODO remove after AUGUST, 30
private void applyCurrencyCode(String newValue) {
String key = "ETH-ETHC-Warning";
if (preferences.showAgain(key) && new Date().before(new Date(2016 - 1900, Calendar.AUGUST, 30))) {
if (newValue.equals("ETH")) {
new Popup().information("The EHT/ETHC fork situation carries considerable risks.\n" +
"Be sure you fully understand the situation and check out the information on the \"Ethereum Classic\" and \"Ethereum\" project web pages.")
.closeButtonText("I understand")
.onAction(() -> Utilities.openWebPage("https://www.ethereum.org/"))
.actionButtonText("Open Ethereum web page")
.dontShowAgainId(key, preferences)
.show();
} else if (newValue.equals("ETHC")) {
new Popup().information("The EHT/ETHC fork situation carries considerable risks.\n" +
"Be sure you fully understand the situation and check out the information on the \"Ethereum Classic\" and \"Ethereum\" project web pages.\n\n" +
"Please note, that the price is denominated as ETHC/BTC not BTC/ETHC!")
.closeButtonText("I understand")
.onAction(() -> Utilities.openWebPage("https://ethereumclassic.github.io/"))
.actionButtonText("Open Ethereum Classic web page")
.dontShowAgainId(key, preferences)
.show();
}
}
}
private void addListeners() {
@ -356,6 +391,9 @@ class CreateOfferViewModel extends ActivatableWithDataModel<CreateOfferDataModel
// dataModel.feeFromFundingTxProperty.addListener(feeFromFundingTxListener);
dataModel.isWalletFunded.addListener(isWalletFundedListener);
//TODO remove after AUGUST, 30
dataModel.tradeCurrencyCode.addListener(currencyCodeListener);
}
private void removeListeners() {
@ -377,6 +415,9 @@ class CreateOfferViewModel extends ActivatableWithDataModel<CreateOfferDataModel
if (offer != null && errorMessageListener != null)
offer.errorMessageProperty().removeListener(errorMessageListener);
//TODO remove after AUGUST, 30
dataModel.tradeCurrencyCode.removeListener(currencyCodeListener);
}

View file

@ -460,7 +460,7 @@ class OfferBookViewModel extends ActivatableViewModel {
}
boolean isIgnored(Offer offer) {
return preferences.getIgnoreTradersList().stream().filter(i -> i.equals(offer.getOffererNodeAddress().hostName)).findAny().isPresent();
return preferences.getIgnoreTradersList().stream().filter(i -> i.equals(offer.getOffererNodeAddress().getHostNameWithoutPostFix())).findAny().isPresent();
}
boolean isOfferBanned(Offer offer) {
@ -474,7 +474,7 @@ class OfferBookViewModel extends ActivatableViewModel {
boolean isNodeBanned(Offer offer) {
return filterManager.getFilter() != null &&
filterManager.getFilter().bannedNodeAddress.stream()
.filter(e -> e.equals(offer.getOffererNodeAddress().hostName))
.filter(e -> e.equals(offer.getOffererNodeAddress().getHostNameWithoutPostFix()))
.findAny()
.isPresent();
}

View file

@ -29,6 +29,7 @@ import io.bitsquare.btc.listeners.BalanceListener;
import io.bitsquare.btc.pricefeed.PriceFeed;
import io.bitsquare.gui.common.model.ActivatableDataModel;
import io.bitsquare.gui.main.overlays.notifications.Notification;
import io.bitsquare.gui.main.overlays.popups.Popup;
import io.bitsquare.gui.util.BSFormatter;
import io.bitsquare.locale.CurrencyUtil;
import io.bitsquare.locale.TradeCurrency;
@ -140,8 +141,10 @@ class TakeOfferDataModel extends ActivatableDataModel {
if (!preferences.getUseStickyMarketPrice() && isTabSelected)
priceFeed.setCurrencyCode(offer.getCurrencyCode());
tradeManager.checkOfferAvailability(offer, () -> {
});
tradeManager.checkOfferAvailability(offer,
() -> {
},
errorMessage -> new Popup().warning(errorMessage).show());
}
@Override
@ -234,7 +237,11 @@ class TakeOfferDataModel extends ActivatableDataModel {
offer,
paymentAccount.getId(),
useSavingsWallet,
tradeResultHandler
tradeResultHandler,
errorMessage -> {
log.warn(errorMessage);
new Popup<>().warning(errorMessage).show();
}
);
}

View file

@ -19,6 +19,7 @@ package io.bitsquare.gui.main.offer.takeoffer;
import io.bitsquare.arbitration.Arbitrator;
import io.bitsquare.btc.pricefeed.PriceFeed;
import io.bitsquare.common.util.Utilities;
import io.bitsquare.gui.Navigation;
import io.bitsquare.gui.common.model.ActivatableWithDataModel;
import io.bitsquare.gui.common.model.ViewModel;
@ -44,6 +45,8 @@ import javafx.collections.ObservableList;
import org.bitcoinj.core.Coin;
import javax.inject.Inject;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import static com.google.common.base.Preconditions.checkNotNull;
@ -136,6 +139,29 @@ class TakeOfferViewModel extends ActivatableWithDataModel<TakeOfferDataModel> im
updateButtonDisableState();
updateSpinnerInfo();
//TODO remove after AUGUST, 30
String key = "ETH-ETHC-Warning";
if (dataModel.getPreferences().showAgain(key) && new Date().before(new Date(2016 - 1900, Calendar.AUGUST, 30))) {
if (dataModel.getCurrencyCode().equals("ETH")) {
new Popup().information("The EHT/ETHC fork situation carries considerable risks.\n" +
"Be sure you fully understand the situation and check out the information on the \"Ethereum Classic\" and \"Ethereum\" project web pages.")
.closeButtonText("I understand")
.onAction(() -> Utilities.openWebPage("https://www.ethereum.org/"))
.actionButtonText("Open Ethereum web page")
.dontShowAgainId(key, dataModel.getPreferences())
.show();
} else if (dataModel.getCurrencyCode().equals("ETHC")) {
new Popup().information("The EHT/ETHC fork situation carries considerable risks.\n" +
"Be sure you fully understand the situation and check out the information on the \"Ethereum Classic\" and \"Ethereum\" project web pages.\n\n" +
"Please note, that the price is denominated as ETHC/BTC not BTC/ETHC!")
.closeButtonText("I understand")
.onAction(() -> Utilities.openWebPage("https://ethereumclassic.github.io/"))
.actionButtonText("Open Ethereum Classic web page")
.dontShowAgainId(key, dataModel.getPreferences())
.show();
}
}
}
@Override

View file

@ -19,6 +19,7 @@ package io.bitsquare.gui.main.portfolio.pendingtrades;
import com.google.inject.Inject;
import io.bitsquare.app.Log;
import io.bitsquare.arbitration.Arbitrator;
import io.bitsquare.arbitration.Dispute;
import io.bitsquare.arbitration.DisputeManager;
import io.bitsquare.btc.FeePolicy;
@ -33,8 +34,10 @@ import io.bitsquare.gui.common.model.ActivatableDataModel;
import io.bitsquare.gui.main.MainView;
import io.bitsquare.gui.main.disputes.DisputesView;
import io.bitsquare.gui.main.overlays.notifications.NotificationCenter;
import io.bitsquare.gui.main.overlays.popups.Popup;
import io.bitsquare.gui.main.overlays.windows.SelectDepositTxWindow;
import io.bitsquare.gui.main.overlays.windows.WalletPasswordWindow;
import io.bitsquare.p2p.P2PService;
import io.bitsquare.payment.PaymentAccountContractData;
import io.bitsquare.trade.BuyerTrade;
import io.bitsquare.trade.SellerTrade;
@ -70,6 +73,7 @@ public class PendingTradesDataModel extends ActivatableDataModel {
private final User user;
private final KeyRing keyRing;
public final DisputeManager disputeManager;
private P2PService p2PService;
public final Navigation navigation;
public final WalletPasswordWindow walletPasswordWindow;
private final NotificationCenter notificationCenter;
@ -90,7 +94,7 @@ public class PendingTradesDataModel extends ActivatableDataModel {
@Inject
public PendingTradesDataModel(TradeManager tradeManager, WalletService walletService, TradeWalletService tradeWalletService,
User user, KeyRing keyRing, DisputeManager disputeManager, Preferences preferences,
User user, KeyRing keyRing, DisputeManager disputeManager, Preferences preferences, P2PService p2PService,
Navigation navigation, WalletPasswordWindow walletPasswordWindow, NotificationCenter notificationCenter) {
this.tradeManager = tradeManager;
this.walletService = walletService;
@ -99,6 +103,7 @@ public class PendingTradesDataModel extends ActivatableDataModel {
this.keyRing = keyRing;
this.disputeManager = disputeManager;
this.preferences = preferences;
this.p2PService = p2PService;
this.navigation = navigation;
this.walletPasswordWindow = walletPasswordWindow;
this.notificationCenter = notificationCenter;
@ -352,6 +357,8 @@ public class PendingTradesDataModel extends ActivatableDataModel {
log.debug("payoutTx is null at doOpenDispute");
}
final Arbitrator acceptedArbitratorByAddress = user.getAcceptedArbitratorByAddress(trade.getArbitratorNodeAddress());
checkNotNull(acceptedArbitratorByAddress);
Dispute dispute = new Dispute(disputeManager.getDisputeStorage(),
trade.getId(),
keyRing.getPubKeyRing().hashCode(), // traderId
@ -368,13 +375,19 @@ public class PendingTradesDataModel extends ActivatableDataModel {
trade.getContractAsJson(),
trade.getOffererContractSignature(),
trade.getTakerContractSignature(),
user.getAcceptedArbitratorByAddress(trade.getArbitratorNodeAddress()).getPubKeyRing(),
acceptedArbitratorByAddress.getPubKeyRing(),
isSupportTicket
);
trade.setDisputeState(Trade.DisputeState.DISPUTE_REQUESTED);
disputeManager.sendOpenNewDisputeMessage(dispute);
navigation.navigateTo(MainView.class, DisputesView.class);
if (p2PService.isBootstrapped()) {
disputeManager.sendOpenNewDisputeMessage(dispute,
() -> navigation.navigateTo(MainView.class, DisputesView.class),
errorMessage -> new Popup().warning(errorMessage).show());
} else {
new Popup().information("You need to wait until you are fully connected to the network.\n" +
"That might take up to about 2 minutes at startup.").show();
}
} else {
log.warn("trade is null at doOpenDispute");
}

View file

@ -289,8 +289,8 @@ public abstract class TradeStepView extends AnchorPane {
}
new Popup().warning(trade.errorMessageProperty().getValue()
+ "\n\nPlease report the problem to your arbitrator.\n\n" +
"He will forward teh information to the developers to investigate the problem.\n" +
"After the problem has be analyzed you will get back all the funds if they are locked.\n" +
"He will forward the information to the developers to investigate the problem.\n" +
"After the problem has be analyzed you will get back all the funds if funds was locked.\n" +
"There will be no arbitration fee charged in case of a software bug.")
.show();

View file

@ -96,9 +96,8 @@ public class BuyerStep2View extends TradeStepView {
.attention(message)
.show();
}
} else if (state == Trade.State.BUYER_CONFIRMED_FIAT_PAYMENT_INITIATED) {
} else if (state == Trade.State.BUYER_CONFIRMED_FIAT_PAYMENT_INITIATED && confirmButton.isDisabled()) {
showStatusInfo();
statusLabel.setText("Sending confirmation...");
} else if (state == Trade.State.BUYER_SENT_FIAT_PAYMENT_INITIATED_MSG) {
hideStatusInfo();
}
@ -245,6 +244,7 @@ public class BuyerStep2View extends TradeStepView {
private void confirmPaymentStarted() {
confirmButton.setDisable(true);
showStatusInfo();
model.dataModel.onPaymentStarted(() -> {
// In case the first send failed we got the support button displayed.
// If it succeeds at a second try we remove the support button again.
@ -261,6 +261,7 @@ public class BuyerStep2View extends TradeStepView {
private void showStatusInfo() {
busyAnimation.play();
statusLabel.setText("Sending confirmation...");
}
private void hideStatusInfo() {

View file

@ -102,9 +102,8 @@ public class SellerStep3View extends TradeStepView {
.show();
}
} else if (state == Trade.State.SELLER_CONFIRMED_FIAT_PAYMENT_RECEIPT) {
} else if (state == Trade.State.SELLER_CONFIRMED_FIAT_PAYMENT_RECEIPT && confirmButton.isDisabled()) {
showStatusInfo();
statusLabel.setText("Sending confirmation...");
} else if (state == Trade.State.SELLER_SENT_FIAT_PAYMENT_RECEIPT_MSG) {
hideStatusInfo();
}
@ -249,19 +248,21 @@ public class SellerStep3View extends TradeStepView {
Preferences preferences = model.dataModel.preferences;
String key = "confirmPaymentReceived";
if (!DevFlags.DEV_MODE && preferences.showAgain(key)) {
PaymentAccountContractData paymentAccountContractData = model.dataModel.getSellersPaymentAccountContractData();
String message = "Have you received the " + CurrencyUtil.getNameByCode(model.dataModel.getCurrencyCode()) +
" payment from your trading partner?\n\n" +
"The trade ID (\"reason for payment\" text) of the transaction is: \"" + trade.getShortId() + "\"\n\n";
" payment from your trading partner?\n\n";
if (!(paymentAccountContractData instanceof CryptoCurrencyAccountContractData)) {
message += "The trade ID (\"reason for payment\" text) of the transaction is: \"" + trade.getShortId() + "\"\n\n";
Optional<String> optionalHolderName = getOptionalHolderName();
if (optionalHolderName.isPresent()) {
message = message +
"Please also verify that the senders name in your bank statement matches that one from the trade contract:\n" +
"Senders name: " + optionalHolderName.get() + "\n\n" +
"If the name is not the same as the one displayed here, please don't confirm but open a " +
"dispute by entering \"cmd + o\" or \"ctrl + o\".\n\n";
Optional<String> optionalHolderName = getOptionalHolderName();
if (optionalHolderName.isPresent()) {
message += "Please also verify that the senders name in your bank statement matches that one from the trade contract:\n" +
"Senders name: " + optionalHolderName.get() + "\n\n" +
"If the name is not the same as the one displayed here, please don't confirm but open a " +
"dispute by entering \"cmd + o\" or \"ctrl + o\".\n\n";
}
}
message = message + "Please note, that as soon you have confirmed the receipt, the locked trade amount will be released " +
message += "Please note, that as soon you have confirmed the receipt, the locked trade amount will be released " +
"to the bitcoin buyer and the security deposit will be refunded.";
new Popup()
.headLine("Confirm that you have received the payment")
@ -282,6 +283,7 @@ public class SellerStep3View extends TradeStepView {
private void confirmPaymentReceived() {
confirmButton.setDisable(true);
showStatusInfo();
model.dataModel.onFiatPaymentReceived(() -> {
// In case the first send failed we got the support button displayed.
@ -299,6 +301,7 @@ public class SellerStep3View extends TradeStepView {
private void showStatusInfo() {
busyAnimation.play();
statusLabel.setText("Sending confirmation...");
}
private void hideStatusInfo() {

View file

@ -34,14 +34,14 @@
<TitledGroupBg text="Bitcoin network" GridPane.rowSpan="2"/>
<!-- <Label text="Use tor:" GridPane.rowIndex="1"/>
<CheckBox fx:id="useTorCheckBox" GridPane.rowIndex="1" GridPane.columnIndex="1"/>
-->
<Label fx:id="bitcoinPeersLabel" text="Connected peers:" GridPane.rowIndex="0"/>
<TextArea fx:id="bitcoinPeersTextArea" GridPane.rowIndex="0" GridPane.columnIndex="1" GridPane.hgrow="ALWAYS"
<Label text="Use tor:" GridPane.rowIndex="0"/>
<CheckBox fx:id="useTorCheckBox" GridPane.rowIndex="0" GridPane.columnIndex="1"/>
<Label fx:id="bitcoinPeersLabel" text="Connected peers:" GridPane.rowIndex="1"/>
<TextArea fx:id="bitcoinPeersTextArea" GridPane.rowIndex="1" GridPane.columnIndex="1" GridPane.hgrow="ALWAYS"
GridPane.vgrow="SOMETIMES" editable="false" focusTraversable="false"/>
<TitledGroupBg text="P2P network" GridPane.rowIndex="1" GridPane.rowSpan="5">
<TitledGroupBg text="P2P network" GridPane.rowIndex="2" GridPane.rowSpan="5">
<padding>
<Insets top="50.0"/>
</padding>
@ -50,20 +50,20 @@
</GridPane.margin>
</TitledGroupBg>
<Label text="My onion address:" GridPane.rowIndex="1">
<Label text="My onion address:" GridPane.rowIndex="2">
<GridPane.margin>
<Insets top="50.0"/>
</GridPane.margin>
</Label>
<TextField fx:id="onionAddress" GridPane.rowIndex="1" GridPane.columnIndex="1"
<TextField fx:id="onionAddress" GridPane.rowIndex="2" GridPane.columnIndex="1"
editable="false" focusTraversable="false">
<GridPane.margin>
<Insets top="50.0"/>
</GridPane.margin>
</TextField>
<Label fx:id="p2PPeersLabel" text="Connected peers:" GridPane.rowIndex="2"/>
<TableView fx:id="tableView" GridPane.rowIndex="2" GridPane.columnIndex="1" GridPane.hgrow="ALWAYS"
<Label fx:id="p2PPeersLabel" text="Connected peers:" GridPane.rowIndex="3"/>
<TableView fx:id="tableView" GridPane.rowIndex="3" GridPane.columnIndex="1" GridPane.hgrow="ALWAYS"
GridPane.vgrow="ALWAYS">
<columns>
<TableColumn text="Onion address" fx:id="onionAddressColumn" minWidth="220">
@ -109,14 +109,14 @@
</columns>
</TableView>
<Label text="Total traffic:" GridPane.rowIndex="3"/>
<TextField fx:id="totalTraffic" GridPane.rowIndex="3" GridPane.columnIndex="1" editable="false"
<Label text="Total traffic:" GridPane.rowIndex="4"/>
<TextField fx:id="totalTraffic" GridPane.rowIndex="4" GridPane.columnIndex="1" editable="false"
focusTraversable="false"/>
<Label text="Use Tor bridges:" GridPane.rowIndex="4"/>
<CheckBox fx:id="useBridgesCheckBox" GridPane.rowIndex="4" GridPane.columnIndex="1"/>
<Label fx:id="bridgesLabel" text="Tor bridges:" GridPane.rowIndex="5" visible="false" managed="false"/>
<TextArea fx:id="bridgesTextArea" GridPane.rowIndex="5" GridPane.columnIndex="1" GridPane.hgrow="ALWAYS"
<Label text="Use Tor bridges:" GridPane.rowIndex="5"/>
<CheckBox fx:id="useBridgesCheckBox" GridPane.rowIndex="5" GridPane.columnIndex="1"/>
<Label fx:id="bridgesLabel" text="Tor bridges:" GridPane.rowIndex="6" visible="false" managed="false"/>
<TextArea fx:id="bridgesTextArea" GridPane.rowIndex="6" GridPane.columnIndex="1" GridPane.hgrow="ALWAYS"
minHeight="60"
GridPane.vgrow="SOMETIMES" editable="true" focusTraversable="true" visible="false" managed="false"/>

View file

@ -17,11 +17,14 @@
package io.bitsquare.gui.main.settings.network;
import io.bitsquare.app.BitsquareApp;
import io.bitsquare.btc.WalletService;
import io.bitsquare.common.Clock;
import io.bitsquare.common.UserThread;
import io.bitsquare.gui.common.model.Activatable;
import io.bitsquare.gui.common.view.ActivatableViewAndModel;
import io.bitsquare.gui.common.view.FxmlView;
import io.bitsquare.gui.main.overlays.popups.Popup;
import io.bitsquare.gui.util.BSFormatter;
import io.bitsquare.p2p.P2PService;
import io.bitsquare.p2p.network.Statistic;
@ -41,6 +44,7 @@ import org.fxmisc.easybind.Subscription;
import javax.inject.Inject;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
@FxmlView
@ -61,8 +65,8 @@ public class NetworkSettingsView extends ActivatableViewAndModel<GridPane, Activ
TextArea bitcoinPeersTextArea, bridgesTextArea;
@FXML
Label bitcoinPeersLabel, p2PPeersLabel, bridgesLabel;
/* @FXML
CheckBox useTorCheckBox;*/
@FXML
CheckBox useTorCheckBox;
@FXML
TableView<P2pNetworkListItem> tableView;
@FXML
@ -124,7 +128,7 @@ public class NetworkSettingsView extends ActivatableViewAndModel<GridPane, Activ
@Override
public void activate() {
/* useTorCheckBox.setSelected(preferences.getUseTorForBitcoinJ());
useTorCheckBox.setSelected(preferences.getUseTorForBitcoinJ());
useTorCheckBox.setOnAction(event -> {
boolean selected = useTorCheckBox.isSelected();
if (selected != preferences.getUseTorForBitcoinJ()) {
@ -139,7 +143,7 @@ public class NetworkSettingsView extends ActivatableViewAndModel<GridPane, Activ
.onClose(() -> useTorCheckBox.setSelected(!selected))
.show();
}
});*/
});
bitcoinPeersSubscription = EasyBind.subscribe(walletService.connectedPeersProperty(), connectedPeers -> updateBitcoinPeersTextArea());
nodeAddressSubscription = EasyBind.subscribe(p2PService.getNetworkNode().nodeAddressProperty(),
@ -166,7 +170,7 @@ public class NetworkSettingsView extends ActivatableViewAndModel<GridPane, Activ
@Override
public void deactivate() {
//useTorCheckBox.setOnAction(null);
useTorCheckBox.setOnAction(null);
if (nodeAddressSubscription != null)
nodeAddressSubscription.unsubscribe();

View file

@ -443,7 +443,11 @@ public class BSFormatter {
duration = StringUtils.replaceOnce(duration, " 1 minutes", " 1 minute");
duration = StringUtils.replaceOnce(duration, " 1 hours", " 1 hour");
duration = StringUtils.replaceOnce(duration, " 1 days", " 1 day");
if (duration.equals(" ,"))
if (duration.startsWith(", "))
duration = duration.replace(" ,", "");
else if (duration.startsWith(", "))
duration = duration.replace(", ", "");
if (duration.equals(""))
duration = "Trade period is over";
return duration.trim();
}

View file

@ -14,9 +14,22 @@
<logger name="io.bitsquare.storage.FileManager" level="WARN"/>
<logger name="io.bitsquare.locale.BSResources" level="ERROR"/>
<logger name="io.bitsquare.p2p" level="WARN"/>
<!--<logger name="io.bitsquare.p2p" level="WARN"/>-->
<logger name="io.bitsquare.btc.pricefeed" level="WARN"/>
<logger name="io.bitsquare.storage.Storage" level="WARN"/>
<logger name="io.bitsquare.storage.FileManager" level="WARN"/>
<logger name="io.bitsquare.p2p.peers" level="WARN"/>
<logger name="io.bitsquare.p2p.peers.getdata" level="WARN"/>
<logger name="io.bitsquare.p2p.peers.keepalive" level="WARN"/>
<logger name="io.bitsquare.p2p.peers.peerexchange" level="WARN"/>
<!--<logger name="io.bitsquare.p2p.network" level="WARN"/>-->
<!-- <logger name="io.bitsquare.p2p.P2PService" level="WARN"/>-->
<!-- <logger name="io.bitsquare.p2p.peers.PeerGroup" level="TRACE"/>
@ -47,10 +60,13 @@
<logger name="com.msopentech.thali.toronionproxy.OnionProxyManagerEventHandler" level="INFO"/>
<logger name="org.bitcoinj" level="WARN"/>
<logger name="org.bitcoinj.core.Peer" level="ERROR"/>-->
<logger name="org.bitcoinj.core.PeerGroup" level="ERROR"/>-->
<logger name="org.bitcoinj.net.ConnectionHandler" level="ERROR"/>-->
<logger name="org.bitcoinj.core.PeerSocketHandler" level="ERROR"/>-->
<logger name="org.bitcoinj.net.NioClientManager" level="ERROR"/>-->
<logger name="org.bitcoinj.net.MultiplexingDiscovery" level="ERROR"/>-->
<!-- <logger name="org.bitcoinj.core.Peer" level="TRACE"/>-->
</configuration>

85
headless/pom.xml Normal file
View file

@ -0,0 +1,85 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>parent</artifactId>
<groupId>io.bitsquare</groupId>
<version>0.4.9</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>headless</artifactId>
<build>
<resources>
<resource>
<filtering>false</filtering>
<directory>${basedir}/src/main/java</directory>
<includes>
<include>**/*.fxml</include>
<include>**/*.css</include>
</includes>
</resource>
<resource>
<filtering>false</filtering>
<directory>${basedir}/src/main/resources</directory>
<includes>
<include>**/*.*</include>
</includes>
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>2.3</version>
<configuration>
<!-- broken with Java 8 (MSHADE-174), using ProGuard instead. -->
<minimizeJar>false</minimizeJar>
<transformers>
<transformer
implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>io.bitsquare.headless.HeadlessMain</mainClass>
</transformer>
</transformers>
<filters>
<filter>
<!-- exclude signatures, the bundling process breaks them for some reason -->
<artifact>*:*</artifact>
<excludes>
<exclude>META-INF/*.SF</exclude>
<exclude>META-INF/*.DSA</exclude>
<exclude>META-INF/*.RSA</exclude>
</excludes>
</filter>
</filters>
</configuration>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<shadedArtifactAttached>true</shadedArtifactAttached>
<shadedClassifierName>bundled</shadedClassifierName>
<finalName>Headless</finalName>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>io.bitsquare</groupId>
<artifactId>core</artifactId>
<version>${project.parent.version}</version>
</dependency>
</dependencies>
</project>

View file

@ -0,0 +1,160 @@
package io.bitsquare.headless;
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.Log;
import io.bitsquare.app.Version;
import io.bitsquare.arbitration.ArbitratorManager;
import io.bitsquare.btc.WalletService;
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.p2p.P2PService;
import io.bitsquare.p2p.P2PServiceListener;
import io.bitsquare.trade.offer.OfferBookService;
import io.bitsquare.trade.offer.OpenOfferManager;
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 Headless {
private static final Logger log = LoggerFactory.getLogger(Headless.class);
private static Environment env;
private final Injector injector;
private final OfferBookService offerBookService;
private final OpenOfferManager openOfferManager;
private final HeadlessModule headlessModule;
private P2PService p2pService;
public static void setEnvironment(Environment env) {
Headless.env = env;
}
public Headless() {
String logPath = Paths.get(env.getProperty(BitsquareEnvironment.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());
headlessModule = new HeadlessModule(env);
injector = Guice.createInjector(headlessModule);
Version.setBtcNetworkId(injector.getInstance(BitsquareEnvironment.class).getBitcoinNetwork().ordinal());
p2pService = injector.getInstance(P2PService.class);
offerBookService = injector.getInstance(OfferBookService.class);
openOfferManager = injector.getInstance(OpenOfferManager.class);
p2pService.start(false, new P2PServiceListener() {
@Override
public void onRequestingDataCompleted() {
openOfferManager.onAllServicesInitialized();
}
@Override
public void onNoSeedNodeAvailable() {
}
@Override
public void onNoPeersAvailable() {
}
@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) {
}
});
}
public void shutDown() {
gracefulShutDown(() -> {
log.info("Shutdown complete");
System.exit(0);
});
}
private 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) -> {
headlessModule.close(injector);
log.info("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.info("App shutdown failed with exception");
t.printStackTrace();
System.exit(1);
}
}
}

View file

@ -0,0 +1,99 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.headless;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import io.bitsquare.app.BitsquareEnvironment;
import io.bitsquare.app.BitsquareExecutable;
import io.bitsquare.common.UserThread;
import joptsimple.OptionException;
import joptsimple.OptionParser;
import joptsimple.OptionSet;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Scanner;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import static io.bitsquare.app.BitsquareEnvironment.*;
public class HeadlessMain extends BitsquareExecutable {
private static final Logger log = LoggerFactory.getLogger(HeadlessMain.class);
private Headless headless;
private boolean isStopped;
public static void main(String[] args) throws Exception {
final ThreadFactory threadFactory = new ThreadFactoryBuilder()
.setNameFormat("HeadlessMain")
.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_headless");
OptionParser parser = new OptionParser();
parser.allowsUnrecognizedOptions();
parser.accepts(USER_DATA_DIR_KEY, description("User data directory", DEFAULT_USER_DATA_DIR))
.withRequiredArg();
parser.accepts(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(BitsquareEnvironment.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(HeadlessMain.class.getClassLoader());
new HeadlessMain().execute(args);
}
@Override
protected void doExecute(OptionSet options) {
Headless.setEnvironment(new BitsquareEnvironment(options));
UserThread.execute(() -> headless = new Headless());
while (!isStopped) {
try {
Scanner scanner = new Scanner(System.in);
while (scanner.hasNextLine()) {
String inputString = scanner.nextLine();
if (inputString.equals("q")) {
UserThread.execute(headless::shutDown);
isStopped = true;
}
}
} catch (Throwable ignore) {
}
}
}
}

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.headless;
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 HeadlessModule extends AppModule {
private static final Logger log = LoggerFactory.getLogger(HeadlessModule.class);
public HeadlessModule(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);
}
}

View file

@ -0,0 +1,42 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ 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 <http://www.gnu.org/licenses/>.
-->
<configuration>
<appender name="CONSOLE_APPENDER" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%highlight(%d{MMM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{15}: %msg %xEx%n)</pattern>
</encoder>
</appender>
<root level="TRACE">
<appender-ref ref="CONSOLE_APPENDER"/>
</root>
<logger name="io.bitsquare.storage.Storage" level="WARN"/>
<logger name="io.bitsquare.storage.FileManager" level="WARN"/>
<!-- <logger name="io.bitsquare.p2p.peers.PeerGroup" level="INFO"/>
<logger name="io.bitsquare.p2p.P2PService" level="INFO"/>
<logger name="io.bitsquare.p2p.storage.ProtectedExpirableDataStorage" level="INFO"/>
<logger name="io.bitsquare.p2p.network.LocalhostNetworkNode" level="INFO"/>
<logger name="io.bitsquare.p2p.network.TorNetworkNode" level="TRACE"/>
<logger name="io.bitsquare.p2p.network.NetworkNode" level="TRACE"/>-->
<logger name="com.msopentech.thali.toronionproxy.OnionProxyManagerEventHandler" level="INFO"/>
</configuration>

View file

@ -1,384 +0,0 @@
/*
* Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code 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 General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package sun.java2d.xr;
import java.awt.*;
import java.awt.geom.*;
import sun.awt.SunToolkit;
import sun.java2d.InvalidPipeException;
import sun.java2d.SunGraphics2D;
import sun.java2d.loops.*;
import sun.java2d.pipe.Region;
import sun.java2d.pipe.PixelDrawPipe;
import sun.java2d.pipe.PixelFillPipe;
import sun.java2d.pipe.ShapeDrawPipe;
import sun.java2d.pipe.SpanIterator;
import sun.java2d.pipe.ShapeSpanIterator;
import sun.java2d.pipe.LoopPipe;
import static sun.java2d.xr.XRUtils.clampToShort;
import static sun.java2d.xr.XRUtils.clampToUShort;
/**
* XRender provides only accalerated rectangles. To emulate higher "order"
* geometry we have to pass everything else to DoPath/FillSpans.
*
* TODO: DrawRect could be instrified
*
* @author Clemens Eisserer
*/
public class XRRenderer implements PixelDrawPipe, PixelFillPipe, ShapeDrawPipe {
XRDrawHandler drawHandler;
MaskTileManager tileManager;
XRDrawLine lineGen;
GrowableRectArray rectBuffer;
public XRRenderer(MaskTileManager tileManager) {
this.tileManager = tileManager;
this.rectBuffer = tileManager.getMainTile().getRects();
this.drawHandler = new XRDrawHandler();
this.lineGen = new XRDrawLine();
}
/**
* Common validate method, used by all XRRender functions to validate the
* destination context.
*/
private final void validateSurface(SunGraphics2D sg2d) {
XRSurfaceData xrsd;
try {
xrsd = (XRSurfaceData) sg2d.surfaceData;
} catch (ClassCastException e) {
throw new InvalidPipeException("wrong surface data type: " + sg2d.surfaceData);
}
xrsd.validateAsDestination(sg2d, sg2d.getCompClip());
xrsd.maskBuffer.validateCompositeState(sg2d.composite, sg2d.transform,
sg2d.paint, sg2d);
}
public void drawLine(SunGraphics2D sg2d, int x1, int y1, int x2, int y2) {
Region compClip = sg2d.getCompClip();
int transX1 = Region.clipAdd(x1, sg2d.transX);
int transY1 = Region.clipAdd(y1, sg2d.transY);
int transX2 = Region.clipAdd(x2, sg2d.transX);
int transY2 = Region.clipAdd(y2, sg2d.transY);
SunToolkit.awtLock();
try {
validateSurface(sg2d);
lineGen.rasterizeLine(rectBuffer, transX1, transY1,
transX2, transY2, compClip.getLoX(), compClip.getLoY(),
compClip.getHiX(), compClip.getHiY(), true, true);
tileManager.fillMask((XRSurfaceData) sg2d.surfaceData);
} finally {
SunToolkit.awtUnlock();
}
}
public void drawRect(SunGraphics2D sg2d,
int x, int y, int width, int height) {
draw(sg2d, new Rectangle2D.Float(x, y, width, height));
}
public void drawPolyline(SunGraphics2D sg2d,
int xpoints[], int ypoints[], int npoints) {
Path2D.Float p2d = new Path2D.Float();
if (npoints > 1) {
p2d.moveTo(xpoints[0], ypoints[0]);
for (int i = 1; i < npoints; i++) {
p2d.lineTo(xpoints[i], ypoints[i]);
}
}
draw(sg2d, p2d);
}
public void drawPolygon(SunGraphics2D sg2d,
int xpoints[], int ypoints[], int npoints) {
draw(sg2d, new Polygon(xpoints, ypoints, npoints));
}
public void fillRect(SunGraphics2D sg2d, int x, int y, int width, int height) {
x = Region.clipAdd(x, sg2d.transX);
y = Region.clipAdd(y, sg2d.transY);
/*
* Limit x/y to signed short, width/height to unsigned short,
* to match the X11 coordinate limits for rectangles.
* Correct width/height in case x/y have been modified by clipping.
*/
if (x > Short.MAX_VALUE || y > Short.MAX_VALUE) {
return;
}
int x2 = Region.dimAdd(x, width);
int y2 = Region.dimAdd(y, height);
if (x2 < Short.MIN_VALUE || y2 < Short.MIN_VALUE) {
return;
}
x = clampToShort(x);
y = clampToShort(y);
width = clampToUShort(x2 - x);
height = clampToUShort(y2 - y);
if (width == 0 || height == 0) {
return;
}
SunToolkit.awtLock();
try {
validateSurface(sg2d);
rectBuffer.pushRectValues(x, y, width, height);
tileManager.fillMask((XRSurfaceData) sg2d.surfaceData);
} finally {
SunToolkit.awtUnlock();
}
}
public void fillPolygon(SunGraphics2D sg2d,
int xpoints[], int ypoints[], int npoints) {
fill(sg2d, new Polygon(xpoints, ypoints, npoints));
}
public void drawRoundRect(SunGraphics2D sg2d,
int x, int y, int width, int height,
int arcWidth, int arcHeight) {
draw(sg2d, new RoundRectangle2D.Float(x, y, width, height,
arcWidth, arcHeight));
}
public void fillRoundRect(SunGraphics2D sg2d, int x, int y,
int width, int height,
int arcWidth, int arcHeight) {
fill(sg2d, new RoundRectangle2D.Float(x, y, width, height,
arcWidth, arcHeight));
}
public void drawOval(SunGraphics2D sg2d,
int x, int y, int width, int height) {
draw(sg2d, new Ellipse2D.Float(x, y, width, height));
}
public void fillOval(SunGraphics2D sg2d,
int x, int y, int width, int height) {
fill(sg2d, new Ellipse2D.Float(x, y, width, height));
}
public void drawArc(SunGraphics2D sg2d,
int x, int y, int width, int height,
int startAngle, int arcAngle) {
draw(sg2d, new Arc2D.Float(x, y, width, height,
startAngle, arcAngle, Arc2D.OPEN));
}
public void fillArc(SunGraphics2D sg2d,
int x, int y, int width, int height,
int startAngle, int arcAngle) {
fill(sg2d, new Arc2D.Float(x, y, width, height,
startAngle, arcAngle, Arc2D.PIE));
}
private class XRDrawHandler extends ProcessPath.DrawHandler {
DirtyRegion region;
XRDrawHandler() {
// these are bogus values; the caller will use validate()
// to ensure that they are set properly prior to each usage
super(0, 0, 0, 0);
this.region = new DirtyRegion();
}
/**
* This method needs to be called prior to each draw/fillPath()
* operation to ensure the clip bounds are up to date.
*/
void validate(SunGraphics2D sg2d) {
Region clip = sg2d.getCompClip();
setBounds(clip.getLoX(), clip.getLoY(),
clip.getHiX(), clip.getHiY(), sg2d.strokeHint);
validateSurface(sg2d);
}
public void drawLine(int x1, int y1, int x2, int y2) {
region.setDirtyLineRegion(x1, y1, x2, y2);
int xDiff = region.x2 - region.x;
int yDiff = region.y2 - region.y;
if (xDiff == 0 || yDiff == 0) {
// horizontal / diagonal lines can be represented by a single
// rectangle
rectBuffer.pushRectValues(region.x, region.y, region.x2 - region.x
+ 1, region.y2 - region.y + 1);
} else if (xDiff == 1 && yDiff == 1) {
// fast path for pattern commonly generated by
// ProcessPath.DrawHandler
rectBuffer.pushRectValues(x1, y1, 1, 1);
rectBuffer.pushRectValues(x2, y2, 1, 1);
} else {
lineGen.rasterizeLine(rectBuffer, x1, y1, x2, y2, 0, 0,
0, 0, false, false);
}
}
public void drawPixel(int x, int y) {
rectBuffer.pushRectValues(x, y, 1, 1);
}
public void drawScanline(int x1, int x2, int y) {
rectBuffer.pushRectValues(x1, y, x2 - x1 + 1, 1);
}
}
protected void drawPath(SunGraphics2D sg2d, Path2D.Float p2df,
int transx, int transy) {
SunToolkit.awtLock();
try {
validateSurface(sg2d);
drawHandler.validate(sg2d);
ProcessPath.drawPath(drawHandler, p2df, transx, transy);
tileManager.fillMask(((XRSurfaceData) sg2d.surfaceData));
} finally {
SunToolkit.awtUnlock();
}
}
protected void fillPath(SunGraphics2D sg2d, Path2D.Float p2df,
int transx, int transy) {
SunToolkit.awtLock();
try {
validateSurface(sg2d);
drawHandler.validate(sg2d);
ProcessPath.fillPath(drawHandler, p2df, transx, transy);
tileManager.fillMask(((XRSurfaceData) sg2d.surfaceData));
} finally {
SunToolkit.awtUnlock();
}
}
protected void fillSpans(SunGraphics2D sg2d, SpanIterator si,
int transx, int transy) {
SunToolkit.awtLock();
try {
validateSurface(sg2d);
int[] spanBox = new int[4];
while (si.nextSpan(spanBox)) {
rectBuffer.pushRectValues(spanBox[0] + transx,
spanBox[1] + transy,
spanBox[2] - spanBox[0],
spanBox[3] - spanBox[1]);
}
tileManager.fillMask(((XRSurfaceData) sg2d.surfaceData));
} finally {
SunToolkit.awtUnlock();
}
}
public void draw(SunGraphics2D sg2d, Shape s) {
if (sg2d.strokeState == SunGraphics2D.STROKE_THIN) {
Path2D.Float p2df;
int transx, transy;
if (sg2d.transformState <= SunGraphics2D.TRANSFORM_INT_TRANSLATE) {
if (s instanceof Path2D.Float) {
p2df = (Path2D.Float) s;
} else {
p2df = new Path2D.Float(s);
}
transx = sg2d.transX;
transy = sg2d.transY;
} else {
p2df = new Path2D.Float(s, sg2d.transform);
transx = 0;
transy = 0;
}
drawPath(sg2d, p2df, transx, transy);
} else if (sg2d.strokeState < SunGraphics2D.STROKE_CUSTOM) {
ShapeSpanIterator si = LoopPipe.getStrokeSpans(sg2d, s);
try {
fillSpans(sg2d, si, 0, 0);
} finally {
si.dispose();
}
} else {
fill(sg2d, sg2d.stroke.createStrokedShape(s));
}
}
public void fill(SunGraphics2D sg2d, Shape s) {
int transx, transy;
if (sg2d.strokeState == SunGraphics2D.STROKE_THIN) {
// Here we are able to use fillPath() for
// high-quality fills.
Path2D.Float p2df;
if (sg2d.transformState <= SunGraphics2D.TRANSFORM_INT_TRANSLATE) {
if (s instanceof Path2D.Float) {
p2df = (Path2D.Float) s;
} else {
p2df = new Path2D.Float(s);
}
transx = sg2d.transX;
transy = sg2d.transY;
} else {
p2df = new Path2D.Float(s, sg2d.transform);
transx = 0;
transy = 0;
}
fillPath(sg2d, p2df, transx, transy);
return;
}
AffineTransform at;
if (sg2d.transformState <= SunGraphics2D.TRANSFORM_INT_TRANSLATE) {
// Transform (translation) will be done by FillSpans
at = null;
transx = sg2d.transX;
transy = sg2d.transY;
} else {
// Transform will be done by the PathIterator
at = sg2d.transform;
transx = transy = 0;
}
ShapeSpanIterator ssi = LoopPipe.getFillSSI(sg2d);
try {
// Subtract transx/y from the SSI clip to match the
// (potentially untranslated) geometry fed to it
Region clip = sg2d.getCompClip();
ssi.setOutputAreaXYXY(clip.getLoX() - transx,
clip.getLoY() - transy,
clip.getHiX() - transx,
clip.getHiY() - transy);
ssi.appendPath(s.getPathIterator(at));
fillSpans(sg2d, ssi, transx, transy);
} finally {
ssi.dispose();
}
}
}

View file

@ -33,6 +33,10 @@ public abstract class TorNode<M extends OnionProxyManager, C extends OnionProxyC
log.info("TorSocks running on port " + proxyPort);
this.proxy = setupSocksProxy(proxyPort);
}
public Socks5Proxy getSocksProxy() {
return proxy;
}
private Socks5Proxy setupSocksProxy(int proxyPort) throws UnknownHostException {
Socks5Proxy proxy = new Socks5Proxy(PROXY_LOCALHOST, proxyPort);

View file

@ -4,5 +4,5 @@ DisableNetwork 1
AvoidDiskWrites 1
PidFile pid
RunAsDaemon 1
SafeSocks 1
SafeSocks 0
SOCKSPort auto

Binary file not shown.

93
monitor/pom.xml Normal file
View file

@ -0,0 +1,93 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>parent</artifactId>
<groupId>io.bitsquare</groupId>
<version>0.4.9</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>monitor</artifactId>
<build>
<resources>
<resource>
<filtering>false</filtering>
<directory>${basedir}/src/main/java</directory>
<includes>
<include>**/*.fxml</include>
<include>**/*.css</include>
</includes>
</resource>
<resource>
<filtering>false</filtering>
<directory>${basedir}/src/main/resources</directory>
<includes>
<include>**/*.*</include>
</includes>
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>2.3</version>
<configuration>
<!-- broken with Java 8 (MSHADE-174), using ProGuard instead. -->
<minimizeJar>false</minimizeJar>
<transformers>
<transformer
implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>io.bitsquare.monitor.MonitorMain</mainClass>
</transformer>
</transformers>
<filters>
<filter>
<!-- exclude signatures, the bundling process breaks them for some reason -->
<artifact>*:*</artifact>
<excludes>
<exclude>META-INF/*.SF</exclude>
<exclude>META-INF/*.DSA</exclude>
<exclude>META-INF/*.RSA</exclude>
</excludes>
</filter>
</filters>
</configuration>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<shadedArtifactAttached>true</shadedArtifactAttached>
<shadedClassifierName>bundled</shadedClassifierName>
<finalName>Monitor</finalName>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>io.bitsquare</groupId>
<artifactId>core</artifactId>
<version>${project.parent.version}</version>
</dependency>
<!--py4j-->
<!-- http://mvnrepository.com/artifact/net.sf.py4j/py4j -->
<dependency>
<groupId>net.sf.py4j</groupId>
<artifactId>py4j</artifactId>
<version>0.10.1</version>
</dependency>
</dependencies>
</project>

View file

@ -0,0 +1,26 @@
package io.bitsquare.monitor;
import io.bitsquare.trade.offer.Offer;
import io.bitsquare.trade.offer.OfferBookService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import py4j.GatewayServer;
import java.util.List;
public class Gateway {
private static final Logger log = LoggerFactory.getLogger(Gateway.class);
private OfferBookService offerBookService;
public Gateway(OfferBookService offerBookService) {
this.offerBookService = offerBookService;
GatewayServer gatewayServer = new GatewayServer(this);
gatewayServer.start();
log.info("Gateway Server Started");
}
public List<Offer> getOffers() {
return offerBookService.getOffers();
}
}

View file

@ -0,0 +1,163 @@
package io.bitsquare.monitor;
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.Log;
import io.bitsquare.app.Version;
import io.bitsquare.arbitration.ArbitratorManager;
import io.bitsquare.btc.WalletService;
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.p2p.P2PService;
import io.bitsquare.p2p.P2PServiceListener;
import io.bitsquare.trade.offer.OfferBookService;
import io.bitsquare.trade.offer.OpenOfferManager;
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 Monitor {
private static final Logger log = LoggerFactory.getLogger(Monitor.class);
private static Environment env;
private final Injector injector;
private final OfferBookService offerBookService;
private final Gateway gateway;
private final OpenOfferManager openOfferManager;
private final MonitorModule monitorModule;
private P2PService p2pService;
public static void setEnvironment(Environment env) {
Monitor.env = env;
}
public Monitor() {
String logPath = Paths.get(env.getProperty(BitsquareEnvironment.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());
monitorModule = new MonitorModule(env);
injector = Guice.createInjector(monitorModule);
Version.setBtcNetworkId(injector.getInstance(BitsquareEnvironment.class).getBitcoinNetwork().ordinal());
p2pService = injector.getInstance(P2PService.class);
offerBookService = injector.getInstance(OfferBookService.class);
openOfferManager = injector.getInstance(OpenOfferManager.class);
p2pService.start(false, new P2PServiceListener() {
@Override
public void onRequestingDataCompleted() {
openOfferManager.onAllServicesInitialized();
}
@Override
public void onNoSeedNodeAvailable() {
}
@Override
public void onNoPeersAvailable() {
}
@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) {
}
});
gateway = new Gateway(offerBookService);
}
public void shutDown() {
gracefulShutDown(() -> {
log.info("Shutdown complete");
System.exit(0);
});
}
private 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) -> {
monitorModule.close(injector);
log.info("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.info("App shutdown failed with exception");
t.printStackTrace();
System.exit(1);
}
}
}

View file

@ -0,0 +1,99 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.monitor;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import io.bitsquare.app.BitsquareEnvironment;
import io.bitsquare.app.BitsquareExecutable;
import io.bitsquare.common.UserThread;
import joptsimple.OptionException;
import joptsimple.OptionParser;
import joptsimple.OptionSet;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Scanner;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import static io.bitsquare.app.BitsquareEnvironment.*;
public class MonitorMain extends BitsquareExecutable {
private static final Logger log = LoggerFactory.getLogger(MonitorMain.class);
private Monitor monitor;
private boolean isStopped;
public static void main(String[] args) throws Exception {
final ThreadFactory threadFactory = new ThreadFactoryBuilder()
.setNameFormat("MonitorMain")
.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_monitor");
OptionParser parser = new OptionParser();
parser.allowsUnrecognizedOptions();
parser.accepts(USER_DATA_DIR_KEY, description("User data directory", DEFAULT_USER_DATA_DIR))
.withRequiredArg();
parser.accepts(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(BitsquareEnvironment.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(MonitorMain.class.getClassLoader());
new MonitorMain().execute(args);
}
@Override
protected void doExecute(OptionSet options) {
Monitor.setEnvironment(new BitsquareEnvironment(options));
UserThread.execute(() -> monitor = new Monitor());
while (!isStopped) {
try {
Scanner scanner = new Scanner(System.in);
while (scanner.hasNextLine()) {
String inputString = scanner.nextLine();
if (inputString.equals("q")) {
UserThread.execute(monitor::shutDown);
isStopped = true;
}
}
} catch (Throwable ignore) {
}
}
}
}

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.monitor;
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 MonitorModule extends AppModule {
private static final Logger log = LoggerFactory.getLogger(MonitorModule.class);
public MonitorModule(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);
}
}

View file

@ -0,0 +1,42 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ 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 <http://www.gnu.org/licenses/>.
-->
<configuration>
<appender name="CONSOLE_APPENDER" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%highlight(%d{MMM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{15}: %msg %xEx%n)</pattern>
</encoder>
</appender>
<root level="TRACE">
<appender-ref ref="CONSOLE_APPENDER"/>
</root>
<logger name="io.bitsquare.storage.Storage" level="WARN"/>
<logger name="io.bitsquare.storage.FileManager" level="WARN"/>
<!-- <logger name="io.bitsquare.p2p.peers.PeerGroup" level="INFO"/>
<logger name="io.bitsquare.p2p.P2PService" level="INFO"/>
<logger name="io.bitsquare.p2p.storage.ProtectedExpirableDataStorage" level="INFO"/>
<logger name="io.bitsquare.p2p.network.LocalhostNetworkNode" level="INFO"/>
<logger name="io.bitsquare.p2p.network.TorNetworkNode" level="TRACE"/>
<logger name="io.bitsquare.p2p.network.NetworkNode" level="TRACE"/>-->
<logger name="com.msopentech.thali.toronionproxy.OnionProxyManagerEventHandler" level="INFO"/>
</configuration>

View file

@ -1,16 +1,14 @@
package io.bitsquare.network;
public class OptionKeys {
public class NetworkOptionKeys {
public static final String TOR_DIR = "torDir";
public static final String USE_LOCALHOST = "useLocalhost";
public static final String MAX_CONNECTIONS = "maxConnections";
public static final String PORT_KEY = "nodePort";
public static final String NETWORK_ID = "networkId";
public static final String SEED_NODES_KEY = "seedNodes";
// Seed nodes
public static final String MY_ADDRESS = "myAddress";
public static final String SEED_NODES_LIST = "seedNodes";
public static final String BAN_LIST = "banList";
public static final String HELP = "help";
}

View file

@ -37,6 +37,11 @@ public final class NodeAddress implements Persistable, Payload {
return addressPrefixHash;
}
public String getHostNameWithoutPostFix() {
return hostName.replace(".onion", "");
}
@Override
public boolean equals(Object o) {
if (this == o) return true;

View file

@ -20,7 +20,7 @@ package io.bitsquare.p2p;
import com.google.inject.Singleton;
import com.google.inject.name.Names;
import io.bitsquare.app.AppModule;
import io.bitsquare.network.OptionKeys;
import io.bitsquare.network.NetworkOptionKeys;
import io.bitsquare.p2p.seed.SeedNodesRepository;
import org.springframework.core.env.Environment;
@ -40,21 +40,23 @@ public class P2PModule extends AppModule {
bind(SeedNodesRepository.class).in(Singleton.class);
bind(P2PService.class).in(Singleton.class);
Boolean useLocalhost = env.getProperty(OptionKeys.USE_LOCALHOST, boolean.class, false);
bind(boolean.class).annotatedWith(Names.named(OptionKeys.USE_LOCALHOST)).toInstance(useLocalhost);
Boolean useLocalhost = env.getProperty(NetworkOptionKeys.USE_LOCALHOST, boolean.class, false);
bind(boolean.class).annotatedWith(Names.named(NetworkOptionKeys.USE_LOCALHOST)).toInstance(useLocalhost);
File torDir = new File(env.getRequiredProperty(OptionKeys.TOR_DIR));
bind(File.class).annotatedWith(named(OptionKeys.TOR_DIR)).toInstance(torDir);
File torDir = new File(env.getRequiredProperty(NetworkOptionKeys.TOR_DIR));
bind(File.class).annotatedWith(named(NetworkOptionKeys.TOR_DIR)).toInstance(torDir);
// use a fixed port as arbitrator use that for his ID
Integer port = env.getProperty(OptionKeys.PORT_KEY, int.class, 9999);
bind(int.class).annotatedWith(Names.named(OptionKeys.PORT_KEY)).toInstance(port);
Integer port = env.getProperty(NetworkOptionKeys.PORT_KEY, int.class, 9999);
bind(int.class).annotatedWith(Names.named(NetworkOptionKeys.PORT_KEY)).toInstance(port);
Integer maxConnections = env.getProperty(OptionKeys.MAX_CONNECTIONS, int.class, P2PService.MAX_CONNECTIONS_DEFAULT);
bind(int.class).annotatedWith(Names.named(OptionKeys.MAX_CONNECTIONS)).toInstance(maxConnections);
Integer maxConnections = env.getProperty(NetworkOptionKeys.MAX_CONNECTIONS, int.class, P2PService.MAX_CONNECTIONS_DEFAULT);
bind(int.class).annotatedWith(Names.named(NetworkOptionKeys.MAX_CONNECTIONS)).toInstance(maxConnections);
Integer networkId = env.getProperty(OptionKeys.NETWORK_ID, int.class, 1);
bind(int.class).annotatedWith(Names.named(OptionKeys.NETWORK_ID)).toInstance(networkId);
bindConstant().annotatedWith(named(OptionKeys.SEED_NODES_KEY)).to(env.getRequiredProperty(OptionKeys.SEED_NODES_KEY));
Integer networkId = env.getProperty(NetworkOptionKeys.NETWORK_ID, int.class, 1);
bind(int.class).annotatedWith(Names.named(NetworkOptionKeys.NETWORK_ID)).toInstance(networkId);
bindConstant().annotatedWith(named(NetworkOptionKeys.SEED_NODES_KEY)).to(env.getRequiredProperty(NetworkOptionKeys.SEED_NODES_KEY));
bindConstant().annotatedWith(named(NetworkOptionKeys.MY_ADDRESS)).to(env.getRequiredProperty(NetworkOptionKeys.MY_ADDRESS));
bindConstant().annotatedWith(named(NetworkOptionKeys.BAN_LIST)).to(env.getRequiredProperty(NetworkOptionKeys.BAN_LIST));
}
}

View file

@ -14,9 +14,10 @@ import io.bitsquare.common.crypto.KeyRing;
import io.bitsquare.common.crypto.PubKeyRing;
import io.bitsquare.crypto.DecryptedMsgWithPubKey;
import io.bitsquare.crypto.EncryptionService;
import io.bitsquare.network.OptionKeys;
import io.bitsquare.network.NetworkOptionKeys;
import io.bitsquare.p2p.messaging.*;
import io.bitsquare.p2p.network.*;
import io.bitsquare.p2p.peers.BanList;
import io.bitsquare.p2p.peers.BroadcastHandler;
import io.bitsquare.p2p.peers.Broadcaster;
import io.bitsquare.p2p.peers.PeerManager;
@ -64,6 +65,7 @@ public class P2PService implements SetupListener, MessageListener, ConnectionLis
private final int maxConnections;
private final File torDir;
private Clock clock;
//TODO optional can be removed as seednode are created with those objects now
private final Optional<EncryptionService> optionalEncryptionService;
private final Optional<KeyRing> optionalKeyRing;
@ -100,13 +102,15 @@ public class P2PService implements SetupListener, MessageListener, ConnectionLis
// Called also from SeedNodeP2PService
@Inject
public P2PService(SeedNodesRepository seedNodesRepository,
@Named(OptionKeys.PORT_KEY) int port,
@Named(OptionKeys.TOR_DIR) File torDir,
@Named(OptionKeys.USE_LOCALHOST) boolean useLocalhost,
@Named(OptionKeys.NETWORK_ID) int networkId,
@Named(OptionKeys.MAX_CONNECTIONS) int maxConnections,
@Named(NetworkOptionKeys.PORT_KEY) int port,
@Named(NetworkOptionKeys.TOR_DIR) File torDir,
@Named(NetworkOptionKeys.USE_LOCALHOST) boolean useLocalhost,
@Named(NetworkOptionKeys.NETWORK_ID) int networkId,
@Named(NetworkOptionKeys.MAX_CONNECTIONS) int maxConnections,
@Named(Storage.DIR_KEY) File storageDir,
@Named(OptionKeys.SEED_NODES_KEY) String seedNodes,
@Named(NetworkOptionKeys.SEED_NODES_KEY) String seedNodes,
@Named(NetworkOptionKeys.MY_ADDRESS) String myAddress,
@Named(NetworkOptionKeys.BAN_LIST) String banList,
Clock clock,
@Nullable EncryptionService encryptionService,
@Nullable KeyRing keyRing) {
@ -119,6 +123,8 @@ public class P2PService implements SetupListener, MessageListener, ConnectionLis
networkId,
storageDir,
seedNodes,
myAddress,
banList,
clock,
encryptionService,
keyRing
@ -133,6 +139,8 @@ public class P2PService implements SetupListener, MessageListener, ConnectionLis
int networkId,
File storageDir,
String seedNodes,
String myAddress,
String banList,
Clock clock,
@Nullable EncryptionService encryptionService,
@Nullable KeyRing keyRing) {
@ -145,13 +153,28 @@ public class P2PService implements SetupListener, MessageListener, ConnectionLis
optionalEncryptionService = Optional.ofNullable(encryptionService);
optionalKeyRing = Optional.ofNullable(keyRing);
init(useLocalhost, networkId, storageDir, seedNodes);
init(useLocalhost,
networkId,
storageDir,
seedNodes,
myAddress,
banList);
}
private void init(boolean useLocalhost, int networkId, File storageDir, String seedNodes) {
private void init(boolean useLocalhost,
int networkId,
File storageDir,
String seedNodes,
String myAddress,
String banList) {
if (!useLocalhost)
FileUtil.rollingBackup(new File(Paths.get(torDir.getAbsolutePath(), "hiddenservice").toString()), "private_key");
if (banList != null && !banList.isEmpty())
BanList.setList(Arrays.asList(banList.replace(" ", "").split(",")).stream().map(NodeAddress::new).collect(Collectors.toList()));
if (myAddress != null && !myAddress.isEmpty())
seedNodesRepository.setNodeAddressToExclude(new NodeAddress(myAddress));
networkNode = useLocalhost ? new LocalhostNetworkNode(port) : new TorNetworkNode(port, torDir);
networkNode.addConnectionListener(this);
networkNode.addMessageListener(this);
@ -199,6 +222,25 @@ public class P2PService implements SetupListener, MessageListener, ConnectionLis
networkNode.start(useBridges, this);
}
public void onAllServicesInitialized() {
Log.traceCall();
if (networkNode.getNodeAddress() != null) {
p2PDataStorage.getMap().values().stream().forEach(protectedStorageEntry -> {
if (protectedStorageEntry instanceof ProtectedMailboxStorageEntry)
processProtectedMailboxStorageEntry((ProtectedMailboxStorageEntry) protectedStorageEntry);
});
} else {
networkNode.nodeAddressProperty().addListener((observable, oldValue, newValue) -> {
if (newValue != null) {
p2PDataStorage.getMap().values().stream().forEach(protectedStorageEntry -> {
if (protectedStorageEntry instanceof ProtectedMailboxStorageEntry)
processProtectedMailboxStorageEntry((ProtectedMailboxStorageEntry) protectedStorageEntry);
});
}
});
}
}
public void shutDown(Runnable shutDownCompleteHandler) {
Log.traceCall();
if (!shutDownInProgress) {
@ -488,8 +530,10 @@ public class P2PService implements SetupListener, MessageListener, ConnectionLis
///////////////////////////////////////////////////////////////////////////////////////////
private void processProtectedMailboxStorageEntry(ProtectedMailboxStorageEntry protectedMailboxStorageEntry) {
// Seed nodes don't have set the encryptionService
if (optionalEncryptionService.isPresent()) {
Log.traceCall();
final NodeAddress nodeAddress = networkNode.getNodeAddress();
// Seed nodes don't receive mailbox messages
if (optionalEncryptionService.isPresent() && nodeAddress != null && !seedNodesRepository.isSeedNode(nodeAddress)) {
Log.traceCall();
MailboxStoragePayload mailboxStoragePayload = protectedMailboxStorageEntry.getMailboxStoragePayload();
PrefixedSealedAndSignedMessage prefixedSealedAndSignedMessage = mailboxStoragePayload.prefixedSealedAndSignedMessage;

View file

@ -44,8 +44,9 @@ public final class PrefixedSealedAndSignedMessage implements MailboxMessage, Sen
@Override
public String toString() {
return "SealedAndSignedMessage{" +
"messageVersion=" + messageVersion +
return "PrefixedSealedAndSignedMessage{" +
"uid=" + uid +
", messageVersion=" + messageVersion +
", sealedAndSigned=" + sealedAndSigned +
", receiverAddressMaskHash.hashCode()=" + Arrays.toString(addressPrefixHash).hashCode() +
'}';

View file

@ -70,7 +70,7 @@ public class Connection implements MessageListener {
//TODO decrease limits again after testing
static final int MSG_THROTTLE_PER_SEC = 200; // With MAX_MSG_SIZE of 200kb results in bandwidth of 40MB/sec or 5 mbit/sec
static final int MSG_THROTTLE_PER_10_SEC = 1000; // With MAX_MSG_SIZE of 200kb results in bandwidth of 20MB/sec or 2.5 mbit/sec
private static final int SOCKET_TIMEOUT = (int) TimeUnit.SECONDS.toMillis(90);
private static final int SOCKET_TIMEOUT = (int) TimeUnit.SECONDS.toMillis(60);
public static int getMaxMsgSize() {
return MAX_MSG_SIZE;
@ -169,6 +169,7 @@ public class Connection implements MessageListener {
public void sendMessage(Message message) {
if (!stopped) {
try {
log.info("sendMessage message=" + getTruncatedMessage(message));
Log.traceCall();
// Throttle outbound messages
long now = System.currentTimeMillis();
@ -190,7 +191,7 @@ public class Connection implements MessageListener {
"Sending direct message to peer" +
"Write object to outputStream to peer: {} (uid={})\ntruncated message={} / size={}" +
"\n>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n",
peersNodeAddress, uid, StringUtils.abbreviate(message.toString(), 100), size);
peersNodeAddress, uid, getTruncatedMessage(message), size);
} else if (message instanceof PrefixedSealedAndSignedMessage && peersNodeAddressOptional.isPresent()) {
setPeerType(Connection.PeerType.DIRECT_MSG_PEER);
@ -198,12 +199,12 @@ public class Connection implements MessageListener {
"Sending direct message to peer" +
"Write object to outputStream to peer: {} (uid={})\ntruncated message={} / size={}" +
"\n>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n",
peersNodeAddress, uid, StringUtils.abbreviate(message.toString(), 100), size);
peersNodeAddress, uid, getTruncatedMessage(message), size);
} else {
log.info("\n\n>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n" +
"Write object to outputStream to peer: {} (uid={})\ntruncated message={} / size={}" +
"\n>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n",
peersNodeAddress, uid, StringUtils.abbreviate(message.toString(), 100), size);
peersNodeAddress, uid, getTruncatedMessage(message), size);
}
if (!stopped) {
@ -450,6 +451,10 @@ public class Connection implements MessageListener {
}
}
private String getTruncatedMessage(Message message) {
return StringUtils.abbreviate(message.toString(), 100).replace("\n", "");
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
@ -689,6 +694,10 @@ public class Connection implements MessageListener {
+ "\n<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n",
connection,
size);
try {
log.error("rawInputObject.className=" + rawInputObject.getClass().getName());
} catch (Throwable ignore) {
}
}
// We want to track the size of each object even if it is invalid data

View file

@ -80,8 +80,7 @@ public class LocalhostNetworkNode extends NetworkNode {
protected Socket createSocket(NodeAddress peerNodeAddress) throws IOException {
return new Socket(peerNodeAddress.hostName, peerNodeAddress.port);
}
///////////////////////////////////////////////////////////////////////////////////////////
// Tor delay simulation
///////////////////////////////////////////////////////////////////////////////////////////

View file

@ -1,6 +1,7 @@
package io.bitsquare.p2p.network;
import com.google.common.util.concurrent.*;
import com.runjva.sourceforge.jsocks.protocol.Socks5Proxy;
import io.bitsquare.app.Log;
import io.bitsquare.common.UserThread;
import io.bitsquare.common.util.Utilities;
@ -213,6 +214,12 @@ public abstract class NetworkNode implements MessageListener {
}
}
@Nullable
public Socks5Proxy getSocksProxy() {
return null;
}
public SettableFuture<Connection> sendMessage(Connection connection, Message message) {
Log.traceCall("\n\tmessage=" + StringUtils.abbreviate(message.toString(), 100) + "\n\tconnection=" + connection);
// connection.sendMessage might take a bit (compression, write to stream), so we use a thread to not block

View file

@ -6,6 +6,7 @@ import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors;
import com.msopentech.thali.java.toronionproxy.JavaOnionProxyContext;
import com.msopentech.thali.java.toronionproxy.JavaOnionProxyManager;
import com.runjva.sourceforge.jsocks.protocol.Socks5Proxy;
import io.bitsquare.app.Log;
import io.bitsquare.common.Timer;
import io.bitsquare.common.UserThread;
@ -95,10 +96,15 @@ public class TorNetworkNode extends NetworkNode {
@Override
protected Socket createSocket(NodeAddress peerNodeAddress) throws IOException {
checkArgument(peerNodeAddress.hostName.endsWith(".onion"), "PeerAddress is not an onion address");
// FIXME: disabling temporarily.
// checkArgument(peerNodeAddress.hostName.endsWith(".onion"), "PeerAddress is not an onion address");
return torNetworkNode.connectToHiddenService(peerNodeAddress.hostName, peerNodeAddress.port);
}
public Socks5Proxy getSocksProxy() {
return torNetworkNode != null ? torNetworkNode.getSocksProxy() : null;
}
public void shutDown(@Nullable Runnable shutDownCompleteHandler) {
Log.traceCall();

View file

@ -120,14 +120,14 @@ public class BroadcastHandler implements PeerManager.Listener {
timeoutTimer = UserThread.runAfter(() -> { // setup before sending to avoid race conditions
String errorMessage = "Timeout: Broadcast did not complete after " + timeoutDelay + " sec.";
log.warn(errorMessage + "\n\t" +
log.debug(errorMessage + "\n\t" +
"numOfPeers=" + numOfPeers + "\n\t" +
"numOfCompletedBroadcasts=" + numOfCompletedBroadcasts + "\n\t" +
"numOfCompletedBroadcasts=" + numOfCompletedBroadcasts + "\n\t" +
"numOfFailedBroadcasts=" + numOfFailedBroadcasts + "\n\t" +
"broadcastQueue.size()=" + broadcastQueue.size() + "\n\t" +
"broadcastQueue=" + broadcastQueue);
onFault(errorMessage);
onFault(errorMessage, false);
}, timeoutDelay);
log.info("Broadcast message to {} peers out of {} total connected peers.", numOfPeers, connectedPeersSet.size());
@ -196,10 +196,10 @@ public class BroadcastHandler implements PeerManager.Listener {
}
});
} else {
onFault("Connection stopped already");
onFault("Connection stopped already", false);
}
} else {
onFault("stopped at sendToPeer: " + errorMessage);
onFault("stopped at sendToPeer: " + errorMessage, false);
}
}
@ -210,7 +210,7 @@ public class BroadcastHandler implements PeerManager.Listener {
@Override
public void onAllConnectionsLost() {
onFault("All connections lost");
onFault("All connections lost", false);
}
@Override

View file

@ -337,7 +337,8 @@ public class PeerManager implements ConnectionListener {
@Nullable
private Peer removeReportedPeer(NodeAddress nodeAddress) {
Optional<Peer> reportedPeerOptional = reportedPeers.stream()
List<Peer> reportedPeersClone = new ArrayList<>(reportedPeers);
Optional<Peer> reportedPeerOptional = reportedPeersClone.stream()
.filter(e -> e.nodeAddress.equals(nodeAddress)).findAny();
if (reportedPeerOptional.isPresent()) {
Peer reportedPeer = reportedPeerOptional.get();
@ -350,7 +351,8 @@ public class PeerManager implements ConnectionListener {
private void removeTooOldReportedPeers() {
Log.traceCall();
Set<Peer> reportedPeersToRemove = reportedPeers.stream()
List<Peer> reportedPeersClone = new ArrayList<>(reportedPeers);
Set<Peer> reportedPeersToRemove = reportedPeersClone.stream()
.filter(reportedPeer -> new Date().getTime() - reportedPeer.date.getTime() > MAX_AGE)
.collect(Collectors.toSet());
reportedPeersToRemove.forEach(this::removeReportedPeer);
@ -406,9 +408,10 @@ public class PeerManager implements ConnectionListener {
if (printReportedPeersDetails) {
StringBuilder result = new StringBuilder("\n\n------------------------------------------------------------\n" +
"Collected reported peers:");
reportedPeers.stream().forEach(e -> result.append("\n").append(e));
List<Peer> reportedPeersClone = new ArrayList<>(reportedPeers);
reportedPeersClone.stream().forEach(e -> result.append("\n").append(e));
result.append("\n------------------------------------------------------------\n");
log.info(result.toString());
log.debug(result.toString());
}
log.info("Number of collected reported peers: {}", reportedPeers.size());
}
@ -417,8 +420,9 @@ public class PeerManager implements ConnectionListener {
private void printNewReportedPeers(HashSet<Peer> reportedPeers) {
if (printReportedPeersDetails) {
StringBuilder result = new StringBuilder("We received new reportedPeers:");
reportedPeers.stream().forEach(e -> result.append("\n\t").append(e));
log.info(result.toString());
List<Peer> reportedPeersClone = new ArrayList<>(reportedPeers);
reportedPeersClone.stream().forEach(e -> result.append("\n\t").append(e));
log.debug(result.toString());
}
log.info("Number of new arrived reported peers: {}", reportedPeers.size());
}

View file

@ -106,7 +106,7 @@ public class RequestDataManager implements MessageListener, ConnectionListener,
public void requestUpdateData() {
Log.traceCall();
checkArgument(nodeAddressOfPreliminaryDataRequest.isPresent(), "seedNodeOfPreliminaryDataRequest must be present");
checkArgument(nodeAddressOfPreliminaryDataRequest.isPresent(), "nodeAddressOfPreliminaryDataRequest must be present");
dataUpdateRequested = true;
List<NodeAddress> remainingNodeAddresses = new ArrayList<>(seedNodeAddresses);
if (!remainingNodeAddresses.isEmpty()) {

View file

@ -196,10 +196,10 @@ public class KeepAliveManager implements MessageListener, ConnectionListener, Pe
});
int size = handlerMap.size();
log.info("maintenanceHandlerMap size=" + size);
log.info("handlerMap size=" + size);
if (size > peerManager.getMaxConnections())
log.warn("Seems we didn't clean up out map correctly.\n" +
"maintenanceHandlerMap size={}, peerManager.getMaxConnections()={}", size, peerManager.getMaxConnections());
"handlerMap size={}, peerManager.getMaxConnections()={}", size, peerManager.getMaxConnections());
} else {
log.warn("We have stopped already. We ignore that keepAlive call.");
}

View file

@ -8,6 +8,7 @@ import org.slf4j.LoggerFactory;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class SeedNodesRepository {
private static final Logger log = LoggerFactory.getLogger(SeedNodesRepository.class);
@ -95,6 +96,11 @@ public class SeedNodesRepository {
this.localhostSeedNodeAddresses = localhostSeedNodeAddresses;
}
public boolean isSeedNode(NodeAddress nodeAddress) {
return Stream.concat(localhostSeedNodeAddresses.stream(), torSeedNodeAddresses.stream())
.filter(e -> e.equals(nodeAddress)).findAny().isPresent();
}
public void setNodeAddressToExclude(NodeAddress nodeAddress) {
this.nodeAddressToExclude = nodeAddress;
}

View file

@ -9,6 +9,7 @@ import io.bitsquare.common.crypto.CryptoException;
import io.bitsquare.common.crypto.Hash;
import io.bitsquare.common.crypto.Sig;
import io.bitsquare.common.persistance.Persistable;
import io.bitsquare.common.util.Tuple2;
import io.bitsquare.common.wire.Payload;
import io.bitsquare.p2p.Message;
import io.bitsquare.p2p.NodeAddress;
@ -36,6 +37,7 @@ import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
// Run in UserThread
public class P2PDataStorage implements MessageListener, ConnectionListener {
@ -95,7 +97,7 @@ public class P2PDataStorage implements MessageListener, ConnectionListener {
ByteArray hashOfPayload = entry.getKey();
ProtectedStorageEntry protectedStorageEntry = map.get(hashOfPayload);
toRemoveSet.add(protectedStorageEntry);
log.info("We found an expired data entry. We remove the protectedData:\n\t" + protectedStorageEntry);
log.info("We found an expired data entry. We remove the protectedData:\n\t" + StringUtils.abbreviate(protectedStorageEntry.toString().replace("\n", ""), 100));
map.remove(hashOfPayload);
});
@ -210,24 +212,26 @@ public class P2PDataStorage implements MessageListener, ConnectionListener {
if (containsKey)
result &= checkIfStoredDataPubKeyMatchesNewDataPubKey(protectedStorageEntry.ownerPubKey, hashOfPayload);
// printData("before add");
if (result) {
map.put(hashOfPayload, protectedStorageEntry);
final boolean hasSequenceNrIncreased = hasSequenceNrIncreased(protectedStorageEntry.sequenceNumber, hashOfPayload);
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);
hashMapChangedListeners.stream().forEach(e -> e.onAdded(protectedStorageEntry));
printData("after add");
} else {
log.trace("We got that version of the data already, so we don't store it.");
}
StringBuilder sb = new StringBuilder("\n\n------------------------------------------------------------\n");
sb.append("Data set after doAdd (truncated)");
map.values().stream().forEach(e -> sb.append("\n").append(StringUtils.abbreviate(e.toString(), 100)));
sb.append("\n------------------------------------------------------------\n");
log.trace(sb.toString());
log.info("Data set after doAdd: size=" + map.values().size());
if (hasSequenceNrIncreased(protectedStorageEntry.sequenceNumber, hashOfPayload)) {
if (hasSequenceNrIncreased) {
sequenceNumberMap.put(hashOfPayload, new MapValue(protectedStorageEntry.sequenceNumber, System.currentTimeMillis()));
storage.queueUpForSave(sequenceNumberMap, 100);
broadcast(new AddDataMessage(protectedStorageEntry), sender, listener, isDataOwner);
} else {
log.trace("We got that version of the data already, so we don't broadcast it.");
}
hashMapChangedListeners.stream().forEach(e -> e.onAdded(protectedStorageEntry));
} else {
log.trace("add failed");
}
@ -250,29 +254,26 @@ public class P2PDataStorage implements MessageListener, ConnectionListener {
return true;
} else {
PublicKey ownerPubKey = storedData.getStoragePayload().getOwnerPubKey();
boolean result = checkSignature(ownerPubKey, hashOfDataAndSeqNr, signature) &&
hasSequenceNrIncreased(sequenceNumber, hashOfPayload) &&
checkIfStoredDataPubKeyMatchesNewDataPubKey(ownerPubKey, hashOfPayload);
final boolean checkSignature = checkSignature(ownerPubKey, hashOfDataAndSeqNr, signature);
final boolean hasSequenceNrIncreased = hasSequenceNrIncreased(sequenceNumber, hashOfPayload);
final boolean checkIfStoredDataPubKeyMatchesNewDataPubKey = checkIfStoredDataPubKeyMatchesNewDataPubKey(ownerPubKey, hashOfPayload);
boolean allValid = checkSignature &&
hasSequenceNrIncreased &&
checkIfStoredDataPubKeyMatchesNewDataPubKey;
if (result) {
log.info("refreshDate called for storedData:\n\t" + StringUtils.abbreviate(storedData.toString(), 100));
// printData("before refreshTTL");
if (allValid) {
log.debug("refreshDate called for storedData:\n\t" + StringUtils.abbreviate(storedData.toString(), 100));
storedData.refreshTTL();
storedData.updateSequenceNumber(sequenceNumber);
storedData.updateSignature(signature);
printData("after refreshTTL");
sequenceNumberMap.put(hashOfPayload, new MapValue(sequenceNumber, System.currentTimeMillis()));
storage.queueUpForSave(sequenceNumberMap, 100);
StringBuilder sb = new StringBuilder("\n\n------------------------------------------------------------\n");
sb.append("Data set after refreshTTL (truncated)");
map.values().stream().forEach(e -> sb.append("\n").append(StringUtils.abbreviate(e.toString(), 100)));
sb.append("\n------------------------------------------------------------\n");
log.trace(sb.toString());
log.info("Data set after refreshTTL: size=" + map.values().size());
broadcast(refreshTTLMessage, sender, null, isDataOwner);
}
return result;
return allValid;
}
} else {
log.debug("We don't have data for that refresh message in our map. That is expected if we missed the data publishing.");
@ -292,14 +293,14 @@ public class P2PDataStorage implements MessageListener, ConnectionListener {
&& checkSignature(protectedStorageEntry)
&& checkIfStoredDataPubKeyMatchesNewDataPubKey(protectedStorageEntry.ownerPubKey, hashOfPayload);
// printData("before remove");
if (result) {
doRemoveProtectedExpirableData(protectedStorageEntry, hashOfPayload);
broadcast(new RemoveDataMessage(protectedStorageEntry), sender, null, isDataOwner);
printData("after remove");
sequenceNumberMap.put(hashOfPayload, new MapValue(protectedStorageEntry.sequenceNumber, System.currentTimeMillis()));
storage.queueUpForSave(sequenceNumberMap, 100);
broadcast(new RemoveDataMessage(protectedStorageEntry), sender, null, isDataOwner);
} else {
log.debug("remove failed");
}
@ -319,13 +320,14 @@ public class P2PDataStorage implements MessageListener, ConnectionListener {
&& checkSignature(protectedMailboxStorageEntry)
&& checkIfStoredMailboxDataMatchesNewMailboxData(protectedMailboxStorageEntry.receiversPubKey, hashOfData);
// printData("before removeMailboxData");
if (result) {
doRemoveProtectedExpirableData(protectedMailboxStorageEntry, hashOfData);
broadcast(new RemoveMailboxDataMessage(protectedMailboxStorageEntry), sender, null, isDataOwner);
printData("after removeMailboxData");
sequenceNumberMap.put(hashOfData, new MapValue(protectedMailboxStorageEntry.sequenceNumber, System.currentTimeMillis()));
storage.queueUpForSave(sequenceNumberMap, 100);
broadcast(new RemoveMailboxDataMessage(protectedMailboxStorageEntry), sender, null, isDataOwner);
} else {
log.debug("removeMailboxData failed");
}
@ -394,13 +396,6 @@ public class P2PDataStorage implements MessageListener, ConnectionListener {
map.remove(hashOfPayload);
log.trace("Data removed from our map. We broadcast the message to our peers.");
hashMapChangedListeners.stream().forEach(e -> e.onRemoved(protectedStorageEntry));
StringBuilder sb = new StringBuilder("\n\n------------------------------------------------------------\n" +
"Data set after removeProtectedExpirableData: (truncated)");
map.values().stream().forEach(e -> sb.append("\n").append(StringUtils.abbreviate(e.toString(), 100)));
sb.append("\n------------------------------------------------------------\n");
log.trace(sb.toString());
log.info("Data set after doRemoveProtectedExpirableData: size=" + map.values().size());
}
private boolean isSequenceNrValid(int newSequenceNumber, ByteArray hashOfData) {
@ -425,15 +420,16 @@ public class P2PDataStorage implements MessageListener, ConnectionListener {
if (newSequenceNumber > storedSequenceNumber) {
return true;
} else if (newSequenceNumber == storedSequenceNumber) {
String msg;
if (newSequenceNumber == 0) {
log.debug("Sequence number is equal to the stored one and both are 0." +
"That is expected for messages which never got updated (mailbox msg).");
return false;
msg = "Sequence number is equal to the stored one and both are 0." +
"That is expected for messages which never got updated (mailbox msg).";
} else {
log.debug("Sequence number is equal to the stored one. sequenceNumber = "
+ newSequenceNumber + " / storedSequenceNumber=" + storedSequenceNumber);
return false;
msg = "Sequence number is equal to the stored one. sequenceNumber = "
+ newSequenceNumber + " / storedSequenceNumber=" + storedSequenceNumber;
}
log.debug(msg);
return false;
} else {
log.debug("Sequence number is invalid. sequenceNumber = "
+ newSequenceNumber + " / storedSequenceNumber=" + storedSequenceNumber + "\n" +
@ -450,7 +446,8 @@ public class P2PDataStorage implements MessageListener, ConnectionListener {
boolean result = Sig.verify(ownerPubKey, hashOfDataAndSeqNr, signature);
if (!result)
log.error("Signature verification failed at checkSignature. " +
"That should not happen. Consider it might be an attempt of fraud.");
"That should not happen. ownerPubKey=" + ownerPubKey +
", hashOfDataAndSeqNr=" + Arrays.toString(hashOfDataAndSeqNr) + ", signature=" + Arrays.toString(signature));
return result;
} catch (CryptoException e) {
@ -469,11 +466,13 @@ public class P2PDataStorage implements MessageListener, ConnectionListener {
private boolean checkPublicKeys(ProtectedStorageEntry protectedStorageEntry, boolean isAddOperation) {
boolean result;
if (protectedStorageEntry.getStoragePayload() instanceof MailboxStoragePayload) {
MailboxStoragePayload expirableMailboxStoragePayload = (MailboxStoragePayload) protectedStorageEntry.getStoragePayload();
MailboxStoragePayload payload = (MailboxStoragePayload) protectedStorageEntry.getStoragePayload();
if (isAddOperation)
result = expirableMailboxStoragePayload.senderPubKeyForAddOperation.equals(protectedStorageEntry.ownerPubKey);
result = payload.senderPubKeyForAddOperation != null &&
payload.senderPubKeyForAddOperation.equals(protectedStorageEntry.ownerPubKey);
else
result = expirableMailboxStoragePayload.receiverPubKeyForRemoveOperation.equals(protectedStorageEntry.ownerPubKey);
result = payload.receiverPubKeyForRemoveOperation != null &&
payload.receiverPubKeyForRemoveOperation.equals(protectedStorageEntry.ownerPubKey);
} else {
// TODO We got sometimes a nullpointer at protectedStorageEntry.ownerPubKey
// Probably caused by an exception at deserialization: Offer: Cannot be deserialized.null
@ -482,16 +481,28 @@ public class P2PDataStorage implements MessageListener, ConnectionListener {
protectedStorageEntry.ownerPubKey.equals(protectedStorageEntry.getStoragePayload().getOwnerPubKey());
}
if (!result)
log.error("PublicKey of payload data and ProtectedData are not matching. Consider it might be an attempt of fraud");
if (!result) {
String res1 = "null";
String res2 = "null";
if (protectedStorageEntry != null) {
res1 = protectedStorageEntry.toString();
if (protectedStorageEntry.getStoragePayload() != null && protectedStorageEntry.getStoragePayload().getOwnerPubKey() != null)
res2 = protectedStorageEntry.getStoragePayload().getOwnerPubKey().toString();
}
log.error("PublicKey of payload data and ProtectedData are not matching. protectedStorageEntry=" + res1 +
"protectedStorageEntry.getStoragePayload().getOwnerPubKey()=" + res2);
}
return result;
}
private boolean checkIfStoredDataPubKeyMatchesNewDataPubKey(PublicKey ownerPubKey, ByteArray hashOfData) {
ProtectedStorageEntry storedData = map.get(hashOfData);
boolean result = storedData.ownerPubKey.equals(ownerPubKey);
boolean result = storedData.ownerPubKey != null && storedData.ownerPubKey.equals(ownerPubKey);
if (!result)
log.error("New data entry does not match our stored data. Consider it might be an attempt of fraud");
log.error("New data entry does not match our stored data. storedData.ownerPubKey=" +
(storedData.ownerPubKey != null ? storedData.ownerPubKey.toString() : "null") +
", ownerPubKey=" + ownerPubKey);
return result;
}
@ -499,12 +510,13 @@ public class P2PDataStorage implements MessageListener, ConnectionListener {
private boolean checkIfStoredMailboxDataMatchesNewMailboxData(PublicKey receiversPubKey, ByteArray hashOfData) {
ProtectedStorageEntry storedData = map.get(hashOfData);
if (storedData instanceof ProtectedMailboxStorageEntry) {
ProtectedMailboxStorageEntry storedMailboxData = (ProtectedMailboxStorageEntry) storedData;
ProtectedMailboxStorageEntry entry = (ProtectedMailboxStorageEntry) storedData;
// publicKey is not the same (stored: sender, new: receiver)
boolean result = storedMailboxData.receiversPubKey.equals(receiversPubKey)
&& getHashAsByteArray(storedMailboxData.getStoragePayload()).equals(hashOfData);
boolean result = entry.receiversPubKey.equals(receiversPubKey)
&& getHashAsByteArray(entry.getStoragePayload()).equals(hashOfData);
if (!result)
log.error("New data entry does not match our stored data. Consider it might be an attempt of fraud");
log.error("New data entry does not match our stored data. entry.receiversPubKey=" + entry.receiversPubKey
+ ", receiversPubKey=" + receiversPubKey);
return result;
} else {
@ -533,6 +545,39 @@ public class P2PDataStorage implements MessageListener, ConnectionListener {
return purged;
}
private void printData(String info) {
StringBuilder sb = new StringBuilder("\n\n------------------------------------------------------------\n");
sb.append("Data set " + info + " operation");
// We print the items sorted by hash with the payload class name and id
List<Tuple2<String, ProtectedStorageEntry>> tempList = map.values().stream()
.map(e -> new Tuple2<>(org.bitcoinj.core.Utils.HEX.encode(getHashAsByteArray(e.getStoragePayload()).bytes), e))
.collect(Collectors.toList());
tempList.sort((o1, o2) -> o1.first.compareTo(o2.first));
tempList.stream().forEach(e -> {
final ProtectedStorageEntry storageEntry = e.second;
final StoragePayload storagePayload = storageEntry.getStoragePayload();
final MapValue mapValue = sequenceNumberMap.get(getHashAsByteArray(storagePayload));
sb.append("\n")
.append("Hash=")
.append(e.first)
.append("; Class=")
.append(storagePayload.getClass().getSimpleName())
.append("; SequenceNumbers (Object/Stored)=")
.append(storageEntry.sequenceNumber)
.append(" / ")
.append(mapValue != null ? mapValue.sequenceNr : "null")
.append("; TimeStamp (Object/Stored)=")
.append(storageEntry.creationTimeStamp)
.append(" / ")
.append(mapValue != null ? mapValue.timeStamp : "null")
.append("; Payload=")
.append(StringUtils.abbreviate(storagePayload.toString(), 100).replace("\n", ""));
});
sb.append("\n------------------------------------------------------------\n");
log.debug(sb.toString());
log.info("Data set " + info + " operation: size=" + map.values().size());
}
///////////////////////////////////////////////////////////////////////////////////////////
// Static class

View file

@ -4,6 +4,7 @@ import io.bitsquare.app.Version;
import io.bitsquare.common.crypto.Sig;
import io.bitsquare.p2p.NodeAddress;
import io.bitsquare.p2p.messaging.PrefixedSealedAndSignedMessage;
import io.bitsquare.p2p.peers.BroadcastHandler;
import io.bitsquare.p2p.storage.storageentry.ProtectedStorageEntry;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -39,7 +40,7 @@ public final class MailboxStoragePayload implements StoragePayload {
* senderStoragePublicKey has to be equal to the ownerPubKey of the ProtectedData
*
* @see ProtectedStorageEntry#ownerPubKey
* @see io.bitsquare.p2p.storage.P2PDataStorage#add(ProtectedStorageEntry, NodeAddress)
* @see io.bitsquare.p2p.storage.P2PDataStorage#add(ProtectedStorageEntry, NodeAddress, BroadcastHandler.Listener, boolean)
*/
public transient PublicKey senderPubKeyForAddOperation;
private final byte[] senderPubKeyForAddOperationBytes;
@ -48,7 +49,7 @@ public final class MailboxStoragePayload implements StoragePayload {
* senderStoragePublicKey has to be equal to the ownerPubKey of the ProtectedData
*
* @see ProtectedStorageEntry#ownerPubKey
* @see io.bitsquare.p2p.storage.P2PDataStorage#remove(ProtectedStorageEntry, NodeAddress)
* @see io.bitsquare.p2p.storage.P2PDataStorage#remove(ProtectedStorageEntry, NodeAddress, boolean)
*/
public transient PublicKey receiverPubKeyForRemoveOperation;
private final byte[] receiverPubKeyForRemoveOperationBytes;
@ -69,7 +70,7 @@ public final class MailboxStoragePayload implements StoragePayload {
senderPubKeyForAddOperation = KeyFactory.getInstance(Sig.KEY_ALGO, "BC").generatePublic(new X509EncodedKeySpec(senderPubKeyForAddOperationBytes));
receiverPubKeyForRemoveOperation = KeyFactory.getInstance(Sig.KEY_ALGO, "BC").generatePublic(new X509EncodedKeySpec(receiverPubKeyForRemoveOperationBytes));
} catch (Throwable t) {
log.warn("Exception at readObject: " + t.getMessage());
log.warn("Exception at readObject: " + t.getMessage() + "\nThis= " + this.toString());
}
}

View file

@ -12,5 +12,4 @@
<logger name="com.msopentech.thali.toronionproxy.OnionProxyManagerEventHandler" level="WARN"/>
</configuration>

View file

@ -1,17 +1,16 @@
package io.bitsquare.p2p.seed;
package io.bitsquare.p2p;
import ch.qos.logback.classic.Level;
import com.google.common.annotations.VisibleForTesting;
import io.bitsquare.app.Log;
import io.bitsquare.app.Version;
import io.bitsquare.common.Clock;
import io.bitsquare.common.CommonOptionKeys;
import io.bitsquare.common.UserThread;
import io.bitsquare.common.util.Utilities;
import io.bitsquare.network.OptionKeys;
import io.bitsquare.p2p.NodeAddress;
import io.bitsquare.p2p.P2PService;
import io.bitsquare.p2p.P2PServiceListener;
import io.bitsquare.network.NetworkOptionKeys;
import io.bitsquare.p2p.peers.BanList;
import io.bitsquare.p2p.seed.SeedNodesRepository;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -26,11 +25,13 @@ import java.util.Set;
import static com.google.common.base.Preconditions.checkArgument;
public class SeedNode {
private static final Logger log = LoggerFactory.getLogger(SeedNode.class);
// Previously used seednode class, replaced now by bootstrap module. We keep it here as it was used in tests...
public class DummySeedNode {
private static final Logger log = LoggerFactory.getLogger(DummySeedNode.class);
public static final int MAX_CONNECTIONS_LIMIT = 1000;
public static final int MAX_CONNECTIONS_DEFAULT = 50;
public static final String SEED_NODES_LIST = "seedNodes";
public static final String HELP = "help";
private NodeAddress mySeedNodeAddress = new NodeAddress("localhost:8001");
private int maxConnections = MAX_CONNECTIONS_DEFAULT; // we keep default a higher connection size for seed nodes
private boolean useLocalhost = false;
@ -40,7 +41,7 @@ public class SeedNode {
private final String defaultUserDataDir;
private Level logLevel = Level.WARN;
public SeedNode(String defaultUserDataDir) {
public DummySeedNode(String defaultUserDataDir) {
Log.traceCall("defaultUserDataDir=" + defaultUserDataDir);
this.defaultUserDataDir = defaultUserDataDir;
}
@ -77,34 +78,34 @@ public class SeedNode {
String arg = args[i];
if (arg.startsWith("--"))
arg = arg.substring(2);
if (arg.startsWith(OptionKeys.MY_ADDRESS)) {
arg = arg.substring(OptionKeys.MY_ADDRESS.length() + 1);
if (arg.startsWith(NetworkOptionKeys.MY_ADDRESS)) {
arg = arg.substring(NetworkOptionKeys.MY_ADDRESS.length() + 1);
checkArgument(arg.contains(":") && arg.split(":").length == 2 && arg.split(":")[1].length() > 3, "Wrong program argument: " + arg);
mySeedNodeAddress = new NodeAddress(arg);
log.info("From processArgs: mySeedNodeAddress=" + mySeedNodeAddress);
} else if (arg.startsWith(OptionKeys.NETWORK_ID)) {
arg = arg.substring(OptionKeys.NETWORK_ID.length() + 1);
} else if (arg.startsWith(NetworkOptionKeys.NETWORK_ID)) {
arg = arg.substring(NetworkOptionKeys.NETWORK_ID.length() + 1);
networkId = Integer.parseInt(arg);
log.info("From processArgs: networkId=" + networkId);
checkArgument(networkId > -1 && networkId < 3,
"networkId out of scope (Mainnet = 0, TestNet = 1, Regtest = 2)");
Version.setBtcNetworkId(networkId);
} else if (arg.startsWith(OptionKeys.MAX_CONNECTIONS)) {
arg = arg.substring(OptionKeys.MAX_CONNECTIONS.length() + 1);
} else if (arg.startsWith(NetworkOptionKeys.MAX_CONNECTIONS)) {
arg = arg.substring(NetworkOptionKeys.MAX_CONNECTIONS.length() + 1);
maxConnections = Integer.parseInt(arg);
log.info("From processArgs: maxConnections=" + maxConnections);
checkArgument(maxConnections < MAX_CONNECTIONS_LIMIT, "maxConnections seems to be a bit too high...");
} else if (arg.startsWith(OptionKeys.USE_LOCALHOST)) {
arg = arg.substring(OptionKeys.USE_LOCALHOST.length() + 1);
} else if (arg.startsWith(NetworkOptionKeys.USE_LOCALHOST)) {
arg = arg.substring(NetworkOptionKeys.USE_LOCALHOST.length() + 1);
checkArgument(arg.equals("true") || arg.equals("false"));
useLocalhost = ("true").equals(arg);
log.info("From processArgs: useLocalhost=" + useLocalhost);
} else if (arg.startsWith(io.bitsquare.common.OptionKeys.LOG_LEVEL_KEY)) {
arg = arg.substring(io.bitsquare.common.OptionKeys.LOG_LEVEL_KEY.length() + 1);
} else if (arg.startsWith(CommonOptionKeys.LOG_LEVEL_KEY)) {
arg = arg.substring(CommonOptionKeys.LOG_LEVEL_KEY.length() + 1);
logLevel = Level.toLevel(arg.toUpperCase());
log.info("From processArgs: logLevel=" + logLevel);
} else if (arg.startsWith(OptionKeys.SEED_NODES_LIST)) {
arg = arg.substring(OptionKeys.SEED_NODES_LIST.length() + 1);
} else if (arg.startsWith(SEED_NODES_LIST)) {
arg = arg.substring(SEED_NODES_LIST.length() + 1);
checkArgument(arg.contains(":") && arg.split(":").length > 1 && arg.split(":")[1].length() > 3,
"Wrong program argument " + arg);
List<String> list = Arrays.asList(arg.split(","));
@ -116,8 +117,8 @@ public class SeedNode {
});
log.info("From processArgs: progArgSeedNodes=" + progArgSeedNodes);
progArgSeedNodes.remove(mySeedNodeAddress);
} else if (arg.startsWith(OptionKeys.BAN_LIST)) {
arg = arg.substring(OptionKeys.BAN_LIST.length() + 1);
} else if (arg.startsWith(NetworkOptionKeys.BAN_LIST)) {
arg = arg.substring(NetworkOptionKeys.BAN_LIST.length() + 1);
checkArgument(arg.contains(":") && arg.split(":").length > 1 && arg.split(":")[1].length() > 3,
"Wrong program argument " + arg);
List<String> list = Arrays.asList(arg.split(","));
@ -127,7 +128,7 @@ public class SeedNode {
BanList.add(new NodeAddress(e));
});
log.info("From processArgs: ignoreList=" + list);
} else if (arg.startsWith(OptionKeys.HELP)) {
} else if (arg.startsWith(HELP)) {
log.info(USAGE);
} else {
log.error("Invalid argument. " + arg + "\n" + USAGE);
@ -186,7 +187,7 @@ public class SeedNode {
seedNodesRepository.setNodeAddressToExclude(mySeedNodeAddress);
seedNodeP2PService = new P2PService(seedNodesRepository, mySeedNodeAddress.port, maxConnections,
torDir, useLocalhost, networkId, storageDir, null, new Clock(), null, null);
torDir, useLocalhost, networkId, storageDir, null, null, null, new Clock(), null, null);
seedNodeP2PService.start(false, listener);
}

View file

@ -1,7 +1,6 @@
package io.bitsquare.p2p;
import io.bitsquare.p2p.network.LocalhostNetworkNode;
import io.bitsquare.p2p.seed.SeedNode;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
@ -27,9 +26,9 @@ public class PeerServiceTest {
final boolean useLocalhost = true;
private CountDownLatch latch;
private int sleepTime;
private SeedNode seedNode1, seedNode2, seedNode3;
private DummySeedNode seedNode1, seedNode2, seedNode3;
private Set<NodeAddress> seedNodeAddresses = new HashSet<>();
private List<SeedNode> seedNodes = new ArrayList<>();
private List<DummySeedNode> seedNodes = new ArrayList<>();
private String test_dummy_dir = "test_dummy_dir";
;
@ -81,7 +80,7 @@ public class PeerServiceTest {
int port = 8000 + i;
NodeAddress nodeAddress = new NodeAddress("localhost:" + port);
seedNodeAddresses.add(nodeAddress);
SeedNode seedNode = new SeedNode(test_dummy_dir);
DummySeedNode seedNode = new DummySeedNode(test_dummy_dir);
seedNodes.add(seedNode);
seedNode.createAndStartP2PService(true);
@ -180,7 +179,7 @@ public class PeerServiceTest {
latch = new CountDownLatch(6);
seedNode1 = new SeedNode("test_dummy_dir");
seedNode1 = new DummySeedNode("test_dummy_dir");
seedNode1.createAndStartP2PService(nodeAddress1, MAX_CONNECTIONS, useLocalhost, 2, true, seedNodeAddresses, new P2PServiceListener() {
@Override
public void onRequestingDataCompleted() {
@ -225,7 +224,7 @@ public class PeerServiceTest {
Thread.sleep(500);
seedNode2 = new SeedNode("test_dummy_dir");
seedNode2 = new DummySeedNode("test_dummy_dir");
seedNode2.createAndStartP2PService(nodeAddress2, MAX_CONNECTIONS, useLocalhost, 2, true, seedNodeAddresses, new P2PServiceListener() {
@Override
public void onRequestingDataCompleted() {
@ -277,12 +276,12 @@ public class PeerServiceTest {
log.debug("### start");
LocalhostNetworkNode.setSimulateTorDelayTorNode(0);
LocalhostNetworkNode.setSimulateTorDelayHiddenService(0);
SeedNode seedNode1 = getAndStartSeedNode(8001);
DummySeedNode seedNode1 = getAndStartSeedNode(8001);
log.debug("### seedNode1");
Thread.sleep(100);
log.debug("### seedNode1 100");
Thread.sleep(1000);
SeedNode seedNode2 = getAndStartSeedNode(8002);
DummySeedNode seedNode2 = getAndStartSeedNode(8002);
// authentication:
// node2 -> node1 RequestAuthenticationMessage
@ -466,8 +465,8 @@ public class PeerServiceTest {
shutDownLatch.await();*/
}
private SeedNode getAndStartSeedNode(int port) throws InterruptedException {
SeedNode seedNode = new SeedNode("test_dummy_dir");
private DummySeedNode getAndStartSeedNode(int port) throws InterruptedException {
DummySeedNode seedNode = new DummySeedNode("test_dummy_dir");
latch = new CountDownLatch(1);
seedNode.createAndStartP2PService(new NodeAddress("localhost", port), MAX_CONNECTIONS, useLocalhost, 2, true, seedNodeAddresses, new P2PServiceListener() {

View file

@ -3,7 +3,6 @@ package io.bitsquare.p2p;
import io.bitsquare.common.Clock;
import io.bitsquare.common.crypto.KeyRing;
import io.bitsquare.crypto.EncryptionService;
import io.bitsquare.p2p.seed.SeedNode;
import io.bitsquare.p2p.seed.SeedNodesRepository;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -64,25 +63,25 @@ public class TestUtils {
return result;
}
public static SeedNode getAndStartSeedNode(int port, boolean useLocalhost, Set<NodeAddress> seedNodes) throws InterruptedException {
SeedNode seedNode;
public static DummySeedNode getAndStartSeedNode(int port, boolean useLocalhost, Set<NodeAddress> seedNodes) throws InterruptedException {
DummySeedNode seedNode;
if (useLocalhost) {
seedNodes.add(new NodeAddress("localhost:8001"));
seedNodes.add(new NodeAddress("localhost:8002"));
seedNodes.add(new NodeAddress("localhost:8003"));
sleepTime = 100;
seedNode = new SeedNode(test_dummy_dir);
seedNode = new DummySeedNode(test_dummy_dir);
} else {
seedNodes.add(new NodeAddress("3omjuxn7z73pxoee.onion:8001"));
seedNodes.add(new NodeAddress("j24fxqyghjetgpdx.onion:8002"));
seedNodes.add(new NodeAddress("45367tl6unwec6kw.onion:8003"));
sleepTime = 10000;
seedNode = new SeedNode(test_dummy_dir);
seedNode = new DummySeedNode(test_dummy_dir);
}
CountDownLatch latch = new CountDownLatch(1);
seedNode.createAndStartP2PService(new NodeAddress("localhost", port), SeedNode.MAX_CONNECTIONS_DEFAULT, useLocalhost, 2, true,
seedNode.createAndStartP2PService(new NodeAddress("localhost", port), DummySeedNode.MAX_CONNECTIONS_DEFAULT, useLocalhost, 2, true,
seedNodes, new P2PServiceListener() {
@Override
public void onRequestingDataCompleted() {
@ -139,7 +138,7 @@ public class TestUtils {
}
P2PService p2PService = new P2PService(seedNodesRepository, port, new File("seed_node_" + port), useLocalhost,
2, P2PService.MAX_CONNECTIONS_DEFAULT, new File("dummy"), null, new Clock(), encryptionService, keyRing);
2, P2PService.MAX_CONNECTIONS_DEFAULT, new File("dummy"), null, null, null, new Clock(), encryptionService, keyRing);
p2PService.start(false, new P2PServiceListener() {
@Override
public void onRequestingDataCompleted() {

View file

@ -9,12 +9,8 @@ import io.bitsquare.common.crypto.PubKeyRing;
import io.bitsquare.common.util.Tuple3;
import io.bitsquare.crypto.DecryptedMsgWithPubKey;
import io.bitsquare.crypto.EncryptionService;
import io.bitsquare.p2p.NodeAddress;
import io.bitsquare.p2p.P2PService;
import io.bitsquare.p2p.P2PServiceListener;
import io.bitsquare.p2p.Utils;
import io.bitsquare.p2p.*;
import io.bitsquare.p2p.messaging.*;
import io.bitsquare.p2p.seed.SeedNode;
import io.bitsquare.p2p.seed.SeedNodesRepository;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
@ -140,7 +136,7 @@ public class NetworkStressTest {
/**
* A single seed node that other nodes will contact to request initial data.
*/
private SeedNode seedNode;
private DummySeedNode seedNode;
/**
* The repository of seed nodes used in the test.
*/
@ -271,12 +267,12 @@ public class NetworkStressTest {
UserThread.setExecutor(Executors.newSingleThreadExecutor());
// Create and start the seed node.
seedNode = new SeedNode(testDataDir.toString());
seedNode = new DummySeedNode(testDataDir.toString());
final NodeAddress seedNodeAddress = newSeedNodeAddress();
useLocalhost = seedNodeAddress.hostName.equals("localhost");
final Set<NodeAddress> seedNodes = new HashSet<>(1);
seedNodes.add(seedNodeAddress); // the only seed node in tests
seedNode.createAndStartP2PService(seedNodeAddress, SeedNode.MAX_CONNECTIONS_DEFAULT, useLocalhost,
seedNode.createAndStartP2PService(seedNodeAddress, DummySeedNode.MAX_CONNECTIONS_DEFAULT, useLocalhost,
REGTEST_NETWORK_ID, USE_DETAILED_LOGGING, seedNodes,
new SeedServiceListener(localServicesLatch, localServicesFailed));
print("created seed node");
@ -381,7 +377,7 @@ public class NetworkStressTest {
final EncryptionService peerEncryptionService = new EncryptionService(peerKeyRing);
return new P2PService(seedNodesRepository, port, peerTorDir, useLocalhost,
REGTEST_NETWORK_ID, P2PService.MAX_CONNECTIONS_DEFAULT, peerStorageDir, null, new Clock(), peerEncryptionService, peerKeyRing);
REGTEST_NETWORK_ID, P2PService.MAX_CONNECTIONS_DEFAULT, peerStorageDir, null, null, null, new Clock(), peerEncryptionService, peerKeyRing);
}
// ## TEST SETUP: P2P service listener classes

View file

@ -1,10 +1,10 @@
package io.bitsquare.p2p.routing;
import io.bitsquare.p2p.DummySeedNode;
import io.bitsquare.p2p.NodeAddress;
import io.bitsquare.p2p.P2PService;
import io.bitsquare.p2p.P2PServiceListener;
import io.bitsquare.p2p.network.LocalhostNetworkNode;
import io.bitsquare.p2p.seed.SeedNode;
import org.junit.After;
import org.junit.Before;
import org.junit.Ignore;
@ -31,7 +31,7 @@ public class PeerManagerTest {
private CountDownLatch latch;
private Set<NodeAddress> seedNodes;
private int sleepTime;
private SeedNode seedNode1, seedNode2, seedNode3;
private DummySeedNode seedNode1, seedNode2, seedNode3;
@Before
public void setup() throws InterruptedException {
@ -81,7 +81,7 @@ public class PeerManagerTest {
seedNodes = new HashSet<>();
NodeAddress nodeAddress = new NodeAddress("localhost:8001");
seedNodes.add(nodeAddress);
seedNode1 = new SeedNode("test_dummy_dir");
seedNode1 = new DummySeedNode("test_dummy_dir");
latch = new CountDownLatch(2);
seedNode1.createAndStartP2PService(nodeAddress, MAX_CONNECTIONS, useLocalhost, 2, true,
seedNodes, new P2PServiceListener() {
@ -142,7 +142,7 @@ public class PeerManagerTest {
latch = new CountDownLatch(6);
seedNode1 = new SeedNode("test_dummy_dir");
seedNode1 = new DummySeedNode("test_dummy_dir");
seedNode1.createAndStartP2PService(nodeAddress1, MAX_CONNECTIONS, useLocalhost, 2, true, seedNodes, new P2PServiceListener() {
@Override
public void onRequestingDataCompleted() {
@ -187,7 +187,7 @@ public class PeerManagerTest {
Thread.sleep(500);
seedNode2 = new SeedNode("test_dummy_dir");
seedNode2 = new DummySeedNode("test_dummy_dir");
seedNode2.createAndStartP2PService(nodeAddress2, MAX_CONNECTIONS, useLocalhost, 2, true, seedNodes, new P2PServiceListener() {
@Override
public void onRequestingDataCompleted() {
@ -239,12 +239,12 @@ public class PeerManagerTest {
log.debug("### start");
LocalhostNetworkNode.setSimulateTorDelayTorNode(0);
LocalhostNetworkNode.setSimulateTorDelayHiddenService(0);
SeedNode seedNode1 = getAndStartSeedNode(8001);
DummySeedNode seedNode1 = getAndStartSeedNode(8001);
log.debug("### seedNode1");
Thread.sleep(100);
log.debug("### seedNode1 100");
Thread.sleep(1000);
SeedNode seedNode2 = getAndStartSeedNode(8002);
DummySeedNode seedNode2 = getAndStartSeedNode(8002);
// authentication:
// node2 -> node1 RequestAuthenticationMessage
@ -428,8 +428,8 @@ public class PeerManagerTest {
shutDownLatch.await();*/
}
private SeedNode getAndStartSeedNode(int port) throws InterruptedException {
SeedNode seedNode = new SeedNode("test_dummy_dir");
private DummySeedNode getAndStartSeedNode(int port) throws InterruptedException {
DummySeedNode seedNode = new DummySeedNode("test_dummy_dir");
latch = new CountDownLatch(1);
seedNode.createAndStartP2PService(new NodeAddress("localhost", port), MAX_CONNECTIONS, useLocalhost, 2, true, seedNodes, new P2PServiceListener() {

View file

@ -15,15 +15,14 @@ cp gui/target/shaded.jar "/Users/mk/vm_shared_windows/Bitsquare-$version.jar"
cp gui/target/shaded.jar "/Users/mk/vm_shared_ubuntu14_32bit/Bitsquare-$version.jar"
cp gui/target/shaded.jar "/Users/mk/vm_shared_windows_32bit/Bitsquare-$version.jar"
cp seednode/target/SeedNode.jar "gui/deploy/SeedNode.jar"
cp seednode/target/SeedNode.jar "gui/deploy/SeedNode-$version.jar"
cp jdkfix/target/jdkfix-$version.jar "/Users/mk/vm_shared_ubuntu/jdkfix-$version.jar"
cp jdkfix/target/jdkfix-$version.jar "/Users/mk/vm_shared_windows/jdkfix-$version.jar"
cp jdkfix/target/jdkfix-$version.jar "/Users/mk/vm_shared_ubuntu14_32bit/jdkfix-$version.jar"
cp jdkfix/target/jdkfix-$version.jar "/Users/mk/vm_shared_windows_32bit/jdkfix-$version.jar"
exit
echo "Using $JAVA_HOME"
echo "Using JAVA_HOME: $JAVA_HOME"
$JAVA_HOME/bin/javapackager \
-deploy \
-BjvmOptions=-Xbootclasspath/a:"jdkfix-$version.jar":"../PlugIns/Java.runtime/Contents/Home/jre/lib/ext/jfxrt.jar" \
@ -48,6 +47,4 @@ rm "gui/deploy/Bitsquare.jnlp"
mv "gui/deploy/bundles/Bitsquare-$version.dmg" "gui/deploy/Bitsquare-$version.dmg"
rm -r "gui/deploy/bundles"
mv "gui/deploy/SeedNode.jar" "gui/deploy/SeedNode-$version.jar"
cd package/mac

View file

@ -5,6 +5,8 @@ version="0.4.9"
target_dir="/Users/mk/Documents/__bitsquare/_releases/$version"
src_dir="/Users/mk/Documents/_intellij/bitsquare"
mkdir -p $target_dir
mac="Bitsquare-$version.dmg"
cp "$src_dir/gui/deploy/$mac" "$target_dir/"
cp "$src_dir/gui/deploy/SeedNode-$version.jar" "$target_dir/"
@ -15,7 +17,7 @@ cp "/Users/mk/vm_shared_ubuntu14_32bit/$deb32" "$target_dir/"
deb64="Bitsquare-64bit-$version.deb"
cp "/Users/mk/vm_shared_ubuntu/$deb64" "$target_dir/"
exe="Bitsquare.exe"
exe="Bitsquare-$version.exe"
win32="Bitsquare-32bit-$version.exe"
cp "/Users/mk/vm_shared_windows_32bit/bundles/$exe" "$target_dir/$win32"
win64="Bitsquare-64bit-$version.exe"

View file

@ -0,0 +1,24 @@
:: edit iss file -> AppVersion
:: Copy gui/deploy.Bitsquare.jar file from mac build to windows
:: edit -> -BappVersion and -srcfiles
:: 64 bit build
:: Needs Inno Setup 5 or later (http://www.jrsoftware.org/isdl.php)
SET version=0.4.9
SET jdk=C:\Program Files\Java\jdk1.8.0_92
SET outdir=\\VBOXSVR\vm_shared_windows_32bit
call "%jdk%\bin\javapackager.exe" -deploy ^
-BappVersion="%version%" ^
-native exe ^
-name Bitsquare ^
-title Bitsquare ^
-vendor Bitsquare ^
-outdir %outdir% ^
-appclass io.bitsquare.app.BitsquareAppMain ^
-srcfiles %outdir%\Bitsquare-%version%.jar ^
-outfile Bitsquare ^
-Bruntime="%jdk%\jre" ^
-BjvmProperties=-Djava.net.preferIPv4Stack=true

View file

@ -1,4 +1,4 @@
:: edit iss file -> AppVersion
:: edit iss file -> AppVersion
:: Copy gui/deploy.Bitsquare.jar file from mac build to windows
:: edit -> -BappVersion and -srcfiles
@ -6,12 +6,13 @@
:: 64 bit build
:: Needs Inno Setup 5 or later (http://www.jrsoftware.org/isdl.php)
:: 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
SET jdk=C:\Program Files\Java\jdk1.8.0_92
SET outdir=\\VBOXSVR\vm_shared_windows
call "%jdk%\bin\javapackager.exe" -deploy ^
-BjvmOptions=-Xbootclasspath/a:^"jdkfix-0.4.9.jar^";^"..\runtime\lib\ext\jfxrt.jar^" ^
-BappVersion="%version%" ^
-native exe ^
-name Bitsquare ^
@ -19,12 +20,7 @@ call "%jdk%\bin\javapackager.exe" -deploy ^
-vendor Bitsquare ^
-outdir %outdir% ^
-appclass io.bitsquare.app.BitsquareAppMain ^
-srcfiles "%outdir%\Bitsquare-%version%.jar;%outdir%\jdkfix-%version%.jar" ^
-srcfiles %outdir%\Bitsquare-%version%.jar ^
-outfile Bitsquare ^
-Bruntime="%jdk%\jre" ^
-BjvmProperties=-Djava.net.preferIPv4Stack=true
:: -BjvmOptions=-verbose:class
:: those works
:: java -Xbootclasspath/a:^"jdkfix-0.4.9.jar^";^"..\runtime\lib\ext\jfxrt.jar^" -jar Bitsquare-0.4.9.jar
:: java -Xbootclasspath/a:"jdkfix-0.4.9.jar";"..\runtime\lib\ext\jfxrt.jar" -jar Bitsquare-0.4.9.jar
-BjvmProperties=-Djava.net.preferIPv4Stack=true

View file

@ -1,13 +0,0 @@
cd ..\..\
mkdir gui\deploy
:: edit iss file -> AppVersion
:: Copy gui/deploy.Bitsquare.jar file from mac build to windows
:: edit -> -BappVersion and -srcfiles
:: 32 bit build
:: Needs Inno Setup 5 or later (http://www.jrsoftware.org/isdl.php)
call "C:\Program Files\Java\jdk1.8.0_92\bin\javapackager.exe" -deploy -BappVersion=0.4.9 -native exe -name Bitsquare -title Bitsquare -vendor Bitsquare -outdir "\\VBOXSVR\vm_shared_windows_32bit" -appclass io.bitsquare.app.BitsquareAppMain -srcfiles "\\VBOXSVR\vm_shared_windows_32bit\Bitsquare-0.4.9.jar" -outfile Bitsquare -Bruntime="C:\Program Files\Java\jdk1.8.0_92\jre" -BjvmProperties=-Djava.net.preferIPv4Stack=true
cd package\windows

View file

@ -44,8 +44,10 @@
<module>jtorctl</module>
<module>jtorproxy</module>
<module>network</module>
<module>seednode</module>
<module>gui</module>
<module>headless</module>
<module>seednode</module>
<module>monitor</module>
<module>jdkfix</module>
</modules>

Some files were not shown because too many files have changed in this diff Show more