This gives listreceivedbyaddress the ability to filter for a single address.
This functionality is useful for users such as TumbleBit who need to filter by address.
This gives listreceivedbyaddress the ability to filter for a single address.
This functionality is useful for users such as TumbleBit who need to filter by address.
81+        if len(res) != 1:
82+            raise AssertionError("listreceivedbyaddress expected only 1 result")
83+
84+        #Not on addr
85+        other_addr = self.nodes[0].getnewaddress() # note on node[0]! just a random addr
86+        res = self.nodes[1].listreceivedbyaddress(5, True, True, other_addr)
1132@@ -1133,6 +1133,9 @@ UniValue ListReceived(const UniValue& params, bool fByAccounts)
1133     if (params.size() > 0)
1134         nMinDepth = params[0].get_int();
1135 
1136+    bool fFilterAddress = !fByAccounts && params.size() > 3;
1137+    const CBitcoinAddress filterAddress = fFilterAddress ?  CBitcoinAddress(params[3].get_str()) : CBitcoinAddress{};
if(params.size() > 2) block, so the params are being tested in order. I also have a slight preference to change this to a if(params.size() > 3) block to match the other tests.
              
            1184@@ -1178,10 +1185,22 @@ UniValue ListReceived(const UniValue& params, bool fByAccounts)
1185     // Reply
1186     UniValue ret(UniValue::VARR);
1187     map<string, tallyitem> mapAccountTally;
1188-    BOOST_FOREACH(const PAIRTYPE(CBitcoinAddress, CAddressBookData)& item, pwalletMain->mapAddressBook)
1189+
1190+
A couple of general comments:
listreceivedbyaddress rpc expected to be? If the rpc only ever returns a small number of addresses, it should be easy enough for the client to receive the full list of balances and then filter the list itself?only_address string to a filter_addresses array?I’ve also added a few nits.
@jnewbery addressed nits.
@EthanHeilman thoughts?
Another optimization would be to allow for caching of this table on construction (maybe keep_cache/clear_cache parameters). This could reduce the O(n*m) complexity for making m repeated calls to O(n + m).
@JeremyRubin The reason why I am interested into that is that here is the code I am using for querying the transactions of a scriptPubKey: Using listtransactions in tumblebit.
As far as I see this PR would be able to replace my listtransactions nicely. Will review.
1146-            filter = filter | ISMINE_WATCH_ONLY;
1147+    const bool fIncludeWatchOnly = params.size() > 2 && params[2].get_bool();
1148+    const isminefilter filter =  ISMINE_SPENDABLE | (fIncludeWatchOnly ? ISMINE_WATCH_ONLY : 0);
1149+
1150+    const bool fFilterAddress = !fByAccounts && params.size() > 3;
1151+    const CBitcoinAddress filterAddress = fFilterAddress ?  CBitcoinAddress(params[3].get_str()) : CBitcoinAddress{};
CBitcoinAddress{} ? shouldn’t it be CBitcoinAddress() ? (I guess not, as it build, but it is the first time I see that)
              
            How large are the results from the listreceivedbyaddress rpc expected to be? If the rpc only ever returns a small number of addresses, it should be easy enough for the client to receive the full list of balances and then filter the list itself?
