@@ -2204,6 +2204,203 @@ static RPCHelpMan scantxoutset()
22042204 };
22052205}
22062206
2207+ /* * RAII object to prevent concurrency issue when scanning blockfilters */
2208+ static std::atomic<int > g_scanfilter_progress;
2209+ static std::atomic<int > g_scanfilter_progress_height;
2210+ static std::atomic<bool > g_scanfilter_in_progress;
2211+ static std::atomic<bool > g_scanfilter_should_abort_scan;
2212+ class BlockFiltersScanReserver
2213+ {
2214+ private:
2215+ bool m_could_reserve{false };
2216+ public:
2217+ explicit BlockFiltersScanReserver () = default;
2218+
2219+ bool reserve () {
2220+ CHECK_NONFATAL (!m_could_reserve);
2221+ if (g_scanfilter_in_progress.exchange (true )) {
2222+ return false ;
2223+ }
2224+ m_could_reserve = true ;
2225+ return true ;
2226+ }
2227+
2228+ ~BlockFiltersScanReserver () {
2229+ if (m_could_reserve) {
2230+ g_scanfilter_in_progress = false ;
2231+ }
2232+ }
2233+ };
2234+
2235+ static RPCHelpMan scanblocks ()
2236+ {
2237+ return RPCHelpMan{" scanblocks" ,
2238+ " \n Return relevant blockhashes for given descriptors.\n "
2239+ " This call may take several minutes. Make sure to use no RPC timeout (bitcoin-cli -rpcclienttimeout=0)" ,
2240+ {
2241+ scan_action_arg_desc,
2242+ scan_objects_arg_desc,
2243+ RPCArg{" start_height" , RPCArg::Type::NUM, RPCArg::Default{0 }, " Height to start to scan from" },
2244+ RPCArg{" stop_height" , RPCArg::Type::NUM, RPCArg::DefaultHint{" chain tip" }, " Height to stop to scan" },
2245+ RPCArg{" filtertype" , RPCArg::Type::STR, RPCArg::Default{BlockFilterTypeName (BlockFilterType::BASIC)}, " The type name of the filter" }
2246+ },
2247+ {
2248+ scan_result_status_none,
2249+ RPCResult{" When action=='start'" , RPCResult::Type::OBJ, " " , " " , {
2250+ {RPCResult::Type::NUM, " from_height" , " The height we started the scan from" },
2251+ {RPCResult::Type::NUM, " to_height" , " The height we ended the scan at" },
2252+ {RPCResult::Type::ARR, " relevant_blocks" , " " , {{RPCResult::Type::STR_HEX, " blockhash" , " A relevant blockhash" },}},
2253+ },
2254+ },
2255+ RPCResult{" when action=='status' and a scan is currently in progress" , RPCResult::Type::OBJ, " " , " " , {
2256+ {RPCResult::Type::NUM, " progress" , " Approximate percent complete" },
2257+ {RPCResult::Type::NUM, " current_height" , " Height of the block currently being scanned" },
2258+ },
2259+ },
2260+ scan_result_abort,
2261+ },
2262+ RPCExamples{
2263+ HelpExampleCli (" scanblocks" , " start '[\" addr(bcrt1q4u4nsgk6ug0sqz7r3rj9tykjxrsl0yy4d0wwte)\" ]' 300000" ) +
2264+ HelpExampleCli (" scanblocks" , " start '[\" addr(bcrt1q4u4nsgk6ug0sqz7r3rj9tykjxrsl0yy4d0wwte)\" ]' 100 150 basic" ) +
2265+ HelpExampleCli (" scanblocks" , " status" ) +
2266+ HelpExampleRpc (" scanblocks" , " \" start\" , [\" addr(bcrt1q4u4nsgk6ug0sqz7r3rj9tykjxrsl0yy4d0wwte)\" ], 300000" ) +
2267+ HelpExampleRpc (" scanblocks" , " \" start\" , [\" addr(bcrt1q4u4nsgk6ug0sqz7r3rj9tykjxrsl0yy4d0wwte)\" ], 100, 150, \" basic\" " ) +
2268+ HelpExampleRpc (" scanblocks" , " \" status\" " )
2269+ },
2270+ [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
2271+ {
2272+ UniValue ret (UniValue::VOBJ);
2273+ if (request.params [0 ].get_str () == " status" ) {
2274+ BlockFiltersScanReserver reserver;
2275+ if (reserver.reserve ()) {
2276+ // no scan in progress
2277+ return NullUniValue;
2278+ }
2279+ ret.pushKV (" progress" , g_scanfilter_progress.load ());
2280+ ret.pushKV (" current_height" , g_scanfilter_progress_height.load ());
2281+ return ret;
2282+ } else if (request.params [0 ].get_str () == " abort" ) {
2283+ BlockFiltersScanReserver reserver;
2284+ if (reserver.reserve ()) {
2285+ // reserve was possible which means no scan was running
2286+ return false ;
2287+ }
2288+ // set the abort flag
2289+ g_scanfilter_should_abort_scan = true ;
2290+ return true ;
2291+ }
2292+ else if (request.params [0 ].get_str () == " start" ) {
2293+ BlockFiltersScanReserver reserver;
2294+ if (!reserver.reserve ()) {
2295+ throw JSONRPCError (RPC_INVALID_PARAMETER, " Scan already in progress, use action \" abort\" or \" status\" " );
2296+ }
2297+ const std::string filtertype_name{request.params [4 ].isNull () ? " basic" : request.params [4 ].get_str ()};
2298+
2299+ BlockFilterType filtertype;
2300+ if (!BlockFilterTypeByName (filtertype_name, filtertype)) {
2301+ throw JSONRPCError (RPC_INVALID_ADDRESS_OR_KEY, " Unknown filtertype" );
2302+ }
2303+
2304+ BlockFilterIndex* index = GetBlockFilterIndex (filtertype);
2305+ if (!index) {
2306+ throw JSONRPCError (RPC_MISC_ERROR, " Index is not enabled for filtertype " + filtertype_name);
2307+ }
2308+
2309+ NodeContext& node = EnsureAnyNodeContext (request.context );
2310+ ChainstateManager& chainman = EnsureChainman (node);
2311+
2312+ // set the start-height
2313+ const CBlockIndex* block = nullptr ;
2314+ const CBlockIndex* stop_block = nullptr ;
2315+ {
2316+ LOCK (cs_main);
2317+ CChain& active_chain = chainman.ActiveChain ();
2318+ block = active_chain.Genesis ();
2319+ stop_block = active_chain.Tip ();
2320+ if (!request.params [2 ].isNull ()) {
2321+ block = active_chain[request.params [2 ].getInt <int >()];
2322+ if (!block) {
2323+ throw JSONRPCError (RPC_MISC_ERROR, " Invalid start_height" );
2324+ }
2325+ }
2326+ if (!request.params [3 ].isNull ()) {
2327+ stop_block = active_chain[request.params [3 ].getInt <int >()];
2328+ if (!stop_block || stop_block->nHeight < block->nHeight ) {
2329+ throw JSONRPCError (RPC_MISC_ERROR, " Invalid stop_height" );
2330+ }
2331+ }
2332+ }
2333+ CHECK_NONFATAL (block);
2334+
2335+ // loop through the scan objects, add scripts to the needle_set
2336+ GCSFilter::ElementSet needle_set;
2337+ for (const UniValue& scanobject : request.params [1 ].get_array ().getValues ()) {
2338+ FlatSigningProvider provider;
2339+ std::vector<CScript> scripts = EvalDescriptorStringOrObject (scanobject, provider);
2340+ for (const CScript& script : scripts) {
2341+ needle_set.emplace (script.begin (), script.end ());
2342+ }
2343+ }
2344+ UniValue blocks (UniValue::VARR);
2345+ const int amount_per_chunk = 10000 ;
2346+ const CBlockIndex* start_index = block; // for remembering the start of a blockfilter range
2347+ std::vector<BlockFilter> filters;
2348+ const CBlockIndex* start_block = block; // for progress reporting
2349+ const int total_blocks_to_process = stop_block->nHeight - start_block->nHeight ;
2350+
2351+ g_scanfilter_should_abort_scan = false ;
2352+ g_scanfilter_progress = 0 ;
2353+ g_scanfilter_progress_height = start_block->nHeight ;
2354+
2355+ while (block) {
2356+ node.rpc_interruption_point (); // allow a clean shutdown
2357+ if (g_scanfilter_should_abort_scan) {
2358+ LogPrintf (" scanblocks RPC aborted at height %d.\n " , block->nHeight );
2359+ break ;
2360+ }
2361+ const CBlockIndex* next = nullptr ;
2362+ {
2363+ LOCK (cs_main);
2364+ CChain& active_chain = chainman.ActiveChain ();
2365+ next = active_chain.Next (block);
2366+ if (block == stop_block) next = nullptr ;
2367+ }
2368+ if (start_index->nHeight + amount_per_chunk == block->nHeight || next == nullptr ) {
2369+ LogPrint (BCLog::RPC, " Fetching blockfilters from height %d to height %d.\n " , start_index->nHeight , block->nHeight );
2370+ if (index->LookupFilterRange (start_index->nHeight , block, filters)) {
2371+ for (const BlockFilter& filter : filters) {
2372+ // compare the elements-set with each filter
2373+ if (filter.GetFilter ().MatchAny (needle_set)) {
2374+ blocks.push_back (filter.GetBlockHash ().GetHex ());
2375+ LogPrint (BCLog::RPC, " scanblocks: found match in %s\n " , filter.GetBlockHash ().GetHex ());
2376+ }
2377+ }
2378+ }
2379+ start_index = block;
2380+
2381+ // update progress
2382+ int blocks_processed = block->nHeight - start_block->nHeight ;
2383+ if (total_blocks_to_process > 0 ) { // avoid division by zero
2384+ g_scanfilter_progress = (int )(100.0 / total_blocks_to_process * blocks_processed);
2385+ } else {
2386+ g_scanfilter_progress = 100 ;
2387+ }
2388+ g_scanfilter_progress_height = block->nHeight ;
2389+ }
2390+ block = next;
2391+ }
2392+ ret.pushKV (" from_height" , start_block->nHeight );
2393+ ret.pushKV (" to_height" , g_scanfilter_progress_height.load ());
2394+ ret.pushKV (" relevant_blocks" , blocks);
2395+ }
2396+ else {
2397+ throw JSONRPCError (RPC_INVALID_PARAMETER, " Invalid command" );
2398+ }
2399+ return ret;
2400+ },
2401+ };
2402+ }
2403+
22072404static RPCHelpMan getblockfilter ()
22082405{
22092406 return RPCHelpMan{" getblockfilter" ,
@@ -2439,6 +2636,7 @@ void RegisterBlockchainRPCCommands(CRPCTable& t)
24392636 {" blockchain" , &verifychain},
24402637 {" blockchain" , &preciousblock},
24412638 {" blockchain" , &scantxoutset},
2639+ {" blockchain" , &scanblocks},
24422640 {" blockchain" , &getblockfilter},
24432641 {" hidden" , &invalidateblock},
24442642 {" hidden" , &reconsiderblock},
0 commit comments