Skip to content

Commit bed7ffc

Browse files
fanquakevijaydasmp
authored andcommitted
Merge bitcoin#16383: rpcwallet: default include_watchonly to true for watchonly wallets
72eaab0 tests: functional watch-only wallet tests (William Casarin) 72ffbdc doc: add release note for include_watchonly default changes (William Casarin) 003a3c7 rpcwallet: document include_watchonly default for watchonly wallets (William Casarin) a50d9e6 rpcwallet: default include_watchonly to true for watchonly wallets (William Casarin) Pull request description: Right now it's a bit annoying to deal with watchonly wallets, many rpc commands have an `include_watchonly` argument that needs to be explicitly set. Wallets created with `createwallet` can have a `disable_private_keys` parameter, for those wallets we already know that they are watchonly, so there's no reason to have to explicitly ask for it for every command. Instead we check this wallet flag when the `include_watchonly` parameter isn't set. ACKs for top commit: achow101: Code review ACK 72eaab0 Sjors: ACK 72eaab0 promag: ACK 72eaab0, code review only, didn't look closely to the test. kallewoof: ACK 72eaab0 fanquake: ACK 72eaab0 - I've looked over the changes, they make sense to me. Compiled and ran the tests etc. Tree-SHA512: d3646b55e97f386594d7efc994f0712f3888475c6a5dc7f131ac9f8c49bf5d4677182b88f42b34152abe1ad101ecadd152b4c20e9d3c1267190db36f77ab8bd7
1 parent 497b047 commit bed7ffc

File tree

4 files changed

+161
-25
lines changed

4 files changed

+161
-25
lines changed

doc/release-notes-16383.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
RPC changes
2+
-----------
3+
4+
RPCs which have an `include_watchonly` argument or `includeWatching`
5+
option now default to `true` for watch-only wallets. Affected RPCs
6+
are: `getbalance`, `listreceivedbyaddress`, `listreceivedbylabel`,
7+
`listtransactions`, `listsinceblock`, `gettransaction`,
8+
`walletcreatefundedpsbt`, and `fundrawtransaction`.

src/wallet/rpcwallet.cpp

Lines changed: 44 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,23 @@
5151

5252
static const std::string WALLET_ENDPOINT_BASE = "/wallet/";
5353

