Files
ObsidianDragon/src/data/wallet_state.h
DanS 0bf80d2757 feat(node): show "node initializing" feedback when the daemon isn't answering yet
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>
2026-06-09 19:32:35 -05:00

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