mirror of
https://github.com/bitcoin-s/bitcoin-s.git
synced 2025-02-21 22:21:53 +01:00
Reserved TxoState (#1111)
* Reserved TxoState * Allow fund transaction handling to mark utxos as Reserved * Add test, fix overloading default arg issue
This commit is contained in:
parent
bc34e2d327
commit
cf3dd113b5
8 changed files with 64 additions and 21 deletions
|
@ -15,6 +15,8 @@ class TxoStateTest extends BitcoinSUnitTest {
|
|||
TxoState.fromString("ConfirmedReceived").get must be(
|
||||
TxoState.ConfirmedReceived)
|
||||
|
||||
TxoState.fromString("Reserved").get must be(TxoState.Reserved)
|
||||
|
||||
TxoState.fromString("PendingConfirmationsSpent").get must be(
|
||||
TxoState.PendingConfirmationsSpent)
|
||||
|
||||
|
|
|
@ -18,6 +18,9 @@ object TxoState {
|
|||
/** Means we have received funds and they are fully confirmed for this utxo */
|
||||
final case object ConfirmedReceived extends ReceivedState
|
||||
|
||||
/** Means we have not spent this utxo yet, but will be used in a future transaction */
|
||||
final case object Reserved extends SpentState
|
||||
|
||||
/** Means we have spent this utxo, but it is not fully confirmed */
|
||||
final case object PendingConfirmationsSpent extends SpentState
|
||||
|
||||
|
@ -27,6 +30,7 @@ object TxoState {
|
|||
val all: Vector[TxoState] = Vector(DoesNotExist,
|
||||
PendingConfirmationsReceived,
|
||||
ConfirmedReceived,
|
||||
Reserved,
|
||||
PendingConfirmationsSpent,
|
||||
ConfirmedSpent)
|
||||
|
||||
|
|
|
@ -27,11 +27,14 @@ trait BitcoinSFixture extends BitcoinSAsyncFixtureTest {
|
|||
destroy: T => Future[Any])(test: OneArgAsyncTest): FutureOutcome = {
|
||||
val fixtureF = build()
|
||||
|
||||
val outcomeF: Future[Outcome] = fixtureF.flatMap { fixture =>
|
||||
test(fixture.asInstanceOf[FixtureParam]).toFuture
|
||||
}.recoverWith { case err =>
|
||||
FutureOutcome.failed(err).toFuture
|
||||
}
|
||||
val outcomeF: Future[Outcome] = fixtureF
|
||||
.flatMap { fixture =>
|
||||
test(fixture.asInstanceOf[FixtureParam]).toFuture
|
||||
}
|
||||
.recoverWith {
|
||||
case err =>
|
||||
FutureOutcome.failed(err).toFuture
|
||||
}
|
||||
|
||||
val futOutcome: FutureOutcome = new FutureOutcome(outcomeF)
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@ package org.bitcoins.wallet
|
|||
import org.bitcoins.core.currency.Bitcoins
|
||||
import org.bitcoins.core.protocol.transaction.TransactionOutput
|
||||
import org.bitcoins.core.wallet.fee.SatoshisPerVirtualByte
|
||||
import org.bitcoins.core.wallet.utxo.TxoState
|
||||
import org.bitcoins.testkit.util.TestUtil
|
||||
import org.bitcoins.testkit.wallet.{BitcoinSWalletTest, WalletTestUtil}
|
||||
import org.bitcoins.testkit.wallet.FundWalletUtil.FundedWallet
|
||||
|
@ -23,7 +24,8 @@ class FundTransactionHandlingTest extends BitcoinSWalletTest {
|
|||
val wallet = fundedWallet.wallet
|
||||
val fundedTxF = wallet.fundRawTransaction(destinations =
|
||||
Vector(destination),
|
||||
feeRate = feeRate)
|
||||
feeRate = feeRate,
|
||||
false)
|
||||
for {
|
||||
fundedTx <- fundedTxF
|
||||
} yield {
|
||||
|
@ -42,7 +44,8 @@ class FundTransactionHandlingTest extends BitcoinSWalletTest {
|
|||
val wallet = fundedWallet.wallet
|
||||
val fundedTxF = wallet.fundRawTransaction(destinations =
|
||||
Vector(newDestination),
|
||||
feeRate = feeRate)
|
||||
feeRate = feeRate,
|
||||
false)
|
||||
for {
|
||||
fundedTx <- fundedTxF
|
||||
} yield {
|
||||
|
@ -60,7 +63,8 @@ class FundTransactionHandlingTest extends BitcoinSWalletTest {
|
|||
|
||||
val wallet = fundedWallet.wallet
|
||||
val fundedTxF = wallet.fundRawTransaction(destinations = destinations,
|
||||
feeRate = feeRate)
|
||||
feeRate = feeRate,
|
||||
false)
|
||||
for {
|
||||
fundedTx <- fundedTxF
|
||||
} yield {
|
||||
|
@ -82,7 +86,8 @@ class FundTransactionHandlingTest extends BitcoinSWalletTest {
|
|||
val wallet = fundedWallet.wallet
|
||||
val fundedTxF = wallet.fundRawTransaction(destinations =
|
||||
Vector(tooBigOutput),
|
||||
feeRate = feeRate)
|
||||
feeRate = feeRate,
|
||||
false)
|
||||
|
||||
recoverToSucceededIf[RuntimeException] {
|
||||
fundedTxF
|
||||
|
@ -100,7 +105,8 @@ class FundTransactionHandlingTest extends BitcoinSWalletTest {
|
|||
//not have enough money for this
|
||||
val fundedTxF = wallet.fundRawTransaction(destinations =
|
||||
Vector(tooBigOutput),
|
||||
feeRate = feeRate)
|
||||
feeRate = feeRate,
|
||||
false)
|
||||
|
||||
recoverToSucceededIf[RuntimeException] {
|
||||
fundedTxF
|
||||
|
@ -120,7 +126,8 @@ class FundTransactionHandlingTest extends BitcoinSWalletTest {
|
|||
account1DbOpt <- account1DbF
|
||||
fundedTx <- wallet.fundRawTransaction(Vector(newDestination),
|
||||
feeRate,
|
||||
account1DbOpt.get)
|
||||
account1DbOpt.get,
|
||||
false)
|
||||
} yield {
|
||||
assert(fundedTx.inputs.nonEmpty)
|
||||
assert(fundedTx.outputs.contains(newDestination))
|
||||
|
@ -141,11 +148,27 @@ class FundTransactionHandlingTest extends BitcoinSWalletTest {
|
|||
account1DbOpt <- account1DbF
|
||||
fundedTx <- wallet.fundRawTransaction(Vector(newDestination),
|
||||
feeRate,
|
||||
account1DbOpt.get)
|
||||
account1DbOpt.get,
|
||||
false)
|
||||
} yield fundedTx
|
||||
|
||||
recoverToSucceededIf[RuntimeException] {
|
||||
fundedTxF
|
||||
}
|
||||
}
|
||||
|
||||
it must "mark utxos as reserved after building a transaction" in {
|
||||
fundedWallet: FundedWallet =>
|
||||
val wallet = fundedWallet.wallet
|
||||
val fundedTxF = wallet.fundRawTransaction(destinations =
|
||||
Vector(destination),
|
||||
feeRate = feeRate,
|
||||
markAsReserved = true)
|
||||
for {
|
||||
fundedTx <- fundedTxF
|
||||
spendingInfos <- wallet.spendingInfoDAO.findOutputsBeingSpent(fundedTx)
|
||||
} yield {
|
||||
assert(spendingInfos.exists(_.state == TxoState.Reserved))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -42,7 +42,8 @@ sealed abstract class Wallet extends LockedWallet with UnlockedWalletApi {
|
|||
destinations = Vector(destination),
|
||||
feeRate = feeRate,
|
||||
fromAccount = fromAccount,
|
||||
keyManagerOpt = Some(keyManager))
|
||||
keyManagerOpt = Some(keyManager),
|
||||
markAsReserved = false)
|
||||
signed <- txBuilder.sign
|
||||
ourOuts <- findOurOuts(signed)
|
||||
_ <- processOurTransaction(signed, blockHashOpt = None)
|
||||
|
|
|
@ -151,6 +151,9 @@ trait LockedWalletApi extends WalletApi {
|
|||
|
||||
def listAddresses(account: HDAccount): Future[Vector[AddressDb]]
|
||||
|
||||
def markUTXOsAsReserved(
|
||||
utxos: Vector[SpendingInfoDb]): Future[Vector[SpendingInfoDb]]
|
||||
|
||||
/** Checks if the wallet contains any data */
|
||||
def isEmpty(): Future[Boolean]
|
||||
|
||||
|
|
|
@ -16,24 +16,28 @@ trait FundTransactionHandling extends WalletLogger { self: LockedWalletApi =>
|
|||
|
||||
def fundRawTransaction(
|
||||
destinations: Vector[TransactionOutput],
|
||||
feeRate: FeeUnit): Future[Transaction] = {
|
||||
feeRate: FeeUnit,
|
||||
markAsReserved: Boolean): Future[Transaction] = {
|
||||
for {
|
||||
account <- getDefaultAccount()
|
||||
funded <- fundRawTransaction(destinations = destinations,
|
||||
feeRate = feeRate,
|
||||
fromAccount = account)
|
||||
fromAccount = account,
|
||||
markAsReserved = markAsReserved)
|
||||
} yield funded
|
||||
}
|
||||
|
||||
def fundRawTransaction(
|
||||
destinations: Vector[TransactionOutput],
|
||||
feeRate: FeeUnit,
|
||||
fromAccount: AccountDb): Future[Transaction] = {
|
||||
fromAccount: AccountDb,
|
||||
markAsReserved: Boolean = false): Future[Transaction] = {
|
||||
val txBuilderF =
|
||||
fundRawTransactionInternal(destinations = destinations,
|
||||
feeRate = feeRate,
|
||||
fromAccount = fromAccount,
|
||||
keyManagerOpt = None)
|
||||
keyManagerOpt = None,
|
||||
markAsReserved = markAsReserved)
|
||||
txBuilderF.flatMap(_.unsignedTx)
|
||||
}
|
||||
|
||||
|
@ -52,14 +56,17 @@ trait FundTransactionHandling extends WalletLogger { self: LockedWalletApi =>
|
|||
destinations: Vector[TransactionOutput],
|
||||
feeRate: FeeUnit,
|
||||
fromAccount: AccountDb,
|
||||
keyManagerOpt: Option[BIP39KeyManager]): Future[BitcoinTxBuilder] = {
|
||||
keyManagerOpt: Option[BIP39KeyManager],
|
||||
markAsReserved: Boolean = false): Future[BitcoinTxBuilder] = {
|
||||
val utxosF = listUtxos(fromAccount.hdAccount)
|
||||
val changeAddrF = getNewChangeAddress(fromAccount)
|
||||
val selectedUtxosF = for {
|
||||
walletUtxos <- utxosF
|
||||
//currently just grab the biggest utxos
|
||||
selectedUtxos = CoinSelector
|
||||
utxos = CoinSelector
|
||||
.accumulateLargest(walletUtxos, destinations, feeRate)
|
||||
selectedUtxos <- if (markAsReserved) markUTXOsAsReserved(utxos)
|
||||
else Future.successful(utxos)
|
||||
} yield selectedUtxos
|
||||
|
||||
val addrInfosWithUtxoF: Future[Vector[(SpendingInfoDb, AddressInfo)]] =
|
||||
|
|
|
@ -203,8 +203,8 @@ private[wallet] trait TransactionProcessing extends WalletLogger {
|
|||
s"Marked utxo=${updated.toHumanReadableString} as state=${updated.state}")
|
||||
)
|
||||
updatedF.map(Some(_))
|
||||
case TxoState.ConfirmedSpent | TxoState.PendingConfirmationsSpent |
|
||||
TxoState.DoesNotExist =>
|
||||
case TxoState.Reserved | TxoState.ConfirmedSpent |
|
||||
TxoState.PendingConfirmationsSpent | TxoState.DoesNotExist =>
|
||||
FutureUtil.none
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue