Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 46 additions & 23 deletions dash-spv-ffi/src/bin/ffi_cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use clap::{Arg, ArgAction, Command};
use dash_spv_ffi::*;
use dashcore::ffi::FFINetwork;
use key_wallet_ffi::managed_account::FFITransactionRecord;
use key_wallet_ffi::types::FFITransactionContext;
use key_wallet_ffi::types::FFIBalance;
use key_wallet_ffi::wallet_manager::wallet_manager_add_wallet_from_mnemonic;
use key_wallet_ffi::FFIError;

Expand Down Expand Up @@ -157,10 +157,10 @@ extern "C" fn on_peers_updated(connected_count: u32, best_height: u32, _user_dat
// Wallet Event Callbacks
// ============================================================================

extern "C" fn on_transaction_received(
extern "C" fn on_mempool_transaction_received(
wallet_id: *const c_char,
account_index: u32,
record: *const FFITransactionRecord,
balance: *const FFIBalance,
_user_data: *mut c_void,
) {
let wallet_str = ffi_string_to_rust(wallet_id);
Expand All @@ -170,36 +170,54 @@ extern "C" fn on_transaction_received(
&wallet_str
};
if record.is_null() {
println!(
"[Wallet] TX received: wallet={}..., account={}, record=null",
wallet_short, account_index
);
println!("[Wallet] Mempool TX received: wallet={}..., record=null", wallet_short);
return;
}
let r = unsafe { &*record };
let b = if balance.is_null() {
FFIBalance::default()
} else {
unsafe { *balance }
};
let txid_hex = hex::encode(r.txid);
println!(
"[Wallet] TX received: wallet={}..., txid={}, account={}, amount={} duffs, tx_size={}",
wallet_short, txid_hex, account_index, r.net_amount, r.tx_len
"[Wallet] Mempool TX received: wallet={}..., txid={}, amount={} duffs, balance[confirmed={}, unconfirmed={}]",
wallet_short, txid_hex, r.net_amount, b.confirmed, b.unconfirmed
);
}

extern "C" fn on_transaction_status_changed(
_wallet_id: *const c_char,
extern "C" fn on_transaction_instant_send_locked(
wallet_id: *const c_char,
txid: *const [u8; 32],
status: FFITransactionContext,
_islock_data: *const u8,
_islock_len: usize,
balance: *const FFIBalance,
_user_data: *mut c_void,
) {
let wallet_str = ffi_string_to_rust(wallet_id);
let wallet_short = if wallet_str.len() > 8 {
&wallet_str[..8]
} else {
&wallet_str
};
let txid_hex = unsafe { hex::encode(*txid) };
println!("[Wallet] TX status changed: txid={}, status={:?}", txid_hex, status);
let b = if balance.is_null() {
FFIBalance::default()
} else {
unsafe { *balance }
};
println!(
"[Wallet] IS lock: wallet={}..., txid={}, balance[confirmed={}]",
wallet_short, txid_hex, b.confirmed
);
}

extern "C" fn on_balance_updated(
extern "C" fn on_block_process_change(
wallet_id: *const c_char,
spendable: u64,
unconfirmed: u64,
immature: u64,
locked: u64,
height: u32,
_transactions_updated: *const FFITransactionRecord,
record_count: u32,
balance: *const FFIBalance,
_user_data: *mut c_void,
) {
let wallet_str = ffi_string_to_rust(wallet_id);
Expand All @@ -208,9 +226,14 @@ extern "C" fn on_balance_updated(
} else {
&wallet_str
};
let b = if balance.is_null() {
FFIBalance::default()
} else {
unsafe { *balance }
};
println!(
"[Wallet] Balance updated: wallet={}..., spendable={}, unconfirmed={}, immature={}, locked={}",
wallet_short, spendable, unconfirmed, immature, locked
"[Wallet] Block processed: wallet={}..., height={}, tx_count={}, balance[confirmed={}, unconfirmed={}, immature={}, locked={}]",
wallet_short, height, record_count, b.confirmed, b.unconfirmed, b.immature, b.locked
);
}

Expand Down Expand Up @@ -435,9 +458,9 @@ fn main() {
user_data: ptr::null_mut(),
},
wallet: FFIWalletEventCallbacks {
on_transaction_received: Some(on_transaction_received),
on_transaction_status_changed: Some(on_transaction_status_changed),
on_balance_updated: Some(on_balance_updated),
on_mempool_transaction_received: Some(on_mempool_transaction_received),
on_transaction_instant_send_locked: Some(on_transaction_instant_send_locked),
on_block_process_change: Some(on_block_process_change),
user_data: ptr::null_mut(),
},
error: FFIClientErrorCallback {
Expand Down
123 changes: 70 additions & 53 deletions dash-spv-ffi/src/callbacks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,9 @@ use dash_spv::sync::{SyncEvent, SyncProgress};
use dash_spv::EventHandler;
use dashcore::hashes::Hash;
use key_wallet_ffi::managed_account::FFITransactionRecord;
use key_wallet_ffi::types::FFITransactionContext;
use key_wallet_ffi::types::FFIBalance;
use key_wallet_manager::WalletEvent;
use std::ffi::CString;
use std::ops::Deref;
use std::os::raw::{c_char, c_void};

// ============================================================================
Expand Down Expand Up @@ -530,46 +529,57 @@ impl FFINetworkEventCallbacks {
// FFIWalletEventCallbacks - One callback per WalletEvent variant
// ============================================================================

/// Callback for WalletEvent::TransactionReceived
/// Callback for `WalletEvent::MempoolTransactionReceived`.
///
/// The `record` pointer is borrowed and only valid for the duration of the
/// callback. Callers must copy any data they need to retain after the callback
/// returns. The record contains all transaction details including serialized
/// transaction bytes, input/output details, and classification metadata.
pub type OnTransactionReceivedCallback = Option<
/// Fires when a wallet-relevant transaction is first seen in the mempool
/// (optionally with an InstantSend lock — in that case the record's context
/// is `InstantSend(..)`).
///
/// All pointer parameters are borrowed and only valid for the duration of the
/// callback. Callers must copy any data they need to retain. `balance` is the
/// wallet's balance *after* the transaction was recorded.
pub type OnMempoolTransactionReceivedCallback = Option<
extern "C" fn(
wallet_id: *const c_char,
account_index: u32,
record: *const FFITransactionRecord,
balance: *const FFIBalance,
user_data: *mut c_void,
),
>;

/// Callback for WalletEvent::TransactionStatusChanged
/// Callback for `WalletEvent::TransactionInstantSendLocked`.
///
/// The `wallet_id` string pointer and `txid` hash pointer are borrowed and only
/// valid for the duration of the callback.
pub type OnTransactionStatusChangedCallback = Option<
/// Fires when a previously-seen wallet-relevant transaction is
/// InstantSend-locked. `islock_data` points to consensus-serialized
/// `InstantLock` bytes valid only for the duration of the callback.
pub type OnTransactionInstantSendLockedCallback = Option<
extern "C" fn(
wallet_id: *const c_char,
txid: *const [u8; 32],
status: FFITransactionContext,
islock_data: *const u8,
islock_len: usize,
balance: *const FFIBalance,
user_data: *mut c_void,
),
>;

/// Callback for WalletEvent::BalanceUpdated
/// Callback for `WalletEvent::BlockProcessChange`.
///
/// The `wallet_id` string pointer is borrowed and only valid for the duration
/// of the callback. Callers must copy the string if they need to retain it
/// after the callback returns.
pub type OnBalanceUpdatedCallback = Option<
/// Fires once per wallet affected by a processed block. `transactions_updated`
/// points to an array of `record_count` records that were newly recorded or
/// had their state updated by this block. The array may be empty when only
/// the wallet's balance shifted (e.g. a coinbase maturing). `balance` is the
/// wallet's balance *after* the block was processed.
///
/// The `transactions_updated` array and all its contents are borrowed and
/// only valid for the duration of the callback.
pub type OnBlockProcessChangeCallback = Option<
extern "C" fn(
wallet_id: *const c_char,
confirmed: u64,
unconfirmed: u64,
immature: u64,
locked: u64,
height: u32,
transactions_updated: *const FFITransactionRecord,
record_count: u32,
balance: *const FFIBalance,
user_data: *mut c_void,
),
>;
Expand All @@ -578,15 +588,15 @@ pub type OnBalanceUpdatedCallback = Option<
///
/// Set only the callbacks you're interested in; unset callbacks will be ignored.
///
/// All pointer parameters passed to callbacks (wallet IDs, txids, addresses)
/// are borrowed and only valid for the duration of the callback invocation.
/// Callers must copy any data they need to retain.
/// All pointer parameters passed to callbacks (wallet IDs, txids, records,
/// balances) are borrowed and only valid for the duration of the callback
/// invocation. Callers must copy any data they need to retain.
#[repr(C)]
#[derive(Clone)]
pub struct FFIWalletEventCallbacks {
pub on_transaction_received: OnTransactionReceivedCallback,
pub on_transaction_status_changed: OnTransactionStatusChangedCallback,
pub on_balance_updated: OnBalanceUpdatedCallback,
pub on_mempool_transaction_received: OnMempoolTransactionReceivedCallback,
pub on_transaction_instant_send_locked: OnTransactionInstantSendLockedCallback,
pub on_block_process_change: OnBlockProcessChangeCallback,
pub user_data: *mut c_void,
}

Expand All @@ -597,9 +607,9 @@ unsafe impl Sync for FFIWalletEventCallbacks {}
impl Default for FFIWalletEventCallbacks {
fn default() -> Self {
Self {
on_transaction_received: None,
on_transaction_status_changed: None,
on_balance_updated: None,
on_mempool_transaction_received: None,
on_transaction_instant_send_locked: None,
on_block_process_change: None,
user_data: std::ptr::null_mut(),
}
}
Expand Down Expand Up @@ -694,60 +704,67 @@ impl FFIWalletEventCallbacks {
/// Dispatch a WalletEvent to the appropriate callback.
pub fn dispatch(&self, event: &WalletEvent) {
match event {
WalletEvent::TransactionReceived {
WalletEvent::MempoolTransactionReceived {
wallet_id,
account_index,
record,
balance,
} => {
if let Some(cb) = self.on_transaction_received {
if let Some(cb) = self.on_mempool_transaction_received {
let wallet_id_hex = hex::encode(wallet_id);
let c_wallet_id = CString::new(wallet_id_hex).unwrap_or_default();

let ffi_record = FFITransactionRecord::from(record.deref());
let ffi_record = FFITransactionRecord::from(record.as_ref());
let ffi_balance = FFIBalance::from(*balance);

cb(
c_wallet_id.as_ptr(),
*account_index,
&ffi_record as *const FFITransactionRecord,
&ffi_balance as *const FFIBalance,
self.user_data,
);
}
}
WalletEvent::TransactionStatusChanged {
WalletEvent::TransactionInstantSendLocked {
wallet_id,
txid,
status,
instant_send_lock,
balance,
} => {
if let Some(cb) = self.on_transaction_status_changed {
if let Some(cb) = self.on_transaction_instant_send_locked {
let wallet_id_hex = hex::encode(wallet_id);
let c_wallet_id = CString::new(wallet_id_hex).unwrap_or_default();
let txid_bytes = txid.as_byte_array();
let ffi_ctx = FFITransactionContext::from(status.clone());
let islock_bytes = dashcore::consensus::serialize(instant_send_lock);
let ffi_balance = FFIBalance::from(*balance);

cb(
c_wallet_id.as_ptr(),
txid_bytes as *const [u8; 32],
ffi_ctx,
islock_bytes.as_ptr(),
islock_bytes.len(),
&ffi_balance as *const FFIBalance,
self.user_data,
);
}
}
WalletEvent::BalanceUpdated {
WalletEvent::BlockProcessChange {
wallet_id,
confirmed,
unconfirmed,
immature,
locked,
height,
transactions_updated,
balance,
} => {
if let Some(cb) = self.on_balance_updated {
if let Some(cb) = self.on_block_process_change {
let wallet_id_hex = hex::encode(wallet_id);
let c_wallet_id = CString::new(wallet_id_hex).unwrap_or_default();
let ffi_records: Vec<FFITransactionRecord> =
transactions_updated.iter().map(FFITransactionRecord::from).collect();
let ffi_balance = FFIBalance::from(*balance);

cb(
c_wallet_id.as_ptr(),
*confirmed,
*unconfirmed,
*immature,
*locked,
*height,
ffi_records.as_ptr(),
ffi_records.len() as u32,
&ffi_balance as *const FFIBalance,
self.user_data,
);
}
Expand Down
Loading
Loading