Fix unconfirmed -> confirmed state change (#4816)

* Fix unconfirmed -> confirmed state change

* refactor

Co-authored-by: Chris Stewart <stewart.chris1234@gmail.com>
This commit is contained in:
rorp 2022-10-06 05:06:18 -07:00 committed by GitHub
parent eb5924ba94
commit ddc672cc46
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 176 additions and 11 deletions

View file

@ -13,13 +13,13 @@ import org.bitcoins.core.wallet.builder.RawTxSigner
import org.bitcoins.core.wallet.fee.{SatoshisPerByte, SatoshisPerVirtualByte}
import org.bitcoins.core.wallet.utxo.TxoState
import org.bitcoins.core.wallet.utxo.TxoState._
import org.bitcoins.crypto.ECPublicKey
import org.bitcoins.crypto.{DoubleSha256DigestBE, ECPublicKey}
import org.bitcoins.testkit.wallet.{
BitcoinSWalletTestCachedBitcoindNewest,
WalletWithBitcoindRpc
}
import org.bitcoins.wallet.models.SpendingInfoDAO
import org.scalatest.{FutureOutcome, Outcome}
import org.scalatest.{Assertion, FutureOutcome, Outcome}
import scala.concurrent.Future
@ -96,6 +96,171 @@ class UTXOLifeCycleTest
}
}
it should "track multiple utxos state change to confirmed spent" in { param =>
val wallet = param.wallet
val bitcoind = param.bitcoind
def checkState(
utxos: Vector[SpendingInfoDb],
txid1: DoubleSha256DigestBE,
txid2: DoubleSha256DigestBE,
txid3: DoubleSha256DigestBE,
state: TxoState): Assertion = {
val utxo1 = utxos.find(_.txid == txid1).get
assert(utxo1.state == state)
val utxo2 = utxos.find(_.txid == txid2).get
assert(utxo2.state == state)
val utxo3 = utxos.find(_.txid == txid3).get
assert(utxo3.state == state)
}
for {
addr1 <- wallet.getNewAddress()
addr2 <- wallet.getNewAddress()
addr3 <- wallet.getNewAddress()
oldUtxos <- wallet.listUtxos()
txid1 <- bitcoind.sendToAddress(addr1, Satoshis(1000))
txid2 <- bitcoind.sendToAddress(addr2, Satoshis(2000))
txid3 <- bitcoind.sendToAddress(addr3, Satoshis(3000))
tx1 <- wallet.findByTxId(txid1)
_ = assert(tx1.isEmpty)
tx2 <- wallet.findByTxId(txid2)
_ = assert(tx2.isEmpty)
tx3 <- wallet.findByTxId(txid3)
_ = assert(tx3.isEmpty)
tx1 <- bitcoind.getRawTransactionRaw(txid1).map(Option.apply).recover {
case _: Throwable => None
}
_ = assert(tx1.nonEmpty)
tx2 <- bitcoind.getRawTransactionRaw(txid2).map(Option.apply).recover {
case _: Throwable => None
}
_ = assert(tx2.nonEmpty)
tx3 <- bitcoind.getRawTransactionRaw(txid3).map(Option.apply).recover {
case _: Throwable => None
}
_ = assert(tx3.nonEmpty)
utxos <- wallet.listUtxos()
_ = assert(oldUtxos == utxos)
// process the transactions from mempool
_ <- wallet.processTransaction(tx1.get, None)
_ <- wallet.processTransaction(tx2.get, None)
_ <- wallet.processTransaction(tx3.get, None)
utxos <- wallet.listUtxos()
_ = assert(oldUtxos.size + 3 == utxos.size)
_ = checkState(utxos, txid1, txid2, txid3, TxoState.BroadcastReceived)
minerAddr <- bitcoind.getNewAddress
// confirm the transactions
blockHashes <- bitcoind.generateToAddress(1, minerAddr)
_ = assert(blockHashes.size == 1)
blockHash = blockHashes.head
block <- bitcoind.getBlockRaw(blockHash)
_ <- wallet.processBlock(block)
_ <- wallet.updateUtxoPendingStates()
utxos <- wallet.listUtxos()
_ = assert(oldUtxos.size + 3 == utxos.size)
// mine the second block
blockHashes <- bitcoind.generateToAddress(1, minerAddr)
_ = assert(blockHashes.size == 1)
blockHash = blockHashes.head
block <- bitcoind.getBlockRaw(blockHash)
_ <- wallet.processBlock(block)
_ <- wallet.updateUtxoPendingStates()
utxos <- wallet.listUtxos()
_ = assert(oldUtxos.size + 3 == utxos.size)
_ = checkState(utxos,
txid1,
txid2,
txid3,
TxoState.PendingConfirmationsReceived)
// mine the third block
blockHashes <- bitcoind.generateToAddress(1, minerAddr)
_ = assert(blockHashes.size == 1)
blockHash = blockHashes.head
block <- bitcoind.getBlockRaw(blockHash)
_ <- wallet.processBlock(block)
_ <- wallet.updateUtxoPendingStates()
utxos <- wallet.listUtxos()
_ = assert(oldUtxos.size + 3 == utxos.size)
_ = checkState(utxos,
txid1,
txid2,
txid3,
TxoState.PendingConfirmationsReceived)
// mine the fourth block
blockHashes <- bitcoind.generateToAddress(1, minerAddr)
_ = assert(blockHashes.size == 1)
blockHash = blockHashes.head
block <- bitcoind.getBlockRaw(blockHash)
_ <- wallet.processBlock(block)
_ <- wallet.updateUtxoPendingStates()
utxos <- wallet.listUtxos()
_ = assert(oldUtxos.size + 3 == utxos.size)
_ = checkState(utxos,
txid1,
txid2,
txid3,
TxoState.PendingConfirmationsReceived)
// mine the fifth block
blockHashes <- bitcoind.generateToAddress(1, minerAddr)
_ = assert(blockHashes.size == 1)
blockHash = blockHashes.head
block <- bitcoind.getBlockRaw(blockHash)
_ <- wallet.processBlock(block)
_ <- wallet.updateUtxoPendingStates()
utxos <- wallet.listUtxos()
_ = assert(oldUtxos.size + 3 == utxos.size)
_ = checkState(utxos,
txid1,
txid2,
txid3,
TxoState.PendingConfirmationsReceived)
// mine the sixth block
blockHashes <- bitcoind.generateToAddress(1, minerAddr)
_ = assert(blockHashes.size == 1)
blockHash = blockHashes.head
block <- bitcoind.getBlockRaw(blockHash)
_ <- wallet.processBlock(block)
_ <- wallet.updateUtxoPendingStates()
utxos <- wallet.listUtxos()
_ = assert(oldUtxos.size + 3 == utxos.size)
utxo1 = utxos.find(_.txid == txid1).get
utxo2 = utxos.find(_.txid == txid2).get
utxo3 = utxos.find(_.txid == txid3).get
} yield {
assert(utxo1.state == ConfirmedReceived)
assert(utxo2.state == ConfirmedReceived)
assert(utxo3.state == ConfirmedReceived)
}
}
it should "handle an RBF transaction on unconfirmed coins" in { param =>
val wallet = param.wallet

View file

@ -159,6 +159,8 @@ private[bitcoins] trait TransactionProcessing extends WalletLogger {
(walletF, transaction) =>
for {
wallet <- walletF
relevantReceivedOutputsForTx = relevantReceivedOutputsForBlock
.getOrElse(transaction.txIdBE, Vector.empty)
processTxResult <- {
wallet.processTransactionImpl(
transaction = transaction,
@ -166,7 +168,7 @@ private[bitcoins] trait TransactionProcessing extends WalletLogger {
newTags = Vector.empty,
receivedSpendingInfoDbsOpt = receivedSpendingInfoDbsOpt,
spentSpendingInfoDbsOpt = cachedSpentOpt,
relevantReceivedOutputs = relevantReceivedOutputsForBlock
relevantReceivedOutputs = relevantReceivedOutputsForTx
)
}
_ = {
@ -673,21 +675,19 @@ private[bitcoins] trait TransactionProcessing extends WalletLogger {
}.toVector
}
private def getRelevantOutputsForBlock(
block: Block): Future[Vector[OutputWithIndex]] = {
private def getRelevantOutputsForBlock(block: Block): Future[
Map[DoubleSha256DigestBE, Vector[OutputWithIndex]]] = {
val spksInBlock: Vector[ScriptPubKey] = block.transactions
.flatMap(tx => tx.outputs.map(o => o.scriptPubKey))
.toVector
val spksInDbF = scriptPubKeyDAO.findScriptPubKeys(spksInBlock)
val result = spksInDbF.map { addrs =>
block.transactions.flatMap { tx =>
val m = matchReceivedTx(addrs, tx)
m
spksInDbF.map { addrs =>
block.transactions.foldLeft(
Map.empty[DoubleSha256DigestBE, Vector[OutputWithIndex]]) { (acc, tx) =>
acc.updated(tx.txIdBE, matchReceivedTx(addrs, tx))
}
}
result.map(_.toVector)
}
/** Processes an incoming transaction that's new to us