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