mirror of
https://github.com/bitcoin-s/bitcoin-s.git
synced 2024-11-19 09:52:09 +01:00
Add ability to unreserve utxos (#1458)
This commit is contained in:
parent
14f2ae793a
commit
c4c660158e
@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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]
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
/**
|
||||
|
Loading…
Reference in New Issue
Block a user