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