When the full-node connect probe (getinfo) times out, the daemon is reachable
at the TCP level but busy initializing (loading the block index, verifying,
activating best chain, …) and won't answer RPC. The wallet only recognized the
JSON-RPC -28 warmup reply, so a raw socket timeout fell through to a bare,
alarming "Connection failed" retry with no indication of what the user was
waiting on.
Add a daemon-initializing UI state that drives the existing loading overlay:
- WalletState::daemon_initializing — daemon up/launching but not serving yet
(distinct from warming_up, which needs a -28 reply).
- App::applyDaemonInitStatus() infers the current phase from the daemon's own
console output (scanning recent lines for Loading/Verifying/Activating/
Rescanning/Rewinding/Pruning) and the latest block height, producing a
friendly title + description, e.g. "Processing blocks… (Block 123456)".
- The connect loop calls it from the daemon-starting and external-detected
branches: a timeout -> "reachable but initializing", a connect refusal ->
"launching, waiting to come online". Cleared on a real connect.
- The loading overlay now shows the description for daemon_initializing too,
and the status-bar amber indicator covers it (so Peers/Console tabs without
the overlay still explain the wait).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
309 lines
9.1 KiB
C++
309 lines
9.1 KiB
C++
// DragonX Wallet - ImGui Edition
|
|
// Copyright 2024-2026 The Hush Developers
|
|
// Released under the GPLv3
|
|
|
|
#pragma once
|
|
|
|
#include <string>
|
|
#include <vector>
|
|
#include <cstdint>
|
|
#include <chrono>
|
|
|
|
namespace dragonx {
|
|
|
|
/**
|
|
* @brief Represents an address with its balance
|
|
*/
|
|
struct AddressInfo {
|
|
std::string address;
|
|
double balance = 0.0;
|
|
std::string type; // "shielded" or "transparent"
|
|
bool has_spending_key = true; // false for view-only (imported via z_importviewingkey)
|
|
|
|
// For display
|
|
std::string label;
|
|
|
|
// Derived
|
|
bool isZAddr() const { return !address.empty() && address[0] == 'z'; }
|
|
bool isShielded() const { return type == "shielded"; }
|
|
bool isSpendable() const { return has_spending_key; }
|
|
};
|
|
|
|
std::vector<size_t> sortedSpendableAddressIndices(const std::vector<AddressInfo>& addresses,
|
|
bool requirePositiveBalance = true);
|
|
int bestSpendableAddressIndex(const std::vector<AddressInfo>& addresses);
|
|
|
|
/**
|
|
* @brief Represents a wallet transaction
|
|
*/
|
|
struct TransactionInfo {
|
|
std::string txid;
|
|
std::string type; // "send", "receive", "mined"
|
|
double amount = 0.0;
|
|
int64_t timestamp = 0; // Unix timestamp
|
|
int confirmations = 0;
|
|
std::string address; // destination (send) or source (receive)
|
|
std::string from_address; // source address for sends
|
|
std::string memo;
|
|
|
|
// Computed fields
|
|
std::string getTimeString() const;
|
|
std::string getTypeDisplay() const;
|
|
bool isConfirmed() const { return confirmations >= 1; }
|
|
bool isMature() const { return confirmations >= 100; }
|
|
};
|
|
|
|
/**
|
|
* @brief Represents a connected peer
|
|
*/
|
|
struct PeerInfo {
|
|
int id = 0;
|
|
std::string addr;
|
|
std::string subver;
|
|
std::string services;
|
|
int version = 0;
|
|
int64_t conntime = 0;
|
|
int banscore = 0;
|
|
double pingtime = 0.0;
|
|
int64_t bytessent = 0;
|
|
int64_t bytesrecv = 0;
|
|
int startingheight = 0;
|
|
int synced_headers = 0;
|
|
int synced_blocks = 0;
|
|
bool inbound = false;
|
|
|
|
// TLS info
|
|
std::string tls_cipher;
|
|
bool tls_verified = false;
|
|
|
|
std::string getConnectionTime() const;
|
|
};
|
|
|
|
/**
|
|
* @brief Represents a banned peer
|
|
*/
|
|
struct BannedPeer {
|
|
std::string address;
|
|
std::string subnet;
|
|
int64_t banned_until = 0;
|
|
|
|
std::string getBannedUntilString() const;
|
|
};
|
|
|
|
/**
|
|
* @brief Mining statistics
|
|
*/
|
|
struct MiningInfo {
|
|
bool generate = false;
|
|
int genproclimit = 0; // -1 means max CPUs
|
|
double localHashrate = 0.0; // Local hashrate (H/s) from getlocalsolps RPC (RandomX)
|
|
double networkHashrate = 0.0; // Network hashrate (H/s)
|
|
int blocks = 0;
|
|
double difficulty = 0.0;
|
|
std::string chain;
|
|
|
|
double daemon_memory_mb = 0.0; // Daemon process RSS in MB
|
|
|
|
// History for chart
|
|
std::vector<double> hashrate_history; // Last N samples
|
|
static constexpr int MAX_HISTORY = 300; // 5 minutes at 1s intervals
|
|
|
|
// Recent daemon log lines for the mining log panel
|
|
std::vector<std::string> log_lines;
|
|
};
|
|
|
|
/**
|
|
* @brief Blockchain synchronization info
|
|
*/
|
|
struct SyncInfo {
|
|
int blocks = 0;
|
|
int headers = 0;
|
|
double verification_progress = 0.0;
|
|
bool syncing = false;
|
|
std::string best_blockhash;
|
|
|
|
// Rescan state (detected from daemon output)
|
|
bool rescanning = false;
|
|
float rescan_progress = 0.0f; // 0.0 - 1.0
|
|
std::string rescan_status; // e.g. "Rescanning... 25%"
|
|
|
|
bool isSynced() const { return !syncing && blocks > 0 && blocks >= headers - 2; }
|
|
};
|
|
|
|
/**
|
|
* @brief Market/price information
|
|
*/
|
|
struct MarketInfo {
|
|
double price_usd = 0.0;
|
|
double price_btc = 0.0;
|
|
double volume_24h = 0.0;
|
|
double change_24h = 0.0;
|
|
double market_cap = 0.0;
|
|
std::string last_updated;
|
|
std::chrono::steady_clock::time_point last_fetch_time{};
|
|
bool price_loading = false;
|
|
std::string price_error;
|
|
|
|
// Price history for chart
|
|
std::vector<double> price_history;
|
|
static constexpr int MAX_HISTORY = 24; // 24 hours
|
|
};
|
|
|
|
/**
|
|
* @brief Pool mining state (from xmrig HTTP API)
|
|
*/
|
|
struct PoolMiningState {
|
|
bool pool_mode = false; // UI toggle: solo vs pool
|
|
bool xmrig_running = false;
|
|
std::string pool_url;
|
|
std::string algo;
|
|
|
|
double hashrate_10s = 0;
|
|
double hashrate_60s = 0;
|
|
double hashrate_15m = 0;
|
|
int64_t accepted = 0;
|
|
int64_t rejected = 0;
|
|
int64_t uptime_sec = 0;
|
|
double pool_diff = 0;
|
|
bool connected = false;
|
|
|
|
// Memory/thread usage (bytes for memory)
|
|
int64_t memory_used = 0;
|
|
int threads_active = 0;
|
|
|
|
// Pool-side hashrate (from pool stats API)
|
|
double pool_hashrate = 0;
|
|
|
|
// Hashrate history for chart (mirrors MiningInfo::hashrate_history)
|
|
std::vector<double> hashrate_history;
|
|
static constexpr int MAX_HISTORY = 60; // 5 minutes at ~5s intervals
|
|
|
|
// Recent log lines for the log panel
|
|
std::vector<std::string> log_lines;
|
|
};
|
|
|
|
/**
|
|
* @brief Complete wallet state - all data fetched from daemon
|
|
*/
|
|
struct WalletState {
|
|
// Connection
|
|
bool connected = false;
|
|
bool warming_up = false; // daemon reachable but in RPC warmup (error -28)
|
|
// True when the daemon is up/launching but not yet answering RPC (e.g. the connect probe
|
|
// times out because the node is loading the block index). Distinct from warming_up, which
|
|
// needs a JSON-RPC -28 reply; here getinfo never returns, so we infer the state from the
|
|
// daemon's launch state + its own console output. Drives the same loading overlay so the
|
|
// user sees WHAT the node is doing instead of a bare "Connection failed".
|
|
bool daemon_initializing = false;
|
|
std::string warmup_status; // user-friendly title, e.g. "Processing blocks..."
|
|
std::string warmup_description; // subtitle explaining the stage
|
|
int daemon_version = 0;
|
|
std::string daemon_subversion;
|
|
int protocol_version = 0;
|
|
int p2p_port = 0;
|
|
int longestchain = 0;
|
|
int notarized = 0;
|
|
|
|
// Sync status
|
|
SyncInfo sync;
|
|
|
|
// Balances (named to match UI usage)
|
|
double privateBalance = 0.0; // shielded balance
|
|
double transparentBalance = 0.0;
|
|
double totalBalance = 0.0;
|
|
double unconfirmedBalance = 0.0;
|
|
|
|
// Aliases for backward compatibility
|
|
double& shielded_balance = privateBalance;
|
|
double& transparent_balance = transparentBalance;
|
|
double& total_balance = totalBalance;
|
|
double& unconfirmed_balance = unconfirmedBalance;
|
|
|
|
// Addresses - combined list for UI convenience
|
|
std::vector<AddressInfo> addresses;
|
|
|
|
// Also keep separate lists for legacy code
|
|
std::vector<AddressInfo> z_addresses;
|
|
std::vector<AddressInfo> t_addresses;
|
|
|
|
// Transactions
|
|
std::vector<TransactionInfo> transactions;
|
|
|
|
// Peers
|
|
std::vector<PeerInfo> peers;
|
|
std::vector<BannedPeer> bannedPeers;
|
|
|
|
// Aliases for banned_peers
|
|
std::vector<BannedPeer>& banned_peers = bannedPeers;
|
|
|
|
// Mining
|
|
MiningInfo mining;
|
|
|
|
// Pool mining (xmrig)
|
|
PoolMiningState pool_mining;
|
|
|
|
// Market
|
|
MarketInfo market;
|
|
|
|
// Wallet encryption state (populated from getwalletinfo)
|
|
bool encrypted = false; // true if wallet has ever been encrypted
|
|
bool locked = false; // true if encrypted && unlocked_until <= now
|
|
int64_t unlocked_until = 0; // 0 = locked, >0 = unix timestamp when auto-lock fires
|
|
bool encryption_state_known = false; // true once first getwalletinfo response processed
|
|
|
|
bool isEncrypted() const { return encrypted; }
|
|
bool isLocked() const { return encrypted && locked; }
|
|
bool isUnlocked() const { return encrypted && !locked; }
|
|
|
|
// Timestamps for refresh logic
|
|
int64_t last_balance_update = 0;
|
|
int64_t last_tx_update = 0;
|
|
int64_t last_peer_update = 0;
|
|
int64_t last_mining_update = 0;
|
|
|
|
// Helper methods
|
|
int getAddressCount() const { return addresses.size(); }
|
|
double getBalanceUSD() const { return totalBalance * market.price_usd; }
|
|
|
|
void clear() {
|
|
connected = false;
|
|
warming_up = false;
|
|
daemon_initializing = false;
|
|
warmup_status.clear();
|
|
warmup_description.clear();
|
|
daemon_version = 0;
|
|
daemon_subversion.clear();
|
|
protocol_version = 0;
|
|
p2p_port = 0;
|
|
longestchain = 0;
|
|
notarized = 0;
|
|
sync = SyncInfo{};
|
|
privateBalance = transparentBalance = totalBalance = 0.0;
|
|
unconfirmedBalance = 0.0;
|
|
encrypted = false;
|
|
locked = false;
|
|
unlocked_until = 0;
|
|
encryption_state_known = false;
|
|
addresses.clear();
|
|
z_addresses.clear();
|
|
t_addresses.clear();
|
|
transactions.clear();
|
|
peers.clear();
|
|
bannedPeers.clear();
|
|
}
|
|
|
|
// Rebuild combined addresses list from z/t lists
|
|
void rebuildAddressList() {
|
|
addresses.clear();
|
|
addresses.reserve(z_addresses.size() + t_addresses.size());
|
|
for (const auto& addr : z_addresses) {
|
|
addresses.push_back(addr);
|
|
}
|
|
for (const auto& addr : t_addresses) {
|
|
addresses.push_back(addr);
|
|
}
|
|
}
|
|
};
|
|
|
|
} // namespace dragonx
|