Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

listreceivedbyaddress Filter Address #9503

Closed

Conversation

JeremyRubin
Copy link
Contributor

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.

@jnewbery
Copy link
Contributor

A couple of general comments:

  • 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?
  • if there's a chance that this functionality needs to be extended further to filter on a list of addresses rather than a single address, it'd be better to include that now. Since rpc: Add support for JSON-RPC named arguments #8811 , the arguments to the RPCs are part of the API, so changing them later becomes more troublesome. Perhaps change the only_address string to a filter_addresses array?

I've also added a few nits.


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

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why set minconf to 5 here? Can you just set it to 0 to match the call to listreceivedbyaddress() above?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

@@ -1133,6 +1133,9 @@ UniValue ListReceived(const UniValue& params, bool fByAccounts)
if (params.size() > 0)
nMinDepth = params[0].get_int();

bool fFilterAddress = !fByAccounts && params.size() > 3;
const CBitcoinAddress filterAddress = fFilterAddress ? CBitcoinAddress(params[3].get_str()) : CBitcoinAddress{};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@@ -1178,10 +1185,22 @@ UniValue ListReceived(const UniValue& params, bool fByAccounts)
// Reply
UniValue ret(UniValue::VARR);
map<string, tallyitem> mapAccountTally;
BOOST_FOREACH(const PAIRTYPE(CBitcoinAddress, CAddressBookData)& item, pwalletMain->mapAddressBook)


Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@JeremyRubin JeremyRubin force-pushed the listreceivedbyaddress-filtered branch 3 times, most recently from 0ab4c34 to c0792a1 Compare January 13, 2017 15:49
@JeremyRubin
Copy link
Contributor Author

@jnewbery addressed nits.

  1. I think it could be quite large? And then you end up sending a bunch of extra crap over the network.
  2. I don't think it would need to be extended as such, but I'll let others chime in.

@JeremyRubin JeremyRubin reopened this Jan 13, 2017
@instagibbs
Copy link
Member

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.

@JeremyRubin
Copy link
Contributor Author

@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).

@NicolasDorier
Copy link
Contributor

NicolasDorier commented Jan 13, 2017

@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.

const isminefilter filter = ISMINE_SPENDABLE | (fIncludeWatchOnly ? ISMINE_WATCH_ONLY : 0);

const bool fFilterAddress = !fByAccounts && params.size() > 3;
const CBitcoinAddress filterAddress = fFilterAddress ? CBitcoinAddress(params[3].get_str()) : CBitcoinAddress{};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CBitcoinAddress{} ? shouldn't it be CBitcoinAddress() ? (I guess not, as it build, but it is the first time I see that)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should be equivalent; it's c++11 list initializer syntax

@EthanHeilman
Copy link
Contributor

EthanHeilman commented Jan 13, 2017

@jnewbery

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.

@jnewbery
Copy link
Contributor

@EthanHeilman - thanks. Sounds like there's widespread demand for this functionality.

concept ACK

Copy link
Member

@luke-jr luke-jr left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

utACK, with some nits.

int nMinDepth = 1;
if (params.size() > 0)
nMinDepth = params[0].get_int();
const int nMinDepth = params.size() == 0 ? 1 : params[0].get_int();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

const bool fIncludeWatchOnly = params.size() > 2 && params[2].get_bool();
const isminefilter filter = ISMINE_SPENDABLE | (fIncludeWatchOnly ? ISMINE_WATCH_ONLY : 0);

const bool fFilterAddress = !fByAccounts && params.size() > 3;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be false if params[3] is null.

@@ -1162,6 +1159,10 @@ UniValue ListReceived(const UniValue& params, bool fByAccounts)
if (!ExtractDestination(txout.scriptPubKey, address))
continue;

if (fFilterAddress && !(filterAddress.Get() == address)) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we should be storing filterAddress.Get() above rather than a CBitcoinAddress?

