Files
ObsidianDragon/src/rpc/rpc_client.h
DanS 08f5704c45 feat(wallet): persist history and surface pending sends
Add an encrypted SQLite transaction history cache with cached tip metadata and
per-address shielded scan progress so startup and full refreshes avoid
re-scanning every z-address while still invalidating on wallet/address/rescan
changes.

Improve wallet history loading by paging transparent transactions, preserving
cached shielded and sent rows, keeping recent/unconfirmed activity visible, and
classifying mining-address receives. Show z_sendmany opid sends immediately in
History and Overview, pin pending rows through refreshes, and apply optimistic
address/balance debits until opids resolve.

Add timestamped RPC console tracing by source/method without logging params or
results, reduce redundant refresh/RPC calls, and cache Explorer recent block
summaries in SQLite.

Expand focused tests for transaction cache encryption, scan-progress
persistence/invalidation, history preservation, operation-status parsing,
pending send visibility, and Explorer/RPC refresh behavior.
2026-05-05 03:22:14 -05:00

236 lines
9.6 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 <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 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