Skip to content

Commit 6a69dd2

Browse files
committed
Add scanblocks RPC call - scan for relevant blocks with descriptors
1 parent 92b7efc commit 6a69dd2

File tree

2 files changed

+196
-0
lines changed

2 files changed

+196
-0
lines changed

src/rpc/blockchain.cpp

Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
"\nReturn 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+
22912483
static 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 */

src/rpc/client.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,9 @@ static const CRPCConvertParam vRPCConvertParams[] =
7777
{ "sendmany", 8, "fee_rate"},
7878
{ "sendmany", 9, "verbose" },
7979
{ "deriveaddresses", 1, "range" },
80+
{ "scanblocks", 1, "scanobjects" },
81+
{ "scanblocks", 2, "start_height" },
82+
{ "scanblocks", 3, "stop_height" },
8083
{ "scantxoutset", 1, "scanobjects" },
8184
{ "addmultisigaddress", 0, "nrequired" },
8285
{ "addmultisigaddress", 1, "keys" },

0 commit comments

Comments
 (0)