fix(history): let the shielded scan complete + unstick send-progress on many-z-addr wallets
Two issues shared one root cause: the shielded-receive scan marked each z-address "scanned at the EXACT current tip," but a new block (~36s on DRGX) advances the tip and invalidates every prior per-address scan. A wallet with more z-addresses than one refresh cycle can scan therefore never reached "all scanned at tip" — so shieldedScanComplete stayed false and transactions_dirty_ stayed true forever, which (a) kept the history-refresh banner lit and the full rescan churning every cycle, and (b) blocked maybeFinishTransactionSendProgress (it waited on transactions_dirty_), leaving the send-progress indicator stuck on. Fix 1 — completion tolerance. Add TransactionRefreshSnapshot::shieldedScanTipTolerance: an address counts as fresh if its last scan is within N blocks of the tip (0 = old strict behavior, so existing tests are unchanged). The app scales N with the z-address count (2 + count/96, capped at 50), so a multi-block pass can COMPLETE before its earliest scan goes stale. This also throttles full rescans to ~N blocks instead of every block — transactions_dirty_ clears, the banner stops, and CPU/RPC churn drops. Already-fresh addresses are skipped, so the per-block cost falls back to just the (cheap) transparent listtransactions. Fix 2 — send-progress gate. maybeFinishTransactionSendProgress() no longer waits on the transaction history scan (transactions_dirty_ / Transactions job): the sent tx is already shown via the optimistic pending insert, and the spend is reflected once the balance refresh lands, so it now finishes on the address/balance signal alone. Test: a tolerant snapshot skips recently-scanned addresses (shieldedAddressesScanned == 0, shieldedScanComplete) while a strict one re-scans them. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -822,10 +822,14 @@ NetworkRefreshService::TransactionRefreshResult NetworkRefreshService::collectTr
|
||||
DEBUG_LOGF("listtransactions error: %s\n", e.what());
|
||||
}
|
||||
|
||||
// An address counts as "scanned at tip" if its last scan is within shieldedScanTipTolerance
|
||||
// blocks of the current tip. Tolerance 0 means strict (exact tip); a small tolerance lets a
|
||||
// multi-cycle pass over many addresses complete even as the tip advances during it.
|
||||
const int scanStaleThreshold = currentBlockHeight - std::max(0, snapshot.shieldedScanTipTolerance);
|
||||
auto scannedAtTip = [&](const std::string& address) {
|
||||
if (currentBlockHeight < 0) return false;
|
||||
auto it = result.shieldedScanHeights.find(address);
|
||||
return it != result.shieldedScanHeights.end() && it->second >= currentBlockHeight;
|
||||
return it != result.shieldedScanHeights.end() && it->second >= scanStaleThreshold;
|
||||
};
|
||||
|
||||
std::size_t shieldedStart = snapshot.shieldedScanStartIndex;
|
||||
|
||||
@@ -178,6 +178,13 @@ public:
|
||||
std::unordered_map<std::string, int> shieldedScanHeights;
|
||||
std::size_t shieldedScanStartIndex = 0;
|
||||
std::size_t maxShieldedReceiveScans = 0;
|
||||
// How many blocks the tip may advance past an address's last scan before it counts as stale
|
||||
// and needs re-scanning. 0 = strict (must be scanned at the exact current tip). A small
|
||||
// tolerance lets a multi-cycle pass over many shielded addresses COMPLETE even though new
|
||||
// blocks arrive mid-pass — otherwise the "scanned at tip" bar moves every block and the scan
|
||||
// (and the transactions_dirty_ flag it drives) never finishes. It also naturally throttles
|
||||
// full rescans to roughly once per `tolerance` blocks.
|
||||
int shieldedScanTipTolerance = 0;
|
||||
};
|
||||
|
||||
struct TransactionRefreshResult {
|
||||
|
||||
Reference in New Issue
Block a user