On a first run or after an upgrade, Windows release builds wrote the bundled Sapling params, asmap, node and miner (about 88 MB) in App::init, before the first frame: 0.5-3 s of frozen blank window, 5-30 s on a slow disk or under antivirus. Zip, AppImage and .app builds copied the bundled files into the daemon dir and hashed the 51.5 MB of params on the UI thread at the first node start, a 0.3-1.5 s freeze in the wizard or the connect loop. A stat-only check at startup decides whether anything is missing, stale or unverified; a normal launch starts no thread and shows nothing. Otherwise one background job extracts (Windows) or copies (Linux, macOS) the files, then verifies the params once: - The first frame paints at once. If the job takes longer than 0.4 s, a chip at the bottom shows "Preparing wallet files... N%", then "Verifying wallet files...". - A node start on the UI thread while the job runs is deferred and replayed when it ends (never into a bootstrap download, a restart or a live connection); worker threads wait, and give up on quit. After an upgrade that ships a new miner, the node starts after the miner is written instead of before. Pool-miner starts and the miner version check wait too, so the old miner is never run or reported while it is being replaced. - Each file goes to "<name>.partial", is flushed and renamed into place under one lock shared with the daemon start, the bundled-node reinstall and the miner extraction, and the job stops between 4 MB chunks at shutdown. An interrupted write used to leave a truncated binary that was trusted forever; a leftover .partial is now removed. Other behaviour changes: - Linux and macOS full-node builds copy the bundled node files (about 77 MB) into the daemon dir at first launch, even if the wallet ends up on an external node; before, this happened at the first embedded start. - Lite no longer writes the 53 MB of params and asmap it never reads. - A disk-full or permission failure that leaves the node files unusable shows a warning; a miner blocked by antivirus alone does not. - A corrupt or truncated param in a zip, AppImage or .app install is replaced from the bundle and verified again instead of trusted forever. - Reinstalling the bundled node holds off the connect loop and a deferred start for its whole run (either could launch the old binary while the new one was written), and is refused while another restart is running. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
156 lines
4.8 KiB
C++
156 lines
4.8 KiB
C++
// DragonX Wallet - ImGui Edition
|
|
// Copyright 2024-2026 The Hush Developers
|
|
// Released under the GPLv3
|
|
|
|
#pragma once
|
|
|
|
#include <string>
|
|
#include <vector>
|
|
#include <utility>
|
|
|
|
namespace dragonx {
|
|
namespace rpc {
|
|
|
|
/**
|
|
* @brief Connection configuration
|
|
*/
|
|
enum class AuthSource {
|
|
Missing,
|
|
ConfigFile,
|
|
Cookie
|
|
};
|
|
|
|
struct ConnectionConfig {
|
|
std::string host = "127.0.0.1";
|
|
std::string port = "21769";
|
|
std::string rpcuser;
|
|
std::string rpcpassword;
|
|
std::string hush_dir;
|
|
std::string proxy; // SOCKS5 proxy for Tor
|
|
bool use_embedded = true;
|
|
bool use_tls = false;
|
|
bool allow_plaintext_remote = false; // rpcallowplaintext=1 — opt in to plaintext creds to a remote host
|
|
AuthSource auth_source = AuthSource::Missing;
|
|
// Non-empty when autoDetectConfig() could not create the data directory; callers
|
|
// should surface it and abort the connect rather than proceeding blindly.
|
|
std::string dir_error;
|
|
};
|
|
|
|
/**
|
|
* @brief Manages connection to dragonxd
|
|
*
|
|
* Handles auto-detection of DRAGONX.conf, starting embedded daemon,
|
|
* and connection lifecycle.
|
|
*/
|
|
class Connection {
|
|
public:
|
|
Connection();
|
|
~Connection();
|
|
|
|
/**
|
|
* @brief Auto-detect and load connection config
|
|
* @return Config from DRAGONX.conf or defaults
|
|
*/
|
|
static ConnectionConfig autoDetectConfig();
|
|
|
|
/**
|
|
* @brief Get the default DRAGONX.conf location
|
|
*/
|
|
static std::string getDefaultConfPath();
|
|
|
|
/**
|
|
* @brief Get the default DragonX data directory
|
|
*/
|
|
static std::string getDefaultDataDir();
|
|
|
|
/**
|
|
* @brief Parse a DRAGONX.conf file
|
|
* @param path Path to conf file
|
|
* @return Parsed configuration
|
|
*/
|
|
static ConnectionConfig parseConfFile(const std::string& path);
|
|
|
|
/**
|
|
* @brief Check if Sapling params exist
|
|
*/
|
|
static bool verifySaplingParams();
|
|
|
|
// Verify the Sapling params in `dir` against a { filename, expected-sha256-hex } list.
|
|
// Exposed with an injectable dir + digest list so the integrity + marker-cache logic is
|
|
// unit-testable without the real ~48MB params; verifySaplingParams() calls it with the
|
|
// pinned production digests and getSaplingParamsDir().
|
|
static bool verifySaplingParamsIn(
|
|
const std::string& dir,
|
|
const std::vector<std::pair<std::string, std::string>>& digests);
|
|
|
|
// Stat-only (never reads or hashes the params): Missing = a param is absent, Verified = the
|
|
// .sapling_verified marker matches every param's size:mtime, NeedsHash = present but unverified.
|
|
enum class SaplingParamState { Missing, NeedsHash, Verified };
|
|
static SaplingParamState saplingParamState();
|
|
static SaplingParamState saplingParamStateIn(
|
|
const std::string& dir,
|
|
const std::vector<std::pair<std::string, std::string>>& digests);
|
|
|
|
/**
|
|
* @brief Get the Sapling params directory
|
|
*/
|
|
static std::string getSaplingParamsDir();
|
|
|
|
/**
|
|
* @brief Create a default DRAGONX.conf file
|
|
* @param path Path to create the file
|
|
* @return true if created successfully
|
|
*/
|
|
static bool createDefaultConfig(const std::string& path);
|
|
|
|
/**
|
|
* @brief Ensure exportdir is set in DRAGONX.conf
|
|
* @param confPath Path to the conf file
|
|
* @return true if exportdir exists or was added
|
|
*/
|
|
static bool ensureExportDir(const std::string& confPath);
|
|
|
|
/**
|
|
* @brief Ensure wallet encryption flags are set in DRAGONX.conf
|
|
* @param confPath Path to the conf file
|
|
* @return true if flags exist or were added
|
|
*/
|
|
static bool ensureEncryptionEnabled(const std::string& confPath);
|
|
|
|
/**
|
|
* @brief Try to read .cookie auth file from the data directory
|
|
* @param dataDir Path to the daemon data directory
|
|
* @param user Output: cookie username (__cookie__)
|
|
* @param password Output: cookie password
|
|
* @return true if cookie file was read successfully
|
|
*/
|
|
static bool readAuthCookie(const std::string& dataDir, std::string& user, std::string& password);
|
|
|
|
/**
|
|
* @brief Build a cookie-auth retry config from a failed config-auth attempt
|
|
*/
|
|
static bool buildCookieAuthConfig(const ConnectionConfig& base, ConnectionConfig& cookieConfig);
|
|
|
|
/**
|
|
* @brief Whether a host is local enough for plaintext HTTP RPC
|
|
*/
|
|
static bool isLocalHost(const std::string& host);
|
|
|
|
/**
|
|
* @brief Whether this config would send RPC credentials over plaintext to a remote host
|
|
*/
|
|
static bool usesPlaintextRemote(const ConnectionConfig& config);
|
|
|
|
// Whether plaintext credentials to a remote host are explicitly allowed (opt-in via the
|
|
// DRAGONX.conf rpcallowplaintext key). Off by default: usesPlaintextRemote() && !this
|
|
// means the connect is refused.
|
|
static bool allowsPlaintextRemote(const ConnectionConfig& config);
|
|
|
|
static const char* authSourceName(AuthSource source);
|
|
|
|
private:
|
|
};
|
|
|
|
} // namespace rpc
|
|
} // namespace dragonx
|