daemon version check, idle mining control, bootstrap mirror, import key paste, and cleanup

- Add startup binary version checking for dragonxd/xmrig
- Display daemon version in UI
- Add idle mining thread count adjustment
- Add bootstrap mirror option (bootstrap2.dragonx.is) in setup wizard
- Add paste button to import private key dialog with address validation
- Add z-address generation UI feedback (loading indicator)
- Add option to delete blockchain data while preserving wallet.dat
- Add font scale slider hotkey tooltip (Ctrl+Plus/Ctrl+Minus)
- Fix Windows RPC auth: trim \r from config values, add .cookie fallback
- Fix connection status message during block index loading
- Improve application shutdown to prevent lingering background process
This commit is contained in:
2026-03-17 14:57:12 -05:00
parent 1b97476a54
commit 40dd6d45b2
27 changed files with 897 additions and 2050 deletions

View File

@@ -104,6 +104,12 @@ bool App::init()
if (!settings_->load()) {
DEBUG_LOGF("Warning: Could not load settings, using defaults\n");
}
// On upgrade (version mismatch), re-save to persist new defaults + current version
if (settings_->needsUpgradeSave()) {
DEBUG_LOGF("[INFO] Wallet upgraded — re-saving settings with new defaults\n");
settings_->save();
settings_->clearUpgradeSave();
}
// Apply verbose logging preference from saved settings
util::Logger::instance().setVerbose(settings_->getVerboseLogging());
@@ -137,6 +143,27 @@ bool App::init()
{
std::string schemaPath = util::Platform::getExecutableDirectory() + "/res/themes/ui.toml";
bool loaded = false;
#if HAS_EMBEDDED_UI_TOML
// If on-disk ui.toml exists but differs in size from the embedded
// version, a newer wallet binary is running against stale theme
// files. Overwrite the on-disk copy so layout matches the binary.
if (std::filesystem::exists(schemaPath)) {
std::error_code ec;
auto diskSize = std::filesystem::file_size(schemaPath, ec);
if (!ec && diskSize != static_cast<std::uintmax_t>(embedded::ui_toml_size)) {
DEBUG_LOGF("[INFO] ui.toml on disk (%ju bytes) differs from embedded (%zu bytes) — updating\n",
(uintmax_t)diskSize, embedded::ui_toml_size);
std::ofstream ofs(schemaPath, std::ios::binary | std::ios::trunc);
if (ofs.is_open()) {
ofs.write(reinterpret_cast<const char*>(embedded::ui_toml_data),
embedded::ui_toml_size);
ofs.close();
}
}
}
#endif
if (std::filesystem::exists(schemaPath)) {
loaded = ui::schema::UISchema::instance().loadFromFile(schemaPath);
}
@@ -1548,6 +1575,9 @@ void App::renderImportKeyDialog()
return it != dlg.extraFloats.end() ? it->second : fb;
};
int btnFont = (int)dlgF("button-font", 1);
float btnW = dlgF("button-width", 120.0f);
if (!ui::material::BeginOverlayDialog("Import Private Key", &show_import_key_, dlgF("width", 500.0f), 0.94f)) {
return;
}
@@ -1561,6 +1591,50 @@ void App::renderImportKeyDialog()
ImGui::SetNextItemWidth(-1);
ImGui::InputText("##importkey", import_key_input_, sizeof(import_key_input_));
// Paste & Clear buttons
if (ui::material::StyledButton(TR("paste"), ImVec2(0, 0), ui::material::resolveButtonFont(btnFont))) {
const char* clipboard = ImGui::GetClipboardText();
if (clipboard) {
snprintf(import_key_input_, sizeof(import_key_input_), "%s", clipboard);
// Trim whitespace
std::string trimmed(import_key_input_);
while (!trimmed.empty() && (trimmed.front() == ' ' || trimmed.front() == '\t' ||
trimmed.front() == '\n' || trimmed.front() == '\r'))
trimmed.erase(trimmed.begin());
while (!trimmed.empty() && (trimmed.back() == ' ' || trimmed.back() == '\t' ||
trimmed.back() == '\n' || trimmed.back() == '\r'))
trimmed.pop_back();
snprintf(import_key_input_, sizeof(import_key_input_), "%s", trimmed.c_str());
}
}
ImGui::SameLine();
if (ui::material::StyledButton(TR("clear"), ImVec2(0, 0), ui::material::resolveButtonFont(btnFont))) {
memset(import_key_input_, 0, sizeof(import_key_input_));
}
// Key validation indicator
if (import_key_input_[0] != '\0') {
std::string k(import_key_input_);
bool isZKey = (k.substr(0, 20) == "secret-extended-key-") ||
(k.length() >= 2 && k[0] == 'S' && k[1] == 'K');
bool isTKey = (k.length() >= 51 && k.length() <= 52 &&
(k[0] == '5' || k[0] == 'K' || k[0] == 'L' || k[0] == 'U'));
if (isZKey || isTKey) {
ImGui::PushFont(ui::material::Type().iconSmall());
ImGui::TextColored(ImVec4(0.3f, 0.8f, 0.3f, 1.0f), ICON_MD_CHECK_CIRCLE);
ImGui::PopFont();
ImGui::SameLine(0, 4.0f);
ImGui::TextColored(ImVec4(0.3f, 0.8f, 0.3f, 1.0f), "%s",
isZKey ? "Shielded spending key" : "Transparent private key");
} else {
ImGui::PushFont(ui::material::Type().iconSmall());
ImGui::TextColored(ImVec4(0.8f, 0.6f, 0.0f, 1.0f), ICON_MD_HELP);
ImGui::PopFont();
ImGui::SameLine(0, 4.0f);
ImGui::TextColored(ImVec4(0.8f, 0.6f, 0.0f, 1.0f), "Unrecognized key format");
}
}
ImGui::Spacing();
if (!import_status_.empty()) {
@@ -1574,8 +1648,6 @@ void App::renderImportKeyDialog()
ImGui::Spacing();
ImGui::Separator();
int btnFont = (int)dlgF("button-font", 1);
float btnW = dlgF("button-width", 120.0f);
if (ui::material::StyledButton("Import", ImVec2(btnW, 0), ui::material::resolveButtonFont(btnFont))) {
std::string key(import_key_input_);
if (!key.empty()) {
@@ -2037,6 +2109,57 @@ void App::rescanBlockchain()
}).detach();
}
void App::deleteBlockchainData()
{
if (!isUsingEmbeddedDaemon() || !embedded_daemon_) {
ui::Notifications::instance().warning(
"Delete blockchain requires embedded daemon. Stop your daemon manually and delete the data directory.");
return;
}
DEBUG_LOGF("[App] Deleting blockchain data - stopping daemon first\n");
ui::Notifications::instance().info("Stopping daemon and deleting blockchain data...");
std::thread([this]() {
DEBUG_LOGF("[App] Stopping daemon for blockchain deletion...\n");
stopEmbeddedDaemon();
if (shutting_down_) return;
for (int i = 0; i < 30 && !shutting_down_; ++i)
std::this_thread::sleep_for(std::chrono::milliseconds(100));
if (shutting_down_) return;
namespace fs = std::filesystem;
std::string dataDir = util::Platform::getDragonXDataDir();
// Directories to remove
const char* dirs[] = { "blocks", "chainstate", "database", "notarizations" };
// Files to remove
const char* files[] = { "peers.dat", "fee_estimates.dat", "banlist.dat",
"db.log", ".lock" };
int removed = 0;
std::error_code ec;
for (auto d : dirs) {
fs::path p = fs::path(dataDir) / d;
if (fs::exists(p, ec)) {
auto n = fs::remove_all(p, ec);
if (!ec) { removed += (int)n; DEBUG_LOGF("[App] Removed %s (%d entries)\n", d, (int)n); }
else { DEBUG_LOGF("[App] Failed to remove %s: %s\n", d, ec.message().c_str()); }
}
}
for (auto f : files) {
fs::path p = fs::path(dataDir) / f;
if (fs::remove(p, ec)) { removed++; DEBUG_LOGF("[App] Removed %s\n", f); }
}
DEBUG_LOGF("[App] Blockchain data deleted (%d items removed), restarting daemon...\n", removed);
daemon_output_offset_ = 0;
startEmbeddedDaemon();
}).detach();
}
double App::getDaemonMemoryUsageMB() const
{
// If we have an embedded daemon with a tracked process handle, use it