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(
walletHolder,
mockChainApi,
mockNode,
feeRateApi
mockNode
)
}

View File

@ -51,8 +51,7 @@ class WalletRoutesSpec
DLCWalletNeutrinoBackendLoader(
walletHolder,
mockChainApi,
mockNode,
feeRateApi
mockNode
)
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.config.DLCNodeAppConfig
import org.bitcoins.dlc.wallet.*
import org.bitcoins.feeprovider.MempoolSpaceTarget.HourFeeTarget
import org.bitcoins.feeprovider.*
import org.bitcoins.node.Node
import org.bitcoins.node.config.NodeAppConfig
@ -185,15 +184,6 @@ class BitcoinSServerMain(override val serverArgParser: ServerArgParser)(implicit
walletCreationTimeOpt = Some(creationTime)
)(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
val walletHolder = WalletHolder.empty
val neutrinoWalletLoaderF = {
@ -203,8 +193,7 @@ class BitcoinSServerMain(override val serverArgParser: ServerArgParser)(implicit
val l = DLCWalletNeutrinoBackendLoader(
walletHolder,
chainApi,
nodeApi = node,
feeRateApi = feeProvider
nodeApi = node
)
walletLoaderApiOpt = Some(l)
l

View File

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

View File

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

View File

@ -775,7 +775,7 @@ lazy val wallet = project
name := "bitcoin-s-wallet",
libraryDependencies ++= Deps.wallet(scalaVersion.value)
)
.dependsOn(coreJVM, appCommons, dbCommons, keyManager, asyncUtilsJVM, tor)
.dependsOn(coreJVM, appCommons, dbCommons, keyManager, asyncUtilsJVM, feeProvider, tor)
lazy val walletTest = project
.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.protocol.BitcoinAddress
import org.bitcoins.core.protocol.transaction.Transaction
import org.bitcoins.core.util.StartStopAsync
import org.bitcoins.core.wallet.fee.FeeUnit
import org.bitcoins.crypto.DoubleSha256DigestBE
@ -22,7 +21,7 @@ import scala.concurrent.{ExecutionContext, Future}
* @see
* [[https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki BIP44]]
*/
trait WalletApi extends StartStopAsync[WalletApi] {
trait WalletApi {
def accountHandling: AccountHandlingApi
def fundTxHandling: FundTransactionHandlingApi
def rescanHandling: RescanHandlingApi
@ -33,7 +32,7 @@ trait WalletApi extends StartStopAsync[WalletApi] {
val nodeApi: NodeApi
val chainQueryApi: ChainQueryApi
val feeRateApi: FeeRateApi
def feeRateApi: FeeRateApi
val creationTime: Instant
def broadcastTransaction(transaction: Transaction): Future[Unit] =
@ -41,10 +40,6 @@ trait WalletApi extends StartStopAsync[WalletApi] {
def getFeeRate(): Future[FeeUnit] = feeRateApi.getFeeRate()
def start(): Future[WalletApi]
def stop(): Future[WalletApi]
/** Gets the sum of all UTXOs in this wallet */
def getBalance()(implicit ec: ExecutionContext): Future[CurrencyUnit] = {
val confirmedF = getConfirmedBalance()

View File

@ -6,8 +6,9 @@ import org.bitcoins.core.protocol.dlc.models.{
DisjointUnionContractInfo,
SingleContractInfo
}
import org.bitcoins.core.wallet.rescan.RescanState
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.scalatest.FutureOutcome
@ -55,7 +56,7 @@ class RescanDLCTest extends DualWalletTestCachedBitcoind {
Vector(hash) <- bitcoind.generate(1)
_ <- wallet.rescanHandling.rescanNeutrinoWallet(
rescanState <- wallet.rescanHandling.rescanNeutrinoWallet(
startOpt = None,
endOpt = Some(BlockHash(hash)),
addressBatchSize = 20,
@ -64,6 +65,7 @@ class RescanDLCTest extends DualWalletTestCachedBitcoind {
)
postStatus <- getDLCStatus(wallet)
_ <- RescanState.awaitRescanDone(rescanState)
} yield assert(postStatus.state == DLCState.Claimed)
}
@ -100,7 +102,7 @@ class RescanDLCTest extends DualWalletTestCachedBitcoind {
Vector(hash) <- bitcoind.generate(1)
_ <- wallet.rescanHandling.rescanNeutrinoWallet(
rescanState <- wallet.rescanHandling.rescanNeutrinoWallet(
startOpt = None,
endOpt = Some(BlockHash(hash)),
addressBatchSize = 20,
@ -109,6 +111,7 @@ class RescanDLCTest extends DualWalletTestCachedBitcoind {
)
postStatus <- getDLCStatus(wallet)
_ <- RescanState.awaitRescanDone(rescanState)
} 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.chain.ChainQueryApi
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.protocol.dlc.models.DLCState.{
AdaptorSigComputationState,
@ -144,13 +143,11 @@ case class DLCAppConfig(
def createDLCWallet(
nodeApi: NodeApi,
chainQueryApi: ChainQueryApi,
feeRateApi: FeeRateApi
chainQueryApi: ChainQueryApi
)(implicit walletConf: WalletAppConfig): Future[DLCWallet] = {
DLCAppConfig.createDLCWallet(
nodeApi = nodeApi,
chainQueryApi = chainQueryApi,
feeRateApi = feeRateApi
chainQueryApi = chainQueryApi
)(walletConf, this)
}
@ -327,8 +324,7 @@ object DLCAppConfig
/** Creates a wallet based on the given [[WalletAppConfig]] */
def createDLCWallet(
nodeApi: NodeApi,
chainQueryApi: ChainQueryApi,
feeRateApi: FeeRateApi
chainQueryApi: ChainQueryApi
)(implicit
walletConf: WalletAppConfig,
dlcConf: DLCAppConfig
@ -339,12 +335,12 @@ object DLCAppConfig
if (walletExists) {
logger.info(s"Using pre-existing wallet")
val wallet =
DLCWallet(nodeApi, chainQueryApi, feeRateApi)
DLCWallet(nodeApi, chainQueryApi)
Future.successful(wallet)
} else {
logger.info(s"Creating new wallet")
val unInitializedWallet =
DLCWallet(nodeApi, chainQueryApi, feeRateApi)
DLCWallet(nodeApi, chainQueryApi)
Wallet
.initialize(

View File

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

View File

@ -188,7 +188,7 @@ val chainApi = new ChainQueryApi {
// Finally, we can initialize our wallet with our own node api
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
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
val wallet =
Wallet(nodeApi = nodeApi, chainQueryApi = chainApi, feeRateApi = ConstantFeeRateProvider(SatoshisPerVirtualByte.one))
Wallet(nodeApi = nodeApi, chainQueryApi = chainApi)
// Then to trigger the event we can run
val exampleBlock = DoubleSha256DigestBE(

View File

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

View File

@ -115,8 +115,7 @@ val genesisHashBEF = bitcoind.getBlockHash(0)
//a fresh wallet
implicit val walletAppConfig: WalletAppConfig = WalletAppConfig.fromDefaultDatadir()
val feeRateProvider: FeeRateApi = MempoolSpaceProvider.fromBlockTarget(6, proxyParams = None)
val wallet = Wallet(bitcoind, bitcoind, feeRateProvider)
val wallet = Wallet(bitcoind, bitcoind)
//yay! we have a synced wallet
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 downloadBlocks(blockHashes: Vector[DoubleSha256DigestBE]): Future[Unit] = Future.successful(())
override def getConnectionCount: Future[Int] = Future.successful(0)
}, chainApi, ConstantFeeRateProvider(SatoshisPerVirtualByte.one))
}, chainApi)
val walletF: Future[WalletApi] = configF.flatMap { _ =>
Wallet.initialize(wallet, wallet.accountHandling, None)
}

View File

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

View File

@ -14,7 +14,7 @@ class RandomFeeProvider extends FeeRateApi {
var lastFeeRate: Option[FeeUnit] = None
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))
lastFeeRate = Some(feeRate)
Future.successful(feeRate)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -34,10 +34,10 @@ import scala.concurrent.{ExecutionContext, Future}
import scala.util.control.NonFatal
abstract class Wallet extends NeutrinoHDWalletApi with WalletLogger {
def keyManager: BIP39KeyManager = {
walletConfig.kmConf.toBip39KeyManager
}
def feeRateApi: FeeRateApi = walletConfig.feeRateApi
implicit val walletConfig: WalletAppConfig
implicit val system: ActorSystem = walletConfig.system
@ -137,44 +137,6 @@ abstract class Wallet extends NeutrinoHDWalletApi with WalletLogger {
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] = {
addressHandling.getNewAddress()
}
@ -353,18 +315,16 @@ object Wallet extends WalletLogger {
private case class WalletImpl(
nodeApi: NodeApi,
chainQueryApi: ChainQueryApi,
feeRateApi: FeeRateApi
chainQueryApi: ChainQueryApi
)(implicit
val walletConfig: WalletAppConfig
) extends Wallet
def apply(
nodeApi: NodeApi,
chainQueryApi: ChainQueryApi,
feeRateApi: FeeRateApi
chainQueryApi: ChainQueryApi
)(implicit config: WalletAppConfig): Wallet = {
WalletImpl(nodeApi, chainQueryApi, feeRateApi)
WalletImpl(nodeApi, chainQueryApi)
}
/** Creates the master xpub for the key manager in the database

View File

@ -69,27 +69,9 @@ class WalletHolder(initWalletOpt: Option[DLCNeutrinoHDWalletApi])(implicit
def replaceWallet(
newWallet: DLCNeutrinoHDWalletApi
): Future[DLCNeutrinoHDWalletApi] =
synchronized {
val oldWalletOpt = walletOpt
walletOpt = None
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
Future.successful(newWallet)
}
private def delegate[T]
@ -116,19 +98,6 @@ class WalletHolder(initWalletOpt: Option[DLCNeutrinoHDWalletApi])(implicit
override lazy val feeRateApi: FeeRateApi = wallet.feeRateApi
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(
_.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.feeprovider.FeeRateApi
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.keymanagement._
import org.bitcoins.core.wallet.keymanagement.*
import org.bitcoins.crypto.AesPassword
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.util.{DBMasterXPubApi, MasterXPubUtil}
import org.bitcoins.feeprovider.{FeeProviderFactory, MempoolSpaceProvider}
import org.bitcoins.feeprovider.MempoolSpaceTarget.HourFeeTarget
import org.bitcoins.keymanager.config.KeyManagerAppConfig
import org.bitcoins.tor.config.TorAppConfig
import org.bitcoins.wallet.callback.{
@ -29,7 +31,7 @@ import org.bitcoins.wallet.{Wallet, WalletLogger}
import java.nio.file.{Files, Path, Paths}
import java.time.Instant
import java.util.concurrent._
import java.util.concurrent.*
import scala.concurrent.duration.{
Duration,
DurationInt,
@ -37,6 +39,7 @@ import scala.concurrent.duration.{
FiniteDuration
}
import scala.concurrent.{Await, ExecutionContext, Future}
import scala.util.control.NonFatal
/** Configuration for the Bitcoin-S wallet
* @param directory
@ -57,6 +60,19 @@ case class WalletAppConfig(
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 =
WalletAppConfig.moduleName
@ -65,7 +81,7 @@ case class WalletAppConfig(
override protected[bitcoins] def newConfigOfType(
configs: Vector[Config]
): WalletAppConfig =
WalletAppConfig(baseDatadir, configs)
WalletAppConfig(baseDatadir, configs, kmConfOpt)
override def appConfig: WalletAppConfig = this
@ -216,6 +232,8 @@ case class WalletAppConfig(
Files.createDirectories(datadir)
}
startFeeRateCallbackScheduler()
for {
_ <- super.start()
_ <- kmConf.start()
@ -257,6 +275,7 @@ case class WalletAppConfig(
stopCallbacksF.flatMap { _ =>
clearCallbacks()
stopRebroadcastTxsScheduler()
stopFeeRateScheduler()
// this eagerly shuts down all scheduled tasks on the scheduler
// in the future, we should actually cancel all things that are scheduled
// manually, and then shutdown the scheduler
@ -325,21 +344,21 @@ case class WalletAppConfig(
/** Creates a wallet based on this [[WalletAppConfig]] */
def createHDWallet(
nodeApi: NodeApi,
chainQueryApi: ChainQueryApi,
feeRateApi: FeeRateApi
chainQueryApi: ChainQueryApi
)(implicit system: ActorSystem): Future[Wallet] = {
WalletAppConfig.createHDWallet(
nodeApi = nodeApi,
chainQueryApi = chainQueryApi,
feeRateApi = feeRateApi
chainQueryApi = chainQueryApi
)(this, system)
}
private[this] var rebroadcastTransactionsCancelOpt
: Option[ScheduledFuture[_]] = None
: Option[ScheduledFuture[?]] = None
private var feeRateCancelOpt: Option[ScheduledFuture[?]] = None
/** Starts the wallet's rebroadcast transaction scheduler */
def startRebroadcastTxsScheduler(wallet: Wallet): Unit = synchronized {
private def startRebroadcastTxsScheduler(wallet: Wallet): Unit =
synchronized {
rebroadcastTransactionsCancelOpt match {
case Some(_) =>
// already scheduled, do nothing
@ -368,20 +387,60 @@ case class WalletAppConfig(
if (!cancel.isCancelled) {
logger.info(s"Stopping wallet rebroadcast task")
cancel.cancel(true)
} else {
rebroadcastTransactionsCancelOpt = None
}
rebroadcastTransactionsCancelOpt = 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
* because of invalid passwords, we return None
*/
def creationTime: Instant = {
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
@ -404,25 +463,24 @@ object WalletAppConfig
/** Creates a wallet based on the given [[WalletAppConfig]] */
def createHDWallet(
nodeApi: NodeApi,
chainQueryApi: ChainQueryApi,
feeRateApi: FeeRateApi
chainQueryApi: ChainQueryApi
)(implicit
walletConf: WalletAppConfig,
system: ActorSystem
): Future[Wallet] = {
import system.dispatcher
walletConf.hasWallet().flatMap { walletExists =>
val walletF = walletConf.hasWallet().flatMap { walletExists =>
val bip39PasswordOpt = walletConf.bip39PasswordOpt
if (walletExists) {
logger.info(s"Using pre-existing wallet")
val wallet =
Wallet(nodeApi, chainQueryApi, feeRateApi)
Wallet(nodeApi, chainQueryApi)
Future.successful(wallet)
} else {
logger.info(s"Creating new wallet")
val unInitializedWallet =
Wallet(nodeApi, chainQueryApi, feeRateApi)
Wallet(nodeApi, chainQueryApi)
Wallet.initialize(
wallet = unInitializedWallet,
@ -431,6 +489,11 @@ object WalletAppConfig
)
}
}
walletF.map { wallet =>
walletConf.startRebroadcastTxsScheduler(wallet)
wallet
}
}
case class RebroadcastTransactionsRunnable(wallet: Wallet)(implicit

View File

@ -18,6 +18,7 @@ import org.bitcoins.core.protocol.blockchain.{
TestNetChainParams
}
import org.bitcoins.core.protocol.script.ScriptPubKey
import org.bitcoins.core.util.HDUtil
import org.bitcoins.db.SafeDatabase
import org.bitcoins.wallet.callback.WalletCallbacks
import org.bitcoins.wallet.config.WalletAppConfig
@ -304,7 +305,7 @@ case class AccountHandling(
override def getNewAddress(account: AccountDb): Future[BitcoinAddress] = {
val action = getNewAddressAction(account)
safeDatabase.run(action)
checkRootAccount.flatMap(_ => safeDatabase.run(action))
}
def getNewAddressAction(account: AccountDb): DBIOAction[
@ -477,7 +478,7 @@ case class AccountHandling(
}
}
protected def getLastAccountOpt(
private def getLastAccountOpt(
purpose: HDPurpose
): Future[Option[AccountDb]] = {
accountDAO
@ -490,6 +491,30 @@ case class AccountHandling(
.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 */
protected[wallet] lazy val DEFAULT_HD_COIN: HDCoin = {
val coinType = DEFAULT_HD_COIN_TYPE