From 5e9f97953f5e7da7a668ca6a66973e16343c419c Mon Sep 17 00:00:00 2001 From: Steve Myers Date: Sat, 4 Oct 2014 19:19:18 -0700 Subject: [PATCH] Added built in dht seed node --- .gitignore | 2 +- build.gradle | 1 + src/main/java/io/bitsquare/BitSquare.java | 7 +- .../java/io/bitsquare/di/BitSquareModule.java | 23 +++++ .../java/io/bitsquare/gui/AWTSystemTray.java | 19 +++- .../java/io/bitsquare/gui/main/MainModel.java | 36 +++++--- .../java/io/bitsquare/msg/DHTSeedService.java | 30 +++++++ .../io/bitsquare/msg/actor/DHTManager.java | 69 ++++++++++++++ .../msg/actor/command/InitializePeer.java | 35 ++++++++ .../msg/actor/event/PeerInitialized.java | 21 +++++ .../java/io/bitsquare/util/ActorService.java | 89 +++++++++++++++++++ .../io/bitsquare/util/MessageHandler.java | 24 +++++ src/main/resources/application.conf | 17 ++++ 13 files changed, 357 insertions(+), 16 deletions(-) create mode 100644 src/main/java/io/bitsquare/msg/DHTSeedService.java create mode 100644 src/main/java/io/bitsquare/msg/actor/DHTManager.java create mode 100644 src/main/java/io/bitsquare/msg/actor/command/InitializePeer.java create mode 100644 src/main/java/io/bitsquare/msg/actor/event/PeerInitialized.java create mode 100644 src/main/java/io/bitsquare/util/ActorService.java create mode 100644 src/main/java/io/bitsquare/util/MessageHandler.java create mode 100644 src/main/resources/application.conf diff --git a/.gitignore b/.gitignore index 88b552d11f..f8779876f6 100644 --- a/.gitignore +++ b/.gitignore @@ -2,7 +2,7 @@ /log /bin /out -.idea +.idea/* !.idea/copyright/Bitsquare_Affero_GPLv3.xml !.idea/copyright/profiles_settings.xml !.idea/codeStyleSettings.xml diff --git a/build.gradle b/build.gradle index aa4421143e..21a9af1e84 100644 --- a/build.gradle +++ b/build.gradle @@ -30,6 +30,7 @@ repositories { dependencies { compile 'org.bitcoinj:bitcoinj-core:0.12' compile 'net.tomp2p:tomp2p-all:5.0-Alpha24.805623c-SNAPSHOT' + compile 'com.typesafe.akka:akka-actor_2.10:2.3.4' compile 'org.slf4j:slf4j-api:1.7.7' compile 'ch.qos.logback:logback-core:1.1.2' compile 'ch.qos.logback:logback-classic:1.1.2' diff --git a/src/main/java/io/bitsquare/BitSquare.java b/src/main/java/io/bitsquare/BitSquare.java index c09c13c12e..9be2ee1ec8 100644 --- a/src/main/java/io/bitsquare/BitSquare.java +++ b/src/main/java/io/bitsquare/BitSquare.java @@ -48,6 +48,7 @@ import javafx.stage.Stage; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import akka.actor.ActorSystem; import lighthouse.files.AppDirectory; public class BitSquare extends Application { @@ -118,11 +119,11 @@ public class BitSquare extends Application { log.error(e.getMessage()); } - // currently there is not SystemTray support for java fx (planned for version 3) so we use the old AWT - AWTSystemTray.createSystemTray(primaryStage); - final Injector injector = Guice.createInjector(new BitSquareModule()); + // currently there is not SystemTray support for java fx (planned for version 3) so we use the old AWT + AWTSystemTray.createSystemTray(primaryStage, injector.getInstance(ActorSystem.class)); + walletFacade = injector.getInstance(WalletFacade.class); messageFacade = injector.getInstance(MessageFacade.class); Profiler.printMsgWithTime("BitSquare: messageFacade, walletFacade created"); diff --git a/src/main/java/io/bitsquare/di/BitSquareModule.java b/src/main/java/io/bitsquare/di/BitSquareModule.java index fd090e0c58..93849519be 100644 --- a/src/main/java/io/bitsquare/di/BitSquareModule.java +++ b/src/main/java/io/bitsquare/di/BitSquareModule.java @@ -18,6 +18,7 @@ package io.bitsquare.di; +import io.bitsquare.BitSquare; import io.bitsquare.btc.BlockChainFacade; import io.bitsquare.btc.FeePolicy; import io.bitsquare.btc.WalletFacade; @@ -32,9 +33,11 @@ import io.bitsquare.gui.util.validation.FiatValidator; import io.bitsquare.gui.util.validation.InputValidator; import io.bitsquare.gui.util.validation.PasswordValidator; import io.bitsquare.msg.BootstrappedPeerFactory; +import io.bitsquare.msg.DHTSeedService; import io.bitsquare.msg.MessageFacade; import io.bitsquare.msg.P2PNode; import io.bitsquare.msg.SeedNodeAddress; +import io.bitsquare.msg.actor.DHTManager; import io.bitsquare.persistence.Persistence; import io.bitsquare.settings.Settings; import io.bitsquare.trade.TradeManager; @@ -55,6 +58,8 @@ import java.util.Properties; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import akka.actor.ActorSystem; + public class BitSquareModule extends AbstractModule { private static final Logger log = LoggerFactory.getLogger(BitSquareModule.class); @@ -95,6 +100,11 @@ public class BitSquareModule extends AbstractModule { bind(SeedNodeAddress.StaticSeedNodeAddresses.class).annotatedWith(Names.named("defaultSeedNode")) .toProvider(StaticSeedNodeAddressesProvider.class).asEagerSingleton(); + + // Actor Related Classes to Inject + bind(ActorSystem.class).toProvider(ActorSystemProvider.class).asEagerSingleton(); + + bind(DHTSeedService.class); } } @@ -155,4 +165,17 @@ class NetworkParametersProvider implements Provider { } return result; } +} + +class ActorSystemProvider implements Provider { + + @Override + public ActorSystem get() { + ActorSystem system = ActorSystem.create(BitSquare.getAppName()); + + // create top level actors + system.actorOf(DHTManager.getProps(), DHTManager.SEED_NAME); + + return system; + } } \ No newline at end of file diff --git a/src/main/java/io/bitsquare/gui/AWTSystemTray.java b/src/main/java/io/bitsquare/gui/AWTSystemTray.java index 0ddea3646d..86be13cb56 100644 --- a/src/main/java/io/bitsquare/gui/AWTSystemTray.java +++ b/src/main/java/io/bitsquare/gui/AWTSystemTray.java @@ -23,6 +23,8 @@ import io.bitsquare.gui.util.ImageUtil; import java.awt.*; +import java.util.concurrent.TimeoutException; + import javax.swing.*; import javafx.application.Platform; @@ -31,6 +33,9 @@ import javafx.stage.Stage; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import akka.actor.ActorSystem; +import scala.concurrent.duration.Duration; + /** * There is no JavaFX support yet, so we need to use AWT. * TODO research more @@ -40,10 +45,13 @@ public class AWTSystemTray { private static boolean isStageVisible = true; private static MenuItem showGuiItem; private static Stage stage; + private static ActorSystem actorSystem; private static TrayIcon trayIcon; - public static void createSystemTray(Stage stage) { + public static void createSystemTray(Stage stage, ActorSystem actorSystem) { AWTSystemTray.stage = stage; + AWTSystemTray.actorSystem = actorSystem; + if (SystemTray.isSupported()) { // prevent exiting the app when the last window get closed Platform.setImplicitExit(false); @@ -82,6 +90,15 @@ public class AWTSystemTray { }); exitItem.addActionListener(e -> { systemTray.remove(trayIcon); + actorSystem.shutdown(); + try { + actorSystem.awaitTermination(Duration.create(5L, "seconds")); + } catch (Exception ex) { + if (ex instanceof TimeoutException) + log.error("ActorSystem did not shutdown properly."); + else + log.error(ex.getMessage()); + } System.exit(0); }); diff --git a/src/main/java/io/bitsquare/gui/main/MainModel.java b/src/main/java/io/bitsquare/gui/main/MainModel.java index 1169275757..b0be246be0 100644 --- a/src/main/java/io/bitsquare/gui/main/MainModel.java +++ b/src/main/java/io/bitsquare/gui/main/MainModel.java @@ -21,7 +21,9 @@ import io.bitsquare.bank.BankAccount; import io.bitsquare.btc.WalletFacade; import io.bitsquare.gui.UIModel; import io.bitsquare.gui.util.Profiler; +import io.bitsquare.msg.DHTSeedService; import io.bitsquare.msg.MessageFacade; +import io.bitsquare.msg.actor.event.PeerInitialized; import io.bitsquare.msg.listeners.BootstrapListener; import io.bitsquare.persistence.Persistence; import io.bitsquare.trade.Trade; @@ -47,6 +49,7 @@ class MainModel extends UIModel { private static final Logger log = LoggerFactory.getLogger(MainModel.class); private final User user; + private final DHTSeedService dhtSeedService; private final WalletFacade walletFacade; private final MessageFacade messageFacade; private final TradeManager tradeManager; @@ -66,9 +69,10 @@ class MainModel extends UIModel { /////////////////////////////////////////////////////////////////////////////////////////// @Inject - private MainModel(User user, WalletFacade walletFacade, MessageFacade messageFacade, + private MainModel(User user, DHTSeedService dhtSeedService, WalletFacade walletFacade, MessageFacade messageFacade, TradeManager tradeManager, Persistence persistence) { this.user = user; + this.dhtSeedService = dhtSeedService; this.walletFacade = walletFacade; this.messageFacade = messageFacade; this.tradeManager = tradeManager; @@ -98,20 +102,30 @@ class MainModel extends UIModel { /////////////////////////////////////////////////////////////////////////////////////////// void initBackend() { - Profiler.printMsgWithTime("MainModel.initFacades"); - messageFacade.init(new BootstrapListener() { - @Override - public void onCompleted() { - messageFacadeInited = true; - if (walletFacadeInited) onFacadesInitialised(); - } - @Override - public void onFailed(Throwable throwable) { - log.error(throwable.toString()); + dhtSeedService.setHandler(m -> { + if (m instanceof PeerInitialized) { + log.debug("dht seed initialized. "); + // init messageFacade after seed node initialized + messageFacade.init(new BootstrapListener() { + @Override + public void onCompleted() { + messageFacadeInited = true; + if (walletFacadeInited) onFacadesInitialised(); + } + + @Override + public void onFailed(Throwable throwable) { + log.error(throwable.toString()); + } + }); } }); + dhtSeedService.initializePeer(); + + Profiler.printMsgWithTime("MainModel.initFacades"); + walletFacade.initialize(() -> { walletFacadeInited = true; if (messageFacadeInited) diff --git a/src/main/java/io/bitsquare/msg/DHTSeedService.java b/src/main/java/io/bitsquare/msg/DHTSeedService.java new file mode 100644 index 0000000000..dcb08a1756 --- /dev/null +++ b/src/main/java/io/bitsquare/msg/DHTSeedService.java @@ -0,0 +1,30 @@ +package io.bitsquare.msg; + +import io.bitsquare.msg.actor.DHTManager; +import io.bitsquare.msg.actor.command.InitializePeer; +import io.bitsquare.util.ActorService; + +import com.google.inject.Inject; + +import java.util.List; + +import net.tomp2p.peers.Number160; + +import akka.actor.ActorSystem; + +public class DHTSeedService extends ActorService { + + private static final List staticSedNodeAddresses = SeedNodeAddress + .StaticSeedNodeAddresses.getAllSeedNodeAddresses(); + + @Inject + public DHTSeedService(ActorSystem system) { + super(system, "/user/" + DHTManager.SEED_NAME); + } + + public void initializePeer() { + + // TODO hard coded seed peer config for now, should read from config properties file + send(new InitializePeer(new Number160(5001), 5001, null)); + } +} diff --git a/src/main/java/io/bitsquare/msg/actor/DHTManager.java b/src/main/java/io/bitsquare/msg/actor/DHTManager.java new file mode 100644 index 0000000000..b2eec25a8c --- /dev/null +++ b/src/main/java/io/bitsquare/msg/actor/DHTManager.java @@ -0,0 +1,69 @@ +package io.bitsquare.msg.actor; + +import io.bitsquare.msg.actor.command.InitializePeer; +import io.bitsquare.msg.actor.event.PeerInitialized; + +import net.tomp2p.connection.Ports; +import net.tomp2p.dht.PeerBuilderDHT; +import net.tomp2p.dht.PeerDHT; +import net.tomp2p.futures.FutureBootstrap; +import net.tomp2p.p2p.Peer; +import net.tomp2p.p2p.PeerBuilder; + +import akka.actor.AbstractActor; +import akka.actor.Props; +import akka.event.Logging; +import akka.event.LoggingAdapter; +import akka.japi.pf.ReceiveBuilder; + +public class DHTManager extends AbstractActor { + + public static final String PEER_NAME = "peerDhtManager"; + public static final String SEED_NAME = "seedDhtManager"; + + private final LoggingAdapter log = Logging.getLogger(context().system(), this); + + // TODO move into app setup + // timeout in ms + private final Long bootstrapTimeout = 10000L; + + public static Props getProps() { + return Props.create(DHTManager.class); + } + + private Peer peer; + private PeerDHT peerDHT; + + public DHTManager() { + + receive(ReceiveBuilder + .match(InitializePeer.class, ip -> { + log.debug("Received message: {}", ip); + + peer = new PeerBuilder(ip.getPeerId()) + .ports(ip.getPort() != null ? ip.getPort() : new Ports().tcpPort()).start(); + peerDHT = new PeerBuilderDHT(peer).start(); + + // TODO add code to discover non-local peers + // FutureDiscover futureDiscover = peer.discover().peerAddress(bootstrapPeers.).start(); + // futureDiscover.awaitUninterruptibly(); + + if (ip.getBootstrapPeers() != null) { + FutureBootstrap futureBootstrap = peer.bootstrap() + .bootstrapTo(ip.getBootstrapPeers()).start(); + futureBootstrap.awaitUninterruptibly(bootstrapTimeout); + } + sender().tell(new PeerInitialized(peer.peerID()), self()); + }) + .matchAny(o -> log.info("received unknown message")).build() + ); + } + + @Override + public void postStop() throws Exception { + log.debug("postStop"); + peerDHT.shutdown(); + super.postStop(); + } +} + diff --git a/src/main/java/io/bitsquare/msg/actor/command/InitializePeer.java b/src/main/java/io/bitsquare/msg/actor/command/InitializePeer.java new file mode 100644 index 0000000000..78fab7c8e5 --- /dev/null +++ b/src/main/java/io/bitsquare/msg/actor/command/InitializePeer.java @@ -0,0 +1,35 @@ +package io.bitsquare.msg.actor.command; + + +import java.util.Collection; + +import net.tomp2p.peers.Number160; +import net.tomp2p.peers.PeerAddress; + +/** + *

Command to initialize TomP2P Peer.

+ */ +public class InitializePeer { + + private final Number160 peerId; + private final Integer port; + private final Collection bootstrapPeers; + + public InitializePeer(Number160 peerId, Integer port, Collection bootstrapPeers) { + this.peerId = peerId; + this.port = port; + this.bootstrapPeers = bootstrapPeers; + } + + public Number160 getPeerId() { + return peerId; + } + + public Integer getPort() { + return port; + } + + public Collection getBootstrapPeers() { + return bootstrapPeers; + } +} diff --git a/src/main/java/io/bitsquare/msg/actor/event/PeerInitialized.java b/src/main/java/io/bitsquare/msg/actor/event/PeerInitialized.java new file mode 100644 index 0000000000..b9b8606a7c --- /dev/null +++ b/src/main/java/io/bitsquare/msg/actor/event/PeerInitialized.java @@ -0,0 +1,21 @@ +package io.bitsquare.msg.actor.event; + + +import net.tomp2p.peers.Number160; + +/** + *

TomP2P Peer Initialized event.

+ */ +public class PeerInitialized { + + private final Number160 peerId; + + public PeerInitialized(Number160 peerId) { + this.peerId = peerId; + } + + public Number160 getPeerId() { + return peerId; + } + +} diff --git a/src/main/java/io/bitsquare/util/ActorService.java b/src/main/java/io/bitsquare/util/ActorService.java new file mode 100644 index 0000000000..12e9a2bc86 --- /dev/null +++ b/src/main/java/io/bitsquare/util/ActorService.java @@ -0,0 +1,89 @@ +/* + * This file is part of Bitsquare. + * + * Bitsquare is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bitsquare is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bitsquare. If not, see . + */ + +package io.bitsquare.util; + +import javafx.concurrent.Service; +import javafx.concurrent.Task; + +import akka.actor.ActorSelection; +import akka.actor.ActorSystem; +import akka.actor.Inbox; +import akka.event.Logging; +import akka.event.LoggingAdapter; +import com.sun.glass.ui.Application; +import scala.concurrent.duration.FiniteDuration; + +public abstract class ActorService extends Service { + + private final LoggingAdapter log; + + private final ActorSystem system; + private final Inbox inbox; + private ActorSelection actor; + + private MessageHandler handler; + + protected ActorService(ActorSystem system, String actorPath) { + this.log = Logging.getLogger(system, this); + this.system = system; + this.inbox = Inbox.create(system); + this.actor = system.actorSelection(actorPath); + log.debug(actor.pathString()); + this.start(); + } + + public void setHandler(MessageHandler handler) { + this.handler = handler; + } + + public void send(Object command) { + if (actor != null) { + actor.tell(command, inbox.getRef()); + } + } + + protected Task createTask() { + + return new Task() { + protected String call() throws Exception { + + while (!isCancelled()) { + if (inbox != null) { + try { + Object result = inbox.receive(FiniteDuration.create(1l, "minute")); + if (result != null) { + System.out.println(result.toString()); + if (handler != null) { + Application.invokeLater(new Runnable() { + @Override + public void run() { + handler.handle(result); + } + }); + } + } + } catch (Exception e) { + //System.out.println(e.toString()); + } + } + } + return null; + } + }; + } +} diff --git a/src/main/java/io/bitsquare/util/MessageHandler.java b/src/main/java/io/bitsquare/util/MessageHandler.java new file mode 100644 index 0000000000..eac501e2ef --- /dev/null +++ b/src/main/java/io/bitsquare/util/MessageHandler.java @@ -0,0 +1,24 @@ +/* + * This file is part of Bitsquare. + * + * Bitsquare is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bitsquare is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bitsquare. If not, see . + */ + +package io.bitsquare.util; + +public interface MessageHandler { + + public void handle(Object message); + +} diff --git a/src/main/resources/application.conf b/src/main/resources/application.conf new file mode 100644 index 0000000000..ce3f33cf7c --- /dev/null +++ b/src/main/resources/application.conf @@ -0,0 +1,17 @@ +akka { + + # Loggers to register at boot time (akka.event.Logging$DefaultLogger logs + # to STDOUT) + #loggers = ["akka.event.slf4j.Slf4jLogger"] + + # Log level used by the configured loggers (see "loggers") as soon + # as they have been started; before that, see "stdout-loglevel" + # Options: OFF, ERROR, WARNING, INFO, DEBUG + loglevel = "DEBUG" + + # Log level for the very basic logger activated during ActorSystem startup. + # This logger prints the log messages to stdout (System.out). + # Options: OFF, ERROR, WARNING, INFO, DEBUG + stdout-loglevel = "DEBUG" + +} \ No newline at end of file