diff --git a/app/server/src/main/scala/org/bitcoins/server/Main.scala b/app/server/src/main/scala/org/bitcoins/server/Main.scala index 8e0750a873..2d5d995bdd 100644 --- a/app/server/src/main/scala/org/bitcoins/server/Main.scala +++ b/app/server/src/main/scala/org/bitcoins/server/Main.scala @@ -1,7 +1,6 @@ package org.bitcoins.server -import java.net.InetSocketAddress -import java.nio.file.{Files, Paths} +import java.nio.file.Paths import akka.actor.ActorSystem import akka.http.scaladsl.Http @@ -14,22 +13,24 @@ import org.bitcoins.chain.models.{ CompactFilterHeaderDAO } import org.bitcoins.core.Core -import org.bitcoins.core.api.{ChainQueryApi, FeeRateApi} -import org.bitcoins.core.util.{BitcoinSLogger, FutureUtil} +import org.bitcoins.core.util.{BitcoinSLogger, FutureUtil, NetworkUtil} import org.bitcoins.db.AppConfig import org.bitcoins.feeprovider.BitcoinerLiveFeeRateProvider -import org.bitcoins.keymanager.KeyManagerInitializeError -import org.bitcoins.keymanager.bip39.{BIP39KeyManager, BIP39LockedKeyManager} import org.bitcoins.node.config.NodeAppConfig import org.bitcoins.node.models.Peer -import org.bitcoins.node._ -import org.bitcoins.wallet.Wallet +import org.bitcoins.node.{ + Node, + NodeCallbacks, + OnBlockHeadersReceived, + OnBlockReceived, + OnCompactFiltersReceived, + OnTxReceived, + SpvNode +} import org.bitcoins.wallet.api._ import org.bitcoins.wallet.config.WalletAppConfig -import org.bitcoins.wallet.models.AccountDAO -import scala.concurrent.duration._ -import scala.concurrent.{Await, ExecutionContext, Future, Promise} +import scala.concurrent.{ExecutionContext, Future, Promise} object Main extends App with BitcoinSLogger { @@ -69,7 +70,8 @@ object Main extends App with BitcoinSLogger { implicit val chainConf: ChainAppConfig = conf.chainConf val peerSocket = - parseInetSocketAddress(nodeConf.peers.head, nodeConf.network.port) + NetworkUtil.parseInetSocketAddress(nodeConf.peers.head, + nodeConf.network.port) val peer = Peer.fromSocket(peerSocket) val bip39PasswordOpt = None //todo need to prompt user for this @@ -83,7 +85,7 @@ object Main extends App with BitcoinSLogger { //get a node that isn't started val uninitializedNodeF = configInitializedF.flatMap { _ => - createNode(peer)(nodeConf, chainConf, system) + nodeConf.createNode(peer)(chainConf, system) } //get our wallet @@ -91,10 +93,10 @@ object Main extends App with BitcoinSLogger { _ <- configInitializedF uninitializedNode <- uninitializedNodeF chainApi <- chainApiF - wallet <- createWallet(uninitializedNode, - chainApi, - BitcoinerLiveFeeRateProvider(60), - bip39PasswordOpt) + wallet <- walletConf.createWallet(uninitializedNode, + chainApi, + BitcoinerLiveFeeRateProvider(60), + bip39PasswordOpt) } yield wallet //add callbacks to our unitialized node @@ -156,83 +158,6 @@ object Main extends App with BitcoinSLogger { //start everything! runMain() - /** Checks if the user already has a wallet */ - private def hasWallet()( - implicit walletConf: WalletAppConfig, - ec: ExecutionContext): Future[Boolean] = { - val walletDB = walletConf.dbPath resolve walletConf.dbName - val hdCoin = walletConf.defaultAccount.coin - if (Files.exists(walletDB) && walletConf.seedExists()) { - AccountDAO().read((hdCoin, 0)).map(_.isDefined) - } else { - Future.successful(false) - } - } - - private def createNode(peer: Peer)( - implicit nodeConf: NodeAppConfig, - chainConf: ChainAppConfig, - system: ActorSystem): Future[Node] = { - if (nodeConf.isSPVEnabled) { - Future.successful(SpvNode(peer, nodeConf, chainConf, system)) - } else if (nodeConf.isNeutrinoEnabled) { - Future.successful(NeutrinoNode(peer, nodeConf, chainConf, system)) - } else { - Future.failed( - new RuntimeException("Neither Neutrino nor SPV mode is enabled.")) - } - } - - private def createWallet( - nodeApi: Node, - chainQueryApi: ChainQueryApi, - feeRateApi: FeeRateApi, - bip39PasswordOpt: Option[String])( - implicit walletConf: WalletAppConfig, - system: ActorSystem): Future[WalletApi] = { - import system.dispatcher - hasWallet().flatMap { walletExists => - if (walletExists) { - logger.info(s"Using pre-existing wallet") - - // TODO change me when we implement proper password handling - BIP39LockedKeyManager.unlock(BIP39KeyManager.badPassphrase, - bip39PasswordOpt, - walletConf.kmParams) match { - case Right(km) => - val wallet = - Wallet(km, nodeApi, chainQueryApi, feeRateApi, km.creationTime) - Future.successful(wallet) - case Left(err) => - error(err) - } - } else { - logger.info(s"Initializing key manager") - val bip39PasswordOpt = None - val keyManagerE: Either[KeyManagerInitializeError, BIP39KeyManager] = - BIP39KeyManager.initialize(kmParams = walletConf.kmParams, - bip39PasswordOpt = bip39PasswordOpt) - - val keyManager = keyManagerE match { - case Right(keyManager) => keyManager - case Left(err) => - error(err) - } - - logger.info(s"Creating new wallet") - val unInitializedWallet = - Wallet(keyManager, - nodeApi, - chainQueryApi, - feeRateApi, - keyManager.creationTime) - - Wallet.initialize(wallet = unInitializedWallet, - bip39PasswordOpt = bip39PasswordOpt) - } - } - } - private def createCallbacks(wallet: WalletApi)( implicit nodeConf: NodeAppConfig, ec: ExecutionContext): Future[NodeCallbacks] = { @@ -287,41 +212,6 @@ object Main extends App with BitcoinSLogger { } } - /** Log the given message, shut down the actor system and quit. */ - private def error(message: Any)(implicit system: ActorSystem): Nothing = { - logger.error(s"FATAL: $message") - logger.error(s"Shutting down actor system") - Await.result(system.terminate(), 10.seconds) - logger.error("Actor system terminated") - logger.error(s"Exiting") - sys.error(message.toString()) - } - - private def parseInetSocketAddress( - address: String, - defaultPort: Int): InetSocketAddress = { - - def parsePort(port: String): Int = { - lazy val errorMsg = s"Invalid peer port: $address" - try { - val res = port.toInt - if (res < 0 || res > 0xffff) { - throw new RuntimeException(errorMsg) - } - res - } catch { - case _: NumberFormatException => - throw new RuntimeException(errorMsg) - } - } - - address.split(":") match { - case Array(host) => new InetSocketAddress(host, defaultPort) - case Array(host, port) => new InetSocketAddress(host, parsePort(port)) - case _ => throw new RuntimeException(s"Invalid peer address: $address") - } - } - /** This is needed for migrations V2/V3 on the chain project to re-calculate the total work for the chain */ private def runChainWorkCalc()( implicit chainAppConfig: ChainAppConfig, diff --git a/core/src/main/scala/org/bitcoins/core/util/NetworkUtil.scala b/core/src/main/scala/org/bitcoins/core/util/NetworkUtil.scala new file mode 100644 index 0000000000..980765d74a --- /dev/null +++ b/core/src/main/scala/org/bitcoins/core/util/NetworkUtil.scala @@ -0,0 +1,35 @@ +package org.bitcoins.core.util + +import java.net.InetSocketAddress + +abstract class NetworkUtil { + + private def parsePort(port: String): Int = { + lazy val errorMsg = s"Invalid peer port: $port" + try { + val res = port.toInt + if (res < 0 || res > 0xffff) { + throw new RuntimeException(errorMsg) + } + res + } catch { + case _: NumberFormatException => + throw new RuntimeException(errorMsg) + } + } + + /** Parses a string that looks like this to [[java.net.InetSocketAddress]] + * "neutrino.testnet3.suredbits.com:18333" + * */ + def parseInetSocketAddress( + address: String, + defaultPort: Int): InetSocketAddress = { + address.split(":") match { + case Array(host) => new InetSocketAddress(host, defaultPort) + case Array(host, port) => new InetSocketAddress(host, parsePort(port)) + case _ => throw new RuntimeException(s"Invalid peer address: $address") + } + } +} + +object NetworkUtil extends NetworkUtil diff --git a/node/src/main/scala/org/bitcoins/node/config/NodeAppConfig.scala b/node/src/main/scala/org/bitcoins/node/config/NodeAppConfig.scala index 4f62c25a4b..53ccd624b3 100644 --- a/node/src/main/scala/org/bitcoins/node/config/NodeAppConfig.scala +++ b/node/src/main/scala/org/bitcoins/node/config/NodeAppConfig.scala @@ -2,10 +2,14 @@ package org.bitcoins.node.config import java.nio.file.Path +import akka.actor.ActorSystem import com.typesafe.config.Config +import org.bitcoins.chain.config.ChainAppConfig import org.bitcoins.core.util.FutureUtil import org.bitcoins.db.{AppConfig, AppConfigFactory, JdbcProfileComponent} +import org.bitcoins.node.{NeutrinoNode, Node, SpvNode} import org.bitcoins.node.db.NodeDbManagement +import org.bitcoins.node.models.Peer import scala.concurrent.{ExecutionContext, Future} @@ -68,6 +72,11 @@ case class NodeAppConfig( 0.until(list.size()) .foldLeft(Vector.empty[String])((acc, i) => acc :+ list.get(i)) } + + /** Creates either a neutrino node or a spv node based on the [[NodeAppConfig]] given */ + def createNode(peer: Peer)(chainConf: ChainAppConfig, system: ActorSystem): Future[Node] = { + NodeAppConfig.createNode(peer)(this,chainConf,system) + } } object NodeAppConfig extends AppConfigFactory[NodeAppConfig] { @@ -81,4 +90,15 @@ object NodeAppConfig extends AppConfigFactory[NodeAppConfig] { useLogbackConf, confs: _*) + /** Creates either a neutrino node or a spv node based on the [[NodeAppConfig]] given */ + def createNode(peer: Peer)(implicit nodeConf: NodeAppConfig, chainConf: ChainAppConfig, system: ActorSystem): Future[Node] = { + if (nodeConf.isSPVEnabled) { + Future.successful(SpvNode(peer, nodeConf, chainConf, system)) + } else if (nodeConf.isNeutrinoEnabled) { + Future.successful(NeutrinoNode(peer, nodeConf, chainConf, system)) + } else { + Future.failed( + new RuntimeException("Neither Neutrino nor SPV mode is enabled.")) + } + } } diff --git a/wallet/src/main/scala/org/bitcoins/wallet/config/WalletAppConfig.scala b/wallet/src/main/scala/org/bitcoins/wallet/config/WalletAppConfig.scala index 5c0d1b8b4d..b890278826 100644 --- a/wallet/src/main/scala/org/bitcoins/wallet/config/WalletAppConfig.scala +++ b/wallet/src/main/scala/org/bitcoins/wallet/config/WalletAppConfig.scala @@ -4,11 +4,20 @@ import java.nio.file.{Files, Path} import java.util.concurrent.TimeUnit import com.typesafe.config.Config +import org.bitcoins.core.api.{ChainQueryApi, FeeRateApi, NodeApi} import org.bitcoins.core.hd._ import org.bitcoins.core.util.FutureUtil import org.bitcoins.db.{AppConfig, AppConfigFactory, JdbcProfileComponent} -import org.bitcoins.keymanager.{KeyManagerParams, WalletStorage} +import org.bitcoins.keymanager.bip39.{BIP39KeyManager, BIP39LockedKeyManager} +import org.bitcoins.keymanager.{ + KeyManagerInitializeError, + KeyManagerParams, + WalletStorage +} +import org.bitcoins.wallet.{Wallet, WalletLogger} +import org.bitcoins.wallet.api.WalletApi import org.bitcoins.wallet.db.WalletDbManagement +import org.bitcoins.wallet.models.AccountDAO import scala.concurrent.duration.{DurationInt, FiniteDuration} import scala.concurrent.{ExecutionContext, Future} @@ -125,9 +134,38 @@ case class WalletAppConfig( 5.second } } + + /** Checks if the following exist + * 1. A wallet exists + * 2. seed exists + * 3. The account exists */ + def hasWallet()(implicit ec: ExecutionContext): Future[Boolean] = { + val walletDB = dbPath.resolve(dbName) + val hdCoin = defaultAccount.coin + if (Files.exists(walletDB) && seedExists()) { + AccountDAO()(ec, this).read((hdCoin, 0)).map(_.isDefined) + } else { + Future.successful(false) + } + } + + /** Creates a wallet based on this [[WalletAppConfig]] */ + def createWallet( + nodeApi: NodeApi, + chainQueryApi: ChainQueryApi, + feeRateApi: FeeRateApi, + bip39PasswordOpt: Option[String])( + implicit ec: ExecutionContext): Future[WalletApi] = { + WalletAppConfig.createWallet(nodeApi = nodeApi, + chainQueryApi = chainQueryApi, + feeRateApi = feeRateApi, + bip39PasswordOpt = bip39PasswordOpt)(this, ec) + } } -object WalletAppConfig extends AppConfigFactory[WalletAppConfig] { +object WalletAppConfig + extends AppConfigFactory[WalletAppConfig] + with WalletLogger { /** Constructs a wallet configuration from the default Bitcoin-S * data directory and given list of configuration overrides. @@ -137,4 +175,53 @@ object WalletAppConfig extends AppConfigFactory[WalletAppConfig] { useLogbackConf: Boolean, confs: Vector[Config])(implicit ec: ExecutionContext): WalletAppConfig = WalletAppConfig(datadir, useLogbackConf, confs: _*) + + /** Creates a wallet based on the given [[WalletAppConfig]] */ + def createWallet( + nodeApi: NodeApi, + chainQueryApi: ChainQueryApi, + feeRateApi: FeeRateApi, + bip39PasswordOpt: Option[String])( + implicit walletConf: WalletAppConfig, + ec: ExecutionContext): Future[WalletApi] = { + walletConf.hasWallet().flatMap { walletExists => + if (walletExists) { + logger.info(s"Using pre-existing wallet") + // TODO change me when we implement proper password handling + BIP39LockedKeyManager.unlock(BIP39KeyManager.badPassphrase, + bip39PasswordOpt, + walletConf.kmParams) match { + case Right(km) => + val wallet = + Wallet(km, nodeApi, chainQueryApi, feeRateApi, km.creationTime) + Future.successful(wallet) + case Left(err) => + sys.error(s"Error initializing key manager, err=${err}") + } + } else { + logger.info(s"Initializing key manager") + val bip39PasswordOpt = None + val keyManagerE: Either[KeyManagerInitializeError, BIP39KeyManager] = + BIP39KeyManager.initialize(kmParams = walletConf.kmParams, + bip39PasswordOpt = bip39PasswordOpt) + + val keyManager = keyManagerE match { + case Right(keyManager) => keyManager + case Left(err) => + sys.error(s"Error initializing key manager, err=${err}") + } + + logger.info(s"Creating new wallet") + val unInitializedWallet = + Wallet(keyManager, + nodeApi, + chainQueryApi, + feeRateApi, + keyManager.creationTime) + + Wallet.initialize(wallet = unInitializedWallet, + bip39PasswordOpt = bip39PasswordOpt) + } + } + } }