Skip to content

Commit

Permalink
Merge bitcoin#9991: listreceivedbyaddress Filter Address
Browse files Browse the repository at this point in the history
f087613 Add tests of listreceivedbyaddress address filtering (Jeremy Rubin)
8ee0812 Add address filtering to listreceivedbyaddress (Jeremy Rubin)

Pull request description:

  Supersede bitcoin#9503 created by @JeremyRubin , I will maintain it.

Tree-SHA512: 2accaed493b7e1c2eb5cb5270180f100f8c718b6585b9574f294191c318dc622a79e42ac185300f291f82d3b2a6f1c00850b6b17e4ff2dbab94d71df695acbfe
  • Loading branch information
laanwj authored and PastaPastaPasta committed Jun 17, 2020
1 parent be6ebaa commit fd97aec
Show file tree
Hide file tree
Showing 3 changed files with 77 additions and 13 deletions.
1 change: 1 addition & 0 deletions src/rpc/client.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ static const CRPCConvertParam vRPCConvertParams[] =
{ "listreceivedbyaddress", 1, "addlocked" },
{ "listreceivedbyaddress", 2, "include_empty" },
{ "listreceivedbyaddress", 3, "include_watchonly" },
{ "listreceivedbyaddress", 4, "address_filter" },
{ "listreceivedbyaccount", 0, "minconf" },
{ "listreceivedbyaccount", 1, "addlocked" },
{ "listreceivedbyaccount", 2, "include_empty" },
Expand Down
49 changes: 39 additions & 10 deletions src/wallet/rpcwallet.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1311,6 +1311,16 @@ UniValue ListReceived(CWallet * const pwallet, const UniValue& params, bool fByA
if(params[3].get_bool())
filter = filter | ISMINE_WATCH_ONLY;

bool has_filtered_address = false;
CTxDestination filtered_address = CNoDestination();
if (!fByAccounts && params.size() > 4) {
if (!IsValidDestinationString(params[4].get_str())) {
throw JSONRPCError(RPC_WALLET_ERROR, "address_filter parameter was invalid");
}
filtered_address = DecodeDestination(params[4].get_str());
has_filtered_address = true;
}

// Tally
std::map<CTxDestination, tallyitem> mapTally;
for (const std::pair<uint256, CWalletTx>& pairWtx : pwallet->mapWallet) {
Expand All @@ -1329,6 +1339,10 @@ UniValue ListReceived(CWallet * const pwallet, const UniValue& params, bool fByA
if (!ExtractDestination(txout.scriptPubKey, address))
continue;

if (has_filtered_address && !(filtered_address == address)) {
continue;
}

isminefilter mine = IsMine(*pwallet, address);
if(!(mine & filter))
continue;
Expand All @@ -1345,14 +1359,28 @@ UniValue ListReceived(CWallet * const pwallet, const UniValue& params, bool fByA
// Reply
UniValue ret(UniValue::VARR);
std::map<std::string, tallyitem> mapAccountTally;
for (const std::pair<CTxDestination, CAddressBookData>& item : pwallet->mapAddressBook) {
const CTxDestination& dest = item.first;
const std::string& strAccount = item.second.name;
std::map<CTxDestination, tallyitem>::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;
auto it = mapTally.find(address);
if (it == mapTally.end() && !fIncludeEmpty)
continue;

isminefilter mine = IsMine(*pwallet, dest);
isminefilter mine = IsMine(*pwallet, address);
if(!(mine & filter))
continue;

Expand All @@ -1378,7 +1406,7 @@ UniValue ListReceived(CWallet * const pwallet, const UniValue& params, bool fByA
UniValue obj(UniValue::VOBJ);
if(fIsWatchonly)
obj.pushKV("involvesWatchonly", true);
obj.pushKV("address", EncodeDestination(dest));
obj.pushKV("address", EncodeDestination(address));
obj.pushKV("account", strAccount);
obj.pushKV("amount", ValueFromAmount(nAmount));
obj.pushKV("confirmations", (nConf == std::numeric_limits<int>::max() ? 0 : nConf));
Expand Down Expand Up @@ -1423,16 +1451,16 @@ UniValue listreceivedbyaddress(const JSONRPCRequest& request)
return NullUniValue;
}

if (request.fHelp || request.params.size() > 4)
if (request.fHelp || request.params.size() > 5)
throw std::runtime_error(
"listreceivedbyaddress ( minconf addlocked include_empty include_watchonly)\n"
"listreceivedbyaddress ( minconf addlocked include_empty include_watchonly address_filter )\n"
"\nList incoming payments grouped by receiving address.\n"
"\nArguments:\n"
"1. minconf (numeric, optional, default=1) The minimum number of confirmations before payments are included.\n"
"2. addlocked (bool, optional, default=false) Whether to include transactions locked via InstantSend.\n"
"3. include_empty (bool, optional, default=false) Whether to include addresses that haven't received any payments.\n"
"4. include_watchonly (bool, optional, default=false) Whether to include watch-only addresses (see 'importaddress').\n"

"5. address_filter (string, optional) If present, only return information on this address.\n"
"\nResult:\n"
"[\n"
" {\n"
Expand All @@ -1456,6 +1484,7 @@ UniValue listreceivedbyaddress(const JSONRPCRequest& request)
+ HelpExampleCli("listreceivedbyaddress", "")
+ HelpExampleCli("listreceivedbyaddress", "6 false true")
+ HelpExampleRpc("listreceivedbyaddress", "6, false, true, true")
+ HelpExampleRpc("listreceivedbyaddress", "6, false, true, true, \"XbtdLrTsrPDhGy1wXtwKYoBpuKovE3JeBK\"")
);

ObserveSafeMode();
Expand Down Expand Up @@ -3534,7 +3563,7 @@ static const CRPCCommand commands[] =
{ "wallet", "listaddressbalances", &listaddressbalances, {"minamount"} },
{ "wallet", "listlockunspent", &listlockunspent, {} },
{ "wallet", "listreceivedbyaccount", &listreceivedbyaccount, {"minconf","addlocked","include_empty","include_watchonly"} },
{ "wallet", "listreceivedbyaddress", &listreceivedbyaddress, {"minconf","addlocked","include_empty","include_watchonly"} },
{ "wallet", "listreceivedbyaddress", &listreceivedbyaddress, {"minconf","addlocked","include_empty","include_watchonly","address_filter"} },
{ "wallet", "listsinceblock", &listsinceblock, {"blockhash","target_confirmations","include_watchonly","include_removed"} },
{ "wallet", "listtransactions", &listtransactions, {"account","count","skip","include_watchonly"} },
{ "wallet", "listunspent", &listunspent, {"minconf","maxconf","addresses","include_unsafe","query_options"} },
Expand Down
40 changes: 37 additions & 3 deletions test/functional/receivedby.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,44 @@ def run_test(self):
assert_array_result(self.nodes[1].listreceivedbyaddress(11), {"address": addr}, {}, True)

# Empty Tx
addr = self.nodes[1].getnewaddress()
empty_addr = self.nodes[1].getnewaddress()
assert_array_result(self.nodes[1].listreceivedbyaddress(0, False, 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, addlocked=False, 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, addlocked=True, include_empty=True, include_watchonly=True, address_filter="bamboozling")
#Another address receive money
res = self.nodes[1].listreceivedbyaddress(0, True, 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, 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, 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, True)
assert_equal(len(res), 3) #Became 3 entries

#Not on random addr
other_addr = self.nodes[0].getnewaddress() # note on node[0]! just a random addr
res = self.nodes[1].listreceivedbyaddress(0, True, True, True, other_addr)
assert_equal(len(res), 0)

self.log.info("getreceivedbyaddress Test")

Expand Down

0 comments on commit fd97aec

Please sign in to comment.