Inside Bitcoin
Opening the selected file…
Opening the selected file…
The code behind the network, with room to understand it.
Release published 2026-10-28 · Snapshot fetched 2026-11-01. Latest stable release at fetch time; development branches and Git history are excluded. Only the selected code window and visible folders load here.
252// If private keys are enabled, check some things.253if (!wallet.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {254if (keys.keys.empty()) {255throw JSONRPCError(RPC_WALLET_ERROR, "Cannot import descriptor without private keys to a wallet with private keys enabled");256}257if (!parsed_desc->HavePrivateKeys(keys)) {258warnings.push_back("Not all private keys provided. Some wallet functionality may return unexpected errors");259}260}262// If this is an unused(KEY) descriptor, check that the wallet doesn't already have other descriptors with this key263if (!parsed_desc->HasScripts()) {264if (wallet.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {265throw JSONRPCError(RPC_WALLET_ERROR, "Cannot import unused() to wallet without private keys enabled");266}267// Unused descriptors must contain a single key.268// Earlier checks will have enforced that this key is either a private key when private keys are enabled,269// or that this key is a public key when private keys are disabled.270// If we can retrieve the corresponding private key from the wallet, then this key is already in the wallet271// and we should not import it.272std::set<CPubKey> pubkeys;273std::set<CExtPubKey> extpubs;274parsed_desc->GetPubKeys(pubkeys, extpubs);275std::transform(extpubs.begin(), extpubs.end(), std::inserter(pubkeys, pubkeys.begin()), [](const CExtPubKey& xpub) { return xpub.pubkey; });276CHECK_NONFATAL(pubkeys.size() == 1);277if (wallet.GetKey(pubkeys.begin()->GetID())) {278throw JSONRPCError(RPC_WALLET_ERROR, "Cannot import an unused() descriptor when its private key is already in the wallet");279}280}282WalletDescriptor w_desc(std::move(parsed_desc), timestamp, range_start, range_end, next_index);284// Add descriptor to the wallet285auto spk_manager_res = wallet.AddWalletDescriptor(w_desc, keys, label, desc_internal);287if (!spk_manager_res) {288throw JSONRPCError(RPC_WALLET_ERROR, strprintf("Could not add descriptor '%s': %s", descriptor, util::ErrorString(spk_manager_res).original));289}291auto& spk_manager = spk_manager_res.value().get();293// Set descriptor as active if necessary294if (active) {295if (!w_desc.descriptor->GetOutputType()) {296warnings.push_back("Unknown output type, cannot set descriptor to active.");297} else {298wallet.AddActiveScriptPubKeyMan(spk_manager.GetID(), *w_desc.descriptor->GetOutputType(), desc_internal);299}300} else {301if (w_desc.descriptor->GetOutputType()) {302wallet.DeactivateScriptPubKeyMan(spk_manager.GetID(), *w_desc.descriptor->GetOutputType(), desc_internal);303}304}305}307result.pushKV("success", UniValue(true));308} catch (const UniValue& e) {309result.pushKV("success", UniValue(false));310result.pushKV("error", e);311}312PushWarnings(warnings, result);313return result;314}316RPCMethod importdescriptors()317{318return RPCMethod{319"importdescriptors",320"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"321"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"322"\nNote: This call can take over an hour to complete if using an early timestamp; during that time, other rpc calls\n"323"may report that the imported keys, addresses or scripts exist but related transactions are still missing.\n"324"The rescan is significantly faster if block filters are available (using startup option \"-blockfilterindex=1\").\n",325{326{"requests", RPCArg::Type::ARR, RPCArg::Optional::NO, "Data to be imported",327{328{"", RPCArg::Type::OBJ, RPCArg::Optional::OMITTED, "",329{330{"desc", RPCArg::Type::STR, RPCArg::Optional::NO, "Descriptor to import."},331{"active", RPCArg::Type::BOOL, RPCArg::Default{false}, "Set this descriptor to be the active descriptor for the corresponding output type/externality"},332{"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"},333{"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"},334{"timestamp", RPCArg::Type::NUM, RPCArg::Optional::NO, "Time from which to start rescanning the blockchain for this descriptor, in " + UNIX_EPOCH_TIME + "\n"335"Use the string \"now\" to substitute the current synced blockchain time.\n"336"\"now\" can be specified to bypass scanning, for outputs which are known to never have been used, and\n"337"0 can be specified to scan the entire blockchain. Blocks up to 2 hours before the earliest timestamp\n"338"of all descriptors being imported will be scanned as well as the mempool.",339RPCArgOptions{.type_str={"timestamp | \"now\"", "integer / string"}}340},341{"internal", RPCArg::Type::BOOL, RPCArg::Default{false}, "Whether matching outputs should be treated as not incoming payments (e.g. change)"},342{"label", RPCArg::Type::STR, RPCArg::Default{""}, "Label to assign to the address, only allowed with internal=false. Disabled for ranged descriptors"},343},344},345},346RPCArgOptions{.oneline_description="requests"}},347},348RPCResult{349RPCResult::Type::ARR, "", "Response is an array with the same size as the input that has the execution result",350{351{RPCResult::Type::OBJ, "", "",352{353{RPCResult::Type::BOOL, "success", ""},354{RPCResult::Type::ARR, "warnings", /*optional=*/true, "",355{356{RPCResult::Type::STR, "", ""},357}},358{RPCResult::Type::OBJ, "error", /*optional=*/true, "",359{360{RPCResult::Type::NUM, "code", "JSONRPC error code"},361{RPCResult::Type::STR, "message", "JSONRPC error message"},362}},363}},364}365},366RPCExamples{367HelpExampleCli("importdescriptors", "'[{ \"desc\": \"<my descriptor>\", \"timestamp\":1455191478, \"internal\": true }, "368"{ \"desc\": \"<my descriptor 2>\", \"label\": \"example 2\", \"timestamp\": 1455191480 }]'") +369HelpExampleCli("importdescriptors", "'[{ \"desc\": \"<my descriptor>\", \"timestamp\":1455191478, \"active\": true, \"range\": [0,100], \"label\": \"<my bech32 wallet>\" }]'")370},371[](const RPCMethod& self, const JSONRPCRequest& main_request) -> UniValue372{373std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(main_request);374if (!pwallet) return UniValue::VNULL;375CWallet& wallet{*pwallet};377WalletRescanReserver reserver(*pwallet);378if (!reserver.reserve(/*with_passphrase=*/true)) {379throw JSONRPCError(RPC_WALLET_ERROR, "Wallet is currently rescanning. Abort existing rescan or wait.");380}382// Make sure the results are valid at least up to the most recent block383// the user could have gotten from another RPC command prior to now384wallet.BlockUntilSyncedToCurrentChain();386// Ensure that the wallet is not locked for the remainder of this RPC, as387// the passphrase is used to top up the keypool.388LOCK(pwallet->m_relock_mutex);390const UniValue& requests = main_request.params[0];391const int64_t minimum_timestamp = 1;392int64_t now = 0;393int64_t lowest_timestamp = 0;394bool rescan = false;395UniValue response(UniValue::VARR);396{397LOCK(pwallet->cs_wallet);398EnsureWalletIsUnlocked(*pwallet);400CHECK_NONFATAL(pwallet->chain().findBlock(pwallet->GetLastBlockHash(), FoundBlock().time(lowest_timestamp).mtpTime(now)));402// Get all timestamps and extract the lowest timestamp403for (const UniValue& request : requests.getValues()) {404// This throws an error if "timestamp" doesn't exist405const int64_t timestamp = std::max(GetImportTimestamp(request, now), minimum_timestamp);406const UniValue result = ProcessDescriptorImport(*pwallet, request, timestamp);407response.push_back(result);409if (lowest_timestamp > timestamp ) {410lowest_timestamp = timestamp;411}413// If we know the chain tip, and at least one request was successful then allow rescan414if (!rescan && result["success"].get_bool()) {415rescan = true;416}417}418pwallet->ConnectScriptPubKeyManNotifiers();419pwallet->RefreshAllTXOs();420}422// Rescan the blockchain using the lowest timestamp423if (rescan) {424int64_t scanned_time = pwallet->RescanFromTime(lowest_timestamp, reserver);425pwallet->ResubmitWalletTransactions(node::TxBroadcast::MEMPOOL_NO_BROADCAST, /*force=*/true);427if (pwallet->IsAbortingRescan()) {428throw JSONRPCError(RPC_MISC_ERROR, "Rescan aborted by user.");429}431if (scanned_time > lowest_timestamp) {432std::vector<UniValue> results = response.getValues();433response.clear();434response.setArray();436// Compose the response437for (unsigned int i = 0; i < requests.size(); ++i) {438const UniValue& request = requests.getValues().at(i);440// If the descriptor timestamp is within the successfully scanned441// range, or if the import result already has an error set, let442// the result stand unmodified. Otherwise replace the result443// with an error message.444if (scanned_time <= GetImportTimestamp(request, now) || results.at(i).exists("error")) {445response.push_back(results.at(i));446} else {447std::string error_msg{strprintf("Rescan failed for descriptor with timestamp %d. There "448"was an error reading a block from time %d, which is after or within %d seconds "449"of key creation, and could contain transactions pertaining to the desc. As a "450"result, transactions and coins using this desc may not appear in the wallet.",451GetImportTimestamp(request, now), scanned_time - TIMESTAMP_WINDOW - 1, TIMESTAMP_WINDOW)};452if (pwallet->chain().havePruned()) {453error_msg += strprintf(" This error could be caused by pruning or data corruption "454"(see bitcoind log for details) and could be dealt with by downloading and "455"rescanning the relevant blocks (see -reindex option and rescanblockchain RPC).");456} else if (pwallet->chain().hasAssumedValidChain()) {457error_msg += strprintf(" This error is likely caused by an in-progress assumeutxo "458"background sync. Check logs or getchainstates RPC for assumeutxo background "459"sync progress and try again later.");460} else {461error_msg += strprintf(" This error could potentially caused by data corruption. If "462"the issue persists you may want to reindex (see -reindex option).");463}465UniValue result = UniValue(UniValue::VOBJ);466result.pushKV("success", UniValue(false));467result.pushKV("error", JSONRPCError(RPC_MISC_ERROR, error_msg));468response.push_back(std::move(result));469}470}471}472}474return response;475},476};477}479RPCMethod listdescriptors()480{481return RPCMethod{482"listdescriptors",483"List all descriptors present in a wallet.\n",484{485{"private", RPCArg::Type::BOOL, RPCArg::Default{false}, "Show private descriptors."}486},487RPCResult{RPCResult::Type::OBJ, "", "", {488{RPCResult::Type::STR, "wallet_name", "Name of wallet this operation was performed on"},489{RPCResult::Type::ARR, "descriptors", "Array of descriptor objects (sorted by descriptor string representation)",490{491{RPCResult::Type::OBJ, "", "", {492{RPCResult::Type::STR, "desc", "Descriptor string representation"},493{RPCResult::Type::NUM, "timestamp", "The creation time of the descriptor"},494{RPCResult::Type::BOOL, "active", "Whether this descriptor is currently used to generate new addresses"},495{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"},496{RPCResult::Type::ARR_FIXED, "range", /*optional=*/true, "Defined only for ranged descriptors", {497{RPCResult::Type::NUM, "", "Range start inclusive"},498{RPCResult::Type::NUM, "", "Range end inclusive"},499}},500{RPCResult::Type::NUM, "next", /*optional=*/true, "Same as next_index field. Kept for compatibility reason."},