54+
/** Used by RPC commands that have an include_watchonly parameter.
55+
* We default to true for watchonly wallets if include_watchonly isn't
56+
* explicitly set.
57+
*/
58+
static bool ParseIncludeWatchonly(const UniValue& include_watchonly, const CWallet& pwallet)
59+
{
60+
if (include_watchonly.isNull()) {
61+
// if include_watchonly isn't explicitly set, then check if we have a watchonly wallet
62+
return pwallet.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS);
63+
}
64+
65+
// otherwise return whatever include_watchonly was set to
66+
return include_watchonly.get_bool();
67+
}
68+
69+
70+
5471
bool GetWalletNameFromJSONRPCRequest(const JSONRPCRequest& request, std::string& wallet_name)
5572
{
5673
if (request.URI.substr(0, WALLET_ENDPOINT_BASE.size()) == WALLET_ENDPOINT_BASE) {
@@ -780,7 +797,7 @@ static UniValue getbalance(const JSONRPCRequest& request)
780797
{"dummy", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, "Remains for backward compatibility. Must be excluded or set to \"*\"."},
781798
{"minconf", RPCArg::Type::NUM, /* default */ "0", "Only include transactions confirmed at least this many times."},
782799
{"addlocked", RPCArg::Type::BOOL, /* default */ "false", "Whether to include transactions locked via InstantSend in the wallet's balance."},
783-
{"include_watchonly", RPCArg::Type::BOOL, /* default */ "false", "Also include balance in watch-only addresses (see 'importaddress')"},
800+
{"include_watchonly", RPCArg::Type::BOOL, /* default */ "true for watch-only wallets, otherwise false", "Also include balance in watch-only addresses (see 'importaddress')"},
784801
},
785802
RPCResult{
786803
"amount (numeric) The total amount in " + CURRENCY_UNIT + " received for this wallet.\n"
@@ -818,10 +835,7 @@ static UniValue getbalance(const JSONRPCRequest& request)
818835
fAddLocked = addlocked.get_bool();
819836
}
820837

821-
bool include_watchonly = false;
822-
if (!request.params[3].isNull() && request.params[3].get_bool()) {
823-
include_watchonly = true;
824-
}
838+
bool include_watchonly = ParseIncludeWatchonly(request.params[2], *pwallet);
825839

826840
const auto bal = pwallet->GetBalance(min_depth, fAddLocked);
827841

@@ -1123,9 +1137,10 @@ static UniValue ListReceived(interfaces::Chain::Lock& locked_chain, CWallet * co
11231137
fIncludeEmpty = params[2].get_bool();
11241138

11251139
isminefilter filter = ISMINE_SPENDABLE;
1126-
if(!params[3].isNull())
1127-
if(params[3].get_bool())
1128-
filter = filter | ISMINE_WATCH_ONLY;
1140+
1141+
if (ParseIncludeWatchonly(params[3], *pwallet)) {
1142+
filter |= ISMINE_WATCH_ONLY;
1143+
}
11291144

11301145
bool has_filtered_address = false;
11311146
CTxDestination filtered_address = CNoDestination();
@@ -1275,7 +1290,7 @@ static UniValue listreceivedbyaddress(const JSONRPCRequest& request)
12751290
{"minconf", RPCArg::Type::NUM, /* default */ "1", "The minimum number of confirmations before payments are included."},
12761291
{"addlocked", RPCArg::Type::BOOL, /* default */ "false", "Whether to include transactions locked via InstantSend."},
12771292
{"include_empty", RPCArg::Type::BOOL, /* default */ "false", "Whether to include addresses that haven't received any payments."},
1278-
{"include_watchonly", RPCArg::Type::BOOL, /* default */ "false", "Whether to include watch-only addresses (see 'importaddress')."},
1293+
{"include_watchonly", RPCArg::Type::BOOL, /* default */ "true for watch-only wallets, otherwise false", "Whether to include watch-only addresses (see 'importaddress')"},
12791294
{"address_filter", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, "If present, only return information on this address."},
12801295
},
12811296
RPCResult{
@@ -1331,7 +1346,7 @@ static UniValue listreceivedbylabel(const JSONRPCRequest& request)
13311346
{"minconf", RPCArg::Type::NUM, /* default */ "1", "The minimum number of confirmations before payments are included."},
13321347
{"addlocked", RPCArg::Type::BOOL, /* default */ "false", "Whether to include transactions locked via InstantSend."},
13331348
{"include_empty", RPCArg::Type::BOOL, /* default */ "false", "Whether to include labels that haven't received any payments."},
1334-
{"include_watchonly", RPCArg::Type::BOOL, /* default */ "false", "Whether to include watch-only addresses (see 'importaddress')."},
1349+
{"include_watchonly", RPCArg::Type::BOOL, /* default */ "true for watch-only wallets, otherwise false", "Whether to include watch-only addresses (see 'importaddress')"},
13351350
},
13361351
RPCResult{
13371352
"[\n"
@@ -1475,7 +1490,7 @@ static UniValue listtransactions(const JSONRPCRequest& request)
14751490
" with the specified label, or \"*\" to disable filtering and return all transactions."},
14761491
{"count", RPCArg::Type::NUM, /* default */ "10", "The number of transactions to return"},
14771492
{"skip", RPCArg::Type::NUM, /* default */ "0", "The number of transactions to skip"},
1478-
{"include_watchonly", RPCArg::Type::BOOL, /* default */ "false", "Include transactions to watch-only addresses (see 'importaddress')"},
1493+
{"include_watchonly", RPCArg::Type::BOOL, /* default */ "true for watch-only wallets, otherwise false", "Include transactions to watch-only addresses (see 'importaddress')"},
14791494
},
14801495
RPCResult{
14811496
"[\n"
@@ -1543,9 +1558,10 @@ static UniValue listtransactions(const JSONRPCRequest& request)
15431558
if (!request.params[2].isNull())
15441559
nFrom = request.params[2].get_int();
15451560
isminefilter filter = ISMINE_SPENDABLE;
1546-
if(!request.params[3].isNull())
1547-
if(request.params[3].get_bool())
1548-
filter = filter | ISMINE_WATCH_ONLY;
1561+
1562+
if (ParseIncludeWatchonly(request.params[3], *pwallet)) {
1563+
filter |= ISMINE_WATCH_ONLY;
1564+
}
15491565

15501566
if (nCount < 0)
15511567
throw JSONRPCError(RPC_INVALID_PARAMETER, "Negative count");
@@ -1600,7 +1616,7 @@ static UniValue listsinceblock(const JSONRPCRequest& request)
16001616
{
16011617
{"blockhash", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, "If set, the block hash to list transactions since, otherwise list all transactions."},
16021618
{"target_confirmations", RPCArg::Type::NUM, /* default */ "1", "Return the nth block hash from the main chain. e.g. 1 would mean the best block hash. Note: this is not used as a filter, but only affects [lastblock] in the return value"},
1603-
{"include_watchonly", RPCArg::Type::BOOL, /* default */ "false", "Include transactions to watch-only addresses (see 'importaddress')"},
1619+
{"include_watchonly", RPCArg::Type::BOOL, /* default */ "true for watch-only wallets, otherwise false", "Include transactions to watch-only addresses (see 'importaddress')"},
16041620
{"include_removed", RPCArg::Type::BOOL, /* default */ "true", "Show transactions that were removed due to a reorg in the \"removed\" array\n"
16051621
" (not guaranteed to work on pruned nodes)"},
16061622
},
@@ -1674,8 +1690,8 @@ static UniValue listsinceblock(const JSONRPCRequest& request)
16741690
}
16751691
}
16761692

