DragonX compatibility: crash fixes, reorg detection, server failover, sync perf
- Fix Rust FFI panics with catch_unwind wrappers and safe CString handling - Handle poisoned mutex/RwLock from prior panics instead of crashing - Add stuck sync detection (10s stall threshold) and chain reorg user prompt - Add "Skip Verification" button to seed phrase wizard - Update payment URIs from hush: to drgx: - Update branding strings throughout UI - Add all 6 lite servers (lite, lite1-5.dragonx.is) with random selection - Add server connectivity probing to skip unreachable servers - Reuse Tokio runtime across block fetch batches to reduce sync overhead - Update Cargo.lock dependencies
This commit is contained in:
Binary file not shown.
37
SilentDragonXLite_resource.rc
Normal file
37
SilentDragonXLite_resource.rc
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
#include <windows.h>
|
||||||
|
|
||||||
|
IDI_ICON1 ICON DISCARDABLE "/home/d/external/SilentDragonXLite/res/icon.ico"
|
||||||
|
|
||||||
|
VS_VERSION_INFO VERSIONINFO
|
||||||
|
FILEVERSION 0,0,0,0
|
||||||
|
PRODUCTVERSION 0,0,0,0
|
||||||
|
FILEFLAGSMASK 0x3fL
|
||||||
|
#ifdef _DEBUG
|
||||||
|
FILEFLAGS VS_FF_DEBUG
|
||||||
|
#else
|
||||||
|
FILEFLAGS 0x0L
|
||||||
|
#endif
|
||||||
|
FILEOS VOS__WINDOWS32
|
||||||
|
FILETYPE VFT_APP
|
||||||
|
FILESUBTYPE 0x0L
|
||||||
|
BEGIN
|
||||||
|
BLOCK "StringFileInfo"
|
||||||
|
BEGIN
|
||||||
|
BLOCK "040904b0"
|
||||||
|
BEGIN
|
||||||
|
VALUE "CompanyName", "\0"
|
||||||
|
VALUE "FileDescription", "\0"
|
||||||
|
VALUE "FileVersion", "0.0.0.0\0"
|
||||||
|
VALUE "LegalCopyright", "\0"
|
||||||
|
VALUE "OriginalFilename", "SilentDragonXLite.exe\0"
|
||||||
|
VALUE "ProductName", "SilentDragonXLite\0"
|
||||||
|
VALUE "ProductVersion", "0.0.0.0\0"
|
||||||
|
END
|
||||||
|
END
|
||||||
|
BLOCK "VarFileInfo"
|
||||||
|
BEGIN
|
||||||
|
VALUE "Translation", 0x0409, 1200
|
||||||
|
END
|
||||||
|
END
|
||||||
|
/* End of Version info */
|
||||||
|
|
||||||
2
lib/.cargo/config.toml
Normal file
2
lib/.cargo/config.toml
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
[net]
|
||||||
|
git-fetch-with-cli = true
|
||||||
3
lib/Cargo.lock
generated
3
lib/Cargo.lock
generated
@@ -1,5 +1,7 @@
|
|||||||
# This file is automatically @generated by Cargo.
|
# This file is automatically @generated by Cargo.
|
||||||
# It is not intended for manual editing.
|
# It is not intended for manual editing.
|
||||||
|
version = 3
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "adler32"
|
name = "adler32"
|
||||||
version = "1.0.4"
|
version = "1.0.4"
|
||||||
@@ -1854,7 +1856,6 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "silentdragonxlitelib"
|
name = "silentdragonxlitelib"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://git.hush.is/dragonx/silentdragonxlite-cli?rev=3eaa2fcf939af9821df10b458af11b185f49e287#3eaa2fcf939af9821df10b458af11b185f49e287"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base58",
|
"base58",
|
||||||
"bellman",
|
"bellman",
|
||||||
|
|||||||
@@ -12,5 +12,5 @@ crate-type = ["staticlib"]
|
|||||||
libc = "0.2.58"
|
libc = "0.2.58"
|
||||||
lazy_static = "1.4.0"
|
lazy_static = "1.4.0"
|
||||||
blake3 = "0.3.4"
|
blake3 = "0.3.4"
|
||||||
silentdragonxlitelib = { git = "https://git.hush.is/dragonx/silentdragonxlite-cli", rev = "505fac6b6eef50be9f0e2b375c2fa6815f8bf18c" }
|
silentdragonxlitelib = { path = "/home/d/external/silentdragonxlite-cli/lib" }
|
||||||
socket2 = "0.3.11"
|
socket2 = "0.3.11"
|
||||||
|
|||||||
@@ -7,9 +7,21 @@ use std::ffi::{CStr, CString};
|
|||||||
use std::sync::{Mutex, Arc};
|
use std::sync::{Mutex, Arc};
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
use std::ptr;
|
use std::ptr;
|
||||||
|
use std::panic;
|
||||||
|
|
||||||
use silentdragonxlitelib::{commands, lightclient::{LightClient, LightClientConfig}};
|
use silentdragonxlitelib::{commands, lightclient::{LightClient, LightClientConfig}};
|
||||||
|
|
||||||
|
/// Helper to create a CString, replacing null bytes to avoid panics
|
||||||
|
fn safe_cstring(s: &str) -> CString {
|
||||||
|
let cleaned: String = s.replace('\0', "");
|
||||||
|
CString::new(cleaned).unwrap_or_else(|_| CString::new("Error: failed to create CString").unwrap())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Helper to create an error CString
|
||||||
|
fn error_cstring(msg: &str) -> *mut c_char {
|
||||||
|
safe_cstring(&format!("Error: {}", msg)).into_raw()
|
||||||
|
}
|
||||||
|
|
||||||
// We'll use a MUTEX to store a global lightclient instance,
|
// We'll use a MUTEX to store a global lightclient instance,
|
||||||
// so we don't have to keep creating it. We need to store it here, in rust
|
// so we don't have to keep creating it. We need to store it here, in rust
|
||||||
// because we can't return such a complex structure back to C++
|
// because we can't return such a complex structure back to C++
|
||||||
@@ -100,10 +112,13 @@ pub extern fn litelib_initialize_new(dangerous: bool,server: *const c_char) -> *
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
LIGHTCLIENT.lock().unwrap().replace(Some(lc));
|
match LIGHTCLIENT.lock() {
|
||||||
|
Ok(l) => { l.replace(Some(lc)); },
|
||||||
|
Err(poisoned) => { poisoned.into_inner().replace(Some(lc)); },
|
||||||
|
};
|
||||||
|
|
||||||
// Return the wallet's seed
|
// Return the wallet's seed
|
||||||
let s_str = CString::new(seed).unwrap();
|
let s_str = safe_cstring(&seed);
|
||||||
return s_str.into_raw();
|
return s_str.into_raw();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -159,9 +174,12 @@ pub extern "C" fn litelib_initialize_new_from_phrase(dangerous: bool, server: *c
|
|||||||
Err(e) => println!("Could not start mempool: {}", e)
|
Err(e) => println!("Could not start mempool: {}", e)
|
||||||
}
|
}
|
||||||
|
|
||||||
LIGHTCLIENT.lock().unwrap().replace(Some(lc));
|
match LIGHTCLIENT.lock() {
|
||||||
|
Ok(l) => { l.replace(Some(lc)); },
|
||||||
|
Err(poisoned) => { poisoned.into_inner().replace(Some(lc)); },
|
||||||
|
};
|
||||||
|
|
||||||
let c_str = CString::new("OK").unwrap_or_else(|_| CString::new("CString creation failed").unwrap());
|
let c_str = safe_cstring("OK");
|
||||||
return c_str.into_raw();
|
return c_str.into_raw();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -202,47 +220,56 @@ pub extern fn litelib_initialize_existing(dangerous: bool, server: *const c_char
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
LIGHTCLIENT.lock().unwrap().replace(Some(lc));
|
match LIGHTCLIENT.lock() {
|
||||||
|
Ok(l) => { l.replace(Some(lc)); },
|
||||||
|
Err(poisoned) => { poisoned.into_inner().replace(Some(lc)); },
|
||||||
|
};
|
||||||
|
|
||||||
let c_str = CString::new("OK").unwrap();
|
let c_str = safe_cstring("OK");
|
||||||
return c_str.into_raw();
|
return c_str.into_raw();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub extern fn litelib_execute(cmd: *const c_char, args: *const c_char) -> *mut c_char {
|
pub extern fn litelib_execute(cmd: *const c_char, args: *const c_char) -> *mut c_char {
|
||||||
let cmd_str = unsafe {
|
let result = panic::catch_unwind(|| {
|
||||||
assert!(!cmd.is_null());
|
let cmd_str = unsafe {
|
||||||
|
assert!(!cmd.is_null());
|
||||||
CStr::from_ptr(cmd).to_string_lossy().into_owned()
|
CStr::from_ptr(cmd).to_string_lossy().into_owned()
|
||||||
};
|
|
||||||
|
|
||||||
let arg_str = unsafe {
|
|
||||||
assert!(!args.is_null());
|
|
||||||
|
|
||||||
CStr::from_ptr(args).to_string_lossy().into_owned()
|
|
||||||
};
|
|
||||||
|
|
||||||
let resp: String;
|
|
||||||
{
|
|
||||||
let lightclient: Arc<LightClient>;
|
|
||||||
{
|
|
||||||
let lc = LIGHTCLIENT.lock().unwrap();
|
|
||||||
|
|
||||||
if lc.borrow().is_none() {
|
|
||||||
let e_str = CString::new("Error: Light Client is not initialized").unwrap();
|
|
||||||
return e_str.into_raw();
|
|
||||||
}
|
|
||||||
|
|
||||||
lightclient = lc.borrow().as_ref().unwrap().clone();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let args = if arg_str.is_empty() { vec![] } else { vec![arg_str.as_ref()] };
|
let arg_str = unsafe {
|
||||||
|
assert!(!args.is_null());
|
||||||
|
CStr::from_ptr(args).to_string_lossy().into_owned()
|
||||||
|
};
|
||||||
|
|
||||||
resp = commands::do_user_command(&cmd_str, &args, lightclient.as_ref()).clone();
|
let resp: String;
|
||||||
};
|
{
|
||||||
|
let lightclient: Arc<LightClient>;
|
||||||
|
{
|
||||||
|
let lc = match LIGHTCLIENT.lock() {
|
||||||
|
Ok(l) => l,
|
||||||
|
Err(poisoned) => poisoned.into_inner(),
|
||||||
|
};
|
||||||
|
|
||||||
let c_str = CString::new(resp.as_bytes()).unwrap();
|
if lc.borrow().is_none() {
|
||||||
return c_str.into_raw();
|
return error_cstring("Light Client is not initialized");
|
||||||
|
}
|
||||||
|
|
||||||
|
lightclient = lc.borrow().as_ref().unwrap().clone();
|
||||||
|
};
|
||||||
|
|
||||||
|
let args = if arg_str.is_empty() { vec![] } else { vec![arg_str.as_ref()] };
|
||||||
|
|
||||||
|
resp = commands::do_user_command(&cmd_str, &args, lightclient.as_ref()).clone();
|
||||||
|
};
|
||||||
|
|
||||||
|
safe_cstring(&resp).into_raw()
|
||||||
|
});
|
||||||
|
|
||||||
|
match result {
|
||||||
|
Ok(ptr) => ptr,
|
||||||
|
Err(_) => error_cstring("Rust panic in litelib_execute"),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check is Server Connection is fine
|
// Check is Server Connection is fine
|
||||||
|
|||||||
@@ -201,11 +201,11 @@ distclean.depends += librustclean
|
|||||||
QMAKE_EXTRA_TARGETS += librust libsodium librustclean distclean
|
QMAKE_EXTRA_TARGETS += librust libsodium librustclean distclean
|
||||||
QMAKE_CLEAN += $$PWD/lib/target/release/libsilentdragonxlite.a res/libsodium.a
|
QMAKE_CLEAN += $$PWD/lib/target/release/libsilentdragonxlite.a res/libsodium.a
|
||||||
|
|
||||||
win32: LIBS += -L$$PWD/lib/target/x86_64-pc-windows-gnu/release -lsilentdragonxlite -L$$PWD/res/ -llibsodium -lsecur32 -lcrypt32 -lncrypt
|
win32: LIBS += -L$$PWD/lib/target/x86_64-pc-windows-gnu/release -lsilentdragonxlite -L$$PWD/lib/libsodium-mingw/ -lsodium -lsecur32 -lcrypt32 -lncrypt
|
||||||
else:macx: LIBS += -L$$PWD/lib/target/release -lsilentdragonxlite -framework Security -framework Foundation -L$$PWD/res/ -lsodium
|
else:macx: LIBS += -L$$PWD/lib/target/release -lsilentdragonxlite -framework Security -framework Foundation -L$$PWD/res/ -lsodium
|
||||||
else:unix: LIBS += -L$$PWD/lib/target/release -lsilentdragonxlite -ldl -L$$PWD/res/ -lsodium
|
else:unix: LIBS += -L$$PWD/lib/target/release -lsilentdragonxlite -ldl -L$$PWD/res/ -lsodium
|
||||||
|
|
||||||
win32: PRE_TARGETDEPS += $$PWD/lib/target/x86_64-pc-windows-gnu/release/silentdragonxlite.lib $$PWD/res/libsodium.a
|
win32: PRE_TARGETDEPS += $$PWD/lib/target/x86_64-pc-windows-gnu/release/silentdragonxlite.lib $$PWD/lib/libsodium-mingw/libsodium.a
|
||||||
else:unix::PRE_TARGETDEPS += $$PWD/lib/target/release/libsilentdragonxlite.a $$PWD/res/libsodium.a
|
else:unix::PRE_TARGETDEPS += $$PWD/lib/target/release/libsilentdragonxlite.a $$PWD/res/libsodium.a
|
||||||
|
|
||||||
INCLUDEPATH += $$PWD/res
|
INCLUDEPATH += $$PWD/res
|
||||||
|
|||||||
16
silentdragonxlite_plugin_import.cpp
Normal file
16
silentdragonxlite_plugin_import.cpp
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
// This file is autogenerated by qmake. It imports static plugin classes for
|
||||||
|
// static plugins specified using QTPLUGIN and QT_PLUGIN_CLASS.<plugin> variables.
|
||||||
|
#include <QtPlugin>
|
||||||
|
Q_IMPORT_PLUGIN(QWindowsVistaStylePlugin)
|
||||||
|
Q_IMPORT_PLUGIN(QWindowsIntegrationPlugin)
|
||||||
|
Q_IMPORT_PLUGIN(QGifPlugin)
|
||||||
|
Q_IMPORT_PLUGIN(QICNSPlugin)
|
||||||
|
Q_IMPORT_PLUGIN(QICOPlugin)
|
||||||
|
Q_IMPORT_PLUGIN(QJp2Plugin)
|
||||||
|
Q_IMPORT_PLUGIN(QJpegPlugin)
|
||||||
|
Q_IMPORT_PLUGIN(QMngPlugin)
|
||||||
|
Q_IMPORT_PLUGIN(QTgaPlugin)
|
||||||
|
Q_IMPORT_PLUGIN(QTiffPlugin)
|
||||||
|
Q_IMPORT_PLUGIN(QWbmpPlugin)
|
||||||
|
Q_IMPORT_PLUGIN(QWebpPlugin)
|
||||||
|
Q_IMPORT_PLUGIN(QGenericEnginePlugin)
|
||||||
@@ -212,7 +212,7 @@ void AddressBook::open(MainWindow* parent, QLineEdit* target)
|
|||||||
QMessageBox::critical(
|
QMessageBox::critical(
|
||||||
parent,
|
parent,
|
||||||
QObject::tr("Address Format Error"),
|
QObject::tr("Address Format Error"),
|
||||||
QObject::tr("%1 doesn't seem to be a valid hush address.").arg(addr),
|
QObject::tr("%1 doesn't seem to be a valid DRGX address.").arg(addr),
|
||||||
QMessageBox::Ok
|
QMessageBox::Ok
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -108,6 +108,7 @@ void ConnectionLoader::ShowProgress()
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto me = this;
|
auto me = this;
|
||||||
|
delete isSyncing;
|
||||||
isSyncing = new QAtomicInteger<bool>(true);
|
isSyncing = new QAtomicInteger<bool>(true);
|
||||||
DEBUG("isSyncing set to true");
|
DEBUG("isSyncing set to true");
|
||||||
|
|
||||||
@@ -123,6 +124,9 @@ void ConnectionLoader::ShowProgress()
|
|||||||
DEBUG("sync rpc error! server=" << config->server);
|
DEBUG("sync rpc error! server=" << config->server);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
lastSyncedBlock = -1;
|
||||||
|
syncStallCount = 0;
|
||||||
|
|
||||||
QObject::connect(syncTimer, &QTimer::timeout, [=]() {
|
QObject::connect(syncTimer, &QTimer::timeout, [=]() {
|
||||||
if (!isSyncing || !isSyncing->load()) {
|
if (!isSyncing || !isSyncing->load()) {
|
||||||
DEBUG("Syncing complete or isSyncing is null, stopping timer");
|
DEBUG("Syncing complete or isSyncing is null, stopping timer");
|
||||||
@@ -140,6 +144,29 @@ void ConnectionLoader::ShowProgress()
|
|||||||
me->showInformation(
|
me->showInformation(
|
||||||
"Syncing... " + QString::number(synced) + " / " + QString::number(total)
|
"Syncing... " + QString::number(synced) + " / " + QString::number(total)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Stuck sync detection
|
||||||
|
if (synced <= me->lastSyncedBlock && synced < total) {
|
||||||
|
me->syncStallCount++;
|
||||||
|
if (me->syncStallCount >= 10) {
|
||||||
|
qDebug() << "Import sync stuck at block" << synced << "for 10s, clearing and rescanning";
|
||||||
|
me->showInformation("Sync stuck, rescanning...");
|
||||||
|
me->syncStallCount = 0;
|
||||||
|
me->lastSyncedBlock = -1;
|
||||||
|
connection->doRPC("clear", "", [=](auto) {
|
||||||
|
connection->doRPC("rescan", "", [=](auto) {
|
||||||
|
qDebug() << "Rescan complete after stuck import sync";
|
||||||
|
}, [=](auto) {
|
||||||
|
qDebug() << "Rescan error after stuck import sync";
|
||||||
|
});
|
||||||
|
}, [=](auto) {
|
||||||
|
qDebug() << "Clear error during stuck import sync";
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
me->syncStallCount = 0;
|
||||||
|
me->lastSyncedBlock = synced;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}, [=](QString err) {
|
}, [=](QString err) {
|
||||||
DEBUG("Sync status error: " << err);
|
DEBUG("Sync status error: " << err);
|
||||||
@@ -148,7 +175,6 @@ void ConnectionLoader::ShowProgress()
|
|||||||
});
|
});
|
||||||
} catch (const std::exception& e) {
|
} catch (const std::exception& e) {
|
||||||
DEBUG("Exception caught in syncstatus: " << e.what());
|
DEBUG("Exception caught in syncstatus: " << e.what());
|
||||||
throw;
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -225,6 +251,7 @@ void ConnectionLoader::doAutoConnect()
|
|||||||
DEBUG("Connection is online.");
|
DEBUG("Connection is online.");
|
||||||
connection->setInfo(reply);
|
connection->setInfo(reply);
|
||||||
DEBUG("getting Connection reply");
|
DEBUG("getting Connection reply");
|
||||||
|
delete isSyncing;
|
||||||
isSyncing = new QAtomicInteger<bool>();
|
isSyncing = new QAtomicInteger<bool>();
|
||||||
isSyncing->store(true);
|
isSyncing->store(true);
|
||||||
DEBUG("isSyncing set to true");
|
DEBUG("isSyncing set to true");
|
||||||
@@ -239,7 +266,76 @@ void ConnectionLoader::doAutoConnect()
|
|||||||
syncTimer->deleteLater();
|
syncTimer->deleteLater();
|
||||||
// When sync is done, set the connection
|
// When sync is done, set the connection
|
||||||
this->doRPCSetConnection(connection);
|
this->doRPCSetConnection(connection);
|
||||||
}, [=](auto) mutable {
|
}, [=](QString err) mutable {
|
||||||
|
qDebug() << "sync rpc error:" << err;
|
||||||
|
|
||||||
|
// If user already confirmed clearing from stuck detection, do it now (old sync is finished)
|
||||||
|
if (me->reorgHandled) {
|
||||||
|
qDebug() << "Old sync finished, performing deferred clear+resync";
|
||||||
|
me->showInformation("Clearing old data and resyncing...");
|
||||||
|
syncTimer->stop();
|
||||||
|
connection->doRPC("clear", "", [=](auto) {
|
||||||
|
qDebug() << "State cleared, starting fresh sync";
|
||||||
|
me->syncStallCount = 0;
|
||||||
|
me->lastSyncedBlock = -1;
|
||||||
|
me->reorgHandled = false;
|
||||||
|
isSyncing->store(true);
|
||||||
|
syncTimer->start();
|
||||||
|
connection->doRPC("sync", "", [=](auto) {
|
||||||
|
qDebug() << "Fresh sync complete";
|
||||||
|
isSyncing->store(false);
|
||||||
|
syncTimer->deleteLater();
|
||||||
|
this->doRPCSetConnection(connection);
|
||||||
|
}, [=](auto) {
|
||||||
|
qDebug() << "Fresh sync also failed";
|
||||||
|
});
|
||||||
|
}, [=](auto) {
|
||||||
|
qDebug() << "Failed to clear wallet state";
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Detect reorg failure from incompatible old chain data
|
||||||
|
if (err.contains("Reorg") || err.contains("reorg")) {
|
||||||
|
qDebug() << "Detected incompatible chain data, prompting user for fresh sync";
|
||||||
|
isSyncing->store(false);
|
||||||
|
syncTimer->stop();
|
||||||
|
|
||||||
|
QMessageBox::StandardButton reply = QMessageBox::question(
|
||||||
|
main,
|
||||||
|
QObject::tr("Incompatible Block Data"),
|
||||||
|
QObject::tr("The wallet contains block data from an old or incompatible chain. "
|
||||||
|
"This prevents syncing.\n\n"
|
||||||
|
"Would you like to clear the old data and sync fresh from the network?\n"
|
||||||
|
"(Your wallet keys and addresses will be preserved)"),
|
||||||
|
QMessageBox::Yes | QMessageBox::No
|
||||||
|
);
|
||||||
|
|
||||||
|
if (reply == QMessageBox::Yes) {
|
||||||
|
qDebug() << "User chose to rescan, clearing state";
|
||||||
|
me->showInformation("Clearing old data and resyncing...");
|
||||||
|
connection->doRPC("clear", "", [=](auto) {
|
||||||
|
qDebug() << "State cleared, starting fresh sync";
|
||||||
|
isSyncing->store(true);
|
||||||
|
syncTimer->start();
|
||||||
|
connection->doRPC("sync", "", [=](auto) {
|
||||||
|
qDebug() << "Fresh sync complete";
|
||||||
|
isSyncing->store(false);
|
||||||
|
syncTimer->deleteLater();
|
||||||
|
this->doRPCSetConnection(connection);
|
||||||
|
}, [=](auto) {
|
||||||
|
qDebug() << "Fresh sync also failed";
|
||||||
|
});
|
||||||
|
}, [=](auto) {
|
||||||
|
qDebug() << "Failed to clear wallet state";
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
me->showError(QObject::tr("Sync cannot proceed with incompatible chain data. "
|
||||||
|
"Please manually delete the wallet data file and restart."));
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
DEBUG("sync rpc error! server=" << config->server);
|
DEBUG("sync rpc error! server=" << config->server);
|
||||||
// Attempt to retry sync RPC with a delay
|
// Attempt to retry sync RPC with a delay
|
||||||
QTimer::singleShot(5000, [=]() { // 5-second delay
|
QTimer::singleShot(5000, [=]() { // 5-second delay
|
||||||
@@ -257,6 +353,12 @@ void ConnectionLoader::doAutoConnect()
|
|||||||
});
|
});
|
||||||
|
|
||||||
// While it is syncing, we'll show the status updates while it is alive.
|
// While it is syncing, we'll show the status updates while it is alive.
|
||||||
|
// Also detect stuck syncs: if synced_blocks doesn't advance (or goes backwards) for 10 ticks (10s),
|
||||||
|
// show reorg dialog.
|
||||||
|
lastSyncedBlock = -1;
|
||||||
|
syncStallCount = 0;
|
||||||
|
reorgHandled = false;
|
||||||
|
|
||||||
QObject::connect(syncTimer, &QTimer::timeout, [=]() {
|
QObject::connect(syncTimer, &QTimer::timeout, [=]() {
|
||||||
DEBUG("Check the sync status");
|
DEBUG("Check the sync status");
|
||||||
if (isSyncing != nullptr && isSyncing->load()) {
|
if (isSyncing != nullptr && isSyncing->load()) {
|
||||||
@@ -269,6 +371,38 @@ void ConnectionLoader::doAutoConnect()
|
|||||||
me->showInformation(
|
me->showInformation(
|
||||||
"Syncing... " + QString::number(synced) + " / " + QString::number(total)
|
"Syncing... " + QString::number(synced) + " / " + QString::number(total)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Stuck sync detection: if synced block hasn't advanced (or goes backwards) in 10 seconds
|
||||||
|
if (synced <= me->lastSyncedBlock && synced < total) {
|
||||||
|
me->syncStallCount++;
|
||||||
|
if (me->syncStallCount >= 10 && !me->reorgHandled) {
|
||||||
|
qDebug() << "Sync stuck/backwards at block" << synced << "for 10s, showing reorg dialog";
|
||||||
|
// Set flag BEFORE showing dialog to prevent re-entry from timer events
|
||||||
|
me->reorgHandled = true;
|
||||||
|
|
||||||
|
QMessageBox::StandardButton reply = QMessageBox::question(
|
||||||
|
main,
|
||||||
|
QObject::tr("Incompatible Block Data"),
|
||||||
|
QObject::tr("The wallet contains block data from an old or incompatible chain. "
|
||||||
|
"This prevents syncing.\n\n"
|
||||||
|
"Would you like to clear the old data and sync fresh from the network?\n"
|
||||||
|
"(Your wallet keys and addresses will be preserved)"),
|
||||||
|
QMessageBox::Yes | QMessageBox::No
|
||||||
|
);
|
||||||
|
|
||||||
|
if (reply == QMessageBox::Yes) {
|
||||||
|
qDebug() << "User chose to clear, waiting for current sync to finish";
|
||||||
|
me->showInformation("Please wait, preparing to clear old data...");
|
||||||
|
} else {
|
||||||
|
syncTimer->stop();
|
||||||
|
me->showError(QObject::tr("Sync cannot proceed with incompatible chain data. "
|
||||||
|
"Please manually delete the wallet data file and restart."));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
me->syncStallCount = 0;
|
||||||
|
me->lastSyncedBlock = synced;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[=](QString err) {
|
[=](QString err) {
|
||||||
|
|||||||
@@ -49,6 +49,10 @@ private:
|
|||||||
QTimer* syncTimer = nullptr;
|
QTimer* syncTimer = nullptr;
|
||||||
QAtomicInteger<bool>* isSyncing = nullptr;
|
QAtomicInteger<bool>* isSyncing = nullptr;
|
||||||
|
|
||||||
|
qint64 lastSyncedBlock = -1;
|
||||||
|
int syncStallCount = 0;
|
||||||
|
bool reorgHandled = false;
|
||||||
|
|
||||||
QDialog* d = nullptr;
|
QDialog* d = nullptr;
|
||||||
Ui_ConnectionDialog* connD = nullptr;
|
Ui_ConnectionDialog* connD = nullptr;
|
||||||
|
|
||||||
|
|||||||
@@ -130,13 +130,13 @@ void MainWindow::showRequesthush() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (d.exec() == QDialog::Accepted) {
|
if (d.exec() == QDialog::Accepted) {
|
||||||
// Construct a hush Payment URI with the data and pay it immediately.
|
// Construct a DRGX Payment URI with the data and pay it immediately.
|
||||||
CAmount amount = CAmount::fromDecimalString(req.txtAmount->text());
|
CAmount amount = CAmount::fromDecimalString(req.txtAmount->text());
|
||||||
QString memoURI = "hush:" + req.lblAddressInfo->text()
|
QString memoURI = "drgx:" + req.lblAddressInfo->text()
|
||||||
+ "?amt=" + amount.toDecimalString()
|
+ "?amt=" + amount.toDecimalString()
|
||||||
+ "&memo=" + QUrl::toPercentEncoding(req.txtMemo->toPlainText());
|
+ "&memo=" + QUrl::toPercentEncoding(req.txtMemo->toPlainText());
|
||||||
|
|
||||||
QString sendURI = "hush:" + AddressBook::addressFromAddressLabel(req.txtFrom->text())
|
QString sendURI = "drgx:" + AddressBook::addressFromAddressLabel(req.txtFrom->text())
|
||||||
+ "?amt=0.0001"
|
+ "?amt=0.0001"
|
||||||
+ "&memo=" + QUrl::toPercentEncoding(memoURI);
|
+ "&memo=" + QUrl::toPercentEncoding(memoURI);
|
||||||
|
|
||||||
|
|||||||
@@ -320,8 +320,9 @@ void Controller::getInfoThenRefresh(bool force)
|
|||||||
int curBlock = reply["latest_block_height"].get<json::number_integer_t>();
|
int curBlock = reply["latest_block_height"].get<json::number_integer_t>();
|
||||||
bool doUpdate = force || (model->getLatestBlock() != curBlock);
|
bool doUpdate = force || (model->getLatestBlock() != curBlock);
|
||||||
int difficulty = reply["difficulty"].get<json::number_integer_t>();
|
int difficulty = reply["difficulty"].get<json::number_integer_t>();
|
||||||
int num_halvings = 1; // number of halvings that have occured already
|
int halving_interval = 3500000;
|
||||||
int blocks_until_halving = (num_halvings*3500000) - curBlock;
|
int num_halvings = curBlock / halving_interval;
|
||||||
|
int blocks_until_halving = ((num_halvings + 1) * halving_interval) - curBlock;
|
||||||
int blocktime = 36;
|
int blocktime = 36;
|
||||||
int halving_days = (blocks_until_halving * blocktime) / (60 * 60 * 24) ;
|
int halving_days = (blocks_until_halving * blocktime) / (60 * 60 * 24) ;
|
||||||
int longestchain = reply["longestchain"].get<json::number_integer_t>();
|
int longestchain = reply["longestchain"].get<json::number_integer_t>();
|
||||||
|
|||||||
@@ -209,7 +209,10 @@ NewOrRestorePage::NewOrRestorePage(FirstTimeWizard *parent) : QWizardPage(parent
|
|||||||
crypto_pwhash_ALG_DEFAULT) != 0) {
|
crypto_pwhash_ALG_DEFAULT) != 0) {
|
||||||
/* out of memory */
|
/* out of memory */
|
||||||
qDebug() << __func__ << ": crypto_pwhash failed! Possibly out of memory";
|
qDebug() << __func__ << ": crypto_pwhash failed! Possibly out of memory";
|
||||||
exit(1);
|
QMessageBox::critical(nullptr, "Fatal Error",
|
||||||
|
"Password hashing failed (possibly out of memory). The application cannot continue.");
|
||||||
|
qApp->exit(1);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
QString passphraseHash1 = QByteArray(reinterpret_cast<const char*>(key), KEY_LEN).toHex();
|
QString passphraseHash1 = QByteArray(reinterpret_cast<const char*>(key), KEY_LEN).toHex();
|
||||||
DataStore::getChatDataStore()->setPassword(passphraseHash1);
|
DataStore::getChatDataStore()->setPassword(passphraseHash1);
|
||||||
@@ -623,7 +626,31 @@ bool NewSeedPage::validatePage() {
|
|||||||
verifyseed.word24->setEnabled(false);
|
verifyseed.word24->setEnabled(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
dialog.exec();
|
// Add a "Skip Verification" button to the dialog
|
||||||
|
QPushButton *skipButton = verifyseed.buttonBox->addButton(tr("Skip Verification"), QDialogButtonBox::ActionRole);
|
||||||
|
QObject::connect(skipButton, &QPushButton::clicked, [&dialog] () {
|
||||||
|
dialog.done(2); // custom result code for skip
|
||||||
|
});
|
||||||
|
|
||||||
|
int dialogResult = dialog.exec();
|
||||||
|
|
||||||
|
// Skip verification: save wallet and proceed
|
||||||
|
if (dialogResult == 2) {
|
||||||
|
QString reply = "";
|
||||||
|
try {
|
||||||
|
char* resp = litelib_execute("save", "");
|
||||||
|
reply = litelib_process_response(resp);
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
qDebug() << __func__ << ": caught an exception, ignoring: " << e.what();
|
||||||
|
}
|
||||||
|
auto parsed = json::parse(reply.toStdString().c_str(), nullptr, false);
|
||||||
|
if (parsed.is_discarded() || parsed.is_null() || parsed.find("result") == parsed.end()) {
|
||||||
|
QMessageBox::warning(this, tr("Failed to save wallet"),
|
||||||
|
tr("Couldn't save the wallet") + "\n" + reply, QMessageBox::Ok);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
QString reply = "";
|
QString reply = "";
|
||||||
if ((verifyseed.verify->toPlainText() == seed) && (verifyseed.verifyBirthday->toPlainText() == birthday)) {
|
if ((verifyseed.verify->toPlainText() == seed) && (verifyseed.verifyBirthday->toPlainText() == birthday)) {
|
||||||
|
|||||||
10
src/main.cpp
10
src/main.cpp
@@ -150,11 +150,11 @@ public:
|
|||||||
|
|
||||||
// Command line parser
|
// Command line parser
|
||||||
QCommandLineParser parser;
|
QCommandLineParser parser;
|
||||||
parser.setApplicationDescription("Shielded desktop light wallet for hush");
|
parser.setApplicationDescription("Shielded desktop light wallet for DragonX");
|
||||||
parser.addHelpOption();
|
parser.addHelpOption();
|
||||||
|
|
||||||
// Positional argument will specify a hush payment URI
|
// Positional argument will specify a DRGX payment URI
|
||||||
parser.addPositionalArgument("hushURI", "An optional hush URI to pay");
|
parser.addPositionalArgument("drgxURI", "An optional DRGX URI to pay");
|
||||||
|
|
||||||
parser.process(a);
|
parser.process(a);
|
||||||
|
|
||||||
@@ -201,7 +201,9 @@ public:
|
|||||||
if (sodium_init() < 0) {
|
if (sodium_init() < 0) {
|
||||||
/* panic! the library couldn't be initialized, it is not safe to use */
|
/* panic! the library couldn't be initialized, it is not safe to use */
|
||||||
qDebug() << "libsodium is not initialized!";
|
qDebug() << "libsodium is not initialized!";
|
||||||
exit(0);
|
QMessageBox::critical(nullptr, "Fatal Error",
|
||||||
|
"Failed to initialize libsodium cryptography library. The application cannot continue.");
|
||||||
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
Settings::getInstance()->setUseEmbedded(false);
|
Settings::getInstance()->setUseEmbedded(false);
|
||||||
|
|||||||
@@ -851,7 +851,7 @@ void MainWindow::setupSettingsModal() {
|
|||||||
settings.cmbServer->addItem("https://lite2.dragonx.is");
|
settings.cmbServer->addItem("https://lite2.dragonx.is");
|
||||||
settings.cmbServer->addItem("https://lite3.dragonx.is");
|
settings.cmbServer->addItem("https://lite3.dragonx.is");
|
||||||
settings.cmbServer->addItem("https://lite4.dragonx.is");
|
settings.cmbServer->addItem("https://lite4.dragonx.is");
|
||||||
settings.cmbServer->addItem("https://dragonlite.printogre.com");
|
settings.cmbServer->addItem("https://lite5.dragonx.is");
|
||||||
|
|
||||||
//TODO: seperate lists of https/Tor servers, only show user or attempt
|
//TODO: seperate lists of https/Tor servers, only show user or attempt
|
||||||
// connection to .onion if user has it enabled
|
// connection to .onion if user has it enabled
|
||||||
@@ -1030,7 +1030,7 @@ void MainWindow::payhushURI(QString uri, QString myAddr) {
|
|||||||
PaymentURI paymentInfo = Settings::parseURI(uri);
|
PaymentURI paymentInfo = Settings::parseURI(uri);
|
||||||
if (!paymentInfo.error.isEmpty()) {
|
if (!paymentInfo.error.isEmpty()) {
|
||||||
QMessageBox::critical(this, tr("Error paying DRGX URI"),
|
QMessageBox::critical(this, tr("Error paying DRGX URI"),
|
||||||
tr("URI should be of the form 'hush:<addr>?amt=x&memo=y") + "\n" + paymentInfo.error);
|
tr("URI should be of the form 'drgx:<addr>?amt=x&memo=y") + "\n" + paymentInfo.error);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1433,7 +1433,7 @@ void MainWindow::setupTransactionsTab() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Payment Request
|
// Payment Request
|
||||||
if (!memo.isEmpty() && memo.startsWith("hush:")) {
|
if (!memo.isEmpty() && memo.startsWith("drgx:")) {
|
||||||
menu.addAction(tr("View Payment Request"), [=] () {
|
menu.addAction(tr("View Payment Request"), [=] () {
|
||||||
RequestDialog::showPaymentConfirmation(this, memo);
|
RequestDialog::showPaymentConfirmation(this, memo);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ void RequestDialog::showPaymentConfirmation(MainWindow* main, QString paymentURI
|
|||||||
PaymentURI payInfo = Settings::parseURI(paymentURI);
|
PaymentURI payInfo = Settings::parseURI(paymentURI);
|
||||||
if (!payInfo.error.isEmpty()) {
|
if (!payInfo.error.isEmpty()) {
|
||||||
QMessageBox::critical(main, tr("Error paying DRGX URI"),
|
QMessageBox::critical(main, tr("Error paying DRGX URI"),
|
||||||
tr("URI should be of the form 'hush:<addr>?amt=x&memo=y") + "\n" + payInfo.error);
|
tr("URI should be of the form 'drgx:<addr>?amt=x&memo=y") + "\n" + payInfo.error);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -177,13 +177,13 @@ void RequestDialog::showRequesthush(MainWindow* main) {
|
|||||||
req.txtFrom->setFocus();
|
req.txtFrom->setFocus();
|
||||||
|
|
||||||
if (d.exec() == QDialog::Accepted) {
|
if (d.exec() == QDialog::Accepted) {
|
||||||
// Construct a hush Payment URI with the data and pay it immediately.
|
// Construct a DRGX Payment URI with the data and pay it immediately.
|
||||||
CAmount amount = CAmount::fromDecimalString(req.txtAmount->text());
|
CAmount amount = CAmount::fromDecimalString(req.txtAmount->text());
|
||||||
QString memoURI = "hush:" + req.cmbMyAddress->currentText()
|
QString memoURI = "drgx:" + req.cmbMyAddress->currentText()
|
||||||
+ "?amt=" + amount.toDecimalString()
|
+ "?amt=" + amount.toDecimalString()
|
||||||
+ "&memo=" + QUrl::toPercentEncoding(req.txtMemo->toPlainText());
|
+ "&memo=" + QUrl::toPercentEncoding(req.txtMemo->toPlainText());
|
||||||
|
|
||||||
QString sendURI = "hush:" + AddressBook::addressFromAddressLabel(req.txtFrom->text())
|
QString sendURI = "drgx:" + AddressBook::addressFromAddressLabel(req.txtFrom->text())
|
||||||
+ "?amt=0.0001"
|
+ "?amt=0.0001"
|
||||||
+ "&memo=" + QUrl::toPercentEncoding(memoURI);
|
+ "&memo=" + QUrl::toPercentEncoding(memoURI);
|
||||||
|
|
||||||
|
|||||||
@@ -311,7 +311,7 @@ QString Settings::getRandomServer() {
|
|||||||
"https://lite2.dragonx.is",
|
"https://lite2.dragonx.is",
|
||||||
"https://lite3.dragonx.is",
|
"https://lite3.dragonx.is",
|
||||||
"https://lite4.dragonx.is",
|
"https://lite4.dragonx.is",
|
||||||
"https://dragonlite.printogre.com"
|
"https://lite5.dragonx.is"
|
||||||
};
|
};
|
||||||
|
|
||||||
// we don't need cryptographic random-ness, but we want
|
// we don't need cryptographic random-ness, but we want
|
||||||
@@ -405,7 +405,7 @@ PaymentURI Settings::parseURI(QString uri) {
|
|||||||
return ans;
|
return ans;
|
||||||
}
|
}
|
||||||
|
|
||||||
uri = uri.right(uri.length() - QString("hush:").length());
|
uri = uri.right(uri.length() - QString("drgx:").length());
|
||||||
|
|
||||||
QRegExp re("([a-zA-Z0-9]+)");
|
QRegExp re("([a-zA-Z0-9]+)");
|
||||||
int pos;
|
int pos;
|
||||||
|
|||||||
@@ -143,7 +143,7 @@ QVariant TxTableModel::data(const QModelIndex &index, int role) const {
|
|||||||
// If there are multiple memos, then mark them as such
|
// If there are multiple memos, then mark them as such
|
||||||
if (dat.items.length() == 1) {
|
if (dat.items.length() == 1) {
|
||||||
auto memo = dat.items[0].memo;
|
auto memo = dat.items[0].memo;
|
||||||
if (memo.startsWith("hush:")) {
|
if (memo.startsWith("drgx:")) {
|
||||||
return Settings::paymentURIPretty(Settings::parseURI(memo));
|
return Settings::paymentURIPretty(Settings::parseURI(memo));
|
||||||
} else {
|
} else {
|
||||||
return modeldata->at(index.row()).type +
|
return modeldata->at(index.row()).type +
|
||||||
@@ -205,7 +205,7 @@ QVariant TxTableModel::data(const QModelIndex &index, int role) const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// If the memo is a Payment URI, then show a payment request icon
|
// If the memo is a Payment URI, then show a payment request icon
|
||||||
if (dat.items.length() == 1 && dat.items[0].memo.startsWith("hush:")) {
|
if (dat.items.length() == 1 && dat.items[0].memo.startsWith("drgx:")) {
|
||||||
QImage image = colorizeIcon(QIcon(":/icons/res/paymentreq.gif"), color);
|
QImage image = colorizeIcon(QIcon(":/icons/res/paymentreq.gif"), color);
|
||||||
QIcon icon;
|
QIcon icon;
|
||||||
icon.addPixmap(QPixmap::fromImage(image));
|
icon.addPixmap(QPixmap::fromImage(image));
|
||||||
|
|||||||
Reference in New Issue
Block a user