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.
2501unspent.pushKV("confirmations", tip->nHeight - coin.nHeight + 1);2503unspents.push_back(std::move(unspent));2504}2505result.pushKV("unspents", std::move(unspents));2506result.pushKV("total_amount", ValueFromAmount(total_in));2507} else {2508throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Invalid action '%s'", action));2509}2510return result;2511},2512};2513}2515/** RAII object to prevent concurrency issue when scanning blockfilters */2516static std::atomic<int> g_scanfilter_progress;2517static std::atomic<int> g_scanfilter_progress_height;2518static std::atomic<bool> g_scanfilter_in_progress;2519static std::atomic<bool> g_scanfilter_should_abort_scan;2520class BlockFiltersScanReserver2521{2522private:2523bool m_could_reserve{false};2524public:2525explicit BlockFiltersScanReserver() = default;2527bool reserve() {2528CHECK_NONFATAL(!m_could_reserve);2529if (g_scanfilter_in_progress.exchange(true)) {2530return false;2531}2532m_could_reserve = true;2533return true;2534}2536~BlockFiltersScanReserver() {2537if (m_could_reserve) {2538g_scanfilter_in_progress = false;2539}2540}2541};2543static bool CheckBlockFilterMatches(BlockManager& blockman, const CBlockIndex& blockindex, const GCSFilter::ElementSet& needles)2544{2545const CBlock block{GetBlockChecked(blockman, blockindex)};2546const CBlockUndo block_undo{GetUndoChecked(blockman, blockindex)};2548// Check if any of the outputs match the scriptPubKey2549for (const auto& tx : block.vtx) {2550if (std::any_of(tx->vout.cbegin(), tx->vout.cend(), [&](const auto& txout) {2551return needles.contains(std::vector<unsigned char>(txout.scriptPubKey.begin(), txout.scriptPubKey.end()));2552})) {2553return true;2554}2555}2556// Check if any of the inputs match the scriptPubKey2557for (const auto& txundo : block_undo.vtxundo) {2558if (std::any_of(txundo.vprevout.cbegin(), txundo.vprevout.cend(), [&](const auto& coin) {2559return needles.contains(std::vector<unsigned char>(coin.out.scriptPubKey.begin(), coin.out.scriptPubKey.end()));2560})) {2561return true;2562}2563}2565return false;2566}2568static RPCMethod scanblocks()2569{2570return RPCMethod{2571"scanblocks",2572"Return relevant blockhashes for given descriptors (requires blockfilterindex).\n"2573"This call may take several minutes. Make sure to use no RPC timeout (bitcoin-cli -rpcclienttimeout=0)",2574{2575scan_action_arg_desc,2576scan_objects_arg_desc,2577RPCArg{"start_height", RPCArg::Type::NUM, RPCArg::Default{0}, "Height to start to scan from"},2578RPCArg{"stop_height", RPCArg::Type::NUM, RPCArg::DefaultHint{"chain tip"}, "Height to stop to scan"},2579RPCArg{"filtertype", RPCArg::Type::STR, RPCArg::Default{BlockFilterTypeName(BlockFilterType::BASIC)}, "The type name of the filter"},2580RPCArg{"options", RPCArg::Type::OBJ_NAMED_PARAMS, RPCArg::Optional::OMITTED, "",2581{2582{"filter_false_positives", RPCArg::Type::BOOL, RPCArg::Default{false}, "Filter false positives (slower and may fail on pruned nodes). Otherwise they may occur at a rate of 1/M"},2583},2584RPCArgOptions{.oneline_description="options"}},2585},2586{2587scan_result_status_none,2588RPCResult{"When action=='start'; only returns after scan completes", RPCResult::Type::OBJ, "", "", {2589{RPCResult::Type::NUM, "from_height", "The height we started the scan from"},2590{RPCResult::Type::NUM, "to_height", "The height we ended the scan at"},2591{RPCResult::Type::ARR, "relevant_blocks", "Blocks that may have matched a scanobject.", {2592{RPCResult::Type::STR_HEX, "blockhash", "A relevant blockhash"},2593}},2594{RPCResult::Type::BOOL, "completed", "true if the scan process was not aborted"}2595}},2596RPCResult{"when action=='status' and a scan is currently in progress", RPCResult::Type::OBJ, "", "", {2597{RPCResult::Type::NUM, "progress", "Approximate percent complete"},2598{RPCResult::Type::NUM, "current_height", "Height of the block currently being scanned"},2599},2600},2601scan_result_abort,2602},2603RPCExamples{2604HelpExampleCli("scanblocks", "start '[\"addr(bcrt1q4u4nsgk6ug0sqz7r3rj9tykjxrsl0yy4d0wwte)\"]' 300000") +2605HelpExampleCli("scanblocks", "start '[\"addr(bcrt1q4u4nsgk6ug0sqz7r3rj9tykjxrsl0yy4d0wwte)\"]' 100 150 basic") +2606HelpExampleCli("scanblocks", "status") +2607HelpExampleRpc("scanblocks", "\"start\", [\"addr(bcrt1q4u4nsgk6ug0sqz7r3rj9tykjxrsl0yy4d0wwte)\"], 300000") +2608HelpExampleRpc("scanblocks", "\"start\", [\"addr(bcrt1q4u4nsgk6ug0sqz7r3rj9tykjxrsl0yy4d0wwte)\"], 100, 150, \"basic\"") +2609HelpExampleRpc("scanblocks", "\"status\"")2610},2611[](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue2612{2613UniValue ret(UniValue::VOBJ);2614auto action{self.Arg<std::string_view>("action")};2615if (action == "status") {2616BlockFiltersScanReserver reserver;2617if (reserver.reserve()) {2618// no scan in progress2619return NullUniValue;2620}2621ret.pushKV("progress", g_scanfilter_progress.load());2622ret.pushKV("current_height", g_scanfilter_progress_height.load());2623return ret;2624} else if (action == "abort") {2625BlockFiltersScanReserver reserver;2626if (reserver.reserve()) {2627// reserve was possible which means no scan was running2628return false;2629}2630// set the abort flag2631g_scanfilter_should_abort_scan = true;2632return true;2633} else if (action == "start") {2634BlockFiltersScanReserver reserver;2635if (!reserver.reserve()) {2636throw JSONRPCError(RPC_INVALID_PARAMETER, "Scan already in progress, use action \"abort\" or \"status\"");2637}2638const UniValue* scanobjects = self.MaybeArg<UniValue>("scanobjects");2639if (!scanobjects) {2640throw JSONRPCError(RPC_MISC_ERROR, "scanobjects argument is required for the start action");2641}2642auto filtertype_name{self.Arg<std::string_view>("filtertype")};2644BlockFilterType filtertype;2645if (!BlockFilterTypeByName(filtertype_name, filtertype)) {2646throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Unknown filtertype");2647}2649UniValue options{request.params[5].isNull() ? UniValue::VOBJ : request.params[5]};2650bool filter_false_positives{options.exists("filter_false_positives") ? options["filter_false_positives"].get_bool() : false};2652BlockFilterIndex* index = GetBlockFilterIndex(filtertype);2653if (!index) {2654throw JSONRPCError(RPC_MISC_ERROR, tfm::format("Index is not enabled for filtertype %s", filtertype_name));2655}2657NodeContext& node = EnsureAnyNodeContext(request.context);2658ChainstateManager& chainman = EnsureChainman(node);2660// set the start-height2661const CBlockIndex* start_index = nullptr;2662const CBlockIndex* stop_block = nullptr;2663{2664LOCK(cs_main);2665CChain& active_chain = chainman.ActiveChain();2666start_index = active_chain.Genesis();2667stop_block = active_chain.Tip(); // If no stop block is provided, stop at the chain tip.2668if (!request.params[2].isNull()) {2669start_index = active_chain[request.params[2].getInt<int>()];2670if (!start_index) {2671throw JSONRPCError(RPC_MISC_ERROR, "Invalid start_height");2672}2673}2674if (!request.params[3].isNull()) {2675stop_block = active_chain[request.params[3].getInt<int>()];2676if (!stop_block || stop_block->nHeight < start_index->nHeight) {2677throw JSONRPCError(RPC_MISC_ERROR, "Invalid stop_height");2678}2679}2680}2681CHECK_NONFATAL(start_index);2682CHECK_NONFATAL(stop_block);2684// loop through the scan objects, add scripts to the needle_set2685GCSFilter::ElementSet needle_set;2686for (const UniValue& scanobject : scanobjects->get_array().getValues()) {2687FlatSigningProvider provider;2688std::vector<CScript> scripts = EvalDescriptorStringOrObject(scanobject, provider);2689for (const CScript& script : scripts) {2690needle_set.emplace(script.begin(), script.end());2691}2692}2693UniValue blocks(UniValue::VARR);2694const int amount_per_chunk = 10000;2695std::vector<BlockFilter> filters;2696int start_block_height = start_index->nHeight; // for progress reporting2697const int total_blocks_to_process = stop_block->nHeight - start_block_height;2699g_scanfilter_should_abort_scan = false;2700g_scanfilter_progress = 0;2701g_scanfilter_progress_height = start_block_height;2702bool completed = true;2704const CBlockIndex* end_range = nullptr;2705do {2706node.rpc_interruption_point(); // allow a clean shutdown2707if (g_scanfilter_should_abort_scan) {2708completed = false;2709break;2710}2712// split the lookup range in chunks if we are deeper than 'amount_per_chunk' blocks from the stopping block2713int start_block = !end_range ? start_index->nHeight : start_index->nHeight + 1; // to not include the previous round 'end_range' block2714end_range = (start_block + amount_per_chunk < stop_block->nHeight) ?2715WITH_LOCK(::cs_main, return chainman.ActiveChain()[start_block + amount_per_chunk]) :2716stop_block;2718if (index->LookupFilterRange(start_block, end_range, filters)) {2719for (const BlockFilter& filter : filters) {2720// compare the elements-set with each filter2721if (filter.GetFilter().MatchAny(needle_set)) {2722if (filter_false_positives) {2723// Double check the filter matches by scanning the block2724const CBlockIndex& blockindex = *CHECK_NONFATAL(WITH_LOCK(cs_main, return chainman.m_blockman.LookupBlockIndex(filter.GetBlockHash())));2726if (!CheckBlockFilterMatches(chainman.m_blockman, blockindex, needle_set)) {2727continue;2728}2729}2731blocks.push_back(filter.GetBlockHash().GetHex());2732}2733}2734}2735start_index = end_range;2737// update progress2738int blocks_processed = end_range->nHeight - start_block_height;2739if (total_blocks_to_process > 0) { // avoid division by zero2740g_scanfilter_progress = (int)(100.0 / total_blocks_to_process * blocks_processed);2741} else {2742g_scanfilter_progress = 100;2743}2744g_scanfilter_progress_height = end_range->nHeight;2746// Finish if we reached the stop block2747} while (start_index != stop_block);2749ret.pushKV("from_height", start_block_height);2750ret.pushKV("to_height", start_index->nHeight); // start_index is always the last scanned block here