integration of TomP2P and basic use cases in dummy views tested

This commit is contained in:
Manfred Karrer 2014-05-10 18:31:55 +02:00
parent 9a29004251
commit 6459184ce9
30 changed files with 1134 additions and 102 deletions

View File

@ -21,11 +21,13 @@ git clone --recursive git://github.com/bitsquare/bitsquare
* Offer fee payment with a OP_RETURN tx and fees to miners
* Pay in to MS fund
* Payout from MS fund
* TomP2P as messaging lib integrated and basic use cases in msg screen implemented: orderbook, add order, remove order, find peer, chat with peer
### Next steps:
* Implement messaging with TomP2P for registration, orderbook and payment process
* Arbitrator integration
* Messaging system
* Other trade variants (Buy BTC taker, Sell BTC offerer, Sell BTC offerer)
* Verify registration and fee payments tx and get them from the blockchain
* ...

View File

@ -1,5 +1,4 @@
- arbitration integration
- Messaging!
- settings
low prio:

28
pom.xml
View File

@ -50,7 +50,7 @@
<!-- bitcoin-j repo -->
<repository>
<id>bitcoinj-release</id>
<releases />
<releases/>
<url>http://distribution.bitcoinj.googlecode.com/git/releases</url>
</repository>
<!-- zxing repo -->
@ -59,6 +59,12 @@
<url>http://mvn-adamgent.googlecode.com/svn/maven/release</url>
<name>Adam Gent Maven Repository</name>
</repository>
<repository>
<id>tomp2p.net</id>
<url>http://tomp2p.net/dev/mvn/</url>
</repository>
</repositories>
<!-- TODO Maven build not working yet... -->
@ -106,13 +112,13 @@
<version>4.11</version>
<scope>test</scope>
</dependency>
<!--
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-jdk14</artifactId>
<version>1.7.7</version>
</dependency>
-->
<!--
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-jdk14</artifactId>
<version>1.7.7</version>
</dependency>
-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
@ -170,6 +176,12 @@
<version>1.3</version>
</dependency>
<dependency>
<groupId>net.tomp2p</groupId>
<artifactId>TomP2P</artifactId>
<version>4.4</version>
</dependency>
</dependencies>
<reporting>

View File

@ -9,6 +9,7 @@ import io.bitsquare.btc.WalletFacade;
import io.bitsquare.di.BitSquareModule;
import io.bitsquare.di.GuiceFXMLLoader;
import io.bitsquare.gui.util.Localisation;
import io.bitsquare.msg.MessageFacade;
import io.bitsquare.settings.Settings;
import io.bitsquare.storage.Storage;
import io.bitsquare.user.Arbitrator;
@ -28,9 +29,15 @@ public class BitSquare extends Application
{
private static final Logger log = LoggerFactory.getLogger(BitSquare.class);
private WalletFacade walletFacade;
private MessageFacade messageFacade;
public static void main(String[] args)
{
if (args.length > 0)
WalletFacade.WALLET_PREFIX = args[0];
else
WalletFacade.WALLET_PREFIX = "bitsquare";
launch(args);
}
@ -39,6 +46,7 @@ public class BitSquare extends Application
{
final Injector injector = Guice.createInjector(new BitSquareModule());
walletFacade = injector.getInstance(WalletFacade.class);
messageFacade = injector.getInstance(MessageFacade.class);
// apply stored data
final User user = injector.getInstance(User.class);
@ -51,7 +59,7 @@ public class BitSquare extends Application
settings.updateFromStorage((Settings) storage.read(settings.getClass().getName()));
stage.setTitle("BitSquare");
stage.setTitle("BitSquare (" + WalletFacade.WALLET_PREFIX + ")");
GuiceFXMLLoader.setInjector(injector);
final GuiceFXMLLoader loader = new GuiceFXMLLoader(getClass().getResource("/io/bitsquare/gui/MainView.fxml"), Localisation.getResourceBundle());
@ -75,6 +83,7 @@ public class BitSquare extends Application
public void stop() throws Exception
{
walletFacade.shutDown();
messageFacade.shutDown();
super.stop();
}

View File

@ -36,11 +36,13 @@ public class WalletFacade implements WalletEventListener
public static final String MAIN_NET = "MAIN_NET";
public static final String TEST_NET = "TEST_NET";
// for testing trade process between offerer and taker
//public static final String WALLET_PREFIX = "offerer"; // offerer
public static final String WALLET_PREFIX = "taker"; // offerer
public static String WALLET_PREFIX;
//public static final String WALLET_PREFIX = "bitsquare";
// for testing trade process between offerer and taker
//public static String WALLET_PREFIX = "offerer"; // offerer
//public static String WALLET_PREFIX = "taker"; // offerer
//public static String WALLET_PREFIX = "bitsquare";
private static final Logger log = LoggerFactory.getLogger(WalletFacade.class);
@ -105,14 +107,11 @@ public class WalletFacade implements WalletEventListener
wallet = walletAppKit.wallet();
// Don't make the user wait for confirmations for now, as the intention is they're sending it their own money!
wallet.allowSpendingUnconfirmedTransactions();
//wallet.allowSpendingUnconfirmedTransactions();
walletAppKit.peerGroup().setMaxConnections(20);
wallet.addEventListener(this);
log.info(wallet.toString());
// testTradeProcessDepositTx();
// testTradeProcessPayOutTx();
}

View File

@ -3,4 +3,6 @@ package io.bitsquare.gui;
public interface ChildController
{
void setNavigationController(NavigationController navigationController);
void cleanup();
}

View File

@ -10,6 +10,7 @@ import io.bitsquare.gui.market.MarketController;
import io.bitsquare.gui.setup.SetupController;
import io.bitsquare.gui.util.Icons;
import io.bitsquare.gui.util.Localisation;
import io.bitsquare.msg.MessageFacade;
import io.bitsquare.trade.Direction;
import io.bitsquare.user.User;
import javafx.application.Platform;
@ -39,6 +40,7 @@ public class MainController implements Initializable, NavigationController, Wall
private User user;
private WalletFacade walletFacade;
private MessageFacade messageFacade;
private ChildController childController;
private ToggleGroup toggleGroup;
private ToggleButton prevToggleButton;
@ -62,10 +64,11 @@ public class MainController implements Initializable, NavigationController, Wall
///////////////////////////////////////////////////////////////////////////////////////////
@Inject
public MainController(User user, WalletFacade walletFacade)
public MainController(User user, WalletFacade walletFacade, MessageFacade messageFacade)
{
this.user = user;
this.walletFacade = walletFacade;
this.messageFacade = messageFacade;
}
@ -80,6 +83,8 @@ public class MainController implements Initializable, NavigationController, Wall
networkSyncPane.setSpacing(10);
networkSyncPane.setPrefHeight(20);
messageFacade.init();
walletFacade.addDownloadListener(this);
walletFacade.initWallet();
@ -116,10 +121,8 @@ public class MainController implements Initializable, NavigationController, Wall
return null;
}
if (childController instanceof MarketController)
{
((MarketController) childController).cleanup();
}
if (childController != null)
childController.cleanup();
final GuiceFXMLLoader loader = new GuiceFXMLLoader(getClass().getResource(fxmlView), Localisation.getResourceBundle());
try
@ -184,14 +187,15 @@ public class MainController implements Initializable, NavigationController, Wall
addNavButton(leftNavPane, "Orders", Icons.ORDERS, Icons.ORDERS, NavigationController.ORDERS);
addNavButton(leftNavPane, "History", Icons.HISTORY, Icons.HISTORY, NavigationController.HISTORY);
addNavButton(leftNavPane, "Funds", Icons.FUNDS, Icons.FUNDS, NavigationController.FUNDS);
addNavButton(leftNavPane, "Message", Icons.MSG, Icons.MSG, NavigationController.MSG);
ToggleButton msgButton = addNavButton(leftNavPane, "Message", Icons.MSG, Icons.MSG, NavigationController.MSG);
addBalanceInfo(rightNavPane);
addAccountComboBox(rightNavPane);
addNavButton(rightNavPane, "Settings", Icons.SETTINGS, Icons.SETTINGS, NavigationController.SETTINGS);
sellButton.fire();
//sellButton.fire();
//homeButton.fire();
msgButton.fire();
}
private ToggleButton addNavButton(Pane parent, String title, String iconId, String iconIdActivated, String navTarget)

