mirror of
https://github.com/bitcoin-s/bitcoin-s.git
synced 2025-02-22 14:33:06 +01:00
Add configurable logging to data directory (#640)
* Add logging to data directory In this commit we add the ability for the node, chain and wallet projects (+ the server) to log to the users data directory instead of whatever directory the binaries was launched from. This is inherently a bit more complicated than our previous setup, because we need to read the user directory before we can create loggers. As a result of this, some files/methods were moved around, so the relevant app config could be found in scope. We also introduce several logging categories that can be tuned individually through user configuration. These logggers are exposed both as traits that give a field `logger`, or as methods that return the required logger. * Add datadir configuration to AppConfig In this commit we add support for AppConfig to pick up the data directory configuration file. We also add a section to the contributing guide on how to tune logging levels. * Pass data directories explicitly for configuration
This commit is contained in:
parent
dd6c86dc14
commit
a76f61f97c
50 changed files with 716 additions and 241 deletions
|
@ -1,4 +1,3 @@
|
||||||
akka {
|
akka {
|
||||||
loggers = ["akka.event.slf4j.Slf4jLogger"]
|
loglevel = "INFO"
|
||||||
loglevel = "DEBUG"
|
|
||||||
}
|
}
|
13
app/server/src/main/resources/logback.xml
Normal file
13
app/server/src/main/resources/logback.xml
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
<configuration>
|
||||||
|
<!-- see how long statements took to execute by setting to DEBUG -->
|
||||||
|
<logger name="slick.jdbc.JdbcBackend.benchmark" level="INFO"/>
|
||||||
|
|
||||||
|
<!-- see what statements are executed by setting to DEBUG -->
|
||||||
|
<logger name="slick.jdbc.JdbcBackend.statement" level="INFO"/>
|
||||||
|
|
||||||
|
<!-- see what slick is compiling to in sql -->
|
||||||
|
<logger name="slick.compiler" level="INFO"/>
|
||||||
|
|
||||||
|
<!-- see what's returned by Slick -->
|
||||||
|
<logger name="slick.jdbc.StatementInvoker.result" level="INFO"/>
|
||||||
|
</configuration>
|
|
@ -6,6 +6,8 @@ import org.bitcoins.node.config.NodeAppConfig
|
||||||
import org.bitcoins.chain.config.ChainAppConfig
|
import org.bitcoins.chain.config.ChainAppConfig
|
||||||
import scala.concurrent.ExecutionContext
|
import scala.concurrent.ExecutionContext
|
||||||
import scala.concurrent.Future
|
import scala.concurrent.Future
|
||||||
|
import java.nio.file.Path
|
||||||
|
import org.bitcoins.db.AppConfig
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A unified config class for all submodules of Bitcoin-S
|
* A unified config class for all submodules of Bitcoin-S
|
||||||
|
@ -13,11 +15,16 @@ import scala.concurrent.Future
|
||||||
* in this case class' companion object an instance
|
* in this case class' companion object an instance
|
||||||
* of this class can be passed in anywhere a wallet,
|
* of this class can be passed in anywhere a wallet,
|
||||||
* chain or node config is required.
|
* chain or node config is required.
|
||||||
|
*
|
||||||
|
* @param directory The data directory of this app configuration
|
||||||
|
* @param confs A sequence of optional configuration overrides
|
||||||
*/
|
*/
|
||||||
case class BitcoinSAppConfig(private val confs: Config*) {
|
case class BitcoinSAppConfig(
|
||||||
val walletConf = WalletAppConfig(confs: _*)
|
private val directory: Path,
|
||||||
val nodeConf = NodeAppConfig(confs: _*)
|
private val confs: Config*) {
|
||||||
val chainConf = ChainAppConfig(confs: _*)
|
val walletConf = WalletAppConfig(directory, confs: _*)
|
||||||
|
val nodeConf = NodeAppConfig(directory, confs: _*)
|
||||||
|
val chainConf = ChainAppConfig(directory, confs: _*)
|
||||||
|
|
||||||
/** Initializes the wallet, node and chain projects */
|
/** Initializes the wallet, node and chain projects */
|
||||||
def initialize()(implicit ec: ExecutionContext): Future[Unit] = {
|
def initialize()(implicit ec: ExecutionContext): Future[Unit] = {
|
||||||
|
@ -44,6 +51,13 @@ case class BitcoinSAppConfig(private val confs: Config*) {
|
||||||
* to be passed in wherever a specializes one is required
|
* to be passed in wherever a specializes one is required
|
||||||
*/
|
*/
|
||||||
object BitcoinSAppConfig {
|
object BitcoinSAppConfig {
|
||||||
|
|
||||||
|
/** Constructs an app configuration from the default Bitcoin-S
|
||||||
|
* data directory and given list of configuration overrides.
|
||||||
|
*/
|
||||||
|
def fromDefaultDatadir(confs: Config*): BitcoinSAppConfig =
|
||||||
|
BitcoinSAppConfig(AppConfig.DEFAULT_BITCOIN_S_DATADIR, confs: _*)
|
||||||
|
|
||||||
import scala.language.implicitConversions
|
import scala.language.implicitConversions
|
||||||
|
|
||||||
/** Converts the given implicit config to a wallet config */
|
/** Converts the given implicit config to a wallet config */
|
||||||
|
|
|
@ -4,14 +4,12 @@ import akka.actor.ActorSystem
|
||||||
import akka.http.scaladsl.server._
|
import akka.http.scaladsl.server._
|
||||||
import akka.http.scaladsl.server.Directives._
|
import akka.http.scaladsl.server.Directives._
|
||||||
import akka.stream.ActorMaterializer
|
import akka.stream.ActorMaterializer
|
||||||
import org.bitcoins.core.util.BitcoinSLogger
|
|
||||||
import org.bitcoins.chain.api.ChainApi
|
import org.bitcoins.chain.api.ChainApi
|
||||||
|
|
||||||
import org.bitcoins.picklers._
|
import org.bitcoins.picklers._
|
||||||
|
|
||||||
case class ChainRoutes(chain: ChainApi)(implicit system: ActorSystem)
|
case class ChainRoutes(chain: ChainApi)(implicit system: ActorSystem)
|
||||||
extends BitcoinSLogger
|
extends ServerRoute {
|
||||||
with ServerRoute {
|
|
||||||
implicit val materializer = ActorMaterializer()
|
implicit val materializer = ActorMaterializer()
|
||||||
import system.dispatcher
|
import system.dispatcher
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,6 @@ package org.bitcoins.server
|
||||||
|
|
||||||
import org.bitcoins.rpc.config.BitcoindInstance
|
import org.bitcoins.rpc.config.BitcoindInstance
|
||||||
import org.bitcoins.node.models.Peer
|
import org.bitcoins.node.models.Peer
|
||||||
import org.bitcoins.core.util.BitcoinSLogger
|
|
||||||
import org.bitcoins.rpc.client.common.BitcoindRpcClient
|
import org.bitcoins.rpc.client.common.BitcoindRpcClient
|
||||||
import akka.actor.ActorSystem
|
import akka.actor.ActorSystem
|
||||||
import scala.concurrent.Await
|
import scala.concurrent.Await
|
||||||
|
@ -17,7 +16,6 @@ import org.bitcoins.wallet.api.InitializeWalletSuccess
|
||||||
import org.bitcoins.wallet.api.InitializeWalletError
|
import org.bitcoins.wallet.api.InitializeWalletError
|
||||||
import org.bitcoins.node.SpvNode
|
import org.bitcoins.node.SpvNode
|
||||||
import org.bitcoins.chain.blockchain.ChainHandler
|
import org.bitcoins.chain.blockchain.ChainHandler
|
||||||
import org.bitcoins.chain.models.BlockHeaderDAO
|
|
||||||
import org.bitcoins.chain.config.ChainAppConfig
|
import org.bitcoins.chain.config.ChainAppConfig
|
||||||
import org.bitcoins.wallet.api.UnlockedWalletApi
|
import org.bitcoins.wallet.api.UnlockedWalletApi
|
||||||
import org.bitcoins.wallet.api.UnlockWalletSuccess
|
import org.bitcoins.wallet.api.UnlockWalletSuccess
|
||||||
|
@ -25,17 +23,19 @@ import org.bitcoins.wallet.api.UnlockWalletError
|
||||||
import org.bitcoins.node.networking.peer.DataMessageHandler
|
import org.bitcoins.node.networking.peer.DataMessageHandler
|
||||||
import org.bitcoins.node.SpvNodeCallbacks
|
import org.bitcoins.node.SpvNodeCallbacks
|
||||||
import org.bitcoins.wallet.WalletStorage
|
import org.bitcoins.wallet.WalletStorage
|
||||||
|
import org.bitcoins.db.AppLoggers
|
||||||
|
import org.bitcoins.chain.models.BlockHeaderDAO
|
||||||
|
|
||||||
object Main
|
object Main extends App {
|
||||||
extends App
|
|
||||||
// TODO we want to log to user data directory
|
|
||||||
// how do we do this?
|
|
||||||
with BitcoinSLogger {
|
|
||||||
implicit val conf = {
|
implicit val conf = {
|
||||||
// val custom = ConfigFactory.parseString("bitcoin-s.network = testnet3")
|
// val custom = ConfigFactory.parseString("bitcoin-s.network = testnet3")
|
||||||
BitcoinSAppConfig()
|
BitcoinSAppConfig.fromDefaultDatadir()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val logger = AppLoggers.getHttpLogger(
|
||||||
|
conf.walletConf // doesn't matter which one we pass in
|
||||||
|
)
|
||||||
|
|
||||||
implicit val walletConf: WalletAppConfig = conf.walletConf
|
implicit val walletConf: WalletAppConfig = conf.walletConf
|
||||||
implicit val nodeConf: NodeAppConfig = conf.nodeConf
|
implicit val nodeConf: NodeAppConfig = conf.nodeConf
|
||||||
implicit val chainConf: ChainAppConfig = conf.chainConf
|
implicit val chainConf: ChainAppConfig = conf.chainConf
|
||||||
|
@ -113,7 +113,7 @@ object Main
|
||||||
SpvNodeCallbacks(onTxReceived = Seq(onTX))
|
SpvNodeCallbacks(onTxReceived = Seq(onTX))
|
||||||
}
|
}
|
||||||
val blockheaderDAO = BlockHeaderDAO()
|
val blockheaderDAO = BlockHeaderDAO()
|
||||||
val chain = ChainHandler(blockheaderDAO, conf)
|
val chain = ChainHandler(blockheaderDAO)
|
||||||
SpvNode(peer, chain, bloom, callbacks).start()
|
SpvNode(peer, chain, bloom, callbacks).start()
|
||||||
}
|
}
|
||||||
_ = logger.info(s"Starting SPV node sync")
|
_ = logger.info(s"Starting SPV node sync")
|
||||||
|
@ -123,7 +123,9 @@ object Main
|
||||||
val walletRoutes = WalletRoutes(wallet, node)
|
val walletRoutes = WalletRoutes(wallet, node)
|
||||||
val nodeRoutes = NodeRoutes(node)
|
val nodeRoutes = NodeRoutes(node)
|
||||||
val chainRoutes = ChainRoutes(node.chainApi)
|
val chainRoutes = ChainRoutes(node.chainApi)
|
||||||
val server = Server(Seq(walletRoutes, nodeRoutes, chainRoutes))
|
val server =
|
||||||
|
Server(nodeConf, // could use either of configurations
|
||||||
|
Seq(walletRoutes, nodeRoutes, chainRoutes))
|
||||||
server.start()
|
server.start()
|
||||||
}
|
}
|
||||||
} yield start
|
} yield start
|
||||||
|
|
|
@ -4,12 +4,10 @@ import akka.actor.ActorSystem
|
||||||
import akka.http.scaladsl.server._
|
import akka.http.scaladsl.server._
|
||||||
import akka.http.scaladsl.server.Directives._
|
import akka.http.scaladsl.server.Directives._
|
||||||
import akka.stream.ActorMaterializer
|
import akka.stream.ActorMaterializer
|
||||||
import org.bitcoins.core.util.BitcoinSLogger
|
|
||||||
import org.bitcoins.node.SpvNode
|
import org.bitcoins.node.SpvNode
|
||||||
|
|
||||||
case class NodeRoutes(node: SpvNode)(implicit system: ActorSystem)
|
case class NodeRoutes(node: SpvNode)(implicit system: ActorSystem)
|
||||||
extends BitcoinSLogger
|
extends ServerRoute {
|
||||||
with ServerRoute {
|
|
||||||
implicit val materializer = ActorMaterializer()
|
implicit val materializer = ActorMaterializer()
|
||||||
|
|
||||||
def handleCommand: PartialFunction[ServerCommand, StandardRoute] = {
|
def handleCommand: PartialFunction[ServerCommand, StandardRoute] = {
|
||||||
|
|
|
@ -4,7 +4,6 @@ import upickle.{default => up}
|
||||||
import akka.actor.ActorSystem
|
import akka.actor.ActorSystem
|
||||||
import akka.http.scaladsl._
|
import akka.http.scaladsl._
|
||||||
import akka.stream.ActorMaterializer
|
import akka.stream.ActorMaterializer
|
||||||
import org.bitcoins.core.util.BitcoinSLogger
|
|
||||||
import akka.http.scaladsl.model._
|
import akka.http.scaladsl.model._
|
||||||
import akka.http.scaladsl.server._
|
import akka.http.scaladsl.server._
|
||||||
import akka.http.scaladsl.server.Directives._
|
import akka.http.scaladsl.server.Directives._
|
||||||
|
@ -12,9 +11,14 @@ import akka.http.scaladsl.server.Directives._
|
||||||
import de.heikoseeberger.akkahttpupickle.UpickleSupport._
|
import de.heikoseeberger.akkahttpupickle.UpickleSupport._
|
||||||
import akka.http.scaladsl.server.directives.DebuggingDirectives
|
import akka.http.scaladsl.server.directives.DebuggingDirectives
|
||||||
import akka.event.Logging
|
import akka.event.Logging
|
||||||
|
import org.bitcoins.db.HttpLogger
|
||||||
|
import org.bitcoins.db.AppConfig
|
||||||
|
|
||||||
|
case class Server(conf: AppConfig, handlers: Seq[ServerRoute])(
|
||||||
|
implicit system: ActorSystem)
|
||||||
|
extends HttpLogger {
|
||||||
|
implicit private val config: AppConfig = conf
|
||||||
|
|
||||||
case class Server(handlers: Seq[ServerRoute])(implicit system: ActorSystem)
|
|
||||||
extends BitcoinSLogger {
|
|
||||||
implicit val materializer = ActorMaterializer()
|
implicit val materializer = ActorMaterializer()
|
||||||
import system.dispatcher
|
import system.dispatcher
|
||||||
|
|
||||||
|
@ -83,7 +87,7 @@ case class Server(handlers: Seq[ServerRoute])(implicit system: ActorSystem)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
object Server extends BitcoinSLogger {
|
object Server {
|
||||||
|
|
||||||
// TODO id parameter
|
// TODO id parameter
|
||||||
case class Response(
|
case class Response(
|
||||||
|
|
|
@ -7,7 +7,6 @@ import akka.http.scaladsl.server._
|
||||||
import akka.http.scaladsl.server.Directives._
|
import akka.http.scaladsl.server.Directives._
|
||||||
import akka.stream.ActorMaterializer
|
import akka.stream.ActorMaterializer
|
||||||
|
|
||||||
import org.bitcoins.core.util.BitcoinSLogger
|
|
||||||
import org.bitcoins.core.currency._
|
import org.bitcoins.core.currency._
|
||||||
import org.bitcoins.wallet.api.UnlockedWalletApi
|
import org.bitcoins.wallet.api.UnlockedWalletApi
|
||||||
import org.bitcoins.core.wallet.fee.SatoshisPerByte
|
import org.bitcoins.core.wallet.fee.SatoshisPerByte
|
||||||
|
@ -19,8 +18,7 @@ import scala.util.Success
|
||||||
|
|
||||||
case class WalletRoutes(wallet: UnlockedWalletApi, node: SpvNode)(
|
case class WalletRoutes(wallet: UnlockedWalletApi, node: SpvNode)(
|
||||||
implicit system: ActorSystem)
|
implicit system: ActorSystem)
|
||||||
extends BitcoinSLogger
|
extends ServerRoute {
|
||||||
with ServerRoute {
|
|
||||||
import system.dispatcher
|
import system.dispatcher
|
||||||
implicit val materializer = ActorMaterializer()
|
implicit val materializer = ActorMaterializer()
|
||||||
|
|
||||||
|
|
|
@ -7,9 +7,12 @@ import com.typesafe.config.ConfigFactory
|
||||||
import org.bitcoins.core.config.RegTest
|
import org.bitcoins.core.config.RegTest
|
||||||
import org.bitcoins.core.config.MainNet
|
import org.bitcoins.core.config.MainNet
|
||||||
import org.bitcoins.chain.config.ChainAppConfig
|
import org.bitcoins.chain.config.ChainAppConfig
|
||||||
|
import java.nio.file.Files
|
||||||
|
import ch.qos.logback.classic.Level
|
||||||
|
|
||||||
class ChainAppConfigTest extends BitcoinSUnitTest {
|
class ChainAppConfigTest extends BitcoinSUnitTest {
|
||||||
val config = ChainAppConfig()
|
val tempDir = Files.createTempDirectory("bitcoin-s")
|
||||||
|
val config = ChainAppConfig(directory = tempDir)
|
||||||
|
|
||||||
it must "be overridable" in {
|
it must "be overridable" in {
|
||||||
assert(config.network == RegTest)
|
assert(config.network == RegTest)
|
||||||
|
@ -30,4 +33,29 @@ class ChainAppConfigTest extends BitcoinSUnitTest {
|
||||||
assert(overriden.network == MainNet)
|
assert(overriden.network == MainNet)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
it must "have user data directory configuration take precedence" in {
|
||||||
|
|
||||||
|
val tempDir = Files.createTempDirectory("bitcoin-s")
|
||||||
|
val tempFile = Files.createFile(tempDir.resolve("bitcoin-s.conf"))
|
||||||
|
val confStr = """
|
||||||
|
| bitcoin-s {
|
||||||
|
| network = testnet3
|
||||||
|
|
|
||||||
|
| logging {
|
||||||
|
| level = off
|
||||||
|
|
|
||||||
|
| p2p = warn
|
||||||
|
| }
|
||||||
|
| }
|
||||||
|
""".stripMargin
|
||||||
|
val _ = Files.write(tempFile, confStr.getBytes())
|
||||||
|
|
||||||
|
val appConfig = ChainAppConfig(directory = tempDir)
|
||||||
|
|
||||||
|
assert(appConfig.datadir == tempDir.resolve("testnet3"))
|
||||||
|
assert(appConfig.network == TestNet3)
|
||||||
|
assert(appConfig.logLevel == Level.OFF)
|
||||||
|
assert(appConfig.p2pLogLevel == Level.WARN)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,7 +12,7 @@ import org.bitcoins.chain.config.ChainAppConfig
|
||||||
*/
|
*/
|
||||||
trait ChainApi {
|
trait ChainApi {
|
||||||
|
|
||||||
def chainConfig: ChainAppConfig
|
implicit private[chain] val chainConfig: ChainAppConfig
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds a block header to our chain project
|
* Adds a block header to our chain project
|
||||||
|
|
|
@ -3,10 +3,11 @@ package org.bitcoins.chain.blockchain
|
||||||
import org.bitcoins.chain.models.{BlockHeaderDAO, BlockHeaderDb}
|
import org.bitcoins.chain.models.{BlockHeaderDAO, BlockHeaderDb}
|
||||||
import org.bitcoins.chain.validation.{TipUpdateResult, TipValidation}
|
import org.bitcoins.chain.validation.{TipUpdateResult, TipValidation}
|
||||||
import org.bitcoins.core.protocol.blockchain.BlockHeader
|
import org.bitcoins.core.protocol.blockchain.BlockHeader
|
||||||
import org.bitcoins.core.util.BitcoinSLogger
|
|
||||||
|
|
||||||
import scala.collection.{IndexedSeqLike, mutable}
|
import scala.collection.{IndexedSeqLike, mutable}
|
||||||
import scala.concurrent.{ExecutionContext, Future}
|
import scala.concurrent.{ExecutionContext, Future}
|
||||||
|
import org.bitcoins.chain.config.ChainAppConfig
|
||||||
|
import org.bitcoins.db.ChainVerificationLogger
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* In memory implementation of a blockchain
|
* In memory implementation of a blockchain
|
||||||
|
@ -21,8 +22,7 @@ import scala.concurrent.{ExecutionContext, Future}
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
case class Blockchain(headers: Vector[BlockHeaderDb])
|
case class Blockchain(headers: Vector[BlockHeaderDb])
|
||||||
extends IndexedSeqLike[BlockHeaderDb, Vector[BlockHeaderDb]]
|
extends IndexedSeqLike[BlockHeaderDb, Vector[BlockHeaderDb]] {
|
||||||
with BitcoinSLogger {
|
|
||||||
val tip: BlockHeaderDb = headers.head
|
val tip: BlockHeaderDb = headers.head
|
||||||
|
|
||||||
/** @inheritdoc */
|
/** @inheritdoc */
|
||||||
|
@ -41,7 +41,7 @@ case class Blockchain(headers: Vector[BlockHeaderDb])
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
object Blockchain extends BitcoinSLogger {
|
object Blockchain extends ChainVerificationLogger {
|
||||||
|
|
||||||
def fromHeaders(headers: Vector[BlockHeaderDb]): Blockchain = {
|
def fromHeaders(headers: Vector[BlockHeaderDb]): Blockchain = {
|
||||||
Blockchain(headers)
|
Blockchain(headers)
|
||||||
|
@ -61,7 +61,8 @@ object Blockchain extends BitcoinSLogger {
|
||||||
* or [[org.bitcoins.chain.blockchain.BlockchainUpdate.Failed Failed]] to connect to a tip
|
* or [[org.bitcoins.chain.blockchain.BlockchainUpdate.Failed Failed]] to connect to a tip
|
||||||
*/
|
*/
|
||||||
def connectTip(header: BlockHeader, blockHeaderDAO: BlockHeaderDAO)(
|
def connectTip(header: BlockHeader, blockHeaderDAO: BlockHeaderDAO)(
|
||||||
implicit ec: ExecutionContext): Future[BlockchainUpdate] = {
|
implicit ec: ExecutionContext,
|
||||||
|
conf: ChainAppConfig): Future[BlockchainUpdate] = {
|
||||||
|
|
||||||
//get all competing chains we have
|
//get all competing chains we have
|
||||||
val blockchainsF: Future[Vector[Blockchain]] =
|
val blockchainsF: Future[Vector[Blockchain]] =
|
||||||
|
|
|
@ -5,20 +5,19 @@ import org.bitcoins.chain.config.ChainAppConfig
|
||||||
import org.bitcoins.chain.models.{BlockHeaderDAO, BlockHeaderDb}
|
import org.bitcoins.chain.models.{BlockHeaderDAO, BlockHeaderDb}
|
||||||
import org.bitcoins.core.crypto.DoubleSha256DigestBE
|
import org.bitcoins.core.crypto.DoubleSha256DigestBE
|
||||||
import org.bitcoins.core.protocol.blockchain.BlockHeader
|
import org.bitcoins.core.protocol.blockchain.BlockHeader
|
||||||
import org.bitcoins.core.util.BitcoinSLogger
|
|
||||||
|
|
||||||
import scala.concurrent.{ExecutionContext, Future}
|
import scala.concurrent.{ExecutionContext, Future}
|
||||||
|
import org.bitcoins.db.ChainVerificationLogger
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Chain Handler is meant to be the reference implementation
|
* Chain Handler is meant to be the reference implementation
|
||||||
* of [[org.bitcoins.chain.api.ChainApi ChainApi]], this is the entry point in to the
|
* of [[org.bitcoins.chain.api.ChainApi ChainApi]], this is the entry point in to the
|
||||||
* chain project.
|
* chain project.
|
||||||
*/
|
*/
|
||||||
case class ChainHandler(
|
case class ChainHandler(blockHeaderDAO: BlockHeaderDAO)(
|
||||||
blockHeaderDAO: BlockHeaderDAO,
|
implicit private[chain] val chainConfig: ChainAppConfig
|
||||||
chainConfig: ChainAppConfig)
|
) extends ChainApi
|
||||||
extends ChainApi
|
with ChainVerificationLogger {
|
||||||
with BitcoinSLogger {
|
|
||||||
|
|
||||||
override def getBlockCount(implicit ec: ExecutionContext): Future[Long] = {
|
override def getBlockCount(implicit ec: ExecutionContext): Future[Long] = {
|
||||||
logger.debug(s"Querying for block count")
|
logger.debug(s"Querying for block count")
|
||||||
|
@ -43,7 +42,8 @@ case class ChainHandler(
|
||||||
override def processHeader(header: BlockHeader)(
|
override def processHeader(header: BlockHeader)(
|
||||||
implicit ec: ExecutionContext): Future[ChainHandler] = {
|
implicit ec: ExecutionContext): Future[ChainHandler] = {
|
||||||
|
|
||||||
val blockchainUpdateF = Blockchain.connectTip(header, blockHeaderDAO)
|
val blockchainUpdateF =
|
||||||
|
Blockchain.connectTip(header, blockHeaderDAO)
|
||||||
|
|
||||||
val newHandlerF = blockchainUpdateF.flatMap {
|
val newHandlerF = blockchainUpdateF.flatMap {
|
||||||
case BlockchainUpdate.Successful(_, updatedHeader) =>
|
case BlockchainUpdate.Successful(_, updatedHeader) =>
|
||||||
|
@ -53,7 +53,7 @@ case class ChainHandler(
|
||||||
createdF.map { header =>
|
createdF.map { header =>
|
||||||
logger.debug(
|
logger.debug(
|
||||||
s"Connected new header to blockchain, height=${header.height} hash=${header.hashBE}")
|
s"Connected new header to blockchain, height=${header.height} hash=${header.hashBE}")
|
||||||
ChainHandler(blockHeaderDAO, chainConfig)
|
ChainHandler(blockHeaderDAO)
|
||||||
}
|
}
|
||||||
case BlockchainUpdate.Failed(_, _, reason) =>
|
case BlockchainUpdate.Failed(_, _, reason) =>
|
||||||
val errMsg =
|
val errMsg =
|
||||||
|
|
|
@ -5,11 +5,12 @@ import org.bitcoins.chain.blockchain.ChainHandler
|
||||||
import org.bitcoins.chain.models.BlockHeaderDb
|
import org.bitcoins.chain.models.BlockHeaderDb
|
||||||
import org.bitcoins.core.crypto.DoubleSha256DigestBE
|
import org.bitcoins.core.crypto.DoubleSha256DigestBE
|
||||||
import org.bitcoins.core.protocol.blockchain.BlockHeader
|
import org.bitcoins.core.protocol.blockchain.BlockHeader
|
||||||
import org.bitcoins.core.util.BitcoinSLogger
|
|
||||||
|
|
||||||
import scala.concurrent.{ExecutionContext, Future}
|
import scala.concurrent.{ExecutionContext, Future}
|
||||||
|
import org.bitcoins.chain.config.ChainAppConfig
|
||||||
|
import org.bitcoins.db.ChainVerificationLogger
|
||||||
|
|
||||||
trait ChainSync extends BitcoinSLogger {
|
trait ChainSync extends ChainVerificationLogger {
|
||||||
|
|
||||||
/** This method checks if our chain handler has the tip of the blockchain as an external source
|
/** This method checks if our chain handler has the tip of the blockchain as an external source
|
||||||
* If we do not have the same chain, we sync our chain handler until we are at the same best block hash
|
* If we do not have the same chain, we sync our chain handler until we are at the same best block hash
|
||||||
|
@ -20,9 +21,12 @@ trait ChainSync extends BitcoinSLogger {
|
||||||
* @param ec
|
* @param ec
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
def sync(chainHandler: ChainHandler,
|
def sync(
|
||||||
|
chainHandler: ChainHandler,
|
||||||
getBlockHeaderFunc: DoubleSha256DigestBE => Future[BlockHeader],
|
getBlockHeaderFunc: DoubleSha256DigestBE => Future[BlockHeader],
|
||||||
getBestBlockHashFunc: () => Future[DoubleSha256DigestBE])(implicit ec: ExecutionContext): Future[ChainApi] = {
|
getBestBlockHashFunc: () => Future[DoubleSha256DigestBE])(
|
||||||
|
implicit ec: ExecutionContext,
|
||||||
|
conf: ChainAppConfig): Future[ChainApi] = {
|
||||||
val currentTipsF: Future[Vector[BlockHeaderDb]] = {
|
val currentTipsF: Future[Vector[BlockHeaderDb]] = {
|
||||||
chainHandler.blockHeaderDAO.chainTips
|
chainHandler.blockHeaderDAO.chainTips
|
||||||
}
|
}
|
||||||
|
@ -50,7 +54,6 @@ trait ChainSync extends BitcoinSLogger {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Keeps walking backwards on the chain until we match one
|
* Keeps walking backwards on the chain until we match one
|
||||||
* of the tips we have in our chain
|
* of the tips we have in our chain
|
||||||
|
@ -61,17 +64,22 @@ trait ChainSync extends BitcoinSLogger {
|
||||||
* @param ec
|
* @param ec
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
private def syncTips(chainApi: ChainApi,
|
private def syncTips(
|
||||||
|
chainApi: ChainApi,
|
||||||
tips: Vector[BlockHeaderDb],
|
tips: Vector[BlockHeaderDb],
|
||||||
bestBlockHash: DoubleSha256DigestBE,
|
bestBlockHash: DoubleSha256DigestBE,
|
||||||
getBlockHeaderFunc: DoubleSha256DigestBE => Future[BlockHeader])(implicit ec: ExecutionContext): Future[ChainApi] = {
|
getBlockHeaderFunc: DoubleSha256DigestBE => Future[BlockHeader])(
|
||||||
|
implicit ec: ExecutionContext,
|
||||||
|
conf: ChainAppConfig): Future[ChainApi] = {
|
||||||
require(tips.nonEmpty, s"Cannot sync without the genesis block")
|
require(tips.nonEmpty, s"Cannot sync without the genesis block")
|
||||||
|
|
||||||
//we need to walk backwards on the chain until we get to one of our tips
|
//we need to walk backwards on the chain until we get to one of our tips
|
||||||
|
|
||||||
val tipsBH = tips.map(_.blockHeader)
|
val tipsBH = tips.map(_.blockHeader)
|
||||||
|
|
||||||
def loop(lastHeaderF: Future[BlockHeader], accum: List[BlockHeader]): Future[List[BlockHeader]] = {
|
def loop(
|
||||||
|
lastHeaderF: Future[BlockHeader],
|
||||||
|
accum: List[BlockHeader]): Future[List[BlockHeader]] = {
|
||||||
lastHeaderF.flatMap { lastHeader =>
|
lastHeaderF.flatMap { lastHeader =>
|
||||||
if (tipsBH.contains(lastHeader)) {
|
if (tipsBH.contains(lastHeader)) {
|
||||||
//means we have synced back to a block that we know
|
//means we have synced back to a block that we know
|
||||||
|
@ -81,9 +89,10 @@ trait ChainSync extends BitcoinSLogger {
|
||||||
logger.debug(s"Last header=${lastHeader.hashBE.hex}")
|
logger.debug(s"Last header=${lastHeader.hashBE.hex}")
|
||||||
//we don't know this block, so we need to keep walking backwards
|
//we don't know this block, so we need to keep walking backwards
|
||||||
//to find a block a we know
|
//to find a block a we know
|
||||||
val newLastHeaderF = getBlockHeaderFunc(lastHeader.previousBlockHashBE)
|
val newLastHeaderF =
|
||||||
|
getBlockHeaderFunc(lastHeader.previousBlockHashBE)
|
||||||
|
|
||||||
loop(newLastHeaderF,lastHeader +: accum)
|
loop(newLastHeaderF, lastHeader +: accum)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -91,7 +100,9 @@ trait ChainSync extends BitcoinSLogger {
|
||||||
val bestHeaderF = getBlockHeaderFunc(bestBlockHash)
|
val bestHeaderF = getBlockHeaderFunc(bestBlockHash)
|
||||||
|
|
||||||
bestHeaderF.map { bestHeader =>
|
bestHeaderF.map { bestHeader =>
|
||||||
logger.info(s"Best tip from third party=${bestHeader.hashBE.hex} currentTips=${tips.map(_.hashBE.hex)}")
|
logger.info(
|
||||||
|
s"Best tip from third party=${bestHeader.hashBE.hex} currentTips=${tips
|
||||||
|
.map(_.hashBE.hex)}")
|
||||||
}
|
}
|
||||||
|
|
||||||
//one sanity check to make sure we aren't _ahead_ of our data source
|
//one sanity check to make sure we aren't _ahead_ of our data source
|
||||||
|
@ -110,7 +121,8 @@ trait ChainSync extends BitcoinSLogger {
|
||||||
|
|
||||||
//now we are going to add them to our chain and return the chain api
|
//now we are going to add them to our chain and return the chain api
|
||||||
headersToSyncF.flatMap { headers =>
|
headersToSyncF.flatMap { headers =>
|
||||||
logger.info(s"Attempting to sync ${headers.length} blockheader to our chainstate")
|
logger.info(
|
||||||
|
s"Attempting to sync ${headers.length} blockheader to our chainstate")
|
||||||
chainApi.processHeaders(headers.toVector)
|
chainApi.processHeaders(headers.toVector)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -120,5 +132,4 @@ trait ChainSync extends BitcoinSLogger {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
object ChainSync extends ChainSync
|
object ChainSync extends ChainSync
|
|
@ -11,13 +11,24 @@ import scala.concurrent.Future
|
||||||
import scala.concurrent.Promise
|
import scala.concurrent.Promise
|
||||||
import scala.util.Success
|
import scala.util.Success
|
||||||
import scala.util.Failure
|
import scala.util.Failure
|
||||||
|
import java.nio.file.Path
|
||||||
|
|
||||||
case class ChainAppConfig(private val confs: Config*) extends AppConfig {
|
/** Configuration for the Bitcoin-S chain verification module
|
||||||
override protected val configOverrides: List[Config] = confs.toList
|
* @param directory The data directory of the module
|
||||||
override protected val moduleName: String = "chain"
|
* @param confs Optional sequence of configuration overrides
|
||||||
override protected type ConfigType = ChainAppConfig
|
*/
|
||||||
override protected def newConfigOfType(configs: Seq[Config]): ChainAppConfig =
|
case class ChainAppConfig(
|
||||||
ChainAppConfig(configs: _*)
|
private val directory: Path,
|
||||||
|
private val confs: Config*)
|
||||||
|
extends AppConfig {
|
||||||
|
override protected[bitcoins] def configOverrides: List[Config] = confs.toList
|
||||||
|
override protected[bitcoins] val moduleName: String = "chain"
|
||||||
|
override protected[bitcoins] type ConfigType = ChainAppConfig
|
||||||
|
override protected[bitcoins] def newConfigOfType(
|
||||||
|
configs: Seq[Config]): ChainAppConfig =
|
||||||
|
ChainAppConfig(directory, configs: _*)
|
||||||
|
|
||||||
|
protected[bitcoins] def baseDatadir: Path = directory
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks whether or not the chain project is initialized by
|
* Checks whether or not the chain project is initialized by
|
||||||
|
@ -70,3 +81,12 @@ case class ChainAppConfig(private val confs: Config*) extends AppConfig {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
object ChainAppConfig {
|
||||||
|
|
||||||
|
/** Constructs a chain verification configuration from the default Bitcoin-S
|
||||||
|
* data directory and given list of configuration overrides.
|
||||||
|
*/
|
||||||
|
def fromDefaultDatadir(confs: Config*): ChainAppConfig =
|
||||||
|
ChainAppConfig(AppConfig.DEFAULT_BITCOIN_S_DATADIR, confs: _*)
|
||||||
|
}
|
||||||
|
|
|
@ -16,8 +16,8 @@ import scala.concurrent.{ExecutionContext, Future}
|
||||||
* our chain project
|
* our chain project
|
||||||
*/
|
*/
|
||||||
case class BlockHeaderDAO()(
|
case class BlockHeaderDAO()(
|
||||||
implicit override val ec: ExecutionContext,
|
implicit ec: ExecutionContext,
|
||||||
override val appConfig: ChainAppConfig)
|
appConfig: ChainAppConfig)
|
||||||
extends CRUD[BlockHeaderDb, DoubleSha256DigestBE] {
|
extends CRUD[BlockHeaderDb, DoubleSha256DigestBE] {
|
||||||
|
|
||||||
import org.bitcoins.db.DbCommonsColumnMappers._
|
import org.bitcoins.db.DbCommonsColumnMappers._
|
||||||
|
|
|
@ -2,8 +2,9 @@ package org.bitcoins.chain.pow
|
||||||
|
|
||||||
import org.bitcoins.chain.models.{BlockHeaderDAO, BlockHeaderDb}
|
import org.bitcoins.chain.models.{BlockHeaderDAO, BlockHeaderDb}
|
||||||
import org.bitcoins.core.number.UInt32
|
import org.bitcoins.core.number.UInt32
|
||||||
|
import org.bitcoins.chain.config.ChainAppConfig
|
||||||
import org.bitcoins.core.protocol.blockchain.{BlockHeader, ChainParams}
|
import org.bitcoins.core.protocol.blockchain.{BlockHeader, ChainParams}
|
||||||
import org.bitcoins.core.util.{BitcoinSLogger, NumberUtil}
|
import org.bitcoins.core.util.NumberUtil
|
||||||
|
|
||||||
import scala.concurrent.{ExecutionContext, Future}
|
import scala.concurrent.{ExecutionContext, Future}
|
||||||
|
|
||||||
|
@ -11,7 +12,7 @@ import scala.concurrent.{ExecutionContext, Future}
|
||||||
* Implements functions found inside of bitcoin core's
|
* Implements functions found inside of bitcoin core's
|
||||||
* @see [[https://github.com/bitcoin/bitcoin/blob/35477e9e4e3f0f207ac6fa5764886b15bf9af8d0/src/pow.cpp pow.cpp]]
|
* @see [[https://github.com/bitcoin/bitcoin/blob/35477e9e4e3f0f207ac6fa5764886b15bf9af8d0/src/pow.cpp pow.cpp]]
|
||||||
*/
|
*/
|
||||||
sealed abstract class Pow extends BitcoinSLogger {
|
sealed abstract class Pow {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the next proof of work requirement for a block
|
* Gets the next proof of work requirement for a block
|
||||||
|
@ -24,8 +25,9 @@ sealed abstract class Pow extends BitcoinSLogger {
|
||||||
tip: BlockHeaderDb,
|
tip: BlockHeaderDb,
|
||||||
newPotentialTip: BlockHeader,
|
newPotentialTip: BlockHeader,
|
||||||
blockHeaderDAO: BlockHeaderDAO)(
|
blockHeaderDAO: BlockHeaderDAO)(
|
||||||
implicit ec: ExecutionContext): Future[UInt32] = {
|
implicit ec: ExecutionContext,
|
||||||
val chainParams = blockHeaderDAO.appConfig.chain
|
config: ChainAppConfig): Future[UInt32] = {
|
||||||
|
val chainParams = config.chain
|
||||||
val currentHeight = tip.height
|
val currentHeight = tip.height
|
||||||
|
|
||||||
val powLimit = NumberUtil.targetCompression(bigInteger =
|
val powLimit = NumberUtil.targetCompression(bigInteger =
|
||||||
|
|
|
@ -8,9 +8,11 @@ import org.bitcoins.chain.models.{
|
||||||
import org.bitcoins.chain.pow.Pow
|
import org.bitcoins.chain.pow.Pow
|
||||||
import org.bitcoins.core.number.UInt32
|
import org.bitcoins.core.number.UInt32
|
||||||
import org.bitcoins.core.protocol.blockchain.BlockHeader
|
import org.bitcoins.core.protocol.blockchain.BlockHeader
|
||||||
import org.bitcoins.core.util.{BitcoinSLogger, NumberUtil}
|
import org.bitcoins.core.util.NumberUtil
|
||||||
|
|
||||||
import scala.concurrent.{ExecutionContext, Future}
|
import scala.concurrent.{ExecutionContext, Future}
|
||||||
|
import org.bitcoins.chain.config.ChainAppConfig
|
||||||
|
import org.bitcoins.db.ChainVerificationLogger
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Responsible for checking if we can connect two
|
* Responsible for checking if we can connect two
|
||||||
|
@ -18,7 +20,7 @@ import scala.concurrent.{ExecutionContext, Future}
|
||||||
* things like proof of work difficulty, if it
|
* things like proof of work difficulty, if it
|
||||||
* references the previous block header correctly etc.
|
* references the previous block header correctly etc.
|
||||||
*/
|
*/
|
||||||
sealed abstract class TipValidation extends BitcoinSLogger {
|
sealed abstract class TipValidation extends ChainVerificationLogger {
|
||||||
|
|
||||||
/** Checks if the given header can be connected to the current tip
|
/** Checks if the given header can be connected to the current tip
|
||||||
* This is the method where a [[org.bitcoins.core.protocol.blockchain.BlockHeader BlockHeader]] is transformed into a
|
* This is the method where a [[org.bitcoins.core.protocol.blockchain.BlockHeader BlockHeader]] is transformed into a
|
||||||
|
@ -30,7 +32,8 @@ sealed abstract class TipValidation extends BitcoinSLogger {
|
||||||
newPotentialTip: BlockHeader,
|
newPotentialTip: BlockHeader,
|
||||||
currentTip: BlockHeaderDb,
|
currentTip: BlockHeaderDb,
|
||||||
blockHeaderDAO: BlockHeaderDAO)(
|
blockHeaderDAO: BlockHeaderDAO)(
|
||||||
implicit ec: ExecutionContext): Future[TipUpdateResult] = {
|
implicit ec: ExecutionContext,
|
||||||
|
conf: ChainAppConfig): Future[TipUpdateResult] = {
|
||||||
val header = newPotentialTip
|
val header = newPotentialTip
|
||||||
logger.trace(
|
logger.trace(
|
||||||
s"Checking header=${header.hashBE.hex} to try to connect to currentTip=${currentTip.hashBE.hex} with height=${currentTip.height}")
|
s"Checking header=${header.hashBE.hex} to try to connect to currentTip=${currentTip.hashBE.hex} with height=${currentTip.height}")
|
||||||
|
@ -67,7 +70,9 @@ sealed abstract class TipValidation extends BitcoinSLogger {
|
||||||
/** Logs the result of [[org.bitcoins.chain.validation.TipValidation.checkNewTip() checkNewTip]] */
|
/** Logs the result of [[org.bitcoins.chain.validation.TipValidation.checkNewTip() checkNewTip]] */
|
||||||
private def logTipResult(
|
private def logTipResult(
|
||||||
connectTipResultF: Future[TipUpdateResult],
|
connectTipResultF: Future[TipUpdateResult],
|
||||||
currentTip: BlockHeaderDb)(implicit ec: ExecutionContext): Unit = {
|
currentTip: BlockHeaderDb)(
|
||||||
|
implicit ec: ExecutionContext,
|
||||||
|
conf: ChainAppConfig): Unit = {
|
||||||
connectTipResultF.map {
|
connectTipResultF.map {
|
||||||
case TipUpdateResult.Success(tipDb) =>
|
case TipUpdateResult.Success(tipDb) =>
|
||||||
logger.trace(
|
logger.trace(
|
||||||
|
@ -102,7 +107,8 @@ sealed abstract class TipValidation extends BitcoinSLogger {
|
||||||
newPotentialTip: BlockHeader,
|
newPotentialTip: BlockHeader,
|
||||||
currentTip: BlockHeaderDb,
|
currentTip: BlockHeaderDb,
|
||||||
blockHeaderDAO: BlockHeaderDAO)(
|
blockHeaderDAO: BlockHeaderDAO)(
|
||||||
implicit ec: ExecutionContext): Future[UInt32] = {
|
implicit ec: ExecutionContext,
|
||||||
|
config: ChainAppConfig): Future[UInt32] = {
|
||||||
Pow.getNetworkWorkRequired(tip = currentTip,
|
Pow.getNetworkWorkRequired(tip = currentTip,
|
||||||
newPotentialTip = newPotentialTip,
|
newPotentialTip = newPotentialTip,
|
||||||
blockHeaderDAO = blockHeaderDAO)
|
blockHeaderDAO = blockHeaderDAO)
|
||||||
|
|
|
@ -3,8 +3,8 @@ package org.bitcoins.core.p2p
|
||||||
import org.bitcoins.testkit.core.gen.p2p.DataMessageGenerator
|
import org.bitcoins.testkit.core.gen.p2p.DataMessageGenerator
|
||||||
import org.bitcoins.testkit.util.BitcoinSUnitTest
|
import org.bitcoins.testkit.util.BitcoinSUnitTest
|
||||||
import scodec.bits._
|
import scodec.bits._
|
||||||
import org.bitcoins.node.util.BitcoinSpvNodeUtil
|
|
||||||
import org.bitcoins.core.crypto.DoubleSha256Digest
|
import org.bitcoins.core.crypto.DoubleSha256Digest
|
||||||
|
import org.bitcoins.node.networking.P2PClient
|
||||||
|
|
||||||
class MerkleBlockMessageTest extends BitcoinSUnitTest {
|
class MerkleBlockMessageTest extends BitcoinSUnitTest {
|
||||||
it must "have serialization symmetry" in {
|
it must "have serialization symmetry" in {
|
||||||
|
@ -42,15 +42,4 @@ class MerkleBlockMessageTest extends BitcoinSUnitTest {
|
||||||
assert(fourth == expectedFourth)
|
assert(fourth == expectedFourth)
|
||||||
}
|
}
|
||||||
|
|
||||||
// we had a bug where we didn't consume the right number of bytes
|
|
||||||
// when parsing a merkle block message, thereby screwing up
|
|
||||||
// the parsing of the remainder
|
|
||||||
it must "parse a byte vector with three messages in it" in {
|
|
||||||
val bytes =
|
|
||||||
hex"fabfb5da6d65726b6c65626c6f636b0097000000b4b6e45d00000020387191f7d488b849b4080fdf105c71269fc841a2f0f2944fc5dc785c830c716e37f36373098aae06a668cc74e388caf50ecdcb5504ce936490b4b72940e08859548c305dffff7f20010000000200000002ecd1c722709bfc241f8b94fc64034dcba2c95409dc4cd1d7b864e1128a04e5b044133327b04ff8ac576e7748a4dae4111f0c765dacbfe0c5a9fddbeb8f60d5af0105fabfb5da747800000000000000000000cc0100004413332702000000065b7f0f3eec398047e921037815aa41709b6243a1897f1423194b7558399ae0300000000017160014008dc9d88d1797305f3fbd30d2b36d6bde984a09feffffffe9145055d671fd705a09f028033da614b619205b9926fe5ebe45e15ae8b3231e0100000017160014d74cfac04bb0e6838c35f1f4a0a60d13655be2fbfeffffff797f8ff9c10fa618b6254343a648be995410e82c03fd8accb0de2271a3fb1abd00000000171600143ee832c09db48eca28a64a358ed7a01dbe52d31bfeffffffc794dba971b9479dfcbc662a3aacd641553bdb2418b15c0221c5dfd4471a7a70000000001716001452c13ba0314f7718c234ed6adfea6422ce03a545feffffffb7c3bf1762b15f3b0e0eaa5beb46fe96a9e2829a7413fd900b9b7e0d192ab64800000000171600143ee832c09db48eca28a64a358ed7a01dbe52d31bfeffffffb6ced6cb8dfc2f7f5b37561938ead3bc5ca4036e2b45d9738cc086a10eed4e010100000017160014aebb17e245fe8c98a75f0b6717fcadca30e491e2feffffff02002a7515000000001976a9148374ff8beb55ea2945039881ca26071b5749fafe88ac485620000000000017a91405d36a2b0bdedf3fc58bed6f9e4026f8934a2716876b050000fabfb5da686561646572730000000000010000001406e05800"
|
|
||||||
val (messages, leftover) = BitcoinSpvNodeUtil.parseIndividualMessages(bytes)
|
|
||||||
assert(messages.length == 3)
|
|
||||||
assert(leftover.isEmpty)
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,41 @@ bitcoin-s {
|
||||||
datadir = ${HOME}/.bitcoin-s
|
datadir = ${HOME}/.bitcoin-s
|
||||||
network = regtest # regtest, testnet3, mainnet
|
network = regtest # regtest, testnet3, mainnet
|
||||||
|
|
||||||
|
logging {
|
||||||
|
level = info # trace, debug, info, warn, error, off
|
||||||
|
|
||||||
|
# You can also tune specific module loggers.
|
||||||
|
# They each take the same levels as above.
|
||||||
|
# If they are commented out (as they are
|
||||||
|
# by default), `logging.level` gets used
|
||||||
|
# instead.
|
||||||
|
# The available loggers are:
|
||||||
|
|
||||||
|
# incoming and outgoing P2P messages
|
||||||
|
# p2p = info
|
||||||
|
|
||||||
|
# verification of block headers, merkle trees
|
||||||
|
# chain-verification = info
|
||||||
|
|
||||||
|
# generation of addresses, signing of TXs
|
||||||
|
# key-handling = info
|
||||||
|
|
||||||
|
# wallet operations not related to key management
|
||||||
|
# wallet = info
|
||||||
|
|
||||||
|
# HTTP RPC server
|
||||||
|
# http = info
|
||||||
|
|
||||||
|
# Database interactions
|
||||||
|
# database = info
|
||||||
|
|
||||||
|
# whether or not to write to the log file
|
||||||
|
disable-file = false
|
||||||
|
|
||||||
|
# whether or not to log to stdout
|
||||||
|
disable-console = false
|
||||||
|
}
|
||||||
|
|
||||||
# settings for wallet module
|
# settings for wallet module
|
||||||
wallet {
|
wallet {
|
||||||
defaultAccountType = legacy # legacy, segwit, nested-segwit
|
defaultAccountType = legacy # legacy, segwit, nested-segwit
|
||||||
|
|
|
@ -26,6 +26,7 @@ import scala.util.Properties
|
||||||
import scala.util.matching.Regex
|
import scala.util.matching.Regex
|
||||||
import scala.concurrent.ExecutionContext
|
import scala.concurrent.ExecutionContext
|
||||||
import scala.concurrent.Future
|
import scala.concurrent.Future
|
||||||
|
import ch.qos.logback.classic.Level
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Everything needed to configure functionality
|
* Everything needed to configure functionality
|
||||||
|
@ -51,15 +52,16 @@ abstract class AppConfig extends BitcoinSLogger {
|
||||||
* the type of themselves, ensuring `withOverrides` return
|
* the type of themselves, ensuring `withOverrides` return
|
||||||
* the correct type
|
* the correct type
|
||||||
*/
|
*/
|
||||||
protected type ConfigType <: AppConfig
|
protected[bitcoins] type ConfigType <: AppConfig
|
||||||
|
|
||||||
/** Constructor to make a new instance of this config type */
|
/** Constructor to make a new instance of this config type */
|
||||||
protected def newConfigOfType(configOverrides: Seq[Config]): ConfigType
|
protected[bitcoins] def newConfigOfType(
|
||||||
|
configOverrides: Seq[Config]): ConfigType
|
||||||
|
|
||||||
/** List of user-provided configs that should
|
/** List of user-provided configs that should
|
||||||
* override defaults
|
* override defaults
|
||||||
*/
|
*/
|
||||||
protected val configOverrides: List[Config] = List.empty
|
protected[bitcoins] def configOverrides: List[Config] = List.empty
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This method returns a new `AppConfig`, where every
|
* This method returns a new `AppConfig`, where every
|
||||||
|
@ -121,7 +123,7 @@ abstract class AppConfig extends BitcoinSLogger {
|
||||||
/**
|
/**
|
||||||
* Name of the module. `chain`, `wallet`, `node` etc.
|
* Name of the module. `chain`, `wallet`, `node` etc.
|
||||||
*/
|
*/
|
||||||
protected def moduleName: String
|
protected[bitcoins] def moduleName: String
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The configuration details for connecting/using the database for our projects
|
* The configuration details for connecting/using the database for our projects
|
||||||
|
@ -206,6 +208,27 @@ abstract class AppConfig extends BitcoinSLogger {
|
||||||
* rest of the fields in this class from
|
* rest of the fields in this class from
|
||||||
*/
|
*/
|
||||||
private[bitcoins] lazy val config: Config = {
|
private[bitcoins] lazy val config: Config = {
|
||||||
|
|
||||||
|
val datadirConfig = {
|
||||||
|
val file = baseDatadir.resolve("bitcoin-s.conf")
|
||||||
|
val config = if (Files.isReadable(file)) {
|
||||||
|
ConfigFactory.parseFile(file.toFile())
|
||||||
|
} else {
|
||||||
|
ConfigFactory.empty()
|
||||||
|
}
|
||||||
|
|
||||||
|
val withDatadir =
|
||||||
|
ConfigFactory.parseString(s"bitcoin-s.datadir = $baseDatadir")
|
||||||
|
withDatadir.withFallback(config)
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.trace(s"Data directory config:")
|
||||||
|
if (datadirConfig.hasPath("bitcoin-s")) {
|
||||||
|
logger.trace(datadirConfig.getConfig("bitcoin-s").asReadableJson)
|
||||||
|
} else {
|
||||||
|
logger.trace(ConfigFactory.empty().asReadableJson)
|
||||||
|
}
|
||||||
|
|
||||||
// `load` tries to resolve substitions,
|
// `load` tries to resolve substitions,
|
||||||
// `parseResources` does not
|
// `parseResources` does not
|
||||||
val dbConfig = ConfigFactory
|
val dbConfig = ConfigFactory
|
||||||
|
@ -226,9 +249,12 @@ abstract class AppConfig extends BitcoinSLogger {
|
||||||
logger.trace(
|
logger.trace(
|
||||||
s"Classpath config: ${classPathConfig.getConfig("bitcoin-s").asReadableJson}")
|
s"Classpath config: ${classPathConfig.getConfig("bitcoin-s").asReadableJson}")
|
||||||
|
|
||||||
// loads reference.conf as well as application.conf,
|
// we want the data directory configuration
|
||||||
// if the user has made one
|
// to take preference over any bundled (classpath)
|
||||||
val unresolvedConfig = classPathConfig
|
// configurations
|
||||||
|
// loads reference.conf (provided by Bitcoin-S)
|
||||||
|
val unresolvedConfig = datadirConfig
|
||||||
|
.withFallback(classPathConfig)
|
||||||
.withFallback(dbConfig)
|
.withFallback(dbConfig)
|
||||||
|
|
||||||
logger.trace(s"Unresolved bitcoin-s config:")
|
logger.trace(s"Unresolved bitcoin-s config:")
|
||||||
|
@ -256,32 +282,92 @@ abstract class AppConfig extends BitcoinSLogger {
|
||||||
unresolvedConfig
|
unresolvedConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
val config = withOverrides
|
val finalConfig = withOverrides
|
||||||
.resolve()
|
.resolve()
|
||||||
.getConfig("bitcoin-s")
|
.getConfig("bitcoin-s")
|
||||||
|
|
||||||
logger.debug(s"Resolved bitcoin-s config:")
|
logger.debug(s"Resolved bitcoin-s config:")
|
||||||
logger.debug(config.asReadableJson)
|
logger.debug(finalConfig.asReadableJson)
|
||||||
|
|
||||||
config
|
finalConfig
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** The data directory used by bitcoin-s apps */
|
/** The base data directory. This is where we look for a configuration file */
|
||||||
lazy val datadir: Path = {
|
protected[bitcoins] def baseDatadir: Path
|
||||||
val basedir = Paths.get(config.getString("datadir"))
|
|
||||||
|
/** The network specific data directory. */
|
||||||
|
val datadir: Path = {
|
||||||
val lastDirname = network match {
|
val lastDirname = network match {
|
||||||
case MainNet => "mainnet"
|
case MainNet => "mainnet"
|
||||||
case TestNet3 => "testnet3"
|
case TestNet3 => "testnet3"
|
||||||
case RegTest => "regtest"
|
case RegTest => "regtest"
|
||||||
}
|
}
|
||||||
basedir.resolve(lastDirname)
|
baseDatadir.resolve(lastDirname)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private def stringToLogLevel(str: String): Level =
|
||||||
|
str.toLowerCase() match {
|
||||||
|
case "trace" => Level.TRACE
|
||||||
|
case "debug" => Level.DEBUG
|
||||||
|
case "info" => Level.INFO
|
||||||
|
case "warn" => Level.WARN
|
||||||
|
case "error" => Level.ERROR
|
||||||
|
case "off" => Level.OFF
|
||||||
|
case other: String => sys.error(s"Unknown logging level: $other")
|
||||||
|
}
|
||||||
|
|
||||||
|
/** The default logging level */
|
||||||
|
lazy val logLevel: Level = {
|
||||||
|
val levelString = config.getString("logging.level")
|
||||||
|
stringToLogLevel(levelString)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Whether or not we should log to file */
|
||||||
|
lazy val disableFileLogging = config.getBoolean("logging.disable-file")
|
||||||
|
|
||||||
|
/** Whether or not we should log to stdout */
|
||||||
|
lazy val disableConsoleLogging = config.getBoolean("logging.disable-console")
|
||||||
|
|
||||||
|
private def levelOrDefault(key: String): Level =
|
||||||
|
config
|
||||||
|
.getStringOrNone(key)
|
||||||
|
.map(stringToLogLevel)
|
||||||
|
.getOrElse(logLevel)
|
||||||
|
|
||||||
|
/** The logging level for our P2P logger */
|
||||||
|
lazy val p2pLogLevel: Level = levelOrDefault("logging.p2p")
|
||||||
|
|
||||||
|
/** The logging level for our chain verification logger */
|
||||||
|
lazy val verificationLogLevel: Level =
|
||||||
|
levelOrDefault("logging.chain-verification")
|
||||||
|
|
||||||
|
/** The logging level for our key handling logger */
|
||||||
|
lazy val keyHandlingLogLevel: Level =
|
||||||
|
levelOrDefault("logging.key-handling")
|
||||||
|
|
||||||
|
/** Logging level for wallet */
|
||||||
|
lazy val walletLogLeveL: Level =
|
||||||
|
levelOrDefault("logging.wallet")
|
||||||
|
|
||||||
|
/** Logging level for HTTP RPC server */
|
||||||
|
lazy val httpLogLevel: Level = levelOrDefault("logging.http")
|
||||||
|
|
||||||
|
/** Logging level for database interactions */
|
||||||
|
lazy val databaseLogLevel: Level = levelOrDefault("logging.database")
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
object AppConfig extends BitcoinSLogger {
|
object AppConfig extends BitcoinSLogger {
|
||||||
|
|
||||||
|
/** The default data directory
|
||||||
|
*
|
||||||
|
* TODO: use different directories on Windows and Mac,
|
||||||
|
* should probably mimic what Bitcoin Core does
|
||||||
|
*/
|
||||||
|
private[bitcoins] val DEFAULT_BITCOIN_S_DATADIR: Path =
|
||||||
|
Paths.get(Properties.userHome, ".bitcoin-s")
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Matches the default data directory location
|
* Matches the default data directory location
|
||||||
* with a network appended,
|
* with a network appended,
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
package org.bitcoins.db
|
package org.bitcoins.db
|
||||||
|
|
||||||
import org.bitcoins.core.util.BitcoinSLogger
|
|
||||||
import slick.jdbc.SQLiteProfile.api._
|
import slick.jdbc.SQLiteProfile.api._
|
||||||
|
|
||||||
import scala.concurrent.{ExecutionContext, Future}
|
import scala.concurrent.{ExecutionContext, Future}
|
||||||
|
@ -15,16 +14,16 @@ import org.bitcoins.core.config.MainNet
|
||||||
* You are responsible for the create function. You also need to specify
|
* You are responsible for the create function. You also need to specify
|
||||||
* the table and the database you are connecting to.
|
* the table and the database you are connecting to.
|
||||||
*/
|
*/
|
||||||
abstract class CRUD[T, PrimaryKeyType] extends BitcoinSLogger {
|
abstract class CRUD[T, PrimaryKeyType](
|
||||||
|
implicit private val config: AppConfig,
|
||||||
def appConfig: AppConfig
|
private val ec: ExecutionContext)
|
||||||
implicit val ec: ExecutionContext
|
extends DatabaseLogger {
|
||||||
|
|
||||||
/** The table inside our database we are inserting into */
|
/** The table inside our database we are inserting into */
|
||||||
val table: TableQuery[_ <: Table[T]]
|
val table: TableQuery[_ <: Table[T]]
|
||||||
|
|
||||||
/** Binding to the actual database itself, this is what is used to run querys */
|
/** Binding to the actual database itself, this is what is used to run querys */
|
||||||
def database: SafeDatabase = SafeDatabase(appConfig)
|
def database: SafeDatabase = SafeDatabase(config)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* create a record in the database
|
* create a record in the database
|
||||||
|
@ -33,7 +32,7 @@ abstract class CRUD[T, PrimaryKeyType] extends BitcoinSLogger {
|
||||||
* @return the inserted record
|
* @return the inserted record
|
||||||
*/
|
*/
|
||||||
def create(t: T): Future[T] = {
|
def create(t: T): Future[T] = {
|
||||||
logger.trace(s"Writing $t to DB with config: ${appConfig.config}")
|
logger.trace(s"Writing $t to DB with config: ${config.config}")
|
||||||
createAll(Vector(t)).map(_.head)
|
createAll(Vector(t)).map(_.head)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -46,7 +45,7 @@ abstract class CRUD[T, PrimaryKeyType] extends BitcoinSLogger {
|
||||||
* @return Option[T] - the record if found, else none
|
* @return Option[T] - the record if found, else none
|
||||||
*/
|
*/
|
||||||
def read(id: PrimaryKeyType): Future[Option[T]] = {
|
def read(id: PrimaryKeyType): Future[Option[T]] = {
|
||||||
logger.trace(s"Reading from DB with config: ${appConfig.config}")
|
logger.trace(s"Reading from DB with config: ${config.config}")
|
||||||
val query = findByPrimaryKey(id)
|
val query = findByPrimaryKey(id)
|
||||||
val rows: Future[Seq[T]] = database.run(query.result)
|
val rows: Future[Seq[T]] = database.run(query.result)
|
||||||
rows.map(_.headOption)
|
rows.map(_.headOption)
|
||||||
|
@ -130,7 +129,8 @@ abstract class CRUD[T, PrimaryKeyType] extends BitcoinSLogger {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
case class SafeDatabase(config: AppConfig) extends BitcoinSLogger {
|
case class SafeDatabase(config: AppConfig) extends DatabaseLogger {
|
||||||
|
implicit private val conf: AppConfig = config
|
||||||
|
|
||||||
import config.database
|
import config.database
|
||||||
|
|
||||||
|
|
|
@ -4,8 +4,12 @@ import slick.dbio.Effect.Write
|
||||||
import slick.jdbc.SQLiteProfile.api._
|
import slick.jdbc.SQLiteProfile.api._
|
||||||
|
|
||||||
import scala.concurrent.Future
|
import scala.concurrent.Future
|
||||||
|
import scala.concurrent.ExecutionContext
|
||||||
|
|
||||||
abstract class CRUDAutoInc[T <: DbRowAutoInc[T]] extends CRUD[T, Long] {
|
abstract class CRUDAutoInc[T <: DbRowAutoInc[T]](
|
||||||
|
implicit config: AppConfig,
|
||||||
|
ec: ExecutionContext)
|
||||||
|
extends CRUD[T, Long] {
|
||||||
|
|
||||||
/** The table inside our database we are inserting into */
|
/** The table inside our database we are inserting into */
|
||||||
override val table: TableQuery[_ <: TableAutoInc[T]]
|
override val table: TableQuery[_ <: TableAutoInc[T]]
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
package org.bitcoins.db
|
package org.bitcoins.db
|
||||||
|
|
||||||
import org.bitcoins.core.util.BitcoinSLogger
|
|
||||||
import slick.jdbc.SQLiteProfile.api._
|
import slick.jdbc.SQLiteProfile.api._
|
||||||
|
|
||||||
import scala.concurrent.{ExecutionContext, Future}
|
import scala.concurrent.{ExecutionContext, Future}
|
||||||
|
|
||||||
abstract class DbManagement extends BitcoinSLogger {
|
abstract class DbManagement extends DatabaseLogger {
|
||||||
def allTables: List[TableQuery[_ <: Table[_]]]
|
def allTables: List[TableQuery[_ <: Table[_]]]
|
||||||
|
|
||||||
/** Lists all tables in the given database */
|
/** Lists all tables in the given database */
|
||||||
|
|
|
@ -11,6 +11,24 @@ package object db {
|
||||||
val options = ConfigRenderOptions.concise().setFormatted(true)
|
val options = ConfigRenderOptions.concise().setFormatted(true)
|
||||||
config.root().render(options)
|
config.root().render(options)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Returns the string at key or the given default value */
|
||||||
|
def getStringOrElse(key: String, default: => String): String = {
|
||||||
|
if (config.hasPath(key)) {
|
||||||
|
config.getString(key)
|
||||||
|
} else {
|
||||||
|
default
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns the string at the given key, if it exists */
|
||||||
|
def getStringOrNone(key: String): Option[String] = {
|
||||||
|
if (config.hasPath(key)) {
|
||||||
|
Some(config.getString(key))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,6 +13,42 @@ It's possible to communicate with other developers through a variety of communic
|
||||||
- [Bitcoin-S Gitter](https://gitter.im/bitcoin-s-core/)
|
- [Bitcoin-S Gitter](https://gitter.im/bitcoin-s-core/)
|
||||||
- [#bitcoin-scala](https://webchat.freenode.net/?channels=bitcoin-scala) on IRC Freenode
|
- [#bitcoin-scala](https://webchat.freenode.net/?channels=bitcoin-scala) on IRC Freenode
|
||||||
|
|
||||||
|
## Working on Bitcoin-S applications
|
||||||
|
|
||||||
|
Bitcoin-S includes a couple of applications that can be run as standalone executables.
|
||||||
|
This includes the node, wallet and (partial) blockchain verification modules, as well
|
||||||
|
as the server that bundles these three together and the CLI used to communicate with
|
||||||
|
the server. These applications are configured with HOCON files. The file
|
||||||
|
[`reference.conf`](https://github.com/bitcoin-s/bitcoin-s/blob/master/db-commons/src/main/resources/reference.conf)
|
||||||
|
is the basis configuration file, and every option read by Bitcoin-S should be present in
|
||||||
|
this file. This means that you can copy sections from this file and edit them, to tune
|
||||||
|
how the application runs on your machine.
|
||||||
|
|
||||||
|
One example of things you can tune is logging levels. Lets say you wanted general logging
|
||||||
|
to happen at the `WARN` level, but the P2P message handling to be logged at `DEBUG`. Your
|
||||||
|
configuration file would then look like:
|
||||||
|
|
||||||
|
```conf
|
||||||
|
bitcoins-s {
|
||||||
|
logging {
|
||||||
|
level = warn
|
||||||
|
|
||||||
|
p2p = debug
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Running the applications
|
||||||
|
|
||||||
|
When running the applications configuration placed in `bitcoin-s.conf` in the current
|
||||||
|
data directory gets picked up. For linux this is by default `$HOME/.bitcoin-s/`, so the
|
||||||
|
file you should edit would be `$HOME/.bitcoin-s/bitcoin-s.conf`.
|
||||||
|
|
||||||
|
### Running tests for the applications
|
||||||
|
|
||||||
|
You can place configuration files in the data directory that tests are being run in,
|
||||||
|
but you can also edit [`reference.conf`](https://github.com/bitcoin-s/bitcoin-s/blob/master/db-commons/src/main/resources/reference.conf).
|
||||||
|
|
||||||
## Developer productivity
|
## Developer productivity
|
||||||
|
|
||||||
### Bloop
|
### Bloop
|
||||||
|
|
|
@ -61,7 +61,7 @@ class BroadcastTransactionTest extends BitcoinSWalletTest {
|
||||||
val peer = Peer.fromBitcoind(rpc.instance)
|
val peer = Peer.fromBitcoind(rpc.instance)
|
||||||
val chainHandler = {
|
val chainHandler = {
|
||||||
val bhDao = BlockHeaderDAO()
|
val bhDao = BlockHeaderDAO()
|
||||||
ChainHandler(bhDao, config)
|
ChainHandler(bhDao)
|
||||||
}
|
}
|
||||||
|
|
||||||
val spv =
|
val spv =
|
||||||
|
|
|
@ -7,9 +7,12 @@ import com.typesafe.config.Config
|
||||||
import com.typesafe.config.ConfigFactory
|
import com.typesafe.config.ConfigFactory
|
||||||
import org.bitcoins.core.config.RegTest
|
import org.bitcoins.core.config.RegTest
|
||||||
import org.bitcoins.core.config.MainNet
|
import org.bitcoins.core.config.MainNet
|
||||||
|
import ch.qos.logback.classic.Level
|
||||||
|
import java.nio.file.Files
|
||||||
|
|
||||||
class NodeAppConfigTest extends BitcoinSUnitTest {
|
class NodeAppConfigTest extends BitcoinSUnitTest {
|
||||||
val config = NodeAppConfig()
|
val tempDir = Files.createTempDirectory("bitcoin-s")
|
||||||
|
val config = NodeAppConfig(directory = tempDir)
|
||||||
|
|
||||||
it must "be overridable" in {
|
it must "be overridable" in {
|
||||||
assert(config.network == RegTest)
|
assert(config.network == RegTest)
|
||||||
|
@ -30,4 +33,29 @@ class NodeAppConfigTest extends BitcoinSUnitTest {
|
||||||
assert(overriden.network == MainNet)
|
assert(overriden.network == MainNet)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
it must "have user data directory configuration take precedence" in {
|
||||||
|
|
||||||
|
val tempDir = Files.createTempDirectory("bitcoin-s")
|
||||||
|
val tempFile = Files.createFile(tempDir.resolve("bitcoin-s.conf"))
|
||||||
|
val confStr = """
|
||||||
|
| bitcoin-s {
|
||||||
|
| network = testnet3
|
||||||
|
|
|
||||||
|
| logging {
|
||||||
|
| level = off
|
||||||
|
|
|
||||||
|
| p2p = warn
|
||||||
|
| }
|
||||||
|
| }
|
||||||
|
""".stripMargin
|
||||||
|
val _ = Files.write(tempFile, confStr.getBytes())
|
||||||
|
|
||||||
|
val appConfig = NodeAppConfig(directory = tempDir)
|
||||||
|
|
||||||
|
assert(appConfig.datadir == tempDir.resolve("testnet3"))
|
||||||
|
assert(appConfig.network == TestNet3)
|
||||||
|
assert(appConfig.logLevel == Level.OFF)
|
||||||
|
assert(appConfig.p2pLogLevel == Level.WARN)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -107,7 +107,7 @@ class NodeWithWalletTest extends BitcoinSWalletTest {
|
||||||
val peer = Peer.fromBitcoind(rpc.instance)
|
val peer = Peer.fromBitcoind(rpc.instance)
|
||||||
val chainHandler = {
|
val chainHandler = {
|
||||||
val bhDao = BlockHeaderDAO()
|
val bhDao = BlockHeaderDAO()
|
||||||
ChainHandler(bhDao, config)
|
ChainHandler(bhDao)
|
||||||
}
|
}
|
||||||
|
|
||||||
val spv =
|
val spv =
|
||||||
|
|
|
@ -12,14 +12,24 @@ import org.bitcoins.testkit.node.NodeTestUtil
|
||||||
import org.bitcoins.testkit.rpc.BitcoindRpcTestUtil
|
import org.bitcoins.testkit.rpc.BitcoindRpcTestUtil
|
||||||
import org.bitcoins.testkit.util.BitcoindRpcTest
|
import org.bitcoins.testkit.util.BitcoindRpcTest
|
||||||
import org.scalatest._
|
import org.scalatest._
|
||||||
|
import scodec.bits._
|
||||||
|
|
||||||
import scala.concurrent.Future
|
import scala.concurrent.Future
|
||||||
import scala.concurrent.duration.DurationInt
|
import scala.concurrent.duration.DurationInt
|
||||||
|
import org.bitcoins.core.p2p.HeadersMessage
|
||||||
|
import org.bitcoins.core.protocol.CompactSizeUInt
|
||||||
|
import org.bitcoins.core.number.UInt64
|
||||||
|
import org.bitcoins.core.protocol.blockchain.BlockHeader
|
||||||
|
import org.bitcoins.core.number.Int32
|
||||||
|
import org.bitcoins.core.crypto.DoubleSha256Digest
|
||||||
|
import org.bitcoins.core.number.UInt32
|
||||||
|
import org.bitcoins.core.p2p.NetworkMessage
|
||||||
|
import org.bitcoins.core.p2p.VersionMessage
|
||||||
|
import org.bitcoins.core.config.TestNet3
|
||||||
|
import org.bitcoins.chain.blockchain.ChainHandler
|
||||||
|
import org.bitcoins.chain.models.BlockHeaderDAO
|
||||||
|
|
||||||
/**
|
class P2PClientTest
|
||||||
* Created by chris on 6/7/16.
|
|
||||||
*/
|
|
||||||
class ClientTest
|
|
||||||
extends BitcoindRpcTest
|
extends BitcoindRpcTest
|
||||||
with MustMatchers
|
with MustMatchers
|
||||||
with BeforeAndAfter
|
with BeforeAndAfter
|
||||||
|
@ -45,8 +55,76 @@ class ClientTest
|
||||||
lazy val bitcoindPeer2F = bitcoindRpcF.map { bitcoind =>
|
lazy val bitcoindPeer2F = bitcoindRpcF.map { bitcoind =>
|
||||||
NodeTestUtil.getBitcoindPeer(bitcoind)
|
NodeTestUtil.getBitcoindPeer(bitcoind)
|
||||||
}
|
}
|
||||||
|
behavior of "parseIndividualMessages"
|
||||||
|
|
||||||
behavior of "Client"
|
it must "block header message that is not aligned with a tcp frame" in {
|
||||||
|
|
||||||
|
val headersMsg = HeadersMessage(
|
||||||
|
CompactSizeUInt(UInt64(2), 1),
|
||||||
|
Vector(
|
||||||
|
BlockHeader(
|
||||||
|
Int32(315017594),
|
||||||
|
DoubleSha256Digest(
|
||||||
|
"177e777f078d2deeaa3ad4b82e78a00ad2f4738c5217f7a36d9cf3bd11e41817"),
|
||||||
|
DoubleSha256Digest(
|
||||||
|
"1dcaebebd620823bb344bd18a18276de508910d66b4e3cbb3426a14eced66224"),
|
||||||
|
UInt32(2845833462L),
|
||||||
|
UInt32(2626024374L),
|
||||||
|
UInt32(2637850613L)
|
||||||
|
),
|
||||||
|
BlockHeader(
|
||||||
|
Int32(1694049746),
|
||||||
|
DoubleSha256Digest(
|
||||||
|
"07b6d61809476830bc7ef862a983a7222997df3f639e0d2aa5902a5a48018430"),
|
||||||
|
DoubleSha256Digest(
|
||||||
|
"68c65f803b70b72563e86ac3e8e20ad11fbfa2eac3f9fddf4bc624d03a14f084"),
|
||||||
|
UInt32(202993555),
|
||||||
|
UInt32(4046619225L),
|
||||||
|
UInt32(1231236881)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
val networkMsg = NetworkMessage(np, 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)
|
||||||
|
val (firstHalfParseHeaders, remainingBytes) =
|
||||||
|
P2PClient.parseIndividualMessages(firstHalf)
|
||||||
|
firstHalfParseHeaders must be(empty)
|
||||||
|
|
||||||
|
val (secondHalfParsedHeaders, _) =
|
||||||
|
P2PClient.parseIndividualMessages(remainingBytes ++ secondHalf)
|
||||||
|
val parsedNetworkMsg = secondHalfParsedHeaders.head
|
||||||
|
val parsedHeadersMsg = parsedNetworkMsg.payload.asInstanceOf[HeadersMessage]
|
||||||
|
parsedNetworkMsg.header must be(networkMsg.header)
|
||||||
|
parsedHeadersMsg.headers.head must be(headersMsg.headers.head)
|
||||||
|
parsedHeadersMsg.headers(1) must be(parsedHeadersMsg.headers(1))
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
it must "return the entire byte array if a message is not aligned to a byte frame" in {
|
||||||
|
val versionMessage =
|
||||||
|
VersionMessage(TestNet3.dnsSeeds(0), np)
|
||||||
|
val networkMsg = NetworkMessage(np, versionMessage)
|
||||||
|
//remove last byte so the message is not aligned
|
||||||
|
val bytes = networkMsg.bytes.slice(0, networkMsg.bytes.size - 1)
|
||||||
|
val (_, unAlignedBytes) = P2PClient.parseIndividualMessages(bytes)
|
||||||
|
|
||||||
|
unAlignedBytes must be(bytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
// we had a bug where we didn't consume the right number of bytes
|
||||||
|
// when parsing a merkle block message, thereby screwing up
|
||||||
|
// the parsing of the remainder
|
||||||
|
it must "parse a byte vector with three messages in it" in {
|
||||||
|
val bytes =
|
||||||
|
hex"fabfb5da6d65726b6c65626c6f636b0097000000b4b6e45d00000020387191f7d488b849b4080fdf105c71269fc841a2f0f2944fc5dc785c830c716e37f36373098aae06a668cc74e388caf50ecdcb5504ce936490b4b72940e08859548c305dffff7f20010000000200000002ecd1c722709bfc241f8b94fc64034dcba2c95409dc4cd1d7b864e1128a04e5b044133327b04ff8ac576e7748a4dae4111f0c765dacbfe0c5a9fddbeb8f60d5af0105fabfb5da747800000000000000000000cc0100004413332702000000065b7f0f3eec398047e921037815aa41709b6243a1897f1423194b7558399ae0300000000017160014008dc9d88d1797305f3fbd30d2b36d6bde984a09feffffffe9145055d671fd705a09f028033da614b619205b9926fe5ebe45e15ae8b3231e0100000017160014d74cfac04bb0e6838c35f1f4a0a60d13655be2fbfeffffff797f8ff9c10fa618b6254343a648be995410e82c03fd8accb0de2271a3fb1abd00000000171600143ee832c09db48eca28a64a358ed7a01dbe52d31bfeffffffc794dba971b9479dfcbc662a3aacd641553bdb2418b15c0221c5dfd4471a7a70000000001716001452c13ba0314f7718c234ed6adfea6422ce03a545feffffffb7c3bf1762b15f3b0e0eaa5beb46fe96a9e2829a7413fd900b9b7e0d192ab64800000000171600143ee832c09db48eca28a64a358ed7a01dbe52d31bfeffffffb6ced6cb8dfc2f7f5b37561938ead3bc5ca4036e2b45d9738cc086a10eed4e010100000017160014aebb17e245fe8c98a75f0b6717fcadca30e491e2feffffff02002a7515000000001976a9148374ff8beb55ea2945039881ca26071b5749fafe88ac485620000000000017a91405d36a2b0bdedf3fc58bed6f9e4026f8934a2716876b050000fabfb5da686561646572730000000000010000001406e05800"
|
||||||
|
val (messages, leftover) = P2PClient.parseIndividualMessages(bytes)
|
||||||
|
assert(messages.length == 3)
|
||||||
|
assert(leftover.isEmpty)
|
||||||
|
|
||||||
|
}
|
||||||
|
behavior of "P2PClient"
|
||||||
|
|
||||||
it must "establish a tcp connection with a bitcoin node" in {
|
it must "establish a tcp connection with a bitcoin node" in {
|
||||||
bitcoindPeerF.flatMap(remote => connectAndDisconnect(remote))
|
bitcoindPeerF.flatMap(remote => connectAndDisconnect(remote))
|
||||||
|
@ -74,8 +152,12 @@ class ClientTest
|
||||||
def connectAndDisconnect(peer: Peer): Future[Assertion] = {
|
def connectAndDisconnect(peer: Peer): Future[Assertion] = {
|
||||||
val probe = TestProbe()
|
val probe = TestProbe()
|
||||||
val remote = peer.socket
|
val remote = peer.socket
|
||||||
|
val chainHandler = {
|
||||||
|
val dao = BlockHeaderDAO()
|
||||||
|
ChainHandler(dao)
|
||||||
|
}
|
||||||
val peerMessageReceiver =
|
val peerMessageReceiver =
|
||||||
PeerMessageReceiver(state = Preconnection)
|
PeerMessageReceiver(state = Preconnection, chainHandler)
|
||||||
val client =
|
val client =
|
||||||
TestActorRef(P2PClient.props(peer, peerMessageReceiver), probe.ref)
|
TestActorRef(P2PClient.props(peer, peerMessageReceiver), probe.ref)
|
||||||
|
|
||||||
|
|
|
@ -2,9 +2,6 @@ package org.bitcoins.node
|
||||||
|
|
||||||
import akka.actor.ActorSystem
|
import akka.actor.ActorSystem
|
||||||
import org.bitcoins.chain.api.ChainApi
|
import org.bitcoins.chain.api.ChainApi
|
||||||
import org.bitcoins.chain.config.ChainAppConfig
|
|
||||||
import org.bitcoins.core.util.BitcoinSLogger
|
|
||||||
import org.bitcoins.node.config.NodeAppConfig
|
|
||||||
import org.bitcoins.node.models.Peer
|
import org.bitcoins.node.models.Peer
|
||||||
import org.bitcoins.node.networking.P2PClient
|
import org.bitcoins.node.networking.P2PClient
|
||||||
import org.bitcoins.node.networking.peer.{
|
import org.bitcoins.node.networking.peer.{
|
||||||
|
@ -23,23 +20,22 @@ import org.bitcoins.node.models.BroadcastAbleTransactionDAO
|
||||||
import slick.jdbc.SQLiteProfile
|
import slick.jdbc.SQLiteProfile
|
||||||
import scala.util.Failure
|
import scala.util.Failure
|
||||||
import scala.util.Success
|
import scala.util.Success
|
||||||
|
import org.bitcoins.db.P2PLogger
|
||||||
|
import org.bitcoins.node.config.NodeAppConfig
|
||||||
|
|
||||||
case class SpvNode(
|
case class SpvNode(
|
||||||
peer: Peer,
|
peer: Peer,
|
||||||
chainApi: ChainApi,
|
chainApi: ChainApi,
|
||||||
bloomFilter: BloomFilter,
|
bloomFilter: BloomFilter,
|
||||||
callbacks: SpvNodeCallbacks = SpvNodeCallbacks.empty
|
callbacks: SpvNodeCallbacks = SpvNodeCallbacks.empty
|
||||||
)(
|
)(implicit system: ActorSystem, nodeAppConfig: NodeAppConfig)
|
||||||
implicit system: ActorSystem,
|
extends P2PLogger {
|
||||||
nodeAppConfig: NodeAppConfig,
|
|
||||||
chainAppConfig: ChainAppConfig)
|
|
||||||
extends BitcoinSLogger {
|
|
||||||
import system.dispatcher
|
import system.dispatcher
|
||||||
|
|
||||||
private val txDAO = BroadcastAbleTransactionDAO(SQLiteProfile)
|
private val txDAO = BroadcastAbleTransactionDAO(SQLiteProfile)
|
||||||
|
|
||||||
private val peerMsgRecv =
|
private val peerMsgRecv =
|
||||||
PeerMessageReceiver.newReceiver(callbacks)
|
PeerMessageReceiver.newReceiver(chainApi, callbacks)
|
||||||
|
|
||||||
private val client: P2PClient =
|
private val client: P2PClient =
|
||||||
P2PClient(context = system, peer = peer, peerMessageReceiver = peerMsgRecv)
|
P2PClient(context = system, peer = peer, peerMessageReceiver = peerMsgRecv)
|
||||||
|
|
|
@ -7,13 +7,24 @@ import scala.concurrent.Future
|
||||||
import org.bitcoins.node.db.NodeDbManagement
|
import org.bitcoins.node.db.NodeDbManagement
|
||||||
import scala.util.Failure
|
import scala.util.Failure
|
||||||
import scala.util.Success
|
import scala.util.Success
|
||||||
|
import java.nio.file.Path
|
||||||
|
|
||||||
case class NodeAppConfig(private val confs: Config*) extends AppConfig {
|
/** Configuration for the Bitcoin-S node
|
||||||
override val configOverrides: List[Config] = confs.toList
|
* @param directory The data directory of the node
|
||||||
override protected def moduleName: String = "node"
|
* @param confs Optional sequence of configuration overrides
|
||||||
override protected type ConfigType = NodeAppConfig
|
*/
|
||||||
override protected def newConfigOfType(configs: Seq[Config]): NodeAppConfig =
|
case class NodeAppConfig(
|
||||||
NodeAppConfig(configs: _*)
|
private val directory: Path,
|
||||||
|
private val confs: Config*)
|
||||||
|
extends AppConfig {
|
||||||
|
override protected[bitcoins] def configOverrides: List[Config] = confs.toList
|
||||||
|
override protected[bitcoins] val moduleName: String = "node"
|
||||||
|
override protected[bitcoins] type ConfigType = NodeAppConfig
|
||||||
|
override protected[bitcoins] def newConfigOfType(
|
||||||
|
configs: Seq[Config]): NodeAppConfig =
|
||||||
|
NodeAppConfig(directory, configs: _*)
|
||||||
|
|
||||||
|
protected[bitcoins] def baseDatadir: Path = directory
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Ensures correct tables and other required information is in
|
* Ensures correct tables and other required information is in
|
||||||
|
@ -31,3 +42,13 @@ case class NodeAppConfig(private val confs: Config*) extends AppConfig {
|
||||||
initF
|
initF
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
object NodeAppConfig {
|
||||||
|
|
||||||
|
/** Constructs a node configuration from the default Bitcoin-S
|
||||||
|
* data directory and given list of configuration overrides.
|
||||||
|
*/
|
||||||
|
def fromDefaultDatadir(confs: Config*): NodeAppConfig =
|
||||||
|
NodeAppConfig(AppConfig.DEFAULT_BITCOIN_S_DATADIR, confs: _*)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
|
@ -5,7 +5,6 @@ import akka.io.{IO, Tcp}
|
||||||
import akka.util.ByteString
|
import akka.util.ByteString
|
||||||
import org.bitcoins.core.config.NetworkParameters
|
import org.bitcoins.core.config.NetworkParameters
|
||||||
import org.bitcoins.core.p2p.NetworkMessage
|
import org.bitcoins.core.p2p.NetworkMessage
|
||||||
import org.bitcoins.core.util.BitcoinSLogger
|
|
||||||
import org.bitcoins.core.p2p.NetworkPayload
|
import org.bitcoins.core.p2p.NetworkPayload
|
||||||
import org.bitcoins.node.models.Peer
|
import org.bitcoins.node.models.Peer
|
||||||
import org.bitcoins.node.networking.peer.PeerMessageReceiver
|
import org.bitcoins.node.networking.peer.PeerMessageReceiver
|
||||||
|
@ -14,6 +13,9 @@ import org.bitcoins.node.util.BitcoinSpvNodeUtil
|
||||||
import scodec.bits.ByteVector
|
import scodec.bits.ByteVector
|
||||||
import org.bitcoins.node.config.NodeAppConfig
|
import org.bitcoins.node.config.NodeAppConfig
|
||||||
import akka.util.CompactByteString
|
import akka.util.CompactByteString
|
||||||
|
import scala.annotation.tailrec
|
||||||
|
import scala.util._
|
||||||
|
import org.bitcoins.db.P2PLogger
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This actor is responsible for creating a connection,
|
* This actor is responsible for creating a connection,
|
||||||
|
@ -50,7 +52,7 @@ case class P2PClientActor(
|
||||||
peerMsgHandlerReceiver: PeerMessageReceiver
|
peerMsgHandlerReceiver: PeerMessageReceiver
|
||||||
)(implicit config: NodeAppConfig)
|
)(implicit config: NodeAppConfig)
|
||||||
extends Actor
|
extends Actor
|
||||||
with BitcoinSLogger {
|
with P2PLogger {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The manager is an actor that handles the underlying low level I/O resources (selectors, channels)
|
* The manager is an actor that handles the underlying low level I/O resources (selectors, channels)
|
||||||
|
@ -179,7 +181,7 @@ case class P2PClientActor(
|
||||||
val bytes: ByteVector = unalignedBytes ++ byteVec
|
val bytes: ByteVector = unalignedBytes ++ byteVec
|
||||||
logger.trace(s"Bytes for message parsing: ${bytes.toHex}")
|
logger.trace(s"Bytes for message parsing: ${bytes.toHex}")
|
||||||
val (messages, newUnalignedBytes) =
|
val (messages, newUnalignedBytes) =
|
||||||
BitcoinSpvNodeUtil.parseIndividualMessages(bytes)
|
P2PClient.parseIndividualMessages(bytes)
|
||||||
|
|
||||||
logger.debug({
|
logger.debug({
|
||||||
val length = messages.length
|
val length = messages.length
|
||||||
|
@ -237,7 +239,7 @@ case class P2PClientActor(
|
||||||
|
|
||||||
case class P2PClient(actor: ActorRef, peer: Peer)
|
case class P2PClient(actor: ActorRef, peer: Peer)
|
||||||
|
|
||||||
object P2PClient {
|
object P2PClient extends P2PLogger {
|
||||||
|
|
||||||
def props(peer: Peer, peerMsgHandlerReceiver: PeerMessageReceiver)(
|
def props(peer: Peer, peerMsgHandlerReceiver: PeerMessageReceiver)(
|
||||||
implicit config: NodeAppConfig
|
implicit config: NodeAppConfig
|
||||||
|
@ -256,4 +258,48 @@ object P2PClient {
|
||||||
P2PClient(actorRef, peer)
|
P2PClient(actorRef, peer)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Akka sends messages as one byte stream. There is not a 1 to 1 relationship between byte streams received and
|
||||||
|
* bitcoin protocol messages. This function parses our byte stream into individual network messages
|
||||||
|
*
|
||||||
|
* @param bytes the bytes that need to be parsed into individual messages
|
||||||
|
* @return the parsed [[NetworkMessage]]'s and the unaligned bytes that did not parse to a message
|
||||||
|
*/
|
||||||
|
private[bitcoins] def parseIndividualMessages(bytes: ByteVector)(
|
||||||
|
implicit conf: NodeAppConfig): (List[NetworkMessage], ByteVector) = {
|
||||||
|
@tailrec
|
||||||
|
def loop(
|
||||||
|
remainingBytes: ByteVector,
|
||||||
|
accum: List[NetworkMessage]): (List[NetworkMessage], ByteVector) = {
|
||||||
|
if (remainingBytes.length <= 0) {
|
||||||
|
(accum.reverse, remainingBytes)
|
||||||
|
} else {
|
||||||
|
val messageTry = Try(NetworkMessage(remainingBytes))
|
||||||
|
messageTry match {
|
||||||
|
case Success(message) =>
|
||||||
|
if (message.header.payloadSize.toInt != message.payload.bytes.size) {
|
||||||
|
//this means our tcp frame was not aligned, therefore put the message back in the
|
||||||
|
//buffer and wait for the remaining bytes
|
||||||
|
(accum.reverse, remainingBytes)
|
||||||
|
} else {
|
||||||
|
val newRemainingBytes = remainingBytes.slice(
|
||||||
|
message.bytes.length,
|
||||||
|
remainingBytes.length)
|
||||||
|
loop(newRemainingBytes, message :: accum)
|
||||||
|
}
|
||||||
|
case Failure(exception) =>
|
||||||
|
logger.error(
|
||||||
|
"Failed to parse network message, could be because TCP frame isn't aligned",
|
||||||
|
exception)
|
||||||
|
//this case means that our TCP frame was not aligned with bitcoin protocol
|
||||||
|
//return the unaligned bytes so we can apply them to the next tcp frame of bytes we receive
|
||||||
|
//http://stackoverflow.com/a/37979529/967713
|
||||||
|
(accum.reverse, remainingBytes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val (messages, remainingBytes) = loop(bytes, Nil)
|
||||||
|
(messages, remainingBytes)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,7 @@
|
||||||
package org.bitcoins.node.networking.peer
|
package org.bitcoins.node.networking.peer
|
||||||
|
|
||||||
import org.bitcoins.chain.api.ChainApi
|
import org.bitcoins.chain.api.ChainApi
|
||||||
import org.bitcoins.chain.blockchain.ChainHandler
|
import org.bitcoins.core.util.FutureUtil
|
||||||
import org.bitcoins.chain.config.ChainAppConfig
|
|
||||||
import org.bitcoins.chain.models.BlockHeaderDAO
|
|
||||||
import org.bitcoins.core.util.{BitcoinSLogger, FutureUtil}
|
|
||||||
import org.bitcoins.core.p2p.{DataPayload, HeadersMessage, InventoryMessage}
|
import org.bitcoins.core.p2p.{DataPayload, HeadersMessage, InventoryMessage}
|
||||||
|
|
||||||
import scala.concurrent.{ExecutionContext, Future}
|
import scala.concurrent.{ExecutionContext, Future}
|
||||||
|
@ -21,21 +18,20 @@ import slick.jdbc.SQLiteProfile
|
||||||
import org.bitcoins.node.config.NodeAppConfig
|
import org.bitcoins.node.config.NodeAppConfig
|
||||||
import org.bitcoins.core.p2p.TypeIdentifier
|
import org.bitcoins.core.p2p.TypeIdentifier
|
||||||
import org.bitcoins.core.p2p.MsgUnassigned
|
import org.bitcoins.core.p2p.MsgUnassigned
|
||||||
|
import org.bitcoins.db.P2PLogger
|
||||||
|
|
||||||
/** This actor is meant to handle a [[org.bitcoins.core.p2p.DataPayload DataPayload]]
|
/** This actor is meant to handle a [[org.bitcoins.core.p2p.DataPayload DataPayload]]
|
||||||
* that a peer to sent to us on the p2p network, for instance, if we a receive a
|
* that a peer to sent to us on the p2p network, for instance, if we a receive a
|
||||||
* [[org.bitcoins.core.p2p.HeadersMessage HeadersMessage]] we should store those headers in our database
|
* [[org.bitcoins.core.p2p.HeadersMessage HeadersMessage]] we should store those headers in our database
|
||||||
*/
|
*/
|
||||||
class DataMessageHandler(callbacks: SpvNodeCallbacks)(
|
class DataMessageHandler(callbacks: SpvNodeCallbacks, chainHandler: ChainApi)(
|
||||||
implicit ec: ExecutionContext,
|
implicit ec: ExecutionContext,
|
||||||
chainConf: ChainAppConfig,
|
appConfig: NodeAppConfig)
|
||||||
nodeConf: NodeAppConfig)
|
extends P2PLogger {
|
||||||
extends BitcoinSLogger {
|
|
||||||
|
|
||||||
private val callbackNum = callbacks.onBlockReceived.length + callbacks.onMerkleBlockReceived.length + callbacks.onTxReceived.length
|
private val callbackNum = callbacks.onBlockReceived.length + callbacks.onMerkleBlockReceived.length + callbacks.onTxReceived.length
|
||||||
logger.debug(s"Given $callbackNum of callback(s)")
|
logger.debug(s"Given $callbackNum of callback(s)")
|
||||||
|
|
||||||
private val blockHeaderDAO: BlockHeaderDAO = BlockHeaderDAO()
|
|
||||||
private val txDAO = BroadcastAbleTransactionDAO(SQLiteProfile)
|
private val txDAO = BroadcastAbleTransactionDAO(SQLiteProfile)
|
||||||
|
|
||||||
def handleDataPayload(
|
def handleDataPayload(
|
||||||
|
@ -76,9 +72,7 @@ class DataMessageHandler(callbacks: SpvNodeCallbacks)(
|
||||||
logger.trace(
|
logger.trace(
|
||||||
s"Received headers message with ${headersMsg.count.toInt} headers")
|
s"Received headers message with ${headersMsg.count.toInt} headers")
|
||||||
val headers = headersMsg.headers
|
val headers = headersMsg.headers
|
||||||
val chainApi: ChainApi =
|
val chainApiF = chainHandler.processHeaders(headers)
|
||||||
ChainHandler(blockHeaderDAO, chainConfig = chainConf)
|
|
||||||
val chainApiF = chainApi.processHeaders(headers)
|
|
||||||
|
|
||||||
chainApiF.map { newApi =>
|
chainApiF.map { newApi =>
|
||||||
val lastHeader = headers.last
|
val lastHeader = headers.last
|
||||||
|
|
|
@ -1,9 +1,7 @@
|
||||||
package org.bitcoins.node.networking.peer
|
package org.bitcoins.node.networking.peer
|
||||||
|
|
||||||
import akka.actor.ActorRefFactory
|
import akka.actor.ActorRefFactory
|
||||||
import org.bitcoins.chain.config.ChainAppConfig
|
|
||||||
import org.bitcoins.core.p2p.NetworkMessage
|
import org.bitcoins.core.p2p.NetworkMessage
|
||||||
import org.bitcoins.core.util.BitcoinSLogger
|
|
||||||
import org.bitcoins.node.config.NodeAppConfig
|
import org.bitcoins.node.config.NodeAppConfig
|
||||||
import org.bitcoins.core.p2p._
|
import org.bitcoins.core.p2p._
|
||||||
import org.bitcoins.node.models.Peer
|
import org.bitcoins.node.models.Peer
|
||||||
|
@ -17,6 +15,8 @@ import org.bitcoins.node.networking.peer.PeerMessageReceiverState.{
|
||||||
|
|
||||||
import scala.util.{Failure, Success, Try}
|
import scala.util.{Failure, Success, Try}
|
||||||
import org.bitcoins.node.SpvNodeCallbacks
|
import org.bitcoins.node.SpvNodeCallbacks
|
||||||
|
import org.bitcoins.db.P2PLogger
|
||||||
|
import org.bitcoins.chain.api.ChainApi
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Responsible for receiving messages from a peer on the
|
* Responsible for receiving messages from a peer on the
|
||||||
|
@ -27,12 +27,10 @@ import org.bitcoins.node.SpvNodeCallbacks
|
||||||
*/
|
*/
|
||||||
class PeerMessageReceiver(
|
class PeerMessageReceiver(
|
||||||
state: PeerMessageReceiverState,
|
state: PeerMessageReceiverState,
|
||||||
callbacks: SpvNodeCallbacks
|
callbacks: SpvNodeCallbacks,
|
||||||
)(
|
chainHandler: ChainApi
|
||||||
implicit ref: ActorRefFactory,
|
)(implicit ref: ActorRefFactory, nodeAppConfig: NodeAppConfig)
|
||||||
nodeAppConfig: NodeAppConfig,
|
extends P2PLogger {
|
||||||
chainAppConfig: ChainAppConfig)
|
|
||||||
extends BitcoinSLogger {
|
|
||||||
|
|
||||||
import ref.dispatcher
|
import ref.dispatcher
|
||||||
|
|
||||||
|
@ -138,7 +136,7 @@ class PeerMessageReceiver(
|
||||||
private def handleDataPayload(
|
private def handleDataPayload(
|
||||||
payload: DataPayload,
|
payload: DataPayload,
|
||||||
sender: PeerMessageSender): Unit = {
|
sender: PeerMessageSender): Unit = {
|
||||||
val dataMsgHandler = new DataMessageHandler(callbacks)
|
val dataMsgHandler = new DataMessageHandler(callbacks, chainHandler)
|
||||||
//else it means we are receiving this data payload from a peer,
|
//else it means we are receiving this data payload from a peer,
|
||||||
//we need to handle it
|
//we need to handle it
|
||||||
dataMsgHandler.handleDataPayload(payload, sender)
|
dataMsgHandler.handleDataPayload(payload, sender)
|
||||||
|
@ -233,18 +231,20 @@ object PeerMessageReceiver {
|
||||||
|
|
||||||
def apply(
|
def apply(
|
||||||
state: PeerMessageReceiverState,
|
state: PeerMessageReceiverState,
|
||||||
|
chainHandler: ChainApi,
|
||||||
callbacks: SpvNodeCallbacks = SpvNodeCallbacks.empty)(
|
callbacks: SpvNodeCallbacks = SpvNodeCallbacks.empty)(
|
||||||
implicit ref: ActorRefFactory,
|
implicit ref: ActorRefFactory,
|
||||||
nodeAppConfig: NodeAppConfig,
|
nodeAppConfig: NodeAppConfig): PeerMessageReceiver = {
|
||||||
chainAppConfig: ChainAppConfig
|
new PeerMessageReceiver(state, callbacks, chainHandler)
|
||||||
): PeerMessageReceiver = {
|
|
||||||
new PeerMessageReceiver(state, callbacks)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def newReceiver(callbacks: SpvNodeCallbacks = SpvNodeCallbacks.empty)(
|
def newReceiver(
|
||||||
|
chainHandler: ChainApi,
|
||||||
|
callbacks: SpvNodeCallbacks = SpvNodeCallbacks.empty)(
|
||||||
implicit nodeAppConfig: NodeAppConfig,
|
implicit nodeAppConfig: NodeAppConfig,
|
||||||
chainAppConfig: ChainAppConfig,
|
|
||||||
ref: ActorRefFactory): PeerMessageReceiver = {
|
ref: ActorRefFactory): PeerMessageReceiver = {
|
||||||
new PeerMessageReceiver(state = PeerMessageReceiverState.fresh(), callbacks)
|
new PeerMessageReceiver(state = PeerMessageReceiverState.fresh(),
|
||||||
|
callbacks,
|
||||||
|
chainHandler)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,11 @@
|
||||||
package org.bitcoins.node.networking.peer
|
package org.bitcoins.node.networking.peer
|
||||||
|
|
||||||
import org.bitcoins.core.util.BitcoinSLogger
|
|
||||||
import org.bitcoins.core.p2p.{VerAckMessage, VersionMessage}
|
import org.bitcoins.core.p2p.{VerAckMessage, VersionMessage}
|
||||||
import org.bitcoins.node.networking.P2PClient
|
import org.bitcoins.node.networking.P2PClient
|
||||||
|
|
||||||
import scala.concurrent.{Future, Promise}
|
import scala.concurrent.{Future, Promise}
|
||||||
|
|
||||||
sealed abstract class PeerMessageReceiverState extends BitcoinSLogger {
|
sealed abstract class PeerMessageReceiverState {
|
||||||
|
|
||||||
/** This promise gets completed when we receive a
|
/** This promise gets completed when we receive a
|
||||||
* [[akka.io.Tcp.Connected]] message from [[org.bitcoins.node.networking.P2PClient P2PClient]]
|
* [[akka.io.Tcp.Connected]] message from [[org.bitcoins.node.networking.P2PClient P2PClient]]
|
||||||
|
|
|
@ -4,14 +4,14 @@ import akka.actor.ActorRef
|
||||||
import akka.io.Tcp
|
import akka.io.Tcp
|
||||||
import org.bitcoins.core.crypto.DoubleSha256Digest
|
import org.bitcoins.core.crypto.DoubleSha256Digest
|
||||||
import org.bitcoins.core.p2p.NetworkMessage
|
import org.bitcoins.core.p2p.NetworkMessage
|
||||||
import org.bitcoins.core.util.BitcoinSLogger
|
|
||||||
import org.bitcoins.core.p2p._
|
import org.bitcoins.core.p2p._
|
||||||
import org.bitcoins.node.networking.P2PClient
|
import org.bitcoins.node.networking.P2PClient
|
||||||
import org.bitcoins.node.config.NodeAppConfig
|
import org.bitcoins.node.config.NodeAppConfig
|
||||||
import org.bitcoins.core.protocol.transaction.Transaction
|
import org.bitcoins.core.protocol.transaction.Transaction
|
||||||
|
import org.bitcoins.db.P2PLogger
|
||||||
|
|
||||||
case class PeerMessageSender(client: P2PClient)(implicit conf: NodeAppConfig)
|
case class PeerMessageSender(client: P2PClient)(implicit conf: NodeAppConfig)
|
||||||
extends BitcoinSLogger {
|
extends P2PLogger {
|
||||||
private val socket = client.peer.socket
|
private val socket = client.peer.socket
|
||||||
|
|
||||||
/** Initiates a connection with the given peer */
|
/** Initiates a connection with the given peer */
|
||||||
|
|
|
@ -30,6 +30,7 @@ object Deps {
|
||||||
val uPickleV = "0.7.4"
|
val uPickleV = "0.7.4"
|
||||||
val akkaHttpUpickleV = "1.27.0"
|
val akkaHttpUpickleV = "1.27.0"
|
||||||
val uJsonV = uPickleV // Li Haoyi ecosystem does common versioning
|
val uJsonV = uPickleV // Li Haoyi ecosystem does common versioning
|
||||||
|
val sourcecodeV = "0.1.7"
|
||||||
|
|
||||||
// CLI deps
|
// CLI deps
|
||||||
val scoptV = "4.0.0-RC2"
|
val scoptV = "4.0.0-RC2"
|
||||||
|
@ -44,7 +45,6 @@ object Deps {
|
||||||
val akkaHttp = "com.typesafe.akka" %% "akka-http" % V.akkav withSources () withJavadoc ()
|
val akkaHttp = "com.typesafe.akka" %% "akka-http" % V.akkav withSources () withJavadoc ()
|
||||||
val akkaStream = "com.typesafe.akka" %% "akka-stream" % V.akkaStreamv withSources () withJavadoc ()
|
val akkaStream = "com.typesafe.akka" %% "akka-stream" % V.akkaStreamv withSources () withJavadoc ()
|
||||||
val akkaActor = "com.typesafe.akka" %% "akka-actor" % V.akkaStreamv withSources () withJavadoc ()
|
val akkaActor = "com.typesafe.akka" %% "akka-actor" % V.akkaStreamv withSources () withJavadoc ()
|
||||||
val akkaLog = "com.typesafe.akka" %% "akka-slf4j" % V.akkaStreamv
|
|
||||||
|
|
||||||
val playJson = "com.typesafe.play" %% "play-json" % V.playv withSources () withJavadoc ()
|
val playJson = "com.typesafe.play" %% "play-json" % V.playv withSources () withJavadoc ()
|
||||||
val typesafeConfig = "com.typesafe" % "config" % V.typesafeConfigV withSources () withJavadoc ()
|
val typesafeConfig = "com.typesafe" % "config" % V.typesafeConfigV withSources () withJavadoc ()
|
||||||
|
@ -65,6 +65,9 @@ object Deps {
|
||||||
// serializing to and from JSON
|
// serializing to and from JSON
|
||||||
val uPickle = "com.lihaoyi" %% "upickle" % V.uPickleV
|
val uPickle = "com.lihaoyi" %% "upickle" % V.uPickleV
|
||||||
|
|
||||||
|
// get access to reflection data at compile-time
|
||||||
|
val sourcecode = "com.lihaoyi" %% "sourcecode" % V.sourcecodeV
|
||||||
|
|
||||||
// make akka-http play nice with upickle
|
// make akka-http play nice with upickle
|
||||||
val akkaHttpUpickle = "de.heikoseeberger" %% "akka-http-upickle" % V.akkaHttpUpickleV
|
val akkaHttpUpickle = "de.heikoseeberger" %% "akka-http-upickle" % V.akkaHttpUpickleV
|
||||||
|
|
||||||
|
@ -93,12 +96,10 @@ object Deps {
|
||||||
}
|
}
|
||||||
|
|
||||||
val chain = List(
|
val chain = List(
|
||||||
Compile.slf4j
|
Compile.logback
|
||||||
)
|
)
|
||||||
|
|
||||||
val chainTest = List(
|
val chainTest = List()
|
||||||
Test.logback
|
|
||||||
)
|
|
||||||
|
|
||||||
val core = List(
|
val core = List(
|
||||||
Compile.bouncycastle,
|
Compile.bouncycastle,
|
||||||
|
@ -151,6 +152,8 @@ object Deps {
|
||||||
|
|
||||||
val dbCommons = List(
|
val dbCommons = List(
|
||||||
Compile.slick,
|
Compile.slick,
|
||||||
|
Compile.sourcecode,
|
||||||
|
Compile.logback,
|
||||||
Compile.sqlite,
|
Compile.sqlite,
|
||||||
Compile.slickHikari
|
Compile.slickHikari
|
||||||
)
|
)
|
||||||
|
@ -169,7 +172,6 @@ object Deps {
|
||||||
Compile.akkaHttpUpickle,
|
Compile.akkaHttpUpickle,
|
||||||
Compile.uPickle,
|
Compile.uPickle,
|
||||||
Compile.logback,
|
Compile.logback,
|
||||||
Compile.akkaLog,
|
|
||||||
Compile.akkaHttp
|
Compile.akkaHttp
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -198,7 +200,6 @@ object Deps {
|
||||||
|
|
||||||
val nodeTest = List(
|
val nodeTest = List(
|
||||||
Test.akkaTestkit,
|
Test.akkaTestkit,
|
||||||
Test.logback,
|
|
||||||
Test.scalaTest
|
Test.scalaTest
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -215,11 +216,11 @@ object Deps {
|
||||||
)
|
)
|
||||||
|
|
||||||
val wallet = List(
|
val wallet = List(
|
||||||
Compile.uJson
|
Compile.uJson,
|
||||||
|
Compile.logback
|
||||||
)
|
)
|
||||||
|
|
||||||
val walletTest = List(
|
val walletTest = List(
|
||||||
Test.logback,
|
|
||||||
Test.akkaTestkit
|
Test.akkaTestkit
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -13,15 +13,7 @@ object BitcoinSTestAppConfig {
|
||||||
*/
|
*/
|
||||||
def getTestConfig(config: Config*): BitcoinSAppConfig = {
|
def getTestConfig(config: Config*): BitcoinSAppConfig = {
|
||||||
val tmpDir = Files.createTempDirectory("bitcoin-s-")
|
val tmpDir = Files.createTempDirectory("bitcoin-s-")
|
||||||
val confStr = s"""
|
BitcoinSAppConfig(tmpDir, config: _*)
|
||||||
| bitcoin-s {
|
|
||||||
| datadir = $tmpDir
|
|
||||||
| }
|
|
||||||
|
|
|
||||||
|""".stripMargin
|
|
||||||
val conf = ConfigFactory.parseString(confStr)
|
|
||||||
val allConfs = conf +: config
|
|
||||||
BitcoinSAppConfig(allConfs: _*)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sealed trait ProjectType
|
sealed trait ProjectType
|
||||||
|
|
|
@ -184,7 +184,7 @@ trait ChainUnitTest
|
||||||
def createPopulatedChainHandler(): Future[ChainHandler] = {
|
def createPopulatedChainHandler(): Future[ChainHandler] = {
|
||||||
for {
|
for {
|
||||||
blockHeaderDAO <- ChainUnitTest.createPopulatedBlockHeaderDAO()
|
blockHeaderDAO <- ChainUnitTest.createPopulatedBlockHeaderDAO()
|
||||||
} yield ChainHandler(blockHeaderDAO = blockHeaderDAO, appConfig)
|
} yield ChainHandler(blockHeaderDAO = blockHeaderDAO)
|
||||||
}
|
}
|
||||||
|
|
||||||
def withPopulatedChainHandler(test: OneArgAsyncTest): FutureOutcome = {
|
def withPopulatedChainHandler(test: OneArgAsyncTest): FutureOutcome = {
|
||||||
|
@ -415,7 +415,7 @@ object ChainUnitTest extends BitcoinSLogger {
|
||||||
ec: ExecutionContext): ChainHandler = {
|
ec: ExecutionContext): ChainHandler = {
|
||||||
lazy val blockHeaderDAO = BlockHeaderDAO()
|
lazy val blockHeaderDAO = BlockHeaderDAO()
|
||||||
|
|
||||||
ChainHandler(blockHeaderDAO = blockHeaderDAO, appConfig)
|
ChainHandler(blockHeaderDAO)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,6 +30,9 @@ import org.scalatest.{
|
||||||
import scala.concurrent.duration._
|
import scala.concurrent.duration._
|
||||||
import scala.concurrent.{ExecutionContext, Future}
|
import scala.concurrent.{ExecutionContext, Future}
|
||||||
import org.bitcoins.testkit.BitcoinSTestAppConfig
|
import org.bitcoins.testkit.BitcoinSTestAppConfig
|
||||||
|
import org.bitcoins.chain.blockchain.ChainHandler
|
||||||
|
import org.bitcoins.chain.models.BlockHeaderDAO
|
||||||
|
import org.bitcoins.node.SpvNodeCallbacks
|
||||||
|
|
||||||
trait NodeUnitTest
|
trait NodeUnitTest
|
||||||
extends BitcoinSFixture
|
extends BitcoinSFixture
|
||||||
|
@ -67,8 +70,11 @@ trait NodeUnitTest
|
||||||
lazy val bitcoindPeerF = startedBitcoindF.map(NodeTestUtil.getBitcoindPeer)
|
lazy val bitcoindPeerF = startedBitcoindF.map(NodeTestUtil.getBitcoindPeer)
|
||||||
|
|
||||||
def buildPeerMessageReceiver(): PeerMessageReceiver = {
|
def buildPeerMessageReceiver(): PeerMessageReceiver = {
|
||||||
|
|
||||||
|
val dao = BlockHeaderDAO()
|
||||||
|
val chainHandler = ChainHandler(dao)
|
||||||
val receiver =
|
val receiver =
|
||||||
PeerMessageReceiver.newReceiver()
|
PeerMessageReceiver.newReceiver(chainHandler, SpvNodeCallbacks.empty)
|
||||||
receiver
|
receiver
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,9 +10,19 @@ import org.bitcoins.core.config.MainNet
|
||||||
import org.bitcoins.wallet.config.WalletAppConfig
|
import org.bitcoins.wallet.config.WalletAppConfig
|
||||||
import java.nio.file.Paths
|
import java.nio.file.Paths
|
||||||
import org.bitcoins.core.hd.HDPurposes
|
import org.bitcoins.core.hd.HDPurposes
|
||||||
|
import java.nio.file.Files
|
||||||
|
import ch.qos.logback.classic.Level
|
||||||
|
import java.nio.file.Path
|
||||||
|
import scala.util.Properties
|
||||||
|
|
||||||
class WalletAppConfigTest extends BitcoinSUnitTest {
|
class WalletAppConfigTest extends BitcoinSUnitTest {
|
||||||
val config = WalletAppConfig()
|
|
||||||
|
val tempDir = Files.createTempDirectory("bitcoin-s")
|
||||||
|
val config = WalletAppConfig(directory = tempDir)
|
||||||
|
|
||||||
|
it must "resolve DB connections correctly " in {
|
||||||
|
assert(config.dbPath.startsWith(Properties.tmpDir))
|
||||||
|
}
|
||||||
|
|
||||||
it must "be overridable" in {
|
it must "be overridable" in {
|
||||||
assert(config.network == RegTest)
|
assert(config.network == RegTest)
|
||||||
|
@ -27,27 +37,25 @@ class WalletAppConfigTest extends BitcoinSUnitTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
it should "not matter how the overrides are passed in" in {
|
it should "not matter how the overrides are passed in" in {
|
||||||
val dir = Paths.get("/", "bar", "biz")
|
|
||||||
val overrider = ConfigFactory.parseString(s"""
|
val overrider = ConfigFactory.parseString(s"""
|
||||||
|bitcoin-s {
|
|bitcoin-s {
|
||||||
| datadir = $dir
|
|
||||||
| network = mainnet
|
| network = mainnet
|
||||||
|}
|
|}
|
||||||
|""".stripMargin)
|
|""".stripMargin)
|
||||||
|
|
||||||
val throughConstuctor = WalletAppConfig(overrider)
|
val throughConstuctor = WalletAppConfig(tempDir, overrider)
|
||||||
val throughWithOverrides = config.withOverrides(overrider)
|
val throughWithOverrides = config.withOverrides(overrider)
|
||||||
assert(throughWithOverrides.network == MainNet)
|
assert(throughWithOverrides.network == MainNet)
|
||||||
assert(throughWithOverrides.network == throughConstuctor.network)
|
assert(throughWithOverrides.network == throughConstuctor.network)
|
||||||
|
|
||||||
assert(throughWithOverrides.datadir.startsWith(dir))
|
|
||||||
assert(throughWithOverrides.datadir == throughConstuctor.datadir)
|
assert(throughWithOverrides.datadir == throughConstuctor.datadir)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
it must "be overridable without screwing up other options" in {
|
it must "be overridable without screwing up other options" in {
|
||||||
val dir = Paths.get("/", "foo", "bar")
|
val otherConf = ConfigFactory.parseString(
|
||||||
val otherConf = ConfigFactory.parseString(s"bitcoin-s.datadir = $dir")
|
s"bitcoin-s.wallet.defaultAccountType = segwit"
|
||||||
|
)
|
||||||
val thirdConf = ConfigFactory.parseString(
|
val thirdConf = ConfigFactory.parseString(
|
||||||
s"bitcoin-s.wallet.defaultAccountType = nested-segwit")
|
s"bitcoin-s.wallet.defaultAccountType = nested-segwit")
|
||||||
|
|
||||||
|
@ -55,9 +63,11 @@ class WalletAppConfigTest extends BitcoinSUnitTest {
|
||||||
|
|
||||||
val twiceOverriden = overriden.withOverrides(thirdConf)
|
val twiceOverriden = overriden.withOverrides(thirdConf)
|
||||||
|
|
||||||
assert(overriden.datadir.startsWith(dir))
|
assert(overriden.defaultAccountKind == HDPurposes.SegWit)
|
||||||
assert(twiceOverriden.datadir.startsWith(dir))
|
|
||||||
assert(twiceOverriden.defaultAccountKind == HDPurposes.NestedSegWit)
|
assert(twiceOverriden.defaultAccountKind == HDPurposes.NestedSegWit)
|
||||||
|
|
||||||
|
assert(config.datadir == overriden.datadir)
|
||||||
|
assert(twiceOverriden.datadir == overriden.datadir)
|
||||||
}
|
}
|
||||||
|
|
||||||
it must "be overridable with multiple levels" in {
|
it must "be overridable with multiple levels" in {
|
||||||
|
@ -65,6 +75,30 @@ class WalletAppConfigTest extends BitcoinSUnitTest {
|
||||||
val mainnet = ConfigFactory.parseString("bitcoin-s.network = mainnet")
|
val mainnet = ConfigFactory.parseString("bitcoin-s.network = mainnet")
|
||||||
val overriden: WalletAppConfig = config.withOverrides(testnet, mainnet)
|
val overriden: WalletAppConfig = config.withOverrides(testnet, mainnet)
|
||||||
assert(overriden.network == MainNet)
|
assert(overriden.network == MainNet)
|
||||||
|
}
|
||||||
|
|
||||||
|
it must "have user data directory configuration take precedence" in {
|
||||||
|
|
||||||
|
val tempDir = Files.createTempDirectory("bitcoin-s")
|
||||||
|
val tempFile = Files.createFile(tempDir.resolve("bitcoin-s.conf"))
|
||||||
|
val confStr = """
|
||||||
|
| bitcoin-s {
|
||||||
|
| network = testnet3
|
||||||
|
|
|
||||||
|
| logging {
|
||||||
|
| level = off
|
||||||
|
|
|
||||||
|
| p2p = warn
|
||||||
|
| }
|
||||||
|
| }
|
||||||
|
""".stripMargin
|
||||||
|
val _ = Files.write(tempFile, confStr.getBytes())
|
||||||
|
|
||||||
|
val appConfig = WalletAppConfig(directory = tempDir)
|
||||||
|
|
||||||
|
assert(appConfig.datadir == tempDir.resolve("testnet3"))
|
||||||
|
assert(appConfig.network == TestNet3)
|
||||||
|
assert(appConfig.logLevel == Level.OFF)
|
||||||
|
assert(appConfig.p2pLogLevel == Level.WARN)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,8 +6,7 @@ import scodec.bits.ByteVector
|
||||||
|
|
||||||
import scala.util.{Failure, Success, Try}
|
import scala.util.{Failure, Success, Try}
|
||||||
|
|
||||||
case class EncryptedMnemonic(value: AesEncryptedData, salt: AesSalt)
|
case class EncryptedMnemonic(value: AesEncryptedData, salt: AesSalt) {
|
||||||
extends BitcoinSLogger {
|
|
||||||
|
|
||||||
def toMnemonic(password: AesPassword): Try[MnemonicCode] = {
|
def toMnemonic(password: AesPassword): Try[MnemonicCode] = {
|
||||||
import org.bitcoins.core.util.EitherUtil.EitherOps._
|
import org.bitcoins.core.util.EitherUtil.EitherOps._
|
||||||
|
|
|
@ -6,7 +6,7 @@ import org.bitcoins.core.currency._
|
||||||
import org.bitcoins.core.hd._
|
import org.bitcoins.core.hd._
|
||||||
import org.bitcoins.core.protocol.BitcoinAddress
|
import org.bitcoins.core.protocol.BitcoinAddress
|
||||||
import org.bitcoins.core.protocol.transaction._
|
import org.bitcoins.core.protocol.transaction._
|
||||||
import org.bitcoins.core.util.{BitcoinSLogger, EitherUtil}
|
import org.bitcoins.core.util.EitherUtil
|
||||||
import org.bitcoins.core.wallet.builder.BitcoinTxBuilder
|
import org.bitcoins.core.wallet.builder.BitcoinTxBuilder
|
||||||
import org.bitcoins.core.wallet.fee.FeeUnit
|
import org.bitcoins.core.wallet.fee.FeeUnit
|
||||||
import org.bitcoins.core.wallet.utxo.BitcoinUTXOSpendingInfo
|
import org.bitcoins.core.wallet.utxo.BitcoinUTXOSpendingInfo
|
||||||
|
@ -17,11 +17,9 @@ import scodec.bits.BitVector
|
||||||
|
|
||||||
import scala.concurrent.{ExecutionContext, Future}
|
import scala.concurrent.{ExecutionContext, Future}
|
||||||
import scala.util.{Failure, Success, Try}
|
import scala.util.{Failure, Success, Try}
|
||||||
|
import org.bitcoins.db.KeyHandlingLogger
|
||||||
|
|
||||||
sealed abstract class Wallet
|
sealed abstract class Wallet extends LockedWallet with UnlockedWalletApi {
|
||||||
extends LockedWallet
|
|
||||||
with UnlockedWalletApi
|
|
||||||
with BitcoinSLogger {
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @inheritdoc
|
* @inheritdoc
|
||||||
|
@ -140,7 +138,7 @@ sealed abstract class Wallet
|
||||||
}
|
}
|
||||||
|
|
||||||
// todo: create multiple wallets, need to maintain multiple databases
|
// todo: create multiple wallets, need to maintain multiple databases
|
||||||
object Wallet extends CreateWalletApi with BitcoinSLogger {
|
object Wallet extends CreateWalletApi with KeyHandlingLogger {
|
||||||
|
|
||||||
private case class WalletImpl(
|
private case class WalletImpl(
|
||||||
mnemonicCode: MnemonicCode
|
mnemonicCode: MnemonicCode
|
||||||
|
@ -235,6 +233,7 @@ object Wallet extends CreateWalletApi with BitcoinSLogger {
|
||||||
private def createRootAccount(wallet: Wallet, purpose: HDPurpose)(
|
private def createRootAccount(wallet: Wallet, purpose: HDPurpose)(
|
||||||
implicit config: WalletAppConfig,
|
implicit config: WalletAppConfig,
|
||||||
ec: ExecutionContext): Future[AccountDb] = {
|
ec: ExecutionContext): Future[AccountDb] = {
|
||||||
|
|
||||||
val coin =
|
val coin =
|
||||||
HDCoin(purpose, HDUtil.getCoinType(config.network))
|
HDCoin(purpose, HDUtil.getCoinType(config.network))
|
||||||
val account = HDAccount(coin, 0)
|
val account = HDAccount(coin, 0)
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
package org.bitcoins.wallet
|
package org.bitcoins.wallet
|
||||||
|
|
||||||
import scala.collection.JavaConverters._
|
import scala.collection.JavaConverters._
|
||||||
import org.bitcoins.core.util.BitcoinSLogger
|
|
||||||
import org.bitcoins.core.crypto.AesPassword
|
import org.bitcoins.core.crypto.AesPassword
|
||||||
import java.nio.file.Files
|
import java.nio.file.Files
|
||||||
import org.bitcoins.core.crypto.MnemonicCode
|
import org.bitcoins.core.crypto.MnemonicCode
|
||||||
|
@ -15,9 +14,10 @@ import java.nio.file.Path
|
||||||
import scala.util.Try
|
import scala.util.Try
|
||||||
import org.bitcoins.wallet.config.WalletAppConfig
|
import org.bitcoins.wallet.config.WalletAppConfig
|
||||||
import org.bitcoins.core.crypto.AesIV
|
import org.bitcoins.core.crypto.AesIV
|
||||||
|
import org.bitcoins.db.KeyHandlingLogger
|
||||||
|
|
||||||
// what do we do if seed exists? error if they aren't equal?
|
// what do we do if seed exists? error if they aren't equal?
|
||||||
object WalletStorage extends BitcoinSLogger {
|
object WalletStorage extends KeyHandlingLogger {
|
||||||
|
|
||||||
/** Checks if a wallet seed exists in datadir */
|
/** Checks if a wallet seed exists in datadir */
|
||||||
def seedExists()(implicit config: WalletAppConfig): Boolean = {
|
def seedExists()(implicit config: WalletAppConfig): Boolean = {
|
||||||
|
@ -183,6 +183,7 @@ object WalletStorage extends BitcoinSLogger {
|
||||||
def decryptMnemonicFromDisk(passphrase: AesPassword)(
|
def decryptMnemonicFromDisk(passphrase: AesPassword)(
|
||||||
implicit
|
implicit
|
||||||
config: WalletAppConfig): ReadMnemonicResult = {
|
config: WalletAppConfig): ReadMnemonicResult = {
|
||||||
|
|
||||||
val encryptedEither = readEncryptedMnemonicFromDisk()
|
val encryptedEither = readEncryptedMnemonicFromDisk()
|
||||||
|
|
||||||
import org.bitcoins.core.util.EitherUtil.EitherOps._
|
import org.bitcoins.core.util.EitherUtil.EitherOps._
|
||||||
|
|
|
@ -10,13 +10,24 @@ import java.nio.file.Files
|
||||||
import org.bitcoins.core.hd.HDPurpose
|
import org.bitcoins.core.hd.HDPurpose
|
||||||
import org.bitcoins.core.hd.HDPurposes
|
import org.bitcoins.core.hd.HDPurposes
|
||||||
import org.bitcoins.core.hd.AddressType
|
import org.bitcoins.core.hd.AddressType
|
||||||
|
import java.nio.file.Path
|
||||||
|
|
||||||
case class WalletAppConfig(private val conf: Config*) extends AppConfig {
|
/** Configuration for the Bitcoin-S wallet
|
||||||
override val configOverrides: List[Config] = conf.toList
|
* @param directory The data directory of the wallet
|
||||||
override def moduleName: String = "wallet"
|
* @param confs Optional sequence of configuration overrides
|
||||||
override type ConfigType = WalletAppConfig
|
*/
|
||||||
override def newConfigOfType(configs: Seq[Config]): WalletAppConfig =
|
case class WalletAppConfig(
|
||||||
WalletAppConfig(configs: _*)
|
private val directory: Path,
|
||||||
|
private val conf: Config*)
|
||||||
|
extends AppConfig {
|
||||||
|
override protected[bitcoins] def configOverrides: List[Config] = conf.toList
|
||||||
|
override protected[bitcoins] def moduleName: String = "wallet"
|
||||||
|
override protected[bitcoins] type ConfigType = WalletAppConfig
|
||||||
|
override protected[bitcoins] def newConfigOfType(
|
||||||
|
configs: Seq[Config]): WalletAppConfig =
|
||||||
|
WalletAppConfig(directory, configs: _*)
|
||||||
|
|
||||||
|
protected[bitcoins] def baseDatadir: Path = directory
|
||||||
|
|
||||||
lazy val defaultAccountKind: HDPurpose =
|
lazy val defaultAccountKind: HDPurpose =
|
||||||
config.getString("wallet.defaultAccountType") match {
|
config.getString("wallet.defaultAccountType") match {
|
||||||
|
@ -63,3 +74,12 @@ case class WalletAppConfig(private val conf: Config*) extends AppConfig {
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
object WalletAppConfig {
|
||||||
|
|
||||||
|
/** Constructs a wallet configuration from the default Bitcoin-S
|
||||||
|
* data directory and given list of configuration overrides.
|
||||||
|
*/
|
||||||
|
def fromDefaultDatadir(confs: Config*): WalletAppConfig =
|
||||||
|
WalletAppConfig(AppConfig.DEFAULT_BITCOIN_S_DATADIR, confs: _*)
|
||||||
|
}
|
||||||
|
|
|
@ -22,12 +22,14 @@ import org.bitcoins.core.protocol.script.ScriptPubKey
|
||||||
import org.bitcoins.core.protocol.transaction.TransactionOutPoint
|
import org.bitcoins.core.protocol.transaction.TransactionOutPoint
|
||||||
import org.bitcoins.core.number.UInt32
|
import org.bitcoins.core.number.UInt32
|
||||||
import org.bitcoins.core.hd.AddressType
|
import org.bitcoins.core.hd.AddressType
|
||||||
|
import org.bitcoins.db.KeyHandlingLogger
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides functionality related to addresses. This includes
|
* Provides functionality related to addresses. This includes
|
||||||
* enumeratng and creating them, primarily.
|
* enumeratng and creating them, primarily.
|
||||||
*/
|
*/
|
||||||
private[wallet] trait AddressHandling { self: LockedWallet =>
|
private[wallet] trait AddressHandling extends KeyHandlingLogger {
|
||||||
|
self: LockedWallet =>
|
||||||
|
|
||||||
override def listAddresses(): Future[Vector[AddressDb]] =
|
override def listAddresses(): Future[Vector[AddressDb]] =
|
||||||
addressDAO.findAll()
|
addressDAO.findAll()
|
||||||
|
|
|
@ -9,13 +9,15 @@ import org.bitcoins.wallet.api.AddUtxoSuccess
|
||||||
import org.bitcoins.wallet.api.AddUtxoError
|
import org.bitcoins.wallet.api.AddUtxoError
|
||||||
import org.bitcoins.core.number.UInt32
|
import org.bitcoins.core.number.UInt32
|
||||||
import org.bitcoins.core.util.FutureUtil
|
import org.bitcoins.core.util.FutureUtil
|
||||||
|
import org.bitcoins.db.KeyHandlingLogger
|
||||||
|
|
||||||
/** Provides functionality for processing transactions. This
|
/** Provides functionality for processing transactions. This
|
||||||
* includes importing UTXOs spent to our wallet, updating
|
* includes importing UTXOs spent to our wallet, updating
|
||||||
* confirmation counts and marking UTXOs as spent when
|
* confirmation counts and marking UTXOs as spent when
|
||||||
* spending from our wallet
|
* spending from our wallet
|
||||||
*/
|
*/
|
||||||
private[wallet] trait TransactionProcessing { self: LockedWallet =>
|
private[wallet] trait TransactionProcessing extends KeyHandlingLogger {
|
||||||
|
self: LockedWallet =>
|
||||||
/////////////////////
|
/////////////////////
|
||||||
// Public facing API
|
// Public facing API
|
||||||
|
|
||||||
|
|
|
@ -22,6 +22,7 @@ import org.bitcoins.core.protocol.BitcoinAddress
|
||||||
import scala.util.Success
|
import scala.util.Success
|
||||||
import scala.util.Failure
|
import scala.util.Failure
|
||||||
import org.bitcoins.core.crypto.DoubleSha256DigestBE
|
import org.bitcoins.core.crypto.DoubleSha256DigestBE
|
||||||
|
import org.bitcoins.db.KeyHandlingLogger
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides functionality related to handling UTXOs in our wallet.
|
* Provides functionality related to handling UTXOs in our wallet.
|
||||||
|
@ -29,7 +30,8 @@ import org.bitcoins.core.crypto.DoubleSha256DigestBE
|
||||||
* UTXOs in the wallet and importing a UTXO into the wallet for later
|
* UTXOs in the wallet and importing a UTXO into the wallet for later
|
||||||
* spending.
|
* spending.
|
||||||
*/
|
*/
|
||||||
private[wallet] trait UtxoHandling { self: LockedWallet =>
|
private[wallet] trait UtxoHandling extends KeyHandlingLogger {
|
||||||
|
self: LockedWallet =>
|
||||||
|
|
||||||
/** @inheritdoc */
|
/** @inheritdoc */
|
||||||
override def listUtxos(): Future[Vector[SpendingInfoDb]] =
|
override def listUtxos(): Future[Vector[SpendingInfoDb]] =
|
||||||
|
|
|
@ -15,8 +15,8 @@ import org.bitcoins.core.protocol.script.ScriptPubKey
|
||||||
import org.bitcoins.core.hd.HDPurpose
|
import org.bitcoins.core.hd.HDPurpose
|
||||||
|
|
||||||
case class AddressDAO()(
|
case class AddressDAO()(
|
||||||
implicit val ec: ExecutionContext,
|
implicit ec: ExecutionContext,
|
||||||
val appConfig: WalletAppConfig
|
config: WalletAppConfig
|
||||||
) extends CRUD[AddressDb, BitcoinAddress] {
|
) extends CRUD[AddressDb, BitcoinAddress] {
|
||||||
import org.bitcoins.db.DbCommonsColumnMappers._
|
import org.bitcoins.db.DbCommonsColumnMappers._
|
||||||
|
|
||||||
|
|
|
@ -16,7 +16,6 @@ import org.bitcoins.core.hd.HDPath
|
||||||
|
|
||||||
import org.bitcoins.core.hd.SegWitHDPath
|
import org.bitcoins.core.hd.SegWitHDPath
|
||||||
import org.bitcoins.core.crypto.BIP39Seed
|
import org.bitcoins.core.crypto.BIP39Seed
|
||||||
import org.bitcoins.core.util.BitcoinSLogger
|
|
||||||
import org.bitcoins.core.hd.LegacyHDPath
|
import org.bitcoins.core.hd.LegacyHDPath
|
||||||
import org.bitcoins.core.crypto.DoubleSha256DigestBE
|
import org.bitcoins.core.crypto.DoubleSha256DigestBE
|
||||||
|
|
||||||
|
@ -85,9 +84,7 @@ case class LegacySpendingInfo(
|
||||||
* we need to derive the private keys, given
|
* we need to derive the private keys, given
|
||||||
* the root wallet seed.
|
* the root wallet seed.
|
||||||
*/
|
*/
|
||||||
sealed trait SpendingInfoDb
|
sealed trait SpendingInfoDb extends DbRowAutoInc[SpendingInfoDb] {
|
||||||
extends DbRowAutoInc[SpendingInfoDb]
|
|
||||||
with BitcoinSLogger {
|
|
||||||
|
|
||||||
protected type PathType <: HDPath
|
protected type PathType <: HDPath
|
||||||
|
|
||||||
|
@ -141,13 +138,6 @@ sealed trait SpendingInfoDb
|
||||||
|
|
||||||
val sign: Sign = Sign(privKey.signFunction, pubAtPath)
|
val sign: Sign = Sign(privKey.signFunction, pubAtPath)
|
||||||
|
|
||||||
logger.info({
|
|
||||||
val shortStr = s"${outPoint.txId.hex}:${outPoint.vout.toInt}"
|
|
||||||
val detailsStr =
|
|
||||||
s"scriptPubKey=${output.scriptPubKey}, amount=${output.value}, keyPath=${privKeyPath}, pubKey=${pubAtPath}"
|
|
||||||
s"Converting DB UTXO $shortStr ($detailsStr) to spending info"
|
|
||||||
})
|
|
||||||
|
|
||||||
BitcoinUTXOSpendingInfo(outPoint,
|
BitcoinUTXOSpendingInfo(outPoint,
|
||||||
output,
|
output,
|
||||||
List(sign),
|
List(sign),
|
||||||
|
|
Loading…
Add table
Reference in a new issue