Skip to content
Merged
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
26 changes: 20 additions & 6 deletions dash-spv-ffi/src/bin/ffi_cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,8 @@ extern "C" fn on_transaction_detected(
wallet_id: *const c_char,
record: *const FFITransactionRecord,
balance: *const FFIBalance,
_account_balances: *const dash_spv_ffi::FFIAccountBalance,
account_balances_count: u32,
_user_data: *mut c_void,
) {
let wallet_short = short_wallet(wallet_id);
Expand All @@ -187,14 +189,15 @@ extern "C" fn on_transaction_detected(
let b = read_balance(balance);
let txid_hex = hex::encode(r.txid);
println!(
"[Wallet] TX detected: wallet={}..., txid={}, account_kind={:?}, account_index={}, amount={} duffs, balance[confirmed={}, unconfirmed={}]",
"[Wallet] TX detected: wallet={}..., txid={}, account_kind={:?}, account_index={}, amount={} duffs, balance[confirmed={}, unconfirmed={}], changed_accounts={}",
wallet_short,
txid_hex,
r.account_type.kind,
r.account_type.index,
r.net_amount,
b.confirmed,
b.unconfirmed
b.unconfirmed,
account_balances_count,
);
}

Expand All @@ -204,6 +207,8 @@ extern "C" fn on_transaction_instant_locked(
_islock_data: *const u8,
islock_len: usize,
balance: *const FFIBalance,
_account_balances: *const dash_spv_ffi::FFIAccountBalance,
account_balances_count: u32,
_user_data: *mut c_void,
) {
let wallet_short = short_wallet(wallet_id);
Expand All @@ -215,11 +220,17 @@ extern "C" fn on_transaction_instant_locked(
let b = read_balance(balance);
let txid_hex = hex::encode(txid_bytes);
println!(
"[Wallet] TX instant-locked: wallet={}..., txid={}, islock_len={}, balance[confirmed={}, unconfirmed={}]",
wallet_short, txid_hex, islock_len, b.confirmed, b.unconfirmed
"[Wallet] TX instant-locked: wallet={}..., txid={}, islock_len={}, balance[confirmed={}, unconfirmed={}], changed_accounts={}",
wallet_short,
txid_hex,
islock_len,
b.confirmed,
b.unconfirmed,
account_balances_count,
);
}

#[allow(clippy::too_many_arguments)]
extern "C" fn on_wallet_block_processed(
wallet_id: *const c_char,
height: u32,
Expand All @@ -230,12 +241,14 @@ extern "C" fn on_wallet_block_processed(
_matured: *const FFITransactionRecord,
matured_count: u32,
balance: *const FFIBalance,
_account_balances: *const dash_spv_ffi::FFIAccountBalance,
account_balances_count: u32,
_user_data: *mut c_void,
) {
let wallet_short = short_wallet(wallet_id);
let b = read_balance(balance);
println!(
"[Wallet] Block processed: wallet={}..., height={}, inserted={}, updated={}, matured={}, balance[confirmed={}, unconfirmed={}, immature={}, locked={}]",
"[Wallet] Block processed: wallet={}..., height={}, inserted={}, updated={}, matured={}, balance[confirmed={}, unconfirmed={}, immature={}, locked={}], changed_accounts={}",
wallet_short,
height,
inserted_count,
Expand All @@ -244,7 +257,8 @@ extern "C" fn on_wallet_block_processed(
b.confirmed,
b.unconfirmed,
b.immature,
b.locked
b.locked,
account_balances_count,
);
}

Expand Down
90 changes: 87 additions & 3 deletions dash-spv-ffi/src/callbacks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,12 @@ use dash_spv::network::NetworkEvent;
use dash_spv::sync::{SyncEvent, SyncProgress};
use dash_spv::EventHandler;
use dashcore::hashes::Hash;
use key_wallet_ffi::managed_account::FFITransactionRecord;
use key_wallet::account::AccountType;
use key_wallet::WalletCoreBalance;
use key_wallet_ffi::managed_account::{FFIAccountType, FFITransactionRecord};
use key_wallet_ffi::types::FFIBalance;
use key_wallet_manager::WalletEvent;
use std::collections::BTreeMap;
use std::ffi::CString;
use std::os::raw::{c_char, c_void};
use std::ptr;
Expand Down Expand Up @@ -528,6 +531,42 @@ impl FFINetworkEventCallbacks {
}
}

// ============================================================================
// FFIAccountBalance - Per-account balance entry
// ============================================================================

/// Per-account balance pair carried on wallet events.
///
/// Wallet events deliver an array of these — one entry per account whose
/// balance changed during the event. Accounts whose balance was unchanged
/// are omitted to keep the payload small (most transactions touch only
/// 1–2 accounts).
///
/// `account_type` follows the same memory rules as the equivalent field on
/// [`FFITransactionRecord`]: the embedded `identity_user` / `identity_friend`
/// pointers (non-null only for Dashpay variants) are owned by the
/// `FFIAccountType` and freed when the array is dropped after the callback
/// returns. Consumers that need to retain the data past the callback must
/// copy the contents.
#[repr(C)]
pub struct FFIAccountBalance {
/// Owning-account descriptor (discriminant + indices + identity ids).
pub account_type: FFIAccountType,
/// Balance for the account after the event.
pub balance: FFIBalance,
}

impl FFIAccountBalance {
fn from_map(map: &BTreeMap<AccountType, WalletCoreBalance>) -> Vec<Self> {
map.iter()
.map(|(account_type, balance)| FFIAccountBalance {
account_type: FFIAccountType::from(account_type),
balance: FFIBalance::from(*balance),
})
.collect()
}
}

// ============================================================================
// FFIWalletEventCallbacks - One callback per WalletEvent variant
// ============================================================================
Expand All @@ -540,12 +579,18 @@ impl FFINetworkEventCallbacks {
///
/// All pointer parameters are borrowed and only valid for the duration of the
/// callback. `balance` is the wallet's balance *after* the transaction was
/// recorded.
/// recorded. `account_balances` is an array of size `account_balances_count`
/// containing one entry per account whose balance changed (typically 1–2
/// entries for a normal transaction); accounts whose balance is unchanged
/// are omitted. The array is null with a zero count when no per-account
/// balance changed.
pub type OnTransactionDetectedCallback = Option<
extern "C" fn(
wallet_id: *const c_char,
record: *const FFITransactionRecord,
balance: *const FFIBalance,
account_balances: *const FFIAccountBalance,
account_balances_count: u32,
user_data: *mut c_void,
),
>;
Expand All @@ -559,13 +604,17 @@ pub type OnTransactionDetectedCallback = Option<
///
/// All pointer parameters are borrowed and only valid for the duration of
/// the callback. `balance` is the wallet's balance *after* the change.
/// `account_balances` follows the same contract as on
/// [`OnTransactionDetectedCallback`].
pub type OnTransactionInstantLockedCallback = Option<
extern "C" fn(
wallet_id: *const c_char,
txid: *const [u8; 32],
islock_data: *const u8,
islock_len: usize,
balance: *const FFIBalance,
account_balances: *const FFIAccountBalance,
account_balances_count: u32,
user_data: *mut c_void,
),
>;
Expand All @@ -577,7 +626,8 @@ pub type OnTransactionInstantLockedCallback = Option<
/// stored, `updated` is previously-known records confirmed, `matured` is
/// older coinbase records whose maturity threshold was just crossed. Empty
/// arrays are passed as null with a zero count. `balance` is the wallet's
/// balance *after* the block was processed.
/// balance *after* the block was processed. `account_balances` follows the
/// same contract as on [`OnTransactionDetectedCallback`].
///
/// All array pointers and their contents are borrowed and only valid for the
/// duration of the callback.
Expand All @@ -592,6 +642,8 @@ pub type OnWalletBlockProcessedCallback = Option<
matured: *const FFITransactionRecord,
matured_count: u32,
balance: *const FFIBalance,
account_balances: *const FFIAccountBalance,
account_balances_count: u32,
user_data: *mut c_void,
),
>;
Expand Down Expand Up @@ -731,42 +783,64 @@ impl FFIWalletEventCallbacks {
wallet_id,
record,
balance,
account_balances,
} => {
if let Some(cb) = self.on_transaction_detected {
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.as_ref());
let ffi_balance = FFIBalance::from(*balance);
let ffi_account_balances = FFIAccountBalance::from_map(account_balances);
let account_balances_ptr = if ffi_account_balances.is_empty() {
ptr::null()
} else {
ffi_account_balances.as_ptr()
};

cb(
c_wallet_id.as_ptr(),
&ffi_record as *const FFITransactionRecord,
&ffi_balance as *const FFIBalance,
account_balances_ptr,
ffi_account_balances.len() as u32,
self.user_data,
);

drop(ffi_account_balances);
}
}
WalletEvent::TransactionInstantLocked {
wallet_id,
txid,
instant_lock,
balance,
account_balances,
} => {
if let Some(cb) = self.on_transaction_instant_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 islock_bytes = dashcore::consensus::serialize(instant_lock);
let ffi_balance = FFIBalance::from(*balance);
let ffi_account_balances = FFIAccountBalance::from_map(account_balances);
let account_balances_ptr = if ffi_account_balances.is_empty() {
ptr::null()
} else {
ffi_account_balances.as_ptr()
};

cb(
c_wallet_id.as_ptr(),
&txid_bytes as *const [u8; 32],
islock_bytes.as_ptr(),
islock_bytes.len(),
&ffi_balance as *const FFIBalance,
account_balances_ptr,
ffi_account_balances.len() as u32,
self.user_data,
);

drop(ffi_account_balances);
}
}
WalletEvent::BlockProcessed {
Expand All @@ -776,6 +850,7 @@ impl FFIWalletEventCallbacks {
updated,
matured,
balance,
account_balances,
} => {
if let Some(cb) = self.on_block_processed {
let wallet_id_hex = hex::encode(wallet_id);
Expand All @@ -787,6 +862,7 @@ impl FFIWalletEventCallbacks {
let ffi_matured: Vec<FFITransactionRecord> =
matured.iter().map(FFITransactionRecord::from).collect();
let ffi_balance = FFIBalance::from(*balance);
let ffi_account_balances = FFIAccountBalance::from_map(account_balances);

// Pass a null pointer when an array is empty so C/Swift
// consumers that null-check before reading don't see a
Expand All @@ -806,6 +882,11 @@ impl FFIWalletEventCallbacks {
} else {
ffi_matured.as_ptr()
};
let account_balances_ptr = if ffi_account_balances.is_empty() {
ptr::null()
} else {
ffi_account_balances.as_ptr()
};

cb(
c_wallet_id.as_ptr(),
Expand All @@ -817,12 +898,15 @@ impl FFIWalletEventCallbacks {
matured_ptr,
ffi_matured.len() as u32,
&ffi_balance as *const FFIBalance,
account_balances_ptr,
ffi_account_balances.len() as u32,
self.user_data,
);

drop(ffi_inserted);
drop(ffi_updated);
drop(ffi_matured);
drop(ffi_account_balances);
}
}
WalletEvent::SyncHeightAdvanced {
Expand Down
Loading
Loading