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 fAllAccounts = (strAccount == string("*"));
bool involvesWatchonly = wtx.IsFromMe(ISMINE_WATCH_ONLY); 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 // Sent
if ((!listSent.empty() || nFee != 0) && (fAllAccounts || strAccount == strSentAccount)) 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))); entry.push_back(Pair("fee", ValueFromAmount(-nFee)));
if (fLong) if (fLong)
WalletTxToJSON(wtx, entry); 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); 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)); entry.push_back(Pair("vout", r.vout));
if (fLong) if (fLong)
WalletTxToJSON(wtx, entry); 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); ret.push_back(entry);
} }
} }
@@ -1955,7 +1968,7 @@ UniValue listtransactions(const UniValue& params, bool fHelp, const CPubKey& myp
UniValue ret(UniValue::VARR); UniValue ret(UniValue::VARR);
std::list<CAccountingEntry> acentries; 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: // iterate backwards until we have nCount items to return:
for (CWallet::TxItems::reverse_iterator it = txOrdered.rbegin(); it != txOrdered.rend(); ++it) 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()) if ((nFrom + nCount) > (int)ret.size())
nCount = ret.size() - nFrom; 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(); return result;
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;
} }
UniValue listaccounts(const UniValue& params, bool fHelp, const CPubKey& mypk) 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); ListTransactions(wtx, "*", 0, false, details, filter);
entry.push_back(Pair("details", details)); 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)); entry.push_back(Pair("hex", strHex));
return entry; return entry;
+4 -2
View File
@@ -1701,10 +1701,12 @@ int64_t CWallet::IncOrderPosNext(CWalletDB *pwalletdb)
return nRet; 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 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. // First: get all CWalletTx and CAccountingEntry into a sorted-by-order multimap.
TxItems txOrdered; TxItems txOrdered;
+3 -1
View File
@@ -1210,10 +1210,12 @@ public:
/** /**
* Get the wallet's activity log * 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 * @return multimap of ordered transactions and accounting entries
* @warning Returned pointers are *only* valid within the scope of passed acentries * @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(); void MarkDirty();
bool UpdateNullifierNoteMap(); bool UpdateNullifierNoteMap();