2024 09 24 simplify wallet (#5685)

* WIP: Simplify wallet

# Conflicts:
#	fee-provider/src/main/scala/org/bitcoins/feeprovider/FeeProviderFactory.scala

# Conflicts:
#	wallet/src/main/scala/org/bitcoins/wallet/Wallet.scala

* Get walletTest/test passing

* Remove WalletApi.{start(),stop()}

 Conflicts:
	core/src/main/scala/org/bitcoins/core/api/wallet/WalletApi.scala
	wallet/src/main/scala/org/bitcoins/wallet/Wallet.scala
	wallet/src/main/scala/org/bitcoins/wallet/WalletHolder.scala

* Cleanup RescanDLCTest

* Move checkRootAccount into AccountHandling.scala

* Fix rebase

* Fix docs
This commit is contained in:
Chris Stewart 2024-09-27 13:40:29 -05:00 committed by GitHub
parent 7caea21b6a
commit 13a895efe9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
27 changed files with 175 additions and 211 deletions

View File

@ -125,8 +125,7 @@ class RoutesSpec extends AnyWordSpec with ScalatestRouteTest with MockFactory {
DLCWalletNeutrinoBackendLoader( DLCWalletNeutrinoBackendLoader(
walletHolder, walletHolder,
mockChainApi, mockChainApi,
mockNode, mockNode
feeRateApi
) )
} }

View File

@ -51,8 +51,7 @@ class WalletRoutesSpec
DLCWalletNeutrinoBackendLoader( DLCWalletNeutrinoBackendLoader(
walletHolder, walletHolder,
mockChainApi, mockChainApi,
mockNode, mockNode
feeRateApi
) )
val walletRoutes: WalletRoutes = val walletRoutes: WalletRoutes =

View File

@ -29,7 +29,6 @@ import org.bitcoins.core.util.TimeUtil
import org.bitcoins.dlc.node.DLCNode import org.bitcoins.dlc.node.DLCNode
import org.bitcoins.dlc.node.config.DLCNodeAppConfig import org.bitcoins.dlc.node.config.DLCNodeAppConfig
import org.bitcoins.dlc.wallet.* import org.bitcoins.dlc.wallet.*
import org.bitcoins.feeprovider.MempoolSpaceTarget.HourFeeTarget
import org.bitcoins.feeprovider.* import org.bitcoins.feeprovider.*
import org.bitcoins.node.Node import org.bitcoins.node.Node
import org.bitcoins.node.config.NodeAppConfig import org.bitcoins.node.config.NodeAppConfig
@ -185,15 +184,6 @@ class BitcoinSServerMain(override val serverArgParser: ServerArgParser)(implicit
walletCreationTimeOpt = Some(creationTime) walletCreationTimeOpt = Some(creationTime)
)(chainConf, system) )(chainConf, system)
val defaultApi =
MempoolSpaceProvider(HourFeeTarget, network, torConf.socks5ProxyParams)
val feeProvider = FeeProviderFactory.getFeeProviderOrElse(
defaultApi,
conf.walletConf.feeProviderNameOpt,
conf.walletConf.feeProviderTargetOpt,
torConf.socks5ProxyParams,
network
)
// get our wallet // get our wallet
val walletHolder = WalletHolder.empty val walletHolder = WalletHolder.empty
val neutrinoWalletLoaderF = { val neutrinoWalletLoaderF = {
@ -203,8 +193,7 @@ class BitcoinSServerMain(override val serverArgParser: ServerArgParser)(implicit
val l = DLCWalletNeutrinoBackendLoader( val l = DLCWalletNeutrinoBackendLoader(
walletHolder, walletHolder,
chainApi, chainApi,
nodeApi = node, nodeApi = node
feeRateApi = feeProvider
) )
walletLoaderApiOpt = Some(l) walletLoaderApiOpt = Some(l)
l l

View File

@ -222,8 +222,7 @@ object BitcoindRpcBackendUtil extends BitcoinSLogger {
) )
val pairedWallet = Wallet( val pairedWallet = Wallet(
nodeApi = nodeApi, nodeApi = nodeApi,
chainQueryApi = bitcoind, chainQueryApi = bitcoind
feeRateApi = wallet.feeRateApi
)(wallet.walletConfig) )(wallet.walletConfig)
walletCallbackP.success(pairedWallet) walletCallbackP.success(pairedWallet)
@ -305,8 +304,7 @@ object BitcoindRpcBackendUtil extends BitcoinSLogger {
walletCallbackP.future, walletCallbackP.future,
chainCallbacksOpt chainCallbacksOpt
), ),
chainQueryApi = bitcoind, chainQueryApi = bitcoind
feeRateApi = wallet.feeRateApi
)(wallet.walletConfig, wallet.dlcConfig) )(wallet.walletConfig, wallet.dlcConfig)
walletCallbackP.success(pairedWallet) walletCallbackP.success(pairedWallet)

View File

