2024 09 19 address handling refactor (#5679)

* wallet: Create has-a relationship for AddressHandling rather than is-a

* Fix infinite loop in AccountHandlingApi.getNewAddress(account)

* Fix appServerTest

* Revert Server.scala
This commit is contained in:
Chris Stewart 2024-09-21 11:41:57 -05:00 committed by GitHub
parent 8c5d685953
commit 8cfd5e8d6b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
31 changed files with 768 additions and 832 deletions

View File

@ -1,6 +1,6 @@
package org.bitcoins.server.routes
import org.apache.pekko.http.scaladsl.server.Directives.{complete, provide}
import org.apache.pekko.http.scaladsl.server.Directives
import org.apache.pekko.http.scaladsl.server.{Directive1, Route}
import scala.util.Try
@ -10,7 +10,7 @@ trait ServerRoute {
def withValidServerCommand[R](validator: Try[R]): Directive1[R] =
validator.fold(
e => complete(Server.httpBadRequest(e)),
provide
{ (e: Throwable) => Directives.complete(Server.httpBadRequest(e)) },
{ (r: R) => Directives.provide(r) }
)
}

View File

@ -24,7 +24,7 @@ class CallBackUtilTest extends BitcoinSWalletTest {
it must "have the kill switch kill messages to the createBitcoindNodeCallbacksForWallet callback" in {
fundedWallet =>
val wallet = fundedWallet.wallet
val addressF = wallet.getNewAddress()
val addressF = wallet.addressHandling.getNewAddress()
val initBalanceF = wallet.getBalance()
val tx1F = addressF.map { addr =>
TransactionGenerators
@ -65,7 +65,7 @@ class CallBackUtilTest extends BitcoinSWalletTest {
it must "have the kill switch kill messages to the createNeutrinoNodeCallbacksForWallet callback" in {
fundedWallet =>
val wallet = fundedWallet.wallet
val addressF = wallet.getNewAddress()
val addressF = wallet.addressHandling.getNewAddress()
val initBalanceF = wallet.getBalance()
val tx1F = addressF.map { addr =>
TransactionGenerators

View File

@ -11,6 +11,7 @@ import org.bitcoins.core.api.chain.db.*
import org.bitcoins.core.api.wallet.db.*
import org.bitcoins.core.api.wallet.{
AccountHandlingApi,
AddressHandlingApi,
AddressInfo,
CoinSelectionAlgo,
RescanHandlingApi
@ -86,6 +87,8 @@ class RoutesSpec extends AnyWordSpec with ScalatestRouteTest with MockFactory {
val mockAccountHandlingApi: AccountHandlingApi = mock[AccountHandlingApi]
val mockAddressHandlingApi: AddressHandlingApi = mock[AddressHandlingApi]
val walletHolder = new WalletHolder(Some(mockWalletApi))
val feeRateApi = ConstantFeeRateProvider(SatoshisPerVirtualByte.one)
@ -553,7 +556,12 @@ class RoutesSpec extends AnyWordSpec with ScalatestRouteTest with MockFactory {
testAddress.scriptPubKey
)
(() => mockWalletApi.listAddresses())
(() => mockWalletApi.addressHandling)
.expects()
.returning(mockAddressHandlingApi)
.anyNumberOfTimes()
(() => mockWalletApi.addressHandling.listAddresses())
.expects()
.returning(Future.successful(Vector(addressDb)))
@ -579,7 +587,12 @@ class RoutesSpec extends AnyWordSpec with ScalatestRouteTest with MockFactory {
testAddress.scriptPubKey
)
(() => mockWalletApi.listSpentAddresses())
(() => mockWalletApi.addressHandling)
.expects()
.returning(mockAddressHandlingApi)
.anyNumberOfTimes()
(() => mockWalletApi.addressHandling.listSpentAddresses())
.expects()
.returning(Future.successful(Vector(addressDb)))
@ -605,7 +618,12 @@ class RoutesSpec extends AnyWordSpec with ScalatestRouteTest with MockFactory {
testAddress.scriptPubKey
)
(() => mockWalletApi.listFundedAddresses())
(() => mockWalletApi.addressHandling)
.expects()
.returning(mockAddressHandlingApi)
.anyNumberOfTimes()
(() => mockWalletApi.addressHandling.listFundedAddresses())
.expects()
.returning(Future.successful(Vector((addressDb, Satoshis.zero))))
@ -630,7 +648,12 @@ class RoutesSpec extends AnyWordSpec with ScalatestRouteTest with MockFactory {
testAddress.scriptPubKey
)
(() => mockWalletApi.listUnusedAddresses())
(() => mockWalletApi.addressHandling)
.expects()
.returning(mockAddressHandlingApi)
.anyNumberOfTimes()
(() => mockWalletApi.addressHandling.listUnusedAddresses())
.expects()
.returning(Future.successful(Vector(addressDb)))
@ -682,7 +705,12 @@ class RoutesSpec extends AnyWordSpec with ScalatestRouteTest with MockFactory {
}
"return a new address" in {
(() => mockWalletApi.getNewAddress())
(() => mockWalletApi.addressHandling)
.expects()
.returning(mockAddressHandlingApi)
.anyNumberOfTimes()
(() => mockWalletApi.addressHandling.getNewAddress())
.expects()
.returning(Future.successful(testAddress))
@ -704,7 +732,12 @@ class RoutesSpec extends AnyWordSpec with ScalatestRouteTest with MockFactory {
val key = ECPublicKey.freshPublicKey
val hdPath = HDPath.fromString("m/84'/1'/0'/0/0")
(mockWalletApi
(() => mockWalletApi.addressHandling)
.expects()
.returning(mockAddressHandlingApi)
.anyNumberOfTimes()
(mockWalletApi.addressHandling
.getAddressInfo(_: BitcoinAddress))
.expects(testAddress)
.returning(Future.successful(Some(AddressInfo(key, RegTest, hdPath))))
@ -725,7 +758,13 @@ class RoutesSpec extends AnyWordSpec with ScalatestRouteTest with MockFactory {
}
"return a new address with a label" in {
(mockWalletApi
(() => mockWalletApi.addressHandling)
.expects()
.returning(mockAddressHandlingApi)
.anyNumberOfTimes()
(mockWalletApi.addressHandling
.getNewAddress(_: Vector[AddressTag]))
.expects(Vector(testLabel))
.returning(Future.successful(testAddress))
@ -816,7 +855,12 @@ class RoutesSpec extends AnyWordSpec with ScalatestRouteTest with MockFactory {
}
"label an address" in {
(mockWalletApi
(() => mockWalletApi.addressHandling)
.expects()
.returning(mockAddressHandlingApi)
.anyNumberOfTimes()
(mockWalletApi.addressHandling
.tagAddress(_: BitcoinAddress, _: AddressTag))
.expects(testAddress, testLabel)
.returning(Future.successful(AddressTagDb(testAddress, testLabel)))
@ -840,7 +884,12 @@ class RoutesSpec extends AnyWordSpec with ScalatestRouteTest with MockFactory {
}
"get address tags" in {
(mockWalletApi
(() => mockWalletApi.addressHandling)
.expects()
.returning(mockAddressHandlingApi)
.anyNumberOfTimes()
(mockWalletApi.addressHandling
.getAddressTags(_: BitcoinAddress))
.expects(testAddress)
.returning(
@ -863,7 +912,12 @@ class RoutesSpec extends AnyWordSpec with ScalatestRouteTest with MockFactory {
}
"get address label" in {
(mockWalletApi
(() => mockWalletApi.addressHandling)
.expects()
.returning(mockAddressHandlingApi)
.anyNumberOfTimes()
(mockWalletApi.addressHandling
.getAddressTags(_: BitcoinAddress, _: AddressTagType))
.expects(testAddress, AddressLabelTagType)
.returning(
@ -886,7 +940,12 @@ class RoutesSpec extends AnyWordSpec with ScalatestRouteTest with MockFactory {
}
"get address labels" in {
(() => mockWalletApi.getAddressTags())
(() => mockWalletApi.addressHandling)
.expects()
.returning(mockAddressHandlingApi)
.anyNumberOfTimes()
(() => mockWalletApi.addressHandling.getAddressTags())
.expects()
.returning(
Future.successful(Vector(AddressTagDb(testAddress, testLabel)))
@ -905,7 +964,13 @@ class RoutesSpec extends AnyWordSpec with ScalatestRouteTest with MockFactory {
"drop address label" in {
val labelName = "label"
(mockWalletApi
(() => mockWalletApi.addressHandling)
.expects()
.returning(mockAddressHandlingApi)
.anyNumberOfTimes()
(mockWalletApi.addressHandling
.dropAddressTagName(_: BitcoinAddress, _: AddressTagName))
.expects(testAddress, AddressLabelTagName(labelName))
.returning(Future.successful(1))
@ -929,7 +994,12 @@ class RoutesSpec extends AnyWordSpec with ScalatestRouteTest with MockFactory {
}
"drop address labels with no labels" in {
(mockWalletApi
(() => mockWalletApi.addressHandling)
.expects()
.returning(mockAddressHandlingApi)
.anyNumberOfTimes()
(mockWalletApi.addressHandling
.dropAddressTagType(_: BitcoinAddress, _: AddressTagType))
.expects(testAddress, AddressLabelTagType)
.returning(Future.successful(0))
@ -948,7 +1018,12 @@ class RoutesSpec extends AnyWordSpec with ScalatestRouteTest with MockFactory {
}
"drop address labels with 1 label" in {
(mockWalletApi
(() => mockWalletApi.addressHandling)
.expects()
.returning(mockAddressHandlingApi)
.anyNumberOfTimes()
(mockWalletApi.addressHandling
.dropAddressTagType(_: BitcoinAddress, _: AddressTagType))
.expects(testAddress, AddressLabelTagType)
.returning(Future.successful(1))
@ -969,7 +1044,12 @@ class RoutesSpec extends AnyWordSpec with ScalatestRouteTest with MockFactory {
}
"drop address labels with 2 labels" in {
(mockWalletApi
(() => mockWalletApi.addressHandling)
.expects()
.returning(mockAddressHandlingApi)
.anyNumberOfTimes()
(mockWalletApi.addressHandling
.dropAddressTagType(_: BitcoinAddress, _: AddressTagType))
.expects(testAddress, AddressLabelTagType)
.returning(Future.successful(2))

View File

@ -4,6 +4,7 @@ import org.apache.pekko.http.scaladsl.model.ContentTypes.*
import org.apache.pekko.http.scaladsl.testkit.ScalatestRouteTest
import org.bitcoins.commons.serializers.Picklers
import org.bitcoins.core.api.chain.ChainApi
import org.bitcoins.core.api.wallet.AccountHandlingApi
import org.bitcoins.core.api.wallet.db.AccountDb
import org.bitcoins.core.crypto.ExtKeyVersion.SegWitMainNetPriv
import org.bitcoins.core.crypto.ExtPrivateKey
@ -39,6 +40,7 @@ class WalletRoutesSpec
val mockNode: Node = mock[Node]
val mockWalletApi: MockWalletApi = mock[MockWalletApi]
val mockAccountHandlingApi: AccountHandlingApi = mock[AccountHandlingApi]
val walletHolder = new WalletHolder(Some(mockWalletApi))
@ -167,12 +169,18 @@ class WalletRoutesSpec
val extPubKey = extPrivKey.extPublicKey
val hdAccount = HDAccount.fromExtKeyVersion(version = keyVersion, idx = 0)
val accountDb = AccountDb(extPubKey, hdAccount = hdAccount)
(mockWalletApi
(() => mockWalletApi.accountHandling)
.expects()
.returning(mockAccountHandlingApi)
.anyNumberOfTimes()
(mockWalletApi.accountHandling
.createNewAccount(_: HDPurpose))
.expects(HDPurpose.default)
.returning(Future.successful(mockWalletApi))
.returning(Future.successful(extPubKey))
(() => mockWalletApi.listAccounts())
(() => mockWalletApi.accountHandling.listAccounts())
.expects()
.returning(Future.successful(Vector(accountDb)))

View File

@ -3,7 +3,6 @@ package org.bitcoins.wallet
import org.bitcoins.core.api.dlc.wallet.DLCNeutrinoHDWalletApi
import org.bitcoins.core.api.wallet.db.AccountDb
import org.bitcoins.core.hd.AddressType
import org.bitcoins.core.protocol.BitcoinAddress
import scala.concurrent.Future
@ -12,9 +11,6 @@ import scala.concurrent.Future
*/
abstract class MockWalletApi extends DLCNeutrinoHDWalletApi {
override def getNewChangeAddress(account: AccountDb): Future[BitcoinAddress] =
stub
override def getDefaultAccount(): Future[AccountDb] = stub
override def getDefaultAccountForType(

View File

@ -177,9 +177,9 @@ case class WalletRoutes(loadWalletApi: DLCWalletLoaderApi)(implicit
complete {
val addressF = labelOpt match {
case Some(label) =>
wallet.getNewAddress(Vector(label))
wallet.addressHandling.getNewAddress(Vector(label))
case None =>
wallet.getNewAddress()
wallet.addressHandling.getNewAddress()
}
addressF.map(address => Server.httpSuccess(address))
}
@ -231,7 +231,7 @@ case class WalletRoutes(loadWalletApi: DLCWalletLoaderApi)(implicit
withValidServerCommand(LabelAddress.fromJsArr(arr)) {
case LabelAddress(address, label) =>
complete {
wallet.tagAddress(address, label).map { tagDb =>
wallet.addressHandling.tagAddress(address, label).map { tagDb =>
Server.httpSuccess(
s"Added label \'${tagDb.tagName.name}\' to ${tagDb.address.value}"
)
@ -243,7 +243,7 @@ case class WalletRoutes(loadWalletApi: DLCWalletLoaderApi)(implicit
withValidServerCommand(GetAddressTags.fromJsArr(arr)) {
case GetAddressTags(address) =>
complete {
wallet.getAddressTags(address).map { tagDbs =>
wallet.addressHandling.getAddressTags(address).map { tagDbs =>
val retStr = tagDbs.map(_.tagName.name)
Server.httpSuccess(retStr)
}
@ -254,16 +254,18 @@ case class WalletRoutes(loadWalletApi: DLCWalletLoaderApi)(implicit
withValidServerCommand(GetAddressLabel.fromJsArr(arr)) {
case GetAddressLabel(address) =>
complete {
wallet.getAddressTags(address, AddressLabelTagType).map { tagDbs =>
val retStr = tagDbs.map(_.tagName.name)
Server.httpSuccess(retStr)
}
wallet.addressHandling
.getAddressTags(address, AddressLabelTagType)
.map { tagDbs =>
val retStr = tagDbs.map(_.tagName.name)
Server.httpSuccess(retStr)
}
}
}
case ServerCommand("getaddresslabels", _) =>
complete {
val allTagsF = wallet.getAddressTags()
val allTagsF = wallet.addressHandling.getAddressTags()
for {
allTags <- allTagsF
grouped = allTags.groupBy(_.address)
@ -284,7 +286,8 @@ case class WalletRoutes(loadWalletApi: DLCWalletLoaderApi)(implicit
case DropAddressLabel(address, label) =>
complete {
val tagName = AddressLabelTagName(label)
val droppedF = wallet.dropAddressTagName(address, tagName)
val droppedF =
wallet.addressHandling.dropAddressTagName(address, tagName)
droppedF.map(handleTagResponse)
}
}
@ -293,7 +296,8 @@ case class WalletRoutes(loadWalletApi: DLCWalletLoaderApi)(implicit
case DropAddressLabels(address) =>
complete {
val droppedF =
wallet.dropAddressTagType(address, AddressLabelTagType)
wallet.addressHandling.dropAddressTagType(address,
AddressLabelTagType)
droppedF.map(handleTagResponse)
}
}
@ -803,7 +807,7 @@ case class WalletRoutes(loadWalletApi: DLCWalletLoaderApi)(implicit
case ServerCommand("getaddresses", _) =>
complete {
wallet.listAddresses().map { addressDbs =>
wallet.addressHandling.listAddresses().map { addressDbs =>
val addresses = addressDbs.map(_.address)
Server.httpSuccess(addresses)
}
@ -811,7 +815,7 @@ case class WalletRoutes(loadWalletApi: DLCWalletLoaderApi)(implicit
case ServerCommand("getspentaddresses", _) =>
complete {
wallet.listSpentAddresses().map { addressDbs =>
wallet.addressHandling.listSpentAddresses().map { addressDbs =>
val addresses = addressDbs.map(_.address)
Server.httpSuccess(addresses)
}
@ -819,7 +823,7 @@ case class WalletRoutes(loadWalletApi: DLCWalletLoaderApi)(implicit
case ServerCommand("getfundedaddresses", _) =>
complete {
wallet.listFundedAddresses().map { addressDbs =>
wallet.addressHandling.listFundedAddresses().map { addressDbs =>
val addressAndValues = addressDbs.map { case (addressDb, value) =>
Obj(
"address" -> Str(addressDb.address.value),
@ -833,7 +837,7 @@ case class WalletRoutes(loadWalletApi: DLCWalletLoaderApi)(implicit
case ServerCommand("getunusedaddresses", _) =>
complete {
wallet.listUnusedAddresses().map { addressDbs =>
wallet.addressHandling.listUnusedAddresses().map { addressDbs =>
val addresses = addressDbs.map(_.address)
Server.httpSuccess(addresses)
}
@ -851,7 +855,7 @@ case class WalletRoutes(loadWalletApi: DLCWalletLoaderApi)(implicit
withValidServerCommand(GetAddressInfo.fromJsArr(arr)) {
case GetAddressInfo(address) =>
complete {
wallet.getAddressInfo(address).map {
wallet.addressHandling.getAddressInfo(address).map {
case Some(addressInfo) =>
val json = Obj(
"pubkey" -> Str(addressInfo.pubkey.hex),
@ -869,8 +873,8 @@ case class WalletRoutes(loadWalletApi: DLCWalletLoaderApi)(implicit
case CreateNewAccount(purpose) =>
complete {
for {
newWallet <- wallet.createNewAccount(purpose)
accounts <- newWallet.listAccounts()
_ <- wallet.accountHandling.createNewAccount(purpose)
accounts <- wallet.accountHandling.listAccounts()
} yield {
val xpubs = accounts.map(_.xpub)
val json =

View File

@ -1,19 +1,80 @@
package org.bitcoins.core.api.wallet
import org.bitcoins.core.api.wallet.db.AccountDb
import org.bitcoins.core.hd.{AddressType, HDAccount}
import org.bitcoins.core.api.wallet.db.{AccountDb, AddressDb}
import org.bitcoins.core.crypto.ExtPublicKey
import org.bitcoins.core.currency.CurrencyUnit
import org.bitcoins.core.hd.{AddressType, HDAccount, HDChainType, HDPurpose}
import org.bitcoins.core.protocol.BitcoinAddress
import org.bitcoins.core.protocol.script.ScriptPubKey
import scala.concurrent.Future
import scala.concurrent.{ExecutionContext, Future}
trait AccountHandlingApi {
/** Tries to create a new account in this wallet. Fails if the most recent
* account has no transaction history, as per BIP44
*
* @see
* [[https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki#account BIP44 account section]]
*/
def createNewAccount(
hdAccount: HDAccount
): Future[ExtPublicKey]
def createNewAccount(purpose: HDPurpose): Future[ExtPublicKey]
def getDefaultAccount(): Future[AccountDb]
def listAccounts(): Future[Vector[AccountDb]]
def getDefaultAccountForType(addressType: AddressType): Future[AccountDb]
def getNewAddress(
account: AccountDb,
chainType: HDChainType
): Future[BitcoinAddress]
final def getNewAddress(account: HDAccount)(implicit
ec: ExecutionContext): Future[BitcoinAddress] = {
val accountDbOptF = findAccount(account)
accountDbOptF.flatMap {
case Some(accountDb) => getNewAddress(accountDb)
case None =>
Future.failed(
new RuntimeException(
s"No account found for given hdaccount=${account}"
)
)
}
}
def getNewAddress(accountDb: AccountDb): Future[BitcoinAddress]
final def getNewChangeAddress(account: HDAccount)(implicit
ec: ExecutionContext): Future[BitcoinAddress] = {
val accountDbOptF = findAccount(account)
accountDbOptF.flatMap {
case Some(accountDb) => getNewChangeAddress(accountDb)
case None =>
Future.failed(
new RuntimeException(
s"No account found for given hdaccount=${account}"
)
)
}
}
def getNewChangeAddress(accountDb: AccountDb): Future[BitcoinAddress]
def clearUtxos(account: HDAccount): Future[Unit]
def findAccount(account: HDAccount): Future[Option[AccountDb]]
def generateScriptPubKeys(
account: HDAccount,
addressBatchSize: Int,
forceGenerateSpks: Boolean
): Future[Vector[ScriptPubKey]]
def listUnusedAddresses(
account: HDAccount
): Future[Vector[AddressDb]]
def listSpentAddresses(
account: HDAccount
): Future[Vector[AddressDb]]
def listAddresses(account: HDAccount): Future[Vector[AddressDb]]
def listFundedAddresses(
account: HDAccount
): Future[Vector[(AddressDb, CurrencyUnit)]]
}

View File

@ -0,0 +1,74 @@
package org.bitcoins.core.api.wallet
import org.bitcoins.core.api.wallet.db.{AddressDb, AddressTagDb, ScriptPubKeyDb}
import org.bitcoins.core.currency.CurrencyUnit
import org.bitcoins.core.hd.AddressType
import org.bitcoins.core.protocol.BitcoinAddress
import org.bitcoins.core.protocol.script.ScriptPubKey
import org.bitcoins.core.protocol.transaction.{
Transaction,
TransactionOutPoint,
TransactionOutput
}
import org.bitcoins.core.wallet.utxo.{
AddressTag,
AddressTagName,
AddressTagType
}
import scala.concurrent.Future
trait AddressHandlingApi {
def dropAddressTag(addressTagDb: AddressTagDb): Future[Int]
def dropAddressTagType(
addressTagType: AddressTagType
): Future[Int]
def dropAddressTagType(
address: BitcoinAddress,
addressTagType: AddressTagType
): Future[Int]
def dropAddressTagName(
address: BitcoinAddress,
addressTagName: AddressTagName
): Future[Int]
/** Given a transaction, returns the outputs (with their corresponding
* outpoints) that pay to this wallet
*/
def findOurOutputs(
transaction: Transaction
): Future[Vector[(TransactionOutput, TransactionOutPoint)]]
def getNewAddress(): Future[BitcoinAddress]
def getNewAddress(
tags: Vector[AddressTag]
): Future[BitcoinAddress]
def getNewAddress(
addressType: AddressType
): Future[BitcoinAddress]
def getNewChangeAddress(): Future[BitcoinAddress]
def getNewAddress(
addressType: AddressType,
tags: Vector[AddressTag]
): Future[BitcoinAddress]
def getAddressInfo(
address: BitcoinAddress
): Future[Option[AddressInfo]]
def getAddressTags(): Future[Vector[AddressTagDb]]
def getAddressTags(address: BitcoinAddress): Future[Vector[AddressTagDb]]
def getAddressTags(
address: BitcoinAddress,
tagType: AddressTagType
): Future[Vector[AddressTagDb]]
def listAddresses(): Future[Vector[AddressDb]]
def listUnusedAddresses(): Future[Vector[AddressDb]]
def listScriptPubKeys(): Future[Vector[ScriptPubKeyDb]]
def listSpentAddresses(): Future[Vector[AddressDb]]
def listFundedAddresses(): Future[Vector[(AddressDb, CurrencyUnit)]]
def tagAddress(
address: BitcoinAddress,
tag: AddressTag
): Future[AddressTagDb]
def watchScriptPubKey(
scriptPubKey: ScriptPubKey
): Future[ScriptPubKeyDb]
}

View File

@ -1,9 +1,9 @@
package org.bitcoins.core.api.wallet
import org.bitcoins.core.api.keymanager.BIP39KeyManagerApi
import org.bitcoins.core.api.wallet.db.{AccountDb, AddressDb, SpendingInfoDb}
import org.bitcoins.core.api.wallet.db.{AccountDb, SpendingInfoDb}
import org.bitcoins.core.currency.CurrencyUnit
import org.bitcoins.core.hd.{AddressType, HDAccount, HDChainType, HDPurpose}
import org.bitcoins.core.hd.{AddressType, HDAccount, HDPurpose}
import org.bitcoins.core.protocol.BitcoinAddress
import org.bitcoins.core.protocol.transaction.{
Transaction,
@ -33,6 +33,7 @@ trait HDWalletApi extends WalletApi {
def accountHandling: AccountHandlingApi
def fundTxHandling: FundTransactionHandlingApi
def rescanHandling: RescanHandlingApi
def addressHandling: AddressHandlingApi
/** Gets the balance of the given account */
def getBalance(account: HDAccount)(implicit
@ -51,21 +52,6 @@ trait HDWalletApi extends WalletApi {
def getUnconfirmedBalance(account: HDAccount): Future[CurrencyUnit]
def getNewAddress(account: HDAccount): Future[BitcoinAddress]
def getNewAddress(account: AccountDb): Future[BitcoinAddress]
/** Generates a new change address */
def getNewChangeAddress(account: AccountDb): Future[BitcoinAddress]
override def getNewChangeAddress()(implicit
ec: ExecutionContext): Future[BitcoinAddress] = {
for {
account <- getDefaultAccount()
addr <- getNewChangeAddress(account)
} yield addr
}
/** Fetches the default account from the DB
* @return
* Future[AccountDb]
@ -443,40 +429,12 @@ trait HDWalletApi extends WalletApi {
} yield tx
}
def listAddresses(account: HDAccount): Future[Vector[AddressDb]]
def listSpentAddresses(account: HDAccount): Future[Vector[AddressDb]]
def listFundedAddresses(
account: HDAccount): Future[Vector[(AddressDb, CurrencyUnit)]]
def listUnusedAddresses(account: HDAccount): Future[Vector[AddressDb]]
def listDefaultAccountUtxos(): Future[Vector[SpendingInfoDb]]
def listUtxos(hdAccount: HDAccount): Future[Vector[SpendingInfoDb]]
override def clearAllUtxos(): Future[HDWalletApi]
/** Gets the address associated with the pubkey at the resulting `BIP32Path`
* determined by the default account and the given chainType and addressIndex
*/
def getAddress(chainType: HDChainType, addressIndex: Int)(implicit
ec: ExecutionContext): Future[AddressDb] = {
for {
account <- getDefaultAccount()
address <- getAddress(account, chainType, addressIndex)
} yield address
}
/** Gets the address associated with the pubkey at the resulting `BIP32Path`
* determined the given account, chainType, and addressIndex
*/
def getAddress(
account: AccountDb,
chainType: HDChainType,
addressIndex: Int): Future[AddressDb]
def listAccounts(): Future[Vector[AccountDb]] = {
accountHandling.listAccounts()
}
@ -491,19 +449,6 @@ trait HDWalletApi extends WalletApi {
accountHandling.listAccounts().map(_.filter(_.hdAccount.purpose == purpose))
}
/** Creates a new account with the given purpose */
def createNewAccount(purpose: HDPurpose): Future[HDWalletApi]
/** Tries to create a new account in this wallet. Fails if the most recent
* account has no transaction history, as per BIP44
*
* @see
* [[https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki#account BIP44 account section]]
*/
def createNewAccount(hdAccount: HDAccount): Future[HDWalletApi]
def findAccount(account: HDAccount): Future[Option[AccountDb]]
def fundRawTransaction(
destinations: Vector[TransactionOutput],
feeRate: FeeUnit,

View File

@ -5,10 +5,9 @@ import org.bitcoins.core.api.feeprovider.FeeRateApi
import org.bitcoins.core.api.keymanager.KeyManagerApi
import org.bitcoins.core.api.node.NodeApi
import org.bitcoins.core.api.wallet.db.*
import org.bitcoins.core.config.NetworkParameters
import org.bitcoins.core.crypto.ExtPublicKey
import org.bitcoins.core.currency.CurrencyUnit
import org.bitcoins.core.hd.{AddressType, HDAccount}
import org.bitcoins.core.hd.HDAccount
import org.bitcoins.core.protocol.BitcoinAddress
import org.bitcoins.core.protocol.blockchain.Block
import org.bitcoins.core.protocol.script.ScriptPubKey
@ -17,19 +16,13 @@ import org.bitcoins.core.protocol.transaction.{
TransactionOutPoint,
TransactionOutput
}
import org.bitcoins.core.util.{FutureUtil, StartStopAsync}
import org.bitcoins.core.util.StartStopAsync
import org.bitcoins.core.wallet.fee.FeeUnit
import org.bitcoins.core.wallet.utxo.{
AddressTag,
AddressTagName,
AddressTagType,
TxoState
}
import org.bitcoins.core.wallet.utxo.{AddressTag, TxoState}
import org.bitcoins.crypto.{DoubleSha256Digest, DoubleSha256DigestBE}
import java.time.Instant
import scala.concurrent.{ExecutionContext, Future}
import scala.util.{Failure, Success}
/** API for the wallet project.
*
@ -108,21 +101,15 @@ trait WalletApi extends StartStopAsync[WalletApi] {
def getConfirmedBalance(tag: AddressTag): Future[CurrencyUnit]
def getNewAddress(): Future[BitcoinAddress]
def getNewChangeAddress(): Future[BitcoinAddress]
/** Gets the sum of all unconfirmed UTXOs in this wallet */
def getUnconfirmedBalance(): Future[CurrencyUnit]
def getUnconfirmedBalance(tag: AddressTag): Future[CurrencyUnit]
def listAddresses(): Future[Vector[AddressDb]]
def listSpentAddresses(): Future[Vector[AddressDb]]
def listFundedAddresses(): Future[Vector[(AddressDb, CurrencyUnit)]]
def listUnusedAddresses(): Future[Vector[AddressDb]]
def listScriptPubKeys(): Future[Vector[ScriptPubKeyDb]]
def listTransactions(): Future[Vector[TransactionDb]]
def listUtxos(): Future[Vector[SpendingInfoDb]]
@ -131,8 +118,6 @@ trait WalletApi extends StartStopAsync[WalletApi] {
def listUtxos(tag: AddressTag): Future[Vector[SpendingInfoDb]]
def watchScriptPubKey(scriptPubKey: ScriptPubKey): Future[ScriptPubKeyDb]
/** Checks if the wallet contains any data */
def isEmpty(): Future[Boolean]
@ -143,86 +128,6 @@ trait WalletApi extends StartStopAsync[WalletApi] {
def clearAllAddresses(): Future[WalletApi]
/** Gets a new external address with the specified type.
* @param addressType
*/
def getNewAddress(addressType: AddressType): Future[BitcoinAddress]
/** Gets a new external address Calling this method multiple times will return
* the same address, until it has received funds.
*/
def getNewAddress(): Future[BitcoinAddress]
def getNewAddress(
addressType: AddressType,
tags: Vector[AddressTag]): Future[BitcoinAddress]
def getNewAddress(tags: Vector[AddressTag]): Future[BitcoinAddress]
/** Gets a external address the given AddressType. Calling this method
* multiple times will return the same address, until it has received funds.
*/
def getUnusedAddress(addressType: AddressType): Future[BitcoinAddress]
/** Gets a external address. Calling this method multiple times will return
* the same address, until it has received funds.
*/
def getUnusedAddress: Future[BitcoinAddress]
/** Mimics the `getaddressinfo` RPC call in Bitcoin Core
*
* @param address
* @return
* If the address is found in our database `Some(address)` is returned,
* otherwise `None`
*/
def getAddressInfo(address: BitcoinAddress): Future[Option[AddressInfo]]
def getAddressInfo(
spendingInfoDb: SpendingInfoDb,
networkParameters: NetworkParameters): Future[Option[AddressInfo]] = {
val addressT = BitcoinAddress.fromScriptPubKeyT(
spk = spendingInfoDb.output.scriptPubKey,
np = networkParameters)
addressT match {
case Success(addr) =>
getAddressInfo(addr)
case Failure(_) =>
FutureUtil.none
}
}
/** Tags the address with the address tag, updates the tag if one of tag's
* TagType already exists
*/
def tagAddress(address: BitcoinAddress, tag: AddressTag): Future[AddressTagDb]
def getAddressTags(address: BitcoinAddress): Future[Vector[AddressTagDb]]
def getAddressTags(
address: BitcoinAddress,
tagType: AddressTagType): Future[Vector[AddressTagDb]]
def getAddressTags(): Future[Vector[AddressTagDb]]
def getAddressTags(tagType: AddressTagType): Future[Vector[AddressTagDb]]
def dropAddressTag(addressTagDb: AddressTagDb): Future[Int]
def dropAddressTagType(addressTagType: AddressTagType): Future[Int]
def dropAddressTagType(
address: BitcoinAddress,
addressTagType: AddressTagType): Future[Int]
def dropAddressTagName(
address: BitcoinAddress,
tagName: AddressTagName): Future[Int]
/** Generates a new change address */
protected[wallet] def getNewChangeAddress()(implicit
ec: ExecutionContext): Future[BitcoinAddress]
def keyManager: KeyManagerApi
protected def determineFeeRate(feeRateOpt: Option[FeeUnit]): Future[FeeUnit] =

View File

@ -301,8 +301,8 @@ abstract class DLCWallet
index: Int
): Future[Vector[AddressDb]] = {
for {
zero <- getAddress(account, chainType, index)
one <- getAddress(account, chainType, index + 1)
zero <- addressHandling.getAddress(account, chainType, index)
one <- addressHandling.getAddress(account, chainType, index + 1)
} yield {
logger.debug(s"Wrote DLC key addresses to database using index $index")
Vector(zero, one)
@ -432,7 +432,7 @@ abstract class DLCWallet
chainType = HDChainType.External
account <- getDefaultAccountForType(AddressType.SegWit)
nextIndex <- getNextAvailableIndex(account, chainType)
nextIndex <- addressHandling.getNextAvailableIndex(account, chainType)
_ <- writeDLCKeysToAddressDb(account, chainType, nextIndex)
fundRawTxHelper <- fundTxHandling.fundRawTransactionInternal(
@ -636,7 +636,8 @@ abstract class DLCWallet
Future.successful(initAccept)
}
case None =>
val nextIndexF = getNextAvailableIndex(account, chainType)
val nextIndexF =
addressHandling.getNextAvailableIndex(account, chainType)
val acceptWithoutSigsWithKeysF
: Future[(DLCAcceptWithoutSigs, DLCPublicKeys)] =
nextIndexF.map { nextIndex =>

View File

@ -59,7 +59,7 @@ class NeutrinoNodeWithWalletTest extends NodeTestWithCachedBitcoindNewest {
): Future[Boolean] = {
for {
balance <- wallet.getBalance()
addresses <- wallet.listAddresses()
addresses <- wallet.addressHandling.listAddresses()
utxos <- wallet.listDefaultAccountUtxos()
} yield {
// +- fee rate because signatures could vary in size
@ -108,7 +108,7 @@ class NeutrinoNodeWithWalletTest extends NodeTestWithCachedBitcoindNewest {
_ <- NodeTestUtil.awaitSync(node, bitcoind)
_ <- AsyncUtil.awaitConditionF(condition1, maxTries = 100) // 10 seconds
// receive
address <- wallet.getNewAddress()
address <- wallet.addressHandling.getNewAddress()
txId <- bitcoind.sendToAddress(address, TestAmount)
expectedTx <- bitcoind.getRawTransactionRaw(txId)
@ -145,7 +145,7 @@ class NeutrinoNodeWithWalletTest extends NodeTestWithCachedBitcoindNewest {
for {
// start watching
_ <- wallet.watchScriptPubKey(spk)
_ <- wallet.addressHandling.watchScriptPubKey(spk)
// send
txSent <- wallet.sendToOutputs(Vector(output), FeeRate)
@ -168,7 +168,7 @@ class NeutrinoNodeWithWalletTest extends NodeTestWithCachedBitcoindNewest {
for {
rescan <- wallet.isRescanning()
balance <- wallet.getBalance()
addresses <- wallet.listAddresses()
addresses <- wallet.addressHandling.listAddresses()
utxos <- wallet.listDefaultAccountUtxos()
spks = utxos
.map(_.output.scriptPubKey)
@ -181,7 +181,7 @@ class NeutrinoNodeWithWalletTest extends NodeTestWithCachedBitcoindNewest {
}
for {
addresses <- wallet.listAddresses()
addresses <- wallet.addressHandling.listAddresses()
utxos <- wallet.listDefaultAccountUtxos()
_ = assert(addresses.size == 6)
_ = assert(utxos.size == 3)
@ -191,7 +191,7 @@ class NeutrinoNodeWithWalletTest extends NodeTestWithCachedBitcoindNewest {
bitcoind
.sendToAddress(address, TestAmount)
addresses <- wallet.listAddresses()
addresses <- wallet.addressHandling.listAddresses()
utxos <- wallet.listDefaultAccountUtxos()
_ = assert(addresses.size == 7)
_ = assert(utxos.size == 3)
@ -207,7 +207,7 @@ class NeutrinoNodeWithWalletTest extends NodeTestWithCachedBitcoindNewest {
.map(_.get.height == bitcoindHeight)
})
_ <- wallet.clearAllUtxos()
addresses <- wallet.listAddresses()
addresses <- wallet.addressHandling.listAddresses()
utxos <- wallet.listDefaultAccountUtxos()
_ = assert(addresses.nonEmpty)
_ = assert(utxos.isEmpty)

View File

@ -392,10 +392,10 @@ object BitcoinSWalletTest extends WalletLogger {
for {
wallet <- defaultWalletF
account1 = WalletTestUtil.getHdAccount1(wallet.walletConfig)
newAccountWallet <- wallet.createNewAccount(
_ <- wallet.accountHandling.createNewAccount(
hdAccount = account1
)
} yield newAccountWallet
} yield wallet
}
@ -411,10 +411,10 @@ object BitcoinSWalletTest extends WalletLogger {
chainQueryApi = chainQueryApi
)
account1 = WalletTestUtil.getHdAccount1(wallet.walletConfig)
newAccountWallet <- wallet.createNewAccount(
_ <- wallet.accountHandling.createNewAccount(
hdAccount = account1
)
} yield newAccountWallet.asInstanceOf[DLCWallet]
} yield wallet
}
/** Pairs the given wallet with a bitcoind instance that has money in the

View File

@ -68,7 +68,7 @@ trait FundWalletUtil extends BitcoinSLogger {
)(implicit ec: ExecutionContext): Future[Wallet] = {
val addressesF: Future[Vector[BitcoinAddress]] = Future.sequence {
Vector.fill(3)(wallet.getNewAddress(account))
Vector.fill(3)(wallet.accountHandling.getNewAddress(account))
}
// construct three txs that send money to these addresses
@ -100,7 +100,7 @@ trait FundWalletUtil extends BitcoinSLogger {
)(implicit ec: ExecutionContext): Future[HDWalletApi] = {
val addressesF: Future[Vector[BitcoinAddress]] = Future.sequence {
Vector.fill(3)(wallet.getNewAddress(account))
Vector.fill(3)(wallet.accountHandling.getNewAddress(account))
}
val txAndHashF = for {

View File

@ -27,11 +27,11 @@ class AddressHandlingTest extends BitcoinSWalletTest {
it must "generate a new address for the default account and then find it" in {
(fundedWallet: FundedWallet) =>
val wallet = fundedWallet.wallet
val addressF = wallet.getNewAddress()
val addressF = wallet.addressHandling.getNewAddress()
for {
address <- addressF
exists <- wallet.contains(address, None)
exists <- wallet.addressHandling.contains(address, None)
} yield {
assert(exists, s"Wallet must contain address after generating it")
}
@ -41,12 +41,14 @@ class AddressHandlingTest extends BitcoinSWalletTest {
(fundedWallet: FundedWallet) =>
val wallet = fundedWallet.wallet
val account1 = WalletTestUtil.getHdAccount1(wallet.walletConfig)
val addressF = wallet.getNewAddress(account1)
val addressF = wallet.accountHandling.getNewAddress(account1)
for {
address <- addressF
listAddressesForAcct <- wallet.listAddresses(account1)
exists <- wallet.contains(address, Some(account1))
doesNotExist <- wallet.contains(address, None)
listAddressesForAcct <- wallet.accountHandling.listAddresses(account1)
exists <- wallet.addressHandling.contains(
address,
Some((wallet.accountHandling, account1)))
doesNotExist <- wallet.addressHandling.contains(address, None)
} yield {
assert(listAddressesForAcct.nonEmpty)
assert(listAddressesForAcct.map(_.address).contains(address))
@ -66,13 +68,13 @@ class AddressHandlingTest extends BitcoinSWalletTest {
val wallet = fundedWallet.wallet
for {
address1 <- wallet.getUnusedAddress
exists <- wallet.contains(address1, None)
address1 <- wallet.addressHandling.getUnusedAddress
exists <- wallet.addressHandling.contains(address1, None)
_ = assert(exists, s"Wallet must contain address after generating it")
address2 <- wallet.getUnusedAddress
address2 <- wallet.addressHandling.getUnusedAddress
_ = assert(address1 == address2, "Must generate same address")
_ <- wallet.sendToAddress(address1, Satoshis(10000), None)
address3 <- wallet.getUnusedAddress
address3 <- wallet.addressHandling.getUnusedAddress
} yield {
assert(address1 != address3, "Must generate a new address")
assert(address2 != address3, "Must generate a new address")
@ -83,7 +85,7 @@ class AddressHandlingTest extends BitcoinSWalletTest {
(fundedWallet: FundedWallet) =>
val wallet = fundedWallet.wallet
val addressesF = Future.sequence {
Vector.fill(10)(wallet.getNewAddress())
Vector.fill(10)(wallet.addressHandling.getNewAddress())
}
for {
@ -102,16 +104,16 @@ class AddressHandlingTest extends BitcoinSWalletTest {
val wallet = fundedWallet.wallet
for {
emptySpentAddresses <- wallet.listSpentAddresses()
emptySpentAddresses <- wallet.addressHandling.listSpentAddresses()
_ = assert(
emptySpentAddresses.isEmpty,
s"Wallet did not start with empty spent addresses, got $emptySpentAddresses"
)
tempAddress <- wallet.getNewAddress()
tempAddress <- wallet.addressHandling.getNewAddress()
tx <- wallet.sendToAddress(tempAddress, Bitcoins(1), None)
spentDbs <- wallet.spendingInfoDAO.findOutputsBeingSpent(tx)
spentAddresses <- wallet.listSpentAddresses()
spentAddresses <- wallet.addressHandling.listSpentAddresses()
} yield {
val diff = spentDbs
.map(_.output.scriptPubKey)
@ -126,7 +128,7 @@ class AddressHandlingTest extends BitcoinSWalletTest {
for {
unspentDbs <- wallet.spendingInfoDAO.findAllUnspent()
fundedAddresses <- wallet.listFundedAddresses()
fundedAddresses <- wallet.addressHandling.listFundedAddresses()
} yield {
val diff = unspentDbs
.map(_.output)
@ -144,7 +146,7 @@ class AddressHandlingTest extends BitcoinSWalletTest {
for {
addrDbs <- wallet.spendingInfoDAO.findAllSpendingInfos()
fundedAddresses <- wallet.listUnusedAddresses()
fundedAddresses <- wallet.addressHandling.listUnusedAddresses()
} yield {
val intersect = addrDbs
.map(_.output.scriptPubKey)
@ -157,13 +159,13 @@ class AddressHandlingTest extends BitcoinSWalletTest {
val wallet = fundedWallet.wallet
for {
addr <- wallet.getNewAddress()
initTags <- wallet.getAddressTags(addr)
addr <- wallet.addressHandling.getNewAddress()
initTags <- wallet.addressHandling.getAddressTags(addr)
_ = assert(initTags.isEmpty)
tag = AddressLabelTag("for test")
_ <- wallet.tagAddress(addr, tag)
tags <- wallet.getAddressTags(addr)
_ <- wallet.addressHandling.tagAddress(addr, tag)
tags <- wallet.addressHandling.getAddressTags(addr)
} yield {
assert(tags.size == 1)
val tagDb = tags.head
@ -176,19 +178,19 @@ class AddressHandlingTest extends BitcoinSWalletTest {
val wallet = fundedWallet.wallet
for {
addr <- wallet.getNewAddress()
initTags <- wallet.getAddressTags(addr)
addr <- wallet.addressHandling.getNewAddress()
initTags <- wallet.addressHandling.getAddressTags(addr)
_ = assert(initTags.isEmpty)
tag = AddressLabelTag("no one knows the supply of eth")
_ <- wallet.tagAddress(addr, tag)
tags <- wallet.getAddressTags(addr)
_ <- wallet.addressHandling.tagAddress(addr, tag)
tags <- wallet.addressHandling.getAddressTags(addr)
_ = assert(tags.size == 1)
tagDb = tags.head
_ = assert(tagDb.address == addr)
_ = assert(tagDb.addressTag == tag)
num <- wallet.dropAddressTag(tagDb)
num <- wallet.addressHandling.dropAddressTag(tagDb)
} yield assert(num == 1)
}
@ -198,16 +200,16 @@ class AddressHandlingTest extends BitcoinSWalletTest {
for {
addr <- wallet.getNewAddress()
addr1 <- wallet.getNewAddress()
initTags <- wallet.getAddressTags(addr)
initTags1 <- wallet.getAddressTags(addr1)
initTags <- wallet.addressHandling.getAddressTags(addr)
initTags1 <- wallet.addressHandling.getAddressTags(addr1)
_ = assert(initTags.isEmpty)
_ = assert(initTags1.isEmpty)
tag = AddressLabelTag("no one knows the supply of eth")
_ <- wallet.tagAddress(addr, tag)
_ <- wallet.tagAddress(addr, HotStorage)
_ <- wallet.tagAddress(addr1, tag)
tags <- wallet.getAddressTags(AddressLabelTagType)
_ <- wallet.addressHandling.tagAddress(addr, tag)
_ <- wallet.addressHandling.tagAddress(addr, HotStorage)
_ <- wallet.addressHandling.tagAddress(addr1, tag)
tags <- wallet.addressHandling.getAddressTags(AddressLabelTagType)
_ = assert(tags.size == 2)
tagDb = tags.head
_ = assert(tagDb.address == addr)
@ -216,8 +218,10 @@ class AddressHandlingTest extends BitcoinSWalletTest {
_ = assert(tagDb1.address == addr1)
_ = assert(tagDb1.addressTag == tag)
numDropped <- wallet.dropAddressTagType(AddressLabelTagType)
hotStorageTags <- wallet.getAddressTags(StorageLocationTagType)
numDropped <- wallet.addressHandling.dropAddressTagType(
AddressLabelTagType)
hotStorageTags <- wallet.addressHandling.getAddressTags(
StorageLocationTagType)
} yield {
assert(numDropped == 2)
assert(hotStorageTags.size == 1)
@ -229,9 +233,9 @@ class AddressHandlingTest extends BitcoinSWalletTest {
val wallet = fundedWallet.wallet
val spk = EmptyScriptPubKey
for {
before <- wallet.listScriptPubKeys()
spkDb <- wallet.watchScriptPubKey(spk)
after <- wallet.listScriptPubKeys()
before <- wallet.addressHandling.listScriptPubKeys()
spkDb <- wallet.addressHandling.watchScriptPubKey(spk)
after <- wallet.addressHandling.listScriptPubKeys()
} yield {
assert(before.size + 1 == after.size)
assert(spkDb.scriptPubKey == spk)

View File

@ -21,10 +21,10 @@ class AddressLabelTest extends BitcoinSWalletTest {
val tag1 = UnknownAddressTag("test_tag_name_1", "test_tag_type_1")
val tag2 = UnknownAddressTag("test_tag_name_2", "test_tag_type_2")
val addressF = for {
address <- wallet.getNewAddress(Vector(tag1))
address <- wallet.addressHandling.getNewAddress(Vector(tag1))
// add another tag to address
tagDb1 <- wallet.getAddressTags(address)
tagDb2 <- wallet.tagAddress(address, tag2)
tagDb1 <- wallet.addressHandling.getAddressTags(address)
tagDb2 <- wallet.addressHandling.tagAddress(address, tag2)
} yield {
assert(tagDb1.head.address == address)
assert(tagDb1.head.tagName == tag1.tagName)
@ -50,8 +50,8 @@ class AddressLabelTest extends BitcoinSWalletTest {
val resultF = for {
address <- wallet.getNewAddress()
// add another tag to address
_ <- wallet.tagAddress(address, tag1)
_ <- wallet.tagAddress(address, tag2)
_ <- wallet.addressHandling.tagAddress(address, tag1)
_ <- wallet.addressHandling.tagAddress(address, tag2)
} yield ()
recoverToSucceededIf[SQLException](resultF)

View File

@ -41,7 +41,7 @@ class AddressTagIntegrationTest extends BitcoinSWalletTest {
IncomingTransactionDAO()(system.dispatcher, walletConfig)
for {
addr <- wallet.getNewAddress()
taggedAddr <- wallet.getNewAddress(Vector(exampleTag))
taggedAddr <- wallet.addressHandling.getNewAddress(Vector(exampleTag))
txId <- bitcoind.sendMany(
Map(addr -> valueFromBitcoind, taggedAddr -> valueFromBitcoind)
)
@ -132,7 +132,7 @@ class AddressTagIntegrationTest extends BitcoinSWalletTest {
val bitcoindAddrF = bitcoind.getNewAddress
val walletAddr1F = wallet.getNewAddress()
val taggedAddrF = wallet.getNewAddress(Vector(exampleTag))
val taggedAddrF = wallet.addressHandling.getNewAddress(Vector(exampleTag))
for {
walletAddr1 <- walletAddr1F
txid <- bitcoind.sendToAddress(walletAddr1, Bitcoins.two)

View File

@ -169,7 +169,7 @@ class FundTransactionHandlingTest
val wallet = fundedWallet.wallet
val walletConfig = fundedWallet.walletConfig
val account1 = WalletTestUtil.getHdAccount1(walletConfig)
val account1DbF = wallet.findAccount(account1)
val account1DbF = wallet.accountHandling.findAccount(account1)
for {
feeRate <- wallet.getFeeRate()
account1DbOpt <- account1DbF
@ -195,7 +195,7 @@ class FundTransactionHandlingTest
val wallet = fundedWallet.wallet
val walletConfig = fundedWallet.walletConfig
val account1 = WalletTestUtil.getHdAccount1(walletConfig)
val account1DbF = wallet.findAccount(account1)
val account1DbF = wallet.accountHandling.findAccount(account1)
val fundedTxF = for {
feeRate <- wallet.getFeeRate()
account1DbOpt <- account1DbF
@ -218,11 +218,12 @@ class FundTransactionHandlingTest
val bitcoind = fundedWallet.bitcoind
val fundedTxF = for {
feeRate <- wallet.getFeeRate()
_ <- wallet.createNewAccount(wallet.keyManager.kmParams.purpose)
_ <- wallet.accountHandling.createNewAccount(
wallet.keyManager.kmParams.purpose)
accounts <- wallet.listAccounts()
account2 = accounts.find(_.hdAccount.index == 2).get
addr <- wallet.getNewAddress(account2)
addr <- wallet.accountHandling.getNewAddress(account2)
hash <- bitcoind.generateToAddress(1, addr).map(_.head)
block <- bitcoind.getBlockRaw(hash)
@ -273,7 +274,7 @@ class FundTransactionHandlingTest
): Future[Assertion] = {
for {
feeRate <- wallet.getFeeRate()
taggedAddr <- wallet.getNewAddress(Vector(tag))
taggedAddr <- wallet.addressHandling.getNewAddress(Vector(tag))
_ <-
wallet.sendToAddress(taggedAddr, destination.value * 2, Some(feeRate))
taggedBalance <- wallet.getBalance(tag)

View File

@ -17,8 +17,8 @@ class LegacyWalletTest extends BitcoinSWalletTest {
addr <- wallet.getNewAddress()
account <- wallet.getDefaultAccount()
otherAddr <- wallet.getNewAddress()
thirdAddr <- wallet.getNewAddress(AddressType.Legacy)
allAddrs <- wallet.listAddresses()
thirdAddr <- wallet.addressHandling.getNewAddress(AddressType.Legacy)
allAddrs <- wallet.addressHandling.listAddresses()
} yield {
assert(account.hdAccount.purpose == HDPurpose.Legacy)
assert(allAddrs.forall(_.address.isInstanceOf[P2PKHAddress]))
@ -32,7 +32,7 @@ class LegacyWalletTest extends BitcoinSWalletTest {
it should "generate segwit addresses" in { wallet =>
for {
account <- wallet.getDefaultAccountForType(AddressType.SegWit)
addr <- wallet.getNewAddress(AddressType.SegWit)
addr <- wallet.addressHandling.getNewAddress(AddressType.SegWit)
} yield {
assert(account.hdAccount.purpose == HDPurpose.SegWit)
assert(addr.isInstanceOf[Bech32Address])
@ -41,9 +41,9 @@ class LegacyWalletTest extends BitcoinSWalletTest {
it should "generate mixed addresses" in { wallet =>
for {
segwit <- wallet.getNewAddress(AddressType.SegWit)
legacy <- wallet.getNewAddress(AddressType.Legacy)
nested <- wallet.getNewAddress(AddressType.NestedSegWit)
segwit <- wallet.addressHandling.getNewAddress(AddressType.SegWit)
legacy <- wallet.addressHandling.getNewAddress(AddressType.Legacy)
nested <- wallet.addressHandling.getNewAddress(AddressType.NestedSegWit)
} yield {
assert(segwit.isInstanceOf[Bech32Address])
assert(legacy.isInstanceOf[P2PKHAddress])

View File

@ -184,7 +184,7 @@ class ProcessBlockTest extends BitcoinSWalletTestCachedBitcoindNewest {
UInt32.zero
)
addrDb <- wallet.getAddressInfo(recvAddr).map(_.get)
addrDb <- wallet.addressHandling.getAddressInfo(recvAddr).map(_.get)
path = addrDb.path
coin = path.coin
accountDb <- accountDAO

View File

@ -29,13 +29,13 @@ class RescanHandlingTest extends BitcoinSWalletTestCachedBitcoindNewest {
utxos <- wallet.listUtxos(account)
_ = assert(utxos.nonEmpty)
addresses <- wallet.listAddresses(account)
addresses <- wallet.accountHandling.listAddresses(account)
_ = assert(addresses.nonEmpty)
_ <- wallet.accountHandling.clearUtxos(account)
clearedUtxos <- wallet.listUtxos(account)
clearedAddresses <- wallet.listAddresses(account)
clearedAddresses <- wallet.accountHandling.listAddresses(account)
} yield {
assert(clearedUtxos.isEmpty)
assert(clearedAddresses.nonEmpty)
@ -52,13 +52,13 @@ class RescanHandlingTest extends BitcoinSWalletTestCachedBitcoindNewest {
utxos <- wallet.listUtxos()
_ = assert(utxos.nonEmpty)
addresses <- wallet.listAddresses()
addresses <- wallet.addressHandling.listAddresses()
_ = assert(addresses.nonEmpty)
_ <- wallet.clearAllUtxos()
clearedUtxos <- wallet.listUtxos()
clearedAddresses <- wallet.listAddresses()
clearedAddresses <- wallet.addressHandling.listAddresses()
} yield {
assert(clearedUtxos.isEmpty)
assert(clearedAddresses.nonEmpty)
@ -362,7 +362,7 @@ class RescanHandlingTest extends BitcoinSWalletTestCachedBitcoindNewest {
force = false
)
usedAddresses <- wallet.listFundedAddresses()
usedAddresses <- wallet.addressHandling.listFundedAddresses()
_ = assert(
!usedAddresses.exists(_._1.address == address),
@ -372,7 +372,7 @@ class RescanHandlingTest extends BitcoinSWalletTestCachedBitcoindNewest {
hashes <- bitcoind.generateToAddress(1, address)
block <- bitcoind.getBlockRaw(hashes.head)
_ <- wallet.processBlock(block)
fundedAddresses <- wallet.listFundedAddresses()
fundedAddresses <- wallet.addressHandling.listFundedAddresses()
utxos <- wallet.listUtxos(TxoState.ImmatureCoinbase)
} yield {
// note 25 bitcoin reward from coinbase tx here
@ -580,7 +580,7 @@ class RescanHandlingTest extends BitcoinSWalletTestCachedBitcoindNewest {
DEFAULT_ADDR_BATCH_SIZE)
_ = assert(rescanState.isInstanceOf[RescanState.RescanStarted])
_ <- RescanState.awaitRescanDone(rescanState)
addresses <- wallet.listAddresses()
addresses <- wallet.addressHandling.listAddresses()
} yield {
assert(addresses.exists(_.isChange))
assert(addresses.exists(!_.isChange))

View File

@ -18,8 +18,8 @@ class SegwitWalletTest extends BitcoinSWalletTest {
addr <- wallet.getNewAddress()
account <- wallet.getDefaultAccount()
otherAddr <- wallet.getNewAddress()
thirdAddr <- wallet.getNewAddress(AddressType.SegWit)
allAddrs <- wallet.listAddresses()
thirdAddr <- wallet.addressHandling.getNewAddress(AddressType.SegWit)
allAddrs <- wallet.addressHandling.listAddresses()
} yield {
assert(account.hdAccount.purpose == HDPurpose.SegWit)
assert(allAddrs.forall(_.address.isInstanceOf[Bech32Address]))
@ -33,7 +33,7 @@ class SegwitWalletTest extends BitcoinSWalletTest {
it should "generate legacy addresses" in { wallet =>
for {
account <- wallet.getDefaultAccountForType(AddressType.Legacy)
addr <- wallet.getNewAddress(AddressType.Legacy)
addr <- wallet.addressHandling.getNewAddress(AddressType.Legacy)
} yield {
assert(account.hdAccount.purpose == HDPurpose.Legacy)
assert(addr.isInstanceOf[P2PKHAddress])
@ -42,9 +42,9 @@ class SegwitWalletTest extends BitcoinSWalletTest {
it should "generate mixed addresses" in { wallet =>
for {
segwit <- wallet.getNewAddress(AddressType.SegWit)
legacy <- wallet.getNewAddress(AddressType.Legacy)
nested <- wallet.getNewAddress(AddressType.NestedSegWit)
segwit <- wallet.addressHandling.getNewAddress(AddressType.SegWit)
legacy <- wallet.addressHandling.getNewAddress(AddressType.Legacy)
nested <- wallet.addressHandling.getNewAddress(AddressType.NestedSegWit)
} yield {
assert(segwit.isInstanceOf[Bech32Address])
assert(legacy.isInstanceOf[P2PKHAddress])

View File

@ -196,7 +196,7 @@ class TrezorAddressTest extends BitcoinSWalletTest with EmptyFixture {
val accountsToCreate = existing.length until testVectors.length
FutureUtil
.sequentially(accountsToCreate) { _ =>
wallet.createNewAccount(keyManagerParams.purpose)
wallet.accountHandling.createNewAccount(keyManagerParams.purpose)
}
.map(_ => ())
}
@ -212,9 +212,10 @@ class TrezorAddressTest extends BitcoinSWalletTest with EmptyFixture {
val addrFutures: Future[Seq[AddressDb]] =
FutureUtil.sequentially(vec.addresses) { vector =>
val addrFut = vector.chain match {
case HDChainType.Change => wallet.getNewChangeAddress(acc)
case HDChainType.Change =>
wallet.accountHandling.getNewChangeAddress(acc)
case HDChainType.External =>
wallet.getNewAddress(acc)
wallet.accountHandling.getNewAddress(acc)
}
addrFut.flatMap(wallet.addressDAO.findAddress).map {
case Some(addr) => addr

View File

@ -49,7 +49,7 @@ class WalletCallbackTest extends BitcoinSWalletTest {
for {
address <- wallet.getNewAddress()
exists <- wallet.contains(address, None)
exists <- wallet.addressHandling.contains(address, None)
_ = assert(exists, "Wallet must contain address after generating it")
result <- resultP.future
} yield assert(result == address)

View File

@ -37,7 +37,7 @@ class WalletUnitTest extends BitcoinSWalletTest {
it should "create a new wallet" in { (wallet: Wallet) =>
for {
accounts <- wallet.listAccounts()
addresses <- wallet.listAddresses()
addresses <- wallet.addressHandling.listAddresses()
} yield {
assert(accounts.length == 3) // legacy, segwit and nested segwit
assert(addresses.isEmpty)
@ -48,7 +48,7 @@ class WalletUnitTest extends BitcoinSWalletTest {
for {
addr <- wallet.getNewAddress()
otherAddr <- wallet.getNewAddress()
allAddrs <- wallet.listAddresses()
allAddrs <- wallet.addressHandling.listAddresses()
} yield {
assert(allAddrs.length == 2)
assert(allAddrs.exists(_.address == addr))
@ -221,7 +221,7 @@ class WalletUnitTest extends BitcoinSWalletTest {
for {
accountDb <- wallet.accountDAO.findAll().map(_.head)
addr <- wallet.getNewAddress(accountDb)
addr <- wallet.accountHandling.getNewAddress(accountDb)
addrDb <- wallet.addressDAO.findAddress(addr).map(_.get)
walletKey = addrDb.ecPublicKey
walletPath = addrDb.path
@ -247,7 +247,7 @@ class WalletUnitTest extends BitcoinSWalletTest {
it must "be able to sign a psbt with our own p2pkh utxo" in {
(wallet: Wallet) =>
for {
addr <- wallet.getNewAddress(AddressType.Legacy)
addr <- wallet.addressHandling.getNewAddress(AddressType.Legacy)
addrDb <- wallet.addressDAO.findAddress(addr).map(_.get)
walletKey = addrDb.ecPublicKey
@ -271,7 +271,7 @@ class WalletUnitTest extends BitcoinSWalletTest {
it must "be able to sign a psbt with our own p2sh segwit utxo" in {
(wallet: Wallet) =>
for {
addr <- wallet.getNewAddress(AddressType.NestedSegWit)
addr <- wallet.addressHandling.getNewAddress(AddressType.NestedSegWit)
addrDb <- wallet.addressDAO.findAddress(addr).map(_.get)
walletKey = addrDb.ecPublicKey
@ -295,7 +295,7 @@ class WalletUnitTest extends BitcoinSWalletTest {
it must "be able to sign a psbt with our own p2wpkh utxo" in {
(wallet: Wallet) =>
for {
addr <- wallet.getNewAddress(AddressType.SegWit)
addr <- wallet.addressHandling.getNewAddress(AddressType.SegWit)
addrDb <- wallet.addressDAO.findAddress(addr).map(_.get)
walletKey = addrDb.ecPublicKey
@ -326,7 +326,7 @@ class WalletUnitTest extends BitcoinSWalletTest {
it must "get correct txs to broadcast" in { (wallet: Wallet) =>
for {
addr <- wallet.getNewAddress(AddressType.SegWit)
addr <- wallet.addressHandling.getNewAddress(AddressType.SegWit)
addrDb <- wallet.addressDAO.findAddress(addr).map(_.get)
walletKey = addrDb.ecPublicKey

View File

@ -44,12 +44,9 @@ import java.time.temporal.ChronoUnit
import java.util.concurrent.TimeUnit
import scala.concurrent.{ExecutionContext, Future}
import scala.util.control.NonFatal
import scala.util.{Failure, Random, Success}
import scala.util.Random
abstract class Wallet
extends NeutrinoHDWalletApi
with AddressHandling
with WalletLogger {
abstract class Wallet extends NeutrinoHDWalletApi with WalletLogger {
override def keyManager: BIP39KeyManager = {
walletConfig.kmConf.toBip39KeyManager
@ -104,13 +101,16 @@ abstract class Wallet
def fundTxHandling: FundTransactionHandling = FundTransactionHandling(
accountHandling = accountHandling,
utxoHandling = utxoHandling,
addressHandling = this,
addressHandling = addressHandling,
spendingInfoDAO = spendingInfoDAO,
transactionDAO = transactionDAO,
keyManager = keyManager
)
def accountHandling: AccountHandlingApi =
AccountHandling(this, walletDAOs)
def accountHandling: AccountHandling =
AccountHandling(walletDAOs, keyManager)
def addressHandling: AddressHandling =
AddressHandling(accountHandling, walletDAOs)
protected lazy val transactionProcessing: TransactionProcessingApi = {
TransactionProcessing(
@ -124,7 +124,7 @@ abstract class Wallet
RescanHandling(
transactionProcessing = transactionProcessing,
accountHandling = accountHandling,
addressHandling = this,
addressHandling = addressHandling,
chainQueryApi = chainQueryApi,
nodeApi = nodeApi,
walletDAOs = walletDAOs
@ -173,6 +173,14 @@ abstract class Wallet
Future.successful(this)
}
override def getNewAddress(): Future[BitcoinAddress] = {
addressHandling.getNewAddress()
}
override def getNewChangeAddress(): Future[BitcoinAddress] = {
addressHandling.getNewChangeAddress()
}
override def getSyncDescriptorOpt(): Future[Option[SyncHeightDescriptor]] = {
stateDescriptorDAO.getSyncHeight()
}
@ -219,7 +227,7 @@ abstract class Wallet
blockFilters: Vector[(DoubleSha256DigestBE, GolombFilter)]
): Future[Wallet] = {
val utxosF = utxoHandling.listUtxos()
val spksF = listScriptPubKeys()
val spksF = addressHandling.listScriptPubKeys()
val blockHashOpt = blockFilters.lastOption.map(_._1)
val heightOptF = blockHashOpt match {
case Some(blockHash) =>
@ -411,7 +419,7 @@ abstract class Wallet
val signed = rawTxHelper.signedTx
val processedTxF = for {
ourOuts <- findOurOuts(signed)
ourOuts <- addressHandling.findOurOutputs(signed)
creditingAmount = rawTxHelper.scriptSigParams.foldLeft(
CurrencyUnits.zero
)(_ + _.amount)
@ -540,7 +548,7 @@ abstract class Wallet
.zip(prevTxs)
.map(info => info._1.toUTXOInfo(keyManager, info._2))
changeAddr <- getNewChangeAddress(fromAccount.hdAccount)
changeAddr <- accountHandling.getNewChangeAddress(fromAccount.hdAccount)
output = TransactionOutput(amount, address.scriptPubKey)
txBuilder = ShufflingNonInteractiveFinalizer.txBuilderFrom(
@ -862,7 +870,7 @@ abstract class Wallet
Random.shuffle(spendingInfos).head
}
addr <- getNewChangeAddress()
addr <- addressHandling.getNewChangeAddress()
childTx <- sendFromOutPoints(Vector(spendingInfo.outPoint), addr, feeRate)
} yield childTx
}
@ -942,62 +950,6 @@ abstract class Wallet
}
}
protected def getLastAccountOpt(
purpose: HDPurpose
): Future[Option[AccountDb]] = {
accountDAO
.findAll()
.map(_.filter(_.hdAccount.purpose == purpose))
.map(_.sortBy(_.hdAccount.index))
// we want to the most recently created account,
// to know what the index of our new account
// should be.
.map(_.lastOption)
}
/** Creates a new account my reading from our account database, finding the
* last account, and then incrementing the account index by one, and then
* creating that account
*
* @return
*/
override def createNewAccount(purpose: HDPurpose): Future[Wallet] = {
getLastAccountOpt(purpose).flatMap {
case Some(accountDb) =>
val hdAccount = accountDb.hdAccount
val newAccount = hdAccount.copy(index = hdAccount.index + 1)
createNewAccount(newAccount)
case None =>
createNewAccount(walletConfig.defaultAccount)
}
}
// todo: check if there's addresses in the most recent
// account before creating new
override def createNewAccount(
hdAccount: HDAccount
): Future[Wallet] = {
logger.info(
s"Creating new account at index ${hdAccount.index} for purpose ${hdAccount.purpose}"
)
val xpub: ExtPublicKey = {
keyManager.deriveXPub(hdAccount) match {
case Failure(exception) =>
// this won't happen, because we're deriving from a privkey
// this should really be changed in the method signature
logger.error(s"Unexpected error when deriving xpub: $exception")
throw exception
case Success(xpub) => xpub
}
}
val newAccountDb = AccountDb(xpub, hdAccount)
accountDAO.create(newAccountDb).map { created =>
logger.debug(s"Created new account ${created.hdAccount}")
this
}
}
override def getWalletName(): Future[String] = {
Future.successful(walletConfig.walletName)
}

View File

@ -13,11 +13,10 @@ import org.bitcoins.core.api.keymanager.BIP39KeyManagerApi
import org.bitcoins.core.api.node.NodeApi
import org.bitcoins.core.api.wallet.*
import org.bitcoins.core.api.wallet.db.*
import org.bitcoins.core.config.NetworkParameters
import org.bitcoins.core.currency.{CurrencyUnit, Satoshis}
import org.bitcoins.core.dlc.accounting.DLCWalletAccounting
import org.bitcoins.core.gcs.GolombFilter
import org.bitcoins.core.hd.{AddressType, HDAccount, HDChainType, HDPurpose}
import org.bitcoins.core.hd.{AddressType, HDAccount, HDPurpose}
import org.bitcoins.core.number.UInt32
import org.bitcoins.core.protocol.blockchain.Block
import org.bitcoins.core.protocol.dlc.models.*
@ -35,12 +34,7 @@ import org.bitcoins.core.wallet.builder.{
ShufflingNonInteractiveFinalizer
}
import org.bitcoins.core.wallet.fee.{FeeUnit, SatoshisPerVirtualByte}
import org.bitcoins.core.wallet.utxo.{
AddressTag,
AddressTagName,
AddressTagType,
TxoState
}
import org.bitcoins.core.wallet.utxo.{AddressTag, TxoState}
import org.bitcoins.crypto.{DoubleSha256DigestBE, Sha256Digest}
import scodec.bits.ByteVector
@ -72,6 +66,8 @@ class WalletHolder(initWalletOpt: Option[DLCNeutrinoHDWalletApi])(implicit
override def fundTxHandling: FundTransactionHandlingApi =
wallet.fundTxHandling
override def addressHandling: AddressHandlingApi = wallet.addressHandling
def isInitialized: Boolean = synchronized {
walletOpt.isDefined
}
@ -107,6 +103,11 @@ class WalletHolder(initWalletOpt: Option[DLCNeutrinoHDWalletApi])(implicit
Future(wallet).flatMap[T](_)
}
override def getNewAddress(): Future[BitcoinAddress] = delegate(
_.getNewAddress())
override def getNewChangeAddress(): Future[BitcoinAddress] = delegate(
_.getNewChangeAddress())
override def processBlock(block: Block): Future[Unit] =
delegate(_.processBlock(block))
@ -214,31 +215,6 @@ class WalletHolder(initWalletOpt: Option[DLCNeutrinoHDWalletApi])(implicit
delegate(_.listUtxos(tag))
}
override def listAddresses(): Future[Vector[AddressDb]] = delegate(
_.listAddresses()
)
override def listSpentAddresses(): Future[Vector[AddressDb]] = delegate(
_.listSpentAddresses()
)
override def listFundedAddresses()
: Future[Vector[(AddressDb, CurrencyUnit)]] = delegate(
_.listFundedAddresses()
)
override def listUnusedAddresses(): Future[Vector[AddressDb]] = delegate(
_.listUnusedAddresses()
)
override def listScriptPubKeys(): Future[Vector[ScriptPubKeyDb]] = delegate(
_.listScriptPubKeys()
)
override def watchScriptPubKey(
scriptPubKey: ScriptPubKey
): Future[ScriptPubKeyDb] = delegate(_.watchScriptPubKey(scriptPubKey))
override def markUTXOsAsReserved(
utxos: Vector[SpendingInfoDb]
): Future[Vector[SpendingInfoDb]] = delegate(_.markUTXOsAsReserved(utxos))
@ -257,75 +233,6 @@ class WalletHolder(initWalletOpt: Option[DLCNeutrinoHDWalletApi])(implicit
override def isEmpty(): Future[Boolean] = delegate(_.isEmpty())
override def getNewAddress(addressType: AddressType): Future[BitcoinAddress] =
delegate(_.getNewAddress(addressType))
override def getNewAddress(account: HDAccount): Future[BitcoinAddress] = {
delegate(_.getNewAddress(account))
}
override def getNewAddress(): Future[BitcoinAddress] = delegate(
_.getNewAddress()
)
override def getNewAddress(
addressType: AddressType,
tags: Vector[AddressTag]
): Future[BitcoinAddress] = delegate(_.getNewAddress(addressType, tags))
override def getNewAddress(tags: Vector[AddressTag]): Future[BitcoinAddress] =
delegate(_.getNewAddress(tags))
override def getUnusedAddress(
addressType: AddressType
): Future[BitcoinAddress] = delegate(_.getUnusedAddress(addressType))
override def getUnusedAddress: Future[BitcoinAddress] = delegate(
_.getUnusedAddress
)
override def getAddressInfo(
address: BitcoinAddress
): Future[Option[AddressInfo]] = delegate(_.getAddressInfo(address))
override def tagAddress(
address: BitcoinAddress,
tag: AddressTag
): Future[AddressTagDb] = delegate(_.tagAddress(address, tag))
override def getAddressTags(
address: BitcoinAddress
): Future[Vector[AddressTagDb]] = delegate(_.getAddressTags(address))
override def getAddressTags(
address: BitcoinAddress,
tagType: AddressTagType
): Future[Vector[AddressTagDb]] = delegate(_.getAddressTags(address, tagType))
override def getAddressTags(): Future[Vector[AddressTagDb]] = delegate(
_.getAddressTags()
)
override def getAddressTags(
tagType: AddressTagType
): Future[Vector[AddressTagDb]] = delegate(_.getAddressTags(tagType))
override def dropAddressTag(addressTagDb: AddressTagDb): Future[Int] =
delegate(_.dropAddressTag(addressTagDb))
override def dropAddressTagType(addressTagType: AddressTagType): Future[Int] =
delegate(_.dropAddressTagType(addressTagType))
override def dropAddressTagType(
address: BitcoinAddress,
addressTagType: AddressTagType
): Future[Int] = delegate(_.dropAddressTagType(address, addressTagType))
override def dropAddressTagName(
address: BitcoinAddress,
tagName: AddressTagName
): Future[Int] = delegate(_.dropAddressTagName(address, tagName))
override def sendFromOutPoints(
outPoints: Vector[TransactionOutPoint],
address: BitcoinAddress,
@ -528,9 +435,6 @@ class WalletHolder(initWalletOpt: Option[DLCNeutrinoHDWalletApi])(implicit
override def getUnconfirmedBalance(account: HDAccount): Future[CurrencyUnit] =
delegate(_.getUnconfirmedBalance(account))
override def getNewChangeAddress(account: AccountDb): Future[BitcoinAddress] =
delegate(_.getNewChangeAddress(account))
override def getDefaultAccount(): Future[AccountDb] = delegate(
_.getDefaultAccount()
)
@ -610,23 +514,6 @@ class WalletHolder(initWalletOpt: Option[DLCNeutrinoHDWalletApi])(implicit
_.makeOpReturnCommitment(message, hashMessage, feeRate, fromAccount)
)
override def listAddresses(account: HDAccount): Future[Vector[AddressDb]] =
delegate(_.listAddresses(account))
override def listSpentAddresses(
account: HDAccount
): Future[Vector[AddressDb]] = delegate(_.listSpentAddresses(account))
override def listFundedAddresses(
account: HDAccount
): Future[Vector[(AddressDb, CurrencyUnit)]] = delegate(
_.listFundedAddresses(account)
)
override def listUnusedAddresses(
account: HDAccount
): Future[Vector[AddressDb]] = delegate(_.listUnusedAddresses(account))
override def clearAllUtxos(): Future[HDWalletApi] = delegate(
_.clearAllUtxos()
)
@ -635,23 +522,6 @@ class WalletHolder(initWalletOpt: Option[DLCNeutrinoHDWalletApi])(implicit
delegate(_.clearAllAddresses())
}
override def getAddress(
account: AccountDb,
chainType: HDChainType,
addressIndex: Int
): Future[AddressDb] = delegate(
_.getAddress(account, chainType, addressIndex)
)
override def createNewAccount(
purpose: HDPurpose
): Future[HDWalletApi] = delegate(_.createNewAccount(purpose))
override def createNewAccount(hdAccount: HDAccount): Future[HDWalletApi] =
delegate(
_.createNewAccount(hdAccount)
)
override def getSyncDescriptorOpt(): Future[Option[SyncHeightDescriptor]] =
delegate(_.getSyncDescriptorOpt())
@ -676,12 +546,6 @@ class WalletHolder(initWalletOpt: Option[DLCNeutrinoHDWalletApi])(implicit
ec: ExecutionContext
): Future[CurrencyUnit] = delegate(_.getBalance(tag))
override def getAddressInfo(
spendingInfoDb: SpendingInfoDb,
networkParameters: NetworkParameters
): Future[Option[AddressInfo]] =
delegate(_.getAddressInfo(spendingInfoDb, networkParameters))
override def sendFromOutPoints(
outPoints: Vector[TransactionOutPoint],
address: BitcoinAddress,
@ -760,11 +624,6 @@ class WalletHolder(initWalletOpt: Option[DLCNeutrinoHDWalletApi])(implicit
): Future[NeutrinoHDWalletApi] =
delegate(_.processCompactFilter(blockHash, blockFilter))
override def getNewChangeAddress()(implicit
ec: ExecutionContext
): Future[BitcoinAddress] =
delegate(_.getNewChangeAddress())
override def sendWithAlgo(
address: BitcoinAddress,
amount: CurrencyUnit,
@ -945,11 +804,6 @@ class WalletHolder(initWalletOpt: Option[DLCNeutrinoHDWalletApi])(implicit
)(implicit ec: ExecutionContext): Future[Transaction] =
delegate(_.makeOpReturnCommitment(message, hashMessage, feeRate))
override def getAddress(chainType: HDChainType, addressIndex: Int)(implicit
ec: ExecutionContext
): Future[AddressDb] =
delegate(_.getAddress(chainType, addressIndex))
override def listAccounts(purpose: HDPurpose)(implicit
ec: ExecutionContext
): Future[Vector[AccountDb]] =
@ -1041,14 +895,6 @@ class WalletHolder(initWalletOpt: Option[DLCNeutrinoHDWalletApi])(implicit
delegate(_.findOutputsBeingSpent(tx))
}
override def findAccount(account: HDAccount): Future[Option[AccountDb]] = {
delegate(_.findAccount(account))
}
override def getNewAddress(account: AccountDb): Future[BitcoinAddress] = {
delegate(_.getNewAddress(account))
}
override def findByScriptPubKey(
scriptPubKey: ScriptPubKey
): Future[Vector[SpendingInfoDb]] = {

View File

@ -1,8 +1,12 @@
package org.bitcoins.wallet.internal
import org.bitcoins.commons.util.BitcoinSLogger
import org.bitcoins.core.api.keymanager.BIP39KeyManagerApi
import org.bitcoins.core.api.wallet.AccountHandlingApi
import org.bitcoins.core.api.wallet.db.AccountDb
import org.bitcoins.core.api.wallet.db.{AccountDb, AddressDb, AddressDbHelper}
import org.bitcoins.core.config.NetworkParameters
import org.bitcoins.core.crypto.ExtPublicKey
import org.bitcoins.core.currency.CurrencyUnit
import org.bitcoins.core.hd.AddressType.*
import org.bitcoins.core.hd.*
import org.bitcoins.core.protocol.BitcoinAddress
@ -15,6 +19,7 @@ import org.bitcoins.core.protocol.blockchain.{
}
import org.bitcoins.core.protocol.script.ScriptPubKey
import org.bitcoins.db.SafeDatabase
import org.bitcoins.wallet.callback.WalletCallbacks
import org.bitcoins.wallet.config.WalletAppConfig
import org.bitcoins.wallet.models.{
AccountDAO,
@ -26,13 +31,14 @@ import org.bitcoins.wallet.models.{
import slick.dbio.{DBIOAction, Effect, NoStream}
import scala.concurrent.{ExecutionContext, Future}
import scala.util.{Failure, Success}
/** Provides functionality related enumerating accounts. Account creation does
* not happen here, as that requires an unlocked wallet.
*/
case class AccountHandling(
addressHandling: AddressHandling,
walletDAOs: WalletDAOs)(implicit
walletDAOs: WalletDAOs,
keyManager: BIP39KeyManagerApi)(implicit
walletConfig: WalletAppConfig,
ec: ExecutionContext)
extends AccountHandlingApi
@ -43,6 +49,49 @@ case class AccountHandling(
private val scriptPubKeyDAO: ScriptPubKeyDAO = walletDAOs.scriptPubKeyDAO
private val safeDatabase: SafeDatabase = spendingInfoDAO.safeDatabase
private val chainParams: ChainParams = walletConfig.chain
private val networkParameters: NetworkParameters = walletConfig.network
private def walletCallbacks: WalletCallbacks = walletConfig.callBacks
override def createNewAccount(
hdAccount: HDAccount
): Future[ExtPublicKey] = {
logger.info(
s"Creating new account at index ${hdAccount.index} for purpose ${hdAccount.purpose}"
)
val xpub: ExtPublicKey = {
keyManager.deriveXPub(hdAccount) match {
case Failure(exception) =>
// this won't happen, because we're deriving from a privkey
// this should really be changed in the method signature
logger.error(s"Unexpected error when deriving xpub: $exception")
throw exception
case Success(xpub) => xpub
}
}
val newAccountDb = AccountDb(xpub, hdAccount)
accountDAO.create(newAccountDb).map { created =>
logger.debug(s"Created new account ${created.hdAccount}")
created.xpub
}
}
/** Creates a new account my reading from our account database, finding the
* last account, and then incrementing the account index by one, and then
* creating that account
*
* @return
*/
override def createNewAccount(purpose: HDPurpose): Future[ExtPublicKey] = {
getLastAccountOpt(purpose).flatMap {
case Some(accountDb) =>
val hdAccount = accountDb.hdAccount
val newAccount = hdAccount.copy(index = hdAccount.index + 1)
createNewAccount(newAccount)
case None =>
createNewAccount(walletConfig.defaultAccount)
}
}
/** @inheritdoc */
override def listAccounts(): Future[Vector[AccountDb]] =
@ -168,7 +217,7 @@ case class AccountHandling(
Effect.Read with Effect.Write with Effect.Transactional] = {
DBIOAction.sequence {
1.to(addressBatchSize)
.map(_ => addressHandling.getNewAddressAction(account))
.map(_ => getNewAddressAction(account))
}
}.map(_.toVector)
@ -180,7 +229,7 @@ case class AccountHandling(
Effect.Read with Effect.Write with Effect.Transactional] = {
DBIOAction.sequence {
1.to(addressBatchSize)
.map(_ => addressHandling.getNewChangeAddressAction(account))
.map(_ => getNewChangeAddressAction(account))
}
}.map(_.toVector)
@ -190,6 +239,247 @@ case class AccountHandling(
} yield receiveAddresses ++ changeAddresses
}
override def getNewChangeAddress(
account: AccountDb): Future[BitcoinAddress] = {
val action = getNewChangeAddressAction(account)
safeDatabase.run(action)
}
/** Queues a request to generate an address and returns a Future that will be
* completed when the request is processed in the queue. If the queue is full
* it throws an exception.
* @throws IllegalStateException
*/
override def getNewAddress(
account: AccountDb,
chainType: HDChainType
): Future[BitcoinAddress] = {
val action = getNewAddressHelperAction(account, chainType)
safeDatabase.run(action)
}
def getNewAddressAction(account: HDAccount): DBIOAction[
BitcoinAddress,
NoStream,
Effect.Read with Effect.Write with Effect.Transactional
] = {
val accountDbOptA = findAccountAction(account)
accountDbOptA.flatMap {
case Some(accountDb) => getNewAddressAction(accountDb)
case None =>
DBIOAction.failed(
new RuntimeException(
s"No account found for given hdaccount=${account}"
)
)
}
}
def getNewChangeAddressAction(account: HDAccount): DBIOAction[
BitcoinAddress,
NoStream,
Effect.Read with Effect.Write with Effect.Transactional
] = {
val accountDbOptA = findAccountAction(account)
accountDbOptA.flatMap {
case Some(accountDb) => getNewChangeAddressAction(accountDb)
case None =>
DBIOAction.failed(
new RuntimeException(
s"No account found for given hdaccount=${account}"
)
)
}
}
override def getNewAddress(account: AccountDb): Future[BitcoinAddress] = {
val action = getNewAddressAction(account)
safeDatabase.run(action)
}
def getNewAddressAction(account: AccountDb): DBIOAction[
BitcoinAddress,
NoStream,
Effect.Read with Effect.Write with Effect.Transactional
] = {
getNewAddressHelperAction(account, HDChainType.External)
}
def getNewChangeAddressAction(account: AccountDb): DBIOAction[
BitcoinAddress,
NoStream,
Effect.Read with Effect.Write with Effect.Transactional
] = {
getNewAddressHelperAction(account, HDChainType.Change)
}
private def findAccountAction(
account: HDAccount
): DBIOAction[Option[AccountDb], NoStream, Effect.Read] = {
accountDAO.findByAccountAction(account)
}
override def findAccount(account: HDAccount): Future[Option[AccountDb]] = {
safeDatabase.run(findAccountAction(account))
}
override def listUnusedAddresses(
account: HDAccount): Future[Vector[AddressDb]] = {
val unusedAddressesF = addressDAO.getUnusedAddresses
unusedAddressesF.map { unusedAddresses =>
unusedAddresses.filter(addr =>
HDAccount.isSameAccount(addr.path, account))
}
}
override def listAddresses(account: HDAccount): Future[Vector[AddressDb]] = {
val allAddressesF: Future[Vector[AddressDb]] = addressDAO.findAllAddresses()
val accountAddressesF = {
allAddressesF.map { addresses =>
addresses.filter { a =>
logger.debug(s"a.path=${a.path} account=${account}")
HDAccount.isSameAccount(a.path, account)
}
}
}
accountAddressesF
}
override def listSpentAddresses(
account: HDAccount
): Future[Vector[AddressDb]] = {
val spentAddressesF = addressDAO.getSpentAddresses
spentAddressesF.map { spentAddresses =>
spentAddresses.filter(addr => HDAccount.isSameAccount(addr.path, account))
}
}
override def listFundedAddresses(
account: HDAccount
): Future[Vector[(AddressDb, CurrencyUnit)]] = {
val spentAddressesF = addressDAO.getFundedAddresses
spentAddressesF.map { spentAddresses =>
spentAddresses.filter(addr =>
HDAccount.isSameAccount(addr._1.path, account))
}
}
private def getNewAddressHelperAction(
account: AccountDb,
chainType: HDChainType
): DBIOAction[
BitcoinAddress,
NoStream,
Effect.Read with Effect.Write with Effect.Transactional
] = {
logger.debug(s"Processing $account $chainType in our address request queue")
val resultA: DBIOAction[
BitcoinAddress,
NoStream,
Effect.Read with Effect.Write with Effect.Transactional
] = for {
addressDb <- getNewAddressDbAction(account, chainType)
writtenAddressDb <- addressDAO.createAction(addressDb)
} yield {
logger.info(
s"Generated new address=${addressDb.address} path=${addressDb.path} isChange=${addressDb.isChange}"
)
writtenAddressDb.address
}
val callbackExecuted = resultA.flatMap { address =>
val executedF =
walletCallbacks.executeOnNewAddressGenerated(address)
DBIOAction
.from(executedF)
.map(_ => address)
}
callbackExecuted
}
private def getNewAddressDbAction(
account: AccountDb,
chainType: HDChainType
): DBIOAction[AddressDb, NoStream, Effect.Read] = {
logger.debug(s"Getting new $chainType adddress for ${account.hdAccount}")
val lastAddrOptA = chainType match {
case HDChainType.External =>
addressDAO.findMostRecentExternalAction(account.hdAccount)
case HDChainType.Change =>
addressDAO.findMostRecentChangeAction(account.hdAccount)
}
lastAddrOptA.map { lastAddrOpt =>
val addrPath: HDPath = lastAddrOpt match {
case Some(addr) =>
val next = addr.path.next
logger.debug(
s"Found previous address at path=${addr.path}, next=$next"
)
next
case None =>
val address = account.hdAccount
.toChain(chainType)
.toHDAddress(0)
val path = address.toPath
logger.debug(s"Did not find previous address, next=$path")
path
}
val pathDiff =
account.hdAccount.diff(addrPath) match {
case Some(value) => value
case None =>
throw new RuntimeException(
s"Could not diff ${account.hdAccount} and $addrPath"
)
}
val pubkey = account.xpub.deriveChildPubKey(pathDiff) match {
case Failure(exception) => throw exception
case Success(value) => value.key
}
addrPath match {
case segwitPath: SegWitHDPath =>
AddressDbHelper
.getSegwitAddress(pubkey, segwitPath, networkParameters)
case legacyPath: LegacyHDPath =>
AddressDbHelper.getLegacyAddress(
pubkey,
legacyPath,
networkParameters
)
case nestedPath: NestedSegWitHDPath =>
AddressDbHelper.getNestedSegwitAddress(
pubkey,
nestedPath,
networkParameters
)
}
}
}
protected def getLastAccountOpt(
purpose: HDPurpose
): Future[Option[AccountDb]] = {
accountDAO
.findAll()
.map(_.filter(_.hdAccount.purpose == purpose))
.map(_.sortBy(_.hdAccount.index))
// we want to the most recently created account,
// to know what the index of our new account
// should be.
.map(_.lastOption)
}
/** The default HD coin for this wallet, read from config */
protected[wallet] lazy val DEFAULT_HD_COIN: HDCoin = {
val coinType = DEFAULT_HD_COIN_TYPE

View File

@ -1,10 +1,15 @@
package org.bitcoins.wallet.internal
import org.bitcoins.core.api.wallet
import org.bitcoins.core.api.wallet.AddressInfo
import org.bitcoins.core.api.wallet.db._
import org.bitcoins.core.api.wallet.{
AccountHandlingApi,
AddressHandlingApi,
AddressInfo
}
import org.bitcoins.core.api.wallet.db.*
import org.bitcoins.core.config.NetworkParameters
import org.bitcoins.core.currency.CurrencyUnit
import org.bitcoins.core.hd._
import org.bitcoins.core.hd.*
import org.bitcoins.core.number.UInt32
import org.bitcoins.core.protocol.BitcoinAddress
import org.bitcoins.core.protocol.script.ScriptPubKey
@ -18,26 +23,40 @@ import org.bitcoins.core.wallet.utxo.{
AddressTagName,
AddressTagType
}
import org.bitcoins.crypto.ECPublicKey
import org.bitcoins.wallet._
import slick.dbio.{DBIOAction, Effect, NoStream}
import org.bitcoins.wallet.*
import org.bitcoins.wallet.config.WalletAppConfig
import org.bitcoins.wallet.models.{
AddressDAO,
AddressTagDAO,
ScriptPubKeyDAO,
WalletDAOs
}
import scala.concurrent.Future
import scala.concurrent.{ExecutionContext, Future}
import scala.util.{Failure, Success}
/** Provides functionality related to addresses. This includes enumeratng and
* creating them, primarily.
*/
private[wallet] trait AddressHandling extends WalletLogger {
self: Wallet =>
case class AddressHandling(
accountHandling: AccountHandlingApi,
walletDAOs: WalletDAOs)(implicit
walletConfig: WalletAppConfig,
ec: ExecutionContext)
extends AddressHandlingApi
with WalletLogger {
private val addressDAO: AddressDAO = walletDAOs.addressDAO
private val addressTagDAO: AddressTagDAO = walletDAOs.addressTagDAO
private val scriptPubKeyDAO: ScriptPubKeyDAO = walletDAOs.scriptPubKeyDAO
private val networkParameters: NetworkParameters = walletConfig.network
def contains(
address: BitcoinAddress,
accountOpt: Option[HDAccount]
accountOpt: Option[(AccountHandlingApi, HDAccount)]
): Future[Boolean] = {
val possibleAddressesF = accountOpt match {
case Some(account) =>
listAddresses(account)
case Some((ah, account)) =>
ah.listAddresses(account)
case None =>
listAddresses()
}
@ -50,66 +69,19 @@ private[wallet] trait AddressHandling extends WalletLogger {
override def listAddresses(): Future[Vector[AddressDb]] =
addressDAO.findAllAddresses()
override def listAddresses(account: HDAccount): Future[Vector[AddressDb]] = {
val allAddressesF: Future[Vector[AddressDb]] = listAddresses()
val accountAddressesF = {
allAddressesF.map { addresses =>
addresses.filter { a =>
logger.info(s"a.path=${a.path} account=${account}")
HDAccount.isSameAccount(a.path, account)
}
}
}
accountAddressesF
}
override def listSpentAddresses(): Future[Vector[AddressDb]] = {
addressDAO.getSpentAddresses
}
override def listSpentAddresses(
account: HDAccount
): Future[Vector[AddressDb]] = {
val spentAddressesF = addressDAO.getSpentAddresses
spentAddressesF.map { spentAddresses =>
spentAddresses.filter(addr => HDAccount.isSameAccount(addr.path, account))
}
}
override def listFundedAddresses()
: Future[Vector[(AddressDb, CurrencyUnit)]] = {
addressDAO.getFundedAddresses
}
override def listFundedAddresses(
account: HDAccount
): Future[Vector[(AddressDb, CurrencyUnit)]] = {
val spentAddressesF = addressDAO.getFundedAddresses
spentAddressesF.map { spentAddresses =>
spentAddresses.filter(addr =>
HDAccount.isSameAccount(addr._1.path, account))
}
}
override def listUnusedAddresses(): Future[Vector[AddressDb]] = {
addressDAO.getUnusedAddresses
}
override def listUnusedAddresses(
account: HDAccount
): Future[Vector[AddressDb]] = {
val unusedAddressesF = addressDAO.getUnusedAddresses
unusedAddressesF.map { unusedAddresses =>
unusedAddresses.filter(addr =>
HDAccount.isSameAccount(addr.path, account))
}
}
override def listScriptPubKeys(): Future[Vector[ScriptPubKeyDb]] =
scriptPubKeyDAO.findAll()
@ -118,92 +90,20 @@ private[wallet] trait AddressHandling extends WalletLogger {
): Future[ScriptPubKeyDb] =
scriptPubKeyDAO.createIfNotExists(ScriptPubKeyDb(scriptPubKey))
/** Enumerates the public keys in this wallet */
protected[wallet] def listPubkeys(): Future[Vector[ECPublicKey]] =
addressDAO.findAllPubkeys()
/** Enumerates the scriptPubKeys in this wallet */
protected[wallet] def listSPKs(): Future[Vector[ScriptPubKey]] =
addressDAO.findAllSPKs()
/** Given a transaction, returns the outputs (with their corresponding
* outpoints) that pay to this wallet
*/
def findOurOuts(
def findOurOutputs(
transaction: Transaction
): Future[Vector[(TransactionOutput, TransactionOutPoint)]] =
for {
spks <- listSPKs()
spks <- listScriptPubKeys()
} yield transaction.outputs.zipWithIndex.collect {
case (out, index) if spks.contains(out.scriptPubKey) =>
case (out, index)
if spks.map(_.scriptPubKey).contains(out.scriptPubKey) =>
(out, TransactionOutPoint(transaction.txId, UInt32(index)))
}.toVector
private def getNewAddressDbAction(
account: AccountDb,
chainType: HDChainType
): DBIOAction[AddressDb, NoStream, Effect.Read] = {
logger.debug(s"Getting new $chainType adddress for ${account.hdAccount}")
val lastAddrOptA = chainType match {
case HDChainType.External =>
addressDAO.findMostRecentExternalAction(account.hdAccount)
case HDChainType.Change =>
addressDAO.findMostRecentChangeAction(account.hdAccount)
}
lastAddrOptA.map { lastAddrOpt =>
val addrPath: HDPath = lastAddrOpt match {
case Some(addr) =>
val next = addr.path.next
logger.debug(
s"Found previous address at path=${addr.path}, next=$next"
)
next
case None =>
val address = account.hdAccount
.toChain(chainType)
.toHDAddress(0)
val path = address.toPath
logger.debug(s"Did not find previous address, next=$path")
path
}
val pathDiff =
account.hdAccount.diff(addrPath) match {
case Some(value) => value
case None =>
throw new RuntimeException(
s"Could not diff ${account.hdAccount} and $addrPath"
)
}
val pubkey = account.xpub.deriveChildPubKey(pathDiff) match {
case Failure(exception) => throw exception
case Success(value) => value.key
}
addrPath match {
case segwitPath: SegWitHDPath =>
AddressDbHelper
.getSegwitAddress(pubkey, segwitPath, networkParameters)
case legacyPath: LegacyHDPath =>
AddressDbHelper.getLegacyAddress(
pubkey,
legacyPath,
networkParameters
)
case nestedPath: NestedSegWitHDPath =>
AddressDbHelper.getNestedSegwitAddress(
pubkey,
nestedPath,
networkParameters
)
}
}
}
/** Derives a new address in the wallet for the given account and chain type
* (change/external). After deriving the address it inserts it into our table
* of addresses.
@ -220,55 +120,10 @@ private[wallet] trait AddressHandling extends WalletLogger {
account: AccountDb,
chainType: HDChainType
): Future[AddressDb] = {
val action = getNewAddressDbAction(account, chainType)
safeDatabase.run(action)
}
private def getNewAddressHelperAction(
account: AccountDb,
chainType: HDChainType
): DBIOAction[
BitcoinAddress,
NoStream,
Effect.Read with Effect.Write with Effect.Transactional
] = {
logger.debug(s"Processing $account $chainType in our address request queue")
val resultA: DBIOAction[
BitcoinAddress,
NoStream,
Effect.Read with Effect.Write with Effect.Transactional
] = for {
addressDb <- getNewAddressDbAction(account, chainType)
writtenAddressDb <- addressDAO.createAction(addressDb)
} yield {
logger.info(
s"Generated new address=${addressDb.address} path=${addressDb.path} isChange=${addressDb.isChange}"
)
writtenAddressDb.address
}
val callbackExecuted = resultA.flatMap { address =>
val executedF =
walletCallbacks.executeOnNewAddressGenerated(address)
DBIOAction
.from(executedF)
.map(_ => address)
}
callbackExecuted
}
/** Queues a request to generate an address and returns a Future that will be
* completed when the request is processed in the queue. If the queue is full
* it throws an exception.
* @throws IllegalStateException
*/
private def getNewAddressHelper(
account: AccountDb,
chainType: HDChainType
): Future[BitcoinAddress] = {
val action = getNewAddressHelperAction(account, chainType)
safeDatabase.run(action)
accountHandling
.getNewAddress(account, chainType)
.flatMap(addr => addressDAO.findAddress(addr))
.map(_.get)
}
def getNextAvailableIndex(
@ -278,73 +133,6 @@ private[wallet] trait AddressHandling extends WalletLogger {
getNewAddressDb(accountDb, chainType).map(_.path.path.last.index)
}
def getNewAddressAction(account: HDAccount): DBIOAction[
BitcoinAddress,
NoStream,
Effect.Read with Effect.Write with Effect.Transactional
] = {
val accountDbOptA = findAccountAction(account)
accountDbOptA.flatMap {
case Some(accountDb) => getNewAddressAction(accountDb)
case None =>
DBIOAction.failed(
new RuntimeException(
s"No account found for given hdaccount=${account}"
)
)
}
}
def getNewChangeAddressAction(account: HDAccount): DBIOAction[
BitcoinAddress,
NoStream,
Effect.Read with Effect.Write with Effect.Transactional
] = {
val accountDbOptA = findAccountAction(account)
accountDbOptA.flatMap {
case Some(accountDb) => getNewChangeAddressAction(accountDb)
case None =>
DBIOAction.failed(
new RuntimeException(
s"No account found for given hdaccount=${account}"
)
)
}
}
def getNewAddress(account: HDAccount): Future[BitcoinAddress] = {
val accountDbOptF = findAccount(account)
accountDbOptF.flatMap {
case Some(accountDb) => getNewAddress(accountDb)
case None =>
Future.failed(
new RuntimeException(
s"No account found for given hdaccount=${account}"
)
)
}
}
def getNewAddressAction(account: AccountDb): DBIOAction[
BitcoinAddress,
NoStream,
Effect.Read with Effect.Write with Effect.Transactional
] = {
getNewAddressHelperAction(account, HDChainType.External)
}
def getNewChangeAddressAction(account: AccountDb): DBIOAction[
BitcoinAddress,
NoStream,
Effect.Read with Effect.Write with Effect.Transactional
] = {
getNewAddressHelperAction(account, HDChainType.Change)
}
def getNewAddress(account: AccountDb): Future[BitcoinAddress] = {
safeDatabase.run(getNewAddressAction(account))
}
/** @inheritdoc */
override def getNewAddress(): Future[BitcoinAddress] = {
getNewAddress(walletConfig.defaultAddressType)
@ -357,6 +145,13 @@ private[wallet] trait AddressHandling extends WalletLogger {
getNewAddress(walletConfig.defaultAddressType, tags)
}
override def getNewChangeAddress(): Future[BitcoinAddress] = {
for {
account <- accountHandling.getDefaultAccount()
addr <- accountHandling.getNewChangeAddress(account)
} yield addr
}
/** @inheritdoc */
def getAddress(
account: AccountDb,
@ -434,11 +229,11 @@ private[wallet] trait AddressHandling extends WalletLogger {
/** @inheritdoc */
def getUnusedAddress(addressType: AddressType): Future[BitcoinAddress] = {
for {
account <- getDefaultAccountForType(addressType)
account <- accountHandling.getDefaultAccountForType(addressType)
addresses <- addressDAO.getUnusedAddresses(account.hdAccount)
address <-
if (addresses.isEmpty) {
getNewAddress(account.hdAccount)
accountHandling.getNewAddress(account.hdAccount)
} else {
Future.successful(addresses.head.address)
}
@ -448,34 +243,24 @@ private[wallet] trait AddressHandling extends WalletLogger {
/** @inheritdoc */
def getUnusedAddress: Future[BitcoinAddress] = {
for {
account <- getDefaultAccount()
account <- accountHandling.getDefaultAccount()
addresses <- addressDAO.getUnusedAddresses(account.hdAccount)
address <-
if (addresses.isEmpty) {
getNewAddress(account.hdAccount)
accountHandling.getNewAddress(account.hdAccount)
} else {
Future.successful(addresses.head.address)
}
} yield address
}
def findAccountAction(
account: HDAccount
): DBIOAction[Option[AccountDb], NoStream, Effect.Read] = {
accountDAO.findByAccountAction(account)
}
override def findAccount(account: HDAccount): Future[Option[AccountDb]] = {
safeDatabase.run(findAccountAction(account))
}
/** @inheritdoc */
override def getNewAddress(
addressType: AddressType
): Future[BitcoinAddress] = {
for {
account <- getDefaultAccountForType(addressType)
address <- getNewAddressHelper(account, HDChainType.External)
account <- accountHandling.getDefaultAccountForType(addressType)
address <- accountHandling.getNewAddress(account, HDChainType.External)
} yield address
}
@ -485,32 +270,14 @@ private[wallet] trait AddressHandling extends WalletLogger {
tags: Vector[AddressTag]
): Future[BitcoinAddress] = {
for {
account <- getDefaultAccountForType(addressType)
address <- getNewAddressHelper(account, HDChainType.External)
account <- accountHandling.getDefaultAccountForType(addressType)
address <- accountHandling.getNewAddress(account, HDChainType.External)
tagDbs = tags.map(tag => AddressTagDb(address, tag))
_ <- addressTagDAO.createAll(tagDbs)
} yield address
}
/** Generates a new change address */
override def getNewChangeAddress(
account: AccountDb
): Future[BitcoinAddress] = {
getNewAddressHelper(account, HDChainType.Change)
}
def getNewChangeAddress(account: HDAccount): Future[BitcoinAddress] = {
val accountDbOptF = findAccount(account)
accountDbOptF.flatMap {
case Some(accountDb) => getNewChangeAddress(accountDb)
case None =>
Future.failed(
new RuntimeException(s"No account found for given hdaccount=$account")
)
}
}
/** @inheritdoc */
override def getAddressInfo(
address: BitcoinAddress

View File

@ -19,9 +19,9 @@ import slick.dbio.{DBIO, DBIOAction, Effect, NoStream}
import scala.concurrent.Future
case class FundTransactionHandling(
accountHandling: AccountHandlingApi,
accountHandling: AccountHandling,
utxoHandling: UtxoHandling,
addressHandling: AddressHandling,
addressHandling: AddressHandlingApi,
spendingInfoDAO: SpendingInfoDAO,
transactionDAO: TransactionDAO,
keyManager: BIP39KeyManagerApi)(implicit
@ -191,7 +191,7 @@ case class FundTransactionHandling(
for {
(selectedUtxos, callbackF) <- selectedUtxosA
change <- addressHandling.getNewChangeAddressAction(fromAccount)
change <- accountHandling.getNewChangeAddressAction(fromAccount)
utxoSpendingInfos = {
selectedUtxos.map { case (utxo, prevTx) =>
utxo.toUTXOInfo(keyManager = keyManager, prevTx)

View File

@ -12,6 +12,7 @@ import org.bitcoins.core.api.node.NodeApi
import org.bitcoins.core.api.wallet.NeutrinoWalletApi.BlockMatchingResponse
import org.bitcoins.core.api.wallet.{
AccountHandlingApi,
AddressHandlingApi,
RescanHandlingApi,
TransactionProcessingApi
}
@ -41,7 +42,7 @@ import scala.util.{Failure, Success}
case class RescanHandling(
transactionProcessing: TransactionProcessingApi,
accountHandling: AccountHandlingApi,
addressHandling: AddressHandling,
addressHandling: AddressHandlingApi,
chainQueryApi: ChainQueryApi,
nodeApi: NodeApi,
walletDAOs: WalletDAOs)(implicit