View File

@ -66,6 +66,12 @@ public class FundsController implements Initializable, ChildController
balanceLabel.setText(BtcFormatter.formatSatoshis(walletFacade.getBalance(), false));
}
@Override
public void cleanup()
{
}
}

View File

@ -24,5 +24,11 @@ public class HistoryController implements Initializable, ChildController
{
this.navigationController = navigationController;
}
@Override
public void cleanup()
{
}
}

View File

@ -28,5 +28,11 @@ public class HomeController implements Initializable, ChildController
this.navigationController = navigationController;
}
@Override
public void cleanup()
{
}
}

View File

@ -93,11 +93,7 @@ public class MarketController implements Initializable, NavigationController, Ch
this.navigationController = navigationController;
}
///////////////////////////////////////////////////////////////////////////////////////////
// Public methods
///////////////////////////////////////////////////////////////////////////////////////////
@Override
public void cleanup()
{
if (orderBookController != null)

View File

@ -114,6 +114,12 @@ public class CreateOfferController implements Initializable, ChildController, Wa
this.navigationController = navigationController;
}
@Override
public void cleanup()
{
}
///////////////////////////////////////////////////////////////////////////////////////////
// Interface implementation: WalletFacade.WalletListener

View File

@ -138,6 +138,15 @@ public class OrderBookController implements Initializable, ChildController
this.navigationController = navigationController;
}
@Override
public void cleanup()
{
orderBookTable.setItems(null);
orderBookTable.getSortOrder().clear();
offerList.comparatorProperty().unbind();
}
public void setDirection(Direction direction)
{
orderBookTable.getSelectionModel().clearSelection();
@ -145,12 +154,6 @@ public class OrderBookController implements Initializable, ChildController
orderBookFilter.setDirection(direction);
}
public void cleanup()
{
orderBookTable.setItems(null);
orderBookTable.getSortOrder().clear();
offerList.comparatorProperty().unbind();
}
private void openTradeTab(OrderBookListItem orderBookListItem)
{

View File

@ -122,6 +122,12 @@ public class TradeController implements Initializable, ChildController, WalletFa
this.navigationController = navigationController;
}
@Override
public void cleanup()
{
}
///////////////////////////////////////////////////////////////////////////////////////////
// Interface implementation: WalletFacade.WalletListener

View File

@ -1,40 +0,0 @@
package io.bitsquare.gui.msg;
import java.util.concurrent.*;
public class MockDelay
{
public String waitForMsg(String expectedMsg)
{
ExecutorService executor = Executors.newSingleThreadExecutor();
Future<String> future = executor.submit(new Task(expectedMsg));
// max timeout 5 sec
try
{
return future.get(5, TimeUnit.SECONDS);
} catch (InterruptedException | TimeoutException | ExecutionException e)
{
e.printStackTrace();
}
executor.shutdownNow();
return null;
}
}
class Task implements Callable<String>
{
private String expectedMsg;
Task(String expectedMsg)
{
this.expectedMsg = expectedMsg;
}
@Override
public String call() throws Exception
{
Thread.sleep(1000); // 1 seconds pause
return expectedMsg;
}
}

View File

