perf(node): throttle RPC polling during sync so block download isn't slowed

The full-node wallet polled the daemon at the per-tab cadence regardless of sync state.
On the Peers/Network tab that meant getpeerinfo every 5s + core every 5s + a full
transaction scan on every new block — and blocks arrive fast during sync. Each of those
calls takes the daemon's cs_main lock, the same lock block connection needs, so the node
synced noticeably slower than on the lightweight Console tab (core 10s, no peer polling).

Make the refresh cadence sync-aware:
- RefreshScheduler::kSyncProfile {core 10s, transactions/addresses/peers disabled} is applied
  to ALL tabs while state_.sync.syncing, and reverts to the per-tab profile when sync ends.
  applyRefreshPolicy() picks the profile; update() re-applies it on the syncing<->synced
  transition. This suppresses getpeerinfo and the per-block tx scan during sync (that data is
  incomplete mid-sync anyway) — every tab now syncs as fast as Console.
- collectCoreRefreshResult(rpc, includeBalance): skip z_gettotalbalance (wallet lock + cs_main)
  while syncing; only getblockchaininfo runs, which is also what drives sync-progress detection.
  applyCoreRefreshResult already leaves the balance untouched when balanceOk is false.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-10 15:06:05 -05:00
parent ee6cac41c4
commit 323cb341f1
6 changed files with 40 additions and 10 deletions

View File

@@ -569,6 +569,12 @@ void App::update()
// Wipe a secret (seed/private key) from the clipboard once its auto-clear delay elapses.
pumpSecretClipboardClear();
// Re-apply the refresh cadence when sync starts/finishes: while syncing we throttle polling to
// a low-impact profile so RPC contention doesn't slow block download (see applyRefreshPolicy).
if (state_.sync.syncing != refresh_policy_syncing_) {
applyRefreshPolicy(current_page_);
}
// Full-node RPC refreshes gate on ACTUAL RPC connectivity, not state_.connected. In lite
// builds state_.connected is the lite-wallet "online" proxy (true when a wallet is open, to
// enable the wallet UI), but there is no RPC daemon — so RPC polls (mining/balance/peers/txs)

View File

@@ -519,6 +519,7 @@ private:
int daemon_wait_attempts_ = 0;
bool daemon_start_error_shown_ = false;
int daemon_last_seen_crashes_ = 0; // surface each new embedded-daemon crash reason once
bool refresh_policy_syncing_ = false; // whether the sync-throttle refresh profile is active
// Auto-clear for secrets copied to the clipboard. Only a hash of the copied secret is kept.
std::uint64_t clipboard_secret_hash_ = 0;
double clipboard_clear_deadline_ = 0.0;

View File

@@ -664,7 +664,14 @@ App::RefreshIntervals App::getIntervalsForPage(ui::NavPage page)
void App::applyRefreshPolicy(ui::NavPage page)
{
network_refresh_.setIntervals(getIntervalsForPage(page));
// While the daemon is syncing, override the per-tab cadence with the low-impact sync profile so
// the wallet stops contending for the daemon's cs_main lock (frequent getpeerinfo / per-block
// transaction scans / balance polls slow block connection). This makes every tab sync as fast
// as the Console tab does today. Reverts to the per-tab profile once sync finishes.
refresh_policy_syncing_ = state_.sync.syncing;
network_refresh_.setIntervals(refresh_policy_syncing_
? services::RefreshScheduler::kSyncProfile
: getIntervalsForPage(page));
}
bool App::currentPageNeedsWalletDataRefresh() const
@@ -1159,10 +1166,13 @@ void App::refreshCoreData()
? fast_rpc_.get() : rpc_.get();
if (!w || !rpc) return;
ui::NavPage tracePage = current_page_;
// Skip the balance call while syncing (it's incomplete anyway and takes the wallet lock +
// cs_main). Captured on the main thread to avoid reading state_ off the worker thread.
const bool includeBalance = !state_.sync.syncing;
auto enqueued = network_refresh_.enqueue(services::NetworkRefreshService::Job::Core, *w, [this, rpc, tracePage]() -> rpc::RPCWorker::MainCb {
auto enqueued = network_refresh_.enqueue(services::NetworkRefreshService::Job::Core, *w, [this, rpc, tracePage, includeBalance]() -> rpc::RPCWorker::MainCb {
AppRefreshRpcGateway refreshRpc(*rpc, traceSource(tracePage, "Core refresh"));
auto result = NetworkRefreshService::collectCoreRefreshResult(refreshRpc);
auto result = NetworkRefreshService::collectCoreRefreshResult(refreshRpc, includeBalance);
return [this, result]() {
try {
NetworkRefreshService::applyCoreRefreshResult(state_, result, std::time(nullptr));

View File

@@ -284,18 +284,20 @@ NetworkRefreshService::CoreRefreshResult NetworkRefreshService::parseCoreRefresh
return result;
}
NetworkRefreshService::CoreRefreshResult NetworkRefreshService::collectCoreRefreshResult(RefreshRpcGateway& rpc)
NetworkRefreshService::CoreRefreshResult NetworkRefreshService::collectCoreRefreshResult(RefreshRpcGateway& rpc, bool includeBalance)
{
json totalBalance;
json blockInfo;
bool balanceOk = false;
bool blockOk = false;
try {
totalBalance = rpc.call("z_gettotalbalance", json::array());
balanceOk = true;
} catch (const std::exception& e) {
DEBUG_LOGF("Balance error: %s\n", e.what());
if (includeBalance) {
try {
totalBalance = rpc.call("z_gettotalbalance", json::array());
balanceOk = true;
} catch (const std::exception& e) {
DEBUG_LOGF("Balance error: %s\n", e.what());
}
}
try {

View File

@@ -230,7 +230,10 @@ public:
bool balanceOk,
const nlohmann::json& blockInfo,
bool blockOk);
static CoreRefreshResult collectCoreRefreshResult(RefreshRpcGateway& rpc);
// includeBalance=false skips z_gettotalbalance (which takes the wallet lock + cs_main) and only
// fetches getblockchaininfo — used while syncing, where the balance is incomplete anyway and the
// wallet should minimise lock contention with block connection.
static CoreRefreshResult collectCoreRefreshResult(RefreshRpcGateway& rpc, bool includeBalance = true);
static MiningRefreshResult parseMiningRefreshResult(const nlohmann::json& miningInfo,
bool miningOk,
const nlohmann::json& localHashrate,

View File

@@ -34,6 +34,14 @@ public:
static constexpr float kTxMaxAge = 15.0f;
static constexpr float kOpidPoll = 2.0f;
// Low-impact polling profile applied while the daemon is SYNCING, regardless of the active tab.
// Only a slow progress poll runs (core, 10s); transactions/addresses/peers are disabled (0).
// Frequent getpeerinfo, per-block transaction scans, and balance polls all contend for the
// daemon's cs_main lock and measurably slow block connection during sync — this is exactly why
// the lightweight Console tab syncs faster than the Peers tab. Reverts to the per-tab profile
// once sync completes. (Tx/address/balance data is incomplete mid-sync anyway.)
static constexpr Intervals kSyncProfile{10.0f, 0.0f, 0.0f, 0.0f};
static Intervals intervalsForPage(ui::NavPage page);
void applyPage(ui::NavPage page);