Skip to content

Commit 0d65106

Browse files
committed
Merge #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
2 parents 95a5918 + 72eaab0 commit 0d65106

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
@@ -52,6 +52,23 @@ static inline bool GetAvoidReuseFlag(CWallet * const pwallet, const UniValue& pa
5252
return avoid_reuse;
5353
}
5454

55+
56+
/** Used by RPC commands that have an include_watchonly parameter.
57+
* We default to true for watchonly wallets if include_watchonly isn't
58+
* explicitly set.
59+
*/
60+
static bool ParseIncludeWatchonly(const UniValue& include_watchonly, const CWallet& pwallet)
61+
{
62+
if (include_watchonly.isNull()) {
63+
// if include_watchonly isn't explicitly set, then check if we have a watchonly wallet
64+
return pwallet.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS);
65+
}
66+
67+
// otherwise return whatever include_watchonly was set to
68+
return include_watchonly.get_bool();
69+
}
70+
71+
5572
/** Checks if a CKey is in the given CWallet compressed or otherwise*/
5673
bool HaveKey(const CWallet& wallet, const CKey& key)
5774
{
@@ -710,7 +727,7 @@ static UniValue getbalance(const JSONRPCRequest& request)
710727
{
711728
{"dummy", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, "Remains for backward compatibility. Must be excluded or set to \"*\"."},
712729
{"minconf", RPCArg::Type::NUM, /* default */ "0", "Only include transactions confirmed at least this many times."},
713-
{"include_watchonly", RPCArg::Type::BOOL, /* default */ "false", "Also include balance in watch-only addresses (see 'importaddress')"},
730+
{"include_watchonly", RPCArg::Type::BOOL, /* default */ "true for watch-only wallets, otherwise false", "Also include balance in watch-only addresses (see 'importaddress')"},
714731
{"avoid_reuse", RPCArg::Type::BOOL, /* default */ "true", "(only available if avoid_reuse wallet flag is set) Do not include balance in dirty outputs; addresses are considered dirty if they have previously been used in a transaction."},
715732
},
716733
RPCResult{
@@ -743,10 +760,7 @@ static UniValue getbalance(const JSONRPCRequest& request)
743760
min_depth = request.params[1].get_int();
744761
}
745762

746-
bool include_watchonly = false;
747-
if (!request.params[2].isNull() && request.params[2].get_bool()) {
748-
include_watchonly = true;
749-
}
763+
bool include_watchonly = ParseIncludeWatchonly(request.params[2], *pwallet);
750764

751765
bool avoid_reuse = GetAvoidReuseFlag(pwallet, request.params[3]);
752766

@@ -1023,9 +1037,10 @@ static UniValue ListReceived(interfaces::Chain::Lock& locked_chain, CWallet * co
10231037
fIncludeEmpty = params[1].get_bool();
10241038

10251039
isminefilter filter = ISMINE_SPENDABLE;
1026-
if(!params[2].isNull())
1027-
if(params[2].get_bool())
1028-
filter = filter | ISMINE_WATCH_ONLY;
1040+
1041+
if (ParseIncludeWatchonly(params[2], *pwallet)) {
1042+
filter |= ISMINE_WATCH_ONLY;
1043+
}
10291044

10301045
bool has_filtered_address = false;
10311046
CTxDestination filtered_address = CNoDestination();
@@ -1169,7 +1184,7 @@ static UniValue listreceivedbyaddress(const JSONRPCRequest& request)
11691184
{
11701185
{"minconf", RPCArg::Type::NUM, /* default */ "1", "The minimum number of confirmations before payments are included."},
11711186
{"include_empty", RPCArg::Type::BOOL, /* default */ "false", "Whether to include addresses that haven't received any payments."},
1172-
{"include_watchonly", RPCArg::Type::BOOL, /* default */ "false", "Whether to include watch-only addresses (see 'importaddress')."},
1187+
{"include_watchonly", RPCArg::Type::BOOL, /* default */ "true for watch-only wallets, otherwise false", "Whether to include watch-only addresses (see 'importaddress')"},
11731188
{"address_filter", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, "If present, only return information on this address."},
11741189
},
11751190
RPCResult{
@@ -1220,7 +1235,7 @@ static UniValue listreceivedbylabel(const JSONRPCRequest& request)
12201235
{
12211236
{"minconf", RPCArg::Type::NUM, /* default */ "1", "The minimum number of confirmations before payments are included."},
12221237
{"include_empty", RPCArg::Type::BOOL, /* default */ "false", "Whether to include labels that haven't received any payments."},
1223-
{"include_watchonly", RPCArg::Type::BOOL, /* default */ "false", "Whether to include watch-only addresses (see 'importaddress')."},
1238+
{"include_watchonly", RPCArg::Type::BOOL, /* default */ "true for watch-only wallets, otherwise false", "Whether to include watch-only addresses (see 'importaddress')"},
12241239
},
12251240
RPCResult{
12261241
"[\n"
@@ -1361,7 +1376,7 @@ UniValue listtransactions(const JSONRPCRequest& request)
13611376
" with the specified label, or \"*\" to disable filtering and return all transactions."},
13621377
{"count", RPCArg::Type::NUM, /* default */ "10", "The number of transactions to return"},
13631378
{"skip", RPCArg::Type::NUM, /* default */ "0", "The number of transactions to skip"},
1364-
{"include_watchonly", RPCArg::Type::BOOL, /* default */ "false", "Include transactions to watch-only addresses (see 'importaddress')"},
1379+
{"include_watchonly", RPCArg::Type::BOOL, /* default */ "true for watch-only wallets, otherwise false", "Include transactions to watch-only addresses (see 'importaddress')"},
13651380
},
13661381
RPCResult{
13671382
"[\n"
@@ -1424,9 +1439,10 @@ UniValue listtransactions(const JSONRPCRequest& request)
14241439
if (!request.params[2].isNull())
14251440
nFrom = request.params[2].get_int();
14261441
isminefilter filter = ISMINE_SPENDABLE;
1427-
if(!request.params[3].isNull())
1428-
if(request.params[3].get_bool())
1429-
filter = filter | ISMINE_WATCH_ONLY;
1442+
1443+
if (ParseIncludeWatchonly(request.params[3], *pwallet)) {
1444+
filter |= ISMINE_WATCH_ONLY;
1445+
}
14301446

14311447
if (nCount < 0)
14321448
throw JSONRPCError(RPC_INVALID_PARAMETER, "Negative count");
@@ -1492,7 +1508,7 @@ static UniValue listsinceblock(const JSONRPCRequest& request)
14921508
{
14931509
{"blockhash", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, "If set, the block hash to list transactions since, otherwise list all transactions."},
14941510
{"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"},
1495-
{"include_watchonly", RPCArg::Type::BOOL, /* default */ "false", "Include transactions to watch-only addresses (see 'importaddress')"},
1511+
{"include_watchonly", RPCArg::Type::BOOL, /* default */ "true for watch-only wallets, otherwise false", "Include transactions to watch-only addresses (see 'importaddress')"},
14961512
{"include_removed", RPCArg::Type::BOOL, /* default */ "true", "Show transactions that were removed due to a reorg in the \"removed\" array\n"
14971513
" (not guaranteed to work on pruned nodes)"},
14981514
},
@@ -1569,8 +1585,8 @@ static UniValue listsinceblock(const JSONRPCRequest& request)
15691585
}
15701586
}
15711587

1572-
if (!request.params[2].isNull() && request.params[2].get_bool()) {
1573-
filter = filter | ISMINE_WATCH_ONLY;
1588+
if (ParseIncludeWatchonly(request.params[2], *pwallet)) {
1589+
filter |= ISMINE_WATCH_ONLY;
15741590
}
15751591

15761592
bool include_removed = (request.params[3].isNull() || request.params[3].get_bool());
@@ -1632,7 +1648,7 @@ static UniValue gettransaction(const JSONRPCRequest& request)
16321648
"\nGet detailed information about in-wallet transaction <txid>\n",
16331649
{
16341650
{"txid", RPCArg::Type::STR, RPCArg::Optional::NO, "The transaction id"},
1635-
{"include_watchonly", RPCArg::Type::BOOL, /* default */ "false", "Whether to include watch-only addresses in balance calculation and details[]"},
1651+
{"include_watchonly", RPCArg::Type::BOOL, /* default */ "true for watch-only wallets, otherwise false", "Whether to include watch-only addresses in balance calculation and details[]"},
16361652
},
16371653
RPCResult{
16381654
"{\n"
@@ -1687,9 +1703,10 @@ static UniValue gettransaction(const JSONRPCRequest& request)
16871703
uint256 hash(ParseHashV(request.params[0], "txid"));
16881704

16891705
isminefilter filter = ISMINE_SPENDABLE;
1690-
if(!request.params[1].isNull())
1691-
if(request.params[1].get_bool())
1692-
filter = filter | ISMINE_WATCH_ONLY;
1706+
1707+
if (ParseIncludeWatchonly(request.params[1], *pwallet)) {
1708+
filter |= ISMINE_WATCH_ONLY;
1709+
}
16931710

16941711
UniValue entry(UniValue::VOBJ);
16951712
auto it = pwallet->mapWallet.find(hash);
@@ -3015,8 +3032,7 @@ void FundTransaction(CWallet* const pwallet, CMutableTransaction& tx, CAmount& f
30153032
}
30163033
}
30173034

3018-
if (options.exists("includeWatching"))
3019-
coinControl.fAllowWatchOnly = options["includeWatching"].get_bool();
3035+
coinControl.fAllowWatchOnly = ParseIncludeWatchonly(options["includeWatching"], *pwallet);
30203036

30213037
if (options.exists("lockUnspents"))
30223038
lockUnspents = options["lockUnspents"].get_bool();
@@ -3048,6 +3064,9 @@ void FundTransaction(CWallet* const pwallet, CMutableTransaction& tx, CAmount& f
30483064
}
30493065
}
30503066
}
3067+
} else {
3068+
// if options is null and not a bool
3069+
coinControl.fAllowWatchOnly = ParseIncludeWatchonly(NullUniValue, *pwallet);
30513070
}
30523071

