wallet: cut per-entry copies in listtransactions and gettransaction

- ListTransactions computes a tx's "size" once, lazily, through a const
  CTransaction reference instead of a slicing copy per entry.
- listtransactions copies the returned window once from the built array.
- OrderedTxItems can open the wallet DB read-only without a flush on close;
  listtransactions uses it, so a read-only RPC no longer forces a BDB
  checkpoint (ThreadFlushWalletDB still flushes about 2 s later).
- gettransaction hex-encodes the tx without copying it.

Output is unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-09 13:31:01 -05:00
co-authored by Claude Opus 5.5
parent 23f3e26d2f
commit 1541d045ee
3 changed files with 31 additions and 23 deletions
+24 -20
View File
@@ -1788,6 +1788,11 @@ void ListTransactions(const CWalletTx& wtx, const string& strAccount, int nMinDe
bool fAllAccounts = (strAccount == string("*"));
bool involvesWatchonly = wtx.IsFromMe(ISMINE_WATCH_ONLY);
// "size" is a property of the transaction, not of an entry: compute it once, when the first
// entry is built, through a const reference instead of a by-value CTransaction copy.
bool fHaveTxSize = false;
uint64_t nTxSize = 0;
// Sent
if ((!listSent.empty() || nFee != 0) && (fAllAccounts || strAccount == strSentAccount))
{
@@ -1804,7 +1809,11 @@ void ListTransactions(const CWalletTx& wtx, const string& strAccount, int nMinDe
entry.push_back(Pair("fee", ValueFromAmount(-nFee)));
if (fLong)
WalletTxToJSON(wtx, entry);
entry.push_back(Pair("size", static_cast<uint64_t>(GetSerializeSize(static_cast<CTransaction>(wtx), SER_NETWORK, PROTOCOL_VERSION))));
if (!fHaveTxSize) {
nTxSize = static_cast<uint64_t>(GetSerializeSize(static_cast<const CTransaction&>(wtx), SER_NETWORK, PROTOCOL_VERSION));
fHaveTxSize = true;
}
entry.push_back(Pair("size", nTxSize));
ret.push_back(entry);
}
}
@@ -1849,7 +1858,11 @@ void ListTransactions(const CWalletTx& wtx, const string& strAccount, int nMinDe
entry.push_back(Pair("vout", r.vout));
if (fLong)
WalletTxToJSON(wtx, entry);
entry.push_back(Pair("size", static_cast<uint64_t>(GetSerializeSize(static_cast<CTransaction>(wtx), SER_NETWORK, PROTOCOL_VERSION))));
if (!fHaveTxSize) {
nTxSize = static_cast<uint64_t>(GetSerializeSize(static_cast<const CTransaction&>(wtx), SER_NETWORK, PROTOCOL_VERSION));
fHaveTxSize = true;
}
entry.push_back(Pair("size", nTxSize));
ret.push_back(entry);
}
}
@@ -1955,7 +1968,7 @@ UniValue listtransactions(const UniValue& params, bool fHelp, const CPubKey& myp
UniValue ret(UniValue::VARR);
std::list<CAccountingEntry> acentries;
CWallet::TxItems txOrdered = pwalletMain->OrderedTxItems(acentries, strAccount);
CWallet::TxItems txOrdered = pwalletMain->OrderedTxItems(acentries, strAccount, true);
// iterate backwards until we have nCount items to return:
for (CWallet::TxItems::reverse_iterator it = txOrdered.rbegin(); it != txOrdered.rend(); ++it)
@@ -1978,23 +1991,14 @@ UniValue listtransactions(const UniValue& params, bool fHelp, const CPubKey& myp
if ((nFrom + nCount) > (int)ret.size())
nCount = ret.size() - nFrom;
vector<UniValue> arrTmp = ret.getValues();
// ret is newest to oldest; the reply is its entries [nFrom, nFrom+nCount), oldest to newest.
// Copy them once, back to front, straight from ret's storage (no vector copy/erase/reverse).
UniValue result(UniValue::VARR);
const std::vector<UniValue>& vNewestFirst = ret.getValues();
for (int i = nFrom + nCount; i > nFrom; --i)
result.push_back(vNewestFirst[i - 1]);
vector<UniValue>::iterator first = arrTmp.begin();
std::advance(first, nFrom);
vector<UniValue>::iterator last = arrTmp.begin();
std::advance(last, nFrom+nCount);
if (last != arrTmp.end()) arrTmp.erase(last, arrTmp.end());
if (first != arrTmp.begin()) arrTmp.erase(arrTmp.begin(), first);
std::reverse(arrTmp.begin(), arrTmp.end()); // Return oldest to newest
ret.clear();
ret.setArray();
ret.push_backV(arrTmp);
return ret;
return result;
}
UniValue listaccounts(const UniValue& params, bool fHelp, const CPubKey& mypk)
@@ -2239,7 +2243,7 @@ UniValue gettransaction(const UniValue& params, bool fHelp, const CPubKey& mypk)
ListTransactions(wtx, "*", 0, false, details, filter);
entry.push_back(Pair("details", details));
string strHex = EncodeHexTx(static_cast<CTransaction>(wtx));
string strHex = EncodeHexTx(static_cast<const CTransaction&>(wtx));
entry.push_back(Pair("hex", strHex));
return entry;
+4 -2
View File
@@ -1701,10 +1701,12 @@ int64_t CWallet::IncOrderPosNext(CWalletDB *pwalletdb)
return nRet;
}
CWallet::TxItems CWallet::OrderedTxItems(std::list<CAccountingEntry>& acentries, std::string strAccount)
CWallet::TxItems CWallet::OrderedTxItems(std::list<CAccountingEntry>& acentries, std::string strAccount, bool fReadOnlyDb)
{
AssertLockHeld(cs_wallet); // mapWallet
CWalletDB walletdb(strWalletFile);
// Only a read cursor over the accounting entries runs on this handle. Pure readers open it
// read-only and without flush-on-close, so the call does not end in a txn_checkpoint.
CWalletDB walletdb(strWalletFile, fReadOnlyDb ? "r" : "r+", !fReadOnlyDb);
// First: get all CWalletTx and CAccountingEntry into a sorted-by-order multimap.
TxItems txOrdered;
+3 -1
View File
@@ -1210,10 +1210,12 @@ public:
/**
* Get the wallet's activity log
* @param fReadOnlyDb open wallet.dat read-only and without flush-on-close (pure readers such as
* listtransactions: no txn_checkpoint per call); false = read-write, flush-on-close
* @return multimap of ordered transactions and accounting entries
* @warning Returned pointers are *only* valid within the scope of passed acentries
*/
TxItems OrderedTxItems(std::list<CAccountingEntry>& acentries, std::string strAccount = "");
TxItems OrderedTxItems(std::list<CAccountingEntry>& acentries, std::string strAccount = "", bool fReadOnlyDb = false);
void MarkDirty();
bool UpdateNullifierNoteMap();