/root/bitcoin/src/wallet/rpc/backup.cpp
| Line | Count | Source | 
| 1 |  | // Copyright (c) 2009-present The Bitcoin Core developers | 
| 2 |  | // Distributed under the MIT software license, see the accompanying | 
| 3 |  | // file COPYING or http://www.opensource.org/licenses/mit-license.php. | 
| 4 |  |  | 
| 5 |  | #include <chain.h> | 
| 6 |  | #include <clientversion.h> | 
| 7 |  | #include <core_io.h> | 
| 8 |  | #include <hash.h> | 
| 9 |  | #include <interfaces/chain.h> | 
| 10 |  | #include <key_io.h> | 
| 11 |  | #include <merkleblock.h> | 
| 12 |  | #include <rpc/util.h> | 
| 13 |  | #include <script/descriptor.h> | 
| 14 |  | #include <script/script.h> | 
| 15 |  | #include <script/solver.h> | 
| 16 |  | #include <sync.h> | 
| 17 |  | #include <uint256.h> | 
| 18 |  | #include <util/bip32.h> | 
| 19 |  | #include <util/fs.h> | 
| 20 |  | #include <util/time.h> | 
| 21 |  | #include <util/translation.h> | 
| 22 |  | #include <wallet/rpc/util.h> | 
| 23 |  | #include <wallet/wallet.h> | 
| 24 |  |  | 
| 25 |  | #include <cstdint> | 
| 26 |  | #include <fstream> | 
| 27 |  | #include <tuple> | 
| 28 |  | #include <string> | 
| 29 |  |  | 
| 30 |  | #include <univalue.h> | 
| 31 |  |  | 
| 32 |  |  | 
| 33 |  |  | 
| 34 |  | using interfaces::FoundBlock; | 
| 35 |  |  | 
| 36 |  | namespace wallet { | 
| 37 |  | RPCHelpMan importprunedfunds() | 
| 38 | 0 | { | 
| 39 | 0 |     return RPCHelpMan{ | 
| 40 | 0 |         "importprunedfunds", | 
| 41 | 0 |         "Imports funds without rescan. Corresponding address or script must previously be included in wallet. Aimed towards pruned wallets. The end-user is responsible to import additional transactions that subsequently spend the imported outputs or rescan after the point in the blockchain the transaction is included.\n", | 
| 42 | 0 |                 { | 
| 43 | 0 |                     {"rawtransaction", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "A raw transaction in hex funding an already-existing address in wallet"}, | 
| 44 | 0 |                     {"txoutproof", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The hex output from gettxoutproof that contains the transaction"}, | 
| 45 | 0 |                 }, | 
| 46 | 0 |                 RPCResult{RPCResult::Type::NONE, "", ""}, | 
| 47 | 0 |                 RPCExamples{""}, | 
| 48 | 0 |         [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue | 
| 49 | 0 | { | 
| 50 | 0 |     std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request); | 
| 51 | 0 |     if (!pwallet) return UniValue::VNULL; | 
| 52 |  |  | 
| 53 | 0 |     CMutableTransaction tx; | 
| 54 | 0 |     if (!DecodeHexTx(tx, request.params[0].get_str())) { | 
| 55 | 0 |         throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "TX decode failed. Make sure the tx has at least one input."); | 
| 56 | 0 |     } | 
| 57 |  |  | 
| 58 | 0 |     DataStream ssMB{ParseHexV(request.params[1], "proof")}; | 
| 59 | 0 |     CMerkleBlock merkleBlock; | 
| 60 | 0 |     ssMB >> merkleBlock; | 
| 61 |  |  | 
| 62 |  |     //Search partial merkle tree in proof for our transaction and index in valid block | 
| 63 | 0 |     std::vector<Txid> vMatch; | 
| 64 | 0 |     std::vector<unsigned int> vIndex; | 
| 65 | 0 |     if (merkleBlock.txn.ExtractMatches(vMatch, vIndex) != merkleBlock.header.hashMerkleRoot) { | 
| 66 | 0 |         throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Something wrong with merkleblock"); | 
| 67 | 0 |     } | 
| 68 |  |  | 
| 69 | 0 |     LOCK(pwallet->cs_wallet); | 
| 70 | 0 |     int height; | 
| 71 | 0 |     if (!pwallet->chain().findAncestorByHash(pwallet->GetLastBlockHash(), merkleBlock.header.GetHash(), FoundBlock().height(height))) { | 
| 72 | 0 |         throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found in chain"); | 
| 73 | 0 |     } | 
| 74 |  |  | 
| 75 | 0 |     std::vector<Txid>::const_iterator it; | 
| 76 | 0 |     if ((it = std::find(vMatch.begin(), vMatch.end(), tx.GetHash())) == vMatch.end()) { | 
| 77 | 0 |         throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Transaction given doesn't exist in proof"); | 
| 78 | 0 |     } | 
| 79 |  |  | 
| 80 | 0 |     unsigned int txnIndex = vIndex[it - vMatch.begin()]; | 
| 81 |  | 
 | 
| 82 | 0 |     CTransactionRef tx_ref = MakeTransactionRef(tx); | 
| 83 | 0 |     if (pwallet->IsMine(*tx_ref)) { | 
| 84 | 0 |         pwallet->AddToWallet(std::move(tx_ref), TxStateConfirmed{merkleBlock.header.GetHash(), height, static_cast<int>(txnIndex)}); | 
| 85 | 0 |         return UniValue::VNULL; | 
| 86 | 0 |     } | 
| 87 |  |  | 
| 88 | 0 |     throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "No addresses in wallet correspond to included transaction"); | 
| 89 | 0 | }, | 
| 90 | 0 |     }; | 
| 91 | 0 | } | 
| 92 |  |  | 
| 93 |  | RPCHelpMan removeprunedfunds() | 
| 94 | 0 | { | 
| 95 | 0 |     return RPCHelpMan{ | 
| 96 | 0 |         "removeprunedfunds", | 
| 97 | 0 |         "Deletes the specified transaction from the wallet. Meant for use with pruned wallets and as a companion to importprunedfunds. This will affect wallet balances.\n", | 
| 98 | 0 |                 { | 
| 99 | 0 |                     {"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The hex-encoded id of the transaction you are deleting"}, | 
| 100 | 0 |                 }, | 
| 101 | 0 |                 RPCResult{RPCResult::Type::NONE, "", ""}, | 
| 102 | 0 |                 RPCExamples{ | 
| 103 | 0 |                     HelpExampleCli("removeprunedfunds", "\"a8d0c0184dde994a09ec054286f1ce581bebf46446a512166eae7628734ea0a5\"") + | 
| 104 | 0 |             "\nAs a JSON-RPC call\n" | 
| 105 | 0 |             + HelpExampleRpc("removeprunedfunds", "\"a8d0c0184dde994a09ec054286f1ce581bebf46446a512166eae7628734ea0a5\"") | 
| 106 | 0 |                 }, | 
| 107 | 0 |         [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue | 
| 108 | 0 | { | 
| 109 | 0 |     std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request); | 
| 110 | 0 |     if (!pwallet) return UniValue::VNULL; | 
| 111 |  |  | 
| 112 | 0 |     LOCK(pwallet->cs_wallet); | 
| 113 |  | 
 | 
| 114 | 0 |     Txid hash{Txid::FromUint256(ParseHashV(request.params[0], "txid"))}; | 
| 115 | 0 |     std::vector<Txid> vHash; | 
| 116 | 0 |     vHash.push_back(hash); | 
| 117 | 0 |     if (auto res = pwallet->RemoveTxs(vHash); !res) { | 
| 118 | 0 |         throw JSONRPCError(RPC_WALLET_ERROR, util::ErrorString(res).original); | 
| 119 | 0 |     } | 
| 120 |  |  | 
| 121 | 0 |     return UniValue::VNULL; | 
| 122 | 0 | }, | 
| 123 | 0 |     }; | 
| 124 | 0 | } | 
| 125 |  |  | 
| 126 |  | static int64_t GetImportTimestamp(const UniValue& data, int64_t now) | 
| 127 | 0 | { | 
| 128 | 0 |     if (data.exists("timestamp")) { | 
| 129 | 0 |         const UniValue& timestamp = data["timestamp"]; | 
| 130 | 0 |         if (timestamp.isNum()) { | 
| 131 | 0 |             return timestamp.getInt<int64_t>(); | 
| 132 | 0 |         } else if (timestamp.isStr() && timestamp.get_str() == "now") { | 
| 133 | 0 |             return now; | 
| 134 | 0 |         } | 
| 135 | 0 |         throw JSONRPCError(RPC_TYPE_ERROR, strprintf("Expected number or \"now\" timestamp value for key. got type %s", uvTypeName(timestamp.type()))); | 
| 136 | 0 |     } | 
| 137 | 0 |     throw JSONRPCError(RPC_TYPE_ERROR, "Missing required timestamp field for key"); | 
| 138 | 0 | } | 
| 139 |  |  | 
| 140 |  | static UniValue ProcessDescriptorImport(CWallet& wallet, const UniValue& data, const int64_t timestamp) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet) | 
| 141 | 0 | { | 
| 142 | 0 |     UniValue warnings(UniValue::VARR); | 
| 143 | 0 |     UniValue result(UniValue::VOBJ); | 
| 144 |  | 
 | 
| 145 | 0 |     try { | 
| 146 | 0 |         if (!data.exists("desc")) { | 
| 147 | 0 |             throw JSONRPCError(RPC_INVALID_PARAMETER, "Descriptor not found."); | 
| 148 | 0 |         } | 
| 149 |  |  | 
| 150 | 0 |         const std::string& descriptor = data["desc"].get_str(); | 
| 151 | 0 |         const bool active = data.exists("active") ? data["active"].get_bool() : false; | 
| 152 | 0 |         const std::string label{LabelFromValue(data["label"])}; | 
| 153 |  |  | 
| 154 |  |         // Parse descriptor string | 
| 155 | 0 |         FlatSigningProvider keys; | 
| 156 | 0 |         std::string error; | 
| 157 | 0 |         auto parsed_descs = Parse(descriptor, keys, error, /* require_checksum = */ true); | 
| 158 | 0 |         if (parsed_descs.empty()) { | 
| 159 | 0 |             throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, error); | 
| 160 | 0 |         } | 
| 161 | 0 |         std::optional<bool> internal; | 
| 162 | 0 |         if (data.exists("internal")) { | 
| 163 | 0 |             if (parsed_descs.size() > 1) { | 
| 164 | 0 |                 throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Cannot have multipath descriptor while also specifying \'internal\'"); | 
| 165 | 0 |             } | 
| 166 | 0 |             internal = data["internal"].get_bool(); | 
| 167 | 0 |         } | 
| 168 |  |  | 
| 169 |  |         // Range check | 
| 170 | 0 |         int64_t range_start = 0, range_end = 1, next_index = 0; | 
| 171 | 0 |         if (!parsed_descs.at(0)->IsRange() && data.exists("range")) { | 
| 172 | 0 |             throw JSONRPCError(RPC_INVALID_PARAMETER, "Range should not be specified for an un-ranged descriptor"); | 
| 173 | 0 |         } else if (parsed_descs.at(0)->IsRange()) { | 
| 174 | 0 |             if (data.exists("range")) { | 
| 175 | 0 |                 auto range = ParseDescriptorRange(data["range"]); | 
| 176 | 0 |                 range_start = range.first; | 
| 177 | 0 |                 range_end = range.second + 1; // Specified range end is inclusive, but we need range end as exclusive | 
| 178 | 0 |             } else { | 
| 179 | 0 |                 warnings.push_back("Range not given, using default keypool range"); | 
| 180 | 0 |                 range_start = 0; | 
| 181 | 0 |                 range_end = wallet.m_keypool_size; | 
| 182 | 0 |             } | 
| 183 | 0 |             next_index = range_start; | 
| 184 |  | 
 | 
| 185 | 0 |             if (data.exists("next_index")) { | 
| 186 | 0 |                 next_index = data["next_index"].getInt<int64_t>(); | 
| 187 |  |                 // bound checks | 
| 188 | 0 |                 if (next_index < range_start || next_index >= range_end) { | 
| 189 | 0 |                     throw JSONRPCError(RPC_INVALID_PARAMETER, "next_index is out of range"); | 
| 190 | 0 |                 } | 
| 191 | 0 |             } | 
| 192 | 0 |         } | 
| 193 |  |  | 
| 194 |  |         // Active descriptors must be ranged | 
| 195 | 0 |         if (active && !parsed_descs.at(0)->IsRange()) { | 
| 196 | 0 |             throw JSONRPCError(RPC_INVALID_PARAMETER, "Active descriptors must be ranged"); | 
| 197 | 0 |         } | 
| 198 |  |  | 
| 199 |  |         // Multipath descriptors should not have a label | 
| 200 | 0 |         if (parsed_descs.size() > 1 && data.exists("label")) { | 
| 201 | 0 |             throw JSONRPCError(RPC_INVALID_PARAMETER, "Multipath descriptors should not have a label"); | 
| 202 | 0 |         } | 
| 203 |  |  | 
| 204 |  |         // Ranged descriptors should not have a label | 
| 205 | 0 |         if (data.exists("range") && data.exists("label")) { | 
| 206 | 0 |             throw JSONRPCError(RPC_INVALID_PARAMETER, "Ranged descriptors should not have a label"); | 
| 207 | 0 |         } | 
| 208 |  |  | 
| 209 |  |         // Internal addresses should not have a label either | 
| 210 | 0 |         if (internal && data.exists("label")) { | 
| 211 | 0 |             throw JSONRPCError(RPC_INVALID_PARAMETER, "Internal addresses should not have a label"); | 
| 212 | 0 |         } | 
| 213 |  |  | 
| 214 |  |         // Combo descriptor check | 
| 215 | 0 |         if (active && !parsed_descs.at(0)->IsSingleType()) { | 
| 216 | 0 |             throw JSONRPCError(RPC_WALLET_ERROR, "Combo descriptors cannot be set to active"); | 
| 217 | 0 |         } | 
| 218 |  |  | 
| 219 |  |         // If the wallet disabled private keys, abort if private keys exist | 
| 220 | 0 |         if (wallet.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS) && !keys.keys.empty()) { | 
| 221 | 0 |             throw JSONRPCError(RPC_WALLET_ERROR, "Cannot import private keys to a wallet with private keys disabled"); | 
| 222 | 0 |         } | 
| 223 |  |  | 
| 224 | 0 |         for (size_t j = 0; j < parsed_descs.size(); ++j) { | 
| 225 | 0 |             auto parsed_desc = std::move(parsed_descs[j]); | 
| 226 | 0 |             bool desc_internal = internal.has_value() && internal.value(); | 
| 227 | 0 |             if (parsed_descs.size() == 2) { | 
| 228 | 0 |                 desc_internal = j == 1; | 
| 229 | 0 |             } else if (parsed_descs.size() > 2) { | 
| 230 | 0 |                 CHECK_NONFATAL(!desc_internal); | 
| 231 | 0 |             } | 
| 232 |  |             // Need to ExpandPrivate to check if private keys are available for all pubkeys | 
| 233 | 0 |             FlatSigningProvider expand_keys; | 
| 234 | 0 |             std::vector<CScript> scripts; | 
| 235 | 0 |             if (!parsed_desc->Expand(0, keys, scripts, expand_keys)) { | 
| 236 | 0 |                 throw JSONRPCError(RPC_WALLET_ERROR, "Cannot expand descriptor. Probably because of hardened derivations without private keys provided"); | 
| 237 | 0 |             } | 
| 238 | 0 |             parsed_desc->ExpandPrivate(0, keys, expand_keys); | 
| 239 |  |  | 
| 240 |  |             // Check if all private keys are provided | 
| 241 | 0 |             bool have_all_privkeys = !expand_keys.keys.empty(); | 
| 242 | 0 |             for (const auto& entry : expand_keys.origins) { | 
| 243 | 0 |                 const CKeyID& key_id = entry.first; | 
| 244 | 0 |                 CKey key; | 
| 245 | 0 |                 if (!expand_keys.GetKey(key_id, key)) { | 
| 246 | 0 |                     have_all_privkeys = false; | 
| 247 | 0 |                     break; | 
| 248 | 0 |                 } | 
| 249 | 0 |             } | 
| 250 |  |  | 
| 251 |  |             // If private keys are enabled, check some things. | 
| 252 | 0 |             if (!wallet.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) { | 
| 253 | 0 |                if (keys.keys.empty()) { | 
| 254 | 0 |                     throw JSONRPCError(RPC_WALLET_ERROR, "Cannot import descriptor without private keys to a wallet with private keys enabled"); | 
| 255 | 0 |                } | 
| 256 | 0 |                if (!have_all_privkeys) { | 
| 257 | 0 |                    warnings.push_back("Not all private keys provided. Some wallet functionality may return unexpected errors"); | 
| 258 | 0 |                } | 
| 259 | 0 |             } | 
| 260 |  |  | 
| 261 | 0 |             WalletDescriptor w_desc(std::move(parsed_desc), timestamp, range_start, range_end, next_index); | 
| 262 |  |  | 
| 263 |  |             // Add descriptor to the wallet | 
| 264 | 0 |             auto spk_manager_res = wallet.AddWalletDescriptor(w_desc, keys, label, desc_internal); | 
| 265 |  | 
 | 
| 266 | 0 |             if (!spk_manager_res) { | 
| 267 | 0 |                 throw JSONRPCError(RPC_WALLET_ERROR, strprintf("Could not add descriptor '%s': %s", descriptor, util::ErrorString(spk_manager_res).original)); | 
| 268 | 0 |             } | 
| 269 |  |  | 
| 270 | 0 |             auto& spk_manager = spk_manager_res.value().get(); | 
| 271 |  |  | 
| 272 |  |             // Set descriptor as active if necessary | 
| 273 | 0 |             if (active) { | 
| 274 | 0 |                 if (!w_desc.descriptor->GetOutputType()) { | 
| 275 | 0 |                     warnings.push_back("Unknown output type, cannot set descriptor to active."); | 
| 276 | 0 |                 } else { | 
| 277 | 0 |                     wallet.AddActiveScriptPubKeyMan(spk_manager.GetID(), *w_desc.descriptor->GetOutputType(), desc_internal); | 
| 278 | 0 |                 } | 
| 279 | 0 |             } else { | 
| 280 | 0 |                 if (w_desc.descriptor->GetOutputType()) { | 
| 281 | 0 |                     wallet.DeactivateScriptPubKeyMan(spk_manager.GetID(), *w_desc.descriptor->GetOutputType(), desc_internal); | 
| 282 | 0 |                 } | 
| 283 | 0 |             } | 
| 284 | 0 |         } | 
| 285 |  |  | 
| 286 | 0 |         result.pushKV("success", UniValue(true)); | 
| 287 | 0 |     } catch (const UniValue& e) { | 
| 288 | 0 |         result.pushKV("success", UniValue(false)); | 
| 289 | 0 |         result.pushKV("error", e); | 
| 290 | 0 |     } | 
| 291 | 0 |     PushWarnings(warnings, result); | 
| 292 | 0 |     return result; | 
| 293 | 0 | } | 
| 294 |  |  | 
| 295 |  | RPCHelpMan importdescriptors() | 
| 296 | 0 | { | 
| 297 | 0 |     return RPCHelpMan{ | 
| 298 | 0 |         "importdescriptors", | 
| 299 | 0 |         "Import descriptors. This will trigger a rescan of the blockchain based on the earliest timestamp of all descriptors being imported. Requires a new wallet backup.\n" | 
| 300 | 0 |         "When importing descriptors with multipath key expressions, if the multipath specifier contains exactly two elements, the descriptor produced from the second element will be imported as an internal descriptor.\n" | 
| 301 | 0 |             "\nNote: This call can take over an hour to complete if using an early timestamp; during that time, other rpc calls\n" | 
| 302 | 0 |             "may report that the imported keys, addresses or scripts exist but related transactions are still missing.\n" | 
| 303 | 0 |             "The rescan is significantly faster if block filters are available (using startup option \"-blockfilterindex=1\").\n", | 
| 304 | 0 |                 { | 
| 305 | 0 |                     {"requests", RPCArg::Type::ARR, RPCArg::Optional::NO, "Data to be imported", | 
| 306 | 0 |                         { | 
| 307 | 0 |                             {"", RPCArg::Type::OBJ, RPCArg::Optional::OMITTED, "", | 
| 308 | 0 |                                 { | 
| 309 | 0 |                                     {"desc", RPCArg::Type::STR, RPCArg::Optional::NO, "Descriptor to import."}, | 
| 310 | 0 |                                     {"active", RPCArg::Type::BOOL, RPCArg::Default{false}, "Set this descriptor to be the active descriptor for the corresponding output type/externality"}, | 
| 311 | 0 |                                     {"range", RPCArg::Type::RANGE, RPCArg::Optional::OMITTED, "If a ranged descriptor is used, this specifies the end or the range (in the form [begin,end]) to import"}, | 
| 312 | 0 |                                     {"next_index", RPCArg::Type::NUM, RPCArg::Optional::OMITTED, "If a ranged descriptor is set to active, this specifies the next index to generate addresses from"}, | 
| 313 | 0 |                                     {"timestamp", RPCArg::Type::NUM, RPCArg::Optional::NO, "Time from which to start rescanning the blockchain for this descriptor, in " + UNIX_EPOCH_TIME + "\n" | 
| 314 | 0 |                                         "Use the string \"now\" to substitute the current synced blockchain time.\n" | 
| 315 | 0 |                                         "\"now\" can be specified to bypass scanning, for outputs which are known to never have been used, and\n" | 
| 316 | 0 |                                         "0 can be specified to scan the entire blockchain. Blocks up to 2 hours before the earliest timestamp\n" | 
| 317 | 0 |                                         "of all descriptors being imported will be scanned as well as the mempool.", | 
| 318 | 0 |                                         RPCArgOptions{.type_str={"timestamp | \"now\"", "integer / string"}} | 
| 319 | 0 |                                     }, | 
| 320 | 0 |                                     {"internal", RPCArg::Type::BOOL, RPCArg::Default{false}, "Whether matching outputs should be treated as not incoming payments (e.g. change)"}, | 
| 321 | 0 |                                     {"label", RPCArg::Type::STR, RPCArg::Default{""}, "Label to assign to the address, only allowed with internal=false. Disabled for ranged descriptors"}, | 
| 322 | 0 |                                 }, | 
| 323 | 0 |                             }, | 
| 324 | 0 |                         }, | 
| 325 | 0 |                         RPCArgOptions{.oneline_description="requests"}}, | 
| 326 | 0 |                 }, | 
| 327 | 0 |                 RPCResult{ | 
| 328 | 0 |                     RPCResult::Type::ARR, "", "Response is an array with the same size as the input that has the execution result", | 
| 329 | 0 |                     { | 
| 330 | 0 |                         {RPCResult::Type::OBJ, "", "", | 
| 331 | 0 |                         { | 
| 332 | 0 |                             {RPCResult::Type::BOOL, "success", ""}, | 
| 333 | 0 |                             {RPCResult::Type::ARR, "warnings", /*optional=*/true, "", | 
| 334 | 0 |                             { | 
| 335 | 0 |                                 {RPCResult::Type::STR, "", ""}, | 
| 336 | 0 |                             }}, | 
| 337 | 0 |                             {RPCResult::Type::OBJ, "error", /*optional=*/true, "", | 
| 338 | 0 |                             { | 
| 339 | 0 |                                 {RPCResult::Type::ELISION, "", "JSONRPC error"}, | 
| 340 | 0 |                             }}, | 
| 341 | 0 |                         }}, | 
| 342 | 0 |                     } | 
| 343 | 0 |                 }, | 
| 344 | 0 |                 RPCExamples{ | 
| 345 | 0 |                     HelpExampleCli("importdescriptors", "'[{ \"desc\": \"<my descriptor>\", \"timestamp\":1455191478, \"internal\": true }, " | 
| 346 | 0 |                                           "{ \"desc\": \"<my descriptor 2>\", \"label\": \"example 2\", \"timestamp\": 1455191480 }]'") + | 
| 347 | 0 |                     HelpExampleCli("importdescriptors", "'[{ \"desc\": \"<my descriptor>\", \"timestamp\":1455191478, \"active\": true, \"range\": [0,100], \"label\": \"<my bech32 wallet>\" }]'") | 
| 348 | 0 |                 }, | 
| 349 | 0 |         [&](const RPCHelpMan& self, const JSONRPCRequest& main_request) -> UniValue | 
| 350 | 0 | { | 
| 351 | 0 |     std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(main_request); | 
| 352 | 0 |     if (!pwallet) return UniValue::VNULL; | 
| 353 | 0 |     CWallet& wallet{*pwallet}; | 
| 354 |  |  | 
| 355 |  |     // Make sure the results are valid at least up to the most recent block | 
| 356 |  |     // the user could have gotten from another RPC command prior to now | 
| 357 | 0 |     wallet.BlockUntilSyncedToCurrentChain(); | 
| 358 |  | 
 | 
| 359 | 0 |     WalletRescanReserver reserver(*pwallet); | 
| 360 | 0 |     if (!reserver.reserve(/*with_passphrase=*/true)) { | 
| 361 | 0 |         throw JSONRPCError(RPC_WALLET_ERROR, "Wallet is currently rescanning. Abort existing rescan or wait."); | 
| 362 | 0 |     } | 
| 363 |  |  | 
| 364 |  |     // Ensure that the wallet is not locked for the remainder of this RPC, as | 
| 365 |  |     // the passphrase is used to top up the keypool. | 
| 366 | 0 |     LOCK(pwallet->m_relock_mutex); | 
| 367 |  | 
 | 
| 368 | 0 |     const UniValue& requests = main_request.params[0]; | 
| 369 | 0 |     const int64_t minimum_timestamp = 1; | 
| 370 | 0 |     int64_t now = 0; | 
| 371 | 0 |     int64_t lowest_timestamp = 0; | 
| 372 | 0 |     bool rescan = false; | 
| 373 | 0 |     UniValue response(UniValue::VARR); | 
| 374 | 0 |     { | 
| 375 | 0 |         LOCK(pwallet->cs_wallet); | 
| 376 | 0 |         EnsureWalletIsUnlocked(*pwallet); | 
| 377 |  | 
 | 
| 378 | 0 |         CHECK_NONFATAL(pwallet->chain().findBlock(pwallet->GetLastBlockHash(), FoundBlock().time(lowest_timestamp).mtpTime(now))); | 
| 379 |  |  | 
| 380 |  |         // Get all timestamps and extract the lowest timestamp | 
| 381 | 0 |         for (const UniValue& request : requests.getValues()) { | 
| 382 |  |             // This throws an error if "timestamp" doesn't exist | 
| 383 | 0 |             const int64_t timestamp = std::max(GetImportTimestamp(request, now), minimum_timestamp); | 
| 384 | 0 |             const UniValue result = ProcessDescriptorImport(*pwallet, request, timestamp); | 
| 385 | 0 |             response.push_back(result); | 
| 386 |  | 
 | 
| 387 | 0 |             if (lowest_timestamp > timestamp ) { | 
| 388 | 0 |                 lowest_timestamp = timestamp; | 
| 389 | 0 |             } | 
| 390 |  |  | 
| 391 |  |             // If we know the chain tip, and at least one request was successful then allow rescan | 
| 392 | 0 |             if (!rescan && result["success"].get_bool()) { | 
| 393 | 0 |                 rescan = true; | 
| 394 | 0 |             } | 
| 395 | 0 |         } | 
| 396 | 0 |         pwallet->ConnectScriptPubKeyManNotifiers(); | 
| 397 | 0 |         pwallet->RefreshAllTXOs(); | 
| 398 | 0 |     } | 
| 399 |  |  | 
| 400 |  |     // Rescan the blockchain using the lowest timestamp | 
| 401 | 0 |     if (rescan) { | 
| 402 | 0 |         int64_t scanned_time = pwallet->RescanFromTime(lowest_timestamp, reserver, /*update=*/true); | 
| 403 | 0 |         pwallet->ResubmitWalletTransactions(/*relay=*/false, /*force=*/true); | 
| 404 |  | 
 | 
| 405 | 0 |         if (pwallet->IsAbortingRescan()) { | 
| 406 | 0 |             throw JSONRPCError(RPC_MISC_ERROR, "Rescan aborted by user."); | 
| 407 | 0 |         } | 
| 408 |  |  | 
| 409 | 0 |         if (scanned_time > lowest_timestamp) { | 
| 410 | 0 |             std::vector<UniValue> results = response.getValues(); | 
| 411 | 0 |             response.clear(); | 
| 412 | 0 |             response.setArray(); | 
| 413 |  |  | 
| 414 |  |             // Compose the response | 
| 415 | 0 |             for (unsigned int i = 0; i < requests.size(); ++i) { | 
| 416 | 0 |                 const UniValue& request = requests.getValues().at(i); | 
| 417 |  |  | 
| 418 |  |                 // If the descriptor timestamp is within the successfully scanned | 
| 419 |  |                 // range, or if the import result already has an error set, let | 
| 420 |  |                 // the result stand unmodified. Otherwise replace the result | 
| 421 |  |                 // with an error message. | 
| 422 | 0 |                 if (scanned_time <= GetImportTimestamp(request, now) || results.at(i).exists("error")) { | 
| 423 | 0 |                     response.push_back(results.at(i)); | 
| 424 | 0 |                 } else { | 
| 425 | 0 |                     std::string error_msg{strprintf("Rescan failed for descriptor with timestamp %d. There " | 
| 426 | 0 |                             "was an error reading a block from time %d, which is after or within %d seconds " | 
| 427 | 0 |                             "of key creation, and could contain transactions pertaining to the desc. As a " | 
| 428 | 0 |                             "result, transactions and coins using this desc may not appear in the wallet.", | 
| 429 | 0 |                             GetImportTimestamp(request, now), scanned_time - TIMESTAMP_WINDOW - 1, TIMESTAMP_WINDOW)}; | 
| 430 | 0 |                     if (pwallet->chain().havePruned()) { | 
| 431 | 0 |                         error_msg += strprintf(" This error could be caused by pruning or data corruption " | 
| 432 | 0 |                                 "(see bitcoind log for details) and could be dealt with by downloading and " | 
| 433 | 0 |                                 "rescanning the relevant blocks (see -reindex option and rescanblockchain RPC)."); | 
| 434 | 0 |                     } else if (pwallet->chain().hasAssumedValidChain()) { | 
| 435 | 0 |                         error_msg += strprintf(" This error is likely caused by an in-progress assumeutxo " | 
| 436 | 0 |                                 "background sync. Check logs or getchainstates RPC for assumeutxo background " | 
| 437 | 0 |                                 "sync progress and try again later."); | 
| 438 | 0 |                     } else { | 
| 439 | 0 |                         error_msg += strprintf(" This error could potentially caused by data corruption. If " | 
| 440 | 0 |                                 "the issue persists you may want to reindex (see -reindex option)."); | 
| 441 | 0 |                     } | 
| 442 |  | 
 | 
| 443 | 0 |                     UniValue result = UniValue(UniValue::VOBJ); | 
| 444 | 0 |                     result.pushKV("success", UniValue(false)); | 
| 445 | 0 |                     result.pushKV("error", JSONRPCError(RPC_MISC_ERROR, error_msg)); | 
| 446 | 0 |                     response.push_back(std::move(result)); | 
| 447 | 0 |                 } | 
| 448 | 0 |             } | 
| 449 | 0 |         } | 
| 450 | 0 |     } | 
| 451 |  |  | 
| 452 | 0 |     return response; | 
| 453 | 0 | }, | 
| 454 | 0 |     }; | 
| 455 | 0 | } | 
| 456 |  |  | 
| 457 |  | RPCHelpMan listdescriptors() | 
| 458 | 0 | { | 
| 459 | 0 |     return RPCHelpMan{ | 
| 460 | 0 |         "listdescriptors", | 
| 461 | 0 |         "List all descriptors present in a wallet.\n", | 
| 462 | 0 |         { | 
| 463 | 0 |             {"private", RPCArg::Type::BOOL, RPCArg::Default{false}, "Show private descriptors."} | 
| 464 | 0 |         }, | 
| 465 | 0 |         RPCResult{RPCResult::Type::OBJ, "", "", { | 
| 466 | 0 |             {RPCResult::Type::STR, "wallet_name", "Name of wallet this operation was performed on"}, | 
| 467 | 0 |             {RPCResult::Type::ARR, "descriptors", "Array of descriptor objects (sorted by descriptor string representation)", | 
| 468 | 0 |             { | 
| 469 | 0 |                 {RPCResult::Type::OBJ, "", "", { | 
| 470 | 0 |                     {RPCResult::Type::STR, "desc", "Descriptor string representation"}, | 
| 471 | 0 |                     {RPCResult::Type::NUM, "timestamp", "The creation time of the descriptor"}, | 
| 472 | 0 |                     {RPCResult::Type::BOOL, "active", "Whether this descriptor is currently used to generate new addresses"}, | 
| 473 | 0 |                     {RPCResult::Type::BOOL, "internal", /*optional=*/true, "True if this descriptor is used to generate change addresses. False if this descriptor is used to generate receiving addresses; defined only for active descriptors"}, | 
| 474 | 0 |                     {RPCResult::Type::ARR_FIXED, "range", /*optional=*/true, "Defined only for ranged descriptors", { | 
| 475 | 0 |                         {RPCResult::Type::NUM, "", "Range start inclusive"}, | 
| 476 | 0 |                         {RPCResult::Type::NUM, "", "Range end inclusive"}, | 
| 477 | 0 |                     }}, | 
| 478 | 0 |                     {RPCResult::Type::NUM, "next", /*optional=*/true, "Same as next_index field. Kept for compatibility reason."}, | 
| 479 | 0 |                     {RPCResult::Type::NUM, "next_index", /*optional=*/true, "The next index to generate addresses from; defined only for ranged descriptors"}, | 
| 480 | 0 |                 }}, | 
| 481 | 0 |             }} | 
| 482 | 0 |         }}, | 
| 483 | 0 |         RPCExamples{ | 
| 484 | 0 |             HelpExampleCli("listdescriptors", "") + HelpExampleRpc("listdescriptors", "") | 
| 485 | 0 |             + HelpExampleCli("listdescriptors", "true") + HelpExampleRpc("listdescriptors", "true") | 
| 486 | 0 |         }, | 
| 487 | 0 |         [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue | 
| 488 | 0 | { | 
| 489 | 0 |     const std::shared_ptr<const CWallet> wallet = GetWalletForJSONRPCRequest(request); | 
| 490 | 0 |     if (!wallet) return UniValue::VNULL; | 
| 491 |  |  | 
| 492 | 0 |     const bool priv = !request.params[0].isNull() && request.params[0].get_bool(); | 
| 493 | 0 |     if (priv) { | 
| 494 | 0 |         EnsureWalletIsUnlocked(*wallet); | 
| 495 | 0 |     } | 
| 496 |  | 
 | 
| 497 | 0 |     LOCK(wallet->cs_wallet); | 
| 498 |  | 
 | 
| 499 | 0 |     const auto active_spk_mans = wallet->GetActiveScriptPubKeyMans(); | 
| 500 |  | 
 | 
| 501 | 0 |     struct WalletDescInfo { | 
| 502 | 0 |         std::string descriptor; | 
| 503 | 0 |         uint64_t creation_time; | 
| 504 | 0 |         bool active; | 
| 505 | 0 |         std::optional<bool> internal; | 
| 506 | 0 |         std::optional<std::pair<int64_t,int64_t>> range; | 
| 507 | 0 |         int64_t next_index; | 
| 508 | 0 |     }; | 
| 509 |  | 
 | 
| 510 | 0 |     std::vector<WalletDescInfo> wallet_descriptors; | 
| 511 | 0 |     for (const auto& spk_man : wallet->GetAllScriptPubKeyMans()) { | 
| 512 | 0 |         const auto desc_spk_man = dynamic_cast<DescriptorScriptPubKeyMan*>(spk_man); | 
| 513 | 0 |         if (!desc_spk_man) { | 
| 514 | 0 |             throw JSONRPCError(RPC_WALLET_ERROR, "Unexpected ScriptPubKey manager type."); | 
| 515 | 0 |         } | 
| 516 | 0 |         LOCK(desc_spk_man->cs_desc_man); | 
| 517 | 0 |         const auto& wallet_descriptor = desc_spk_man->GetWalletDescriptor(); | 
| 518 | 0 |         std::string descriptor; | 
| 519 | 0 |         if (!desc_spk_man->GetDescriptorString(descriptor, priv)) { | 
| 520 | 0 |             throw JSONRPCError(RPC_WALLET_ERROR, "Can't get descriptor string."); | 
| 521 | 0 |         } | 
| 522 | 0 |         const bool is_range = wallet_descriptor.descriptor->IsRange(); | 
| 523 | 0 |         wallet_descriptors.push_back({ | 
| 524 | 0 |             descriptor, | 
| 525 | 0 |             wallet_descriptor.creation_time, | 
| 526 | 0 |             active_spk_mans.count(desc_spk_man) != 0, | 
| 527 | 0 |             wallet->IsInternalScriptPubKeyMan(desc_spk_man), | 
| 528 | 0 |             is_range ? std::optional(std::make_pair(wallet_descriptor.range_start, wallet_descriptor.range_end)) : std::nullopt, | 
| 529 | 0 |             wallet_descriptor.next_index | 
| 530 | 0 |         }); | 
| 531 | 0 |     } | 
| 532 |  |  | 
| 533 | 0 |     std::sort(wallet_descriptors.begin(), wallet_descriptors.end(), [](const auto& a, const auto& b) { | 
| 534 | 0 |         return a.descriptor < b.descriptor; | 
| 535 | 0 |     }); | 
| 536 |  | 
 | 
| 537 | 0 |     UniValue descriptors(UniValue::VARR); | 
| 538 | 0 |     for (const WalletDescInfo& info : wallet_descriptors) { | 
| 539 | 0 |         UniValue spk(UniValue::VOBJ); | 
| 540 | 0 |         spk.pushKV("desc", info.descriptor); | 
| 541 | 0 |         spk.pushKV("timestamp", info.creation_time); | 
| 542 | 0 |         spk.pushKV("active", info.active); | 
| 543 | 0 |         if (info.internal.has_value()) { | 
| 544 | 0 |             spk.pushKV("internal", info.internal.value()); | 
| 545 | 0 |         } | 
| 546 | 0 |         if (info.range.has_value()) { | 
| 547 | 0 |             UniValue range(UniValue::VARR); | 
| 548 | 0 |             range.push_back(info.range->first); | 
| 549 | 0 |             range.push_back(info.range->second - 1); | 
| 550 | 0 |             spk.pushKV("range", std::move(range)); | 
| 551 | 0 |             spk.pushKV("next", info.next_index); | 
| 552 | 0 |             spk.pushKV("next_index", info.next_index); | 
| 553 | 0 |         } | 
| 554 | 0 |         descriptors.push_back(std::move(spk)); | 
| 555 | 0 |     } | 
| 556 |  | 
 | 
| 557 | 0 |     UniValue response(UniValue::VOBJ); | 
| 558 | 0 |     response.pushKV("wallet_name", wallet->GetName()); | 
| 559 | 0 |     response.pushKV("descriptors", std::move(descriptors)); | 
| 560 |  | 
 | 
| 561 | 0 |     return response; | 
| 562 | 0 | }, | 
| 563 | 0 |     }; | 
| 564 | 0 | } | 
| 565 |  |  | 
| 566 |  | RPCHelpMan backupwallet() | 
| 567 | 0 | { | 
| 568 | 0 |     return RPCHelpMan{ | 
| 569 | 0 |         "backupwallet", | 
| 570 | 0 |         "Safely copies the current wallet file to the specified destination, which can either be a directory or a path with a filename.\n", | 
| 571 | 0 |                 { | 
| 572 | 0 |                     {"destination", RPCArg::Type::STR, RPCArg::Optional::NO, "The destination directory or file"}, | 
| 573 | 0 |                 }, | 
| 574 | 0 |                 RPCResult{RPCResult::Type::NONE, "", ""}, | 
| 575 | 0 |                 RPCExamples{ | 
| 576 | 0 |                     HelpExampleCli("backupwallet", "\"backup.dat\"") | 
| 577 | 0 |             + HelpExampleRpc("backupwallet", "\"backup.dat\"") | 
| 578 | 0 |                 }, | 
| 579 | 0 |         [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue | 
| 580 | 0 | { | 
| 581 | 0 |     const std::shared_ptr<const CWallet> pwallet = GetWalletForJSONRPCRequest(request); | 
| 582 | 0 |     if (!pwallet) return UniValue::VNULL; | 
| 583 |  |  | 
| 584 |  |     // Make sure the results are valid at least up to the most recent block | 
| 585 |  |     // the user could have gotten from another RPC command prior to now | 
| 586 | 0 |     pwallet->BlockUntilSyncedToCurrentChain(); | 
| 587 |  | 
 | 
| 588 | 0 |     LOCK(pwallet->cs_wallet); | 
| 589 |  | 
 | 
| 590 | 0 |     std::string strDest = request.params[0].get_str(); | 
| 591 | 0 |     if (!pwallet->BackupWallet(strDest)) { | 
| 592 | 0 |         throw JSONRPCError(RPC_WALLET_ERROR, "Error: Wallet backup failed!"); | 
| 593 | 0 |     } | 
| 594 |  |  | 
| 595 | 0 |     return UniValue::VNULL; | 
| 596 | 0 | }, | 
| 597 | 0 |     }; | 
| 598 | 0 | } | 
| 599 |  |  | 
| 600 |  |  | 
| 601 |  | RPCHelpMan restorewallet() | 
| 602 | 0 | { | 
| 603 | 0 |     return RPCHelpMan{ | 
| 604 | 0 |         "restorewallet", | 
| 605 | 0 |         "Restores and loads a wallet from backup.\n" | 
| 606 | 0 |         "\nThe rescan is significantly faster if block filters are available" | 
| 607 | 0 |         "\n(using startup option \"-blockfilterindex=1\").\n", | 
| 608 | 0 |         { | 
| 609 | 0 |             {"wallet_name", RPCArg::Type::STR, RPCArg::Optional::NO, "The name that will be applied to the restored wallet"}, | 
| 610 | 0 |             {"backup_file", RPCArg::Type::STR, RPCArg::Optional::NO, "The backup file that will be used to restore the wallet."}, | 
| 611 | 0 |             {"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."}, | 
| 612 | 0 |         }, | 
| 613 | 0 |         RPCResult{ | 
| 614 | 0 |             RPCResult::Type::OBJ, "", "", | 
| 615 | 0 |             { | 
| 616 | 0 |                 {RPCResult::Type::STR, "name", "The wallet name if restored successfully."}, | 
| 617 | 0 |                 {RPCResult::Type::ARR, "warnings", /*optional=*/true, "Warning messages, if any, related to restoring and loading the wallet.", | 
| 618 | 0 |                 { | 
| 619 | 0 |                     {RPCResult::Type::STR, "", ""}, | 
| 620 | 0 |                 }}, | 
| 621 | 0 |             } | 
| 622 | 0 |         }, | 
| 623 | 0 |         RPCExamples{ | 
| 624 | 0 |             HelpExampleCli("restorewallet", "\"testwallet\" \"home\\backups\\backup-file.bak\"") | 
| 625 | 0 |             + HelpExampleRpc("restorewallet", "\"testwallet\" \"home\\backups\\backup-file.bak\"") | 
| 626 | 0 |             + HelpExampleCliNamed("restorewallet", {{"wallet_name", "testwallet"}, {"backup_file", "home\\backups\\backup-file.bak\""}, {"load_on_startup", true}}) | 
| 627 | 0 |             + HelpExampleRpcNamed("restorewallet", {{"wallet_name", "testwallet"}, {"backup_file", "home\\backups\\backup-file.bak\""}, {"load_on_startup", true}}) | 
| 628 | 0 |         }, | 
| 629 | 0 |         [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue | 
| 630 | 0 | { | 
| 631 |  | 
 | 
| 632 | 0 |     WalletContext& context = EnsureWalletContext(request.context); | 
| 633 |  | 
 | 
| 634 | 0 |     auto backup_file = fs::u8path(request.params[1].get_str()); | 
| 635 |  | 
 | 
| 636 | 0 |     std::string wallet_name = request.params[0].get_str(); | 
| 637 |  | 
 | 
| 638 | 0 |     std::optional<bool> load_on_start = request.params[2].isNull() ? std::nullopt : std::optional<bool>(request.params[2].get_bool()); | 
| 639 |  | 
 | 
| 640 | 0 |     DatabaseStatus status; | 
| 641 | 0 |     bilingual_str error; | 
| 642 | 0 |     std::vector<bilingual_str> warnings; | 
| 643 |  | 
 | 
| 644 | 0 |     const std::shared_ptr<CWallet> wallet = RestoreWallet(context, backup_file, wallet_name, load_on_start, status, error, warnings); | 
| 645 |  | 
 | 
| 646 | 0 |     HandleWalletError(wallet, status, error); | 
| 647 |  | 
 | 
| 648 | 0 |     UniValue obj(UniValue::VOBJ); | 
| 649 | 0 |     obj.pushKV("name", wallet->GetName()); | 
| 650 | 0 |     PushWarnings(warnings, obj); | 
| 651 |  | 
 | 
| 652 | 0 |     return obj; | 
| 653 |  | 
 | 
| 654 | 0 | }, | 
| 655 | 0 |     }; | 
| 656 | 0 | } | 
| 657 |  | } // namespace wallet |