mirror of
https://github.com/bitcoin-s/bitcoin-s.git
synced 2024-11-19 01:40:55 +01:00
Multi module configuration (#494)
* Replace AppConfig with more specific types * Rework database configuration In this commit we: 1) Introduce new database settings for all submodules, and at the same time remove the singular datbase setting for the entire project 2) Introduce a new method .initialize on all configs that is responsible for creating needed directories, databases and files 3) Introduce a new class BitcoinSAppConfig that wraps configs for all three other subproject config. We also provide implicit conversions that enable this super-config to be passed in wherever a specialized config is required. 4) Add more tests for our configuration setup. * Add Ammonite to Docs deps
This commit is contained in:
parent
49ffbea801
commit
3c4157a698
@ -353,7 +353,7 @@ lazy val nodeTest = {
|
||||
|
||||
lazy val testkit = project
|
||||
.in(file("testkit"))
|
||||
.settings(commonProdSettings: _*)
|
||||
.settings(commonSettings: _*)
|
||||
.dependsOn(
|
||||
core,
|
||||
chain,
|
||||
@ -383,7 +383,8 @@ lazy val docs = project
|
||||
bitcoins / Compile / unidoc,
|
||||
Compile / docusaurusPublishGhpages
|
||||
)
|
||||
.value
|
||||
.value,
|
||||
libraryDependencies ++= Deps.docs
|
||||
)
|
||||
.dependsOn(
|
||||
bitcoindRpc,
|
||||
|
@ -4,7 +4,6 @@ import akka.actor.ActorSystem
|
||||
import org.bitcoins.chain.config.ChainAppConfig
|
||||
import org.bitcoins.chain.models.BlockHeaderDAO
|
||||
import org.bitcoins.core.protocol.blockchain.MainNetChainParams
|
||||
import org.bitcoins.db.AppConfig
|
||||
import org.bitcoins.testkit.chain.fixture.{ChainFixture, ChainFixtureTag}
|
||||
import org.bitcoins.testkit.chain.{ChainTestUtil, ChainUnitTest}
|
||||
import org.scalatest.FutureOutcome
|
||||
@ -15,18 +14,18 @@ class BitcoinPowTest extends ChainUnitTest {
|
||||
|
||||
override type FixtureParam = ChainFixture
|
||||
|
||||
override lazy implicit val appConfig: ChainAppConfig = mainnetAppConfig
|
||||
implicit override lazy val appConfig: ChainAppConfig = mainnetAppConfig
|
||||
|
||||
override def withFixture(test: OneArgAsyncTest): FutureOutcome =
|
||||
withChainFixture(test)
|
||||
|
||||
override implicit val system: ActorSystem = ActorSystem("BitcoinPowTest")
|
||||
implicit override val system: ActorSystem = ActorSystem("BitcoinPowTest")
|
||||
|
||||
behavior of "BitcoinPow"
|
||||
|
||||
it must "NOT calculate a POW change when one is not needed" inFixtured {
|
||||
case ChainFixture.Empty =>
|
||||
val blockHeaderDAO = BlockHeaderDAO(appConfig)
|
||||
val blockHeaderDAO = BlockHeaderDAO()
|
||||
val header1 = ChainTestUtil.ValidPOWChange.blockHeaderDb566494
|
||||
val header2 = ChainTestUtil.ValidPOWChange.blockHeaderDb566495
|
||||
|
||||
|
@ -16,7 +16,6 @@ import org.bitcoins.testkit.chain.{
|
||||
import org.scalatest.{Assertion, FutureOutcome}
|
||||
|
||||
import scala.concurrent.Future
|
||||
import org.bitcoins.db.AppConfig
|
||||
import org.bitcoins.chain.config.ChainAppConfig
|
||||
import com.typesafe.config.ConfigFactory
|
||||
|
||||
@ -25,12 +24,12 @@ class TipValidationTest extends ChainUnitTest {
|
||||
override type FixtureParam = BlockHeaderDAO
|
||||
|
||||
// we're working with mainnet data
|
||||
override lazy implicit val appConfig: ChainAppConfig = mainnetAppConfig
|
||||
implicit override lazy val appConfig: ChainAppConfig = mainnetAppConfig
|
||||
|
||||
override def withFixture(test: OneArgAsyncTest): FutureOutcome =
|
||||
withBlockHeaderDAO(test)
|
||||
|
||||
override implicit val system: ActorSystem = ActorSystem("TipValidationTest")
|
||||
implicit override val system: ActorSystem = ActorSystem("TipValidationTest")
|
||||
|
||||
behavior of "TipValidation"
|
||||
|
||||
|
@ -1,18 +1,18 @@
|
||||
package org.bitcoins.chain.api
|
||||
|
||||
import org.bitcoins.db._
|
||||
import org.bitcoins.chain.models.BlockHeaderDb
|
||||
import org.bitcoins.core.crypto.DoubleSha256DigestBE
|
||||
import org.bitcoins.core.protocol.blockchain.BlockHeader
|
||||
|
||||
import scala.concurrent.{ExecutionContext, Future}
|
||||
import org.bitcoins.chain.config.ChainAppConfig
|
||||
|
||||
/**
|
||||
* Entry api to the chain project for adding new things to our blockchain
|
||||
*/
|
||||
trait ChainApi {
|
||||
|
||||
def chainConfig: AppConfig
|
||||
def chainConfig: ChainAppConfig
|
||||
|
||||
/**
|
||||
* Adds a block header to our chain project
|
||||
|
@ -54,7 +54,7 @@ object Blockchain extends BitcoinSLogger {
|
||||
val nested: Vector[Future[BlockchainUpdate]] = blockchains.map {
|
||||
blockchain =>
|
||||
val tip = blockchain.tip
|
||||
logger.info(
|
||||
logger.debug(
|
||||
s"Attempting to add new tip=${header.hashBE.hex} with prevhash=${header.previousBlockHashBE.hex} to chain with current tips=${tip.hashBE.hex}")
|
||||
val tipResultF = TipValidation.checkNewTip(newPotentialTip = header,
|
||||
currentTip = tip,
|
||||
|
@ -14,23 +14,29 @@ import scala.util.Failure
|
||||
|
||||
case class ChainAppConfig(val confs: Config*) extends AppConfig {
|
||||
override protected val configOverrides: List[Config] = confs.toList
|
||||
override protected val moduleConfigName: String = "chain.conf"
|
||||
override protected val moduleName: String = "chain"
|
||||
override protected type ConfigType = ChainAppConfig
|
||||
override protected def newConfigOfType(
|
||||
configs: List[Config]): ChainAppConfig = ChainAppConfig(configs: _*)
|
||||
|
||||
/**
|
||||
* Checks whether or not the chain project is initialized by
|
||||
* trying to read the genesis block header from our block
|
||||
* header table
|
||||
*/
|
||||
def isInitialized()(implicit ec: ExecutionContext): Future[Boolean] = {
|
||||
val bhDAO = BlockHeaderDAO(this)
|
||||
val bhDAO =
|
||||
BlockHeaderDAO()(ec = implicitly[ExecutionContext], appConfig = this)
|
||||
val p = Promise[Boolean]()
|
||||
val isDefinedOptF = {
|
||||
bhDAO.read(chain.genesisBlock.blockHeader.hashBE).map(_.isDefined)
|
||||
}
|
||||
isDefinedOptF.onComplete {
|
||||
case Success(bool) =>
|
||||
logger.info(s"Chain project is initialized")
|
||||
logger.debug(s"Chain project is initialized")
|
||||
p.success(bool)
|
||||
case Failure(err) =>
|
||||
logger.info(s"Failed to init chain app err=${err.getMessage}")
|
||||
logger.info(s"Chain project is not initialized")
|
||||
p.success(false)
|
||||
}
|
||||
|
||||
@ -41,8 +47,7 @@ case class ChainAppConfig(val confs: Config*) extends AppConfig {
|
||||
* This creates the necessary tables for the chain project
|
||||
* and inserts preliminary data like the genesis block header
|
||||
* */
|
||||
def initialize(implicit ec: ExecutionContext): Future[Unit] = {
|
||||
val blockHeaderDAO = BlockHeaderDAO(this)
|
||||
override def initialize()(implicit ec: ExecutionContext): Future[Unit] = {
|
||||
val isInitF = isInitialized()
|
||||
isInitF.flatMap { isInit =>
|
||||
if (isInit) {
|
||||
@ -53,9 +58,14 @@ case class ChainAppConfig(val confs: Config*) extends AppConfig {
|
||||
BlockHeaderDbHelper.fromBlockHeader(height = 0,
|
||||
bh =
|
||||
chain.genesisBlock.blockHeader)
|
||||
val blockHeaderDAO =
|
||||
BlockHeaderDAO()(ec = implicitly[ExecutionContext], appConfig = this)
|
||||
val bhCreatedF =
|
||||
createdF.flatMap(_ => blockHeaderDAO.create(genesisHeader))
|
||||
bhCreatedF.flatMap(_ => FutureUtil.unit)
|
||||
bhCreatedF.flatMap { _ =>
|
||||
logger.info(s"Inserted genesis block header into DB")
|
||||
FutureUtil.unit
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -6,6 +6,8 @@ import org.bitcoins.db.{DbManagement}
|
||||
import slick.lifted.TableQuery
|
||||
|
||||
import scala.concurrent.Future
|
||||
import scala.concurrent.ExecutionContext
|
||||
import org.bitcoins.chain.config.ChainAppConfig
|
||||
|
||||
/**
|
||||
* Responsible for creating and destroying database
|
||||
@ -19,11 +21,12 @@ sealed abstract class ChainDbManagement extends DbManagement {
|
||||
override val allTables = List(chainTable)
|
||||
|
||||
def createHeaderTable(createIfNotExists: Boolean = true)(
|
||||
implicit config: AppConfig): Future[Unit] = {
|
||||
implicit config: ChainAppConfig,
|
||||
ec: ExecutionContext): Future[Unit] = {
|
||||
createTable(chainTable, createIfNotExists)
|
||||
}
|
||||
|
||||
def dropHeaderTable()(implicit config: AppConfig): Future[Unit] = {
|
||||
def dropHeaderTable()(implicit config: ChainAppConfig): Future[Unit] = {
|
||||
dropTable(chainTable)
|
||||
}
|
||||
}
|
||||
|
@ -15,8 +15,9 @@ import scala.concurrent.{ExecutionContext, Future}
|
||||
* to [[org.bitcoins.core.protocol.blockchain.BlockHeader]]s in
|
||||
* our chain project
|
||||
*/
|
||||
case class BlockHeaderDAO(appConfig: ChainAppConfig)(
|
||||
implicit override val ec: ExecutionContext)
|
||||
case class BlockHeaderDAO()(
|
||||
implicit override val ec: ExecutionContext,
|
||||
override val appConfig: ChainAppConfig)
|
||||
extends CRUD[BlockHeaderDb, DoubleSha256DigestBE] {
|
||||
|
||||
import org.bitcoins.db.DbCommonsColumnMappers._
|
||||
|
@ -24,6 +24,8 @@ import java.nio.file.Files
|
||||
|
||||
import scala.util.Properties
|
||||
import scala.util.matching.Regex
|
||||
import scala.concurrent.ExecutionContext
|
||||
import scala.concurrent.Future
|
||||
|
||||
/**
|
||||
* Everything needed to configure functionality
|
||||
@ -34,6 +36,17 @@ import scala.util.matching.Regex
|
||||
*/
|
||||
abstract class AppConfig extends BitcoinSLogger {
|
||||
|
||||
/**
|
||||
* Initializes this project.
|
||||
* After this future resolves, all operations should be
|
||||
* able to be performed correctly.
|
||||
*
|
||||
* Initializing may include creating database tables,
|
||||
* making directories or files needed latern or
|
||||
* something else entirely.
|
||||
*/
|
||||
def initialize()(implicit ec: ExecutionContext): Future[Unit]
|
||||
|
||||
/** Sub members of AppConfig should override this type with
|
||||
* the type of themselves, ensuring `withOverrides` return
|
||||
* the correct type
|
||||
@ -92,24 +105,18 @@ abstract class AppConfig extends BitcoinSLogger {
|
||||
}
|
||||
|
||||
/**
|
||||
* Name of module specific
|
||||
* config file. `wallet.conf`, `node.conf`,
|
||||
* etc.
|
||||
* Name of the module. `chain`, `wallet`, `node` etc.
|
||||
*/
|
||||
protected def moduleConfigName: String
|
||||
protected def moduleName: String
|
||||
|
||||
/**
|
||||
* The configuration details for connecting/using the database for our projects
|
||||
* that require datbase connections
|
||||
*/
|
||||
lazy val dbConfig: DatabaseConfig[SQLiteProfile] = {
|
||||
//if we don't pass specific class, non-deterministic
|
||||
//errors around the loaded configuration depending
|
||||
//on the state of the default classLoader
|
||||
//https://github.com/lightbend/config#debugging-your-configuration
|
||||
val dbConfig = {
|
||||
Try {
|
||||
DatabaseConfig.forConfig[SQLiteProfile](path = "database", config)
|
||||
DatabaseConfig.forConfig[SQLiteProfile](path = moduleName, config)
|
||||
} match {
|
||||
case Success(value) =>
|
||||
value
|
||||
@ -120,7 +127,7 @@ abstract class AppConfig extends BitcoinSLogger {
|
||||
}
|
||||
}
|
||||
|
||||
logger.trace(s"Resolved DB config: ${dbConfig.config}")
|
||||
logger.debug(s"Resolved DB config: ${dbConfig.config}")
|
||||
|
||||
val _ = createDbFileIfDNE()
|
||||
|
||||
@ -133,20 +140,33 @@ abstract class AppConfig extends BitcoinSLogger {
|
||||
}
|
||||
|
||||
/** The path where our DB is located */
|
||||
// todo: what happens when to this if we
|
||||
// todo: what happens to this if we
|
||||
// dont use SQLite?
|
||||
lazy val dbPath: Path = {
|
||||
val pathStr = config.getString("database.dbPath")
|
||||
val pathStr = config.getString(s"$moduleName.db.path")
|
||||
val path = Paths.get(pathStr)
|
||||
logger.debug(s"DB path: $path")
|
||||
path
|
||||
}
|
||||
|
||||
/** The name of our database */
|
||||
// todo: what happens to this if we
|
||||
// dont use SQLite?
|
||||
lazy val dbName: String = {
|
||||
config.getString(s"$moduleName.db.name")
|
||||
}
|
||||
|
||||
private def createDbFileIfDNE(): Unit = {
|
||||
//should add a check in here that we are using sqlite
|
||||
if (!Files.exists(dbPath)) {
|
||||
logger.debug(s"Creating database directory=$dbPath")
|
||||
val _ = Files.createDirectories(dbPath)
|
||||
val _ = {
|
||||
logger.debug(s"Creating database directory=$dbPath")
|
||||
Files.createDirectories(dbPath)
|
||||
val dbFilePath = dbPath.resolve(dbName)
|
||||
logger.debug(s"Creating database file=$dbFilePath")
|
||||
Files.createFile(dbFilePath)
|
||||
}
|
||||
|
||||
()
|
||||
}
|
||||
}
|
||||
@ -171,13 +191,7 @@ abstract class AppConfig extends BitcoinSLogger {
|
||||
* The underlying config that we derive the
|
||||
* rest of the fields in this class from
|
||||
*/
|
||||
protected lazy val config: Config = {
|
||||
val moduleConfig =
|
||||
ConfigFactory.load(moduleConfigName)
|
||||
|
||||
logger.debug(
|
||||
s"Module config: ${moduleConfig.getConfig("bitcoin-s").asReadableJson}")
|
||||
|
||||
private[bitcoins] lazy val config: Config = {
|
||||
// `load` tries to resolve substitions,
|
||||
// `parseResources` does not
|
||||
val dbConfig = ConfigFactory
|
||||
@ -186,9 +200,14 @@ abstract class AppConfig extends BitcoinSLogger {
|
||||
logger.trace(
|
||||
s"DB config: ${dbConfig.getConfig("bitcoin-s").asReadableJson}")
|
||||
|
||||
val classPathConfig =
|
||||
ConfigFactory
|
||||
.load()
|
||||
// we want to NOT resolve substitutions in the configuraton until the user
|
||||
// provided configs also has been loaded. .parseResources() does not do that
|
||||
// whereas .load() does
|
||||
val classPathConfig = {
|
||||
val applicationConf = ConfigFactory.parseResources("application.conf")
|
||||
val referenceConf = ConfigFactory.parseResources("reference.conf")
|
||||
applicationConf.withFallback(referenceConf)
|
||||
}
|
||||
|
||||
logger.trace(
|
||||
s"Classpath config: ${classPathConfig.getConfig("bitcoin-s").asReadableJson}")
|
||||
@ -196,7 +215,6 @@ abstract class AppConfig extends BitcoinSLogger {
|
||||
// loads reference.conf as well as application.conf,
|
||||
// if the user has made one
|
||||
val unresolvedConfig = classPathConfig
|
||||
.withFallback(moduleConfig)
|
||||
.withFallback(dbConfig)
|
||||
|
||||
logger.trace(s"Unresolved bitcoin-s config:")
|
||||
@ -219,6 +237,7 @@ abstract class AppConfig extends BitcoinSLogger {
|
||||
// in this order
|
||||
overrides.withFallback(unresolvedConfig)
|
||||
} else {
|
||||
logger.trace(s"No user-provided overrides")
|
||||
unresolvedConfig
|
||||
}
|
||||
|
||||
|
@ -30,7 +30,10 @@ abstract class CRUD[T, PrimaryKeyType] extends BitcoinSLogger {
|
||||
* @param t - the record to be inserted
|
||||
* @return the inserted record
|
||||
*/
|
||||
def create(t: T): Future[T] = createAll(Vector(t)).map(_.head)
|
||||
def create(t: T): Future[T] = {
|
||||
logger.trace(s"Writing $t to DB with config: ${appConfig.config}")
|
||||
createAll(Vector(t)).map(_.head)
|
||||
}
|
||||
|
||||
def createAll(ts: Vector[T]): Future[Vector[T]]
|
||||
|
||||
@ -41,6 +44,7 @@ abstract class CRUD[T, PrimaryKeyType] extends BitcoinSLogger {
|
||||
* @return Option[T] - the record if found, else none
|
||||
*/
|
||||
def read(id: PrimaryKeyType): Future[Option[T]] = {
|
||||
logger.trace(s"Reading from DB with config: ${appConfig.config}")
|
||||
val query = findByPrimaryKey(id)
|
||||
val rows: Future[Seq[T]] = database.run(query.result)
|
||||
rows.map(_.headOption)
|
||||
|
@ -16,29 +16,38 @@ abstract class DbManagement extends BitcoinSLogger {
|
||||
db.run(query)
|
||||
}
|
||||
|
||||
/** Lists all tables in the given database */
|
||||
def listTables(db: SafeDatabase): Future[Vector[SQLiteTableInfo]] =
|
||||
listTables(db.config.database)
|
||||
|
||||
def createAll()(
|
||||
implicit config: AppConfig,
|
||||
ec: ExecutionContext): Future[List[Unit]] = {
|
||||
Future.sequence(allTables.map(createTable(_)))
|
||||
ec: ExecutionContext): Future[Unit] = {
|
||||
Future.sequence(allTables.map(createTable(_))).map(_ => ())
|
||||
}
|
||||
|
||||
def dropAll()(
|
||||
implicit config: AppConfig,
|
||||
ec: ExecutionContext): Future[List[Unit]] = {
|
||||
Future.sequence(allTables.reverse.map(dropTable(_)))
|
||||
ec: ExecutionContext): Future[Unit] = {
|
||||
Future.sequence(allTables.reverse.map(dropTable(_))).map(_ => ())
|
||||
}
|
||||
|
||||
def createTable(
|
||||
table: TableQuery[_ <: Table[_]],
|
||||
createIfNotExists: Boolean = true)(
|
||||
implicit config: AppConfig): Future[Unit] = {
|
||||
implicit config: AppConfig,
|
||||
ec: ExecutionContext): Future[Unit] = {
|
||||
val tableName = table.baseTableRow.tableName
|
||||
logger.debug(
|
||||
s"Creating table $tableName with DB config: ${config.dbConfig.config} ")
|
||||
|
||||
import config.database
|
||||
val result = if (createIfNotExists) {
|
||||
database.run(table.schema.createIfNotExists)
|
||||
val query = if (createIfNotExists) {
|
||||
table.schema.createIfNotExists
|
||||
} else {
|
||||
database.run(table.schema.create)
|
||||
table.schema.create
|
||||
}
|
||||
result
|
||||
database.run(query).map(_ => logger.debug(s"Created table $tableName"))
|
||||
}
|
||||
|
||||
def dropTable(
|
||||
|
@ -15,6 +15,7 @@ import org.scalatest._
|
||||
|
||||
import scala.concurrent.Future
|
||||
import scala.concurrent.duration.DurationInt
|
||||
import org.bitcoins.testkit.BitcoinSAppConfig
|
||||
|
||||
/**
|
||||
* Created by chris on 6/7/16.
|
||||
@ -28,21 +29,22 @@ class ClientTest
|
||||
implicit val system = ActorSystem(
|
||||
s"Client-Test-System-${System.currentTimeMillis()}")
|
||||
|
||||
private val appConfig = NodeTestUtil.nodeAppConfig
|
||||
implicit private val config: BitcoinSAppConfig =
|
||||
BitcoinSAppConfig.getTestConfig()
|
||||
implicit private val chainConf = config.chainConf
|
||||
implicit private val nodeConf = config.nodeConf
|
||||
|
||||
private val chainAppConfig = ChainAppConfig()
|
||||
implicit val np = config.chainConf.network
|
||||
|
||||
implicit val np = appConfig.network
|
||||
lazy val bitcoindRpcF = BitcoindRpcTestUtil.startedBitcoindRpcClient()
|
||||
|
||||
val bitcoindRpcF = BitcoindRpcTestUtil.startedBitcoindRpcClient()
|
||||
|
||||
val bitcoindPeerF = bitcoindRpcF.map { bitcoind =>
|
||||
lazy val bitcoindPeerF = bitcoindRpcF.map { bitcoind =>
|
||||
NodeTestUtil.getBitcoindPeer(bitcoind)
|
||||
}
|
||||
|
||||
val bitcoindRpc2F = BitcoindRpcTestUtil.startedBitcoindRpcClient()
|
||||
lazy val bitcoindRpc2F = BitcoindRpcTestUtil.startedBitcoindRpcClient()
|
||||
|
||||
val bitcoindPeer2F = bitcoindRpcF.map { bitcoind =>
|
||||
lazy val bitcoindPeer2F = bitcoindRpcF.map { bitcoind =>
|
||||
NodeTestUtil.getBitcoindPeer(bitcoind)
|
||||
}
|
||||
|
||||
@ -75,9 +77,7 @@ class ClientTest
|
||||
val probe = TestProbe()
|
||||
val remote = peer.socket
|
||||
val peerMessageReceiver =
|
||||
PeerMessageReceiver(state = Preconnection,
|
||||
nodeAppConfig = appConfig,
|
||||
chainAppConfig = chainAppConfig)
|
||||
PeerMessageReceiver(state = Preconnection)
|
||||
val client =
|
||||
TestActorRef(Client.props(peer, peerMessageReceiver), probe.ref)
|
||||
|
||||
|
@ -24,7 +24,7 @@ case class SpvNode(peer: Peer, chainApi: ChainApi)(
|
||||
import system.dispatcher
|
||||
|
||||
private val peerMsgRecv =
|
||||
PeerMessageReceiver.newReceiver(nodeAppConfig, chainAppConfig)
|
||||
PeerMessageReceiver.newReceiver
|
||||
|
||||
private val client: Client =
|
||||
Client(context = system, peer = peer, peerMessageReceiver = peerMsgRecv)
|
||||
@ -35,21 +35,24 @@ case class SpvNode(peer: Peer, chainApi: ChainApi)(
|
||||
|
||||
/** Starts our spv node */
|
||||
def start(): Future[SpvNode] = {
|
||||
peerMsgSender.connect()
|
||||
for {
|
||||
_ <- nodeAppConfig.initialize()
|
||||
node <- {
|
||||
peerMsgSender.connect()
|
||||
|
||||
val isInitializedF =
|
||||
AsyncUtil.retryUntilSatisfied(peerMsgRecv.isInitialized)
|
||||
val isInitializedF =
|
||||
AsyncUtil.retryUntilSatisfied(peerMsgRecv.isInitialized)
|
||||
|
||||
isInitializedF.map { _ =>
|
||||
logger.info(s"Our peer=${peer} has been initialized")
|
||||
}
|
||||
isInitializedF.failed.foreach(err =>
|
||||
logger.error(s"Failed to conenct with peer=$peer with err=${err}"))
|
||||
|
||||
isInitializedF.failed.foreach { err =>
|
||||
logger.error(s"Failed to conenct with peer=$peer with err=${err}")
|
||||
isInitializedF.map { _ =>
|
||||
logger.info(s"Our peer=${peer} has been initialized")
|
||||
this
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
isInitializedF.map(_ => this)
|
||||
} yield node
|
||||
}
|
||||
|
||||
/** Stops our spv node */
|
||||
|
@ -2,12 +2,32 @@ package org.bitcoins.node.config
|
||||
|
||||
import com.typesafe.config.Config
|
||||
import org.bitcoins.db.AppConfig
|
||||
import scala.concurrent.ExecutionContext
|
||||
import scala.concurrent.Future
|
||||
import org.bitcoins.node.db.NodeDbManagement
|
||||
import scala.util.Failure
|
||||
import scala.util.Success
|
||||
|
||||
case class NodeAppConfig(confs: Config*) extends AppConfig {
|
||||
override val configOverrides: List[Config] = confs.toList
|
||||
override protected def moduleConfigName: String = "node.conf"
|
||||
override protected def moduleName: String = "node"
|
||||
override protected type ConfigType = NodeAppConfig
|
||||
override protected def newConfigOfType(configs: List[Config]): NodeAppConfig =
|
||||
NodeAppConfig(configs: _*)
|
||||
|
||||
/**
|
||||
* Ensures correct tables and other required information is in
|
||||
* place for our node.
|
||||
*/
|
||||
override def initialize()(implicit ec: ExecutionContext): Future[Unit] = {
|
||||
logger.debug(s"Initializing node setup")
|
||||
val initF = NodeDbManagement.createAll()(config = this, ec)
|
||||
initF.onComplete {
|
||||
case Failure(err) =>
|
||||
logger.error(s"Error when initializing node: ${err.getMessage}")
|
||||
case Success(_) =>
|
||||
logger.debug(s"Initializing node setup: done")
|
||||
}
|
||||
initF
|
||||
}
|
||||
}
|
||||
|
@ -1,22 +1,14 @@
|
||||
package org.bitcoins.node.constant
|
||||
|
||||
import akka.actor.ActorSystem
|
||||
import org.bitcoins.core.config.{MainNet, NetworkParameters, RegTest, TestNet3}
|
||||
import org.bitcoins.core.protocol.blockchain.{
|
||||
ChainParams,
|
||||
MainNetChainParams,
|
||||
RegTestNetChainParams,
|
||||
TestNetChainParams
|
||||
}
|
||||
import org.bitcoins.node.config.NodeAppConfig
|
||||
import org.bitcoins.node.versions.ProtocolVersion70013
|
||||
import slick.jdbc.PostgresProfile.api._
|
||||
|
||||
import scala.concurrent.duration.DurationInt
|
||||
import com.typesafe.config.ConfigFactory
|
||||
|
||||
case object Constants {
|
||||
lazy val actorSystem = ActorSystem("BitcoinSpvNode")
|
||||
def networkParameters: NetworkParameters = appConfig.network
|
||||
val emptyConfig = ConfigFactory.parseString("")
|
||||
lazy val actorSystem = ActorSystem("BitcoinSpvNode", emptyConfig)
|
||||
def version = ProtocolVersion70013
|
||||
|
||||
def timeout = 5.seconds
|
||||
@ -25,19 +17,4 @@ case object Constants {
|
||||
/** This is the file where our block headers are stored */
|
||||
def blockHeaderFile = new java.io.File("src/main/resources/block_headers.dat")
|
||||
|
||||
lazy val appConfig: NodeAppConfig = NodeAppConfig()
|
||||
|
||||
/** The [[ChainParams]] for the blockchain we are currently connected to */
|
||||
def chainParams: ChainParams = networkParameters match {
|
||||
case MainNet => MainNetChainParams
|
||||
case TestNet3 => TestNetChainParams
|
||||
case RegTest => RegTestNetChainParams
|
||||
}
|
||||
|
||||
/** This is the database we are currently bound to, this
|
||||
* should be the database that stores information corresponding to the network
|
||||
* we are currently connected to inside of the [[networkParameters]] function
|
||||
* @return
|
||||
*/
|
||||
def database: Database = appConfig.database
|
||||
}
|
||||
|
@ -7,7 +7,7 @@ import akka.util.ByteString
|
||||
import org.bitcoins.core.config.NetworkParameters
|
||||
import org.bitcoins.core.util.BitcoinSLogger
|
||||
import org.bitcoins.node.NetworkMessage
|
||||
import org.bitcoins.node.constant.Constants
|
||||
import org.bitcoins.node.config.NodeAppConfig
|
||||
import org.bitcoins.node.messages.NetworkPayload
|
||||
import org.bitcoins.node.models.Peer
|
||||
import org.bitcoins.node.networking.peer.PeerMessageReceiver
|
||||
@ -41,6 +41,8 @@ import scodec.bits.ByteVector
|
||||
*/
|
||||
sealed abstract class ClientActor extends Actor with BitcoinSLogger {
|
||||
|
||||
val config: NodeAppConfig
|
||||
|
||||
def peer: Peer
|
||||
|
||||
/** The place we send messages that we successfully parsed from our
|
||||
@ -61,7 +63,7 @@ sealed abstract class ClientActor extends Actor with BitcoinSLogger {
|
||||
* i.e. [[org.bitcoins.core.config.MainNet]] or [[org.bitcoins.core.config.TestNet3]]
|
||||
* @return
|
||||
*/
|
||||
def network: NetworkParameters = Constants.networkParameters
|
||||
def network: NetworkParameters = config.network
|
||||
|
||||
/**
|
||||
* This actor signifies the node we are connected to on the p2p network
|
||||
@ -223,16 +225,20 @@ case class Client(actor: ActorRef, peer: Peer)
|
||||
object Client {
|
||||
private case class ClientActorImpl(
|
||||
peer: Peer,
|
||||
peerMsgHandlerReceiver: PeerMessageReceiver)
|
||||
extends ClientActor
|
||||
peerMsgHandlerReceiver: PeerMessageReceiver)(
|
||||
implicit override val config: NodeAppConfig
|
||||
) extends ClientActor
|
||||
|
||||
def props(peer: Peer, peerMsgHandlerReceiver: PeerMessageReceiver): Props =
|
||||
Props(classOf[ClientActorImpl], peer, peerMsgHandlerReceiver)
|
||||
def props(peer: Peer, peerMsgHandlerReceiver: PeerMessageReceiver)(
|
||||
implicit config: NodeAppConfig
|
||||
): Props =
|
||||
Props(classOf[ClientActorImpl], peer, peerMsgHandlerReceiver, config)
|
||||
|
||||
def apply(
|
||||
context: ActorRefFactory,
|
||||
peer: Peer,
|
||||
peerMessageReceiver: PeerMessageReceiver): Client = {
|
||||
peerMessageReceiver: PeerMessageReceiver)(
|
||||
implicit config: NodeAppConfig): Client = {
|
||||
val actorRef = context.actorOf(
|
||||
props(peer = peer, peerMsgHandlerReceiver = peerMessageReceiver),
|
||||
BitcoinSpvNodeUtil.createActorName(this.getClass))
|
||||
|
@ -17,11 +17,12 @@ import scala.concurrent.{ExecutionContext, Future}
|
||||
* that a peer to sent to us on the p2p network, for instance, if we a receive a
|
||||
* [[HeadersMessage]] we should store those headers in our database
|
||||
*/
|
||||
class DataMessageHandler(appConfig: ChainAppConfig)(
|
||||
implicit ec: ExecutionContext)
|
||||
class DataMessageHandler()(
|
||||
implicit ec: ExecutionContext,
|
||||
appConfig: ChainAppConfig)
|
||||
extends BitcoinSLogger {
|
||||
|
||||
private val blockHeaderDAO: BlockHeaderDAO = BlockHeaderDAO(appConfig)
|
||||
private val blockHeaderDAO: BlockHeaderDAO = BlockHeaderDAO()
|
||||
|
||||
def handleDataPayload(
|
||||
payload: DataPayload,
|
||||
|
@ -30,6 +30,9 @@ class PeerMessageReceiver(
|
||||
chainAppConfig: ChainAppConfig)(implicit ref: ActorRefFactory)
|
||||
extends BitcoinSLogger {
|
||||
|
||||
implicit private val nodeConfig = nodeAppConfig
|
||||
implicit private val chainConfig = chainAppConfig
|
||||
|
||||
import ref.dispatcher
|
||||
|
||||
//TODO: Really bad to just modify this internal state
|
||||
@ -136,7 +139,7 @@ class PeerMessageReceiver(
|
||||
private def handleDataPayload(
|
||||
payload: DataPayload,
|
||||
sender: PeerMessageSender): Unit = {
|
||||
val dataMsgHandler = new DataMessageHandler(chainAppConfig)
|
||||
val dataMsgHandler = new DataMessageHandler()
|
||||
//else it means we are receiving this data payload from a peer,
|
||||
//we need to handle it
|
||||
dataMsgHandler.handleDataPayload(payload, sender)
|
||||
@ -227,16 +230,18 @@ object PeerMessageReceiver {
|
||||
case class NetworkMessageReceived(msg: NetworkMessage, client: Client)
|
||||
extends PeerMessageReceiverMsg
|
||||
|
||||
def apply(
|
||||
state: PeerMessageReceiverState,
|
||||
def apply(state: PeerMessageReceiverState)(
|
||||
implicit ref: ActorRefFactory,
|
||||
nodeAppConfig: NodeAppConfig,
|
||||
chainAppConfig: ChainAppConfig)(
|
||||
implicit ref: ActorRefFactory): PeerMessageReceiver = {
|
||||
chainAppConfig: ChainAppConfig
|
||||
): PeerMessageReceiver = {
|
||||
new PeerMessageReceiver(state, nodeAppConfig, chainAppConfig)(ref)
|
||||
}
|
||||
|
||||
def newReceiver(nodeAppConfig: NodeAppConfig, chainAppConfig: ChainAppConfig)(
|
||||
implicit ref: ActorRefFactory): PeerMessageReceiver = {
|
||||
def newReceiver(
|
||||
implicit nodeAppConfig: NodeAppConfig,
|
||||
chainAppConfig: ChainAppConfig,
|
||||
ref: ActorRefFactory): PeerMessageReceiver = {
|
||||
new PeerMessageReceiver(state = PeerMessageReceiverState.fresh(),
|
||||
nodeAppConfig,
|
||||
chainAppConfig)(ref)
|
||||
|
@ -210,7 +210,7 @@ object Deps {
|
||||
Test.ammonite
|
||||
)
|
||||
|
||||
val doc = List(
|
||||
val docs = List(
|
||||
Compile.ammonite,
|
||||
Compile.logback,
|
||||
Test.scalaTest,
|
||||
|
@ -0,0 +1,103 @@
|
||||
package org.bitcoins.testkit
|
||||
|
||||
import com.typesafe.config.Config
|
||||
import org.bitcoins.wallet.config.WalletAppConfig
|
||||
import org.bitcoins.node.config.NodeAppConfig
|
||||
import org.bitcoins.chain.config.ChainAppConfig
|
||||
import scala.concurrent.ExecutionContext
|
||||
import scala.concurrent.Future
|
||||
import java.nio.file.Files
|
||||
import com.typesafe.config.ConfigFactory
|
||||
|
||||
/**
|
||||
* A unified config class for all submodules of Bitcoin-S
|
||||
* that accepts configuration. Thanks to implicit definitions
|
||||
* in this case class' companion object an instance
|
||||
* of this class can be passed in anywhere a wallet,
|
||||
* chain or node config is required.
|
||||
*/
|
||||
case class BitcoinSAppConfig(confs: Config*) {
|
||||
val walletConf = WalletAppConfig(confs: _*)
|
||||
val nodeConf = NodeAppConfig(confs: _*)
|
||||
val chainConf = ChainAppConfig(confs: _*)
|
||||
|
||||
/** Initializes the wallet, node and chain projects */
|
||||
def initialize()(implicit ec: ExecutionContext): Future[Unit] = {
|
||||
val futures = List(walletConf.initialize(),
|
||||
nodeConf.initialize(),
|
||||
chainConf.initialize())
|
||||
|
||||
Future.sequence(futures).map(_ => ())
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implicit conversions that allow a unified configuration
|
||||
* to be passed in wherever a specializes one is required
|
||||
*/
|
||||
object BitcoinSAppConfig {
|
||||
import scala.language.implicitConversions
|
||||
|
||||
/** Converts the given implicit config to a wallet config */
|
||||
implicit def implicitToWalletConf(
|
||||
implicit conf: BitcoinSAppConfig): WalletAppConfig =
|
||||
conf.walletConf
|
||||
|
||||
/** Converts the given config to a wallet config */
|
||||
implicit def toWalletConf(conf: BitcoinSAppConfig): WalletAppConfig =
|
||||
conf.walletConf
|
||||
|
||||
/** Converts the given implicit config to a chain config */
|
||||
implicit def implicitToChainConf(
|
||||
implicit conf: BitcoinSAppConfig): ChainAppConfig =
|
||||
conf.chainConf
|
||||
|
||||
/** Converts the given config to a chain config */
|
||||
implicit def toChainConf(conf: BitcoinSAppConfig): ChainAppConfig =
|
||||
conf.chainConf
|
||||
|
||||
/** Converts the given implicit config to a node config */
|
||||
implicit def implicitToNodeConf(
|
||||
implicit conf: BitcoinSAppConfig): NodeAppConfig =
|
||||
conf.nodeConf
|
||||
|
||||
/** Converts the given config to a node config */
|
||||
implicit def toNodeConf(conf: BitcoinSAppConfig): NodeAppConfig =
|
||||
conf.nodeConf
|
||||
|
||||
/**
|
||||
* App configuration suitable for test purposes:
|
||||
|
||||
* 1) Data directory is set to user temp directory
|
||||
* 2) All databases are in-memory
|
||||
*/
|
||||
def getTestConfig(config: Config*) = {
|
||||
val tmpDir = Files.createTempDirectory("bitcoin-s-")
|
||||
val confStr = s"""
|
||||
| bitcoin-s {
|
||||
| datadir = $tmpDir
|
||||
|
|
||||
| wallet.db {
|
||||
| url = "jdbc:sqlite:file::memory:?cache=shared"
|
||||
| connectionPool = disabled
|
||||
| keepAliveConnection = true
|
||||
| }
|
||||
| node.db {
|
||||
| url = "jdbc:sqlite:file::memory:?cache=shared"
|
||||
| connectionPool = disabled
|
||||
| keepAliveConnection = true
|
||||
| }
|
||||
| chain.db {
|
||||
| url = "jdbc:sqlite:file::memory:?cache=shared"
|
||||
| connectionPool = disabled
|
||||
| keepAliveConnection = true
|
||||
| }
|
||||
| }
|
||||
|
|
||||
|""".stripMargin
|
||||
val conf = ConfigFactory.parseString(confStr)
|
||||
val allConfs = conf +: config
|
||||
BitcoinSAppConfig(allConfs: _*)
|
||||
}
|
||||
|
||||
}
|
@ -14,13 +14,13 @@ import org.bitcoins.chain.models.{
|
||||
}
|
||||
import org.bitcoins.core.protocol.blockchain.{Block, BlockHeader, ChainParams}
|
||||
import org.bitcoins.core.util.BitcoinSLogger
|
||||
import org.bitcoins.db.AppConfig
|
||||
import org.bitcoins.rpc.client.common.BitcoindRpcClient
|
||||
import org.bitcoins.testkit.chain
|
||||
import org.bitcoins.testkit.chain.fixture._
|
||||
import org.bitcoins.testkit.fixtures.BitcoinSFixture
|
||||
import org.bitcoins.testkit.rpc.BitcoindRpcTestUtil
|
||||
import org.bitcoins.zmq.ZMQSubscriber
|
||||
import org.bitcoins.testkit.BitcoinSAppConfig
|
||||
import org.scalatest._
|
||||
import play.api.libs.json.{JsError, JsSuccess, Json}
|
||||
import scodec.bits.ByteVector
|
||||
@ -28,6 +28,7 @@ import scodec.bits.ByteVector
|
||||
import scala.annotation.tailrec
|
||||
import scala.concurrent.duration._
|
||||
import scala.concurrent.{ExecutionContext, Future}
|
||||
import org.bitcoins.db.AppConfig
|
||||
|
||||
trait ChainUnitTest
|
||||
extends org.scalatest.fixture.AsyncFlatSpec
|
||||
@ -44,16 +45,16 @@ trait ChainUnitTest
|
||||
|
||||
implicit lazy val chainParam: ChainParams = appConfig.chain
|
||||
|
||||
implicit lazy val appConfig: ChainAppConfig = ChainAppConfig()
|
||||
implicit lazy val appConfig: ChainAppConfig =
|
||||
BitcoinSAppConfig.getTestConfig()
|
||||
|
||||
/**
|
||||
* Behaves exactly like the default conf, execpt
|
||||
* network is set to mainnet
|
||||
*/
|
||||
lazy val mainnetAppConfig: ChainAppConfig = {
|
||||
val defaultConfig = ChainAppConfig()
|
||||
val mainnetConf = ConfigFactory.parseString("bitcoin-s.network = mainnet")
|
||||
defaultConfig.withOverrides(mainnetConf)
|
||||
BitcoinSAppConfig.getTestConfig(mainnetConf)
|
||||
}
|
||||
|
||||
override def beforeAll(): Unit = {
|
||||
@ -378,7 +379,7 @@ object ChainUnitTest extends BitcoinSLogger {
|
||||
}
|
||||
}
|
||||
|
||||
def destroyHeaderTable()(implicit appConfig: AppConfig): Future[Unit] = {
|
||||
def destroyHeaderTable()(implicit appConfig: ChainAppConfig): Future[Unit] = {
|
||||
ChainDbManagement.dropHeaderTable()
|
||||
}
|
||||
|
||||
@ -389,7 +390,8 @@ object ChainUnitTest extends BitcoinSLogger {
|
||||
|
||||
/** Creates the [[org.bitcoins.chain.models.BlockHeaderTable]] */
|
||||
private def setupHeaderTable()(
|
||||
implicit appConfig: AppConfig): Future[Unit] = {
|
||||
implicit appConfig: ChainAppConfig,
|
||||
ec: ExecutionContext): Future[Unit] = {
|
||||
ChainDbManagement.createHeaderTable(createIfNotExists = true)
|
||||
}
|
||||
|
||||
@ -411,7 +413,7 @@ object ChainUnitTest extends BitcoinSLogger {
|
||||
def makeChainHandler()(
|
||||
implicit appConfig: ChainAppConfig,
|
||||
ec: ExecutionContext): ChainHandler = {
|
||||
lazy val blockHeaderDAO = BlockHeaderDAO(appConfig)
|
||||
lazy val blockHeaderDAO = BlockHeaderDAO()
|
||||
|
||||
ChainHandler(blockHeaderDAO = blockHeaderDAO, appConfig)
|
||||
}
|
||||
|
@ -71,10 +71,9 @@ abstract class NodeTestUtil {
|
||||
)
|
||||
}
|
||||
|
||||
def nodeAppConfig: NodeAppConfig = NodeAppConfig()
|
||||
|
||||
def client(peer: Peer, peerMsgReceiver: PeerMessageReceiver)(
|
||||
implicit ref: ActorRefFactory): Client = {
|
||||
implicit ref: ActorRefFactory,
|
||||
conf: NodeAppConfig): Client = {
|
||||
Client.apply(ref, peer, peerMsgReceiver)
|
||||
}
|
||||
|
||||
|
@ -3,12 +3,10 @@ package org.bitcoins.testkit.node
|
||||
import java.net.InetSocketAddress
|
||||
|
||||
import akka.actor.ActorSystem
|
||||
import org.bitcoins.chain.config.ChainAppConfig
|
||||
import org.bitcoins.core.config.NetworkParameters
|
||||
import org.bitcoins.core.util.BitcoinSLogger
|
||||
import org.bitcoins.db.AppConfig
|
||||
import org.bitcoins.node.SpvNode
|
||||
import org.bitcoins.node.config.NodeAppConfig
|
||||
import org.bitcoins.node.models.Peer
|
||||
import org.bitcoins.node.networking.peer.{
|
||||
PeerHandler,
|
||||
@ -31,6 +29,9 @@ import org.scalatest.{
|
||||
import scala.concurrent.duration._
|
||||
import scala.concurrent.{ExecutionContext, Future}
|
||||
|
||||
import org.bitcoins.testkit.BitcoinSAppConfig
|
||||
import org.bitcoins.testkit.BitcoinSAppConfig._
|
||||
|
||||
trait NodeUnitTest
|
||||
extends BitcoinSFixture
|
||||
with MustMatchers
|
||||
@ -39,7 +40,7 @@ trait NodeUnitTest
|
||||
with BeforeAndAfterAll {
|
||||
|
||||
override def beforeAll(): Unit = {
|
||||
AppConfig.throwIfDefaultDatadir(nodeAppConfig)
|
||||
AppConfig.throwIfDefaultDatadir(config.nodeConf)
|
||||
}
|
||||
|
||||
override def afterAll(): Unit = {
|
||||
@ -56,9 +57,11 @@ trait NodeUnitTest
|
||||
|
||||
val timeout: FiniteDuration = 10.seconds
|
||||
|
||||
implicit lazy val nodeAppConfig: NodeAppConfig = NodeAppConfig()
|
||||
implicit lazy val chainAppConfig: ChainAppConfig = ChainAppConfig()
|
||||
implicit val np: NetworkParameters = nodeAppConfig.network
|
||||
/** Wallet config with data directory set to user temp directory */
|
||||
implicit protected lazy val config: BitcoinSAppConfig =
|
||||
BitcoinSAppConfig.getTestConfig()
|
||||
|
||||
implicit lazy val np: NetworkParameters = config.nodeConf.network
|
||||
|
||||
lazy val startedBitcoindF = BitcoindRpcTestUtil.startedBitcoindRpcClient()
|
||||
|
||||
@ -66,7 +69,7 @@ trait NodeUnitTest
|
||||
|
||||
def buildPeerMessageReceiver(): PeerMessageReceiver = {
|
||||
val receiver =
|
||||
PeerMessageReceiver.newReceiver(nodeAppConfig, chainAppConfig)
|
||||
PeerMessageReceiver.newReceiver
|
||||
receiver
|
||||
}
|
||||
|
||||
@ -143,7 +146,7 @@ trait NodeUnitTest
|
||||
object NodeUnitTest {
|
||||
|
||||
def destroySpvNode(spvNode: SpvNode)(
|
||||
implicit appConfig: NodeAppConfig,
|
||||
implicit config: BitcoinSAppConfig,
|
||||
ec: ExecutionContext): Future[Unit] = {
|
||||
val stopF = spvNode.stop()
|
||||
stopF.flatMap(_ => ChainUnitTest.destroyHeaderTable())
|
||||
@ -152,7 +155,7 @@ object NodeUnitTest {
|
||||
def destorySpvNodeConnectedWithBitcoind(
|
||||
spvNodeConnectedWithBitcoind: SpvNodeConnectedWithBitcoind)(
|
||||
implicit system: ActorSystem,
|
||||
appConfig: NodeAppConfig): Future[Unit] = {
|
||||
appConfig: BitcoinSAppConfig): Future[Unit] = {
|
||||
import system.dispatcher
|
||||
val spvNode = spvNodeConnectedWithBitcoind.spvNode
|
||||
val bitcoind = spvNodeConnectedWithBitcoind.bitcoind
|
||||
|
@ -0,0 +1,126 @@
|
||||
package org.bitcoins.testkit.db
|
||||
|
||||
import org.bitcoins.testkit.util.BitcoinSUnitTest
|
||||
import org.bitcoins.testkit.BitcoinSAppConfig
|
||||
import org.bitcoins.testkit.BitcoinSAppConfig._
|
||||
import com.typesafe.config.ConfigFactory
|
||||
import org.bitcoins.core.config.TestNet3
|
||||
import org.bitcoins.chain.models.BlockHeaderDAO
|
||||
import akka.actor.ActorSystem
|
||||
import scala.concurrent.ExecutionContext
|
||||
import org.bitcoins.wallet.models.AccountDAO
|
||||
import org.bitcoins.testkit.chain.ChainTestUtil
|
||||
import org.bitcoins.chain.models.BlockHeaderDb
|
||||
import org.bitcoins.chain.models.BlockHeaderDbHelper
|
||||
import org.bitcoins.wallet.models.AccountDb
|
||||
import org.bitcoins.core.hd.HDAccount
|
||||
import org.bitcoins.core.hd.HDCoin
|
||||
import org.bitcoins.core.hd.HDPurposes
|
||||
import org.bitcoins.core.hd.HDCoinType
|
||||
import org.bitcoins.testkit.core.gen.CryptoGenerators
|
||||
import os.write
|
||||
import org.bitcoins.node.db.NodeDbManagement
|
||||
import org.bitcoins.db.DbManagement
|
||||
import org.bitcoins.wallet.db.WalletDbManagement
|
||||
import org.bitcoins.db.SQLiteTableInfo
|
||||
import slick.jdbc.SQLiteProfile.api._
|
||||
import org.bitcoins.db.CRUD
|
||||
import java.nio.file.Files
|
||||
|
||||
class AppConfigTest extends BitcoinSUnitTest {
|
||||
|
||||
val system = ActorSystem()
|
||||
implicit val ec: ExecutionContext = system.dispatcher
|
||||
|
||||
behavior of "BitcoinSAppConfig"
|
||||
|
||||
it must "propagate values correctly to all sub configs" in {
|
||||
val networkOverride =
|
||||
ConfigFactory.parseString("bitcoin-s.network = testnet3")
|
||||
|
||||
val config = BitcoinSAppConfig.getTestConfig(networkOverride)
|
||||
val chainConf = config.chainConf
|
||||
val walletConf = config.walletConf
|
||||
val nodeConf = config.nodeConf
|
||||
|
||||
assert(chainConf.datadir == walletConf.datadir)
|
||||
assert(walletConf.datadir == nodeConf.datadir)
|
||||
|
||||
assert(chainConf.network == TestNet3)
|
||||
assert(walletConf.network == TestNet3)
|
||||
assert(nodeConf.network == TestNet3)
|
||||
}
|
||||
|
||||
it must "have the same DB path" in {
|
||||
val conf = BitcoinSAppConfig.getTestConfig()
|
||||
val chainConf = conf.chainConf
|
||||
val walletConf = conf.walletConf
|
||||
val nodeConf = conf.nodeConf
|
||||
assert(chainConf.dbPath == walletConf.dbPath)
|
||||
assert(walletConf.dbPath == nodeConf.dbPath)
|
||||
}
|
||||
|
||||
it must "have distinct databases" in {
|
||||
val conf = BitcoinSAppConfig.getTestConfig()
|
||||
val chainConf = conf.chainConf
|
||||
val walletConf = conf.walletConf
|
||||
val nodeConf = conf.nodeConf
|
||||
assert(chainConf.dbName != walletConf.dbName)
|
||||
assert(walletConf.dbName != nodeConf.dbName)
|
||||
}
|
||||
|
||||
it must "be able to write to distinct databases" in {
|
||||
implicit val config = BitcoinSAppConfig.getTestConfig()
|
||||
val chainConf = config.chainConf
|
||||
val walletConf = config.walletConf
|
||||
val nodeConf = config.nodeConf
|
||||
val allConfs = List(chainConf, walletConf, nodeConf)
|
||||
|
||||
val bhDAO = BlockHeaderDAO()
|
||||
val accountDAO = AccountDAO()
|
||||
|
||||
allConfs.foreach { conf =>
|
||||
val fullDbPath = conf.dbPath.resolve(conf.dbName)
|
||||
assert(!Files.exists(fullDbPath))
|
||||
}
|
||||
|
||||
val writeF = {
|
||||
for {
|
||||
_ <- config.initialize()
|
||||
_ = {
|
||||
allConfs.foreach { conf =>
|
||||
val fullDbPath = conf.dbPath.resolve(conf.dbName)
|
||||
assert(Files.isRegularFile(fullDbPath))
|
||||
}
|
||||
}
|
||||
_ <- {
|
||||
bhDAO.create(ChainTestUtil.regTestGenesisHeaderDb)
|
||||
}
|
||||
_ <- {
|
||||
val hdAccount =
|
||||
HDAccount(HDCoin(HDPurposes.Legacy, HDCoinType.Bitcoin), 0)
|
||||
val xpub = CryptoGenerators.extPublicKey.sample.get
|
||||
val account = AccountDb(xpub, hdAccount)
|
||||
accountDAO.create(account)
|
||||
}
|
||||
} yield ()
|
||||
}
|
||||
|
||||
for {
|
||||
_ <- writeF
|
||||
nodeTables <- NodeDbManagement.listTables(bhDAO.database)
|
||||
walletTables <- WalletDbManagement.listTables(accountDAO.database)
|
||||
} yield {
|
||||
def hasTable(tables: Seq[SQLiteTableInfo], dao: CRUD[_, _]): Boolean =
|
||||
tables.exists(_.name == dao.table.baseTableRow.tableName)
|
||||
|
||||
assert(hasTable(walletTables, accountDAO))
|
||||
assert(!hasTable(walletTables, bhDAO))
|
||||
|
||||
assert(hasTable(nodeTables, bhDAO))
|
||||
assert(!hasTable(nodeTables, accountDAO))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -21,6 +21,8 @@ import org.scalatest._
|
||||
import scala.concurrent.duration.{DurationInt, FiniteDuration}
|
||||
import scala.concurrent.{ExecutionContext, Future}
|
||||
import org.bitcoins.db.AppConfig
|
||||
import org.bitcoins.testkit.BitcoinSAppConfig
|
||||
import org.bitcoins.testkit.BitcoinSAppConfig._
|
||||
|
||||
trait BitcoinSWalletTest
|
||||
extends fixture.AsyncFlatSpec
|
||||
@ -31,7 +33,10 @@ trait BitcoinSWalletTest
|
||||
implicit val ec: ExecutionContext = actorSystem.dispatcher
|
||||
|
||||
protected lazy val chainParams: ChainParams = WalletTestUtil.chainParams
|
||||
protected implicit lazy val appConfig: WalletAppConfig = WalletAppConfig()
|
||||
|
||||
/** Wallet config with data directory set to user temp directory */
|
||||
implicit protected lazy val config: BitcoinSAppConfig =
|
||||
BitcoinSAppConfig.getTestConfig()
|
||||
|
||||
/** Timeout for async operations */
|
||||
protected val timeout: FiniteDuration = 10.seconds
|
||||
@ -43,19 +48,24 @@ trait BitcoinSWalletTest
|
||||
}
|
||||
|
||||
override def beforeAll(): Unit = {
|
||||
AppConfig.throwIfDefaultDatadir(appConfig)
|
||||
AppConfig.throwIfDefaultDatadir(config.walletConf)
|
||||
}
|
||||
|
||||
def destroyWallet(wallet: UnlockedWalletApi): Future[Unit] =
|
||||
WalletDbManagement.dropAll().map(_ => ())
|
||||
def destroyWallet(wallet: UnlockedWalletApi): Future[Unit] = {
|
||||
WalletDbManagement
|
||||
.dropAll()(config = config.walletConf, ec = implicitly[ExecutionContext])
|
||||
.map(_ => ())
|
||||
}
|
||||
|
||||
def createNewWallet(): Future[UnlockedWalletApi] = {
|
||||
|
||||
for {
|
||||
_ <- WalletDbManagement.createAll()
|
||||
_ <- config.initialize()
|
||||
wallet <- Wallet.initialize().map {
|
||||
case InitializeWalletSuccess(wallet) => wallet
|
||||
case err: InitializeWalletError => fail(err)
|
||||
case err: InitializeWalletError =>
|
||||
logger.error(s"Could not initialize wallet: $err")
|
||||
fail(err)
|
||||
}
|
||||
} yield wallet
|
||||
}
|
||||
|
@ -3,13 +3,13 @@ package org.bitcoins.wallet
|
||||
import org.bitcoins.core.hd._
|
||||
import org.bitcoins.core.crypto._
|
||||
import org.bitcoins.core.config._
|
||||
import org.bitcoins.db.AppConfig
|
||||
import org.bitcoins.wallet.config.WalletAppConfig
|
||||
|
||||
private[wallet] object HDUtil {
|
||||
|
||||
/** Gets the xpriv version required for the given HD purpose */
|
||||
def getXprivVersion(hdPurpose: HDPurpose)(
|
||||
implicit config: AppConfig): ExtKeyPrivVersion = {
|
||||
implicit config: WalletAppConfig): ExtKeyPrivVersion = {
|
||||
import config.network
|
||||
import org.bitcoins.core.hd.HDPurposes._
|
||||
import ExtKeyVersion._
|
||||
@ -28,7 +28,7 @@ private[wallet] object HDUtil {
|
||||
|
||||
/** Gets the xpub version required for the given HD purpose */
|
||||
def getXpubVersion(hdPurpose: HDPurpose)(
|
||||
implicit config: AppConfig): ExtKeyPubVersion = {
|
||||
implicit config: WalletAppConfig): ExtKeyPubVersion = {
|
||||
import config.network
|
||||
import org.bitcoins.core.hd.HDPurposes._
|
||||
import ExtKeyVersion._
|
||||
|
@ -16,7 +16,7 @@ import scodec.bits.BitVector
|
||||
import scala.concurrent.{ExecutionContext, Future}
|
||||
import scala.util.{Failure, Success, Try}
|
||||
import org.bitcoins.core.hd._
|
||||
import org.bitcoins.db.AppConfig
|
||||
import org.bitcoins.wallet.config.WalletAppConfig
|
||||
|
||||
sealed abstract class Wallet
|
||||
extends LockedWallet
|
||||
@ -108,7 +108,7 @@ object Wallet extends CreateWalletApi with BitcoinSLogger {
|
||||
private case class WalletImpl(
|
||||
mnemonicCode: MnemonicCode
|
||||
)(
|
||||
implicit override val walletConfig: AppConfig,
|
||||
implicit override val walletConfig: WalletAppConfig,
|
||||
override val ec: ExecutionContext)
|
||||
extends Wallet {
|
||||
|
||||
@ -117,7 +117,7 @@ object Wallet extends CreateWalletApi with BitcoinSLogger {
|
||||
}
|
||||
|
||||
def apply(mnemonicCode: MnemonicCode)(
|
||||
implicit config: AppConfig,
|
||||
implicit config: WalletAppConfig,
|
||||
ec: ExecutionContext): Wallet =
|
||||
WalletImpl(mnemonicCode)
|
||||
|
||||
@ -126,7 +126,7 @@ object Wallet extends CreateWalletApi with BitcoinSLogger {
|
||||
|
||||
// todo fix signature
|
||||
override def initializeWithEntropy(entropy: BitVector)(
|
||||
implicit config: AppConfig,
|
||||
implicit config: WalletAppConfig,
|
||||
ec: ExecutionContext): Future[InitializeWalletResult] = {
|
||||
import org.bitcoins.core.util.EitherUtil.EitherOps._
|
||||
|
||||
@ -182,6 +182,7 @@ object Wallet extends CreateWalletApi with BitcoinSLogger {
|
||||
logger.debug(s"Saved encrypted wallet mnemonic to $mnemonicPath")
|
||||
|
||||
for {
|
||||
_ <- config.initialize()
|
||||
_ <- wallet.accountDAO
|
||||
.create(accountDb)
|
||||
.map(_ => logger.trace(s"Saved account to DB"))
|
||||
|
@ -13,7 +13,7 @@ import scala.util.Success
|
||||
import java.nio.file.Paths
|
||||
import java.nio.file.Path
|
||||
import scala.util.Try
|
||||
import org.bitcoins.db.AppConfig
|
||||
import org.bitcoins.wallet.config.WalletAppConfig
|
||||
|
||||
// what do we do if seed exists? error if they aren't equal?
|
||||
object WalletStorage extends BitcoinSLogger {
|
||||
@ -35,7 +35,7 @@ object WalletStorage extends BitcoinSLogger {
|
||||
* the file name.
|
||||
*/
|
||||
def writeMnemonicToDisk(mnemonic: EncryptedMnemonic)(
|
||||
implicit config: AppConfig): Path = {
|
||||
implicit config: WalletAppConfig): Path = {
|
||||
import mnemonic.{value => encrypted}
|
||||
|
||||
val jsObject = {
|
||||
@ -98,7 +98,7 @@ object WalletStorage extends BitcoinSLogger {
|
||||
* performing no decryption
|
||||
*/
|
||||
private def readEncryptedMnemonicFromDisk()(
|
||||
implicit config: AppConfig): Either[
|
||||
implicit config: WalletAppConfig): Either[
|
||||
ReadMnemonicError,
|
||||
EncryptedMnemonic] = {
|
||||
|
||||
@ -175,7 +175,7 @@ object WalletStorage extends BitcoinSLogger {
|
||||
*/
|
||||
def decryptMnemonicFromDisk(passphrase: AesPassword)(
|
||||
implicit
|
||||
config: AppConfig): ReadMnemonicResult = {
|
||||
config: WalletAppConfig): ReadMnemonicResult = {
|
||||
val encryptedEither = readEncryptedMnemonicFromDisk()
|
||||
|
||||
import org.bitcoins.core.util.EitherUtil.EitherOps._
|
||||
|
@ -14,7 +14,7 @@ import org.bitcoins.wallet.models.{AccountDb, AddressDb, UTXOSpendingInfoDb}
|
||||
|
||||
import scala.concurrent.Future
|
||||
import scala.concurrent.ExecutionContext
|
||||
import org.bitcoins.db.AppConfig
|
||||
import org.bitcoins.wallet.config.WalletAppConfig
|
||||
|
||||
/**
|
||||
* API for the wallet project.
|
||||
@ -25,7 +25,7 @@ import org.bitcoins.db.AppConfig
|
||||
*/
|
||||
sealed trait WalletApi {
|
||||
|
||||
implicit val walletConfig: AppConfig
|
||||
implicit val walletConfig: WalletAppConfig
|
||||
implicit val ec: ExecutionContext
|
||||
|
||||
def chainParams: ChainParams = walletConfig.chain
|
||||
|
@ -2,11 +2,37 @@ package org.bitcoins.wallet.config
|
||||
|
||||
import com.typesafe.config.Config
|
||||
import org.bitcoins.db.AppConfig
|
||||
import scala.concurrent.{ExecutionContext, Future}
|
||||
import org.bitcoins.wallet.db.WalletDbManagement
|
||||
import scala.util.Failure
|
||||
import scala.util.Success
|
||||
import java.nio.file.Files
|
||||
|
||||
case class WalletAppConfig(conf: Config*) extends AppConfig {
|
||||
override val configOverrides: List[Config] = conf.toList
|
||||
override def moduleConfigName: String = "wallet.conf"
|
||||
override def moduleName: String = "wallet"
|
||||
override type ConfigType = WalletAppConfig
|
||||
override def newConfigOfType(configs: List[Config]): WalletAppConfig =
|
||||
WalletAppConfig(configs: _*)
|
||||
|
||||
override def initialize()(implicit ec: ExecutionContext): Future[Unit] = {
|
||||
logger.debug(s"Initializing wallet setup")
|
||||
|
||||
if (Files.notExists(datadir)) {
|
||||
Files.createDirectories(datadir)
|
||||
}
|
||||
|
||||
val initF = {
|
||||
WalletDbManagement.createAll()(this, ec)
|
||||
}
|
||||
initF.onComplete {
|
||||
case Failure(exception) =>
|
||||
logger.error(s"Error on wallet setup: ${exception.getMessage}")
|
||||
case Success(_) =>
|
||||
logger.debug(s"Initializing wallet setup: done")
|
||||
}
|
||||
|
||||
initF
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -7,16 +7,15 @@ import slick.jdbc.SQLiteProfile.api._
|
||||
import scala.concurrent.{Future}
|
||||
import org.bitcoins.db.CRUD
|
||||
import org.bitcoins.db.SlickUtil
|
||||
import org.bitcoins.db.AppConfig
|
||||
import scala.concurrent.ExecutionContext
|
||||
|
||||
case class AccountDAO()(implicit val ec: ExecutionContext)
|
||||
case class AccountDAO()(
|
||||
implicit val ec: ExecutionContext,
|
||||
val appConfig: WalletAppConfig)
|
||||
extends CRUD[AccountDb, (HDCoin, Int)] {
|
||||
|
||||
import org.bitcoins.db.DbCommonsColumnMappers._
|
||||
|
||||
override def appConfig: WalletAppConfig = WalletAppConfig()
|
||||
|
||||
override val table: TableQuery[AccountTable] = TableQuery[AccountTable]
|
||||
|
||||
override def createAll(ts: Vector[AccountDb]): Future[Vector[AccountDb]] =
|
||||
|
@ -9,16 +9,14 @@ import slick.sql.SqlAction
|
||||
|
||||
import scala.concurrent.{ExecutionContext, Future}
|
||||
import org.bitcoins.core.hd.HDChainType
|
||||
import org.bitcoins.db.AppConfig
|
||||
import org.bitcoins.wallet.config.WalletAppConfig
|
||||
|
||||
case class AddressDAO()(
|
||||
implicit val ec: ExecutionContext
|
||||
implicit val ec: ExecutionContext,
|
||||
val appConfig: WalletAppConfig
|
||||
) extends CRUD[AddressDb, BitcoinAddress] {
|
||||
import org.bitcoins.db.DbCommonsColumnMappers._
|
||||
|
||||
override def appConfig: WalletAppConfig = WalletAppConfig()
|
||||
|
||||
override val table: TableQuery[AddressTable] = TableQuery[AddressTable]
|
||||
|
||||
override def createAll(ts: Vector[AddressDb]): Future[Vector[AddressDb]] =
|
||||
|
@ -6,13 +6,12 @@ import slick.jdbc.SQLiteProfile.api._
|
||||
|
||||
import scala.concurrent.Future
|
||||
import scala.concurrent.ExecutionContext
|
||||
import org.bitcoins.db.AppConfig
|
||||
|
||||
case class UTXOSpendingInfoDAO()(implicit val ec: ExecutionContext)
|
||||
case class UTXOSpendingInfoDAO()(
|
||||
implicit val ec: ExecutionContext,
|
||||
val appConfig: WalletAppConfig)
|
||||
extends CRUDAutoInc[UTXOSpendingInfoDb] {
|
||||
|
||||
override def appConfig: WalletAppConfig = WalletAppConfig()
|
||||
|
||||
/** The table inside our database we are inserting into */
|
||||
override val table = TableQuery[UTXOSpendingInfoTable]
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user