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 <atomic>
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <set>
|
#include <set>
|
||||||
|
#include <chrono>
|
||||||
|
|
||||||
namespace dragonx {
|
namespace dragonx {
|
||||||
namespace ui {
|
namespace ui {
|
||||||
@@ -73,6 +74,10 @@ static std::string s_fuzzy_query_cached;
|
|||||||
static std::vector<BlockSummary> s_fuzzy_results;
|
static std::vector<BlockSummary> s_fuzzy_results;
|
||||||
static constexpr int kMaxFuzzyResults = 100;
|
static constexpr int kMaxFuzzyResults = 100;
|
||||||
static std::atomic<int> s_pending_block_fetches{0};
|
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;
|
static bool s_recent_disk_cache_validation_pending = false;
|
||||||
|
|
||||||
// Block detail modal
|
// Block detail modal
|
||||||
@@ -277,6 +282,7 @@ static bool fetchRecentBlock(App* app, int height) {
|
|||||||
auto* rpc = app->rpc();
|
auto* rpc = app->rpc();
|
||||||
if (height < 1 || !worker || !rpc) return false;
|
if (height < 1 || !worker || !rpc) return false;
|
||||||
if (s_pending_block_heights.find(height) != s_pending_block_heights.end()) 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_heights.insert(height);
|
||||||
s_pending_block_fetches.fetch_add(1);
|
s_pending_block_fetches.fetch_add(1);
|
||||||
@@ -285,9 +291,17 @@ static bool fetchRecentBlock(App* app, int height) {
|
|||||||
BlockSummary block;
|
BlockSummary block;
|
||||||
try {
|
try {
|
||||||
rpc::RPCClient::TraceScope trace("Explorer tab / Recent blocks");
|
rpc::RPCClient::TraceScope trace("Explorer tab / Recent blocks");
|
||||||
auto hashResult = rpc->call("getblockhash", {height});
|
json result;
|
||||||
auto hash = hashResult.get<std::string>();
|
try {
|
||||||
auto result = rpc->call("getblock", {hash});
|
// 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.height = result.value("height", 0);
|
||||||
block.hash = result.value("hash", "");
|
block.hash = result.value("hash", "");
|
||||||
block.time = result.value("time", (int64_t)0);
|
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 {
|
return [height, block = std::move(block)]() mutable {
|
||||||
if (block.height > 0) {
|
if (block.height > 0) {
|
||||||
s_recent_block_cache.storeBlock(block);
|
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);
|
s_pending_block_heights.erase(height);
|
||||||
if (s_pending_block_fetches.load() > 0) {
|
if (s_pending_block_fetches.load() > 0) {
|
||||||
|
|||||||
Reference in New Issue
Block a user