Add ability to unreserve utxos (#1458)

This commit is contained in:
Ben Carman 2020-05-22 06:29:11 -05:00 committed by GitHub
parent 14f2ae793a
commit c4c660158e
4 changed files with 94 additions and 38 deletions

View File

@ -79,4 +79,24 @@ class UTXOLifeCycleTest extends BitcoinSWalletTest {
assert(updatedCoins.forall(_.state == TxoState.Reserved))
}
}
it should "track a utxo state change to reserved and then to unreserved" in {
param =>
val WalletWithBitcoindRpc(wallet, _) = param
val dummyOutput = TransactionOutput(Satoshis(3000), EmptyScriptPubKey)
for {
tx <- wallet.fundRawTransaction(Vector(dummyOutput),
SatoshisPerVirtualByte.one,
markAsReserved = true)
reservedUtxos <- wallet.spendingInfoDAO.findOutputsBeingSpent(tx)
_ = assert(reservedUtxos.forall(_.state == TxoState.Reserved))
unreservedUtxos <- wallet.unmarkUTXOsAsReserved(reservedUtxos.toVector)
} yield {
assert(unreservedUtxos.forall(_.state != TxoState.Reserved))
}
}
}

View File

@ -14,22 +14,22 @@ import org.bitcoins.core.protocol.script.ScriptPubKey
import org.bitcoins.core.protocol.transaction._
import org.bitcoins.core.script.constant.ScriptConstant
import org.bitcoins.core.script.control.OP_RETURN
import org.bitcoins.core.util.BitcoinScriptUtil
import org.bitcoins.core.util.{BitcoinScriptUtil, FutureUtil}
import org.bitcoins.core.wallet.builder.{
NonInteractiveWithChangeFinalizer,
RawTxBuilderWithFinalizer,
RawTxSigner
}
import org.bitcoins.core.wallet.fee.FeeUnit
import org.bitcoins.core.wallet.utxo.TxoState.{
ConfirmedReceived,
PendingConfirmationsReceived
}
import org.bitcoins.core.wallet.utxo.{
InputInfo,
ScriptSignatureParams,
TxoState
}
import org.bitcoins.core.wallet.utxo.TxoState.{
ConfirmedReceived,
PendingConfirmationsReceived
}
import org.bitcoins.crypto.{CryptoUtil, ECPublicKey}
import org.bitcoins.keymanager.KeyManagerParams
import org.bitcoins.keymanager.bip39.BIP39KeyManager
@ -41,7 +41,7 @@ import org.bitcoins.wallet.models.{SpendingInfoDb, _}
import scodec.bits.ByteVector
import scala.concurrent.{ExecutionContext, Future}
import scala.util.{Failure, Success}
import scala.util.{Failure, Success, Try}
abstract class Wallet
extends WalletApi
@ -207,16 +207,43 @@ abstract class Wallet
spendingInfoDAO.updateAll(updated)
}
override def unmarkUTXOsAsReserved(
utxos: Vector[SpendingInfoDb]): Future[Vector[SpendingInfoDb]] = {
val unreserved = utxos.filterNot(_.state == TxoState.Reserved)
require(unreserved.isEmpty, s"Some utxos are not reserved, got $unreserved")
// unmark all utxos are reserved
val groupedUtxos = utxos
.map(_.copyWithState(TxoState.PendingConfirmationsReceived))
.groupBy(_.blockHash)
val mempoolUtxos = Try(groupedUtxos(None)).getOrElse(Vector.empty)
// get the ones in blocks
val utxosInBlocks = groupedUtxos.map {
case (Some(hash), utxos) =>
Some(hash, utxos)
case (None, _) =>
None
}.flatten
for {
updatedMempoolUtxos <- spendingInfoDAO.updateAll(mempoolUtxos)
// update the confirmed ones
updatedBlockUtxos <- FutureUtil
.sequentially(utxosInBlocks.toVector) {
case (hash, utxos) =>
updateUtxoConfirmedStates(utxos, hash)
}
} yield updatedMempoolUtxos ++ updatedBlockUtxos.flatten
}
/** @inheritdoc */
def updateUtxoPendingStates(
blockHeader: BlockHeader): Future[Vector[SpendingInfoDb]] = {
for {
infos <- spendingInfoDAO.findAllPendingConfirmation
updatedInfos <- {
val updatedInfoFs =
infos.map(info => updateUtxoConfirmedState(info, blockHeader.hashBE))
Future.sequence(updatedInfoFs)
}
updatedInfos <- updateUtxoConfirmedStates(infos, blockHeader.hashBE)
} yield updatedInfos
}

