fix(sync): stop large-wallet balance polling from starving block connection
On a fully-shielded (ac_private=1) chain, z_gettotalbalance is O(mapWallet) and holds the daemon's cs_main for its whole duration — ~20s on a ~5k-tx wallet. The Overview refresh polled it every ~2s (twice: minconf 0 and 1), so cs_main was held almost continuously, starving the single block-connection thread: the node connected blocks only in the gaps between polls and could fall further behind the tip than it caught up (observed live: gap growing 58→100 blocks while the GUI was open, one core pegged on GetFilteredNotes, 22 idle, ~17 B/s download). Two hardening changes on top of the existing "skip balance while syncing" guard: - Hysteresis: keep the low-impact sync profile (and balance suppression) for a short settle window after catching up, so a large-wallet scan can't immediately re-starve connection and bounce the node back into syncing. Armed only on the syncing→caught-up edge, so a wallet synced from the start is never throttled at connect (effectivelySyncing()). - Adaptive balance cadence: time each z_gettotalbalance scan and require the next poll to wait at least (cost / 10%), so balance scanning never occupies more than ~10% of wall-clock. Cheap wallets are unaffected (the tab's Core timer stays the cadence); a ~20s scan backs off to ~200s. Wallet mutations (send/shield) force the next poll through so the user's own action updates the balance immediately (balanceRefreshDue()). getblockchaininfo keeps its normal cadence throughout, so sync progress stays live. Build + test_phase4 green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -3,6 +3,7 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <chrono>
|
||||
#include <cmath>
|
||||
#include <cstdlib>
|
||||
#include <map>
|
||||
@@ -294,8 +295,13 @@ NetworkRefreshService::CoreRefreshResult NetworkRefreshService::collectCoreRefre
|
||||
json blockInfo;
|
||||
bool balanceOk = false;
|
||||
bool blockOk = false;
|
||||
double balanceScanMs = 0.0;
|
||||
|
||||
if (includeBalance) {
|
||||
// z_gettotalbalance is O(mapWallet) and holds the daemon's cs_main for its whole duration —
|
||||
// seconds on a large shielded wallet. Time it so the caller can throttle how often it polls
|
||||
// (balanceRefreshDue()), keeping balance scans from starving block connection.
|
||||
const auto balanceStart = std::chrono::steady_clock::now();
|
||||
try { // DISPLAY total: minconf=0 — includes the user's own pending change so it doesn't crater
|
||||
totalBalance = rpc.call("z_gettotalbalance", json::array({0}));
|
||||
balanceOk = true;
|
||||
@@ -305,6 +311,8 @@ NetworkRefreshService::CoreRefreshResult NetworkRefreshService::collectCoreRefre
|
||||
try { // SPENDABLE total: minconf=1 (confirmed). If absent, spendable degrades to display in apply.
|
||||
spendableBalance = rpc.call("z_gettotalbalance", json::array({1}));
|
||||
} catch (...) {}
|
||||
balanceScanMs = std::chrono::duration<double, std::milli>(
|
||||
std::chrono::steady_clock::now() - balanceStart).count();
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -314,7 +322,9 @@ NetworkRefreshService::CoreRefreshResult NetworkRefreshService::collectCoreRefre
|
||||
DEBUG_LOGF("BlockchainInfo error: %s\n", e.what());
|
||||
}
|
||||
|
||||
return parseCoreRefreshResult(totalBalance, spendableBalance, balanceOk, blockInfo, blockOk);
|
||||
auto result = parseCoreRefreshResult(totalBalance, spendableBalance, balanceOk, blockInfo, blockOk);
|
||||
result.balanceScanMs = balanceScanMs;
|
||||
return result;
|
||||
}
|
||||
|
||||
NetworkRefreshService::MiningRefreshResult NetworkRefreshService::parseMiningRefreshResult(
|
||||
|
||||
Reference in New Issue
Block a user