@ -1,26 +1,346 @@
package io.bitsquare.gui.msg;
import com.google.inject.Inject;
import io.bitsquare.btc.WalletFacade;
import io.bitsquare.gui.ChildController;
import io.bitsquare.gui.NavigationController;
import io.bitsquare.msg.MessageFacade;
import io.bitsquare.msg.MessageListener;
import javafx.beans.property.ReadOnlyObjectWrapper;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.*;
import javafx.util.Callback;
import net.tomp2p.peers.Number160;
import net.tomp2p.storage.Data;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Map;
import java.util.ResourceBundle;
public class MsgController implements Initializable, ChildController
public class MsgController implements Initializable, ChildController, MessageListener
{
private NavigationController navigationController;
private static final Logger log = LoggerFactory.getLogger(MsgController.class);
private MessageFacade messageFacade;
private String selectedCurrency;
private ObservableList<OfferListItem> offerList = FXCollections.observableArrayList();
private int selectedIndex;
private String myID, otherID;
private boolean pingPending;
@FXML
public ComboBox currencyComboBox;
@FXML
public Button sendButton;
@FXML
public TextArea chatTextArea;
@FXML
public TextField chatInputField, peerIDTextField, currencyTextField, offerDataTextField;
@FXML
public TableView offerTable;
@FXML
public TableColumn<String, OfferListItem> connectToPeerColumn, removeOfferColumn, offerColumn;
///////////////////////////////////////////////////////////////////////////////////////////
// Constructor
///////////////////////////////////////////////////////////////////////////////////////////
@Inject
public MsgController(MessageFacade messageFacade)
{
this.messageFacade = messageFacade;
}
///////////////////////////////////////////////////////////////////////////////////////////
// Interface implementation: Initializable
///////////////////////////////////////////////////////////////////////////////////////////
@Override
public void initialize(URL url, ResourceBundle rb)
{
myID = WalletFacade.WALLET_PREFIX;
otherID = WalletFacade.WALLET_PREFIX.equals("taker") ? "offerer" : "taker";
messageFacade.addMessageListener(this);
peerIDTextField.setText(myID);
currencyTextField.setText("EUR");
offerDataTextField.setText(myID + " serialized offer object");
selectedCurrency = currencyTextField.getText();
currencyComboBox.setItems(FXCollections.observableArrayList(new ArrayList<>(Arrays.asList("EUR", "USD", "CHF"))));
currencyComboBox.getSelectionModel().select(0);
setupConnectToPeerOfferColumn();
setupRemoveOfferColumn();
offerTable.setItems(offerList);
offerTable.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
}
///////////////////////////////////////////////////////////////////////////////////////////
// Interface implementation: MessageListener
///////////////////////////////////////////////////////////////////////////////////////////
@Override
public void onMessage(Object message)
{
sendButton.setDisable(!messageFacade.isOtherPeerDefined());
if (message instanceof String)
chatTextArea.appendText("\n" + otherID + ": " + message);
}
@Override
public void onPing()
{
sendChatMsg(MessageFacade.PONG);
}
@Override
public void onOfferPublished(boolean success)
{
if (success)
getOffers();
else
log.warn("onOfferPublished returned false");
}
@Override
public void onOffersReceived(Map<Number160, Data> dataMap, boolean success)
{
if (success && dataMap != null)
{
offerList.clear();
for (Data offerData : dataMap.values())
{
try
{
Object offerDataObject = offerData.getObject();
if (offerDataObject instanceof OfferListItem && offerDataObject != null)
offerList.add((OfferListItem) offerDataObject);
} catch (ClassNotFoundException | IOException e)
{
e.printStackTrace();
}
}
}
else
{
offerList.clear();
}
}
@Override
public void onOfferRemoved(boolean success)
{
if (success)
getOffers();
else
log.warn("onOfferRemoved failed");
}
@Override
public void onResponseFromSend(Object response)
{
String msg = (response instanceof String) ? (String) response : null;
if (msg != null)
{
chatTextArea.appendText("\n" + otherID + ": " + msg);
offerTable.getSelectionModel().select(selectedIndex);
}
}
@Override
public void onSendFailed()
{
offerTable.getSelectionModel().clearSelection();
}
@Override
public void onPeerFound()
{
sendButton.setDisable(!messageFacade.isOtherPeerDefined());
if (pingPending)
sendChatMsg(MessageFacade.PING);
}
///////////////////////////////////////////////////////////////////////////////////////////
// Interface implementation: ChildController
///////////////////////////////////////////////////////////////////////////////////////////
@Override
public void setNavigationController(NavigationController navigationController)
{
this.navigationController = navigationController;
}
@Override
public void cleanup()
{
messageFacade.removeMessageListener(this);
}
///////////////////////////////////////////////////////////////////////////////////////////
// GUI Event handlers
///////////////////////////////////////////////////////////////////////////////////////////
@FXML
public void publishOffer(ActionEvent actionEvent)
{
OfferListItem offerListItem = new OfferListItem(offerDataTextField.getText(), messageFacade.getPubKeyAsHex(), currencyTextField.getText());
try
{
messageFacade.publishOffer(currencyTextField.getText(), offerListItem);
} catch (IOException e)
{
e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates.
}
}
@FXML
public void selectCurrency(ActionEvent actionEvent)
{
selectedCurrency = currencyComboBox.getSelectionModel().getSelectedItem().toString();
getOffers();
}
@FXML
public void sendChatMsg(ActionEvent actionEvent)
{
sendChatMsg(chatInputField.getText());
}
///////////////////////////////////////////////////////////////////////////////////////////
// Private Methods
///////////////////////////////////////////////////////////////////////////////////////////
private void inviteForChat(OfferListItem item, int index)
{
selectedIndex = index;
messageFacade.findPeer(item.getPubKey());
pingPending = true;
}
private void sendChatMsg(String msg)
{
messageFacade.sendMessage(msg);
chatTextArea.appendText("\n" + myID + ": " + msg);
chatInputField.setText("");
}
private void getOffers()
{
messageFacade.getOffers(selectedCurrency);
}
private void removeOffer(OfferListItem offer)
{
try
{
messageFacade.removeOffer(currencyTextField.getText(), offer);
} catch (IOException e)
{
e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates.
}
}
///////////////////////////////////////////////////////////////////////////////////////////
// Columns
///////////////////////////////////////////////////////////////////////////////////////////
private void setupRemoveOfferColumn()
{
removeOfferColumn.setCellValueFactory((offer) -> new ReadOnlyObjectWrapper(offer.getValue()));
removeOfferColumn.setCellFactory(new Callback<TableColumn<String, OfferListItem>, TableCell<String, OfferListItem>>()
{
@Override
public TableCell<String, OfferListItem> call(TableColumn<String, OfferListItem> directionColumn)
{
return new TableCell<String, OfferListItem>()
{
final Button button = new Button();
{
button.setMinWidth(70);
}
@Override
public void updateItem(final OfferListItem item, boolean empty)
{
super.updateItem(item, empty);
if (item != null)
{
button.setText("Remove");
setGraphic(button);
button.setOnAction(event -> removeOffer(item));
}
else
{
setGraphic(null);
}
}
};
}
});
}
private void setupConnectToPeerOfferColumn()
{
connectToPeerColumn.setCellValueFactory((offer) -> new ReadOnlyObjectWrapper(offer.getValue()));
connectToPeerColumn.setCellFactory(new Callback<TableColumn<String, OfferListItem>, TableCell<String, OfferListItem>>()
{
@Override
public TableCell<String, OfferListItem> call(TableColumn<String, OfferListItem> directionColumn)
{
return new TableCell<String, OfferListItem>()
{
final Button button = new Button();
{
button.setMinWidth(70);
}
@Override
public void updateItem(OfferListItem item, boolean empty)
{
super.updateItem(item, empty);
if (item != null)
{
button.setText("Chat");
setGraphic(button);
button.setOnAction(event -> inviteForChat(item, getIndex()));
}
else
{
setGraphic(null);
}
}
};
}
});
}
}

