Skip to content

Commit adfd177

Browse files
committed
Merge branch 'fix/get-chain-height-chain-type'
2 parents 931062f + 75397e3 commit adfd177

1 file changed

Lines changed: 127 additions & 28 deletions

File tree

rust/src/lib.rs

Lines changed: 127 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
use std::cmp::Ordering;
22
use std::os::raw::c_char;
33
use std::ffi::{CString, CStr, c_void};
4-
use std::path::Path;
4+
use std::path::{Path, PathBuf};
55
use std::sync::Arc;
6+
use std::sync::Once;
67
use rand::thread_rng;
78
use serde::{Deserialize, Serialize};
89
use uuid::Uuid;
@@ -102,6 +103,61 @@ macro_rules! ensure_wallet (
102103
)
103104
);
104105

106+
static PANIC_HOOK_INIT: Once = Once::new();
107+
108+
/// Install a panic hook that appends panic info + backtrace to a log file so
109+
/// silent FFI panics (especially from worker threads detached from stderr)
110+
/// leave evidence. Idempotent across calls and threads thanks to `Once`.
111+
fn install_panic_hook() {
112+
PANIC_HOOK_INIT.call_once(|| {
113+
// Stack Wallet's Linux data dir is ~/.stackwallet (legacy naming),
114+
// not the XDG default. Write there so panic logs sit next to wallet
115+
// data the user can grab. Fall back to /tmp if HOME is unset.
116+
let log_path = std::env::var_os("HOME")
117+
.map(PathBuf::from)
118+
.map(|p| p.join(".stackwallet/flutter_libmwc-panic.log"))
119+
.unwrap_or_else(|| PathBuf::from("/tmp/flutter_libmwc-panic.log"));
120+
if let Some(parent) = log_path.parent() {
121+
let _ = std::fs::create_dir_all(parent);
122+
}
123+
let previous = std::panic::take_hook();
124+
std::panic::set_hook(Box::new(move |info| {
125+
let payload = info.payload();
126+
let msg = if let Some(s) = payload.downcast_ref::<&str>() {
127+
(*s).to_string()
128+
} else if let Some(s) = payload.downcast_ref::<String>() {
129+
s.clone()
130+
} else {
131+
"<non-string panic payload>".to_string()
132+
};
133+
let location = info.location()
134+
.map(|l| format!("{}:{}:{}", l.file(), l.line(), l.column()))
135+
.unwrap_or_else(|| "<unknown>".to_string());
136+
let backtrace = std::backtrace::Backtrace::force_capture();
137+
let timestamp = std::time::SystemTime::now()
138+
.duration_since(std::time::UNIX_EPOCH)
139+
.map(|d| d.as_secs())
140+
.unwrap_or(0);
141+
let thread = std::thread::current();
142+
let thread_name = thread.name().unwrap_or("<unnamed>");
143+
let entry = format!(
144+
"\n=== flutter_libmwc panic at unix={} ===\nthread: {}\nlocation: {}\nmessage: {}\nbacktrace:\n{}\n",
145+
timestamp, thread_name, location, msg, backtrace,
146+
);
147+
if let Ok(mut f) = std::fs::OpenOptions::new()
148+
.create(true)
149+
.append(true)
150+
.open(&log_path)
151+
{
152+
use std::io::Write;
153+
let _ = f.write_all(entry.as_bytes());
154+
let _ = f.flush();
155+
}
156+
previous(info);
157+
}));
158+
});
159+
}
160+
105161

