fix(sync): prioritize getblockchaininfo and pause chat scans while behind

The node kept falling behind the network near the tip because sync
DETECTION was starved: getblockchaininfo was queued behind the
O(mapWallet) wallet RPCs (z_gettotalbalance / z_listunspent), so
longestchain went stale, the wallet decided it was "synced", and it
resumed hammering cs_main — a feedback loop.

- Issue getblockchaininfo FIRST each cycle and skip the balance/address/
  tx refresh entirely while behind, so sync state (and kSyncProfile)
  updates before any heavy wallet scan runs.
- Gate the two chat note scans (refreshChatNoteBudgetNode /
  fastScanChatMemos) on effectivelySyncing() and the active page, so chat
  memo scanning no longer competes with block connection during sync.
- Windows debug.log tailer: reset the read offset when dragonxd truncates
  the log on startup (it was stranding at Block:0 with no witness/rescan
  progress).
- Tests cover the getblockchaininfo-first ordering and behind-skip.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-01 19:36:30 -05:00
parent 56d93b6128
commit 4492aa3425
5 changed files with 231 additions and 29 deletions

View File

@@ -1417,31 +1417,51 @@ void testNetworkRefreshRpcCollectors()
});
coreRpc.addResponse("getblockchaininfo", json{
{"blocks", 150},
{"headers", 155},
{"headers", 150},
{"bestblockhash", "core-best-150"},
{"verificationprogress", 0.80},
{"longestchain", 160},
{"verificationprogress", 1.0},
{"longestchain", 150}, // caught up → balance scan runs
{"notarized", 145}
});
auto core = Refresh::collectCoreRefreshResult(coreRpc);
// getblockchaininfo is issued FIRST — sync detection gates (and must not be delayed behind) the
// O(mapWallet) balance scan. When caught up, the balance scan follows: display (minconf=0) +
// spendable (minconf=1).
EXPECT_TRUE(coreRpc.methodNames() == std::vector<std::string>({
"z_gettotalbalance", "z_gettotalbalance", "getblockchaininfo" // display (minconf=0) + spendable (minconf=1)
"getblockchaininfo", "z_gettotalbalance", "z_gettotalbalance"
}));
EXPECT_EQ(coreRpc.calls[0].params, json::array({0}));
EXPECT_EQ(coreRpc.calls[1].params, json::array({1}));
EXPECT_EQ(coreRpc.calls[1].params, json::array({0}));
EXPECT_EQ(coreRpc.calls[2].params, json::array({1}));
EXPECT_TRUE(core.balanceOk);
EXPECT_TRUE(core.blockchainOk);
EXPECT_NEAR(*core.totalBalance, 4.25, 0.00000001);
EXPECT_EQ(*core.blocks, 150);
EXPECT_EQ(*core.bestBlockHash, std::string("core-best-150"));
EXPECT_EQ(*core.longestChain, 160);
EXPECT_EQ(*core.longestChain, 150);
// When getblockchaininfo shows the node is behind (blocks < longestchain - 2), the balance scan is
// skipped this cycle regardless of includeBalance — so sync-state (and hence kSyncProfile) updates
// promptly instead of after the multi-second scan. Only getblockchaininfo is issued.
MockRefreshRpc coreBehindRpc;
coreBehindRpc.addResponse("z_gettotalbalance", json{{"total", "4.25000000"}});
coreBehindRpc.addResponse("getblockchaininfo", json{
{"blocks", 150}, {"headers", 160}, {"longestchain", 160}
});
auto coreBehind = Refresh::collectCoreRefreshResult(coreBehindRpc);
EXPECT_TRUE(coreBehindRpc.methodNames() == std::vector<std::string>({"getblockchaininfo"}));
EXPECT_FALSE(coreBehind.balanceOk);
EXPECT_TRUE(coreBehind.blockchainOk);
EXPECT_EQ(*coreBehind.blocks, 150);
EXPECT_EQ(*coreBehind.longestChain, 160);
MockRefreshRpc coreFallbackRpc;
coreFallbackRpc.addFailure("z_gettotalbalance", "wallet warming up");
coreFallbackRpc.addResponse("getblockchaininfo", json{{"blocks", 8}, {"headers", 9}});
auto partialCore = Refresh::collectCoreRefreshResult(coreFallbackRpc);
// No longestchain in the response → not classified as "behind" → balance is still attempted (here it
// fails). getblockchaininfo is still issued first.
EXPECT_TRUE(coreFallbackRpc.methodNames() == std::vector<std::string>({
"z_gettotalbalance", "z_gettotalbalance", "getblockchaininfo"
"getblockchaininfo", "z_gettotalbalance", "z_gettotalbalance"
}));
EXPECT_FALSE(partialCore.balanceOk);
EXPECT_TRUE(partialCore.blockchainOk);