@@ -2288,6 +2288,198 @@ static RPCHelpMan scantxoutset()
22882288 };
22892289}
22902290
2291+ /* * RAII object to prevent concurrency issue when scanning blockfilters */
2292+ static std::atomic<int > g_scanfilter_progress;
2293+ static std::atomic<int > g_scanfilter_progress_height;
2294+ static std::atomic<bool > g_scanfilter_in_progress;
2295+ static std::atomic<bool > g_scanfilter_should_abort_scan;
2296+ class BlockFiltersScanReserver
2297+ {
2298+ private:
2299+ bool m_could_reserve;
2300+ public:
2301+ explicit BlockFiltersScanReserver () : m_could_reserve(false ) {}
2302+
2303+ bool reserve () {
2304+ CHECK_NONFATAL (!m_could_reserve);
2305+ if (g_scanfilter_in_progress.exchange (true )) {
2306+ return false ;
2307+ }
2308+ m_could_reserve = true ;
2309+ return true ;
2310+ }
2311+
2312+ ~BlockFiltersScanReserver () {
2313+ if (m_could_reserve) {
2314+ g_scanfilter_in_progress = false ;
2315+ }
2316+ }
2317+ };
2318+
2319+ static RPCHelpMan scanblocks ()
2320+ {
2321+ return RPCHelpMan{" scanblocks" ,
2322+ " \n Return relevant blockhashes for given descriptors.\n "
2323+ " This call may take serval minutes. Make sure to use no RPC timeout (bitcoin-cli -rpcclienttimeout=0)" ,
2324+ {
2325+ {" action" , RPCArg::Type::STR, RPCArg::Optional::NO, " The action to execute\n "
2326+ " \" start\" for starting a scan\n "
2327+ " \" abort\" for aborting the current scan (returns true when abort was successful)\n "
2328+ " \" status\" for progress report (in %) of the current scan (returns Null if there is no ongoing scan)" },
2329+ {" scanobjects" , RPCArg::Type::ARR, RPCArg::Optional::OMITTED, " Array of scan objects.\n "
2330+ " Every scan object is either a string descriptor or an object:" ,
2331+ {
2332+ {" descriptor" , RPCArg::Type::STR, RPCArg::Optional::OMITTED, " An output descriptor" },
2333+ {" " , RPCArg::Type::OBJ, RPCArg::Optional::OMITTED, " An object with output descriptor and metadata" ,
2334+ {
2335+ {" desc" , RPCArg::Type::STR, RPCArg::Optional::NO, " An output descriptor" },
2336+ {" range" , RPCArg::Type::RANGE, /* default */ " 1000" , " The range of HD chain indexes to explore (either end or [begin,end])" },
2337+ },
2338+ },
2339+ },
2340+ " [scanobjects,...]" },
2341+ {" start_height" , RPCArg::Type::NUM, /* default*/ " 0" , " height to start to filter from" },
2342+ {" stop_height" , RPCArg::Type::NUM, /* default*/ " <tip>" , " height to stop to scan" },
2343+ {" filtertype" , RPCArg::Type::STR, /* default*/ " basic" , " The type name of the filter" }
2344+ },
2345+ RPCResult{
2346+ RPCResult::Type::ARR, " " , " " ,
2347+ {
2348+ {RPCResult::Type::STR_HEX, " " , " The blockhash" },
2349+ }},
2350+ RPCExamples{
2351+ HelpExampleCli (" scanblocks" , " \" [\" addr(bcrt1q4u4nsgk6ug0sqz7r3rj9tykjxrsl0yy4d0wwte)\" ]\" 300000" ) +
2352+ HelpExampleRpc (" scanblocks" , " \" [\" addr(bcrt1q4u4nsgk6ug0sqz7r3rj9tykjxrsl0yy4d0wwte)\" ]\" 300000" )
2353+ },
2354+ [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
2355+ {
2356+ UniValue ret (UniValue::VOBJ);
2357+ if (request.params [0 ].get_str () == " status" ) {
2358+ BlockFiltersScanReserver reserver;
2359+ if (reserver.reserve ()) {
2360+ // no scan in progress
2361+ return NullUniValue;
2362+ }
2363+ ret.pushKV (" progress" , g_scanfilter_progress);
2364+ ret.pushKV (" current_height" , g_scanfilter_progress_height);
2365+ return ret;
2366+ } else if (request.params [0 ].get_str () == " abort" ) {
2367+ BlockFiltersScanReserver reserver;
2368+ if (reserver.reserve ()) {
2369+ // reserve was possible which means no scan was running
2370+ return false ;
2371+ }
2372+ // set the abort flag
2373+ g_scanfilter_should_abort_scan = true ;
2374+ return true ;
2375+ }
2376+ else if (request.params [0 ].get_str () == " start" ) {
2377+ BlockFiltersScanReserver reserver;
2378+ if (!reserver.reserve ()) {
2379+ throw JSONRPCError (RPC_INVALID_PARAMETER, " Scan already in progress, use action \" abort\" or \" status\" " );
2380+ }
2381+ const std::string filtertype_name{request.params [4 ].isNull () ? " basic" : request.params [4 ].get_str ()};
2382+
2383+ BlockFilterType filtertype;
2384+ if (!BlockFilterTypeByName (filtertype_name, filtertype)) {
2385+ throw JSONRPCError (RPC_INVALID_ADDRESS_OR_KEY, " Unknown filtertype" );
2386+ }
2387+
2388+ BlockFilterIndex* index = GetBlockFilterIndex (filtertype);
2389+ if (!index) {
2390+ throw JSONRPCError (RPC_MISC_ERROR, " Index is not enabled for filtertype " + filtertype_name);
2391+ }
2392+
2393+ // set the start-height
2394+ const CBlockIndex* block = nullptr ;
2395+ const CBlockIndex* stop_block = nullptr ;
2396+ {
2397+ LOCK (cs_main);
2398+ block = ::ChainActive ().Genesis ();
2399+ stop_block = ::ChainActive ().Tip ();
2400+ if (!request.params [2 ].isNull ()) {
2401+ block = ::ChainActive ()[request.params [2 ].get_int ()];
2402+ if (!block) {
2403+ throw JSONRPCError (RPC_MISC_ERROR, " Invalid start_height" );
2404+ }
2405+ }
2406+ if (!request.params [3 ].isNull ()) {
2407+ stop_block = ::ChainActive ()[request.params [3 ].get_int ()];
2408+ if (!stop_block || stop_block->nHeight < block->nHeight ) {
2409+ throw JSONRPCError (RPC_MISC_ERROR, " Invalid stop_height" );
2410+ }
2411+ }
2412+ }
2413+ CHECK_NONFATAL (block);
2414+
2415+ // loop through the scan objects, add scripts to the needle_set
2416+ GCSFilter::ElementSet needle_set;
2417+ for (const UniValue& scanobject : request.params [1 ].get_array ().getValues ()) {
2418+ FlatSigningProvider provider;
2419+ std::vector<CScript> scripts = EvalDescriptorStringOrObject (scanobject, provider);
2420+ for (const CScript& script : scripts) {
2421+ needle_set.emplace (script.begin (), script.end ());
2422+ }
2423+ }
2424+ NodeContext& node = EnsureNodeContext (request.context );
2425+ UniValue blocks (UniValue::VARR);
2426+ const int amount_per_chunk = 10000 ;
2427+ const CBlockIndex* start_index = block; // for remembering the start of a blockfilter range
2428+ std::vector<BlockFilter> filters;
2429+ const CBlockIndex* start_block = block; // for progress reporting
2430+ const CBlockIndex* last_scanned_block = block;
2431+ g_scanfilter_should_abort_scan = false ;
2432+ g_scanfilter_progress = 0 ;
2433+ g_scanfilter_progress_height = start_block->nHeight ;
2434+ while (block) {
2435+ node.rpc_interruption_point (); // allow a clean shutdown
2436+ if (g_scanfilter_should_abort_scan) {
2437+ break ;
2438+ }
2439+ const CBlockIndex* next = nullptr ;
2440+ {
2441+ LOCK (cs_main);
2442+ next = ChainActive ().Next (block);
2443+ if (block == stop_block) next = nullptr ;
2444+ }
2445+ if (start_index->nHeight + amount_per_chunk == block->nHeight || next == nullptr ) {
2446+ LogPrint (BCLog::RPC, " Fetching blockfilters from height %d to height %d.\n " , start_index->nHeight , block->nHeight );
2447+ if (index->LookupFilterRange (start_index->nHeight , block, filters)) {
2448+ for (const BlockFilter& filter : filters) {
2449+ // compare the elements-set with each filter
2450+ if (filter.GetFilter ().MatchAny (needle_set)) {
2451+ blocks.push_back (filter.GetBlockHash ().GetHex ());
2452+ LogPrint (BCLog::RPC, " scanblocks: found match in %s\n " , filter.GetBlockHash ().GetHex ());
2453+ }
2454+ }
2455+ }
2456+ start_index = block;
2457+
2458+ // update progress
2459+ int blocks_processed = block->nHeight - start_block->nHeight ;
2460+ int total_blocks_to_process = stop_block->nHeight - start_block->nHeight ;
2461+ if (total_blocks_to_process > 0 ) { // avoid division by zero
2462+ g_scanfilter_progress = (int )(100.0 / total_blocks_to_process * blocks_processed);
2463+ } else {
2464+ g_scanfilter_progress = 100 ;
2465+ }
2466+ g_scanfilter_progress_height = block->nHeight ;
2467+ }
2468+ last_scanned_block = block;
2469+ block = next;
2470+ }
2471+ ret.pushKV (" from_height" , start_block->nHeight );
2472+ ret.pushKV (" to_height" , last_scanned_block->nHeight );
2473+ ret.pushKV (" relevant_blocks" , blocks);
2474+ }
2475+ else {
2476+ throw JSONRPCError (RPC_INVALID_PARAMETER, " Invalid action argument" );
2477+ }
2478+ return ret;
2479+ },
2480+ };
2481+ }
2482+
22912483static RPCHelpMan getblockfilter ()
22922484{
22932485 return RPCHelpMan{" getblockfilter" ,
@@ -2511,6 +2703,7 @@ static const CRPCCommand commands[] =
25112703
25122704 { " blockchain" , &preciousblock, },
25132705 { " blockchain" , &scantxoutset, },
2706+ { " blockchain" , &scanblocks, },
25142707 { " blockchain" , &getblockfilter, },
25152708
25162709 /* Not shown in help */
0 commit comments