Problem: gettxspendingprevout erases each mempool result from a vector while holding mempool.cs, shifting the remaining requests every time and making large calls quadratic in the critical section.
For 10,000 mempool matches, an operation-count model reaches nearly 50 million moves.
For mixed requests, the RPC returns mempool results before txospenderindex results instead of following request order.
#34749 introduced both regressions.
Fix: gettxspendingprevout stores each result at its request position and collects unresolved requests in a reserved worklist for the txospenderindex lookup.
The mempool pass is linear, the response follows request order, and Clang can verify the lock requirement on GetConflictTx.
Benchmark: The functional benchmark sends mempool-only requests ranging from 8,000 to 128,000 entries ten times per size. Using the same settings for the unfixed and fixed commits:
AMD Ryzen 7 3700X (8 cores)
unfixed ██████████████████████████████ 90 s
fixed ███▒░░░░░░░░░░░░░░░░░░░░░░░░░░ 10 s (-80 s, 9.0x faster)
Raspberry Pi 5 (4 cores)
unfixed ██████████████████████████████ 685 s
fixed ▓░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 22 s (-663 s, 31.1x faster)
The unfixed run timed out after ~9 minutes on a Raspberry Pi 4 with 1 GB RAM.
<details><summary>Benchmark command</summary>
for commit in 963b061358b489b2ff4ff64895f2b895b3b89844 46e7173550a93cbe9d4e8ea28cfe7216286d8197; do \
git fetch origin "$commit" && git checkout --detach "$commit" && \
rm -rfd build && cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTS=OFF -DENABLE_WALLET=OFF >/dev/null 2>&1 && \
ninja -C build -j1 bitcoind >/dev/null 2>&1 && \
build/test/functional/test_runner.py rpc_gettxspendingprevout_quadratic.py --repeats=10 || break; \
done
</details>