perf(net): one scan budget for all wallet scans, gated on what changed
The balance, address scan, history and probe were throttled one by one at 10% each, which added up to 30-40% of dragonxd's cs_main with all of them due. services::WalletScanGovernor runs one wallet scan at a time across both RPC lanes and keeps one shared quiet clock at 15% of wall-clock (cut when the node is behind). Waiters are served first in, first out, with a head start for what the page shows and a starvation floor of max(30 s visible / 900 s hidden, 15x the stream's cost). Explicit work (a dirty set, the user's send, a first load) is never refused but is charged. Routine scans run only when the tip, the mempool size (getmempoolinfo: no cs_main) or a wallet-mutation epoch changed since their last good scan, or a safety sweep is due. Pages that don't show the balance poll it at most every max(10 min, 50x its cost) (2 min with client-side auto-shield); coming back into view with an old value fetches it at once. The staleness badge counts from the last poll that confirmed nothing changed, and hides while the budget holds the balance within its guarantee. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -132,6 +132,11 @@ public:
|
||||
bool balanceAttempted = false;
|
||||
bool spendableQueried = false; // the minconf=1 call was made
|
||||
std::optional<std::int64_t> totalBalanceZat; // the minconf=0 "total" in zatoshis, exact
|
||||
// getmempoolinfo (CoreRefreshPlan::fetchMempool): a change signal that takes no cs_main.
|
||||
bool mempoolFetched = false;
|
||||
bool mempoolOk = false;
|
||||
std::int64_t mempoolTxs = 0;
|
||||
std::int64_t mempoolBytes = 0;
|
||||
};
|
||||
|
||||
// What one Core refresh fetches. The minconf=1 (spendable) z_gettotalbalance is a second whole-wallet
|
||||
@@ -141,6 +146,7 @@ public:
|
||||
bool includeBalance = true;
|
||||
bool needSpendable = true;
|
||||
std::optional<std::int64_t> prevTotalZat;
|
||||
bool fetchMempool = false; // add getmempoolinfo after a successful getblockchaininfo
|
||||
};
|
||||
|
||||
struct MiningRefreshResult {
|
||||
@@ -200,6 +206,8 @@ public:
|
||||
// The wallet's unspent notes from this same z_listunspent scan, so a consumer (e.g. the chat
|
||||
// note-budget) can be fed for free instead of running its own duplicate z_listunspent.
|
||||
std::vector<UnspentNoteLite> unspentNotes;
|
||||
bool balancesOk = true; // false: a balance call failed, the per-address balances may be stale
|
||||
bool replyTimedOut = false; // a call hit the reply timeout (the daemon kept running it)
|
||||
};
|
||||
|
||||
struct AddressRefreshSnapshot {
|
||||
@@ -283,6 +291,8 @@ public:
|
||||
// Txids found but not enriched (over the per-cycle cap, or the view failed transiently): the caller
|
||||
// keeps them and feeds them back as enrichBacklogTxids.
|
||||
std::unordered_set<std::string> unenrichedTxids;
|
||||
bool rpcError = false; // a wallet-wide call failed
|
||||
bool replyTimedOut = false; // ...with a reply timeout (the daemon kept running it)
|
||||
};
|
||||
|
||||
struct OperationStatusPollResult {
|
||||
@@ -465,6 +475,7 @@ public:
|
||||
void markDue(Timer timer) { scheduler_.markDue(timer); }
|
||||
void setTimer(Timer timer, float seconds) { scheduler_.setTimer(timer, seconds); }
|
||||
void deferFor(Timer timer, float seconds) { scheduler_.deferFor(timer, seconds); }
|
||||
void retrySoon(Timer timer, float seconds) { scheduler_.retrySoon(timer, seconds); }
|
||||
float timer(Timer timer) const { return scheduler_.timer(timer); }
|
||||
float interval(Timer timer) const { return scheduler_.interval(timer); }
|
||||
void markImmediateRefresh() { scheduler_.markImmediateRefresh(); }
|
||||
|
||||
Reference in New Issue
Block a user