wallet/rpc: order history RPC txs by pointer, not by deep copy

getalldata, z_listreceivedaddress and z_listsentbyaddress build a
map<int64_t, CWalletTx> of the whole wallet, keyed by nOrderPos, before
any filter runs. Every insert deep-copies the CWalletTx, including each
note's witness list (~100KB per note on a mainnet wallet). That is
gigabytes of allocation per call on a large wallet, under
LOCK2(cs_main, cs_wallet). The GUI's history scan runs
z_listsentbyaddress on every launch and about once per new tip.

The map now holds const CWalletTx* into mapWallet. It is still a
std::map, so the first tx inserted for an nOrderPos wins, exactly as
before. mapWallet can't change while the RPC holds cs_wallet, so the
pointers stay valid. Output is unchanged for a consistent wallet. One
corner case differs: with a nullifier map entry pointing at a note
its tx lacks, the spends helper's operator[] inserts an empty note into
that tx, and a later iteration now sees it where the old copies didn't.
The next commit removes that insertion.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-17 08:16:09 -05:00
co-authored by Claude Opus 5.5
parent 190023f85f
commit 7c3563cd5a
+18 -12
View File
@@ -3032,11 +3032,13 @@ UniValue getalldata(const UniValue& params, bool fHelp,const CPubKey&)
} }
//Create Ordered List //Create Ordered List
map<int64_t,CWalletTx> orderedTxs; // Pointers into mapWallet, not copies (a CWalletTx copy deep-copies every note's witness list).
// As before, the first tx inserted for an nOrderPos wins.
map<int64_t,const CWalletTx*> orderedTxs;
for (map<uint256, CWalletTx>::iterator it = pwalletMain->mapWallet.begin(); it != pwalletMain->mapWallet.end(); ++it) { for (map<uint256, CWalletTx>::iterator it = pwalletMain->mapWallet.begin(); it != pwalletMain->mapWallet.end(); ++it) {
const uint256& wtxid = it->first; const uint256& wtxid = it->first;
const CWalletTx& wtx = (*it).second; const CWalletTx& wtx = (*it).second;
orderedTxs.insert(std::pair<int64_t,CWalletTx>(wtx.nOrderPos, wtx)); orderedTxs.insert(std::make_pair(wtx.nOrderPos, &wtx));
unsigned int txType = 0; unsigned int txType = 0;
// 0 Unassigend // 0 Unassigend
@@ -3229,9 +3231,9 @@ UniValue getalldata(const UniValue& params, bool fHelp,const CPubKey&)
uint64_t t = GetTime(); uint64_t t = GetTime();
for (map<int64_t,CWalletTx>::reverse_iterator it = orderedTxs.rbegin(); it != orderedTxs.rend(); ++it) for (map<int64_t,const CWalletTx*>::reverse_iterator it = orderedTxs.rbegin(); it != orderedTxs.rend(); ++it)
{ {
const CWalletTx& wtx = (*it).second; const CWalletTx& wtx = *(*it).second;
if (!CheckFinalTx(wtx)) if (!CheckFinalTx(wtx))
continue; continue;
@@ -3515,17 +3517,19 @@ UniValue z_listreceivedaddress(const UniValue& params, bool fHelp,const CPubKey&
throw runtime_error("Filter must be greater that 0."); throw runtime_error("Filter must be greater that 0.");
//Created Ordered Transaction Map //Created Ordered Transaction Map
map<int64_t,CWalletTx> orderedTxs; // Pointers into mapWallet, not copies (a CWalletTx copy deep-copies every note's witness list).
// As before, the first tx inserted for an nOrderPos wins.
map<int64_t,const CWalletTx*> orderedTxs;
for (map<uint256, CWalletTx>::iterator it = pwalletMain->mapWallet.begin(); it != pwalletMain->mapWallet.end(); ++it) { for (map<uint256, CWalletTx>::iterator it = pwalletMain->mapWallet.begin(); it != pwalletMain->mapWallet.end(); ++it) {
const CWalletTx& wtx = (*it).second; const CWalletTx& wtx = (*it).second;
orderedTxs.insert(std::pair<int64_t,CWalletTx>(wtx.nOrderPos, wtx)); orderedTxs.insert(std::make_pair(wtx.nOrderPos, &wtx));
} }
uint64_t t = GetTime(); uint64_t t = GetTime();
//Reverse Iterate thru transactions //Reverse Iterate thru transactions
for (map<int64_t,CWalletTx>::reverse_iterator it = orderedTxs.rbegin(); it != orderedTxs.rend(); ++it) { for (map<int64_t,const CWalletTx*>::reverse_iterator it = orderedTxs.rbegin(); it != orderedTxs.rend(); ++it) {
const CWalletTx& wtx = (*it).second; const CWalletTx& wtx = *(*it).second;
bool includeTransaction = true; bool includeTransaction = true;
//Excude transactions with less confirmations than required //Excude transactions with less confirmations than required
@@ -3698,17 +3702,19 @@ UniValue z_listsentbyaddress(const UniValue& params, bool fHelp,const CPubKey&)
throw runtime_error("Filter must be greater that 0."); throw runtime_error("Filter must be greater that 0.");
//Created Ordered Transaction Map //Created Ordered Transaction Map
map<int64_t,CWalletTx> orderedTxs; // Pointers into mapWallet, not copies (a CWalletTx copy deep-copies every note's witness list).
// As before, the first tx inserted for an nOrderPos wins.
map<int64_t,const CWalletTx*> orderedTxs;
for (map<uint256, CWalletTx>::iterator it = pwalletMain->mapWallet.begin(); it != pwalletMain->mapWallet.end(); ++it) { for (map<uint256, CWalletTx>::iterator it = pwalletMain->mapWallet.begin(); it != pwalletMain->mapWallet.end(); ++it) {
const CWalletTx& wtx = (*it).second; const CWalletTx& wtx = (*it).second;
orderedTxs.insert(std::pair<int64_t,CWalletTx>(wtx.nOrderPos, wtx)); orderedTxs.insert(std::make_pair(wtx.nOrderPos, &wtx));
} }
uint64_t t = GetTime(); uint64_t t = GetTime();
//Reverse Iterate thru transactions //Reverse Iterate thru transactions
for (map<int64_t,CWalletTx>::reverse_iterator it = orderedTxs.rbegin(); it != orderedTxs.rend(); ++it) { for (map<int64_t,const CWalletTx*>::reverse_iterator it = orderedTxs.rbegin(); it != orderedTxs.rend(); ++it) {
const CWalletTx& wtx = (*it).second; const CWalletTx& wtx = *(*it).second;
if (!CheckFinalTx(wtx)) if (!CheckFinalTx(wtx))
continue; continue;