fix(lite-backend): don't panic when a reorg lands during a send

After broadcasting, the send path looked up the spent notes' and
UTXOs' transactions with unwrap. A reorg while the transaction was
being proved could remove one of them, so the backend panicked and
poisoned the wallet lock although the transaction was already
broadcast. The lookups are now `if let` with a warning.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-11 06:05:06 -05:00
co-authored by Claude Opus 5.5
parent 7c0afd06a8
commit 22e3f104d6
3 changed files with 110 additions and 10 deletions
@@ -2747,22 +2747,31 @@ pub fn scan_full_mempool_tx(&self, tx: &Transaction, height: i32, _datetime: u64
// Mark notes as spent.
{
// Mark sapling notes as unconfirmed spent
// Mark sapling notes as unconfirmed spent. The tx is already broadcast, so never panic here: a
// reorg or a rescan (clear_blocks) while it was proved can have removed the tx that holds a
// selected note or utxo. Skip those with a warning (a panic would poison `txs`; the send went out).
let mut txs = self.txs.write().unwrap();
for selected in notes {
let mut spent_note = txs.get_mut(&selected.txid).unwrap()
.notes.iter_mut()
.find(|nd| &nd.nullifier[..] == &selected.nullifier[..])
.unwrap();
spent_note.unconfirmed_spent = Some(tx.txid());
if let Some(spent_note) = txs.get_mut(&selected.txid)
.and_then(|wtx| wtx.notes.iter_mut().find(|nd| &nd.nullifier[..] == &selected.nullifier[..]))
{
spent_note.unconfirmed_spent = Some(tx.txid());
} else {
warn!("Sent {}, but its input note from tx {} is no longer in the wallet (reorg or rescan); not marked pending",
tx.txid(), selected.txid);
}
}
// Mark this utxo as unconfirmed spent
for utxo in tinputs {
let mut spent_utxo = txs.get_mut(&utxo.txid).unwrap().utxos.iter_mut()
.find(|u| utxo.txid == u.txid && utxo.output_index == u.output_index)
.unwrap();
spent_utxo.unconfirmed_spent = Some(tx.txid());
if let Some(spent_utxo) = txs.get_mut(&utxo.txid)
.and_then(|wtx| wtx.utxos.iter_mut().find(|u| utxo.txid == u.txid && utxo.output_index == u.output_index))
{
spent_utxo.unconfirmed_spent = Some(tx.txid());
} else {
warn!("Sent {}, but its input utxo {}:{} is no longer in the wallet (reorg or rescan); not marked pending",
tx.txid(), utxo.txid, utxo.output_index);
}
}
}