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:
2026-05-05 03:22:14 -05:00
parent 948ef419ac
commit 975743f754
43 changed files with 3732 additions and 702 deletions
+70
View File
@@ -0,0 +1,70 @@
#pragma once
#include <cstdint>
#include <map>
#include <string>
struct sqlite3;
namespace dragonx {
namespace ui {
struct ExplorerBlockSummary {
int height = 0;
std::string hash;
int tx_count = 0;
int size = 0;
std::int64_t time = 0;
double difficulty = 0.0;
};
class ExplorerBlockCache {
public:
struct SavedTipValidation {
bool needed = false;
int height = 0;
std::string expectedHash;
};
ExplorerBlockCache();
ExplorerBlockCache(std::string databasePath, std::string legacyJsonPath);
~ExplorerBlockCache();
ExplorerBlockCache(const ExplorerBlockCache&) = delete;
ExplorerBlockCache& operator=(const ExplorerBlockCache&) = delete;
static std::string defaultDatabasePath();
static std::string defaultLegacyJsonPath();
bool ensureOpen();
bool isOpen() const { return db_ != nullptr; }
const std::string& databasePath() const { return database_path_; }
std::map<int, ExplorerBlockSummary> loadRange(int minHeight, int maxHeight);
bool storeBlock(const ExplorerBlockSummary& block);
int cachedBlockCount();
void clearBlocks();
SavedTipValidation prepareValidation(int currentHeight, const std::string& currentBestHash);
void applySavedTipValidation(const SavedTipValidation& validation,
const std::string& actualHash,
int currentHeight,
const std::string& currentBestHash);
void updateTip(int height, const std::string& hash);
private:
bool exec(const char* sql);
std::string getMetaValue(const std::string& key);
int getMetaInt(const std::string& key, int fallback);
void setMetaValue(const std::string& key, const std::string& value);
bool createSchema();
void migrateLegacyJsonIfNeeded();
void close();
sqlite3* db_ = nullptr;
std::string database_path_;
std::string legacy_json_path_;
};
} // namespace ui
} // namespace dragonx