View File

@ -1,6 +1,80 @@
<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.Pane?>
<Pane fx:controller="io.bitsquare.gui.msg.MsgController"
xmlns:fx="http://javafx.com/fxml">
<Label text="Msg"/>
</Pane>
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.*?>
<?import javafx.scene.control.cell.PropertyValueFactory?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.text.Font?>
<AnchorPane fx:controller="io.bitsquare.gui.msg.MsgController" AnchorPane.bottomAnchor="0" AnchorPane.leftAnchor="0" AnchorPane.rightAnchor="0"
AnchorPane.topAnchor="0" xmlns="http://javafx.com/javafx/8"
xmlns:fx="http://javafx.com/fxml/1">
<children>
<Label text="Offer list" AnchorPane.leftAnchor="10" AnchorPane.topAnchor="15.0">
<font>
<Font name="System Bold" size="18.0"/>
</font>
</Label>
<ComboBox fx:id="currencyComboBox" onAction="#selectCurrency" prefWidth="100" AnchorPane.rightAnchor="405" AnchorPane.topAnchor="10.0"/>
<TableView fx:id="offerTable" prefHeight="200.0" AnchorPane.leftAnchor="10.0" AnchorPane.rightAnchor="405"
AnchorPane.topAnchor="40.0">
<columns>
<TableColumn text="Offer" fx:id="offerColumn" minWidth="120">
<cellValueFactory>
<PropertyValueFactory property="offer"/>
</cellValueFactory>
</TableColumn>
<TableColumn text="Peer PubKey" fx:id="pubKeyColumn" minWidth="80" prefWidth="80">
<cellValueFactory>
<PropertyValueFactory property="pubKey"/>
</cellValueFactory>
</TableColumn>
<TableColumn text="" fx:id="removeOfferColumn" minWidth="80" sortable="false"/>
<TableColumn text="" fx:id="connectToPeerColumn" minWidth="80" sortable="false"/>
</columns>
</TableView>
<Label text="Chat with peer" AnchorPane.bottomAnchor="210.0" AnchorPane.leftAnchor="10.0">
<font>
<Font name="System Bold" size="18.0"/>
</font>
</Label>
<TextArea fx:id="chatTextArea" editable="false" prefHeight="160.0" AnchorPane.bottomAnchor="45.0" AnchorPane.leftAnchor="10.0"
AnchorPane.rightAnchor="10.0"/>
<TextField fx:id="chatInputField" AnchorPane.bottomAnchor="10.0" AnchorPane.leftAnchor="10.0" AnchorPane.rightAnchor="120.0"/>
<Button fx:id="sendButton" onAction="#sendChatMsg" disable="true" defaultButton="true" prefWidth="100.0" text="Send" AnchorPane.bottomAnchor="10.0"
AnchorPane.rightAnchor="10"/>
<GridPane hgap="5.0" prefWidth="385.0" vgap="5.0" AnchorPane.rightAnchor="10.0" AnchorPane.topAnchor="40.0">
<children>
<Label layoutX="418.0" text="Add new Offer" AnchorPane.topAnchor="10.0" GridPane.columnSpan="2" GridPane.halignment="LEFT"
GridPane.rowIndex="0">
<font>
<Font name="System Bold" size="18.0"/>
</font>
</Label>
<Label text="Peer ID:" GridPane.rowIndex="1"/>
<Label text="Currency:" GridPane.rowIndex="2"/>
<Label text="Offer data:" GridPane.rowIndex="3"/>
<TextField fx:id="peerIDTextField" GridPane.columnIndex="1" GridPane.rowIndex="1"/>
<TextField fx:id="currencyTextField" GridPane.columnIndex="1" GridPane.rowIndex="2"/>
<TextField fx:id="offerDataTextField" GridPane.columnIndex="1" GridPane.rowIndex="3"/>
<Button onAction="#publishOffer" text="Add Offer" GridPane.columnIndex="1" GridPane.rowIndex="4"/>
</children>
<columnConstraints>
<ColumnConstraints halignment="RIGHT" hgrow="SOMETIMES" minWidth="10.0"/>
<ColumnConstraints hgrow="ALWAYS" minWidth="10.0"/>
</columnConstraints>
<rowConstraints>
<RowConstraints minHeight="10.0" vgrow="SOMETIMES"/>
<RowConstraints minHeight="10.0" vgrow="SOMETIMES"/>
<RowConstraints minHeight="10.0" vgrow="SOMETIMES"/>
<RowConstraints minHeight="10.0" vgrow="SOMETIMES"/>
<RowConstraints minHeight="10.0" vgrow="ALWAYS"/>
</rowConstraints>
</GridPane>
</children>
</AnchorPane>

