Merge #14441: [wallet] Backport(0.17): Restore ability to list incoming transactions by label
89306ab0df
[wallet] Restore ability to list incoming transactions by label (Russell Yanofsky)
Pull request description:
Backport of PR #14411 to v0.17.
This change partially reverts #13075 and #14023.
Fixes #14382
Tree-SHA512: 1f8300e1a79e826cd706561265b8788deef505fa510be1a76ed9a62e5fca37cf6a741423ac0e5de2a36d6e8b9f25f141885455aacacbbf6474814e6eae406a27
This commit is contained in:
commit
5150accdd2
4 changed files with 56 additions and 17 deletions
|
@ -71,7 +71,23 @@ Notable changes
|
||||||
0.17.x change log
|
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
|
Credits
|
||||||
=======
|
=======
|
||||||
|
|
|
@ -1805,9 +1805,14 @@ static void ListTransactions(CWallet* const pwallet, const CWalletTx& wtx, const
|
||||||
bool fAllAccounts = (strAccount == std::string("*"));
|
bool fAllAccounts = (strAccount == std::string("*"));
|
||||||
bool involvesWatchonly = wtx.IsFromMe(ISMINE_WATCH_ONLY);
|
bool involvesWatchonly = wtx.IsFromMe(ISMINE_WATCH_ONLY);
|
||||||
|
|
||||||
|
bool list_sent = fAllAccounts;
|
||||||
|
|
||||||
|
if (IsDeprecatedRPCEnabled("accounts")) {
|
||||||
|
list_sent |= strAccount == strSentAccount;
|
||||||
|
}
|
||||||
|
|
||||||
// Sent
|
// Sent
|
||||||
if ((!listSent.empty() || nFee != 0) && (fAllAccounts || strAccount == strSentAccount))
|
if (list_sent) {
|
||||||
{
|
|
||||||
for (const COutputEntry& s : listSent)
|
for (const COutputEntry& s : listSent)
|
||||||
{
|
{
|
||||||
UniValue entry(UniValue::VOBJ);
|
UniValue entry(UniValue::VOBJ);
|
||||||
|
@ -1901,12 +1906,14 @@ UniValue listtransactions(const JSONRPCRequest& request)
|
||||||
|
|
||||||
std::string help_text {};
|
std::string help_text {};
|
||||||
if (!IsDeprecatedRPCEnabled("accounts")) {
|
if (!IsDeprecatedRPCEnabled("accounts")) {
|
||||||
help_text = "listtransactions (dummy count skip include_watchonly)\n"
|
help_text = "listtransactions (label count skip include_watchonly)\n"
|
||||||
"\nReturns up to 'count' most recent transactions skipping the first 'from' transactions for account 'account'.\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"
|
"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"
|
"bitcoind with -deprecatedrpc=accounts\n"
|
||||||
"\nArguments:\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"
|
"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"
|
"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"
|
"4. include_watchonly (bool, optional, default=false) Include transactions to watch-only addresses (see 'importaddress')\n"
|
||||||
|
@ -2012,8 +2019,8 @@ UniValue listtransactions(const JSONRPCRequest& request)
|
||||||
std::string strAccount = "*";
|
std::string strAccount = "*";
|
||||||
if (!request.params[0].isNull()) {
|
if (!request.params[0].isNull()) {
|
||||||
strAccount = request.params[0].get_str();
|
strAccount = request.params[0].get_str();
|
||||||
if (!IsDeprecatedRPCEnabled("accounts") && strAccount != "*") {
|
if (!IsDeprecatedRPCEnabled("accounts") && strAccount.empty()) {
|
||||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Dummy value must be set to \"*\"");
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "Label argument must be a valid label name or \"*\".");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
int nCount = 10;
|
int nCount = 10;
|
||||||
|
@ -4801,7 +4808,7 @@ static const CRPCCommand commands[] =
|
||||||
{ "wallet", "listlockunspent", &listlockunspent, {} },
|
{ "wallet", "listlockunspent", &listlockunspent, {} },
|
||||||
{ "wallet", "listreceivedbyaddress", &listreceivedbyaddress, {"minconf","include_empty","include_watchonly","address_filter"} },
|
{ "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|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", "listunspent", &listunspent, {"minconf","maxconf","addresses","include_unsafe","query_options"} },
|
||||||
{ "wallet", "listwallets", &listwallets, {} },
|
{ "wallet", "listwallets", &listwallets, {} },
|
||||||
{ "wallet", "loadwallet", &loadwallet, {"filename"} },
|
{ "wallet", "loadwallet", &loadwallet, {"filename"} },
|
||||||
|
|
|
@ -46,11 +46,11 @@ class Variant(collections.namedtuple("Variant", "call data rescan prune")):
|
||||||
|
|
||||||
if self.call == Call.single:
|
if self.call == Call.single:
|
||||||
if self.data == Data.address:
|
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:
|
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:
|
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)
|
assert_equal(response, None)
|
||||||
|
|
||||||
elif self.call == Call.multi:
|
elif self.call == Call.multi:
|
||||||
|
@ -61,18 +61,32 @@ class Variant(collections.namedtuple("Variant", "call data rescan prune")):
|
||||||
"timestamp": timestamp + TIMESTAMP_WINDOW + (1 if self.rescan == Rescan.late_timestamp else 0),
|
"timestamp": timestamp + TIMESTAMP_WINDOW + (1 if self.rescan == Rescan.late_timestamp else 0),
|
||||||
"pubkeys": [self.address["pubkey"]] if self.data == Data.pub else [],
|
"pubkeys": [self.address["pubkey"]] if self.data == Data.pub else [],
|
||||||
"keys": [self.key] if self.data == Data.priv else [],
|
"keys": [self.key] if self.data == Data.priv else [],
|
||||||
|
"label": self.label,
|
||||||
"watchonly": self.data != Data.priv
|
"watchonly": self.data != Data.priv
|
||||||
}], {"rescan": self.rescan in (Rescan.yes, Rescan.late_timestamp)})
|
}], {"rescan": self.rescan in (Rescan.yes, Rescan.late_timestamp)})
|
||||||
assert_equal(response, [{"success": True}])
|
assert_equal(response, [{"success": True}])
|
||||||
|
|
||||||
def check(self, txid=None, amount=None, confirmations=None):
|
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'])
|
addresses = self.node.listreceivedbyaddress(minconf=0, include_watchonly=True, address_filter=self.address['address'])
|
||||||
if self.expected_txs:
|
if self.expected_txs:
|
||||||
assert_equal(len(addresses[0]["txids"]), self.expected_txs)
|
assert_equal(len(addresses[0]["txids"]), self.expected_txs)
|
||||||
|
|
||||||
if txid is not None:
|
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"]]
|
address, = [ad for ad in addresses if txid in ad["txids"]]
|
||||||
assert_equal(address["address"], self.address["address"])
|
assert_equal(address["address"], self.address["address"])
|
||||||
assert_equal(address["amount"], self.expected_balance)
|
assert_equal(address["amount"], self.expected_balance)
|
||||||
|
@ -136,7 +150,8 @@ class ImportRescanTest(BitcoinTestFramework):
|
||||||
# Create one transaction on node 0 with a unique amount for
|
# Create one transaction on node 0 with a unique amount for
|
||||||
# each possible type of wallet import RPC.
|
# each possible type of wallet import RPC.
|
||||||
for i, variant in enumerate(IMPORT_VARIANTS):
|
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.key = self.nodes[1].dumpprivkey(variant.address["address"])
|
||||||
variant.initial_amount = 10 - (i + 1) / 4.0
|
variant.initial_amount = 10 - (i + 1) / 4.0
|
||||||
variant.initial_txid = self.nodes[0].sendtoaddress(variant.address["address"], variant.initial_amount)
|
variant.initial_txid = self.nodes[0].sendtoaddress(variant.address["address"], variant.initial_amount)
|
||||||
|
|
|
@ -97,9 +97,10 @@ class ListTransactionsTest(BitcoinTestFramework):
|
||||||
txid = self.nodes[1].sendtoaddress(multisig["address"], 0.1)
|
txid = self.nodes[1].sendtoaddress(multisig["address"], 0.1)
|
||||||
self.nodes[1].generate(1)
|
self.nodes[1].generate(1)
|
||||||
self.sync_all()
|
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"]
|
assert len(self.nodes[0].listtransactions(label="watchonly", count=100, skip=0, include_watchonly=False)) == 0
|
||||||
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(self.nodes[0].listtransactions(label="watchonly", count=100, skip=0, include_watchonly=True),
|
||||||
assert_array_result(txs, {"category": "receive", "amount": Decimal("0.1")}, {"txid": txid})
|
{"category": "receive", "amount": Decimal("0.1")},
|
||||||
|
{"txid": txid, "label": "watchonly"})
|
||||||
|
|
||||||
self.run_rbf_opt_in_test()
|
self.run_rbf_opt_in_test()
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue