Skip to content

Commit 4ca7c1e

Browse files
committed
Merge #9991: listreceivedbyaddress Filter Address
f087613 Add tests of listreceivedbyaddress address filtering (Jeremy Rubin) 8ee0812 Add address filtering to listreceivedbyaddress (Jeremy Rubin) Pull request description: Supersede bitcoin/bitcoin#9503 created by @JeremyRubin , I will maintain it. Tree-SHA512: 2accaed493b7e1c2eb5cb5270180f100f8c718b6585b9574f294191c318dc622a79e42ac185300f291f82d3b2a6f1c00850b6b17e4ff2dbab94d71df695acbfe
2 parents 3fa24bb + f087613 commit 4ca7c1e

File tree

3 files changed

+76
-12
lines changed

3 files changed

+76
-12
lines changed

src/rpc/client.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ static const CRPCConvertParam vRPCConvertParams[] =
4343
{ "listreceivedbyaddress", 0, "minconf" },
4444
{ "listreceivedbyaddress", 1, "include_empty" },
4545
{ "listreceivedbyaddress", 2, "include_watchonly" },
46+
{ "listreceivedbyaddress", 3, "address_filter" },
4647
{ "listreceivedbyaccount", 0, "minconf" },
4748
{ "listreceivedbyaccount", 1, "include_empty" },
4849
{ "listreceivedbyaccount", 2, "include_watchonly" },

src/wallet/rpcwallet.cpp

