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
|
||||
=================
|
||||
|
||||
...
|
||||
`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
|
||||
=======
|
||||
|
|
|
@ -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);
|
||||
|
@ -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"
|
||||
|
@ -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;
|
||||
|
@ -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"} },
|
||||
|
|
|
@ -46,11 +46,11 @@ class Variant(collections.namedtuple("Variant", "call data rescan prune")):
|
|||
|
||||
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:
|
||||
|
@ -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),
|
||||
"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)
|
||||
|
@ -136,7 +150,8 @@ class ImportRescanTest(BitcoinTestFramework):
|
|||
# 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)
|
||||
|
|
|
@ -97,9 +97,10 @@ class ListTransactionsTest(BitcoinTestFramework):
|
|||
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()
|
||||
|
||||
|
|
Loading…
Reference in a new issue