1677-
if (!request.params[2].isNull() && request.params[2].get_bool()) {
1678-
filter = filter | ISMINE_WATCH_ONLY;
1693+
if (ParseIncludeWatchonly(request.params[2], *pwallet)) {
1694+
filter |= ISMINE_WATCH_ONLY;
16791695
}
16801696

16811697
bool include_removed = (request.params[3].isNull() || request.params[3].get_bool());
@@ -1739,7 +1755,7 @@ static UniValue gettransaction(const JSONRPCRequest& request)
17391755
"\nGet detailed information about in-wallet transaction <txid>\n",
17401756
{
17411757
{"txid", RPCArg::Type::STR, RPCArg::Optional::NO, "The transaction id"},
1742-
{"include_watchonly", RPCArg::Type::BOOL, /* default */ "false", "Whether to include watch-only addresses in balance calculation and details[]"},
1758+
{"include_watchonly", RPCArg::Type::BOOL, /* default */ "true for watch-only wallets, otherwise false", "Whether to include watch-only addresses in balance calculation and details[]"},
17431759
},
17441760
RPCResult{
17451761
"{\n"
@@ -1791,9 +1807,10 @@ static UniValue gettransaction(const JSONRPCRequest& request)
17911807
hash.SetHex(request.params[0].get_str());
17921808

17931809
isminefilter filter = ISMINE_SPENDABLE;
1794-
if(!request.params[1].isNull())
1795-
if(request.params[1].get_bool())
1796-
filter = filter | ISMINE_WATCH_ONLY;
1810+
1811+
if (ParseIncludeWatchonly(request.params[1], *pwallet)) {
1812+
filter |= ISMINE_WATCH_ONLY;
1813+
}
17971814

17981815
UniValue entry(UniValue::VOBJ);
17991816
auto it = pwallet->mapWallet.find(hash);
@@ -3268,8 +3285,7 @@ void FundTransaction(CWallet* const pwallet, CMutableTransaction& tx, CAmount& f
32683285
if (options.exists("changePosition"))
32693286
change_position = options["changePosition"].get_int();
32703287

3271-
if (options.exists("includeWatching"))
3272-
coinControl.fAllowWatchOnly = options["includeWatching"].get_bool();
3288+
coinControl.fAllowWatchOnly = ParseIncludeWatchonly(options["includeWatching"], *pwallet);
32733289

32743290
if (options.exists("lockUnspents"))
32753291
lockUnspents = options["lockUnspents"].get_bool();
@@ -3297,6 +3313,9 @@ void FundTransaction(CWallet* const pwallet, CMutableTransaction& tx, CAmount& f
32973313
}
32983314
}
32993315
}
3316+
} else {
3317+
// if options is null and not a bool
3318+
coinControl.fAllowWatchOnly = ParseIncludeWatchonly(NullUniValue, *pwallet);
33003319
}
33013320

