@@ -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
RefreshWalletTxTXOs (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
}
@@ -3369,6 +3406,10 @@ bool CWallet::AttachChain(const std::shared_ptr<CWallet>& walletInstance, interf
3369
3406
}
3370
3407
walletInstance->m_attaching_chain = false ;
3371
3408
3409
+ // Remove TXOs that have already been spent
3410
+ // We do this here as we need to have an attached chain to figure out what has actually been spent.
3411
+ walletInstance->PruneSpentTXOs ();
3412
+
3372
3413
return true ;
3373
3414
}
3374
3415
@@ -4152,9 +4193,9 @@ bool CWallet::ApplyMigrationData(MigrationData& data, bilingual_str& error)
4152
4193
return false ;
4153
4194
}
4154
4195
4155
- // Update m_txos to match the descriptors remaining in this wallet
4196
+ // Clear m_txos and m_unusable_txos. These will be updated next to match the descriptors remaining in this wallet
4156
4197
m_txos.clear ();
4157
- RefreshAllTXOs ();
4198
+ m_unusable_txos. clear ();
4158
4199
4159
4200
// Check if the transactions in the wallet are still ours. Either they belong here, or they belong in the watchonly wallet.
4160
4201
// We need to go through these in the tx insertion order so that lookups to spends works.
@@ -4180,6 +4221,9 @@ bool CWallet::ApplyMigrationData(MigrationData& data, bilingual_str& error)
4180
4221
}
4181
4222
}
4182
4223
for (const auto & [_pos, wtx] : wtxOrdered) {
4224
+ // First update the UTXOs
4225
+ wtx->m_txos .clear ();
4226
+ RefreshWalletTxTXOs (*wtx);
4183
4227
// Check it is the watchonly wallet's
4184
4228
// solvable_wallet doesn't need to be checked because transactions for those scripts weren't being watched for
4185
4229
bool is_mine = IsMine (*wtx->tx ) || IsFromMe (*wtx->tx );
@@ -4193,6 +4237,7 @@ bool CWallet::ApplyMigrationData(MigrationData& data, bilingual_str& error)
4193
4237
if (!new_tx) return false ;
4194
4238
ins_wtx.SetTx (to_copy_wtx.tx );
4195
4239
ins_wtx.CopyFrom (to_copy_wtx);
4240
+ data.watchonly_wallet ->RefreshWalletTxTXOs (ins_wtx);
4196
4241
return true ;
4197
4242
})) {
4198
4243
error = strprintf (_ (" Error: Could not add watchonly tx %s to watchonly wallet" ), wtx->GetHash ().GetHex ());
@@ -4667,26 +4712,34 @@ std::optional<CKey> CWallet::GetKey(const CKeyID& keyid) const
4667
4712
return std::nullopt;
4668
4713
}
4669
4714
4715
+ using TXOMap = std::unordered_map<COutPoint, WalletTXO, SaltedOutpointHasher>;
4670
4716
void CWallet::RefreshWalletTxTXOs (const CWalletTx& wtx)
4671
4717
{
4672
4718
AssertLockHeld (cs_wallet);
4673
4719
for (uint32_t i = 0 ; i < wtx.tx ->vout .size (); ++i) {
4674
4720
const CTxOut& txout = wtx.tx ->vout .at (i);
4675
4721
COutPoint outpoint (wtx.GetHash (), i);
4676
4722
4677
- auto it = m_txos.find (outpoint);
4678
-
4679
4723
isminetype ismine = IsMine (txout);
4680
4724
if (ismine == ISMINE_NO) {
4681
4725
continue ;
4682
4726
}
4683
4727
4684
- if (it != m_txos.end ()) {
4728
+ auto it = wtx.m_txos .find (i);
4729
+ if (it != wtx.m_txos .end ()) {
4685
4730
it->second .SetIsMine (ismine);
4686
4731
it->second .SetState (wtx.GetState ());
4687
4732
} else {
4688
- auto [txo_it, _] = m_txos.emplace (outpoint, WalletTXO{txout, ismine, wtx.GetState (), wtx.IsCoinBase (), wtx.m_from_me , wtx.GetTxTime ()});
4689
- wtx.m_txos .emplace (i, txo_it->second );
4733
+ TXOMap::iterator txo_it;
4734
+ bool txos_inserted = false ;
4735
+ if (m_last_block_processed_height >= 0 && IsSpent (outpoint, /* min_depth=*/ 1 )) {
4736
+ std::tie (txo_it, txos_inserted) = m_unusable_txos.emplace (outpoint, WalletTXO{txout, ismine, wtx.GetState (), wtx.IsCoinBase (), wtx.m_from_me , wtx.GetTxTime ()});
4737
+ assert (txos_inserted);
4738
+ } else {
4739
+ std::tie (txo_it, txos_inserted) = m_txos.emplace (outpoint, WalletTXO{txout, ismine, wtx.GetState (), wtx.IsCoinBase (), wtx.m_from_me , wtx.GetTxTime ()});
4740
+ }
4741
+ auto [_, wtx_inserted] = wtx.m_txos .emplace (i, txo_it->second );
4742
+ assert (wtx_inserted);
4690
4743
}
4691
4744
}
4692
4745
}
@@ -4703,9 +4756,58 @@ std::optional<WalletTXO> CWallet::GetTXO(const COutPoint& outpoint) const
4703
4756
{
4704
4757
AssertLockHeld (cs_wallet);
4705
4758
const auto & it = m_txos.find (outpoint);
4706
- if (it = = m_txos.end ()) {
4707
- return std::nullopt ;
4759
+ if (it ! = m_txos.end ()) {
4760
+ return it-> second ;
4708
4761
}
4709
- return it->second ;
4762
+ const auto & u_it = m_unusable_txos.find (outpoint);
4763
+ if (u_it != m_unusable_txos.end ()) {
4764
+ return u_it->second ;
4765
+ }
4766
+ return std::nullopt;
4767
+ }
4768
+
4769
+ void CWallet::PruneSpentTXOs ()
4770
+ {
4771
+ AssertLockHeld (cs_wallet);
4772
+ auto it = m_txos.begin ();
4773
+ while (it != m_txos.end ()) {
4774
+ if (std::get_if<TxStateBlockConflicted>(&it->second .GetState ()) || IsSpent (it->first , /* min_depth=*/ 1 )) {
4775
+ it = MarkTXOUnusable (it->first ).first ;
4776
+ } else {
4777
+ it++;
4778
+ }
4779
+ }
4780
+ }
4781
+
4782
+ std::pair<TXOMap::iterator, TXOMap::iterator> CWallet::MarkTXOUnusable (const COutPoint& outpoint)
4783
+ {
4784
+ AssertLockHeld (cs_wallet);
4785
+ auto txos_it = m_txos.find (outpoint);
4786
+ auto unusable_txos_it = m_unusable_txos.end ();
4787
+ if (txos_it != m_txos.end ()) {
4788
+ auto next_txo_it = std::next (txos_it);
4789
+ auto nh = m_txos.extract (txos_it);
4790
+ txos_it = next_txo_it;
4791
+ auto [position, inserted, _] = m_unusable_txos.insert (std::move (nh));
4792
+ unusable_txos_it = position;
4793
+ assert (inserted);
4794
+ }
4795
+ return {txos_it, unusable_txos_it};
4796
+ }
4797
+
4798
+ std::pair<TXOMap::iterator, TXOMap::iterator> CWallet::MarkTXOUsable (const COutPoint& outpoint)
4799
+ {
4800
+ AssertLockHeld (cs_wallet);
4801
+ auto txos_it = m_txos.end ();
4802
+ auto unusable_txos_it = m_unusable_txos.find (outpoint);
4803
+ if (unusable_txos_it != m_unusable_txos.end ()) {
4804
+ auto next_unusable_txo_it = std::next (unusable_txos_it);
4805
+ auto nh = m_unusable_txos.extract (unusable_txos_it);
4806
+ unusable_txos_it = next_unusable_txo_it;
4807
+ auto [position, inserted, _] = m_txos.insert (std::move (nh));
4808
+ assert (inserted);
4809
+ txos_it = position;
4810
+ }
4811
+ return {unusable_txos_it, txos_it};
4710
4812
}
4711
4813
} // namespace wallet
0 commit comments