updated to master branch of tomp2p. refactored messaging. bootstrapping to seed node (server supported). right management.

This commit is contained in:
Manfred Karrer 2014-07-31 18:45:58 +02:00
parent 222045ca5a
commit 57d5ddcc4f
54 changed files with 3284 additions and 717 deletions

View file

@ -10,6 +10,10 @@ Then you can generate coins on demand with the Bitcoin qt client with that comma
See: https://bitcoinj.github.io/testing See: https://bitcoinj.github.io/testing
You can change the network mode in the guice module: BitSquareModule.java You can change the network mode in the guice module: BitSquareModule.java
We use a fork of the actual TomP2P master branch: https://github.com/ManfredKarrer/TomP2P
You need to check that out as well and deploy it to the local maven repository:
mvn clean install -DskipTests
### Resources: ### Resources:
* Web: http://bitsquare.io * Web: http://bitsquare.io

View file

@ -124,11 +124,13 @@
<version>0.11.3</version> <version>0.11.3</version>
</dependency> </dependency>
<!--
<dependency> <dependency>
<groupId>net.tomp2p</groupId> <groupId>net.tomp2p</groupId>
<artifactId>TomP2P</artifactId> <artifactId>tomp2p-all</artifactId>
<version>4.4</version> <version>5.0-Alpha24-SNAPSHOT</version>
</dependency> </dependency>
-->
<dependency> <dependency>
<groupId>junit</groupId> <groupId>junit</groupId>

View file

@ -1,3 +1,3 @@
Manifest-Version: 1.0 Manifest-Version: 1.0
Main-Class: io.bitsquare.Relay Main-Class: io.bitsquare.BootstrapNode_

View file

@ -38,7 +38,7 @@ public class BitSquare extends Application
{ {
private static final Logger log = LoggerFactory.getLogger(BitSquare.class); 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 String APP_NAME = "bitsquare";
private static Stage primaryStage; private static Stage primaryStage;

View file

@ -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);
}
});
}
}
}

View file

@ -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.
* <p>
* 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<SeedNodeAddress.StaticSeedNodeAddresses> 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<BaseFuture>()
{
@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<BaseFuture>()
{
@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);
}
}
}

View file

@ -22,5 +22,4 @@ class BitSquareWallet extends Wallet implements Serializable
super(params, keyCrypter); super(params, keyCrypter);
} }
} }

View file

@ -190,11 +190,9 @@ public class WalletFacade
} }
} }
public void shutDown() public void shutDown()
{ {
wallet.removeEventListener(walletEventListener); wallet.removeEventListener(walletEventListener);
walletAppKit.stopAsync(); walletAppKit.stopAsync();
} }
@ -596,6 +594,7 @@ public class WalletFacade
Wallet.SendRequest sendRequest = Wallet.SendRequest.forTx(tx); 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 // 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.coinSelector = new AddressBasedCoinSelector(params, getRegistrationAddressInfo(), false);
sendRequest.changeAddress = getRegistrationAddressInfo().getAddress(); sendRequest.changeAddress = getRegistrationAddressInfo().getAddress();
Wallet.SendResult sendResult = wallet.sendCoins(sendRequest); 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 // Trade process
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////

View file

@ -15,6 +15,7 @@ import io.bitsquare.btc.FeePolicy;
import io.bitsquare.btc.WalletFacade; import io.bitsquare.btc.WalletFacade;
import io.bitsquare.crypto.CryptoFacade; import io.bitsquare.crypto.CryptoFacade;
import io.bitsquare.msg.MessageFacade; import io.bitsquare.msg.MessageFacade;
import io.bitsquare.msg.SeedNodeAddress;
import io.bitsquare.settings.Settings; import io.bitsquare.settings.Settings;
import io.bitsquare.storage.Storage; import io.bitsquare.storage.Storage;
import io.bitsquare.trade.Trading; 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(String.class).annotatedWith(Names.named("networkType")).toInstance(WalletFacade.TEST_NET);
bind(NetworkParameters.class).toProvider(NetworkParametersProvider.class).asEagerSingleton(); bind(NetworkParameters.class).toProvider(NetworkParametersProvider.class).asEagerSingleton();
bind(BitSquareWalletAppKit.class).toProvider(BitSquareWalletAppKitProvider.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);
} }
} }

View file

@ -11,6 +11,7 @@ import io.bitsquare.gui.orders.OrdersController;
import io.bitsquare.gui.util.Icons; import io.bitsquare.gui.util.Icons;
import io.bitsquare.gui.util.Transitions; import io.bitsquare.gui.util.Transitions;
import io.bitsquare.locale.Localisation; import io.bitsquare.locale.Localisation;
import io.bitsquare.msg.BootstrapListener;
import io.bitsquare.msg.MessageFacade; import io.bitsquare.msg.MessageFacade;
import io.bitsquare.storage.Storage; import io.bitsquare.storage.Storage;
import io.bitsquare.trade.Direction; import io.bitsquare.trade.Direction;
@ -176,8 +177,24 @@ public class MainController implements Initializable, NavigationController
private void init() 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); trading.addTakeOfferRequestListener(this::onTakeOfferRequested);

View file

