feat: non-blocking warmup — connect during daemon initialization
Instead of blocking the entire UI with "Activating best chain..." until the daemon finishes warmup, treat warmup responses as a successful connection. The wallet now: - Sets connected=true + warming_up=true when daemon returns RPC -28 - Shows warmup status with block progress in the loading overlay - Polls getinfo every few seconds to detect warmup completion - Allows Console, Peers, Settings tabs during warmup - Shows orange status indicator with warmup message in status bar - Skips balance/tx/address refresh until warmup completes - Triggers full data refresh once daemon is ready Also: fix curl handle/header leak on reconnect, fill in empty externalDetected error branch, bump version to v1.2.0 in build scripts.
This commit is contained in:
2
build.sh
2
build.sh
@@ -20,7 +20,7 @@
|
|||||||
set -e
|
set -e
|
||||||
|
|
||||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
VERSION="1.1.1"
|
VERSION="1.2.0"
|
||||||
|
|
||||||
# ── Colours ──────────────────────────────────────────────────────────────────
|
# ── Colours ──────────────────────────────────────────────────────────────────
|
||||||
RED='\033[0;31m'
|
RED='\033[0;31m'
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ set -e
|
|||||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
BUILD_DIR="${SCRIPT_DIR}/build/linux"
|
BUILD_DIR="${SCRIPT_DIR}/build/linux"
|
||||||
APPDIR="${BUILD_DIR}/AppDir"
|
APPDIR="${BUILD_DIR}/AppDir"
|
||||||
VERSION="1.1.1"
|
VERSION="1.2.0"
|
||||||
|
|
||||||
# Colors
|
# Colors
|
||||||
GREEN='\033[0;32m'
|
GREEN='\033[0;32m'
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ set -e
|
|||||||
|
|
||||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
BUILD_DIR="${SCRIPT_DIR}/build/linux"
|
BUILD_DIR="${SCRIPT_DIR}/build/linux"
|
||||||
VERSION="1.1.1"
|
VERSION="1.2.0"
|
||||||
|
|
||||||
# Colors for output
|
# Colors for output
|
||||||
RED='\033[0;31m'
|
RED='\033[0;31m'
|
||||||
|
|||||||
35
src/app.cpp
35
src/app.cpp
@@ -610,13 +610,16 @@ void App::update()
|
|||||||
core_timer_ = 0.0f;
|
core_timer_ = 0.0f;
|
||||||
refreshCoreData();
|
refreshCoreData();
|
||||||
}
|
}
|
||||||
if (transaction_timer_ >= active_tx_interval_) {
|
// Skip balance/tx/address refresh during warmup — RPC calls fail with -28
|
||||||
transaction_timer_ = 0.0f;
|
if (!state_.warming_up) {
|
||||||
refreshTransactionData();
|
if (transaction_timer_ >= active_tx_interval_) {
|
||||||
}
|
transaction_timer_ = 0.0f;
|
||||||
if (address_timer_ >= active_addr_interval_) {
|
refreshTransactionData();
|
||||||
address_timer_ = 0.0f;
|
}
|
||||||
refreshAddressData();
|
if (address_timer_ >= active_addr_interval_) {
|
||||||
|
address_timer_ = 0.0f;
|
||||||
|
refreshAddressData();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (peer_timer_ >= active_peer_interval_) {
|
if (peer_timer_ >= active_peer_interval_) {
|
||||||
peer_timer_ = 0.0f;
|
peer_timer_ = 0.0f;
|
||||||
@@ -1087,7 +1090,7 @@ void App::render()
|
|||||||
bool pageNeedsDaemon = (current_page_ != ui::NavPage::Console &&
|
bool pageNeedsDaemon = (current_page_ != ui::NavPage::Console &&
|
||||||
current_page_ != ui::NavPage::Peers &&
|
current_page_ != ui::NavPage::Peers &&
|
||||||
current_page_ != ui::NavPage::Settings);
|
current_page_ != ui::NavPage::Settings);
|
||||||
bool daemonReady = state_.connected; // don't gate on sync state
|
bool daemonReady = state_.connected && !state_.warming_up;
|
||||||
|
|
||||||
// Don't show lock screen while pool mining — xmrig runs independently
|
// Don't show lock screen while pool mining — xmrig runs independently
|
||||||
// of the wallet and locking would block the mining UI needlessly.
|
// of the wallet and locking would block the mining UI needlessly.
|
||||||
@@ -1095,6 +1098,11 @@ void App::render()
|
|||||||
if (state_.isLocked() && !poolMiningActive) {
|
if (state_.isLocked() && !poolMiningActive) {
|
||||||
// Lock screen — covers tab content just like the loading overlay
|
// Lock screen — covers tab content just like the loading overlay
|
||||||
renderLockScreen();
|
renderLockScreen();
|
||||||
|
} else if (state_.warming_up) {
|
||||||
|
// Daemon is reachable but still initializing — show warmup overlay
|
||||||
|
// without blocking on encryption state (getwalletinfo fails during warmup)
|
||||||
|
lock_screen_was_visible_ = false;
|
||||||
|
renderLoadingOverlay(contentH);
|
||||||
} else if (pageNeedsDaemon && (!daemonReady || (state_.connected && !state_.encryption_state_known))) {
|
} else if (pageNeedsDaemon && (!daemonReady || (state_.connected && !state_.encryption_state_known))) {
|
||||||
// Track how long we've been waiting for encryption state
|
// Track how long we've been waiting for encryption state
|
||||||
if (state_.connected && !state_.encryption_state_known) {
|
if (state_.connected && !state_.encryption_state_known) {
|
||||||
@@ -1421,7 +1429,16 @@ void App::renderStatusBar()
|
|||||||
// Connection status
|
// Connection status
|
||||||
float dotOpacity = S.drawElement("components.status-bar", "connection-dot").opacity;
|
float dotOpacity = S.drawElement("components.status-bar", "connection-dot").opacity;
|
||||||
if (dotOpacity < 0.0f) dotOpacity = 1.0f;
|
if (dotOpacity < 0.0f) dotOpacity = 1.0f;
|
||||||
if (state_.connected) {
|
if (state_.warming_up) {
|
||||||
|
ImGui::PushFont(ui::material::Type().iconSmall());
|
||||||
|
ImGui::TextColored(ImVec4(1.0f, 0.7f, 0.0f, dotOpacity), ICON_MD_CIRCLE);
|
||||||
|
ImGui::PopFont();
|
||||||
|
ImGui::SameLine(0, sbIconTextGap);
|
||||||
|
// Show truncated warmup status (e.g. "Activating best chain... (Block 12345)")
|
||||||
|
const char* warmupText = state_.warmup_status.empty()
|
||||||
|
? "Warming up..." : state_.warmup_status.c_str();
|
||||||
|
ImGui::TextColored(ImVec4(1.0f, 0.8f, 0.0f, 1.0f), "%s", warmupText);
|
||||||
|
} else if (state_.connected) {
|
||||||
ImGui::PushFont(ui::material::Type().iconSmall());
|
ImGui::PushFont(ui::material::Type().iconSmall());
|
||||||
ImGui::TextColored(ImVec4(0.2f, 0.8f, 0.2f, dotOpacity), ICON_MD_CIRCLE);
|
ImGui::TextColored(ImVec4(0.2f, 0.8f, 0.2f, dotOpacity), ICON_MD_CIRCLE);
|
||||||
ImGui::PopFont();
|
ImGui::PopFont();
|
||||||
|
|||||||
@@ -150,12 +150,29 @@ void App::tryConnect()
|
|||||||
worker_->post([this, config, daemonStarting, externalDetected, attempt]() -> rpc::RPCWorker::MainCb {
|
worker_->post([this, config, daemonStarting, externalDetected, attempt]() -> rpc::RPCWorker::MainCb {
|
||||||
bool connected = rpc_->connect(config.host, config.port, config.rpcuser, config.rpcpassword);
|
bool connected = rpc_->connect(config.host, config.port, config.rpcuser, config.rpcpassword);
|
||||||
std::string connectErr = rpc_->getLastConnectError();
|
std::string connectErr = rpc_->getLastConnectError();
|
||||||
|
bool warmingUp = rpc_->isWarmingUp();
|
||||||
|
std::string warmupStatus = rpc_->getWarmupStatus();
|
||||||
|
|
||||||
return [this, config, connected, daemonStarting, externalDetected, attempt, connectErr]() {
|
return [this, config, connected, warmingUp, warmupStatus, daemonStarting, externalDetected, attempt, connectErr]() {
|
||||||
if (connected) {
|
if (connected) {
|
||||||
VERBOSE_LOGF("[connect #%d] Connected successfully\n", attempt);
|
VERBOSE_LOGF("[connect #%d] Connected successfully%s\n", attempt,
|
||||||
|
warmingUp ? " (daemon warming up)" : "");
|
||||||
saved_config_ = config; // save for fast-lane connection
|
saved_config_ = config; // save for fast-lane connection
|
||||||
onConnected();
|
onConnected();
|
||||||
|
if (warmingUp) {
|
||||||
|
// Daemon is reachable and auth works, but RPC calls will
|
||||||
|
// fail until warmup completes. Set the warmup state so
|
||||||
|
// the UI shows status instead of a blocking overlay.
|
||||||
|
state_.warming_up = true;
|
||||||
|
state_.warmup_status = warmupStatus;
|
||||||
|
// Append current block height from daemon output
|
||||||
|
if (embedded_daemon_) {
|
||||||
|
int h = embedded_daemon_->getLastBlockHeight();
|
||||||
|
if (h > 0)
|
||||||
|
state_.warmup_status += " (Block " + std::to_string(h) + ")";
|
||||||
|
}
|
||||||
|
connection_status_ = state_.warmup_status;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// HTTP 401 = authentication failure. The daemon is running
|
// HTTP 401 = authentication failure. The daemon is running
|
||||||
// but our rpcuser/rpcpassword don't match. Don't retry
|
// but our rpcuser/rpcpassword don't match. Don't retry
|
||||||
@@ -203,18 +220,6 @@ void App::tryConnect()
|
|||||||
"RPC authentication failed (HTTP 401). "
|
"RPC authentication failed (HTTP 401). "
|
||||||
"The rpcuser/rpcpassword in DRAGONX.conf don't match the running daemon. "
|
"The rpcuser/rpcpassword in DRAGONX.conf don't match the running daemon. "
|
||||||
"Restart the daemon or correct the credentials.");
|
"Restart the daemon or correct the credentials.");
|
||||||
} else if (connectErr.find("Loading") != std::string::npos ||
|
|
||||||
connectErr.find("Verifying") != std::string::npos ||
|
|
||||||
connectErr.find("Activating") != std::string::npos ||
|
|
||||||
connectErr.find("Rewinding") != std::string::npos ||
|
|
||||||
connectErr.find("Rescanning") != std::string::npos ||
|
|
||||||
connectErr.find("Pruning") != std::string::npos) {
|
|
||||||
// Daemon is reachable but still in warmup (Loading block index, etc.)
|
|
||||||
// Check this BEFORE daemonStarting so the actual warmup status is shown.
|
|
||||||
state_.connected = false;
|
|
||||||
connection_status_ = connectErr;
|
|
||||||
VERBOSE_LOGF("[connect #%d] Daemon warmup: %s\n", attempt, connectErr.c_str());
|
|
||||||
core_timer_ = CORE_INTERVAL_DEFAULT - 1.0f;
|
|
||||||
} else if (daemonStarting) {
|
} else if (daemonStarting) {
|
||||||
state_.connected = false;
|
state_.connected = false;
|
||||||
// Show the actual RPC error alongside the waiting message so
|
// Show the actual RPC error alongside the waiting message so
|
||||||
@@ -228,6 +233,15 @@ void App::tryConnect()
|
|||||||
attempt, connectErr.c_str());
|
attempt, connectErr.c_str());
|
||||||
core_timer_ = CORE_INTERVAL_DEFAULT - 1.0f;
|
core_timer_ = CORE_INTERVAL_DEFAULT - 1.0f;
|
||||||
} else if (externalDetected) {
|
} else if (externalDetected) {
|
||||||
|
state_.connected = false;
|
||||||
|
if (!connectErr.empty()) {
|
||||||
|
connection_status_ = "Connecting to daemon — " + connectErr;
|
||||||
|
} else {
|
||||||
|
connection_status_ = "Connecting to external daemon...";
|
||||||
|
}
|
||||||
|
VERBOSE_LOGF("[connect #%d] External daemon detected but RPC failed (%s), will retry...\n",
|
||||||
|
attempt, connectErr.c_str());
|
||||||
|
core_timer_ = CORE_INTERVAL_DEFAULT - 1.0f;
|
||||||
} else {
|
} else {
|
||||||
onDisconnected("Connection failed");
|
onDisconnected("Connection failed");
|
||||||
VERBOSE_LOGF("[connect #%d] RPC connection failed — no daemon starting, no external detected\n", attempt);
|
VERBOSE_LOGF("[connect #%d] RPC connection failed — no daemon starting, no external detected\n", attempt);
|
||||||
@@ -376,6 +390,8 @@ void App::onConnected()
|
|||||||
void App::onDisconnected(const std::string& reason)
|
void App::onDisconnected(const std::string& reason)
|
||||||
{
|
{
|
||||||
state_.connected = false;
|
state_.connected = false;
|
||||||
|
state_.warming_up = false;
|
||||||
|
state_.warmup_status.clear();
|
||||||
state_.clear();
|
state_.clear();
|
||||||
connection_status_ = reason;
|
connection_status_ = reason;
|
||||||
|
|
||||||
@@ -490,6 +506,13 @@ void App::refreshData()
|
|||||||
{
|
{
|
||||||
if (!state_.connected || !rpc_ || !worker_) return;
|
if (!state_.connected || !rpc_ || !worker_) return;
|
||||||
|
|
||||||
|
// During warmup, only poll for warmup completion via refreshCoreData.
|
||||||
|
// Other RPC calls (balance, addresses, transactions) will fail with -28.
|
||||||
|
if (state_.warming_up) {
|
||||||
|
refreshCoreData();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Dispatch each category independently — results trickle into the UI
|
// Dispatch each category independently — results trickle into the UI
|
||||||
// as each completes, rather than waiting for the slowest phase.
|
// as each completes, rather than waiting for the slowest phase.
|
||||||
refreshCoreData();
|
refreshCoreData();
|
||||||
@@ -518,6 +541,57 @@ void App::refreshCoreData()
|
|||||||
{
|
{
|
||||||
if (!state_.connected) return;
|
if (!state_.connected) return;
|
||||||
|
|
||||||
|
// During warmup, poll getinfo to detect when warmup ends.
|
||||||
|
// Most RPC calls (balance, blockchain info) will fail with -28 during warmup.
|
||||||
|
if (state_.warming_up) {
|
||||||
|
if (core_refresh_in_progress_.exchange(true)) return;
|
||||||
|
worker_->post([this]() -> rpc::RPCWorker::MainCb {
|
||||||
|
json info;
|
||||||
|
bool ok = false;
|
||||||
|
std::string errMsg;
|
||||||
|
try {
|
||||||
|
info = rpc_->call("getinfo");
|
||||||
|
ok = true;
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
errMsg = e.what();
|
||||||
|
}
|
||||||
|
return [this, info, ok, errMsg]() {
|
||||||
|
if (ok) {
|
||||||
|
// Warmup finished — daemon is fully ready
|
||||||
|
state_.warming_up = false;
|
||||||
|
state_.warmup_status.clear();
|
||||||
|
connection_status_ = "Connected";
|
||||||
|
VERBOSE_LOGF("[warmup] Daemon ready, warmup complete\n");
|
||||||
|
// Parse initial info
|
||||||
|
try {
|
||||||
|
if (info.contains("version"))
|
||||||
|
state_.daemon_version = info["version"].get<int>();
|
||||||
|
if (info.contains("blocks"))
|
||||||
|
state_.sync.blocks = info["blocks"].get<int>();
|
||||||
|
if (info.contains("longestchain")) {
|
||||||
|
int lc = info["longestchain"].get<int>();
|
||||||
|
if (lc > 0) state_.longestchain = lc;
|
||||||
|
}
|
||||||
|
} catch (...) {}
|
||||||
|
// Trigger full data refresh now that daemon is ready
|
||||||
|
refreshData();
|
||||||
|
} else {
|
||||||
|
// Still warming up — update status
|
||||||
|
state_.warmup_status = errMsg;
|
||||||
|
if (embedded_daemon_) {
|
||||||
|
int h = embedded_daemon_->getLastBlockHeight();
|
||||||
|
if (h > 0)
|
||||||
|
state_.warmup_status += " (Block " + std::to_string(h) + ")";
|
||||||
|
}
|
||||||
|
connection_status_ = state_.warmup_status;
|
||||||
|
VERBOSE_LOGF("[warmup] Still warming up: %s\n", errMsg.c_str());
|
||||||
|
}
|
||||||
|
core_refresh_in_progress_.store(false, std::memory_order_release);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Use fast-lane on Overview for snappier balance updates
|
// Use fast-lane on Overview for snappier balance updates
|
||||||
bool useFast = (current_page_ == ui::NavPage::Overview);
|
bool useFast = (current_page_ == ui::NavPage::Overview);
|
||||||
auto* w = useFast && fast_worker_ && fast_worker_->isRunning()
|
auto* w = useFast && fast_worker_ && fast_worker_->isRunning()
|
||||||
|
|||||||
@@ -116,6 +116,26 @@ public:
|
|||||||
* @brief Get last N lines of daemon output (thread-safe snapshot)
|
* @brief Get last N lines of daemon output (thread-safe snapshot)
|
||||||
*/
|
*/
|
||||||
std::vector<std::string> getRecentLines(int maxLines = 8) const;
|
std::vector<std::string> getRecentLines(int maxLines = 8) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Extract the latest block height from daemon output (thread-safe).
|
||||||
|
* Parses the last "height=N" from UpdateTip lines without copying
|
||||||
|
* the entire output buffer. Returns -1 if no UpdateTip found.
|
||||||
|
*/
|
||||||
|
int getLastBlockHeight() const {
|
||||||
|
std::lock_guard<std::mutex> lk(output_mutex_);
|
||||||
|
// Search backwards from the end for "height="
|
||||||
|
size_t pos = process_output_.rfind("height=");
|
||||||
|
if (pos == std::string::npos) return -1;
|
||||||
|
pos += 7; // skip "height="
|
||||||
|
int h = 0;
|
||||||
|
for (size_t i = pos; i < process_output_.size(); ++i) {
|
||||||
|
char c = process_output_[i];
|
||||||
|
if (c >= '0' && c <= '9') h = h * 10 + (c - '0');
|
||||||
|
else break;
|
||||||
|
}
|
||||||
|
return h > 0 ? h : -1;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Whether start() detected an existing daemon on the RPC port.
|
* @brief Whether start() detected an existing daemon on the RPC port.
|
||||||
|
|||||||
@@ -180,6 +180,8 @@ struct PoolMiningState {
|
|||||||
struct WalletState {
|
struct WalletState {
|
||||||
// Connection
|
// Connection
|
||||||
bool connected = false;
|
bool connected = false;
|
||||||
|
bool warming_up = false; // daemon reachable but in RPC warmup (error -28)
|
||||||
|
std::string warmup_status; // e.g. "Activating best chain..."
|
||||||
int daemon_version = 0;
|
int daemon_version = 0;
|
||||||
std::string daemon_subversion;
|
std::string daemon_subversion;
|
||||||
int protocol_version = 0;
|
int protocol_version = 0;
|
||||||
@@ -250,6 +252,8 @@ struct WalletState {
|
|||||||
|
|
||||||
void clear() {
|
void clear() {
|
||||||
connected = false;
|
connected = false;
|
||||||
|
warming_up = false;
|
||||||
|
warmup_status.clear();
|
||||||
daemon_version = 0;
|
daemon_version = 0;
|
||||||
daemon_subversion.clear();
|
daemon_subversion.clear();
|
||||||
protocol_version = 0;
|
protocol_version = 0;
|
||||||
|
|||||||
@@ -73,6 +73,16 @@ bool RPCClient::connect(const std::string& host, const std::string& port,
|
|||||||
impl_->url = "http://" + host + ":" + port + "/";
|
impl_->url = "http://" + host + ":" + port + "/";
|
||||||
VERBOSE_LOGF("Connecting to dragonxd at %s\n", impl_->url.c_str());
|
VERBOSE_LOGF("Connecting to dragonxd at %s\n", impl_->url.c_str());
|
||||||
|
|
||||||
|
// Clean up previous curl handle/headers to avoid leaks on retries
|
||||||
|
if (impl_->headers) {
|
||||||
|
curl_slist_free_all(impl_->headers);
|
||||||
|
impl_->headers = nullptr;
|
||||||
|
}
|
||||||
|
if (impl_->curl) {
|
||||||
|
curl_easy_cleanup(impl_->curl);
|
||||||
|
impl_->curl = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
// Initialize curl handle
|
// Initialize curl handle
|
||||||
impl_->curl = curl_easy_init();
|
impl_->curl = curl_easy_init();
|
||||||
if (!impl_->curl) {
|
if (!impl_->curl) {
|
||||||
@@ -81,7 +91,7 @@ bool RPCClient::connect(const std::string& host, const std::string& port,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Set up headers - daemon expects text/plain, not application/json
|
// Set up headers - daemon expects text/plain, not application/json
|
||||||
impl_->headers = curl_slist_append(impl_->headers, "Content-Type: text/plain");
|
impl_->headers = curl_slist_append(nullptr, "Content-Type: text/plain");
|
||||||
std::string auth_header = "Authorization: Basic " + auth_;
|
std::string auth_header = "Authorization: Basic " + auth_;
|
||||||
impl_->headers = curl_slist_append(impl_->headers, auth_header.c_str());
|
impl_->headers = curl_slist_append(impl_->headers, auth_header.c_str());
|
||||||
|
|
||||||
@@ -97,6 +107,8 @@ bool RPCClient::connect(const std::string& host, const std::string& port,
|
|||||||
json result = call("getinfo");
|
json result = call("getinfo");
|
||||||
if (result.contains("version")) {
|
if (result.contains("version")) {
|
||||||
connected_ = true;
|
connected_ = true;
|
||||||
|
warming_up_ = false;
|
||||||
|
warmup_status_.clear();
|
||||||
last_connect_error_.clear();
|
last_connect_error_.clear();
|
||||||
DEBUG_LOGF("Connected to dragonxd v%d\n", result["version"].get<int>());
|
DEBUG_LOGF("Connected to dragonxd v%d\n", result["version"].get<int>());
|
||||||
return true;
|
return true;
|
||||||
@@ -104,7 +116,9 @@ bool RPCClient::connect(const std::string& host, const std::string& port,
|
|||||||
} catch (const std::exception& e) {
|
} catch (const std::exception& e) {
|
||||||
last_connect_error_ = e.what();
|
last_connect_error_ = e.what();
|
||||||
// Daemon warmup messages (Loading block index, Verifying blocks, etc.)
|
// Daemon warmup messages (Loading block index, Verifying blocks, etc.)
|
||||||
// are normal startup progress — don't label them "Connection failed".
|
// are normal startup progress — the daemon is reachable and auth works,
|
||||||
|
// it just hasn't finished initializing yet. Mark as connected+warmup
|
||||||
|
// so the wallet can show the UI instead of a blocking overlay.
|
||||||
std::string msg = e.what();
|
std::string msg = e.what();
|
||||||
bool isWarmup = (msg.find("Loading") != std::string::npos ||
|
bool isWarmup = (msg.find("Loading") != std::string::npos ||
|
||||||
msg.find("Verifying") != std::string::npos ||
|
msg.find("Verifying") != std::string::npos ||
|
||||||
@@ -113,13 +127,19 @@ bool RPCClient::connect(const std::string& host, const std::string& port,
|
|||||||
msg.find("Rescanning") != std::string::npos ||
|
msg.find("Rescanning") != std::string::npos ||
|
||||||
msg.find("Pruning") != std::string::npos);
|
msg.find("Pruning") != std::string::npos);
|
||||||
if (isWarmup) {
|
if (isWarmup) {
|
||||||
DEBUG_LOGF("Daemon starting: %s\n", msg.c_str());
|
connected_ = true;
|
||||||
|
warming_up_ = true;
|
||||||
|
warmup_status_ = msg;
|
||||||
|
DEBUG_LOGF("Daemon warming up: %s\n", msg.c_str());
|
||||||
|
return true;
|
||||||
} else {
|
} else {
|
||||||
DEBUG_LOGF("Connection failed: %s\n", msg.c_str());
|
DEBUG_LOGF("Connection failed: %s\n", msg.c_str());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
connected_ = false;
|
connected_ = false;
|
||||||
|
warming_up_ = false;
|
||||||
|
warmup_status_.clear();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -127,6 +147,8 @@ void RPCClient::disconnect()
|
|||||||
{
|
{
|
||||||
std::lock_guard<std::recursive_mutex> lk(curl_mutex_);
|
std::lock_guard<std::recursive_mutex> lk(curl_mutex_);
|
||||||
connected_ = false;
|
connected_ = false;
|
||||||
|
warming_up_ = false;
|
||||||
|
warmup_status_.clear();
|
||||||
if (impl_->curl) {
|
if (impl_->curl) {
|
||||||
curl_easy_cleanup(impl_->curl);
|
curl_easy_cleanup(impl_->curl);
|
||||||
impl_->curl = nullptr;
|
impl_->curl = nullptr;
|
||||||
|
|||||||
@@ -53,6 +53,19 @@ public:
|
|||||||
*/
|
*/
|
||||||
bool isConnected() const { return connected_; }
|
bool isConnected() const { return connected_; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief True if the last connect() succeeded but daemon returned a warmup error.
|
||||||
|
* The curl handle is valid and auth succeeded — RPC calls will throw warmup errors
|
||||||
|
* until the daemon finishes initializing.
|
||||||
|
*/
|
||||||
|
bool isWarmingUp() const { return warming_up_; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief The warmup status message (e.g. "Activating best chain...").
|
||||||
|
* Empty when not in warmup.
|
||||||
|
*/
|
||||||
|
const std::string& getWarmupStatus() const { return warmup_status_; }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Get the error message from the last failed connect() attempt.
|
* @brief Get the error message from the last failed connect() attempt.
|
||||||
*/
|
*/
|
||||||
@@ -182,6 +195,8 @@ private:
|
|||||||
std::string port_;
|
std::string port_;
|
||||||
std::string auth_; // Base64 encoded "user:password"
|
std::string auth_; // Base64 encoded "user:password"
|
||||||
bool connected_ = false;
|
bool connected_ = false;
|
||||||
|
bool warming_up_ = false;
|
||||||
|
std::string warmup_status_;
|
||||||
std::string last_connect_error_;
|
std::string last_connect_error_;
|
||||||
mutable std::recursive_mutex curl_mutex_; // serializes all curl handle access
|
mutable std::recursive_mutex curl_mutex_; // serializes all curl handle access
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user