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:
2026-03-21 03:43:23 -05:00
parent b0d7319182
commit cd5a9f3736
283 changed files with 124331 additions and 124080 deletions

2
lib/.cargo/config.toml Normal file
View File

@@ -0,0 +1,2 @@
[net]
git-fetch-with-cli = true

4
lib/.gitignore vendored
View File

@@ -1,2 +1,2 @@
/target/
/target/

3
lib/Cargo.lock generated
View File

@@ -1,5 +1,7 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "adler32"
version = "1.0.4"
@@ -1854,7 +1856,6 @@ dependencies = [
[[package]]
name = "silentdragonxlitelib"
version = "0.1.0"
source = "git+https://git.hush.is/dragonx/silentdragonxlite-cli?rev=3eaa2fcf939af9821df10b458af11b185f49e287#3eaa2fcf939af9821df10b458af11b185f49e287"
dependencies = [
"base58",
"bellman",

View File

@@ -1,16 +1,16 @@
[package]
name = "qtlib"
version = "0.1.0"
authors = ["zecwallet", "The Hush Developers"]
edition = "2018"
[lib]
name = "silentdragonxlite"
crate-type = ["staticlib"]
[dependencies]
libc = "0.2.58"
lazy_static = "1.4.0"
blake3 = "0.3.4"
silentdragonxlitelib = { git = "https://git.hush.is/dragonx/silentdragonxlite-cli", rev = "505fac6b6eef50be9f0e2b375c2fa6815f8bf18c" }
socket2 = "0.3.11"
[package]
name = "qtlib"
version = "0.1.0"
authors = ["zecwallet", "The Hush Developers"]
edition = "2018"
[lib]
name = "silentdragonxlite"
crate-type = ["staticlib"]
[dependencies]
libc = "0.2.58"
lazy_static = "1.4.0"
blake3 = "0.3.4"
silentdragonxlitelib = { path = "/home/d/external/silentdragonxlite-cli/lib" }
socket2 = "0.3.11"

View File

@@ -1,28 +1,28 @@
ifeq ($(shell uname),Darwin)
EXT := dylib
CFLAGS := "-mmacosx-version-min=10.11"
else
EXT := a
CFLAGS := ""
endif
PWD := $(shell pwd)
all: release
winrelease: target/x86_64-pc-windows-gnu/release/silentdragonxlite.lib
target/x86_64-pc-windows-gnu/release/silentdragonxlite.lib: src/lib.rs Cargo.toml
SODIUM_LIB_DIR="$(PWD)/libsodium-mingw/" cargo build --lib --release --target x86_64-pc-windows-gnu
release: target/release/silentdragonxlite.$(EXT)
debug: target/debug/silentdragonxlite.$(EXT)
target/release/silentdragonxlite.$(EXT): src/lib.rs Cargo.toml
LIBS="" CFLAGS=$(CFLAGS) cargo build --lib --release
target/debug/silentdragonxlite.$(EXT): src/lib.rs Cargo.toml
LIBS="" CFLAGS=$(CFLAGS) cargo build --lib
clean:
ifeq ($(shell uname),Darwin)
EXT := dylib
CFLAGS := "-mmacosx-version-min=10.11"
else
EXT := a
CFLAGS := ""
endif
PWD := $(shell pwd)
all: release
winrelease: target/x86_64-pc-windows-gnu/release/silentdragonxlite.lib
target/x86_64-pc-windows-gnu/release/silentdragonxlite.lib: src/lib.rs Cargo.toml
SODIUM_LIB_DIR="$(PWD)/libsodium-mingw/" cargo build --lib --release --target x86_64-pc-windows-gnu
release: target/release/silentdragonxlite.$(EXT)
debug: target/debug/silentdragonxlite.$(EXT)
target/release/silentdragonxlite.$(EXT): src/lib.rs Cargo.toml
LIBS="" CFLAGS=$(CFLAGS) cargo build --lib --release
target/debug/silentdragonxlite.$(EXT): src/lib.rs Cargo.toml
LIBS="" CFLAGS=$(CFLAGS) cargo build --lib
clean:
rm -rf target

View File

@@ -1,27 +1,27 @@
#ifndef _hush_PAPER_RUST_H
#define _hush_PAPER_RUST_H
#ifdef __cplusplus
extern "C" {
#endif
extern bool litelib_wallet_exists (const char* chain_name);
extern char * litelib_initialize_new (bool dangerous, const char* server);
extern char * litelib_initialize_new_from_phrase
(bool dangerous, const char* server, const char* seed,
unsigned long long birthday, unsigned long long number);
extern char * litelib_initialize_existing (bool dangerous,const char* server);
extern char * litelib_execute (const char* s, const char* args);
extern void litelib_rust_free_string (char* s);
extern char * blake3_PW (char* pw);
extern bool litelib_check_server_online (const char* server);
#ifdef __cplusplus
}
#endif
// This is a function implemented in connection.cpp that will process a string response from
// the litelib and turn into into a QString in a memory-safe way.
QString litelib_process_response(char* resp);
#endif
#ifndef _hush_PAPER_RUST_H
#define _hush_PAPER_RUST_H
#ifdef __cplusplus
extern "C" {
#endif
extern bool litelib_wallet_exists (const char* chain_name);
extern char * litelib_initialize_new (bool dangerous, const char* server);
extern char * litelib_initialize_new_from_phrase
(bool dangerous, const char* server, const char* seed,
unsigned long long birthday, unsigned long long number);
extern char * litelib_initialize_existing (bool dangerous,const char* server);
extern char * litelib_execute (const char* s, const char* args);
extern void litelib_rust_free_string (char* s);
extern char * blake3_PW (char* pw);
extern bool litelib_check_server_online (const char* server);
#ifdef __cplusplus
}
#endif
// This is a function implemented in connection.cpp that will process a string response from
// the litelib and turn into into a QString in a memory-safe way.
QString litelib_process_response(char* resp);
#endif

View File

@@ -1,276 +1,303 @@
#[macro_use]
extern crate lazy_static;
use libc::{c_char};
use std::ffi::{CStr, CString};
use std::sync::{Mutex, Arc};
use std::cell::RefCell;
use std::ptr;
use silentdragonxlitelib::{commands, lightclient::{LightClient, LightClientConfig}};
// 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
// because we can't return such a complex structure back to C++
lazy_static! {
static ref LIGHTCLIENT: Mutex<RefCell<Option<Arc<LightClient>>>> = Mutex::new(RefCell::new(None));
}
// Check if there is an existing wallet
#[no_mangle]
pub extern fn litelib_wallet_exists(chain_name: *const c_char) -> bool {
let chain_name_str = unsafe {
assert!(!chain_name.is_null());
CStr::from_ptr(chain_name).to_string_lossy().into_owned()
};
let config = LightClientConfig::create_unconnected(chain_name_str, None);
println!("Wallet exists: {}", config.wallet_exists());
config.wallet_exists()
}
//////hash blake3
#[no_mangle]
pub extern fn blake3_PW(pw: *const c_char) -> *mut c_char{
let passwd = unsafe {
assert!(!pw.is_null());
CStr::from_ptr(pw).to_string_lossy().into_owned()
};
let data = passwd.as_bytes();
// Hash an input all at once.
let hash1 = blake3::hash(data).to_hex();
// This is sensitive metadata, do not log it to stdout
//println!("\nBlake3 Hash: {}", hash1);
println!("\nBlake3 Hash calculated");
//let sttring = CString::new(hash1).unwrap();
let e_str = CString::new(format!("{}", hash1)).unwrap();
return e_str.into_raw();
}
/// Create a new wallet and return the seed for the newly created wallet.
#[no_mangle]
pub extern fn litelib_initialize_new(dangerous: bool,server: *const c_char) -> *mut c_char {
let server_str = unsafe {
assert!(!server.is_null());
CStr::from_ptr(server).to_string_lossy().into_owned()
};
let server = LightClientConfig::get_server_or_default(Some(server_str));
let (config, latest_block_height) = match LightClientConfig::create(server, dangerous) {
Ok((c, h)) => (c, h),
Err(e) => {
let e_str = CString::new(format!("Error: {}", e)).unwrap();
return e_str.into_raw();
}
};
let lightclient = match LightClient::new(&config, latest_block_height) {
Ok(l) => l,
Err(e) => {
let e_str = CString::new(format!("Error: {}", e)).unwrap();
return e_str.into_raw();
}
};
// Initialize logging
let _ = lightclient.init_logging();
let seed = match lightclient.do_seed_phrase() {
Ok(s) => s.dump(),
Err(e) => {
let e_str = CString::new(format!("Error: {}", e)).unwrap();
return e_str.into_raw();
}
};
let lc = Arc::new(lightclient);
match LightClient::start_mempool_monitor(lc.clone()) {
Ok(_) => {println!("Starting Mempool")},
Err(e) => {
println!("Couldnt start mempool {}", e)
}
}
LIGHTCLIENT.lock().unwrap().replace(Some(lc));
// Return the wallet's seed
let s_str = CString::new(seed).unwrap();
return s_str.into_raw();
}
/// Restore a wallet from the seed phrase
#[no_mangle]
pub extern "C" fn litelib_initialize_new_from_phrase(dangerous: bool, server: *const c_char,
seed: *const c_char, birthday: u64, number: u64, overwrite: bool) -> *mut c_char {
if server.is_null() || seed.is_null() {
println!("Server or seed is null");
return ptr::null_mut();
}
let server_str = unsafe {
CStr::from_ptr(server).to_string_lossy().into_owned()
};
let seed_str = unsafe {
CStr::from_ptr(seed).to_string_lossy().into_owned()
};
//println!("Initializing with server: {}, seed: {}", server_str, seed_str);
let server = LightClientConfig::get_server_or_default(Some(server_str));
let (config, _latest_block_height) = match LightClientConfig::create(server, dangerous) {
Ok((c, h)) => {
println!("Config created successfully");
(c, h)
},
Err(e) => {
println!("Error creating config: {}", e);
let e_str = CString::new(format!("Error: {}", e)).unwrap_or_else(|_| CString::new("Error creating CString").unwrap());
return e_str.into_raw();
}
};
let lightclient = match LightClient::new_from_phrase(seed_str, &config, birthday, number, overwrite) {
Ok(l) => {
println!("LightClient created successfully");
l
},
Err(e) => {
println!("Error creating LightClient: {}", e);
let e_str = CString::new(format!("Error: {}", e)).unwrap_or_else(|_| CString::new("Error creating CString").unwrap());
return e_str.into_raw();
}
};
// Initialize logging
let _ = lightclient.init_logging();
let lc = Arc::new(lightclient);
match LightClient::start_mempool_monitor(lc.clone()) {
Ok(_) => println!("Starting Mempool"),
Err(e) => println!("Could not start mempool: {}", e)
}
LIGHTCLIENT.lock().unwrap().replace(Some(lc));
let c_str = CString::new("OK").unwrap_or_else(|_| CString::new("CString creation failed").unwrap());
return c_str.into_raw();
}
// Initialize a new lightclient and store its value
#[no_mangle]
pub extern fn litelib_initialize_existing(dangerous: bool, server: *const c_char) -> *mut c_char {
let server_str = unsafe {
assert!(!server.is_null());
CStr::from_ptr(server).to_string_lossy().into_owned()
};
let server = LightClientConfig::get_server_or_default(Some(server_str));
let (config, _latest_block_height) = match LightClientConfig::create(server,dangerous) {
Ok((c, h)) => (c, h),
Err(e) => {
let e_str = CString::new(format!("Error: {}", e)).unwrap();
return e_str.into_raw();
}
};
let lightclient = match LightClient::read_from_disk(&config) {
Ok(l) => l,
Err(e) => {
let e_str = CString::new(format!("Error: {}", e)).unwrap();
return e_str.into_raw();
}
};
// Initialize logging
let _ = lightclient.init_logging();
let lc = Arc::new(lightclient);
match LightClient::start_mempool_monitor(lc.clone()) {
Ok(_) => {println!("Starting Mempool")},
Err(e) => {
println!("Couldnt start mempool {}",e)
}
}
LIGHTCLIENT.lock().unwrap().replace(Some(lc));
let c_str = CString::new("OK").unwrap();
return c_str.into_raw();
}
#[no_mangle]
pub extern fn litelib_execute(cmd: *const c_char, args: *const c_char) -> *mut c_char {
let cmd_str = unsafe {
assert!(!cmd.is_null());
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()] };
resp = commands::do_user_command(&cmd_str, &args, lightclient.as_ref()).clone();
};
let c_str = CString::new(resp.as_bytes()).unwrap();
return c_str.into_raw();
}
// Check is Server Connection is fine
#[no_mangle]
pub extern "C" fn litelib_check_server_online(server: *const c_char) -> bool {
let server_str = unsafe {
assert!(!server.is_null());
CStr::from_ptr(server).to_string_lossy().into_owned()
};
let server = LightClientConfig::get_server_or_default(Some(server_str));
let result = LightClientConfig::create(server, false);
match result {
Ok(_) => true,
Err(_) => false,
}
}
/**
* Callers that receive string return values from other functions should call this to return the string
* back to rust, so it can be freed. Failure to call this function will result in a memory leak
*/
#[no_mangle]
pub extern fn litelib_rust_free_string(s: *mut c_char) {
unsafe {
if s.is_null() { return }
CString::from_raw(s)
};
}
#[macro_use]
extern crate lazy_static;
use libc::{c_char};
use std::ffi::{CStr, CString};
use std::sync::{Mutex, Arc};
use std::cell::RefCell;
use std::ptr;
use std::panic;
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,
// 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++
lazy_static! {
static ref LIGHTCLIENT: Mutex<RefCell<Option<Arc<LightClient>>>> = Mutex::new(RefCell::new(None));
}
// Check if there is an existing wallet
#[no_mangle]
pub extern fn litelib_wallet_exists(chain_name: *const c_char) -> bool {
let chain_name_str = unsafe {
assert!(!chain_name.is_null());
CStr::from_ptr(chain_name).to_string_lossy().into_owned()
};
let config = LightClientConfig::create_unconnected(chain_name_str, None);
println!("Wallet exists: {}", config.wallet_exists());
config.wallet_exists()
}
//////hash blake3
#[no_mangle]
pub extern fn blake3_PW(pw: *const c_char) -> *mut c_char{
let passwd = unsafe {
assert!(!pw.is_null());
CStr::from_ptr(pw).to_string_lossy().into_owned()
};
let data = passwd.as_bytes();
// Hash an input all at once.
let hash1 = blake3::hash(data).to_hex();
// This is sensitive metadata, do not log it to stdout
//println!("\nBlake3 Hash: {}", hash1);
println!("\nBlake3 Hash calculated");
//let sttring = CString::new(hash1).unwrap();
let e_str = CString::new(format!("{}", hash1)).unwrap();
return e_str.into_raw();
}
/// Create a new wallet and return the seed for the newly created wallet.
#[no_mangle]
pub extern fn litelib_initialize_new(dangerous: bool,server: *const c_char) -> *mut c_char {
let server_str = unsafe {
assert!(!server.is_null());
CStr::from_ptr(server).to_string_lossy().into_owned()
};
let server = LightClientConfig::get_server_or_default(Some(server_str));
let (config, latest_block_height) = match LightClientConfig::create(server, dangerous) {
Ok((c, h)) => (c, h),
Err(e) => {
let e_str = CString::new(format!("Error: {}", e)).unwrap();
return e_str.into_raw();
}
};
let lightclient = match LightClient::new(&config, latest_block_height) {
Ok(l) => l,
Err(e) => {
let e_str = CString::new(format!("Error: {}", e)).unwrap();
return e_str.into_raw();
}
};
// Initialize logging
let _ = lightclient.init_logging();
let seed = match lightclient.do_seed_phrase() {
Ok(s) => s.dump(),
Err(e) => {
let e_str = CString::new(format!("Error: {}", e)).unwrap();
return e_str.into_raw();
}
};
let lc = Arc::new(lightclient);
match LightClient::start_mempool_monitor(lc.clone()) {
Ok(_) => {println!("Starting Mempool")},
Err(e) => {
println!("Couldnt start mempool {}", e)
}
}
match LIGHTCLIENT.lock() {
Ok(l) => { l.replace(Some(lc)); },
Err(poisoned) => { poisoned.into_inner().replace(Some(lc)); },
};
// Return the wallet's seed
let s_str = safe_cstring(&seed);
return s_str.into_raw();
}
/// Restore a wallet from the seed phrase
#[no_mangle]
pub extern "C" fn litelib_initialize_new_from_phrase(dangerous: bool, server: *const c_char,
seed: *const c_char, birthday: u64, number: u64, overwrite: bool) -> *mut c_char {
if server.is_null() || seed.is_null() {
println!("Server or seed is null");
return ptr::null_mut();
}
let server_str = unsafe {
CStr::from_ptr(server).to_string_lossy().into_owned()
};
let seed_str = unsafe {
CStr::from_ptr(seed).to_string_lossy().into_owned()
};
//println!("Initializing with server: {}, seed: {}", server_str, seed_str);
let server = LightClientConfig::get_server_or_default(Some(server_str));
let (config, _latest_block_height) = match LightClientConfig::create(server, dangerous) {
Ok((c, h)) => {
println!("Config created successfully");
(c, h)
},
Err(e) => {
println!("Error creating config: {}", e);
let e_str = CString::new(format!("Error: {}", e)).unwrap_or_else(|_| CString::new("Error creating CString").unwrap());
return e_str.into_raw();
}
};
let lightclient = match LightClient::new_from_phrase(seed_str, &config, birthday, number, overwrite) {
Ok(l) => {
println!("LightClient created successfully");
l
},
Err(e) => {
println!("Error creating LightClient: {}", e);
let e_str = CString::new(format!("Error: {}", e)).unwrap_or_else(|_| CString::new("Error creating CString").unwrap());
return e_str.into_raw();
}
};
// Initialize logging
let _ = lightclient.init_logging();
let lc = Arc::new(lightclient);
match LightClient::start_mempool_monitor(lc.clone()) {
Ok(_) => println!("Starting Mempool"),
Err(e) => println!("Could not start mempool: {}", e)
}
match LIGHTCLIENT.lock() {
Ok(l) => { l.replace(Some(lc)); },
Err(poisoned) => { poisoned.into_inner().replace(Some(lc)); },
};
let c_str = safe_cstring("OK");
return c_str.into_raw();
}
// Initialize a new lightclient and store its value
#[no_mangle]
pub extern fn litelib_initialize_existing(dangerous: bool, server: *const c_char) -> *mut c_char {
let server_str = unsafe {
assert!(!server.is_null());
CStr::from_ptr(server).to_string_lossy().into_owned()
};
let server = LightClientConfig::get_server_or_default(Some(server_str));
let (config, _latest_block_height) = match LightClientConfig::create(server,dangerous) {
Ok((c, h)) => (c, h),
Err(e) => {
let e_str = CString::new(format!("Error: {}", e)).unwrap();
return e_str.into_raw();
}
};
let lightclient = match LightClient::read_from_disk(&config) {
Ok(l) => l,
Err(e) => {
let e_str = CString::new(format!("Error: {}", e)).unwrap();
return e_str.into_raw();
}
};
// Initialize logging
let _ = lightclient.init_logging();
let lc = Arc::new(lightclient);
match LightClient::start_mempool_monitor(lc.clone()) {
Ok(_) => {println!("Starting Mempool")},
Err(e) => {
println!("Couldnt start mempool {}",e)
}
}
match LIGHTCLIENT.lock() {
Ok(l) => { l.replace(Some(lc)); },
Err(poisoned) => { poisoned.into_inner().replace(Some(lc)); },
};
let c_str = safe_cstring("OK");
return c_str.into_raw();
}
#[no_mangle]
pub extern fn litelib_execute(cmd: *const c_char, args: *const c_char) -> *mut c_char {
let result = panic::catch_unwind(|| {
let cmd_str = unsafe {
assert!(!cmd.is_null());
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 = match LIGHTCLIENT.lock() {
Ok(l) => l,
Err(poisoned) => poisoned.into_inner(),
};
if lc.borrow().is_none() {
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
#[no_mangle]
pub extern "C" fn litelib_check_server_online(server: *const c_char) -> bool {
let server_str = unsafe {
assert!(!server.is_null());
CStr::from_ptr(server).to_string_lossy().into_owned()
};
let server = LightClientConfig::get_server_or_default(Some(server_str));
let result = LightClientConfig::create(server, false);
match result {
Ok(_) => true,
Err(_) => false,
}
}
/**
* Callers that receive string return values from other functions should call this to return the string
* back to rust, so it can be freed. Failure to call this function will result in a memory leak
*/
#[no_mangle]
pub extern fn litelib_rust_free_string(s: *mut c_char) {
unsafe {
if s.is_null() { return }
CString::from_raw(s)
};
}