View File

@ -0,0 +1,40 @@
package io.bitsquare.gui.msg;
import java.io.Serializable;
/**
* Wrapper for observable properties used by orderbook table view
*/
public class OfferListItem implements Serializable
{
private static final long serialVersionUID = 7914481258209700131L;
private String _offer;
private String pubKey;
private String currency;
public OfferListItem(String offer, String pubKey, String currency)
{
_offer = offer;
this.pubKey = pubKey;
this.currency = currency;
}
public String getOffer()
{
return _offer;
}
public String getPubKey()
{
return pubKey;
}
public String getCurrency()
{
return currency;
}
}

View File

@ -26,5 +26,11 @@ public class OrdersController implements Initializable, ChildController
this.navigationController = navigationController;
}
@Override
public void cleanup()
{
}
}

View File

@ -39,5 +39,11 @@ public class SettingsController implements Initializable, ChildController
this.navigationController = navigationController;
}
@Override
public void cleanup()
{
}
}

View File

@ -18,6 +18,7 @@ import io.bitsquare.gui.util.FormBuilder;
import io.bitsquare.gui.util.Localisation;
import io.bitsquare.gui.util.Popups;
import io.bitsquare.gui.util.Verification;
import io.bitsquare.msg.MessageFacade;
import io.bitsquare.storage.Storage;
import io.bitsquare.user.User;
import io.bitsquare.util.Utils;
@ -43,6 +44,7 @@ public class SetupController implements Initializable, ChildController, WalletFa
private final User user;
private final WalletFacade walletFacade;
private MessageFacade messageFacade;
private final Storage storage;
private final List<ProcessStepItem> processStepItems = new ArrayList<>();
private NavigationController navigationController;
@ -71,10 +73,11 @@ public class SetupController implements Initializable, ChildController, WalletFa
///////////////////////////////////////////////////////////////////////////////////////////
@Inject
public SetupController(User user, WalletFacade walletFacade, Storage storage)
public SetupController(User user, WalletFacade walletFacade, MessageFacade messageFacade, Storage storage)
{
this.user = user;
this.walletFacade = walletFacade;
this.messageFacade = messageFacade;
this.storage = storage;
}
@ -118,6 +121,11 @@ public class SetupController implements Initializable, ChildController, WalletFa
this.navigationController = navigationController;
}
@Override
public void cleanup()
{
}
///////////////////////////////////////////////////////////////////////////////////////////
// Interface implementation: WalletFacade.WalletListener
@ -305,6 +313,7 @@ public class SetupController implements Initializable, ChildController, WalletFa
walletFacade.sendRegistrationTx(user.getStringifiedBankAccounts());
user.setAccountID(walletFacade.getAccountRegistrationAddress().toString());
user.setMessageID(walletFacade.getAccountRegistrationPubKey());
//user.setMessageID(messageFacade.getPubKey());
storage.write(user.getClass().getName(), user);
processStepBar.next();

View File

@ -0,0 +1,24 @@
package io.bitsquare.msg;
import net.tomp2p.p2p.Peer;
import net.tomp2p.p2p.PeerMaker;
import net.tomp2p.peers.Number160;
public class BootstrapMasterPeer
{
private static Peer masterPeer = null;
public static Number160 ID = Number160.createHash(1);
public static void main(String[] args) throws Exception
{
INSTANCE(5000);
}
public static Peer INSTANCE(int port) throws Exception
{
if (masterPeer == null)
masterPeer = new PeerMaker(ID).setPorts(port).makeAndListen();
return masterPeer;
}
}

View File

