Skip to content

Commit c022a39

Browse files
committed
feat: add mempool support
See the changes in `ARCHITECTURE.md` for a detailed description. - Add `MempoolManager` that activates after initial sync to monitor unconfirmed transactions via BIP37 bloom filters or local address matching. Includes peer relay management, dedup tracking, IS lock handling, and auto-rebuilding filters on address pool changes. - Extend `WalletInterface` with `MempoolTransactionResult` return type and `watched_outpoints()` for bloom filter construction. Wire mempool manager into `SyncCoordinator` and propagate `confirmed_txids` through `BlockProcessed` events for mempool eviction. - Add FFI bindings, dashd integration tests, and wallet unit tests.
1 parent 92cf7db commit c022a39

34 files changed

Lines changed: 4296 additions & 55 deletions

dash-spv-ffi/FFI_API.md

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ This document provides a comprehensive reference for all FFI (Foreign Function I
44

55
**Auto-generated**: This documentation is automatically generated from the source code. Do not edit manually.
66

7-
**Total Functions**: 49
7+
**Total Functions**: 50
88

99
## Table of Contents
1010

@@ -13,6 +13,7 @@ This document provides a comprehensive reference for all FFI (Foreign Function I
1313
- [Synchronization](#synchronization)
1414
- [Wallet Operations](#wallet-operations)
1515
- [Transaction Management](#transaction-management)
16+
- [Mempool Operations](#mempool-operations)
1617
- [Platform Integration](#platform-integration)
1718
- [Event Callbacks](#event-callbacks)
1819
- [Error Handling](#error-handling)
@@ -82,6 +83,14 @@ Functions: 1
8283
|----------|-------------|--------|
8384
| `dash_spv_ffi_client_broadcast_transaction` | Broadcasts a transaction to the Dash network via connected peers | client |
8485

86+
### Mempool Operations
87+
88+
Functions: 1
89+
90+
| Function | Description | Module |
91+
|----------|-------------|--------|
92+
| `dash_spv_ffi_mempool_progress_destroy` | Destroy an `FFIMempoolProgress` object | types |
93+
8594
### Platform Integration
8695

8796
Functions: 2
@@ -558,6 +567,24 @@ Broadcasts a transaction to the Dash network via connected peers. # Safety - `
558567

559568
---
560569

570+
### Mempool Operations - Detailed
571+
572+
#### `dash_spv_ffi_mempool_progress_destroy`
573+
574+
```c
575+
dash_spv_ffi_mempool_progress_destroy(progress: *mut FFIMempoolProgress) -> ()
576+
```
577+
578+
**Description:**
579+
Destroy an `FFIMempoolProgress` object. # Safety - `progress` must be a pointer returned from this crate, or null.
580+
581+
**Safety:**
582+
- `progress` must be a pointer returned from this crate, or null.
583+
584+
**Module:** `types`
585+
586+
---
587+
561588
### Platform Integration - Detailed
562589
563590
#### `ffi_dash_spv_get_platform_activation_height`

dash-spv-ffi/include/dash_spv_ffi.h

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ typedef enum FFIManagerId {
3838
Masternodes = 4,
3939
ChainLocks = 5,
4040
InstantSend = 6,
41+
Mempool = 7,
4142
} FFIManagerId;
4243

4344
typedef enum FFIMempoolStrategy {
@@ -144,6 +145,18 @@ typedef struct FFIInstantSendProgress {
144145
uint64_t last_activity;
145146
} FFIInstantSendProgress;
146147

148+
/**
149+
* Progress for mempool transaction monitoring.
150+
*/
151+
typedef struct FFIMempoolProgress {
152+
enum FFISyncState state;
153+
uint32_t received;
154+
uint32_t relevant;
155+
uint32_t tracked;
156+
uint32_t removed;
157+
uint64_t last_activity;
158+
} FFIMempoolProgress;
159+
147160
/**
148161
* Aggregate progress for all sync managers.
149162
* Provides a complete view of the parallel sync system's state.
@@ -162,6 +175,7 @@ typedef struct FFISyncProgress {
162175
struct FFIMasternodesProgress *masternodes;
163176
struct FFIChainLockProgress *chainlocks;
164177
struct FFIInstantSendProgress *instantsend;
178+
struct FFIMempoolProgress *mempool;
165179
} FFISyncProgress;
166180

167181
/**
@@ -249,6 +263,8 @@ typedef void (*OnBlocksNeededCallback)(const struct FFIBlockNeeded *blocks,
249263
typedef void (*OnBlockProcessedCallback)(uint32_t height,
250264
const uint8_t (*hash)[32],
251265
uint32_t new_address_count,
266+
const uint8_t (*confirmed_txids)[32],
267+
uint32_t confirmed_txid_count,
252268
void *user_data);
253269

254270
/**
@@ -976,6 +992,14 @@ struct FFIResult ffi_dash_spv_get_platform_activation_height(struct FFIDashSpvCl
976992
*/
977993
void dash_spv_ffi_instantsend_progress_destroy(struct FFIInstantSendProgress *progress) ;
978994

995+
/**
996+
* Destroy an `FFIMempoolProgress` object.
997+
*
998+
* # Safety
999+
* - `progress` must be a pointer returned from this crate, or null.
1000+
*/
1001+
void dash_spv_ffi_mempool_progress_destroy(struct FFIMempoolProgress *progress) ;
1002+
9791003
/**
9801004
* Destroy an `FFISyncProgress` object and all its nested pointers.
9811005
*

dash-spv-ffi/src/bin/ffi_cli.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ extern "C" fn on_sync_start(manager_id: FFIManagerId, _user_data: *mut c_void) {
2929
FFIManagerId::Masternodes => "Masternodes",
3030
FFIManagerId::ChainLocks => "ChainLocks",
3131
FFIManagerId::InstantSend => "InstantSend",
32+
FFIManagerId::Mempool => "Mempool",
3233
};
3334
println!("[Sync] Manager started: {}", manager_name);
3435
}
@@ -76,9 +77,14 @@ extern "C" fn on_block_processed(
7677
height: u32,
7778
_hash: *const [u8; 32],
7879
new_address_count: u32,
80+
_confirmed_txids: *const [u8; 32],
81+
confirmed_txid_count: u32,
7982
_user_data: *mut c_void,
8083
) {
81-
println!("[Sync] Block processed: height={}, new_addresses={}", height, new_address_count);
84+
println!(
85+
"[Sync] Block processed: height={}, new_addresses={}, confirmed_txs={}",
86+
height, new_address_count, confirmed_txid_count
87+
);
8288
}
8389

8490
extern "C" fn on_masternode_state_updated(height: u32, _user_data: *mut c_void) {

dash-spv-ffi/src/callbacks.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ pub enum FFIManagerId {
2727
Masternodes = 4,
2828
ChainLocks = 5,
2929
InstantSend = 6,
30+
Mempool = 7,
3031
}
3132

3233
impl From<dash_spv::sync::ManagerIdentifier> for FFIManagerId {
@@ -39,6 +40,7 @@ impl From<dash_spv::sync::ManagerIdentifier> for FFIManagerId {
3940
dash_spv::sync::ManagerIdentifier::Masternode => FFIManagerId::Masternodes,
4041
dash_spv::sync::ManagerIdentifier::ChainLock => FFIManagerId::ChainLocks,
4142
dash_spv::sync::ManagerIdentifier::InstantSend => FFIManagerId::InstantSend,
43+
dash_spv::sync::ManagerIdentifier::Mempool => FFIManagerId::Mempool,
4244
}
4345
}
4446
}
@@ -162,6 +164,8 @@ pub type OnBlockProcessedCallback = Option<
162164
height: u32,
163165
hash: *const [u8; 32],
164166
new_address_count: u32,
167+
confirmed_txids: *const [u8; 32],
168+
confirmed_txid_count: u32,
165169
user_data: *mut c_void,
166170
),
167171
>;
@@ -350,13 +354,18 @@ impl FFISyncEventCallbacks {
350354
block_hash,
351355
height,
352356
new_addresses,
357+
confirmed_txids,
353358
} => {
354359
if let Some(cb) = self.on_block_processed {
355360
let hash_bytes = block_hash.as_byte_array();
361+
let txid_bytes: Vec<[u8; 32]> =
362+
confirmed_txids.iter().map(|txid| *txid.as_byte_array()).collect();
356363
cb(
357364
*height,
358365
hash_bytes as *const [u8; 32],
359366
new_addresses.len() as u32,
367+
txid_bytes.as_ptr(),
368+
txid_bytes.len() as u32,
360369
self.user_data,
361370
);
362371
}

dash-spv-ffi/src/types.rs

Lines changed: 49 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
use dash_spv::client::config::MempoolStrategy;
22
use dash_spv::sync::{
33
BlockHeadersProgress, BlocksProgress, ChainLockProgress, FilterHeadersProgress,
4-
FiltersProgress, InstantSendProgress, MasternodesProgress, ProgressPercentage, SyncProgress,
5-
SyncState,
4+
FiltersProgress, InstantSendProgress, MasternodesProgress, MempoolProgress, ProgressPercentage,
5+
SyncProgress, SyncState,
66
};
77
use dash_spv::types::MempoolRemovalReason;
88
use std::ffi::{CStr, CString};
@@ -259,6 +259,31 @@ impl From<&InstantSendProgress> for FFIInstantSendProgress {
259259
}
260260
}
261261

262+
/// Progress for mempool transaction monitoring.
263+
#[repr(C)]
264+
#[derive(Debug, Clone, Default)]
265+
pub struct FFIMempoolProgress {
266+
pub state: FFISyncState,
267+
pub received: u32,
268+
pub relevant: u32,
269+
pub tracked: u32,
270+
pub removed: u32,
271+
pub last_activity: u64,
272+
}
273+
274+
impl From<&MempoolProgress> for FFIMempoolProgress {
275+
fn from(progress: &MempoolProgress) -> Self {
276+
FFIMempoolProgress {
277+
state: progress.state().into(),
278+
received: progress.received(),
279+
relevant: progress.relevant(),
280+
tracked: progress.tracked(),
281+
removed: progress.removed(),
282+
last_activity: progress.last_activity().elapsed().as_secs(),
283+
}
284+
}
285+
}
286+
262287
/// Aggregate progress for all sync managers.
263288
/// Provides a complete view of the parallel sync system's state.
264289
#[repr(C)]
@@ -274,6 +299,7 @@ pub struct FFISyncProgress {
274299
pub masternodes: *mut FFIMasternodesProgress,
275300
pub chainlocks: *mut FFIChainLockProgress,
276301
pub instantsend: *mut FFIInstantSendProgress,
302+
pub mempool: *mut FFIMempoolProgress,
277303
}
278304

279305
impl From<SyncProgress> for FFISyncProgress {
@@ -320,6 +346,12 @@ impl From<SyncProgress> for FFISyncProgress {
320346
.map(|p| Box::into_raw(Box::new(FFIInstantSendProgress::from(p))))
321347
.unwrap_or(std::ptr::null_mut());
322348

349+
let mempool = progress
350+
.mempool()
351+
.ok()
352+
.map(|p| Box::into_raw(Box::new(FFIMempoolProgress::from(p))))
353+
.unwrap_or(std::ptr::null_mut());
354+
323355
Self {
324356
state: progress.state().into(),
325357
percentage: progress.percentage(),
@@ -331,6 +363,7 @@ impl From<SyncProgress> for FFISyncProgress {
331363
masternodes,
332364
chainlocks,
333365
instantsend,
366+
mempool,
334367
}
335368
}
336369
}
@@ -486,6 +519,17 @@ pub unsafe extern "C" fn dash_spv_ffi_instantsend_progress_destroy(
486519
}
487520
}
488521

522+
/// Destroy an `FFIMempoolProgress` object.
523+
///
524+
/// # Safety
525+
/// - `progress` must be a pointer returned from this crate, or null.
526+
#[no_mangle]
527+
pub unsafe extern "C" fn dash_spv_ffi_mempool_progress_destroy(progress: *mut FFIMempoolProgress) {
528+
if !progress.is_null() {
529+
let _ = Box::from_raw(progress);
530+
}
531+
}
532+
489533
/// Destroy an `FFISyncProgress` object and all its nested pointers.
490534
///
491535
/// # Safety
@@ -517,5 +561,8 @@ pub unsafe extern "C" fn dash_spv_ffi_sync_progress_destroy(progress: *mut FFISy
517561
if !p.instantsend.is_null() {
518562
dash_spv_ffi_instantsend_progress_destroy(p.instantsend);
519563
}
564+
if !p.mempool.is_null() {
565+
dash_spv_ffi_mempool_progress_destroy(p.mempool);
566+
}
520567
}
521568
}

dash-spv-ffi/tests/dashd_sync/callbacks.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -233,14 +233,21 @@ extern "C" fn on_block_processed(
233233
height: u32,
234234
_hash: *const [u8; 32],
235235
new_address_count: u32,
236+
_confirmed_txids: *const [u8; 32],
237+
confirmed_txid_count: u32,
236238
user_data: *mut c_void,
237239
) {
238240
let Some(tracker) = (unsafe { tracker_from(user_data) }) else {
239241
return;
240242
};
241243
tracker.processed_block_heights.lock().unwrap_or_else(|e| e.into_inner()).push(height);
242244
tracker.block_processed_count.fetch_add(1, Ordering::SeqCst);
243-
tracing::debug!("on_block_processed: height={}, new_addresses={}", height, new_address_count);
245+
tracing::debug!(
246+
"on_block_processed: height={}, new_addresses={}, confirmed_txs={}",
247+
height,
248+
new_address_count,
249+
confirmed_txid_count
250+
);
244251
}
245252

246253
extern "C" fn on_masternode_state_updated(height: u32, user_data: *mut c_void) {

0 commit comments

Comments
 (0)