diff --git a/key-wallet-ffi/FFI_API.md b/key-wallet-ffi/FFI_API.md index fd57ee77e..b7fde4f58 100644 --- a/key-wallet-ffi/FFI_API.md +++ b/key-wallet-ffi/FFI_API.md @@ -4,7 +4,7 @@ This document provides a comprehensive reference for all FFI (Foreign Function I **Auto-generated**: This documentation is automatically generated from the source code. Do not edit manually. -**Total Functions**: 259 +**Total Functions**: 257 ## Table of Contents @@ -69,7 +69,7 @@ Functions: 19 ### Wallet Operations -Functions: 64 +Functions: 63 | Function | Description | Module | |----------|-------------|--------| @@ -81,7 +81,6 @@ Functions: 64 | `key_wallet_derive_address_from_seed` | Derive an address from a seed at a specific derivation path # Safety -... | derivation | | `key_wallet_derive_private_key_from_seed` | Derive a private key from a seed at a specific derivation path # Safety -... | derivation | | `managed_core_account_get_parent_wallet_id` | Get the parent wallet ID of a managed account Note: ManagedAccount doesn't... | managed_account | -| `managed_wallet_check_transaction` | Check if a transaction belongs to the wallet This function checks a... | transaction_checking | | `managed_wallet_free` | Free managed wallet info # Safety - `managed_wallet` must be a valid... | managed_wallet | | `managed_wallet_generate_addresses_to_index` | Generate addresses up to a specific index in a pool This ensures that... | address_pool | | `managed_wallet_get_account` | Get a managed account from a managed wallet This function gets a... | managed_account | @@ -273,14 +272,13 @@ Functions: 10 ### Transaction Management -Functions: 14 +Functions: 13 | Function | Description | Module | |----------|-------------|--------| | `transaction_add_input` | Add an input to a transaction # Safety - `tx` must be a valid pointer to an... | transaction | | `transaction_add_output` | Add an output to a transaction # Safety - `tx` must be a valid pointer to... | transaction | | `transaction_bytes_free` | Free transaction bytes # Safety - `tx_bytes` must be a valid pointer... | transaction | -| `transaction_check_result_free` | Free a transaction check result # Safety - `result` must be a valid... | transaction_checking | | `transaction_classify` | Get the transaction classification for routing Returns a string describing... | transaction_checking | | `transaction_create` | Create a new empty transaction # Returns - Pointer to FFITransaction on... | transaction | | `transaction_deserialize` | Deserialize a transaction # Safety - `data` must be a valid pointer to... | transaction | @@ -852,22 +850,6 @@ Get the parent wallet ID of a managed account Note: ManagedAccount doesn't stor --- -#### `managed_wallet_check_transaction` - -```c -managed_wallet_check_transaction(managed_wallet: *mut FFIManagedWalletInfo, wallet: *mut FFIWallet, tx_bytes: *const u8, tx_len: usize, context_type: FFITransactionContextType, block_info: FFIBlockInfo, islock_data: *const u8, islock_len: usize, update_state: bool, result_out: *mut FFITransactionCheckResult, error: *mut FFIError,) -> bool -``` - -**Description:** -Check if a transaction belongs to the wallet This function checks a transaction against all relevant account types in the wallet and returns detailed information about which accounts are affected. # Safety - `managed_wallet` must be a valid pointer to an FFIManagedWalletInfo - `wallet` must be a valid pointer to an FFIWallet (needed for address generation and DashPay queries) - `tx_bytes` must be a valid pointer to transaction bytes with at least `tx_len` bytes - `result_out` must be a valid pointer to store the result - `error` must be a valid pointer to an FFIError - The affected_accounts array in the result must be freed with `transaction_check_result_free` - -**Safety:** -- `managed_wallet` must be a valid pointer to an FFIManagedWalletInfo - `wallet` must be a valid pointer to an FFIWallet (needed for address generation and DashPay queries) - `tx_bytes` must be a valid pointer to transaction bytes with at least `tx_len` bytes - `result_out` must be a valid pointer to store the result - `error` must be a valid pointer to an FFIError - The affected_accounts array in the result must be freed with `transaction_check_result_free` - -**Module:** `transaction_checking` - ---- - #### `managed_wallet_free` ```c @@ -3622,22 +3604,6 @@ Free transaction bytes # Safety - `tx_bytes` must be a valid pointer created b --- -#### `transaction_check_result_free` - -```c -transaction_check_result_free(result: *mut FFITransactionCheckResult) -> () -``` - -**Description:** -Free a transaction check result # Safety - `result` must be a valid pointer to an FFITransactionCheckResult - This function must only be called once per result - -**Safety:** -- `result` must be a valid pointer to an FFITransactionCheckResult - This function must only be called once per result - -**Module:** `transaction_checking` - ---- - #### `transaction_classify` ```c diff --git a/key-wallet-ffi/src/transaction_checking.rs b/key-wallet-ffi/src/transaction_checking.rs index 2c2081f6c..fe25cf9f2 100644 --- a/key-wallet-ffi/src/transaction_checking.rs +++ b/key-wallet-ffi/src/transaction_checking.rs @@ -5,64 +5,17 @@ //! routing, classification, and account matching. use std::ffi::CString; -use std::os::raw::{c_char, c_uint}; +use std::os::raw::c_char; use std::slice; -use crate::error::{FFIError, FFIErrorCode}; +use crate::error::FFIError; use crate::managed_wallet::{managed_wallet_info_free, FFIManagedWalletInfo}; -use crate::types::{ - transaction_context_from_ffi, FFIBlockInfo, FFITransactionContextType, FFIWallet, -}; -use crate::{check_ptr, deref_ptr, deref_ptr_mut, unwrap_or_return}; +use crate::types::FFIWallet; +use crate::{check_ptr, deref_ptr, unwrap_or_return}; use dashcore::consensus::Decodable; use dashcore::Transaction; -use key_wallet::transaction_checking::{ - account_checker::CoreAccountTypeMatch, TransactionContext, WalletTransactionChecker, -}; use key_wallet::wallet::managed_wallet_info::ManagedWalletInfo; -// Transaction context for checking -// FFITransactionContextType is imported from types module at the top -/// Account type match result -#[repr(C)] -pub struct FFIAccountMatch { - /// Account type ID (matches FFIAccountType enum values) - pub account_type: c_uint, - /// Account index (if applicable) - pub account_index: c_uint, - /// Registration index for identity top-up (if applicable) - pub registration_index: c_uint, - /// Amount received by this account - pub received: u64, - /// Amount sent from this account - pub sent: u64, - /// Number of external addresses involved - pub external_addresses_count: c_uint, - /// Number of internal addresses involved - pub internal_addresses_count: c_uint, - /// Whether external addresses were involved - pub has_external_addresses: bool, - /// Whether internal addresses were involved - pub has_internal_addresses: bool, -} - -/// Transaction check result -#[repr(C)] -pub struct FFITransactionCheckResult { - /// Whether the transaction belongs to the wallet - pub is_relevant: bool, - /// Total amount received across all accounts - pub total_received: u64, - /// Total amount sent across all accounts - pub total_sent: u64, - /// Total amount received for credit conversion - pub total_received_for_credit_conversion: u64, - /// Array of affected accounts (must be freed) - pub affected_accounts: *mut FFIAccountMatch, - /// Number of affected accounts - pub affected_accounts_count: c_uint, -} - /// Create a managed wallet from a regular wallet /// /// This creates a ManagedWalletInfo instance from a Wallet, which includes @@ -83,400 +36,6 @@ pub unsafe extern "C" fn wallet_create_managed_wallet( Box::into_raw(Box::new(FFIManagedWalletInfo::new(managed_info))) } -/// Check if a transaction belongs to the wallet -/// -/// This function checks a transaction against all relevant account types in the wallet -/// and returns detailed information about which accounts are affected. -/// -/// # Safety -/// -/// - `managed_wallet` must be a valid pointer to an FFIManagedWalletInfo -/// - `wallet` must be a valid pointer to an FFIWallet (needed for address generation and DashPay queries) -/// - `tx_bytes` must be a valid pointer to transaction bytes with at least `tx_len` bytes -/// - `result_out` must be a valid pointer to store the result -/// - `error` must be a valid pointer to an FFIError -/// - The affected_accounts array in the result must be freed with `transaction_check_result_free` -#[no_mangle] -pub unsafe extern "C" fn managed_wallet_check_transaction( - managed_wallet: *mut FFIManagedWalletInfo, - wallet: *mut FFIWallet, - tx_bytes: *const u8, - tx_len: usize, - context_type: FFITransactionContextType, - block_info: FFIBlockInfo, - islock_data: *const u8, - islock_len: usize, - update_state: bool, - result_out: *mut FFITransactionCheckResult, - error: *mut FFIError, -) -> bool { - let managed_wallet: &mut ManagedWalletInfo = deref_ptr_mut!(managed_wallet, error).inner_mut(); - check_ptr!(tx_bytes, error); - check_ptr!(result_out, error); - - let tx_slice = slice::from_raw_parts(tx_bytes, tx_len); - - let tx = unwrap_or_return!(Transaction::consensus_decode(&mut &tx_slice[..]), error); - - // Build the transaction context - let context = match transaction_context_from_ffi( - context_type, - &block_info, - islock_data, - islock_len, - ) { - Some(ctx) => ctx, - None => { - (*error).set(FFIErrorCode::InvalidInput, "Invalid transaction context: block info is zeroed for a confirmed context, or IS lock data is missing/malformed for InstantSend"); - return false; - } - }; - - if let TransactionContext::InstantSend(ref lock) = context { - if lock.txid != tx.txid() { - (*error).set(FFIErrorCode::InvalidInput, "InstantLock txid does not match transaction"); - return false; - } - } - - let wallet_mut = match deref_ptr_mut!(wallet, error).inner_mut() { - Some(w) => w, - None => { - (*error).set( - FFIErrorCode::InternalError, - "Cannot get mutable wallet reference (Arc has multiple owners)", - ); - return false; - } - }; - - // Block on the async check_transaction call - let check_result = tokio::runtime::Handle::current().block_on( - managed_wallet.check_core_transaction(&tx, context, wallet_mut, update_state, true), - ); - - // Convert the result to FFI format - let affected_accounts = if check_result.affected_accounts.is_empty() { - std::ptr::null_mut() - } else { - let mut ffi_accounts = Vec::with_capacity(check_result.affected_accounts.len()); - - for account_match in &check_result.affected_accounts { - match &account_match.account_type_match { - CoreAccountTypeMatch::StandardBIP44 { - account_index, - involved_receive_addresses, - involved_change_addresses, - } => { - let external_count = involved_receive_addresses.len() as c_uint; - let internal_count = involved_change_addresses.len() as c_uint; - let ffi_match = FFIAccountMatch { - account_type: 0, // StandardBIP44 - account_index: *account_index, - registration_index: 0, - received: account_match.received, - sent: account_match.sent, - external_addresses_count: external_count, - internal_addresses_count: internal_count, - has_external_addresses: external_count > 0, - has_internal_addresses: internal_count > 0, - }; - ffi_accounts.push(ffi_match); - continue; - } - CoreAccountTypeMatch::StandardBIP32 { - account_index, - involved_receive_addresses, - involved_change_addresses, - } => { - let external_count = involved_receive_addresses.len() as c_uint; - let internal_count = involved_change_addresses.len() as c_uint; - let ffi_match = FFIAccountMatch { - account_type: 1, // StandardBIP32 - account_index: *account_index, - registration_index: 0, - received: account_match.received, - sent: account_match.sent, - external_addresses_count: external_count, - internal_addresses_count: internal_count, - has_external_addresses: external_count > 0, - has_internal_addresses: internal_count > 0, - }; - ffi_accounts.push(ffi_match); - continue; - } - CoreAccountTypeMatch::CoinJoin { - account_index, - involved_addresses, - } => { - let ffi_match = FFIAccountMatch { - account_type: 2, // CoinJoin - account_index: *account_index, - registration_index: 0, - received: account_match.received, - sent: account_match.sent, - external_addresses_count: involved_addresses.len() as c_uint, - internal_addresses_count: 0, - has_external_addresses: !involved_addresses.is_empty(), - has_internal_addresses: false, - }; - ffi_accounts.push(ffi_match); - continue; - } - CoreAccountTypeMatch::IdentityRegistration { - involved_addresses, - } => { - let ffi_match = FFIAccountMatch { - account_type: 3, // IdentityRegistration - account_index: 0, - registration_index: 0, - received: account_match.received, - sent: account_match.sent, - external_addresses_count: involved_addresses.len() as c_uint, - internal_addresses_count: 0, - has_external_addresses: !involved_addresses.is_empty(), - has_internal_addresses: false, - }; - ffi_accounts.push(ffi_match); - continue; - } - CoreAccountTypeMatch::IdentityTopUp { - account_index, - involved_addresses, - } => { - let ffi_match = FFIAccountMatch { - account_type: 4, // IdentityTopUp - account_index: 0, - registration_index: *account_index, - received: account_match.received, - sent: account_match.sent, - external_addresses_count: involved_addresses.len() as c_uint, - internal_addresses_count: 0, - has_external_addresses: !involved_addresses.is_empty(), - has_internal_addresses: false, - }; - ffi_accounts.push(ffi_match); - continue; - } - CoreAccountTypeMatch::IdentityTopUpNotBound { - involved_addresses, - } => { - let ffi_match = FFIAccountMatch { - account_type: 5, // IdentityTopUpNotBound - account_index: 0, - registration_index: 0, - received: account_match.received, - sent: account_match.sent, - external_addresses_count: involved_addresses.len() as c_uint, - internal_addresses_count: 0, - has_external_addresses: !involved_addresses.is_empty(), - has_internal_addresses: false, - }; - ffi_accounts.push(ffi_match); - continue; - } - CoreAccountTypeMatch::IdentityInvitation { - involved_addresses, - } => { - let ffi_match = FFIAccountMatch { - account_type: 6, // IdentityInvitation - account_index: 0, - registration_index: 0, - received: account_match.received, - sent: account_match.sent, - external_addresses_count: involved_addresses.len() as c_uint, - internal_addresses_count: 0, - has_external_addresses: !involved_addresses.is_empty(), - has_internal_addresses: false, - }; - ffi_accounts.push(ffi_match); - continue; - } - CoreAccountTypeMatch::AssetLockAddressTopUp { - involved_addresses, - } => { - let ffi_match = FFIAccountMatch { - account_type: 14, // AssetLockAddressTopUp - account_index: 0, - registration_index: 0, - received: account_match.received, - sent: account_match.sent, - external_addresses_count: involved_addresses.len() as c_uint, - internal_addresses_count: 0, - has_external_addresses: !involved_addresses.is_empty(), - has_internal_addresses: false, - }; - ffi_accounts.push(ffi_match); - continue; - } - CoreAccountTypeMatch::AssetLockShieldedAddressTopUp { - involved_addresses, - } => { - let ffi_match = FFIAccountMatch { - account_type: 15, // AssetLockShieldedAddressTopUp - account_index: 0, - registration_index: 0, - received: account_match.received, - sent: account_match.sent, - external_addresses_count: involved_addresses.len() as c_uint, - internal_addresses_count: 0, - has_external_addresses: !involved_addresses.is_empty(), - has_internal_addresses: false, - }; - ffi_accounts.push(ffi_match); - continue; - } - CoreAccountTypeMatch::ProviderVotingKeys { - involved_addresses, - } => { - let ffi_match = FFIAccountMatch { - account_type: 7, // ProviderVotingKeys - account_index: 0, - registration_index: 0, - received: account_match.received, - sent: account_match.sent, - external_addresses_count: involved_addresses.len() as c_uint, - internal_addresses_count: 0, - has_external_addresses: !involved_addresses.is_empty(), - has_internal_addresses: false, - }; - ffi_accounts.push(ffi_match); - continue; - } - CoreAccountTypeMatch::ProviderOwnerKeys { - involved_addresses, - } => { - let ffi_match = FFIAccountMatch { - account_type: 8, // ProviderOwnerKeys - account_index: 0, - registration_index: 0, - received: account_match.received, - sent: account_match.sent, - external_addresses_count: involved_addresses.len() as c_uint, - internal_addresses_count: 0, - has_external_addresses: !involved_addresses.is_empty(), - has_internal_addresses: false, - }; - ffi_accounts.push(ffi_match); - continue; - } - CoreAccountTypeMatch::ProviderOperatorKeys { - involved_addresses, - } => { - let ffi_match = FFIAccountMatch { - account_type: 9, // ProviderOperatorKeys - account_index: 0, - registration_index: 0, - received: account_match.received, - sent: account_match.sent, - external_addresses_count: involved_addresses.len() as c_uint, - internal_addresses_count: 0, - has_external_addresses: !involved_addresses.is_empty(), - has_internal_addresses: false, - }; - ffi_accounts.push(ffi_match); - continue; - } - CoreAccountTypeMatch::ProviderPlatformKeys { - involved_addresses, - } => { - let ffi_match = FFIAccountMatch { - account_type: 10, // ProviderPlatformKeys - account_index: 0, - registration_index: 0, - received: account_match.received, - sent: account_match.sent, - external_addresses_count: involved_addresses.len() as c_uint, - internal_addresses_count: 0, - has_external_addresses: !involved_addresses.is_empty(), - has_internal_addresses: false, - }; - ffi_accounts.push(ffi_match); - continue; - } - CoreAccountTypeMatch::DashpayReceivingFunds { - account_index, - involved_addresses, - } => { - let ffi_match = FFIAccountMatch { - account_type: 11, // DashpayReceivingFunds - account_index: *account_index, - registration_index: 0, - received: account_match.received, - sent: account_match.sent, - external_addresses_count: involved_addresses.len() as c_uint, - internal_addresses_count: 0, - has_external_addresses: !involved_addresses.is_empty(), - has_internal_addresses: false, - }; - ffi_accounts.push(ffi_match); - continue; - } - CoreAccountTypeMatch::DashpayExternalAccount { - account_index, - involved_addresses, - } => { - let ffi_match = FFIAccountMatch { - account_type: 12, // DashpayExternalAccount - account_index: *account_index, - registration_index: 0, - received: account_match.received, - sent: account_match.sent, - external_addresses_count: involved_addresses.len() as c_uint, - internal_addresses_count: 0, - has_external_addresses: !involved_addresses.is_empty(), - has_internal_addresses: false, - }; - ffi_accounts.push(ffi_match); - continue; - } - } - } - - // Convert vector to raw array - let _len = ffi_accounts.len(); - let ptr = ffi_accounts.as_mut_ptr(); - std::mem::forget(ffi_accounts); - ptr - }; - - // Fill the result - *result_out = FFITransactionCheckResult { - is_relevant: check_result.is_relevant, - total_received: check_result.total_received, - total_sent: check_result.total_sent, - total_received_for_credit_conversion: check_result.total_received_for_credit_conversion, - affected_accounts, - affected_accounts_count: check_result.affected_accounts.len() as c_uint, - }; - - (*error).clean(); - true -} - -/// Free a transaction check result -/// -/// # Safety -/// -/// - `result` must be a valid pointer to an FFITransactionCheckResult -/// - This function must only be called once per result -#[no_mangle] -pub unsafe extern "C" fn transaction_check_result_free(result: *mut FFITransactionCheckResult) { - if !result.is_null() { - let result = &mut *result; - if !result.affected_accounts.is_null() && result.affected_accounts_count > 0 { - // Reconstruct the vector and drop it - let _ = Vec::from_raw_parts( - result.affected_accounts, - result.affected_accounts_count as usize, - result.affected_accounts_count as usize, - ); - result.affected_accounts = std::ptr::null_mut(); - result.affected_accounts_count = 0; - } - } -} - /// Free a managed wallet (FFIManagedWalletInfo type) /// /// # Safety @@ -516,7 +75,7 @@ pub unsafe extern "C" fn transaction_classify( #[cfg(test)] mod tests { - use super::*; + use crate::types::FFITransactionContextType; #[test] fn test_transaction_context_conversion() {