@@ -2204,6 +2204,203 @@ static RPCHelpMan scantxoutset()
2204
2204
};
2205
2205
}
2206
2206
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
+
2207
2404
static RPCHelpMan getblockfilter ()
2208
2405
{
2209
2406
return RPCHelpMan{" getblockfilter" ,
@@ -2439,6 +2636,7 @@ void RegisterBlockchainRPCCommands(CRPCTable& t)
2439
2636
{" blockchain" , &verifychain},
2440
2637
{" blockchain" , &preciousblock},
2441
2638
{" blockchain" , &scantxoutset},
2639
+ {" blockchain" , &scanblocks},
2442
2640
{" blockchain" , &getblockfilter},
2443
2641
{" hidden" , &invalidateblock},
2444
2642
{" hidden" , &reconsiderblock},
0 commit comments