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