diff --git a/src/rpc/client.cpp b/src/rpc/client.cpp --- a/src/rpc/client.cpp +++ b/src/rpc/client.cpp @@ -44,6 +44,7 @@ {"listreceivedbyaccount", 0, "minconf"}, {"listreceivedbyaccount", 1, "include_empty"}, {"listreceivedbyaccount", 2, "include_watchonly"}, + {"listreceivedbyaddress", 3, "address_filter"}, {"getbalance", 1, "minconf"}, {"getbalance", 2, "include_watchonly"}, {"getblockhash", 0, "height"}, diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -1411,6 +1411,19 @@ filter = filter | ISMINE_WATCH_ONLY; } + bool has_filtered_address = false; + CTxDestination filtered_address = CNoDestination(); + if (!fByAccounts && params.size() > 3) { + if (!IsValidDestinationString(params[3].get_str(), + config.GetChainParams())) { + throw JSONRPCError(RPC_WALLET_ERROR, + "address_filter parameter was invalid"); + } + filtered_address = + DecodeDestination(params[3].get_str(), config.GetChainParams()); + has_filtered_address = true; + } + // Tally std::map mapTally; for (const std::pair &pairWtx : pwallet->mapWallet) { @@ -1434,6 +1447,10 @@ continue; } + if (has_filtered_address && !(filtered_address == address)) { + continue; + } + isminefilter mine = IsMine(*pwallet, address); if (!(mine & filter)) { continue; @@ -1452,11 +1469,24 @@ // Reply UniValue ret(UniValue::VARR); std::map mapAccountTally; - for (const std::pair &item : - pwallet->mapAddressBook) { - const CTxDestination &dest = item.first; - const std::string &strAccount = item.second.name; - std::map::iterator it = mapTally.find(dest); + + // Create mapAddressBook iterator + // If we aren't filtering, go from begin() to end() + auto start = pwallet->mapAddressBook.begin(); + auto end = pwallet->mapAddressBook.end(); + // If we are filtering, find() the applicable entry + if (has_filtered_address) { + start = pwallet->mapAddressBook.find(filtered_address); + if (start != end) { + end = std::next(start); + } + } + + for (auto item_it = start; item_it != end; ++item_it) { + const CTxDestination &address = item_it->first; + const std::string &strAccount = item_it->second.name; + std::map::iterator it = + mapTally.find(address); if (it == mapTally.end() && !fIncludeEmpty) { continue; } @@ -1480,7 +1510,7 @@ if (fIsWatchonly) { obj.push_back(Pair("involvesWatchonly", true)); } - obj.push_back(Pair("address", EncodeDestination(dest))); + obj.push_back(Pair("address", EncodeDestination(address))); obj.push_back(Pair("account", strAccount)); obj.push_back(Pair("amount", ValueFromAmount(nAmount))); obj.push_back( @@ -1529,9 +1559,10 @@ return NullUniValue; } - if (request.fHelp || request.params.size() > 3) { + if (request.fHelp || request.params.size() > 4) { throw std::runtime_error( - "listreceivedbyaddress ( minconf include_empty include_watchonly)\n" + "listreceivedbyaddress ( minconf include_empty include_watchonly " + "address_filter )\n" "\nList balances by receiving address.\n" "\nArguments:\n" "1. minconf (numeric, optional, default=1) The minimum " @@ -1540,7 +1571,8 @@ "include addresses that haven't received any payments.\n" "3. include_watchonly (bool, optional, default=false) Whether to " "include watch-only addresses (see 'importaddress').\n" - + "4. address_filter (string, optional) If present, only return " + "information on this address.\n" "\nResult:\n" "[\n" " {\n" @@ -1570,7 +1602,10 @@ "\nExamples:\n" + HelpExampleCli("listreceivedbyaddress", "") + HelpExampleCli("listreceivedbyaddress", "6 true") + - HelpExampleRpc("listreceivedbyaddress", "6, true, true")); + HelpExampleRpc("listreceivedbyaddress", "6, true, true") + + HelpExampleRpc( + "listreceivedbyaddress", + "6, true, true, \"1M72Sfpbz1BPpXFHz9m3CdqATR44Jvaydd\"")); } ObserveSafeMode(); @@ -3652,7 +3687,7 @@ { "wallet", "listaddressgroupings", listaddressgroupings, {} }, { "wallet", "listlockunspent", listlockunspent, {} }, { "wallet", "listreceivedbyaccount", listreceivedbyaccount, {"minconf","include_empty","include_watchonly"} }, - { "wallet", "listreceivedbyaddress", listreceivedbyaddress, {"minconf","include_empty","include_watchonly"} }, + { "wallet", "listreceivedbyaddress", listreceivedbyaddress, {"minconf","include_empty","include_watchonly","address_filter"} }, { "wallet", "listsinceblock", listsinceblock, {"blockhash","target_confirmations","include_watchonly"} }, { "wallet", "listtransactions", listtransactions, {"account","count","skip","include_watchonly"} }, { "wallet", "listunspent", listunspent, {"minconf","maxconf","addresses","include_unsafe","query_options"} }, diff --git a/test/functional/receivedby.py b/test/functional/receivedby.py --- a/test/functional/receivedby.py +++ b/test/functional/receivedby.py @@ -49,10 +49,50 @@ "address": addr}, {}, True) # Empty Tx - addr = self.nodes[1].getnewaddress() + empty_addr = self.nodes[1].getnewaddress() assert_array_result(self.nodes[1].listreceivedbyaddress(0, True), - {"address": addr}, - {"address": addr, "account": "", "amount": 0, "confirmations": 0, "txids": []}) + {"address": empty_addr}, + {"address": empty_addr, "account": "", "amount": 0, "confirmations": 0, "txids": []}) + + # Test Address filtering + # Only on addr + expected = {"address": addr, "account": "", "amount": Decimal( + "0.1"), "confirmations": 10, "txids": [txid, ]} + res = self.nodes[1].listreceivedbyaddress( + minconf=0, include_empty=True, include_watchonly=True, address_filter=addr) + assert_array_result(res, {"address": addr}, expected) + assert_equal(len(res), 1) + # Error on invalid address + assert_raises_rpc_error(-4, "address_filter parameter was invalid", + self.nodes[1].listreceivedbyaddress, minconf=0, include_empty=True, include_watchonly=True, address_filter="bamboozling") + # Another address receive money + res = self.nodes[1].listreceivedbyaddress(0, True, True) + assert_equal(len(res), 2) # Right now 2 entries + other_addr = self.nodes[1].getnewaddress() + txid2 = self.nodes[0].sendtoaddress(other_addr, 0.1) + self.nodes[0].generate(1) + self.sync_all() + # Same test as above should still pass + expected = {"address": addr, "account": "", "amount": Decimal( + "0.1"), "confirmations": 11, "txids": [txid, ]} + res = self.nodes[1].listreceivedbyaddress(0, True, True, addr) + assert_array_result(res, {"address": addr}, expected) + assert_equal(len(res), 1) + # Same test as above but with other_addr should still pass + expected = {"address": other_addr, "account": "", "amount": Decimal( + "0.1"), "confirmations": 1, "txids": [txid2, ]} + res = self.nodes[1].listreceivedbyaddress(0, True, True, other_addr) + assert_array_result(res, {"address": other_addr}, expected) + assert_equal(len(res), 1) + # Should be two entries though without filter + res = self.nodes[1].listreceivedbyaddress(0, True, True) + assert_equal(len(res), 3) # Became 3 entries + + # Not on random addr + # note on node[0]! just a random addr + other_addr = self.nodes[0].getnewaddress() + res = self.nodes[1].listreceivedbyaddress(0, True, True, other_addr) + assert_equal(len(res), 0) self.log.info("getreceivedbyaddress Test")