perf(explorer): fetch each block with one RPC and back off on failure
Each recent block was fetched with two round trips (getblockhash then getblock), and a height that failed, such as one briefly above the tip during a reorg, was refetched on every frame. - getblock is now called once with the height directly, falling back to the getblockhash plus getblock pair only when an older daemon answers "not found" for the short string; any other error is a real answer. - A failed fetch backs off with a doubling wait (2 s up to 32 s) before any new fetch is scheduled, and a successful fetch resets the backoff. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -30,6 +30,7 @@
|
||||
#include <atomic>
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <chrono>
|
||||
|
||||
namespace dragonx {
|
||||
namespace ui {
|
||||
@@ -73,6 +74,10 @@ static std::string s_fuzzy_query_cached;
|
||||
static std::vector<BlockSummary> s_fuzzy_results;
|
||||
static constexpr int kMaxFuzzyResults = 100;
|
||||
static std::atomic<int> s_pending_block_fetches{0};
|
||||
// Backoff for failing recent-block fetches (main thread only): consecutive failed fetches double the
|
||||
// wait (2s..60s) before any new fetch is scheduled, so a failing height is not refetched every frame.
|
||||
static int s_block_fetch_failures = 0;
|
||||
static std::chrono::steady_clock::time_point s_block_fetch_retry_after{};
|
||||
static bool s_recent_disk_cache_validation_pending = false;
|
||||
|
||||
// Block detail modal
|
||||
@@ -277,6 +282,7 @@ static bool fetchRecentBlock(App* app, int height) {
|
||||
auto* rpc = app->rpc();
|
||||
if (height < 1 || !worker || !rpc) return false;
|
||||
if (s_pending_block_heights.find(height) != s_pending_block_heights.end()) return false;
|
||||
if (std::chrono::steady_clock::now() < s_block_fetch_retry_after) return false;
|
||||
|
||||
s_pending_block_heights.insert(height);
|
||||
s_pending_block_fetches.fetch_add(1);
|
||||
@@ -285,9 +291,17 @@ static bool fetchRecentBlock(App* app, int height) {
|
||||
BlockSummary block;
|
||||
try {
|
||||
rpc::RPCClient::TraceScope trace("Explorer tab / Recent blocks");
|
||||
auto hashResult = rpc->call("getblockhash", {height});
|
||||
auto hash = hashResult.get<std::string>();
|
||||
auto result = rpc->call("getblock", {hash});
|
||||
json result;
|
||||
try {
|
||||
// One round trip: getblock accepts a height string directly.
|
||||
result = rpc->call("getblock", {std::to_string(height)});
|
||||
} catch (const rpc::RpcError& e) {
|
||||
// Older daemons treat a short string as a hash and answer -5 (not found); fall back to
|
||||
// getblockhash + getblock. Any other error (e.g. -8 out of range) is a real answer.
|
||||
if (e.code != -5) throw;
|
||||
auto hash = rpc->call("getblockhash", {height}).get<std::string>();
|
||||
result = rpc->call("getblock", {hash});
|
||||
}
|
||||
block.height = result.value("height", 0);
|
||||
block.hash = result.value("hash", "");
|
||||
block.time = result.value("time", (int64_t)0);
|
||||
@@ -300,6 +314,12 @@ static bool fetchRecentBlock(App* app, int height) {
|
||||
return [height, block = std::move(block)]() mutable {
|
||||
if (block.height > 0) {
|
||||
s_recent_block_cache.storeBlock(block);
|
||||
s_block_fetch_failures = 0;
|
||||
} else {
|
||||
// A height briefly above the daemon tip (reorg) lands here too; it self-heals after the wait.
|
||||
s_block_fetch_failures = std::min(s_block_fetch_failures + 1, 5);
|
||||
s_block_fetch_retry_after = std::chrono::steady_clock::now() +
|
||||
std::chrono::seconds(std::min(60, 1 << s_block_fetch_failures));
|
||||
}
|
||||
s_pending_block_heights.erase(height);
|
||||
if (s_pending_block_fetches.load() > 0) {
|
||||
|
||||
Reference in New Issue
Block a user