diff options
Diffstat (limited to 'src/wallet/rpc/transactions.cpp')
-rw-r--r-- | src/wallet/rpc/transactions.cpp | 153 |
1 files changed, 70 insertions, 83 deletions
diff --git a/src/wallet/rpc/transactions.cpp b/src/wallet/rpc/transactions.cpp index eef2c13ee1..0e13e4756b 100644 --- a/src/wallet/rpc/transactions.cpp +++ b/src/wallet/rpc/transactions.cpp @@ -15,6 +15,7 @@ using interfaces::FoundBlock; namespace wallet { static void WalletTxToJSON(const CWallet& wallet, const CWalletTx& wtx, UniValue& entry) + EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet) { interfaces::Chain& chain = wallet.chain(); int confirms = wallet.GetTxDepthInMainChain(wtx); @@ -34,6 +35,7 @@ static void WalletTxToJSON(const CWallet& wallet, const CWalletTx& wtx, UniValue } uint256 hash = wtx.GetHash(); entry.pushKV("txid", hash.GetHex()); + entry.pushKV("wtxid", wtx.GetWitnessHash().GetHex()); UniValue conflicts(UniValue::VARR); for (const uint256& conflict : wallet.GetTxConflicts(wtx)) conflicts.push_back(conflict.GetHex()); @@ -62,9 +64,7 @@ struct tallyitem int nConf{std::numeric_limits<int>::max()}; std::vector<uint256> txids; bool fIsWatchonly{false}; - tallyitem() - { - } + tallyitem() = default; }; static UniValue ListReceived(const CWallet& wallet, const UniValue& params, const bool by_label, const bool include_immature_coinbase) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet) @@ -72,7 +72,7 @@ static UniValue ListReceived(const CWallet& wallet, const UniValue& params, cons // Minimum confirmations int nMinDepth = 1; if (!params[0].isNull()) - nMinDepth = params[0].get_int(); + nMinDepth = params[0].getInt<int>(); // Whether to include empty labels bool fIncludeEmpty = false; @@ -85,22 +85,12 @@ static UniValue ListReceived(const CWallet& wallet, const UniValue& params, cons filter |= ISMINE_WATCH_ONLY; } - bool has_filtered_address = false; - CTxDestination filtered_address = CNoDestination(); + std::optional<CTxDestination> filtered_address{std::nullopt}; if (!by_label && !params[3].isNull() && !params[3].get_str().empty()) { if (!IsValidDestinationString(params[3].get_str())) { throw JSONRPCError(RPC_WALLET_ERROR, "address_filter parameter was invalid"); } filtered_address = DecodeDestination(params[3].get_str()); - has_filtered_address = true; - } - - // Excluding coinbase outputs is deprecated - // It can be enabled by setting deprecatedrpc=exclude_coinbase - const bool include_coinbase{!wallet.chain().rpcEnableDeprecated("exclude_coinbase")}; - - if (include_immature_coinbase && !include_coinbase) { - throw JSONRPCError(RPC_INVALID_PARAMETER, "include_immature_coinbase is incompatible with deprecated exclude_coinbase"); } // Tally @@ -113,24 +103,22 @@ static UniValue ListReceived(const CWallet& wallet, const UniValue& params, cons continue; // Coinbase with less than 1 confirmation is no longer in the main chain - if ((wtx.IsCoinBase() && (nDepth < 1 || !include_coinbase)) - || (wallet.IsTxImmatureCoinBase(wtx) && !include_immature_coinbase)) - { + if ((wtx.IsCoinBase() && (nDepth < 1)) + || (wallet.IsTxImmatureCoinBase(wtx) && !include_immature_coinbase)) { continue; } - for (const CTxOut& txout : wtx.tx->vout) - { + for (const CTxOut& txout : wtx.tx->vout) { CTxDestination address; if (!ExtractDestination(txout.scriptPubKey, address)) continue; - if (has_filtered_address && !(filtered_address == address)) { + if (filtered_address && !(filtered_address == address)) { continue; } isminefilter mine = wallet.IsMine(address); - if(!(mine & filter)) + if (!(mine & filter)) continue; tallyitem& item = mapTally[address]; @@ -146,70 +134,55 @@ static UniValue ListReceived(const CWallet& wallet, const UniValue& params, cons UniValue ret(UniValue::VARR); std::map<std::string, tallyitem> label_tally; - // Create m_address_book iterator - // If we aren't filtering, go from begin() to end() - auto start = wallet.m_address_book.begin(); - auto end = wallet.m_address_book.end(); - // If we are filtering, find() the applicable entry - if (has_filtered_address) { - start = wallet.m_address_book.find(filtered_address); - if (start != end) { - end = std::next(start); - } - } + const auto& func = [&](const CTxDestination& address, const std::string& label, const std::string& purpose, bool is_change) { + if (is_change) return; // no change addresses - for (auto item_it = start; item_it != end; ++item_it) - { - if (item_it->second.IsChange()) continue; - const CTxDestination& address = item_it->first; - const std::string& label = item_it->second.GetLabel(); auto it = mapTally.find(address); if (it == mapTally.end() && !fIncludeEmpty) - continue; + return; CAmount nAmount = 0; int nConf = std::numeric_limits<int>::max(); bool fIsWatchonly = false; - if (it != mapTally.end()) - { + if (it != mapTally.end()) { nAmount = (*it).second.nAmount; nConf = (*it).second.nConf; fIsWatchonly = (*it).second.fIsWatchonly; } - if (by_label) - { + if (by_label) { tallyitem& _item = label_tally[label]; _item.nAmount += nAmount; _item.nConf = std::min(_item.nConf, nConf); _item.fIsWatchonly = fIsWatchonly; - } - else - { + } else { UniValue obj(UniValue::VOBJ); - if(fIsWatchonly) - obj.pushKV("involvesWatchonly", true); + if (fIsWatchonly) obj.pushKV("involvesWatchonly", true); obj.pushKV("address", EncodeDestination(address)); obj.pushKV("amount", ValueFromAmount(nAmount)); obj.pushKV("confirmations", (nConf == std::numeric_limits<int>::max() ? 0 : nConf)); obj.pushKV("label", label); UniValue transactions(UniValue::VARR); - if (it != mapTally.end()) - { - for (const uint256& _item : (*it).second.txids) - { + if (it != mapTally.end()) { + for (const uint256& _item : (*it).second.txids) { transactions.push_back(_item.GetHex()); } } obj.pushKV("txids", transactions); ret.push_back(obj); } + }; + + if (filtered_address) { + const auto& entry = wallet.FindAddressBookEntry(*filtered_address, /*allow_change=*/false); + if (entry) func(*filtered_address, entry->GetLabel(), entry->purpose, /*is_change=*/false); + } else { + // No filtered addr, walk-through the addressbook entry + wallet.ForEachAddrBookEntry(func); } - if (by_label) - { - for (const auto& entry : label_tally) - { + if (by_label) { + for (const auto& entry : label_tally) { CAmount nAmount = entry.second.nAmount; int nConf = entry.second.nConf; UniValue obj(UniValue::VOBJ); @@ -263,7 +236,7 @@ RPCHelpMan listreceivedbyaddress() [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { const std::shared_ptr<const CWallet> pwallet = GetWalletForJSONRPCRequest(request); - if (!pwallet) return NullUniValue; + if (!pwallet) return UniValue::VNULL; // Make sure the results are valid at least up to the most recent block // the user could have gotten from another RPC command prior to now @@ -308,7 +281,7 @@ RPCHelpMan listreceivedbylabel() [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { const std::shared_ptr<const CWallet> pwallet = GetWalletForJSONRPCRequest(request); - if (!pwallet) return NullUniValue; + if (!pwallet) return UniValue::VNULL; // Make sure the results are valid at least up to the most recent block // the user could have gotten from another RPC command prior to now @@ -337,17 +310,21 @@ static void MaybePushAddress(UniValue & entry, const CTxDestination &dest) * @param wtx The wallet transaction. * @param nMinDepth The minimum confirmation depth. * @param fLong Whether to include the JSON version of the transaction. - * @param ret The UniValue into which the result is stored. + * @param ret The vector into which the result is stored. * @param filter_ismine The "is mine" filter flags. * @param filter_label Optional label string to filter incoming transactions. */ -static void ListTransactions(const CWallet& wallet, const CWalletTx& wtx, int nMinDepth, bool fLong, UniValue& ret, const isminefilter& filter_ismine, const std::string* filter_label) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet) +template <class Vec> +static void ListTransactions(const CWallet& wallet, const CWalletTx& wtx, int nMinDepth, bool fLong, + Vec& ret, const isminefilter& filter_ismine, const std::string* filter_label, + bool include_change = false) + EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet) { CAmount nFee; std::list<COutputEntry> listReceived; std::list<COutputEntry> listSent; - CachedTxGetAmounts(wallet, wtx, listReceived, listSent, nFee, filter_ismine); + CachedTxGetAmounts(wallet, wtx, listReceived, listSent, nFee, filter_ismine, include_change); bool involvesWatchonly = CachedTxIsFromMe(wallet, wtx, ISMINE_WATCH_ONLY); @@ -393,6 +370,7 @@ static void ListTransactions(const CWallet& wallet, const CWalletTx& wtx, int nM entry.pushKV("involvesWatchonly", true); } MaybePushAddress(entry, r.destination); + PushParentDescriptors(wallet, wtx.tx->vout.at(r.vout).scriptPubKey, entry); if (wtx.IsCoinBase()) { if (wallet.GetTxDepthInMainChain(wtx) < 1) @@ -431,6 +409,7 @@ static const std::vector<RPCResult> TransactionDescriptionString() {RPCResult::Type::NUM, "blockindex", /*optional=*/true, "The index of the transaction in the block that includes it."}, {RPCResult::Type::NUM_TIME, "blocktime", /*optional=*/true, "The block time expressed in " + UNIX_EPOCH_TIME + "."}, {RPCResult::Type::STR_HEX, "txid", "The transaction id."}, + {RPCResult::Type::STR_HEX, "wtxid", "The hash of serialized transaction, including witness data."}, {RPCResult::Type::ARR, "walletconflicts", "Conflicting transaction ids.", { {RPCResult::Type::STR_HEX, "txid", "The transaction id."}, @@ -442,8 +421,12 @@ static const std::vector<RPCResult> TransactionDescriptionString() {RPCResult::Type::NUM_TIME, "time", "The transaction time expressed in " + UNIX_EPOCH_TIME + "."}, {RPCResult::Type::NUM_TIME, "timereceived", "The time received expressed in " + UNIX_EPOCH_TIME + "."}, {RPCResult::Type::STR, "comment", /*optional=*/true, "If a comment is associated with the transaction, only present if not empty."}, - {RPCResult::Type::STR, "bip125-replaceable", "(\"yes|no|unknown\") Whether this transaction could be replaced due to BIP125 (replace-by-fee);\n" - "may be unknown for unconfirmed transactions not in the mempool."}}; + {RPCResult::Type::STR, "bip125-replaceable", "(\"yes|no|unknown\") Whether this transaction signals BIP125 replaceability or has an unconfirmed ancestor signaling BIP125 replaceability.\n" + "May be unknown for unconfirmed transactions not in the mempool because their unconfirmed ancestors are unknown."}, + {RPCResult::Type::ARR, "parent_descs", /*optional=*/true, "Only if 'category' is 'received'. List of parent descriptors for the scriptPubKey of this coin.", { + {RPCResult::Type::STR, "desc", "The descriptor string."}, + }}, + }; } RPCHelpMan listtransactions() @@ -496,7 +479,7 @@ RPCHelpMan listtransactions() [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { const std::shared_ptr<const CWallet> pwallet = GetWalletForJSONRPCRequest(request); - if (!pwallet) return NullUniValue; + if (!pwallet) return UniValue::VNULL; // Make sure the results are valid at least up to the most recent block // the user could have gotten from another RPC command prior to now @@ -511,10 +494,10 @@ RPCHelpMan listtransactions() } int nCount = 10; if (!request.params[1].isNull()) - nCount = request.params[1].get_int(); + nCount = request.params[1].getInt<int>(); int nFrom = 0; if (!request.params[2].isNull()) - nFrom = request.params[2].get_int(); + nFrom = request.params[2].getInt<int>(); isminefilter filter = ISMINE_SPENDABLE; if (ParseIncludeWatchonly(request.params[3], *pwallet)) { @@ -526,8 +509,7 @@ RPCHelpMan listtransactions() if (nFrom < 0) throw JSONRPCError(RPC_INVALID_PARAMETER, "Negative from"); - UniValue ret(UniValue::VARR); - + std::vector<UniValue> ret; { LOCK(pwallet->cs_wallet); @@ -549,9 +531,9 @@ RPCHelpMan listtransactions() if ((nFrom + nCount) > (int)ret.size()) nCount = ret.size() - nFrom; - const std::vector<UniValue>& txs = ret.getValues(); + auto txs_rev_it{std::make_move_iterator(ret.rend())}; UniValue result{UniValue::VARR}; - result.push_backV({ txs.rend() - nFrom - nCount, txs.rend() - nFrom }); // Return oldest to newest + result.push_backV(txs_rev_it - nFrom - nCount, txs_rev_it - nFrom); // Return oldest to newest return result; }, }; @@ -569,6 +551,7 @@ RPCHelpMan listsinceblock() {"include_watchonly", RPCArg::Type::BOOL, RPCArg::DefaultHint{"true for watch-only wallets, otherwise false"}, "Include transactions to watch-only addresses (see 'importaddress')"}, {"include_removed", RPCArg::Type::BOOL, RPCArg::Default{true}, "Show transactions that were removed due to a reorg in the \"removed\" array\n" "(not guaranteed to work on pruned nodes)"}, + {"include_change", RPCArg::Type::BOOL, RPCArg::Default{false}, "Also add entries for change outputs.\n"}, }, RPCResult{ RPCResult::Type::OBJ, "", "", @@ -612,7 +595,7 @@ RPCHelpMan listsinceblock() [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { const std::shared_ptr<const CWallet> pwallet = GetWalletForJSONRPCRequest(request); - if (!pwallet) return NullUniValue; + if (!pwallet) return UniValue::VNULL; const CWallet& wallet = *pwallet; // Make sure the results are valid at least up to the most recent block @@ -637,7 +620,7 @@ RPCHelpMan listsinceblock() } if (!request.params[1].isNull()) { - target_confirms = request.params[1].get_int(); + target_confirms = request.params[1].getInt<int>(); if (target_confirms < 1) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter"); @@ -649,6 +632,7 @@ RPCHelpMan listsinceblock() } bool include_removed = (request.params[3].isNull() || request.params[3].get_bool()); + bool include_change = (!request.params[4].isNull() && request.params[4].get_bool()); int depth = height ? wallet.GetLastBlockHeight() + 1 - *height : -1; @@ -658,7 +642,7 @@ RPCHelpMan listsinceblock() const CWalletTx& tx = pairWtx.second; if (depth == -1 || abs(wallet.GetTxDepthInMainChain(tx)) < depth) { - ListTransactions(wallet, tx, 0, true, transactions, filter, nullptr /* filter_label */); + ListTransactions(wallet, tx, 0, true, transactions, filter, nullptr /* filter_label */, /*include_change=*/include_change); } } @@ -675,7 +659,7 @@ RPCHelpMan listsinceblock() if (it != wallet.mapWallet.end()) { // We want all transactions regardless of confirmation count to appear here, // even negative confirmation ones, hence the big negative. - ListTransactions(wallet, it->second, -100000000, true, removed, filter, nullptr /* filter_label */); + ListTransactions(wallet, it->second, -100000000, true, removed, filter, nullptr /* filter_label */, /*include_change=*/include_change); } } blockId = block.hashPrevBlock; @@ -735,6 +719,9 @@ RPCHelpMan gettransaction() "'send' category of transactions."}, {RPCResult::Type::BOOL, "abandoned", /*optional=*/true, "'true' if the transaction has been abandoned (inputs are respendable). Only available for the \n" "'send' category of transactions."}, + {RPCResult::Type::ARR, "parent_descs", /*optional=*/true, "Only if 'category' is 'received'. List of parent descriptors for the scriptPubKey of this coin.", { + {RPCResult::Type::STR, "desc", "The descriptor string."}, + }}, }}, }}, {RPCResult::Type::STR_HEX, "hex", "Raw data for transaction"}, @@ -753,7 +740,7 @@ RPCHelpMan gettransaction() [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { const std::shared_ptr<const CWallet> pwallet = GetWalletForJSONRPCRequest(request); - if (!pwallet) return NullUniValue; + if (!pwallet) return UniValue::VNULL; // Make sure the results are valid at least up to the most recent block // the user could have gotten from another RPC command prior to now @@ -798,7 +785,7 @@ RPCHelpMan gettransaction() if (verbose) { UniValue decoded(UniValue::VOBJ); - TxToUniv(*wtx.tx, uint256(), decoded, false); + TxToUniv(*wtx.tx, /*block_hash=*/uint256(), /*entry=*/decoded, /*include_hex=*/false); entry.pushKV("decoded", decoded); } @@ -826,7 +813,7 @@ RPCHelpMan abandontransaction() [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request); - if (!pwallet) return NullUniValue; + if (!pwallet) return UniValue::VNULL; // Make sure the results are valid at least up to the most recent block // the user could have gotten from another RPC command prior to now @@ -843,7 +830,7 @@ RPCHelpMan abandontransaction() throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Transaction not eligible for abandonment"); } - return NullUniValue; + return UniValue::VNULL; }, }; } @@ -871,7 +858,7 @@ RPCHelpMan rescanblockchain() [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request); - if (!pwallet) return NullUniValue; + if (!pwallet) return UniValue::VNULL; CWallet& wallet{*pwallet}; // Make sure the results are valid at least up to the most recent block @@ -891,14 +878,14 @@ RPCHelpMan rescanblockchain() int tip_height = pwallet->GetLastBlockHeight(); if (!request.params[0].isNull()) { - start_height = request.params[0].get_int(); + start_height = request.params[0].getInt<int>(); if (start_height < 0 || start_height > tip_height) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid start_height"); } } if (!request.params[1].isNull()) { - stop_height = request.params[1].get_int(); + stop_height = request.params[1].getInt<int>(); if (*stop_height < 0 || *stop_height > tip_height) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid stop_height"); } else if (*stop_height < start_height) { @@ -915,7 +902,7 @@ RPCHelpMan rescanblockchain() } CWallet::ScanResult result = - pwallet->ScanForWalletTransactions(start_block, start_height, stop_height, reserver, true /* fUpdate */); + pwallet->ScanForWalletTransactions(start_block, start_height, stop_height, reserver, /*fUpdate=*/true, /*save_progress=*/false); switch (result.status) { case CWallet::ScanResult::SUCCESS: break; @@ -951,7 +938,7 @@ RPCHelpMan abortrescan() [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request); - if (!pwallet) return NullUniValue; + if (!pwallet) return UniValue::VNULL; if (!pwallet->IsScanning() || pwallet->IsAbortingRescan()) return false; pwallet->AbortRescan(); |