106162
fn init_logger() {
107163
android_logger::init_once(
@@ -276,6 +332,7 @@ pub unsafe extern "C" fn mwc_rust_open_wallet(
276332
config: *const c_char,
277333
password: *const c_char,
278334
) -> *const c_char {
335+
install_panic_hook();
279336
//init_logger();
280337
let result = match _open_wallet(
281338
config,
@@ -475,36 +532,55 @@ pub unsafe extern "C" fn mwc_rust_wallet_scan_outputs(
475532
start_height: *const c_char,
476533
number_of_blocks: *const c_char,
477534
) -> *const c_char {
478-
let wallet_ptr = CStr::from_ptr(wallet);
479-
let c_start_height = CStr::from_ptr(start_height);
480-
let c_number_of_blocks = CStr::from_ptr(number_of_blocks);
481-
let start_height: u64 = c_start_height.to_str().unwrap().to_string().parse().unwrap();
482-
let number_of_blocks: u64 = c_number_of_blocks.to_str().unwrap().to_string().parse().unwrap();
483-
484-
let wallet_data = wallet_ptr.to_str().unwrap();
485-
let tuple_wallet_data: (u64, Option<SecretKey>) = serde_json::from_str(wallet_data).unwrap();
486-
let wlt = tuple_wallet_data.0;
487-
let sek_key = tuple_wallet_data.1;
488-
489-
ensure_wallet!(wlt, wallet);
535+
install_panic_hook();
536+
let wallet_ptr_raw = wallet;
537+
let start_height_raw = start_height;
538+
let number_of_blocks_raw = number_of_blocks;
539+
let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
540+
let wallet_ptr = CStr::from_ptr(wallet_ptr_raw);
541+
let c_start_height = CStr::from_ptr(start_height_raw);
542+
let c_number_of_blocks = CStr::from_ptr(number_of_blocks_raw);
543+
let start_height: u64 = c_start_height.to_str().unwrap().to_string().parse().unwrap();
544+
let number_of_blocks: u64 = c_number_of_blocks.to_str().unwrap().to_string().parse().unwrap();
545+
546+
let wallet_data = wallet_ptr.to_str().unwrap();
547+
let tuple_wallet_data: (u64, Option<SecretKey>) = serde_json::from_str(wallet_data).unwrap();
548+
let wlt = tuple_wallet_data.0;
549+
let sek_key = tuple_wallet_data.1;
490550

491-
let result = match _wallet_scan_outputs(
492-
wallet,
493-
sek_key,
494-
start_height,
495-
number_of_blocks
496-
) {
497-
Ok(scan) => {
498-
scan
499-
}, Err(e ) => {
500-
let error_msg = format!("Error {}", &e.to_string());
501-
let error_msg_ptr = CString::new(error_msg).unwrap();
502-
let ptr = error_msg_ptr.as_ptr(); // Get a pointer to the underlaying memory for s
503-
std::mem::forget(error_msg_ptr);
551+
ensure_wallet!(wlt, wallet);
552+
553+
match _wallet_scan_outputs(
554+
wallet,
555+
sek_key,
556+
start_height,
557+
number_of_blocks
558+
) {
559+
Ok(scan) => scan,
560+
Err(e) => {
561+
let error_msg = format!("Error {}", &e.to_string());
562+
let error_msg_ptr = CString::new(error_msg).unwrap();
563+
let ptr = error_msg_ptr.as_ptr();
564+
std::mem::forget(error_msg_ptr);
565+
ptr
566+
}
567+
}
568+
}));
569+
match result {
570+
Ok(ptr) => ptr,
571+
Err(_) => {
572+
// Panic already captured by install_panic_hook; surface a Dart-side
573+
// error so the wallet's _startScans rethrows rather than the host
574+
// process aborting on the unwind boundary.
575+
let error_msg = CString::new(
576+
"Error scanOutputs panicked; see flutter_libmwc-panic.log",
577+
)
578+
.unwrap();
579+
let ptr = error_msg.as_ptr();
580+
std::mem::forget(error_msg);
504581
ptr
505582
}
506-
};
507-
result
583+
}
508584
}
509585

510586
fn _wallet_scan_outputs(
@@ -751,6 +827,7 @@ fn _tx_cancel(
751827
pub unsafe extern "C" fn mwc_rust_get_chain_height(
752828
config: *const c_char,
753829
) -> *const c_char {
830+
install_panic_hook();
754831
let result = match _get_chain_height(
755832
config
756833
) {
@@ -1376,6 +1453,15 @@ pub fn get_chain_height(config: &str) -> Result<u64, Error> {
13761453
return Err(e);
13771454
}
13781455
};
1456+
// HTTPNodeClient::get_chain_tip touches MWC code paths that require the
1457+
// chain type to be set. get_chain_height can run before any wallet-open
1458+
// path on a fresh process (e.g. via Stack Wallet's chainHeight getter
1459+
// firing on shouldAutoSync), so we must initialize chain type here too.
1460+
let target_chaintype = wallet_config.chain_type.unwrap_or(ChainTypes::Mainnet);
1461+
global::set_global_chain_type(target_chaintype);
1462+
if global::get_chain_type() != target_chaintype {
1463+
global::set_local_chain_type(target_chaintype);
1464+
};
13791465
let node_api_secret = get_first_line(wallet_config.node_api_secret_path.clone());
13801466
let node_client = HTTPNodeClient::new(vec![wallet_config.check_node_api_http_addr], node_api_secret)
13811467
.map_err(|e| Error::ClientCallback(format!("{}", e)))?;
@@ -1902,6 +1988,18 @@ impl Task for Listener {
19021988
type Output = usize;
19031989

19041990
fn run(&self, cancel_tok: &CancellationToken) -> Result<Self::Output, anyhow::Error> {
1991+
install_panic_hook();
1992+
// The listener runs on a worker thread spawned by `export_task!`.
1993+
// `mwc_rust_open_wallet` set `GLOBAL_CHAIN_TYPE` on the main thread,
1994+
// but the worker has its own thread-local `CHAIN_TYPE`. MWC code that
1995+
// reads `get_chain_type()` falls back to the global, so this is a
1996+
// defensive belt-and-suspenders init for any path that might read the
1997+
// thread-local directly.
1998+
let target_chaintype = ChainTypes::Mainnet;
1999+
global::set_global_chain_type(target_chaintype);
2000+
if global::get_chain_type() != target_chaintype {
2001+
global::set_local_chain_type(target_chaintype);
2002+
}
19052003
let mut spins = 0;
19062004
let tuple_wallet_data: (u64, Option<SecretKey>) = serde_json::from_str(&self.wallet_ptr_str)?;
19072005
let wlt = tuple_wallet_data.0;
@@ -1947,6 +2045,7 @@ pub unsafe extern "C" fn mwc_rust_mwcmqs_listener_start(
19472045
wallet: *const c_char,
19482046
mwcmqs_config: *const c_char,
19492047
) -> *mut c_void {
2048+
install_panic_hook();
19502049
let wallet_ptr = CStr::from_ptr(wallet);
19512050
let mwcmqs_config = CStr::from_ptr(mwcmqs_config);
19522051
let mwcmqs_config = mwcmqs_config.to_str().unwrap();

0 commit comments

Comments
 (0)