Skip to content

Commit

Permalink
rpc: faster scanblocks by not traversing the whole chain block by block
Browse files Browse the repository at this point in the history
The current flow walks-through every block in the active chain until
hits the chain tip or processes 10k blocks, then calls the
`lookupFilterRange` function to obtain all the filters from that
particular range.

That isn't the best as it traverses the whole chain only to obtain
the heights range to lookup the block filters.

As `scanblocks` only lookup block filters in the active chain, we can
directly calculate the lookup range heights, by using the chain tip,
without requiring to traverse the chain block by block.
  • Loading branch information
furszy committed Sep 15, 2022
1 parent d19cf65 commit 279beb7
Showing 1 changed file with 43 additions and 41 deletions.
84 changes: 43 additions & 41 deletions src/rpc/blockchain.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2299,27 +2299,30 @@ static RPCHelpMan scanblocks()
ChainstateManager& chainman = EnsureChainman(node);

// set the start-height
const CBlockIndex* block = nullptr;
const CBlockIndex* start_index = nullptr;
const CBlockIndex* stop_block = nullptr;
{
LOCK(cs_main);
CChain& active_chain = chainman.ActiveChain();
block = active_chain.Genesis();
start_index = active_chain.Genesis();
stop_block = active_chain.Tip();
if (!request.params[2].isNull()) {
block = active_chain[request.params[2].getInt<int>()];
if (!block) {
start_index = active_chain[request.params[2].getInt<int>()];
if (!start_index) {
throw JSONRPCError(RPC_MISC_ERROR, "Invalid start_height");
}
}
if (!request.params[3].isNull()) {
stop_block = active_chain[request.params[3].getInt<int>()];
if (!stop_block || stop_block->nHeight < block->nHeight) {
if (!stop_block || !start_index || stop_block->nHeight < start_index->nHeight) {
throw JSONRPCError(RPC_MISC_ERROR, "Invalid stop_height");
}
} else {
// No stop block provided, stop at the chain tip.
stop_block = active_chain.Tip();
}
}
CHECK_NONFATAL(block);
CHECK_NONFATAL(start_index);

// loop through the scan objects, add scripts to the needle_set
GCSFilter::ElementSet needle_set;
Expand All @@ -2332,54 +2335,53 @@ static RPCHelpMan scanblocks()
}
UniValue blocks(UniValue::VARR);
const int amount_per_chunk = 10000;
const CBlockIndex* start_index = block; // for remembering the start of a blockfilter range
std::vector<BlockFilter> filters;
const CBlockIndex* start_block = block; // for progress reporting
const int total_blocks_to_process = stop_block->nHeight - start_block->nHeight;
int start_block_height = start_index->nHeight; // for progress reporting
const int total_blocks_to_process = stop_block->nHeight - start_block_height;

g_scanfilter_should_abort_scan = false;
g_scanfilter_progress = 0;
g_scanfilter_progress_height = start_block->nHeight;
g_scanfilter_progress_height = start_block_height;

while (block) {
const CBlockIndex* end_range = start_index;
while (true) {
node.rpc_interruption_point(); // allow a clean shutdown
if (g_scanfilter_should_abort_scan) {
LogPrintf("scanblocks RPC aborted at height %d.\n", block->nHeight);
LogPrintf("scanblocks RPC aborted at height %d.\n", end_range->nHeight);
break;
}
const CBlockIndex* next = nullptr;
{
LOCK(cs_main);
CChain& active_chain = chainman.ActiveChain();
next = active_chain.Next(block);
if (block == stop_block) next = nullptr;
}
if (start_index->nHeight + amount_per_chunk == block->nHeight || next == nullptr) {
LogPrint(BCLog::RPC, "Fetching blockfilters from height %d to height %d.\n", start_index->nHeight, block->nHeight);
if (index->LookupFilterRange(start_index->nHeight, block, filters)) {
for (const BlockFilter& filter : filters) {
// compare the elements-set with each filter
if (filter.GetFilter().MatchAny(needle_set)) {
blocks.push_back(filter.GetBlockHash().GetHex());
LogPrint(BCLog::RPC, "scanblocks: found match in %s\n", filter.GetBlockHash().GetHex());
}

// break the lookup range in chunks if we are deeper than 'amount_per_chunk' blocks from the stopping block
end_range = (start_index->nHeight + amount_per_chunk < stop_block->nHeight) ?
WITH_LOCK(::cs_main, return chainman.ActiveChain()[start_index->nHeight + amount_per_chunk]) :
stop_block;

LogPrint(BCLog::RPC, "Fetching blockfilters from height %d to height %d.\n", start_index->nHeight, end_range->nHeight);
if (index->LookupFilterRange(start_index->nHeight, end_range, filters)) {
for (const BlockFilter& filter : filters) {
// compare the elements-set with each filter
if (filter.GetFilter().MatchAny(needle_set)) {
blocks.push_back(filter.GetBlockHash().GetHex());
LogPrint(BCLog::RPC, "scanblocks: found match in %s\n", filter.GetBlockHash().GetHex());
}
}
start_index = block;

// update progress
int blocks_processed = block->nHeight - start_block->nHeight;
if (total_blocks_to_process > 0) { // avoid division by zero
g_scanfilter_progress = (int)(100.0 / total_blocks_to_process * blocks_processed);
} else {
g_scanfilter_progress = 100;
}
g_scanfilter_progress_height = block->nHeight;
}
block = next;
start_index = end_range;

// update progress
int blocks_processed = end_range->nHeight - start_block_height;
if (total_blocks_to_process > 0) { // avoid division by zero
g_scanfilter_progress = (int)(100.0 / total_blocks_to_process * blocks_processed);
} else {
g_scanfilter_progress = 100;
}
g_scanfilter_progress_height = end_range->nHeight;

// Finish if we reached the stop block
if (start_index == stop_block) break;
}
ret.pushKV("from_height", start_block->nHeight);
ret.pushKV("to_height", g_scanfilter_progress_height.load());
ret.pushKV("from_height", start_block_height);
ret.pushKV("to_height", stop_block->nHeight);
ret.pushKV("relevant_blocks", blocks);
}
else {
Expand Down

0 comments on commit 279beb7

Please sign in to comment.