Skip to content

Commit

Permalink
[wallet] Restore ability to list incoming transactions by label
Browse files Browse the repository at this point in the history
Backport of PR 14411 to v0.17.

This change partially reverts bitcoin#13075 and bitcoin#14023.

Fixes bitcoin#14382
  • Loading branch information
ryanofsky authored and jnewbery committed Oct 10, 2018
1 parent 5b47b8e commit 89306ab
Show file tree
Hide file tree
Showing 4 changed files with 56 additions and 17 deletions.
18 changes: 17 additions & 1 deletion doc/release-notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,23 @@ Notable changes
0.17.x change log
=================

...
`listtransactions` label support
--------------------------------

The `listtransactions` RPC `account` parameter which was deprecated in 0.17.0
and renamed to `dummy` has been un-deprecated and renamed again to `label`.

When bitcoin is configured with the `-deprecatedrpc=accounts` setting, specifying
a label/account/dummy argument will return both outgoing and incoming
transactions. Without the `-deprecatedrpc=accounts` setting, it will only return
incoming transactions (because it used to be possible to create transactions
spending from specific accounts, but this is no longer possible with labels).

When `-deprecatedrpc=accounts` is set, it's possible to pass the empty string ""
to list transactions that don't have any label. Without
`-deprecatedrpc=accounts`, passing the empty string is an error because returning
only non-labeled transactions is not generally useful behavior and can cause
confusion.

Credits
=======
Expand Down
23 changes: 15 additions & 8 deletions src/wallet/rpcwallet.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1805,9 +1805,14 @@ static void ListTransactions(CWallet* const pwallet, const CWalletTx& wtx, const
bool fAllAccounts = (strAccount == std::string("*"));
bool involvesWatchonly = wtx.IsFromMe(ISMINE_WATCH_ONLY);

bool list_sent = fAllAccounts;

if (IsDeprecatedRPCEnabled("accounts")) {
list_sent |= strAccount == strSentAccount;
}

// Sent
if ((!listSent.empty() || nFee != 0) && (fAllAccounts || strAccount == strSentAccount))
{
if (list_sent) {
for (const COutputEntry& s : listSent)
{
UniValue entry(UniValue::VOBJ);
Expand Down Expand Up @@ -1901,12 +1906,14 @@ UniValue listtransactions(const JSONRPCRequest& request)

std::string help_text {};
if (!IsDeprecatedRPCEnabled("accounts")) {
help_text = "listtransactions (dummy count skip include_watchonly)\n"
"\nReturns up to 'count' most recent transactions skipping the first 'from' transactions for account 'account'.\n"
help_text = "listtransactions (label count skip include_watchonly)\n"
"\nIf a label name is provided, this will return only incoming transactions paying to addresses with the specified label.\n"
"\nReturns up to 'count' most recent transactions skipping the first 'from' transactions.\n"
"Note that the \"account\" argument and \"otheraccount\" return value have been removed in V0.17. To use this RPC with an \"account\" argument, restart\n"
"bitcoind with -deprecatedrpc=accounts\n"
"\nArguments:\n"
"1. \"dummy\" (string, optional) If set, should be \"*\" for backwards compatibility.\n"
"1. \"label\" (string, optional) If set, should be a valid label name to return only incoming transactions\n"
" with the specified label, or \"*\" to disable filtering and return all transactions.\n"
"2. count (numeric, optional, default=10) The number of transactions to return\n"
"3. skip (numeric, optional, default=0) The number of transactions to skip\n"
"4. include_watchonly (bool, optional, default=false) Include transactions to watch-only addresses (see 'importaddress')\n"
Expand Down Expand Up @@ -2012,8 +2019,8 @@ UniValue listtransactions(const JSONRPCRequest& request)
std::string strAccount = "*";
if (!request.params[0].isNull()) {
strAccount = request.params[0].get_str();
if (!IsDeprecatedRPCEnabled("accounts") && strAccount != "*") {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Dummy value must be set to \"*\"");
if (!IsDeprecatedRPCEnabled("accounts") && strAccount.empty()) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Label argument must be a valid label name or \"*\".");
}
}
int nCount = 10;
Expand Down Expand Up @@ -4801,7 +4808,7 @@ static const CRPCCommand commands[] =
{ "wallet", "listlockunspent", &listlockunspent, {} },
{ "wallet", "listreceivedbyaddress", &listreceivedbyaddress, {"minconf","include_empty","include_watchonly","address_filter"} },
{ "wallet", "listsinceblock", &listsinceblock, {"blockhash","target_confirmations","include_watchonly","include_removed"} },
{ "wallet", "listtransactions", &listtransactions, {"account|dummy","count","skip","include_watchonly"} },
{ "wallet", "listtransactions", &listtransactions, {"account|label|dummy","count","skip","include_watchonly"} },
{ "wallet", "listunspent", &listunspent, {"minconf","maxconf","addresses","include_unsafe","query_options"} },
{ "wallet", "listwallets", &listwallets, {} },
{ "wallet", "loadwallet", &loadwallet, {"filename"} },
Expand Down
25 changes: 20 additions & 5 deletions test/functional/wallet_import_rescan.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,11 @@ def do_import(self, timestamp):

if self.call == Call.single:
if self.data == Data.address:
response = self.try_rpc(self.node.importaddress, address=self.address["address"], rescan=rescan)
response = self.try_rpc(self.node.importaddress, address=self.address["address"], label=self.label, rescan=rescan)
elif self.data == Data.pub:
response = self.try_rpc(self.node.importpubkey, pubkey=self.address["pubkey"], rescan=rescan)
response = self.try_rpc(self.node.importpubkey, pubkey=self.address["pubkey"], label=self.label, rescan=rescan)
elif self.data == Data.priv:
response = self.try_rpc(self.node.importprivkey, privkey=self.key, rescan=rescan)
response = self.try_rpc(self.node.importprivkey, privkey=self.key, label=self.label, rescan=rescan)
assert_equal(response, None)

elif self.call == Call.multi:
Expand All @@ -61,18 +61,32 @@ def do_import(self, timestamp):
"timestamp": timestamp + TIMESTAMP_WINDOW + (1 if self.rescan == Rescan.late_timestamp else 0),
"pubkeys": [self.address["pubkey"]] if self.data == Data.pub else [],
"keys": [self.key] if self.data == Data.priv else [],
"label": self.label,
"watchonly": self.data != Data.priv
}], {"rescan": self.rescan in (Rescan.yes, Rescan.late_timestamp)})
assert_equal(response, [{"success": True}])

