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:
@@ -1477,6 +1477,36 @@ void testNetworkRefreshRpcCollectors()
|
||||
EXPECT_EQ(pagedResult.transactions.size(), static_cast<size_t>(1001));
|
||||
EXPECT_EQ(pagedResult.transactions[0].txid, std::string("paged-1000"));
|
||||
|
||||
// shieldedScanTipTolerance: an address scanned a few blocks below the tip is re-scanned when
|
||||
// strict (tolerance 0) but SKIPPED within tolerance — this is what lets a multi-block pass
|
||||
// complete (and unsticks the perpetual "refreshing history" state).
|
||||
{
|
||||
Refresh::TransactionRefreshSnapshot strictSnapshot;
|
||||
strictSnapshot.shieldedAddresses = {"zs-a", "zs-b"};
|
||||
strictSnapshot.shieldedScanHeights = {{"zs-a", 100}, {"zs-b", 100}};
|
||||
strictSnapshot.maxShieldedReceiveScans = 4;
|
||||
strictSnapshot.shieldedScanTipTolerance = 0; // strict: heights 100 are stale at tip 102
|
||||
MockRefreshRpc strictRpc;
|
||||
strictRpc.addResponse("listtransactions", json::array());
|
||||
strictRpc.addResponse("z_listreceivedbyaddress", json::array());
|
||||
strictRpc.addResponse("z_listreceivedbyaddress", json::array());
|
||||
auto strictResult = Refresh::collectTransactionRefreshResult(strictRpc, strictSnapshot, 102, 4);
|
||||
EXPECT_EQ(strictResult.shieldedAddressesScanned, static_cast<size_t>(2)); // both re-scanned
|
||||
|
||||
Refresh::TransactionRefreshSnapshot tolerantSnapshot;
|
||||
tolerantSnapshot.shieldedAddresses = {"zs-a", "zs-b"};
|
||||
tolerantSnapshot.shieldedScanHeights = {{"zs-a", 100}, {"zs-b", 100}};
|
||||
tolerantSnapshot.maxShieldedReceiveScans = 4;
|
||||
tolerantSnapshot.shieldedScanTipTolerance = 5; // 100 >= 102-5 -> still fresh
|
||||
MockRefreshRpc tolerantRpc;
|
||||
tolerantRpc.addResponse("listtransactions", json::array());
|
||||
// No z_listreceivedbyaddress responses: the addresses must be skipped (else call() throws).
|
||||
auto tolerantResult = Refresh::collectTransactionRefreshResult(tolerantRpc, tolerantSnapshot, 102, 4);
|
||||
EXPECT_EQ(tolerantResult.shieldedAddressesScanned, static_cast<size_t>(0)); // both skipped
|
||||
EXPECT_TRUE(tolerantResult.shieldedScanComplete);
|
||||
EXPECT_TRUE(tolerantRpc.methodNames() == std::vector<std::string>({"listtransactions"}));
|
||||
}
|
||||
|
||||
Refresh::TransactionRefreshSnapshot recentSnapshot;
|
||||
dragonx::TransactionInfo previousShielded;
|
||||
previousShielded.txid = "shielded-old";
|
||||
|
||||
Reference in New Issue
Block a user