junit
diff --git a/src/main/java/META-INF/MANIFEST.MF b/src/main/java/META-INF/MANIFEST.MF
index de29527d5c..4a577d5aa6 100644
--- a/src/main/java/META-INF/MANIFEST.MF
+++ b/src/main/java/META-INF/MANIFEST.MF
@@ -1,3 +1,3 @@
Manifest-Version: 1.0
-Main-Class: io.bitsquare.Relay
+Main-Class: io.bitsquare.BootstrapNode_
diff --git a/src/main/java/io/bitsquare/BitSquare.java b/src/main/java/io/bitsquare/BitSquare.java
index 559a855de8..710f8d6f23 100644
--- a/src/main/java/io/bitsquare/BitSquare.java
+++ b/src/main/java/io/bitsquare/BitSquare.java
@@ -38,7 +38,7 @@ public class BitSquare extends Application
{
private static final Logger log = LoggerFactory.getLogger(BitSquare.class);
- public static boolean fillFormsWithDummyData = false;
+ public static boolean fillFormsWithDummyData = true;
private static String APP_NAME = "bitsquare";
private static Stage primaryStage;
diff --git a/src/main/java/io/bitsquare/Relay.java b/src/main/java/io/bitsquare/Relay.java
deleted file mode 100644
index 5e0e0521df..0000000000
--- a/src/main/java/io/bitsquare/Relay.java
+++ /dev/null
@@ -1,69 +0,0 @@
-package io.bitsquare;
-
-import java.io.IOException;
-import javafx.application.Application;
-import javafx.stage.Stage;
-import net.tomp2p.p2p.Peer;
-import net.tomp2p.p2p.PeerMaker;
-import net.tomp2p.peers.Number160;
-import net.tomp2p.peers.PeerAddress;
-import net.tomp2p.peers.PeerMapChangeListener;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-
-public class Relay extends Application
-{
- private static final Logger log = LoggerFactory.getLogger(Relay.class);
- private static final Number160 ID = Number160.createHash(1);
-
- private static Peer masterPeer = null;
- private static int port;
-
- public static void main(String[] args)
- {
- if (args != null && args.length == 1)
- {
- port = new Integer(args[0]);
- }
- else
- {
- port = 5001;
- }
-
- launch(args);
- }
-
- @Override
- public void start(Stage primaryStage) throws IOException
- {
- log.trace("Startup: start");
- if (masterPeer == null)
- {
- masterPeer = new PeerMaker(ID).setPorts(port).makeAndListen();
- // masterPeer = new PeerMaker(ID).setPorts(port).setBagSize(100).makeAndListen(); // setBagSize cause sync problems...
- masterPeer.getBroadcastRPC().getConnectionBean().getConnectionReservation().reserve(3).awaitUninterruptibly();
- masterPeer.getConnectionHandler().getPeerBean().getPeerMap().addPeerMapChangeListener(new PeerMapChangeListener()
- {
- @Override
- public void peerInserted(PeerAddress peerAddress)
- {
- log.info("peerInserted " + peerAddress);
- }
-
- @Override
- public void peerRemoved(PeerAddress peerAddress)
- {
- log.info("peerRemoved " + peerAddress);
- }
-
- @Override
- public void peerUpdated(PeerAddress peerAddress)
- {
- log.info("peerUpdated " + peerAddress);
- }
- });
- }
- }
-
-}
diff --git a/src/main/java/io/bitsquare/SeedNode.java b/src/main/java/io/bitsquare/SeedNode.java
new file mode 100644
index 0000000000..eb035c5001
--- /dev/null
+++ b/src/main/java/io/bitsquare/SeedNode.java
@@ -0,0 +1,174 @@
+package io.bitsquare;
+
+import io.bitsquare.msg.SeedNodeAddress;
+import java.util.List;
+import net.tomp2p.dht.PeerBuilderDHT;
+import net.tomp2p.futures.BaseFuture;
+import net.tomp2p.futures.BaseFutureListener;
+import net.tomp2p.nat.PeerBuilderNAT;
+import net.tomp2p.nat.PeerNAT;
+import net.tomp2p.p2p.Peer;
+import net.tomp2p.p2p.PeerBuilder;
+import net.tomp2p.peers.Number160;
+import net.tomp2p.peers.PeerAddress;
+import net.tomp2p.peers.PeerMapChangeListener;
+import net.tomp2p.peers.PeerStatatistic;
+import net.tomp2p.relay.FutureRelay;
+import net.tomp2p.relay.RelayRPC;
+import net.tomp2p.tracker.PeerBuilderTracker;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Well known node which is reachable for all peers for bootstrapping.
+ * There will be several SeedNodes running on several servers.
+ *
+ * TODO: Alternative bootstrap methods will follow later (save locally list of known nodes reported form other peers,...)
+ */
+public class SeedNode
+{
+ private static final Logger log = LoggerFactory.getLogger(SeedNode.class);
+
+ private static final List staticSedNodeAddresses = SeedNodeAddress.StaticSeedNodeAddresses.getAllSeedNodeAddresses();
+
+ /**
+ * @param args If no args passed we use localhost, otherwise the param is used as index for selecting an address from seedNodeAddresses
+ * @throws Exception
+ */
+ public static void main(String[] args)
+ {
+ int index = 0;
+ SeedNode seedNode = new SeedNode();
+ if (args.length > 0)
+ {
+ // use host index passes as param
+ int param = Integer.valueOf(args[0]);
+ if (param < staticSedNodeAddresses.size())
+ index = param;
+ }
+ try
+ {
+ seedNode.startupUsingAddress(new SeedNodeAddress(staticSedNodeAddresses.get(index)));
+ } catch (Exception e)
+ {
+ e.printStackTrace();
+ log.error(e.toString());
+ }
+ }
+
+
+ ///////////////////////////////////////////////////////////////////////////////////////////
+ // Constructor
+ ///////////////////////////////////////////////////////////////////////////////////////////
+
+
+ public SeedNode()
+ {
+ }
+
+
+ ///////////////////////////////////////////////////////////////////////////////////////////
+ // Public Methods
+ ///////////////////////////////////////////////////////////////////////////////////////////
+
+ public void startupUsingAddress(SeedNodeAddress seedNodeAddress)
+ {
+ try
+ {
+ Peer peer = new PeerBuilder(Number160.createHash(seedNodeAddress.getId())).ports(seedNodeAddress.getPort()).start();
+
+ // Need to add all features the clients will use (otherwise msg type is UNKNOWN_ID)
+ new PeerBuilderDHT(peer).start();
+ PeerNAT nodeBehindNat = new PeerBuilderNAT(peer).start();
+ new RelayRPC(peer);
+ new PeerBuilderTracker(peer);
+ nodeBehindNat.startSetupRelay(new FutureRelay());
+
+ log.debug("Peer started. " + peer.peerAddress());
+
+ peer.peerBean().peerMap().addPeerMapChangeListener(new PeerMapChangeListener()
+ {
+ @Override
+ public void peerInserted(PeerAddress peerAddress, boolean verified)
+ {
+ log.debug("Peer inserted: peerAddress=" + peerAddress + ", verified=" + verified);
+ }
+
+ @Override
+ public void peerRemoved(PeerAddress peerAddress, PeerStatatistic peerStatistics)
+ {
+ log.debug("Peer removed: peerAddress=" + peerAddress + ", peerStatistics=" + peerStatistics);
+ }
+
+ @Override
+ public void peerUpdated(PeerAddress peerAddress, PeerStatatistic peerStatistics)
+ {
+ log.debug("Peer updated: peerAddress=" + peerAddress + ", peerStatistics=" + peerStatistics);
+ }
+ });
+
+ // We keep server in endless loop
+ for (; ; )
+ {
+ // Optional pinging
+ boolean pingPeers = false;
+ if (pingPeers)
+ {
+ for (PeerAddress peerAddress : peer.peerBean().peerMap().all())
+ {
+ BaseFuture future = peer.ping().peerAddress(peerAddress).tcpPing().start();
+ future.addListener(new BaseFutureListener()
+ {
+ @Override
+ public void operationComplete(BaseFuture future) throws Exception
+ {
+ if (future.isSuccess())
+ {
+ log.debug("peer online (TCP):" + peerAddress);
+ }
+ else
+ {
+ log.debug("offline " + peerAddress);
+ }
+ }
+
+ @Override
+ public void exceptionCaught(Throwable t) throws Exception
+ {
+ log.error("exceptionCaught " + t);
+ }
+ });
+
+ future = peer.ping().peerAddress(peerAddress).start();
+ future.addListener(new BaseFutureListener()
+ {
+ @Override
+ public void operationComplete(BaseFuture future) throws Exception
+ {
+ if (future.isSuccess())
+ {
+ log.debug("peer online (UDP):" + peerAddress);
+ }
+ else
+ {
+ log.debug("offline " + peerAddress);
+ }
+ }
+
+ @Override
+ public void exceptionCaught(Throwable t) throws Exception
+ {
+ log.error("exceptionCaught " + t);
+ }
+ });
+ }
+ Thread.sleep(1500);
+ }
+ }
+ } catch (Exception e)
+ {
+ log.error("Exception: " + e);
+ }
+ }
+
+}
diff --git a/src/main/java/io/bitsquare/btc/BitSquareWallet.java b/src/main/java/io/bitsquare/btc/BitSquareWallet.java
index 9f35712e2e..cbcc1d4621 100644
--- a/src/main/java/io/bitsquare/btc/BitSquareWallet.java
+++ b/src/main/java/io/bitsquare/btc/BitSquareWallet.java
@@ -22,5 +22,4 @@ class BitSquareWallet extends Wallet implements Serializable
super(params, keyCrypter);
}
-
}
diff --git a/src/main/java/io/bitsquare/btc/WalletFacade.java b/src/main/java/io/bitsquare/btc/WalletFacade.java
index 31312cbb3c..f331d9ad84 100644
--- a/src/main/java/io/bitsquare/btc/WalletFacade.java
+++ b/src/main/java/io/bitsquare/btc/WalletFacade.java
@@ -190,11 +190,9 @@ public class WalletFacade
}
}
-
public void shutDown()
{
wallet.removeEventListener(walletEventListener);
-
walletAppKit.stopAsync();
}
@@ -596,6 +594,7 @@ public class WalletFacade
Wallet.SendRequest sendRequest = Wallet.SendRequest.forTx(tx);
// we don't allow spending of unconfirmed tx as with fake registrations we would open up doors for spam and market manipulation with fake offers
+ // so set includePending to false
sendRequest.coinSelector = new AddressBasedCoinSelector(params, getRegistrationAddressInfo(), false);
sendRequest.changeAddress = getRegistrationAddressInfo().getAddress();
Wallet.SendResult sendResult = wallet.sendCoins(sendRequest);
@@ -677,6 +676,8 @@ public class WalletFacade
}
+ // TODO: Trade process - use P2SH instead and optimize data exchange
+
///////////////////////////////////////////////////////////////////////////////////////////
// Trade process
///////////////////////////////////////////////////////////////////////////////////////////
diff --git a/src/main/java/io/bitsquare/di/BitSquareModule.java b/src/main/java/io/bitsquare/di/BitSquareModule.java
index 37a971b055..ac28e5fbf4 100644
--- a/src/main/java/io/bitsquare/di/BitSquareModule.java
+++ b/src/main/java/io/bitsquare/di/BitSquareModule.java
@@ -15,6 +15,7 @@ import io.bitsquare.btc.FeePolicy;
import io.bitsquare.btc.WalletFacade;
import io.bitsquare.crypto.CryptoFacade;
import io.bitsquare.msg.MessageFacade;
+import io.bitsquare.msg.SeedNodeAddress;
import io.bitsquare.settings.Settings;
import io.bitsquare.storage.Storage;
import io.bitsquare.trade.Trading;
@@ -52,6 +53,11 @@ public class BitSquareModule extends AbstractModule
//bind(String.class).annotatedWith(Names.named("networkType")).toInstance(WalletFacade.TEST_NET);
bind(NetworkParameters.class).toProvider(NetworkParametersProvider.class).asEagerSingleton();
bind(BitSquareWalletAppKit.class).toProvider(BitSquareWalletAppKitProvider.class).asEagerSingleton();
+
+ // bind(Boolean.class).annotatedWith(Names.named("useDiskStorage")).toInstance(new Boolean(true));
+ bind(Boolean.class).annotatedWith(Names.named("useDiskStorage")).toInstance(new Boolean(false));
+ bind(SeedNodeAddress.StaticSeedNodeAddresses.class).annotatedWith(Names.named("defaultSeedNode")).toInstance(SeedNodeAddress.StaticSeedNodeAddresses.LOCALHOST);
+ // bind(SeedNodeAddress.StaticSeedNodeAddresses.class).annotatedWith(Names.named("defaultSeedNode")).toInstance(SeedNodeAddress.StaticSeedNodeAddresses.DIGITAL_OCEAN);
}
}
diff --git a/src/main/java/io/bitsquare/gui/MainController.java b/src/main/java/io/bitsquare/gui/MainController.java
index c15d72487d..d1cf7a388f 100644
--- a/src/main/java/io/bitsquare/gui/MainController.java
+++ b/src/main/java/io/bitsquare/gui/MainController.java
@@ -11,6 +11,7 @@ import io.bitsquare.gui.orders.OrdersController;
import io.bitsquare.gui.util.Icons;
import io.bitsquare.gui.util.Transitions;
import io.bitsquare.locale.Localisation;
+import io.bitsquare.msg.BootstrapListener;
import io.bitsquare.msg.MessageFacade;
import io.bitsquare.storage.Storage;
import io.bitsquare.trade.Direction;
@@ -176,8 +177,24 @@ public class MainController implements Initializable, NavigationController
private void init()
{
+ messageFacade.init(new BootstrapListener()
+ {
+ @Override
+ public void onCompleted()
+ {
+ messageFacadeInited();
+ }
- messageFacade.init();
+ @Override
+ public void onFailed(Throwable throwable)
+ {
+
+ }
+ });
+ }
+
+ private void messageFacadeInited()
+ {
trading.addTakeOfferRequestListener(this::onTakeOfferRequested);
diff --git a/src/main/java/io/bitsquare/gui/arbitrators/overview/ArbitratorOverviewController.java b/src/main/java/io/bitsquare/gui/arbitrators/overview/ArbitratorOverviewController.java
index 3220ff867d..7013f98054 100644
--- a/src/main/java/io/bitsquare/gui/arbitrators/overview/ArbitratorOverviewController.java
+++ b/src/main/java/io/bitsquare/gui/arbitrators/overview/ArbitratorOverviewController.java
@@ -26,9 +26,14 @@ import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.Pane;
import javafx.stage.Stage;
import javax.inject.Inject;
-import net.tomp2p.peers.Number160;
+import net.tomp2p.peers.Number640;
import net.tomp2p.storage.Data;
+/**
+ * TODO remove tomp2p dependencies
+ * import net.tomp2p.peers.Number160;
+ * import net.tomp2p.storage.Data;
+ */
@SuppressWarnings({"ALL", "UnusedParameters"})
public class ArbitratorOverviewController implements Initializable, ChildController, NavigationController, ArbitratorListener
{
@@ -135,7 +140,7 @@ public class ArbitratorOverviewController implements Initializable, ChildControl
}
@Override
- public void onArbitratorsReceived(Map dataMap, boolean success)
+ public void onArbitratorsReceived(Map dataMap, boolean success)
{
if (success && dataMap != null)
{
@@ -145,7 +150,7 @@ public class ArbitratorOverviewController implements Initializable, ChildControl
{
try
{
- Object arbitratorDataObject = arbitratorData.getObject();
+ Object arbitratorDataObject = arbitratorData.object();
if (arbitratorDataObject instanceof Arbitrator)
{
Arbitrator arbitrator = (Arbitrator) arbitratorDataObject;
diff --git a/src/main/java/io/bitsquare/gui/arbitrators/registration/ArbitratorRegistrationController.java b/src/main/java/io/bitsquare/gui/arbitrators/registration/ArbitratorRegistrationController.java
index a26e1d25a3..769349cf8e 100644
--- a/src/main/java/io/bitsquare/gui/arbitrators/registration/ArbitratorRegistrationController.java
+++ b/src/main/java/io/bitsquare/gui/arbitrators/registration/ArbitratorRegistrationController.java
@@ -22,8 +22,8 @@ import io.bitsquare.msg.MessageFacade;
import io.bitsquare.storage.Storage;
import io.bitsquare.user.Arbitrator;
import io.bitsquare.user.Reputation;
+import io.bitsquare.user.User;
import io.bitsquare.util.DSAKeyUtil;
-import java.io.IOException;
import java.math.BigInteger;
import java.net.URL;
import java.util.*;
@@ -48,6 +48,7 @@ public class ArbitratorRegistrationController implements Initializable, ChildCon
private final Storage storage;
private final WalletFacade walletFacade;
private final MessageFacade messageFacade;
+ private User user;
private Arbitrator arbitrator = new Arbitrator();
private ArbitratorProfileController arbitratorProfileController;
private boolean isEditMode;
@@ -92,11 +93,12 @@ public class ArbitratorRegistrationController implements Initializable, ChildCon
///////////////////////////////////////////////////////////////////////////////////////////
@Inject
- private ArbitratorRegistrationController(Storage storage, WalletFacade walletFacade, MessageFacade messageFacade)
+ private ArbitratorRegistrationController(Storage storage, WalletFacade walletFacade, MessageFacade messageFacade, User user)
{
this.storage = storage;
this.walletFacade = walletFacade;
this.messageFacade = messageFacade;
+ this.user = user;
}
@@ -338,13 +340,7 @@ public class ArbitratorRegistrationController implements Initializable, ChildCon
}
}
- try
- {
- messageFacade.addArbitrator(arbitrator);
- } catch (IOException e)
- {
- e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates.
- }
+ messageFacade.addArbitrator(arbitrator);
}
@FXML
@@ -474,7 +470,7 @@ public class ArbitratorRegistrationController implements Initializable, ChildCon
minArbitrationFeeTextField);
String pubKeyAsHex = walletFacade.getArbitratorDepositAddressInfo().getPubKeyAsHexString();
- String messagePubKeyAsHex = DSAKeyUtil.getHexStringFromPublicKey(messageFacade.getPubKey());
+ String messagePubKeyAsHex = DSAKeyUtil.getHexStringFromPublicKey(user.getMessagePublicKey());
String name = nameTextField.getText();
double maxTradeVolume = BitSquareConverter.stringToDouble(maxTradeVolumeTextField.getText());
diff --git a/src/main/java/io/bitsquare/gui/market/createOffer/CreateOfferController.java b/src/main/java/io/bitsquare/gui/market/createOffer/CreateOfferController.java
index aa4e71a636..efb401a1cb 100644
--- a/src/main/java/io/bitsquare/gui/market/createOffer/CreateOfferController.java
+++ b/src/main/java/io/bitsquare/gui/market/createOffer/CreateOfferController.java
@@ -205,11 +205,9 @@ public class CreateOfferController implements Initializable, ChildController, Hi
return;
}
- log.debug("create offer pubkey " + user.getMessagePubKeyAsHex());
-
if (user.getCurrentBankAccount() != null)
{
- offer = new Offer(user.getMessagePubKeyAsHex(),
+ offer = new Offer(user.getMessagePublicKey(),
direction,
BitSquareConverter.stringToDouble(priceTextField.getText()),
BtcFormatter.stringValueToSatoshis(amountTextField.getText()),
diff --git a/src/main/java/io/bitsquare/gui/market/createOffer/CreateOfferView.fxml b/src/main/java/io/bitsquare/gui/market/createOffer/CreateOfferView.fxml
index 3dde43564d..a408b77706 100644
--- a/src/main/java/io/bitsquare/gui/market/createOffer/CreateOfferView.fxml
+++ b/src/main/java/io/bitsquare/gui/market/createOffer/CreateOfferView.fxml
@@ -42,12 +42,13 @@
-
-
+
@@ -67,17 +68,17 @@
-
-
+
+
+ GridPane.rowIndex="14" GridPane.rowSpan="2" GridPane.valignment="TOP">
-
+
-
+
diff --git a/src/main/java/io/bitsquare/gui/market/orderbook/OrderBookController.java b/src/main/java/io/bitsquare/gui/market/orderbook/OrderBookController.java
index 031ad6d4e9..d754b86c77 100644
--- a/src/main/java/io/bitsquare/gui/market/orderbook/OrderBookController.java
+++ b/src/main/java/io/bitsquare/gui/market/orderbook/OrderBookController.java
@@ -28,7 +28,6 @@ import io.bitsquare.trade.Offer;
import io.bitsquare.trade.orderbook.OrderBook;
import io.bitsquare.trade.orderbook.OrderBookFilter;
import io.bitsquare.user.User;
-import io.bitsquare.util.DSAKeyUtil;
import io.bitsquare.util.Utilities;
import java.math.BigInteger;
import java.net.URL;
@@ -304,10 +303,6 @@ public class OrderBookController implements Initializable, ChildController
{
user.setAccountID(walletFacade.getRegistrationAddressInfo().toString());
}
- if (messageFacade != null && messageFacade.getPubKey() != null)
- {
- user.setMessagePubKeyAsHex(DSAKeyUtil.getHexStringFromPublicKey(messageFacade.getPubKey()));
- }
storage.write(user.getClass().getName(), user);
} catch (InsufficientMoneyException e1)
@@ -437,7 +432,7 @@ public class OrderBookController implements Initializable, ChildController
Image icon;
Offer offer = orderBookListItem.getOffer();
- if (offer.getMessagePubKeyAsHex().equals(user.getMessagePubKeyAsHex()))
+ if (offer.getMessagePublicKey().equals(user.getMessagePublicKey()))
{
icon = Icons.getIconImage(Icons.REMOVE);
title = "Remove";
diff --git a/src/main/java/io/bitsquare/gui/market/trade/TakerOfferController.java b/src/main/java/io/bitsquare/gui/market/trade/TakerOfferController.java
index 3dff68f208..9911690fec 100644
--- a/src/main/java/io/bitsquare/gui/market/trade/TakerOfferController.java
+++ b/src/main/java/io/bitsquare/gui/market/trade/TakerOfferController.java
@@ -191,7 +191,7 @@ public class TakerOfferController implements Initializable, ChildController
public void onBankTransferInited(String tradeId)
{
setTradeId(tradeId);
- headLineLabel.setText("Bank transfer inited");
+ headLineLabel.setText("Bank transfer initialised");
infoLabel.setText("Check your bank account and continue \n" + "when you have received the money.");
receivedFiatButton.setDisable(false);
}
diff --git a/src/main/java/io/bitsquare/gui/orders/pending/PendingTradeView.fxml b/src/main/java/io/bitsquare/gui/orders/pending/PendingTradeView.fxml
index af014b70f4..ade525b580 100644
--- a/src/main/java/io/bitsquare/gui/orders/pending/PendingTradeView.fxml
+++ b/src/main/java/io/bitsquare/gui/orders/pending/PendingTradeView.fxml
@@ -118,7 +118,7 @@
-
diff --git a/src/main/java/io/bitsquare/gui/settings/SettingsController.java b/src/main/java/io/bitsquare/gui/settings/SettingsController.java
index 1b36a71b4b..e759807cf6 100644
--- a/src/main/java/io/bitsquare/gui/settings/SettingsController.java
+++ b/src/main/java/io/bitsquare/gui/settings/SettingsController.java
@@ -139,7 +139,7 @@ public class SettingsController implements Initializable, ChildController, Navig
if (settings.getAcceptedArbitrators().isEmpty())
{
String pubKeyAsHex = Utils.bytesToHexString(new ECKey().getPubKey());
- String messagePubKeyAsHex = DSAKeyUtil.getHexStringFromPublicKey(messageFacade.getPubKey());
+ String messagePubKeyAsHex = DSAKeyUtil.getHexStringFromPublicKey(user.getMessagePublicKey());
List languages = new ArrayList<>();
languages.add(LanguageUtil.getDefaultLanguageLocale());
List arbitrationMethods = new ArrayList<>();
@@ -168,13 +168,7 @@ public class SettingsController implements Initializable, ChildController, Navig
settings.addAcceptedArbitrator(arbitrator);
storage.write(settings.getClass().getName(), settings);
- try
- {
- messageFacade.addArbitrator(arbitrator);
- } catch (IOException e)
- {
- e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates.
- }
+ messageFacade.addArbitrator(arbitrator);
}
}
diff --git a/src/main/java/io/bitsquare/msg/BootstrapListener.java b/src/main/java/io/bitsquare/msg/BootstrapListener.java
new file mode 100644
index 0000000000..6fc67c4b80
--- /dev/null
+++ b/src/main/java/io/bitsquare/msg/BootstrapListener.java
@@ -0,0 +1,9 @@
+package io.bitsquare.msg;
+
+public interface BootstrapListener
+{
+ public void onCompleted();
+
+ public void onFailed(Throwable throwable);
+
+}
diff --git a/src/main/java/io/bitsquare/msg/BootstrappedPeerFactory.java b/src/main/java/io/bitsquare/msg/BootstrappedPeerFactory.java
new file mode 100644
index 0000000000..167ce3a0c1
--- /dev/null
+++ b/src/main/java/io/bitsquare/msg/BootstrappedPeerFactory.java
@@ -0,0 +1,329 @@
+package io.bitsquare.msg;
+
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.SettableFuture;
+import java.io.IOException;
+import java.net.InetAddress;
+import java.security.KeyPair;
+import javax.annotation.concurrent.Immutable;
+import net.tomp2p.connection.Ports;
+import net.tomp2p.dht.PeerBuilderDHT;
+import net.tomp2p.dht.PeerDHT;
+import net.tomp2p.dht.StorageLayer;
+import net.tomp2p.futures.BaseFuture;
+import net.tomp2p.futures.BaseFutureListener;
+import net.tomp2p.futures.FutureBootstrap;
+import net.tomp2p.futures.FutureDiscover;
+import net.tomp2p.nat.FutureNAT;
+import net.tomp2p.nat.PeerBuilderNAT;
+import net.tomp2p.nat.PeerNAT;
+import net.tomp2p.p2p.Peer;
+import net.tomp2p.p2p.PeerBuilder;
+import net.tomp2p.peers.Number160;
+import net.tomp2p.peers.PeerAddress;
+import net.tomp2p.peers.PeerMapChangeListener;
+import net.tomp2p.peers.PeerStatatistic;
+import net.tomp2p.relay.DistributedRelay;
+import net.tomp2p.relay.FutureRelay;
+import net.tomp2p.storage.Storage;
+import org.jetbrains.annotations.NotNull;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Creates a DHT peer and bootstrap to a seed node
+ */
+@Immutable
+public class BootstrappedPeerFactory
+{
+ private static final Logger log = LoggerFactory.getLogger(BootstrappedPeerFactory.class);
+
+ private final KeyPair keyPair;
+ private final Storage storage;
+ private final SeedNodeAddress seedNodeAddress;
+
+ private final SettableFuture settableFuture = SettableFuture.create();
+
+
+ ///////////////////////////////////////////////////////////////////////////////////////////
+ // Constructor
+ ///////////////////////////////////////////////////////////////////////////////////////////
+
+ /**
+ * We use localhost as default seed node
+ */
+ public BootstrappedPeerFactory(@NotNull KeyPair keyPair, @NotNull Storage storage)
+ {
+ this(keyPair, storage, new SeedNodeAddress(SeedNodeAddress.StaticSeedNodeAddresses.LOCALHOST));
+ }
+
+ public BootstrappedPeerFactory(@NotNull KeyPair keyPair, @NotNull Storage storage, @NotNull SeedNodeAddress seedNodeAddress)
+ {
+ this.keyPair = keyPair;
+ this.storage = storage;
+ this.seedNodeAddress = seedNodeAddress;
+ }
+
+
+ ///////////////////////////////////////////////////////////////////////////////////////////
+ // Public methods
+ ///////////////////////////////////////////////////////////////////////////////////////////
+
+ public ListenableFuture start()
+ {
+ try
+ {
+ int randomPort = new Ports().tcpPort();
+ Peer peer = new PeerBuilder(keyPair).ports(randomPort).start();
+ PeerDHT peerDHT = new PeerBuilderDHT(peer).storageLayer(new StorageLayer(storage)).start();
+
+ peer.peerBean().peerMap().addPeerMapChangeListener(new PeerMapChangeListener()
+ {
+ @Override
+ public void peerInserted(PeerAddress peerAddress, boolean verified)
+ {
+ log.debug("Peer inserted: peerAddress=" + peerAddress + ", verified=" + verified);
+
+ /* NavigableSet closePeers = peer.peerBean().peerMap().closePeers(2);
+ log.debug("closePeers size = " + closePeers.size());
+ log.debug("closePeers = " + closePeers);
+ closePeers.forEach(e -> log.debug("forEach: " + e.toString()));
+
+ List allPeers = peer.peerBean().peerMap().all();
+ log.debug("allPeers size = " + allPeers.size());
+ log.debug("allPeers = " + allPeers);
+ allPeers.forEach(e -> log.debug("forEach: " + e.toString()));*/
+ }
+
+ @Override
+ public void peerRemoved(PeerAddress peerAddress, PeerStatatistic peerStatistics)
+ {
+ log.debug("Peer removed: peerAddress=" + peerAddress + ", peerStatistics=" + peerStatistics);
+ }
+
+ @Override
+ public void peerUpdated(PeerAddress peerAddress, PeerStatatistic peerStatistics)
+ {
+ // log.debug("Peer updated: peerAddress=" + peerAddress + ", peerStatistics=" + peerStatistics);
+ }
+ });
+
+ discover(peerDHT);
+ } catch (IOException e)
+ {
+ log.error("Exception: " + e);
+ settableFuture.setException(e);
+ }
+
+ return settableFuture;
+ }
+
+ private void discover(PeerDHT peerDHT)
+ {
+ try
+ {
+ PeerAddress bootstrapAddress = new PeerAddress(Number160.createHash(seedNodeAddress.getId()),
+ InetAddress.getByName(seedNodeAddress.getIp()),
+ seedNodeAddress.getPort(),
+ seedNodeAddress.getPort());
+ // Check if peer is reachable from outside
+ FutureDiscover futureDiscover = peerDHT.peer().discover().peerAddress(bootstrapAddress).start();
+ futureDiscover.addListener(new BaseFutureListener()
+ {
+ @Override
+ public void operationComplete(BaseFuture future) throws Exception
+ {
+ if (future.isSuccess())
+ {
+ // We are not behind a NAT and reachable to other peers
+ log.debug("We are not behind a NAT and reachable to other peers: My address visible to the outside is " + futureDiscover.peerAddress());
+ getBootstrapPeerMap();
+ settableFuture.set(peerDHT);
+ }
+ else
+ {
+ log.warn("Discover has failed. Reason: " + futureDiscover.failedReason());
+ log.warn("We are probably behind a NAT and not reachable to other peers. We try port forwarding as next step.");
+
+ startPortForwarding(peerDHT, bootstrapAddress, futureDiscover);
+ }
+ }
+
+ @Override
+ public void exceptionCaught(Throwable t) throws Exception
+ {
+ log.error("Exception at discover: " + t);
+ settableFuture.setException(t);
+ }
+ });
+ } catch (IOException e)
+ {
+ log.error("Exception: " + e);
+ settableFuture.setException(e);
+ }
+ }
+
+ private void startPortForwarding(PeerDHT peerDHT, PeerAddress bootstrapAddress, FutureDiscover futureDiscover)
+ {
+ // Assume we are behind a NAT device
+ PeerNAT nodeBehindNat = new PeerBuilderNAT(peerDHT.peer()).start();
+
+ // Try to set up port forwarding with UPNP and NATPMP if peer is not reachable
+ FutureNAT futureNAT = nodeBehindNat.startSetupPortforwarding(futureDiscover);
+ futureNAT.addListener(new BaseFutureListener()
+ {
+ @Override
+ public void operationComplete(BaseFuture future) throws Exception
+ {
+ if (future.isSuccess())
+ {
+ // Port forwarding has succeed
+ log.debug("Port forwarding was successful. My address visible to the outside is " + futureNAT.peerAddress());
+ getBootstrapPeerMap();
+ settableFuture.set(peerDHT);
+ }
+ else
+ {
+ log.warn("Port forwarding has failed. Reason: " + futureNAT.failedReason());
+ log.warn("We try to use a relay as next step.");
+
+ prepareRelay(peerDHT, nodeBehindNat, bootstrapAddress);
+ }
+ }
+
+ @Override
+ public void exceptionCaught(Throwable t) throws Exception
+ {
+ log.error("Exception at port forwarding: " + t);
+ settableFuture.setException(t);
+ }
+ });
+ }
+
+ private void prepareRelay(PeerDHT peerDHT, PeerNAT nodeBehindNat, PeerAddress bootstrapAddress)
+ {
+ // Last resort: we try to use other peers as relays
+
+ // The firewalled flags have to be set, so that other peers don’t add the unreachable peer to their peer maps.
+ Peer peer = peerDHT.peer();
+ PeerAddress serverPeerAddress = peer.peerBean().serverPeerAddress();
+ serverPeerAddress = serverPeerAddress.changeFirewalledTCP(true).changeFirewalledUDP(true);
+ peer.peerBean().serverPeerAddress(serverPeerAddress);
+
+ // Find neighbors
+ FutureBootstrap futureBootstrap = peer.bootstrap().peerAddress(bootstrapAddress).start();
+ futureBootstrap.addListener(new BaseFutureListener()
+ {
+ @Override
+ public void operationComplete(BaseFuture future) throws Exception
+ {
+ if (future.isSuccess())
+ {
+ log.debug("Bootstrap was successful. bootstrapTo = " + futureBootstrap.bootstrapTo());
+
+ setupRelay(peerDHT, nodeBehindNat, bootstrapAddress);
+ }
+ else
+ {
+ log.error("Bootstrap failed. Reason:" + futureBootstrap.failedReason());
+ settableFuture.setException(new Exception(futureBootstrap.failedReason()));
+ }
+ }
+
+ @Override
+ public void exceptionCaught(Throwable t) throws Exception
+ {
+ log.error("Exception at bootstrap: " + t);
+ settableFuture.setException(t);
+ }
+ });
+ }
+
+ private void setupRelay(PeerDHT peerDHT, PeerNAT nodeBehindNat, PeerAddress bootstrapAddress)
+ {
+ FutureRelay futureRelay = new FutureRelay();
+ futureRelay.addListener(new BaseFutureListener()
+ {
+ @Override
+ public void operationComplete(BaseFuture future) throws Exception
+ {
+ if (future.isSuccess())
+ {
+ log.debug("Start setup relay was successful.");
+ futureRelay.relays().forEach(e -> log.debug("remotePeer = " + e.remotePeer()));
+
+ findNeighbors2(peerDHT, nodeBehindNat, bootstrapAddress);
+ }
+ else
+ {
+ log.error("setupRelay failed. Reason: " + futureRelay.failedReason());
+ log.error("Bootstrap failed. We give up...");
+ settableFuture.setException(new Exception(futureRelay.failedReason()));
+ }
+ }
+
+ @Override
+ public void exceptionCaught(Throwable t) throws Exception
+ {
+ log.error("Exception at setup relay: " + t);
+ }
+ });
+
+ DistributedRelay distributedRelay = nodeBehindNat.startSetupRelay(futureRelay);
+ distributedRelay.addRelayListener((distributedRelay1, peerConnection) -> {
+ log.error("startSetupRelay Failed");
+ settableFuture.setException(new Exception("startSetupRelay Failed"));
+ });
+ }
+
+ private void findNeighbors2(PeerDHT peerDHT, PeerNAT nodeBehindNat, PeerAddress bootstrapAddress)
+ {
+ // find neighbors again
+ FutureBootstrap futureBootstrap2 = peerDHT.peer().bootstrap().peerAddress(bootstrapAddress).start();
+ futureBootstrap2.addListener(new BaseFutureListener()
+ {
+ @Override
+ public void operationComplete(BaseFuture future) throws Exception
+ {
+ if (future.isSuccess())
+ {
+ log.debug("Final bootstrap was successful. bootstrapTo = " + futureBootstrap2.bootstrapTo());
+ getBootstrapPeerMap();
+ settableFuture.set(peerDHT);
+ }
+ else
+ {
+ log.error("Bootstrap 2 failed. Reason:" + futureBootstrap2.failedReason());
+ log.error("We give up...");
+ settableFuture.setException(new Exception(futureBootstrap2.failedReason()));
+ }
+ }
+
+ @Override
+ public void exceptionCaught(Throwable t) throws Exception
+ {
+ log.error("Exception at bootstrap 2: " + t);
+ settableFuture.setException(t);
+ }
+ });
+ }
+
+ // TODO we want to get a list of connected nodes form the seed node and save them locally for future bootstrapping
+ // The seed node should only be used if no other known peers are available
+ private void getBootstrapPeerMap()
+ {
+ log.debug("getBootstrapPeerMap");
+
+ /* NavigableSet closePeers = peer.peerBean().peerMap().closePeers(2);
+ log.debug("closePeers size = " + closePeers.size());
+ log.debug("closePeers = " + closePeers);
+ closePeers.forEach(e -> log.debug("forEach: " + e.toString()));
+
+ List allPeers = peer.peerBean().peerMap().all();
+ log.debug("allPeers size = " + allPeers.size());
+ log.debug("allPeers = " + allPeers);
+ allPeers.forEach(e -> log.debug("forEach: " + e.toString())); */
+ }
+
+
+}
diff --git a/src/main/java/io/bitsquare/msg/MessageBroker.java b/src/main/java/io/bitsquare/msg/MessageBroker.java
new file mode 100644
index 0000000000..fd65a3d865
--- /dev/null
+++ b/src/main/java/io/bitsquare/msg/MessageBroker.java
@@ -0,0 +1,8 @@
+package io.bitsquare.msg;
+
+import net.tomp2p.peers.PeerAddress;
+
+public interface MessageBroker
+{
+ void handleMessage(Object message, PeerAddress peerAddress);
+}
diff --git a/src/main/java/io/bitsquare/msg/MessageFacade.java b/src/main/java/io/bitsquare/msg/MessageFacade.java
index 74f2cc1142..70a08b6fc8 100644
--- a/src/main/java/io/bitsquare/msg/MessageFacade.java
+++ b/src/main/java/io/bitsquare/msg/MessageFacade.java
@@ -1,66 +1,65 @@
package io.bitsquare.msg;
-import io.bitsquare.BitSquare;
+import com.google.common.util.concurrent.FutureCallback;
+import com.google.inject.name.Named;
import io.bitsquare.msg.listeners.*;
import io.bitsquare.trade.Offer;
import io.bitsquare.user.Arbitrator;
-import io.bitsquare.util.DSAKeyUtil;
-import io.bitsquare.util.StorageDirectory;
+import io.bitsquare.user.User;
import java.io.IOException;
-import java.security.KeyPair;
import java.security.PublicKey;
-import java.util.*;
+import java.util.ArrayList;
+import java.util.Currency;
+import java.util.List;
+import java.util.Locale;
import javafx.application.Platform;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
+import javax.annotation.Nullable;
import javax.inject.Inject;
-import net.tomp2p.connection.Bindings;
-import net.tomp2p.connection.PeerConnection;
-import net.tomp2p.futures.*;
-import net.tomp2p.p2p.Peer;
-import net.tomp2p.p2p.PeerMaker;
+import net.tomp2p.dht.FutureGet;
+import net.tomp2p.dht.FuturePut;
+import net.tomp2p.dht.FutureRemove;
+import net.tomp2p.dht.PeerDHT;
+import net.tomp2p.futures.BaseFuture;
+import net.tomp2p.futures.BaseFutureAdapter;
+import net.tomp2p.futures.BaseFutureListener;
+import net.tomp2p.futures.FutureDirect;
import net.tomp2p.peers.Number160;
import net.tomp2p.peers.PeerAddress;
import net.tomp2p.storage.Data;
-import net.tomp2p.storage.StorageDisk;
import net.tomp2p.utils.Utils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
- * That facade delivers messaging functionality from the TomP2P library
+ * That facade delivers direct messaging and DHT functionality from the TomP2P library
+ * It is the translating domain specific functionality to the messaging layer.
* The TomP2P library codebase shall not be used outside that facade.
- * That way a change of the library will only affect that class.
+ * That way we limit the dependency of the TomP2P library only to that class (and it's sub components).
+ *
+ * TODO: improve callbacks that Platform.runLater is not necessary. We call usually that methods form teh UI thread.
*/
-@SuppressWarnings({"EmptyMethod", "ConstantConditions"})
-public class MessageFacade
+public class MessageFacade implements MessageBroker
{
private static final Logger log = LoggerFactory.getLogger(MessageFacade.class);
- // private static final String PING = "ping";
- // private static final String PONG = "pong";
- private static final int MASTER_PEER_PORT = 5001;
+ private static final String ARBITRATORS_ROOT = "ArbitratorsRoot";
+
+ private final P2PNode p2pNode;
private final List orderBookListeners = new ArrayList<>();
private final List arbitratorListeners = new ArrayList<>();
-
private final List incomingTradeMessageListeners = new ArrayList<>();
- // private final List pingPeerListeners = new ArrayList<>();
- private final BooleanProperty isDirty = new SimpleBooleanProperty(false);
- private Peer myPeer;
-
- private KeyPair keyPair;
- private Long lastTimeStamp = -3L;
-
-
///////////////////////////////////////////////////////////////////////////////////////////
// Constructor
///////////////////////////////////////////////////////////////////////////////////////////
@Inject
- public MessageFacade()
+ public MessageFacade(User user, @Named("useDiskStorage") Boolean useDiskStorage, @Named("defaultSeedNode") SeedNodeAddress.StaticSeedNodeAddresses defaultStaticSeedNodeAddresses)
{
+ this.p2pNode = new P2PNode(user.getMessageKeyPair(), useDiskStorage, defaultStaticSeedNodeAddresses, this);
}
@@ -68,168 +67,190 @@ public class MessageFacade
// Public Methods
///////////////////////////////////////////////////////////////////////////////////////////
- public void init()
+ public void init(BootstrapListener bootstrapListener)
{
- int port = Bindings.MAX_PORT - Math.abs(new Random().nextInt()) % (Bindings.MAX_PORT - Bindings.MIN_DYN_PORT);
- if (BitSquare.getAppName().contains("taker"))
+ p2pNode.start(new FutureCallback()
{
- port = 4501;
- }
- else if (BitSquare.getAppName().contains("offerer"))
- {
- port = 4500;
- }
+ @Override
+ public void onSuccess(@Nullable PeerDHT result)
+ {
+ log.debug("p2pNode.start success result = " + result);
+ Platform.runLater(() -> bootstrapListener.onCompleted());
+ }
- try
- {
- createMyPeerInstance(port);
- // setupStorage();
- //TODO save periodically or get informed if network address changes
- saveMyAddressToDHT();
- setupReplyHandler();
- } catch (IOException e)
- {
- shutDown();
- log.error("Error at init myPeerInstance" + e.getMessage());
- }
+ @Override
+ public void onFailure(Throwable t)
+ {
+ log.error(t.toString());
+ Platform.runLater(() -> bootstrapListener.onFailed(t));
+ }
+ });
}
public void shutDown()
{
- if (myPeer != null)
+ if (p2pNode != null)
+ p2pNode.shutDown();
+ }
+
+
+ ///////////////////////////////////////////////////////////////////////////////////////////
+ // Find peer address by publicKey
+ ///////////////////////////////////////////////////////////////////////////////////////////
+
+
+ public void getPeerAddress(PublicKey publicKey, GetPeerAddressListener listener)
+ {
+ final Number160 locationKey = Utils.makeSHAHash(publicKey.getEncoded());
+ try
{
- myPeer.shutdown();
+ FutureGet futureGet = p2pNode.getDomainProtectedData(locationKey, publicKey);
+
+ futureGet.addListener(new BaseFutureAdapter()
+ {
+ @Override
+ public void operationComplete(BaseFuture baseFuture) throws Exception
+ {
+ if (baseFuture.isSuccess() && futureGet.data() != null)
+ {
+ final PeerAddress peerAddress = (PeerAddress) futureGet.data().object();
+ Platform.runLater(() -> listener.onResult(peerAddress));
+ }
+ else
+ {
+ Platform.runLater(() -> listener.onFailed());
+ }
+ }
+ });
+ } catch (IOException | ClassNotFoundException e)
+ {
+ e.printStackTrace();
+ log.error(e.toString());
}
}
///////////////////////////////////////////////////////////////////////////////////////////
- // Find peer address
+ // Offer
///////////////////////////////////////////////////////////////////////////////////////////
- public void getPeerAddress(String pubKeyAsHex, GetPeerAddressListener listener)
+ public void addOffer(Offer offer)
{
- final Number160 location = Number160.createHash(pubKeyAsHex);
- final FutureDHT getPeerAddressFuture = myPeer.get(location).start();
- getPeerAddressFuture.addListener(new BaseFutureAdapter()
+ Number160 locationKey = Number160.createHash(offer.getCurrency().getCurrencyCode());
+ try
+ {
+ final Data data = new Data(offer);
+ // the offer is default 30 days valid
+ int defaultOfferTTL = 30 * 24 * 60 * 60 * 1000;
+ data.ttlSeconds(defaultOfferTTL);
+
+ FuturePut futurePut = p2pNode.addProtectedData(locationKey, data);
+ futurePut.addListener(new BaseFutureListener()
+ {
+ @Override
+ public void operationComplete(BaseFuture future) throws Exception
+ {
+ Platform.runLater(() -> {
+ orderBookListeners.stream().forEach(orderBookListener -> orderBookListener.onOfferAdded(data, future.isSuccess()));
+ setDirty(locationKey);
+ });
+ if (future.isSuccess())
+ {
+ log.trace("Add offer to DHT was successful. Stored data: [key: " + locationKey + ", value: " + data + "]");
+ }
+ else
+ {
+ log.error("Add offer to DHT failed. Reason: " + future.failedReason());
+ }
+ }
+
+ @Override
+ public void exceptionCaught(Throwable t) throws Exception
+ {
+ log.error(t.toString());
+ }
+ });
+ } catch (IOException | ClassNotFoundException e)
+ {
+ e.printStackTrace();
+ log.error(e.toString());
+ }
+ }
+
+ public void removeOffer(Offer offer)
+ {
+ Number160 locationKey = Number160.createHash(offer.getCurrency().getCurrencyCode());
+ try
+ {
+ final Data data = new Data(offer);
+ FutureRemove futureRemove = p2pNode.removeFromDataMap(locationKey, data);
+ futureRemove.addListener(new BaseFutureListener()
+ {
+ @Override
+ public void operationComplete(BaseFuture future) throws Exception
+ {
+ Platform.runLater(() -> {
+ orderBookListeners.stream().forEach(orderBookListener -> orderBookListener.onOfferRemoved(data, future.isSuccess()));
+ setDirty(locationKey);
+ });
+ if (future.isSuccess())
+ {
+ log.trace("Remove offer from DHT was successful. Stored data: [key: " + locationKey + ", value: " + data + "]");
+ }
+ else
+ {
+ log.error("Remove offer from DHT failed. Reason: " + future.failedReason());
+ }
+ }
+
+ @Override
+ public void exceptionCaught(Throwable t) throws Exception
+ {
+ log.error(t.toString());
+ }
+ });
+ } catch (IOException | ClassNotFoundException e)
+ {
+ e.printStackTrace();
+ }
+ }
+
+ public void getOffers(String currencyCode)
+ {
+ Number160 locationKey = Number160.createHash(currencyCode);
+ FutureGet futureGet = p2pNode.getDataMap(locationKey);
+ futureGet.addListener(new BaseFutureAdapter()
{
@Override
public void operationComplete(BaseFuture baseFuture) throws Exception
{
- if (baseFuture.isSuccess() && getPeerAddressFuture.getData() != null)
+ Platform.runLater(() -> orderBookListeners.stream().forEach(orderBookListener -> orderBookListener.onOffersReceived(futureGet.dataMap(), baseFuture.isSuccess())));
+ if (baseFuture.isSuccess())
{
- final PeerAddress peerAddress = (PeerAddress) getPeerAddressFuture.getData().getObject();
- Platform.runLater(() -> listener.onResult(peerAddress));
+ log.trace("Get offers from DHT was successful. Stored data: [key: " + locationKey + ", values: " + futureGet.dataMap() + "]");
}
else
{
- Platform.runLater(() -> listener.onFailed());
+ log.error("Get offers from DHT failed with reason:" + baseFuture.failedReason());
}
}
});
}
- ///////////////////////////////////////////////////////////////////////////////////////////
- // Publish offer
- ///////////////////////////////////////////////////////////////////////////////////////////
-
- public void addOffer(Offer offer) throws IOException
- {
- log.trace("addOffer");
- Number160 locationKey = Number160.createHash(offer.getCurrency().getCurrencyCode());
- final Number160 contentKey = Number160.createHash(offer.getId());
- final Data offerData = new Data(offer);
- //offerData.setTTLSeconds(5);
- final FutureDHT addFuture = myPeer.put(locationKey).setData(contentKey, offerData).start();
- //final FutureDHT addFuture = myPeer.add(locationKey).setData(offerData).start();
- addFuture.addListener(new BaseFutureAdapter()
- {
- @Override
- public void operationComplete(BaseFuture future) throws Exception
- {
- Platform.runLater(() -> onOfferAdded(offerData, future.isSuccess(), locationKey));
- }
- });
- }
-
- private void onOfferAdded(Data offerData, boolean success, Number160 locationKey)
- {
- log.trace("onOfferAdded");
- setDirty(locationKey);
- orderBookListeners.stream().forEach(orderBookListener -> orderBookListener.onOfferAdded(offerData, success));
- }
-
-
- ///////////////////////////////////////////////////////////////////////////////////////////
- // Get offers
- ///////////////////////////////////////////////////////////////////////////////////////////
-
- public void getOffers(String currency)
- {
- log.trace("getOffers");
- final Number160 locationKey = Number160.createHash(currency);
- final FutureDHT getOffersFuture = myPeer.get(locationKey).setAll().start();
- getOffersFuture.addListener(new BaseFutureAdapter()
- {
- @Override
- public void operationComplete(BaseFuture future) throws Exception
- {
- final Map dataMap = getOffersFuture.getDataMap();
- Platform.runLater(() -> onOffersReceived(dataMap, future.isSuccess()));
- }
- });
- }
-
- private void onOffersReceived(Map dataMap, boolean success)
- {
- log.trace("onOffersReceived");
- orderBookListeners.stream().forEach(orderBookListener -> orderBookListener.onOffersReceived(dataMap, success));
- }
-
- ///////////////////////////////////////////////////////////////////////////////////////////
- // Remove offer
- ///////////////////////////////////////////////////////////////////////////////////////////
-
- public void removeOffer(Offer offer)
- {
- log.trace("removeOffer");
- Number160 locationKey = Number160.createHash(offer.getCurrency().getCurrencyCode());
- Number160 contentKey = Number160.createHash(offer.getId());
- log.debug("removeOffer");
- FutureDHT removeFuture = myPeer.remove(locationKey).setReturnResults().setContentKey(contentKey).start();
- removeFuture.addListener(new BaseFutureAdapter()
- {
- @Override
- public void operationComplete(BaseFuture future) throws Exception
- {
- Platform.runLater(() -> onOfferRemoved(removeFuture.getData(), future.isSuccess(), locationKey));
- }
- });
- }
-
- private void onOfferRemoved(Data data, boolean success, Number160 locationKey)
- {
- log.trace("onOfferRemoved");
- setDirty(locationKey);
- orderBookListeners.stream().forEach(orderBookListener -> orderBookListener.onOfferRemoved(data, success));
- }
-
-
///////////////////////////////////////////////////////////////////////////////////////////
// Trade process
///////////////////////////////////////////////////////////////////////////////////////////
public void sendTradeMessage(PeerAddress peerAddress, TradeMessage tradeMessage, OutgoingTradeMessageListener listener)
{
- final PeerConnection peerConnection = myPeer.createPeerConnection(peerAddress, 10);
- final FutureResponse sendFuture = myPeer.sendDirect(peerConnection).setObject(tradeMessage).start();
- sendFuture.addListener(new BaseFutureAdapter()
+ FutureDirect futureDirect = p2pNode.sendData(peerAddress, tradeMessage);
+ futureDirect.addListener(new BaseFutureListener()
{
@Override
- public void operationComplete(BaseFuture baseFuture) throws Exception
+ public void operationComplete(BaseFuture future) throws Exception
{
- if (sendFuture.isSuccess())
+ if (futureDirect.isSuccess())
{
Platform.runLater(() -> listener.onResult());
}
@@ -238,316 +259,109 @@ public class MessageFacade
Platform.runLater(() -> listener.onFailed());
}
}
+
+ @Override
+ public void exceptionCaught(Throwable t) throws Exception
+ {
+ Platform.runLater(() -> listener.onFailed());
+ }
});
}
- ///////////////////////////////////////////////////////////////////////////////////////////
- // Reputation
- ///////////////////////////////////////////////////////////////////////////////////////////
-
- public void setupReputationRoot() throws IOException
- {
- String pubKeyAsHex = DSAKeyUtil.getHexStringFromPublicKey(getPubKey()); // out message ID
- final Number160 locationKey = Number160.createHash("REPUTATION_" + pubKeyAsHex); // out reputation root storage location
- final Number160 contentKey = Utils.makeSHAHash(getPubKey().getEncoded()); // my pubKey -> i may only put in 1 reputation
- final Data reputationData = new Data(Number160.ZERO).setProtectedEntry().setPublicKey(getPubKey()); // at registration time we add a null value as data
- // we use a pubkey where we provable cannot own the private key.
- // the domain key must be verifiable by peers to be sure the reputation root was net deleted by the owner.
- // so we use the locationKey as it already meets our requirements (verifiable and impossible to create a private key out of it)
- myPeer.put(locationKey).setData(contentKey, reputationData).setDomainKey(locationKey).setProtectDomain().start();
- }
-
- public void addReputation(String pubKeyAsHex) throws IOException
- {
- final Number160 locationKey = Number160.createHash("REPUTATION_" + pubKeyAsHex); // reputation root storage location ot the peer
- final Number160 contentKey = Utils.makeSHAHash(getPubKey().getEncoded()); // my pubKey -> i may only put in 1 reputation, I may update it later. eg. counter for 5 trades...
- final Data reputationData = new Data("TODO: some reputation data..., content signed and sig attached").setProtectedEntry().setPublicKey(getPubKey());
- myPeer.put(locationKey).setData(contentKey, reputationData).start();
- }
-
- // At any offer or take offer fee payment the trader add the tx id and the pubKey and the signature of that tx to that entry.
- // That way he can prove with the signature that he is the payer of the offer fee.
- // It does not assure that the trade was really executed, but we can protect the traders privacy that way.
- // If we use the trade, we would link all trades together and would reveal the whole trading history.
- @SuppressWarnings("UnusedParameters")
- public void addOfferFeePaymentToReputation(String txId, String pubKeyOfFeePayment) throws IOException
- {
- String pubKeyAsHex = DSAKeyUtil.getHexStringFromPublicKey(getPubKey()); // out message ID
- final Number160 locationKey = Number160.createHash("REPUTATION_" + pubKeyAsHex); // reputation root storage location ot the peer
- final Number160 contentKey = Utils.makeSHAHash(getPubKey().getEncoded()); // my pubKey -> i may only put in 1 reputation, I may update it later. eg. counter for 5 trades...
- final Data reputationData = new Data("TODO: tx, btc_pubKey, sig(tx), content signed and sig attached").setProtectedEntry().setPublicKey(getPubKey());
- myPeer.put(locationKey).setData(contentKey, reputationData).start();
- }
-
-
///////////////////////////////////////////////////////////////////////////////////////////
// Arbitrators
///////////////////////////////////////////////////////////////////////////////////////////
- public void addArbitrator(Arbitrator arbitrator) throws IOException
+ public void addArbitrator(Arbitrator arbitrator)
{
- Number160 locationKey = Number160.createHash("Arbitrators");
- final Number160 contentKey = Number160.createHash(arbitrator.getId());
- final Data arbitratorData = new Data(arbitrator);
- //offerData.setTTLSeconds(5);
- final FutureDHT addFuture = myPeer.put(locationKey).setData(contentKey, arbitratorData).start();
- //final FutureDHT addFuture = myPeer.add(locationKey).setData(offerData).start();
- addFuture.addListener(new BaseFutureAdapter()
- {
- @Override
- public void operationComplete(BaseFuture future) throws Exception
- {
- Platform.runLater(() -> onArbitratorAdded(arbitratorData, future.isSuccess(), locationKey));
- }
- });
- }
-
- @SuppressWarnings("UnusedParameters")
- private void onArbitratorAdded(Data arbitratorData, boolean success, Number160 locationKey)
- {
- }
-
-
- @SuppressWarnings("UnusedParameters")
- public void getArbitrators(Locale languageLocale)
- {
- final Number160 locationKey = Number160.createHash("Arbitrators");
- final FutureDHT getArbitratorsFuture = myPeer.get(locationKey).setAll().start();
- getArbitratorsFuture.addListener(new BaseFutureAdapter()
- {
- @Override
- public void operationComplete(BaseFuture future) throws Exception
- {
- final Map dataMap = getArbitratorsFuture.getDataMap();
- Platform.runLater(() -> onArbitratorsReceived(dataMap, future.isSuccess()));
- }
- });
- }
-
- private void onArbitratorsReceived(Map dataMap, boolean success)
- {
- for (ArbitratorListener arbitratorListener : arbitratorListeners)
- {
- arbitratorListener.onArbitratorsReceived(dataMap, success);
- }
- }
-
- ///////////////////////////////////////////////////////////////////////////////////////////
- // Check dirty flag for a location key
- ///////////////////////////////////////////////////////////////////////////////////////////
-
- // TODO just temp...
- public BooleanProperty getIsDirtyProperty()
- {
- return isDirty;
- }
-
- public void getDirtyFlag(Currency currency)
- {
- Number160 locationKey = Number160.createHash(currency.getCurrencyCode());
- FutureDHT getFuture = myPeer.get(getDirtyLocationKey(locationKey)).start();
- getFuture.addListener(new BaseFutureListener()
- {
- @Override
- public void operationComplete(BaseFuture future) throws Exception
- {
- Data data = getFuture.getData();
- if (data != null)
- {
- Object object = data.getObject();
- if (object instanceof Long)
- {
- Platform.runLater(() -> onGetDirtyFlag((Long) object));
- }
- }
- }
-
- @Override
- public void exceptionCaught(Throwable t) throws Exception
- {
- System.out.println("getFuture exceptionCaught " + System.currentTimeMillis());
- }
- });
- }
-
- private void onGetDirtyFlag(long timeStamp)
- {
- // TODO don't get updates at first execute....
- if (lastTimeStamp != timeStamp)
- {
- isDirty.setValue(!isDirty.get());
- }
- if (lastTimeStamp > 0)
- {
- lastTimeStamp = timeStamp;
- }
- else
- {
- lastTimeStamp++;
- }
- }
-
- private Number160 getDirtyLocationKey(Number160 locationKey)
- {
- return Number160.createHash(locationKey + "Dirty");
- }
-
- private void setDirty(Number160 locationKey)
- {
- // we don't want to get an update from dirty for own changes, so update the lastTimeStamp to omit a change trigger
- lastTimeStamp = System.currentTimeMillis();
+ Number160 locationKey = Number160.createHash(ARBITRATORS_ROOT);
try
{
- FutureDHT putFuture = myPeer.put(getDirtyLocationKey(locationKey)).setData(new Data(lastTimeStamp)).start();
- putFuture.addListener(new BaseFutureListener()
+ final Data arbitratorData = new Data(arbitrator);
+
+ FuturePut addFuture = p2pNode.addProtectedData(locationKey, arbitratorData);
+ addFuture.addListener(new BaseFutureAdapter()
{
@Override
public void operationComplete(BaseFuture future) throws Exception
{
- //System.out.println("operationComplete");
- }
-
- @Override
- public void exceptionCaught(Throwable t) throws Exception
- {
- System.err.println("exceptionCaught");
+ Platform.runLater(() -> arbitratorListeners.stream().forEach(listener -> listener.onArbitratorAdded(arbitratorData, addFuture.isSuccess())));
+ if (addFuture.isSuccess())
+ {
+ log.trace("Add arbitrator to DHT was successful. Stored data: [key: " + locationKey + ", values: " + arbitratorData + "]");
+ }
+ else
+ {
+ log.error("Add arbitrator to DHT failed with reason:" + addFuture.failedReason());
+ }
}
});
} catch (IOException e)
{
- log.warn("Error at writing dirty flag (timeStamp) " + e.getMessage());
- }
- }
-
-
- ///////////////////////////////////////////////////////////////////////////////////////////
- // Send message
- ///////////////////////////////////////////////////////////////////////////////////////////
-
- /* public boolean sendMessage(Object message)
- {
- boolean result = false;
- if (otherPeerAddress != null)
+ e.printStackTrace();
+ } catch (ClassNotFoundException e)
{
- if (peerConnection != null)
- peerConnection.close();
-
- peerConnection = myPeer.createPeerConnection(otherPeerAddress, 20);
- if (!peerConnection.isClosed())
- {
- FutureResponse sendFuture = myPeer.sendDirect(peerConnection).setObject(message).start();
- sendFuture.addListener(new BaseFutureAdapter()
- {
- @Override
- public void operationComplete(BaseFuture baseFuture) throws Exception
- {
- if (sendFuture.isSuccess())
- {
- final Object object = sendFuture.getObject();
- Platform.runLater(() -> onResponseFromSend(object));
- }
- else
- {
- Platform.runLater(() -> onSendFailed());
- }
- }
- }
- );
- result = true;
- }
+ e.printStackTrace();
}
- return result;
- } */
- /*
- private void onResponseFromSend(Object response)
- {
- for (MessageListener messageListener : messageListeners)
- messageListener.onResponseFromSend(response);
}
- private void onSendFailed()
+ public void removeArbitrator(Arbitrator arbitrator) throws IOException, ClassNotFoundException
{
- for (MessageListener messageListener : messageListeners)
- messageListener.onSendFailed();
- }
- */
-
-
- ///////////////////////////////////////////////////////////////////////////////////////////
- // Ping peer
- ///////////////////////////////////////////////////////////////////////////////////////////
- //TODO not working anymore...
- /* public void pingPeer(String publicKeyAsHex)
- {
- Number160 location = Number160.createHash(publicKeyAsHex);
- final FutureDHT getPeerAddressFuture = myPeer.get(location).start();
- getPeerAddressFuture.addListener(new BaseFutureAdapter()
+ Number160 locationKey = Number160.createHash(ARBITRATORS_ROOT);
+ final Data arbitratorData = new Data(arbitrator);
+ FutureRemove removeFuture = p2pNode.removeFromDataMap(locationKey, arbitratorData);
+ removeFuture.addListener(new BaseFutureAdapter()
{
@Override
- public void operationComplete(BaseFuture baseFuture) throws Exception
+ public void operationComplete(BaseFuture future) throws Exception
{
- final Data data = getPeerAddressFuture.getData();
- if (data != null && data.getObject() instanceof PeerAddress)
+ Platform.runLater(() -> arbitratorListeners.stream().forEach(listener -> listener.onArbitratorRemoved(arbitratorData, removeFuture.isSuccess())));
+ if (removeFuture.isSuccess())
{
- final PeerAddress peerAddress = (PeerAddress) data.getObject();
- Platform.runLater(() -> onAddressFoundPingPeer(peerAddress));
+ log.trace("Remove arbitrator from DHT was successful. Stored data: [key: " + locationKey + ", values: " + arbitratorData + "]");
+ }
+ else
+ {
+ log.error("Remove arbitrators from DHT failed with reason:" + removeFuture.failedReason());
}
}
});
}
- private void onAddressFoundPingPeer(PeerAddress peerAddress)
+ public void getArbitrators(Locale languageLocale)
{
- try
+ Number160 locationKey = Number160.createHash(ARBITRATORS_ROOT);
+ FutureGet futureGet = p2pNode.getDataMap(locationKey);
+ futureGet.addListener(new BaseFutureAdapter()
{
- final PeerConnection peerConnection = myPeer.createPeerConnection(peerAddress, 10);
- if (!peerConnection.isClosed())
+ @Override
+ public void operationComplete(BaseFuture baseFuture) throws Exception
{
- FutureResponse sendFuture = myPeer.sendDirect(peerConnection).setObject(PING).start();
- sendFuture.addListener(new BaseFutureAdapter()
- {
- @Override
- public void operationComplete(BaseFuture baseFuture) throws Exception
- {
- if (sendFuture.isSuccess())
- {
- final String pong = (String) sendFuture.getObject();
- Platform.runLater(() -> onResponseFromPing(PONG.equals(pong)));
- }
- else
- {
- peerConnection.close();
- Platform.runLater(() -> onResponseFromPing(false));
- }
- }
- }
- );
+ Platform.runLater(() -> arbitratorListeners.stream().forEach(listener -> listener.onArbitratorsReceived(futureGet.dataMap(), baseFuture.isSuccess())));
+ if (baseFuture.isSuccess())
+ {
+ log.trace("Get arbitrators from DHT was successful. Stored data: [key: " + locationKey + ", values: " + futureGet.dataMap() + "]");
+ }
+ else
+ {
+ log.error("Get arbitrators from DHT failed with reason:" + baseFuture.failedReason());
+ }
}
- } catch (Exception e)
- {
- // ClosedChannelException can happen, check out if there is a better way to ping a myPeerInstance for online status
- }
+ });
}
- private void onResponseFromPing(boolean success)
- {
- for (PingPeerListener pingPeerListener : pingPeerListeners)
- pingPeerListener.onPingPeerResult(success);
- }
- */
-
///////////////////////////////////////////////////////////////////////////////////////////
// Event Listeners
///////////////////////////////////////////////////////////////////////////////////////////
- public void addMessageListener(OrderBookListener listener)
+ public void addOrderBookListener(OrderBookListener listener)
{
orderBookListeners.add(listener);
}
- public void removeMessageListener(OrderBookListener listener)
+ public void removeOrderBookListener(OrderBookListener listener)
{
orderBookListeners.remove(listener);
}
@@ -584,12 +398,99 @@ public class MessageFacade
///////////////////////////////////////////////////////////////////////////////////////////
- // Getters
+ // Check dirty flag for a location key
///////////////////////////////////////////////////////////////////////////////////////////
- public PublicKey getPubKey()
+ // TODO just temp...
+ public BooleanProperty getIsDirtyProperty()
{
- return keyPair.getPublic();
+ return isDirty;
+ }
+
+ public void getDirtyFlag(Currency currency)
+ {
+ Number160 locationKey = Number160.createHash(currency.getCurrencyCode());
+ try
+ {
+ FutureGet getFuture = p2pNode.getData(getDirtyLocationKey(locationKey));
+ getFuture.addListener(new BaseFutureListener()
+ {
+ @Override
+ public void operationComplete(BaseFuture future) throws Exception
+ {
+ Data data = getFuture.data();
+ if (data != null)
+ {
+ Object object = data.object();
+ if (object instanceof Long)
+ {
+ Platform.runLater(() -> onGetDirtyFlag((Long) object));
+ }
+ }
+ }
+
+ @Override
+ public void exceptionCaught(Throwable t) throws Exception
+ {
+ log.error("getFuture exceptionCaught " + t.toString());
+ }
+ });
+ } catch (IOException | ClassNotFoundException e)
+ {
+ e.printStackTrace();
+ }
+ }
+
+ private Long lastTimeStamp = -3L;
+ private final BooleanProperty isDirty = new SimpleBooleanProperty(false);
+
+ private void onGetDirtyFlag(long timeStamp)
+ {
+ // TODO don't get updates at first execute....
+ if (lastTimeStamp != timeStamp)
+ {
+ isDirty.setValue(!isDirty.get());
+ }
+ if (lastTimeStamp > 0)
+ {
+ lastTimeStamp = timeStamp;
+ }
+ else
+ {
+ lastTimeStamp++;
+ }
+ }
+
+ private void setDirty(Number160 locationKey)
+ {
+ // we don't want to get an update from dirty for own changes, so update the lastTimeStamp to omit a change trigger
+ lastTimeStamp = System.currentTimeMillis();
+ try
+ {
+ FuturePut putFuture = p2pNode.putData(getDirtyLocationKey(locationKey), new Data(lastTimeStamp));
+ putFuture.addListener(new BaseFutureListener()
+ {
+ @Override
+ public void operationComplete(BaseFuture future) throws Exception
+ {
+ // log.trace("operationComplete");
+ }
+
+ @Override
+ public void exceptionCaught(Throwable t) throws Exception
+ {
+ log.warn("Error at writing dirty flag (timeStamp) " + t.toString());
+ }
+ });
+ } catch (IOException | ClassNotFoundException e)
+ {
+ log.warn("Error at writing dirty flag (timeStamp) " + e.getMessage());
+ }
+ }
+
+ private Number160 getDirtyLocationKey(Number160 locationKey)
+ {
+ return Number160.createHash(locationKey + "Dirty");
}
@@ -597,89 +498,13 @@ public class MessageFacade
// Incoming message handler
///////////////////////////////////////////////////////////////////////////////////////////
- private void onMessage(Object request, PeerAddress sender)
+ @Override
+ public void handleMessage(Object message, PeerAddress peerAddress)
{
- if (request instanceof TradeMessage)
+ if (message instanceof TradeMessage)
{
- incomingTradeMessageListeners.stream().forEach(e -> e.onMessage((TradeMessage) request, sender));
- }
- /* else
- {
- for (OrderBookListener orderBookListener : orderBookListeners)
- orderBookListener.onMessage(request);
- } */
- }
-
- ///////////////////////////////////////////////////////////////////////////////////////////
- // Private Methods
- ///////////////////////////////////////////////////////////////////////////////////////////
-
- private void createMyPeerInstance(int port) throws IOException
- {
- keyPair = DSAKeyUtil.getKeyPair();
- myPeer = new PeerMaker(keyPair).setPorts(port).makeAndListen();
- final FutureBootstrap futureBootstrap = myPeer.bootstrap().setBroadcast().setPorts(MASTER_PEER_PORT).start();
- futureBootstrap.addListener(new BaseFutureAdapter()
- {
- @Override
- public void operationComplete(BaseFuture future) throws Exception
- {
- if (futureBootstrap.getBootstrapTo() != null)
- {
- PeerAddress masterPeerAddress = futureBootstrap.getBootstrapTo().iterator().next();
- final FutureDiscover futureDiscover = myPeer.discover().setPeerAddress(masterPeerAddress).start();
- futureDiscover.addListener(new BaseFutureListener()
- {
- @Override
- public void operationComplete(BaseFuture future) throws Exception
- {
- //System.out.println("operationComplete");
- }
-
- @Override
- public void exceptionCaught(Throwable t) throws Exception
- {
- System.err.println("exceptionCaught");
- }
- });
- }
- }
- });
- }
-
- private void setupReplyHandler()
- {
- myPeer.setObjectDataReply((sender, request) -> {
- if (!sender.equals(myPeer.getPeerAddress()))
- {
- Platform.runLater(() -> onMessage(request, sender));
- }
- else
- {
- log.error("Received msg from myself. That should never happen.");
- }
- //noinspection ReturnOfNull
- return null;
- });
- }
-
- private void setupStorage()
- {
- try
- {
- myPeer.getPeerBean().setStorage(new StorageDisk(StorageDirectory.getStorageDirectory().getCanonicalPath() + "/" + BitSquare.getAppName() + "_tomP2P"));
- } catch (IOException e)
- {
- e.printStackTrace();
+ log.error("####################");
+ Platform.runLater(() -> incomingTradeMessageListeners.stream().forEach(e -> e.onMessage((TradeMessage) message, peerAddress)));
}
}
-
- private void saveMyAddressToDHT() throws IOException
- {
- Number160 location = Number160.createHash(DSAKeyUtil.getHexStringFromPublicKey(getPubKey()));
- //log.debug("saveMyAddressToDHT location "+location.toString());
- myPeer.put(location).setData(new Data(myPeer.getPeerAddress())).start();
- }
-
-
}
diff --git a/src/main/java/io/bitsquare/msg/P2PNode.java b/src/main/java/io/bitsquare/msg/P2PNode.java
new file mode 100644
index 0000000000..8cd24e88b7
--- /dev/null
+++ b/src/main/java/io/bitsquare/msg/P2PNode.java
@@ -0,0 +1,385 @@
+package io.bitsquare.msg;
+
+import com.google.common.util.concurrent.FutureCallback;
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.ListenableFuture;
+import io.bitsquare.BitSquare;
+import io.bitsquare.util.DSAKeyUtil;
+import io.bitsquare.util.StorageDirectory;
+import java.io.File;
+import java.io.IOException;
+import java.security.KeyPair;
+import java.security.PublicKey;
+import java.util.Timer;
+import java.util.TimerTask;
+import javax.annotation.Nullable;
+import net.tomp2p.connection.DSASignatureFactory;
+import net.tomp2p.dht.*;
+import net.tomp2p.futures.BaseFuture;
+import net.tomp2p.futures.BaseFutureListener;
+import net.tomp2p.futures.FutureDirect;
+import net.tomp2p.futures.FuturePeerConnection;
+import net.tomp2p.peers.Number160;
+import net.tomp2p.peers.PeerAddress;
+import net.tomp2p.storage.Data;
+import net.tomp2p.storage.Storage;
+import net.tomp2p.storage.StorageDisk;
+import net.tomp2p.utils.Utils;
+import org.jetbrains.annotations.NotNull;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The fully bootstrapped P2PNode which is responsible himself for his availability in the messaging system. It saves for instance the IP address periodically.
+ * This class is offering generic functionality of TomP2P needed for Bitsquare, like data and domain protection.
+ * It does not handle any domain aspects of Bitsquare.
+ */
+public class P2PNode
+{
+ private static final Logger log = LoggerFactory.getLogger(P2PNode.class);
+
+ private Thread bootstrapToLocalhostThread;
+ private Thread bootstrapToServerThread;
+
+ // just for lightweight client test
+ public static void main(String[] args)
+ {
+ P2PNode p2pNode = new P2PNode(DSAKeyUtil.getKeyPair(), false, SeedNodeAddress.StaticSeedNodeAddresses.LOCALHOST,
+ (message, peerAddress) -> log.debug("handleMessage: message= " + message + "/ peerAddress=" + peerAddress));
+ p2pNode.start(new FutureCallback()
+ {
+ @Override
+ public void onSuccess(@Nullable PeerDHT result)
+ {
+ log.debug("p2pNode.start success result = " + result);
+ }
+
+ @Override
+ public void onFailure(Throwable t)
+ {
+ log.error(t.toString());
+ }
+ });
+ for (; ; )
+ {
+ }
+ }
+
+ private final KeyPair keyPair;
+ private final Boolean useDiskStorage;
+ private final SeedNodeAddress.StaticSeedNodeAddresses defaultStaticSeedNodeAddresses;
+ private final MessageBroker messageBroker;
+
+ private PeerAddress storedPeerAddress;
+ private PeerDHT peerDHT;
+ private Storage storage;
+
+
+ ///////////////////////////////////////////////////////////////////////////////////////////
+ // Constructor
+ ///////////////////////////////////////////////////////////////////////////////////////////
+
+ public P2PNode(KeyPair keyPair, Boolean useDiskStorage, SeedNodeAddress.StaticSeedNodeAddresses defaultStaticSeedNodeAddresses, MessageBroker messageBroker)
+ {
+ this.keyPair = keyPair;
+ this.useDiskStorage = useDiskStorage;
+ this.defaultStaticSeedNodeAddresses = defaultStaticSeedNodeAddresses;
+ this.messageBroker = messageBroker;
+ }
+
+ // for unit testing
+ P2PNode(KeyPair keyPair, PeerDHT peerDHT)
+ {
+ this.keyPair = keyPair;
+ this.peerDHT = peerDHT;
+ messageBroker = (message, peerAddress) -> {
+ };
+ useDiskStorage = false;
+ defaultStaticSeedNodeAddresses = SeedNodeAddress.StaticSeedNodeAddresses.LOCALHOST;
+ peerDHT.peerBean().keyPair(keyPair);
+ }
+
+
+ ///////////////////////////////////////////////////////////////////////////////////////////
+ // Public methods
+ ///////////////////////////////////////////////////////////////////////////////////////////
+
+
+ public void start(FutureCallback callback)
+ {
+ useDiscStorage(useDiskStorage);
+ setupTimerForIPCheck();
+
+ FutureCallback localCallback = new FutureCallback()
+ {
+ @Override
+ public void onSuccess(@Nullable PeerDHT result)
+ {
+ log.debug("p2pNode.start success result = " + result);
+ callback.onSuccess(result);
+ bootstrapThreadCompleted();
+ }
+
+ @Override
+ public void onFailure(Throwable t)
+ {
+ log.error(t.toString());
+ callback.onFailure(t);
+ }
+ };
+
+ bootstrapToLocalhostThread = runBootstrapThread(localCallback, new SeedNodeAddress(defaultStaticSeedNodeAddresses));
+ bootstrapToServerThread = runBootstrapThread(localCallback, new SeedNodeAddress(SeedNodeAddress.StaticSeedNodeAddresses.DIGITAL_OCEAN));
+ }
+
+ public void bootstrapThreadCompleted()
+ {
+ if (bootstrapToLocalhostThread != null)
+ bootstrapToLocalhostThread.interrupt();
+
+ if (bootstrapToServerThread != null)
+ bootstrapToServerThread.interrupt();
+ }
+
+ private Thread runBootstrapThread(FutureCallback callback, SeedNodeAddress seedNodeAddress)
+ {
+ Thread bootstrapThread = new Thread(new Runnable()
+ {
+ @Override
+ public void run()
+ {
+ log.debug("runBootstrapThread");
+ ListenableFuture bootstrapComplete = bootstrap(seedNodeAddress);
+ Futures.addCallback(bootstrapComplete, callback);
+ }
+ });
+ bootstrapThread.start();
+ return bootstrapThread;
+ }
+
+ public void shutDown()
+ {
+ if (peerDHT != null && peerDHT.peer() != null)
+ peerDHT.peer().shutdown();
+
+ if (storage != null)
+ storage.close();
+ }
+
+
+ ///////////////////////////////////////////////////////////////////////////////////////////
+ // Generic DHT methods
+ ///////////////////////////////////////////////////////////////////////////////////////////
+
+ // The data and the domain are protected by that key pair.
+ public FuturePut putDomainProtectedData(Number160 locationKey, Data data) throws IOException, ClassNotFoundException
+ {
+ data.protectEntry(keyPair);
+ final Number160 ownerKeyHash = Utils.makeSHAHash(keyPair.getPublic().getEncoded());
+ return peerDHT.put(locationKey).data(data).keyPair(keyPair).domainKey(ownerKeyHash).protectDomain().start();
+ }
+
+ // No protection, everybody can write.
+ public FuturePut putData(Number160 locationKey, Data data) throws IOException, ClassNotFoundException
+ {
+ return peerDHT.put(locationKey).data(data).start();
+ }
+
+ // Not public readable. Only users with the public key of the peer who stored the data can read that data
+ public FutureGet getDomainProtectedData(Number160 locationKey, PublicKey publicKey) throws IOException, ClassNotFoundException
+ {
+ final Number160 ownerKeyHash = Utils.makeSHAHash(publicKey.getEncoded());
+ return peerDHT.get(locationKey).domainKey(ownerKeyHash).start();
+ }
+
+ // No protection, everybody can read.
+ public FutureGet getData(Number160 locationKey) throws IOException, ClassNotFoundException
+ {
+ return peerDHT.get(locationKey).start();
+ }
+
+ // No domain protection, but entry protection
+ public FuturePut addProtectedData(Number160 locationKey, Data data) throws IOException, ClassNotFoundException
+ {
+ data.protectEntry(keyPair);
+ log.trace("addProtectedData with contentKey " + data.hash().toString());
+ return peerDHT.add(locationKey).data(data).keyPair(keyPair).start();
+ }
+
+ // No domain protection, but entry protection
+ public FutureRemove removeFromDataMap(Number160 locationKey, Data data) throws IOException, ClassNotFoundException
+ {
+ Number160 contentKey = data.hash();
+ log.trace("removeFromDataMap with contentKey " + contentKey.toString());
+ return peerDHT.remove(locationKey).contentKey(contentKey).keyPair(keyPair).start();
+ }
+
+ // Public readable
+ public FutureGet getDataMap(Number160 locationKey)
+ {
+ return peerDHT.get(locationKey).all().start();
+ }
+
+ // Send signed payLoad to peer
+ public FutureDirect sendData(PeerAddress peerAddress, Object payLoad)
+ {
+ // use 30 seconds as max idle time before connection get closed
+ FuturePeerConnection futurePeerConnection = peerDHT.peer().createPeerConnection(peerAddress, 30000);
+ FutureDirect futureDirect = peerDHT.peer().sendDirect(futurePeerConnection).object(payLoad).sign().start();
+ futureDirect.addListener(new BaseFutureListener()
+ {
+ @Override
+ public void operationComplete(BaseFuture future) throws Exception
+ {
+ if (futureDirect.isSuccess())
+ {
+ log.debug("sendMessage completed");
+ }
+ else
+ {
+ log.error("sendData failed with Reason " + futureDirect.failedReason());
+ }
+ }
+
+ @Override
+ public void exceptionCaught(Throwable t) throws Exception
+ {
+ log.error("Exception at sendData " + t.toString());
+ }
+ });
+
+ return futureDirect;
+ }
+
+ ///////////////////////////////////////////////////////////////////////////////////////////
+ // Private
+ ///////////////////////////////////////////////////////////////////////////////////////////
+
+ private ListenableFuture bootstrap(SeedNodeAddress seedNodeAddress)
+ {
+ BootstrappedPeerFactory bootstrappedPeerFactory = new BootstrappedPeerFactory(keyPair, storage, seedNodeAddress);
+ ListenableFuture bootstrapComplete = bootstrappedPeerFactory.start();
+ Futures.addCallback(bootstrapComplete, new FutureCallback()
+ {
+ @Override
+ public void onSuccess(@Nullable PeerDHT peerDHT)
+ {
+ try
+ {
+ if (peerDHT != null)
+ {
+ P2PNode.this.peerDHT = peerDHT;
+ setupReplyHandler();
+ FuturePut futurePut = storePeerAddress();
+ futurePut.addListener(new BaseFutureListener()
+ {
+ @Override
+ public void operationComplete(BaseFuture future) throws Exception
+ {
+ if (future.isSuccess())
+ {
+ storedPeerAddress = peerDHT.peerAddress();
+ log.debug("storedPeerAddress = " + storedPeerAddress);
+ }
+ else
+ {
+ log.error("");
+ }
+ }
+
+ @Override
+ public void exceptionCaught(Throwable t) throws Exception
+ {
+ log.error(t.toString());
+ }
+ });
+ }
+ else
+ {
+ log.error("peerDHT is null");
+ }
+ } catch (IOException | ClassNotFoundException e)
+ {
+ e.printStackTrace();
+ log.error(e.toString());
+ }
+ }
+
+ @Override
+ public void onFailure(@NotNull Throwable t)
+ {
+ log.error(t.toString());
+ }
+ });
+ return bootstrapComplete;
+ }
+
+ private void setupReplyHandler()
+ {
+ peerDHT.peer().objectDataReply((sender, request) -> {
+ if (!sender.equals(peerDHT.peer().peerAddress()))
+ if (messageBroker != null) messageBroker.handleMessage(request, sender);
+ else
+ log.error("Received msg from myself. That should never happen.");
+ return null;
+ });
+ }
+
+ private void setupTimerForIPCheck()
+ {
+ Timer timer = new Timer();
+ long checkIfIPChangedPeriod = 600 * 1000;
+ timer.scheduleAtFixedRate(new TimerTask()
+ {
+ @Override
+ public void run()
+ {
+ if (peerDHT != null && !storedPeerAddress.equals(peerDHT.peerAddress()))
+ {
+ try
+ {
+ storePeerAddress();
+ } catch (IOException | ClassNotFoundException e)
+ {
+ e.printStackTrace();
+ log.error(e.toString());
+ }
+ }
+ }
+ }, checkIfIPChangedPeriod, checkIfIPChangedPeriod);
+ }
+
+ private FuturePut storePeerAddress() throws IOException, ClassNotFoundException
+ {
+ Number160 locationKey = Utils.makeSHAHash(keyPair.getPublic().getEncoded());
+ Data data = new Data(peerDHT.peerAddress());
+ return putDomainProtectedData(locationKey, data);
+ }
+
+ private void useDiscStorage(boolean useDiscStorage)
+ {
+ if (useDiscStorage)
+ {
+ try
+ {
+
+ File path = new File(StorageDirectory.getStorageDirectory().getCanonicalPath() + "/" + BitSquare.getAppName() + "_tomP2P");
+ if (!path.exists())
+ {
+ boolean created = path.mkdir();
+ if (!created)
+ throw new RuntimeException("Could not create the directory '" + path + "'");
+ }
+ storage = new StorageDisk(Number160.ZERO, path, new DSASignatureFactory());
+
+ } catch (IOException e)
+ {
+ e.printStackTrace();
+ }
+ }
+ else
+ {
+ storage = new StorageMemory();
+ }
+ }
+}
diff --git a/src/main/java/io/bitsquare/msg/SeedNodeAddress.java b/src/main/java/io/bitsquare/msg/SeedNodeAddress.java
new file mode 100644
index 0000000000..9c0c3f56cc
--- /dev/null
+++ b/src/main/java/io/bitsquare/msg/SeedNodeAddress.java
@@ -0,0 +1,81 @@
+package io.bitsquare.msg;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+public class SeedNodeAddress
+{
+ private final String id;
+ private final String ip;
+ private final int port;
+
+ public SeedNodeAddress(StaticSeedNodeAddresses staticSeedNodeAddresses)
+ {
+ this(staticSeedNodeAddresses.getId(), staticSeedNodeAddresses.getIp(), staticSeedNodeAddresses.getPort());
+ }
+
+ public SeedNodeAddress(String id, String ip, int port)
+ {
+ this.id = id;
+ this.ip = ip;
+ this.port = port;
+ }
+
+ public String getId()
+ {
+ return id;
+ }
+
+ public String getIp()
+ {
+ return ip;
+ }
+
+ public int getPort()
+ {
+ return port;
+ }
+
+
+ ///////////////////////////////////////////////////////////////////////////////////////////
+ // Enum
+ ///////////////////////////////////////////////////////////////////////////////////////////
+
+ public enum StaticSeedNodeAddresses
+ {
+ LOCALHOST("localhost", "127.0.0.1", 5001),
+ DIGITAL_OCEAN("digitalocean.bitsquare.io", "188.226.179.109", 5000);
+
+ private final String id;
+ private final String ip;
+ private final int port;
+
+ StaticSeedNodeAddresses(String id, String ip, int port)
+ {
+ this.id = id;
+ this.ip = ip;
+ this.port = port;
+ }
+
+ public static List getAllSeedNodeAddresses()
+ {
+ return new ArrayList<>(Arrays.asList(StaticSeedNodeAddresses.values()));
+ }
+
+ public String getId()
+ {
+ return id;
+ }
+
+ public String getIp()
+ {
+ return ip;
+ }
+
+ public int getPort()
+ {
+ return port;
+ }
+ }
+}
diff --git a/src/main/java/io/bitsquare/msg/listeners/ArbitratorListener.java b/src/main/java/io/bitsquare/msg/listeners/ArbitratorListener.java
index 7454e94002..b6733c472b 100644
--- a/src/main/java/io/bitsquare/msg/listeners/ArbitratorListener.java
+++ b/src/main/java/io/bitsquare/msg/listeners/ArbitratorListener.java
@@ -1,7 +1,7 @@
package io.bitsquare.msg.listeners;
import java.util.Map;
-import net.tomp2p.peers.Number160;
+import net.tomp2p.peers.Number640;
import net.tomp2p.storage.Data;
@SuppressWarnings({"EmptyMethod", "UnusedParameters"})
@@ -9,7 +9,7 @@ public interface ArbitratorListener
{
void onArbitratorAdded(Data offerData, boolean success);
- void onArbitratorsReceived(Map dataMap, boolean success);
+ void onArbitratorsReceived(Map dataMap, boolean success);
void onArbitratorRemoved(Data data, boolean success);
}
\ No newline at end of file
diff --git a/src/main/java/io/bitsquare/msg/listeners/OrderBookListener.java b/src/main/java/io/bitsquare/msg/listeners/OrderBookListener.java
index fc93ce0660..676c9c88b2 100644
--- a/src/main/java/io/bitsquare/msg/listeners/OrderBookListener.java
+++ b/src/main/java/io/bitsquare/msg/listeners/OrderBookListener.java
@@ -1,7 +1,7 @@
package io.bitsquare.msg.listeners;
import java.util.Map;
-import net.tomp2p.peers.Number160;
+import net.tomp2p.peers.Number640;
import net.tomp2p.storage.Data;
public interface OrderBookListener
@@ -9,7 +9,7 @@ public interface OrderBookListener
@SuppressWarnings("UnusedParameters")
void onOfferAdded(Data offerData, boolean success);
- void onOffersReceived(Map dataMap, boolean success);
+ void onOffersReceived(Map dataMap, boolean success);
void onOfferRemoved(Data data, boolean success);
}
\ No newline at end of file
diff --git a/src/main/java/io/bitsquare/trade/Contract.java b/src/main/java/io/bitsquare/trade/Contract.java
index 6b9d06a1eb..f0736510a4 100644
--- a/src/main/java/io/bitsquare/trade/Contract.java
+++ b/src/main/java/io/bitsquare/trade/Contract.java
@@ -1,8 +1,10 @@
package io.bitsquare.trade;
import io.bitsquare.bank.BankAccount;
+import io.bitsquare.util.DSAKeyUtil;
import java.io.Serializable;
import java.math.BigInteger;
+import java.security.PublicKey;
public class Contract implements Serializable
{
@@ -15,8 +17,8 @@ public class Contract implements Serializable
private final String takerAccountID;
private final BankAccount offererBankAccount;
private final BankAccount takerBankAccount;
- private final String offererMessagePubKeyAsHex;
- private final String takerMessagePubKeyAsHex;
+ private final String offererMessagePublicKeyAsString;
+ private final String takerMessagePublicKeyAsString;
public Contract(Offer offer,
BigInteger tradeAmount,
@@ -25,8 +27,8 @@ public class Contract implements Serializable
String takerAccountID,
BankAccount offererBankAccount,
BankAccount takerBankAccount,
- String offererMessagePubKeyAsHex,
- String takerMessagePubKeyAsHex)
+ PublicKey offererMessagePublicKey,
+ PublicKey takerMessagePublicKey)
{
this.offer = offer;
this.tradeAmount = tradeAmount;
@@ -35,8 +37,8 @@ public class Contract implements Serializable
this.takerAccountID = takerAccountID;
this.offererBankAccount = offererBankAccount;
this.takerBankAccount = takerBankAccount;
- this.offererMessagePubKeyAsHex = offererMessagePubKeyAsHex;
- this.takerMessagePubKeyAsHex = takerMessagePubKeyAsHex;
+ this.offererMessagePublicKeyAsString = DSAKeyUtil.getHexStringFromPublicKey(offererMessagePublicKey);
+ this.takerMessagePublicKeyAsString = DSAKeyUtil.getHexStringFromPublicKey(takerMessagePublicKey);
}
@@ -79,17 +81,16 @@ public class Contract implements Serializable
return takerBankAccount;
}
- public String getTakerMessagePubKeyAsHex()
+ public String getTakerMessagePublicKey()
{
- return takerMessagePubKeyAsHex;
+ return takerMessagePublicKeyAsString;
}
- public String getOffererMessagePubKeyAsHex()
+ public String getOffererMessagePublicKey()
{
- return offererMessagePubKeyAsHex;
+ return offererMessagePublicKeyAsString;
}
-
@Override
public String toString()
{
@@ -101,10 +102,8 @@ public class Contract implements Serializable
", takerAccountID='" + takerAccountID + '\'' +
", offererBankAccount=" + offererBankAccount +
", takerBankAccount=" + takerBankAccount +
- ", offererMessagePubKeyAsHex='" + offererMessagePubKeyAsHex + '\'' +
- ", takerMessagePubKeyAsHex='" + takerMessagePubKeyAsHex + '\'' +
+ ", takerMessagePublicKeyAsString=" + takerMessagePublicKeyAsString +
+ ", offererMessagePublicKeyAsString=" + offererMessagePublicKeyAsString +
'}';
}
-
-
}
diff --git a/src/main/java/io/bitsquare/trade/Offer.java b/src/main/java/io/bitsquare/trade/Offer.java
index a342ac3c37..f0f7f13371 100644
--- a/src/main/java/io/bitsquare/trade/Offer.java
+++ b/src/main/java/io/bitsquare/trade/Offer.java
@@ -6,6 +6,7 @@ import io.bitsquare.locale.Country;
import io.bitsquare.user.Arbitrator;
import java.io.Serializable;
import java.math.BigInteger;
+import java.security.PublicKey;
import java.util.*;
public class Offer implements Serializable
@@ -23,7 +24,7 @@ public class Offer implements Serializable
private final double price;
private final BigInteger amount;
private final BigInteger minAmount;
- private final String messagePubKeyAsHex;
+ private final PublicKey messagePublicKey;
private final BankAccountType bankAccountType;
private final Country bankAccountCountry;
@@ -39,7 +40,7 @@ public class Offer implements Serializable
// Constructor
///////////////////////////////////////////////////////////////////////////////////////////
- public Offer(String messagePubKeyAsHex,
+ public Offer(PublicKey messagePublicKey,
Direction direction,
double price,
BigInteger amount,
@@ -53,7 +54,7 @@ public class Offer implements Serializable
List acceptedCountries,
List acceptedLanguageLocales)
{
- this.messagePubKeyAsHex = messagePubKeyAsHex;
+ this.messagePublicKey = messagePublicKey;
this.direction = direction;
this.price = price;
this.amount = amount;
@@ -77,9 +78,9 @@ public class Offer implements Serializable
// Setters
///////////////////////////////////////////////////////////////////////////////////////////
- public String getMessagePubKeyAsHex()
+ public PublicKey getMessagePublicKey()
{
- return messagePubKeyAsHex;
+ return messagePublicKey;
}
@@ -183,7 +184,7 @@ public class Offer implements Serializable
", price=" + price +
", amount=" + amount +
", minAmount=" + minAmount +
- ", messagePubKey=" + messagePubKeyAsHex.hashCode() +
+ ", messagePubKey=" + messagePublicKey.hashCode() +
", bankAccountTypeEnum=" + bankAccountType +
", bankAccountCountryLocale=" + bankAccountCountry +
", collateral=" + collateral +
diff --git a/src/main/java/io/bitsquare/trade/Trading.java b/src/main/java/io/bitsquare/trade/Trading.java
index 700bc91c35..d0b4146847 100644
--- a/src/main/java/io/bitsquare/trade/Trading.java
+++ b/src/main/java/io/bitsquare/trade/Trading.java
@@ -136,11 +136,11 @@ public class Trading
}
public void removeOffer(Offer offer)
- {
- if (!offers.containsKey(offer.getId()))
+ { //TODO
+ /* if (!offers.containsKey(offer.getId()))
{
throw new IllegalStateException("offers does not contain the offer with the ID " + offer.getId());
- }
+ }*/
offers.remove(offer.getId());
saveOffers();
diff --git a/src/main/java/io/bitsquare/trade/orderbook/OrderBook.java b/src/main/java/io/bitsquare/trade/orderbook/OrderBook.java
index 4aeca9d15c..e26d008b7a 100644
--- a/src/main/java/io/bitsquare/trade/orderbook/OrderBook.java
+++ b/src/main/java/io/bitsquare/trade/orderbook/OrderBook.java
@@ -21,11 +21,17 @@ import javafx.collections.ObservableList;
import javafx.collections.transformation.FilteredList;
import javafx.collections.transformation.SortedList;
import javax.inject.Inject;
-import net.tomp2p.peers.Number160;
+import net.tomp2p.peers.Number640;
import net.tomp2p.storage.Data;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+/*
+TODO
+remove dependencies to tomp2p
+import net.tomp2p.peers.Number160;
+import net.tomp2p.storage.Data;
+ */
public class OrderBook implements OrderBookListener
{
private static final Logger log = LoggerFactory.getLogger(OrderBook.class);
@@ -59,12 +65,12 @@ public class OrderBook implements OrderBookListener
public void init()
{
- messageFacade.addMessageListener(this);
+ messageFacade.addOrderBookListener(this);
}
public void cleanup()
{
- messageFacade.removeMessageListener(this);
+ messageFacade.removeOrderBookListener(this);
}
public void loadOffers()
@@ -177,7 +183,7 @@ public class OrderBook implements OrderBookListener
{
try
{
- Object offerDataObject = offerData.getObject();
+ Object offerDataObject = offerData.object();
if (offerDataObject instanceof Offer)
{
Offer offer = (Offer) offerDataObject;
@@ -190,7 +196,7 @@ public class OrderBook implements OrderBookListener
}
@Override
- public void onOffersReceived(Map dataMap, boolean success)
+ public void onOffersReceived(Map dataMap, boolean success)
{
if (success && dataMap != null)
{
@@ -200,7 +206,7 @@ public class OrderBook implements OrderBookListener
{
try
{
- Object offerDataObject = offerData.getObject();
+ Object offerDataObject = offerData.object();
if (offerDataObject instanceof Offer)
{
Offer offer = (Offer) offerDataObject;
@@ -226,7 +232,7 @@ public class OrderBook implements OrderBookListener
{
try
{
- Object offerDataObject = offerData.getObject();
+ Object offerDataObject = offerData.object();
if (offerDataObject instanceof Offer)
{
Offer offer = (Offer) offerDataObject;
diff --git a/src/main/java/io/bitsquare/trade/protocol/offerer/ProtocolForOffererAsBuyer.java b/src/main/java/io/bitsquare/trade/protocol/offerer/ProtocolForOffererAsBuyer.java
index f39486481b..48412736be 100644
--- a/src/main/java/io/bitsquare/trade/protocol/offerer/ProtocolForOffererAsBuyer.java
+++ b/src/main/java/io/bitsquare/trade/protocol/offerer/ProtocolForOffererAsBuyer.java
@@ -16,6 +16,7 @@ import io.bitsquare.trade.protocol.taker.RequestOffererPublishDepositTxMessage;
import io.bitsquare.trade.protocol.taker.TakeOfferFeePayedMessage;
import io.bitsquare.user.User;
import java.math.BigInteger;
+import java.security.PublicKey;
import net.tomp2p.peers.PeerAddress;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
@@ -76,7 +77,7 @@ public class ProtocolForOffererAsBuyer
private final String arbitratorPubKey;
private final BankAccount bankAccount;
private final String accountId;
- private final String messagePubKey;
+ private final PublicKey messagePublicKey;
private final ECKey accountKey;
private final String payoutAddress;
@@ -90,7 +91,7 @@ public class ProtocolForOffererAsBuyer
private String peersPayoutAddress;
private String peersAccountId;
private BankAccount peersBankAccount;
- private String peersMessagePubKey;
+ private PublicKey peersMessagePublicKey;
private String peersContractAsJson;
private String signedTakerDepositTxAsHex;
private String txConnOutAsHex;
@@ -129,7 +130,7 @@ public class ProtocolForOffererAsBuyer
bankAccount = user.getBankAccount(trade.getOffer().getBankAccountId());
accountId = user.getAccountId();
- messagePubKey = user.getMessagePubKeyAsHex();
+ messagePublicKey = user.getMessagePublicKey();
accountKey = walletFacade.getRegistrationAddressInfo().getKey();
payoutAddress = walletFacade.getAddressInfoByTradeID(tradeId).getAddressString();
@@ -241,7 +242,7 @@ public class ProtocolForOffererAsBuyer
String peersPayoutAddress = nonEmptyStringOf(message.getTakerPayoutAddress());
String peersAccountId = nonEmptyStringOf(message.getTakerAccountId());
BankAccount peersBankAccount = checkNotNull(message.getTakerBankAccount());
- String peersMessagePubKey = nonEmptyStringOf(message.getTakerMessagePubKey());
+ PublicKey peersMessagePublicKey = checkNotNull(message.getTakerMessagePublicKey());
String peersContractAsJson = nonEmptyStringOf(message.getTakerContractAsJson());
String signedTakerDepositTxAsHex = nonEmptyStringOf(message.getSignedTakerDepositTxAsHex());
String txConnOutAsHex = nonEmptyStringOf(message.getTxConnOutAsHex());
@@ -253,7 +254,7 @@ public class ProtocolForOffererAsBuyer
this.peersPayoutAddress = peersPayoutAddress;
this.peersAccountId = peersAccountId;
this.peersBankAccount = peersBankAccount;
- this.peersMessagePubKey = peersMessagePubKey;
+ this.peersMessagePublicKey = peersMessagePublicKey;
this.peersContractAsJson = peersContractAsJson;
this.signedTakerDepositTxAsHex = signedTakerDepositTxAsHex;
this.txConnOutAsHex = txConnOutAsHex;
@@ -277,12 +278,12 @@ public class ProtocolForOffererAsBuyer
accountId,
tradeAmount,
takeOfferFeeTxId,
- messagePubKey,
+ messagePublicKey,
offer,
peersAccountId,
bankAccount,
peersBankAccount,
- peersMessagePubKey,
+ peersMessagePublicKey,
peersContractAsJson,
accountKey);
}
diff --git a/src/main/java/io/bitsquare/trade/protocol/offerer/VerifyAndSignContract.java b/src/main/java/io/bitsquare/trade/protocol/offerer/VerifyAndSignContract.java
index 483d4996ac..11d5c6c022 100644
--- a/src/main/java/io/bitsquare/trade/protocol/offerer/VerifyAndSignContract.java
+++ b/src/main/java/io/bitsquare/trade/protocol/offerer/VerifyAndSignContract.java
@@ -8,6 +8,7 @@ import io.bitsquare.trade.Offer;
import io.bitsquare.trade.protocol.FaultHandler;
import io.bitsquare.util.Utilities;
import java.math.BigInteger;
+import java.security.PublicKey;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -21,17 +22,17 @@ public class VerifyAndSignContract
String accountId,
BigInteger tradeAmount,
String takeOfferFeeTxId,
- String messagePubKeyAsHex,
+ PublicKey messagePublicKey,
Offer offer,
String peersAccountId,
BankAccount bankAccount,
BankAccount peersBankAccount,
- String takerMessagePubKey,
+ PublicKey takerMessagePublicKey,
String peersContractAsJson,
ECKey registrationKey)
{
log.trace("Run task");
- Contract contract = new Contract(offer, tradeAmount, takeOfferFeeTxId, accountId, peersAccountId, bankAccount, peersBankAccount, messagePubKeyAsHex, takerMessagePubKey);
+ Contract contract = new Contract(offer, tradeAmount, takeOfferFeeTxId, accountId, peersAccountId, bankAccount, peersBankAccount, messagePublicKey, takerMessagePublicKey);
String contractAsJson = Utilities.objectToJson(contract);
// log.trace("Offerer contract created: " + contract);
diff --git a/src/main/java/io/bitsquare/trade/protocol/taker/CreateAndSignContract.java b/src/main/java/io/bitsquare/trade/protocol/taker/CreateAndSignContract.java
index 405da7bc79..cb713030ba 100644
--- a/src/main/java/io/bitsquare/trade/protocol/taker/CreateAndSignContract.java
+++ b/src/main/java/io/bitsquare/trade/protocol/taker/CreateAndSignContract.java
@@ -8,6 +8,7 @@ import io.bitsquare.trade.Offer;
import io.bitsquare.trade.protocol.FaultHandler;
import io.bitsquare.util.Utilities;
import java.math.BigInteger;
+import java.security.PublicKey;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -23,8 +24,8 @@ public class CreateAndSignContract
String takeOfferFeeTxId,
String accountId,
BankAccount bankAccount,
- String peersMessagePubKeyAsHex,
- String messagePubKeyAsHex,
+ PublicKey peersMessagePublicKey,
+ PublicKey messagePublicKey,
String peersAccountId,
BankAccount peersBankAccount,
ECKey registrationKey)
@@ -32,7 +33,7 @@ public class CreateAndSignContract
log.trace("Run task");
try
{
- Contract contract = new Contract(offer, tradeAmount, takeOfferFeeTxId, peersAccountId, accountId, peersBankAccount, bankAccount, peersMessagePubKeyAsHex, messagePubKeyAsHex);
+ Contract contract = new Contract(offer, tradeAmount, takeOfferFeeTxId, peersAccountId, accountId, peersBankAccount, bankAccount, peersMessagePublicKey, messagePublicKey);
String contractAsJson = Utilities.objectToJson(contract);
String signature = cryptoFacade.signContract(registrationKey, contractAsJson);
diff --git a/src/main/java/io/bitsquare/trade/protocol/taker/GetPeerAddress.java b/src/main/java/io/bitsquare/trade/protocol/taker/GetPeerAddress.java
index 30227161c4..aee368b5f0 100644
--- a/src/main/java/io/bitsquare/trade/protocol/taker/GetPeerAddress.java
+++ b/src/main/java/io/bitsquare/trade/protocol/taker/GetPeerAddress.java
@@ -3,6 +3,7 @@ package io.bitsquare.trade.protocol.taker;
import io.bitsquare.msg.MessageFacade;
import io.bitsquare.msg.listeners.GetPeerAddressListener;
import io.bitsquare.trade.protocol.FaultHandler;
+import java.security.PublicKey;
import net.tomp2p.peers.PeerAddress;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -11,10 +12,10 @@ public class GetPeerAddress
{
private static final Logger log = LoggerFactory.getLogger(GetPeerAddress.class);
- public static void run(ResultHandler resultHandler, FaultHandler faultHandler, MessageFacade messageFacade, String messagePubKeyAsHex)
+ public static void run(ResultHandler resultHandler, FaultHandler faultHandler, MessageFacade messageFacade, PublicKey messagePublicKey)
{
log.trace("Run task");
- messageFacade.getPeerAddress(messagePubKeyAsHex, new GetPeerAddressListener()
+ messageFacade.getPeerAddress(messagePublicKey, new GetPeerAddressListener()
{
@Override
public void onResult(PeerAddress peerAddress)
diff --git a/src/main/java/io/bitsquare/trade/protocol/taker/ProtocolForTakerAsSeller.java b/src/main/java/io/bitsquare/trade/protocol/taker/ProtocolForTakerAsSeller.java
index 7a6718d5b8..28fc32c346 100644
--- a/src/main/java/io/bitsquare/trade/protocol/taker/ProtocolForTakerAsSeller.java
+++ b/src/main/java/io/bitsquare/trade/protocol/taker/ProtocolForTakerAsSeller.java
@@ -16,6 +16,7 @@ import io.bitsquare.trade.protocol.offerer.RequestTakerDepositPaymentMessage;
import io.bitsquare.trade.protocol.offerer.RespondToTakeOfferRequestMessage;
import io.bitsquare.user.User;
import java.math.BigInteger;
+import java.security.PublicKey;
import net.tomp2p.peers.PeerAddress;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -66,11 +67,11 @@ public class ProtocolForTakerAsSeller
private final String tradeId;
private final BankAccount bankAccount;
private final String accountId;
- private final String messagePubKey;
+ private final PublicKey messagePublicKey;
private final BigInteger tradeAmount;
private final String pubKeyForThatTrade;
private final ECKey accountKey;
- private final String peersMessagePubKey;
+ private final PublicKey peersMessagePublicKey;
private final BigInteger collateral;
private final String arbitratorPubKey;
@@ -122,11 +123,11 @@ public class ProtocolForTakerAsSeller
collateral = trade.getCollateralAmount();
arbitratorPubKey = trade.getOffer().getArbitrator().getPubKeyAsHex();
- peersMessagePubKey = offer.getMessagePubKeyAsHex();
+ peersMessagePublicKey = offer.getMessagePublicKey();
bankAccount = user.getBankAccount(offer.getBankAccountId());
accountId = user.getAccountId();
- messagePubKey = user.getMessagePubKeyAsHex();
+ messagePublicKey = user.getMessagePublicKey();
pubKeyForThatTrade = walletFacade.getAddressInfoByTradeID(tradeId).getPubKeyAsHexString();
accountKey = walletFacade.getRegistrationAddressInfo().getKey();
@@ -138,7 +139,7 @@ public class ProtocolForTakerAsSeller
{
log.debug("start called " + step++);
state = State.GetPeerAddress;
- GetPeerAddress.run(this::onResultGetPeerAddress, this::onFault, messageFacade, peersMessagePubKey);
+ GetPeerAddress.run(this::onResultGetPeerAddress, this::onFault, messageFacade, peersMessagePublicKey);
}
public void onResultGetPeerAddress(PeerAddress peerAddress)
@@ -176,6 +177,7 @@ public class ProtocolForTakerAsSeller
else
{
listener.onTakeOfferRequestRejected(trade);
+ // exit case
}
}
@@ -239,8 +241,8 @@ public class ProtocolForTakerAsSeller
takeOfferFeeTxId,
accountId,
bankAccount,
- peersMessagePubKey,
- messagePubKey,
+ peersMessagePublicKey,
+ messagePublicKey,
peersAccountId,
peersBankAccount,
accountKey);
@@ -272,7 +274,7 @@ public class ProtocolForTakerAsSeller
walletFacade,
bankAccount,
accountId,
- messagePubKey,
+ messagePublicKey,
tradeId,
contractAsJson,
takerSignature,
diff --git a/src/main/java/io/bitsquare/trade/protocol/taker/RequestOffererPublishDepositTxMessage.java b/src/main/java/io/bitsquare/trade/protocol/taker/RequestOffererPublishDepositTxMessage.java
index c06dc90cae..4eb1b3bba7 100644
--- a/src/main/java/io/bitsquare/trade/protocol/taker/RequestOffererPublishDepositTxMessage.java
+++ b/src/main/java/io/bitsquare/trade/protocol/taker/RequestOffererPublishDepositTxMessage.java
@@ -3,6 +3,7 @@ package io.bitsquare.trade.protocol.taker;
import io.bitsquare.bank.BankAccount;
import io.bitsquare.msg.TradeMessage;
import java.io.Serializable;
+import java.security.PublicKey;
public class RequestOffererPublishDepositTxMessage implements Serializable, TradeMessage
{
@@ -10,7 +11,7 @@ public class RequestOffererPublishDepositTxMessage implements Serializable, Trad
private final String tradeId;
private BankAccount bankAccount;
private String accountID;
- private String takerMessagePubKey;
+ private PublicKey takerMessagePublicKey;
private String signedTakerDepositTxAsHex;
private String txScriptSigAsHex;
private String txConnOutAsHex;
@@ -25,7 +26,7 @@ public class RequestOffererPublishDepositTxMessage implements Serializable, Trad
public RequestOffererPublishDepositTxMessage(String tradeId,
BankAccount bankAccount,
String accountID,
- String takerMessagePubKey,
+ PublicKey takerMessagePublicKey,
String signedTakerDepositTxAsHex,
String txScriptSigAsHex,
String txConnOutAsHex,
@@ -39,7 +40,7 @@ public class RequestOffererPublishDepositTxMessage implements Serializable, Trad
this.tradeId = tradeId;
this.bankAccount = bankAccount;
this.accountID = accountID;
- this.takerMessagePubKey = takerMessagePubKey;
+ this.takerMessagePublicKey = takerMessagePublicKey;
this.signedTakerDepositTxAsHex = signedTakerDepositTxAsHex;
this.txScriptSigAsHex = txScriptSigAsHex;
this.txConnOutAsHex = txConnOutAsHex;
@@ -72,9 +73,9 @@ public class RequestOffererPublishDepositTxMessage implements Serializable, Trad
return accountID;
}
- public String getTakerMessagePubKey()
+ public PublicKey getTakerMessagePublicKey()
{
- return takerMessagePubKey;
+ return takerMessagePublicKey;
}
public String getSignedTakerDepositTxAsHex()
diff --git a/src/main/java/io/bitsquare/trade/protocol/taker/SendSignedTakerDepositTxAsHex.java b/src/main/java/io/bitsquare/trade/protocol/taker/SendSignedTakerDepositTxAsHex.java
index 928d7e99ca..47cfda2eb5 100644
--- a/src/main/java/io/bitsquare/trade/protocol/taker/SendSignedTakerDepositTxAsHex.java
+++ b/src/main/java/io/bitsquare/trade/protocol/taker/SendSignedTakerDepositTxAsHex.java
@@ -8,6 +8,7 @@ import io.bitsquare.msg.MessageFacade;
import io.bitsquare.msg.listeners.OutgoingTradeMessageListener;
import io.bitsquare.trade.protocol.FaultHandler;
import io.bitsquare.trade.protocol.ResultHandler;
+import java.security.PublicKey;
import net.tomp2p.peers.PeerAddress;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -23,7 +24,7 @@ public class SendSignedTakerDepositTxAsHex
WalletFacade walletFacade,
BankAccount bankAccount,
String accountId,
- String messagePubKeyAsHex,
+ PublicKey messagePublicKey,
String tradeId,
String contractAsJson,
String takerSignature,
@@ -36,7 +37,7 @@ public class SendSignedTakerDepositTxAsHex
RequestOffererPublishDepositTxMessage tradeMessage = new RequestOffererPublishDepositTxMessage(tradeId,
bankAccount,
accountId,
- messagePubKeyAsHex,
+ messagePublicKey,
Utils.bytesToHexString(signedTakerDepositTx.bitcoinSerialize()),
Utils.bytesToHexString(signedTakerDepositTx.getInput(1).getScriptBytes()),
Utils.bytesToHexString(signedTakerDepositTx.getInput(1)
diff --git a/src/main/java/io/bitsquare/user/User.java b/src/main/java/io/bitsquare/user/User.java
index 4c98bbf262..4ede94402e 100644
--- a/src/main/java/io/bitsquare/user/User.java
+++ b/src/main/java/io/bitsquare/user/User.java
@@ -1,7 +1,10 @@
package io.bitsquare.user;
import io.bitsquare.bank.BankAccount;
+import io.bitsquare.util.DSAKeyUtil;
import java.io.Serializable;
+import java.security.KeyPair;
+import java.security.PublicKey;
import java.util.ArrayList;
import java.util.List;
import javafx.beans.property.SimpleBooleanProperty;
@@ -11,19 +14,17 @@ public class User implements Serializable
private static final long serialVersionUID = 7409078808248518638L;
transient private final SimpleBooleanProperty bankAccountChangedProperty = new SimpleBooleanProperty();
+ transient private KeyPair messageKeyPair = DSAKeyUtil.getKeyPair();
-
+ private PublicKey messagePublicKey;
private String accountID;
-
- private String messagePubKeyAsHex;
private boolean isOnline;
-
private List bankAccounts = new ArrayList<>();
-
private BankAccount currentBankAccount = null;
public User()
{
+ messagePublicKey = messageKeyPair.getPublic();
}
@@ -36,11 +37,14 @@ public class User implements Serializable
if (savedUser != null)
{
accountID = savedUser.getAccountId();
- messagePubKeyAsHex = savedUser.getMessagePubKeyAsHex();
+ // TODO handled by DSAKeyUtil -> change that storage check is only done here
+ // messagePublicKey = savedUser.getMessagePublicKey();
isOnline = savedUser.getIsOnline();
bankAccounts = savedUser.getBankAccounts();
currentBankAccount = savedUser.getCurrentBankAccount();
}
+
+ messagePublicKey = messageKeyPair.getPublic();
}
public void addBankAccount(BankAccount bankAccount)
@@ -94,17 +98,6 @@ public class User implements Serializable
}
- public String getMessagePubKeyAsHex()
- {
- return messagePubKeyAsHex;
- }
-
- public void setMessagePubKeyAsHex(String messageID)
- {
- this.messagePubKeyAsHex = messageID;
- }
-
-
public String getAccountId()
{
return accountID;
@@ -172,17 +165,27 @@ public class User implements Serializable
return bankAccountChangedProperty;
}
-
@Override
public String toString()
{
return "User{" +
"bankAccountChangedProperty=" + bankAccountChangedProperty +
+ ", messageKeyPair=" + messageKeyPair +
+ ", messagePublicKey=" + messagePublicKey +
", accountID='" + accountID + '\'' +
- ", messagePubKeyAsHex='" + messagePubKeyAsHex + '\'' +
", isOnline=" + isOnline +
", bankAccounts=" + bankAccounts +
", currentBankAccount=" + currentBankAccount +
'}';
}
+
+ public KeyPair getMessageKeyPair()
+ {
+ return messageKeyPair;
+ }
+
+ public PublicKey getMessagePublicKey()
+ {
+ return messagePublicKey;
+ }
}
diff --git a/src/main/java/lighthouse/protocol/LHUtils.java b/src/main/java/lighthouse/protocol/LHUtils.java
new file mode 100644
index 0000000000..de4a837eab
--- /dev/null
+++ b/src/main/java/lighthouse/protocol/LHUtils.java
@@ -0,0 +1,139 @@
+package lighthouse.protocol;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+import java.util.stream.Stream;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class LHUtils
+{
+ private static final Logger log = LoggerFactory.getLogger(LHUtils.class);
+
+ public static List listDir(Path dir) throws IOException
+ {
+ List contents = new LinkedList<>();
+ try (Stream list = Files.list(dir))
+ {
+ list.forEach(contents::add);
+ }
+ return contents;
+ }
+
+ //region Generic Java 8 enhancements
+ public interface UncheckedRun
+ {
+ public T run() throws Throwable;
+ }
+
+ public interface UncheckedRunnable
+ {
+ public void run() throws Throwable;
+ }
+
+ public static T unchecked(UncheckedRun run)
+ {
+ try
+ {
+ return run.run();
+ } catch (Throwable throwable)
+ {
+ throw new RuntimeException(throwable);
+ }
+ }
+
+ public static void uncheck(UncheckedRunnable run)
+ {
+ try
+ {
+ run.run();
+ } catch (Throwable throwable)
+ {
+ throw new RuntimeException(throwable);
+ }
+ }
+
+ public static void ignoreAndLog(UncheckedRunnable runnable)
+ {
+ try
+ {
+ runnable.run();
+ } catch (Throwable t)
+ {
+ log.error("Ignoring error", t);
+ }
+ }
+
+ public static T ignoredAndLogged(UncheckedRun runnable)
+ {
+ try
+ {
+ return runnable.run();
+ } catch (Throwable t)
+ {
+ log.error("Ignoring error", t);
+ return null;
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ public static T checkedGet(Future future) throws E
+ {
+ try
+ {
+ return future.get();
+ } catch (InterruptedException e)
+ {
+ throw new RuntimeException(e);
+ } catch (ExecutionException e)
+ {
+ throw (E) e.getCause();
+ }
+ }
+
+ public static boolean didThrow(UncheckedRun run)
+ {
+ try
+ {
+ run.run();
+ return false;
+ } catch (Throwable throwable)
+ {
+ return true;
+ }
+ }
+
+ public static boolean didThrow(UncheckedRunnable run)
+ {
+ try
+ {
+ run.run();
+ return false;
+ } catch (Throwable throwable)
+ {
+ return true;
+ }
+ }
+
+ public static T stopwatched(String description, UncheckedRun run)
+ {
+ long now = System.currentTimeMillis();
+ T result = unchecked(run::run);
+ log.info("{}: {}ms", description, System.currentTimeMillis() - now);
+ return result;
+ }
+
+ public static void stopwatch(String description, UncheckedRunnable run)
+ {
+ long now = System.currentTimeMillis();
+ uncheck(run::run);
+ log.info("{}: {}ms", description, System.currentTimeMillis() - now);
+ }
+
+ //endregion
+}
\ No newline at end of file
diff --git a/src/main/java/lighthouse/threading/AffinityExecutor.java b/src/main/java/lighthouse/threading/AffinityExecutor.java
new file mode 100644
index 0000000000..c43cba8baa
--- /dev/null
+++ b/src/main/java/lighthouse/threading/AffinityExecutor.java
@@ -0,0 +1,171 @@
+package lighthouse.threading;
+
+import com.google.common.util.concurrent.Uninterruptibles;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.atomic.AtomicReference;
+import javafx.application.Platform;
+import lighthouse.protocol.LHUtils;
+
+import static com.google.common.base.Preconditions.checkState;
+
+/**
+ * An extended executor interface that supports thread affinity assertions and short circuiting.
+ */
+public interface AffinityExecutor extends Executor
+{
+ /**
+ * Returns true if the current thread is equal to the thread this executor is backed by.
+ */
+ public boolean isOnThread();
+
+ /**
+ * Throws an IllegalStateException if the current thread is equal to the thread this executor is backed by.
+ */
+ public void checkOnThread();
+
+ /**
+ * If isOnThread() then runnable is invoked immediately, otherwise the closure is queued onto the backing thread.
+ */
+ public void executeASAP(LHUtils.UncheckedRunnable runnable);
+
+ public abstract static class BaseAffinityExecutor implements AffinityExecutor
+ {
+ protected final Thread.UncaughtExceptionHandler exceptionHandler;
+
+ protected BaseAffinityExecutor()
+ {
+ exceptionHandler = Thread.currentThread().getUncaughtExceptionHandler();
+ }
+
+ @Override
+ public abstract boolean isOnThread();
+
+ @Override
+ public void checkOnThread()
+ {
+ checkState(isOnThread(), "On wrong thread: %s", Thread.currentThread());
+ }
+
+ @Override
+ public void executeASAP(LHUtils.UncheckedRunnable runnable)
+ {
+ final Runnable command = () -> {
+ try
+ {
+ runnable.run();
+ } catch (Throwable throwable)
+ {
+ exceptionHandler.uncaughtException(Thread.currentThread(), throwable);
+ }
+ };
+ if (isOnThread())
+ command.run();
+ else
+ {
+ execute(command);
+ }
+ }
+
+ // Must comply with the Executor definition w.r.t. exceptions here.
+ @Override
+ public abstract void execute(Runnable command);
+ }
+
+ public static AffinityExecutor UI_THREAD = new BaseAffinityExecutor()
+ {
+ @Override
+ public boolean isOnThread()
+ {
+ return Platform.isFxApplicationThread();
+ }
+
+ @Override
+ public void execute(Runnable command)
+ {
+ Platform.runLater(command);
+ }
+ };
+
+ public static AffinityExecutor SAME_THREAD = new BaseAffinityExecutor()
+ {
+ @Override
+ public boolean isOnThread()
+ {
+ return true;
+ }
+
+ @Override
+ public void execute(Runnable command)
+ {
+ command.run();
+ }
+ };
+
+ public static class ServiceAffinityExecutor extends BaseAffinityExecutor
+ {
+ protected AtomicReference whichThread = new AtomicReference<>(null);
+ private final Thread.UncaughtExceptionHandler handler = Thread.currentThread().getUncaughtExceptionHandler();
+ public final ScheduledExecutorService service;
+
+ public ServiceAffinityExecutor(String threadName)
+ {
+ service = Executors.newSingleThreadScheduledExecutor(runnable -> {
+ Thread thread = new Thread(runnable);
+ thread.setDaemon(true);
+ thread.setName(threadName);
+ thread.setUncaughtExceptionHandler(handler);
+ whichThread.set(thread);
+ return thread;
+ });
+ }
+
+ @Override
+ public boolean isOnThread()
+ {
+ return Thread.currentThread() == whichThread.get();
+ }
+
+ @Override
+ public void execute(Runnable command)
+ {
+ service.execute(command);
+ }
+ }
+
+ /**
+ * An executor useful for unit tests: allows the current thread to block until a command arrives from another
+ * thread, which is then executed. Inbound closures/commands stack up until they are cleared by looping.
+ */
+ public static class Gate extends BaseAffinityExecutor
+ {
+ private final Thread thisThread = Thread.currentThread();
+ private final LinkedBlockingQueue commandQ = new LinkedBlockingQueue<>();
+
+ @Override
+ public boolean isOnThread()
+ {
+ return Thread.currentThread() == thisThread;
+ }
+
+ @Override
+ public void execute(Runnable command)
+ {
+ Uninterruptibles.putUninterruptibly(commandQ, command);
+ }
+
+ public void waitAndRun()
+ {
+ final Runnable runnable = Uninterruptibles.takeUninterruptibly(commandQ);
+ System.err.println("Gate running " + runnable);
+ runnable.run();
+ }
+
+ public int getTaskQueueSize()
+ {
+ return commandQ.size();
+ }
+ }
+}
diff --git a/src/main/java/lighthouse/threading/ConcatenatingList.java b/src/main/java/lighthouse/threading/ConcatenatingList.java
new file mode 100644
index 0000000000..010906bc1b
--- /dev/null
+++ b/src/main/java/lighthouse/threading/ConcatenatingList.java
@@ -0,0 +1,101 @@
+package lighthouse.threading;
+
+import java.util.ArrayList;
+import java.util.List;
+import javafx.collections.ListChangeListener;
+import javafx.collections.ObservableList;
+import javafx.collections.ObservableListBase;
+import javafx.collections.WeakListChangeListener;
+
+/**
+ * This list is created by dynamically concatenating all the source lists together.
+ */
+public class ConcatenatingList extends ObservableListBase implements ObservableList
+{
+ private List> sources = new ArrayList<>();
+ private ListChangeListener listener = this::sourceChanged;
+
+ @SafeVarargs
+ public ConcatenatingList(ObservableList... source)
+ {
+ super();
+ for (ObservableList s : source)
+ {
+ sources.add(s);
+ s.addListener(new WeakListChangeListener(listener));
+ }
+ if (sources.isEmpty())
+ throw new IllegalArgumentException();
+ }
+
+ private int calculateOffset(ObservableList extends T> source)
+ {
+ int cursor = 0;
+ for (ObservableList ts : sources)
+ {
+ if (ts == source) return cursor;
+ cursor += ts.size();
+ }
+ return cursor;
+ }
+
+ private void sourceChanged(ListChangeListener.Change extends T> c)
+ {
+ ObservableList extends T> source = c.getList();
+ int offset = calculateOffset(source);
+ beginChange();
+ while (c.next())
+ {
+ if (c.wasPermutated())
+ {
+ int[] perm = new int[c.getTo() - c.getFrom()];
+ for (int i = c.getFrom(); i < c.getTo(); i++)
+ perm[i - c.getFrom()] = c.getPermutation(i) + offset;
+ nextPermutation(c.getFrom() + offset, c.getTo() + offset, perm);
+ }
+ else if (c.wasUpdated())
+ {
+ for (int i = c.getFrom(); i < c.getTo(); i++)
+ {
+ nextUpdate(i + offset);
+ }
+ }
+ else
+ {
+ if (c.wasRemoved())
+ {
+ // Removed should come first to properly handle replacements, then add.
+ nextRemove(c.getFrom() + offset, c.getRemoved());
+ }
+ if (c.wasAdded())
+ {
+ nextAdd(c.getFrom() + offset, c.getTo() + offset);
+ }
+ }
+ }
+ endChange();
+ }
+
+ @Override
+ public T get(int index)
+ {
+ for (ObservableList source : sources)
+ {
+ if (index < source.size())
+ {
+ return source.get(index);
+ }
+ else
+ {
+ index -= source.size();
+ }
+ }
+ throw new IndexOutOfBoundsException();
+ }
+
+ @Override
+ public int size()
+ {
+ return sources.stream().mapToInt(List::size).sum();
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/lighthouse/threading/MappedList.java b/src/main/java/lighthouse/threading/MappedList.java
new file mode 100644
index 0000000000..91cfae0320
--- /dev/null
+++ b/src/main/java/lighthouse/threading/MappedList.java
@@ -0,0 +1,113 @@
+package lighthouse.threading;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+import javafx.collections.ListChangeListener;
+import javafx.collections.ObservableList;
+import javafx.collections.transformation.TransformationList;
+
+/**
+ * Maps elements of type F to E with change listeners working as expected.
+ */
+public class MappedList extends TransformationList
+{
+ private final Function mapper;
+ private final ArrayList mapped;
+
+ /**
+ * Creates a new MappedList list wrapped around the source list.
+ * Each element will have the given function applied to it, such that the list is cast through the mapper.
+ */
+ public MappedList(ObservableList extends F> source, Function mapper)
+ {
+ super(source);
+ this.mapper = mapper;
+ this.mapped = new ArrayList<>(source.size());
+ mapAll();
+ }
+
+ private void mapAll()
+ {
+ mapped.clear();
+ for (F val : getSource())
+ mapped.add(mapper.apply(val));
+ }
+
+ @Override
+ protected void sourceChanged(ListChangeListener.Change extends F> c)
+ {
+ // Is all this stuff right for every case? Probably it doesn't matter for this app.
+ beginChange();
+ while (c.next())
+ {
+ if (c.wasPermutated())
+ {
+ int[] perm = new int[c.getTo() - c.getFrom()];
+ for (int i = c.getFrom(); i < c.getTo(); i++)
+ perm[i - c.getFrom()] = c.getPermutation(i);
+ nextPermutation(c.getFrom(), c.getTo(), perm);
+ }
+ else if (c.wasUpdated())
+ {
+ for (int i = c.getFrom(); i < c.getTo(); i++)
+ {
+ remapIndex(i);
+ nextUpdate(i);
+ }
+ }
+ else
+ {
+ if (c.wasRemoved())
+ {
+ // Removed should come first to properly handle replacements, then add.
+ List removed = mapped.subList(c.getFrom(), c.getFrom() + c.getRemovedSize());
+ ArrayList duped = new ArrayList<>(removed);
+ removed.clear();
+ nextRemove(c.getFrom(), duped);
+ }
+ if (c.wasAdded())
+ {
+ for (int i = c.getFrom(); i < c.getTo(); i++)
+ {
+ mapped.addAll(c.getFrom(), c.getAddedSubList().stream().map(mapper).collect(Collectors.toList()));
+ remapIndex(i);
+ }
+ nextAdd(c.getFrom(), c.getTo());
+ }
+ }
+ }
+ endChange();
+ }
+
+ private void remapIndex(int i)
+ {
+ if (i >= mapped.size())
+ {
+ for (int j = mapped.size(); j <= i; j++)
+ {
+ mapped.add(mapper.apply(getSource().get(j)));
+ }
+ }
+ mapped.set(i, mapper.apply(getSource().get(i)));
+ }
+
+ @Override
+ public int getSourceIndex(int index)
+ {
+ return index;
+ }
+
+ @Override
+ public E get(int index)
+ {
+ return mapped.get(index);
+ }
+
+ @Override
+ public int size()
+ {
+ return mapped.size();
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/lighthouse/threading/MarshallingObservers.java b/src/main/java/lighthouse/threading/MarshallingObservers.java
new file mode 100644
index 0000000000..e0d4a1380f
--- /dev/null
+++ b/src/main/java/lighthouse/threading/MarshallingObservers.java
@@ -0,0 +1,43 @@
+package lighthouse.threading;
+
+import java.util.concurrent.Executor;
+import javafx.beans.InvalidationListener;
+import javafx.beans.Observable;
+import javafx.collections.*;
+
+/**
+ * An attempt to make multi-threading and observable/reactive UI programming work together inside JavaFX without too
+ * many headaches. This class allows you to register change listeners on the target Observable which will be
+ * run with the given {@link java.util.concurrent.Executor}. In this way an observable collection which is updated by
+ * one thread can be observed from another thread without needing to use explicit locks or explicit marshalling.
+ */
+public class MarshallingObservers
+{
+ public static InvalidationListener addListener(Observable observable, InvalidationListener listener, Executor executor)
+ {
+ InvalidationListener l = x -> executor.execute(() -> listener.invalidated(x));
+ observable.addListener(l);
+ return l;
+ }
+
+ public static ListChangeListener addListener(ObservableList observable, ListChangeListener listener, Executor executor)
+ {
+ ListChangeListener l = (ListChangeListener.Change extends T> c) -> executor.execute(() -> listener.onChanged(c));
+ observable.addListener(l);
+ return l;
+ }
+
+ public static SetChangeListener addListener(ObservableSet observable, SetChangeListener listener, Executor executor)
+ {
+ SetChangeListener l = (SetChangeListener.Change extends T> c) -> executor.execute(() -> listener.onChanged(c));
+ observable.addListener(l);
+ return l;
+ }
+
+ public static MapChangeListener addListener(ObservableMap observable, MapChangeListener listener, Executor executor)
+ {
+ MapChangeListener l = (MapChangeListener.Change extends K, ? extends V> c) -> executor.execute(() -> listener.onChanged(c));
+ observable.addListener(l);
+ return l;
+ }
+}
diff --git a/src/main/java/lighthouse/threading/ObservableMirrors.java b/src/main/java/lighthouse/threading/ObservableMirrors.java
new file mode 100644
index 0000000000..e55a7260c4
--- /dev/null
+++ b/src/main/java/lighthouse/threading/ObservableMirrors.java
@@ -0,0 +1,195 @@
+package lighthouse.threading;
+
+import java.lang.ref.WeakReference;
+import java.util.List;
+import java.util.Set;
+import javafx.beans.WeakListener;
+import javafx.collections.*;
+
+/**
+ * Utility functions that mirror changes from one list into another list. JavaFX already provides this functionality
+ * of course under the name "content binding"; a mirror is a content binding that relays changes into other threads
+ * first. Thus you can have an ObservableList which is updated in one thread, but still bound to directly in the UI
+ * thread, without needing to worry about cross-thread interference.
+ */
+public class ObservableMirrors
+{
+ /**
+ * Creates an unmodifiable list that asynchronously follows changes in mirrored, with changes applied using
+ * the given executor. This should only be called on the thread that owns the list to be mirrored, as the contents
+ * will be read.
+ */
+ public static ObservableList mirrorList(ObservableList mirrored, AffinityExecutor runChangesIn)
+ {
+ ObservableList result = FXCollections.observableArrayList();
+ result.setAll(mirrored);
+ mirrored.addListener(new ListMirror(result, runChangesIn));
+ return FXCollections.unmodifiableObservableList(result);
+ }
+
+ private static class ListMirror implements ListChangeListener, WeakListener
+ {
+ private final WeakReference> targetList;
+ private final AffinityExecutor runChangesIn;
+
+ public ListMirror(ObservableList list, AffinityExecutor runChangesIn)
+ {
+ this.targetList = new WeakReference<>(list);
+ this.runChangesIn = runChangesIn;
+ }
+
+ @Override
+ public void onChanged(Change extends E> change)
+ {
+ final List list = targetList.get();
+ if (list == null)
+ {
+ change.getList().removeListener(this);
+ }
+ else
+ {
+ // If we're already in the right thread this will just run the change immediately, as per normal.
+ runChangesIn.executeASAP(() -> {
+ while (change.next())
+ {
+ if (change.wasPermutated())
+ {
+ list.subList(change.getFrom(), change.getTo()).clear();
+ list.addAll(change.getFrom(), change.getList().subList(change.getFrom(), change.getTo()));
+ }
+ else
+ {
+ if (change.wasRemoved())
+ {
+ list.subList(change.getFrom(), change.getFrom() + change.getRemovedSize()).clear();
+ }
+ if (change.wasAdded())
+ {
+ list.addAll(change.getFrom(), change.getAddedSubList());
+ }
+ }
+ }
+ });
+ }
+ }
+
+ @Override
+ public boolean wasGarbageCollected()
+ {
+ return targetList.get() == null;
+ }
+
+ // Do we really need these?
+ @Override
+ public int hashCode()
+ {
+ final List list = targetList.get();
+ return (list == null) ? 0 : list.hashCode();
+ }
+
+ @Override
+ public boolean equals(Object obj)
+ {
+ if (this == obj)
+ {
+ return true;
+ }
+
+ final List list1 = targetList.get();
+ if (list1 == null)
+ {
+ return false;
+ }
+
+ if (obj instanceof ListMirror)
+ {
+ final ListMirror> other = (ListMirror>) obj;
+ final List> list2 = other.targetList.get();
+ return list1 == list2;
+ }
+ return false;
+ }
+ }
+
+
+ /**
+ * Creates an unmodifiable list that asynchronously follows changes in mirrored, with changes applied using
+ * the given executor. This should only be called on the thread that owns the list to be mirrored, as the contents
+ * will be read.
+ */
+ public static ObservableSet mirrorSet(ObservableSet mirrored, AffinityExecutor runChangesIn)
+ {
+ @SuppressWarnings("unchecked") ObservableSet result = FXCollections.observableSet();
+ result.addAll(mirrored);
+ mirrored.addListener(new SetMirror(result, runChangesIn));
+ return FXCollections.unmodifiableObservableSet(result);
+ }
+
+ private static class SetMirror implements SetChangeListener, WeakListener
+ {
+ private final WeakReference> targetSet;
+ private final AffinityExecutor runChangesIn;
+
+ public SetMirror(ObservableSet set, AffinityExecutor runChangesIn)
+ {
+ this.targetSet = new WeakReference<>(set);
+ this.runChangesIn = runChangesIn;
+ }
+
+ @Override
+ public void onChanged(final Change extends E> change)
+ {
+ final ObservableSet set = targetSet.get();
+ if (set == null)
+ {
+ change.getSet().removeListener(this);
+ }
+ else
+ {
+ // If we're already in the right thread this will just run the change immediately, as per normal.
+ runChangesIn.executeASAP(() -> {
+ if (change.wasAdded())
+ set.add(change.getElementAdded());
+ if (change.wasRemoved())
+ set.remove(change.getElementRemoved());
+ });
+ }
+ }
+
+ @Override
+ public boolean wasGarbageCollected()
+ {
+ return targetSet.get() == null;
+ }
+
+ @Override
+ public int hashCode()
+ {
+ final ObservableSet set = targetSet.get();
+ return (set == null) ? 0 : set.hashCode();
+ }
+
+ @Override
+ public boolean equals(Object obj)
+ {
+ if (this == obj)
+ {
+ return true;
+ }
+
+ final Set set1 = targetSet.get();
+ if (set1 == null)
+ {
+ return false;
+ }
+
+ if (obj instanceof SetMirror)
+ {
+ final SetMirror> other = (SetMirror>) obj;
+ final Set> list2 = other.targetSet.get();
+ return set1 == list2;
+ }
+ return false;
+ }
+ }
+}
diff --git a/src/main/resources/images/systemTrayIcon.png b/src/main/resources/images/systemTrayIcon.png
index 920aef4189..f5f770a699 100644
Binary files a/src/main/resources/images/systemTrayIcon.png and b/src/main/resources/images/systemTrayIcon.png differ
diff --git a/src/main/resources/logback.xml b/src/main/resources/logback.xml
index acac07e8f6..144c0e8e14 100644
--- a/src/main/resources/logback.xml
+++ b/src/main/resources/logback.xml
@@ -22,25 +22,30 @@
-
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+ -->
diff --git a/src/test/java/io/bitsquare/BitSquareTestSuite.java b/src/test/java/io/bitsquare/BitSquareTestSuite.java
new file mode 100644
index 0000000000..2962904316
--- /dev/null
+++ b/src/test/java/io/bitsquare/BitSquareTestSuite.java
@@ -0,0 +1,18 @@
+package io.bitsquare;
+
+import io.bitsquare.btc.BtcValidatorTest;
+import io.bitsquare.gui.util.BitSquareConverterTest;
+import io.bitsquare.gui.util.BitSquareValidatorTest;
+import org.junit.runner.RunWith;
+import org.junit.runners.Suite;
+
+@RunWith(Suite.class)
+@Suite.SuiteClasses({
+ BtcValidatorTest.class,
+ BitSquareConverterTest.class,
+ BitSquareValidatorTest.class,
+ })
+
+public class BitSquareTestSuite
+{
+}
diff --git a/src/test/java/io/bitsquare/btc/BtcValidatorTest.java b/src/test/java/io/bitsquare/btc/BtcValidatorTest.java
new file mode 100644
index 0000000000..3c800da90f
--- /dev/null
+++ b/src/test/java/io/bitsquare/btc/BtcValidatorTest.java
@@ -0,0 +1,34 @@
+package io.bitsquare.btc;
+
+import com.google.bitcoin.core.Transaction;
+import java.math.BigInteger;
+import org.junit.Test;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+public class BtcValidatorTest
+{
+ @Test
+ public void testIsMinSpendableAmount()
+ {
+ BigInteger amount = null;
+ //noinspection ConstantConditions
+ assertFalse("tx unfunded, pending", BtcValidator.isMinSpendableAmount(amount));
+
+ amount = BigInteger.ZERO;
+ assertFalse("tx unfunded, pending", BtcValidator.isMinSpendableAmount(amount));
+
+ amount = FeePolicy.TX_FEE;
+ assertFalse("tx unfunded, pending", BtcValidator.isMinSpendableAmount(amount));
+
+ amount = Transaction.MIN_NONDUST_OUTPUT;
+ assertFalse("tx unfunded, pending", BtcValidator.isMinSpendableAmount(amount));
+
+ amount = FeePolicy.TX_FEE.add(Transaction.MIN_NONDUST_OUTPUT);
+ assertFalse("tx unfunded, pending", BtcValidator.isMinSpendableAmount(amount));
+
+ amount = FeePolicy.TX_FEE.add(Transaction.MIN_NONDUST_OUTPUT).add(BigInteger.ONE);
+ assertTrue("tx unfunded, pending", BtcValidator.isMinSpendableAmount(amount));
+ }
+}
diff --git a/src/test/java/io/bitsquare/gui/util/BitSquareConverterTest.java b/src/test/java/io/bitsquare/gui/util/BitSquareConverterTest.java
new file mode 100644
index 0000000000..9cffaef731
--- /dev/null
+++ b/src/test/java/io/bitsquare/gui/util/BitSquareConverterTest.java
@@ -0,0 +1,29 @@
+package io.bitsquare.gui.util;
+
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+
+public class BitSquareConverterTest
+{
+
+ @Test
+ public void testStringToDouble()
+ {
+
+ assertEquals(1, BitSquareConverter.stringToDouble("1"), 0);
+ assertEquals(0.1, BitSquareConverter.stringToDouble("0.1"), 0);
+ assertEquals(0.1, BitSquareConverter.stringToDouble("0,1"), 0);
+ assertEquals(1, BitSquareConverter.stringToDouble("1.0"), 0);
+ assertEquals(1, BitSquareConverter.stringToDouble("1,0"), 0);
+
+ assertEquals(Double.NEGATIVE_INFINITY, BitSquareConverter.stringToDouble("1,000.2"), 0);
+ assertEquals(Double.NEGATIVE_INFINITY, BitSquareConverter.stringToDouble("1,000.2"), 0);
+ assertEquals(Double.NEGATIVE_INFINITY, BitSquareConverter.stringToDouble(null), 0);
+ assertEquals(Double.NEGATIVE_INFINITY, BitSquareConverter.stringToDouble(""), 0);
+ assertEquals(Double.NEGATIVE_INFINITY, BitSquareConverter.stringToDouble(""), 0);
+ assertEquals(Double.NEGATIVE_INFINITY, BitSquareConverter.stringToDouble("."), 0);
+ assertEquals(Double.NEGATIVE_INFINITY, BitSquareConverter.stringToDouble(","), 0);
+ assertEquals(Double.NEGATIVE_INFINITY, BitSquareConverter.stringToDouble("a"), 0);
+ }
+}
diff --git a/src/test/java/io/bitsquare/gui/util/BitSquareValidatorTest.java b/src/test/java/io/bitsquare/gui/util/BitSquareValidatorTest.java
new file mode 100644
index 0000000000..517493c85d
--- /dev/null
+++ b/src/test/java/io/bitsquare/gui/util/BitSquareValidatorTest.java
@@ -0,0 +1,38 @@
+package io.bitsquare.gui.util;
+
+import org.junit.Test;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+public class BitSquareValidatorTest
+{
+ @Test
+ public void testValidateStringAsDouble()
+ {
+ assertTrue(BitSquareValidator.validateStringAsDouble("0"));
+ assertTrue(BitSquareValidator.validateStringAsDouble("1"));
+ assertTrue(BitSquareValidator.validateStringAsDouble("0,1"));
+ assertTrue(BitSquareValidator.validateStringAsDouble("0.01"));
+
+ assertFalse(BitSquareValidator.validateStringAsDouble(""));
+ assertFalse(BitSquareValidator.validateStringAsDouble("a"));
+ assertFalse(BitSquareValidator.validateStringAsDouble("0.0.1"));
+ assertFalse(BitSquareValidator.validateStringAsDouble("1,000.1"));
+ assertFalse(BitSquareValidator.validateStringAsDouble("1.000,1"));
+ assertFalse(BitSquareValidator.validateStringAsDouble(null));
+ }
+
+ @Test
+ public void testValidateStringNotEmpty()
+ {
+ assertTrue(BitSquareValidator.validateStringNotEmpty("a"));
+ assertTrue(BitSquareValidator.validateStringNotEmpty("123"));
+
+ assertFalse(BitSquareValidator.validateStringNotEmpty(""));
+ assertFalse(BitSquareValidator.validateStringNotEmpty(" "));
+ assertFalse(BitSquareValidator.validateStringNotEmpty(null));
+ }
+
+
+}
diff --git a/src/test/java/io/bitsquare/msg/P2PNodeTest.java b/src/test/java/io/bitsquare/msg/P2PNodeTest.java
new file mode 100644
index 0000000000..3dc408deb7
--- /dev/null
+++ b/src/test/java/io/bitsquare/msg/P2PNodeTest.java
@@ -0,0 +1,375 @@
+package io.bitsquare.msg;
+
+import java.io.IOException;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.util.Random;
+import net.tomp2p.dht.FutureGet;
+import net.tomp2p.dht.FuturePut;
+import net.tomp2p.dht.FutureRemove;
+import net.tomp2p.dht.PeerDHT;
+import net.tomp2p.futures.FutureDirect;
+import net.tomp2p.peers.Number160;
+import net.tomp2p.peers.PeerAddress;
+import net.tomp2p.rpc.ObjectDataReply;
+import net.tomp2p.storage.Data;
+import net.tomp2p.utils.Utils;
+import org.junit.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import static org.junit.Assert.*;
+
+public class P2PNodeTest
+{
+ private static final Logger log = LoggerFactory.getLogger(P2PNodeTest.class);
+
+ final private static Random rnd = new Random(42L);
+
+ @Test
+ public void testSendData() throws Exception
+ {
+ PeerDHT[] peers = UtilsDHT2.createNodes(3, rnd, 41001);
+ PeerDHT master = peers[0];
+ PeerDHT client = peers[1];
+ PeerDHT otherPeer = peers[2];
+ UtilsDHT2.perfectRouting(peers);
+
+
+ for (final PeerDHT peer : peers)
+ {
+ peer.peer().objectDataReply(new ObjectDataReply()
+ {
+ @Override
+ public Object reply(PeerAddress sender, Object request) throws Exception
+ {
+ return true;
+ }
+ });
+ }
+
+ final KeyPairGenerator keyGen = KeyPairGenerator.getInstance("DSA");
+ keyGen.initialize(1024);
+ KeyPair keyPairClient = keyGen.genKeyPair();
+ KeyPair keyPairOtherPeer = keyGen.genKeyPair();
+
+ P2PNode node;
+ Number160 locationKey;
+ Object object;
+ FutureDirect futureDirect;
+
+ node = new P2PNode(keyPairClient, client);
+ object = "clients data";
+ futureDirect = node.sendData(otherPeer.peerAddress(), object);
+ futureDirect.awaitUninterruptibly();
+
+ assertTrue(futureDirect.isSuccess());
+ // we return true from objectDataReply
+ assertTrue((Boolean) futureDirect.object());
+ }
+
+ @Test
+ public void testProtectedPutGet() throws Exception
+ {
+ PeerDHT[] peers = UtilsDHT2.createNodes(3, rnd, 41001);
+ PeerDHT master = peers[0];
+ PeerDHT client = peers[1];
+ PeerDHT otherPeer = peers[2];
+ UtilsDHT2.perfectRouting(peers);
+
+ final KeyPairGenerator keyGen = KeyPairGenerator.getInstance("DSA");
+ keyGen.initialize(1024);
+ KeyPair keyPairClient = keyGen.genKeyPair();
+ KeyPair keyPairOtherPeer = keyGen.genKeyPair();
+
+ P2PNode node;
+ Number160 locationKey;
+ Data data;
+ FuturePut futurePut;
+ FutureGet futureGet;
+
+ // otherPeer tries to squat clients location store
+ // he can do it but as he has not the domain key of the client he cannot do any harm
+ // he only can store und that path: locationKey.otherPeerDomainKey.data
+ node = new P2PNode(keyPairOtherPeer, otherPeer);
+ locationKey = Number160.createHash("clients location");
+ data = new Data("otherPeer data");
+ futurePut = node.putDomainProtectedData(locationKey, data);
+ futurePut.awaitUninterruptibly();
+ assertTrue(futurePut.isSuccess());
+
+ futureGet = node.getDomainProtectedData(locationKey, keyPairOtherPeer.getPublic());
+ futureGet.awaitUninterruptibly();
+ assertTrue(futureGet.isSuccess());
+ assertEquals("otherPeer data", futureGet.data().object());
+
+ // client store his data und his domainkey, no problem with previous occupied
+ // he only can store und that path: locationKey.clientDomainKey.data
+ node = new P2PNode(keyPairClient, client);
+ locationKey = Number160.createHash("clients location");
+ data = new Data("client data");
+ futurePut = node.putDomainProtectedData(locationKey, data);
+ futurePut.awaitUninterruptibly();
+ assertTrue(futurePut.isSuccess());
+
+ futureGet = node.getDomainProtectedData(locationKey, keyPairClient.getPublic());
+ futureGet.awaitUninterruptibly();
+ assertTrue(futureGet.isSuccess());
+ assertEquals("client data", futureGet.data().object());
+
+ // also other peers can read that data if they know the public key of the client
+ node = new P2PNode(keyPairOtherPeer, otherPeer);
+ futureGet = node.getDomainProtectedData(locationKey, keyPairClient.getPublic());
+ futureGet.awaitUninterruptibly();
+ assertTrue(futureGet.isSuccess());
+ assertEquals("client data", futureGet.data().object());
+
+
+ // other peer try to use pub key of other peer as domain key hash.
+ // must fail as he don't have the full key pair (private key of client missing)
+ locationKey = Number160.createHash("clients location");
+ data = new Data("otherPeer data hack");
+
+ data.protectEntry(keyPairOtherPeer);
+ // he use the pub key from the client
+ final Number160 keyHash = Utils.makeSHAHash(keyPairClient.getPublic().getEncoded());
+ futurePut = otherPeer.put(locationKey).data(data).keyPair(keyPairOtherPeer).domainKey(keyHash).protectDomain().start();
+
+ futurePut.awaitUninterruptibly();
+ assertFalse(futurePut.isSuccess());
+
+ // he can read his prev. stored data
+ node = new P2PNode(keyPairOtherPeer, otherPeer);
+ futureGet = node.getDomainProtectedData(locationKey, keyPairOtherPeer.getPublic());
+ futureGet.awaitUninterruptibly();
+ assertTrue(futureGet.isSuccess());
+ assertEquals("otherPeer data", futureGet.data().object());
+
+ // he can read clients data
+ futureGet = node.getDomainProtectedData(locationKey, keyPairClient.getPublic());
+ futureGet.awaitUninterruptibly();
+ assertTrue(futureGet.isSuccess());
+ assertEquals("client data", futureGet.data().object());
+
+ master.shutdown();
+ }
+
+ @Test
+ public void testAddToListGetList() throws Exception
+ {
+ PeerDHT[] peers = UtilsDHT2.createNodes(3, rnd, 41001);
+ PeerDHT master = peers[0];
+ PeerDHT client = peers[1];
+ PeerDHT otherPeer = peers[2];
+ UtilsDHT2.perfectRouting(peers);
+
+ P2PNode node;
+ final KeyPairGenerator keyGen = KeyPairGenerator.getInstance("DSA");
+ keyGen.initialize(1024);
+ KeyPair keyPairClient = keyGen.genKeyPair();
+ KeyPair keyPairOtherPeer = keyGen.genKeyPair();
+
+ Number160 locationKey;
+ Data data;
+ FuturePut futurePut;
+ FutureGet futureGet;
+
+ // client add a value
+ node = new P2PNode(keyPairClient, client);
+ locationKey = Number160.createHash("add to list clients location");
+ data = new Data("add to list client data1");
+ Data data_1 = data;
+ futurePut = node.addProtectedData(locationKey, data);
+ futurePut.awaitUninterruptibly();
+ assertTrue(futurePut.isSuccess());
+
+ data = new Data("add to list client data2");
+ Data data_2 = data;
+ futurePut = node.addProtectedData(locationKey, data);
+ futurePut.awaitUninterruptibly();
+ assertTrue(futurePut.isSuccess());
+
+ futureGet = node.getDataMap(locationKey);
+ futureGet.awaitUninterruptibly();
+ assertTrue(futureGet.isSuccess());
+ boolean foundData1 = futureGet.dataMap().values().stream().anyMatch(data1 -> {
+ try
+ {
+ return data1.object().equals("add to list client data1");
+ } catch (ClassNotFoundException | IOException e)
+ {
+ e.printStackTrace();
+ }
+ return false;
+ });
+ boolean foundData2 = futureGet.dataMap().values().stream().anyMatch(data1 -> {
+ try
+ {
+ return data1.object().equals("add to list client data2");
+ } catch (ClassNotFoundException | IOException e)
+ {
+ e.printStackTrace();
+ }
+ return false;
+ });
+ assertTrue(foundData1);
+ assertTrue(foundData2);
+ assertEquals(2, futureGet.dataMap().values().size());
+
+
+ // other peer tried to overwrite that entry
+ // but will not succeed, instead he will add a new entry.
+ // TODO investigate why it is not possible to overwrite the entry with that method
+ // The protection entry with the key does not make any difference as also the client himself cannot overwrite any entry
+ // http://tomp2p.net/doc/P2P-with-TomP2P-1.pdf
+ // "add(location_key, value) is translated to put(location_key, hash(value), value)"
+
+ // fake content key with content key from previous clients entry
+ Number160 contentKey = Number160.createHash("add to list client data1");
+
+ data = new Data("add to list other peer data HACK!");
+ data.protectEntry(keyPairOtherPeer); // also with client key it does not work...
+ futurePut = otherPeer.put(locationKey).data(contentKey, data).keyPair(keyPairOtherPeer).start();
+ futurePut.awaitUninterruptibly();
+ assertTrue(futurePut.isSuccess());
+
+ node = new P2PNode(keyPairOtherPeer, otherPeer);
+ futureGet = node.getDataMap(locationKey);
+ futureGet.awaitUninterruptibly();
+ assertTrue(futureGet.isSuccess());
+
+ foundData1 = futureGet.dataMap().values().stream().anyMatch(data1 -> {
+ try
+ {
+ return data1.object().equals("add to list client data1");
+ } catch (ClassNotFoundException | IOException e)
+ {
+ e.printStackTrace();
+ }
+ return false;
+ });
+ foundData2 = futureGet.dataMap().values().stream().anyMatch(data1 -> {
+ try
+ {
+ return data1.object().equals("add to list client data2");
+ } catch (ClassNotFoundException | IOException e)
+ {
+ e.printStackTrace();
+ }
+ return false;
+ });
+ boolean foundData3 = futureGet.dataMap().values().stream().anyMatch(data1 -> {
+ try
+ {
+ return data1.object().equals("add to list other peer data HACK!");
+ } catch (ClassNotFoundException | IOException e)
+ {
+ e.printStackTrace();
+ }
+ return false;
+ });
+ assertTrue(foundData1);
+ assertTrue(foundData2);
+ assertTrue(foundData3);
+ assertEquals(3, futureGet.dataMap().values().size());
+
+
+ // client removes his entry -> OK
+ node = new P2PNode(keyPairClient, client);
+ FutureRemove futureRemove = node.removeFromDataMap(locationKey, data_1);
+ futureRemove.awaitUninterruptibly();
+ assertTrue(futureRemove.isSuccess());
+
+ futureGet = node.getDataMap(locationKey);
+ futureGet.awaitUninterruptibly();
+ assertTrue(futureGet.isSuccess());
+
+ foundData1 = futureGet.dataMap().values().stream().anyMatch(data1 -> {
+ try
+ {
+ return data1.object().equals("add to list client data1");
+ } catch (ClassNotFoundException | IOException e)
+ {
+ e.printStackTrace();
+ }
+ return false;
+ });
+ foundData2 = futureGet.dataMap().values().stream().anyMatch(data1 -> {
+ try
+ {
+ return data1.object().equals("add to list client data2");
+ } catch (ClassNotFoundException | IOException e)
+ {
+ e.printStackTrace();
+ }
+ return false;
+ });
+ foundData3 = futureGet.dataMap().values().stream().anyMatch(data1 -> {
+ try
+ {
+ return data1.object().equals("add to list other peer data HACK!");
+ } catch (ClassNotFoundException | IOException e)
+ {
+ e.printStackTrace();
+ }
+ return false;
+ });
+
+ assertFalse(foundData1);
+ assertTrue(foundData2);
+ assertTrue(foundData3);
+ assertEquals(2, futureGet.dataMap().values().size());
+
+
+ // otherPeer tries to removes client entry -> FAIL
+ node = new P2PNode(keyPairOtherPeer, otherPeer);
+ futureRemove = node.removeFromDataMap(locationKey, data_2);
+ futureRemove.awaitUninterruptibly();
+ assertFalse(futureRemove.isSuccess());
+
+ futureGet = node.getDataMap(locationKey);
+ futureGet.awaitUninterruptibly();
+ assertTrue(futureGet.isSuccess());
+
+ foundData1 = futureGet.dataMap().values().stream().anyMatch(data1 -> {
+ try
+ {
+ return data1.object().equals("add to list client data1");
+ } catch (ClassNotFoundException | IOException e)
+ {
+ e.printStackTrace();
+ }
+ return false;
+ });
+ foundData2 = futureGet.dataMap().values().stream().anyMatch(data1 -> {
+ try
+ {
+ return data1.object().equals("add to list client data2");
+ } catch (ClassNotFoundException | IOException e)
+ {
+ e.printStackTrace();
+ }
+ return false;
+ });
+ foundData3 = futureGet.dataMap().values().stream().anyMatch(data1 -> {
+ try
+ {
+ return data1.object().equals("add to list other peer data HACK!");
+ } catch (ClassNotFoundException | IOException e)
+ {
+ e.printStackTrace();
+ }
+ return false;
+ });
+
+ assertFalse(foundData1);
+ assertTrue(foundData2);
+ assertTrue(foundData3);
+ assertEquals(2, futureGet.dataMap().values().size());
+
+
+ master.shutdown();
+ }
+
+
+}
diff --git a/src/test/java/io/bitsquare/msg/UtilsDHT2.java b/src/test/java/io/bitsquare/msg/UtilsDHT2.java
new file mode 100644
index 0000000000..fdfe0034d3
--- /dev/null
+++ b/src/test/java/io/bitsquare/msg/UtilsDHT2.java
@@ -0,0 +1,412 @@
+/*
+ * Copyright 2012 Thomas Bocek
+ *
+ * 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.msg;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.*;
+import net.tomp2p.connection.Bindings;
+import net.tomp2p.dht.PeerBuilderDHT;
+import net.tomp2p.dht.PeerDHT;
+import net.tomp2p.futures.FutureBootstrap;
+import net.tomp2p.futures.FutureDiscover;
+import net.tomp2p.message.Message;
+import net.tomp2p.message.Message.Type;
+import net.tomp2p.p2p.AutomaticFuture;
+import net.tomp2p.p2p.Peer;
+import net.tomp2p.p2p.PeerBuilder;
+import net.tomp2p.peers.*;
+
+public class UtilsDHT2
+{
+ /**
+ * Used to make the testcases predictable. Used as an input for {@link java.util.Random}.
+ */
+ public static final long THE_ANSWER = 42L;
+
+ /**
+ * Having two peers in a network, the seed needs to be different, otherwise we create a peer with the same id twice.
+ */
+ public static final long THE_ANSWER2 = 43L;
+
+ public static Message createDummyMessage() throws UnknownHostException
+ {
+ return createDummyMessage(false, false);
+ }
+
+ public static Message createDummyMessage(boolean firewallUDP, boolean firewallTCP)
+ throws UnknownHostException
+ {
+ return createDummyMessage(new Number160("0x4321"), "127.0.0.1", 8001, 8002, new Number160("0x1234"),
+ "127.0.0.1", 8003, 8004, (byte) 0, Type.REQUEST_1, firewallUDP, firewallTCP);
+ }
+
+ public static PeerAddress createAddress(Number160 id) throws UnknownHostException
+ {
+ return createAddress(id, "127.0.0.1", 8005, 8006, false, false);
+ }
+
+ public static PeerAddress createAddress() throws UnknownHostException
+ {
+ return createAddress(new Number160("0x5678"), "127.0.0.1", 8005, 8006, false, false);
+ }
+
+ public static PeerAddress createAddress(int id) throws UnknownHostException
+ {
+ return createAddress(new Number160(id), "127.0.0.1", 8005, 8006, false, false);
+ }
+
+ public static PeerAddress createAddress(String id) throws UnknownHostException
+ {
+ return createAddress(new Number160(id), "127.0.0.1", 8005, 8006, false, false);
+ }
+
+ public static PeerAddress createAddress(Number160 idSender, String inetSender, int tcpPortSender,
+ int udpPortSender, boolean firewallUDP, boolean firewallTCP) throws UnknownHostException
+ {
+ InetAddress inetSend = InetAddress.getByName(inetSender);
+ PeerSocketAddress peerSocketAddress = new PeerSocketAddress(inetSend, tcpPortSender, udpPortSender);
+ PeerAddress n1 = new PeerAddress(idSender, peerSocketAddress, firewallTCP, firewallUDP, false,
+ PeerAddress.EMPTY_PEER_SOCKET_ADDRESSES);
+ return n1;
+ }
+
+ public static Message createDummyMessage(Number160 idSender, String inetSender, int tcpPortSendor,
+ int udpPortSender, Number160 idRecipien, String inetRecipient, int tcpPortRecipient,
+ int udpPortRecipient, byte command, Type type, boolean firewallUDP, boolean firewallTCP)
+ throws UnknownHostException
+ {
+ Message message = new Message();
+ PeerAddress n1 = createAddress(idSender, inetSender, tcpPortSendor, udpPortSender, firewallUDP,
+ firewallTCP);
+ message.sender(n1);
+ //
+ PeerAddress n2 = createAddress(idRecipien, inetRecipient, tcpPortRecipient, udpPortRecipient,
+ firewallUDP, firewallTCP);
+ message.recipient(n2);
+ message.type(type);
+ message.command(command);
+ return message;
+ }
+
+ public static PeerDHT[] createNodes(int nrOfPeers, Random rnd, int port) throws Exception
+ {
+ return createNodes(nrOfPeers, rnd, port, null);
+ }
+
+ public static PeerDHT[] createNodes(int nrOfPeers, Random rnd, int port, AutomaticFuture automaticFuture)
+ throws Exception
+ {
+ return createNodes(nrOfPeers, rnd, port, automaticFuture, false);
+ }
+
+ /**
+ * Creates peers for testing. The first peer (peer[0]) will be used as the master. This means that shutting down
+ * peer[0] will shut down all other peers
+ *
+ * @param nrOfPeers The number of peers to create including the master
+ * @param rnd The random object to create random peer IDs
+ * @param port The port where the master peer will listen to
+ * @return All the peers, with the master peer at position 0 -> peer[0]
+ * @throws Exception If the creation of nodes fail.
+ */
+ public static PeerDHT[] createNodes(int nrOfPeers, Random rnd, int port, AutomaticFuture automaticFuture,
+ boolean maintenance) throws Exception
+ {
+ if (nrOfPeers < 1)
+ {
+ throw new IllegalArgumentException("Cannot create less than 1 peer");
+ }
+ Bindings bindings = new Bindings();
+ PeerDHT[] peers = new PeerDHT[nrOfPeers];
+ final Peer master;
+ if (automaticFuture != null)
+ {
+ Number160 peerId = new Number160(rnd);
+ PeerMap peerMap = new PeerMap(new PeerMapConfiguration(peerId));
+ master = new PeerBuilder(peerId)
+ .ports(port).enableMaintenance(maintenance)
+ .externalBindings(bindings).peerMap(peerMap).start().addAutomaticFuture(automaticFuture);
+ peers[0] = new PeerBuilderDHT(master).start();
+
+ }
+ else
+ {
+ Number160 peerId = new Number160(rnd);
+ PeerMap peerMap = new PeerMap(new PeerMapConfiguration(peerId));
+ master = new PeerBuilder(peerId).enableMaintenance(maintenance).externalBindings(bindings)
+ .peerMap(peerMap).ports(port).start();
+ peers[0] = new PeerBuilderDHT(master).start();
+ }
+
+ for (int i = 1; i < nrOfPeers; i++)
+ {
+ if (automaticFuture != null)
+ {
+ Number160 peerId = new Number160(rnd);
+ PeerMap peerMap = new PeerMap(new PeerMapConfiguration(peerId));
+ Peer peer = new PeerBuilder(peerId)
+ .masterPeer(master)
+ .enableMaintenance(maintenance).enableMaintenance(maintenance).peerMap(peerMap).externalBindings(bindings).start().addAutomaticFuture(automaticFuture);
+ peers[i] = new PeerBuilderDHT(peer).start();
+ }
+ else
+ {
+ Number160 peerId = new Number160(rnd);
+ PeerMap peerMap = new PeerMap(new PeerMapConfiguration(peerId).peerNoVerification());
+ Peer peer = new PeerBuilder(peerId).enableMaintenance(maintenance)
+ .externalBindings(bindings).peerMap(peerMap).masterPeer(master)
+ .start();
+ peers[i] = new PeerBuilderDHT(peer).start();
+ }
+ }
+ System.err.println("peers created.");
+ return peers;
+ }
+
+ public static Peer[] createRealNodes(int nrOfPeers, Random rnd, int startPort,
+ AutomaticFuture automaticFuture) throws Exception
+ {
+ if (nrOfPeers < 1)
+ {
+ throw new IllegalArgumentException("Cannot create less than 1 peer");
+ }
+ Peer[] peers = new Peer[nrOfPeers];
+ for (int i = 0; i < nrOfPeers; i++)
+ {
+ peers[i] = new PeerBuilder(new Number160(rnd))
+ .ports(startPort + i).start().addAutomaticFuture(automaticFuture);
+ }
+ System.err.println("real peers created.");
+ return peers;
+ }
+
+ public static Peer[] createNonMaintenanceNodes(int nrOfPeers, Random rnd, int port) throws IOException
+ {
+ if (nrOfPeers < 1)
+ {
+ throw new IllegalArgumentException("Cannot create less than 1 peer");
+ }
+ Peer[] peers = new Peer[nrOfPeers];
+ peers[0] = new PeerBuilder(new Number160(rnd)).enableMaintenance(false).ports(port).start();
+ for (int i = 1; i < nrOfPeers; i++)
+ {
+ peers[i] = new PeerBuilder(new Number160(rnd)).enableMaintenance(false).masterPeer(peers[0])
+ .start();
+ }
+ System.err.println("non-maintenance peers created.");
+ return peers;
+ }
+
+ /**
+ * Perfect routing, where each neighbor has contacted each other. This means that for small number of peers, every
+ * peer knows every other peer.
+ *
+ * @param peers The peers taking part in the p2p network.
+ */
+ public static void perfectRouting(PeerDHT... peers)
+ {
+ for (int i = 0; i < peers.length; i++)
+ {
+ for (int j = 0; j < peers.length; j++)
+ peers[i].peer().peerBean().peerMap().peerFound(peers[j].peer().peerAddress(), null);
+ }
+ System.err.println("perfect routing done.");
+ }
+
+ public static void perfectRoutingIndirect(PeerDHT... peers)
+ {
+ for (int i = 0; i < peers.length; i++)
+ {
+ for (int j = 0; j < peers.length; j++)
+ peers[i].peerBean().peerMap().peerFound(peers[j].peerAddress(), peers[j].peerAddress());
+ }
+ System.err.println("perfect routing done.");
+ }
+
+ public static void main(String[] args) throws IOException
+ {
+ createTempDirectory();
+ }
+
+ private static final int TEMP_DIR_ATTEMPTS = 10000;
+
+ public static File createTempDirectory() throws IOException
+ {
+ File baseDir = new File(System.getProperty("java.io.tmpdir"));
+ String baseName = System.currentTimeMillis() + "-";
+
+ for (int counter = 0; counter < TEMP_DIR_ATTEMPTS; counter++)
+ {
+ File tempDir = new File(baseDir, baseName + counter);
+ if (tempDir.mkdir())
+ {
+ return tempDir;
+ }
+ }
+ throw new IllegalStateException("Failed to create directory within " + TEMP_DIR_ATTEMPTS
+ + " attempts (tried " + baseName + "0 to " + baseName + (TEMP_DIR_ATTEMPTS - 1) + ')');
+ }
+
+ public static Peer[] createAndAttachNodes(int nr, int port, Random rnd) throws Exception
+ {
+ Peer[] peers = new Peer[nr];
+ for (int i = 0; i < nr; i++)
+ {
+ if (i == 0)
+ {
+ peers[0] = new PeerBuilder(new Number160(rnd)).ports(port).start();
+ }
+ else
+ {
+ peers[i] = new PeerBuilder(new Number160(rnd)).masterPeer(peers[0]).start();
+ }
+ }
+ return peers;
+ }
+
+ public static void bootstrap(Peer[] peers)
+ {
+ List futures1 = new ArrayList();
+ List futures2 = new ArrayList();
+ for (int i = 1; i < peers.length; i++)
+ {
+ FutureDiscover tmp = peers[i].discover().peerAddress(peers[0].peerAddress()).start();
+ futures2.add(tmp);
+ }
+ for (FutureDiscover future : futures2)
+ {
+ future.awaitUninterruptibly();
+ }
+ for (int i = 1; i < peers.length; i++)
+ {
+ FutureBootstrap tmp = peers[i].bootstrap().peerAddress(peers[0].peerAddress()).start();
+ futures1.add(tmp);
+ }
+ for (int i = 1; i < peers.length; i++)
+ {
+ FutureBootstrap tmp = peers[0].bootstrap().peerAddress(peers[i].peerAddress()).start();
+ futures1.add(tmp);
+ }
+ for (FutureBootstrap future : futures1)
+ future.awaitUninterruptibly();
+ }
+
+ public static void routing(Number160 key, Peer[] peers, int start)
+ {
+ System.out.println("routing: searching for key " + key);
+ NavigableSet pa1 = new TreeSet(PeerMap.createComparator(key));
+ NavigableSet queried = new TreeSet(PeerMap.createComparator(key));
+ Number160 result = Number160.ZERO;
+ Number160 resultPeer = new Number160("0xd75d1a3d57841fbc9e2a3d175d6a35dc2e15b9f");
+ int round = 0;
+ while (!resultPeer.equals(result))
+ {
+ System.out.println("round " + round);
+ round++;
+ pa1.addAll(peers[start].peerBean().peerMap().all());
+ queried.add(peers[start].peerAddress());
+ System.out.println("closest so far: " + queried.first());
+ PeerAddress next = pa1.pollFirst();
+ while (queried.contains(next))
+ {
+ next = pa1.pollFirst();
+ }
+ result = next.peerId();
+ start = findNr(next.peerId().toString(), peers);
+ }
+ }
+
+ public static void findInMap(PeerAddress key, Peer[] peers)
+ {
+ for (int i = 0; i < peers.length; i++)
+ {
+ if (peers[i].peerBean().peerMap().contains(key))
+ {
+ System.out.println("Peer " + i + " with the id " + peers[i].peerID() + " knows the peer "
+ + key);
+ }
+ }
+ }
+
+ public static int findNr(String string, Peer[] peers)
+ {
+ for (int i = 0; i < peers.length; i++)
+ {
+ if (peers[i].peerID().equals(new Number160(string)))
+ {
+ System.out.println("we found the number " + i + " for peer with id " + string);
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ public static Peer find(String string, Peer[] peers)
+ {
+ for (int i = 0; i < peers.length; i++)
+ {
+ if (peers[i].peerID().equals(new Number160(string)))
+ {
+ System.out.println("!!we found the number " + i + " for peer with id " + string);
+ return peers[i];
+ }
+ }
+ return null;
+ }
+
+ public static void exec(String cmd) throws Exception
+ {
+ Process p = Runtime.getRuntime().exec(cmd);
+ p.waitFor();
+
+ BufferedReader br = new BufferedReader(new InputStreamReader(p.getInputStream()));
+ String line = null;
+ while ((line = br.readLine()) != null)
+ {
+ System.out.println(line);
+ }
+ br.close();
+ }
+
+ public static PeerAddress createAddressIP(String inet) throws UnknownHostException
+ {
+ return createAddress(Number160.createHash(inet), inet, 8005, 8006, false, false);
+ }
+
+ public static PeerAddress[] createDummyAddress(int size, int portTCP, int portUDP) throws UnknownHostException
+ {
+ PeerAddress[] pa = new PeerAddress[size];
+ for (int i = 0; i < size; i++)
+ {
+ pa[i] = createAddress(i + 1, portTCP, portUDP);
+ }
+ return pa;
+ }
+
+ public static PeerAddress createAddress(int iid, int portTCP, int portUDP) throws UnknownHostException
+ {
+ Number160 id = new Number160(iid);
+ InetAddress address = InetAddress.getByName("127.0.0.1");
+ return new PeerAddress(id, address, portTCP, portUDP);
+ }
+
+}
diff --git a/src/test/java/lighthouse/threading/ConcatenatingListTest.java b/src/test/java/lighthouse/threading/ConcatenatingListTest.java
new file mode 100644
index 0000000000..17b6c76865
--- /dev/null
+++ b/src/test/java/lighthouse/threading/ConcatenatingListTest.java
@@ -0,0 +1,26 @@
+package lighthouse.threading;
+
+import javafx.collections.FXCollections;
+import javafx.collections.ObservableList;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+
+
+public class ConcatenatingListTest
+{
+ @Test
+ public void basic() throws Exception
+ {
+ ObservableList a = FXCollections.observableArrayList();
+ ObservableList b = FXCollections.observableArrayList();
+ ConcatenatingList concat = new ConcatenatingList<>(a, b);
+ assertEquals(0, concat.size());
+ a.add("1");
+ assertEquals(1, concat.size());
+ assertEquals("1", concat.get(0));
+ b.add("2");
+ assertEquals(2, concat.size());
+ assertEquals("2", concat.get(1));
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/lighthouse/threading/MappedListTest.java b/src/test/java/lighthouse/threading/MappedListTest.java
new file mode 100644
index 0000000000..08faacdb83
--- /dev/null
+++ b/src/test/java/lighthouse/threading/MappedListTest.java
@@ -0,0 +1,91 @@
+package lighthouse.threading;
+
+import java.util.LinkedList;
+import java.util.Queue;
+import javafx.collections.FXCollections;
+import javafx.collections.ListChangeListener;
+import javafx.collections.ObservableList;
+import org.junit.Before;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+public class MappedListTest
+{
+ private ObservableList inputs;
+ private ObservableList outputs;
+ private Queue> changes;
+
+ @Before
+ public void setup()
+ {
+ inputs = FXCollections.observableArrayList();
+ outputs = new MappedList<>(inputs, str -> "Hello " + str);
+ changes = new LinkedList<>();
+ outputs.addListener(changes::add);
+ }
+
+ @Test
+ public void add() throws Exception
+ {
+ assertEquals(0, outputs.size());
+ inputs.add("Mike");
+ ListChangeListener.Change extends String> change = getChange();
+ assertTrue(change.wasAdded());
+ assertEquals("Hello Mike", change.getAddedSubList().get(0));
+ assertEquals(1, outputs.size());
+ assertEquals("Hello Mike", outputs.get(0));
+ inputs.remove(0);
+ assertEquals(0, outputs.size());
+ }
+
+ private ListChangeListener.Change extends String> getChange()
+ {
+ ListChangeListener.Change extends String> change = changes.poll();
+ change.next();
+ return change;
+ }
+
+ @Test
+ public void remove()
+ {
+ inputs.add("Mike");
+ inputs.add("Dave");
+ inputs.add("Katniss");
+ getChange();
+ getChange();
+ getChange();
+ assertEquals("Hello Mike", outputs.get(0));
+ assertEquals("Hello Dave", outputs.get(1));
+ assertEquals("Hello Katniss", outputs.get(2));
+ inputs.remove(0);
+ ListChangeListener.Change extends String> change = getChange();
+ assertTrue(change.wasRemoved());
+ assertEquals(2, outputs.size());
+ assertEquals(1, change.getRemovedSize());
+ assertEquals("Hello Mike", change.getRemoved().get(0));
+ assertEquals("Hello Dave", outputs.get(0));
+
+ inputs.remove(1);
+ assertEquals(1, outputs.size());
+ assertEquals("Hello Dave", outputs.get(0));
+ }
+
+ @Test
+ public void replace() throws Exception
+ {
+ inputs.add("Mike");
+ inputs.add("Dave");
+ getChange();
+ getChange();
+ inputs.set(0, "Bob");
+ assertEquals("Hello Bob", outputs.get(0));
+ ListChangeListener.Change extends String> change = getChange();
+ assertTrue(change.wasReplaced());
+ assertEquals("Hello Mike", change.getRemoved().get(0));
+ assertEquals("Hello Bob", change.getAddedSubList().get(0));
+ }
+
+ // Could also test permutation here if I could figure out how to actually apply one!
+}
\ No newline at end of file