11use crate :: {
2- null_check, set_last_error, FFIClientConfig , FFIErrorCode , FFINetworkEventCallbacks ,
3- FFIProgressCallback , FFISyncEventCallbacks , FFISyncProgress , FFIWalletEventCallbacks ,
4- FFIWalletManager ,
2+ null_check, set_last_error, FFIClientConfig , FFIClientErrorCallback , FFIErrorCode ,
3+ FFINetworkEventCallbacks , FFIProgressCallback , FFISyncEventCallbacks , FFISyncProgress ,
4+ FFIWalletEventCallbacks , FFIWalletManager ,
55} ;
66// Import wallet types from key-wallet-ffi
77use key_wallet_ffi:: FFIWalletManager as KeyWalletFFIWalletManager ;
@@ -130,6 +130,7 @@ pub struct FFIDashSpvClient {
130130 network_event_callbacks : Arc < Mutex < Option < FFINetworkEventCallbacks > > > ,
131131 wallet_event_callbacks : Arc < Mutex < Option < FFIWalletEventCallbacks > > > ,
132132 progress_callback : Arc < Mutex < Option < FFIProgressCallback > > > ,
133+ client_error_callback : Arc < Mutex < Option < FFIClientErrorCallback > > > ,
133134}
134135
135136/// Create a new SPV client and return an opaque pointer.
@@ -189,6 +190,7 @@ pub unsafe extern "C" fn dash_spv_ffi_client_new(
189190 network_event_callbacks : Arc :: new ( Mutex :: new ( None ) ) ,
190191 wallet_event_callbacks : Arc :: new ( Mutex :: new ( None ) ) ,
191192 progress_callback : Arc :: new ( Mutex :: new ( None ) ) ,
193+ client_error_callback : Arc :: new ( Mutex :: new ( None ) ) ,
192194 } ;
193195 Box :: into_raw ( Box :: new ( ffi_client) )
194196 }
@@ -380,34 +382,6 @@ pub unsafe extern "C" fn dash_spv_ffi_client_run(client: *mut FFIDashSpvClient)
380382
381383 tracing:: info!( "dash_spv_ffi_client_run: starting client" ) ;
382384
383- // Start the client first
384- let inner = client. inner . clone ( ) ;
385- let start_result = client. runtime . block_on ( async {
386- let mut spv_client = {
387- let mut guard = inner. lock ( ) . unwrap ( ) ;
388- match guard. take ( ) {
389- Some ( c) => c,
390- None => {
391- return Err ( dash_spv:: SpvError :: Storage ( dash_spv:: StorageError :: NotFound (
392- "Client not initialized" . to_string ( ) ,
393- ) ) )
394- }
395- }
396- } ;
397- let res = spv_client. start ( ) . await ;
398- let mut guard = inner. lock ( ) . unwrap ( ) ;
399- * guard = Some ( spv_client) ;
400- res
401- } ) ;
402-
403- if let Err ( e) = start_result {
404- tracing:: error!( "dash_spv_ffi_client_run: start failed: {}" , e) ;
405- set_last_error ( & e. to_string ( ) ) ;
406- return FFIErrorCode :: from ( e) as i32 ;
407- }
408-
409- tracing:: info!( "dash_spv_ffi_client_run: client started, setting up event monitoring" ) ;
410-
411385 // Get event subscriptions before taking the client for the sync thread.
412386 // The sync thread needs exclusive access, so we must subscribe first.
413387 let inner = client. inner . clone ( ) ;
@@ -487,6 +461,8 @@ pub unsafe extern "C" fn dash_spv_ffi_client_run(client: *mut FFIDashSpvClient)
487461
488462 tracing:: info!( "dash_spv_ffi_client_run: spawning sync thread" ) ;
489463
464+ let error_callback = client. client_error_callback . clone ( ) ;
465+
490466 // Now take the client for the sync thread
491467 let spv_client = {
492468 let mut guard = inner. lock ( ) . unwrap ( ) ;
@@ -506,7 +482,19 @@ pub unsafe extern "C" fn dash_spv_ffi_client_run(client: *mut FFIDashSpvClient)
506482
507483 let mut spv_client = spv_client;
508484
509- tracing:: debug!( "Sync thread: got client, starting monitor_network" ) ;
485+ if let Err ( e) = spv_client. start ( ) . await {
486+ tracing:: error!( "Sync thread: client start error: {}" , e) ;
487+ let guard = error_callback. lock ( ) . unwrap ( ) ;
488+ if let Some ( cb) = guard. as_ref ( ) {
489+ cb. dispatch ( & e. to_string ( ) ) ;
490+ }
491+ drop ( guard) ;
492+ let mut guard = inner. lock ( ) . unwrap ( ) ;
493+ * guard = Some ( spv_client) ;
494+ return ;
495+ }
496+
497+ tracing:: debug!( "Sync thread: starting monitor_network" ) ;
510498
511499 let ( _command_sender, command_receiver) = tokio:: sync:: mpsc:: unbounded_channel ( ) ;
512500 let run_token = shutdown_token. clone ( ) ;
@@ -536,6 +524,11 @@ pub unsafe extern "C" fn dash_spv_ffi_client_run(client: *mut FFIDashSpvClient)
536524
537525 if let Err ( e) = result {
538526 tracing:: error!( "Sync thread: sync error: {}" , e) ;
527+ let guard = error_callback. lock ( ) . unwrap ( ) ;
528+ if let Some ( cb) = guard. as_ref ( ) {
529+ cb. dispatch ( & e. to_string ( ) ) ;
530+ }
531+ drop ( guard) ;
539532 }
540533
541534 tracing:: debug!( "Sync thread: putting client back" ) ;
@@ -1071,3 +1064,38 @@ pub unsafe extern "C" fn dash_spv_ffi_client_clear_progress_callback(
10711064
10721065 FFIErrorCode :: Success as i32
10731066}
1067+
1068+ /// Set a callback for fatal client errors (start failure, sync thread crash).
1069+ ///
1070+ /// # Safety
1071+ /// - `client` must be a valid, non-null pointer to an `FFIDashSpvClient`.
1072+ /// - The `callback` struct and its `user_data` must remain valid until the callback is cleared.
1073+ /// - The callback must be thread-safe as it may be called from a background thread.
1074+ #[ no_mangle]
1075+ pub unsafe extern "C" fn dash_spv_ffi_client_set_client_error_callback (
1076+ client : * mut FFIDashSpvClient ,
1077+ callback : FFIClientErrorCallback ,
1078+ ) -> i32 {
1079+ null_check ! ( client) ;
1080+
1081+ let client = & ( * client) ;
1082+ * client. client_error_callback . lock ( ) . unwrap ( ) = Some ( callback) ;
1083+
1084+ FFIErrorCode :: Success as i32
1085+ }
1086+
1087+ /// Clear the client error callback.
1088+ ///
1089+ /// # Safety
1090+ /// - `client` must be a valid, non-null pointer to an `FFIDashSpvClient`.
1091+ #[ no_mangle]
1092+ pub unsafe extern "C" fn dash_spv_ffi_client_clear_client_error_callback (
1093+ client : * mut FFIDashSpvClient ,
1094+ ) -> i32 {
1095+ null_check ! ( client) ;
1096+
1097+ let client = & ( * client) ;
1098+ * client. client_error_callback . lock ( ) . unwrap ( ) = None ;
1099+
1100+ FFIErrorCode :: Success as i32
1101+ }
0 commit comments