diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -1470,27 +1470,30 @@ /** * List transactions based on the given criteria. * - * @param pwallet The wallet. - * @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 filter The "is mine" filter bool. + * @param pwallet The wallet. + * @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 filter_ismine The "is mine" filter flags. + * @param filter_label Optional label string to filter incoming transactions. */ static void ListTransactions(interfaces::Chain::Lock &locked_chain, CWallet *const pwallet, const CWalletTx &wtx, int nMinDepth, bool fLong, UniValue &ret, - const isminefilter &filter) { + const isminefilter &filter_ismine, + const std::string *filter_label) { Amount nFee; std::list listReceived; std::list listSent; - wtx.GetAmounts(listReceived, listSent, nFee, filter); + wtx.GetAmounts(listReceived, listSent, nFee, filter_ismine); bool involvesWatchonly = wtx.IsFromMe(ISMINE_WATCH_ONLY); // Sent - if (!listSent.empty() || nFee != Amount::zero()) { + if (!filter_label) { for (const COutputEntry &s : listSent) { UniValue entry(UniValue::VOBJ); if (involvesWatchonly || @@ -1522,6 +1525,9 @@ if (pwallet->mapAddressBook.count(r.destination)) { label = pwallet->mapAddressBook[r.destination].name; } + if (filter_label && label != *filter_label) { + continue; + } UniValue entry(UniValue::VOBJ); if (involvesWatchonly || (::IsMine(*pwallet, r.destination) & ISMINE_WATCH_ONLY)) { @@ -1562,12 +1568,16 @@ if (request.fHelp || request.params.size() > 4) { throw std::runtime_error( - "listtransactions ( \"dummy\" count skip include_watchonly)\n" + "listtransactions ( \"label\" count skip include_watchonly )\n" + "\nIf a label name is provided, this will return only incoming " + "transactions paying to addresses with the specified label.\n" "\nReturns up to 'count' most recent transactions skipping the " "first 'from' transactions.\n" "\nArguments:\n" - "1. \"dummy\" (string, optional) If set, should be \"*\" for " - "backwards compatibility.\n" + "1. \"label\" (string, optional) If set, should be a valid " + "label name to return only incoming transactions with the " + "specified label, or \"*\" to disable filtering and return all " + "transactions.\n" "2. count (numeric, optional, default=10) The number of " "transactions to return\n" "3. skip (numeric, optional, default=0) The number of " @@ -1639,9 +1649,14 @@ auto locked_chain = pwallet->chain().lock(); LOCK(pwallet->cs_wallet); + const std::string *filter_label = nullptr; if (!request.params[0].isNull() && request.params[0].get_str() != "*") { - throw JSONRPCError(RPC_INVALID_PARAMETER, - "Dummy value must be set to \"*\""); + filter_label = &request.params[0].get_str(); + if (filter_label->empty()) { + throw JSONRPCError( + RPC_INVALID_PARAMETER, + "Label argument must be a valid label name or \"*\"."); + } } int nCount = 10; if (!request.params[1].isNull()) { @@ -1672,7 +1687,8 @@ for (CWallet::TxItems::const_reverse_iterator it = txOrdered.rbegin(); it != txOrdered.rend(); ++it) { CWalletTx *const pwtx = (*it).second; - ListTransactions(*locked_chain, pwallet, *pwtx, 0, true, ret, filter); + ListTransactions(*locked_chain, pwallet, *pwtx, 0, true, ret, filter, + filter_label); if ((int)ret.size() >= (nCount + nFrom)) { break; } @@ -1877,7 +1893,7 @@ if (depth == -1 || tx.GetDepthInMainChain(*locked_chain) < depth) { ListTransactions(*locked_chain, pwallet, tx, 0, true, transactions, - filter); + filter, nullptr /* filter_label */); } } @@ -1899,7 +1915,8 @@ // appear here, even negative confirmation ones, hence the big // negative. ListTransactions(*locked_chain, pwallet, it->second, -100000000, - true, removed, filter); + true, removed, filter, + nullptr /* filter_label */); } } paltindex = paltindex->pprev; @@ -2046,7 +2063,8 @@ WalletTxToJSON(pwallet->chain(), *locked_chain, wtx, entry); UniValue details(UniValue::VARR); - ListTransactions(*locked_chain, pwallet, wtx, 0, false, details, filter); + ListTransactions(*locked_chain, pwallet, wtx, 0, false, details, filter, + nullptr /* filter_label */); entry.pushKV("details", details); std::string strHex = EncodeHexTx(*wtx.tx, RPCSerializationFlags()); @@ -4686,7 +4704,7 @@ { "wallet", "listreceivedbyaddress", listreceivedbyaddress, {"minconf","include_empty","include_watchonly","address_filter"} }, { "wallet", "listreceivedbylabel", listreceivedbylabel, {"minconf","include_empty","include_watchonly"} }, { "wallet", "listsinceblock", listsinceblock, {"blockhash","target_confirmations","include_watchonly","include_removed"} }, - { "wallet", "listtransactions", listtransactions, {"dummy","count","skip","include_watchonly"} }, + { "wallet", "listtransactions", listtransactions, {"label|dummy","count","skip","include_watchonly"} }, { "wallet", "listunspent", listunspent, {"minconf","maxconf","addresses","include_unsafe","query_options"} }, { "wallet", "listwallets", listwallets, {} }, { "wallet", "loadwallet", loadwallet, {"filename"} }, diff --git a/test/functional/wallet_import_rescan.py b/test/functional/wallet_import_rescan.py --- a/test/functional/wallet_import_rescan.py +++ b/test/functional/wallet_import_rescan.py @@ -56,13 +56,22 @@ if self.call == Call.single: if self.data == Data.address: response = self.try_rpc( - self.node.importaddress, address=self.address["address"], rescan=rescan) + self.node.importaddress, + address=self.address["address"], + label=self.label, + rescan=rescan) elif self.data == Data.pub: response = self.try_rpc( - self.node.importpubkey, pubkey=self.address["pubkey"], rescan=rescan) + self.node.importpubkey, + pubkey=self.address["pubkey"], + label=self.label, + rescan=rescan) elif self.data == Data.priv: response = self.try_rpc( - self.node.importprivkey, privkey=self.key, rescan=rescan) + self.node.importprivkey, + privkey=self.key, + label=self.label, + rescan=rescan) assert_equal(response, None) elif self.call == Call.multi: @@ -73,12 +82,17 @@ "timestamp": timestamp + TIMESTAMP_WINDOW + (1 if self.rescan == Rescan.late_timestamp else 0), "pubkeys": [self.address["pubkey"]] if self.data == Data.pub else [], "keys": [self.key] if self.data == Data.priv else [], + "label": self.label, "watchonly": self.data != Data.priv }], {"rescan": self.rescan in (Rescan.yes, Rescan.late_timestamp)}) assert_equal(response, [{"success": True}]) def check(self, txid=None, amount=None, confirmations=None): - """Verify that listreceivedbyaddress returns expected values.""" + """Verify that listtransactions/listreceivedbyaddress return expected values.""" + + txs = self.node.listtransactions( + label=self.label, count=10000, include_watchonly=True) + assert_equal(len(txs), self.expected_txs) addresses = self.node.listreceivedbyaddress( minconf=0, include_watchonly=True, address_filter=self.address['address']) @@ -86,6 +100,16 @@ assert_equal(len(addresses[0]["txids"]), self.expected_txs) if txid is not None: + tx, = [tx for tx in txs if tx["txid"] == txid] + assert_equal(tx["label"], self.label) + assert_equal(tx["address"], self.address["address"]) + assert_equal(tx["amount"], amount) + assert_equal(tx["category"], "receive") + assert_equal(tx["label"], self.label) + assert_equal(tx["txid"], txid) + assert_equal(tx["confirmations"], confirmations) + assert_equal("trusted" not in tx, True) + address, = [ad for ad in addresses if txid in ad["txids"]] assert_equal(address["address"], self.address["address"]) assert_equal(address["amount"], self.expected_balance) @@ -151,8 +175,9 @@ # Create one transaction on node 0 with a unique amount for # each possible type of wallet import RPC. for i, variant in enumerate(IMPORT_VARIANTS): + variant.label = "label {} {}".format(i, variant) variant.address = self.nodes[1].getaddressinfo( - self.nodes[1].getnewaddress()) + self.nodes[1].getnewaddress(variant.label)) variant.key = self.nodes[1].dumpprivkey(variant.address["address"]) variant.initial_amount = 10 - (i + 1) / 4.0 variant.initial_txid = self.nodes[0].sendtoaddress( diff --git a/test/functional/wallet_listtransactions.py b/test/functional/wallet_listtransactions.py --- a/test/functional/wallet_listtransactions.py +++ b/test/functional/wallet_listtransactions.py @@ -88,12 +88,14 @@ txid = self.nodes[1].sendtoaddress(multisig["address"], 0.1) self.nodes[1].generate(1) self.sync_all() - assert not [tx for tx in self.nodes[0].listtransactions( - dummy="*", count=100, skip=0, include_watchonly=False) if "label" in tx and tx["label"] == "watchonly"] - txs = [tx for tx in self.nodes[0].listtransactions( - dummy="*", count=100, skip=0, include_watchonly=True) if "label" in tx and tx['label'] == 'watchonly'] - assert_array_result( - txs, {"category": "receive", "amount": Decimal("0.1")}, {"txid": txid}) + assert len( + self.nodes[0].listtransactions( + label="watchonly", + count=100, + include_watchonly=False)) == 0 + assert_array_result(self.nodes[0].listtransactions(label="watchonly", count=100, include_watchonly=True), + {"category": "receive", "amount": Decimal("0.1")}, + {"txid": txid, "label": "watchonly"}) if __name__ == '__main__':