Add SendFundsHandlingApi, remove HDWalletApi (#5680)

* Begin moving methods out of HDWalletApi

* Add SendFundsHandlingApi, remove HDWalletApi

* Fix docs

* Move makeOpReturnCommitment() to SendFundsHandlingApi

* Remove MockWalletApi

* Cleanup

* Fix RoutesSpec

* Revert logback-test.xml
This commit is contained in:
Chris Stewart 2024-09-22 09:32:01 -05:00 committed by GitHub
parent 287bf984a0
commit d17934f17f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
46 changed files with 1325 additions and 1707 deletions

View file

@ -2,6 +2,7 @@ package org.bitcoins.server
import org.apache.pekko.http.scaladsl.model.ContentTypes
import org.apache.pekko.http.scaladsl.testkit.ScalatestRouteTest
import org.bitcoins.core.api.dlc.wallet.DLCNeutrinoHDWalletApi
import org.bitcoins.core.api.dlc.wallet.db.DLCContactDb
import org.bitcoins.core.currency.{Bitcoins, Satoshis}
import org.bitcoins.core.protocol.dlc.models.ContractInfo
@ -11,7 +12,6 @@ import org.bitcoins.crypto.Sha256Digest
import org.bitcoins.dlc.node.DLCNode
import org.bitcoins.server.routes.ServerCommand
import org.bitcoins.testkit.BitcoinSTestAppConfig
import org.bitcoins.wallet.MockWalletApi
import org.scalamock.scalatest.MockFactory
import org.scalatest.wordspec.AnyWordSpec
@ -26,7 +26,7 @@ class DLCRoutesSpec
implicit val conf: BitcoinSAppConfig =
BitcoinSTestAppConfig.getNeutrinoTestConfig()
val mockWallet = mock[MockWalletApi]
val mockWallet = mock[DLCNeutrinoHDWalletApi]
val mockNodeApi = DLCNode(mockWallet)(system, conf.dlcNodeConf)

View file

@ -8,13 +8,17 @@ import org.apache.pekko.http.scaladsl.testkit.{
}
import org.bitcoins.core.api.chain.ChainApi
import org.bitcoins.core.api.chain.db.*
import org.bitcoins.core.api.dlc.wallet.DLCNeutrinoHDWalletApi
import org.bitcoins.core.api.wallet.db.*
import org.bitcoins.core.api.wallet.{
AccountHandlingApi,
AddressHandlingApi,
AddressInfo,
CoinSelectionAlgo,
RescanHandlingApi
FundTransactionHandlingApi,
RescanHandlingApi,
SendFundsHandlingApi,
UtxoHandlingApi
}
import org.bitcoins.core.config.RegTest
import org.bitcoins.core.crypto.ExtPublicKey
@ -49,7 +53,7 @@ import org.bitcoins.server.routes.{CommonRoutes, ServerCommand}
import org.bitcoins.testkit.BitcoinSTestAppConfig
import org.bitcoins.testkit.wallet.DLCWalletUtil
import org.bitcoins.testkitcore.util.TransactionTestUtil
import org.bitcoins.wallet.{MockWalletApi, WalletHolder}
import org.bitcoins.wallet.WalletHolder
import org.scalamock.scalatest.MockFactory
import org.scalatest.wordspec.AnyWordSpec
import scodec.bits.ByteVector
@ -73,6 +77,17 @@ class RoutesSpec extends AnyWordSpec with ScalatestRouteTest with MockFactory {
val testAddress: BitcoinAddress = BitcoinAddress.fromString(testAddressStr)
val testLabel: AddressLabelTag = AddressLabelTag("test")
val xpub = ExtPublicKey
.fromString(
"xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8"
)
val accountDb =
AccountDb(
xpub = xpub,
hdAccount = HDAccount(HDCoin(HDPurpose.Legacy, HDCoinType.Testnet), 0)
)
val mockChainApi: ChainApi = mock[ChainApi]
val mockNode: Node = mock[Node]
@ -81,7 +96,7 @@ class RoutesSpec extends AnyWordSpec with ScalatestRouteTest with MockFactory {
val nodeRoutes: NodeRoutes = NodeRoutes(mockNode)
val mockWalletApi: MockWalletApi = mock[MockWalletApi]
val mockWalletApi: DLCNeutrinoHDWalletApi = mock[DLCNeutrinoHDWalletApi]
val mockRescanHandlingApi: RescanHandlingApi = mock[RescanHandlingApi]
@ -89,10 +104,19 @@ class RoutesSpec extends AnyWordSpec with ScalatestRouteTest with MockFactory {
val mockAddressHandlingApi: AddressHandlingApi = mock[AddressHandlingApi]
val mockFundTxHandlingApi: FundTransactionHandlingApi =
mock[FundTransactionHandlingApi]
val mockSendFundsHandlingApi: SendFundsHandlingApi =
mock[SendFundsHandlingApi]
val mockUtxoHandlingApi: UtxoHandlingApi = mock[UtxoHandlingApi]
val walletHolder = new WalletHolder(Some(mockWalletApi))
val feeRateApi = ConstantFeeRateProvider(SatoshisPerVirtualByte.one)
val fee = SatoshisPerVirtualByte(Satoshis(4))
val walletLoader: DLCWalletNeutrinoBackendLoader = {
DLCWalletNeutrinoBackendLoader(
walletHolder,
@ -671,16 +695,6 @@ class RoutesSpec extends AnyWordSpec with ScalatestRouteTest with MockFactory {
}
"return the wallet accounts" in {
val xpub = ExtPublicKey
.fromString(
"xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8"
)
val accountDb =
AccountDb(
xpub = xpub,
hdAccount = HDAccount(HDCoin(HDPurpose.Legacy, HDCoinType.Testnet), 0)
)
(() => mockWalletApi.accountHandling)
.expects()
@ -1617,11 +1631,35 @@ class RoutesSpec extends AnyWordSpec with ScalatestRouteTest with MockFactory {
"send to an address" in {
// positive cases
(mockWalletApi
.sendToAddress(_: BitcoinAddress, _: CurrencyUnit, _: Option[FeeUnit])(
_: ExecutionContext
))
.expects(testAddress, Bitcoins(100), *, executor)
(() => mockWalletApi.sendFundsHandling)
.expects()
.returning(mockSendFundsHandlingApi)
.anyNumberOfTimes()
(() => mockWalletApi.sendFundsHandling.accountHandling)
.expects()
.returning(mockAccountHandlingApi)
.anyNumberOfTimes()
(() =>
mockWalletApi.sendFundsHandling.accountHandling.getDefaultAccount())
.expects()
.returning(Future.successful(accountDb))
.anyNumberOfTimes()
(mockWalletApi.sendFundsHandling
.sendWithAlgo(_: BitcoinAddress,
_: CurrencyUnit,
_: FeeUnit,
_: CoinSelectionAlgo,
_: AccountDb,
_: Vector[AddressTag]))
.expects(testAddress,
Bitcoins(100),
fee,
CoinSelectionAlgo.LeastWaste,
accountDb,
Vector.empty)
.returning(Future.successful(EmptyTransaction))
(mockWalletApi.broadcastTransaction _)
@ -1708,19 +1746,38 @@ class RoutesSpec extends AnyWordSpec with ScalatestRouteTest with MockFactory {
"send from outpoints" in {
// positive cases
(mockWalletApi
(() => mockWalletApi.sendFundsHandling)
.expects()
.returning(mockSendFundsHandlingApi)
.anyNumberOfTimes()
(() => mockWalletApi.sendFundsHandling.accountHandling)
.expects()
.returning(mockAccountHandlingApi)
.anyNumberOfTimes()
(() =>
mockWalletApi.sendFundsHandling.accountHandling.getDefaultAccount())
.expects()
.returning(Future.successful(accountDb))
.anyNumberOfTimes()
(mockWalletApi.sendFundsHandling
.sendFromOutPoints(
_: Vector[TransactionOutPoint],
_: BitcoinAddress,
_: CurrencyUnit,
_: Option[FeeUnit]
)(_: ExecutionContext))
_: FeeUnit,
_: AccountDb,
_: Vector[AddressTag]
))
.expects(
Vector.empty[TransactionOutPoint],
testAddress,
Bitcoins(100),
*,
executor
fee,
accountDb,
Vector.empty
)
.returning(Future.successful(EmptyTransaction))
@ -1822,11 +1879,25 @@ class RoutesSpec extends AnyWordSpec with ScalatestRouteTest with MockFactory {
}
"sweep wallet" in {
(mockWalletApi
.sweepWallet(_: BitcoinAddress, _: Option[FeeUnit])(
_: ExecutionContext
))
.expects(testAddress, *, executor)
(() => mockWalletApi.sendFundsHandling)
.expects()
.returning(mockSendFundsHandlingApi)
.anyNumberOfTimes()
(() => mockWalletApi.sendFundsHandling.utxoHandling)
.expects()
.returning(mockUtxoHandlingApi)
.anyNumberOfTimes()
(() => mockWalletApi.sendFundsHandling.utxoHandling.listUtxos())
.expects()
.returning(Future.successful(Vector.empty))
(mockWalletApi.sendFundsHandling
.sendFromOutPoints(_: Vector[TransactionOutPoint],
_: BitcoinAddress,
_: FeeUnit))
.expects(Vector.empty, testAddress, fee)
.returning(Future.successful(EmptyTransaction))
(mockWalletApi.broadcastTransaction _)
@ -1849,7 +1920,12 @@ class RoutesSpec extends AnyWordSpec with ScalatestRouteTest with MockFactory {
"send with algo" in {
// positive cases
(mockWalletApi
(() => mockWalletApi.sendFundsHandling)
.expects()
.returning(mockSendFundsHandlingApi)
.anyNumberOfTimes()
(mockWalletApi.sendFundsHandling
.sendWithAlgo(
_: BitcoinAddress,
_: CurrencyUnit,
@ -1859,7 +1935,7 @@ class RoutesSpec extends AnyWordSpec with ScalatestRouteTest with MockFactory {
.expects(
testAddress,
Bitcoins(100),
Some(SatoshisPerVirtualByte(Satoshis(4))),
Some(fee),
CoinSelectionAlgo.AccumulateSmallestViable,
executor
)
@ -1972,7 +2048,12 @@ class RoutesSpec extends AnyWordSpec with ScalatestRouteTest with MockFactory {
"020000000258e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd750000000000ffffffff838d0427d0ec650a68aa46bb0b098aea4422c071b2ca78352a077959d07cea1d0100000000ffffffff0270aaf00800000000160014d85c2b71d0060b09c9886aeb815e50991dda124d00e1f5050000000016001400aea9a2e5f0f876a588df5546e8742d1d87008f00000000"
)
(mockWalletApi
(() => mockWalletApi.sendFundsHandling)
.expects()
.returning(mockSendFundsHandlingApi)
.anyNumberOfTimes()
(mockWalletApi.sendFundsHandling
.signPSBT(_: PSBT)(_: ExecutionContext))
.expects(PSBT.empty, executor)
.returning(Future.successful(PSBT.fromUnsignedTx(tx)))
@ -1995,11 +2076,14 @@ class RoutesSpec extends AnyWordSpec with ScalatestRouteTest with MockFactory {
val message = "Never gonna give you up, never gonna let you down"
(mockWalletApi
.makeOpReturnCommitment(_: String, _: Boolean, _: Option[FeeUnit])(
_: ExecutionContext
))
.expects(message, false, *, executor)
(() => mockWalletApi.sendFundsHandling)
.expects()
.returning(mockSendFundsHandlingApi)
.anyNumberOfTimes()
(mockWalletApi.sendFundsHandling
.makeOpReturnCommitment(_: String, _: Boolean, _: Option[FeeUnit]))
.expects(message, false, *)
.returning(Future.successful(EmptyTransaction))
(mockWalletApi.broadcastTransaction _)
@ -2020,7 +2104,13 @@ class RoutesSpec extends AnyWordSpec with ScalatestRouteTest with MockFactory {
}
"bump fee with rbf" in {
(mockWalletApi
(() => mockWalletApi.sendFundsHandling)
.expects()
.returning(mockSendFundsHandlingApi)
.anyNumberOfTimes()
(mockWalletApi.sendFundsHandling
.bumpFeeRBF(_: DoubleSha256DigestBE, _: FeeUnit))
.expects(DoubleSha256DigestBE.empty, SatoshisPerVirtualByte.one)
.returning(Future.successful(EmptyTransaction))
@ -2046,7 +2136,12 @@ class RoutesSpec extends AnyWordSpec with ScalatestRouteTest with MockFactory {
}
"bump fee with CPFP" in {
(mockWalletApi
(() => mockWalletApi.sendFundsHandling)
.expects()
.returning(mockSendFundsHandlingApi)
.anyNumberOfTimes()
(mockWalletApi.sendFundsHandling
.bumpFeeCPFP(_: DoubleSha256DigestBE, _: FeeUnit))
.expects(DoubleSha256DigestBE.empty, SatoshisPerVirtualByte.one)
.returning(Future.successful(EmptyTransaction))

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.dlc.wallet.DLCNeutrinoHDWalletApi
import org.bitcoins.core.api.wallet.AccountHandlingApi
import org.bitcoins.core.api.wallet.db.AccountDb
import org.bitcoins.core.crypto.ExtKeyVersion.SegWitMainNetPriv
@ -21,7 +22,7 @@ import org.bitcoins.server.routes.ServerCommand
import org.bitcoins.testkit.BitcoinSTestAppConfig
import org.bitcoins.testkitcore.Implicits.GeneratorOps
import org.bitcoins.testkitcore.gen.TLVGen
import org.bitcoins.wallet.{MockWalletApi, WalletHolder}
import org.bitcoins.wallet.WalletHolder
import org.scalamock.scalatest.MockFactory
import org.scalatest.wordspec.AnyWordSpec
@ -39,7 +40,7 @@ class WalletRoutesSpec
val mockChainApi: ChainApi = mock[ChainApi]
val mockNode: Node = mock[Node]
val mockWalletApi: MockWalletApi = mock[MockWalletApi]
val mockWalletApi: DLCNeutrinoHDWalletApi = mock[DLCNeutrinoHDWalletApi]
val mockAccountHandlingApi: AccountHandlingApi = mock[AccountHandlingApi]
val walletHolder = new WalletHolder(Some(mockWalletApi))

View file

@ -1,23 +0,0 @@
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 scala.concurrent.Future
/** ScalaMock cannot stub traits with protected methods, so we need to stub them
* manually.
*/
abstract class MockWalletApi extends DLCNeutrinoHDWalletApi {
override def getDefaultAccount(): Future[AccountDb] = stub
override def getDefaultAccountForType(
addressType: AddressType
): Future[AccountDb] = stub
private def stub[T] =
Future.failed[T](new RuntimeException("Not implemented"))
}

View file

@ -442,7 +442,7 @@ class BitcoinSServerMain(override val serverArgParser: ServerArgParser)(implicit
bitcoind <- bitcoindF
walletHolder = result._1
callback = BitcoindCallbacks.onBlockReceived(
walletHolder.processBlock(_).map(_ => ()))
walletHolder.transactionProcessing.processBlock(_).map(_ => ()))
_ = bitcoind.bitcoindRpcAppConfig.addCallbacks(callback)
} yield result
}

View file

@ -244,7 +244,7 @@ object BitcoindRpcBackendUtil extends BitcoinSLogger {
val rawTxListener: Option[Transaction => Unit] = Some {
{ (tx: Transaction) =>
logger.debug(s"Received tx ${tx.txIdBE.hex}, processing")
val f = wallet.processTransaction(tx, None)
val f = wallet.transactionProcessing.processTransaction(tx, None)
f.failed.foreach { err =>
logger.error("failed to process raw tx zmq message", err)
}
@ -267,7 +267,7 @@ object BitcoindRpcBackendUtil extends BitcoinSLogger {
logger.info(
s"Received block ${block.blockHeader.hashBE.hex}, processing"
)
val f = wallet.processBlock(block)
val f = wallet.transactionProcessing.processBlock(block)
f.failed.foreach { err =>
logger.error("failed to process raw block zmq message", err)
}
@ -364,7 +364,8 @@ object BitcoindRpcBackendUtil extends BitcoinSLogger {
walletF.map { wallet =>
Sink.foreachAsync(1) {
case (block: Block, blockHeaderResult: GetBlockHeaderResult) =>
val blockProcessedF = wallet.processBlock(block)
val blockProcessedF =
wallet.transactionProcessing.processBlock(block)
val executeCallbackF: Future[Unit] = {
for {
wallet <- blockProcessedF

View file

@ -669,7 +669,7 @@ case class WalletRoutes(loadWalletApi: DLCWalletLoaderApi)(implicit
) =>
complete {
for {
tx <- wallet.sendToAddress(
tx <- wallet.sendFundsHandling.sendToAddress(
address,
bitcoins,
satoshisPerVirtualByteOpt
@ -690,7 +690,7 @@ case class WalletRoutes(loadWalletApi: DLCWalletLoaderApi)(implicit
) =>
complete {
for {
tx <- wallet.sendFromOutPoints(
tx <- wallet.sendFundsHandling.sendFromOutPoints(
outPoints,
address,
bitcoins,
@ -705,10 +705,13 @@ case class WalletRoutes(loadWalletApi: DLCWalletLoaderApi)(implicit
withValidServerCommand(SweepWallet.fromJsArr(arr)) {
case SweepWallet(address, feeRateOpt) =>
complete {
for {
tx <- wallet.sweepWallet(address, feeRateOpt)
val f = for {
tx <- wallet.sendFundsHandling.sweepWallet(address, feeRateOpt)
_ <- wallet.broadcastTransaction(tx)
} yield Server.httpSuccess(tx.txIdBE)
f.failed.foreach(err => logger.error(s"f.err", err))
f
}
}
@ -717,7 +720,7 @@ case class WalletRoutes(loadWalletApi: DLCWalletLoaderApi)(implicit
case SendWithAlgo(address, bitcoins, satoshisPerVirtualByteOpt, algo) =>
complete {
for {
tx <- wallet.sendWithAlgo(
tx <- wallet.sendFundsHandling.sendWithAlgo(
address,
bitcoins,
satoshisPerVirtualByteOpt,
@ -731,7 +734,7 @@ case class WalletRoutes(loadWalletApi: DLCWalletLoaderApi)(implicit
case ServerCommand("signpsbt", arr) =>
withValidServerCommand(SignPSBT.fromJsArr(arr)) { case SignPSBT(psbt) =>
complete {
wallet.signPSBT(psbt).map { signed =>
wallet.sendFundsHandling.signPSBT(psbt).map { signed =>
Server.httpSuccess(signed.base64)
}
}
@ -742,7 +745,7 @@ case class WalletRoutes(loadWalletApi: DLCWalletLoaderApi)(implicit
case OpReturnCommit(message, hashMessage, satoshisPerVirtualByteOpt) =>
complete {
for {
tx <- wallet.makeOpReturnCommitment(
tx <- wallet.sendFundsHandling.makeOpReturnCommitment(
message,
hashMessage,
satoshisPerVirtualByteOpt
@ -759,7 +762,7 @@ case class WalletRoutes(loadWalletApi: DLCWalletLoaderApi)(implicit
case BumpFee(txId, feeRate) =>
complete {
for {
tx <- wallet.bumpFeeRBF(txId, feeRate)
tx <- wallet.sendFundsHandling.bumpFeeRBF(txId, feeRate)
_ <- wallet.broadcastTransaction(tx)
} yield Server.httpSuccess(tx.txIdBE)
}
@ -770,7 +773,7 @@ case class WalletRoutes(loadWalletApi: DLCWalletLoaderApi)(implicit
case BumpFee(txId, feeRate) =>
complete {
for {
tx <- wallet.bumpFeeCPFP(txId, feeRate)
tx <- wallet.sendFundsHandling.bumpFeeCPFP(txId, feeRate)
_ <- wallet.broadcastTransaction(tx)
} yield Server.httpSuccess(tx.txIdBE)
}
@ -845,7 +848,7 @@ case class WalletRoutes(loadWalletApi: DLCWalletLoaderApi)(implicit
case ServerCommand("getaccounts", _) =>
complete {
wallet.listAccounts().map { accounts =>
wallet.accountHandling.listAccounts().map { accounts =>
val xpubs = accounts.map(_.xpub)
Server.httpSuccess(xpubs)
}

View file

@ -24,7 +24,7 @@ object CallbackUtil extends BitcoinSLogger {
import system.dispatcher
val txSink = Sink.foreachAsync[Transaction](1) { case tx: Transaction =>
logger.debug(s"Receiving transaction txid=${tx.txIdBE.hex} as a callback")
wallet
wallet.transactionProcessing
.processTransaction(tx, blockHashOpt = None)
.map(_ => ())
}
@ -46,7 +46,9 @@ object CallbackUtil extends BitcoinSLogger {
logger.debug(
s"Executing onBlock callback=${block.blockHeader.hashBE.hex}"
)
wallet.processBlock(block).map(_ => ())
wallet.transactionProcessing
.processBlock(block)
.map(_ => ())
}
}
@ -106,7 +108,7 @@ object CallbackUtil extends BitcoinSLogger {
import system.dispatcher
val txSink = Sink.foreachAsync[Transaction](1) { case tx: Transaction =>
logger.debug(s"Receiving transaction txid=${tx.txIdBE.hex} as a callback")
wallet
wallet.transactionProcessing
.processTransaction(tx, blockHashOpt = None)
.map(_ => ())
}
@ -118,7 +120,7 @@ object CallbackUtil extends BitcoinSLogger {
}
val blockSink = Sink.foreachAsync[Block](1) { block =>
wallet
wallet.transactionProcessing
.processBlock(block)
.map(_ => ())
}

View file

@ -23,8 +23,29 @@ trait AccountHandlingApi {
def createNewAccount(purpose: HDPurpose): Future[ExtPublicKey]
/** Gets the balance of the given account */
def getBalance(account: HDAccount)(implicit
ec: ExecutionContext): Future[CurrencyUnit] = {
val confirmedF = getConfirmedBalance(account)
val unconfirmedF = getUnconfirmedBalance(account)
for {
confirmed <- confirmedF
unconfirmed <- unconfirmedF
} yield {
confirmed + unconfirmed
}
}
def getConfirmedBalance(account: HDAccount): Future[CurrencyUnit]
def getUnconfirmedBalance(account: HDAccount): Future[CurrencyUnit]
def getDefaultAccount(): Future[AccountDb]
def listAccounts(): Future[Vector[AccountDb]]
def listAccounts(purpose: HDPurpose)(implicit
ec: ExecutionContext): Future[Vector[AccountDb]] = {
listAccounts().map(_.filter(_.hdAccount.purpose == purpose))
}
def getDefaultAccountForType(addressType: AddressType): Future[AccountDb]
def getNewAddress(
account: AccountDb,

View file

@ -1,9 +1,8 @@
package org.bitcoins.core.api.wallet
import org.bitcoins.core.api.keymanager.BIP39KeyManagerApi
import org.bitcoins.core.api.wallet.db.{AccountDb, SpendingInfoDb}
import org.bitcoins.core.api.feeprovider.FeeRateApi
import org.bitcoins.core.api.wallet.db.AccountDb
import org.bitcoins.core.currency.CurrencyUnit
import org.bitcoins.core.hd.{AddressType, HDAccount, HDPurpose}
import org.bitcoins.core.protocol.BitcoinAddress
import org.bitcoins.core.protocol.transaction.{
Transaction,
@ -11,58 +10,48 @@ import org.bitcoins.core.protocol.transaction.{
TransactionOutput
}
import org.bitcoins.core.psbt.PSBT
import org.bitcoins.core.wallet.builder.{
FundRawTxHelper,
ShufflingNonInteractiveFinalizer
}
import org.bitcoins.core.wallet.fee.FeeUnit
import org.bitcoins.core.wallet.utxo.AddressTag
import org.bitcoins.crypto.DoubleSha256DigestBE
import scala.concurrent.{ExecutionContext, Future}
/** API for the wallet project.
*
* This wallet API is BIP44 compliant.
*
* @see
* [[https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki BIP44]]
*/
trait HDWalletApi extends WalletApi {
trait SendFundsHandlingApi {
override def keyManager: BIP39KeyManagerApi
def accountHandling: AccountHandlingApi
def fundTxHandling: FundTransactionHandlingApi
def rescanHandling: RescanHandlingApi
def addressHandling: AddressHandlingApi
def feeRateApi: FeeRateApi
def utxoHandling: UtxoHandlingApi
/** Gets the balance of the given account */
def getBalance(account: HDAccount)(implicit
ec: ExecutionContext): Future[CurrencyUnit] = {
val confirmedF = getConfirmedBalance(account)
val unconfirmedF = getUnconfirmedBalance(account)
for {
confirmed <- confirmedF
unconfirmed <- unconfirmedF
} yield {
confirmed + unconfirmed
}
}
def bumpFeeRBF(
txId: DoubleSha256DigestBE,
newFeeRate: FeeUnit
): Future[Transaction]
def bumpFeeCPFP(
txId: DoubleSha256DigestBE,
feeRate: FeeUnit): Future[Transaction]
def getConfirmedBalance(account: HDAccount): Future[CurrencyUnit]
def makeOpReturnCommitment(
message: String,
hashMessage: Boolean,
feeRate: FeeUnit,
fromAccount: AccountDb
): Future[Transaction]
def getUnconfirmedBalance(account: HDAccount): Future[CurrencyUnit]
def makeOpReturnCommitment(
message: String,
hashMessage: Boolean,
feeRateOpt: Option[FeeUnit],
fromAccount: AccountDb): Future[Transaction]
/** Fetches the default account from the DB
* @return
* Future[AccountDb]
*/
def getDefaultAccount(): Future[AccountDb] =
accountHandling.getDefaultAccount()
def makeOpReturnCommitment(
message: String,
hashMessage: Boolean,
feeRate: FeeUnit): Future[Transaction]
/** Fetches the default account for the given address/account kind
* @param addressType
*/
def getDefaultAccountForType(addressType: AddressType): Future[AccountDb]
def makeOpReturnCommitment(
message: String,
hashMessage: Boolean,
feeRateOpt: Option[FeeUnit]): Future[Transaction]
def sendWithAlgo(
address: BitcoinAddress,
@ -70,16 +59,14 @@ trait HDWalletApi extends WalletApi {
feeRate: FeeUnit,
algo: CoinSelectionAlgo,
fromAccount: AccountDb,
newTags: Vector[AddressTag])(implicit
ec: ExecutionContext): Future[Transaction]
newTags: Vector[AddressTag]): Future[Transaction]
def sendWithAlgo(
final def sendWithAlgo(
address: BitcoinAddress,
amount: CurrencyUnit,
feeRate: FeeUnit,
algo: CoinSelectionAlgo,
fromAccount: AccountDb)(implicit
ec: ExecutionContext): Future[Transaction] =
fromAccount: AccountDb): Future[Transaction] =
sendWithAlgo(address, amount, feeRate, algo, fromAccount, Vector.empty)
def sendWithAlgo(
@ -95,26 +82,26 @@ trait HDWalletApi extends WalletApi {
} yield tx
}
override def sendWithAlgo(
def sendWithAlgo(
address: BitcoinAddress,
amount: CurrencyUnit,
feeRateOpt: Option[FeeUnit],
algo: CoinSelectionAlgo)(implicit
ec: ExecutionContext): Future[Transaction] = {
for {
account <- getDefaultAccount()
account <- accountHandling.getDefaultAccount()
tx <- sendWithAlgo(address, amount, feeRateOpt, algo, account)
} yield tx
}
override def sendWithAlgo(
def sendWithAlgo(
address: BitcoinAddress,
amount: CurrencyUnit,
feeRate: FeeUnit,
algo: CoinSelectionAlgo)(implicit
ec: ExecutionContext): Future[Transaction] = {
for {
account <- getDefaultAccount()
account <- accountHandling.getDefaultAccount()
tx <- sendWithAlgo(address, amount, feeRate, algo, account)
} yield tx
}
@ -127,7 +114,7 @@ trait HDWalletApi extends WalletApi {
newTags: Vector[AddressTag])(implicit
ec: ExecutionContext): Future[Transaction] = {
for {
account <- getDefaultAccount()
account <- accountHandling.getDefaultAccount()
tx <- sendWithAlgo(address, amount, feeRate, algo, account, newTags)
} yield tx
}
@ -142,16 +129,30 @@ trait HDWalletApi extends WalletApi {
amount: CurrencyUnit,
feeRate: FeeUnit,
fromAccount: AccountDb,
newTags: Vector[AddressTag])(implicit
ec: ExecutionContext): Future[Transaction]
newTags: Vector[AddressTag]): Future[Transaction]
def sendFromOutPoints(
outPoints: Vector[TransactionOutPoint],
address: BitcoinAddress,
feeRate: FeeUnit): Future[Transaction]
final def sendFromOutPoints(
outPoints: Vector[TransactionOutPoint],
address: BitcoinAddress,
feeRateOpt: Option[FeeUnit])(implicit
ec: ExecutionContext): Future[Transaction] = {
for {
feeRate <- determineFeeRate(feeRateOpt)
tx <- sendFromOutPoints(outPoints, address, feeRate)
} yield tx
}
final def sendFromOutPoints(
outPoints: Vector[TransactionOutPoint],
address: BitcoinAddress,
amount: CurrencyUnit,
feeRate: FeeUnit,
fromAccount: AccountDb)(implicit
ec: ExecutionContext): Future[Transaction] =
fromAccount: AccountDb): Future[Transaction] =
sendFromOutPoints(outPoints,
address,
amount,
@ -159,7 +160,7 @@ trait HDWalletApi extends WalletApi {
fromAccount,
Vector.empty)
def sendFromOutPoints(
final def sendFromOutPoints(
outPoints: Vector[TransactionOutPoint],
address: BitcoinAddress,
amount: CurrencyUnit,
@ -172,30 +173,30 @@ trait HDWalletApi extends WalletApi {
} yield tx
}
override def sendFromOutPoints(
final def sendFromOutPoints(
outPoints: Vector[TransactionOutPoint],
address: BitcoinAddress,
amount: CurrencyUnit,
feeRateOpt: Option[FeeUnit]
)(implicit ec: ExecutionContext): Future[Transaction] = {
for {
account <- getDefaultAccount()
account <- accountHandling.getDefaultAccount()
tx <- sendFromOutPoints(outPoints, address, amount, feeRateOpt, account)
} yield tx
}
override def sendFromOutPoints(
final def sendFromOutPoints(
outPoints: Vector[TransactionOutPoint],
address: BitcoinAddress,
amount: CurrencyUnit,
feeRate: FeeUnit)(implicit ec: ExecutionContext): Future[Transaction] = {
for {
account <- getDefaultAccount()
account <- accountHandling.getDefaultAccount()
tx <- sendFromOutPoints(outPoints, address, amount, feeRate, account)
} yield tx
}
def sendFromOutPoints(
final def sendFromOutPoints(
outPoints: Vector[TransactionOutPoint],
address: BitcoinAddress,
amount: CurrencyUnit,
@ -203,33 +204,36 @@ trait HDWalletApi extends WalletApi {
newTags: Vector[AddressTag])(implicit
ec: ExecutionContext): Future[Transaction] = {
for {
account <- getDefaultAccount()
account <- accountHandling.getDefaultAccount()
tx <-
sendFromOutPoints(outPoints, address, amount, feeRate, account, newTags)
} yield tx
}
/** Sends money from the specified account
*
* todo: add error handling to signature
*/
def sendToAddress(
final def sendToAddress(
address: BitcoinAddress,
amount: CurrencyUnit,
feeRate: FeeUnit,
fromAccount: AccountDb,
newTags: Vector[AddressTag])(implicit
ec: ExecutionContext): Future[Transaction]
newTags: Vector[AddressTag]
): Future[Transaction] =
sendWithAlgo(
address,
amount,
feeRate,
CoinSelectionAlgo.LeastWaste,
fromAccount,
newTags
)
def sendToAddress(
final def sendToAddress(
address: BitcoinAddress,
amount: CurrencyUnit,
feeRate: FeeUnit,
fromAccount: AccountDb)(implicit
ec: ExecutionContext): Future[Transaction] =
fromAccount: AccountDb): Future[Transaction] =
sendToAddress(address, amount, feeRate, fromAccount, Vector.empty)
def sendToAddress(
final def sendToAddress(
address: BitcoinAddress,
amount: CurrencyUnit,
feeRateOpt: Option[FeeUnit],
@ -241,35 +245,35 @@ trait HDWalletApi extends WalletApi {
} yield tx
}
override def sendToAddress(
final def sendToAddress(
address: BitcoinAddress,
amount: CurrencyUnit,
feeRateOpt: Option[FeeUnit]
)(implicit ec: ExecutionContext): Future[Transaction] = {
for {
account <- getDefaultAccount()
account <- accountHandling.getDefaultAccount()
tx <- sendToAddress(address, amount, feeRateOpt, account)
} yield tx
}
override def sendToAddress(
final def sendToAddress(
address: BitcoinAddress,
amount: CurrencyUnit,
feeRate: FeeUnit)(implicit ec: ExecutionContext): Future[Transaction] = {
for {
account <- getDefaultAccount()
account <- accountHandling.getDefaultAccount()
tx <- sendToAddress(address, amount, feeRate, account)
} yield tx
}
def sendToAddress(
final def sendToAddress(
address: BitcoinAddress,
amount: CurrencyUnit,
feeRate: FeeUnit,
newTags: Vector[AddressTag])(implicit
ec: ExecutionContext): Future[Transaction] = {
for {
account <- getDefaultAccount()
account <- accountHandling.getDefaultAccount()
tx <- sendToAddress(address, amount, feeRate, account, newTags)
} yield tx
}
@ -283,18 +287,16 @@ trait HDWalletApi extends WalletApi {
amounts: Vector[CurrencyUnit],
feeRate: FeeUnit,
fromAccount: AccountDb,
newTags: Vector[AddressTag])(implicit
ec: ExecutionContext): Future[Transaction]
newTags: Vector[AddressTag]): Future[Transaction]
def sendToAddresses(
final def sendToAddresses(
addresses: Vector[BitcoinAddress],
amounts: Vector[CurrencyUnit],
feeRate: FeeUnit,
fromAccount: AccountDb)(implicit
ec: ExecutionContext): Future[Transaction] =
fromAccount: AccountDb): Future[Transaction] =
sendToAddresses(addresses, amounts, feeRate, fromAccount, Vector.empty)
def sendToAddresses(
final def sendToAddresses(
addresses: Vector[BitcoinAddress],
amounts: Vector[CurrencyUnit],
feeRateOpt: Option[FeeUnit],
@ -306,35 +308,35 @@ trait HDWalletApi extends WalletApi {
} yield tx
}
override def sendToAddresses(
final def sendToAddresses(
addresses: Vector[BitcoinAddress],
amounts: Vector[CurrencyUnit],
feeRateOpt: Option[FeeUnit]
)(implicit ec: ExecutionContext): Future[Transaction] = {
for {
account <- getDefaultAccount()
account <- accountHandling.getDefaultAccount()
tx <- sendToAddresses(addresses, amounts, feeRateOpt, account)
} yield tx
}
override def sendToAddresses(
final def sendToAddresses(
addresses: Vector[BitcoinAddress],
amounts: Vector[CurrencyUnit],
feeRate: FeeUnit)(implicit ec: ExecutionContext): Future[Transaction] = {
for {
account <- getDefaultAccount()
account <- accountHandling.getDefaultAccount()
tx <- sendToAddresses(addresses, amounts, feeRate, account)
} yield tx
}
def sendToAddresses(
final def sendToAddresses(
addresses: Vector[BitcoinAddress],
amounts: Vector[CurrencyUnit],
feeRate: FeeUnit,
newTags: Vector[AddressTag])(implicit
ec: ExecutionContext): Future[Transaction] = {
for {
account <- getDefaultAccount()
account <- accountHandling.getDefaultAccount()
tx <- sendToAddresses(addresses, amounts, feeRate, account, newTags)
} yield tx
}
@ -347,17 +349,15 @@ trait HDWalletApi extends WalletApi {
outputs: Vector[TransactionOutput],
feeRate: FeeUnit,
fromAccount: AccountDb,
newTags: Vector[AddressTag])(implicit
ec: ExecutionContext): Future[Transaction]
newTags: Vector[AddressTag]): Future[Transaction]
def sendToOutputs(
final def sendToOutputs(
outputs: Vector[TransactionOutput],
feeRate: FeeUnit,
fromAccount: AccountDb)(implicit
ec: ExecutionContext): Future[Transaction] =
fromAccount: AccountDb): Future[Transaction] =
sendToOutputs(outputs, feeRate, fromAccount, Vector.empty)
def sendToOutputs(
final def sendToOutputs(
outputs: Vector[TransactionOutput],
feeRateOpt: Option[FeeUnit],
fromAccount: AccountDb)(implicit
@ -368,96 +368,59 @@ trait HDWalletApi extends WalletApi {
} yield tx
}
def sendToOutputs(
final def sendToOutputs(
outputs: Vector[TransactionOutput],
feeRate: FeeUnit,
newTags: Vector[AddressTag])(implicit
ec: ExecutionContext): Future[Transaction] = {
for {
account <- getDefaultAccount()
account <- accountHandling.getDefaultAccount()
tx <- sendToOutputs(outputs, feeRate, account, newTags)
} yield tx
}
override def sendToOutputs(
final def sendToOutputs(
outputs: Vector[TransactionOutput],
feeRateOpt: Option[FeeUnit]
)(implicit ec: ExecutionContext): Future[Transaction] = {
for {
account <- getDefaultAccount()
account <- accountHandling.getDefaultAccount()
tx <- sendToOutputs(outputs, feeRateOpt, account)
} yield tx
}
override def sendToOutputs(
outputs: Vector[TransactionOutput],
feeRate: FeeUnit)(implicit ec: ExecutionContext): Future[Transaction] = {
final def sendToOutputs(outputs: Vector[TransactionOutput], feeRate: FeeUnit)(
implicit ec: ExecutionContext): Future[Transaction] = {
for {
account <- getDefaultAccount()
account <- accountHandling.getDefaultAccount()
tx <- sendToOutputs(outputs, feeRate, account)
} yield tx
}
/** Sends the entire wallet balance to the given address */
final def sweepWallet(address: BitcoinAddress, feeRate: FeeUnit)(implicit
ec: ExecutionContext
): Future[Transaction] = {
for {
utxos <- utxoHandling.listUtxos()
outpoints = utxos.map(_.outPoint)
tx <- sendFromOutPoints(outpoints, address, feeRate)
} yield tx
}
final def sweepWallet(address: BitcoinAddress, feeRateOpt: Option[FeeUnit])(
implicit ec: ExecutionContext
): Future[Transaction] = {
determineFeeRate(feeRateOpt).flatMap(sweepWallet(address, _))
}
def signPSBT(psbt: PSBT)(implicit ec: ExecutionContext): Future[PSBT]
def makeOpReturnCommitment(
message: String,
hashMessage: Boolean,
feeRate: FeeUnit,
fromAccount: AccountDb)(implicit
ec: ExecutionContext): Future[Transaction]
def makeOpReturnCommitment(
message: String,
hashMessage: Boolean,
feeRateOpt: Option[FeeUnit],
fromAccount: AccountDb)(implicit
ec: ExecutionContext): Future[Transaction] = {
for {
feeRate <- determineFeeRate(feeRateOpt)
tx <- makeOpReturnCommitment(message, hashMessage, feeRate, fromAccount)
} yield tx
}
override def makeOpReturnCommitment(
message: String,
hashMessage: Boolean,
feeRate: FeeUnit)(implicit ec: ExecutionContext): Future[Transaction] = {
for {
account <- getDefaultAccount()
tx <- makeOpReturnCommitment(message, hashMessage, feeRate, account)
} yield tx
}
def listDefaultAccountUtxos(): Future[Vector[SpendingInfoDb]]
def listUtxos(hdAccount: HDAccount): Future[Vector[SpendingInfoDb]]
override def clearAllUtxos(): Future[HDWalletApi]
def listAccounts(): Future[Vector[AccountDb]] = {
accountHandling.listAccounts()
}
/** Lists all wallet accounts with the given type
* @param purpose
* @return
* [[Future[Vector[AccountDb]]
*/
def listAccounts(purpose: HDPurpose)(implicit
ec: ExecutionContext): Future[Vector[AccountDb]] = {
accountHandling.listAccounts().map(_.filter(_.hdAccount.purpose == purpose))
}
def fundRawTransaction(
destinations: Vector[TransactionOutput],
feeRate: FeeUnit,
fromAccount: AccountDb,
markAsReserved: Boolean)
: Future[FundRawTxHelper[ShufflingNonInteractiveFinalizer]] = {
fundTxHandling.fundRawTransaction(destinations,
feeRate,
fromAccount,
markAsReserved)
}
private def determineFeeRate(feeRateOpt: Option[FeeUnit]): Future[FeeUnit] =
feeRateOpt match {
case None =>
feeRateApi.getFeeRate()
case Some(feeRate) =>
Future.successful(feeRate)
}
}

View file

@ -9,6 +9,15 @@ import scala.concurrent.Future
trait UtxoHandlingApi {
/** Removes all utxos from the wallet. Don't call this unless you are sure you
* can recover your wallet
*/
def clearAllUtxos(): Future[Unit]
def clearAllAddresses(): Future[Unit]
def listDefaultAccountUtxos(): Future[Vector[SpendingInfoDb]]
/** Lists unspent transaction outputs in the wallet
* @return
* Vector[SpendingInfoDb]
@ -19,6 +28,8 @@ trait UtxoHandlingApi {
def listUtxos(state: TxoState): Future[Vector[SpendingInfoDb]]
def listUtxos(account: HDAccount): Future[Vector[SpendingInfoDb]]
def listUtxos(
hdAccount: HDAccount,
tag: AddressTag): Future[Vector[SpendingInfoDb]]

View file

@ -2,14 +2,12 @@ package org.bitcoins.core.api.wallet
import org.bitcoins.core.api.chain.ChainQueryApi
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.crypto.ExtPublicKey
import org.bitcoins.core.currency.CurrencyUnit
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
import org.bitcoins.core.protocol.transaction.{
Transaction,
@ -32,6 +30,13 @@ import scala.concurrent.{ExecutionContext, Future}
* [[https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki BIP44]]
*/
trait WalletApi extends StartStopAsync[WalletApi] {
def accountHandling: AccountHandlingApi
def fundTxHandling: FundTransactionHandlingApi
def rescanHandling: RescanHandlingApi
def addressHandling: AddressHandlingApi
def utxoHandling: UtxoHandlingApi
def transactionProcessing: TransactionProcessingApi
def sendFundsHandling: SendFundsHandlingApi
val nodeApi: NodeApi
val chainQueryApi: ChainQueryApi
@ -49,30 +54,6 @@ trait WalletApi extends StartStopAsync[WalletApi] {
def stop(): Future[WalletApi]
/** Processes the give block, updating our DB state if it's relevant to us.
*
* @param block
* The block we're processing
*/
def processBlock(block: Block): Future[Unit]
def processTransaction(
transaction: Transaction,
blockHashOpt: Option[DoubleSha256DigestBE]
): Future[Unit]
/** Processes TXs originating from our wallet. This is called right after
* we've signed a TX, updating our UTXO state.
*/
def processOurTransaction(
transaction: Transaction,
feeRate: FeeUnit,
inputAmount: CurrencyUnit,
sentAmount: CurrencyUnit,
blockHashOpt: Option[DoubleSha256DigestBE],
newTags: Vector[AddressTag]
): Future[ProcessTxResult]
/** Gets the sum of all UTXOs in this wallet */
def getBalance()(implicit ec: ExecutionContext): Future[CurrencyUnit] = {
val confirmedF = getConfirmedBalance()
@ -121,15 +102,6 @@ trait WalletApi extends StartStopAsync[WalletApi] {
/** Checks if the wallet contains any data */
def isEmpty(): Future[Boolean]
/** Removes all utxos from the wallet. Don't call this unless you are sure you
* can recover your wallet
*/
def clearAllUtxos(): Future[WalletApi]
def clearAllAddresses(): Future[WalletApi]
def keyManager: KeyManagerApi
protected def determineFeeRate(feeRateOpt: Option[FeeUnit]): Future[FeeUnit] =
feeRateOpt match {
case None =>
@ -138,158 +110,6 @@ trait WalletApi extends StartStopAsync[WalletApi] {
Future.successful(feeRate)
}
def sendFromOutPoints(
outPoints: Vector[TransactionOutPoint],
address: BitcoinAddress,
amount: CurrencyUnit,
feeRate: FeeUnit)(implicit ec: ExecutionContext): Future[Transaction]
def sendFromOutPoints(
outPoints: Vector[TransactionOutPoint],
address: BitcoinAddress,
amount: CurrencyUnit,
feeRateOpt: Option[FeeUnit]
)(implicit ec: ExecutionContext): Future[Transaction] = {
for {
feeRate <- determineFeeRate(feeRateOpt)
tx <- sendFromOutPoints(outPoints, address, amount, feeRate)
} yield tx
}
def sendFromOutPoints(
outPoints: Vector[TransactionOutPoint],
address: BitcoinAddress,
feeRate: FeeUnit)(implicit ec: ExecutionContext): Future[Transaction]
def sendFromOutPoints(
outPoints: Vector[TransactionOutPoint],
address: BitcoinAddress,
feeRateOpt: Option[FeeUnit]
)(implicit ec: ExecutionContext): Future[Transaction] = {
for {
feeRate <- determineFeeRate(feeRateOpt)
tx <- sendFromOutPoints(outPoints, address, feeRate)
} yield tx
}
/** Sends the entire wallet balance to the given address */
def sweepWallet(address: BitcoinAddress)(implicit
ec: ExecutionContext): Future[Transaction] = sweepWallet(address, None)
/** Sends the entire wallet balance to the given address */
def sweepWallet(address: BitcoinAddress, feeRateOpt: Option[FeeUnit])(implicit
ec: ExecutionContext): Future[Transaction] = {
for {
feeRate <- determineFeeRate(feeRateOpt)
tx <- sweepWallet(address, feeRate)
} yield tx
}
/** Sends the entire wallet balance to the given address */
def sweepWallet(address: BitcoinAddress, feeRate: FeeUnit)(implicit
ec: ExecutionContext): Future[Transaction]
def sendWithAlgo(
address: BitcoinAddress,
amount: CurrencyUnit,
feeRate: FeeUnit,
algo: CoinSelectionAlgo)(implicit
ec: ExecutionContext): Future[Transaction]
def sendWithAlgo(
address: BitcoinAddress,
amount: CurrencyUnit,
feeRateOpt: Option[FeeUnit],
algo: CoinSelectionAlgo
)(implicit ec: ExecutionContext): Future[Transaction] = {
for {
feeRate <- determineFeeRate(feeRateOpt)
tx <- sendWithAlgo(address, amount, feeRate, algo)
} yield tx
}
/** Sends money to the address
*
* todo: add error handling to signature
*/
def sendToAddress(
address: BitcoinAddress,
amount: CurrencyUnit,
feeRate: FeeUnit)(implicit ec: ExecutionContext): Future[Transaction]
def sendToAddress(
address: BitcoinAddress,
amount: CurrencyUnit,
feeRateOpt: Option[FeeUnit]
)(implicit ec: ExecutionContext): Future[Transaction] = {
for {
feeRate <- determineFeeRate(feeRateOpt)
tx <- sendToAddress(address, amount, feeRate)
} yield tx
}
/** Sends funds using the specified outputs
*
* todo: add error handling to signature
*/
def sendToOutputs(
outputs: Vector[TransactionOutput],
feeRateOpt: Option[FeeUnit])(implicit
ec: ExecutionContext): Future[Transaction] = {
for {
feeRate <- determineFeeRate(feeRateOpt)
tx <- sendToOutputs(outputs, feeRate)
} yield tx
}
def sendToOutputs(outputs: Vector[TransactionOutput], feeRate: FeeUnit)(
implicit ec: ExecutionContext): Future[Transaction]
/** Sends funds to each address
*/
def sendToAddresses(
addresses: Vector[BitcoinAddress],
amounts: Vector[CurrencyUnit],
feeRateOpt: Option[FeeUnit])(implicit
ec: ExecutionContext): Future[Transaction] = {
for {
feeRate <- determineFeeRate(feeRateOpt)
tx <- sendToAddresses(addresses, amounts, feeRate)
} yield tx
}
def sendToAddresses(
addresses: Vector[BitcoinAddress],
amounts: Vector[CurrencyUnit],
feeRate: FeeUnit)(implicit ec: ExecutionContext): Future[Transaction]
def bumpFeeRBF(
txId: DoubleSha256DigestBE,
newFeeRate: FeeUnit): Future[Transaction]
/** Bumps the fee of the parent transaction with a new child transaction with
* the given fee rate
*/
def bumpFeeCPFP(
txId: DoubleSha256DigestBE,
feeRate: FeeUnit): Future[Transaction]
def makeOpReturnCommitment(
message: String,
hashMessage: Boolean,
feeRate: FeeUnit)(implicit ec: ExecutionContext): Future[Transaction]
def makeOpReturnCommitment(
message: String,
hashMessage: Boolean,
feeRateOpt: Option[FeeUnit])(implicit
ec: ExecutionContext): Future[Transaction] = {
for {
feeRate <- determineFeeRate(feeRateOpt)
tx <- makeOpReturnCommitment(message, hashMessage, feeRate)
} yield tx
}
/** Determines if the given output is from this wallet and is a change output
* from this wallet
*/
@ -365,4 +185,4 @@ case class WalletInfo(
imported: Boolean)
/** An HDWallet that uses Neutrino to sync */
trait NeutrinoHDWalletApi extends HDWalletApi with NeutrinoWalletApi
trait NeutrinoHDWalletApi extends WalletApi with NeutrinoWalletApi

View file

@ -96,7 +96,7 @@ class DLCWalletCallbackTest extends BitcoinSDualWalletTest {
_ <- initF
contractId <- DLCWalletUtil.getContractId(wallets._1.wallet)
fundingTx <- walletA.getDLCFundingTx(contractId)
_ <- walletA.processTransaction(
_ <- walletA.transactionProcessing.processTransaction(
transaction = fundingTx,
blockHashOpt = Some(CryptoGenerators.doubleSha256DigestBE.sample.get)
)
@ -107,7 +107,7 @@ class DLCWalletCallbackTest extends BitcoinSDualWalletTest {
}
}
transaction <- walletA.executeDLC(contractId, sigs._1).map(_.get)
_ <- walletB.processTransaction(transaction, None)
_ <- walletB.transactionProcessing.processTransaction(transaction, None)
} yield ()
for {
@ -207,12 +207,12 @@ class DLCWalletCallbackTest extends BitcoinSDualWalletTest {
_ <- initF
contractId <- DLCWalletUtil.getContractId(wallets._1.wallet)
fundingTx <- walletA.getDLCFundingTx(contractId)
_ <- walletA.processTransaction(
_ <- walletA.transactionProcessing.processTransaction(
transaction = fundingTx,
blockHashOpt = Some(CryptoGenerators.doubleSha256DigestBE.sample.get)
)
transaction <- walletA.executeDLCRefund(contractId)
_ <- walletB.processTransaction(transaction, None)
_ <- walletB.transactionProcessing.processTransaction(transaction, None)
} yield ()
for {

View file

@ -45,10 +45,10 @@ class MultiWalletDLCTest extends BitcoinSWalletTest {
)(configB, system)
for {
accountA <- walletA.getDefaultAccount()
accountA <- walletA.accountHandling.getDefaultAccount()
walletB <- walletBF
accountB <- walletB.getDefaultAccount()
accountB <- walletB.accountHandling.getDefaultAccount()
_ = assert(accountA.xpub != accountB.xpub)

View file

@ -714,7 +714,7 @@ class WalletDLCSetupTest extends BitcoinSDualWalletTest {
tx <- walletB.broadcastDLCFundingTx(sign.contractId)
// make sure other wallet sees it
_ <- walletA.processTransaction(tx, None)
_ <- walletA.transactionProcessing.processTransaction(tx, None)
dlcId = calcDLCId(offer.fundingInputs.map(_.outPoint))
@ -752,7 +752,7 @@ class WalletDLCSetupTest extends BitcoinSDualWalletTest {
tx <- walletB.broadcastDLCFundingTx(sign.contractId)
// make sure other wallet sees it
_ <- walletA.processTransaction(tx, None)
_ <- walletA.transactionProcessing.processTransaction(tx, None)
dlcId = calcDLCId(offer.fundingInputs.map(_.outPoint))
@ -884,7 +884,7 @@ class WalletDLCSetupTest extends BitcoinSDualWalletTest {
}
tx <- walletB.broadcastDLCFundingTx(sign.contractId)
_ <- walletA.processTransaction(tx, None)
_ <- walletA.transactionProcessing.processTransaction(tx, None)
func = (wallet: DLCWallet) =>
wallet.executeDLC(sign.contractId, sig).map(_.get)

View file

@ -113,8 +113,7 @@ abstract class DLCWallet
DLCActionBuilder(dlcWalletDAOs)
}
override protected lazy val transactionProcessing
: DLCTransactionProcessing = {
override lazy val transactionProcessing: DLCTransactionProcessing = {
val txProcessing = TransactionProcessing(
walletApi = this,
chainQueryApi = chainQueryApi,
@ -431,7 +430,7 @@ abstract class DLCWallet
_ <- oracleNonceDAO.createAll(nonceDbs)
chainType = HDChainType.External
account <- getDefaultAccountForType(AddressType.SegWit)
account <- accountHandling.getDefaultAccountForType(AddressType.SegWit)
nextIndex <- addressHandling.getNextAvailableIndex(account, chainType)
_ <- writeDLCKeysToAddressDb(account, chainType, nextIndex)
@ -880,7 +879,7 @@ abstract class DLCWallet
s"Creating DLC Accept for tempContractId ${offer.tempContractId.hex}"
)
val result = for {
account <- getDefaultAccountForType(AddressType.SegWit)
account <- accountHandling.getDefaultAccountForType(AddressType.SegWit)
fundRawTxHelper <- fundDLCAcceptMsg(
offer = offer,
collateral = collateral,

View file

@ -79,6 +79,6 @@ walletConf.addCallbacks(exampleCallbacks)
// Then to trigger the event we can run
val exampleTx = Transaction(
"0200000000010258e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd7500000000da00473044022074018ad4180097b873323c0015720b3684cc8123891048e7dbcd9b55ad679c99022073d369b740e3eb53dcefa33823c8070514ca55a7dd9544f157c167913261118c01483045022100f61038b308dc1da865a34852746f015772934208c6d24454393cd99bdf2217770220056e675a675a6d0a02b85b14e5e29074d8a25a9b5760bea2816f661910a006ea01475221029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f2102dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d752aeffffffff838d0427d0ec650a68aa46bb0b098aea4422c071b2ca78352a077959d07cea1d01000000232200208c2353173743b595dfb4a07b72ba8e42e3797da74e87fe7d9d7497e3b2028903ffffffff0270aaf00800000000160014d85c2b71d0060b09c9886aeb815e50991dda124d00e1f5050000000016001400aea9a2e5f0f876a588df5546e8742d1d87008f000400473044022062eb7a556107a7c73f45ac4ab5a1dddf6f7075fb1275969a7f383efff784bcb202200c05dbb7470dbf2f08557dd356c7325c1ed30913e996cd3840945db12228da5f01473044022065f45ba5998b59a27ffe1a7bed016af1f1f90d54b3aa8f7450aa5f56a25103bd02207f724703ad1edb96680b284b56d4ffcb88f7fb759eabbe08aa30f29b851383d20147522103089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc21023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7352ae00000000")
wallet.processTransaction(exampleTx, None)
wallet.transactionProcessing.processTransaction(exampleTx, None)
```

View file

@ -90,11 +90,11 @@ val initBalanceF = for {
val clearedWalletF = for {
w <- walletF
_ <- initBalanceF
clearedWallet <- w.clearAllUtxos()
zeroBalance <- clearedWallet.getBalance()
_<- w.utxoHandling.clearAllUtxos()
zeroBalance <- w.getBalance()
} yield {
println(s"Balance after clearing utxos: ${zeroBalance}")
clearedWallet
w
}
//we need to pick how many addresses we want to generate off of our keychain

View file

@ -170,7 +170,7 @@ val transactionF: Future[(Transaction, Option[DoubleSha256DigestBE])] = for {
val balanceF: Future[CurrencyUnit] = for {
wallet <- walletF
(tx, blockhash) <- transactionF
_ <- wallet.processTransaction(tx, blockhash)
_ <- wallet.transactionProcessing.processTransaction(tx, blockhash)
balance <- wallet.getBalance()
} yield balance

View file

@ -60,7 +60,7 @@ class NeutrinoNodeWithWalletTest extends NodeTestWithCachedBitcoindNewest {
for {
balance <- wallet.getBalance()
addresses <- wallet.addressHandling.listAddresses()
utxos <- wallet.listDefaultAccountUtxos()
utxos <- wallet.utxoHandling.listDefaultAccountUtxos()
} yield {
// +- fee rate because signatures could vary in size
(expectedBalance === balance +- FeeRate.currencyUnit) &&
@ -97,7 +97,9 @@ class NeutrinoNodeWithWalletTest extends NodeTestWithCachedBitcoindNewest {
for {
// send
addr <- bitcoind.getNewAddress
_ <- wallet.sendToAddress(addr, TestAmount, Some(FeeRate))
_ <- wallet.sendFundsHandling.sendToAddress(addr,
TestAmount,
Some(FeeRate))
_ <- wallet.getConfirmedBalance()
_ <- wallet.getUnconfirmedBalance()
@ -148,7 +150,7 @@ class NeutrinoNodeWithWalletTest extends NodeTestWithCachedBitcoindNewest {
_ <- wallet.addressHandling.watchScriptPubKey(spk)
// send
txSent <- wallet.sendToOutputs(Vector(output), FeeRate)
txSent <- wallet.sendFundsHandling.sendToOutputs(Vector(output), FeeRate)
_ <- node.broadcastTransaction(txSent)
// confirm
@ -169,7 +171,7 @@ class NeutrinoNodeWithWalletTest extends NodeTestWithCachedBitcoindNewest {
rescan <- wallet.isRescanning()
balance <- wallet.getBalance()
addresses <- wallet.addressHandling.listAddresses()
utxos <- wallet.listDefaultAccountUtxos()
utxos <- wallet.utxoHandling.listDefaultAccountUtxos()
spks = utxos
.map(_.output.scriptPubKey)
} yield {
@ -182,7 +184,7 @@ class NeutrinoNodeWithWalletTest extends NodeTestWithCachedBitcoindNewest {
for {
addresses <- wallet.addressHandling.listAddresses()
utxos <- wallet.listDefaultAccountUtxos()
utxos <- wallet.utxoHandling.listDefaultAccountUtxos()
_ = assert(addresses.size == 6)
_ = assert(utxos.size == 3)
@ -192,7 +194,7 @@ class NeutrinoNodeWithWalletTest extends NodeTestWithCachedBitcoindNewest {
.sendToAddress(address, TestAmount)
addresses <- wallet.addressHandling.listAddresses()
utxos <- wallet.listDefaultAccountUtxos()
utxos <- wallet.utxoHandling.listDefaultAccountUtxos()
_ = assert(addresses.size == 7)
_ = assert(utxos.size == 3)
_ <-
@ -206,9 +208,9 @@ class NeutrinoNodeWithWalletTest extends NodeTestWithCachedBitcoindNewest {
.getSyncDescriptorOpt()
.map(_.get.height == bitcoindHeight)
})
_ <- wallet.clearAllUtxos()
_ <- wallet.utxoHandling.clearAllUtxos()
addresses <- wallet.addressHandling.listAddresses()
utxos <- wallet.listDefaultAccountUtxos()
utxos <- wallet.utxoHandling.listDefaultAccountUtxos()
_ = assert(addresses.nonEmpty)
_ = assert(utxos.isEmpty)
@ -282,7 +284,9 @@ class NeutrinoNodeWithWalletTest extends NodeTestWithCachedBitcoindNewest {
stoppedNode <- stopF
// create a transaction that spends to bitcoind with our wallet
tx <- wallet.sendToAddress(bitcoindAddr, sendAmt, SatoshisPerByte.one)
tx <- wallet.sendFundsHandling.sendToAddress(bitcoindAddr,
sendAmt,
SatoshisPerByte.one)
// broadcast tx
_ <- bitcoind.sendRawTransaction(tx)
_ <- bitcoind.generateToAddress(1, bitcoindAddr)

View file

@ -599,8 +599,9 @@ object BitcoinSWalletTest extends WalletLogger {
val hdAccount1 = WalletTestUtil.getHdAccount1(config)
val expectedDefaultAmt = BitcoinSWalletTest.expectedDefaultAmt
val expectedAccount1Amt = BitcoinSWalletTest.expectedAccount1Amt
val defaultBalanceF = fundedWallet.wallet.getBalance(defaultAccount)
val account1BalanceF = fundedWallet.wallet.getBalance(hdAccount1)
val accountHandling = fundedWallet.wallet.accountHandling
val defaultBalanceF = accountHandling.getBalance(defaultAccount)
val account1BalanceF = accountHandling.getBalance(hdAccount1)
for {
balance <- defaultBalanceF
account1Balance <- account1BalanceF

View file

@ -401,7 +401,7 @@ object DLCWalletUtil extends BitcoinSLogger {
sigs <- walletA.signDLC(accept)
_ <- walletB.addDLCSigs(sigs)
tx <- walletB.broadcastDLCFundingTx(sigs.contractId)
_ <- walletA.processTransaction(tx, None)
_ <- walletA.transactionProcessing.processTransaction(tx, None)
} yield {
(
InitializedDLCWallet(FundedDLCWallet(walletA)),
@ -478,8 +478,8 @@ object DLCWalletUtil extends BitcoinSLogger {
func(dlcB)
}
_ <- {
if (asInitiator) dlcB.processTransaction(tx, None)
else dlcA.processTransaction(tx, None)
if (asInitiator) dlcB.transactionProcessing.processTransaction(tx, None)
else dlcA.transactionProcessing.processTransaction(tx, None)
}
_ <- dlcA.broadcastTransaction(tx)
dlcDb <- dlcA.dlcDAO.findByContractId(contractId)

View file

@ -4,7 +4,7 @@ import org.apache.pekko.actor.ActorSystem
import org.bitcoins.commons.util.BitcoinSLogger
import org.bitcoins.core.api.chain.ChainQueryApi
import org.bitcoins.core.api.node.NodeApi
import org.bitcoins.core.api.wallet.HDWalletApi
import org.bitcoins.core.api.wallet.NeutrinoHDWalletApi
import org.bitcoins.core.currency.CurrencyUnit
import org.bitcoins.core.hd.HDAccount
import org.bitcoins.core.protocol.BitcoinAddress
@ -87,7 +87,8 @@ trait FundWalletUtil extends BitcoinSLogger {
val fundedWalletF =
txsF.flatMap(txs =>
FutureUtil.sequentially(txs)(tx => wallet.processTransaction(tx, None)))
FutureUtil.sequentially(txs)(tx =>
wallet.transactionProcessing.processTransaction(tx, None)))
fundedWalletF.map(_ => wallet)
}
@ -95,9 +96,9 @@ trait FundWalletUtil extends BitcoinSLogger {
def fundAccountForWalletWithBitcoind(
amts: Vector[CurrencyUnit],
account: HDAccount,
wallet: HDWalletApi,
wallet: NeutrinoHDWalletApi,
bitcoind: BitcoindRpcClient
)(implicit ec: ExecutionContext): Future[HDWalletApi] = {
)(implicit ec: ExecutionContext): Future[NeutrinoHDWalletApi] = {
val addressesF: Future[Vector[BitcoinAddress]] = Future.sequence {
Vector.fill(3)(wallet.accountHandling.getNewAddress(account))
@ -107,7 +108,7 @@ trait FundWalletUtil extends BitcoinSLogger {
addresses <- addressesF
addressAmountMap = addresses.zip(amts).toMap
(tx, blockHash) <- fundAddressesWithBitcoind(addressAmountMap, bitcoind)
_ <- wallet.processTransaction(tx, Some(blockHash))
_ <- wallet.transactionProcessing.processTransaction(tx, Some(blockHash))
} yield (tx, blockHash)
txAndHashF.map(_ => wallet)
@ -154,13 +155,14 @@ trait FundWalletUtil extends BitcoinSLogger {
// sanity check to make sure we have money
for {
fundedWallet <- fundedAccount1WalletF
balance <- fundedWallet.getBalance(defaultAccount)
accountHandling = fundedWallet.accountHandling
balance <- accountHandling.getBalance(defaultAccount)
_ = require(
balance == BitcoinSWalletTest.expectedDefaultAmt,
s"Funding wallet fixture failed to fund the wallet, got balance=${balance} expected=${BitcoinSWalletTest.expectedDefaultAmt}"
)
account1Balance <- fundedWallet.getBalance(hdAccount1)
account1Balance <- accountHandling.getBalance(hdAccount1)
_ = require(
account1Balance == BitcoinSWalletTest.expectedAccount1Amt,
s"Funding wallet fixture failed to fund account 1, " +

View file

@ -73,7 +73,9 @@ class AddressHandlingTest extends BitcoinSWalletTest {
_ = assert(exists, s"Wallet must contain address after generating it")
address2 <- wallet.addressHandling.getUnusedAddress
_ = assert(address1 == address2, "Must generate same address")
_ <- wallet.sendToAddress(address1, Satoshis(10000), None)
_ <- wallet.sendFundsHandling.sendToAddress(address1,
Satoshis(10000),
None)
address3 <- wallet.addressHandling.getUnusedAddress
} yield {
assert(address1 != address3, "Must generate a new address")
@ -111,7 +113,9 @@ class AddressHandlingTest extends BitcoinSWalletTest {
)
tempAddress <- wallet.addressHandling.getNewAddress()
tx <- wallet.sendToAddress(tempAddress, Bitcoins(1), None)
tx <- wallet.sendFundsHandling.sendToAddress(tempAddress,
Bitcoins(1),
None)
spentDbs <- wallet.spendingInfoDAO.findOutputsBeingSpent(tx)
spentAddresses <- wallet.addressHandling.listSpentAddresses()
} yield {

View file

@ -56,7 +56,7 @@ class AddressTagIntegrationTest extends BitcoinSWalletTest {
.map(unconfirmed => assert(unconfirmed == 0.bitcoin))
// after this, tx is unconfirmed in wallet
_ <- wallet.processTransaction(tx, None)
_ <- wallet.transactionProcessing.processTransaction(tx, None)
// we should now have one UTXO in the wallet
// it should not be confirmed
@ -93,7 +93,7 @@ class AddressTagIntegrationTest extends BitcoinSWalletTest {
)
}
signedTx = rawTxHelper.signedTx
_ <- wallet.processTransaction(signedTx, None)
_ <- wallet.transactionProcessing.processTransaction(signedTx, None)
utxos <- wallet.listUtxos()
balancePostSend <- wallet.getBalance()
@ -137,15 +137,15 @@ class AddressTagIntegrationTest extends BitcoinSWalletTest {
walletAddr1 <- walletAddr1F
txid <- bitcoind.sendToAddress(walletAddr1, Bitcoins.two)
tx <- bitcoind.getRawTransaction(txid)
_ <- wallet.processTransaction(tx.hex, None)
_ <- wallet.transactionProcessing.processTransaction(tx.hex, None)
taggedAddress <- taggedAddrF
tx <- wallet.sendToAddress(
tx <- wallet.sendFundsHandling.sendToAddress(
taggedAddress,
Satoshis(100000),
SatoshisPerVirtualByte.one,
Vector(exampleTag)
)
_ <- wallet.processTransaction(tx, None)
_ <- wallet.transactionProcessing.processTransaction(tx, None)
_ <- bitcoind.sendRawTransaction(tx)
bitcoindAddr <- bitcoindAddrF
_ <- PekkoUtil.nonBlockingSleep(1.second)

View file

@ -51,7 +51,7 @@ class BitcoindBlockPollingTest
wallet,
bitcoind,
None
)(wallet.processBlock(_).map(_ => ()))
)(wallet.transactionProcessing.processBlock(_).map(_ => ()))
_ <- bitcoind.generateToAddress(6, bech32Address)
// Wait for it to process

View file

@ -1,10 +1,10 @@
package org.bitcoins.wallet
import org.bitcoins.core.api.wallet.HDWalletApi
import org.bitcoins.core.api.wallet.NeutrinoHDWalletApi
import org.bitcoins.core.currency.Bitcoins
import org.bitcoins.core.protocol.transaction.TransactionOutput
import org.bitcoins.core.wallet.utxo.StorageLocationTag.HotStorage
import org.bitcoins.core.wallet.utxo._
import org.bitcoins.core.wallet.utxo.*
import org.bitcoins.testkit.wallet.{
BitcoinSWalletTestCachedBitcoindNewest,
WalletTestUtil,
@ -173,7 +173,7 @@ class FundTransactionHandlingTest
for {
feeRate <- wallet.getFeeRate()
account1DbOpt <- account1DbF
fundRawTxHelper <- wallet.fundRawTransaction(
fundRawTxHelper <- wallet.fundTxHandling.fundRawTransaction(
Vector(newDestination),
feeRate,
account1DbOpt.get,
@ -199,7 +199,7 @@ class FundTransactionHandlingTest
val fundedTxF = for {
feeRate <- wallet.getFeeRate()
account1DbOpt <- account1DbF
fundedTx <- wallet.fundRawTransaction(
fundedTx <- wallet.fundTxHandling.fundRawTransaction(
destinations = Vector(newDestination),
feeRate = feeRate,
fromAccount = account1DbOpt.get,
@ -218,22 +218,20 @@ class FundTransactionHandlingTest
val bitcoind = fundedWallet.bitcoind
val fundedTxF = for {
feeRate <- wallet.getFeeRate()
_ <- wallet.accountHandling.createNewAccount(
wallet.keyManager.kmParams.purpose)
accounts <- wallet.listAccounts()
accounts <- wallet.accountHandling.listAccounts()
account2 = accounts.find(_.hdAccount.index == 2).get
addr <- wallet.accountHandling.getNewAddress(account2)
hash <- bitcoind.generateToAddress(1, addr).map(_.head)
block <- bitcoind.getBlockRaw(hash)
_ <- wallet.processBlock(block)
_ <- wallet.transactionProcessing.processBlock(block)
utxos <- wallet.listUtxos(account2.hdAccount)
utxos <- wallet.utxoHandling.listUtxos(account2.hdAccount)
_ = assert(utxos.size == 1)
fundedTx <-
wallet.fundRawTransaction(
wallet.fundTxHandling.fundRawTransaction(
destinations = Vector(destination),
feeRate = feeRate,
fromAccount = account2,
@ -269,14 +267,16 @@ class FundTransactionHandlingTest
}
def testAddressTagFunding(
wallet: HDWalletApi,
wallet: NeutrinoHDWalletApi,
tag: AddressTag
): Future[Assertion] = {
for {
feeRate <- wallet.getFeeRate()
taggedAddr <- wallet.addressHandling.getNewAddress(Vector(tag))
_ <-
wallet.sendToAddress(taggedAddr, destination.value * 2, Some(feeRate))
wallet.sendFundsHandling.sendToAddress(taggedAddr,
destination.value * 2,
Some(feeRate))
taggedBalance <- wallet.getBalance(tag)
_ = assert(taggedBalance == destination.value * 2)

View file

@ -15,7 +15,7 @@ class LegacyWalletTest extends BitcoinSWalletTest {
it should "generate legacy addresses" in { (wallet: Wallet) =>
for {
addr <- wallet.getNewAddress()
account <- wallet.getDefaultAccount()
account <- wallet.accountHandling.getDefaultAccount()
otherAddr <- wallet.getNewAddress()
thirdAddr <- wallet.addressHandling.getNewAddress(AddressType.Legacy)
allAddrs <- wallet.addressHandling.listAddresses()
@ -31,7 +31,8 @@ class LegacyWalletTest extends BitcoinSWalletTest {
it should "generate segwit addresses" in { wallet =>
for {
account <- wallet.getDefaultAccountForType(AddressType.SegWit)
account <- wallet.accountHandling.getDefaultAccountForType(
AddressType.SegWit)
addr <- wallet.addressHandling.getNewAddress(AddressType.SegWit)
} yield {
assert(account.hdAccount.purpose == HDPurpose.SegWit)

View file

@ -41,10 +41,10 @@ class MultiWalletTest extends BitcoinSAsyncTest with EmbeddedPg {
val assertionF: Future[Assertion] = for {
walletA <- walletAF
accountA <- walletA.getDefaultAccount()
accountA <- walletA.accountHandling.getDefaultAccount()
walletB <- walletBF
accountB <- walletB.getDefaultAccount()
accountB <- walletB.accountHandling.getDefaultAccount()
_ <- walletA.walletConfig.stop()
_ <- walletB.walletConfig.stop()
} yield assert(accountA.xpub != accountB.xpub)

View file

@ -50,7 +50,7 @@ class ProcessBlockTest extends BitcoinSWalletTestCachedBitcoindNewest {
.map(_.head)
block <- bitcoind.getBlockRaw(hash)
_ <- wallet.processBlock(block)
_ <- wallet.transactionProcessing.processBlock(block)
utxos <- wallet.listUtxos()
height <- bitcoind.getBlockCount()
bestHash <- bitcoind.getBestBlockHash()
@ -78,7 +78,8 @@ class ProcessBlockTest extends BitcoinSWalletTestCachedBitcoindNewest {
addr <- wallet.getNewAddress()
hashes <- bitcoind.generateToAddress(101, addr)
blocks <- FutureUtil.sequentially(hashes)(bitcoind.getBlockRaw)
_ <- FutureUtil.sequentially(blocks)(wallet.processBlock)
_ <- FutureUtil.sequentially(blocks)(
wallet.transactionProcessing.processBlock)
coinbaseUtxos <- wallet.listUtxos(TxoState.ImmatureCoinbase)
confirmedUtxos <- wallet.listUtxos(TxoState.ConfirmedReceived)
balance <- wallet.getConfirmedBalance()
@ -203,7 +204,7 @@ class ProcessBlockTest extends BitcoinSWalletTestCachedBitcoindNewest {
.addUTXOToInput(recvTx, 0)
.addKeyPathToInput(accountDb.xpub, bip32Path, addrDb.pubkey, 0)
signed <- wallet.signPSBT(psbt)
signed <- wallet.sendFundsHandling.signPSBT(psbt)
tx <- Future.fromTry(
signed.finalizePSBT.flatMap(_.extractTransactionAndValidate)
)
@ -212,7 +213,7 @@ class ProcessBlockTest extends BitcoinSWalletTestCachedBitcoindNewest {
hash <- bitcoind.generateToAddress(1, bitcoindAddr).map(_.head)
block <- bitcoind.getBlockRaw(hash)
_ <- wallet.processBlock(block)
_ <- wallet.transactionProcessing.processBlock(block)
balance <- wallet.getBalance()
} yield assert(balance == output0.value)

View file

@ -64,16 +64,16 @@ class ProcessTransactionTest extends BitcoinSWalletTest {
tx =
TransactionTestUtil.buildTransactionTo(output, outPoint)
_ <- wallet.processTransaction(tx, None)
_ <- wallet.transactionProcessing.processTransaction(tx, None)
oldConfirmed <- wallet.getConfirmedBalance()
oldUnconfirmed <- wallet.getUnconfirmedBalance()
// repeating the action should not make a difference
_ <- checkUtxosAndBalance(wallet) {
wallet.processTransaction(tx, None)
wallet.transactionProcessing.processTransaction(tx, None)
}
_ <- wallet.processTransaction(
_ <- wallet.transactionProcessing.processTransaction(
tx,
Some(MockChainQueryApi.testBlockHash)
)
@ -83,7 +83,9 @@ class ProcessTransactionTest extends BitcoinSWalletTest {
// repeating the action should not make a difference
_ <- checkUtxosAndBalance(wallet) {
wallet.processTransaction(tx, Some(MockChainQueryApi.testBlockHash))
wallet.transactionProcessing.processTransaction(
tx,
Some(MockChainQueryApi.testBlockHash))
}
} yield {
val ourOutputs =
@ -104,23 +106,23 @@ class ProcessTransactionTest extends BitcoinSWalletTest {
.transactionTo(address.scriptPubKey)
.sampleSome
_ <- wallet.processTransaction(tx, None)
_ <- wallet.transactionProcessing.processTransaction(tx, None)
oldConfirmed <- wallet.getConfirmedBalance()
oldUnconfirmed <- wallet.getUnconfirmedBalance()
// repeating the action should not make a difference
_ <- checkUtxosAndBalance(wallet) {
wallet.processTransaction(tx, None)
wallet.transactionProcessing.processTransaction(tx, None)
}
_ <- wallet.processTransaction(tx, None)
_ <- wallet.transactionProcessing.processTransaction(tx, None)
newConfirmed <- wallet.getConfirmedBalance()
newUnconfirmed <- wallet.getUnconfirmedBalance()
utxosPostAdd <- wallet.listUtxos()
// repeating the action should not make a difference
_ <- checkUtxosAndBalance(wallet) {
wallet.processTransaction(tx, None)
wallet.transactionProcessing.processTransaction(tx, None)
}
} yield {
val ourOutputs =
@ -137,7 +139,7 @@ class ProcessTransactionTest extends BitcoinSWalletTest {
val unrelated = TransactionGenerators.transaction.sampleSome
for {
_ <- checkUtxosAndBalance(wallet) {
wallet.processTransaction(unrelated, None)
wallet.transactionProcessing.processTransaction(unrelated, None)
}
balance <- wallet.getBalance()
@ -173,7 +175,8 @@ class ProcessTransactionTest extends BitcoinSWalletTest {
// make sure wallet is empty
balance <- wallet.getBalance()
_ = assert(balance == Bitcoins.zero)
processed <- wallet.processTransaction(fundingTx, None)
processed <- wallet.transactionProcessing.processTransaction(fundingTx,
None)
balance <- wallet.getBalance()
_ = assert(balance == amtWithFee)
} yield processed
@ -191,7 +194,7 @@ class ProcessTransactionTest extends BitcoinSWalletTest {
fromTagOpt = None,
markAsReserved = true
)
_ <- wallet.processTransaction(
_ <- wallet.transactionProcessing.processTransaction(
transaction = rawTxHelper.signedTx,
blockHashOpt = None
)
@ -210,8 +213,8 @@ class ProcessTransactionTest extends BitcoinSWalletTest {
.transactionTo(address.scriptPubKey)
.sampleSome
_ <- wallet.processTransaction(tx, None)
accountBalance <- wallet.getUnconfirmedBalance(account1)
_ <- wallet.transactionProcessing.processTransaction(tx, None)
accountBalance <- wallet.accountHandling.getUnconfirmedBalance(account1)
} yield {
assert(accountBalance == CurrencyUnits.zero)
}

View file

@ -24,9 +24,9 @@ class RescanHandlingTest extends BitcoinSWalletTestCachedBitcoindNewest {
val wallet = fixture.wallet
for {
accountDb <- wallet.getDefaultAccount()
accountDb <- wallet.accountHandling.getDefaultAccount()
account = accountDb.hdAccount
utxos <- wallet.listUtxos(account)
utxos <- wallet.utxoHandling.listUtxos(account)
_ = assert(utxos.nonEmpty)
addresses <- wallet.accountHandling.listAddresses(account)
@ -34,7 +34,7 @@ class RescanHandlingTest extends BitcoinSWalletTestCachedBitcoindNewest {
_ <- wallet.accountHandling.clearUtxos(account)
clearedUtxos <- wallet.listUtxos(account)
clearedUtxos <- wallet.utxoHandling.listUtxos(account)
clearedAddresses <- wallet.accountHandling.listAddresses(account)
} yield {
assert(clearedUtxos.isEmpty)
@ -55,7 +55,7 @@ class RescanHandlingTest extends BitcoinSWalletTestCachedBitcoindNewest {
addresses <- wallet.addressHandling.listAddresses()
_ = assert(addresses.nonEmpty)
_ <- wallet.clearAllUtxos()
_ <- wallet.utxoHandling.clearAllUtxos()
clearedUtxos <- wallet.listUtxos()
clearedAddresses <- wallet.addressHandling.listAddresses()
@ -110,7 +110,7 @@ class RescanHandlingTest extends BitcoinSWalletTestCachedBitcoindNewest {
bitcoindAddr <- bitcoindAddrF
blockHashes <-
bitcoind.generateToAddress(blocks = numBlocks, address = bitcoindAddr)
_ <- wallet.processTransaction(
_ <- wallet.transactionProcessing.processTransaction(
transaction = tx,
blockHashOpt = blockHashes.headOption
)
@ -131,7 +131,7 @@ class RescanHandlingTest extends BitcoinSWalletTestCachedBitcoindNewest {
initBlockHeight <- initBlockHeightF
txInBlockHeight = initBlockHeight + numBlocks
txInBlockHeightOpt = Some(BlockStamp.BlockHeight(txInBlockHeight))
_ <- wallet.clearAllUtxos()
_ <- wallet.utxoHandling.clearAllUtxos()
zeroBalance <- wallet.getBalance()
_ = assert(zeroBalance == Satoshis.zero)
rescanState <- wallet.rescanHandling.rescanNeutrinoWallet(
@ -159,7 +159,7 @@ class RescanHandlingTest extends BitcoinSWalletTestCachedBitcoindNewest {
val numBlocks = 1
val initBalanceF = wallet.getBalance()
val defaultAccountF = wallet.getDefaultAccount()
val defaultAccountF = wallet.accountHandling.getDefaultAccount()
// send funds to a fresh wallet address
val addrF = wallet.getNewAddress()
val bitcoindAddrF = bitcoind.getNewAddress
@ -171,7 +171,7 @@ class RescanHandlingTest extends BitcoinSWalletTestCachedBitcoindNewest {
bitcoindAddr <- bitcoindAddrF
blockHashes <-
bitcoind.generateToAddress(blocks = numBlocks, address = bitcoindAddr)
_ <- wallet.processTransaction(
_ <- wallet.transactionProcessing.processTransaction(
transaction = tx,
blockHashOpt = blockHashes.headOption
)
@ -191,15 +191,15 @@ class RescanHandlingTest extends BitcoinSWalletTestCachedBitcoindNewest {
account <- defaultAccountF
txIds <-
wallet
wallet.utxoHandling
.listUtxos(account.hdAccount)
.map(_.map(_.txid))
_ <- wallet
.findByTxIds(txIds)
.map(_.flatMap(_.blockHashOpt))
_ <- wallet.clearAllUtxos()
_ <- wallet.clearAllAddresses()
_ <- wallet.utxoHandling.clearAllUtxos()
_ <- wallet.utxoHandling.clearAllAddresses()
balanceAfterClear <- wallet.getBalance()
rescanState <- wallet.rescanHandling.fullRescanNeutrinoWallet(
addressBatchSize = 1,
@ -235,7 +235,7 @@ class RescanHandlingTest extends BitcoinSWalletTestCachedBitcoindNewest {
bitcoindAddr <- bitcoindAddrF
blockHashes <-
bitcoind.generateToAddress(blocks = numBlocks, address = bitcoindAddr)
_ <- wallet.processTransaction(
_ <- wallet.transactionProcessing.processTransaction(
transaction = tx,
blockHashOpt = blockHashes.headOption
)
@ -371,7 +371,7 @@ class RescanHandlingTest extends BitcoinSWalletTestCachedBitcoindNewest {
// now send a payment to our wallet
hashes <- bitcoind.generateToAddress(1, address)
block <- bitcoind.getBlockRaw(hashes.head)
_ <- wallet.processBlock(block)
_ <- wallet.transactionProcessing.processBlock(block)
fundedAddresses <- wallet.addressHandling.listFundedAddresses()
utxos <- wallet.listUtxos(TxoState.ImmatureCoinbase)
} yield {
@ -412,7 +412,7 @@ class RescanHandlingTest extends BitcoinSWalletTestCachedBitcoindNewest {
)
txid <- bitcoind.sendToAddress(addressNoFunds, amt)
tx <- bitcoind.getRawTransactionRaw(txid)
_ <- wallet.processTransaction(tx, None)
_ <- wallet.transactionProcessing.processTransaction(tx, None)
unconfirmedBalance <- wallet.getUnconfirmedBalance()
} yield {
assert(unconfirmedBalance == amt)
@ -505,7 +505,7 @@ class RescanHandlingTest extends BitcoinSWalletTestCachedBitcoindNewest {
val numBlocks = 1
val initBalanceF = wallet.getBalance()
val defaultAccountF = wallet.getDefaultAccount()
val defaultAccountF = wallet.accountHandling.getDefaultAccount()
// send funds to a fresh wallet address
val addr1F = wallet.getNewAddress()
val addr2F = wallet.getNewAddress()
@ -523,7 +523,7 @@ class RescanHandlingTest extends BitcoinSWalletTestCachedBitcoindNewest {
bitcoindAddr <- bitcoindAddrF
blockHashes <-
bitcoind.generateToAddress(blocks = numBlocks, address = bitcoindAddr)
_ <- wallet.processTransaction(
_ <- wallet.transactionProcessing.processTransaction(
transaction = tx,
blockHashOpt = blockHashes.headOption
)
@ -543,15 +543,15 @@ class RescanHandlingTest extends BitcoinSWalletTestCachedBitcoindNewest {
account <- defaultAccountF
txIds <-
wallet
wallet.utxoHandling
.listUtxos(account.hdAccount)
.map(_.map(_.txid))
_ <- wallet
.findByTxIds(txIds)
.map(_.flatMap(_.blockHashOpt))
_ <- wallet.clearAllUtxos()
_ <- wallet.clearAllAddresses()
_ <- wallet.utxoHandling.clearAllUtxos()
_ <- wallet.utxoHandling.clearAllAddresses()
balanceAfterClear <- wallet.getBalance()
rescanState <- wallet.rescanHandling.fullRescanNeutrinoWallet(1, true)
_ <- RescanState.awaitRescanDone(rescanState)
@ -574,8 +574,8 @@ class RescanHandlingTest extends BitcoinSWalletTestCachedBitcoindNewest {
// need to clear all utxos / addresses to test this
// otherwise the wallet will see we already have addresses and not generate
// a fresh pool of addresses
_ <- wallet.clearAllUtxos()
_ <- wallet.clearAllAddresses()
_ <- wallet.utxoHandling.clearAllUtxos()
_ <- wallet.utxoHandling.clearAllAddresses()
rescanState <- wallet.rescanHandling.fullRescanNeutrinoWallet(
DEFAULT_ADDR_BATCH_SIZE)
_ = assert(rescanState.isInstanceOf[RescanState.RescanStarted])

View file

@ -16,7 +16,7 @@ class SegwitWalletTest extends BitcoinSWalletTest {
it should "generate segwit addresses" in { wallet =>
for {
addr <- wallet.getNewAddress()
account <- wallet.getDefaultAccount()
account <- wallet.accountHandling.getDefaultAccount()
otherAddr <- wallet.getNewAddress()
thirdAddr <- wallet.addressHandling.getNewAddress(AddressType.SegWit)
allAddrs <- wallet.addressHandling.listAddresses()
@ -32,7 +32,8 @@ class SegwitWalletTest extends BitcoinSWalletTest {
it should "generate legacy addresses" in { wallet =>
for {
account <- wallet.getDefaultAccountForType(AddressType.Legacy)
account <- wallet.accountHandling.getDefaultAccountForType(
AddressType.Legacy)
addr <- wallet.addressHandling.getNewAddress(AddressType.Legacy)
} yield {
assert(account.hdAccount.purpose == HDPurpose.Legacy)

View file

@ -246,14 +246,14 @@ class TrezorAddressTest extends BitcoinSWalletTest with EmptyFixture {
val assertionsF: Future[Seq[Assertion]] = for {
wallet <- getWallet(conf)
existingAccounts <- wallet.listAccounts(purpose)
existingAccounts <- wallet.accountHandling.listAccounts(purpose)
_ <- createNeededAccounts(
wallet,
existingAccounts,
conf.kmParams,
testVectors
)
accounts <- wallet.listAccounts(purpose)
accounts <- wallet.accountHandling.listAccounts(purpose)
// we want to find all accounts for the given account type,
// and match it with its corresponding test vector
accountsWithVectors = {

View file

@ -51,7 +51,9 @@ class UTXOLifeCycleTest
for {
oldTransactions <- wallet.listTransactions()
tx <- wallet.sendToAddress(testAddr, Satoshis(3000), None)
tx <- wallet.sendFundsHandling.sendToAddress(testAddr,
Satoshis(3000),
None)
updatedCoins <- wallet.findOutputsBeingSpent(tx)
newTransactions <- wallet.listTransactions()
@ -69,7 +71,9 @@ class UTXOLifeCycleTest
val walletConfig = param.walletConfig
for {
oldTransactions <- wallet.listTransactions()
tx <- wallet.sendToAddress(testAddr, Satoshis(3000), None)
tx <- wallet.sendFundsHandling.sendToAddress(testAddr,
Satoshis(3000),
None)
updatedCoins <- wallet.findOutputsBeingSpent(tx)
newTransactions <- wallet.listTransactions()
@ -79,7 +83,7 @@ class UTXOLifeCycleTest
// Give tx a fake hash so it can appear as it's in a block
hash <- bitcoind.getBestBlockHash()
_ <- wallet.processTransaction(tx, Some(hash))
_ <- wallet.transactionProcessing.processTransaction(tx, Some(hash))
_ <- wallet.updateUtxoPendingStates()
pendingCoins <- wallet.findOutputsBeingSpent(tx)
@ -150,9 +154,9 @@ class UTXOLifeCycleTest
_ = assert(oldUtxos == utxos)
// process the transactions from mempool
_ <- wallet.processTransaction(tx1.get, None)
_ <- wallet.processTransaction(tx2.get, None)
_ <- wallet.processTransaction(tx3.get, None)
_ <- wallet.transactionProcessing.processTransaction(tx1.get, None)
_ <- wallet.transactionProcessing.processTransaction(tx2.get, None)
_ <- wallet.transactionProcessing.processTransaction(tx3.get, None)
utxos <- wallet.listUtxos()
_ = assert(oldUtxos.size + 3 == utxos.size)
@ -166,7 +170,7 @@ class UTXOLifeCycleTest
_ = assert(blockHashes.size == 1)
blockHash = blockHashes.head
block <- bitcoind.getBlockRaw(blockHash)
_ <- wallet.processBlock(block)
_ <- wallet.transactionProcessing.processBlock(block)
_ <- wallet.updateUtxoPendingStates()
utxos <- wallet.listUtxos()
@ -177,7 +181,7 @@ class UTXOLifeCycleTest
_ = assert(blockHashes.size == 1)
blockHash = blockHashes.head
block <- bitcoind.getBlockRaw(blockHash)
_ <- wallet.processBlock(block)
_ <- wallet.transactionProcessing.processBlock(block)
_ <- wallet.updateUtxoPendingStates()
utxos <- wallet.listUtxos()
@ -196,7 +200,7 @@ class UTXOLifeCycleTest
_ = assert(blockHashes.size == 1)
blockHash = blockHashes.head
block <- bitcoind.getBlockRaw(blockHash)
_ <- wallet.processBlock(block)
_ <- wallet.transactionProcessing.processBlock(block)
_ <- wallet.updateUtxoPendingStates()
utxos <- wallet.listUtxos()
@ -215,7 +219,7 @@ class UTXOLifeCycleTest
_ = assert(blockHashes.size == 1)
blockHash = blockHashes.head
block <- bitcoind.getBlockRaw(blockHash)
_ <- wallet.processBlock(block)
_ <- wallet.transactionProcessing.processBlock(block)
_ <- wallet.updateUtxoPendingStates()
utxos <- wallet.listUtxos()
@ -234,7 +238,7 @@ class UTXOLifeCycleTest
_ = assert(blockHashes.size == 1)
blockHash = blockHashes.head
block <- bitcoind.getBlockRaw(blockHash)
_ <- wallet.processBlock(block)
_ <- wallet.transactionProcessing.processBlock(block)
_ <- wallet.updateUtxoPendingStates()
utxos <- wallet.listUtxos()
@ -253,7 +257,7 @@ class UTXOLifeCycleTest
_ = assert(blockHashes.size == 1)
blockHash = blockHashes.head
block <- bitcoind.getBlockRaw(blockHash)
_ <- wallet.processBlock(block)
_ <- wallet.transactionProcessing.processBlock(block)
_ <- wallet.updateUtxoPendingStates()
utxos <- wallet.listUtxos()
@ -274,7 +278,7 @@ class UTXOLifeCycleTest
val wallet = param.wallet
for {
tx <- wallet.sendToAddress(
tx <- wallet.sendFundsHandling.sendToAddress(
testAddr,
Satoshis(3000),
Some(SatoshisPerByte.one)
@ -284,8 +288,9 @@ class UTXOLifeCycleTest
_ = assert(coins.forall(_.state == BroadcastSpent))
_ = assert(coins.forall(_.spendingTxIdOpt.contains(tx.txIdBE)))
rbf <- wallet.bumpFeeRBF(tx.txIdBE, SatoshisPerByte.fromLong(3))
_ <- wallet.processTransaction(rbf, None)
rbf <- wallet.sendFundsHandling.bumpFeeRBF(tx.txIdBE,
SatoshisPerByte.fromLong(3))
_ <- wallet.transactionProcessing.processTransaction(rbf, None)
rbfCoins <- wallet.findOutputsBeingSpent(rbf)
} yield {
assert(rbfCoins.forall(_.state == BroadcastSpent))
@ -299,7 +304,9 @@ class UTXOLifeCycleTest
val spendingInfoDAO =
SpendingInfoDAO()(system.dispatcher, param.walletConfig)
for {
tx <- wallet.sendToAddress(testAddr, Satoshis(3000), None)
tx <- wallet.sendFundsHandling.sendToAddress(testAddr,
Satoshis(3000),
None)
coins <- wallet.findOutputsBeingSpent(tx)
@ -315,7 +322,7 @@ class UTXOLifeCycleTest
}
res <- recoverToSucceededIf[RuntimeException](
wallet.processTransaction(newTx, None)
wallet.transactionProcessing.processTransaction(newTx, None)
)
} yield res
}
@ -326,7 +333,9 @@ class UTXOLifeCycleTest
val walletConfig = param.walletConfig
for {
oldTransactions <- wallet.listTransactions()
tx <- wallet.sendToAddress(testAddr, Satoshis(3000), None)
tx <- wallet.sendFundsHandling.sendToAddress(testAddr,
Satoshis(3000),
None)
updatedCoins <- wallet.findOutputsBeingSpent(tx)
newTransactions <- wallet.listTransactions()
@ -336,7 +345,7 @@ class UTXOLifeCycleTest
// Give tx a fake hash so it can appear as it's in a block
hash <- bitcoind.getBestBlockHash()
_ <- wallet.processTransaction(tx, Some(hash))
_ <- wallet.transactionProcessing.processTransaction(tx, Some(hash))
_ <- wallet.updateUtxoPendingStates()
pendingCoins <- wallet.findOutputsBeingSpent(tx)
@ -361,7 +370,7 @@ class UTXOLifeCycleTest
}
res <- recoverToSucceededIf[RuntimeException](
wallet.processTransaction(newTx, None)
wallet.transactionProcessing.processTransaction(newTx, None)
)
} yield res
}
@ -376,7 +385,7 @@ class UTXOLifeCycleTest
txId <- bitcoind.sendToAddress(addr, Satoshis(3000))
tx <- bitcoind.getRawTransactionRaw(txId)
_ <- wallet.processOurTransaction(
_ <- wallet.transactionProcessing.processOurTransaction(
transaction = tx,
feeRate = SatoshisPerByte(Satoshis(3)),
inputAmount = Satoshis(4000),
@ -405,7 +414,7 @@ class UTXOLifeCycleTest
txId <- bitcoind.sendToAddress(addr, Satoshis(3000))
tx <- bitcoind.getRawTransactionRaw(txId)
_ <- wallet.processOurTransaction(
_ <- wallet.transactionProcessing.processOurTransaction(
transaction = tx,
feeRate = SatoshisPerByte(Satoshis(3)),
inputAmount = Satoshis(4000),
@ -422,7 +431,7 @@ class UTXOLifeCycleTest
hash <- bitcoind.getNewAddress
.flatMap(bitcoind.generateToAddress(1, _))
.map(_.head)
_ <- wallet.processTransaction(tx, Some(hash))
_ <- wallet.transactionProcessing.processTransaction(tx, Some(hash))
pendingCoins <-
wallet.findByScriptPubKey(addr.scriptPubKey)
@ -447,7 +456,7 @@ class UTXOLifeCycleTest
txId <- bitcoind.sendToAddress(addr, Satoshis(3000))
tx <- bitcoind.getRawTransactionRaw(txId)
_ <- wallet.processOurTransaction(
_ <- wallet.transactionProcessing.processOurTransaction(
transaction = tx,
feeRate = SatoshisPerByte(Satoshis(3)),
inputAmount = Satoshis(4000),
@ -575,11 +584,11 @@ class UTXOLifeCycleTest
Satoshis(100000),
P2PKHScriptPubKey(ECPublicKey.freshPublicKey)
)
val accountF = wallet.getDefaultAccount()
val accountF = wallet.accountHandling.getDefaultAccount()
for {
oldTransactions <- wallet.listTransactions()
account <- accountF
rawTxHelper <- wallet.fundRawTransaction(
rawTxHelper <- wallet.fundTxHandling.fundRawTransaction(
destinations = Vector(dummyOutput),
feeRate = SatoshisPerVirtualByte.one,
fromAccount = account,
@ -604,7 +613,7 @@ class UTXOLifeCycleTest
.flatMap(bitcoind.generateToAddress(1, _))
.map(_.head)
block <- bitcoind.getBlockRaw(hash)
_ <- wallet.processBlock(block)
_ <- wallet.transactionProcessing.processBlock(block)
newReserved <- wallet.listUtxos(TxoState.Reserved)
newTransactions <- wallet.listTransactions()
@ -646,7 +655,7 @@ class UTXOLifeCycleTest
PSBT.fromUnsignedTx(tx)
}
psbt <- wallet.signPSBT(unsignedPSBT)
psbt <- wallet.sendFundsHandling.signPSBT(unsignedPSBT)
tx <- Future.fromTry(
psbt.finalizePSBT.flatMap(_.extractTransactionAndValidate)
@ -656,7 +665,7 @@ class UTXOLifeCycleTest
_ <- bitcoind.sendRawTransaction(tx)
hash <- bitcoind.generateToAddress(1, testAddr).map(_.head)
block <- bitcoind.getBlockRaw(hash)
_ <- wallet.processBlock(block)
_ <- wallet.transactionProcessing.processBlock(block)
updatedCoins <- wallet.findOutputsBeingSpent(tx)
} yield {
@ -714,7 +723,7 @@ class UTXOLifeCycleTest
throwAwayAddr <- throwAwayAddrF
hashes <- bitcoind.generateToAddress(blocks = 1, throwAwayAddr)
block <- bitcoind.getBlockRaw(hashes.head)
_ <- wallet.processBlock(block)
_ <- wallet.transactionProcessing.processBlock(block)
// make sure the utxo is pending confirmations received
utxos <- wallet.listUtxos(TxoState.PendingConfirmationsReceived)
@ -733,7 +742,7 @@ class UTXOLifeCycleTest
// now process another block
hashes2 <- bitcoind.generateToAddress(blocks = 1, throwAwayAddr)
block2 <- bitcoind.getBlockRaw(hashes2.head)
_ <- wallet.processBlock(block2)
_ <- wallet.transactionProcessing.processBlock(block2)
// the utxo should still be reserved
reservedUtxos <- wallet.listUtxos(TxoState.Reserved)
@ -755,7 +764,9 @@ class UTXOLifeCycleTest
bitcoindAdr <- bitcoindAddrF
utxoCount <- utxoCountF
// build a spending transaction
tx <- wallet.sendToAddress(bitcoindAdr, amt, SatoshisPerVirtualByte.one)
tx <- wallet.sendFundsHandling.sendToAddress(bitcoindAdr,
amt,
SatoshisPerVirtualByte.one)
c <- wallet.listUtxos()
_ = assert(c.length == utxoCount.length)
txIdBE <- bitcoind.sendRawTransaction(tx)
@ -775,7 +786,7 @@ class UTXOLifeCycleTest
_ = assert(newReservedUtxos.length == utxoCount.length)
blockHash <- bitcoind.generateToAddress(1, bitcoindAdr).map(_.head)
block <- bitcoind.getBlockRaw(blockHash)
_ <- wallet.processBlock(block)
_ <- wallet.transactionProcessing.processBlock(block)
broadcastSpentUtxo <- wallet.listUtxos(
TxoState.PendingConfirmationsSpent
)
@ -807,7 +818,7 @@ class UTXOLifeCycleTest
walletAddress <- wallet.getNewAddress()
txId <- bitcoind.sendToAddress(walletAddress, receiveValue)
receiveTx <- bitcoind.getRawTransactionRaw(txId)
_ <- wallet.processTransaction(receiveTx, None)
_ <- wallet.transactionProcessing.processTransaction(receiveTx, None)
receiveOutPointPair = receiveTx.outputs.zipWithIndex
.find(_._1.value == receiveValue)
.map(out => (receiveTx.txId, out._2))
@ -824,7 +835,7 @@ class UTXOLifeCycleTest
// spend and broadcast unconfirmed
feeRate <- wallet.feeRateApi.getFeeRate()
sendTx <- wallet.sendWithAlgo(
sendTx <- wallet.sendFundsHandling.sendWithAlgo(
testAddr,
sendValue,
feeRate,
@ -839,7 +850,7 @@ class UTXOLifeCycleTest
// confirm receive and spend
blockHashes <- bitcoind.generate(1)
block <- bitcoind.getBlockRaw(blockHashes.head)
_ <- wallet.processBlock(block)
_ <- wallet.transactionProcessing.processBlock(block)
receivedUtxo <- wallet.findByOutPoints(Vector(receiveOutPoint))
} yield {
@ -861,7 +872,7 @@ class UTXOLifeCycleTest
walletAddress <- wallet.getNewAddress()
txId <- bitcoind.sendToAddress(walletAddress, receiveValue)
receiveTx <- bitcoind.getRawTransactionRaw(txId)
_ <- wallet.processTransaction(receiveTx, None)
_ <- wallet.transactionProcessing.processTransaction(receiveTx, None)
receiveOutPointPair = receiveTx.outputs.zipWithIndex
.find(_._1.value == receiveValue)
.map(out => (receiveTx.txId, out._2))
@ -878,7 +889,7 @@ class UTXOLifeCycleTest
// spend unconfirmed
feeRate <- wallet.feeRateApi.getFeeRate()
sendTx <- wallet.sendWithAlgo(
sendTx <- wallet.sendFundsHandling.sendWithAlgo(
testAddr,
sendValue,
feeRate,
@ -892,7 +903,7 @@ class UTXOLifeCycleTest
// confirm receive
blockHashes <- bitcoind.generate(1)
block <- bitcoind.getBlockRaw(blockHashes.head)
_ <- wallet.processBlock(block)
_ <- wallet.transactionProcessing.processBlock(block)
receivedUtxo <- wallet.findByOutPoints(Vector(receiveOutPoint))
_ = assert(receivedUtxo.size == 1)
@ -902,7 +913,7 @@ class UTXOLifeCycleTest
_ <- wallet.broadcastTransaction(sendTx)
blockHashes <- bitcoind.generate(1)
block <- bitcoind.getBlockRaw(blockHashes.head)
_ <- wallet.processBlock(block)
_ <- wallet.transactionProcessing.processBlock(block)
receivedUtxo <- wallet.findByOutPoints(Vector(receiveOutPoint))
} yield {

View file

@ -74,7 +74,9 @@ class WalletCallbackTest extends BitcoinSWalletTest {
for {
address <- wallet.getNewAddress()
tx <- wallet.sendToAddress(address, Satoshis(1000), None)
tx <- wallet.sendFundsHandling.sendToAddress(address,
Satoshis(1000),
None)
result <- resultP.future
} yield assert(result == tx)
}
@ -108,7 +110,7 @@ class WalletCallbackTest extends BitcoinSWalletTest {
for {
txno <- wallet.listTransactions().map(_.size)
_ <- wallet.processTransaction(tx, None)
_ <- wallet.transactionProcessing.processTransaction(tx, None)
_ <- AsyncUtil.nonBlockingSleep(50.millis)
txs <- wallet.listTransactions()
} yield {
@ -211,7 +213,7 @@ class WalletCallbackTest extends BitcoinSWalletTest {
val wallet = fundedWallet.wallet
for {
_ <- wallet.processBlock(block)
_ <- wallet.transactionProcessing.processBlock(block)
result <- resultP.future
} yield assert(result == block)
}

View file

@ -70,7 +70,7 @@ class WalletIntegrationTest extends BitcoinSWalletTestCachedBitcoindNewest {
.map(unconfirmed => assert(unconfirmed == 0.bitcoin))
// after this, tx is unconfirmed in wallet
_ <- wallet.processTransaction(tx, None)
_ <- wallet.transactionProcessing.processTransaction(tx, None)
// we should now have one UTXO in the wallet
// it should not be confirmed
@ -92,7 +92,7 @@ class WalletIntegrationTest extends BitcoinSWalletTestCachedBitcoindNewest {
rawTx <- bitcoind.getRawTransaction(txId)
// after this, tx should be confirmed
_ <- wallet.processTransaction(tx, rawTx.blockhash)
_ <- wallet.transactionProcessing.processTransaction(tx, rawTx.blockhash)
_ <-
wallet
.listUtxos()
@ -116,7 +116,7 @@ class WalletIntegrationTest extends BitcoinSWalletTestCachedBitcoindNewest {
.map(unconfirmed => assert(unconfirmed == 0.satoshis))
signedTx <- bitcoind.getNewAddress.flatMap {
wallet.sendToAddress(_, valueToBitcoind, None)
wallet.sendFundsHandling.sendToAddress(_, valueToBitcoind, None)
}
feeRate =
@ -176,7 +176,8 @@ class WalletIntegrationTest extends BitcoinSWalletTestCachedBitcoindNewest {
txId <- bitcoind.sendToAddress(addr, valueFromBitcoind)
rawTx <- bitcoind.getRawTransaction(txId)
_ <- bitcoind.generate(6)
_ <- wallet.processTransaction(rawTx.hex, rawTx.blockhash)
_ <- wallet.transactionProcessing.processTransaction(rawTx.hex,
rawTx.blockhash)
// Verify we funded the wallet
balance <- wallet.getBalance()
@ -184,7 +185,9 @@ class WalletIntegrationTest extends BitcoinSWalletTestCachedBitcoindNewest {
// Create first tx
tx1 <- bitcoind.getNewAddress.flatMap {
wallet.sendToAddress(_, valueToBitcoind, SatoshisPerVirtualByte.one)
wallet.sendFundsHandling.sendToAddress(_,
valueToBitcoind,
SatoshisPerVirtualByte.one)
}
_ <- bitcoind.sendRawTransaction(tx1)
@ -196,7 +199,8 @@ class WalletIntegrationTest extends BitcoinSWalletTestCachedBitcoindNewest {
// Create replacement tx
newFeeRate = SatoshisPerVirtualByte.fromLong(20)
replacementTx <- wallet.bumpFeeRBF(tx1.txIdBE, newFeeRate)
replacementTx <- wallet.sendFundsHandling.bumpFeeRBF(tx1.txIdBE,
newFeeRate)
// Check tx being replaced exists
tx1Info <- bitcoind.getRawTransaction(tx1.txIdBE)
@ -239,7 +243,8 @@ class WalletIntegrationTest extends BitcoinSWalletTestCachedBitcoindNewest {
txId <- bitcoind.sendToAddress(addr, valueFromBitcoind)
_ <- bitcoind.generate(6)
rawTx <- bitcoind.getRawTransaction(txId)
_ <- wallet.processTransaction(rawTx.hex, rawTx.blockhash)
_ <- wallet.transactionProcessing.processTransaction(rawTx.hex,
rawTx.blockhash)
// Verify we funded the wallet
balance <- wallet.getBalance()
@ -247,7 +252,9 @@ class WalletIntegrationTest extends BitcoinSWalletTestCachedBitcoindNewest {
// Create rbf tx
rbf <- bitcoind.getNewAddress.flatMap {
wallet.sendToAddress(_, valueToBitcoind, SatoshisPerVirtualByte.one)
wallet.sendFundsHandling.sendToAddress(_,
valueToBitcoind,
SatoshisPerVirtualByte.one)
}
_ <- bitcoind.sendRawTransaction(rbf)
@ -255,11 +262,13 @@ class WalletIntegrationTest extends BitcoinSWalletTestCachedBitcoindNewest {
_ <- bitcoind.generate(1)
rawTx1 <- bitcoind.getRawTransaction(rbf.txIdBE)
_ = require(rawTx1.blockhash.isDefined)
_ <- wallet.processTransaction(rbf, rawTx1.blockhash)
_ <- wallet.transactionProcessing.processTransaction(rbf,
rawTx1.blockhash)
// fail to RBF confirmed tx
res <- recoverToSucceededIf[IllegalArgumentException] {
wallet.bumpFeeRBF(rbf.txIdBE, SatoshisPerVirtualByte.fromLong(20))
wallet.sendFundsHandling.bumpFeeRBF(rbf.txIdBE,
SatoshisPerVirtualByte.fromLong(20))
}
} yield res
}
@ -274,7 +283,8 @@ class WalletIntegrationTest extends BitcoinSWalletTestCachedBitcoindNewest {
txId <- bitcoind.sendToAddress(addr, valueFromBitcoind)
rawTx <- bitcoind.getRawTransaction(txId)
_ <- bitcoind.generate(6)
_ <- wallet.processTransaction(rawTx.hex, rawTx.blockhash)
_ <- wallet.transactionProcessing.processTransaction(rawTx.hex,
rawTx.blockhash)
// Verify we funded the wallet
balance <- wallet.getBalance()
@ -282,7 +292,7 @@ class WalletIntegrationTest extends BitcoinSWalletTestCachedBitcoindNewest {
// Create parent tx
parentTx <- bitcoind.getNewAddress.flatMap {
wallet.sendToAddress(_, valueToBitcoind, None)
wallet.sendFundsHandling.sendToAddress(_, valueToBitcoind, None)
}
_ <- bitcoind.sendRawTransaction(parentTx)
@ -294,7 +304,8 @@ class WalletIntegrationTest extends BitcoinSWalletTestCachedBitcoindNewest {
// Create child tx
childFeeRate <- wallet.feeRateApi.getFeeRate()
childTx <- wallet.bumpFeeCPFP(parentTx.txIdBE, childFeeRate)
childTx <- wallet.sendFundsHandling.bumpFeeCPFP(parentTx.txIdBE,
childFeeRate)
_ <- bitcoind.sendRawTransaction(childTx)
// Check we didn't send again to bitcoind
@ -325,7 +336,7 @@ class WalletIntegrationTest extends BitcoinSWalletTestCachedBitcoindNewest {
coinbaseTx.outputs.exists(_.scriptPubKey == addr.scriptPubKey)
)
_ <- wallet.processBlock(block)
_ <- wallet.transactionProcessing.processBlock(block)
// Verify we funded the wallet
allUtxos <- wallet.listUtxos()
@ -337,7 +348,9 @@ class WalletIntegrationTest extends BitcoinSWalletTestCachedBitcoindNewest {
// Attempt to spend utxo
_ <- recoverToSucceededIf[RuntimeException](
wallet.sendToAddress(bitcoindAddr, valueToBitcoind, None)
wallet.sendFundsHandling.sendToAddress(bitcoindAddr,
valueToBitcoind,
None)
)
spendingTx = {
@ -350,7 +363,7 @@ class WalletIntegrationTest extends BitcoinSWalletTestCachedBitcoindNewest {
}
_ <- recoverToSucceededIf[RuntimeException](
wallet.processTransaction(spendingTx, None)
wallet.transactionProcessing.processTransaction(spendingTx, None)
)
// Make coinbase mature
@ -359,13 +372,13 @@ class WalletIntegrationTest extends BitcoinSWalletTestCachedBitcoindNewest {
// Create valid spending tx
psbt = PSBT.fromUnsignedTx(spendingTx)
signedPSBT <- wallet.signPSBT(psbt)
signedPSBT <- wallet.sendFundsHandling.signPSBT(psbt)
signedTx = signedPSBT.finalizePSBT
.flatMap(_.extractTransactionAndValidate)
.get
// Process tx, validate correctly moved to
_ <- wallet.processTransaction(signedTx, None)
_ <- wallet.transactionProcessing.processTransaction(signedTx, None)
newCoinbaseUtxos <- wallet.listUtxos(TxoState.ImmatureCoinbase)
_ = assert(newCoinbaseUtxos.isEmpty)
spentUtxos <- wallet.listUtxos(TxoState.BroadcastSpent)

View file

@ -39,7 +39,9 @@ class WalletSendingTest extends BitcoinSWalletTest {
it should "correctly send to an address" in { fundedWallet =>
val wallet = fundedWallet.wallet
for {
tx <- wallet.sendToAddress(testAddress, amountToSend, None)
tx <- wallet.sendFundsHandling.sendToAddress(testAddress,
amountToSend,
None)
} yield {
assert(
tx.outputs.contains(
@ -66,7 +68,7 @@ class WalletSendingTest extends BitcoinSWalletTest {
it should "correctly send to multiple addresses" in { fundedWallet =>
val wallet = fundedWallet.wallet
for {
tx <- wallet.sendToAddresses(addresses, amounts, None)
tx <- wallet.sendFundsHandling.sendToAddresses(addresses, amounts, None)
} yield {
val expectedOutputs = addresses.zip(amounts).map { case (addr, amount) =>
TransactionOutput(amount, addr.scriptPubKey)
@ -79,7 +81,7 @@ class WalletSendingTest extends BitcoinSWalletTest {
fundedWallet =>
val wallet = fundedWallet.wallet
val sendToAddressesF =
wallet.sendToAddresses(addresses, amounts.tail, None)
wallet.sendFundsHandling.sendToAddresses(addresses, amounts.tail, None)
recoverToSucceededIf[IllegalArgumentException] {
sendToAddressesF
@ -90,7 +92,7 @@ class WalletSendingTest extends BitcoinSWalletTest {
fundedWallet =>
val wallet = fundedWallet.wallet
val sendToAddressesF =
wallet.sendToAddresses(addresses.tail, amounts, None)
wallet.sendFundsHandling.sendToAddresses(addresses.tail, amounts, None)
recoverToSucceededIf[IllegalArgumentException] {
sendToAddressesF
@ -104,7 +106,7 @@ class WalletSendingTest extends BitcoinSWalletTest {
}
for {
tx <- wallet.sendToOutputs(expectedOutputs, None)
tx <- wallet.sendFundsHandling.sendToOutputs(expectedOutputs, None)
} yield {
assert(expectedOutputs.diff(tx.outputs).isEmpty)
}
@ -116,7 +118,7 @@ class WalletSendingTest extends BitcoinSWalletTest {
): Future[Assertion] = {
val message = "ben was here"
for {
tx <- wallet.makeOpReturnCommitment(
tx <- wallet.sendFundsHandling.makeOpReturnCommitment(
message = message,
hashMessage = hashMessage,
feeRateOpt = None
@ -167,7 +169,7 @@ class WalletSendingTest extends BitcoinSWalletTest {
fundedWallet =>
val wallet = fundedWallet.wallet
recoverToSucceededIf[IllegalArgumentException] {
wallet.makeOpReturnCommitment(
wallet.sendFundsHandling.makeOpReturnCommitment(
"This message is much too long and is over 80 bytes, the limit for OP_RETURN. It should cause an error.",
hashMessage = false,
None
@ -178,7 +180,7 @@ class WalletSendingTest extends BitcoinSWalletTest {
it should "fail to send to a different network address" in { fundedWallet =>
val wallet = fundedWallet.wallet
val sendToAddressesF =
wallet.sendToAddress(
wallet.sendFundsHandling.sendToAddress(
BitcoinAddress("1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa"),
Satoshis(1000),
None
@ -199,7 +201,7 @@ class WalletSendingTest extends BitcoinSWalletTest {
)
val sendToAddressesF =
wallet.sendToAddresses(addrs, amounts, None)
wallet.sendFundsHandling.sendToAddresses(addrs, amounts, None)
recoverToSucceededIf[IllegalArgumentException] {
sendToAddressesF
@ -212,7 +214,10 @@ class WalletSendingTest extends BitcoinSWalletTest {
allOutPoints <- wallet.spendingInfoDAO.findAllOutpoints()
// use half of them
outPoints = allOutPoints.drop(allOutPoints.size / 2)
tx <- wallet.sendFromOutPoints(outPoints, testAddress, amountToSend, None)
tx <- wallet.sendFundsHandling.sendFromOutPoints(outPoints,
testAddress,
amountToSend,
None)
} yield {
assert(
outPoints.forall(outPoint =>
@ -237,7 +242,9 @@ class WalletSendingTest extends BitcoinSWalletTest {
// use half of them
utxos = allUtxos.drop(allUtxos.size / 2)
outPoints = utxos.map(_.outPoint)
tx <- wallet.sendFromOutPoints(outPoints, testAddress, None)
tx <- wallet.sendFundsHandling.sendFromOutPoints(outPoints,
testAddress,
None)
} yield {
val expectedFeeRate =
wallet.feeRateApi.asInstanceOf[RandomFeeProvider].lastFeeRate.get
@ -281,7 +288,7 @@ class WalletSendingTest extends BitcoinSWalletTest {
val wallet = fundedWallet.wallet
for {
utxos <- wallet.listUtxos()
tx <- wallet.sweepWallet(testAddress, None)
tx <- wallet.sendFundsHandling.sweepWallet(testAddress, None)
balance <- wallet.getBalance()
} yield {
assert(balance == Satoshis.zero)
@ -331,12 +338,14 @@ class WalletSendingTest extends BitcoinSWalletTest {
val feeRate = FeeUnitGen.satsPerByte.sampleSome
for {
tx <- wallet.sendToAddress(testAddress, amountToSend, feeRate)
tx <- wallet.sendFundsHandling.sendToAddress(testAddress,
amountToSend,
feeRate)
firstBal <- wallet.getBalance()
newFeeRate = SatoshisPerByte(feeRate.currencyUnit + Satoshis(50))
bumpedTx <- wallet.bumpFeeRBF(tx.txIdBE, newFeeRate)
bumpedTx <- wallet.sendFundsHandling.bumpFeeRBF(tx.txIdBE, newFeeRate)
txDb1Opt <- wallet.outgoingTxDAO.findByTxId(tx.txIdBE)
txDb2Opt <- wallet.outgoingTxDAO.findByTxId(bumpedTx.txIdBE)
@ -360,11 +369,15 @@ class WalletSendingTest extends BitcoinSWalletTest {
val newFeeRate = SatoshisPerByte(feeRate.currencyUnit + Satoshis.one)
for {
tx <- wallet.sendToAddress(testAddress, amountToSend, feeRate)
_ <- wallet.processTransaction(tx, Some(DoubleSha256DigestBE.empty))
tx <- wallet.sendFundsHandling.sendToAddress(testAddress,
amountToSend,
feeRate)
_ <- wallet.transactionProcessing.processTransaction(
tx,
Some(DoubleSha256DigestBE.empty))
res <- recoverToSucceededIf[IllegalArgumentException] {
wallet.bumpFeeRBF(tx.txIdBE, newFeeRate)
wallet.sendFundsHandling.bumpFeeRBF(tx.txIdBE, newFeeRate)
}
} yield res
}
@ -389,12 +402,14 @@ class WalletSendingTest extends BitcoinSWalletTest {
psbt = PSBT.fromUnsignedTx(tx)
// Have wallet sign and process transaction
signedPSBT <- wallet.signPSBT(psbt)
signedPSBT <- wallet.sendFundsHandling.signPSBT(psbt)
signedTx = signedPSBT.finalizePSBT.get.extractTransactionAndValidate.get
_ <- wallet.processTransaction(signedTx, None)
_ <- wallet.transactionProcessing.processTransaction(signedTx, None)
res <- recoverToSucceededIf[IllegalArgumentException] {
wallet.bumpFeeRBF(signedTx.txIdBE, SatoshisPerVirtualByte.fromLong(100))
wallet.sendFundsHandling.bumpFeeRBF(
signedTx.txIdBE,
SatoshisPerVirtualByte.fromLong(100))
}
} yield res
}
@ -402,9 +417,11 @@ class WalletSendingTest extends BitcoinSWalletTest {
it should "correctly CPFP a transaction" in { fundedWallet =>
val wallet = fundedWallet.wallet
for {
parent <- wallet.sendToAddress(testAddress, amountToSend, None)
parent <- wallet.sendFundsHandling.sendToAddress(testAddress,
amountToSend,
None)
bumpRate <- wallet.feeRateApi.getFeeRate()
child <- wallet.bumpFeeCPFP(parent.txIdBE, bumpRate)
child <- wallet.sendFundsHandling.bumpFeeCPFP(parent.txIdBE, bumpRate)
received <- wallet.spendingInfoDAO.findTx(child).map(_.nonEmpty)
} yield {
@ -442,11 +459,15 @@ class WalletSendingTest extends BitcoinSWalletTest {
val feeRate = FeeUnitGen.satsPerByte.sampleSome
for {
tx <- wallet.sendToAddress(testAddress, amountToSend, feeRate)
_ <- wallet.processTransaction(tx, Some(DoubleSha256DigestBE.empty))
tx <- wallet.sendFundsHandling.sendToAddress(testAddress,
amountToSend,
feeRate)
_ <- wallet.transactionProcessing.processTransaction(
tx,
Some(DoubleSha256DigestBE.empty))
res <- recoverToSucceededIf[IllegalArgumentException] {
wallet.bumpFeeCPFP(tx.txIdBE, feeRate)
wallet.sendFundsHandling.bumpFeeCPFP(tx.txIdBE, feeRate)
}
} yield res
}
@ -455,7 +476,8 @@ class WalletSendingTest extends BitcoinSWalletTest {
val wallet = fundedWallet.wallet
recoverToSucceededIf[RuntimeException](
wallet.bumpFeeCPFP(EmptyTransaction.txIdBE, SatoshisPerByte.one)
wallet.sendFundsHandling.bumpFeeCPFP(EmptyTransaction.txIdBE,
SatoshisPerByte.one)
)
}
@ -472,7 +494,7 @@ class WalletSendingTest extends BitcoinSWalletTest {
.copyWithState(TxoState.PendingConfirmationsSpent)
_ <- wallet.spendingInfoDAO.update(spent)
test <- recoverToSucceededIf[IllegalArgumentException](
wallet.sendFromOutPoints(
wallet.sendFundsHandling.sendFromOutPoints(
allUtxos.map(_.outPoint),
testAddress,
amountToSend,
@ -487,16 +509,19 @@ class WalletSendingTest extends BitcoinSWalletTest {
algo: CoinSelectionAlgo
): Future[Assertion] = {
for {
account <- wallet.getDefaultAccount()
account <- wallet.accountHandling.getDefaultAccount()
feeRate <- wallet.getFeeRate()
allUtxos <- wallet
allUtxos <- wallet.utxoHandling
.listUtxos(account.hdAccount)
.map(_.map(CoinSelectorUtxo.fromSpendingInfoDb))
output = TransactionOutput(amountToSend, testAddress.scriptPubKey)
expectedUtxos =
CoinSelector.selectByAlgo(algo, allUtxos, Vector(output), feeRate)
tx <- wallet.sendWithAlgo(testAddress, amountToSend, feeRate, algo)
tx <- wallet.sendFundsHandling.sendWithAlgo(testAddress,
amountToSend,
feeRate,
algo)
} yield {
val diff =
expectedUtxos.map(_.outPoint).diff(tx.inputs.map(_.previousOutput))
@ -543,8 +568,12 @@ class WalletSendingTest extends BitcoinSWalletTest {
) // for fee, fee rates are random so we might need a lot
// build these transactions in parallel intentionally
tx1F = fundedWallet.wallet.sendToAddress(addr1, amt, None)
tx2F = fundedWallet.wallet.sendToAddress(addr2, amt, None)
tx1F = fundedWallet.wallet.sendFundsHandling.sendToAddress(addr1,
amt,
None)
tx2F = fundedWallet.wallet.sendFundsHandling.sendToAddress(addr2,
amt,
None)
// one of these should fail because we don't have enough money
_ <- tx1F
_ <- tx2F

View file

@ -36,7 +36,7 @@ class WalletUnitTest extends BitcoinSWalletTest {
it should "create a new wallet" in { (wallet: Wallet) =>
for {
accounts <- wallet.listAccounts()
accounts <- wallet.accountHandling.listAccounts()
addresses <- wallet.addressHandling.listAddresses()
} yield {
assert(accounts.length == 3) // legacy, segwit and nested segwit
@ -125,7 +125,7 @@ class WalletUnitTest extends BitcoinSWalletTest {
}
for {
account <- wallet.getDefaultAccount()
account <- wallet.accountHandling.getDefaultAccount()
_ <- testChain(hdAccount = account.hdAccount, External)
res <- testChain(hdAccount = account.hdAccount, Change)
} yield res
@ -234,7 +234,7 @@ class WalletUnitTest extends BitcoinSWalletTest {
psbt = dummyPSBT(prevTxId = dummyPrevTx.txId)
.addKeyPathToInput(accountDb.xpub, walletPath, walletKey, 0)
signed <- wallet.signPSBT(psbt)
signed <- wallet.sendFundsHandling.signPSBT(psbt)
} yield {
assert(signed != psbt)
assert(
@ -254,11 +254,13 @@ class WalletUnitTest extends BitcoinSWalletTest {
spk = addr.scriptPubKey
_ = assert(spk == P2PKHScriptPubKey(walletKey))
dummyPrevTx = dummyTx(spk = spk)
_ <- wallet.processTransaction(dummyPrevTx, blockHashOpt = None)
_ <- wallet.transactionProcessing.processTransaction(dummyPrevTx,
blockHashOpt =
None)
psbt = dummyPSBT(prevTxId = dummyPrevTx.txId)
signed <- wallet.signPSBT(psbt)
signed <- wallet.sendFundsHandling.signPSBT(psbt)
} yield {
assert(signed != psbt)
assert(
@ -278,11 +280,13 @@ class WalletUnitTest extends BitcoinSWalletTest {
spk = addr.scriptPubKey
_ = assert(spk == P2SHScriptPubKey(P2WPKHWitnessSPKV0(walletKey)))
dummyPrevTx = dummyTx(spk = spk)
_ <- wallet.processTransaction(dummyPrevTx, blockHashOpt = None)
_ <- wallet.transactionProcessing.processTransaction(dummyPrevTx,
blockHashOpt =
None)
psbt = dummyPSBT(prevTxId = dummyPrevTx.txId)
signed <- wallet.signPSBT(psbt)
signed <- wallet.sendFundsHandling.signPSBT(psbt)
} yield {
assert(signed != psbt)
assert(
@ -302,12 +306,14 @@ class WalletUnitTest extends BitcoinSWalletTest {
spk = addr.scriptPubKey
_ = assert(spk == P2WPKHWitnessSPKV0(walletKey))
dummyPrevTx = dummyTx(spk = spk)
_ <- wallet.processTransaction(dummyPrevTx, blockHashOpt = None)
_ <- wallet.transactionProcessing.processTransaction(dummyPrevTx,
blockHashOpt =
None)
psbt = dummyPSBT(prevTxId = dummyPrevTx.txId)
.addUTXOToInput(dummyPrevTx, 0)
signed <- wallet.signPSBT(psbt)
signed <- wallet.sendFundsHandling.signPSBT(psbt)
} yield {
assert(signed != psbt)
assert(
@ -320,7 +326,7 @@ class WalletUnitTest extends BitcoinSWalletTest {
it must "be able to sign a psbt with no wallet utxos" in { (wallet: Wallet) =>
val psbt = dummyPSBT()
for {
signed <- wallet.signPSBT(psbt)
signed <- wallet.sendFundsHandling.signPSBT(psbt)
} yield assert(signed == psbt)
}
@ -333,10 +339,12 @@ class WalletUnitTest extends BitcoinSWalletTest {
spk = addr.scriptPubKey
_ = assert(spk == P2WPKHWitnessSPKV0(walletKey))
dummyPrevTx = dummyTx(spk = spk)
_ <- wallet.processTransaction(dummyPrevTx, blockHashOpt = None)
_ <- wallet.transactionProcessing.processTransaction(dummyPrevTx,
blockHashOpt = None)
dummyPrevTx1 = dummyTx(prevTxId = dummyPrevTx.txId, spk = spk)
_ <- wallet.processTransaction(dummyPrevTx1, blockHashOpt = None)
_ <- wallet.transactionProcessing.processTransaction(dummyPrevTx1,
blockHashOpt = None)
toBroadcast <- wallet.getTransactionsToBroadcast
} yield assert(toBroadcast.map(_.txIdBE) == Vector(dummyPrevTx1.txIdBE))

View file

@ -15,16 +15,11 @@ import org.bitcoins.core.crypto.ExtPublicKey
import org.bitcoins.core.currency.*
import org.bitcoins.core.gcs.{GolombFilter, SimpleFilterMatcher}
import org.bitcoins.core.hd.*
import org.bitcoins.core.number.UInt32
import org.bitcoins.core.protocol.BitcoinAddress
import org.bitcoins.core.protocol.blockchain.{Block, ChainParams}
import org.bitcoins.core.protocol.blockchain.ChainParams
import org.bitcoins.core.protocol.script.ScriptPubKey
import org.bitcoins.core.protocol.transaction.*
import org.bitcoins.core.psbt.PSBT
import org.bitcoins.core.script.constant.ScriptConstant
import org.bitcoins.core.script.control.OP_RETURN
import org.bitcoins.core.util.{BitcoinScriptUtil, FutureUtil, HDUtil}
import org.bitcoins.core.wallet.builder.*
import org.bitcoins.core.util.{FutureUtil, HDUtil}
import org.bitcoins.core.wallet.fee.*
import org.bitcoins.core.wallet.utxo.*
import org.bitcoins.core.wallet.utxo.TxoState.*
@ -36,7 +31,6 @@ import org.bitcoins.wallet.callback.WalletCallbacks
import org.bitcoins.wallet.config.WalletAppConfig
import org.bitcoins.wallet.internal.*
import org.bitcoins.wallet.models.*
import scodec.bits.ByteVector
import slick.dbio.{DBIOAction, Effect, NoStream}
import java.time.Instant
@ -44,11 +38,10 @@ import java.time.temporal.ChronoUnit
import java.util.concurrent.TimeUnit
import scala.concurrent.{ExecutionContext, Future}
import scala.util.control.NonFatal
import scala.util.Random
abstract class Wallet extends NeutrinoHDWalletApi with WalletLogger {
override def keyManager: BIP39KeyManager = {
def keyManager: BIP39KeyManager = {
walletConfig.kmConf.toBip39KeyManager
}
implicit val walletConfig: WalletAppConfig
@ -96,15 +89,17 @@ abstract class Wallet extends NeutrinoHDWalletApi with WalletLogger {
val creationTime: Instant = keyManager.creationTime
def utxoHandling: UtxoHandling =
UtxoHandling(spendingInfoDAO, transactionDAO, chainQueryApi)
UtxoHandling(spendingInfoDAO, transactionDAO, addressDAO, chainQueryApi)
def fundTxHandling: FundTransactionHandling = FundTransactionHandling(
accountHandling = accountHandling,
utxoHandling = utxoHandling,
addressHandling = addressHandling,
transactionProcessing = transactionProcessing,
spendingInfoDAO = spendingInfoDAO,
transactionDAO = transactionDAO,
keyManager = keyManager
keyManager = keyManager,
feeRateApi = feeRateApi
)
def accountHandling: AccountHandling =
AccountHandling(walletDAOs, keyManager)
@ -112,7 +107,7 @@ abstract class Wallet extends NeutrinoHDWalletApi with WalletLogger {
def addressHandling: AddressHandling =
AddressHandling(accountHandling, walletDAOs)
protected lazy val transactionProcessing: TransactionProcessingApi = {
override lazy val transactionProcessing: TransactionProcessingApi = {
TransactionProcessing(
walletApi = this,
chainQueryApi = chainQueryApi,
@ -131,6 +126,19 @@ abstract class Wallet extends NeutrinoHDWalletApi with WalletLogger {
)
}
override def sendFundsHandling: SendFundsHandlingApi = {
SendFundsHandlingHandling(
accountHandling = accountHandling,
feeRateApi = feeRateApi,
fundTxHandling = fundTxHandling,
addressHandling = addressHandling,
transactionProcessing = transactionProcessing,
utxoHandling = utxoHandling,
keyManager = keyManager,
walletDAOs = walletDAOs
)
}
override def isRescanning(): Future[Boolean] = rescanHandling.isRescanning()
def walletCallbacks: WalletCallbacks = walletConfig.callBacks
@ -194,35 +202,6 @@ abstract class Wallet extends NeutrinoHDWalletApi with WalletLogger {
}
}
override def processBlock(block: Block): Future[Unit] = {
transactionProcessing.processBlock(block)
}
override def processTransaction(
transaction: Transaction,
blockHashOpt: Option[DoubleSha256DigestBE]): Future[Unit] = {
transactionProcessing.processTransaction(transaction, blockHashOpt)
}
/** Processes TXs originating from our wallet. This is called right after
* we've signed a TX, updating our UTXO state.
*/
override def processOurTransaction(
transaction: Transaction,
feeRate: FeeUnit,
inputAmount: CurrencyUnit,
sentAmount: CurrencyUnit,
blockHashOpt: Option[DoubleSha256DigestBE],
newTags: Vector[AddressTag]
): Future[ProcessTxResult] = {
transactionProcessing.processOurTransaction(transaction,
feeRate,
inputAmount,
sentAmount,
blockHashOpt,
newTags)
}
override def processCompactFilters(
blockFilters: Vector[(DoubleSha256DigestBE, GolombFilter)]
): Future[Wallet] = {
@ -321,30 +300,6 @@ abstract class Wallet extends NeutrinoHDWalletApi with WalletLogger {
spendingInfoCount <- spendingInfoDAO.count()
} yield addressCount == 0 && spendingInfoCount == 0
override def clearAllUtxos(): Future[Wallet] = {
val aggregatedActions
: DBIOAction[Unit, NoStream, Effect.Write with Effect.Transactional] =
spendingInfoDAO.deleteAllAction().map(_ => ())
val resultedF = safeDatabase.run(aggregatedActions)
resultedF.failed.foreach(err =>
logger.error(
s"Failed to clear utxos, addresses and scripts from the database",
err
))
resultedF.map(_ => this)
}
override def clearAllAddresses(): Future[Wallet] = {
val action = addressDAO
.deleteAllAction()
.map(_ => ())
safeDatabase
.run(action)
.map(_ => this)
}
override def getBalance()(implicit
ec: ExecutionContext
): Future[CurrencyUnit] = {
@ -354,11 +309,6 @@ abstract class Wallet extends NeutrinoHDWalletApi with WalletLogger {
override def getConfirmedBalance(): Future[CurrencyUnit] = {
safeDatabase.run(spendingInfoDAO.getConfirmedBalanceAction())
}
override def getConfirmedBalance(account: HDAccount): Future[CurrencyUnit] = {
safeDatabase.run(spendingInfoDAO.getConfirmedBalanceAction(Some(account)))
}
override def getConfirmedBalance(tag: AddressTag): Future[CurrencyUnit] = {
spendingInfoDAO.findAllUnspentForTag(tag).map { allUnspent =>
val confirmed = allUnspent.filter(_.state == ConfirmedReceived)
@ -370,12 +320,6 @@ abstract class Wallet extends NeutrinoHDWalletApi with WalletLogger {
safeDatabase.run(spendingInfoDAO.getUnconfirmedBalanceAction())
}
override def getUnconfirmedBalance(
account: HDAccount
): Future[CurrencyUnit] = {
safeDatabase.run(spendingInfoDAO.getUnconfirmedBalanceAction(Some(account)))
}
override def getUnconfirmedBalance(tag: AddressTag): Future[CurrencyUnit] = {
spendingInfoDAO.findAllUnspentForTag(tag).map { allUnspent =>
val confirmed = allUnspent
@ -402,424 +346,6 @@ abstract class Wallet extends NeutrinoHDWalletApi with WalletLogger {
spendingInfoDAO.findOutputsBeingSpent(tx)
}
/** Enumerates all the TX outpoints in the wallet */
protected[wallet] def listOutpoints(): Future[Vector[TransactionOutPoint]] =
spendingInfoDAO.findAllOutpoints()
/** Takes a [[RawTxBuilderWithFinalizer]] for a transaction to be sent, and
* completes it by: finalizing and signing the transaction, then correctly
* processing and logging it
*/
private def finishSend[F <: RawTxFinalizer](
rawTxHelper: FundRawTxHelper[F],
sentAmount: CurrencyUnit,
feeRate: FeeUnit,
newTags: Vector[AddressTag]
): Future[Transaction] = {
val signed = rawTxHelper.signedTx
val processedTxF = for {
ourOuts <- addressHandling.findOurOutputs(signed)
creditingAmount = rawTxHelper.scriptSigParams.foldLeft(
CurrencyUnits.zero
)(_ + _.amount)
_ <- transactionProcessing.processOurTransaction(
transaction = signed,
feeRate = feeRate,
inputAmount = creditingAmount,
sentAmount = sentAmount,
blockHashOpt = None,
newTags = newTags
)
} yield {
logger.debug(
s"Signed transaction=${signed.txIdBE.hex} with outputs=${signed.outputs.length}, inputs=${signed.inputs.length}"
)
logger.trace(s"Change output(s) for transaction=${signed.txIdBE.hex}")
ourOuts.foreach { out =>
logger.trace(s" $out")
}
signed
}
processedTxF.recoverWith { case _ =>
// if something fails, we need to unreserve the utxos associated with this tx
// and then propogate the failed future upwards
utxoHandling.unmarkUTXOsAsReserved(signed).flatMap(_ => processedTxF)
}
}
override def sendFromOutPoints(
outPoints: Vector[TransactionOutPoint],
address: BitcoinAddress,
feeRate: FeeUnit
)(implicit ec: ExecutionContext): Future[Transaction] = {
require(
address.networkParameters.isSameNetworkBytes(networkParameters),
s"Cannot send to address on other network, got ${address.networkParameters}"
)
logger.info(s"Sending to $address at feerate $feeRate")
for {
utxoDbs <- spendingInfoDAO.findByOutPoints(outPoints)
diff = utxoDbs.map(_.outPoint).diff(outPoints)
_ = require(
diff.isEmpty,
s"Not all OutPoints belong to this wallet, diff $diff"
)
spentUtxos =
utxoDbs.filterNot(utxo => TxoState.receivedStates.contains(utxo.state))
_ = require(
spentUtxos.isEmpty,
s"Some out points given have already been spent, ${spentUtxos.map(_.outPoint)}"
)
utxos <- Future.sequence {
utxoDbs.map(utxo =>
transactionDAO
.findByOutPoint(utxo.outPoint)
.map(txDb => utxo.toUTXOInfo(keyManager, txDb.get.transaction)))
}
inputInfos = utxos.map(_.inputInfo)
utxoAmount = utxoDbs.map(_.output.value).sum
dummyOutput = TransactionOutput(utxoAmount, address.scriptPubKey)
inputs = InputUtil.calcSequenceForInputs(utxos)
txBuilder = RawTxBuilder() ++= inputs += dummyOutput
finalizer = SubtractFeeFromOutputsFinalizer(
inputInfos,
feeRate,
Vector(address.scriptPubKey)
)
.andThen(ShuffleFinalizer)
.andThen(AddWitnessDataFinalizer(inputInfos))
withFinalizer = txBuilder.setFinalizer(finalizer)
tmp = withFinalizer.buildTx()
_ = require(
tmp.outputs.size == 1,
s"Created tx is not as expected, does not have 1 output, got $tmp"
)
rawTxHelper = FundRawTxHelper(withFinalizer, utxos, feeRate, Future.unit)
tx <- finishSend(
rawTxHelper,
tmp.outputs.head.value,
feeRate,
Vector.empty
)
} yield tx
}
override def sendFromOutPoints(
outPoints: Vector[TransactionOutPoint],
address: BitcoinAddress,
amount: CurrencyUnit,
feeRate: FeeUnit,
fromAccount: AccountDb,
newTags: Vector[AddressTag]
)(implicit ec: ExecutionContext): Future[Transaction] = {
require(
address.networkParameters.isSameNetworkBytes(networkParameters),
s"Cannot send to address on other network, got ${address.networkParameters}"
)
logger.info(s"Sending $amount to $address at feerate $feeRate")
for {
utxoDbs <- spendingInfoDAO.findByOutPoints(outPoints)
diff = utxoDbs.map(_.outPoint).diff(outPoints)
_ = require(
diff.isEmpty,
s"Not all OutPoints belong to this wallet, diff $diff"
)
spentUtxos =
utxoDbs.filterNot(utxo => TxoState.receivedStates.contains(utxo.state))
_ = require(
spentUtxos.isEmpty,
s"Some out points given have already been spent, ${spentUtxos.map(_.outPoint)}"
)
prevTxFs = utxoDbs.map(utxo =>
transactionDAO.findByOutPoint(utxo.outPoint).map(_.get.transaction))
prevTxs <- FutureUtil.collect(prevTxFs)
utxos =
utxoDbs
.zip(prevTxs)
.map(info => info._1.toUTXOInfo(keyManager, info._2))
changeAddr <- accountHandling.getNewChangeAddress(fromAccount.hdAccount)
output = TransactionOutput(amount, address.scriptPubKey)
txBuilder = ShufflingNonInteractiveFinalizer.txBuilderFrom(
Vector(output),
utxos,
feeRate,
changeAddr.scriptPubKey
)
rawTxHelper = FundRawTxHelper(txBuilder, utxos, feeRate, Future.unit)
tx <- finishSend(rawTxHelper, amount, feeRate, newTags)
} yield tx
}
/** Sends the entire wallet balance to the given address */
override def sweepWallet(address: BitcoinAddress, feeRate: FeeUnit)(implicit
ec: ExecutionContext
): Future[Transaction] = {
for {
utxos <- utxoHandling.listUtxos()
balance = utxos.foldLeft(CurrencyUnits.zero)(_ + _.output.value)
_ = logger.info(s"Sweeping wallet balance=$balance to address=$address")
outpoints = utxos.map(_.outPoint)
tx <- sendFromOutPoints(outpoints, address, feeRate)
} yield tx
}
override def bumpFeeRBF(
txId: DoubleSha256DigestBE,
newFeeRate: FeeUnit
): Future[Transaction] = {
for {
txDbOpt <- transactionDAO.findByTxId(txId)
txDb <- txDbOpt match {
case Some(db) => Future.successful(db)
case None =>
Future.failed(
new RuntimeException(s"Unable to find transaction ${txId.hex}")
)
}
tx = txDb.transaction
_ = require(TxUtil.isRBFEnabled(tx), "Transaction is not signaling RBF")
outPoints = tx.inputs.map(_.previousOutput).toVector
spks = tx.outputs.map(_.scriptPubKey).toVector
utxos <- spendingInfoDAO.findByOutPoints(outPoints)
_ = require(utxos.nonEmpty, "Can only bump fee for our own transaction")
_ = require(
utxos.size == tx.inputs.size,
"Can only bump fee for a transaction we own all the inputs"
)
_ = require(
txDb.blockHashOpt.isEmpty,
s"Cannot replace a confirmed transaction, ${txDb.blockHashOpt.get.hex}"
)
spendingInfos <- FutureUtil.sequentially(utxos) { utxo =>
transactionDAO
.findByOutPoint(utxo.outPoint)
.map(txDbOpt =>
utxo.toUTXOInfo(keyManager = keyManager, txDbOpt.get.transaction))
}
_ = {
val inputAmount = utxos.foldLeft(CurrencyUnits.zero)(_ + _.output.value)
val oldFeeRate = newFeeRate match {
case _: SatoshisPerByte =>
SatoshisPerByte.calc(inputAmount, tx)
case _: SatoshisPerKiloByte =>
SatoshisPerKiloByte.calc(inputAmount, tx)
case _: SatoshisPerVirtualByte =>
SatoshisPerVirtualByte.calc(inputAmount, tx)
case _: SatoshisPerKW =>
SatoshisPerKW.calc(inputAmount, tx)
}
require(
oldFeeRate.currencyUnit < newFeeRate.currencyUnit,
s"Cannot bump to a lower fee ${oldFeeRate.currencyUnit} < ${newFeeRate.currencyUnit}"
)
}
myAddrs <- addressDAO.findByScriptPubKeys(spks)
_ = require(myAddrs.nonEmpty, "Must have an output we own")
changeSpks = myAddrs.flatMap { db =>
if (db.isChange) {
Some(db.scriptPubKey)
} else None
}
changeSpk =
if (changeSpks.nonEmpty) {
// Pick a random change spk
Random.shuffle(changeSpks).head
} else {
// If none are explicit change, pick a random one we own
Random.shuffle(myAddrs.map(_.scriptPubKey)).head
}
oldOutputs <- spendingInfoDAO.findDbsForTx(txId)
// delete old outputs
_ <- spendingInfoDAO.deleteAll(oldOutputs)
sequence = tx.inputs.head.sequence + UInt32.one
outputs = tx.outputs.filterNot(_.scriptPubKey == changeSpk)
txBuilder = StandardNonInteractiveFinalizer.txBuilderFrom(
outputs,
spendingInfos,
newFeeRate,
changeSpk,
sequence
)
amount = outputs.foldLeft(CurrencyUnits.zero)(_ + _.value)
rawTxHelper = FundRawTxHelper(
txBuilder,
spendingInfos,
newFeeRate,
Future.unit
)
tx <-
finishSend(rawTxHelper, amount, newFeeRate, Vector.empty)
} yield tx
}
override def sendWithAlgo(
address: BitcoinAddress,
amount: CurrencyUnit,
feeRate: FeeUnit,
algo: CoinSelectionAlgo,
fromAccount: AccountDb,
newTags: Vector[AddressTag]
)(implicit ec: ExecutionContext): Future[Transaction] = {
require(
address.networkParameters.isSameNetworkBytes(networkParameters),
s"Cannot send to address on other network, got ${address.networkParameters}"
)
logger.info(s"Sending $amount to $address at feerate $feeRate")
val destination = TransactionOutput(amount, address.scriptPubKey)
for {
rawTxHelper <- fundTxHandling.fundRawTransactionInternal(
destinations = Vector(destination),
feeRate = feeRate,
fromAccount = fromAccount,
coinSelectionAlgo = algo,
fromTagOpt = None,
markAsReserved = true
)
tx <- finishSend(rawTxHelper, amount, feeRate, newTags)
} yield tx
}
override def sendToAddress(
address: BitcoinAddress,
amount: CurrencyUnit,
feeRate: FeeUnit,
fromAccount: AccountDb
)(implicit ec: ExecutionContext): Future[Transaction] =
sendWithAlgo(
address,
amount,
feeRate,
CoinSelectionAlgo.LeastWaste,
fromAccount
)
override def sendToAddress(
address: BitcoinAddress,
amount: CurrencyUnit,
feeRate: FeeUnit,
fromAccount: AccountDb,
newTags: Vector[AddressTag]
)(implicit ec: ExecutionContext): Future[Transaction] =
sendWithAlgo(
address,
amount,
feeRate,
CoinSelectionAlgo.LeastWaste,
fromAccount,
newTags
)
override def sendToAddresses(
addresses: Vector[BitcoinAddress],
amounts: Vector[CurrencyUnit],
feeRate: FeeUnit,
fromAccount: AccountDb,
newTags: Vector[AddressTag]
)(implicit ec: ExecutionContext): Future[Transaction] = {
require(
amounts.size == addresses.size,
"Must have an amount for every address"
)
require(
addresses.forall(
_.networkParameters.isSameNetworkBytes(networkParameters)
),
s"Cannot send to address on other network, got ${addresses.map(_.networkParameters)}"
)
val destinations = addresses.zip(amounts).map { case (address, amount) =>
logger.info(s"Sending $amount to $address at feerate $feeRate")
TransactionOutput(amount, address.scriptPubKey)
}
sendToOutputs(destinations, feeRate, fromAccount, newTags)
}
override def makeOpReturnCommitment(
message: String,
hashMessage: Boolean,
feeRate: FeeUnit,
fromAccount: AccountDb
)(implicit ec: ExecutionContext): Future[Transaction] = {
val messageToUse = if (hashMessage) {
CryptoUtil.sha256(ByteVector(message.getBytes)).bytes
} else {
if (message.length > 80) {
throw new IllegalArgumentException(
s"Message cannot be greater than 80 characters, it should be hashed, got $message"
)
} else ByteVector(message.getBytes)
}
val asm = Seq(OP_RETURN) ++ BitcoinScriptUtil.calculatePushOp(
messageToUse
) :+ ScriptConstant(messageToUse)
val scriptPubKey = ScriptPubKey(asm)
val output = TransactionOutput(0.satoshis, scriptPubKey)
for {
fundRawTxHelper <- fundTxHandling.fundRawTransactionInternal(
destinations = Vector(output),
feeRate = feeRate,
fromAccount = fromAccount,
coinSelectionAlgo = CoinSelectionAlgo.RandomSelection,
fromTagOpt = None,
markAsReserved = true
)
tx <- finishSend(
fundRawTxHelper,
CurrencyUnits.zero,
feeRate,
Vector.empty
)
} yield tx
}
override def sendToOutputs(
outputs: Vector[TransactionOutput],
feeRate: FeeUnit,
fromAccount: AccountDb,
newTags: Vector[AddressTag]
)(implicit ec: ExecutionContext): Future[Transaction] = {
for {
fundRawTxHelper <- fundTxHandling.fundRawTransactionInternal(
destinations = outputs,
feeRate = feeRate,
fromAccount = fromAccount,
fromTagOpt = None,
markAsReserved = true
)
sentAmount = outputs.foldLeft(CurrencyUnits.zero)(_ + _.value)
tx <- finishSend(fundRawTxHelper, sentAmount, feeRate, newTags)
} yield tx
}
/** @inheritdoc */
override def isChange(output: TransactionOutput): Future[Boolean] = {
addressDAO.findByScriptPubKey(output.scriptPubKey).map {
@ -828,135 +354,13 @@ abstract class Wallet extends NeutrinoHDWalletApi with WalletLogger {
}
}
/** @inheritdoc */
override def bumpFeeCPFP(
txId: DoubleSha256DigestBE,
feeRate: FeeUnit
): Future[Transaction] = {
for {
txDbOpt <- transactionDAO.findByTxId(txId)
txDb <- txDbOpt match {
case Some(db) => Future.successful(db)
case None =>
Future.failed(
new RuntimeException(s"Unable to find transaction ${txId.hex}")
)
}
tx = txDb.transaction
spendingInfos <- spendingInfoDAO.findTx(tx)
_ = require(
spendingInfos.nonEmpty,
s"Transaction ${txId.hex} must have an output we own"
)
_ = require(
txDb.blockHashOpt.isEmpty,
s"Cannot replace a confirmed transaction, ${txDb.blockHashOpt.get.hex}"
)
changeSpendingInfos = spendingInfos.flatMap { db =>
if (db.isChange) {
Some(db)
} else None
}
spendingInfo =
if (changeSpendingInfos.nonEmpty) {
// Pick a random change spendingInfo
Random.shuffle(changeSpendingInfos).head
} else {
// If none are explicit change, pick a random one we own
Random.shuffle(spendingInfos).head
}
addr <- addressHandling.getNewChangeAddress()
childTx <- sendFromOutPoints(Vector(spendingInfo.outPoint), addr, feeRate)
} yield childTx
}
override def signPSBT(
psbt: PSBT
)(implicit ec: ExecutionContext): Future[PSBT] = {
val inputTxIds = psbt.transaction.inputs.zipWithIndex.map {
case (input, index) =>
input.previousOutput.txIdBE -> index
}.toMap
for {
accountDbs <- accountDAO.findAll()
ourXpubs = accountDbs.map(_.xpub)
utxos <- spendingInfoDAO.findAll()
txs <- transactionDAO.findByTxIds(inputTxIds.keys.toVector)
} yield {
val updated = txs.foldLeft(psbt) { (accum, tx) =>
val index = inputTxIds(tx.txIdBE)
accum.addUTXOToInput(tx.transaction, index)
}
val signed =
updated.inputMaps.zipWithIndex.foldLeft(updated) {
case (unsigned, (input, index)) =>
val xpubKeyPaths = input.BIP32DerivationPaths
.filter { path =>
ourXpubs.exists(_.fingerprint == path.masterFingerprint)
}
.map(bip32Path =>
HDPath.fromString(
bip32Path.path.toString
)) // TODO add a way to get a HDPath from a BIP32 Path
val (utxoPath, withData) = {
val outPoint = unsigned.transaction.inputs(index).previousOutput
utxos.find(_.outpoint == outPoint) match {
case Some(utxo) =>
val psbtWithUtxoData = utxo.redeemScript match {
case Some(redeemScript) =>
unsigned.addRedeemOrWitnessScriptToInput(
redeemScript,
index
)
case None => unsigned
}
(Vector(utxo.path), psbtWithUtxoData)
case None => (Vector.empty, unsigned)
}
}
val keyPaths = xpubKeyPaths ++ utxoPath
keyPaths.foldLeft(withData) { (accum, hdPath) =>
val sign = keyManager.toSign(hdPath)
// Only sign if that key doesn't have a signature yet
if (
!input.partialSignatures.exists(
_.pubKey.toPublicKey == sign.publicKey
)
) {
logger.debug(
s"Signing input $index with key ${sign.publicKey.hex}"
)
accum.sign(index, sign)
} else {
accum
}
}
}
if (updated == signed) {
logger.warn("Did not find any keys or utxos that belong to this wallet")
}
signed
}
}
override def getWalletName(): Future[String] = {
Future.successful(walletConfig.walletName)
}
override def getInfo(): Future[WalletInfo] = {
for {
accountDb <- getDefaultAccount()
accountDb <- accountHandling.getDefaultAccount()
walletState <- getSyncState()
rescan <- rescanHandling.isRescanning()
} yield {
@ -1017,18 +421,10 @@ abstract class Wallet extends NeutrinoHDWalletApi with WalletLogger {
override def listUtxos(state: TxoState): Future[Vector[SpendingInfoDb]] =
utxoHandling.listUtxos(state)
override def listUtxos(
hdAccount: HDAccount): Future[Vector[SpendingInfoDb]] = {
utxoHandling.listUtxos(hdAccount)
}
override def listUtxos(tag: AddressTag): Future[Vector[SpendingInfoDb]] = {
utxoHandling.listUtxos(tag)
}
override def listDefaultAccountUtxos(): Future[Vector[SpendingInfoDb]] = {
utxoHandling.listDefaultAccountUtxos()
}
def markUTXOsAsReserved(
utxos: Vector[SpendingInfoDb]): Future[Vector[SpendingInfoDb]] = {
utxoHandling.markUTXOsAsReserved(utxos)
@ -1052,11 +448,6 @@ abstract class Wallet extends NeutrinoHDWalletApi with WalletLogger {
tx: Transaction): Future[Vector[SpendingInfoDb]] = {
utxoHandling.unmarkUTXOsAsReserved(tx)
}
override def getDefaultAccountForType(
addressType: AddressType): Future[AccountDb] = {
accountHandling.getDefaultAccountForType(addressType)
}
}
// todo: create multiple wallets, need to maintain multiple databases

View file

@ -9,16 +9,14 @@ import org.bitcoins.core.api.dlc.wallet.db.{
IncomingDLCOfferDb
}
import org.bitcoins.core.api.feeprovider.FeeRateApi
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.currency.{CurrencyUnit, Satoshis}
import org.bitcoins.core.dlc.accounting.DLCWalletAccounting
import org.bitcoins.core.gcs.GolombFilter
import org.bitcoins.core.hd.{AddressType, HDAccount, HDPurpose}
import org.bitcoins.core.hd.HDAccount
import org.bitcoins.core.number.UInt32
import org.bitcoins.core.protocol.blockchain.Block
import org.bitcoins.core.protocol.dlc.models.*
import org.bitcoins.core.protocol.script.ScriptPubKey
import org.bitcoins.core.protocol.tlv.*
@ -28,11 +26,6 @@ import org.bitcoins.core.protocol.transaction.{
TransactionOutput
}
import org.bitcoins.core.protocol.BitcoinAddress
import org.bitcoins.core.psbt.PSBT
import org.bitcoins.core.wallet.builder.{
FundRawTxHelper,
ShufflingNonInteractiveFinalizer
}
import org.bitcoins.core.wallet.fee.{FeeUnit, SatoshisPerVirtualByte}
import org.bitcoins.core.wallet.utxo.{AddressTag, TxoState}
import org.bitcoins.crypto.{DoubleSha256DigestBE, Sha256Digest}
@ -67,7 +60,15 @@ class WalletHolder(initWalletOpt: Option[DLCNeutrinoHDWalletApi])(implicit
override def fundTxHandling: FundTransactionHandlingApi =
wallet.fundTxHandling
override def utxoHandling: UtxoHandlingApi = wallet.utxoHandling
override def addressHandling: AddressHandlingApi = wallet.addressHandling
override def transactionProcessing: TransactionProcessingApi =
wallet.transactionProcessing
override def sendFundsHandling: SendFundsHandlingApi =
wallet.sendFundsHandling
def isInitialized: Boolean = synchronized {
walletOpt.isDefined
}
@ -108,34 +109,6 @@ class WalletHolder(initWalletOpt: Option[DLCNeutrinoHDWalletApi])(implicit
override def getNewChangeAddress(): Future[BitcoinAddress] = delegate(
_.getNewChangeAddress())
override def processBlock(block: Block): Future[Unit] =
delegate(_.processBlock(block))
override def processTransaction(
transaction: Transaction,
blockHashOpt: Option[DoubleSha256DigestBE]): Future[Unit] = {
delegate(_.processTransaction(transaction, blockHashOpt))
}
/** Processes TXs originating from our wallet. This is called right after
* we've signed a TX, updating our UTXO state.
*/
override def processOurTransaction(
transaction: Transaction,
feeRate: FeeUnit,
inputAmount: CurrencyUnit,
sentAmount: CurrencyUnit,
blockHashOpt: Option[DoubleSha256DigestBE],
newTags: Vector[AddressTag]
): Future[ProcessTxResult] = {
delegate(
_.processOurTransaction(transaction,
feeRate,
inputAmount,
sentAmount,
blockHashOpt,
newTags))
}
override def processCompactFilters(
blockFilters: Vector[(DoubleSha256DigestBE, GolombFilter)]
@ -164,17 +137,6 @@ class WalletHolder(initWalletOpt: Option[DLCNeutrinoHDWalletApi])(implicit
res
}
override def fundRawTransaction(
destinations: Vector[TransactionOutput],
feeRate: FeeUnit,
fromAccount: AccountDb,
markAsReserved: Boolean
): Future[FundRawTxHelper[ShufflingNonInteractiveFinalizer]] = {
delegate(
_.fundRawTransaction(destinations, feeRate, fromAccount, markAsReserved)
)
}
override def updateUtxoPendingStates(): Future[Vector[SpendingInfoDb]] =
delegate(_.updateUtxoPendingStates())
@ -192,10 +154,6 @@ class WalletHolder(initWalletOpt: Option[DLCNeutrinoHDWalletApi])(implicit
override def getUnconfirmedBalance(tag: AddressTag): Future[CurrencyUnit] =
delegate(_.getUnconfirmedBalance(tag))
override def listDefaultAccountUtxos(): Future[Vector[SpendingInfoDb]] = {
delegate(_.listDefaultAccountUtxos())
}
override def listTransactions(): Future[Vector[TransactionDb]] = {
delegate(_.listTransactions())
}
@ -203,11 +161,6 @@ class WalletHolder(initWalletOpt: Option[DLCNeutrinoHDWalletApi])(implicit
_.listUtxos()
)
override def listUtxos(
hdAccount: HDAccount): Future[Vector[SpendingInfoDb]] = {
delegate(_.listUtxos(hdAccount))
}
override def listUtxos(state: TxoState): Future[Vector[SpendingInfoDb]] =
delegate(_.listUtxos(state))
@ -233,27 +186,6 @@ class WalletHolder(initWalletOpt: Option[DLCNeutrinoHDWalletApi])(implicit
override def isEmpty(): Future[Boolean] = delegate(_.isEmpty())
override def sendFromOutPoints(
outPoints: Vector[TransactionOutPoint],
address: BitcoinAddress,
feeRate: FeeUnit
)(implicit ec: ExecutionContext): Future[Transaction] =
delegate(_.sendFromOutPoints(outPoints, address, feeRate))
override def sweepWallet(address: BitcoinAddress, feeRate: FeeUnit)(implicit
ec: ExecutionContext
): Future[Transaction] = delegate(_.sweepWallet(address, feeRate))
override def bumpFeeRBF(
txId: DoubleSha256DigestBE,
newFeeRate: FeeUnit
): Future[Transaction] = delegate(_.bumpFeeRBF(txId, newFeeRate))
override def bumpFeeCPFP(
txId: DoubleSha256DigestBE,
feeRate: FeeUnit
): Future[Transaction] = delegate(_.bumpFeeCPFP(txId, feeRate))
override def isChange(output: TransactionOutput): Future[Boolean] = delegate(
_.isChange(output)
)
@ -427,100 +359,11 @@ class WalletHolder(initWalletOpt: Option[DLCNeutrinoHDWalletApi])(implicit
address: InetSocketAddress
): Future[Vector[DLCStatus]] = delegate(_.listDLCsByContact(address))
override def keyManager: BIP39KeyManagerApi = wallet.keyManager
def getConfirmedBalance(account: HDAccount): Future[CurrencyUnit] =
delegate(_.accountHandling.getConfirmedBalance(account))
override def getConfirmedBalance(account: HDAccount): Future[CurrencyUnit] =
delegate(_.getConfirmedBalance(account))
override def getUnconfirmedBalance(account: HDAccount): Future[CurrencyUnit] =
delegate(_.getUnconfirmedBalance(account))
override def getDefaultAccount(): Future[AccountDb] = delegate(
_.getDefaultAccount()
)
override def getDefaultAccountForType(
addressType: AddressType
): Future[AccountDb] = delegate(_.getDefaultAccountForType(addressType))
override def sendWithAlgo(
address: BitcoinAddress,
amount: CurrencyUnit,
feeRate: FeeUnit,
algo: CoinSelectionAlgo,
fromAccount: AccountDb,
newTags: Vector[AddressTag]
)(implicit ec: ExecutionContext): Future[Transaction] = delegate(
_.sendWithAlgo(address, amount, feeRate, algo, fromAccount, newTags)
)
override def sendFromOutPoints(
outPoints: Vector[TransactionOutPoint],
address: BitcoinAddress,
amount: CurrencyUnit,
feeRate: FeeUnit,
fromAccount: AccountDb,
newTags: Vector[AddressTag]
)(implicit ec: ExecutionContext): Future[Transaction] = delegate(
_.sendFromOutPoints(
outPoints,
address,
amount,
feeRate,
fromAccount,
newTags
)
)
override def sendToAddress(
address: BitcoinAddress,
amount: CurrencyUnit,
feeRate: FeeUnit,
fromAccount: AccountDb,
newTags: Vector[AddressTag]
)(implicit ec: ExecutionContext): Future[Transaction] = delegate(
_.sendToAddress(address, amount, feeRate, fromAccount, newTags)
)
override def sendToAddresses(
addresses: Vector[BitcoinAddress],
amounts: Vector[CurrencyUnit],
feeRate: FeeUnit,
fromAccount: AccountDb,
newTags: Vector[AddressTag]
)(implicit ec: ExecutionContext): Future[Transaction] = delegate(
_.sendToAddresses(addresses, amounts, feeRate, fromAccount, newTags)
)
override def sendToOutputs(
outputs: Vector[TransactionOutput],
feeRate: FeeUnit,
fromAccount: AccountDb,
newTags: Vector[AddressTag]
)(implicit ec: ExecutionContext): Future[Transaction] = delegate(
_.sendToOutputs(outputs, feeRate, fromAccount, newTags)
)
override def signPSBT(psbt: PSBT)(implicit
ec: ExecutionContext
): Future[PSBT] = delegate(_.signPSBT(psbt))
override def makeOpReturnCommitment(
message: String,
hashMessage: Boolean,
feeRate: FeeUnit,
fromAccount: AccountDb
)(implicit ec: ExecutionContext): Future[Transaction] = delegate(
_.makeOpReturnCommitment(message, hashMessage, feeRate, fromAccount)
)
override def clearAllUtxos(): Future[HDWalletApi] = delegate(
_.clearAllUtxos()
)
override def clearAllAddresses(): Future[WalletApi] = {
delegate(_.clearAllAddresses())
}
def getUnconfirmedBalance(account: HDAccount): Future[CurrencyUnit] =
delegate(_.accountHandling.getUnconfirmedBalance(account))
override def getSyncDescriptorOpt(): Future[Option[SyncHeightDescriptor]] =
delegate(_.getSyncDescriptorOpt())
@ -546,77 +389,9 @@ class WalletHolder(initWalletOpt: Option[DLCNeutrinoHDWalletApi])(implicit
ec: ExecutionContext
): Future[CurrencyUnit] = delegate(_.getBalance(tag))
override def sendFromOutPoints(
outPoints: Vector[TransactionOutPoint],
address: BitcoinAddress,
amount: CurrencyUnit,
feeRateOpt: Option[FeeUnit]
)(implicit ec: ExecutionContext): Future[Transaction] = delegate(
_.sendFromOutPoints(outPoints, address, amount, feeRateOpt)
)
override def sendFromOutPoints(
outPoints: Vector[TransactionOutPoint],
address: BitcoinAddress,
feeRateOpt: Option[FeeUnit]
)(implicit ec: ExecutionContext): Future[Transaction] = delegate(
_.sendFromOutPoints(outPoints, address, feeRateOpt)
)
override def sweepWallet(address: BitcoinAddress)(implicit
def getBalance(account: HDAccount)(implicit
ec: ExecutionContext
): Future[Transaction] = delegate(_.sweepWallet(address))
override def sweepWallet(
address: BitcoinAddress,
feeRateOpt: Option[FeeUnit]
)(implicit ec: ExecutionContext): Future[Transaction] = delegate(
_.sweepWallet(address, feeRateOpt)
)
override def sendWithAlgo(
address: BitcoinAddress,
amount: CurrencyUnit,
feeRateOpt: Option[FeeUnit],
algo: CoinSelectionAlgo
)(implicit ec: ExecutionContext): Future[Transaction] = delegate(
_.sendWithAlgo(address, amount, feeRateOpt, algo)
)
override def sendToAddress(
address: BitcoinAddress,
amount: CurrencyUnit,
feeRateOpt: Option[FeeUnit]
)(implicit ec: ExecutionContext): Future[Transaction] = delegate(
_.sendToAddress(address, amount, feeRateOpt)
)
override def sendToOutputs(
outputs: Vector[TransactionOutput],
feeRateOpt: Option[FeeUnit]
)(implicit ec: ExecutionContext): Future[Transaction] = delegate(
_.sendToOutputs(outputs, feeRateOpt)
)
override def sendToAddresses(
addresses: Vector[BitcoinAddress],
amounts: Vector[CurrencyUnit],
feeRateOpt: Option[FeeUnit]
)(implicit ec: ExecutionContext): Future[Transaction] = delegate(
_.sendToAddresses(addresses, amounts, feeRateOpt)
)
override def makeOpReturnCommitment(
message: String,
hashMessage: Boolean,
feeRateOpt: Option[FeeUnit]
)(implicit ec: ExecutionContext): Future[Transaction] = delegate(
_.makeOpReturnCommitment(message, hashMessage, feeRateOpt)
)
override def getBalance(account: HDAccount)(implicit
ec: ExecutionContext
): Future[CurrencyUnit] = delegate(_.getBalance(account))
): Future[CurrencyUnit] = delegate(_.accountHandling.getBalance(account))
override def processCompactFilter(
blockHash: DoubleSha256DigestBE,
@ -624,191 +399,6 @@ class WalletHolder(initWalletOpt: Option[DLCNeutrinoHDWalletApi])(implicit
): Future[NeutrinoHDWalletApi] =
delegate(_.processCompactFilter(blockHash, blockFilter))
override def sendWithAlgo(
address: BitcoinAddress,
amount: CurrencyUnit,
feeRate: FeeUnit,
algo: CoinSelectionAlgo,
fromAccount: AccountDb
)(implicit ec: ExecutionContext): Future[Transaction] =
delegate(_.sendWithAlgo(address, amount, feeRate, algo, fromAccount))
override def sendWithAlgo(
address: BitcoinAddress,
amount: CurrencyUnit,
feeRateOpt: Option[FeeUnit],
algo: CoinSelectionAlgo,
fromAccount: AccountDb
)(implicit ec: ExecutionContext): Future[Transaction] =
delegate(_.sendWithAlgo(address, amount, feeRateOpt, algo, fromAccount))
override def sendWithAlgo(
address: BitcoinAddress,
amount: CurrencyUnit,
feeRate: FeeUnit,
algo: CoinSelectionAlgo
)(implicit ec: ExecutionContext): Future[Transaction] =
delegate(_.sendWithAlgo(address, amount, feeRate, algo))
override def sendWithAlgo(
address: BitcoinAddress,
amount: CurrencyUnit,
feeRate: FeeUnit,
algo: CoinSelectionAlgo,
newTags: Vector[AddressTag]
)(implicit ec: ExecutionContext): Future[Transaction] =
delegate(_.sendWithAlgo(address, amount, feeRate, algo, newTags))
override def sendFromOutPoints(
outPoints: Vector[TransactionOutPoint],
address: BitcoinAddress,
amount: CurrencyUnit,
feeRate: FeeUnit,
fromAccount: AccountDb
)(implicit ec: ExecutionContext): Future[Transaction] =
delegate(
_.sendFromOutPoints(outPoints, address, amount, feeRate, fromAccount)
)
override def sendFromOutPoints(
outPoints: Vector[TransactionOutPoint],
address: BitcoinAddress,
amount: CurrencyUnit,
feeRateOpt: Option[FeeUnit],
fromAccount: AccountDb
)(implicit ec: ExecutionContext): Future[Transaction] =
delegate(
_.sendFromOutPoints(outPoints, address, amount, feeRateOpt, fromAccount)
)
override def sendFromOutPoints(
outPoints: Vector[TransactionOutPoint],
address: BitcoinAddress,
amount: CurrencyUnit,
feeRate: FeeUnit
)(implicit ec: ExecutionContext): Future[Transaction] =
delegate(_.sendFromOutPoints(outPoints, address, amount, feeRate))
override def sendFromOutPoints(
outPoints: Vector[TransactionOutPoint],
address: BitcoinAddress,
amount: CurrencyUnit,
feeRate: FeeUnit,
newTags: Vector[AddressTag]
)(implicit ec: ExecutionContext): Future[Transaction] =
delegate(_.sendFromOutPoints(outPoints, address, amount, feeRate, newTags))
override def sendToAddress(
address: BitcoinAddress,
amount: CurrencyUnit,
feeRate: FeeUnit,
fromAccount: AccountDb
)(implicit ec: ExecutionContext): Future[Transaction] =
delegate(_.sendToAddress(address, amount, feeRate, fromAccount))
override def sendToAddress(
address: BitcoinAddress,
amount: CurrencyUnit,
feeRateOpt: Option[FeeUnit],
fromAccount: AccountDb
)(implicit ec: ExecutionContext): Future[Transaction] =
delegate(_.sendToAddress(address, amount, feeRateOpt, fromAccount))
override def sendToAddress(
address: BitcoinAddress,
amount: CurrencyUnit,
feeRate: FeeUnit
)(implicit ec: ExecutionContext): Future[Transaction] =
delegate(_.sendToAddress(address, amount, feeRate))
override def sendToAddress(
address: BitcoinAddress,
amount: CurrencyUnit,
feeRate: FeeUnit,
newTags: Vector[AddressTag]
)(implicit ec: ExecutionContext): Future[Transaction] =
delegate(_.sendToAddress(address, amount, feeRate, newTags))
override def sendToAddresses(
addresses: Vector[BitcoinAddress],
amounts: Vector[CurrencyUnit],
feeRate: FeeUnit,
fromAccount: AccountDb
)(implicit ec: ExecutionContext): Future[Transaction] =
delegate(_.sendToAddresses(addresses, amounts, feeRate, fromAccount))
override def sendToAddresses(
addresses: Vector[BitcoinAddress],
amounts: Vector[CurrencyUnit],
feeRateOpt: Option[FeeUnit],
fromAccount: AccountDb
)(implicit ec: ExecutionContext): Future[Transaction] =
delegate(_.sendToAddresses(addresses, amounts, feeRateOpt, fromAccount))
override def sendToAddresses(
addresses: Vector[BitcoinAddress],
amounts: Vector[CurrencyUnit],
feeRate: FeeUnit
)(implicit ec: ExecutionContext): Future[Transaction] =
delegate(_.sendToAddresses(addresses, amounts, feeRate))
override def sendToAddresses(
addresses: Vector[BitcoinAddress],
amounts: Vector[CurrencyUnit],
feeRate: FeeUnit,
newTags: Vector[AddressTag]
)(implicit ec: ExecutionContext): Future[Transaction] =
delegate(_.sendToAddresses(addresses, amounts, feeRate, newTags))
override def sendToOutputs(
outputs: Vector[TransactionOutput],
feeRate: FeeUnit,
fromAccount: AccountDb
)(implicit ec: ExecutionContext): Future[Transaction] =
delegate(_.sendToOutputs(outputs, feeRate, fromAccount))
override def sendToOutputs(
outputs: Vector[TransactionOutput],
feeRateOpt: Option[FeeUnit],
fromAccount: AccountDb
)(implicit ec: ExecutionContext): Future[Transaction] =
delegate(_.sendToOutputs(outputs, feeRateOpt, fromAccount))
override def sendToOutputs(
outputs: Vector[TransactionOutput],
feeRate: FeeUnit,
newTags: Vector[AddressTag]
)(implicit ec: ExecutionContext): Future[Transaction] =
delegate(_.sendToOutputs(outputs, feeRate, newTags))
override def sendToOutputs(
outputs: Vector[TransactionOutput],
feeRate: FeeUnit
)(implicit ec: ExecutionContext): Future[Transaction] =
delegate(_.sendToOutputs(outputs, feeRate))
override def makeOpReturnCommitment(
message: String,
hashMessage: Boolean,
feeRateOpt: Option[FeeUnit],
fromAccount: AccountDb
)(implicit ec: ExecutionContext): Future[Transaction] =
delegate(
_.makeOpReturnCommitment(message, hashMessage, feeRateOpt, fromAccount)
)
override def makeOpReturnCommitment(
message: String,
hashMessage: Boolean,
feeRate: FeeUnit
)(implicit ec: ExecutionContext): Future[Transaction] =
delegate(_.makeOpReturnCommitment(message, hashMessage, feeRate))
override def listAccounts(purpose: HDPurpose)(implicit
ec: ExecutionContext
): Future[Vector[AccountDb]] =
delegate(_.listAccounts(purpose))
override def createDLCOffer(
contractInfoTLV: ContractInfoTLV,
collateral: Satoshis,

View file

@ -93,6 +93,16 @@ case class AccountHandling(
}
}
override def getUnconfirmedBalance(
account: HDAccount
): Future[CurrencyUnit] = {
safeDatabase.run(spendingInfoDAO.getUnconfirmedBalanceAction(Some(account)))
}
override def getConfirmedBalance(account: HDAccount): Future[CurrencyUnit] = {
safeDatabase.run(spendingInfoDAO.getConfirmedBalanceAction(Some(account)))
}
/** @inheritdoc */
override def listAccounts(): Future[Vector[AccountDb]] =
accountDAO.findAll()

View file

@ -1,6 +1,7 @@
package org.bitcoins.wallet.internal
import org.apache.pekko.actor.ActorSystem
import org.bitcoins.core.api.feeprovider.FeeRateApi
import org.bitcoins.core.api.keymanager.BIP39KeyManagerApi
import org.bitcoins.core.api.wallet.*
import org.bitcoins.core.api.wallet.db.AccountDb
@ -22,9 +23,11 @@ case class FundTransactionHandling(
accountHandling: AccountHandling,
utxoHandling: UtxoHandling,
addressHandling: AddressHandlingApi,
transactionProcessing: TransactionProcessingApi,
spendingInfoDAO: SpendingInfoDAO,
transactionDAO: TransactionDAO,
keyManager: BIP39KeyManagerApi)(implicit
keyManager: BIP39KeyManagerApi,
feeRateApi: FeeRateApi)(implicit
walletConfig: WalletAppConfig,
system: ActorSystem)
extends FundTransactionHandlingApi

View file

@ -0,0 +1,626 @@
package org.bitcoins.wallet.internal
import org.bitcoins.commons.util.BitcoinSLogger
import org.bitcoins.core.api.feeprovider.FeeRateApi
import org.bitcoins.core.api.keymanager.BIP39KeyManagerApi
import org.bitcoins.core.api.wallet.db.AccountDb
import org.bitcoins.core.api.wallet.{
AccountHandlingApi,
AddressHandlingApi,
CoinSelectionAlgo,
SendFundsHandlingApi,
TransactionProcessingApi,
UtxoHandlingApi
}
import org.bitcoins.core.config.NetworkParameters
import org.bitcoins.core.currency.{CurrencyUnit, CurrencyUnits, Satoshis}
import org.bitcoins.core.hd.HDPath
import org.bitcoins.core.number.UInt32
import org.bitcoins.core.protocol.BitcoinAddress
import org.bitcoins.core.protocol.script.ScriptPubKey
import org.bitcoins.core.protocol.transaction.{
InputUtil,
Transaction,
TransactionOutPoint,
TransactionOutput,
TxUtil
}
import org.bitcoins.core.psbt.PSBT
import org.bitcoins.core.script.constant.ScriptConstant
import org.bitcoins.core.script.control.OP_RETURN
import org.bitcoins.core.util.{BitcoinScriptUtil, FutureUtil}
import org.bitcoins.core.wallet.builder.{
AddWitnessDataFinalizer,
FundRawTxHelper,
RawTxBuilder,
RawTxFinalizer,
ShuffleFinalizer,
ShufflingNonInteractiveFinalizer,
StandardNonInteractiveFinalizer,
SubtractFeeFromOutputsFinalizer
}
import org.bitcoins.core.wallet.fee.{
FeeUnit,
SatoshisPerByte,
SatoshisPerKW,
SatoshisPerKiloByte,
SatoshisPerVirtualByte
}
import org.bitcoins.core.wallet.utxo.{AddressTag, TxoState}
import org.bitcoins.crypto.{CryptoUtil, DoubleSha256DigestBE}
import org.bitcoins.wallet.config.WalletAppConfig
import org.bitcoins.wallet.models.{
AccountDAO,
AddressDAO,
SpendingInfoDAO,
TransactionDAO,
WalletDAOs
}
import scodec.bits.ByteVector
import scala.concurrent.{ExecutionContext, Future}
import scala.util.Random
case class SendFundsHandlingHandling(
accountHandling: AccountHandlingApi,
feeRateApi: FeeRateApi,
fundTxHandling: FundTransactionHandling,
addressHandling: AddressHandlingApi,
transactionProcessing: TransactionProcessingApi,
utxoHandling: UtxoHandlingApi,
keyManager: BIP39KeyManagerApi,
walletDAOs: WalletDAOs)(implicit walletConfig: WalletAppConfig)
extends SendFundsHandlingApi
with BitcoinSLogger {
import walletConfig.ec
import org.bitcoins.core.currency.currencyUnitNumeric
private val networkParameters: NetworkParameters = walletConfig.network
private val addressDAO: AddressDAO = walletDAOs.addressDAO
private val spendingInfoDAO: SpendingInfoDAO = walletDAOs.utxoDAO
private val transactionDAO: TransactionDAO = walletDAOs.transactionDAO
private val accountDAO: AccountDAO = walletDAOs.accountDAO
override def bumpFeeRBF(
txId: DoubleSha256DigestBE,
newFeeRate: FeeUnit
): Future[Transaction] = {
for {
txDbOpt <- transactionDAO.findByTxId(txId)
txDb <- txDbOpt match {
case Some(db) => Future.successful(db)
case None =>
Future.failed(
new RuntimeException(s"Unable to find transaction ${txId.hex}")
)
}
tx = txDb.transaction
_ = require(TxUtil.isRBFEnabled(tx), "Transaction is not signaling RBF")
outPoints = tx.inputs.map(_.previousOutput).toVector
spks = tx.outputs.map(_.scriptPubKey).toVector
utxos <- spendingInfoDAO.findByOutPoints(outPoints)
_ = require(utxos.nonEmpty, "Can only bump fee for our own transaction")
_ = require(
utxos.size == tx.inputs.size,
"Can only bump fee for a transaction we own all the inputs"
)
_ = require(
txDb.blockHashOpt.isEmpty,
s"Cannot replace a confirmed transaction, ${txDb.blockHashOpt.get.hex}"
)
spendingInfos <- FutureUtil.sequentially(utxos) { utxo =>
transactionDAO
.findByOutPoint(utxo.outPoint)
.map(txDbOpt =>
utxo.toUTXOInfo(keyManager = keyManager, txDbOpt.get.transaction))
}
_ = {
val inputAmount = utxos.foldLeft(CurrencyUnits.zero)(_ + _.output.value)
val oldFeeRate = newFeeRate match {
case _: SatoshisPerByte =>
SatoshisPerByte.calc(inputAmount, tx)
case _: SatoshisPerKiloByte =>
SatoshisPerKiloByte.calc(inputAmount, tx)
case _: SatoshisPerVirtualByte =>
SatoshisPerVirtualByte.calc(inputAmount, tx)
case _: SatoshisPerKW =>
SatoshisPerKW.calc(inputAmount, tx)
}
require(
oldFeeRate.currencyUnit < newFeeRate.currencyUnit,
s"Cannot bump to a lower fee ${oldFeeRate.currencyUnit} < ${newFeeRate.currencyUnit}"
)
}
myAddrs <- addressDAO.findByScriptPubKeys(spks)
_ = require(myAddrs.nonEmpty, "Must have an output we own")
changeSpks = myAddrs.flatMap { db =>
if (db.isChange) {
Some(db.scriptPubKey)
} else None
}
changeSpk =
if (changeSpks.nonEmpty) {
// Pick a random change spk
Random.shuffle(changeSpks).head
} else {
// If none are explicit change, pick a random one we own
Random.shuffle(myAddrs.map(_.scriptPubKey)).head
}
oldOutputs <- spendingInfoDAO.findDbsForTx(txId)
// delete old outputs
_ <- spendingInfoDAO.deleteAll(oldOutputs)
sequence = tx.inputs.head.sequence + UInt32.one
outputs = tx.outputs.filterNot(_.scriptPubKey == changeSpk)
txBuilder = StandardNonInteractiveFinalizer.txBuilderFrom(
outputs,
spendingInfos,
newFeeRate,
changeSpk,
sequence
)
amount = outputs.foldLeft(CurrencyUnits.zero)(_ + _.value)
rawTxHelper = FundRawTxHelper(
txBuilder,
spendingInfos,
newFeeRate,
Future.unit
)
tx <-
finishSend(rawTxHelper, amount, newFeeRate, Vector.empty)
} yield tx
}
/** @inheritdoc */
override def bumpFeeCPFP(
txId: DoubleSha256DigestBE,
feeRate: FeeUnit
): Future[Transaction] = {
for {
txDbOpt <- transactionDAO.findByTxId(txId)
txDb <- txDbOpt match {
case Some(db) => Future.successful(db)
case None =>
Future.failed(
new RuntimeException(s"Unable to find transaction ${txId.hex}")
)
}
tx = txDb.transaction
spendingInfos <- spendingInfoDAO.findTx(tx)
_ = require(
spendingInfos.nonEmpty,
s"Transaction ${txId.hex} must have an output we own"
)
_ = require(
txDb.blockHashOpt.isEmpty,
s"Cannot replace a confirmed transaction, ${txDb.blockHashOpt.get.hex}"
)
changeSpendingInfos = spendingInfos.flatMap { db =>
if (db.isChange) {
Some(db)
} else None
}
spendingInfo =
if (changeSpendingInfos.nonEmpty) {
// Pick a random change spendingInfo
Random.shuffle(changeSpendingInfos).head
} else {
// If none are explicit change, pick a random one we own
Random.shuffle(spendingInfos).head
}
addr <- addressHandling.getNewChangeAddress()
childTx <- sendFromOutPoints(Vector(spendingInfo.outPoint), addr, feeRate)
} yield childTx
}
override def makeOpReturnCommitment(
message: String,
hashMessage: Boolean,
feeRate: FeeUnit,
fromAccount: AccountDb
): Future[Transaction] = {
val messageToUse = if (hashMessage) {
CryptoUtil.sha256(ByteVector(message.getBytes)).bytes
} else {
if (message.length > 80) {
throw new IllegalArgumentException(
s"Message cannot be greater than 80 characters, it should be hashed, got $message"
)
} else ByteVector(message.getBytes)
}
val asm = Seq(OP_RETURN) ++ BitcoinScriptUtil.calculatePushOp(
messageToUse
) :+ ScriptConstant(messageToUse)
val scriptPubKey = ScriptPubKey(asm)
val output = TransactionOutput(Satoshis.zero, scriptPubKey)
for {
fundRawTxHelper <- fundTxHandling.fundRawTransactionInternal(
destinations = Vector(output),
feeRate = feeRate,
fromAccount = fromAccount,
coinSelectionAlgo = CoinSelectionAlgo.RandomSelection,
fromTagOpt = None,
markAsReserved = true
)
tx <- finishSend(
fundRawTxHelper,
CurrencyUnits.zero,
feeRate,
Vector.empty
)
} yield tx
}
override def makeOpReturnCommitment(
message: String,
hashMessage: Boolean,
feeRateOpt: Option[FeeUnit],
fromAccount: AccountDb): Future[Transaction] = {
for {
feeRate <- determineFeeRate(feeRateOpt)
tx <- makeOpReturnCommitment(message, hashMessage, feeRate, fromAccount)
} yield tx
}
override def makeOpReturnCommitment(
message: String,
hashMessage: Boolean,
feeRate: FeeUnit): Future[Transaction] = {
for {
account <- accountHandling.getDefaultAccount()
tx <- makeOpReturnCommitment(message, hashMessage, feeRate, account)
} yield tx
}
override def makeOpReturnCommitment(
message: String,
hashMessage: Boolean,
feeRateOpt: Option[FeeUnit]): Future[Transaction] = {
for {
feeRate <- determineFeeRate(feeRateOpt)
tx <- makeOpReturnCommitment(message, hashMessage, feeRate)
} yield tx
}
override def sendWithAlgo(
address: BitcoinAddress,
amount: CurrencyUnit,
feeRate: FeeUnit,
algo: CoinSelectionAlgo,
fromAccount: AccountDb,
newTags: Vector[AddressTag]
): Future[Transaction] = {
require(
address.networkParameters.isSameNetworkBytes(networkParameters),
s"Cannot send to address on other network, got ${address.networkParameters}"
)
logger.info(s"Sending $amount to $address at feerate $feeRate")
val destination = TransactionOutput(amount, address.scriptPubKey)
for {
rawTxHelper <- fundTxHandling.fundRawTransactionInternal(
destinations = Vector(destination),
feeRate = feeRate,
fromAccount = fromAccount,
coinSelectionAlgo = algo,
fromTagOpt = None,
markAsReserved = true
)
tx <- finishSend(rawTxHelper, amount, feeRate, newTags)
} yield tx
}
/** Sends money from the specified account
*
* todo: add error handling to signature
*/
override def sendFromOutPoints(
outPoints: Vector[TransactionOutPoint],
address: BitcoinAddress,
amount: CurrencyUnit,
feeRate: FeeUnit,
fromAccount: AccountDb,
newTags: Vector[AddressTag]
): Future[Transaction] = {
require(
address.networkParameters.isSameNetworkBytes(networkParameters),
s"Cannot send to address on other network, got ${address.networkParameters}"
)
logger.info(s"Sending $amount to $address at feerate $feeRate")
for {
utxoDbs <- spendingInfoDAO.findByOutPoints(outPoints)
diff = utxoDbs.map(_.outPoint).diff(outPoints)
_ = require(
diff.isEmpty,
s"Not all OutPoints belong to this wallet, diff $diff"
)
spentUtxos =
utxoDbs.filterNot(utxo => TxoState.receivedStates.contains(utxo.state))
_ = require(
spentUtxos.isEmpty,
s"Some out points given have already been spent, ${spentUtxos.map(_.outPoint)}"
)
prevTxFs = utxoDbs.map(utxo =>
transactionDAO.findByOutPoint(utxo.outPoint).map(_.get.transaction))
prevTxs <- FutureUtil.collect(prevTxFs)
utxos =
utxoDbs
.zip(prevTxs)
.map(info => info._1.toUTXOInfo(keyManager, info._2))
changeAddr <- accountHandling.getNewChangeAddress(fromAccount.hdAccount)
output = TransactionOutput(amount, address.scriptPubKey)
txBuilder = ShufflingNonInteractiveFinalizer.txBuilderFrom(
Vector(output),
utxos,
feeRate,
changeAddr.scriptPubKey
)
rawTxHelper = FundRawTxHelper(txBuilder, utxos, feeRate, Future.unit)
tx <- finishSend(rawTxHelper, amount, feeRate, newTags)
} yield tx
}
/** Sends money from the specified account
*
* todo: add error handling to signature
*/
override def sendToAddresses(
addresses: Vector[BitcoinAddress],
amounts: Vector[CurrencyUnit],
feeRate: FeeUnit,
fromAccount: AccountDb,
newTags: Vector[AddressTag]
): Future[Transaction] = {
require(
amounts.size == addresses.size,
"Must have an amount for every address"
)
require(
addresses.forall(
_.networkParameters.isSameNetworkBytes(networkParameters)
),
s"Cannot send to address on other network, got ${addresses.map(_.networkParameters)}"
)
val destinations = addresses.zip(amounts).map { case (address, amount) =>
logger.info(s"Sending $amount to $address at feerate $feeRate")
TransactionOutput(amount, address.scriptPubKey)
}
sendToOutputs(destinations, feeRate, fromAccount, newTags)
}
/** Sends money from the specified account
*
* todo: add error handling to signature
*/
override def sendToOutputs(
outputs: Vector[TransactionOutput],
feeRate: FeeUnit,
fromAccount: AccountDb,
newTags: Vector[AddressTag]
): Future[Transaction] = {
for {
fundRawTxHelper <- fundTxHandling.fundRawTransactionInternal(
destinations = outputs,
feeRate = feeRate,
fromAccount = fromAccount,
fromTagOpt = None,
markAsReserved = true
)
sentAmount = outputs.foldLeft(CurrencyUnits.zero)(_ + _.value)
tx <- finishSend(fundRawTxHelper, sentAmount, feeRate, newTags)
} yield tx
}
override def sendFromOutPoints(
outPoints: Vector[TransactionOutPoint],
address: BitcoinAddress,
feeRate: FeeUnit
): Future[Transaction] = {
require(
address.networkParameters.isSameNetworkBytes(networkParameters),
s"Cannot send to address on other network, got ${address.networkParameters}"
)
logger.info(s"Sending to $address at feerate $feeRate")
for {
utxoDbs <- spendingInfoDAO.findByOutPoints(outPoints)
diff = utxoDbs.map(_.outPoint).diff(outPoints)
_ = require(
diff.isEmpty,
s"Not all OutPoints belong to this wallet, diff $diff"
)
spentUtxos =
utxoDbs.filterNot(utxo => TxoState.receivedStates.contains(utxo.state))
_ = require(
spentUtxos.isEmpty,
s"Some out points given have already been spent, ${spentUtxos.map(_.outPoint)}"
)
utxos <- Future.sequence {
utxoDbs.map(utxo =>
transactionDAO
.findByOutPoint(utxo.outPoint)
.map(txDb => utxo.toUTXOInfo(keyManager, txDb.get.transaction)))
}
inputInfos = utxos.map(_.inputInfo)
utxoAmount = utxoDbs.map(_.output.value).sum
dummyOutput = TransactionOutput(utxoAmount, address.scriptPubKey)
inputs = InputUtil.calcSequenceForInputs(utxos)
txBuilder = RawTxBuilder() ++= inputs += dummyOutput
finalizer = SubtractFeeFromOutputsFinalizer(
inputInfos,
feeRate,
Vector(address.scriptPubKey)
)
.andThen(ShuffleFinalizer)
.andThen(AddWitnessDataFinalizer(inputInfos))
withFinalizer = txBuilder.setFinalizer(finalizer)
tmp = withFinalizer.buildTx()
_ = require(
tmp.outputs.size == 1,
s"Created tx is not as expected, does not have 1 output, got $tmp"
)
rawTxHelper = FundRawTxHelper(withFinalizer, utxos, feeRate, Future.unit)
tx <- finishSend(
rawTxHelper,
tmp.outputs.head.value,
feeRate,
Vector.empty
)
} yield tx
}
override def signPSBT(
psbt: PSBT
)(implicit ec: ExecutionContext): Future[PSBT] = {
val inputTxIds = psbt.transaction.inputs.zipWithIndex.map {
case (input, index) =>
input.previousOutput.txIdBE -> index
}.toMap
for {
accountDbs <- accountDAO.findAll()
ourXpubs = accountDbs.map(_.xpub)
utxos <- spendingInfoDAO.findAll()
txs <- transactionDAO.findByTxIds(inputTxIds.keys.toVector)
} yield {
val updated = txs.foldLeft(psbt) { (accum, tx) =>
val index = inputTxIds(tx.txIdBE)
accum.addUTXOToInput(tx.transaction, index)
}
val signed =
updated.inputMaps.zipWithIndex.foldLeft(updated) {
case (unsigned, (input, index)) =>
val xpubKeyPaths = input.BIP32DerivationPaths
.filter { path =>
ourXpubs.exists(_.fingerprint == path.masterFingerprint)
}
.map(bip32Path =>
HDPath.fromString(
bip32Path.path.toString
)) // TODO add a way to get a HDPath from a BIP32 Path
val (utxoPath, withData) = {
val outPoint = unsigned.transaction.inputs(index).previousOutput
utxos.find(_.outpoint == outPoint) match {
case Some(utxo) =>
val psbtWithUtxoData = utxo.redeemScript match {
case Some(redeemScript) =>
unsigned.addRedeemOrWitnessScriptToInput(
redeemScript,
index
)
case None => unsigned
}
(Vector(utxo.path), psbtWithUtxoData)
case None => (Vector.empty, unsigned)
}
}
val keyPaths = xpubKeyPaths ++ utxoPath
keyPaths.foldLeft(withData) { (accum, hdPath) =>
val sign = keyManager.toSign(hdPath)
// Only sign if that key doesn't have a signature yet
if (
!input.partialSignatures.exists(
_.pubKey.toPublicKey == sign.publicKey
)
) {
logger.debug(
s"Signing input $index with key ${sign.publicKey.hex}"
)
accum.sign(index, sign)
} else {
accum
}
}
}
if (updated == signed) {
logger.warn("Did not find any keys or utxos that belong to this wallet")
}
signed
}
}
/** Takes a [[RawTxBuilderWithFinalizer]] for a transaction to be sent, and
* completes it by: finalizing and signing the transaction, then correctly
* processing and logging it
*/
private def finishSend[F <: RawTxFinalizer](
rawTxHelper: FundRawTxHelper[F],
sentAmount: CurrencyUnit,
feeRate: FeeUnit,
newTags: Vector[AddressTag]
): Future[Transaction] = {
val signed = rawTxHelper.signedTx
val processedTxF = for {
ourOuts <- addressHandling.findOurOutputs(signed)
creditingAmount = rawTxHelper.scriptSigParams.foldLeft(
CurrencyUnits.zero
)(_ + _.amount)
_ <- transactionProcessing.processOurTransaction(
transaction = signed,
feeRate = feeRate,
inputAmount = creditingAmount,
sentAmount = sentAmount,
blockHashOpt = None,
newTags = newTags
)
} yield {
logger.debug(
s"Signed transaction=${signed.txIdBE.hex} with outputs=${signed.outputs.length}, inputs=${signed.inputs.length}"
)
logger.trace(s"Change output(s) for transaction=${signed.txIdBE.hex}")
ourOuts.foreach { out =>
logger.trace(s" $out")
}
signed
}
processedTxF.recoverWith { case _ =>
// if something fails, we need to unreserve the utxos associated with this tx
// and then propogate the failed future upwards
utxoHandling.unmarkUTXOsAsReserved(signed).flatMap(_ => processedTxF)
}
}
private def determineFeeRate(feeRateOpt: Option[FeeUnit]): Future[FeeUnit] =
feeRateOpt match {
case None =>
feeRateApi.getFeeRate()
case Some(feeRate) =>
Future.successful(feeRate)
}
}

View file

@ -18,7 +18,7 @@ import org.bitcoins.crypto.DoubleSha256DigestBE
import org.bitcoins.db.SafeDatabase
import org.bitcoins.wallet.callback.WalletCallbacks
import org.bitcoins.wallet.config.WalletAppConfig
import org.bitcoins.wallet.models.{SpendingInfoDAO, TransactionDAO}
import org.bitcoins.wallet.models.{AddressDAO, SpendingInfoDAO, TransactionDAO}
import slick.dbio.{DBIOAction, Effect, NoStream}
import scala.concurrent.Future
@ -30,6 +30,7 @@ import scala.concurrent.Future
case class UtxoHandling(
spendingInfoDAO: SpendingInfoDAO,
transactionDAO: TransactionDAO,
addressDAO: AddressDAO,
chainQueryApi: ChainQueryApi)(implicit
walletConfig: WalletAppConfig,
system: ActorSystem)
@ -40,6 +41,30 @@ case class UtxoHandling(
private val walletCallbacks: WalletCallbacks = walletConfig.callBacks
private val safeDatabase: SafeDatabase = spendingInfoDAO.safeDatabase
override def clearAllUtxos(): Future[Unit] = {
val aggregatedActions
: DBIOAction[Unit, NoStream, Effect.Write with Effect.Transactional] =
spendingInfoDAO.deleteAllAction().map(_ => ())
val resultedF = safeDatabase.run(aggregatedActions)
resultedF.failed.foreach(err =>
logger.error(
s"Failed to clear utxos, addresses and scripts from the database",
err
))
resultedF.map(_ => ())
}
override def clearAllAddresses(): Future[Unit] = {
val action = addressDAO
.deleteAllAction()
.map(_ => ())
safeDatabase
.run(action)
.map(_ => ())
}
/** @inheritdoc */
override def listUtxos(): Future[Vector[SpendingInfoDb]] = {
spendingInfoDAO.findAllUnspent()

View file

@ -44,7 +44,7 @@ trait WalletSync extends BitcoinSLogger {
blocksToSync <- blocksToSyncF
syncedWallet <- FutureUtil.foldLeftAsync(wallet, blocksToSync) {
case (wallet, nextBlock) =>
wallet
wallet.transactionProcessing
.processBlock(nextBlock)
.map(_ => wallet)
}