mirror of
https://github.com/bitcoin-s/bitcoin-s.git
synced 2024-11-19 09:52:09 +01:00
Add Broadcast TxoState (#2735)
* Add broadcast TxoState * Update scaladoc
This commit is contained in:
parent
1daba85ddf
commit
12bff309c2
@ -19,7 +19,12 @@ object TxoState extends StringFactory[TxoState] {
|
||||
*/
|
||||
final case object ImmatureCoinbase extends TxoState
|
||||
|
||||
/** Means we have received funds to this utxo, but they are not confirmed */
|
||||
/** Means we have received funds to this utxo, and they have not been confirmed in a block */
|
||||
final case object BroadcastReceived extends ReceivedState
|
||||
|
||||
/** Means we have received funds to this utxo, and they have some confirmations but
|
||||
* have not reached our confirmation threshold
|
||||
*/
|
||||
final case object PendingConfirmationsReceived extends ReceivedState
|
||||
|
||||
/** Means we have received funds and they are fully confirmed for this utxo */
|
||||
@ -28,33 +33,49 @@ object TxoState extends StringFactory[TxoState] {
|
||||
/** 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, and they have not been confirmed in a block */
|
||||
final case object BroadcastSpent extends SpentState
|
||||
|
||||
/** Means we have spent this utxo, and they have some confirmations but
|
||||
* have not reached our confirmation threshold
|
||||
*/
|
||||
final case object PendingConfirmationsSpent extends SpentState
|
||||
|
||||
/** Means we have spent this utxo, and it is fully confirmed */
|
||||
final case object ConfirmedSpent extends SpentState
|
||||
|
||||
val pendingConfStates: Set[TxoState] =
|
||||
Set(TxoState.ImmatureCoinbase,
|
||||
TxoState.PendingConfirmationsReceived,
|
||||
TxoState.PendingConfirmationsSpent)
|
||||
Set(BroadcastSpent,
|
||||
BroadcastReceived,
|
||||
ImmatureCoinbase,
|
||||
PendingConfirmationsReceived,
|
||||
PendingConfirmationsSpent)
|
||||
|
||||
val confirmedStates: Set[TxoState] =
|
||||
Set(TxoState.ConfirmedReceived, TxoState.ConfirmedSpent)
|
||||
|
||||
val receivedStates: Set[TxoState] =
|
||||
Set(PendingConfirmationsReceived, ConfirmedReceived)
|
||||
Set(PendingConfirmationsReceived, ConfirmedReceived, BroadcastReceived)
|
||||
|
||||
val spentStates: Set[TxoState] =
|
||||
Set(PendingConfirmationsSpent, TxoState.ConfirmedSpent, Reserved)
|
||||
Set(PendingConfirmationsSpent,
|
||||
TxoState.ConfirmedSpent,
|
||||
Reserved,
|
||||
BroadcastSpent)
|
||||
|
||||
val all: Vector[TxoState] = Vector(DoesNotExist,
|
||||
ImmatureCoinbase,
|
||||
PendingConfirmationsReceived,
|
||||
ConfirmedReceived,
|
||||
Reserved,
|
||||
PendingConfirmationsSpent,
|
||||
ConfirmedSpent)
|
||||
val broadcastStates: Set[TxoState] = Set(BroadcastReceived, BroadcastSpent)
|
||||
|
||||
val all: Vector[TxoState] = Vector(
|
||||
DoesNotExist,
|
||||
ImmatureCoinbase,
|
||||
BroadcastReceived,
|
||||
PendingConfirmationsReceived,
|
||||
ConfirmedReceived,
|
||||
Reserved,
|
||||
BroadcastSpent,
|
||||
PendingConfirmationsSpent,
|
||||
ConfirmedSpent
|
||||
)
|
||||
|
||||
override def fromStringOpt(str: String): Option[TxoState] = {
|
||||
all.find(state => str.toLowerCase() == state.toString.toLowerCase)
|
||||
|
@ -25,7 +25,7 @@ class UTXOLifeCycleTest extends BitcoinSWalletTest {
|
||||
withFundedWalletAndBitcoind(test, getBIP39PasswordOpt())
|
||||
}
|
||||
|
||||
it should "track a utxo state change to pending spent" in { param =>
|
||||
it should "track a utxo state change to broadcast spent" in { param =>
|
||||
val WalletWithBitcoindRpc(wallet, _) = param
|
||||
|
||||
for {
|
||||
@ -35,7 +35,7 @@ class UTXOLifeCycleTest extends BitcoinSWalletTest {
|
||||
updatedCoins <- wallet.spendingInfoDAO.findOutputsBeingSpent(tx)
|
||||
newTransactions <- wallet.listTransactions()
|
||||
} yield {
|
||||
assert(updatedCoins.forall(_.state == TxoState.PendingConfirmationsSpent))
|
||||
assert(updatedCoins.forall(_.state == TxoState.BroadcastSpent))
|
||||
assert(updatedCoins.forall(_.spendingTxIdOpt.contains(tx.txIdBE)))
|
||||
assert(!oldTransactions.map(_.transaction).contains(tx))
|
||||
assert(newTransactions.map(_.transaction).contains(tx))
|
||||
@ -51,7 +51,7 @@ class UTXOLifeCycleTest extends BitcoinSWalletTest {
|
||||
|
||||
updatedCoins <- wallet.spendingInfoDAO.findOutputsBeingSpent(tx)
|
||||
newTransactions <- wallet.listTransactions()
|
||||
_ = assert(updatedCoins.forall(_.state == PendingConfirmationsSpent))
|
||||
_ = assert(updatedCoins.forall(_.state == BroadcastSpent))
|
||||
_ = assert(!oldTransactions.map(_.transaction).contains(tx))
|
||||
_ = assert(newTransactions.map(_.transaction).contains(tx))
|
||||
|
||||
@ -59,8 +59,8 @@ class UTXOLifeCycleTest extends BitcoinSWalletTest {
|
||||
hash <- bitcoind.getBestBlockHash
|
||||
_ <- wallet.processTransaction(tx, Some(hash))
|
||||
|
||||
pendingCoins <- wallet.spendingInfoDAO.findOutputsBeingSpent(tx)
|
||||
_ <- wallet.updateUtxoPendingStates()
|
||||
pendingCoins <- wallet.spendingInfoDAO.findOutputsBeingSpent(tx)
|
||||
_ = assert(pendingCoins.forall(_.state == PendingConfirmationsSpent))
|
||||
|
||||
// Put confirmations on top of the tx's block
|
||||
@ -85,14 +85,14 @@ class UTXOLifeCycleTest extends BitcoinSWalletTest {
|
||||
Some(SatoshisPerByte.one))
|
||||
|
||||
coins <- wallet.spendingInfoDAO.findOutputsBeingSpent(tx)
|
||||
_ = assert(coins.forall(_.state == PendingConfirmationsSpent))
|
||||
_ = assert(coins.forall(_.state == BroadcastSpent))
|
||||
_ = assert(coins.forall(_.spendingTxIdOpt.contains(tx.txIdBE)))
|
||||
|
||||
rbf <- wallet.bumpFeeRBF(tx.txIdBE, SatoshisPerByte.fromLong(3))
|
||||
_ <- wallet.processTransaction(rbf, None)
|
||||
rbfCoins <- wallet.spendingInfoDAO.findOutputsBeingSpent(rbf)
|
||||
} yield {
|
||||
assert(rbfCoins.forall(_.state == PendingConfirmationsSpent))
|
||||
assert(rbfCoins.forall(_.state == BroadcastSpent))
|
||||
assert(rbfCoins.forall(_.spendingTxIdOpt.contains(rbf.txIdBE)))
|
||||
}
|
||||
}
|
||||
@ -130,7 +130,7 @@ class UTXOLifeCycleTest extends BitcoinSWalletTest {
|
||||
|
||||
updatedCoins <- wallet.spendingInfoDAO.findOutputsBeingSpent(tx)
|
||||
newTransactions <- wallet.listTransactions()
|
||||
_ = assert(updatedCoins.forall(_.state == PendingConfirmationsSpent))
|
||||
_ = assert(updatedCoins.forall(_.state == BroadcastSpent))
|
||||
_ = assert(!oldTransactions.map(_.transaction).contains(tx))
|
||||
_ = assert(newTransactions.map(_.transaction).contains(tx))
|
||||
|
||||
@ -138,8 +138,8 @@ class UTXOLifeCycleTest extends BitcoinSWalletTest {
|
||||
hash <- bitcoind.getBestBlockHash
|
||||
_ <- wallet.processTransaction(tx, Some(hash))
|
||||
|
||||
pendingCoins <- wallet.spendingInfoDAO.findOutputsBeingSpent(tx)
|
||||
_ <- wallet.updateUtxoPendingStates()
|
||||
pendingCoins <- wallet.spendingInfoDAO.findOutputsBeingSpent(tx)
|
||||
_ = assert(pendingCoins.forall(_.state == PendingConfirmationsSpent))
|
||||
|
||||
// Put confirmations on top of the tx's block
|
||||
@ -191,7 +191,7 @@ class UTXOLifeCycleTest extends BitcoinSWalletTest {
|
||||
} yield res
|
||||
}
|
||||
|
||||
it should "track a utxo state change to pending received" in { param =>
|
||||
it should "track a utxo state change to broadcast received" in { param =>
|
||||
val WalletWithBitcoindRpc(wallet, bitcoind) = param
|
||||
|
||||
for {
|
||||
@ -210,9 +210,44 @@ class UTXOLifeCycleTest extends BitcoinSWalletTest {
|
||||
updatedCoin <-
|
||||
wallet.spendingInfoDAO.findByScriptPubKey(addr.scriptPubKey)
|
||||
newTransactions <- wallet.listTransactions()
|
||||
} yield {
|
||||
assert(updatedCoin.forall(_.state == TxoState.BroadcastReceived))
|
||||
assert(!oldTransactions.map(_.transaction).contains(tx))
|
||||
assert(newTransactions.map(_.transaction).contains(tx))
|
||||
}
|
||||
}
|
||||
|
||||
it should "track a utxo state change to pending received" in { param =>
|
||||
val WalletWithBitcoindRpc(wallet, bitcoind) = param
|
||||
|
||||
for {
|
||||
oldTransactions <- wallet.listTransactions()
|
||||
addr <- wallet.getNewAddress()
|
||||
|
||||
txId <- bitcoind.sendToAddress(addr, Satoshis(3000))
|
||||
tx <- bitcoind.getRawTransactionRaw(txId)
|
||||
_ <- wallet.processOurTransaction(transaction = tx,
|
||||
feeRate = SatoshisPerByte(Satoshis(3)),
|
||||
inputAmount = Satoshis(4000),
|
||||
sentAmount = Satoshis(3000),
|
||||
blockHashOpt = None,
|
||||
newTags = Vector.empty)
|
||||
|
||||
updatedCoin <-
|
||||
wallet.spendingInfoDAO.findByScriptPubKey(addr.scriptPubKey)
|
||||
newTransactions <- wallet.listTransactions()
|
||||
_ = assert(updatedCoin.forall(_.state == TxoState.BroadcastReceived))
|
||||
|
||||
hash <- bitcoind.getNewAddress
|
||||
.flatMap(bitcoind.generateToAddress(1, _))
|
||||
.map(_.head)
|
||||
_ <- wallet.processTransaction(tx, Some(hash))
|
||||
|
||||
pendingCoins <-
|
||||
wallet.spendingInfoDAO.findByScriptPubKey(addr.scriptPubKey)
|
||||
} yield {
|
||||
assert(
|
||||
updatedCoin.forall(_.state == TxoState.PendingConfirmationsReceived))
|
||||
pendingCoins.forall(_.state == TxoState.PendingConfirmationsReceived))
|
||||
assert(!oldTransactions.map(_.transaction).contains(tx))
|
||||
assert(newTransactions.map(_.transaction).contains(tx))
|
||||
}
|
||||
@ -376,5 +411,4 @@ class UTXOLifeCycleTest extends BitcoinSWalletTest {
|
||||
assert(newTransactions.map(_.transaction).contains(tx))
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -333,7 +333,7 @@ class WalletIntegrationTest extends BitcoinSWalletTest {
|
||||
_ <- wallet.processTransaction(signedTx, None)
|
||||
newCoinbaseUtxos <- wallet.listUtxos(TxoState.ImmatureCoinbase)
|
||||
_ = assert(newCoinbaseUtxos.isEmpty)
|
||||
spentUtxos <- wallet.listUtxos(TxoState.PendingConfirmationsSpent)
|
||||
spentUtxos <- wallet.listUtxos(TxoState.BroadcastSpent)
|
||||
_ = assert(spentUtxos.size == 1)
|
||||
|
||||
// Assert spending tx valid to bitcoind
|
||||
|
@ -31,10 +31,7 @@ import org.bitcoins.core.wallet.keymanagement.{
|
||||
KeyManagerParams,
|
||||
KeyManagerUnlockError
|
||||
}
|
||||
import org.bitcoins.core.wallet.utxo.TxoState.{
|
||||
ConfirmedReceived,
|
||||
PendingConfirmationsReceived
|
||||
}
|
||||
import org.bitcoins.core.wallet.utxo.TxoState._
|
||||
import org.bitcoins.core.wallet.utxo._
|
||||
import org.bitcoins.crypto._
|
||||
import org.bitcoins.keymanager.bip39.{BIP39KeyManager, BIP39LockedKeyManager}
|
||||
@ -267,11 +264,11 @@ abstract class Wallet
|
||||
.map { txo =>
|
||||
txo.state match {
|
||||
case TxoState.PendingConfirmationsReceived |
|
||||
TxoState.ConfirmedReceived =>
|
||||
TxoState.ConfirmedReceived | TxoState.BroadcastReceived =>
|
||||
txo.output.value
|
||||
case TxoState.Reserved | TxoState.PendingConfirmationsSpent |
|
||||
TxoState.ConfirmedSpent | TxoState.DoesNotExist |
|
||||
TxoState.ImmatureCoinbase =>
|
||||
TxoState.ConfirmedSpent | TxoState.BroadcastSpent |
|
||||
TxoState.DoesNotExist | TxoState.ImmatureCoinbase =>
|
||||
CurrencyUnits.zero
|
||||
}
|
||||
}
|
||||
@ -307,10 +304,12 @@ abstract class Wallet
|
||||
}
|
||||
|
||||
override def getUnconfirmedBalance(): Future[CurrencyUnit] = {
|
||||
filterThenSum(_.state == PendingConfirmationsReceived).map { balance =>
|
||||
logger.trace(s"Unconfirmed balance=${balance.satoshis}")
|
||||
balance
|
||||
}
|
||||
filterThenSum(utxo =>
|
||||
utxo.state == PendingConfirmationsReceived || utxo.state == BroadcastReceived)
|
||||
.map { balance =>
|
||||
logger.trace(s"Unconfirmed balance=${balance.satoshis}")
|
||||
balance
|
||||
}
|
||||
}
|
||||
|
||||
override def getUnconfirmedBalance(
|
||||
@ -320,7 +319,7 @@ abstract class Wallet
|
||||
} yield {
|
||||
val confirmedUtxos = allUnspent.filter { utxo =>
|
||||
HDAccount.isSameAccount(utxo.privKeyPath.path, account) &&
|
||||
utxo.state == PendingConfirmationsReceived
|
||||
utxo.state == PendingConfirmationsReceived || utxo.state == BroadcastReceived
|
||||
}
|
||||
confirmedUtxos.foldLeft(CurrencyUnits.zero)(_ + _.output.value)
|
||||
}
|
||||
@ -328,7 +327,8 @@ abstract class Wallet
|
||||
|
||||
override def getUnconfirmedBalance(tag: AddressTag): Future[CurrencyUnit] = {
|
||||
spendingInfoDAO.findAllUnspentForTag(tag).map { allUnspent =>
|
||||
val confirmed = allUnspent.filter(_.state == PendingConfirmationsReceived)
|
||||
val confirmed = allUnspent.filter(utxo =>
|
||||
utxo.state == PendingConfirmationsReceived || utxo.state == BroadcastReceived)
|
||||
confirmed.foldLeft(CurrencyUnits.zero)(_ + _.output.value)
|
||||
}
|
||||
}
|
||||
|
@ -10,6 +10,7 @@ import org.bitcoins.core.protocol.blockchain.Block
|
||||
import org.bitcoins.core.protocol.transaction.{Transaction, TransactionOutput}
|
||||
import org.bitcoins.core.util.TimeUtil
|
||||
import org.bitcoins.core.wallet.fee.FeeUnit
|
||||
import org.bitcoins.core.wallet.utxo.TxoState._
|
||||
import org.bitcoins.core.wallet.utxo.{AddressTag, TxoState}
|
||||
import org.bitcoins.crypto.{DoubleSha256Digest, DoubleSha256DigestBE}
|
||||
import org.bitcoins.wallet._
|
||||
@ -267,10 +268,11 @@ private[wallet] trait TransactionProcessing extends WalletLogger {
|
||||
out: SpendingInfoDb,
|
||||
spendingTxId: DoubleSha256DigestBE): Future[Option[SpendingInfoDb]] = {
|
||||
out.state match {
|
||||
case TxoState.ConfirmedReceived | TxoState.PendingConfirmationsReceived =>
|
||||
case ConfirmedReceived | PendingConfirmationsReceived |
|
||||
BroadcastReceived =>
|
||||
val updated =
|
||||
out
|
||||
.copyWithState(state = TxoState.PendingConfirmationsSpent)
|
||||
.copyWithState(state = BroadcastSpent)
|
||||
.copyWithSpendingTxId(spendingTxId)
|
||||
val updatedF =
|
||||
spendingInfoDAO.update(updated)
|
||||
@ -278,7 +280,8 @@ private[wallet] trait TransactionProcessing extends WalletLogger {
|
||||
logger.debug(
|
||||
s"Marked utxo=${updated.toHumanReadableString} as state=${updated.state}"))
|
||||
updatedF.map(Some(_))
|
||||
case TxoState.Reserved | TxoState.PendingConfirmationsSpent =>
|
||||
case TxoState.Reserved | TxoState.PendingConfirmationsSpent |
|
||||
BroadcastSpent =>
|
||||
val updated =
|
||||
out.copyWithSpendingTxId(spendingTxId)
|
||||
val updatedF =
|
||||
@ -345,7 +348,8 @@ private[wallet] trait TransactionProcessing extends WalletLogger {
|
||||
case TxoState.PendingConfirmationsReceived |
|
||||
TxoState.ConfirmedReceived |
|
||||
TxoState.PendingConfirmationsSpent | TxoState.ConfirmedSpent |
|
||||
TxoState.DoesNotExist | TxoState.ImmatureCoinbase =>
|
||||
TxoState.DoesNotExist | TxoState.ImmatureCoinbase |
|
||||
BroadcastReceived | BroadcastSpent =>
|
||||
foundTxo
|
||||
}
|
||||
|
||||
@ -377,7 +381,7 @@ private[wallet] trait TransactionProcessing extends WalletLogger {
|
||||
Seq[SpendingInfoDb]] = {
|
||||
val stateF: Future[TxoState] = blockHashOpt match {
|
||||
case None =>
|
||||
Future.successful(TxoState.PendingConfirmationsReceived)
|
||||
Future.successful(TxoState.BroadcastReceived)
|
||||
case Some(blockHash) =>
|
||||
chainQueryApi.getNumberOfConfirmations(blockHash).map {
|
||||
case None =>
|
||||
|
@ -116,9 +116,10 @@ private[wallet] trait UtxoHandling extends WalletLogger {
|
||||
val blockHashMap = txDbs.map(db => db.txIdBE -> db.blockHashOpt).toMap
|
||||
val blockHashAndDb = spendingInfoDbs.map { txo =>
|
||||
val txToUse = txo.state match {
|
||||
case _: ReceivedState | DoesNotExist | ImmatureCoinbase | Reserved =>
|
||||
case _: ReceivedState | DoesNotExist | ImmatureCoinbase |
|
||||
Reserved | BroadcastReceived =>
|
||||
txo.txid
|
||||
case PendingConfirmationsSpent | ConfirmedSpent =>
|
||||
case PendingConfirmationsSpent | ConfirmedSpent | BroadcastSpent =>
|
||||
txo.spendingTxIdOpt.get
|
||||
}
|
||||
(blockHashMap(txToUse), txo)
|
||||
@ -143,14 +144,14 @@ private[wallet] trait UtxoHandling extends WalletLogger {
|
||||
else
|
||||
txo.copyWithState(TxoState.PendingConfirmationsReceived)
|
||||
} else txo
|
||||
case TxoState.PendingConfirmationsReceived =>
|
||||
case TxoState.PendingConfirmationsReceived | BroadcastReceived =>
|
||||
if (confs >= walletConfig.requiredConfirmations)
|
||||
txo.copyWithState(TxoState.ConfirmedReceived)
|
||||
else txo
|
||||
case TxoState.PendingConfirmationsSpent =>
|
||||
else txo.copyWithState(PendingConfirmationsReceived)
|
||||
case TxoState.PendingConfirmationsSpent | BroadcastSpent =>
|
||||
if (confs >= walletConfig.requiredConfirmations)
|
||||
txo.copyWithState(TxoState.ConfirmedSpent)
|
||||
else txo
|
||||
else txo.copyWithState(PendingConfirmationsSpent)
|
||||
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
|
||||
|
Loading…
Reference in New Issue
Block a user