diff --git a/src/wallet/rpchushwallet.cpp b/src/wallet/rpchushwallet.cpp index 39f090ef7..cdcb08f2d 100644 --- a/src/wallet/rpchushwallet.cpp +++ b/src/wallet/rpchushwallet.cpp @@ -11,11 +11,72 @@ #include "utilmoneystr.h" #include +#include +#include +#include +#include + using namespace std; using namespace libzcash; bool EnsureWalletIsAvailable(bool avoidException); +const std::vector& ZsTxJSONContext::keys() +{ + if (!keysBuilt) { + // The calls, order and filter of the per-output key loops this replaces. + std::set addresses; + pwalletMain->GetSaplingPaymentAddresses(addresses); + for (std::set::const_iterator it = addresses.begin(); it != addresses.end(); ++it) { + libzcash::SaplingExtendedSpendingKey extsk; + if (!pwalletMain->GetSaplingExtendedSpendingKey(*it, extsk)) + continue; // viewing-key-only, or encrypted and locked: skipped, as before + libzcash::SaplingFullViewingKey fvk = extsk.expsk.full_viewing_key(); + Key key; + key.addr = *it; + key.ivk = fvk.in_viewing_key(); + key.ovk = fvk.ovk; + vKeys.push_back(key); + } + keysBuilt = true; + } + return vKeys; +} + +bool ZsTxJSONContext::hasNullifier(const libzcash::SaplingPaymentAddress& addr, const uint256& nullifier) +{ + if (!nullifiersBuilt) { + // The population GetNullifiersForAddresses() scans: every mapWallet note that has a nullifier. + for (std::map::const_iterator it = pwalletMain->mapWallet.begin(); it != pwalletMain->mapWallet.end(); ++it) { + for (mapSaplingNoteData_t::const_iterator nit = it->second.mapSaplingNoteData.begin(); nit != it->second.mapSaplingNoteData.end(); ++nit) { + if (nit->second.nullifier) + nfByIvk.insert(std::make_pair(nit->second.ivk, nit->second.nullifier.get())); + } + } + nullifiersBuilt = true; + } + // GetNullifiersForAddresses() ignores the result of this lookup, so an address the wallet does not + // know is matched against the default (null) ivk. Keep that. + libzcash::SaplingIncomingViewingKey ivk; + pwalletMain->GetSaplingIncomingViewingKey(addr, ivk); + return nfByIvk.count(std::make_pair(ivk, nullifier)) > 0; +} + +bool ZsTxJSONContext::seedOvk(uint256& ovkOut) +{ + if (!seedOvkTried) { + HDSeed seed; + if (pwalletMain->GetHDSeedForDerivation(seed)) { + seedOvkValue = ovkForShieldingFromTaddr(seed); + seedOvkOk = true; + } + seedOvkTried = true; + } + if (seedOvkOk) + ovkOut = seedOvkValue; + return seedOvkOk; +} + void zsTxSpendsToJSON(const CWalletTx& wtx, UniValue& spends, CAmount& totalSpends, CAmount& filteredSpends, const std::string& strAddress, bool filterByAddress) { LOCK2(cs_main, pwalletMain->cs_wallet); @@ -132,13 +193,16 @@ void zsTxSpendsToJSON(const CWalletTx& wtx, UniValue& spends, CAmount& totalSpen } -void zsTxReceivedToJSON(const CWalletTx& wtx, UniValue& received, CAmount& totalReceived, const std::string& strAddress, bool filterByAddress) { +void zsTxReceivedToJSON(const CWalletTx& wtx, UniValue& received, CAmount& totalReceived, const std::string& strAddress, bool filterByAddress, ZsTxJSONContext* ctx) { if(fZdebug) fprintf(stderr,"%s: txid=%s\n", __func__, wtx.GetHash().ToString().c_str() ); LOCK2(cs_main, pwalletMain->cs_wallet); + ZsTxJSONContext localCtx; // used only when the caller passed no per-call memo + ZsTxJSONContext& zctx = ctx ? *ctx : localCtx; + //Check address bool isTAddress = false; bool isZsAddress = false; @@ -192,13 +256,36 @@ void zsTxReceivedToJSON(const CWalletTx& wtx, UniValue& received, CAmount& total UniValue obj(UniValue::VOBJ); bool changeTx = false; //Decrypt sapling incoming commitments using IVK - std::set addresses; - pwalletMain->GetSaplingPaymentAddresses(addresses); - for (auto addr : addresses) { - libzcash::SaplingExtendedSpendingKey extsk; - if (pwalletMain->GetSaplingExtendedSpendingKey(addr, extsk)) { + const std::vector& keys = zctx.keys(); + // An output opens under at most one ivk (decrypt() checks the note commitment, which binds + // pk_d = [ivk]g_d), and FindMySaplingNotes records that ivk in mapSaplingNoteData. When the recorded + // ivk is one of keys(), pass 0 tries only the keys with that ivk: every other key would fail, as in + // the old single loop. If pass 0 opens nothing (stale note data), pass 1 tries the remaining keys in + // the same order. Without a usable record only pass 1 runs, over every key, exactly as before. + libzcash::SaplingIncomingViewingKey recordedIvk; + bool narrow = false; + { + mapSaplingNoteData_t::const_iterator ndIt = wtx.mapSaplingNoteData.find(SaplingOutPoint(wtx.GetHash(), i)); + if (ndIt != wtx.mapSaplingNoteData.end()) { + for (size_t k = 0; k < keys.size() && !narrow; k++) { + if (keys[k].ivk == ndIt->second.ivk) { + recordedIvk = ndIt->second.ivk; + narrow = true; + } + } + } + } + bool openedByRecordedIvk = false; + for (int pass = narrow ? 0 : 1; pass < 2 && !openedByRecordedIvk; pass++) { + for (size_t k = 0; k < keys.size(); k++) { + if (narrow && ((pass == 0) != (keys[k].ivk == recordedIvk))) + continue; // pass 0: only the recorded ivk; pass 1: only the others + const libzcash::SaplingPaymentAddress& addr = keys[k].addr; + { // spending-key availability is resolved once per call, in ZsTxJSONContext::keys() auto pt = libzcash::SaplingNotePlaintext::decrypt( - outputDesc.encCiphertext, extsk.expsk.full_viewing_key().in_viewing_key(), outputDesc.ephemeralKey, outputDesc.cm); + outputDesc.encCiphertext, keys[k].ivk, outputDesc.ephemeralKey, outputDesc.cm); + if (pt && pass == 0) + openedByRecordedIvk = true; if (pt) { auto note = pt.get(); @@ -219,10 +306,8 @@ void zsTxReceivedToJSON(const CWalletTx& wtx, UniValue& received, CAmount& total //Check Change Status if (wtx.vShieldedSpend.size()!=0) { - std::set> nullifierSet; - nullifierSet = pwalletMain->GetNullifiersForAddresses({addr}); BOOST_FOREACH(const SpendDescription& spendDesc, wtx.vShieldedSpend) { - if (nullifierSet.count(std::make_pair(addr, spendDesc.nullifier))) { + if (zctx.hasNullifier(addr, spendDesc.nullifier)) { changeTx = true; } } @@ -235,6 +320,7 @@ void zsTxReceivedToJSON(const CWalletTx& wtx, UniValue& received, CAmount& total } } } + } // pass } } received.push_back(Pair("saplingReceived",zsReceived)); @@ -243,10 +329,13 @@ void zsTxReceivedToJSON(const CWalletTx& wtx, UniValue& received, CAmount& total } -void zsTxSendsToJSON(const CWalletTx& wtx, UniValue& sends, CAmount& totalSends, const std::string& strAddress, bool filterByAddress) { +void zsTxSendsToJSON(const CWalletTx& wtx, UniValue& sends, CAmount& totalSends, const std::string& strAddress, bool filterByAddress, ZsTxJSONContext* ctx) { LOCK2(cs_main, pwalletMain->cs_wallet); + ZsTxJSONContext localCtx; // used only when the caller passed no per-call memo + ZsTxJSONContext& zctx = ctx ? *ctx : localCtx; + //Used to identify incomplete key sets int shieldedOutputCount = 0; int shieldedOutputDecryptedCount = 0; @@ -304,10 +393,10 @@ void zsTxSendsToJSON(const CWalletTx& wtx, UniValue& sends, CAmount& totalSends, //Decrypt sapling outgoing t to z transaction using HDseed if (wtx.vShieldedSpend.size()==0) { - HDSeed seed; - if (pwalletMain->GetHDSeedForDerivation(seed)) { + uint256 taddrOvk; + if (zctx.seedOvk(taddrOvk)) { auto opt = libzcash::SaplingOutgoingPlaintext::decrypt( - outputDesc.outCiphertext,ovkForShieldingFromTaddr(seed),outputDesc.cv,outputDesc.cm,outputDesc.ephemeralKey); + outputDesc.outCiphertext,taddrOvk,outputDesc.cv,outputDesc.cm,outputDesc.ephemeralKey); if (opt) { auto opt_unwrapped = opt.get(); @@ -332,13 +421,11 @@ void zsTxSendsToJSON(const CWalletTx& wtx, UniValue& sends, CAmount& totalSends, //attempt Decryption of Outgoing Sapling using wallet extended spending keys } else { - std::set addresses; - pwalletMain->GetSaplingPaymentAddresses(addresses); - for (auto addr : addresses) { - libzcash::SaplingExtendedSpendingKey extsk; - if (pwalletMain->GetSaplingExtendedSpendingKey(addr, extsk)) { + const std::vector& keys = zctx.keys(); + for (size_t k = 0; k < keys.size(); k++) { + { // spending-key availability is resolved once per call, in ZsTxJSONContext::keys() auto opt = libzcash::SaplingOutgoingPlaintext::decrypt( - outputDesc.outCiphertext,extsk.expsk.full_viewing_key().ovk,outputDesc.cv,outputDesc.cm,outputDesc.ephemeralKey); + outputDesc.outCiphertext,keys[k].ovk,outputDesc.cv,outputDesc.cm,outputDesc.ephemeralKey); if (opt) { auto opt_unwrapped = opt.get(); @@ -365,10 +452,8 @@ void zsTxSendsToJSON(const CWalletTx& wtx, UniValue& sends, CAmount& totalSends, //Check Change Status if (wtx.vShieldedSpend.size()!=0) { - std::set> nullifierSet; - nullifierSet = pwalletMain->GetNullifiersForAddresses({sentAddr}); BOOST_FOREACH(const SpendDescription& spendDesc, wtx.vShieldedSpend) { - if (nullifierSet.count(std::make_pair(sentAddr, spendDesc.nullifier))) { + if (zctx.hasNullifier(sentAddr, spendDesc.nullifier)) { changeTx = true; } } @@ -397,7 +482,7 @@ void zsTxSendsToJSON(const CWalletTx& wtx, UniValue& sends, CAmount& totalSends, } -void zsWalletTxJSON(const CWalletTx& wtx, UniValue& ret, const std::string strAddress, bool fBool, const int returnType) { +void zsWalletTxJSON(const CWalletTx& wtx, UniValue& ret, const std::string strAddress, bool fBool, const int returnType, ZsTxJSONContext* ctx) { LOCK2(cs_main, pwalletMain->cs_wallet); @@ -445,7 +530,7 @@ void zsWalletTxJSON(const CWalletTx& wtx, UniValue& ret, const std::string strAd tx.push_back(Pair("expiryheight", (int64_t)wtx.nExpiryHeight)); tx.push_back(Pair("confirmations", confirms)); tx.push_back(Pair("time", wtx.GetTxTime())); - tx.push_back(Pair("size", static_cast(GetSerializeSize(static_cast(wtx), SER_NETWORK, PROTOCOL_VERSION)))); + tx.push_back(Pair("size", static_cast(GetSerializeSize(static_cast(wtx), SER_NETWORK, PROTOCOL_VERSION)))); //Wallet Conflicts UniValue conflicts(UniValue::VARR); @@ -469,7 +554,7 @@ void zsWalletTxJSON(const CWalletTx& wtx, UniValue& ret, const std::string strAd } // Get Received if (returnType == 0 || returnType == 2) { - zsTxReceivedToJSON(wtx, received, totalReceived, strAddress, fBool); + zsTxReceivedToJSON(wtx, received, totalReceived, strAddress, fBool, ctx); if (!fBool || totalReceived != 0) { tx.push_back(Pair("received",received)); } @@ -479,7 +564,7 @@ void zsWalletTxJSON(const CWalletTx& wtx, UniValue& ret, const std::string strAd if (returnType == 0 || returnType == 3) { //Only include sends if there are spends that belong to the wallet. if (totalSpends != 0 || fBool) { - zsTxSendsToJSON(wtx, sends, totalSends, strAddress, fBool); + zsTxSendsToJSON(wtx, sends, totalSends, strAddress, fBool, ctx); } if (!fBool || totalSends != 0) { tx.push_back(Pair("sends",sends)); diff --git a/src/wallet/rpchushwallet.h b/src/wallet/rpchushwallet.h index 416c5b45a..45671cf6b 100644 --- a/src/wallet/rpchushwallet.h +++ b/src/wallet/rpchushwallet.h @@ -6,6 +6,13 @@ #ifndef HUSH_WALLET_RPCHUSHWALLET_H #define HUSH_WALLET_RPCHUSHWALLET_H +#include "uint256.h" +#include "zcash/Address.hpp" + +#include +#include +#include + struct balancestruct { CAmount confirmed; CAmount unconfirmed; @@ -13,10 +20,46 @@ struct balancestruct { CAmount immature; }; +// Per-RPC-call memo for the zs*ToJSON helpers: the wallet's usable Sapling keys, the HD-seed ovk and a +// nullifier index, derived once per call instead of once per output. Every member is a copy; nothing +// points into wallet maps. Valid only while the caller holds LOCK2(cs_main, pwalletMain->cs_wallet) for +// the whole call: declare it after that lock and never keep it across calls. Passing NULL to the helpers +// gives each helper call its own memo (the old per-call cost profile, minus the per-output repeats). +class ZsTxJSONContext { +public: + struct Key { + libzcash::SaplingPaymentAddress addr; + libzcash::SaplingIncomingViewingKey ivk; + uint256 ovk; + }; + + ZsTxJSONContext() : keysBuilt(false), nullifiersBuilt(false), seedOvkTried(false), seedOvkOk(false) {} + ZsTxJSONContext(const ZsTxJSONContext&) = delete; + ZsTxJSONContext& operator=(const ZsTxJSONContext&) = delete; + + // Wallet z-addresses (std::set order) whose extended spending key is available now, with that key's + // extsk.expsk.full_viewing_key().in_viewing_key() and .ovk. Empty for an encrypted+locked wallet or + // one that holds only viewing keys. + const std::vector& keys(); + // == GetNullifiersForAddresses({addr}).count(std::make_pair(addr, nullifier)) > 0 + bool hasNullifier(const libzcash::SaplingPaymentAddress& addr, const uint256& nullifier); + // == GetHDSeedForDerivation(seed) ? (ovkOut = ovkForShieldingFromTaddr(seed), true) : false + bool seedOvk(uint256& ovkOut); + +private: + bool keysBuilt; + std::vector vKeys; + bool nullifiersBuilt; + std::set > nfByIvk; + bool seedOvkTried; + bool seedOvkOk; + uint256 seedOvkValue; +}; + void zsTxSpendsToJSON(const CWalletTx& wtx, UniValue& spends, CAmount& totalSpends, CAmount& filteredSpends, const std::string& strAddress, bool filterByAddress); -void zsTxReceivedToJSON(const CWalletTx& wtx, UniValue& received, CAmount& totalReceived, const std::string& strAddress, bool filterByAddress); -void zsTxSendsToJSON(const CWalletTx& wtx, UniValue& sends, CAmount& totalSends, const std::string& strAddress, bool filterByAddress); -void zsWalletTxJSON(const CWalletTx& wtx, UniValue& ret, const std::string strAddress, bool fBool, const int returnType); +void zsTxReceivedToJSON(const CWalletTx& wtx, UniValue& received, CAmount& totalReceived, const std::string& strAddress, bool filterByAddress, ZsTxJSONContext* ctx = NULL); +void zsTxSendsToJSON(const CWalletTx& wtx, UniValue& sends, CAmount& totalSends, const std::string& strAddress, bool filterByAddress, ZsTxJSONContext* ctx = NULL); +void zsWalletTxJSON(const CWalletTx& wtx, UniValue& ret, const std::string strAddress, bool fBool, const int returnType, ZsTxJSONContext* ctx = NULL); #endif //HUSH_WALLET_RPCWALLET_H diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index 0ea4fa1df..5fa5c5fd9 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -48,6 +48,7 @@ #include #include #include +#include #include #include "hush_defs.h" #include @@ -2960,6 +2961,26 @@ UniValue listunspent(const UniValue& params, bool fHelp, const CPubKey& mypk) return results; } +// Wallet transactions ordered by nOrderPos without copying them (the old code copied every CWalletTx, +// with every note's witness list, into a map). The pointers point into +// pwalletMain->mapWallet and are valid only while cs_wallet is held and nothing erases from mapWallet +// (std::map insertions do not invalidate them). Reproduces map::insert exactly: +// ascending nOrderPos and, for equal nOrderPos, only the first tx in mapWallet (txid) order, because +// insert() never replaces an existing key; hence stable_sort + unique. +typedef std::pair OrderedWtx; +static bool OrderedWtxLess(const OrderedWtx& a, const OrderedWtx& b) { return a.first < b.first; } +static bool OrderedWtxSamePos(const OrderedWtx& a, const OrderedWtx& b) { return a.first == b.first; } +static void BuildOrderedWalletTxs(std::vector& out) +{ + AssertLockHeld(pwalletMain->cs_wallet); + out.clear(); + out.reserve(pwalletMain->mapWallet.size()); + for (std::map::const_iterator it = pwalletMain->mapWallet.begin(); it != pwalletMain->mapWallet.end(); ++it) + out.push_back(OrderedWtx(it->second.nOrderPos, &it->second)); + std::stable_sort(out.begin(), out.end(), OrderedWtxLess); + out.erase(std::unique(out.begin(), out.end(), OrderedWtxSamePos), out.end()); +} + // Return current blockchain status, wallet balance, address balance and the last 200 transactions UniValue getalldata(const UniValue& params, bool fHelp,const CPubKey&) { @@ -3031,12 +3052,14 @@ UniValue getalldata(const UniValue& params, bool fHelp,const CPubKey&) addressBalances.insert(make_pair(addressString,txAmounts)); } - //Create Ordered List - map orderedTxs; + // Per-call key/nullifier memo for the note loop below and for zsWalletTxJSON (rpchushwallet.h). + // Declared after the LOCK2 above, so it is destroyed before cs_wallet is released. + ZsTxJSONContext zsCtx; + + //Classify every wallet tx for the balances (the ordered list for listtransactions is built below) for (map::iterator it = pwalletMain->mapWallet.begin(); it != pwalletMain->mapWallet.end(); ++it) { const uint256& wtxid = it->first; const CWalletTx& wtx = (*it).second; - orderedTxs.insert(std::pair(wtx.nOrderPos, wtx)); unsigned int txType = 0; // 0 Unassigend @@ -3105,18 +3128,19 @@ UniValue getalldata(const UniValue& params, bool fHelp,const CPubKey&) for (auto & pair : wtx.mapSaplingNoteData) { SaplingOutPoint op = pair.first; - SaplingNoteData nd = pair.second; + const SaplingNoteData& nd = pair.second; //Skip Spent if (nd.nullifier && pwalletMain->IsSaplingSpent(*nd.nullifier)) continue; //Decrypt sapling incoming commitments using IVK - for (auto addr : zs_addresses) { - libzcash::SaplingExtendedSpendingKey extsk; - if (pwalletMain->GetSaplingExtendedSpendingKey(addr, extsk)) { + const std::vector& zsKeys = zsCtx.keys(); + for (size_t k = 0; k < zsKeys.size(); k++) { + const libzcash::SaplingPaymentAddress& addr = zsKeys[k].addr; + { // spending-key availability is resolved once per call, in ZsTxJSONContext::keys() auto pt = libzcash::SaplingNotePlaintext::decrypt( - wtx.vShieldedOutput[op.n].encCiphertext,extsk.expsk.full_viewing_key().in_viewing_key(),wtx.vShieldedOutput[op.n].ephemeralKey,wtx.vShieldedOutput[op.n].cm); + wtx.vShieldedOutput[op.n].encCiphertext,zsKeys[k].ivk,wtx.vShieldedOutput[op.n].ephemeralKey,wtx.vShieldedOutput[op.n].cm); if (txType == 0 && pwalletMain->IsLockedNote(op)) txType == 3; @@ -3228,10 +3252,14 @@ UniValue getalldata(const UniValue& params, bool fHelp,const CPubKey&) } + //Ordered list of pointers into mapWallet (cs_wallet is held for the whole call) + std::vector orderedTxs; + BuildOrderedWalletTxs(orderedTxs); + uint64_t t = GetTime(); - for (map::reverse_iterator it = orderedTxs.rbegin(); it != orderedTxs.rend(); ++it) + for (std::vector::reverse_iterator it = orderedTxs.rbegin(); it != orderedTxs.rend(); ++it) { - const CWalletTx& wtx = (*it).second; + const CWalletTx& wtx = *(it->second); if (!CheckFinalTx(wtx)) continue; @@ -3248,7 +3276,7 @@ UniValue getalldata(const UniValue& params, bool fHelp,const CPubKey&) continue; } - zsWalletTxJSON(wtx, trans, "*", false, 0); + zsWalletTxJSON(wtx, trans, "*", false, 0, &zsCtx); if (trans.size() >= nCount) break; } @@ -3514,18 +3542,16 @@ UniValue z_listreceivedaddress(const UniValue& params, bool fHelp,const CPubKey& if (nFilter < 0) throw runtime_error("Filter must be greater that 0."); - //Created Ordered Transaction Map - map orderedTxs; - for (map::iterator it = pwalletMain->mapWallet.begin(); it != pwalletMain->mapWallet.end(); ++it) { - const CWalletTx& wtx = (*it).second; - orderedTxs.insert(std::pair(wtx.nOrderPos, wtx)); - } + //Ordered list of pointers into mapWallet (cs_wallet is held for the whole call) + std::vector orderedTxs; + BuildOrderedWalletTxs(orderedTxs); + ZsTxJSONContext zsCtx; // per-call key/nullifier memo (rpchushwallet.h) uint64_t t = GetTime(); //Reverse Iterate thru transactions - for (map::reverse_iterator it = orderedTxs.rbegin(); it != orderedTxs.rend(); ++it) { - const CWalletTx& wtx = (*it).second; + for (std::vector::reverse_iterator it = orderedTxs.rbegin(); it != orderedTxs.rend(); ++it) { + const CWalletTx& wtx = *(it->second); bool includeTransaction = true; //Excude transactions with less confirmations than required @@ -3546,7 +3572,7 @@ UniValue z_listreceivedaddress(const UniValue& params, bool fHelp,const CPubKey& } if (includeTransaction) { - zsWalletTxJSON(wtx, ret, params[0].get_str() , true, 2); + zsWalletTxJSON(wtx, ret, params[0].get_str() , true, 2, &zsCtx); } if (ret.size() > nCount) break; @@ -3597,7 +3623,7 @@ UniValue z_listsentbyaddress(const UniValue& params, bool fHelp,const CPubKey&) if (!EnsureWalletIsAvailable(fHelp)) return NullUniValue; - if (fHelp || params.size() > 5 || params.size() == 3) + if (fHelp || params.size() > 6 || params.size() == 3) throw runtime_error( "z_listsentbyaddress\n" "\nReturns decrypted DragonX outputs sent to a single address.\n" @@ -3622,11 +3648,16 @@ UniValue z_listsentbyaddress(const UniValue& params, bool fHelp,const CPubKey&) "5. \"Count:\" (numeric, optional, default=9999999) \n" " Last n number of transactions returned\n" "\n" + "6. \"Sends only:\" (boolean, optional, default=false) \n" + " true: omit the \"spends\" and \"received\" keys of every entry and skip the\n" + " trial decryption they need. false: the full historic output.\n" + "\n" "Default Parameters:\n" "2. 0 - O confimations required\n" "3. 0 - Returns all transactions\n" "4. 9999999 - Ignored\n" "5. 9999999 - Return the last 9,999,999 transactions.\n" + "6. false - Include the spends and received sections.\n" "\n" "\nResult:\n" " \"txid\": \"transactionid\", (string) The transaction id.\n" @@ -3675,6 +3706,7 @@ UniValue z_listsentbyaddress(const UniValue& params, bool fHelp,const CPubKey&) int64_t nFilterType = 0; int64_t nFilter = 9999999; int64_t nCount = 9999999; + bool fSendsOnly = false; // opt-in, see help: omits the "spends"/"received" keys if (params.size() >= 2) nMinConfirms = params[1].get_int64(); @@ -3684,10 +3716,14 @@ UniValue z_listsentbyaddress(const UniValue& params, bool fHelp,const CPubKey&) nFilter = params[3].get_int64(); } - if (params.size() == 5) { + if (params.size() >= 5) { nCount = params[4].get_int64(); } + if (params.size() == 6) { + fSendsOnly = params[5].get_bool(); + } + if (nMinConfirms < 0) throw runtime_error("Minimum confimations must be greater that 0"); @@ -3697,18 +3733,16 @@ UniValue z_listsentbyaddress(const UniValue& params, bool fHelp,const CPubKey&) if (nFilter < 0) throw runtime_error("Filter must be greater that 0."); - //Created Ordered Transaction Map - map orderedTxs; - for (map::iterator it = pwalletMain->mapWallet.begin(); it != pwalletMain->mapWallet.end(); ++it) { - const CWalletTx& wtx = (*it).second; - orderedTxs.insert(std::pair(wtx.nOrderPos, wtx)); - } + //Ordered list of pointers into mapWallet (cs_wallet is held for the whole call) + std::vector orderedTxs; + BuildOrderedWalletTxs(orderedTxs); + ZsTxJSONContext zsCtx; // per-call key/nullifier memo (rpchushwallet.h) uint64_t t = GetTime(); //Reverse Iterate thru transactions - for (map::reverse_iterator it = orderedTxs.rbegin(); it != orderedTxs.rend(); ++it) { - const CWalletTx& wtx = (*it).second; + for (std::vector::reverse_iterator it = orderedTxs.rbegin(); it != orderedTxs.rend(); ++it) { + const CWalletTx& wtx = *(it->second); if (!CheckFinalTx(wtx)) continue; @@ -3731,21 +3765,21 @@ UniValue z_listsentbyaddress(const UniValue& params, bool fHelp,const CPubKey&) if (nFilterType == 2 && wtx.GetDepthInMainChain() > nFilter) continue; - zsWalletTxJSON(wtx, ret, "*", false, 0); + zsWalletTxJSON(wtx, ret, "*", false, fSendsOnly ? 3 : 0, &zsCtx); if (ret.size() >= nCount) break; } - vector arrTmp = ret.getValues(); - - std::reverse(arrTmp.begin(), arrTmp.end()); // Return oldest to newest - - ret.clear(); - ret.setArray(); - ret.push_backV(arrTmp); - - return ret; + // Return oldest to newest (ret was filled newest first): one copy of each entry instead of + // getValues() + std::reverse + push_backV + UniValue oldestFirst(UniValue::VARR); + { + const std::vector& newestFirst = ret.getValues(); + for (std::vector::const_reverse_iterator rit = newestFirst.rbegin(); rit != newestFirst.rend(); ++rit) + oldestFirst.push_back(*rit); + } + return oldestFirst; } UniValue z_getstats(const UniValue& params, bool fHelp, const CPubKey& mypk)