Merge #9991: listreceivedbyaddress Filter Address

f08761371 Add tests of listreceivedbyaddress address filtering (Jeremy Rubin)
8ee08120d Add address filtering to listreceivedbyaddress (Jeremy Rubin)

Pull request description:

  Supersede https://github.com/bitcoin/bitcoin/pull/9503 created by @JeremyRubin , I will maintain it.

Tree-SHA512: 2accaed493b7e1c2eb5cb5270180f100f8c718b6585b9574f294191c318dc622a79e42ac185300f291f82d3b2a6f1c00850b6b17e4ff2dbab94d71df695acbfe
This commit is contained in:
Wladimir J. van der Laan 2018-03-07 16:03:42 +01:00
commit 4ca7c1e4ac
No known key found for this signature in database
GPG Key ID: 1E4AED62986CD25D
3 changed files with 76 additions and 12 deletions

View File

@ -43,6 +43,7 @@ static const CRPCConvertParam vRPCConvertParams[] =
{ "listreceivedbyaddress", 0, "minconf" }, { "listreceivedbyaddress", 0, "minconf" },
{ "listreceivedbyaddress", 1, "include_empty" }, { "listreceivedbyaddress", 1, "include_empty" },
{ "listreceivedbyaddress", 2, "include_watchonly" }, { "listreceivedbyaddress", 2, "include_watchonly" },
{ "listreceivedbyaddress", 3, "address_filter" },
{ "listreceivedbyaccount", 0, "minconf" }, { "listreceivedbyaccount", 0, "minconf" },
{ "listreceivedbyaccount", 1, "include_empty" }, { "listreceivedbyaccount", 1, "include_empty" },
{ "listreceivedbyaccount", 2, "include_watchonly" }, { "listreceivedbyaccount", 2, "include_watchonly" },

View File

