The connection state machine never tore down on a lost connection: refresh-loop RPC errors were swallowed, rpc_->isConnected() stayed true after a daemon crash/restart/socket drop, and the UI showed stale balances with no reconnect. Several operations also ran synchronous curl straight from ImGui handlers. - Add handleLostConnection(): after N consecutive cycles where BOTH core RPCs fail (warmup excluded, so no reconnect loop), disconnect so update()'s reconnect branch re-enters tryConnect(). - Move banPeer/unbanPeer/clearBans and key export/import onto the worker thread (import requests a rescan that could freeze the UI for the curl timeout). - Run the block-info dialog's two chained RPCs on the worker thread (+ guard the getblockhash result type). - Detect daemon warmup via the JSON-RPC -28 code (new RpcError carrying the code; message text preserved so 401/warmup string-matching is unaffected), and widen CONNECTTIMEOUT to 10s for remote/TLS hosts (2s localhost). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
252 lines
10 KiB
C++
252 lines
10 KiB
C++
// DragonX Wallet - ImGui Edition
|
|
// Copyright 2024-2026 The Hush Developers
|
|
// Released under the GPLv3
|
|
|
|
#pragma once
|
|
|
|
#include "types.h"
|
|
#include <string>
|
|
#include <functional>
|
|
#include <memory>
|
|
#include <mutex>
|
|
#include <stdexcept>
|
|
#include <nlohmann/json.hpp>
|
|
|
|
namespace dragonx {
|
|
namespace rpc {
|
|
|
|
using json = nlohmann::json;
|
|
using Callback = std::function<void(const json&)>;
|
|
using ErrorCallback = std::function<void(const std::string&)>;
|
|
|
|
/**
|
|
* @brief A JSON-RPC error carrying the daemon's numeric error code.
|
|
*
|
|
* what() preserves the exact human-readable message (so existing string matching
|
|
* still works); `code` exposes the JSON-RPC error code — notably -28 (RPC_IN_WARMUP)
|
|
* for a daemon still starting up. Derives from std::runtime_error, so every existing
|
|
* `catch (const std::exception&)` continues to handle it unchanged.
|
|
*/
|
|
class RpcError : public std::runtime_error {
|
|
public:
|
|
RpcError(int errorCode, const std::string& message)
|
|
: std::runtime_error(message), code(errorCode) {}
|
|
int code = 0;
|
|
};
|
|
|
|
/**
|
|
* @brief JSON-RPC client for dragonxd
|
|
*
|
|
* Handles all communication with the dragonxd daemon via JSON-RPC.
|
|
*/
|
|
class RPCClient {
|
|
public:
|
|
using TraceCallback = std::function<void(const std::string& source, const std::string& method)>;
|
|
|
|
class TraceScope {
|
|
public:
|
|
explicit TraceScope(std::string source);
|
|
~TraceScope();
|
|
|
|
TraceScope(const TraceScope&) = delete;
|
|
TraceScope& operator=(const TraceScope&) = delete;
|
|
|
|
private:
|
|
std::string previous_;
|
|
};
|
|
|
|
RPCClient();
|
|
~RPCClient();
|
|
|
|
// Non-copyable
|
|
RPCClient(const RPCClient&) = delete;
|
|
RPCClient& operator=(const RPCClient&) = delete;
|
|
|
|
/**
|
|
* @brief Connect to dragonxd
|
|
* @param host RPC host (default: 127.0.0.1)
|
|
* @param port RPC port (default: 18031)
|
|
* @param user RPC username
|
|
* @param password RPC password
|
|
* @return true if connection successful
|
|
*/
|
|
bool connect(const std::string& host, const std::string& port,
|
|
const std::string& user, const std::string& password);
|
|
|
|
bool connect(const std::string& host, const std::string& port,
|
|
const std::string& user, const std::string& password,
|
|
bool useTls);
|
|
|
|
/**
|
|
* @brief Disconnect from dragonxd
|
|
*/
|
|
void disconnect();
|
|
|
|
/**
|
|
* @brief Check if 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.
|
|
*/
|
|
const std::string& getLastConnectError() const { return last_connect_error_; }
|
|
json getLastConnectInfo() const;
|
|
|
|
static void setTraceCallback(TraceCallback callback);
|
|
static void setTraceEnabled(bool enabled);
|
|
static bool isTraceEnabled();
|
|
static std::string currentTraceSource();
|
|
static void setTraceSource(std::string source);
|
|
|
|
/**
|
|
* @brief Make a raw RPC call
|
|
* @param method RPC method name
|
|
* @param params Method parameters (optional)
|
|
* @return JSON response or null on error
|
|
*/
|
|
json call(const std::string& method, const json& params = json::array());
|
|
|
|
/**
|
|
* @brief Make a raw RPC call with a custom timeout
|
|
* @param method RPC method name
|
|
* @param params Method parameters
|
|
* @param timeoutSec Timeout in seconds (0 = no timeout)
|
|
* @return JSON response or throws on error
|
|
*/
|
|
json call(const std::string& method, const json& params, long timeoutSec);
|
|
|
|
/**
|
|
* @brief Make a raw RPC call and return the result field as a string
|
|
* @param method RPC method name
|
|
* @param params Method parameters (optional)
|
|
* @return Raw JSON string of the "result" field (preserves key order)
|
|
*/
|
|
std::string callRaw(const std::string& method, const json& params = json::array());
|
|
|
|
// High-level API methods - mirror the Qt version
|
|
|
|
// Info methods
|
|
void getInfo(Callback cb, ErrorCallback err = nullptr);
|
|
void getBlockchainInfo(Callback cb, ErrorCallback err = nullptr);
|
|
void getMiningInfo(Callback cb, ErrorCallback err = nullptr);
|
|
|
|
// Balance methods
|
|
void getBalance(Callback cb, ErrorCallback err = nullptr);
|
|
void z_getTotalBalance(Callback cb, ErrorCallback err = nullptr);
|
|
void listUnspent(Callback cb, ErrorCallback err = nullptr);
|
|
void z_listUnspent(Callback cb, ErrorCallback err = nullptr);
|
|
|
|
// Address methods
|
|
void getAddressesByAccount(Callback cb, ErrorCallback err = nullptr);
|
|
void z_listAddresses(Callback cb, ErrorCallback err = nullptr);
|
|
void getNewAddress(Callback cb, ErrorCallback err = nullptr);
|
|
void z_getNewAddress(Callback cb, ErrorCallback err = nullptr);
|
|
|
|
// Transaction methods
|
|
void listTransactions(int count, Callback cb, ErrorCallback err = nullptr);
|
|
void z_viewTransaction(const std::string& txid, Callback cb, ErrorCallback err = nullptr);
|
|
void getRawTransaction(const std::string& txid, Callback cb, ErrorCallback err = nullptr);
|
|
void sendToAddress(const std::string& address, double amount, Callback cb, ErrorCallback err = nullptr);
|
|
void z_sendMany(const std::string& from, const json& recipients, Callback cb, ErrorCallback err = nullptr);
|
|
|
|
// Mining methods
|
|
void setGenerate(bool generate, int threads, Callback cb, ErrorCallback err = nullptr);
|
|
void getNetworkHashPS(Callback cb, ErrorCallback err = nullptr);
|
|
void getLocalHashrate(Callback cb, ErrorCallback err = nullptr);
|
|
|
|
// Peer methods
|
|
void getPeerInfo(Callback cb, ErrorCallback err = nullptr);
|
|
void listBanned(Callback cb, ErrorCallback err = nullptr);
|
|
void setBan(const std::string& ip, const std::string& command, Callback cb, ErrorCallback err = nullptr, int bantime = 86400);
|
|
void clearBanned(Callback cb, ErrorCallback err = nullptr);
|
|
|
|
// Key management
|
|
void dumpPrivKey(const std::string& address, Callback cb, ErrorCallback err = nullptr);
|
|
void z_exportKey(const std::string& address, Callback cb, ErrorCallback err = nullptr);
|
|
void z_exportViewingKey(const std::string& address, Callback cb, ErrorCallback err = nullptr);
|
|
void importPrivKey(const std::string& key, bool rescan, Callback cb, ErrorCallback err = nullptr);
|
|
void z_importKey(const std::string& key, bool rescan, Callback cb, ErrorCallback err = nullptr);
|
|
|
|
// Utility
|
|
void validateAddress(const std::string& address, Callback cb, ErrorCallback err = nullptr);
|
|
void z_validateAddress(const std::string& address, Callback cb, ErrorCallback err = nullptr);
|
|
void getBlock(const std::string& hash_or_height, Callback cb, ErrorCallback err = nullptr);
|
|
void getBlockHash(int height, Callback cb, ErrorCallback err = nullptr);
|
|
void getTransaction(const std::string& txid, Callback cb, ErrorCallback err = nullptr);
|
|
void getWalletInfo(Callback cb, ErrorCallback err = nullptr);
|
|
void stop(Callback cb, ErrorCallback err = nullptr);
|
|
|
|
// Wallet maintenance
|
|
void rescanBlockchain(int startHeight, Callback cb, ErrorCallback err = nullptr);
|
|
|
|
// Wallet encryption & locking
|
|
void encryptWallet(const std::string& passphrase, Callback cb, ErrorCallback err = nullptr);
|
|
void walletPassphrase(const std::string& passphrase, int timeout, Callback cb, ErrorCallback err = nullptr);
|
|
void walletLock(Callback cb, ErrorCallback err = nullptr);
|
|
void walletPassphraseChange(const std::string& oldPass, const std::string& newPass,
|
|
Callback cb, ErrorCallback err = nullptr);
|
|
|
|
// Wallet export/import (for decrypt flow)
|
|
void z_exportWallet(const std::string& filename, Callback cb, ErrorCallback err = nullptr);
|
|
void z_importWallet(const std::string& filename, Callback cb, ErrorCallback err = nullptr);
|
|
|
|
// Shielding operations
|
|
void z_shieldCoinbase(const std::string& fromAddr, const std::string& toAddr,
|
|
double fee, int limit, Callback cb, ErrorCallback err = nullptr);
|
|
void z_mergeToAddress(const std::vector<std::string>& fromAddrs, const std::string& toAddr,
|
|
double fee, int limit, Callback cb, ErrorCallback err = nullptr);
|
|
|
|
// Operation status monitoring
|
|
void z_getOperationStatus(const std::vector<std::string>& opids, Callback cb, ErrorCallback err = nullptr);
|
|
void z_getOperationResult(const std::vector<std::string>& opids, Callback cb, ErrorCallback err = nullptr);
|
|
|
|
// Received transactions
|
|
void z_listReceivedByAddress(const std::string& address, int minconf, Callback cb, ErrorCallback err = nullptr);
|
|
|
|
// Unified callback versions (result + error)
|
|
using UnifiedCallback = std::function<void(const json& result, const std::string& error)>;
|
|
void getInfo(UnifiedCallback cb);
|
|
void rescanBlockchain(int startHeight, UnifiedCallback cb);
|
|
void z_shieldCoinbase(const std::string& fromAddr, const std::string& toAddr,
|
|
double fee, int limit, UnifiedCallback cb);
|
|
void z_mergeToAddress(const std::vector<std::string>& fromAddrs, const std::string& toAddr,
|
|
double fee, int limit, UnifiedCallback cb);
|
|
void z_getOperationStatus(const std::vector<std::string>& opids, UnifiedCallback cb);
|
|
void getBlock(int height, UnifiedCallback cb);
|
|
|
|
private:
|
|
json makePayload(const std::string& method, const json& params = json::array());
|
|
void doRPC(const std::string& method, const json& params, Callback cb, ErrorCallback err);
|
|
|
|
std::string host_;
|
|
std::string port_;
|
|
std::string auth_; // Base64 encoded "user:password"
|
|
bool connected_ = false;
|
|
bool warming_up_ = false;
|
|
std::string warmup_status_;
|
|
std::string last_connect_error_;
|
|
json last_connect_info_;
|
|
mutable std::recursive_mutex curl_mutex_; // serializes all curl handle access
|
|
|
|
// HTTP client (implementation hidden)
|
|
class Impl;
|
|
std::unique_ptr<Impl> impl_;
|
|
};
|
|
|
|
} // namespace rpc
|
|
} // namespace dragonx
|