refactor(audit): batch 7 — RPC/settings/lite/effects mechanical dedups
Six behavior-preserving consolidations from the audit: - RPCClient::call() overloads share a private performCall() (payload dump + curl_easy_perform + http code) and a static parseRpcResult() (error->RpcError extraction). The timeout overload restores the prior timeout in both the success and catch paths, exactly as before. - The six UnifiedCallback methods delegate to one splitUnified() that builds the (Callback, ErrorCallback) pair once (null-check preserved). - One liteTrimCopy() in lite_connection_service replaces 5 file-local trim-copy helpers across the lite slice. - lite_wallet_controller's inline height JSON parse now routes through the tested parseLiteHeightResponse(). - theme_effects.cpp: unpackRGB()/scaledAlpha() replace 14 RGB-unpack + 5 alpha-scale copy-paste blocks. - settings.cpp load() uses loadScalar()/loadClamped() helpers for ~44 scalar fields. Besides removing boilerplate this HARDENS loading: ~44 fields that previously only checked contains() now also verify the JSON type, so a malformed value in settings.json falls back to the default instead of throwing/misreading. Custom cases (enum parses, legacy migrations, arrays) stay inline; save() is unchanged. Full-node + Lite build clean; ctest 1/1; hygiene clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -12,6 +12,8 @@
|
||||
#include <fstream>
|
||||
#include <filesystem>
|
||||
#include <ctime>
|
||||
#include <algorithm>
|
||||
#include <type_traits>
|
||||
|
||||
#include "../util/logger.h"
|
||||
#include "../util/platform.h"
|
||||
@@ -76,6 +78,34 @@ const char* poolSelectModeName(Settings::PoolSelectMode mode)
|
||||
return "manual";
|
||||
}
|
||||
|
||||
// True if j[key] exists and holds a JSON value convertible to T. Guards every scalar
|
||||
// read so a malformed value (wrong type / missing key) leaves the field's default
|
||||
// instead of throwing out of the whole load().
|
||||
template <typename T>
|
||||
bool jsonHasType(const json& v)
|
||||
{
|
||||
if constexpr (std::is_same_v<T, bool>) return v.is_boolean();
|
||||
else if constexpr (std::is_same_v<T, std::string>) return v.is_string();
|
||||
else if constexpr (std::is_floating_point_v<T>) return v.is_number();
|
||||
else if constexpr (std::is_integral_v<T>) return v.is_number_integer();
|
||||
else return false;
|
||||
}
|
||||
|
||||
// Reads j[key] into field only when present AND of the matching type.
|
||||
template <typename T>
|
||||
void loadScalar(const json& j, const char* key, T& field)
|
||||
{
|
||||
if (j.contains(key) && jsonHasType<T>(j[key])) field = j[key].get<T>();
|
||||
}
|
||||
|
||||
// Same as loadScalar but clamps the read value into [lo, hi].
|
||||
template <typename T>
|
||||
void loadClamped(const json& j, const char* key, T& field, T lo, T hi)
|
||||
{
|
||||
if (j.contains(key) && jsonHasType<T>(j[key]))
|
||||
field = std::max(lo, std::min(hi, j[key].get<T>()));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
std::string Settings::getDefaultPath()
|
||||
@@ -127,29 +157,29 @@ bool Settings::load(const std::string& path)
|
||||
json j;
|
||||
file >> j;
|
||||
|
||||
if (j.contains("theme")) theme_ = j["theme"].get<std::string>();
|
||||
if (j.contains("save_ztxs")) save_ztxs_ = j["save_ztxs"].get<bool>();
|
||||
if (j.contains("auto_shield")) auto_shield_ = j["auto_shield"].get<bool>();
|
||||
if (j.contains("use_tor")) use_tor_ = j["use_tor"].get<bool>();
|
||||
if (j.contains("allow_custom_fees")) allow_custom_fees_ = j["allow_custom_fees"].get<bool>();
|
||||
if (j.contains("default_fee")) default_fee_ = j["default_fee"].get<double>();
|
||||
if (j.contains("fetch_prices")) fetch_prices_ = j["fetch_prices"].get<bool>();
|
||||
if (j.contains("tx_explorer_url")) tx_explorer_url_ = j["tx_explorer_url"].get<std::string>();
|
||||
if (j.contains("address_explorer_url")) address_explorer_url_ = j["address_explorer_url"].get<std::string>();
|
||||
if (j.contains("language")) language_ = j["language"].get<std::string>();
|
||||
if (j.contains("skin_id")) skin_id_ = j["skin_id"].get<std::string>();
|
||||
if (j.contains("acrylic_enabled")) acrylic_enabled_ = j["acrylic_enabled"].get<bool>();
|
||||
if (j.contains("acrylic_quality")) acrylic_quality_ = j["acrylic_quality"].get<int>();
|
||||
if (j.contains("blur_multiplier")) blur_multiplier_ = j["blur_multiplier"].get<float>();
|
||||
if (j.contains("noise_opacity")) noise_opacity_ = j["noise_opacity"].get<float>();
|
||||
if (j.contains("gradient_background")) gradient_background_ = j["gradient_background"].get<bool>();
|
||||
loadScalar(j, "theme", theme_);
|
||||
loadScalar(j, "save_ztxs", save_ztxs_);
|
||||
loadScalar(j, "auto_shield", auto_shield_);
|
||||
loadScalar(j, "use_tor", use_tor_);
|
||||
loadScalar(j, "allow_custom_fees", allow_custom_fees_);
|
||||
loadScalar(j, "default_fee", default_fee_);
|
||||
loadScalar(j, "fetch_prices", fetch_prices_);
|
||||
loadScalar(j, "tx_explorer_url", tx_explorer_url_);
|
||||
loadScalar(j, "address_explorer_url", address_explorer_url_);
|
||||
loadScalar(j, "language", language_);
|
||||
loadScalar(j, "skin_id", skin_id_);
|
||||
loadScalar(j, "acrylic_enabled", acrylic_enabled_);
|
||||
loadScalar(j, "acrylic_quality", acrylic_quality_);
|
||||
loadScalar(j, "blur_multiplier", blur_multiplier_);
|
||||
loadScalar(j, "noise_opacity", noise_opacity_);
|
||||
loadScalar(j, "gradient_background", gradient_background_);
|
||||
// Migrate legacy reduced_transparency bool -> ui_opacity float
|
||||
if (j.contains("ui_opacity")) {
|
||||
ui_opacity_ = j["ui_opacity"].get<float>();
|
||||
} else if (j.contains("reduced_transparency") && j["reduced_transparency"].get<bool>()) {
|
||||
ui_opacity_ = 1.0f; // legacy: reduced = fully opaque
|
||||
}
|
||||
if (j.contains("window_opacity")) window_opacity_ = j["window_opacity"].get<float>();
|
||||
loadScalar(j, "window_opacity", window_opacity_);
|
||||
if (j.contains("balance_layout")) {
|
||||
if (j["balance_layout"].is_string())
|
||||
balance_layout_ = j["balance_layout"].get<std::string>();
|
||||
@@ -163,7 +193,7 @@ bool Settings::load(const std::string& path)
|
||||
if (idx >= 0 && idx < 9) balance_layout_ = legacyIds[idx];
|
||||
}
|
||||
}
|
||||
if (j.contains("scanline_enabled")) scanline_enabled_ = j["scanline_enabled"].get<bool>();
|
||||
loadScalar(j, "scanline_enabled", scanline_enabled_);
|
||||
if (j.contains("hidden_addresses") && j["hidden_addresses"].is_array()) {
|
||||
hidden_addresses_.clear();
|
||||
for (const auto& a : j["hidden_addresses"])
|
||||
@@ -189,13 +219,13 @@ bool Settings::load(const std::string& path)
|
||||
address_meta_[addr] = m;
|
||||
}
|
||||
}
|
||||
if (j.contains("wizard_completed")) wizard_completed_ = j["wizard_completed"].get<bool>();
|
||||
if (j.contains("auto_lock_timeout")) auto_lock_timeout_ = j["auto_lock_timeout"].get<int>();
|
||||
if (j.contains("unlock_duration")) unlock_duration_ = j["unlock_duration"].get<int>();
|
||||
if (j.contains("pin_enabled")) pin_enabled_ = j["pin_enabled"].get<bool>();
|
||||
if (j.contains("keep_daemon_running")) keep_daemon_running_ = j["keep_daemon_running"].get<bool>();
|
||||
if (j.contains("stop_external_daemon")) stop_external_daemon_ = j["stop_external_daemon"].get<bool>();
|
||||
if (j.contains("max_connections")) max_connections_ = j["max_connections"].get<int>();
|
||||
loadScalar(j, "wizard_completed", wizard_completed_);
|
||||
loadScalar(j, "auto_lock_timeout", auto_lock_timeout_);
|
||||
loadScalar(j, "unlock_duration", unlock_duration_);
|
||||
loadScalar(j, "pin_enabled", pin_enabled_);
|
||||
loadScalar(j, "keep_daemon_running", keep_daemon_running_);
|
||||
loadScalar(j, "stop_external_daemon", stop_external_daemon_);
|
||||
loadScalar(j, "max_connections", max_connections_);
|
||||
if (j.contains("lite_wallet") && j["lite_wallet"].is_object()) {
|
||||
const auto& lite = j["lite_wallet"];
|
||||
if (lite.contains("server_selection_mode")) {
|
||||
@@ -256,34 +286,36 @@ bool Settings::load(const std::string& path)
|
||||
if (u.is_string()) lite_hidden_servers_.insert(u.get<std::string>());
|
||||
}
|
||||
}
|
||||
if (j.contains("verbose_logging")) verbose_logging_ = j["verbose_logging"].get<bool>();
|
||||
loadScalar(j, "verbose_logging", verbose_logging_);
|
||||
if (j.contains("debug_categories") && j["debug_categories"].is_array()) {
|
||||
debug_categories_.clear();
|
||||
for (const auto& c : j["debug_categories"])
|
||||
if (c.is_string()) debug_categories_.insert(c.get<std::string>());
|
||||
}
|
||||
if (j.contains("theme_effects_enabled")) theme_effects_enabled_ = j["theme_effects_enabled"].get<bool>();
|
||||
if (j.contains("low_spec_mode")) low_spec_mode_ = j["low_spec_mode"].get<bool>();
|
||||
if (j.contains("reduce_motion")) reduce_motion_ = j["reduce_motion"].get<bool>();
|
||||
if (j.contains("selected_exchange")) selected_exchange_ = j["selected_exchange"].get<std::string>();
|
||||
if (j.contains("selected_pair")) selected_pair_ = j["selected_pair"].get<std::string>();
|
||||
if (j.contains("pool_url")) pool_url_ = j["pool_url"].get<std::string>();
|
||||
loadScalar(j, "theme_effects_enabled", theme_effects_enabled_);
|
||||
loadScalar(j, "low_spec_mode", low_spec_mode_);
|
||||
loadScalar(j, "reduce_motion", reduce_motion_);
|
||||
loadScalar(j, "selected_exchange", selected_exchange_);
|
||||
loadScalar(j, "selected_pair", selected_pair_);
|
||||
loadScalar(j, "pool_url", pool_url_);
|
||||
// Migrate old default pool URL that was missing the stratum port
|
||||
if (pool_url_ == "pool.dragonx.is") pool_url_ = "pool.dragonx.is:3433";
|
||||
if (j.contains("pool_algo")) pool_algo_ = j["pool_algo"].get<std::string>();
|
||||
if (j.contains("pool_worker")) pool_worker_ = j["pool_worker"].get<std::string>();
|
||||
if (j.contains("pool_threads")) pool_threads_ = j["pool_threads"].get<int>();
|
||||
if (j.contains("pool_tls")) pool_tls_ = j["pool_tls"].get<bool>();
|
||||
if (j.contains("pool_hugepages")) pool_hugepages_ = j["pool_hugepages"].get<bool>();
|
||||
if (j.contains("pool_mode")) pool_mode_ = j["pool_mode"].get<bool>();
|
||||
loadScalar(j, "pool_algo", pool_algo_);
|
||||
loadScalar(j, "pool_worker", pool_worker_);
|
||||
loadScalar(j, "pool_threads", pool_threads_);
|
||||
loadScalar(j, "pool_tls", pool_tls_);
|
||||
loadScalar(j, "pool_hugepages", pool_hugepages_);
|
||||
loadScalar(j, "pool_mode", pool_mode_);
|
||||
if (j.contains("pool_select_mode")) pool_select_mode_ = parsePoolSelectMode(j["pool_select_mode"]);
|
||||
if (j.contains("mine_when_idle")) mine_when_idle_ = j["mine_when_idle"].get<bool>();
|
||||
if (j.contains("xmrig_version")) xmrig_version_ = j["xmrig_version"].get<std::string>();
|
||||
if (j.contains("mine_idle_delay")) mine_idle_delay_= std::max(30, j["mine_idle_delay"].get<int>());
|
||||
if (j.contains("idle_thread_scaling")) idle_thread_scaling_ = j["idle_thread_scaling"].get<bool>();
|
||||
if (j.contains("idle_threads_active")) idle_threads_active_ = j["idle_threads_active"].get<int>();
|
||||
if (j.contains("idle_threads_idle")) idle_threads_idle_ = j["idle_threads_idle"].get<int>();
|
||||
if (j.contains("idle_gpu_aware")) idle_gpu_aware_ = j["idle_gpu_aware"].get<bool>();
|
||||
loadScalar(j, "mine_when_idle", mine_when_idle_);
|
||||
loadScalar(j, "xmrig_version", xmrig_version_);
|
||||
// Lower-bounded only (min 30s), matching setMineIdleDelay; now type-guarded too.
|
||||
if (j.contains("mine_idle_delay") && j["mine_idle_delay"].is_number_integer())
|
||||
mine_idle_delay_ = std::max(30, j["mine_idle_delay"].get<int>());
|
||||
loadScalar(j, "idle_thread_scaling", idle_thread_scaling_);
|
||||
loadScalar(j, "idle_threads_active", idle_threads_active_);
|
||||
loadScalar(j, "idle_threads_idle", idle_threads_idle_);
|
||||
loadScalar(j, "idle_gpu_aware", idle_gpu_aware_);
|
||||
if (j.contains("saved_pool_urls") && j["saved_pool_urls"].is_array()) {
|
||||
saved_pool_urls_.clear();
|
||||
for (const auto& u : j["saved_pool_urls"])
|
||||
@@ -307,15 +339,12 @@ bool Settings::load(const std::string& path)
|
||||
if (!entry.label.empty()) portfolio_entries_.push_back(std::move(entry));
|
||||
}
|
||||
}
|
||||
if (j.contains("font_scale") && j["font_scale"].is_number())
|
||||
font_scale_ = std::max(1.0f, std::min(1.5f, j["font_scale"].get<float>()));
|
||||
if (j.contains("window_width") && j["window_width"].is_number_integer())
|
||||
window_width_ = j["window_width"].get<int>();
|
||||
if (j.contains("window_height") && j["window_height"].is_number_integer())
|
||||
window_height_ = j["window_height"].get<int>();
|
||||
loadClamped(j, "font_scale", font_scale_, 1.0f, 1.5f);
|
||||
loadScalar(j, "window_width", window_width_);
|
||||
loadScalar(j, "window_height", window_height_);
|
||||
|
||||
// Version tracking — detect upgrades so we can re-save with new defaults
|
||||
if (j.contains("settings_version")) settings_version_ = j["settings_version"].get<std::string>();
|
||||
loadScalar(j, "settings_version", settings_version_);
|
||||
if (settings_version_ != DRAGONX_VERSION) {
|
||||
DEBUG_LOGF("Settings version %s differs from wallet %s — will re-save\n",
|
||||
settings_version_.empty() ? "(none)" : settings_version_.c_str(),
|
||||
|
||||
@@ -290,13 +290,8 @@ json RPCClient::makePayload(const std::string& method, const json& params)
|
||||
};
|
||||
}
|
||||
|
||||
json RPCClient::call(const std::string& method, const json& params)
|
||||
std::string RPCClient::performCall(const std::string& method, const json& params, long& httpCodeOut)
|
||||
{
|
||||
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);
|
||||
@@ -310,22 +305,27 @@ json RPCClient::call(const std::string& method, const json& params)
|
||||
|
||||
// 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);
|
||||
|
||||
httpCodeOut = 0;
|
||||
curl_easy_getinfo(impl_->curl, CURLINFO_RESPONSE_CODE, &httpCodeOut);
|
||||
|
||||
return response_data;
|
||||
}
|
||||
|
||||
json RPCClient::parseRpcResult(long httpCode, const std::string& body)
|
||||
{
|
||||
// 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) {
|
||||
if (httpCode != 200) {
|
||||
int errCode = 0;
|
||||
try {
|
||||
json response = json::parse(response_data);
|
||||
json response = json::parse(body);
|
||||
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>();
|
||||
@@ -337,10 +337,10 @@ json RPCClient::call(const std::string& method, const json& params)
|
||||
} 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));
|
||||
throw RpcError(errCode, "RPC error: HTTP " + std::to_string(httpCode));
|
||||
}
|
||||
|
||||
json response = json::parse(response_data);
|
||||
json response = json::parse(body);
|
||||
|
||||
if (response.contains("error") && !response["error"].is_null()) {
|
||||
int errCode = 0;
|
||||
@@ -358,6 +358,18 @@ json RPCClient::call(const std::string& method, const json& params)
|
||||
return response["result"];
|
||||
}
|
||||
|
||||
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");
|
||||
}
|
||||
|
||||
long http_code = 0;
|
||||
std::string response_data = performCall(method, params, http_code);
|
||||
return parseRpcResult(http_code, response_data);
|
||||
}
|
||||
|
||||
json RPCClient::call(const std::string& method, const json& params, long timeoutSec)
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> lk(curl_mutex_);
|
||||
@@ -365,65 +377,16 @@ json RPCClient::call(const std::string& method, const json& params, long timeout
|
||||
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);
|
||||
|
||||
long http_code = 0;
|
||||
std::string response_data = performCall(method, params, http_code);
|
||||
// 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"];
|
||||
return parseRpcResult(http_code, response_data);
|
||||
} catch (...) {
|
||||
// Ensure timeout is always restored
|
||||
curl_easy_setopt(impl_->curl, CURLOPT_TIMEOUT, prevTimeout);
|
||||
@@ -762,43 +725,40 @@ void RPCClient::z_listReceivedByAddress(const std::string& address, int minconf,
|
||||
doRPC("z_listreceivedbyaddress", {address, minconf}, cb, err);
|
||||
}
|
||||
|
||||
// Unified callback versions
|
||||
void RPCClient::getInfo(UnifiedCallback cb)
|
||||
// Adapts a UnifiedCallback into the (Callback, ErrorCallback) pair doRPC expects: the
|
||||
// success lambda forwards (result, ""), the error lambda forwards ({}, error). The cb
|
||||
// null-check is preserved on each invocation.
|
||||
std::pair<Callback, ErrorCallback> RPCClient::splitUnified(UnifiedCallback cb)
|
||||
{
|
||||
doRPC("getinfo", {},
|
||||
return {
|
||||
[cb](const json& result) {
|
||||
if (cb) cb(result, "");
|
||||
},
|
||||
[cb](const std::string& error) {
|
||||
if (cb) cb(json{}, error);
|
||||
}
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
// Unified callback versions
|
||||
void RPCClient::getInfo(UnifiedCallback cb)
|
||||
{
|
||||
auto handlers = splitUnified(std::move(cb));
|
||||
doRPC("getinfo", {}, handlers.first, handlers.second);
|
||||
}
|
||||
|
||||
void RPCClient::rescanBlockchain(int startHeight, UnifiedCallback cb)
|
||||
{
|
||||
// hush/komodo daemons expose this as "rescan <height>", not bitcoin's "rescanblockchain".
|
||||
doRPC("rescan", {startHeight},
|
||||
[cb](const json& result) {
|
||||
if (cb) cb(result, "");
|
||||
},
|
||||
[cb](const std::string& error) {
|
||||
if (cb) cb(json{}, error);
|
||||
}
|
||||
);
|
||||
auto handlers = splitUnified(std::move(cb));
|
||||
doRPC("rescan", {startHeight}, handlers.first, handlers.second);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
);
|
||||
auto handlers = splitUnified(std::move(cb));
|
||||
doRPC("z_shieldcoinbase", {fromAddr, toAddr, fee, limit}, handlers.first, handlers.second);
|
||||
}
|
||||
|
||||
void RPCClient::z_mergeToAddress(const std::vector<std::string>& fromAddrs, const std::string& toAddr,
|
||||
@@ -808,14 +768,8 @@ void RPCClient::z_mergeToAddress(const std::vector<std::string>& fromAddrs, cons
|
||||
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);
|
||||
}
|
||||
);
|
||||
auto handlers = splitUnified(std::move(cb));
|
||||
doRPC("z_mergetoaddress", {addrs, toAddr, fee, 0, limit}, handlers.first, handlers.second);
|
||||
}
|
||||
|
||||
void RPCClient::z_getOperationStatus(const std::vector<std::string>& opids, UnifiedCallback cb)
|
||||
@@ -824,30 +778,18 @@ void RPCClient::z_getOperationStatus(const std::vector<std::string>& opids, Unif
|
||||
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);
|
||||
}
|
||||
);
|
||||
auto handlers = splitUnified(std::move(cb));
|
||||
doRPC("z_getoperationstatus", {ids}, handlers.first, handlers.second);
|
||||
}
|
||||
|
||||
void RPCClient::getBlock(int height, UnifiedCallback cb)
|
||||
{
|
||||
// First get block hash, then get block
|
||||
getBlockHash(height,
|
||||
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);
|
||||
}
|
||||
);
|
||||
auto handlers = splitUnified(cb);
|
||||
getBlock(hash, handlers.first, handlers.second);
|
||||
},
|
||||
[cb](const std::string& error) {
|
||||
if (cb) cb(json{}, error);
|
||||
|
||||
@@ -245,6 +245,17 @@ 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);
|
||||
|
||||
// Shared blocking-call core: dumps the payload, performs the request (throwing on a
|
||||
// curl transport error) and returns the response body plus HTTP status. Caller must
|
||||
// hold curl_mutex_ and have verified impl_->curl.
|
||||
std::string performCall(const std::string& method, const json& params, long& httpCodeOut);
|
||||
// Centralizes the HTTP-code check and JSON error->RpcError extraction, returning
|
||||
// response["result"] on success.
|
||||
static json parseRpcResult(long httpCode, const std::string& body);
|
||||
|
||||
// Splits a UnifiedCallback into the (Callback, ErrorCallback) pair used by doRPC.
|
||||
static std::pair<Callback, ErrorCallback> splitUnified(UnifiedCallback cb);
|
||||
|
||||
std::string host_;
|
||||
std::string port_;
|
||||
std::string auth_; // Base64 encoded "user:password"
|
||||
|
||||
@@ -15,6 +15,23 @@ namespace dragonx {
|
||||
namespace ui {
|
||||
namespace effects {
|
||||
|
||||
namespace {
|
||||
|
||||
// Colors are packed little-endian RGBA (R at bit 0, G at 8, B at 16, A at 24) — the
|
||||
// same layout IM_COL32 produces. unpackRGB pulls out the three color channels (alpha is
|
||||
// handled separately at each site).
|
||||
struct RGB { int r, g, b; };
|
||||
static inline RGB unpackRGB(ImU32 c) {
|
||||
return RGB{ (int)(c & 0xFF), (int)((c >> 8) & 0xFF), (int)((c >> 16) & 0xFF) };
|
||||
}
|
||||
|
||||
// Scales a 0..1 base alpha by the background opacity into a clamped 0..255 int.
|
||||
static inline int scaledAlpha(float base, float bgOpacity) {
|
||||
return std::clamp((int)(base * bgOpacity * 255.0f), 0, 255);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// ============================================================================
|
||||
// Singleton
|
||||
// ============================================================================
|
||||
@@ -327,16 +344,13 @@ void ThemeEffects::drawShimmer(ImDrawList* dl, ImVec2 pMin, ImVec2 pMax,
|
||||
if (clLeft >= clRight) return;
|
||||
|
||||
float mid = (clLeft + clRight) * 0.5f;
|
||||
int peakA = (int)(shimmer_.alpha * bgOpacity_ * 255.0f);
|
||||
peakA = std::clamp(peakA, 0, 255);
|
||||
int peakA = scaledAlpha(shimmer_.alpha, bgOpacity_);
|
||||
|
||||
// Extract shimmer color RGB (ignore alpha, use shimmer_.alpha)
|
||||
int sR = shimmer_.color & 0xFF;
|
||||
int sG = (shimmer_.color >> 8) & 0xFF;
|
||||
int sB = (shimmer_.color >> 16) & 0xFF;
|
||||
RGB s = unpackRGB(shimmer_.color);
|
||||
|
||||
ImU32 clear = IM_COL32(sR, sG, sB, 0);
|
||||
ImU32 peak = IM_COL32(sR, sG, sB, peakA);
|
||||
ImU32 clear = IM_COL32(s.r, s.g, s.b, 0);
|
||||
ImU32 peak = IM_COL32(s.r, s.g, s.b, peakA);
|
||||
|
||||
// Inset clip rect by corner rounding to prevent shimmer bleeding into rounded corners
|
||||
float cr = std::min(rounding, std::min(w, h) * 0.5f) * 0.3f;
|
||||
@@ -374,9 +388,7 @@ void ThemeEffects::drawSpecularGlare(ImDrawList* dl, ImVec2 pMin, ImVec2 pMax,
|
||||
float minDim = std::min(w, h);
|
||||
float glareR = minDim * specular_glare_.radius;
|
||||
|
||||
int cR = specular_glare_.color & 0xFF;
|
||||
int cG = (specular_glare_.color >> 8) & 0xFF;
|
||||
int cB = (specular_glare_.color >> 16) & 0xFF;
|
||||
RGB c = unpackRGB(specular_glare_.color);
|
||||
|
||||
// Per-panel seed from position — unique drift per panel
|
||||
float seed = pMin.x * 0.0073f + pMin.y * 0.0137f;
|
||||
@@ -410,7 +422,7 @@ void ThemeEffects::drawSpecularGlare(ImDrawList* dl, ImVec2 pMin, ImVec2 pMax,
|
||||
if (a <= 0) continue;
|
||||
|
||||
dl->AddCircleFilled(ImVec2(cx, cy), ringRadius,
|
||||
IM_COL32(cR, cG, cB, a), 32);
|
||||
IM_COL32(c.r, c.g, c.b, a), 32);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -445,18 +457,14 @@ ImU32 ThemeEffects::tintByPosition(ImU32 baseColor, float screenY) const {
|
||||
float s = positional_hue_.strength;
|
||||
float inv = 1.0f - s;
|
||||
|
||||
int bR = baseColor & 0xFF;
|
||||
int bG = (baseColor >> 8) & 0xFF;
|
||||
int bB = (baseColor >> 16) & 0xFF;
|
||||
RGB base = unpackRGB(baseColor);
|
||||
int bA = (baseColor >> 24) & 0xFF;
|
||||
|
||||
int tR = tint & 0xFF;
|
||||
int tG = (tint >> 8) & 0xFF;
|
||||
int tB = (tint >> 16) & 0xFF;
|
||||
RGB t = unpackRGB(tint);
|
||||
|
||||
int r = (int)(bR * inv + tR * s);
|
||||
int g = (int)(bG * inv + tG * s);
|
||||
int b = (int)(bB * inv + tB * s);
|
||||
int r = (int)(base.r * inv + t.r * s);
|
||||
int g = (int)(base.g * inv + t.g * s);
|
||||
int b = (int)(base.b * inv + t.b * s);
|
||||
|
||||
return IM_COL32(r, g, b, bA);
|
||||
}
|
||||
@@ -474,10 +482,8 @@ void ThemeEffects::drawGlowPulse(ImDrawList* dl, ImVec2 pMin, ImVec2 pMax,
|
||||
float alpha = glow_pulse_.minAlpha + phase * (glow_pulse_.maxAlpha - glow_pulse_.minAlpha);
|
||||
if (alpha <= 0.001f) return;
|
||||
|
||||
int a = std::clamp((int)(alpha * bgOpacity_ * 255.0f), 0, 255);
|
||||
int cR = glow_pulse_.color & 0xFF;
|
||||
int cG = (glow_pulse_.color >> 8) & 0xFF;
|
||||
int cB = (glow_pulse_.color >> 16) & 0xFF;
|
||||
int a = scaledAlpha(alpha, bgOpacity_);
|
||||
RGB c = unpackRGB(glow_pulse_.color);
|
||||
|
||||
// Border-only glow: concentric outlines with smooth Gaussian falloff.
|
||||
// Use many fine sub-rings (4× the pixel radius) to avoid visible
|
||||
@@ -496,7 +502,7 @@ void ThemeEffects::drawGlowPulse(ImDrawList* dl, ImVec2 pMin, ImVec2 pMax,
|
||||
dl->AddRect(
|
||||
ImVec2(pMin.x - expand, pMin.y - expand),
|
||||
ImVec2(pMax.x + expand, pMax.y + expand),
|
||||
IM_COL32(cR, cG, cB, ringAlpha),
|
||||
IM_COL32(c.r, c.g, c.b, ringAlpha),
|
||||
rounding + expand * 0.5f, 0, thickness);
|
||||
}
|
||||
}
|
||||
@@ -513,17 +519,13 @@ void ThemeEffects::drawGradientBorderShift(ImDrawList* dl, ImVec2 pMin, ImVec2 p
|
||||
float phase = std::sin(time_ * gradient_border_.speed * 2.0f * 3.14159265f) * 0.5f + 0.5f;
|
||||
|
||||
// Extract RGBA from both colors and lerp
|
||||
int aR = gradient_border_.colorA & 0xFF;
|
||||
int aG = (gradient_border_.colorA >> 8) & 0xFF;
|
||||
int aB = (gradient_border_.colorA >> 16) & 0xFF;
|
||||
int bR = gradient_border_.colorB & 0xFF;
|
||||
int bG = (gradient_border_.colorB >> 8) & 0xFF;
|
||||
int bB = (gradient_border_.colorB >> 16) & 0xFF;
|
||||
RGB ca = unpackRGB(gradient_border_.colorA);
|
||||
RGB cb = unpackRGB(gradient_border_.colorB);
|
||||
|
||||
int r = aR + (int)((bR - aR) * phase);
|
||||
int g = aG + (int)((bG - aG) * phase);
|
||||
int b = aB + (int)((bB - aB) * phase);
|
||||
int a = std::clamp((int)(gradient_border_.alpha * bgOpacity_ * 255.0f), 0, 255);
|
||||
int r = ca.r + (int)((cb.r - ca.r) * phase);
|
||||
int g = ca.g + (int)((cb.g - ca.g) * phase);
|
||||
int b = ca.b + (int)((cb.b - ca.b) * phase);
|
||||
int a = scaledAlpha(gradient_border_.alpha, bgOpacity_);
|
||||
|
||||
// Draw the shifting border
|
||||
dl->AddRect(pMin, pMax, IM_COL32(r, g, b, a),
|
||||
@@ -640,9 +642,7 @@ void ThemeEffects::drawEdgeTrace(ImDrawList* dl, ImVec2 pMin, ImVec2 pMax,
|
||||
float headPos = std::fmod(time_ * edge_trace_.speed, 1.0f);
|
||||
float traceLen = edge_trace_.length;
|
||||
|
||||
int cR = edge_trace_.color & 0xFF;
|
||||
int cG = (edge_trace_.color >> 8) & 0xFF;
|
||||
int cB = (edge_trace_.color >> 16) & 0xFF;
|
||||
RGB c = unpackRGB(edge_trace_.color);
|
||||
|
||||
// Draw the trace as connected line segments with fading alpha.
|
||||
// Use enough segments to make curved corners smooth.
|
||||
@@ -667,16 +667,16 @@ void ThemeEffects::drawEdgeTrace(ImDrawList* dl, ImVec2 pMin, ImVec2 pMax,
|
||||
aFrac = aFrac * aFrac; // quadratic falloff for natural fade
|
||||
int a = (int)(edge_trace_.alpha * aFrac * bgOpacity_ * 255.0f);
|
||||
if (a > 0) {
|
||||
dl->AddLine(prev, pt, IM_COL32(cR, cG, cB, a), edge_trace_.thickness);
|
||||
dl->AddLine(prev, pt, IM_COL32(c.r, c.g, c.b, a), edge_trace_.thickness);
|
||||
}
|
||||
prev = pt;
|
||||
}
|
||||
|
||||
// Bright dot at the head
|
||||
int headA = (int)(edge_trace_.alpha * bgOpacity_ * 255.0f);
|
||||
int headA = scaledAlpha(edge_trace_.alpha, bgOpacity_);
|
||||
ImVec2 headPt = perimeterPoint(pMin, pMax, headPos, rounding);
|
||||
dl->AddCircleFilled(headPt, edge_trace_.thickness * 1.5f,
|
||||
IM_COL32(cR, cG, cB, headA), 8);
|
||||
IM_COL32(c.r, c.g, c.b, headA), 8);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
@@ -690,9 +690,7 @@ void ThemeEffects::drawEmberRise(ImDrawList* dl, ImVec2 pMin, ImVec2 pMax) const
|
||||
float h = pMax.y - pMin.y;
|
||||
if (w <= 0 || h <= 0) return;
|
||||
|
||||
int cR = ember_rise_.color & 0xFF;
|
||||
int cG = (ember_rise_.color >> 8) & 0xFF;
|
||||
int cB = (ember_rise_.color >> 16) & 0xFF;
|
||||
RGB c = unpackRGB(ember_rise_.color);
|
||||
|
||||
for (int i = 0; i < ember_rise_.count; i++) {
|
||||
// Golden-ratio spacing gives evenly distributed phases
|
||||
@@ -723,11 +721,11 @@ void ThemeEffects::drawEmberRise(ImDrawList* dl, ImVec2 pMin, ImVec2 pMax) const
|
||||
|
||||
// Warm core
|
||||
dl->AddCircleFilled(ImVec2(x, y), size,
|
||||
IM_COL32(cR, cG, cB, a), 6);
|
||||
IM_COL32(c.r, c.g, c.b, a), 6);
|
||||
// Softer outer glow
|
||||
if (a > 30) {
|
||||
dl->AddCircleFilled(ImVec2(x, y), size * 2.0f,
|
||||
IM_COL32(cR, cG, cB, a / 4), 6);
|
||||
IM_COL32(c.r, c.g, c.b, a / 4), 6);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -746,9 +744,7 @@ void ThemeEffects::drawViewportEmbers(ImDrawList* dl) const {
|
||||
float vpY = vp->WorkPos.y;
|
||||
if (vpW <= 0 || vpH <= 0) return;
|
||||
|
||||
int cR = ember_rise_.color & 0xFF;
|
||||
int cG = (ember_rise_.color >> 8) & 0xFF;
|
||||
int cB = (ember_rise_.color >> 16) & 0xFF;
|
||||
RGB c = unpackRGB(ember_rise_.color);
|
||||
|
||||
// Viewport embers: more particles, spread across the whole screen
|
||||
int vpCount = ember_rise_.count * 3;
|
||||
@@ -777,10 +773,10 @@ void ThemeEffects::drawViewportEmbers(ImDrawList* dl) const {
|
||||
if (a <= 0) continue;
|
||||
|
||||
dl->AddCircleFilled(ImVec2(x, y), size,
|
||||
IM_COL32(cR, cG, cB, a), 6);
|
||||
IM_COL32(c.r, c.g, c.b, a), 6);
|
||||
if (a > 20) {
|
||||
dl->AddCircleFilled(ImVec2(x, y), size * 2.5f,
|
||||
IM_COL32(cR, cG, cB, a / 5), 6);
|
||||
IM_COL32(c.r, c.g, c.b, a / 5), 6);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -799,9 +795,7 @@ void ThemeEffects::drawSandstorm(ImDrawList* dl) const {
|
||||
float vpY = vp->WorkPos.y;
|
||||
if (vpW <= 0 || vpH <= 0) return;
|
||||
|
||||
int cR = sandstorm_.color & 0xFF;
|
||||
int cG = (sandstorm_.color >> 8) & 0xFF;
|
||||
int cB = (sandstorm_.color >> 16) & 0xFF;
|
||||
RGB c = unpackRGB(sandstorm_.color);
|
||||
|
||||
// Wind direction vector from angle (degrees from horizontal)
|
||||
float windRad = sandstorm_.windAngle * 3.14159265f / 180.0f;
|
||||
@@ -875,21 +869,21 @@ void ThemeEffects::drawSandstorm(ImDrawList* dl) const {
|
||||
dl->AddLine(
|
||||
ImVec2(x, y),
|
||||
ImVec2(x + sx, y + sy),
|
||||
IM_COL32(cR, cG, cB, a),
|
||||
IM_COL32(c.r, c.g, c.b, a),
|
||||
size * 0.7f);
|
||||
// Bright head dot
|
||||
dl->AddCircleFilled(ImVec2(x, y), size * 0.5f,
|
||||
IM_COL32(cR, cG, cB, std::min(255, a * 3 / 2)), 5);
|
||||
IM_COL32(c.r, c.g, c.b, std::min(255, a * 3 / 2)), 5);
|
||||
} else {
|
||||
// Small/slow particles: simple circle
|
||||
dl->AddCircleFilled(ImVec2(x, y), size,
|
||||
IM_COL32(cR, cG, cB, a), 6);
|
||||
IM_COL32(c.r, c.g, c.b, a), 6);
|
||||
}
|
||||
|
||||
// Occasional larger dust puff (every ~8th particle, double size, half alpha)
|
||||
if (i % 8 == 0 && a > 15) {
|
||||
dl->AddCircleFilled(ImVec2(x, y), size * 2.5f,
|
||||
IM_COL32(cR, cG, cB, a / 4), 8);
|
||||
IM_COL32(c.r, c.g, c.b, a / 4), 8);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -916,9 +910,7 @@ void ThemeEffects::drawPanelEffects(ImDrawList* dl, ImVec2 pMin, ImVec2 pMax,
|
||||
float headPos = std::fmod(time_ * edge_trace_.speed + posKey, 1.0f);
|
||||
float traceLen = edge_trace_.length;
|
||||
|
||||
int cR = edge_trace_.color & 0xFF;
|
||||
int cG = (edge_trace_.color >> 8) & 0xFF;
|
||||
int cB = (edge_trace_.color >> 16) & 0xFF;
|
||||
RGB c = unpackRGB(edge_trace_.color);
|
||||
|
||||
// Panel edge trace is subtler than sidebar — 60% alpha
|
||||
float panelAlpha = edge_trace_.alpha * 0.6f * bgOpacity_;
|
||||
@@ -935,7 +927,7 @@ void ThemeEffects::drawPanelEffects(ImDrawList* dl, ImVec2 pMin, ImVec2 pMax,
|
||||
aFrac = aFrac * aFrac;
|
||||
int a = (int)(panelAlpha * aFrac * 255.0f);
|
||||
if (a > 0) {
|
||||
dl->AddLine(prev, pt, IM_COL32(cR, cG, cB, a),
|
||||
dl->AddLine(prev, pt, IM_COL32(c.r, c.g, c.b, a),
|
||||
edge_trace_.thickness * 0.8f);
|
||||
}
|
||||
prev = pt;
|
||||
@@ -945,7 +937,7 @@ void ThemeEffects::drawPanelEffects(ImDrawList* dl, ImVec2 pMin, ImVec2 pMax,
|
||||
int headA = (int)(panelAlpha * 255.0f);
|
||||
ImVec2 headPt = perimeterPoint(pMin, pMax, headPos, rounding);
|
||||
dl->AddCircleFilled(headPt, edge_trace_.thickness * 1.2f,
|
||||
IM_COL32(cR, cG, cB, headA), 6);
|
||||
IM_COL32(c.r, c.g, c.b, headA), 6);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -958,9 +950,7 @@ void ThemeEffects::drawPanelEffects(ImDrawList* dl, ImVec2 pMin, ImVec2 pMax,
|
||||
int panelCount = std::max(2, ember_rise_.count / 3);
|
||||
float panelAlpha = ember_rise_.alpha * 0.5f * bgOpacity_;
|
||||
|
||||
int cR = ember_rise_.color & 0xFF;
|
||||
int cG = (ember_rise_.color >> 8) & 0xFF;
|
||||
int cB = (ember_rise_.color >> 16) & 0xFF;
|
||||
RGB c = unpackRGB(ember_rise_.color);
|
||||
|
||||
// Use panel position as seed for unique particle distribution
|
||||
float seed = pMin.x * 0.013f + pMin.y * 0.031f;
|
||||
@@ -987,7 +977,7 @@ void ThemeEffects::drawPanelEffects(ImDrawList* dl, ImVec2 pMin, ImVec2 pMax,
|
||||
if (a <= 0) continue;
|
||||
|
||||
dl->AddCircleFilled(ImVec2(x, y), size,
|
||||
IM_COL32(cR, cG, cB, a), 6);
|
||||
IM_COL32(c.r, c.g, c.b, a), 6);
|
||||
}
|
||||
|
||||
dl->PopClipRect();
|
||||
@@ -1074,14 +1064,12 @@ void ThemeEffects::drawViewportOverlay(ImDrawList* dl) const {
|
||||
// === Vignette: edge darkening/tinting (4 gradient strips) ===
|
||||
// Overlapping strips naturally darken corners more than edges — cinematic look
|
||||
if (viewport_overlay_.vignetteEnabled) {
|
||||
int cR = viewport_overlay_.vignetteColor & 0xFF;
|
||||
int cG = (viewport_overlay_.vignetteColor >> 8) & 0xFF;
|
||||
int cB = (viewport_overlay_.vignetteColor >> 16) & 0xFF;
|
||||
int maxA = std::clamp((int)(viewport_overlay_.vignetteAlpha * bgOpacity_ * 255.0f), 0, 255);
|
||||
RGB c = unpackRGB(viewport_overlay_.vignetteColor);
|
||||
int maxA = scaledAlpha(viewport_overlay_.vignetteAlpha, bgOpacity_);
|
||||
|
||||
if (maxA > 0) {
|
||||
ImU32 edgeCol = IM_COL32(cR, cG, cB, maxA);
|
||||
ImU32 clearCol = IM_COL32(cR, cG, cB, 0);
|
||||
ImU32 edgeCol = IM_COL32(c.r, c.g, c.b, maxA);
|
||||
ImU32 clearCol = IM_COL32(c.r, c.g, c.b, 0);
|
||||
|
||||
float fadeW = vpSize.x * viewport_overlay_.vignetteRadius;
|
||||
float fadeH = vpSize.y * viewport_overlay_.vignetteRadius;
|
||||
|
||||
@@ -13,17 +13,6 @@ namespace wallet {
|
||||
|
||||
namespace {
|
||||
|
||||
std::string trimCopy(const std::string& value)
|
||||
{
|
||||
auto begin = value.begin();
|
||||
while (begin != value.end() && std::isspace(static_cast<unsigned char>(*begin))) ++begin;
|
||||
|
||||
auto end = value.end();
|
||||
while (end != begin && std::isspace(static_cast<unsigned char>(*(end - 1)))) --end;
|
||||
|
||||
return std::string(begin, end);
|
||||
}
|
||||
|
||||
bool startsWith(const std::string& value, const char* prefix)
|
||||
{
|
||||
const std::string prefixValue(prefix);
|
||||
@@ -34,7 +23,7 @@ bool startsWith(const std::string& value, const char* prefix)
|
||||
LiteServerEndpoint trimmedEndpoint(const LiteServerEndpoint& endpoint)
|
||||
{
|
||||
LiteServerEndpoint normalized = endpoint;
|
||||
normalized.url = trimCopy(normalized.url);
|
||||
normalized.url = liteTrimCopy(normalized.url);
|
||||
return normalized;
|
||||
}
|
||||
|
||||
@@ -52,12 +41,23 @@ std::vector<std::pair<std::size_t, LiteServerEndpoint>> usableConfiguredServers(
|
||||
|
||||
std::string normalizedChainName(const std::string& chainName)
|
||||
{
|
||||
const std::string normalized = trimCopy(chainName);
|
||||
const std::string normalized = liteTrimCopy(chainName);
|
||||
return normalized.empty() ? kDragonXLiteChainName : normalized;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
std::string liteTrimCopy(const std::string& value)
|
||||
{
|
||||
auto begin = value.begin();
|
||||
while (begin != value.end() && std::isspace(static_cast<unsigned char>(*begin))) ++begin;
|
||||
|
||||
auto end = value.end();
|
||||
while (end != begin && std::isspace(static_cast<unsigned char>(*(end - 1)))) --end;
|
||||
|
||||
return std::string(begin, end);
|
||||
}
|
||||
|
||||
std::vector<LiteServerEndpoint> defaultDragonXLiteServers()
|
||||
{
|
||||
return {
|
||||
@@ -83,13 +83,13 @@ LiteConnectionSettings defaultLiteConnectionSettings()
|
||||
|
||||
bool isLiteServerUrlUsable(const std::string& serverUrl)
|
||||
{
|
||||
const std::string normalized = trimCopy(serverUrl);
|
||||
const std::string normalized = liteTrimCopy(serverUrl);
|
||||
return startsWith(normalized, "https://") || startsWith(normalized, "http://");
|
||||
}
|
||||
|
||||
bool isOfficialLiteServer(const std::string& serverUrl)
|
||||
{
|
||||
const std::string url = trimCopy(serverUrl);
|
||||
const std::string url = liteTrimCopy(serverUrl);
|
||||
for (const auto& s : defaultDragonXLiteServers())
|
||||
if (s.url == url) return true;
|
||||
return false;
|
||||
@@ -126,7 +126,7 @@ const char* liteConnectionAvailabilityName(LiteConnectionAvailability availabili
|
||||
LiteServerSelectionResult selectLiteServer(const LiteConnectionSettings& settings)
|
||||
{
|
||||
auto usableServers = usableConfiguredServers(settings);
|
||||
const std::string stickyServerUrl = trimCopy(settings.stickyServerUrl);
|
||||
const std::string stickyServerUrl = liteTrimCopy(settings.stickyServerUrl);
|
||||
|
||||
if (settings.selectionMode == LiteServerSelectionMode::Sticky &&
|
||||
isLiteServerUrlUsable(stickyServerUrl)) {
|
||||
|
||||
@@ -84,6 +84,8 @@ struct LiteServerHealthCheckResult {
|
||||
std::string error;
|
||||
};
|
||||
|
||||
// Trims leading/trailing whitespace, returning a copy. Shared across the lite slice.
|
||||
std::string liteTrimCopy(const std::string& value);
|
||||
std::vector<LiteServerEndpoint> defaultDragonXLiteServers();
|
||||
LiteConnectionSettings defaultLiteConnectionSettings();
|
||||
bool isLiteServerUrlUsable(const std::string& serverUrl);
|
||||
|
||||
@@ -1,24 +1,12 @@
|
||||
#include "wallet/lite_sync_service.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <limits>
|
||||
#include <utility>
|
||||
|
||||
namespace dragonx::wallet {
|
||||
namespace {
|
||||
|
||||
std::string trimSyncCopy(const std::string& value)
|
||||
{
|
||||
auto begin = value.begin();
|
||||
while (begin != value.end() && std::isspace(static_cast<unsigned char>(*begin))) ++begin;
|
||||
|
||||
auto end = value.end();
|
||||
while (end != begin && std::isspace(static_cast<unsigned char>(*(end - 1)))) --end;
|
||||
|
||||
return std::string(begin, end);
|
||||
}
|
||||
|
||||
std::uint64_t effectiveStagnantThreshold(std::uint64_t threshold)
|
||||
{
|
||||
return threshold == 0 ? 1 : threshold;
|
||||
@@ -283,7 +271,7 @@ LiteSyncStatusResult LiteSyncService::pollSyncStatus(const LiteSyncStatusRequest
|
||||
|
||||
LiteServerSelectionResult LiteSyncService::selectServerForRequest(const std::string& serverUrl) const
|
||||
{
|
||||
const auto overrideUrl = trimSyncCopy(serverUrl);
|
||||
const auto overrideUrl = liteTrimCopy(serverUrl);
|
||||
if (!overrideUrl.empty()) {
|
||||
if (!isLiteServerUrlUsable(overrideUrl)) {
|
||||
return LiteServerSelectionResult{false, {}, 0, false, "lite sync server URL is not usable"};
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include "lite_wallet_controller.h"
|
||||
|
||||
#include "lite_diagnostics.h"
|
||||
#include "lite_result_parsers.h"
|
||||
#include "../data/wallet_state.h"
|
||||
|
||||
#include <algorithm>
|
||||
@@ -695,19 +696,14 @@ std::optional<LiteWalletAppRefreshModel> LiteWalletController::refreshModel()
|
||||
if (bridge_) {
|
||||
const auto h = bridge_->execute("height", "");
|
||||
if (h.ok) {
|
||||
try {
|
||||
const auto j = nlohmann::json::parse(h.value);
|
||||
if (j.is_object() && j.contains("height") && j["height"].is_number_unsigned()) {
|
||||
const unsigned long long height = j["height"].get<unsigned long long>();
|
||||
if (height > 0) {
|
||||
model.hasSyncStatus = true;
|
||||
model.sync.walletHeight = height;
|
||||
model.sync.chainHeight = height; // synced: wallet height == chain tip
|
||||
model.sync.complete = true;
|
||||
model.sync.progress = 1.0;
|
||||
}
|
||||
}
|
||||
} catch (...) { /* leave the mapped sync fields as-is */ }
|
||||
const auto parsed = parseLiteHeightResponse(h.value);
|
||||
if (parsed.ok && parsed.height.height > 0) {
|
||||
model.hasSyncStatus = true;
|
||||
model.sync.walletHeight = parsed.height.height;
|
||||
model.sync.chainHeight = parsed.height.height; // synced: wallet height == chain tip
|
||||
model.sync.complete = true;
|
||||
model.sync.progress = 1.0;
|
||||
}
|
||||
}
|
||||
}
|
||||
return model;
|
||||
|
||||
@@ -1,22 +1,10 @@
|
||||
#include "wallet/lite_wallet_gateway.h"
|
||||
|
||||
#include <cctype>
|
||||
#include <utility>
|
||||
|
||||
namespace dragonx::wallet {
|
||||
namespace {
|
||||
|
||||
std::string trimGatewayCopy(const std::string& value)
|
||||
{
|
||||
auto begin = value.begin();
|
||||
while (begin != value.end() && std::isspace(static_cast<unsigned char>(*begin))) ++begin;
|
||||
|
||||
auto end = value.end();
|
||||
while (end != begin && std::isspace(static_cast<unsigned char>(*(end - 1)))) --end;
|
||||
|
||||
return std::string(begin, end);
|
||||
}
|
||||
|
||||
WalletBackendStatus makeGatewayStatus(WalletBackendState state, std::string message)
|
||||
{
|
||||
WalletBackendStatus status;
|
||||
@@ -262,7 +250,7 @@ LiteWalletRefreshResult LiteWalletGateway::refresh(const LiteWalletRefreshReques
|
||||
|
||||
LiteServerSelectionResult LiteWalletGateway::selectServerForRequest(const std::string& serverUrl) const
|
||||
{
|
||||
const auto overrideUrl = trimGatewayCopy(serverUrl);
|
||||
const auto overrideUrl = liteTrimCopy(serverUrl);
|
||||
if (!overrideUrl.empty()) {
|
||||
if (!isLiteServerUrlUsable(overrideUrl)) {
|
||||
return LiteServerSelectionResult{false, {}, 0, false, "lite gateway server URL is not usable"};
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
|
||||
#include "lite_wallet_lifecycle_service.h"
|
||||
|
||||
#include <cctype>
|
||||
#include <utility>
|
||||
|
||||
namespace dragonx {
|
||||
@@ -12,17 +11,6 @@ namespace wallet {
|
||||
|
||||
namespace {
|
||||
|
||||
std::string trimLifecycleCopy(const std::string& value)
|
||||
{
|
||||
auto begin = value.begin();
|
||||
while (begin != value.end() && std::isspace(static_cast<unsigned char>(*begin))) ++begin;
|
||||
|
||||
auto end = value.end();
|
||||
while (end != begin && std::isspace(static_cast<unsigned char>(*(end - 1)))) --end;
|
||||
|
||||
return std::string(begin, end);
|
||||
}
|
||||
|
||||
LiteRedactedPrivateData redactedField(LitePrivateDataKind kind, const std::string& value)
|
||||
{
|
||||
return LiteRedactedPrivateData{kind, !value.empty(), redactLitePrivateDataValue(value)};
|
||||
@@ -172,7 +160,7 @@ LiteWalletLifecyclePlan LiteWalletLifecycleService::planOpenWallet(
|
||||
LiteWalletLifecyclePlan LiteWalletLifecycleService::planRestoreWallet(
|
||||
const LiteWalletRestoreRequest& request) const
|
||||
{
|
||||
const std::string validationError = trimLifecycleCopy(request.seedPhrase).empty()
|
||||
const std::string validationError = liteTrimCopy(request.seedPhrase).empty()
|
||||
? "restore seed phrase is required"
|
||||
: std::string();
|
||||
return makePlan(LiteWalletLifecycleOperation::RestoreFromSeed,
|
||||
@@ -220,7 +208,7 @@ LiteWalletLifecycleResult LiteWalletLifecycleService::restoreWallet(
|
||||
LiteServerSelectionResult LiteWalletLifecycleService::selectServerForRequest(
|
||||
const std::string& serverUrl) const
|
||||
{
|
||||
const std::string overrideUrl = trimLifecycleCopy(serverUrl);
|
||||
const std::string overrideUrl = liteTrimCopy(serverUrl);
|
||||
if (!overrideUrl.empty()) {
|
||||
if (!isLiteServerUrlUsable(overrideUrl)) {
|
||||
return LiteServerSelectionResult{false, {}, 0, false, "lite lifecycle server URL is not usable"};
|
||||
|
||||
@@ -1,25 +1,10 @@
|
||||
#include "lite_wallet_server_selection_adapter.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
|
||||
namespace dragonx {
|
||||
namespace wallet {
|
||||
|
||||
namespace {
|
||||
|
||||
std::string trimCopy(const std::string& value)
|
||||
{
|
||||
const auto first = std::find_if_not(value.begin(), value.end(), [](unsigned char ch) {
|
||||
return std::isspace(ch) != 0;
|
||||
});
|
||||
const auto last = std::find_if_not(value.rbegin(), value.rend(), [](unsigned char ch) {
|
||||
return std::isspace(ch) != 0;
|
||||
}).base();
|
||||
if (first >= last) return {};
|
||||
return std::string(first, last);
|
||||
}
|
||||
|
||||
LiteServerSelectionMode walletModeFromSettings(
|
||||
config::Settings::LiteServerSelectionPreferenceMode mode)
|
||||
{
|
||||
@@ -52,14 +37,14 @@ LiteConnectionSettings liteConnectionSettingsFromAppSettings(const config::Setti
|
||||
connectionSettings.servers.clear();
|
||||
for (const auto& server : settings.getLiteServers()) {
|
||||
connectionSettings.servers.push_back(LiteServerEndpoint{
|
||||
trimCopy(server.url),
|
||||
liteTrimCopy(server.url),
|
||||
server.label,
|
||||
server.enabled
|
||||
});
|
||||
}
|
||||
connectionSettings.selectionMode = walletModeFromSettings(settings.getLiteServerSelectionMode());
|
||||
connectionSettings.stickyServerUrl = trimCopy(settings.getLiteStickyServerUrl());
|
||||
connectionSettings.chainName = trimCopy(settings.getLiteChainName());
|
||||
connectionSettings.stickyServerUrl = liteTrimCopy(settings.getLiteStickyServerUrl());
|
||||
connectionSettings.chainName = liteTrimCopy(settings.getLiteChainName());
|
||||
if (connectionSettings.chainName.empty()) connectionSettings.chainName = kDragonXLiteChainName;
|
||||
connectionSettings.randomSelectionSeed = settings.getLiteRandomSelectionSeed();
|
||||
return connectionSettings;
|
||||
@@ -72,24 +57,24 @@ void applyLiteConnectionSettingsToAppSettings(config::Settings& settings,
|
||||
servers.reserve(connectionSettings.servers.size());
|
||||
for (const auto& server : connectionSettings.servers) {
|
||||
servers.push_back(config::Settings::LiteServerPreference{
|
||||
trimCopy(server.url),
|
||||
liteTrimCopy(server.url),
|
||||
server.label,
|
||||
server.enabled
|
||||
});
|
||||
}
|
||||
|
||||
settings.setLiteServerSelectionMode(settingsModeFromWallet(connectionSettings.selectionMode));
|
||||
settings.setLiteStickyServerUrl(trimCopy(connectionSettings.stickyServerUrl));
|
||||
settings.setLiteChainName(trimCopy(connectionSettings.chainName).empty()
|
||||
settings.setLiteStickyServerUrl(liteTrimCopy(connectionSettings.stickyServerUrl));
|
||||
settings.setLiteChainName(liteTrimCopy(connectionSettings.chainName).empty()
|
||||
? kDragonXLiteChainName
|
||||
: trimCopy(connectionSettings.chainName));
|
||||
: liteTrimCopy(connectionSettings.chainName));
|
||||
settings.setLiteRandomSelectionSeed(connectionSettings.randomSelectionSeed);
|
||||
settings.setLiteServers(servers);
|
||||
}
|
||||
|
||||
std::string redactLiteServerSelectionValue(const std::string& value)
|
||||
{
|
||||
const std::string trimmed = trimCopy(value);
|
||||
const std::string trimmed = liteTrimCopy(value);
|
||||
if (trimmed.empty()) return "<empty>";
|
||||
const auto scheme = trimmed.find("://");
|
||||
if (scheme == std::string::npos) return "<redacted>";
|
||||
|
||||
Reference in New Issue
Block a user