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:
Torkel Rogstad 2019-06-05 17:48:15 +02:00 committed by Chris Stewart
parent 46db2a57b7
commit ceec55d458
50 changed files with 589 additions and 244 deletions

View file

@ -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,

View file

@ -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

View file

@ -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"

View file

@ -1,5 +0,0 @@
bitcoin-s {
database {
name = "chaindb.sqlite"
}
}

View file

@ -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

View file

@ -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,

View file

@ -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
}
}
}
}

View file

@ -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)
}
}

View file

@ -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._

View file

@ -1,17 +1,43 @@
bitcoin-s {
database {
dataSourceClass = slick.jdbc.DatabaseUrlDataSource
profile = "slick.jdbc.SQLiteProfile$"
dbPath = ${bitcoin-s.datadir}/${bitcoin-s.network}/
common = {
dataSourceClass = slick.jdbc.DatabaseUrlDataSource
profile = "slick.jdbc.SQLiteProfile$"
db {
path = ${bitcoin-s.datadir}/${bitcoin-s.network}/
driver = org.sqlite.JDBC
# as long as we're on SQLite there's no point
# in doing connection pooling
connectionPool = disabled
}
}
bitcoin-s {
wallet = ${common}
wallet {
# this config key is read by Slick
db {
driver = org.sqlite.JDBC
url = "jdbc:sqlite:"${bitcoin-s.database.dbPath}${bitcoin-s.database.name}
name = walletdb.sqlite
url = "jdbc:sqlite:"${bitcoin-s.wallet.db.path}${bitcoin-s.wallet.db.name}
# as long as we're on SQLite there's no point
# in doing connection pooling
connectionPool = disabled
}
}
node = ${common}
node {
# this config key is read by Slick
db {
name = nodedb.sqlite
url = "jdbc:sqlite:"${bitcoin-s.node.db.path}${bitcoin-s.node.db.name}
}
}
chain = ${common}
chain {
# this config key is read by Slick
db {
name = chaindb.sqlite
url = "jdbc:sqlite:"${bitcoin-s.chain.db.path}${bitcoin-s.chain.db.name}
}
}
}

View file

@ -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
}

View file

@ -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)

View file

@ -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(

View file

@ -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)

View file

@ -16,16 +16,17 @@ import org.bitcoins.node.messages.HeadersMessage
import org.bitcoins.node.messages.control.VersionMessage
import org.bitcoins.node.messages.data.HeadersMessage
import org.bitcoins.testkit.util.BitcoinSUnitTest
import org.bitcoins.testkit.BitcoinSAppConfig
import org.bitcoins.node.config.NodeAppConfig
/**
* Created by chris on 9/6/16.
*/
class BitcoinSpvNodeUtilTest extends BitcoinSUnitTest {
lazy val config: NodeAppConfig = BitcoinSAppConfig.getTestConfig()
"BitcoinSpvNodeUtil" must "return the entire byte array if a message is not aligned to a byte frame" in {
val versionMessage =
VersionMessage(TestNet3.dnsSeeds(0), Constants.networkParameters)
val networkMsg = NetworkMessage(Constants.networkParameters, versionMessage)
VersionMessage(TestNet3.dnsSeeds(0), config.network)
val networkMsg = NetworkMessage(config.network, versionMessage)
//remove last byte so the message is not aligned
val bytes = networkMsg.bytes.slice(0, networkMsg.bytes.size - 1)
val (_, unAlignedBytes) = BitcoinSpvNodeUtil.parseIndividualMessages(bytes)
@ -60,7 +61,7 @@ class BitcoinSpvNodeUtilTest extends BitcoinSUnitTest {
)
)
)
val networkMsg = NetworkMessage(Constants.networkParameters, headersMsg)
val networkMsg = NetworkMessage(config.network, headersMsg)
//split the network msg at a random index to simulate a tcp frame not being aligned
val randomIndex = scala.util.Random.nextInt().abs % networkMsg.bytes.size
val (firstHalf, secondHalf) = networkMsg.bytes.splitAt(randomIndex)

View file

@ -1,5 +0,0 @@
bitcoin-s {
database {
name = "nodedb.sqlite"
}
}

View file

@ -24,7 +24,7 @@ object Main extends App with BitcoinSLogger {
implicit val chainAppConfig = ChainAppConfig()
logger.info(s"Chain config: ${chainAppConfig.dbConfig.config}")
val bhDAO = BlockHeaderDAO(chainAppConfig)
val bhDAO = BlockHeaderDAO()
val chainApi = ChainHandler(bhDAO, chainAppConfig)
val table = TableQuery[BlockHeaderTable]

View file

@ -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 */

View file

@ -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
}
}

View file

@ -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
}

View file

