Inside Bitcoin
Opening the selected file…
Inside Bitcoin
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.
2251}2253namespace {2254//! Search for a given set of pubkey scripts2255bool FindScriptPubKey(std::atomic<int>& scan_progress, const std::atomic<bool>& should_abort, int64_t& count, CCoinsViewCursor* cursor, const std::set<CScript>& needles, std::map<COutPoint, Coin>& out_results, std::function<void()>& interruption_point)2256{2257scan_progress = 0;2258count = 0;2259while (cursor->Valid()) {2260COutPoint key;2261Coin coin;2262if (!cursor->GetKey(key) || !cursor->GetValue(coin)) return false;2263if (++count % 8192 == 0) {2264interruption_point();2265if (should_abort) {2266// allow to abort the scan via the abort reference2267return false;2268}2269}2270if (count % 256 == 0) {2271// update progress reference every 256 item2272uint32_t high = 0x100 * *UCharCast(key.hash.begin()) + *(UCharCast(key.hash.begin()) + 1);2273scan_progress = (int)(high * 100.0 / 65536.0 + 0.5);2274}2275if (needles.contains(coin.out.scriptPubKey)) {2276out_results.emplace(key, coin);2277}2278cursor->Next();2279}2280scan_progress = 100;2281return true;2282}2283} // namespace2285/** RAII object to prevent concurrency issue when scanning the txout set */2286static std::atomic<int> g_scan_progress;2287static std::atomic<bool> g_scan_in_progress;2288static std::atomic<bool> g_should_abort_scan;2289class CoinsViewScanReserver2290{2291private:2292bool m_could_reserve{false};2293public:2294explicit CoinsViewScanReserver() = default;2296bool reserve() {2297CHECK_NONFATAL(!m_could_reserve);2298if (g_scan_in_progress.exchange(true)) {2299return false;2300}2301CHECK_NONFATAL(g_scan_progress == 0);2302m_could_reserve = true;2303return true;2304}2306~CoinsViewScanReserver() {2307if (m_could_reserve) {2308g_scan_in_progress = false;2309g_scan_progress = 0;2310}2311}2312};2314static const auto scan_action_arg_desc = RPCArg{2315"action", RPCArg::Type::STR, RPCArg::Optional::NO, "The action to execute\n"2316"\"start\" for starting a scan\n"2317"\"abort\" for aborting the current scan (returns true when abort was successful)\n"2318"\"status\" for progress report (in %) of the current scan"2319};2321static const auto output_descriptor_obj = RPCArg{2322"", RPCArg::Type::OBJ, RPCArg::Optional::OMITTED, "An object with output descriptor and metadata",2323{2324{"desc", RPCArg::Type::STR, RPCArg::Optional::NO, "An output descriptor"},2325{"range", RPCArg::Type::RANGE, RPCArg::Default{1000}, "The range of HD chain indexes to explore (either end or [begin,end])"},2326}2327};2329static const auto scan_objects_arg_desc = RPCArg{2330"scanobjects", RPCArg::Type::ARR, RPCArg::Optional::OMITTED, "Array of scan objects. Required for \"start\" action\n"2331"Every scan object is either a string descriptor or an object:",2332{2333{"descriptor", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "An output descriptor"},2334output_descriptor_obj,2335},2336RPCArgOptions{.oneline_description="[scanobjects,...]"},2337};2339static const auto scan_result_abort = RPCResult{2340"when action=='abort'", RPCResult::Type::BOOL, "success",2341"True if scan will be aborted (not necessarily before this RPC returns), or false if there is no scan to abort"2342};2343static const auto scan_result_status_none = RPCResult{2344"when action=='status' and no scan is in progress - possibly already completed", RPCResult::Type::NONE, "", ""2345};2346static const auto scan_result_status_some = RPCResult{2347"when action=='status' and a scan is currently in progress", RPCResult::Type::OBJ, "", "",2348{{RPCResult::Type::NUM, "progress", "Approximate percent complete"},}2349};2352static RPCMethod scantxoutset()2353{2354// raw() descriptor corresponding to mainnet address 12cbQLTFMXRnSzktFkuoG3eHoMeFtpTu3S2355const std::string EXAMPLE_DESCRIPTOR_RAW = "raw(76a91411b366edfc0a8b66feebae5c2e25a7b6a5d1cf3188ac)#fm24fxxy";2357return RPCMethod{2358"scantxoutset",2359"Scans the unspent transaction output set for entries that match certain output descriptors.\n"2360"Examples of output descriptors are:\n"2361" addr(<address>) Outputs whose output script corresponds to the specified address (does not include P2PK)\n"2362" raw(<hex script>) Outputs whose output script equals the specified hex-encoded bytes\n"2363" combo(<pubkey>) P2PK, P2PKH, P2WPKH, and P2SH-P2WPKH outputs for the given pubkey\n"2364" pkh(<pubkey>) P2PKH outputs for the given pubkey\n"2365" sh(multi(<n>,<pubkey>,<pubkey>,...)) P2SH-multisig outputs for the given threshold and pubkeys\n"2366" tr(<pubkey>) P2TR\n"2367" tr(<pubkey>,{pk(<pubkey>)}) P2TR with single fallback pubkey in tapscript\n"2368" rawtr(<pubkey>) P2TR with the specified key as output key rather than inner\n"2369" wsh(and_v(v:pk(<pubkey>),after(2))) P2WSH miniscript with mandatory pubkey and a timelock\n"2370"\nIn the above, <pubkey> either refers to a fixed public key in hexadecimal notation, or to an xpub/xprv optionally followed by one\n"2371"or more path elements separated by \"/\", and optionally ending in \"/*\" (unhardened), or \"/*'\" or \"/*h\" (hardened) to specify all\n"2372"unhardened or hardened child keys.\n"2373"In the latter case, a range needs to be specified by below if different from 1000.\n"2374"For more information on output descriptors, see the documentation in the doc/descriptors.md file.\n",2375{2376scan_action_arg_desc,2377scan_objects_arg_desc,2378},2379{2380RPCResult{"when action=='start'; only returns after scan completes", RPCResult::Type::OBJ, "", "", {2381{RPCResult::Type::BOOL, "success", "Whether the scan was completed"},2382{RPCResult::Type::NUM, "txouts", "The number of unspent transaction outputs scanned"},2383{RPCResult::Type::NUM, "height", "The block height at which the scan was done"},2384{RPCResult::Type::STR_HEX, "bestblock", "The hash of the block at the tip of the chain"},2385{RPCResult::Type::ARR, "unspents", "",2386{2387{RPCResult::Type::OBJ, "", "",2388{2389{RPCResult::Type::STR_HEX, "txid", "The transaction id"},2390{RPCResult::Type::NUM, "vout", "The vout value"},2391{RPCResult::Type::STR_HEX, "scriptPubKey", "The output script"},2392{RPCResult::Type::STR, "desc", "A specialized descriptor for the matched output script"},2393{RPCResult::Type::STR_AMOUNT, "amount", "The total amount in " + CURRENCY_UNIT + " of the unspent output"},2394{RPCResult::Type::BOOL, "coinbase", "Whether this is a coinbase output"},2395{RPCResult::Type::NUM, "height", "Height of the unspent transaction output"},2396{RPCResult::Type::STR_HEX, "blockhash", "Blockhash of the unspent transaction output"},2397{RPCResult::Type::NUM, "confirmations", "Number of confirmations of the unspent transaction output when the scan was done"},2398}},2399}},2400{RPCResult::Type::STR_AMOUNT, "total_amount", "The total amount of all found unspent outputs in " + CURRENCY_UNIT},2401}},2402scan_result_abort,2403scan_result_status_some,2404scan_result_status_none,2405},2406RPCExamples{2407HelpExampleCli("scantxoutset", "start \'[\"" + EXAMPLE_DESCRIPTOR_RAW + "\"]\'") +2408HelpExampleCli("scantxoutset", "status") +2409HelpExampleCli("scantxoutset", "abort") +2410HelpExampleRpc("scantxoutset", "\"start\", [\"" + EXAMPLE_DESCRIPTOR_RAW + "\"]") +2411HelpExampleRpc("scantxoutset", "\"status\"") +2412HelpExampleRpc("scantxoutset", "\"abort\"")2413},2414[](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue2415{2416UniValue result(UniValue::VOBJ);2417const auto action{self.Arg<std::string_view>("action")};2418if (action == "status") {2419CoinsViewScanReserver reserver;2420if (reserver.reserve()) {2421// no scan in progress2422return UniValue::VNULL;2423}2424result.pushKV("progress", g_scan_progress.load());2425return result;2426} else if (action == "abort") {2427CoinsViewScanReserver reserver;2428if (reserver.reserve()) {2429// reserve was possible which means no scan was running2430return false;2431}2432// set the abort flag2433g_should_abort_scan = true;2434return true;2435} else if (action == "start") {2436CoinsViewScanReserver reserver;2437if (!reserver.reserve()) {2438throw JSONRPCError(RPC_INVALID_PARAMETER, "Scan already in progress, use action \"abort\" or \"status\"");2439}2441const UniValue* scanobjects = self.MaybeArg<UniValue>("scanobjects");2442if (!scanobjects) {2443throw JSONRPCError(RPC_MISC_ERROR, "scanobjects argument is required for the start action");2444}2446std::set<CScript> needles;2447std::map<CScript, std::string> descriptors;2448CAmount total_in = 0;2450// loop through the scan objects2451for (const UniValue& scanobject : scanobjects->get_array().getValues()) {2452FlatSigningProvider provider;2453auto scripts = EvalDescriptorStringOrObject(scanobject, provider);2454for (CScript& script : scripts) {2455std::string inferred = InferDescriptor(script, provider)->ToString();2456needles.emplace(script);2457descriptors.emplace(std::move(script), std::move(inferred));2458}2459}2461// Scan the unspent transaction output set for inputs2462UniValue unspents(UniValue::VARR);2463std::vector<CTxOut> input_txos;2464std::map<COutPoint, Coin> coins;2465g_should_abort_scan = false;2466int64_t count = 0;2467std::unique_ptr<CCoinsViewCursor> pcursor;2468const CBlockIndex* tip;2469NodeContext& node = EnsureAnyNodeContext(request.context);2470{2471ChainstateManager& chainman = EnsureChainman(node);2472LOCK(cs_main);2473Chainstate& active_chainstate = chainman.ActiveChainstate();2474active_chainstate.ForceFlushStateToDisk(/*wipe_cache=*/false);2475pcursor = active_chainstate.CoinsDB().Cursor();2476tip = CHECK_NONFATAL(active_chainstate.m_chain.Tip());2477}2478bool res = FindScriptPubKey(g_scan_progress, g_should_abort_scan, count, pcursor.get(), needles, coins, node.rpc_interruption_point);2479result.pushKV("success", res);2480result.pushKV("txouts", count);2481result.pushKV("height", tip->nHeight);2482result.pushKV("bestblock", tip->GetBlockHash().GetHex());2484for (const auto& it : coins) {2485const COutPoint& outpoint = it.first;2486const Coin& coin = it.second;2487const CTxOut& txo = coin.out;2488const CBlockIndex& coinb_block{*CHECK_NONFATAL(tip->GetAncestor(coin.nHeight))};2489input_txos.push_back(txo);2490total_in += txo.nValue;2492UniValue unspent(UniValue::VOBJ);2493unspent.pushKV("txid", outpoint.hash.GetHex());2494unspent.pushKV("vout", outpoint.n);2495unspent.pushKV("scriptPubKey", HexStr(txo.scriptPubKey));2496unspent.pushKV("desc", descriptors[txo.scriptPubKey]);2497unspent.pushKV("amount", ValueFromAmount(txo.nValue));2498unspent.pushKV("coinbase", coin.IsCoinBase());2499unspent.pushKV("height", coin.nHeight);2500unspent.pushKV("blockhash", coinb_block.GetBlockHash().GetHex());