- rpc_client: wipe the plaintext "user:password" temporary with sodium_memzero after base64-encoding it into the auth header (std::string doesn't zero its buffer on destruction). - connection: the auto-generated DRAGONX.conf holds rpcuser/rpcpassword in plaintext but was written with the default umask (often world-readable 0644). Restrict it to owner read/write after creation so another local user can't read the credentials. - app: copying a seed phrase / private key to the clipboard now arms an auto-clear — App::copySecretToClipboard() copies the secret and, after 45s, wipes the clipboard IF it still holds that secret (compared via a stored hash, never the plaintext). Wired into the lite first-run wizard's seed Copy and the Settings export-secret Copy, with a "clipboard auto-clears in 45s" notice. pumpSecretClipboardClear() runs each frame. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
851 lines
27 KiB
C++
851 lines
27 KiB
C++
// DragonX Wallet - ImGui Edition
|
|
// Copyright 2024-2026 The Hush Developers
|
|
// Released under the GPLv3
|
|
//
|
|
// rpc_client.cpp — JSON-RPC client over HTTPS using libcurl.
|
|
// All calls are blocking; run on RPCWorker threads, never on main thread.
|
|
|
|
#include "rpc_client.h"
|
|
#include "connection.h"
|
|
#include "../config/version.h"
|
|
#include "../util/base64.h"
|
|
|
|
#include <curl/curl.h>
|
|
#include <sodium.h>
|
|
#include <atomic>
|
|
#include <cstdio>
|
|
#include <cstring>
|
|
#include <utility>
|
|
#include "../util/logger.h"
|
|
|
|
namespace dragonx {
|
|
namespace rpc {
|
|
|
|
namespace {
|
|
|
|
std::mutex g_trace_mutex;
|
|
RPCClient::TraceCallback g_trace_callback;
|
|
std::atomic_bool g_trace_enabled{false};
|
|
thread_local std::string g_trace_source;
|
|
|
|
void emitRpcTrace(const std::string& method)
|
|
{
|
|
if (!g_trace_enabled.load(std::memory_order_relaxed)) return;
|
|
|
|
RPCClient::TraceCallback callback;
|
|
{
|
|
std::lock_guard<std::mutex> lock(g_trace_mutex);
|
|
callback = g_trace_callback;
|
|
}
|
|
if (!callback) return;
|
|
|
|
std::string source = g_trace_source.empty() ? std::string("App") : g_trace_source;
|
|
callback(source, method);
|
|
}
|
|
|
|
} // namespace
|
|
|
|
RPCClient::TraceScope::TraceScope(std::string source)
|
|
: previous_(RPCClient::currentTraceSource())
|
|
{
|
|
RPCClient::setTraceSource(std::move(source));
|
|
}
|
|
|
|
RPCClient::TraceScope::~TraceScope()
|
|
{
|
|
RPCClient::setTraceSource(std::move(previous_));
|
|
}
|
|
|
|
void RPCClient::setTraceCallback(TraceCallback callback)
|
|
{
|
|
std::lock_guard<std::mutex> lock(g_trace_mutex);
|
|
g_trace_callback = std::move(callback);
|
|
}
|
|
|
|
void RPCClient::setTraceEnabled(bool enabled)
|
|
{
|
|
g_trace_enabled.store(enabled, std::memory_order_relaxed);
|
|
}
|
|
|
|
bool RPCClient::isTraceEnabled()
|
|
{
|
|
return g_trace_enabled.load(std::memory_order_relaxed);
|
|
}
|
|
|
|
std::string RPCClient::currentTraceSource()
|
|
{
|
|
return g_trace_source;
|
|
}
|
|
|
|
void RPCClient::setTraceSource(std::string source)
|
|
{
|
|
g_trace_source = std::move(source);
|
|
}
|
|
|
|
// Callback for libcurl to write response data
|
|
static size_t WriteCallback(void* contents, size_t size, size_t nmemb, std::string* userp) {
|
|
size_t totalSize = size * nmemb;
|
|
userp->append((char*)contents, totalSize);
|
|
return totalSize;
|
|
}
|
|
|
|
// curl progress callback: a non-zero return aborts the in-flight transfer. This lets a
|
|
// requestAbort() from another thread (disconnect/shutdown) unblock curl_easy_perform so the
|
|
// UI thread's worker join() returns promptly instead of waiting out the request timeout.
|
|
static int xferInfoCallback(void* clientp, curl_off_t, curl_off_t, curl_off_t, curl_off_t) {
|
|
const auto* self = static_cast<const RPCClient*>(clientp);
|
|
return (self != nullptr && self->abortRequested()) ? 1 : 0;
|
|
}
|
|
|
|
// Private implementation using libcurl
|
|
class RPCClient::Impl {
|
|
public:
|
|
CURL* curl = nullptr;
|
|
struct curl_slist* headers = nullptr;
|
|
std::string url;
|
|
|
|
~Impl() {
|
|
if (headers) {
|
|
curl_slist_free_all(headers);
|
|
}
|
|
if (curl) {
|
|
curl_easy_cleanup(curl);
|
|
}
|
|
}
|
|
};
|
|
|
|
// Initialize curl globally (once)
|
|
static bool initCurl() {
|
|
static bool initialized = false;
|
|
if (!initialized) {
|
|
curl_global_init(CURL_GLOBAL_DEFAULT);
|
|
initialized = true;
|
|
}
|
|
return true;
|
|
}
|
|
static bool curl_init = initCurl();
|
|
|
|
RPCClient::RPCClient() : impl_(std::make_unique<Impl>())
|
|
{
|
|
}
|
|
|
|
RPCClient::~RPCClient() = default;
|
|
|
|
bool RPCClient::connect(const std::string& host, const std::string& port,
|
|
const std::string& user, const std::string& password)
|
|
{
|
|
return connect(host, port, user, password, false);
|
|
}
|
|
|
|
bool RPCClient::connect(const std::string& host, const std::string& port,
|
|
const std::string& user, const std::string& password,
|
|
bool useTls)
|
|
{
|
|
std::lock_guard<std::recursive_mutex> lk(curl_mutex_);
|
|
host_ = host;
|
|
port_ = port;
|
|
last_connect_info_ = json();
|
|
|
|
// Create Basic auth header with proper base64 encoding, then wipe the plaintext
|
|
// "user:password" temporary (std::string does not zero its buffer on destruction).
|
|
std::string credentials = user + ":" + password;
|
|
auth_ = util::base64_encode(credentials);
|
|
if (!credentials.empty()) sodium_memzero(credentials.data(), credentials.size());
|
|
|
|
impl_->url = std::string(useTls ? "https://" : "http://") + host + ":" + port + "/";
|
|
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
|
|
impl_->curl = curl_easy_init();
|
|
if (!impl_->curl) {
|
|
DEBUG_LOGF("Failed to initialize curl\n");
|
|
return false;
|
|
}
|
|
|
|
// Set up headers - daemon expects text/plain, not application/json
|
|
impl_->headers = curl_slist_append(nullptr, "Content-Type: text/plain");
|
|
std::string auth_header = "Authorization: Basic " + auth_;
|
|
impl_->headers = curl_slist_append(impl_->headers, auth_header.c_str());
|
|
|
|
// Configure curl
|
|
curl_easy_setopt(impl_->curl, CURLOPT_URL, impl_->url.c_str());
|
|
curl_easy_setopt(impl_->curl, CURLOPT_HTTPHEADER, impl_->headers);
|
|
curl_easy_setopt(impl_->curl, CURLOPT_WRITEFUNCTION, WriteCallback);
|
|
// Progress callback so requestAbort() can unblock an in-flight curl_easy_perform.
|
|
clearAbort(); // a fresh connection must not start in the aborted state
|
|
curl_easy_setopt(impl_->curl, CURLOPT_NOPROGRESS, 0L);
|
|
curl_easy_setopt(impl_->curl, CURLOPT_XFERINFOFUNCTION, xferInfoCallback);
|
|
curl_easy_setopt(impl_->curl, CURLOPT_XFERINFODATA, this);
|
|
curl_easy_setopt(impl_->curl, CURLOPT_TIMEOUT, 30L);
|
|
// Localhost fails fast if nothing is listening; a remote/TLS daemon needs a larger
|
|
// budget for the TCP + TLS handshake over real network latency (1s would spuriously fail).
|
|
const long connectTimeout = Connection::isLocalHost(host) ? 2L : 10L;
|
|
curl_easy_setopt(impl_->curl, CURLOPT_CONNECTTIMEOUT, connectTimeout);
|
|
|
|
// Test connection with getinfo. Use a SHORT timeout for the probe on localhost: a healthy
|
|
// local daemon answers in milliseconds and a warming one returns -28 just as fast, so a long
|
|
// hang means a wedged/loading occupant — no point blocking the full 30s before we retry and
|
|
// update the UI. (call(timeoutSec) restores the persistent 30s afterwards, so normal RPC calls
|
|
// that legitimately take longer are unaffected.) Remote/TLS daemons keep the full budget.
|
|
const long probeTimeout = Connection::isLocalHost(host) ? 8L : 30L;
|
|
try {
|
|
json result = call("getinfo", json::array(), probeTimeout);
|
|
if (result.contains("version")) {
|
|
connected_ = true;
|
|
warming_up_ = false;
|
|
warmup_status_.clear();
|
|
last_connect_error_.clear();
|
|
last_connect_info_ = result;
|
|
DEBUG_LOGF("Connected to dragonxd v%d\n", result["version"].get<int>());
|
|
return true;
|
|
}
|
|
} catch (const std::exception& e) {
|
|
last_connect_error_ = e.what();
|
|
// Daemon warmup messages (Loading block index, Verifying blocks, etc.)
|
|
// 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();
|
|
// Warmup is JSON-RPC error code -28 (RPC_IN_WARMUP) — the robust signal. Fall back
|
|
// to message substrings for any path that didn't carry the numeric code.
|
|
int code = 0;
|
|
if (const auto* re = dynamic_cast<const RpcError*>(&e)) code = re->code;
|
|
bool isWarmup = (code == -28) ||
|
|
(msg.find("Loading") != std::string::npos ||
|
|
msg.find("Verifying") != std::string::npos ||
|
|
msg.find("Activating") != std::string::npos ||
|
|
msg.find("Rewinding") != std::string::npos ||
|
|
msg.find("Rescanning") != std::string::npos ||
|
|
msg.find("Pruning") != std::string::npos);
|
|
if (isWarmup) {
|
|
connected_ = true;
|
|
warming_up_ = true;
|
|
warmup_status_ = msg;
|
|
DEBUG_LOGF("Daemon warming up: %s\n", msg.c_str());
|
|
return true;
|
|
} else {
|
|
DEBUG_LOGF("Connection failed: %s\n", msg.c_str());
|
|
}
|
|
}
|
|
|
|
connected_ = false;
|
|
warming_up_ = false;
|
|
warmup_status_.clear();
|
|
last_connect_info_ = json();
|
|
return false;
|
|
}
|
|
|
|
json RPCClient::getLastConnectInfo() const
|
|
{
|
|
std::lock_guard<std::recursive_mutex> lk(curl_mutex_);
|
|
return last_connect_info_;
|
|
}
|
|
|
|
void RPCClient::requestAbort()
|
|
{
|
|
// Deliberately NOT taking curl_mutex_ — the whole point is to interrupt a call() that is
|
|
// currently holding it inside curl_easy_perform. The atomic is read by xferInfoCallback.
|
|
abort_.store(true, std::memory_order_relaxed);
|
|
}
|
|
|
|
void RPCClient::clearAbort()
|
|
{
|
|
abort_.store(false, std::memory_order_relaxed);
|
|
}
|
|
|
|
void RPCClient::disconnect()
|
|
{
|
|
std::lock_guard<std::recursive_mutex> lk(curl_mutex_);
|
|
connected_ = false;
|
|
warming_up_ = false;
|
|
warmup_status_.clear();
|
|
last_connect_info_ = json();
|
|
if (impl_->curl) {
|
|
curl_easy_cleanup(impl_->curl);
|
|
impl_->curl = nullptr;
|
|
}
|
|
if (impl_->headers) {
|
|
curl_slist_free_all(impl_->headers);
|
|
impl_->headers = nullptr;
|
|
}
|
|
}
|
|
|
|
json RPCClient::makePayload(const std::string& method, const json& params)
|
|
{
|
|
return {
|
|
{"jsonrpc", "1.0"},
|
|
{"id", "ObsidianDragon"},
|
|
{"method", method},
|
|
{"params", params}
|
|
};
|
|
}
|
|
|
|
json RPCClient::call(const std::string& method, const json& params)
|
|
{
|
|
std::lock_guard<std::recursive_mutex> lk(curl_mutex_);
|
|
if (!impl_->curl) {
|
|
throw std::runtime_error("Not connected");
|
|
}
|
|
|
|
emitRpcTrace(method);
|
|
|
|
json payload = makePayload(method, params);
|
|
std::string body = payload.dump();
|
|
std::string response_data;
|
|
|
|
// Set POST data
|
|
curl_easy_setopt(impl_->curl, CURLOPT_POSTFIELDS, body.c_str());
|
|
curl_easy_setopt(impl_->curl, CURLOPT_POSTFIELDSIZE, (long)body.size());
|
|
curl_easy_setopt(impl_->curl, CURLOPT_WRITEDATA, &response_data);
|
|
|
|
// Perform request
|
|
CURLcode res = curl_easy_perform(impl_->curl);
|
|
|
|
if (res != CURLE_OK) {
|
|
throw std::runtime_error("RPC request failed: " + std::string(curl_easy_strerror(res)));
|
|
}
|
|
|
|
// Check HTTP response code
|
|
long http_code = 0;
|
|
curl_easy_getinfo(impl_->curl, CURLINFO_RESPONSE_CODE, &http_code);
|
|
|
|
// Bitcoin/Hush RPC returns HTTP 500 for application-level errors
|
|
// (insufficient funds, bad params, etc.) with a valid JSON body.
|
|
// Parse the body first to extract the real error message.
|
|
if (http_code != 200) {
|
|
int errCode = 0;
|
|
try {
|
|
json response = json::parse(response_data);
|
|
if (response.contains("error") && response["error"].is_object()) {
|
|
if (response["error"].contains("code") && response["error"]["code"].is_number_integer())
|
|
errCode = response["error"]["code"].get<int>();
|
|
if (response["error"].contains("message") && response["error"]["message"].is_string())
|
|
throw RpcError(errCode, response["error"]["message"].get<std::string>());
|
|
// message missing/non-string — keep the detail instead of a bare HTTP code
|
|
throw RpcError(errCode, "RPC error: " + response["error"].dump());
|
|
}
|
|
} catch (const json::exception&) {
|
|
// Body wasn't valid JSON — fall through to generic HTTP error
|
|
}
|
|
throw RpcError(errCode, "RPC error: HTTP " + std::to_string(http_code));
|
|
}
|
|
|
|
json response = json::parse(response_data);
|
|
|
|
if (response.contains("error") && !response["error"].is_null()) {
|
|
int errCode = 0;
|
|
std::string err_msg;
|
|
if (response["error"].is_object()) {
|
|
if (response["error"].contains("code") && response["error"]["code"].is_number_integer())
|
|
errCode = response["error"]["code"].get<int>();
|
|
if (response["error"].contains("message") && response["error"]["message"].is_string())
|
|
err_msg = response["error"]["message"].get<std::string>();
|
|
}
|
|
if (err_msg.empty()) err_msg = response["error"].dump();
|
|
throw RpcError(errCode, "RPC error: " + err_msg);
|
|
}
|
|
|
|
return response["result"];
|
|
}
|
|
|
|
json RPCClient::call(const std::string& method, const json& params, long timeoutSec)
|
|
{
|
|
std::lock_guard<std::recursive_mutex> lk(curl_mutex_);
|
|
if (!impl_->curl) {
|
|
throw std::runtime_error("Not connected");
|
|
}
|
|
|
|
emitRpcTrace(method);
|
|
|
|
// Temporarily override timeout
|
|
long prevTimeout = 30L;
|
|
curl_easy_setopt(impl_->curl, CURLOPT_TIMEOUT, timeoutSec);
|
|
|
|
try {
|
|
// Unlock before calling to avoid recursive lock issues — but we already hold it,
|
|
// and call() also locks with recursive_mutex, so just delegate to the body directly.
|
|
json payload = makePayload(method, params);
|
|
std::string body = payload.dump();
|
|
std::string response_data;
|
|
|
|
curl_easy_setopt(impl_->curl, CURLOPT_POSTFIELDS, body.c_str());
|
|
curl_easy_setopt(impl_->curl, CURLOPT_POSTFIELDSIZE, (long)body.size());
|
|
curl_easy_setopt(impl_->curl, CURLOPT_WRITEDATA, &response_data);
|
|
|
|
CURLcode res = curl_easy_perform(impl_->curl);
|
|
|
|
// Restore original timeout
|
|
curl_easy_setopt(impl_->curl, CURLOPT_TIMEOUT, prevTimeout);
|
|
|
|
if (res != CURLE_OK) {
|
|
throw std::runtime_error("RPC request failed: " + std::string(curl_easy_strerror(res)));
|
|
}
|
|
|
|
long http_code = 0;
|
|
curl_easy_getinfo(impl_->curl, CURLINFO_RESPONSE_CODE, &http_code);
|
|
|
|
if (http_code != 200) {
|
|
int errCode = 0;
|
|
try {
|
|
json response = json::parse(response_data);
|
|
if (response.contains("error") && response["error"].is_object()) {
|
|
if (response["error"].contains("code") && response["error"]["code"].is_number_integer())
|
|
errCode = response["error"]["code"].get<int>();
|
|
if (response["error"].contains("message") && response["error"]["message"].is_string())
|
|
throw RpcError(errCode, response["error"]["message"].get<std::string>());
|
|
throw RpcError(errCode, "RPC error: " + response["error"].dump());
|
|
}
|
|
} catch (const json::exception&) {}
|
|
throw RpcError(errCode, "RPC error: HTTP " + std::to_string(http_code));
|
|
}
|
|
|
|
json response = json::parse(response_data);
|
|
if (response.contains("error") && !response["error"].is_null()) {
|
|
int errCode = 0;
|
|
std::string err_msg;
|
|
if (response["error"].is_object()) {
|
|
if (response["error"].contains("code") && response["error"]["code"].is_number_integer())
|
|
errCode = response["error"]["code"].get<int>();
|
|
if (response["error"].contains("message") && response["error"]["message"].is_string())
|
|
err_msg = response["error"]["message"].get<std::string>();
|
|
}
|
|
if (err_msg.empty()) err_msg = response["error"].dump();
|
|
throw RpcError(errCode, "RPC error: " + err_msg);
|
|
}
|
|
|
|
return response["result"];
|
|
} catch (...) {
|
|
// Ensure timeout is always restored
|
|
curl_easy_setopt(impl_->curl, CURLOPT_TIMEOUT, prevTimeout);
|
|
throw;
|
|
}
|
|
}
|
|
|
|
std::string RPCClient::callRaw(const std::string& method, const json& params)
|
|
{
|
|
std::lock_guard<std::recursive_mutex> lk(curl_mutex_);
|
|
if (!impl_->curl) {
|
|
throw std::runtime_error("Not connected");
|
|
}
|
|
|
|
emitRpcTrace(method);
|
|
|
|
json payload = makePayload(method, params);
|
|
std::string body = payload.dump();
|
|
std::string response_data;
|
|
|
|
curl_easy_setopt(impl_->curl, CURLOPT_POSTFIELDS, body.c_str());
|
|
curl_easy_setopt(impl_->curl, CURLOPT_POSTFIELDSIZE, (long)body.size());
|
|
curl_easy_setopt(impl_->curl, CURLOPT_WRITEDATA, &response_data);
|
|
|
|
CURLcode res = curl_easy_perform(impl_->curl);
|
|
if (res != CURLE_OK) {
|
|
throw std::runtime_error("RPC request failed: " + std::string(curl_easy_strerror(res)));
|
|
}
|
|
|
|
long http_code = 0;
|
|
curl_easy_getinfo(impl_->curl, CURLINFO_RESPONSE_CODE, &http_code);
|
|
|
|
if (http_code != 200) {
|
|
try {
|
|
json response = json::parse(response_data);
|
|
if (response.contains("error") && !response["error"].is_null()) {
|
|
std::string err_msg = response["error"]["message"].get<std::string>();
|
|
throw std::runtime_error(err_msg);
|
|
}
|
|
} catch (const json::exception&) {}
|
|
throw std::runtime_error("RPC error: HTTP " + std::to_string(http_code));
|
|
}
|
|
|
|
// Parse with ordered_json to preserve the daemon's original key order
|
|
nlohmann::ordered_json oj = nlohmann::ordered_json::parse(response_data);
|
|
if (oj.contains("error") && !oj["error"].is_null()) {
|
|
std::string err_msg = oj["error"]["message"].get<std::string>();
|
|
throw std::runtime_error("RPC error: " + err_msg);
|
|
}
|
|
|
|
auto& result = oj["result"];
|
|
if (result.is_null()) {
|
|
return "null";
|
|
} else if (result.is_string()) {
|
|
// Return the raw string (not JSON-encoded) — caller wraps as needed
|
|
return result.get<std::string>();
|
|
} else {
|
|
return result.dump(4);
|
|
}
|
|
}
|
|
|
|
void RPCClient::doRPC(const std::string& method, const json& params, Callback cb, ErrorCallback err)
|
|
{
|
|
try {
|
|
json result = call(method, params);
|
|
if (cb) cb(result);
|
|
} catch (const std::exception& e) {
|
|
if (err) {
|
|
err(e.what());
|
|
} else {
|
|
DEBUG_LOGF("RPC error (%s): %s\n", method.c_str(), e.what());
|
|
}
|
|
}
|
|
}
|
|
|
|
// High-level API implementations
|
|
|
|
void RPCClient::getInfo(Callback cb, ErrorCallback err)
|
|
{
|
|
doRPC("getinfo", {}, cb, err);
|
|
}
|
|
|
|
void RPCClient::getBlockchainInfo(Callback cb, ErrorCallback err)
|
|
{
|
|
doRPC("getblockchaininfo", {}, cb, err);
|
|
}
|
|
|
|
void RPCClient::getMiningInfo(Callback cb, ErrorCallback err)
|
|
{
|
|
doRPC("getmininginfo", {}, cb, err);
|
|
}
|
|
|
|
void RPCClient::getBalance(Callback cb, ErrorCallback err)
|
|
{
|
|
doRPC("getbalance", {}, cb, err);
|
|
}
|
|
|
|
void RPCClient::z_getTotalBalance(Callback cb, ErrorCallback err)
|
|
{
|
|
doRPC("z_gettotalbalance", {}, cb, err);
|
|
}
|
|
|
|
void RPCClient::listUnspent(Callback cb, ErrorCallback err)
|
|
{
|
|
doRPC("listunspent", {0}, cb, err);
|
|
}
|
|
|
|
void RPCClient::z_listUnspent(Callback cb, ErrorCallback err)
|
|
{
|
|
doRPC("z_listunspent", {0}, cb, err);
|
|
}
|
|
|
|
void RPCClient::getAddressesByAccount(Callback cb, ErrorCallback err)
|
|
{
|
|
doRPC("getaddressesbyaccount", {""}, cb, err);
|
|
}
|
|
|
|
void RPCClient::z_listAddresses(Callback cb, ErrorCallback err)
|
|
{
|
|
doRPC("z_listaddresses", {}, cb, err);
|
|
}
|
|
|
|
void RPCClient::getNewAddress(Callback cb, ErrorCallback err)
|
|
{
|
|
doRPC("getnewaddress", {}, cb, err);
|
|
}
|
|
|
|
void RPCClient::z_getNewAddress(Callback cb, ErrorCallback err)
|
|
{
|
|
doRPC("z_getnewaddress", {}, cb, err);
|
|
}
|
|
|
|
void RPCClient::listTransactions(int count, Callback cb, ErrorCallback err)
|
|
{
|
|
doRPC("listtransactions", {"", count}, cb, err);
|
|
}
|
|
|
|
void RPCClient::z_viewTransaction(const std::string& txid, Callback cb, ErrorCallback err)
|
|
{
|
|
doRPC("z_viewtransaction", {txid}, cb, err);
|
|
}
|
|
|
|
void RPCClient::getRawTransaction(const std::string& txid, Callback cb, ErrorCallback err)
|
|
{
|
|
doRPC("getrawtransaction", {txid, 1}, cb, err);
|
|
}
|
|
|
|
void RPCClient::sendToAddress(const std::string& address, double amount, Callback cb, ErrorCallback err)
|
|
{
|
|
doRPC("sendtoaddress", {address, amount}, cb, err);
|
|
}
|
|
|
|
void RPCClient::z_sendMany(const std::string& from, const json& recipients, Callback cb, ErrorCallback err)
|
|
{
|
|
doRPC("z_sendmany", {from, recipients}, cb, err);
|
|
}
|
|
|
|
void RPCClient::setGenerate(bool generate, int threads, Callback cb, ErrorCallback err)
|
|
{
|
|
doRPC("setgenerate", {generate, threads}, cb, err);
|
|
}
|
|
|
|
void RPCClient::getNetworkHashPS(Callback cb, ErrorCallback err)
|
|
{
|
|
doRPC("getnetworkhashps", {}, cb, err);
|
|
}
|
|
|
|
void RPCClient::getLocalHashrate(Callback cb, ErrorCallback err)
|
|
{
|
|
// RPC name is "getlocalsolps" (inherited from HUSH/Zcash daemon API)
|
|
// but DragonX uses RandomX, so the value is H/s not Sol/s
|
|
doRPC("getlocalsolps", {}, cb, err);
|
|
}
|
|
|
|
void RPCClient::getPeerInfo(Callback cb, ErrorCallback err)
|
|
{
|
|
doRPC("getpeerinfo", {}, cb, err);
|
|
}
|
|
|
|
void RPCClient::listBanned(Callback cb, ErrorCallback err)
|
|
{
|
|
doRPC("listbanned", {}, cb, err);
|
|
}
|
|
|
|
void RPCClient::setBan(const std::string& ip, const std::string& command, Callback cb, ErrorCallback err, int bantime)
|
|
{
|
|
// setban "ip" "add|remove" [bantime] [absolute]
|
|
doRPC("setban", {ip, command, bantime}, cb, err);
|
|
}
|
|
|
|
void RPCClient::clearBanned(Callback cb, ErrorCallback err)
|
|
{
|
|
doRPC("clearbanned", {}, cb, err);
|
|
}
|
|
|
|
void RPCClient::dumpPrivKey(const std::string& address, Callback cb, ErrorCallback err)
|
|
{
|
|
doRPC("dumpprivkey", {address}, cb, err);
|
|
}
|
|
|
|
void RPCClient::z_exportKey(const std::string& address, Callback cb, ErrorCallback err)
|
|
{
|
|
doRPC("z_exportkey", {address}, cb, err);
|
|
}
|
|
|
|
void RPCClient::z_exportViewingKey(const std::string& address, Callback cb, ErrorCallback err)
|
|
{
|
|
doRPC("z_exportviewingkey", {address}, cb, err);
|
|
}
|
|
|
|
void RPCClient::importPrivKey(const std::string& key, bool rescan, Callback cb, ErrorCallback err)
|
|
{
|
|
doRPC("importprivkey", {key, "", rescan}, cb, err);
|
|
}
|
|
|
|
void RPCClient::z_importKey(const std::string& key, bool rescan, Callback cb, ErrorCallback err)
|
|
{
|
|
doRPC("z_importkey", {key, rescan ? "yes" : "no"}, cb, err);
|
|
}
|
|
|
|
void RPCClient::validateAddress(const std::string& address, Callback cb, ErrorCallback err)
|
|
{
|
|
doRPC("validateaddress", {address}, cb, err);
|
|
}
|
|
|
|
void RPCClient::getBlock(const std::string& hash_or_height, Callback cb, ErrorCallback err)
|
|
{
|
|
doRPC("getblock", {hash_or_height}, cb, err);
|
|
}
|
|
|
|
void RPCClient::stop(Callback cb, ErrorCallback err)
|
|
{
|
|
doRPC("stop", {}, cb, err);
|
|
}
|
|
|
|
void RPCClient::rescanBlockchain(int startHeight, Callback cb, ErrorCallback err)
|
|
{
|
|
doRPC("rescanblockchain", {startHeight}, cb, err);
|
|
}
|
|
|
|
void RPCClient::z_validateAddress(const std::string& address, Callback cb, ErrorCallback err)
|
|
{
|
|
doRPC("z_validateaddress", {address}, cb, err);
|
|
}
|
|
|
|
void RPCClient::getBlockHash(int height, Callback cb, ErrorCallback err)
|
|
{
|
|
doRPC("getblockhash", {height}, cb, err);
|
|
}
|
|
|
|
void RPCClient::getTransaction(const std::string& txid, Callback cb, ErrorCallback err)
|
|
{
|
|
doRPC("gettransaction", {txid}, cb, err);
|
|
}
|
|
|
|
void RPCClient::getWalletInfo(Callback cb, ErrorCallback err)
|
|
{
|
|
doRPC("getwalletinfo", {}, cb, err);
|
|
}
|
|
|
|
void RPCClient::encryptWallet(const std::string& passphrase, Callback cb, ErrorCallback err)
|
|
{
|
|
doRPC("encryptwallet", {passphrase}, cb, err);
|
|
}
|
|
|
|
void RPCClient::walletPassphrase(const std::string& passphrase, int timeout, Callback cb, ErrorCallback err)
|
|
{
|
|
doRPC("walletpassphrase", {passphrase, timeout}, cb, err);
|
|
}
|
|
|
|
void RPCClient::walletLock(Callback cb, ErrorCallback err)
|
|
{
|
|
doRPC("walletlock", {}, cb, err);
|
|
}
|
|
|
|
void RPCClient::walletPassphraseChange(const std::string& oldPass, const std::string& newPass,
|
|
Callback cb, ErrorCallback err)
|
|
{
|
|
doRPC("walletpassphrasechange", {oldPass, newPass}, cb, err);
|
|
}
|
|
|
|
void RPCClient::z_exportWallet(const std::string& filename, Callback cb, ErrorCallback err)
|
|
{
|
|
doRPC("z_exportwallet", {filename}, cb, err);
|
|
}
|
|
|
|
void RPCClient::z_importWallet(const std::string& filename, Callback cb, ErrorCallback err)
|
|
{
|
|
doRPC("z_importwallet", {filename}, cb, err);
|
|
}
|
|
|
|
void RPCClient::z_shieldCoinbase(const std::string& fromAddr, const std::string& toAddr,
|
|
double fee, int limit, Callback cb, ErrorCallback err)
|
|
{
|
|
doRPC("z_shieldcoinbase", {fromAddr, toAddr, fee, limit}, cb, err);
|
|
}
|
|
|
|
void RPCClient::z_mergeToAddress(const std::vector<std::string>& fromAddrs, const std::string& toAddr,
|
|
double fee, int limit, Callback cb, ErrorCallback err)
|
|
{
|
|
json addrs = json::array();
|
|
for (const auto& addr : fromAddrs) {
|
|
addrs.push_back(addr);
|
|
}
|
|
doRPC("z_mergetoaddress", {addrs, toAddr, fee, 0, limit}, cb, err);
|
|
}
|
|
|
|
void RPCClient::z_getOperationStatus(const std::vector<std::string>& opids, Callback cb, ErrorCallback err)
|
|
{
|
|
json ids = json::array();
|
|
for (const auto& id : opids) {
|
|
ids.push_back(id);
|
|
}
|
|
doRPC("z_getoperationstatus", {ids}, cb, err);
|
|
}
|
|
|
|
void RPCClient::z_getOperationResult(const std::vector<std::string>& opids, Callback cb, ErrorCallback err)
|
|
{
|
|
json ids = json::array();
|
|
for (const auto& id : opids) {
|
|
ids.push_back(id);
|
|
}
|
|
doRPC("z_getoperationresult", {ids}, cb, err);
|
|
}
|
|
|
|
void RPCClient::z_listReceivedByAddress(const std::string& address, int minconf, Callback cb, ErrorCallback err)
|
|
{
|
|
doRPC("z_listreceivedbyaddress", {address, minconf}, cb, err);
|
|
}
|
|
|
|
// Unified callback versions
|
|
void RPCClient::getInfo(UnifiedCallback cb)
|
|
{
|
|
doRPC("getinfo", {},
|
|
[cb](const json& result) {
|
|
if (cb) cb(result, "");
|
|
},
|
|
[cb](const std::string& error) {
|
|
if (cb) cb(json{}, error);
|
|
}
|
|
);
|
|
}
|
|
|
|
void RPCClient::rescanBlockchain(int startHeight, UnifiedCallback cb)
|
|
{
|
|
doRPC("rescanblockchain", {startHeight},
|
|
[cb](const json& result) {
|
|
if (cb) cb(result, "");
|
|
},
|
|
[cb](const std::string& error) {
|
|
if (cb) cb(json{}, error);
|
|
}
|
|
);
|
|
}
|
|
|
|
void RPCClient::z_shieldCoinbase(const std::string& fromAddr, const std::string& toAddr,
|
|
double fee, int limit, UnifiedCallback cb)
|
|
{
|
|
doRPC("z_shieldcoinbase", {fromAddr, toAddr, fee, limit},
|
|
[cb](const json& result) {
|
|
if (cb) cb(result, "");
|
|
},
|
|
[cb](const std::string& error) {
|
|
if (cb) cb(json{}, error);
|
|
}
|
|
);
|
|
}
|
|
|
|
void RPCClient::z_mergeToAddress(const std::vector<std::string>& fromAddrs, const std::string& toAddr,
|
|
double fee, int limit, UnifiedCallback cb)
|
|
{
|
|
json addrs = json::array();
|
|
for (const auto& addr : fromAddrs) {
|
|
addrs.push_back(addr);
|
|
}
|
|
doRPC("z_mergetoaddress", {addrs, toAddr, fee, 0, limit},
|
|
[cb](const json& result) {
|
|
if (cb) cb(result, "");
|
|
},
|
|
[cb](const std::string& error) {
|
|
if (cb) cb(json{}, error);
|
|
}
|
|
);
|
|
}
|
|
|
|
void RPCClient::z_getOperationStatus(const std::vector<std::string>& opids, UnifiedCallback cb)
|
|
{
|
|
json ids = json::array();
|
|
for (const auto& id : opids) {
|
|
ids.push_back(id);
|
|
}
|
|
doRPC("z_getoperationstatus", {ids},
|
|
[cb](const json& result) {
|
|
if (cb) cb(result, "");
|
|
},
|
|
[cb](const std::string& error) {
|
|
if (cb) cb(json{}, error);
|
|
}
|
|
);
|
|
}
|
|
|
|
void RPCClient::getBlock(int height, UnifiedCallback cb)
|
|
{
|
|
// First get block hash, then get block
|
|
getBlockHash(height,
|
|
[this, cb](const json& hashResult) {
|
|
std::string hash = hashResult.get<std::string>();
|
|
getBlock(hash,
|
|
[cb](const json& blockResult) {
|
|
if (cb) cb(blockResult, "");
|
|
},
|
|
[cb](const std::string& error) {
|
|
if (cb) cb(json{}, error);
|
|
}
|
|
);
|
|
},
|
|
[cb](const std::string& error) {
|
|
if (cb) cb(json{}, error);
|
|
}
|
|
);
|
|
}
|
|
|
|
} // namespace rpc
|
|
} // namespace dragonx
|