@ -3,11 +3,12 @@ package org.bitcoins.node.models
import org.bitcoins.db.{CRUDAutoInc}
import slick.jdbc.SQLiteProfile.api._
import org.bitcoins.db.AppConfig
import scala.concurrent.ExecutionContext
import org.bitcoins.node.config.NodeAppConfig
case class PeerDAO(appConfig: AppConfig)(
implicit override val ec: ExecutionContext)
case class PeerDAO()(
implicit override val ec: ExecutionContext,
override val appConfig: NodeAppConfig)
extends CRUDAutoInc[Peer] {
override val table = TableQuery[PeerTable]
}

View file

@ -22,7 +22,7 @@ sealed abstract class BlockActor extends Actor with BitcoinSLogger {
val inv = Inventory(TypeIdentifier.MsgBlock, hash)
val getDataMessage = GetDataMessage(inv)
val networkMessage =
NetworkMessage(Constants.networkParameters, getDataMessage)
NetworkMessage(network = ???, getDataMessage)
peerMsgHandler ! networkMessage
context.become(awaitBlockMsg)
case blockHeader: BlockHeader =>

View file

@ -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))

View file

@ -49,7 +49,7 @@ sealed abstract class PaymentActor extends Actor with BitcoinSLogger {
BloomFilter(10, 0.0001, UInt32.zero, BloomUpdateNone).insert(hash)
val filterLoadMsg = FilterLoadMessage(bloomFilter)
val bloomFilterNetworkMsg =
NetworkMessage(Constants.networkParameters, filterLoadMsg)
NetworkMessage(network = ???, filterLoadMsg)
peerMsgHandler ! bloomFilterNetworkMsg
}
@ -111,7 +111,7 @@ sealed abstract class PaymentActor extends Actor with BitcoinSLogger {
Inventory(TypeIdentifier.MsgFilteredBlock, blockHashes.head)
val getDataMsg = GetDataMessage(merkleBlockInventory)
val getDataNetworkMessage =
NetworkMessage(Constants.networkParameters, getDataMsg)
NetworkMessage(network = ???, getDataMsg)
peerMessageHandler ! getDataNetworkMessage
logger.debug("Switching to awaitMerkleBlockMessage")
context.become(

View file

@ -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,

View file

@ -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)

View file

@ -210,7 +210,7 @@ object Deps {
Test.ammonite
)
val doc = List(
val docs = List(
Compile.ammonite,
Compile.logback,
Test.scalaTest,

View file

@ -1,19 +0,0 @@
bitcoin-s {
datadir = ${HOME}/.bitcoin-s/.unittest
database {
db {
# run against a file-based database
# url="jdbc:sqlite:"${dbPath}${dbName}
# run against an in-memory database
# cache=shared is needed to make it persist across DB connections
# (each query is one connection). DB is wiped on JVM being killed
# with this option set.
url = "jdbc:sqlite:file::memory:?cache=shared"
connectionPool = disabled
keepAliveConnection = true
}
}
}

View file

@ -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: _*)
}
}

View file

@ -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)
}

View file

@ -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)
}

View file

@ -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

View file

@ -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))
}
}
}

View file

