@@ -5,8 +5,10 @@ use crate::{
55// Import wallet types from key-wallet-ffi
66use key_wallet_ffi:: FFIWalletManager ;
77
8+ use dash_spv:: storage:: DiskStorageManager ;
89use dash_spv:: types:: SyncStage ;
910use dash_spv:: DashSpvClient ;
11+ use dash_spv:: Hash ;
1012use dashcore:: Txid ;
1113
1214use once_cell:: sync:: Lazy ;
@@ -106,7 +108,7 @@ type InnerClient = DashSpvClient<
106108 key_wallet:: wallet:: managed_wallet_info:: ManagedWalletInfo ,
107109 > ,
108110 dash_spv:: network:: MultiPeerNetworkManager ,
109- dash_spv :: storage :: MemoryStorageManager ,
111+ DiskStorageManager ,
110112> ;
111113type SharedClient = Arc < Mutex < Option < InnerClient > > > ;
112114
@@ -144,11 +146,24 @@ pub unsafe extern "C" fn dash_spv_ffi_client_new(
144146 }
145147 } ;
146148
147- let client_config = config. clone_inner ( ) ;
148- let client_result = runtime. block_on ( async {
149+ let mut client_config = config. clone_inner ( ) ;
150+
151+ let storage_path = client_config. storage_path . clone ( ) . unwrap_or_else ( || {
152+ let mut path = std:: env:: temp_dir ( ) ;
153+ path. push ( "dash-spv" ) ;
154+ path. push ( format ! ( "{:?}" , client_config. network) . to_lowercase ( ) ) ;
155+ tracing:: warn!(
156+ "dash-spv FFI config missing storage path, falling back to temp dir {:?}" ,
157+ path
158+ ) ;
159+ path
160+ } ) ;
161+ client_config. storage_path = Some ( storage_path. clone ( ) ) ;
162+
163+ let client_result = runtime. block_on ( async move {
149164 // Construct concrete implementations for generics
150165 let network = dash_spv:: network:: MultiPeerNetworkManager :: new ( & client_config) . await ;
151- let storage = dash_spv :: storage :: MemoryStorageManager :: new ( ) . await ;
166+ let storage = DiskStorageManager :: new ( storage_path . clone ( ) ) . await ;
152167 let wallet = key_wallet_manager:: wallet_manager:: WalletManager :: <
153168 key_wallet:: wallet:: managed_wallet_info:: ManagedWalletInfo ,
154169 > :: new ( ) ;
@@ -994,6 +1009,187 @@ pub unsafe extern "C" fn dash_spv_ffi_client_get_stats(
9941009 }
9951010}
9961011
1012+ /// Get the current chain tip hash (32 bytes) if available.
1013+ ///
1014+ /// # Safety
1015+ /// - `client` must be a valid, non-null pointer.
1016+ /// - `out_hash` must be a valid pointer to a 32-byte buffer.
1017+ #[ no_mangle]
1018+ pub unsafe extern "C" fn dash_spv_ffi_client_get_tip_hash (
1019+ client : * mut FFIDashSpvClient ,
1020+ out_hash : * mut u8 ,
1021+ ) -> i32 {
1022+ null_check ! ( client) ;
1023+ if out_hash. is_null ( ) {
1024+ set_last_error ( "Null out_hash pointer" ) ;
1025+ return FFIErrorCode :: NullPointer as i32 ;
1026+ }
1027+
1028+ let client = & ( * client) ;
1029+ let inner = client. inner . clone ( ) ;
1030+
1031+ let result = client. runtime . block_on ( async {
1032+ let spv_client = {
1033+ let mut guard = inner. lock ( ) . unwrap ( ) ;
1034+ match guard. take ( ) {
1035+ Some ( c) => c,
1036+ None => {
1037+ return Err ( dash_spv:: SpvError :: Config ( "Client not initialized" . to_string ( ) ) )
1038+ }
1039+ }
1040+ } ;
1041+ let tip = spv_client. tip_hash ( ) . await ;
1042+ let mut guard = inner. lock ( ) . unwrap ( ) ;
1043+ * guard = Some ( spv_client) ;
1044+ Ok ( tip)
1045+ } ) ;
1046+
1047+ match result {
1048+ Ok ( Some ( hash) ) => {
1049+ let bytes = hash. to_byte_array ( ) ;
1050+ // SAFETY: out_hash points to a buffer with at least 32 bytes
1051+ std:: ptr:: copy_nonoverlapping ( bytes. as_ptr ( ) , out_hash, 32 ) ;
1052+ FFIErrorCode :: Success as i32
1053+ }
1054+ Ok ( None ) => {
1055+ set_last_error ( "No tip hash available" ) ;
1056+ FFIErrorCode :: StorageError as i32
1057+ }
1058+ Err ( e) => {
1059+ set_last_error ( & e. to_string ( ) ) ;
1060+ FFIErrorCode :: from ( e) as i32
1061+ }
1062+ }
1063+ }
1064+
1065+ /// Get the current chain tip height (absolute).
1066+ ///
1067+ /// # Safety
1068+ /// - `client` must be a valid, non-null pointer.
1069+ /// - `out_height` must be a valid, non-null pointer.
1070+ #[ no_mangle]
1071+ pub unsafe extern "C" fn dash_spv_ffi_client_get_tip_height (
1072+ client : * mut FFIDashSpvClient ,
1073+ out_height : * mut u32 ,
1074+ ) -> i32 {
1075+ null_check ! ( client) ;
1076+ if out_height. is_null ( ) {
1077+ set_last_error ( "Null out_height pointer" ) ;
1078+ return FFIErrorCode :: NullPointer as i32 ;
1079+ }
1080+
1081+ let client = & ( * client) ;
1082+ let inner = client. inner . clone ( ) ;
1083+
1084+ let result = client. runtime . block_on ( async {
1085+ let spv_client = {
1086+ let mut guard = inner. lock ( ) . unwrap ( ) ;
1087+ match guard. take ( ) {
1088+ Some ( c) => c,
1089+ None => {
1090+ return Err ( dash_spv:: SpvError :: Config ( "Client not initialized" . to_string ( ) ) )
1091+ }
1092+ }
1093+ } ;
1094+ let height = spv_client. tip_height ( ) . await ;
1095+ let mut guard = inner. lock ( ) . unwrap ( ) ;
1096+ * guard = Some ( spv_client) ;
1097+ Ok ( height)
1098+ } ) ;
1099+
1100+ match result {
1101+ Ok ( height) => {
1102+ * out_height = height;
1103+ FFIErrorCode :: Success as i32
1104+ }
1105+ Err ( e) => {
1106+ set_last_error ( & e. to_string ( ) ) ;
1107+ FFIErrorCode :: from ( e) as i32
1108+ }
1109+ }
1110+ }
1111+
1112+ /// Clear all persisted SPV storage (headers, filters, metadata, sync state).
1113+ ///
1114+ /// # Safety
1115+ /// - `client` must be a valid, non-null pointer.
1116+ #[ no_mangle]
1117+ pub unsafe extern "C" fn dash_spv_ffi_client_clear_storage ( client : * mut FFIDashSpvClient ) -> i32 {
1118+ null_check ! ( client) ;
1119+
1120+ let client = & ( * client) ;
1121+ let inner = client. inner . clone ( ) ;
1122+
1123+ let result = client. runtime . block_on ( async {
1124+ let mut spv_client = {
1125+ let mut guard = inner. lock ( ) . unwrap ( ) ;
1126+ match guard. take ( ) {
1127+ Some ( c) => c,
1128+ None => {
1129+ return Err ( dash_spv:: SpvError :: Config ( "Client not initialized" . to_string ( ) ) )
1130+ }
1131+ }
1132+ } ;
1133+
1134+ // Try to stop before clearing to ensure no in-flight writes race the wipe.
1135+ if let Err ( e) = spv_client. stop ( ) . await {
1136+ tracing:: warn!( "Failed to stop client before clearing storage: {}" , e) ;
1137+ }
1138+
1139+ let res = spv_client. clear_storage ( ) . await ;
1140+ let mut guard = inner. lock ( ) . unwrap ( ) ;
1141+ * guard = Some ( spv_client) ;
1142+ res
1143+ } ) ;
1144+
1145+ match result {
1146+ Ok ( _) => FFIErrorCode :: Success as i32 ,
1147+ Err ( e) => {
1148+ set_last_error ( & e. to_string ( ) ) ;
1149+ FFIErrorCode :: from ( e) as i32
1150+ }
1151+ }
1152+ }
1153+
1154+ /// Clear only the persisted sync-state snapshot.
1155+ ///
1156+ /// # Safety
1157+ /// - `client` must be a valid, non-null pointer.
1158+ #[ no_mangle]
1159+ pub unsafe extern "C" fn dash_spv_ffi_client_clear_sync_state (
1160+ client : * mut FFIDashSpvClient ,
1161+ ) -> i32 {
1162+ null_check ! ( client) ;
1163+
1164+ let client = & ( * client) ;
1165+ let inner = client. inner . clone ( ) ;
1166+
1167+ let result = client. runtime . block_on ( async {
1168+ let mut spv_client = {
1169+ let mut guard = inner. lock ( ) . unwrap ( ) ;
1170+ match guard. take ( ) {
1171+ Some ( c) => c,
1172+ None => {
1173+ return Err ( dash_spv:: SpvError :: Config ( "Client not initialized" . to_string ( ) ) )
1174+ }
1175+ }
1176+ } ;
1177+
1178+ let res = spv_client. clear_sync_state ( ) . await ;
1179+ let mut guard = inner. lock ( ) . unwrap ( ) ;
1180+ * guard = Some ( spv_client) ;
1181+ res
1182+ } ) ;
1183+
1184+ match result {
1185+ Ok ( _) => FFIErrorCode :: Success as i32 ,
1186+ Err ( e) => {
1187+ set_last_error ( & e. to_string ( ) ) ;
1188+ FFIErrorCode :: from ( e) as i32
1189+ }
1190+ }
1191+ }
1192+
9971193/// Check if compact filter sync is currently available.
9981194///
9991195/// # Safety
0 commit comments