Get both bundle and app server logging working (#3362)

* Get both bundle and app server logging working, oracleServer does not compile

* Get everythig compiling

* Fix bug where bundle wouldn't use command line param rpc port for ConsoleCli

* Add unit tests to make sure we are translating to config properly

* Fix bug where app config wasn't re-created after parsing bundle args

* Fix datadir resolution in BundleGUI

* Implement force-recalc-chainwork

* fix bug

* Try not using tilde on windows

* Try to print datadir on CI

* Try using stackoverflow answer

* Try escaping windows path

* Take ben's suggestion of using AppConfig.safePathToString

* Just check the paths are equal

* Fix nullpointerexceptions on startup

* Fix bug where bitcoin-s-bundle.conf was not being read

* Remove usage of --conf flag in bundle when starting backend server

* Fix compile

* Rename usedDir -> networkDir

* Fix passing in datadir
This commit is contained in:
Chris Stewart 2021-07-07 10:59:11 -05:00 committed by GitHub
parent 5df7a8bdf3
commit bc79a24f53
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 451 additions and 276 deletions

View File

@ -1,12 +1,10 @@
package org.bitcoins.bundle.gui
import akka.actor.ActorSystem
import com.typesafe.config.Config
import org.bitcoins.db.AppConfig
import org.bitcoins.db.AppConfig.DEFAULT_BITCOIN_S_DATADIR
import org.bitcoins.bundle.util.BitcoinSAppJFX3
import org.bitcoins.db.util.{DatadirParser, ServerArgParser}
import org.bitcoins.gui._
import org.bitcoins.gui.util.GUIUtil
import org.bitcoins.server.util.DatadirUtil
import org.bitcoins.server.BitcoinSAppConfig
import scalafx.application.{JFXApp3, Platform}
import scalafx.geometry.Pos
import scalafx.scene.Scene
@ -14,14 +12,14 @@ import scalafx.scene.control.Alert.AlertType
import scalafx.scene.control._
import scalafx.scene.layout.VBox
import java.nio.file.{Path, Paths}
object BundleGUI extends WalletGUI with BitcoinSAppJFX3 {
object BundleGUI extends WalletGUI with JFXApp3 {
override val customFinalDirOpt: Option[String] = None
implicit override lazy val system: ActorSystem = ActorSystem(
s"bitcoin-s-gui-${System.currentTimeMillis()}")
override val actorSystemName: String =
s"bitcoin-s-gui-${System.currentTimeMillis()}"
lazy val args = parameters.raw
override lazy val commandLineArgs: Array[String] = parameters.raw.toArray
override def start(): Unit = {
// Catch unhandled exceptions on FX Application thread
@ -37,19 +35,26 @@ object BundleGUI extends WalletGUI with JFXApp3 {
}.showAndWait()
})
// Set log location
val baseConfig: Config = AppConfig
.getBaseConfig(DEFAULT_BITCOIN_S_DATADIR)
.resolve()
lazy val serverArgParser = ServerArgParser(commandLineArgs.toVector)
val datadir: Path =
Paths.get(baseConfig.getString("bitcoin-s.datadir"))
val datadirParser = DatadirParser(serverArgParser, customFinalDirOpt)
val usedDir = DatadirUtil.getFinalDatadir(datadir, baseConfig, None)
System.setProperty("bitcoins.log.location",
datadirParser.networkDir.toAbsolutePath.toString)
System.setProperty("bitcoins.log.location", usedDir.toAbsolutePath.toString)
//adjust the rpc port if one was specified
GlobalData.rpcPortOpt = serverArgParser.rpcPortOpt match {
case Some(rpcPort) => Some(rpcPort)
case None => GlobalData.rpcPortOpt //keep previous setting
}
implicit val appConfig: BitcoinSAppConfig =
BitcoinSAppConfig.fromDatadirWithBundleConfWithServerArgs(
datadirParser.datadir,
serverArgParser)(system.dispatcher)
val landingPane = new LandingPane(glassPane, serverArgParser)
val landingPane = new LandingPane(glassPane)
rootView.children = Vector(landingPane.view, glassPane)
lazy val guiScene: Scene = new Scene(1400, 600) {

View File

@ -2,6 +2,7 @@ package org.bitcoins.bundle.gui
import akka.actor.ActorSystem
import grizzled.slf4j.Logging
import org.bitcoins.db.util.ServerArgParser
import org.bitcoins.gui._
import org.bitcoins.node.NodeType
import org.bitcoins.server.BitcoinSAppConfig
@ -13,15 +14,12 @@ import scalafx.scene.text._
import scala.util.Try
class LandingPane(glassPane: VBox)(implicit system: ActorSystem)
class LandingPane(glassPane: VBox, serverArgParser: ServerArgParser)(implicit
system: ActorSystem,
appConfig: BitcoinSAppConfig)
extends Logging {
import system.dispatcher
val appConfig: BitcoinSAppConfig =
BitcoinSAppConfig.fromDefaultDatadirWithBundleConf()
val model = new LandingPaneModel()
val model = new LandingPaneModel(serverArgParser)
private val label: Label = new Label("Welcome to Bitcoin-S") {
alignmentInParent = Pos.BottomCenter

View File

@ -5,20 +5,22 @@ import com.typesafe.config._
import grizzled.slf4j.Logging
import org.bitcoins.bundle.gui.BundleGUI._
import org.bitcoins.db.AppConfig
import org.bitcoins.db.util.{DatadirUtil, ServerArgParser}
import org.bitcoins.gui._
import org.bitcoins.node.NodeType._
import org.bitcoins.node._
import org.bitcoins.server.BitcoinSAppConfig.toNodeConf
import org.bitcoins.server._
import org.bitcoins.server.util.DatadirUtil
import scalafx.beans.property.ObjectProperty
import scalafx.stage.Window
import java.nio.file.{Files, Path}
import java.nio.file.Files
import scala.concurrent._
import scala.concurrent.duration.DurationInt
class LandingPaneModel()(implicit system: ActorSystem) extends Logging {
class LandingPaneModel(serverArgParser: ServerArgParser)(implicit
system: ActorSystem)
extends Logging {
var taskRunner: TaskRunner = _
@ -40,13 +42,11 @@ class LandingPaneModel()(implicit system: ActorSystem) extends Logging {
logger.info(s"Writing bundle config to $file")
Files.write(file, bundleConfStr.getBytes)
val tmpFile = Files.createTempFile("bitcoin-s-tmp-config", ".conf")
val finalConfF: Future[Path] = {
val networkConfigF: Future[Config] = {
val tmpConf =
BitcoinSAppConfig.fromConfig(
bundleConf.withFallback(appConfig.config))
val netConfF = tmpConf.nodeType match {
val netConfF: Future[Config] = tmpConf.nodeType match {
case _: InternalImplementationNodeType =>
// If we are connecting to a node we cannot
// know what network it is on now
@ -60,13 +60,9 @@ class LandingPaneModel()(implicit system: ActorSystem) extends Logging {
}
netConfF.map { netConf =>
val finalConf =
BitcoinSAppConfig.fromDefaultDatadirWithBundleConf(
Vector(netConf, bundleConf))
val totalConfStr = AppConfig.configToString(finalConf.config)
logger.info(s"Writing resolved config to $tmpFile")
Files.write(tmpFile, totalConfStr.getBytes)
serverArgParser.toConfig
.withFallback(netConf)
.withFallback(bundleConf)
}
}
@ -78,11 +74,17 @@ class LandingPaneModel()(implicit system: ActorSystem) extends Logging {
promise.success(())
}
finalConfF.map { path =>
val extraArgs = Vector("--conf", path.toAbsolutePath.toString)
val usedArgs = extraArgs ++ args
val startedF = networkConfigF.map { networkConfig =>
val finalAppConfig =
BitcoinSAppConfig.fromDatadir(appConfig.baseDatadir, networkConfig)
// use class base constructor to share the actor system
new BitcoinSServerMain(usedArgs.toArray).run()
new BitcoinSServerMain(serverArgParser)(system, finalAppConfig)
.run()
}
startedF.failed.foreach { case err =>
throw err
}
Await.result(promise.future, 60.seconds)

View File

@ -0,0 +1,6 @@
package org.bitcoins.bundle.util
import org.bitcoins.server.util.BitcoinSApp
import scalafx.application.JFXApp3
trait BitcoinSAppJFX3 extends BitcoinSApp with JFXApp3

View File

@ -1,22 +1,21 @@
package org.bitcoins.oracle.server
import akka.actor.ActorSystem
import org.bitcoins.db.util.{DatadirParser, ServerArgParser}
import org.bitcoins.dlc.oracle.config.DLCOracleAppConfig
import org.bitcoins.server.routes.{BitcoinSRunner, Server}
import org.bitcoins.server.util.BitcoinSApp
import org.bitcoins.server.routes.{BitcoinSServerRunner, Server}
import org.bitcoins.server.util.BitcoinSAppScalaDaemon
import scala.concurrent.Future
class OracleServerMain(override val args: Array[String])(implicit
override val system: ActorSystem)
extends BitcoinSRunner {
implicit val conf: DLCOracleAppConfig =
DLCOracleAppConfig(datadir, baseConfig)
class OracleServerMain(override val serverArgParser: ServerArgParser)(implicit
override val system: ActorSystem,
conf: DLCOracleAppConfig)
extends BitcoinSServerRunner {
override def start(): Future[Unit] = {
val bindConfOpt = rpcBindOpt match {
val bindConfOpt = serverArgParser.rpcBindOpt match {
case Some(rpcbind) => Some(rpcbind)
case None => conf.rpcBindOpt
}
@ -26,7 +25,7 @@ class OracleServerMain(override val args: Array[String])(implicit
oracle <- conf.initialize()
routes = Seq(OracleRoutes(oracle))
server = rpcPortOpt match {
server = serverArgParser.rpcPortOpt match {
case Some(rpcport) =>
Server(conf = conf,
handlers = routes,
@ -59,9 +58,24 @@ class OracleServerMain(override val args: Array[String])(implicit
}
}
object OracleServerMain extends BitcoinSApp {
object OracleServerMain extends BitcoinSAppScalaDaemon {
override val actorSystemName =
s"bitcoin-s-oracle-${System.currentTimeMillis()}"
new OracleServerMain(args).run(Some("oracle"))
/** Directory specific for current network or custom dir */
override val customFinalDirOpt: Option[String] = Some("oracle")
val serverCmdLineArgs = ServerArgParser(args.toVector)
val datadirParser =
DatadirParser(serverCmdLineArgs, customFinalDirOpt)
System.setProperty("bitcoins.log.location", datadirParser.networkDir.toString)
implicit lazy val conf: DLCOracleAppConfig =
DLCOracleAppConfig(datadirParser.datadir, datadirParser.baseConfig)(
system.dispatcher)
new OracleServerMain(serverCmdLineArgs).run()
}

View File

@ -8,7 +8,7 @@ import org.bitcoins.core.protocol.transaction.WitnessTransaction
import org.bitcoins.rpc.client.common.BitcoindRpcClient
import org.bitcoins.server.BitcoindRpcAppConfig
import org.bitcoins.server.routes.BitcoinSRunner
import org.bitcoins.server.util.BitcoinSApp
import org.bitcoins.server.util.{BitcoinSAppScalaDaemon}
import scala.concurrent.Future
@ -17,13 +17,11 @@ import scala.concurrent.Future
* between bitcoin-s and bitcoind inside of bitcoin-s.conf
* @see https://bitcoin-s.org/docs/config/configuration#example-configuration-file
*/
class ScanBitcoind(override val args: Array[String])(implicit
override val system: ActorSystem)
class ScanBitcoind()(implicit
override val system: ActorSystem,
rpcAppConfig: BitcoindRpcAppConfig)
extends BitcoinSRunner {
implicit val rpcAppConfig: BitcoindRpcAppConfig =
BitcoindRpcAppConfig(datadir, baseConfig)
override def start(): Future[Unit] = {
val bitcoind = rpcAppConfig.client
@ -96,9 +94,15 @@ class ScanBitcoind(override val args: Array[String])(implicit
}
}
object ScanBitcoind extends BitcoinSApp {
object ScanBitcoind extends BitcoinSAppScalaDaemon {
override val actorSystemName: String =
s"scan-bitcoind-${System.currentTimeMillis()}"
new ScanBitcoind(args).run()
override val customFinalDirOpt = None
implicit val rpcAppConfig: BitcoindRpcAppConfig =
BitcoindRpcAppConfig.fromDefaultDatadir()(system.dispatcher)
new ScanBitcoind().run()
}

View File

@ -1,20 +1,19 @@
package org.bitcoins.scripts
import akka.actor.ActorSystem
import org.bitcoins.db.util.{DatadirParser, ServerArgParser}
import org.bitcoins.server.BitcoinSAppConfig
import org.bitcoins.server.routes.BitcoinSRunner
import org.bitcoins.server.util.BitcoinSApp
import org.bitcoins.server.routes.{BitcoinSServerRunner}
import org.bitcoins.server.util.{BitcoinSAppScalaDaemon}
import java.nio.file.Paths
import scala.concurrent.Future
/** This script zips your $HOME/.bitcoin-s/ directory to a specified path, excluding chaindb.sqlite */
class ZipDatadir(override val args: Array[String])(implicit
override val system: ActorSystem)
extends BitcoinSRunner {
implicit lazy val conf: BitcoinSAppConfig =
BitcoinSAppConfig(datadir, baseConfig)
class ZipDatadir(override val serverArgParser: ServerArgParser)(implicit
override val system: ActorSystem,
conf: BitcoinSAppConfig)
extends BitcoinSServerRunner {
override def start(): Future[Unit] = {
@ -30,9 +29,23 @@ class ZipDatadir(override val args: Array[String])(implicit
override def stop(): Future[Unit] = Future.unit
}
object Zip extends BitcoinSApp {
object Zip extends BitcoinSAppScalaDaemon {
override val actorSystemName: String =
s"zip-datadir-${System.currentTimeMillis()}"
new ZipDatadir(args).run()
override val customFinalDirOpt = None
val serverCmdLineArgs = ServerArgParser(args.toVector)
val datadirParser =
DatadirParser(serverCmdLineArgs, customFinalDirOpt)
System.setProperty("bitcoins.log.location", datadirParser.networkDir.toString)
implicit lazy val conf: BitcoinSAppConfig =
BitcoinSAppConfig(datadirParser.datadir, datadirParser.baseConfig)(
system.dispatcher)
new ZipDatadir(serverCmdLineArgs).run()
}

View File

@ -1,134 +1,37 @@
package org.bitcoins.server.routes
import akka.actor.ActorSystem
import com.typesafe.config.{Config, ConfigFactory}
import grizzled.slf4j.Logging
import org.bitcoins.core.config._
import org.bitcoins.core.util.{EnvUtil, StartStopAsync}
import org.bitcoins.db.AppConfig
import org.bitcoins.db.AppConfig.safePathToString
import org.bitcoins.server.util.DatadirUtil
import org.bitcoins.db.util.ServerArgParser
import java.nio.file.{Path, Paths}
import scala.concurrent.{ExecutionContext, Future}
import scala.util.Properties
trait BitcoinSRunner extends StartStopAsync[Unit] with Logging {
protected def args: Array[String]
implicit def system: ActorSystem
implicit lazy val ec: ExecutionContext = system.dispatcher
lazy val argsWithIndex: Vector[(String, Int)] = args.toVector.zipWithIndex
/** The ip address we are binding the server to */
lazy val rpcBindOpt: Option[String] = {
val rpcbindOpt = argsWithIndex.find(_._1.toLowerCase == "--rpcbind")
rpcbindOpt.map { case (_, idx) =>
args(idx + 1)
}
}
lazy val rpcPortOpt: Option[Int] = {
val portOpt = argsWithIndex.find(_._1.toLowerCase == "--rpcport")
portOpt.map { case (_, idx) =>
args(idx + 1).toInt
}
}
lazy val networkOpt: Option[BitcoinNetwork] = {
val netOpt = argsWithIndex.find(_._1.toLowerCase == "--network")
netOpt.map { case (_, idx) =>
val string = args(idx + 1)
string.toLowerCase match {
case "mainnet" => MainNet
case "main" => MainNet
case "testnet3" => TestNet3
case "testnet" => TestNet3
case "test" => TestNet3
case "regtest" => RegTest
case "signet" => SigNet
case "sig" => SigNet
case _: String =>
throw new IllegalArgumentException(s"Invalid network $string")
}
}
}
lazy val forceChainWorkRecalc: Boolean =
args.exists(_.toLowerCase == "--force-recalc-chainwork")
private lazy val dataDirIndexOpt: Option[(String, Int)] = {
argsWithIndex.find(_._1.toLowerCase == "--datadir")
}
/** Sets the default data dir, overridden by the --datadir option */
private lazy val datadirPath: Path = dataDirIndexOpt match {
case None => AppConfig.DEFAULT_BITCOIN_S_DATADIR
case Some((_, dataDirIndex)) =>
val str = args(dataDirIndex + 1)
val usableStr = str.replace("~", Properties.userHome)
Paths.get(usableStr)
}
private lazy val configIndexOpt: Option[Int] = {
argsWithIndex.find(_._1.toLowerCase == "--conf").map(_._2)
}
lazy val datadirConfig: Config =
ConfigFactory.parseString(
s"bitcoin-s.datadir = ${safePathToString(datadirPath)}")
lazy val networkConfig: Config = networkOpt match {
case Some(network) =>
val networkStr = DatadirUtil.networkStrToDirName(network.name)
ConfigFactory.parseString(s"bitcoin-s.network = $networkStr")
case None => ConfigFactory.empty()
}
lazy val baseConfig: Config = configIndexOpt match {
case None =>
AppConfig
.getBaseConfig(datadirPath, List(networkConfig))
.withFallback(datadirConfig)
.resolve()
case Some(configIndex) =>
val str = args(configIndex + 1)
val usableStr = str.replace("~", Properties.userHome)
val path = Paths.get(usableStr)
val conf = ConfigFactory
.parseFile(path.toFile)
.withFallback(datadirConfig)
networkConfig.withFallback(conf)
}
/** Base directory for all bitcoin-s data. This is the resulting datadir from
* the --datadir option and all configuration files.
*/
lazy val datadir: Path =
Paths.get(baseConfig.getString("bitcoin-s.datadir"))
// start everything!
final def run(customFinalDirOpt: Option[String] = None): Unit = {
/** Directory specific for current network or custom dir */
val usedDir: Path =
DatadirUtil.getFinalDatadir(datadir, baseConfig, customFinalDirOpt)
final def run(): Unit = {
//We need to set the system property before any logger instances
//are in instantiated. If we don't do this, we will not log to
//the correct location
//see: https://github.com/bitcoin-s/bitcoin-s/issues/2496
System.setProperty("bitcoins.log.location", usedDir.toAbsolutePath.toString)
//System.setProperty("bitcoins.log.location", usedDir.toAbsolutePath.toString)
logger.info(s"version=${EnvUtil.getVersion}")
logger.info(s"using directory ${usedDir.toAbsolutePath.toString}")
//logger.info(s"using directory ${usedDir.toAbsolutePath.toString}")
val runner: Future[Unit] = start()
runner.failed.foreach { err =>
logger.error(s"Failed to startup server!", err)
}(scala.concurrent.ExecutionContext.Implicits.global)
}
}
trait BitcoinSServerRunner extends BitcoinSRunner {
protected def serverArgParser: ServerArgParser
}

View File

@ -2,8 +2,18 @@ package org.bitcoins.server.util
import akka.actor.ActorSystem
trait BitcoinSApp extends App {
trait BitcoinSApp {
def actorSystemName: String
implicit lazy val system: ActorSystem = ActorSystem(actorSystemName)
def commandLineArgs: Array[String]
/** Useful for projects like the oracle server to specify a custom directory inside of ~./bitcoin-s */
def customFinalDirOpt: Option[String]
}
/** Trait for using BitcoinS app with a daemon backend */
trait BitcoinSAppScalaDaemon extends App with BitcoinSApp {
final override def commandLineArgs: Array[String] = args
}

View File

@ -1,67 +0,0 @@
package org.bitcoins.server
import org.bitcoins.rpc.client.common.BitcoindVersion
import org.bitcoins.rpc.util.RpcUtil
import org.bitcoins.testkit.BitcoinSTestAppConfig
import org.bitcoins.testkit.fixtures.BitcoinSFixture
import org.bitcoins.testkit.util.{AkkaUtil, BitcoinSAsyncTest}
import scala.concurrent.duration.DurationInt
import java.nio.file.Files
import scala.reflect.io.Directory
class LogLocationTest extends BitcoinSAsyncTest {
behavior of "LogLocationTest"
it must "start up and log to the correct location" in {
val datadir = BitcoinSTestAppConfig.tmpDir()
val directory = new Directory(datadir.toFile)
val confFile = datadir.resolve("bitcoin-s.conf")
for {
bitcoind <-
BitcoinSFixture.createBitcoindWithFunds(Some(BitcoindVersion.V21))
// Make it so we connect to the correct bitcoind
port = bitcoind.instance.uri.getPort
confStr = s"""bitcoin-s.node.peers = ["localhost:$port"]"""
_ = Files.write(confFile, confStr.getBytes)
// Add config options
randPort = RpcUtil.randomPort
args = Array("--datadir",
datadir.toAbsolutePath.toString,
"--rpcport",
randPort.toString)
main = new BitcoinSServerMain(args)
// Start the server in a separate thread
runnable = new Runnable {
override def run(): Unit = {
main.run()
}
}
thread = new Thread(runnable)
_ = thread.start()
// Wait for the server to have successfully started up
_ <- AkkaUtil.nonBlockingSleep(1.second)
binding <- BitcoinSServer.startedF
// Stop the server
_ <- bitcoind.stop()
_ <- binding.terminate(5.seconds)
_ = thread.interrupt()
_ <- main.stop()
} yield {
// Cleanup
directory.deleteRecursively()
val expectedDir = datadir.resolve("regtest")
// Check the log location was correctly set
assert(
System.getProperty("bitcoins.log.location") == expectedDir.toString)
}
}
}

View File

@ -1,5 +1,6 @@
package org.bitcoins.server
import org.bitcoins.db.util.ServerArgParser
import org.bitcoins.rpc.util.RpcUtil
import org.bitcoins.testkit.BitcoinSTestAppConfig
import org.bitcoins.testkit.util.{AkkaUtil, BitcoinSAsyncTest}
@ -19,16 +20,18 @@ class ServerRunTest extends BitcoinSAsyncTest {
// Note: on this test passing it will output a stack trace
// because runMain calls err.printStackTrace() on failure
it must "throw errors" in {
val datadir = BitcoinSTestAppConfig.tmpDir()
implicit val config = BitcoinSTestAppConfig.getNeutrinoTestConfig()
val datadir = config.chainConf.datadir
val directory = new Directory(datadir.toFile)
val randPort = RpcUtil.randomPort
val args = Array("--datadir",
datadir.toAbsolutePath.toString,
"--rpcport",
randPort.toString)
val args = Vector("--datadir",
datadir.toAbsolutePath.toString,
"--rpcport",
randPort.toString)
val main = new BitcoinSServerMain(args)
val serverArgParser = ServerArgParser(args)
val main = new BitcoinSServerMain(serverArgParser)
val runMainF = main.start()
// Use Exception because different errors can occur
val assertionF: Future[Assertion] = recoverToSucceededIf[Exception] {

View File

@ -6,6 +6,7 @@ import org.bitcoins.chain.config.ChainAppConfig
import org.bitcoins.commons.file.FileUtil
import org.bitcoins.core.util.StartStopAsync
import org.bitcoins.db.AppConfig
import org.bitcoins.db.util.ServerArgParser
import org.bitcoins.dlc.wallet.DLCAppConfig
import org.bitcoins.keymanager.config.KeyManagerAppConfig
import org.bitcoins.node.config.NodeAppConfig
@ -123,6 +124,18 @@ object BitcoinSAppConfig extends Logging {
fromConfig(ConfigFactory.load())
}
def fromDatadir(datadir: Path, confs: Config*)(implicit
ec: ExecutionContext): BitcoinSAppConfig = {
BitcoinSAppConfig(datadir, confs: _*)
}
def fromDatadirWithServerArgs(
datadir: Path,
serverArgsParser: ServerArgParser)(implicit
ec: ExecutionContext): BitcoinSAppConfig = {
fromDatadir(datadir, serverArgsParser.toConfig)
}
/** Constructs an app configuration from the default Bitcoin-S
* data directory and given list of configuration overrides.
*/
@ -132,8 +145,15 @@ object BitcoinSAppConfig extends Logging {
def fromDefaultDatadirWithBundleConf(confs: Vector[Config] = Vector.empty)(
implicit ec: ExecutionContext): BitcoinSAppConfig = {
fromDatadirWithBundleConf(AppConfig.DEFAULT_BITCOIN_S_DATADIR, confs)
}
def fromDatadirWithBundleConf(
datadir: Path,
confs: Vector[Config] = Vector.empty)(implicit
ec: ExecutionContext): BitcoinSAppConfig = {
val baseConf: BitcoinSAppConfig =
BitcoinSAppConfig.fromDefaultDatadir()
fromDatadir(datadir, confs: _*)
// Grab saved bundle config
val bundleConfFile =
@ -148,6 +168,27 @@ object BitcoinSAppConfig extends Logging {
baseConf.copyWithConfig(extraConfig +: confs)
}
/** Resolve BitcoinSAppConfig in the following order of precedence
* 1. Server args
* 2. bitcoin-s-bundle.conf
* 3. bitcoin-s.conf
* 4. application.conf
* 5. reference.conf
*/
def fromDatadirWithBundleConfWithServerArgs(
datadir: Path,
serverArgParser: ServerArgParser)(implicit
ec: ExecutionContext): BitcoinSAppConfig = {
fromDatadirWithBundleConf(datadir, Vector(serverArgParser.toConfig))
}
/** Creates a BitcoinSAppConfig the the given daemon args to a server */
def fromDefaultDatadirWithServerArgs(serverArgParser: ServerArgParser)(
implicit ec: ExecutionContext): BitcoinSAppConfig = {
val config = serverArgParser.toConfig
fromConfig(config)
}
import scala.language.implicitConversions
/** Converts the given implicit config to a wallet config */

View File

@ -12,6 +12,7 @@ import org.bitcoins.core.api.feeprovider.FeeRateApi
import org.bitcoins.core.api.node.NodeApi
import org.bitcoins.core.util.NetworkUtil
import org.bitcoins.core.wallet.fee.SatoshisPerVirtualByte
import org.bitcoins.db.util.{DatadirParser, ServerArgParser}
import org.bitcoins.dlc.wallet._
import org.bitcoins.feeprovider.FeeProviderName._
import org.bitcoins.feeprovider.MempoolSpaceTarget.HourFeeTarget
@ -20,19 +21,17 @@ import org.bitcoins.node._
import org.bitcoins.node.config.NodeAppConfig
import org.bitcoins.node.models.Peer
import org.bitcoins.rpc.config.ZmqConfig
import org.bitcoins.server.routes.{BitcoinSRunner, Server}
import org.bitcoins.server.util.BitcoinSApp
import org.bitcoins.server.routes.{BitcoinSServerRunner, Server}
import org.bitcoins.server.util.BitcoinSAppScalaDaemon
import org.bitcoins.wallet.Wallet
import org.bitcoins.wallet.config.WalletAppConfig
import scala.concurrent.{ExecutionContext, Future, Promise}
class BitcoinSServerMain(override val args: Array[String])(implicit
override val system: ActorSystem)
extends BitcoinSRunner {
implicit lazy val conf: BitcoinSAppConfig =
BitcoinSAppConfig(datadir, baseConfig)
class BitcoinSServerMain(override val serverArgParser: ServerArgParser)(implicit
override val system: ActorSystem,
conf: BitcoinSAppConfig)
extends BitcoinSServerRunner {
implicit lazy val walletConf: WalletAppConfig = conf.walletConf
implicit lazy val nodeConf: NodeAppConfig = conf.nodeConf
@ -92,7 +91,7 @@ class BitcoinSServerMain(override val args: Array[String])(implicit
//run chain work migration
val chainApiF = runChainWorkCalc(
forceChainWorkRecalc || chainConf.forceRecalcChainWork)
serverArgParser.forceChainWorkRecalc || chainConf.forceRecalcChainWork)
//get a node that isn't started
val nodeF = nodeConf.createNode(peer)(chainConf, system)
@ -141,8 +140,7 @@ class BitcoinSServerMain(override val args: Array[String])(implicit
binding <- startHttpServer(nodeApi = node,
chainApi = chainApi,
wallet = wallet,
rpcbindOpt = rpcBindOpt,
rpcPortOpt = rpcPortOpt)
serverCmdLineArgs = serverArgParser)
_ = {
logger.info(
s"Starting ${nodeConf.nodeType.shortName} node sync, it took=${System
@ -206,8 +204,7 @@ class BitcoinSServerMain(override val args: Array[String])(implicit
binding <- startHttpServer(nodeApi = bitcoind,
chainApi = bitcoind,
wallet = wallet,
rpcbindOpt = rpcBindOpt,
rpcPortOpt = rpcPortOpt)
serverCmdLineArgs = serverArgParser)
_ = BitcoinSServer.startedFP.success(Future.successful(binding))
} yield {
logger.info(s"Done starting Main!")
@ -297,8 +294,7 @@ class BitcoinSServerMain(override val args: Array[String])(implicit
nodeApi: NodeApi,
chainApi: ChainApi,
wallet: DLCWallet,
rpcbindOpt: Option[String],
rpcPortOpt: Option[Int])(implicit
serverCmdLineArgs: ServerArgParser)(implicit
system: ActorSystem,
conf: BitcoinSAppConfig): Future[Http.ServerBinding] = {
implicit val nodeConf: NodeAppConfig = conf.nodeConf
@ -309,13 +305,13 @@ class BitcoinSServerMain(override val args: Array[String])(implicit
val chainRoutes = ChainRoutes(chainApi, nodeConf.network)
val coreRoutes = CoreRoutes(Core)
val bindConfOpt = rpcbindOpt match {
val bindConfOpt = serverCmdLineArgs.rpcBindOpt match {
case Some(rpcbind) => Some(rpcbind)
case None => conf.rpcBindOpt
}
val server = {
rpcPortOpt match {
serverCmdLineArgs.rpcPortOpt match {
case Some(rpcport) =>
Server(conf = nodeConf,
handlers =
@ -392,12 +388,27 @@ class BitcoinSServerMain(override val args: Array[String])(implicit
}
}
object BitcoinSServerMain extends BitcoinSApp {
object BitcoinSServerMain extends BitcoinSAppScalaDaemon {
override val actorSystemName =
s"bitcoin-s-server-${System.currentTimeMillis()}"
new BitcoinSServerMain(args).run()
/** Directory specific for current network or custom dir */
override val customFinalDirOpt: Option[String] = None
val serverCmdLineArgs = ServerArgParser(args.toVector)
val datadirParser =
DatadirParser(serverCmdLineArgs, customFinalDirOpt)
System.setProperty("bitcoins.log.location", datadirParser.networkDir.toString)
implicit lazy val conf: BitcoinSAppConfig =
BitcoinSAppConfig(datadirParser.datadir,
datadirParser.baseConfig,
serverCmdLineArgs.toConfig)(system.dispatcher)
new BitcoinSServerMain(serverCmdLineArgs).run()
}
object BitcoinSServer {

View File

@ -0,0 +1,44 @@
package org.bitcoins.db.util
import com.typesafe.config.ConfigFactory
import org.bitcoins.testkit.BitcoinSTestAppConfig
import org.bitcoins.testkitcore.util.BitcoinSUnitTest
import java.nio.file.Paths
class ServerArgParserTest extends BitcoinSUnitTest {
behavior of "ServerArgParser"
it must "handle no command line flags" in {
val parser = ServerArgParser(Vector.empty)
//config must be empty
assert(parser.toConfig == ConfigFactory.empty())
}
it must "handle having all command line args we support" in {
val datadir = BitcoinSTestAppConfig.tmpDir()
val datadirString = datadir.toAbsolutePath.toString
val args = Vector("--rpcport",
"1234",
"--rpcbind",
"my.cool.site.com",
"--datadir",
s"${datadirString}",
"--force-recalc-chainwork")
val parser = ServerArgParser(args)
val config = parser.toConfig
val datadirPathConfigKey = s"bitcoin-s.datadir"
assert(config.hasPath(datadirPathConfigKey))
assert(config.hasPath(s"bitcoin-s.server.rpcbind"))
assert(config.hasPath(s"bitcoin-s.server.rpcport"))
assert(config.hasPath(s"bitcoin-s.chain.force-recalc-chainwork"))
val datadirFromConfig = config.getString(datadirPathConfigKey)
val path = Paths.get(datadirFromConfig)
assert(path == datadir)
}
}

View File

@ -0,0 +1,64 @@
package org.bitcoins.db.util
import com.typesafe.config.{Config, ConfigFactory}
import org.bitcoins.db.AppConfig
import org.bitcoins.db.AppConfig.safePathToString
import java.nio.file.{Path, Paths}
/** Parses the correct datadir given the possible input sources for datadir config
* 1. The --datadir command line flag
* 2. Inferring the datadir based on the bitcoin network configured
* 3. ??? Anything else i'm forgetting ????
*/
case class DatadirParser(
serverArgs: ServerArgParser,
customFinalDirOpt: Option[String]) {
/** Sets the default data dir, overridden by the --datadir option */
private lazy val datadirPath: Path = serverArgs.datadirOpt match {
case None => AppConfig.DEFAULT_BITCOIN_S_DATADIR
case Some(datadir) => datadir
}
lazy val datadirConfig: Config =
ConfigFactory.parseString(
s"bitcoin-s.datadir = ${safePathToString(datadirPath)}")
lazy val networkConfig: Config = serverArgs.networkOpt match {
case Some(network) =>
val networkStr = DatadirUtil.networkStrToDirName(network.name)
ConfigFactory.parseString(s"bitcoin-s.network = $networkStr")
case None => ConfigFactory.empty()
}
lazy val baseConfig: Config = {
serverArgs.configOpt match {
case None =>
AppConfig
.getBaseConfig(datadirPath, List(networkConfig))
.withFallback(datadirConfig)
.resolve()
case Some(config) =>
val conf = ConfigFactory
.parseFile(config.toFile)
.withFallback(datadirConfig)
networkConfig.withFallback(conf)
}
}
/** Base directory for all bitcoin-s data. This is the resulting datadir from
* the --datadir option and all configuration files.
*/
lazy val datadir: Path =
Paths.get(baseConfig.getString("bitcoin-s.datadir"))
/** Directory specific for current network or custom dir
* Examples are
* HOME/.bitcoin-s/mainnet
* HOME/.bitcoin-s/testnet3
* HOME/.bitcoin-s/oracle
*/
def networkDir: Path =
DatadirUtil.getFinalDatadir(datadir, baseConfig, customFinalDirOpt)
}

View File

@ -1,4 +1,4 @@
package org.bitcoins.server.util
package org.bitcoins.db.util
import com.typesafe.config.Config
import org.bitcoins.core.config._

View File

@ -0,0 +1,124 @@
package org.bitcoins.db.util
import com.typesafe.config.{Config, ConfigFactory}
import org.bitcoins.core.config._
import org.bitcoins.db.AppConfig
import java.nio.file.{Path, Paths}
import scala.util.Properties
/** Parses arguments passed to the bitcoin-s app server as command line arguments
* This does NOT consider things that exist in reference.conf or application.conf files
*/
case class ServerArgParser(commandLineArgs: Vector[String]) {
private lazy val argsWithIndex: Vector[(String, Int)] =
commandLineArgs.zipWithIndex
/** The ip address we are binding the server to */
lazy val rpcBindOpt: Option[String] = {
val rpcbindOpt = argsWithIndex.find(_._1.toLowerCase == "--rpcbind")
rpcbindOpt.map { case (_, idx) =>
commandLineArgs(idx + 1)
}
}
lazy val rpcPortOpt: Option[Int] = {
val portOpt = argsWithIndex.find(_._1.toLowerCase == "--rpcport")
portOpt.map { case (_, idx) =>
commandLineArgs(idx + 1).toInt
}
}
lazy val networkOpt: Option[BitcoinNetwork] = {
val netOpt = argsWithIndex.find(_._1.toLowerCase == "--network")
netOpt.map { case (_, idx) =>
val string = commandLineArgs(idx + 1)
string.toLowerCase match {
case "mainnet" => MainNet
case "main" => MainNet
case "testnet3" => TestNet3
case "testnet" => TestNet3
case "test" => TestNet3
case "regtest" => RegTest
case "signet" => SigNet
case "sig" => SigNet
case _: String =>
throw new IllegalArgumentException(s"Invalid network $string")
}
}
}
private lazy val dataDirIndexOpt: Option[(String, Int)] = {
argsWithIndex.find(_._1.toLowerCase == "--datadir")
}
/** The datadir passed in as a command line arg using --datadir */
lazy val datadirOpt: Option[Path] = dataDirIndexOpt.map { case (_, idx) =>
val str = commandLineArgs(idx + 1)
//we only want the replace ~ if it is first in the file path
//otherwise windows gets mangled as it can have parts of the file path containing ~
//https://stackoverflow.com/a/7163455/967713
//C:\Users\RUNNER~1\AppData\Local\Temp\bitcoin-s-13391384540028797275
val usableStr = str.replaceFirst("^~", Properties.userHome)
Paths.get(usableStr)
}
private lazy val configIndexOpt: Option[Int] = {
argsWithIndex.find(_._1.toLowerCase == "--conf").map(_._2)
}
/** A custom configuration file passed in as a command line arg with --conf */
lazy val configOpt: Option[Path] = {
configIndexOpt.map { idx =>
val str = commandLineArgs(idx + 1)
val usableStr = str.replace("~", Properties.userHome)
Paths.get(usableStr)
}
}
lazy val forceChainWorkRecalc: Boolean =
commandLineArgs.exists(_.toLowerCase == "--force-recalc-chainwork")
/** Converts the given command line args into a Config object.
* There is one exclusion to this, we cannot write the --conf
* flag to the config file as that is self referential
*/
def toConfig: Config = {
val rpcPortString = rpcPortOpt match {
case Some(rpcPort) =>
s"bitcoin-s.server.rpcport=$rpcPort\n"
case None => s""
}
val rpcBindString = rpcBindOpt match {
case Some(rpcbind) =>
s"bitcoin-s.server.rpcbind=$rpcbind\n"
case None => s""
}
val datadirString = datadirOpt match {
case Some(datadir) =>
s"bitcoin-s.datadir=" + AppConfig.safePathToString(datadir) + "\n"
case None => s""
}
val forceChainWorkRecalcString = if (forceChainWorkRecalc) {
s"bitcoin-s.chain.force-recalc-chainwork=$forceChainWorkRecalc\n"
} else {
""
}
//omitting configOpt as i don't know if we can do anything with that?
val all =
rpcPortString + rpcBindString + datadirString + forceChainWorkRecalcString
ConfigFactory.parseString(all)
}
}
object ServerArgParser {
val empty: ServerArgParser = ServerArgParser(Vector.empty)
}

View File

@ -130,7 +130,7 @@ abstract class Wallet
if (account.xpub != xpub) {
val errorMsg =
s"Divergent xpubs for account=$account. Existing database xpub=${account.xpub}, key manager's xpub=$xpub. " +
s"It is possible we have a different key manager being used than expected, key manager=$keyManager"
s"It is possible we have a different key manager being used than expected, key manager=${keyManager.kmParams.seedPath.toAbsolutePath.toString}"
Future.failed(new RuntimeException(errorMsg))
} else {
Future.unit
@ -995,7 +995,7 @@ object Wallet extends WalletLogger {
if (account.xpub != xpub) {
val errorMsg =
s"Divergent xpubs for account=${account}. Existing database xpub=${account.xpub}, new xpub=${xpub}. " +
s"It is possible we have a different key manager being used than expected, keymanager=${keyManager}"
s"It is possible we have a different key manager being used than expected, keymanager=${keyManager.kmParams.seedPath.toAbsolutePath.toString}"
Future.failed(new RuntimeException(errorMsg))
} else {
logger.debug(