@ -1,30 +1,398 @@
package io.bitsquare.msg;
import com.google.bitcoin.core.Utils;
import com.google.inject.Inject;
import io.bitsquare.btc.WalletFacade;
import javafx.application.Platform;
import net.tomp2p.connection.Bindings;
import net.tomp2p.connection.PeerConnection;
import net.tomp2p.futures.*;
import net.tomp2p.p2p.Peer;
import net.tomp2p.p2p.PeerMaker;
import net.tomp2p.peers.Number160;
import net.tomp2p.peers.PeerAddress;
import net.tomp2p.rpc.ObjectDataReply;
import net.tomp2p.storage.Data;
import net.tomp2p.storage.StorageDisk;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.IOException;
import java.security.KeyPair;
import java.security.PublicKey;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Random;
/**
* That facade delivers messaging functionality from an external library -> to be defined...
* The external library codebase must not be used outside that facade.
* That facade delivers messaging functionality from the TomP2P library
* The TomP2P library codebase shall not be used outside that facade.
* That way a change of the library will only affect that class.
*/
public class MessageFacade
{
private static final Logger log = LoggerFactory.getLogger(MessageFacade.class);
public void broadcast(Message message)
public static final String PING = "ping";
public static final String PONG = "pong";
private static final int MASTER_PEER_PORT = 5000;
private static String MASTER_PEER_IP = "127.0.0.1";
private Peer myPeerInstance;
private int port;
private KeyPair keyPair;
private Peer masterPeer;
private PeerAddress otherPeerAddress;
private PeerConnection peerConnection;
private List<MessageListener> messageListeners = new ArrayList<>();
///////////////////////////////////////////////////////////////////////////////////////////
// Constructor
///////////////////////////////////////////////////////////////////////////////////////////
@Inject
public MessageFacade()
{
log.info(message.toString());
try
{
masterPeer = BootstrapMasterPeer.INSTANCE(MASTER_PEER_PORT);
} catch (Exception e)
{
if (masterPeer != null)
masterPeer.shutdown();
log.info("masterPeer already instantiated by another app. " + e.getMessage());
}
}
public void send(Message message, String receiverPubKey)
///////////////////////////////////////////////////////////////////////////////////////////
// Public Methods
///////////////////////////////////////////////////////////////////////////////////////////
public void init()
{
log.info(message.toString() + "/" + receiverPubKey);
String keyName = WalletFacade.WALLET_PREFIX;
port = Bindings.MAX_PORT - Math.abs(new Random().nextInt()) % (Bindings.MAX_PORT - Bindings.MIN_DYN_PORT);
if (WalletFacade.WALLET_PREFIX.equals("taker"))
port = 4501;
else if (WalletFacade.WALLET_PREFIX.equals("offerer"))
port = 4500;
try
{
createMyPeerInstance(keyName, port);
setupStorage();
saveMyAddressToDHT();
setupReplyHandler();
} catch (IOException e)
{
shutDown();
log.error("Error at setup peer" + e.getMessage());
}
//log.info("myPeerInstance knows: " + myPeerInstance.getPeerBean().getPeerMap().getAll());
}
public void registerListener(String listenerPubKey)
public void shutDown()
{
log.info(listenerPubKey);
if (peerConnection != null)
peerConnection.close();
if (myPeerInstance != null)
myPeerInstance.shutdown();
if (masterPeer != null)
masterPeer.shutdown();
}
///////////////////////////////////////////////////////////////////////////////////////////
// Publish offer
///////////////////////////////////////////////////////////////////////////////////////////
//TODO use Offer and do proper serialisation here
public void publishOffer(String currency, Object offerObject) throws IOException
{
Number160 locationKey = Number160.createHash(currency);
Data offerData = new Data(offerObject);
offerData.setTTLSeconds(5);
FutureDHT putFuture = myPeerInstance.add(locationKey).setData(offerData).start();
putFuture.addListener(new BaseFutureAdapter<BaseFuture>()
{
@Override
public void operationComplete(BaseFuture future) throws Exception
{
Platform.runLater(() -> onOfferPublished(future.isSuccess()));
}
});
}
private void onOfferPublished(boolean success)
{
for (MessageListener messageListener : messageListeners)
messageListener.onOfferPublished(success);
}
///////////////////////////////////////////////////////////////////////////////////////////
// Get offers
///////////////////////////////////////////////////////////////////////////////////////////
public void getOffers(String currency)
{
Number160 locationKey = Number160.createHash(currency);
final FutureDHT getOffersFuture = myPeerInstance.get(locationKey).setAll().start();
getOffersFuture.addListener(new BaseFutureAdapter<BaseFuture>()
{
@Override
public void operationComplete(BaseFuture future) throws Exception
{
final Map<Number160, Data> dataMap = getOffersFuture.getDataMap();
Platform.runLater(() -> onOffersReceived(dataMap, future.isSuccess()));
}
});
}
private void onOffersReceived(Map<Number160, Data> dataMap, boolean success)
{
for (MessageListener messageListener : messageListeners)
messageListener.onOffersReceived(dataMap, success);
}
///////////////////////////////////////////////////////////////////////////////////////////
// Remove offer
///////////////////////////////////////////////////////////////////////////////////////////
public void removeOffer(String currency, Object offerObject) throws IOException
{
Data offerData = new Data(offerObject);
Number160 locationKey = Number160.createHash(currency);
Number160 contentKey = offerData.getHash();
FutureDHT putFuture = myPeerInstance.remove(locationKey).setContentKey(contentKey).start();
putFuture.addListener(new BaseFutureAdapter<BaseFuture>()
{
@Override
public void operationComplete(BaseFuture future) throws Exception
{
Platform.runLater(() -> onOfferRemoved(future.isSuccess()));
}
});
}
private void onOfferRemoved(boolean success)
{
for (MessageListener messageListener : messageListeners)
messageListener.onOfferRemoved(success);
}
///////////////////////////////////////////////////////////////////////////////////////////
// Send message
///////////////////////////////////////////////////////////////////////////////////////////
public boolean sendMessage(String message)
{
boolean result = false;
if (otherPeerAddress != null)
{
if (peerConnection != null)
peerConnection.close();
peerConnection = myPeerInstance.createPeerConnection(otherPeerAddress, 20);
if (!peerConnection.isClosed())
{
FutureResponse sendFuture = myPeerInstance.sendDirect(peerConnection).setObject(message).start();
sendFuture.addListener(new BaseFutureAdapter<BaseFuture>()
{
@Override
public void operationComplete(BaseFuture baseFuture) throws Exception
{
if (sendFuture.isSuccess())
{
final Object object = sendFuture.getObject();
Platform.runLater(() -> onResponseFromSend(object));
}
else
{
Platform.runLater(() -> onSendFailed());
}
}
}
);
result = true;
}
}
return result;
}
private void onResponseFromSend(Object response)
{
for (MessageListener messageListener : messageListeners)
messageListener.onResponseFromSend(response);
}
private void onSendFailed()
{
for (MessageListener messageListener : messageListeners)
messageListener.onSendFailed();
}
///////////////////////////////////////////////////////////////////////////////////////////
// Find peer
///////////////////////////////////////////////////////////////////////////////////////////
public void findPeer(String pubKeyAsHex)
{
final FutureDHT getPeerAddressFuture = myPeerInstance.get(getPubKeyHash(pubKeyAsHex)).start();
getPeerAddressFuture.addListener(new BaseFutureAdapter<BaseFuture>()
{
@Override
public void operationComplete(BaseFuture baseFuture) throws Exception
{
final PeerAddress peerAddress = (PeerAddress) getPeerAddressFuture.getData().getObject();
Platform.runLater(() -> onPeerFound(peerAddress));
}
});
}
private void onPeerFound(PeerAddress peerAddress)
{
if (!peerAddress.equals(myPeerInstance.getPeerAddress()))
{
otherPeerAddress = peerAddress;
for (MessageListener messageListener : messageListeners)
messageListener.onPeerFound();
}
}
///////////////////////////////////////////////////////////////////////////////////////////
// Misc
///////////////////////////////////////////////////////////////////////////////////////////
public boolean isOtherPeerDefined()
{
return otherPeerAddress != null;
}
public String getPubKeyAsHex()
{
return Utils.bytesToHexString(keyPair.getPublic().getEncoded());
}
public PublicKey getPubKey()
{
return keyPair.getPublic();
}
///////////////////////////////////////////////////////////////////////////////////////////
// Event Listeners
///////////////////////////////////////////////////////////////////////////////////////////
public void addMessageListener(MessageListener listener)
{
messageListeners.add(listener);
}
public void removeMessageListener(MessageListener listener)
{
messageListeners.remove(listener);
}
///////////////////////////////////////////////////////////////////////////////////////////
// Private Methods
///////////////////////////////////////////////////////////////////////////////////////////
private void createMyPeerInstance(String keyName, int port) throws IOException
{
keyPair = MsgKeyUtil.getKeyPair(keyName);
myPeerInstance = new PeerMaker(keyPair).setPorts(port).makeAndListen();
//TODO use list of multiple master bootstrap peers
/*PeerAddress bootstrapServerPeerAddress = new PeerAddress(BootstrapMasterPeer.ID, new InetSocketAddress(InetAddress.getByName(MASTER_PEER_IP), port));
FutureBootstrap futureBootstrap = myPeerInstance.bootstrap().setPeerAddress(bootstrapServerPeerAddress).start();
*/
FutureBootstrap futureBootstrap = myPeerInstance.bootstrap().setBroadcast().setPorts(MASTER_PEER_PORT).start();
if (futureBootstrap != null)
{
futureBootstrap.awaitUninterruptibly();
if (futureBootstrap.getBootstrapTo() != null)
{
PeerAddress peerAddress = futureBootstrap.getBootstrapTo().iterator().next();
myPeerInstance.discover().setPeerAddress(peerAddress).start().awaitUninterruptibly();
}
}
}
private void setupStorage() throws IOException
{
//TODO WalletFacade.WALLET_PREFIX just temp...
String dirPath = io.bitsquare.util.Utils.getRootDir() + "tomP2P_" + WalletFacade.WALLET_PREFIX;
File dirFile = new File(dirPath);
boolean success = true;
if (!dirFile.exists())
success = dirFile.mkdir();
if (success)
myPeerInstance.getPeerBean().setStorage(new StorageDisk(dirPath));
else
log.warn("Unable to create directory " + dirPath);
}
private void saveMyAddressToDHT() throws IOException
{
myPeerInstance.put(getPubKeyHash(getPubKeyAsHex())).setData(new Data(myPeerInstance.getPeerAddress())).start();
}
///////////////////////////////////////////////////////////////////////////////////////////
// Incoming message handler
///////////////////////////////////////////////////////////////////////////////////////////
private void setupReplyHandler()
{
myPeerInstance.setObjectDataReply(new ObjectDataReply()
{
@Override
public Object reply(PeerAddress sender, Object request) throws Exception
{
String reply = null;
if (!sender.equals(myPeerInstance.getPeerAddress()))
{
otherPeerAddress = sender;
Platform.runLater(() -> onMessage(request));
if (request.equals(PING))
{
Platform.runLater(() -> onPing());
}
}
return reply;
}
});
}
private void onMessage(Object message)
{
for (MessageListener messageListener : messageListeners)
messageListener.onMessage(message);
}
private void onPing()
{
for (MessageListener messageListener : messageListeners)
messageListener.onPing();
}
///////////////////////////////////////////////////////////////////////////////////////////
// Utils
///////////////////////////////////////////////////////////////////////////////////////////
private Number160 getPubKeyHash(String pubKeyAsHex)
{
return net.tomp2p.utils.Utils.makeSHAHash(pubKeyAsHex);
}
}

