Every history refresh, address refresh, connect and lock-state check rewrote the encrypted tx-history snapshot on the UI thread: a JSON document of every row, its encryption, then INSERT OR REPLACE and a prune, an estimated 40-120 ms on a 5k-transaction wallet, even when nothing had changed. Now a signature of what would be stored (about 1 ms on 5k rows) decides whether to write. Confirmations count only by the thresholds the UI reads (conflicted, pending, 1-9, 10-99, 100+); per-address scan heights, exact confirmations and the tip drift every block and form a separate part. - Routine refreshes write a content change at most every 20 s (a deferred one is released from the update loop) and the per-block drift at most every 10 min. Nothing changed means no write. - A mining-address relabel, a passphrase change, a rescan, a disconnect, a wallet lock and exit write whatever is not on disk yet, at once. - The UI thread only copies the rows and the key into a job; a single background writer (newest job wins) builds, encrypts and stores it. A failed write is retried. - Exit queues the final write so it overlaps the daemon stop, then waits up to 3 s for the writer; a disk that hangs past that keeps the previous snapshot. - A loaded snapshot counts as stored, so the first identical refresh doesn't rewrite it. An address refresh re-keys the snapshot only when the address set changed. After a hard kill the snapshot can now be up to ~20 s behind for rows and 10 min for scan heights, exact confirmations and the tip (it was one refresh); the first refresh replaces it either way. Lite builds, which never read this cache, no longer write it on Refresh or a mining relabel, and the screenshot capture mode's demo rows never reach it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
65 lines
2.9 KiB
C++
65 lines
2.9 KiB
C++
// DragonX Wallet - ImGui Edition
|
|
// Copyright 2024-2026 The Hush Developers
|
|
// Released under the GPLv3
|
|
//
|
|
// Pure decision logic for persisting the tx-history snapshot: what counts as "changed", and when a write is
|
|
// due. No I/O, no clock, no sodium: unit-tested directly.
|
|
|
|
#pragma once
|
|
|
|
#include "wallet_state.h"
|
|
|
|
#include <cstddef>
|
|
#include <cstdint>
|
|
#include <string>
|
|
#include <unordered_map>
|
|
#include <unordered_set>
|
|
#include <vector>
|
|
|
|
namespace dragonx {
|
|
namespace data {
|
|
|
|
// 64-bit fingerprints of what a snapshot would contain.
|
|
struct HistorySnapshotSig {
|
|
std::uint64_t content = 0; // rows (confirmations by UI bucket) + wallet hash + key id
|
|
std::uint64_t slow = 0; // shielded scan heights + raw confirmations + tip: drift every block
|
|
std::size_t rows = 0; // rows that would be persisted (pending sends excluded)
|
|
};
|
|
|
|
// Order-independent over rows. `content` covers the persisted row fields (txid, type, amount bit pattern,
|
|
// timestamp, address, from_address, memo) with confirmations bucketed at the thresholds the UI and the refresh
|
|
// read off a stored row (< 0 conflicted, 0 pending, 1-9 counted, >= 10 confirmed, >= 100 mature), plus the
|
|
// wallet hash and `keyId` (a passphrase change must rewrite: the old ciphertext is unreadable with the new key).
|
|
// `slow` covers what changes every block: the per-address scan heights (entries replace() would persist), each
|
|
// row's raw confirmations, and the tip.
|
|
HistorySnapshotSig historySnapshotSignature(const std::vector<TransactionInfo>& transactions,
|
|
const std::unordered_set<std::string>& skipTxids,
|
|
const std::string& walletHash,
|
|
std::uint64_t keyId,
|
|
const std::unordered_map<std::string, int>& shieldedScanHeights,
|
|
int tipHeight,
|
|
const std::string& tipHash);
|
|
|
|
enum class HistoryStoreMode {
|
|
Coalesce, // routine refresh: content changes at most every kHistoryMinStoreIntervalSec, slow drift every
|
|
// kHistorySlowStoreIntervalSec
|
|
Now // user action / disconnect / lock / exit: store now if anything differs from the last submit
|
|
};
|
|
enum class HistoryStoreAction { Skip, Defer, Store };
|
|
|
|
constexpr double kHistoryMinStoreIntervalSec = 20.0;
|
|
constexpr double kHistorySlowStoreIntervalSec = 600.0;
|
|
|
|
struct HistoryStoreInput {
|
|
HistoryStoreMode mode = HistoryStoreMode::Coalesce;
|
|
bool haveStored = false; // a snapshot of this session (submitted, or the loaded one) is on disk
|
|
HistorySnapshotSig stored; // its signature
|
|
HistorySnapshotSig current;
|
|
double sinceLastSubmitSec = 1e9; // 1e9 = never submitted this session
|
|
};
|
|
|
|
HistoryStoreAction decideHistoryStore(const HistoryStoreInput& input);
|
|
|
|
} // namespace data
|
|
} // namespace dragonx
|