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:
@@ -103,6 +103,7 @@ public:
|
||||
bool blockchainOk = false;
|
||||
std::optional<int> blocks;
|
||||
std::optional<int> headers;
|
||||
std::optional<std::string> bestBlockHash;
|
||||
std::optional<double> verificationProgress;
|
||||
std::optional<int> longestChain;
|
||||
std::optional<int> notarized;
|
||||
@@ -146,6 +147,10 @@ public:
|
||||
std::vector<AddressInfo> transparentAddresses;
|
||||
};
|
||||
|
||||
struct AddressRefreshSnapshot {
|
||||
std::unordered_map<std::string, bool> shieldedSpendingKeys;
|
||||
};
|
||||
|
||||
struct TransactionViewCacheEntry {
|
||||
std::string from_address;
|
||||
std::int64_t timestamp = 0;
|
||||
@@ -165,12 +170,32 @@ public:
|
||||
std::unordered_set<std::string> fullyEnrichedTxids;
|
||||
TransactionViewCache viewTxCache;
|
||||
std::unordered_set<std::string> sendTxids;
|
||||
std::unordered_set<std::string> pendingOpids;
|
||||
std::vector<TransactionInfo> previousTransactions;
|
||||
std::set<std::string> miningAddresses;
|
||||
std::unordered_map<std::string, int> shieldedScanHeights;
|
||||
std::size_t shieldedScanStartIndex = 0;
|
||||
std::size_t maxShieldedReceiveScans = 0;
|
||||
};
|
||||
|
||||
struct TransactionRefreshResult {
|
||||
std::vector<TransactionInfo> transactions;
|
||||
int blockHeight = -1;
|
||||
TransactionViewCache newViewTxEntries;
|
||||
std::size_t nextShieldedScanStartIndex = 0;
|
||||
std::size_t shieldedAddressesScanned = 0;
|
||||
std::size_t shieldedAddressCount = 0;
|
||||
std::unordered_map<std::string, int> shieldedScanHeights;
|
||||
bool shieldedScanComplete = true;
|
||||
};
|
||||
|
||||
struct OperationStatusPollResult {
|
||||
std::vector<std::string> doneOpids;
|
||||
std::vector<std::string> staleOpids;
|
||||
std::vector<std::string> successTxids;
|
||||
std::unordered_map<std::string, std::string> successTxidsByOpid;
|
||||
std::vector<std::string> failureMessages;
|
||||
bool anySuccess = false;
|
||||
};
|
||||
|
||||
struct TransactionCacheUpdate {
|
||||
@@ -187,7 +212,9 @@ public:
|
||||
static ConnectionInfoResult parseConnectionInfoResult(const nlohmann::json& info);
|
||||
static WalletEncryptionResult parseWalletEncryptionResult(const nlohmann::json& walletInfo);
|
||||
static WarmupPollResult collectWarmupPollResult(RefreshRpcGateway& rpc);
|
||||
static ConnectionInitResult collectConnectionInitResult(RefreshRpcGateway& rpc);
|
||||
static ConnectionInitResult collectConnectionInitResult(
|
||||
RefreshRpcGateway& rpc,
|
||||
const std::optional<ConnectionInfoResult>& prefetchedInfo = std::nullopt);
|
||||
static CoreRefreshResult parseCoreRefreshResult(const nlohmann::json& totalBalance,
|
||||
bool balanceOk,
|
||||
const nlohmann::json& blockInfo,
|
||||
@@ -200,7 +227,8 @@ public:
|
||||
double daemonMemoryMb);
|
||||
static MiningRefreshResult collectMiningRefreshResult(RefreshRpcGateway& rpc,
|
||||
double daemonMemoryMb,
|
||||
bool includeSlowRefresh);
|
||||
bool includeSlowRefresh,
|
||||
bool includeLocalHashrate = true);
|
||||
static PeerRefreshResult parsePeerRefreshResult(const nlohmann::json& peers,
|
||||
const nlohmann::json& bannedPeers);
|
||||
static PeerRefreshResult collectPeerRefreshResult(RefreshRpcGateway& rpc);
|
||||
@@ -217,17 +245,22 @@ public:
|
||||
const nlohmann::json& unspent);
|
||||
static void applyTransparentBalancesFromUnspent(std::vector<AddressInfo>& addresses,
|
||||
const nlohmann::json& unspent);
|
||||
static AddressRefreshResult collectAddressRefreshResult(RefreshRpcGateway& rpc);
|
||||
static AddressRefreshSnapshot buildAddressRefreshSnapshot(const WalletState& state);
|
||||
static AddressRefreshResult collectAddressRefreshResult(
|
||||
RefreshRpcGateway& rpc,
|
||||
const AddressRefreshSnapshot& snapshot = {});
|
||||
static TransactionRefreshSnapshot buildTransactionRefreshSnapshot(const WalletState& state,
|
||||
const TransactionViewCache& viewTxCache,
|
||||
const std::unordered_set<std::string>& sendTxids);
|
||||
static void appendTransparentTransactions(std::vector<TransactionInfo>& transactions,
|
||||
std::set<std::string>& knownTxids,
|
||||
const nlohmann::json& result);
|
||||
const nlohmann::json& result,
|
||||
const std::set<std::string>& miningAddresses = {});
|
||||
static void appendShieldedReceivedTransactions(std::vector<TransactionInfo>& transactions,
|
||||
std::set<std::string>& knownTxids,
|
||||
const std::string& address,
|
||||
const nlohmann::json& received);
|
||||
const nlohmann::json& received,
|
||||
const std::set<std::string>& miningAddresses = {});
|
||||
static TransactionViewCacheEntry parseViewTransactionCacheEntry(const nlohmann::json& viewTransaction);
|
||||
static void appendViewTransactionOutputs(std::vector<TransactionInfo>& transactions,
|
||||
const std::string& txid,
|
||||
@@ -237,6 +270,13 @@ public:
|
||||
const TransactionRefreshSnapshot& snapshot,
|
||||
int currentBlockHeight,
|
||||
int maxViewTransactionsPerCycle);
|
||||
static TransactionRefreshResult collectRecentTransactionRefreshResult(
|
||||
RefreshRpcGateway& rpc,
|
||||
const TransactionRefreshSnapshot& snapshot,
|
||||
int currentBlockHeight,
|
||||
int pageSize = 100);
|
||||
static OperationStatusPollResult parseOperationStatusPoll(const nlohmann::json& result,
|
||||
const std::vector<std::string>& requestedOpids);
|
||||
|
||||
static void applyConnectionInfoResult(WalletState& state, const ConnectionInfoResult& result);
|
||||
static void applyWalletEncryptionResult(WalletState& state, const WalletEncryptionResult& result);
|
||||
@@ -280,7 +320,6 @@ public:
|
||||
void resetTxAge() { scheduler_.resetTxAge(); }
|
||||
bool shouldRefreshTransactions(int lastTxBlockHeight,
|
||||
int currentBlockHeight,
|
||||
bool transactionsEmpty,
|
||||
bool transactionsDirty) const;
|
||||
|
||||
bool beginJob(Job job);
|
||||
@@ -301,7 +340,12 @@ public:
|
||||
if (!ticket.accepted) return {ticket, false, queueDepth};
|
||||
|
||||
worker.post([this, ticket, work = std::forward<WorkFn>(work)]() mutable -> rpc::RPCWorker::MainCb {
|
||||
auto mainCallback = work();
|
||||
rpc::RPCWorker::MainCb mainCallback;
|
||||
try {
|
||||
mainCallback = work();
|
||||
} catch (...) {
|
||||
mainCallback = nullptr;
|
||||
}
|
||||
return [this, ticket, mainCallback = std::move(mainCallback)]() mutable {
|
||||
if (!completeDispatch(ticket)) return;
|
||||
if (mainCallback) mainCallback();
|
||||
|
||||
Reference in New Issue
Block a user