View File

@ -0,0 +1,26 @@
package io.bitsquare.msg;
import net.tomp2p.peers.Number160;
import net.tomp2p.storage.Data;
import java.util.EventListener;
import java.util.Map;
public interface MessageListener extends EventListener
{
void onMessage(Object message);
void onPing();
void onOfferPublished(boolean success);
void onSendFailed();
void onResponseFromSend(Object response);
void onPeerFound();
void onOffersReceived(Map<Number160, Data> dataMap, boolean success);
void onOfferRemoved(boolean success);
}

View File

@ -0,0 +1,131 @@
package io.bitsquare.msg;
import io.bitsquare.util.Utils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.security.*;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
public class MsgKeyUtil
{
private static final Logger log = LoggerFactory.getLogger(MsgKeyUtil.class);
private static final String baseDir = Utils.getRootDir();
public static KeyPair getKeyPair()
{
return getKeyPair("public.key", "private.key");
}
public static KeyPair getKeyPair(String keyName)
{
return getKeyPair("public_" + keyName + ".key", "private_" + keyName + ".key");
}
public static KeyPair getKeyPair(String pubKeyPath, String privKeyPath)
{
try
{
KeyPair loadedKeyPair = loadKeyPair(pubKeyPath, privKeyPath, "DSA");
//System.out.println("Loaded Key Pair");
return loadedKeyPair;
} catch (Exception e)
{
try
{
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("DSA");
keyGen.initialize(1024);
KeyPair generatedKeyPair = keyGen.genKeyPair();
// System.out.println("Generated Key Pair");
dumpKeyPair(generatedKeyPair);
saveKeyPair(pubKeyPath, privKeyPath, generatedKeyPair);
return generatedKeyPair;
} catch (Exception e2)
{
e2.printStackTrace();
return null;
}
}
}
private static void dumpKeyPair(KeyPair keyPair)
{
PublicKey pub = keyPair.getPublic();
// System.out.println("Public Key: " + getHexString(pub.getEncoded()));
PrivateKey priv = keyPair.getPrivate();
// System.out.println("Private Key: " + getHexString(priv.getEncoded()));
}
private static String getHexString(byte[] b)
{
String result = "";
for (int i = 0; i < b.length; i++)
{
result += Integer.toString((b[i] & 0xff) + 0x100, 16).substring(1);
}
return result;
}
public static void saveKeyPair(String pubKeyPath, String privKeyPath, KeyPair keyPair) throws IOException
{
PrivateKey privateKey = keyPair.getPrivate();
PublicKey publicKey = keyPair.getPublic();
// Store Public Key.
X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(
publicKey.getEncoded());
FileOutputStream fos = new FileOutputStream(baseDir + pubKeyPath);
fos.write(x509EncodedKeySpec.getEncoded());
fos.close();
// Store Private Key.
PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(
privateKey.getEncoded());
fos = new FileOutputStream(baseDir + privKeyPath);
fos.write(pkcs8EncodedKeySpec.getEncoded());
fos.close();
}
public static KeyPair loadKeyPair(String pubKeyPath, String privKeyPath, String algorithm)
throws IOException, NoSuchAlgorithmException,
InvalidKeySpecException
{
// Read Public Key.
File filePublicKey = new File(baseDir + pubKeyPath);
FileInputStream fis = new FileInputStream(baseDir + pubKeyPath);
byte[] encodedPublicKey = new byte[(int) filePublicKey.length()];
fis.read(encodedPublicKey);
fis.close();
// Read Private Key.
File filePrivateKey = new File(baseDir + privKeyPath);
fis = new FileInputStream(baseDir + privKeyPath);
byte[] encodedPrivateKey = new byte[(int) filePrivateKey.length()];
fis.read(encodedPrivateKey);
fis.close();
// Generate KeyPair.
KeyFactory keyFactory = KeyFactory.getInstance(algorithm);
X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(
encodedPublicKey);
PublicKey publicKey = keyFactory.generatePublic(publicKeySpec);
PKCS8EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec(
encodedPrivateKey);
PrivateKey privateKey = keyFactory.generatePrivate(privateKeySpec);
return new KeyPair(publicKey, privateKey);
}
}