@ -26,9 +26,14 @@ import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.Pane; import javafx.scene.layout.Pane;
import javafx.stage.Stage; import javafx.stage.Stage;
import javax.inject.Inject; import javax.inject.Inject;
import net.tomp2p.peers.Number160; import net.tomp2p.peers.Number640;
import net.tomp2p.storage.Data; import net.tomp2p.storage.Data;
/**
* TODO remove tomp2p dependencies
* import net.tomp2p.peers.Number160;
* import net.tomp2p.storage.Data;
*/
@SuppressWarnings({"ALL", "UnusedParameters"}) @SuppressWarnings({"ALL", "UnusedParameters"})
public class ArbitratorOverviewController implements Initializable, ChildController, NavigationController, ArbitratorListener public class ArbitratorOverviewController implements Initializable, ChildController, NavigationController, ArbitratorListener
{ {
@ -135,7 +140,7 @@ public class ArbitratorOverviewController implements Initializable, ChildControl
} }
@Override @Override
public void onArbitratorsReceived(Map<Number160, Data> dataMap, boolean success) public void onArbitratorsReceived(Map<Number640, Data> dataMap, boolean success)
{ {
if (success && dataMap != null) if (success && dataMap != null)
{ {
@ -145,7 +150,7 @@ public class ArbitratorOverviewController implements Initializable, ChildControl
{ {
try try
{ {
Object arbitratorDataObject = arbitratorData.getObject(); Object arbitratorDataObject = arbitratorData.object();
if (arbitratorDataObject instanceof Arbitrator) if (arbitratorDataObject instanceof Arbitrator)
{ {
Arbitrator arbitrator = (Arbitrator) arbitratorDataObject; Arbitrator arbitrator = (Arbitrator) arbitratorDataObject;

View file

@ -22,8 +22,8 @@ import io.bitsquare.msg.MessageFacade;
import io.bitsquare.storage.Storage; import io.bitsquare.storage.Storage;
import io.bitsquare.user.Arbitrator; import io.bitsquare.user.Arbitrator;
import io.bitsquare.user.Reputation; import io.bitsquare.user.Reputation;
import io.bitsquare.user.User;
import io.bitsquare.util.DSAKeyUtil; import io.bitsquare.util.DSAKeyUtil;
import java.io.IOException;
import java.math.BigInteger; import java.math.BigInteger;
import java.net.URL; import java.net.URL;
import java.util.*; import java.util.*;
@ -48,6 +48,7 @@ public class ArbitratorRegistrationController implements Initializable, ChildCon
private final Storage storage; private final Storage storage;
private final WalletFacade walletFacade; private final WalletFacade walletFacade;
private final MessageFacade messageFacade; private final MessageFacade messageFacade;
private User user;
private Arbitrator arbitrator = new Arbitrator(); private Arbitrator arbitrator = new Arbitrator();
private ArbitratorProfileController arbitratorProfileController; private ArbitratorProfileController arbitratorProfileController;
private boolean isEditMode; private boolean isEditMode;
@ -92,11 +93,12 @@ public class ArbitratorRegistrationController implements Initializable, ChildCon
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
@Inject @Inject
private ArbitratorRegistrationController(Storage storage, WalletFacade walletFacade, MessageFacade messageFacade) private ArbitratorRegistrationController(Storage storage, WalletFacade walletFacade, MessageFacade messageFacade, User user)
{ {
this.storage = storage; this.storage = storage;
this.walletFacade = walletFacade; this.walletFacade = walletFacade;
this.messageFacade = messageFacade; this.messageFacade = messageFacade;
this.user = user;
} }
@ -338,13 +340,7 @@ public class ArbitratorRegistrationController implements Initializable, ChildCon
} }
} }
try
{
messageFacade.addArbitrator(arbitrator); messageFacade.addArbitrator(arbitrator);
} catch (IOException e)
{
e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates.
}
} }
@FXML @FXML
@ -474,7 +470,7 @@ public class ArbitratorRegistrationController implements Initializable, ChildCon
minArbitrationFeeTextField); minArbitrationFeeTextField);
String pubKeyAsHex = walletFacade.getArbitratorDepositAddressInfo().getPubKeyAsHexString(); String pubKeyAsHex = walletFacade.getArbitratorDepositAddressInfo().getPubKeyAsHexString();
String messagePubKeyAsHex = DSAKeyUtil.getHexStringFromPublicKey(messageFacade.getPubKey()); String messagePubKeyAsHex = DSAKeyUtil.getHexStringFromPublicKey(user.getMessagePublicKey());
String name = nameTextField.getText(); String name = nameTextField.getText();
double maxTradeVolume = BitSquareConverter.stringToDouble(maxTradeVolumeTextField.getText()); double maxTradeVolume = BitSquareConverter.stringToDouble(maxTradeVolumeTextField.getText());

View file

@ -205,11 +205,9 @@ public class CreateOfferController implements Initializable, ChildController, Hi
return; return;
} }
log.debug("create offer pubkey " + user.getMessagePubKeyAsHex());
if (user.getCurrentBankAccount() != null) if (user.getCurrentBankAccount() != null)
{ {
offer = new Offer(user.getMessagePubKeyAsHex(), offer = new Offer(user.getMessagePublicKey(),
direction, direction,
BitSquareConverter.stringToDouble(priceTextField.getText()), BitSquareConverter.stringToDouble(priceTextField.getText()),
BtcFormatter.stringValueToSatoshis(amountTextField.getText()), BtcFormatter.stringValueToSatoshis(amountTextField.getText()),

View file

@ -42,12 +42,13 @@
<Label text="Balance:" GridPane.rowIndex="7"/> <Label text="Balance:" GridPane.rowIndex="7"/>
<BalanceTextField fx:id="balanceTextField" GridPane.columnIndex="1" GridPane.rowIndex="7"/> <BalanceTextField fx:id="balanceTextField" GridPane.columnIndex="1" GridPane.rowIndex="7"/>
<!--
<Label id="form-header-text" text="Offer details" GridPane.rowIndex="8"> <Label id="form-header-text" text="Offer details" GridPane.rowIndex="8">
<GridPane.margin> <GridPane.margin>
<Insets top="10.0"/> <Insets top="10.0"/>
</GridPane.margin> </GridPane.margin>
</Label> </Label>
-->
<Label text="Bank account type:" GridPane.rowIndex="9"/> <Label text="Bank account type:" GridPane.rowIndex="9"/>
<TextField fx:id="bankAccountTypeTextField" editable="false" GridPane.columnIndex="1" GridPane.rowIndex="9"/> <TextField fx:id="bankAccountTypeTextField" editable="false" GridPane.columnIndex="1" GridPane.rowIndex="9"/>
@ -67,17 +68,17 @@
<Button fx:id="placeOfferButton" defaultButton="true" onAction="#onPlaceOffer" text="Place offer" GridPane.columnIndex="1" GridPane.rowIndex="14"/> <Button fx:id="placeOfferButton" defaultButton="true" onAction="#onPlaceOffer" text="Place offer" GridPane.columnIndex="1" GridPane.rowIndex="14"/>
<Label fx:id="txTitleLabel" text="Transaction ID:" visible="false" GridPane.rowIndex="15"/> <Label fx:id="txTitleLabel" text="Transaction ID:" visible="false" GridPane.rowIndex="14"/>
<TextField fx:id="txTextField" visible="false" editable="false" GridPane.columnIndex="1" GridPane.rowIndex="15"/> <TextField fx:id="txTextField" visible="false" editable="false" GridPane.columnIndex="1" GridPane.rowIndex="14"/>
<ConfidenceProgressIndicator fx:id="progressIndicator" visible="false" progress="0" GridPane.columnIndex="2" GridPane.halignment="LEFT" <ConfidenceProgressIndicator fx:id="progressIndicator" visible="false" progress="0" GridPane.columnIndex="2" GridPane.halignment="LEFT"
GridPane.rowIndex="15" GridPane.rowSpan="2" GridPane.valignment="TOP"> GridPane.rowIndex="14" GridPane.rowSpan="2" GridPane.valignment="TOP">
<GridPane.margin> <GridPane.margin>
<Insets top="2.0"/> <Insets top="2.0"/>
</GridPane.margin> </GridPane.margin>
</ConfidenceProgressIndicator> </ConfidenceProgressIndicator>
<Label fx:id="confirmationLabel" text="Checking confirmations..." visible="false" GridPane.columnIndex="3" GridPane.rowIndex="15"/> <Label fx:id="confirmationLabel" text="Checking confirmations..." visible="false" GridPane.columnIndex="3" GridPane.rowIndex="14"/>
<Button fx:id="closeButton" visible="false" defaultButton="true" onAction="#onClose" text="Close" GridPane.columnIndex="1" GridPane.rowIndex="16"/> <Button fx:id="closeButton" visible="false" defaultButton="true" onAction="#onClose" text="Close" GridPane.columnIndex="1" GridPane.rowIndex="15"/>
<columnConstraints> <columnConstraints>
<ColumnConstraints halignment="RIGHT" hgrow="SOMETIMES"/> <ColumnConstraints halignment="RIGHT" hgrow="SOMETIMES"/>

View file

@ -28,7 +28,6 @@ import io.bitsquare.trade.Offer;
import io.bitsquare.trade.orderbook.OrderBook; import io.bitsquare.trade.orderbook.OrderBook;
import io.bitsquare.trade.orderbook.OrderBookFilter; import io.bitsquare.trade.orderbook.OrderBookFilter;
import io.bitsquare.user.User; import io.bitsquare.user.User;
import io.bitsquare.util.DSAKeyUtil;
import io.bitsquare.util.Utilities; import io.bitsquare.util.Utilities;
import java.math.BigInteger; import java.math.BigInteger;
import java.net.URL; import java.net.URL;
@ -304,10 +303,6 @@ public class OrderBookController implements Initializable, ChildController
{ {
user.setAccountID(walletFacade.getRegistrationAddressInfo().toString()); user.setAccountID(walletFacade.getRegistrationAddressInfo().toString());
} }
if (messageFacade != null && messageFacade.getPubKey() != null)
{
user.setMessagePubKeyAsHex(DSAKeyUtil.getHexStringFromPublicKey(messageFacade.getPubKey()));
}
storage.write(user.getClass().getName(), user); storage.write(user.getClass().getName(), user);
} catch (InsufficientMoneyException e1) } catch (InsufficientMoneyException e1)
@ -437,7 +432,7 @@ public class OrderBookController implements Initializable, ChildController
Image icon; Image icon;
Offer offer = orderBookListItem.getOffer(); Offer offer = orderBookListItem.getOffer();
if (offer.getMessagePubKeyAsHex().equals(user.getMessagePubKeyAsHex())) if (offer.getMessagePublicKey().equals(user.getMessagePublicKey()))
{ {
icon = Icons.getIconImage(Icons.REMOVE); icon = Icons.getIconImage(Icons.REMOVE);
title = "Remove"; title = "Remove";

View file

@ -191,7 +191,7 @@ public class TakerOfferController implements Initializable, ChildController
public void onBankTransferInited(String tradeId) public void onBankTransferInited(String tradeId)
{ {
setTradeId(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."); infoLabel.setText("Check your bank account and continue \n" + "when you have received the money.");
receivedFiatButton.setDisable(false); receivedFiatButton.setDisable(false);
} }

View file

@ -118,7 +118,7 @@
</Label> </Label>
<!-- row 7 --> <!-- row 7 -->
<Button fx:id="bankTransferInitedButton" defaultButton="true" onAction="#bankTransferInited" disable="true" text="Bank transfer inited" <Button fx:id="bankTransferInitedButton" defaultButton="true" onAction="#bankTransferInited" disable="true" text="Bank transfer initialised "
GridPane.columnIndex="1" GridPane.columnIndex="1"
GridPane.rowIndex="7"/> GridPane.rowIndex="7"/>

View file

@ -139,7 +139,7 @@ public class SettingsController implements Initializable, ChildController, Navig
if (settings.getAcceptedArbitrators().isEmpty()) if (settings.getAcceptedArbitrators().isEmpty())
{ {
String pubKeyAsHex = Utils.bytesToHexString(new ECKey().getPubKey()); String pubKeyAsHex = Utils.bytesToHexString(new ECKey().getPubKey());
String messagePubKeyAsHex = DSAKeyUtil.getHexStringFromPublicKey(messageFacade.getPubKey()); String messagePubKeyAsHex = DSAKeyUtil.getHexStringFromPublicKey(user.getMessagePublicKey());
List<Locale> languages = new ArrayList<>(); List<Locale> languages = new ArrayList<>();
languages.add(LanguageUtil.getDefaultLanguageLocale()); languages.add(LanguageUtil.getDefaultLanguageLocale());
List<Arbitrator.METHOD> arbitrationMethods = new ArrayList<>(); List<Arbitrator.METHOD> arbitrationMethods = new ArrayList<>();
@ -168,13 +168,7 @@ public class SettingsController implements Initializable, ChildController, Navig
settings.addAcceptedArbitrator(arbitrator); settings.addAcceptedArbitrator(arbitrator);
storage.write(settings.getClass().getName(), settings); storage.write(settings.getClass().getName(), settings);
try
{
messageFacade.addArbitrator(arbitrator); messageFacade.addArbitrator(arbitrator);
} catch (IOException e)
{
e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates.
}
} }
} }

View file

@ -0,0 +1,9 @@
package io.bitsquare.msg;
public interface BootstrapListener
{
public void onCompleted();
public void onFailed(Throwable throwable);
}

View file

@ -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<PeerDHT> 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<PeerDHT> 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<PeerAddress> 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<PeerAddress> 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<BaseFuture>()
{
@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<BaseFuture>()
{
@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 dont 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<BaseFuture>()
{
@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<BaseFuture>()
{
@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<BaseFuture>()
{
@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<PeerAddress> 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<PeerAddress> allPeers = peer.peerBean().peerMap().all();
log.debug("allPeers size = " + allPeers.size());
log.debug("allPeers = " + allPeers);
allPeers.forEach(e -> log.debug("forEach: " + e.toString())); */
}
}

View file

@ -0,0 +1,8 @@
package io.bitsquare.msg;
import net.tomp2p.peers.PeerAddress;
public interface MessageBroker
{
void handleMessage(Object message, PeerAddress peerAddress);
}

View file

@ -1,66 +1,65 @@
package io.bitsquare.msg; 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.msg.listeners.*;
import io.bitsquare.trade.Offer; import io.bitsquare.trade.Offer;
import io.bitsquare.user.Arbitrator; import io.bitsquare.user.Arbitrator;
import io.bitsquare.util.DSAKeyUtil; import io.bitsquare.user.User;
import io.bitsquare.util.StorageDirectory;
import java.io.IOException; import java.io.IOException;
import java.security.KeyPair;
import java.security.PublicKey; 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.application.Platform;
import javafx.beans.property.BooleanProperty; import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty; import javafx.beans.property.SimpleBooleanProperty;
import javax.annotation.Nullable;
import javax.inject.Inject; import javax.inject.Inject;
import net.tomp2p.connection.Bindings; import net.tomp2p.dht.FutureGet;
import net.tomp2p.connection.PeerConnection; import net.tomp2p.dht.FuturePut;
import net.tomp2p.futures.*; import net.tomp2p.dht.FutureRemove;
import net.tomp2p.p2p.Peer; import net.tomp2p.dht.PeerDHT;
import net.tomp2p.p2p.PeerMaker; 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.Number160;
import net.tomp2p.peers.PeerAddress; import net.tomp2p.peers.PeerAddress;
import net.tomp2p.storage.Data; import net.tomp2p.storage.Data;
import net.tomp2p.storage.StorageDisk;
import net.tomp2p.utils.Utils; import net.tomp2p.utils.Utils;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; 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. * 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).
* <p>
* TODO: improve callbacks that Platform.runLater is not necessary. We call usually that methods form teh UI thread.
*/ */
@SuppressWarnings({"EmptyMethod", "ConstantConditions"}) public class MessageFacade implements MessageBroker
public class MessageFacade
{ {
private static final Logger log = LoggerFactory.getLogger(MessageFacade.class); private static final Logger log = LoggerFactory.getLogger(MessageFacade.class);
// private static final String PING = "ping"; private static final String ARBITRATORS_ROOT = "ArbitratorsRoot";
// private static final String PONG = "pong";
private static final int MASTER_PEER_PORT = 5001; private final P2PNode p2pNode;
private final List<OrderBookListener> orderBookListeners = new ArrayList<>(); private final List<OrderBookListener> orderBookListeners = new ArrayList<>();
private final List<ArbitratorListener> arbitratorListeners = new ArrayList<>(); private final List<ArbitratorListener> arbitratorListeners = new ArrayList<>();
private final List<IncomingTradeMessageListener> incomingTradeMessageListeners = new ArrayList<>(); private final List<IncomingTradeMessageListener> incomingTradeMessageListeners = new ArrayList<>();
// private final List<PingPeerListener> pingPeerListeners = new ArrayList<>();
private final BooleanProperty isDirty = new SimpleBooleanProperty(false);
private Peer myPeer;
private KeyPair keyPair;
private Long lastTimeStamp = -3L;
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
// Constructor // Constructor
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
@Inject @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,57 +67,53 @@ public class MessageFacade
// Public Methods // 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); p2pNode.start(new FutureCallback<PeerDHT>()
if (BitSquare.getAppName().contains("taker"))
{ {
port = 4501; @Override
} public void onSuccess(@Nullable PeerDHT result)
else if (BitSquare.getAppName().contains("offerer"))
{ {
port = 4500; log.debug("p2pNode.start success result = " + result);
Platform.runLater(() -> bootstrapListener.onCompleted());
} }
try @Override
public void onFailure(Throwable t)
{ {
createMyPeerInstance(port); log.error(t.toString());
// setupStorage(); Platform.runLater(() -> bootstrapListener.onFailed(t));
//TODO save periodically or get informed if network address changes
saveMyAddressToDHT();
setupReplyHandler();
} catch (IOException e)
{
shutDown();
log.error("Error at init myPeerInstance" + e.getMessage());
} }
});
} }
public void shutDown() public void shutDown()
{ {
if (myPeer != null) if (p2pNode != null)
{ p2pNode.shutDown();
myPeer.shutdown();
}
} }
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
// Find peer address // Find peer address by publicKey
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
public void getPeerAddress(String pubKeyAsHex, GetPeerAddressListener listener)
public void getPeerAddress(PublicKey publicKey, GetPeerAddressListener listener)
{ {
final Number160 location = Number160.createHash(pubKeyAsHex); final Number160 locationKey = Utils.makeSHAHash(publicKey.getEncoded());
final FutureDHT getPeerAddressFuture = myPeer.get(location).start(); try
getPeerAddressFuture.addListener(new BaseFutureAdapter<BaseFuture>() {
FutureGet futureGet = p2pNode.getDomainProtectedData(locationKey, publicKey);
futureGet.addListener(new BaseFutureAdapter<BaseFuture>()
{ {
@Override @Override
public void operationComplete(BaseFuture baseFuture) throws Exception public void operationComplete(BaseFuture baseFuture) throws Exception
{ {
if (baseFuture.isSuccess() && getPeerAddressFuture.getData() != null) if (baseFuture.isSuccess() && futureGet.data() != null)
{ {
final PeerAddress peerAddress = (PeerAddress) getPeerAddressFuture.getData().getObject(); final PeerAddress peerAddress = (PeerAddress) futureGet.data().object();
Platform.runLater(() -> listener.onResult(peerAddress)); Platform.runLater(() -> listener.onResult(peerAddress));
} }
else else
@ -127,92 +122,119 @@ public class MessageFacade
} }
} }
}); });
} catch (IOException | ClassNotFoundException e)
{
e.printStackTrace();
log.error(e.toString());
}
} }
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
// Publish offer // Offer
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
public void addOffer(Offer offer) throws IOException public void addOffer(Offer offer)
{ {
log.trace("addOffer");
Number160 locationKey = Number160.createHash(offer.getCurrency().getCurrencyCode()); Number160 locationKey = Number160.createHash(offer.getCurrency().getCurrencyCode());
final Number160 contentKey = Number160.createHash(offer.getId()); try
final Data offerData = new Data(offer); {
//offerData.setTTLSeconds(5); final Data data = new Data(offer);
final FutureDHT addFuture = myPeer.put(locationKey).setData(contentKey, offerData).start(); // the offer is default 30 days valid
//final FutureDHT addFuture = myPeer.add(locationKey).setData(offerData).start(); int defaultOfferTTL = 30 * 24 * 60 * 60 * 1000;
addFuture.addListener(new BaseFutureAdapter<BaseFuture>() data.ttlSeconds(defaultOfferTTL);
FuturePut futurePut = p2pNode.addProtectedData(locationKey, data);
futurePut.addListener(new BaseFutureListener<BaseFuture>()
{ {
@Override @Override
public void operationComplete(BaseFuture future) throws Exception public void operationComplete(BaseFuture future) throws Exception
{ {
Platform.runLater(() -> onOfferAdded(offerData, future.isSuccess(), locationKey)); Platform.runLater(() -> {
} orderBookListeners.stream().forEach(orderBookListener -> orderBookListener.onOfferAdded(data, future.isSuccess()));
});
}
private void onOfferAdded(Data offerData, boolean success, Number160 locationKey)
{
log.trace("onOfferAdded");
setDirty(locationKey); setDirty(locationKey);
orderBookListeners.stream().forEach(orderBookListener -> orderBookListener.onOfferAdded(offerData, success)); });
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());
}
} }
///////////////////////////////////////////////////////////////////////////////////////////
// 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<BaseFuture>()
{
@Override @Override
public void operationComplete(BaseFuture future) throws Exception public void exceptionCaught(Throwable t) throws Exception
{ {
final Map<Number160, Data> dataMap = getOffersFuture.getDataMap(); log.error(t.toString());
Platform.runLater(() -> onOffersReceived(dataMap, future.isSuccess()));
} }
}); });
} } catch (IOException | ClassNotFoundException e)
private void onOffersReceived(Map<Number160, Data> dataMap, boolean success)
{ {
log.trace("onOffersReceived"); e.printStackTrace();
orderBookListeners.stream().forEach(orderBookListener -> orderBookListener.onOffersReceived(dataMap, success)); log.error(e.toString());
}
} }
///////////////////////////////////////////////////////////////////////////////////////////
// Remove offer
///////////////////////////////////////////////////////////////////////////////////////////
public void removeOffer(Offer offer) public void removeOffer(Offer offer)
{ {
log.trace("removeOffer");
Number160 locationKey = Number160.createHash(offer.getCurrency().getCurrencyCode()); Number160 locationKey = Number160.createHash(offer.getCurrency().getCurrencyCode());
Number160 contentKey = Number160.createHash(offer.getId()); try
log.debug("removeOffer"); {
FutureDHT removeFuture = myPeer.remove(locationKey).setReturnResults().setContentKey(contentKey).start(); final Data data = new Data(offer);
removeFuture.addListener(new BaseFutureAdapter<BaseFuture>() FutureRemove futureRemove = p2pNode.removeFromDataMap(locationKey, data);
futureRemove.addListener(new BaseFutureListener<BaseFuture>()
{ {
@Override @Override
public void operationComplete(BaseFuture future) throws Exception public void operationComplete(BaseFuture future) throws Exception
{ {
Platform.runLater(() -> onOfferRemoved(removeFuture.getData(), future.isSuccess(), locationKey)); 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());
}
} }
private void onOfferRemoved(Data data, boolean success, Number160 locationKey) @Override
public void exceptionCaught(Throwable t) throws Exception
{ {
log.trace("onOfferRemoved"); log.error(t.toString());
setDirty(locationKey); }
orderBookListeners.stream().forEach(orderBookListener -> orderBookListener.onOfferRemoved(data, success)); });
} 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<BaseFuture>()
{
@Override
public void operationComplete(BaseFuture baseFuture) throws Exception
{
Platform.runLater(() -> orderBookListeners.stream().forEach(orderBookListener -> orderBookListener.onOffersReceived(futureGet.dataMap(), baseFuture.isSuccess())));
if (baseFuture.isSuccess())
{
log.trace("Get offers from DHT was successful. Stored data: [key: " + locationKey + ", values: " + futureGet.dataMap() + "]");
}
else
{
log.error("Get offers from DHT failed with reason:" + baseFuture.failedReason());
}
}
});
} }
@ -222,14 +244,13 @@ public class MessageFacade
public void sendTradeMessage(PeerAddress peerAddress, TradeMessage tradeMessage, OutgoingTradeMessageListener listener) public void sendTradeMessage(PeerAddress peerAddress, TradeMessage tradeMessage, OutgoingTradeMessageListener listener)
{ {
final PeerConnection peerConnection = myPeer.createPeerConnection(peerAddress, 10); FutureDirect futureDirect = p2pNode.sendData(peerAddress, tradeMessage);
final FutureResponse sendFuture = myPeer.sendDirect(peerConnection).setObject(tradeMessage).start(); futureDirect.addListener(new BaseFutureListener<BaseFuture>()
sendFuture.addListener(new BaseFutureAdapter<BaseFuture>()
{ {
@Override @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()); Platform.runLater(() -> listener.onResult());
} }
@ -238,316 +259,109 @@ public class MessageFacade
Platform.runLater(() -> listener.onFailed()); 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 // Arbitrators
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
public void addArbitrator(Arbitrator arbitrator) throws IOException public void addArbitrator(Arbitrator arbitrator)
{
Number160 locationKey = Number160.createHash(ARBITRATORS_ROOT);
try
{ {
Number160 locationKey = Number160.createHash("Arbitrators");
final Number160 contentKey = Number160.createHash(arbitrator.getId());
final Data arbitratorData = new Data(arbitrator); final Data arbitratorData = new Data(arbitrator);
//offerData.setTTLSeconds(5);
final FutureDHT addFuture = myPeer.put(locationKey).setData(contentKey, arbitratorData).start(); FuturePut addFuture = p2pNode.addProtectedData(locationKey, arbitratorData);
//final FutureDHT addFuture = myPeer.add(locationKey).setData(offerData).start();
addFuture.addListener(new BaseFutureAdapter<BaseFuture>() addFuture.addListener(new BaseFutureAdapter<BaseFuture>()
{ {
@Override @Override
public void operationComplete(BaseFuture future) throws Exception public void operationComplete(BaseFuture future) throws Exception
{ {
Platform.runLater(() -> onArbitratorAdded(arbitratorData, future.isSuccess(), locationKey)); Platform.runLater(() -> arbitratorListeners.stream().forEach(listener -> listener.onArbitratorAdded(arbitratorData, addFuture.isSuccess())));
} if (addFuture.isSuccess())
});
}
@SuppressWarnings("UnusedParameters")
private void onArbitratorAdded(Data arbitratorData, boolean success, Number160 locationKey)
{ {
} log.trace("Add arbitrator to DHT was successful. Stored data: [key: " + locationKey + ", values: " + arbitratorData + "]");
@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<BaseFuture>()
{
@Override
public void operationComplete(BaseFuture future) throws Exception
{
final Map<Number160, Data> dataMap = getArbitratorsFuture.getDataMap();
Platform.runLater(() -> onArbitratorsReceived(dataMap, future.isSuccess()));
}
});
}
private void onArbitratorsReceived(Map<Number160, Data> 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<BaseFuture>()
{
@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 else
{ {
lastTimeStamp++; log.error("Add arbitrator to DHT failed with reason:" + addFuture.failedReason());
} }
} }
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();
try
{
FutureDHT putFuture = myPeer.put(getDirtyLocationKey(locationKey)).setData(new Data(lastTimeStamp)).start();
putFuture.addListener(new BaseFutureListener<BaseFuture>()
{
@Override
public void operationComplete(BaseFuture future) throws Exception
{
//System.out.println("operationComplete");
}
@Override
public void exceptionCaught(Throwable t) throws Exception
{
System.err.println("exceptionCaught");
}
}); });
} catch (IOException e) } catch (IOException e)
{ {
log.warn("Error at writing dirty flag (timeStamp) " + e.getMessage()); e.printStackTrace();
} catch (ClassNotFoundException e)
{
e.printStackTrace();
} }
} }
public void removeArbitrator(Arbitrator arbitrator) throws IOException, ClassNotFoundException
///////////////////////////////////////////////////////////////////////////////////////////
// Send message
///////////////////////////////////////////////////////////////////////////////////////////
/* public boolean sendMessage(Object message)
{ {
boolean result = false; Number160 locationKey = Number160.createHash(ARBITRATORS_ROOT);
if (otherPeerAddress != null) final Data arbitratorData = new Data(arbitrator);
{ FutureRemove removeFuture = p2pNode.removeFromDataMap(locationKey, arbitratorData);
if (peerConnection != null) removeFuture.addListener(new BaseFutureAdapter<BaseFuture>()
peerConnection.close();
peerConnection = myPeer.createPeerConnection(otherPeerAddress, 20);
if (!peerConnection.isClosed())
{
FutureResponse sendFuture = myPeer.sendDirect(peerConnection).setObject(message).start();
sendFuture.addListener(new BaseFutureAdapter<BaseFuture>()
{ {
@Override @Override
public void operationComplete(BaseFuture baseFuture) throws Exception public void operationComplete(BaseFuture future) throws Exception
{ {
if (sendFuture.isSuccess()) Platform.runLater(() -> arbitratorListeners.stream().forEach(listener -> listener.onArbitratorRemoved(arbitratorData, removeFuture.isSuccess())));
if (removeFuture.isSuccess())
{ {
final Object object = sendFuture.getObject(); log.trace("Remove arbitrator from DHT was successful. Stored data: [key: " + locationKey + ", values: " + arbitratorData + "]");
Platform.runLater(() -> onResponseFromSend(object));
} }
else else
{ {
Platform.runLater(() -> onSendFailed()); log.error("Remove arbitrators from DHT failed with reason:" + removeFuture.failedReason());
}
}
}
);
result = true;
}
}
return result;
} */
/*
private void onResponseFromSend(Object response)
{
for (MessageListener messageListener : messageListeners)
messageListener.onResponseFromSend(response);
}
private void onSendFailed()
{
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<BaseFuture>()
{
@Override
public void operationComplete(BaseFuture baseFuture) throws Exception
{
final Data data = getPeerAddressFuture.getData();
if (data != null && data.getObject() instanceof PeerAddress)
{
final PeerAddress peerAddress = (PeerAddress) data.getObject();
Platform.runLater(() -> onAddressFoundPingPeer(peerAddress));
} }
} }
}); });
} }
private void onAddressFoundPingPeer(PeerAddress peerAddress) public void getArbitrators(Locale languageLocale)
{ {
try Number160 locationKey = Number160.createHash(ARBITRATORS_ROOT);
{ FutureGet futureGet = p2pNode.getDataMap(locationKey);
final PeerConnection peerConnection = myPeer.createPeerConnection(peerAddress, 10); futureGet.addListener(new BaseFutureAdapter<BaseFuture>()
if (!peerConnection.isClosed())
{
FutureResponse sendFuture = myPeer.sendDirect(peerConnection).setObject(PING).start();
sendFuture.addListener(new BaseFutureAdapter<BaseFuture>()
{ {
@Override @Override
public void operationComplete(BaseFuture baseFuture) throws Exception public void operationComplete(BaseFuture baseFuture) throws Exception
{ {
if (sendFuture.isSuccess()) Platform.runLater(() -> arbitratorListeners.stream().forEach(listener -> listener.onArbitratorsReceived(futureGet.dataMap(), baseFuture.isSuccess())));
if (baseFuture.isSuccess())
{ {
final String pong = (String) sendFuture.getObject(); log.trace("Get arbitrators from DHT was successful. Stored data: [key: " + locationKey + ", values: " + futureGet.dataMap() + "]");
Platform.runLater(() -> onResponseFromPing(PONG.equals(pong)));
} }
else else
{ {
peerConnection.close(); log.error("Get arbitrators from DHT failed with reason:" + baseFuture.failedReason());
Platform.runLater(() -> onResponseFromPing(false));
} }
} }
});
} }
);
}
} 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 // Event Listeners
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
public void addMessageListener(OrderBookListener listener) public void addOrderBookListener(OrderBookListener listener)
{ {
orderBookListeners.add(listener); orderBookListeners.add(listener);
} }
public void removeMessageListener(OrderBookListener listener) public void removeOrderBookListener(OrderBookListener listener)
{ {
orderBookListeners.remove(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<BaseFuture>()
{
@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<BaseFuture>()
{
@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 // Incoming message handler
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
private void onMessage(Object request, PeerAddress sender)
{
if (request 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<BaseFuture>()
{
@Override @Override
public void operationComplete(BaseFuture future) throws Exception public void handleMessage(Object message, PeerAddress peerAddress)
{ {
if (futureBootstrap.getBootstrapTo() != null) if (message instanceof TradeMessage)
{ {
PeerAddress masterPeerAddress = futureBootstrap.getBootstrapTo().iterator().next(); log.error("####################");
final FutureDiscover futureDiscover = myPeer.discover().setPeerAddress(masterPeerAddress).start(); Platform.runLater(() -> incomingTradeMessageListeners.stream().forEach(e -> e.onMessage((TradeMessage) message, peerAddress)));
futureDiscover.addListener(new BaseFutureListener<BaseFuture>()
{
@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();
}
}
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();
}
} }

View file

@ -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<PeerDHT>()
{
@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<PeerDHT> callback)
{
useDiscStorage(useDiskStorage);
setupTimerForIPCheck();
FutureCallback<PeerDHT> localCallback = new FutureCallback<PeerDHT>()
{
@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<PeerDHT> callback, SeedNodeAddress seedNodeAddress)
{
Thread bootstrapThread = new Thread(new Runnable()
{
@Override
public void run()
{
log.debug("runBootstrapThread");
ListenableFuture<PeerDHT> 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<BaseFuture>()
{
@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<PeerDHT> bootstrap(SeedNodeAddress seedNodeAddress)
{
BootstrappedPeerFactory bootstrappedPeerFactory = new BootstrappedPeerFactory(keyPair, storage, seedNodeAddress);
ListenableFuture<PeerDHT> bootstrapComplete = bootstrappedPeerFactory.start();
Futures.addCallback(bootstrapComplete, new FutureCallback<PeerDHT>()
{
@Override
public void onSuccess(@Nullable PeerDHT peerDHT)
{
try
{
if (peerDHT != null)
{
P2PNode.this.peerDHT = peerDHT;
setupReplyHandler();
FuturePut futurePut = storePeerAddress();
futurePut.addListener(new BaseFutureListener<BaseFuture>()
{
@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();
}
}
}

View file

@ -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<StaticSeedNodeAddresses> getAllSeedNodeAddresses()
{
return new ArrayList<>(Arrays.asList(StaticSeedNodeAddresses.values()));
}
public String getId()
{
return id;
}
public String getIp()
{
return ip;
}
public int getPort()
{
return port;
}
}
}

View file

@ -1,7 +1,7 @@
package io.bitsquare.msg.listeners; package io.bitsquare.msg.listeners;
import java.util.Map; import java.util.Map;
import net.tomp2p.peers.Number160; import net.tomp2p.peers.Number640;
import net.tomp2p.storage.Data; import net.tomp2p.storage.Data;
@SuppressWarnings({"EmptyMethod", "UnusedParameters"}) @SuppressWarnings({"EmptyMethod", "UnusedParameters"})
@ -9,7 +9,7 @@ public interface ArbitratorListener
{ {
void onArbitratorAdded(Data offerData, boolean success); void onArbitratorAdded(Data offerData, boolean success);
void onArbitratorsReceived(Map<Number160, Data> dataMap, boolean success); void onArbitratorsReceived(Map<Number640, Data> dataMap, boolean success);
void onArbitratorRemoved(Data data, boolean success); void onArbitratorRemoved(Data data, boolean success);
} }

View file

@ -1,7 +1,7 @@
package io.bitsquare.msg.listeners; package io.bitsquare.msg.listeners;
import java.util.Map; import java.util.Map;
import net.tomp2p.peers.Number160; import net.tomp2p.peers.Number640;
import net.tomp2p.storage.Data; import net.tomp2p.storage.Data;
public interface OrderBookListener public interface OrderBookListener
@ -9,7 +9,7 @@ public interface OrderBookListener
@SuppressWarnings("UnusedParameters") @SuppressWarnings("UnusedParameters")
void onOfferAdded(Data offerData, boolean success); void onOfferAdded(Data offerData, boolean success);
void onOffersReceived(Map<Number160, Data> dataMap, boolean success); void onOffersReceived(Map<Number640, Data> dataMap, boolean success);
void onOfferRemoved(Data data, boolean success); void onOfferRemoved(Data data, boolean success);
} }

View file

@ -1,8 +1,10 @@
package io.bitsquare.trade; package io.bitsquare.trade;
import io.bitsquare.bank.BankAccount; import io.bitsquare.bank.BankAccount;
import io.bitsquare.util.DSAKeyUtil;
import java.io.Serializable; import java.io.Serializable;
import java.math.BigInteger; import java.math.BigInteger;
import java.security.PublicKey;
public class Contract implements Serializable public class Contract implements Serializable
{ {
@ -15,8 +17,8 @@ public class Contract implements Serializable
private final String takerAccountID; private final String takerAccountID;
private final BankAccount offererBankAccount; private final BankAccount offererBankAccount;
private final BankAccount takerBankAccount; private final BankAccount takerBankAccount;
private final String offererMessagePubKeyAsHex; private final String offererMessagePublicKeyAsString;
private final String takerMessagePubKeyAsHex; private final String takerMessagePublicKeyAsString;
public Contract(Offer offer, public Contract(Offer offer,
BigInteger tradeAmount, BigInteger tradeAmount,
@ -25,8 +27,8 @@ public class Contract implements Serializable
String takerAccountID, String takerAccountID,
BankAccount offererBankAccount, BankAccount offererBankAccount,
BankAccount takerBankAccount, BankAccount takerBankAccount,
String offererMessagePubKeyAsHex, PublicKey offererMessagePublicKey,
String takerMessagePubKeyAsHex) PublicKey takerMessagePublicKey)
{ {
this.offer = offer; this.offer = offer;
this.tradeAmount = tradeAmount; this.tradeAmount = tradeAmount;
@ -35,8 +37,8 @@ public class Contract implements Serializable
this.takerAccountID = takerAccountID; this.takerAccountID = takerAccountID;
this.offererBankAccount = offererBankAccount; this.offererBankAccount = offererBankAccount;
this.takerBankAccount = takerBankAccount; this.takerBankAccount = takerBankAccount;
this.offererMessagePubKeyAsHex = offererMessagePubKeyAsHex; this.offererMessagePublicKeyAsString = DSAKeyUtil.getHexStringFromPublicKey(offererMessagePublicKey);
this.takerMessagePubKeyAsHex = takerMessagePubKeyAsHex; this.takerMessagePublicKeyAsString = DSAKeyUtil.getHexStringFromPublicKey(takerMessagePublicKey);
} }
@ -79,17 +81,16 @@ public class Contract implements Serializable
return takerBankAccount; return takerBankAccount;
} }
public String getTakerMessagePubKeyAsHex() public String getTakerMessagePublicKey()
{ {
return takerMessagePubKeyAsHex; return takerMessagePublicKeyAsString;
} }
public String getOffererMessagePubKeyAsHex() public String getOffererMessagePublicKey()
{ {
return offererMessagePubKeyAsHex; return offererMessagePublicKeyAsString;
} }
@Override @Override
public String toString() public String toString()
{ {
@ -101,10 +102,8 @@ public class Contract implements Serializable
", takerAccountID='" + takerAccountID + '\'' + ", takerAccountID='" + takerAccountID + '\'' +
", offererBankAccount=" + offererBankAccount + ", offererBankAccount=" + offererBankAccount +
", takerBankAccount=" + takerBankAccount + ", takerBankAccount=" + takerBankAccount +
", offererMessagePubKeyAsHex='" + offererMessagePubKeyAsHex + '\'' + ", takerMessagePublicKeyAsString=" + takerMessagePublicKeyAsString +
", takerMessagePubKeyAsHex='" + takerMessagePubKeyAsHex + '\'' + ", offererMessagePublicKeyAsString=" + offererMessagePublicKeyAsString +
'}'; '}';
} }
} }

View file

@ -6,6 +6,7 @@ import io.bitsquare.locale.Country;
import io.bitsquare.user.Arbitrator; import io.bitsquare.user.Arbitrator;
import java.io.Serializable; import java.io.Serializable;
import java.math.BigInteger; import java.math.BigInteger;
import java.security.PublicKey;
import java.util.*; import java.util.*;
public class Offer implements Serializable public class Offer implements Serializable
@ -23,7 +24,7 @@ public class Offer implements Serializable
private final double price; private final double price;
private final BigInteger amount; private final BigInteger amount;
private final BigInteger minAmount; private final BigInteger minAmount;
private final String messagePubKeyAsHex; private final PublicKey messagePublicKey;
private final BankAccountType bankAccountType; private final BankAccountType bankAccountType;
private final Country bankAccountCountry; private final Country bankAccountCountry;
@ -39,7 +40,7 @@ public class Offer implements Serializable
// Constructor // Constructor
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
public Offer(String messagePubKeyAsHex, public Offer(PublicKey messagePublicKey,
Direction direction, Direction direction,
double price, double price,
BigInteger amount, BigInteger amount,
@ -53,7 +54,7 @@ public class Offer implements Serializable
List<Country> acceptedCountries, List<Country> acceptedCountries,
List<Locale> acceptedLanguageLocales) List<Locale> acceptedLanguageLocales)
{ {
this.messagePubKeyAsHex = messagePubKeyAsHex; this.messagePublicKey = messagePublicKey;
this.direction = direction; this.direction = direction;
this.price = price; this.price = price;
this.amount = amount; this.amount = amount;
@ -77,9 +78,9 @@ public class Offer implements Serializable
// Setters // Setters
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
public String getMessagePubKeyAsHex() public PublicKey getMessagePublicKey()
{ {
return messagePubKeyAsHex; return messagePublicKey;
} }
@ -183,7 +184,7 @@ public class Offer implements Serializable
", price=" + price + ", price=" + price +
", amount=" + amount + ", amount=" + amount +
", minAmount=" + minAmount + ", minAmount=" + minAmount +
", messagePubKey=" + messagePubKeyAsHex.hashCode() + ", messagePubKey=" + messagePublicKey.hashCode() +
", bankAccountTypeEnum=" + bankAccountType + ", bankAccountTypeEnum=" + bankAccountType +
", bankAccountCountryLocale=" + bankAccountCountry + ", bankAccountCountryLocale=" + bankAccountCountry +
", collateral=" + collateral + ", collateral=" + collateral +

View file

@ -136,11 +136,11 @@ public class Trading
} }
public void removeOffer(Offer offer) public void removeOffer(Offer offer)
{ { //TODO
if (!offers.containsKey(offer.getId())) /* if (!offers.containsKey(offer.getId()))
{ {
throw new IllegalStateException("offers does not contain the offer with the ID " + offer.getId()); throw new IllegalStateException("offers does not contain the offer with the ID " + offer.getId());
} }*/
offers.remove(offer.getId()); offers.remove(offer.getId());
saveOffers(); saveOffers();

View file

@ -21,11 +21,17 @@ import javafx.collections.ObservableList;
import javafx.collections.transformation.FilteredList; import javafx.collections.transformation.FilteredList;
import javafx.collections.transformation.SortedList; import javafx.collections.transformation.SortedList;
import javax.inject.Inject; import javax.inject.Inject;
import net.tomp2p.peers.Number160; import net.tomp2p.peers.Number640;
import net.tomp2p.storage.Data; import net.tomp2p.storage.Data;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
/*
TODO
remove dependencies to tomp2p
import net.tomp2p.peers.Number160;
import net.tomp2p.storage.Data;
*/
public class OrderBook implements OrderBookListener public class OrderBook implements OrderBookListener
{ {
private static final Logger log = LoggerFactory.getLogger(OrderBook.class); private static final Logger log = LoggerFactory.getLogger(OrderBook.class);
@ -59,12 +65,12 @@ public class OrderBook implements OrderBookListener
public void init() public void init()
{ {
messageFacade.addMessageListener(this); messageFacade.addOrderBookListener(this);
} }
public void cleanup() public void cleanup()
{ {
messageFacade.removeMessageListener(this); messageFacade.removeOrderBookListener(this);
} }
public void loadOffers() public void loadOffers()
@ -177,7 +183,7 @@ public class OrderBook implements OrderBookListener
{ {
try try
{ {
Object offerDataObject = offerData.getObject(); Object offerDataObject = offerData.object();
if (offerDataObject instanceof Offer) if (offerDataObject instanceof Offer)
{ {
Offer offer = (Offer) offerDataObject; Offer offer = (Offer) offerDataObject;
@ -190,7 +196,7 @@ public class OrderBook implements OrderBookListener
} }
@Override @Override
public void onOffersReceived(Map<Number160, Data> dataMap, boolean success) public void onOffersReceived(Map<Number640, Data> dataMap, boolean success)
{ {
if (success && dataMap != null) if (success && dataMap != null)
{ {
@ -200,7 +206,7 @@ public class OrderBook implements OrderBookListener
{ {
try try
{ {
Object offerDataObject = offerData.getObject(); Object offerDataObject = offerData.object();
if (offerDataObject instanceof Offer) if (offerDataObject instanceof Offer)
{ {
Offer offer = (Offer) offerDataObject; Offer offer = (Offer) offerDataObject;
@ -226,7 +232,7 @@ public class OrderBook implements OrderBookListener
{ {
try try
{ {
Object offerDataObject = offerData.getObject(); Object offerDataObject = offerData.object();
if (offerDataObject instanceof Offer) if (offerDataObject instanceof Offer)
{ {
Offer offer = (Offer) offerDataObject; Offer offer = (Offer) offerDataObject;

View file

@ -16,6 +16,7 @@ import io.bitsquare.trade.protocol.taker.RequestOffererPublishDepositTxMessage;
import io.bitsquare.trade.protocol.taker.TakeOfferFeePayedMessage; import io.bitsquare.trade.protocol.taker.TakeOfferFeePayedMessage;
import io.bitsquare.user.User; import io.bitsquare.user.User;
import java.math.BigInteger; import java.math.BigInteger;
import java.security.PublicKey;
import net.tomp2p.peers.PeerAddress; import net.tomp2p.peers.PeerAddress;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger; import org.slf4j.Logger;
@ -76,7 +77,7 @@ public class ProtocolForOffererAsBuyer
private final String arbitratorPubKey; private final String arbitratorPubKey;
private final BankAccount bankAccount; private final BankAccount bankAccount;
private final String accountId; private final String accountId;
private final String messagePubKey; private final PublicKey messagePublicKey;
private final ECKey accountKey; private final ECKey accountKey;
private final String payoutAddress; private final String payoutAddress;
@ -90,7 +91,7 @@ public class ProtocolForOffererAsBuyer
private String peersPayoutAddress; private String peersPayoutAddress;
private String peersAccountId; private String peersAccountId;
private BankAccount peersBankAccount; private BankAccount peersBankAccount;
private String peersMessagePubKey; private PublicKey peersMessagePublicKey;
private String peersContractAsJson; private String peersContractAsJson;
private String signedTakerDepositTxAsHex; private String signedTakerDepositTxAsHex;
private String txConnOutAsHex; private String txConnOutAsHex;
@ -129,7 +130,7 @@ public class ProtocolForOffererAsBuyer
bankAccount = user.getBankAccount(trade.getOffer().getBankAccountId()); bankAccount = user.getBankAccount(trade.getOffer().getBankAccountId());
accountId = user.getAccountId(); accountId = user.getAccountId();
messagePubKey = user.getMessagePubKeyAsHex(); messagePublicKey = user.getMessagePublicKey();
accountKey = walletFacade.getRegistrationAddressInfo().getKey(); accountKey = walletFacade.getRegistrationAddressInfo().getKey();
payoutAddress = walletFacade.getAddressInfoByTradeID(tradeId).getAddressString(); payoutAddress = walletFacade.getAddressInfoByTradeID(tradeId).getAddressString();
@ -241,7 +242,7 @@ public class ProtocolForOffererAsBuyer
String peersPayoutAddress = nonEmptyStringOf(message.getTakerPayoutAddress()); String peersPayoutAddress = nonEmptyStringOf(message.getTakerPayoutAddress());
String peersAccountId = nonEmptyStringOf(message.getTakerAccountId()); String peersAccountId = nonEmptyStringOf(message.getTakerAccountId());
BankAccount peersBankAccount = checkNotNull(message.getTakerBankAccount()); BankAccount peersBankAccount = checkNotNull(message.getTakerBankAccount());
String peersMessagePubKey = nonEmptyStringOf(message.getTakerMessagePubKey()); PublicKey peersMessagePublicKey = checkNotNull(message.getTakerMessagePublicKey());
String peersContractAsJson = nonEmptyStringOf(message.getTakerContractAsJson()); String peersContractAsJson = nonEmptyStringOf(message.getTakerContractAsJson());
String signedTakerDepositTxAsHex = nonEmptyStringOf(message.getSignedTakerDepositTxAsHex()); String signedTakerDepositTxAsHex = nonEmptyStringOf(message.getSignedTakerDepositTxAsHex());
String txConnOutAsHex = nonEmptyStringOf(message.getTxConnOutAsHex()); String txConnOutAsHex = nonEmptyStringOf(message.getTxConnOutAsHex());
@ -253,7 +254,7 @@ public class ProtocolForOffererAsBuyer
this.peersPayoutAddress = peersPayoutAddress; this.peersPayoutAddress = peersPayoutAddress;
this.peersAccountId = peersAccountId; this.peersAccountId = peersAccountId;
this.peersBankAccount = peersBankAccount; this.peersBankAccount = peersBankAccount;
this.peersMessagePubKey = peersMessagePubKey; this.peersMessagePublicKey = peersMessagePublicKey;
this.peersContractAsJson = peersContractAsJson; this.peersContractAsJson = peersContractAsJson;
this.signedTakerDepositTxAsHex = signedTakerDepositTxAsHex; this.signedTakerDepositTxAsHex = signedTakerDepositTxAsHex;
this.txConnOutAsHex = txConnOutAsHex; this.txConnOutAsHex = txConnOutAsHex;
@ -277,12 +278,12 @@ public class ProtocolForOffererAsBuyer
accountId, accountId,
tradeAmount, tradeAmount,
takeOfferFeeTxId, takeOfferFeeTxId,
messagePubKey, messagePublicKey,
offer, offer,
peersAccountId, peersAccountId,
bankAccount, bankAccount,
peersBankAccount, peersBankAccount,
peersMessagePubKey, peersMessagePublicKey,
peersContractAsJson, peersContractAsJson,
accountKey); accountKey);
} }

View file

@ -8,6 +8,7 @@ import io.bitsquare.trade.Offer;
import io.bitsquare.trade.protocol.FaultHandler; import io.bitsquare.trade.protocol.FaultHandler;
import io.bitsquare.util.Utilities; import io.bitsquare.util.Utilities;
import java.math.BigInteger; import java.math.BigInteger;
import java.security.PublicKey;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -21,17 +22,17 @@ public class VerifyAndSignContract
String accountId, String accountId,
BigInteger tradeAmount, BigInteger tradeAmount,
String takeOfferFeeTxId, String takeOfferFeeTxId,
String messagePubKeyAsHex, PublicKey messagePublicKey,
Offer offer, Offer offer,
String peersAccountId, String peersAccountId,
BankAccount bankAccount, BankAccount bankAccount,
BankAccount peersBankAccount, BankAccount peersBankAccount,
String takerMessagePubKey, PublicKey takerMessagePublicKey,
String peersContractAsJson, String peersContractAsJson,
ECKey registrationKey) ECKey registrationKey)
{ {
log.trace("Run task"); 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); String contractAsJson = Utilities.objectToJson(contract);
// log.trace("Offerer contract created: " + contract); // log.trace("Offerer contract created: " + contract);

View file

@ -8,6 +8,7 @@ import io.bitsquare.trade.Offer;
import io.bitsquare.trade.protocol.FaultHandler; import io.bitsquare.trade.protocol.FaultHandler;
import io.bitsquare.util.Utilities; import io.bitsquare.util.Utilities;
import java.math.BigInteger; import java.math.BigInteger;
import java.security.PublicKey;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -23,8 +24,8 @@ public class CreateAndSignContract
String takeOfferFeeTxId, String takeOfferFeeTxId,
String accountId, String accountId,
BankAccount bankAccount, BankAccount bankAccount,
String peersMessagePubKeyAsHex, PublicKey peersMessagePublicKey,
String messagePubKeyAsHex, PublicKey messagePublicKey,
String peersAccountId, String peersAccountId,
BankAccount peersBankAccount, BankAccount peersBankAccount,
ECKey registrationKey) ECKey registrationKey)
@ -32,7 +33,7 @@ public class CreateAndSignContract
log.trace("Run task"); log.trace("Run task");
try 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 contractAsJson = Utilities.objectToJson(contract);
String signature = cryptoFacade.signContract(registrationKey, contractAsJson); String signature = cryptoFacade.signContract(registrationKey, contractAsJson);

View file

@ -3,6 +3,7 @@ package io.bitsquare.trade.protocol.taker;
import io.bitsquare.msg.MessageFacade; import io.bitsquare.msg.MessageFacade;
import io.bitsquare.msg.listeners.GetPeerAddressListener; import io.bitsquare.msg.listeners.GetPeerAddressListener;
import io.bitsquare.trade.protocol.FaultHandler; import io.bitsquare.trade.protocol.FaultHandler;
import java.security.PublicKey;
import net.tomp2p.peers.PeerAddress; import net.tomp2p.peers.PeerAddress;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -11,10 +12,10 @@ public class GetPeerAddress
{ {
private static final Logger log = LoggerFactory.getLogger(GetPeerAddress.class); 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"); log.trace("Run task");
messageFacade.getPeerAddress(messagePubKeyAsHex, new GetPeerAddressListener() messageFacade.getPeerAddress(messagePublicKey, new GetPeerAddressListener()
{ {
@Override @Override
public void onResult(PeerAddress peerAddress) public void onResult(PeerAddress peerAddress)

View file

@ -16,6 +16,7 @@ import io.bitsquare.trade.protocol.offerer.RequestTakerDepositPaymentMessage;
import io.bitsquare.trade.protocol.offerer.RespondToTakeOfferRequestMessage; import io.bitsquare.trade.protocol.offerer.RespondToTakeOfferRequestMessage;
import io.bitsquare.user.User; import io.bitsquare.user.User;
import java.math.BigInteger; import java.math.BigInteger;
import java.security.PublicKey;
import net.tomp2p.peers.PeerAddress; import net.tomp2p.peers.PeerAddress;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -66,11 +67,11 @@ public class ProtocolForTakerAsSeller
private final String tradeId; private final String tradeId;
private final BankAccount bankAccount; private final BankAccount bankAccount;
private final String accountId; private final String accountId;
private final String messagePubKey; private final PublicKey messagePublicKey;
private final BigInteger tradeAmount; private final BigInteger tradeAmount;
private final String pubKeyForThatTrade; private final String pubKeyForThatTrade;
private final ECKey accountKey; private final ECKey accountKey;
private final String peersMessagePubKey; private final PublicKey peersMessagePublicKey;
private final BigInteger collateral; private final BigInteger collateral;
private final String arbitratorPubKey; private final String arbitratorPubKey;
@ -122,11 +123,11 @@ public class ProtocolForTakerAsSeller
collateral = trade.getCollateralAmount(); collateral = trade.getCollateralAmount();
arbitratorPubKey = trade.getOffer().getArbitrator().getPubKeyAsHex(); arbitratorPubKey = trade.getOffer().getArbitrator().getPubKeyAsHex();
peersMessagePubKey = offer.getMessagePubKeyAsHex(); peersMessagePublicKey = offer.getMessagePublicKey();
bankAccount = user.getBankAccount(offer.getBankAccountId()); bankAccount = user.getBankAccount(offer.getBankAccountId());
accountId = user.getAccountId(); accountId = user.getAccountId();
messagePubKey = user.getMessagePubKeyAsHex(); messagePublicKey = user.getMessagePublicKey();
pubKeyForThatTrade = walletFacade.getAddressInfoByTradeID(tradeId).getPubKeyAsHexString(); pubKeyForThatTrade = walletFacade.getAddressInfoByTradeID(tradeId).getPubKeyAsHexString();
accountKey = walletFacade.getRegistrationAddressInfo().getKey(); accountKey = walletFacade.getRegistrationAddressInfo().getKey();
@ -138,7 +139,7 @@ public class ProtocolForTakerAsSeller
{ {
log.debug("start called " + step++); log.debug("start called " + step++);
state = State.GetPeerAddress; state = State.GetPeerAddress;
GetPeerAddress.run(this::onResultGetPeerAddress, this::onFault, messageFacade, peersMessagePubKey); GetPeerAddress.run(this::onResultGetPeerAddress, this::onFault, messageFacade, peersMessagePublicKey);
} }
public void onResultGetPeerAddress(PeerAddress peerAddress) public void onResultGetPeerAddress(PeerAddress peerAddress)
@ -176,6 +177,7 @@ public class ProtocolForTakerAsSeller
else else
{ {
listener.onTakeOfferRequestRejected(trade); listener.onTakeOfferRequestRejected(trade);
// exit case
} }
} }
@ -239,8 +241,8 @@ public class ProtocolForTakerAsSeller
takeOfferFeeTxId, takeOfferFeeTxId,
accountId, accountId,
bankAccount, bankAccount,
peersMessagePubKey, peersMessagePublicKey,
messagePubKey, messagePublicKey,
peersAccountId, peersAccountId,
peersBankAccount, peersBankAccount,
accountKey); accountKey);
@ -272,7 +274,7 @@ public class ProtocolForTakerAsSeller
walletFacade, walletFacade,
bankAccount, bankAccount,
accountId, accountId,
messagePubKey, messagePublicKey,
tradeId, tradeId,
contractAsJson, contractAsJson,
takerSignature, takerSignature,

View file

@ -3,6 +3,7 @@ package io.bitsquare.trade.protocol.taker;
import io.bitsquare.bank.BankAccount; import io.bitsquare.bank.BankAccount;
import io.bitsquare.msg.TradeMessage; import io.bitsquare.msg.TradeMessage;
import java.io.Serializable; import java.io.Serializable;
import java.security.PublicKey;
public class RequestOffererPublishDepositTxMessage implements Serializable, TradeMessage public class RequestOffererPublishDepositTxMessage implements Serializable, TradeMessage
{ {
@ -10,7 +11,7 @@ public class RequestOffererPublishDepositTxMessage implements Serializable, Trad
private final String tradeId; private final String tradeId;
private BankAccount bankAccount; private BankAccount bankAccount;
private String accountID; private String accountID;
private String takerMessagePubKey; private PublicKey takerMessagePublicKey;
private String signedTakerDepositTxAsHex; private String signedTakerDepositTxAsHex;
private String txScriptSigAsHex; private String txScriptSigAsHex;
private String txConnOutAsHex; private String txConnOutAsHex;
@ -25,7 +26,7 @@ public class RequestOffererPublishDepositTxMessage implements Serializable, Trad
public RequestOffererPublishDepositTxMessage(String tradeId, public RequestOffererPublishDepositTxMessage(String tradeId,
BankAccount bankAccount, BankAccount bankAccount,
String accountID, String accountID,
String takerMessagePubKey, PublicKey takerMessagePublicKey,
String signedTakerDepositTxAsHex, String signedTakerDepositTxAsHex,
String txScriptSigAsHex, String txScriptSigAsHex,
String txConnOutAsHex, String txConnOutAsHex,
@ -39,7 +40,7 @@ public class RequestOffererPublishDepositTxMessage implements Serializable, Trad
this.tradeId = tradeId; this.tradeId = tradeId;
this.bankAccount = bankAccount; this.bankAccount = bankAccount;
this.accountID = accountID; this.accountID = accountID;
this.takerMessagePubKey = takerMessagePubKey; this.takerMessagePublicKey = takerMessagePublicKey;
this.signedTakerDepositTxAsHex = signedTakerDepositTxAsHex; this.signedTakerDepositTxAsHex = signedTakerDepositTxAsHex;
this.txScriptSigAsHex = txScriptSigAsHex; this.txScriptSigAsHex = txScriptSigAsHex;
this.txConnOutAsHex = txConnOutAsHex; this.txConnOutAsHex = txConnOutAsHex;
@ -72,9 +73,9 @@ public class RequestOffererPublishDepositTxMessage implements Serializable, Trad
return accountID; return accountID;
} }
public String getTakerMessagePubKey() public PublicKey getTakerMessagePublicKey()
{ {
return takerMessagePubKey; return takerMessagePublicKey;
} }
public String getSignedTakerDepositTxAsHex() public String getSignedTakerDepositTxAsHex()

View file

@ -8,6 +8,7 @@ import io.bitsquare.msg.MessageFacade;
import io.bitsquare.msg.listeners.OutgoingTradeMessageListener; import io.bitsquare.msg.listeners.OutgoingTradeMessageListener;
import io.bitsquare.trade.protocol.FaultHandler; import io.bitsquare.trade.protocol.FaultHandler;
import io.bitsquare.trade.protocol.ResultHandler; import io.bitsquare.trade.protocol.ResultHandler;
import java.security.PublicKey;
import net.tomp2p.peers.PeerAddress; import net.tomp2p.peers.PeerAddress;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -23,7 +24,7 @@ public class SendSignedTakerDepositTxAsHex
WalletFacade walletFacade, WalletFacade walletFacade,
BankAccount bankAccount, BankAccount bankAccount,
String accountId, String accountId,
String messagePubKeyAsHex, PublicKey messagePublicKey,
String tradeId, String tradeId,
String contractAsJson, String contractAsJson,
String takerSignature, String takerSignature,
@ -36,7 +37,7 @@ public class SendSignedTakerDepositTxAsHex
RequestOffererPublishDepositTxMessage tradeMessage = new RequestOffererPublishDepositTxMessage(tradeId, RequestOffererPublishDepositTxMessage tradeMessage = new RequestOffererPublishDepositTxMessage(tradeId,
bankAccount, bankAccount,
accountId, accountId,
messagePubKeyAsHex, messagePublicKey,
Utils.bytesToHexString(signedTakerDepositTx.bitcoinSerialize()), Utils.bytesToHexString(signedTakerDepositTx.bitcoinSerialize()),
Utils.bytesToHexString(signedTakerDepositTx.getInput(1).getScriptBytes()), Utils.bytesToHexString(signedTakerDepositTx.getInput(1).getScriptBytes()),
Utils.bytesToHexString(signedTakerDepositTx.getInput(1) Utils.bytesToHexString(signedTakerDepositTx.getInput(1)

View file

@ -1,7 +1,10 @@
package io.bitsquare.user; package io.bitsquare.user;
import io.bitsquare.bank.BankAccount; import io.bitsquare.bank.BankAccount;
import io.bitsquare.util.DSAKeyUtil;
import java.io.Serializable; import java.io.Serializable;
import java.security.KeyPair;
import java.security.PublicKey;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import javafx.beans.property.SimpleBooleanProperty; import javafx.beans.property.SimpleBooleanProperty;
@ -11,19 +14,17 @@ public class User implements Serializable
private static final long serialVersionUID = 7409078808248518638L; private static final long serialVersionUID = 7409078808248518638L;
transient private final SimpleBooleanProperty bankAccountChangedProperty = new SimpleBooleanProperty(); transient private final SimpleBooleanProperty bankAccountChangedProperty = new SimpleBooleanProperty();
transient private KeyPair messageKeyPair = DSAKeyUtil.getKeyPair();
private PublicKey messagePublicKey;
private String accountID; private String accountID;
private String messagePubKeyAsHex;
private boolean isOnline; private boolean isOnline;
private List<BankAccount> bankAccounts = new ArrayList<>(); private List<BankAccount> bankAccounts = new ArrayList<>();
private BankAccount currentBankAccount = null; private BankAccount currentBankAccount = null;
public User() public User()
{ {
messagePublicKey = messageKeyPair.getPublic();
} }
@ -36,11 +37,14 @@ public class User implements Serializable
if (savedUser != null) if (savedUser != null)
{ {
accountID = savedUser.getAccountId(); accountID = savedUser.getAccountId();
messagePubKeyAsHex = savedUser.getMessagePubKeyAsHex(); // TODO handled by DSAKeyUtil -> change that storage check is only done here
// messagePublicKey = savedUser.getMessagePublicKey();
isOnline = savedUser.getIsOnline(); isOnline = savedUser.getIsOnline();
bankAccounts = savedUser.getBankAccounts(); bankAccounts = savedUser.getBankAccounts();
currentBankAccount = savedUser.getCurrentBankAccount(); currentBankAccount = savedUser.getCurrentBankAccount();
} }
messagePublicKey = messageKeyPair.getPublic();
} }
public void addBankAccount(BankAccount bankAccount) 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() public String getAccountId()
{ {
return accountID; return accountID;
@ -172,17 +165,27 @@ public class User implements Serializable
return bankAccountChangedProperty; return bankAccountChangedProperty;
} }
@Override @Override
public String toString() public String toString()
{ {
return "User{" + return "User{" +
"bankAccountChangedProperty=" + bankAccountChangedProperty + "bankAccountChangedProperty=" + bankAccountChangedProperty +
", messageKeyPair=" + messageKeyPair +
", messagePublicKey=" + messagePublicKey +
", accountID='" + accountID + '\'' + ", accountID='" + accountID + '\'' +
", messagePubKeyAsHex='" + messagePubKeyAsHex + '\'' +
", isOnline=" + isOnline + ", isOnline=" + isOnline +
", bankAccounts=" + bankAccounts + ", bankAccounts=" + bankAccounts +
", currentBankAccount=" + currentBankAccount + ", currentBankAccount=" + currentBankAccount +
'}'; '}';
} }
public KeyPair getMessageKeyPair()
{
return messageKeyPair;
}
public PublicKey getMessagePublicKey()
{
return messagePublicKey;
}
} }

View file

@ -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<Path> listDir(Path dir) throws IOException
{
List<Path> contents = new LinkedList<>();
try (Stream<Path> list = Files.list(dir))
{
list.forEach(contents::add);
}
return contents;
}
//region Generic Java 8 enhancements
public interface UncheckedRun<T>
{
public T run() throws Throwable;
}
public interface UncheckedRunnable
{
public void run() throws Throwable;
}
public static <T> T unchecked(UncheckedRun<T> 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> T ignoredAndLogged(UncheckedRun<T> runnable)
{
try
{
return runnable.run();
} catch (Throwable t)
{
log.error("Ignoring error", t);
return null;
}
}
@SuppressWarnings("unchecked")
public static <T, E extends Throwable> T checkedGet(Future<T> 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> T stopwatched(String description, UncheckedRun<T> 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
}

View file

@ -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<Thread> 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<Runnable> 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();
}
}
}

View file

@ -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<T> extends ObservableListBase<T> implements ObservableList<T>
{
private List<ObservableList<T>> sources = new ArrayList<>();
private ListChangeListener<T> listener = this::sourceChanged;
@SafeVarargs
public ConcatenatingList(ObservableList<T>... source)
{
super();
for (ObservableList<T> s : source)
{
sources.add(s);
s.addListener(new WeakListChangeListener<T>(listener));
}
if (sources.isEmpty())
throw new IllegalArgumentException();
}
private int calculateOffset(ObservableList<? extends T> source)
{
int cursor = 0;
for (ObservableList<T> 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<T> 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();
}
}

View file

@ -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<E, F> extends TransformationList<E, F>
{
private final Function<F, E> mapper;
private final ArrayList<E> 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<F, E> 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<E> removed = mapped.subList(c.getFrom(), c.getFrom() + c.getRemovedSize());
ArrayList<E> 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();
}
}

View file

@ -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 <T> ListChangeListener<T> addListener(ObservableList<T> observable, ListChangeListener<T> listener, Executor executor)
{
ListChangeListener<T> l = (ListChangeListener.Change<? extends T> c) -> executor.execute(() -> listener.onChanged(c));
observable.addListener(l);
return l;
}
public static <T> SetChangeListener<T> addListener(ObservableSet<T> observable, SetChangeListener<T> listener, Executor executor)
{
SetChangeListener<T> l = (SetChangeListener.Change<? extends T> c) -> executor.execute(() -> listener.onChanged(c));
observable.addListener(l);
return l;
}
public static <K, V> MapChangeListener<K, V> addListener(ObservableMap<K, V> observable, MapChangeListener<K, V> listener, Executor executor)
{
MapChangeListener<K, V> l = (MapChangeListener.Change<? extends K, ? extends V> c) -> executor.execute(() -> listener.onChanged(c));
observable.addListener(l);
return l;
}
}

View file

@ -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 <T> ObservableList<T> mirrorList(ObservableList<T> mirrored, AffinityExecutor runChangesIn)
{
ObservableList<T> result = FXCollections.observableArrayList();
result.setAll(mirrored);
mirrored.addListener(new ListMirror<T>(result, runChangesIn));
return FXCollections.unmodifiableObservableList(result);
}
private static class ListMirror<E> implements ListChangeListener<E>, WeakListener
{
private final WeakReference<ObservableList<E>> targetList;
private final AffinityExecutor runChangesIn;
public ListMirror(ObservableList<E> list, AffinityExecutor runChangesIn)
{
this.targetList = new WeakReference<>(list);
this.runChangesIn = runChangesIn;
}
@Override
public void onChanged(Change<? extends E> change)
{
final List<E> 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<E> list = targetList.get();
return (list == null) ? 0 : list.hashCode();
}
@Override
public boolean equals(Object obj)
{
if (this == obj)
{
return true;
}
final List<E> 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 <T> ObservableSet<T> mirrorSet(ObservableSet<T> mirrored, AffinityExecutor runChangesIn)
{
@SuppressWarnings("unchecked") ObservableSet<T> result = FXCollections.observableSet();
result.addAll(mirrored);
mirrored.addListener(new SetMirror<T>(result, runChangesIn));
return FXCollections.unmodifiableObservableSet(result);
}
private static class SetMirror<E> implements SetChangeListener<E>, WeakListener
{
private final WeakReference<ObservableSet<E>> targetSet;
private final AffinityExecutor runChangesIn;
public SetMirror(ObservableSet<E> set, AffinityExecutor runChangesIn)
{
this.targetSet = new WeakReference<>(set);
this.runChangesIn = runChangesIn;
}
@Override
public void onChanged(final Change<? extends E> change)
{
final ObservableSet<E> 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<E> set = targetSet.get();
return (set == null) ? 0 : set.hashCode();
}
@Override
public boolean equals(Object obj)
{
if (this == obj)
{
return true;
}
final Set<E> 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;
}
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Before After
Before After

View file

@ -22,13 +22,18 @@
<appender-ref ref="FILE"/> <appender-ref ref="FILE"/>
</root> </root>
<logger name="io.bitsquare" level="TRACE"/> <logger name="io.bitsquare" level="DEBUG"/>
<logger name="com.google.bitcoin" level="WARN"/>
<logger name="net.tomp2p" level="WARN"/>
<logger name="io.netty.util" level="WARN"/>
<logger name="io.netty.channel" level="WARN"/>
<logger name="io.netty.buffer" level="WARN"/>
<logger name="com.google.bitcoin" level="INFO"/>
<logger name="net.tomp2p" level="INFO"/>
<!-- <!--
-->
<logger name="com.google.bitcoin.core.Wallet" level="INFO"/> <logger name="com.google.bitcoin.core.Wallet" level="INFO"/>
<logger name="com.google.bitcoin.core.MemoryPool" level="WARN" additivity="false"/> <logger name="com.google.bitcoin.core.MemoryPool" level="WARN" additivity="false"/>
<logger name="com.google.bitcoin.net.discovery.DnsDiscovery" level="WARN" additivity="false"/> <logger name="com.google.bitcoin.net.discovery.DnsDiscovery" level="WARN" additivity="false"/>
@ -41,6 +46,6 @@
<logger name="com.google.bitcoin.core.PeerSocketHandler" level="WARN" additivity="false"/> <logger name="com.google.bitcoin.core.PeerSocketHandler" level="WARN" additivity="false"/>
<logger name="com.google.bitcoin.net.NioClientManager" level="WARN" additivity="false"/> <logger name="com.google.bitcoin.net.NioClientManager" level="WARN" additivity="false"/>
<logger name="com.google.bitcoin.net.ConnectionHandler" level="WARN" additivity="false"/> <logger name="com.google.bitcoin.net.ConnectionHandler" level="WARN" additivity="false"/>
-->
</configuration> </configuration>

View file

@ -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
{
}

View file

@ -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));
}
}

View file

@ -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);
}
}

View file

@ -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));
}
}

View file

@ -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();
}
}

View file

@ -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<FutureBootstrap> futures1 = new ArrayList<FutureBootstrap>();
List<FutureDiscover> futures2 = new ArrayList<FutureDiscover>();
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<PeerAddress> pa1 = new TreeSet<PeerAddress>(PeerMap.createComparator(key));
NavigableSet<PeerAddress> queried = new TreeSet<PeerAddress>(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);
}
}

View file

@ -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<String> a = FXCollections.observableArrayList();
ObservableList<String> b = FXCollections.observableArrayList();
ConcatenatingList<String> 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));
}
}

View file

@ -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<String> inputs;
private ObservableList<String> outputs;
private Queue<ListChangeListener.Change<? extends String>> 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!
}