def check(self, txid=None, amount=None, confirmations=None):
"""Verify that listreceivedbyaddress returns expected values."""
"""Verify that listtransactions/listreceivedbyaddress return expected values."""

txs = self.node.listtransactions(label=self.label, count=10000, skip=0, include_watchonly=True)
assert_equal(len(txs), self.expected_txs)

addresses = self.node.listreceivedbyaddress(minconf=0, include_watchonly=True, address_filter=self.address['address'])
if self.expected_txs:
assert_equal(len(addresses[0]["txids"]), self.expected_txs)

if txid is not None:
tx, = [tx for tx in txs if tx["txid"] == txid]
assert_equal(tx["label"], self.label)
assert_equal(tx["address"], self.address["address"])
assert_equal(tx["amount"], amount)
assert_equal(tx["category"], "receive")
assert_equal(tx["label"], self.label)
assert_equal(tx["txid"], txid)
assert_equal(tx["confirmations"], confirmations)
assert_equal("trusted" not in tx, True)

address, = [ad for ad in addresses if txid in ad["txids"]]
assert_equal(address["address"], self.address["address"])
assert_equal(address["amount"], self.expected_balance)
Expand Down Expand Up @@ -124,7 +138,8 @@ def run_test(self):
# Create one transaction on node 0 with a unique amount for
# each possible type of wallet import RPC.
for i, variant in enumerate(IMPORT_VARIANTS):
variant.address = self.nodes[1].getaddressinfo(self.nodes[1].getnewaddress())
variant.label = "label {} {}".format(i, variant)
variant.address = self.nodes[1].getaddressinfo(self.nodes[1].getnewaddress(variant.label))
variant.key = self.nodes[1].dumpprivkey(variant.address["address"])
variant.initial_amount = 10 - (i + 1) / 4.0
variant.initial_txid = self.nodes[0].sendtoaddress(variant.address["address"], variant.initial_amount)
Expand Down
7 changes: 4 additions & 3 deletions test/functional/wallet_listtransactions.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,9 +94,10 @@ def run_test(self):
txid = self.nodes[1].sendtoaddress(multisig["address"], 0.1)
self.nodes[1].generate(1)
self.sync_all()
assert not [tx for tx in self.nodes[0].listtransactions(dummy="*", count=100, skip=0, include_watchonly=False) if "label" in tx and tx["label"] == "watchonly"]
txs = [tx for tx in self.nodes[0].listtransactions(dummy="*", count=100, skip=0, include_watchonly=True) if "label" in tx and tx['label'] == 'watchonly']
assert_array_result(txs, {"category": "receive", "amount": Decimal("0.1")}, {"txid": txid})
assert len(self.nodes[0].listtransactions(label="watchonly", count=100, skip=0, include_watchonly=False)) == 0
assert_array_result(self.nodes[0].listtransactions(label="watchonly", count=100, skip=0, include_watchonly=True),
{"category": "receive", "amount": Decimal("0.1")},
{"txid": txid, "label": "watchonly"})

self.run_rbf_opt_in_test()

Expand Down

0 comments on commit 89306ab

Please sign in to comment.