@ -1403,6 +1403,16 @@ UniValue ListReceived(CWallet * const pwallet, const UniValue& params, bool fByA
if(params[2].get_bool()) if(params[2].get_bool())
filter = filter | ISMINE_WATCH_ONLY; 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())) {
throw JSONRPCError(RPC_WALLET_ERROR, "address_filter parameter was invalid");
}
filtered_address = DecodeDestination(params[3].get_str());
has_filtered_address = true;
}
// Tally // Tally
std::map<CTxDestination, tallyitem> mapTally; std::map<CTxDestination, tallyitem> mapTally;
for (const std::pair<uint256, CWalletTx>& pairWtx : pwallet->mapWallet) { for (const std::pair<uint256, CWalletTx>& pairWtx : pwallet->mapWallet) {
@ -1421,6 +1431,10 @@ UniValue ListReceived(CWallet * const pwallet, const UniValue& params, bool fByA
if (!ExtractDestination(txout.scriptPubKey, address)) if (!ExtractDestination(txout.scriptPubKey, address))
continue; continue;
if (has_filtered_address && !(filtered_address == address)) {
continue;
}
isminefilter mine = IsMine(*pwallet, address); isminefilter mine = IsMine(*pwallet, address);
if(!(mine & filter)) if(!(mine & filter))
continue; continue;
@ -1437,10 +1451,24 @@ UniValue ListReceived(CWallet * const pwallet, const UniValue& params, bool fByA
// Reply // Reply
UniValue ret(UniValue::VARR); UniValue ret(UniValue::VARR);
std::map<std::string, tallyitem> mapAccountTally; std::map<std::string, tallyitem> mapAccountTally;
for (const std::pair<CTxDestination, CAddressBookData>& item : pwallet->mapAddressBook) {
const CTxDestination& dest = item.first; // Create mapAddressBook iterator
const std::string& strAccount = item.second.name; // If we aren't filtering, go from begin() to end()
std::map<CTxDestination, tallyitem>::iterator it = mapTally.find(dest); 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) if (it == mapTally.end() && !fIncludeEmpty)
continue; continue;
@ -1466,7 +1494,7 @@ UniValue ListReceived(CWallet * const pwallet, const UniValue& params, bool fByA
UniValue obj(UniValue::VOBJ); UniValue obj(UniValue::VOBJ);
if(fIsWatchonly) if(fIsWatchonly)
obj.pushKV("involvesWatchonly", true); obj.pushKV("involvesWatchonly", true);
obj.pushKV("address", EncodeDestination(dest)); obj.pushKV("address", EncodeDestination(address));
obj.pushKV("account", strAccount); obj.pushKV("account", strAccount);
obj.pushKV("amount", ValueFromAmount(nAmount)); obj.pushKV("amount", ValueFromAmount(nAmount));
obj.pushKV("confirmations", (nConf == std::numeric_limits<int>::max() ? 0 : nConf)); obj.pushKV("confirmations", (nConf == std::numeric_limits<int>::max() ? 0 : nConf));
@ -1511,15 +1539,15 @@ UniValue listreceivedbyaddress(const JSONRPCRequest& request)
return NullUniValue; return NullUniValue;
} }
if (request.fHelp || request.params.size() > 3) if (request.fHelp || request.params.size() > 4)
throw std::runtime_error( 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" "\nList balances by receiving address.\n"
"\nArguments:\n" "\nArguments:\n"
"1. minconf (numeric, optional, default=1) The minimum number of confirmations before payments are included.\n" "1. minconf (numeric, optional, default=1) The minimum number of confirmations before payments are included.\n"
"2. include_empty (bool, optional, default=false) Whether to include addresses that haven't received any payments.\n" "2. include_empty (bool, optional, default=false) Whether to 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" "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" "\nResult:\n"
"[\n" "[\n"
" {\n" " {\n"
@ -1541,6 +1569,7 @@ UniValue listreceivedbyaddress(const JSONRPCRequest& request)
+ HelpExampleCli("listreceivedbyaddress", "") + HelpExampleCli("listreceivedbyaddress", "")
+ HelpExampleCli("listreceivedbyaddress", "6 true") + HelpExampleCli("listreceivedbyaddress", "6 true")
+ HelpExampleRpc("listreceivedbyaddress", "6, true, true") + HelpExampleRpc("listreceivedbyaddress", "6, true, true")
+ HelpExampleRpc("listreceivedbyaddress", "6, true, true, \"1M72Sfpbz1BPpXFHz9m3CdqATR44Jvaydd\"")
); );
ObserveSafeMode(); ObserveSafeMode();
@ -3837,7 +3866,7 @@ static const CRPCCommand commands[] =
{ "wallet", "listaddressgroupings", &listaddressgroupings, {} }, { "wallet", "listaddressgroupings", &listaddressgroupings, {} },
{ "wallet", "listlockunspent", &listlockunspent, {} }, { "wallet", "listlockunspent", &listlockunspent, {} },
{ "wallet", "listreceivedbyaccount", &listreceivedbyaccount, {"minconf","include_empty","include_watchonly"} }, { "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","include_removed"} }, { "wallet", "listsinceblock", &listsinceblock, {"blockhash","target_confirmations","include_watchonly","include_removed"} },
{ "wallet", "listtransactions", &listtransactions, {"account","count","skip","include_watchonly"} }, { "wallet", "listtransactions", &listtransactions, {"account","count","skip","include_watchonly"} },
{ "wallet", "listunspent", &listunspent, {"minconf","maxconf","addresses","include_unsafe","query_options"} }, { "wallet", "listunspent", &listunspent, {"minconf","maxconf","addresses","include_unsafe","query_options"} },

View File

@ -45,10 +45,44 @@ class ReceivedByTest(BitcoinTestFramework):
assert_array_result(self.nodes[1].listreceivedbyaddress(11), {"address": addr}, {}, True) assert_array_result(self.nodes[1].listreceivedbyaddress(11), {"address": addr}, {}, True)
# Empty Tx # Empty Tx
addr = self.nodes[1].getnewaddress() empty_addr = self.nodes[1].getnewaddress()
assert_array_result(self.nodes[1].listreceivedbyaddress(0, True), assert_array_result(self.nodes[1].listreceivedbyaddress(0, True),
{"address": addr}, {"address": empty_addr},
{"address": addr, "account": "", "amount": 0, "confirmations": 0, "txids": []}) {"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
other_addr = self.nodes[0].getnewaddress() # note on node[0]! just a random addr
res = self.nodes[1].listreceivedbyaddress(0, True, True, other_addr)
assert_equal(len(res), 0)
self.log.info("getreceivedbyaddress Test") self.log.info("getreceivedbyaddress Test")