@ -19,15 +19,21 @@ import java.nio.file.Paths
import org.bitcoins.wallet.ReadMnemonicError.DecryptionError
import java.{util => ju}
import org.bitcoins.wallet.ReadMnemonicError.JsonParsingError
import org.bitcoins.testkit.BitcoinSAppConfig._
import scala.concurrent.Await
import scala.concurrent.duration._
class WalletStorageTest
extends BitcoinSWalletTest
with BeforeAndAfterEach
with EmptyFixture {
val datadir = appConfig.datadir
val datadir = config.walletConf.datadir
override def beforeEach(): Unit = {
// make sure datadir is created for reading/writing mnemonics
Await.result(config.walletConf.initialize(), 5.seconds)
Files
.walk(datadir)
.iterator()

View file

@ -6,10 +6,15 @@ import org.bitcoins.wallet.util.BitcoinSWalletTest
import org.scalatest._
import scala.concurrent.Future
import org.bitcoins.wallet.config.WalletAppConfig
trait AccountDAOFixture extends fixture.AsyncFlatSpec with BitcoinSWalletTest {
override final type FixtureParam = AccountDAO
// to get around the config in `BitcoinSWalletTest` not resolving
// as an AppConfig
private implicit val walletConfig: WalletAppConfig = config.walletConf
override final def withFixture(test: OneArgAsyncTest): FutureOutcome =
makeDependentFixture(createAccountTable, dropAccountTable)(test)

View file

@ -6,6 +6,7 @@ import org.bitcoins.wallet.db.WalletDbManagement
import org.bitcoins.wallet.models.{AccountDAO, AddressDAO}
import org.bitcoins.wallet.util.BitcoinSWalletTest
import org.scalatest._
import org.bitcoins.wallet.config.WalletAppConfig
/**
* This fixture has a tuple of DAOs, because
@ -18,6 +19,10 @@ trait AddressDAOFixture extends fixture.AsyncFlatSpec with BitcoinSWalletTest {
override final def withFixture(test: OneArgAsyncTest): FutureOutcome =
makeDependentFixture(createTables, dropTables)(test)
// to get around the config in `BitcoinSWalletTest` not resolving
// as an AppConfig
private implicit val walletConfig: WalletAppConfig = config.walletConf
private def dropTables(daos: FixtureParam): Future[Unit] = {
val (account, address) = daos
val dropAccountF = WalletDbManagement.dropTable(account.table)

View file

@ -7,6 +7,7 @@ import slick.jdbc.SQLiteProfile.api._
import scala.language.reflectiveCalls
import scala.concurrent.{Await, Future}
import org.bitcoins.wallet.config.WalletAppConfig
private[fixtures] trait DAOFixture
extends fixture.AsyncFlatSpec
@ -17,6 +18,10 @@ private[fixtures] trait DAOFixture
private[fixtures] val daoAccumulator =
Vector.newBuilder[HasTable]
// to get around the config in `BitcoinSWalletTest` not resolving
// as an AppConfig
private implicit val walletConfig: WalletAppConfig = config.walletConf
override def beforeAll(): Unit = {
val tables = daoAccumulator.result()

View file

@ -6,6 +6,7 @@ import org.bitcoins.wallet.util.BitcoinSWalletTest
import org.scalatest._
import scala.concurrent.Future
import org.bitcoins.wallet.config.WalletAppConfig
trait UtxoDAOFixture extends fixture.AsyncFlatSpec with BitcoinSWalletTest {
@ -14,6 +15,10 @@ trait UtxoDAOFixture extends fixture.AsyncFlatSpec with BitcoinSWalletTest {
override final def withFixture(test: OneArgAsyncTest): FutureOutcome =
makeDependentFixture(createUtxoTable, dropUtxoTable)(test)
// to get around the config in `BitcoinSWalletTest` not resolving
// as an AppConfig
private implicit val walletConfig: WalletAppConfig = config.walletConf
private def dropUtxoTable(utxoDAO: FixtureParam): Future[Unit] = {
WalletDbManagement.dropTable(utxoDAO.table)
}

View file

@ -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
}

View file

@ -1,5 +0,0 @@
bitcoin-s {
database {
name = "walletdb.sqlite"
}
}

View file

@ -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._

View file

@ -20,10 +20,10 @@ import org.bitcoins.wallet.models._
import scala.concurrent.Future
import scala.util.Success
import scala.util.Failure
import org.bitcoins.db.AppConfig
import scala.concurrent.ExecutionContext
import org.bitcoins.wallet.ReadMnemonicError.DecryptionError
import org.bitcoins.wallet.ReadMnemonicError.JsonParsingError
import org.bitcoins.wallet.config.WalletAppConfig
abstract class LockedWallet extends LockedWalletApi with BitcoinSLogger {
@ -263,9 +263,11 @@ abstract class LockedWallet extends LockedWalletApi with BitcoinSLogger {
object LockedWallet {
private case class LockedWalletImpl()(
implicit val ec: ExecutionContext,
val walletConfig: AppConfig)
val walletConfig: WalletAppConfig)
extends LockedWallet
def apply()(implicit ec: ExecutionContext, config: AppConfig): LockedWallet =
LockedWalletImpl()
def apply()(
implicit ec: ExecutionContext,
config: WalletAppConfig): LockedWallet = LockedWalletImpl()
}

View file

@ -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"))

View file

@ -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._

View file

@ -4,7 +4,7 @@ import org.bitcoins.core.crypto.MnemonicCode
import scodec.bits.BitVector
import scala.concurrent.{ExecutionContext, Future}
import org.bitcoins.db.AppConfig
import org.bitcoins.wallet.config.WalletAppConfig
/**
* @define initialize
@ -25,7 +25,7 @@ trait CreateWalletApi {
private def initializeInternal()(
implicit executionContext: ExecutionContext,
appConfig: AppConfig): Future[InitializeWalletResult] =
config: WalletAppConfig): Future[InitializeWalletResult] =
initializeWithEntropy(entropy = MnemonicCode.getEntropy256Bits)
/**
@ -33,19 +33,19 @@ trait CreateWalletApi {
*/
final def initialize()(
implicit executionContext: ExecutionContext,
appConfig: AppConfig): Future[InitializeWalletResult] =
config: WalletAppConfig): Future[InitializeWalletResult] =
initializeInternal()
/**
* $initializeWithEnt
*/
def initializeWithEntropy(entropy: BitVector)(
implicit config: AppConfig,
implicit config: WalletAppConfig,
executionContext: ExecutionContext): Future[InitializeWalletResult]
// todo: scaladoc
final def initializeWithMnemonic(mnemonicCode: MnemonicCode)(
implicit config: AppConfig,
implicit config: WalletAppConfig,
executionContext: ExecutionContext): Future[InitializeWalletResult] = {
val entropy = mnemonicCode.toEntropy
initializeWithEntropy(entropy)

View file

@ -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

View file

@ -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
}
}

View file

@ -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]] =

View file

@ -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]] =

View file

@ -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]