Lines changed: 38 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1403,6 +1403,16 @@ UniValue ListReceived(CWallet * const pwallet, const UniValue& params, bool fByA
14031403
if(params[2].get_bool())
14041404
filter = filter | ISMINE_WATCH_ONLY;
14051405

1406+
bool has_filtered_address = false;
1407+
CTxDestination filtered_address = CNoDestination();
1408+
if (!fByAccounts && params.size() > 3) {
1409+
if (!IsValidDestinationString(params[3].get_str())) {
1410+
throw JSONRPCError(RPC_WALLET_ERROR, "address_filter parameter was invalid");
1411+
}
1412+
filtered_address = DecodeDestination(params[3].get_str());
1413+
has_filtered_address = true;
1414+
}
1415+
14061416
// Tally
14071417
std::map<CTxDestination, tallyitem> mapTally;
14081418
for (const std::pair<uint256, CWalletTx>& pairWtx : pwallet->mapWallet) {
@@ -1421,6 +1431,10 @@ UniValue ListReceived(CWallet * const pwallet, const UniValue& params, bool fByA
14211431
if (!ExtractDestination(txout.scriptPubKey, address))
14221432
continue;
14231433

1434+
if (has_filtered_address && !(filtered_address == address)) {
1435+
continue;
1436+
}
1437+
14241438
isminefilter mine = IsMine(*pwallet, address);
14251439
if(!(mine & filter))
14261440
continue;
@@ -1437,10 +1451,24 @@ UniValue ListReceived(CWallet * const pwallet, const UniValue& params, bool fByA
14371451
// Reply
14381452
UniValue ret(UniValue::VARR);
14391453
std::map<std::string, tallyitem> mapAccountTally;
1440-
for (const std::pair<CTxDestination, CAddressBookData>& item : pwallet->mapAddressBook) {
1441-
const CTxDestination& dest = item.first;
1442-
const std::string& strAccount = item.second.name;
1443-
std::map<CTxDestination, tallyitem>::iterator it = mapTally.find(dest);
1454+
1455+
// Create mapAddressBook iterator
1456+
// If we aren't filtering, go from begin() to end()
1457+
auto start = pwallet->mapAddressBook.begin();
1458+
auto end = pwallet->mapAddressBook.end();
1459+
// If we are filtering, find() the applicable entry
1460+
if (has_filtered_address) {
1461+
start = pwallet->mapAddressBook.find(filtered_address);
1462+
if (start != end) {
1463+
end = std::next(start);
1464+
}
1465+
}
1466+
1467+
for (auto item_it = start; item_it != end; ++item_it)
1468+
{
1469+
const CTxDestination& address = item_it->first;
1470+
const std::string& strAccount = item_it->second.name;
1471+
auto it = mapTally.find(address);
14441472
if (it == mapTally.end() && !fIncludeEmpty)
14451473
continue;
14461474

@@ -1466,7 +1494,7 @@ UniValue ListReceived(CWallet * const pwallet, const UniValue& params, bool fByA
14661494
UniValue obj(UniValue::VOBJ);
14671495
if(fIsWatchonly)
14681496
obj.pushKV("involvesWatchonly", true);
1469-
obj.pushKV("address", EncodeDestination(dest));
1497+
obj.pushKV("address", EncodeDestination(address));
14701498
obj.pushKV("account", strAccount);
14711499
obj.pushKV("amount", ValueFromAmount(nAmount));
14721500
obj.pushKV("confirmations", (nConf == std::numeric_limits<int>::max() ? 0 : nConf));
@@ -1511,15 +1539,15 @@ UniValue listreceivedbyaddress(const JSONRPCRequest& request)
15111539
return NullUniValue;
15121540
}
15131541

1514-
if (request.fHelp || request.params.size() > 3)
1542+
if (request.fHelp || request.params.size() > 4)
15151543
throw std::runtime_error(
1516-
"listreceivedbyaddress ( minconf include_empty include_watchonly)\n"
1544+
"listreceivedbyaddress ( minconf include_empty include_watchonly address_filter )\n"
15171545
"\nList balances by receiving address.\n"
15181546
"\nArguments:\n"
15191547
"1. minconf (numeric, optional, default=1) The minimum number of confirmations before payments are included.\n"
15201548
"2. include_empty (bool, optional, default=false) Whether to include addresses that haven't received any payments.\n"
15211549
"3. include_watchonly (bool, optional, default=false) Whether to include watch-only addresses (see 'importaddress').\n"
1522-
1550+
"4. address_filter (string, optional) If present, only return information on this address.\n"
15231551
"\nResult:\n"
15241552
"[\n"
15251553
" {\n"
@@ -1541,6 +1569,7 @@ UniValue listreceivedbyaddress(const JSONRPCRequest& request)
15411569
+ HelpExampleCli("listreceivedbyaddress", "")
15421570
+ HelpExampleCli("listreceivedbyaddress", "6 true")
15431571
+ HelpExampleRpc("listreceivedbyaddress", "6, true, true")
1572+
+ HelpExampleRpc("listreceivedbyaddress", "6, true, true, \"1M72Sfpbz1BPpXFHz9m3CdqATR44Jvaydd\"")
15441573
);
15451574

15461575
ObserveSafeMode();
@@ -3837,7 +3866,7 @@ static const CRPCCommand commands[] =
38373866
{ "wallet", "listaddressgroupings", &listaddressgroupings, {} },
38383867
{ "wallet", "listlockunspent", &listlockunspent, {} },
38393868
{ "wallet", "listreceivedbyaccount", &listreceivedbyaccount, {"minconf","include_empty","include_watchonly"} },
3840-
{ "wallet", "listreceivedbyaddress", &listreceivedbyaddress, {"minconf","include_empty","include_watchonly"} },
3869+
{ "wallet", "listreceivedbyaddress", &listreceivedbyaddress, {"minconf","include_empty","include_watchonly","address_filter"} },
38413870
{ "wallet", "listsinceblock", &listsinceblock, {"blockhash","target_confirmations","include_watchonly","include_removed"} },
38423871
{ "wallet", "listtransactions", &listtransactions, {"account","count","skip","include_watchonly"} },
38433872
{ "wallet", "listunspent", &listunspent, {"minconf","maxconf","addresses","include_unsafe","query_options"} },

test/functional/wallet_listreceivedby.py

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,10 +45,44 @@ def run_test(self):
4545
assert_array_result(self.nodes[1].listreceivedbyaddress(11), {"address": addr}, {}, True)
4646

4747
# Empty Tx
48-
addr = self.nodes[1].getnewaddress()
48+
empty_addr = self.nodes[1].getnewaddress()
4949
assert_array_result(self.nodes[1].listreceivedbyaddress(0, True),
50-
{"address": addr},
51-
{"address": addr, "account": "", "amount": 0, "confirmations": 0, "txids": []})
50+
{"address": empty_addr},
51+
{"address": empty_addr, "account": "", "amount": 0, "confirmations": 0, "txids": []})
52+
53+
#Test Address filtering
54+
#Only on addr
55+
expected = {"address":addr, "account":"", "amount":Decimal("0.1"), "confirmations":10, "txids":[txid,]}
56+
res = self.nodes[1].listreceivedbyaddress(minconf=0, include_empty=True, include_watchonly=True, address_filter=addr)
57+
assert_array_result(res, {"address":addr}, expected)
58+
assert_equal(len(res), 1)
59+
#Error on invalid address
60+
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")
61+
#Another address receive money
62+
res = self.nodes[1].listreceivedbyaddress(0, True, True)
63+
assert_equal(len(res), 2) #Right now 2 entries
64+
other_addr = self.nodes[1].getnewaddress()
65+
txid2 = self.nodes[0].sendtoaddress(other_addr, 0.1)
66+
self.nodes[0].generate(1)
67+
self.sync_all()
68+
#Same test as above should still pass
69+
expected = {"address":addr, "account":"", "amount":Decimal("0.1"), "confirmations":11, "txids":[txid,]}
70+
res = self.nodes[1].listreceivedbyaddress(0, True, True, addr)
71+
assert_array_result(res, {"address":addr}, expected)
72+
assert_equal(len(res), 1)
73+
#Same test as above but with other_addr should still pass
74+
expected = {"address":other_addr, "account":"", "amount":Decimal("0.1"), "confirmations":1, "txids":[txid2,]}
75+
res = self.nodes[1].listreceivedbyaddress(0, True, True, other_addr)
76+
assert_array_result(res, {"address":other_addr}, expected)
77+
assert_equal(len(res), 1)
78+
#Should be two entries though without filter
79+
res = self.nodes[1].listreceivedbyaddress(0, True, True)
80+
assert_equal(len(res), 3) #Became 3 entries
81+
82+
#Not on random addr
83+
other_addr = self.nodes[0].getnewaddress() # note on node[0]! just a random addr
84+
res = self.nodes[1].listreceivedbyaddress(0, True, True, other_addr)
85+
assert_equal(len(res), 0)
5286

5387
self.log.info("getreceivedbyaddress Test")
5488

0 commit comments

Comments
 (0)