mirror of
https://github.com/bitcoin-s/bitcoin-s.git
synced 2025-03-26 21:42:48 +01:00
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:
parent
eb5924ba94
commit
ddc672cc46
2 changed files with 176 additions and 11 deletions
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Add table
Reference in a new issue