View File

@ -1,5 +1,6 @@
package io.bitsquare.storage;
import io.bitsquare.util.Utils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -26,7 +27,7 @@ public class Storage
public Storage()
{
storageFile = Storage.class.getProtectionDomain().getCodeSource().getLocation().getFile() + "/" + preferencesFileName;
storageFile = Utils.getRootDir() + preferencesFileName;
dict = readDataVO();
if (dict == null)

View File

@ -11,15 +11,18 @@ import java.util.UUID;
public class Offer
{
// key attributes for lookup
private Direction direction;
private Currency currency;
private UUID uid;
private double price;
private BigInteger amount;
private BigInteger minAmount;
private String accountID;
private String messageID;
private Direction direction;
private BankAccountType.BankAccountTypeEnum bankAccountTypeEnum;
private Currency currency;
private Locale bankAccountCountryLocale;
private double collateral;

View File

@ -9,7 +9,6 @@ import io.bitsquare.btc.Fees;
import io.bitsquare.btc.KeyPair;
import io.bitsquare.btc.WalletFacade;
import io.bitsquare.crypto.CryptoFacade;
import io.bitsquare.msg.Message;
import io.bitsquare.msg.MessageFacade;
import io.bitsquare.settings.Settings;
import io.bitsquare.user.User;
@ -70,7 +69,7 @@ public class Trading
offers.put(offer.getUid().toString(), offer);
walletFacade.payFee(Fees.OFFER_CREATION_FEE, callback);
messageFacade.broadcast(new Message(Message.BROADCAST_NEW_OFFER, offer));
// messageFacade.broadcast(new Message(Message.BROADCAST_NEW_OFFER, offer));
}
public Trade createTrade(Offer offer)

View File

@ -21,6 +21,12 @@ public class Utils
{
private static final Logger log = LoggerFactory.getLogger(Utils.class);
public static String getRootDir()
{
return Utils.class.getProtectionDomain().getCodeSource().getLocation().getFile() + "/";
}
public static String objectToJson(Object object)
{
Gson gson = new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.UPPER_CAMEL_CASE).create();

View File

@ -13,14 +13,17 @@
<appender-ref ref="CONSOLE_APPENDER"/>
</root>
<logger name="io.bitsquare" level="DEBUG"/>
<logger name="io.bitsquare" level="INFO"/>
<logger name="com.google.bitcoin" level="ERROR"/>
<logger name="net.tomp2p" level="INFO"/>
<logger name="com.google.bitcoin" level="INFO"/>
<logger name="com.google.bitcoin.core.Peer" level="ERROR" additivity="false"/>
<logger name="com.google.bitcoin.core.PeerGroup" level="ERROR" additivity="false"/>
<logger name="com.google.bitcoin.net.NioClientManager" level="ERROR" additivity="false"/>
<logger name="com.google.bitcoin.net.ConnectionHandler" level="ERROR" additivity="false"/>
<logger name="com.google.bitcoin.net.discovery.DnsDiscovery" level="OFF" additivity="false"/>
<!--
-->