30533072
if (tx.vout.size() == 0)
@@ -3102,7 +3121,7 @@ static UniValue fundrawtransaction(const JSONRPCRequest& request)
31023121
{"changeAddress", RPCArg::Type::STR, /* default */ "pool address", "The bitcoin address to receive the change"},
31033122
{"changePosition", RPCArg::Type::NUM, /* default */ "random", "The index of the change output"},
31043123
{"change_type", RPCArg::Type::STR, /* default */ "set by -changetype", "The output type to use. Only valid if changeAddress is not specified. Options are \"legacy\", \"p2sh-segwit\", and \"bech32\"."},
3105-
{"includeWatching", RPCArg::Type::BOOL, /* default */ "false", "Also select inputs which are watch only"},
3124+
{"includeWatching", RPCArg::Type::BOOL, /* default */ "true for watch-only wallets, otherwise false", "Also select inputs which are watch only"},
31063125
{"lockUnspents", RPCArg::Type::BOOL, /* default */ "false", "Lock selected unspent outputs"},
31073126
{"feeRate", RPCArg::Type::AMOUNT, /* default */ "not set: makes wallet determine the fee", "Set a specific fee rate in " + CURRENCY_UNIT + "/kB"},
31083127
{"subtractFeeFromOutputs", RPCArg::Type::ARR, /* default */ "empty array", "A json array of integers.\n"
@@ -4047,7 +4066,7 @@ UniValue walletcreatefundedpsbt(const JSONRPCRequest& request)
40474066
{"changeAddress", RPCArg::Type::STR_HEX, /* default */ "pool address", "The bitcoin address to receive the change"},
40484067
{"changePosition", RPCArg::Type::NUM, /* default */ "random", "The index of the change output"},
40494068
{"change_type", RPCArg::Type::STR, /* default */ "set by -changetype", "The output type to use. Only valid if changeAddress is not specified. Options are \"legacy\", \"p2sh-segwit\", and \"bech32\"."},
4050-
{"includeWatching", RPCArg::Type::BOOL, /* default */ "false", "Also select inputs which are watch only"},
4069+
{"includeWatching", RPCArg::Type::BOOL, /* default */ "true for watch-only wallets, otherwise false", "Also select inputs which are watch only"},
40514070
{"lockUnspents", RPCArg::Type::BOOL, /* default */ "false", "Lock selected unspent outputs"},
40524071
{"feeRate", RPCArg::Type::AMOUNT, /* default */ "not set: makes wallet determine the fee", "Set a specific fee rate in " + CURRENCY_UNIT + "/kB"},
40534072
{"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
@@ -129,6 +129,8 @@
129129
'wallet_multiwallet.py --usecli',
130130
'wallet_createwallet.py',
131131
'wallet_createwallet.py --usecli',
132+
'wallet_watchonly.py',
133+
'wallet_watchonly.py --usecli',
132134
'interface_http.py',
133135
'interface_rpc.py',
134136
'rpc_psbt.py',

test/functional/wallet_watchonly.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)