@ -50,7 +50,6 @@ sealed trait DLCWalletLoaderApi
protected def loadWallet( protected def loadWallet(
chainQueryApi: ChainQueryApi, chainQueryApi: ChainQueryApi,
nodeApi: NodeApi, nodeApi: NodeApi,
feeProviderApi: FeeRateApi,
walletNameOpt: Option[String], walletNameOpt: Option[String],
aesPasswordOpt: Option[AesPassword] aesPasswordOpt: Option[AesPassword]
)(implicit )(implicit
@ -71,8 +70,7 @@ sealed trait DLCWalletLoaderApi
_ <- dlcConfig.start() _ <- dlcConfig.start()
dlcWallet <- dlcConfig.createDLCWallet( dlcWallet <- dlcConfig.createDLCWallet(
nodeApi = nodeApi, nodeApi = nodeApi,
chainQueryApi = chainQueryApi, chainQueryApi = chainQueryApi
feeRateApi = feeProviderApi
)(walletConfig) )(walletConfig)
} yield (dlcWallet, walletConfig, dlcConfig) } yield (dlcWallet, walletConfig, dlcConfig)
} }
@ -271,8 +269,7 @@ sealed trait DLCWalletLoaderApi
case class DLCWalletNeutrinoBackendLoader( case class DLCWalletNeutrinoBackendLoader(
walletHolder: WalletHolder, walletHolder: WalletHolder,
chainQueryApi: ChainQueryApi, chainQueryApi: ChainQueryApi,
nodeApi: NodeApi, nodeApi: NodeApi
feeRateApi: FeeRateApi
)(implicit )(implicit
override val conf: BitcoinSAppConfig, override val conf: BitcoinSAppConfig,
override val system: ActorSystem override val system: ActorSystem
@ -301,7 +298,6 @@ case class DLCWalletNeutrinoBackendLoader(
(dlcWallet, walletConfig, dlcConfig) <- loadWallet( (dlcWallet, walletConfig, dlcConfig) <- loadWallet(
chainQueryApi = chainQueryApi, chainQueryApi = chainQueryApi,
nodeApi = nodeApi, nodeApi = nodeApi,
feeProviderApi = feeRateApi,
walletNameOpt = walletNameOpt, walletNameOpt = walletNameOpt,
aesPasswordOpt = aesPasswordOpt aesPasswordOpt = aesPasswordOpt
) )
@ -351,7 +347,6 @@ case class DLCWalletBitcoindBackendLoader(
(dlcWallet, walletConfig, dlcConfig) <- loadWallet( (dlcWallet, walletConfig, dlcConfig) <- loadWallet(
chainQueryApi = bitcoind, chainQueryApi = bitcoind,
nodeApi = nodeApi, nodeApi = nodeApi,
feeProviderApi = feeProvider,
walletNameOpt = walletNameOpt, walletNameOpt = walletNameOpt,
aesPasswordOpt = aesPasswordOpt aesPasswordOpt = aesPasswordOpt
) )

View File

@ -775,7 +775,7 @@ lazy val wallet = project
name := "bitcoin-s-wallet", name := "bitcoin-s-wallet",
libraryDependencies ++= Deps.wallet(scalaVersion.value) libraryDependencies ++= Deps.wallet(scalaVersion.value)
) )
.dependsOn(coreJVM, appCommons, dbCommons, keyManager, asyncUtilsJVM, tor) .dependsOn(coreJVM, appCommons, dbCommons, keyManager, asyncUtilsJVM, feeProvider, tor)
lazy val walletTest = project lazy val walletTest = project
.in(file("wallet-test")) .in(file("wallet-test"))

View File

@ -8,7 +8,6 @@ import org.bitcoins.core.currency.CurrencyUnit
import org.bitcoins.core.hd.HDAccount import org.bitcoins.core.hd.HDAccount
import org.bitcoins.core.protocol.BitcoinAddress import org.bitcoins.core.protocol.BitcoinAddress
import org.bitcoins.core.protocol.transaction.Transaction import org.bitcoins.core.protocol.transaction.Transaction
import org.bitcoins.core.util.StartStopAsync
import org.bitcoins.core.wallet.fee.FeeUnit import org.bitcoins.core.wallet.fee.FeeUnit
import org.bitcoins.crypto.DoubleSha256DigestBE import org.bitcoins.crypto.DoubleSha256DigestBE
@ -22,7 +21,7 @@ import scala.concurrent.{ExecutionContext, Future}
* @see * @see
* [[https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki BIP44]] * [[https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki BIP44]]
*/ */
trait WalletApi extends StartStopAsync[WalletApi] { trait WalletApi {
def accountHandling: AccountHandlingApi def accountHandling: AccountHandlingApi
def fundTxHandling: FundTransactionHandlingApi def fundTxHandling: FundTransactionHandlingApi
def rescanHandling: RescanHandlingApi def rescanHandling: RescanHandlingApi
@ -33,7 +32,7 @@ trait WalletApi extends StartStopAsync[WalletApi] {
val nodeApi: NodeApi val nodeApi: NodeApi
val chainQueryApi: ChainQueryApi val chainQueryApi: ChainQueryApi
val feeRateApi: FeeRateApi def feeRateApi: FeeRateApi
val creationTime: Instant val creationTime: Instant
def broadcastTransaction(transaction: Transaction): Future[Unit] = def broadcastTransaction(transaction: Transaction): Future[Unit] =
@ -41,10 +40,6 @@ trait WalletApi extends StartStopAsync[WalletApi] {
def getFeeRate(): Future[FeeUnit] = feeRateApi.getFeeRate() def getFeeRate(): Future[FeeUnit] = feeRateApi.getFeeRate()
def start(): Future[WalletApi]
def stop(): Future[WalletApi]
/** Gets the sum of all UTXOs in this wallet */ /** Gets the sum of all UTXOs in this wallet */
def getBalance()(implicit ec: ExecutionContext): Future[CurrencyUnit] = { def getBalance()(implicit ec: ExecutionContext): Future[CurrencyUnit] = {
val confirmedF = getConfirmedBalance() val confirmedF = getConfirmedBalance()

View File

@ -6,8 +6,9 @@ import org.bitcoins.core.protocol.dlc.models.{
DisjointUnionContractInfo, DisjointUnionContractInfo,
SingleContractInfo SingleContractInfo
} }
import org.bitcoins.core.wallet.rescan.RescanState
import org.bitcoins.rpc.client.common.BitcoindRpcClient import org.bitcoins.rpc.client.common.BitcoindRpcClient
import org.bitcoins.testkit.wallet.DLCWalletUtil._ import org.bitcoins.testkit.wallet.DLCWalletUtil.*
import org.bitcoins.testkit.wallet.{DLCWalletUtil, DualWalletTestCachedBitcoind} import org.bitcoins.testkit.wallet.{DLCWalletUtil, DualWalletTestCachedBitcoind}
import org.scalatest.FutureOutcome import org.scalatest.FutureOutcome
@ -55,7 +56,7 @@ class RescanDLCTest extends DualWalletTestCachedBitcoind {
Vector(hash) <- bitcoind.generate(1) Vector(hash) <- bitcoind.generate(1)
_ <- wallet.rescanHandling.rescanNeutrinoWallet( rescanState <- wallet.rescanHandling.rescanNeutrinoWallet(
startOpt = None, startOpt = None,
endOpt = Some(BlockHash(hash)), endOpt = Some(BlockHash(hash)),
addressBatchSize = 20, addressBatchSize = 20,
@ -64,6 +65,7 @@ class RescanDLCTest extends DualWalletTestCachedBitcoind {
) )
postStatus <- getDLCStatus(wallet) postStatus <- getDLCStatus(wallet)
_ <- RescanState.awaitRescanDone(rescanState)
} yield assert(postStatus.state == DLCState.Claimed) } yield assert(postStatus.state == DLCState.Claimed)
} }
@ -100,7 +102,7 @@ class RescanDLCTest extends DualWalletTestCachedBitcoind {
Vector(hash) <- bitcoind.generate(1) Vector(hash) <- bitcoind.generate(1)
_ <- wallet.rescanHandling.rescanNeutrinoWallet( rescanState <- wallet.rescanHandling.rescanNeutrinoWallet(
startOpt = None, startOpt = None,
endOpt = Some(BlockHash(hash)), endOpt = Some(BlockHash(hash)),
addressBatchSize = 20, addressBatchSize = 20,
@ -109,6 +111,7 @@ class RescanDLCTest extends DualWalletTestCachedBitcoind {
) )
postStatus <- getDLCStatus(wallet) postStatus <- getDLCStatus(wallet)
_ <- RescanState.awaitRescanDone(rescanState)
} yield assert(postStatus.state == DLCState.RemoteClaimed) } yield assert(postStatus.state == DLCState.RemoteClaimed)
} }
} }

View File

@ -6,7 +6,6 @@ import org.bitcoins.commons.config.{AppConfigFactoryBase, ConfigOps}
import org.bitcoins.core.api.CallbackConfig import org.bitcoins.core.api.CallbackConfig
import org.bitcoins.core.api.chain.ChainQueryApi import org.bitcoins.core.api.chain.ChainQueryApi
import org.bitcoins.core.api.dlc.wallet.db.DLCDb import org.bitcoins.core.api.dlc.wallet.db.DLCDb
import org.bitcoins.core.api.feeprovider.FeeRateApi
import org.bitcoins.core.api.node.NodeApi import org.bitcoins.core.api.node.NodeApi
import org.bitcoins.core.protocol.dlc.models.DLCState.{ import org.bitcoins.core.protocol.dlc.models.DLCState.{
AdaptorSigComputationState, AdaptorSigComputationState,
@ -144,13 +143,11 @@ case class DLCAppConfig(
def createDLCWallet( def createDLCWallet(
nodeApi: NodeApi, nodeApi: NodeApi,
chainQueryApi: ChainQueryApi, chainQueryApi: ChainQueryApi
feeRateApi: FeeRateApi
)(implicit walletConf: WalletAppConfig): Future[DLCWallet] = { )(implicit walletConf: WalletAppConfig): Future[DLCWallet] = {
DLCAppConfig.createDLCWallet( DLCAppConfig.createDLCWallet(
nodeApi = nodeApi, nodeApi = nodeApi,
chainQueryApi = chainQueryApi, chainQueryApi = chainQueryApi
feeRateApi = feeRateApi
)(walletConf, this) )(walletConf, this)
} }
@ -327,8 +324,7 @@ object DLCAppConfig
/** Creates a wallet based on the given [[WalletAppConfig]] */ /** Creates a wallet based on the given [[WalletAppConfig]] */
def createDLCWallet( def createDLCWallet(
nodeApi: NodeApi, nodeApi: NodeApi,
chainQueryApi: ChainQueryApi, chainQueryApi: ChainQueryApi
feeRateApi: FeeRateApi
)(implicit )(implicit
walletConf: WalletAppConfig, walletConf: WalletAppConfig,
dlcConf: DLCAppConfig dlcConf: DLCAppConfig
@ -339,12 +335,12 @@ object DLCAppConfig
if (walletExists) { if (walletExists) {
logger.info(s"Using pre-existing wallet") logger.info(s"Using pre-existing wallet")
val wallet = val wallet =
DLCWallet(nodeApi, chainQueryApi, feeRateApi) DLCWallet(nodeApi, chainQueryApi)
Future.successful(wallet) Future.successful(wallet)
} else { } else {
logger.info(s"Creating new wallet") logger.info(s"Creating new wallet")
val unInitializedWallet = val unInitializedWallet =
DLCWallet(nodeApi, chainQueryApi, feeRateApi) DLCWallet(nodeApi, chainQueryApi)
Wallet Wallet
.initialize( .initialize(

View File

@ -3,7 +3,6 @@ package org.bitcoins.dlc.wallet
import org.bitcoins.core.api.chain.ChainQueryApi import org.bitcoins.core.api.chain.ChainQueryApi
import org.bitcoins.core.api.dlc.wallet.DLCNeutrinoHDWalletApi import org.bitcoins.core.api.dlc.wallet.DLCNeutrinoHDWalletApi
import org.bitcoins.core.api.dlc.wallet.db.DLCDb import org.bitcoins.core.api.dlc.wallet.db.DLCDb
import org.bitcoins.core.api.feeprovider.FeeRateApi
import org.bitcoins.core.api.node.NodeApi import org.bitcoins.core.api.node.NodeApi
import org.bitcoins.core.api.wallet.db.* import org.bitcoins.core.api.wallet.db.*
import org.bitcoins.core.currency.* import org.bitcoins.core.currency.*
@ -2267,8 +2266,7 @@ object DLCWallet extends WalletLogger {
private case class DLCWalletImpl( private case class DLCWalletImpl(
nodeApi: NodeApi, nodeApi: NodeApi,
chainQueryApi: ChainQueryApi, chainQueryApi: ChainQueryApi
feeRateApi: FeeRateApi
)(implicit )(implicit
val walletConfig: WalletAppConfig, val walletConfig: WalletAppConfig,
val dlcConfig: DLCAppConfig val dlcConfig: DLCAppConfig
@ -2276,10 +2274,9 @@ object DLCWallet extends WalletLogger {
def apply( def apply(
nodeApi: NodeApi, nodeApi: NodeApi,
chainQueryApi: ChainQueryApi, chainQueryApi: ChainQueryApi
feeRateApi: FeeRateApi
)(implicit config: WalletAppConfig, dlcConfig: DLCAppConfig): DLCWallet = { )(implicit config: WalletAppConfig, dlcConfig: DLCAppConfig): DLCWallet = {
DLCWalletImpl(nodeApi, chainQueryApi, feeRateApi) DLCWalletImpl(nodeApi, chainQueryApi)
} }
private object AcceptingOffersLatch { private object AcceptingOffersLatch {

View File

@ -188,7 +188,7 @@ val chainApi = new ChainQueryApi {
// Finally, we can initialize our wallet with our own node api // Finally, we can initialize our wallet with our own node api
val wallet = val wallet =
Wallet(nodeApi = nodeApi, chainQueryApi = chainApi, feeRateApi = ConstantFeeRateProvider(SatoshisPerVirtualByte.one)) Wallet(nodeApi = nodeApi, chainQueryApi = chainApi)
// Then to trigger one of the events we can run // Then to trigger one of the events we can run
wallet.chainQueryApi.getFiltersBetweenHeights(100, 150) wallet.chainQueryApi.getFiltersBetweenHeights(100, 150)

View File

@ -95,7 +95,7 @@ val exampleCallback = createCallback(exampleProcessBlock)
// Finally, we can initialize our wallet with our own node api // Finally, we can initialize our wallet with our own node api
val wallet = val wallet =
Wallet(nodeApi = nodeApi, chainQueryApi = chainApi, feeRateApi = ConstantFeeRateProvider(SatoshisPerVirtualByte.one)) Wallet(nodeApi = nodeApi, chainQueryApi = chainApi)
// Then to trigger the event we can run // Then to trigger the event we can run
val exampleBlock = DoubleSha256DigestBE( val exampleBlock = DoubleSha256DigestBE(

View File

@ -70,8 +70,7 @@ val exampleCallbacks = WalletCallbacks(
val wallet = val wallet =
Wallet( Wallet(
nodeApi = bitcoind, nodeApi = bitcoind,
chainQueryApi = bitcoind, chainQueryApi = bitcoind)
feeRateApi = ConstantFeeRateProvider(SatoshisPerVirtualByte.one))
// Finally, we can add the callbacks to our wallet config // Finally, we can add the callbacks to our wallet config
walletConf.addCallbacks(exampleCallbacks) walletConf.addCallbacks(exampleCallbacks)

View File

@ -115,8 +115,7 @@ val genesisHashBEF = bitcoind.getBlockHash(0)
//a fresh wallet //a fresh wallet
implicit val walletAppConfig: WalletAppConfig = WalletAppConfig.fromDefaultDatadir() implicit val walletAppConfig: WalletAppConfig = WalletAppConfig.fromDefaultDatadir()
val feeRateProvider: FeeRateApi = MempoolSpaceProvider.fromBlockTarget(6, proxyParams = None) val wallet = Wallet(bitcoind, bitcoind)
val wallet = Wallet(bitcoind, bitcoind, feeRateProvider)
//yay! we have a synced wallet //yay! we have a synced wallet
val syncedWalletF = genesisHashBEF.flatMap { genesisHash => val syncedWalletF = genesisHashBEF.flatMap { genesisHash =>

View File

@ -150,7 +150,7 @@ val wallet = Wallet(new NodeApi {
override def broadcastTransactions(txs: Vector[Transaction]): Future[Unit] = Future.successful(()) override def broadcastTransactions(txs: Vector[Transaction]): Future[Unit] = Future.successful(())
override def downloadBlocks(blockHashes: Vector[DoubleSha256DigestBE]): Future[Unit] = Future.successful(()) override def downloadBlocks(blockHashes: Vector[DoubleSha256DigestBE]): Future[Unit] = Future.successful(())
override def getConnectionCount: Future[Int] = Future.successful(0) override def getConnectionCount: Future[Int] = Future.successful(0)
}, chainApi, ConstantFeeRateProvider(SatoshisPerVirtualByte.one)) }, chainApi)
val walletF: Future[WalletApi] = configF.flatMap { _ => val walletF: Future[WalletApi] = configF.flatMap { _ =>
Wallet.initialize(wallet, wallet.accountHandling, None) Wallet.initialize(wallet, wallet.accountHandling, None)
} }

View File

@ -17,7 +17,7 @@ object FeeProviderName extends StringFactory[FeeProviderName] {
case object Random extends FeeProviderName case object Random extends FeeProviderName
val all: Vector[FeeProviderName] = val all: Vector[FeeProviderName] =
Vector(BitcoinerLive, BitGo, Constant, MempoolSpace) Vector(BitcoinerLive, BitGo, Constant, MempoolSpace, Random)
override def fromStringOpt(str: String): Option[FeeProviderName] = { override def fromStringOpt(str: String): Option[FeeProviderName] = {
all.find(_.toString.toLowerCase == str.toLowerCase) all.find(_.toString.toLowerCase == str.toLowerCase)

View File

@ -14,7 +14,7 @@ class RandomFeeProvider extends FeeRateApi {
var lastFeeRate: Option[FeeUnit] = None var lastFeeRate: Option[FeeUnit] = None
override def getFeeRate(): Future[FeeUnit] = { override def getFeeRate(): Future[FeeUnit] = {
val raw = scala.util.Random.between(1, 10000) val raw = scala.util.Random.between(1, 1000)
val feeRate = SatoshisPerVirtualByte(Satoshis(raw)) val feeRate = SatoshisPerVirtualByte(Satoshis(raw))
lastFeeRate = Some(feeRate) lastFeeRate = Some(feeRate)
Future.successful(feeRate) Future.successful(feeRate)

View File

@ -84,6 +84,7 @@ object BitcoinSTestAppConfig {
| relay = true | relay = true
| enable-peer-discovery = false | enable-peer-discovery = false
| } | }
| fee-provider.name = "random"
| proxy.enabled = $torEnabled | proxy.enabled = $torEnabled
| tor.enabled = $torEnabled | tor.enabled = $torEnabled
| tor.use-random-ports = false | tor.use-random-ports = false

View File

@ -216,11 +216,8 @@ trait NodeTestWithCachedBitcoind extends BaseNodeTest with CachedTor {
): Future[Unit] = { ): Future[Unit] = {
val node = nodeWithBitcoind.node val node = nodeWithBitcoind.node
val destroyNodeF = tearDownNode(node, appConfig) val destroyNodeF = tearDownNode(node, appConfig)
val destroyWalletF =
BitcoinSWalletTest.destroyWallet(nodeWithBitcoind.wallet)
for { for {
_ <- destroyNodeF _ <- destroyNodeF
_ <- destroyWalletF
_ <- BitcoinSWalletTest.destroyWalletAppConfig(appConfig.walletConf) _ <- BitcoinSWalletTest.destroyWalletAppConfig(appConfig.walletConf)
} yield () } yield ()
} }

View File

@ -24,8 +24,7 @@ trait WalletLoaderFixtures
// initialize the default wallet so it can be used in tests // initialize the default wallet so it can be used in tests
_ <- config.walletConf.createHDWallet( _ <- config.walletConf.createHDWallet(
nodeApi = bitcoind, nodeApi = bitcoind,
chainQueryApi = bitcoind, chainQueryApi = bitcoind
feeRateApi = bitcoind
) )
walletHolder = WalletHolder.empty walletHolder = WalletHolder.empty

View File

@ -9,7 +9,6 @@ import org.bitcoins.core.api.node.NodeApi
import org.bitcoins.core.api.wallet.WalletApi import org.bitcoins.core.api.wallet.WalletApi
import org.bitcoins.core.currency.* import org.bitcoins.core.currency.*
import org.bitcoins.dlc.wallet.{DLCAppConfig, DLCWallet} import org.bitcoins.dlc.wallet.{DLCAppConfig, DLCWallet}
import org.bitcoins.feeprovider.RandomFeeProvider
import org.bitcoins.node.NodeCallbacks import org.bitcoins.node.NodeCallbacks
import org.bitcoins.rpc.client.common.{BitcoindRpcClient, BitcoindVersion} import org.bitcoins.rpc.client.common.{BitcoindRpcClient, BitcoindVersion}
import org.bitcoins.server.BitcoinSAppConfig import org.bitcoins.server.BitcoinSAppConfig
@ -75,9 +74,8 @@ trait BitcoinSWalletTest
makeDependentFixture[Wallet]( makeDependentFixture[Wallet](
build = build =
createNewWallet(nodeApi = nodeApi, chainQueryApi = chainQueryApi), createNewWallet(nodeApi = nodeApi, chainQueryApi = chainQueryApi),
destroy = { wallet => destroy = { (_: WalletApi) =>
for { for {
_ <- destroyWallet(wallet)
_ <- destroyWalletAppConfig(newWalletConf) _ <- destroyWalletAppConfig(newWalletConf)
} yield () } yield ()
} }
@ -93,9 +91,8 @@ trait BitcoinSWalletTest
)(implicit walletAppConfig: WalletAppConfig): FutureOutcome = { )(implicit walletAppConfig: WalletAppConfig): FutureOutcome = {
makeDependentFixture( makeDependentFixture(
build = () => FundWalletUtil.createFundedWallet(nodeApi, chainQueryApi), build = () => FundWalletUtil.createFundedWallet(nodeApi, chainQueryApi),
destroy = { (funded: FundedWallet) => destroy = { (_: FundedWallet) =>
for { for {
_ <- destroyWallet(funded.wallet)
_ <- destroyWalletAppConfig(walletAppConfig) _ <- destroyWalletAppConfig(walletAppConfig)
} yield () } yield ()
} }
@ -107,9 +104,8 @@ trait BitcoinSWalletTest
)(implicit walletAppConfig: WalletAppConfig): FutureOutcome = { )(implicit walletAppConfig: WalletAppConfig): FutureOutcome = {
makeDependentFixture( makeDependentFixture(
build = () => FundWalletUtil.createFundedWallet(nodeApi, chainQueryApi), build = () => FundWalletUtil.createFundedWallet(nodeApi, chainQueryApi),
destroy = { (funded: FundedWallet) => destroy = { (_: FundedWallet) =>
for { for {
_ <- destroyWallet(funded.wallet)
_ <- destroyWalletAppConfig(walletAppConfig) _ <- destroyWalletAppConfig(walletAppConfig)
} yield () } yield ()
} }
@ -152,9 +148,8 @@ trait BitcoinSWalletTest
build = { () => build = { () =>
createDefaultWallet(nodeApi, chainQueryApi) createDefaultWallet(nodeApi, chainQueryApi)
}, },
destroy = { wallet => destroy = { (_: WalletApi) =>
for { for {
_ <- destroyWallet(wallet)
_ <- destroyWalletAppConfig(walletAppConfig) _ <- destroyWalletAppConfig(walletAppConfig)
} yield () } yield ()
} }
@ -168,7 +163,7 @@ trait BitcoinSWalletTest
build = { () => build = { () =>
createWallet2Accounts(nodeApi, chainQueryApi) createWallet2Accounts(nodeApi, chainQueryApi)
}, },
destroy = destroyWallet destroy = { (_: WalletApi) => Future.unit }
)(test) )(test)
} }
@ -285,7 +280,7 @@ object BitcoinSWalletTest extends WalletLogger {
walletConfig.start().flatMap { _ => walletConfig.start().flatMap { _ =>
val wallet = val wallet =
Wallet(nodeApi, chainQueryApi, new RandomFeeProvider)(walletConfig) Wallet(nodeApi, chainQueryApi)(walletConfig)
Wallet.initialize(wallet, Wallet.initialize(wallet,
wallet.accountHandling, wallet.accountHandling,
walletConfig.bip39PasswordOpt) walletConfig.bip39PasswordOpt)
@ -310,7 +305,7 @@ object BitcoinSWalletTest extends WalletLogger {
initConfs.flatMap { _ => initConfs.flatMap { _ =>
val wallet = val wallet =
DLCWallet(nodeApi, chainQueryApi, new RandomFeeProvider)( DLCWallet(nodeApi, chainQueryApi)(
config.walletConf, config.walletConf,
config.dlcConf config.dlcConf
) )
@ -351,8 +346,7 @@ object BitcoinSWalletTest extends WalletLogger {
walletWithCallback = Wallet( walletWithCallback = Wallet(
nodeApi = nodeApi =
SyncUtil.getNodeApiWalletCallback(bitcoind, walletCallbackP.future), SyncUtil.getNodeApiWalletCallback(bitcoind, walletCallbackP.future),
chainQueryApi = bitcoind, chainQueryApi = bitcoind
feeRateApi = new RandomFeeProvider
)(wallet.walletConfig) )(wallet.walletConfig)
// complete the walletCallbackP so we can handle the callbacks when they are // complete the walletCallbackP so we can handle the callbacks when they are
// called without hanging forever. // called without hanging forever.
@ -523,23 +517,13 @@ object BitcoinSWalletTest extends WalletLogger {
walletWithBitcoind: WalletWithBitcoind[_] walletWithBitcoind: WalletWithBitcoind[_]
)(implicit ec: ExecutionContext): Future[Unit] = { )(implicit ec: ExecutionContext): Future[Unit] = {
for { for {
_ <- destroyWallet(walletWithBitcoind.wallet)
_ <- destroyWalletAppConfig(walletWithBitcoind.walletConfig) _ <- destroyWalletAppConfig(walletWithBitcoind.walletConfig)
} yield () } yield ()
} }
def destroyWallet(
wallet: WalletApi
)(implicit ec: ExecutionContext): Future[Unit] = {
for {
_ <- wallet.stop()
} yield ()
}
def destroyDLCWallet(wallet: DLCWallet): Future[Unit] = { def destroyDLCWallet(wallet: DLCWallet): Future[Unit] = {
import wallet.ec import wallet.ec
for { for {
_ <- destroyWallet(wallet)
_ <- destroyWalletAppConfig(wallet.walletConfig) _ <- destroyWalletAppConfig(wallet.walletConfig)
_ <- wallet.dlcConfig.stop() _ <- wallet.dlcConfig.stop()
} yield () } yield ()

View File

@ -6,9 +6,7 @@ import org.bitcoins.core.crypto.{ExtPublicKey, MnemonicCode}
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.util.FutureUtil import org.bitcoins.core.util.FutureUtil
import org.bitcoins.core.wallet.fee.SatoshisPerVirtualByte
import org.bitcoins.core.wallet.keymanagement.KeyManagerParams import org.bitcoins.core.wallet.keymanagement.KeyManagerParams
import org.bitcoins.feeprovider.ConstantFeeRateProvider
import org.bitcoins.testkit.BitcoinSTestAppConfig import org.bitcoins.testkit.BitcoinSTestAppConfig
import org.bitcoins.testkit.chain.MockChainQueryApi import org.bitcoins.testkit.chain.MockChainQueryApi
import org.bitcoins.testkit.fixtures.EmptyFixture import org.bitcoins.testkit.fixtures.EmptyFixture
@ -153,8 +151,7 @@ class TrezorAddressTest extends BitcoinSWalletTest with EmptyFixture {
wallet = wallet =
Wallet( Wallet(
MockNodeApi, MockNodeApi,
MockChainQueryApi, MockChainQueryApi
ConstantFeeRateProvider(SatoshisPerVirtualByte.one)
)(config) )(config)
init <- Wallet.initialize( init <- Wallet.initialize(
wallet = wallet, wallet = wallet,

View File

@ -204,7 +204,7 @@ class WalletUnitTest extends BitcoinSWalletTest {
val walletDiffKeyManagerF: Future[Wallet] = for { val walletDiffKeyManagerF: Future[Wallet] = for {
_ <- startedF _ <- startedF
} yield { } yield {
Wallet(wallet.nodeApi, wallet.chainQueryApi, wallet.feeRateApi)( Wallet(wallet.nodeApi, wallet.chainQueryApi)(
uniqueEntropyWalletConfig uniqueEntropyWalletConfig
) )
} }

View File

@ -34,10 +34,10 @@ import scala.concurrent.{ExecutionContext, Future}
import scala.util.control.NonFatal import scala.util.control.NonFatal
abstract class Wallet extends NeutrinoHDWalletApi with WalletLogger { abstract class Wallet extends NeutrinoHDWalletApi with WalletLogger {
def keyManager: BIP39KeyManager = { def keyManager: BIP39KeyManager = {
walletConfig.kmConf.toBip39KeyManager walletConfig.kmConf.toBip39KeyManager
} }
def feeRateApi: FeeRateApi = walletConfig.feeRateApi
implicit val walletConfig: WalletAppConfig implicit val walletConfig: WalletAppConfig
implicit val system: ActorSystem = walletConfig.system implicit val system: ActorSystem = walletConfig.system
@ -137,44 +137,6 @@ abstract class Wallet extends NeutrinoHDWalletApi with WalletLogger {
def walletCallbacks: WalletCallbacks = walletConfig.callBacks def walletCallbacks: WalletCallbacks = walletConfig.callBacks
private def checkRootAccount: Future[Unit] = {
val coinType = HDUtil.getCoinType(keyManager.kmParams.network)
val coin =
HDCoin(purpose = keyManager.kmParams.purpose, coinType = coinType)
val account = HDAccount(coin = coin, index = 0)
// safe since we're deriving from a priv
val xpub = keyManager.deriveXPub(account).get
accountDAO.read((account.coin, account.index)).flatMap {
case Some(account) =>
if (account.xpub != xpub) {
val errorMsg =
s"Divergent xpubs for account=$account. Existing database xpub=${account.xpub}, key manager's xpub=$xpub. " +
s"It is possible we have a different key manager being used than expected, key manager=${keyManager.kmParams.seedPath.toAbsolutePath.toString}"
Future.failed(new RuntimeException(errorMsg))
} else {
Future.unit
}
case None =>
val errorMsg = s"Missing root xpub for account $account in database"
Future.failed(new RuntimeException(errorMsg))
}
}
override def start(): Future[Wallet] = {
logger.info("Starting Wallet")
checkRootAccount.map { _ =>
walletConfig.startRebroadcastTxsScheduler(this)
startFeeRateCallbackScheduler()
this
}
}
override def stop(): Future[Wallet] = {
Future.successful(this)
}
override def getNewAddress(): Future[BitcoinAddress] = { override def getNewAddress(): Future[BitcoinAddress] = {
addressHandling.getNewAddress() addressHandling.getNewAddress()
} }
@ -353,18 +315,16 @@ object Wallet extends WalletLogger {
private case class WalletImpl( private case class WalletImpl(
nodeApi: NodeApi, nodeApi: NodeApi,
chainQueryApi: ChainQueryApi, chainQueryApi: ChainQueryApi
feeRateApi: FeeRateApi
)(implicit )(implicit
val walletConfig: WalletAppConfig val walletConfig: WalletAppConfig
) extends Wallet ) extends Wallet
def apply( def apply(
nodeApi: NodeApi, nodeApi: NodeApi,
chainQueryApi: ChainQueryApi, chainQueryApi: ChainQueryApi
feeRateApi: FeeRateApi
)(implicit config: WalletAppConfig): Wallet = { )(implicit config: WalletAppConfig): Wallet = {
WalletImpl(nodeApi, chainQueryApi, feeRateApi) WalletImpl(nodeApi, chainQueryApi)
} }
/** Creates the master xpub for the key manager in the database /** Creates the master xpub for the key manager in the database

View File

@ -70,26 +70,8 @@ class WalletHolder(initWalletOpt: Option[DLCNeutrinoHDWalletApi])(implicit
newWallet: DLCNeutrinoHDWalletApi newWallet: DLCNeutrinoHDWalletApi
): Future[DLCNeutrinoHDWalletApi] = ): Future[DLCNeutrinoHDWalletApi] =
synchronized { synchronized {
val oldWalletOpt = walletOpt walletOpt = Some(newWallet)
walletOpt = None Future.successful(newWallet)
val res = for {
_ <- {
oldWalletOpt match {
case Some(oldWallet) => oldWallet.stop()
case None => Future.unit
}
}
_ <- newWallet.start()
} yield {
synchronized {
walletOpt = Some(newWallet)
newWallet
}
}
res.failed.foreach(ex => logger.error("Cannot start wallet ", ex))
res
} }
private def delegate[T] private def delegate[T]
@ -116,19 +98,6 @@ class WalletHolder(initWalletOpt: Option[DLCNeutrinoHDWalletApi])(implicit
override lazy val feeRateApi: FeeRateApi = wallet.feeRateApi override lazy val feeRateApi: FeeRateApi = wallet.feeRateApi
override lazy val creationTime: Instant = wallet.creationTime override lazy val creationTime: Instant = wallet.creationTime
override def start(): Future[WalletApi] = delegate(_.start())
override def stop(): Future[WalletApi] = {
val res = delegate(_.stop())
res.onComplete { _ =>
synchronized {
walletOpt = None
}
}
res
}
override def getConfirmedBalance(): Future[CurrencyUnit] = delegate( override def getConfirmedBalance(): Future[CurrencyUnit] = delegate(
_.getConfirmedBalance() _.getConfirmedBalance()
) )

View File

@ -8,14 +8,16 @@ import org.bitcoins.core.api.CallbackConfig
import org.bitcoins.core.api.chain.ChainQueryApi import org.bitcoins.core.api.chain.ChainQueryApi
import org.bitcoins.core.api.feeprovider.FeeRateApi import org.bitcoins.core.api.feeprovider.FeeRateApi
import org.bitcoins.core.api.node.NodeApi import org.bitcoins.core.api.node.NodeApi
import org.bitcoins.core.hd._ import org.bitcoins.core.hd.*
import org.bitcoins.core.wallet.fee.SatoshisPerVirtualByte import org.bitcoins.core.wallet.fee.SatoshisPerVirtualByte
import org.bitcoins.core.wallet.keymanagement._ import org.bitcoins.core.wallet.keymanagement.*
import org.bitcoins.crypto.AesPassword import org.bitcoins.crypto.AesPassword
import org.bitcoins.db.DatabaseDriver.{PostgreSQL, SQLite} import org.bitcoins.db.DatabaseDriver.{PostgreSQL, SQLite}
import org.bitcoins.db._ import org.bitcoins.db.*
import org.bitcoins.db.models.MasterXPubDAO import org.bitcoins.db.models.MasterXPubDAO
import org.bitcoins.db.util.{DBMasterXPubApi, MasterXPubUtil} import org.bitcoins.db.util.{DBMasterXPubApi, MasterXPubUtil}
import org.bitcoins.feeprovider.{FeeProviderFactory, MempoolSpaceProvider}
import org.bitcoins.feeprovider.MempoolSpaceTarget.HourFeeTarget
import org.bitcoins.keymanager.config.KeyManagerAppConfig import org.bitcoins.keymanager.config.KeyManagerAppConfig
import org.bitcoins.tor.config.TorAppConfig import org.bitcoins.tor.config.TorAppConfig
import org.bitcoins.wallet.callback.{ import org.bitcoins.wallet.callback.{
@ -29,7 +31,7 @@ import org.bitcoins.wallet.{Wallet, WalletLogger}
import java.nio.file.{Files, Path, Paths} import java.nio.file.{Files, Path, Paths}
import java.time.Instant import java.time.Instant
import java.util.concurrent._ import java.util.concurrent.*
import scala.concurrent.duration.{ import scala.concurrent.duration.{
Duration, Duration,
DurationInt, DurationInt,
@ -37,6 +39,7 @@ import scala.concurrent.duration.{
FiniteDuration FiniteDuration
} }
import scala.concurrent.{Await, ExecutionContext, Future} import scala.concurrent.{Await, ExecutionContext, Future}
import scala.util.control.NonFatal
/** Configuration for the Bitcoin-S wallet /** Configuration for the Bitcoin-S wallet
* @param directory * @param directory
@ -57,6 +60,19 @@ case class WalletAppConfig(
implicit override val ec: ExecutionContext = system.dispatcher implicit override val ec: ExecutionContext = system.dispatcher
private val defaultApi =
MempoolSpaceProvider(HourFeeTarget, network, torConf.socks5ProxyParams)
lazy val feeRateApi: FeeRateApi = {
FeeProviderFactory.getFeeProviderOrElse(
defaultApi,
feeProviderNameOpt,
feeProviderTargetOpt,
torConf.socks5ProxyParams,
network
)
}
override protected[bitcoins] def moduleName: String = override protected[bitcoins] def moduleName: String =
WalletAppConfig.moduleName WalletAppConfig.moduleName
@ -65,7 +81,7 @@ case class WalletAppConfig(
override protected[bitcoins] def newConfigOfType( override protected[bitcoins] def newConfigOfType(
configs: Vector[Config] configs: Vector[Config]
): WalletAppConfig = ): WalletAppConfig =
WalletAppConfig(baseDatadir, configs) WalletAppConfig(baseDatadir, configs, kmConfOpt)
override def appConfig: WalletAppConfig = this override def appConfig: WalletAppConfig = this
@ -216,6 +232,8 @@ case class WalletAppConfig(
Files.createDirectories(datadir) Files.createDirectories(datadir)
} }
startFeeRateCallbackScheduler()
for { for {
_ <- super.start() _ <- super.start()
_ <- kmConf.start() _ <- kmConf.start()
@ -257,6 +275,7 @@ case class WalletAppConfig(
stopCallbacksF.flatMap { _ => stopCallbacksF.flatMap { _ =>
clearCallbacks() clearCallbacks()
stopRebroadcastTxsScheduler() stopRebroadcastTxsScheduler()
stopFeeRateScheduler()
// this eagerly shuts down all scheduled tasks on the scheduler // this eagerly shuts down all scheduled tasks on the scheduler
// in the future, we should actually cancel all things that are scheduled // in the future, we should actually cancel all things that are scheduled
// manually, and then shutdown the scheduler // manually, and then shutdown the scheduler
@ -325,41 +344,41 @@ case class WalletAppConfig(
/** Creates a wallet based on this [[WalletAppConfig]] */ /** Creates a wallet based on this [[WalletAppConfig]] */
def createHDWallet( def createHDWallet(
nodeApi: NodeApi, nodeApi: NodeApi,
chainQueryApi: ChainQueryApi, chainQueryApi: ChainQueryApi
feeRateApi: FeeRateApi
)(implicit system: ActorSystem): Future[Wallet] = { )(implicit system: ActorSystem): Future[Wallet] = {
WalletAppConfig.createHDWallet( WalletAppConfig.createHDWallet(
nodeApi = nodeApi, nodeApi = nodeApi,
chainQueryApi = chainQueryApi, chainQueryApi = chainQueryApi
feeRateApi = feeRateApi
)(this, system) )(this, system)
} }
private[this] var rebroadcastTransactionsCancelOpt private[this] var rebroadcastTransactionsCancelOpt
: Option[ScheduledFuture[_]] = None : Option[ScheduledFuture[?]] = None
private var feeRateCancelOpt: Option[ScheduledFuture[?]] = None
/** Starts the wallet's rebroadcast transaction scheduler */ /** Starts the wallet's rebroadcast transaction scheduler */
def startRebroadcastTxsScheduler(wallet: Wallet): Unit = synchronized { private def startRebroadcastTxsScheduler(wallet: Wallet): Unit =
rebroadcastTransactionsCancelOpt match { synchronized {
case Some(_) => rebroadcastTransactionsCancelOpt match {
// already scheduled, do nothing case Some(_) =>
() // already scheduled, do nothing
case None => ()
logger.info(s"Starting wallet rebroadcast task") case None =>
logger.info(s"Starting wallet rebroadcast task")
val interval = rebroadcastFrequency.toSeconds val interval = rebroadcastFrequency.toSeconds
val initDelay = interval val initDelay = interval
val future = val future =
scheduler.scheduleAtFixedRate( scheduler.scheduleAtFixedRate(
RebroadcastTransactionsRunnable(wallet), RebroadcastTransactionsRunnable(wallet),
initDelay, initDelay,
interval, interval,
TimeUnit.SECONDS TimeUnit.SECONDS
) )
rebroadcastTransactionsCancelOpt = Some(future) rebroadcastTransactionsCancelOpt = Some(future)
() ()
}
} }
}
/** Kills the wallet's rebroadcast transaction scheduler */ /** Kills the wallet's rebroadcast transaction scheduler */
def stopRebroadcastTxsScheduler(): Unit = synchronized { def stopRebroadcastTxsScheduler(): Unit = synchronized {
@ -368,20 +387,60 @@ case class WalletAppConfig(
if (!cancel.isCancelled) { if (!cancel.isCancelled) {
logger.info(s"Stopping wallet rebroadcast task") logger.info(s"Stopping wallet rebroadcast task")
cancel.cancel(true) cancel.cancel(true)
} else {
rebroadcastTransactionsCancelOpt = None
} }
rebroadcastTransactionsCancelOpt = None
() ()
case None => () case None => ()
} }
} }
private def stopFeeRateScheduler(): Unit = synchronized {
feeRateCancelOpt match {
case Some(cancel) =>
if (!cancel.isCancelled) {
cancel.cancel(true)
}
feeRateCancelOpt = None
case None =>
()
}
}
/** The creation time of the mnemonic seed If we cannot decrypt the seed /** The creation time of the mnemonic seed If we cannot decrypt the seed
* because of invalid passwords, we return None * because of invalid passwords, we return None
*/ */
def creationTime: Instant = { def creationTime: Instant = {
kmConf.creationTime kmConf.creationTime
} }
private def startFeeRateCallbackScheduler(): Unit = {
val feeRateChangedRunnable = new Runnable {
override def run(): Unit = {
feeRateApi
.getFeeRate()
.map(feeRate => Some(feeRate))
.recover { case NonFatal(_) =>
// logger.error("Cannot get fee rate ", ex)
None
}
.foreach { feeRateOpt =>
callBacks.executeOnFeeRateChanged(
feeRateOpt.getOrElse(SatoshisPerVirtualByte.negativeOne)
)
}
()
}
}
val cancel: ScheduledFuture[?] = scheduler.scheduleAtFixedRate(
feeRateChangedRunnable,
feeRatePollDelay.toSeconds,
feeRatePollInterval.toSeconds,
TimeUnit.SECONDS
)
feeRateCancelOpt = Some(cancel)
}
} }
object WalletAppConfig object WalletAppConfig
@ -404,25 +463,24 @@ object WalletAppConfig
/** Creates a wallet based on the given [[WalletAppConfig]] */ /** Creates a wallet based on the given [[WalletAppConfig]] */
def createHDWallet( def createHDWallet(
nodeApi: NodeApi, nodeApi: NodeApi,
chainQueryApi: ChainQueryApi, chainQueryApi: ChainQueryApi
feeRateApi: FeeRateApi
)(implicit )(implicit
walletConf: WalletAppConfig, walletConf: WalletAppConfig,
system: ActorSystem system: ActorSystem
): Future[Wallet] = { ): Future[Wallet] = {
import system.dispatcher import system.dispatcher
walletConf.hasWallet().flatMap { walletExists => val walletF = walletConf.hasWallet().flatMap { walletExists =>
val bip39PasswordOpt = walletConf.bip39PasswordOpt val bip39PasswordOpt = walletConf.bip39PasswordOpt
if (walletExists) { if (walletExists) {
logger.info(s"Using pre-existing wallet") logger.info(s"Using pre-existing wallet")
val wallet = val wallet =
Wallet(nodeApi, chainQueryApi, feeRateApi) Wallet(nodeApi, chainQueryApi)
Future.successful(wallet) Future.successful(wallet)
} else { } else {
logger.info(s"Creating new wallet") logger.info(s"Creating new wallet")
val unInitializedWallet = val unInitializedWallet =
Wallet(nodeApi, chainQueryApi, feeRateApi) Wallet(nodeApi, chainQueryApi)
Wallet.initialize( Wallet.initialize(
wallet = unInitializedWallet, wallet = unInitializedWallet,
@ -431,6 +489,11 @@ object WalletAppConfig
) )
} }
} }
walletF.map { wallet =>
walletConf.startRebroadcastTxsScheduler(wallet)
wallet
}
} }
case class RebroadcastTransactionsRunnable(wallet: Wallet)(implicit case class RebroadcastTransactionsRunnable(wallet: Wallet)(implicit

View File

@ -18,6 +18,7 @@ import org.bitcoins.core.protocol.blockchain.{
TestNetChainParams TestNetChainParams
} }
import org.bitcoins.core.protocol.script.ScriptPubKey import org.bitcoins.core.protocol.script.ScriptPubKey
import org.bitcoins.core.util.HDUtil
import org.bitcoins.db.SafeDatabase import org.bitcoins.db.SafeDatabase
import org.bitcoins.wallet.callback.WalletCallbacks import org.bitcoins.wallet.callback.WalletCallbacks
import org.bitcoins.wallet.config.WalletAppConfig import org.bitcoins.wallet.config.WalletAppConfig
@ -304,7 +305,7 @@ case class AccountHandling(
override def getNewAddress(account: AccountDb): Future[BitcoinAddress] = { override def getNewAddress(account: AccountDb): Future[BitcoinAddress] = {
val action = getNewAddressAction(account) val action = getNewAddressAction(account)
safeDatabase.run(action) checkRootAccount.flatMap(_ => safeDatabase.run(action))
} }
def getNewAddressAction(account: AccountDb): DBIOAction[ def getNewAddressAction(account: AccountDb): DBIOAction[
@ -477,7 +478,7 @@ case class AccountHandling(
} }
} }
protected def getLastAccountOpt( private def getLastAccountOpt(
purpose: HDPurpose purpose: HDPurpose
): Future[Option[AccountDb]] = { ): Future[Option[AccountDb]] = {
accountDAO accountDAO
@ -490,6 +491,30 @@ case class AccountHandling(
.map(_.lastOption) .map(_.lastOption)
} }
private def checkRootAccount: Future[Unit] = {
val coinType = HDUtil.getCoinType(keyManager.kmParams.network)
val coin =
HDCoin(purpose = keyManager.kmParams.purpose, coinType = coinType)
val account = HDAccount(coin = coin, index = 0)
// safe since we're deriving from a priv
val xpub = keyManager.deriveXPub(account).get
accountDAO.read((account.coin, account.index)).flatMap {
case Some(account) =>
if (account.xpub != xpub) {
val errorMsg =
s"Divergent xpubs for account=$account. Existing database xpub=${account.xpub}, key manager's xpub=$xpub. " +
s"It is possible we have a different key manager being used than expected, key manager=${keyManager.kmParams.seedPath.toAbsolutePath.toString}"
Future.failed(new RuntimeException(errorMsg))
} else {
Future.unit
}
case None =>
val errorMsg = s"Missing root xpub for account $account in database"
Future.failed(new RuntimeException(errorMsg))
}
}
/** The default HD coin for this wallet, read from config */ /** The default HD coin for this wallet, read from config */
protected[wallet] lazy val DEFAULT_HD_COIN: HDCoin = { protected[wallet] lazy val DEFAULT_HD_COIN: HDCoin = {
val coinType = DEFAULT_HD_COIN_TYPE val coinType = DEFAULT_HD_COIN_TYPE