perf(lite-backend): trim per-note witness history in place

The block scan replaced each note's witness list with the result of
split_off(), which allocates a new buffer on every call: every block,
each note with history paid an allocation and, once its list was full, a
copy of all 100 entries.

The duplicate of the newest witness is now pushed and, when the list
exceeds 100 entries, the oldest is drained in place; notes with no
history are skipped. The list keeps the same entries in the same order,
so nothing in the output changes.

The witness tests and the oracle pass, and a differential run against
the unmodified backend is byte-identical (every witness entry and the
whole saved file after every operation) apart from the lists the
previous commit clears.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-10 09:50:03 -05:00
co-authored by Claude Opus 5.5
parent 7f99fbb9c6
commit e7ebc568b0
@@ -2237,15 +2237,16 @@ pub fn scan_full_mempool_tx(&self, tx: &Transaction, height: i32, _datetime: u64
// Prepare the note witnesses for updating
for tx in txs.values_mut() {
for nd in tx.notes.iter_mut() {
// Duplicate the most recent witness
// Duplicate the most recent witness (nothing to do for notes with no history)
if let Some(witness) = nd.witnesses.last() {
let clone = witness.clone();
nd.witnesses.push(clone);
// Trim the oldest witnesses in place (no fresh Vec per note per block)
let len = nd.witnesses.len();
if len > MAX_REORG {
nd.witnesses.drain(..len - MAX_REORG);
}
}
// Trim the oldest witnesses
nd.witnesses = nd
.witnesses
.split_off(nd.witnesses.len().saturating_sub(100));
}
}