perf(chat): sends re-read by origin, chat scans under the scan budget

A z_sendmany submit set off a burst of wallet scans before anything had
changed. Ops now re-read the wallet when they finish, by who started them:
the user's sends and dialog ops get a re-read plus one forced balance once
none of their ops is pending; client auto-shield re-reads unforced; chat
messages and splits refresh only the chat note view. A pending send no
longer forces scans, and chat txids no longer enter the tracked-send set.

Chat ingest skips stored messages before decrypting and caches
undecryptable ones per identity. The inbox scan runs when getwalletinfo's
txcount changed (a cheap probe, at most 2% duty) or a cost-scaled safety
net is due; the note-buffer scan runs on need. Both are admitted by the
scan budget, on a steady clock. The chat payer comes from a per-address
view of verified notes, with debits re-applied across in-flight scans;
splits are sized to one payer and only when they can refill the buffer.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-09 11:06:34 -05:00
co-authored by Claude Opus 5.5
parent f86cc4d6b7
commit b29a09eaa0
15 changed files with 1037 additions and 177 deletions
+33
View File
@@ -1704,6 +1704,39 @@ std::optional<json> NetworkRefreshService::buildOperationFilterParams(const std:
return params;
}
NetworkRefreshService::OperationBatchPlan NetworkRefreshService::planOperationBatch(
const OperationStatusPollResult& parsed, const std::unordered_map<std::string, OperationOrigin>& origins)
{
auto originOf = [&](const std::string& id) {
const auto it = origins.find(id);
return it == origins.end() ? OperationOrigin::User : it->second;
};
OperationBatchPlan plan;
auto terminal = [&](const std::string& id) {
if (originOf(id) == OperationOrigin::Chat) plan.chatTerminal = true;
else plan.walletTerminal = true;
};
for (const auto& id : parsed.doneOpids) { // success, failed or cancelled
terminal(id);
if (parsed.failureByOpid.count(id)) continue; // failed: nothing committed
const auto origin = originOf(id);
if (origin == OperationOrigin::User) plan.userSucceeded = true; // a success without a txid too
else if (origin == OperationOrigin::Auto) plan.autoSucceeded = true;
}
for (const auto& id : parsed.staleOpids) terminal(id);
return plan;
}
bool NetworkRefreshService::anyUserOperationPending(const std::vector<std::string>& pendingOpids,
const std::unordered_map<std::string, OperationOrigin>& origins)
{
for (const auto& id : pendingOpids) {
const auto it = origins.find(id);
if (it == origins.end() || it->second == OperationOrigin::User) return true;
}
return false;
}
void NetworkRefreshService::applyConnectionInfoResult(WalletState& state, const ConnectionInfoResult& result)
{
if (!result.ok) return;