throw runtime_error(
"listreceivedbyaddress ( minconf include_empty include_watchonly)\n"
"listreceivedbyaddress ( minconf include_empty include_watchonly only_address)\n"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Prefer replacing all the params with an options Object, but perhaps that is better done in a separate PR.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah. Separate PR...

@JeremyRubin JeremyRubin force-pushed the listreceivedbyaddress-filtered branch 2 times, most recently from b7d7953 to a96fbed Compare January 20, 2017 23:10
@JeremyRubin
Copy link
Contributor Author

Addressed feedback, and squashed.

@luke-jr it now errors if the passed in address was not a valid address.

throw runtime_error(
"listreceivedbyaddress ( minconf include_empty include_watchonly)\n"
"listreceivedbyaddress ( minconf include_empty include_watchonly only_address)\n"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: space before end paren at 'only_address )' to match space after start paren at '( minconf'

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if I should fix that -- that's how it was before my PR.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yea, best to fix, I think.

Copy link
Member

@luke-jr luke-jr left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

utACK

@@ -1145,6 +1145,17 @@ UniValue ListReceived(const UniValue& params, bool fByAccounts)
if(params[2].get_bool())
filter = filter | ISMINE_WATCH_ONLY;

bool fFilterAddress = false;
CTxDestination filterAddress = CBitcoinAddress{}.Get();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you use CNoDestination(); here ? just to be coherent with nulladdress. (and the fact that I never saw this syntax before)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, please.

NicolasDorier added a commit to NTumbleBit/NTumbleBit that referenced this pull request Feb 6, 2017
@NicolasDorier
Copy link
Contributor

NicolasDorier commented Feb 6, 2017

tested and integrated in NTumbleBit (NTumbleBit/NTumbleBit@cd7c2f4). This replace listtransaction * as I needed.

Outside of my nit, ACK a96fbed

Copy link
Contributor

@TheBlueMatt TheBlueMatt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

utACK a96fbed aside from the style nits.

throw runtime_error(
"listreceivedbyaddress ( minconf include_empty include_watchonly)\n"
"listreceivedbyaddress ( minconf include_empty include_watchonly only_address)\n"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yea, best to fix, I think.

@@ -1145,6 +1145,17 @@ UniValue ListReceived(const UniValue& params, bool fByAccounts)
if(params[2].get_bool())
filter = filter | ISMINE_WATCH_ONLY;

bool fFilterAddress = false;
CTxDestination filterAddress = CBitcoinAddress{}.Get();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, please.

@JeremyRubin
Copy link
Contributor Author

@NicolasDorier
Copy link
Contributor

ACK e6f053a

Copy link
Member

@kallewoof kallewoof left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

utACK e6f053a

res = self.nodes[1].listreceivedbyaddress(0, True, True, addr)
assert_array_result(res, {"address":addr}, expected)
if len(res) != 1:
raise AssertionError("listreceivedbyaddress expected only 1 result")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Couldn't you do

assert_equal(len(res), 1)

instead, here? That way the resulting len(res) would be visible in the output.

other_addr = self.nodes[0].getnewaddress() # note on node[0]! just a random addr
res = self.nodes[1].listreceivedbyaddress(0, True, True, other_addr)
if res != []:
raise AssertionError("Should not have listed any transactions, got\n%s"%res)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe you can use assert_equal here too.

@@ -1164,6 +1175,10 @@ UniValue ListReceived(const UniValue& params, bool fByAccounts)
if (!ExtractDestination(txout.scriptPubKey, address))
continue;

if (fFilterAddress && !(filterAddress == address)) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not fFilterAddress && filterAddress != address?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(a != b) is not the same as !(a == b). I don't think != is defined here iirc.

Copy link
Member

@kallewoof kallewoof Mar 1, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Except it's not a one liner, it needs to be added for all sorts of classes.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Member

@kallewoof kallewoof Mar 1, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

throw runtime_error(
"listreceivedbyaddress ( minconf include_empty include_watchonly)\n"
"listreceivedbyaddress (minconf include_empty include_watchonly only_address)\n"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: standard elsewhere is to show optionals as "( opt1 opt2 ... )", not "(opt1 opt2 ...)" (i.e. instead of removing starting space, add ending space)

@@ -2940,7 +2969,7 @@ static const CRPCCommand commands[] =
{ "wallet", "listaddressgroupings", &listaddressgroupings, false, {} },
{ "wallet", "listlockunspent", &listlockunspent, false, {} },
{ "wallet", "listreceivedbyaccount", &listreceivedbyaccount, false, {"minconf","include_empty","include_watchonly"} },
{ "wallet", "listreceivedbyaddress", &listreceivedbyaddress, false, {"minconf","include_empty","include_watchonly"} },
{ "wallet", "listreceivedbyaddress", &listreceivedbyaddress, false, {"minconf","include_empty","include_watchonly", "only_address"} },
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: abide by surrounding style of no space after comma

@NicolasDorier
Copy link
Contributor

@JeremyRubin you might need to add a line in client.cpp (#9982)

@NicolasDorier
Copy link
Contributor

@JeremyRubin Let me know if you are a bit busy and prefer I take care of this PR.

@JeremyRubin
Copy link
Contributor Author

@NicolasDorier Go for it, sorry for the hold up!

@maflcko
Copy link
Member

maflcko commented Mar 14, 2017

Discussion continues at #9991

@maflcko maflcko closed this Mar 14, 2017
DanGould pushed a commit to DanGould/NTumbleBit that referenced this pull request Mar 29, 2017
laanwj added a commit that referenced this pull request Mar 7, 2018
f087613 Add tests of listreceivedbyaddress address filtering (Jeremy Rubin)
8ee0812 Add address filtering to listreceivedbyaddress (Jeremy Rubin)

Pull request description:

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

Tree-SHA512: 2accaed493b7e1c2eb5cb5270180f100f8c718b6585b9574f294191c318dc622a79e42ac185300f291f82d3b2a6f1c00850b6b17e4ff2dbab94d71df695acbfe
PastaPastaPasta pushed a commit to PastaPastaPasta/dash that referenced this pull request Jun 17, 2020
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
PastaPastaPasta pushed a commit to PastaPastaPasta/dash that referenced this pull request Jun 17, 2020
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
PastaPastaPasta pushed a commit to PastaPastaPasta/dash that referenced this pull request Jun 17, 2020
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
PastaPastaPasta pushed a commit to PastaPastaPasta/dash that referenced this pull request Jun 27, 2020
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
@bitcoin bitcoin locked as resolved and limited conversation to collaborators Sep 8, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

10 participants