View File

@ -189,6 +189,9 @@ trait WalletApi extends WalletLogger {
def markUTXOsAsReserved(
utxos: Vector[SpendingInfoDb]): Future[Vector[SpendingInfoDb]]
def unmarkUTXOsAsReserved(
utxos: Vector[SpendingInfoDb]): Future[Vector[SpendingInfoDb]]
/** Checks if the wallet contains any data */
def isEmpty(): Future[Boolean]

View File

@ -17,9 +17,9 @@ import org.bitcoins.core.protocol.transaction.{
import org.bitcoins.core.util.EitherUtil
import org.bitcoins.core.wallet.utxo.TxoState
import org.bitcoins.crypto.DoubleSha256DigestBE
import org.bitcoins.wallet.{Wallet, WalletLogger}
import org.bitcoins.wallet.api.{AddUtxoError, AddUtxoResult, AddUtxoSuccess}
import org.bitcoins.wallet.models._
import org.bitcoins.wallet.{Wallet, WalletLogger}
import scala.concurrent.Future
import scala.util.{Failure, Success}
@ -46,40 +46,46 @@ private[wallet] trait UtxoHandling extends WalletLogger {
protected def updateUtxoConfirmedState(
txo: SpendingInfoDb,
blockHash: DoubleSha256DigestBE): Future[SpendingInfoDb] = {
updateUtxoConfirmedStates(Vector(txo), blockHash).map(_.head)
}
protected def updateUtxoConfirmedStates(
txos: Vector[SpendingInfoDb],
blockHash: DoubleSha256DigestBE): Future[Vector[SpendingInfoDb]] = {
for {
confsOpt <- chainQueryApi.getNumberOfConfirmations(blockHash)
stateChange <- {
stateChanges <- {
confsOpt match {
case None =>
Future.successful(txo)
Future.successful(txos)
case Some(confs) =>
txo.state match {
case TxoState.PendingConfirmationsReceived |
TxoState.DoesNotExist =>
if (confs >= walletConfig.requiredConfirmations) {
spendingInfoDAO.update(
txo.copyWithState(TxoState.ConfirmedReceived))
} else {
Future.successful(
txo.copyWithState(TxoState.PendingConfirmationsReceived))
}
case TxoState.PendingConfirmationsSpent =>
if (confs >= walletConfig.requiredConfirmations) {
spendingInfoDAO.update(
txo.copyWithState(TxoState.ConfirmedSpent))
} else {
Future.successful(txo)
}
case TxoState.Reserved =>
// We should keep the utxo as reserved so it is not used in
// a future transaction that it should not be in
Future.successful(txo)
case TxoState.ConfirmedReceived | TxoState.ConfirmedSpent =>
Future.successful(txo)
val updatedTxos = txos.map { txo =>
txo.state match {
case TxoState.PendingConfirmationsReceived |
TxoState.DoesNotExist =>
if (confs >= walletConfig.requiredConfirmations) {
txo.copyWithState(TxoState.ConfirmedReceived)
} else {
txo.copyWithState(TxoState.PendingConfirmationsReceived)
}
case TxoState.PendingConfirmationsSpent =>
if (confs >= walletConfig.requiredConfirmations) {
txo.copyWithState(TxoState.ConfirmedSpent)
} else {
txo
}
case TxoState.Reserved =>
// We should keep the utxo as reserved so it is not used in
// a future transaction that it should not be in
txo
case TxoState.ConfirmedReceived | TxoState.ConfirmedSpent =>
txo
}
}
spendingInfoDAO.upsertAll(updatedTxos)
}
}
} yield stateChange
} yield stateChanges
}
/**