To perform a 800 user mix with TumbleBit we would need a watch list of 1600 addresses. However we there are times in which we only want to learn the status of a single address.
Sorting received transactions by address is a common enough usecase to have an RPC call. It seems likely that people are calling it and then writing filters to select the addresses they want (for instance this stackexchange question or this reddit post). It is an natural addition to the RPC API and one that would make our project and other projects more performant and cleaner.
@EthanHeilman - thanks. Sounds like there’s widespread demand for this functionality.
concept ACK
1128@@ -1129,19 +1129,16 @@ struct tallyitem
1129 UniValue ListReceived(const UniValue& params, bool fByAccounts)
1130 {
1131     // Minimum confirmations
1132-    int nMinDepth = 1;
1133-    if (params.size() > 0)
1134-        nMinDepth = params[0].get_int();
1135+    const int nMinDepth = params.size() == 0 ? 1 : params[0].get_int();
1145-        if(params[2].get_bool())
1146-            filter = filter | ISMINE_WATCH_ONLY;
1147+    const bool fIncludeWatchOnly = params.size() > 2 && params[2].get_bool();
1148+    const isminefilter filter =  ISMINE_SPENDABLE | (fIncludeWatchOnly ? ISMINE_WATCH_ONLY : 0);
1149+
1150+    const bool fFilterAddress = !fByAccounts && params.size() > 3;
1158@@ -1162,6 +1159,10 @@ UniValue ListReceived(const UniValue& params, bool fByAccounts)
1159             if (!ExtractDestination(txout.scriptPubKey, address))
1160                 continue;
1161 
1162+            if (fFilterAddress && !(filterAddress.Get() == address)) {
filterAddress.Get() above rather than a CBitcoinAddress?
              
            1264@@ -1251,14 +1265,15 @@ UniValue listreceivedbyaddress(const JSONRPCRequest& request)
1265     if (!EnsureWalletIsAvailable(request.fHelp))
1266         return NullUniValue;
1267 
1268-    if (request.fHelp || request.params.size() > 3)
1269+    if (request.fHelp || request.params.size() > 4)
1270         throw runtime_error(
1271-            "listreceivedbyaddress ( minconf include_empty include_watchonly)\n"
1272+            "listreceivedbyaddress ( minconf include_empty include_watchonly only_address)\n"
1280@@ -1253,15 +1281,15 @@ UniValue listreceivedbyaddress(const JSONRPCRequest& request)
1281     if (!EnsureWalletIsAvailable(request.fHelp))
1282         return NullUniValue;
1283 
1284-    if (request.fHelp || request.params.size() > 3)
1285+    if (request.fHelp || request.params.size() > 4)
1286         throw runtime_error(
1287-            "listreceivedbyaddress ( minconf include_empty include_watchonly)\n"
1288+            "listreceivedbyaddress ( minconf include_empty include_watchonly only_address)\n"
1144@@ -1145,6 +1145,17 @@ UniValue ListReceived(const UniValue& params, bool fByAccounts)
1145         if(params[2].get_bool())
1146             filter = filter | ISMINE_WATCH_ONLY;
1147 
1148+    bool fFilterAddress = false;
1149+    CTxDestination filterAddress =  CBitcoinAddress{}.Get();
CNoDestination(); here ? just to be coherent with nulladdress. (and the fact that I never saw this syntax before)
              
            tested and integrated in NTumbleBit (https://github.com/NTumbleBit/NTumbleBit/commit/cd7c2f42eb7ba1ff071519dd0c2798a1f2fc43d7). This replace listtransaction * as I needed.
Outside of my nit, ACK a96fbed
77+        #Only on addr
78+        expected = {"address":addr, "account":"", "amount":Decimal("0.1"), "confirmations":10, "txids":[txid,]}
79+        res = self.nodes[1].listreceivedbyaddress(0, True, True, addr)
80+        assert_array_result(res, {"address":addr}, expected)
81+        if len(res) != 1:
82+            raise AssertionError("listreceivedbyaddress expected only 1 result")
Couldn’t you do
0assert_equal(len(res), 1)
instead, here? That way the resulting len(res) would be visible in the output.
83+
84+        #Not on addr
85+        other_addr = self.nodes[0].getnewaddress() # note on node[0]! just a random addr
86+        res = self.nodes[1].listreceivedbyaddress(0, True, True, other_addr)
87+        if res != []:
88+            raise AssertionError("Should not have listed any transactions, got\n%s"%res)
assert_equal here too.
              
            1174@@ -1164,6 +1175,10 @@ UniValue ListReceived(const UniValue& params, bool fByAccounts)
1175             if (!ExtractDestination(txout.scriptPubKey, address))
1176                 continue;
1177 
1178+            if (fFilterAddress && !(filterAddress == address)) {
fFilterAddress && filterAddress != address?
              
            Not the same only in the sense that the != operator may not be defined, right? Semantically they’re identical. And yeah, I see now. CNoDestination has no operator!=.
I wonder if it would be worth adding the one liner
0friend bool operator!=(const CNoDestination &a, const CNoDestination &b) { return false; }
to make this line look less confusing.
CNoDestination, it’s a CTxDestination, which is a boost variant. Some supported versions of boost do not support operator!= for variants.
              
            CNoDestination would be enough, but maybe I’m off on that one.
Edit: re-read your response; okay, didn’t know that. Gotcha.
              
            1280@@ -1253,15 +1281,15 @@ UniValue listreceivedbyaddress(const JSONRPCRequest& request)
1281     if (!EnsureWalletIsAvailable(request.fHelp))
1282         return NullUniValue;
1283 
1284-    if (request.fHelp || request.params.size() > 3)
1285+    if (request.fHelp || request.params.size() > 4)
1286         throw runtime_error(
1287-            "listreceivedbyaddress ( minconf include_empty include_watchonly)\n"
1288+            "listreceivedbyaddress (minconf include_empty include_watchonly only_address)\n"
2968@@ -2940,7 +2969,7 @@ static const CRPCCommand commands[] =
2969     { "wallet",             "listaddressgroupings",     &listaddressgroupings,     false,  {} },
2970     { "wallet",             "listlockunspent",          &listlockunspent,          false,  {} },
2971     { "wallet",             "listreceivedbyaccount",    &listreceivedbyaccount,    false,  {"minconf","include_empty","include_watchonly"} },
2972-    { "wallet",             "listreceivedbyaddress",    &listreceivedbyaddress,    false,  {"minconf","include_empty","include_watchonly"} },
2973+    { "wallet",             "listreceivedbyaddress",    &listreceivedbyaddress,    false,  {"minconf","include_empty","include_watchonly", "only_address"} },