Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 32 additions & 2 deletions src/wallet/rpcwallet.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1498,7 +1498,17 @@ UniValue listtransactions(const UniValue& params, bool fHelp)
UniValue ret(UniValue::VARR);

std::list<CAccountingEntry> acentries;
CWallet::TxItems txOrdered = pwalletMain->OrderedTxItems(acentries, strAccount);

// Perf: we only ever return the newest (nCount+nFrom) rows, so first try a limit-aware
// pass that builds only the newest (nCount+nFrom) wallet items instead of the entire
// ordered log. A single item can expand to 0..N output rows, so if the limited window is
// a strict subset of the wallet and still cannot fill (nCount+nFrom) rows, we fall back
// to the full unlimited pass to guarantee byte-identical output.
const int nWindow = nCount + nFrom;
bool fLimited = nWindow > 0;
CWallet::TxItems txOrdered = fLimited
? pwalletMain->OrderedTxItems(acentries, strAccount, nWindow)
: pwalletMain->OrderedTxItems(acentries, strAccount);

// iterate backwards until we have nCount items to return:
for (CWallet::TxItems::reverse_iterator it = txOrdered.rbegin(); it != txOrdered.rend(); ++it)
Expand All @@ -1510,7 +1520,27 @@ UniValue listtransactions(const UniValue& params, bool fHelp)
if (pacentry != 0)
AcentryToJSON(*pacentry, strAccount, ret);

if ((int)ret.size() >= (nCount+nFrom)) break;
if ((int)ret.size() >= nWindow) break;
}

// If the limited window may have dropped older items that we still needed to fill the
// requested rows, redo with the complete ordered log (same result as the original code).
if (fLimited && (int)ret.size() < nWindow && (int)txOrdered.size() >= nWindow)
{
ret.clear();
ret.setArray();
txOrdered = pwalletMain->OrderedTxItems(acentries, strAccount);
for (CWallet::TxItems::reverse_iterator it = txOrdered.rbegin(); it != txOrdered.rend(); ++it)
{
CWalletTx *const pwtx = (*it).second.first;
if (pwtx != 0)
ListTransactions(*pwtx, strAccount, 0, true, ret, filter);
CAccountingEntry *const pacentry = (*it).second.second;
if (pacentry != 0)
AcentryToJSON(*pacentry, strAccount, ret);

if ((int)ret.size() >= nWindow) break;
}
}
// ret is newest to oldest

Expand Down
23 changes: 21 additions & 2 deletions src/wallet/wallet.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1274,26 +1274,38 @@ 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, int nLimit)
{
AssertLockHeld(cs_wallet); // mapWallet
CWalletDB walletdb(strWalletFile);

// First: get all CWalletTx and CAccountingEntry into a sorted-by-order multimap.
TxItems txOrdered;

// Perf: when nLimit > 0 the caller only needs the newest nLimit items (largest
// nOrderPos), so we trim the smallest as we go and never grow past nLimit. This keeps
// the exact same newest-suffix the caller would have iterated, at a fraction of the
// memory/insertion cost on large wallets. The full path (nLimit <= 0) is unchanged.
// Note: a single item can expand to multiple (or zero) output rows downstream, so the
// caller treats a trimmed result as a hint and falls back to an unlimited call if it
// cannot fill its window.
//
// Note: maintaining indices in the database of (account,time) --> txid and (account, time) --> acentry
// would make this much faster for applications that do this a lot.
for (map<uint256, CWalletTx>::iterator it = mapWallet.begin(); it != mapWallet.end(); ++it)
{
CWalletTx* wtx = &((*it).second);
txOrdered.insert(make_pair(wtx->nOrderPos, TxPair(wtx, (CAccountingEntry*)0)));
if (nLimit > 0 && (int)txOrdered.size() > nLimit)
txOrdered.erase(txOrdered.begin());
}
acentries.clear();
walletdb.ListAccountCreditDebit(strAccount, acentries);
BOOST_FOREACH(CAccountingEntry& entry, acentries)
{
txOrdered.insert(make_pair(entry.nOrderPos, TxPair((CWalletTx*)0, &entry)));
if (nLimit > 0 && (int)txOrdered.size() > nLimit)
txOrdered.erase(txOrdered.begin());
}

return txOrdered;
Expand Down Expand Up @@ -3336,9 +3348,16 @@ bool CWallet::SelectCoinsMinConf(const CAmount& nTargetValue, int nConfMine, int
bool CWallet::SelectCoins(const CAmount& nTargetValue, set<pair<const CWalletTx*,unsigned int> >& setCoinsRet, CAmount& nValueRet, bool& fOnlyCoinbaseCoinsRet, bool& fNeedCoinbaseCoinsRet, const CCoinControl* coinControl) const
{
// Output parameter fOnlyCoinbaseCoinsRet is set to true when the only available coins are coinbase utxos.
// Perf: a single AvailableCoins pass with fIncludeCoinBase=true yields the full set; the
// no-coinbase set differs only by excluding coinbase utxos (the lone filter difference in
// AvailableCoins is fIncludeCoinBase), so we partition here instead of scanning mapWallet twice.
vector<COutput> vCoinsNoCoinbase, vCoinsWithCoinbase;
AvailableCoins(vCoinsNoCoinbase, true, coinControl, false, false);
AvailableCoins(vCoinsWithCoinbase, true, coinControl, false, true);
vCoinsNoCoinbase.reserve(vCoinsWithCoinbase.size());
for (const COutput& out : vCoinsWithCoinbase) {
if (!out.tx->IsCoinBase())
vCoinsNoCoinbase.push_back(out);
}
fOnlyCoinbaseCoinsRet = vCoinsNoCoinbase.size() == 0 && vCoinsWithCoinbase.size() > 0;

// If coinbase utxos can only be sent to zaddrs, exclude any coinbase utxos from coin selection.
Expand Down
9 changes: 8 additions & 1 deletion src/wallet/wallet.h
Original file line number Diff line number Diff line change
Expand Up @@ -1212,8 +1212,15 @@ class CWallet : public CCryptoKeyStore, public CValidationInterface
* Get the wallet's activity log
* @return multimap of ordered transactions and accounting entries
* @warning Returned pointers are *only* valid within the scope of passed acentries
*
* When nLimit > 0, only the newest nLimit items (largest nOrderPos) are returned;
* this is a perf hint for callers (e.g. listtransactions) that consume newest-first
* and need only a bounded window. Callers MUST treat a result smaller than the
* whole wallet as potentially under-supplied and fall back to an unlimited call if
* they cannot fill their requested window, since a single tx may yield 0..N output
* rows. With nLimit <= 0 (default) the full ordered log is returned, unchanged.
*/
TxItems OrderedTxItems(std::list<CAccountingEntry>& acentries, std::string strAccount = "");
TxItems OrderedTxItems(std::list<CAccountingEntry>& acentries, std::string strAccount = "", int nLimit = 0);

void MarkDirty();
bool UpdateNullifierNoteMap();
Expand Down
Loading