@@ -1156,6 +1156,20 @@ CWalletTx* CWallet::AddToWallet(CTransactionRef tx, const TxState& state, const
1156
1156
// Break debit/credit balance caches:
1157
1157
wtx.MarkDirty ();
1158
1158
1159
+ // Remove or add back the inputs from m_txos to match the state of this tx.
1160
+ if (wtx.isConfirmed ())
1161
+ {
1162
+ // When a transaction becomes confirmed, we can remove all of the txos that were spent
1163
+ // in its inputs as they are no longer relevant.
1164
+ for (const CTxIn& txin : wtx.tx ->vin ) {
1165
+ MarkTXOUnusable (txin.prevout );
1166
+ }
1167
+ } else if (wtx.isInactive ()) {
1168
+ // When a transaction becomes inactive, we need to mark its inputs as usable again
1169
+ for (const CTxIn& txin : wtx.tx ->vin ) {
1170
+ MarkTXOUsable (txin.prevout );
1171
+ }
1172
+ }
1159
1173
// Get the outputs that belong to the wallet
1160
1174
RefreshWalletTxTXOs (wtx);
1161
1175
@@ -1398,12 +1412,20 @@ void CWallet::RecursiveUpdateTxState(const uint256& tx_hash, const TryUpdatingSt
1398
1412
batch.WriteTx (wtx);
1399
1413
// Iterate over all its outputs, and update those tx states as well (if applicable)
1400
1414
for (unsigned int i = 0 ; i < wtx.tx ->vout .size (); ++i) {
1401
- std::pair<TxSpends::const_iterator, TxSpends::const_iterator> range = mapTxSpends.equal_range (COutPoint (Txid::FromUint256 (now), i));
1415
+ COutPoint outpoint{Txid::FromUint256 (now), i};
1416
+ std::pair<TxSpends::const_iterator, TxSpends::const_iterator> range = mapTxSpends.equal_range (outpoint);
1402
1417
for (TxSpends::const_iterator iter = range.first ; iter != range.second ; ++iter) {
1403
1418
if (!done.count (iter->second )) {
1404
1419
todo.insert (iter->second );
1405
1420
}
1406
1421
}
1422
+ if (wtx.state <TxStateConflicted>() || wtx.state <TxStateConfirmed>()) {
1423
+ // If the state applied is conflicted or confirmed, the outputs are unusable
1424
+ MarkTXOUnusable (outpoint);
1425
+ } else {
1426
+ // Otherwise make the outputs usable
1427
+ MarkTXOUsable (outpoint);
1428
+ }
1407
1429
}
1408
1430
1409
1431
if (update_state == TxUpdate::NOTIFY_CHANGED) {
@@ -1413,6 +1435,21 @@ void CWallet::RecursiveUpdateTxState(const uint256& tx_hash, const TryUpdatingSt
1413
1435
// If a transaction changes its tx state, that usually changes the balance
1414
1436
// available of the outputs it spends. So force those to be recomputed
1415
1437
MarkInputsDirty (wtx.tx );
1438
+ // Make the non-conflicted inputs usable again
1439
+ for (unsigned int i = 0 ; i < wtx.tx ->vin .size (); ++i) {
1440
+ const CTxIn& txin = wtx.tx ->vin .at (i);
1441
+ auto unusable_txo_it = m_unusable_txos.find (txin.prevout );
1442
+ if (unusable_txo_it == m_unusable_txos.end ()) {
1443
+ continue ;
1444
+ }
1445
+
1446
+ if (std::get_if<TxStateConflicted>(&unusable_txo_it->second .GetState ()) ||
1447
+ std::get_if<TxStateConfirmed>(&unusable_txo_it->second .GetState ())) {
1448
+ continue ;
1449
+ }
1450
+
1451
+ MarkTXOUsable (txin.prevout );
1452
+ }
1416
1453
}
1417
1454
}
1418
1455
}
@@ -3248,6 +3285,10 @@ bool CWallet::AttachChain(const std::shared_ptr<CWallet>& walletInstance, interf
3248
3285
}
3249
3286
walletInstance->m_attaching_chain = false ;
3250
3287
3288
+ // Remove TXOs that have already been spent
3289
+ // We do this here as we need to have an attached chain to figure out what has actually been spent.
3290
+ walletInstance->PruneSpentTXOs ();
3291
+
3251
3292
return true ;
3252
3293
}
3253
3294
@@ -4398,26 +4439,34 @@ util::Result<MigrationResult> MigrateLegacyToDescriptor(const std::string& walle
4398
4439
return res;
4399
4440
}
4400
4441
4442
+ using TXOMap = std::unordered_map<COutPoint, WalletTXO, SaltedOutpointHasher>;
4401
4443
void CWallet::RefreshWalletTxTXOs (const CWalletTx& wtx)
4402
4444
{
4403
4445
AssertLockHeld (cs_wallet);
4404
4446
for (uint32_t i = 0 ; i < wtx.tx ->vout .size (); ++i) {
4405
4447
const CTxOut& txout = wtx.tx ->vout .at (i);
4406
4448
COutPoint outpoint (wtx.GetHash (), i);
4407
4449
4408
- auto it = m_txos.find (outpoint);
4409
-
4410
4450
isminetype ismine = IsMine (txout);
4411
4451
if (ismine == ISMINE_NO) {
4412
4452
continue ;
4413
4453
}
4414
4454
4415
- if (it != m_txos.end ()) {
4455
+ auto it = wtx.m_txos .find (i);
4456
+ if (it != wtx.m_txos .end ()) {
4416
4457
it->second .SetIsMine (ismine);
4417
4458
it->second .SetState (wtx.GetState ());
4418
4459
} else {
4419
- auto [txo_it, _] = m_txos.emplace (outpoint, WalletTXO{txout, ismine, wtx.GetState (), wtx.IsCoinBase (), wtx.m_from_me , wtx.GetTxTime ()});
4420
- wtx.m_txos .emplace (i, txo_it->second );
4460
+ TXOMap::iterator txo_it;
4461
+ bool txos_inserted = false ;
4462
+ if (m_last_block_processed_height >= 0 && IsSpent (outpoint, /* min_depth=*/ 1 )) {
4463
+ std::tie (txo_it, txos_inserted) = m_unusable_txos.emplace (outpoint, WalletTXO{txout, ismine, wtx.GetState (), wtx.IsCoinBase (), wtx.m_from_me , wtx.GetTxTime ()});
4464
+ assert (txos_inserted);
4465
+ } else {
4466
+ std::tie (txo_it, txos_inserted) = m_txos.emplace (outpoint, WalletTXO{txout, ismine, wtx.GetState (), wtx.IsCoinBase (), wtx.m_from_me , wtx.GetTxTime ()});
4467
+ }
4468
+ auto [_, wtx_inserted] = wtx.m_txos .emplace (i, txo_it->second );
4469
+ assert (wtx_inserted);
4421
4470
}
4422
4471
}
4423
4472
}
@@ -4434,9 +4483,58 @@ std::optional<WalletTXO> CWallet::GetTXO(const COutPoint& outpoint) const
4434
4483
{
4435
4484
AssertLockHeld (cs_wallet);
4436
4485
const auto & it = m_txos.find (outpoint);
4437
- if (it = = m_txos.end ()) {
4438
- return std::nullopt ;
4486
+ if (it ! = m_txos.end ()) {
4487
+ return it-> second ;
4439
4488
}
4440
- return it->second ;
4489
+ const auto & u_it = m_unusable_txos.find (outpoint);
4490
+ if (u_it != m_unusable_txos.end ()) {
4491
+ return u_it->second ;
4492
+ }
4493
+ return std::nullopt;
4494
+ }
4495
+
4496
+ void CWallet::PruneSpentTXOs ()
4497
+ {
4498
+ AssertLockHeld (cs_wallet);
4499
+ auto it = m_txos.begin ();
4500
+ while (it != m_txos.end ()) {
4501
+ if (std::get_if<TxStateConflicted>(&it->second .GetState ()) || IsSpent (it->first , /* min_depth=*/ 1 )) {
4502
+ it = MarkTXOUnusable (it->first ).first ;
4503
+ } else {
4504
+ it++;
4505
+ }
4506
+ }
4507
+ }
4508
+
4509
+ std::pair<TXOMap::iterator, TXOMap::iterator> CWallet::MarkTXOUnusable (const COutPoint& outpoint)
4510
+ {
4511
+ AssertLockHeld (cs_wallet);
4512
+ auto txos_it = m_txos.find (outpoint);
4513
+ auto unusable_txos_it = m_unusable_txos.end ();
4514
+ if (txos_it != m_txos.end ()) {
4515
+ auto next_txo_it = std::next (txos_it);
4516
+ auto nh = m_txos.extract (txos_it);
4517
+ txos_it = next_txo_it;
4518
+ auto [position, inserted, _] = m_unusable_txos.insert (std::move (nh));
4519
+ unusable_txos_it = position;
4520
+ assert (inserted);
4521
+ }
4522
+ return {txos_it, unusable_txos_it};
4523
+ }
4524
+
4525
+ std::pair<TXOMap::iterator, TXOMap::iterator> CWallet::MarkTXOUsable (const COutPoint& outpoint)
4526
+ {
4527
+ AssertLockHeld (cs_wallet);
4528
+ auto txos_it = m_txos.end ();
4529
+ auto unusable_txos_it = m_unusable_txos.find (outpoint);
4530
+ if (unusable_txos_it != m_unusable_txos.end ()) {
4531
+ auto next_unusable_txo_it = std::next (unusable_txos_it);
4532
+ auto nh = m_unusable_txos.extract (unusable_txos_it);
4533
+ unusable_txos_it = next_unusable_txo_it;
4534
+ auto [position, inserted, _] = m_txos.insert (std::move (nh));
4535
+ assert (inserted);
4536
+ txos_it = position;
4537
+ }
4538
+ return {unusable_txos_it, txos_it};
4441
4539
}
4442
4540
} // namespace wallet
0 commit comments