@@ -2019,6 +2019,40 @@ class CoinsViewScanReserver
2019
2019
}
2020
2020
};
2021
2021
2022
+ static const auto scan_action_arg_desc = RPCArg{
2023
+ " action" , RPCArg::Type::STR, RPCArg::Optional::NO, " The action to execute\n "
2024
+ " \" start\" for starting a scan\n "
2025
+ " \" abort\" for aborting the current scan (returns true when abort was successful)\n "
2026
+ " \" status\" for progress report (in %) of the current scan"
2027
+ };
2028
+
2029
+ static const auto scan_objects_arg_desc = RPCArg{
2030
+ " scanobjects" , RPCArg::Type::ARR, RPCArg::Optional::OMITTED, " Array of scan objects. Required for \" start\" action\n "
2031
+ " Every scan object is either a string descriptor or an object:" ,
2032
+ {
2033
+ {" descriptor" , RPCArg::Type::STR, RPCArg::Optional::OMITTED, " An output descriptor" },
2034
+ {" " , RPCArg::Type::OBJ, RPCArg::Optional::OMITTED, " An object with output descriptor and metadata" ,
2035
+ {
2036
+ {" desc" , RPCArg::Type::STR, RPCArg::Optional::NO, " An output descriptor" },
2037
+ {" range" , RPCArg::Type::RANGE, RPCArg::Default{1000 }, " The range of HD chain indexes to explore (either end or [begin,end])" },
2038
+ }},
2039
+ },
2040
+ RPCArgOptions{.oneline_description =" [scanobjects,...]" },
2041
+ };
2042
+
2043
+ static const auto scan_result_abort = RPCResult{
2044
+ " when action=='abort'" , RPCResult::Type::BOOL, " success" ,
2045
+ " True if scan will be aborted (not necessarily before this RPC returns), or false if there is no scan to abort"
2046
+ };
2047
+ static const auto scan_result_status_none = RPCResult{
2048
+ " when action=='status' and no scan is in progress - possibly already completed" , RPCResult::Type::NONE, " " , " "
2049
+ };
2050
+ static const auto scan_result_status_some = RPCResult{
2051
+ " when action=='status' and a scan is currently in progress" , RPCResult::Type::OBJ, " " , " " ,
2052
+ {{RPCResult::Type::NUM, " progress" , " Approximate percent complete" },}
2053
+ };
2054
+
2055
+
2022
2056
static RPCHelpMan scantxoutset ()
2023
2057
{
2024
2058
// scriptPubKey corresponding to mainnet address 12cbQLTFMXRnSzktFkuoG3eHoMeFtpTu3S
@@ -2038,21 +2072,8 @@ static RPCHelpMan scantxoutset()
2038
2072
" In the latter case, a range needs to be specified by below if different from 1000.\n "
2039
2073
" For more information on output descriptors, see the documentation in the doc/descriptors.md file.\n " ,
2040
2074
{
2041
- {" action" , RPCArg::Type::STR, RPCArg::Optional::NO, " The action to execute\n "
2042
- " \" start\" for starting a scan\n "
2043
- " \" abort\" for aborting the current scan (returns true when abort was successful)\n "
2044
- " \" status\" for progress report (in %) of the current scan" },
2045
- {" scanobjects" , RPCArg::Type::ARR, RPCArg::Optional::OMITTED, " Array of scan objects. Required for \" start\" action\n "
2046
- " Every scan object is either a string descriptor or an object:" ,
2047
- {
2048
- {" descriptor" , RPCArg::Type::STR, RPCArg::Optional::OMITTED, " An output descriptor" },
2049
- {" " , RPCArg::Type::OBJ, RPCArg::Optional::OMITTED, " An object with output descriptor and metadata" ,
2050
- {
2051
- {" desc" , RPCArg::Type::STR, RPCArg::Optional::NO, " An output descriptor" },
2052
- {" range" , RPCArg::Type::RANGE, RPCArg::Default{1000 }, " The range of HD chain indexes to explore (either end or [begin,end])" },
2053
- }},
2054
- },
2055
- RPCArgOptions{.oneline_description =" [scanobjects,...]" }},
2075
+ scan_action_arg_desc,
2076
+ scan_objects_arg_desc,
2056
2077
},
2057
2078
{
2058
2079
RPCResult{" when action=='start'; only returns after scan completes" , RPCResult::Type::OBJ, " " , " " , {
@@ -2075,12 +2096,9 @@ static RPCHelpMan scantxoutset()
2075
2096
}},
2076
2097
{RPCResult::Type::STR_AMOUNT, " total_amount" , " The total amount of all found unspent outputs in " + CURRENCY_UNIT},
2077
2098
}},
2078
- RPCResult{" when action=='abort'" , RPCResult::Type::BOOL, " success" , " True if scan will be aborted (not necessarily before this RPC returns), or false if there is no scan to abort" },
2079
- RPCResult{" when action=='status' and a scan is currently in progress" , RPCResult::Type::OBJ, " " , " " ,
2080
- {
2081
- {RPCResult::Type::NUM, " progress" , " Approximate percent complete" },
2082
- }},
2083
- RPCResult{" when action=='status' and no scan is in progress - possibly already completed" , RPCResult::Type::NONE, " " , " " },
2099
+ scan_result_abort,
2100
+ scan_result_status_some,
2101
+ scan_result_status_none,
2084
2102
},
2085
2103
RPCExamples{
2086
2104
HelpExampleCli (" scantxoutset" , " start \' [\" " + EXAMPLE_DESCRIPTOR_RAW + " \" ]\' " ) +
@@ -2188,6 +2206,203 @@ static RPCHelpMan scantxoutset()
2188
2206
};
2189
2207
}
2190
2208
2209
+ /* * RAII object to prevent concurrency issue when scanning blockfilters */
2210
+ static std::atomic<int > g_scanfilter_progress;
2211
+ static std::atomic<int > g_scanfilter_progress_height;
2212
+ static std::atomic<bool > g_scanfilter_in_progress;
2213
+ static std::atomic<bool > g_scanfilter_should_abort_scan;
2214
+ class BlockFiltersScanReserver
2215
+ {
2216
+ private:
2217
+ bool m_could_reserve{false };
2218
+ public:
2219
+ explicit BlockFiltersScanReserver () = default;
2220
+
2221
+ bool reserve () {
2222
+ CHECK_NONFATAL (!m_could_reserve);
2223
+ if (g_scanfilter_in_progress.exchange (true )) {
2224
+ return false ;
2225
+ }
2226
+ m_could_reserve = true ;
2227
+ return true ;
2228
+ }
2229
+
2230
+ ~BlockFiltersScanReserver () {
2231
+ if (m_could_reserve) {
2232
+ g_scanfilter_in_progress = false ;
2233
+ }
2234
+ }
2235
+ };
2236
+
2237
+ static RPCHelpMan scanblocks ()
2238
+ {
2239
+ return RPCHelpMan{" scanblocks" ,
2240
+ " \n Return relevant blockhashes for given descriptors.\n "
2241
+ " This call may take several minutes. Make sure to use no RPC timeout (bitcoin-cli -rpcclienttimeout=0)" ,
2242
+ {
2243
+ scan_action_arg_desc,
2244
+ scan_objects_arg_desc,
2245
+ RPCArg{" start_height" , RPCArg::Type::NUM, RPCArg::Default{0 }, " Height to start to scan from" },
2246
+ RPCArg{" stop_height" , RPCArg::Type::NUM, RPCArg::DefaultHint{" chain tip" }, " Height to stop to scan" },
2247
+ RPCArg{" filtertype" , RPCArg::Type::STR, RPCArg::Default{BlockFilterTypeName (BlockFilterType::BASIC)}, " The type name of the filter" }
2248
+ },
2249
+ {
2250
+ scan_result_status_none,
2251
+ RPCResult{" When action=='start'" , RPCResult::Type::OBJ, " " , " " , {
2252
+ {RPCResult::Type::NUM, " from_height" , " The height we started the scan from" },
2253
+ {RPCResult::Type::NUM, " to_height" , " The height we ended the scan at" },
2254
+ {RPCResult::Type::ARR, " relevant_blocks" , " " , {{RPCResult::Type::STR_HEX, " blockhash" , " A relevant blockhash" },}},
2255
+ },
2256
+ },
2257
+ RPCResult{" when action=='status' and a scan is currently in progress" , RPCResult::Type::OBJ, " " , " " , {
2258
+ {RPCResult::Type::NUM, " progress" , " Approximate percent complete" },
2259
+ {RPCResult::Type::NUM, " current_height" , " Height of the block currently being scanned" },
2260
+ },
2261
+ },
2262
+ scan_result_abort,
2263
+ },
2264
+ RPCExamples{
2265
+ HelpExampleCli (" scanblocks" , " start '[\" addr(bcrt1q4u4nsgk6ug0sqz7r3rj9tykjxrsl0yy4d0wwte)\" ]' 300000" ) +
2266
+ HelpExampleCli (" scanblocks" , " start '[\" addr(bcrt1q4u4nsgk6ug0sqz7r3rj9tykjxrsl0yy4d0wwte)\" ]' 100 150 basic" ) +
2267
+ HelpExampleCli (" scanblocks" , " status" ) +
2268
+ HelpExampleRpc (" scanblocks" , " \" start\" , [\" addr(bcrt1q4u4nsgk6ug0sqz7r3rj9tykjxrsl0yy4d0wwte)\" ], 300000" ) +
2269
+ HelpExampleRpc (" scanblocks" , " \" start\" , [\" addr(bcrt1q4u4nsgk6ug0sqz7r3rj9tykjxrsl0yy4d0wwte)\" ], 100, 150, \" basic\" " ) +
2270
+ HelpExampleRpc (" scanblocks" , " \" status\" " )
2271
+ },
2272
+ [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
2273
+ {
2274
+ UniValue ret (UniValue::VOBJ);
2275
+ if (request.params [0 ].get_str () == " status" ) {
2276
+ BlockFiltersScanReserver reserver;
2277
+ if (reserver.reserve ()) {
2278
+ // no scan in progress
2279
+ return NullUniValue;
2280
+ }
2281
+ ret.pushKV (" progress" , g_scanfilter_progress.load ());
2282
+ ret.pushKV (" current_height" , g_scanfilter_progress_height.load ());
2283
+ return ret;
2284
+ } else if (request.params [0 ].get_str () == " abort" ) {
2285
+ BlockFiltersScanReserver reserver;
2286
+ if (reserver.reserve ()) {
2287
+ // reserve was possible which means no scan was running
2288
+ return false ;
2289
+ }
2290
+ // set the abort flag
2291
+ g_scanfilter_should_abort_scan = true ;
2292
+ return true ;
2293
+ }
2294
+ else if (request.params [0 ].get_str () == " start" ) {
2295
+ BlockFiltersScanReserver reserver;
2296
+ if (!reserver.reserve ()) {
2297
+ throw JSONRPCError (RPC_INVALID_PARAMETER, " Scan already in progress, use action \" abort\" or \" status\" " );
2298
+ }
2299
+ const std::string filtertype_name{request.params [4 ].isNull () ? " basic" : request.params [4 ].get_str ()};
2300
+
2301
+ BlockFilterType filtertype;
2302
+ if (!BlockFilterTypeByName (filtertype_name, filtertype)) {
2303
+ throw JSONRPCError (RPC_INVALID_ADDRESS_OR_KEY, " Unknown filtertype" );
2304
+ }
2305
+
2306
+ BlockFilterIndex* index = GetBlockFilterIndex (filtertype);
2307
+ if (!index) {
2308
+ throw JSONRPCError (RPC_MISC_ERROR, " Index is not enabled for filtertype " + filtertype_name);
2309
+ }
2310
+
2311
+ NodeContext& node = EnsureAnyNodeContext (request.context );
2312
+ ChainstateManager& chainman = EnsureChainman (node);
2313
+
2314
+ // set the start-height
2315
+ const CBlockIndex* block = nullptr ;
2316
+ const CBlockIndex* stop_block = nullptr ;
2317
+ {
2318
+ LOCK (cs_main);
2319
+ CChain& active_chain = chainman.ActiveChain ();
2320
+ block = active_chain.Genesis ();
2321
+ stop_block = active_chain.Tip ();
2322
+ if (!request.params [2 ].isNull ()) {
2323
+ block = active_chain[request.params [2 ].getInt <int >()];
2324
+ if (!block) {
2325
+ throw JSONRPCError (RPC_MISC_ERROR, " Invalid start_height" );
2326
+ }
2327
+ }
2328
+ if (!request.params [3 ].isNull ()) {
2329
+ stop_block = active_chain[request.params [3 ].getInt <int >()];
2330
+ if (!stop_block || stop_block->nHeight < block->nHeight ) {
2331
+ throw JSONRPCError (RPC_MISC_ERROR, " Invalid stop_height" );
2332
+ }
2333
+ }
2334
+ }
2335
+ CHECK_NONFATAL (block);
2336
+
2337
+ // loop through the scan objects, add scripts to the needle_set
2338
+ GCSFilter::ElementSet needle_set;
2339
+ for (const UniValue& scanobject : request.params [1 ].get_array ().getValues ()) {
2340
+ FlatSigningProvider provider;
2341
+ std::vector<CScript> scripts = EvalDescriptorStringOrObject (scanobject, provider);
2342
+ for (const CScript& script : scripts) {
2343
+ needle_set.emplace (script.begin (), script.end ());
2344
+ }
2345
+ }
2346
+ UniValue blocks (UniValue::VARR);
2347
+ const int amount_per_chunk = 10000 ;
2348
+ const CBlockIndex* start_index = block; // for remembering the start of a blockfilter range
2349
+ std::vector<BlockFilter> filters;
2350
+ const CBlockIndex* start_block = block; // for progress reporting
2351
+ const int total_blocks_to_process = stop_block->nHeight - start_block->nHeight ;
2352
+
2353
+ g_scanfilter_should_abort_scan = false ;
2354
+ g_scanfilter_progress = 0 ;
2355
+ g_scanfilter_progress_height = start_block->nHeight ;
2356
+
2357
+ while (block) {
2358
+ node.rpc_interruption_point (); // allow a clean shutdown
2359
+ if (g_scanfilter_should_abort_scan) {
2360
+ LogPrintf (" scanblocks RPC aborted at height %d.\n " , block->nHeight );
2361
+ break ;
2362
+ }
2363
+ const CBlockIndex* next = nullptr ;
2364
+ {
2365
+ LOCK (cs_main);
2366
+ CChain& active_chain = chainman.ActiveChain ();
2367
+ next = active_chain.Next (block);
2368
+ if (block == stop_block) next = nullptr ;
2369
+ }
2370
+ if (start_index->nHeight + amount_per_chunk == block->nHeight || next == nullptr ) {
2371
+ LogPrint (BCLog::RPC, " Fetching blockfilters from height %d to height %d.\n " , start_index->nHeight , block->nHeight );
2372
+ if (index->LookupFilterRange (start_index->nHeight , block, filters)) {
2373
+ for (const BlockFilter& filter : filters) {
2374
+ // compare the elements-set with each filter
2375
+ if (filter.GetFilter ().MatchAny (needle_set)) {
2376
+ blocks.push_back (filter.GetBlockHash ().GetHex ());
2377
+ LogPrint (BCLog::RPC, " scanblocks: found match in %s\n " , filter.GetBlockHash ().GetHex ());
2378
+ }
2379
+ }
2380
+ }
2381
+ start_index = block;
2382
+
2383
+ // update progress
2384
+ int blocks_processed = block->nHeight - start_block->nHeight ;
2385
+ if (total_blocks_to_process > 0 ) { // avoid division by zero
2386
+ g_scanfilter_progress = (int )(100.0 / total_blocks_to_process * blocks_processed);
2387
+ } else {
2388
+ g_scanfilter_progress = 100 ;
2389
+ }
2390
+ g_scanfilter_progress_height = block->nHeight ;
2391
+ }
2392
+ block = next;
2393
+ }
2394
+ ret.pushKV (" from_height" , start_block->nHeight );
2395
+ ret.pushKV (" to_height" , g_scanfilter_progress_height.load ());
2396
+ ret.pushKV (" relevant_blocks" , blocks);
2397
+ }
2398
+ else {
2399
+ throw JSONRPCError (RPC_INVALID_PARAMETER, " Invalid command" );
2400
+ }
2401
+ return ret;
2402
+ },
2403
+ };
2404
+ }
2405
+
2191
2406
static RPCHelpMan getblockfilter ()
2192
2407
{
2193
2408
return RPCHelpMan{" getblockfilter" ,
@@ -2423,6 +2638,7 @@ void RegisterBlockchainRPCCommands(CRPCTable& t)
2423
2638
{" blockchain" , &verifychain},
2424
2639
{" blockchain" , &preciousblock},
2425
2640
{" blockchain" , &scantxoutset},
2641
+ {" blockchain" , &scanblocks},
2426
2642
{" blockchain" , &getblockfilter},
2427
2643
{" hidden" , &invalidateblock},
2428
2644
{" hidden" , &reconsiderblock},
0 commit comments