Skip to content

Commit 21f6ae1

Browse files
feat: additional ffi
1 parent 9b85df8 commit 21f6ae1

File tree

4 files changed

+298
-4
lines changed

4 files changed

+298
-4
lines changed

dash-spv-ffi/include/dash_spv_ffi.h

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -421,6 +421,40 @@ int32_t dash_spv_ffi_client_sync_to_tip_with_progress(struct FFIDashSpvClient *c
421421
*/
422422
struct FFISpvStats *dash_spv_ffi_client_get_stats(struct FFIDashSpvClient *client) ;
423423

424+
/**
425+
* Get the current chain tip hash (32 bytes) if available.
426+
*
427+
* # Safety
428+
* - `client` must be a valid, non-null pointer.
429+
* - `out_hash` must be a valid pointer to a 32-byte buffer.
430+
*/
431+
int32_t dash_spv_ffi_client_get_tip_hash(struct FFIDashSpvClient *client, uint8_t *out_hash) ;
432+
433+
/**
434+
* Get the current chain tip height (absolute).
435+
*
436+
* # Safety
437+
* - `client` must be a valid, non-null pointer.
438+
* - `out_height` must be a valid, non-null pointer.
439+
*/
440+
int32_t dash_spv_ffi_client_get_tip_height(struct FFIDashSpvClient *client, uint32_t *out_height) ;
441+
442+
/**
443+
* Clear all persisted SPV storage (headers, filters, metadata, sync state).
444+
*
445+
* # Safety
446+
* - `client` must be a valid, non-null pointer.
447+
*/
448+
int32_t dash_spv_ffi_client_clear_storage(struct FFIDashSpvClient *client) ;
449+
450+
/**
451+
* Clear only the persisted sync-state snapshot.
452+
*
453+
* # Safety
454+
* - `client` must be a valid, non-null pointer.
455+
*/
456+
int32_t dash_spv_ffi_client_clear_sync_state(struct FFIDashSpvClient *client) ;
457+
424458
/**
425459
* Check if compact filter sync is currently available.
426460
*

dash-spv-ffi/src/client.rs

Lines changed: 200 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,10 @@ use crate::{
55
// Import wallet types from key-wallet-ffi
66
use key_wallet_ffi::FFIWalletManager;
77

8+
use dash_spv::storage::DiskStorageManager;
89
use dash_spv::types::SyncStage;
910
use dash_spv::DashSpvClient;
11+
use dash_spv::Hash;
1012
use dashcore::Txid;
1113

1214
use 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
>;
111113
type 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

dash-spv/src/client/mod.rs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,30 @@ impl<
9494
S: StorageManager + Send + Sync + 'static,
9595
> DashSpvClient<W, N, S>
9696
{
97+
/// Returns the current chain tip hash if available.
98+
pub async fn tip_hash(&self) -> Option<dashcore::BlockHash> {
99+
let state = self.state.read().await;
100+
state.tip_hash()
101+
}
102+
103+
/// Returns the current chain tip height (absolute), accounting for checkpoint base.
104+
pub async fn tip_height(&self) -> u32 {
105+
let state = self.state.read().await;
106+
state.tip_height()
107+
}
108+
109+
/// Clear all persisted storage (headers, filters, state, sync state).
110+
pub async fn clear_storage(&mut self) -> Result<()> {
111+
let mut storage = self.storage.lock().await;
112+
storage.clear().await.map_err(SpvError::Storage)
113+
}
114+
115+
/// Clear only the persisted sync state snapshot (keep headers/filters).
116+
pub async fn clear_sync_state(&mut self) -> Result<()> {
117+
let mut storage = self.storage.lock().await;
118+
storage.clear_sync_state().await.map_err(SpvError::Storage)
119+
}
120+
97121
/// Take the progress receiver for external consumption.
98122
pub fn take_progress_receiver(
99123
&mut self,

swift-dash-core-sdk/Sources/DashSPVFFI/include/dash_spv_ffi.h

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,11 @@ typedef void (*WalletTransactionCallback)(const char *wallet_id,
149149
bool is_ours,
150150
void *user_data);
151151

152+
typedef void (*FilterHeadersProgressCallback)(uint32_t filter_height,
153+
uint32_t header_height,
154+
double percentage,
155+
void *user_data);
156+
152157
typedef struct FFIEventCallbacks {
153158
BlockCallback on_block;
154159
TransactionCallback on_transaction;
@@ -158,6 +163,7 @@ typedef struct FFIEventCallbacks {
158163
MempoolRemovedCallback on_mempool_transaction_removed;
159164
CompactFilterMatchedCallback on_compact_filter_matched;
160165
WalletTransactionCallback on_wallet_transaction;
166+
FilterHeadersProgressCallback on_filter_headers_progress;
161167
void *user_data;
162168
} FFIEventCallbacks;
163169

@@ -415,6 +421,40 @@ int32_t dash_spv_ffi_client_sync_to_tip_with_progress(struct FFIDashSpvClient *c
415421
*/
416422
struct FFISpvStats *dash_spv_ffi_client_get_stats(struct FFIDashSpvClient *client) ;
417423

424+
/**
425+
* Get the current chain tip hash (32 bytes) if available.
426+
*
427+
* # Safety
428+
* - `client` must be a valid, non-null pointer.
429+
* - `out_hash` must be a valid pointer to a 32-byte buffer.
430+
*/
431+
int32_t dash_spv_ffi_client_get_tip_hash(struct FFIDashSpvClient *client, uint8_t *out_hash) ;
432+
433+
/**
434+
* Get the current chain tip height (absolute).
435+
*
436+
* # Safety
437+
* - `client` must be a valid, non-null pointer.
438+
* - `out_height` must be a valid, non-null pointer.
439+
*/
440+
int32_t dash_spv_ffi_client_get_tip_height(struct FFIDashSpvClient *client, uint32_t *out_height) ;
441+
442+
/**
443+
* Clear all persisted SPV storage (headers, filters, metadata, sync state).
444+
*
445+
* # Safety
446+
* - `client` must be a valid, non-null pointer.
447+
*/
448+
int32_t dash_spv_ffi_client_clear_storage(struct FFIDashSpvClient *client) ;
449+
450+
/**
451+
* Clear only the persisted sync-state snapshot.
452+
*
453+
* # Safety
454+
* - `client` must be a valid, non-null pointer.
455+
*/
456+
int32_t dash_spv_ffi_client_clear_sync_state(struct FFIDashSpvClient *client) ;
457+
418458
/**
419459
* Check if compact filter sync is currently available.
420460
*

0 commit comments

Comments
 (0)