feat(wallet): persist history and surface pending sends
Add an encrypted SQLite transaction history cache with cached tip metadata and per-address shielded scan progress so startup and full refreshes avoid re-scanning every z-address while still invalidating on wallet/address/rescan changes. Improve wallet history loading by paging transparent transactions, preserving cached shielded and sent rows, keeping recent/unconfirmed activity visible, and classifying mining-address receives. Show z_sendmany opid sends immediately in History and Overview, pin pending rows through refreshes, and apply optimistic address/balance debits until opids resolve. Add timestamped RPC console tracing by source/method without logging params or results, reduce redundant refresh/RPC calls, and cache Explorer recent block summaries in SQLite. Expand focused tests for transaction cache encryption, scan-progress persistence/invalidation, history preservation, operation-status parsing, pending send visibility, and Explorer/RPC refresh behavior.
This commit is contained in:
55
src/app.h
55
src/app.h
@@ -12,6 +12,7 @@
|
||||
#include <chrono>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include "data/transaction_history_cache.h"
|
||||
#include "data/wallet_state.h"
|
||||
#include "rpc/connection.h"
|
||||
#include "services/network_refresh_service.h"
|
||||
@@ -217,6 +218,9 @@ public:
|
||||
void setAddressSortOrder(const std::string& addr, int order);
|
||||
int getNextSortOrder() const;
|
||||
void swapAddressOrder(const std::string& a, const std::string& b);
|
||||
bool isMiningAddress(const std::string& addr) const;
|
||||
void setMiningAddress(const std::string& addr, bool mining);
|
||||
void invalidateAddressValidationCache();
|
||||
|
||||
// Key export/import
|
||||
void exportPrivateKey(const std::string& address, std::function<void(const std::string&)> callback);
|
||||
@@ -353,12 +357,38 @@ public:
|
||||
|
||||
/// @brief Check if RPC worker has queued results waiting to be processed
|
||||
bool hasPendingRPCResults() const;
|
||||
bool hasTransactionSendProgress() const { return send_progress_active_ || send_submissions_in_flight_ > 0 || !pending_opids_.empty(); }
|
||||
std::string transactionSendProgressText() const;
|
||||
std::string transactionRefreshProgressText() const;
|
||||
bool isTransactionRefreshInProgress() const {
|
||||
return network_refresh_.jobInProgress(services::NetworkRefreshService::Job::Transactions);
|
||||
}
|
||||
|
||||
private:
|
||||
friend class AppDaemonLifecycleRuntime;
|
||||
friend class AppDaemonLifecycleTaskContext;
|
||||
|
||||
bool sendStopCommandSafely(rpc::RPCClient& client, const char* context);
|
||||
void maybeFinishTransactionSendProgress();
|
||||
void upsertPendingSendTransaction(const std::string& opid,
|
||||
const std::string& from,
|
||||
const std::string& to,
|
||||
double amount,
|
||||
const std::string& memo);
|
||||
void markPendingSendTransactionSucceeded(const std::string& opid,
|
||||
const std::string& txid);
|
||||
void removePendingSendTransactions(const std::vector<std::string>& opids,
|
||||
bool restoreBalances);
|
||||
void applyPendingSendBalanceDeltas(bool includeAggregateBalances);
|
||||
std::string transactionHistoryCacheWalletIdentity() const;
|
||||
bool ensureTransactionHistoryCacheUnlockedFor(const std::string& walletIdentity);
|
||||
void unlockTransactionHistoryCacheWithPassphrase(const std::string& passphrase);
|
||||
void loadTransactionHistoryCacheIfAvailable();
|
||||
void storeTransactionHistoryCacheIfAvailable();
|
||||
void wipePendingTransactionHistoryCachePassphrase();
|
||||
void resetTransactionHistoryCacheSession();
|
||||
void pruneShieldedHistoryScanProgress();
|
||||
void invalidateShieldedHistoryScanProgress(bool persistCache);
|
||||
|
||||
// Subsystems
|
||||
std::unique_ptr<rpc::RPCClient> rpc_;
|
||||
@@ -477,6 +507,9 @@ private:
|
||||
// P4: Incremental transaction cache
|
||||
int last_tx_block_height_ = -1; // block height at last full tx fetch
|
||||
static constexpr int MAX_VIEWTX_PER_CYCLE = 25; // cap z_viewtransaction calls per refresh
|
||||
std::size_t shielded_history_scan_cursor_ = 0;
|
||||
bool shielded_history_scan_pending_ = false;
|
||||
std::unordered_map<std::string, int> shielded_history_scan_heights_;
|
||||
|
||||
// P4b: z_viewtransaction result cache — avoids re-calling the RPC for
|
||||
// txids we've already enriched. Keyed by txid.
|
||||
@@ -492,11 +525,24 @@ private:
|
||||
|
||||
// Dirty flags for demand-driven refresh
|
||||
bool addresses_dirty_ = true; // true → refreshAddresses() will run
|
||||
bool address_validation_cache_dirty_ = true;
|
||||
bool transactions_dirty_ = false; // true → force tx refresh regardless of block height
|
||||
bool encryption_state_prefetched_ = false; // suppress duplicate getwalletinfo on connect
|
||||
bool rescan_status_poll_in_progress_ = false;
|
||||
bool opid_poll_in_progress_ = false;
|
||||
|
||||
// Pending z_sendmany operation tracking
|
||||
bool send_progress_active_ = false;
|
||||
int send_submissions_in_flight_ = 0;
|
||||
std::vector<std::string> pending_opids_; // opids to poll for completion
|
||||
struct PendingSendInfo {
|
||||
std::string from;
|
||||
std::string to;
|
||||
std::string memo;
|
||||
double amount = 0.0;
|
||||
std::int64_t timestamp = 0;
|
||||
};
|
||||
std::unordered_map<std::string, PendingSendInfo> pending_send_info_;
|
||||
// Txids from completed z_sendmany operations.
|
||||
// Ensures shielded sends are discoverable by z_viewtransaction
|
||||
// even when they don't appear in listtransactions or
|
||||
@@ -520,6 +566,9 @@ private:
|
||||
|
||||
// PIN vault
|
||||
std::unique_ptr<util::SecureVault> vault_;
|
||||
data::TransactionHistoryCache transaction_history_cache_;
|
||||
std::string pending_transaction_history_cache_passphrase_;
|
||||
bool transaction_history_cache_loaded_ = false;
|
||||
|
||||
// Lock screen state
|
||||
bool lock_screen_was_visible_ = false; // tracks lock→unlock transitions for auto-focus
|
||||
@@ -600,13 +649,17 @@ private:
|
||||
void refreshCoreData(); // Balance + blockchain info (can use fast_worker_)
|
||||
void refreshAddressData(); // Address lists + balances
|
||||
void refreshTransactionData(); // Transaction list + z_viewtransaction enrichment
|
||||
void refreshEncryptionState(); // Wallet encryption/lock state
|
||||
void refreshRecentTransactionData(); // Lightweight recent/unconfirmed tx poll
|
||||
bool refreshEncryptionState(); // Wallet encryption/lock state
|
||||
void refreshBalance(); // Legacy: balance-only refresh (used by specific callers)
|
||||
void refreshAddresses(); // Legacy: standalone address refresh
|
||||
void refreshPrice();
|
||||
void refreshWalletEncryptionState();
|
||||
void applyRefreshPolicy(ui::NavPage page);
|
||||
bool currentPageNeedsWalletDataRefresh() const;
|
||||
bool shouldRunWalletTransactionRefresh() const;
|
||||
bool shouldRefreshTransactions() const;
|
||||
bool shouldRefreshRecentTransactions() const;
|
||||
void checkAutoLock();
|
||||
void checkIdleMining();
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user