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)
Why set minconf to 5 here? Can you just set it to 0 to match the call to listreceivedbyaddress() above?
👍
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{};
nit: I think it's clearer to move these two lines below the 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.
I agree that it's important for the style to be consistent. I opted to convert the other argument parsing in ListReceived to match this style, at the expense of a larger diff, for the benefit of declaring the input parameters const.
My personal preference is to keep the if statements rather than converting them to ternary conditionals, as I think that's clearer (and consistent with all the other RPCs). I'm not that concerned about having the parameters const.
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 | +
nit: prefer not to have two blank lines in the middle of a function. Perhaps you can move one of them to between the if block and for block below.
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.
I think (2) isn't really needed since if you need more than a small number you can just do un-filtered or a few repeated calls. I'm no expert on this use-case though.
@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)
should be equivalent; it's c++11 list initializer syntax
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();
Probably better to leave these alone. If params[0] is null, we really should silently use the default value... The longer version is also more readable/obvious.
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;
Should be false if params[3] is null.
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)) {
Maybe we should be storing 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"
Prefer replacing all the params with an options Object, but perhaps that is better done in a separate PR.
yeah. Separate PR...
utACK, with some nits.
Addressed feedback, and squashed. @luke-jr it now errors if the passed in address was not a valid address.
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"
Nit: space before end paren at 'only_address )' to match space after start paren at '( minconf'
Not sure if I should fix that -- that's how it was before my PR.
Yea, best to fix, I think.
utACK
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();
can you use CNoDestination(); here ? just to be coherent with nulladdress. (and the fact that I never saw this syntax before)
Yes, please.
tested and integrated in NTumbleBit (https://github.com/NTumbleBit/NTumbleBit/commit/cd7c2f42eb7ba1ff071519dd0c2798a1f2fc43d7). This replace listtransaction * as I needed.
Outside of my nit, ACK a96fbed
utACK a96fbed73ed24591dd42845088639da0afaa436a aside from the style nits.
Nits addressed and squashed. Preserved the pre-squash here https://github.com/JeremyRubin/bitcoin/tree/listreceivedbyaddress-filtered-a96fbed73ed24591dd42845088639da0afaa436a
ACK e6f053a66f9196e2d75d15af2f89facd693402ff
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
assert_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)
I believe you can use 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)) {
Why not fFilterAddress && filterAddress != address?
(a != b) is not the same as !(a == b). I don't think != is defined here iirc.
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
friend bool operator!=(const CNoDestination &a, const CNoDestination &b) { return false; }
to make this line look less confusing.
Except it's not a one liner, it needs to be added for all sorts of classes.
It's not a CNoDestination, it's a CTxDestination, which is a boost variant. Some supported versions of boost do not support operator!= for variants.
I thought if all variants accepted the operator the variant would accept it, in which case adding it to 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"
Nit: standard elsewhere is to show optionals as "( opt1 opt2 ... )", not "(opt1 opt2 ...)" (i.e. instead of removing starting space, add ending space)
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"} },
Nit: abide by surrounding style of no space after comma
utACK e6f053a
@JeremyRubin you might need to add a line in client.cpp (https://github.com/bitcoin/bitcoin/issues/9982)
@JeremyRubin Let me know if you are a bit busy and prefer I take care of this PR.
@NicolasDorier Go for it, sorry for the hold up!
Discussion continues at #9991