@@ -1171,6 +1171,20 @@ CWalletTx* CWallet::AddToWallet(CTransactionRef tx, const TxState& state, const
1171
1171
// Break debit/credit balance caches:
1172
1172
wtx.MarkDirty ();
1173
1173
1174
+ // Remove or add back the inputs from m_txos to match the state of this tx.
1175
+ if (wtx.isConfirmed ())
1176
+ {
1177
+ // When a transaction becomes confirmed, we can remove all of the txos that were spent
1178
+ // in its inputs as they are no longer relevant.
1179
+ for (const CTxIn& txin : wtx.tx ->vin ) {
1180
+ MarkTXOUnusable (txin.prevout );
1181
+ }
1182
+ } else if (wtx.isInactive ()) {
1183
+ // When a transaction becomes inactive, we need to mark its inputs as usable again
1184
+ for (const CTxIn& txin : wtx.tx ->vin ) {
1185
+ MarkTXOUsable (txin.prevout );
1186
+ }
1187
+ }
1174
1188
// Cache the outputs that belong to the wallet
1175
1189
RefreshSingleTxTXOs (wtx);
1176
1190
@@ -1416,12 +1430,20 @@ void CWallet::RecursiveUpdateTxState(WalletBatch* batch, const uint256& tx_hash,
1416
1430
if (batch) batch->WriteTx (wtx);
1417
1431
// Iterate over all its outputs, and update those tx states as well (if applicable)
1418
1432
for (unsigned int i = 0 ; i < wtx.tx ->vout .size (); ++i) {
1419
- std::pair<TxSpends::const_iterator, TxSpends::const_iterator> range = mapTxSpends.equal_range (COutPoint (Txid::FromUint256 (now), i));
1433
+ COutPoint outpoint{Txid::FromUint256 (now), i};
1434
+ std::pair<TxSpends::const_iterator, TxSpends::const_iterator> range = mapTxSpends.equal_range (outpoint);
1420
1435
for (TxSpends::const_iterator iter = range.first ; iter != range.second ; ++iter) {
1421
1436
if (!done.count (iter->second )) {
1422
1437
todo.insert (iter->second );
1423
1438
}
1424
1439
}
1440
+ if (wtx.state <TxStateBlockConflicted>() || wtx.state <TxStateConfirmed>()) {
1441
+ // If the state applied is conflicted or confirmed, the outputs are unusable
1442
+ MarkTXOUnusable (outpoint);
1443
+ } else {
1444
+ // Otherwise make the outputs usable
1445
+ MarkTXOUsable (outpoint);
1446
+ }
1425
1447
}
1426
1448
1427
1449
if (update_state == TxUpdate::NOTIFY_CHANGED) {
@@ -1431,6 +1453,21 @@ void CWallet::RecursiveUpdateTxState(WalletBatch* batch, const uint256& tx_hash,
1431
1453
// If a transaction changes its tx state, that usually changes the balance
1432
1454
// available of the outputs it spends. So force those to be recomputed
1433
1455
MarkInputsDirty (wtx.tx );
1456
+ // Make the non-conflicted inputs usable again
1457
+ for (unsigned int i = 0 ; i < wtx.tx ->vin .size (); ++i) {
1458
+ const CTxIn& txin = wtx.tx ->vin .at (i);
1459
+ auto unusable_txo_it = m_unusable_txos.find (txin.prevout );
1460
+ if (unusable_txo_it == m_unusable_txos.end ()) {
1461
+ continue ;
1462
+ }
1463
+
1464
+ if (std::get_if<TxStateBlockConflicted>(&unusable_txo_it->second .GetState ()) ||
1465
+ std::get_if<TxStateConfirmed>(&unusable_txo_it->second .GetState ())) {
1466
+ continue ;
1467
+ }
1468
+
1469
+ MarkTXOUsable (txin.prevout );
1470
+ }
1434
1471
}
1435
1472
}
1436
1473
}
@@ -3383,6 +3420,10 @@ bool CWallet::AttachChain(const std::shared_ptr<CWallet>& walletInstance, interf
3383
3420
}
3384
3421
walletInstance->m_attaching_chain = false ;
3385
3422
3423
+ // Remove TXOs that have already been spent
3424
+ // We do this here as we need to have an attached chain to figure out what has actually been spent.
3425
+ walletInstance->PruneSpentTXOs ();
3426
+
3386
3427
return true ;
3387
3428
}
3388
3429
@@ -4173,9 +4214,9 @@ util::Result<void> CWallet::ApplyMigrationData(WalletBatch& local_wallet_batch,
4173
4214
return util::Error{_ (" Error: Unable to read wallet's best block locator record" )};
4174
4215
}
4175
4216
4176
- // Update m_txos to match the descriptors remaining in this wallet
4217
+ // Clear m_txos and m_unusable_txos. These will be updated next to match the descriptors remaining in this wallet
4177
4218
m_txos.clear ();
4178
- RefreshAllTXOs ();
4219
+ m_unusable_txos. clear ();
4179
4220
4180
4221
// Check if the transactions in the wallet are still ours. Either they belong here, or they belong in the watchonly wallet.
4181
4222
// We need to go through these in the tx insertion order so that lookups to spends works.
@@ -4203,6 +4244,9 @@ util::Result<void> CWallet::ApplyMigrationData(WalletBatch& local_wallet_batch,
4203
4244
}
4204
4245
}
4205
4246
for (const auto & [_pos, wtx] : wtxOrdered) {
4247
+ // First update the UTXOs
4248
+ wtx->m_txos .clear ();
4249
+ RefreshSingleTxTXOs (*wtx);
4206
4250
// Check it is the watchonly wallet's
4207
4251
// solvable_wallet doesn't need to be checked because transactions for those scripts weren't being watched for
4208
4252
bool is_mine = IsMine (*wtx->tx ) || IsFromMe (*wtx->tx );
@@ -4216,6 +4260,7 @@ util::Result<void> CWallet::ApplyMigrationData(WalletBatch& local_wallet_batch,
4216
4260
if (!new_tx) return false ;
4217
4261
ins_wtx.SetTx (to_copy_wtx.tx );
4218
4262
ins_wtx.CopyFrom (to_copy_wtx);
4263
+ data.watchonly_wallet ->RefreshSingleTxTXOs (ins_wtx);
4219
4264
return true ;
4220
4265
})) {
4221
4266
return util::Error{strprintf (_ (" Error: Could not add watchonly tx %s to watchonly wallet" ), wtx->GetHash ().GetHex ())};
@@ -4676,26 +4721,34 @@ std::optional<CKey> CWallet::GetKey(const CKeyID& keyid) const
4676
4721
return std::nullopt;
4677
4722
}
4678
4723
4724
+ using TXOMap = std::unordered_map<COutPoint, WalletTXO, SaltedOutpointHasher>;
4679
4725
void CWallet::RefreshSingleTxTXOs (const CWalletTx& wtx)
4680
4726
{
4681
4727
AssertLockHeld (cs_wallet);
4682
4728
for (uint32_t i = 0 ; i < wtx.tx ->vout .size (); ++i) {
4683
4729
const CTxOut& txout = wtx.tx ->vout .at (i);
4684
4730
COutPoint outpoint (wtx.GetHash (), i);
4685
4731
4686
- auto it = m_txos.find (outpoint);
4687
-
4688
4732
isminetype ismine = IsMine (txout);
4689
4733
if (ismine == ISMINE_NO) {
4690
4734
continue ;
4691
4735
}
4692
4736
4693
- if (it != m_txos.end ()) {
4694
- it->second .SetIsMine (ismine);
4695
- it->second .SetState (wtx.GetState ());
4737
+ auto it = wtx.m_txos .find (i);
4738
+ if (it != wtx.m_txos .end ()) {
4739
+ it->second ->SetIsMine (ismine);
4740
+ it->second ->SetState (wtx.GetState ());
4696
4741
} else {
4697
- auto [txo_it, _] = m_txos.emplace (outpoint, WalletTXO{txout, ismine, wtx.GetState (), wtx.IsCoinBase (), wtx.m_from_me , wtx.GetTxTime ()});
4698
- wtx.m_txos .emplace (i, &txo_it->second );
4742
+ TXOMap::iterator txo_it;
4743
+ bool txos_inserted = false ;
4744
+ if (m_last_block_processed_height >= 0 && IsSpent (outpoint, /* min_depth=*/ 1 )) {
4745
+ std::tie (txo_it, txos_inserted) = m_unusable_txos.emplace (outpoint, WalletTXO{txout, ismine, wtx.GetState (), wtx.IsCoinBase (), wtx.m_from_me , wtx.GetTxTime ()});
4746
+ assert (txos_inserted);
4747
+ } else {
4748
+ std::tie (txo_it, txos_inserted) = m_txos.emplace (outpoint, WalletTXO{txout, ismine, wtx.GetState (), wtx.IsCoinBase (), wtx.m_from_me , wtx.GetTxTime ()});
4749
+ }
4750
+ auto [_, wtx_inserted] = wtx.m_txos .emplace (i, &txo_it->second );
4751
+ assert (wtx_inserted);
4699
4752
}
4700
4753
}
4701
4754
}
@@ -4712,9 +4765,58 @@ std::optional<WalletTXO> CWallet::GetTXO(const COutPoint& outpoint) const
4712
4765
{
4713
4766
AssertLockHeld (cs_wallet);
4714
4767
const auto & it = m_txos.find (outpoint);
4715
- if (it = = m_txos.end ()) {
4716
- return std::nullopt ;
4768
+ if (it ! = m_txos.end ()) {
4769
+ return it-> second ;
4717
4770
}
4718
- return it->second ;
4771
+ const auto & u_it = m_unusable_txos.find (outpoint);
4772
+ if (u_it != m_unusable_txos.end ()) {
4773
+ return u_it->second ;
4774
+ }
4775
+ return std::nullopt;
4776
+ }
4777
+
4778
+ void CWallet::PruneSpentTXOs ()
4779
+ {
4780
+ AssertLockHeld (cs_wallet);
4781
+ auto it = m_txos.begin ();
4782
+ while (it != m_txos.end ()) {
4783
+ if (std::get_if<TxStateBlockConflicted>(&it->second .GetState ()) || IsSpent (it->first , /* min_depth=*/ 1 )) {
4784
+ it = MarkTXOUnusable (it->first ).first ;
4785
+ } else {
4786
+ it++;
4787
+ }
4788
+ }
4789
+ }
4790
+
4791
+ std::pair<TXOMap::iterator, TXOMap::iterator> CWallet::MarkTXOUnusable (const COutPoint& outpoint)
4792
+ {
4793
+ AssertLockHeld (cs_wallet);
4794
+ auto txos_it = m_txos.find (outpoint);
4795
+ auto unusable_txos_it = m_unusable_txos.end ();
4796
+ if (txos_it != m_txos.end ()) {
4797
+ auto next_txo_it = std::next (txos_it);
4798
+ auto nh = m_txos.extract (txos_it);
4799
+ txos_it = next_txo_it;
4800
+ auto [position, inserted, _] = m_unusable_txos.insert (std::move (nh));
4801
+ unusable_txos_it = position;
4802
+ assert (inserted);
4803
+ }
4804
+ return {txos_it, unusable_txos_it};
4805
+ }
4806
+
4807
+ std::pair<TXOMap::iterator, TXOMap::iterator> CWallet::MarkTXOUsable (const COutPoint& outpoint)
4808
+ {
4809
+ AssertLockHeld (cs_wallet);
4810
+ auto txos_it = m_txos.end ();
4811
+ auto unusable_txos_it = m_unusable_txos.find (outpoint);
4812
+ if (unusable_txos_it != m_unusable_txos.end ()) {
4813
+ auto next_unusable_txo_it = std::next (unusable_txos_it);
4814
+ auto nh = m_unusable_txos.extract (unusable_txos_it);
4815
+ unusable_txos_it = next_unusable_txo_it;
4816
+ auto [position, inserted, _] = m_txos.insert (std::move (nh));
4817
+ assert (inserted);
4818
+ txos_it = position;
4819
+ }
4820
+ return {unusable_txos_it, txos_it};
4719
4821
}
4720
4822
} // namespace wallet
0 commit comments