Files
ObsidianDragon/src/app.h
dan_s b4e09cdf75 Reduce redundant RPC calls in periodic refresh cycle
- Remove getinfo from refreshMiningInfo slow path; daemon_version,
  protocol_version, and p2p_port are static per connection (set in
  onConnected). Move longestchain/notarized into refreshBalance's
  existing getblockchaininfo callback.

- Remove refreshMiningInfo from refreshData(); it already runs on
  the 1-second fast_refresh_timer_ independently.

- Make refreshAddresses demand-driven via addresses_dirty_ flag;
  only re-fetch when a new address is created or a send completes,
  not unconditionally every 5 seconds.

- Gate refreshPeerInfo to only run when the Peers tab is active.

- Skip duplicate getwalletinfo on connect; onConnected() already
  prefetches it for immediate lock-screen display, so suppress the
  redundant call in the first refreshData() cycle.

Steady-state savings: ~8 fewer RPC calls per 5-second cycle
(from ~12+ down to ~4-5 in the common case).
2026-02-27 02:28:29 -06:00

531 lines
19 KiB
C++

// DragonX Wallet - ImGui Edition
// Copyright 2024-2026 The Hush Developers
// Released under the GPLv3
#pragma once
#include <memory>
#include <string>
#include <functional>
#include <thread>
#include <atomic>
#include <chrono>
#include "data/wallet_state.h"
#include "ui/sidebar.h"
#include "ui/windows/console_tab.h"
#include "imgui.h"
// Forward declarations
namespace dragonx {
namespace rpc {
class RPCClient;
class RPCWorker;
struct ConnectionConfig;
}
namespace config { class Settings; }
namespace daemon { class EmbeddedDaemon; class XmrigManager; }
namespace util { class Bootstrap; class SecureVault; }
}
namespace dragonx {
/**
* @brief First-run wizard states
*/
enum class WizardPhase {
None, // No wizard — normal operation
BootstrapOffer, // Step 1: offer bootstrap download
BootstrapInProgress,// downloading / extracting
BootstrapFailed, // error with retry/skip option
Appearance, // Step 2: visual effects / performance options
EncryptOffer, // Step 3: offer wallet encryption
EncryptInProgress, // encrypting + daemon restarting
PinSetup, // Step 4: optional PIN setup (after encryption)
Done // wizard complete, launch normally
};
/**
* @brief Encrypt-wallet dialog phases (settings page flow)
*/
enum class EncryptDialogPhase {
PassphraseEntry, // Enter & confirm passphrase
Encrypting, // In-progress animation
PinSetup, // Offer PIN after successful encryption
Done // Finished — close dialog
};
/**
* @brief Main application class
*
* Manages application state, RPC connection, and coordinates UI rendering.
*/
class App {
public:
App();
~App();
// Non-copyable
App(const App&) = delete;
App& operator=(const App&) = delete;
/**
* @brief Initialize the application
* @return true if initialization succeeded
*/
bool init();
/**
* @brief Update application state (called every frame)
*/
void update();
/**
* @brief Render the application UI (called every frame)
*/
void render();
/**
* @brief Clean shutdown
*/
void shutdown();
/**
* @brief Check if app should exit (render loop can stop)
* Enforces minimum 1-second display of shutdown screen so user sees feedback.
*/
bool shouldQuit() const {
if (!quit_requested_ || !shutdown_complete_) return false;
auto elapsed = std::chrono::steady_clock::now() - shutdown_start_time_;
return elapsed >= std::chrono::seconds(1);
}
/**
* @brief Request application exit — begins async shutdown
*/
void requestQuit();
/**
* @brief Begin graceful shutdown (called on window close)
* Starts daemon stop on a background thread while UI keeps rendering.
*/
void beginShutdown();
/**
* @brief Whether we are in the shutdown phase
*/
bool isShuttingDown() const { return shutting_down_; }
/**
* @brief Render the shutdown overlay (called instead of normal UI during shutdown)
*/
void renderShutdownScreen();
/**
* @brief Render loading overlay in content area while daemon is starting/syncing
* @param contentH Height of the content area child window
*/
void renderLoadingOverlay(float contentH);
// Accessors for subsystems
rpc::RPCClient* rpc() { return rpc_.get(); }
rpc::RPCWorker* worker() { return worker_.get(); }
config::Settings* settings() { return settings_.get(); }
WalletState& state() { return state_; }
const WalletState& state() const { return state_; }
const WalletState& getWalletState() const { return state_; }
// Connection state (convenience wrappers)
bool isConnected() const { return state_.connected; }
int getBlockHeight() const { return state_.sync.blocks; }
const std::string& getConnectionStatus() const { return connection_status_; }
const std::string& getDaemonStatus() const { return daemon_status_; }
// Balance info (convenience wrappers)
double getShieldedBalance() const { return state_.shielded_balance; }
double getTransparentBalance() const { return state_.transparent_balance; }
double getTotalBalance() const { return state_.total_balance; }
double getUnconfirmedBalance() const { return state_.unconfirmed_balance; }
// Addresses
const std::vector<AddressInfo>& getZAddresses() const { return state_.z_addresses; }
const std::vector<AddressInfo>& getTAddresses() const { return state_.t_addresses; }
// Transactions
const std::vector<TransactionInfo>& getTransactions() const { return state_.transactions; }
// Mining
const MiningInfo& getMiningInfo() const { return state_.mining; }
void startMining(int threads);
void stopMining();
bool isMiningToggleInProgress() const { return mining_toggle_in_progress_.load(std::memory_order_relaxed); }
// Pool mining (xmrig)
void startPoolMining(int threads);
void stopPoolMining();
// Peers
const std::vector<PeerInfo>& getPeers() const { return state_.peers; }
const std::vector<BannedPeer>& getBannedPeers() const { return state_.bannedPeers; }
void banPeer(const std::string& ip, int duration_seconds = 86400);
void unbanPeer(const std::string& ip);
void clearBans();
// Address operations
void createNewZAddress(std::function<void(const std::string&)> callback = nullptr);
void createNewTAddress(std::function<void(const std::string&)> callback = nullptr);
// Hide/unhide addresses from the address list (persisted in settings)
void hideAddress(const std::string& addr);
void unhideAddress(const std::string& addr);
bool isAddressHidden(const std::string& addr) const;
int getHiddenAddressCount() const;
void favoriteAddress(const std::string& addr);
void unfavoriteAddress(const std::string& addr);
bool isAddressFavorite(const std::string& addr) const;
// Key export/import
void exportPrivateKey(const std::string& address, std::function<void(const std::string&)> callback);
void exportAllKeys(std::function<void(const std::string&)> callback);
void importPrivateKey(const std::string& key, std::function<void(bool, const std::string&)> callback);
// Wallet backup
void backupWallet(const std::string& destination, std::function<void(bool, const std::string&)> callback);
// Transaction operations
void sendTransaction(const std::string& from, const std::string& to,
double amount, double fee, const std::string& memo,
std::function<void(bool success, const std::string& result)> callback);
// Force refresh
void refreshNow();
void refreshMiningInfo();
void refreshPeerInfo();
void refreshMarketData();
// UI navigation
void setCurrentPage(ui::NavPage page) { current_page_ = page; }
ui::NavPage getCurrentPage() const { return current_page_; }
// Dialog triggers (used by settings page to open modal dialogs)
void showImportKeyDialog() { show_import_key_ = true; }
void showExportKeyDialog() { show_export_key_ = true; }
void showBackupDialog() { show_backup_ = true; }
void showAboutDialog() { show_about_ = true; }
// Legacy tab compat — maps int to NavPage
void setCurrentTab(int tab);
int getCurrentTab() const { return static_cast<int>(current_page_); }
// Payment URI handling
void handlePaymentURI(const std::string& uri);
bool hasPendingPayment() const { return pending_payment_valid_; }
void clearPendingPayment() { pending_payment_valid_ = false; }
std::string getPendingToAddress() const { return pending_to_address_; }
double getPendingAmount() const { return pending_amount_; }
std::string getPendingMemo() const { return pending_memo_; }
std::string getPendingLabel() const { return pending_label_; }
// Embedded daemon control
bool startEmbeddedDaemon();
void stopEmbeddedDaemon();
bool isEmbeddedDaemonRunning() const;
bool isUsingEmbeddedDaemon() const { return use_embedded_daemon_; }
void setUseEmbeddedDaemon(bool use) { use_embedded_daemon_ = use; }
// Get daemon memory usage in MB (uses embedded daemon handle if available,
// falls back to platform-level process scan for external daemons)
double getDaemonMemoryUsageMB() const;
// Diagnostic string describing daemon memory detection path (for debugging)
const std::string& getDaemonMemDiag() const { return daemon_mem_diag_; }
// Background gradient overlay texture
void setGradientTexture(ImTextureID tex) { gradient_tex_ = tex; }
ImTextureID getGradientTexture() const { return gradient_tex_; }
// Logo texture accessor (wallet branding icon)
ImTextureID getLogoTexture() const { return logo_tex_; }
// Coin logo texture accessor (DragonX currency icon for balance tab)
ImTextureID getCoinLogoTexture() const { return coin_logo_tex_; }
/**
* @brief Reload theme images (background gradient + logo) from new paths
* @param bgPath Path to background image override (empty = use default)
* @param logoPath Path to logo image override (empty = use default)
*/
void reloadThemeImages(const std::string& bgPath, const std::string& logoPath);
// Wizard / first-run
WizardPhase getWizardPhase() const { return wizard_phase_; }
bool isFirstRun() const;
/**
* @brief Stop daemon and re-run the setup wizard
* Called from Settings. Daemon restarts automatically when wizard completes.
*/
void restartWizard();
/**
* @brief Restart the embedded daemon (e.g. after changing debug categories)
* Shows "Restarting daemon..." in the loading overlay while the daemon cycles.
*/
void restartDaemon();
// Wallet encryption helpers
void encryptWalletWithPassphrase(const std::string& passphrase);
void unlockWallet(const std::string& passphrase, int timeout);
void lockWallet();
void changePassphrase(const std::string& oldPass, const std::string& newPass);
// Dialog triggers for encryption (from settings page)
void showEncryptDialog() {
show_encrypt_dialog_ = true;
encrypt_dialog_phase_ = EncryptDialogPhase::PassphraseEntry;
encrypt_status_.clear();
memset(encrypt_pass_buf_, 0, sizeof(encrypt_pass_buf_));
memset(encrypt_confirm_buf_, 0, sizeof(encrypt_confirm_buf_));
memset(enc_dlg_pin_buf_, 0, sizeof(enc_dlg_pin_buf_));
memset(enc_dlg_pin_confirm_buf_, 0, sizeof(enc_dlg_pin_confirm_buf_));
enc_dlg_saved_passphrase_.clear();
enc_dlg_pin_status_.clear();
}
void showChangePassphraseDialog() { show_change_passphrase_ = true; }
void showDecryptDialog() {
show_decrypt_dialog_ = true;
decrypt_phase_ = 0; // passphrase entry
decrypt_status_.clear();
decrypt_in_progress_ = false;
memset(decrypt_pass_buf_, 0, sizeof(decrypt_pass_buf_));
}
// Dialog triggers for PIN (from settings page)
void showPinSetupDialog() { show_pin_setup_ = true; pin_status_.clear(); }
void showPinChangeDialog() { show_pin_change_ = true; pin_status_.clear(); }
void showPinRemoveDialog() { show_pin_remove_ = true; pin_status_.clear(); }
bool hasPinVault() const;
/// @brief Check if RPC worker has queued results waiting to be processed
bool hasPendingRPCResults() const;
private:
// Subsystems
std::unique_ptr<rpc::RPCClient> rpc_;
std::unique_ptr<rpc::RPCWorker> worker_;
std::unique_ptr<config::Settings> settings_;
std::unique_ptr<daemon::EmbeddedDaemon> embedded_daemon_;
std::unique_ptr<daemon::XmrigManager> xmrig_manager_;
bool pending_antivirus_dialog_ = false; // Show Windows Defender help dialog
// Wallet state
WalletState state_;
// Shutdown state
std::atomic<bool> shutting_down_{false};
std::atomic<bool> shutdown_complete_{false};
std::atomic<bool> refresh_in_progress_{false};
bool address_list_dirty_ = false; // P8: dedup rebuildAddressList
std::string shutdown_status_;
std::thread shutdown_thread_;
float shutdown_timer_ = 0.0f;
std::chrono::steady_clock::time_point shutdown_start_time_;
// Daemon restart (e.g. after changing debug log categories)
std::atomic<bool> daemon_restarting_{false};
std::thread daemon_restart_thread_;
// UI State
bool quit_requested_ = false;
bool show_demo_window_ = false;
bool show_settings_ = false;
bool show_about_ = false;
bool show_import_key_ = false;
bool show_export_key_ = false;
bool show_backup_ = false;
bool show_address_book_ = false;
// Embedded daemon state
bool use_embedded_daemon_ = true;
std::string daemon_status_;
mutable std::string daemon_mem_diag_; // diagnostic info for daemon memory detection
// Export/Import state
std::string export_result_;
char import_key_input_[512] = {0};
std::string export_address_;
std::string import_status_;
bool import_success_ = false;
std::string backup_status_;
bool backup_success_ = false;
// Connection
std::string connection_status_ = "Disconnected";
bool connection_in_progress_ = false;
float loading_timer_ = 0.0f; // spinner animation for loading overlay
// Current page (sidebar navigation)
ui::NavPage current_page_ = ui::NavPage::Overview;
ui::NavPage prev_page_ = ui::NavPage::Overview;
float page_alpha_ = 1.0f; // 0→1 fade on page switch
bool sidebar_collapsed_ = false; // true = icon-only mode
bool sidebar_user_toggled_ = false; // user manually toggled — suppress auto-collapse
float sidebar_width_anim_ = 0.0f; // animated width (0 = uninitialized)
float prev_dpi_scale_ = 0.0f; // detect DPI changes to snap sidebar width
// Background gradient overlay
ImTextureID gradient_tex_ = 0;
// Logo texture (reload on skin change / dark↔light switch)
ImTextureID logo_tex_ = 0;
int logo_w_ = 0;
int logo_h_ = 0;
bool logo_loaded_ = false;
bool logo_is_dark_variant_ = true; // tracks which variant is currently loaded
// Coin logo texture (DragonX currency icon, separate from wallet branding)
ImTextureID coin_logo_tex_ = 0;
int coin_logo_w_ = 0;
int coin_logo_h_ = 0;
bool coin_logo_loaded_ = false;
// Console tab
ui::ConsoleTab console_tab_;
// Pending payment from URI
bool pending_payment_valid_ = false;
std::string pending_to_address_;
double pending_amount_ = 0.0;
std::string pending_memo_;
std::string pending_label_;
// Timers (in seconds since last update)
float refresh_timer_ = 0.0f;
float price_timer_ = 0.0f;
float fast_refresh_timer_ = 0.0f; // For mining stats
// Refresh intervals (seconds)
static constexpr float REFRESH_INTERVAL = 5.0f;
static constexpr float PRICE_INTERVAL = 60.0f;
static constexpr float FAST_REFRESH_INTERVAL = 1.0f;
// Mining refresh guard (prevents worker queue pileup)
std::atomic<bool> mining_refresh_in_progress_{false};
int mining_slow_counter_ = 0; // counts fast ticks; fires slow refresh every N
// Mining toggle guard (prevents concurrent setgenerate calls)
std::atomic<bool> mining_toggle_in_progress_{false};
// Auto-shield guard (prevents concurrent auto-shield operations)
std::atomic<bool> auto_shield_pending_{false};
// P4: Incremental transaction cache
int last_tx_block_height_ = -1; // block height at last full tx fetch
// Dirty flags for demand-driven refresh
bool addresses_dirty_ = true; // true → refreshAddresses() will run
bool encryption_state_prefetched_ = false; // suppress duplicate getwalletinfo on connect
// First-run wizard state
WizardPhase wizard_phase_ = WizardPhase::None;
std::unique_ptr<util::Bootstrap> bootstrap_;
std::string wizard_pending_passphrase_; // held until daemon connects
std::string wizard_saved_passphrase_; // held until PinSetup completes/skipped
// Deferred encryption (wizard background task)
std::string deferred_encrypt_passphrase_;
std::string deferred_encrypt_pin_;
bool deferred_encrypt_pending_ = false;
// Wizard: stopping an external daemon before bootstrap
bool wizard_stopping_external_ = false;
std::string wizard_stop_status_;
std::thread wizard_stop_thread_;
// PIN vault
std::unique_ptr<util::SecureVault> vault_;
// Lock screen state
bool lock_screen_was_visible_ = false; // tracks lock→unlock transitions for auto-focus
bool lock_use_pin_ = true; // true = PIN input, false = passphrase input
char lock_pin_buf_[16] = {};
char lock_passphrase_buf_[256] = {};
std::string lock_error_msg_;
float lock_error_timer_ = 0.0f;
int lock_attempts_ = 0;
float lock_lockout_timer_ = 0.0f;
bool lock_unlock_in_progress_ = false;
// Encrypt wallet dialog state
bool show_encrypt_dialog_ = false;
bool show_change_passphrase_ = false;
EncryptDialogPhase encrypt_dialog_phase_ = EncryptDialogPhase::PassphraseEntry;
char encrypt_pass_buf_[256] = {};
char encrypt_confirm_buf_[256] = {};
char change_old_pass_buf_[256] = {};
char change_new_pass_buf_[256] = {};
char change_confirm_buf_[256] = {};
std::string encrypt_status_;
bool encrypt_in_progress_ = false;
std::string enc_dlg_saved_passphrase_; // held for PIN setup after encrypt
char enc_dlg_pin_buf_[16] = {};
char enc_dlg_pin_confirm_buf_[16] = {};
std::string enc_dlg_pin_status_;
// PIN setup dialog state (settings page)
bool show_pin_setup_ = false;
bool show_pin_change_ = false;
bool show_pin_remove_ = false;
char pin_buf_[16] = {};
char pin_confirm_buf_[16] = {};
char pin_old_buf_[16] = {};
char pin_passphrase_buf_[256] = {};
std::string pin_status_;
bool pin_in_progress_ = false;
// Decrypt wallet dialog state
bool show_decrypt_dialog_ = false;
int decrypt_phase_ = 0; // 0=passphrase, 1=working, 2=done, 3=error
char decrypt_pass_buf_[256] = {};
std::string decrypt_status_;
bool decrypt_in_progress_ = false;
// Wizard PIN setup state
char wizard_pin_buf_[16] = {};
char wizard_pin_confirm_buf_[16] = {};
std::string wizard_pin_status_;
// Auto-lock on idle
std::chrono::steady_clock::time_point last_interaction_ = std::chrono::steady_clock::now();
// Private methods - rendering
void renderStatusBar();
void renderAboutDialog();
void renderImportKeyDialog();
void renderExportKeyDialog();
void renderBackupDialog();
void renderFirstRunWizard();
void renderLockScreen();
void renderEncryptWalletDialog();
void renderDecryptWalletDialog();
void renderPinDialogs();
void renderAntivirusHelpDialog();
void processDeferredEncryption();
// Private methods - connection
void tryConnect();
void onConnected();
void onDisconnected(const std::string& reason);
// Private methods - data refresh
void refreshData();
void refreshBalance();
void refreshAddresses();
void refreshTransactions();
void refreshPrice();
void refreshWalletEncryptionState();
void checkAutoLock();
};
} // namespace dragonx