33023321
if (tx.vout.size() == 0)
@@ -3352,7 +3371,7 @@ static UniValue fundrawtransaction(const JSONRPCRequest& request)
33523371
{
33533372
{"changeAddress", RPCArg::Type::STR, /* default */ "pool address", "The dash address to receive the change"},
33543373
{"changePosition", RPCArg::Type::NUM, /* default */ "random", "The index of the change output"},
3355-
{"includeWatching", RPCArg::Type::BOOL, /* default */ "false", "Also select inputs which are watch only"},
3374+
{"includeWatching", RPCArg::Type::BOOL, /* default */ "true for watch-only wallets, otherwise false", "Also select inputs which are watch only"},
33563375
{"lockUnspents", RPCArg::Type::BOOL, /* default */ "false", "Lock selected unspent outputs"},
33573376
{"feeRate", RPCArg::Type::AMOUNT, /* default */ "not set: makes wallet determine the fee", "Set a specific fee rate in " + CURRENCY_UNIT + "/kB"},
33583377
{"subtractFeeFromOutputs", RPCArg::Type::ARR, /* default */ "empty array", "A json array of integers.\n"
@@ -4072,7 +4091,7 @@ UniValue walletcreatefundedpsbt(const JSONRPCRequest& request)
40724091
{
40734092
{"changeAddress", RPCArg::Type::STR_HEX, /* default */ "pool address", "The dash address to receive the change"},
40744093
{"changePosition", RPCArg::Type::NUM, /* default */ "random", "The index of the change output"},
4075-
{"includeWatching", RPCArg::Type::BOOL, /* default */ "false", "Also select inputs which are watch only"},
4094+
{"includeWatching", RPCArg::Type::BOOL, /* default */ "true for watch-only wallets, otherwise false", "Also select inputs which are watch only"},
40764095
{"lockUnspents", RPCArg::Type::BOOL, /* default */ "false", "Lock selected unspent outputs"},
40774096
{"feeRate", RPCArg::Type::AMOUNT, /* default */ "not set: makes wallet determine the fee", "Set a specific fee rate in " + CURRENCY_UNIT + "/kB"},
40784097
{"subtractFeeFromOutputs", RPCArg::Type::ARR, /* default */ "empty array", "A json array of integers.\n"

test/functional/test_runner.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,8 @@
137137
'wallet_multiwallet.py',
138138
'wallet_disableprivatekeys.py',
139139
'wallet_disableprivatekeys.py --usecli',
140+
'wallet_watchonly.py',
141+
'wallet_watchonly.py --usecli',
140142
'interface_http.py',
141143
'interface_rpc.py',
142144
'rpc_psbt.py',
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
#!/usr/bin/env python3
2+
# Copyright (c) 2018-2019 The Bitcoin Core developers
3+
# Distributed under the MIT software license, see the accompanying
4+
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
5+
"""Test createwallet arguments.
6+
"""
7+
8+
from test_framework.test_framework import BitcoinTestFramework
9+
from test_framework.util import (
10+
assert_equal,
11+
assert_raises_rpc_error
12+
)
13+
14+
15+
class CreateWalletWatchonlyTest(BitcoinTestFramework):
16+
def set_test_params(self):
17+
self.setup_clean_chain = False
18+
self.num_nodes = 1
19+
self.supports_cli = True
20+
21+
def skip_test_if_missing_module(self):
22+
self.skip_if_no_wallet()
23+
24+
def run_test(self):
25+
node = self.nodes[0]
26+
27+
self.nodes[0].createwallet(wallet_name='default')
28+
def_wallet = node.get_wallet_rpc('default')
29+
30+
a1 = def_wallet.getnewaddress()
31+
wo_change = def_wallet.getnewaddress()
32+
wo_addr = def_wallet.getnewaddress()
33+
34+
self.nodes[0].createwallet(wallet_name='wo', disable_private_keys=True)
35+
wo_wallet = node.get_wallet_rpc('wo')
36+
37+
wo_wallet.importpubkey(pubkey=def_wallet.getaddressinfo(wo_addr)['pubkey'])
38+
wo_wallet.importpubkey(pubkey=def_wallet.getaddressinfo(wo_change)['pubkey'])
39+
40+
# generate some btc for testing
41+
node.generatetoaddress(101, a1)
42+
43+
# send 1 btc to our watch-only address
44+
txid = def_wallet.sendtoaddress(wo_addr, 1)
45+
self.nodes[0].generate(1)
46+
47+
# getbalance
48+
self.log.info('include_watchonly should default to true for watch-only wallets')
49+
self.log.info('Testing getbalance watch-only defaults')
50+
assert_equal(wo_wallet.getbalance(), 1)
51+
assert_equal(len(wo_wallet.listtransactions()), 1)
52+
assert_equal(wo_wallet.getbalance(include_watchonly=False), 0)
53+
54+
self.log.info('Testing listreceivedbyaddress watch-only defaults')
55+
result = wo_wallet.listreceivedbyaddress()
56+
assert_equal(len(result), 1)
57+
assert_equal(result[0]["involvesWatchonly"], True)
58+
result = wo_wallet.listreceivedbyaddress(include_watchonly=False)
59+
assert_equal(len(result), 0)
60+
61+
self.log.info('Testing listreceivedbylabel watch-only defaults')
62+
result = wo_wallet.listreceivedbylabel()
63+
assert_equal(len(result), 1)
64+
assert_equal(result[0]["involvesWatchonly"], True)
65+
result = wo_wallet.listreceivedbylabel(include_watchonly=False)
66+
assert_equal(len(result), 0)
67+
68+
self.log.info('Testing listtransactions watch-only defaults')
69+
result = wo_wallet.listtransactions()
70+
assert_equal(len(result), 1)
71+
assert_equal(result[0]["involvesWatchonly"], True)
72+
result = wo_wallet.listtransactions(include_watchonly=False)
73+
assert_equal(len(result), 0)
74+
75+
self.log.info('Testing listsinceblock watch-only defaults')
76+
result = wo_wallet.listsinceblock()
77+
assert_equal(len(result["transactions"]), 1)
78+
assert_equal(result["transactions"][0]["involvesWatchonly"], True)
79+
result = wo_wallet.listsinceblock(include_watchonly=False)
80+
assert_equal(len(result["transactions"]), 0)
81+
82+
self.log.info('Testing gettransaction watch-only defaults')
83+
result = wo_wallet.gettransaction(txid)
84+
assert_equal(result["details"][0]["involvesWatchonly"], True)
85+
result = wo_wallet.gettransaction(txid=txid, include_watchonly=False)
86+
assert_equal(len(result["details"]), 0)
87+
88+
self.log.info('Testing walletcreatefundedpsbt watch-only defaults')
89+
inputs = []
90+
outputs = [{a1: 0.5}]
91+
options = {'changeAddress': wo_change}
92+
no_wo_options = {'changeAddress': wo_change, 'includeWatching': False}
93+
94+
result = wo_wallet.walletcreatefundedpsbt(inputs=inputs, outputs=outputs, options=options)
95+
assert_equal("psbt" in result, True)
96+
assert_raises_rpc_error(-4, "Insufficient funds", wo_wallet.walletcreatefundedpsbt, inputs, outputs, 0, no_wo_options)
97+
98+
self.log.info('Testing fundrawtransaction watch-only defaults')
99+
rawtx = wo_wallet.createrawtransaction(inputs=inputs, outputs=outputs)
100+
result = wo_wallet.fundrawtransaction(hexstring=rawtx, options=options)
101+
assert_equal("hex" in result, True)
102+
assert_raises_rpc_error(-4, "Insufficient funds", wo_wallet.fundrawtransaction, rawtx, no_wo_options)
103+
104+
105+
106+
if __name__ == '__main__':
107+
CreateWalletWatchonlyTest().main()

0 commit comments

Comments
 (0)