Branch data Line data Source code
1 : : // Copyright (c) 2010 Satoshi Nakamoto
2 : : // Copyright (c) 2009-present 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 : :
6 : : #include <bitcoin-build-config.h> // IWYU pragma: keep
7 : :
8 : : #include <wallet/rpc/wallet.h>
9 : :
10 : : #include <coins.h>
11 : : #include <core_io.h>
12 : : #include <key_io.h>
13 : : #include <rpc/server.h>
14 : : #include <rpc/util.h>
15 : : #include <univalue.h>
16 : : #include <util/translation.h>
17 : : #include <wallet/context.h>
18 : : #include <wallet/receive.h>
19 : : #include <wallet/rpc/util.h>
20 : : #include <wallet/wallet.h>
21 : : #include <wallet/walletutil.h>
22 : :
23 : : #include <optional>
24 : : #include <string_view>
25 : :
26 : :
27 : : namespace wallet {
28 : :
29 : : static const std::map<uint64_t, std::string> WALLET_FLAG_CAVEATS{
30 : : {WALLET_FLAG_AVOID_REUSE,
31 : : "You need to rescan the blockchain in order to correctly mark used "
32 : : "destinations in the past. Until this is done, some destinations may "
33 : : "be considered unused, even if the opposite is the case."},
34 : : };
35 : :
36 : 1270 : static RPCMethod getwalletinfo()
37 : : {
38 : 1270 : return RPCMethod{"getwalletinfo",
39 [ + - ]: 2540 : "Returns an object containing various wallet state info.\n",
40 : : {},
41 [ + - ]: 2540 : RPCResult{
42 [ + - ]: 2540 : RPCResult::Type::OBJ, "", "",
43 : : {
44 : : {
45 [ + - + - ]: 2540 : {RPCResult::Type::STR, "walletname", "the wallet name"},
46 [ + - + - ]: 2540 : {RPCResult::Type::NUM, "walletversion", "(DEPRECATED) only related to unsupported legacy wallet, returns the latest version 169900 for backwards compatibility"},
47 [ + - + - ]: 2540 : {RPCResult::Type::STR, "format", "the database format (only sqlite)"},
48 [ + - + - ]: 2540 : {RPCResult::Type::NUM, "txcount", "the total number of transactions in the wallet"},
49 [ + - + - ]: 2540 : {RPCResult::Type::NUM, "keypoolsize", "how many new keys are pre-generated (only counts external keys)"},
50 [ + - + - ]: 2540 : {RPCResult::Type::NUM, "keypoolsize_hd_internal", /*optional=*/true, "how many new keys are pre-generated for internal use (used for change outputs, only appears if the wallet is using this feature, otherwise external keys are used)"},
51 [ + - + - ]: 2540 : {RPCResult::Type::NUM_TIME, "unlocked_until", /*optional=*/true, "the " + UNIX_EPOCH_TIME + " until which the wallet is unlocked for transfers, or 0 if the wallet is locked (only present for passphrase-encrypted wallets)"},
52 [ + - + - ]: 2540 : {RPCResult::Type::BOOL, "private_keys_enabled", "false if privatekeys are disabled for this wallet (enforced watch-only wallet)"},
53 [ + - + - ]: 2540 : {RPCResult::Type::BOOL, "avoid_reuse", "whether this wallet tracks clean/dirty coins in terms of reuse"},
54 [ + - + - ]: 2540 : {RPCResult::Type::OBJ, "scanning", "current scanning details, or false if no scan is in progress",
55 : : {
56 [ + - + - ]: 2540 : {RPCResult::Type::NUM, "duration", "elapsed seconds since scan start"},
57 [ + - + - ]: 2540 : {RPCResult::Type::NUM, "progress", "scanning progress percentage [0.0, 1.0]"},
58 : : }, {.skip_type_check=true}, },
59 [ + - + - ]: 2540 : {RPCResult::Type::BOOL, "descriptors", "whether this wallet uses descriptors for output script management"},
60 [ + - + - ]: 2540 : {RPCResult::Type::BOOL, "external_signer", "whether this wallet is configured to use an external signer such as a hardware wallet"},
61 [ + - + - ]: 2540 : {RPCResult::Type::BOOL, "blank", "Whether this wallet intentionally does not contain any keys, scripts, or descriptors"},
62 [ + - + - ]: 2540 : {RPCResult::Type::NUM_TIME, "birthtime", /*optional=*/true, "The start time for blocks scanning. It could be modified by (re)importing any descriptor with an earlier timestamp."},
63 [ + - + - ]: 2540 : {RPCResult::Type::ARR, "flags", "The flags currently set on the wallet",
64 : : {
65 [ + - + - ]: 2540 : {RPCResult::Type::STR, "flag", "The name of the flag"},
66 : : }},
67 : : RESULT_LAST_PROCESSED_BLOCK,
68 : : }},
69 [ + - + - : 52070 : },
+ - + - +
+ + + + +
- - - - -
- ]
70 : 1270 : RPCExamples{
71 [ + - + - : 2540 : HelpExampleCli("getwalletinfo", "")
+ - ]
72 [ + - + - : 5080 : + HelpExampleRpc("getwalletinfo", "")
+ - + - ]
73 [ + - ]: 1270 : },
74 : 1270 : [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
75 : : {
76 [ - + ]: 431 : const std::shared_ptr<const CWallet> pwallet = GetWalletForJSONRPCRequest(request);
77 [ - + ]: 421 : if (!pwallet) return UniValue::VNULL;
78 : :
79 : : // Make sure the results are valid at least up to the most recent block
80 : : // the user could have gotten from another RPC command prior to now
81 [ + - ]: 421 : pwallet->BlockUntilSyncedToCurrentChain();
82 : :
83 [ + - ]: 421 : LOCK(pwallet->cs_wallet);
84 : :
85 : 421 : UniValue obj(UniValue::VOBJ);
86 : :
87 : 421 : const int latest_legacy_wallet_minversion{169900};
88 : :
89 [ + - ]: 421 : size_t kpExternalSize = pwallet->KeypoolCountExternalKeys();
90 [ + - + - : 842 : obj.pushKV("walletname", pwallet->GetName());
+ - ]
91 [ + - + - : 842 : obj.pushKV("walletversion", latest_legacy_wallet_minversion);
+ - ]
92 [ + - + - : 842 : obj.pushKV("format", pwallet->GetDatabase().Format());
+ - + - ]
93 [ + - + - : 842 : obj.pushKV("txcount", pwallet->mapWallet.size());
+ - ]
94 [ + - + - : 842 : obj.pushKV("keypoolsize", kpExternalSize);
+ - ]
95 [ + - + - : 842 : obj.pushKV("keypoolsize_hd_internal", pwallet->GetKeyPoolSize() - kpExternalSize);
+ - + - ]
96 : :
97 [ + - + + ]: 421 : if (pwallet->HasEncryptionKeys()) {
98 [ + - + - : 66 : obj.pushKV("unlocked_until", pwallet->nRelockTime);
+ - ]
99 : : }
100 [ + - + - : 842 : obj.pushKV("private_keys_enabled", !pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS));
+ - + - ]
101 [ + - + - : 842 : obj.pushKV("avoid_reuse", pwallet->IsWalletFlagSet(WALLET_FLAG_AVOID_REUSE));
+ - + - ]
102 [ - + ]: 421 : if (pwallet->IsScanning()) {
103 : 0 : UniValue scanning(UniValue::VOBJ);
104 [ # # # # : 0 : scanning.pushKV("duration", Ticks<std::chrono::seconds>(pwallet->ScanningDuration()));
# # ]
105 [ # # # # : 0 : scanning.pushKV("progress", pwallet->ScanningProgress());
# # ]
106 [ # # # # ]: 0 : obj.pushKV("scanning", std::move(scanning));
107 : 0 : } else {
108 [ + - + - : 842 : obj.pushKV("scanning", false);
+ - ]
109 : : }
110 [ + - + - : 842 : obj.pushKV("descriptors", pwallet->IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS));
+ - + - ]
111 [ + - + - : 842 : obj.pushKV("external_signer", pwallet->IsWalletFlagSet(WALLET_FLAG_EXTERNAL_SIGNER));
+ - + - ]
112 [ + - + - : 842 : obj.pushKV("blank", pwallet->IsWalletFlagSet(WALLET_FLAG_BLANK_WALLET));
+ - + - ]
113 [ + + ]: 421 : if (int64_t birthtime = pwallet->GetBirthTime(); birthtime != UNKNOWN_TIME) {
114 [ + - + - : 764 : obj.pushKV("birthtime", birthtime);
+ - ]
115 : : }
116 : :
117 : : // Push known flags
118 : 421 : UniValue flags(UniValue::VARR);
119 [ + - ]: 421 : uint64_t wallet_flags = pwallet->GetWalletFlags();
120 [ + + ]: 27365 : for (uint64_t i = 0; i < 64; ++i) {
121 : 26944 : uint64_t flag = uint64_t{1} << i;
122 [ + + ]: 26944 : if (flag & wallet_flags) {
123 [ + - ]: 1064 : if (flag & KNOWN_WALLET_FLAGS) {
124 [ + - + - : 1064 : flags.push_back(WALLET_FLAG_TO_STRING.at(WalletFlags{flag}));
+ - ]
125 : : } else {
126 [ # # # # : 0 : flags.push_back(strprintf("unknown_flag_%u", i));
# # ]
127 : : }
128 : : }
129 : : }
130 [ + - + - : 842 : obj.pushKV("flags", flags);
+ - ]
131 : :
132 [ + - ]: 421 : AppendLastProcessedBlock(obj, *pwallet);
133 : 421 : return obj;
134 [ + - ]: 1263 : },
135 [ + - + - ]: 5080 : };
136 [ + - + - : 45720 : }
+ - + - +
- + - + -
+ - + - +
- + - + -
+ - + - +
- + - + -
+ - + - -
- - - ]
137 : :
138 : 913 : static RPCMethod listwalletdir()
139 : : {
140 : 913 : return RPCMethod{"listwalletdir",
141 [ + - ]: 1826 : "Returns a list of wallets in the wallet directory.\n",
142 : : {},
143 [ + - ]: 1826 : RPCResult{
144 [ + - ]: 1826 : RPCResult::Type::OBJ, "", "",
145 : : {
146 [ + - + - ]: 1826 : {RPCResult::Type::ARR, "wallets", "",
147 : : {
148 [ + - + - ]: 1826 : {RPCResult::Type::OBJ, "", "",
149 : : {
150 [ + - + - ]: 1826 : {RPCResult::Type::STR, "name", "The wallet name"},
151 [ + - + - ]: 1826 : {RPCResult::Type::ARR, "warnings", /*optional=*/true, "Warning messages, if any, related to loading the wallet.",
152 : : {
153 [ + - + - ]: 1826 : {RPCResult::Type::STR, "", ""},
154 : : }},
155 : : }},
156 : : }},
157 : : }
158 [ + - + - : 13695 : },
+ - + - +
- + + + +
+ + + + -
- - - - -
- - ]
159 : 913 : RPCExamples{
160 [ + - + - : 1826 : HelpExampleCli("listwalletdir", "")
+ - ]
161 [ + - + - : 3652 : + HelpExampleRpc("listwalletdir", "")
+ - + - ]
162 [ + - ]: 913 : },
163 : 913 : [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
164 : : {
165 : 74 : UniValue wallets(UniValue::VARR);
166 [ + - + - : 1703 : for (const auto& [path, db_type] : ListDatabases(GetWalletDir())) {
+ + ]
167 : 1555 : UniValue wallet(UniValue::VOBJ);
168 [ + - + - : 3110 : wallet.pushKV("name", path.utf8string());
+ - + - ]
169 : 1555 : UniValue warnings(UniValue::VARR);
170 [ + + ]: 1555 : if (db_type == "bdb") {
171 [ + - + - ]: 82 : warnings.push_back("This wallet is a legacy wallet and will need to be migrated with migratewallet before it can be loaded");
172 : : }
173 [ + - + - : 3110 : wallet.pushKV("warnings", warnings);
+ - ]
174 [ + - ]: 1555 : wallets.push_back(std::move(wallet));
175 : 1629 : }
176 : :
177 : 74 : UniValue result(UniValue::VOBJ);
178 [ + - + - ]: 148 : result.pushKV("wallets", std::move(wallets));
179 : 74 : return result;
180 : 74 : },
181 [ + - + - ]: 3652 : };
182 [ + - + - : 9130 : }
+ - + - +
- - - ]
183 : :
184 : 901 : static RPCMethod listwallets()
185 : : {
186 : 901 : return RPCMethod{"listwallets",
187 [ + - ]: 1802 : "Returns a list of currently loaded wallets.\n"
188 : : "For full information on the wallet, use \"getwalletinfo\"\n",
189 : : {},
190 [ + - ]: 1802 : RPCResult{
191 [ + - ]: 1802 : RPCResult::Type::ARR, "", "",
192 : : {
193 [ + - + - ]: 1802 : {RPCResult::Type::STR, "walletname", "the wallet name"},
194 : : }
195 [ + - + - : 3604 : },
+ + - - ]
196 : 901 : RPCExamples{
197 [ + - + - : 1802 : HelpExampleCli("listwallets", "")
+ - ]
198 [ + - + - : 3604 : + HelpExampleRpc("listwallets", "")
+ - + - ]
199 [ + - ]: 901 : },
200 : 901 : [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
201 : : {
202 : 62 : UniValue obj(UniValue::VARR);
203 : :
204 [ + - ]: 62 : WalletContext& context = EnsureWalletContext(request.context);
205 [ + - + + ]: 565 : for (const std::shared_ptr<CWallet>& wallet : GetWallets(context)) {
206 [ + - ]: 503 : LOCK(wallet->cs_wallet);
207 [ + - + - : 503 : obj.push_back(wallet->GetName());
+ - ]
208 : 565 : }
209 : :
210 : 62 : return obj;
211 : 0 : },
212 [ + - + - ]: 3604 : };
213 [ + - ]: 1802 : }
214 : :
215 : 995 : static RPCMethod loadwallet()
216 : : {
217 : 995 : return RPCMethod{
218 : 995 : "loadwallet",
219 [ + - ]: 1990 : "Loads a wallet from a wallet file or directory."
220 : : "\nNote that all wallet command-line options used when starting bitcoind will be"
221 : : "\napplied to the new wallet.\n",
222 : : {
223 [ + - + - ]: 1990 : {"filename", RPCArg::Type::STR, RPCArg::Optional::NO, "The path to the directory of the wallet to be loaded, either absolute or relative to the \"wallets\" directory. The \"wallets\" directory is set by the -walletdir option and defaults to the \"wallets\" folder within the data directory."},
224 [ + - + - ]: 1990 : {"load_on_startup", RPCArg::Type::BOOL, RPCArg::Optional::OMITTED, "Save wallet name to persistent settings and load on startup. True to add wallet to startup list, false to remove, null to leave unchanged."},
225 : : },
226 [ + - ]: 1990 : RPCResult{
227 [ + - + - ]: 1990 : RPCResult::Type::OBJ, "", "",
228 : : {
229 [ + - + - ]: 1990 : {RPCResult::Type::STR, "name", "The wallet name if loaded successfully."},
230 [ + - + - ]: 1990 : {RPCResult::Type::ARR, "warnings", /*optional=*/true, "Warning messages, if any, related to loading the wallet.",
231 : : {
232 [ + - + - ]: 1990 : {RPCResult::Type::STR, "", ""},
233 : : }},
234 : : }
235 [ + - + - : 8955 : },
+ - + + +
+ - - -
- ]
236 : 995 : RPCExamples{
237 : : "\nLoad wallet from the wallet dir:\n"
238 [ + - + - : 1990 : + HelpExampleCli("loadwallet", "\"walletname\"")
+ - + - ]
239 [ + - + - : 3980 : + HelpExampleRpc("loadwallet", "\"walletname\"")
+ - + - ]
240 : 995 : + "\nLoad wallet using absolute path (Unix):\n"
241 [ + - + - : 3980 : + HelpExampleCli("loadwallet", "\"/path/to/walletname/\"")
+ - + - ]
242 [ + - + - : 3980 : + HelpExampleRpc("loadwallet", "\"/path/to/walletname/\"")
+ - + - ]
243 : 995 : + "\nLoad wallet using absolute path (Windows):\n"
244 [ + - + - : 3980 : + HelpExampleCli("loadwallet", "\"DriveLetter:\\path\\to\\walletname\\\"")
+ - + - ]
245 [ + - + - : 3980 : + HelpExampleRpc("loadwallet", "\"DriveLetter:\\path\\to\\walletname\\\"")
+ - + - ]
246 [ + - ]: 995 : },
247 : 995 : [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
248 : : {
249 : 156 : WalletContext& context = EnsureWalletContext(request.context);
250 [ - + ]: 156 : const std::string name(request.params[0].get_str());
251 : :
252 [ + - ]: 156 : DatabaseOptions options;
253 : 156 : DatabaseStatus status;
254 [ + - ]: 156 : ReadDatabaseArgs(*context.args, options);
255 : 156 : options.require_existing = true;
256 [ + - ]: 156 : bilingual_str error;
257 : 156 : std::vector<bilingual_str> warnings;
258 [ + - + + : 156 : std::optional<bool> load_on_start = request.params[1].isNull() ? std::nullopt : std::optional<bool>(request.params[1].get_bool());
+ - + - ]
259 : :
260 : 156 : {
261 [ + - ]: 156 : LOCK(context.wallets_mutex);
262 [ + + + + ]: 797 : if (std::any_of(context.wallets.begin(), context.wallets.end(), [&name](const auto& wallet) { return wallet->GetName() == name; })) {
263 [ + - + - ]: 6 : throw JSONRPCError(RPC_WALLET_ALREADY_LOADED, "Wallet \"" + name + "\" is already loaded.");
264 : : }
265 : 2 : }
266 : :
267 [ + - ]: 154 : std::shared_ptr<CWallet> const wallet = LoadWallet(context, name, load_on_start, options, status, error, warnings);
268 : :
269 [ + + ]: 154 : HandleWalletError(wallet, status, error);
270 : :
271 : 136 : UniValue obj(UniValue::VOBJ);
272 [ + - + - : 272 : obj.pushKV("name", wallet->GetName());
+ - ]
273 [ + - ]: 136 : PushWarnings(warnings, obj);
274 : :
275 [ + - ]: 136 : return obj;
276 : 332 : },
277 [ + - + - : 5970 : };
+ + - - ]
278 [ + - + - : 9950 : }
+ - + - +
- - - -
- ]
279 : :
280 : 847 : static RPCMethod setwalletflag()
281 : : {
282 : 847 : std::string flags;
283 [ + + ]: 6776 : for (auto& it : STRING_TO_WALLET_FLAG)
284 [ + + ]: 5929 : if (it.second & MUTABLE_WALLET_FLAGS)
285 [ - + + - ]: 1694 : flags += (flags == "" ? "" : ", ") + it.first;
286 : :
287 : 847 : return RPCMethod{
288 [ + - ]: 1694 : "setwalletflag",
289 [ + - ]: 1694 : "Change the state of the given wallet flag for a wallet.\n",
290 : : {
291 [ + - + - ]: 1694 : {"flag", RPCArg::Type::STR, RPCArg::Optional::NO, "The name of the flag to change. Current available flags: " + flags},
292 [ + - + - : 2541 : {"value", RPCArg::Type::BOOL, RPCArg::Default{true}, "The new state."},
+ - ]
293 : : },
294 [ + - ]: 1694 : RPCResult{
295 [ + - + - ]: 1694 : RPCResult::Type::OBJ, "", "",
296 : : {
297 [ + - + - ]: 1694 : {RPCResult::Type::STR, "flag_name", "The name of the flag that was modified"},
298 [ + - + - ]: 1694 : {RPCResult::Type::BOOL, "flag_state", "The new state of the flag"},
299 [ + - + - ]: 1694 : {RPCResult::Type::STR, "warnings", /*optional=*/true, "Any warnings associated with the change"},
300 : : }
301 [ + - + - : 6776 : },
+ + - - ]
302 : 847 : RPCExamples{
303 [ + - + - : 1694 : HelpExampleCli("setwalletflag", "avoid_reuse")
+ - ]
304 [ + - + - : 3388 : + HelpExampleRpc("setwalletflag", "\"avoid_reuse\"")
+ - + - ]
305 [ + - ]: 847 : },
306 : 847 : [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
307 : : {
308 : 8 : std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);
309 [ - + ]: 8 : if (!pwallet) return UniValue::VNULL;
310 : :
311 [ + - + - : 8 : std::string flag_str = request.params[0].get_str();
- + ]
312 [ + - + + : 8 : bool value = request.params[1].isNull() || request.params[1].get_bool();
+ - + - +
+ ]
313 : :
314 [ + + ]: 8 : if (!STRING_TO_WALLET_FLAG.contains(flag_str)) {
315 [ + - + - ]: 2 : throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Unknown wallet flag: %s", flag_str));
316 : : }
317 : :
318 [ + - ]: 7 : auto flag = STRING_TO_WALLET_FLAG.at(flag_str);
319 : :
320 [ + + ]: 7 : if (!(flag & MUTABLE_WALLET_FLAGS)) {
321 [ + - + - ]: 6 : throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Wallet flag is immutable: %s", flag_str));
322 : : }
323 : :
324 : 4 : UniValue res(UniValue::VOBJ);
325 : :
326 [ + - + + ]: 4 : if (pwallet->IsWalletFlagSet(flag) == value) {
327 [ + + + - : 5 : throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Wallet flag is already set to %s: %s", value ? "true" : "false", flag_str));
+ - ]
328 : : }
329 : :
330 [ + - + - : 4 : res.pushKV("flag_name", flag_str);
+ - ]
331 [ + - + - : 4 : res.pushKV("flag_state", value);
+ - ]
332 : :
333 [ + + ]: 2 : if (value) {
334 [ + - ]: 1 : pwallet->SetWalletFlag(flag);
335 : : } else {
336 [ + - ]: 1 : pwallet->UnsetWalletFlag(flag);
337 : : }
338 : :
339 [ + - + + : 2 : if (flag && value && WALLET_FLAG_CAVEATS.contains(flag)) {
+ - ]
340 [ + - + - : 2 : res.pushKV("warnings", WALLET_FLAG_CAVEATS.at(flag));
+ - + - ]
341 : : }
342 : :
343 : 2 : return res;
344 : 12 : },
345 [ + - + - : 4235 : };
+ + - - ]
346 [ + - + - : 9317 : }
+ - + - +
- - - -
- ]
347 : :
348 : 1470 : static RPCMethod createwallet()
349 : : {
350 : 1470 : return RPCMethod{
351 : 1470 : "createwallet",
352 [ + - ]: 2940 : "Creates and loads a new wallet.\n",
353 : : {
354 [ + - + - ]: 2940 : {"wallet_name", RPCArg::Type::STR, RPCArg::Optional::NO, "The name for the new wallet. If this is a path, the wallet will be created at the path location."},
355 [ + - + - : 4410 : {"disable_private_keys", RPCArg::Type::BOOL, RPCArg::Default{false}, "Disable the possibility of private keys (only watchonlys are possible in this mode)."},
+ - ]
356 [ + - + - : 4410 : {"blank", RPCArg::Type::BOOL, RPCArg::Default{false}, "Create a blank wallet. A blank wallet has no keys."},
+ - ]
357 [ + - + - ]: 2940 : {"passphrase", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "Encrypt the wallet with this passphrase."},
358 [ + - + - : 4410 : {"avoid_reuse", RPCArg::Type::BOOL, RPCArg::Default{false}, "Keep track of coin reuse, and treat dirty and clean coins differently with privacy considerations in mind."},
+ - ]
359 [ + - + - : 4410 : {"descriptors", RPCArg::Type::BOOL, RPCArg::Default{true}, "If set, must be \"true\""},
+ - ]
360 [ + - + - ]: 2940 : {"load_on_startup", RPCArg::Type::BOOL, RPCArg::Optional::OMITTED, "Save wallet name to persistent settings and load on startup. True to add wallet to startup list, false to remove, null to leave unchanged."},
361 [ + - + - : 4410 : {"external_signer", RPCArg::Type::BOOL, RPCArg::Default{false}, "Use an external signer such as a hardware wallet. Requires -signer to be configured. Wallet creation will fail if keys cannot be fetched. Requires disable_private_keys and descriptors set to true."},
+ - ]
362 : : },
363 [ + - ]: 2940 : RPCResult{
364 [ + - + - ]: 2940 : RPCResult::Type::OBJ, "", "",
365 : : {
366 [ + - + - ]: 2940 : {RPCResult::Type::STR, "name", "The wallet name if created successfully. If the wallet was created using a full path, the wallet_name will be the full path."},
367 [ + - + - ]: 2940 : {RPCResult::Type::ARR, "warnings", /*optional=*/true, "Warning messages, if any, related to creating and loading the wallet.",
368 : : {
369 [ + - + - ]: 2940 : {RPCResult::Type::STR, "", ""},
370 : : }},
371 : : }
372 [ + - + - : 13230 : },
+ - + + +
+ - - -
- ]
373 : 1470 : RPCExamples{
374 [ + - + - : 2940 : HelpExampleCli("createwallet", "\"testwallet\"")
+ - ]
375 [ + - + - : 5880 : + HelpExampleRpc("createwallet", "\"testwallet\"")
+ - + - ]
376 [ + - + - : 10290 : + HelpExampleCliNamed("createwallet", {{"wallet_name", "descriptors"}, {"avoid_reuse", true}, {"load_on_startup", true}})
+ - + - +
+ - - ]
377 [ + - + - : 10290 : + HelpExampleRpcNamed("createwallet", {{"wallet_name", "descriptors"}, {"avoid_reuse", true}, {"load_on_startup", true}})
+ - + - +
+ - - ]
378 [ + - ]: 1470 : },
379 : 1470 : [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
380 : : {
381 : 631 : WalletContext& context = EnsureWalletContext(request.context);
382 : 631 : uint64_t flags = 0;
383 [ + + + + ]: 631 : if (!request.params[1].isNull() && request.params[1].get_bool()) {
384 : : flags |= WALLET_FLAG_DISABLE_PRIVATE_KEYS;
385 : : }
386 : :
387 [ + + + + ]: 631 : if (!request.params[2].isNull() && request.params[2].get_bool()) {
388 : 164 : flags |= WALLET_FLAG_BLANK_WALLET;
389 : : }
390 [ + - ]: 631 : SecureString passphrase;
391 [ + - ]: 631 : passphrase.reserve(100);
392 : 631 : std::vector<bilingual_str> warnings;
393 [ + - + + ]: 631 : if (!request.params[3].isNull()) {
394 [ + - + - : 16 : passphrase = std::string_view{request.params[3].get_str()};
- + + - ]
395 [ + + ]: 16 : if (passphrase.empty()) {
396 : : // Empty string means unencrypted
397 [ + - + - : 8 : warnings.emplace_back(Untranslated("Empty string given as passphrase, wallet will not be encrypted."));
+ - ]
398 : : }
399 : : }
400 : :
401 [ + - + + : 631 : if (!request.params[4].isNull() && request.params[4].get_bool()) {
+ - + - +
+ ]
402 : 3 : flags |= WALLET_FLAG_AVOID_REUSE;
403 : : }
404 : 631 : flags |= WALLET_FLAG_DESCRIPTORS;
405 [ + - + + ]: 631 : if (!self.Arg<bool>("descriptors")) {
406 [ + - + - ]: 4 : throw JSONRPCError(RPC_WALLET_ERROR, "descriptors argument must be set to \"true\"; it is no longer possible to create a legacy wallet.");
407 : : }
408 [ + - + + : 629 : if (!request.params[7].isNull() && request.params[7].get_bool()) {
+ - + - +
+ ]
409 : : #ifdef ENABLE_EXTERNAL_SIGNER
410 : 6 : flags |= WALLET_FLAG_EXTERNAL_SIGNER;
411 : : #else
412 : : throw JSONRPCError(RPC_WALLET_ERROR, "Compiled without external signing support (required for external signing)");
413 : : #endif
414 : : }
415 : :
416 [ + - ]: 629 : DatabaseOptions options;
417 : 629 : DatabaseStatus status;
418 [ + - ]: 629 : ReadDatabaseArgs(*context.args, options);
419 : 629 : options.require_create = true;
420 : 629 : options.create_flags = flags;
421 [ + - ]: 629 : options.create_passphrase = passphrase;
422 [ + - ]: 629 : bilingual_str error;
423 [ + - + + : 629 : std::optional<bool> load_on_start = request.params[6].isNull() ? std::nullopt : std::optional<bool>(request.params[6].get_bool());
+ - + - ]
424 [ + - + - : 629 : const std::shared_ptr<CWallet> wallet = CreateWallet(context, request.params[0].get_str(), load_on_start, options, status, error, warnings);
+ + ]
425 [ + + ]: 626 : HandleWalletError(wallet, status, error);
426 : :
427 : 591 : UniValue obj(UniValue::VOBJ);
428 [ + - + - : 1182 : obj.pushKV("name", wallet->GetName());
+ - ]
429 [ + - ]: 591 : PushWarnings(warnings, obj);
430 : :
431 [ + - ]: 591 : return obj;
432 : 1295 : },
433 [ + - + - : 17640 : };
+ + - - ]
434 [ + - + - : 35280 : }
+ - + - +
- + - + -
+ - + - +
- + - + -
+ - + - +
- + - + -
- - - - -
- - - ]
435 : :
436 : 1136 : static RPCMethod unloadwallet()
437 : : {
438 : 1136 : return RPCMethod{"unloadwallet",
439 [ + - ]: 2272 : "Unloads the wallet referenced by the request endpoint or the wallet_name argument.\n"
440 : : "If both are specified, they must be identical.",
441 : : {
442 [ + - + - : 3408 : {"wallet_name", RPCArg::Type::STR, RPCArg::DefaultHint{"the wallet name from the RPC endpoint"}, "The name of the wallet to unload. If provided both here and in the RPC endpoint, the two must be identical."},
+ - ]
443 [ + - + - ]: 2272 : {"load_on_startup", RPCArg::Type::BOOL, RPCArg::Optional::OMITTED, "Save wallet name to persistent settings and load on startup. True to add wallet to startup list, false to remove, null to leave unchanged."},
444 : : },
445 [ + - + - : 4544 : RPCResult{RPCResult::Type::OBJ, "", "", {
+ - ]
446 [ + - + - ]: 2272 : {RPCResult::Type::ARR, "warnings", /*optional=*/true, "Warning messages, if any, related to unloading the wallet.",
447 : : {
448 [ + - + - ]: 2272 : {RPCResult::Type::STR, "", ""},
449 : : }},
450 [ + - + - : 7952 : }},
+ - + + +
+ - - -
- ]
451 : 1136 : RPCExamples{
452 [ + - + - : 2272 : HelpExampleCli("unloadwallet", "wallet_name")
+ - ]
453 [ + - + - : 4544 : + HelpExampleRpc("unloadwallet", "wallet_name")
+ - + - ]
454 [ + - ]: 1136 : },
455 : 1136 : [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
456 : : {
457 : 297 : const std::string wallet_name{EnsureUniqueWalletName(request, self.MaybeArg<std::string_view>("wallet_name"))};
458 : :
459 [ + - ]: 293 : WalletContext& context = EnsureWalletContext(request.context);
460 [ + - ]: 293 : std::shared_ptr<CWallet> wallet = GetWallet(context, wallet_name);
461 [ + + ]: 293 : if (!wallet) {
462 [ + - + - ]: 8 : throw JSONRPCError(RPC_WALLET_NOT_FOUND, "Requested wallet does not exist or is not loaded");
463 : : }
464 : :
465 : 289 : std::vector<bilingual_str> warnings;
466 : 289 : {
467 : 289 : WalletRescanReserver reserver(*wallet);
468 [ - + ]: 289 : if (!reserver.reserve()) {
469 [ # # # # ]: 0 : throw JSONRPCError(RPC_WALLET_ERROR, "Wallet is currently rescanning. Abort existing rescan or wait.");
470 : : }
471 : :
472 : : // Release the "main" shared pointer and prevent further notifications.
473 : : // Note that any attempt to load the same wallet would fail until the wallet
474 : : // is destroyed (see CheckUniqueFileid).
475 [ + - ]: 289 : std::optional<bool> load_on_start{self.MaybeArg<bool>("load_on_startup")};
476 [ + - - + ]: 289 : if (!RemoveWallet(context, wallet, load_on_start, warnings)) {
477 [ # # # # ]: 0 : throw JSONRPCError(RPC_MISC_ERROR, "Requested wallet already unloaded");
478 : : }
479 : 289 : }
480 : :
481 [ + - ]: 289 : WaitForDeleteWallet(std::move(wallet));
482 : :
483 : 289 : UniValue result(UniValue::VOBJ);
484 [ + - ]: 289 : PushWarnings(warnings, result);
485 : :
486 : 578 : return result;
487 [ - + ]: 293 : },
488 [ + - + - : 6816 : };
+ + - - ]
489 [ + - + - : 9088 : }
+ - + - -
- ]
490 : :
491 : 865 : RPCMethod simulaterawtransaction()
492 : : {
493 : 865 : return RPCMethod{
494 : 865 : "simulaterawtransaction",
495 [ + - ]: 1730 : "Calculate the balance change resulting in the signing and broadcasting of the given transaction(s).\n",
496 : : {
497 [ + - + - ]: 1730 : {"rawtxs", RPCArg::Type::ARR, RPCArg::Optional::OMITTED, "An array of hex strings of raw transactions.\n",
498 : : {
499 [ + - + - ]: 1730 : {"rawtx", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED, ""},
500 : : },
501 : : },
502 [ + - + - ]: 1730 : {"options", RPCArg::Type::OBJ_NAMED_PARAMS, RPCArg::Optional::OMITTED, "",
503 : : {
504 [ + - + - : 2595 : {"include_watchonly", RPCArg::Type::BOOL, RPCArg::Default{false}, "(DEPRECATED) No longer used"},
+ - ]
505 : : },
506 : : },
507 : : },
508 [ + - ]: 1730 : RPCResult{
509 [ + - + - ]: 1730 : RPCResult::Type::OBJ, "", "",
510 : : {
511 [ + - + - ]: 1730 : {RPCResult::Type::STR_AMOUNT, "balance_change", "The wallet balance change (negative means decrease)."},
512 : : }
513 [ + - + - : 3460 : },
+ + - - ]
514 : 865 : RPCExamples{
515 [ + - + - : 1730 : HelpExampleCli("simulaterawtransaction", "[\"myhex\"]")
+ - ]
516 [ + - + - : 3460 : + HelpExampleRpc("simulaterawtransaction", "[\"myhex\"]")
+ - + - ]
517 [ + - ]: 865 : },
518 : 865 : [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
519 : : {
520 [ - + ]: 26 : const std::shared_ptr<const CWallet> rpc_wallet = GetWalletForJSONRPCRequest(request);
521 [ - + ]: 26 : if (!rpc_wallet) return UniValue::VNULL;
522 [ + - ]: 26 : const CWallet& wallet = *rpc_wallet;
523 : :
524 [ + - ]: 26 : LOCK(wallet.cs_wallet);
525 : :
526 [ + - + - ]: 26 : const auto& txs = request.params[0].get_array();
527 : 26 : CAmount changes{0};
528 : 26 : std::map<COutPoint, CAmount> new_utxos; // UTXO:s that were made available in transaction array
529 : 26 : std::set<COutPoint> spent;
530 : :
531 [ - + + + ]: 54 : for (size_t i = 0; i < txs.size(); ++i) {
532 [ + - ]: 38 : CMutableTransaction mtx;
533 [ + - + - : 38 : if (!DecodeHexTx(mtx, txs[i].get_str(), /*try_no_witness=*/ true, /*try_witness=*/ true)) {
+ - - + ]
534 [ # # # # ]: 0 : throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "Transaction hex string decoding failure.");
535 : : }
536 : :
537 : : // Fetch previous transactions (inputs)
538 : 38 : std::map<COutPoint, Coin> coins;
539 [ + + ]: 67 : for (const CTxIn& txin : mtx.vin) {
540 [ + - ]: 29 : coins[txin.prevout]; // Create empty map entry keyed by prevout.
541 : : }
542 [ + - ]: 38 : wallet.chain().findCoins(coins);
543 : :
544 : : // Fetch debit; we are *spending* these; if the transaction is signed and
545 : : // broadcast, we will lose everything in these
546 [ + + ]: 57 : for (const auto& txin : mtx.vin) {
547 : 29 : const auto& outpoint = txin.prevout;
548 [ + + ]: 29 : if (spent.contains(outpoint)) {
549 [ + - + - ]: 6 : throw JSONRPCError(RPC_INVALID_PARAMETER, "Transaction(s) are spending the same output more than once");
550 : : }
551 [ + + ]: 26 : if (new_utxos.contains(outpoint)) {
552 [ + - ]: 6 : changes -= new_utxos.at(outpoint);
553 : 6 : new_utxos.erase(outpoint);
554 : : } else {
555 [ + - + + ]: 20 : if (coins.at(outpoint).IsSpent()) {
556 [ + - + - ]: 14 : throw JSONRPCError(RPC_INVALID_PARAMETER, "One or more transaction inputs are missing or have been spent already");
557 : : }
558 [ + - ]: 13 : changes -= wallet.GetDebit(txin);
559 : : }
560 [ + - ]: 19 : spent.insert(outpoint);
561 : : }
562 : :
563 : : // Iterate over outputs; we are *receiving* these, if the wallet considers
564 : : // them "mine"; if the transaction is signed and broadcast, we will receive
565 : : // everything in these
566 : : // Also populate new_utxos in case these are spent in later transactions
567 : :
568 [ + - ]: 28 : const auto& hash = mtx.GetHash();
569 [ - + + + ]: 69 : for (size_t i = 0; i < mtx.vout.size(); ++i) {
570 [ + - ]: 41 : const auto& txout = mtx.vout[i];
571 [ + - ]: 41 : bool is_mine = wallet.IsMine(txout);
572 [ + + + - ]: 41 : changes += new_utxos[COutPoint(hash, i)] = is_mine ? txout.nValue : 0;
573 : : }
574 : 76 : }
575 : :
576 : 16 : UniValue result(UniValue::VOBJ);
577 [ + - + - : 32 : result.pushKV("balance_change", ValueFromAmount(changes));
+ - ]
578 : :
579 : 16 : return result;
580 [ + - ]: 78 : }
581 [ + - + - : 10380 : };
+ - + - +
+ + + + +
- - - - -
- ]
582 [ + - + - : 8650 : }
+ - + - +
- - - ]
583 : :
584 : 888 : static RPCMethod migratewallet()
585 : : {
586 : 888 : return RPCMethod{
587 : 888 : "migratewallet",
588 [ + - ]: 1776 : "Migrate the wallet to a descriptor wallet.\n"
589 : : "A new wallet backup will need to be made.\n"
590 : : "\nThe migration process will create a backup of the wallet before migrating. This backup\n"
591 : : "file will be named <wallet name>-<timestamp>.legacy.bak and can be found in the directory\n"
592 : : "for this wallet. In the event of an incorrect migration, the backup can be restored using restorewallet."
593 : : "\nEncrypted wallets must have the passphrase provided as an argument to this call.\n"
594 : : "\nThis RPC may take a long time to complete. Increasing the RPC client timeout is recommended.",
595 : : {
596 [ + - + - : 2664 : {"wallet_name", RPCArg::Type::STR, RPCArg::DefaultHint{"the wallet name from the RPC endpoint"}, "The name of the wallet to migrate. If provided both here and in the RPC endpoint, the two must be identical."},
+ - ]
597 [ + - + - ]: 1776 : {"passphrase", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "The wallet passphrase"},
598 : : },
599 [ + - ]: 1776 : RPCResult{
600 [ + - + - ]: 1776 : RPCResult::Type::OBJ, "", "",
601 : : {
602 [ + - + - ]: 1776 : {RPCResult::Type::STR, "wallet_name", "The name of the primary migrated wallet"},
603 [ + - + - ]: 1776 : {RPCResult::Type::STR, "watchonly_name", /*optional=*/true, "The name of the migrated wallet containing the watchonly scripts"},
604 [ + - + - ]: 1776 : {RPCResult::Type::STR, "solvables_name", /*optional=*/true, "The name of the migrated wallet containing solvable but not watched scripts"},
605 [ + - + - ]: 1776 : {RPCResult::Type::STR, "backup_path", "The location of the backup of the original wallet"},
606 : : }
607 [ + - + - : 8880 : },
+ + - - ]
608 : 888 : RPCExamples{
609 [ + - + - : 1776 : HelpExampleCli("migratewallet", "")
+ - ]
610 [ + - + - : 3552 : + HelpExampleRpc("migratewallet", "")
+ - + - ]
611 [ + - ]: 888 : },
612 : 888 : [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
613 : : {
614 : 49 : const std::string wallet_name{EnsureUniqueWalletName(request, self.MaybeArg<std::string_view>("wallet_name"))};
615 : :
616 [ + - ]: 47 : SecureString wallet_pass;
617 [ + - ]: 47 : wallet_pass.reserve(100);
618 [ + - + + ]: 47 : if (!request.params[1].isNull()) {
619 [ + - + - : 3 : wallet_pass = std::string_view{request.params[1].get_str()};
- + + - ]
620 : : }
621 : :
622 [ + - ]: 47 : WalletContext& context = EnsureWalletContext(request.context);
623 [ + - ]: 47 : util::Result<MigrationResult> res = MigrateLegacyToDescriptor(wallet_name, wallet_pass, context);
624 [ + + ]: 47 : if (!res) {
625 [ + - + - ]: 24 : throw JSONRPCError(RPC_WALLET_ERROR, util::ErrorString(res).original);
626 : : }
627 : :
628 : 35 : UniValue r{UniValue::VOBJ};
629 [ + - + - : 70 : r.pushKV("wallet_name", res->wallet_name);
+ - ]
630 [ + + ]: 35 : if (res->watchonly_wallet) {
631 [ + - + - : 16 : r.pushKV("watchonly_name", res->watchonly_wallet->GetName());
+ - ]
632 : : }
633 [ + + ]: 35 : if (res->solvables_wallet) {
634 [ + - + - : 10 : r.pushKV("solvables_name", res->solvables_wallet->GetName());
+ - ]
635 : : }
636 [ + - + - : 70 : r.pushKV("backup_path", res->backup_path.utf8string());
+ - + - ]
637 : :
638 : 35 : return r;
639 : 59 : },
640 [ + - + - : 5328 : };
+ + - - ]
641 [ + - + - : 10656 : }
+ - + - +
- + - - -
- - ]
642 : :
643 : 879 : RPCMethod gethdkeys()
644 : : {
645 : 879 : return RPCMethod{
646 : 879 : "gethdkeys",
647 [ + - ]: 1758 : "List all BIP 32 HD keys in the wallet and which descriptors use them.\n",
648 : : {
649 [ + - + - ]: 1758 : {"options", RPCArg::Type::OBJ_NAMED_PARAMS, RPCArg::Optional::OMITTED, "", {
650 [ + - + - : 2637 : {"active_only", RPCArg::Type::BOOL, RPCArg::Default{false}, "Show the keys for only active descriptors"},
+ - ]
651 [ + - + - : 2637 : {"private", RPCArg::Type::BOOL, RPCArg::Default{false}, "Show private keys"}
+ - ]
652 : : }},
653 : : },
654 [ + - + - : 3516 : RPCResult{RPCResult::Type::ARR, "", "", {
+ - ]
655 : : {
656 [ + - + - ]: 1758 : {RPCResult::Type::OBJ, "", "", {
657 [ + - + - ]: 1758 : {RPCResult::Type::STR, "xpub", "The extended public key"},
658 [ + - + - ]: 1758 : {RPCResult::Type::BOOL, "has_private", "Whether the wallet has the private key for this xpub"},
659 [ + - + - ]: 1758 : {RPCResult::Type::STR, "xprv", /*optional=*/true, "The extended private key if \"private\" is true"},
660 [ + - + - ]: 1758 : {RPCResult::Type::ARR, "descriptors", "Array of descriptor objects that use this HD key",
661 : : {
662 [ + - + - ]: 1758 : {RPCResult::Type::OBJ, "", "", {
663 [ + - + - ]: 1758 : {RPCResult::Type::STR, "desc", "Descriptor string public representation"},
664 [ + - + - ]: 1758 : {RPCResult::Type::BOOL, "active", "Whether this descriptor is currently used to generate new addresses"},
665 : : }},
666 : : }},
667 : : }},
668 : : }
669 [ + - + - : 18459 : }},
+ - + - +
- + + + +
+ + + + -
- - - - -
- - ]
670 : 879 : RPCExamples{
671 [ + - + - : 1758 : HelpExampleCli("gethdkeys", "") + HelpExampleRpc("gethdkeys", "")
+ - + - +
- + - +
- ]
672 [ + - + - : 8790 : + HelpExampleCliNamed("gethdkeys", {{"active_only", "true"}, {"private", "true"}}) + HelpExampleRpcNamed("gethdkeys", {{"active_only", "true"}, {"private", "true"}})
+ - + - +
- + - + -
+ - + + +
+ - - -
- ]
673 [ + - ]: 879 : },
674 : 879 : [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
675 : : {
676 [ - + ]: 40 : const std::shared_ptr<const CWallet> wallet = GetWalletForJSONRPCRequest(request);
677 [ - + ]: 40 : if (!wallet) return UniValue::VNULL;
678 : :
679 [ + - ]: 40 : LOCK(wallet->cs_wallet);
680 : :
681 [ + - + + : 40 : UniValue options{request.params[0].isNull() ? UniValue::VOBJ : request.params[0]};
+ - + - ]
682 [ + - + + : 86 : const bool active_only{options.exists("active_only") ? options["active_only"].get_bool() : false};
+ - + - +
- ]
683 [ + - + + : 92 : const bool priv{options.exists("private") ? options["private"].get_bool() : false};
+ - + - +
- ]
684 [ + + ]: 40 : if (priv) {
685 [ + + ]: 12 : EnsureWalletIsUnlocked(*wallet);
686 : : }
687 : :
688 : :
689 [ + + ]: 39 : std::set<ScriptPubKeyMan*> spkms;
690 [ + + ]: 39 : if (active_only) {
691 [ + - ]: 12 : spkms = wallet->GetActiveScriptPubKeyMans();
692 : : } else {
693 [ + - ]: 66 : spkms = wallet->GetAllScriptPubKeyMans();
694 : : }
695 : :
696 : 39 : std::map<CExtPubKey, std::set<std::tuple<std::string, bool, bool>>> wallet_xpubs;
697 : 39 : std::map<CExtPubKey, CExtKey> wallet_xprvs;
698 [ + + ]: 317 : for (auto* spkm : spkms) {
699 [ + - ]: 278 : auto* desc_spkm{dynamic_cast<DescriptorScriptPubKeyMan*>(spkm)};
700 [ + - ]: 278 : CHECK_NONFATAL(desc_spkm);
701 [ + - ]: 278 : LOCK(desc_spkm->cs_desc_man);
702 [ + - ]: 278 : WalletDescriptor w_desc = desc_spkm->GetWalletDescriptor();
703 : :
704 : : // Retrieve the pubkeys from the descriptor
705 [ + - ]: 278 : std::set<CPubKey> desc_pubkeys;
706 : 278 : std::set<CExtPubKey> desc_xpubs;
707 [ + - ]: 278 : w_desc.descriptor->GetPubKeys(desc_pubkeys, desc_xpubs);
708 [ + + ]: 549 : for (const CExtPubKey& xpub : desc_xpubs) {
709 [ + - ]: 271 : std::string desc_str;
710 [ + - ]: 271 : bool ok = desc_spkm->GetDescriptorString(desc_str, /*priv=*/false);
711 [ + - ]: 271 : CHECK_NONFATAL(ok);
712 [ + - + - : 271 : wallet_xpubs[xpub].emplace(desc_str, wallet->IsActiveScriptPubKeyMan(*spkm), desc_spkm->HasPrivKey(xpub.pubkey.GetID()));
+ - + - +
- ]
713 [ + + + - : 271 : if (std::optional<CKey> key = priv ? desc_spkm->GetKey(xpub.pubkey.GetID()) : std::nullopt) {
+ - + + ]
714 [ + - + - ]: 89 : wallet_xprvs[xpub] = CExtKey(xpub, *key);
715 : 271 : }
716 : 271 : }
717 [ + - ]: 556 : }
718 : :
719 : 39 : UniValue response(UniValue::VARR);
720 [ + + ]: 83 : for (const auto& [xpub, descs] : wallet_xpubs) {
721 : 44 : bool has_xprv = false;
722 : 44 : UniValue descriptors(UniValue::VARR);
723 [ + + ]: 315 : for (const auto& [desc, active, has_priv] : descs) {
724 : 271 : UniValue d(UniValue::VOBJ);
725 [ + - + - : 542 : d.pushKV("desc", desc);
+ - ]
726 [ + - + - : 542 : d.pushKV("active", active);
+ - ]
727 : 271 : has_xprv |= has_priv;
728 : :
729 [ + - ]: 271 : descriptors.push_back(std::move(d));
730 : 271 : }
731 : 44 : UniValue xpub_info(UniValue::VOBJ);
732 [ + - + - : 88 : xpub_info.pushKV("xpub", EncodeExtPubKey(xpub));
+ - + - ]
733 [ + - + - : 88 : xpub_info.pushKV("has_private", has_xprv);
+ - ]
734 [ + + + + ]: 44 : if (priv && has_xprv) {
735 [ + - + - : 22 : xpub_info.pushKV("xprv", EncodeExtKey(wallet_xprvs.at(xpub)));
+ - + - +
- ]
736 : : }
737 [ + - + - ]: 88 : xpub_info.pushKV("descriptors", std::move(descriptors));
738 : :
739 [ + - ]: 44 : response.push_back(std::move(xpub_info));
740 : 44 : }
741 : :
742 : 39 : return response;
743 [ + - ]: 119 : },
744 [ + - + - : 7911 : };
+ - + + +
+ - - -
- ]
745 [ + - + - : 21096 : }
+ - + - +
- + - + -
+ - + - +
- + - + -
+ - + - +
- - - - -
- - - - -
- ]
746 : :
747 : 852 : static RPCMethod createwalletdescriptor()
748 : : {
749 : 852 : return RPCMethod{"createwalletdescriptor",
750 : : "Creates the wallet's descriptor for the given address type. "
751 : : "The address type must be one that the wallet does not already have a descriptor for."
752 [ + - ]: 1704 : + HELP_REQUIRING_PASSPHRASE,
753 : : {
754 [ + - + - : 1704 : {"type", RPCArg::Type::STR, RPCArg::Optional::NO, "The address type the descriptor will produce. Options are " + FormatAllOutputTypes() + "."},
+ - ]
755 [ + - + - ]: 1704 : {"options", RPCArg::Type::OBJ_NAMED_PARAMS, RPCArg::Optional::OMITTED, "", {
756 [ + - + - : 2556 : {"internal", RPCArg::Type::BOOL, RPCArg::DefaultHint{"Both external and internal will be generated unless this parameter is specified"}, "Whether to only make one descriptor that is internal (if parameter is true) or external (if parameter is false)"},
+ - ]
757 [ + - + - : 2556 : {"hdkey", RPCArg::Type::STR, RPCArg::DefaultHint{"The HD key used by all other active descriptors"}, "The HD key that the wallet knows the private key of, listed using 'gethdkeys', to use for this descriptor's key"},
+ - ]
758 : : }},
759 : : },
760 [ + - ]: 1704 : RPCResult{
761 [ + - + - ]: 1704 : RPCResult::Type::OBJ, "", "",
762 : : {
763 [ + - + - ]: 1704 : {RPCResult::Type::ARR, "descs", "The public descriptors that were added to the wallet",
764 [ + - + - ]: 1704 : {{RPCResult::Type::STR, "", ""}}
765 : : }
766 : : },
767 [ + - + - : 5964 : },
+ - + + +
+ - - -
- ]
768 : 852 : RPCExamples{
769 [ + - + - : 1704 : HelpExampleCli("createwalletdescriptor", "bech32m")
+ - ]
770 [ + - + - : 3408 : + HelpExampleRpc("createwalletdescriptor", "bech32m")
+ - + - ]
771 [ + - ]: 852 : },
772 : 852 : [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
773 : : {
774 : 13 : std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);
775 [ - + ]: 13 : if (!pwallet) return UniValue::VNULL;
776 : :
777 [ + - + - : 13 : std::optional<OutputType> output_type = ParseOutputType(request.params[0].get_str());
- + + - ]
778 [ + + ]: 13 : if (!output_type) {
779 [ + - + - : 2 : throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Unknown address type '%s'", request.params[0].get_str()));
+ - + - ]
780 : : }
781 : :
782 [ + - + + : 12 : UniValue options{request.params[1].isNull() ? UniValue::VOBJ : request.params[1]};
+ - + - ]
783 [ + - + - : 12 : UniValue internal_only{options["internal"]};
+ - ]
784 [ + - + - : 12 : UniValue hdkey{options["hdkey"]};
+ - ]
785 : :
786 : 12 : std::vector<bool> internals;
787 [ + + ]: 12 : if (internal_only.isNull()) {
788 [ + - ]: 10 : internals.push_back(false);
789 [ + - ]: 10 : internals.push_back(true);
790 : : } else {
791 [ + - + - ]: 2 : internals.push_back(internal_only.get_bool());
792 : : }
793 : :
794 [ + - ]: 12 : LOCK(pwallet->cs_wallet);
795 [ + + ]: 12 : EnsureWalletIsUnlocked(*pwallet);
796 : :
797 [ + + ]: 11 : CExtPubKey xpub;
798 [ + + ]: 11 : if (hdkey.isNull()) {
799 [ + - ]: 7 : std::set<CExtPubKey> active_xpubs = pwallet->GetActiveHDPubKeys();
800 [ + + ]: 7 : if (active_xpubs.size() != 1) {
801 [ + - + - ]: 4 : throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Unable to determine which HD key to use from active descriptors. Please specify with 'hdkey'");
802 : : }
803 : 5 : xpub = *active_xpubs.begin();
804 : 7 : } else {
805 [ + - + - ]: 4 : xpub = DecodeExtPubKey(hdkey.get_str());
806 [ + + ]: 4 : if (!xpub.pubkey.IsValid()) {
807 [ + - + - ]: 2 : throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Unable to parse HD key. Please provide a valid xpub");
808 : : }
809 : : }
810 : :
811 [ + - + - ]: 8 : std::optional<CKey> key = pwallet->GetKey(xpub.pubkey.GetID());
812 [ + + ]: 8 : if (!key) {
813 [ + - + - : 2 : throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Private key for %s is not known", EncodeExtPubKey(xpub)));
+ - ]
814 : : }
815 [ + - ]: 7 : CExtKey active_hdkey(xpub, *key);
816 : :
817 : 7 : std::vector<std::reference_wrapper<DescriptorScriptPubKeyMan>> spkms;
818 [ + - ]: 7 : WalletBatch batch{pwallet->GetDatabase()};
819 [ + - - + : 38 : for (bool internal : internals) {
- + ]
820 [ + - ]: 12 : WalletDescriptor w_desc = GenerateWalletDescriptor(xpub, *output_type, internal);
821 [ + - ]: 12 : uint256 w_id = DescriptorID(*w_desc.descriptor);
822 [ + - + + ]: 12 : if (!pwallet->GetScriptPubKeyMan(w_id)) {
823 [ + - + - ]: 10 : spkms.emplace_back(pwallet->SetupDescriptorScriptPubKeyMan(batch, active_hdkey, *output_type, internal));
824 : : }
825 : 12 : }
826 [ + + ]: 7 : if (spkms.empty()) {
827 [ + - + - ]: 2 : throw JSONRPCError(RPC_WALLET_ERROR, "Descriptor already exists");
828 : : }
829 : :
830 : : // Fetch each descspkm from the wallet in order to get the descriptor strings
831 : 6 : UniValue descs{UniValue::VARR};
832 [ + + ]: 16 : for (const auto& spkm : spkms) {
833 [ + - ]: 10 : std::string desc_str;
834 [ + - ]: 10 : bool ok = spkm.get().GetDescriptorString(desc_str, false);
835 [ + - ]: 10 : CHECK_NONFATAL(ok);
836 [ + - + - ]: 10 : descs.push_back(desc_str);
837 : 10 : }
838 : 6 : UniValue out{UniValue::VOBJ};
839 [ + - + - ]: 12 : out.pushKV("descs", std::move(descs));
840 : 6 : return out;
841 [ + - ]: 53 : }
842 [ + - + - : 8520 : };
+ - + + +
+ - - -
- ]
843 [ + - + - : 10224 : }
+ - + - +
- + - - -
- - ]
844 : :
845 : 844 : RPCMethod addhdkey()
846 : : {
847 : 844 : return RPCMethod{
848 : 844 : "addhdkey",
849 [ + - ]: 1688 : "Add a BIP 32 HD key to the wallet that can be used with 'createwalletdescriptor'\n",
850 : : {
851 [ + - + - : 2532 : {"hdkey", RPCArg::Type::STR, RPCArg::DefaultHint{"Automatically generated new key"}, "The BIP 32 extended private key to add. If none is provided, a randomly generated one will be added."},
+ - ]
852 : : },
853 [ + - ]: 1688 : RPCResult{
854 [ + - + - ]: 1688 : RPCResult::Type::OBJ, "", "",
855 : : {
856 [ + - + - ]: 1688 : {RPCResult::Type::STR, "xpub", "The xpub of the HD key that was added to the wallet"}
857 : : },
858 [ + - + - : 3376 : },
+ + - - ]
859 : 844 : RPCExamples{
860 [ + - + - : 1688 : HelpExampleCli("addhdkey", "xprv") + HelpExampleRpc("addhdkey", "xprv")
+ - + - +
- + - +
- ]
861 [ + - ]: 844 : },
862 : 844 : [&](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
863 : : {
864 : 5 : std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
865 [ - + ]: 5 : if (!wallet) return UniValue::VNULL;
866 : :
867 [ + - + + ]: 5 : if (wallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {
868 [ + - + - ]: 2 : throw JSONRPCError(RPC_WALLET_ERROR, "addhdkey is not available for wallets without private keys");
869 : : }
870 : :
871 [ + - ]: 4 : EnsureWalletIsUnlocked(*wallet);
872 : :
873 [ + - ]: 4 : CExtKey hdkey;
874 [ + - + + ]: 4 : if (request.params[0].isNull()) {
875 : 1 : CKey seed_key = GenerateRandomKey();
876 [ + - + - ]: 2 : hdkey.SetSeed(seed_key);
877 : 1 : } else {
878 [ + - + - : 3 : hdkey = DecodeExtKey(request.params[0].get_str());
+ - ]
879 [ + + ]: 3 : if (!hdkey.key.IsValid()) {
880 : : // Check if the user gave us an xpub and give a more descriptive error if so
881 [ + - + - : 1 : CExtPubKey xpub = DecodeExtPubKey(request.params[0].get_str());
+ - ]
882 [ + - ]: 1 : if (xpub.pubkey.IsValid()) {
883 [ + - + - ]: 2 : throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Extended public key (xpub) provided, but extended private key (xprv) is required");
884 : : } else {
885 [ # # # # ]: 0 : throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Could not parse HD key");
886 : : }
887 : : }
888 : : }
889 : :
890 [ + - ]: 3 : LOCK(wallet->cs_wallet);
891 [ + - + - ]: 6 : std::string desc_str = "unused(" + EncodeExtKey(hdkey) + ")";
892 : 3 : FlatSigningProvider keys;
893 [ - + ]: 3 : std::string error;
894 [ - + + - ]: 3 : std::vector<std::unique_ptr<Descriptor>> descs = Parse(desc_str, keys, error, false);
895 [ + - ]: 3 : CHECK_NONFATAL(!descs.empty());
896 [ + - + - : 3 : WalletDescriptor w_desc(std::move(descs.at(0)), GetTime(), 0, 0, 0);
+ - + - ]
897 [ + - + + ]: 3 : if (wallet->GetDescriptorScriptPubKeyMan(w_desc) != nullptr) {
898 [ + - + - ]: 2 : throw JSONRPCError(RPC_WALLET_ERROR, "HD key already exists");
899 : : }
900 : :
901 [ + - + - ]: 2 : auto spkm = wallet->AddWalletDescriptor(w_desc, keys, /*label=*/"", /*internal=*/false);
902 [ - + ]: 2 : if (!spkm) {
903 [ # # # # ]: 0 : throw JSONRPCError(RPC_WALLET_ERROR, util::ErrorString(spkm).original);
904 : : }
905 : :
906 : 2 : UniValue response(UniValue::VOBJ);
907 [ + - ]: 2 : const DescriptorScriptPubKeyMan& desc_spkm = spkm->get();
908 [ + - ]: 2 : LOCK(desc_spkm.cs_desc_man);
909 [ + - ]: 2 : std::set<CPubKey> pubkeys;
910 : 2 : std::set<CExtPubKey> extpubs;
911 [ + - + - ]: 2 : desc_spkm.GetWalletDescriptor().descriptor->GetPubKeys(pubkeys, extpubs);
912 [ + - ]: 2 : CHECK_NONFATAL(pubkeys.size() == 0);
913 [ + - ]: 2 : CHECK_NONFATAL(extpubs.size() == 1);
914 [ + - + - : 4 : response.pushKV("xpub", EncodeExtPubKey(*extpubs.begin()));
+ - + - ]
915 : :
916 : 2 : return response;
917 [ + - + - ]: 15 : },
918 [ + - + - : 4220 : };
+ + - - ]
919 [ + - + - ]: 3376 : }
920 : :
921 : : // addresses
922 : : RPCMethod getaddressinfo();
923 : : RPCMethod getnewaddress();
924 : : RPCMethod getrawchangeaddress();
925 : : RPCMethod setlabel();
926 : : RPCMethod listaddressgroupings();
927 : : RPCMethod keypoolrefill();
928 : : RPCMethod getaddressesbylabel();
929 : : RPCMethod listlabels();
930 : : #ifdef ENABLE_EXTERNAL_SIGNER
931 : : RPCMethod walletdisplayaddress();
932 : : #endif // ENABLE_EXTERNAL_SIGNER
933 : :
934 : : // backup
935 : : RPCMethod importprunedfunds();
936 : : RPCMethod removeprunedfunds();
937 : : RPCMethod importdescriptors();
938 : : RPCMethod listdescriptors();
939 : : RPCMethod backupwallet();
940 : : RPCMethod restorewallet();
941 : :
942 : : // coins
943 : : RPCMethod getreceivedbyaddress();
944 : : RPCMethod getreceivedbylabel();
945 : : RPCMethod getbalance();
946 : : RPCMethod lockunspent();
947 : : RPCMethod listlockunspent();
948 : : RPCMethod getbalances();
949 : : RPCMethod listunspent();
950 : :
951 : : // encryption
952 : : RPCMethod walletpassphrase();
953 : : RPCMethod walletpassphrasechange();
954 : : RPCMethod walletlock();
955 : : RPCMethod encryptwallet();
956 : :
957 : : // spend
958 : : RPCMethod sendtoaddress();
959 : : RPCMethod sendmany();
960 : : RPCMethod fundrawtransaction();
961 : : RPCMethod bumpfee();
962 : : RPCMethod psbtbumpfee();
963 : : RPCMethod send();
964 : : RPCMethod sendall();
965 : : RPCMethod walletprocesspsbt();
966 : : RPCMethod walletcreatefundedpsbt();
967 : : RPCMethod signrawtransactionwithwallet();
968 : :
969 : : // signmessage
970 : : RPCMethod signmessage();
971 : :
972 : : // transactions
973 : : RPCMethod listreceivedbyaddress();
974 : : RPCMethod listreceivedbylabel();
975 : : RPCMethod listtransactions();
976 : : RPCMethod listsinceblock();
977 : : RPCMethod gettransaction();
978 : : RPCMethod abandontransaction();
979 : : RPCMethod rescanblockchain();
980 : : RPCMethod abortrescan();
981 : :
982 : 428 : std::span<const CRPCCommand> GetWalletRPCCommands()
983 : : {
984 : 428 : static const CRPCCommand commands[]{
985 [ + - ]: 834 : {"rawtransactions", &fundrawtransaction},
986 [ + - ]: 834 : {"wallet", &abandontransaction},
987 [ + - ]: 834 : {"wallet", &abortrescan},
988 [ + - ]: 834 : {"wallet", &addhdkey},
989 [ + - ]: 834 : {"wallet", &backupwallet},
990 [ + - ]: 834 : {"wallet", &bumpfee},
991 [ + - ]: 834 : {"wallet", &psbtbumpfee},
992 [ + - ]: 834 : {"wallet", &createwallet},
993 [ + - ]: 834 : {"wallet", &createwalletdescriptor},
994 [ + - ]: 834 : {"wallet", &restorewallet},
995 [ + - ]: 834 : {"wallet", &encryptwallet},
996 [ + - ]: 834 : {"wallet", &getaddressesbylabel},
997 [ + - ]: 834 : {"wallet", &getaddressinfo},
998 [ + - ]: 834 : {"wallet", &getbalance},
999 [ + - ]: 834 : {"wallet", &gethdkeys},
1000 [ + - ]: 834 : {"wallet", &getnewaddress},
1001 [ + - ]: 834 : {"wallet", &getrawchangeaddress},
1002 [ + - ]: 834 : {"wallet", &getreceivedbyaddress},
1003 [ + - ]: 834 : {"wallet", &getreceivedbylabel},
1004 [ + - ]: 834 : {"wallet", &gettransaction},
1005 [ + - ]: 834 : {"wallet", &getbalances},
1006 [ + - ]: 834 : {"wallet", &getwalletinfo},
1007 [ + - ]: 834 : {"wallet", &importdescriptors},
1008 [ + - ]: 834 : {"wallet", &importprunedfunds},
1009 [ + - ]: 834 : {"wallet", &keypoolrefill},
1010 [ + - ]: 834 : {"wallet", &listaddressgroupings},
1011 [ + - ]: 834 : {"wallet", &listdescriptors},
1012 [ + - ]: 834 : {"wallet", &listlabels},
1013 [ + - ]: 834 : {"wallet", &listlockunspent},
1014 [ + - ]: 834 : {"wallet", &listreceivedbyaddress},
1015 [ + - ]: 834 : {"wallet", &listreceivedbylabel},
1016 [ + - ]: 834 : {"wallet", &listsinceblock},
1017 [ + - ]: 834 : {"wallet", &listtransactions},
1018 [ + - ]: 834 : {"wallet", &listunspent},
1019 [ + - ]: 834 : {"wallet", &listwalletdir},
1020 [ + - ]: 834 : {"wallet", &listwallets},
1021 [ + - ]: 834 : {"wallet", &loadwallet},
1022 [ + - ]: 834 : {"wallet", &lockunspent},
1023 [ + - ]: 834 : {"wallet", &migratewallet},
1024 [ + - ]: 834 : {"wallet", &removeprunedfunds},
1025 [ + - ]: 834 : {"wallet", &rescanblockchain},
1026 [ + - ]: 834 : {"wallet", &send},
1027 [ + - ]: 834 : {"wallet", &sendmany},
1028 [ + - ]: 834 : {"wallet", &sendtoaddress},
1029 [ + - ]: 834 : {"wallet", &setlabel},
1030 [ + - ]: 834 : {"wallet", &setwalletflag},
1031 [ + - ]: 834 : {"wallet", &signmessage},
1032 [ + - ]: 834 : {"wallet", &signrawtransactionwithwallet},
1033 [ + - ]: 834 : {"wallet", &simulaterawtransaction},
1034 [ + - ]: 834 : {"wallet", &sendall},
1035 [ + - ]: 834 : {"wallet", &unloadwallet},
1036 [ + - ]: 834 : {"wallet", &walletcreatefundedpsbt},
1037 : : #ifdef ENABLE_EXTERNAL_SIGNER
1038 [ + - ]: 834 : {"wallet", &walletdisplayaddress},
1039 : : #endif // ENABLE_EXTERNAL_SIGNER
1040 [ + - ]: 834 : {"wallet", &walletlock},
1041 [ + - ]: 834 : {"wallet", &walletpassphrase},
1042 [ + - ]: 834 : {"wallet", &walletpassphrasechange},
1043 [ + - ]: 834 : {"wallet", &walletprocesspsbt},
1044 [ + + + - : 24197 : };
+ - + - +
- + - + -
+ - + - +
- + - + -
+ - + - +
- + - + -
+ - + - +
- + - + -
+ - + - +
- + - + -
+ - + - +
- + - + -
+ - + - +
- + - + -
+ - + - +
- + - + -
+ - + - +
- + - + -
+ - + - +
- + - + -
+ - + - +
- + - + -
+ - + - -
- ]
1045 : 428 : return commands;
1046 : : }
1047 : : } // namespace wallet
|